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
package/dist/cli.js CHANGED
@@ -1,8 +1,6 @@
1
- import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
2
- import { createReadStream, existsSync } from "node:fs";
3
- import { Buffer } from "node:buffer";
4
- import { createHash } from "node:crypto";
5
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
1
+ import { mkdir, readFile, readdir, rename, rm, unlink } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, join, resolve } from "node:path";
6
4
  import { fileURLToPath } from "node:url";
7
5
  import { buildRepoManifest } from "./extractors/fileInventory.js";
8
6
  import { buildFileDisposition } from "./extractors/disposition.js";
@@ -13,14 +11,14 @@ import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
13
11
  import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
14
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
15
13
  import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
16
- import { isFileMissingError, readJsonFile, writeJsonFile } from "./io/json.js";
14
+ import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues } from "@audit-tools/shared";
17
15
  import { validateArtifactBundle } from "./validation/artifacts.js";
18
16
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
19
- import { prefixValidationIssues } from "./validation/basic.js";
20
17
  import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
21
18
  import { buildAuditReportModel, renderAuditReportMarkdown, } from "./reporting/synthesis.js";
22
19
  import { deriveAuditState } from "./orchestrator/state.js";
23
20
  import { advanceAudit } from "./orchestrator/advance.js";
21
+ import { checkFileIntegrity } from "./orchestrator/fileIntegrity.js";
24
22
  import { decideNextStep } from "./orchestrator/nextStep.js";
25
23
  import { renderDesignReviewPrompt } from "./orchestrator/designReviewPrompt.js";
26
24
  import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
@@ -29,228 +27,26 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
29
27
  import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
30
28
  import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
31
29
  import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
32
- import { buildReviewPackets, orderTasksForPacketReview, estimateTaskGroupTokens, } from "./orchestrator/reviewPackets.js";
33
- import { buildFileAnchorSummary, } from "./orchestrator/fileAnchors.js";
30
+ import { estimateTaskGroupTokens, } from "./orchestrator/reviewPackets.js";
34
31
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
35
32
  import { runAuditCodeMcpServer } from "./mcp/server.js";
36
- import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, } from "./quota/index.js";
33
+ import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
34
+ // Re-exports from extracted modules
35
+ export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
36
+ import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, fromBase64Url, renderCommand, summarizeLaunchExit, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, resolveHostDispatchCapability, countLines, listBatchResultFiles, } from "./cli/args.js";
37
+ import { nextStepCommand, mergeAndIngestCommand, renderDispatchReviewPrompt, renderSingleTaskFallbackStepPrompt, renderPresentReportPrompt, renderBlockedStepPrompt, } from "./cli/prompts.js";
38
+ import { writeCurrentStep, } from "./cli/steps.js";
39
+ import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArtifacts, isWorkerResult, buildWorkerFailureBlocker, formatAuditResultValidationError, } from "./cli/workerResult.js";
40
+ import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
41
+ import { readWaveManifest, writeWaveManifest, removeWaveManifest, buildWaveSlotEntry, } from "./cli/waveManifest.js";
37
42
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
38
43
  const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
39
- const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
40
- const STEP_CONTRACT_VERSION = "audit-code-step/v1alpha1";
41
- const LARGE_FILE_PACKET_TARGET_LINES = 2500;
42
- const SMALL_MODEL_HINT_MAX_LINES = 500;
43
- const SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS = 3000;
44
- const DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS = 9000;
45
- const DIRECT_CLI_DEFAULTS = {
46
- rootDir: ".",
47
- artifactsDir: ".artifacts",
48
- maxRuns: 1000,
49
- agentBatchSize: 6,
50
- parallelWorkers: 1,
51
- timeoutMs: 30 * 60 * 1000, // 30 minutes
52
- uiMode: "headless",
53
- };
54
- // Keep the sample-run payload explicit so the demo command is deterministic.
55
44
  const SAMPLE_REPO_FILES = [
56
45
  { path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
57
46
  { path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
58
47
  { path: "infra/deploy.yml", size_bytes: 420, hash: "ghi789" },
59
48
  { path: "docs/notes.md", size_bytes: 300, hash: "doc111" },
60
49
  ];
61
- function isLongFlagToken(value) {
62
- return typeof value === "string" && value.startsWith("--");
63
- }
64
- // Read a long-form CLI flag while treating a following flag token as "missing".
65
- function getFlag(argv, name, fallback) {
66
- const index = argv.indexOf(name);
67
- if (index < 0)
68
- return fallback;
69
- const candidate = argv[index + 1];
70
- if (!candidate || isLongFlagToken(candidate))
71
- return fallback;
72
- return candidate;
73
- }
74
- // Boolean flags only care whether the token is present at all.
75
- function hasFlag(argv, name) {
76
- return argv.includes(name);
77
- }
78
- function getOptionalBooleanFlag(argv, name) {
79
- const raw = getFlag(argv, name);
80
- if (raw === undefined) {
81
- return undefined;
82
- }
83
- if (raw === "true") {
84
- return true;
85
- }
86
- if (raw === "false") {
87
- return false;
88
- }
89
- throw new Error(`${name} must be either true or false.`);
90
- }
91
- function optionalBooleanEnv(value) {
92
- if (value === "true")
93
- return true;
94
- if (value === "false")
95
- return false;
96
- return undefined;
97
- }
98
- export function resolveHostDispatchCapability(options) {
99
- if (options.explicit !== undefined) {
100
- return options.explicit;
101
- }
102
- if (options.sessionConfig.host_can_dispatch_subagents !== undefined) {
103
- return options.sessionConfig.host_can_dispatch_subagents;
104
- }
105
- return optionalBooleanEnv((options.env ?? process.env).AUDIT_CODE_HOST_CAN_DISPATCH) ?? true;
106
- }
107
- function toBase64Url(value) {
108
- return Buffer.from(value, "utf8").toString("base64url");
109
- }
110
- function fromBase64Url(value) {
111
- return Buffer.from(value, "base64url").toString("utf8");
112
- }
113
- function digestId(value) {
114
- return createHash("sha256").update(value).digest("hex").slice(0, 12);
115
- }
116
- function safeArtifactStem(value) {
117
- const sanitized = value
118
- .replace(/[^a-zA-Z0-9_-]+/g, "_")
119
- .replace(/^_+|_+$/g, "")
120
- .slice(0, 80);
121
- return sanitized.length > 0 ? sanitized : "artifact";
122
- }
123
- function artifactNameForId(value, extension) {
124
- return `${safeArtifactStem(value)}_${digestId(value)}.${extension}`;
125
- }
126
- function quoteCommandArg(value) {
127
- return /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
128
- }
129
- function renderCommand(argv) {
130
- return argv.map((item) => quoteCommandArg(item)).join(" ");
131
- }
132
- function summarizeLaunchExit(result) {
133
- if (result.accepted !== false && !result.error) {
134
- return null;
135
- }
136
- const parts = [
137
- result.signal
138
- ? `signal ${result.signal}`
139
- : `exit code ${result.exitCode ?? "unknown"}`,
140
- result.command ? `command: ${result.command}` : null,
141
- result.stdoutPath ? `stdout: ${result.stdoutPath}` : null,
142
- result.stderrPath ? `stderr: ${result.stderrPath}` : null,
143
- result.error ?? null,
144
- ].filter((part) => Boolean(part));
145
- return parts.join("; ");
146
- }
147
- function taskResultPath(taskResultsDir, taskId) {
148
- return join(taskResultsDir, artifactNameForId(taskId, "json"));
149
- }
150
- function packetPromptPath(taskResultsDir, packetId) {
151
- return join(taskResultsDir, artifactNameForId(packetId, "prompt.md"));
152
- }
153
- async function readStdinText() {
154
- if (process.stdin.isTTY) {
155
- return "";
156
- }
157
- return await new Promise((resolveInput, reject) => {
158
- let input = "";
159
- process.stdin.setEncoding("utf8");
160
- process.stdin.on("data", (chunk) => {
161
- input += chunk;
162
- });
163
- process.stdin.on("end", () => resolveInput(input));
164
- process.stdin.on("error", reject);
165
- });
166
- }
167
- function resolveFlagPath(argv, name, fallback) {
168
- return resolve(getFlag(argv, name, fallback));
169
- }
170
- function normalizePositiveInteger(value) {
171
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
172
- return undefined;
173
- }
174
- return Math.floor(value);
175
- }
176
- function parsePositiveIntegerFlag(argv, name) {
177
- const raw = getFlag(argv, name);
178
- if (raw === undefined) {
179
- return undefined;
180
- }
181
- return normalizePositiveInteger(Number(raw));
182
- }
183
- function getArtifactsDir(argv) {
184
- return resolveFlagPath(argv, "--artifacts-dir", DIRECT_CLI_DEFAULTS.artifactsDir);
185
- }
186
- function getRootDir(argv) {
187
- return resolveFlagPath(argv, "--root", DIRECT_CLI_DEFAULTS.rootDir);
188
- }
189
- function warnIfNotGitRepo(root) {
190
- const gitEntry = join(root, ".git");
191
- if (!existsSync(gitEntry)) {
192
- console.warn(`Warning: target directory '${root}' does not appear to be a git repository. Diff-based signals will be unavailable.`);
193
- }
194
- }
195
- function getBatchResultsDir(argv) {
196
- const value = getFlag(argv, "--batch-results");
197
- return value ? resolve(value) : undefined;
198
- }
199
- function getMaxRuns(argv) {
200
- return parsePositiveIntegerFlag(argv, "--max-runs") ?? DIRECT_CLI_DEFAULTS.maxRuns;
201
- }
202
- function getAgentBatchSize(argv, sessionConfig) {
203
- return (parsePositiveIntegerFlag(argv, "--agent-batch-size") ??
204
- normalizePositiveInteger(sessionConfig.agent_task_batch_size) ??
205
- DIRECT_CLI_DEFAULTS.agentBatchSize);
206
- }
207
- function getParallelWorkers(argv, sessionConfig) {
208
- return (parsePositiveIntegerFlag(argv, "--parallel") ??
209
- normalizePositiveInteger(sessionConfig.parallel_workers) ??
210
- DIRECT_CLI_DEFAULTS.parallelWorkers);
211
- }
212
- function getTimeoutMs(argv, sessionConfig) {
213
- return (parsePositiveIntegerFlag(argv, "--timeout") ??
214
- normalizePositiveInteger(sessionConfig.timeout_ms) ??
215
- DIRECT_CLI_DEFAULTS.timeoutMs);
216
- }
217
- function getExplicitProvider(argv) {
218
- return getFlag(argv, "--provider");
219
- }
220
- function getHostModel(argv) {
221
- return getFlag(argv, "--host-model") ?? null;
222
- }
223
- function getHostMaxActiveSubagents(argv) {
224
- return parsePositiveIntegerFlag(argv, "--host-max-active-subagents") ?? null;
225
- }
226
- function getQuotaProbeMode(argv, sessionConfig) {
227
- const raw = getFlag(argv, "--quota-probe") ?? sessionConfig.quota?.probe ?? "auto";
228
- if (raw === "auto" || raw === "never" || raw === "force")
229
- return raw;
230
- return "auto";
231
- }
232
- function resolveRunProviderName(argv, sessionConfig) {
233
- return resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig);
234
- }
235
- function chunkArray(arr, size) {
236
- const chunkSize = normalizePositiveInteger(size);
237
- if (chunkSize === undefined) {
238
- throw new Error("chunkArray size must be a positive integer.");
239
- }
240
- const chunks = [];
241
- for (let i = 0; i < arr.length; i += chunkSize) {
242
- chunks.push(arr.slice(i, i + chunkSize));
243
- }
244
- return chunks;
245
- }
246
- function getUiMode(argv, fallback = DIRECT_CLI_DEFAULTS.uiMode) {
247
- const raw = getFlag(argv, "--ui");
248
- if (raw === "visible")
249
- return "visible";
250
- if (raw === "headless")
251
- return "headless";
252
- return fallback;
253
- }
254
50
  function buildEnvelope(params) {
255
51
  return {
256
52
  contract_version: ADVANCE_AUDIT_CONTRACT_VERSION,
@@ -312,30 +108,6 @@ function buildBlockedAuditState(params) {
312
108
  : item),
313
109
  };
314
110
  }
315
- async function countLines(path) {
316
- return new Promise((resolve, reject) => {
317
- let lines = 0;
318
- let byteCount = 0;
319
- let lastByte = -1;
320
- const stream = createReadStream(path);
321
- stream.on("data", (chunk) => {
322
- const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
323
- byteCount += buffer.length;
324
- for (let i = 0; i < buffer.length; ++i) {
325
- if (buffer[i] === 10)
326
- lines++;
327
- lastByte = buffer[i];
328
- }
329
- });
330
- stream.on("end", () => {
331
- if (byteCount === 0)
332
- return resolve(0);
333
- // Files not ending with \n have one final line not counted above
334
- resolve(lastByte !== 10 ? lines + 1 : lines);
335
- });
336
- stream.on("error", reject);
337
- });
338
- }
339
111
  async function buildLineIndex(root, repoManifest) {
340
112
  const entries = [];
341
113
  const batchSize = 25;
@@ -368,26 +140,6 @@ async function buildLineIndexForPaths(root, paths) {
368
140
  }));
369
141
  return Object.fromEntries(entries);
370
142
  }
371
- async function listBatchResultFiles(batchDir) {
372
- const entries = await readdir(batchDir, { withFileTypes: true });
373
- const files = entries
374
- .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json"))
375
- .map((entry) => join(batchDir, entry.name))
376
- .sort((a, b) => a.localeCompare(b));
377
- if (files.length === 0) {
378
- throw new Error(`No JSON audit result files found in ${batchDir}.`);
379
- }
380
- return files;
381
- }
382
- function buildPendingAuditTasks(bundle) {
383
- const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
384
- const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
385
- const lineIndex = Object.fromEntries(pendingTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
386
- return orderTasksForPacketReview(pendingTasks, {
387
- graphBundle: bundle.graph_bundle,
388
- lineIndex,
389
- });
390
- }
391
143
  async function addFileLineCountHints(root, tasks) {
392
144
  const lineIndex = await buildLineIndexForPaths(root, tasks.flatMap((task) => task.file_paths));
393
145
  return tasks.map((task) => ({
@@ -477,6 +229,11 @@ async function ensureSemanticReviewRun(params) {
477
229
  const pendingTasks = await addFileLineCountHints(params.root, buildPendingAuditTasks(params.bundle));
478
230
  const pendingTasksPath = join(paths.runDir, "pending-audit-tasks.json");
479
231
  const auditResultsPath = join(paths.runDir, "audit-results.json");
232
+ const taskReadPaths = new Set();
233
+ for (const pt of pendingTasks) {
234
+ for (const fp of pt.file_paths)
235
+ taskReadPaths.add(fp);
236
+ }
480
237
  const task = {
481
238
  contract_version: "audit-code-worker/v1alpha1",
482
239
  run_id: runId,
@@ -496,6 +253,10 @@ async function ensureSemanticReviewRun(params) {
496
253
  pending_audit_tasks_path: pendingTasksPath,
497
254
  timeout_ms: params.timeoutMs,
498
255
  max_retries: 0,
256
+ access: {
257
+ read_paths: [...taskReadPaths],
258
+ write_paths: [auditResultsPath, paths.resultPath],
259
+ },
499
260
  };
500
261
  const prompt = renderWorkerPrompt(task);
501
262
  await writeWorkerTaskFiles(task, prompt, paths, params.artifactsDir, pendingTasks);
@@ -519,174 +280,6 @@ async function ensureSemanticReviewRun(params) {
519
280
  });
520
281
  return { state: blockedState, bundle: blockedBundle, activeReviewRun };
521
282
  }
522
- function nextStepCommand(root, artifactsDir, extraArgs = []) {
523
- return renderCommand([
524
- "audit-code",
525
- "next-step",
526
- "--root",
527
- root,
528
- "--artifacts-dir",
529
- artifactsDir,
530
- ...extraArgs,
531
- ]);
532
- }
533
- function mergeAndIngestCommand(artifactsDir, runId) {
534
- return renderCommand([
535
- "audit-code",
536
- "merge-and-ingest",
537
- "--run-id",
538
- runId,
539
- "--artifacts-dir",
540
- artifactsDir,
541
- ]);
542
- }
543
- function renderDispatchReviewPrompt(params) {
544
- const mergeCommand = mergeAndIngestCommand(params.artifactsDir, params.activeReviewRun.run_id);
545
- const continueCommand = nextStepCommand(params.root, params.artifactsDir);
546
- const modelLine = params.hostCanSelectSubagentModel
547
- ? "When launching each subagent, map `entry.model_hint.tier` (`small`, `standard`, `deep`) to an available host model without asking the user for model names."
548
- : "Ignore `entry.model_hint`; this host did not report per-subagent model selection.";
549
- const toolsLine = params.hostCanRestrictSubagentTools
550
- ? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
551
- : "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
552
- const dispatchDataLines = params.dispatchQuotaPath
553
- ? [
554
- "Read these generated files unless the current tool response already included equivalent `dispatch_plan_entries` and `dispatch_quota` fields:",
555
- "",
556
- ` Dispatch plan: ${params.dispatchPlanPath}`,
557
- ` Dispatch quota: ${params.dispatchQuotaPath}`,
558
- "",
559
- "Use the `wave_size` from the quota data. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
560
- "",
561
- "`host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
562
- "",
563
- "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.",
564
- ]
565
- : [
566
- "Read this generated dispatch plan unless the current tool response already included equivalent `dispatch_plan_entries`:",
567
- "",
568
- ` ${params.dispatchPlanPath}`,
569
- "",
570
- "Launch one subagent for each entry in the plan.",
571
- ];
572
- return [
573
- "# audit-code dispatch review",
574
- "",
575
- ...dispatchDataLines,
576
- "",
577
- "Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
578
- "",
579
- "Subagent prompt shape:",
580
- "",
581
- ' Read and follow the audit instructions in: <entry.prompt_path>',
582
- "",
583
- modelLine,
584
- toolsLine,
585
- "",
586
- "Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
587
- "",
588
- "**After all waves complete:**",
589
- "",
590
- "Run exactly:",
591
- "",
592
- ` ${mergeCommand}`,
593
- "",
594
- "If merge-and-ingest fails, stop and report the exact command and error output. Do not manually merge results or edit audit state.",
595
- "",
596
- "If merge-and-ingest succeeds, run:",
597
- "",
598
- ` ${continueCommand}`,
599
- "",
600
- "Read and follow only the new step prompt path returned by that command.",
601
- "",
602
- ].join("\n");
603
- }
604
- function renderSingleTaskFallbackStepPrompt(params) {
605
- return [
606
- "# audit-code single-task fallback step",
607
- "",
608
- "Use this step only because the host reported no callable subagent facility.",
609
- "",
610
- "Read and follow exactly this generated single-task prompt:",
611
- "",
612
- ` ${params.singleTaskPromptPath}`,
613
- "",
614
- "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.",
615
- "",
616
- "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.",
617
- "",
618
- "The only backend command allowed after writing the result is:",
619
- "",
620
- ` ${renderCommand(params.activeReviewRun.worker_command)}`,
621
- "",
622
- ].join("\n");
623
- }
624
- function renderPresentReportPrompt(finalReportPath) {
625
- return [
626
- "# audit-code present report",
627
- "",
628
- "The deterministic audit is complete.",
629
- "",
630
- "Read the final audit report from the `audit-code://report/current` MCP resource (or from the file at:",
631
- ` ${finalReportPath}`,
632
- "if a Read tool is available).",
633
- "",
634
- "Present the completed audit with work blocks first.",
635
- "",
636
- "Do not run the orchestrator again for this completed audit.",
637
- "",
638
- ].join("\n");
639
- }
640
- function renderBlockedStepPrompt(reason) {
641
- return [
642
- "# audit-code blocked",
643
- "",
644
- "The audit cannot continue automatically from this step.",
645
- "",
646
- "Report this blocker verbatim and stop:",
647
- "",
648
- reason,
649
- "",
650
- ].join("\n");
651
- }
652
- async function writeCurrentStep(params) {
653
- const stepsDir = join(params.artifactsDir, "steps");
654
- await mkdir(stepsDir, { recursive: true });
655
- const promptPath = join(stepsDir, "current-prompt.md");
656
- const stepPath = join(stepsDir, "current-step.json");
657
- await writeFile(promptPath, params.prompt, "utf8");
658
- const step = {
659
- contract_version: STEP_CONTRACT_VERSION,
660
- step_kind: params.stepKind,
661
- prompt_path: promptPath,
662
- status: params.status,
663
- run_id: params.runId,
664
- allowed_commands: params.allowedCommands,
665
- stop_condition: params.stopCondition,
666
- repo_root: params.repoRoot,
667
- artifacts_dir: params.artifactsDir,
668
- artifact_paths: {
669
- current_step: stepPath,
670
- current_prompt: promptPath,
671
- ...params.artifactPaths,
672
- },
673
- };
674
- await writeJsonFile(stepPath, step);
675
- return step;
676
- }
677
- function formatAuditResultValidationError(issues) {
678
- return (`audit-results validation failed with ${issues.length} error(s):\n` +
679
- formatAuditResultIssues(issues));
680
- }
681
- function buildWorkerFailureBlocker(workerResult) {
682
- const details = workerResult.errors.filter((error) => error.trim().length > 0);
683
- return details.length > 0
684
- ? `${workerResult.summary} ${details.join(" ")}`
685
- : workerResult.summary;
686
- }
687
- function looksLikeCliFlag(value) {
688
- return isLongFlagToken(value);
689
- }
690
283
  export const cliTestUtils = {
691
284
  defaults: DIRECT_CLI_DEFAULTS,
692
285
  getFlag,
@@ -757,6 +350,7 @@ async function runAuditStep(options) {
757
350
  runtimeValidationUpdates,
758
351
  externalAnalyzerResults,
759
352
  preferredExecutor: options.preferredExecutor,
353
+ opentoken: options.opentoken,
760
354
  });
761
355
  await writeCoreArtifacts(options.artifactsDir, result.updated_bundle);
762
356
  const archivedPendingResults = await maybeArchiveLegacyPendingResults(options.auditResultsPath);
@@ -805,29 +399,6 @@ async function ingestBatchAuditResults(options) {
805
399
  next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
806
400
  };
807
401
  }
808
- function buildWorkerResult(params) {
809
- return {
810
- contract_version: WORKER_RESULT_CONTRACT_VERSION,
811
- run_id: params.runId,
812
- obligation_id: params.obligationId,
813
- status: params.status,
814
- progress_made: params.progressMade,
815
- selected_executor: params.selectedExecutor,
816
- artifacts_written: params.artifactsWritten,
817
- summary: params.summary,
818
- next_likely_step: params.nextLikelyStep,
819
- errors: params.errors,
820
- };
821
- }
822
- async function persistWorkerRunArtifacts(paths, workerResult, executionMode) {
823
- await writeJsonFile(paths.resultPath, workerResult);
824
- await writeJsonFile(paths.statusPath, {
825
- run_id: workerResult.run_id,
826
- status: workerResult.status,
827
- execution_mode: executionMode,
828
- result_path: paths.resultPath,
829
- });
830
- }
831
402
  async function persistConfigErrorHandoff(params) {
832
403
  const bundle = await loadArtifactBundle(params.artifactsDir);
833
404
  const blockedState = buildBlockedAuditState({
@@ -850,12 +421,6 @@ async function persistConfigErrorHandoff(params) {
850
421
  });
851
422
  await writeAuditCodeHandoffArtifacts(handoff);
852
423
  }
853
- function isWorkerResult(value) {
854
- return (typeof value === "object" &&
855
- value !== null &&
856
- value.contract_version ===
857
- WORKER_RESULT_CONTRACT_VERSION);
858
- }
859
424
  export async function runSample(argv = process.argv) {
860
425
  const repoManifest = buildRepoManifest("sample-repo", SAMPLE_REPO_FILES);
861
426
  const disposition = buildFileDisposition(repoManifest);
@@ -990,6 +555,7 @@ async function cmdAdvanceAudit(argv) {
990
555
  auditResultsPath: getFlag(argv, "--results"),
991
556
  runtimeUpdatesPath: getFlag(argv, "--updates"),
992
557
  externalAnalyzerPath,
558
+ opentoken: sessionConfig.opentoken?.enabled,
993
559
  });
994
560
  if (result.selected_executor !== "agent") {
995
561
  await clearDispatchFiles(artifactsDir);
@@ -1039,6 +605,22 @@ async function runDeterministicForNextStep(params) {
1039
605
  : join(params.artifactsDir, "audit-report.md"),
1040
606
  };
1041
607
  }
608
+ if (index === 0 && bundle.repo_manifest) {
609
+ const pendingTasks = buildPendingAuditTasks(bundle);
610
+ const taskFiles = new Set();
611
+ for (const task of pendingTasks) {
612
+ for (const fp of Object.keys(task.file_line_counts ?? {}))
613
+ taskFiles.add(fp);
614
+ }
615
+ if (taskFiles.size > 0) {
616
+ const integrity = await checkFileIntegrity(params.root, bundle.repo_manifest, [...taskFiles]);
617
+ if (!integrity.is_clean) {
618
+ console.log(`File integrity check: ${integrity.changed_files.length} changed, ${integrity.missing_files.length} missing — re-running intake.`);
619
+ await advanceAudit(bundle, { root: params.root, preferredExecutor: "intake_executor", opentoken: params.opentoken });
620
+ continue;
621
+ }
622
+ }
623
+ }
1042
624
  if (decision.selected_executor === "design_review") {
1043
625
  const findingsPath = join(params.artifactsDir, "incoming", "design-review-findings.json");
1044
626
  let reviewFindings;
@@ -1095,11 +677,42 @@ async function runDeterministicForNextStep(params) {
1095
677
  reason: lastSummary || decision.reason,
1096
678
  };
1097
679
  }
1098
- const result = await runAuditStep({
1099
- root: params.root,
1100
- artifactsDir: params.artifactsDir,
1101
- });
680
+ let result;
681
+ try {
682
+ result = await runAuditStep({
683
+ root: params.root,
684
+ artifactsDir: params.artifactsDir,
685
+ opentoken: params.opentoken,
686
+ });
687
+ }
688
+ catch (error) {
689
+ const current = await loadArtifactBundle(params.artifactsDir);
690
+ const currentState = deriveAuditState(current);
691
+ currentState.last_executor = decision.selected_executor ?? undefined;
692
+ currentState.last_obligation = decision.selected_obligation ?? undefined;
693
+ await writeCoreArtifacts(params.artifactsDir, { ...current, audit_state: currentState });
694
+ await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
695
+ iteration: index + 1,
696
+ max_runs: params.maxRuns,
697
+ last_executor: decision.selected_executor,
698
+ last_obligation: decision.selected_obligation,
699
+ prior_summary: lastSummary || null,
700
+ error: error instanceof Error ? error.message : String(error),
701
+ timestamp: new Date().toISOString(),
702
+ });
703
+ const detail = error instanceof Error ? error.message : String(error);
704
+ throw new Error(`Deterministic executor ${decision.selected_executor} failed on obligation ${decision.selected_obligation} (iteration ${index + 1}/${params.maxRuns}, prior progress: ${lastSummary || "none"}): ${detail}`, { cause: error instanceof Error ? error : undefined });
705
+ }
1102
706
  lastSummary = result.progress_summary;
707
+ await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
708
+ iteration: index + 1,
709
+ max_runs: params.maxRuns,
710
+ last_executor: result.selected_executor,
711
+ last_obligation: decision.selected_obligation,
712
+ progress_made: result.progress_made,
713
+ summary: result.progress_summary,
714
+ timestamp: new Date().toISOString(),
715
+ });
1103
716
  if (result.selected_executor !== "agent") {
1104
717
  await clearDispatchFiles(params.artifactsDir);
1105
718
  }
@@ -1169,6 +782,7 @@ async function cmdNextStep(argv) {
1169
782
  selfCliPath: resolve(argv[1] ?? process.argv[1] ?? ""),
1170
783
  timeoutMs: getTimeoutMs(argv, sessionConfig),
1171
784
  maxRuns: getMaxRuns(argv),
785
+ opentoken: sessionConfig.opentoken?.enabled,
1172
786
  });
1173
787
  if (result.kind === "complete") {
1174
788
  const step = await writeCurrentStep({
@@ -1258,11 +872,16 @@ async function cmdNextStep(argv) {
1258
872
  singleTaskPromptPath,
1259
873
  activeReviewRun: result.activeReviewRun,
1260
874
  }),
875
+ access: {
876
+ read_paths: [singleTaskPromptPath],
877
+ write_paths: [result.activeReviewRun.audit_results_path],
878
+ },
1261
879
  });
1262
880
  console.log(JSON.stringify(step, null, 2));
1263
881
  return;
1264
882
  }
1265
883
  const dispatch = await prepareDispatchArtifacts({
884
+ packageRoot,
1266
885
  runId: result.activeReviewRun.run_id,
1267
886
  artifactsDir,
1268
887
  root,
@@ -1299,6 +918,13 @@ async function cmdNextStep(argv) {
1299
918
  hostCanRestrictSubagentTools,
1300
919
  hostCanSelectSubagentModel,
1301
920
  }),
921
+ access: {
922
+ read_paths: [
923
+ dispatch.dispatch_plan_path,
924
+ ...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
925
+ ],
926
+ write_paths: [],
927
+ },
1302
928
  });
1303
929
  console.log(JSON.stringify(step, null, 2));
1304
930
  }
@@ -1349,6 +975,38 @@ async function cmdRunToCompletion(argv) {
1349
975
  while (runCount < maxRuns) {
1350
976
  const bundle = await loadArtifactBundle(artifactsDir);
1351
977
  const decision = decideNextStep(bundle);
978
+ // Resume interrupted parallel wave: ingest any results that workers
979
+ // wrote before the previous process exited.
980
+ const priorWave = await readWaveManifest(artifactsDir);
981
+ if (priorWave) {
982
+ process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
983
+ let recoveredProgress = false;
984
+ for (const entry of priorWave.slots) {
985
+ try {
986
+ const results = await readJsonFile(entry.audit_results_path);
987
+ if (!results || results.length === 0)
988
+ continue;
989
+ const stepResult = await runAuditStep({
990
+ root,
991
+ artifactsDir,
992
+ preferredExecutor: "result_ingestion_executor",
993
+ auditResultsPath: entry.audit_results_path,
994
+ });
995
+ if (stepResult.progress_made) {
996
+ recoveredProgress = true;
997
+ anyProgress = true;
998
+ for (const a of stepResult.artifacts_written)
999
+ artifactsWritten.add(a);
1000
+ }
1001
+ }
1002
+ catch {
1003
+ process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
1004
+ }
1005
+ }
1006
+ await removeWaveManifest(artifactsDir);
1007
+ if (recoveredProgress)
1008
+ continue;
1009
+ }
1352
1010
  if (decision.selected_executor === "agent" &&
1353
1011
  bundle.audit_tasks?.some((t) => t.tags?.includes("selective_deepening") &&
1354
1012
  t.status !== "complete") &&
@@ -1402,6 +1060,11 @@ async function cmdRunToCompletion(argv) {
1402
1060
  const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
1403
1061
  const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
1404
1062
  const blockAuditResultsPath = join(blockPaths.runDir, "audit-results.json");
1063
+ const blockReadPaths = new Set();
1064
+ for (const pt of blockPendingTasks) {
1065
+ for (const fp of pt.file_paths)
1066
+ blockReadPaths.add(fp);
1067
+ }
1405
1068
  const blockTask = {
1406
1069
  contract_version: "audit-code-worker/v1alpha1",
1407
1070
  run_id: blockRunId,
@@ -1421,6 +1084,10 @@ async function cmdRunToCompletion(argv) {
1421
1084
  pending_audit_tasks_path: blockPendingTasksPath,
1422
1085
  timeout_ms: timeoutMs,
1423
1086
  max_retries: 0,
1087
+ access: {
1088
+ read_paths: [...blockReadPaths],
1089
+ write_paths: [blockAuditResultsPath, blockPaths.resultPath],
1090
+ },
1424
1091
  };
1425
1092
  const blockPrompt = renderWorkerPrompt(blockTask);
1426
1093
  await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
@@ -1526,6 +1193,11 @@ async function cmdRunToCompletion(argv) {
1526
1193
  const slotPaths = getRunPaths(artifactsDir, slotRunId);
1527
1194
  const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
1528
1195
  const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
1196
+ const slotReadPaths = new Set();
1197
+ for (const t of group) {
1198
+ for (const fp of t.file_paths)
1199
+ slotReadPaths.add(fp);
1200
+ }
1529
1201
  const slotTask = {
1530
1202
  contract_version: "audit-code-worker/v1alpha1",
1531
1203
  run_id: slotRunId,
@@ -1540,6 +1212,10 @@ async function cmdRunToCompletion(argv) {
1540
1212
  worker_command_mode: "deferred",
1541
1213
  timeout_ms: timeoutMs,
1542
1214
  max_retries: 0,
1215
+ access: {
1216
+ read_paths: [...slotReadPaths],
1217
+ write_paths: [slotAuditResultsPath, slotPaths.resultPath],
1218
+ },
1543
1219
  };
1544
1220
  const slotPrompt = renderWorkerPrompt(slotTask);
1545
1221
  await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
@@ -1556,6 +1232,12 @@ async function cmdRunToCompletion(argv) {
1556
1232
  pending_audit_tasks_path: slot.pendingTasksPath,
1557
1233
  })), workerSlots.flatMap((slot) => slot.group));
1558
1234
  const parallelStartedAt = new Date().toISOString();
1235
+ await writeWaveManifest(artifactsDir, {
1236
+ obligation_id: obligationId ?? "unknown",
1237
+ started_at: parallelStartedAt,
1238
+ pid: process.pid,
1239
+ slots: workerSlots.map(buildWaveSlotEntry),
1240
+ });
1559
1241
  const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
1560
1242
  repoRoot: root,
1561
1243
  runId: slot.runId,
@@ -1710,6 +1392,7 @@ async function cmdRunToCompletion(argv) {
1710
1392
  }
1711
1393
  }
1712
1394
  }
1395
+ await removeWaveManifest(artifactsDir);
1713
1396
  if (batchErrors.length > 0) {
1714
1397
  const bundleAfter = await loadArtifactBundle(artifactsDir);
1715
1398
  const blockedState = buildBlockedAuditState({
@@ -1884,6 +1567,13 @@ async function cmdRunToCompletion(argv) {
1884
1567
  const providerAuditResultsPath = preferredExecutor === "agent"
1885
1568
  ? join(paths.runDir, "audit-results.json")
1886
1569
  : auditResultsPath;
1570
+ const providerReadPaths = new Set();
1571
+ if (pendingAuditTasks) {
1572
+ for (const pt of pendingAuditTasks) {
1573
+ for (const fp of pt.file_paths)
1574
+ providerReadPaths.add(fp);
1575
+ }
1576
+ }
1887
1577
  const task = {
1888
1578
  contract_version: "audit-code-worker/v1alpha1",
1889
1579
  run_id: runId,
@@ -1905,6 +1595,10 @@ async function cmdRunToCompletion(argv) {
1905
1595
  external_analyzer_results_path: externalAnalyzerPath,
1906
1596
  timeout_ms: timeoutMs,
1907
1597
  max_retries: 0,
1598
+ access: providerReadPaths.size > 0 ? {
1599
+ read_paths: [...providerReadPaths],
1600
+ write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
1601
+ } : undefined,
1908
1602
  };
1909
1603
  const prompt = renderWorkerPrompt(task);
1910
1604
  await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
@@ -2141,520 +1835,12 @@ async function cmdWorkerRun(argv) {
2141
1835
  process.exitCode = 1;
2142
1836
  }
2143
1837
  }
2144
- const DISPATCH_RESULT_MAP_FILENAME = "dispatch-result-map.json";
2145
- const ACTIVE_DISPATCH_FILENAME = "active-dispatch.json";
2146
- function dispatchResultMapPath(runDir) {
2147
- return join(runDir, DISPATCH_RESULT_MAP_FILENAME);
2148
- }
2149
- function resolveRunScopedArg(argv, rawFlag, b64Flag) {
2150
- const raw = getFlag(argv, rawFlag);
2151
- const encoded = getFlag(argv, b64Flag);
2152
- return raw ?? (encoded ? fromBase64Url(encoded) : undefined);
2153
- }
2154
- async function loadDispatchResultMap(runDir) {
2155
- try {
2156
- return await readJsonFile(dispatchResultMapPath(runDir));
2157
- }
2158
- catch (error) {
2159
- if (!isFileMissingError(error)) {
2160
- throw error;
2161
- }
2162
- return null;
2163
- }
2164
- }
2165
- function entriesByTaskId(entries) {
2166
- return new Map(entries.map((entry) => [entry.task_id, entry]));
2167
- }
2168
- function isIsolatedLargeFilePacket(packet) {
2169
- return (packet.file_paths.length === 1 &&
2170
- packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES);
2171
- }
2172
- function buildDispatchComplexity(packet, largeFileMode) {
2173
- return {
2174
- priority: packet.priority,
2175
- task_count: packet.task_ids.length,
2176
- file_count: packet.file_paths.length,
2177
- total_lines: packet.total_lines,
2178
- estimated_tokens: packet.estimated_tokens,
2179
- lenses: packet.lenses,
2180
- tags: packet.tags ?? [],
2181
- large_file_mode: largeFileMode,
2182
- };
2183
- }
2184
- function buildDispatchModelHint(complexity) {
2185
- const deepReasons = [];
2186
- if (complexity.priority === "high")
2187
- deepReasons.push("high_priority");
2188
- if (complexity.large_file_mode)
2189
- deepReasons.push("isolated_large_file");
2190
- if (complexity.estimated_tokens >= DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS) {
2191
- deepReasons.push("high_estimated_tokens");
2192
- }
2193
- if (complexity.tags.some((tag) => tag === "critical_flow" || tag.startsWith("critical_flow:"))) {
2194
- deepReasons.push("critical_flow");
2195
- }
2196
- if (complexity.tags.some((tag) => tag === "external_analyzer_signal" || tag.startsWith("external_tool:"))) {
2197
- deepReasons.push("external_analyzer_signal");
2198
- }
2199
- if (complexity.tags.includes("lens_verification")) {
2200
- deepReasons.push("lens_verification");
2201
- }
2202
- if (deepReasons.length > 0) {
2203
- return { tier: "deep", reasons: deepReasons };
2204
- }
2205
- const sensitiveLenses = new Set(["security", "data_integrity", "reliability"]);
2206
- const hasSensitiveLens = complexity.lenses.some((lens) => sensitiveLenses.has(lens));
2207
- if (complexity.priority === "low" &&
2208
- complexity.total_lines <= SMALL_MODEL_HINT_MAX_LINES &&
2209
- complexity.estimated_tokens <= SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS &&
2210
- !hasSensitiveLens &&
2211
- complexity.tags.length === 0) {
2212
- return { tier: "small", reasons: ["small_low_priority_packet"] };
2213
- }
2214
- const reasons = [];
2215
- if (complexity.priority === "medium")
2216
- reasons.push("medium_priority");
2217
- if (hasSensitiveLens)
2218
- reasons.push("sensitive_lens");
2219
- if (complexity.total_lines > SMALL_MODEL_HINT_MAX_LINES) {
2220
- reasons.push("moderate_size");
2221
- }
2222
- return {
2223
- tier: "standard",
2224
- reasons: reasons.length > 0 ? reasons : ["default_review_packet"],
2225
- };
2226
- }
2227
- function withinRoot(root, path) {
2228
- const rootPath = resolve(root);
2229
- const absolutePath = resolve(rootPath, path);
2230
- const relativePath = relative(rootPath, absolutePath);
2231
- if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
2232
- throw new Error(`Path '${path}' escapes repository root '${rootPath}'.`);
2233
- }
2234
- return absolutePath;
2235
- }
2236
- function renderAnchorPreview(summary, anchorPath) {
2237
- const preview = summary.anchors.slice(0, 24).map((anchor) => {
2238
- const location = anchor.line ? `${summary.path}:${anchor.line}` : summary.path;
2239
- const detail = anchor.detail ? ` - ${anchor.detail}` : "";
2240
- return `- ${location} [${anchor.kind}] ${anchor.name}${detail}`;
2241
- });
2242
- return [
2243
- "## Large File Review Mode",
2244
- "This packet is intentionally isolated because it covers one large file.",
2245
- "Use targeted reads/searches within this file, guided by the mechanical anchors.",
2246
- "Do not read unrelated files unless a finding cannot be evidenced without a direct boundary check.",
2247
- `Anchor file: ${anchorPath}`,
2248
- `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}`,
2249
- "Anchor preview:",
2250
- ...(preview.length > 0 ? preview : ["- no anchors extracted beyond file boundaries"]),
2251
- "",
2252
- ];
2253
- }
2254
- function formatPacketConfidence(value) {
2255
- return typeof value === "number" && Number.isFinite(value)
2256
- ? value.toFixed(2)
2257
- : "n/a";
2258
- }
2259
- function renderPacketGraphContext(packet) {
2260
- const hasContext = (packet.entrypoints?.length ?? 0) > 0 ||
2261
- (packet.key_edges?.length ?? 0) > 0 ||
2262
- (packet.boundary_files?.length ?? 0) > 0 ||
2263
- packet.quality !== undefined;
2264
- if (!hasContext) {
2265
- return [];
2266
- }
2267
- const lines = ["## Packet graph context"];
2268
- if (packet.entrypoints?.length) {
2269
- lines.push("Entrypoints:");
2270
- lines.push(...packet.entrypoints.map((entrypoint) => `- ${entrypoint}`));
2271
- }
2272
- if (packet.key_edges?.length) {
2273
- lines.push("Key internal edges:");
2274
- lines.push(...packet.key_edges.map((edge) => {
2275
- const kind = edge.kind ? ` [${edge.kind}]` : "";
2276
- const reason = edge.reason ? ` - ${edge.reason}` : "";
2277
- return `- ${edge.from} -> ${edge.to}${kind} confidence=${formatPacketConfidence(edge.confidence)}${reason}`;
2278
- }));
2279
- }
2280
- if (packet.boundary_files?.length) {
2281
- lines.push("Boundary files to check only when evidence crosses the packet:");
2282
- lines.push(...packet.boundary_files.map((path) => `- ${path}`));
2283
- }
2284
- if (packet.quality) {
2285
- 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}`);
2286
- }
2287
- lines.push("");
2288
- return lines;
2289
- }
2290
- async function prepareDispatchArtifacts(params) {
2291
- const runId = params.runId;
2292
- const artifactsDir = params.artifactsDir;
2293
- const runDir = join(artifactsDir, "runs", runId);
2294
- const taskResultsDir = join(runDir, "task-results");
2295
- const dispatchPlanPath = join(runDir, "dispatch-plan.json");
2296
- let reviewRoot = params.root;
2297
- try {
2298
- const workerTask = await readJsonFile(join(runDir, "task.json"));
2299
- reviewRoot ??= workerTask.repo_root;
2300
- }
2301
- catch (error) {
2302
- if (!isFileMissingError(error)) {
2303
- throw error;
2304
- }
2305
- }
2306
- const bundle = await loadArtifactBundle(artifactsDir);
2307
- const tasksPath = join(runDir, "pending-audit-tasks.json");
2308
- const tasks = await readJsonFile(tasksPath).catch((error) => {
2309
- if (isFileMissingError(error))
2310
- return buildPendingAuditTasks(bundle);
2311
- throw error;
2312
- });
2313
- const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
2314
- const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
2315
- const lensDefs = await readJsonFile(lensDefsPath);
2316
- await mkdir(taskResultsDir, { recursive: true });
2317
- // On resume: skip tasks whose result files already exist from a prior dispatch.
2318
- const priorResultTaskIds = new Set();
2319
- for (const task of tasks) {
2320
- if (existsSync(taskResultPath(taskResultsDir, task.task_id))) {
2321
- priorResultTaskIds.add(task.task_id);
2322
- }
2323
- }
2324
- const dispatchTasks = priorResultTaskIds.size > 0
2325
- ? tasks.filter((task) => !priorResultTaskIds.has(task.task_id))
2326
- : tasks;
2327
- const lineIndex = Object.fromEntries(dispatchTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
2328
- const orderedTasks = orderTasksForPacketReview(dispatchTasks, {
2329
- graphBundle: bundle.graph_bundle,
2330
- lineIndex,
2331
- });
2332
- const packets = buildReviewPackets(orderedTasks, {
2333
- graphBundle: bundle.graph_bundle,
2334
- lineIndex,
2335
- });
2336
- const tasksById = new Map(orderedTasks.map((task) => [task.task_id, task]));
2337
- const resultPathByTaskId = new Map(orderedTasks.map((task) => [
2338
- task.task_id,
2339
- taskResultPath(taskResultsDir, task.task_id),
2340
- ]));
2341
- const resultPathSet = new Set(resultPathByTaskId.values());
2342
- if (resultPathSet.size !== resultPathByTaskId.size) {
2343
- throw new Error("prepare-dispatch generated duplicate result paths; task ids must be uniquely addressable.");
2344
- }
2345
- const plan = [];
2346
- const resultMapEntries = [];
2347
- for (const task of tasks) {
2348
- if (priorResultTaskIds.has(task.task_id)) {
2349
- resultMapEntries.push({
2350
- packet_id: "__prior_dispatch__",
2351
- task_id: task.task_id,
2352
- result_path: taskResultPath(taskResultsDir, task.task_id),
2353
- });
2354
- }
2355
- }
2356
- let largestPacketId = null;
2357
- let largestLines = 0;
2358
- let largestEstimatedTokens = 0;
2359
- const warnings = [];
2360
- for (const packet of packets) {
2361
- const promptPath = packetPromptPath(taskResultsDir, packet.packet_id);
2362
- const packetTasks = packet.task_ids
2363
- .map((taskId) => tasksById.get(taskId))
2364
- .filter((task) => task !== undefined);
2365
- if (packet.total_lines > largestLines) {
2366
- largestLines = packet.total_lines;
2367
- largestEstimatedTokens = packet.estimated_tokens;
2368
- largestPacketId = packet.packet_id;
2369
- }
2370
- const largeFileMode = isIsolatedLargeFilePacket(packet);
2371
- if (packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES && !largeFileMode) {
2372
- warnings.push({
2373
- code: "large_packet",
2374
- message: `large packet ${packet.packet_id} (~${packet.total_lines} lines) may hit quota limits`,
2375
- });
2376
- }
2377
- for (const task of packetTasks) {
2378
- if (!lensDefs[task.lens]) {
2379
- warnings.push({
2380
- code: "missing_lens_definition",
2381
- message: `no lens definition for '${task.lens}' (task ${task.task_id})`,
2382
- });
2383
- }
2384
- }
2385
- const fileList = packet.file_paths.map((path) => {
2386
- const lines = packet.file_line_counts[path] ?? 0;
2387
- return `- ${path} (${lines} lines)`;
2388
- }).join("\n");
2389
- let anchorPath = null;
2390
- let anchorSummary = null;
2391
- if (largeFileMode) {
2392
- const filePath = packet.file_paths[0];
2393
- if (!reviewRoot) {
2394
- warnings.push({
2395
- code: "large_file_anchor_unavailable",
2396
- message: `large single-file packet ${packet.packet_id} has no repo root available for anchor extraction`,
2397
- });
2398
- }
2399
- else {
2400
- try {
2401
- const totalLines = packet.file_line_counts[filePath] ?? packet.total_lines;
2402
- const content = await readFile(withinRoot(reviewRoot, filePath), "utf8");
2403
- anchorSummary = buildFileAnchorSummary({
2404
- path: filePath,
2405
- content,
2406
- totalLines,
2407
- graphBundle: bundle.graph_bundle,
2408
- externalAnalyzerResults: bundle.external_analyzer_results,
2409
- });
2410
- anchorPath = join(taskResultsDir, artifactNameForId(packet.packet_id, "anchors.json"));
2411
- await writeJsonFile(anchorPath, anchorSummary);
2412
- }
2413
- catch (error) {
2414
- warnings.push({
2415
- code: "large_file_anchor_failed",
2416
- message: `large single-file packet ${packet.packet_id} could not be anchored mechanically: ` +
2417
- (error instanceof Error ? error.message : String(error)),
2418
- });
2419
- }
2420
- }
2421
- }
2422
- const largeFileSection = anchorSummary && anchorPath
2423
- ? renderAnchorPreview(anchorSummary, anchorPath)
2424
- : largeFileMode
2425
- ? [
2426
- "## Large File Review Mode",
2427
- "This packet is intentionally isolated because it covers one large file.",
2428
- "Use targeted reads/searches within this file only.",
2429
- "No mechanical anchor file was available, so rely on targeted symbol and keyword searches before reading broad ranges.",
2430
- "",
2431
- ]
2432
- : [];
2433
- const taskSections = packetTasks.flatMap((task) => {
2434
- const lensDef = lensDefs[task.lens];
2435
- const inputLines = task.inputs
2436
- ? Object.entries(task.inputs)
2437
- .sort(([a], [b]) => a.localeCompare(b))
2438
- .map(([key, value]) => `input.${key}: ${value}`)
2439
- : [];
2440
- const isLensVerification = task.tags?.includes("lens_verification") ?? false;
2441
- const coverageTemplate = task.file_paths.map((path) => ({
2442
- path,
2443
- total_lines: task.file_line_counts?.[path] ?? lineIndex[path] ?? 0,
2444
- }));
2445
- return [
2446
- `### ${task.task_id}`,
2447
- `unit_id: ${task.unit_id}`,
2448
- `pass_id: ${task.pass_id}`,
2449
- `lens: ${task.lens}`,
2450
- ...(task.tags?.length ? [`tags: ${task.tags.join(", ")}`] : []),
2451
- ...inputLines,
2452
- `rationale: ${task.rationale}`,
2453
- "",
2454
- `Lens guidance: ${lensDef?.description ?? task.lens}`,
2455
- `Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
2456
- ...(isLensVerification
2457
- ? [
2458
- "",
2459
- "Lens verification mode: review the prior result summary in the rationale and use only targeted source checks.",
2460
- "Do not redo every packet and do not write direct findings for this task.",
2461
- "Return findings: [] plus verification metadata. Include followup_tasks only for bounded, specific re-review packets.",
2462
- ]
2463
- : []),
2464
- "",
2465
- "file_coverage (copy exactly into your AuditResult for this task):",
2466
- "```json",
2467
- JSON.stringify(coverageTemplate),
2468
- "```",
2469
- "",
2470
- ];
2471
- });
2472
- const submitCommand = `"${process.execPath}" "${join(packageRoot, "audit-code.mjs")}" submit-packet ` +
2473
- `--run-id-b64 ${toBase64Url(runId)} ` +
2474
- `--packet-id-b64 ${toBase64Url(packet.packet_id)} ` +
2475
- `--artifacts-dir-b64 ${toBase64Url(artifactsDir)}`;
2476
- const complexity = buildDispatchComplexity(packet, largeFileMode);
2477
- for (const task of packetTasks) {
2478
- resultMapEntries.push({
2479
- packet_id: packet.packet_id,
2480
- task_id: task.task_id,
2481
- result_path: resultPathByTaskId.get(task.task_id),
2482
- });
2483
- }
2484
- const prompt = [
2485
- "You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
2486
- "",
2487
- "## Packet",
2488
- `packet_id: ${packet.packet_id}`,
2489
- `task_count: ${packet.task_ids.length}`,
2490
- `lenses: ${packet.lenses.join(", ")}`,
2491
- `estimated_tokens: ${packet.estimated_tokens}`,
2492
- "",
2493
- "## Files to read",
2494
- largeFileMode
2495
- ? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
2496
- : "Use your Read tool. Paths are repo-relative from the current working directory.",
2497
- "Prefer host Read/Grep tools. On native Windows, do not use Unix pipelines like `grep ... | head`; if shell search is unavoidable, use `Select-String` as a fallback.",
2498
- fileList,
2499
- "",
2500
- ...renderPacketGraphContext(packet),
2501
- ...largeFileSection,
2502
- "## Tasks",
2503
- ...taskSections,
2504
- "## Output",
2505
- "Do not write files directly. Do not use a Write tool, create temp files, edit source files,",
2506
- "remediate findings, create extra task results, or run unrelated audits.",
2507
- "Produce one JSON array containing exactly one AuditResult object for each listed task.",
2508
- "",
2509
- "Required AuditResult fields:",
2510
- " task_id copy from the task metadata",
2511
- " unit_id copy from the task metadata",
2512
- " pass_id copy from the task metadata",
2513
- " lens copy from the task metadata",
2514
- " file_coverage [{path, total_lines}] - copy the exact template from each task section above",
2515
- " findings [] or array of finding objects",
2516
- "",
2517
- "Lens verification tasks:",
2518
- " tasks tagged lens_verification must use findings: [] and include verification:",
2519
- " {verified: boolean, needs_followup: boolean, concerns?: string[],",
2520
- " coverage_concerns?: string[], confidence_concerns?: string[],",
2521
- " followup_tasks?: AuditTask[]}.",
2522
- " Follow-up AuditTask suggestions must stay bounded to files in this packet and use the same lens.",
2523
- "",
2524
- "Each finding object:",
2525
- " id unique ID, e.g. \"COR-001\"",
2526
- " title short title",
2527
- " category specific finding category, such as missing-validation or command-execution",
2528
- " severity critical|high|medium|low|info",
2529
- " confidence high|medium|low",
2530
- " lens must match the task lens exactly",
2531
- " summary 1-2 sentence description",
2532
- " affected_files [{path, line_start?, line_end?, symbol?}] - objects, not strings; min 1 entry",
2533
- " evidence [\"path/to/file.ts:42 - description of what you see there\"] - min 1 entry",
2534
- "",
2535
- "Constraints:",
2536
- "1. line_end must not exceed the file's actual line count.",
2537
- "2. affected_files entries are objects with a path key, not plain strings.",
2538
- "3. Only reference files from the packet unless a finding genuinely crosses a boundary.",
2539
- "4. findings: [] is correct when you find nothing genuine.",
2540
- "",
2541
- "## Submit",
2542
- "Pipe the JSON array on stdin to this command:",
2543
- ` ${submitCommand}`,
2544
- "",
2545
- "The command validates and writes the packet-owned result files. Exit 0 means accepted.",
2546
- "Non-zero: read the errors, fix the JSON, and run the same submit command again. Retry up to 3 times.",
2547
- "",
2548
- "## Final response",
2549
- `After the submit command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
2550
- ].join("\n");
2551
- await writeFile(promptPath, prompt, "utf8");
2552
- plan.push({
2553
- packet_id: packet.packet_id,
2554
- description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)` +
2555
- (largeFileMode ? " [isolated large-file mode]" : ""),
2556
- prompt_path: promptPath,
2557
- complexity,
2558
- model_hint: buildDispatchModelHint(complexity),
2559
- });
2560
- }
2561
- await writeJsonFile(dispatchPlanPath, plan);
2562
- await writeJsonFile(dispatchResultMapPath(runDir), {
2563
- contract_version: "audit-code-dispatch-results/v1alpha1",
2564
- run_id: runId,
2565
- entries: resultMapEntries,
2566
- });
2567
- // Compute and write dispatch-quota.json
2568
- const hostModel = params.hostModel ?? null;
2569
- const perPacketTokens = plan.map((p) => p.complexity.estimated_tokens);
2570
- const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
2571
- const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
2572
- const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
2573
- const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
2574
- const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
2575
- explicitLimit: params.hostActiveSubagentLimit,
2576
- sessionConfig,
2577
- });
2578
- const dispatchCachedLimits = await lookupDiscoveredLimits(quotaProviderKey).catch(() => null);
2579
- const waveSchedule = scheduleWave({
2580
- providerName: quotaProviderName,
2581
- sessionConfig,
2582
- hostModel,
2583
- requestedConcurrency: sessionConfig.parallel_workers ?? plan.length,
2584
- estimatedSlotTokens: perPacketTokens,
2585
- quotaStateEntry,
2586
- hostConcurrencyLimit,
2587
- discoveredLimits: dispatchCachedLimits,
2588
- });
2589
- const dispatchQuota = {
2590
- contract_version: "audit-code-dispatch-quota/v1alpha2",
2591
- run_id: runId,
2592
- model: hostModel,
2593
- resolved_limits: waveSchedule.resolved_limits,
2594
- confidence: waveSchedule.confidence,
2595
- source: waveSchedule.source,
2596
- host_concurrency_limit: waveSchedule.host_concurrency_limit,
2597
- wave_size: waveSchedule.wave_size,
2598
- estimated_wave_tokens: waveSchedule.estimated_wave_tokens,
2599
- cooldown_until: waveSchedule.cooldown_until,
2600
- quota_source_snapshot: waveSchedule.quota_source_snapshot ?? null,
2601
- backoff_state: null,
2602
- };
2603
- const dispatchQuotaPath = join(runDir, "dispatch-quota.json");
2604
- await writeJsonFile(dispatchQuotaPath, dispatchQuota);
2605
- // Warn about packets that exceed the context budget only when we have reliable limit
2606
- // information (confidence medium/high). Low-confidence limits are conservative defaults
2607
- // and would produce misleading warnings since the real context window is unknown.
2608
- if (waveSchedule.confidence !== "low") {
2609
- const contextBudget = waveSchedule.resolved_limits.context_tokens - waveSchedule.resolved_limits.output_tokens;
2610
- for (const p of plan) {
2611
- if (p.complexity.estimated_tokens > contextBudget) {
2612
- warnings.push({
2613
- code: "oversized_packet",
2614
- message: `Packet ${p.packet_id} estimated tokens (${p.complexity.estimated_tokens}) exceed ` +
2615
- `context budget (${contextBudget}). This packet may fail at dispatch. ` +
2616
- `Set quota.default_context_tokens or quota.models in session-config.json to override.`,
2617
- });
2618
- }
2619
- }
2620
- }
2621
- const warningsPath = warnings.length > 0
2622
- ? join(runDir, "dispatch-warnings.json")
2623
- : null;
2624
- if (warningsPath) {
2625
- await writeJsonFile(warningsPath, warnings);
2626
- }
2627
- const activeDispatch = {
2628
- run_id: runId,
2629
- created_at: new Date().toISOString(),
2630
- packet_count: plan.length,
2631
- task_count: orderedTasks.length,
2632
- status: "active",
2633
- };
2634
- await writeJsonFile(join(artifactsDir, ACTIVE_DISPATCH_FILENAME), activeDispatch);
2635
- return {
2636
- run_id: runId,
2637
- dispatch_plan_path: dispatchPlanPath,
2638
- dispatch_quota_path: dispatchQuotaPath,
2639
- packet_count: plan.length,
2640
- task_count: orderedTasks.length,
2641
- skipped_task_count: priorResultTaskIds.size,
2642
- largest_packet: largestPacketId
2643
- ? {
2644
- packet_id: largestPacketId,
2645
- total_lines: largestLines,
2646
- estimated_tokens: largestEstimatedTokens,
2647
- }
2648
- : null,
2649
- warning_count: warnings.length,
2650
- dispatch_warnings_path: warningsPath,
2651
- };
2652
- }
2653
1838
  async function cmdPrepareDispatch(argv) {
2654
1839
  const runId = getFlag(argv, "--run-id");
2655
1840
  if (!runId)
2656
1841
  throw new Error("prepare-dispatch requires --run-id <run_id>");
2657
1842
  const result = await prepareDispatchArtifacts({
1843
+ packageRoot,
2658
1844
  runId,
2659
1845
  artifactsDir: getArtifactsDir(argv),
2660
1846
  root: getFlag(argv, "--root") ? getRootDir(argv) : undefined,
@@ -3551,6 +2737,7 @@ async function cmdDispatchStatus(argv) {
3551
2737
  }, null, 2));
3552
2738
  }
3553
2739
  async function main(argv) {
2740
+ setQuotaStateDir(join(homedir(), ".audit-code"));
3554
2741
  const command = argv[2] ?? "sample-run";
3555
2742
  switch (command) {
3556
2743
  case "sample-run":