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
@@ -5,6 +5,7 @@ import { loadArtifactBundle, promoteFinalAuditReport, writeCoreArtifacts, AUDIT_
5
5
  import { advanceAudit } from "../orchestrator/advance.js";
6
6
  import { computeArtifactStateSignature } from "../orchestrator/artifactMetadata.js";
7
7
  import { decideNextStep } from "../orchestrator/nextStep.js";
8
+ import { isHostDelegationExecutor } from "../orchestrator/executors.js";
8
9
  import { deriveAuditState } from "../orchestrator/state.js";
9
10
  import { checkFileIntegrity } from "../orchestrator/fileIntegrity.js";
10
11
  import { buildEdgeReasoningPrompt, collectLowConfidenceEdges, edgeReasoningContentHash, } from "../orchestrator/edgeReasoning.js";
@@ -23,54 +24,260 @@ import { renderSemanticReviewStep } from "./semanticReviewStep.js";
23
24
  import { writeCurrentStep } from "./steps.js";
24
25
  import { nextStepCommand, renderAnalyzerInstallPrompt, renderBlockedStepPrompt, renderEdgeReasoningDispatchPrompt, renderEdgeReasoningStepPrompt, renderPresentReportPrompt, } from "./prompts.js";
25
26
  import { getArtifactsDir, getFlag, getHostMaxActiveSubagents, getMaxRuns, getOptionalBooleanFlag, getRootDir, getTimeoutMs, resolveHostDispatchCapability, warnIfNotGitRepo, } from "./args.js";
26
- async function runDeterministicForNextStep(params) {
27
- let lastSummary = "";
28
- let analyzers = params.analyzers;
29
- // Finalization thrashing guard. A converging run produces a (mostly) new
30
- // artifact state each iteration, so the iteration count tracks the number of
31
- // distinct states closely (a few idempotent passes are normal). When
32
- // iterations outrun distinct states by this tolerance, deterministic executors
33
- // are revisiting states (a staleness ping-pong, e.g. runtime_validation <->
34
- // synthesis) rather than progressing — stop instead of spinning to maxRuns.
35
- const FINALIZATION_CYCLE_TOLERANCE = 16;
36
- const seenStateSignatures = new Set();
37
- const obligationTrail = [];
38
- // Build the terminal step for a deterministic loop that has stopped advancing
39
- // (hit the run backstop or the finalization cycle guard). A rendered report is
40
- // the deliverable: if synthesis already produced one — or the state is formally
41
- // complete — present it instead of reporting the stopped loop as a bare
42
- // "blocked" failure. A completed audit must never surface as blocked just
43
- // because finalization kept churning (e.g. a runtime_validation <-> synthesis
44
- // ping-pong, or revision churn from filesystem retries) after the report was
45
- // written. With no report yet, the stop is a genuine block.
46
- async function terminalStep(bundle, state, blockedReason) {
47
- const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
48
- await writeHandoffOnly({
27
+ // ── Incoming-artifact helper ──────────────────────────────────────────────────
28
+ /**
29
+ * Read a JSON file from the `incoming/` subdirectory of `artifactsDir`.
30
+ * Returns `{ value, path }` when the file exists and parses successfully.
31
+ * Returns `undefined` when the file is absent (ENOENT-family errors).
32
+ * Re-throws all other IO errors unchanged.
33
+ */
34
+ export async function tryConsumeIncoming(artifactsDir, filename) {
35
+ const filePath = join(artifactsDir, "incoming", filename);
36
+ try {
37
+ const value = await readJsonFile(filePath);
38
+ return { value, path: filePath };
39
+ }
40
+ catch (error) {
41
+ if (isFileMissingError(error))
42
+ return undefined;
43
+ throw error;
44
+ }
45
+ }
46
+ // ── Extracted helpers ─────────────────────────────────────────────────────────
47
+ /**
48
+ * Build the terminal step for a deterministic loop that has stopped advancing
49
+ * (hit the run backstop or the finalization cycle guard). A rendered report is
50
+ * the deliverable: if synthesis already produced one — or the state is formally
51
+ * complete — present it instead of reporting the stopped loop as a bare
52
+ * "blocked" failure. A completed audit must never surface as blocked just
53
+ * because finalization kept churning (e.g. a runtime_validation <-> synthesis
54
+ * ping-pong, or revision churn from filesystem retries) after the report was
55
+ * written. With no report yet, the stop is a genuine block.
56
+ */
57
+ export async function buildTerminalStep(params, bundle, state, blockedReason) {
58
+ const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
59
+ await writeHandoffOnly({
60
+ root: params.root,
61
+ artifactsDir: params.artifactsDir,
62
+ bundle,
63
+ audit_state: state,
64
+ progress_summary: reportRendered && state.status !== "complete"
65
+ ? `Audit report already rendered; ending run. ${blockedReason}`
66
+ : blockedReason,
67
+ providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
68
+ });
69
+ if (!reportRendered) {
70
+ return { kind: "blocked", state, bundle, reason: blockedReason };
71
+ }
72
+ const promoted = await promoteFinalAuditReport({
73
+ artifactsDir: params.artifactsDir,
74
+ repoRoot: params.root,
75
+ });
76
+ return {
77
+ kind: "complete",
78
+ state,
79
+ bundle,
80
+ finalReportPath: promoted.promoted
81
+ ? join(params.root, AUDIT_REPORT_FILENAME)
82
+ : join(params.artifactsDir, AUDIT_REPORT_FILENAME),
83
+ };
84
+ }
85
+ /**
86
+ * Handle the `graph_enrichment_executor` incoming-artifact polling block.
87
+ * Checks for pending analyzer install decisions and edge-reasoning results.
88
+ * Returns an action object:
89
+ * - `continue` → caller should `continue` the for-loop (already consumed an artifact).
90
+ * - `return` → caller should return the embedded result to cmdNextStep.
91
+ * - `fallthrough` → no incoming artifacts; fall through to the deterministic executor.
92
+ */
93
+ export async function handleGraphEnrichmentBranch(params, bundle, state, analyzersRef) {
94
+ const includedFiles = bundle.repo_manifest
95
+ ? [
96
+ ...new Set(buildPathLookup(bundle.repo_manifest, buildDispositionMap(bundle.file_disposition)).values()),
97
+ ]
98
+ : [];
99
+ const plan = resolveAnalyzerPlan(params.root, analyzersRef.value, includedFiles);
100
+ const unresolved = plan.filter(needsInstallDecision);
101
+ if (unresolved.length > 0) {
102
+ const incoming = await tryConsumeIncoming(params.artifactsDir, "analyzer-decisions.json");
103
+ if (incoming && typeof incoming.value === "object") {
104
+ const settings = {};
105
+ for (const [id, value] of Object.entries(incoming.value)) {
106
+ if (value === "ephemeral" ||
107
+ value === "permanent" ||
108
+ value === "skip" ||
109
+ value === "repo" ||
110
+ value === "auto") {
111
+ settings[id] = value;
112
+ }
113
+ }
114
+ if (Object.keys(settings).length > 0) {
115
+ const merged = await persistAnalyzerSettings(params.artifactsDir, settings);
116
+ analyzersRef.value = merged.analyzers;
117
+ }
118
+ await unlink(incoming.path).catch(() => { });
119
+ return { action: "continue" };
120
+ }
121
+ return { action: "return", result: { kind: "analyzer_install", state, bundle, unresolved } };
122
+ }
123
+ // Phase 4B — optional edge-reasoning producing turn. Once analyzer installs
124
+ // are resolved, if the flag is on and the floor carries low-confidence
125
+ // (< 0.65) edges, emit one bounded host turn (subagent dispatch or a single
126
+ // host step) to produce reason rewrites, then re-run. The enrichment
127
+ // executor applies the host-supplied rewrites in the SAME advanceAudit call
128
+ // that merges analyzer edges and writes analyzer_capability, so graph_bundle
129
+ // and its marker stay revision-consistent (no staleness loop). Flag off or
130
+ // no candidates → fall through and run the executor with no rewrites.
131
+ if (params.graphLlmEdgeReasoning === true && bundle.graph_bundle) {
132
+ const candidates = collectLowConfidenceEdges(bundle.graph_bundle);
133
+ if (candidates.length > 0) {
134
+ const edgeReasoningIncoming = await tryConsumeIncoming(params.artifactsDir, "edge-reasoning.json");
135
+ if (edgeReasoningIncoming) {
136
+ await runAuditStep({
137
+ root: params.root,
138
+ artifactsDir: params.artifactsDir,
139
+ analyzers: analyzersRef.value,
140
+ graphLlmEdgeReasoning: true,
141
+ edgeReasoningResultsPath: edgeReasoningIncoming.path,
142
+ since: params.since,
143
+ opentoken: params.opentoken,
144
+ });
145
+ await unlink(edgeReasoningIncoming.path).catch(() => { });
146
+ return { action: "continue" };
147
+ }
148
+ return { action: "return", result: { kind: "edge_reasoning", state, bundle, candidates } };
149
+ }
150
+ }
151
+ // No undecided installs (and no pending edge reasoning): fall through to run
152
+ // the executor below (it installs for ephemeral/permanent, uses repo/cache,
153
+ // skips the rest).
154
+ return { action: "fallthrough" };
155
+ }
156
+ /**
157
+ * Handle the `design_review` incoming-artifact polling block.
158
+ * Returns `continue` if an incoming findings file was consumed, or `return`
159
+ * with a design_review kind when the host turn is still needed.
160
+ */
161
+ export async function handleDesignReviewBranch(params, bundle, state) {
162
+ const findingsIncoming = await tryConsumeIncoming(params.artifactsDir, "design-review-findings.json");
163
+ if (findingsIncoming && Array.isArray(findingsIncoming.value)) {
164
+ const existing = bundle.design_assessment;
165
+ if (existing) {
166
+ existing.review_findings = findingsIncoming.value;
167
+ existing.reviewed = true;
168
+ await writeJsonFile(join(params.artifactsDir, "design_assessment.json"), existing);
169
+ await unlink(findingsIncoming.path).catch(() => { });
170
+ return { action: "continue" };
171
+ }
172
+ }
173
+ return { action: "return", result: { kind: "design_review", state, bundle } };
174
+ }
175
+ /**
176
+ * Handle the `synthesis_narrative_executor` incoming-artifact polling block.
177
+ * Returns `continue` if an incoming narrative file was consumed, or `return`
178
+ * with a synthesis_narrative kind when the host turn is still needed (and
179
+ * narrative is enabled), or `continue` when narrative is disabled (so the
180
+ * deterministic omit runs below).
181
+ */
182
+ export async function handleSynthesisNarrativeBranch(params, bundle, state) {
183
+ const narrativeIncoming = await tryConsumeIncoming(params.artifactsDir, "synthesis-narrative.json");
184
+ if (narrativeIncoming) {
185
+ await runAuditStep({
49
186
  root: params.root,
50
187
  artifactsDir: params.artifactsDir,
51
- bundle,
52
- audit_state: state,
53
- progress_summary: reportRendered && state.status !== "complete"
54
- ? `Audit report already rendered; ending run. ${blockedReason}`
55
- : blockedReason,
56
- providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
188
+ preferredExecutor: "synthesis_narrative_executor",
189
+ narrativeResultsPath: narrativeIncoming.path,
190
+ opentoken: params.opentoken,
57
191
  });
58
- if (!reportRendered) {
59
- return { kind: "blocked", state, bundle, reason: blockedReason };
60
- }
61
- const promoted = await promoteFinalAuditReport({
192
+ await unlink(narrativeIncoming.path).catch(() => { });
193
+ return { action: "continue" };
194
+ }
195
+ if (params.narrativeEnabled) {
196
+ return { action: "return", result: { kind: "synthesis_narrative", state, bundle } };
197
+ }
198
+ // Narrative disabled: fall through so the deterministic omit runs below.
199
+ return { action: "continue" };
200
+ }
201
+ /**
202
+ * Execute one deterministic audit step and record its progress. Throws (with
203
+ * cause) if the executor fails, preserving the existing throw-with-cause pattern.
204
+ */
205
+ export async function executeAndRecord(params, analyzers, decision, index, lastSummary) {
206
+ try {
207
+ const result = await runAuditStep({
208
+ root: params.root,
62
209
  artifactsDir: params.artifactsDir,
63
- repoRoot: params.root,
210
+ analyzers,
211
+ graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
212
+ since: params.since,
213
+ opentoken: params.opentoken,
64
214
  });
65
- return {
66
- kind: "complete",
67
- state,
68
- bundle,
69
- finalReportPath: promoted.promoted
70
- ? join(params.root, AUDIT_REPORT_FILENAME)
71
- : join(params.artifactsDir, AUDIT_REPORT_FILENAME),
72
- };
215
+ await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
216
+ iteration: index + 1,
217
+ max_runs: params.maxRuns,
218
+ last_executor: result.selected_executor,
219
+ last_obligation: decision.selected_obligation,
220
+ progress_made: result.progress_made,
221
+ summary: result.progress_summary,
222
+ timestamp: new Date().toISOString(),
223
+ });
224
+ return result;
225
+ }
226
+ catch (error) {
227
+ const current = await loadArtifactBundle(params.artifactsDir);
228
+ const currentState = deriveAuditState(current);
229
+ currentState.last_executor = decision.selected_executor ?? undefined;
230
+ currentState.last_obligation = decision.selected_obligation ?? undefined;
231
+ await writeCoreArtifacts(params.artifactsDir, { ...current, audit_state: currentState });
232
+ await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
233
+ iteration: index + 1,
234
+ max_runs: params.maxRuns,
235
+ last_executor: decision.selected_executor,
236
+ last_obligation: decision.selected_obligation,
237
+ prior_summary: lastSummary || null,
238
+ error: error instanceof Error ? error.message : String(error),
239
+ timestamp: new Date().toISOString(),
240
+ });
241
+ const detail = error instanceof Error ? error.message : String(error);
242
+ throw new Error(`Deterministic executor ${decision.selected_executor} failed on obligation ${decision.selected_obligation} (iteration ${index + 1}/${params.maxRuns}, prior progress: ${lastSummary || "none"}): ${detail}`, { cause: error instanceof Error ? error : undefined });
243
+ }
244
+ }
245
+ /**
246
+ * Check for a finalization cycle: when iterations outrun distinct artifact
247
+ * states by FINALIZATION_CYCLE_TOLERANCE, the deterministic executors are
248
+ * revisiting states rather than progressing. Returns a terminal-step result
249
+ * when a cycle is detected, or undefined when the run is still progressing.
250
+ */
251
+ export async function checkFinalizationCycle(ctx) {
252
+ ctx.obligationTrail.push(ctx.selectedObligation ?? "unknown");
253
+ ctx.seenStateSignatures.add(computeArtifactStateSignature(ctx.result.updated_bundle));
254
+ if (ctx.index + 1 - ctx.seenStateSignatures.size < ctx.tolerance) {
255
+ return undefined;
73
256
  }
257
+ const cycle = Array.from(new Set(ctx.obligationTrail.slice(-ctx.tolerance)));
258
+ await writeJsonFile(join(ctx.params.artifactsDir, "steps", "deterministic-progress.json"), {
259
+ iteration: ctx.index + 1,
260
+ max_runs: ctx.params.maxRuns,
261
+ cycle_detected: true,
262
+ cycling_obligations: cycle,
263
+ summary: "Finalization kept revisiting prior artifact states without net " +
264
+ `progress; stopping. Cycling obligations: ${cycle.join(" -> ")}.`,
265
+ timestamp: new Date().toISOString(),
266
+ });
267
+ return buildTerminalStep(ctx.params, ctx.result.updated_bundle, ctx.result.audit_state, "Finalization is not converging: deterministic executors kept revisiting " +
268
+ `prior artifact states (${cycle.join(" -> ")}). Review whether these ` +
269
+ "obligations are erroneously invalidating each other.");
270
+ }
271
+ // ── Coordinator ───────────────────────────────────────────────────────────────
272
+ async function runDeterministicForNextStep(params) {
273
+ let lastSummary = "";
274
+ const analyzersRef = {
275
+ value: params.analyzers,
276
+ };
277
+ // Finalization thrashing guard — see checkFinalizationCycle for details.
278
+ const FINALIZATION_CYCLE_TOLERANCE = 16;
279
+ const seenStateSignatures = new Set();
280
+ const obligationTrail = [];
74
281
  for (let index = 0; index < params.maxRuns; index++) {
75
282
  const bundle = await loadArtifactBundle(params.artifactsDir);
76
283
  const decision = decideNextStep(bundle);
@@ -118,145 +325,28 @@ async function runDeterministicForNextStep(params) {
118
325
  }
119
326
  }
120
327
  if (decision.selected_executor === "graph_enrichment_executor") {
121
- const includedFiles = bundle.repo_manifest
122
- ? [
123
- ...new Set(buildPathLookup(bundle.repo_manifest, buildDispositionMap(bundle.file_disposition)).values()),
124
- ]
125
- : [];
126
- const plan = resolveAnalyzerPlan(params.root, analyzers, includedFiles);
127
- const unresolved = plan.filter(needsInstallDecision);
128
- if (unresolved.length > 0) {
129
- const decisionsPath = join(params.artifactsDir, "incoming", "analyzer-decisions.json");
130
- let decisions;
131
- try {
132
- decisions = await readJsonFile(decisionsPath);
133
- }
134
- catch (error) {
135
- if (!isFileMissingError(error))
136
- throw error;
137
- }
138
- if (decisions && typeof decisions === "object") {
139
- const settings = {};
140
- for (const [id, value] of Object.entries(decisions)) {
141
- if (value === "ephemeral" ||
142
- value === "permanent" ||
143
- value === "skip" ||
144
- value === "repo" ||
145
- value === "auto") {
146
- settings[id] = value;
147
- }
148
- }
149
- if (Object.keys(settings).length > 0) {
150
- const merged = await persistAnalyzerSettings(params.artifactsDir, settings);
151
- analyzers = merged.analyzers;
152
- }
153
- await unlink(decisionsPath).catch(() => { });
154
- continue;
155
- }
156
- return {
157
- kind: "analyzer_install",
158
- state,
159
- bundle,
160
- unresolved,
161
- };
162
- }
163
- // Phase 4B — optional edge-reasoning producing turn. Once analyzer installs
164
- // are resolved, if the flag is on and the floor carries low-confidence
165
- // (< 0.65) edges, emit one bounded host turn (subagent dispatch or a single
166
- // host step) to produce reason rewrites, then re-run. The enrichment
167
- // executor applies the host-supplied rewrites in the SAME advanceAudit call
168
- // that merges analyzer edges and writes analyzer_capability, so graph_bundle
169
- // and its marker stay revision-consistent (no staleness loop). Flag off or
170
- // no candidates → fall through and run the executor with no rewrites.
171
- if (params.graphLlmEdgeReasoning === true && bundle.graph_bundle) {
172
- const candidates = collectLowConfidenceEdges(bundle.graph_bundle);
173
- if (candidates.length > 0) {
174
- const edgeReasoningResultsPath = join(params.artifactsDir, "incoming", "edge-reasoning.json");
175
- let edgeReasoningResults;
176
- try {
177
- edgeReasoningResults = await readJsonFile(edgeReasoningResultsPath);
178
- }
179
- catch (error) {
180
- if (!isFileMissingError(error))
181
- throw error;
182
- }
183
- if (edgeReasoningResults) {
184
- await runAuditStep({
185
- root: params.root,
186
- artifactsDir: params.artifactsDir,
187
- analyzers,
188
- graphLlmEdgeReasoning: true,
189
- edgeReasoningResultsPath,
190
- since: params.since,
191
- opentoken: params.opentoken,
192
- });
193
- await unlink(edgeReasoningResultsPath).catch(() => { });
194
- continue;
195
- }
196
- return { kind: "edge_reasoning", state, bundle, candidates };
197
- }
198
- }
199
- // No undecided installs (and no pending edge reasoning): fall through to run
200
- // the executor below (it installs for ephemeral/permanent, uses repo/cache,
201
- // skips the rest).
328
+ const branch = await handleGraphEnrichmentBranch(params, bundle, state, analyzersRef);
329
+ if (branch.action === "continue")
330
+ continue;
331
+ if (branch.action === "return")
332
+ return branch.result;
333
+ // fallthrough: run the executor below
202
334
  }
335
+ // Host-delegation executors (design_review, agent) exit the deterministic
336
+ // loop entirely — they pause the pipeline and hand control to the LLM agent.
203
337
  if (decision.selected_executor === "design_review") {
204
- const findingsPath = join(params.artifactsDir, "incoming", "design-review-findings.json");
205
- let reviewFindings;
206
- try {
207
- reviewFindings = await readJsonFile(findingsPath);
208
- }
209
- catch (error) {
210
- if (!isFileMissingError(error))
211
- throw error;
212
- }
213
- if (reviewFindings && Array.isArray(reviewFindings)) {
214
- const existing = bundle.design_assessment;
215
- if (existing) {
216
- existing.review_findings = reviewFindings;
217
- existing.reviewed = true;
218
- await writeJsonFile(join(params.artifactsDir, "design_assessment.json"), existing);
219
- await unlink(findingsPath).catch(() => { });
220
- continue;
221
- }
222
- }
223
- return {
224
- kind: "design_review",
225
- state,
226
- bundle,
227
- };
338
+ const branch = await handleDesignReviewBranch(params, bundle, state);
339
+ if (branch.action === "continue")
340
+ continue;
341
+ return branch.result;
228
342
  }
229
343
  if (decision.selected_executor === "synthesis_narrative_executor") {
230
- const narrativePath = join(params.artifactsDir, "incoming", "synthesis-narrative.json");
231
- let narrativeResults;
232
- try {
233
- narrativeResults = await readJsonFile(narrativePath);
234
- }
235
- catch (error) {
236
- if (!isFileMissingError(error))
237
- throw error;
238
- }
239
- if (narrativeResults) {
240
- await runAuditStep({
241
- root: params.root,
242
- artifactsDir: params.artifactsDir,
243
- preferredExecutor: "synthesis_narrative_executor",
244
- narrativeResultsPath: narrativePath,
245
- opentoken: params.opentoken,
246
- });
247
- await unlink(narrativePath).catch(() => { });
344
+ const branch = await handleSynthesisNarrativeBranch(params, bundle, state);
345
+ if (branch.action === "continue")
248
346
  continue;
249
- }
250
- if (params.narrativeEnabled) {
251
- return {
252
- kind: "synthesis_narrative",
253
- state,
254
- bundle,
255
- };
256
- }
257
- // Narrative disabled: fall through so the deterministic omit runs below.
347
+ return branch.result;
258
348
  }
259
- if (decision.selected_executor === "agent") {
349
+ if (isHostDelegationExecutor(decision.selected_executor ?? "")) {
260
350
  return {
261
351
  kind: "semantic_review",
262
352
  ...(await ensureSemanticReviewRun({
@@ -286,46 +376,9 @@ async function runDeterministicForNextStep(params) {
286
376
  reason: lastSummary || decision.reason,
287
377
  };
288
378
  }
289
- let result;
290
- try {
291
- result = await runAuditStep({
292
- root: params.root,
293
- artifactsDir: params.artifactsDir,
294
- analyzers,
295
- graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
296
- since: params.since,
297
- opentoken: params.opentoken,
298
- });
299
- }
300
- catch (error) {
301
- const current = await loadArtifactBundle(params.artifactsDir);
302
- const currentState = deriveAuditState(current);
303
- currentState.last_executor = decision.selected_executor ?? undefined;
304
- currentState.last_obligation = decision.selected_obligation ?? undefined;
305
- await writeCoreArtifacts(params.artifactsDir, { ...current, audit_state: currentState });
306
- await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
307
- iteration: index + 1,
308
- max_runs: params.maxRuns,
309
- last_executor: decision.selected_executor,
310
- last_obligation: decision.selected_obligation,
311
- prior_summary: lastSummary || null,
312
- error: error instanceof Error ? error.message : String(error),
313
- timestamp: new Date().toISOString(),
314
- });
315
- const detail = error instanceof Error ? error.message : String(error);
316
- throw new Error(`Deterministic executor ${decision.selected_executor} failed on obligation ${decision.selected_obligation} (iteration ${index + 1}/${params.maxRuns}, prior progress: ${lastSummary || "none"}): ${detail}`, { cause: error instanceof Error ? error : undefined });
317
- }
379
+ const result = await executeAndRecord(params, analyzersRef.value, decision, index, lastSummary);
318
380
  lastSummary = result.progress_summary;
319
- await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
320
- iteration: index + 1,
321
- max_runs: params.maxRuns,
322
- last_executor: result.selected_executor,
323
- last_obligation: decision.selected_obligation,
324
- progress_made: result.progress_made,
325
- summary: result.progress_summary,
326
- timestamp: new Date().toISOString(),
327
- });
328
- if (result.selected_executor !== "agent") {
381
+ if (!isHostDelegationExecutor(result.selected_executor ?? "")) {
329
382
  await clearDispatchFiles(params.artifactsDir);
330
383
  }
331
384
  if (!result.progress_made) {
@@ -341,27 +394,23 @@ async function runDeterministicForNextStep(params) {
341
394
  // thrashing (no net progress) rather than converging. The canonical outputs
342
395
  // are already rendered, so stop and surface the cycling obligations instead
343
396
  // of spinning to maxRuns and crashing.
344
- obligationTrail.push(decision.selected_obligation ?? "unknown");
345
- seenStateSignatures.add(computeArtifactStateSignature(result.updated_bundle));
346
- if (index + 1 - seenStateSignatures.size >= FINALIZATION_CYCLE_TOLERANCE) {
347
- const cycle = Array.from(new Set(obligationTrail.slice(-FINALIZATION_CYCLE_TOLERANCE)));
348
- await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
349
- iteration: index + 1,
350
- max_runs: params.maxRuns,
351
- cycle_detected: true,
352
- cycling_obligations: cycle,
353
- summary: "Finalization kept revisiting prior artifact states without net " +
354
- `progress; stopping. Cycling obligations: ${cycle.join(" -> ")}.`,
355
- timestamp: new Date().toISOString(),
356
- });
357
- return await terminalStep(result.updated_bundle, result.audit_state, "Finalization is not converging: deterministic executors kept revisiting " +
358
- `prior artifact states (${cycle.join(" -> ")}). Review whether these ` +
359
- "obligations are erroneously invalidating each other.");
360
- }
397
+ const cycleResult = await checkFinalizationCycle({
398
+ index,
399
+ obligationTrail,
400
+ seenStateSignatures,
401
+ tolerance: FINALIZATION_CYCLE_TOLERANCE,
402
+ params,
403
+ bundle,
404
+ state,
405
+ result,
406
+ selectedObligation: decision.selected_obligation,
407
+ });
408
+ if (cycleResult !== undefined)
409
+ return cycleResult;
361
410
  }
362
411
  const bundle = await loadArtifactBundle(params.artifactsDir);
363
412
  const state = deriveAuditState(bundle);
364
- return await terminalStep(bundle, state, `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`);
413
+ return buildTerminalStep(params, bundle, state, `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`);
365
414
  }
366
415
  export async function cmdNextStep(argv) {
367
416
  const root = getRootDir(argv);
@@ -0,0 +1 @@
1
+ export declare function cmdPlan(argv: string[]): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import { runAuditStep } from "./auditStep.js";
2
+ import { getArtifactsDir, getFlag, getRootDir } from "./args.js";
3
+ export async function cmdPlan(argv) {
4
+ const artifactsDir = getArtifactsDir(argv);
5
+ const result = await runAuditStep({
6
+ root: getRootDir(argv),
7
+ artifactsDir,
8
+ since: getFlag(argv, "--since"),
9
+ });
10
+ console.log(JSON.stringify({
11
+ artifacts_dir: artifactsDir,
12
+ selected_executor: result.selected_executor,
13
+ progress_summary: result.progress_summary,
14
+ next_likely_step: result.next_likely_step,
15
+ }, null, 2));
16
+ }
@@ -0,0 +1 @@
1
+ export declare function cmdPrepareDispatch(argv: string[]): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { getArtifactsDir, getExplicitProvider, getFlag, getHostMaxActiveSubagents, getHostModel, getRootDir } from "./args.js";
2
+ import { createFreshSessionProvider } from "../providers/index.js";
3
+ import { loadSessionConfig } from "../supervisor/sessionConfig.js";
4
+ import { prepareDispatchArtifacts } from "./dispatch.js";
5
+ import { packageRoot } from "./paths.js";
6
+ export async function cmdPrepareDispatch(argv) {
7
+ const runId = getFlag(argv, "--run-id");
8
+ if (!runId)
9
+ throw new Error("prepare-dispatch requires --run-id <run_id>");
10
+ const artifactsDir = getArtifactsDir(argv);
11
+ const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
12
+ const provider = createFreshSessionProvider(getExplicitProvider(argv), sessionConfig);
13
+ const hostModel = getHostModel(argv) ?? sessionConfig.block_quota?.host_model ?? null;
14
+ const result = await prepareDispatchArtifacts({
15
+ packageRoot,
16
+ runId,
17
+ artifactsDir,
18
+ root: getFlag(argv, "--root") ? getRootDir(argv) : undefined,
19
+ sessionConfig,
20
+ hostModel,
21
+ queryLimits: provider.queryLimits?.bind(provider),
22
+ hostActiveSubagentLimit: getHostMaxActiveSubagents(argv),
23
+ });
24
+ console.log(JSON.stringify(result, null, 2));
25
+ }
@@ -0,0 +1 @@
1
+ export declare function cmdQuota(argv: string[]): Promise<void>;