@praneeth_54/agentdoctor 2.0.0 → 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 (95) hide show
  1. package/CHANGELOG.md +73 -4
  2. package/README.md +283 -158
  3. package/dist/architecture/contract.d.ts +64 -0
  4. package/dist/architecture/contract.js +373 -0
  5. package/dist/assurance/change.d.ts +211 -0
  6. package/dist/assurance/change.js +624 -0
  7. package/dist/assurance/proof.d.ts +136 -0
  8. package/dist/assurance/proof.js +338 -0
  9. package/dist/auth/index.d.ts +81 -0
  10. package/dist/auth/index.js +130 -0
  11. package/dist/auth/rbac.d.ts +9 -0
  12. package/dist/auth/rbac.js +26 -0
  13. package/dist/cli/commands/architecture.d.ts +6 -0
  14. package/dist/cli/commands/architecture.js +109 -0
  15. package/dist/cli/commands/assurance.d.ts +67 -0
  16. package/dist/cli/commands/assurance.js +210 -0
  17. package/dist/cli/commands/complete.js +2 -2
  18. package/dist/cli/commands/platform.d.ts +2 -0
  19. package/dist/cli/commands/platform.js +5 -1
  20. package/dist/cli/commands/policy-graph-run.d.ts +40 -0
  21. package/dist/cli/commands/policy-graph-run.js +200 -0
  22. package/dist/cli/commands/workspace.d.ts +9 -0
  23. package/dist/cli/commands/workspace.js +82 -0
  24. package/dist/cli/program.js +535 -17
  25. package/dist/constants.d.ts +1 -1
  26. package/dist/constants.js +1 -1
  27. package/dist/contracts/index.d.ts +1 -1
  28. package/dist/contracts/index.js +2 -2
  29. package/dist/core/brain-product/init.js +5 -2
  30. package/dist/core/secrets/scan.d.ts +7 -1
  31. package/dist/core/secrets/scan.js +9 -9
  32. package/dist/coverage/cobertura.d.ts +6 -0
  33. package/dist/coverage/cobertura.js +52 -0
  34. package/dist/coverage/istanbul.d.ts +7 -0
  35. package/dist/coverage/istanbul.js +134 -0
  36. package/dist/coverage/lcov.d.ts +7 -0
  37. package/dist/coverage/lcov.js +59 -0
  38. package/dist/coverage/load.d.ts +9 -0
  39. package/dist/coverage/load.js +39 -0
  40. package/dist/coverage/types.d.ts +23 -0
  41. package/dist/coverage/types.js +5 -0
  42. package/dist/dashboard/server.d.ts +1 -1
  43. package/dist/dashboard/server.js +21 -5
  44. package/dist/enforcement/runner.d.ts +47 -3
  45. package/dist/enforcement/runner.js +420 -16
  46. package/dist/index.d.ts +23 -2
  47. package/dist/index.js +15 -2
  48. package/dist/intelligence/graph/build.d.ts +2 -0
  49. package/dist/intelligence/graph/build.js +49 -5
  50. package/dist/intelligence/graph/incremental.d.ts +68 -0
  51. package/dist/intelligence/graph/incremental.js +234 -0
  52. package/dist/intelligence/resolve/imports.d.ts +36 -0
  53. package/dist/intelligence/resolve/imports.js +245 -0
  54. package/dist/languages/go.d.ts +9 -0
  55. package/dist/languages/go.js +45 -0
  56. package/dist/languages/index.d.ts +11 -0
  57. package/dist/languages/index.js +76 -0
  58. package/dist/languages/php.d.ts +7 -0
  59. package/dist/languages/php.js +193 -0
  60. package/dist/languages/python.d.ts +7 -0
  61. package/dist/languages/python.js +150 -0
  62. package/dist/languages/types.d.ts +42 -0
  63. package/dist/languages/types.js +26 -0
  64. package/dist/languages/typescript.d.ts +3 -0
  65. package/dist/languages/typescript.js +95 -0
  66. package/dist/mcp/intelligence/handlers.d.ts +5 -0
  67. package/dist/mcp/intelligence/handlers.js +229 -40
  68. package/dist/mcp/intelligence/path-safety.d.ts +2 -4
  69. package/dist/mcp/intelligence/path-safety.js +25 -44
  70. package/dist/mcp/intelligence/registry.d.ts +1 -1
  71. package/dist/mcp/intelligence/registry.js +69 -1
  72. package/dist/platform/firewall/evaluate.d.ts +6 -3
  73. package/dist/platform/firewall/evaluate.js +66 -15
  74. package/dist/platform/graph/build.js +19 -23
  75. package/dist/platform/health/analyze.js +23 -17
  76. package/dist/platform/index.js +2 -0
  77. package/dist/platform/test-impact/analyze.d.ts +21 -3
  78. package/dist/platform/test-impact/analyze.js +214 -28
  79. package/dist/platform/tokens/plan.js +39 -20
  80. package/dist/platform/types.d.ts +9 -0
  81. package/dist/policy/compose.d.ts +34 -0
  82. package/dist/policy/compose.js +118 -0
  83. package/dist/policy/packs.js +33 -1
  84. package/dist/security/paths.d.ts +21 -0
  85. package/dist/security/paths.js +105 -0
  86. package/dist/storage/postgres.d.ts +26 -0
  87. package/dist/storage/postgres.js +90 -0
  88. package/dist/storage/provider.d.ts +2 -7
  89. package/dist/storage/provider.js +22 -16
  90. package/dist/storage/sqlite.d.ts +33 -0
  91. package/dist/storage/sqlite.js +77 -0
  92. package/dist/team/auth.js +1 -1
  93. package/dist/workspace/index.d.ts +69 -0
  94. package/dist/workspace/index.js +220 -0
  95. package/package.json +9 -3
@@ -1,46 +1,394 @@
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 { 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
+ 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
+ }
4
241
  /**
5
242
  * AgentDoctor-controlled command runner.
6
- * Only this boundary can mark execution as blocked-by-enforcement when it refuses to run.
243
+ * Executes only when executeIfAllowed===true AND policy decision===allow.
244
+ * Default path uses shell:false with argv parsing (no shell metacharacters).
7
245
  */
8
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
+ }
9
264
  const action = {
10
265
  actionId: randomUUID(),
11
266
  agentId: "enforcement-runner",
12
267
  timestamp: new Date().toISOString(),
13
268
  type: "shell",
14
- params: { command: options.command },
15
- repositoryRoot: options.root,
269
+ params: { command: commandStr },
270
+ repositoryRoot: root,
16
271
  };
17
- const verdict = await evaluateAgentAction(options.root, action, {
272
+ const verdict = await evaluateAgentAction(root, action, {
18
273
  failClosed: options.failClosed === true,
19
274
  });
20
275
  const blocked = verdict.decision === "block" || verdict.decision === "deny-network";
21
- const decision = {
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 {
22
363
  actionId: verdict.actionId,
23
364
  decision: verdict.decision,
24
- reason: verdict.reason,
365
+ reason: [verdict.reason, extraReason, riskNote].filter(Boolean).join(" | "),
25
366
  ...(verdict.policyId ? { policyId: verdict.policyId } : {}),
26
367
  policyVersion: "2.0",
27
368
  inputClassification: "shell",
28
369
  evidence: [{ id: "ev_policy", kind: "observed", detail: verdict.reason }],
29
370
  riskLevel: verdict.riskLevel,
30
- executionStatus: blocked
31
- ? "blocked-by-enforcement"
32
- : options.executeIfAllowed && verdict.decision === "allow"
33
- ? "not-executed"
34
- : "not-executed",
371
+ executionStatus,
35
372
  approvalStatus: verdict.approvalStatus,
36
373
  timestamp: new Date().toISOString(),
37
374
  };
375
+ }
376
+ function buildFailedResult(_root, message, executionStatus) {
38
377
  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}`,
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,
44
392
  };
45
393
  }
46
394
  /** Append-only tamper-evident audit chain (HMAC over previous hash). */
@@ -54,3 +402,59 @@ export function appendAuditChain(previousHash, record, secret = "local-dev-audit
54
402
  export function auditRecordId(parts) {
55
403
  return `audit_${createHash("sha1").update(parts).digest("hex").slice(0, 12)}`;
56
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
@@ -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";
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";
@@ -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.
@@ -5,10 +5,18 @@ import ts from "typescript";
5
5
  import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
6
6
  import { buildRepositoryGraph } from "../../platform/graph/build.js";
7
7
  import { CONTRACTS_VERSION } from "../../contracts/index.js";
8
- function nodeId(kind, key) {
8
+ import { loadTsconfigPaths, resolveImportSpecifier, } from "../resolve/imports.js";
9
+ function evidenceForConfidence(confidence) {
10
+ if (confidence === "EXACT")
11
+ return "verified";
12
+ if (confidence === "RESOLVED" || confidence === "INFERRED")
13
+ return "inferred";
14
+ return "unknown";
15
+ }
16
+ export function nodeId(kind, key) {
9
17
  return `${kind}:${createHash("sha1").update(key).digest("hex").slice(0, 12)}`;
10
18
  }
11
- async function listTsFiles(root, limit = 400) {
19
+ export async function listTsFiles(root, limit = 400) {
12
20
  const out = [];
13
21
  const skip = new Set(["node_modules", ".git", "dist", "coverage", ".agentdoctor", "vendor"]);
14
22
  async function walk(dir) {
@@ -78,6 +86,10 @@ export async function buildIntelligenceGraph(options) {
78
86
  const nodes = [];
79
87
  const edges = [];
80
88
  const seen = new Set();
89
+ // TypeScript uses `/` on Windows; path.join uses `\`. Normalize before matching.
90
+ const normalizeFsPath = (p) => path.normalize(p).toLowerCase();
91
+ const fileSet = new Set(files.map(normalizeFsPath));
92
+ const tsconfigPaths = loadTsconfigPaths(root);
81
93
  const pushNode = (n) => {
82
94
  if (seen.has(n.id))
83
95
  return;
@@ -87,7 +99,7 @@ export async function buildIntelligenceGraph(options) {
87
99
  for (const sf of program.getSourceFiles()) {
88
100
  if (sf.isDeclarationFile)
89
101
  continue;
90
- if (!files.includes(sf.fileName))
102
+ if (!fileSet.has(normalizeFsPath(sf.fileName)))
91
103
  continue;
92
104
  const rel = toPosixRelative(root, sf.fileName);
93
105
  const fileId = nodeId("file", rel);
@@ -143,21 +155,52 @@ export async function buildIntelligenceGraph(options) {
143
155
  node.moduleSpecifier &&
144
156
  ts.isStringLiteral(node.moduleSpecifier)) {
145
157
  const spec = node.moduleSpecifier.text;
158
+ const resolution = resolveImportSpecifier({
159
+ root,
160
+ fromFile: rel,
161
+ specifier: spec,
162
+ tsconfig: tsconfigPaths,
163
+ });
164
+ // Specifier dependency node (always — records the literal import).
146
165
  const toId = nodeId("dependency", `${rel}->${spec}`);
147
166
  pushNode({
148
167
  id: toId,
149
168
  kind: "dependency",
150
169
  label: spec,
151
170
  path: rel,
152
- meta: { specifier: spec, parser: "typescript-ast" },
171
+ meta: {
172
+ specifier: spec,
173
+ parser: "typescript-ast",
174
+ confidence: resolution.confidence,
175
+ ...(resolution.resolvedPath ? { resolvedPath: resolution.resolvedPath } : {}),
176
+ ...(resolution.via ? { via: resolution.via } : {}),
177
+ },
153
178
  });
154
179
  edges.push({
155
180
  id: nodeId("edge", `${fileId}->${toId}`),
156
181
  from: fileId,
157
182
  to: toId,
158
183
  kind: "imports",
159
- evidence: spec.startsWith(".") ? "verified" : "inferred",
184
+ evidence: evidenceForConfidence(resolution.confidence),
160
185
  });
186
+ // Concrete file→file edge only when resolved — never invent UNRESOLVED targets.
187
+ if (resolution.resolvedPath && resolution.confidence !== "UNRESOLVED") {
188
+ const targetFileId = nodeId("file", resolution.resolvedPath);
189
+ pushNode({
190
+ id: targetFileId,
191
+ kind: "file",
192
+ label: path.basename(resolution.resolvedPath),
193
+ path: resolution.resolvedPath,
194
+ meta: { importConfidence: resolution.confidence },
195
+ });
196
+ edges.push({
197
+ id: nodeId("edge", `${fileId}->file:${resolution.resolvedPath}`),
198
+ from: fileId,
199
+ to: targetFileId,
200
+ kind: "imports",
201
+ evidence: evidenceForConfidence(resolution.confidence),
202
+ });
203
+ }
161
204
  }
162
205
  if (ts.isCallExpression(node) &&
163
206
  ts.isIdentifier(node.expression) &&
@@ -195,6 +238,7 @@ export async function buildIntelligenceGraph(options) {
195
238
  limitations: [
196
239
  "TypeScript AST builder uses the TypeScript compiler API for .ts/.tsx only",
197
240
  "Cross-file call resolution is identifier-based (not full type-checker binding)",
241
+ "Import edges use EXACT|RESOLVED|INFERRED confidence; UNRESOLVED never invents file targets",
198
242
  "Non-TypeScript languages fall back to regex graph when requested via auto+empty TS set",
199
243
  `analysisVersion=${CONTRACTS_VERSION}`,
200
244
  ],