@praneeth_54/agentdoctor 2.0.0 → 2.1.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 (168) hide show
  1. package/CHANGELOG.md +141 -4
  2. package/README.md +329 -153
  3. package/dist/agent/approvals.d.ts +24 -0
  4. package/dist/agent/approvals.js +64 -0
  5. package/dist/agent/chat/index.d.ts +7 -0
  6. package/dist/agent/chat/index.js +5 -0
  7. package/dist/agent/chat/memory.d.ts +44 -0
  8. package/dist/agent/chat/memory.js +103 -0
  9. package/dist/agent/chat/project-summary.d.ts +16 -0
  10. package/dist/agent/chat/project-summary.js +70 -0
  11. package/dist/agent/chat/prompts.d.ts +6 -0
  12. package/dist/agent/chat/prompts.js +39 -0
  13. package/dist/agent/chat/response.d.ts +19 -0
  14. package/dist/agent/chat/response.js +109 -0
  15. package/dist/agent/chat/service.d.ts +42 -0
  16. package/dist/agent/chat/service.js +252 -0
  17. package/dist/agent/chat/types.d.ts +48 -0
  18. package/dist/agent/chat/types.js +1 -0
  19. package/dist/agent/context/retrieve.d.ts +21 -0
  20. package/dist/agent/context/retrieve.js +117 -0
  21. package/dist/agent/context/truth.d.ts +6 -0
  22. package/dist/agent/context/truth.js +19 -0
  23. package/dist/agent/context/types.d.ts +23 -0
  24. package/dist/agent/context/types.js +4 -0
  25. package/dist/agent/index.d.ts +26 -0
  26. package/dist/agent/index.js +14 -0
  27. package/dist/agent/loop.d.ts +51 -0
  28. package/dist/agent/loop.js +222 -0
  29. package/dist/agent/modes.d.ts +18 -0
  30. package/dist/agent/modes.js +102 -0
  31. package/dist/agent/plan.d.ts +30 -0
  32. package/dist/agent/plan.js +121 -0
  33. package/dist/agent/runtime.d.ts +67 -0
  34. package/dist/agent/runtime.js +180 -0
  35. package/dist/agent/state.d.ts +30 -0
  36. package/dist/agent/state.js +95 -0
  37. package/dist/agent/student.d.ts +56 -0
  38. package/dist/agent/student.js +230 -0
  39. package/dist/agent/tools/execute.d.ts +18 -0
  40. package/dist/agent/tools/execute.js +355 -0
  41. package/dist/agent/tools/index.d.ts +6 -0
  42. package/dist/agent/tools/index.js +5 -0
  43. package/dist/agent/tools/registry.d.ts +7 -0
  44. package/dist/agent/tools/registry.js +212 -0
  45. package/dist/agent/tools/run.d.ts +24 -0
  46. package/dist/agent/tools/run.js +44 -0
  47. package/dist/agent/tools/types.d.ts +32 -0
  48. package/dist/agent/tools/types.js +10 -0
  49. package/dist/agent/tools/write.d.ts +24 -0
  50. package/dist/agent/tools/write.js +121 -0
  51. package/dist/agent/verify.d.ts +29 -0
  52. package/dist/agent/verify.js +210 -0
  53. package/dist/ai/config.d.ts +22 -0
  54. package/dist/ai/config.js +68 -0
  55. package/dist/ai/index.d.ts +17 -0
  56. package/dist/ai/index.js +52 -0
  57. package/dist/ai/providers/mock.d.ts +20 -0
  58. package/dist/ai/providers/mock.js +84 -0
  59. package/dist/ai/providers/none.d.ts +6 -0
  60. package/dist/ai/providers/none.js +23 -0
  61. package/dist/ai/providers/openai-compatible.d.ts +21 -0
  62. package/dist/ai/providers/openai-compatible.js +151 -0
  63. package/dist/ai/redact.d.ts +8 -0
  64. package/dist/ai/redact.js +21 -0
  65. package/dist/ai/types.d.ts +71 -0
  66. package/dist/ai/types.js +6 -0
  67. package/dist/architecture/contract.d.ts +64 -0
  68. package/dist/architecture/contract.js +373 -0
  69. package/dist/assurance/change.d.ts +211 -0
  70. package/dist/assurance/change.js +624 -0
  71. package/dist/assurance/proof.d.ts +136 -0
  72. package/dist/assurance/proof.js +338 -0
  73. package/dist/auth/index.d.ts +81 -0
  74. package/dist/auth/index.js +130 -0
  75. package/dist/auth/rbac.d.ts +9 -0
  76. package/dist/auth/rbac.js +26 -0
  77. package/dist/cli/commands/agent.d.ts +27 -0
  78. package/dist/cli/commands/agent.js +153 -0
  79. package/dist/cli/commands/architecture.d.ts +6 -0
  80. package/dist/cli/commands/architecture.js +109 -0
  81. package/dist/cli/commands/assurance.d.ts +67 -0
  82. package/dist/cli/commands/assurance.js +210 -0
  83. package/dist/cli/commands/chat.d.ts +14 -0
  84. package/dist/cli/commands/chat.js +155 -0
  85. package/dist/cli/commands/complete.js +2 -2
  86. package/dist/cli/commands/learn.d.ts +12 -0
  87. package/dist/cli/commands/learn.js +107 -0
  88. package/dist/cli/commands/platform.d.ts +2 -0
  89. package/dist/cli/commands/platform.js +5 -1
  90. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  91. package/dist/cli/commands/policy-graph-run.js +200 -0
  92. package/dist/cli/commands/workspace.d.ts +9 -0
  93. package/dist/cli/commands/workspace.js +82 -0
  94. package/dist/cli/program.js +627 -17
  95. package/dist/constants.d.ts +1 -1
  96. package/dist/constants.js +1 -1
  97. package/dist/contracts/index.d.ts +1 -1
  98. package/dist/contracts/index.js +2 -2
  99. package/dist/core/brain-product/init.js +5 -2
  100. package/dist/core/secrets/scan.d.ts +7 -1
  101. package/dist/core/secrets/scan.js +9 -9
  102. package/dist/coverage/cobertura.d.ts +6 -0
  103. package/dist/coverage/cobertura.js +52 -0
  104. package/dist/coverage/istanbul.d.ts +7 -0
  105. package/dist/coverage/istanbul.js +134 -0
  106. package/dist/coverage/lcov.d.ts +7 -0
  107. package/dist/coverage/lcov.js +59 -0
  108. package/dist/coverage/load.d.ts +9 -0
  109. package/dist/coverage/load.js +39 -0
  110. package/dist/coverage/types.d.ts +23 -0
  111. package/dist/coverage/types.js +5 -0
  112. package/dist/dashboard/server.d.ts +7 -1
  113. package/dist/dashboard/server.js +110 -6
  114. package/dist/enforcement/runner.d.ts +52 -3
  115. package/dist/enforcement/runner.js +467 -16
  116. package/dist/index.d.ts +36 -2
  117. package/dist/index.js +23 -2
  118. package/dist/intelligence/graph/build.d.ts +2 -0
  119. package/dist/intelligence/graph/build.js +49 -5
  120. package/dist/intelligence/graph/incremental.d.ts +68 -0
  121. package/dist/intelligence/graph/incremental.js +234 -0
  122. package/dist/intelligence/resolve/imports.d.ts +36 -0
  123. package/dist/intelligence/resolve/imports.js +245 -0
  124. package/dist/languages/go.d.ts +9 -0
  125. package/dist/languages/go.js +50 -0
  126. package/dist/languages/index.d.ts +11 -0
  127. package/dist/languages/index.js +76 -0
  128. package/dist/languages/php.d.ts +8 -0
  129. package/dist/languages/php.js +202 -0
  130. package/dist/languages/python.d.ts +11 -0
  131. package/dist/languages/python.js +180 -0
  132. package/dist/languages/types.d.ts +42 -0
  133. package/dist/languages/types.js +26 -0
  134. package/dist/languages/typescript.d.ts +3 -0
  135. package/dist/languages/typescript.js +95 -0
  136. package/dist/mcp/agent/registry.d.ts +13 -0
  137. package/dist/mcp/agent/registry.js +234 -0
  138. package/dist/mcp/agentdoctor/server.js +8 -1
  139. package/dist/mcp/intelligence/handlers.d.ts +5 -0
  140. package/dist/mcp/intelligence/handlers.js +229 -40
  141. package/dist/mcp/intelligence/path-safety.d.ts +2 -4
  142. package/dist/mcp/intelligence/path-safety.js +25 -44
  143. package/dist/mcp/intelligence/registry.d.ts +1 -1
  144. package/dist/mcp/intelligence/registry.js +69 -1
  145. package/dist/platform/firewall/evaluate.d.ts +6 -3
  146. package/dist/platform/firewall/evaluate.js +66 -15
  147. package/dist/platform/graph/build.js +19 -23
  148. package/dist/platform/health/analyze.js +23 -17
  149. package/dist/platform/index.js +2 -0
  150. package/dist/platform/test-impact/analyze.d.ts +21 -3
  151. package/dist/platform/test-impact/analyze.js +214 -28
  152. package/dist/platform/tokens/plan.js +39 -20
  153. package/dist/platform/types.d.ts +9 -0
  154. package/dist/policy/compose.d.ts +34 -0
  155. package/dist/policy/compose.js +118 -0
  156. package/dist/policy/packs.js +33 -1
  157. package/dist/security/paths.d.ts +21 -0
  158. package/dist/security/paths.js +133 -0
  159. package/dist/storage/postgres.d.ts +26 -0
  160. package/dist/storage/postgres.js +90 -0
  161. package/dist/storage/provider.d.ts +2 -7
  162. package/dist/storage/provider.js +22 -16
  163. package/dist/storage/sqlite.d.ts +33 -0
  164. package/dist/storage/sqlite.js +77 -0
  165. package/dist/team/auth.js +1 -1
  166. package/dist/workspace/index.d.ts +69 -0
  167. package/dist/workspace/index.js +220 -0
  168. package/package.json +9 -3
@@ -13,10 +13,19 @@ import { runBaselineCommand, runChangesCommand, runContextHealthCommand, runDash
13
13
  import { runPlatformCommand } from "./commands/platform.js";
14
14
  import { runInitCommand, runV2SurfaceCommand } from "./commands/complete.js";
15
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 { runAskCommand, runChatCommand } from "./commands/chat.js";
21
+ import { runAgentCommand, runPlanCommand } from "./commands/agent.js";
22
+ import { runLearnCommand } from "./commands/learn.js";
16
23
  import { collectOpsHealth } from "../ops/health.js";
17
24
  import { listSessions, loadSession } from "../platform/sessions/store.js";
18
25
  import { exportReports } from "../platform/reports/export.js";
19
26
  import { runPlatformScan } from "../platform/index.js";
27
+ import { analyzeTestImpact, formatTestImpactHuman, persistTestImpactReport, } from "../platform/test-impact/analyze.js";
28
+ import { resolveRepoRoot } from "../utils/path.js";
20
29
  function parseMinScore(value) {
21
30
  const parsed = Number(value);
22
31
  if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
@@ -89,7 +98,7 @@ export function createProgram() {
89
98
  const program = new Command();
90
99
  addScanOptions(program
91
100
  .name("agentdoctor")
92
- .description("Audit AI coding agent configuration in a repository (local, deterministic, no API key).")
101
+ .description("Engineering intelligence and safety for AI coding agents — scan, knowledge, MCP, and verification (local, deterministic; no API key required for core flows).")
93
102
  .version(PACKAGE_VERSION, "-V, --version", "Print AgentDoctor version")
94
103
  .argument("[path]", "Repository path to scan (default: current directory)")).action(async (pathArg, _options, command) => {
95
104
  await runScanFromCli(pathArg, command);
@@ -317,21 +326,76 @@ export function createProgram() {
317
326
  ...(options.domain ? { domain: options.domain } : {}),
318
327
  });
319
328
  });
320
- program
329
+ const graph = program
321
330
  .command("graph")
322
- .description("Build intelligence graph (TypeScript AST when available; regex fallback)")
331
+ .description("Build / update intelligence graph (TypeScript AST when available; regex fallback). Bare `graph` aliases build.")
323
332
  .argument("[path]", "Repository path")
324
333
  .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
325
334
  .option("--json", "Emit JSON", false)
326
335
  .action(async (pathArg, _options, command) => {
327
336
  const options = command.optsWithGlobals();
328
- process.exitCode = await runV2SurfaceCommand({
329
- action: "graph-ast",
337
+ // Backward compat: `agentdoctor graph --json` → build + persist
338
+ process.exitCode = await runGraphSurfaceCommand({
339
+ action: "build",
340
+ root: resolveTargetArgument(pathArg),
341
+ mode: options.mode ?? "auto",
342
+ json: Boolean(options.json),
343
+ });
344
+ });
345
+ graph
346
+ .command("build")
347
+ .description("Build graph and persist .agentdoctor/graph/index.json")
348
+ .argument("[path]", "Repository path")
349
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
350
+ .option("--json", "Emit JSON", false)
351
+ .action(async (pathArg, options) => {
352
+ process.exitCode = await runGraphSurfaceCommand({
353
+ action: "build",
354
+ root: resolveTargetArgument(pathArg),
355
+ mode: options.mode ?? "auto",
356
+ json: Boolean(options.json),
357
+ });
358
+ });
359
+ graph
360
+ .command("update")
361
+ .description("Incrementally update graph from git/hash changes (rebuilds on corruption)")
362
+ .argument("[path]", "Repository path")
363
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
364
+ .option("--json", "Emit JSON", false)
365
+ .action(async (pathArg, options) => {
366
+ process.exitCode = await runGraphSurfaceCommand({
367
+ action: "update",
368
+ root: resolveTargetArgument(pathArg),
369
+ mode: options.mode ?? "auto",
370
+ json: Boolean(options.json),
371
+ });
372
+ });
373
+ graph
374
+ .command("rebuild")
375
+ .description("Force full graph rebuild")
376
+ .argument("[path]", "Repository path")
377
+ .option("--mode <mode>", "auto|regex|typescript-ast", "auto")
378
+ .option("--json", "Emit JSON", false)
379
+ .action(async (pathArg, options) => {
380
+ process.exitCode = await runGraphSurfaceCommand({
381
+ action: "rebuild",
330
382
  root: resolveTargetArgument(pathArg),
331
383
  mode: options.mode ?? "auto",
332
384
  json: Boolean(options.json),
333
385
  });
334
386
  });
387
+ graph
388
+ .command("status")
389
+ .description("Show persisted graph index status")
390
+ .argument("[path]", "Repository path")
391
+ .option("--json", "Emit JSON", false)
392
+ .action(async (pathArg, options) => {
393
+ process.exitCode = await runGraphSurfaceCommand({
394
+ action: "status",
395
+ root: resolveTargetArgument(pathArg),
396
+ json: Boolean(options.json),
397
+ });
398
+ });
335
399
  program
336
400
  .command("health")
337
401
  .description("Git hotspot / engineering intelligence report")
@@ -347,29 +411,63 @@ export function createProgram() {
347
411
  });
348
412
  program
349
413
  .command("impact")
350
- .description("Change/test impact analysis")
414
+ .description("Change/test impact analysis (heuristic by default; coverage-backed with --coverage)")
351
415
  .argument("[path]", "Repository path")
416
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
417
+ .option("--since <ref>", "Diff since git ref")
352
418
  .option("--json", "Emit JSON", false)
353
419
  .action(async (pathArg, _options, command) => {
354
420
  const options = command.optsWithGlobals();
355
- process.exitCode = await runV2SurfaceCommand({
356
- action: "impact",
357
- root: resolveTargetArgument(pathArg),
358
- json: Boolean(options.json),
359
- });
421
+ const root = resolveTargetArgument(pathArg);
422
+ try {
423
+ const impact = await analyzeTestImpact({
424
+ root,
425
+ ...(options.coverage ? { coveragePath: options.coverage } : {}),
426
+ ...(options.since ? { since: options.since } : {}),
427
+ });
428
+ if (options.json) {
429
+ process.stdout.write(`${JSON.stringify(impact, null, 2)}\n`);
430
+ }
431
+ else {
432
+ process.stdout.write(formatTestImpactHuman(impact));
433
+ }
434
+ process.exitCode = EXIT_CODES.SUCCESS;
435
+ }
436
+ catch (error) {
437
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
438
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
439
+ }
360
440
  });
361
441
  program
362
442
  .command("test-impact")
363
443
  .description("Test-impact analysis (alias of impact)")
364
444
  .argument("[path]", "Repository path")
445
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
446
+ .option("--since <ref>", "Diff since git ref")
365
447
  .option("--json", "Emit JSON", false)
366
448
  .action(async (pathArg, _options, command) => {
367
449
  const options = command.optsWithGlobals();
368
- process.exitCode = await runV2SurfaceCommand({
369
- action: "impact",
370
- root: resolveTargetArgument(pathArg),
371
- json: Boolean(options.json),
372
- });
450
+ const root = resolveTargetArgument(pathArg);
451
+ try {
452
+ const impact = await analyzeTestImpact({
453
+ root,
454
+ ...(options.coverage ? { coveragePath: options.coverage } : {}),
455
+ ...(options.since ? { since: options.since } : {}),
456
+ });
457
+ const out = await persistTestImpactReport(resolveRepoRoot(root), impact);
458
+ if (options.json) {
459
+ process.stdout.write(`${JSON.stringify({ ...impact, reportPath: out }, null, 2)}\n`);
460
+ }
461
+ else {
462
+ process.stdout.write(formatTestImpactHuman(impact));
463
+ process.stdout.write(` wrote: ${out}\n`);
464
+ }
465
+ process.exitCode = EXIT_CODES.SUCCESS;
466
+ }
467
+ catch (error) {
468
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
469
+ process.exitCode = EXIT_CODES.INTERNAL_ERROR;
470
+ }
373
471
  });
374
472
  program
375
473
  .command("refactor-impact")
@@ -458,6 +556,57 @@ export function createProgram() {
458
556
  json: Boolean(options.json),
459
557
  });
460
558
  });
559
+ const architecture = program
560
+ .command("architecture")
561
+ .description("Architecture contract + C4 views (rules are advisory until you treat check as a gate)");
562
+ architecture
563
+ .command("init")
564
+ .description("Write default .agentdoctor/architecture.json if missing")
565
+ .argument("[path]", "Repository path")
566
+ .option("--json", "Emit JSON", false)
567
+ .action(async (pathArg, options) => {
568
+ process.exitCode = await runArchitectureCommand({
569
+ action: "init",
570
+ root: resolveTargetArgument(pathArg),
571
+ json: Boolean(options.json),
572
+ });
573
+ });
574
+ architecture
575
+ .command("analyze")
576
+ .description("C4 views + architecture contract summary")
577
+ .argument("[path]", "Repository path")
578
+ .option("--json", "Emit JSON", false)
579
+ .action(async (pathArg, options) => {
580
+ process.exitCode = await runArchitectureCommand({
581
+ action: "analyze",
582
+ root: resolveTargetArgument(pathArg),
583
+ json: Boolean(options.json),
584
+ });
585
+ });
586
+ architecture
587
+ .command("check")
588
+ .description("Check import edges against .agentdoctor/architecture.json|.yml")
589
+ .argument("[path]", "Repository path")
590
+ .option("--json", "Emit JSON", false)
591
+ .action(async (pathArg, options) => {
592
+ process.exitCode = await runArchitectureCommand({
593
+ action: "check",
594
+ root: resolveTargetArgument(pathArg),
595
+ json: Boolean(options.json),
596
+ });
597
+ });
598
+ architecture
599
+ .command("explain")
600
+ .description("Explain architecture layers, forbidden/allowed rules, and recent violations")
601
+ .argument("[path]", "Repository path")
602
+ .option("--json", "Emit JSON", false)
603
+ .action(async (pathArg, options) => {
604
+ process.exitCode = await runArchitectureCommand({
605
+ action: "explain",
606
+ root: resolveTargetArgument(pathArg),
607
+ json: Boolean(options.json),
608
+ });
609
+ });
461
610
  program
462
611
  .command("knowledge")
463
612
  .description("List governed knowledge records")
@@ -554,6 +703,374 @@ export function createProgram() {
554
703
  json: Boolean(options.json),
555
704
  });
556
705
  });
706
+ const change = program
707
+ .command("change")
708
+ .description("Change assurance — structured assessment and evidence (never claims verified without evidence)");
709
+ change
710
+ .command("analyze")
711
+ .description("Assemble a ChangeAssessment from git, graph, policy, knowledge, and test signals")
712
+ .argument("[path]", "Repository path")
713
+ .option("--since <ref>", "Diff since git ref (e.g. main)")
714
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
715
+ .option("--json", "Emit JSON", false)
716
+ .action(async (pathArg, options) => {
717
+ process.exitCode = await runChangeAnalyzeCommand({
718
+ root: resolveTargetArgument(pathArg),
719
+ ...(options.since ? { since: options.since } : {}),
720
+ ...(options.coverage ? { coverage: options.coverage } : {}),
721
+ json: Boolean(options.json),
722
+ });
723
+ });
724
+ change
725
+ .command("verify")
726
+ .description("Produce a durable evidence bundle under .agentdoctor/evidence/<id>/ (status: evidence-produced)")
727
+ .argument("[path]", "Repository path")
728
+ .option("--since <ref>", "Diff since git ref (e.g. main)")
729
+ .option("--change-id <id>", "Reuse a change id")
730
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
731
+ .option("--json", "Emit JSON", false)
732
+ .action(async (pathArg, options) => {
733
+ process.exitCode = await runChangeVerifyCommand({
734
+ root: resolveTargetArgument(pathArg),
735
+ ...(options.since ? { since: options.since } : {}),
736
+ ...(options.changeId ? { changeId: options.changeId } : {}),
737
+ ...(options.coverage ? { coverage: options.coverage } : {}),
738
+ json: Boolean(options.json),
739
+ });
740
+ });
741
+ change
742
+ .command("explain")
743
+ .description("Human explanation of a change assessment (does not claim correctness)")
744
+ .argument("[path]", "Repository path")
745
+ .option("--since <ref>", "Diff since git ref")
746
+ .option("--change-id <id>", "Change id")
747
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
748
+ .option("--json", "Emit JSON", false)
749
+ .action(async (pathArg, options) => {
750
+ process.exitCode = await runChangeExplainCommand({
751
+ root: resolveTargetArgument(pathArg),
752
+ ...(options.since ? { since: options.since } : {}),
753
+ ...(options.changeId ? { changeId: options.changeId } : {}),
754
+ ...(options.coverage ? { coverage: options.coverage } : {}),
755
+ json: Boolean(options.json),
756
+ });
757
+ });
758
+ change
759
+ .command("diff")
760
+ .description("Summarize base/target file diffs from git")
761
+ .argument("[path]", "Repository path")
762
+ .option("--since <ref>", "Diff since git ref")
763
+ .option("--change-id <id>", "Change id (label only)")
764
+ .option("--json", "Emit JSON", false)
765
+ .action(async (pathArg, options) => {
766
+ process.exitCode = await runChangeDiffCommand({
767
+ root: resolveTargetArgument(pathArg),
768
+ ...(options.since ? { since: options.since } : {}),
769
+ ...(options.changeId ? { changeId: options.changeId } : {}),
770
+ json: Boolean(options.json),
771
+ });
772
+ });
773
+ change
774
+ .command("status")
775
+ .description("Verification / evidence status for latest or --change-id")
776
+ .argument("[path]", "Repository path")
777
+ .option("--change-id <id>", "Change id")
778
+ .option("--json", "Emit JSON", false)
779
+ .action(async (pathArg, options) => {
780
+ process.exitCode = await runChangeStatusCommand({
781
+ root: resolveTargetArgument(pathArg),
782
+ ...(options.changeId ? { changeId: options.changeId } : {}),
783
+ json: Boolean(options.json),
784
+ });
785
+ });
786
+ const evidence = program
787
+ .command("evidence")
788
+ .description("Inspect and hash-verify change evidence bundles");
789
+ evidence
790
+ .command("inspect")
791
+ .description("List evidence artifacts for a change id")
792
+ .argument("<changeId>", "Change id (chg_…)")
793
+ .argument("[path]", "Repository path")
794
+ .option("--json", "Emit JSON", false)
795
+ .action(async (changeId, pathArg, options) => {
796
+ process.exitCode = await runEvidenceInspectCommand({
797
+ root: resolveTargetArgument(pathArg),
798
+ changeId,
799
+ json: Boolean(options.json),
800
+ });
801
+ });
802
+ evidence
803
+ .command("verify")
804
+ .description("Hash-check evidence bundle; status verified only when all hashes match")
805
+ .argument("<changeId>", "Change id (chg_…)")
806
+ .argument("[path]", "Repository path")
807
+ .option("--json", "Emit JSON", false)
808
+ .action(async (changeId, pathArg, options) => {
809
+ process.exitCode = await runEvidenceVerifyCommand({
810
+ root: resolveTargetArgument(pathArg),
811
+ changeId,
812
+ json: Boolean(options.json),
813
+ });
814
+ });
815
+ const proof = program
816
+ .command("proof")
817
+ .description("ChangeProof — hash integrity over evidence (never claims engineering correctness)");
818
+ proof
819
+ .command("build")
820
+ .description("Build a ChangeProof from an evidence bundle")
821
+ .argument("<changeId>", "Change id (chg_…)")
822
+ .argument("[path]", "Repository path")
823
+ .option("--json", "Emit JSON", false)
824
+ .action(async (changeId, pathArg, options) => {
825
+ process.exitCode = await runProofBuildCommand({
826
+ root: resolveTargetArgument(pathArg),
827
+ changeId,
828
+ json: Boolean(options.json),
829
+ });
830
+ });
831
+ proof
832
+ .command("inspect")
833
+ .description("Inspect a proof by proofId or changeId")
834
+ .argument("<id>", "proofId or changeId")
835
+ .argument("[path]", "Repository path")
836
+ .option("--json", "Emit JSON", false)
837
+ .action(async (id, pathArg, options) => {
838
+ process.exitCode = await runProofInspectCommand({
839
+ root: resolveTargetArgument(pathArg),
840
+ id,
841
+ json: Boolean(options.json),
842
+ });
843
+ });
844
+ proof
845
+ .command("explain")
846
+ .description("Human explanation of a ChangeProof (does not claim correctness)")
847
+ .argument("<id>", "proofId or changeId")
848
+ .argument("[path]", "Repository path")
849
+ .option("--json", "Emit JSON", false)
850
+ .action(async (id, pathArg, options) => {
851
+ process.exitCode = await runProofExplainCommand({
852
+ root: resolveTargetArgument(pathArg),
853
+ id,
854
+ json: Boolean(options.json),
855
+ });
856
+ });
857
+ proof
858
+ .command("verify")
859
+ .description("Re-hash evidence and compare to proof (integrity only)")
860
+ .argument("<id>", "proofId or changeId")
861
+ .argument("[path]", "Repository path")
862
+ .option("--json", "Emit JSON", false)
863
+ .action(async (id, pathArg, options) => {
864
+ process.exitCode = await runProofVerifyCommand({
865
+ root: resolveTargetArgument(pathArg),
866
+ id,
867
+ json: Boolean(options.json),
868
+ });
869
+ });
870
+ proof
871
+ .command("export")
872
+ .description("Export a proof JSON file")
873
+ .argument("<id>", "proofId or changeId")
874
+ .requiredOption("--out <file>", "Output file path")
875
+ .argument("[path]", "Repository path")
876
+ .option("--json", "Emit JSON", false)
877
+ .action(async (id, pathArg, options) => {
878
+ process.exitCode = await runProofExportCommand({
879
+ root: resolveTargetArgument(pathArg),
880
+ id,
881
+ out: options.out,
882
+ json: Boolean(options.json),
883
+ });
884
+ });
885
+ const policy = program
886
+ .command("policy")
887
+ .description("Action policy check / explain / enforce (controlled runner)");
888
+ policy
889
+ .command("check")
890
+ .description("Evaluate a command against local policy (evaluate-only)")
891
+ .requiredOption("--command <cmd>", "Command string")
892
+ .argument("[path]", "Repository path")
893
+ .option("--fail-closed", "Deny on invalid local policy", false)
894
+ .option("--json", "Emit JSON", false)
895
+ .action(async (pathArg, options) => {
896
+ process.exitCode = await runPolicyCheckCommand({
897
+ root: resolveTargetArgument(pathArg),
898
+ command: options.command,
899
+ failClosed: Boolean(options.failClosed),
900
+ json: Boolean(options.json),
901
+ });
902
+ });
903
+ policy
904
+ .command("explain")
905
+ .description("Explain why a command receives its policy decision")
906
+ .requiredOption("--command <cmd>", "Command string")
907
+ .argument("[path]", "Repository path")
908
+ .option("--fail-closed", "Deny on invalid local policy", false)
909
+ .option("--json", "Emit JSON", false)
910
+ .action(async (pathArg, options) => {
911
+ process.exitCode = await runPolicyExplainCommand({
912
+ root: resolveTargetArgument(pathArg),
913
+ command: options.command,
914
+ failClosed: Boolean(options.failClosed),
915
+ json: Boolean(options.json),
916
+ });
917
+ });
918
+ policy
919
+ .command("enforce")
920
+ .description("Evaluate policy; optionally execute when --execute and decision=allow")
921
+ .requiredOption("--command <cmd>", "Command string")
922
+ .option("--execute", "Execute via controlled runner when allowed", false)
923
+ .argument("[path]", "Repository path")
924
+ .option("--fail-closed", "Deny on invalid local policy", false)
925
+ .option("--json", "Emit JSON", false)
926
+ .action(async (pathArg, options) => {
927
+ process.exitCode = await runPolicyEnforceCommand({
928
+ root: resolveTargetArgument(pathArg),
929
+ command: options.command,
930
+ execute: Boolean(options.execute),
931
+ failClosed: Boolean(options.failClosed),
932
+ json: Boolean(options.json),
933
+ });
934
+ });
935
+ const runCmd = program
936
+ .command("run")
937
+ .description("Controlled execution (shell=false by default). Prefer: agentdoctor run -- <cmd> <args...>");
938
+ runCmd
939
+ .command("explain")
940
+ .description("Explain controlled-runner decision for a command (evaluate-only, never executes)")
941
+ .requiredOption("--command <cmd>", "Command string")
942
+ .argument("[path]", "Repository path")
943
+ .option("--shell", "Explain as if shell=true were requested", false)
944
+ .option("--fail-closed", "Deny on invalid local policy", false)
945
+ .option("--json", "Emit JSON", false)
946
+ .action(async (pathArg, options) => {
947
+ process.exitCode = await runRunExplainCommand({
948
+ root: resolveTargetArgument(pathArg),
949
+ command: options.command,
950
+ shell: Boolean(options.shell),
951
+ failClosed: Boolean(options.failClosed),
952
+ json: Boolean(options.json),
953
+ });
954
+ });
955
+ runCmd
956
+ .option("--shell", "HIGH RISK: allow shell interpretation (still requires policy allow)", false)
957
+ .option("--timeout <ms>", "Kill after timeout milliseconds", "30000")
958
+ .option("--fail-closed", "Deny on invalid local policy", false)
959
+ .option("--json", "Emit JSON", false)
960
+ .argument("[path]", "Repository path (ignored when -- separates args; use cwd)")
961
+ .argument("[command...]", "Command argv after --")
962
+ .allowUnknownOption(false)
963
+ .action(async (pathArg, commandParts, cmd) => {
964
+ const options = cmd.opts();
965
+ // Commander may put path as first arg when no `--`; prefer argv after `--` via raw args.
966
+ const raw = cmd.args;
967
+ let root = process.cwd();
968
+ let argv = [];
969
+ // If first token looks like a path-only invocation without command, treat as root.
970
+ if (commandParts && commandParts.length > 0) {
971
+ argv = commandParts;
972
+ if (pathArg)
973
+ root = resolveTargetArgument(pathArg);
974
+ }
975
+ else if (pathArg && !pathArg.startsWith("-")) {
976
+ // `run npm --version` style: pathArg is actually first command token
977
+ argv = [pathArg, ...(raw.slice(1) ?? [])];
978
+ }
979
+ // Prefer explicit `--` remainder from process.argv
980
+ const dd = process.argv.indexOf("--");
981
+ if (dd >= 0) {
982
+ argv = process.argv.slice(dd + 1);
983
+ root = process.cwd();
984
+ }
985
+ const timeoutMs = Number(options.timeout ?? 30000);
986
+ process.exitCode = await runRunCommand({
987
+ root: resolveTargetArgument(root),
988
+ argv,
989
+ shell: Boolean(options.shell),
990
+ timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 30000,
991
+ failClosed: Boolean(options.failClosed),
992
+ json: Boolean(options.json),
993
+ });
994
+ });
995
+ const workspace = program
996
+ .command("workspace")
997
+ .description("Multi-repo workspace model under .agentdoctor/workspaces/ (local isolation)");
998
+ workspace
999
+ .command("init")
1000
+ .description("Create a workspace JSON record")
1001
+ .requiredOption("--name <name>", "Workspace display name")
1002
+ .option("--id <id>", "Optional workspace id")
1003
+ .option("--repo <path>", "Initial repository root to add")
1004
+ .option("--allow-cross-read", "Allow reading other member repos (default false)", false)
1005
+ .argument("[path]", "Control root (stores .agentdoctor/workspaces)")
1006
+ .option("--json", "Emit JSON", false)
1007
+ .action(async (pathArg, options) => {
1008
+ process.exitCode = await runWorkspaceCommand({
1009
+ action: "init",
1010
+ root: resolveTargetArgument(pathArg),
1011
+ name: options.name,
1012
+ ...(options.id ? { id: options.id } : {}),
1013
+ ...(options.repo ? { repositoryRoot: resolveTargetArgument(options.repo) } : {}),
1014
+ allowCrossRead: Boolean(options.allowCrossRead),
1015
+ json: Boolean(options.json),
1016
+ });
1017
+ });
1018
+ workspace
1019
+ .command("add")
1020
+ .description("Add a repository root to a workspace")
1021
+ .requiredOption("--id <id>", "Workspace id")
1022
+ .requiredOption("--repo <path>", "Repository root to add")
1023
+ .argument("[path]", "Control root")
1024
+ .option("--json", "Emit JSON", false)
1025
+ .action(async (pathArg, options) => {
1026
+ process.exitCode = await runWorkspaceCommand({
1027
+ action: "add",
1028
+ root: resolveTargetArgument(pathArg),
1029
+ id: options.id,
1030
+ repositoryRoot: resolveTargetArgument(options.repo),
1031
+ json: Boolean(options.json),
1032
+ });
1033
+ });
1034
+ workspace
1035
+ .command("list")
1036
+ .description("List workspaces")
1037
+ .argument("[path]", "Control root")
1038
+ .option("--json", "Emit JSON", false)
1039
+ .action(async (pathArg, options) => {
1040
+ process.exitCode = await runWorkspaceCommand({
1041
+ action: "list",
1042
+ root: resolveTargetArgument(pathArg),
1043
+ json: Boolean(options.json),
1044
+ });
1045
+ });
1046
+ workspace
1047
+ .command("status")
1048
+ .description("Show workspace membership and missing roots")
1049
+ .requiredOption("--id <id>", "Workspace id")
1050
+ .argument("[path]", "Control root")
1051
+ .option("--json", "Emit JSON", false)
1052
+ .action(async (pathArg, options) => {
1053
+ process.exitCode = await runWorkspaceCommand({
1054
+ action: "status",
1055
+ root: resolveTargetArgument(pathArg),
1056
+ id: options.id,
1057
+ json: Boolean(options.json),
1058
+ });
1059
+ });
1060
+ workspace
1061
+ .command("remove")
1062
+ .description("Delete a workspace record")
1063
+ .requiredOption("--id <id>", "Workspace id")
1064
+ .argument("[path]", "Control root")
1065
+ .option("--json", "Emit JSON", false)
1066
+ .action(async (pathArg, options) => {
1067
+ process.exitCode = await runWorkspaceCommand({
1068
+ action: "remove",
1069
+ root: resolveTargetArgument(pathArg),
1070
+ id: options.id,
1071
+ json: Boolean(options.json),
1072
+ });
1073
+ });
557
1074
  program
558
1075
  .command("changes")
559
1076
  .description("Analyze git working-tree / range changes")
@@ -721,6 +1238,95 @@ export function createProgram() {
721
1238
  json: Boolean(options.json),
722
1239
  });
723
1240
  });
1241
+ program
1242
+ .command("chat")
1243
+ .description("Project Chat (2.1): evidence-backed conversation about this repository (requires AI provider)")
1244
+ .argument("[path]", "Repository path (default: current directory)")
1245
+ .action(async (pathArg) => {
1246
+ process.exitCode = await runChatCommand({
1247
+ root: resolveTargetArgument(pathArg),
1248
+ });
1249
+ });
1250
+ program
1251
+ .command("ask")
1252
+ .description("One-shot Project Chat question (same engine as chat)")
1253
+ .argument("<question>", "Question about the project")
1254
+ .argument("[path]", "Repository path (default: current directory)")
1255
+ .option("--json", "Emit JSON response", false)
1256
+ .action(async (question, pathArg, options) => {
1257
+ process.exitCode = await runAskCommand({
1258
+ question,
1259
+ root: resolveTargetArgument(pathArg),
1260
+ json: Boolean(options.json),
1261
+ });
1262
+ });
1263
+ program
1264
+ .command("plan")
1265
+ .description("Project Agent plan (2.1 M3): understand goal, list likely files, request approval — no file edits")
1266
+ .argument("<goal>", "What you want to build or change")
1267
+ .argument("[path]", "Repository path (default: current directory)")
1268
+ .option("--json", "Emit JSON plan", false)
1269
+ .option("--approve", "Record human approval (still no writes in M3)", false)
1270
+ .action(async (goal, pathArg, options) => {
1271
+ process.exitCode = await runPlanCommand({
1272
+ goal,
1273
+ root: resolveTargetArgument(pathArg),
1274
+ json: Boolean(options.json),
1275
+ approve: Boolean(options.approve),
1276
+ });
1277
+ });
1278
+ program
1279
+ .command("agent")
1280
+ .description("Project Agent (2.1): list tools, run tools, plan, or apply approved file/command changes")
1281
+ .argument("[path]", "Repository path (default: current directory)")
1282
+ .option("--list-tools", "List available agent tools", false)
1283
+ .option("--tool <name>", "Run a single tool by name")
1284
+ .option("--goal <text>", "Build a change plan for this goal")
1285
+ .option("--approve", "Record explicit human approval", false)
1286
+ .option("--apply", "Apply approved changes (requires --approve)", false)
1287
+ .option("--apply-ops <json>", "JSON array of {name, arguments} tool ops for --apply")
1288
+ .option("--skip-verify", "Skip post-change verification after apply", false)
1289
+ .option("--run-tests", "Run controlled tests during verification", false)
1290
+ .option("--json", "Emit JSON", false)
1291
+ .action(async (pathArg, options) => {
1292
+ process.exitCode = await runAgentCommand({
1293
+ root: resolveTargetArgument(pathArg),
1294
+ listTools: Boolean(options.listTools),
1295
+ ...(options.tool !== undefined ? { tool: options.tool } : {}),
1296
+ ...(options.goal !== undefined ? { goal: options.goal } : {}),
1297
+ approve: Boolean(options.approve),
1298
+ apply: Boolean(options.apply),
1299
+ ...(options.applyOps !== undefined ? { applyOpsJson: options.applyOps } : {}),
1300
+ verify: !options.skipVerify,
1301
+ runTests: Boolean(options.runTests),
1302
+ json: Boolean(options.json),
1303
+ });
1304
+ });
1305
+ program
1306
+ .command("learn")
1307
+ .description("Student Learn mode (2.1 M6): explain project, viva questions, grounded documentation")
1308
+ .argument("[path]", "Repository path (default: current directory)")
1309
+ .option("--mode <mode>", "LEARN | BUILD_WITH_ME | BUILD_FOR_ME | DEVELOPER", "LEARN")
1310
+ .option("--viva", "Generate viva questions from detected stack", false)
1311
+ .option("--docs", "Generate project documentation sections", false)
1312
+ .option("--build <goal>", "BUILD_WITH_ME / BUILD_FOR_ME: explain, plan, then coding loop")
1313
+ .option("--approve", "Human approval required before BUILD writes", false)
1314
+ .option("--apply-ops <json>", "Deterministic tool ops JSON array for --build --approve")
1315
+ .option("--json", "Emit JSON", false)
1316
+ .option("--mock", "Use MockModelProvider", false)
1317
+ .action(async (pathArg, options) => {
1318
+ process.exitCode = await runLearnCommand({
1319
+ root: resolveTargetArgument(pathArg),
1320
+ ...(options.mode !== undefined ? { mode: options.mode } : {}),
1321
+ viva: Boolean(options.viva),
1322
+ docs: Boolean(options.docs),
1323
+ ...(options.build !== undefined ? { build: options.build } : {}),
1324
+ approve: Boolean(options.approve),
1325
+ ...(options.applyOps !== undefined ? { applyOpsJson: options.applyOps } : {}),
1326
+ json: Boolean(options.json),
1327
+ useMock: Boolean(options.mock),
1328
+ });
1329
+ });
724
1330
  const platform = program
725
1331
  .command("platform")
726
1332
  .description("AgentDoctor 2.0 local platform (intelligence, Action Policy Evaluator, sessions, reports)");
@@ -752,8 +1358,10 @@ export function createProgram() {
752
1358
  });
753
1359
  platform
754
1360
  .command("test-impact")
755
- .description("Analyze which tests likely relate to git changes (heuristic; does not run tests)")
1361
+ .description("Analyze which tests relate to git changes (heuristic; coverage-backed with --coverage; does not run tests)")
756
1362
  .argument("[path]", "Repository path")
1363
+ .option("--coverage <path>", "LCOV / Istanbul JSON / Cobertura XML coverage file")
1364
+ .option("--since <ref>", "Diff since git ref")
757
1365
  .option("--json", "Emit JSON", false)
758
1366
  .action(async (pathArg, _options, command) => {
759
1367
  const options = command.optsWithGlobals();
@@ -761,6 +1369,8 @@ export function createProgram() {
761
1369
  action: "test-impact",
762
1370
  root: resolveTargetArgument(pathArg),
763
1371
  json: Boolean(options.json),
1372
+ ...(options.coverage ? { coverage: options.coverage } : {}),
1373
+ ...(options.since ? { since: options.since } : {}),
764
1374
  });
765
1375
  });
766
1376
  const registerPolicyCheck = (name, aliasNote) => {