@documentation-robotics/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +10 -14
  2. package/dist/analyzers/active-analyzer.js +62 -0
  3. package/dist/analyzers/active-analyzer.js.map +7 -0
  4. package/dist/analyzers/cards/build.js +21 -1
  5. package/dist/analyzers/cards/build.js.map +2 -2
  6. package/dist/analyzers/cards/extract.js +138 -0
  7. package/dist/analyzers/cards/extract.js.map +7 -0
  8. package/dist/analyzers/cards/routes.js +96 -0
  9. package/dist/analyzers/cards/routes.js.map +7 -0
  10. package/dist/analyzers/cbm-analyzer.js +76 -4
  11. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  12. package/dist/analyzers/cbm-responses.js +12 -6
  13. package/dist/analyzers/cbm-responses.js.map +2 -2
  14. package/dist/analyzers/code-changes.js +159 -0
  15. package/dist/analyzers/code-changes.js.map +7 -0
  16. package/dist/analyzers/edge-relationships.js +6 -2
  17. package/dist/analyzers/edge-relationships.js.map +2 -2
  18. package/dist/analyzers/extraction-plan.js +541 -0
  19. package/dist/analyzers/extraction-plan.js.map +7 -0
  20. package/dist/analyzers/index-job.js +229 -0
  21. package/dist/analyzers/index-job.js.map +7 -0
  22. package/dist/cli.js +12 -10
  23. package/dist/cli.js.map +2 -2
  24. package/dist/coding-agents/drbot-mcp.js +120 -18
  25. package/dist/coding-agents/drbot-mcp.js.map +2 -2
  26. package/dist/commands/add.js +4 -2
  27. package/dist/commands/add.js.map +2 -2
  28. package/dist/commands/analyzer.js +35 -35
  29. package/dist/commands/analyzer.js.map +2 -2
  30. package/dist/commands/changeset-verify.js +16 -8
  31. package/dist/commands/changeset-verify.js.map +2 -2
  32. package/dist/commands/changeset.js +41 -11
  33. package/dist/commands/changeset.js.map +2 -2
  34. package/dist/commands/init.js +9 -26
  35. package/dist/commands/init.js.map +2 -2
  36. package/dist/commands/mcp.js +35 -31
  37. package/dist/commands/mcp.js.map +3 -3
  38. package/dist/commands/relationship.js +2 -12
  39. package/dist/commands/relationship.js.map +2 -2
  40. package/dist/commands/reports.js +3 -3
  41. package/dist/commands/reports.js.map +2 -2
  42. package/dist/commands/update.js +14 -13
  43. package/dist/commands/update.js.map +2 -2
  44. package/dist/commands/upgrade.js +28 -120
  45. package/dist/commands/upgrade.js.map +2 -2
  46. package/dist/commands/validate.js +19 -3
  47. package/dist/commands/validate.js.map +2 -2
  48. package/dist/commands/version.js +1 -1
  49. package/dist/commands/visualize.js +30 -12
  50. package/dist/commands/visualize.js.map +2 -2
  51. package/dist/core/annotation-store.js +15 -5
  52. package/dist/core/annotation-store.js.map +2 -2
  53. package/dist/core/catalog-migration.js +2 -1
  54. package/dist/core/catalog-migration.js.map +2 -2
  55. package/dist/core/changeset-base-index.js +7 -0
  56. package/dist/core/changeset-base-index.js.map +2 -2
  57. package/dist/core/changeset-brief-compact.js +256 -0
  58. package/dist/core/changeset-brief-compact.js.map +7 -0
  59. package/dist/core/changeset-verify.js +356 -26
  60. package/dist/core/changeset-verify.js.map +3 -3
  61. package/dist/core/extraction-observability.js +161 -0
  62. package/dist/core/extraction-observability.js.map +7 -0
  63. package/dist/core/migration-registry.js +13 -1
  64. package/dist/core/migration-registry.js.map +2 -2
  65. package/dist/core/project-init.js +53 -0
  66. package/dist/core/project-init.js.map +7 -0
  67. package/dist/core/relationship-staging.js +94 -1
  68. package/dist/core/relationship-staging.js.map +2 -2
  69. package/dist/core/staged-changeset-storage.js +83 -3
  70. package/dist/core/staged-changeset-storage.js.map +2 -2
  71. package/dist/core/staging-area.js +85 -8
  72. package/dist/core/staging-area.js.map +2 -2
  73. package/dist/core/upgrade-plan.js +117 -0
  74. package/dist/core/upgrade-plan.js.map +7 -0
  75. package/dist/generated/relationship-index.js +124 -15
  76. package/dist/generated/relationship-index.js.map +2 -2
  77. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  78. package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
  79. package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
  80. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  81. package/dist/integrations/claude_code/commands/dr-map.md +7 -1
  82. package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
  83. package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
  84. package/dist/integrations/claude_code/docs/README.md +20 -1
  85. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  86. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  87. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
  88. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
  89. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
  90. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
  91. package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
  92. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
  93. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
  94. package/dist/mcp/changeset-only.js +14 -3
  95. package/dist/mcp/changeset-only.js.map +2 -2
  96. package/dist/mcp/project-root.js +85 -0
  97. package/dist/mcp/project-root.js.map +7 -0
  98. package/dist/mcp/server-instructions.js +27 -0
  99. package/dist/mcp/server-instructions.js.map +7 -0
  100. package/dist/mcp/strict-schema.js +52 -0
  101. package/dist/mcp/strict-schema.js.map +7 -0
  102. package/dist/mcp/tool-registry.js +39 -5
  103. package/dist/mcp/tool-registry.js.map +2 -2
  104. package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
  105. package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
  106. package/dist/mcp/tools/changeset-commit.js +224 -0
  107. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  108. package/dist/mcp/tools/changeset-editing.js +133 -57
  109. package/dist/mcp/tools/changeset-editing.js.map +3 -3
  110. package/dist/mcp/tools/changeset-lifecycle.js +1 -1
  111. package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
  112. package/dist/mcp/tools/changeset-verify.js +12 -6
  113. package/dist/mcp/tools/changeset-verify.js.map +2 -2
  114. package/dist/mcp/tools/codebase-extract.js +836 -0
  115. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  116. package/dist/mcp/tools/codebase-index.js +125 -0
  117. package/dist/mcp/tools/codebase-index.js.map +7 -0
  118. package/dist/mcp/tools/codebase-shared.js +265 -0
  119. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  120. package/dist/mcp/tools/codebase-verify.js +48 -0
  121. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  122. package/dist/mcp/tools/index.js +15 -0
  123. package/dist/mcp/tools/index.js.map +2 -2
  124. package/dist/mcp/tools/model-add.js +189 -73
  125. package/dist/mcp/tools/model-add.js.map +2 -2
  126. package/dist/mcp/tools/model-update.js +16 -12
  127. package/dist/mcp/tools/model-update.js.map +2 -2
  128. package/dist/mcp/tools/model-validate.js +46 -4
  129. package/dist/mcp/tools/model-validate.js.map +2 -2
  130. package/dist/mcp/tools/project-status-collectors.js +381 -0
  131. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  132. package/dist/mcp/tools/project-status.js +237 -0
  133. package/dist/mcp/tools/project-status.js.map +7 -0
  134. package/dist/mcp/tools/project-tools.js +505 -0
  135. package/dist/mcp/tools/project-tools.js.map +7 -0
  136. package/dist/mcp/tools/relationship-tools.js +89 -20
  137. package/dist/mcp/tools/relationship-tools.js.map +2 -2
  138. package/dist/mcp/tools/review-open.js +210 -0
  139. package/dist/mcp/tools/review-open.js.map +7 -0
  140. package/dist/mcp/tools/shared.js +10 -2
  141. package/dist/mcp/tools/shared.js.map +2 -2
  142. package/dist/reports/model-report-orchestrator.js +31 -2
  143. package/dist/reports/model-report-orchestrator.js.map +2 -2
  144. package/dist/schemas/bundled/analyzers/cbm.json +22 -10
  145. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  146. package/dist/schemas/bundled/api.json +1 -1
  147. package/dist/schemas/bundled/apm.json +1 -1
  148. package/dist/schemas/bundled/application.json +1 -1
  149. package/dist/schemas/bundled/base.json +1 -1
  150. package/dist/schemas/bundled/business.json +1 -1
  151. package/dist/schemas/bundled/data-model.json +1 -1
  152. package/dist/schemas/bundled/data-store.json +1 -1
  153. package/dist/schemas/bundled/manifest.json +3 -3
  154. package/dist/schemas/bundled/motivation.json +1 -1
  155. package/dist/schemas/bundled/navigation.json +1 -1
  156. package/dist/schemas/bundled/product.json +1 -1
  157. package/dist/schemas/bundled/security.json +1 -1
  158. package/dist/schemas/bundled/technology.json +1 -1
  159. package/dist/schemas/bundled/testing.json +145 -1
  160. package/dist/schemas/bundled/ux.json +1 -1
  161. package/dist/server/server.js +508 -237
  162. package/dist/server/server.js.map +3 -3
  163. package/dist/server/viewer-registry.js +264 -0
  164. package/dist/server/viewer-registry.js.map +7 -0
  165. package/dist/telemetry/index.js +1 -1
  166. package/dist/utils/id-generator.js +5 -2
  167. package/dist/utils/id-generator.js.map +2 -2
  168. package/dist/utils/source-reference.js +58 -27
  169. package/dist/utils/source-reference.js.map +2 -2
  170. package/dist/utils/spec-version.js +2 -2
  171. package/dist/utils/spec-version.js.map +1 -1
  172. package/dist/validators/model-warnings.js +28 -0
  173. package/dist/validators/model-warnings.js.map +7 -0
  174. package/dist/viewer/README.md +6 -6
  175. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  176. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  177. package/dist/viewer/index.html +2 -2
  178. package/dist/viewer/manifest.json +10 -10
  179. package/dist/viewer/version.json +1 -1
  180. package/package.json +1 -1
  181. package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
  182. package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
@@ -0,0 +1,265 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { AnalyzerRegistry } from "../../analyzers/registry.js";
6
+ import { analyzerHomepage, currentGitHead, readIndexFreshness } from "../../analyzers/active-analyzer.js";
7
+ import { SOURCE_EXT, VENDOR_EXCLUDE } from "../../analyzers/cards/build.js";
8
+ import { readIndexJob } from "../../analyzers/index-job.js";
9
+ import { readSession } from "../../analyzers/session-state.js";
10
+ import { performDiscover } from "../../commands/discover-logic.js";
11
+ import { CLIError, ErrorCategory } from "../../utils/errors.js";
12
+ const defaultCodebaseToolDeps = {
13
+ async getAnalyzer(name) {
14
+ const registry = AnalyzerRegistry.getInstance();
15
+ return await registry.initialize(), registry.getAnalyzer(name);
16
+ },
17
+ async discover() {
18
+ const registry = AnalyzerRegistry.getInstance();
19
+ await registry.initialize();
20
+ const { discoveryResult } = await performDiscover(registry, { json: !0, isTTY: !1 });
21
+ return { found: discoveryResult.found, selected: discoveryResult.selected ?? void 0 };
22
+ },
23
+ gitHead: currentGitHead
24
+ };
25
+ async function canonicalRoots(model) {
26
+ const resolve = async (dir) => {
27
+ try {
28
+ return await realpath(dir);
29
+ } catch {
30
+ return dir;
31
+ }
32
+ };
33
+ return { projectRoot: await resolve(model.rootPath), codebaseRoot: await resolve(model.codebaseRoot) };
34
+ }
35
+ async function installSuggestions(name) {
36
+ return [
37
+ `Install codebase-memory-mcp (${await analyzerHomepage(name)}), for example: npm install -g codebase-memory-mcp`,
38
+ "Make sure the binary is on the PATH the dr MCP server runs with (restart the client after installing)",
39
+ "Then call codebase_index again"
40
+ ];
41
+ }
42
+ async function notInstalledError(name) {
43
+ return new CLIError(`The code analyzer is not installed: ${name}`, ErrorCategory.USER, await installSuggestions(name));
44
+ }
45
+ function analyzerNotFoundError(name) {
46
+ return new CLIError(`Analyzer not found: ${name}`, ErrorCategory.NOT_FOUND, [
47
+ "Run `dr analyzer discover --reselect` in the project to select an installed analyzer"
48
+ ]);
49
+ }
50
+ const runGit = (args, cwd) => new Promise((resolve) => {
51
+ execFile("git", args, {
52
+ cwd,
53
+ timeout: 1e4,
54
+ maxBuffer: 32 * 1024 * 1024,
55
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
56
+ }, (error, stdout, stderr) => {
57
+ if (!error)
58
+ return resolve({ ok: !0, stdout: String(stdout) });
59
+ const missing = error.code === "ENOENT";
60
+ resolve({ ok: !1, missing, error: String(stderr).trim() || error.message });
61
+ });
62
+ }), DR_FOLDERS = /* @__PURE__ */ new Set(["documentation-robotics", ".dr"]), INDEXED_EXT = /\.(mts|cts|kts|c|cc|cpp|cxx|h|hh|hpp|hxx|lua|dart|groovy)$/i;
63
+ function isIndexedSource(file) {
64
+ const posix = file.replace(/\\/g, "/");
65
+ return posix.split("/").some((segment) => DR_FOLDERS.has(segment)) || VENDOR_EXCLUDE.test(posix) ? !1 : SOURCE_EXT.test(posix) || INDEXED_EXT.test(posix);
66
+ }
67
+ async function workingChanges(git, codebaseRoot) {
68
+ const prefix = await git(["rev-parse", "--show-prefix"], codebaseRoot);
69
+ if (!prefix.ok)
70
+ return null;
71
+ const status = await git(["status", "--porcelain", "-z", "--untracked-files=all", "--", "."], codebaseRoot);
72
+ if (!status.ok)
73
+ return null;
74
+ const top = prefix.stdout.trim(), local = (file) => top ? file.startsWith(top) ? file.slice(top.length) : null : file, changes = [], add = (file, gone) => {
75
+ const l = file ? local(file) : null;
76
+ l && isIndexedSource(l) && changes.push({ file: l, gone });
77
+ }, parts = status.stdout.split("\0");
78
+ for (let i = 0; i < parts.length; i++) {
79
+ const entry = parts[i];
80
+ if (entry.length < 4)
81
+ continue;
82
+ const code = entry.slice(0, 2), file = entry.slice(3);
83
+ if (code[0] === "R" || code[0] === "C") {
84
+ const from = parts[++i];
85
+ add(file, !1), code[0] === "R" && add(from, !0);
86
+ continue;
87
+ }
88
+ add(file, code.includes("D"));
89
+ }
90
+ return changes;
91
+ }
92
+ async function hashWorkingChanges(codebaseRoot, changes) {
93
+ const files = {};
94
+ for (const change of changes) {
95
+ if (change.gone) {
96
+ files[change.file] = null;
97
+ continue;
98
+ }
99
+ try {
100
+ files[change.file] = createHash("sha1").update(await readFile(path.join(codebaseRoot, change.file))).digest("hex");
101
+ } catch {
102
+ files[change.file] = null;
103
+ }
104
+ }
105
+ return files;
106
+ }
107
+ const snapshotPath = (projectRoot, analyzer) => path.join(projectRoot, ".dr", "analyzers", analyzer, "index-worktree.json");
108
+ async function takeWorktreeSnapshot(codebaseRoot, git = runGit) {
109
+ const takenAt = (/* @__PURE__ */ new Date()).toISOString(), changes = await workingChanges(git, codebaseRoot);
110
+ return changes ? { version: 1, takenAt, files: await hashWorkingChanges(codebaseRoot, changes) } : null;
111
+ }
112
+ async function writeWorktreeSnapshot(projectRoot, analyzer, snapshot) {
113
+ await mkdir(path.dirname(snapshotPath(projectRoot, analyzer)), { recursive: !0 }), await writeFile(snapshotPath(projectRoot, analyzer), JSON.stringify(snapshot, null, 2));
114
+ }
115
+ async function readWorktreeSnapshot(projectRoot, analyzer, indexedAt, isAlive) {
116
+ let snapshot;
117
+ try {
118
+ snapshot = JSON.parse(await readFile(snapshotPath(projectRoot, analyzer), "utf-8"));
119
+ } catch {
120
+ return null;
121
+ }
122
+ const job = await readIndexJob(projectRoot, { isAlive }), at = Date.parse(indexedAt);
123
+ return !job || job.jobId !== snapshot.jobId || job.state !== "done" || snapshot.version !== 1 || !(Date.parse(snapshot.takenAt) <= at) || job.finishedAt && Date.parse(job.finishedAt) < at ? null : snapshot;
124
+ }
125
+ const CLOCK_SKEW_MS = 1e3;
126
+ function indexProbe(codebaseRoot, options = {}) {
127
+ const git = options.git ?? runGit;
128
+ return {
129
+ async changedBetween(from, to) {
130
+ if (options.committedChanges)
131
+ return options.committedChanges(codebaseRoot, from, to);
132
+ const result = await git(["diff", "--name-only", "--no-renames", "-z", "--relative", from, to, "--"], codebaseRoot);
133
+ return result.ok ? result.stdout.split("\0").filter((file) => file.length > 0) : null;
134
+ },
135
+ async editedSince(sinceMs) {
136
+ if (options.sourceEdits)
137
+ return options.sourceEdits(codebaseRoot, sinceMs, isIndexedSource);
138
+ const changes = await workingChanges(git, codebaseRoot);
139
+ if (!changes)
140
+ return [];
141
+ if (options.snapshot) {
142
+ const now = await hashWorkingChanges(codebaseRoot, changes), then = options.snapshot.files;
143
+ return [.../* @__PURE__ */ new Set([...Object.keys(now), ...Object.keys(then)])].filter((file) => !(file in now) || !(file in then) || now[file] !== then[file]);
144
+ }
145
+ const limit = Date.now() + CLOCK_SKEW_MS, edited = [];
146
+ for (const change of changes) {
147
+ if (change.gone) {
148
+ edited.push(change.file);
149
+ continue;
150
+ }
151
+ try {
152
+ const mtime = (await stat(path.join(codebaseRoot, change.file))).mtimeMs;
153
+ mtime > sinceMs && mtime <= limit && edited.push(change.file);
154
+ } catch {
155
+ edited.push(change.file);
156
+ }
157
+ }
158
+ return [...new Set(edited)];
159
+ }
160
+ };
161
+ }
162
+ const short = (sha) => sha.slice(0, 7);
163
+ function listed(files) {
164
+ return files.length > 5 ? `${files.slice(0, 5).join(", ")} and ${files.length - 5} more` : files.join(", ");
165
+ }
166
+ async function judgeIndex(index, head, probe) {
167
+ if (head === null)
168
+ return { state: "stale", reason: "the code's git HEAD can't be read", committedSince: [], editedSince: [] };
169
+ let committedSince = [], moved;
170
+ if (head !== index.indexedCommit) {
171
+ const changed = await probe.changedBetween(index.indexedCommit, head);
172
+ if (changed === null)
173
+ return {
174
+ state: "stale",
175
+ reason: `it was taken at git HEAD ${index.indexedCommit}, and the code is now at ${head}`,
176
+ committedSince: [],
177
+ editedSince: []
178
+ };
179
+ if (committedSince = changed.filter(isIndexedSource).sort(), committedSince.length > 0) {
180
+ const n = committedSince.length;
181
+ return {
182
+ state: "stale",
183
+ reason: `${n} source file${n === 1 ? "" : "s"} changed in commits since it was taken at ${short(index.indexedCommit)} (the code is now at ${short(head)}): ${listed(committedSince)}`,
184
+ committedSince,
185
+ editedSince: []
186
+ };
187
+ }
188
+ moved = `the code moved from ${short(index.indexedCommit)} to ${short(head)}, but no commit since changed a source file`;
189
+ }
190
+ const at = Date.parse(index.indexedAt), editedSince = Number.isNaN(at) ? [] : [...await probe.editedSince(at)].sort();
191
+ if (editedSince.length > 0) {
192
+ const n = editedSince.length;
193
+ return {
194
+ state: "fresh-with-uncommitted-edits",
195
+ reason: `${n} source file${n === 1 ? " has" : "s have"} uncommitted changes (edits, deletes or renames) the index hasn't seen: ${listed(editedSince)}`,
196
+ committedSince,
197
+ editedSince
198
+ };
199
+ }
200
+ return { state: "fresh", ...moved ? { reason: moved } : {}, committedSince, editedSince };
201
+ }
202
+ async function assessIndex(projectRoot, analyzer, codebaseRoot, deps, options = {}) {
203
+ const freshness = await readIndexFreshness(projectRoot, analyzer, codebaseRoot, deps.gitHead);
204
+ if (!freshness.indexed)
205
+ return { freshness, judgement: null };
206
+ const snapshot = options.uncommittedEdits === !1 ? null : await readWorktreeSnapshot(projectRoot, analyzer, freshness.meta.timestamp, deps.isAlive), probe = indexProbe(codebaseRoot, { ...deps, snapshot }), judgement = await judgeIndex({ indexedCommit: freshness.meta.git_head, indexedAt: freshness.meta.timestamp }, freshness.currentGitHead, options.uncommittedEdits === !1 ? { ...probe, editedSince: async () => [] } : probe);
207
+ return { freshness, judgement };
208
+ }
209
+ function describeIndex(freshness, judgement) {
210
+ if (!freshness.indexed)
211
+ return { indexed: !1, fresh: !1 };
212
+ const state = judgement?.state ?? (freshness.fresh ? "fresh" : "unknown");
213
+ return {
214
+ indexed: !0,
215
+ fresh: state === "fresh",
216
+ state,
217
+ ...judgement?.reason ? { reason: judgement.reason } : {},
218
+ ...judgement?.editedSince.length ? { uncommittedEdits: judgement.editedSince } : {},
219
+ lastIndexed: freshness.meta.timestamp,
220
+ gitHead: freshness.meta.git_head,
221
+ currentGitHead: freshness.currentGitHead,
222
+ nodeCount: freshness.meta.node_count,
223
+ edgeCount: freshness.meta.edge_count
224
+ };
225
+ }
226
+ const WAIT_FOR_INDEX = "Call codebase_index with status: true and wait: 30 until job.state is done";
227
+ async function requireFreshIndex(projectRoot, codebaseRoot, deps, tool, options = {}) {
228
+ const reindex = `Call codebase_index with wait: 30 (then with status: true and wait: 30 until job.state is done), then call ${tool} again`, job = await readIndexJob(projectRoot, { isAlive: deps.isAlive });
229
+ if (job?.state === "running")
230
+ throw new CLIError(`An index run is in progress (started ${job.startedAt})`, ErrorCategory.USER, [
231
+ `${WAIT_FOR_INDEX}, then call ${tool} again`
232
+ ]);
233
+ const analyzer = (await readSession(projectRoot))?.active_analyzer, { freshness, judgement } = analyzer ? await assessIndex(projectRoot, analyzer, codebaseRoot, deps, { uncommittedEdits: options.uncommittedEdits === !0 }) : { freshness: { indexed: !1, fresh: !1 }, judgement: null };
234
+ if (!analyzer || !freshness.indexed || !judgement)
235
+ throw new CLIError("The code is not indexed", ErrorCategory.USER, [reindex]);
236
+ if (judgement.state === "stale" || freshness.currentGitHead === null) {
237
+ const why = freshness.currentGitHead === null ? `the code's git HEAD can't be read in ${codebaseRoot}` : judgement.reason;
238
+ throw new CLIError(`The index is stale: ${why}`, ErrorCategory.USER, [reindex]);
239
+ }
240
+ return {
241
+ analyzer,
242
+ // `fresh` by the rule, not by HEAD equality, for callers that describe it (describeIndex).
243
+ freshness: { ...freshness, fresh: !0 },
244
+ index: judgement
245
+ };
246
+ }
247
+ export {
248
+ WAIT_FOR_INDEX,
249
+ analyzerNotFoundError,
250
+ assessIndex,
251
+ canonicalRoots,
252
+ defaultCodebaseToolDeps,
253
+ describeIndex,
254
+ indexProbe,
255
+ installSuggestions,
256
+ isIndexedSource,
257
+ judgeIndex,
258
+ notInstalledError,
259
+ readWorktreeSnapshot,
260
+ requireFreshIndex,
261
+ runGit,
262
+ takeWorktreeSnapshot,
263
+ writeWorktreeSnapshot
264
+ };
265
+ //# sourceMappingURL=codebase-shared.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/codebase-shared.ts"],
4
+ "sourcesContent": ["/**\n * Shared pieces of the codebase tools (codebase_index, codebase_verify, codebase_extract): the\n * analyzer access they need, injectable so tests run without codebase-memory-mcp, the errors that\n * tell the agent what to do next, and the one rule for whether the code index is fresh (also used\n * by project_status).\n */\n\nimport { execFile } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AnalyzerRegistry } from \"../../analyzers/registry.js\";\nimport { analyzerHomepage, currentGitHead, readIndexFreshness, type IndexFreshness } from \"../../analyzers/active-analyzer.js\";\nimport { SOURCE_EXT, VENDOR_EXCLUDE } from \"../../analyzers/cards/build.js\";\nimport { readIndexJob } from \"../../analyzers/index-job.js\";\nimport { readSession } from \"../../analyzers/session-state.js\";\nimport type { CodeChangesResult } from \"../../analyzers/code-changes.js\";\nimport type { AnalyzerBackend } from \"../../analyzers/base-analyzer.js\";\nimport type { AvailableAnalyzer } from \"../../analyzers/types.js\";\nimport type { SpawnFn } from \"../../analyzers/index-job.js\";\nimport type { DrCommand } from \"../../coding-agents/drbot-mcp.js\";\nimport { performDiscover } from \"../../commands/discover-logic.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\n\nexport interface CodebaseToolDeps {\n /** The registered backend for an analyzer name. */\n getAnalyzer(name: string): Promise<AnalyzerBackend | undefined>;\n /** Non-interactive discovery: the analyzers found, and the first installed one. */\n discover(): Promise<{ found: AvailableAnalyzer[]; selected?: string }>;\n /** Spawns the background `dr analyzer index` (defaults to node's spawn). */\n spawn?: SpawnFn;\n /** How to run this dr installation (defaults to the running one). */\n drCommand?: DrCommand;\n /** Whether a job's process is still the job (defaults to the pid's liveness and command line). */\n isAlive?: (pid: number) => boolean;\n /** Sends signals, to stop a run that `force` replaces (defaults to `process.kill`). */\n kill?: (pid: number, signal: NodeJS.Signals) => void;\n /** The code's git HEAD (defaults to `git rev-parse HEAD`). */\n gitHead?: (dir: string) => string | null;\n /** Files a git range changed, for codebase_extract's `since` (defaults to git). */\n codeChanges?: (codebaseRoot: string, since: string) => Promise<CodeChangesResult>;\n /** Source files with uncommitted edits newer than a time (defaults to git status and mtimes). */\n sourceEdits?: (codebaseRoot: string, sinceMs: number, isSource: (file: string) => boolean) => Promise<string[]>;\n /**\n * Files changed between two commits, relative to the codebase root; null when git can't say\n * (defaults to git diff). Index freshness reads it when HEAD has moved since the index.\n */\n committedChanges?: (codebaseRoot: string, from: string, to: string) => Promise<string[] | null>;\n /** How long codebase_index's `wait` sleeps between reads of the job (default 1 s). */\n pollIntervalMs?: number;\n /** codebase_extract's time budget in milliseconds (default 100 s). */\n budgetMs?: number;\n}\n\nexport const defaultCodebaseToolDeps: CodebaseToolDeps = {\n async getAnalyzer(name) {\n const registry = AnalyzerRegistry.getInstance();\n await registry.initialize();\n return registry.getAnalyzer(name);\n },\n async discover() {\n const registry = AnalyzerRegistry.getInstance();\n await registry.initialize();\n const { discoveryResult } = await performDiscover(registry, { json: true, isTTY: false });\n return { found: discoveryResult.found, selected: discoveryResult.selected ?? undefined };\n },\n gitHead: currentGitHead,\n};\n\n/**\n * The project and codebase roots, spelled canonically (symlinks resolved). The background child\n * finds its project from its working directory, which the OS reports resolved, and the analyzer\n * matches projects by the exact root string, so both sides must use these spellings.\n */\nexport async function canonicalRoots(model: { rootPath: string; codebaseRoot: string }): Promise<{\n projectRoot: string;\n codebaseRoot: string;\n}> {\n const resolve = async (dir: string) => {\n try {\n return await realpath(dir);\n } catch {\n return dir;\n }\n };\n return { projectRoot: await resolve(model.rootPath), codebaseRoot: await resolve(model.codebaseRoot) };\n}\n\n/** How to install an analyzer, for an agent to relay. */\nexport async function installSuggestions(name: string): Promise<string[]> {\n const homepage = await analyzerHomepage(name);\n return [\n `Install codebase-memory-mcp (${homepage}), for example: npm install -g codebase-memory-mcp`,\n \"Make sure the binary is on the PATH the dr MCP server runs with (restart the client after installing)\",\n \"Then call codebase_index again\",\n ];\n}\n\nexport async function notInstalledError(name: string): Promise<CLIError> {\n return new CLIError(`The code analyzer is not installed: ${name}`, ErrorCategory.USER, await installSuggestions(name));\n}\n\nexport function analyzerNotFoundError(name: string): CLIError {\n return new CLIError(`Analyzer not found: ${name}`, ErrorCategory.NOT_FOUND, [\n \"Run `dr analyzer discover --reselect` in the project to select an installed analyzer\",\n ]);\n}\n\n// ---------------------------------------------------------------------------------------------\n// git\n\nexport type GitResult = { ok: true; stdout: string } | { ok: false; error: string; missing?: boolean };\nexport type GitRunner = (args: string[], cwd: string) => Promise<GitResult>;\n\n/** Runs git read-only: no shell, no optional locks (so it never contends for index.lock), a timeout. */\nexport const runGit: GitRunner = (args, cwd) =>\n new Promise((resolve) => {\n execFile(\n \"git\",\n args,\n {\n cwd,\n timeout: 10_000,\n maxBuffer: 32 * 1024 * 1024,\n env: { ...process.env, GIT_OPTIONAL_LOCKS: \"0\", GIT_TERMINAL_PROMPT: \"0\" },\n },\n (error, stdout, stderr) => {\n if (!error) return resolve({ ok: true, stdout: String(stdout) });\n const missing = (error as NodeJS.ErrnoException).code === \"ENOENT\";\n resolve({ ok: false, missing, error: String(stderr).trim() || error.message });\n }\n );\n });\n\n// ---------------------------------------------------------------------------------------------\n// index freshness: the one rule\n\n/** DR's own folders. Changes under them never make the code index stale. */\nconst DR_FOLDERS = new Set([\"documentation-robotics\", \".dr\"]);\n\n/**\n * Source files the analyzer indexes: what extraction reads (SOURCE_EXT), plus the other languages\n * codebase-memory-mcp supports (spec/analyzers/cbm/analyzer.json) and their header and module\n * variants.\n */\nconst INDEXED_EXT = /\\.(mts|cts|kts|c|cc|cpp|cxx|h|hh|hpp|hxx|lua|dart|groovy)$/i;\n\n/**\n * Whether a change to `file` (relative to the codebase root) can change what the index holds: a\n * source file the analyzer reads, outside DR's own folders and vendored or built code.\n */\nexport function isIndexedSource(file: string): boolean {\n const posix = file.replace(/\\\\/g, \"/\");\n if (posix.split(\"/\").some((segment) => DR_FOLDERS.has(segment))) return false;\n if (VENDOR_EXCLUDE.test(posix)) return false;\n return SOURCE_EXT.test(posix) || INDEXED_EXT.test(posix);\n}\n\n/** An uncommitted change to a source file: `gone` when it was deleted or renamed away. */\ninterface WorkingChange {\n file: string;\n gone: boolean;\n}\n\n/**\n * The source files with uncommitted changes (staged, unstaged or untracked), relative to the\n * codebase root. A delete, and the old path of a rename, come back `gone`. Null when git can't say.\n */\nasync function workingChanges(git: GitRunner, codebaseRoot: string): Promise<WorkingChange[] | null> {\n const prefix = await git([\"rev-parse\", \"--show-prefix\"], codebaseRoot);\n if (!prefix.ok) return null;\n const status = await git([\"status\", \"--porcelain\", \"-z\", \"--untracked-files=all\", \"--\", \".\"], codebaseRoot);\n if (!status.ok) return null;\n const top = prefix.stdout.trim();\n const local = (file: string): string | null => (!top ? file : file.startsWith(top) ? file.slice(top.length) : null);\n const changes: WorkingChange[] = [];\n const add = (file: string | undefined, gone: boolean) => {\n const l = file ? local(file) : null;\n if (l && isIndexedSource(l)) changes.push({ file: l, gone });\n };\n const parts = status.stdout.split(\"\\0\");\n for (let i = 0; i < parts.length; i++) {\n const entry = parts[i];\n if (entry.length < 4) continue;\n const code = entry.slice(0, 2);\n const file = entry.slice(3);\n if (code[0] === \"R\" || code[0] === \"C\") {\n const from = parts[++i];\n add(file, false);\n if (code[0] === \"R\") add(from, true);\n continue;\n }\n add(file, code.includes(\"D\"));\n }\n return changes;\n}\n\n/** A hash of each changed file's content; null for a file that is gone. */\nasync function hashWorkingChanges(codebaseRoot: string, changes: WorkingChange[]): Promise<Record<string, string | null>> {\n const files: Record<string, string | null> = {};\n for (const change of changes) {\n if (change.gone) {\n files[change.file] = null;\n continue;\n }\n try {\n files[change.file] = createHash(\"sha1\").update(await readFile(path.join(codebaseRoot, change.file))).digest(\"hex\");\n } catch {\n files[change.file] = null;\n }\n }\n return files;\n}\n\n/**\n * The uncommitted source files as an index run starts, so a later check can tell any change to\n * them, including a revert (`git stash`, `git checkout -- file`) that no mtime shows. Written by\n * codebase_index next to the index; used only when it belongs to the run that wrote the index.\n */\nexport interface WorktreeSnapshot {\n version: 1;\n jobId: string;\n takenAt: string;\n files: Record<string, string | null>;\n}\n\nconst snapshotPath = (projectRoot: string, analyzer: string) =>\n path.join(projectRoot, \".dr\", \"analyzers\", analyzer, \"index-worktree.json\");\n\n/** The working tree's uncommitted source files now, with their content hashes. */\nexport async function takeWorktreeSnapshot(codebaseRoot: string, git: GitRunner = runGit): Promise<Omit<WorktreeSnapshot, \"jobId\"> | null> {\n const takenAt = new Date().toISOString();\n const changes = await workingChanges(git, codebaseRoot);\n if (!changes) return null;\n return { version: 1, takenAt, files: await hashWorkingChanges(codebaseRoot, changes) };\n}\n\nexport async function writeWorktreeSnapshot(projectRoot: string, analyzer: string, snapshot: WorktreeSnapshot): Promise<void> {\n await mkdir(path.dirname(snapshotPath(projectRoot, analyzer)), { recursive: true });\n await writeFile(snapshotPath(projectRoot, analyzer), JSON.stringify(snapshot, null, 2));\n}\n\n/**\n * The snapshot of the run that wrote the current index, or null. It must belong to the last job,\n * that job must have finished, and the index must have been taken between the snapshot and the\n * job's end; an index from another run (a failed one, or `dr analyzer index` outside MCP) has none.\n */\nexport async function readWorktreeSnapshot(\n projectRoot: string,\n analyzer: string,\n indexedAt: string,\n isAlive?: (pid: number) => boolean\n): Promise<WorktreeSnapshot | null> {\n let snapshot: WorktreeSnapshot;\n try {\n snapshot = JSON.parse(await readFile(snapshotPath(projectRoot, analyzer), \"utf-8\")) as WorktreeSnapshot;\n } catch {\n return null;\n }\n const job = await readIndexJob(projectRoot, { isAlive });\n const at = Date.parse(indexedAt);\n if (!job || job.jobId !== snapshot.jobId || job.state !== \"done\" || snapshot.version !== 1) return null;\n if (!(Date.parse(snapshot.takenAt) <= at) || (job.finishedAt && Date.parse(job.finishedAt) < at)) return null;\n return snapshot;\n}\n\n/** A clock a little ahead of this one still counts as now; beyond that an mtime is skew. */\nconst CLOCK_SKEW_MS = 1000;\n\n/**\n * - `fresh`: no source file changed since the index was taken. HEAD may have moved, by commits\n * that touched only the model, DR's folders or non-source files.\n * - `fresh-with-uncommitted-edits`: no commit changed a source file, but uncommitted source edits\n * were written after the index was taken. The tools still run, with a warning.\n * - `stale`: commits since the index changed source files, or git can't tell.\n */\nexport type IndexState = \"fresh\" | \"fresh-with-uncommitted-edits\" | \"stale\";\n\nexport interface IndexJudgement {\n state: IndexState;\n /** Why the index is not plainly fresh, or why it is fresh though HEAD moved. */\n reason?: string;\n /** Source files changed by commits since the indexed commit. */\n committedSince: string[];\n /** Source files with uncommitted edits written after the index was taken. */\n editedSince: string[];\n}\n\n/** What the rule asks git: commits between two points, and uncommitted edits after a time. */\nexport interface IndexProbe {\n /** Files changed between two commits, relative to the codebase root; null when git can't say. */\n changedBetween(from: string, to: string): Promise<string[] | null>;\n /** Source files with uncommitted edits written after `sinceMs`, relative to the codebase root. */\n editedSince(sinceMs: number): Promise<string[]>;\n}\n\n/**\n * The probe the rule uses: git from the codebase root (async), or the injected deps.\n *\n * Uncommitted changes: with the snapshot of the run that wrote the index, any source file whose\n * uncommitted state differs from the snapshot (edited, deleted, renamed, reverted). Without one,\n * every delete and renamed-away path, and the files written after the index was taken; an mtime\n * in the future is clock skew and is ignored, so it can't ask for a re-index on every call.\n */\nexport function indexProbe(\n codebaseRoot: string,\n options: { git?: GitRunner; snapshot?: WorktreeSnapshot | null } & Pick<CodebaseToolDeps, \"committedChanges\" | \"sourceEdits\"> = {}\n): IndexProbe {\n const git = options.git ?? runGit;\n return {\n async changedBetween(from, to) {\n if (options.committedChanges) return options.committedChanges(codebaseRoot, from, to);\n const result = await git([\"diff\", \"--name-only\", \"--no-renames\", \"-z\", \"--relative\", from, to, \"--\"], codebaseRoot);\n return result.ok ? result.stdout.split(\"\\0\").filter((file) => file.length > 0) : null;\n },\n async editedSince(sinceMs) {\n if (options.sourceEdits) return options.sourceEdits(codebaseRoot, sinceMs, isIndexedSource);\n const changes = await workingChanges(git, codebaseRoot);\n if (!changes) return [];\n if (options.snapshot) {\n const now = await hashWorkingChanges(codebaseRoot, changes);\n const then = options.snapshot.files;\n const files = new Set([...Object.keys(now), ...Object.keys(then)]);\n return [...files].filter((file) => !(file in now) || !(file in then) || now[file] !== then[file]);\n }\n const limit = Date.now() + CLOCK_SKEW_MS;\n const edited: string[] = [];\n for (const change of changes) {\n if (change.gone) {\n edited.push(change.file);\n continue;\n }\n try {\n const mtime = (await stat(path.join(codebaseRoot, change.file))).mtimeMs;\n if (mtime > sinceMs && mtime <= limit) edited.push(change.file);\n } catch {\n edited.push(change.file); // listed by git, gone now\n }\n }\n return [...new Set(edited)];\n },\n };\n}\n\nconst short = (sha: string): string => sha.slice(0, 7);\n\nfunction listed(files: string[]): string {\n return files.length > 5 ? `${files.slice(0, 5).join(\", \")} and ${files.length - 5} more` : files.join(\", \");\n}\n\n/**\n * Whether an index taken at `indexedCommit` and `indexedAt` still describes the code at `head`.\n * The one freshness rule for project_status, codebase_index, codebase_verify and codebase_extract.\n */\nexport async function judgeIndex(\n index: { indexedCommit: string; indexedAt: string },\n head: string | null,\n probe: IndexProbe\n): Promise<IndexJudgement> {\n if (head === null) {\n return { state: \"stale\", reason: \"the code's git HEAD can't be read\", committedSince: [], editedSince: [] };\n }\n let committedSince: string[] = [];\n let moved: string | undefined;\n if (head !== index.indexedCommit) {\n const changed = await probe.changedBetween(index.indexedCommit, head);\n if (changed === null) {\n return {\n state: \"stale\",\n reason: `it was taken at git HEAD ${index.indexedCommit}, and the code is now at ${head}`,\n committedSince: [],\n editedSince: [],\n };\n }\n committedSince = changed.filter(isIndexedSource).sort();\n if (committedSince.length > 0) {\n const n = committedSince.length;\n return {\n state: \"stale\",\n reason:\n `${n} source file${n === 1 ? \"\" : \"s\"} changed in commits since it was taken at ` +\n `${short(index.indexedCommit)} (the code is now at ${short(head)}): ${listed(committedSince)}`,\n committedSince,\n editedSince: [],\n };\n }\n moved = `the code moved from ${short(index.indexedCommit)} to ${short(head)}, but no commit since changed a source file`;\n }\n const at = Date.parse(index.indexedAt);\n const editedSince = Number.isNaN(at) ? [] : [...(await probe.editedSince(at))].sort();\n if (editedSince.length > 0) {\n const n = editedSince.length;\n return {\n state: \"fresh-with-uncommitted-edits\",\n reason: `${n} source file${n === 1 ? \" has\" : \"s have\"} uncommitted changes (edits, deletes or renames) the index hasn't seen: ${listed(editedSince)}`,\n committedSince,\n editedSince,\n };\n }\n return { state: \"fresh\", ...(moved ? { reason: moved } : {}), committedSince, editedSince };\n}\n\n/**\n * The index's freshness by the rule, read the way the codebase tools read it. With\n * `uncommittedEdits: false` it skips the uncommitted-edits check (which never makes an index\n * stale), so `fresh-with-uncommitted-edits` reads as `fresh`.\n */\nexport async function assessIndex(\n projectRoot: string,\n analyzer: string,\n codebaseRoot: string,\n deps: CodebaseToolDeps,\n options: { uncommittedEdits?: boolean } = {}\n): Promise<{ freshness: IndexFreshness; judgement: IndexJudgement | null }> {\n const freshness = await readIndexFreshness(projectRoot, analyzer, codebaseRoot, deps.gitHead);\n if (!freshness.indexed) return { freshness, judgement: null };\n const snapshot =\n options.uncommittedEdits === false ? null : await readWorktreeSnapshot(projectRoot, analyzer, freshness.meta.timestamp, deps.isAlive);\n const probe = indexProbe(codebaseRoot, { ...deps, snapshot });\n const judgement = await judgeIndex(\n { indexedCommit: freshness.meta.git_head, indexedAt: freshness.meta.timestamp },\n freshness.currentGitHead,\n options.uncommittedEdits === false ? { ...probe, editedSince: async () => [] } : probe\n );\n return { freshness, judgement };\n}\n\n/**\n * The index part of a codebase tool result. `fresh` is true only for the `fresh` state. Without a\n * judgement it never judges by HEAD equality: it reports the freshness it was given, which\n * requireFreshIndex sets by the rule, and otherwise says the state is unknown.\n *\n * Pass the judgement whenever there is one, so the state matches project_status: a caller of\n * requireFreshIndex passes its `index` (`describeIndex(result.freshness, result.index)`), and\n * then uncommitted edits read `fresh-with-uncommitted-edits` (with `fresh: false`, the reason and\n * `uncommittedEdits`) instead of a plain `fresh` beside a warning about those edits.\n */\nexport function describeIndex(freshness: IndexFreshness, judgement?: IndexJudgement | null): Record<string, unknown> {\n if (!freshness.indexed) return { indexed: false, fresh: false };\n const state: IndexState | \"unknown\" = judgement?.state ?? (freshness.fresh ? \"fresh\" : \"unknown\");\n return {\n indexed: true,\n fresh: state === \"fresh\",\n state,\n ...(judgement?.reason ? { reason: judgement.reason } : {}),\n ...(judgement?.editedSince.length ? { uncommittedEdits: judgement.editedSince } : {}),\n lastIndexed: freshness.meta.timestamp,\n gitHead: freshness.meta.git_head,\n currentGitHead: freshness.currentGitHead,\n nodeCount: freshness.meta.node_count,\n edgeCount: freshness.meta.edge_count,\n };\n}\n\n/** How an agent waits for an index run: a blocking call, not a tight polling loop. */\nexport const WAIT_FOR_INDEX = \"Call codebase_index with status: true and wait: 30 until job.state is done\";\n\n/**\n * The analyzer and its index, when the index is usable: not stale by the rule (judgeIndex), with\n * no index run in flight. Otherwise a CLIError that points at codebase_index, then `tool` again.\n *\n * Uncommitted edits never block. With `uncommittedEdits: true`, `index.state` is\n * `fresh-with-uncommitted-edits` when uncommitted source edits are newer than the index, and\n * `index.editedSince` lists them for the caller's warning; without it that check is skipped.\n */\nexport async function requireFreshIndex(\n projectRoot: string,\n codebaseRoot: string,\n deps: CodebaseToolDeps,\n tool: string,\n options: { uncommittedEdits?: boolean } = {}\n): Promise<{\n analyzer: string;\n freshness: Extract<IndexFreshness, { indexed: true }> & { currentGitHead: string };\n index: IndexJudgement;\n}> {\n const reindex = `Call codebase_index with wait: 30 (then with status: true and wait: 30 until job.state is done), then call ${tool} again`;\n const job = await readIndexJob(projectRoot, { isAlive: deps.isAlive });\n if (job?.state === \"running\") {\n throw new CLIError(`An index run is in progress (started ${job.startedAt})`, ErrorCategory.USER, [\n `${WAIT_FOR_INDEX}, then call ${tool} again`,\n ]);\n }\n const analyzer = (await readSession(projectRoot))?.active_analyzer;\n const { freshness, judgement } = analyzer\n ? await assessIndex(projectRoot, analyzer, codebaseRoot, deps, { uncommittedEdits: options.uncommittedEdits === true })\n : { freshness: { indexed: false, fresh: false } as const, judgement: null };\n if (!analyzer || !freshness.indexed || !judgement) {\n throw new CLIError(\"The code is not indexed\", ErrorCategory.USER, [reindex]);\n }\n if (judgement.state === \"stale\" || freshness.currentGitHead === null) {\n const why = freshness.currentGitHead === null ? `the code's git HEAD can't be read in ${codebaseRoot}` : judgement.reason;\n throw new CLIError(`The index is stale: ${why}`, ErrorCategory.USER, [reindex]);\n }\n return {\n analyzer,\n // `fresh` by the rule, not by HEAD equality, for callers that describe it (describeIndex).\n freshness: { ...freshness, fresh: true } as Extract<IndexFreshness, { indexed: true }> & { currentGitHead: string },\n index: judgement,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { splitVerifyLayers, VERIFIABLE_LAYERS } from "../../analyzers/active-analyzer.js";
3
+ import { jsonResult, loadModel, rootPathSchema, runTool } from "./shared.js";
4
+ import { analyzerNotFoundError, canonicalRoots, defaultCodebaseToolDeps, requireFreshIndex } from "./codebase-shared.js";
5
+ const inputSchema = {
6
+ layers: z.array(z.string()).optional().describe(`Layers to verify (default ["api"]). Only ${VERIFIABLE_LAYERS.join(", ")} has a check; others are listed as not verified.`),
7
+ rootPath: rootPathSchema
8
+ }, VERIFY_SCOPE = "codebase_verify checks only the API layer: the HTTP routes found in the code against the model's API operations. It says nothing about the other layers.", NO_ROUTES_NOTE = "The analyzer found no HTTP routes in the code (a library, a CLI, or routes it can't read), so every API operation in the model lands in in_model_only. That is expected here and is not drift; don't change the model because of it.";
9
+ function createCodebaseVerifyHandler(deps = defaultCodebaseToolDeps) {
10
+ return async function(args) {
11
+ return runTool(async () => {
12
+ const model = await loadModel(args.rootPath), { projectRoot, codebaseRoot } = await canonicalRoots(model), { verifiable, unsupported } = splitVerifyLayers(args.layers), notVerified = unsupported.map((layer) => ({ layer, reason: "No verification available for this layer" }));
13
+ if (verifiable.length === 0)
14
+ return jsonResult({ layers_verified: [], layers_not_verified: notVerified, scope: VERIFY_SCOPE });
15
+ const { analyzer, index } = await requireFreshIndex(projectRoot, codebaseRoot, deps, "codebase_verify", {
16
+ uncommittedEdits: !0
17
+ }), backend = await deps.getAnalyzer(analyzer);
18
+ if (!backend)
19
+ throw analyzerNotFoundError(analyzer);
20
+ const report = await backend.verify(projectRoot, {
21
+ layers: verifiable,
22
+ changesetAware: !0,
23
+ codebaseRoot
24
+ }), routesInCode = report.buckets.matched.length + report.buckets.in_graph_only.length;
25
+ return jsonResult({
26
+ ...report,
27
+ ...notVerified.length > 0 ? { layers_not_verified: notVerified } : {},
28
+ scope: VERIFY_SCOPE,
29
+ ...routesInCode === 0 ? { note: NO_ROUTES_NOTE } : {},
30
+ ...index.state === "fresh-with-uncommitted-edits" ? { warning: `This report may miss uncommitted edits: ${index.reason}. Call codebase_index with wait: 30 to re-index them.` } : {}
31
+ });
32
+ });
33
+ };
34
+ }
35
+ const codebaseVerifyHandler = createCodebaseVerifyHandler(), codebaseVerifyTool = {
36
+ name: "codebase_verify",
37
+ description: "Checks the API layer only: compares the HTTP routes the code analyzer found in the indexed code with the model's API operations (the active changeset's view when one is active). Returns buckets: matched, in_graph_only (in the code, not the model), in_model_only (in the model, not found in the code) and ignored, with a summary and hints. On a library or a project with no HTTP routes, every operation lands in in_model_only: that is expected, not drift. It doesn't check other layers. Needs a fresh index: if there is none, a run is in flight, or source files changed since, it says so; run codebase_index first. Read-only.",
38
+ inputSchema,
39
+ handler: codebaseVerifyHandler
40
+ };
41
+ export {
42
+ NO_ROUTES_NOTE,
43
+ VERIFY_SCOPE,
44
+ codebaseVerifyHandler,
45
+ codebaseVerifyTool,
46
+ createCodebaseVerifyHandler
47
+ };
48
+ //# sourceMappingURL=codebase-verify.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/codebase-verify.ts"],
4
+ "sourcesContent": ["/**\n * codebase_verify \u2014 compare the HTTP routes the code analyzer found in the code with the model's\n * API operations, as structured data. It checks the API layer only. The same report as\n * `dr analyzer verify --json`, from the same engine (analyzers/verify-engine.ts through the\n * analyzer's verify()). Read-only.\n *\n * It needs a usable index (codebase-shared.ts judgeIndex): when there is none, a run is in\n * flight, or source files changed in commits since the last one, it says so and points at\n * codebase_index. Uncommitted edits newer than the index give a warning, not a refusal.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { splitVerifyLayers, VERIFIABLE_LAYERS } from \"../../analyzers/active-analyzer.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport {\n analyzerNotFoundError,\n canonicalRoots,\n defaultCodebaseToolDeps,\n requireFreshIndex,\n type CodebaseToolDeps,\n} from \"./codebase-shared.js\";\n\nconst inputSchema = {\n layers: z\n .array(z.string())\n .optional()\n .describe(`Layers to verify (default [\"api\"]). Only ${VERIFIABLE_LAYERS.join(\", \")} has a check; others are listed as not verified.`),\n rootPath: rootPathSchema,\n};\n\nexport type CodebaseVerifyArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\n/** What every report says about its reach, so its findings aren't read as more than they are. */\nexport const VERIFY_SCOPE =\n \"codebase_verify checks only the API layer: the HTTP routes found in the code against the model's API operations. \" +\n \"It says nothing about the other layers.\";\n\n/** Said when the code has no routes: a library's operations are not drift. */\nexport const NO_ROUTES_NOTE =\n \"The analyzer found no HTTP routes in the code (a library, a CLI, or routes it can't read), so every API operation \" +\n \"in the model lands in in_model_only. That is expected here and is not drift; don't change the model because of it.\";\n\nexport function createCodebaseVerifyHandler(deps: CodebaseToolDeps = defaultCodebaseToolDeps) {\n return async function codebaseVerifyHandler(args: CodebaseVerifyArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n // The same spelling codebase_index's run indexed under (see canonicalRoots).\n const { projectRoot, codebaseRoot } = await canonicalRoots(model);\n\n const { verifiable, unsupported } = splitVerifyLayers(args.layers);\n const notVerified = unsupported.map((layer) => ({ layer, reason: \"No verification available for this layer\" }));\n if (verifiable.length === 0) {\n return jsonResult({ layers_verified: [], layers_not_verified: notVerified, scope: VERIFY_SCOPE });\n }\n\n const { analyzer, index } = await requireFreshIndex(projectRoot, codebaseRoot, deps, \"codebase_verify\", {\n uncommittedEdits: true,\n });\n\n const backend = await deps.getAnalyzer(analyzer);\n if (!backend) throw analyzerNotFoundError(analyzer);\n\n const report = await backend.verify(projectRoot, {\n layers: verifiable,\n changesetAware: true,\n codebaseRoot,\n });\n const routesInCode = report.buckets.matched.length + report.buckets.in_graph_only.length;\n return jsonResult({\n ...report,\n ...(notVerified.length > 0 ? { layers_not_verified: notVerified } : {}),\n scope: VERIFY_SCOPE,\n ...(routesInCode === 0 ? { note: NO_ROUTES_NOTE } : {}),\n ...(index.state === \"fresh-with-uncommitted-edits\"\n ? { warning: `This report may miss uncommitted edits: ${index.reason}. Call codebase_index with wait: 30 to re-index them.` }\n : {}),\n });\n });\n };\n}\n\nexport const codebaseVerifyHandler = createCodebaseVerifyHandler();\n\nexport const codebaseVerifyTool: McpToolDefinition<typeof inputSchema> = {\n name: \"codebase_verify\",\n description:\n \"Checks the API layer only: compares the HTTP routes the code analyzer found in the indexed code with the \" +\n \"model's API operations (the active changeset's view when one is active). Returns buckets: matched, \" +\n \"in_graph_only (in the code, not the model), in_model_only (in the model, not found in the code) and ignored, \" +\n \"with a summary and hints. On a library or a project with no HTTP routes, every operation lands in \" +\n \"in_model_only: that is expected, not drift. It doesn't check other layers. Needs a fresh index: if there is \" +\n \"none, a run is in flight, or source files changed since, it says so; run codebase_index first. Read-only.\",\n inputSchema,\n handler: codebaseVerifyHandler,\n};\n"],
5
+ "mappings": "AAWA,SAAS,SAAS;AAElB,SAAS,mBAAmB,yBAAyB;AACrD,SAAS,YAAY,WAAW,gBAAgB,eAAuC;AACvF,SACE,uBACA,gBACA,yBACA,yBAEK;AAEP,MAAM,cAAc;EAClB,QAAQ,EACL,MAAM,EAAE,OAAM,CAAE,EAChB,SAAQ,EACR,SAAS,4CAA4C,kBAAkB,KAAK,IAAI,CAAC,kDAAkD;EACtI,UAAU;GAMC,eACX,4JAIW,iBACX;AAGI,SAAU,4BAA4B,OAAyB,yBAAuB;AAC1F,SAAO,eAAqC,MAAwB;AAClE,WAAO,QAAQ,YAAW;AACxB,YAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GAErC,EAAE,aAAa,aAAY,IAAK,MAAM,eAAe,KAAK,GAE1D,EAAE,YAAY,YAAW,IAAK,kBAAkB,KAAK,MAAM,GAC3D,cAAc,YAAY,IAAI,CAAC,WAAW,EAAE,OAAO,QAAQ,2CAA0C,EAAG;AAC9G,UAAI,WAAW,WAAW;AACxB,eAAO,WAAW,EAAE,iBAAiB,CAAA,GAAI,qBAAqB,aAAa,OAAO,aAAY,CAAE;AAGlG,YAAM,EAAE,UAAU,MAAK,IAAK,MAAM,kBAAkB,aAAa,cAAc,MAAM,mBAAmB;QACtG,kBAAkB;OACnB,GAEK,UAAU,MAAM,KAAK,YAAY,QAAQ;AAC/C,UAAI,CAAC;AAAS,cAAM,sBAAsB,QAAQ;AAElD,YAAM,SAAS,MAAM,QAAQ,OAAO,aAAa;QAC/C,QAAQ;QACR,gBAAgB;QAChB;OACD,GACK,eAAe,OAAO,QAAQ,QAAQ,SAAS,OAAO,QAAQ,cAAc;AAClF,aAAO,WAAW;QAChB,GAAG;QACH,GAAI,YAAY,SAAS,IAAI,EAAE,qBAAqB,YAAW,IAAK,CAAA;QACpE,OAAO;QACP,GAAI,iBAAiB,IAAI,EAAE,MAAM,eAAc,IAAK,CAAA;QACpD,GAAI,MAAM,UAAU,iCAChB,EAAE,SAAS,2CAA2C,MAAM,MAAM,wDAAuD,IACzH,CAAA;OACL;IACH,CAAC;EACH;AACF;AAEO,MAAM,wBAAwB,4BAA2B,GAEnD,qBAA4D;EACvE,MAAM;EACN,aACE;EAMF;EACA,SAAS;;",
6
+ "names": []
7
+ }
@@ -26,6 +26,13 @@ import { chatStatusTool } from "./chat-status.js";
26
26
  import { chatLaunchTool } from "./chat-launch.js";
27
27
  import { changesetVerifyTool } from "./changeset-verify.js";
28
28
  import { changesetUpdateTool, changesetUnstageTool, changesetDiscardTool } from "./changeset-editing.js";
29
+ import { projectInitTool, projectUpgradeTool } from "./project-tools.js";
30
+ import { codebaseIndexTool } from "./codebase-index.js";
31
+ import { codebaseVerifyTool } from "./codebase-verify.js";
32
+ import { changesetCommitTool } from "./changeset-commit.js";
33
+ import { reviewOpenTool } from "./review-open.js";
34
+ import { projectStatusTool } from "./project-status.js";
35
+ import { codebaseExtractTool } from "./codebase-extract.js";
29
36
  export {
30
37
  annotationCreateTool,
31
38
  annotationDeleteTool,
@@ -35,6 +42,7 @@ export {
35
42
  annotationUpdateTool,
36
43
  changesetActivateTool,
37
44
  changesetBriefTool,
45
+ changesetCommitTool,
38
46
  changesetCreateTool,
39
47
  changesetDeactivateTool,
40
48
  changesetDiscardTool,
@@ -46,6 +54,9 @@ export {
46
54
  changesetVerifyTool,
47
55
  chatLaunchTool,
48
56
  chatStatusTool,
57
+ codebaseExtractTool,
58
+ codebaseIndexTool,
59
+ codebaseVerifyTool,
49
60
  modelAddTool,
50
61
  modelDeleteTool,
51
62
  modelExportTool,
@@ -58,8 +69,12 @@ export {
58
69
  modelTraceTool,
59
70
  modelUpdateTool,
60
71
  modelValidateTool,
72
+ projectInitTool,
73
+ projectStatusTool,
74
+ projectUpgradeTool,
61
75
  relationshipAddTool,
62
76
  relationshipDeleteTool,
77
+ reviewOpenTool,
63
78
  specTypeTool
64
79
  };
65
80
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/mcp/tools/index.ts"],
4
- "sourcesContent": ["export { annotationListTool } from \"./annotation-list.js\";\nexport { annotationGetTool } from \"./annotation-get.js\";\nexport { annotationCreateTool } from \"./annotation-create.js\";\nexport { annotationUpdateTool } from \"./annotation-update.js\";\nexport { annotationDeleteTool } from \"./annotation-delete.js\";\nexport { annotationReplyTool } from \"./annotation-reply.js\";\nexport { modelListTool } from \"./model-list.js\";\nexport { modelShowTool } from \"./model-show.js\";\nexport { modelSearchTool } from \"./model-search.js\";\nexport { modelStatsTool } from \"./model-stats.js\";\nexport { modelInfoTool } from \"./model-info.js\";\nexport { modelTraceTool } from \"./model-trace.js\";\nexport { modelAddTool } from \"./model-add.js\";\nexport { modelUpdateTool } from \"./model-update.js\";\nexport { modelDeleteTool } from \"./model-delete.js\";\nexport { modelValidateTool } from \"./model-validate.js\";\nexport { modelExportTool } from \"./model-export.js\";\nexport { modelReloadTool } from \"./model-reload.js\";\nexport { changesetListTool } from \"./changeset-list.js\";\nexport { changesetShowTool } from \"./changeset-show.js\";\nexport { changesetCreateTool, changesetActivateTool, changesetDeactivateTool } from \"./changeset-lifecycle.js\";\nexport { changesetImpactTool, changesetBriefTool } from \"./changeset-analysis-tools.js\";\nexport { relationshipAddTool, relationshipDeleteTool } from \"./relationship-tools.js\";\nexport { specTypeTool } from \"./spec-type.js\";\nexport { chatStatusTool } from \"./chat-status.js\";\nexport { chatLaunchTool } from \"./chat-launch.js\";\nexport { changesetVerifyTool } from \"./changeset-verify.js\";\nexport type { McpToolDefinition } from \"./shared.js\";\nexport { changesetUpdateTool, changesetUnstageTool, changesetDiscardTool } from \"./changeset-editing.js\";\n"],
5
- "mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,4BAA4B;AACrC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB,uBAAuB,+BAA+B;AACpF,SAAS,qBAAqB,0BAA0B;AACxD,SAAS,qBAAqB,8BAA8B;AAC5D,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AAEpC,SAAS,qBAAqB,sBAAsB,4BAA4B;",
4
+ "sourcesContent": ["export { annotationListTool } from \"./annotation-list.js\";\nexport { annotationGetTool } from \"./annotation-get.js\";\nexport { annotationCreateTool } from \"./annotation-create.js\";\nexport { annotationUpdateTool } from \"./annotation-update.js\";\nexport { annotationDeleteTool } from \"./annotation-delete.js\";\nexport { annotationReplyTool } from \"./annotation-reply.js\";\nexport { modelListTool } from \"./model-list.js\";\nexport { modelShowTool } from \"./model-show.js\";\nexport { modelSearchTool } from \"./model-search.js\";\nexport { modelStatsTool } from \"./model-stats.js\";\nexport { modelInfoTool } from \"./model-info.js\";\nexport { modelTraceTool } from \"./model-trace.js\";\nexport { modelAddTool } from \"./model-add.js\";\nexport { modelUpdateTool } from \"./model-update.js\";\nexport { modelDeleteTool } from \"./model-delete.js\";\nexport { modelValidateTool } from \"./model-validate.js\";\nexport { modelExportTool } from \"./model-export.js\";\nexport { modelReloadTool } from \"./model-reload.js\";\nexport { changesetListTool } from \"./changeset-list.js\";\nexport { changesetShowTool } from \"./changeset-show.js\";\nexport { changesetCreateTool, changesetActivateTool, changesetDeactivateTool } from \"./changeset-lifecycle.js\";\nexport { changesetImpactTool, changesetBriefTool } from \"./changeset-analysis-tools.js\";\nexport { relationshipAddTool, relationshipDeleteTool } from \"./relationship-tools.js\";\nexport { specTypeTool } from \"./spec-type.js\";\nexport { chatStatusTool } from \"./chat-status.js\";\nexport { chatLaunchTool } from \"./chat-launch.js\";\nexport { changesetVerifyTool } from \"./changeset-verify.js\";\nexport type { McpToolDefinition } from \"./shared.js\";\nexport { changesetUpdateTool, changesetUnstageTool, changesetDiscardTool } from \"./changeset-editing.js\";\nexport { projectInitTool, projectUpgradeTool } from \"./project-tools.js\";\nexport { codebaseIndexTool } from \"./codebase-index.js\";\nexport { codebaseVerifyTool } from \"./codebase-verify.js\";\nexport { changesetCommitTool } from \"./changeset-commit.js\";\nexport { reviewOpenTool } from \"./review-open.js\";\nexport { projectStatusTool } from \"./project-status.js\";\nexport { codebaseExtractTool } from \"./codebase-extract.js\";\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,4BAA4B;AACrC,SAAS,4BAA4B;AACrC,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB,uBAAuB,+BAA+B;AACpF,SAAS,qBAAqB,0BAA0B;AACxD,SAAS,qBAAqB,8BAA8B;AAC5D,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AAEpC,SAAS,qBAAqB,sBAAsB,4BAA4B;AAChF,SAAS,iBAAiB,0BAA0B;AACpD,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;",
6
6
  "names": []
7
7
  }