auditor-lambda 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/audit-code-wrapper-lib.mjs +149 -129
  2. package/dist/adapters/normalizeExternal.js +6 -3
  3. package/dist/cli/args.d.ts +0 -1
  4. package/dist/cli/args.js +0 -6
  5. package/dist/cli/dispatch.js +3 -2
  6. package/dist/cli/lineIndex.js +4 -1
  7. package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
  8. package/dist/cli/mergeAndIngestCommand.js +219 -0
  9. package/dist/cli/nextStepCommand.js +5 -1
  10. package/dist/cli/runToCompletion.d.ts +9 -0
  11. package/dist/cli/runToCompletion.js +655 -480
  12. package/dist/cli/statusCommand.d.ts +1 -0
  13. package/dist/cli/statusCommand.js +113 -0
  14. package/dist/cli/submitPacketCommand.d.ts +1 -0
  15. package/dist/cli/submitPacketCommand.js +155 -0
  16. package/dist/cli/workerResult.d.ts +1 -1
  17. package/dist/cli/workerRunCommand.d.ts +1 -0
  18. package/dist/cli/workerRunCommand.js +88 -0
  19. package/dist/cli.js +14 -563
  20. package/dist/extractors/analyzers/sql.js +4 -1
  21. package/dist/extractors/analyzers/treeSitter.js +29 -15
  22. package/dist/extractors/analyzers/typescript.js +10 -8
  23. package/dist/extractors/designAssessment.js +43 -24
  24. package/dist/extractors/graph.js +139 -73
  25. package/dist/extractors/pathPatterns.js +17 -5
  26. package/dist/io/runArtifactTypes.d.ts +18 -0
  27. package/dist/io/runArtifactTypes.js +1 -0
  28. package/dist/io/runArtifacts.d.ts +2 -18
  29. package/dist/io/runArtifacts.js +14 -3
  30. package/dist/mcp/server.js +9 -0
  31. package/dist/orchestrator/advance.js +37 -22
  32. package/dist/orchestrator/artifactFreshness.js +2 -2
  33. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  34. package/dist/orchestrator/autoFixExecutor.js +16 -8
  35. package/dist/orchestrator/dependencyMap.d.ts +1 -1
  36. package/dist/orchestrator/dependencyMap.js +7 -1
  37. package/dist/orchestrator/fileAnchors.js +14 -3
  38. package/dist/orchestrator/flowCoverage.js +1 -0
  39. package/dist/orchestrator/flowRequeue.js +4 -1
  40. package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
  41. package/dist/orchestrator/ingestionExecutors.js +237 -0
  42. package/dist/orchestrator/intakeExecutors.d.ts +3 -0
  43. package/dist/orchestrator/intakeExecutors.js +25 -0
  44. package/dist/orchestrator/planningExecutors.d.ts +4 -0
  45. package/dist/orchestrator/planningExecutors.js +95 -0
  46. package/dist/orchestrator/runtimeCommand.js +7 -15
  47. package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
  48. package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
  49. package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
  50. package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
  51. package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
  52. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
  53. package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
  54. package/dist/orchestrator/selectiveDeepening/index.js +128 -0
  55. package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
  56. package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
  57. package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
  58. package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
  59. package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
  60. package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
  61. package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
  62. package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
  63. package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
  64. package/dist/orchestrator/selectiveDeepening.js +6 -760
  65. package/dist/orchestrator/staleness.js +3 -3
  66. package/dist/orchestrator/structureExecutors.d.ts +5 -0
  67. package/dist/orchestrator/structureExecutors.js +94 -0
  68. package/dist/orchestrator/taskBuilder.d.ts +2 -2
  69. package/dist/orchestrator/taskBuilder.js +101 -82
  70. package/dist/providers/index.d.ts +7 -0
  71. package/dist/providers/index.js +14 -95
  72. package/dist/quota/discoveredLimits.d.ts +1 -0
  73. package/dist/quota/discoveredLimits.js +7 -1
  74. package/dist/quota/index.d.ts +0 -2
  75. package/dist/quota/index.js +1 -2
  76. package/dist/reporting/workBlocks.js +7 -4
  77. package/dist/types/reviewPlanning.d.ts +23 -16
  78. package/dist/validation/auditResults.js +97 -95
  79. package/dist/validation/sessionConfig.d.ts +2 -2
  80. package/dist/validation/sessionConfig.js +14 -7
  81. package/package.json +4 -3
  82. package/schemas/audit_findings.schema.json +3 -3
  83. package/schemas/critical_flows.schema.json +3 -2
  84. package/schemas/dispatch_quota.schema.json +1 -1
  85. package/schemas/graph_bundle.schema.json +1 -1
  86. package/schemas/review_packets.schema.json +1 -1
  87. package/schemas/step_contract.schema.json +80 -0
  88. package/scripts/postinstall.mjs +19 -2
  89. package/skills/audit-code/opencode-command-template.txt +3 -3
  90. package/dist/orchestrator/internalExecutors.js +0 -424
  91. package/dist/providers/localSubprocessProvider.d.ts +0 -9
  92. package/dist/providers/localSubprocessProvider.js +0 -18
  93. package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
  94. package/dist/providers/subprocessTemplateProvider.js +0 -59
  95. package/dist/providers/vscodeTaskProvider.d.ts +0 -7
  96. package/dist/providers/vscodeTaskProvider.js +0 -14
  97. package/dist/quota/probe.d.ts +0 -10
  98. package/dist/quota/probe.js +0 -18
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, readdir, rm, } from "node:fs/promises";
1
+ import { mkdir, readFile, rm, } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join, resolve, } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -11,29 +11,31 @@ import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
11
11
  import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
12
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
13
13
  import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
14
- import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, } from "@audit-tools/shared";
14
+ import { isFileMissingError, readJsonFile, prefixValidationIssues, DEFAULT_EMPIRICAL_HALF_LIFE_HOURS, } from "@audit-tools/shared";
15
15
  import { buildQuotaSource } from "@audit-tools/shared/quota/compositeQuotaSource";
16
16
  import { validateArtifactBundle } from "./validation/artifacts.js";
17
- import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
17
+ import { validateAuditResults, } 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
21
  import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
22
- import { loadRunLedger, } from "./supervisor/runLedger.js";
23
22
  import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
24
23
  import { clearDispatchFiles, ensureSupervisorDirs, } from "./io/runArtifacts.js";
25
24
  import { runAuditCodeMcpServer } from "./mcp/server.js";
26
- import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, lookupDiscoveredLimits, setQuotaStateDir, } from "./quota/index.js";
27
- import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, isCanonicalResultFilename, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
28
- import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, formatAuditResultValidationError, } from "./cli/workerResult.js";
29
- import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
30
- import { buildLineIndex, buildLineIndexForPaths, addFileLineCountHints, } from "./cli/lineIndex.js";
25
+ import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit, computeMaxSafeConcurrency, getQuotaStatePath, lookupDiscoveredLimits, setQuotaStateDir, } from "./quota/index.js";
26
+ import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
27
+ import { ACTIVE_DISPATCH_FILENAME, loadDispatchResultMap, prepareDispatchArtifacts, } from "./cli/dispatch.js";
28
+ import { buildLineIndex, } from "./cli/lineIndex.js";
31
29
  import { emitEnvelope, } from "./cli/envelope.js";
32
30
  import { persistConfigErrorHandoff } from "./cli/reviewRun.js";
33
31
  import { runAuditStep, ingestBatchAuditResults, } from "./cli/auditStep.js";
34
32
  import { packageRoot } from "./cli/paths.js";
35
33
  import { cmdNextStep } from "./cli/nextStepCommand.js";
36
34
  import { cmdRunToCompletion } from "./cli/runToCompletion.js";
35
+ import { cmdWorkerRun } from "./cli/workerRunCommand.js";
36
+ import { cmdSubmitPacket } from "./cli/submitPacketCommand.js";
37
+ import { cmdMergeAndIngest } from "./cli/mergeAndIngestCommand.js";
38
+ import { cmdStatus } from "./cli/statusCommand.js";
37
39
  import { cleanupStaleArtifactsDir } from "./cli/cleanup.js";
38
40
  const SAMPLE_REPO_FILES = [
39
41
  { path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
@@ -218,88 +220,6 @@ async function cmdAdvanceAudit(argv) {
218
220
  await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
219
221
  }
220
222
  }
221
- async function cmdWorkerRun(argv) {
222
- const taskPath = getFlag(argv, "--task");
223
- if (!taskPath) {
224
- throw new Error("worker-run requires --task <path>");
225
- }
226
- const task = await readJsonFile(taskPath);
227
- let workerResult;
228
- try {
229
- if (looksLikeCliFlag(task.audit_results_path)) {
230
- throw new Error(`task.audit_results_path resolved to '${task.audit_results_path}', which looks like a CLI flag instead of a file path.`);
231
- }
232
- if (task.preferred_executor === "agent" && !task.audit_results_path) {
233
- throw new Error("agent worker-run requires audit_results_path so provider-assisted review can be ingested.");
234
- }
235
- if (task.preferred_executor === "agent" && task.audit_results_path) {
236
- const pendingTasks = task.pending_audit_tasks_path
237
- ? await readJsonFile(task.pending_audit_tasks_path)
238
- : [];
239
- const auditResults = await readJsonFile(task.audit_results_path);
240
- const pendingTaskIds = new Set(pendingTasks.map((item) => item.task_id));
241
- const matchedResultCount = auditResults.filter((result) => pendingTaskIds.has(result.task_id)).length;
242
- if (pendingTasks.length > 0 && matchedResultCount === 0) {
243
- throw new Error("Provider-assisted review did not emit any audit results for the pending audit tasks.");
244
- }
245
- const issues = validateAuditResults(auditResults, pendingTasks, {
246
- lineIndex: await buildLineIndexForPaths(task.repo_root, pendingTasks.flatMap((item) => item.file_paths)),
247
- });
248
- const errors = issues.filter((issue) => issue.severity === "error");
249
- const warnings = issues.filter((issue) => issue.severity === "warning");
250
- if (warnings.length > 0) {
251
- process.stderr.write(`audit-results validation: ${warnings.length} warning(s):\n` +
252
- formatAuditResultIssues(warnings) +
253
- "\n");
254
- }
255
- if (errors.length > 0) {
256
- throw new Error(formatAuditResultValidationError(errors));
257
- }
258
- }
259
- const preferredExecutor = task.preferred_executor === "agent"
260
- ? "result_ingestion_executor"
261
- : task.preferred_executor;
262
- const result = await runAuditStep({
263
- root: task.repo_root,
264
- artifactsDir: task.artifacts_dir,
265
- preferredExecutor,
266
- auditResultsPath: task.audit_results_path,
267
- runtimeUpdatesPath: task.runtime_updates_path,
268
- externalAnalyzerPath: task.external_analyzer_results_path,
269
- });
270
- workerResult = {
271
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
272
- run_id: task.run_id,
273
- obligation_id: task.obligation_id,
274
- status: result.progress_made ? "completed" : "no_progress",
275
- progress_made: result.progress_made,
276
- selected_executor: result.selected_executor,
277
- artifacts_written: result.artifacts_written,
278
- summary: result.progress_summary,
279
- next_likely_step: result.next_likely_step,
280
- errors: [],
281
- };
282
- }
283
- catch (error) {
284
- workerResult = {
285
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
286
- run_id: task.run_id,
287
- obligation_id: task.obligation_id,
288
- status: "failed",
289
- progress_made: false,
290
- selected_executor: task.preferred_executor,
291
- artifacts_written: [],
292
- summary: `Worker failed for executor ${task.preferred_executor}: ${error instanceof Error ? error.message : String(error)}`,
293
- next_likely_step: task.obligation_id,
294
- errors: [error instanceof Error ? error.message : String(error)],
295
- };
296
- }
297
- await writeJsonFile(task.result_path, workerResult);
298
- console.log(JSON.stringify(workerResult, null, 2));
299
- if (workerResult.status === "failed") {
300
- process.exitCode = 1;
301
- }
302
- }
303
223
  async function cmdPrepareDispatch(argv) {
304
224
  const runId = getFlag(argv, "--run-id");
305
225
  if (!runId)
@@ -320,365 +240,6 @@ async function cmdPrepareDispatch(argv) {
320
240
  });
321
241
  console.log(JSON.stringify(result, null, 2));
322
242
  }
323
- async function cmdSubmitPacket(argv) {
324
- const runId = resolveRunScopedArg(argv, "--run-id", "--run-id-b64");
325
- const packetId = resolveRunScopedArg(argv, "--packet-id", "--packet-id-b64");
326
- const artifactsDirB64 = getFlag(argv, "--artifacts-dir-b64");
327
- const artifactsDir = artifactsDirB64
328
- ? resolve(fromBase64Url(artifactsDirB64))
329
- : getArtifactsDir(argv);
330
- if (!runId || !packetId) {
331
- throw new Error("submit-packet requires --run-id and --packet-id (or --run-id-b64/--packet-id-b64)");
332
- }
333
- const runDir = join(artifactsDir, "runs", runId);
334
- const tasksPath = join(runDir, "pending-audit-tasks.json");
335
- const resultMap = await loadDispatchResultMap(runDir);
336
- if (!resultMap) {
337
- throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
338
- }
339
- let packetEntries = resultMap.entries.filter((entry) => entry.packet_id === packetId);
340
- let resolvedPacketId = packetId;
341
- if (packetEntries.length === 0) {
342
- const trimmed = packetId.trim();
343
- packetEntries = resultMap.entries.filter((entry) => entry.packet_id.trim().toLowerCase() === trimmed.toLowerCase());
344
- if (packetEntries.length > 0) {
345
- resolvedPacketId = packetEntries[0].packet_id;
346
- process.stderr.write(`[submit-packet] Resolved packet_id '${packetId}' → '${resolvedPacketId}' (case/whitespace normalization)\n`);
347
- }
348
- }
349
- if (packetEntries.length === 0) {
350
- const knownIds = [...new Set(resultMap.entries.map((e) => e.packet_id))];
351
- throw new Error(`Unknown packet_id '${packetId}' for run ${runId}.\n` +
352
- `Valid packet IDs: ${knownIds.join(", ")}`);
353
- }
354
- if (entriesByTaskId(packetEntries).size !== packetEntries.length) {
355
- throw new Error(`Dispatch result map has duplicate task entries for packet '${resolvedPacketId}'.`);
356
- }
357
- const allTasks = await readJsonFile(tasksPath);
358
- const taskById = new Map(allTasks.map((task) => [task.task_id, task]));
359
- const packetTasks = packetEntries.map((entry) => taskById.get(entry.task_id));
360
- const missingTask = packetEntries.find((entry, index) => !packetTasks[index]);
361
- if (missingTask) {
362
- throw new Error(`Dispatch result map references unknown task '${missingTask.task_id}'.`);
363
- }
364
- const tasks = packetTasks;
365
- const expectedTaskIds = new Set(tasks.map((task) => task.task_id));
366
- const lineIndex = Object.fromEntries(tasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
367
- const encodedResults = getFlag(argv, "--results-b64");
368
- const raw = encodedResults ? fromBase64Url(encodedResults) : await readStdinText();
369
- if (raw.trim().length === 0) {
370
- throw new Error("submit-packet requires an AuditResult[] JSON payload on stdin or --results-b64.");
371
- }
372
- let payload;
373
- try {
374
- payload = JSON.parse(raw);
375
- }
376
- catch (error) {
377
- throw new Error(`Invalid submit-packet JSON: ${error instanceof Error ? error.message : String(error)}`);
378
- }
379
- const resultErrors = [];
380
- const issues = validateAuditResults(payload, tasks, { lineIndex });
381
- const validationErrors = issues.filter((issue) => issue.severity === "error");
382
- const validationWarnings = issues.filter((issue) => issue.severity === "warning");
383
- if (validationWarnings.length > 0) {
384
- process.stderr.write(`audit-results validation: ${validationWarnings.length} warning(s):\n` +
385
- formatAuditResultIssues(validationWarnings) +
386
- "\n");
387
- }
388
- if (validationErrors.length > 0) {
389
- resultErrors.push(formatAuditResultIssues(validationErrors));
390
- }
391
- if (Array.isArray(payload)) {
392
- const seen = new Set();
393
- for (const [index, result] of payload.entries()) {
394
- if (!result || typeof result !== "object" || Array.isArray(result)) {
395
- continue;
396
- }
397
- const taskId = result.task_id;
398
- if (typeof taskId !== "string" || taskId.trim().length === 0) {
399
- continue;
400
- }
401
- if (seen.has(taskId)) {
402
- resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
403
- }
404
- seen.add(taskId);
405
- if (!expectedTaskIds.has(taskId)) {
406
- resultErrors.push(`Result at index ${index} uses task_id '${taskId}', which is not assigned to packet '${resolvedPacketId}'.`);
407
- }
408
- }
409
- for (const task of tasks) {
410
- if (!seen.has(task.task_id)) {
411
- resultErrors.push(`Missing audit result for assigned task '${task.task_id}'.`);
412
- }
413
- }
414
- }
415
- if (resultErrors.length > 0) {
416
- throw new Error(`submit-packet rejected ${resolvedPacketId}:\n${resultErrors.join("\n")}`);
417
- }
418
- // Check for duplicate findings against already-submitted results in this run
419
- const existingFindingKeys = new Set();
420
- const otherEntries = resultMap.entries.filter((e) => e.packet_id !== resolvedPacketId);
421
- for (const other of otherEntries) {
422
- try {
423
- const existing = JSON.parse(await readFile(other.result_path, "utf8"));
424
- if (existing?.findings) {
425
- for (const f of existing.findings) {
426
- const key = [
427
- (f.lens ?? "").trim().toLowerCase(),
428
- (f.category ?? "").trim().toLowerCase(),
429
- (f.title ?? "").trim().toLowerCase(),
430
- f.affected_files?.[0]?.path ?? "",
431
- ].join("|");
432
- existingFindingKeys.add(key);
433
- }
434
- }
435
- }
436
- catch { /* file doesn't exist yet or invalid — skip */ }
437
- }
438
- let dupCount = 0;
439
- for (const result of payload) {
440
- for (const f of result.findings ?? []) {
441
- const key = [
442
- (f.lens ?? "").trim().toLowerCase(),
443
- (f.category ?? "").trim().toLowerCase(),
444
- (f.title ?? "").trim().toLowerCase(),
445
- f.affected_files?.[0]?.path ?? "",
446
- ].join("|");
447
- if (existingFindingKeys.has(key)) {
448
- dupCount++;
449
- }
450
- }
451
- }
452
- if (dupCount > 0) {
453
- process.stderr.write(`[submit-packet] Warning: ${dupCount} finding(s) appear to duplicate findings from other packets in this run.\n`);
454
- }
455
- const entryByTaskId = entriesByTaskId(packetEntries);
456
- for (const result of payload) {
457
- const entry = entryByTaskId.get(result.task_id);
458
- if (!entry) {
459
- throw new Error(`Internal error: no result path for accepted task '${result.task_id}'.`);
460
- }
461
- await writeJsonFile(entry.result_path, result);
462
- }
463
- const findingCount = payload.reduce((sum, result) => sum + result.findings.length, 0);
464
- console.log(JSON.stringify({
465
- run_id: runId,
466
- packet_id: resolvedPacketId,
467
- accepted_count: payload.length,
468
- finding_count: findingCount,
469
- ...(dupCount > 0 ? { duplicate_warning_count: dupCount } : {}),
470
- }, null, 2));
471
- }
472
- async function cmdMergeAndIngest(argv) {
473
- const runId = getFlag(argv, "--run-id");
474
- if (!runId)
475
- throw new Error("merge-and-ingest requires --run-id <run_id>");
476
- const artifactsDir = getArtifactsDir(argv);
477
- const runDir = join(artifactsDir, "runs", runId);
478
- const taskResultsDir = join(runDir, "task-results");
479
- const auditResultsPath = join(runDir, "audit-results.json");
480
- const taskPath = join(runDir, "task.json");
481
- const tasksPath = join(runDir, "pending-audit-tasks.json");
482
- const workerTask = await readJsonFile(taskPath);
483
- const resultMap = await loadDispatchResultMap(runDir);
484
- if (!resultMap) {
485
- throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
486
- }
487
- let allTasks = [];
488
- try {
489
- allTasks = await readJsonFile(tasksPath);
490
- }
491
- catch { /* may not exist */ }
492
- const entryByTaskId = entriesByTaskId(resultMap.entries);
493
- if (entryByTaskId.size !== resultMap.entries.length) {
494
- throw new Error(`Dispatch result map for run ${runId} contains duplicate task entries.`);
495
- }
496
- const expectedPaths = new Set(resultMap.entries.map((entry) => resolve(entry.result_path)));
497
- let files;
498
- try {
499
- files = (await readdir(taskResultsDir)).filter(f => f.endsWith(".json")).sort();
500
- }
501
- catch {
502
- files = [];
503
- }
504
- const passing = [];
505
- const failing = [];
506
- const seenTaskIds = new Set();
507
- let spuriousFileCount = 0;
508
- const fallbackByTaskId = new Map();
509
- for (const filename of files) {
510
- const filePath = resolve(join(taskResultsDir, filename));
511
- if (expectedPaths.has(filePath))
512
- continue;
513
- // Not part of this round's plan. Still read it so a current task can be
514
- // recovered by task_id (e.g. a subagent wrote a valid result under a
515
- // non-assigned name).
516
- try {
517
- const raw = await readFile(filePath, "utf8");
518
- const parsed = JSON.parse(raw);
519
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
520
- const tid = typeof parsed.task_id === "string"
521
- ? String(parsed.task_id) : undefined;
522
- if (tid && !fallbackByTaskId.has(tid)) {
523
- fallbackByTaskId.set(tid, parsed);
524
- }
525
- }
526
- }
527
- catch { /* not parseable — skip */ }
528
- // Only genuinely stray files are "spurious". Canonical per-task result files
529
- // (<stem>_<digest>.json) left by prior deepening rounds in the same
530
- // task-results/ dir are legitimate and must not inflate the count or bury
531
- // the real stray-file signal (3 -> 191 over a run before this fix).
532
- if (!isCanonicalResultFilename(filename)) {
533
- spuriousFileCount++;
534
- process.stderr.write(`[merge-and-ingest] Warning: unexpected file in task-results/: ${filename}\n`);
535
- }
536
- }
537
- for (const task of allTasks) {
538
- const entry = entryByTaskId.get(task.task_id);
539
- if (!entry) {
540
- failing.push({
541
- task_id: task.task_id,
542
- errors: ["Missing dispatch result-map entry for assigned task."],
543
- });
544
- continue;
545
- }
546
- const filePath = entry.result_path;
547
- let obj;
548
- try {
549
- obj = JSON.parse(await readFile(filePath, "utf8"));
550
- }
551
- catch (e) {
552
- if (isFileMissingError(e)) {
553
- const fallback = fallbackByTaskId.get(task.task_id);
554
- if (fallback) {
555
- process.stderr.write(`[merge-and-ingest] Recovered result for '${task.task_id}' from unexpected file (matched by task_id)\n`);
556
- obj = fallback;
557
- }
558
- else {
559
- failing.push({
560
- task_id: task.task_id,
561
- errors: ["Missing audit result for assigned task."],
562
- });
563
- continue;
564
- }
565
- }
566
- else {
567
- failing.push({ task_id: task.task_id, errors: [`Invalid JSON: ${e.message}`] });
568
- continue;
569
- }
570
- }
571
- const record = obj && typeof obj === "object" && !Array.isArray(obj)
572
- ? obj
573
- : undefined;
574
- const taskId = typeof record?.task_id === "string"
575
- ? String(record.task_id) : undefined;
576
- const resultErrors = [];
577
- if (taskId) {
578
- if (seenTaskIds.has(taskId)) {
579
- resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
580
- }
581
- else {
582
- seenTaskIds.add(taskId);
583
- }
584
- if (taskId !== task.task_id) {
585
- resultErrors.push(`Result file is assigned to '${task.task_id}' but contains task_id '${taskId}'.`);
586
- }
587
- }
588
- const issues = validateAuditResults([obj], [task], { lineIndex: task.file_line_counts ?? {} });
589
- resultErrors.push(...issues
590
- .filter(i => i.severity === "error")
591
- .map(i => i.message));
592
- if (resultErrors.length === 0) {
593
- passing.push(obj);
594
- }
595
- else {
596
- failing.push({ task_id: taskId ?? task.task_id, errors: resultErrors });
597
- }
598
- }
599
- await writeJsonFile(auditResultsPath, passing);
600
- const failedTasksPath = join(runDir, "failed-tasks.json");
601
- if (failing.length > 0) {
602
- await writeJsonFile(failedTasksPath, failing);
603
- }
604
- if (passing.length === 0 && failing.length > 0) {
605
- throw new Error(`All ${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
606
- }
607
- const findingCount = passing.reduce((sum, result) => sum + result.findings.length, 0);
608
- let result = null;
609
- if (passing.length > 0) {
610
- result = await runAuditStep({
611
- root: workerTask.repo_root,
612
- artifactsDir,
613
- preferredExecutor: "result_ingestion_executor",
614
- auditResultsPath,
615
- });
616
- const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
617
- await writeJsonFile(tasksPath, updatedPendingTasks);
618
- }
619
- const activeDispatchPath = join(artifactsDir, ACTIVE_DISPATCH_FILENAME);
620
- try {
621
- const dispatch = await readJsonFile(activeDispatchPath);
622
- if (dispatch.run_id === runId) {
623
- dispatch.status = failing.length > 0 ? "active" : "merged";
624
- await writeJsonFile(activeDispatchPath, dispatch);
625
- }
626
- }
627
- catch { /* no active dispatch file — skip */ }
628
- let retryDispatchPath = null;
629
- if (failing.length > 0) {
630
- const failedTaskIds = new Set(failing.map((f) => f.task_id));
631
- const failedPacketIds = [
632
- ...new Set(resultMap.entries
633
- .filter((e) => failedTaskIds.has(e.task_id))
634
- .map((e) => e.packet_id)),
635
- ];
636
- const retryDispatch = {
637
- run_id: runId,
638
- retry_packet_ids: failedPacketIds,
639
- failed_task_count: failing.length,
640
- accepted_task_count: passing.length,
641
- };
642
- retryDispatchPath = join(runDir, "retry-dispatch.json");
643
- await writeJsonFile(retryDispatchPath, retryDispatch);
644
- process.stderr.write(`[merge-and-ingest] ${passing.length} accepted, ${failing.length} failed. ` +
645
- `Retry packets: ${failedPacketIds.join(", ")}\n`);
646
- }
647
- const status = failing.length > 0
648
- ? "partial"
649
- : (result?.progress_made ? "completed" : "no_progress");
650
- const workerResult = buildWorkerResult({
651
- runId,
652
- obligationId: workerTask.obligation_id,
653
- status: failing.length > 0 ? "no_progress" : (result?.progress_made ? "completed" : "no_progress"),
654
- progressMade: result?.progress_made ?? false,
655
- selectedExecutor: result?.selected_executor ?? null,
656
- artifactsWritten: result?.artifacts_written ?? [],
657
- summary: result?.progress_summary ?? `${failing.length} task(s) failed`,
658
- nextLikelyStep: result?.next_likely_step ?? null,
659
- errors: [],
660
- });
661
- await writeJsonFile(workerTask.result_path, workerResult);
662
- console.log(JSON.stringify({
663
- run_id: runId,
664
- status,
665
- accepted_count: passing.length,
666
- rejected_count: failing.length,
667
- spurious_file_count: spuriousFileCount,
668
- finding_count: findingCount,
669
- audit_results_path: auditResultsPath,
670
- ...(retryDispatchPath ? { retry_dispatch_path: retryDispatchPath } : {}),
671
- ...(result ? {
672
- selected_executor: workerResult.selected_executor,
673
- progress_made: workerResult.progress_made,
674
- progress_summary: workerResult.summary,
675
- next_likely_step: workerResult.next_likely_step,
676
- } : {}),
677
- }, null, 2));
678
- if (failing.length > 0) {
679
- process.exitCode = 2;
680
- }
681
- }
682
243
  async function cmdValidateResult(argv) {
683
244
  const rawRunId = getFlag(argv, "--run-id");
684
245
  const runIdB64 = getFlag(argv, "--run-id-b64");
@@ -870,7 +431,7 @@ async function cmdValidate(argv) {
870
431
  : prefixValidationIssues("session_config", validateSessionConfig(rawSessionConfig));
871
432
  const providerIssues = rawSessionConfig === undefined || sessionConfigIssues.length > 0
872
433
  ? []
873
- : prefixValidationIssues("session_config", validateConfiguredProviderEnvironment(rawSessionConfig));
434
+ : prefixValidationIssues("session_config", await validateConfiguredProviderEnvironment(rawSessionConfig));
874
435
  const issues = [
875
436
  ...artifactIssues,
876
437
  ...sessionConfigIssues,
@@ -978,114 +539,6 @@ async function cmdCleanup(argv) {
978
539
  dry_run: dryRun,
979
540
  }, null, 2));
980
541
  }
981
- async function cmdStatus(argv) {
982
- const artifactsDir = getArtifactsDir(argv);
983
- const auditStatePath = join(artifactsDir, "audit_state.json");
984
- // 1. Read audit_state.json
985
- let auditState = null;
986
- try {
987
- auditState = await readJsonFile(auditStatePath);
988
- }
989
- catch (error) {
990
- if (!isFileMissingError(error)) {
991
- throw error;
992
- }
993
- }
994
- if (!auditState) {
995
- console.error("No audit_state.json found; no active audit in this artifacts directory.");
996
- process.exitCode = 1;
997
- return;
998
- }
999
- // Build obligations summary: count by state
1000
- const obligationStates = {
1001
- missing: 0,
1002
- present: 0,
1003
- stale: 0,
1004
- blocked: 0,
1005
- satisfied: 0,
1006
- };
1007
- for (const obligation of auditState.obligations ?? []) {
1008
- const state = obligation.state;
1009
- if (state in obligationStates) {
1010
- obligationStates[state]++;
1011
- }
1012
- }
1013
- // 2. Read run ledger for last N entries
1014
- const ledger = await loadRunLedger(artifactsDir);
1015
- const RECENT_RUN_LIMIT = 5;
1016
- const recentRuns = ledger.runs
1017
- .slice(-RECENT_RUN_LIMIT)
1018
- .reverse()
1019
- .map((entry) => ({
1020
- run_id: entry.run_id,
1021
- obligation_id: entry.obligation_id,
1022
- status: entry.status,
1023
- started_at: entry.started_at,
1024
- }));
1025
- // 3. Find the most recent run directory and read pending-audit-tasks.json
1026
- let pendingTasksSummary = null;
1027
- const runsDir = join(artifactsDir, "runs");
1028
- let runDirs = [];
1029
- try {
1030
- const entries = await readdir(runsDir, { withFileTypes: true });
1031
- runDirs = entries
1032
- .filter((e) => e.isDirectory())
1033
- .map((e) => e.name)
1034
- .sort()
1035
- .reverse();
1036
- }
1037
- catch {
1038
- // runs directory may not exist yet
1039
- }
1040
- for (const runDirName of runDirs) {
1041
- const runDir = join(runsDir, runDirName);
1042
- const tasksPath = join(runDir, "pending-audit-tasks.json");
1043
- let tasks = null;
1044
- try {
1045
- tasks = await readJsonFile(tasksPath);
1046
- }
1047
- catch {
1048
- continue; // no pending-audit-tasks.json in this run dir — try previous
1049
- }
1050
- if (!Array.isArray(tasks))
1051
- continue;
1052
- // Count remaining: tasks without status "complete"
1053
- const total = tasks.length;
1054
- const remaining = tasks.filter((t) => t.status !== "complete").length;
1055
- pendingTasksSummary = {
1056
- run_id: runDirName,
1057
- total,
1058
- remaining,
1059
- };
1060
- break;
1061
- }
1062
- // 4. Surface failed-tasks.json from the most recent run that has one
1063
- let failedTasks = null;
1064
- for (const runDirName of runDirs) {
1065
- const failedTasksPath = join(runsDir, runDirName, "failed-tasks.json");
1066
- try {
1067
- const raw = await readJsonFile(failedTasksPath);
1068
- if (Array.isArray(raw) && raw.length > 0) {
1069
- failedTasks = raw;
1070
- break;
1071
- }
1072
- }
1073
- catch {
1074
- // Not present in this run dir — keep looking
1075
- }
1076
- }
1077
- console.log(JSON.stringify({
1078
- artifacts_dir: artifactsDir,
1079
- status: auditState.status,
1080
- last_obligation: auditState.last_obligation ?? null,
1081
- last_executor: auditState.last_executor ?? null,
1082
- blockers: auditState.blockers ?? [],
1083
- obligations_summary: obligationStates,
1084
- recent_runs: recentRuns,
1085
- pending_tasks: pendingTasksSummary,
1086
- failed_tasks: failedTasks,
1087
- }, null, 2));
1088
- }
1089
542
  async function cmdMcp(argv) {
1090
543
  await runAuditCodeMcpServer(argv.slice(3));
1091
544
  }
@@ -1094,14 +547,13 @@ async function cmdQuota(argv) {
1094
547
  const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
1095
548
  const explicitProvider = getExplicitProvider(argv);
1096
549
  const hostModel = getHostModel(argv);
1097
- const probeMode = getQuotaProbeMode(argv, sessionConfig);
1098
550
  const providerName = resolveFreshSessionProviderName(explicitProvider, sessionConfig);
1099
551
  const providerModelKey = buildProviderModelKey(providerName, hostModel);
1100
552
  const { limits, source, confidence } = resolveLimits({ providerName, sessionConfig, hostModel });
1101
- const probeResult = await probeProvider(providerName, probeMode);
1102
553
  const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
1103
554
  const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
1104
- const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
555
+ const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ??
556
+ DEFAULT_EMPIRICAL_HALF_LIFE_HOURS;
1105
557
  const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
1106
558
  explicitLimit: getHostMaxActiveSubagents(argv),
1107
559
  sessionConfig,
@@ -1127,7 +579,6 @@ async function cmdQuota(argv) {
1127
579
  confidence,
1128
580
  source,
1129
581
  host_concurrency_limit: hostConcurrencyLimit,
1130
- probe: probeResult,
1131
582
  learned_caps: quotaStateEntry
1132
583
  ? {
1133
584
  max_safe_concurrency: computeMaxSafeConcurrency(quotaStateEntry, halfLifeHours),
@@ -9,7 +9,10 @@ import { normalizeGraphPath } from "../graphPathUtils.js";
9
9
  function supports(file) {
10
10
  return normalizeGraphPath(file).toLowerCase().endsWith(".sql");
11
11
  }
12
- function analyze() {
12
+ // Signature matches the `LanguageAnalyzer.analyze` interface (the params the
13
+ // other analyzers receive) so the registry type stays uniform; the stub
14
+ // ignores them and emits no edges yet.
15
+ function analyze(_files, _context) {
13
16
  return { edges: [] };
14
17
  }
15
18
  export const sqlAnalyzer = {