@imenam/mcp-documentor 1.0.66 → 1.0.67
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/README.md +18 -11
- package/dist/shared/schemas/doc.schema.d.ts +15 -3
- package/dist/shared/schemas/doc.schema.js +35 -6
- package/dist/src/core/project-scope.d.ts +24 -0
- package/dist/src/core/project-scope.js +28 -0
- package/dist/src/core/storage-engine.d.ts +0 -4
- package/dist/src/core/storage-engine.js +4 -9
- package/dist/src/gui-worker.js +46 -7
- package/dist/src/index.js +126 -41
- package/dist/src/tools/check-freshness.d.ts +2 -2
- package/dist/src/tools/check-freshness.js +8 -7
- package/dist/src/tools/create-doc.js +4 -6
- package/dist/src/tools/delete-doc.js +14 -7
- package/dist/src/tools/get-doc-metadata.d.ts +2 -2
- package/dist/src/tools/get-doc-metadata.js +6 -5
- package/dist/src/tools/get-doc-version.js +5 -2
- package/dist/src/tools/get-doc.d.ts +2 -2
- package/dist/src/tools/get-doc.js +6 -5
- package/dist/src/tools/git-diff-doc.js +8 -4
- package/dist/src/tools/list-doc-versions.js +5 -2
- package/dist/src/tools/move-doc.js +5 -8
- package/dist/src/tools/patch-doc.js +6 -5
- package/dist/src/tools/refresh-file-snapshot.js +8 -4
- package/dist/src/tools/restore-doc-version.js +5 -2
- package/dist/src/tools/update-doc.d.ts +2 -2
- package/dist/src/tools/update-doc.js +6 -5
- package/dist/src/tools/upload-image.js +12 -3
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/{arc-sHJlsIyi.js → arc-Carsq0k3.js} +1 -1
- package/gui/dist/assets/{architectureDiagram-3BPJPVTR-BiF2hxSq.js → architectureDiagram-3BPJPVTR-caqcwdTw.js} +1 -1
- package/gui/dist/assets/{blockDiagram-GPEHLZMM-BCnnBvty.js → blockDiagram-GPEHLZMM-0wUO8k2I.js} +1 -1
- package/gui/dist/assets/{c4Diagram-AAUBKEIU-Bu3KJ-ij.js → c4Diagram-AAUBKEIU-DS6q7fMy.js} +1 -1
- package/gui/dist/assets/channel-D51HexOc.js +1 -0
- package/gui/dist/assets/{chunk-2J33WTMH-C_ekvnEC.js → chunk-2J33WTMH-CrJc-G6D.js} +1 -1
- package/gui/dist/assets/{chunk-4BX2VUAB-Dmp27nYT.js → chunk-4BX2VUAB-C5vtPFbB.js} +1 -1
- package/gui/dist/assets/{chunk-55IACEB6-Jrgqw_BR.js → chunk-55IACEB6-BchyQUFM.js} +1 -1
- package/gui/dist/assets/{chunk-727SXJPM-BYwz54rU.js → chunk-727SXJPM-BCIaWtKq.js} +1 -1
- package/gui/dist/assets/{chunk-AQP2D5EJ-CuvuX4qY.js → chunk-AQP2D5EJ-BEQJDJt5.js} +1 -1
- package/gui/dist/assets/{chunk-FMBD7UC4-CNL_nFEL.js → chunk-FMBD7UC4-IfQ3QtrC.js} +1 -1
- package/gui/dist/assets/{chunk-ND2GUHAM-DECudjWh.js → chunk-ND2GUHAM-4Du33w7t.js} +1 -1
- package/gui/dist/assets/{chunk-QZHKN3VN-BAtGKt_6.js → chunk-QZHKN3VN-HR0CP6fa.js} +1 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-CU0dQ3zy.js +1 -0
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-CU0dQ3zy.js +1 -0
- package/gui/dist/assets/{cose-bilkent-S5V4N54A-DQ2M_2Lo.js → cose-bilkent-S5V4N54A-C9Re3ENx.js} +1 -1
- package/gui/dist/assets/{dagre-BM42HDAG-D6vlHW8t.js → dagre-BM42HDAG-DZmcvMMp.js} +1 -1
- package/gui/dist/assets/{diagram-2AECGRRQ-s2G4QZQa.js → diagram-2AECGRRQ-EyJrAGht.js} +1 -1
- package/gui/dist/assets/{diagram-5GNKFQAL-YyU3AqQI.js → diagram-5GNKFQAL-CHd3T9_b.js} +1 -1
- package/gui/dist/assets/{diagram-KO2AKTUF-BuFjXIUa.js → diagram-KO2AKTUF-CIyFrZSR.js} +1 -1
- package/gui/dist/assets/{diagram-LMA3HP47-CjO5OS2W.js → diagram-LMA3HP47-BlkJALyz.js} +1 -1
- package/gui/dist/assets/{diagram-OG6HWLK6-DVYIxc4z.js → diagram-OG6HWLK6-P8LA7IZE.js} +1 -1
- package/gui/dist/assets/{erDiagram-TEJ5UH35-BsxhnVVZ.js → erDiagram-TEJ5UH35-BPywgWN1.js} +1 -1
- package/gui/dist/assets/{flowDiagram-I6XJVG4X-D2BV25QW.js → flowDiagram-I6XJVG4X-ByL0kGiV.js} +1 -1
- package/gui/dist/assets/{ganttDiagram-6RSMTGT7-BR95C5C6.js → ganttDiagram-6RSMTGT7-_pnaYCU4.js} +1 -1
- package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-bHC_XC7V.js → gitGraphDiagram-PVQCEYII-DN67CDGs.js} +1 -1
- package/gui/dist/assets/{index-DhwbMWam.js → index-Bjc5MOe1.js} +88 -88
- package/gui/dist/assets/{index-Cbo0HWer.css → index-hjkEDRmc.css} +1 -1
- package/gui/dist/assets/{infoDiagram-5YYISTIA-DE0rWygZ.js → infoDiagram-5YYISTIA-Bm5LEpzH.js} +1 -1
- package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-C03LZBFa.js → ishikawaDiagram-YF4QCWOH-DUYSULkT.js} +1 -1
- package/gui/dist/assets/{journeyDiagram-JHISSGLW-tRPsZRYs.js → journeyDiagram-JHISSGLW-BvJ1_f0L.js} +1 -1
- package/gui/dist/assets/{kanban-definition-UN3LZRKU-BYePPZ0q.js → kanban-definition-UN3LZRKU-DDYKg6RY.js} +1 -1
- package/gui/dist/assets/{linear-Bgo5v91h.js → linear-HlpG9u6P.js} +1 -1
- package/gui/dist/assets/{mindmap-definition-RKZ34NQL-C87iCyRq.js → mindmap-definition-RKZ34NQL-CPa_wo_s.js} +1 -1
- package/gui/dist/assets/{pieDiagram-4H26LBE5-BINdJQBG.js → pieDiagram-4H26LBE5-BWo0c4DP.js} +1 -1
- package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-BNcy-pgy.js → quadrantDiagram-W4KKPZXB-CLahU-6B.js} +1 -1
- package/gui/dist/assets/{requirementDiagram-4Y6WPE33-D_DCIFsd.js → requirementDiagram-4Y6WPE33-BoYbgzQ7.js} +1 -1
- package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-nCf_iEpj.js → sankeyDiagram-5OEKKPKP-CGm79aMo.js} +1 -1
- package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-3rvwaBlu.js → sequenceDiagram-3UESZ5HK-BkknTD52.js} +1 -1
- package/gui/dist/assets/{stateDiagram-AJRCARHV-BRPOJHIL.js → stateDiagram-AJRCARHV-DSBurgwC.js} +1 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DfKn22XF.js +1 -0
- package/gui/dist/assets/{timeline-definition-PNZ67QCA-C75DjOcj.js → timeline-definition-PNZ67QCA-CBHyrGuz.js} +1 -1
- package/gui/dist/assets/{vennDiagram-CIIHVFJN-CLZG8u4c.js → vennDiagram-CIIHVFJN-Dn9fr_bR.js} +1 -1
- package/gui/dist/assets/{wardley-L42UT6IY-BJnZFkDe.js → wardley-L42UT6IY-nMuySeAb.js} +1 -1
- package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-CBvWRjIO.js → wardleyDiagram-YWT4CUSO-Byn2STLI.js} +1 -1
- package/gui/dist/assets/{xychartDiagram-2RQKCTM6-BGMFJ2t3.js → xychartDiagram-2RQKCTM6-Bi-Y9wp5.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/channel-fkOAmVLo.js +0 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-D_v2HS-t.js +0 -1
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-D_v2HS-t.js +0 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-BUd1OQnG.js +0 -1
package/dist/src/index.js
CHANGED
|
@@ -154,6 +154,9 @@ function handleIPCMessage(msg) {
|
|
|
154
154
|
else if (msg.type === "UPDATE_DOC") {
|
|
155
155
|
handleUpdateDocIPC(msg);
|
|
156
156
|
}
|
|
157
|
+
else if (msg.type === "DELETE_DOC") {
|
|
158
|
+
handleDeleteDocIPC(msg);
|
|
159
|
+
}
|
|
157
160
|
else if (msg.type === "GET_FILE_SECTION") {
|
|
158
161
|
handleGetFileSection(msg);
|
|
159
162
|
}
|
|
@@ -161,6 +164,16 @@ function handleIPCMessage(msg) {
|
|
|
161
164
|
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
162
165
|
}
|
|
163
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* GUI requests that target a specific doc must name their project: the GUI can be
|
|
169
|
+
* showing project A while an agent switches the server's active project to B.
|
|
170
|
+
*/
|
|
171
|
+
function requireProjectFromIPC(msg) {
|
|
172
|
+
const project = msg.data?.project;
|
|
173
|
+
if (!project)
|
|
174
|
+
throw new Error("project is required for this operation");
|
|
175
|
+
return project;
|
|
176
|
+
}
|
|
164
177
|
async function handleGetInitialState(msg) {
|
|
165
178
|
try {
|
|
166
179
|
const config = storageEngine.getConfig();
|
|
@@ -272,12 +285,13 @@ async function handleListDocs(msg) {
|
|
|
272
285
|
}
|
|
273
286
|
async function handleGetDoc(msg) {
|
|
274
287
|
try {
|
|
275
|
-
const
|
|
288
|
+
const project = requireProjectFromIPC(msg);
|
|
289
|
+
const doc = await storageEngine.getDoc(msg.data.id ?? msg.data.path, project);
|
|
276
290
|
broadcastToGUI({
|
|
277
291
|
type: "GET_DOC_RESPONSE",
|
|
278
292
|
correlationId: msg.correlationId,
|
|
279
|
-
data:
|
|
280
|
-
error:
|
|
293
|
+
data: doc ? { doc } : null,
|
|
294
|
+
error: doc ? undefined : `Doc not found in project "${project}"`,
|
|
281
295
|
timestamp: new Date().toISOString(),
|
|
282
296
|
});
|
|
283
297
|
}
|
|
@@ -330,15 +344,16 @@ async function handleCreateDocIPC(msg) {
|
|
|
330
344
|
return;
|
|
331
345
|
}
|
|
332
346
|
try {
|
|
333
|
-
const { title, content, folder, tags
|
|
347
|
+
const { title, content, folder, tags } = msg.data ?? {};
|
|
334
348
|
if (!title || !content)
|
|
335
349
|
throw new Error("title and content are required");
|
|
350
|
+
const project = requireProjectFromIPC(msg);
|
|
336
351
|
const result = await createDoc(storageEngine, embeddingEngine, {
|
|
337
352
|
title,
|
|
338
353
|
content,
|
|
354
|
+
project,
|
|
339
355
|
...(folder && { folder }),
|
|
340
356
|
...(tags && { tags }),
|
|
341
|
-
...(project && { project }),
|
|
342
357
|
});
|
|
343
358
|
if (result.isError) {
|
|
344
359
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "Create failed";
|
|
@@ -396,6 +411,7 @@ async function handleUpdateDocIPC(msg) {
|
|
|
396
411
|
try {
|
|
397
412
|
const result = await updateDoc(storageEngine, embeddingEngine, {
|
|
398
413
|
path: msg.data.id,
|
|
414
|
+
project: requireProjectFromIPC(msg),
|
|
399
415
|
content: msg.data.content,
|
|
400
416
|
...(msg.data.title !== undefined && { title: msg.data.title }),
|
|
401
417
|
});
|
|
@@ -426,16 +442,72 @@ async function handleUpdateDocIPC(msg) {
|
|
|
426
442
|
});
|
|
427
443
|
}
|
|
428
444
|
}
|
|
445
|
+
async function handleDeleteDocIPC(msg) {
|
|
446
|
+
if (isReadOnly()) {
|
|
447
|
+
broadcastToGUI({
|
|
448
|
+
type: "DELETE_DOC_RESPONSE",
|
|
449
|
+
correlationId: msg.correlationId,
|
|
450
|
+
error: "READ_ONLY mode: deletion disabled",
|
|
451
|
+
timestamp: new Date().toISOString(),
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const project = requireProjectFromIPC(msg);
|
|
457
|
+
const result = await deleteDoc(storageEngine, embeddingEngine, { path: msg.data.id, project });
|
|
458
|
+
if (result.isError) {
|
|
459
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "Delete failed";
|
|
460
|
+
broadcastToGUI({
|
|
461
|
+
type: "DELETE_DOC_RESPONSE",
|
|
462
|
+
correlationId: msg.correlationId,
|
|
463
|
+
error: text,
|
|
464
|
+
timestamp: new Date().toISOString(),
|
|
465
|
+
});
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const message = result.content[0]?.type === "text" ? result.content[0].text : undefined;
|
|
469
|
+
broadcastToGUI({
|
|
470
|
+
type: "DELETE_DOC_RESPONSE",
|
|
471
|
+
correlationId: msg.correlationId,
|
|
472
|
+
data: { success: true, message },
|
|
473
|
+
timestamp: new Date().toISOString(),
|
|
474
|
+
});
|
|
475
|
+
const config = storageEngine.getConfig();
|
|
476
|
+
const docs = await storageEngine.listDocs();
|
|
477
|
+
for (const doc of docs) {
|
|
478
|
+
const full = await storageEngine.getDoc(doc.id);
|
|
479
|
+
if (full && full.tracked_files.length > 0) {
|
|
480
|
+
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
481
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
482
|
+
doc.isBroken = report.status === "broken";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
broadcastToGUI({
|
|
486
|
+
type: "INITIAL_STATE",
|
|
487
|
+
data: { config, docs, projects: config.projects },
|
|
488
|
+
timestamp: new Date().toISOString(),
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
broadcastToGUI({
|
|
493
|
+
type: "DELETE_DOC_RESPONSE",
|
|
494
|
+
correlationId: msg.correlationId,
|
|
495
|
+
error: err.message,
|
|
496
|
+
timestamp: new Date().toISOString(),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
429
500
|
async function handleGetFileSection(msg) {
|
|
430
501
|
try {
|
|
431
502
|
const { docId, filePath } = msg.data;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
503
|
+
const project = requireProjectFromIPC(msg);
|
|
504
|
+
const doc = await storageEngine.getDoc(docId, project);
|
|
505
|
+
if (!doc)
|
|
506
|
+
throw new Error(`Doc not found in project "${project}"`);
|
|
507
|
+
const trackedFile = doc.tracked_files.find((f) => f.path === filePath);
|
|
436
508
|
if (!trackedFile || !trackedFile.sections?.length)
|
|
437
509
|
throw new Error("No sections for this file");
|
|
438
|
-
const absolutePath = toAbsolutePath(trackedFile.path,
|
|
510
|
+
const absolutePath = toAbsolutePath(trackedFile.path, project);
|
|
439
511
|
if (!absolutePath)
|
|
440
512
|
throw new Error("Cannot resolve file on this machine (no code root configured)");
|
|
441
513
|
const fileLines = fs.readFileSync(absolutePath, "utf-8").split("\n");
|
|
@@ -468,18 +540,18 @@ async function handleGetFileSection(msg) {
|
|
|
468
540
|
}
|
|
469
541
|
async function handleCheckFreshness(msg) {
|
|
470
542
|
try {
|
|
471
|
-
const
|
|
472
|
-
const doc =
|
|
543
|
+
const project = requireProjectFromIPC(msg);
|
|
544
|
+
const doc = await storageEngine.getDoc(msg.data.id ?? msg.data.path, project);
|
|
473
545
|
if (!doc) {
|
|
474
546
|
broadcastToGUI({
|
|
475
547
|
type: "CHECK_FRESHNESS_RESPONSE",
|
|
476
548
|
correlationId: msg.correlationId,
|
|
477
|
-
error:
|
|
549
|
+
error: `Doc not found in project "${project}"`,
|
|
478
550
|
timestamp: new Date().toISOString(),
|
|
479
551
|
});
|
|
480
552
|
return;
|
|
481
553
|
}
|
|
482
|
-
const report = computeFreshness(doc.tracked_files,
|
|
554
|
+
const report = computeFreshness(doc.tracked_files, project);
|
|
483
555
|
broadcastToGUI({
|
|
484
556
|
type: "CHECK_FRESHNESS_RESPONSE",
|
|
485
557
|
correlationId: msg.correlationId,
|
|
@@ -536,7 +608,10 @@ async function handleSearchDocs(msg) {
|
|
|
536
608
|
}
|
|
537
609
|
async function handleGitDiff(msg) {
|
|
538
610
|
try {
|
|
539
|
-
const result = await gitDiffDoc(storageEngine, {
|
|
611
|
+
const result = await gitDiffDoc(storageEngine, {
|
|
612
|
+
paths: [msg.data.id ?? msg.data.path],
|
|
613
|
+
project: requireProjectFromIPC(msg),
|
|
614
|
+
});
|
|
540
615
|
const text = result.content[0]?.text ?? "";
|
|
541
616
|
broadcastToGUI({
|
|
542
617
|
type: "GIT_DIFF_RESPONSE",
|
|
@@ -599,8 +674,7 @@ async function handleUpdateConfig(msg) {
|
|
|
599
674
|
async function handleGetDocHistory(msg) {
|
|
600
675
|
try {
|
|
601
676
|
const id = msg.data?.id ?? msg.data?.path;
|
|
602
|
-
const
|
|
603
|
-
const project = msg.data?.project ?? config.currentProject;
|
|
677
|
+
const project = requireProjectFromIPC(msg);
|
|
604
678
|
const versions = historyStore.listVersions(project, id);
|
|
605
679
|
broadcastToGUI({
|
|
606
680
|
type: "GET_DOC_HISTORY_RESPONSE",
|
|
@@ -622,8 +696,7 @@ async function handleGetDocVersionIPC(msg) {
|
|
|
622
696
|
try {
|
|
623
697
|
const id = msg.data?.id ?? msg.data?.path;
|
|
624
698
|
const version = msg.data?.version;
|
|
625
|
-
const
|
|
626
|
-
const project = msg.data?.project ?? config.currentProject;
|
|
699
|
+
const project = requireProjectFromIPC(msg);
|
|
627
700
|
const entry = historyStore.getVersion(project, id, version);
|
|
628
701
|
broadcastToGUI({
|
|
629
702
|
type: "GET_DOC_VERSION_RESPONSE",
|
|
@@ -716,6 +789,7 @@ async function reconnectGui(args) {
|
|
|
716
789
|
};
|
|
717
790
|
}
|
|
718
791
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
792
|
+
const PROJECT_PARAM_DESCRIPTION = "Project the documentation entry belongs to. REQUIRED — never guessed, and never defaulted to the current project. Use list_projects or get_doc_tree if you are unsure which project holds the entry.";
|
|
719
793
|
const tools = [
|
|
720
794
|
{
|
|
721
795
|
name: "create_doc",
|
|
@@ -767,20 +841,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
767
841
|
items: { type: "string" },
|
|
768
842
|
description: "IDs of related documentation entries",
|
|
769
843
|
},
|
|
770
|
-
project: { type: "string", description: "Project to create the doc in
|
|
844
|
+
project: { type: "string", description: "Project to create the doc in. REQUIRED — never defaulted to the current project. If the project name does not exist yet, it will be created automatically." },
|
|
771
845
|
},
|
|
772
|
-
required: ["title"],
|
|
846
|
+
required: ["title", "project"],
|
|
773
847
|
},
|
|
774
848
|
},
|
|
775
849
|
{
|
|
776
850
|
name: "get_doc",
|
|
777
|
-
description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata.",
|
|
851
|
+
description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata. The project is required — lookups are never resolved across projects.",
|
|
778
852
|
inputSchema: {
|
|
779
853
|
type: "object",
|
|
780
854
|
properties: {
|
|
781
855
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
|
|
856
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
782
857
|
},
|
|
783
|
-
required: ["path"],
|
|
858
|
+
required: ["path", "project"],
|
|
784
859
|
},
|
|
785
860
|
},
|
|
786
861
|
{
|
|
@@ -846,8 +921,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
846
921
|
items: { type: "string" },
|
|
847
922
|
description: "Paths of related documentation entries (replaces existing), e.g. ['backend/auth/authentication']",
|
|
848
923
|
},
|
|
924
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
849
925
|
},
|
|
850
|
-
required: ["path"],
|
|
926
|
+
required: ["path", "project"],
|
|
851
927
|
},
|
|
852
928
|
},
|
|
853
929
|
{
|
|
@@ -858,8 +934,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
858
934
|
properties: {
|
|
859
935
|
path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
|
|
860
936
|
new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
|
|
937
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " The doc stays in this project — move_doc never moves an entry across projects." },
|
|
861
938
|
},
|
|
862
|
-
required: ["path", "new_path"],
|
|
939
|
+
required: ["path", "new_path", "project"],
|
|
863
940
|
},
|
|
864
941
|
},
|
|
865
942
|
{
|
|
@@ -881,7 +958,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
881
958
|
type: "object",
|
|
882
959
|
properties: {
|
|
883
960
|
path: { type: "string", description: "Path of a single documentation entry to check, e.g. 'backend/auth/authentication'. If omitted, scans all docs in the project." },
|
|
884
|
-
project: { type: "string", description: "Project to
|
|
961
|
+
project: { type: "string", description: "Project to work in. REQUIRED whenever path is provided — the freshness of a single entry is never resolved across projects. When path is omitted, it selects the project to scan (all projects if omitted too)." },
|
|
885
962
|
},
|
|
886
963
|
required: [],
|
|
887
964
|
},
|
|
@@ -921,8 +998,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
921
998
|
items: { type: "string" },
|
|
922
999
|
description: "Specific tracked file paths to refresh (all tracked files if omitted)",
|
|
923
1000
|
},
|
|
1001
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " Every entry in paths must belong to it — call once per project otherwise." },
|
|
924
1002
|
},
|
|
925
|
-
required: ["paths"],
|
|
1003
|
+
required: ["paths", "project"],
|
|
926
1004
|
},
|
|
927
1005
|
},
|
|
928
1006
|
{
|
|
@@ -932,8 +1010,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
932
1010
|
type: "object",
|
|
933
1011
|
properties: {
|
|
934
1012
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1013
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
935
1014
|
},
|
|
936
|
-
required: ["path"],
|
|
1015
|
+
required: ["path", "project"],
|
|
937
1016
|
},
|
|
938
1017
|
},
|
|
939
1018
|
{
|
|
@@ -947,8 +1026,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
947
1026
|
search_file: { type: "string", description: "Absolute path to a text file whose content will be used as the search text. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
|
|
948
1027
|
replacement: { type: "string", description: "Text to replace it with. Supports [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
|
|
949
1028
|
replacement_file: { type: "string", description: "Absolute path to a text file whose content will be used as the replacement. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
|
|
1029
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
950
1030
|
},
|
|
951
|
-
required: ["path", "search"],
|
|
1031
|
+
required: ["path", "search", "project"],
|
|
952
1032
|
},
|
|
953
1033
|
},
|
|
954
1034
|
{
|
|
@@ -967,8 +1047,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
967
1047
|
items: { type: "string" },
|
|
968
1048
|
description: "Specific tracked file paths to diff (all tracked files if omitted)",
|
|
969
1049
|
},
|
|
1050
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " Every entry in paths must belong to it — call once per project otherwise." },
|
|
970
1051
|
},
|
|
971
|
-
required: ["paths"],
|
|
1052
|
+
required: ["paths", "project"],
|
|
972
1053
|
},
|
|
973
1054
|
},
|
|
974
1055
|
{
|
|
@@ -984,13 +1065,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
984
1065
|
},
|
|
985
1066
|
{
|
|
986
1067
|
name: "delete_doc",
|
|
987
|
-
description: "Permanently delete a documentation entry.
|
|
1068
|
+
description: "Permanently delete a documentation entry from an explicitly named project. The project is MANDATORY: deletion is never resolved across projects, so an entry that exists under the same path in several projects can never be deleted by mistake. Use list_projects / get_doc_tree first if you are unsure which project holds the entry.",
|
|
988
1069
|
inputSchema: {
|
|
989
1070
|
type: "object",
|
|
990
1071
|
properties: {
|
|
991
1072
|
path: { type: "string", description: "Path of the documentation entry to delete, e.g. 'backend/auth/authentication'" },
|
|
1073
|
+
project: {
|
|
1074
|
+
type: "string",
|
|
1075
|
+
description: "Project the documentation entry belongs to. Required — never guessed, and never defaulted to the current project.",
|
|
1076
|
+
},
|
|
992
1077
|
},
|
|
993
|
-
required: ["path"],
|
|
1078
|
+
required: ["path", "project"],
|
|
994
1079
|
},
|
|
995
1080
|
},
|
|
996
1081
|
{
|
|
@@ -1010,9 +1095,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1010
1095
|
type: "object",
|
|
1011
1096
|
properties: {
|
|
1012
1097
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1013
|
-
project: { type: "string", description:
|
|
1098
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1014
1099
|
},
|
|
1015
|
-
required: ["path"],
|
|
1100
|
+
required: ["path", "project"],
|
|
1016
1101
|
},
|
|
1017
1102
|
},
|
|
1018
1103
|
{
|
|
@@ -1023,9 +1108,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1023
1108
|
properties: {
|
|
1024
1109
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1025
1110
|
version: { type: "number", description: "Version number to retrieve" },
|
|
1026
|
-
project: { type: "string", description:
|
|
1111
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1027
1112
|
},
|
|
1028
|
-
required: ["path", "version"],
|
|
1113
|
+
required: ["path", "version", "project"],
|
|
1029
1114
|
},
|
|
1030
1115
|
},
|
|
1031
1116
|
{
|
|
@@ -1036,9 +1121,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1036
1121
|
properties: {
|
|
1037
1122
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1038
1123
|
version: { type: "number", description: "Version number to restore" },
|
|
1039
|
-
project: { type: "string", description:
|
|
1124
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1040
1125
|
},
|
|
1041
|
-
required: ["path", "version"],
|
|
1126
|
+
required: ["path", "version", "project"],
|
|
1042
1127
|
},
|
|
1043
1128
|
},
|
|
1044
1129
|
{
|
|
@@ -1064,7 +1149,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1064
1149
|
docs: {
|
|
1065
1150
|
type: "array",
|
|
1066
1151
|
items: { type: "string" },
|
|
1067
|
-
description: "Doc references to include
|
|
1152
|
+
description: "Doc references to include, each fully qualified as 'project:doc-id'. The project prefix is REQUIRED — a bare 'doc-id' is rejected so a bundle can never pick up another project's entry.",
|
|
1068
1153
|
},
|
|
1069
1154
|
description: { type: "string", description: "Short description of the bundle (optional)" },
|
|
1070
1155
|
},
|
|
@@ -1090,10 +1175,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1090
1175
|
properties: {
|
|
1091
1176
|
filename: { type: "string", description: "Filename with extension, e.g. 'screenshot.png'" },
|
|
1092
1177
|
filePath: { type: "string", description: "Absolute path to the image file on the server" },
|
|
1093
|
-
project: { type: "string", description: "Target project
|
|
1178
|
+
project: { type: "string", description: "Target project whose assets receive the image. REQUIRED — never defaulted to the current project." },
|
|
1094
1179
|
alt: { type: "string", description: "Alt text for the markdown image link (optional)" },
|
|
1095
1180
|
},
|
|
1096
|
-
required: ["filename", "filePath"],
|
|
1181
|
+
required: ["filename", "filePath", "project"],
|
|
1097
1182
|
},
|
|
1098
1183
|
},
|
|
1099
1184
|
{
|
|
@@ -4,15 +4,15 @@ export declare function computeFileHash(absolutePath: string): string;
|
|
|
4
4
|
export declare function computeFreshness(trackedFiles: TrackedFile[], project: string): FreshnessReport;
|
|
5
5
|
export declare function formatFreshnessReport(title: string, report: FreshnessReport): string;
|
|
6
6
|
export declare function checkFreshness(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
7
|
-
isError: boolean;
|
|
8
7
|
content: {
|
|
9
8
|
type: string;
|
|
10
9
|
text: string;
|
|
11
10
|
}[];
|
|
11
|
+
isError?: undefined;
|
|
12
12
|
} | {
|
|
13
|
+
isError: boolean;
|
|
13
14
|
content: {
|
|
14
15
|
type: string;
|
|
15
16
|
text: string;
|
|
16
17
|
}[];
|
|
17
|
-
isError?: undefined;
|
|
18
18
|
}>;
|
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import { CheckFreshnessArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
4
4
|
import { findAndHashSection } from "../core/section-tracker.js";
|
|
5
5
|
import { toAbsolutePath } from "../core/path-resolver.js";
|
|
6
|
+
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
6
7
|
export function computeFileHash(absolutePath) {
|
|
7
8
|
return crypto.createHash("sha256").update(fs.readFileSync(absolutePath)).digest("hex");
|
|
8
9
|
}
|
|
@@ -192,13 +193,13 @@ export async function checkFreshness(storageEngine, args) {
|
|
|
192
193
|
try {
|
|
193
194
|
const validated = CheckFreshnessArgsSchema.parse(args);
|
|
194
195
|
if (validated.path) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const report = computeFreshness(doc.tracked_files, project);
|
|
201
|
-
return { content: [{ type: "text", text: formatFreshnessReport(doc.title, report) }] };
|
|
196
|
+
// The schema guarantees project is present alongside path.
|
|
197
|
+
const project = validated.project;
|
|
198
|
+
const resolved = await resolveDocInProject(storageEngine, validated.path, project, "freshness check");
|
|
199
|
+
if (isScopeError(resolved))
|
|
200
|
+
return scopeErrorResult(resolved);
|
|
201
|
+
const report = computeFreshness(resolved.doc.tracked_files, project);
|
|
202
|
+
return { content: [{ type: "text", text: formatFreshnessReport(resolved.doc.title, report) }] };
|
|
202
203
|
}
|
|
203
204
|
if (validated.project) {
|
|
204
205
|
const scan = await scanProjectFreshness(storageEngine, validated.project);
|
|
@@ -13,7 +13,7 @@ export function slugify(title) {
|
|
|
13
13
|
export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
14
14
|
try {
|
|
15
15
|
const validated = CreateDocArgsSchema.parse(args);
|
|
16
|
-
const { project } =
|
|
16
|
+
const { project } = validated;
|
|
17
17
|
const rawContent = validated.content_file
|
|
18
18
|
? fs.readFileSync(validated.content_file, "utf-8")
|
|
19
19
|
: validated.content;
|
|
@@ -25,13 +25,11 @@ export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
|
25
25
|
if (existing) {
|
|
26
26
|
return {
|
|
27
27
|
isError: true,
|
|
28
|
-
content: [{ type: "text", text: `A documentation entry with path "${id}" already exists. Use update_doc to modify it.` }],
|
|
28
|
+
content: [{ type: "text", text: `A documentation entry with path "${id}" already exists in project "${project}". Use update_doc to modify it.` }],
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
const resolvedProject = project ?? storageEngine.getConfig().currentProject;
|
|
31
|
+
storageEngine.ensureProject(project);
|
|
32
|
+
const resolvedProject = project;
|
|
35
33
|
const allTrackedInputs = [
|
|
36
34
|
...(validated.tracked_files ?? []),
|
|
37
35
|
...additionalTrackedFiles,
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import { DeleteDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
1
3
|
export async function deleteDoc(storageEngine, embeddingEngine, args) {
|
|
2
4
|
try {
|
|
3
|
-
const { path: docPath } = args;
|
|
4
|
-
const resolved = await storageEngine
|
|
5
|
-
if (
|
|
6
|
-
return
|
|
5
|
+
const { path: docPath, project } = DeleteDocArgsSchema.parse(args);
|
|
6
|
+
const resolved = await resolveDocInProject(storageEngine, docPath, project, "deletion");
|
|
7
|
+
if (isScopeError(resolved))
|
|
8
|
+
return scopeErrorResult(resolved);
|
|
9
|
+
const deleted = await storageEngine.deleteDoc(resolved.doc.id, project);
|
|
10
|
+
if (!deleted) {
|
|
11
|
+
return {
|
|
12
|
+
isError: true,
|
|
13
|
+
content: [{ type: "text", text: `Failed to delete "${docPath}" from project "${project}".` }],
|
|
14
|
+
};
|
|
7
15
|
}
|
|
8
|
-
await
|
|
9
|
-
await embeddingEngine.removeEmbedding(resolved.doc.id, resolved.project);
|
|
16
|
+
await embeddingEngine.removeEmbedding(resolved.doc.id, project);
|
|
10
17
|
return {
|
|
11
|
-
content: [{ type: "text", text: `Documentation entry "${docPath}" deleted from project "${
|
|
18
|
+
content: [{ type: "text", text: `Documentation entry "${docPath}" deleted from project "${project}".` }],
|
|
12
19
|
};
|
|
13
20
|
}
|
|
14
21
|
catch (err) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
2
|
export declare function getDocMetadata(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
3
|
-
isError: boolean;
|
|
4
3
|
content: {
|
|
5
4
|
type: string;
|
|
6
5
|
text: string;
|
|
7
6
|
}[];
|
|
7
|
+
isError?: undefined;
|
|
8
8
|
} | {
|
|
9
|
+
isError: boolean;
|
|
9
10
|
content: {
|
|
10
11
|
type: string;
|
|
11
12
|
text: string;
|
|
12
13
|
}[];
|
|
13
|
-
isError?: undefined;
|
|
14
14
|
}>;
|
|
@@ -3,14 +3,15 @@ import { GetDocMetadataArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
|
3
3
|
import { computeFreshness } from "./check-freshness.js";
|
|
4
4
|
import { findAndHashSection } from "../core/section-tracker.js";
|
|
5
5
|
import { toAbsolutePath } from "../core/path-resolver.js";
|
|
6
|
+
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
6
7
|
export async function getDocMetadata(storageEngine, args) {
|
|
7
8
|
try {
|
|
8
9
|
const validated = GetDocMetadataArgsSchema.parse(args);
|
|
9
|
-
const resolved = await storageEngine.
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
const
|
|
10
|
+
const resolved = await resolveDocInProject(storageEngine, validated.path, validated.project, "lookup");
|
|
11
|
+
if (isScopeError(resolved))
|
|
12
|
+
return scopeErrorResult(resolved);
|
|
13
|
+
const { doc } = resolved;
|
|
14
|
+
const project = validated.project;
|
|
14
15
|
const report = computeFreshness(doc.tracked_files, project);
|
|
15
16
|
const fileStatusIcon = (filePath) => {
|
|
16
17
|
if (report.broken.includes(filePath))
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { GetDocVersionArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
import { assertProject, scopeErrorResult } from "../core/project-scope.js";
|
|
2
3
|
export async function getDocVersion(historyStore, storageEngine, args) {
|
|
3
4
|
try {
|
|
4
5
|
const validated = GetDocVersionArgsSchema.parse(args);
|
|
5
|
-
const
|
|
6
|
-
const
|
|
6
|
+
const project = validated.project;
|
|
7
|
+
const projectError = assertProject(storageEngine, project);
|
|
8
|
+
if (projectError)
|
|
9
|
+
return scopeErrorResult(projectError);
|
|
7
10
|
const entry = historyStore.getVersion(project, validated.path, validated.version);
|
|
8
11
|
if (!entry) {
|
|
9
12
|
return {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
2
|
export declare function getDoc(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
3
|
-
isError: boolean;
|
|
4
3
|
content: {
|
|
5
4
|
type: string;
|
|
6
5
|
text: string;
|
|
7
6
|
}[];
|
|
7
|
+
isError?: undefined;
|
|
8
8
|
} | {
|
|
9
|
+
isError: boolean;
|
|
9
10
|
content: {
|
|
10
11
|
type: string;
|
|
11
12
|
text: string;
|
|
12
13
|
}[];
|
|
13
|
-
isError?: undefined;
|
|
14
14
|
}>;
|
|
@@ -3,14 +3,15 @@ import { GetDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
|
3
3
|
import { computeFreshness } from "./check-freshness.js";
|
|
4
4
|
import { findAndHashSection } from "../core/section-tracker.js";
|
|
5
5
|
import { toAbsolutePath } from "../core/path-resolver.js";
|
|
6
|
+
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
6
7
|
export async function getDoc(storageEngine, args) {
|
|
7
8
|
try {
|
|
8
9
|
const validated = GetDocArgsSchema.parse(args);
|
|
9
|
-
const resolved = await storageEngine.
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
const
|
|
10
|
+
const resolved = await resolveDocInProject(storageEngine, validated.path, validated.project, "lookup");
|
|
11
|
+
if (isScopeError(resolved))
|
|
12
|
+
return scopeErrorResult(resolved);
|
|
13
|
+
const { doc } = resolved;
|
|
14
|
+
const resolvedProject = validated.project;
|
|
14
15
|
const trackedFilesText = doc.tracked_files.length > 0
|
|
15
16
|
? doc.tracked_files.map((f) => {
|
|
16
17
|
if (f.sections && f.sections.length > 0) {
|
|
@@ -3,6 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { GitDiffDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
4
4
|
import { computeFreshness } from "./check-freshness.js";
|
|
5
5
|
import { toAbsolutePath, toStoredPath } from "../core/path-resolver.js";
|
|
6
|
+
import { assertProject, scopeErrorResult } from "../core/project-scope.js";
|
|
6
7
|
function getGitDiffForFile(tracked, project) {
|
|
7
8
|
const filePath = toAbsolutePath(tracked.path, project);
|
|
8
9
|
if (!filePath) {
|
|
@@ -42,15 +43,18 @@ function getGitDiffForFile(tracked, project) {
|
|
|
42
43
|
export async function gitDiffDoc(storageEngine, args) {
|
|
43
44
|
try {
|
|
44
45
|
const validated = GitDiffDocArgsSchema.parse(args);
|
|
46
|
+
const project = validated.project;
|
|
47
|
+
const projectError = assertProject(storageEngine, project);
|
|
48
|
+
if (projectError)
|
|
49
|
+
return scopeErrorResult(projectError);
|
|
45
50
|
const docSections = [];
|
|
46
51
|
const seenFilePaths = new Set();
|
|
47
52
|
for (const docPath of validated.paths) {
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
docSections.push(`## ${docPath}\n\n[ERROR] Documentation entry not found in
|
|
53
|
+
const doc = await storageEngine.getDoc(docPath, project);
|
|
54
|
+
if (!doc) {
|
|
55
|
+
docSections.push(`## ${docPath}\n\n[ERROR] Documentation entry not found in project "${project}".`);
|
|
51
56
|
continue;
|
|
52
57
|
}
|
|
53
|
-
const { doc, project } = resolved;
|
|
54
58
|
if (!doc.tracked_files || doc.tracked_files.length === 0) {
|
|
55
59
|
docSections.push(`## ${doc.title}\n\n[INFO] No tracked files.`);
|
|
56
60
|
continue;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { ListDocVersionsArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
import { assertProject, scopeErrorResult } from "../core/project-scope.js";
|
|
2
3
|
export async function listDocVersions(historyStore, storageEngine, args) {
|
|
3
4
|
try {
|
|
4
5
|
const validated = ListDocVersionsArgsSchema.parse(args);
|
|
5
|
-
const
|
|
6
|
-
const
|
|
6
|
+
const project = validated.project;
|
|
7
|
+
const projectError = assertProject(storageEngine, project);
|
|
8
|
+
if (projectError)
|
|
9
|
+
return scopeErrorResult(projectError);
|
|
7
10
|
const versions = historyStore.listVersions(project, validated.path);
|
|
8
11
|
if (versions.length === 0) {
|
|
9
12
|
return {
|