auditor-lambda 0.3.40 → 0.3.41

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 (148) hide show
  1. package/audit-code-wrapper-lib.mjs +20 -2
  2. package/dist/cli/args.d.ts +59 -0
  3. package/dist/cli/args.js +244 -0
  4. package/dist/cli/dispatch.d.ts +80 -0
  5. package/dist/cli/dispatch.js +528 -0
  6. package/dist/cli/prompts.d.ts +18 -0
  7. package/dist/cli/prompts.js +130 -0
  8. package/dist/cli/steps.d.ts +29 -0
  9. package/dist/cli/steps.js +30 -0
  10. package/dist/cli/waveManifest.d.ts +40 -0
  11. package/dist/cli/waveManifest.js +41 -0
  12. package/dist/cli/workerResult.d.ts +18 -0
  13. package/dist/cli/workerResult.js +42 -0
  14. package/dist/cli.d.ts +2 -22
  15. package/dist/cli.js +160 -973
  16. package/dist/extractors/browserExtension.d.ts +1 -3
  17. package/dist/extractors/browserExtension.js +2 -2
  18. package/dist/extractors/designAssessment.d.ts +1 -3
  19. package/dist/extractors/disposition.d.ts +2 -1
  20. package/dist/extractors/disposition.js +3 -0
  21. package/dist/extractors/flows.d.ts +1 -3
  22. package/dist/extractors/flows.js +2 -2
  23. package/dist/extractors/graph.d.ts +1 -2
  24. package/dist/extractors/graph.js +4 -326
  25. package/dist/extractors/graphManifestEdges.d.ts +1 -1
  26. package/dist/extractors/graphPathUtils.d.ts +1 -1
  27. package/dist/extractors/graphPythonImports.d.ts +3 -0
  28. package/dist/extractors/graphPythonImports.js +326 -0
  29. package/dist/extractors/risk.d.ts +1 -2
  30. package/dist/extractors/surfaces.d.ts +1 -3
  31. package/dist/extractors/surfaces.js +2 -2
  32. package/dist/io/artifacts.d.ts +1 -5
  33. package/dist/io/artifacts.js +1 -1
  34. package/dist/io/runArtifacts.js +1 -1
  35. package/dist/mcp/server.js +1 -1
  36. package/dist/orchestrator/advance.d.ts +1 -0
  37. package/dist/orchestrator/advance.js +8 -5
  38. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  39. package/dist/orchestrator/auditTaskUtils.js +27 -0
  40. package/dist/orchestrator/fileAnchors.d.ts +1 -1
  41. package/dist/orchestrator/fileIntegrity.d.ts +7 -0
  42. package/dist/orchestrator/fileIntegrity.js +41 -0
  43. package/dist/orchestrator/flowCoverage.d.ts +1 -1
  44. package/dist/orchestrator/flowPlanning.d.ts +1 -1
  45. package/dist/orchestrator/flowRequeue.d.ts +1 -1
  46. package/dist/orchestrator/internalExecutors.d.ts +3 -1
  47. package/dist/orchestrator/internalExecutors.js +23 -5
  48. package/dist/orchestrator/nextStep.d.ts +2 -1
  49. package/dist/orchestrator/nextStep.js +1 -1
  50. package/dist/orchestrator/planning.d.ts +1 -1
  51. package/dist/orchestrator/requeueCommand.d.ts +1 -1
  52. package/dist/orchestrator/reviewPackets.d.ts +1 -1
  53. package/dist/orchestrator/reviewPackets.js +21 -113
  54. package/dist/orchestrator/runtimeValidation.d.ts +1 -1
  55. package/dist/orchestrator/taskBuilder.d.ts +1 -1
  56. package/dist/orchestrator/taskBuilder.js +1 -12
  57. package/dist/orchestrator/unionFind.d.ts +7 -0
  58. package/dist/orchestrator/unionFind.js +32 -0
  59. package/dist/orchestrator/unitBuilder.d.ts +2 -2
  60. package/dist/orchestrator/unitBuilder.js +4 -18
  61. package/dist/prompts/renderWorkerPrompt.js +18 -1
  62. package/dist/providers/claudeCodeProvider.d.ts +4 -4
  63. package/dist/providers/claudeCodeProvider.js +9 -3
  64. package/dist/providers/constants.d.ts +1 -1
  65. package/dist/providers/constants.js +1 -1
  66. package/dist/providers/index.d.ts +1 -2
  67. package/dist/providers/index.js +5 -4
  68. package/dist/providers/localSubprocessProvider.d.ts +2 -2
  69. package/dist/providers/localSubprocessProvider.js +1 -1
  70. package/dist/providers/opencodeProvider.d.ts +4 -4
  71. package/dist/providers/opencodeProvider.js +7 -2
  72. package/dist/providers/spawnLoggedCommand.d.ts +3 -1
  73. package/dist/providers/spawnLoggedCommand.js +21 -0
  74. package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
  75. package/dist/providers/subprocessTemplateProvider.js +8 -3
  76. package/dist/providers/vscodeTaskProvider.d.ts +3 -4
  77. package/dist/providers/vscodeTaskProvider.js +2 -2
  78. package/dist/quota/discoveredLimits.js +1 -1
  79. package/dist/quota/hostLimits.d.ts +1 -2
  80. package/dist/quota/hostLimits.js +4 -46
  81. package/dist/quota/index.d.ts +18 -15
  82. package/dist/quota/index.js +4 -9
  83. package/dist/quota/scheduler.d.ts +1 -3
  84. package/dist/quota/scheduler.js +1 -2
  85. package/dist/reporting/synthesis.d.ts +1 -2
  86. package/dist/reporting/synthesis.js +2 -0
  87. package/dist/reporting/workBlocks.d.ts +1 -2
  88. package/dist/supervisor/operatorHandoff.js +1 -1
  89. package/dist/supervisor/runLedger.d.ts +1 -1
  90. package/dist/supervisor/runLedger.js +2 -2
  91. package/dist/supervisor/sessionConfig.d.ts +1 -1
  92. package/dist/supervisor/sessionConfig.js +1 -3
  93. package/dist/types/reviewPlanning.d.ts +1 -1
  94. package/dist/types/workerSession.d.ts +6 -0
  95. package/dist/validation/artifacts.d.ts +1 -1
  96. package/dist/validation/artifacts.js +1 -1
  97. package/dist/validation/auditResults.d.ts +1 -1
  98. package/dist/validation/auditResults.js +1 -1
  99. package/dist/validation/sessionConfig.d.ts +2 -3
  100. package/dist/validation/sessionConfig.js +2 -3
  101. package/package.json +4 -2
  102. package/scripts/postinstall.mjs +0 -1
  103. package/dist/io/json.d.ts +0 -10
  104. package/dist/io/json.js +0 -142
  105. package/dist/providers/types.d.ts +0 -33
  106. package/dist/providers/types.js +0 -1
  107. package/dist/quota/compositeQuotaSource.d.ts +0 -7
  108. package/dist/quota/compositeQuotaSource.js +0 -20
  109. package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
  110. package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
  111. package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
  112. package/dist/quota/errorParsers/genericErrorParser.js +0 -7
  113. package/dist/quota/errorParsers/index.d.ts +0 -5
  114. package/dist/quota/errorParsers/index.js +0 -12
  115. package/dist/quota/errorParsing.d.ts +0 -7
  116. package/dist/quota/errorParsing.js +0 -69
  117. package/dist/quota/fileLock.d.ts +0 -6
  118. package/dist/quota/fileLock.js +0 -64
  119. package/dist/quota/learnedQuotaSource.d.ts +0 -7
  120. package/dist/quota/learnedQuotaSource.js +0 -25
  121. package/dist/quota/limits.d.ts +0 -16
  122. package/dist/quota/limits.js +0 -77
  123. package/dist/quota/quotaSource.d.ts +0 -12
  124. package/dist/quota/quotaSource.js +0 -1
  125. package/dist/quota/slidingWindow.d.ts +0 -4
  126. package/dist/quota/slidingWindow.js +0 -28
  127. package/dist/quota/state.d.ts +0 -15
  128. package/dist/quota/state.js +0 -148
  129. package/dist/quota/types.d.ts +0 -67
  130. package/dist/quota/types.js +0 -1
  131. package/dist/reporting/rootCause.d.ts +0 -10
  132. package/dist/reporting/rootCause.js +0 -146
  133. package/dist/types/disposition.d.ts +0 -9
  134. package/dist/types/disposition.js +0 -1
  135. package/dist/types/flows.d.ts +0 -17
  136. package/dist/types/flows.js +0 -1
  137. package/dist/types/graph.d.ts +0 -22
  138. package/dist/types/graph.js +0 -1
  139. package/dist/types/risk.d.ts +0 -9
  140. package/dist/types/risk.js +0 -1
  141. package/dist/types/runLedger.d.ts +0 -17
  142. package/dist/types/runLedger.js +0 -6
  143. package/dist/types/sessionConfig.d.ts +0 -79
  144. package/dist/types/sessionConfig.js +0 -15
  145. package/dist/types/surfaces.d.ts +0 -15
  146. package/dist/types/surfaces.js +0 -1
  147. package/dist/validation/basic.d.ts +0 -13
  148. package/dist/validation/basic.js +0 -46
@@ -0,0 +1,528 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
+ import { isFileMissingError, readJsonFile, writeJsonFile } from "@audit-tools/shared";
5
+ import { loadArtifactBundle } from "../io/artifacts.js";
6
+ import { orderTasksForPacketReview, buildReviewPackets } from "../orchestrator/reviewPackets.js";
7
+ import { buildFileAnchorSummary } from "../orchestrator/fileAnchors.js";
8
+ import { resolveFreshSessionProviderName } from "../providers/index.js";
9
+ import { loadSessionConfig } from "../supervisor/sessionConfig.js";
10
+ import { scheduleWave, buildProviderModelKey, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, } from "../quota/index.js";
11
+ import { taskResultPath, packetPromptPath, artifactNameForId, toBase64Url, fromBase64Url, getFlag, } from "./args.js";
12
+ export const LARGE_FILE_PACKET_TARGET_LINES = 2500;
13
+ export const SMALL_MODEL_HINT_MAX_LINES = 500;
14
+ export const SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS = 3000;
15
+ export const DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS = 9000;
16
+ export const DISPATCH_RESULT_MAP_FILENAME = "dispatch-result-map.json";
17
+ export const ACTIVE_DISPATCH_FILENAME = "active-dispatch.json";
18
+ export function dispatchResultMapPath(runDir) {
19
+ return join(runDir, DISPATCH_RESULT_MAP_FILENAME);
20
+ }
21
+ export function resolveRunScopedArg(argv, rawFlag, b64Flag) {
22
+ const raw = getFlag(argv, rawFlag);
23
+ const encoded = getFlag(argv, b64Flag);
24
+ return raw ?? (encoded ? fromBase64Url(encoded) : undefined);
25
+ }
26
+ export async function loadDispatchResultMap(runDir) {
27
+ try {
28
+ return await readJsonFile(dispatchResultMapPath(runDir));
29
+ }
30
+ catch (error) {
31
+ if (!isFileMissingError(error)) {
32
+ throw error;
33
+ }
34
+ return null;
35
+ }
36
+ }
37
+ export function entriesByTaskId(entries) {
38
+ return new Map(entries.map((entry) => [entry.task_id, entry]));
39
+ }
40
+ export function isIsolatedLargeFilePacket(packet) {
41
+ return (packet.file_paths.length === 1 &&
42
+ packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES);
43
+ }
44
+ export function buildDispatchComplexity(packet, largeFileMode) {
45
+ return {
46
+ priority: packet.priority,
47
+ task_count: packet.task_ids.length,
48
+ file_count: packet.file_paths.length,
49
+ total_lines: packet.total_lines,
50
+ estimated_tokens: packet.estimated_tokens,
51
+ lenses: packet.lenses,
52
+ tags: packet.tags ?? [],
53
+ large_file_mode: largeFileMode,
54
+ };
55
+ }
56
+ export function buildDispatchModelHint(complexity) {
57
+ const deepReasons = [];
58
+ if (complexity.priority === "high")
59
+ deepReasons.push("high_priority");
60
+ if (complexity.large_file_mode)
61
+ deepReasons.push("isolated_large_file");
62
+ if (complexity.estimated_tokens >= DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS) {
63
+ deepReasons.push("high_estimated_tokens");
64
+ }
65
+ if (complexity.tags.some((tag) => tag === "critical_flow" || tag.startsWith("critical_flow:"))) {
66
+ deepReasons.push("critical_flow");
67
+ }
68
+ if (complexity.tags.some((tag) => tag === "external_analyzer_signal" || tag.startsWith("external_tool:"))) {
69
+ deepReasons.push("external_analyzer_signal");
70
+ }
71
+ if (complexity.tags.includes("lens_verification")) {
72
+ deepReasons.push("lens_verification");
73
+ }
74
+ if (deepReasons.length > 0) {
75
+ return { tier: "deep", reasons: deepReasons };
76
+ }
77
+ const sensitiveLenses = new Set(["security", "data_integrity", "reliability"]);
78
+ const hasSensitiveLens = complexity.lenses.some((lens) => sensitiveLenses.has(lens));
79
+ if (complexity.priority === "low" &&
80
+ complexity.total_lines <= SMALL_MODEL_HINT_MAX_LINES &&
81
+ complexity.estimated_tokens <= SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS &&
82
+ !hasSensitiveLens &&
83
+ complexity.tags.length === 0) {
84
+ return { tier: "small", reasons: ["small_low_priority_packet"] };
85
+ }
86
+ const reasons = [];
87
+ if (complexity.priority === "medium")
88
+ reasons.push("medium_priority");
89
+ if (hasSensitiveLens)
90
+ reasons.push("sensitive_lens");
91
+ if (complexity.total_lines > SMALL_MODEL_HINT_MAX_LINES) {
92
+ reasons.push("moderate_size");
93
+ }
94
+ return {
95
+ tier: "standard",
96
+ reasons: reasons.length > 0 ? reasons : ["default_review_packet"],
97
+ };
98
+ }
99
+ export function withinRoot(root, path) {
100
+ const rootPath = resolve(root);
101
+ const absolutePath = resolve(rootPath, path);
102
+ const relativePath = relative(rootPath, absolutePath);
103
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
104
+ throw new Error(`Path '${path}' escapes repository root '${rootPath}'.`);
105
+ }
106
+ return absolutePath;
107
+ }
108
+ function renderAnchorPreview(summary, anchorPath) {
109
+ const preview = summary.anchors.slice(0, 24).map((anchor) => {
110
+ const location = anchor.line ? `${summary.path}:${anchor.line}` : summary.path;
111
+ const detail = anchor.detail ? ` - ${anchor.detail}` : "";
112
+ return `- ${location} [${anchor.kind}] ${anchor.name}${detail}`;
113
+ });
114
+ return [
115
+ "## Large File Review Mode",
116
+ "This packet is intentionally isolated because it covers one large file.",
117
+ "Use targeted reads/searches within this file, guided by the mechanical anchors.",
118
+ "Do not read unrelated files unless a finding cannot be evidenced without a direct boundary check.",
119
+ `Anchor file: ${anchorPath}`,
120
+ `Anchor counts: symbols=${summary.counts.symbols}, routes=${summary.counts.routes}, keywords=${summary.counts.keywords}, graph_edges=${summary.counts.graph_edges}, analyzer_signals=${summary.counts.analyzer_signals}, omitted=${summary.omitted_anchor_count}`,
121
+ "Anchor preview:",
122
+ ...(preview.length > 0 ? preview : ["- no anchors extracted beyond file boundaries"]),
123
+ "",
124
+ ];
125
+ }
126
+ function formatPacketConfidence(value) {
127
+ return typeof value === "number" && Number.isFinite(value)
128
+ ? value.toFixed(2)
129
+ : "n/a";
130
+ }
131
+ function renderPacketGraphContext(packet) {
132
+ const hasContext = (packet.entrypoints?.length ?? 0) > 0 ||
133
+ (packet.key_edges?.length ?? 0) > 0 ||
134
+ (packet.boundary_files?.length ?? 0) > 0 ||
135
+ packet.quality !== undefined;
136
+ if (!hasContext) {
137
+ return [];
138
+ }
139
+ const lines = ["## Packet graph context"];
140
+ if (packet.entrypoints?.length) {
141
+ lines.push("Entrypoints:");
142
+ lines.push(...packet.entrypoints.map((entrypoint) => `- ${entrypoint}`));
143
+ }
144
+ if (packet.key_edges?.length) {
145
+ lines.push("Key internal edges:");
146
+ lines.push(...packet.key_edges.map((edge) => {
147
+ const kind = edge.kind ? ` [${edge.kind}]` : "";
148
+ const reason = edge.reason ? ` - ${edge.reason}` : "";
149
+ return `- ${edge.from} -> ${edge.to}${kind} confidence=${formatPacketConfidence(edge.confidence)}${reason}`;
150
+ }));
151
+ }
152
+ if (packet.boundary_files?.length) {
153
+ lines.push("Boundary files to check only when evidence crosses the packet:");
154
+ lines.push(...packet.boundary_files.map((path) => `- ${path}`));
155
+ }
156
+ if (packet.quality) {
157
+ lines.push(`Quality: cohesion=${packet.quality.cohesion_score}, internal_edges=${packet.quality.internal_edge_count}, boundary_edges=${packet.quality.boundary_edge_count}, unexplained_files=${packet.quality.unexplained_file_count}`);
158
+ }
159
+ lines.push("");
160
+ return lines;
161
+ }
162
+ export function buildPendingAuditTasks(bundle) {
163
+ const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
164
+ const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
165
+ const lineIndex = Object.fromEntries(pendingTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
166
+ return orderTasksForPacketReview(pendingTasks, {
167
+ graphBundle: bundle.graph_bundle,
168
+ lineIndex,
169
+ });
170
+ }
171
+ export async function prepareDispatchArtifacts(params) {
172
+ const runId = params.runId;
173
+ const artifactsDir = params.artifactsDir;
174
+ const runDir = join(artifactsDir, "runs", runId);
175
+ const taskResultsDir = join(runDir, "task-results");
176
+ const dispatchPlanPath = join(runDir, "dispatch-plan.json");
177
+ let reviewRoot = params.root;
178
+ try {
179
+ const workerTask = await readJsonFile(join(runDir, "task.json"));
180
+ reviewRoot ??= workerTask.repo_root;
181
+ }
182
+ catch (error) {
183
+ if (!isFileMissingError(error)) {
184
+ throw error;
185
+ }
186
+ }
187
+ const bundle = await loadArtifactBundle(artifactsDir);
188
+ const tasksPath = join(runDir, "pending-audit-tasks.json");
189
+ const tasks = await readJsonFile(tasksPath).catch((error) => {
190
+ if (isFileMissingError(error))
191
+ return buildPendingAuditTasks(bundle);
192
+ throw error;
193
+ });
194
+ const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
195
+ const lensDefsPath = join(params.packageRoot, "dispatch", "lens-definitions.json");
196
+ const lensDefs = await readJsonFile(lensDefsPath);
197
+ await mkdir(taskResultsDir, { recursive: true });
198
+ const priorResultTaskIds = new Set();
199
+ for (const task of tasks) {
200
+ if (existsSync(taskResultPath(taskResultsDir, task.task_id))) {
201
+ priorResultTaskIds.add(task.task_id);
202
+ }
203
+ }
204
+ const dispatchTasks = priorResultTaskIds.size > 0
205
+ ? tasks.filter((task) => !priorResultTaskIds.has(task.task_id))
206
+ : tasks;
207
+ const lineIndex = Object.fromEntries(dispatchTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
208
+ const orderedTasks = orderTasksForPacketReview(dispatchTasks, {
209
+ graphBundle: bundle.graph_bundle,
210
+ lineIndex,
211
+ });
212
+ const packets = buildReviewPackets(orderedTasks, {
213
+ graphBundle: bundle.graph_bundle,
214
+ lineIndex,
215
+ });
216
+ const tasksById = new Map(orderedTasks.map((task) => [task.task_id, task]));
217
+ const resultPathByTaskId = new Map(orderedTasks.map((task) => [
218
+ task.task_id,
219
+ taskResultPath(taskResultsDir, task.task_id),
220
+ ]));
221
+ const resultPathSet = new Set(resultPathByTaskId.values());
222
+ if (resultPathSet.size !== resultPathByTaskId.size) {
223
+ throw new Error("prepare-dispatch generated duplicate result paths; task ids must be uniquely addressable.");
224
+ }
225
+ const plan = [];
226
+ const resultMapEntries = [];
227
+ for (const task of tasks) {
228
+ if (priorResultTaskIds.has(task.task_id)) {
229
+ resultMapEntries.push({
230
+ packet_id: "__prior_dispatch__",
231
+ task_id: task.task_id,
232
+ result_path: taskResultPath(taskResultsDir, task.task_id),
233
+ });
234
+ }
235
+ }
236
+ let largestPacketId = null;
237
+ let largestLines = 0;
238
+ let largestEstimatedTokens = 0;
239
+ const warnings = [];
240
+ for (const packet of packets) {
241
+ const promptPath = packetPromptPath(taskResultsDir, packet.packet_id);
242
+ const packetTasks = packet.task_ids
243
+ .map((taskId) => tasksById.get(taskId))
244
+ .filter((task) => task !== undefined);
245
+ if (packet.total_lines > largestLines) {
246
+ largestLines = packet.total_lines;
247
+ largestEstimatedTokens = packet.estimated_tokens;
248
+ largestPacketId = packet.packet_id;
249
+ }
250
+ const largeFileMode = isIsolatedLargeFilePacket(packet);
251
+ if (packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES && !largeFileMode) {
252
+ warnings.push({
253
+ code: "large_packet",
254
+ message: `large packet ${packet.packet_id} (~${packet.total_lines} lines) may hit quota limits`,
255
+ });
256
+ }
257
+ for (const task of packetTasks) {
258
+ if (!lensDefs[task.lens]) {
259
+ warnings.push({
260
+ code: "missing_lens_definition",
261
+ message: `no lens definition for '${task.lens}' (task ${task.task_id})`,
262
+ });
263
+ }
264
+ }
265
+ const fileList = packet.file_paths.map((path) => {
266
+ const lines = packet.file_line_counts[path] ?? 0;
267
+ return `- ${path} (${lines} lines)`;
268
+ }).join("\n");
269
+ let anchorPath = null;
270
+ let anchorSummary = null;
271
+ if (largeFileMode) {
272
+ const filePath = packet.file_paths[0];
273
+ if (!reviewRoot) {
274
+ warnings.push({
275
+ code: "large_file_anchor_unavailable",
276
+ message: `large single-file packet ${packet.packet_id} has no repo root available for anchor extraction`,
277
+ });
278
+ }
279
+ else {
280
+ try {
281
+ const totalLines = packet.file_line_counts[filePath] ?? packet.total_lines;
282
+ const content = await readFile(withinRoot(reviewRoot, filePath), "utf8");
283
+ anchorSummary = buildFileAnchorSummary({
284
+ path: filePath,
285
+ content,
286
+ totalLines,
287
+ graphBundle: bundle.graph_bundle,
288
+ externalAnalyzerResults: bundle.external_analyzer_results,
289
+ });
290
+ anchorPath = join(taskResultsDir, artifactNameForId(packet.packet_id, "anchors.json"));
291
+ await writeJsonFile(anchorPath, anchorSummary);
292
+ }
293
+ catch (error) {
294
+ warnings.push({
295
+ code: "large_file_anchor_failed",
296
+ message: `large single-file packet ${packet.packet_id} could not be anchored mechanically: ` +
297
+ (error instanceof Error ? error.message : String(error)),
298
+ });
299
+ }
300
+ }
301
+ }
302
+ const largeFileSection = anchorSummary && anchorPath
303
+ ? renderAnchorPreview(anchorSummary, anchorPath)
304
+ : largeFileMode
305
+ ? [
306
+ "## Large File Review Mode",
307
+ "This packet is intentionally isolated because it covers one large file.",
308
+ "Use targeted reads/searches within this file only.",
309
+ "No mechanical anchor file was available, so rely on targeted symbol and keyword searches before reading broad ranges.",
310
+ "",
311
+ ]
312
+ : [];
313
+ const taskSections = packetTasks.flatMap((task) => {
314
+ const lensDef = lensDefs[task.lens];
315
+ const inputLines = task.inputs
316
+ ? Object.entries(task.inputs)
317
+ .sort(([a], [b]) => a.localeCompare(b))
318
+ .map(([key, value]) => `input.${key}: ${value}`)
319
+ : [];
320
+ const isLensVerification = task.tags?.includes("lens_verification") ?? false;
321
+ const coverageTemplate = task.file_paths.map((path) => ({
322
+ path,
323
+ total_lines: task.file_line_counts?.[path] ?? lineIndex[path] ?? 0,
324
+ }));
325
+ return [
326
+ `### ${task.task_id}`,
327
+ `unit_id: ${task.unit_id}`,
328
+ `pass_id: ${task.pass_id}`,
329
+ `lens: ${task.lens}`,
330
+ ...(task.tags?.length ? [`tags: ${task.tags.join(", ")}`] : []),
331
+ ...inputLines,
332
+ `rationale: ${task.rationale}`,
333
+ "",
334
+ `Lens guidance: ${lensDef?.description ?? task.lens}`,
335
+ `Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
336
+ ...(isLensVerification
337
+ ? [
338
+ "",
339
+ "Lens verification mode: review the prior result summary in the rationale and use only targeted source checks.",
340
+ "Do not redo every packet and do not write direct findings for this task.",
341
+ "Return findings: [] plus verification metadata. Include followup_tasks only for bounded, specific re-review packets.",
342
+ ]
343
+ : []),
344
+ "",
345
+ "file_coverage (copy exactly into your AuditResult for this task):",
346
+ "```json",
347
+ JSON.stringify(coverageTemplate),
348
+ "```",
349
+ "",
350
+ ];
351
+ });
352
+ const submitCommand = `"${process.execPath}" "${join(params.packageRoot, "audit-code.mjs")}" submit-packet ` +
353
+ `--run-id-b64 ${toBase64Url(runId)} ` +
354
+ `--packet-id-b64 ${toBase64Url(packet.packet_id)} ` +
355
+ `--artifacts-dir-b64 ${toBase64Url(artifactsDir)}`;
356
+ const complexity = buildDispatchComplexity(packet, largeFileMode);
357
+ for (const task of packetTasks) {
358
+ resultMapEntries.push({
359
+ packet_id: packet.packet_id,
360
+ task_id: task.task_id,
361
+ result_path: resultPathByTaskId.get(task.task_id),
362
+ });
363
+ }
364
+ const prompt = [
365
+ "You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
366
+ "",
367
+ "## Packet",
368
+ `packet_id: ${packet.packet_id}`,
369
+ `task_count: ${packet.task_ids.length}`,
370
+ `lenses: ${packet.lenses.join(", ")}`,
371
+ `estimated_tokens: ${packet.estimated_tokens}`,
372
+ "",
373
+ "## Files to read",
374
+ largeFileMode
375
+ ? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
376
+ : "Use your Read tool. Paths are repo-relative from the current working directory.",
377
+ "Use host Read and Grep tools for source inspection. Do not use shell search commands.",
378
+ fileList,
379
+ "",
380
+ ...renderPacketGraphContext(packet),
381
+ ...largeFileSection,
382
+ "## Tasks",
383
+ ...taskSections,
384
+ "## Output",
385
+ "Do not write files directly. Do not use a Write tool, create temp files, edit source files,",
386
+ "remediate findings, create extra task results, or run unrelated audits.",
387
+ "Produce one JSON array containing exactly one AuditResult object for each listed task.",
388
+ "",
389
+ "Required AuditResult fields:",
390
+ " task_id copy from the task metadata",
391
+ " unit_id copy from the task metadata",
392
+ " pass_id copy from the task metadata",
393
+ " lens copy from the task metadata",
394
+ " file_coverage [{path, total_lines}] - copy the exact template from each task section above",
395
+ " findings [] or array of finding objects",
396
+ "",
397
+ "Lens verification tasks:",
398
+ " tasks tagged lens_verification must use findings: [] and include verification:",
399
+ " {verified: boolean, needs_followup: boolean, concerns?: string[],",
400
+ " coverage_concerns?: string[], confidence_concerns?: string[],",
401
+ " followup_tasks?: AuditTask[]}.",
402
+ " Follow-up AuditTask suggestions must stay bounded to files in this packet and use the same lens.",
403
+ "",
404
+ "Each finding object:",
405
+ " id unique ID, e.g. \"COR-001\"",
406
+ " title short title",
407
+ " category specific finding category, such as missing-validation or command-execution",
408
+ " severity critical|high|medium|low|info",
409
+ " confidence high|medium|low",
410
+ " lens must match the task lens exactly",
411
+ " summary 1-2 sentence description",
412
+ " affected_files [{path, line_start?, line_end?, symbol?}] - objects, not strings; min 1 entry",
413
+ " evidence [\"path/to/file.ts:42 - description of what you see there\"] - min 1 entry",
414
+ "",
415
+ "Constraints:",
416
+ "1. line_end must not exceed the file's actual line count.",
417
+ "2. affected_files entries are objects with a path key, not plain strings.",
418
+ "3. Only reference files from the packet unless a finding genuinely crosses a boundary.",
419
+ "4. findings: [] is correct when you find nothing genuine.",
420
+ "",
421
+ "## Submit",
422
+ "Pipe the JSON array on stdin to this command:",
423
+ ` ${submitCommand}`,
424
+ "",
425
+ "The command validates and writes the packet-owned result files. Exit 0 means accepted.",
426
+ "Non-zero: read the errors, fix the JSON, and run the same submit command again. Retry up to 3 times.",
427
+ "",
428
+ "## Final response",
429
+ `After the submit command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
430
+ ].join("\n");
431
+ await writeFile(promptPath, prompt, "utf8");
432
+ plan.push({
433
+ packet_id: packet.packet_id,
434
+ description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)` +
435
+ (largeFileMode ? " [isolated large-file mode]" : ""),
436
+ prompt_path: promptPath,
437
+ complexity,
438
+ model_hint: buildDispatchModelHint(complexity),
439
+ });
440
+ }
441
+ await writeJsonFile(dispatchPlanPath, plan);
442
+ await writeJsonFile(dispatchResultMapPath(runDir), {
443
+ contract_version: "audit-code-dispatch-results/v1alpha1",
444
+ run_id: runId,
445
+ entries: resultMapEntries,
446
+ });
447
+ const hostModel = params.hostModel ?? null;
448
+ const perPacketTokens = plan.map((p) => p.complexity.estimated_tokens);
449
+ const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
450
+ const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
451
+ const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
452
+ const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
453
+ const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
454
+ explicitLimit: params.hostActiveSubagentLimit,
455
+ sessionConfig,
456
+ });
457
+ const dispatchCachedLimits = await lookupDiscoveredLimits(quotaProviderKey).catch(() => null);
458
+ const waveSchedule = scheduleWave({
459
+ providerName: quotaProviderName,
460
+ sessionConfig,
461
+ hostModel,
462
+ requestedConcurrency: sessionConfig.parallel_workers ?? plan.length,
463
+ estimatedSlotTokens: perPacketTokens,
464
+ quotaStateEntry,
465
+ hostConcurrencyLimit,
466
+ discoveredLimits: dispatchCachedLimits,
467
+ });
468
+ const dispatchQuota = {
469
+ contract_version: "audit-code-dispatch-quota/v1alpha2",
470
+ run_id: runId,
471
+ model: hostModel,
472
+ resolved_limits: waveSchedule.resolved_limits,
473
+ confidence: waveSchedule.confidence,
474
+ source: waveSchedule.source,
475
+ host_concurrency_limit: waveSchedule.host_concurrency_limit,
476
+ wave_size: waveSchedule.wave_size,
477
+ estimated_wave_tokens: waveSchedule.estimated_wave_tokens,
478
+ cooldown_until: waveSchedule.cooldown_until,
479
+ quota_source_snapshot: waveSchedule.quota_source_snapshot ?? null,
480
+ backoff_state: null,
481
+ };
482
+ const dispatchQuotaPath = join(runDir, "dispatch-quota.json");
483
+ await writeJsonFile(dispatchQuotaPath, dispatchQuota);
484
+ if (waveSchedule.confidence !== "low") {
485
+ const contextBudget = waveSchedule.resolved_limits.context_tokens - waveSchedule.resolved_limits.output_tokens;
486
+ for (const p of plan) {
487
+ if (p.complexity.estimated_tokens > contextBudget) {
488
+ warnings.push({
489
+ code: "oversized_packet",
490
+ message: `Packet ${p.packet_id} estimated tokens (${p.complexity.estimated_tokens}) exceed ` +
491
+ `context budget (${contextBudget}). This packet may fail at dispatch. ` +
492
+ `Set quota.default_context_tokens or quota.models in session-config.json to override.`,
493
+ });
494
+ }
495
+ }
496
+ }
497
+ const warningsPath = warnings.length > 0
498
+ ? join(runDir, "dispatch-warnings.json")
499
+ : null;
500
+ if (warningsPath) {
501
+ await writeJsonFile(warningsPath, warnings);
502
+ }
503
+ const activeDispatch = {
504
+ run_id: runId,
505
+ created_at: new Date().toISOString(),
506
+ packet_count: plan.length,
507
+ task_count: orderedTasks.length,
508
+ status: "active",
509
+ };
510
+ await writeJsonFile(join(artifactsDir, ACTIVE_DISPATCH_FILENAME), activeDispatch);
511
+ return {
512
+ run_id: runId,
513
+ dispatch_plan_path: dispatchPlanPath,
514
+ dispatch_quota_path: dispatchQuotaPath,
515
+ packet_count: plan.length,
516
+ task_count: orderedTasks.length,
517
+ skipped_task_count: priorResultTaskIds.size,
518
+ largest_packet: largestPacketId
519
+ ? {
520
+ packet_id: largestPacketId,
521
+ total_lines: largestLines,
522
+ estimated_tokens: largestEstimatedTokens,
523
+ }
524
+ : null,
525
+ warning_count: warnings.length,
526
+ dispatch_warnings_path: warningsPath,
527
+ };
528
+ }
@@ -0,0 +1,18 @@
1
+ import type { ActiveReviewRun } from "../supervisor/operatorHandoff.js";
2
+ export declare function nextStepCommand(root: string, artifactsDir: string): string;
3
+ export declare function mergeAndIngestCommand(artifactsDir: string, runId: string): string;
4
+ export declare function renderDispatchReviewPrompt(params: {
5
+ root: string;
6
+ artifactsDir: string;
7
+ activeReviewRun: ActiveReviewRun;
8
+ dispatchPlanPath: string;
9
+ dispatchQuotaPath: string | null;
10
+ hostCanRestrictSubagentTools: boolean;
11
+ hostCanSelectSubagentModel: boolean;
12
+ }): string;
13
+ export declare function renderSingleTaskFallbackStepPrompt(params: {
14
+ singleTaskPromptPath: string;
15
+ activeReviewRun: ActiveReviewRun;
16
+ }): string;
17
+ export declare function renderPresentReportPrompt(finalReportPath: string): string;
18
+ export declare function renderBlockedStepPrompt(reason: string): string;
@@ -0,0 +1,130 @@
1
+ import { renderCommand } from "./args.js";
2
+ export function nextStepCommand(root, artifactsDir) {
3
+ return renderCommand([
4
+ "audit-code",
5
+ "next-step",
6
+ "--root",
7
+ root,
8
+ "--artifacts-dir",
9
+ artifactsDir,
10
+ ]);
11
+ }
12
+ export function mergeAndIngestCommand(artifactsDir, runId) {
13
+ return renderCommand([
14
+ "audit-code",
15
+ "merge-and-ingest",
16
+ "--artifacts-dir",
17
+ artifactsDir,
18
+ "--run-id",
19
+ runId,
20
+ ]);
21
+ }
22
+ export function renderDispatchReviewPrompt(params) {
23
+ const mergeCommand = mergeAndIngestCommand(params.artifactsDir, params.activeReviewRun.run_id);
24
+ const continueCommand = nextStepCommand(params.root, params.artifactsDir);
25
+ const modelLine = params.hostCanSelectSubagentModel
26
+ ? "When launching each subagent, map `entry.model_hint.tier` (`small`, `standard`, `deep`) to an available host model without asking the user for model names."
27
+ : "Ignore `entry.model_hint`; this host did not report per-subagent model selection.";
28
+ const toolsLine = params.hostCanRestrictSubagentTools
29
+ ? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
30
+ : "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
31
+ const dispatchDataLines = params.dispatchQuotaPath
32
+ ? [
33
+ "Read these generated files:",
34
+ "",
35
+ ` Dispatch plan: ${params.dispatchPlanPath}`,
36
+ ` Dispatch quota: ${params.dispatchQuotaPath}`,
37
+ "",
38
+ "Use the `wave_size` from the quota data. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
39
+ "",
40
+ "`host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
41
+ "",
42
+ "For each wave: use the `task` tool (or equivalent subagent dispatch) to launch up to `wave_size` subagents in parallel (one per entry), wait for all to finish, then start the next wave.",
43
+ ]
44
+ : [
45
+ "Read this generated dispatch plan:",
46
+ "",
47
+ ` ${params.dispatchPlanPath}`,
48
+ "",
49
+ "Launch one subagent for each entry in the plan.",
50
+ ];
51
+ return [
52
+ "# audit-code dispatch review",
53
+ "",
54
+ ...dispatchDataLines,
55
+ "",
56
+ "Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
57
+ "",
58
+ "Subagent prompt shape:",
59
+ "",
60
+ ' Read and follow the audit instructions in: <entry.prompt_path>',
61
+ "",
62
+ modelLine,
63
+ toolsLine,
64
+ "",
65
+ "Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
66
+ "",
67
+ "**File access pre-approval:** Each dispatch plan entry includes an `access` object with `read_paths` and `write_paths`. If your host supports per-subagent file access restrictions, pre-approve those paths before launching each subagent. Workers should not access files outside their declared paths.",
68
+ "",
69
+ "**After all waves complete:**",
70
+ "",
71
+ "Run exactly:",
72
+ "",
73
+ ` ${mergeCommand}`,
74
+ "",
75
+ "If merge-and-ingest fails, stop and report the exact command and error output. Do not manually merge results or edit audit state.",
76
+ "",
77
+ "If merge-and-ingest succeeds, run:",
78
+ "",
79
+ ` ${continueCommand}`,
80
+ "",
81
+ "Read and follow only the new step prompt path returned by that command.",
82
+ "",
83
+ ].join("\n");
84
+ }
85
+ export function renderSingleTaskFallbackStepPrompt(params) {
86
+ return [
87
+ "# audit-code single-task fallback step",
88
+ "",
89
+ "Use this step only because the host reported no callable subagent facility.",
90
+ "",
91
+ "Read and follow exactly this generated single-task prompt:",
92
+ "",
93
+ ` ${params.singleTaskPromptPath}`,
94
+ "",
95
+ "Complete exactly one AuditResult for the task named there, write the JSON array to the prompt's audit_results_path, run the exact worker_command from that prompt, then stop.",
96
+ "",
97
+ "Do not run dispatch commands, do not prepare packets, do not run next-step again in this turn, and do not read a report after the worker command.",
98
+ "",
99
+ "The only backend command allowed after writing the result is:",
100
+ "",
101
+ ` ${renderCommand(params.activeReviewRun.worker_command)}`,
102
+ "",
103
+ ].join("\n");
104
+ }
105
+ export function renderPresentReportPrompt(finalReportPath) {
106
+ return [
107
+ "# audit-code present report",
108
+ "",
109
+ "The deterministic audit is complete.",
110
+ "",
111
+ `Read the final audit report from: ${finalReportPath}`,
112
+ "",
113
+ "Present the completed audit with work blocks first.",
114
+ "",
115
+ "Do not run the orchestrator again for this completed audit.",
116
+ "",
117
+ ].join("\n");
118
+ }
119
+ export function renderBlockedStepPrompt(reason) {
120
+ return [
121
+ "# audit-code blocked",
122
+ "",
123
+ "The audit cannot continue automatically from this step.",
124
+ "",
125
+ "Report this blocker verbatim and stop:",
126
+ "",
127
+ reason,
128
+ "",
129
+ ].join("\n");
130
+ }