@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
@@ -0,0 +1,155 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { ChatService, CHAT_PROVIDER_NONE_MESSAGE, 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(`${CHAT_PROVIDER_NONE_MESSAGE}\n`);
98
+ await chat.end();
99
+ return EXIT_CODES.USAGE_ERROR;
100
+ }
101
+ const scripted = options.scriptedInputs ? [...options.scriptedInputs] : undefined;
102
+ const rl = scripted === undefined
103
+ ? readline.createInterface({ input, output, terminal: Boolean(output.isTTY) })
104
+ : undefined;
105
+ const readLine = async () => {
106
+ if (scripted) {
107
+ if (scripted.length === 0)
108
+ return null;
109
+ return scripted.shift() ?? null;
110
+ }
111
+ const answer = await rl.question("You:\n> ");
112
+ return answer;
113
+ };
114
+ try {
115
+ while (true) {
116
+ const raw = await readLine();
117
+ if (raw === null)
118
+ break;
119
+ const line = raw.trim();
120
+ if (!line)
121
+ continue;
122
+ if (line === "/exit" || line === "exit" || line === "quit") {
123
+ process.stdout.write("Goodbye.\n");
124
+ break;
125
+ }
126
+ if (line === "/help" || line === "help") {
127
+ printHelp();
128
+ continue;
129
+ }
130
+ if (line === "/project") {
131
+ process.stdout.write(await chat.formatProject());
132
+ continue;
133
+ }
134
+ if (line === "/context") {
135
+ process.stdout.write(chat.formatContext());
136
+ continue;
137
+ }
138
+ if (line === "/clear") {
139
+ chat.clearMemory();
140
+ process.stdout.write("Conversation memory cleared.\n");
141
+ continue;
142
+ }
143
+ const response = await chat.ask(line);
144
+ process.stdout.write(chat.formatResponse(response));
145
+ if (response.status === "provider-error") {
146
+ // stay in loop; user may fix provider elsewhere
147
+ }
148
+ }
149
+ return EXIT_CODES.SUCCESS;
150
+ }
151
+ finally {
152
+ rl?.close();
153
+ await chat.end();
154
+ }
155
+ }
@@ -183,11 +183,11 @@ export async function runV2SurfaceCommand(options) {
183
183
  return EXIT_CODES.SUCCESS;
184
184
  }
185
185
  case "impact": {
186
- const impact = await analyzeTestImpact(root);
186
+ const impact = await analyzeTestImpact({ root });
187
187
  if (options.json)
188
188
  printJson({ kind: "test-impact", contractsVersion: CONTRACTS_VERSION, impact });
189
189
  else
190
- process.stdout.write(`impact recommendedTests=${impact.recommendedTests.length}\n`);
190
+ process.stdout.write(`impact mode=${impact.mode} recommendedTests=${impact.recommendedTests.length}\n`);
191
191
  return EXIT_CODES.SUCCESS;
192
192
  }
193
193
  case "refactor-impact": {
@@ -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
+ }
@@ -12,4 +12,6 @@ export declare function runPlatformCommand(options: {
12
12
  actionType?: string;
13
13
  sessionId?: string;
14
14
  failClosed?: boolean;
15
+ coverage?: string;
16
+ since?: string;
15
17
  }): Promise<ExitCode>;
@@ -38,7 +38,11 @@ export async function runPlatformCommand(options) {
38
38
  return EXIT_CODES.SUCCESS;
39
39
  }
40
40
  case "test-impact": {
41
- const report = await analyzeTestImpact(root);
41
+ const report = await analyzeTestImpact({
42
+ root,
43
+ ...(options.coverage ? { coveragePath: options.coverage } : {}),
44
+ ...(options.since ? { since: options.since } : {}),
45
+ });
42
46
  const out = await persistTestImpactReport(root, report);
43
47
  if (options.json)
44
48
  printJson({ ...report, reportPath: out });
@@ -0,0 +1,40 @@
1
+ export declare function runGraphSurfaceCommand(options: {
2
+ action: "build" | "update" | "rebuild" | "status" | "show";
3
+ root: string;
4
+ mode?: string;
5
+ json?: boolean;
6
+ }): Promise<number>;
7
+ export declare function runPolicyCheckCommand(options: {
8
+ root: string;
9
+ command: string;
10
+ json?: boolean;
11
+ failClosed?: boolean;
12
+ }): Promise<number>;
13
+ export declare function runPolicyExplainCommand(options: {
14
+ root: string;
15
+ command: string;
16
+ json?: boolean;
17
+ failClosed?: boolean;
18
+ }): Promise<number>;
19
+ export declare function runPolicyEnforceCommand(options: {
20
+ root: string;
21
+ command: string;
22
+ execute?: boolean;
23
+ json?: boolean;
24
+ failClosed?: boolean;
25
+ }): Promise<number>;
26
+ export declare function runRunExplainCommand(options: {
27
+ root: string;
28
+ command: string;
29
+ json?: boolean;
30
+ failClosed?: boolean;
31
+ shell?: boolean;
32
+ }): Promise<number>;
33
+ export declare function runRunCommand(options: {
34
+ root: string;
35
+ argv: string[];
36
+ shell?: boolean;
37
+ timeoutMs?: number;
38
+ json?: boolean;
39
+ failClosed?: boolean;
40
+ }): Promise<number>;
@@ -0,0 +1,200 @@
1
+ import { evaluateAgentAction, EVALUATE_ONLY } from "../../platform/firewall/evaluate.js";
2
+ import { runControlledCommand } from "../../enforcement/runner.js";
3
+ import { graphBuild, graphRebuild, graphStatus, graphUpdate, } from "../../intelligence/graph/incremental.js";
4
+ import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
5
+ import { EXIT_CODES } from "../../types/index.js";
6
+ function emit(json, value, human) {
7
+ if (json) {
8
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
9
+ }
10
+ else {
11
+ process.stdout.write(`${human}\n`);
12
+ }
13
+ }
14
+ export async function runGraphSurfaceCommand(options) {
15
+ try {
16
+ const mode = options.mode ?? "auto";
17
+ if (options.action === "status") {
18
+ const status = await graphStatus({ root: options.root });
19
+ emit(Boolean(options.json), status, [
20
+ `Graph index ${status.present ? "present" : "missing"}`,
21
+ ` path: ${status.path}`,
22
+ ` nodes=${status.nodeCount} edges=${status.edgeCount} files=${status.fileCount}`,
23
+ ` builder=${status.builder ?? "n/a"}`,
24
+ ` stale=${status.staleFiles.length} missing=${status.missingFiles.length}`,
25
+ status.corrupt ? ` corrupt: ${status.error}` : "",
26
+ ]
27
+ .filter(Boolean)
28
+ .join("\n"));
29
+ return EXIT_CODES.SUCCESS;
30
+ }
31
+ if (options.action === "update") {
32
+ const result = await graphUpdate({ root: options.root, mode });
33
+ emit(Boolean(options.json), result, `graph update rebuilt=${result.rebuilt} reason=${result.reason} changed=${result.changedFiles.length} nodes=${result.snapshot.nodes.length}`);
34
+ return EXIT_CODES.SUCCESS;
35
+ }
36
+ if (options.action === "rebuild") {
37
+ const result = await graphRebuild({ root: options.root, mode });
38
+ emit(Boolean(options.json), result, `graph rebuild nodes=${result.snapshot.nodes.length} edges=${result.snapshot.edges.length} builder=${result.snapshot.builder}`);
39
+ return EXIT_CODES.SUCCESS;
40
+ }
41
+ // build / show (backward-compat one-shot)
42
+ if (options.action === "show") {
43
+ const graph = await buildIntelligenceGraph({ root: options.root, mode });
44
+ emit(Boolean(options.json), graph, `graph builder=${graph.builder} nodes=${graph.nodes.length} edges=${graph.edges.length} astFiles=${graph.astFilesParsed}`);
45
+ return EXIT_CODES.SUCCESS;
46
+ }
47
+ const result = await graphBuild({ root: options.root, mode });
48
+ emit(Boolean(options.json), result, `graph build nodes=${result.snapshot.nodes.length} edges=${result.snapshot.edges.length} builder=${result.snapshot.builder} path=${result.path}`);
49
+ return EXIT_CODES.SUCCESS;
50
+ }
51
+ catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ process.stderr.write(`Error: ${message}\n`);
54
+ return EXIT_CODES.INTERNAL_ERROR;
55
+ }
56
+ }
57
+ export async function runPolicyCheckCommand(options) {
58
+ try {
59
+ const verdict = await evaluateAgentAction(options.root, {
60
+ actionId: `policy-check-${Date.now()}`,
61
+ agentId: "cli",
62
+ timestamp: new Date().toISOString(),
63
+ type: "shell",
64
+ params: { command: options.command },
65
+ repositoryRoot: options.root,
66
+ }, { failClosed: options.failClosed === true });
67
+ emit(Boolean(options.json), { ...verdict, notice: EVALUATE_ONLY }, `policy check decision=${verdict.decision} risk=${verdict.riskLevel}\n ${verdict.reason}\n ${EVALUATE_ONLY}`);
68
+ return EXIT_CODES.SUCCESS;
69
+ }
70
+ catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ process.stderr.write(`Error: ${message}\n`);
73
+ return EXIT_CODES.INTERNAL_ERROR;
74
+ }
75
+ }
76
+ export async function runPolicyExplainCommand(options) {
77
+ try {
78
+ const verdict = await evaluateAgentAction(options.root, {
79
+ actionId: `policy-explain-${Date.now()}`,
80
+ agentId: "cli",
81
+ timestamp: new Date().toISOString(),
82
+ type: "shell",
83
+ params: { command: options.command },
84
+ repositoryRoot: options.root,
85
+ }, { failClosed: options.failClosed === true });
86
+ const explanation = {
87
+ command: options.command,
88
+ decision: verdict.decision,
89
+ reason: verdict.reason,
90
+ policyId: verdict.policyId ?? null,
91
+ riskLevel: verdict.riskLevel,
92
+ approvalStatus: verdict.approvalStatus,
93
+ executionResult: verdict.executionResult,
94
+ notice: EVALUATE_ONLY,
95
+ meaning: verdict.decision === "allow"
96
+ ? "Allowlisted or matched an allow rule — still evaluate-only unless agentdoctor run / enforce --execute is used."
97
+ : verdict.decision === "block" || verdict.decision === "deny-network"
98
+ ? "Would be blocked by AgentDoctor-controlled runner if executed through it."
99
+ : "Requires human approval or further policy before controlled execution.",
100
+ };
101
+ emit(Boolean(options.json), explanation, [
102
+ `policy explain: ${options.command}`,
103
+ ` decision: ${verdict.decision}`,
104
+ ` policyId: ${verdict.policyId ?? "n/a"}`,
105
+ ` risk: ${verdict.riskLevel}`,
106
+ ` reason: ${verdict.reason}`,
107
+ ` ${explanation.meaning}`,
108
+ ].join("\n"));
109
+ return EXIT_CODES.SUCCESS;
110
+ }
111
+ catch (error) {
112
+ const message = error instanceof Error ? error.message : String(error);
113
+ process.stderr.write(`Error: ${message}\n`);
114
+ return EXIT_CODES.INTERNAL_ERROR;
115
+ }
116
+ }
117
+ export async function runPolicyEnforceCommand(options) {
118
+ try {
119
+ const result = await runControlledCommand({
120
+ root: options.root,
121
+ command: options.command,
122
+ executeIfAllowed: options.execute === true,
123
+ failClosed: options.failClosed === true,
124
+ });
125
+ emit(Boolean(options.json), result, [
126
+ `policy enforce decision=${result.decision.decision} status=${result.decision.executionStatus}`,
127
+ ` enforced=${result.enforced}`,
128
+ ` ${result.notice}`,
129
+ ].join("\n"));
130
+ return EXIT_CODES.SUCCESS;
131
+ }
132
+ catch (error) {
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ process.stderr.write(`Error: ${message}\n`);
135
+ return EXIT_CODES.INTERNAL_ERROR;
136
+ }
137
+ }
138
+ export async function runRunExplainCommand(options) {
139
+ try {
140
+ const { explainControlledRun } = await import("../../enforcement/runner.js");
141
+ const explanation = await explainControlledRun({
142
+ root: options.root,
143
+ command: options.command,
144
+ failClosed: options.failClosed === true,
145
+ useShell: options.shell === true,
146
+ });
147
+ emit(Boolean(options.json), explanation, [
148
+ `run explain: ${explanation.command}`,
149
+ ` decision: ${explanation.decision}`,
150
+ ` policyId: ${explanation.policyId ?? "n/a"}`,
151
+ ` risk: ${explanation.riskLevel}`,
152
+ ` wouldExecute: ${explanation.wouldExecute}`,
153
+ ` ${explanation.meaning}`,
154
+ ` ${explanation.notice}`,
155
+ ].join("\n"));
156
+ return EXIT_CODES.SUCCESS;
157
+ }
158
+ catch (error) {
159
+ const message = error instanceof Error ? error.message : String(error);
160
+ process.stderr.write(`Error: ${message}\n`);
161
+ return EXIT_CODES.INTERNAL_ERROR;
162
+ }
163
+ }
164
+ export async function runRunCommand(options) {
165
+ try {
166
+ if (!options.argv.length) {
167
+ process.stderr.write("Error: provide a command after -- (e.g. agentdoctor run -- npm --version)\n");
168
+ return EXIT_CODES.USAGE_ERROR;
169
+ }
170
+ const result = await runControlledCommand({
171
+ root: options.root,
172
+ argv: options.argv,
173
+ executeIfAllowed: true,
174
+ useShell: options.shell === true,
175
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
176
+ failClosed: options.failClosed === true,
177
+ });
178
+ emit(Boolean(options.json), result, [
179
+ `run decision=${result.decision.decision} status=${result.decision.executionStatus}`,
180
+ ` exitCode=${result.exitCode ?? "n/a"} durationMs=${result.durationMs ?? "n/a"}`,
181
+ ` ${result.notice}`,
182
+ result.stdout ? `--- stdout ---\n${result.stdout}` : "",
183
+ result.stderr ? `--- stderr ---\n${result.stderr}` : "",
184
+ ]
185
+ .filter(Boolean)
186
+ .join("\n"));
187
+ if (result.decision.executionStatus === "blocked-by-enforcement") {
188
+ return EXIT_CODES.ISSUES_OR_THRESHOLD;
189
+ }
190
+ if (result.decision.executionStatus === "execution-failed") {
191
+ return EXIT_CODES.INTERNAL_ERROR;
192
+ }
193
+ return EXIT_CODES.SUCCESS;
194
+ }
195
+ catch (error) {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ process.stderr.write(`Error: ${message}\n`);
198
+ return EXIT_CODES.INTERNAL_ERROR;
199
+ }
200
+ }
@@ -0,0 +1,9 @@
1
+ export declare function runWorkspaceCommand(options: {
2
+ action: "init" | "add" | "list" | "status" | "remove";
3
+ root: string;
4
+ name?: string;
5
+ id?: string;
6
+ repositoryRoot?: string;
7
+ allowCrossRead?: boolean;
8
+ json?: boolean;
9
+ }): Promise<number>;
@@ -0,0 +1,82 @@
1
+ import { EXIT_CODES } from "../../types/index.js";
2
+ import { addRepositoryToWorkspace, initWorkspace, listWorkspaces, removeWorkspace, workspaceStatus, } from "../../workspace/index.js";
3
+ function emit(json, value, human) {
4
+ if (json) {
5
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
6
+ }
7
+ else {
8
+ process.stdout.write(`${human}\n`);
9
+ }
10
+ }
11
+ export async function runWorkspaceCommand(options) {
12
+ try {
13
+ const controlRoot = options.root;
14
+ switch (options.action) {
15
+ case "init": {
16
+ if (!options.name?.trim()) {
17
+ process.stderr.write("Error: workspace init requires --name\n");
18
+ return EXIT_CODES.USAGE_ERROR;
19
+ }
20
+ const ws = await initWorkspace({
21
+ controlRoot,
22
+ name: options.name,
23
+ ...(options.id ? { id: options.id } : {}),
24
+ ...(options.repositoryRoot ? { repositoryRoot: options.repositoryRoot } : {}),
25
+ allowCrossRead: options.allowCrossRead === true,
26
+ });
27
+ emit(Boolean(options.json), ws, `workspace init id=${ws.id} name=${ws.name} roots=${ws.repositoryRoots.length} allowCrossRead=${ws.allowCrossRead}`);
28
+ return EXIT_CODES.SUCCESS;
29
+ }
30
+ case "add": {
31
+ if (!options.id?.trim() || !options.repositoryRoot?.trim()) {
32
+ process.stderr.write("Error: workspace add requires --id and --repo\n");
33
+ return EXIT_CODES.USAGE_ERROR;
34
+ }
35
+ const ws = await addRepositoryToWorkspace({
36
+ controlRoot,
37
+ workspaceId: options.id,
38
+ repositoryRoot: options.repositoryRoot,
39
+ });
40
+ emit(Boolean(options.json), ws, `workspace add id=${ws.id} roots=${ws.repositoryRoots.join(", ")}`);
41
+ return EXIT_CODES.SUCCESS;
42
+ }
43
+ case "list": {
44
+ const list = await listWorkspaces(controlRoot);
45
+ emit(Boolean(options.json), { workspaces: list }, list.length === 0
46
+ ? "No workspaces under .agentdoctor/workspaces/"
47
+ : list
48
+ .map((w) => `${w.id}\t${w.name}\troots=${w.repositoryRoots.length}\tcrossRead=${w.allowCrossRead}`)
49
+ .join("\n"));
50
+ return EXIT_CODES.SUCCESS;
51
+ }
52
+ case "status": {
53
+ if (!options.id?.trim()) {
54
+ process.stderr.write("Error: workspace status requires --id\n");
55
+ return EXIT_CODES.USAGE_ERROR;
56
+ }
57
+ const status = await workspaceStatus(controlRoot, options.id);
58
+ emit(Boolean(options.json), status, status.ok
59
+ ? `workspace ${options.id} ok roots=${status.workspace?.repositoryRoots.length ?? 0}`
60
+ : `workspace ${options.id} ${status.error ?? "degraded"} missing=${status.missingRoots.join(", ") || "none"}`);
61
+ return status.ok ? EXIT_CODES.SUCCESS : EXIT_CODES.ISSUES_OR_THRESHOLD;
62
+ }
63
+ case "remove": {
64
+ if (!options.id?.trim()) {
65
+ process.stderr.write("Error: workspace remove requires --id\n");
66
+ return EXIT_CODES.USAGE_ERROR;
67
+ }
68
+ const removed = await removeWorkspace(controlRoot, options.id);
69
+ emit(Boolean(options.json), { removed, id: options.id }, removed ? `workspace removed ${options.id}` : `workspace not found ${options.id}`);
70
+ return removed ? EXIT_CODES.SUCCESS : EXIT_CODES.ISSUES_OR_THRESHOLD;
71
+ }
72
+ default:
73
+ process.stderr.write("Error: unknown workspace action\n");
74
+ return EXIT_CODES.USAGE_ERROR;
75
+ }
76
+ }
77
+ catch (error) {
78
+ const message = error instanceof Error ? error.message : String(error);
79
+ process.stderr.write(`Error: ${message}\n`);
80
+ return EXIT_CODES.INTERNAL_ERROR;
81
+ }
82
+ }