@imenam/mcp-documentor 1.0.85 → 1.0.87
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 +323 -329
- package/dist/shared/read-only.js +1 -0
- package/dist/shared/schemas/doc.schema.d.ts +2 -1
- package/dist/shared/schemas/doc.schema.js +12 -1
- package/dist/src/cli/setup-mcp.js +2 -5
- package/dist/src/core/code-source.d.ts +33 -22
- package/dist/src/core/code-source.js +92 -69
- package/dist/src/core/embedding-engine.d.ts +1 -1
- package/dist/src/core/embedding-engine.js +3 -3
- package/dist/src/core/embedding-store.d.ts +1 -1
- package/dist/src/core/embedding-store.js +4 -4
- package/dist/src/core/github-code-api.d.ts +28 -0
- package/dist/src/core/github-code-api.js +92 -31
- package/dist/src/core/local-state.d.ts +49 -0
- package/dist/src/core/local-state.js +78 -0
- package/dist/src/core/migrate-code-sources.d.ts +17 -0
- package/dist/src/core/migrate-code-sources.js +157 -0
- package/dist/src/core/path-resolver.d.ts +29 -9
- package/dist/src/core/path-resolver.js +18 -42
- package/dist/src/core/project-settings.d.ts +21 -0
- package/dist/src/core/project-settings.js +63 -0
- package/dist/src/core/storage-engine.d.ts +12 -2
- package/dist/src/core/storage-engine.js +35 -15
- package/dist/src/gui-worker.js +70 -2
- package/dist/src/index.js +165 -11
- package/dist/src/tools/check-freshness.d.ts +2 -1
- package/dist/src/tools/check-freshness.js +1 -2
- package/dist/src/tools/code-sources.d.ts +102 -0
- package/dist/src/tools/code-sources.js +260 -0
- package/dist/src/tools/move-doc.js +20 -6
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +16 -2
- package/gui/dist/assets/{arc-CdXRzMBT.js → arc-DBgCQU2O.js} +1 -1
- package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CGUnwe_I.js → architectureDiagram-3BPJPVTR-BIPBAzZr.js} +1 -1
- package/gui/dist/assets/{blockDiagram-GPEHLZMM-BCI5_vQE.js → blockDiagram-GPEHLZMM-BE9WwuyF.js} +1 -1
- package/gui/dist/assets/{c4Diagram-AAUBKEIU-Bm9a6HGg.js → c4Diagram-AAUBKEIU-V1_4ktIt.js} +1 -1
- package/gui/dist/assets/channel-B_cci0fU.js +1 -0
- package/gui/dist/assets/{chunk-2J33WTMH-c7xNwExL.js → chunk-2J33WTMH-DskG-Kmf.js} +1 -1
- package/gui/dist/assets/{chunk-4BX2VUAB-CmcU4Q0o.js → chunk-4BX2VUAB-DaPhidEI.js} +1 -1
- package/gui/dist/assets/{chunk-55IACEB6-K8rRjNKN.js → chunk-55IACEB6-C9r6WDFM.js} +1 -1
- package/gui/dist/assets/{chunk-727SXJPM-DMPnL1bv.js → chunk-727SXJPM-BA-aC8US.js} +1 -1
- package/gui/dist/assets/{chunk-AQP2D5EJ-BeRIIft8.js → chunk-AQP2D5EJ-ByBp3whV.js} +1 -1
- package/gui/dist/assets/{chunk-FMBD7UC4-CN4It-fp.js → chunk-FMBD7UC4-CzqUByx7.js} +1 -1
- package/gui/dist/assets/{chunk-ND2GUHAM-Dy-ZqPua.js → chunk-ND2GUHAM-C0_B2ryQ.js} +1 -1
- package/gui/dist/assets/{chunk-QZHKN3VN-CaV4seJw.js → chunk-QZHKN3VN-B3eEwjDh.js} +1 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-BqlcJ5BP.js +1 -0
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BqlcJ5BP.js +1 -0
- package/gui/dist/assets/{cose-bilkent-S5V4N54A-B1qt_j1Z.js → cose-bilkent-S5V4N54A-D_i0I2pU.js} +1 -1
- package/gui/dist/assets/{dagre-BM42HDAG-4x5JEBQG.js → dagre-BM42HDAG-DtEVPK_z.js} +1 -1
- package/gui/dist/assets/{diagram-2AECGRRQ-Q7TvjRjv.js → diagram-2AECGRRQ-BftnXa5w.js} +1 -1
- package/gui/dist/assets/{diagram-5GNKFQAL-Dguj26bf.js → diagram-5GNKFQAL-Dh9-kl_u.js} +1 -1
- package/gui/dist/assets/{diagram-KO2AKTUF-DBv6MoZF.js → diagram-KO2AKTUF-CvB0BWCa.js} +1 -1
- package/gui/dist/assets/{diagram-LMA3HP47-mfVXwL5q.js → diagram-LMA3HP47-CDy-J1uy.js} +1 -1
- package/gui/dist/assets/{diagram-OG6HWLK6-Bn3_yQWG.js → diagram-OG6HWLK6-iY9FwkIF.js} +1 -1
- package/gui/dist/assets/{erDiagram-TEJ5UH35-Bp4wnBw7.js → erDiagram-TEJ5UH35-BT2_IX8D.js} +1 -1
- package/gui/dist/assets/{flowDiagram-I6XJVG4X-CGk72E1Q.js → flowDiagram-I6XJVG4X-DaixWyMx.js} +1 -1
- package/gui/dist/assets/{ganttDiagram-6RSMTGT7-C1WQIgpu.js → ganttDiagram-6RSMTGT7-DSo3cQPH.js} +1 -1
- package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-DSIs1ZqI.js → gitGraphDiagram-PVQCEYII-BfFT7Lds.js} +1 -1
- package/gui/dist/assets/index-CxCC1gxu.css +1 -0
- package/gui/dist/assets/{index-T7ZLHNgs.js → index-DPGmAc6z.js} +107 -105
- package/gui/dist/assets/{infoDiagram-5YYISTIA-DzHdfk5Y.js → infoDiagram-5YYISTIA-BxbxNxe2.js} +1 -1
- package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DLxqTmDU.js → ishikawaDiagram-YF4QCWOH-DpopzqZr.js} +1 -1
- package/gui/dist/assets/{journeyDiagram-JHISSGLW-DYUPmZnf.js → journeyDiagram-JHISSGLW-DrOlFzIk.js} +1 -1
- package/gui/dist/assets/{kanban-definition-UN3LZRKU-f6km2MDT.js → kanban-definition-UN3LZRKU-vH81lYXy.js} +1 -1
- package/gui/dist/assets/{linear-DHhAaAXH.js → linear-BipFpFah.js} +1 -1
- package/gui/dist/assets/{mindmap-definition-RKZ34NQL-nergVxV3.js → mindmap-definition-RKZ34NQL-DsTxCLgj.js} +1 -1
- package/gui/dist/assets/{pieDiagram-4H26LBE5-D4IJAdGz.js → pieDiagram-4H26LBE5-Bbq2ZWQU.js} +1 -1
- package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-B5yYdOsV.js → quadrantDiagram-W4KKPZXB-BdTSaEo4.js} +1 -1
- package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BrhJp07G.js → requirementDiagram-4Y6WPE33-BndI8YZy.js} +1 -1
- package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CUBA4e7F.js → sankeyDiagram-5OEKKPKP-ofBkVITf.js} +1 -1
- package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-DP0-rUie.js → sequenceDiagram-3UESZ5HK-Cjj2UrN9.js} +1 -1
- package/gui/dist/assets/{stateDiagram-AJRCARHV-_BnVcpam.js → stateDiagram-AJRCARHV-CzPrXUuz.js} +1 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DAq4ObN9.js +1 -0
- package/gui/dist/assets/{timeline-definition-PNZ67QCA-crbYjsrq.js → timeline-definition-PNZ67QCA-Cz4X-IOO.js} +1 -1
- package/gui/dist/assets/{vennDiagram-CIIHVFJN-EOqENtE1.js → vennDiagram-CIIHVFJN-BwIgXJcZ.js} +1 -1
- package/gui/dist/assets/{wardley-L42UT6IY-B1FVq-_Y.js → wardley-L42UT6IY-keQS0aNZ.js} +1 -1
- package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-D1DZwIH_.js → wardleyDiagram-YWT4CUSO-DhykSgbD.js} +1 -1
- package/gui/dist/assets/{xychartDiagram-2RQKCTM6-B6Wr8_OJ.js → xychartDiagram-2RQKCTM6-DFTqc4cj.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/scripts/migrate-paths-to-relative.js +196 -192
- package/gui/dist/assets/channel-f0BgDIcS.js +0 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-tGvt4n1B.js +0 -1
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-tGvt4n1B.js +0 -1
- package/gui/dist/assets/index-QkCDSQ2A.css +0 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-B5Vwq56z.js +0 -1
- package/scripts/generate-version.js +0 -20
package/dist/src/gui-worker.js
CHANGED
|
@@ -197,7 +197,15 @@ function setupIpcErrorHandlers() {
|
|
|
197
197
|
});
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
const BROADCAST_TYPES = [
|
|
201
|
+
"INITIAL_STATE",
|
|
202
|
+
"CONFIG_UPDATED",
|
|
203
|
+
"BUNDLES_UPDATED",
|
|
204
|
+
"PROJECTS_CHANGED",
|
|
205
|
+
"DOCS_CHANGED",
|
|
206
|
+
"CODE_SOURCES_CHANGED",
|
|
207
|
+
];
|
|
208
|
+
if (msg && BROADCAST_TYPES.includes(msg.type)) {
|
|
201
209
|
for (const controller of sseControllers) {
|
|
202
210
|
try {
|
|
203
211
|
controller.writeSSE({ data: JSON.stringify(msg), event: "message" });
|
|
@@ -217,6 +225,8 @@ const READ_ONLY_WRITE_ROUTES = [
|
|
|
217
225
|
{ method: "POST", prefix: "/api/images" },
|
|
218
226
|
{ method: "POST", prefix: "/api/configurations/apply" },
|
|
219
227
|
{ method: "POST", prefix: "/api/sync" },
|
|
228
|
+
{ method: "POST", prefix: "/api/code-sources/apply" },
|
|
229
|
+
{ method: "POST", prefix: "/api/code-roots/apply" },
|
|
220
230
|
];
|
|
221
231
|
app.use("*", async (c, next) => {
|
|
222
232
|
if (isReadOnly()) {
|
|
@@ -359,6 +369,64 @@ app.post("/api/sync", async (c) => {
|
|
|
359
369
|
return c.json({ error: result.error }, 500);
|
|
360
370
|
return c.json(result.data);
|
|
361
371
|
});
|
|
372
|
+
// --- Code sources ---
|
|
373
|
+
/** Relays one IPC request and answers with its data, or with its error and `errorStatus`. */
|
|
374
|
+
async function relay(c, type, data, timeout, errorStatus = 500) {
|
|
375
|
+
const result = await ipcRequest(type, data, timeout);
|
|
376
|
+
if (result.error)
|
|
377
|
+
return c.json({ error: result.error }, errorStatus);
|
|
378
|
+
return c.json(result.data);
|
|
379
|
+
}
|
|
380
|
+
async function readJson(c) {
|
|
381
|
+
try {
|
|
382
|
+
return await c.req.json();
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return {};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// GitHub projects are checked against their branch: one API round trip each.
|
|
389
|
+
app.get("/api/code-sources", (c) => relay(c, "LIST_CODE_SOURCES", {}, 30000));
|
|
390
|
+
app.post("/api/code-sources/inspect", async (c) => {
|
|
391
|
+
const { repo, branch } = await readJson(c);
|
|
392
|
+
if (!repo)
|
|
393
|
+
return c.json({ error: "repo is required" }, 400);
|
|
394
|
+
return relay(c, "INSPECT_REPOSITORY", { repo, branch }, 30000);
|
|
395
|
+
});
|
|
396
|
+
// Reads every tracked file of the project at the previewed source.
|
|
397
|
+
// `codeRoot`: omitted keeps the project's root, "" means the machine default, else the project's own.
|
|
398
|
+
app.post("/api/code-sources/preview", async (c) => {
|
|
399
|
+
const { project, config, codeRoot } = await readJson(c);
|
|
400
|
+
if (!project || !config)
|
|
401
|
+
return c.json({ error: "project and config are required" }, 400);
|
|
402
|
+
return relay(c, "PREVIEW_CODE_SOURCE", { project, config, codeRoot }, 120000, 400);
|
|
403
|
+
});
|
|
404
|
+
app.post("/api/code-sources/apply", async (c) => {
|
|
405
|
+
const { project, config, codeRoot } = await readJson(c);
|
|
406
|
+
if (!project || !config)
|
|
407
|
+
return c.json({ error: "project and config are required" }, 400);
|
|
408
|
+
return relay(c, "SAVE_CODE_SOURCE", { project, config, codeRoot }, 10000, 400);
|
|
409
|
+
});
|
|
410
|
+
app.get("/api/code-roots", (c) => relay(c, "GET_CODE_ROOTS", {}, 5000));
|
|
411
|
+
app.post("/api/code-sources/search", async (c) => {
|
|
412
|
+
const { query } = await readJson(c);
|
|
413
|
+
return relay(c, "SEARCH_REPOSITORIES", { query: query ?? "" }, 15000);
|
|
414
|
+
});
|
|
415
|
+
app.post("/api/code-roots/inspect", async (c) => {
|
|
416
|
+
const { path: root } = await readJson(c);
|
|
417
|
+
return relay(c, "INSPECT_CODE_ROOT", { path: root ?? "" }, 5000);
|
|
418
|
+
});
|
|
419
|
+
// Without a project, sets the machine default root. An empty path removes the root.
|
|
420
|
+
app.post("/api/code-roots/apply", async (c) => {
|
|
421
|
+
const { path: root, project } = await readJson(c);
|
|
422
|
+
if (typeof root !== "string")
|
|
423
|
+
return c.json({ error: "path is required" }, 400);
|
|
424
|
+
return relay(c, "SET_CODE_ROOT", { path: root, project }, 5000, 400);
|
|
425
|
+
});
|
|
426
|
+
// The token is read from MCP_DOCUMENTOR_CODE_TOKEN only: the GUI shows its status, never sets it.
|
|
427
|
+
app.get("/api/github-token", (c) => relay(c, "GET_GITHUB_TOKEN", {}, 15000));
|
|
428
|
+
app.get("/api/code-source-migration", (c) => relay(c, "GET_CODE_SOURCE_MIGRATION", {}, 5000));
|
|
429
|
+
app.post("/api/code-source-migration/ack", (c) => relay(c, "ACK_CODE_SOURCE_MIGRATION", {}, 5000));
|
|
362
430
|
app.get("/api/projects", async (c) => {
|
|
363
431
|
const result = await ipcRequest("LIST_PROJECTS");
|
|
364
432
|
if (result.error)
|
|
@@ -575,7 +643,7 @@ app.get("/api/export-zip/:id", async (c) => {
|
|
|
575
643
|
docId = ref;
|
|
576
644
|
}
|
|
577
645
|
else {
|
|
578
|
-
zip.file(`${ref}.md`, `# [Unqualified doc reference: ${ref} — no project given]
|
|
646
|
+
zip.file(`${ref}.md`, `# [Unqualified doc reference: ${ref} — no project given]
|
|
579
647
|
`);
|
|
580
648
|
continue;
|
|
581
649
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -20,7 +20,12 @@ import { searchDocs } from "./tools/search-docs.js";
|
|
|
20
20
|
import { checkFreshness, computeFreshness, DOC_CONCURRENCY } from "./tools/check-freshness.js";
|
|
21
21
|
import { mapWithConcurrency } from "./core/concurrency.js";
|
|
22
22
|
import { findAndHashSection } from "./core/section-tracker.js";
|
|
23
|
-
import { getCodeSource } from "./core/code-source.js";
|
|
23
|
+
import { getCodeSource, invalidateCodeSources, setCodeSourceProvider } from "./core/code-source.js";
|
|
24
|
+
import { readCodeSourceConfig } from "./core/project-settings.js";
|
|
25
|
+
import { LocalState } from "./core/local-state.js";
|
|
26
|
+
import { migrateCodeSources } from "./core/migrate-code-sources.js";
|
|
27
|
+
import { setCodeRootProvider } from "./core/path-resolver.js";
|
|
28
|
+
import { getCodeSourceTool, getGitHubTokenStatus, GITHUB_TOKEN_VAR, inspectCodeRoot, inspectRepository, listCodeSources, previewCodeSource, saveCodeSource, searchRepositoriesForInput, setCodeRoot, setCodeRootTool, setCodeSourceTool, } from "./tools/code-sources.js";
|
|
24
29
|
import { runInCodeOperation } from "./core/code-operation.js";
|
|
25
30
|
import { listProjects } from "./tools/list-projects.js";
|
|
26
31
|
import { refreshFileSnapshot } from "./tools/refresh-file-snapshot.js";
|
|
@@ -98,6 +103,12 @@ const historyStore = new HistoryStore();
|
|
|
98
103
|
historyStore.init(storageEngine.getDataDir());
|
|
99
104
|
storageEngine.setHistoryStore(historyStore);
|
|
100
105
|
const syncEngine = new SyncEngine(storageEngine);
|
|
106
|
+
const localState = new LocalState(storageEngine.getDataDir());
|
|
107
|
+
setCodeRootProvider((project) => localState.getCodeRoot(project));
|
|
108
|
+
setCodeSourceProvider({
|
|
109
|
+
codeSourceConfig: (project) => readCodeSourceConfig(storageEngine.getProjectDir(project)),
|
|
110
|
+
githubToken: () => process.env[GITHUB_TOKEN_VAR]?.trim() || undefined,
|
|
111
|
+
});
|
|
101
112
|
const server = new Server({ name: "mcp-documentor", version: APP_VERSION }, { capabilities: { tools: {} } });
|
|
102
113
|
let guiLauncher;
|
|
103
114
|
let guiStatus = "disabled";
|
|
@@ -189,6 +200,44 @@ function dispatchIPCMessage(msg) {
|
|
|
189
200
|
else if (msg.type === "SYNC_DOCS") {
|
|
190
201
|
handleSyncDocsIPC(msg);
|
|
191
202
|
}
|
|
203
|
+
else if (msg.type === "LIST_CODE_SOURCES") {
|
|
204
|
+
replyToGUI(msg, "LIST_CODE_SOURCES_RESPONSE", async () => ({ projects: await listCodeSources(storageEngine, localState) }));
|
|
205
|
+
}
|
|
206
|
+
else if (msg.type === "INSPECT_REPOSITORY") {
|
|
207
|
+
replyToGUI(msg, "INSPECT_REPOSITORY_RESPONSE", () => inspectRepository(msg.data ?? {}));
|
|
208
|
+
}
|
|
209
|
+
else if (msg.type === "SEARCH_REPOSITORIES") {
|
|
210
|
+
replyToGUI(msg, "SEARCH_REPOSITORIES_RESPONSE", async () => ({
|
|
211
|
+
results: await searchRepositoriesForInput(String(msg.data?.query ?? "")),
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
else if (msg.type === "INSPECT_CODE_ROOT") {
|
|
215
|
+
replyToGUI(msg, "INSPECT_CODE_ROOT_RESPONSE", () => inspectCodeRoot(String(msg.data?.path ?? "")));
|
|
216
|
+
}
|
|
217
|
+
else if (msg.type === "PREVIEW_CODE_SOURCE") {
|
|
218
|
+
replyToGUI(msg, "PREVIEW_CODE_SOURCE_RESPONSE", () => previewCodeSource(storageEngine, localState, requireProjectFromIPC(msg), msg.data?.config, msg.data?.codeRoot));
|
|
219
|
+
}
|
|
220
|
+
else if (msg.type === "SAVE_CODE_SOURCE") {
|
|
221
|
+
handleSaveCodeSource(msg);
|
|
222
|
+
}
|
|
223
|
+
else if (msg.type === "GET_CODE_ROOTS") {
|
|
224
|
+
replyToGUI(msg, "GET_CODE_ROOTS_RESPONSE", () => localState.getCodeRoots());
|
|
225
|
+
}
|
|
226
|
+
else if (msg.type === "SET_CODE_ROOT") {
|
|
227
|
+
handleSetCodeRoot(msg);
|
|
228
|
+
}
|
|
229
|
+
else if (msg.type === "GET_GITHUB_TOKEN") {
|
|
230
|
+
replyToGUI(msg, "GET_GITHUB_TOKEN_RESPONSE", () => getGitHubTokenStatus());
|
|
231
|
+
}
|
|
232
|
+
else if (msg.type === "GET_CODE_SOURCE_MIGRATION") {
|
|
233
|
+
replyToGUI(msg, "GET_CODE_SOURCE_MIGRATION_RESPONSE", () => ({ report: localState.getMigrationReport() ?? null }));
|
|
234
|
+
}
|
|
235
|
+
else if (msg.type === "ACK_CODE_SOURCE_MIGRATION") {
|
|
236
|
+
replyToGUI(msg, "ACK_CODE_SOURCE_MIGRATION_RESPONSE", () => {
|
|
237
|
+
localState.markMigrationReportSeen();
|
|
238
|
+
return { success: true };
|
|
239
|
+
});
|
|
240
|
+
}
|
|
192
241
|
else {
|
|
193
242
|
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
194
243
|
}
|
|
@@ -203,6 +252,54 @@ function requireProjectFromIPC(msg) {
|
|
|
203
252
|
throw new Error("project is required for this operation");
|
|
204
253
|
return project;
|
|
205
254
|
}
|
|
255
|
+
/** Answers a GUI request with what `compute` returns, or with the error it throws. */
|
|
256
|
+
async function replyToGUI(msg, type, compute) {
|
|
257
|
+
try {
|
|
258
|
+
const data = await compute();
|
|
259
|
+
broadcastToGUI({ type, correlationId: msg.correlationId, data, timestamp: new Date().toISOString() });
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
broadcastToGUI({ type, correlationId: msg.correlationId, error: err.message, timestamp: new Date().toISOString() });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** Tells every connected GUI that code sources changed, so selectors and freshness are re-read. */
|
|
266
|
+
function broadcastCodeSourcesChanged(project) {
|
|
267
|
+
broadcastToGUI({
|
|
268
|
+
type: "CODE_SOURCES_CHANGED",
|
|
269
|
+
data: { project: project ?? null },
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function handleSaveCodeSource(msg) {
|
|
274
|
+
if (isReadOnly()) {
|
|
275
|
+
replyToGUI(msg, "SAVE_CODE_SOURCE_RESPONSE", () => {
|
|
276
|
+
throw new Error("READ_ONLY mode: configuration changes disabled");
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
return replyToGUI(msg, "SAVE_CODE_SOURCE_RESPONSE", () => {
|
|
281
|
+
const project = requireProjectFromIPC(msg);
|
|
282
|
+
const config = saveCodeSource(storageEngine, localState, project, msg.data?.config, msg.data?.codeRoot);
|
|
283
|
+
logger.info(`Code source of project "${project}" set to ${getCodeSource(project).label}`);
|
|
284
|
+
broadcastCodeSourcesChanged(project);
|
|
285
|
+
return { config };
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/** Sets the machine default code root, or a project's own when the message names one. */
|
|
289
|
+
function handleSetCodeRoot(msg) {
|
|
290
|
+
if (isReadOnly()) {
|
|
291
|
+
replyToGUI(msg, "SET_CODE_ROOT_RESPONSE", () => {
|
|
292
|
+
throw new Error("READ_ONLY mode: configuration changes disabled");
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return replyToGUI(msg, "SET_CODE_ROOT_RESPONSE", () => {
|
|
297
|
+
const project = msg.data?.project || undefined;
|
|
298
|
+
const root = setCodeRoot(storageEngine, localState, String(msg.data?.path ?? ""), project);
|
|
299
|
+
broadcastCodeSourcesChanged(project);
|
|
300
|
+
return { path: root ?? null };
|
|
301
|
+
});
|
|
302
|
+
}
|
|
206
303
|
/** Loads one project's summaries and flags each entry as stale/broken. */
|
|
207
304
|
async function listDocsWithFreshness(project) {
|
|
208
305
|
const listings = await storageEngine.listDocsDetailed(project);
|
|
@@ -245,6 +342,7 @@ async function handleGetInitialState(msg) {
|
|
|
245
342
|
bundles,
|
|
246
343
|
readOnly: isReadOnly(),
|
|
247
344
|
syncEnabled: syncEngine.isConfigured(),
|
|
345
|
+
codeSourceMigrationPending: localState.getMigrationReport()?.seen === false,
|
|
248
346
|
},
|
|
249
347
|
timestamp: new Date().toISOString(),
|
|
250
348
|
});
|
|
@@ -684,9 +782,9 @@ async function handleSyncDocsIPC(msg) {
|
|
|
684
782
|
}
|
|
685
783
|
try {
|
|
686
784
|
// Reuse the tool so the GUI and the agent get identical pull/push semantics.
|
|
687
|
-
const result = await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, {
|
|
785
|
+
const result = afterSync(await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, {
|
|
688
786
|
message: msg.data?.message,
|
|
689
|
-
});
|
|
787
|
+
}));
|
|
690
788
|
const text = result.content.map((c) => c.text).join("\n");
|
|
691
789
|
if (result.isError) {
|
|
692
790
|
broadcastToGUI({
|
|
@@ -1031,15 +1129,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1031
1129
|
},
|
|
1032
1130
|
{
|
|
1033
1131
|
name: "move_doc",
|
|
1034
|
-
description: "Move a documentation entry to a new path (changes its folder and/or name). All existing doc:// links and related_docs references pointing to the old
|
|
1132
|
+
description: "Move a documentation entry to a new path (changes its folder and/or name) and/or to another project. All existing doc:// links and related_docs references pointing to the old location are automatically rewritten across all projects. When the entry changes project, its own same-project links are qualified with the source project so they keep their target, and its tracked_files are then resolved against the target project's code source. This is the only way to relocate a document — update_doc does not change the location. Provide new_path, new_project, or both.",
|
|
1035
1133
|
inputSchema: {
|
|
1036
1134
|
type: "object",
|
|
1037
1135
|
properties: {
|
|
1038
1136
|
path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
|
|
1039
|
-
new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
|
|
1040
|
-
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + "
|
|
1137
|
+
new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both. Omitted: the entry keeps its current path (new_project is then required)." },
|
|
1138
|
+
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION + " This is the project the entry is moved from." },
|
|
1139
|
+
new_project: { type: "string", description: "Project the entry is moved to. Must already exist (see list_projects). Omitted: the entry stays in `project`." },
|
|
1041
1140
|
},
|
|
1042
|
-
required: ["path", "
|
|
1141
|
+
required: ["path", "project"],
|
|
1043
1142
|
},
|
|
1044
1143
|
},
|
|
1045
1144
|
{
|
|
@@ -1370,6 +1469,42 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1370
1469
|
required: ["path", "decisions"],
|
|
1371
1470
|
},
|
|
1372
1471
|
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "get_code_source",
|
|
1474
|
+
description: "Show where each project's source code is read from when checking freshness: its local working copy or one branch of a GitHub repository, and the code root that applies on this machine. GitHub sources are checked against their branch, and a project whose repository or branch cannot be found is flagged.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
type: "object",
|
|
1477
|
+
properties: {
|
|
1478
|
+
project: { type: "string", description: "Project to show (defaults to every project)" },
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
name: "set_code_source",
|
|
1484
|
+
description: "Set where a project's source code is read from: 'local' (the working copy on this machine, under its code root) or 'github' (one branch of a repository, read over the GitHub API). The repository and branch are checked before saving. The setting is stored with the docs, so every machine reads the same source after a sync. The GitHub token is not settable here: it is read from the MCP_DOCUMENTOR_CODE_TOKEN environment variable only.",
|
|
1485
|
+
inputSchema: {
|
|
1486
|
+
type: "object",
|
|
1487
|
+
properties: {
|
|
1488
|
+
project: { type: "string", description: "Project to configure. REQUIRED." },
|
|
1489
|
+
kind: { type: "string", enum: ["local", "github"], description: "'local' reads the working copy, 'github' reads a repository branch" },
|
|
1490
|
+
repo: { type: "string", description: "Repository when kind is 'github': 'owner/repo' or a GitHub URL" },
|
|
1491
|
+
branch: { type: "string", description: "Branch when kind is 'github' (defaults to the repository's default branch)" },
|
|
1492
|
+
},
|
|
1493
|
+
required: ["project", "kind"],
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
name: "set_code_root",
|
|
1498
|
+
description: "Set the code root on THIS machine: the folder tracked-file paths are stored relative to, and that the local working copy is read from. With a project, sets that project's own root; without, sets the machine default used by every project that has none. An empty path removes the root (a project then uses the machine default). The folder must exist. Code roots are machine-local and never synced.",
|
|
1499
|
+
inputSchema: {
|
|
1500
|
+
type: "object",
|
|
1501
|
+
properties: {
|
|
1502
|
+
path: { type: "string", description: "Absolute path of the folder, or an empty string to remove the root" },
|
|
1503
|
+
project: { type: "string", description: "Project whose own root is set (omit to set the machine default)" },
|
|
1504
|
+
},
|
|
1505
|
+
required: ["path"],
|
|
1506
|
+
},
|
|
1507
|
+
},
|
|
1373
1508
|
{
|
|
1374
1509
|
name: "reconnect_gui",
|
|
1375
1510
|
description: "Relaunch the GUI worker and re-register it with the proxy server. Use this when the proxy server was not running when the MCP server started (so the GUI registration failed), to redeploy the GUI without restarting the MCP server. Does nothing if the GUI is already registered, unless force=true.",
|
|
@@ -1479,19 +1614,31 @@ async function handleToolCall(request) {
|
|
|
1479
1614
|
case "sync_status":
|
|
1480
1615
|
return await syncStatus(syncEngine);
|
|
1481
1616
|
case "pull_docs":
|
|
1482
|
-
return await pullDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore);
|
|
1617
|
+
return afterSync(await pullDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore));
|
|
1483
1618
|
case "push_docs":
|
|
1484
1619
|
return await pushDocs(syncEngine, args);
|
|
1485
1620
|
case "sync_docs":
|
|
1486
|
-
return await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args);
|
|
1621
|
+
return afterSync(await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args));
|
|
1487
1622
|
case "resolve_sync_conflict":
|
|
1488
|
-
return await resolveSyncConflict(syncEngine, args);
|
|
1623
|
+
return afterSync(await resolveSyncConflict(syncEngine, args));
|
|
1489
1624
|
case "list_conflicts":
|
|
1490
1625
|
return await listConflicts(syncEngine);
|
|
1491
1626
|
case "get_conflict_hunks":
|
|
1492
1627
|
return await getConflictHunks(syncEngine, args);
|
|
1493
1628
|
case "resolve_conflict_hunks":
|
|
1494
|
-
return await resolveConflictHunks(syncEngine, args);
|
|
1629
|
+
return afterSync(await resolveConflictHunks(syncEngine, args));
|
|
1630
|
+
case "get_code_source":
|
|
1631
|
+
return await getCodeSourceTool(storageEngine, localState, args);
|
|
1632
|
+
case "set_code_source": {
|
|
1633
|
+
const result = await setCodeSourceTool(storageEngine, localState, args);
|
|
1634
|
+
broadcastCodeSourcesChanged(args?.project);
|
|
1635
|
+
return result;
|
|
1636
|
+
}
|
|
1637
|
+
case "set_code_root": {
|
|
1638
|
+
const result = setCodeRootTool(storageEngine, localState, args);
|
|
1639
|
+
broadcastCodeSourcesChanged(args?.project);
|
|
1640
|
+
return result;
|
|
1641
|
+
}
|
|
1495
1642
|
case "reconnect_gui":
|
|
1496
1643
|
return await reconnectGui(args);
|
|
1497
1644
|
default:
|
|
@@ -1505,6 +1652,12 @@ async function handleToolCall(request) {
|
|
|
1505
1652
|
};
|
|
1506
1653
|
}
|
|
1507
1654
|
}
|
|
1655
|
+
/** A sync can bring other machines' project settings: code sources are re-read on next use. */
|
|
1656
|
+
function afterSync(result) {
|
|
1657
|
+
invalidateCodeSources();
|
|
1658
|
+
broadcastCodeSourcesChanged();
|
|
1659
|
+
return result;
|
|
1660
|
+
}
|
|
1508
1661
|
async function syncPullOnStartup() {
|
|
1509
1662
|
if (!syncEngine.isConfigured())
|
|
1510
1663
|
return;
|
|
@@ -1533,6 +1686,7 @@ async function main() {
|
|
|
1533
1686
|
await migrateFolders(storageEngine);
|
|
1534
1687
|
await migrateEmbeddings(embeddingStore, storageEngine);
|
|
1535
1688
|
await syncPullOnStartup();
|
|
1689
|
+
migrateCodeSources(storageEngine, localState);
|
|
1536
1690
|
const transport = new StdioServerTransport();
|
|
1537
1691
|
await server.connect(transport);
|
|
1538
1692
|
logger.info("MCP Server connected and running on stdio");
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { TrackedFile, FreshnessReport } from "../../shared/schemas/doc.schema.js";
|
|
2
2
|
import type { StorageEngine } from "../core/storage-engine.js";
|
|
3
|
+
import { type CodeSource } from "../core/code-source.js";
|
|
3
4
|
/**
|
|
4
5
|
* Docs checked at once during a whole-project scan. With FILE_CONCURRENCY this caps in-flight
|
|
5
6
|
* source reads at 32 — enough to hide network latency on a 160-doc project, far below anything
|
|
6
7
|
* GitHub objects to.
|
|
7
8
|
*/
|
|
8
9
|
export declare const DOC_CONCURRENCY = 8;
|
|
9
|
-
export declare function computeFreshness(trackedFiles: TrackedFile[], project: string): Promise<FreshnessReport>;
|
|
10
|
+
export declare function computeFreshness(trackedFiles: TrackedFile[], project: string, source?: CodeSource): Promise<FreshnessReport>;
|
|
10
11
|
export declare function formatFreshnessReport(title: string, report: FreshnessReport): string;
|
|
11
12
|
export declare function checkFreshness(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
12
13
|
content: {
|
|
@@ -48,8 +48,7 @@ async function checkTrackedFile(tracked, source) {
|
|
|
48
48
|
return { kind: "source_error", message: err instanceof Error ? err.message : String(err) };
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
export async function computeFreshness(trackedFiles, project) {
|
|
52
|
-
const source = getCodeSource(project);
|
|
51
|
+
export async function computeFreshness(trackedFiles, project, source = getCodeSource(project)) {
|
|
53
52
|
const empty = { modified: [], missing: [], unchanged: [], anchors_lost: [], broken: [], source: source.label };
|
|
54
53
|
if (!trackedFiles || trackedFiles.length === 0) {
|
|
55
54
|
return { status: "no_tracking", ...empty, total: 0 };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
import type { LocalState } from "../core/local-state.js";
|
|
3
|
+
import { type BranchHead, type RepositoryInfo, type RepositorySearchResult } from "../core/github-code-api.js";
|
|
4
|
+
import { type CodeSourceConfig } from "../core/project-settings.js";
|
|
5
|
+
/** The only variable the GitHub token is read from. It is never stored nor settable elsewhere. */
|
|
6
|
+
export declare const GITHUB_TOKEN_VAR = "MCP_DOCUMENTOR_CODE_TOKEN";
|
|
7
|
+
/** One project as the project selector shows it. */
|
|
8
|
+
export interface ProjectCodeSource {
|
|
9
|
+
project: string;
|
|
10
|
+
/** Undefined when the project's settings cannot be read — `error` then says why. */
|
|
11
|
+
config?: CodeSourceConfig;
|
|
12
|
+
/** The code root that applies to the project on this machine. */
|
|
13
|
+
codeRoot?: string;
|
|
14
|
+
/** Whether that root is the project's own, or the machine default. */
|
|
15
|
+
codeRootOrigin?: "project" | "default";
|
|
16
|
+
/** The machine default root, shown as what an empty project root falls back to. */
|
|
17
|
+
defaultCodeRoot?: string;
|
|
18
|
+
/** Why the source cannot be read right now: unreadable settings, unknown repository or branch… */
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Every project's code source and code root, with GitHub ones checked against their branch. */
|
|
22
|
+
export declare function listCodeSources(storageEngine: StorageEngine, localState: LocalState): Promise<ProjectCodeSource[]>;
|
|
23
|
+
export type RepositoryInspection = {
|
|
24
|
+
ok: true;
|
|
25
|
+
repo: RepositoryInfo;
|
|
26
|
+
branches: string[];
|
|
27
|
+
branch: string;
|
|
28
|
+
head: BranchHead;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
field: "repo" | "branch";
|
|
32
|
+
error: string;
|
|
33
|
+
repo?: RepositoryInfo;
|
|
34
|
+
branches?: string[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Checks a repository: whether it exists for the configured token, its branches, and the commit
|
|
38
|
+
* the chosen branch — or the default one — points at.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inspectRepository(input: {
|
|
41
|
+
repo: string;
|
|
42
|
+
branch?: string;
|
|
43
|
+
}): Promise<RepositoryInspection>;
|
|
44
|
+
/** Repositories matching what is typed in the repository field — empty below two characters. */
|
|
45
|
+
export declare function searchRepositoriesForInput(term: string): Promise<RepositorySearchResult[]>;
|
|
46
|
+
/** Checks that a code root is an existing folder given as an absolute path. */
|
|
47
|
+
export declare function inspectCodeRoot(root: string): {
|
|
48
|
+
ok: true;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
/** How the project's docs would read against a code source that is not saved yet. */
|
|
54
|
+
export interface CodeSourcePreview {
|
|
55
|
+
label: string;
|
|
56
|
+
docs: number;
|
|
57
|
+
upToDate: number;
|
|
58
|
+
stale: number;
|
|
59
|
+
unverifiable: number;
|
|
60
|
+
broken: number;
|
|
61
|
+
/** Set when the source itself could not be consulted. */
|
|
62
|
+
sourceError?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function previewCodeSource(storageEngine: StorageEngine, localState: LocalState, project: string, rawConfig: unknown, codeRoot?: string): Promise<CodeSourcePreview>;
|
|
65
|
+
/**
|
|
66
|
+
* Sets a code root on this machine: the project's own one, or the machine default when `project`
|
|
67
|
+
* is undefined. An empty root removes it — a project then uses the default.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setCodeRoot(storageEngine: StorageEngine, localState: LocalState, root: string, project?: string): string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Saves a project's code source and, when `codeRoot` is given, its code root on this machine (see
|
|
72
|
+
* `setCodeRoot`). The next operation on that project reads them.
|
|
73
|
+
*/
|
|
74
|
+
export declare function saveCodeSource(storageEngine: StorageEngine, localState: LocalState, project: string, rawConfig: unknown, codeRoot?: string): CodeSourceConfig;
|
|
75
|
+
export interface GitHubTokenStatus {
|
|
76
|
+
/** The variable the token is read from. */
|
|
77
|
+
variable: string;
|
|
78
|
+
configured: boolean;
|
|
79
|
+
/** Account the token belongs to. */
|
|
80
|
+
login?: string;
|
|
81
|
+
/** Why the token was rejected. */
|
|
82
|
+
error?: string;
|
|
83
|
+
}
|
|
84
|
+
export declare function getGitHubTokenStatus(): Promise<GitHubTokenStatus>;
|
|
85
|
+
export declare function getCodeSourceTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
86
|
+
content: {
|
|
87
|
+
type: string;
|
|
88
|
+
text: string;
|
|
89
|
+
}[];
|
|
90
|
+
}>;
|
|
91
|
+
export declare function setCodeSourceTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
92
|
+
content: {
|
|
93
|
+
type: string;
|
|
94
|
+
text: string;
|
|
95
|
+
}[];
|
|
96
|
+
}>;
|
|
97
|
+
export declare function setCodeRootTool(storageEngine: StorageEngine, localState: LocalState, args: unknown): {
|
|
98
|
+
content: {
|
|
99
|
+
type: string;
|
|
100
|
+
text: string;
|
|
101
|
+
}[];
|
|
102
|
+
};
|