@praneeth_54/agentdoctor 1.1.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/CHANGELOG.md +164 -7
  2. package/README.md +263 -513
  3. package/dist/agents/aider/adapter.d.ts +1 -0
  4. package/dist/agents/aider/adapter.js +1 -0
  5. package/dist/agents/aider/detector.d.ts +14 -0
  6. package/dist/agents/aider/detector.js +120 -0
  7. package/dist/agents/copilot/adapter.d.ts +1 -0
  8. package/dist/agents/copilot/adapter.js +1 -0
  9. package/dist/agents/copilot/detector.d.ts +13 -0
  10. package/dist/agents/copilot/detector.js +113 -0
  11. package/dist/agents/gemini/adapter.d.ts +1 -0
  12. package/dist/agents/gemini/adapter.js +1 -0
  13. package/dist/agents/gemini/detector.d.ts +13 -0
  14. package/dist/agents/gemini/detector.js +151 -0
  15. package/dist/agents/registry.js +13 -1
  16. package/dist/agents/types.d.ts +1 -1
  17. package/dist/agents/windsurf/adapter.d.ts +1 -0
  18. package/dist/agents/windsurf/adapter.js +1 -0
  19. package/dist/agents/windsurf/detector.d.ts +14 -0
  20. package/dist/agents/windsurf/detector.js +119 -0
  21. package/dist/architecture/c4.d.ts +22 -0
  22. package/dist/architecture/c4.js +62 -0
  23. package/dist/architecture/contract.d.ts +64 -0
  24. package/dist/architecture/contract.js +373 -0
  25. package/dist/assurance/change.d.ts +211 -0
  26. package/dist/assurance/change.js +624 -0
  27. package/dist/assurance/proof.d.ts +136 -0
  28. package/dist/assurance/proof.js +338 -0
  29. package/dist/auth/index.d.ts +81 -0
  30. package/dist/auth/index.js +130 -0
  31. package/dist/auth/rbac.d.ts +9 -0
  32. package/dist/auth/rbac.js +26 -0
  33. package/dist/cli/commands/architecture.d.ts +6 -0
  34. package/dist/cli/commands/architecture.js +109 -0
  35. package/dist/cli/commands/assurance.d.ts +67 -0
  36. package/dist/cli/commands/assurance.js +210 -0
  37. package/dist/cli/commands/brain.d.ts +12 -0
  38. package/dist/cli/commands/brain.js +162 -0
  39. package/dist/cli/commands/complete.d.ts +23 -0
  40. package/dist/cli/commands/complete.js +232 -0
  41. package/dist/cli/commands/explain.js +1 -1
  42. package/dist/cli/commands/fix.js +22 -2
  43. package/dist/cli/commands/mcp.d.ts +8 -0
  44. package/dist/cli/commands/mcp.js +14 -0
  45. package/dist/cli/commands/platform.d.ts +17 -0
  46. package/dist/cli/commands/platform.js +195 -0
  47. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  48. package/dist/cli/commands/policy-graph-run.js +200 -0
  49. package/dist/cli/commands/v2.d.ts +55 -0
  50. package/dist/cli/commands/v2.js +278 -0
  51. package/dist/cli/commands/workspace.d.ts +9 -0
  52. package/dist/cli/commands/workspace.js +82 -0
  53. package/dist/cli/program.js +1328 -3
  54. package/dist/constants.d.ts +3 -2
  55. package/dist/constants.js +5 -1
  56. package/dist/contracts/adapters.d.ts +7 -0
  57. package/dist/contracts/adapters.js +60 -0
  58. package/dist/contracts/index.d.ts +162 -0
  59. package/dist/contracts/index.js +18 -0
  60. package/dist/core/baseline/store.d.ts +48 -0
  61. package/dist/core/baseline/store.js +163 -0
  62. package/dist/core/brain-cli/service.d.ts +26 -0
  63. package/dist/core/brain-cli/service.js +175 -0
  64. package/dist/core/brain-product/init.d.ts +45 -0
  65. package/dist/core/brain-product/init.js +235 -0
  66. package/dist/core/changes/analyze.d.ts +41 -0
  67. package/dist/core/changes/analyze.js +223 -0
  68. package/dist/core/context-health/analyze.d.ts +15 -0
  69. package/dist/core/context-health/analyze.js +234 -0
  70. package/dist/core/fix/apply.d.ts +21 -4
  71. package/dist/core/fix/apply.js +118 -18
  72. package/dist/core/fix/backup.d.ts +30 -0
  73. package/dist/core/fix/backup.js +193 -0
  74. package/dist/core/fix/plan.d.ts +6 -3
  75. package/dist/core/fix/plan.js +113 -9
  76. package/dist/core/fix/render.d.ts +2 -0
  77. package/dist/core/fix/render.js +36 -21
  78. package/dist/core/fix/run.js +5 -1
  79. package/dist/core/fix/safe-target.d.ts +14 -0
  80. package/dist/core/fix/safe-target.js +81 -0
  81. package/dist/core/fix/types.d.ts +12 -1
  82. package/dist/core/fix/types.js +2 -0
  83. package/dist/core/fix/writers/claude-settings.js +3 -1
  84. package/dist/core/fix/writers/codex-config.d.ts +3 -0
  85. package/dist/core/fix/writers/codex-config.js +24 -3
  86. package/dist/core/fix/writers/cursorignore.js +3 -1
  87. package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
  88. package/dist/core/fix/writers/simple-ignore.js +68 -0
  89. package/dist/core/monorepo/detect.d.ts +13 -0
  90. package/dist/core/monorepo/detect.js +121 -0
  91. package/dist/core/rules/build-context.js +22 -3
  92. package/dist/core/rules/context/generated-directory.js +11 -1
  93. package/dist/core/rules/context/large-instruction-file.js +6 -0
  94. package/dist/core/rules/context/large-log-file.js +16 -1
  95. package/dist/core/rules/ignore.d.ts +11 -1
  96. package/dist/core/rules/ignore.js +22 -1
  97. package/dist/core/rules/instructions/empty-instructions.js +7 -0
  98. package/dist/core/rules/security/env-file-exposure.js +23 -2
  99. package/dist/core/rules/security/private-key-file.js +1 -1
  100. package/dist/core/scanner/scan.js +1 -1
  101. package/dist/core/schemas/validate.d.ts +6 -0
  102. package/dist/core/schemas/validate.js +62 -0
  103. package/dist/core/scoring/compute-scores.d.ts +1 -1
  104. package/dist/core/scoring/compute-scores.js +5 -1
  105. package/dist/core/scoring/placeholder.d.ts +1 -1
  106. package/dist/core/scoring/placeholder.js +5 -1
  107. package/dist/core/secrets/scan.d.ts +29 -0
  108. package/dist/core/secrets/scan.js +172 -0
  109. package/dist/coverage/cobertura.d.ts +6 -0
  110. package/dist/coverage/cobertura.js +52 -0
  111. package/dist/coverage/istanbul.d.ts +7 -0
  112. package/dist/coverage/istanbul.js +134 -0
  113. package/dist/coverage/lcov.d.ts +7 -0
  114. package/dist/coverage/lcov.js +59 -0
  115. package/dist/coverage/load.d.ts +9 -0
  116. package/dist/coverage/load.js +39 -0
  117. package/dist/coverage/types.d.ts +23 -0
  118. package/dist/coverage/types.js +5 -0
  119. package/dist/dashboard/server.d.ts +24 -0
  120. package/dist/dashboard/server.js +402 -0
  121. package/dist/enforcement/runner.d.ts +67 -0
  122. package/dist/enforcement/runner.js +460 -0
  123. package/dist/index.d.ts +34 -0
  124. package/dist/index.js +25 -0
  125. package/dist/integrations/github/pr-analyze.d.ts +28 -0
  126. package/dist/integrations/github/pr-analyze.js +98 -0
  127. package/dist/integrations/local-ai/provider.d.ts +45 -0
  128. package/dist/integrations/local-ai/provider.js +104 -0
  129. package/dist/intelligence/git/analyze.d.ts +34 -0
  130. package/dist/intelligence/git/analyze.js +136 -0
  131. package/dist/intelligence/graph/build.d.ts +15 -0
  132. package/dist/intelligence/graph/build.js +259 -0
  133. package/dist/intelligence/graph/incremental.d.ts +68 -0
  134. package/dist/intelligence/graph/incremental.js +234 -0
  135. package/dist/intelligence/resolve/imports.d.ts +36 -0
  136. package/dist/intelligence/resolve/imports.js +245 -0
  137. package/dist/knowledge/store.d.ts +25 -0
  138. package/dist/knowledge/store.js +91 -0
  139. package/dist/languages/go.d.ts +9 -0
  140. package/dist/languages/go.js +45 -0
  141. package/dist/languages/index.d.ts +11 -0
  142. package/dist/languages/index.js +76 -0
  143. package/dist/languages/php.d.ts +7 -0
  144. package/dist/languages/php.js +193 -0
  145. package/dist/languages/python.d.ts +7 -0
  146. package/dist/languages/python.js +150 -0
  147. package/dist/languages/types.d.ts +42 -0
  148. package/dist/languages/types.js +26 -0
  149. package/dist/languages/typescript.d.ts +3 -0
  150. package/dist/languages/typescript.js +95 -0
  151. package/dist/mcp/agentdoctor/server.d.ts +16 -0
  152. package/dist/mcp/agentdoctor/server.js +67 -0
  153. package/dist/mcp/brain/session.js +7 -7
  154. package/dist/mcp/intelligence/handlers.d.ts +16 -0
  155. package/dist/mcp/intelligence/handlers.js +364 -0
  156. package/dist/mcp/intelligence/path-safety.d.ts +9 -0
  157. package/dist/mcp/intelligence/path-safety.js +63 -0
  158. package/dist/mcp/intelligence/registry.d.ts +8 -0
  159. package/dist/mcp/intelligence/registry.js +228 -0
  160. package/dist/ops/health.d.ts +18 -0
  161. package/dist/ops/health.js +51 -0
  162. package/dist/platform/ai-quality/analyze.d.ts +9 -0
  163. package/dist/platform/ai-quality/analyze.js +65 -0
  164. package/dist/platform/architecture/drift.d.ts +17 -0
  165. package/dist/platform/architecture/drift.js +74 -0
  166. package/dist/platform/auth/local.d.ts +15 -0
  167. package/dist/platform/auth/local.js +58 -0
  168. package/dist/platform/context-security/analyze.d.ts +6 -0
  169. package/dist/platform/context-security/analyze.js +97 -0
  170. package/dist/platform/firewall/evaluate.d.ts +57 -0
  171. package/dist/platform/firewall/evaluate.js +359 -0
  172. package/dist/platform/graph/build.d.ts +6 -0
  173. package/dist/platform/graph/build.js +189 -0
  174. package/dist/platform/health/analyze.d.ts +7 -0
  175. package/dist/platform/health/analyze.js +192 -0
  176. package/dist/platform/index.d.ts +27 -0
  177. package/dist/platform/index.js +89 -0
  178. package/dist/platform/knowledge/analyze.d.ts +20 -0
  179. package/dist/platform/knowledge/analyze.js +96 -0
  180. package/dist/platform/provenance/build.d.ts +44 -0
  181. package/dist/platform/provenance/build.js +65 -0
  182. package/dist/platform/readiness/scorecard.d.ts +5 -0
  183. package/dist/platform/readiness/scorecard.js +98 -0
  184. package/dist/platform/refactor/impact.d.ts +20 -0
  185. package/dist/platform/refactor/impact.js +60 -0
  186. package/dist/platform/reports/export.d.ts +12 -0
  187. package/dist/platform/reports/export.js +92 -0
  188. package/dist/platform/security/redact.d.ts +15 -0
  189. package/dist/platform/security/redact.js +87 -0
  190. package/dist/platform/sessions/store.d.ts +32 -0
  191. package/dist/platform/sessions/store.js +97 -0
  192. package/dist/platform/store.d.ts +6 -0
  193. package/dist/platform/store.js +57 -0
  194. package/dist/platform/test-impact/analyze.d.ts +50 -0
  195. package/dist/platform/test-impact/analyze.js +350 -0
  196. package/dist/platform/time-machine/compare.d.ts +17 -0
  197. package/dist/platform/time-machine/compare.js +62 -0
  198. package/dist/platform/tokens/plan.d.ts +28 -0
  199. package/dist/platform/tokens/plan.js +108 -0
  200. package/dist/platform/types.d.ts +120 -0
  201. package/dist/platform/types.js +5 -0
  202. package/dist/plugins/runtime.d.ts +36 -0
  203. package/dist/plugins/runtime.js +101 -0
  204. package/dist/plugins/sdk.d.ts +29 -0
  205. package/dist/plugins/sdk.js +114 -0
  206. package/dist/policy/compose.d.ts +34 -0
  207. package/dist/policy/compose.js +118 -0
  208. package/dist/policy/packs.d.ts +9 -0
  209. package/dist/policy/packs.js +91 -0
  210. package/dist/reporters/terminal/report.js +38 -5
  211. package/dist/security/paths.d.ts +21 -0
  212. package/dist/security/paths.js +105 -0
  213. package/dist/storage/postgres.d.ts +26 -0
  214. package/dist/storage/postgres.js +90 -0
  215. package/dist/storage/provider.d.ts +30 -0
  216. package/dist/storage/provider.js +109 -0
  217. package/dist/storage/sqlite.d.ts +33 -0
  218. package/dist/storage/sqlite.js +77 -0
  219. package/dist/team/auth.d.ts +35 -0
  220. package/dist/team/auth.js +70 -0
  221. package/dist/types/index.d.ts +5 -1
  222. package/dist/workspace/index.d.ts +69 -0
  223. package/dist/workspace/index.js +220 -0
  224. package/package.json +15 -5
@@ -0,0 +1,460 @@
1
+ import { createHash, randomUUID } from "node:crypto";
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 { realpathSync } from "node:fs";
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
+ const DEFAULT_TIMEOUT_MS = 30_000;
13
+ const DEFAULT_MAX_BUFFER = 1024 * 1024;
14
+ const OUTPUT_TRUNCATE = 32_768;
15
+ const ENV_ALLOWLIST = new Set(["PATH", "NODE_ENV", "HOME", "LANG"]);
16
+ /**
17
+ * Parse a command string into argv without invoking a shell.
18
+ * Supports simple double/single quotes; rejects unclosed quotes.
19
+ */
20
+ export function parseArgv(command) {
21
+ const trimmed = command.trim();
22
+ if (!trimmed)
23
+ return [];
24
+ const args = [];
25
+ let current = "";
26
+ let quote = null;
27
+ for (let i = 0; i < trimmed.length; i += 1) {
28
+ const ch = trimmed[i];
29
+ if (quote) {
30
+ if (ch === quote) {
31
+ quote = null;
32
+ }
33
+ else {
34
+ current += ch;
35
+ }
36
+ continue;
37
+ }
38
+ if (ch === '"' || ch === "'") {
39
+ quote = ch;
40
+ continue;
41
+ }
42
+ if (/\s/.test(ch)) {
43
+ if (current.length > 0) {
44
+ args.push(current);
45
+ current = "";
46
+ }
47
+ continue;
48
+ }
49
+ current += ch;
50
+ }
51
+ if (quote) {
52
+ throw new Error("unclosed quote in command");
53
+ }
54
+ if (current.length > 0)
55
+ args.push(current);
56
+ return args;
57
+ }
58
+ export function buildAllowlistedEnv(source = process.env) {
59
+ const env = {};
60
+ for (const key of ENV_ALLOWLIST) {
61
+ const value = source[key];
62
+ if (typeof value === "string")
63
+ env[key] = value;
64
+ }
65
+ for (const [key, value] of Object.entries(source)) {
66
+ if (key.startsWith("AGENTDOCTOR_") && typeof value === "string") {
67
+ env[key] = value;
68
+ }
69
+ }
70
+ return env;
71
+ }
72
+ /**
73
+ * Reject path-like args that resolve outside the repository root.
74
+ */
75
+ export function assertArgsInsideRoot(args, root) {
76
+ for (const arg of args) {
77
+ if (arg.startsWith("-"))
78
+ continue;
79
+ const looksLikePath = arg.includes("..") || arg.includes("/") || arg.includes("\\") || path.isAbsolute(arg);
80
+ if (!looksLikePath)
81
+ continue;
82
+ try {
83
+ assertInsideRepo(root, arg);
84
+ }
85
+ catch (error) {
86
+ if (error instanceof PathEscapeError) {
87
+ throw new Error("path escape rejected");
88
+ }
89
+ throw new Error("path escape rejected");
90
+ }
91
+ }
92
+ }
93
+ function truncate(text, max = OUTPUT_TRUNCATE) {
94
+ if (text.length <= max)
95
+ return text;
96
+ return `${text.slice(0, max)}\n…[truncated ${text.length - max} chars]`;
97
+ }
98
+ function resolveRealRoot(rootInput) {
99
+ const resolved = resolveRepoRoot(rootInput);
100
+ try {
101
+ return realpathSync(resolved);
102
+ }
103
+ catch {
104
+ return resolved;
105
+ }
106
+ }
107
+ async function appendExecutionAudit(root, record) {
108
+ const auditPath = path.join(root, ".agentdoctor", "audit", "execution.jsonl");
109
+ await fs.mkdir(path.dirname(auditPath), { recursive: true });
110
+ let previousHash = null;
111
+ try {
112
+ const existing = await fs.readFile(auditPath, "utf8");
113
+ const lines = existing.trim().split("\n").filter(Boolean);
114
+ const last = lines[lines.length - 1];
115
+ if (last) {
116
+ const parsed = JSON.parse(last);
117
+ previousHash = typeof parsed.hash === "string" ? parsed.hash : null;
118
+ }
119
+ }
120
+ catch {
121
+ previousHash = null;
122
+ }
123
+ const { hash, payload } = appendAuditChain(previousHash, record);
124
+ const line = `${JSON.stringify({ hash, previousHash, record: JSON.parse(payload) })}\n`;
125
+ await fs.appendFile(auditPath, line, "utf8");
126
+ }
127
+ async function executeArgv(options) {
128
+ const start = Date.now();
129
+ const env = buildAllowlistedEnv();
130
+ const cwd = options.root;
131
+ if (options.useShell) {
132
+ const command = options.argv.join(" ");
133
+ return await new Promise((resolve) => {
134
+ const child = spawn(command, {
135
+ cwd,
136
+ env,
137
+ shell: true,
138
+ windowsHide: true,
139
+ });
140
+ let stdout = "";
141
+ let stderr = "";
142
+ let settled = false;
143
+ const timer = setTimeout(() => {
144
+ child.kill("SIGKILL");
145
+ if (!settled) {
146
+ settled = true;
147
+ resolve({
148
+ stdout: truncate(stdout),
149
+ stderr: truncate(stderr || "timeout"),
150
+ exitCode: null,
151
+ durationMs: Date.now() - start,
152
+ failed: true,
153
+ error: `timeout after ${options.timeoutMs}ms`,
154
+ });
155
+ }
156
+ }, options.timeoutMs);
157
+ child.stdout?.on("data", (chunk) => {
158
+ stdout += chunk.toString("utf8");
159
+ if (stdout.length > options.maxBuffer) {
160
+ child.kill("SIGKILL");
161
+ }
162
+ });
163
+ child.stderr?.on("data", (chunk) => {
164
+ stderr += chunk.toString("utf8");
165
+ if (stderr.length > options.maxBuffer) {
166
+ child.kill("SIGKILL");
167
+ }
168
+ });
169
+ child.on("error", (err) => {
170
+ if (settled)
171
+ return;
172
+ settled = true;
173
+ clearTimeout(timer);
174
+ resolve({
175
+ stdout: truncate(stdout),
176
+ stderr: truncate(stderr),
177
+ exitCode: null,
178
+ durationMs: Date.now() - start,
179
+ failed: true,
180
+ error: err.message,
181
+ });
182
+ });
183
+ child.on("close", (code) => {
184
+ if (settled)
185
+ return;
186
+ settled = true;
187
+ clearTimeout(timer);
188
+ resolve({
189
+ stdout: truncate(stdout),
190
+ stderr: truncate(stderr),
191
+ exitCode: code,
192
+ durationMs: Date.now() - start,
193
+ failed: code !== 0,
194
+ });
195
+ });
196
+ });
197
+ }
198
+ const [file, ...args] = options.argv;
199
+ if (!file) {
200
+ return {
201
+ stdout: "",
202
+ stderr: "empty command",
203
+ exitCode: null,
204
+ durationMs: Date.now() - start,
205
+ failed: true,
206
+ error: "empty command",
207
+ };
208
+ }
209
+ try {
210
+ const result = await execFileAsync(file, args, {
211
+ cwd,
212
+ env,
213
+ timeout: options.timeoutMs,
214
+ maxBuffer: options.maxBuffer,
215
+ windowsHide: true,
216
+ shell: false,
217
+ });
218
+ return {
219
+ stdout: truncate(typeof result.stdout === "string" ? result.stdout : String(result.stdout)),
220
+ stderr: truncate(typeof result.stderr === "string" ? result.stderr : String(result.stderr)),
221
+ exitCode: 0,
222
+ durationMs: Date.now() - start,
223
+ failed: false,
224
+ };
225
+ }
226
+ catch (error) {
227
+ const err = error;
228
+ const exitCode = typeof err.code === "number" ? err.code : null;
229
+ return {
230
+ stdout: truncate(typeof err.stdout === "string" ? err.stdout : err.stdout ? String(err.stdout) : ""),
231
+ stderr: truncate(typeof err.stderr === "string" ? err.stderr : err.stderr ? String(err.stderr) : ""),
232
+ exitCode,
233
+ durationMs: Date.now() - start,
234
+ failed: true,
235
+ error: err.killed
236
+ ? `timeout after ${options.timeoutMs}ms`
237
+ : (err.message ?? "execution failed"),
238
+ };
239
+ }
240
+ }
241
+ /**
242
+ * AgentDoctor-controlled command runner.
243
+ * Executes only when executeIfAllowed===true AND policy decision===allow.
244
+ * Default path uses shell:false with argv parsing (no shell metacharacters).
245
+ */
246
+ export async function runControlledCommand(options) {
247
+ const root = resolveRealRoot(options.root);
248
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
249
+ const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
250
+ const useShell = options.useShell === true;
251
+ let argv;
252
+ try {
253
+ argv =
254
+ options.argv && options.argv.length > 0 ? options.argv : parseArgv(options.command ?? "");
255
+ }
256
+ catch (error) {
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ return buildFailedResult(root, message, "not-executed");
259
+ }
260
+ const commandStr = argv.join(" ");
261
+ if (!commandStr) {
262
+ return buildFailedResult(root, "empty command", "not-executed");
263
+ }
264
+ const action = {
265
+ actionId: randomUUID(),
266
+ agentId: "enforcement-runner",
267
+ timestamp: new Date().toISOString(),
268
+ type: "shell",
269
+ params: { command: commandStr },
270
+ repositoryRoot: root,
271
+ };
272
+ const verdict = await evaluateAgentAction(root, action, {
273
+ failClosed: options.failClosed === true,
274
+ });
275
+ const blocked = verdict.decision === "block" || verdict.decision === "deny-network";
276
+ if (blocked) {
277
+ const decision = toDecision(verdict, "blocked-by-enforcement");
278
+ await appendExecutionAudit(root, {
279
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
280
+ command: commandStr,
281
+ argv,
282
+ executionStatus: "blocked-by-enforcement",
283
+ decision: verdict.decision,
284
+ reason: verdict.reason,
285
+ at: new Date().toISOString(),
286
+ });
287
+ return {
288
+ decision,
289
+ enforced: true,
290
+ notice: `Blocked by AgentDoctor-controlled runner. ${verdict.reason}`,
291
+ };
292
+ }
293
+ if (!options.executeIfAllowed || verdict.decision !== "allow") {
294
+ const decision = toDecision(verdict, "not-executed");
295
+ return {
296
+ decision,
297
+ enforced: false,
298
+ notice: verdict.decision === "allow"
299
+ ? `Policy allow; runner did not execute (executeIfAllowed=false). ${EVALUATE_ONLY}`
300
+ : `Policy ${verdict.decision}; runner did not execute. ${EVALUATE_ONLY}`,
301
+ };
302
+ }
303
+ if (!useShell) {
304
+ try {
305
+ assertArgsInsideRoot(argv, root);
306
+ }
307
+ catch (error) {
308
+ const message = error instanceof Error ? error.message : String(error);
309
+ const decision = toDecision(verdict, "execution-failed", message);
310
+ await appendExecutionAudit(root, {
311
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
312
+ command: commandStr,
313
+ argv,
314
+ executionStatus: "execution-failed",
315
+ reason: message,
316
+ at: new Date().toISOString(),
317
+ });
318
+ return {
319
+ decision,
320
+ enforced: false,
321
+ notice: `Execution failed: ${message}`,
322
+ };
323
+ }
324
+ }
325
+ const execResult = await executeArgv({
326
+ root,
327
+ argv,
328
+ timeoutMs,
329
+ maxBuffer,
330
+ useShell,
331
+ });
332
+ const executionStatus = execResult.failed ? "execution-failed" : "executed";
333
+ const decision = toDecision(verdict, executionStatus, execResult.error, useShell ? "HIGH RISK: executed via shell=true (explicit --shell flag)" : undefined);
334
+ await appendExecutionAudit(root, {
335
+ id: auditRecordId(`${commandStr}:${Date.now()}`),
336
+ command: commandStr,
337
+ argv,
338
+ useShell,
339
+ executionStatus,
340
+ exitCode: execResult.exitCode,
341
+ durationMs: execResult.durationMs,
342
+ stdout: execResult.stdout,
343
+ stderr: execResult.stderr,
344
+ ...(execResult.error ? { error: execResult.error } : {}),
345
+ at: new Date().toISOString(),
346
+ });
347
+ return {
348
+ decision,
349
+ enforced: false,
350
+ notice: execResult.failed
351
+ ? `Execution failed${execResult.error ? `: ${execResult.error}` : ""}`
352
+ : useShell
353
+ ? "Executed under AgentDoctor control with shell=true (HIGH RISK)."
354
+ : "Executed under AgentDoctor control (shell=false).",
355
+ stdout: execResult.stdout,
356
+ stderr: execResult.stderr,
357
+ exitCode: execResult.exitCode,
358
+ durationMs: execResult.durationMs,
359
+ };
360
+ }
361
+ function toDecision(verdict, executionStatus, extraReason, riskNote) {
362
+ return {
363
+ actionId: verdict.actionId,
364
+ decision: verdict.decision,
365
+ reason: [verdict.reason, extraReason, riskNote].filter(Boolean).join(" | "),
366
+ ...(verdict.policyId ? { policyId: verdict.policyId } : {}),
367
+ policyVersion: "2.0",
368
+ inputClassification: "shell",
369
+ evidence: [{ id: "ev_policy", kind: "observed", detail: verdict.reason }],
370
+ riskLevel: verdict.riskLevel,
371
+ executionStatus,
372
+ approvalStatus: verdict.approvalStatus,
373
+ timestamp: new Date().toISOString(),
374
+ };
375
+ }
376
+ function buildFailedResult(_root, message, executionStatus) {
377
+ return {
378
+ decision: {
379
+ actionId: randomUUID(),
380
+ decision: "block",
381
+ reason: message,
382
+ policyVersion: "2.0",
383
+ inputClassification: "shell",
384
+ evidence: [{ id: "ev_parse", kind: "observed", detail: message }],
385
+ riskLevel: "high",
386
+ executionStatus,
387
+ approvalStatus: "not-required",
388
+ timestamp: new Date().toISOString(),
389
+ },
390
+ enforced: executionStatus === "blocked-by-enforcement",
391
+ notice: message,
392
+ };
393
+ }
394
+ /** Append-only tamper-evident audit chain (HMAC over previous hash). */
395
+ export function appendAuditChain(previousHash, record, secret = "local-dev-audit-key") {
396
+ const body = JSON.stringify(record);
397
+ const hash = createHmac("sha256", secret)
398
+ .update(`${previousHash ?? "GENESIS"}:${body}`)
399
+ .digest("hex");
400
+ return { hash, payload: body };
401
+ }
402
+ export function auditRecordId(parts) {
403
+ return `audit_${createHash("sha1").update(parts).digest("hex").slice(0, 12)}`;
404
+ }
405
+ /**
406
+ * Explain what the controlled runner would do for a command (evaluate-only).
407
+ * Does not execute.
408
+ */
409
+ export async function explainControlledRun(options) {
410
+ const root = resolveRealRoot(options.root);
411
+ let argv;
412
+ try {
413
+ argv =
414
+ options.argv && options.argv.length > 0 ? options.argv : parseArgv(options.command ?? "");
415
+ }
416
+ catch (error) {
417
+ const message = error instanceof Error ? error.message : String(error);
418
+ return {
419
+ command: options.command ?? "",
420
+ argv: [],
421
+ useShell: options.useShell === true,
422
+ decision: "block",
423
+ reason: message,
424
+ policyId: null,
425
+ riskLevel: "high",
426
+ wouldExecute: false,
427
+ executionResult: "not-executed",
428
+ notice: EVALUATE_ONLY,
429
+ meaning: "Command could not be parsed for controlled execution.",
430
+ };
431
+ }
432
+ const commandStr = argv.join(" ");
433
+ const action = {
434
+ actionId: randomUUID(),
435
+ agentId: "enforcement-explain",
436
+ timestamp: new Date().toISOString(),
437
+ type: "shell",
438
+ params: { command: commandStr },
439
+ repositoryRoot: root,
440
+ };
441
+ const verdict = await evaluateAgentAction(root, action, {
442
+ failClosed: options.failClosed === true,
443
+ });
444
+ const wouldExecute = verdict.decision === "allow";
445
+ return {
446
+ command: commandStr,
447
+ argv,
448
+ useShell: options.useShell === true,
449
+ decision: verdict.decision,
450
+ reason: verdict.reason,
451
+ policyId: verdict.policyId ?? null,
452
+ riskLevel: verdict.riskLevel,
453
+ wouldExecute,
454
+ executionResult: "not-executed",
455
+ notice: EVALUATE_ONLY,
456
+ meaning: wouldExecute
457
+ ? "Policy allow — `agentdoctor run` would execute under AgentDoctor control (still not claimed as enterprise interception)."
458
+ : "Policy would block or require approval — controlled runner will not execute.",
459
+ };
460
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,40 @@
1
+ export { CONTRACTS_VERSION, DEFAULT_FEATURE_FLAGS } from "./contracts/index.js";
2
+ export { buildIntelligenceGraph } from "./intelligence/graph/build.js";
3
+ export { analyzeGitIntelligence } from "./intelligence/git/analyze.js";
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";
11
+ export { createKnowledgeRecord, listKnowledge, transitionKnowledge, retrieveAuthoritative, } from "./knowledge/store.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";
21
+ export { registerLocalDevUser, authenticateLocalDev, authorize } from "./team/auth.js";
22
+ export { runProjectInit, listProposals, reviewProposal } from "./core/brain-product/init.js";
23
+ export { collectOpsHealth } from "./ops/health.js";
24
+ export { listPolicyPacks, getPolicyPack } from "./policy/packs.js";
25
+ export { listIntelligenceMcpTools, INTELLIGENCE_MCP_TOOL_NAMES, } from "./mcp/intelligence/registry.js";
1
26
  export { scan } from "./core/scanner/scan.js";
2
27
  export { verify } from "./core/verify/verify.js";
3
28
  export { compareFindings } from "./core/verify/compare.js";
4
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";
5
38
  export type { ScanOptions, ScanResult, RepositoryInfo, Finding, FindingsSummary, Scores, AgentPresence, AgentDetectionResult, AgentSecurityAnalysisMode, CliOptions, ExitCode, } from "./types/index.js";
6
39
  export type { VerifyResult, VerifyOptions } from "./core/verify/verify.js";
7
40
  export type { VerifyFindingRef, FindingCompareResult } from "./core/verify/compare.js";
@@ -12,6 +45,7 @@ export { ruleRegistry, getRuleById } from "./core/rules/registry.js";
12
45
  export { runRules } from "./core/rules/run-rules.js";
13
46
  export { buildFixPlan } from "./core/fix/plan.js";
14
47
  export { applyFixPlan } from "./core/fix/apply.js";
48
+ export type { ApplyFixPlanOptions } from "./core/fix/apply.js";
15
49
  export { runFix } from "./core/fix/run.js";
16
50
  export { evaluatePolicy, evaluateScanPolicy, evaluateVerifyPolicy, parseFailOnRules, parseSeverityGate, } from "./core/policy/evaluate.js";
17
51
  export type { PolicyOptions, PolicyViolation, PolicyViolationCode, PolicyInput, } from "./core/policy/evaluate.js";
package/dist/index.js CHANGED
@@ -1,7 +1,32 @@
1
+ export { CONTRACTS_VERSION, DEFAULT_FEATURE_FLAGS } from "./contracts/index.js";
2
+ export { buildIntelligenceGraph } from "./intelligence/graph/build.js";
3
+ export { analyzeGitIntelligence } from "./intelligence/git/analyze.js";
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";
8
+ export { createKnowledgeRecord, listKnowledge, transitionKnowledge, retrieveAuthoritative, } from "./knowledge/store.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";
16
+ export { registerLocalDevUser, authenticateLocalDev, authorize } from "./team/auth.js";
17
+ export { runProjectInit, listProposals, reviewProposal } from "./core/brain-product/init.js";
18
+ export { collectOpsHealth } from "./ops/health.js";
19
+ export { listPolicyPacks, getPolicyPack } from "./policy/packs.js";
20
+ export { listIntelligenceMcpTools, INTELLIGENCE_MCP_TOOL_NAMES, } from "./mcp/intelligence/registry.js";
1
21
  export { scan } from "./core/scanner/scan.js";
2
22
  export { verify } from "./core/verify/verify.js";
3
23
  export { compareFindings } from "./core/verify/compare.js";
4
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";
5
30
  export { EXIT_CODES } from "./types/index.js";
6
31
  export { agentRegistry } from "./agents/registry.js";
7
32
  export { detectAgents } from "./agents/detect-agents.js";
@@ -0,0 +1,28 @@
1
+ export interface PrReviewFinding {
2
+ severity: "critical" | "warning" | "info";
3
+ title: string;
4
+ message: string;
5
+ file?: string;
6
+ line?: number;
7
+ confidence: "high" | "medium" | "low";
8
+ source: "scan" | "context-health" | "changes";
9
+ }
10
+ export interface PrReviewReport {
11
+ root: string;
12
+ mode: "local" | "github-dry-run";
13
+ baseRef: string | null;
14
+ headRef: string | null;
15
+ findings: PrReviewFinding[];
16
+ commentMarkdown: string;
17
+ limitations: string[];
18
+ posted: false;
19
+ }
20
+ /**
21
+ * Local PR review. Never posts to GitHub unless a future explicit command is added.
22
+ * GitHub token is unused; dry-run is the only supported remote mode.
23
+ */
24
+ export declare function analyzePullRequest(options: {
25
+ root: string;
26
+ baseRef?: string;
27
+ dryRun?: boolean;
28
+ }): Promise<PrReviewReport>;
@@ -0,0 +1,98 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { analyzeChanges } from "../../core/changes/analyze.js";
3
+ import { analyzeContextHealth } from "../../core/context-health/analyze.js";
4
+ import { resolveRepoRoot } from "../../utils/path.js";
5
+ import { scan } from "../../core/scanner/scan.js";
6
+ function redactSecretsInText(text) {
7
+ return text
8
+ .replace(/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED]")
9
+ .replace(/\bghp_[A-Za-z0-9]{36}\b/g, "[REDACTED]")
10
+ .replace(/-----BEGIN [^-]+PRIVATE KEY-----/g, "[REDACTED PRIVATE KEY]");
11
+ }
12
+ function buildComment(findings) {
13
+ const lines = [
14
+ "## AgentDoctor PR review (local)",
15
+ "",
16
+ "_Generated locally. Secrets are redacted. Nothing was posted to GitHub._",
17
+ "",
18
+ ];
19
+ if (findings.length === 0) {
20
+ lines.push("No actionable findings in local analysis.");
21
+ return lines.join("\n");
22
+ }
23
+ for (const f of findings) {
24
+ const loc = f.file ? ` \`${f.file}${f.line ? `:${f.line}` : ""}\`` : "";
25
+ lines.push(`- **${f.severity}** (${f.confidence}) ${redactSecretsInText(f.title)}${loc}: ${redactSecretsInText(f.message)}`);
26
+ }
27
+ return `${lines.join("\n")}\n`;
28
+ }
29
+ /**
30
+ * Local PR review. Never posts to GitHub unless a future explicit command is added.
31
+ * GitHub token is unused; dry-run is the only supported remote mode.
32
+ */
33
+ export async function analyzePullRequest(options) {
34
+ const root = resolveRepoRoot(options.root);
35
+ const limitations = [
36
+ "GitHub comment posting is disabled; use --dry-run / local mode only",
37
+ "Optional GitHub API integration requires explicit future invocation with a token",
38
+ ];
39
+ const baseRef = options.baseRef ?? null;
40
+ const changes = await analyzeChanges({
41
+ root,
42
+ ...(baseRef ? { since: baseRef } : {}),
43
+ });
44
+ const context = await analyzeContextHealth(root);
45
+ const scanResult = await scan({ cwd: root });
46
+ const findings = [];
47
+ for (const f of scanResult.findings.filter((x) => x.severity === "critical" || x.severity === "warning")) {
48
+ findings.push({
49
+ severity: f.severity,
50
+ title: f.title,
51
+ message: f.message,
52
+ ...(f.evidence?.path ? { file: f.evidence.path } : {}),
53
+ ...(typeof f.evidence?.line === "number" ? { line: f.evidence.line } : {}),
54
+ confidence: "high",
55
+ source: "scan",
56
+ });
57
+ }
58
+ for (const c of context.conflicts) {
59
+ findings.push({
60
+ severity: c.severity === "warning" ? "warning" : "info",
61
+ title: c.kind,
62
+ message: c.message,
63
+ ...(c.files[0] ? { file: c.files[0] } : {}),
64
+ confidence: "medium",
65
+ source: "context-health",
66
+ });
67
+ }
68
+ if (changes.gitAvailable && changes.files.length > 0) {
69
+ findings.push({
70
+ severity: "info",
71
+ title: "changed-files",
72
+ message: `${changes.files.length} changed file(s) in working tree / range`,
73
+ confidence: "high",
74
+ source: "changes",
75
+ });
76
+ }
77
+ else if (!changes.gitAvailable) {
78
+ limitations.push(...changes.limitations);
79
+ }
80
+ let headRef = changes.head;
81
+ if (!headRef) {
82
+ const probe = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
83
+ cwd: root,
84
+ encoding: "utf8",
85
+ });
86
+ headRef = probe.status === 0 ? probe.stdout.trim() : null;
87
+ }
88
+ return {
89
+ root,
90
+ mode: options.dryRun === false ? "local" : "github-dry-run",
91
+ baseRef,
92
+ headRef,
93
+ findings,
94
+ commentMarkdown: buildComment(findings),
95
+ limitations,
96
+ posted: false,
97
+ };
98
+ }