@praneeth_54/agentdoctor 2.1.0 → 3.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 (167) hide show
  1. package/CHANGELOG.md +86 -3
  2. package/README.md +490 -290
  3. package/dist/agent/chat/deterministic.d.ts +6 -0
  4. package/dist/agent/chat/deterministic.js +180 -0
  5. package/dist/agent/chat/service.js +8 -14
  6. package/dist/agent/context/retrieve.d.ts +2 -1
  7. package/dist/agent/context/retrieve.js +14 -1
  8. package/dist/agent/index.d.ts +2 -0
  9. package/dist/agent/index.js +1 -0
  10. package/dist/agent/loop.d.ts +2 -0
  11. package/dist/agent/loop.js +31 -0
  12. package/dist/agent/roles.d.ts +12 -0
  13. package/dist/agent/roles.js +138 -0
  14. package/dist/agent/runtime.d.ts +21 -5
  15. package/dist/agent/runtime.js +141 -28
  16. package/dist/agent/tools/execute.d.ts +2 -0
  17. package/dist/agent/tools/execute.js +49 -43
  18. package/dist/agent/tools/write.d.ts +1 -1
  19. package/dist/agent/tools/write.js +18 -4
  20. package/dist/ai/config.js +1 -0
  21. package/dist/ai/providers/adversarial-local.d.ts +7 -0
  22. package/dist/ai/providers/adversarial-local.js +146 -0
  23. package/dist/ai/redact.d.ts +1 -0
  24. package/dist/ai/redact.js +47 -7
  25. package/dist/ai/types.d.ts +1 -1
  26. package/dist/cli/commands/agent.d.ts +2 -0
  27. package/dist/cli/commands/agent.js +32 -11
  28. package/dist/cli/commands/architecture.js +7 -2
  29. package/dist/cli/commands/brain.js +7 -2
  30. package/dist/cli/commands/chat.js +15 -7
  31. package/dist/cli/commands/complete.js +13 -3
  32. package/dist/cli/commands/fix.js +6 -0
  33. package/dist/cli/commands/learn.js +7 -2
  34. package/dist/cli/commands/mcp.js +7 -3
  35. package/dist/cli/commands/platform.js +7 -2
  36. package/dist/cli/commands/policy-graph-run.js +7 -0
  37. package/dist/cli/commands/product.d.ts +19 -0
  38. package/dist/cli/commands/product.js +157 -0
  39. package/dist/cli/commands/scan.js +6 -0
  40. package/dist/cli/commands/start.d.ts +15 -0
  41. package/dist/cli/commands/start.js +86 -0
  42. package/dist/cli/commands/v2.js +61 -11
  43. package/dist/cli/commands/verify.js +6 -0
  44. package/dist/cli/program.js +141 -0
  45. package/dist/cli/safe-root.d.ts +17 -0
  46. package/dist/cli/safe-root.js +32 -0
  47. package/dist/constants.d.ts +1 -1
  48. package/dist/constants.js +1 -1
  49. package/dist/core/brain-cli/service.js +5 -2
  50. package/dist/core/monorepo/detect.js +10 -0
  51. package/dist/core/secrets/scan.js +9 -0
  52. package/dist/core/understanding/brain/storage/store.d.ts +4 -0
  53. package/dist/core/understanding/brain/storage/store.js +22 -2
  54. package/dist/dashboard/page.d.ts +2 -0
  55. package/dist/dashboard/page.js +53 -0
  56. package/dist/dashboard/server.js +117 -140
  57. package/dist/dashboard/ui/client.d.ts +2 -0
  58. package/dist/dashboard/ui/client.js +2 -0
  59. package/dist/dashboard/ui/styles.d.ts +2 -0
  60. package/dist/dashboard/ui/styles.js +138 -0
  61. package/dist/discovery/files.js +13 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/intelligence/graph/build.js +46 -38
  65. package/dist/intelligence/graph/incremental.d.ts +2 -0
  66. package/dist/intelligence/graph/incremental.js +9 -0
  67. package/dist/intelligence/resolve/imports.js +1 -1
  68. package/dist/languages/dart.d.ts +10 -0
  69. package/dist/languages/dart.js +99 -0
  70. package/dist/languages/go.d.ts +13 -7
  71. package/dist/languages/go.js +84 -24
  72. package/dist/languages/index.d.ts +5 -1
  73. package/dist/languages/index.js +13 -36
  74. package/dist/languages/java.d.ts +10 -0
  75. package/dist/languages/java.js +80 -0
  76. package/dist/languages/kotlin.d.ts +10 -0
  77. package/dist/languages/kotlin.js +85 -0
  78. package/dist/languages/rust.d.ts +10 -0
  79. package/dist/languages/rust.js +93 -0
  80. package/dist/languages/types.d.ts +4 -3
  81. package/dist/mcp/agent/registry.d.ts +1 -1
  82. package/dist/mcp/agent/registry.js +109 -23
  83. package/dist/mcp/intelligence/handlers.d.ts +3 -0
  84. package/dist/mcp/intelligence/handlers.js +28 -0
  85. package/dist/mcp/intelligence/path-safety.d.ts +6 -1
  86. package/dist/mcp/intelligence/path-safety.js +55 -2
  87. package/dist/mcp/intelligence/registry.d.ts +1 -1
  88. package/dist/mcp/intelligence/registry.js +30 -1
  89. package/dist/platform/graph/build.js +9 -0
  90. package/dist/product/api/doctor.d.ts +14 -0
  91. package/dist/product/api/doctor.js +185 -0
  92. package/dist/product/api/openapi.d.ts +7 -0
  93. package/dist/product/api/openapi.js +122 -0
  94. package/dist/product/approval/model.d.ts +30 -0
  95. package/dist/product/approval/model.js +64 -0
  96. package/dist/product/approval/session.d.ts +49 -0
  97. package/dist/product/approval/session.js +134 -0
  98. package/dist/product/database/doctor.d.ts +30 -0
  99. package/dist/product/database/doctor.js +184 -0
  100. package/dist/product/decisions/ledger.d.ts +36 -0
  101. package/dist/product/decisions/ledger.js +141 -0
  102. package/dist/product/deps/analyze.d.ts +46 -0
  103. package/dist/product/deps/analyze.js +137 -0
  104. package/dist/product/deps/lockfiles.d.ts +25 -0
  105. package/dist/product/deps/lockfiles.js +200 -0
  106. package/dist/product/discovery/roots.d.ts +52 -0
  107. package/dist/product/discovery/roots.js +231 -0
  108. package/dist/product/dna/build.d.ts +50 -0
  109. package/dist/product/dna/build.js +255 -0
  110. package/dist/product/eval/lab.d.ts +17 -0
  111. package/dist/product/eval/lab.js +218 -0
  112. package/dist/product/events/doctor.d.ts +21 -0
  113. package/dist/product/events/doctor.js +147 -0
  114. package/dist/product/evidence-scan.d.ts +12 -0
  115. package/dist/product/evidence-scan.js +46 -0
  116. package/dist/product/evolution/timeline.d.ts +29 -0
  117. package/dist/product/evolution/timeline.js +123 -0
  118. package/dist/product/features/intelligence.d.ts +23 -0
  119. package/dist/product/features/intelligence.js +158 -0
  120. package/dist/product/forensic/mode.d.ts +25 -0
  121. package/dist/product/forensic/mode.js +70 -0
  122. package/dist/product/graph/enrich-languages.d.ts +20 -0
  123. package/dist/product/graph/enrich-languages.js +168 -0
  124. package/dist/product/health/code-health.d.ts +20 -0
  125. package/dist/product/health/code-health.js +149 -0
  126. package/dist/product/index.d.ts +72 -0
  127. package/dist/product/index.js +36 -0
  128. package/dist/product/ledger/change-ledger.d.ts +23 -0
  129. package/dist/product/ledger/change-ledger.js +64 -0
  130. package/dist/product/map/software-map.d.ts +17 -0
  131. package/dist/product/map/software-map.js +96 -0
  132. package/dist/product/memory/institutional.d.ts +16 -0
  133. package/dist/product/memory/institutional.js +92 -0
  134. package/dist/product/ops/incident.d.ts +20 -0
  135. package/dist/product/ops/incident.js +61 -0
  136. package/dist/product/ops/infra.d.ts +15 -0
  137. package/dist/product/ops/infra.js +112 -0
  138. package/dist/product/org/model.d.ts +34 -0
  139. package/dist/product/org/model.js +195 -0
  140. package/dist/product/privacy/doctor.d.ts +13 -0
  141. package/dist/product/privacy/doctor.js +90 -0
  142. package/dist/product/requirements/trace.d.ts +21 -0
  143. package/dist/product/requirements/trace.js +166 -0
  144. package/dist/product/search/index.d.ts +29 -0
  145. package/dist/product/search/index.js +116 -0
  146. package/dist/product/search/software-search.d.ts +19 -0
  147. package/dist/product/search/software-search.js +105 -0
  148. package/dist/product/security/doctor.d.ts +23 -0
  149. package/dist/product/security/doctor.js +124 -0
  150. package/dist/product/self/diagnose.d.ts +15 -0
  151. package/dist/product/self/diagnose.js +82 -0
  152. package/dist/product/techdebt/roadmap.d.ts +20 -0
  153. package/dist/product/techdebt/roadmap.js +118 -0
  154. package/dist/product/testbrain/analyze.d.ts +29 -0
  155. package/dist/product/testbrain/analyze.js +129 -0
  156. package/dist/product/truth.d.ts +12 -0
  157. package/dist/product/truth.js +30 -0
  158. package/dist/product/twin/digital-twin.d.ts +23 -0
  159. package/dist/product/twin/digital-twin.js +52 -0
  160. package/dist/product/twin/store.d.ts +21 -0
  161. package/dist/product/twin/store.js +78 -0
  162. package/dist/product/whatif/engine.d.ts +28 -0
  163. package/dist/product/whatif/engine.js +91 -0
  164. package/dist/project/ownership.d.ts +65 -0
  165. package/dist/project/ownership.js +169 -0
  166. package/dist/utils/fs.js +10 -2
  167. package/package.json +1 -1
package/dist/ai/redact.js CHANGED
@@ -1,18 +1,58 @@
1
1
  /**
2
2
  * Redaction for outbound model payloads.
3
3
  * Extends the local-ai redaction patterns; never send raw secrets to providers.
4
+ * Patterns avoid nested unbounded quantifiers (ReDoS-safe).
4
5
  */
5
- const SECRET_PATTERNS = [
6
+ const REDACTED = "[REDACTED]";
7
+ const SIMPLE_SECRET_PATTERNS = [
6
8
  /\b(AKIA[0-9A-Z]{16})\b/g,
7
9
  /\b(ghp_[A-Za-z0-9]{36})\b/g,
8
- /\b(sk-[A-Za-z0-9]{20,})\b/g,
9
- /-----BEGIN [^-]+PRIVATE KEY-----[\s\S]*?-----END [^-]+PRIVATE KEY-----/g,
10
- /\b((?:export\s+)?(?:AWS_SECRET_ACCESS_KEY|GITHUB_TOKEN|NPM_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY|STRIPE_SECRET_KEY|AGENTDOCTOR_AI_API_KEY)\s*=\s*)(['"]?)[^\s'"]+\2/gi,
10
+ /\b(sk-[A-Za-z0-9]{20,80})\b/g,
11
+ /\b((?:export\s+)?(?:AWS_SECRET_ACCESS_KEY|GITHUB_TOKEN|NPM_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY|STRIPE_SECRET_KEY|AGENTDOCTOR_AI_API_KEY)\s*=\s*)(\S+)/gi,
11
12
  ];
13
+ /** Linear scan — avoids polynomial backtracking on PEM fences. */
14
+ function redactPrivateKeyBlocks(text) {
15
+ const beginToken = "-----BEGIN ";
16
+ const endToken = "-----END ";
17
+ const keySuffix = "PRIVATE KEY-----";
18
+ let out = "";
19
+ let i = 0;
20
+ while (i < text.length) {
21
+ const start = text.indexOf(beginToken, i);
22
+ if (start === -1) {
23
+ out += text.slice(i);
24
+ break;
25
+ }
26
+ out += text.slice(i, start);
27
+ const afterBegin = start + beginToken.length;
28
+ const keyLabelEnd = text.indexOf(keySuffix, afterBegin);
29
+ if (keyLabelEnd === -1 || keyLabelEnd - afterBegin > 64) {
30
+ out += text.slice(start, afterBegin);
31
+ i = afterBegin;
32
+ continue;
33
+ }
34
+ const headerEnd = keyLabelEnd + keySuffix.length;
35
+ const endStart = text.indexOf(endToken, headerEnd);
36
+ if (endStart === -1) {
37
+ out += text.slice(start, headerEnd);
38
+ i = headerEnd;
39
+ continue;
40
+ }
41
+ const endLabelEnd = text.indexOf(keySuffix, endStart + endToken.length);
42
+ if (endLabelEnd === -1 || endLabelEnd - (endStart + endToken.length) > 64) {
43
+ out += text.slice(start, headerEnd);
44
+ i = headerEnd;
45
+ continue;
46
+ }
47
+ out += REDACTED;
48
+ i = endLabelEnd + keySuffix.length;
49
+ }
50
+ return out;
51
+ }
12
52
  export function redactForModel(text) {
13
- let out = text;
14
- for (const re of SECRET_PATTERNS) {
15
- out = out.replace(re, "[REDACTED]");
53
+ let out = redactPrivateKeyBlocks(text);
54
+ for (const re of SIMPLE_SECRET_PATTERNS) {
55
+ out = out.replace(re, (_m, prefix) => typeof prefix === "string" && prefix.includes("=") ? `${prefix}${REDACTED}` : REDACTED);
16
56
  }
17
57
  return out;
18
58
  }
@@ -2,7 +2,7 @@
2
2
  * Provider-agnostic model types for AgentDoctor 2.1 Project AI Agent.
3
3
  * The model reasons; AgentDoctor owns context, tools, and verification.
4
4
  */
5
- export type AiProviderId = "none" | "mock" | "openai-compatible" | "ollama" | "anthropic" | "gemini";
5
+ export type AiProviderId = "none" | "deterministic" | "mock" | "openai-compatible" | "ollama" | "anthropic" | "gemini";
6
6
  export type ChatRole = "system" | "user" | "assistant" | "tool";
7
7
  export interface ChatMessage {
8
8
  role: ChatRole;
@@ -1,3 +1,4 @@
1
+ import { type AgentRole } from "../../agent/roles.js";
1
2
  import { type ExitCode } from "../../types/index.js";
2
3
  /**
3
4
  * agentdoctor plan — produce an implementation plan (no writes unless --apply elsewhere).
@@ -24,4 +25,5 @@ export declare function runAgentCommand(options: {
24
25
  applyOpsJson?: string;
25
26
  verify?: boolean;
26
27
  runTests?: boolean;
28
+ role?: AgentRole;
27
29
  }): Promise<ExitCode>;
@@ -1,15 +1,21 @@
1
1
  import { approvePlan, buildAgentPlan, formatAgentPlan } from "../../agent/plan.js";
2
2
  import { evaluateApproval, formatApprovalPrompt } from "../../agent/approvals.js";
3
3
  import { runCodingLoop } from "../../agent/loop.js";
4
+ import { runRoleAgent } from "../../agent/roles.js";
4
5
  import { executeAgentTool, getToolSpec, listAgentToolSpecs, newToolCall, } from "../../agent/tools/index.js";
5
6
  import { EXIT_CODES } from "../../types/index.js";
6
- import { resolveRepoRoot } from "../../utils/path.js";
7
+ import { resolveCliProjectRoot } from "../safe-root.js";
7
8
  import { colors } from "../../utils/colors.js";
8
9
  /**
9
10
  * agentdoctor plan — produce an implementation plan (no writes unless --apply elsewhere).
10
11
  */
11
12
  export async function runPlanCommand(options) {
12
- const root = resolveRepoRoot(options.root ?? process.cwd());
13
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
14
+ if (!gated.ok) {
15
+ console.error(`Error: ${gated.message}`);
16
+ return gated.code;
17
+ }
18
+ const root = gated.root;
13
19
  const plan = await buildAgentPlan({ root, goal: options.goal });
14
20
  if (options.approve) {
15
21
  const approved = approvePlan(plan, true);
@@ -34,7 +40,12 @@ export async function runPlanCommand(options) {
34
40
  * agentdoctor agent — inspect, plan, and (with --approve) mutate via controlled tools.
35
41
  */
36
42
  export async function runAgentCommand(options) {
37
- const root = resolveRepoRoot(options.root ?? process.cwd());
43
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
44
+ if (!gated.ok) {
45
+ console.error(`Error: ${gated.message}`);
46
+ return gated.code;
47
+ }
48
+ const root = gated.root;
38
49
  const approvedByHuman = options.approve === true;
39
50
  if (options.listTools) {
40
51
  const tools = listAgentToolSpecs({ includeWrite: true, includeExecute: true });
@@ -107,14 +118,24 @@ export async function runAgentCommand(options) {
107
118
  return EXIT_CODES.USAGE_ERROR;
108
119
  }
109
120
  }
110
- const result = await runCodingLoop({
111
- root,
112
- goal: options.goal,
113
- approvedByHuman: true,
114
- ...(toolCalls ? { toolCalls } : {}),
115
- verify: options.verify !== false,
116
- runTests: options.runTests === true,
117
- });
121
+ const result = options.role
122
+ ? await runRoleAgent({
123
+ role: options.role,
124
+ root,
125
+ goal: options.goal,
126
+ approvedByHuman: true,
127
+ ...(toolCalls ? { toolCalls } : {}),
128
+ verify: options.verify !== false,
129
+ runTests: options.runTests === true,
130
+ })
131
+ : await runCodingLoop({
132
+ root,
133
+ goal: options.goal,
134
+ approvedByHuman: true,
135
+ ...(toolCalls ? { toolCalls } : {}),
136
+ verify: options.verify !== false,
137
+ runTests: options.runTests === true,
138
+ });
118
139
  if (options.json) {
119
140
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
120
141
  }
@@ -1,5 +1,5 @@
1
1
  import { EXIT_CODES } from "../../types/index.js";
2
- import { resolveRepoRoot } from "../../utils/path.js";
2
+ import { resolveCliProjectRoot } from "../safe-root.js";
3
3
  import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
4
4
  import { buildC4Views } from "../../architecture/c4.js";
5
5
  import { checkArchitectureAtRoot, explainArchitecture, initArchitecture, loadArchitectureContract, } from "../../architecture/contract.js";
@@ -7,7 +7,12 @@ function printJson(value) {
7
7
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
8
8
  }
9
9
  export async function runArchitectureCommand(options) {
10
- const root = resolveRepoRoot(options.root ?? process.cwd());
10
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
11
+ if (!gated.ok) {
12
+ console.error(`Error: ${gated.message}`);
13
+ return gated.code;
14
+ }
15
+ const root = gated.root;
11
16
  try {
12
17
  switch (options.action) {
13
18
  case "init": {
@@ -1,12 +1,17 @@
1
1
  import { EXIT_CODES } from "../../types/index.js";
2
- import { resolveRepoRoot } from "../../utils/path.js";
2
+ import { resolveCliProjectRoot } from "../safe-root.js";
3
3
  import { exportBrain, getBrainStatus, importBrain, initBrainStore, inspectBrainSummary, listBrainSnapshots, loadLatestBrain, rebuildBrain, searchBrain, showBrainSnapshot, } from "../../core/brain-cli/service.js";
4
4
  import { listProposals, reviewProposal, writeBrainProductSnapshot, } from "../../core/brain-product/init.js";
5
5
  function printJson(value) {
6
6
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
7
7
  }
8
8
  export async function runBrainCommand(options) {
9
- const root = resolveRepoRoot(options.root ?? process.cwd());
9
+ const gated = await resolveCliProjectRoot(options.root);
10
+ if (!gated.ok) {
11
+ console.error(`Error: ${gated.message}`);
12
+ return gated.code;
13
+ }
14
+ const root = gated.root;
10
15
  const json = options.json === true;
11
16
  try {
12
17
  switch (options.action) {
@@ -1,9 +1,9 @@
1
1
  import readline from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
- import { ChatService, CHAT_PROVIDER_NONE_MESSAGE, formatChatResponseForCli, } from "../../agent/chat/index.js";
3
+ import { ChatService, formatChatResponseForCli } from "../../agent/chat/index.js";
4
4
  import { MockModelProvider, createModelProvider, loadAiConfig } from "../../ai/index.js";
5
5
  import { EXIT_CODES } from "../../types/index.js";
6
- import { resolveRepoRoot } from "../../utils/path.js";
6
+ import { resolveCliProjectRoot } from "../safe-root.js";
7
7
  import { colors } from "../../utils/colors.js";
8
8
  function printBanner(options) {
9
9
  const lines = [
@@ -51,7 +51,12 @@ function printHelp() {
51
51
  ].join("\n"));
52
52
  }
53
53
  export async function runAskCommand(options) {
54
- const root = resolveRepoRoot(options.root ?? process.cwd());
54
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
55
+ if (!gated.ok) {
56
+ console.error(`Error: ${gated.message}`);
57
+ return gated.code;
58
+ }
59
+ const root = gated.root;
55
60
  const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
56
61
  const chat = new ChatService({
57
62
  root,
@@ -78,7 +83,12 @@ export async function runAskCommand(options) {
78
83
  }
79
84
  }
80
85
  export async function runChatCommand(options) {
81
- const root = resolveRepoRoot(options.root ?? process.cwd());
86
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
87
+ if (!gated.ok) {
88
+ console.error(`Error: ${gated.message}`);
89
+ return gated.code;
90
+ }
91
+ const root = gated.root;
82
92
  const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
83
93
  const chat = new ChatService({
84
94
  root,
@@ -94,9 +104,7 @@ export async function runChatCommand(options) {
94
104
  frameworks: started.summary.frameworks,
95
105
  });
96
106
  if (provider.id === "none" && !options.useMock) {
97
- process.stdout.write(`${CHAT_PROVIDER_NONE_MESSAGE}\n`);
98
- await chat.end();
99
- return EXIT_CODES.USAGE_ERROR;
107
+ process.stdout.write("Deterministic project chat (no LLM). Architecture, auth, and dependency questions use local analyzers.\n\n");
100
108
  }
101
109
  const scripted = options.scriptedInputs ? [...options.scriptedInputs] : undefined;
102
110
  const rl = scripted === undefined
@@ -1,5 +1,5 @@
1
1
  import { EXIT_CODES } from "../../types/index.js";
2
- import { resolveRepoRoot } from "../../utils/path.js";
2
+ import { resolveCliProjectRoot } from "../safe-root.js";
3
3
  import { runProjectInit } from "../../core/brain-product/init.js";
4
4
  import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
5
5
  import { analyzeGitIntelligence, deadCodeCategory } from "../../intelligence/git/analyze.js";
@@ -14,7 +14,12 @@ function printJson(value) {
14
14
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
15
15
  }
16
16
  export async function runInitCommand(options) {
17
- const root = resolveRepoRoot(options.root ?? process.cwd());
17
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
18
+ if (!gated.ok) {
19
+ console.error(`Error: ${gated.message}`);
20
+ return gated.code;
21
+ }
22
+ const root = gated.root;
18
23
  const result = await runProjectInit(root, {
19
24
  ...(options.name ? { projectName: options.name } : {}),
20
25
  ...(options.domain ? { businessDomain: options.domain } : {}),
@@ -27,7 +32,12 @@ export async function runInitCommand(options) {
27
32
  return EXIT_CODES.SUCCESS;
28
33
  }
29
34
  export async function runV2SurfaceCommand(options) {
30
- const root = resolveRepoRoot(options.root ?? process.cwd());
35
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
36
+ if (!gated.ok) {
37
+ console.error(`Error: ${gated.message}`);
38
+ return gated.code;
39
+ }
40
+ const root = gated.root;
31
41
  try {
32
42
  switch (options.action) {
33
43
  case "graph-ast": {
@@ -6,6 +6,7 @@ import { buildFixPlan } from "../../core/fix/plan.js";
6
6
  import { renderFixPlanTerminal } from "../../core/fix/render.js";
7
7
  import { runFix } from "../../core/fix/run.js";
8
8
  import { scan } from "../../core/scanner/scan.js";
9
+ import { classifyBroadUserScanRoot } from "../../product/discovery/roots.js";
9
10
  import { colors } from "../../utils/colors.js";
10
11
  /**
11
12
  * Safe Repository Mutation — apply allowlisted agent-config exclusions.
@@ -16,6 +17,11 @@ export async function runFixCommand(options) {
16
17
  console.error(`Error: not a directory: ${target}`);
17
18
  return EXIT_CODES.USAGE_ERROR;
18
19
  }
20
+ const broad = classifyBroadUserScanRoot(target);
21
+ if (broad.blocked) {
22
+ console.error(`Error: ${broad.reason}`);
23
+ return EXIT_CODES.USAGE_ERROR;
24
+ }
19
25
  const dryRun = options.dryRun === true;
20
26
  const yes = options.yes === true;
21
27
  try {
@@ -2,9 +2,14 @@ import { MockModelProvider, createModelProvider, loadAiConfig } from "../../ai/i
2
2
  import { StudentService } from "../../agent/student.js";
3
3
  import { parseAgentMode, defaultStudentMode } from "../../agent/modes.js";
4
4
  import { EXIT_CODES } from "../../types/index.js";
5
- import { resolveRepoRoot } from "../../utils/path.js";
5
+ import { resolveCliProjectRoot } from "../safe-root.js";
6
6
  export async function runLearnCommand(options) {
7
- const root = resolveRepoRoot(options.root ?? process.cwd());
7
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
8
+ if (!gated.ok) {
9
+ console.error(`Error: ${gated.message}`);
10
+ return gated.code;
11
+ }
12
+ const root = gated.root;
8
13
  const mode = parseAgentMode(options.mode) ?? defaultStudentMode();
9
14
  const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
10
15
  const resolvedMode = options.build
@@ -1,13 +1,17 @@
1
1
  import { EXIT_CODES } from "../../types/index.js";
2
- import { resolveRepoRoot } from "../../utils/path.js";
2
+ import { resolveCliProjectRoot } from "../safe-root.js";
3
3
  import { runAgentDoctorMcpStdio } from "../../mcp/agentdoctor/server.js";
4
4
  /**
5
5
  * Start combined AgentDoctor MCP (Brain tools + intelligence tools) over STDIO.
6
6
  */
7
7
  export async function runMcpCommand(options) {
8
- const root = resolveRepoRoot(options.root);
8
+ const gated = await resolveCliProjectRoot(options.root);
9
+ if (!gated.ok) {
10
+ console.error(`Error: ${gated.message}`);
11
+ return gated.code;
12
+ }
9
13
  await runAgentDoctorMcpStdio({
10
- root,
14
+ root: gated.root,
11
15
  buildIfMissing: options.buildIfMissing !== false,
12
16
  });
13
17
  return EXIT_CODES.SUCCESS;
@@ -1,13 +1,18 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { EXIT_CODES } from "../../types/index.js";
3
- import { resolveRepoRoot } from "../../utils/path.js";
3
+ import { resolveCliProjectRoot } from "../safe-root.js";
4
4
  import { analyzeRenameImpact, analyzeTestImpact, appendSessionEvent, buildProvenance, compareCommits, createSession, endSession, evaluateAgentAction, exportSessionMarkdown, formatTestImpactHuman, listSessions, loadSession, persistTestImpactReport, planContext, runPlatformScan, saveProvenance, EVALUATE_ONLY, } from "../../platform/index.js";
5
5
  import { buildRepositoryGraph } from "../../platform/graph/build.js";
6
6
  function printJson(value) {
7
7
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
8
8
  }
9
9
  export async function runPlatformCommand(options) {
10
- const root = resolveRepoRoot(options.root ?? process.cwd());
10
+ const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
11
+ if (!gated.ok) {
12
+ console.error(`Error: ${gated.message}`);
13
+ return gated.code;
14
+ }
15
+ const root = gated.root;
11
16
  try {
12
17
  switch (options.action) {
13
18
  case "scan": {
@@ -3,6 +3,7 @@ import { runControlledCommand } from "../../enforcement/runner.js";
3
3
  import { graphBuild, graphRebuild, graphStatus, graphUpdate, } from "../../intelligence/graph/incremental.js";
4
4
  import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
5
5
  import { EXIT_CODES } from "../../types/index.js";
6
+ import { resolveCliProjectRoot } from "../safe-root.js";
6
7
  function emit(json, value, human) {
7
8
  if (json) {
8
9
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
@@ -13,6 +14,12 @@ function emit(json, value, human) {
13
14
  }
14
15
  export async function runGraphSurfaceCommand(options) {
15
16
  try {
17
+ const gated = await resolveCliProjectRoot(options.root);
18
+ if (!gated.ok) {
19
+ console.error(`Error: ${gated.message}`);
20
+ return gated.code;
21
+ }
22
+ options = { ...options, root: gated.root };
16
23
  const mode = options.mode ?? "auto";
17
24
  if (options.action === "status") {
18
25
  const status = await graphStatus({ root: options.root });
@@ -0,0 +1,19 @@
1
+ import { type ExitCode } from "../../types/index.js";
2
+ import { type AgentRole } from "../../agent/roles.js";
3
+ export type ProductCommandAction = "requirements" | "api" | "database" | "events" | "dependency" | "health-code" | "map" | "what-if" | "decisions" | "forensic" | "twin" | "eval-lab" | "self-check" | "infra" | "incident" | "security-doctor" | "privacy-doctor" | "search" | "tech-debt" | "features" | "evolution" | "memory" | "org" | "role-agent" | "test-brain";
4
+ export declare function runProductCommand(options: {
5
+ action: ProductCommandAction;
6
+ root?: string;
7
+ json?: boolean;
8
+ target?: string;
9
+ changedFiles?: string[];
10
+ query?: string;
11
+ role?: AgentRole;
12
+ goal?: string;
13
+ approve?: boolean;
14
+ apply?: boolean;
15
+ }): Promise<ExitCode>;
16
+ export declare function runChangeLedgerListCommand(options: {
17
+ root?: string;
18
+ json?: boolean;
19
+ }): Promise<ExitCode>;
@@ -0,0 +1,157 @@
1
+ import { EXIT_CODES } from "../../types/index.js";
2
+ import { resolveCliProjectRoot } from "../safe-root.js";
3
+ import { resolveTargetArgument } from "./scan.js";
4
+ import { traceRequirements, analyzeApiSurface, analyzeDatabaseSchema, analyzeEvents, analyzeDependencies, analyzeCodeHealth, buildSoftwareMap, analyzeWhatIf, loadDecisionLedger, runForensicAnalysis, buildSoftwareDigitalTwin, runEvalLab, diagnoseAgentDoctorSelf, analyzeInfra, buildIncidentHypotheses, readChangeLedger, analyzeSecuritySurface, analyzePrivacySurface, searchSymbolsAndConcepts, buildTechnicalDebtRoadmap, analyzeFeatureIntelligence, buildSoftwareEvolutionTimeline, queryMemory, loadOrganizationModel, analyzeTestBrain, } from "../../product/index.js";
5
+ import { runRoleAgent } from "../../agent/roles.js";
6
+ function printJson(value) {
7
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
8
+ }
9
+ export async function runProductCommand(options) {
10
+ const gated = await resolveCliProjectRoot(options.root ?? resolveTargetArgument(undefined));
11
+ if (!gated.ok) {
12
+ console.error(`Error: ${gated.message}`);
13
+ return gated.code;
14
+ }
15
+ const root = gated.root;
16
+ try {
17
+ let payload;
18
+ switch (options.action) {
19
+ case "requirements":
20
+ payload = await traceRequirements(root);
21
+ break;
22
+ case "api":
23
+ payload = await analyzeApiSurface(root);
24
+ break;
25
+ case "database":
26
+ payload = await analyzeDatabaseSchema(root);
27
+ break;
28
+ case "events":
29
+ payload = await analyzeEvents(root);
30
+ break;
31
+ case "dependency":
32
+ payload = await analyzeDependencies(root);
33
+ break;
34
+ case "health-code":
35
+ payload = await analyzeCodeHealth(root);
36
+ break;
37
+ case "map":
38
+ payload = await buildSoftwareMap(root);
39
+ break;
40
+ case "what-if":
41
+ if (!options.target) {
42
+ console.error("Error: what-if requires a file path or symbol target (--target or argument)");
43
+ return EXIT_CODES.USAGE_ERROR;
44
+ }
45
+ payload = await analyzeWhatIf(root, options.target);
46
+ break;
47
+ case "decisions":
48
+ payload = await loadDecisionLedger(root);
49
+ break;
50
+ case "forensic":
51
+ payload = await runForensicAnalysis(root);
52
+ break;
53
+ case "twin":
54
+ payload = await buildSoftwareDigitalTwin(root);
55
+ break;
56
+ case "eval-lab":
57
+ payload = await runEvalLab(root);
58
+ break;
59
+ case "self-check":
60
+ payload = await diagnoseAgentDoctorSelf(root);
61
+ break;
62
+ case "infra":
63
+ payload = await analyzeInfra(root);
64
+ break;
65
+ case "incident":
66
+ payload = await buildIncidentHypotheses(root, options.changedFiles ? { changedFiles: options.changedFiles } : undefined);
67
+ break;
68
+ case "security-doctor":
69
+ payload = await analyzeSecuritySurface(root);
70
+ break;
71
+ case "privacy-doctor":
72
+ payload = await analyzePrivacySurface(root);
73
+ break;
74
+ case "search":
75
+ if (!options.query?.trim()) {
76
+ console.error("Error: search requires a query (--query or argument)");
77
+ return EXIT_CODES.USAGE_ERROR;
78
+ }
79
+ payload = await searchSymbolsAndConcepts(root, options.query);
80
+ break;
81
+ case "tech-debt":
82
+ payload = await buildTechnicalDebtRoadmap(root);
83
+ break;
84
+ case "features":
85
+ payload = await analyzeFeatureIntelligence(root);
86
+ break;
87
+ case "evolution":
88
+ payload = await buildSoftwareEvolutionTimeline(root);
89
+ break;
90
+ case "memory":
91
+ if (!options.query?.trim()) {
92
+ console.error("Error: memory requires a query (--query or argument)");
93
+ return EXIT_CODES.USAGE_ERROR;
94
+ }
95
+ payload = await queryMemory(root, options.query);
96
+ break;
97
+ case "org":
98
+ payload = await loadOrganizationModel(root);
99
+ break;
100
+ case "test-brain":
101
+ payload = await analyzeTestBrain({ root });
102
+ break;
103
+ case "role-agent":
104
+ if (!options.role || !options.goal?.trim()) {
105
+ console.error("Error: role-agent requires --role and --goal");
106
+ return EXIT_CODES.USAGE_ERROR;
107
+ }
108
+ if (options.apply && !options.approve) {
109
+ console.error("Error: role-agent --apply requires --approve");
110
+ return EXIT_CODES.USAGE_ERROR;
111
+ }
112
+ payload = await runRoleAgent({
113
+ role: options.role,
114
+ goal: options.goal,
115
+ root,
116
+ approvedByHuman: options.approve === true,
117
+ });
118
+ break;
119
+ default:
120
+ return EXIT_CODES.USAGE_ERROR;
121
+ }
122
+ if (options.json) {
123
+ printJson(payload);
124
+ }
125
+ else {
126
+ const action = options.action;
127
+ process.stdout.write(`AgentDoctor ${action} completed for ${root}\n`);
128
+ if (action === "self-check" && payload && typeof payload === "object" && "ok" in payload) {
129
+ process.stdout.write(` ok: ${payload.ok}\n`);
130
+ }
131
+ if (action === "eval-lab" && payload && typeof payload === "object" && "passed" in payload) {
132
+ process.stdout.write(` passed: ${payload.passed}\n`);
133
+ }
134
+ process.stdout.write(" (use --json for full report)\n");
135
+ }
136
+ return EXIT_CODES.SUCCESS;
137
+ }
138
+ catch (error) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ console.error(`Error: ${message}`);
141
+ return EXIT_CODES.INTERNAL_ERROR;
142
+ }
143
+ }
144
+ export async function runChangeLedgerListCommand(options) {
145
+ const gated = await resolveCliProjectRoot(options.root);
146
+ if (!gated.ok) {
147
+ console.error(`Error: ${gated.message}`);
148
+ return gated.code;
149
+ }
150
+ const root = gated.root;
151
+ const payload = await readChangeLedger(root);
152
+ if (options.json)
153
+ printJson(payload);
154
+ else
155
+ process.stdout.write(`Change ledger entries: ${payload.entries.length}\n`);
156
+ return EXIT_CODES.SUCCESS;
157
+ }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { evaluateScanPolicy } from "../../core/policy/evaluate.js";
3
3
  import { scan } from "../../core/scanner/scan.js";
4
+ import { classifyBroadUserScanRoot } from "../../product/discovery/roots.js";
4
5
  import { emitGithubReports } from "../../reporters/github/emit.js";
5
6
  import { renderJsonReport } from "../../reporters/json/report.js";
6
7
  import { renderTerminalReport } from "../../reporters/terminal/report.js";
@@ -13,6 +14,11 @@ export async function runScanCommand(options) {
13
14
  console.error(`Error: not a directory: ${target}`);
14
15
  return EXIT_CODES.USAGE_ERROR;
15
16
  }
17
+ const broad = classifyBroadUserScanRoot(target);
18
+ if (broad.blocked) {
19
+ console.error(`Error: ${broad.reason}`);
20
+ return EXIT_CODES.USAGE_ERROR;
21
+ }
16
22
  try {
17
23
  const result = await scan({
18
24
  cwd: target,
@@ -0,0 +1,15 @@
1
+ import { type ExitCode } from "../../types/index.js";
2
+ export declare function runStartCommand(options: {
3
+ root?: string;
4
+ json?: boolean;
5
+ select?: string;
6
+ maxEntries?: number;
7
+ initBrain?: boolean;
8
+ rebuildBrain?: boolean;
9
+ listOnly?: boolean;
10
+ }): Promise<ExitCode>;
11
+ export declare function runDnaCommand(options: {
12
+ root?: string;
13
+ json?: boolean;
14
+ persist?: boolean;
15
+ }): Promise<ExitCode>;