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