@hanna84/mcp-writing 3.23.2 → 3.24.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 +7 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/tools/metadata.js +258 -20
- package/src/tools/search.js +1 -1
- package/src/workflows/workflow-catalogue.js +6 -2
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.24.0](https://github.com/hannasdev/mcp-writing/compare/v3.23.2...v3.24.0)
|
|
8
|
+
|
|
9
|
+
- feat: add one-sided scene evidence workflows [`#233`](https://github.com/hannasdev/mcp-writing/pull/233)
|
|
10
|
+
|
|
7
11
|
#### [v3.23.2](https://github.com/hannasdev/mcp-writing/compare/v3.23.1...v3.23.2)
|
|
8
12
|
|
|
13
|
+
> 6 June 2026
|
|
14
|
+
|
|
9
15
|
- docs: clarify relationship workflow guidance [`#232`](https://github.com/hannasdev/mcp-writing/pull/232)
|
|
16
|
+
- Release 3.23.2 [`a4bd2e9`](https://github.com/hannasdev/mcp-writing/commit/a4bd2e9e957ebaf8b933325d04202411ab0b86e8)
|
|
10
17
|
|
|
11
18
|
#### [v3.23.1](https://github.com/hannasdev/mcp-writing/compare/v3.23.0...v3.23.1)
|
|
12
19
|
|
package/README.md
CHANGED
|
@@ -153,7 +153,7 @@ Outcome: subplot structure stays visible and auditable, which reduces dropped th
|
|
|
153
153
|
Goal: keep indexes accurate without manually re-tagging everything.
|
|
154
154
|
|
|
155
155
|
1. After rewriting scenes, call `enrich_scene` to re-derive lightweight metadata from current prose.
|
|
156
|
-
2. Use `update_scene_metadata` for intentional editorial fields (for example, beat, POV, status, and tags). It rejects scene `characters` and `places`; use `connect_character_place_evidence` when a scene proves paired sheet-backed character/place evidence,
|
|
156
|
+
2. Use `update_scene_metadata` for intentional editorial fields (for example, beat, POV, status, and tags). It rejects scene `characters` and `places`; use `connect_character_place_evidence` when a scene proves paired sheet-backed character/place evidence, `connect_scene_character_evidence` for character-only evidence, and `connect_scene_place_evidence` for place-only evidence. Use `audit_relationship_metadata` for retained sidecar/frontmatter relationship fields. Use `list_chapters` plus `assign_scene_to_chapter` or `move_scene` for chapter placement and ordering.
|
|
157
157
|
3. Use `search_metadata` and `find_scenes` to verify scenes are discoverable under the expected filters.
|
|
158
158
|
|
|
159
159
|
Outcome: your AI assistant can reliably find the right scenes without drifting from the manuscript.
|
package/package.json
CHANGED
package/src/tools/metadata.js
CHANGED
|
@@ -119,9 +119,14 @@ function buildRelationshipMetadataBoundaryDetails({ projectId, sceneId, blockedF
|
|
|
119
119
|
scene_id: sceneId,
|
|
120
120
|
blocked_fields: blockedFields,
|
|
121
121
|
boundary: "scene_relationship_metadata",
|
|
122
|
-
relationship_tools: [
|
|
122
|
+
relationship_tools: [
|
|
123
|
+
"connect_character_place_evidence",
|
|
124
|
+
"connect_scene_character_evidence",
|
|
125
|
+
"connect_scene_place_evidence",
|
|
126
|
+
"audit_relationship_metadata",
|
|
127
|
+
],
|
|
123
128
|
discovery_workflows: ["describe_workflows", "find_scenes", "list_characters", "list_places"],
|
|
124
|
-
next_step: "Use find_scenes, list_characters, and list_places to identify stable IDs. Use connect_character_place_evidence when the scene proves paired sheet-backed character/place evidence
|
|
129
|
+
next_step: "Use find_scenes, list_characters, and list_places to identify stable IDs. Use connect_character_place_evidence when the scene proves paired sheet-backed character/place evidence, connect_scene_character_evidence for character-only evidence, connect_scene_place_evidence for place-only evidence, and audit_relationship_metadata to review legacy sidecar/frontmatter relationship fields.",
|
|
125
130
|
};
|
|
126
131
|
}
|
|
127
132
|
|
|
@@ -526,11 +531,50 @@ function buildSceneMetadataSearchKeywords(meta, relationshipSnapshot) {
|
|
|
526
531
|
}
|
|
527
532
|
|
|
528
533
|
function restoreSceneRelationshipSearchKeywords(db, { sceneId, projectId, meta, snapshot }) {
|
|
529
|
-
db.prepare(`
|
|
530
|
-
|
|
531
|
-
|
|
534
|
+
const existingFts = db.prepare(`
|
|
535
|
+
SELECT logline, title
|
|
536
|
+
FROM scenes_fts
|
|
532
537
|
WHERE scene_id = ? AND project_id = ?
|
|
533
|
-
`).
|
|
538
|
+
`).get(sceneId, projectId);
|
|
539
|
+
try {
|
|
540
|
+
db.exec("SAVEPOINT scene_relationship_fts_refresh");
|
|
541
|
+
db.prepare(`DELETE FROM scenes_fts WHERE scene_id = ? AND project_id = ?`).run(sceneId, projectId);
|
|
542
|
+
db.prepare(`
|
|
543
|
+
INSERT INTO scenes_fts (scene_id, project_id, logline, title, keywords)
|
|
544
|
+
VALUES (?, ?, ?, ?, ?)
|
|
545
|
+
`).run(
|
|
546
|
+
sceneId,
|
|
547
|
+
projectId,
|
|
548
|
+
meta.logline ?? meta.synopsis ?? existingFts?.logline ?? "",
|
|
549
|
+
meta.title ?? existingFts?.title ?? "",
|
|
550
|
+
buildSceneMetadataSearchKeywords(meta, snapshot),
|
|
551
|
+
);
|
|
552
|
+
db.exec("RELEASE scene_relationship_fts_refresh");
|
|
553
|
+
} catch (err) {
|
|
554
|
+
try {
|
|
555
|
+
db.exec("ROLLBACK TO scene_relationship_fts_refresh");
|
|
556
|
+
db.exec("RELEASE scene_relationship_fts_refresh");
|
|
557
|
+
} catch (rollbackErr) {
|
|
558
|
+
void rollbackErr;
|
|
559
|
+
}
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function writeSceneRelationshipCompatibilityOutput({ db, sceneId, projectId, scenePath, syncDir, snapshot }) {
|
|
565
|
+
const { sourceMeta } = readSourceMeta(scenePath, syncDir, { writable: true });
|
|
566
|
+
const nextMeta = {
|
|
567
|
+
...sourceMeta,
|
|
568
|
+
characters: uniqueSorted(snapshot.characters),
|
|
569
|
+
places: uniqueSorted(snapshot.places),
|
|
570
|
+
};
|
|
571
|
+
writeMeta(scenePath, nextMeta, { syncDir });
|
|
572
|
+
restoreSceneRelationshipSearchKeywords(db, {
|
|
573
|
+
sceneId,
|
|
574
|
+
projectId,
|
|
575
|
+
meta: nextMeta,
|
|
576
|
+
snapshot,
|
|
577
|
+
});
|
|
534
578
|
}
|
|
535
579
|
|
|
536
580
|
export function registerMetadataTools(s, {
|
|
@@ -890,12 +934,14 @@ export function registerMetadataTools(s, {
|
|
|
890
934
|
});
|
|
891
935
|
} else {
|
|
892
936
|
try {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
937
|
+
writeSceneRelationshipCompatibilityOutput({
|
|
938
|
+
db,
|
|
939
|
+
sceneId: scene_id,
|
|
940
|
+
projectId: project_id,
|
|
941
|
+
scenePath: scene.file_path,
|
|
942
|
+
syncDir: SYNC_DIR,
|
|
943
|
+
snapshot: after,
|
|
944
|
+
});
|
|
899
945
|
} catch (err) {
|
|
900
946
|
compatibilityDiagnostics.push({
|
|
901
947
|
code: err?.code ?? "COMPATIBILITY_OUTPUT_FAILED",
|
|
@@ -933,6 +979,172 @@ export function registerMetadataTools(s, {
|
|
|
933
979
|
});
|
|
934
980
|
}
|
|
935
981
|
|
|
982
|
+
async function connectOneSidedSceneEvidence({
|
|
983
|
+
project_id,
|
|
984
|
+
scene_id,
|
|
985
|
+
entity_id,
|
|
986
|
+
note,
|
|
987
|
+
}, {
|
|
988
|
+
operation,
|
|
989
|
+
entityKind,
|
|
990
|
+
idField,
|
|
991
|
+
tableName,
|
|
992
|
+
resolveEntity,
|
|
993
|
+
}) {
|
|
994
|
+
if (!SYNC_DIR_WRITABLE) {
|
|
995
|
+
return errorResponse("READ_ONLY", `Cannot connect scene ${entityKind} evidence: sync dir is read-only.`);
|
|
996
|
+
}
|
|
997
|
+
const projectIdCheck = validateProjectId(project_id);
|
|
998
|
+
if (!projectIdCheck.ok) {
|
|
999
|
+
return errorResponse("INVALID_PROJECT_ID", projectIdCheck.reason, { project_id });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const scene = db.prepare(`
|
|
1003
|
+
SELECT scene_id, project_id, file_path
|
|
1004
|
+
FROM scenes
|
|
1005
|
+
WHERE scene_id = ? AND project_id = ?
|
|
1006
|
+
`).get(scene_id, project_id);
|
|
1007
|
+
if (!scene) {
|
|
1008
|
+
return errorResponse("NOT_FOUND", `Scene '${scene_id}' not found in project '${project_id}'.`);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (!resolveEntity(entity_id)) {
|
|
1012
|
+
const label = entityKind[0].toUpperCase() + entityKind.slice(1);
|
|
1013
|
+
return errorResponse("NOT_FOUND", `${label} '${entity_id}' is not indexed for project '${project_id}' or its universe.`);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const before = querySceneRelationshipSnapshot(db, { sceneId: scene_id, projectId: project_id });
|
|
1017
|
+
const alreadyLinked = before[entityKind === "character" ? "characters" : "places"].includes(entity_id);
|
|
1018
|
+
try {
|
|
1019
|
+
db.exec("BEGIN");
|
|
1020
|
+
db.prepare(`
|
|
1021
|
+
INSERT OR IGNORE INTO ${tableName} (scene_id, project_id, ${idField})
|
|
1022
|
+
VALUES (?, ?, ?)
|
|
1023
|
+
`).run(scene_id, project_id, entity_id);
|
|
1024
|
+
db.exec("COMMIT");
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
try {
|
|
1027
|
+
db.exec("ROLLBACK");
|
|
1028
|
+
} catch (rollbackErr) {
|
|
1029
|
+
void rollbackErr;
|
|
1030
|
+
}
|
|
1031
|
+
return errorResponse("IO_ERROR", `Failed to connect scene ${entityKind} evidence for scene '${scene_id}': ${err.message}`);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const after = querySceneRelationshipSnapshot(db, { sceneId: scene_id, projectId: project_id });
|
|
1035
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
1036
|
+
syncDir: SYNC_DIR,
|
|
1037
|
+
projectId: project_id,
|
|
1038
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1039
|
+
operation,
|
|
1040
|
+
actor: createToolActor(operation),
|
|
1041
|
+
affected: {
|
|
1042
|
+
scenes: [scene_id],
|
|
1043
|
+
[`${entityKind}s`]: [entity_id],
|
|
1044
|
+
},
|
|
1045
|
+
summary: `Connected ${entityKind} "${entity_id}" as evidence in scene "${scene_id}".`,
|
|
1046
|
+
before: {
|
|
1047
|
+
scene_relationships: before,
|
|
1048
|
+
},
|
|
1049
|
+
after: {
|
|
1050
|
+
scene_relationships: after,
|
|
1051
|
+
},
|
|
1052
|
+
metadata: {
|
|
1053
|
+
note: note ?? null,
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
const compatibilityDiagnostics = [];
|
|
1058
|
+
if (!scene.file_path) {
|
|
1059
|
+
compatibilityDiagnostics.push({
|
|
1060
|
+
code: "STALE_PATH",
|
|
1061
|
+
severity: "warning",
|
|
1062
|
+
message: `Canonical scene ${entityKind} evidence was committed, but scene '${scene_id}' has no indexed file path for generated compatibility output.`,
|
|
1063
|
+
next_step: "Treat SQLite and project backup artifacts as current. Run sync and inspect the indexed scene path before retrying compatibility output.",
|
|
1064
|
+
details: {
|
|
1065
|
+
scene_id,
|
|
1066
|
+
project_id,
|
|
1067
|
+
indexed_path: null,
|
|
1068
|
+
},
|
|
1069
|
+
});
|
|
1070
|
+
} else {
|
|
1071
|
+
try {
|
|
1072
|
+
writeSceneRelationshipCompatibilityOutput({
|
|
1073
|
+
db,
|
|
1074
|
+
sceneId: scene_id,
|
|
1075
|
+
projectId: project_id,
|
|
1076
|
+
scenePath: scene.file_path,
|
|
1077
|
+
syncDir: SYNC_DIR,
|
|
1078
|
+
snapshot: after,
|
|
1079
|
+
});
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
compatibilityDiagnostics.push({
|
|
1082
|
+
code: err?.code ?? "COMPATIBILITY_OUTPUT_FAILED",
|
|
1083
|
+
severity: "warning",
|
|
1084
|
+
message: `Canonical scene ${entityKind} evidence was committed, but generated scene metadata compatibility output could not be refreshed: ${err.message}`,
|
|
1085
|
+
next_step: "Treat SQLite and project backup artifacts as current. Run sync and inspect the indexed scene path before retrying compatibility output.",
|
|
1086
|
+
details: {
|
|
1087
|
+
scene_id,
|
|
1088
|
+
project_id,
|
|
1089
|
+
indexed_path: scene.file_path,
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return jsonResponse({
|
|
1096
|
+
ok: true,
|
|
1097
|
+
action: "connected",
|
|
1098
|
+
already_linked: alreadyLinked,
|
|
1099
|
+
scene_id,
|
|
1100
|
+
project_id,
|
|
1101
|
+
[idField]: entity_id,
|
|
1102
|
+
note: note ?? null,
|
|
1103
|
+
scene_relationships: after,
|
|
1104
|
+
mutation_order: [
|
|
1105
|
+
"validated_request",
|
|
1106
|
+
"sqlite_commit",
|
|
1107
|
+
"project_backup_refresh",
|
|
1108
|
+
"compatibility_output_refresh",
|
|
1109
|
+
],
|
|
1110
|
+
compatibility_output: buildCompatibilityOutput({
|
|
1111
|
+
refreshed: compatibilityDiagnostics.length === 0,
|
|
1112
|
+
diagnostics: compatibilityDiagnostics,
|
|
1113
|
+
}),
|
|
1114
|
+
...backupMutationFields(backupResult),
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
async function connectSceneCharacterEvidence(args) {
|
|
1119
|
+
return connectOneSidedSceneEvidence({
|
|
1120
|
+
project_id: args.project_id,
|
|
1121
|
+
scene_id: args.scene_id,
|
|
1122
|
+
entity_id: args.character_id,
|
|
1123
|
+
note: args.note,
|
|
1124
|
+
}, {
|
|
1125
|
+
operation: "connect_scene_character_evidence",
|
|
1126
|
+
entityKind: "character",
|
|
1127
|
+
idField: "character_id",
|
|
1128
|
+
tableName: "scene_characters",
|
|
1129
|
+
resolveEntity: (characterId) => resolveCharacterForProject(db, { characterId, projectId: args.project_id }),
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async function connectScenePlaceEvidence(args) {
|
|
1134
|
+
return connectOneSidedSceneEvidence({
|
|
1135
|
+
project_id: args.project_id,
|
|
1136
|
+
scene_id: args.scene_id,
|
|
1137
|
+
entity_id: args.place_id,
|
|
1138
|
+
note: args.note,
|
|
1139
|
+
}, {
|
|
1140
|
+
operation: "connect_scene_place_evidence",
|
|
1141
|
+
entityKind: "place",
|
|
1142
|
+
idField: "place_id",
|
|
1143
|
+
tableName: "scene_places",
|
|
1144
|
+
resolveEntity: (placeId) => resolvePlaceForProject(db, { placeId, projectId: args.project_id }),
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
|
|
936
1148
|
async function recordCharacterRelationshipBeat({
|
|
937
1149
|
project_id,
|
|
938
1150
|
from_character,
|
|
@@ -1120,7 +1332,7 @@ export function registerMetadataTools(s, {
|
|
|
1120
1332
|
project_id: scene.project_id,
|
|
1121
1333
|
compatibility: compatibilityRelationships,
|
|
1122
1334
|
canonical: canonicalRelationships,
|
|
1123
|
-
next_step: "Treat SQLite relationship rows as canonical. Use find_scenes, list_characters, and list_places to inspect stable IDs; use connect_character_place_evidence when evidence is paired,
|
|
1335
|
+
next_step: "Treat SQLite relationship rows as canonical. Use find_scenes, list_characters, and list_places to inspect stable IDs; use connect_character_place_evidence when evidence is paired, connect_scene_character_evidence for character-only evidence, and connect_scene_place_evidence for place-only evidence.",
|
|
1124
1336
|
});
|
|
1125
1337
|
}
|
|
1126
1338
|
}
|
|
@@ -1213,7 +1425,7 @@ export function registerMetadataTools(s, {
|
|
|
1213
1425
|
compatibility_note_count: diagnostics.filter(diagnostic => diagnostic.severity === "info").length,
|
|
1214
1426
|
},
|
|
1215
1427
|
next_steps: [
|
|
1216
|
-
"Use connect_character_place_evidence when scene-backed character/place evidence is paired;
|
|
1428
|
+
"Use connect_character_place_evidence when scene-backed character/place evidence is paired; use connect_scene_character_evidence or connect_scene_place_evidence for one-sided scene evidence.",
|
|
1217
1429
|
"Use record_character_relationship_beat for relationship arcs between characters.",
|
|
1218
1430
|
"Use link_reference_evidence for explicit reference evidence.",
|
|
1219
1431
|
"Use export_project_backup when a fresh recovery snapshot is needed.",
|
|
@@ -1440,7 +1652,7 @@ export function registerMetadataTools(s, {
|
|
|
1440
1652
|
// ---- connect_character_place_evidence ------------------------------------
|
|
1441
1653
|
s.tool(
|
|
1442
1654
|
"connect_character_place_evidence",
|
|
1443
|
-
"Connect a character and place as paired scene-backed story evidence. This outcome-level workflow covers sheet-backed character/place associations: SQLite scene relationship indexes commit first, project backups refresh after commit, and scene sidecar characters/places are
|
|
1655
|
+
"Connect a character and place as paired scene-backed story evidence. This outcome-level workflow covers sheet-backed character/place associations: SQLite scene relationship indexes commit first, project backups refresh after commit, and scene sidecar characters/places are regenerated only as generated compatibility output from canonical indexes. Use connect_scene_character_evidence or connect_scene_place_evidence for one-sided scene evidence.",
|
|
1444
1656
|
{
|
|
1445
1657
|
project_id: z.string().describe("Project the scene belongs to (e.g. 'the-lamb')."),
|
|
1446
1658
|
scene_id: z.string().describe("Scene that provides the evidence for this character/place association."),
|
|
@@ -1451,6 +1663,32 @@ export function registerMetadataTools(s, {
|
|
|
1451
1663
|
async (args) => connectCharacterPlaceEvidence(args)
|
|
1452
1664
|
);
|
|
1453
1665
|
|
|
1666
|
+
// ---- connect_scene_character_evidence -----------------------------------
|
|
1667
|
+
s.tool(
|
|
1668
|
+
"connect_scene_character_evidence",
|
|
1669
|
+
"Connect a sheet-backed character to a scene without requiring paired place evidence. This outcome-level workflow records character-only scene evidence in SQLite first, refreshes project backups after commit, and regenerates scene sidecar characters/places only as generated compatibility output from canonical indexes.",
|
|
1670
|
+
{
|
|
1671
|
+
project_id: z.string().describe("Project the scene belongs to (e.g. 'the-lamb')."),
|
|
1672
|
+
scene_id: z.string().describe("Scene that provides the evidence for this character."),
|
|
1673
|
+
character_id: z.string().describe("Sheet-backed character_id present in the scene. Use list_characters to find valid IDs; freeform names are rejected."),
|
|
1674
|
+
note: z.string().optional().describe("Optional review note explaining the evidence. Stored in operation history, not in compatibility sidecars."),
|
|
1675
|
+
},
|
|
1676
|
+
async (args) => connectSceneCharacterEvidence(args)
|
|
1677
|
+
);
|
|
1678
|
+
|
|
1679
|
+
// ---- connect_scene_place_evidence ---------------------------------------
|
|
1680
|
+
s.tool(
|
|
1681
|
+
"connect_scene_place_evidence",
|
|
1682
|
+
"Connect a sheet-backed place to a scene without requiring paired character evidence. This outcome-level workflow records place-only scene evidence in SQLite first, refreshes project backups after commit, and regenerates scene sidecar characters/places only as generated compatibility output from canonical indexes.",
|
|
1683
|
+
{
|
|
1684
|
+
project_id: z.string().describe("Project the scene belongs to (e.g. 'the-lamb')."),
|
|
1685
|
+
scene_id: z.string().describe("Scene that provides the evidence for this place."),
|
|
1686
|
+
place_id: z.string().describe("Sheet-backed place_id present in the scene. Use list_places to find valid IDs; freeform names are rejected."),
|
|
1687
|
+
note: z.string().optional().describe("Optional review note explaining the evidence. Stored in operation history, not in compatibility sidecars."),
|
|
1688
|
+
},
|
|
1689
|
+
async (args) => connectScenePlaceEvidence(args)
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1454
1692
|
// ---- record_character_relationship_beat ----------------------------------
|
|
1455
1693
|
s.tool(
|
|
1456
1694
|
"record_character_relationship_beat",
|
|
@@ -2344,7 +2582,7 @@ export function registerMetadataTools(s, {
|
|
|
2344
2582
|
// ---- update_scene_metadata -----------------------------------------------
|
|
2345
2583
|
s.tool(
|
|
2346
2584
|
"update_scene_metadata",
|
|
2347
|
-
"Update one or more non-structural, non-relationship metadata fields for a scene. Writes only supplied allowed fields to the .meta.yaml sidecar and preserves existing structural compatibility fields; it never modifies prose, mirrors path-derived structure, or changes scene character/place relationship authority. Structural fields (part, chapter, chapter_id, chapter_title, timeline_position) are rejected here; use list_chapters plus assign_scene_to_chapter, move_scene, rename_chapter, or reorder_chapter for structure changes. Relationship fields (characters, places) are rejected here; use discovery workflows plus connect_character_place_evidence when evidence is paired,
|
|
2585
|
+
"Update one or more non-structural, non-relationship metadata fields for a scene. Writes only supplied allowed fields to the .meta.yaml sidecar and preserves existing structural compatibility fields; it never modifies prose, mirrors path-derived structure, or changes scene character/place relationship authority. Structural fields (part, chapter, chapter_id, chapter_title, timeline_position) are rejected here; use list_chapters plus assign_scene_to_chapter, move_scene, rename_chapter, or reorder_chapter for structure changes. Relationship fields (characters, places) are rejected here; use discovery workflows plus connect_character_place_evidence when evidence is paired, connect_scene_character_evidence for character-only evidence, connect_scene_place_evidence for place-only evidence, and audit_relationship_metadata for legacy sidecar/frontmatter relationship review. Allowed changes are immediately reflected in the index. Only available when the sync dir is writable.",
|
|
2348
2586
|
{
|
|
2349
2587
|
scene_id: z.string().describe("The scene_id to update (e.g. 'sc-011-sebastian')."),
|
|
2350
2588
|
project_id: z.string().describe("Project the scene belongs to (e.g. 'the-lamb')."),
|
|
@@ -2361,8 +2599,8 @@ export function registerMetadataTools(s, {
|
|
|
2361
2599
|
timeline_position: z.number().int().optional().describe("Rejected by update_scene_metadata. Use move_scene for ordering changes."),
|
|
2362
2600
|
story_time: z.string().optional(),
|
|
2363
2601
|
tags: z.array(z.string()).optional(),
|
|
2364
|
-
characters: z.array(z.string()).optional().describe("Rejected by update_scene_metadata. Use find_scenes, list_characters, list_places, connect_character_place_evidence when evidence is paired,
|
|
2365
|
-
places: z.array(z.string()).optional().describe("Rejected by update_scene_metadata. Use find_scenes, list_characters, list_places, connect_character_place_evidence when evidence is paired,
|
|
2602
|
+
characters: z.array(z.string()).optional().describe("Rejected by update_scene_metadata. Use find_scenes, list_characters, list_places, connect_character_place_evidence when evidence is paired, connect_scene_character_evidence for character-only evidence, and audit_relationship_metadata for compatibility review."),
|
|
2603
|
+
places: z.array(z.string()).optional().describe("Rejected by update_scene_metadata. Use find_scenes, list_characters, list_places, connect_character_place_evidence when evidence is paired, connect_scene_place_evidence for place-only evidence, and audit_relationship_metadata for compatibility review."),
|
|
2366
2604
|
}).describe("Fields to update. Only supplied keys are changed."),
|
|
2367
2605
|
},
|
|
2368
2606
|
async ({ scene_id, project_id, fields }) => {
|
|
@@ -2607,7 +2845,7 @@ export function registerMetadataTools(s, {
|
|
|
2607
2845
|
// ---- update_place_sheet --------------------------------------------------
|
|
2608
2846
|
s.tool(
|
|
2609
2847
|
"update_place_sheet",
|
|
2610
|
-
"Update canonical place profile fields and retained compatibility notes. The place name commits to SQLite first and refreshes project backups; associated_characters and tags are compatibility/review metadata only. Use connect_character_place_evidence when scene-backed character/place evidence is paired
|
|
2848
|
+
"Update canonical place profile fields and retained compatibility notes. The place name commits to SQLite first and refreshes project backups; associated_characters and tags are compatibility/review metadata only. Use connect_character_place_evidence when scene-backed character/place evidence is paired, or connect_scene_place_evidence when scene evidence is place-only.",
|
|
2611
2849
|
{
|
|
2612
2850
|
place_id: z.string().describe("The place_id to update (e.g. 'place-harbor-district'). Use list_places to find valid IDs."),
|
|
2613
2851
|
fields: z.object({
|
|
@@ -2716,7 +2954,7 @@ export function registerMetadataTools(s, {
|
|
|
2716
2954
|
}),
|
|
2717
2955
|
non_canonical_fields: ["associated_characters", "tags"].filter(field => Object.hasOwn(fields, field)),
|
|
2718
2956
|
next_step: Object.hasOwn(fields, "associated_characters")
|
|
2719
|
-
? "Use connect_character_place_evidence when paired scene-backed character/place evidence should become authoritative;
|
|
2957
|
+
? "Use connect_character_place_evidence when paired scene-backed character/place evidence should become authoritative; use connect_scene_character_evidence or connect_scene_place_evidence for one-sided scene evidence."
|
|
2720
2958
|
: undefined,
|
|
2721
2959
|
...backupMutationFields(backupResult),
|
|
2722
2960
|
});
|
package/src/tools/search.js
CHANGED
|
@@ -609,7 +609,7 @@ export function registerSearchTools(s, {
|
|
|
609
609
|
// ---- get_place_sheet -----------------------------------------------------
|
|
610
610
|
s.tool(
|
|
611
611
|
"get_place_sheet",
|
|
612
|
-
"Get full place details, including canonical sheet content plus retained sidecar associated_characters and tags as compatibility/review notes. Use connect_character_place_evidence when scene-backed character/place evidence is paired
|
|
612
|
+
"Get full place details, including canonical sheet content plus retained sidecar associated_characters and tags as compatibility/review notes. Use connect_character_place_evidence when scene-backed character/place evidence is paired, or connect_scene_place_evidence when scene evidence is place-only. Response shape note: returns a structured envelope (`results`, `total_count`) with one result row.",
|
|
613
613
|
{
|
|
614
614
|
place_id: z.string().describe("The place_id to look up (e.g. 'place-harbor-district'). Use list_places to find valid IDs."),
|
|
615
615
|
},
|
|
@@ -70,7 +70,9 @@ export const WORKFLOW_CATALOGUE = [
|
|
|
70
70
|
steps: [
|
|
71
71
|
{ tool: "find_scenes", note: "Identify scene_id and project_id from story context before recording relationships; use list_characters and list_places when stable entity IDs need disambiguation." },
|
|
72
72
|
{ tool: "track_thread_arc", note: "Use when a scene should carry a storyline, subplot, setup, escalation, reveal, reversal, payoff, or other thread beat." },
|
|
73
|
-
{ tool: "connect_character_place_evidence", note: "Use when a scene proves paired sheet-backed character/place evidence; SQLite scene relationship indexes commit first and sidecar characters/places remain generated compatibility output.
|
|
73
|
+
{ tool: "connect_character_place_evidence", note: "Use when a scene proves paired sheet-backed character/place evidence; SQLite scene relationship indexes commit first and sidecar characters/places remain generated compatibility output." },
|
|
74
|
+
{ tool: "connect_scene_character_evidence", note: "Use when a scene proves sheet-backed character evidence without a specific place association; SQLite scene character links commit first and sidecar characters/places remain generated compatibility output." },
|
|
75
|
+
{ tool: "connect_scene_place_evidence", note: "Use when a scene proves sheet-backed place evidence without a specific character association; SQLite scene place links commit first and sidecar characters/places remain generated compatibility output." },
|
|
74
76
|
{ tool: "record_character_relationship_beat", note: "Use when a scene proves a relationship beat between two characters without exposing the character_relationships table." },
|
|
75
77
|
{ tool: "link_reference_evidence", note: "Use when scene, character, place, or reference evidence should point to a reference document; SQLite commits first and compatibility output is generated transparency." },
|
|
76
78
|
{ tool: "audit_relationship_metadata", note: "Use before repair work to review stale relationship indexes, retained sidecar/frontmatter characters or places, and compatibility drift without mutating canonical state." },
|
|
@@ -103,7 +105,9 @@ export const WORKFLOW_CATALOGUE = [
|
|
|
103
105
|
{ tool: "diagnose_structure", note: "Use when sidecar, frontmatter, folder, chapter, epigraph, or generated-export structure appears to disagree with SQLite canonical state." },
|
|
104
106
|
{ tool: "audit_relationship_metadata", note: "Use when sidecar threads, tags, flags, associated_characters, characters, places, or reference aliases need authority classification before repair; relationship fields are compatibility input/review evidence, not generic metadata writes." },
|
|
105
107
|
{ tool: "track_thread_arc", note: "Use for current thread authority instead of editing sidecar threads." },
|
|
106
|
-
{ tool: "connect_character_place_evidence", note: "Use for paired scene-backed character/place evidence instead of editing sidecar relationship lists or sending characters/places through update_scene_metadata
|
|
108
|
+
{ tool: "connect_character_place_evidence", note: "Use for paired scene-backed character/place evidence instead of editing sidecar relationship lists or sending characters/places through update_scene_metadata." },
|
|
109
|
+
{ tool: "connect_scene_character_evidence", note: "Use for character-only scene evidence instead of editing sidecar characters or sending characters through update_scene_metadata." },
|
|
110
|
+
{ tool: "connect_scene_place_evidence", note: "Use for place-only scene evidence instead of editing sidecar places or sending places through update_scene_metadata." },
|
|
107
111
|
{ tool: "link_reference_evidence", note: "Use for current reference-link authority instead of editing sidecar/frontmatter aliases." },
|
|
108
112
|
{ tool: "export_project_backup", note: "Generate a recovery snapshot from SQLite canonical state after meaningful canonical migration or repair work; editing backup artifacts does not mutate current state." },
|
|
109
113
|
],
|