@hanna84/mcp-writing 3.18.0 → 3.19.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/README.md +2 -2
- package/package.json +1 -1
- package/src/core/filesystem-boundary.js +12 -0
- package/src/index.js +1 -0
- package/src/structure/project-backup-diagnostics.js +346 -0
- package/src/structure/project-backup-operations.js +116 -0
- package/src/structure/project-backup-refresh.js +160 -0
- package/src/structure/project-backup.js +476 -0
- package/src/tools/editing.js +55 -0
- package/src/tools/metadata.js +469 -8
- package/src/tools/search.js +34 -0
- package/src/tools/sync.js +200 -0
- package/src/workflows/workflow-catalogue.js +2 -0
package/src/tools/metadata.js
CHANGED
|
@@ -28,9 +28,58 @@ import {
|
|
|
28
28
|
upsertExplicitReferenceLinkRow,
|
|
29
29
|
upsertSerializedReferenceLinks,
|
|
30
30
|
} from "./reference-link-persistence.js";
|
|
31
|
+
import {
|
|
32
|
+
createToolActor,
|
|
33
|
+
refreshProjectBackupAfterMutation,
|
|
34
|
+
} from "../structure/project-backup-refresh.js";
|
|
31
35
|
|
|
32
36
|
const STRUCTURAL_SCENE_METADATA_FIELDS = ["part", "chapter", "chapter_id", "timeline_position"];
|
|
33
37
|
|
|
38
|
+
function emptyBackupMutationResult() {
|
|
39
|
+
return {
|
|
40
|
+
operation_history: null,
|
|
41
|
+
backup_refresh: null,
|
|
42
|
+
backup_warnings: [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function refreshProjectScopedBackupAfterMutation(db, {
|
|
47
|
+
syncDir,
|
|
48
|
+
projectId,
|
|
49
|
+
applicationVersion,
|
|
50
|
+
operation,
|
|
51
|
+
actor,
|
|
52
|
+
affected,
|
|
53
|
+
before,
|
|
54
|
+
after,
|
|
55
|
+
summary,
|
|
56
|
+
metadata,
|
|
57
|
+
}) {
|
|
58
|
+
if (!projectId) {
|
|
59
|
+
return emptyBackupMutationResult();
|
|
60
|
+
}
|
|
61
|
+
return refreshProjectBackupAfterMutation(db, {
|
|
62
|
+
syncDir,
|
|
63
|
+
projectId,
|
|
64
|
+
applicationVersion,
|
|
65
|
+
operation,
|
|
66
|
+
actor,
|
|
67
|
+
affected,
|
|
68
|
+
before,
|
|
69
|
+
after,
|
|
70
|
+
summary,
|
|
71
|
+
metadata,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function backupMutationFields(backupResult) {
|
|
76
|
+
return {
|
|
77
|
+
operation_history: backupResult.operation_history,
|
|
78
|
+
backup_refresh: backupResult.backup_refresh,
|
|
79
|
+
backup_warnings: backupResult.backup_warnings,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
34
83
|
function getProvidedStructuralSceneMetadataFields(fields) {
|
|
35
84
|
return STRUCTURAL_SCENE_METADATA_FIELDS.filter((field) => Object.hasOwn(fields, field));
|
|
36
85
|
}
|
|
@@ -317,6 +366,7 @@ export function registerMetadataTools(s, {
|
|
|
317
366
|
db,
|
|
318
367
|
SYNC_DIR,
|
|
319
368
|
SYNC_DIR_WRITABLE,
|
|
369
|
+
MCP_SERVER_VERSION = "0.0.0",
|
|
320
370
|
errorResponse,
|
|
321
371
|
jsonResponse,
|
|
322
372
|
createCanonicalWorldEntity,
|
|
@@ -364,8 +414,34 @@ export function registerMetadataTools(s, {
|
|
|
364
414
|
universeId: universe_id,
|
|
365
415
|
meta: fields ?? {},
|
|
366
416
|
});
|
|
417
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
418
|
+
syncDir: SYNC_DIR,
|
|
419
|
+
projectId: project_id,
|
|
420
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
421
|
+
operation: "create_character_sheet",
|
|
422
|
+
actor: createToolActor("create_character_sheet"),
|
|
423
|
+
affected: {
|
|
424
|
+
characters: [result.id],
|
|
425
|
+
},
|
|
426
|
+
summary: `${result.created ? "Created" : "Reused"} character sheet "${result.id}".`,
|
|
427
|
+
before: null,
|
|
428
|
+
after: {
|
|
429
|
+
character: {
|
|
430
|
+
character_id: result.id,
|
|
431
|
+
project_id: result.project_id,
|
|
432
|
+
universe_id: result.universe_id,
|
|
433
|
+
name,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
});
|
|
367
437
|
|
|
368
|
-
return jsonResponse({
|
|
438
|
+
return jsonResponse({
|
|
439
|
+
ok: true,
|
|
440
|
+
action: result.created ? "created" : "exists",
|
|
441
|
+
kind: "character",
|
|
442
|
+
...result,
|
|
443
|
+
...backupMutationFields(backupResult),
|
|
444
|
+
});
|
|
369
445
|
} catch (err) {
|
|
370
446
|
if (err?.name === "CoreValidationError") {
|
|
371
447
|
return errorResponse(err.code, err.message, err.details);
|
|
@@ -416,8 +492,34 @@ export function registerMetadataTools(s, {
|
|
|
416
492
|
universeId: universe_id,
|
|
417
493
|
meta: fields ?? {},
|
|
418
494
|
});
|
|
495
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
496
|
+
syncDir: SYNC_DIR,
|
|
497
|
+
projectId: project_id,
|
|
498
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
499
|
+
operation: "create_place_sheet",
|
|
500
|
+
actor: createToolActor("create_place_sheet"),
|
|
501
|
+
affected: {
|
|
502
|
+
places: [result.id],
|
|
503
|
+
},
|
|
504
|
+
summary: `${result.created ? "Created" : "Reused"} place sheet "${result.id}".`,
|
|
505
|
+
before: null,
|
|
506
|
+
after: {
|
|
507
|
+
place: {
|
|
508
|
+
place_id: result.id,
|
|
509
|
+
project_id: result.project_id,
|
|
510
|
+
universe_id: result.universe_id,
|
|
511
|
+
name,
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
});
|
|
419
515
|
|
|
420
|
-
return jsonResponse({
|
|
516
|
+
return jsonResponse({
|
|
517
|
+
ok: true,
|
|
518
|
+
action: result.created ? "created" : "exists",
|
|
519
|
+
kind: "place",
|
|
520
|
+
...result,
|
|
521
|
+
...backupMutationFields(backupResult),
|
|
522
|
+
});
|
|
421
523
|
} catch (err) {
|
|
422
524
|
if (err?.name === "CoreValidationError") {
|
|
423
525
|
return errorResponse(err.code, err.message, err.details);
|
|
@@ -443,6 +545,10 @@ export function registerMetadataTools(s, {
|
|
|
443
545
|
if (!SYNC_DIR_WRITABLE) {
|
|
444
546
|
return errorResponse("READ_ONLY", "Cannot write thread links: sync dir is read-only.");
|
|
445
547
|
}
|
|
548
|
+
const projectIdCheck = validateProjectId(project_id);
|
|
549
|
+
if (!projectIdCheck.ok) {
|
|
550
|
+
return errorResponse("INVALID_PROJECT_ID", projectIdCheck.reason, { project_id });
|
|
551
|
+
}
|
|
446
552
|
|
|
447
553
|
const existingThread = db.prepare(`SELECT thread_id, project_id FROM threads WHERE thread_id = ?`).get(thread_id);
|
|
448
554
|
if (existingThread && existingThread.project_id !== project_id) {
|
|
@@ -475,12 +581,30 @@ export function registerMetadataTools(s, {
|
|
|
475
581
|
const thread = db.prepare(`SELECT * FROM threads WHERE thread_id = ?`).get(thread_id);
|
|
476
582
|
const link = db.prepare(`SELECT scene_id, project_id, thread_id, beat FROM scene_threads WHERE scene_id = ? AND project_id = ? AND thread_id = ?`)
|
|
477
583
|
.get(scene_id, project_id, thread_id);
|
|
584
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
585
|
+
syncDir: SYNC_DIR,
|
|
586
|
+
projectId: project_id,
|
|
587
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
588
|
+
operation: "upsert_thread_link",
|
|
589
|
+
actor: createToolActor("upsert_thread_link"),
|
|
590
|
+
affected: {
|
|
591
|
+
threads: [thread_id],
|
|
592
|
+
scenes: [scene_id],
|
|
593
|
+
},
|
|
594
|
+
summary: `Upserted thread "${thread_id}" link for scene "${scene_id}".`,
|
|
595
|
+
before: null,
|
|
596
|
+
after: {
|
|
597
|
+
thread,
|
|
598
|
+
link,
|
|
599
|
+
},
|
|
600
|
+
});
|
|
478
601
|
|
|
479
602
|
return jsonResponse({
|
|
480
603
|
ok: true,
|
|
481
604
|
action: "upserted",
|
|
482
605
|
thread,
|
|
483
606
|
link,
|
|
607
|
+
...backupMutationFields(backupResult),
|
|
484
608
|
});
|
|
485
609
|
}
|
|
486
610
|
);
|
|
@@ -511,7 +635,7 @@ export function registerMetadataTools(s, {
|
|
|
511
635
|
}
|
|
512
636
|
|
|
513
637
|
const targetDoc = db.prepare(`
|
|
514
|
-
SELECT doc_id
|
|
638
|
+
SELECT doc_id, project_id
|
|
515
639
|
FROM reference_docs
|
|
516
640
|
WHERE doc_id = ?
|
|
517
641
|
`).get(target_doc_id);
|
|
@@ -623,11 +747,29 @@ export function registerMetadataTools(s, {
|
|
|
623
747
|
FROM reference_links
|
|
624
748
|
WHERE source_kind = ? AND source_project_id = ? AND source_id = ? AND target_doc_id = ? AND relation = ?
|
|
625
749
|
`).get(source_kind, resolvedSourceProjectId, source_id, target_doc_id, normalizedRelation);
|
|
750
|
+
const backupProjectId = resolvedSourceProjectId || targetDoc.project_id || null;
|
|
751
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
752
|
+
syncDir: SYNC_DIR,
|
|
753
|
+
projectId: backupProjectId,
|
|
754
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
755
|
+
operation: "upsert_reference_link",
|
|
756
|
+
actor: createToolActor("upsert_reference_link"),
|
|
757
|
+
affected: {
|
|
758
|
+
reference_docs: [target_doc_id],
|
|
759
|
+
sources: [`${source_kind}:${source_id}`],
|
|
760
|
+
},
|
|
761
|
+
summary: `Upserted ${source_kind} reference link from "${source_id}" to "${target_doc_id}".`,
|
|
762
|
+
before: null,
|
|
763
|
+
after: {
|
|
764
|
+
link,
|
|
765
|
+
},
|
|
766
|
+
});
|
|
626
767
|
|
|
627
768
|
return jsonResponse({
|
|
628
769
|
ok: true,
|
|
629
770
|
action: "upserted",
|
|
630
771
|
link,
|
|
772
|
+
...backupMutationFields(backupResult),
|
|
631
773
|
});
|
|
632
774
|
}
|
|
633
775
|
);
|
|
@@ -683,6 +825,29 @@ export function registerMetadataTools(s, {
|
|
|
683
825
|
return errorResponse("IO_ERROR", `Failed to create chapter '${plan.chapter.chapter_id}': ${err.message}`);
|
|
684
826
|
}
|
|
685
827
|
|
|
828
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
829
|
+
syncDir: SYNC_DIR,
|
|
830
|
+
projectId: project_id,
|
|
831
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
832
|
+
operation: "create_chapter",
|
|
833
|
+
actor: createToolActor("create_chapter"),
|
|
834
|
+
affected: {
|
|
835
|
+
chapters: [plan.chapter.chapter_id],
|
|
836
|
+
},
|
|
837
|
+
summary: `Created chapter "${plan.chapter.title}" at sort_index ${plan.chapter.sort_index}.`,
|
|
838
|
+
before: null,
|
|
839
|
+
after: {
|
|
840
|
+
chapter: {
|
|
841
|
+
chapter_id: plan.chapter.chapter_id,
|
|
842
|
+
project_id: plan.chapter.project_id,
|
|
843
|
+
title: plan.chapter.title,
|
|
844
|
+
sort_index: plan.chapter.sort_index,
|
|
845
|
+
logline: plan.chapter.logline,
|
|
846
|
+
metadata_stale: plan.chapter.metadata_stale,
|
|
847
|
+
},
|
|
848
|
+
},
|
|
849
|
+
});
|
|
850
|
+
|
|
686
851
|
return jsonResponse({
|
|
687
852
|
ok: true,
|
|
688
853
|
action: "created",
|
|
@@ -695,6 +860,9 @@ export function registerMetadataTools(s, {
|
|
|
695
860
|
metadata_stale: plan.chapter.metadata_stale,
|
|
696
861
|
},
|
|
697
862
|
diagnostics: plan.diagnostics,
|
|
863
|
+
operation_history: backupResult.operation_history,
|
|
864
|
+
backup_refresh: backupResult.backup_refresh,
|
|
865
|
+
backup_warnings: backupResult.backup_warnings,
|
|
698
866
|
next_steps: [
|
|
699
867
|
"Use assign_scene_to_chapter to place unchaptered scenes in this chapter.",
|
|
700
868
|
"Run diagnose_structure if existing folders or sidecars may imply conflicting structure.",
|
|
@@ -781,6 +949,39 @@ export function registerMetadataTools(s, {
|
|
|
781
949
|
failureCode: "SCENE_SIDECAR_UPDATE_FAILED",
|
|
782
950
|
syncDir: SYNC_DIR,
|
|
783
951
|
});
|
|
952
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
953
|
+
syncDir: SYNC_DIR,
|
|
954
|
+
projectId: project_id,
|
|
955
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
956
|
+
operation: "rename_chapter",
|
|
957
|
+
actor: createToolActor("rename_chapter"),
|
|
958
|
+
affected: {
|
|
959
|
+
chapters: [plan.chapter.chapter_id],
|
|
960
|
+
scenes: linkedScenes.map(scene => scene.scene_id),
|
|
961
|
+
},
|
|
962
|
+
summary: `Renamed chapter "${plan.previousChapter.title}" to "${plan.chapter.title}".`,
|
|
963
|
+
before: {
|
|
964
|
+
chapter: {
|
|
965
|
+
chapter_id: plan.previousChapter.chapter_id,
|
|
966
|
+
title: plan.previousChapter.title,
|
|
967
|
+
sort_index: plan.previousChapter.sort_index,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
after: {
|
|
971
|
+
chapter: {
|
|
972
|
+
chapter_id: plan.chapter.chapter_id,
|
|
973
|
+
project_id: plan.chapter.project_id,
|
|
974
|
+
title: plan.chapter.title,
|
|
975
|
+
sort_index: plan.chapter.sort_index,
|
|
976
|
+
logline: plan.chapter.logline,
|
|
977
|
+
metadata_stale: plan.chapter.metadata_stale,
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
metadata: {
|
|
981
|
+
updated_scene_count: linkedScenes.length,
|
|
982
|
+
updated_sidecar_count: sidecarWriteResult.updatedCount,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
784
985
|
|
|
785
986
|
return jsonResponse({
|
|
786
987
|
ok: true,
|
|
@@ -800,6 +1001,9 @@ export function registerMetadataTools(s, {
|
|
|
800
1001
|
...plan.diagnostics,
|
|
801
1002
|
...sidecarWriteResult.diagnostics,
|
|
802
1003
|
],
|
|
1004
|
+
operation_history: backupResult.operation_history,
|
|
1005
|
+
backup_refresh: backupResult.backup_refresh,
|
|
1006
|
+
backup_warnings: backupResult.backup_warnings,
|
|
803
1007
|
next_steps: [
|
|
804
1008
|
"Use list_chapters to confirm the canonical title.",
|
|
805
1009
|
"Run diagnose_structure if folder-derived structure may still use the previous chapter title.",
|
|
@@ -887,6 +1091,39 @@ export function registerMetadataTools(s, {
|
|
|
887
1091
|
failureCode: "SCENE_SIDECAR_UPDATE_FAILED",
|
|
888
1092
|
syncDir: SYNC_DIR,
|
|
889
1093
|
});
|
|
1094
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
1095
|
+
syncDir: SYNC_DIR,
|
|
1096
|
+
projectId: project_id,
|
|
1097
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1098
|
+
operation: "reorder_chapter",
|
|
1099
|
+
actor: createToolActor("reorder_chapter"),
|
|
1100
|
+
affected: {
|
|
1101
|
+
chapters: [plan.chapter.chapter_id],
|
|
1102
|
+
scenes: linkedScenes.map(scene => scene.scene_id),
|
|
1103
|
+
},
|
|
1104
|
+
summary: `Reordered chapter "${plan.chapter.title}" from ${plan.previousChapter.sort_index} to ${plan.chapter.sort_index}.`,
|
|
1105
|
+
before: {
|
|
1106
|
+
chapter: {
|
|
1107
|
+
chapter_id: plan.previousChapter.chapter_id,
|
|
1108
|
+
title: plan.previousChapter.title,
|
|
1109
|
+
sort_index: plan.previousChapter.sort_index,
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
after: {
|
|
1113
|
+
chapter: {
|
|
1114
|
+
chapter_id: plan.chapter.chapter_id,
|
|
1115
|
+
project_id: plan.chapter.project_id,
|
|
1116
|
+
title: plan.chapter.title,
|
|
1117
|
+
sort_index: plan.chapter.sort_index,
|
|
1118
|
+
logline: plan.chapter.logline,
|
|
1119
|
+
metadata_stale: plan.chapter.metadata_stale,
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
metadata: {
|
|
1123
|
+
updated_scene_count: linkedScenes.length,
|
|
1124
|
+
updated_sidecar_count: sidecarWriteResult.updatedCount,
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
890
1127
|
|
|
891
1128
|
return jsonResponse({
|
|
892
1129
|
ok: true,
|
|
@@ -906,6 +1143,9 @@ export function registerMetadataTools(s, {
|
|
|
906
1143
|
...plan.diagnostics,
|
|
907
1144
|
...sidecarWriteResult.diagnostics,
|
|
908
1145
|
],
|
|
1146
|
+
operation_history: backupResult.operation_history,
|
|
1147
|
+
backup_refresh: backupResult.backup_refresh,
|
|
1148
|
+
backup_warnings: backupResult.backup_warnings,
|
|
909
1149
|
next_steps: [
|
|
910
1150
|
"Use list_chapters to confirm canonical order.",
|
|
911
1151
|
"Run diagnose_structure if folder-derived structure may still use the previous order.",
|
|
@@ -969,6 +1209,40 @@ export function registerMetadataTools(s, {
|
|
|
969
1209
|
failureCode: "EPIGRAPH_SIDECAR_UPDATE_FAILED",
|
|
970
1210
|
syncDir: SYNC_DIR,
|
|
971
1211
|
});
|
|
1212
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
1213
|
+
syncDir: SYNC_DIR,
|
|
1214
|
+
projectId: project_id,
|
|
1215
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1216
|
+
operation: "attach_epigraph",
|
|
1217
|
+
actor: createToolActor("attach_epigraph"),
|
|
1218
|
+
affected: {
|
|
1219
|
+
epigraphs: [plan.epigraph.epigraph_id],
|
|
1220
|
+
chapters: [plan.chapter.chapter_id],
|
|
1221
|
+
},
|
|
1222
|
+
summary: `Attached epigraph "${plan.epigraph.epigraph_id}" to chapter "${plan.chapter.title}".`,
|
|
1223
|
+
before: {
|
|
1224
|
+
epigraph: {
|
|
1225
|
+
epigraph_id: plan.epigraph.epigraph_id,
|
|
1226
|
+
chapter_id: plan.previousChapter?.chapter_id ?? null,
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
after: {
|
|
1230
|
+
epigraph: {
|
|
1231
|
+
epigraph_id: plan.epigraph.epigraph_id,
|
|
1232
|
+
project_id: plan.epigraph.project_id,
|
|
1233
|
+
chapter_id: plan.epigraph.chapter_id,
|
|
1234
|
+
metadata_stale: plan.epigraph.metadata_stale,
|
|
1235
|
+
},
|
|
1236
|
+
chapter: {
|
|
1237
|
+
chapter_id: plan.chapter.chapter_id,
|
|
1238
|
+
title: plan.chapter.title,
|
|
1239
|
+
sort_index: plan.chapter.sort_index,
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
metadata: {
|
|
1243
|
+
updated_sidecar_count: sidecarWriteResult.updatedCount,
|
|
1244
|
+
},
|
|
1245
|
+
});
|
|
972
1246
|
|
|
973
1247
|
return jsonResponse({
|
|
974
1248
|
ok: true,
|
|
@@ -996,6 +1270,9 @@ export function registerMetadataTools(s, {
|
|
|
996
1270
|
...plan.diagnostics,
|
|
997
1271
|
...sidecarWriteResult.diagnostics,
|
|
998
1272
|
],
|
|
1273
|
+
operation_history: backupResult.operation_history,
|
|
1274
|
+
backup_refresh: backupResult.backup_refresh,
|
|
1275
|
+
backup_warnings: backupResult.backup_warnings,
|
|
999
1276
|
next_steps: [
|
|
1000
1277
|
"Use find_epigraphs to confirm the canonical epigraph attachment.",
|
|
1001
1278
|
"Run diagnose_structure if folder-derived structure may still imply the previous chapter.",
|
|
@@ -1146,6 +1423,40 @@ export function registerMetadataTools(s, {
|
|
|
1146
1423
|
managedStructure: isManagedStructureProject(db, project_id),
|
|
1147
1424
|
});
|
|
1148
1425
|
}
|
|
1426
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
1427
|
+
syncDir: SYNC_DIR,
|
|
1428
|
+
projectId: project_id,
|
|
1429
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1430
|
+
operation: "move_scene",
|
|
1431
|
+
actor: createToolActor("move_scene"),
|
|
1432
|
+
affected: {
|
|
1433
|
+
scenes: [scene_id],
|
|
1434
|
+
chapters: [
|
|
1435
|
+
plan.previousChapterId ?? null,
|
|
1436
|
+
plan.assignedChapter?.chapter_id ?? null,
|
|
1437
|
+
].filter(Boolean),
|
|
1438
|
+
},
|
|
1439
|
+
summary: `Moved scene "${scene_id}" to ${plan.assignedChapter?.chapter_id ?? "no chapter"} at timeline_position ${plan.timelinePosition ?? "unchanged"}.`,
|
|
1440
|
+
before: {
|
|
1441
|
+
scene: {
|
|
1442
|
+
scene_id,
|
|
1443
|
+
project_id,
|
|
1444
|
+
chapter_id: plan.previousChapterId ?? null,
|
|
1445
|
+
timeline_position: plan.previousTimelinePosition ?? null,
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
after: {
|
|
1449
|
+
scene: {
|
|
1450
|
+
scene_id,
|
|
1451
|
+
project_id,
|
|
1452
|
+
chapter_id: plan.assignedChapter?.chapter_id ?? null,
|
|
1453
|
+
timeline_position: plan.timelinePosition ?? null,
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
metadata: {
|
|
1457
|
+
updated_sidecar_count: sidecarMirror.updatedCount,
|
|
1458
|
+
},
|
|
1459
|
+
});
|
|
1149
1460
|
|
|
1150
1461
|
return jsonResponse({
|
|
1151
1462
|
ok: true,
|
|
@@ -1169,6 +1480,9 @@ export function registerMetadataTools(s, {
|
|
|
1169
1480
|
},
|
|
1170
1481
|
...sidecarMirror.diagnostics,
|
|
1171
1482
|
],
|
|
1483
|
+
operation_history: backupResult.operation_history,
|
|
1484
|
+
backup_refresh: backupResult.backup_refresh,
|
|
1485
|
+
backup_warnings: backupResult.backup_warnings,
|
|
1172
1486
|
next_steps: [
|
|
1173
1487
|
"Use find_scenes to confirm the scene's canonical chapter and timeline_position.",
|
|
1174
1488
|
"Run diagnose_structure if folder-derived structure may still imply the previous placement.",
|
|
@@ -1264,6 +1578,40 @@ export function registerMetadataTools(s, {
|
|
|
1264
1578
|
managedStructure: isManagedStructureProject(db, project_id),
|
|
1265
1579
|
});
|
|
1266
1580
|
}
|
|
1581
|
+
const backupResult = refreshProjectBackupAfterMutation(db, {
|
|
1582
|
+
syncDir: SYNC_DIR,
|
|
1583
|
+
projectId: project_id,
|
|
1584
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1585
|
+
operation: "assign_scene_to_chapter",
|
|
1586
|
+
actor: createToolActor("assign_scene_to_chapter"),
|
|
1587
|
+
affected: {
|
|
1588
|
+
scenes: [scene_id],
|
|
1589
|
+
chapters: [
|
|
1590
|
+
plan.previousChapterId ?? scene.chapter_id ?? null,
|
|
1591
|
+
plan.assignedChapter?.chapter_id ?? null,
|
|
1592
|
+
].filter(Boolean),
|
|
1593
|
+
},
|
|
1594
|
+
summary: chapter === null
|
|
1595
|
+
? `Cleared chapter assignment for scene "${scene_id}".`
|
|
1596
|
+
: `Assigned scene "${scene_id}" to chapter "${plan.assignedChapter.chapter_id}".`,
|
|
1597
|
+
before: {
|
|
1598
|
+
scene: {
|
|
1599
|
+
scene_id,
|
|
1600
|
+
project_id,
|
|
1601
|
+
chapter_id: plan.previousChapterId ?? scene.chapter_id ?? null,
|
|
1602
|
+
},
|
|
1603
|
+
},
|
|
1604
|
+
after: {
|
|
1605
|
+
scene: {
|
|
1606
|
+
scene_id,
|
|
1607
|
+
project_id,
|
|
1608
|
+
chapter_id: plan.assignedChapter?.chapter_id ?? null,
|
|
1609
|
+
},
|
|
1610
|
+
},
|
|
1611
|
+
metadata: {
|
|
1612
|
+
updated_sidecar_count: sidecarMirror.updatedCount,
|
|
1613
|
+
},
|
|
1614
|
+
});
|
|
1267
1615
|
|
|
1268
1616
|
return jsonResponse({
|
|
1269
1617
|
ok: true,
|
|
@@ -1274,6 +1622,9 @@ export function registerMetadataTools(s, {
|
|
|
1274
1622
|
chapter: plan.assignedChapter,
|
|
1275
1623
|
updated_sidecar_count: sidecarMirror.updatedCount,
|
|
1276
1624
|
diagnostics: sidecarMirror.diagnostics,
|
|
1625
|
+
operation_history: backupResult.operation_history,
|
|
1626
|
+
backup_refresh: backupResult.backup_refresh,
|
|
1627
|
+
backup_warnings: backupResult.backup_warnings,
|
|
1277
1628
|
});
|
|
1278
1629
|
} catch (err) {
|
|
1279
1630
|
if (err?.name === "CoreValidationError") {
|
|
@@ -1341,8 +1692,46 @@ export function registerMetadataTools(s, {
|
|
|
1341
1692
|
indexSceneFile(db, SYNC_DIR, scene.file_path, updated, prose, {
|
|
1342
1693
|
managedStructure: isManagedStructureProject(db, project_id),
|
|
1343
1694
|
});
|
|
1695
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
1696
|
+
syncDir: SYNC_DIR,
|
|
1697
|
+
projectId: project_id,
|
|
1698
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1699
|
+
operation: "update_scene_metadata",
|
|
1700
|
+
actor: createToolActor("update_scene_metadata"),
|
|
1701
|
+
affected: {
|
|
1702
|
+
scenes: [scene_id],
|
|
1703
|
+
},
|
|
1704
|
+
summary: `Updated metadata for scene "${scene_id}".`,
|
|
1705
|
+
before: {
|
|
1706
|
+
scene: {
|
|
1707
|
+
scene_id,
|
|
1708
|
+
project_id,
|
|
1709
|
+
fields: Object.keys(fields).sort().reduce((acc, key) => {
|
|
1710
|
+
acc[key] = meta[key] ?? null;
|
|
1711
|
+
return acc;
|
|
1712
|
+
}, {}),
|
|
1713
|
+
},
|
|
1714
|
+
},
|
|
1715
|
+
after: {
|
|
1716
|
+
scene: {
|
|
1717
|
+
scene_id,
|
|
1718
|
+
project_id,
|
|
1719
|
+
fields: Object.keys(fields).sort().reduce((acc, key) => {
|
|
1720
|
+
acc[key] = updated[key] ?? null;
|
|
1721
|
+
return acc;
|
|
1722
|
+
}, {}),
|
|
1723
|
+
},
|
|
1724
|
+
},
|
|
1725
|
+
});
|
|
1344
1726
|
|
|
1345
|
-
return {
|
|
1727
|
+
return jsonResponse({
|
|
1728
|
+
ok: true,
|
|
1729
|
+
action: "updated",
|
|
1730
|
+
message: `Updated metadata for scene '${scene_id}'.`,
|
|
1731
|
+
scene_id,
|
|
1732
|
+
project_id,
|
|
1733
|
+
...backupMutationFields(backupResult),
|
|
1734
|
+
});
|
|
1346
1735
|
} catch (err) {
|
|
1347
1736
|
if (err.code === "ENOENT") {
|
|
1348
1737
|
return errorResponse("STALE_PATH", `Prose file for scene '${scene_id}' not found at indexed path — the file may have moved. Run sync() to refresh.`, { indexed_path: scene.file_path });
|
|
@@ -1370,7 +1759,7 @@ export function registerMetadataTools(s, {
|
|
|
1370
1759
|
if (!SYNC_DIR_WRITABLE) {
|
|
1371
1760
|
return errorResponse("READ_ONLY", "Cannot update character: sync dir is read-only.");
|
|
1372
1761
|
}
|
|
1373
|
-
const char = db.prepare(`SELECT file_path FROM characters WHERE character_id = ?`).get(character_id);
|
|
1762
|
+
const char = db.prepare(`SELECT character_id, project_id, universe_id, file_path, name, role, arc_summary, first_appearance FROM characters WHERE character_id = ?`).get(character_id);
|
|
1374
1763
|
if (!char) {
|
|
1375
1764
|
return errorResponse("NOT_FOUND", `Character '${character_id}' not found.`);
|
|
1376
1765
|
}
|
|
@@ -1393,8 +1782,47 @@ export function registerMetadataTools(s, {
|
|
|
1393
1782
|
db.prepare(`INSERT OR IGNORE INTO character_traits (character_id, trait) VALUES (?, ?)`).run(character_id, t);
|
|
1394
1783
|
}
|
|
1395
1784
|
}
|
|
1785
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
1786
|
+
syncDir: SYNC_DIR,
|
|
1787
|
+
projectId: char.project_id,
|
|
1788
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1789
|
+
operation: "update_character_sheet",
|
|
1790
|
+
actor: createToolActor("update_character_sheet"),
|
|
1791
|
+
affected: {
|
|
1792
|
+
characters: [character_id],
|
|
1793
|
+
},
|
|
1794
|
+
summary: `Updated character sheet "${character_id}".`,
|
|
1795
|
+
before: {
|
|
1796
|
+
character: {
|
|
1797
|
+
character_id,
|
|
1798
|
+
project_id: char.project_id,
|
|
1799
|
+
universe_id: char.universe_id,
|
|
1800
|
+
name: char.name,
|
|
1801
|
+
role: char.role,
|
|
1802
|
+
arc_summary: char.arc_summary,
|
|
1803
|
+
first_appearance: char.first_appearance,
|
|
1804
|
+
},
|
|
1805
|
+
},
|
|
1806
|
+
after: {
|
|
1807
|
+
character: {
|
|
1808
|
+
character_id,
|
|
1809
|
+
project_id: char.project_id,
|
|
1810
|
+
universe_id: char.universe_id,
|
|
1811
|
+
name: updated.name ?? meta.name ?? null,
|
|
1812
|
+
role: updated.role ?? null,
|
|
1813
|
+
arc_summary: updated.arc_summary ?? null,
|
|
1814
|
+
first_appearance: updated.first_appearance ?? null,
|
|
1815
|
+
},
|
|
1816
|
+
},
|
|
1817
|
+
});
|
|
1396
1818
|
|
|
1397
|
-
return {
|
|
1819
|
+
return jsonResponse({
|
|
1820
|
+
ok: true,
|
|
1821
|
+
action: "updated",
|
|
1822
|
+
message: `Updated character sheet for '${character_id}'.`,
|
|
1823
|
+
character_id,
|
|
1824
|
+
...backupMutationFields(backupResult),
|
|
1825
|
+
});
|
|
1398
1826
|
} catch (err) {
|
|
1399
1827
|
if (err?.name === "CoreValidationError") {
|
|
1400
1828
|
return errorResponse(err.code, err.message, err.details);
|
|
@@ -1423,7 +1851,7 @@ export function registerMetadataTools(s, {
|
|
|
1423
1851
|
if (!SYNC_DIR_WRITABLE) {
|
|
1424
1852
|
return errorResponse("READ_ONLY", "Cannot update place: sync dir is read-only.");
|
|
1425
1853
|
}
|
|
1426
|
-
const place = db.prepare(`SELECT file_path FROM places WHERE place_id = ?`).get(place_id);
|
|
1854
|
+
const place = db.prepare(`SELECT place_id, project_id, universe_id, file_path, name FROM places WHERE place_id = ?`).get(place_id);
|
|
1427
1855
|
if (!place) {
|
|
1428
1856
|
return errorResponse("NOT_FOUND", `Place '${place_id}' not found.`);
|
|
1429
1857
|
}
|
|
@@ -1434,8 +1862,41 @@ export function registerMetadataTools(s, {
|
|
|
1434
1862
|
|
|
1435
1863
|
db.prepare(`UPDATE places SET name = ? WHERE place_id = ?`)
|
|
1436
1864
|
.run(updated.name ?? meta.name ?? place_id, place_id);
|
|
1865
|
+
const backupResult = refreshProjectScopedBackupAfterMutation(db, {
|
|
1866
|
+
syncDir: SYNC_DIR,
|
|
1867
|
+
projectId: place.project_id,
|
|
1868
|
+
applicationVersion: MCP_SERVER_VERSION,
|
|
1869
|
+
operation: "update_place_sheet",
|
|
1870
|
+
actor: createToolActor("update_place_sheet"),
|
|
1871
|
+
affected: {
|
|
1872
|
+
places: [place_id],
|
|
1873
|
+
},
|
|
1874
|
+
summary: `Updated place sheet "${place_id}".`,
|
|
1875
|
+
before: {
|
|
1876
|
+
place: {
|
|
1877
|
+
place_id,
|
|
1878
|
+
project_id: place.project_id,
|
|
1879
|
+
universe_id: place.universe_id,
|
|
1880
|
+
name: place.name,
|
|
1881
|
+
},
|
|
1882
|
+
},
|
|
1883
|
+
after: {
|
|
1884
|
+
place: {
|
|
1885
|
+
place_id,
|
|
1886
|
+
project_id: place.project_id,
|
|
1887
|
+
universe_id: place.universe_id,
|
|
1888
|
+
name: updated.name ?? meta.name ?? place_id,
|
|
1889
|
+
},
|
|
1890
|
+
},
|
|
1891
|
+
});
|
|
1437
1892
|
|
|
1438
|
-
return {
|
|
1893
|
+
return jsonResponse({
|
|
1894
|
+
ok: true,
|
|
1895
|
+
action: "updated",
|
|
1896
|
+
message: `Updated place sheet for '${place_id}'.`,
|
|
1897
|
+
place_id,
|
|
1898
|
+
...backupMutationFields(backupResult),
|
|
1899
|
+
});
|
|
1439
1900
|
} catch (err) {
|
|
1440
1901
|
if (err?.name === "CoreValidationError") {
|
|
1441
1902
|
return errorResponse(err.code, err.message, err.details);
|