@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.
Files changed (87) hide show
  1. package/README.md +323 -329
  2. package/dist/shared/read-only.js +1 -0
  3. package/dist/shared/schemas/doc.schema.d.ts +2 -1
  4. package/dist/shared/schemas/doc.schema.js +12 -1
  5. package/dist/src/cli/setup-mcp.js +2 -5
  6. package/dist/src/core/code-source.d.ts +33 -22
  7. package/dist/src/core/code-source.js +92 -69
  8. package/dist/src/core/embedding-engine.d.ts +1 -1
  9. package/dist/src/core/embedding-engine.js +3 -3
  10. package/dist/src/core/embedding-store.d.ts +1 -1
  11. package/dist/src/core/embedding-store.js +4 -4
  12. package/dist/src/core/github-code-api.d.ts +28 -0
  13. package/dist/src/core/github-code-api.js +92 -31
  14. package/dist/src/core/local-state.d.ts +49 -0
  15. package/dist/src/core/local-state.js +78 -0
  16. package/dist/src/core/migrate-code-sources.d.ts +17 -0
  17. package/dist/src/core/migrate-code-sources.js +157 -0
  18. package/dist/src/core/path-resolver.d.ts +29 -9
  19. package/dist/src/core/path-resolver.js +18 -42
  20. package/dist/src/core/project-settings.d.ts +21 -0
  21. package/dist/src/core/project-settings.js +63 -0
  22. package/dist/src/core/storage-engine.d.ts +12 -2
  23. package/dist/src/core/storage-engine.js +35 -15
  24. package/dist/src/gui-worker.js +70 -2
  25. package/dist/src/index.js +165 -11
  26. package/dist/src/tools/check-freshness.d.ts +2 -1
  27. package/dist/src/tools/check-freshness.js +1 -2
  28. package/dist/src/tools/code-sources.d.ts +102 -0
  29. package/dist/src/tools/code-sources.js +260 -0
  30. package/dist/src/tools/move-doc.js +20 -6
  31. package/dist/src/version.d.ts +1 -1
  32. package/dist/src/version.js +16 -2
  33. package/gui/dist/assets/{arc-CdXRzMBT.js → arc-DBgCQU2O.js} +1 -1
  34. package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CGUnwe_I.js → architectureDiagram-3BPJPVTR-BIPBAzZr.js} +1 -1
  35. package/gui/dist/assets/{blockDiagram-GPEHLZMM-BCI5_vQE.js → blockDiagram-GPEHLZMM-BE9WwuyF.js} +1 -1
  36. package/gui/dist/assets/{c4Diagram-AAUBKEIU-Bm9a6HGg.js → c4Diagram-AAUBKEIU-V1_4ktIt.js} +1 -1
  37. package/gui/dist/assets/channel-B_cci0fU.js +1 -0
  38. package/gui/dist/assets/{chunk-2J33WTMH-c7xNwExL.js → chunk-2J33WTMH-DskG-Kmf.js} +1 -1
  39. package/gui/dist/assets/{chunk-4BX2VUAB-CmcU4Q0o.js → chunk-4BX2VUAB-DaPhidEI.js} +1 -1
  40. package/gui/dist/assets/{chunk-55IACEB6-K8rRjNKN.js → chunk-55IACEB6-C9r6WDFM.js} +1 -1
  41. package/gui/dist/assets/{chunk-727SXJPM-DMPnL1bv.js → chunk-727SXJPM-BA-aC8US.js} +1 -1
  42. package/gui/dist/assets/{chunk-AQP2D5EJ-BeRIIft8.js → chunk-AQP2D5EJ-ByBp3whV.js} +1 -1
  43. package/gui/dist/assets/{chunk-FMBD7UC4-CN4It-fp.js → chunk-FMBD7UC4-CzqUByx7.js} +1 -1
  44. package/gui/dist/assets/{chunk-ND2GUHAM-Dy-ZqPua.js → chunk-ND2GUHAM-C0_B2ryQ.js} +1 -1
  45. package/gui/dist/assets/{chunk-QZHKN3VN-CaV4seJw.js → chunk-QZHKN3VN-B3eEwjDh.js} +1 -1
  46. package/gui/dist/assets/classDiagram-4FO5ZUOK-BqlcJ5BP.js +1 -0
  47. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BqlcJ5BP.js +1 -0
  48. package/gui/dist/assets/{cose-bilkent-S5V4N54A-B1qt_j1Z.js → cose-bilkent-S5V4N54A-D_i0I2pU.js} +1 -1
  49. package/gui/dist/assets/{dagre-BM42HDAG-4x5JEBQG.js → dagre-BM42HDAG-DtEVPK_z.js} +1 -1
  50. package/gui/dist/assets/{diagram-2AECGRRQ-Q7TvjRjv.js → diagram-2AECGRRQ-BftnXa5w.js} +1 -1
  51. package/gui/dist/assets/{diagram-5GNKFQAL-Dguj26bf.js → diagram-5GNKFQAL-Dh9-kl_u.js} +1 -1
  52. package/gui/dist/assets/{diagram-KO2AKTUF-DBv6MoZF.js → diagram-KO2AKTUF-CvB0BWCa.js} +1 -1
  53. package/gui/dist/assets/{diagram-LMA3HP47-mfVXwL5q.js → diagram-LMA3HP47-CDy-J1uy.js} +1 -1
  54. package/gui/dist/assets/{diagram-OG6HWLK6-Bn3_yQWG.js → diagram-OG6HWLK6-iY9FwkIF.js} +1 -1
  55. package/gui/dist/assets/{erDiagram-TEJ5UH35-Bp4wnBw7.js → erDiagram-TEJ5UH35-BT2_IX8D.js} +1 -1
  56. package/gui/dist/assets/{flowDiagram-I6XJVG4X-CGk72E1Q.js → flowDiagram-I6XJVG4X-DaixWyMx.js} +1 -1
  57. package/gui/dist/assets/{ganttDiagram-6RSMTGT7-C1WQIgpu.js → ganttDiagram-6RSMTGT7-DSo3cQPH.js} +1 -1
  58. package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-DSIs1ZqI.js → gitGraphDiagram-PVQCEYII-BfFT7Lds.js} +1 -1
  59. package/gui/dist/assets/index-CxCC1gxu.css +1 -0
  60. package/gui/dist/assets/{index-T7ZLHNgs.js → index-DPGmAc6z.js} +107 -105
  61. package/gui/dist/assets/{infoDiagram-5YYISTIA-DzHdfk5Y.js → infoDiagram-5YYISTIA-BxbxNxe2.js} +1 -1
  62. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DLxqTmDU.js → ishikawaDiagram-YF4QCWOH-DpopzqZr.js} +1 -1
  63. package/gui/dist/assets/{journeyDiagram-JHISSGLW-DYUPmZnf.js → journeyDiagram-JHISSGLW-DrOlFzIk.js} +1 -1
  64. package/gui/dist/assets/{kanban-definition-UN3LZRKU-f6km2MDT.js → kanban-definition-UN3LZRKU-vH81lYXy.js} +1 -1
  65. package/gui/dist/assets/{linear-DHhAaAXH.js → linear-BipFpFah.js} +1 -1
  66. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-nergVxV3.js → mindmap-definition-RKZ34NQL-DsTxCLgj.js} +1 -1
  67. package/gui/dist/assets/{pieDiagram-4H26LBE5-D4IJAdGz.js → pieDiagram-4H26LBE5-Bbq2ZWQU.js} +1 -1
  68. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-B5yYdOsV.js → quadrantDiagram-W4KKPZXB-BdTSaEo4.js} +1 -1
  69. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BrhJp07G.js → requirementDiagram-4Y6WPE33-BndI8YZy.js} +1 -1
  70. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CUBA4e7F.js → sankeyDiagram-5OEKKPKP-ofBkVITf.js} +1 -1
  71. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-DP0-rUie.js → sequenceDiagram-3UESZ5HK-Cjj2UrN9.js} +1 -1
  72. package/gui/dist/assets/{stateDiagram-AJRCARHV-_BnVcpam.js → stateDiagram-AJRCARHV-CzPrXUuz.js} +1 -1
  73. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DAq4ObN9.js +1 -0
  74. package/gui/dist/assets/{timeline-definition-PNZ67QCA-crbYjsrq.js → timeline-definition-PNZ67QCA-Cz4X-IOO.js} +1 -1
  75. package/gui/dist/assets/{vennDiagram-CIIHVFJN-EOqENtE1.js → vennDiagram-CIIHVFJN-BwIgXJcZ.js} +1 -1
  76. package/gui/dist/assets/{wardley-L42UT6IY-B1FVq-_Y.js → wardley-L42UT6IY-keQS0aNZ.js} +1 -1
  77. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-D1DZwIH_.js → wardleyDiagram-YWT4CUSO-DhykSgbD.js} +1 -1
  78. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-B6Wr8_OJ.js → xychartDiagram-2RQKCTM6-DFTqc4cj.js} +1 -1
  79. package/gui/dist/index.html +2 -2
  80. package/package.json +2 -3
  81. package/scripts/migrate-paths-to-relative.js +196 -192
  82. package/gui/dist/assets/channel-f0BgDIcS.js +0 -1
  83. package/gui/dist/assets/classDiagram-4FO5ZUOK-tGvt4n1B.js +0 -1
  84. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-tGvt4n1B.js +0 -1
  85. package/gui/dist/assets/index-QkCDSQ2A.css +0 -1
  86. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-B5Vwq56z.js +0 -1
  87. package/scripts/generate-version.js +0 -20
@@ -197,7 +197,15 @@ function setupIpcErrorHandlers() {
197
197
  });
198
198
  return;
199
199
  }
200
- if (msg && (msg.type === "INITIAL_STATE" || msg.type === "CONFIG_UPDATED" || msg.type === "BUNDLES_UPDATED")) {
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 path are automatically rewritten across the project. This is the only way to relocate a document — update_doc does not change the location.",
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 + " The doc stays in this project — move_doc never moves an entry across projects." },
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", "new_path", "project"],
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
+ };