auditor-lambda 0.10.3 → 0.10.7

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 (183) hide show
  1. package/audit-code-wrapper-build.mjs +198 -0
  2. package/audit-code-wrapper-install-hosts.mjs +1140 -0
  3. package/audit-code-wrapper-io.mjs +155 -0
  4. package/audit-code-wrapper-legacy.mjs +125 -0
  5. package/audit-code-wrapper-lib.mjs +17 -1801
  6. package/audit-code-wrapper-opencode.mjs +256 -0
  7. package/dispatch/merge-results.mjs +5 -3
  8. package/dispatch/validate-result.mjs +2 -2
  9. package/dist/adapters/coverageSummary.js +6 -2
  10. package/dist/adapters/normalizeExternal.js +16 -1
  11. package/dist/adapters/npmAudit.js +20 -9
  12. package/dist/adapters/semgrep.js +26 -1
  13. package/dist/cli/advanceAuditCommand.d.ts +1 -0
  14. package/dist/cli/advanceAuditCommand.js +95 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/auditStep.js +2 -2
  17. package/dist/cli/cleanup.d.ts +11 -1
  18. package/dist/cli/cleanup.js +25 -5
  19. package/dist/cli/cleanupCommand.d.ts +1 -0
  20. package/dist/cli/cleanupCommand.js +24 -0
  21. package/dist/cli/dispatch.d.ts +55 -31
  22. package/dist/cli/dispatch.js +298 -241
  23. package/dist/cli/dispatchStatusCommand.d.ts +1 -0
  24. package/dist/cli/dispatchStatusCommand.js +68 -0
  25. package/dist/cli/explainTaskCommand.d.ts +1 -0
  26. package/dist/cli/explainTaskCommand.js +33 -0
  27. package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
  28. package/dist/cli/importExternalAnalyzerCommand.js +20 -0
  29. package/dist/cli/ingestResultsCommand.d.ts +1 -0
  30. package/dist/cli/ingestResultsCommand.js +34 -0
  31. package/dist/cli/intakeCommand.d.ts +1 -0
  32. package/dist/cli/intakeCommand.js +17 -0
  33. package/dist/cli/lineIndex.js +19 -12
  34. package/dist/cli/nextStepCommand.d.ts +139 -0
  35. package/dist/cli/nextStepCommand.js +281 -232
  36. package/dist/cli/planCommand.d.ts +1 -0
  37. package/dist/cli/planCommand.js +16 -0
  38. package/dist/cli/prepareDispatchCommand.d.ts +1 -0
  39. package/dist/cli/prepareDispatchCommand.js +25 -0
  40. package/dist/cli/quotaCommand.d.ts +1 -0
  41. package/dist/cli/quotaCommand.js +56 -0
  42. package/dist/cli/requeueCommand.d.ts +1 -0
  43. package/dist/cli/requeueCommand.js +10 -0
  44. package/dist/cli/runToCompletion.js +451 -412
  45. package/dist/cli/sampleRunCommand.d.ts +1 -0
  46. package/dist/cli/sampleRunCommand.js +93 -0
  47. package/dist/cli/statusCommand.js +1 -1
  48. package/dist/cli/steps.js +4 -1
  49. package/dist/cli/submitPacketCommand.js +16 -15
  50. package/dist/cli/synthesizeCommand.d.ts +1 -0
  51. package/dist/cli/synthesizeCommand.js +15 -0
  52. package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
  53. package/dist/cli/updateRuntimeValidationCommand.js +16 -0
  54. package/dist/cli/validateCommand.d.ts +1 -0
  55. package/dist/cli/validateCommand.js +41 -0
  56. package/dist/cli/validateResultCommand.d.ts +1 -0
  57. package/dist/cli/validateResultCommand.js +63 -0
  58. package/dist/cli/validateResultsCommand.d.ts +1 -0
  59. package/dist/cli/validateResultsCommand.js +31 -0
  60. package/dist/cli/workerRunCommand.d.ts +15 -1
  61. package/dist/cli/workerRunCommand.js +40 -4
  62. package/dist/cli.d.ts +3 -2
  63. package/dist/cli.js +21 -628
  64. package/dist/coverage.js +7 -3
  65. package/dist/extractors/analyzers/css.js +2 -2
  66. package/dist/extractors/analyzers/html.js +2 -2
  67. package/dist/extractors/analyzers/python.js +2 -2
  68. package/dist/extractors/analyzers/registry.js +17 -36
  69. package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
  70. package/dist/extractors/analyzers/treeSitter.js +28 -6
  71. package/dist/extractors/analyzers/typescript.js +104 -85
  72. package/dist/extractors/browserExtension.js +4 -1
  73. package/dist/extractors/designAssessment.js +21 -21
  74. package/dist/extractors/fsIntake.js +34 -10
  75. package/dist/extractors/graph.js +17 -7
  76. package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
  77. package/dist/extractors/graphManifestEdges/cargo.js +107 -0
  78. package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
  79. package/dist/extractors/graphManifestEdges/go.js +151 -0
  80. package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
  81. package/dist/extractors/graphManifestEdges/index.js +11 -0
  82. package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
  83. package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
  84. package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
  85. package/dist/extractors/graphManifestEdges/maven.js +73 -0
  86. package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
  87. package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
  88. package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
  89. package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
  90. package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
  91. package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
  92. package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
  93. package/dist/extractors/graphManifestEdges/toml.js +68 -0
  94. package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
  95. package/dist/extractors/graphManifestEdges/typescript.js +56 -0
  96. package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
  97. package/dist/extractors/graphManifestEdges/workspace.js +72 -0
  98. package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
  99. package/dist/extractors/graphManifestEdges/yaml.js +59 -0
  100. package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
  101. package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
  102. package/dist/extractors/graphPythonImports.js +4 -20
  103. package/dist/extractors/pathPatterns.js +3 -13
  104. package/dist/io/artifacts.d.ts +1 -1
  105. package/dist/io/artifacts.js +4 -1
  106. package/dist/io/runArtifacts.d.ts +8 -2
  107. package/dist/io/runArtifacts.js +103 -69
  108. package/dist/io/toolingManifest.js +2 -1
  109. package/dist/orchestrator/advance.js +36 -0
  110. package/dist/orchestrator/artifactFreshness.d.ts +1 -1
  111. package/dist/orchestrator/artifactFreshness.js +1 -1
  112. package/dist/orchestrator/artifactMetadata.js +5 -5
  113. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  114. package/dist/orchestrator/auditTaskUtils.js +8 -12
  115. package/dist/orchestrator/autoFixExecutor.js +40 -26
  116. package/dist/orchestrator/dependencyMap.js +1 -1
  117. package/dist/orchestrator/executorResult.d.ts +33 -0
  118. package/dist/orchestrator/executors.d.ts +7 -0
  119. package/dist/orchestrator/executors.js +24 -0
  120. package/dist/orchestrator/fileAnchors.js +42 -29
  121. package/dist/orchestrator/fileIntegrity.js +6 -1
  122. package/dist/orchestrator/flowCoverage.js +1 -2
  123. package/dist/orchestrator/flowPlanning.js +8 -4
  124. package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
  125. package/dist/orchestrator/ingestionExecutors.js +9 -1
  126. package/dist/orchestrator/intakeExecutors.d.ts +0 -4
  127. package/dist/orchestrator/intakeExecutors.js +24 -14
  128. package/dist/orchestrator/localCommands.d.ts +1 -0
  129. package/dist/orchestrator/localCommands.js +10 -17
  130. package/dist/orchestrator/nextStep.js +3 -1
  131. package/dist/orchestrator/requeueCommand.js +4 -0
  132. package/dist/orchestrator/reviewPacketGraph.js +50 -18
  133. package/dist/orchestrator/reviewPackets.js +10 -8
  134. package/dist/orchestrator/runtimeCommand.js +35 -7
  135. package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
  136. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
  137. package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
  138. package/dist/orchestrator/staleness.js +3 -3
  139. package/dist/orchestrator/state.js +1 -1
  140. package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
  141. package/dist/orchestrator/synthesisExecutors.js +1 -0
  142. package/dist/orchestrator/taskBuilder.js +5 -4
  143. package/dist/providers/claudeCodeProvider.js +4 -1
  144. package/dist/providers/opencodeProvider.js +4 -1
  145. package/dist/quota/discoveredLimits.js +3 -3
  146. package/dist/quota/headerExtraction.js +5 -2
  147. package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
  148. package/dist/quota/headerExtractors/index.js +3 -3
  149. package/dist/quota/index.d.ts +3 -1
  150. package/dist/quota/index.js +3 -0
  151. package/dist/reporting/findingRanks.d.ts +3 -0
  152. package/dist/reporting/findingRanks.js +24 -0
  153. package/dist/reporting/mergeFindings.js +1 -24
  154. package/dist/reporting/synthesis.d.ts +3 -1
  155. package/dist/reporting/synthesis.js +30 -6
  156. package/dist/reporting/synthesisNarrativePrompt.js +3 -0
  157. package/dist/reporting/workBlocks.js +1 -14
  158. package/dist/supervisor/operatorHandoff.js +2 -6
  159. package/dist/supervisor/runLedger.js +30 -41
  160. package/dist/types/activeDispatch.d.ts +31 -0
  161. package/dist/types/activeDispatch.js +2 -0
  162. package/dist/types.d.ts +21 -4
  163. package/dist/types.js +24 -16
  164. package/dist/validation/artifacts.js +3 -0
  165. package/dist/validation/auditResults.js +8 -2
  166. package/package.json +2 -2
  167. package/schemas/audit_findings.schema.json +5 -1
  168. package/schemas/audit_plan_metrics.schema.json +1 -1
  169. package/schemas/audit_result.schema.json +5 -6
  170. package/schemas/audit_task.schema.json +1 -4
  171. package/schemas/blind_spot_register.schema.json +1 -1
  172. package/schemas/coverage_matrix.schema.json +2 -8
  173. package/schemas/finding.schema.json +1 -16
  174. package/schemas/flow_coverage.schema.json +2 -8
  175. package/schemas/graph_bundle.schema.json +31 -0
  176. package/schemas/lens.schema.json +7 -0
  177. package/schemas/review_packets.schema.json +6 -17
  178. package/schemas/step_contract.schema.json +8 -2
  179. package/schemas/unit_manifest.schema.json +1 -4
  180. package/scripts/postinstall.mjs +3 -1
  181. package/skills/audit-code/audit-code.prompt.md +2 -3
  182. package/dist/extractors/graphManifestEdges.d.ts +0 -12
  183. package/dist/extractors/graphManifestEdges.js +0 -1135
@@ -4,10 +4,6 @@ import { isGitRepo, writeJsonFile } from "@audit-tools/shared";
4
4
  import { buildFileDisposition, isAuditExcludedStatus, } from "../extractors/disposition.js";
5
5
  import { buildRepoManifestFromFs } from "../extractors/fsIntake.js";
6
6
  import { loadIgnoreFile } from "../extractors/ignore.js";
7
- /** Prefix used to carry the scope summary inside `progress_summary` for hosts
8
- * that read the step's progress text rather than the `scope_summary.json`
9
- * artifact. The loader extracts everything after this marker as JSON. */
10
- export const SCOPE_SUMMARY_PREFIX = "SCOPE_SUMMARY:";
11
7
  /**
12
8
  * Detect signals that the resolved audit root may be the *wrong* directory.
13
9
  * Two heuristics, returned as zero or more human-readable warnings:
@@ -37,14 +33,30 @@ export function detectMisScopeSmells(root) {
37
33
  }
38
34
  }
39
35
  // (b) Workspace member of a parent monorepo.
36
+ // Walk up ancestor directories (up to 3 levels) looking for a package.json
37
+ // that declares a `workspaces` field. This handles standard nested monorepo
38
+ // layouts like `monorepo-root/packages/my-pkg` where the direct parent
39
+ // (`packages/`) has no package.json of its own. Stop early if a .git
40
+ // boundary is found (we already checked that case under heuristic (a)).
40
41
  const rootPkg = readPackageJson(root);
41
42
  if (rootPkg && rootPkg.name !== undefined) {
42
- const parent = dirname(root);
43
- if (parent && parent !== root) {
44
- const parentPkg = readPackageJson(parent);
45
- if (parentPkg && parentPkg.workspaces !== undefined) {
46
- smells.push(`root appears to be a workspace member of a parent monorepo at '${parent}' — consider auditing from the monorepo root instead`);
43
+ let current = dirname(root);
44
+ let previous = root;
45
+ let levelsChecked = 0;
46
+ const maxLevels = 3;
47
+ while (current && current !== previous && levelsChecked < maxLevels) {
48
+ const ancestorPkg = readPackageJson(current);
49
+ if (ancestorPkg && ancestorPkg.workspaces !== undefined) {
50
+ smells.push(`root appears to be a workspace member of a parent monorepo at '${current}' — consider auditing from the monorepo root instead`);
51
+ break;
52
+ }
53
+ // Stop at a .git boundary — the repo root won't be a workspaces ancestor.
54
+ if (existsSync(join(current, ".git"))) {
55
+ break;
47
56
  }
57
+ previous = current;
58
+ current = dirname(current);
59
+ levelsChecked++;
48
60
  }
49
61
  }
50
62
  return smells;
@@ -84,15 +96,13 @@ export async function runIntakeExecutor(bundle, root, artifactsDir) {
84
96
  };
85
97
  const artifactsWritten = ["repo_manifest.json", "file_disposition.json"];
86
98
  // Persist the scope summary alongside the other intake artifacts when we know
87
- // where the artifacts directory is. The typed `scope_summary` field and the
88
- // progress_summary marker below carry the same data for hosts that don't read
89
- // the file directly.
99
+ // where the artifacts directory is. Hosts read scope_summary.json directly;
100
+ // the typed `scope_summary` field on ExecutorRunResult is the in-process channel.
90
101
  if (artifactsDir) {
91
102
  await writeJsonFile(join(artifactsDir, "scope_summary.json"), scopeSummary);
92
103
  artifactsWritten.push("scope_summary.json");
93
104
  }
94
- const progressSummary = `${SCOPE_SUMMARY_PREFIX}${JSON.stringify(scopeSummary)}\n` +
95
- `Created intake artifacts for ${repoManifest.files.length} files ` +
105
+ const progressSummary = `Created intake artifacts for ${repoManifest.files.length} files ` +
96
106
  `(${auditableCount} auditable). Scope: ${root}, git: ${scopeSummary.git_available ? "yes" : "no"}` +
97
107
  (scopeSummary.mis_scope_smells.length > 0
98
108
  ? `; ${scopeSummary.mis_scope_smells.length} mis-scope warning(s)`
@@ -10,5 +10,6 @@ export interface LocalCommandResult {
10
10
  stderr: string;
11
11
  error?: Error;
12
12
  }
13
+ export declare function __resolveFromPathForTests(command: string): string | null;
13
14
  export declare function runFirstAvailableCommand(root: string, candidates: LocalCommandCandidate[]): LocalCommandResult | null;
14
15
  export declare function resolveNodeTool(root: string, relativePath: string, args: string[], display: string): LocalCommandCandidate[];
@@ -9,6 +9,9 @@ function toSpawnTuple(candidate) {
9
9
  const resolved = resolveExecArgv([candidate.command, ...candidate.args]);
10
10
  return { command: resolved[0], args: resolved.slice(1) };
11
11
  }
12
+ export function __resolveFromPathForTests(command) {
13
+ return resolveFromPath(command);
14
+ }
12
15
  function resolveFromPath(command) {
13
16
  if (command.trim().length === 0) {
14
17
  return null;
@@ -30,24 +33,14 @@ function resolveFromPath(command) {
30
33
  .filter((ext) => ext.length > 0)
31
34
  .map((ext) => (ext.startsWith(".") ? ext : `.${ext}`))
32
35
  : [""];
36
+ // On Win32 without an extension: probe PATHEXT extensions first, then the
37
+ // bare name (empty-string suffix). On Win32 with an extension, or non-Win32:
38
+ // use only the bare name (extensions is already [''] on non-Win32).
39
+ const effectiveExtensions = process.platform === "win32" && extname(command).length === 0
40
+ ? [...extensions, ""]
41
+ : [""];
33
42
  for (const dir of pathEntries) {
34
- const directPath = join(dir, command);
35
- if (process.platform === "win32" && extname(command).length === 0) {
36
- for (const ext of extensions) {
37
- const candidatePath = join(dir, `${command}${ext}`);
38
- if (existsSync(candidatePath)) {
39
- return candidatePath;
40
- }
41
- }
42
- if (existsSync(directPath)) {
43
- return directPath;
44
- }
45
- continue;
46
- }
47
- if (existsSync(directPath)) {
48
- return directPath;
49
- }
50
- for (const ext of extensions) {
43
+ for (const ext of effectiveExtensions) {
51
44
  const candidatePath = join(dir, `${command}${ext}`);
52
45
  if (existsSync(candidatePath)) {
53
46
  return candidatePath;
@@ -53,6 +53,8 @@ export function decideNextStep(bundle) {
53
53
  state,
54
54
  selected_obligation: next.id,
55
55
  selected_executor: executor?.id ?? null,
56
- reason: `Selected highest-priority actionable obligation ${next.id}.`,
56
+ reason: executor
57
+ ? `Selected highest-priority actionable obligation ${next.id}.`
58
+ : `No executor found for obligation ${next.id}; EXECUTOR_REGISTRY has no entry for this obligation ID. This is a configuration gap — the obligation was selected but cannot be dispatched.`,
57
59
  };
58
60
  }
@@ -26,10 +26,14 @@ function dedupeByScope(tasks) {
26
26
  return deduped;
27
27
  }
28
28
  export function buildRequeuePayload(matrix, criticalFlows, flowCoverage, externalAnalyzerResults) {
29
+ // Dedupe within each source first — each generator may emit duplicate task_ids independently.
29
30
  const fileTasks = dedupeTasks(buildRequeueTasks(matrix, externalAnalyzerResults));
30
31
  const flowTasks = criticalFlows && flowCoverage
31
32
  ? dedupeTasks(buildFlowRequeueTasks(criticalFlows, flowCoverage, matrix, externalAnalyzerResults))
32
33
  : [];
34
+ // Two-pass post-merge dedup:
35
+ // 1. dedupeTasks removes any cross-source task_id collisions (same task emitted by both generators).
36
+ // 2. dedupeByScope removes tasks that cover the same lens+file_paths scope but carry different task_ids.
33
37
  const tasks = dedupeByScope(dedupeTasks([...fileTasks, ...flowTasks]));
34
38
  return {
35
39
  task_count: tasks.length,
@@ -202,26 +202,58 @@ export function unionFindFromGroups(groups, graphEdges) {
202
202
  const uf = new UnionFind(groups.keys());
203
203
  const fileToGroupKeys = buildFileToGroupKeys(groups);
204
204
  const degreeIndex = buildGraphDegreeIndex(graphEdges);
205
+ const verbose = Boolean(process.env.AUDIT_CODE_VERBOSE);
205
206
  for (const keys of fileToGroupKeys.values()) {
206
207
  const [first, ...rest] = [...keys].sort((a, b) => a.localeCompare(b));
207
208
  if (!first)
208
209
  continue;
209
210
  for (const key of rest) {
210
- uf.union(first, key);
211
+ if (verbose) {
212
+ const rootBefore = uf.find(key);
213
+ const rootFirst = uf.find(first);
214
+ uf.union(first, key);
215
+ if (rootFirst !== rootBefore) {
216
+ process.stderr.write(`[audit-code:packet-planning] shared-file merge: "${first}" + "${key}" (roots "${rootFirst}" + "${rootBefore}" → "${uf.find(first)}")\n`);
217
+ }
218
+ }
219
+ else {
220
+ uf.union(first, key);
221
+ }
211
222
  }
212
223
  }
213
224
  for (const edge of graphEdges) {
225
+ const fromGroups = fileToGroupKeys.get(normalizeGraphPath(edge.from));
226
+ const toGroups = fileToGroupKeys.get(normalizeGraphPath(edge.to));
214
227
  if (!isPacketExpansionEdge(edge, degreeIndex)) {
228
+ if (verbose && fromGroups && toGroups) {
229
+ // Edge has group mappings but was filtered — check if it was the
230
+ // high fan-degree guard specifically.
231
+ const fromFanOut = degreeIndex.fanOut.get(normalizeGraphPath(edge.from)) ?? 0;
232
+ const toFanIn = degreeIndex.fanIn.get(normalizeGraphPath(edge.to)) ?? 0;
233
+ const highFanEdge = fromFanOut > HIGH_FAN_DEGREE_THRESHOLD ||
234
+ toFanIn > HIGH_FAN_DEGREE_THRESHOLD;
235
+ if (highFanEdge) {
236
+ process.stderr.write(`[audit-code:packet-planning] edge skip (high-fan-degree): "${edge.from}" → "${edge.to}" (fanOut=${fromFanOut}, fanIn=${toFanIn})\n`);
237
+ }
238
+ }
215
239
  continue;
216
240
  }
217
- const fromGroups = fileToGroupKeys.get(normalizeGraphPath(edge.from));
218
- const toGroups = fileToGroupKeys.get(normalizeGraphPath(edge.to));
219
241
  if (!fromGroups || !toGroups) {
220
242
  continue;
221
243
  }
222
244
  for (const fromKey of fromGroups) {
223
245
  for (const toKey of toGroups) {
224
- uf.union(fromKey, toKey);
246
+ if (verbose) {
247
+ const rootFrom = uf.find(fromKey);
248
+ const rootTo = uf.find(toKey);
249
+ uf.union(fromKey, toKey);
250
+ if (rootFrom !== rootTo) {
251
+ process.stderr.write(`[audit-code:packet-planning] edge-driven merge: "${fromKey}" + "${toKey}" via edge "${edge.from}" → "${edge.to}" (kind=${edge.kind ?? "unknown"})\n`);
252
+ }
253
+ }
254
+ else {
255
+ uf.union(fromKey, toKey);
256
+ }
225
257
  }
226
258
  }
227
259
  }
@@ -588,20 +620,20 @@ function buildEntrypointFlowBridgeEdges(groups, graphEdges, graphBundle) {
588
620
  return [...bridgeEdges.values()].sort(compareGraphEdges);
589
621
  }
590
622
  export function buildPlanningGraphEdges(groups, graphEdges, graphBundle, lineIndex, sizeIndex, targetPacketTokens = DEFAULT_TARGET_PACKET_TOKENS) {
591
- const bridgeEdges = buildEntrypointFlowBridgeEdges(groups, graphEdges, graphBundle);
592
- const graphWithBridges = bridgeEdges.length > 0 ? [...graphEdges, ...bridgeEdges] : graphEdges;
593
- const subsystemEdges = buildSubsystemClusterEdges(groups, graphWithBridges, lineIndex, sizeIndex, targetPacketTokens);
594
- const graphWithSubsystems = subsystemEdges.length > 0
595
- ? [...graphWithBridges, ...subsystemEdges]
596
- : graphWithBridges;
597
- const packageOwnershipEdges = buildPackageOwnershipClusterEdges(groups, graphWithSubsystems, lineIndex, sizeIndex, targetPacketTokens);
598
- const graphWithPackageOwnership = packageOwnershipEdges.length > 0
599
- ? [...graphWithSubsystems, ...packageOwnershipEdges]
600
- : graphWithSubsystems;
601
- const moduleOwnershipEdges = buildModuleOwnershipClusterEdges(groups, graphWithPackageOwnership, lineIndex, sizeIndex, targetPacketTokens);
602
- return moduleOwnershipEdges.length > 0
603
- ? [...graphWithPackageOwnership, ...moduleOwnershipEdges]
604
- : graphWithPackageOwnership;
623
+ let edges = graphEdges;
624
+ const bridgeEdges = buildEntrypointFlowBridgeEdges(groups, edges, graphBundle);
625
+ if (bridgeEdges.length > 0)
626
+ edges = [...edges, ...bridgeEdges];
627
+ const subsystemEdges = buildSubsystemClusterEdges(groups, edges, lineIndex, sizeIndex, targetPacketTokens);
628
+ if (subsystemEdges.length > 0)
629
+ edges = [...edges, ...subsystemEdges];
630
+ const packageOwnershipEdges = buildPackageOwnershipClusterEdges(groups, edges, lineIndex, sizeIndex, targetPacketTokens);
631
+ if (packageOwnershipEdges.length > 0)
632
+ edges = [...edges, ...packageOwnershipEdges];
633
+ const moduleOwnershipEdges = buildModuleOwnershipClusterEdges(groups, edges, lineIndex, sizeIndex, targetPacketTokens);
634
+ if (moduleOwnershipEdges.length > 0)
635
+ edges = [...edges, ...moduleOwnershipEdges];
636
+ return edges;
605
637
  }
606
638
  function compareGraphEdges(a, b) {
607
639
  const confidenceDelta = graphEdgeConfidence(b) - graphEdgeConfidence(a);
@@ -3,6 +3,7 @@ import { LENS_ORDER, priorityRank, sortLenses } from "./auditTaskUtils.js";
3
3
  import { normalizeGraphPath } from "../extractors/graphPathUtils.js";
4
4
  import { DEFAULT_MAX_TASKS_PER_PACKET, DEFAULT_TARGET_PACKET_TOKENS, ESTIMATED_TOKENS_PER_LINE, ESTIMATED_PACKET_PROMPT_TOKENS, sizeIndexFromManifest, fileGroupContentTokens, taskContentTokens, estimateTaskGroupTokens, } from "./reviewPacketSizing.js";
5
5
  import { HIGH_FAN_DEGREE_THRESHOLD, collectGraphEdges, buildGraphDegreeIndex, isPacketExpansionEdge, buildFileToGroupKeys, isConcreteGraphEdge, unionFindFromGroups, roundQuality, buildPlanningGraphEdges, buildPacketGraphContext, } from "./reviewPacketGraph.js";
6
+ import { sanitizeSegment } from "./selectiveDeepening/shared.js";
6
7
  // Re-exported for scope.ts, which imports the canonical path normalizer here.
7
8
  export { normalizeGraphPath };
8
9
  // Sizing / token-budget arithmetic moved to reviewPacketSizing.ts; re-exported
@@ -42,12 +43,6 @@ function buildTaskGroups(tasks) {
42
43
  }
43
44
  return groups;
44
45
  }
45
- function sanitizeSegment(value) {
46
- const sanitized = value
47
- .replace(/[^a-zA-Z0-9_-]+/g, "-")
48
- .replace(/^-+|-+$/g, "");
49
- return sanitized.length > 0 ? sanitized : "packet";
50
- }
51
46
  function packetIdFor(tasks, packetIndex) {
52
47
  const unit = sanitizeSegment(tasks[0]?.unit_id ?? "review");
53
48
  const lenses = sortLenses(tasks.map((task) => task.lens)).join("-");
@@ -75,15 +70,19 @@ function comparePackets(a, b) {
75
70
  function chunkPacketTasks(tasks, options) {
76
71
  const chunks = [];
77
72
  let current = [];
73
+ const verbose = Boolean(process.env.AUDIT_CODE_VERBOSE);
78
74
  for (const task of tasks.sort(compareTasksForPacket)) {
75
+ const taskEstimatedTokens = taskContentTokens(task, options.sizeIndex, options.lineIndex);
79
76
  const isolatedLargeFileTask = task.file_paths.length === 1 &&
80
- taskContentTokens(task, options.sizeIndex, options.lineIndex) >
81
- options.targetPacketTokens;
77
+ taskEstimatedTokens > options.targetPacketTokens;
82
78
  if (isolatedLargeFileTask) {
83
79
  if (current.length > 0) {
84
80
  chunks.push(current);
85
81
  current = [];
86
82
  }
83
+ if (verbose) {
84
+ process.stderr.write(`[audit-code:packet-planning] isolated large-file chunk: task="${task.task_id}" file="${task.file_paths[0]}" estimatedTokens=${taskEstimatedTokens} targetPacketTokens=${options.targetPacketTokens}\n`);
85
+ }
87
86
  chunks.push([task]);
88
87
  continue;
89
88
  }
@@ -93,6 +92,9 @@ function chunkPacketTasks(tasks, options) {
93
92
  const wouldExceedTaskCount = options.maxTasksPerPacket > 0 && current.length > 0 && candidate.length > options.maxTasksPerPacket;
94
93
  const wouldExceedTokens = current.length > 0 && candidateContentTokens > options.targetPacketTokens;
95
94
  if (wouldExceedTaskCount || wouldExceedTokens) {
95
+ if (verbose && wouldExceedTokens) {
96
+ process.stderr.write(`[audit-code:packet-planning] token-budget split: task="${task.task_id}" file="${task.file_paths[0] ?? ""}" candidateContentTokens=${candidateContentTokens} targetPacketTokens=${options.targetPacketTokens}\n`);
97
+ }
96
98
  chunks.push(current);
97
99
  current = [];
98
100
  }
@@ -32,21 +32,49 @@ export async function runCommand(command, cwd, options = {}) {
32
32
  child.stderr.on("data", (chunk) => {
33
33
  stderr += String(chunk);
34
34
  });
35
+ let exitCode = null;
36
+ let exitSignal = null;
35
37
  child.on("error", (error) => {
38
+ const output = `${stdout}\n${stderr}`.trim();
39
+ const lines = output.length > 0 ? output.split(/\r?\n/) : [];
40
+ const truncated = lines.length > 10;
41
+ const evidence = truncated
42
+ ? [`[... truncated: showing last 10 of ${lines.length} lines ...]`, ...lines.slice(-10)]
43
+ : lines;
36
44
  resolve({
37
45
  status: "inconclusive",
38
46
  summary: `Failed to execute ${displayCommand}: ${error.message}`,
39
- evidence: [],
47
+ evidence,
40
48
  });
41
49
  });
42
- child.on("exit", (code) => {
50
+ child.on("exit", (code, signal) => {
51
+ exitCode = code;
52
+ exitSignal = signal;
53
+ });
54
+ child.on("close", () => {
43
55
  const output = `${stdout}\n${stderr}`.trim();
44
- const evidence = output.length > 0 ? output.split(/\r?\n/).slice(-10) : [];
56
+ const lines = output.length > 0 ? output.split(/\r?\n/) : [];
57
+ const truncated = lines.length > 10;
58
+ const evidence = truncated
59
+ ? [`[... truncated: showing last 10 of ${lines.length} lines ...]`, ...lines.slice(-10)]
60
+ : lines;
61
+ const succeeded = exitCode === 0;
62
+ let summary;
63
+ if (succeeded) {
64
+ summary = `Deterministic runtime command succeeded: ${displayCommand}`;
65
+ }
66
+ else if (exitCode !== null) {
67
+ summary = `Deterministic runtime command failed with exit code ${exitCode}: ${displayCommand}`;
68
+ }
69
+ else if (exitSignal !== null) {
70
+ summary = `Deterministic runtime command terminated by signal ${exitSignal}: ${displayCommand}`;
71
+ }
72
+ else {
73
+ summary = `Deterministic runtime command exited with unknown status: ${displayCommand}`;
74
+ }
45
75
  resolve({
46
- status: code === 0 ? "confirmed" : "not_confirmed",
47
- summary: code === 0
48
- ? `Deterministic runtime command succeeded: ${displayCommand}`
49
- : `Deterministic runtime command failed with exit code ${code}: ${displayCommand}`,
76
+ status: succeeded ? "confirmed" : "not_confirmed",
77
+ summary,
50
78
  evidence,
51
79
  });
52
80
  });
@@ -8,14 +8,17 @@ function normalizeResult(result) {
8
8
  export function updateRuntimeValidationReport(tasks, existing, updates) {
9
9
  const validTaskIds = new Set(tasks.tasks.map((task) => task.id));
10
10
  const merged = new Map();
11
+ const staleIds = new Set();
11
12
  for (const result of existing.results) {
12
13
  if (!validTaskIds.has(result.task_id)) {
14
+ staleIds.add(result.task_id);
13
15
  continue;
14
16
  }
15
17
  merged.set(result.task_id, normalizeResult(result));
16
18
  }
17
19
  for (const update of updates.results) {
18
20
  if (!validTaskIds.has(update.task_id)) {
21
+ staleIds.add(update.task_id);
19
22
  continue;
20
23
  }
21
24
  const prior = merged.get(update.task_id);
@@ -33,6 +36,9 @@ export function updateRuntimeValidationReport(tasks, existing, updates) {
33
36
  notes: [...new Set([...(prior.notes ?? []), ...(update.notes ?? [])])],
34
37
  });
35
38
  }
39
+ if (staleIds.size > 0) {
40
+ console.warn("[runtimeValidationUpdate] Dropped %d stale result(s) not in task manifest: %s", staleIds.size, [...staleIds].join(", "));
41
+ }
36
42
  for (const task of tasks.tasks) {
37
43
  if (!merged.has(task.id)) {
38
44
  merged.set(task.id, {
@@ -18,8 +18,9 @@ export function isHighRiskCleanResult(result, task) {
18
18
  return result.requires_followup === true && task.priority === "medium";
19
19
  }
20
20
  export function buildHighRiskCleanFollowupTask(params) {
21
- const paths = uniqueSorted((params.task?.file_paths.length ?? 0) > 0
22
- ? (params.task?.file_paths ?? [])
21
+ const taskFilePaths = params.task?.file_paths;
22
+ const paths = uniqueSorted(taskFilePaths && taskFilePaths.length > 0
23
+ ? taskFilePaths
23
24
  : params.result.file_coverage.map((coverage) => coverage.path));
24
25
  return {
25
26
  task_id: taskIdFor("clean", [params.result.task_id, params.result.lens]),
@@ -1,5 +1,15 @@
1
1
  import { isHighRiskCleanResult } from "./highRiskClean.js";
2
2
  import { DEEPENING_TAG, IMPORTANT_LENS_VERIFICATION_LENSES, LENS_VERIFICATION_TAG, MAX_LENS_VERIFICATION_FILES, MAX_LENS_VERIFICATION_RESULT_SUMMARIES, SEVERITY_RANK, formatList, getExternalAnalyzerPaths, isDeepeningTask, isLensVerificationTask, lineCountForPath, lineCountFromSources, priorityLabel, priorityRank, taskIdFor, uniqueSorted, } from "./shared.js";
3
+ /** Score boost for files touched by a critical-flow task — highest semantic signal. */
4
+ const SCORE_CRITICAL_FLOW = 6;
5
+ /** Score boost for files flagged by an external analyzer tool — treated equally to critical-flow signal. */
6
+ const SCORE_EXTERNAL_ANALYZER_SIGNAL = 6;
7
+ /** Score boost for files from a large-file task — moderately elevated scrutiny. */
8
+ const SCORE_LARGE_FILE = 4;
9
+ /** Score boost for a high-risk task whose result was suspiciously clean — warrants re-examination. */
10
+ const SCORE_HIGH_RISK_CLEAN = 5;
11
+ /** Score boost for files directly matched by an external-analyzer path set — strongest single boost, above tag signals. */
12
+ const SCORE_EXTERNAL_ANALYZER_PATH_MATCH = 8;
3
13
  function sourceTaskIds(sources) {
4
14
  return uniqueSorted(sources.map((source) => source.result.task_id));
5
15
  }
@@ -16,12 +26,16 @@ function lensVerificationTriggers(params) {
16
26
  const cleanResults = params.sources.filter((source) => source.result.findings.length === 0 &&
17
27
  source.result.requires_followup !== false);
18
28
  const highRiskCleanResults = params.sources.filter((source) => isHighRiskCleanResult(source.result, source.task));
29
+ const pathOwnerMap = new Map();
30
+ for (const source of params.sources) {
31
+ for (const path of resultFiles(source)) {
32
+ if (!pathOwnerMap.has(path))
33
+ pathOwnerMap.set(path, source);
34
+ }
35
+ }
19
36
  const totalLines = filePaths.reduce((sum, path) => {
20
- const owner = params.sources.find((source) => resultFiles(source).includes(path));
21
- return (sum +
22
- (owner
23
- ? lineCountForPath(path, owner.task, owner.result)
24
- : 0));
37
+ const owner = pathOwnerMap.get(path);
38
+ return sum + (owner ? lineCountForPath(path, owner.task, owner.result) : 0);
25
39
  }, 0);
26
40
  const triggers = [];
27
41
  if (params.sources.some((source) => source.task?.priority === "high")) {
@@ -90,7 +104,7 @@ function shouldBuildLensVerificationTask(params) {
90
104
  const candidateId = taskIdFor("steward", [params.lens, ...sourceSignature]);
91
105
  return !params.existingTasks.some((task) => task.task_id === candidateId);
92
106
  }
93
- function selectLensVerificationFiles(sources, externalAnalyzerPaths) {
107
+ function selectLensVerificationFiles(sources, externalAnalyzerPaths, lens) {
94
108
  const scores = new Map();
95
109
  function add(path, score, lines) {
96
110
  const current = scores.get(path) ?? { score: 0, lines };
@@ -104,13 +118,13 @@ function selectLensVerificationFiles(sources, externalAnalyzerPaths) {
104
118
  for (const path of resultFiles(source)) {
105
119
  add(path, priorityScore, lineCountForPath(path, source.task, source.result));
106
120
  if (source.task?.tags?.includes("critical_flow"))
107
- add(path, 6, 0);
121
+ add(path, SCORE_CRITICAL_FLOW, 0);
108
122
  if (source.task?.tags?.includes("external_analyzer_signal"))
109
- add(path, 6, 0);
123
+ add(path, SCORE_EXTERNAL_ANALYZER_SIGNAL, 0);
110
124
  if (source.task?.tags?.includes("large_file"))
111
- add(path, 4, 0);
125
+ add(path, SCORE_LARGE_FILE, 0);
112
126
  if (highRiskClean)
113
- add(path, 5, 0);
127
+ add(path, SCORE_HIGH_RISK_CLEAN, 0);
114
128
  }
115
129
  for (const finding of source.result.findings) {
116
130
  for (const file of finding.affected_files) {
@@ -120,7 +134,7 @@ function selectLensVerificationFiles(sources, externalAnalyzerPaths) {
120
134
  }
121
135
  for (const path of externalAnalyzerPaths) {
122
136
  if (scores.has(path)) {
123
- add(path, 8, 0);
137
+ add(path, SCORE_EXTERNAL_ANALYZER_PATH_MATCH, 0);
124
138
  }
125
139
  }
126
140
  const ranked = [...scores.entries()].sort((a, b) => {
@@ -133,10 +147,15 @@ function selectLensVerificationFiles(sources, externalAnalyzerPaths) {
133
147
  return a[0].localeCompare(b[0]);
134
148
  });
135
149
  if (ranked.length > MAX_LENS_VERIFICATION_FILES) {
136
- // No RunLogger in scope here: emit the established structured-stderr signal
137
- // so the silent task-budget truncation leaves a trace.
138
- process.stderr.write(`[audit-code] selectiveDeepening: truncated verification-file list to ` +
139
- `${MAX_LENS_VERIFICATION_FILES} of ${ranked.length}\n`);
150
+ process.stderr.write(JSON.stringify({
151
+ level: "warn",
152
+ source: "audit-code:selectiveDeepening",
153
+ event: "truncated_verification_file_list",
154
+ lens,
155
+ kept: MAX_LENS_VERIFICATION_FILES,
156
+ total: ranked.length,
157
+ ts: new Date().toISOString(),
158
+ }) + "\n");
140
159
  }
141
160
  return ranked.slice(0, MAX_LENS_VERIFICATION_FILES).map(([path]) => path);
142
161
  }
@@ -156,13 +175,20 @@ function summarizeLensVerificationSource(source) {
156
175
  }
157
176
  function buildLensVerificationTask(params) {
158
177
  const sourceIds = sourceTaskIds(params.sources);
159
- const selectedPaths = selectLensVerificationFiles(params.sources, params.externalAnalyzerPaths);
178
+ const selectedPaths = selectLensVerificationFiles(params.sources, params.externalAnalyzerPaths, params.lens);
160
179
  const allPaths = uniqueSorted(params.sources.flatMap(resultFiles));
161
180
  const omittedPathCount = Math.max(0, allPaths.length - selectedPaths.length);
162
181
  const externalPathsInScope = allPaths.filter((path) => params.externalAnalyzerPaths.has(path));
163
182
  if (params.sources.length > MAX_LENS_VERIFICATION_RESULT_SUMMARIES) {
164
- process.stderr.write(`[audit-code] selectiveDeepening: truncated result-summary list to ` +
165
- `${MAX_LENS_VERIFICATION_RESULT_SUMMARIES} of ${params.sources.length}\n`);
183
+ process.stderr.write(JSON.stringify({
184
+ level: "warn",
185
+ source: "audit-code:selectiveDeepening",
186
+ event: "truncated_result_summary_list",
187
+ lens: params.lens,
188
+ kept: MAX_LENS_VERIFICATION_RESULT_SUMMARIES,
189
+ total: params.sources.length,
190
+ ts: new Date().toISOString(),
191
+ }) + "\n");
166
192
  }
167
193
  const summaries = params.sources
168
194
  .sort((a, b) => a.result.task_id.localeCompare(b.result.task_id))
@@ -1,14 +1,14 @@
1
1
  import { getArtifactValue } from "../io/artifacts.js";
2
2
  import { ARTIFACT_DEPENDENTS_MAP } from "./dependencyMap.js";
3
3
  import { present } from "./artifactMetadata.js";
4
- import { buildReverseDependencyMap, hashArtifactValue, stableStringify, } from "./artifactFreshness.js";
4
+ import { buildArtifactDependenciesMap, hashArtifactValue, stableStringify, } from "./artifactFreshness.js";
5
5
  function computeContentHash(artifactName, bundle) {
6
6
  const value = getArtifactValue(bundle, artifactName);
7
7
  if (value === undefined || value === null)
8
8
  return undefined;
9
9
  return hashArtifactValue(artifactName, value);
10
10
  }
11
- const REVERSE_DEPENDENCY_MAP = buildReverseDependencyMap();
11
+ const ARTIFACT_DEPENDENCIES_MAP = buildArtifactDependenciesMap();
12
12
  export function computeStaleArtifacts(bundle) {
13
13
  const stale = new Set();
14
14
  const metadata = bundle.artifact_metadata;
@@ -16,7 +16,7 @@ export function computeStaleArtifacts(bundle) {
16
16
  for (const [artifactName, entry] of Object.entries(metadata.artifacts)) {
17
17
  if (!present(bundle, artifactName))
18
18
  continue;
19
- const expectedDependencies = [...(REVERSE_DEPENDENCY_MAP[artifactName] ?? [])]
19
+ const expectedDependencies = [...(ARTIFACT_DEPENDENCIES_MAP[artifactName] ?? [])]
20
20
  .filter((dependencyName) => dependencyName !== "artifact_metadata.json")
21
21
  .sort();
22
22
  const recordedDependencies = Object.keys(entry.dependency_revisions).sort();
@@ -72,7 +72,7 @@ export function deriveAuditState(bundle) {
72
72
  obligations.push(obligation("runtime_validation_current", runtimeReady
73
73
  ? "satisfied"
74
74
  : has(bundle.runtime_validation_report)
75
- ? "missing"
75
+ ? "stale"
76
76
  : "missing", runtimeTasks.length === 0
77
77
  ? "No deterministic runtime validation tasks were planned."
78
78
  : undefined));
@@ -42,6 +42,19 @@ function hasEslintConfig(root) {
42
42
  function snippet(value) {
43
43
  return value.replace(/\s+/g, " ").trim().slice(0, 500);
44
44
  }
45
+ function commandErrorResult(tool, command, results) {
46
+ return {
47
+ results,
48
+ status: {
49
+ tool,
50
+ command: command?.candidate.display,
51
+ resolved: Boolean(command),
52
+ status: command?.error ? "spawn_error" : "not_resolved",
53
+ exit_code: command?.exitCode,
54
+ error: command?.error?.message,
55
+ },
56
+ };
57
+ }
45
58
  function runTsc(root) {
46
59
  const results = [];
47
60
  const command = runFirstAvailableCommand(root, [
@@ -49,17 +62,7 @@ function runTsc(root) {
49
62
  { command: "tsc", args: ["--noEmit"], display: "tsc --noEmit" },
50
63
  ]);
51
64
  if (!command || command.error) {
52
- return {
53
- results,
54
- status: {
55
- tool: "tsc",
56
- command: command?.candidate.display,
57
- resolved: Boolean(command),
58
- status: command?.error ? "spawn_error" : "not_resolved",
59
- exit_code: command?.exitCode,
60
- error: command?.error?.message,
61
- },
62
- };
65
+ return commandErrorResult("tsc", command, results);
63
66
  }
64
67
  const output = [command.stdout, command.stderr].filter(Boolean).join("\n");
65
68
  const lines = output.split("\n");
@@ -91,7 +94,7 @@ function runTsc(root) {
91
94
  }
92
95
  if (results.length === 0 && output.trim().length > 0) {
93
96
  const outputSnippet = snippet(output);
94
- process.stderr.write(`[syntax-resolution] tsc output could not be parsed: ${outputSnippet}\n`);
97
+ process.stderr.write(`[syntax-resolution] tsc output could not be parsed: ${outputSnippet} (root=${root}, exit_code=${command.exitCode}, ts=${new Date().toISOString()})\n`);
95
98
  return {
96
99
  results,
97
100
  status: {
@@ -136,17 +139,7 @@ function runEslint(root) {
136
139
  },
137
140
  ]);
138
141
  if (!command || command.error) {
139
- return {
140
- results,
141
- status: {
142
- tool: "eslint",
143
- command: command?.candidate.display,
144
- resolved: Boolean(command),
145
- status: command?.error ? "spawn_error" : "not_resolved",
146
- exit_code: command?.exitCode,
147
- error: command?.error?.message,
148
- },
149
- };
142
+ return commandErrorResult("eslint", command, results);
150
143
  }
151
144
  const output = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
152
145
  if (output.length === 0) {
@@ -181,7 +174,7 @@ function runEslint(root) {
181
174
  }
182
175
  catch {
183
176
  const outputSnippet = snippet(output);
184
- process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${outputSnippet}\n`);
177
+ process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${outputSnippet} (root=${root}, exit_code=${command.exitCode}, ts=${new Date().toISOString()})\n`);
185
178
  return {
186
179
  results,
187
180
  status: {
@@ -7,6 +7,7 @@ function buildBaseFindingsReport(bundle, results) {
7
7
  criticalFlows: bundle.critical_flows,
8
8
  coverageMatrix: bundle.coverage_matrix,
9
9
  runtimeValidationReport: bundle.runtime_validation_report,
10
+ runtimeValidationTaskManifest: bundle.runtime_validation_tasks,
10
11
  externalAnalyzerResults: bundle.external_analyzer_results,
11
12
  designAssessment: bundle.design_assessment,
12
13
  }));