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.
- package/audit-code-wrapper-lib.mjs +20 -2
- package/dist/cli/args.d.ts +59 -0
- package/dist/cli/args.js +244 -0
- package/dist/cli/dispatch.d.ts +80 -0
- package/dist/cli/dispatch.js +528 -0
- package/dist/cli/prompts.d.ts +18 -0
- package/dist/cli/prompts.js +130 -0
- package/dist/cli/steps.d.ts +29 -0
- package/dist/cli/steps.js +30 -0
- package/dist/cli/waveManifest.d.ts +40 -0
- package/dist/cli/waveManifest.js +41 -0
- package/dist/cli/workerResult.d.ts +18 -0
- package/dist/cli/workerResult.js +42 -0
- package/dist/cli.d.ts +2 -22
- package/dist/cli.js +160 -973
- package/dist/extractors/browserExtension.d.ts +1 -3
- package/dist/extractors/browserExtension.js +2 -2
- package/dist/extractors/designAssessment.d.ts +1 -3
- package/dist/extractors/disposition.d.ts +2 -1
- package/dist/extractors/disposition.js +3 -0
- package/dist/extractors/flows.d.ts +1 -3
- package/dist/extractors/flows.js +2 -2
- package/dist/extractors/graph.d.ts +1 -2
- package/dist/extractors/graph.js +4 -326
- package/dist/extractors/graphManifestEdges.d.ts +1 -1
- package/dist/extractors/graphPathUtils.d.ts +1 -1
- package/dist/extractors/graphPythonImports.d.ts +3 -0
- package/dist/extractors/graphPythonImports.js +326 -0
- package/dist/extractors/risk.d.ts +1 -2
- package/dist/extractors/surfaces.d.ts +1 -3
- package/dist/extractors/surfaces.js +2 -2
- package/dist/io/artifacts.d.ts +1 -5
- package/dist/io/artifacts.js +1 -1
- package/dist/io/runArtifacts.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/orchestrator/advance.d.ts +1 -0
- package/dist/orchestrator/advance.js +8 -5
- package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
- package/dist/orchestrator/auditTaskUtils.js +27 -0
- package/dist/orchestrator/fileAnchors.d.ts +1 -1
- package/dist/orchestrator/fileIntegrity.d.ts +7 -0
- package/dist/orchestrator/fileIntegrity.js +41 -0
- package/dist/orchestrator/flowCoverage.d.ts +1 -1
- package/dist/orchestrator/flowPlanning.d.ts +1 -1
- package/dist/orchestrator/flowRequeue.d.ts +1 -1
- package/dist/orchestrator/internalExecutors.d.ts +3 -1
- package/dist/orchestrator/internalExecutors.js +23 -5
- package/dist/orchestrator/nextStep.d.ts +2 -1
- package/dist/orchestrator/nextStep.js +1 -1
- package/dist/orchestrator/planning.d.ts +1 -1
- package/dist/orchestrator/requeueCommand.d.ts +1 -1
- package/dist/orchestrator/reviewPackets.d.ts +1 -1
- package/dist/orchestrator/reviewPackets.js +21 -113
- package/dist/orchestrator/runtimeValidation.d.ts +1 -1
- package/dist/orchestrator/taskBuilder.d.ts +1 -1
- package/dist/orchestrator/taskBuilder.js +1 -12
- package/dist/orchestrator/unionFind.d.ts +7 -0
- package/dist/orchestrator/unionFind.js +32 -0
- package/dist/orchestrator/unitBuilder.d.ts +2 -2
- package/dist/orchestrator/unitBuilder.js +4 -18
- package/dist/prompts/renderWorkerPrompt.js +18 -1
- package/dist/providers/claudeCodeProvider.d.ts +4 -4
- package/dist/providers/claudeCodeProvider.js +9 -3
- package/dist/providers/constants.d.ts +1 -1
- package/dist/providers/constants.js +1 -1
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.js +5 -4
- package/dist/providers/localSubprocessProvider.d.ts +2 -2
- package/dist/providers/localSubprocessProvider.js +1 -1
- package/dist/providers/opencodeProvider.d.ts +4 -4
- package/dist/providers/opencodeProvider.js +7 -2
- package/dist/providers/spawnLoggedCommand.d.ts +3 -1
- package/dist/providers/spawnLoggedCommand.js +21 -0
- package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
- package/dist/providers/subprocessTemplateProvider.js +8 -3
- package/dist/providers/vscodeTaskProvider.d.ts +3 -4
- package/dist/providers/vscodeTaskProvider.js +2 -2
- package/dist/quota/discoveredLimits.js +1 -1
- package/dist/quota/hostLimits.d.ts +1 -2
- package/dist/quota/hostLimits.js +4 -46
- package/dist/quota/index.d.ts +18 -15
- package/dist/quota/index.js +4 -9
- package/dist/quota/scheduler.d.ts +1 -3
- package/dist/quota/scheduler.js +1 -2
- package/dist/reporting/synthesis.d.ts +1 -2
- package/dist/reporting/synthesis.js +2 -0
- package/dist/reporting/workBlocks.d.ts +1 -2
- package/dist/supervisor/operatorHandoff.js +1 -1
- package/dist/supervisor/runLedger.d.ts +1 -1
- package/dist/supervisor/runLedger.js +2 -2
- package/dist/supervisor/sessionConfig.d.ts +1 -1
- package/dist/supervisor/sessionConfig.js +1 -3
- package/dist/types/reviewPlanning.d.ts +1 -1
- package/dist/types/workerSession.d.ts +6 -0
- package/dist/validation/artifacts.d.ts +1 -1
- package/dist/validation/artifacts.js +1 -1
- package/dist/validation/auditResults.d.ts +1 -1
- package/dist/validation/auditResults.js +1 -1
- package/dist/validation/sessionConfig.d.ts +2 -3
- package/dist/validation/sessionConfig.js +2 -3
- package/package.json +4 -2
- package/scripts/postinstall.mjs +0 -1
- package/dist/io/json.d.ts +0 -10
- package/dist/io/json.js +0 -142
- package/dist/providers/types.d.ts +0 -33
- package/dist/providers/types.js +0 -1
- package/dist/quota/compositeQuotaSource.d.ts +0 -7
- package/dist/quota/compositeQuotaSource.js +0 -20
- package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
- package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
- package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
- package/dist/quota/errorParsers/genericErrorParser.js +0 -7
- package/dist/quota/errorParsers/index.d.ts +0 -5
- package/dist/quota/errorParsers/index.js +0 -12
- package/dist/quota/errorParsing.d.ts +0 -7
- package/dist/quota/errorParsing.js +0 -69
- package/dist/quota/fileLock.d.ts +0 -6
- package/dist/quota/fileLock.js +0 -64
- package/dist/quota/learnedQuotaSource.d.ts +0 -7
- package/dist/quota/learnedQuotaSource.js +0 -25
- package/dist/quota/limits.d.ts +0 -16
- package/dist/quota/limits.js +0 -77
- package/dist/quota/quotaSource.d.ts +0 -12
- package/dist/quota/quotaSource.js +0 -1
- package/dist/quota/slidingWindow.d.ts +0 -4
- package/dist/quota/slidingWindow.js +0 -28
- package/dist/quota/state.d.ts +0 -15
- package/dist/quota/state.js +0 -148
- package/dist/quota/types.d.ts +0 -67
- package/dist/quota/types.js +0 -1
- package/dist/reporting/rootCause.d.ts +0 -10
- package/dist/reporting/rootCause.js +0 -146
- package/dist/types/disposition.d.ts +0 -9
- package/dist/types/disposition.js +0 -1
- package/dist/types/flows.d.ts +0 -17
- package/dist/types/flows.js +0 -1
- package/dist/types/graph.d.ts +0 -22
- package/dist/types/graph.js +0 -1
- package/dist/types/risk.d.ts +0 -9
- package/dist/types/risk.js +0 -1
- package/dist/types/runLedger.d.ts +0 -17
- package/dist/types/runLedger.js +0 -6
- package/dist/types/sessionConfig.d.ts +0 -79
- package/dist/types/sessionConfig.js +0 -15
- package/dist/types/surfaces.d.ts +0 -15
- package/dist/types/surfaces.js +0 -1
- package/dist/validation/basic.d.ts +0 -13
- package/dist/validation/basic.js +0 -46
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, rename, rm, unlink
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 "
|
|
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 {
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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":
|