@imenam/mcp-documentor 1.0.66 → 1.0.68
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 +34 -11
- package/dist/shared/schemas/doc.schema.d.ts +15 -3
- package/dist/shared/schemas/doc.schema.js +35 -6
- package/dist/src/cli/setup-mcp.d.ts +5 -0
- package/dist/src/cli/setup-mcp.js +94 -0
- package/dist/src/core/log-dir.d.ts +6 -0
- package/dist/src/core/log-dir.js +12 -0
- 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 +48 -8
- package/dist/src/index.js +135 -43
- 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
|
@@ -8,6 +8,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { APP_VERSION } from "./version.js";
|
|
10
10
|
import { GuiLauncher, createLogger, setupLogging } from "@imenam/mcp-gui-interface";
|
|
11
|
+
import { resolveLogDir } from "./core/log-dir.js";
|
|
11
12
|
import { StorageEngine } from "./core/storage-engine.js";
|
|
12
13
|
import { EmbeddingStore } from "./core/embedding-store.js";
|
|
13
14
|
import { EmbeddingEngine } from "./core/embedding-engine.js";
|
|
@@ -45,6 +46,12 @@ import { syncStatus, pullDocs, pushDocs, syncDocs, resolveSyncConflict } from ".
|
|
|
45
46
|
import { isReadOnly, WRITE_TOOLS } from "../shared/read-only.js";
|
|
46
47
|
const __filename = fileURLToPath(import.meta.url);
|
|
47
48
|
const __dirname = path.dirname(__filename);
|
|
49
|
+
// Les flags CLI sont traités avant toute initialisation (storage, sqlite, logs) :
|
|
50
|
+
// ils s'exécutent dans le répertoire de l'utilisateur, pas dans un serveur MCP.
|
|
51
|
+
if (process.argv.slice(2).includes("--claude-setup-mcp")) {
|
|
52
|
+
const { runSetupMcp } = await import("./cli/setup-mcp.js");
|
|
53
|
+
process.exit(runSetupMcp({ cwd: process.cwd(), force: process.argv.slice(2).includes("--force") }));
|
|
54
|
+
}
|
|
48
55
|
function findProjectRoot(startDir) {
|
|
49
56
|
let current = startDir;
|
|
50
57
|
while (current !== path.dirname(current)) {
|
|
@@ -57,7 +64,7 @@ function findProjectRoot(startDir) {
|
|
|
57
64
|
}
|
|
58
65
|
const rootDir = findProjectRoot(__dirname);
|
|
59
66
|
const packageEnvPath = path.join(rootDir, ".env");
|
|
60
|
-
setupLogging({ processLabel: "mcp-documentor", logDir:
|
|
67
|
+
setupLogging({ processLabel: "mcp-documentor", logDir: resolveLogDir(rootDir) });
|
|
61
68
|
const logger = createLogger("mcp-documentor");
|
|
62
69
|
function getConfig() {
|
|
63
70
|
if (fs.existsSync(packageEnvPath)) {
|
|
@@ -154,6 +161,9 @@ function handleIPCMessage(msg) {
|
|
|
154
161
|
else if (msg.type === "UPDATE_DOC") {
|
|
155
162
|
handleUpdateDocIPC(msg);
|
|
156
163
|
}
|
|
164
|
+
else if (msg.type === "DELETE_DOC") {
|
|
165
|
+
handleDeleteDocIPC(msg);
|
|
166
|
+
}
|
|
157
167
|
else if (msg.type === "GET_FILE_SECTION") {
|
|
158
168
|
handleGetFileSection(msg);
|
|
159
169
|
}
|
|
@@ -161,6 +171,16 @@ function handleIPCMessage(msg) {
|
|
|
161
171
|
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
162
172
|
}
|
|
163
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* GUI requests that target a specific doc must name their project: the GUI can be
|
|
176
|
+
* showing project A while an agent switches the server's active project to B.
|
|
177
|
+
*/
|
|
178
|
+
function requireProjectFromIPC(msg) {
|
|
179
|
+
const project = msg.data?.project;
|
|
180
|
+
if (!project)
|
|
181
|
+
throw new Error("project is required for this operation");
|
|
182
|
+
return project;
|
|
183
|
+
}
|
|
164
184
|
async function handleGetInitialState(msg) {
|
|
165
185
|
try {
|
|
166
186
|
const config = storageEngine.getConfig();
|
|
@@ -272,12 +292,13 @@ async function handleListDocs(msg) {
|
|
|
272
292
|
}
|
|
273
293
|
async function handleGetDoc(msg) {
|
|
274
294
|
try {
|
|
275
|
-
const
|
|
295
|
+
const project = requireProjectFromIPC(msg);
|
|
296
|
+
const doc = await storageEngine.getDoc(msg.data.id ?? msg.data.path, project);
|
|
276
297
|
broadcastToGUI({
|
|
277
298
|
type: "GET_DOC_RESPONSE",
|
|
278
299
|
correlationId: msg.correlationId,
|
|
279
|
-
data:
|
|
280
|
-
error:
|
|
300
|
+
data: doc ? { doc } : null,
|
|
301
|
+
error: doc ? undefined : `Doc not found in project "${project}"`,
|
|
281
302
|
timestamp: new Date().toISOString(),
|
|
282
303
|
});
|
|
283
304
|
}
|
|
@@ -330,15 +351,16 @@ async function handleCreateDocIPC(msg) {
|
|
|
330
351
|
return;
|
|
331
352
|
}
|
|
332
353
|
try {
|
|
333
|
-
const { title, content, folder, tags
|
|
354
|
+
const { title, content, folder, tags } = msg.data ?? {};
|
|
334
355
|
if (!title || !content)
|
|
335
356
|
throw new Error("title and content are required");
|
|
357
|
+
const project = requireProjectFromIPC(msg);
|
|
336
358
|
const result = await createDoc(storageEngine, embeddingEngine, {
|
|
337
359
|
title,
|
|
338
360
|
content,
|
|
361
|
+
project,
|
|
339
362
|
...(folder && { folder }),
|
|
340
363
|
...(tags && { tags }),
|
|
341
|
-
...(project && { project }),
|
|
342
364
|
});
|
|
343
365
|
if (result.isError) {
|
|
344
366
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "Create failed";
|
|
@@ -396,6 +418,7 @@ async function handleUpdateDocIPC(msg) {
|
|
|
396
418
|
try {
|
|
397
419
|
const result = await updateDoc(storageEngine, embeddingEngine, {
|
|
398
420
|
path: msg.data.id,
|
|
421
|
+
project: requireProjectFromIPC(msg),
|
|
399
422
|
content: msg.data.content,
|
|
400
423
|
...(msg.data.title !== undefined && { title: msg.data.title }),
|
|
401
424
|
});
|
|
@@ -426,16 +449,72 @@ async function handleUpdateDocIPC(msg) {
|
|
|
426
449
|
});
|
|
427
450
|
}
|
|
428
451
|
}
|
|
452
|
+
async function handleDeleteDocIPC(msg) {
|
|
453
|
+
if (isReadOnly()) {
|
|
454
|
+
broadcastToGUI({
|
|
455
|
+
type: "DELETE_DOC_RESPONSE",
|
|
456
|
+
correlationId: msg.correlationId,
|
|
457
|
+
error: "READ_ONLY mode: deletion disabled",
|
|
458
|
+
timestamp: new Date().toISOString(),
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const project = requireProjectFromIPC(msg);
|
|
464
|
+
const result = await deleteDoc(storageEngine, embeddingEngine, { path: msg.data.id, project });
|
|
465
|
+
if (result.isError) {
|
|
466
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "Delete failed";
|
|
467
|
+
broadcastToGUI({
|
|
468
|
+
type: "DELETE_DOC_RESPONSE",
|
|
469
|
+
correlationId: msg.correlationId,
|
|
470
|
+
error: text,
|
|
471
|
+
timestamp: new Date().toISOString(),
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const message = result.content[0]?.type === "text" ? result.content[0].text : undefined;
|
|
476
|
+
broadcastToGUI({
|
|
477
|
+
type: "DELETE_DOC_RESPONSE",
|
|
478
|
+
correlationId: msg.correlationId,
|
|
479
|
+
data: { success: true, message },
|
|
480
|
+
timestamp: new Date().toISOString(),
|
|
481
|
+
});
|
|
482
|
+
const config = storageEngine.getConfig();
|
|
483
|
+
const docs = await storageEngine.listDocs();
|
|
484
|
+
for (const doc of docs) {
|
|
485
|
+
const full = await storageEngine.getDoc(doc.id);
|
|
486
|
+
if (full && full.tracked_files.length > 0) {
|
|
487
|
+
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
488
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
489
|
+
doc.isBroken = report.status === "broken";
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
broadcastToGUI({
|
|
493
|
+
type: "INITIAL_STATE",
|
|
494
|
+
data: { config, docs, projects: config.projects },
|
|
495
|
+
timestamp: new Date().toISOString(),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
broadcastToGUI({
|
|
500
|
+
type: "DELETE_DOC_RESPONSE",
|
|
501
|
+
correlationId: msg.correlationId,
|
|
502
|
+
error: err.message,
|
|
503
|
+
timestamp: new Date().toISOString(),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
429
507
|
async function handleGetFileSection(msg) {
|
|
430
508
|
try {
|
|
431
509
|
const { docId, filePath } = msg.data;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
510
|
+
const project = requireProjectFromIPC(msg);
|
|
511
|
+
const doc = await storageEngine.getDoc(docId, project);
|
|
512
|
+
if (!doc)
|
|
513
|
+
throw new Error(`Doc not found in project "${project}"`);
|
|
514
|
+
const trackedFile = doc.tracked_files.find((f) => f.path === filePath);
|
|
436
515
|
if (!trackedFile || !trackedFile.sections?.length)
|
|
437
516
|
throw new Error("No sections for this file");
|
|
438
|
-
const absolutePath = toAbsolutePath(trackedFile.path,
|
|
517
|
+
const absolutePath = toAbsolutePath(trackedFile.path, project);
|
|
439
518
|
if (!absolutePath)
|
|
440
519
|
throw new Error("Cannot resolve file on this machine (no code root configured)");
|
|
441
520
|
const fileLines = fs.readFileSync(absolutePath, "utf-8").split("\n");
|
|
@@ -468,18 +547,18 @@ async function handleGetFileSection(msg) {
|
|
|
468
547
|
}
|
|
469
548
|
async function handleCheckFreshness(msg) {
|
|
470
549
|
try {
|
|
471
|
-
const
|
|
472
|
-
const doc =
|
|
550
|
+
const project = requireProjectFromIPC(msg);
|
|
551
|
+
const doc = await storageEngine.getDoc(msg.data.id ?? msg.data.path, project);
|
|
473
552
|
if (!doc) {
|
|
474
553
|
broadcastToGUI({
|
|
475
554
|
type: "CHECK_FRESHNESS_RESPONSE",
|
|
476
555
|
correlationId: msg.correlationId,
|
|
477
|
-
error:
|
|
556
|
+
error: `Doc not found in project "${project}"`,
|
|
478
557
|
timestamp: new Date().toISOString(),
|
|
479
558
|
});
|
|
480
559
|
return;
|
|
481
560
|
}
|
|
482
|
-
const report = computeFreshness(doc.tracked_files,
|
|
561
|
+
const report = computeFreshness(doc.tracked_files, project);
|
|
483
562
|
broadcastToGUI({
|
|
484
563
|
type: "CHECK_FRESHNESS_RESPONSE",
|
|
485
564
|
correlationId: msg.correlationId,
|
|
@@ -536,7 +615,10 @@ async function handleSearchDocs(msg) {
|
|
|
536
615
|
}
|
|
537
616
|
async function handleGitDiff(msg) {
|
|
538
617
|
try {
|
|
539
|
-
const result = await gitDiffDoc(storageEngine, {
|
|
618
|
+
const result = await gitDiffDoc(storageEngine, {
|
|
619
|
+
paths: [msg.data.id ?? msg.data.path],
|
|
620
|
+
project: requireProjectFromIPC(msg),
|
|
621
|
+
});
|
|
540
622
|
const text = result.content[0]?.text ?? "";
|
|
541
623
|
broadcastToGUI({
|
|
542
624
|
type: "GIT_DIFF_RESPONSE",
|
|
@@ -599,8 +681,7 @@ async function handleUpdateConfig(msg) {
|
|
|
599
681
|
async function handleGetDocHistory(msg) {
|
|
600
682
|
try {
|
|
601
683
|
const id = msg.data?.id ?? msg.data?.path;
|
|
602
|
-
const
|
|
603
|
-
const project = msg.data?.project ?? config.currentProject;
|
|
684
|
+
const project = requireProjectFromIPC(msg);
|
|
604
685
|
const versions = historyStore.listVersions(project, id);
|
|
605
686
|
broadcastToGUI({
|
|
606
687
|
type: "GET_DOC_HISTORY_RESPONSE",
|
|
@@ -622,8 +703,7 @@ async function handleGetDocVersionIPC(msg) {
|
|
|
622
703
|
try {
|
|
623
704
|
const id = msg.data?.id ?? msg.data?.path;
|
|
624
705
|
const version = msg.data?.version;
|
|
625
|
-
const
|
|
626
|
-
const project = msg.data?.project ?? config.currentProject;
|
|
706
|
+
const project = requireProjectFromIPC(msg);
|
|
627
707
|
const entry = historyStore.getVersion(project, id, version);
|
|
628
708
|
broadcastToGUI({
|
|
629
709
|
type: "GET_DOC_VERSION_RESPONSE",
|
|
@@ -711,11 +791,12 @@ async function reconnectGui(args) {
|
|
|
711
791
|
};
|
|
712
792
|
}
|
|
713
793
|
return {
|
|
714
|
-
content: [{ type: "text", text:
|
|
794
|
+
content: [{ type: "text", text: `GUI worker started but did not confirm proxy registration within 15s. Check the logs (${resolveLogDir(rootDir)}), then retry.` }],
|
|
715
795
|
isError: true,
|
|
716
796
|
};
|
|
717
797
|
}
|
|
718
798
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
799
|
+
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
800
|
const tools = [
|
|
720
801
|
{
|
|
721
802
|
name: "create_doc",
|
|
@@ -767,20 +848,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
767
848
|
items: { type: "string" },
|
|
768
849
|
description: "IDs of related documentation entries",
|
|
769
850
|
},
|
|
770
|
-
project: { type: "string", description: "Project to create the doc in
|
|
851
|
+
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
852
|
},
|
|
772
|
-
required: ["title"],
|
|
853
|
+
required: ["title", "project"],
|
|
773
854
|
},
|
|
774
855
|
},
|
|
775
856
|
{
|
|
776
857
|
name: "get_doc",
|
|
777
|
-
description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata.",
|
|
858
|
+
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
859
|
inputSchema: {
|
|
779
860
|
type: "object",
|
|
780
861
|
properties: {
|
|
781
862
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
|
|
863
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
782
864
|
},
|
|
783
|
-
required: ["path"],
|
|
865
|
+
required: ["path", "project"],
|
|
784
866
|
},
|
|
785
867
|
},
|
|
786
868
|
{
|
|
@@ -846,8 +928,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
846
928
|
items: { type: "string" },
|
|
847
929
|
description: "Paths of related documentation entries (replaces existing), e.g. ['backend/auth/authentication']",
|
|
848
930
|
},
|
|
931
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
849
932
|
},
|
|
850
|
-
required: ["path"],
|
|
933
|
+
required: ["path", "project"],
|
|
851
934
|
},
|
|
852
935
|
},
|
|
853
936
|
{
|
|
@@ -858,8 +941,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
858
941
|
properties: {
|
|
859
942
|
path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
|
|
860
943
|
new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
|
|
944
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " The doc stays in this project — move_doc never moves an entry across projects." },
|
|
861
945
|
},
|
|
862
|
-
required: ["path", "new_path"],
|
|
946
|
+
required: ["path", "new_path", "project"],
|
|
863
947
|
},
|
|
864
948
|
},
|
|
865
949
|
{
|
|
@@ -881,7 +965,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
881
965
|
type: "object",
|
|
882
966
|
properties: {
|
|
883
967
|
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
|
|
968
|
+
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
969
|
},
|
|
886
970
|
required: [],
|
|
887
971
|
},
|
|
@@ -921,8 +1005,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
921
1005
|
items: { type: "string" },
|
|
922
1006
|
description: "Specific tracked file paths to refresh (all tracked files if omitted)",
|
|
923
1007
|
},
|
|
1008
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " Every entry in paths must belong to it — call once per project otherwise." },
|
|
924
1009
|
},
|
|
925
|
-
required: ["paths"],
|
|
1010
|
+
required: ["paths", "project"],
|
|
926
1011
|
},
|
|
927
1012
|
},
|
|
928
1013
|
{
|
|
@@ -932,8 +1017,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
932
1017
|
type: "object",
|
|
933
1018
|
properties: {
|
|
934
1019
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1020
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
935
1021
|
},
|
|
936
|
-
required: ["path"],
|
|
1022
|
+
required: ["path", "project"],
|
|
937
1023
|
},
|
|
938
1024
|
},
|
|
939
1025
|
{
|
|
@@ -947,8 +1033,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
947
1033
|
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
1034
|
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
1035
|
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)." },
|
|
1036
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
950
1037
|
},
|
|
951
|
-
required: ["path", "search"],
|
|
1038
|
+
required: ["path", "search", "project"],
|
|
952
1039
|
},
|
|
953
1040
|
},
|
|
954
1041
|
{
|
|
@@ -967,8 +1054,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
967
1054
|
items: { type: "string" },
|
|
968
1055
|
description: "Specific tracked file paths to diff (all tracked files if omitted)",
|
|
969
1056
|
},
|
|
1057
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " Every entry in paths must belong to it — call once per project otherwise." },
|
|
970
1058
|
},
|
|
971
|
-
required: ["paths"],
|
|
1059
|
+
required: ["paths", "project"],
|
|
972
1060
|
},
|
|
973
1061
|
},
|
|
974
1062
|
{
|
|
@@ -984,13 +1072,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
984
1072
|
},
|
|
985
1073
|
{
|
|
986
1074
|
name: "delete_doc",
|
|
987
|
-
description: "Permanently delete a documentation entry.
|
|
1075
|
+
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
1076
|
inputSchema: {
|
|
989
1077
|
type: "object",
|
|
990
1078
|
properties: {
|
|
991
1079
|
path: { type: "string", description: "Path of the documentation entry to delete, e.g. 'backend/auth/authentication'" },
|
|
1080
|
+
project: {
|
|
1081
|
+
type: "string",
|
|
1082
|
+
description: "Project the documentation entry belongs to. Required — never guessed, and never defaulted to the current project.",
|
|
1083
|
+
},
|
|
992
1084
|
},
|
|
993
|
-
required: ["path"],
|
|
1085
|
+
required: ["path", "project"],
|
|
994
1086
|
},
|
|
995
1087
|
},
|
|
996
1088
|
{
|
|
@@ -1010,9 +1102,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1010
1102
|
type: "object",
|
|
1011
1103
|
properties: {
|
|
1012
1104
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1013
|
-
project: { type: "string", description:
|
|
1105
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1014
1106
|
},
|
|
1015
|
-
required: ["path"],
|
|
1107
|
+
required: ["path", "project"],
|
|
1016
1108
|
},
|
|
1017
1109
|
},
|
|
1018
1110
|
{
|
|
@@ -1023,9 +1115,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1023
1115
|
properties: {
|
|
1024
1116
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1025
1117
|
version: { type: "number", description: "Version number to retrieve" },
|
|
1026
|
-
project: { type: "string", description:
|
|
1118
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1027
1119
|
},
|
|
1028
|
-
required: ["path", "version"],
|
|
1120
|
+
required: ["path", "version", "project"],
|
|
1029
1121
|
},
|
|
1030
1122
|
},
|
|
1031
1123
|
{
|
|
@@ -1036,9 +1128,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1036
1128
|
properties: {
|
|
1037
1129
|
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
1038
1130
|
version: { type: "number", description: "Version number to restore" },
|
|
1039
|
-
project: { type: "string", description:
|
|
1131
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1040
1132
|
},
|
|
1041
|
-
required: ["path", "version"],
|
|
1133
|
+
required: ["path", "version", "project"],
|
|
1042
1134
|
},
|
|
1043
1135
|
},
|
|
1044
1136
|
{
|
|
@@ -1064,7 +1156,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1064
1156
|
docs: {
|
|
1065
1157
|
type: "array",
|
|
1066
1158
|
items: { type: "string" },
|
|
1067
|
-
description: "Doc references to include
|
|
1159
|
+
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
1160
|
},
|
|
1069
1161
|
description: { type: "string", description: "Short description of the bundle (optional)" },
|
|
1070
1162
|
},
|
|
@@ -1090,10 +1182,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1090
1182
|
properties: {
|
|
1091
1183
|
filename: { type: "string", description: "Filename with extension, e.g. 'screenshot.png'" },
|
|
1092
1184
|
filePath: { type: "string", description: "Absolute path to the image file on the server" },
|
|
1093
|
-
project: { type: "string", description: "Target project
|
|
1185
|
+
project: { type: "string", description: "Target project whose assets receive the image. REQUIRED — never defaulted to the current project." },
|
|
1094
1186
|
alt: { type: "string", description: "Alt text for the markdown image link (optional)" },
|
|
1095
1187
|
},
|
|
1096
|
-
required: ["filename", "filePath"],
|
|
1188
|
+
required: ["filename", "filePath", "project"],
|
|
1097
1189
|
},
|
|
1098
1190
|
},
|
|
1099
1191
|
{
|
|
@@ -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) {
|