auditor-lambda 0.8.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 (98) hide show
  1. package/audit-code-wrapper-lib.mjs +149 -129
  2. package/dist/adapters/normalizeExternal.js +6 -3
  3. package/dist/cli/args.d.ts +0 -1
  4. package/dist/cli/args.js +0 -6
  5. package/dist/cli/dispatch.js +3 -2
  6. package/dist/cli/lineIndex.js +4 -1
  7. package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
  8. package/dist/cli/mergeAndIngestCommand.js +219 -0
  9. package/dist/cli/nextStepCommand.js +5 -1
  10. package/dist/cli/runToCompletion.d.ts +9 -0
  11. package/dist/cli/runToCompletion.js +655 -480
  12. package/dist/cli/statusCommand.d.ts +1 -0
  13. package/dist/cli/statusCommand.js +113 -0
  14. package/dist/cli/submitPacketCommand.d.ts +1 -0
  15. package/dist/cli/submitPacketCommand.js +155 -0
  16. package/dist/cli/workerResult.d.ts +1 -1
  17. package/dist/cli/workerRunCommand.d.ts +1 -0
  18. package/dist/cli/workerRunCommand.js +88 -0
  19. package/dist/cli.js +14 -563
  20. package/dist/extractors/analyzers/sql.js +4 -1
  21. package/dist/extractors/analyzers/treeSitter.js +29 -15
  22. package/dist/extractors/analyzers/typescript.js +10 -8
  23. package/dist/extractors/designAssessment.js +43 -24
  24. package/dist/extractors/graph.js +139 -73
  25. package/dist/extractors/pathPatterns.js +17 -5
  26. package/dist/io/runArtifactTypes.d.ts +18 -0
  27. package/dist/io/runArtifactTypes.js +1 -0
  28. package/dist/io/runArtifacts.d.ts +2 -18
  29. package/dist/io/runArtifacts.js +14 -3
  30. package/dist/mcp/server.js +9 -0
  31. package/dist/orchestrator/advance.js +37 -22
  32. package/dist/orchestrator/artifactFreshness.js +2 -2
  33. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  34. package/dist/orchestrator/autoFixExecutor.js +16 -8
  35. package/dist/orchestrator/dependencyMap.d.ts +1 -1
  36. package/dist/orchestrator/dependencyMap.js +7 -1
  37. package/dist/orchestrator/fileAnchors.js +14 -3
  38. package/dist/orchestrator/flowCoverage.js +1 -0
  39. package/dist/orchestrator/flowRequeue.js +4 -1
  40. package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
  41. package/dist/orchestrator/ingestionExecutors.js +237 -0
  42. package/dist/orchestrator/intakeExecutors.d.ts +3 -0
  43. package/dist/orchestrator/intakeExecutors.js +25 -0
  44. package/dist/orchestrator/planningExecutors.d.ts +4 -0
  45. package/dist/orchestrator/planningExecutors.js +95 -0
  46. package/dist/orchestrator/runtimeCommand.js +7 -15
  47. package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
  48. package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
  49. package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
  50. package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
  51. package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
  52. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
  53. package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
  54. package/dist/orchestrator/selectiveDeepening/index.js +128 -0
  55. package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
  56. package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
  57. package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
  58. package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
  59. package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
  60. package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
  61. package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
  62. package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
  63. package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
  64. package/dist/orchestrator/selectiveDeepening.js +6 -760
  65. package/dist/orchestrator/staleness.js +3 -3
  66. package/dist/orchestrator/structureExecutors.d.ts +5 -0
  67. package/dist/orchestrator/structureExecutors.js +94 -0
  68. package/dist/orchestrator/taskBuilder.d.ts +2 -2
  69. package/dist/orchestrator/taskBuilder.js +101 -82
  70. package/dist/providers/index.d.ts +7 -0
  71. package/dist/providers/index.js +14 -95
  72. package/dist/quota/discoveredLimits.d.ts +1 -0
  73. package/dist/quota/discoveredLimits.js +7 -1
  74. package/dist/quota/index.d.ts +0 -2
  75. package/dist/quota/index.js +1 -2
  76. package/dist/reporting/workBlocks.js +7 -4
  77. package/dist/types/reviewPlanning.d.ts +23 -16
  78. package/dist/validation/auditResults.js +97 -95
  79. package/dist/validation/sessionConfig.d.ts +2 -2
  80. package/dist/validation/sessionConfig.js +14 -7
  81. package/package.json +3 -2
  82. package/schemas/audit_findings.schema.json +3 -3
  83. package/schemas/critical_flows.schema.json +3 -2
  84. package/schemas/dispatch_quota.schema.json +1 -1
  85. package/schemas/graph_bundle.schema.json +1 -1
  86. package/schemas/review_packets.schema.json +1 -1
  87. package/schemas/step_contract.schema.json +80 -0
  88. package/scripts/postinstall.mjs +19 -2
  89. package/skills/audit-code/opencode-command-template.txt +3 -3
  90. package/dist/orchestrator/internalExecutors.js +0 -424
  91. package/dist/providers/localSubprocessProvider.d.ts +0 -9
  92. package/dist/providers/localSubprocessProvider.js +0 -18
  93. package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
  94. package/dist/providers/subprocessTemplateProvider.js +0 -59
  95. package/dist/providers/vscodeTaskProvider.d.ts +0 -7
  96. package/dist/providers/vscodeTaskProvider.js +0 -14
  97. package/dist/quota/probe.d.ts +0 -10
  98. package/dist/quota/probe.js +0 -18
@@ -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
+ }
@@ -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";
@@ -54,14 +54,17 @@ function computeDependencies(params) {
54
54
  }
55
55
  }
56
56
  for (const flow of params.criticalFlows?.flows ?? []) {
57
- const flowBlocks = new Set();
57
+ // Order blocks by first appearance along the flow path so dependency
58
+ // direction follows the flow's traversal order, not block-id lexical order.
59
+ const ordered = [];
60
+ const seen = new Set();
58
61
  for (const path of flow.paths) {
59
62
  const blockId = blockByFile.get(path);
60
- if (blockId) {
61
- flowBlocks.add(blockId);
63
+ if (blockId && !seen.has(blockId)) {
64
+ seen.add(blockId);
65
+ ordered.push(blockId);
62
66
  }
63
67
  }
64
- const ordered = [...flowBlocks].sort();
65
68
  for (let i = 1; i < ordered.length; i++) {
66
69
  dependsOn.get(ordered[i - 1])?.add(ordered[i]);
67
70
  }
@@ -36,6 +36,28 @@ export interface ReviewPacket {
36
36
  rationale: string;
37
37
  estimated_tokens: number;
38
38
  }
39
+ /**
40
+ * Aggregate quality signals for a planned set of review packets — cohesion,
41
+ * boundary-crossing counts, and the weakly-explained-packet diagnostics. Promoted
42
+ * from an inline anonymous object on {@link AuditPlanMetrics} so it can be named
43
+ * and referenced.
44
+ */
45
+ export interface PacketQuality {
46
+ average_cohesion_score: number;
47
+ boundary_crossing_count: number;
48
+ merge_edge_kind_counts: Record<string, number>;
49
+ boundary_edge_kind_counts: Record<string, number>;
50
+ orphan_task_count: number;
51
+ high_fan_in_file_count: number;
52
+ high_fan_out_file_count: number;
53
+ weakly_explained_gap_counts: Record<WeaklyExplainedPacketSample["primary_gap"], number>;
54
+ weakly_explained_file_extension_counts: Record<string, number>;
55
+ weakly_explained_packet_count: number;
56
+ weakly_explained_packet_ids: string[];
57
+ weakly_explained_packet_samples: WeaklyExplainedPacketSample[];
58
+ largest_unexplained_packet_id?: string;
59
+ largest_unexplained_packet_files: number;
60
+ }
39
61
  export interface AuditPlanMetrics {
40
62
  generated_at: string;
41
63
  task_count: number;
@@ -55,22 +77,7 @@ export interface AuditPlanMetrics {
55
77
  largest_packet_id?: string;
56
78
  lens_task_counts: Partial<Record<Lens, number>>;
57
79
  priority_task_counts: Record<NonNullable<AuditTask["priority"]>, number>;
58
- packet_quality: {
59
- average_cohesion_score: number;
60
- boundary_crossing_count: number;
61
- merge_edge_kind_counts: Record<string, number>;
62
- boundary_edge_kind_counts: Record<string, number>;
63
- orphan_task_count: number;
64
- high_fan_in_file_count: number;
65
- high_fan_out_file_count: number;
66
- weakly_explained_gap_counts: Record<WeaklyExplainedPacketSample["primary_gap"], number>;
67
- weakly_explained_file_extension_counts: Record<string, number>;
68
- weakly_explained_packet_count: number;
69
- weakly_explained_packet_ids: string[];
70
- weakly_explained_packet_samples: WeaklyExplainedPacketSample[];
71
- largest_unexplained_packet_id?: string;
72
- largest_unexplained_packet_files: number;
73
- };
80
+ packet_quality: PacketQuality;
74
81
  packet_size: {
75
82
  single_task_packets: number;
76
83
  multi_task_packets: number;