@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
@@ -1,46 +1,441 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { createHmac } from "node:crypto";
3
+ import { execFile, spawn } from "node:child_process";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { existsSync, realpathSync } from "node:fs";
3
8
  import { evaluateAgentAction, EVALUATE_ONLY } from "../platform/firewall/evaluate.js";
9
+ import { resolveRepoRoot } from "../utils/path.js";
10
+ import { PathEscapeError, assertInsideRepo } from "../security/paths.js";
11
+ const execFileAsync = promisify(execFile);
12
+ /**
13
+ * Resolve npm/npx to `node <cli.js>` on Windows so execFile(shell:false) does not
14
+ * hit ENOENT on the `npm` name or fail to spawn `.cmd` shims without a shell.
15
+ */
16
+ function resolveNpmCliJs(cliFile) {
17
+ const candidates = [
18
+ path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", cliFile),
19
+ path.join(path.dirname(process.execPath), "..", "lib", "node_modules", "npm", "bin", cliFile),
20
+ ];
21
+ for (const candidate of candidates) {
22
+ if (existsSync(candidate))
23
+ return candidate;
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Map argv to a portable executable form for shell:false execution.
29
+ * Policy evaluation still uses the original human-facing argv/command string.
30
+ */
31
+ export function resolveExecutableArgv(argv) {
32
+ if (argv.length === 0)
33
+ return argv;
34
+ const bin = argv[0];
35
+ if (process.platform === "win32" && (bin === "npm" || bin === "npx")) {
36
+ const cli = resolveNpmCliJs(bin === "npx" ? "npx-cli.js" : "npm-cli.js");
37
+ if (cli) {
38
+ return [process.execPath, cli, ...argv.slice(1)];
39
+ }
40
+ // Last resort: .cmd name (may still require shell on some hosts).
41
+ return [`${bin}.cmd`, ...argv.slice(1)];
42
+ }
43
+ return argv;
44
+ }
45
+ const DEFAULT_TIMEOUT_MS = 30_000;
46
+ const DEFAULT_MAX_BUFFER = 1024 * 1024;
47
+ const OUTPUT_TRUNCATE = 32_768;
48
+ const ENV_ALLOWLIST = new Set([
49
+ "PATH",
50
+ "Path",
51
+ "NODE_ENV",
52
+ "HOME",
53
+ "USERPROFILE",
54
+ "LANG",
55
+ "SystemRoot",
56
+ "SYSTEMROOT",
57
+ "ComSpec",
58
+ "PATHEXT",
59
+ ]);
60
+ /**
61
+ * Parse a command string into argv without invoking a shell.
62
+ * Supports simple double/single quotes; rejects unclosed quotes.
63
+ */
64
+ export function parseArgv(command) {
65
+ const trimmed = command.trim();
66
+ if (!trimmed)
67
+ return [];
68
+ const args = [];
69
+ let current = "";
70
+ let quote = null;
71
+ for (let i = 0; i < trimmed.length; i += 1) {
72
+ const ch = trimmed[i];
73
+ if (quote) {
74
+ if (ch === quote) {
75
+ quote = null;
76
+ }
77
+ else {
78
+ current += ch;
79
+ }
80
+ continue;
81
+ }
82
+ if (ch === '"' || ch === "'") {
83
+ quote = ch;
84
+ continue;
85
+ }
86
+ if (/\s/.test(ch)) {
87
+ if (current.length > 0) {
88
+ args.push(current);
89
+ current = "";
90
+ }
91
+ continue;
92
+ }
93
+ current += ch;
94
+ }
95
+ if (quote) {
96
+ throw new Error("unclosed quote in command");
97
+ }
98
+ if (current.length > 0)
99
+ args.push(current);
100
+ return args;
101
+ }
102
+ export function buildAllowlistedEnv(source = process.env) {
103
+ const env = {};
104
+ for (const key of ENV_ALLOWLIST) {
105
+ const value = source[key];
106
+ if (typeof value === "string")
107
+ env[key] = value;
108
+ }
109
+ for (const [key, value] of Object.entries(source)) {
110
+ if (key.startsWith("AGENTDOCTOR_") && typeof value === "string") {
111
+ env[key] = value;
112
+ }
113
+ }
114
+ return env;
115
+ }
116
+ /**
117
+ * Reject path-like args that resolve outside the repository root.
118
+ */
119
+ export function assertArgsInsideRoot(args, root) {
120
+ for (const arg of args) {
121
+ if (arg.startsWith("-"))
122
+ continue;
123
+ const looksLikePath = arg.includes("..") || arg.includes("/") || arg.includes("\\") || path.isAbsolute(arg);
124
+ if (!looksLikePath)
125
+ continue;
126
+ try {
127
+ assertInsideRepo(root, arg);
128
+ }
129
+ catch (error) {
130
+ if (error instanceof PathEscapeError) {
131
+ throw new Error("path escape rejected");
132
+ }
133
+ throw new Error("path escape rejected");
134
+ }
135
+ }
136
+ }
137
+ function truncate(text, max = OUTPUT_TRUNCATE) {
138
+ if (text.length <= max)
139
+ return text;
140
+ return `${text.slice(0, max)}\n…[truncated ${text.length - max} chars]`;
141
+ }
142
+ function resolveRealRoot(rootInput) {
143
+ const resolved = resolveRepoRoot(rootInput);
144
+ try {
145
+ return realpathSync(resolved);
146
+ }
147
+ catch {
148
+ return resolved;
149
+ }
150
+ }
151
+ async function appendExecutionAudit(root, record) {
152
+ const auditPath = path.join(root, ".agentdoctor", "audit", "execution.jsonl");
153
+ await fs.mkdir(path.dirname(auditPath), { recursive: true });
154
+ let previousHash = null;
155
+ try {
156
+ const existing = await fs.readFile(auditPath, "utf8");
157
+ const lines = existing.trim().split("\n").filter(Boolean);
158
+ const last = lines[lines.length - 1];
159
+ if (last) {
160
+ const parsed = JSON.parse(last);
161
+ previousHash = typeof parsed.hash === "string" ? parsed.hash : null;
162
+ }
163
+ }
164
+ catch {
165
+ previousHash = null;
166
+ }
167
+ const { hash, payload } = appendAuditChain(previousHash, record);
168
+ const line = `${JSON.stringify({ hash, previousHash, record: JSON.parse(payload) })}\n`;
169
+ await fs.appendFile(auditPath, line, "utf8");
170
+ }
171
+ async function executeArgv(options) {
172
+ const start = Date.now();
173
+ const env = buildAllowlistedEnv();
174
+ const cwd = options.root;
175
+ if (options.useShell) {
176
+ const command = options.argv.join(" ");
177
+ return await new Promise((resolve) => {
178
+ const child = spawn(command, {
179
+ cwd,
180
+ env,
181
+ shell: true,
182
+ windowsHide: true,
183
+ });
184
+ let stdout = "";
185
+ let stderr = "";
186
+ let settled = false;
187
+ const timer = setTimeout(() => {
188
+ child.kill("SIGKILL");
189
+ if (!settled) {
190
+ settled = true;
191
+ resolve({
192
+ stdout: truncate(stdout),
193
+ stderr: truncate(stderr || "timeout"),
194
+ exitCode: null,
195
+ durationMs: Date.now() - start,
196
+ failed: true,
197
+ error: `timeout after ${options.timeoutMs}ms`,
198
+ });
199
+ }
200
+ }, options.timeoutMs);
201
+ child.stdout?.on("data", (chunk) => {
202
+ stdout += chunk.toString("utf8");
203
+ if (stdout.length > options.maxBuffer) {
204
+ child.kill("SIGKILL");
205
+ }
206
+ });
207
+ child.stderr?.on("data", (chunk) => {
208
+ stderr += chunk.toString("utf8");
209
+ if (stderr.length > options.maxBuffer) {
210
+ child.kill("SIGKILL");
211
+ }
212
+ });
213
+ child.on("error", (err) => {
214
+ if (settled)
215
+ return;
216
+ settled = true;
217
+ clearTimeout(timer);
218
+ resolve({
219
+ stdout: truncate(stdout),
220
+ stderr: truncate(stderr),
221
+ exitCode: null,
222
+ durationMs: Date.now() - start,
223
+ failed: true,
224
+ error: err.message,
225
+ });
226
+ });
227
+ child.on("close", (code) => {
228
+ if (settled)
229
+ return;
230
+ settled = true;
231
+ clearTimeout(timer);
232
+ resolve({
233
+ stdout: truncate(stdout),
234
+ stderr: truncate(stderr),
235
+ exitCode: code,
236
+ durationMs: Date.now() - start,
237
+ failed: code !== 0,
238
+ });
239
+ });
240
+ });
241
+ }
242
+ const resolved = resolveExecutableArgv(options.argv);
243
+ const [file, ...args] = resolved;
244
+ if (!file) {
245
+ return {
246
+ stdout: "",
247
+ stderr: "empty command",
248
+ exitCode: null,
249
+ durationMs: Date.now() - start,
250
+ failed: true,
251
+ error: "empty command",
252
+ };
253
+ }
254
+ // Windows .cmd shims cannot be execFile'd with shell:false; use shell only then.
255
+ const needsWindowsCmdShell = process.platform === "win32" && /\.(cmd|bat)$/i.test(file);
256
+ try {
257
+ const result = await execFileAsync(file, args, {
258
+ cwd,
259
+ env,
260
+ timeout: options.timeoutMs,
261
+ maxBuffer: options.maxBuffer,
262
+ windowsHide: true,
263
+ shell: needsWindowsCmdShell,
264
+ });
265
+ return {
266
+ stdout: truncate(typeof result.stdout === "string" ? result.stdout : String(result.stdout)),
267
+ stderr: truncate(typeof result.stderr === "string" ? result.stderr : String(result.stderr)),
268
+ exitCode: 0,
269
+ durationMs: Date.now() - start,
270
+ failed: false,
271
+ };
272
+ }
273
+ catch (error) {
274
+ const err = error;
275
+ const exitCode = typeof err.code === "number" ? err.code : null;
276
+ return {
277
+ stdout: truncate(typeof err.stdout === "string" ? err.stdout : err.stdout ? String(err.stdout) : ""),
278
+ stderr: truncate(typeof err.stderr === "string" ? err.stderr : err.stderr ? String(err.stderr) : ""),
279
+ exitCode,
280
+ durationMs: Date.now() - start,
281
+ failed: true,
282
+ error: err.killed
283
+ ? `timeout after ${options.timeoutMs}ms`
284
+ : (err.message ?? "execution failed"),
285
+ };
286
+ }
287
+ }
4
288
  /**
5
289
  * AgentDoctor-controlled command runner.
6
- * Only this boundary can mark execution as blocked-by-enforcement when it refuses to run.
290
+ * Executes only when executeIfAllowed===true AND policy decision===allow.
291
+ * Default path uses shell:false with argv parsing (no shell metacharacters).
7
292
  */
8
293
  export async function runControlledCommand(options) {
294
+ const root = resolveRealRoot(options.root);
295
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
296
+ const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
297
+ const useShell = options.useShell === true;
298
+ let argv;
299
+ try {
300
+ argv =
301
+ options.argv && options.argv.length > 0 ? options.argv : parseArgv(options.command ?? "");
302
+ }
303
+ catch (error) {
304
+ const message = error instanceof Error ? error.message : String(error);
305
+ return buildFailedResult(root, message, "not-executed");
306
+ }
307
+ const commandStr = argv.join(" ");
308
+ if (!commandStr) {
309
+ return buildFailedResult(root, "empty command", "not-executed");
310
+ }
9
311
  const action = {
10
312
  actionId: randomUUID(),
11
313
  agentId: "enforcement-runner",
12
314
  timestamp: new Date().toISOString(),
13
315
  type: "shell",
14
- params: { command: options.command },
15
- repositoryRoot: options.root,
316
+ params: { command: commandStr },
317
+ repositoryRoot: root,
16
318
  };
17
- const verdict = await evaluateAgentAction(options.root, action, {
319
+ const verdict = await evaluateAgentAction(root, action, {
18
320
  failClosed: options.failClosed === true,
19
321
  });
20
322
  const blocked = verdict.decision === "block" || verdict.decision === "deny-network";
21
- const decision = {
323
+ if (blocked) {
324
+ const decision = toDecision(verdict, "blocked-by-enforcement");
325
+ await appendExecutionAudit(root, {
326
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
327
+ command: commandStr,
328
+ argv,
329
+ executionStatus: "blocked-by-enforcement",
330
+ decision: verdict.decision,
331
+ reason: verdict.reason,
332
+ at: new Date().toISOString(),
333
+ });
334
+ return {
335
+ decision,
336
+ enforced: true,
337
+ notice: `Blocked by AgentDoctor-controlled runner. ${verdict.reason}`,
338
+ };
339
+ }
340
+ if (!options.executeIfAllowed || verdict.decision !== "allow") {
341
+ const decision = toDecision(verdict, "not-executed");
342
+ return {
343
+ decision,
344
+ enforced: false,
345
+ notice: verdict.decision === "allow"
346
+ ? `Policy allow; runner did not execute (executeIfAllowed=false). ${EVALUATE_ONLY}`
347
+ : `Policy ${verdict.decision}; runner did not execute. ${EVALUATE_ONLY}`,
348
+ };
349
+ }
350
+ if (!useShell) {
351
+ try {
352
+ assertArgsInsideRoot(argv, root);
353
+ }
354
+ catch (error) {
355
+ const message = error instanceof Error ? error.message : String(error);
356
+ const decision = toDecision(verdict, "execution-failed", message);
357
+ await appendExecutionAudit(root, {
358
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
359
+ command: commandStr,
360
+ argv,
361
+ executionStatus: "execution-failed",
362
+ reason: message,
363
+ at: new Date().toISOString(),
364
+ });
365
+ return {
366
+ decision,
367
+ enforced: false,
368
+ notice: `Execution failed: ${message}`,
369
+ };
370
+ }
371
+ }
372
+ const execResult = await executeArgv({
373
+ root,
374
+ argv,
375
+ timeoutMs,
376
+ maxBuffer,
377
+ useShell,
378
+ });
379
+ const executionStatus = execResult.failed ? "execution-failed" : "executed";
380
+ const decision = toDecision(verdict, executionStatus, execResult.error, useShell ? "HIGH RISK: executed via shell=true (explicit --shell flag)" : undefined);
381
+ await appendExecutionAudit(root, {
382
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
383
+ command: commandStr,
384
+ argv,
385
+ useShell,
386
+ executionStatus,
387
+ exitCode: execResult.exitCode,
388
+ durationMs: execResult.durationMs,
389
+ stdout: execResult.stdout,
390
+ stderr: execResult.stderr,
391
+ ...(execResult.error ? { error: execResult.error } : {}),
392
+ at: new Date().toISOString(),
393
+ });
394
+ return {
395
+ decision,
396
+ enforced: false,
397
+ notice: execResult.failed
398
+ ? `Execution failed${execResult.error ? `: ${execResult.error}` : ""}`
399
+ : useShell
400
+ ? "Executed under AgentDoctor control with shell=true (HIGH RISK)."
401
+ : "Executed under AgentDoctor control (shell=false).",
402
+ stdout: execResult.stdout,
403
+ stderr: execResult.stderr,
404
+ exitCode: execResult.exitCode,
405
+ durationMs: execResult.durationMs,
406
+ };
407
+ }
408
+ function toDecision(verdict, executionStatus, extraReason, riskNote) {
409
+ return {
22
410
  actionId: verdict.actionId,
23
411
  decision: verdict.decision,
24
- reason: verdict.reason,
412
+ reason: [verdict.reason, extraReason, riskNote].filter(Boolean).join(" | "),
25
413
  ...(verdict.policyId ? { policyId: verdict.policyId } : {}),
26
414
  policyVersion: "2.0",
27
415
  inputClassification: "shell",
28
416
  evidence: [{ id: "ev_policy", kind: "observed", detail: verdict.reason }],
29
417
  riskLevel: verdict.riskLevel,
30
- executionStatus: blocked
31
- ? "blocked-by-enforcement"
32
- : options.executeIfAllowed && verdict.decision === "allow"
33
- ? "not-executed"
34
- : "not-executed",
418
+ executionStatus,
35
419
  approvalStatus: verdict.approvalStatus,
36
420
  timestamp: new Date().toISOString(),
37
421
  };
422
+ }
423
+ function buildFailedResult(_root, message, executionStatus) {
38
424
  return {
39
- decision,
40
- enforced: blocked,
41
- notice: blocked
42
- ? `Blocked by AgentDoctor-controlled runner. ${EVALUATE_ONLY}`
43
- : `Policy ${verdict.decision}; runner did not execute. ${EVALUATE_ONLY}`,
425
+ decision: {
426
+ actionId: randomUUID(),
427
+ decision: "block",
428
+ reason: message,
429
+ policyVersion: "2.0",
430
+ inputClassification: "shell",
431
+ evidence: [{ id: "ev_parse", kind: "observed", detail: message }],
432
+ riskLevel: "high",
433
+ executionStatus,
434
+ approvalStatus: "not-required",
435
+ timestamp: new Date().toISOString(),
436
+ },
437
+ enforced: executionStatus === "blocked-by-enforcement",
438
+ notice: message,
44
439
  };
45
440
  }
46
441
  /** Append-only tamper-evident audit chain (HMAC over previous hash). */
@@ -54,3 +449,59 @@ export function appendAuditChain(previousHash, record, secret = "local-dev-audit
54
449
  export function auditRecordId(parts) {
55
450
  return `audit_${createHash("sha1").update(parts).digest("hex").slice(0, 12)}`;
56
451
  }
452
+ /**
453
+ * Explain what the controlled runner would do for a command (evaluate-only).
454
+ * Does not execute.
455
+ */
456
+ export async function explainControlledRun(options) {
457
+ const root = resolveRealRoot(options.root);
458
+ let argv;
459
+ try {
460
+ argv =
461
+ options.argv && options.argv.length > 0 ? options.argv : parseArgv(options.command ?? "");
462
+ }
463
+ catch (error) {
464
+ const message = error instanceof Error ? error.message : String(error);
465
+ return {
466
+ command: options.command ?? "",
467
+ argv: [],
468
+ useShell: options.useShell === true,
469
+ decision: "block",
470
+ reason: message,
471
+ policyId: null,
472
+ riskLevel: "high",
473
+ wouldExecute: false,
474
+ executionResult: "not-executed",
475
+ notice: EVALUATE_ONLY,
476
+ meaning: "Command could not be parsed for controlled execution.",
477
+ };
478
+ }
479
+ const commandStr = argv.join(" ");
480
+ const action = {
481
+ actionId: randomUUID(),
482
+ agentId: "enforcement-explain",
483
+ timestamp: new Date().toISOString(),
484
+ type: "shell",
485
+ params: { command: commandStr },
486
+ repositoryRoot: root,
487
+ };
488
+ const verdict = await evaluateAgentAction(root, action, {
489
+ failClosed: options.failClosed === true,
490
+ });
491
+ const wouldExecute = verdict.decision === "allow";
492
+ return {
493
+ command: commandStr,
494
+ argv,
495
+ useShell: options.useShell === true,
496
+ decision: verdict.decision,
497
+ reason: verdict.reason,
498
+ policyId: verdict.policyId ?? null,
499
+ riskLevel: verdict.riskLevel,
500
+ wouldExecute,
501
+ executionResult: "not-executed",
502
+ notice: EVALUATE_ONLY,
503
+ meaning: wouldExecute
504
+ ? "Policy allow — `agentdoctor run` would execute under AgentDoctor control (still not claimed as enterprise interception)."
505
+ : "Policy would block or require approval — controlled runner will not execute.",
506
+ };
507
+ }
package/dist/index.d.ts CHANGED
@@ -2,9 +2,22 @@ export { CONTRACTS_VERSION, DEFAULT_FEATURE_FLAGS } from "./contracts/index.js";
2
2
  export { buildIntelligenceGraph } from "./intelligence/graph/build.js";
3
3
  export { analyzeGitIntelligence } from "./intelligence/git/analyze.js";
4
4
  export { buildC4Views } from "./architecture/c4.js";
5
+ export { loadArchitectureContract, checkArchitecture, checkArchitectureAtRoot, initArchitecture, explainArchitecture, DEFAULT_ARCHITECTURE_CONTRACT, } from "./architecture/contract.js";
6
+ export type { ArchitectureContract, ArchitectureViolation, ArchitectureCheckResult, } from "./architecture/contract.js";
7
+ export { loadCoverage, parseLcov, parseIstanbul, parseCobertura } from "./coverage/load.js";
8
+ export type { NormalizedCoverage, CoverageFormat, CoverageFile } from "./coverage/types.js";
9
+ export { parseSourceFile, languageAdapters, detectLanguage } from "./languages/index.js";
10
+ export type { LanguageAdapter, ParseResult, LanguageId } from "./languages/index.js";
5
11
  export { createKnowledgeRecord, listKnowledge, transitionKnowledge, retrieveAuthoritative, } from "./knowledge/store.js";
6
- export { FilesystemStorageProvider, MemoryStorageProvider } from "./storage/provider.js";
7
- export { runControlledCommand } from "./enforcement/runner.js";
12
+ export { FilesystemStorageProvider, MemoryStorageProvider, tryCreateSqliteStorage, tryCreatePostgresStorage, } from "./storage/provider.js";
13
+ export { SqliteStorageProvider } from "./storage/sqlite.js";
14
+ export { PostgresStorageProvider } from "./storage/postgres.js";
15
+ export { LocalIdentityProvider, OidcIdentityProvider, hasRbacPermission } from "./auth/index.js";
16
+ export type { IdentityClaims, IdentityProvider, RbacRole, RbacPermission } from "./auth/index.js";
17
+ export { initWorkspace, listWorkspaces, loadWorkspace, addRepositoryToWorkspace, removeWorkspace, workspaceStatus, assertWorkspacePathAccess, readFileWithinWorkspace, } from "./workspace/index.js";
18
+ export type { WorkspaceModel } from "./workspace/index.js";
19
+ export { composePolicy, explainComposedDecision } from "./policy/compose.js";
20
+ export { runControlledCommand, parseArgv, assertArgsInsideRoot, explainControlledRun, } from "./enforcement/runner.js";
8
21
  export { registerLocalDevUser, authenticateLocalDev, authorize } from "./team/auth.js";
9
22
  export { runProjectInit, listProposals, reviewProposal } from "./core/brain-product/init.js";
10
23
  export { collectOpsHealth } from "./ops/health.js";
@@ -14,6 +27,14 @@ export { scan } from "./core/scanner/scan.js";
14
27
  export { verify } from "./core/verify/verify.js";
15
28
  export { compareFindings } from "./core/verify/compare.js";
16
29
  export { PACKAGE_VERSION } from "./constants.js";
30
+ export { analyzeChange, verifyChange, inspectEvidence, verifyEvidence, explainChange, diffChange, changeStatus, deriveGraphChangeImpact, derivePackageImpact, EVIDENCE_SCHEMA_VERSION, } from "./assurance/change.js";
31
+ export type { ChangeAssessment, EvidenceManifest, VerificationStatus } from "./assurance/change.js";
32
+ export { buildProofFromEvidence, inspectProof, verifyProof, exportProof, explainProof, } from "./assurance/proof.js";
33
+ export type { ChangeProof, IntegrityStatus, CorrectnessStatus, EngineeringChecksStatus, ProofVerificationState, } from "./assurance/proof.js";
34
+ export { assertInsideRepo, resolveSafeRepoPath, safeRelPath, PathEscapeError, } from "./security/paths.js";
35
+ export { resolveImportSpecifier, resolveImportEdges, loadTsconfigPaths, } from "./intelligence/resolve/imports.js";
36
+ export type { ImportConfidence, ImportResolutionResult, TsconfigPathsConfig, } from "./intelligence/resolve/imports.js";
37
+ export { graphBuild, graphUpdate, graphRebuild, graphStatus, } from "./intelligence/graph/incremental.js";
17
38
  export type { ScanOptions, ScanResult, RepositoryInfo, Finding, FindingsSummary, Scores, AgentPresence, AgentDetectionResult, AgentSecurityAnalysisMode, CliOptions, ExitCode, } from "./types/index.js";
18
39
  export type { VerifyResult, VerifyOptions } from "./core/verify/verify.js";
19
40
  export type { VerifyFindingRef, FindingCompareResult } from "./core/verify/compare.js";
@@ -29,3 +50,16 @@ export { runFix } from "./core/fix/run.js";
29
50
  export { evaluatePolicy, evaluateScanPolicy, evaluateVerifyPolicy, parseFailOnRules, parseSeverityGate, } from "./core/policy/evaluate.js";
30
51
  export type { PolicyOptions, PolicyViolation, PolicyViolationCode, PolicyInput, } from "./core/policy/evaluate.js";
31
52
  export type { FixPlan, FixAction, FixApplyResult } from "./core/fix/types.js";
53
+ /** AgentDoctor 2.1 AI provider layer (optional — core works without AI). */
54
+ export { createModelProvider, loadAiConfig, publicAiConfig, redactForModel, AI_PROVIDER_REQUIRED_MESSAGE, NoneModelProvider, MockModelProvider, OpenAiCompatibleProvider, } from "./ai/index.js";
55
+ export type { AiConfig, AiProviderId, ChatMessage, ChatRequest, ChatResponse, ModelMetadata, ModelProvider, ToolSpec, ToolCallRequest, TokenUsage, } from "./ai/index.js";
56
+ /** AgentDoctor 2.1 Project AI Agent core (M1 scaffolding). */
57
+ export { AgentState, AgentStateMachine, AgentRuntime, DEFAULT_AGENT_LIMITS, retrieveProjectContext, truthLabelHelp, minTruth, } from "./agent/index.js";
58
+ export type { AgentLimits, AgentAuditEvent, AgentRuntimeOptions, AgentTurnResult, AgentStateTransition, ContextBundle, ContextCitation, EvidenceType, TruthLabel, RetrieveContextOptions, } from "./agent/index.js";
59
+ export { ChatService, createChatService, ChatMemory, CHAT_PROVIDER_NONE_MESSAGE, buildChatTurnResponse, formatChatResponseForCli, summarizeProjectForChat, } from "./agent/chat/index.js";
60
+ export type { ChatTurnResponse, ChatServiceOptions, TruthClaim, ProjectChatSummary, } from "./agent/chat/index.js";
61
+ /** AgentDoctor 2.1 agent tools + plan (M3). */
62
+ export { listAgentToolSpecs, getToolSpec, riskForTool, executeAgentTool, isReadTool, newToolCall, evaluateApproval, formatApprovalPrompt, buildAgentPlan, formatAgentPlan, approvePlan, runCodingLoop, verifyAgentWork, formatVerificationReport, } from "./agent/index.js";
63
+ export type { AgentToolName, AgentToolSpec, AgentToolCall, AgentToolResult, AgentRiskLevel, ApprovalRequest, ApprovalResult, ApprovalDecision, AgentPlan, AgentPlanStep, CodingLoopOptions, CodingLoopResult, AgentVerificationReport, VerificationCheck, } from "./agent/index.js";
64
+ export { getModeProfile, defaultStudentMode, parseAgentMode, modeAllowsMutation, modeBlocksToolCategory, StudentService, } from "./agent/index.js";
65
+ export type { AgentMode, ModeProfile, StudentDocSection, BuildWithMeResult, } from "./agent/index.js";
package/dist/index.js CHANGED
@@ -2,9 +2,17 @@ export { CONTRACTS_VERSION, DEFAULT_FEATURE_FLAGS } from "./contracts/index.js";
2
2
  export { buildIntelligenceGraph } from "./intelligence/graph/build.js";
3
3
  export { analyzeGitIntelligence } from "./intelligence/git/analyze.js";
4
4
  export { buildC4Views } from "./architecture/c4.js";
5
+ export { loadArchitectureContract, checkArchitecture, checkArchitectureAtRoot, initArchitecture, explainArchitecture, DEFAULT_ARCHITECTURE_CONTRACT, } from "./architecture/contract.js";
6
+ export { loadCoverage, parseLcov, parseIstanbul, parseCobertura } from "./coverage/load.js";
7
+ export { parseSourceFile, languageAdapters, detectLanguage } from "./languages/index.js";
5
8
  export { createKnowledgeRecord, listKnowledge, transitionKnowledge, retrieveAuthoritative, } from "./knowledge/store.js";
6
- export { FilesystemStorageProvider, MemoryStorageProvider } from "./storage/provider.js";
7
- export { runControlledCommand } from "./enforcement/runner.js";
9
+ export { FilesystemStorageProvider, MemoryStorageProvider, tryCreateSqliteStorage, tryCreatePostgresStorage, } from "./storage/provider.js";
10
+ export { SqliteStorageProvider } from "./storage/sqlite.js";
11
+ export { PostgresStorageProvider } from "./storage/postgres.js";
12
+ export { LocalIdentityProvider, OidcIdentityProvider, hasRbacPermission } from "./auth/index.js";
13
+ export { initWorkspace, listWorkspaces, loadWorkspace, addRepositoryToWorkspace, removeWorkspace, workspaceStatus, assertWorkspacePathAccess, readFileWithinWorkspace, } from "./workspace/index.js";
14
+ export { composePolicy, explainComposedDecision } from "./policy/compose.js";
15
+ export { runControlledCommand, parseArgv, assertArgsInsideRoot, explainControlledRun, } from "./enforcement/runner.js";
8
16
  export { registerLocalDevUser, authenticateLocalDev, authorize } from "./team/auth.js";
9
17
  export { runProjectInit, listProposals, reviewProposal } from "./core/brain-product/init.js";
10
18
  export { collectOpsHealth } from "./ops/health.js";
@@ -14,6 +22,11 @@ export { scan } from "./core/scanner/scan.js";
14
22
  export { verify } from "./core/verify/verify.js";
15
23
  export { compareFindings } from "./core/verify/compare.js";
16
24
  export { PACKAGE_VERSION } from "./constants.js";
25
+ export { analyzeChange, verifyChange, inspectEvidence, verifyEvidence, explainChange, diffChange, changeStatus, deriveGraphChangeImpact, derivePackageImpact, EVIDENCE_SCHEMA_VERSION, } from "./assurance/change.js";
26
+ export { buildProofFromEvidence, inspectProof, verifyProof, exportProof, explainProof, } from "./assurance/proof.js";
27
+ export { assertInsideRepo, resolveSafeRepoPath, safeRelPath, PathEscapeError, } from "./security/paths.js";
28
+ export { resolveImportSpecifier, resolveImportEdges, loadTsconfigPaths, } from "./intelligence/resolve/imports.js";
29
+ export { graphBuild, graphUpdate, graphRebuild, graphStatus, } from "./intelligence/graph/incremental.js";
17
30
  export { EXIT_CODES } from "./types/index.js";
18
31
  export { agentRegistry } from "./agents/registry.js";
19
32
  export { detectAgents } from "./agents/detect-agents.js";
@@ -23,3 +36,11 @@ export { buildFixPlan } from "./core/fix/plan.js";
23
36
  export { applyFixPlan } from "./core/fix/apply.js";
24
37
  export { runFix } from "./core/fix/run.js";
25
38
  export { evaluatePolicy, evaluateScanPolicy, evaluateVerifyPolicy, parseFailOnRules, parseSeverityGate, } from "./core/policy/evaluate.js";
39
+ /** AgentDoctor 2.1 AI provider layer (optional — core works without AI). */
40
+ export { createModelProvider, loadAiConfig, publicAiConfig, redactForModel, AI_PROVIDER_REQUIRED_MESSAGE, NoneModelProvider, MockModelProvider, OpenAiCompatibleProvider, } from "./ai/index.js";
41
+ /** AgentDoctor 2.1 Project AI Agent core (M1 scaffolding). */
42
+ export { AgentState, AgentStateMachine, AgentRuntime, DEFAULT_AGENT_LIMITS, retrieveProjectContext, truthLabelHelp, minTruth, } from "./agent/index.js";
43
+ export { ChatService, createChatService, ChatMemory, CHAT_PROVIDER_NONE_MESSAGE, buildChatTurnResponse, formatChatResponseForCli, summarizeProjectForChat, } from "./agent/chat/index.js";
44
+ /** AgentDoctor 2.1 agent tools + plan (M3). */
45
+ export { listAgentToolSpecs, getToolSpec, riskForTool, executeAgentTool, isReadTool, newToolCall, evaluateApproval, formatApprovalPrompt, buildAgentPlan, formatAgentPlan, approvePlan, runCodingLoop, verifyAgentWork, formatVerificationReport, } from "./agent/index.js";
46
+ export { getModeProfile, defaultStudentMode, parseAgentMode, modeAllowsMutation, modeBlocksToolCategory, StudentService, } from "./agent/index.js";
@@ -1,5 +1,7 @@
1
1
  import type { RepositoryGraph } from "../../platform/types.js";
2
2
  export type GraphBuilderMode = "regex" | "typescript-ast" | "auto";
3
+ export declare function nodeId(kind: string, key: string): string;
4
+ export declare function listTsFiles(root: string, limit?: number): Promise<string[]>;
3
5
  /**
4
6
  * TypeScript compiler API based graph enrichment.
5
7
  * Falls back to regex graph when mode=regex or on failure.