auditor-lambda 0.2.5 → 0.2.8

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 (71) hide show
  1. package/README.md +35 -7
  2. package/audit-code-wrapper-lib.mjs +1612 -331
  3. package/dist/cli.js +397 -38
  4. package/dist/coverage.d.ts +2 -2
  5. package/dist/coverage.js +5 -5
  6. package/dist/extractors/disposition.js +10 -1
  7. package/dist/extractors/flows.js +7 -1
  8. package/dist/extractors/pathPatterns.d.ts +3 -0
  9. package/dist/extractors/pathPatterns.js +15 -0
  10. package/dist/extractors/risk.js +7 -1
  11. package/dist/io/artifacts.d.ts +6 -6
  12. package/dist/io/artifacts.js +14 -17
  13. package/dist/io/json.d.ts +2 -0
  14. package/dist/io/json.js +15 -0
  15. package/dist/io/runArtifacts.d.ts +3 -1
  16. package/dist/io/runArtifacts.js +20 -5
  17. package/dist/mcp/server.d.ts +1 -0
  18. package/dist/mcp/server.js +579 -0
  19. package/dist/orchestrator/advance.js +9 -2
  20. package/dist/orchestrator/dependencyMap.js +9 -13
  21. package/dist/orchestrator/executors.js +7 -2
  22. package/dist/orchestrator/flowRequeue.d.ts +2 -2
  23. package/dist/orchestrator/flowRequeue.js +16 -3
  24. package/dist/orchestrator/internalExecutors.d.ts +2 -1
  25. package/dist/orchestrator/internalExecutors.js +129 -48
  26. package/dist/orchestrator/requeue.js +10 -4
  27. package/dist/orchestrator/requeueCommand.js +15 -2
  28. package/dist/orchestrator/resultIngestion.d.ts +2 -1
  29. package/dist/orchestrator/resultIngestion.js +26 -6
  30. package/dist/orchestrator/runtimeValidation.d.ts +7 -2
  31. package/dist/orchestrator/runtimeValidation.js +61 -49
  32. package/dist/orchestrator/runtimeValidationUpdate.js +2 -4
  33. package/dist/orchestrator/state.js +28 -14
  34. package/dist/orchestrator/taskBuilder.js +4 -2
  35. package/dist/orchestrator/trivialAudit.d.ts +4 -0
  36. package/dist/orchestrator/trivialAudit.js +49 -0
  37. package/dist/prompts/renderWorkerPrompt.js +6 -2
  38. package/dist/providers/spawnLoggedCommand.js +17 -0
  39. package/dist/reporting/mergeFindings.js +3 -11
  40. package/dist/reporting/rootCause.js +92 -9
  41. package/dist/reporting/synthesis.d.ts +25 -22
  42. package/dist/reporting/synthesis.js +92 -59
  43. package/dist/reporting/workBlocks.d.ts +12 -3
  44. package/dist/reporting/workBlocks.js +124 -70
  45. package/dist/supervisor/sessionConfig.js +4 -2
  46. package/dist/types/flows.d.ts +2 -0
  47. package/dist/types/runtimeValidation.d.ts +2 -1
  48. package/dist/types.d.ts +8 -6
  49. package/dist/validation/auditResults.d.ts +5 -2
  50. package/dist/validation/auditResults.js +335 -43
  51. package/docs/agent-integrations.md +38 -29
  52. package/docs/artifacts.md +18 -51
  53. package/docs/bootstrap-install.md +60 -30
  54. package/docs/contract.md +25 -117
  55. package/docs/field-trial-bug-report.md +237 -0
  56. package/docs/next-steps.md +59 -44
  57. package/docs/packaging.md +13 -3
  58. package/docs/production-launch-bar.md +2 -2
  59. package/docs/production-readiness.md +9 -5
  60. package/docs/releasing.md +81 -0
  61. package/docs/session-config.md +20 -1
  62. package/docs/usage.md +22 -0
  63. package/package.json +4 -1
  64. package/schemas/audit_result.schema.json +4 -5
  65. package/schemas/audit_task.schema.json +10 -0
  66. package/schemas/runtime_validation_report.schema.json +1 -1
  67. package/skills/audit-code/SKILL.md +11 -2
  68. package/skills/audit-code/audit-code.prompt.md +11 -10
  69. package/schemas/merged_findings.schema.json +0 -19
  70. package/schemas/root_cause_clusters.schema.json +0 -28
  71. package/schemas/synthesis_report.schema.json +0 -61
package/dist/cli.js CHANGED
@@ -1,20 +1,20 @@
1
- import { access, mkdir } from "node:fs/promises";
1
+ import { access, mkdir, readdir, rename } from "node:fs/promises";
2
2
  import { createReadStream } from "node:fs";
3
- import { join, resolve } from "node:path";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
4
  import { buildRepoManifest } from "./extractors/fileInventory.js";
5
5
  import { buildFileDisposition } from "./extractors/disposition.js";
6
6
  import { buildCriticalFlowManifest } from "./extractors/flows.js";
7
7
  import { buildSurfaceManifest } from "./extractors/surfaces.js";
8
8
  import { buildUnitManifest } from "./orchestrator/unitBuilder.js";
9
9
  import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
10
- import { buildRuntimeValidationTasks, buildPlaceholderRuntimeValidationReport, } from "./orchestrator/runtimeValidation.js";
10
+ import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
11
11
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
12
- import { loadArtifactBundle, writeCoreArtifacts, cleanupIntermediateArtifacts, } from "./io/artifacts.js";
12
+ import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
13
13
  import { readJsonFile, writeJsonFile } from "./io/json.js";
14
14
  import { validateArtifactBundle } from "./validation/artifacts.js";
15
15
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
16
16
  import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
17
- import { buildSynthesisReport } from "./reporting/synthesis.js";
17
+ import { buildAuditReportModel, renderAuditReportMarkdown, } from "./reporting/synthesis.js";
18
18
  import { deriveAuditState } from "./orchestrator/state.js";
19
19
  import { advanceAudit } from "./orchestrator/advance.js";
20
20
  import { decideNextStep } from "./orchestrator/nextStep.js";
@@ -22,9 +22,10 @@ import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./
22
22
  import { appendRunLedgerEntry } from "./supervisor/runLedger.js";
23
23
  import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
24
24
  import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
25
- import { buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
25
+ import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
26
26
  import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
27
27
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
28
+ import { runAuditCodeMcpServer } from "./mcp/server.js";
28
29
  const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
29
30
  const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
30
31
  const DEFAULT_MAX_RUNS = 1000;
@@ -50,6 +51,10 @@ function getArtifactsDir(argv) {
50
51
  function getRootDir(argv) {
51
52
  return resolve(getFlag(argv, "--root", "."));
52
53
  }
54
+ function getBatchResultsDir(argv) {
55
+ const value = getFlag(argv, "--batch-results");
56
+ return value ? resolve(value) : undefined;
57
+ }
53
58
  function getMaxRuns(argv) {
54
59
  const raw = Number(getFlag(argv, "--max-runs", String(DEFAULT_MAX_RUNS)));
55
60
  return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_MAX_RUNS;
@@ -78,6 +83,15 @@ function getParallelWorkers(argv, sessionConfig) {
78
83
  }
79
84
  return 1;
80
85
  }
86
+ function getTimeoutMs(argv, sessionConfig) {
87
+ const fromArg = getFlag(argv, "--timeout");
88
+ if (fromArg !== undefined) {
89
+ const parsed = Number(fromArg);
90
+ if (Number.isFinite(parsed) && parsed > 0)
91
+ return Math.floor(parsed);
92
+ }
93
+ return sessionConfig.timeout_ms ?? DEFAULT_TIMEOUT_MS;
94
+ }
81
95
  function chunkArray(arr, size) {
82
96
  const chunks = [];
83
97
  for (let i = 0; i < arr.length; i += size) {
@@ -203,6 +217,29 @@ async function buildLineIndex(root, repoManifest) {
203
217
  }
204
218
  return Object.fromEntries(entries);
205
219
  }
220
+ async function buildLineIndexForPaths(root, paths) {
221
+ const uniquePaths = [...new Set(paths)].sort();
222
+ const entries = await Promise.all(uniquePaths.map(async (path) => {
223
+ try {
224
+ return [path, await countLines(resolve(root, path))];
225
+ }
226
+ catch {
227
+ return [path, 0];
228
+ }
229
+ }));
230
+ return Object.fromEntries(entries);
231
+ }
232
+ async function listBatchResultFiles(batchDir) {
233
+ const entries = await readdir(batchDir, { withFileTypes: true });
234
+ const files = entries
235
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json"))
236
+ .map((entry) => join(batchDir, entry.name))
237
+ .sort((a, b) => a.localeCompare(b));
238
+ if (files.length === 0) {
239
+ throw new Error(`No JSON audit result files found in ${batchDir}.`);
240
+ }
241
+ return files;
242
+ }
206
243
  const PROJECT_SIGNALS = [
207
244
  "package.json",
208
245
  "go.mod",
@@ -229,33 +266,122 @@ async function detectProjectRoot(root) {
229
266
  }
230
267
  function buildPendingAuditTasks(bundle) {
231
268
  const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
232
- return (bundle.audit_tasks ?? []).filter((task) => !completedTaskIds.has(task.task_id));
269
+ return (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
270
+ }
271
+ function formatAuditResultValidationError(issues) {
272
+ return (`audit-results validation failed with ${issues.length} error(s):\n` +
273
+ formatAuditResultIssues(issues));
274
+ }
275
+ function buildWorkerFailureBlocker(workerResult) {
276
+ const details = workerResult.errors.filter((error) => error.trim().length > 0);
277
+ return details.length > 0
278
+ ? `${workerResult.summary} ${details.join(" ")}`
279
+ : workerResult.summary;
280
+ }
281
+ function looksLikeCliFlag(value) {
282
+ return typeof value === "string" && value.startsWith("--");
283
+ }
284
+ async function maybeArchiveLegacyPendingResults(auditResultsPath) {
285
+ if (!auditResultsPath || basename(auditResultsPath) !== "worker_results_pending.json") {
286
+ return undefined;
287
+ }
288
+ const archivedPath = join(dirname(auditResultsPath), `worker_results_submitted_${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
289
+ try {
290
+ await rename(auditResultsPath, archivedPath);
291
+ return archivedPath;
292
+ }
293
+ catch (error) {
294
+ process.stderr.write(`[audit-results cleanup] failed to archive ${auditResultsPath}: ${error instanceof Error ? error.message : String(error)}\n`);
295
+ return undefined;
296
+ }
233
297
  }
234
298
  async function runAuditStep(options) {
235
299
  const bundle = await loadArtifactBundle(options.artifactsDir);
300
+ const lineIndex = bundle.repo_manifest
301
+ ? await buildLineIndex(options.root, bundle.repo_manifest)
302
+ : undefined;
303
+ if (looksLikeCliFlag(options.auditResultsPath)) {
304
+ throw new Error(`Invalid audit results path '${options.auditResultsPath}'. This looks like a CLI flag rather than a file path.`);
305
+ }
236
306
  const auditResults = options.auditResultsPath
237
307
  ? await readJsonFile(options.auditResultsPath)
238
308
  : undefined;
309
+ if (auditResults !== undefined) {
310
+ const issues = validateAuditResults(auditResults, bundle.audit_tasks ?? [], {
311
+ lineIndex,
312
+ });
313
+ const errors = issues.filter((issue) => issue.severity === "error");
314
+ const warnings = issues.filter((issue) => issue.severity === "warning");
315
+ if (warnings.length > 0) {
316
+ process.stderr.write(`audit-results validation: ${warnings.length} warning(s):\n` +
317
+ formatAuditResultIssues(warnings) +
318
+ "\n");
319
+ }
320
+ if (errors.length > 0) {
321
+ throw new Error(formatAuditResultValidationError(errors));
322
+ }
323
+ }
239
324
  const runtimeValidationUpdates = options.runtimeUpdatesPath
240
325
  ? await readJsonFile(options.runtimeUpdatesPath)
241
326
  : undefined;
242
327
  const externalAnalyzerResults = options.externalAnalyzerPath
243
328
  ? await readJsonFile(options.externalAnalyzerPath)
244
329
  : undefined;
245
- const lineIndex = bundle.repo_manifest
246
- ? await buildLineIndex(options.root, bundle.repo_manifest)
247
- : undefined;
248
330
  const result = await advanceAudit(bundle, {
249
331
  root: options.root,
250
332
  lineIndex,
251
- auditResults,
333
+ auditResults: auditResults,
252
334
  runtimeValidationUpdates,
253
335
  externalAnalyzerResults,
254
336
  preferredExecutor: options.preferredExecutor,
255
337
  });
256
338
  await writeCoreArtifacts(options.artifactsDir, result.updated_bundle);
339
+ const archivedPendingResults = await maybeArchiveLegacyPendingResults(options.auditResultsPath);
340
+ if (archivedPendingResults) {
341
+ result.progress_summary +=
342
+ ` Archived legacy staging file to ${archivedPendingResults}.`;
343
+ }
257
344
  return result;
258
345
  }
346
+ async function ingestBatchAuditResults(options) {
347
+ const batchFiles = await listBatchResultFiles(options.batchDir);
348
+ const artifactsWritten = new Set();
349
+ const progressSummaries = [];
350
+ let lastStep = null;
351
+ let anyProgress = false;
352
+ for (const batchFile of batchFiles) {
353
+ const step = await runAuditStep({
354
+ root: options.root,
355
+ artifactsDir: options.artifactsDir,
356
+ preferredExecutor: "result_ingestion_executor",
357
+ auditResultsPath: batchFile,
358
+ });
359
+ lastStep = step;
360
+ anyProgress ||= step.progress_made;
361
+ for (const artifact of step.artifacts_written) {
362
+ artifactsWritten.add(artifact);
363
+ }
364
+ progressSummaries.push(`${basename(batchFile)}: ${step.progress_summary}`);
365
+ }
366
+ const bundle = lastStep?.updated_bundle ??
367
+ (await loadArtifactBundle(options.artifactsDir));
368
+ const state = lastStep?.audit_state ?? deriveAuditState(bundle);
369
+ const decision = decideNextStep(bundle);
370
+ return {
371
+ batchFiles,
372
+ bundle,
373
+ audit_state: state,
374
+ selected_obligation: lastStep?.selected_obligation ?? decision.selected_obligation,
375
+ selected_executor: lastStep?.selected_executor ?? "result_ingestion_executor",
376
+ progress_made: anyProgress,
377
+ artifacts_written: Array.from(artifactsWritten),
378
+ progress_summary: `Imported ${batchFiles.length} batch result file${batchFiles.length === 1 ? "" : "s"} from ${options.batchDir}.` +
379
+ (progressSummaries.length > 0
380
+ ? `\n${progressSummaries.join("\n")}`
381
+ : ""),
382
+ next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
383
+ };
384
+ }
259
385
  function isWorkerResult(value) {
260
386
  return (typeof value === "object" &&
261
387
  value !== null &&
@@ -276,16 +402,35 @@ export async function runSample() {
276
402
  pass_id: "pass:security",
277
403
  lens: "security",
278
404
  agent_role: "security-auditor",
279
- reviewed_ranges: [{ path: "src/api/auth.ts", start: 1, end: 100 }],
405
+ file_coverage: [{ path: "src/api/auth.ts", total_lines: 100 }],
280
406
  findings: [],
281
407
  notes: ["Sample result ingestion path."],
282
408
  requires_followup: false,
283
409
  },
284
410
  ];
285
411
  const flowCoverage = buildFlowCoverage(criticalFlows, coverage);
286
- const runtimeValidationTasks = buildRuntimeValidationTasks(unitManifest, criticalFlows, flowCoverage);
287
- const runtimeValidationReport = buildPlaceholderRuntimeValidationReport(runtimeValidationTasks);
288
- const synthesisReport = buildSynthesisReport(sampleResults, runtimeValidationReport);
412
+ const runtimeValidationTasks = buildRuntimeValidationTasks({
413
+ unitManifest,
414
+ criticalFlows,
415
+ flowCoverage,
416
+ command: ["npm", "test"],
417
+ });
418
+ const runtimeValidationReport = {
419
+ results: runtimeValidationTasks.tasks.map((task) => ({
420
+ task_id: task.id,
421
+ status: "confirmed",
422
+ summary: "Sample runtime validation completed.",
423
+ evidence: [],
424
+ notes: [],
425
+ })),
426
+ };
427
+ const auditReport = renderAuditReportMarkdown(buildAuditReportModel({
428
+ results: sampleResults,
429
+ unitManifest,
430
+ criticalFlows,
431
+ coverageMatrix: coverage,
432
+ runtimeValidationReport,
433
+ }));
289
434
  const auditState = deriveAuditState({
290
435
  repo_manifest: repoManifest,
291
436
  file_disposition: disposition,
@@ -297,7 +442,7 @@ export async function runSample() {
297
442
  runtime_validation_tasks: runtimeValidationTasks,
298
443
  runtime_validation_report: runtimeValidationReport,
299
444
  audit_results: sampleResults,
300
- synthesis_report: synthesisReport,
445
+ audit_report: auditReport,
301
446
  });
302
447
  const artifactsDir = getArtifactsDir(process.argv);
303
448
  await mkdir(artifactsDir, { recursive: true });
@@ -312,7 +457,7 @@ export async function runSample() {
312
457
  runtime_validation_tasks: runtimeValidationTasks,
313
458
  runtime_validation_report: runtimeValidationReport,
314
459
  audit_results: sampleResults,
315
- synthesis_report: synthesisReport,
460
+ audit_report: auditReport,
316
461
  audit_state: auditState,
317
462
  });
318
463
  console.log(JSON.stringify({ audit_state: auditState, artifacts_dir: artifactsDir }, null, 2));
@@ -322,6 +467,37 @@ async function cmdAdvanceAudit(argv) {
322
467
  const artifactsDir = getArtifactsDir(argv);
323
468
  const sessionConfig = await loadSessionConfig(artifactsDir);
324
469
  const providerName = resolveFreshSessionProviderName(getFlag(argv, "--provider"), sessionConfig);
470
+ const batchResultsDir = getBatchResultsDir(argv);
471
+ if (batchResultsDir && getFlag(argv, "--results")) {
472
+ throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
473
+ }
474
+ if (batchResultsDir) {
475
+ const result = await ingestBatchAuditResults({
476
+ root,
477
+ artifactsDir,
478
+ batchDir: batchResultsDir,
479
+ });
480
+ if (result.selected_executor !== "agent") {
481
+ await clearDispatchFiles(artifactsDir);
482
+ }
483
+ await emitEnvelope({
484
+ root,
485
+ artifactsDir,
486
+ bundle: result.bundle,
487
+ audit_state: result.audit_state,
488
+ selected_obligation: result.selected_obligation,
489
+ selected_executor: result.selected_executor,
490
+ progress_made: result.progress_made,
491
+ artifacts_written: result.artifacts_written,
492
+ progress_summary: result.progress_summary,
493
+ next_likely_step: result.next_likely_step,
494
+ providerName,
495
+ });
496
+ if (result.audit_state.status === "complete") {
497
+ await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
498
+ }
499
+ return;
500
+ }
325
501
  const externalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
326
502
  const result = await runAuditStep({
327
503
  root,
@@ -332,6 +508,9 @@ async function cmdAdvanceAudit(argv) {
332
508
  runtimeUpdatesPath: getFlag(argv, "--updates"),
333
509
  externalAnalyzerPath,
334
510
  });
511
+ if (result.selected_executor !== "agent") {
512
+ await clearDispatchFiles(artifactsDir);
513
+ }
335
514
  await emitEnvelope({
336
515
  root,
337
516
  artifactsDir,
@@ -346,7 +525,7 @@ async function cmdAdvanceAudit(argv) {
346
525
  providerName,
347
526
  });
348
527
  if (result.audit_state.status === "complete") {
349
- await cleanupIntermediateArtifacts(artifactsDir);
528
+ await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
350
529
  }
351
530
  }
352
531
  async function cmdRunToCompletion(argv) {
@@ -358,10 +537,17 @@ async function cmdRunToCompletion(argv) {
358
537
  const maxRuns = getMaxRuns(argv);
359
538
  const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
360
539
  const parallelWorkers = getParallelWorkers(argv, sessionConfig);
361
- const timeoutMs = sessionConfig.timeout_ms ?? DEFAULT_TIMEOUT_MS;
540
+ const timeoutMs = getTimeoutMs(argv, sessionConfig);
362
541
  const selfCliPath = resolve(process.argv[1] ?? "");
363
542
  await mkdir(artifactsDir, { recursive: true });
364
543
  await ensureSupervisorDirs(artifactsDir);
544
+ const batchResultsDir = getBatchResultsDir(argv);
545
+ if (batchResultsDir && getFlag(argv, "--results")) {
546
+ throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
547
+ }
548
+ let pendingBatchAuditResults = batchResultsDir
549
+ ? await listBatchResultFiles(batchResultsDir)
550
+ : [];
365
551
  const earlyBundle = await loadArtifactBundle(artifactsDir);
366
552
  if (!earlyBundle.unit_manifest) {
367
553
  const foundSignal = await detectProjectRoot(root);
@@ -411,6 +597,11 @@ async function cmdRunToCompletion(argv) {
411
597
  obligationId = "external_analyzer_import";
412
598
  externalAnalyzerPath = pendingExternalAnalyzerPath;
413
599
  }
600
+ else if (pendingBatchAuditResults.length > 0 && bundle.coverage_matrix) {
601
+ preferredExecutor = "result_ingestion_executor";
602
+ obligationId = "audit_results_ingested";
603
+ auditResultsPath = pendingBatchAuditResults[0];
604
+ }
414
605
  else if (pendingAuditResultsPath && bundle.coverage_matrix) {
415
606
  preferredExecutor = "result_ingestion_executor";
416
607
  obligationId = "audit_results_ingested";
@@ -457,7 +648,7 @@ async function cmdRunToCompletion(argv) {
457
648
  pending_audit_tasks_path: blockPendingTasksPath,
458
649
  };
459
650
  const blockPrompt = renderWorkerPrompt(blockTask);
460
- await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir);
651
+ await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
461
652
  await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
462
653
  await emitEnvelope({
463
654
  root,
@@ -479,6 +670,7 @@ async function cmdRunToCompletion(argv) {
479
670
  }
480
671
  if (!preferredExecutor) {
481
672
  const state = bundle.audit_state ?? decision.state;
673
+ await clearDispatchFiles(artifactsDir);
482
674
  await emitEnvelope({
483
675
  root,
484
676
  artifactsDir,
@@ -499,7 +691,7 @@ async function cmdRunToCompletion(argv) {
499
691
  providerName: provider.name,
500
692
  });
501
693
  if (state.status === "complete") {
502
- await cleanupIntermediateArtifacts(artifactsDir);
694
+ await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
503
695
  }
504
696
  return;
505
697
  }
@@ -527,12 +719,12 @@ async function cmdRunToCompletion(argv) {
527
719
  skip_worker_command: true,
528
720
  };
529
721
  const slotPrompt = renderWorkerPrompt(slotTask);
530
- await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir);
722
+ await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group);
531
723
  await writeJsonFile(slotPendingTasksPath, group);
532
724
  workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
533
725
  }
534
726
  const parallelStartedAt = new Date().toISOString();
535
- await Promise.allSettled(workerSlots.map((slot) => provider.launch({
727
+ const launchResults = await Promise.allSettled(workerSlots.map((slot) => provider.launch({
536
728
  repoRoot: root,
537
729
  runId: slot.runId,
538
730
  obligationId,
@@ -544,21 +736,37 @@ async function cmdRunToCompletion(argv) {
544
736
  uiMode,
545
737
  timeoutMs,
546
738
  })));
739
+ const launchErrorsByRunId = new Map();
740
+ for (let index = 0; index < launchResults.length; index++) {
741
+ const outcome = launchResults[index];
742
+ if (outcome?.status === "rejected") {
743
+ launchErrorsByRunId.set(workerSlots[index].runId, outcome.reason instanceof Error
744
+ ? outcome.reason.message
745
+ : String(outcome.reason));
746
+ }
747
+ }
547
748
  // Result ingestion is intentionally sequential even though agent launch
548
749
  // was parallel. Writing to coverage_matrix.json is not atomic, so
549
750
  // concurrent ingest calls would race and corrupt coverage state.
550
751
  let batchProgress = false;
752
+ const batchErrors = [];
551
753
  for (const slot of workerSlots) {
552
754
  const parallelEndedAt = new Date().toISOString();
553
755
  let slotStatus = "no_progress";
554
756
  try {
757
+ const launchError = launchErrorsByRunId.get(slot.runId);
758
+ if (launchError) {
759
+ throw new Error(`Worker launch failed: ${launchError}`);
760
+ }
555
761
  const auditResults = await readJsonFile(slot.auditResultsPath);
556
762
  const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
557
763
  const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
558
764
  if (slot.group.length > 0 && matchedCount === 0) {
559
765
  throw new Error("Worker did not emit any audit results for the assigned tasks.");
560
766
  }
561
- const issues = validateAuditResults(auditResults, slot.group);
767
+ const issues = validateAuditResults(auditResults, slot.group, {
768
+ lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
769
+ });
562
770
  const errors = issues.filter((issue) => issue.severity === "error");
563
771
  const warnings = issues.filter((issue) => issue.severity === "warning");
564
772
  if (warnings.length > 0) {
@@ -582,8 +790,11 @@ async function cmdRunToCompletion(argv) {
582
790
  for (const a of stepResult.artifacts_written)
583
791
  artifactsWritten.add(a);
584
792
  }
585
- catch {
793
+ catch (error) {
586
794
  slotStatus = "failed";
795
+ const message = error instanceof Error ? error.message : String(error);
796
+ batchErrors.push(`${slot.runId}: ${message}`);
797
+ process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
587
798
  }
588
799
  await appendRunLedgerEntry(artifactsDir, {
589
800
  run_id: slot.runId,
@@ -597,6 +808,35 @@ async function cmdRunToCompletion(argv) {
597
808
  });
598
809
  artifactsWritten.add("run-ledger.json");
599
810
  }
811
+ if (batchErrors.length > 0) {
812
+ const bundleAfter = await loadArtifactBundle(artifactsDir);
813
+ const blockedState = buildBlockedAuditState({
814
+ state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
815
+ obligationId,
816
+ executor: "agent",
817
+ blocker: `Parallel worker batch failed for ${batchErrors.length} run(s). ` +
818
+ batchErrors.slice(0, 3).join(" | "),
819
+ });
820
+ await writeCoreArtifacts(artifactsDir, {
821
+ ...bundleAfter,
822
+ audit_state: blockedState,
823
+ });
824
+ await emitEnvelope({
825
+ root,
826
+ artifactsDir,
827
+ bundle: { ...bundleAfter, audit_state: blockedState },
828
+ audit_state: blockedState,
829
+ selected_obligation: obligationId,
830
+ selected_executor: "agent",
831
+ progress_made: anyProgress,
832
+ artifacts_written: Array.from(new Set([...artifactsWritten, "audit_state.json"])),
833
+ progress_summary: `Parallel worker batch failed for ${batchErrors.length} run(s).\n` +
834
+ batchErrors.join("\n"),
835
+ next_likely_step: null,
836
+ providerName: provider.name,
837
+ });
838
+ return;
839
+ }
600
840
  if (!batchProgress) {
601
841
  const bundleAfter = await loadArtifactBundle(artifactsDir);
602
842
  const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
@@ -650,7 +890,7 @@ async function cmdRunToCompletion(argv) {
650
890
  external_analyzer_results_path: externalAnalyzerPath,
651
891
  };
652
892
  const prompt = renderWorkerPrompt(task);
653
- await writeWorkerTaskFiles(task, prompt, paths, artifactsDir);
893
+ await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
654
894
  if (pendingAuditTasksPath && pendingAuditTasks) {
655
895
  await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
656
896
  }
@@ -686,6 +926,7 @@ async function cmdRunToCompletion(argv) {
686
926
  };
687
927
  }
688
928
  catch (error) {
929
+ const message = error instanceof Error ? error.message : String(error);
689
930
  workerResult = {
690
931
  contract_version: WORKER_RESULT_CONTRACT_VERSION,
691
932
  run_id: runId,
@@ -694,9 +935,9 @@ async function cmdRunToCompletion(argv) {
694
935
  progress_made: false,
695
936
  selected_executor: preferredExecutor,
696
937
  artifacts_written: [],
697
- summary: `Worker launch failed for ${preferredExecutor}.`,
938
+ summary: `Worker launch failed for ${preferredExecutor}: ${message}`,
698
939
  next_likely_step: decision.selected_obligation,
699
- errors: [error instanceof Error ? error.message : String(error)],
940
+ errors: [message],
700
941
  };
701
942
  await writeJsonFile(paths.resultPath, workerResult);
702
943
  }
@@ -720,6 +961,13 @@ async function cmdRunToCompletion(argv) {
720
961
  artifactsWritten.add("run-ledger.json");
721
962
  if (externalAnalyzerPath)
722
963
  pendingExternalAnalyzerPath = undefined;
964
+ if (auditResultsPath &&
965
+ pendingBatchAuditResults[0] === auditResultsPath &&
966
+ preferredExecutor === "result_ingestion_executor" &&
967
+ workerResult.status !== "failed" &&
968
+ workerResult.status !== "blocked") {
969
+ pendingBatchAuditResults.shift();
970
+ }
723
971
  if (providerAuditResultsPath)
724
972
  pendingAuditResultsPath = undefined;
725
973
  if (runtimeUpdatesPath)
@@ -728,18 +976,36 @@ async function cmdRunToCompletion(argv) {
728
976
  workerResult.status === "blocked" ||
729
977
  workerResult.status === "no_progress") {
730
978
  const bundleAfter = await loadArtifactBundle(artifactsDir);
731
- const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
979
+ const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
980
+ const state = shouldBlock
981
+ ? buildBlockedAuditState({
982
+ state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
983
+ obligationId: workerResult.obligation_id,
984
+ executor: workerResult.selected_executor,
985
+ blocker: buildWorkerFailureBlocker(workerResult),
986
+ })
987
+ : bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
988
+ if (shouldBlock) {
989
+ await writeCoreArtifacts(artifactsDir, {
990
+ ...bundleAfter,
991
+ audit_state: state,
992
+ });
993
+ }
732
994
  await emitEnvelope({
733
995
  root,
734
996
  artifactsDir,
735
- bundle: bundleAfter,
997
+ bundle: shouldBlock
998
+ ? { ...bundleAfter, audit_state: state }
999
+ : bundleAfter,
736
1000
  audit_state: state,
737
1001
  selected_obligation: workerResult.obligation_id,
738
1002
  selected_executor: workerResult.selected_executor,
739
1003
  progress_made: anyProgress,
740
- artifacts_written: Array.from(artifactsWritten),
741
- progress_summary: workerResult.summary,
742
- next_likely_step: workerResult.next_likely_step,
1004
+ artifacts_written: Array.from(shouldBlock
1005
+ ? new Set([...artifactsWritten, "audit_state.json"])
1006
+ : artifactsWritten),
1007
+ progress_summary: buildWorkerFailureBlocker(workerResult),
1008
+ next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
743
1009
  providerName: provider.name,
744
1010
  });
745
1011
  return;
@@ -748,6 +1014,9 @@ async function cmdRunToCompletion(argv) {
748
1014
  const bundle = await loadArtifactBundle(artifactsDir);
749
1015
  const decision = decideNextStep(bundle);
750
1016
  const state = bundle.audit_state ?? decision.state;
1017
+ if (state.status === "complete") {
1018
+ await clearDispatchFiles(artifactsDir);
1019
+ }
751
1020
  await emitEnvelope({
752
1021
  root,
753
1022
  artifactsDir,
@@ -770,6 +1039,9 @@ async function cmdWorkerRun(argv) {
770
1039
  const task = await readJsonFile(taskPath);
771
1040
  let workerResult;
772
1041
  try {
1042
+ if (looksLikeCliFlag(task.audit_results_path)) {
1043
+ throw new Error(`task.audit_results_path resolved to '${task.audit_results_path}', which looks like a CLI flag instead of a file path.`);
1044
+ }
773
1045
  if (task.preferred_executor === "agent" && !task.audit_results_path) {
774
1046
  throw new Error("agent worker-run requires audit_results_path so provider-assisted review can be ingested.");
775
1047
  }
@@ -783,7 +1055,9 @@ async function cmdWorkerRun(argv) {
783
1055
  if (pendingTasks.length > 0 && matchedResultCount === 0) {
784
1056
  throw new Error("Provider-assisted review did not emit any audit results for the pending audit tasks.");
785
1057
  }
786
- const issues = validateAuditResults(auditResults, pendingTasks);
1058
+ const issues = validateAuditResults(auditResults, pendingTasks, {
1059
+ lineIndex: await buildLineIndexForPaths(task.repo_root, pendingTasks.flatMap((item) => item.file_paths)),
1060
+ });
787
1061
  const errors = issues.filter((issue) => issue.severity === "error");
788
1062
  const warnings = issues.filter((issue) => issue.severity === "warning");
789
1063
  if (warnings.length > 0) {
@@ -792,8 +1066,7 @@ async function cmdWorkerRun(argv) {
792
1066
  "\n");
793
1067
  }
794
1068
  if (errors.length > 0) {
795
- throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
796
- formatAuditResultIssues(errors));
1069
+ throw new Error(formatAuditResultValidationError(errors));
797
1070
  }
798
1071
  }
799
1072
  const preferredExecutor = task.preferred_executor === "agent"
@@ -829,7 +1102,7 @@ async function cmdWorkerRun(argv) {
829
1102
  progress_made: false,
830
1103
  selected_executor: task.preferred_executor,
831
1104
  artifacts_written: [],
832
- summary: `Worker failed for executor ${task.preferred_executor}.`,
1105
+ summary: `Worker failed for executor ${task.preferred_executor}: ${error instanceof Error ? error.message : String(error)}`,
833
1106
  next_likely_step: task.obligation_id,
834
1107
  errors: [error instanceof Error ? error.message : String(error)],
835
1108
  };
@@ -882,6 +1155,24 @@ async function cmdPlan(argv) {
882
1155
  }
883
1156
  async function cmdIngestResults(argv) {
884
1157
  const artifactsDir = getArtifactsDir(argv);
1158
+ const batchResultsDir = getBatchResultsDir(argv);
1159
+ if (batchResultsDir && getFlag(argv, "--results")) {
1160
+ throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
1161
+ }
1162
+ if (batchResultsDir) {
1163
+ const result = await ingestBatchAuditResults({
1164
+ root: getRootDir(argv),
1165
+ artifactsDir,
1166
+ batchDir: batchResultsDir,
1167
+ });
1168
+ console.log(JSON.stringify({
1169
+ artifacts_dir: artifactsDir,
1170
+ imported_files: result.batchFiles,
1171
+ selected_executor: result.selected_executor,
1172
+ progress_summary: result.progress_summary,
1173
+ }, null, 2));
1174
+ return;
1175
+ }
885
1176
  const result = await runAuditStep({
886
1177
  root: getRootDir(argv),
887
1178
  artifactsDir,
@@ -894,6 +1185,37 @@ async function cmdIngestResults(argv) {
894
1185
  progress_summary: result.progress_summary,
895
1186
  }, null, 2));
896
1187
  }
1188
+ async function cmdExplainTask(argv) {
1189
+ const artifactsDir = getArtifactsDir(argv);
1190
+ const taskId = getFlag(argv, "--task-id") ?? argv[3];
1191
+ if (!taskId) {
1192
+ throw new Error("explain-task requires <task_id> or --task-id <task_id>");
1193
+ }
1194
+ const bundle = await loadArtifactBundle(artifactsDir);
1195
+ const task = [...(bundle.audit_tasks ?? []), ...(bundle.requeue_tasks ?? [])].find((item) => item.task_id === taskId);
1196
+ if (!task) {
1197
+ throw new Error(`Unknown task_id '${taskId}'.`);
1198
+ }
1199
+ const coverageEntries = (bundle.coverage_matrix?.files ?? [])
1200
+ .filter((file) => task.file_paths.includes(file.path))
1201
+ .sort((a, b) => a.path.localeCompare(b.path));
1202
+ const matchingResults = (bundle.audit_results ?? []).filter((result) => result.task_id === task.task_id);
1203
+ console.log(JSON.stringify({
1204
+ artifacts_dir: artifactsDir,
1205
+ task_id: task.task_id,
1206
+ task,
1207
+ file_count: task.file_paths.length,
1208
+ coverage_entries: coverageEntries,
1209
+ pending_coverage: coverageEntries
1210
+ .map((file) => ({
1211
+ path: file.path,
1212
+ missing_lenses: file.required_lenses.filter((lens) => !file.completed_lenses.includes(lens)),
1213
+ }))
1214
+ .filter((file) => file.missing_lenses.length > 0),
1215
+ matching_result_count: matchingResults.length,
1216
+ matching_finding_ids: matchingResults.flatMap((result) => result.findings.map((finding) => finding.id)),
1217
+ }, null, 2));
1218
+ }
897
1219
  async function cmdUpdateRuntimeValidation(argv) {
898
1220
  const artifactsDir = getArtifactsDir(argv);
899
1221
  const result = await runAuditStep({
@@ -942,6 +1264,31 @@ async function cmdValidate(argv) {
942
1264
  }, null, 2));
943
1265
  process.exitCode = issues.length > 0 ? 1 : 0;
944
1266
  }
1267
+ async function cmdValidateResults(argv) {
1268
+ const artifactsDir = getArtifactsDir(argv);
1269
+ const resultsPath = getFlag(argv, "--results");
1270
+ if (!resultsPath) {
1271
+ throw new Error("validate-results requires --results <file>");
1272
+ }
1273
+ const bundle = await loadArtifactBundle(artifactsDir);
1274
+ const lineIndex = bundle.repo_manifest
1275
+ ? await buildLineIndex(getRootDir(argv), bundle.repo_manifest)
1276
+ : undefined;
1277
+ const auditResults = await readJsonFile(resultsPath);
1278
+ const issues = validateAuditResults(auditResults, bundle.audit_tasks ?? [], {
1279
+ lineIndex,
1280
+ });
1281
+ const errors = issues.filter((issue) => issue.severity === "error");
1282
+ const warnings = issues.filter((issue) => issue.severity === "warning");
1283
+ console.log(JSON.stringify({
1284
+ artifacts_dir: artifactsDir,
1285
+ results_path: resolve(resultsPath),
1286
+ warning_count: warnings.length,
1287
+ error_count: errors.length,
1288
+ issues,
1289
+ }, null, 2));
1290
+ process.exitCode = errors.length > 0 ? 1 : 0;
1291
+ }
945
1292
  async function cmdRequeue(argv) {
946
1293
  const artifactsDir = getArtifactsDir(argv);
947
1294
  const bundle = await loadArtifactBundle(artifactsDir);
@@ -963,6 +1310,9 @@ async function cmdSynthesize(argv) {
963
1310
  progress_summary: result.progress_summary,
964
1311
  }, null, 2));
965
1312
  }
1313
+ async function cmdMcp(argv) {
1314
+ await runAuditCodeMcpServer(argv.slice(3));
1315
+ }
966
1316
  async function main(argv) {
967
1317
  const command = argv[2] ?? "sample-run";
968
1318
  switch (command) {
@@ -990,21 +1340,30 @@ async function main(argv) {
990
1340
  case "ingest-results":
991
1341
  await cmdIngestResults(argv);
992
1342
  return;
1343
+ case "explain-task":
1344
+ await cmdExplainTask(argv);
1345
+ return;
993
1346
  case "update-runtime-validation":
994
1347
  await cmdUpdateRuntimeValidation(argv);
995
1348
  return;
996
1349
  case "validate":
997
1350
  await cmdValidate(argv);
998
1351
  return;
1352
+ case "validate-results":
1353
+ await cmdValidateResults(argv);
1354
+ return;
999
1355
  case "requeue":
1000
1356
  await cmdRequeue(argv);
1001
1357
  return;
1002
1358
  case "synthesize":
1003
1359
  await cmdSynthesize(argv);
1004
1360
  return;
1361
+ case "mcp":
1362
+ await cmdMcp(argv);
1363
+ return;
1005
1364
  default:
1006
1365
  console.error(`Unknown command: ${command}`);
1007
- console.error("Available commands: sample-run, advance-audit, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, update-runtime-validation, validate, requeue, synthesize");
1366
+ console.error("Available commands: sample-run, advance-audit, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, mcp");
1008
1367
  process.exitCode = 1;
1009
1368
  }
1010
1369
  }