auditor-lambda 0.7.0 → 0.9.0

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