auditor-lambda 0.2.1 → 0.2.2

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 (61) hide show
  1. package/audit-code-wrapper-lib.mjs +229 -289
  2. package/dist/adapters/eslint.js +4 -2
  3. package/dist/adapters/npmAudit.js +1 -1
  4. package/dist/cli.js +82 -45
  5. package/dist/extractors/bucketing.js +14 -35
  6. package/dist/extractors/disposition.js +8 -9
  7. package/dist/extractors/fileInventory.js +0 -2
  8. package/dist/extractors/flows.js +14 -23
  9. package/dist/extractors/pathPatterns.d.ts +19 -0
  10. package/dist/extractors/pathPatterns.js +87 -0
  11. package/dist/extractors/surfaces.js +2 -7
  12. package/dist/io/artifacts.d.ts +23 -1
  13. package/dist/io/artifacts.js +4 -3
  14. package/dist/io/runArtifacts.js +1 -1
  15. package/dist/orchestrator/advance.js +53 -71
  16. package/dist/orchestrator/flowCoverage.js +1 -2
  17. package/dist/orchestrator/internalExecutors.js +4 -6
  18. package/dist/orchestrator/planning.js +12 -20
  19. package/dist/orchestrator/resultIngestion.js +3 -2
  20. package/dist/orchestrator/runtimeValidation.js +5 -0
  21. package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
  22. package/dist/orchestrator/taskBuilder.js +15 -28
  23. package/dist/prompts/renderWorkerPrompt.js +2 -1
  24. package/dist/providers/claudeCodeProvider.js +1 -2
  25. package/dist/providers/constants.d.ts +1 -0
  26. package/dist/providers/constants.js +1 -0
  27. package/dist/providers/index.js +7 -3
  28. package/dist/providers/opencodeProvider.js +1 -6
  29. package/dist/providers/spawnLoggedCommand.js +4 -0
  30. package/dist/providers/types.d.ts +0 -1
  31. package/dist/supervisor/operatorHandoff.d.ts +2 -0
  32. package/dist/supervisor/operatorHandoff.js +21 -9
  33. package/dist/supervisor/runLedger.js +7 -8
  34. package/dist/supervisor/sessionConfig.js +1 -0
  35. package/dist/types/flowCoverage.d.ts +1 -1
  36. package/dist/types/runLedger.d.ts +1 -1
  37. package/dist/types/runtimeValidation.d.ts +2 -1
  38. package/dist/types/sessionConfig.d.ts +0 -6
  39. package/dist/types/surfaces.d.ts +2 -1
  40. package/dist/types/workerSession.d.ts +2 -0
  41. package/dist/types.d.ts +0 -1
  42. package/dist/validation/sessionConfig.js +1 -15
  43. package/package.json +1 -1
  44. package/schemas/audit-code-v1alpha1.schema.json +4 -0
  45. package/schemas/audit_result.schema.json +9 -3
  46. package/schemas/audit_state.schema.json +2 -2
  47. package/schemas/audit_task.schema.json +14 -3
  48. package/schemas/blind_spot_register.schema.json +13 -3
  49. package/schemas/coverage_matrix.schema.json +16 -4
  50. package/schemas/critical_flows.schema.json +6 -3
  51. package/schemas/external_analyzer_results.schema.json +10 -4
  52. package/schemas/finding.schema.json +31 -3
  53. package/schemas/flow_coverage.schema.json +12 -3
  54. package/schemas/graph_bundle.schema.json +12 -6
  55. package/schemas/merged_findings.schema.json +7 -2
  56. package/schemas/risk_register.schema.json +5 -1
  57. package/schemas/root_cause_clusters.schema.json +2 -1
  58. package/schemas/runtime_validation_tasks.schema.json +4 -1
  59. package/schemas/surface_manifest.schema.json +4 -1
  60. package/schemas/unit_manifest.schema.json +10 -3
  61. package/skills/audit-code/audit-code.prompt.md +0 -2
@@ -1,8 +1,10 @@
1
1
  import { normalizeGenericExternalResults } from "./normalizeExternal.js";
2
+ const ESLINT_SEVERITY_ERROR = 2;
3
+ const ESLINT_SEVERITY_WARNING = 1;
2
4
  function mapSeverity(value) {
3
- if (value === 2)
5
+ if (value === ESLINT_SEVERITY_ERROR)
4
6
  return "medium";
5
- if (value === 1)
7
+ if (value === ESLINT_SEVERITY_WARNING)
6
8
  return "low";
7
9
  return "info";
8
10
  }
@@ -1,7 +1,7 @@
1
1
  import { normalizeGenericExternalResults } from "./normalizeExternal.js";
2
2
  export function normalizeNpmAuditJson(input) {
3
3
  return normalizeGenericExternalResults("npm-audit", Object.entries(input.vulnerabilities ?? {}).map(([pkg, vuln], index) => ({
4
- id: `npm-audit-${index + 1}`,
4
+ id: `npm-audit-${index}`,
5
5
  category: "dependency_risk",
6
6
  severity: vuln.severity ?? "unknown",
7
7
  path: "package-lock.json",
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdir } from "node:fs/promises";
1
+ import { access, mkdir } from "node:fs/promises";
2
2
  import { createReadStream } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { buildRepoManifest } from "./extractors/fileInventory.js";
@@ -24,8 +24,11 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
24
24
  import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
25
25
  import { buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
26
26
  import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
27
+ import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
27
28
  const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
28
29
  const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
30
+ const DEFAULT_MAX_RUNS = 1000;
31
+ const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
29
32
  const sampleFiles = [
30
33
  { path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
31
34
  { path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
@@ -48,8 +51,8 @@ function getRootDir(argv) {
48
51
  return resolve(getFlag(argv, "--root", "."));
49
52
  }
50
53
  function getMaxRuns(argv) {
51
- const raw = Number(getFlag(argv, "--max-runs", "1000"));
52
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 1000;
54
+ const raw = Number(getFlag(argv, "--max-runs", String(DEFAULT_MAX_RUNS)));
55
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_MAX_RUNS;
53
56
  }
54
57
  function getAgentBatchSize(argv, sessionConfig) {
55
58
  const fromArg = getFlag(argv, "--agent-batch-size");
@@ -58,8 +61,7 @@ function getAgentBatchSize(argv, sessionConfig) {
58
61
  if (Number.isFinite(parsed) && parsed > 0)
59
62
  return Math.floor(parsed);
60
63
  }
61
- if (typeof sessionConfig.agent_task_batch_size === "number" &&
62
- sessionConfig.agent_task_batch_size > 0) {
64
+ if (typeof sessionConfig.agent_task_batch_size === "number" && sessionConfig.agent_task_batch_size > 0) {
63
65
  return Math.floor(sessionConfig.agent_task_batch_size);
64
66
  }
65
67
  return 1;
@@ -71,8 +73,7 @@ function getParallelWorkers(argv, sessionConfig) {
71
73
  if (Number.isFinite(parsed) && parsed > 0)
72
74
  return Math.floor(parsed);
73
75
  }
74
- if (typeof sessionConfig.parallel_workers === "number" &&
75
- sessionConfig.parallel_workers > 0) {
76
+ if (typeof sessionConfig.parallel_workers === "number" && sessionConfig.parallel_workers > 0) {
76
77
  return Math.floor(sessionConfig.parallel_workers);
77
78
  }
78
79
  return 1;
@@ -113,6 +114,7 @@ async function emitEnvelope(params) {
113
114
  bundle: params.bundle,
114
115
  providerName: params.providerName,
115
116
  progressSummary: params.progress_summary,
117
+ isConfigError: params.isConfigError,
116
118
  });
117
119
  await writeAuditCodeHandoffArtifacts(handoff);
118
120
  console.log(JSON.stringify(buildEnvelope({
@@ -127,7 +129,7 @@ async function emitEnvelope(params) {
127
129
  }), null, 2));
128
130
  }
129
131
  function buildManualReviewBlocker(providerName) {
130
- return providerName === "local-subprocess"
132
+ return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
131
133
  ? "Automatic local-subprocess work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider such as claude-code, opencode, or subprocess-template."
132
134
  : "Automatic work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider.";
133
135
  }
@@ -159,8 +161,9 @@ function buildBlockedAuditState(params) {
159
161
  }
160
162
  async function countLines(path) {
161
163
  return new Promise((resolve, reject) => {
162
- let lines = 1;
164
+ let lines = 0;
163
165
  let byteCount = 0;
166
+ let lastByte = -1;
164
167
  const stream = createReadStream(path);
165
168
  stream.on("data", (chunk) => {
166
169
  const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
@@ -168,9 +171,15 @@ async function countLines(path) {
168
171
  for (let i = 0; i < buffer.length; ++i) {
169
172
  if (buffer[i] === 10)
170
173
  lines++;
174
+ lastByte = buffer[i];
171
175
  }
172
176
  });
173
- stream.on("end", () => resolve(byteCount === 0 ? 0 : lines));
177
+ stream.on("end", () => {
178
+ if (byteCount === 0)
179
+ return resolve(0);
180
+ // Files not ending with \n have one final line not counted above
181
+ resolve(lastByte !== 10 ? lines + 1 : lines);
182
+ });
174
183
  stream.on("error", reject);
175
184
  });
176
185
  }
@@ -194,20 +203,29 @@ async function buildLineIndex(root, repoManifest) {
194
203
  }
195
204
  return Object.fromEntries(entries);
196
205
  }
197
- function resolveModelForTasks(tasks, sessionConfig) {
198
- if (!sessionConfig.model_tiers)
199
- return undefined;
200
- const tiers = [
201
- "capable",
202
- "balanced",
203
- "fast",
204
- ];
205
- for (const tier of tiers) {
206
- if (tasks.some((t) => t.model_hint === tier)) {
207
- return sessionConfig.model_tiers[tier];
206
+ const PROJECT_SIGNALS = [
207
+ "package.json",
208
+ "go.mod",
209
+ "Cargo.toml",
210
+ "pom.xml",
211
+ "build.gradle",
212
+ "pyproject.toml",
213
+ "setup.py",
214
+ "setup.cfg",
215
+ "Makefile",
216
+ "CMakeLists.txt",
217
+ ];
218
+ async function detectProjectRoot(root) {
219
+ for (const signal of PROJECT_SIGNALS) {
220
+ try {
221
+ await access(join(root, signal));
222
+ return signal;
223
+ }
224
+ catch {
225
+ // not found, try next
208
226
  }
209
227
  }
210
- return undefined;
228
+ return null;
211
229
  }
212
230
  function buildPendingAuditTasks(bundle) {
213
231
  const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
@@ -337,10 +355,39 @@ async function cmdRunToCompletion(argv) {
337
355
  const maxRuns = getMaxRuns(argv);
338
356
  const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
339
357
  const parallelWorkers = getParallelWorkers(argv, sessionConfig);
340
- const timeoutMs = sessionConfig.timeout_ms ?? 30 * 60 * 1000;
358
+ const timeoutMs = sessionConfig.timeout_ms ?? DEFAULT_TIMEOUT_MS;
341
359
  const selfCliPath = resolve(process.argv[1] ?? "");
342
360
  await mkdir(artifactsDir, { recursive: true });
343
361
  await ensureSupervisorDirs(artifactsDir);
362
+ const earlyBundle = await loadArtifactBundle(artifactsDir);
363
+ if (!earlyBundle.unit_manifest) {
364
+ const foundSignal = await detectProjectRoot(root);
365
+ if (!foundSignal) {
366
+ const blocker = `No recognisable project signals found in ${root}. Expected one of: ${PROJECT_SIGNALS.join(", ")}. Check that --root points to the repository root, not a subdirectory or an unrelated path.`;
367
+ const earlyState = deriveAuditState(earlyBundle);
368
+ const blockedState = buildBlockedAuditState({
369
+ state: earlyState,
370
+ obligationId: null,
371
+ executor: null,
372
+ blocker,
373
+ });
374
+ await emitEnvelope({
375
+ root,
376
+ artifactsDir,
377
+ bundle: { ...earlyBundle, audit_state: blockedState },
378
+ audit_state: blockedState,
379
+ selected_obligation: null,
380
+ selected_executor: null,
381
+ progress_made: false,
382
+ artifacts_written: [],
383
+ progress_summary: blocker,
384
+ next_likely_step: null,
385
+ providerName: provider.name,
386
+ isConfigError: true,
387
+ });
388
+ return;
389
+ }
390
+ }
344
391
  let pendingAuditResultsPath = getFlag(argv, "--results");
345
392
  let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
346
393
  let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
@@ -371,7 +418,7 @@ async function cmdRunToCompletion(argv) {
371
418
  obligationId = "runtime_validation_current";
372
419
  runtimeUpdatesPath = pendingRuntimeUpdatesPath;
373
420
  }
374
- if (preferredExecutor === "agent" && provider.name === "local-subprocess") {
421
+ if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
375
422
  const blocker = buildManualReviewBlocker(provider.name);
376
423
  const blockedState = buildBlockedAuditState({
377
424
  state: bundle.audit_state ?? decision.state,
@@ -468,13 +515,7 @@ async function cmdRunToCompletion(argv) {
468
515
  obligation_id: obligationId,
469
516
  preferred_executor: "agent",
470
517
  result_path: slotPaths.resultPath,
471
- worker_command: [
472
- process.execPath,
473
- selfCliPath,
474
- "worker-run",
475
- "--task",
476
- slotPaths.taskPath,
477
- ],
518
+ worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
478
519
  audit_results_path: slotAuditResultsPath,
479
520
  pending_audit_tasks_path: slotPendingTasksPath,
480
521
  skip_worker_command: true,
@@ -482,13 +523,7 @@ async function cmdRunToCompletion(argv) {
482
523
  const slotPrompt = renderWorkerPrompt(slotTask);
483
524
  await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir);
484
525
  await writeJsonFile(slotPendingTasksPath, group);
485
- workerSlots.push({
486
- runId: slotRunId,
487
- paths: slotPaths,
488
- auditResultsPath: slotAuditResultsPath,
489
- pendingTasksPath: slotPendingTasksPath,
490
- group,
491
- });
526
+ workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
492
527
  }
493
528
  const parallelStartedAt = new Date().toISOString();
494
529
  await Promise.allSettled(workerSlots.map((slot) => provider.launch({
@@ -502,8 +537,10 @@ async function cmdRunToCompletion(argv) {
502
537
  stderrPath: slot.paths.stderrPath,
503
538
  uiMode,
504
539
  timeoutMs,
505
- model: resolveModelForTasks(slot.group, sessionConfig),
506
540
  })));
541
+ // Result ingestion is intentionally sequential even though agent launch
542
+ // was parallel. Writing to coverage_matrix.json is not atomic, so
543
+ // concurrent ingest calls would race and corrupt coverage state.
507
544
  let batchProgress = false;
508
545
  for (const slot of workerSlots) {
509
546
  const parallelEndedAt = new Date().toISOString();
@@ -520,8 +557,7 @@ async function cmdRunToCompletion(argv) {
520
557
  const warnings = issues.filter((issue) => issue.severity === "warning");
521
558
  if (warnings.length > 0) {
522
559
  process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
523
- formatAuditResultIssues(warnings) +
524
- "\n");
560
+ formatAuditResultIssues(warnings) + "\n");
525
561
  }
526
562
  if (errors.length > 0) {
527
563
  throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
@@ -626,9 +662,6 @@ async function cmdRunToCompletion(argv) {
626
662
  stderrPath: paths.stderrPath,
627
663
  uiMode,
628
664
  timeoutMs,
629
- model: pendingAuditTasks
630
- ? resolveModelForTasks(pendingAuditTasks, sessionConfig)
631
- : undefined,
632
665
  });
633
666
  const candidate = await readJsonFile(paths.resultPath);
634
667
  workerResult = isWorkerResult(candidate)
@@ -881,7 +914,11 @@ async function cmdValidate(argv) {
881
914
  const providerIssues = rawSessionConfig === undefined || sessionConfigIssues.length > 0
882
915
  ? []
883
916
  : prefixValidationIssues("session_config", validateConfiguredProviderEnvironment(rawSessionConfig));
884
- const issues = [...artifactIssues, ...sessionConfigIssues, ...providerIssues];
917
+ const issues = [
918
+ ...artifactIssues,
919
+ ...sessionConfigIssues,
920
+ ...providerIssues,
921
+ ];
885
922
  const resolvedProvider = rawSessionConfig === undefined
886
923
  ? "local-subprocess"
887
924
  : sessionConfigIssues.length > 0
@@ -1,3 +1,4 @@
1
+ import { isNodeModulesOrGit, isTestPath, isInterfacePath, isDataLayerPath, isSecuritySensitivePath, isConcurrencyPath, isScriptPath, isDeploymentConfigPath, isDocPath, isGeneratedPath, } from "./pathPatterns.js";
1
2
  function addBucket(buckets, rationale, bucket, reason) {
2
3
  if (!buckets.has(bucket)) {
3
4
  buckets.add(bucket);
@@ -8,57 +9,35 @@ export function bucketFile(path) {
8
9
  const normalized = path.toLowerCase();
9
10
  const buckets = new Set();
10
11
  const rationale = [];
11
- if (normalized.includes("test") ||
12
- normalized.includes("spec") ||
13
- normalized.includes("__tests__")) {
12
+ if (isNodeModulesOrGit(normalized)) {
13
+ addBucket(buckets, rationale, "generated_vendor", "node_modules or .git excluded by convention");
14
+ return { path, buckets: [...buckets], rationale };
15
+ }
16
+ if (isTestPath(normalized)) {
14
17
  addBucket(buckets, rationale, "tests", "path suggests tests");
15
18
  }
16
- if (normalized.includes("route") ||
17
- normalized.includes("controller") ||
18
- normalized.includes("handler") ||
19
- normalized.includes("api/")) {
19
+ if (isInterfacePath(normalized)) {
20
20
  addBucket(buckets, rationale, "interface", "path suggests interface code");
21
21
  }
22
- if (normalized.includes("model") ||
23
- normalized.includes("schema") ||
24
- normalized.includes("migration") ||
25
- normalized.includes("seed") ||
26
- normalized.includes("db/")) {
22
+ if (isDataLayerPath(normalized)) {
27
23
  addBucket(buckets, rationale, "data_layer", "path suggests data-layer code");
28
24
  }
29
- if (normalized.includes("auth") ||
30
- normalized.includes("secret") ||
31
- normalized.includes("token") ||
32
- normalized.includes("permission") ||
33
- normalized.includes("session")) {
25
+ if (isSecuritySensitivePath(normalized)) {
34
26
  addBucket(buckets, rationale, "security_sensitive", "path suggests security-sensitive code");
35
27
  }
36
- if (normalized.includes("queue") ||
37
- normalized.includes("worker") ||
38
- normalized.includes("job") ||
39
- normalized.includes("cache") ||
40
- normalized.includes("retry") ||
41
- normalized.includes("lock")) {
28
+ if (isConcurrencyPath(normalized)) {
42
29
  addBucket(buckets, rationale, "concurrency_state", "path suggests concurrency or stateful behavior");
43
30
  }
44
- if (normalized.includes("script") ||
45
- normalized.startsWith("scripts/") ||
46
- normalized.startsWith("bin/")) {
31
+ if (isScriptPath(normalized)) {
47
32
  addBucket(buckets, rationale, "tooling_scripts", "path suggests tooling or scripts");
48
33
  }
49
- if (normalized.includes("docker") ||
50
- normalized.includes("terraform") ||
51
- normalized.includes("deploy") ||
52
- normalized.includes("workflow") ||
53
- normalized.includes("k8s") ||
54
- normalized.endsWith(".yml") ||
55
- normalized.endsWith(".yaml")) {
34
+ if (isDeploymentConfigPath(normalized)) {
56
35
  addBucket(buckets, rationale, "config_deployment", "path suggests config or deployment artifact");
57
36
  }
58
- if (normalized.endsWith(".md") || normalized.startsWith("docs/")) {
37
+ if (isDocPath(normalized)) {
59
38
  addBucket(buckets, rationale, "docs_specs", "path suggests documentation");
60
39
  }
61
- if (normalized.includes("vendor") || normalized.includes("generated")) {
40
+ if (isGeneratedPath(normalized)) {
62
41
  addBucket(buckets, rationale, "generated_vendor", "path suggests generated or vendored content");
63
42
  }
64
43
  if (buckets.size === 0) {
@@ -1,24 +1,23 @@
1
+ import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isDocPath, } from "./pathPatterns.js";
1
2
  function inferDisposition(path) {
2
3
  const normalized = path.toLowerCase();
3
- if (normalized.startsWith("dist/") || normalized.startsWith("build/")) {
4
+ if (isNodeModulesOrGit(normalized)) {
5
+ return { path, status: "excluded", reason: "node_modules or .git excluded by convention." };
6
+ }
7
+ if (isBuildOutput(normalized)) {
4
8
  return { path, status: "generated", reason: "Build output path." };
5
9
  }
6
- if (normalized.includes("vendor") || normalized.includes("third_party")) {
10
+ if (isVendorPath(normalized)) {
7
11
  return { path, status: "vendor", reason: "Vendor or third-party path." };
8
12
  }
9
- if (normalized.endsWith(".png") ||
10
- normalized.endsWith(".jpg") ||
11
- normalized.endsWith(".jpeg") ||
12
- normalized.endsWith(".gif") ||
13
- normalized.endsWith(".pdf") ||
14
- normalized.endsWith(".zip")) {
13
+ if (isBinaryArtifact(normalized)) {
15
14
  return {
16
15
  path,
17
16
  status: "binary",
18
17
  reason: "Non-source binary-like artifact.",
19
18
  };
20
19
  }
21
- if (normalized.endsWith(".md") || normalized.startsWith("docs/")) {
20
+ if (isDocPath(normalized)) {
22
21
  return { path, status: "doc_only", reason: "Documentation artifact." };
23
22
  }
24
23
  return {
@@ -6,8 +6,6 @@ function inferLanguage(path) {
6
6
  return "typescript";
7
7
  case "js":
8
8
  case "jsx":
9
- case "mjs":
10
- case "cjs":
11
9
  return "javascript";
12
10
  case "py":
13
11
  return "python";
@@ -1,27 +1,18 @@
1
1
  import { isAuditExcludedStatus } from "./disposition.js";
2
+ import { isSecuritySensitivePath, isDataLayerPath, isConcurrencyPath, isInterfacePath, isDeploymentConfigPath, } from "./pathPatterns.js";
2
3
  function inferConcerns(paths) {
3
4
  const concerns = new Set();
4
5
  for (const path of paths) {
5
6
  const normalized = path.toLowerCase();
6
- if (normalized.includes("auth") ||
7
- normalized.includes("token") ||
8
- normalized.includes("session"))
7
+ if (isSecuritySensitivePath(normalized))
9
8
  concerns.add("security");
10
- if (normalized.includes("db") ||
11
- normalized.includes("model") ||
12
- normalized.includes("schema") ||
13
- normalized.includes("migration") ||
9
+ if (isDataLayerPath(normalized) ||
14
10
  normalized.includes("invoice") ||
15
11
  normalized.includes("payment"))
16
12
  concerns.add("data_integrity");
17
- if (normalized.includes("worker") ||
18
- normalized.includes("job") ||
19
- normalized.includes("retry") ||
20
- normalized.includes("queue"))
13
+ if (isConcurrencyPath(normalized))
21
14
  concerns.add("reliability");
22
- if (normalized.includes("api") ||
23
- normalized.includes("route") ||
24
- normalized.includes("controller"))
15
+ if (isInterfacePath(normalized))
25
16
  concerns.add("correctness");
26
17
  }
27
18
  return concerns.size > 0 ? [...concerns] : ["correctness"];
@@ -33,13 +24,15 @@ function relatedPaths(entry, availablePaths) {
33
24
  const lower = path.toLowerCase();
34
25
  if (path === entry)
35
26
  continue;
36
- if ((normalized.includes("auth") || normalized.includes("session")) &&
27
+ // Auth / session flows: link sibling auth, session, token, user paths
28
+ if (isSecuritySensitivePath(normalized) &&
37
29
  (lower.includes("auth") ||
38
30
  lower.includes("session") ||
39
31
  lower.includes("token") ||
40
32
  lower.includes("user"))) {
41
33
  linked.add(path);
42
34
  }
35
+ // Billing / payment flows: link ledger and subscription paths
43
36
  if ((normalized.includes("billing") ||
44
37
  normalized.includes("invoice") ||
45
38
  normalized.includes("payment")) &&
@@ -50,9 +43,8 @@ function relatedPaths(entry, availablePaths) {
50
43
  lower.includes("subscription"))) {
51
44
  linked.add(path);
52
45
  }
53
- if ((normalized.includes("job") ||
54
- normalized.includes("worker") ||
55
- normalized.includes("queue")) &&
46
+ // Async / queue flows: link worker, job, retry, and task paths
47
+ if (isConcurrencyPath(normalized) &&
56
48
  (lower.includes("queue") ||
57
49
  lower.includes("retry") ||
58
50
  lower.includes("worker") ||
@@ -60,7 +52,8 @@ function relatedPaths(entry, availablePaths) {
60
52
  lower.includes("task"))) {
61
53
  linked.add(path);
62
54
  }
63
- if ((normalized.includes("deploy") || normalized.includes("docker")) &&
55
+ // Deployment / infra flows: link docker, k8s, terraform, workflow paths
56
+ if (isDeploymentConfigPath(normalized) &&
64
57
  (lower.includes("deploy") ||
65
58
  lower.includes("docker") ||
66
59
  lower.includes("workflow") ||
@@ -75,7 +68,7 @@ function dedupeFlows(flows) {
75
68
  const seen = new Set();
76
69
  const deduped = [];
77
70
  for (const flow of flows) {
78
- const signature = `${flow.name}|${flow.paths.join(",")}|${flow.concerns.join(",")}`;
71
+ const signature = `${flow.name}|${[...flow.paths].sort().join(",")}|${[...flow.concerns].sort().join(",")}`;
79
72
  if (seen.has(signature))
80
73
  continue;
81
74
  seen.add(signature);
@@ -108,9 +101,7 @@ export function buildCriticalFlowManifest(repoManifest, surfaceManifest, disposi
108
101
  }
109
102
  for (const path of availablePaths) {
110
103
  const lower = path.toLowerCase();
111
- if (lower.includes("migration") ||
112
- lower.includes("schema") ||
113
- lower.includes("seed")) {
104
+ if (isDataLayerPath(lower) || lower.includes("seed")) {
114
105
  flows.push({
115
106
  id: `flow:data:${path.replace(/[^a-zA-Z0-9:_-]/g, "-")}`,
116
107
  name: `data evolution flow for ${path}`,
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Centralised path-pattern predicates shared across disposition, bucketing,
3
+ * surfaces, and flows extractors. Every function operates on the
4
+ * already-lower-cased form of the path.
5
+ */
6
+ export declare function isNodeModulesOrGit(normalized: string): boolean;
7
+ export declare function isBuildOutput(normalized: string): boolean;
8
+ export declare function isVendorPath(normalized: string): boolean;
9
+ export declare function isBinaryArtifact(normalized: string): boolean;
10
+ export declare function isDocPath(normalized: string): boolean;
11
+ export declare function isTestPath(normalized: string): boolean;
12
+ export declare function isInterfacePath(normalized: string): boolean;
13
+ export declare function isDataLayerPath(normalized: string): boolean;
14
+ export declare function isSecuritySensitivePath(normalized: string): boolean;
15
+ export declare function isConcurrencyPath(normalized: string): boolean;
16
+ export declare function isScriptPath(normalized: string): boolean;
17
+ export declare function isDeploymentConfigPath(normalized: string): boolean;
18
+ export declare function isGeneratedPath(normalized: string): boolean;
19
+ export declare function isSurfacePath(normalized: string): boolean;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Centralised path-pattern predicates shared across disposition, bucketing,
3
+ * surfaces, and flows extractors. Every function operates on the
4
+ * already-lower-cased form of the path.
5
+ */
6
+ export function isNodeModulesOrGit(normalized) {
7
+ return (normalized.startsWith("node_modules/") ||
8
+ normalized === "node_modules" ||
9
+ normalized.startsWith(".git/") ||
10
+ normalized === ".git");
11
+ }
12
+ export function isBuildOutput(normalized) {
13
+ return normalized.startsWith("dist/") || normalized.startsWith("build/");
14
+ }
15
+ export function isVendorPath(normalized) {
16
+ return normalized.includes("vendor") || normalized.includes("third_party");
17
+ }
18
+ export function isBinaryArtifact(normalized) {
19
+ return (normalized.endsWith(".png") ||
20
+ normalized.endsWith(".jpg") ||
21
+ normalized.endsWith(".jpeg") ||
22
+ normalized.endsWith(".gif") ||
23
+ normalized.endsWith(".pdf") ||
24
+ normalized.endsWith(".zip"));
25
+ }
26
+ export function isDocPath(normalized) {
27
+ return normalized.endsWith(".md") || normalized.startsWith("docs/");
28
+ }
29
+ export function isTestPath(normalized) {
30
+ return (normalized.includes("test") ||
31
+ normalized.includes("spec") ||
32
+ normalized.includes("__tests__"));
33
+ }
34
+ export function isInterfacePath(normalized) {
35
+ return (normalized.includes("route") ||
36
+ normalized.includes("controller") ||
37
+ normalized.includes("handler") ||
38
+ normalized.includes("api/"));
39
+ }
40
+ export function isDataLayerPath(normalized) {
41
+ return (normalized.includes("model") ||
42
+ normalized.includes("schema") ||
43
+ normalized.includes("migration") ||
44
+ normalized.includes("seed") ||
45
+ normalized.includes("db/"));
46
+ }
47
+ export function isSecuritySensitivePath(normalized) {
48
+ return (normalized.includes("auth") ||
49
+ normalized.includes("secret") ||
50
+ normalized.includes("token") ||
51
+ normalized.includes("permission") ||
52
+ normalized.includes("session"));
53
+ }
54
+ export function isConcurrencyPath(normalized) {
55
+ return (normalized.includes("queue") ||
56
+ normalized.includes("worker") ||
57
+ normalized.includes("job") ||
58
+ normalized.includes("cache") ||
59
+ normalized.includes("retry") ||
60
+ normalized.includes("lock"));
61
+ }
62
+ export function isScriptPath(normalized) {
63
+ return (normalized.includes("script") ||
64
+ normalized.startsWith("scripts/") ||
65
+ normalized.startsWith("bin/"));
66
+ }
67
+ export function isDeploymentConfigPath(normalized) {
68
+ return (normalized.includes("docker") ||
69
+ normalized.includes("terraform") ||
70
+ normalized.includes("deploy") ||
71
+ normalized.includes("workflow") ||
72
+ normalized.includes("k8s") ||
73
+ normalized.endsWith(".yml") ||
74
+ normalized.endsWith(".yaml"));
75
+ }
76
+ export function isGeneratedPath(normalized) {
77
+ return normalized.includes("vendor") || normalized.includes("generated");
78
+ }
79
+ export function isSurfacePath(normalized) {
80
+ return (normalized.includes("api/") ||
81
+ normalized.includes("route") ||
82
+ normalized.includes("controller") ||
83
+ normalized.includes("worker") ||
84
+ normalized.includes("job") ||
85
+ normalized.includes("command") ||
86
+ normalized.includes("cli"));
87
+ }
@@ -1,4 +1,5 @@
1
1
  import { isAuditExcludedStatus } from "./disposition.js";
2
+ import { isSurfacePath } from "./pathPatterns.js";
2
3
  function methodsForPath(path) {
3
4
  const normalized = path.toLowerCase();
4
5
  if (normalized.includes("api") || normalized.includes("route")) {
@@ -15,13 +16,7 @@ export function buildSurfaceManifest(repoManifest, disposition) {
15
16
  continue;
16
17
  }
17
18
  const normalized = file.path.toLowerCase();
18
- if (normalized.includes("api/") ||
19
- normalized.includes("route") ||
20
- normalized.includes("controller") ||
21
- normalized.includes("worker") ||
22
- normalized.includes("job") ||
23
- normalized.includes("command") ||
24
- normalized.includes("cli")) {
19
+ if (isSurfacePath(normalized)) {
25
20
  surfaces.push({
26
21
  id: `surface:${file.path}`,
27
22
  kind: normalized.includes("worker") || normalized.includes("job")
@@ -32,7 +32,29 @@ export interface ArtifactBundle {
32
32
  audit_state?: AuditState;
33
33
  artifact_metadata?: ArtifactMetadataManifest;
34
34
  }
35
- export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: Record<string, keyof ArtifactBundle>;
35
+ export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: {
36
+ readonly "repo_manifest.json": "repo_manifest";
37
+ readonly "file_disposition.json": "file_disposition";
38
+ readonly "auto_fixes_applied.json": "auto_fixes_applied";
39
+ readonly "unit_manifest.json": "unit_manifest";
40
+ readonly "graph_bundle.json": "graph_bundle";
41
+ readonly "surface_manifest.json": "surface_manifest";
42
+ readonly "critical_flows.json": "critical_flows";
43
+ readonly "flow_coverage.json": "flow_coverage";
44
+ readonly "risk_register.json": "risk_register";
45
+ readonly "coverage_matrix.json": "coverage_matrix";
46
+ readonly "runtime_validation_tasks.json": "runtime_validation_tasks";
47
+ readonly "runtime_validation_report.json": "runtime_validation_report";
48
+ readonly "external_analyzer_results.json": "external_analyzer_results";
49
+ readonly "audit_results.jsonl": "audit_results";
50
+ readonly "audit_tasks.json": "audit_tasks";
51
+ readonly "requeue_tasks.json": "requeue_tasks";
52
+ readonly "merged_findings.json": "merged_findings";
53
+ readonly "root_cause_clusters.json": "root_cause_clusters";
54
+ readonly "synthesis_report.json": "synthesis_report";
55
+ readonly "audit_state.json": "audit_state";
56
+ readonly "artifact_metadata.json": "artifact_metadata";
57
+ };
36
58
  export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
37
59
  export declare function loadArtifactBundle(root: string): Promise<ArtifactBundle>;
38
60
  export declare function writeCoreArtifacts(root: string, bundle: ArtifactBundle): Promise<void>;