auditor-lambda 0.6.10 → 0.6.11

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.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
- import { mkdir, readFile, readdir, rm, unlink, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, readdir, rm, } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { join, resolve, } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { buildRepoManifest } from "./extractors/fileInventory.js";
6
6
  import { buildFileDisposition } from "./extractors/disposition.js";
@@ -10,46 +10,33 @@ import { buildUnitManifest } from "./orchestrator/unitBuilder.js";
10
10
  import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
11
11
  import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
12
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
13
- import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, AUDIT_REPORT_FILENAME, } from "./io/artifacts.js";
14
- import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues } from "@audit-tools/shared";
13
+ import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
14
+ import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, } from "@audit-tools/shared";
15
15
  import { buildQuotaSource } from "@audit-tools/shared/quota/compositeQuotaSource";
16
16
  import { validateArtifactBundle } from "./validation/artifacts.js";
17
17
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
18
18
  import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
19
19
  import { buildAuditReportModel, renderAuditReportMarkdown, } from "./reporting/synthesis.js";
20
20
  import { deriveAuditState } from "./orchestrator/state.js";
21
- import { advanceAudit } from "./orchestrator/advance.js";
22
- import { checkFileIntegrity } from "./orchestrator/fileIntegrity.js";
23
- import { decideNextStep } from "./orchestrator/nextStep.js";
24
- import { collectLowConfidenceEdges, buildEdgeReasoningPrompt, edgeReasoningContentHash, } from "./orchestrator/edgeReasoning.js";
25
- import { renderDesignReviewPrompt } from "./orchestrator/designReviewPrompt.js";
26
- import { renderSynthesisNarrativePrompt } from "./reporting/synthesisNarrativePrompt.js";
27
21
  import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
28
- import { appendRunLedgerEntry, loadRunLedger } from "./supervisor/runLedger.js";
29
- import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
30
- import { getSessionConfigPath, loadSessionConfig, persistAnalyzerSettings, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
31
- import { resolveAnalyzerPlan, needsInstallDecision, } from "./extractors/analyzers/registry.js";
32
- import { buildPathLookup } from "./extractors/graph.js";
33
- import { buildDispositionMap } from "./extractors/disposition.js";
34
- import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
35
- import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
36
- import { estimateTaskGroupTokens, sizeIndexFromManifest, } from "./orchestrator/reviewPackets.js";
37
- import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
22
+ import { loadRunLedger, } from "./supervisor/runLedger.js";
23
+ import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
24
+ import { clearDispatchFiles, ensureSupervisorDirs, } from "./io/runArtifacts.js";
38
25
  import { runAuditCodeMcpServer } from "./mcp/server.js";
39
- import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
26
+ import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, lookupDiscoveredLimits, setQuotaStateDir, } from "./quota/index.js";
40
27
  // Re-exports from extracted modules
41
28
  export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
42
- import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, fromBase64Url, renderCommand, summarizeLaunchExit, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, resolveHostDispatchCapability, countLines, listBatchResultFiles, } from "./cli/args.js";
43
- import { nextStepCommand, mergeAndIngestCommand, renderDispatchReviewPrompt, renderSingleTaskFallbackStepPrompt, renderPresentReportPrompt, renderAnalyzerInstallPrompt, renderEdgeReasoningStepPrompt, renderEdgeReasoningDispatchPrompt, renderBlockedStepPrompt, } from "./cli/prompts.js";
44
- import { writeCurrentStep, } from "./cli/steps.js";
45
- import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArtifacts, isWorkerResult, buildWorkerFailureBlocker, formatAuditResultValidationError, } from "./cli/workerResult.js";
29
+ import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
30
+ import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, formatAuditResultValidationError, } from "./cli/workerResult.js";
46
31
  import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
47
- import { readWaveManifest, writeWaveManifest, removeWaveManifest, buildWaveSlotEntry, } from "./cli/waveManifest.js";
48
32
  import { buildLineIndex, buildLineIndexForPaths, addFileLineCountHints, } from "./cli/lineIndex.js";
49
- import { emitEnvelope, buildBlockedAuditState, buildManualReviewBlocker, shouldRunInlineExecutor, } from "./cli/envelope.js";
50
- import { writeHandoffOnly, ensureSemanticReviewRun, } from "./cli/reviewRun.js";
33
+ import { emitEnvelope, } from "./cli/envelope.js";
34
+ import { persistConfigErrorHandoff } from "./cli/reviewRun.js";
51
35
  import { runAuditStep, ingestBatchAuditResults, } from "./cli/auditStep.js";
52
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
36
+ import { packageRoot } from "./cli/paths.js";
37
+ import { cmdNextStep } from "./cli/nextStepCommand.js";
38
+ import { cmdRunToCompletion } from "./cli/runToCompletion.js";
39
+ import { cleanupStaleArtifactsDir } from "./cli/cleanup.js";
53
40
  const SAMPLE_REPO_FILES = [
54
41
  { path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
55
42
  { path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
@@ -73,28 +60,6 @@ export const cliTestUtils = {
73
60
  countLines,
74
61
  warnIfNotGitRepo,
75
62
  };
76
- async function persistConfigErrorHandoff(params) {
77
- const bundle = await loadArtifactBundle(params.artifactsDir);
78
- const blockedState = buildBlockedAuditState({
79
- state: bundle.audit_state ?? deriveAuditState(bundle),
80
- obligationId: null,
81
- executor: null,
82
- blocker: params.progressSummary,
83
- });
84
- await writeCoreArtifacts(params.artifactsDir, {
85
- ...bundle,
86
- audit_state: blockedState,
87
- });
88
- const handoff = buildAuditCodeHandoff({
89
- root: params.root,
90
- artifactsDir: params.artifactsDir,
91
- state: blockedState,
92
- bundle: { ...bundle, audit_state: blockedState },
93
- progressSummary: params.progressSummary,
94
- isConfigError: true,
95
- });
96
- await writeAuditCodeHandoffArtifacts(handoff);
97
- }
98
63
  export async function runSample(argv = process.argv) {
99
64
  const repoManifest = buildRepoManifest("sample-repo", SAMPLE_REPO_FILES);
100
65
  const disposition = buildFileDisposition(repoManifest);
@@ -255,1482 +220,6 @@ async function cmdAdvanceAudit(argv) {
255
220
  await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
256
221
  }
257
222
  }
258
- async function runDeterministicForNextStep(params) {
259
- let lastSummary = "";
260
- let analyzers = params.analyzers;
261
- for (let index = 0; index < params.maxRuns; index++) {
262
- const bundle = await loadArtifactBundle(params.artifactsDir);
263
- const decision = decideNextStep(bundle);
264
- const state = decision.state;
265
- if (state.status === "complete") {
266
- await writeHandoffOnly({
267
- root: params.root,
268
- artifactsDir: params.artifactsDir,
269
- bundle,
270
- audit_state: state,
271
- progress_summary: decision.reason,
272
- providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
273
- });
274
- const promoted = await promoteFinalAuditReport({
275
- artifactsDir: params.artifactsDir,
276
- repoRoot: params.root,
277
- });
278
- return {
279
- kind: "complete",
280
- state,
281
- bundle,
282
- finalReportPath: promoted.promoted
283
- ? join(params.root, AUDIT_REPORT_FILENAME)
284
- : join(params.artifactsDir, AUDIT_REPORT_FILENAME),
285
- };
286
- }
287
- if (index === 0 && bundle.repo_manifest) {
288
- const pendingTasks = buildPendingAuditTasks(bundle);
289
- const taskFiles = new Set();
290
- for (const task of pendingTasks) {
291
- for (const fp of Object.keys(task.file_line_counts ?? {}))
292
- taskFiles.add(fp);
293
- }
294
- if (taskFiles.size > 0) {
295
- const integrity = await checkFileIntegrity(params.root, bundle.repo_manifest, [...taskFiles]);
296
- if (!integrity.is_clean) {
297
- console.log(`File integrity check: ${integrity.changed_files.length} changed, ${integrity.missing_files.length} missing — re-running intake.`);
298
- await advanceAudit(bundle, { root: params.root, preferredExecutor: "intake_executor", opentoken: params.opentoken });
299
- continue;
300
- }
301
- }
302
- }
303
- if (decision.selected_executor === "graph_enrichment_executor") {
304
- const includedFiles = bundle.repo_manifest
305
- ? [
306
- ...new Set(buildPathLookup(bundle.repo_manifest, buildDispositionMap(bundle.file_disposition)).values()),
307
- ]
308
- : [];
309
- const plan = resolveAnalyzerPlan(params.root, analyzers, includedFiles);
310
- const unresolved = plan.filter(needsInstallDecision);
311
- if (unresolved.length > 0) {
312
- const decisionsPath = join(params.artifactsDir, "incoming", "analyzer-decisions.json");
313
- let decisions;
314
- try {
315
- decisions = await readJsonFile(decisionsPath);
316
- }
317
- catch (error) {
318
- if (!isFileMissingError(error))
319
- throw error;
320
- }
321
- if (decisions && typeof decisions === "object") {
322
- const settings = {};
323
- for (const [id, value] of Object.entries(decisions)) {
324
- if (value === "ephemeral" ||
325
- value === "permanent" ||
326
- value === "skip" ||
327
- value === "repo" ||
328
- value === "auto") {
329
- settings[id] = value;
330
- }
331
- }
332
- if (Object.keys(settings).length > 0) {
333
- const merged = await persistAnalyzerSettings(params.artifactsDir, settings);
334
- analyzers = merged.analyzers;
335
- }
336
- await unlink(decisionsPath).catch(() => { });
337
- continue;
338
- }
339
- return {
340
- kind: "analyzer_install",
341
- state,
342
- bundle,
343
- unresolved,
344
- };
345
- }
346
- // Phase 4B — optional edge-reasoning producing turn. Once analyzer installs
347
- // are resolved, if the flag is on and the floor carries low-confidence
348
- // (< 0.65) edges, emit one bounded host turn (subagent dispatch or a single
349
- // host step) to produce reason rewrites, then re-run. The enrichment
350
- // executor applies the host-supplied rewrites in the SAME advanceAudit call
351
- // that merges analyzer edges and writes analyzer_capability, so graph_bundle
352
- // and its marker stay revision-consistent (no staleness loop). Flag off or
353
- // no candidates → fall through and run the executor with no rewrites.
354
- if (params.graphLlmEdgeReasoning === true && bundle.graph_bundle) {
355
- const candidates = collectLowConfidenceEdges(bundle.graph_bundle);
356
- if (candidates.length > 0) {
357
- const edgeReasoningResultsPath = join(params.artifactsDir, "incoming", "edge-reasoning.json");
358
- let edgeReasoningResults;
359
- try {
360
- edgeReasoningResults = await readJsonFile(edgeReasoningResultsPath);
361
- }
362
- catch (error) {
363
- if (!isFileMissingError(error))
364
- throw error;
365
- }
366
- if (edgeReasoningResults) {
367
- await runAuditStep({
368
- root: params.root,
369
- artifactsDir: params.artifactsDir,
370
- analyzers,
371
- graphLlmEdgeReasoning: true,
372
- edgeReasoningResultsPath,
373
- since: params.since,
374
- opentoken: params.opentoken,
375
- });
376
- await unlink(edgeReasoningResultsPath).catch(() => { });
377
- continue;
378
- }
379
- return { kind: "edge_reasoning", state, bundle, candidates };
380
- }
381
- }
382
- // No undecided installs (and no pending edge reasoning): fall through to run
383
- // the executor below (it installs for ephemeral/permanent, uses repo/cache,
384
- // skips the rest).
385
- }
386
- if (decision.selected_executor === "design_review") {
387
- const findingsPath = join(params.artifactsDir, "incoming", "design-review-findings.json");
388
- let reviewFindings;
389
- try {
390
- reviewFindings = await readJsonFile(findingsPath);
391
- }
392
- catch (error) {
393
- if (!isFileMissingError(error))
394
- throw error;
395
- }
396
- if (reviewFindings && Array.isArray(reviewFindings)) {
397
- const existing = bundle.design_assessment;
398
- if (existing) {
399
- existing.review_findings = reviewFindings;
400
- existing.reviewed = true;
401
- await writeJsonFile(join(params.artifactsDir, "design_assessment.json"), existing);
402
- await unlink(findingsPath).catch(() => { });
403
- continue;
404
- }
405
- }
406
- return {
407
- kind: "design_review",
408
- state,
409
- bundle,
410
- };
411
- }
412
- if (decision.selected_executor === "synthesis_narrative_executor") {
413
- const narrativePath = join(params.artifactsDir, "incoming", "synthesis-narrative.json");
414
- let narrativeResults;
415
- try {
416
- narrativeResults = await readJsonFile(narrativePath);
417
- }
418
- catch (error) {
419
- if (!isFileMissingError(error))
420
- throw error;
421
- }
422
- if (narrativeResults) {
423
- await runAuditStep({
424
- root: params.root,
425
- artifactsDir: params.artifactsDir,
426
- preferredExecutor: "synthesis_narrative_executor",
427
- narrativeResultsPath: narrativePath,
428
- opentoken: params.opentoken,
429
- });
430
- await unlink(narrativePath).catch(() => { });
431
- continue;
432
- }
433
- if (params.narrativeEnabled) {
434
- return {
435
- kind: "synthesis_narrative",
436
- state,
437
- bundle,
438
- };
439
- }
440
- // Narrative disabled: fall through so the deterministic omit runs below.
441
- }
442
- if (decision.selected_executor === "agent") {
443
- return {
444
- kind: "semantic_review",
445
- ...(await ensureSemanticReviewRun({
446
- root: params.root,
447
- artifactsDir: params.artifactsDir,
448
- bundle,
449
- state,
450
- obligationId: decision.selected_obligation,
451
- selfCliPath: params.selfCliPath,
452
- timeoutMs: params.timeoutMs,
453
- })),
454
- };
455
- }
456
- if (!decision.selected_executor) {
457
- await writeHandoffOnly({
458
- root: params.root,
459
- artifactsDir: params.artifactsDir,
460
- bundle,
461
- audit_state: state,
462
- progress_summary: lastSummary || decision.reason,
463
- providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
464
- });
465
- return {
466
- kind: "blocked",
467
- state,
468
- bundle,
469
- reason: lastSummary || decision.reason,
470
- };
471
- }
472
- let result;
473
- try {
474
- result = await runAuditStep({
475
- root: params.root,
476
- artifactsDir: params.artifactsDir,
477
- analyzers,
478
- graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
479
- since: params.since,
480
- opentoken: params.opentoken,
481
- });
482
- }
483
- catch (error) {
484
- const current = await loadArtifactBundle(params.artifactsDir);
485
- const currentState = deriveAuditState(current);
486
- currentState.last_executor = decision.selected_executor ?? undefined;
487
- currentState.last_obligation = decision.selected_obligation ?? undefined;
488
- await writeCoreArtifacts(params.artifactsDir, { ...current, audit_state: currentState });
489
- await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
490
- iteration: index + 1,
491
- max_runs: params.maxRuns,
492
- last_executor: decision.selected_executor,
493
- last_obligation: decision.selected_obligation,
494
- prior_summary: lastSummary || null,
495
- error: error instanceof Error ? error.message : String(error),
496
- timestamp: new Date().toISOString(),
497
- });
498
- const detail = error instanceof Error ? error.message : String(error);
499
- 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 });
500
- }
501
- lastSummary = result.progress_summary;
502
- await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
503
- iteration: index + 1,
504
- max_runs: params.maxRuns,
505
- last_executor: result.selected_executor,
506
- last_obligation: decision.selected_obligation,
507
- progress_made: result.progress_made,
508
- summary: result.progress_summary,
509
- timestamp: new Date().toISOString(),
510
- });
511
- if (result.selected_executor !== "agent") {
512
- await clearDispatchFiles(params.artifactsDir);
513
- }
514
- if (!result.progress_made) {
515
- return {
516
- kind: "blocked",
517
- state: result.audit_state,
518
- bundle: result.updated_bundle,
519
- reason: result.progress_summary,
520
- };
521
- }
522
- }
523
- const bundle = await loadArtifactBundle(params.artifactsDir);
524
- const state = deriveAuditState(bundle);
525
- return {
526
- kind: "blocked",
527
- state,
528
- bundle,
529
- reason: `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`,
530
- };
531
- }
532
- // Renders the actionable semantic-review step (packet dispatch or single-task
533
- // fallback) and writes steps/current-step.json. Shared by next-step and
534
- // run-to-completion so the backend produces the actionable step itself rather
535
- // than handing the host a second command. Host dispatch capability is resolved
536
- // by the caller (flag -> session config -> env -> default true) and is never
537
- // required from the host to make progress.
538
- async function renderSemanticReviewStep(params) {
539
- const { root, artifactsDir, activeReviewRun } = params;
540
- if (!params.hostCanDispatch) {
541
- const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
542
- const workerCommand = renderCommand(activeReviewRun.worker_command);
543
- return writeCurrentStep({
544
- artifactsDir,
545
- stepKind: "single_task_fallback",
546
- status: "ready",
547
- runId: activeReviewRun.run_id,
548
- allowedCommands: [workerCommand],
549
- stopCondition: "Run the exact worker_command after one result, then stop without looping.",
550
- repoRoot: root,
551
- artifactPaths: {
552
- active_review_task: activeReviewRun.task_path,
553
- active_review_prompt: activeReviewRun.prompt_path,
554
- pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
555
- audit_results: activeReviewRun.audit_results_path,
556
- single_task_prompt: singleTaskPromptPath,
557
- },
558
- prompt: renderSingleTaskFallbackStepPrompt({
559
- singleTaskPromptPath,
560
- activeReviewRun,
561
- }),
562
- access: {
563
- read_paths: [singleTaskPromptPath],
564
- write_paths: [activeReviewRun.audit_results_path],
565
- },
566
- });
567
- }
568
- const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
569
- const provider = createFreshSessionProvider(undefined, sessionConfig);
570
- const dispatch = await prepareDispatchArtifacts({
571
- packageRoot,
572
- runId: activeReviewRun.run_id,
573
- artifactsDir,
574
- root,
575
- sessionConfig,
576
- hostModel: sessionConfig.block_quota?.host_model ?? null,
577
- queryLimits: provider.queryLimits?.bind(provider),
578
- hostActiveSubagentLimit: params.hostMaxActiveSubagents,
579
- });
580
- const mergeCommand = mergeAndIngestCommand(artifactsDir, activeReviewRun.run_id);
581
- const continueCommand = nextStepCommand(root, artifactsDir);
582
- return writeCurrentStep({
583
- artifactsDir,
584
- stepKind: "dispatch_review",
585
- status: "ready",
586
- runId: activeReviewRun.run_id,
587
- allowedCommands: [mergeCommand, continueCommand],
588
- allowedMcpTools: ["auditor_merge_and_ingest", "auditor_continue_audit"],
589
- progress: {
590
- summary: `Dispatching ${dispatch.packet_count} review packet(s) covering ` +
591
- `${dispatch.task_count} task(s) in waves of ${dispatch.wave_size}` +
592
- (dispatch.skipped_task_count > 0
593
- ? `; ${dispatch.skipped_task_count} task(s) already completed.`
594
- : "."),
595
- pending_packets: dispatch.packet_count,
596
- pending_tasks: dispatch.task_count,
597
- completed_tasks: dispatch.skipped_task_count,
598
- wave_size: dispatch.wave_size,
599
- },
600
- stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
601
- repoRoot: root,
602
- artifactPaths: {
603
- dispatch_plan: dispatch.dispatch_plan_path,
604
- dispatch_quota: dispatch.dispatch_quota_path,
605
- dispatch_warnings: dispatch.dispatch_warnings_path,
606
- active_review_task: activeReviewRun.task_path,
607
- pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
608
- },
609
- prompt: renderDispatchReviewPrompt({
610
- root,
611
- artifactsDir,
612
- activeReviewRun,
613
- dispatchPlanPath: dispatch.dispatch_plan_path,
614
- dispatchQuotaPath: dispatch.dispatch_quota_path,
615
- hostCanRestrictSubagentTools: params.hostCanRestrictSubagentTools,
616
- hostCanSelectSubagentModel: params.hostCanSelectSubagentModel,
617
- }),
618
- access: {
619
- read_paths: [
620
- dispatch.dispatch_plan_path,
621
- ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
622
- ],
623
- write_paths: [],
624
- },
625
- });
626
- }
627
- async function cmdNextStep(argv) {
628
- const root = getRootDir(argv);
629
- warnIfNotGitRepo(root);
630
- const artifactsDir = getArtifactsDir(argv);
631
- await mkdir(artifactsDir, { recursive: true });
632
- await ensureSupervisorDirs(artifactsDir);
633
- const hostCanDispatchSubagents = getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents");
634
- const hostCanRestrictSubagentTools = getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ??
635
- false;
636
- const hostCanSelectSubagentModel = getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false;
637
- const hostMaxActiveSubagents = getHostMaxActiveSubagents(argv);
638
- let sessionConfig;
639
- try {
640
- sessionConfig = await loadSessionConfig(artifactsDir);
641
- }
642
- catch (error) {
643
- const reason = error instanceof Error ? error.message : String(error);
644
- await persistConfigErrorHandoff({
645
- root,
646
- artifactsDir,
647
- progressSummary: reason,
648
- });
649
- const step = await writeCurrentStep({
650
- artifactsDir,
651
- stepKind: "blocked",
652
- status: "blocked",
653
- runId: null,
654
- allowedCommands: [],
655
- stopCondition: "Report the configuration blocker and stop.",
656
- repoRoot: root,
657
- artifactPaths: {
658
- operator_handoff: join(artifactsDir, "operator-handoff.json"),
659
- },
660
- prompt: renderBlockedStepPrompt(reason),
661
- });
662
- console.log(JSON.stringify(step, null, 2));
663
- return;
664
- }
665
- const hostCanDispatch = resolveHostDispatchCapability({
666
- explicit: hostCanDispatchSubagents,
667
- sessionConfig,
668
- });
669
- const result = await runDeterministicForNextStep({
670
- root,
671
- artifactsDir,
672
- selfCliPath: resolve(argv[1] ?? process.argv[1] ?? ""),
673
- timeoutMs: getTimeoutMs(argv, sessionConfig),
674
- maxRuns: getMaxRuns(argv),
675
- opentoken: sessionConfig.opentoken?.enabled,
676
- narrativeEnabled: sessionConfig.synthesis?.narrative !== false,
677
- analyzers: sessionConfig.analyzers,
678
- graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
679
- since: getFlag(argv, "--since"),
680
- });
681
- if (result.kind === "complete") {
682
- const step = await writeCurrentStep({
683
- artifactsDir,
684
- stepKind: "present_report",
685
- status: "complete",
686
- runId: null,
687
- allowedCommands: [],
688
- stopCondition: "Present the final report and stop.",
689
- repoRoot: root,
690
- artifactPaths: {
691
- final_report: result.finalReportPath,
692
- },
693
- prompt: renderPresentReportPrompt(result.finalReportPath),
694
- });
695
- console.log(JSON.stringify(step, null, 2));
696
- return;
697
- }
698
- if (result.kind === "blocked") {
699
- const step = await writeCurrentStep({
700
- artifactsDir,
701
- stepKind: "blocked",
702
- status: "blocked",
703
- runId: null,
704
- allowedCommands: [],
705
- stopCondition: "Report the blocker and stop.",
706
- repoRoot: root,
707
- artifactPaths: {
708
- operator_handoff: join(artifactsDir, "operator-handoff.json"),
709
- },
710
- prompt: renderBlockedStepPrompt(result.reason),
711
- });
712
- console.log(JSON.stringify(step, null, 2));
713
- return;
714
- }
715
- if (result.kind === "design_review") {
716
- const designReviewResultsPath = join(artifactsDir, "incoming", "design-review-findings.json");
717
- await mkdir(join(artifactsDir, "incoming"), { recursive: true });
718
- const continueCommand = nextStepCommand(root, artifactsDir);
719
- const prompt = renderDesignReviewPrompt(result.bundle);
720
- const fullPrompt = [
721
- prompt,
722
- "## Results path",
723
- "",
724
- `Write the JSON array of findings to:`,
725
- "",
726
- ` ${designReviewResultsPath}`,
727
- "",
728
- `Then run: ${continueCommand}`,
729
- "",
730
- ].join("\n");
731
- const step = await writeCurrentStep({
732
- artifactsDir,
733
- stepKind: "design_review",
734
- status: "ready",
735
- runId: null,
736
- allowedCommands: [continueCommand],
737
- stopCondition: "Write design review findings to the results path, then run next-step.",
738
- repoRoot: root,
739
- artifactPaths: {
740
- design_review_results: designReviewResultsPath,
741
- },
742
- prompt: fullPrompt,
743
- });
744
- console.log(JSON.stringify(step, null, 2));
745
- return;
746
- }
747
- if (result.kind === "analyzer_install") {
748
- const decisionsPath = join(artifactsDir, "incoming", "analyzer-decisions.json");
749
- await mkdir(join(artifactsDir, "incoming"), { recursive: true });
750
- const continueCommand = nextStepCommand(root, artifactsDir);
751
- const step = await writeCurrentStep({
752
- artifactsDir,
753
- stepKind: "analyzer_install",
754
- status: "ready",
755
- runId: null,
756
- allowedCommands: [continueCommand],
757
- stopCondition: "Write analyzer install decisions to the results path, then run next-step.",
758
- repoRoot: root,
759
- artifactPaths: {
760
- analyzer_decisions: decisionsPath,
761
- },
762
- prompt: renderAnalyzerInstallPrompt({
763
- unresolved: result.unresolved,
764
- decisionsPath,
765
- continueCommand,
766
- }),
767
- });
768
- console.log(JSON.stringify(step, null, 2));
769
- return;
770
- }
771
- if (result.kind === "edge_reasoning") {
772
- await mkdir(join(artifactsDir, "incoming"), { recursive: true });
773
- const edgeReasoningResultsPath = join(artifactsDir, "incoming", "edge-reasoning.json");
774
- const continueCommand = nextStepCommand(root, artifactsDir);
775
- const basePrompt = buildEdgeReasoningPrompt(result.candidates);
776
- const contentHash = edgeReasoningContentHash(result.candidates);
777
- if (hostCanDispatch) {
778
- // Dispatch path: isolate the (potentially large) edge-list prompt in a file
779
- // and have the host fan it out to one subagent, mirroring the packet review
780
- // dispatch contract. The subagent writes the rewrites file; next-step applies.
781
- const edgeReasoningPromptPath = join(artifactsDir, "incoming", "edge-reasoning-prompt.md");
782
- await writeFile(edgeReasoningPromptPath, basePrompt, "utf8");
783
- const step = await writeCurrentStep({
784
- artifactsDir,
785
- stepKind: "edge_reasoning_dispatch",
786
- status: "ready",
787
- runId: null,
788
- allowedCommands: [continueCommand],
789
- stopCondition: "Dispatch one subagent to write the edge-reasoning rewrites, then run next-step.",
790
- repoRoot: root,
791
- artifactPaths: {
792
- edge_reasoning_prompt: edgeReasoningPromptPath,
793
- edge_reasoning_results: edgeReasoningResultsPath,
794
- },
795
- prompt: renderEdgeReasoningDispatchPrompt({
796
- promptPath: edgeReasoningPromptPath,
797
- resultsPath: edgeReasoningResultsPath,
798
- continueCommand,
799
- contentHash,
800
- candidateCount: result.candidates.length,
801
- }),
802
- access: {
803
- read_paths: [edgeReasoningPromptPath],
804
- write_paths: [edgeReasoningResultsPath],
805
- },
806
- });
807
- console.log(JSON.stringify(step, null, 2));
808
- return;
809
- }
810
- // One-step fallback (no callable subagent facility): the host produces the
811
- // rewrites itself in a single bounded turn, mirroring the narrative step.
812
- const step = await writeCurrentStep({
813
- artifactsDir,
814
- stepKind: "edge_reasoning",
815
- status: "ready",
816
- runId: null,
817
- allowedCommands: [continueCommand],
818
- stopCondition: "Write the edge-reasoning rewrites to the results path, then run next-step.",
819
- repoRoot: root,
820
- artifactPaths: {
821
- edge_reasoning_results: edgeReasoningResultsPath,
822
- },
823
- prompt: renderEdgeReasoningStepPrompt({
824
- basePrompt,
825
- resultsPath: edgeReasoningResultsPath,
826
- continueCommand,
827
- contentHash,
828
- }),
829
- access: {
830
- read_paths: [],
831
- write_paths: [edgeReasoningResultsPath],
832
- },
833
- });
834
- console.log(JSON.stringify(step, null, 2));
835
- return;
836
- }
837
- if (result.kind === "synthesis_narrative") {
838
- const narrativeResultsPath = join(artifactsDir, "incoming", "synthesis-narrative.json");
839
- await mkdir(join(artifactsDir, "incoming"), { recursive: true });
840
- const continueCommand = nextStepCommand(root, artifactsDir);
841
- const basePrompt = result.bundle.audit_findings
842
- ? renderSynthesisNarrativePrompt(result.bundle.audit_findings)
843
- : "# Synthesis narrative\n\nNo findings report is available; write an empty themes array.";
844
- const fullPrompt = [
845
- basePrompt,
846
- "## Results path",
847
- "",
848
- "Write the SynthesisNarrative JSON object to:",
849
- "",
850
- ` ${narrativeResultsPath}`,
851
- "",
852
- `Then run: ${continueCommand}`,
853
- "",
854
- ].join("\n");
855
- const step = await writeCurrentStep({
856
- artifactsDir,
857
- stepKind: "synthesis_narrative",
858
- status: "ready",
859
- runId: null,
860
- allowedCommands: [continueCommand],
861
- stopCondition: "Write the synthesis narrative to the results path, then run next-step.",
862
- repoRoot: root,
863
- artifactPaths: {
864
- synthesis_narrative_results: narrativeResultsPath,
865
- },
866
- prompt: fullPrompt,
867
- });
868
- console.log(JSON.stringify(step, null, 2));
869
- return;
870
- }
871
- const step = await renderSemanticReviewStep({
872
- root,
873
- artifactsDir,
874
- activeReviewRun: result.activeReviewRun,
875
- hostCanDispatch,
876
- hostMaxActiveSubagents,
877
- hostCanRestrictSubagentTools,
878
- hostCanSelectSubagentModel,
879
- });
880
- console.log(JSON.stringify(step, null, 2));
881
- }
882
- async function cmdRunToCompletion(argv) {
883
- const root = getRootDir(argv);
884
- warnIfNotGitRepo(root);
885
- const artifactsDir = getArtifactsDir(argv);
886
- await cleanupStaleArtifactsDir(artifactsDir);
887
- await mkdir(artifactsDir, { recursive: true });
888
- await ensureSupervisorDirs(artifactsDir);
889
- let sessionConfig;
890
- try {
891
- sessionConfig = await loadSessionConfig(artifactsDir);
892
- }
893
- catch (error) {
894
- await persistConfigErrorHandoff({
895
- root,
896
- artifactsDir,
897
- progressSummary: error instanceof Error ? error.message : String(error),
898
- });
899
- throw error;
900
- }
901
- const explicitProvider = getExplicitProvider(argv);
902
- const provider = createFreshSessionProvider(explicitProvider, sessionConfig);
903
- const uiMode = getUiMode(argv, sessionConfig.ui_mode ?? "headless");
904
- const maxRuns = getMaxRuns(argv);
905
- const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
906
- const parallelWorkers = getParallelWorkers(argv, sessionConfig);
907
- const timeoutMs = getTimeoutMs(argv, sessionConfig);
908
- const hostModel = getHostModel(argv);
909
- const selfCliPath = resolve(argv[1] ?? process.argv[1] ?? "");
910
- const batchResultsDir = getBatchResultsDir(argv);
911
- if (batchResultsDir && getFlag(argv, "--results")) {
912
- throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
913
- }
914
- let pendingBatchAuditResults = batchResultsDir
915
- ? await listBatchResultFiles(batchResultsDir)
916
- : [];
917
- let pendingAuditResultsPath = getFlag(argv, "--results");
918
- let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
919
- let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
920
- let runCount = 0;
921
- let deepeningCycles = 0;
922
- const MAX_DEEPENING_CYCLES = 3;
923
- let anyProgress = false;
924
- let lastResult = null;
925
- const artifactsWritten = new Set();
926
- while (runCount < maxRuns) {
927
- const bundle = await loadArtifactBundle(artifactsDir);
928
- const decision = decideNextStep(bundle);
929
- // Resume interrupted parallel wave: ingest any results that workers
930
- // wrote before the previous process exited.
931
- const priorWave = await readWaveManifest(artifactsDir);
932
- if (priorWave) {
933
- process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
934
- let recoveredProgress = false;
935
- for (const entry of priorWave.slots) {
936
- try {
937
- const results = await readJsonFile(entry.audit_results_path);
938
- if (!results || results.length === 0)
939
- continue;
940
- const stepResult = await runAuditStep({
941
- root,
942
- artifactsDir,
943
- preferredExecutor: "result_ingestion_executor",
944
- auditResultsPath: entry.audit_results_path,
945
- });
946
- if (stepResult.progress_made) {
947
- recoveredProgress = true;
948
- anyProgress = true;
949
- for (const a of stepResult.artifacts_written)
950
- artifactsWritten.add(a);
951
- }
952
- }
953
- catch {
954
- process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
955
- }
956
- }
957
- await removeWaveManifest(artifactsDir);
958
- if (recoveredProgress)
959
- continue;
960
- }
961
- if (decision.selected_executor === "agent" &&
962
- bundle.audit_tasks?.some((t) => t.tags?.includes("selective_deepening") &&
963
- t.status !== "complete") &&
964
- !bundle.audit_tasks?.some((t) => !t.tags?.includes("selective_deepening") &&
965
- t.status !== "complete")) {
966
- deepeningCycles++;
967
- if (deepeningCycles > MAX_DEEPENING_CYCLES) {
968
- process.stderr.write(`[audit-code] Reached max deepening cycles (${MAX_DEEPENING_CYCLES}). Stopping to prevent churn.\n`);
969
- break;
970
- }
971
- }
972
- let preferredExecutor = decision.selected_executor;
973
- let obligationId = decision.selected_obligation;
974
- let auditResultsPath;
975
- let runtimeUpdatesPath;
976
- let externalAnalyzerPath;
977
- if (pendingExternalAnalyzerPath) {
978
- preferredExecutor = "external_analyzer_import_executor";
979
- obligationId = "external_analyzer_import";
980
- externalAnalyzerPath = pendingExternalAnalyzerPath;
981
- }
982
- else if (pendingBatchAuditResults.length > 0 && bundle.coverage_matrix) {
983
- preferredExecutor = "result_ingestion_executor";
984
- obligationId = "audit_results_ingested";
985
- auditResultsPath = pendingBatchAuditResults[0];
986
- }
987
- else if (pendingAuditResultsPath && bundle.coverage_matrix) {
988
- preferredExecutor = "result_ingestion_executor";
989
- obligationId = "audit_results_ingested";
990
- auditResultsPath = pendingAuditResultsPath;
991
- }
992
- else if (pendingRuntimeUpdatesPath && bundle.runtime_validation_tasks) {
993
- preferredExecutor = "runtime_validation_update_executor";
994
- obligationId = "runtime_validation_current";
995
- runtimeUpdatesPath = pendingRuntimeUpdatesPath;
996
- }
997
- if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
998
- const blocker = buildManualReviewBlocker(provider.name);
999
- const blockedState = buildBlockedAuditState({
1000
- state: decision.state,
1001
- obligationId,
1002
- executor: preferredExecutor,
1003
- blocker,
1004
- });
1005
- await writeCoreArtifacts(artifactsDir, {
1006
- ...bundle,
1007
- audit_state: blockedState,
1008
- });
1009
- const blockRunId = buildRunId(obligationId, runCount + 1);
1010
- const blockPaths = getRunPaths(artifactsDir, blockRunId);
1011
- const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
1012
- const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
1013
- const blockAuditResultsPath = join(blockPaths.runDir, "audit-results.json");
1014
- const blockReadPaths = new Set();
1015
- for (const pt of blockPendingTasks) {
1016
- for (const fp of pt.file_paths)
1017
- blockReadPaths.add(fp);
1018
- }
1019
- const blockTask = {
1020
- contract_version: "audit-code-worker/v1alpha1",
1021
- run_id: blockRunId,
1022
- repo_root: root,
1023
- artifacts_dir: artifactsDir,
1024
- obligation_id: obligationId,
1025
- preferred_executor: preferredExecutor,
1026
- result_path: blockPaths.resultPath,
1027
- worker_command: [
1028
- process.execPath,
1029
- selfCliPath,
1030
- "worker-run",
1031
- "--task",
1032
- blockPaths.taskPath,
1033
- ],
1034
- audit_results_path: blockAuditResultsPath,
1035
- pending_audit_tasks_path: blockPendingTasksPath,
1036
- timeout_ms: timeoutMs,
1037
- max_retries: 0,
1038
- access: {
1039
- read_paths: [...blockReadPaths],
1040
- write_paths: [blockAuditResultsPath, blockPaths.resultPath],
1041
- },
1042
- };
1043
- const blockPrompt = renderWorkerPrompt(blockTask);
1044
- await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
1045
- await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
1046
- const reviewRun = {
1047
- run_id: blockRunId,
1048
- task_path: blockPaths.taskPath,
1049
- prompt_path: blockPaths.promptPath,
1050
- pending_audit_tasks_path: blockPendingTasksPath,
1051
- audit_results_path: blockAuditResultsPath,
1052
- worker_command: blockTask.worker_command,
1053
- };
1054
- // Render the actionable dispatch / single-task step here instead of
1055
- // leaving the host to issue next-step as a second command. Capability is
1056
- // resolved from flags/config/env with a sane default, so nothing is
1057
- // required from the host to make progress. If rendering fails we still
1058
- // emit the hand-off below — run-to-completion is never worse than before,
1059
- // and next-step will re-render and surface the error loudly.
1060
- try {
1061
- await renderSemanticReviewStep({
1062
- root,
1063
- artifactsDir,
1064
- activeReviewRun: reviewRun,
1065
- hostCanDispatch: resolveHostDispatchCapability({
1066
- explicit: getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents"),
1067
- sessionConfig,
1068
- }),
1069
- hostMaxActiveSubagents: getHostMaxActiveSubagents(argv),
1070
- hostCanRestrictSubagentTools: getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ?? false,
1071
- hostCanSelectSubagentModel: getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false,
1072
- });
1073
- }
1074
- catch (stepError) {
1075
- process.stderr.write(`[audit-code] Could not pre-render the review step; the operator hand-off points to next-step instead. ${stepError instanceof Error ? stepError.message : String(stepError)}\n`);
1076
- }
1077
- await emitEnvelope({
1078
- root,
1079
- artifactsDir,
1080
- bundle: {
1081
- ...bundle,
1082
- audit_state: blockedState,
1083
- },
1084
- audit_state: blockedState,
1085
- selected_obligation: obligationId,
1086
- selected_executor: preferredExecutor,
1087
- progress_made: anyProgress,
1088
- artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
1089
- progress_summary: blocker,
1090
- next_likely_step: null,
1091
- providerName: provider.name,
1092
- activeReviewRun: reviewRun,
1093
- });
1094
- return;
1095
- }
1096
- if (!preferredExecutor) {
1097
- const state = decision.state;
1098
- await clearDispatchFiles(artifactsDir);
1099
- await emitEnvelope({
1100
- root,
1101
- artifactsDir,
1102
- bundle,
1103
- audit_state: state,
1104
- selected_obligation: anyProgress
1105
- ? (lastResult?.obligation_id ?? null)
1106
- : null,
1107
- selected_executor: anyProgress
1108
- ? (lastResult?.selected_executor ?? null)
1109
- : null,
1110
- progress_made: anyProgress,
1111
- artifacts_written: Array.from(artifactsWritten),
1112
- progress_summary: anyProgress && state.status === "complete"
1113
- ? `Completed audit in ${runCount} fresh worker runs.`
1114
- : decision.reason,
1115
- next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
1116
- providerName: provider.name,
1117
- });
1118
- if (state.status === "complete") {
1119
- await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
1120
- }
1121
- return;
1122
- }
1123
- if (preferredExecutor === "agent" && parallelWorkers > 1) {
1124
- const quotaState = await readQuotaState();
1125
- const providerModelKey = buildProviderModelKey(provider.name, hostModel);
1126
- const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
1127
- const allCandidateTasks = buildPendingAuditTasks(bundle);
1128
- const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
1129
- const candidateSizeIndex = sizeIndexFromManifest(bundle.repo_manifest);
1130
- const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g, candidateSizeIndex));
1131
- const providerLimits = await provider.queryLimits?.(hostModel)
1132
- .then((r) => r ? { ...r, source: "provider_query" } : null)
1133
- .catch(() => null)
1134
- ?? null;
1135
- const cachedLimits = await lookupDiscoveredLimits(providerModelKey).catch(() => null);
1136
- const discoveredLimits = mergeDiscoveredLimits(providerLimits, cachedLimits);
1137
- const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
1138
- const quotaSource = buildQuotaSource({ halfLifeHours });
1139
- const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
1140
- const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
1141
- sessionConfig,
1142
- });
1143
- const waveSchedule = scheduleWave({
1144
- providerName: resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig),
1145
- sessionConfig,
1146
- hostModel,
1147
- requestedConcurrency: parallelWorkers,
1148
- estimatedSlotTokens: slotTokenEstimates,
1149
- quotaStateEntry,
1150
- hostConcurrencyLimit,
1151
- quotaSourceSnapshot,
1152
- discoveredLimits,
1153
- });
1154
- const waveSize = waveSchedule.wave_size;
1155
- if (waveSchedule.cooldown_until) {
1156
- const waitMs = new Date(waveSchedule.cooldown_until).getTime() - Date.now();
1157
- if (waitMs > 0) {
1158
- const cappedWait = Math.min(waitMs, 120_000);
1159
- process.stderr.write(`[quota] Cooldown active — waiting ${Math.ceil(cappedWait / 1000)}s before next wave.\n`);
1160
- await new Promise((r) => setTimeout(r, cappedWait));
1161
- }
1162
- }
1163
- const taskGroups = candidateGroups.slice(0, waveSize);
1164
- const workerSlots = [];
1165
- for (const rawGroup of taskGroups) {
1166
- const group = await addFileLineCountHints(root, rawGroup);
1167
- runCount += 1;
1168
- const slotRunId = buildRunId(obligationId, runCount);
1169
- const slotPaths = getRunPaths(artifactsDir, slotRunId);
1170
- const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
1171
- const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
1172
- const slotReadPaths = new Set();
1173
- for (const t of group) {
1174
- for (const fp of t.file_paths)
1175
- slotReadPaths.add(fp);
1176
- }
1177
- const slotTask = {
1178
- contract_version: "audit-code-worker/v1alpha1",
1179
- run_id: slotRunId,
1180
- repo_root: root,
1181
- artifacts_dir: artifactsDir,
1182
- obligation_id: obligationId,
1183
- preferred_executor: "agent",
1184
- result_path: slotPaths.resultPath,
1185
- worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
1186
- audit_results_path: slotAuditResultsPath,
1187
- pending_audit_tasks_path: slotPendingTasksPath,
1188
- worker_command_mode: "deferred",
1189
- timeout_ms: timeoutMs,
1190
- max_retries: 0,
1191
- access: {
1192
- read_paths: [...slotReadPaths],
1193
- write_paths: [slotAuditResultsPath, slotPaths.resultPath],
1194
- },
1195
- };
1196
- const slotPrompt = renderWorkerPrompt(slotTask);
1197
- await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
1198
- await writeJsonFile(slotPendingTasksPath, group);
1199
- workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
1200
- }
1201
- await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
1202
- run_id: slot.runId,
1203
- task_path: slot.paths.taskPath,
1204
- prompt_path: slot.paths.promptPath,
1205
- result_path: slot.paths.resultPath,
1206
- status_path: slot.paths.statusPath,
1207
- audit_results_path: slot.auditResultsPath,
1208
- pending_audit_tasks_path: slot.pendingTasksPath,
1209
- })), workerSlots.flatMap((slot) => slot.group));
1210
- const parallelStartedAt = new Date().toISOString();
1211
- await writeWaveManifest(artifactsDir, {
1212
- obligation_id: obligationId ?? "unknown",
1213
- started_at: parallelStartedAt,
1214
- pid: process.pid,
1215
- slots: workerSlots.map(buildWaveSlotEntry),
1216
- });
1217
- const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
1218
- repoRoot: root,
1219
- runId: slot.runId,
1220
- obligationId,
1221
- promptPath: slot.paths.promptPath,
1222
- taskPath: slot.paths.taskPath,
1223
- resultPath: slot.paths.resultPath,
1224
- stdoutPath: slot.paths.stdoutPath,
1225
- stderrPath: slot.paths.stderrPath,
1226
- uiMode,
1227
- timeoutMs,
1228
- })), waveSize);
1229
- const launchErrorsByRunId = new Map();
1230
- for (let index = 0; index < launchResults.length; index++) {
1231
- const outcome = launchResults[index];
1232
- if (outcome?.status === "rejected") {
1233
- launchErrorsByRunId.set(workerSlots[index].runId, outcome.reason instanceof Error
1234
- ? outcome.reason.message
1235
- : String(outcome.reason));
1236
- }
1237
- else if (outcome?.status === "fulfilled") {
1238
- const launchExitSummary = summarizeLaunchExit(outcome.value);
1239
- if (launchExitSummary) {
1240
- launchErrorsByRunId.set(workerSlots[index].runId, launchExitSummary);
1241
- }
1242
- }
1243
- }
1244
- // Result ingestion is intentionally sequential even though agent launch
1245
- // was parallel. Writing to coverage_matrix.json is not atomic, so
1246
- // concurrent ingest calls would race and corrupt coverage state.
1247
- let batchProgress = false;
1248
- const batchErrors = [];
1249
- for (const slot of workerSlots) {
1250
- const parallelEndedAt = new Date().toISOString();
1251
- let workerResult = buildWorkerResult({
1252
- runId: slot.runId,
1253
- obligationId,
1254
- status: "no_progress",
1255
- progressMade: false,
1256
- selectedExecutor: "agent",
1257
- artifactsWritten: [],
1258
- summary: "Parallel worker batch made no progress.",
1259
- nextLikelyStep: obligationId,
1260
- errors: [],
1261
- });
1262
- try {
1263
- const launchError = launchErrorsByRunId.get(slot.runId);
1264
- if (launchError) {
1265
- throw new Error(`Worker launch failed: ${launchError}`);
1266
- }
1267
- const auditResults = await readJsonFile(slot.auditResultsPath);
1268
- const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
1269
- const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
1270
- if (slot.group.length > 0 && matchedCount === 0) {
1271
- throw new Error("Worker did not emit any audit results for the assigned tasks.");
1272
- }
1273
- const issues = validateAuditResults(auditResults, slot.group, {
1274
- lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
1275
- });
1276
- const errors = issues.filter((issue) => issue.severity === "error");
1277
- const warnings = issues.filter((issue) => issue.severity === "warning");
1278
- if (warnings.length > 0) {
1279
- process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
1280
- formatAuditResultIssues(warnings) + "\n");
1281
- }
1282
- if (errors.length > 0) {
1283
- throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
1284
- formatAuditResultIssues(errors));
1285
- }
1286
- const stepResult = await runAuditStep({
1287
- root,
1288
- artifactsDir,
1289
- preferredExecutor: "result_ingestion_executor",
1290
- auditResultsPath: slot.auditResultsPath,
1291
- });
1292
- workerResult = buildWorkerResult({
1293
- runId: slot.runId,
1294
- obligationId,
1295
- status: stepResult.progress_made ? "completed" : "no_progress",
1296
- progressMade: stepResult.progress_made,
1297
- selectedExecutor: stepResult.selected_executor,
1298
- artifactsWritten: stepResult.artifacts_written,
1299
- summary: stepResult.progress_summary,
1300
- nextLikelyStep: stepResult.next_likely_step,
1301
- errors: [],
1302
- });
1303
- batchProgress ||= stepResult.progress_made;
1304
- if (stepResult.progress_made)
1305
- anyProgress = true;
1306
- for (const a of stepResult.artifacts_written)
1307
- artifactsWritten.add(a);
1308
- }
1309
- catch (error) {
1310
- const message = error instanceof Error ? error.message : String(error);
1311
- batchErrors.push(`${slot.runId}: ${message}`);
1312
- workerResult = buildWorkerResult({
1313
- runId: slot.runId,
1314
- obligationId,
1315
- status: "failed",
1316
- progressMade: false,
1317
- selectedExecutor: "agent",
1318
- artifactsWritten: [],
1319
- summary: `Worker failed for executor agent: ${message}`,
1320
- nextLikelyStep: obligationId,
1321
- errors: [message],
1322
- });
1323
- process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
1324
- }
1325
- await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
1326
- await appendRunLedgerEntry(artifactsDir, {
1327
- run_id: slot.runId,
1328
- provider: provider.name,
1329
- obligation_id: obligationId,
1330
- selected_executor: workerResult.selected_executor,
1331
- status: workerResult.status,
1332
- started_at: parallelStartedAt,
1333
- ended_at: parallelEndedAt,
1334
- result_path: slot.paths.resultPath,
1335
- });
1336
- artifactsWritten.add("run-ledger.json");
1337
- }
1338
- // Record outcome for adaptive learning (best-effort — never blocks dispatch)
1339
- {
1340
- const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
1341
- const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
1342
- const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
1343
- await recordWaveOutcome(providerModelKey, {
1344
- concurrency: workerSlots.length,
1345
- estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
1346
- outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
1347
- cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
1348
- }, sessionConfig.quota?.empirical_half_life_hours ?? 24).catch(() => undefined);
1349
- }
1350
- // Extract rate-limit headers from worker stderr (best-effort)
1351
- {
1352
- const extractor = getHeaderExtractorForProvider(provider.name);
1353
- for (const slot of workerSlots) {
1354
- try {
1355
- const stderr = await readFile(slot.paths.stderrPath, "utf8");
1356
- const extracted = extractor.extract(stderr);
1357
- if (extracted && (extracted.requests_per_minute != null || extracted.input_tokens_per_minute != null)) {
1358
- await updateDiscoveredLimits(providerModelKey, {
1359
- requests_per_minute: extracted.requests_per_minute,
1360
- input_tokens_per_minute: extracted.input_tokens_per_minute,
1361
- source: "header_extraction",
1362
- });
1363
- break; // one successful extraction is enough
1364
- }
1365
- }
1366
- catch {
1367
- // stderr file missing or unreadable — skip
1368
- }
1369
- }
1370
- }
1371
- await removeWaveManifest(artifactsDir);
1372
- if (batchErrors.length > 0) {
1373
- const bundleAfter = await loadArtifactBundle(artifactsDir);
1374
- const blockedState = buildBlockedAuditState({
1375
- state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
1376
- obligationId,
1377
- executor: "agent",
1378
- blocker: `Parallel worker batch failed for ${batchErrors.length} run(s). ` +
1379
- batchErrors.slice(0, 3).join(" | "),
1380
- });
1381
- await writeCoreArtifacts(artifactsDir, {
1382
- ...bundleAfter,
1383
- audit_state: blockedState,
1384
- });
1385
- await emitEnvelope({
1386
- root,
1387
- artifactsDir,
1388
- bundle: { ...bundleAfter, audit_state: blockedState },
1389
- audit_state: blockedState,
1390
- selected_obligation: obligationId,
1391
- selected_executor: "agent",
1392
- progress_made: anyProgress,
1393
- artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
1394
- progress_summary: `Parallel worker batch failed for ${batchErrors.length} run(s).\n` +
1395
- batchErrors.join("\n"),
1396
- next_likely_step: null,
1397
- providerName: provider.name,
1398
- });
1399
- return;
1400
- }
1401
- if (!batchProgress) {
1402
- const bundleAfter = await loadArtifactBundle(artifactsDir);
1403
- const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
1404
- await emitEnvelope({
1405
- root,
1406
- artifactsDir,
1407
- bundle: bundleAfter,
1408
- audit_state: state,
1409
- selected_obligation: obligationId,
1410
- selected_executor: "agent",
1411
- progress_made: anyProgress,
1412
- artifacts_written: Array.from(artifactsWritten),
1413
- progress_summary: "Parallel worker batch made no progress.",
1414
- next_likely_step: obligationId,
1415
- providerName: provider.name,
1416
- });
1417
- return;
1418
- }
1419
- continue;
1420
- }
1421
- runCount += 1;
1422
- const runId = buildRunId(obligationId, runCount);
1423
- const paths = getRunPaths(artifactsDir, runId);
1424
- if (shouldRunInlineExecutor(preferredExecutor)) {
1425
- await clearDispatchFiles(artifactsDir);
1426
- const startedAt = new Date().toISOString();
1427
- let workerResult;
1428
- try {
1429
- const result = await runAuditStep({
1430
- root,
1431
- artifactsDir,
1432
- preferredExecutor,
1433
- auditResultsPath,
1434
- runtimeUpdatesPath,
1435
- externalAnalyzerPath,
1436
- analyzers: sessionConfig.analyzers,
1437
- since: getFlag(argv, "--since"),
1438
- });
1439
- workerResult = {
1440
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
1441
- run_id: runId,
1442
- obligation_id: obligationId,
1443
- status: result.progress_made ? "completed" : "no_progress",
1444
- progress_made: result.progress_made,
1445
- selected_executor: result.selected_executor,
1446
- artifacts_written: result.artifacts_written,
1447
- summary: result.progress_summary,
1448
- next_likely_step: result.next_likely_step,
1449
- errors: [],
1450
- };
1451
- }
1452
- catch (error) {
1453
- const message = error instanceof Error ? error.message : String(error);
1454
- workerResult = {
1455
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
1456
- run_id: runId,
1457
- obligation_id: obligationId,
1458
- status: "failed",
1459
- progress_made: false,
1460
- selected_executor: preferredExecutor,
1461
- artifacts_written: [],
1462
- summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
1463
- next_likely_step: decision.selected_obligation,
1464
- errors: [message],
1465
- };
1466
- }
1467
- await persistWorkerRunArtifacts(paths, workerResult, "inline");
1468
- await appendRunLedgerEntry(artifactsDir, {
1469
- run_id: runId,
1470
- provider: provider.name,
1471
- obligation_id: obligationId,
1472
- selected_executor: workerResult.selected_executor,
1473
- status: workerResult.status,
1474
- started_at: startedAt,
1475
- ended_at: new Date().toISOString(),
1476
- result_path: paths.resultPath,
1477
- });
1478
- lastResult = workerResult;
1479
- if (workerResult.progress_made) {
1480
- anyProgress = true;
1481
- }
1482
- for (const artifact of workerResult.artifacts_written) {
1483
- artifactsWritten.add(artifact);
1484
- }
1485
- artifactsWritten.add("run-ledger.json");
1486
- if (externalAnalyzerPath)
1487
- pendingExternalAnalyzerPath = undefined;
1488
- if (auditResultsPath &&
1489
- pendingBatchAuditResults[0] === auditResultsPath &&
1490
- preferredExecutor === "result_ingestion_executor" &&
1491
- workerResult.status !== "failed" &&
1492
- workerResult.status !== "blocked") {
1493
- pendingBatchAuditResults.shift();
1494
- }
1495
- if (auditResultsPath)
1496
- pendingAuditResultsPath = undefined;
1497
- if (runtimeUpdatesPath)
1498
- pendingRuntimeUpdatesPath = undefined;
1499
- if (workerResult.status === "failed" ||
1500
- workerResult.status === "blocked" ||
1501
- workerResult.status === "no_progress") {
1502
- const bundleAfter = await loadArtifactBundle(artifactsDir);
1503
- const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
1504
- const state = shouldBlock
1505
- ? buildBlockedAuditState({
1506
- state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
1507
- obligationId: workerResult.obligation_id,
1508
- executor: workerResult.selected_executor,
1509
- blocker: buildWorkerFailureBlocker(workerResult),
1510
- })
1511
- : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
1512
- if (shouldBlock) {
1513
- await writeCoreArtifacts(artifactsDir, {
1514
- ...bundleAfter,
1515
- audit_state: state,
1516
- });
1517
- }
1518
- await emitEnvelope({
1519
- root,
1520
- artifactsDir,
1521
- bundle: shouldBlock
1522
- ? { ...bundleAfter, audit_state: state }
1523
- : bundleAfter,
1524
- audit_state: state,
1525
- selected_obligation: workerResult.obligation_id,
1526
- selected_executor: workerResult.selected_executor,
1527
- progress_made: anyProgress,
1528
- artifacts_written: Array.from(shouldBlock
1529
- ? new Set([...artifactsWritten, "audit_state.json"])
1530
- : artifactsWritten),
1531
- progress_summary: buildWorkerFailureBlocker(workerResult),
1532
- next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
1533
- providerName: provider.name,
1534
- });
1535
- return;
1536
- }
1537
- continue;
1538
- }
1539
- const pendingAuditTasks = preferredExecutor === "agent"
1540
- ? await addFileLineCountHints(root, buildPendingAuditTasks(bundle))
1541
- : undefined;
1542
- const pendingAuditTasksPath = preferredExecutor === "agent"
1543
- ? join(paths.runDir, "pending-audit-tasks.json")
1544
- : undefined;
1545
- const providerAuditResultsPath = preferredExecutor === "agent"
1546
- ? join(paths.runDir, "audit-results.json")
1547
- : auditResultsPath;
1548
- const providerReadPaths = new Set();
1549
- if (pendingAuditTasks) {
1550
- for (const pt of pendingAuditTasks) {
1551
- for (const fp of pt.file_paths)
1552
- providerReadPaths.add(fp);
1553
- }
1554
- }
1555
- const task = {
1556
- contract_version: "audit-code-worker/v1alpha1",
1557
- run_id: runId,
1558
- repo_root: root,
1559
- artifacts_dir: artifactsDir,
1560
- obligation_id: obligationId,
1561
- preferred_executor: preferredExecutor,
1562
- result_path: paths.resultPath,
1563
- worker_command: [
1564
- process.execPath,
1565
- selfCliPath,
1566
- "worker-run",
1567
- "--task",
1568
- paths.taskPath,
1569
- ],
1570
- audit_results_path: providerAuditResultsPath,
1571
- pending_audit_tasks_path: pendingAuditTasksPath,
1572
- runtime_updates_path: runtimeUpdatesPath,
1573
- external_analyzer_results_path: externalAnalyzerPath,
1574
- timeout_ms: timeoutMs,
1575
- max_retries: 0,
1576
- access: providerReadPaths.size > 0 ? {
1577
- read_paths: [...providerReadPaths],
1578
- write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
1579
- } : undefined,
1580
- };
1581
- const prompt = renderWorkerPrompt(task);
1582
- await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
1583
- if (pendingAuditTasksPath && pendingAuditTasks) {
1584
- await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
1585
- }
1586
- const startedAt = new Date().toISOString();
1587
- let workerResult;
1588
- let launchResult = null;
1589
- try {
1590
- launchResult = await provider.launch({
1591
- repoRoot: root,
1592
- runId,
1593
- obligationId,
1594
- promptPath: paths.promptPath,
1595
- taskPath: paths.taskPath,
1596
- resultPath: paths.resultPath,
1597
- stdoutPath: paths.stdoutPath,
1598
- stderrPath: paths.stderrPath,
1599
- uiMode,
1600
- timeoutMs,
1601
- });
1602
- const candidate = await readJsonFile(paths.resultPath);
1603
- if (isWorkerResult(candidate)) {
1604
- workerResult = candidate;
1605
- }
1606
- else {
1607
- const launchExitSummary = summarizeLaunchExit(launchResult);
1608
- workerResult = {
1609
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
1610
- run_id: runId,
1611
- obligation_id: obligationId,
1612
- status: "failed",
1613
- progress_made: false,
1614
- selected_executor: preferredExecutor,
1615
- artifacts_written: [],
1616
- summary: launchExitSummary
1617
- ? `Worker did not emit a valid worker result after provider exit: ${launchExitSummary}`
1618
- : "Worker did not emit a valid worker result.",
1619
- next_likely_step: decision.selected_obligation,
1620
- errors: ["Invalid worker result contract."],
1621
- };
1622
- }
1623
- }
1624
- catch (error) {
1625
- const message = error instanceof Error ? error.message : String(error);
1626
- const launchExitSummary = launchResult && summarizeLaunchExit(launchResult);
1627
- workerResult = {
1628
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
1629
- run_id: runId,
1630
- obligation_id: obligationId,
1631
- status: "failed",
1632
- progress_made: false,
1633
- selected_executor: preferredExecutor,
1634
- artifacts_written: [],
1635
- summary: `Worker launch failed for ${preferredExecutor}: ${launchExitSummary ?? message}`,
1636
- next_likely_step: decision.selected_obligation,
1637
- errors: launchExitSummary ? [message, launchExitSummary] : [message],
1638
- };
1639
- await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
1640
- }
1641
- await appendRunLedgerEntry(artifactsDir, {
1642
- run_id: runId,
1643
- provider: provider.name,
1644
- obligation_id: obligationId,
1645
- selected_executor: workerResult.selected_executor,
1646
- status: workerResult.status,
1647
- started_at: startedAt,
1648
- ended_at: new Date().toISOString(),
1649
- result_path: paths.resultPath,
1650
- });
1651
- lastResult = workerResult;
1652
- if (workerResult.progress_made) {
1653
- anyProgress = true;
1654
- }
1655
- for (const artifact of workerResult.artifacts_written) {
1656
- artifactsWritten.add(artifact);
1657
- }
1658
- artifactsWritten.add("run-ledger.json");
1659
- if (externalAnalyzerPath)
1660
- pendingExternalAnalyzerPath = undefined;
1661
- if (auditResultsPath &&
1662
- pendingBatchAuditResults[0] === auditResultsPath &&
1663
- preferredExecutor === "result_ingestion_executor" &&
1664
- workerResult.status !== "failed" &&
1665
- workerResult.status !== "blocked") {
1666
- pendingBatchAuditResults.shift();
1667
- }
1668
- if (providerAuditResultsPath)
1669
- pendingAuditResultsPath = undefined;
1670
- if (runtimeUpdatesPath)
1671
- pendingRuntimeUpdatesPath = undefined;
1672
- if (workerResult.status === "failed" ||
1673
- workerResult.status === "blocked" ||
1674
- workerResult.status === "no_progress") {
1675
- const bundleAfter = await loadArtifactBundle(artifactsDir);
1676
- const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
1677
- const state = shouldBlock
1678
- ? buildBlockedAuditState({
1679
- state: deriveAuditState(bundleAfter),
1680
- obligationId: workerResult.obligation_id,
1681
- executor: workerResult.selected_executor,
1682
- blocker: buildWorkerFailureBlocker(workerResult),
1683
- })
1684
- : deriveAuditState(bundleAfter);
1685
- if (shouldBlock) {
1686
- await writeCoreArtifacts(artifactsDir, {
1687
- ...bundleAfter,
1688
- audit_state: state,
1689
- });
1690
- }
1691
- await emitEnvelope({
1692
- root,
1693
- artifactsDir,
1694
- bundle: shouldBlock
1695
- ? { ...bundleAfter, audit_state: state }
1696
- : bundleAfter,
1697
- audit_state: state,
1698
- selected_obligation: workerResult.obligation_id,
1699
- selected_executor: workerResult.selected_executor,
1700
- progress_made: anyProgress,
1701
- artifacts_written: Array.from(shouldBlock
1702
- ? new Set([...artifactsWritten, "audit_state.json"])
1703
- : artifactsWritten),
1704
- progress_summary: buildWorkerFailureBlocker(workerResult),
1705
- next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
1706
- providerName: provider.name,
1707
- });
1708
- return;
1709
- }
1710
- }
1711
- const bundle = await loadArtifactBundle(artifactsDir);
1712
- const decision = decideNextStep(bundle);
1713
- const state = decision.state;
1714
- if (state.status === "complete") {
1715
- await clearDispatchFiles(artifactsDir);
1716
- }
1717
- await emitEnvelope({
1718
- root,
1719
- artifactsDir,
1720
- bundle,
1721
- audit_state: state,
1722
- selected_obligation: lastResult?.obligation_id ?? decision.selected_obligation,
1723
- selected_executor: lastResult?.selected_executor ?? decision.selected_executor,
1724
- progress_made: anyProgress,
1725
- artifacts_written: Array.from(artifactsWritten),
1726
- progress_summary: `Reached max run limit (${maxRuns}) before terminal state.`,
1727
- next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
1728
- providerName: provider.name,
1729
- });
1730
- if (state.status === "complete") {
1731
- await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
1732
- }
1733
- }
1734
223
  async function cmdWorkerRun(argv) {
1735
224
  const taskPath = getFlag(argv, "--task");
1736
225
  if (!taskPath) {
@@ -2443,22 +932,6 @@ async function cmdSynthesize(argv) {
2443
932
  progress_summary: result.progress_summary,
2444
933
  }, null, 2));
2445
934
  }
2446
- async function cleanupStaleArtifactsDir(artifactsDir) {
2447
- let status;
2448
- try {
2449
- const state = await readJsonFile(join(artifactsDir, "audit_state.json"));
2450
- status = state.status;
2451
- }
2452
- catch (error) {
2453
- if (!isFileMissingError(error)) {
2454
- throw error;
2455
- }
2456
- return;
2457
- }
2458
- if (status === "complete" || status === "not_started") {
2459
- await rm(artifactsDir, { recursive: true, force: true });
2460
- }
2461
- }
2462
935
  async function cmdCleanup(argv) {
2463
936
  const artifactsDir = getArtifactsDir(argv);
2464
937
  const dryRun = hasFlag(argv, "--dry-run");