@praneeth_54/agentdoctor 1.1.1 → 2.0.1

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 (224) hide show
  1. package/CHANGELOG.md +164 -7
  2. package/README.md +263 -513
  3. package/dist/agents/aider/adapter.d.ts +1 -0
  4. package/dist/agents/aider/adapter.js +1 -0
  5. package/dist/agents/aider/detector.d.ts +14 -0
  6. package/dist/agents/aider/detector.js +120 -0
  7. package/dist/agents/copilot/adapter.d.ts +1 -0
  8. package/dist/agents/copilot/adapter.js +1 -0
  9. package/dist/agents/copilot/detector.d.ts +13 -0
  10. package/dist/agents/copilot/detector.js +113 -0
  11. package/dist/agents/gemini/adapter.d.ts +1 -0
  12. package/dist/agents/gemini/adapter.js +1 -0
  13. package/dist/agents/gemini/detector.d.ts +13 -0
  14. package/dist/agents/gemini/detector.js +151 -0
  15. package/dist/agents/registry.js +13 -1
  16. package/dist/agents/types.d.ts +1 -1
  17. package/dist/agents/windsurf/adapter.d.ts +1 -0
  18. package/dist/agents/windsurf/adapter.js +1 -0
  19. package/dist/agents/windsurf/detector.d.ts +14 -0
  20. package/dist/agents/windsurf/detector.js +119 -0
  21. package/dist/architecture/c4.d.ts +22 -0
  22. package/dist/architecture/c4.js +62 -0
  23. package/dist/architecture/contract.d.ts +64 -0
  24. package/dist/architecture/contract.js +373 -0
  25. package/dist/assurance/change.d.ts +211 -0
  26. package/dist/assurance/change.js +624 -0
  27. package/dist/assurance/proof.d.ts +136 -0
  28. package/dist/assurance/proof.js +338 -0
  29. package/dist/auth/index.d.ts +81 -0
  30. package/dist/auth/index.js +130 -0
  31. package/dist/auth/rbac.d.ts +9 -0
  32. package/dist/auth/rbac.js +26 -0
  33. package/dist/cli/commands/architecture.d.ts +6 -0
  34. package/dist/cli/commands/architecture.js +109 -0
  35. package/dist/cli/commands/assurance.d.ts +67 -0
  36. package/dist/cli/commands/assurance.js +210 -0
  37. package/dist/cli/commands/brain.d.ts +12 -0
  38. package/dist/cli/commands/brain.js +162 -0
  39. package/dist/cli/commands/complete.d.ts +23 -0
  40. package/dist/cli/commands/complete.js +232 -0
  41. package/dist/cli/commands/explain.js +1 -1
  42. package/dist/cli/commands/fix.js +22 -2
  43. package/dist/cli/commands/mcp.d.ts +8 -0
  44. package/dist/cli/commands/mcp.js +14 -0
  45. package/dist/cli/commands/platform.d.ts +17 -0
  46. package/dist/cli/commands/platform.js +195 -0
  47. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  48. package/dist/cli/commands/policy-graph-run.js +200 -0
  49. package/dist/cli/commands/v2.d.ts +55 -0
  50. package/dist/cli/commands/v2.js +278 -0
  51. package/dist/cli/commands/workspace.d.ts +9 -0
  52. package/dist/cli/commands/workspace.js +82 -0
  53. package/dist/cli/program.js +1328 -3
  54. package/dist/constants.d.ts +3 -2
  55. package/dist/constants.js +5 -1
  56. package/dist/contracts/adapters.d.ts +7 -0
  57. package/dist/contracts/adapters.js +60 -0
  58. package/dist/contracts/index.d.ts +162 -0
  59. package/dist/contracts/index.js +18 -0
  60. package/dist/core/baseline/store.d.ts +48 -0
  61. package/dist/core/baseline/store.js +163 -0
  62. package/dist/core/brain-cli/service.d.ts +26 -0
  63. package/dist/core/brain-cli/service.js +175 -0
  64. package/dist/core/brain-product/init.d.ts +45 -0
  65. package/dist/core/brain-product/init.js +235 -0
  66. package/dist/core/changes/analyze.d.ts +41 -0
  67. package/dist/core/changes/analyze.js +223 -0
  68. package/dist/core/context-health/analyze.d.ts +15 -0
  69. package/dist/core/context-health/analyze.js +234 -0
  70. package/dist/core/fix/apply.d.ts +21 -4
  71. package/dist/core/fix/apply.js +118 -18
  72. package/dist/core/fix/backup.d.ts +30 -0
  73. package/dist/core/fix/backup.js +193 -0
  74. package/dist/core/fix/plan.d.ts +6 -3
  75. package/dist/core/fix/plan.js +113 -9
  76. package/dist/core/fix/render.d.ts +2 -0
  77. package/dist/core/fix/render.js +36 -21
  78. package/dist/core/fix/run.js +5 -1
  79. package/dist/core/fix/safe-target.d.ts +14 -0
  80. package/dist/core/fix/safe-target.js +81 -0
  81. package/dist/core/fix/types.d.ts +12 -1
  82. package/dist/core/fix/types.js +2 -0
  83. package/dist/core/fix/writers/claude-settings.js +3 -1
  84. package/dist/core/fix/writers/codex-config.d.ts +3 -0
  85. package/dist/core/fix/writers/codex-config.js +24 -3
  86. package/dist/core/fix/writers/cursorignore.js +3 -1
  87. package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
  88. package/dist/core/fix/writers/simple-ignore.js +68 -0
  89. package/dist/core/monorepo/detect.d.ts +13 -0
  90. package/dist/core/monorepo/detect.js +121 -0
  91. package/dist/core/rules/build-context.js +22 -3
  92. package/dist/core/rules/context/generated-directory.js +11 -1
  93. package/dist/core/rules/context/large-instruction-file.js +6 -0
  94. package/dist/core/rules/context/large-log-file.js +16 -1
  95. package/dist/core/rules/ignore.d.ts +11 -1
  96. package/dist/core/rules/ignore.js +22 -1
  97. package/dist/core/rules/instructions/empty-instructions.js +7 -0
  98. package/dist/core/rules/security/env-file-exposure.js +23 -2
  99. package/dist/core/rules/security/private-key-file.js +1 -1
  100. package/dist/core/scanner/scan.js +1 -1
  101. package/dist/core/schemas/validate.d.ts +6 -0
  102. package/dist/core/schemas/validate.js +62 -0
  103. package/dist/core/scoring/compute-scores.d.ts +1 -1
  104. package/dist/core/scoring/compute-scores.js +5 -1
  105. package/dist/core/scoring/placeholder.d.ts +1 -1
  106. package/dist/core/scoring/placeholder.js +5 -1
  107. package/dist/core/secrets/scan.d.ts +29 -0
  108. package/dist/core/secrets/scan.js +172 -0
  109. package/dist/coverage/cobertura.d.ts +6 -0
  110. package/dist/coverage/cobertura.js +52 -0
  111. package/dist/coverage/istanbul.d.ts +7 -0
  112. package/dist/coverage/istanbul.js +134 -0
  113. package/dist/coverage/lcov.d.ts +7 -0
  114. package/dist/coverage/lcov.js +59 -0
  115. package/dist/coverage/load.d.ts +9 -0
  116. package/dist/coverage/load.js +39 -0
  117. package/dist/coverage/types.d.ts +23 -0
  118. package/dist/coverage/types.js +5 -0
  119. package/dist/dashboard/server.d.ts +24 -0
  120. package/dist/dashboard/server.js +402 -0
  121. package/dist/enforcement/runner.d.ts +67 -0
  122. package/dist/enforcement/runner.js +460 -0
  123. package/dist/index.d.ts +34 -0
  124. package/dist/index.js +25 -0
  125. package/dist/integrations/github/pr-analyze.d.ts +28 -0
  126. package/dist/integrations/github/pr-analyze.js +98 -0
  127. package/dist/integrations/local-ai/provider.d.ts +45 -0
  128. package/dist/integrations/local-ai/provider.js +104 -0
  129. package/dist/intelligence/git/analyze.d.ts +34 -0
  130. package/dist/intelligence/git/analyze.js +136 -0
  131. package/dist/intelligence/graph/build.d.ts +15 -0
  132. package/dist/intelligence/graph/build.js +259 -0
  133. package/dist/intelligence/graph/incremental.d.ts +68 -0
  134. package/dist/intelligence/graph/incremental.js +234 -0
  135. package/dist/intelligence/resolve/imports.d.ts +36 -0
  136. package/dist/intelligence/resolve/imports.js +245 -0
  137. package/dist/knowledge/store.d.ts +25 -0
  138. package/dist/knowledge/store.js +91 -0
  139. package/dist/languages/go.d.ts +9 -0
  140. package/dist/languages/go.js +45 -0
  141. package/dist/languages/index.d.ts +11 -0
  142. package/dist/languages/index.js +76 -0
  143. package/dist/languages/php.d.ts +7 -0
  144. package/dist/languages/php.js +193 -0
  145. package/dist/languages/python.d.ts +7 -0
  146. package/dist/languages/python.js +150 -0
  147. package/dist/languages/types.d.ts +42 -0
  148. package/dist/languages/types.js +26 -0
  149. package/dist/languages/typescript.d.ts +3 -0
  150. package/dist/languages/typescript.js +95 -0
  151. package/dist/mcp/agentdoctor/server.d.ts +16 -0
  152. package/dist/mcp/agentdoctor/server.js +67 -0
  153. package/dist/mcp/brain/session.js +7 -7
  154. package/dist/mcp/intelligence/handlers.d.ts +16 -0
  155. package/dist/mcp/intelligence/handlers.js +364 -0
  156. package/dist/mcp/intelligence/path-safety.d.ts +9 -0
  157. package/dist/mcp/intelligence/path-safety.js +63 -0
  158. package/dist/mcp/intelligence/registry.d.ts +8 -0
  159. package/dist/mcp/intelligence/registry.js +228 -0
  160. package/dist/ops/health.d.ts +18 -0
  161. package/dist/ops/health.js +51 -0
  162. package/dist/platform/ai-quality/analyze.d.ts +9 -0
  163. package/dist/platform/ai-quality/analyze.js +65 -0
  164. package/dist/platform/architecture/drift.d.ts +17 -0
  165. package/dist/platform/architecture/drift.js +74 -0
  166. package/dist/platform/auth/local.d.ts +15 -0
  167. package/dist/platform/auth/local.js +58 -0
  168. package/dist/platform/context-security/analyze.d.ts +6 -0
  169. package/dist/platform/context-security/analyze.js +97 -0
  170. package/dist/platform/firewall/evaluate.d.ts +57 -0
  171. package/dist/platform/firewall/evaluate.js +359 -0
  172. package/dist/platform/graph/build.d.ts +6 -0
  173. package/dist/platform/graph/build.js +189 -0
  174. package/dist/platform/health/analyze.d.ts +7 -0
  175. package/dist/platform/health/analyze.js +192 -0
  176. package/dist/platform/index.d.ts +27 -0
  177. package/dist/platform/index.js +89 -0
  178. package/dist/platform/knowledge/analyze.d.ts +20 -0
  179. package/dist/platform/knowledge/analyze.js +96 -0
  180. package/dist/platform/provenance/build.d.ts +44 -0
  181. package/dist/platform/provenance/build.js +65 -0
  182. package/dist/platform/readiness/scorecard.d.ts +5 -0
  183. package/dist/platform/readiness/scorecard.js +98 -0
  184. package/dist/platform/refactor/impact.d.ts +20 -0
  185. package/dist/platform/refactor/impact.js +60 -0
  186. package/dist/platform/reports/export.d.ts +12 -0
  187. package/dist/platform/reports/export.js +92 -0
  188. package/dist/platform/security/redact.d.ts +15 -0
  189. package/dist/platform/security/redact.js +87 -0
  190. package/dist/platform/sessions/store.d.ts +32 -0
  191. package/dist/platform/sessions/store.js +97 -0
  192. package/dist/platform/store.d.ts +6 -0
  193. package/dist/platform/store.js +57 -0
  194. package/dist/platform/test-impact/analyze.d.ts +50 -0
  195. package/dist/platform/test-impact/analyze.js +350 -0
  196. package/dist/platform/time-machine/compare.d.ts +17 -0
  197. package/dist/platform/time-machine/compare.js +62 -0
  198. package/dist/platform/tokens/plan.d.ts +28 -0
  199. package/dist/platform/tokens/plan.js +108 -0
  200. package/dist/platform/types.d.ts +120 -0
  201. package/dist/platform/types.js +5 -0
  202. package/dist/plugins/runtime.d.ts +36 -0
  203. package/dist/plugins/runtime.js +101 -0
  204. package/dist/plugins/sdk.d.ts +29 -0
  205. package/dist/plugins/sdk.js +114 -0
  206. package/dist/policy/compose.d.ts +34 -0
  207. package/dist/policy/compose.js +118 -0
  208. package/dist/policy/packs.d.ts +9 -0
  209. package/dist/policy/packs.js +91 -0
  210. package/dist/reporters/terminal/report.js +38 -5
  211. package/dist/security/paths.d.ts +21 -0
  212. package/dist/security/paths.js +105 -0
  213. package/dist/storage/postgres.d.ts +26 -0
  214. package/dist/storage/postgres.js +90 -0
  215. package/dist/storage/provider.d.ts +30 -0
  216. package/dist/storage/provider.js +109 -0
  217. package/dist/storage/sqlite.d.ts +33 -0
  218. package/dist/storage/sqlite.js +77 -0
  219. package/dist/team/auth.d.ts +35 -0
  220. package/dist/team/auth.js +70 -0
  221. package/dist/types/index.d.ts +5 -1
  222. package/dist/workspace/index.d.ts +69 -0
  223. package/dist/workspace/index.js +220 -0
  224. package/package.json +15 -5
@@ -3,11 +3,26 @@ import { PACKAGE_VERSION } from "../constants.js";
3
3
  import { parseFailOnRules, parseSeverityGate } from "../core/policy/evaluate.js";
4
4
  import { EXIT_CODES } from "../types/index.js";
5
5
  import { runBrainMcpCommand } from "./commands/brain-mcp.js";
6
+ import { runBrainCommand } from "./commands/brain.js";
6
7
  import { runDoctorCommand } from "./commands/doctor.js";
7
8
  import { runExplainCommand } from "./commands/explain.js";
8
9
  import { runFixCommand } from "./commands/fix.js";
9
10
  import { resolveTargetArgument, runScanCommand } from "./commands/scan.js";
10
11
  import { runVerifyCommand } from "./commands/verify.js";
12
+ import { runBaselineCommand, runChangesCommand, runContextHealthCommand, runDashboardCommand, runFixHistoryCommand, runFixUndoCommand, runLocalAiCommand, runPackagesCommand, runPluginsCommand, runPrReviewCommand, runSecretsCommand, } from "./commands/v2.js";
13
+ import { runPlatformCommand } from "./commands/platform.js";
14
+ import { runInitCommand, runV2SurfaceCommand } from "./commands/complete.js";
15
+ import { runMcpCommand } from "./commands/mcp.js";
16
+ import { runChangeAnalyzeCommand, runChangeDiffCommand, runChangeExplainCommand, runChangeStatusCommand, runChangeVerifyCommand, runEvidenceInspectCommand, runEvidenceVerifyCommand, runProofBuildCommand, runProofExplainCommand, runProofExportCommand, runProofInspectCommand, runProofVerifyCommand, } from "./commands/assurance.js";
17
+ import { runGraphSurfaceCommand, runPolicyCheckCommand, runPolicyEnforceCommand, runPolicyExplainCommand, runRunCommand, runRunExplainCommand, } from "./commands/policy-graph-run.js";
18
+ import { runArchitectureCommand } from "./commands/architecture.js";
19
+ import { runWorkspaceCommand } from "./commands/workspace.js";
20
+ import { collectOpsHealth } from "../ops/health.js";
21
+ import { listSessions, loadSession } from "../platform/sessions/store.js";
22
+ import { exportReports } from "../platform/reports/export.js";
23
+ import { runPlatformScan } from "../platform/index.js";
24
+ import { analyzeTestImpact, formatTestImpactHuman, persistTestImpactReport, } from "../platform/test-impact/analyze.js";
25
+ import { resolveRepoRoot } from "../utils/path.js";
11
26
  function parseMinScore(value) {
12
27
  const parsed = Number(value);
13
28
  if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
@@ -80,7 +95,7 @@ export function createProgram() {
80
95
  const program = new Command();
81
96
  addScanOptions(program
82
97
  .name("agentdoctor")
83
- .description("Audit AI coding agent configuration in a repository (local, deterministic, no API key).")
98
+ .description("Engineering intelligence and safety for AI coding agents — scan, knowledge, MCP, and verification (local, deterministic; no API key required for core flows).")
84
99
  .version(PACKAGE_VERSION, "-V, --version", "Print AgentDoctor version")
85
100
  .argument("[path]", "Repository path to scan (default: current directory)")).action(async (pathArg, _options, command) => {
86
101
  await runScanFromCli(pathArg, command);
@@ -93,7 +108,7 @@ export function createProgram() {
93
108
  });
94
109
  program
95
110
  .command("fix")
96
- .description("Apply safe automatic fixes (Cursor .cursorignore, Claude Code Read deny, and Codex filesystem deny for safe context findings)")
111
+ .description("Apply safe automatic fixes (.cursorignore, Claude Code Read deny, Codex filesystem deny, .geminiignore, .aiderignore for safe context findings)")
97
112
  .argument("[path]", "Repository path (default: current directory)")
98
113
  .option("--dry-run", "Show proposed fixes without writing files", false)
99
114
  .option("-y, --yes", "Skip confirmation prompts", false)
@@ -105,6 +120,1295 @@ export function createProgram() {
105
120
  });
106
121
  process.exitCode = code;
107
122
  });
123
+ program
124
+ .command("fix-undo")
125
+ .description("Restore files from a Safe Fix audit backup")
126
+ .argument("<auditId>", "Audit id from fix-history")
127
+ .argument("[path]", "Repository path (default: current directory)")
128
+ .action(async (auditId, pathArg) => {
129
+ process.exitCode = await runFixUndoCommand({
130
+ auditId,
131
+ root: resolveTargetArgument(pathArg),
132
+ });
133
+ });
134
+ program
135
+ .command("fix-history")
136
+ .description("List Safe Fix audit / backup records")
137
+ .argument("[path]", "Repository path (default: current directory)")
138
+ .option("--json", "Emit JSON", false)
139
+ .action(async (pathArg, options) => {
140
+ process.exitCode = await runFixHistoryCommand({
141
+ root: resolveTargetArgument(pathArg),
142
+ json: Boolean(options.json),
143
+ });
144
+ });
145
+ const brain = program.command("brain").description("Project Brain CLI (local, deterministic)");
146
+ brain
147
+ .command("init")
148
+ .description("Ensure Project Brain store directories exist")
149
+ .argument("[path]", "Repository path")
150
+ .option("--json", "Emit JSON", false)
151
+ .action(async (pathArg, _options, command) => {
152
+ const options = command.optsWithGlobals();
153
+ process.exitCode = await runBrainCommand({
154
+ action: "init",
155
+ root: resolveTargetArgument(pathArg),
156
+ json: Boolean(options.json),
157
+ });
158
+ });
159
+ brain
160
+ .command("status")
161
+ .description("Show Project Brain store status")
162
+ .argument("[path]", "Repository path")
163
+ .option("--json", "Emit JSON", false)
164
+ .action(async (pathArg, _options, command) => {
165
+ const options = command.optsWithGlobals();
166
+ process.exitCode = await runBrainCommand({
167
+ action: "status",
168
+ root: resolveTargetArgument(pathArg),
169
+ json: Boolean(options.json),
170
+ });
171
+ });
172
+ brain
173
+ .command("inspect")
174
+ .description("Inspect latest Project Brain summary")
175
+ .argument("[path]", "Repository path")
176
+ .option("--json", "Emit JSON", false)
177
+ .action(async (pathArg, _options, command) => {
178
+ const options = command.optsWithGlobals();
179
+ process.exitCode = await runBrainCommand({
180
+ action: "inspect",
181
+ root: resolveTargetArgument(pathArg),
182
+ json: Boolean(options.json),
183
+ });
184
+ });
185
+ brain
186
+ .command("rebuild")
187
+ .description("Compile and save a new Project Brain snapshot")
188
+ .argument("[path]", "Repository path")
189
+ .option("--json", "Emit JSON", false)
190
+ .action(async (pathArg, _options, command) => {
191
+ const options = command.optsWithGlobals();
192
+ process.exitCode = await runBrainCommand({
193
+ action: "rebuild",
194
+ root: resolveTargetArgument(pathArg),
195
+ json: Boolean(options.json),
196
+ });
197
+ });
198
+ brain
199
+ .command("history")
200
+ .description("List Project Brain snapshots")
201
+ .argument("[path]", "Repository path")
202
+ .option("--json", "Emit JSON", false)
203
+ .action(async (pathArg, _options, command) => {
204
+ const options = command.optsWithGlobals();
205
+ process.exitCode = await runBrainCommand({
206
+ action: "history",
207
+ root: resolveTargetArgument(pathArg),
208
+ json: Boolean(options.json),
209
+ });
210
+ });
211
+ brain
212
+ .command("search")
213
+ .description("Search claims/components in the latest brain")
214
+ .argument("<query>", "Search text")
215
+ .argument("[path]", "Repository path")
216
+ .option("--json", "Emit JSON", false)
217
+ .action(async (query, pathArg, _options, command) => {
218
+ const options = command.optsWithGlobals();
219
+ process.exitCode = await runBrainCommand({
220
+ action: "search",
221
+ query,
222
+ root: resolveTargetArgument(pathArg),
223
+ json: Boolean(options.json),
224
+ });
225
+ });
226
+ brain
227
+ .command("export")
228
+ .description("Export a redacted brain snapshot to JSON")
229
+ .requiredOption("--file <path>", "Output file")
230
+ .option("--snapshot <id>", "Snapshot id (default: latest)")
231
+ .argument("[path]", "Repository path")
232
+ .action(async (pathArg, options) => {
233
+ process.exitCode = await runBrainCommand({
234
+ action: "export",
235
+ file: options.file,
236
+ ...(options.snapshot ? { snapshotId: options.snapshot } : {}),
237
+ root: resolveTargetArgument(pathArg),
238
+ });
239
+ });
240
+ brain
241
+ .command("import")
242
+ .description("Import a brain JSON snapshot into the local store")
243
+ .requiredOption("--file <path>", "Input file")
244
+ .argument("[path]", "Repository path")
245
+ .action(async (pathArg, options) => {
246
+ process.exitCode = await runBrainCommand({
247
+ action: "import",
248
+ file: options.file,
249
+ root: resolveTargetArgument(pathArg),
250
+ });
251
+ });
252
+ brain
253
+ .command("snapshot")
254
+ .description("Create Brain + Repository Brain product snapshots")
255
+ .argument("[path]", "Repository path")
256
+ .option("--json", "Emit JSON", false)
257
+ .action(async (pathArg, _options, command) => {
258
+ const options = command.optsWithGlobals();
259
+ process.exitCode = await runBrainCommand({
260
+ action: "snapshot",
261
+ root: resolveTargetArgument(pathArg),
262
+ json: Boolean(options.json),
263
+ });
264
+ });
265
+ brain
266
+ .command("update")
267
+ .description("Rebuild Project Brain and write product snapshot")
268
+ .argument("[path]", "Repository path")
269
+ .option("--json", "Emit JSON", false)
270
+ .action(async (pathArg, _options, command) => {
271
+ const options = command.optsWithGlobals();
272
+ process.exitCode = await runBrainCommand({
273
+ action: "update",
274
+ root: resolveTargetArgument(pathArg),
275
+ json: Boolean(options.json),
276
+ });
277
+ });
278
+ brain
279
+ .command("review")
280
+ .description("Human review of Repository Brain proposals (never auto-approves)")
281
+ .requiredOption("--artifact <id>", "Proposal artifact id")
282
+ .requiredOption("--decision <status>", "approved|rejected|pending-review|deprecated")
283
+ .option("--note <text>", "Review note")
284
+ .argument("[path]", "Repository path")
285
+ .option("--json", "Emit JSON", false)
286
+ .action(async (pathArg, _options, command) => {
287
+ const options = command.optsWithGlobals();
288
+ process.exitCode = await runBrainCommand({
289
+ action: "review",
290
+ artifactId: options.artifact,
291
+ decision: options.decision,
292
+ ...(options.note ? { note: options.note } : {}),
293
+ root: resolveTargetArgument(pathArg),
294
+ json: Boolean(options.json),
295
+ });
296
+ });
297
+ brain
298
+ .command("proposals")
299
+ .description("List Repository Brain proposal artifacts")
300
+ .argument("[path]", "Repository path")
301
+ .option("--json", "Emit JSON", false)
302
+ .action(async (pathArg, _options, command) => {
303
+ const options = command.optsWithGlobals();
304
+ process.exitCode = await runBrainCommand({
305
+ action: "proposals",
306
+ root: resolveTargetArgument(pathArg),
307
+ json: Boolean(options.json),
308
+ });
309
+ });
310
+ program
311
+ .command("init")
312
+ .description("Repository Brain initializer — PROPOSED artifacts only (not approved facts)")
313
+ .argument("[path]", "Repository path")
314
+ .option("--name <name>", "Project name")
315
+ .option("--domain <domain>", "Business domain")
316
+ .option("--json", "Emit JSON", false)
317
+ .action(async (pathArg, _options, command) => {
318
+ const options = command.optsWithGlobals();
319
+ process.exitCode = await runInitCommand({
320
+ root: resolveTargetArgument(pathArg),
321
+ json: Boolean(options.json),
322
+ ...(options.name ? { name: options.name } : {}),
323
+ ...(options.domain ? { domain: options.domain } : {}),
324
+ });
325
+ });
326
+ const graph = program
327
+ .command("graph")
328
+ .description("Build / update intelligence graph (TypeScript AST when available; regex fallback). Bare `graph` aliases build.")
329
+ .argument("[path]", "Repository path")
330
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
331
+ .option("--json", "Emit JSON", false)
332
+ .action(async (pathArg, _options, command) => {
333
+ const options = command.optsWithGlobals();
334
+ // Backward compat: `agentdoctor graph --json` → build + persist
335
+ process.exitCode = await runGraphSurfaceCommand({
336
+ action: "build",
337
+ root: resolveTargetArgument(pathArg),
338
+ mode: options.mode ?? "auto",
339
+ json: Boolean(options.json),
340
+ });
341
+ });
342
+ graph
343
+ .command("build")
344
+ .description("Build graph and persist .agentdoctor/graph/index.json")
345
+ .argument("[path]", "Repository path")
346
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
347
+ .option("--json", "Emit JSON", false)
348
+ .action(async (pathArg, options) => {
349
+ process.exitCode = await runGraphSurfaceCommand({
350
+ action: "build",
351
+ root: resolveTargetArgument(pathArg),
352
+ mode: options.mode ?? "auto",
353
+ json: Boolean(options.json),
354
+ });
355
+ });
356
+ graph
357
+ .command("update")
358
+ .description("Incrementally update graph from git/hash changes (rebuilds on corruption)")
359
+ .argument("[path]", "Repository path")
360
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
361
+ .option("--json", "Emit JSON", false)
362
+ .action(async (pathArg, options) => {
363
+ process.exitCode = await runGraphSurfaceCommand({
364
+ action: "update",
365
+ root: resolveTargetArgument(pathArg),
366
+ mode: options.mode ?? "auto",
367
+ json: Boolean(options.json),
368
+ });
369
+ });
370
+ graph
371
+ .command("rebuild")
372
+ .description("Force full graph rebuild")
373
+ .argument("[path]", "Repository path")
374
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
375
+ .option("--json", "Emit JSON", false)
376
+ .action(async (pathArg, options) => {
377
+ process.exitCode = await runGraphSurfaceCommand({
378
+ action: "rebuild",
379
+ root: resolveTargetArgument(pathArg),
380
+ mode: options.mode ?? "auto",
381
+ json: Boolean(options.json),
382
+ });
383
+ });
384
+ graph
385
+ .command("status")
386
+ .description("Show persisted graph index status")
387
+ .argument("[path]", "Repository path")
388
+ .option("--json", "Emit JSON", false)
389
+ .action(async (pathArg, options) => {
390
+ process.exitCode = await runGraphSurfaceCommand({
391
+ action: "status",
392
+ root: resolveTargetArgument(pathArg),
393
+ json: Boolean(options.json),
394
+ });
395
+ });
396
+ program
397
+ .command("health")
398
+ .description("Git hotspot / engineering intelligence report")
399
+ .argument("[path]", "Repository path")
400
+ .option("--json", "Emit JSON", false)
401
+ .action(async (pathArg, _options, command) => {
402
+ const options = command.optsWithGlobals();
403
+ process.exitCode = await runV2SurfaceCommand({
404
+ action: "health",
405
+ root: resolveTargetArgument(pathArg),
406
+ json: Boolean(options.json),
407
+ });
408
+ });
409
+ program
410
+ .command("impact")
411
+ .description("Change/test impact analysis (heuristic by default; coverage-backed with --coverage)")
412
+ .argument("[path]", "Repository path")
413
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
414
+ .option("--since <ref>", "Diff since git ref")
415
+ .option("--json", "Emit JSON", false)
416
+ .action(async (pathArg, _options, command) => {
417
+ const options = command.optsWithGlobals();
418
+ const root = resolveTargetArgument(pathArg);
419
+ try {
420
+ const impact = await analyzeTestImpact({
421
+ root,
422
+ ...(options.coverage ? { coveragePath: options.coverage } : {}),
423
+ ...(options.since ? { since: options.since } : {}),
424
+ });
425
+ if (options.json) {
426
+ process.stdout.write(`${JSON.stringify(impact, null, 2)}\n`);
427
+ }
428
+ else {
429
+ process.stdout.write(formatTestImpactHuman(impact));
430
+ }
431
+ process.exitCode = EXIT_CODES.SUCCESS;
432
+ }
433
+ catch (error) {
434
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
435
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
436
+ }
437
+ });
438
+ program
439
+ .command("test-impact")
440
+ .description("Test-impact analysis (alias of impact)")
441
+ .argument("[path]", "Repository path")
442
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
443
+ .option("--since <ref>", "Diff since git ref")
444
+ .option("--json", "Emit JSON", false)
445
+ .action(async (pathArg, _options, command) => {
446
+ const options = command.optsWithGlobals();
447
+ const root = resolveTargetArgument(pathArg);
448
+ try {
449
+ const impact = await analyzeTestImpact({
450
+ root,
451
+ ...(options.coverage ? { coveragePath: options.coverage } : {}),
452
+ ...(options.since ? { since: options.since } : {}),
453
+ });
454
+ const out = await persistTestImpactReport(resolveRepoRoot(root), impact);
455
+ if (options.json) {
456
+ process.stdout.write(`${JSON.stringify({ ...impact, reportPath: out }, null, 2)}\n`);
457
+ }
458
+ else {
459
+ process.stdout.write(formatTestImpactHuman(impact));
460
+ process.stdout.write(` wrote: ${out}\n`);
461
+ }
462
+ process.exitCode = EXIT_CODES.SUCCESS;
463
+ }
464
+ catch (error) {
465
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
466
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
467
+ }
468
+ });
469
+ program
470
+ .command("refactor-impact")
471
+ .description("Refactor/rename blast-radius analysis")
472
+ .requiredOption("--symbol <name>", "Symbol to analyze")
473
+ .argument("[path]", "Repository path")
474
+ .option("--json", "Emit JSON", false)
475
+ .action(async (pathArg, _options, command) => {
476
+ const options = command.optsWithGlobals();
477
+ process.exitCode = await runV2SurfaceCommand({
478
+ action: "refactor-impact",
479
+ root: resolveTargetArgument(pathArg),
480
+ symbol: options.symbol,
481
+ json: Boolean(options.json),
482
+ });
483
+ });
484
+ program
485
+ .command("session")
486
+ .description("List or show agent sessions")
487
+ .argument("[path]", "Repository path")
488
+ .option("--id <id>", "Session id to show")
489
+ .option("--json", "Emit JSON", false)
490
+ .action(async (pathArg, _options, command) => {
491
+ const options = command.optsWithGlobals();
492
+ const root = resolveTargetArgument(pathArg);
493
+ try {
494
+ if (options.id) {
495
+ const session = await loadSession(root, options.id);
496
+ if (options.json)
497
+ process.stdout.write(`${JSON.stringify(session, null, 2)}\n`);
498
+ else
499
+ process.stdout.write(`${session?.id ?? "not-found"}\n`);
500
+ }
501
+ else {
502
+ const sessions = await listSessions(root);
503
+ if (options.json)
504
+ process.stdout.write(`${JSON.stringify(sessions, null, 2)}\n`);
505
+ else
506
+ for (const id of sessions)
507
+ process.stdout.write(`${id}\n`);
508
+ }
509
+ process.exitCode = EXIT_CODES.SUCCESS;
510
+ }
511
+ catch (error) {
512
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
513
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
514
+ }
515
+ });
516
+ program
517
+ .command("report")
518
+ .description("Run platform scan and export local reports")
519
+ .argument("[path]", "Repository path")
520
+ .option("--json", "Emit JSON", false)
521
+ .action(async (pathArg, _options, command) => {
522
+ const options = command.optsWithGlobals();
523
+ const root = resolveTargetArgument(pathArg);
524
+ try {
525
+ const result = await runPlatformScan(root);
526
+ const paths = await exportReports({
527
+ root,
528
+ findings: result.snapshot.findings,
529
+ readiness: result.snapshot.readiness,
530
+ snapshot: result.snapshot,
531
+ });
532
+ if (options.json)
533
+ process.stdout.write(`${JSON.stringify({ ...result.reportPaths, ...paths }, null, 2)}\n`);
534
+ else
535
+ process.stdout.write(`Reports written under .agentdoctor/platform/\n`);
536
+ process.exitCode = EXIT_CODES.SUCCESS;
537
+ }
538
+ catch (error) {
539
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
540
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
541
+ }
542
+ });
543
+ program
544
+ .command("c4")
545
+ .description("C4-style architecture views from graph evidence")
546
+ .argument("[path]", "Repository path")
547
+ .option("--json", "Emit JSON", false)
548
+ .action(async (pathArg, _options, command) => {
549
+ const options = command.optsWithGlobals();
550
+ process.exitCode = await runV2SurfaceCommand({
551
+ action: "c4",
552
+ root: resolveTargetArgument(pathArg),
553
+ json: Boolean(options.json),
554
+ });
555
+ });
556
+ const architecture = program
557
+ .command("architecture")
558
+ .description("Architecture contract + C4 views (rules are advisory until you treat check as a gate)");
559
+ architecture
560
+ .command("init")
561
+ .description("Write default .agentdoctor/architecture.json if missing")
562
+ .argument("[path]", "Repository path")
563
+ .option("--json", "Emit JSON", false)
564
+ .action(async (pathArg, options) => {
565
+ process.exitCode = await runArchitectureCommand({
566
+ action: "init",
567
+ root: resolveTargetArgument(pathArg),
568
+ json: Boolean(options.json),
569
+ });
570
+ });
571
+ architecture
572
+ .command("analyze")
573
+ .description("C4 views + architecture contract summary")
574
+ .argument("[path]", "Repository path")
575
+ .option("--json", "Emit JSON", false)
576
+ .action(async (pathArg, options) => {
577
+ process.exitCode = await runArchitectureCommand({
578
+ action: "analyze",
579
+ root: resolveTargetArgument(pathArg),
580
+ json: Boolean(options.json),
581
+ });
582
+ });
583
+ architecture
584
+ .command("check")
585
+ .description("Check import edges against .agentdoctor/architecture.json|.yml")
586
+ .argument("[path]", "Repository path")
587
+ .option("--json", "Emit JSON", false)
588
+ .action(async (pathArg, options) => {
589
+ process.exitCode = await runArchitectureCommand({
590
+ action: "check",
591
+ root: resolveTargetArgument(pathArg),
592
+ json: Boolean(options.json),
593
+ });
594
+ });
595
+ architecture
596
+ .command("explain")
597
+ .description("Explain architecture layers, forbidden/allowed rules, and recent violations")
598
+ .argument("[path]", "Repository path")
599
+ .option("--json", "Emit JSON", false)
600
+ .action(async (pathArg, options) => {
601
+ process.exitCode = await runArchitectureCommand({
602
+ action: "explain",
603
+ root: resolveTargetArgument(pathArg),
604
+ json: Boolean(options.json),
605
+ });
606
+ });
607
+ program
608
+ .command("knowledge")
609
+ .description("List governed knowledge records")
610
+ .argument("[path]", "Repository path")
611
+ .option("--json", "Emit JSON", false)
612
+ .action(async (pathArg, _options, command) => {
613
+ const options = command.optsWithGlobals();
614
+ process.exitCode = await runV2SurfaceCommand({
615
+ action: "knowledge-list",
616
+ root: resolveTargetArgument(pathArg),
617
+ json: Boolean(options.json),
618
+ });
619
+ });
620
+ program
621
+ .command("knowledge-create")
622
+ .description("Create a draft knowledge record")
623
+ .requiredOption("--title <title>", "Title")
624
+ .requiredOption("--content <text>", "Content")
625
+ .argument("[path]", "Repository path")
626
+ .option("--json", "Emit JSON", false)
627
+ .action(async (pathArg, _options, command) => {
628
+ const options = command.optsWithGlobals();
629
+ process.exitCode = await runV2SurfaceCommand({
630
+ action: "knowledge-create",
631
+ root: resolveTargetArgument(pathArg),
632
+ title: options.title,
633
+ content: options.content,
634
+ json: Boolean(options.json),
635
+ });
636
+ });
637
+ program
638
+ .command("knowledge-approve")
639
+ .description("Transition knowledge record (explicit human approval)")
640
+ .requiredOption("--id <id>", "Record id")
641
+ .requiredOption("--decision <status>", "approved|rejected|pending-review|deprecated|draft")
642
+ .argument("[path]", "Repository path")
643
+ .option("--json", "Emit JSON", false)
644
+ .action(async (pathArg, _options, command) => {
645
+ const options = command.optsWithGlobals();
646
+ process.exitCode = await runV2SurfaceCommand({
647
+ action: "knowledge-transition",
648
+ root: resolveTargetArgument(pathArg),
649
+ id: options.id,
650
+ decision: options.decision,
651
+ json: Boolean(options.json),
652
+ });
653
+ });
654
+ program
655
+ .command("enforce")
656
+ .description("AgentDoctor-controlled runner policy check (does not execute by default)")
657
+ .requiredOption("--command <cmd>", "Command string")
658
+ .argument("[path]", "Repository path")
659
+ .option("--json", "Emit JSON", false)
660
+ .action(async (pathArg, _options, command) => {
661
+ const options = command.optsWithGlobals();
662
+ process.exitCode = await runV2SurfaceCommand({
663
+ action: "enforce-check",
664
+ root: resolveTargetArgument(pathArg),
665
+ command: options.command,
666
+ json: Boolean(options.json),
667
+ });
668
+ });
669
+ program
670
+ .command("team-register")
671
+ .description("Register local-dev team user (NOT enterprise SSO)")
672
+ .requiredOption("--username <name>", "Username")
673
+ .requiredOption("--password <password>", "Password (min 8)")
674
+ .argument("[path]", "Repository path")
675
+ .option("--json", "Emit JSON", false)
676
+ .action(async (pathArg, _options, command) => {
677
+ const options = command.optsWithGlobals();
678
+ process.exitCode = await runV2SurfaceCommand({
679
+ action: "team-register",
680
+ root: resolveTargetArgument(pathArg),
681
+ username: options.username,
682
+ password: options.password,
683
+ json: Boolean(options.json),
684
+ });
685
+ });
686
+ program
687
+ .command("team-login")
688
+ .description("Authenticate local-dev team user (NOT enterprise SSO)")
689
+ .requiredOption("--username <name>", "Username")
690
+ .requiredOption("--password <password>", "Password")
691
+ .argument("[path]", "Repository path")
692
+ .option("--json", "Emit JSON", false)
693
+ .action(async (pathArg, _options, command) => {
694
+ const options = command.optsWithGlobals();
695
+ process.exitCode = await runV2SurfaceCommand({
696
+ action: "team-login",
697
+ root: resolveTargetArgument(pathArg),
698
+ username: options.username,
699
+ password: options.password,
700
+ json: Boolean(options.json),
701
+ });
702
+ });
703
+ const change = program
704
+ .command("change")
705
+ .description("Change assurance — structured assessment and evidence (never claims verified without evidence)");
706
+ change
707
+ .command("analyze")
708
+ .description("Assemble a ChangeAssessment from git, graph, policy, knowledge, and test signals")
709
+ .argument("[path]", "Repository path")
710
+ .option("--since <ref>", "Diff since git ref (e.g. main)")
711
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
712
+ .option("--json", "Emit JSON", false)
713
+ .action(async (pathArg, options) => {
714
+ process.exitCode = await runChangeAnalyzeCommand({
715
+ root: resolveTargetArgument(pathArg),
716
+ ...(options.since ? { since: options.since } : {}),
717
+ ...(options.coverage ? { coverage: options.coverage } : {}),
718
+ json: Boolean(options.json),
719
+ });
720
+ });
721
+ change
722
+ .command("verify")
723
+ .description("Produce a durable evidence bundle under .agentdoctor/evidence/<id>/ (status: evidence-produced)")
724
+ .argument("[path]", "Repository path")
725
+ .option("--since <ref>", "Diff since git ref (e.g. main)")
726
+ .option("--change-id <id>", "Reuse a change id")
727
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
728
+ .option("--json", "Emit JSON", false)
729
+ .action(async (pathArg, options) => {
730
+ process.exitCode = await runChangeVerifyCommand({
731
+ root: resolveTargetArgument(pathArg),
732
+ ...(options.since ? { since: options.since } : {}),
733
+ ...(options.changeId ? { changeId: options.changeId } : {}),
734
+ ...(options.coverage ? { coverage: options.coverage } : {}),
735
+ json: Boolean(options.json),
736
+ });
737
+ });
738
+ change
739
+ .command("explain")
740
+ .description("Human explanation of a change assessment (does not claim correctness)")
741
+ .argument("[path]", "Repository path")
742
+ .option("--since <ref>", "Diff since git ref")
743
+ .option("--change-id <id>", "Change id")
744
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
745
+ .option("--json", "Emit JSON", false)
746
+ .action(async (pathArg, options) => {
747
+ process.exitCode = await runChangeExplainCommand({
748
+ root: resolveTargetArgument(pathArg),
749
+ ...(options.since ? { since: options.since } : {}),
750
+ ...(options.changeId ? { changeId: options.changeId } : {}),
751
+ ...(options.coverage ? { coverage: options.coverage } : {}),
752
+ json: Boolean(options.json),
753
+ });
754
+ });
755
+ change
756
+ .command("diff")
757
+ .description("Summarize base/target file diffs from git")
758
+ .argument("[path]", "Repository path")
759
+ .option("--since <ref>", "Diff since git ref")
760
+ .option("--change-id <id>", "Change id (label only)")
761
+ .option("--json", "Emit JSON", false)
762
+ .action(async (pathArg, options) => {
763
+ process.exitCode = await runChangeDiffCommand({
764
+ root: resolveTargetArgument(pathArg),
765
+ ...(options.since ? { since: options.since } : {}),
766
+ ...(options.changeId ? { changeId: options.changeId } : {}),
767
+ json: Boolean(options.json),
768
+ });
769
+ });
770
+ change
771
+ .command("status")
772
+ .description("Verification / evidence status for latest or --change-id")
773
+ .argument("[path]", "Repository path")
774
+ .option("--change-id <id>", "Change id")
775
+ .option("--json", "Emit JSON", false)
776
+ .action(async (pathArg, options) => {
777
+ process.exitCode = await runChangeStatusCommand({
778
+ root: resolveTargetArgument(pathArg),
779
+ ...(options.changeId ? { changeId: options.changeId } : {}),
780
+ json: Boolean(options.json),
781
+ });
782
+ });
783
+ const evidence = program
784
+ .command("evidence")
785
+ .description("Inspect and hash-verify change evidence bundles");
786
+ evidence
787
+ .command("inspect")
788
+ .description("List evidence artifacts for a change id")
789
+ .argument("<changeId>", "Change id (chg_…)")
790
+ .argument("[path]", "Repository path")
791
+ .option("--json", "Emit JSON", false)
792
+ .action(async (changeId, pathArg, options) => {
793
+ process.exitCode = await runEvidenceInspectCommand({
794
+ root: resolveTargetArgument(pathArg),
795
+ changeId,
796
+ json: Boolean(options.json),
797
+ });
798
+ });
799
+ evidence
800
+ .command("verify")
801
+ .description("Hash-check evidence bundle; status verified only when all hashes match")
802
+ .argument("<changeId>", "Change id (chg_…)")
803
+ .argument("[path]", "Repository path")
804
+ .option("--json", "Emit JSON", false)
805
+ .action(async (changeId, pathArg, options) => {
806
+ process.exitCode = await runEvidenceVerifyCommand({
807
+ root: resolveTargetArgument(pathArg),
808
+ changeId,
809
+ json: Boolean(options.json),
810
+ });
811
+ });
812
+ const proof = program
813
+ .command("proof")
814
+ .description("ChangeProof — hash integrity over evidence (never claims engineering correctness)");
815
+ proof
816
+ .command("build")
817
+ .description("Build a ChangeProof from an evidence bundle")
818
+ .argument("<changeId>", "Change id (chg_…)")
819
+ .argument("[path]", "Repository path")
820
+ .option("--json", "Emit JSON", false)
821
+ .action(async (changeId, pathArg, options) => {
822
+ process.exitCode = await runProofBuildCommand({
823
+ root: resolveTargetArgument(pathArg),
824
+ changeId,
825
+ json: Boolean(options.json),
826
+ });
827
+ });
828
+ proof
829
+ .command("inspect")
830
+ .description("Inspect a proof by proofId or changeId")
831
+ .argument("<id>", "proofId or changeId")
832
+ .argument("[path]", "Repository path")
833
+ .option("--json", "Emit JSON", false)
834
+ .action(async (id, pathArg, options) => {
835
+ process.exitCode = await runProofInspectCommand({
836
+ root: resolveTargetArgument(pathArg),
837
+ id,
838
+ json: Boolean(options.json),
839
+ });
840
+ });
841
+ proof
842
+ .command("explain")
843
+ .description("Human explanation of a ChangeProof (does not claim correctness)")
844
+ .argument("<id>", "proofId or changeId")
845
+ .argument("[path]", "Repository path")
846
+ .option("--json", "Emit JSON", false)
847
+ .action(async (id, pathArg, options) => {
848
+ process.exitCode = await runProofExplainCommand({
849
+ root: resolveTargetArgument(pathArg),
850
+ id,
851
+ json: Boolean(options.json),
852
+ });
853
+ });
854
+ proof
855
+ .command("verify")
856
+ .description("Re-hash evidence and compare to proof (integrity only)")
857
+ .argument("<id>", "proofId or changeId")
858
+ .argument("[path]", "Repository path")
859
+ .option("--json", "Emit JSON", false)
860
+ .action(async (id, pathArg, options) => {
861
+ process.exitCode = await runProofVerifyCommand({
862
+ root: resolveTargetArgument(pathArg),
863
+ id,
864
+ json: Boolean(options.json),
865
+ });
866
+ });
867
+ proof
868
+ .command("export")
869
+ .description("Export a proof JSON file")
870
+ .argument("<id>", "proofId or changeId")
871
+ .requiredOption("--out <file>", "Output file path")
872
+ .argument("[path]", "Repository path")
873
+ .option("--json", "Emit JSON", false)
874
+ .action(async (id, pathArg, options) => {
875
+ process.exitCode = await runProofExportCommand({
876
+ root: resolveTargetArgument(pathArg),
877
+ id,
878
+ out: options.out,
879
+ json: Boolean(options.json),
880
+ });
881
+ });
882
+ const policy = program
883
+ .command("policy")
884
+ .description("Action policy check / explain / enforce (controlled runner)");
885
+ policy
886
+ .command("check")
887
+ .description("Evaluate a command against local policy (evaluate-only)")
888
+ .requiredOption("--command <cmd>", "Command string")
889
+ .argument("[path]", "Repository path")
890
+ .option("--fail-closed", "Deny on invalid local policy", false)
891
+ .option("--json", "Emit JSON", false)
892
+ .action(async (pathArg, options) => {
893
+ process.exitCode = await runPolicyCheckCommand({
894
+ root: resolveTargetArgument(pathArg),
895
+ command: options.command,
896
+ failClosed: Boolean(options.failClosed),
897
+ json: Boolean(options.json),
898
+ });
899
+ });
900
+ policy
901
+ .command("explain")
902
+ .description("Explain why a command receives its policy decision")
903
+ .requiredOption("--command <cmd>", "Command string")
904
+ .argument("[path]", "Repository path")
905
+ .option("--fail-closed", "Deny on invalid local policy", false)
906
+ .option("--json", "Emit JSON", false)
907
+ .action(async (pathArg, options) => {
908
+ process.exitCode = await runPolicyExplainCommand({
909
+ root: resolveTargetArgument(pathArg),
910
+ command: options.command,
911
+ failClosed: Boolean(options.failClosed),
912
+ json: Boolean(options.json),
913
+ });
914
+ });
915
+ policy
916
+ .command("enforce")
917
+ .description("Evaluate policy; optionally execute when --execute and decision=allow")
918
+ .requiredOption("--command <cmd>", "Command string")
919
+ .option("--execute", "Execute via controlled runner when allowed", false)
920
+ .argument("[path]", "Repository path")
921
+ .option("--fail-closed", "Deny on invalid local policy", false)
922
+ .option("--json", "Emit JSON", false)
923
+ .action(async (pathArg, options) => {
924
+ process.exitCode = await runPolicyEnforceCommand({
925
+ root: resolveTargetArgument(pathArg),
926
+ command: options.command,
927
+ execute: Boolean(options.execute),
928
+ failClosed: Boolean(options.failClosed),
929
+ json: Boolean(options.json),
930
+ });
931
+ });
932
+ const runCmd = program
933
+ .command("run")
934
+ .description("Controlled execution (shell=false by default). Prefer: agentdoctor run -- <cmd> <args...>");
935
+ runCmd
936
+ .command("explain")
937
+ .description("Explain controlled-runner decision for a command (evaluate-only, never executes)")
938
+ .requiredOption("--command <cmd>", "Command string")
939
+ .argument("[path]", "Repository path")
940
+ .option("--shell", "Explain as if shell=true were requested", false)
941
+ .option("--fail-closed", "Deny on invalid local policy", false)
942
+ .option("--json", "Emit JSON", false)
943
+ .action(async (pathArg, options) => {
944
+ process.exitCode = await runRunExplainCommand({
945
+ root: resolveTargetArgument(pathArg),
946
+ command: options.command,
947
+ shell: Boolean(options.shell),
948
+ failClosed: Boolean(options.failClosed),
949
+ json: Boolean(options.json),
950
+ });
951
+ });
952
+ runCmd
953
+ .option("--shell", "HIGH RISK: allow shell interpretation (still requires policy allow)", false)
954
+ .option("--timeout <ms>", "Kill after timeout milliseconds", "30000")
955
+ .option("--fail-closed", "Deny on invalid local policy", false)
956
+ .option("--json", "Emit JSON", false)
957
+ .argument("[path]", "Repository path (ignored when -- separates args; use cwd)")
958
+ .argument("[command...]", "Command argv after --")
959
+ .allowUnknownOption(false)
960
+ .action(async (pathArg, commandParts, cmd) => {
961
+ const options = cmd.opts();
962
+ // Commander may put path as first arg when no `--`; prefer argv after `--` via raw args.
963
+ const raw = cmd.args;
964
+ let root = process.cwd();
965
+ let argv = [];
966
+ // If first token looks like a path-only invocation without command, treat as root.
967
+ if (commandParts && commandParts.length > 0) {
968
+ argv = commandParts;
969
+ if (pathArg)
970
+ root = resolveTargetArgument(pathArg);
971
+ }
972
+ else if (pathArg && !pathArg.startsWith("-")) {
973
+ // `run npm --version` style: pathArg is actually first command token
974
+ argv = [pathArg, ...(raw.slice(1) ?? [])];
975
+ }
976
+ // Prefer explicit `--` remainder from process.argv
977
+ const dd = process.argv.indexOf("--");
978
+ if (dd >= 0) {
979
+ argv = process.argv.slice(dd + 1);
980
+ root = process.cwd();
981
+ }
982
+ const timeoutMs = Number(options.timeout ?? 30000);
983
+ process.exitCode = await runRunCommand({
984
+ root: resolveTargetArgument(root),
985
+ argv,
986
+ shell: Boolean(options.shell),
987
+ timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 30000,
988
+ failClosed: Boolean(options.failClosed),
989
+ json: Boolean(options.json),
990
+ });
991
+ });
992
+ const workspace = program
993
+ .command("workspace")
994
+ .description("Multi-repo workspace model under .agentdoctor/workspaces/ (local isolation)");
995
+ workspace
996
+ .command("init")
997
+ .description("Create a workspace JSON record")
998
+ .requiredOption("--name <name>", "Workspace display name")
999
+ .option("--id <id>", "Optional workspace id")
1000
+ .option("--repo <path>", "Initial repository root to add")
1001
+ .option("--allow-cross-read", "Allow reading other member repos (default false)", false)
1002
+ .argument("[path]", "Control root (stores .agentdoctor/workspaces)")
1003
+ .option("--json", "Emit JSON", false)
1004
+ .action(async (pathArg, options) => {
1005
+ process.exitCode = await runWorkspaceCommand({
1006
+ action: "init",
1007
+ root: resolveTargetArgument(pathArg),
1008
+ name: options.name,
1009
+ ...(options.id ? { id: options.id } : {}),
1010
+ ...(options.repo ? { repositoryRoot: resolveTargetArgument(options.repo) } : {}),
1011
+ allowCrossRead: Boolean(options.allowCrossRead),
1012
+ json: Boolean(options.json),
1013
+ });
1014
+ });
1015
+ workspace
1016
+ .command("add")
1017
+ .description("Add a repository root to a workspace")
1018
+ .requiredOption("--id <id>", "Workspace id")
1019
+ .requiredOption("--repo <path>", "Repository root to add")
1020
+ .argument("[path]", "Control root")
1021
+ .option("--json", "Emit JSON", false)
1022
+ .action(async (pathArg, options) => {
1023
+ process.exitCode = await runWorkspaceCommand({
1024
+ action: "add",
1025
+ root: resolveTargetArgument(pathArg),
1026
+ id: options.id,
1027
+ repositoryRoot: resolveTargetArgument(options.repo),
1028
+ json: Boolean(options.json),
1029
+ });
1030
+ });
1031
+ workspace
1032
+ .command("list")
1033
+ .description("List workspaces")
1034
+ .argument("[path]", "Control root")
1035
+ .option("--json", "Emit JSON", false)
1036
+ .action(async (pathArg, options) => {
1037
+ process.exitCode = await runWorkspaceCommand({
1038
+ action: "list",
1039
+ root: resolveTargetArgument(pathArg),
1040
+ json: Boolean(options.json),
1041
+ });
1042
+ });
1043
+ workspace
1044
+ .command("status")
1045
+ .description("Show workspace membership and missing roots")
1046
+ .requiredOption("--id <id>", "Workspace id")
1047
+ .argument("[path]", "Control root")
1048
+ .option("--json", "Emit JSON", false)
1049
+ .action(async (pathArg, options) => {
1050
+ process.exitCode = await runWorkspaceCommand({
1051
+ action: "status",
1052
+ root: resolveTargetArgument(pathArg),
1053
+ id: options.id,
1054
+ json: Boolean(options.json),
1055
+ });
1056
+ });
1057
+ workspace
1058
+ .command("remove")
1059
+ .description("Delete a workspace record")
1060
+ .requiredOption("--id <id>", "Workspace id")
1061
+ .argument("[path]", "Control root")
1062
+ .option("--json", "Emit JSON", false)
1063
+ .action(async (pathArg, options) => {
1064
+ process.exitCode = await runWorkspaceCommand({
1065
+ action: "remove",
1066
+ root: resolveTargetArgument(pathArg),
1067
+ id: options.id,
1068
+ json: Boolean(options.json),
1069
+ });
1070
+ });
1071
+ program
1072
+ .command("changes")
1073
+ .description("Analyze git working-tree / range changes")
1074
+ .argument("[path]", "Repository path")
1075
+ .option("--since <ref>", "Diff since git ref (e.g. main)")
1076
+ .option("--impact", "Compute dependency impact from Project Brain (UNKNOWN when edges are missing)", false)
1077
+ .option("--json", "Emit JSON", false)
1078
+ .action(async (pathArg, options) => {
1079
+ process.exitCode = await runChangesCommand({
1080
+ root: resolveTargetArgument(pathArg),
1081
+ ...(options.since ? { since: options.since } : {}),
1082
+ impact: Boolean(options.impact),
1083
+ json: Boolean(options.json),
1084
+ });
1085
+ });
1086
+ program
1087
+ .command("context-health")
1088
+ .description("Detect instruction / ignore surface conflicts")
1089
+ .argument("[path]", "Repository path")
1090
+ .option("--json", "Emit JSON", false)
1091
+ .action(async (pathArg, options) => {
1092
+ process.exitCode = await runContextHealthCommand({
1093
+ root: resolveTargetArgument(pathArg),
1094
+ json: Boolean(options.json),
1095
+ });
1096
+ });
1097
+ program
1098
+ .command("secrets")
1099
+ .description("Opt-in content secret scan (findings are always redacted)")
1100
+ .argument("[path]", "Repository path")
1101
+ .option("--json", "Emit JSON", false)
1102
+ .action(async (pathArg, options) => {
1103
+ process.exitCode = await runSecretsCommand({
1104
+ root: resolveTargetArgument(pathArg),
1105
+ json: Boolean(options.json),
1106
+ });
1107
+ });
1108
+ const baseline = program.command("baseline").description("Named scan baselines");
1109
+ baseline
1110
+ .command("save")
1111
+ .description("Save current scan as a named baseline")
1112
+ .requiredOption("--name <name>", "Baseline name")
1113
+ .argument("[path]", "Repository path")
1114
+ .action(async (pathArg, options) => {
1115
+ process.exitCode = await runBaselineCommand({
1116
+ action: "save",
1117
+ name: options.name,
1118
+ root: resolveTargetArgument(pathArg),
1119
+ });
1120
+ });
1121
+ baseline
1122
+ .command("list")
1123
+ .description("List named baselines")
1124
+ .argument("[path]", "Repository path")
1125
+ .option("--json", "Emit JSON", false)
1126
+ .action(async (pathArg, options) => {
1127
+ process.exitCode = await runBaselineCommand({
1128
+ action: "list",
1129
+ root: resolveTargetArgument(pathArg),
1130
+ json: Boolean(options.json),
1131
+ });
1132
+ });
1133
+ baseline
1134
+ .command("diff")
1135
+ .description("Compare current scan to a named baseline")
1136
+ .requiredOption("--name <name>", "Baseline name")
1137
+ .argument("[path]", "Repository path")
1138
+ .option("--json", "Emit JSON", false)
1139
+ .action(async (pathArg, options) => {
1140
+ process.exitCode = await runBaselineCommand({
1141
+ action: "diff",
1142
+ name: options.name,
1143
+ root: resolveTargetArgument(pathArg),
1144
+ json: Boolean(options.json),
1145
+ });
1146
+ });
1147
+ baseline
1148
+ .command("delete")
1149
+ .description("Delete a named baseline (fails if missing)")
1150
+ .requiredOption("--name <name>", "Baseline name")
1151
+ .argument("[path]", "Repository path")
1152
+ .action(async (pathArg, options) => {
1153
+ process.exitCode = await runBaselineCommand({
1154
+ action: "delete",
1155
+ name: options.name,
1156
+ root: resolveTargetArgument(pathArg),
1157
+ });
1158
+ });
1159
+ baseline
1160
+ .command("trends")
1161
+ .description("Show baseline count trends over time (no causality claimed)")
1162
+ .argument("[path]", "Repository path")
1163
+ .option("--json", "Emit JSON", false)
1164
+ .action(async (pathArg, options) => {
1165
+ process.exitCode = await runBaselineCommand({
1166
+ action: "trends",
1167
+ root: resolveTargetArgument(pathArg),
1168
+ json: Boolean(options.json),
1169
+ });
1170
+ });
1171
+ program
1172
+ .command("packages")
1173
+ .description("Detect workspace packages / optional per-package scan")
1174
+ .argument("[path]", "Repository path")
1175
+ .option("--scan", "Run a scan per package", false)
1176
+ .option("--json", "Emit JSON", false)
1177
+ .action(async (pathArg, options) => {
1178
+ process.exitCode = await runPackagesCommand({
1179
+ root: resolveTargetArgument(pathArg),
1180
+ scan: Boolean(options.scan),
1181
+ json: Boolean(options.json),
1182
+ });
1183
+ });
1184
+ program
1185
+ .command("pr-review")
1186
+ .description("Local PR review dry-run (never posts to GitHub)")
1187
+ .argument("[path]", "Repository path")
1188
+ .option("--base <ref>", "Base git ref")
1189
+ .option("--json", "Emit JSON", false)
1190
+ .action(async (pathArg, options) => {
1191
+ process.exitCode = await runPrReviewCommand({
1192
+ root: resolveTargetArgument(pathArg),
1193
+ ...(options.base ? { base: options.base } : {}),
1194
+ json: Boolean(options.json),
1195
+ });
1196
+ });
1197
+ program
1198
+ .command("dashboard")
1199
+ .description("Start local read-only dashboard (loopback only by default)")
1200
+ .argument("[path]", "Repository path")
1201
+ .option("--host <host>", "Bind host (default 127.0.0.1)", "127.0.0.1")
1202
+ .option("--port <port>", "Bind port", "8787")
1203
+ .option("--allow-non-loopback", "Unsafe: allow binding outside 127.0.0.1/::1/localhost (not an enterprise security boundary)", false)
1204
+ .action(async (pathArg, options) => {
1205
+ process.exitCode = await runDashboardCommand({
1206
+ root: resolveTargetArgument(pathArg),
1207
+ host: options.host ?? "127.0.0.1",
1208
+ port: Number(options.port ?? 8787),
1209
+ allowNonLoopback: Boolean(options.allowNonLoopback),
1210
+ });
1211
+ });
1212
+ program
1213
+ .command("plugins")
1214
+ .description("List / validate local plugins under .agentdoctor/plugins")
1215
+ .argument("[path]", "Repository path")
1216
+ .option("--run", "Execute analyzer plugin hooks (isolated; timeouts apply)", false)
1217
+ .option("--json", "Emit JSON", false)
1218
+ .action(async (pathArg, options) => {
1219
+ process.exitCode = await runPluginsCommand({
1220
+ root: resolveTargetArgument(pathArg),
1221
+ json: Boolean(options.json),
1222
+ run: Boolean(options.run),
1223
+ });
1224
+ });
1225
+ program
1226
+ .command("local-ai")
1227
+ .description("Optional local AI provider probe (default: none; core stays deterministic)")
1228
+ .option("--provider <id>", "none | mock | ollama", "none")
1229
+ .option("--prompt <text>", "Prompt text")
1230
+ .option("--json", "Emit JSON", false)
1231
+ .action(async (options) => {
1232
+ process.exitCode = await runLocalAiCommand({
1233
+ ...(options.provider ? { provider: options.provider } : {}),
1234
+ ...(options.prompt ? { prompt: options.prompt } : {}),
1235
+ json: Boolean(options.json),
1236
+ });
1237
+ });
1238
+ const platform = program
1239
+ .command("platform")
1240
+ .description("AgentDoctor 2.0 local platform (intelligence, Action Policy Evaluator, sessions, reports)");
1241
+ platform
1242
+ .command("scan")
1243
+ .description("Run integrated platform scan and write local reports")
1244
+ .argument("[path]", "Repository path")
1245
+ .option("--json", "Emit JSON", false)
1246
+ .action(async (pathArg, _options, command) => {
1247
+ const options = command.optsWithGlobals();
1248
+ process.exitCode = await runPlatformCommand({
1249
+ action: "scan",
1250
+ root: resolveTargetArgument(pathArg),
1251
+ json: Boolean(options.json),
1252
+ });
1253
+ });
1254
+ platform
1255
+ .command("graph")
1256
+ .description("Build repository intelligence graph")
1257
+ .argument("[path]", "Repository path")
1258
+ .option("--json", "Emit JSON", false)
1259
+ .action(async (pathArg, _options, command) => {
1260
+ const options = command.optsWithGlobals();
1261
+ process.exitCode = await runPlatformCommand({
1262
+ action: "graph",
1263
+ root: resolveTargetArgument(pathArg),
1264
+ json: Boolean(options.json),
1265
+ });
1266
+ });
1267
+ platform
1268
+ .command("test-impact")
1269
+ .description("Analyze which tests relate to git changes (heuristic; coverage-backed with --coverage; does not run tests)")
1270
+ .argument("[path]", "Repository path")
1271
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
1272
+ .option("--since <ref>", "Diff since git ref")
1273
+ .option("--json", "Emit JSON", false)
1274
+ .action(async (pathArg, _options, command) => {
1275
+ const options = command.optsWithGlobals();
1276
+ process.exitCode = await runPlatformCommand({
1277
+ action: "test-impact",
1278
+ root: resolveTargetArgument(pathArg),
1279
+ json: Boolean(options.json),
1280
+ ...(options.coverage ? { coverage: options.coverage } : {}),
1281
+ ...(options.since ? { since: options.since } : {}),
1282
+ });
1283
+ });
1284
+ const registerPolicyCheck = (name, aliasNote) => {
1285
+ platform
1286
+ .command(name)
1287
+ .description(`Action Policy Evaluator${aliasNote}: evaluate an agent action (never executes; no agent interception)`)
1288
+ .argument("[path]", "Repository path")
1289
+ .option("--type <type>", "Action type", "shell")
1290
+ .option("--command <cmd>", "Shell command string")
1291
+ .option("--path <file>", "Target path")
1292
+ .option("--fail-closed", "If local policy JSON is invalid, deny the action instead of falling back to defaults", false)
1293
+ .option("--json", "Emit JSON", false)
1294
+ .action(async (pathArg, _options, cmd) => {
1295
+ const options = cmd.optsWithGlobals();
1296
+ process.exitCode = await runPlatformCommand({
1297
+ action: "policy-check",
1298
+ root: resolveTargetArgument(pathArg),
1299
+ ...(options.type ? { actionType: options.type } : {}),
1300
+ ...(options.command ? { command: options.command } : {}),
1301
+ ...(options.path ? { path: options.path } : {}),
1302
+ json: Boolean(options.json),
1303
+ failClosed: Boolean(options.failClosed),
1304
+ });
1305
+ });
1306
+ };
1307
+ registerPolicyCheck("policy-check", "");
1308
+ registerPolicyCheck("firewall-check", " (alias)");
1309
+ platform
1310
+ .command("session-list")
1311
+ .description("List recorded agent sessions")
1312
+ .argument("[path]", "Repository path")
1313
+ .option("--json", "Emit JSON", false)
1314
+ .action(async (pathArg, _options, command) => {
1315
+ const options = command.optsWithGlobals();
1316
+ process.exitCode = await runPlatformCommand({
1317
+ action: "session-list",
1318
+ root: resolveTargetArgument(pathArg),
1319
+ json: Boolean(options.json),
1320
+ });
1321
+ });
1322
+ platform
1323
+ .command("session-show")
1324
+ .description("Show / export an agent session")
1325
+ .requiredOption("--session <id>", "Session id")
1326
+ .argument("[path]", "Repository path")
1327
+ .option("--json", "Emit JSON", false)
1328
+ .action(async (pathArg, _options, command) => {
1329
+ const options = command.optsWithGlobals();
1330
+ process.exitCode = await runPlatformCommand({
1331
+ action: "session-show",
1332
+ sessionId: options.session,
1333
+ root: resolveTargetArgument(pathArg),
1334
+ json: Boolean(options.json),
1335
+ });
1336
+ });
1337
+ platform
1338
+ .command("demo-session")
1339
+ .description("Record a demo session with Action Policy Evaluator (evaluate-only; no command execution)")
1340
+ .argument("[path]", "Repository path")
1341
+ .option("--json", "Emit JSON", false)
1342
+ .action(async (pathArg, _options, command) => {
1343
+ const options = command.optsWithGlobals();
1344
+ process.exitCode = await runPlatformCommand({
1345
+ action: "demo-session",
1346
+ root: resolveTargetArgument(pathArg),
1347
+ json: Boolean(options.json),
1348
+ });
1349
+ });
1350
+ platform
1351
+ .command("provenance")
1352
+ .description("Build a provenance record for a file (unknown fields marked)")
1353
+ .requiredOption("--path <file>", "File path")
1354
+ .argument("[repo]", "Repository path")
1355
+ .option("--json", "Emit JSON", false)
1356
+ .action(async (repo, _options, command) => {
1357
+ const options = command.optsWithGlobals();
1358
+ process.exitCode = await runPlatformCommand({
1359
+ action: "provenance",
1360
+ path: options.path,
1361
+ root: resolveTargetArgument(repo),
1362
+ json: Boolean(options.json),
1363
+ });
1364
+ });
1365
+ platform
1366
+ .command("context")
1367
+ .description("Plan token-optimized context for a query")
1368
+ .option("--query <text>", "Query", "src")
1369
+ .argument("[path]", "Repository path")
1370
+ .option("--json", "Emit JSON", false)
1371
+ .action(async (pathArg, _options, command) => {
1372
+ const options = command.optsWithGlobals();
1373
+ process.exitCode = await runPlatformCommand({
1374
+ action: "context",
1375
+ query: options.query ?? "src",
1376
+ root: resolveTargetArgument(pathArg),
1377
+ json: Boolean(options.json),
1378
+ });
1379
+ });
1380
+ platform
1381
+ .command("refactor")
1382
+ .description("Analyze rename impact for a symbol (does not apply edits)")
1383
+ .requiredOption("--symbol <name>", "Symbol name")
1384
+ .argument("[path]", "Repository path")
1385
+ .option("--json", "Emit JSON", false)
1386
+ .action(async (pathArg, _options, command) => {
1387
+ const options = command.optsWithGlobals();
1388
+ process.exitCode = await runPlatformCommand({
1389
+ action: "refactor",
1390
+ symbol: options.symbol,
1391
+ root: resolveTargetArgument(pathArg),
1392
+ json: Boolean(options.json),
1393
+ });
1394
+ });
1395
+ platform
1396
+ .command("time-machine")
1397
+ .description("Compare two git refs")
1398
+ .requiredOption("--left <ref>", "Left ref")
1399
+ .requiredOption("--right <ref>", "Right ref")
1400
+ .argument("[path]", "Repository path")
1401
+ .option("--json", "Emit JSON", false)
1402
+ .action(async (pathArg, _options, command) => {
1403
+ const options = command.optsWithGlobals();
1404
+ process.exitCode = await runPlatformCommand({
1405
+ action: "time-machine",
1406
+ left: options.left,
1407
+ right: options.right,
1408
+ root: resolveTargetArgument(pathArg),
1409
+ json: Boolean(options.json),
1410
+ });
1411
+ });
108
1412
  program
109
1413
  .command("verify")
110
1414
  .description("Re-scan and compare against a prior scan JSON baseline (Scan → Fix → Verify)")
@@ -150,7 +1454,16 @@ export function createProgram() {
150
1454
  program
151
1455
  .command("doctor")
152
1456
  .description("Check AgentDoctor installation health")
153
- .action(async () => {
1457
+ .option("--json", "Emit JSON ops health", false)
1458
+ .argument("[path]", "Repository path")
1459
+ .action(async (pathArg, _options, command) => {
1460
+ const options = command.optsWithGlobals();
1461
+ if (options.json) {
1462
+ const health = await collectOpsHealth(resolveTargetArgument(pathArg));
1463
+ process.stdout.write(`${JSON.stringify(health, null, 2)}\n`);
1464
+ process.exitCode = health.ok ? EXIT_CODES.SUCCESS : EXIT_CODES.INTERNAL_ERROR;
1465
+ return;
1466
+ }
154
1467
  const code = await runDoctorCommand();
155
1468
  process.exitCode = code;
156
1469
  });
@@ -166,6 +1479,18 @@ export function createProgram() {
166
1479
  });
167
1480
  process.exitCode = code;
168
1481
  });
1482
+ program
1483
+ .command("mcp")
1484
+ .description("Start combined AgentDoctor MCP (Brain tools + intelligence tools) over STDIO")
1485
+ .requiredOption("--root <path>", "Absolute or relative project root (required; never uses process.cwd() implicitly)")
1486
+ .option("--no-build-if-missing", "Fail when no Brain snapshot exists instead of compiling")
1487
+ .action(async (options) => {
1488
+ const code = await runMcpCommand({
1489
+ root: options.root,
1490
+ buildIfMissing: options.buildIfMissing !== false,
1491
+ });
1492
+ process.exitCode = code;
1493
+ });
169
1494
  program.configureOutput({
170
1495
  outputError: (str, write) => write(str),
171
1496
  });