auditor-lambda 0.3.41 → 0.6.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 (78) hide show
  1. package/dist/cli/dispatch.js +5 -1
  2. package/dist/cli/prompts.d.ts +19 -0
  3. package/dist/cli/prompts.js +95 -0
  4. package/dist/cli/steps.d.ts +1 -1
  5. package/dist/cli.js +398 -78
  6. package/dist/extractors/analyzers/css.d.ts +2 -0
  7. package/dist/extractors/analyzers/css.js +101 -0
  8. package/dist/extractors/analyzers/html.d.ts +2 -0
  9. package/dist/extractors/analyzers/html.js +92 -0
  10. package/dist/extractors/analyzers/merge.d.ts +14 -0
  11. package/dist/extractors/analyzers/merge.js +85 -0
  12. package/dist/extractors/analyzers/python.d.ts +2 -0
  13. package/dist/extractors/analyzers/python.js +104 -0
  14. package/dist/extractors/analyzers/registry.d.ts +33 -0
  15. package/dist/extractors/analyzers/registry.js +100 -0
  16. package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
  17. package/dist/extractors/analyzers/resourceUrl.js +25 -0
  18. package/dist/extractors/analyzers/sql.d.ts +2 -0
  19. package/dist/extractors/analyzers/sql.js +19 -0
  20. package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
  21. package/dist/extractors/analyzers/treeSitter.js +111 -0
  22. package/dist/extractors/analyzers/types.d.ts +53 -0
  23. package/dist/extractors/analyzers/types.js +1 -0
  24. package/dist/extractors/analyzers/typescript.d.ts +2 -0
  25. package/dist/extractors/analyzers/typescript.js +257 -0
  26. package/dist/extractors/disposition.js +8 -1
  27. package/dist/extractors/graph.d.ts +1 -0
  28. package/dist/extractors/graph.js +167 -1
  29. package/dist/extractors/graphPythonImports.d.ts +15 -0
  30. package/dist/extractors/graphPythonImports.js +36 -0
  31. package/dist/extractors/pathPatterns.d.ts +6 -0
  32. package/dist/extractors/pathPatterns.js +8 -0
  33. package/dist/io/artifacts.d.ts +13 -1
  34. package/dist/io/artifacts.js +19 -3
  35. package/dist/mcp/server.js +3 -3
  36. package/dist/orchestrator/advance.d.ts +20 -0
  37. package/dist/orchestrator/advance.js +61 -2
  38. package/dist/orchestrator/dependencyMap.js +27 -0
  39. package/dist/orchestrator/edgeReasoning.d.ts +39 -0
  40. package/dist/orchestrator/edgeReasoning.js +125 -0
  41. package/dist/orchestrator/executors.js +11 -1
  42. package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
  43. package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
  44. package/dist/orchestrator/internalExecutors.d.ts +10 -1
  45. package/dist/orchestrator/internalExecutors.js +89 -11
  46. package/dist/orchestrator/localCommands.js +6 -25
  47. package/dist/orchestrator/nextStep.js +2 -0
  48. package/dist/orchestrator/reviewPackets.d.ts +37 -4
  49. package/dist/orchestrator/reviewPackets.js +93 -46
  50. package/dist/orchestrator/runtimeValidation.js +4 -31
  51. package/dist/orchestrator/scope.d.ts +62 -0
  52. package/dist/orchestrator/scope.js +227 -0
  53. package/dist/orchestrator/state.js +2 -0
  54. package/dist/reporting/synthesis.d.ts +37 -2
  55. package/dist/reporting/synthesis.js +95 -16
  56. package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
  57. package/dist/reporting/synthesisNarrativePrompt.js +60 -0
  58. package/dist/reporting/workBlocks.d.ts +2 -10
  59. package/dist/supervisor/operatorHandoff.d.ts +1 -1
  60. package/dist/supervisor/operatorHandoff.js +26 -16
  61. package/dist/supervisor/sessionConfig.d.ts +8 -1
  62. package/dist/supervisor/sessionConfig.js +22 -1
  63. package/dist/types/analyzerCapability.d.ts +16 -0
  64. package/dist/types/analyzerCapability.js +1 -0
  65. package/dist/types/auditScope.d.ts +43 -0
  66. package/dist/types/auditScope.js +14 -0
  67. package/dist/types/synthesisNarrative.d.ts +7 -0
  68. package/dist/types/synthesisNarrative.js +5 -0
  69. package/dist/types.d.ts +2 -19
  70. package/dist/validation/artifacts.js +9 -0
  71. package/dist/validation/sessionConfig.js +24 -1
  72. package/docs/contracts.md +10 -3
  73. package/package.json +4 -2
  74. package/schemas/analyzer_capability.schema.json +47 -0
  75. package/schemas/audit_findings.schema.json +141 -0
  76. package/schemas/finding.schema.json +2 -1
  77. package/schemas/graph_bundle.schema.json +5 -0
  78. package/schemas/scope.schema.json +46 -0
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, readdir, rename, rm, unlink } from "node:fs/promises";
1
+ import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
@@ -10,8 +10,8 @@ import { buildUnitManifest } from "./orchestrator/unitBuilder.js";
10
10
  import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
11
11
  import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
12
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
13
- import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
14
- import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues } from "@audit-tools/shared";
13
+ import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, AUDIT_REPORT_FILENAME, } from "./io/artifacts.js";
14
+ import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, RunLogger } from "@audit-tools/shared";
15
15
  import { validateArtifactBundle } from "./validation/artifacts.js";
16
16
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
17
17
  import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
@@ -20,21 +20,26 @@ import { deriveAuditState } from "./orchestrator/state.js";
20
20
  import { advanceAudit } from "./orchestrator/advance.js";
21
21
  import { checkFileIntegrity } from "./orchestrator/fileIntegrity.js";
22
22
  import { decideNextStep } from "./orchestrator/nextStep.js";
23
+ import { collectLowConfidenceEdges, buildEdgeReasoningPrompt, edgeReasoningContentHash, } from "./orchestrator/edgeReasoning.js";
23
24
  import { renderDesignReviewPrompt } from "./orchestrator/designReviewPrompt.js";
25
+ import { renderSynthesisNarrativePrompt } from "./reporting/synthesisNarrativePrompt.js";
24
26
  import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
25
27
  import { appendRunLedgerEntry, loadRunLedger } from "./supervisor/runLedger.js";
26
28
  import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
27
- import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
29
+ import { getSessionConfigPath, loadSessionConfig, persistAnalyzerSettings, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
30
+ import { resolveAnalyzerPlan, needsInstallDecision, } from "./extractors/analyzers/registry.js";
31
+ import { buildPathLookup } from "./extractors/graph.js";
32
+ import { buildDispositionMap } from "./extractors/disposition.js";
28
33
  import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
29
34
  import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
30
- import { estimateTaskGroupTokens, } from "./orchestrator/reviewPackets.js";
35
+ import { estimateTaskGroupTokens, sizeIndexFromManifest, } from "./orchestrator/reviewPackets.js";
31
36
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
32
37
  import { runAuditCodeMcpServer } from "./mcp/server.js";
33
38
  import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
34
39
  // Re-exports from extracted modules
35
40
  export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
36
41
  import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, fromBase64Url, renderCommand, summarizeLaunchExit, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, resolveHostDispatchCapability, countLines, listBatchResultFiles, } from "./cli/args.js";
37
- import { nextStepCommand, mergeAndIngestCommand, renderDispatchReviewPrompt, renderSingleTaskFallbackStepPrompt, renderPresentReportPrompt, renderBlockedStepPrompt, } from "./cli/prompts.js";
42
+ import { nextStepCommand, mergeAndIngestCommand, renderDispatchReviewPrompt, renderSingleTaskFallbackStepPrompt, renderPresentReportPrompt, renderAnalyzerInstallPrompt, renderEdgeReasoningStepPrompt, renderEdgeReasoningDispatchPrompt, renderBlockedStepPrompt, } from "./cli/prompts.js";
38
43
  import { writeCurrentStep, } from "./cli/steps.js";
39
44
  import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArtifacts, isWorkerResult, buildWorkerFailureBlocker, formatAuditResultValidationError, } from "./cli/workerResult.js";
40
45
  import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
@@ -313,9 +318,15 @@ async function maybeArchiveLegacyPendingResults(auditResultsPath) {
313
318
  }
314
319
  async function runAuditStep(options) {
315
320
  const bundle = await loadArtifactBundle(options.artifactsDir);
321
+ const runLogger = new RunLogger(join(options.artifactsDir, "run.log.jsonl"), {
322
+ enabled: options.runLog ?? true,
323
+ });
316
324
  const lineIndex = bundle.repo_manifest
317
325
  ? await buildLineIndex(options.root, bundle.repo_manifest)
318
326
  : undefined;
327
+ const sizeIndex = bundle.repo_manifest
328
+ ? sizeIndexFromManifest(bundle.repo_manifest)
329
+ : undefined;
319
330
  if (looksLikeCliFlag(options.auditResultsPath)) {
320
331
  throw new Error(`Invalid audit results path '${options.auditResultsPath}'. This looks like a CLI flag rather than a file path.`);
321
332
  }
@@ -343,14 +354,27 @@ async function runAuditStep(options) {
343
354
  const externalAnalyzerResults = options.externalAnalyzerPath
344
355
  ? await readJsonFile(options.externalAnalyzerPath)
345
356
  : undefined;
357
+ const narrativeResults = options.narrativeResultsPath
358
+ ? await readJsonFile(options.narrativeResultsPath)
359
+ : undefined;
360
+ const edgeReasoningResults = options.edgeReasoningResultsPath
361
+ ? await readJsonFile(options.edgeReasoningResultsPath)
362
+ : undefined;
346
363
  const result = await advanceAudit(bundle, {
347
364
  root: options.root,
348
365
  lineIndex,
366
+ sizeIndex,
349
367
  auditResults: auditResults,
350
368
  runtimeValidationUpdates,
351
369
  externalAnalyzerResults,
370
+ narrativeResults,
371
+ edgeReasoningResults,
372
+ analyzers: options.analyzers,
373
+ graphLlmEdgeReasoning: options.graphLlmEdgeReasoning,
374
+ since: options.since,
352
375
  preferredExecutor: options.preferredExecutor,
353
376
  opentoken: options.opentoken,
377
+ runLogger,
354
378
  });
355
379
  await writeCoreArtifacts(options.artifactsDir, result.updated_bundle);
356
380
  const archivedPendingResults = await maybeArchiveLegacyPendingResults(options.auditResultsPath);
@@ -555,7 +579,11 @@ async function cmdAdvanceAudit(argv) {
555
579
  auditResultsPath: getFlag(argv, "--results"),
556
580
  runtimeUpdatesPath: getFlag(argv, "--updates"),
557
581
  externalAnalyzerPath,
582
+ analyzers: sessionConfig.analyzers,
583
+ graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
584
+ since: getFlag(argv, "--since"),
558
585
  opentoken: sessionConfig.opentoken?.enabled,
586
+ runLog: sessionConfig.observability?.run_log,
559
587
  });
560
588
  if (result.selected_executor !== "agent") {
561
589
  await clearDispatchFiles(artifactsDir);
@@ -579,6 +607,7 @@ async function cmdAdvanceAudit(argv) {
579
607
  }
580
608
  async function runDeterministicForNextStep(params) {
581
609
  let lastSummary = "";
610
+ let analyzers = params.analyzers;
582
611
  for (let index = 0; index < params.maxRuns; index++) {
583
612
  const bundle = await loadArtifactBundle(params.artifactsDir);
584
613
  const decision = decideNextStep(bundle);
@@ -601,8 +630,8 @@ async function runDeterministicForNextStep(params) {
601
630
  state,
602
631
  bundle,
603
632
  finalReportPath: promoted.promoted
604
- ? join(params.root, "audit-report.md")
605
- : join(params.artifactsDir, "audit-report.md"),
633
+ ? join(params.root, AUDIT_REPORT_FILENAME)
634
+ : join(params.artifactsDir, AUDIT_REPORT_FILENAME),
606
635
  };
607
636
  }
608
637
  if (index === 0 && bundle.repo_manifest) {
@@ -621,6 +650,89 @@ async function runDeterministicForNextStep(params) {
621
650
  }
622
651
  }
623
652
  }
653
+ if (decision.selected_executor === "graph_enrichment_executor") {
654
+ const includedFiles = bundle.repo_manifest
655
+ ? [
656
+ ...new Set(buildPathLookup(bundle.repo_manifest, buildDispositionMap(bundle.file_disposition)).values()),
657
+ ]
658
+ : [];
659
+ const plan = resolveAnalyzerPlan(params.root, analyzers, includedFiles);
660
+ const unresolved = plan.filter(needsInstallDecision);
661
+ if (unresolved.length > 0) {
662
+ const decisionsPath = join(params.artifactsDir, "incoming", "analyzer-decisions.json");
663
+ let decisions;
664
+ try {
665
+ decisions = await readJsonFile(decisionsPath);
666
+ }
667
+ catch (error) {
668
+ if (!isFileMissingError(error))
669
+ throw error;
670
+ }
671
+ if (decisions && typeof decisions === "object") {
672
+ const settings = {};
673
+ for (const [id, value] of Object.entries(decisions)) {
674
+ if (value === "ephemeral" ||
675
+ value === "permanent" ||
676
+ value === "skip" ||
677
+ value === "repo" ||
678
+ value === "auto") {
679
+ settings[id] = value;
680
+ }
681
+ }
682
+ if (Object.keys(settings).length > 0) {
683
+ const merged = await persistAnalyzerSettings(params.artifactsDir, settings);
684
+ analyzers = merged.analyzers;
685
+ }
686
+ await unlink(decisionsPath).catch(() => { });
687
+ continue;
688
+ }
689
+ return {
690
+ kind: "analyzer_install",
691
+ state,
692
+ bundle,
693
+ unresolved,
694
+ };
695
+ }
696
+ // Phase 4B — optional edge-reasoning producing turn. Once analyzer installs
697
+ // are resolved, if the flag is on and the floor carries low-confidence
698
+ // (< 0.65) edges, emit one bounded host turn (subagent dispatch or a single
699
+ // host step) to produce reason rewrites, then re-run. The enrichment
700
+ // executor applies the host-supplied rewrites in the SAME advanceAudit call
701
+ // that merges analyzer edges and writes analyzer_capability, so graph_bundle
702
+ // and its marker stay revision-consistent (no staleness loop). Flag off or
703
+ // no candidates → fall through and run the executor with no rewrites.
704
+ if (params.graphLlmEdgeReasoning === true && bundle.graph_bundle) {
705
+ const candidates = collectLowConfidenceEdges(bundle.graph_bundle);
706
+ if (candidates.length > 0) {
707
+ const edgeReasoningResultsPath = join(params.artifactsDir, "incoming", "edge-reasoning.json");
708
+ let edgeReasoningResults;
709
+ try {
710
+ edgeReasoningResults = await readJsonFile(edgeReasoningResultsPath);
711
+ }
712
+ catch (error) {
713
+ if (!isFileMissingError(error))
714
+ throw error;
715
+ }
716
+ if (edgeReasoningResults) {
717
+ await runAuditStep({
718
+ root: params.root,
719
+ artifactsDir: params.artifactsDir,
720
+ analyzers,
721
+ graphLlmEdgeReasoning: true,
722
+ edgeReasoningResultsPath,
723
+ since: params.since,
724
+ opentoken: params.opentoken,
725
+ });
726
+ await unlink(edgeReasoningResultsPath).catch(() => { });
727
+ continue;
728
+ }
729
+ return { kind: "edge_reasoning", state, bundle, candidates };
730
+ }
731
+ }
732
+ // No undecided installs (and no pending edge reasoning): fall through to run
733
+ // the executor below (it installs for ephemeral/permanent, uses repo/cache,
734
+ // skips the rest).
735
+ }
624
736
  if (decision.selected_executor === "design_review") {
625
737
  const findingsPath = join(params.artifactsDir, "incoming", "design-review-findings.json");
626
738
  let reviewFindings;
@@ -647,6 +759,36 @@ async function runDeterministicForNextStep(params) {
647
759
  bundle,
648
760
  };
649
761
  }
762
+ if (decision.selected_executor === "synthesis_narrative_executor") {
763
+ const narrativePath = join(params.artifactsDir, "incoming", "synthesis-narrative.json");
764
+ let narrativeResults;
765
+ try {
766
+ narrativeResults = await readJsonFile(narrativePath);
767
+ }
768
+ catch (error) {
769
+ if (!isFileMissingError(error))
770
+ throw error;
771
+ }
772
+ if (narrativeResults) {
773
+ await runAuditStep({
774
+ root: params.root,
775
+ artifactsDir: params.artifactsDir,
776
+ preferredExecutor: "synthesis_narrative_executor",
777
+ narrativeResultsPath: narrativePath,
778
+ opentoken: params.opentoken,
779
+ });
780
+ await unlink(narrativePath).catch(() => { });
781
+ continue;
782
+ }
783
+ if (params.narrativeEnabled) {
784
+ return {
785
+ kind: "synthesis_narrative",
786
+ state,
787
+ bundle,
788
+ };
789
+ }
790
+ // Narrative disabled: fall through so the deterministic omit runs below.
791
+ }
650
792
  if (decision.selected_executor === "agent") {
651
793
  return {
652
794
  kind: "semantic_review",
@@ -682,6 +824,9 @@ async function runDeterministicForNextStep(params) {
682
824
  result = await runAuditStep({
683
825
  root: params.root,
684
826
  artifactsDir: params.artifactsDir,
827
+ analyzers,
828
+ graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
829
+ since: params.since,
685
830
  opentoken: params.opentoken,
686
831
  });
687
832
  }
@@ -734,6 +879,89 @@ async function runDeterministicForNextStep(params) {
734
879
  reason: `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`,
735
880
  };
736
881
  }
882
+ // Renders the actionable semantic-review step (packet dispatch or single-task
883
+ // fallback) and writes steps/current-step.json. Shared by next-step and
884
+ // run-to-completion so the backend produces the actionable step itself rather
885
+ // than handing the host a second command. Host dispatch capability is resolved
886
+ // by the caller (flag -> session config -> env -> default true) and is never
887
+ // required from the host to make progress.
888
+ async function renderSemanticReviewStep(params) {
889
+ const { root, artifactsDir, activeReviewRun } = params;
890
+ if (!params.hostCanDispatch) {
891
+ const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
892
+ const workerCommand = renderCommand(activeReviewRun.worker_command);
893
+ return writeCurrentStep({
894
+ artifactsDir,
895
+ stepKind: "single_task_fallback",
896
+ status: "ready",
897
+ runId: activeReviewRun.run_id,
898
+ allowedCommands: [workerCommand],
899
+ stopCondition: "Run the exact worker_command after one result, then stop without looping.",
900
+ repoRoot: root,
901
+ artifactPaths: {
902
+ active_review_task: activeReviewRun.task_path,
903
+ active_review_prompt: activeReviewRun.prompt_path,
904
+ pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
905
+ audit_results: activeReviewRun.audit_results_path,
906
+ single_task_prompt: singleTaskPromptPath,
907
+ },
908
+ prompt: renderSingleTaskFallbackStepPrompt({
909
+ singleTaskPromptPath,
910
+ activeReviewRun,
911
+ }),
912
+ access: {
913
+ read_paths: [singleTaskPromptPath],
914
+ write_paths: [activeReviewRun.audit_results_path],
915
+ },
916
+ });
917
+ }
918
+ const dispatch = await prepareDispatchArtifacts({
919
+ packageRoot,
920
+ runId: activeReviewRun.run_id,
921
+ artifactsDir,
922
+ root,
923
+ hostActiveSubagentLimit: params.hostMaxActiveSubagents,
924
+ });
925
+ const mergeCommand = mergeAndIngestCommand(artifactsDir, activeReviewRun.run_id);
926
+ const continueCommand = nextStepCommand(root, artifactsDir);
927
+ return writeCurrentStep({
928
+ artifactsDir,
929
+ stepKind: "dispatch_review",
930
+ status: "ready",
931
+ runId: activeReviewRun.run_id,
932
+ allowedCommands: [
933
+ "auditor_merge_and_ingest",
934
+ "auditor_continue_audit",
935
+ mergeCommand,
936
+ continueCommand,
937
+ ],
938
+ stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
939
+ repoRoot: root,
940
+ artifactPaths: {
941
+ dispatch_plan: dispatch.dispatch_plan_path,
942
+ dispatch_quota: dispatch.dispatch_quota_path,
943
+ dispatch_warnings: dispatch.dispatch_warnings_path,
944
+ active_review_task: activeReviewRun.task_path,
945
+ pending_audit_tasks: activeReviewRun.pending_audit_tasks_path ?? null,
946
+ },
947
+ prompt: renderDispatchReviewPrompt({
948
+ root,
949
+ artifactsDir,
950
+ activeReviewRun,
951
+ dispatchPlanPath: dispatch.dispatch_plan_path,
952
+ dispatchQuotaPath: dispatch.dispatch_quota_path,
953
+ hostCanRestrictSubagentTools: params.hostCanRestrictSubagentTools,
954
+ hostCanSelectSubagentModel: params.hostCanSelectSubagentModel,
955
+ }),
956
+ access: {
957
+ read_paths: [
958
+ dispatch.dispatch_plan_path,
959
+ ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
960
+ ],
961
+ write_paths: [],
962
+ },
963
+ });
964
+ }
737
965
  async function cmdNextStep(argv) {
738
966
  const root = getRootDir(argv);
739
967
  warnIfNotGitRepo(root);
@@ -783,6 +1011,10 @@ async function cmdNextStep(argv) {
783
1011
  timeoutMs: getTimeoutMs(argv, sessionConfig),
784
1012
  maxRuns: getMaxRuns(argv),
785
1013
  opentoken: sessionConfig.opentoken?.enabled,
1014
+ narrativeEnabled: sessionConfig.synthesis?.narrative !== false,
1015
+ analyzers: sessionConfig.analyzers,
1016
+ graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
1017
+ since: getFlag(argv, "--since"),
786
1018
  });
787
1019
  if (result.kind === "complete") {
788
1020
  const step = await writeCurrentStep({
@@ -850,81 +1082,138 @@ async function cmdNextStep(argv) {
850
1082
  console.log(JSON.stringify(step, null, 2));
851
1083
  return;
852
1084
  }
853
- if (!hostCanDispatch) {
854
- const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
855
- const workerCommand = renderCommand(result.activeReviewRun.worker_command);
1085
+ if (result.kind === "analyzer_install") {
1086
+ const decisionsPath = join(artifactsDir, "incoming", "analyzer-decisions.json");
1087
+ await mkdir(join(artifactsDir, "incoming"), { recursive: true });
1088
+ const continueCommand = nextStepCommand(root, artifactsDir);
856
1089
  const step = await writeCurrentStep({
857
1090
  artifactsDir,
858
- stepKind: "single_task_fallback",
1091
+ stepKind: "analyzer_install",
859
1092
  status: "ready",
860
- runId: result.activeReviewRun.run_id,
861
- allowedCommands: [workerCommand],
862
- stopCondition: "Run the exact worker_command after one result, then stop without looping.",
1093
+ runId: null,
1094
+ allowedCommands: [continueCommand],
1095
+ stopCondition: "Write analyzer install decisions to the results path, then run next-step.",
863
1096
  repoRoot: root,
864
1097
  artifactPaths: {
865
- active_review_task: result.activeReviewRun.task_path,
866
- active_review_prompt: result.activeReviewRun.prompt_path,
867
- pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
868
- audit_results: result.activeReviewRun.audit_results_path,
869
- single_task_prompt: singleTaskPromptPath,
1098
+ analyzer_decisions: decisionsPath,
870
1099
  },
871
- prompt: renderSingleTaskFallbackStepPrompt({
872
- singleTaskPromptPath,
873
- activeReviewRun: result.activeReviewRun,
1100
+ prompt: renderAnalyzerInstallPrompt({
1101
+ unresolved: result.unresolved,
1102
+ decisionsPath,
1103
+ continueCommand,
1104
+ }),
1105
+ });
1106
+ console.log(JSON.stringify(step, null, 2));
1107
+ return;
1108
+ }
1109
+ if (result.kind === "edge_reasoning") {
1110
+ await mkdir(join(artifactsDir, "incoming"), { recursive: true });
1111
+ const edgeReasoningResultsPath = join(artifactsDir, "incoming", "edge-reasoning.json");
1112
+ const continueCommand = nextStepCommand(root, artifactsDir);
1113
+ const basePrompt = buildEdgeReasoningPrompt(result.candidates);
1114
+ const contentHash = edgeReasoningContentHash(result.candidates);
1115
+ if (hostCanDispatch) {
1116
+ // Dispatch path: isolate the (potentially large) edge-list prompt in a file
1117
+ // and have the host fan it out to one subagent, mirroring the packet review
1118
+ // dispatch contract. The subagent writes the rewrites file; next-step applies.
1119
+ const edgeReasoningPromptPath = join(artifactsDir, "incoming", "edge-reasoning-prompt.md");
1120
+ await writeFile(edgeReasoningPromptPath, basePrompt, "utf8");
1121
+ const step = await writeCurrentStep({
1122
+ artifactsDir,
1123
+ stepKind: "edge_reasoning_dispatch",
1124
+ status: "ready",
1125
+ runId: null,
1126
+ allowedCommands: [continueCommand],
1127
+ stopCondition: "Dispatch one subagent to write the edge-reasoning rewrites, then run next-step.",
1128
+ repoRoot: root,
1129
+ artifactPaths: {
1130
+ edge_reasoning_prompt: edgeReasoningPromptPath,
1131
+ edge_reasoning_results: edgeReasoningResultsPath,
1132
+ },
1133
+ prompt: renderEdgeReasoningDispatchPrompt({
1134
+ promptPath: edgeReasoningPromptPath,
1135
+ resultsPath: edgeReasoningResultsPath,
1136
+ continueCommand,
1137
+ contentHash,
1138
+ candidateCount: result.candidates.length,
1139
+ }),
1140
+ access: {
1141
+ read_paths: [edgeReasoningPromptPath],
1142
+ write_paths: [edgeReasoningResultsPath],
1143
+ },
1144
+ });
1145
+ console.log(JSON.stringify(step, null, 2));
1146
+ return;
1147
+ }
1148
+ // One-step fallback (no callable subagent facility): the host produces the
1149
+ // rewrites itself in a single bounded turn, mirroring the narrative step.
1150
+ const step = await writeCurrentStep({
1151
+ artifactsDir,
1152
+ stepKind: "edge_reasoning",
1153
+ status: "ready",
1154
+ runId: null,
1155
+ allowedCommands: [continueCommand],
1156
+ stopCondition: "Write the edge-reasoning rewrites to the results path, then run next-step.",
1157
+ repoRoot: root,
1158
+ artifactPaths: {
1159
+ edge_reasoning_results: edgeReasoningResultsPath,
1160
+ },
1161
+ prompt: renderEdgeReasoningStepPrompt({
1162
+ basePrompt,
1163
+ resultsPath: edgeReasoningResultsPath,
1164
+ continueCommand,
1165
+ contentHash,
874
1166
  }),
875
1167
  access: {
876
- read_paths: [singleTaskPromptPath],
877
- write_paths: [result.activeReviewRun.audit_results_path],
1168
+ read_paths: [],
1169
+ write_paths: [edgeReasoningResultsPath],
878
1170
  },
879
1171
  });
880
1172
  console.log(JSON.stringify(step, null, 2));
881
1173
  return;
882
1174
  }
883
- const dispatch = await prepareDispatchArtifacts({
884
- packageRoot,
885
- runId: result.activeReviewRun.run_id,
886
- artifactsDir,
1175
+ if (result.kind === "synthesis_narrative") {
1176
+ const narrativeResultsPath = join(artifactsDir, "incoming", "synthesis-narrative.json");
1177
+ await mkdir(join(artifactsDir, "incoming"), { recursive: true });
1178
+ const continueCommand = nextStepCommand(root, artifactsDir);
1179
+ const basePrompt = result.bundle.audit_findings
1180
+ ? renderSynthesisNarrativePrompt(result.bundle.audit_findings)
1181
+ : "# Synthesis narrative\n\nNo findings report is available; write an empty themes array.";
1182
+ const fullPrompt = [
1183
+ basePrompt,
1184
+ "## Results path",
1185
+ "",
1186
+ "Write the SynthesisNarrative JSON object to:",
1187
+ "",
1188
+ ` ${narrativeResultsPath}`,
1189
+ "",
1190
+ `Then run: ${continueCommand}`,
1191
+ "",
1192
+ ].join("\n");
1193
+ const step = await writeCurrentStep({
1194
+ artifactsDir,
1195
+ stepKind: "synthesis_narrative",
1196
+ status: "ready",
1197
+ runId: null,
1198
+ allowedCommands: [continueCommand],
1199
+ stopCondition: "Write the synthesis narrative to the results path, then run next-step.",
1200
+ repoRoot: root,
1201
+ artifactPaths: {
1202
+ synthesis_narrative_results: narrativeResultsPath,
1203
+ },
1204
+ prompt: fullPrompt,
1205
+ });
1206
+ console.log(JSON.stringify(step, null, 2));
1207
+ return;
1208
+ }
1209
+ const step = await renderSemanticReviewStep({
887
1210
  root,
888
- hostActiveSubagentLimit: hostMaxActiveSubagents,
889
- });
890
- const mergeCommand = mergeAndIngestCommand(artifactsDir, result.activeReviewRun.run_id);
891
- const continueCommand = nextStepCommand(root, artifactsDir);
892
- const step = await writeCurrentStep({
893
1211
  artifactsDir,
894
- stepKind: "dispatch_review",
895
- status: "ready",
896
- runId: result.activeReviewRun.run_id,
897
- allowedCommands: [
898
- "auditor_merge_and_ingest",
899
- "auditor_continue_audit",
900
- mergeCommand,
901
- continueCommand,
902
- ],
903
- stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
904
- repoRoot: root,
905
- artifactPaths: {
906
- dispatch_plan: dispatch.dispatch_plan_path,
907
- dispatch_quota: dispatch.dispatch_quota_path,
908
- dispatch_warnings: dispatch.dispatch_warnings_path,
909
- active_review_task: result.activeReviewRun.task_path,
910
- pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
911
- },
912
- prompt: renderDispatchReviewPrompt({
913
- root,
914
- artifactsDir,
915
- activeReviewRun: result.activeReviewRun,
916
- dispatchPlanPath: dispatch.dispatch_plan_path,
917
- dispatchQuotaPath: dispatch.dispatch_quota_path,
918
- hostCanRestrictSubagentTools,
919
- hostCanSelectSubagentModel,
920
- }),
921
- access: {
922
- read_paths: [
923
- dispatch.dispatch_plan_path,
924
- ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
925
- ],
926
- write_paths: [],
927
- },
1212
+ activeReviewRun: result.activeReviewRun,
1213
+ hostCanDispatch,
1214
+ hostMaxActiveSubagents,
1215
+ hostCanRestrictSubagentTools,
1216
+ hostCanSelectSubagentModel,
928
1217
  });
929
1218
  console.log(JSON.stringify(step, null, 2));
930
1219
  }
@@ -1092,6 +1381,37 @@ async function cmdRunToCompletion(argv) {
1092
1381
  const blockPrompt = renderWorkerPrompt(blockTask);
1093
1382
  await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
1094
1383
  await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
1384
+ const reviewRun = {
1385
+ run_id: blockRunId,
1386
+ task_path: blockPaths.taskPath,
1387
+ prompt_path: blockPaths.promptPath,
1388
+ pending_audit_tasks_path: blockPendingTasksPath,
1389
+ audit_results_path: blockAuditResultsPath,
1390
+ worker_command: blockTask.worker_command,
1391
+ };
1392
+ // Render the actionable dispatch / single-task step here instead of
1393
+ // leaving the host to issue next-step as a second command. Capability is
1394
+ // resolved from flags/config/env with a sane default, so nothing is
1395
+ // required from the host to make progress. If rendering fails we still
1396
+ // emit the hand-off below — run-to-completion is never worse than before,
1397
+ // and next-step will re-render and surface the error loudly.
1398
+ try {
1399
+ await renderSemanticReviewStep({
1400
+ root,
1401
+ artifactsDir,
1402
+ activeReviewRun: reviewRun,
1403
+ hostCanDispatch: resolveHostDispatchCapability({
1404
+ explicit: getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents"),
1405
+ sessionConfig,
1406
+ }),
1407
+ hostMaxActiveSubagents: getHostMaxActiveSubagents(argv),
1408
+ hostCanRestrictSubagentTools: getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ?? false,
1409
+ hostCanSelectSubagentModel: getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false,
1410
+ });
1411
+ }
1412
+ catch (stepError) {
1413
+ process.stderr.write(`[audit-code] Could not pre-render the review step; the operator hand-off points to next-step instead. ${stepError instanceof Error ? stepError.message : String(stepError)}\n`);
1414
+ }
1095
1415
  await emitEnvelope({
1096
1416
  root,
1097
1417
  artifactsDir,
@@ -1107,14 +1427,7 @@ async function cmdRunToCompletion(argv) {
1107
1427
  progress_summary: blocker,
1108
1428
  next_likely_step: null,
1109
1429
  providerName: provider.name,
1110
- activeReviewRun: {
1111
- run_id: blockRunId,
1112
- task_path: blockPaths.taskPath,
1113
- prompt_path: blockPaths.promptPath,
1114
- pending_audit_tasks_path: blockPendingTasksPath,
1115
- audit_results_path: blockAuditResultsPath,
1116
- worker_command: blockTask.worker_command,
1117
- },
1430
+ activeReviewRun: reviewRun,
1118
1431
  });
1119
1432
  return;
1120
1433
  }
@@ -1151,7 +1464,8 @@ async function cmdRunToCompletion(argv) {
1151
1464
  const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
1152
1465
  const allCandidateTasks = buildPendingAuditTasks(bundle);
1153
1466
  const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
1154
- const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g));
1467
+ const candidateSizeIndex = sizeIndexFromManifest(bundle.repo_manifest);
1468
+ const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g, candidateSizeIndex));
1155
1469
  const providerLimits = await provider.queryLimits?.(hostModel)
1156
1470
  .then((r) => r ? { ...r, source: "provider_query" } : null)
1157
1471
  .catch(() => null)
@@ -1457,6 +1771,8 @@ async function cmdRunToCompletion(argv) {
1457
1771
  auditResultsPath,
1458
1772
  runtimeUpdatesPath,
1459
1773
  externalAnalyzerPath,
1774
+ analyzers: sessionConfig.analyzers,
1775
+ since: getFlag(argv, "--since"),
1460
1776
  });
1461
1777
  workerResult = {
1462
1778
  contract_version: WORKER_RESULT_CONTRACT_VERSION,
@@ -2290,7 +2606,11 @@ async function cmdIntake(argv) {
2290
2606
  }
2291
2607
  async function cmdPlan(argv) {
2292
2608
  const artifactsDir = getArtifactsDir(argv);
2293
- const result = await runAuditStep({ root: getRootDir(argv), artifactsDir });
2609
+ const result = await runAuditStep({
2610
+ root: getRootDir(argv),
2611
+ artifactsDir,
2612
+ since: getFlag(argv, "--since"),
2613
+ });
2294
2614
  console.log(JSON.stringify({
2295
2615
  artifacts_dir: artifactsDir,
2296
2616
  selected_executor: result.selected_executor,
@@ -0,0 +1,2 @@
1
+ import type { LanguageAnalyzer } from "./types.js";
2
+ export declare const cssAnalyzer: LanguageAnalyzer;