@praneeth_54/agentdoctor 2.0.1 → 3.0.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 (192) hide show
  1. package/CHANGELOG.md +123 -1
  2. package/README.md +510 -260
  3. package/dist/agent/approvals.d.ts +24 -0
  4. package/dist/agent/approvals.js +64 -0
  5. package/dist/agent/chat/deterministic.d.ts +6 -0
  6. package/dist/agent/chat/deterministic.js +180 -0
  7. package/dist/agent/chat/index.d.ts +7 -0
  8. package/dist/agent/chat/index.js +5 -0
  9. package/dist/agent/chat/memory.d.ts +44 -0
  10. package/dist/agent/chat/memory.js +103 -0
  11. package/dist/agent/chat/project-summary.d.ts +16 -0
  12. package/dist/agent/chat/project-summary.js +70 -0
  13. package/dist/agent/chat/prompts.d.ts +6 -0
  14. package/dist/agent/chat/prompts.js +39 -0
  15. package/dist/agent/chat/response.d.ts +19 -0
  16. package/dist/agent/chat/response.js +109 -0
  17. package/dist/agent/chat/service.d.ts +42 -0
  18. package/dist/agent/chat/service.js +246 -0
  19. package/dist/agent/chat/types.d.ts +48 -0
  20. package/dist/agent/chat/types.js +1 -0
  21. package/dist/agent/context/retrieve.d.ts +21 -0
  22. package/dist/agent/context/retrieve.js +117 -0
  23. package/dist/agent/context/truth.d.ts +6 -0
  24. package/dist/agent/context/truth.js +19 -0
  25. package/dist/agent/context/types.d.ts +23 -0
  26. package/dist/agent/context/types.js +4 -0
  27. package/dist/agent/index.d.ts +28 -0
  28. package/dist/agent/index.js +15 -0
  29. package/dist/agent/loop.d.ts +53 -0
  30. package/dist/agent/loop.js +253 -0
  31. package/dist/agent/modes.d.ts +18 -0
  32. package/dist/agent/modes.js +102 -0
  33. package/dist/agent/plan.d.ts +30 -0
  34. package/dist/agent/plan.js +121 -0
  35. package/dist/agent/roles.d.ts +12 -0
  36. package/dist/agent/roles.js +138 -0
  37. package/dist/agent/runtime.d.ts +83 -0
  38. package/dist/agent/runtime.js +293 -0
  39. package/dist/agent/state.d.ts +30 -0
  40. package/dist/agent/state.js +95 -0
  41. package/dist/agent/student.d.ts +56 -0
  42. package/dist/agent/student.js +230 -0
  43. package/dist/agent/tools/execute.d.ts +20 -0
  44. package/dist/agent/tools/execute.js +365 -0
  45. package/dist/agent/tools/index.d.ts +6 -0
  46. package/dist/agent/tools/index.js +5 -0
  47. package/dist/agent/tools/registry.d.ts +7 -0
  48. package/dist/agent/tools/registry.js +212 -0
  49. package/dist/agent/tools/run.d.ts +24 -0
  50. package/dist/agent/tools/run.js +44 -0
  51. package/dist/agent/tools/types.d.ts +32 -0
  52. package/dist/agent/tools/types.js +10 -0
  53. package/dist/agent/tools/write.d.ts +24 -0
  54. package/dist/agent/tools/write.js +121 -0
  55. package/dist/agent/verify.d.ts +29 -0
  56. package/dist/agent/verify.js +210 -0
  57. package/dist/ai/config.d.ts +22 -0
  58. package/dist/ai/config.js +69 -0
  59. package/dist/ai/index.d.ts +17 -0
  60. package/dist/ai/index.js +52 -0
  61. package/dist/ai/providers/mock.d.ts +20 -0
  62. package/dist/ai/providers/mock.js +84 -0
  63. package/dist/ai/providers/none.d.ts +6 -0
  64. package/dist/ai/providers/none.js +23 -0
  65. package/dist/ai/providers/openai-compatible.d.ts +21 -0
  66. package/dist/ai/providers/openai-compatible.js +151 -0
  67. package/dist/ai/redact.d.ts +9 -0
  68. package/dist/ai/redact.js +61 -0
  69. package/dist/ai/types.d.ts +71 -0
  70. package/dist/ai/types.js +6 -0
  71. package/dist/cli/commands/agent.d.ts +29 -0
  72. package/dist/cli/commands/agent.js +164 -0
  73. package/dist/cli/commands/chat.d.ts +14 -0
  74. package/dist/cli/commands/chat.js +153 -0
  75. package/dist/cli/commands/learn.d.ts +12 -0
  76. package/dist/cli/commands/learn.js +107 -0
  77. package/dist/cli/commands/product.d.ts +19 -0
  78. package/dist/cli/commands/product.js +147 -0
  79. package/dist/cli/commands/start.d.ts +15 -0
  80. package/dist/cli/commands/start.js +80 -0
  81. package/dist/cli/program.js +233 -0
  82. package/dist/constants.d.ts +1 -1
  83. package/dist/constants.js +1 -1
  84. package/dist/dashboard/server.d.ts +6 -0
  85. package/dist/dashboard/server.js +333 -43
  86. package/dist/enforcement/runner.d.ts +5 -0
  87. package/dist/enforcement/runner.js +51 -4
  88. package/dist/index.d.ts +17 -0
  89. package/dist/index.js +10 -0
  90. package/dist/intelligence/graph/build.js +36 -8
  91. package/dist/intelligence/resolve/imports.js +1 -1
  92. package/dist/languages/dart.d.ts +10 -0
  93. package/dist/languages/dart.js +99 -0
  94. package/dist/languages/go.d.ts +13 -7
  95. package/dist/languages/go.js +91 -26
  96. package/dist/languages/index.d.ts +5 -1
  97. package/dist/languages/index.js +13 -36
  98. package/dist/languages/java.d.ts +10 -0
  99. package/dist/languages/java.js +80 -0
  100. package/dist/languages/kotlin.d.ts +10 -0
  101. package/dist/languages/kotlin.js +85 -0
  102. package/dist/languages/php.d.ts +1 -0
  103. package/dist/languages/php.js +11 -2
  104. package/dist/languages/python.d.ts +6 -2
  105. package/dist/languages/python.js +39 -9
  106. package/dist/languages/rust.d.ts +10 -0
  107. package/dist/languages/rust.js +93 -0
  108. package/dist/languages/types.d.ts +4 -3
  109. package/dist/mcp/agent/registry.d.ts +13 -0
  110. package/dist/mcp/agent/registry.js +320 -0
  111. package/dist/mcp/agentdoctor/server.js +8 -1
  112. package/dist/mcp/intelligence/handlers.d.ts +3 -0
  113. package/dist/mcp/intelligence/handlers.js +28 -0
  114. package/dist/mcp/intelligence/registry.d.ts +1 -1
  115. package/dist/mcp/intelligence/registry.js +30 -1
  116. package/dist/product/api/doctor.d.ts +14 -0
  117. package/dist/product/api/doctor.js +185 -0
  118. package/dist/product/api/openapi.d.ts +7 -0
  119. package/dist/product/api/openapi.js +122 -0
  120. package/dist/product/approval/model.d.ts +30 -0
  121. package/dist/product/approval/model.js +64 -0
  122. package/dist/product/approval/session.d.ts +49 -0
  123. package/dist/product/approval/session.js +134 -0
  124. package/dist/product/database/doctor.d.ts +30 -0
  125. package/dist/product/database/doctor.js +184 -0
  126. package/dist/product/decisions/ledger.d.ts +24 -0
  127. package/dist/product/decisions/ledger.js +110 -0
  128. package/dist/product/deps/analyze.d.ts +46 -0
  129. package/dist/product/deps/analyze.js +137 -0
  130. package/dist/product/deps/lockfiles.d.ts +25 -0
  131. package/dist/product/deps/lockfiles.js +200 -0
  132. package/dist/product/discovery/roots.d.ts +42 -0
  133. package/dist/product/discovery/roots.js +215 -0
  134. package/dist/product/dna/build.d.ts +50 -0
  135. package/dist/product/dna/build.js +255 -0
  136. package/dist/product/eval/lab.d.ts +17 -0
  137. package/dist/product/eval/lab.js +218 -0
  138. package/dist/product/events/doctor.d.ts +21 -0
  139. package/dist/product/events/doctor.js +147 -0
  140. package/dist/product/evidence-scan.d.ts +12 -0
  141. package/dist/product/evidence-scan.js +46 -0
  142. package/dist/product/evolution/timeline.d.ts +29 -0
  143. package/dist/product/evolution/timeline.js +123 -0
  144. package/dist/product/features/intelligence.d.ts +23 -0
  145. package/dist/product/features/intelligence.js +158 -0
  146. package/dist/product/forensic/mode.d.ts +25 -0
  147. package/dist/product/forensic/mode.js +70 -0
  148. package/dist/product/graph/enrich-languages.d.ts +20 -0
  149. package/dist/product/graph/enrich-languages.js +193 -0
  150. package/dist/product/health/code-health.d.ts +20 -0
  151. package/dist/product/health/code-health.js +149 -0
  152. package/dist/product/index.d.ts +69 -0
  153. package/dist/product/index.js +35 -0
  154. package/dist/product/ledger/change-ledger.d.ts +23 -0
  155. package/dist/product/ledger/change-ledger.js +64 -0
  156. package/dist/product/map/software-map.d.ts +17 -0
  157. package/dist/product/map/software-map.js +95 -0
  158. package/dist/product/memory/institutional.d.ts +16 -0
  159. package/dist/product/memory/institutional.js +87 -0
  160. package/dist/product/ops/incident.d.ts +20 -0
  161. package/dist/product/ops/incident.js +61 -0
  162. package/dist/product/ops/infra.d.ts +15 -0
  163. package/dist/product/ops/infra.js +112 -0
  164. package/dist/product/org/model.d.ts +34 -0
  165. package/dist/product/org/model.js +195 -0
  166. package/dist/product/privacy/doctor.d.ts +13 -0
  167. package/dist/product/privacy/doctor.js +90 -0
  168. package/dist/product/requirements/trace.d.ts +21 -0
  169. package/dist/product/requirements/trace.js +166 -0
  170. package/dist/product/search/index.d.ts +29 -0
  171. package/dist/product/search/index.js +116 -0
  172. package/dist/product/search/software-search.d.ts +19 -0
  173. package/dist/product/search/software-search.js +100 -0
  174. package/dist/product/security/doctor.d.ts +23 -0
  175. package/dist/product/security/doctor.js +124 -0
  176. package/dist/product/self/diagnose.d.ts +15 -0
  177. package/dist/product/self/diagnose.js +82 -0
  178. package/dist/product/techdebt/roadmap.d.ts +20 -0
  179. package/dist/product/techdebt/roadmap.js +118 -0
  180. package/dist/product/testbrain/analyze.d.ts +29 -0
  181. package/dist/product/testbrain/analyze.js +129 -0
  182. package/dist/product/truth.d.ts +12 -0
  183. package/dist/product/truth.js +30 -0
  184. package/dist/product/twin/digital-twin.d.ts +23 -0
  185. package/dist/product/twin/digital-twin.js +52 -0
  186. package/dist/product/twin/store.d.ts +21 -0
  187. package/dist/product/twin/store.js +70 -0
  188. package/dist/product/whatif/engine.d.ts +28 -0
  189. package/dist/product/whatif/engine.js +70 -0
  190. package/dist/security/paths.js +43 -15
  191. package/dist/utils/fs.js +10 -2
  192. package/package.json +1 -1
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Redaction for outbound model payloads.
3
+ * Extends the local-ai redaction patterns; never send raw secrets to providers.
4
+ * Patterns avoid nested unbounded quantifiers (ReDoS-safe).
5
+ */
6
+ export declare function redactForModel(text: string): string;
7
+ export declare function redactMessages<T extends {
8
+ content: string;
9
+ }>(messages: T[]): T[];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Redaction for outbound model payloads.
3
+ * Extends the local-ai redaction patterns; never send raw secrets to providers.
4
+ * Patterns avoid nested unbounded quantifiers (ReDoS-safe).
5
+ */
6
+ const REDACTED = "[REDACTED]";
7
+ const SIMPLE_SECRET_PATTERNS = [
8
+ /\b(AKIA[0-9A-Z]{16})\b/g,
9
+ /\b(ghp_[A-Za-z0-9]{36})\b/g,
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,
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
+ }
52
+ export function redactForModel(text) {
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);
56
+ }
57
+ return out;
58
+ }
59
+ export function redactMessages(messages) {
60
+ return messages.map((m) => ({ ...m, content: redactForModel(m.content) }));
61
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Provider-agnostic model types for AgentDoctor 2.1 Project AI Agent.
3
+ * The model reasons; AgentDoctor owns context, tools, and verification.
4
+ */
5
+ export type AiProviderId = "none" | "deterministic" | "mock" | "openai-compatible" | "ollama" | "anthropic" | "gemini";
6
+ export type ChatRole = "system" | "user" | "assistant" | "tool";
7
+ export interface ChatMessage {
8
+ role: ChatRole;
9
+ content: string;
10
+ /** Tool call id when role is tool */
11
+ toolCallId?: string;
12
+ name?: string;
13
+ }
14
+ export interface ToolSpec {
15
+ name: string;
16
+ description: string;
17
+ /** JSON Schema object for arguments */
18
+ parameters: Record<string, unknown>;
19
+ }
20
+ export interface ToolCallRequest {
21
+ id: string;
22
+ name: string;
23
+ arguments: Record<string, unknown>;
24
+ }
25
+ export interface TokenUsage {
26
+ promptTokens?: number;
27
+ completionTokens?: number;
28
+ totalTokens?: number;
29
+ }
30
+ export interface ChatRequest {
31
+ messages: ChatMessage[];
32
+ tools?: ToolSpec[];
33
+ model?: string;
34
+ temperature?: number;
35
+ maxTokens?: number;
36
+ /** AbortSignal for cancellation when supported */
37
+ signal?: AbortSignal;
38
+ }
39
+ export interface ChatResponse {
40
+ provider: AiProviderId;
41
+ model: string;
42
+ message: ChatMessage;
43
+ toolCalls: ToolCallRequest[];
44
+ usage?: TokenUsage;
45
+ finishReason?: string;
46
+ /** Always true for model-generated content */
47
+ aiGenerated: true;
48
+ error?: string;
49
+ }
50
+ export interface ModelMetadata {
51
+ provider: AiProviderId;
52
+ model: string;
53
+ supportsTools: boolean;
54
+ supportsStreaming: boolean;
55
+ supportsStructuredOutput: boolean;
56
+ }
57
+ export interface ModelProvider {
58
+ readonly id: AiProviderId;
59
+ metadata(): ModelMetadata;
60
+ chat(request: ChatRequest): Promise<ChatResponse>;
61
+ /**
62
+ * Optional streaming. Providers that do not support streaming reject.
63
+ */
64
+ streamChat?(request: ChatRequest): AsyncIterable<{
65
+ delta: string;
66
+ done: boolean;
67
+ response?: ChatResponse;
68
+ }>;
69
+ }
70
+ /** User-facing copy when AI is not configured. */
71
+ export declare const AI_PROVIDER_REQUIRED_MESSAGE = "AI features require an AI provider. AgentDoctor's project analysis features continue to work without AI.";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Provider-agnostic model types for AgentDoctor 2.1 Project AI Agent.
3
+ * The model reasons; AgentDoctor owns context, tools, and verification.
4
+ */
5
+ /** User-facing copy when AI is not configured. */
6
+ export const AI_PROVIDER_REQUIRED_MESSAGE = "AI features require an AI provider. AgentDoctor's project analysis features continue to work without AI.";
@@ -0,0 +1,29 @@
1
+ import { type AgentRole } from "../../agent/roles.js";
2
+ import { type ExitCode } from "../../types/index.js";
3
+ /**
4
+ * agentdoctor plan — produce an implementation plan (no writes unless --apply elsewhere).
5
+ */
6
+ export declare function runPlanCommand(options: {
7
+ goal: string;
8
+ root?: string;
9
+ json?: boolean;
10
+ approve?: boolean;
11
+ }): Promise<ExitCode>;
12
+ /**
13
+ * agentdoctor agent — inspect, plan, and (with --approve) mutate via controlled tools.
14
+ */
15
+ export declare function runAgentCommand(options: {
16
+ root?: string;
17
+ goal?: string;
18
+ tool?: string;
19
+ toolArgs?: Record<string, unknown>;
20
+ listTools?: boolean;
21
+ json?: boolean;
22
+ approve?: boolean;
23
+ apply?: boolean;
24
+ /** JSON array of {name, arguments} for deterministic apply */
25
+ applyOpsJson?: string;
26
+ verify?: boolean;
27
+ runTests?: boolean;
28
+ role?: AgentRole;
29
+ }): Promise<ExitCode>;
@@ -0,0 +1,164 @@
1
+ import { approvePlan, buildAgentPlan, formatAgentPlan } from "../../agent/plan.js";
2
+ import { evaluateApproval, formatApprovalPrompt } from "../../agent/approvals.js";
3
+ import { runCodingLoop } from "../../agent/loop.js";
4
+ import { runRoleAgent } from "../../agent/roles.js";
5
+ import { executeAgentTool, getToolSpec, listAgentToolSpecs, newToolCall, } from "../../agent/tools/index.js";
6
+ import { EXIT_CODES } from "../../types/index.js";
7
+ import { resolveRepoRoot } from "../../utils/path.js";
8
+ import { colors } from "../../utils/colors.js";
9
+ /**
10
+ * agentdoctor plan — produce an implementation plan (no writes unless --apply elsewhere).
11
+ */
12
+ export async function runPlanCommand(options) {
13
+ const root = resolveRepoRoot(options.root ?? process.cwd());
14
+ const plan = await buildAgentPlan({ root, goal: options.goal });
15
+ if (options.approve) {
16
+ const approved = approvePlan(plan, true);
17
+ if (options.json) {
18
+ process.stdout.write(`${JSON.stringify({ ...approved, note: "Approval recorded; use agent --apply to mutate" }, null, 2)}\n`);
19
+ }
20
+ else {
21
+ process.stdout.write(formatAgentPlan(approved));
22
+ process.stdout.write(`${colors.yellow('\nApproval recorded. Use: agentdoctor agent --goal "..." --approve --apply\n')}\n`);
23
+ }
24
+ return EXIT_CODES.SUCCESS;
25
+ }
26
+ if (options.json) {
27
+ process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
28
+ }
29
+ else {
30
+ process.stdout.write(formatAgentPlan(plan));
31
+ }
32
+ return EXIT_CODES.SUCCESS;
33
+ }
34
+ /**
35
+ * agentdoctor agent — inspect, plan, and (with --approve) mutate via controlled tools.
36
+ */
37
+ export async function runAgentCommand(options) {
38
+ const root = resolveRepoRoot(options.root ?? process.cwd());
39
+ const approvedByHuman = options.approve === true;
40
+ if (options.listTools) {
41
+ const tools = listAgentToolSpecs({ includeWrite: true, includeExecute: true });
42
+ if (options.json) {
43
+ process.stdout.write(`${JSON.stringify(tools, null, 2)}\n`);
44
+ }
45
+ else {
46
+ process.stdout.write(`\n${colors.bold("AgentDoctor tools")}\n\n`);
47
+ for (const t of tools) {
48
+ process.stdout.write(` [${t.risk}] ${t.name} (${t.category}) — ${t.description}\n`);
49
+ }
50
+ process.stdout.write("\n");
51
+ }
52
+ return EXIT_CODES.SUCCESS;
53
+ }
54
+ if (options.tool) {
55
+ const name = options.tool;
56
+ const spec = getToolSpec(name);
57
+ if (!spec) {
58
+ process.stderr.write(`Unknown tool: ${name}\n`);
59
+ return EXIT_CODES.USAGE_ERROR;
60
+ }
61
+ const approval = evaluateApproval({ action: `tool:${name}`, risk: spec.risk, toolName: name }, { approvedByHuman });
62
+ if (approval.needsHumanApproval) {
63
+ process.stderr.write(formatApprovalPrompt({ action: `tool:${name}`, risk: approval.risk, toolName: name }, approval));
64
+ process.stderr.write("Re-run with --approve for write/execute tools.\n");
65
+ return EXIT_CODES.USAGE_ERROR;
66
+ }
67
+ const call = newToolCall("cli-agent", name, options.toolArgs ?? {});
68
+ const result = await executeAgentTool(root, call, {
69
+ allowWrite: approvedByHuman,
70
+ allowExecute: approvedByHuman,
71
+ approvedByHuman,
72
+ });
73
+ if (options.json) {
74
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
75
+ }
76
+ else if (result.ok) {
77
+ process.stdout.write(`${JSON.stringify(result.data, null, 2)}\n`);
78
+ }
79
+ else {
80
+ process.stderr.write(`Tool failed: ${result.error?.code} — ${result.error?.message}\n`);
81
+ return EXIT_CODES.INTERNAL_ERROR;
82
+ }
83
+ return EXIT_CODES.SUCCESS;
84
+ }
85
+ if (options.goal && options.apply) {
86
+ if (!approvedByHuman) {
87
+ process.stderr.write("Refusing --apply without --approve (human approval required).\n");
88
+ return EXIT_CODES.USAGE_ERROR;
89
+ }
90
+ let toolCalls;
91
+ if (options.applyOpsJson) {
92
+ try {
93
+ const parsed = JSON.parse(options.applyOpsJson);
94
+ if (!Array.isArray(parsed))
95
+ throw new Error("expected array");
96
+ toolCalls = parsed.map((item) => {
97
+ const row = item;
98
+ if (!row.name || typeof row.name !== "string")
99
+ throw new Error("ops need name");
100
+ return {
101
+ name: row.name,
102
+ arguments: row.arguments ?? {},
103
+ };
104
+ });
105
+ }
106
+ catch (error) {
107
+ process.stderr.write(`Invalid --apply-ops JSON: ${error instanceof Error ? error.message : String(error)}\n`);
108
+ return EXIT_CODES.USAGE_ERROR;
109
+ }
110
+ }
111
+ const result = options.role
112
+ ? await runRoleAgent({
113
+ role: options.role,
114
+ root,
115
+ goal: options.goal,
116
+ approvedByHuman: true,
117
+ ...(toolCalls ? { toolCalls } : {}),
118
+ verify: options.verify !== false,
119
+ runTests: options.runTests === true,
120
+ })
121
+ : await runCodingLoop({
122
+ root,
123
+ goal: options.goal,
124
+ approvedByHuman: true,
125
+ ...(toolCalls ? { toolCalls } : {}),
126
+ verify: options.verify !== false,
127
+ runTests: options.runTests === true,
128
+ });
129
+ if (options.json) {
130
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
131
+ }
132
+ else {
133
+ process.stdout.write(result.responseText);
134
+ }
135
+ if (result.stoppedReason === "awaiting-approval")
136
+ return EXIT_CODES.USAGE_ERROR;
137
+ if (result.stoppedReason === "failed" || result.stoppedReason === "limit") {
138
+ return EXIT_CODES.INTERNAL_ERROR;
139
+ }
140
+ return EXIT_CODES.SUCCESS;
141
+ }
142
+ if (options.goal) {
143
+ return runPlanCommand({
144
+ goal: options.goal,
145
+ root,
146
+ ...(options.json !== undefined ? { json: options.json } : {}),
147
+ ...(approvedByHuman ? { approve: true } : {}),
148
+ });
149
+ }
150
+ process.stderr.write([
151
+ "",
152
+ "AgentDoctor agent (Milestone 4)",
153
+ "",
154
+ "Usage:",
155
+ " agentdoctor agent --list-tools",
156
+ " agentdoctor agent --tool inspect_project [path]",
157
+ ' agentdoctor plan "Add registration" [path]',
158
+ " agentdoctor agent --goal \"Add registration\" --approve --apply --apply-ops '[...]' [path]",
159
+ "",
160
+ "Writes require --approve. Unrestricted shell is never exposed.",
161
+ "",
162
+ ].join("\n"));
163
+ return EXIT_CODES.USAGE_ERROR;
164
+ }
@@ -0,0 +1,14 @@
1
+ import { type ExitCode } from "../../types/index.js";
2
+ export declare function runAskCommand(options: {
3
+ question: string;
4
+ root?: string;
5
+ json?: boolean;
6
+ /** Test-only: force mock provider */
7
+ useMock?: boolean;
8
+ }): Promise<ExitCode>;
9
+ export declare function runChatCommand(options: {
10
+ root?: string;
11
+ /** Non-interactive scripted lines (tests) */
12
+ scriptedInputs?: string[];
13
+ useMock?: boolean;
14
+ }): Promise<ExitCode>;
@@ -0,0 +1,153 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { ChatService, formatChatResponseForCli } from "../../agent/chat/index.js";
4
+ import { MockModelProvider, createModelProvider, loadAiConfig } from "../../ai/index.js";
5
+ import { EXIT_CODES } from "../../types/index.js";
6
+ import { resolveRepoRoot } from "../../utils/path.js";
7
+ import { colors } from "../../utils/colors.js";
8
+ function printBanner(options) {
9
+ const lines = [
10
+ "",
11
+ colors.bold("AgentDoctor Project Chat"),
12
+ "",
13
+ `Project:`,
14
+ ` ${options.name}`,
15
+ ` ${options.root}`,
16
+ "",
17
+ `Detected:`,
18
+ ` ${(options.languages.length ? options.languages : ["(none)"]).join(", ")}`,
19
+ ` frameworks: ${(options.frameworks.length ? options.frameworks : ["(none)"]).join(", ")}`,
20
+ "",
21
+ `AI:`,
22
+ ` ${options.providerLabel}`,
23
+ "",
24
+ "Ask questions about your project.",
25
+ "",
26
+ "Commands:",
27
+ " /help Show help",
28
+ " /project Project summary",
29
+ " /context Context used for last answer",
30
+ " /clear Clear conversation memory",
31
+ " /exit Leave chat",
32
+ "",
33
+ "Milestone 2 does not edit files or run commands.",
34
+ "",
35
+ ];
36
+ process.stdout.write(`${lines.join("\n")}\n`);
37
+ }
38
+ function printHelp() {
39
+ process.stdout.write([
40
+ "",
41
+ "Ask natural-language questions about this repository.",
42
+ "Answers use project evidence (Brain/graph/context) when available.",
43
+ "",
44
+ "Truth labels: VERIFIED | INFERRED | UNKNOWN | EXTERNAL",
45
+ "",
46
+ "Examples:",
47
+ " Explain my project.",
48
+ " How does authentication work?",
49
+ " Explain this like I'm a beginner.",
50
+ "",
51
+ ].join("\n"));
52
+ }
53
+ export async function runAskCommand(options) {
54
+ const root = resolveRepoRoot(options.root ?? process.cwd());
55
+ const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
56
+ const chat = new ChatService({
57
+ root,
58
+ provider,
59
+ persistAudit: !options.useMock,
60
+ });
61
+ try {
62
+ const response = await chat.ask(options.question);
63
+ if (options.json) {
64
+ process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
65
+ }
66
+ else {
67
+ process.stdout.write(formatChatResponseForCli(response));
68
+ }
69
+ if (response.status === "provider-none")
70
+ return EXIT_CODES.USAGE_ERROR;
71
+ if (response.status === "provider-error" || response.status === "failed") {
72
+ return EXIT_CODES.INTERNAL_ERROR;
73
+ }
74
+ return EXIT_CODES.SUCCESS;
75
+ }
76
+ finally {
77
+ await chat.end();
78
+ }
79
+ }
80
+ export async function runChatCommand(options) {
81
+ const root = resolveRepoRoot(options.root ?? process.cwd());
82
+ const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
83
+ const chat = new ChatService({
84
+ root,
85
+ provider,
86
+ persistAudit: !options.useMock,
87
+ });
88
+ const started = await chat.start();
89
+ printBanner({
90
+ root: started.summary.root,
91
+ name: started.summary.name,
92
+ providerLabel: started.providerLabel,
93
+ languages: started.summary.languages,
94
+ frameworks: started.summary.frameworks,
95
+ });
96
+ if (provider.id === "none" && !options.useMock) {
97
+ process.stdout.write("Deterministic project chat (no LLM). Architecture, auth, and dependency questions use local analyzers.\n\n");
98
+ }
99
+ const scripted = options.scriptedInputs ? [...options.scriptedInputs] : undefined;
100
+ const rl = scripted === undefined
101
+ ? readline.createInterface({ input, output, terminal: Boolean(output.isTTY) })
102
+ : undefined;
103
+ const readLine = async () => {
104
+ if (scripted) {
105
+ if (scripted.length === 0)
106
+ return null;
107
+ return scripted.shift() ?? null;
108
+ }
109
+ const answer = await rl.question("You:\n> ");
110
+ return answer;
111
+ };
112
+ try {
113
+ while (true) {
114
+ const raw = await readLine();
115
+ if (raw === null)
116
+ break;
117
+ const line = raw.trim();
118
+ if (!line)
119
+ continue;
120
+ if (line === "/exit" || line === "exit" || line === "quit") {
121
+ process.stdout.write("Goodbye.\n");
122
+ break;
123
+ }
124
+ if (line === "/help" || line === "help") {
125
+ printHelp();
126
+ continue;
127
+ }
128
+ if (line === "/project") {
129
+ process.stdout.write(await chat.formatProject());
130
+ continue;
131
+ }
132
+ if (line === "/context") {
133
+ process.stdout.write(chat.formatContext());
134
+ continue;
135
+ }
136
+ if (line === "/clear") {
137
+ chat.clearMemory();
138
+ process.stdout.write("Conversation memory cleared.\n");
139
+ continue;
140
+ }
141
+ const response = await chat.ask(line);
142
+ process.stdout.write(chat.formatResponse(response));
143
+ if (response.status === "provider-error") {
144
+ // stay in loop; user may fix provider elsewhere
145
+ }
146
+ }
147
+ return EXIT_CODES.SUCCESS;
148
+ }
149
+ finally {
150
+ rl?.close();
151
+ await chat.end();
152
+ }
153
+ }
@@ -0,0 +1,12 @@
1
+ import { type ExitCode } from "../../types/index.js";
2
+ export declare function runLearnCommand(options: {
3
+ root?: string;
4
+ mode?: string;
5
+ viva?: boolean;
6
+ docs?: boolean;
7
+ build?: string;
8
+ approve?: boolean;
9
+ applyOpsJson?: string;
10
+ json?: boolean;
11
+ useMock?: boolean;
12
+ }): Promise<ExitCode>;
@@ -0,0 +1,107 @@
1
+ import { MockModelProvider, createModelProvider, loadAiConfig } from "../../ai/index.js";
2
+ import { StudentService } from "../../agent/student.js";
3
+ import { parseAgentMode, defaultStudentMode } from "../../agent/modes.js";
4
+ import { EXIT_CODES } from "../../types/index.js";
5
+ import { resolveRepoRoot } from "../../utils/path.js";
6
+ export async function runLearnCommand(options) {
7
+ const root = resolveRepoRoot(options.root ?? process.cwd());
8
+ const mode = parseAgentMode(options.mode) ?? defaultStudentMode();
9
+ const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
10
+ const resolvedMode = options.build
11
+ ? mode === "LEARN"
12
+ ? "BUILD_WITH_ME"
13
+ : mode
14
+ : options.viva || options.docs
15
+ ? "LEARN"
16
+ : mode;
17
+ const student = new StudentService({
18
+ root,
19
+ provider,
20
+ mode: resolvedMode,
21
+ });
22
+ try {
23
+ if (options.build) {
24
+ let toolCalls;
25
+ if (options.applyOpsJson) {
26
+ try {
27
+ const parsed = JSON.parse(options.applyOpsJson);
28
+ if (!Array.isArray(parsed))
29
+ throw new Error("expected array");
30
+ toolCalls = parsed.map((item) => {
31
+ const row = item;
32
+ if (!row.name || typeof row.name !== "string")
33
+ throw new Error("ops need name");
34
+ return {
35
+ name: row.name,
36
+ arguments: row.arguments ?? {},
37
+ };
38
+ });
39
+ }
40
+ catch (error) {
41
+ process.stderr.write(`Invalid --apply-ops JSON: ${error instanceof Error ? error.message : String(error)}\n`);
42
+ return EXIT_CODES.USAGE_ERROR;
43
+ }
44
+ }
45
+ const result = await student.buildFeature({
46
+ goal: options.build,
47
+ approvedByHuman: options.approve === true,
48
+ ...(toolCalls ? { toolCalls } : {}),
49
+ });
50
+ if (options.json) {
51
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
52
+ }
53
+ else {
54
+ process.stdout.write(result.explanation);
55
+ if (result.status === "awaiting-approval") {
56
+ process.stdout.write("\nRe-run with --approve to execute the coding loop.\n");
57
+ }
58
+ else if (result.coding) {
59
+ process.stdout.write(result.evidenceText);
60
+ }
61
+ else {
62
+ process.stdout.write(`${result.evidenceText}\n`);
63
+ }
64
+ }
65
+ if (result.status === "awaiting-approval" || result.status === "mode_forbidden") {
66
+ return EXIT_CODES.USAGE_ERROR;
67
+ }
68
+ if (result.status === "failed" || result.status === "limit") {
69
+ return EXIT_CODES.INTERNAL_ERROR;
70
+ }
71
+ return EXIT_CODES.SUCCESS;
72
+ }
73
+ if (options.viva) {
74
+ const questions = await student.generateVivaQuestions();
75
+ if (options.json)
76
+ process.stdout.write(`${JSON.stringify({ mode, questions }, null, 2)}\n`);
77
+ else {
78
+ process.stdout.write("\nViva questions (grounded in detected stack):\n\n");
79
+ for (const [i, q] of questions.entries())
80
+ process.stdout.write(` ${i + 1}. ${q}\n`);
81
+ process.stdout.write("\n");
82
+ }
83
+ return EXIT_CODES.SUCCESS;
84
+ }
85
+ if (options.docs) {
86
+ const sections = await student.generateProjectDocumentation();
87
+ if (options.json)
88
+ process.stdout.write(`${JSON.stringify({ mode, sections }, null, 2)}\n`);
89
+ else {
90
+ for (const s of sections) {
91
+ process.stdout.write(`\n[${s.truth}] ${s.title}\n${s.body}\n`);
92
+ }
93
+ process.stdout.write("\n");
94
+ }
95
+ return EXIT_CODES.SUCCESS;
96
+ }
97
+ const explained = await student.explainProject();
98
+ if (options.json)
99
+ process.stdout.write(`${JSON.stringify({ mode, ...explained }, null, 2)}\n`);
100
+ else
101
+ process.stdout.write(explained.text);
102
+ return EXIT_CODES.SUCCESS;
103
+ }
104
+ finally {
105
+ await student.end();
106
+ }
107
+ }
@@ -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>;