@hanna84/mcp-writing 3.21.2 → 3.22.0
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 +14 -0
- package/package.json +1 -1
- package/src/scripts/normalize-scene-characters.mjs +5 -5
- package/src/sync/scene-character-batch.js +13 -4
- package/src/sync/sync.js +29 -9
- package/src/tools/metadata.js +987 -313
- package/src/tools/reference-link-persistence.js +7 -2
- package/src/tools/search.js +84 -44
- package/src/tools/sync.js +20 -7
- package/src/workflows/workflow-catalogue.js +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,9 +4,23 @@ 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.22.0](https://github.com/hannasdev/mcp-writing/compare/v3.21.3...v3.22.0)
|
|
8
|
+
|
|
9
|
+
- feat(metadata): add outcome relationship workflows [`#225`](https://github.com/hannasdev/mcp-writing/pull/225)
|
|
10
|
+
|
|
11
|
+
#### [v3.21.3](https://github.com/hannasdev/mcp-writing/compare/v3.21.2...v3.21.3)
|
|
12
|
+
|
|
13
|
+
> 28 May 2026
|
|
14
|
+
|
|
15
|
+
- fix(metadata): preserve sidecar structure fields on generic writes [`#224`](https://github.com/hannasdev/mcp-writing/pull/224)
|
|
16
|
+
- Release 3.21.3 [`91bc41f`](https://github.com/hannasdev/mcp-writing/commit/91bc41f98e6acf0f46cf93f329e1ecebad2e2305)
|
|
17
|
+
|
|
7
18
|
#### [v3.21.2](https://github.com/hannasdev/mcp-writing/compare/v3.21.1...v3.21.2)
|
|
8
19
|
|
|
20
|
+
> 28 May 2026
|
|
21
|
+
|
|
9
22
|
- docs: accept architecture snapshot milestone [`#223`](https://github.com/hannasdev/mcp-writing/pull/223)
|
|
23
|
+
- Release 3.21.2 [`7f0e627`](https://github.com/hannasdev/mcp-writing/commit/7f0e62741059ad487577e7295ea75564fd64d33a)
|
|
10
24
|
|
|
11
25
|
#### [v3.21.1](https://github.com/hannasdev/mcp-writing/compare/v3.21.0...v3.21.1)
|
|
12
26
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { openDb } from "../core/db.js";
|
|
4
4
|
import { buildCharacterNormalizationContext, normalizeSceneCharacters } from "../sync/scene-character-normalization.js";
|
|
5
|
-
import {
|
|
5
|
+
import { readMeta, readSourceMeta, syncAll, writeMeta } from "../sync/sync.js";
|
|
6
6
|
|
|
7
7
|
function readRequiredValue(argv, index, option) {
|
|
8
8
|
const value = argv[index + 1];
|
|
@@ -138,11 +138,11 @@ function runNormalization({ syncDir, projectId, write, limit }) {
|
|
|
138
138
|
if (!normalized.changed) continue;
|
|
139
139
|
|
|
140
140
|
if (write) {
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const { sourceMeta } = readSourceMeta(scene.file_path, syncDir, { writable: true });
|
|
142
|
+
writeMeta(scene.file_path, {
|
|
143
|
+
...sourceMeta,
|
|
143
144
|
characters: normalized.after,
|
|
144
|
-
})
|
|
145
|
-
writeMeta(scene.file_path, updatedMeta, { syncDir });
|
|
145
|
+
}, { syncDir });
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
changed.push({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import matter from "gray-matter";
|
|
3
3
|
import { buildCharacterNormalizationContext, escapeRegex, resolveCharacterReference } from "./scene-character-normalization.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readSourceMeta, writeMeta } from "./sync.js";
|
|
5
5
|
|
|
6
6
|
function normalizeCharacterRows(rows) {
|
|
7
7
|
return buildCharacterNormalizationContext(rows);
|
|
@@ -138,7 +138,7 @@ export async function runSceneCharacterBatch({ syncDir, args, onProgress, should
|
|
|
138
138
|
try {
|
|
139
139
|
const raw = fs.readFileSync(scene.file_path, "utf8");
|
|
140
140
|
const { content: prose } = matter(raw);
|
|
141
|
-
const { meta } =
|
|
141
|
+
const { sourceMeta: meta } = readSourceMeta(scene.file_path, syncDir, { writable: !dry_run });
|
|
142
142
|
|
|
143
143
|
const before_characters = [...new Set((meta.characters ?? []).map(String).filter(Boolean))];
|
|
144
144
|
const normalized_before_characters = [...new Set(
|
|
@@ -169,10 +169,10 @@ export async function runSceneCharacterBatch({ syncDir, args, onProgress, should
|
|
|
169
169
|
const changed = added.length > 0 || removed.length > 0;
|
|
170
170
|
|
|
171
171
|
if (!dry_run && changed) {
|
|
172
|
-
const updatedMeta =
|
|
172
|
+
const updatedMeta = {
|
|
173
173
|
...meta,
|
|
174
174
|
characters: after_characters,
|
|
175
|
-
}
|
|
175
|
+
};
|
|
176
176
|
|
|
177
177
|
writeMeta(scene.file_path, updatedMeta, { syncDir });
|
|
178
178
|
}
|
|
@@ -234,6 +234,15 @@ export async function runSceneCharacterBatch({ syncDir, args, onProgress, should
|
|
|
234
234
|
cancelled: Boolean(typeof shouldCancel === "function" && shouldCancel() && processed_scenes < targetScenes.length),
|
|
235
235
|
project_id,
|
|
236
236
|
dry_run: Boolean(dry_run),
|
|
237
|
+
relationship_authority: {
|
|
238
|
+
canonical_owner: "SQLite scene_characters",
|
|
239
|
+
compatibility_source: "scene sidecar characters",
|
|
240
|
+
compatibility_mutation_surface: false,
|
|
241
|
+
apply_order: dry_run ? "preview_only" : "compatibility_output_then_sync_index",
|
|
242
|
+
note: dry_run
|
|
243
|
+
? "Dry run only reviews prose-derived character repairs."
|
|
244
|
+
: "M4 retains this batch repair as a prose-derived compatibility path; completion syncs the SQLite relationship index and refreshes backups for changed scenes.",
|
|
245
|
+
},
|
|
237
246
|
total_scenes: targetScenes.length,
|
|
238
247
|
processed_scenes,
|
|
239
248
|
scenes_changed,
|
package/src/sync/sync.js
CHANGED
|
@@ -494,11 +494,12 @@ export function parseFile(filePath) {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
|
-
* Read metadata for a scene file. Priority: sidecar > frontmatter.
|
|
498
|
-
*
|
|
499
|
-
*
|
|
497
|
+
* Read source metadata for a scene file. Priority: sidecar > frontmatter.
|
|
498
|
+
* Does not apply path-derived structural normalization or auto-generate sidecars.
|
|
499
|
+
* Generic metadata writers use this so non-structural updates do not mirror
|
|
500
|
+
* path-derived structure fields into sidecars.
|
|
500
501
|
*/
|
|
501
|
-
export function
|
|
502
|
+
export function readSourceMeta(filePath, syncDir, { writable = false } = {}) {
|
|
502
503
|
const sidecar = sidecarPath(filePath);
|
|
503
504
|
const syncDirAbs = path.resolve(syncDir);
|
|
504
505
|
|
|
@@ -517,26 +518,45 @@ export function readMeta(filePath, syncDir, { writable = false } = {}) {
|
|
|
517
518
|
}
|
|
518
519
|
const raw = fs.readFileSync(sidecar, "utf8");
|
|
519
520
|
const parsed = parseYaml(raw) ?? {};
|
|
520
|
-
return {
|
|
521
|
+
return { meta: parsed, sourceMeta: parsed, source: "sidecar", sidecarGenerated: false };
|
|
521
522
|
}
|
|
522
523
|
|
|
523
524
|
// Fall back to frontmatter
|
|
524
525
|
const { data: frontmatter } = parseFile(filePath);
|
|
525
526
|
if (!Object.keys(frontmatter).length) {
|
|
526
|
-
return {
|
|
527
|
+
return { meta: {}, sourceMeta: {}, source: "empty", sidecarGenerated: false };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return { meta: frontmatter, sourceMeta: frontmatter, source: "frontmatter", sidecarGenerated: false };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Read metadata for a scene file. Priority: sidecar > frontmatter.
|
|
535
|
+
* If sidecar doesn't exist but frontmatter does, auto-generates the sidecar.
|
|
536
|
+
* Returns { meta, sidecarGenerated }.
|
|
537
|
+
*/
|
|
538
|
+
export function readMeta(filePath, syncDir, { writable = false } = {}) {
|
|
539
|
+
const sourceRead = readSourceMeta(filePath, syncDir, { writable });
|
|
540
|
+
|
|
541
|
+
if (sourceRead.source === "sidecar" || sourceRead.source === "empty") {
|
|
542
|
+
return {
|
|
543
|
+
...normalizeSceneMetaForPath(syncDir, filePath, sourceRead.sourceMeta),
|
|
544
|
+
sourceMeta: sourceRead.sourceMeta,
|
|
545
|
+
sidecarGenerated: false,
|
|
546
|
+
};
|
|
527
547
|
}
|
|
528
548
|
|
|
529
|
-
const normalized = normalizeSceneMetaForPath(syncDir, filePath,
|
|
549
|
+
const normalized = normalizeSceneMetaForPath(syncDir, filePath, sourceRead.sourceMeta);
|
|
530
550
|
|
|
531
551
|
// Auto-migrate: write sidecar from frontmatter (only if writable)
|
|
532
552
|
if (writable) {
|
|
533
553
|
try {
|
|
534
554
|
writeMeta(filePath, normalized.meta, { syncDir });
|
|
535
|
-
return { ...normalized, sourceMeta:
|
|
555
|
+
return { ...normalized, sourceMeta: sourceRead.sourceMeta, sidecarGenerated: true };
|
|
536
556
|
} catch { /* empty */ }
|
|
537
557
|
}
|
|
538
558
|
|
|
539
|
-
return { ...normalized, sourceMeta:
|
|
559
|
+
return { ...normalized, sourceMeta: sourceRead.sourceMeta, sidecarGenerated: false };
|
|
540
560
|
}
|
|
541
561
|
|
|
542
562
|
/**
|