@hanna84/mcp-writing 3.9.3 → 3.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/structure/structure-inference.js +32 -9
- package/src/sync/sync.js +4 -0
- package/src/tools/metadata.js +18 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,9 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [v3.9.4](https://github.com/hannasdev/mcp-writing/compare/v3.9.3...v3.9.4)
|
|
8
|
+
|
|
9
|
+
- refactor(structure): centralize scene structure patches [`#200`](https://github.com/hannasdev/mcp-writing/pull/200)
|
|
10
|
+
|
|
7
11
|
#### [v3.9.3](https://github.com/hannasdev/mcp-writing/compare/v3.9.2...v3.9.3)
|
|
8
12
|
|
|
13
|
+
> 17 May 2026
|
|
14
|
+
|
|
9
15
|
- refactor(structure): extract target architecture M1 boundaries [`#199`](https://github.com/hannasdev/mcp-writing/pull/199)
|
|
16
|
+
- Release 3.9.3 [`55c87ea`](https://github.com/hannasdev/mcp-writing/commit/55c87ea405841153765144d32c57803b6139a103)
|
|
10
17
|
|
|
11
18
|
#### [v3.9.2](https://github.com/hannasdev/mcp-writing/compare/v3.9.1...v3.9.2)
|
|
12
19
|
|
package/package.json
CHANGED
|
@@ -109,24 +109,35 @@ export function inferChapterStructureFromPath(syncDir, filePath, meta = {}) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export function
|
|
112
|
+
export function buildSceneStructurePatch(syncDir, filePath, meta = {}, { chapter } = {}) {
|
|
113
113
|
const derived = inferScenePositionFromPath(syncDir, filePath);
|
|
114
114
|
const chapterStructure = inferChapterStructureFromPath(syncDir, filePath, meta);
|
|
115
|
-
const
|
|
115
|
+
const patch = {};
|
|
116
116
|
|
|
117
|
-
if (derived.part !== null)
|
|
118
|
-
if (derived.chapter !== null)
|
|
117
|
+
if (derived.part !== null) patch.part = derived.part;
|
|
118
|
+
if (derived.chapter !== null) patch.chapter = derived.chapter;
|
|
119
119
|
if (chapterStructure.chapter?.chapter_id) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
patch.chapter_id = chapterStructure.chapter.chapter_id;
|
|
121
|
+
patch.chapter = chapterStructure.chapter.sort_index;
|
|
122
|
+
patch.chapter_title = chapterStructure.chapter.title;
|
|
123
123
|
}
|
|
124
124
|
if (chapterStructure.role) {
|
|
125
|
-
|
|
125
|
+
patch.scene_role = chapterStructure.role;
|
|
126
|
+
}
|
|
127
|
+
if (chapter !== undefined) {
|
|
128
|
+
if (chapter === null) {
|
|
129
|
+
patch.chapter_id = null;
|
|
130
|
+
patch.chapter = null;
|
|
131
|
+
patch.chapter_title = null;
|
|
132
|
+
} else {
|
|
133
|
+
patch.chapter_id = chapter.chapter_id;
|
|
134
|
+
patch.chapter = chapter.sort_index;
|
|
135
|
+
patch.chapter_title = chapter.title ?? null;
|
|
136
|
+
}
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
return {
|
|
129
|
-
|
|
140
|
+
patch,
|
|
130
141
|
derived,
|
|
131
142
|
chapterStructure,
|
|
132
143
|
mismatches: {
|
|
@@ -135,3 +146,15 @@ export function normalizeSceneMetaForPath(syncDir, filePath, meta = {}) {
|
|
|
135
146
|
},
|
|
136
147
|
};
|
|
137
148
|
}
|
|
149
|
+
|
|
150
|
+
export function applySceneStructurePatch(syncDir, filePath, meta = {}, options = {}) {
|
|
151
|
+
const plan = buildSceneStructurePatch(syncDir, filePath, meta, options);
|
|
152
|
+
return {
|
|
153
|
+
...plan,
|
|
154
|
+
meta: { ...meta, ...plan.patch },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function normalizeSceneMetaForPath(syncDir, filePath, meta = {}) {
|
|
159
|
+
return applySceneStructurePatch(syncDir, filePath, meta);
|
|
160
|
+
}
|
package/src/sync/sync.js
CHANGED
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
import yaml from "js-yaml";
|
|
5
5
|
import {
|
|
6
|
+
applySceneStructurePatch,
|
|
7
|
+
buildSceneStructurePatch,
|
|
6
8
|
inferChapterStructureFromPath,
|
|
7
9
|
inferScenePositionFromPath,
|
|
8
10
|
normalizeSceneMetaForPath,
|
|
@@ -16,6 +18,8 @@ import { indexCanonicalEpigraph } from "../structure/epigraph-indexing.js";
|
|
|
16
18
|
const { load: parseYaml, dump: stringifyYaml } = yaml;
|
|
17
19
|
|
|
18
20
|
export {
|
|
21
|
+
applySceneStructurePatch,
|
|
22
|
+
buildSceneStructurePatch,
|
|
19
23
|
inferChapterStructureFromPath,
|
|
20
24
|
inferScenePositionFromPath,
|
|
21
25
|
normalizeSceneMetaForPath,
|
package/src/tools/metadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
|
-
import { readMeta, writeMeta, indexSceneFile,
|
|
4
|
+
import { readMeta, writeMeta, indexSceneFile, applySceneStructurePatch } from "../sync/sync.js";
|
|
5
5
|
import { validateProjectId, validateUniverseId } from "../sync/importer.js";
|
|
6
6
|
import { resolveValidatedChapterFilter } from "../core/chapter-resolution.js";
|
|
7
7
|
import {
|
|
@@ -545,6 +545,7 @@ export function registerMetadataTools(s, {
|
|
|
545
545
|
try {
|
|
546
546
|
const { meta } = readMeta(scene.file_path, SYNC_DIR, { writable: true });
|
|
547
547
|
const nextFields = { ...fields };
|
|
548
|
+
let chapter = undefined;
|
|
548
549
|
|
|
549
550
|
if (fields.chapter_id === null && fields.chapter !== undefined) {
|
|
550
551
|
return errorResponse(
|
|
@@ -559,8 +560,20 @@ export function registerMetadataTools(s, {
|
|
|
559
560
|
}
|
|
560
561
|
|
|
561
562
|
if (fields.chapter_id === null) {
|
|
562
|
-
|
|
563
|
-
|
|
563
|
+
const structurePlan = applySceneStructurePatch(SYNC_DIR, scene.file_path, meta);
|
|
564
|
+
if (structurePlan.derived.chapter !== null || structurePlan.chapterStructure.chapter?.chapter_id) {
|
|
565
|
+
return errorResponse(
|
|
566
|
+
"VALIDATION_ERROR",
|
|
567
|
+
"chapter_id cannot be cleared for a scene whose file path implies a chapter.",
|
|
568
|
+
{
|
|
569
|
+
project_id,
|
|
570
|
+
scene_id,
|
|
571
|
+
chapter_id: null,
|
|
572
|
+
path_chapter: structurePlan.chapterStructure.chapter?.chapter_id ?? structurePlan.derived.chapter,
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
chapter = null;
|
|
564
577
|
} else if (fields.chapter_id !== undefined || fields.chapter !== undefined) {
|
|
565
578
|
const resolvedChapterFilter = resolveValidatedChapterFilter(db, {
|
|
566
579
|
projectId: project_id,
|
|
@@ -594,12 +607,10 @@ export function registerMetadataTools(s, {
|
|
|
594
607
|
);
|
|
595
608
|
}
|
|
596
609
|
|
|
597
|
-
|
|
598
|
-
nextFields.chapter = resolvedChapter.sort_index;
|
|
599
|
-
nextFields.chapter_title = resolvedChapter.title ?? null;
|
|
610
|
+
chapter = resolvedChapter;
|
|
600
611
|
}
|
|
601
612
|
|
|
602
|
-
const updated =
|
|
613
|
+
const updated = applySceneStructurePatch(SYNC_DIR, scene.file_path, { ...meta, ...nextFields }, { chapter }).meta;
|
|
603
614
|
writeMeta(scene.file_path, updated);
|
|
604
615
|
|
|
605
616
|
const { content: prose } = matter(fs.readFileSync(scene.file_path, "utf8"));
|