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
@@ -1,5 +1,5 @@
1
1
  import { getArtifactValue } from "../io/artifacts.js";
2
- import { ARTIFACT_DEPENDENCY_MAP } from "./dependencyMap.js";
2
+ import { ARTIFACT_DEPENDENTS_MAP } from "./dependencyMap.js";
3
3
  import { present } from "./artifactMetadata.js";
4
4
  import { buildReverseDependencyMap, hashArtifactValue, stableStringify, } from "./artifactFreshness.js";
5
5
  function computeContentHash(artifactName, bundle) {
@@ -54,7 +54,7 @@ export function computeStaleArtifacts(bundle) {
54
54
  stale.add(artifactName);
55
55
  }
56
56
  }
57
- for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
57
+ for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENTS_MAP)) {
58
58
  if (upstream === "tooling_manifest.json" && !present(bundle, upstream)) {
59
59
  continue;
60
60
  }
@@ -70,7 +70,7 @@ export function computeStaleArtifacts(bundle) {
70
70
  let changed = true;
71
71
  while (changed) {
72
72
  changed = false;
73
- for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
73
+ for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENTS_MAP)) {
74
74
  if (!stale.has(upstream)) {
75
75
  continue;
76
76
  }
@@ -0,0 +1,5 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ import type { ExecutorRunResult } from "./executorResult.js";
3
+ export declare function runStructureExecutor(bundle: ArtifactBundle, root?: string): Promise<ExecutorRunResult>;
4
+ export declare function runDesignAssessmentExecutor(bundle: ArtifactBundle): ExecutorRunResult;
5
+ export declare function runDesignReviewAutoComplete(bundle: ArtifactBundle): ExecutorRunResult;
@@ -0,0 +1,94 @@
1
+ import { buildFileDisposition } from "../extractors/disposition.js";
2
+ import { buildGraphBundle, buildGraphBundleFromFs, } from "../extractors/graph.js";
3
+ import { buildCriticalFlowManifest } from "../extractors/flows.js";
4
+ import { buildRiskRegister } from "../extractors/risk.js";
5
+ import { buildSurfaceManifest } from "../extractors/surfaces.js";
6
+ import { buildUnitManifest } from "./unitBuilder.js";
7
+ import { buildDesignAssessment } from "../extractors/designAssessment.js";
8
+ export async function runStructureExecutor(bundle, root) {
9
+ if (!bundle.repo_manifest) {
10
+ throw new Error("Cannot run structure executor without repo_manifest");
11
+ }
12
+ const externalAnalyzerResults = bundle.external_analyzer_results;
13
+ const disposition = bundle.file_disposition ?? buildFileDisposition(bundle.repo_manifest);
14
+ const unitManifest = buildUnitManifest(bundle.repo_manifest, disposition);
15
+ const graphBundle = root
16
+ ? await buildGraphBundleFromFs(bundle.repo_manifest, root, disposition, {
17
+ externalAnalyzerResults,
18
+ })
19
+ : buildGraphBundle(bundle.repo_manifest, disposition, {
20
+ externalAnalyzerResults,
21
+ });
22
+ const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition, { graphBundle });
23
+ const criticalFlows = buildCriticalFlowManifest(bundle.repo_manifest, surfaceManifest, disposition);
24
+ const riskRegister = buildRiskRegister(unitManifest, criticalFlows, externalAnalyzerResults);
25
+ return {
26
+ updated: {
27
+ ...bundle,
28
+ file_disposition: disposition,
29
+ unit_manifest: unitManifest,
30
+ surface_manifest: surfaceManifest,
31
+ graph_bundle: graphBundle,
32
+ critical_flows: criticalFlows,
33
+ risk_register: riskRegister,
34
+ },
35
+ artifacts_written: [
36
+ "file_disposition.json",
37
+ "unit_manifest.json",
38
+ "surface_manifest.json",
39
+ "graph_bundle.json",
40
+ "critical_flows.json",
41
+ "risk_register.json",
42
+ ],
43
+ progress_summary: `Built structure artifacts for ${unitManifest.units.length} units and ${criticalFlows.flows.length} critical flows.` +
44
+ (criticalFlows.fallback_required
45
+ ? " Deterministic flow inference did not fully meet the confidence bar."
46
+ : ""),
47
+ };
48
+ }
49
+ export function runDesignAssessmentExecutor(bundle) {
50
+ if (!bundle.unit_manifest ||
51
+ !bundle.graph_bundle ||
52
+ !bundle.critical_flows ||
53
+ !bundle.risk_register) {
54
+ throw new Error("Cannot run design assessment executor without structure artifacts");
55
+ }
56
+ const designAssessment = buildDesignAssessment({
57
+ unitManifest: bundle.unit_manifest,
58
+ graphBundle: bundle.graph_bundle,
59
+ criticalFlows: bundle.critical_flows,
60
+ riskRegister: bundle.risk_register,
61
+ });
62
+ const previous = bundle.design_assessment;
63
+ if (previous?.reviewed) {
64
+ designAssessment.reviewed = true;
65
+ designAssessment.review_findings = previous.review_findings ?? [];
66
+ }
67
+ return {
68
+ updated: {
69
+ ...bundle,
70
+ design_assessment: designAssessment,
71
+ },
72
+ artifacts_written: ["design_assessment.json"],
73
+ progress_summary: `Design assessment complete: ${designAssessment.findings.length} structural finding(s).`,
74
+ };
75
+ }
76
+ export function runDesignReviewAutoComplete(bundle) {
77
+ const existing = bundle.design_assessment;
78
+ if (!existing) {
79
+ throw new Error("Cannot auto-complete design review without design_assessment artifact");
80
+ }
81
+ const updated = {
82
+ ...existing,
83
+ reviewed: true,
84
+ review_findings: existing.review_findings ?? [],
85
+ };
86
+ return {
87
+ updated: {
88
+ ...bundle,
89
+ design_assessment: updated,
90
+ },
91
+ artifacts_written: ["design_assessment.json"],
92
+ progress_summary: "Design review auto-completed (host-agent review available via next-step).",
93
+ };
94
+ }
@@ -1,3 +1,3 @@
1
1
  import type { ArtifactBundle } from "../io/artifacts.js";
2
- import type { ExecutorRunResult } from "./internalExecutors.js";
2
+ import type { ExecutorRunResult } from "./executorResult.js";
3
3
  export declare function runSyntaxResolutionExecutor(bundle: ArtifactBundle, root: string): ExecutorRunResult;
@@ -0,0 +1,12 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ import type { AuditResult } from "../types.js";
3
+ import type { ExecutorRunResult } from "./executorResult.js";
4
+ import type { SynthesisNarrative } from "@audit-tools/shared";
5
+ export declare function runSynthesisExecutor(bundle: ArtifactBundle, results?: AuditResult[]): ExecutorRunResult;
6
+ /**
7
+ * Resolve the optional synthesis-narrative obligation. When a host/provider
8
+ * narrative is supplied it is merged into the canonical findings report and the
9
+ * human report is re-rendered with themes/executive-summary/top-risks; without
10
+ * one the narrative is recorded as omitted and the deterministic report stands.
11
+ */
12
+ export declare function runSynthesisNarrativeExecutor(bundle: ArtifactBundle, narrative?: SynthesisNarrative): ExecutorRunResult;
@@ -0,0 +1,90 @@
1
+ import { applyNarrative, buildAuditFindingsReport, buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
2
+ function buildBaseFindingsReport(bundle, results) {
3
+ return buildAuditFindingsReport(buildAuditReportModel({
4
+ results,
5
+ unitManifest: bundle.unit_manifest,
6
+ graphBundle: bundle.graph_bundle,
7
+ criticalFlows: bundle.critical_flows,
8
+ coverageMatrix: bundle.coverage_matrix,
9
+ runtimeValidationReport: bundle.runtime_validation_report,
10
+ externalAnalyzerResults: bundle.external_analyzer_results,
11
+ designAssessment: bundle.design_assessment,
12
+ }));
13
+ }
14
+ export function runSynthesisExecutor(bundle, results) {
15
+ const finalResults = results ?? bundle.audit_results ?? [];
16
+ // Emit the canonical machine contract and render the human report from it.
17
+ // No narrative yet — that is layered by the synthesis-narrative obligation.
18
+ const findings = buildBaseFindingsReport(bundle, finalResults);
19
+ // Synthesis renders findings; it does NOT own audit_results. Writing
20
+ // audit_results back here desyncs it from its metadata entry (it isn't in
21
+ // artifacts_written, so computeArtifactMetadata reuses the prior hash) and, in
22
+ // the zero-result case, materializes an empty audit_results.jsonl that did not
23
+ // exist before — both perpetually re-stale coverage_matrix → planning,
24
+ // forcing a planning re-run that rewrites runtime_validation_report.json (the
25
+ // finalization-oscillation engine). Leave audit_results as the ingested value.
26
+ return {
27
+ updated: {
28
+ ...bundle,
29
+ audit_findings: findings,
30
+ audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope }),
31
+ },
32
+ artifacts_written: ["audit-findings.json", "audit-report.md"],
33
+ progress_summary: `Rendered deterministic audit report and canonical findings for ${finalResults.length} audit result entries.`,
34
+ };
35
+ }
36
+ /**
37
+ * Resolve the optional synthesis-narrative obligation. When a host/provider
38
+ * narrative is supplied it is merged into the canonical findings report and the
39
+ * human report is re-rendered with themes/executive-summary/top-risks; without
40
+ * one the narrative is recorded as omitted and the deterministic report stands.
41
+ */
42
+ export function runSynthesisNarrativeExecutor(bundle, narrative) {
43
+ const baseReport = bundle.audit_findings ??
44
+ buildBaseFindingsReport(bundle, bundle.audit_results ?? []);
45
+ const needsBaseWrite = !bundle.audit_findings;
46
+ const hasNarrative = Boolean(narrative &&
47
+ ((narrative.themes?.length ?? 0) > 0 ||
48
+ (narrative.executive_summary?.trim().length ?? 0) > 0 ||
49
+ (narrative.top_risks?.length ?? 0) > 0));
50
+ if (!hasNarrative) {
51
+ const record = {
52
+ status: "omitted",
53
+ theme_count: 0,
54
+ executive_summary_present: false,
55
+ top_risk_count: 0,
56
+ };
57
+ return {
58
+ updated: {
59
+ ...bundle,
60
+ audit_findings: baseReport,
61
+ synthesis_narrative: record,
62
+ },
63
+ artifacts_written: needsBaseWrite
64
+ ? ["audit-findings.json", "synthesis-narrative.json"]
65
+ : ["synthesis-narrative.json"],
66
+ progress_summary: "Synthesis narrative omitted; deterministic findings report retained.",
67
+ };
68
+ }
69
+ const enriched = applyNarrative(baseReport, narrative);
70
+ const record = {
71
+ status: "applied",
72
+ theme_count: enriched.themes?.length ?? 0,
73
+ executive_summary_present: (enriched.executive_summary?.trim().length ?? 0) > 0,
74
+ top_risk_count: enriched.top_risks?.length ?? 0,
75
+ };
76
+ return {
77
+ updated: {
78
+ ...bundle,
79
+ audit_findings: enriched,
80
+ audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope }),
81
+ synthesis_narrative: record,
82
+ },
83
+ artifacts_written: [
84
+ "audit-findings.json",
85
+ "audit-report.md",
86
+ "synthesis-narrative.json",
87
+ ],
88
+ progress_summary: `Synthesis narrative applied: ${record.theme_count} theme(s), ${record.top_risk_count} top risk(s).`,
89
+ };
90
+ }
@@ -7,8 +7,8 @@ export interface UnitLineIndex {
7
7
  export interface BuildChunkedTaskOptions {
8
8
  /**
9
9
  * Line count above which a single file gets its own task rather than being
10
- * grouped with the rest of its unit. Default: 3000. Set to 0 to disable
11
- * splitting entirely.
10
+ * grouped with the rest of its unit. Default: `DEFAULT_FILE_SPLIT_THRESHOLD`
11
+ * (5000). Set to 0 to disable splitting entirely.
12
12
  */
13
13
  file_split_threshold?: number;
14
14
  /**
@@ -40,6 +40,97 @@ const DEFAULT_MAX_TASK_LINES = 3000;
40
40
  const DEFAULT_MAX_TASK_FILES = 15;
41
41
  const DEFAULT_TINY_TEST_FILE_LINES = 250;
42
42
  const TINY_TEST_UNIT_ID = "tests-tiny-files";
43
+ // Split a flat list of file paths into review-task-sized chunks, bounded by both
44
+ // an aggregate line budget and a max file count. Hoisted out of
45
+ // `buildChunkedAuditTasks` so it no longer closes over the loop's locals; the
46
+ // line index and limits are passed in explicitly.
47
+ function chunkByTaskBudget(filePaths, unitLineIndex, limits) {
48
+ const { maxTaskLines, maxTaskFiles } = limits;
49
+ if (filePaths.length === 0) {
50
+ return [];
51
+ }
52
+ if (maxTaskLines <= 0 && maxTaskFiles <= 0) {
53
+ return [filePaths];
54
+ }
55
+ const chunks = [];
56
+ let current = [];
57
+ let currentLines = 0;
58
+ for (const path of filePaths) {
59
+ const lineCount = unitLineIndex[path] ?? 0;
60
+ const wouldExceedFiles = maxTaskFiles > 0 && current.length >= maxTaskFiles;
61
+ const wouldExceedLines = maxTaskLines > 0 &&
62
+ current.length > 0 &&
63
+ currentLines + lineCount > maxTaskLines;
64
+ if (wouldExceedFiles || wouldExceedLines) {
65
+ chunks.push(current);
66
+ current = [];
67
+ currentLines = 0;
68
+ }
69
+ current.push(path);
70
+ currentLines += lineCount;
71
+ }
72
+ if (current.length > 0) {
73
+ chunks.push(current);
74
+ }
75
+ return chunks;
76
+ }
77
+ // Emit one or more audit tasks for a scope/lens. Normal-sized files are grouped
78
+ // into budget-bounded chunks; files over `fileSplitThreshold` get their own
79
+ // isolated task. Hoisted to module scope: the per-call mutable accumulators
80
+ // (`tasks`, `seen`) and budget config are now explicit parameters instead of
81
+ // captured closure state.
82
+ function addTaskBlock(params, context) {
83
+ const { tasks, seen, unitLineIndex, fileSplitThreshold, budgetLimits } = context;
84
+ const oversizedFiles = fileSplitThreshold > 0
85
+ ? params.filePaths.filter((path) => (unitLineIndex[path] ?? 0) > fileSplitThreshold)
86
+ : [];
87
+ const oversizedSet = new Set(oversizedFiles);
88
+ const normalFiles = params.filePaths.filter((path) => !oversizedSet.has(path));
89
+ const normalChunks = chunkByTaskBudget(normalFiles, unitLineIndex, budgetLimits);
90
+ for (let index = 0; index < normalChunks.length; index++) {
91
+ const chunk = normalChunks[index];
92
+ const splitKind = normalChunks.length > 1 ? "budget" : "none";
93
+ const taskId = splitKind === "budget"
94
+ ? `${params.scopeId}:${params.lens}:part-${index + 1}`
95
+ : `${params.scopeId}:${params.lens}`;
96
+ if (!seen.has(taskId)) {
97
+ seen.add(taskId);
98
+ tasks.push({
99
+ task_id: taskId,
100
+ unit_id: params.unitId,
101
+ pass_id: params.passId,
102
+ lens: params.lens,
103
+ file_paths: chunk,
104
+ rationale: params.rationale(chunk, splitKind),
105
+ priority: params.priority,
106
+ tags: splitKind === "budget"
107
+ ? [...new Set([...params.tags, "line_budget_split"])]
108
+ : params.tags.length > 0
109
+ ? params.tags
110
+ : undefined,
111
+ });
112
+ }
113
+ }
114
+ for (const filePath of oversizedFiles) {
115
+ const taskId = `${params.scopeId}:${params.lens}:${filePath}`;
116
+ if (seen.has(taskId)) {
117
+ continue;
118
+ }
119
+ seen.add(taskId);
120
+ tasks.push({
121
+ task_id: taskId,
122
+ unit_id: params.unitId,
123
+ pass_id: params.passId,
124
+ lens: params.lens,
125
+ file_paths: [filePath],
126
+ rationale: params.rationale([filePath], "large_file"),
127
+ priority: params.priority,
128
+ tags: params.tags.length > 0
129
+ ? [...new Set([...params.tags, "large_file"])]
130
+ : ["large_file"],
131
+ });
132
+ }
133
+ }
43
134
  function buildCoverageIndex(coverageMatrix) {
44
135
  return new Map(coverageMatrix.files.map((file) => [file.path, file]));
45
136
  }
@@ -94,86 +185,14 @@ export function buildChunkedAuditTasks(coverageMatrix, unitLineIndex, options =
94
185
  pendingByLens.set(lens, pending);
95
186
  }
96
187
  }
97
- function chunkByTaskBudget(filePaths) {
98
- if (filePaths.length === 0) {
99
- return [];
100
- }
101
- if (maxTaskLines <= 0 && maxTaskFiles <= 0) {
102
- return [filePaths];
103
- }
104
- const chunks = [];
105
- let current = [];
106
- let currentLines = 0;
107
- for (const path of filePaths) {
108
- const lineCount = unitLineIndex[path] ?? 0;
109
- const wouldExceedFiles = maxTaskFiles > 0 && current.length >= maxTaskFiles;
110
- const wouldExceedLines = maxTaskLines > 0 &&
111
- current.length > 0 &&
112
- currentLines + lineCount > maxTaskLines;
113
- if (wouldExceedFiles || wouldExceedLines) {
114
- chunks.push(current);
115
- current = [];
116
- currentLines = 0;
117
- }
118
- current.push(path);
119
- currentLines += lineCount;
120
- }
121
- if (current.length > 0) {
122
- chunks.push(current);
123
- }
124
- return chunks;
125
- }
126
- function addTaskBlock(params) {
127
- const oversizedFiles = fileSplitThreshold > 0
128
- ? params.filePaths.filter((path) => (unitLineIndex[path] ?? 0) > fileSplitThreshold)
129
- : [];
130
- const oversizedSet = new Set(oversizedFiles);
131
- const normalFiles = params.filePaths.filter((path) => !oversizedSet.has(path));
132
- const normalChunks = chunkByTaskBudget(normalFiles);
133
- for (let index = 0; index < normalChunks.length; index++) {
134
- const chunk = normalChunks[index];
135
- const splitKind = normalChunks.length > 1 ? "budget" : "none";
136
- const taskId = splitKind === "budget"
137
- ? `${params.scopeId}:${params.lens}:part-${index + 1}`
138
- : `${params.scopeId}:${params.lens}`;
139
- if (!seen.has(taskId)) {
140
- seen.add(taskId);
141
- tasks.push({
142
- task_id: taskId,
143
- unit_id: params.unitId,
144
- pass_id: params.passId,
145
- lens: params.lens,
146
- file_paths: chunk,
147
- rationale: params.rationale(chunk, splitKind),
148
- priority: params.priority,
149
- tags: splitKind === "budget"
150
- ? [...new Set([...params.tags, "line_budget_split"])]
151
- : params.tags.length > 0
152
- ? params.tags
153
- : undefined,
154
- });
155
- }
156
- }
157
- for (const filePath of oversizedFiles) {
158
- const taskId = `${params.scopeId}:${params.lens}:${filePath}`;
159
- if (seen.has(taskId)) {
160
- continue;
161
- }
162
- seen.add(taskId);
163
- tasks.push({
164
- task_id: taskId,
165
- unit_id: params.unitId,
166
- pass_id: params.passId,
167
- lens: params.lens,
168
- file_paths: [filePath],
169
- rationale: params.rationale([filePath], "large_file"),
170
- priority: params.priority,
171
- tags: params.tags.length > 0
172
- ? [...new Set([...params.tags, "large_file"])]
173
- : ["large_file"],
174
- });
175
- }
176
- }
188
+ const budgetLimits = { maxTaskLines, maxTaskFiles };
189
+ const taskBlockContext = {
190
+ tasks,
191
+ seen,
192
+ unitLineIndex,
193
+ fileSplitThreshold,
194
+ budgetLimits,
195
+ };
177
196
  const assigned = new Set();
178
197
  const flowBlocks = options.critical_flows
179
198
  ? claimFlowReviewBlocks(options.critical_flows, pendingByLens, assigned)
@@ -195,7 +214,7 @@ export function buildChunkedAuditTasks(coverageMatrix, unitLineIndex, options =
195
214
  : splitKind === "budget"
196
215
  ? `Audit part of critical flow ${block.flow_id} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
197
216
  : `Audit critical flow ${block.flow_id} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
198
- });
217
+ }, taskBlockContext);
199
218
  }
200
219
  const groupedRemainders = new Map();
201
220
  for (const lens of LENS_ORDER) {
@@ -246,7 +265,7 @@ export function buildChunkedAuditTasks(coverageMatrix, unitLineIndex, options =
246
265
  : splitKind === "budget"
247
266
  ? `Audit part of ${block.unitId} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
248
267
  : `Audit ${block.unitId} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
249
- });
268
+ }, taskBlockContext);
250
269
  }
251
270
  return tasks.sort((a, b) => {
252
271
  const priorityDelta = priorityRank(b.priority) - priorityRank(a.priority);
@@ -1,4 +1,11 @@
1
1
  import type { FreshSessionProvider, ResolvedProviderName, SessionConfig } from "@audit-tools/shared";
2
+ /**
3
+ * Auto-resolution and provider wiring are single-sourced in `@audit-tools/shared`.
4
+ * This module is a thin audit-code-specific adapter: it injects audit-code's own
5
+ * `ClaudeCodeProvider`/`OpenCodeProvider` (whose prompt delivery and
6
+ * skip-permissions semantics differ from the remediator's) and attributes the
7
+ * auto-fallback warning to `audit-code`.
8
+ */
2
9
  export declare function resolveFreshSessionProviderName(name: string | undefined, sessionConfig?: SessionConfig, options?: {
3
10
  env?: NodeJS.ProcessEnv;
4
11
  commandExists?: (command: string) => boolean;
@@ -1,101 +1,20 @@
1
- import { spawnSync } from "node:child_process";
2
- import { LocalSubprocessProvider } from "./localSubprocessProvider.js";
3
- import { SubprocessTemplateProvider } from "./subprocessTemplateProvider.js";
1
+ import { createFreshSessionProvider as createSharedFreshSessionProvider, resolveFreshSessionProviderName as resolveSharedFreshSessionProviderName, } from "@audit-tools/shared";
4
2
  import { ClaudeCodeProvider } from "./claudeCodeProvider.js";
5
3
  import { OpenCodeProvider } from "./opencodeProvider.js";
6
- import { VSCodeTaskProvider } from "./vscodeTaskProvider.js";
7
- function hasEntries(values) {
8
- return (values?.length ?? 0) > 0;
9
- }
10
- function hasConfiguredClaudeCode(sessionConfig) {
11
- return (Boolean(sessionConfig.claude_code?.command?.trim()) ||
12
- hasEntries(sessionConfig.claude_code?.extra_args) ||
13
- sessionConfig.claude_code?.dangerously_skip_permissions === true);
14
- }
15
- function hasConfiguredOpenCode(sessionConfig) {
16
- return (Boolean(sessionConfig.opencode?.command?.trim()) ||
17
- hasEntries(sessionConfig.opencode?.extra_args));
18
- }
19
- function commandExists(command) {
20
- const lookupCommand = process.platform === "win32" ? "where" : "which";
21
- const result = spawnSync(lookupCommand, [command], { stdio: "ignore" });
22
- return result.status === 0;
23
- }
4
+ /**
5
+ * Auto-resolution and provider wiring are single-sourced in `@audit-tools/shared`.
6
+ * This module is a thin audit-code-specific adapter: it injects audit-code's own
7
+ * `ClaudeCodeProvider`/`OpenCodeProvider` (whose prompt delivery and
8
+ * skip-permissions semantics differ from the remediator's) and attributes the
9
+ * auto-fallback warning to `audit-code`.
10
+ */
24
11
  export function resolveFreshSessionProviderName(name, sessionConfig = {}, options = {}) {
25
- const requestedProvider = name ?? sessionConfig.provider;
26
- const shouldAutoDetect = requestedProvider === undefined ||
27
- requestedProvider === "auto" ||
28
- (name === undefined && requestedProvider === "local-subprocess");
29
- if (!shouldAutoDetect) {
30
- return requestedProvider;
31
- }
32
- const env = options.env ?? process.env;
33
- const lookupCommand = options.commandExists ?? commandExists;
34
- const inVSCode = (env.TERM_PROGRAM ?? "").toLowerCase() === "vscode";
35
- const insideClaudeCode = Boolean(env.CLAUDECODE);
36
- const insideOpenCode = Boolean(env.OPENCODE);
37
- // If we're inside a specific IDE/conversation, use that as the provider
38
- if (insideOpenCode) {
39
- return "opencode";
40
- }
41
- if (insideClaudeCode) {
42
- return "claude-code";
43
- }
44
- if (inVSCode && hasEntries(sessionConfig.vscode_task?.command_template)) {
45
- return "vscode-task";
46
- }
47
- if (hasEntries(sessionConfig.subprocess_template?.command_template)) {
48
- return "subprocess-template";
49
- }
50
- const claudeCommand = sessionConfig.claude_code?.command ?? "claude";
51
- const opencodeCommand = sessionConfig.opencode?.command ?? "opencode";
52
- const claudeAvailable = !insideClaudeCode && lookupCommand(claudeCommand);
53
- const opencodeAvailable = lookupCommand(opencodeCommand);
54
- if (!insideClaudeCode && hasConfiguredClaudeCode(sessionConfig) && claudeAvailable) {
55
- return "claude-code";
56
- }
57
- if (hasConfiguredOpenCode(sessionConfig) && opencodeAvailable) {
58
- return "opencode";
59
- }
60
- if (claudeAvailable && !opencodeAvailable) {
61
- return "claude-code";
62
- }
63
- if (opencodeAvailable && !claudeAvailable) {
64
- return "opencode";
65
- }
66
- return "local-subprocess";
12
+ return resolveSharedFreshSessionProviderName(name, sessionConfig, options);
67
13
  }
68
14
  export function createFreshSessionProvider(name, sessionConfig = {}) {
69
- const providerName = resolveFreshSessionProviderName(name, sessionConfig);
70
- const opentoken = sessionConfig.opentoken ?? {};
71
- const requestedProvider = name ?? sessionConfig.provider;
72
- const autoDetectionRequested = requestedProvider === undefined ||
73
- requestedProvider === "auto" ||
74
- (name === undefined && requestedProvider === "local-subprocess");
75
- if (providerName === "local-subprocess" &&
76
- autoDetectionRequested) {
77
- process.stderr.write("audit-code: auto provider resolved to local-subprocess — no capable agent provider detected. " +
78
- "Agent tasks will require manual dispatch. Configure claude-code, opencode, or subprocess-template " +
79
- "in session-config.json to automate them.\n");
80
- }
81
- switch (providerName) {
82
- case "local-subprocess":
83
- return new LocalSubprocessProvider();
84
- case "subprocess-template":
85
- if (!sessionConfig.subprocess_template?.command_template?.length) {
86
- throw new Error("subprocess-template provider requires session-config.json with subprocess_template.command_template.");
87
- }
88
- return new SubprocessTemplateProvider(sessionConfig.subprocess_template, undefined, opentoken);
89
- case "claude-code":
90
- return new ClaudeCodeProvider(sessionConfig.claude_code, undefined, opentoken);
91
- case "opencode":
92
- return new OpenCodeProvider(sessionConfig.opencode, opentoken);
93
- case "vscode-task":
94
- if (!sessionConfig.vscode_task?.command_template?.length) {
95
- throw new Error("vscode-task provider requires session-config.json with vscode_task.command_template.");
96
- }
97
- return new VSCodeTaskProvider(sessionConfig.vscode_task, opentoken);
98
- default:
99
- throw new Error(`Unknown provider: ${providerName}`);
100
- }
15
+ return createSharedFreshSessionProvider(name, sessionConfig, {
16
+ orchestratorName: "audit-code",
17
+ createClaudeCodeProvider: (config, opentoken) => new ClaudeCodeProvider(config, undefined, opentoken),
18
+ createOpenCodeProvider: (config, opentoken) => new OpenCodeProvider(config, opentoken),
19
+ });
101
20
  }
@@ -7,6 +7,7 @@ export interface DiscoveredRateLimits {
7
7
  export interface DiscoveredLimitsCacheEntry {
8
8
  requests_per_minute?: number;
9
9
  input_tokens_per_minute?: number;
10
+ output_tokens_per_minute?: number;
10
11
  discovered_at: string;
11
12
  source: string;
12
13
  }
@@ -41,6 +41,9 @@ export async function updateDiscoveredLimits(providerModelKey, limits) {
41
41
  if (limits.input_tokens_per_minute != null) {
42
42
  entry.input_tokens_per_minute = limits.input_tokens_per_minute;
43
43
  }
44
+ if (limits.output_tokens_per_minute != null) {
45
+ entry.output_tokens_per_minute = limits.output_tokens_per_minute;
46
+ }
44
47
  cache.entries[providerModelKey] = entry;
45
48
  await writeDiscoveredLimitsCache(cache);
46
49
  }
@@ -49,11 +52,14 @@ export async function lookupDiscoveredLimits(providerModelKey) {
49
52
  const entry = cache.entries[providerModelKey];
50
53
  if (!entry)
51
54
  return null;
52
- if (entry.requests_per_minute == null && entry.input_tokens_per_minute == null)
55
+ if (entry.requests_per_minute == null &&
56
+ entry.input_tokens_per_minute == null &&
57
+ entry.output_tokens_per_minute == null)
53
58
  return null;
54
59
  return {
55
60
  requests_per_minute: entry.requests_per_minute ?? null,
56
61
  input_tokens_per_minute: entry.input_tokens_per_minute ?? null,
62
+ output_tokens_per_minute: entry.output_tokens_per_minute ?? null,
57
63
  source: entry.source,
58
64
  };
59
65
  }
@@ -4,8 +4,6 @@ export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, Resolve
4
4
  export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
5
5
  export type { ScheduleWaveOptions } from "@audit-tools/shared";
6
6
  export { detectHostActiveSubagentLimit, resolveHostActiveSubagentLimit, } from "./hostLimits.js";
7
- export { probeProvider } from "./probe.js";
8
- export type { ProbeResult } from "./probe.js";
9
7
  export { lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, readDiscoveredLimitsCache, writeDiscoveredLimitsCache, } from "./discoveredLimits.js";
10
8
  export type { DiscoveredRateLimits, DiscoveredLimitsCache, DiscoveredLimitsCacheEntry } from "./discoveredLimits.js";
11
9
  export { extractRateLimitHeaders } from "./headerExtraction.js";
@@ -4,9 +4,8 @@ export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writ
4
4
  // both orchestrators). Auditor passes its discovered-limits via the structural
5
5
  // DiscoveredRateLimitsInput the shared scheduler accepts.
6
6
  export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
7
- // Auditor-specific: probe, discovered limits, header extraction
7
+ // Auditor-specific: discovered limits, header extraction
8
8
  export { detectHostActiveSubagentLimit, resolveHostActiveSubagentLimit, } from "./hostLimits.js";
9
- export { probeProvider } from "./probe.js";
10
9
  export { lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, readDiscoveredLimitsCache, writeDiscoveredLimitsCache, } from "./discoveredLimits.js";
11
10
  export { extractRateLimitHeaders } from "./headerExtraction.js";
12
11
  export { GenericHeaderExtractor, ClaudeCodeHeaderExtractor, getHeaderExtractorForProvider } from "./headerExtractors/index.js";