auditor-lambda 0.8.0 → 0.9.0

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 (98) hide show
  1. package/audit-code-wrapper-lib.mjs +149 -129
  2. package/dist/adapters/normalizeExternal.js +6 -3
  3. package/dist/cli/args.d.ts +0 -1
  4. package/dist/cli/args.js +0 -6
  5. package/dist/cli/dispatch.js +3 -2
  6. package/dist/cli/lineIndex.js +4 -1
  7. package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
  8. package/dist/cli/mergeAndIngestCommand.js +219 -0
  9. package/dist/cli/nextStepCommand.js +5 -1
  10. package/dist/cli/runToCompletion.d.ts +9 -0
  11. package/dist/cli/runToCompletion.js +655 -480
  12. package/dist/cli/statusCommand.d.ts +1 -0
  13. package/dist/cli/statusCommand.js +113 -0
  14. package/dist/cli/submitPacketCommand.d.ts +1 -0
  15. package/dist/cli/submitPacketCommand.js +155 -0
  16. package/dist/cli/workerResult.d.ts +1 -1
  17. package/dist/cli/workerRunCommand.d.ts +1 -0
  18. package/dist/cli/workerRunCommand.js +88 -0
  19. package/dist/cli.js +14 -563
  20. package/dist/extractors/analyzers/sql.js +4 -1
  21. package/dist/extractors/analyzers/treeSitter.js +29 -15
  22. package/dist/extractors/analyzers/typescript.js +10 -8
  23. package/dist/extractors/designAssessment.js +43 -24
  24. package/dist/extractors/graph.js +139 -73
  25. package/dist/extractors/pathPatterns.js +17 -5
  26. package/dist/io/runArtifactTypes.d.ts +18 -0
  27. package/dist/io/runArtifactTypes.js +1 -0
  28. package/dist/io/runArtifacts.d.ts +2 -18
  29. package/dist/io/runArtifacts.js +14 -3
  30. package/dist/mcp/server.js +9 -0
  31. package/dist/orchestrator/advance.js +37 -22
  32. package/dist/orchestrator/artifactFreshness.js +2 -2
  33. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  34. package/dist/orchestrator/autoFixExecutor.js +16 -8
  35. package/dist/orchestrator/dependencyMap.d.ts +1 -1
  36. package/dist/orchestrator/dependencyMap.js +7 -1
  37. package/dist/orchestrator/fileAnchors.js +14 -3
  38. package/dist/orchestrator/flowCoverage.js +1 -0
  39. package/dist/orchestrator/flowRequeue.js +4 -1
  40. package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
  41. package/dist/orchestrator/ingestionExecutors.js +237 -0
  42. package/dist/orchestrator/intakeExecutors.d.ts +3 -0
  43. package/dist/orchestrator/intakeExecutors.js +25 -0
  44. package/dist/orchestrator/planningExecutors.d.ts +4 -0
  45. package/dist/orchestrator/planningExecutors.js +95 -0
  46. package/dist/orchestrator/runtimeCommand.js +7 -15
  47. package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
  48. package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
  49. package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
  50. package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
  51. package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
  52. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
  53. package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
  54. package/dist/orchestrator/selectiveDeepening/index.js +128 -0
  55. package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
  56. package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
  57. package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
  58. package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
  59. package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
  60. package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
  61. package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
  62. package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
  63. package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
  64. package/dist/orchestrator/selectiveDeepening.js +6 -760
  65. package/dist/orchestrator/staleness.js +3 -3
  66. package/dist/orchestrator/structureExecutors.d.ts +5 -0
  67. package/dist/orchestrator/structureExecutors.js +94 -0
  68. package/dist/orchestrator/taskBuilder.d.ts +2 -2
  69. package/dist/orchestrator/taskBuilder.js +101 -82
  70. package/dist/providers/index.d.ts +7 -0
  71. package/dist/providers/index.js +14 -95
  72. package/dist/quota/discoveredLimits.d.ts +1 -0
  73. package/dist/quota/discoveredLimits.js +7 -1
  74. package/dist/quota/index.d.ts +0 -2
  75. package/dist/quota/index.js +1 -2
  76. package/dist/reporting/workBlocks.js +7 -4
  77. package/dist/types/reviewPlanning.d.ts +23 -16
  78. package/dist/validation/auditResults.js +97 -95
  79. package/dist/validation/sessionConfig.d.ts +2 -2
  80. package/dist/validation/sessionConfig.js +14 -7
  81. package/package.json +3 -2
  82. package/schemas/audit_findings.schema.json +3 -3
  83. package/schemas/critical_flows.schema.json +3 -2
  84. package/schemas/dispatch_quota.schema.json +1 -1
  85. package/schemas/graph_bundle.schema.json +1 -1
  86. package/schemas/review_packets.schema.json +1 -1
  87. package/schemas/step_contract.schema.json +80 -0
  88. package/scripts/postinstall.mjs +19 -2
  89. package/skills/audit-code/opencode-command-template.txt +3 -3
  90. package/dist/orchestrator/internalExecutors.js +0 -424
  91. package/dist/providers/localSubprocessProvider.d.ts +0 -9
  92. package/dist/providers/localSubprocessProvider.js +0 -18
  93. package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
  94. package/dist/providers/subprocessTemplateProvider.js +0 -59
  95. package/dist/providers/vscodeTaskProvider.d.ts +0 -7
  96. package/dist/providers/vscodeTaskProvider.js +0 -14
  97. package/dist/quota/probe.d.ts +0 -10
  98. package/dist/quota/probe.js +0 -18
@@ -24,6 +24,564 @@ import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArti
24
24
  import { readWaveManifest, writeWaveManifest, removeWaveManifest, buildWaveSlotEntry, } from "./waveManifest.js";
25
25
  import { cleanupStaleArtifactsDir } from "./cleanup.js";
26
26
  import { getRootDir, warnIfNotGitRepo, getArtifactsDir, getUiMode, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getHostModel, getBatchResultsDir, getFlag, listBatchResultFiles, getExplicitProvider, chunkArray, resolveHostDispatchCapability, getOptionalBooleanFlag, getHostMaxActiveSubagents, summarizeLaunchExit, } from "./args.js";
27
+ async function recoverInterruptedWave(params) {
28
+ const { artifactsDir, root, artifactsWritten } = params;
29
+ let anyProgress = params.anyProgress;
30
+ // Resume interrupted parallel wave: ingest any results that workers
31
+ // wrote before the previous process exited.
32
+ const priorWave = await readWaveManifest(artifactsDir);
33
+ if (priorWave) {
34
+ process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
35
+ let recoveredProgress = false;
36
+ for (const entry of priorWave.slots) {
37
+ try {
38
+ const results = await readJsonFile(entry.audit_results_path);
39
+ if (!results || results.length === 0)
40
+ continue;
41
+ const stepResult = await runAuditStep({
42
+ root,
43
+ artifactsDir,
44
+ preferredExecutor: "result_ingestion_executor",
45
+ auditResultsPath: entry.audit_results_path,
46
+ });
47
+ if (stepResult.progress_made) {
48
+ recoveredProgress = true;
49
+ anyProgress = true;
50
+ for (const a of stepResult.artifacts_written)
51
+ artifactsWritten.add(a);
52
+ }
53
+ }
54
+ catch {
55
+ process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
56
+ }
57
+ }
58
+ await removeWaveManifest(artifactsDir);
59
+ if (recoveredProgress)
60
+ return { recovered: true, anyProgress };
61
+ }
62
+ return { recovered: false, anyProgress };
63
+ }
64
+ async function buildParallelWaveSlots(params) {
65
+ const { root, artifactsDir, selfCliPath, taskGroups, obligationId, timeoutMs } = params;
66
+ let runCount = params.runCountStart;
67
+ const workerSlots = [];
68
+ for (const rawGroup of taskGroups) {
69
+ const group = await addFileLineCountHints(root, rawGroup);
70
+ runCount += 1;
71
+ const slotRunId = buildRunId(obligationId, runCount);
72
+ const slotPaths = getRunPaths(artifactsDir, slotRunId);
73
+ const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
74
+ const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
75
+ const slotReadPaths = new Set();
76
+ for (const t of group) {
77
+ for (const fp of t.file_paths)
78
+ slotReadPaths.add(fp);
79
+ }
80
+ const slotTask = {
81
+ contract_version: "audit-code-worker/v1alpha1",
82
+ run_id: slotRunId,
83
+ repo_root: root,
84
+ artifacts_dir: artifactsDir,
85
+ obligation_id: obligationId,
86
+ preferred_executor: "agent",
87
+ result_path: slotPaths.resultPath,
88
+ worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
89
+ audit_results_path: slotAuditResultsPath,
90
+ pending_audit_tasks_path: slotPendingTasksPath,
91
+ worker_command_mode: "deferred",
92
+ timeout_ms: timeoutMs,
93
+ max_retries: 0,
94
+ access: {
95
+ read_paths: [...slotReadPaths],
96
+ write_paths: [slotAuditResultsPath, slotPaths.resultPath],
97
+ },
98
+ };
99
+ const slotPrompt = renderWorkerPrompt(slotTask);
100
+ await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
101
+ await writeJsonFile(slotPendingTasksPath, group);
102
+ workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
103
+ }
104
+ await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
105
+ run_id: slot.runId,
106
+ task_path: slot.paths.taskPath,
107
+ prompt_path: slot.paths.promptPath,
108
+ result_path: slot.paths.resultPath,
109
+ status_path: slot.paths.statusPath,
110
+ audit_results_path: slot.auditResultsPath,
111
+ pending_audit_tasks_path: slot.pendingTasksPath,
112
+ })), workerSlots.flatMap((slot) => slot.group));
113
+ return { slots: workerSlots, runCountAfter: runCount };
114
+ }
115
+ async function ingestParallelWaveResults(params) {
116
+ const { root, artifactsDir, workerSlots, launchErrorsByRunId, obligationId, parallelStartedAt, providerName, artifactsWritten, } = params;
117
+ let anyProgress = params.anyProgress;
118
+ // Result ingestion is intentionally sequential even though agent launch
119
+ // was parallel. Writing to coverage_matrix.json is not atomic, so
120
+ // concurrent ingest calls would race and corrupt coverage state.
121
+ let batchProgress = false;
122
+ const batchErrors = [];
123
+ for (const slot of workerSlots) {
124
+ const parallelEndedAt = new Date().toISOString();
125
+ let workerResult = buildWorkerResult({
126
+ runId: slot.runId,
127
+ obligationId,
128
+ status: "no_progress",
129
+ progressMade: false,
130
+ selectedExecutor: "agent",
131
+ artifactsWritten: [],
132
+ summary: "Parallel worker batch made no progress.",
133
+ nextLikelyStep: obligationId,
134
+ errors: [],
135
+ });
136
+ try {
137
+ const launchError = launchErrorsByRunId.get(slot.runId);
138
+ if (launchError) {
139
+ throw new Error(`Worker launch failed: ${launchError}`);
140
+ }
141
+ const auditResults = await readJsonFile(slot.auditResultsPath);
142
+ const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
143
+ const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
144
+ if (slot.group.length > 0 && matchedCount === 0) {
145
+ throw new Error("Worker did not emit any audit results for the assigned tasks.");
146
+ }
147
+ const issues = validateAuditResults(auditResults, slot.group, {
148
+ lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
149
+ });
150
+ const errors = issues.filter((issue) => issue.severity === "error");
151
+ const warnings = issues.filter((issue) => issue.severity === "warning");
152
+ if (warnings.length > 0) {
153
+ process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
154
+ formatAuditResultIssues(warnings) + "\n");
155
+ }
156
+ if (errors.length > 0) {
157
+ throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
158
+ formatAuditResultIssues(errors));
159
+ }
160
+ const stepResult = await runAuditStep({
161
+ root,
162
+ artifactsDir,
163
+ preferredExecutor: "result_ingestion_executor",
164
+ auditResultsPath: slot.auditResultsPath,
165
+ });
166
+ workerResult = buildWorkerResult({
167
+ runId: slot.runId,
168
+ obligationId,
169
+ status: stepResult.progress_made ? "completed" : "no_progress",
170
+ progressMade: stepResult.progress_made,
171
+ selectedExecutor: stepResult.selected_executor,
172
+ artifactsWritten: stepResult.artifacts_written,
173
+ summary: stepResult.progress_summary,
174
+ nextLikelyStep: stepResult.next_likely_step,
175
+ errors: [],
176
+ });
177
+ batchProgress ||= stepResult.progress_made;
178
+ if (stepResult.progress_made)
179
+ anyProgress = true;
180
+ for (const a of stepResult.artifacts_written)
181
+ artifactsWritten.add(a);
182
+ }
183
+ catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ batchErrors.push(`${slot.runId}: ${message}`);
186
+ workerResult = buildWorkerResult({
187
+ runId: slot.runId,
188
+ obligationId,
189
+ status: "failed",
190
+ progressMade: false,
191
+ selectedExecutor: "agent",
192
+ artifactsWritten: [],
193
+ summary: `Worker failed for executor agent: ${message}`,
194
+ nextLikelyStep: obligationId,
195
+ errors: [message],
196
+ });
197
+ process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
198
+ }
199
+ await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
200
+ await appendRunLedgerEntry(artifactsDir, {
201
+ run_id: slot.runId,
202
+ provider: providerName,
203
+ obligation_id: obligationId,
204
+ selected_executor: workerResult.selected_executor,
205
+ status: workerResult.status,
206
+ started_at: parallelStartedAt,
207
+ ended_at: parallelEndedAt,
208
+ result_path: slot.paths.resultPath,
209
+ });
210
+ artifactsWritten.add("run-ledger.json");
211
+ }
212
+ return { batchProgress, batchErrors, anyProgress, artifactsWritten };
213
+ }
214
+ async function recordWaveQuota(params) {
215
+ const { providerModelKey, providerName, workerSlots, slotTokenEstimates, batchErrors, halfLifeHours, } = params;
216
+ // Record outcome for adaptive learning (best-effort — never blocks dispatch)
217
+ {
218
+ const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
219
+ const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
220
+ const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
221
+ await recordWaveOutcome(providerModelKey, {
222
+ concurrency: workerSlots.length,
223
+ estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
224
+ outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
225
+ cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
226
+ }, halfLifeHours).catch(() => undefined);
227
+ }
228
+ // Extract rate-limit headers from worker stderr (best-effort)
229
+ {
230
+ const extractor = getHeaderExtractorForProvider(providerName);
231
+ for (const slot of workerSlots) {
232
+ try {
233
+ const stderr = await readFile(slot.paths.stderrPath, "utf8");
234
+ const extracted = extractor.extract(stderr);
235
+ if (extracted && (extracted.requests_per_minute != null || extracted.input_tokens_per_minute != null)) {
236
+ await updateDiscoveredLimits(providerModelKey, {
237
+ requests_per_minute: extracted.requests_per_minute,
238
+ input_tokens_per_minute: extracted.input_tokens_per_minute,
239
+ source: "header_extraction",
240
+ });
241
+ break; // one successful extraction is enough
242
+ }
243
+ }
244
+ catch {
245
+ // stderr file missing or unreadable — skip
246
+ }
247
+ }
248
+ }
249
+ }
250
+ async function runInlineStep(params) {
251
+ const { root, artifactsDir, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, analyzers, since, artifactsWritten, providerName, decision, pendingBatchAuditResults, } = params;
252
+ let anyProgress = params.anyProgress;
253
+ let pendingAuditResultsPath = params.pendingAuditResultsPath;
254
+ let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
255
+ let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
256
+ await clearDispatchFiles(artifactsDir);
257
+ const startedAt = new Date().toISOString();
258
+ let workerResult;
259
+ try {
260
+ const result = await runAuditStep({
261
+ root,
262
+ artifactsDir,
263
+ preferredExecutor,
264
+ auditResultsPath,
265
+ runtimeUpdatesPath,
266
+ externalAnalyzerPath,
267
+ analyzers,
268
+ since,
269
+ });
270
+ workerResult = {
271
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
272
+ run_id: runId,
273
+ obligation_id: obligationId,
274
+ status: result.progress_made ? "completed" : "no_progress",
275
+ progress_made: result.progress_made,
276
+ selected_executor: result.selected_executor,
277
+ artifacts_written: result.artifacts_written,
278
+ summary: result.progress_summary,
279
+ next_likely_step: result.next_likely_step,
280
+ errors: [],
281
+ };
282
+ }
283
+ catch (error) {
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ workerResult = {
286
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
287
+ run_id: runId,
288
+ obligation_id: obligationId,
289
+ status: "failed",
290
+ progress_made: false,
291
+ selected_executor: preferredExecutor,
292
+ artifacts_written: [],
293
+ summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
294
+ next_likely_step: decision.selected_obligation,
295
+ errors: [message],
296
+ };
297
+ }
298
+ await persistWorkerRunArtifacts(paths, workerResult, "inline");
299
+ await appendRunLedgerEntry(artifactsDir, {
300
+ run_id: runId,
301
+ provider: providerName,
302
+ obligation_id: obligationId,
303
+ selected_executor: workerResult.selected_executor,
304
+ status: workerResult.status,
305
+ started_at: startedAt,
306
+ ended_at: new Date().toISOString(),
307
+ result_path: paths.resultPath,
308
+ });
309
+ const lastResult = workerResult;
310
+ if (workerResult.progress_made) {
311
+ anyProgress = true;
312
+ }
313
+ for (const artifact of workerResult.artifacts_written) {
314
+ artifactsWritten.add(artifact);
315
+ }
316
+ artifactsWritten.add("run-ledger.json");
317
+ if (externalAnalyzerPath)
318
+ pendingExternalAnalyzerPath = undefined;
319
+ if (auditResultsPath &&
320
+ pendingBatchAuditResults[0] === auditResultsPath &&
321
+ preferredExecutor === "result_ingestion_executor" &&
322
+ workerResult.status !== "failed" &&
323
+ workerResult.status !== "blocked") {
324
+ pendingBatchAuditResults.shift();
325
+ }
326
+ if (auditResultsPath)
327
+ pendingAuditResultsPath = undefined;
328
+ if (runtimeUpdatesPath)
329
+ pendingRuntimeUpdatesPath = undefined;
330
+ if (workerResult.status === "failed" ||
331
+ workerResult.status === "blocked" ||
332
+ workerResult.status === "no_progress") {
333
+ const bundleAfter = await loadArtifactBundle(artifactsDir);
334
+ const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
335
+ const state = shouldBlock
336
+ ? buildBlockedAuditState({
337
+ state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
338
+ obligationId: workerResult.obligation_id,
339
+ executor: workerResult.selected_executor,
340
+ blocker: buildWorkerFailureBlocker(workerResult),
341
+ })
342
+ : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
343
+ if (shouldBlock) {
344
+ await writeCoreArtifacts(artifactsDir, {
345
+ ...bundleAfter,
346
+ audit_state: state,
347
+ });
348
+ }
349
+ await emitEnvelope({
350
+ root,
351
+ artifactsDir,
352
+ bundle: shouldBlock
353
+ ? { ...bundleAfter, audit_state: state }
354
+ : bundleAfter,
355
+ audit_state: state,
356
+ selected_obligation: workerResult.obligation_id,
357
+ selected_executor: workerResult.selected_executor,
358
+ progress_made: anyProgress,
359
+ artifacts_written: Array.from(shouldBlock
360
+ ? new Set([...artifactsWritten, "audit_state.json"])
361
+ : artifactsWritten),
362
+ progress_summary: buildWorkerFailureBlocker(workerResult),
363
+ next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
364
+ providerName,
365
+ });
366
+ return {
367
+ done: true,
368
+ lastResult,
369
+ anyProgress,
370
+ artifactsWritten,
371
+ pendingBatchAuditResults,
372
+ pendingAuditResultsPath,
373
+ pendingRuntimeUpdatesPath,
374
+ pendingExternalAnalyzerPath,
375
+ };
376
+ }
377
+ return {
378
+ done: false,
379
+ lastResult,
380
+ anyProgress,
381
+ artifactsWritten,
382
+ pendingBatchAuditResults,
383
+ pendingAuditResultsPath,
384
+ pendingRuntimeUpdatesPath,
385
+ pendingExternalAnalyzerPath,
386
+ };
387
+ }
388
+ async function runSingleWorkerStep(params) {
389
+ const { root, artifactsDir, selfCliPath, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, bundle, timeoutMs, uiMode, provider, artifactsWritten, decision, pendingBatchAuditResults, } = params;
390
+ let anyProgress = params.anyProgress;
391
+ let pendingAuditResultsPath = params.pendingAuditResultsPath;
392
+ let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
393
+ let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
394
+ const pendingAuditTasks = preferredExecutor === "agent"
395
+ ? await addFileLineCountHints(root, buildPendingAuditTasks(bundle))
396
+ : undefined;
397
+ const pendingAuditTasksPath = preferredExecutor === "agent"
398
+ ? join(paths.runDir, "pending-audit-tasks.json")
399
+ : undefined;
400
+ const providerAuditResultsPath = preferredExecutor === "agent"
401
+ ? join(paths.runDir, "audit-results.json")
402
+ : auditResultsPath;
403
+ const providerReadPaths = new Set();
404
+ if (pendingAuditTasks) {
405
+ for (const pt of pendingAuditTasks) {
406
+ for (const fp of pt.file_paths)
407
+ providerReadPaths.add(fp);
408
+ }
409
+ }
410
+ const task = {
411
+ contract_version: "audit-code-worker/v1alpha1",
412
+ run_id: runId,
413
+ repo_root: root,
414
+ artifacts_dir: artifactsDir,
415
+ obligation_id: obligationId,
416
+ preferred_executor: preferredExecutor,
417
+ result_path: paths.resultPath,
418
+ worker_command: [
419
+ process.execPath,
420
+ selfCliPath,
421
+ "worker-run",
422
+ "--task",
423
+ paths.taskPath,
424
+ ],
425
+ audit_results_path: providerAuditResultsPath,
426
+ pending_audit_tasks_path: pendingAuditTasksPath,
427
+ runtime_updates_path: runtimeUpdatesPath,
428
+ external_analyzer_results_path: externalAnalyzerPath,
429
+ timeout_ms: timeoutMs,
430
+ max_retries: 0,
431
+ access: providerReadPaths.size > 0 ? {
432
+ read_paths: [...providerReadPaths],
433
+ write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
434
+ } : undefined,
435
+ };
436
+ const prompt = renderWorkerPrompt(task);
437
+ await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
438
+ if (pendingAuditTasksPath && pendingAuditTasks) {
439
+ await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
440
+ }
441
+ const startedAt = new Date().toISOString();
442
+ let workerResult;
443
+ let launchResult = null;
444
+ try {
445
+ launchResult = await provider.launch({
446
+ repoRoot: root,
447
+ runId,
448
+ obligationId,
449
+ promptPath: paths.promptPath,
450
+ taskPath: paths.taskPath,
451
+ resultPath: paths.resultPath,
452
+ stdoutPath: paths.stdoutPath,
453
+ stderrPath: paths.stderrPath,
454
+ uiMode,
455
+ timeoutMs,
456
+ });
457
+ const candidate = await readJsonFile(paths.resultPath);
458
+ if (isWorkerResult(candidate)) {
459
+ workerResult = candidate;
460
+ }
461
+ else {
462
+ const launchExitSummary = summarizeLaunchExit(launchResult);
463
+ workerResult = {
464
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
465
+ run_id: runId,
466
+ obligation_id: obligationId,
467
+ status: "failed",
468
+ progress_made: false,
469
+ selected_executor: preferredExecutor,
470
+ artifacts_written: [],
471
+ summary: launchExitSummary
472
+ ? `Worker did not emit a valid worker result after provider exit: ${launchExitSummary}`
473
+ : "Worker did not emit a valid worker result.",
474
+ next_likely_step: decision.selected_obligation,
475
+ errors: ["Invalid worker result contract."],
476
+ };
477
+ }
478
+ }
479
+ catch (error) {
480
+ const message = error instanceof Error ? error.message : String(error);
481
+ const launchExitSummary = launchResult && summarizeLaunchExit(launchResult);
482
+ workerResult = {
483
+ contract_version: WORKER_RESULT_CONTRACT_VERSION,
484
+ run_id: runId,
485
+ obligation_id: obligationId,
486
+ status: "failed",
487
+ progress_made: false,
488
+ selected_executor: preferredExecutor,
489
+ artifacts_written: [],
490
+ summary: `Worker launch failed for ${preferredExecutor}: ${launchExitSummary ?? message}`,
491
+ next_likely_step: decision.selected_obligation,
492
+ errors: launchExitSummary ? [message, launchExitSummary] : [message],
493
+ };
494
+ await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
495
+ }
496
+ await appendRunLedgerEntry(artifactsDir, {
497
+ run_id: runId,
498
+ provider: provider.name,
499
+ obligation_id: obligationId,
500
+ selected_executor: workerResult.selected_executor,
501
+ status: workerResult.status,
502
+ started_at: startedAt,
503
+ ended_at: new Date().toISOString(),
504
+ result_path: paths.resultPath,
505
+ });
506
+ const lastResult = workerResult;
507
+ if (workerResult.progress_made) {
508
+ anyProgress = true;
509
+ }
510
+ for (const artifact of workerResult.artifacts_written) {
511
+ artifactsWritten.add(artifact);
512
+ }
513
+ artifactsWritten.add("run-ledger.json");
514
+ if (externalAnalyzerPath)
515
+ pendingExternalAnalyzerPath = undefined;
516
+ if (auditResultsPath &&
517
+ pendingBatchAuditResults[0] === auditResultsPath &&
518
+ preferredExecutor === "result_ingestion_executor" &&
519
+ workerResult.status !== "failed" &&
520
+ workerResult.status !== "blocked") {
521
+ pendingBatchAuditResults.shift();
522
+ }
523
+ if (providerAuditResultsPath)
524
+ pendingAuditResultsPath = undefined;
525
+ if (runtimeUpdatesPath)
526
+ pendingRuntimeUpdatesPath = undefined;
527
+ if (workerResult.status === "failed" ||
528
+ workerResult.status === "blocked" ||
529
+ workerResult.status === "no_progress") {
530
+ const bundleAfter = await loadArtifactBundle(artifactsDir);
531
+ const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
532
+ const state = shouldBlock
533
+ ? buildBlockedAuditState({
534
+ state: deriveAuditState(bundleAfter),
535
+ obligationId: workerResult.obligation_id,
536
+ executor: workerResult.selected_executor,
537
+ blocker: buildWorkerFailureBlocker(workerResult),
538
+ })
539
+ : deriveAuditState(bundleAfter);
540
+ if (shouldBlock) {
541
+ await writeCoreArtifacts(artifactsDir, {
542
+ ...bundleAfter,
543
+ audit_state: state,
544
+ });
545
+ }
546
+ await emitEnvelope({
547
+ root,
548
+ artifactsDir,
549
+ bundle: shouldBlock
550
+ ? { ...bundleAfter, audit_state: state }
551
+ : bundleAfter,
552
+ audit_state: state,
553
+ selected_obligation: workerResult.obligation_id,
554
+ selected_executor: workerResult.selected_executor,
555
+ progress_made: anyProgress,
556
+ artifacts_written: Array.from(shouldBlock
557
+ ? new Set([...artifactsWritten, "audit_state.json"])
558
+ : artifactsWritten),
559
+ progress_summary: buildWorkerFailureBlocker(workerResult),
560
+ next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
561
+ providerName: provider.name,
562
+ });
563
+ return {
564
+ done: true,
565
+ lastResult,
566
+ anyProgress,
567
+ artifactsWritten,
568
+ pendingBatchAuditResults,
569
+ pendingAuditResultsPath,
570
+ pendingRuntimeUpdatesPath,
571
+ pendingExternalAnalyzerPath,
572
+ };
573
+ }
574
+ return {
575
+ done: false,
576
+ lastResult,
577
+ anyProgress,
578
+ artifactsWritten,
579
+ pendingBatchAuditResults,
580
+ pendingAuditResultsPath,
581
+ pendingRuntimeUpdatesPath,
582
+ pendingExternalAnalyzerPath,
583
+ };
584
+ }
27
585
  export async function cmdRunToCompletion(argv) {
28
586
  const root = getRootDir(argv);
29
587
  warnIfNotGitRepo(root);
@@ -73,34 +631,15 @@ export async function cmdRunToCompletion(argv) {
73
631
  const decision = decideNextStep(bundle);
74
632
  // Resume interrupted parallel wave: ingest any results that workers
75
633
  // wrote before the previous process exited.
76
- const priorWave = await readWaveManifest(artifactsDir);
77
- if (priorWave) {
78
- process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
79
- let recoveredProgress = false;
80
- for (const entry of priorWave.slots) {
81
- try {
82
- const results = await readJsonFile(entry.audit_results_path);
83
- if (!results || results.length === 0)
84
- continue;
85
- const stepResult = await runAuditStep({
86
- root,
87
- artifactsDir,
88
- preferredExecutor: "result_ingestion_executor",
89
- auditResultsPath: entry.audit_results_path,
90
- });
91
- if (stepResult.progress_made) {
92
- recoveredProgress = true;
93
- anyProgress = true;
94
- for (const a of stepResult.artifacts_written)
95
- artifactsWritten.add(a);
96
- }
97
- }
98
- catch {
99
- process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
100
- }
101
- }
102
- await removeWaveManifest(artifactsDir);
103
- if (recoveredProgress)
634
+ {
635
+ const recovery = await recoverInterruptedWave({
636
+ artifactsDir,
637
+ root,
638
+ anyProgress,
639
+ artifactsWritten,
640
+ });
641
+ anyProgress = recovery.anyProgress;
642
+ if (recovery.recovered)
104
643
  continue;
105
644
  }
106
645
  if (decision.selected_executor === "agent" &&
@@ -306,52 +845,16 @@ export async function cmdRunToCompletion(argv) {
306
845
  }
307
846
  }
308
847
  const taskGroups = candidateGroups.slice(0, waveSize);
309
- const workerSlots = [];
310
- for (const rawGroup of taskGroups) {
311
- const group = await addFileLineCountHints(root, rawGroup);
312
- runCount += 1;
313
- const slotRunId = buildRunId(obligationId, runCount);
314
- const slotPaths = getRunPaths(artifactsDir, slotRunId);
315
- const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
316
- const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
317
- const slotReadPaths = new Set();
318
- for (const t of group) {
319
- for (const fp of t.file_paths)
320
- slotReadPaths.add(fp);
321
- }
322
- const slotTask = {
323
- contract_version: "audit-code-worker/v1alpha1",
324
- run_id: slotRunId,
325
- repo_root: root,
326
- artifacts_dir: artifactsDir,
327
- obligation_id: obligationId,
328
- preferred_executor: "agent",
329
- result_path: slotPaths.resultPath,
330
- worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
331
- audit_results_path: slotAuditResultsPath,
332
- pending_audit_tasks_path: slotPendingTasksPath,
333
- worker_command_mode: "deferred",
334
- timeout_ms: timeoutMs,
335
- max_retries: 0,
336
- access: {
337
- read_paths: [...slotReadPaths],
338
- write_paths: [slotAuditResultsPath, slotPaths.resultPath],
339
- },
340
- };
341
- const slotPrompt = renderWorkerPrompt(slotTask);
342
- await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
343
- await writeJsonFile(slotPendingTasksPath, group);
344
- workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
345
- }
346
- await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
347
- run_id: slot.runId,
348
- task_path: slot.paths.taskPath,
349
- prompt_path: slot.paths.promptPath,
350
- result_path: slot.paths.resultPath,
351
- status_path: slot.paths.statusPath,
352
- audit_results_path: slot.auditResultsPath,
353
- pending_audit_tasks_path: slot.pendingTasksPath,
354
- })), workerSlots.flatMap((slot) => slot.group));
848
+ const { slots: workerSlots, runCountAfter } = await buildParallelWaveSlots({
849
+ root,
850
+ artifactsDir,
851
+ selfCliPath,
852
+ taskGroups,
853
+ obligationId,
854
+ runCountStart: runCount,
855
+ timeoutMs,
856
+ });
857
+ runCount = runCountAfter;
355
858
  const parallelStartedAt = new Date().toISOString();
356
859
  await writeWaveManifest(artifactsDir, {
357
860
  obligation_id: obligationId ?? "unknown",
@@ -386,133 +889,28 @@ export async function cmdRunToCompletion(argv) {
386
889
  }
387
890
  }
388
891
  }
389
- // Result ingestion is intentionally sequential even though agent launch
390
- // was parallel. Writing to coverage_matrix.json is not atomic, so
391
- // concurrent ingest calls would race and corrupt coverage state.
392
- let batchProgress = false;
393
- const batchErrors = [];
394
- for (const slot of workerSlots) {
395
- const parallelEndedAt = new Date().toISOString();
396
- let workerResult = buildWorkerResult({
397
- runId: slot.runId,
398
- obligationId,
399
- status: "no_progress",
400
- progressMade: false,
401
- selectedExecutor: "agent",
402
- artifactsWritten: [],
403
- summary: "Parallel worker batch made no progress.",
404
- nextLikelyStep: obligationId,
405
- errors: [],
406
- });
407
- try {
408
- const launchError = launchErrorsByRunId.get(slot.runId);
409
- if (launchError) {
410
- throw new Error(`Worker launch failed: ${launchError}`);
411
- }
412
- const auditResults = await readJsonFile(slot.auditResultsPath);
413
- const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
414
- const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
415
- if (slot.group.length > 0 && matchedCount === 0) {
416
- throw new Error("Worker did not emit any audit results for the assigned tasks.");
417
- }
418
- const issues = validateAuditResults(auditResults, slot.group, {
419
- lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
420
- });
421
- const errors = issues.filter((issue) => issue.severity === "error");
422
- const warnings = issues.filter((issue) => issue.severity === "warning");
423
- if (warnings.length > 0) {
424
- process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
425
- formatAuditResultIssues(warnings) + "\n");
426
- }
427
- if (errors.length > 0) {
428
- throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
429
- formatAuditResultIssues(errors));
430
- }
431
- const stepResult = await runAuditStep({
432
- root,
433
- artifactsDir,
434
- preferredExecutor: "result_ingestion_executor",
435
- auditResultsPath: slot.auditResultsPath,
436
- });
437
- workerResult = buildWorkerResult({
438
- runId: slot.runId,
439
- obligationId,
440
- status: stepResult.progress_made ? "completed" : "no_progress",
441
- progressMade: stepResult.progress_made,
442
- selectedExecutor: stepResult.selected_executor,
443
- artifactsWritten: stepResult.artifacts_written,
444
- summary: stepResult.progress_summary,
445
- nextLikelyStep: stepResult.next_likely_step,
446
- errors: [],
447
- });
448
- batchProgress ||= stepResult.progress_made;
449
- if (stepResult.progress_made)
450
- anyProgress = true;
451
- for (const a of stepResult.artifacts_written)
452
- artifactsWritten.add(a);
453
- }
454
- catch (error) {
455
- const message = error instanceof Error ? error.message : String(error);
456
- batchErrors.push(`${slot.runId}: ${message}`);
457
- workerResult = buildWorkerResult({
458
- runId: slot.runId,
459
- obligationId,
460
- status: "failed",
461
- progressMade: false,
462
- selectedExecutor: "agent",
463
- artifactsWritten: [],
464
- summary: `Worker failed for executor agent: ${message}`,
465
- nextLikelyStep: obligationId,
466
- errors: [message],
467
- });
468
- process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
469
- }
470
- await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
471
- await appendRunLedgerEntry(artifactsDir, {
472
- run_id: slot.runId,
473
- provider: provider.name,
474
- obligation_id: obligationId,
475
- selected_executor: workerResult.selected_executor,
476
- status: workerResult.status,
477
- started_at: parallelStartedAt,
478
- ended_at: parallelEndedAt,
479
- result_path: slot.paths.resultPath,
480
- });
481
- artifactsWritten.add("run-ledger.json");
482
- }
483
- // Record outcome for adaptive learning (best-effort — never blocks dispatch)
484
- {
485
- const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
486
- const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
487
- const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
488
- await recordWaveOutcome(providerModelKey, {
489
- concurrency: workerSlots.length,
490
- estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
491
- outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
492
- cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
493
- }, sessionConfig.quota?.empirical_half_life_hours ?? 24).catch(() => undefined);
494
- }
495
- // Extract rate-limit headers from worker stderr (best-effort)
496
- {
497
- const extractor = getHeaderExtractorForProvider(provider.name);
498
- for (const slot of workerSlots) {
499
- try {
500
- const stderr = await readFile(slot.paths.stderrPath, "utf8");
501
- const extracted = extractor.extract(stderr);
502
- if (extracted && (extracted.requests_per_minute != null || extracted.input_tokens_per_minute != null)) {
503
- await updateDiscoveredLimits(providerModelKey, {
504
- requests_per_minute: extracted.requests_per_minute,
505
- input_tokens_per_minute: extracted.input_tokens_per_minute,
506
- source: "header_extraction",
507
- });
508
- break; // one successful extraction is enough
509
- }
510
- }
511
- catch {
512
- // stderr file missing or unreadable — skip
513
- }
514
- }
515
- }
892
+ const ingestion = await ingestParallelWaveResults({
893
+ root,
894
+ artifactsDir,
895
+ workerSlots,
896
+ launchErrorsByRunId,
897
+ obligationId,
898
+ parallelStartedAt,
899
+ providerName: provider.name,
900
+ anyProgress,
901
+ artifactsWritten,
902
+ });
903
+ const batchProgress = ingestion.batchProgress;
904
+ const batchErrors = ingestion.batchErrors;
905
+ anyProgress = ingestion.anyProgress;
906
+ await recordWaveQuota({
907
+ providerModelKey,
908
+ providerName: provider.name,
909
+ workerSlots,
910
+ slotTokenEstimates,
911
+ batchErrors,
912
+ halfLifeHours: sessionConfig.quota?.empirical_half_life_hours ?? 24,
913
+ });
516
914
  await removeWaveManifest(artifactsDir);
517
915
  if (batchErrors.length > 0) {
518
916
  const bundleAfter = await loadArtifactBundle(artifactsDir);
@@ -567,291 +965,68 @@ export async function cmdRunToCompletion(argv) {
567
965
  const runId = buildRunId(obligationId, runCount);
568
966
  const paths = getRunPaths(artifactsDir, runId);
569
967
  if (shouldRunInlineExecutor(preferredExecutor)) {
570
- await clearDispatchFiles(artifactsDir);
571
- const startedAt = new Date().toISOString();
572
- let workerResult;
573
- try {
574
- const result = await runAuditStep({
575
- root,
576
- artifactsDir,
577
- preferredExecutor,
578
- auditResultsPath,
579
- runtimeUpdatesPath,
580
- externalAnalyzerPath,
581
- analyzers: sessionConfig.analyzers,
582
- since: getFlag(argv, "--since"),
583
- });
584
- workerResult = {
585
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
586
- run_id: runId,
587
- obligation_id: obligationId,
588
- status: result.progress_made ? "completed" : "no_progress",
589
- progress_made: result.progress_made,
590
- selected_executor: result.selected_executor,
591
- artifacts_written: result.artifacts_written,
592
- summary: result.progress_summary,
593
- next_likely_step: result.next_likely_step,
594
- errors: [],
595
- };
596
- }
597
- catch (error) {
598
- const message = error instanceof Error ? error.message : String(error);
599
- workerResult = {
600
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
601
- run_id: runId,
602
- obligation_id: obligationId,
603
- status: "failed",
604
- progress_made: false,
605
- selected_executor: preferredExecutor,
606
- artifacts_written: [],
607
- summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
608
- next_likely_step: decision.selected_obligation,
609
- errors: [message],
610
- };
611
- }
612
- await persistWorkerRunArtifacts(paths, workerResult, "inline");
613
- await appendRunLedgerEntry(artifactsDir, {
614
- run_id: runId,
615
- provider: provider.name,
616
- obligation_id: obligationId,
617
- selected_executor: workerResult.selected_executor,
618
- status: workerResult.status,
619
- started_at: startedAt,
620
- ended_at: new Date().toISOString(),
621
- result_path: paths.resultPath,
622
- });
623
- lastResult = workerResult;
624
- if (workerResult.progress_made) {
625
- anyProgress = true;
626
- }
627
- for (const artifact of workerResult.artifacts_written) {
628
- artifactsWritten.add(artifact);
629
- }
630
- artifactsWritten.add("run-ledger.json");
631
- if (externalAnalyzerPath)
632
- pendingExternalAnalyzerPath = undefined;
633
- if (auditResultsPath &&
634
- pendingBatchAuditResults[0] === auditResultsPath &&
635
- preferredExecutor === "result_ingestion_executor" &&
636
- workerResult.status !== "failed" &&
637
- workerResult.status !== "blocked") {
638
- pendingBatchAuditResults.shift();
639
- }
640
- if (auditResultsPath)
641
- pendingAuditResultsPath = undefined;
642
- if (runtimeUpdatesPath)
643
- pendingRuntimeUpdatesPath = undefined;
644
- if (workerResult.status === "failed" ||
645
- workerResult.status === "blocked" ||
646
- workerResult.status === "no_progress") {
647
- const bundleAfter = await loadArtifactBundle(artifactsDir);
648
- const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
649
- const state = shouldBlock
650
- ? buildBlockedAuditState({
651
- state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
652
- obligationId: workerResult.obligation_id,
653
- executor: workerResult.selected_executor,
654
- blocker: buildWorkerFailureBlocker(workerResult),
655
- })
656
- : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
657
- if (shouldBlock) {
658
- await writeCoreArtifacts(artifactsDir, {
659
- ...bundleAfter,
660
- audit_state: state,
661
- });
662
- }
663
- await emitEnvelope({
664
- root,
665
- artifactsDir,
666
- bundle: shouldBlock
667
- ? { ...bundleAfter, audit_state: state }
668
- : bundleAfter,
669
- audit_state: state,
670
- selected_obligation: workerResult.obligation_id,
671
- selected_executor: workerResult.selected_executor,
672
- progress_made: anyProgress,
673
- artifacts_written: Array.from(shouldBlock
674
- ? new Set([...artifactsWritten, "audit_state.json"])
675
- : artifactsWritten),
676
- progress_summary: buildWorkerFailureBlocker(workerResult),
677
- next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
678
- providerName: provider.name,
679
- });
680
- return;
681
- }
682
- continue;
683
- }
684
- const pendingAuditTasks = preferredExecutor === "agent"
685
- ? await addFileLineCountHints(root, buildPendingAuditTasks(bundle))
686
- : undefined;
687
- const pendingAuditTasksPath = preferredExecutor === "agent"
688
- ? join(paths.runDir, "pending-audit-tasks.json")
689
- : undefined;
690
- const providerAuditResultsPath = preferredExecutor === "agent"
691
- ? join(paths.runDir, "audit-results.json")
692
- : auditResultsPath;
693
- const providerReadPaths = new Set();
694
- if (pendingAuditTasks) {
695
- for (const pt of pendingAuditTasks) {
696
- for (const fp of pt.file_paths)
697
- providerReadPaths.add(fp);
698
- }
699
- }
700
- const task = {
701
- contract_version: "audit-code-worker/v1alpha1",
702
- run_id: runId,
703
- repo_root: root,
704
- artifacts_dir: artifactsDir,
705
- obligation_id: obligationId,
706
- preferred_executor: preferredExecutor,
707
- result_path: paths.resultPath,
708
- worker_command: [
709
- process.execPath,
710
- selfCliPath,
711
- "worker-run",
712
- "--task",
713
- paths.taskPath,
714
- ],
715
- audit_results_path: providerAuditResultsPath,
716
- pending_audit_tasks_path: pendingAuditTasksPath,
717
- runtime_updates_path: runtimeUpdatesPath,
718
- external_analyzer_results_path: externalAnalyzerPath,
719
- timeout_ms: timeoutMs,
720
- max_retries: 0,
721
- access: providerReadPaths.size > 0 ? {
722
- read_paths: [...providerReadPaths],
723
- write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
724
- } : undefined,
725
- };
726
- const prompt = renderWorkerPrompt(task);
727
- await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
728
- if (pendingAuditTasksPath && pendingAuditTasks) {
729
- await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
730
- }
731
- const startedAt = new Date().toISOString();
732
- let workerResult;
733
- let launchResult = null;
734
- try {
735
- launchResult = await provider.launch({
736
- repoRoot: root,
968
+ const inline = await runInlineStep({
969
+ root,
970
+ artifactsDir,
737
971
  runId,
972
+ paths,
973
+ preferredExecutor,
738
974
  obligationId,
739
- promptPath: paths.promptPath,
740
- taskPath: paths.taskPath,
741
- resultPath: paths.resultPath,
742
- stdoutPath: paths.stdoutPath,
743
- stderrPath: paths.stderrPath,
744
- uiMode,
745
- timeoutMs,
975
+ auditResultsPath,
976
+ runtimeUpdatesPath,
977
+ externalAnalyzerPath,
978
+ analyzers: sessionConfig.analyzers,
979
+ since: getFlag(argv, "--since"),
980
+ anyProgress,
981
+ artifactsWritten,
982
+ providerName: provider.name,
983
+ decision,
984
+ pendingBatchAuditResults,
985
+ pendingAuditResultsPath,
986
+ pendingRuntimeUpdatesPath,
987
+ pendingExternalAnalyzerPath,
746
988
  });
747
- const candidate = await readJsonFile(paths.resultPath);
748
- if (isWorkerResult(candidate)) {
749
- workerResult = candidate;
750
- }
751
- else {
752
- const launchExitSummary = summarizeLaunchExit(launchResult);
753
- workerResult = {
754
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
755
- run_id: runId,
756
- obligation_id: obligationId,
757
- status: "failed",
758
- progress_made: false,
759
- selected_executor: preferredExecutor,
760
- artifacts_written: [],
761
- summary: launchExitSummary
762
- ? `Worker did not emit a valid worker result after provider exit: ${launchExitSummary}`
763
- : "Worker did not emit a valid worker result.",
764
- next_likely_step: decision.selected_obligation,
765
- errors: ["Invalid worker result contract."],
766
- };
767
- }
768
- }
769
- catch (error) {
770
- const message = error instanceof Error ? error.message : String(error);
771
- const launchExitSummary = launchResult && summarizeLaunchExit(launchResult);
772
- workerResult = {
773
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
774
- run_id: runId,
775
- obligation_id: obligationId,
776
- status: "failed",
777
- progress_made: false,
778
- selected_executor: preferredExecutor,
779
- artifacts_written: [],
780
- summary: `Worker launch failed for ${preferredExecutor}: ${launchExitSummary ?? message}`,
781
- next_likely_step: decision.selected_obligation,
782
- errors: launchExitSummary ? [message, launchExitSummary] : [message],
783
- };
784
- await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
989
+ lastResult = inline.lastResult;
990
+ anyProgress = inline.anyProgress;
991
+ pendingBatchAuditResults = inline.pendingBatchAuditResults;
992
+ pendingAuditResultsPath = inline.pendingAuditResultsPath;
993
+ pendingRuntimeUpdatesPath = inline.pendingRuntimeUpdatesPath;
994
+ pendingExternalAnalyzerPath = inline.pendingExternalAnalyzerPath;
995
+ if (inline.done)
996
+ return;
997
+ continue;
785
998
  }
786
- await appendRunLedgerEntry(artifactsDir, {
787
- run_id: runId,
788
- provider: provider.name,
789
- obligation_id: obligationId,
790
- selected_executor: workerResult.selected_executor,
791
- status: workerResult.status,
792
- started_at: startedAt,
793
- ended_at: new Date().toISOString(),
794
- result_path: paths.resultPath,
999
+ const single = await runSingleWorkerStep({
1000
+ root,
1001
+ artifactsDir,
1002
+ selfCliPath,
1003
+ runId,
1004
+ paths,
1005
+ preferredExecutor,
1006
+ obligationId,
1007
+ auditResultsPath,
1008
+ runtimeUpdatesPath,
1009
+ externalAnalyzerPath,
1010
+ bundle,
1011
+ timeoutMs,
1012
+ uiMode,
1013
+ provider,
1014
+ anyProgress,
1015
+ artifactsWritten,
1016
+ decision,
1017
+ pendingBatchAuditResults,
1018
+ pendingAuditResultsPath,
1019
+ pendingRuntimeUpdatesPath,
1020
+ pendingExternalAnalyzerPath,
795
1021
  });
796
- lastResult = workerResult;
797
- if (workerResult.progress_made) {
798
- anyProgress = true;
799
- }
800
- for (const artifact of workerResult.artifacts_written) {
801
- artifactsWritten.add(artifact);
802
- }
803
- artifactsWritten.add("run-ledger.json");
804
- if (externalAnalyzerPath)
805
- pendingExternalAnalyzerPath = undefined;
806
- if (auditResultsPath &&
807
- pendingBatchAuditResults[0] === auditResultsPath &&
808
- preferredExecutor === "result_ingestion_executor" &&
809
- workerResult.status !== "failed" &&
810
- workerResult.status !== "blocked") {
811
- pendingBatchAuditResults.shift();
812
- }
813
- if (providerAuditResultsPath)
814
- pendingAuditResultsPath = undefined;
815
- if (runtimeUpdatesPath)
816
- pendingRuntimeUpdatesPath = undefined;
817
- if (workerResult.status === "failed" ||
818
- workerResult.status === "blocked" ||
819
- workerResult.status === "no_progress") {
820
- const bundleAfter = await loadArtifactBundle(artifactsDir);
821
- const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
822
- const state = shouldBlock
823
- ? buildBlockedAuditState({
824
- state: deriveAuditState(bundleAfter),
825
- obligationId: workerResult.obligation_id,
826
- executor: workerResult.selected_executor,
827
- blocker: buildWorkerFailureBlocker(workerResult),
828
- })
829
- : deriveAuditState(bundleAfter);
830
- if (shouldBlock) {
831
- await writeCoreArtifacts(artifactsDir, {
832
- ...bundleAfter,
833
- audit_state: state,
834
- });
835
- }
836
- await emitEnvelope({
837
- root,
838
- artifactsDir,
839
- bundle: shouldBlock
840
- ? { ...bundleAfter, audit_state: state }
841
- : bundleAfter,
842
- audit_state: state,
843
- selected_obligation: workerResult.obligation_id,
844
- selected_executor: workerResult.selected_executor,
845
- progress_made: anyProgress,
846
- artifacts_written: Array.from(shouldBlock
847
- ? new Set([...artifactsWritten, "audit_state.json"])
848
- : artifactsWritten),
849
- progress_summary: buildWorkerFailureBlocker(workerResult),
850
- next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
851
- providerName: provider.name,
852
- });
1022
+ lastResult = single.lastResult;
1023
+ anyProgress = single.anyProgress;
1024
+ pendingBatchAuditResults = single.pendingBatchAuditResults;
1025
+ pendingAuditResultsPath = single.pendingAuditResultsPath;
1026
+ pendingRuntimeUpdatesPath = single.pendingRuntimeUpdatesPath;
1027
+ pendingExternalAnalyzerPath = single.pendingExternalAnalyzerPath;
1028
+ if (single.done)
853
1029
  return;
854
- }
855
1030
  }
856
1031
  const bundle = await loadArtifactBundle(artifactsDir);
857
1032
  const decision = decideNextStep(bundle);