auditor-lambda 0.3.3 → 0.3.5
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/README.md +6 -1
- package/audit-code-wrapper-lib.mjs +87 -7
- package/dist/cli.js +517 -91
- package/dist/extractors/graph.d.ts +5 -1
- package/dist/extractors/graph.js +223 -3
- package/dist/extractors/pathPatterns.d.ts +3 -2
- package/dist/extractors/pathPatterns.js +97 -24
- package/dist/io/artifacts.d.ts +5 -0
- package/dist/io/artifacts.js +2 -0
- package/dist/orchestrator/advance.js +1 -1
- package/dist/orchestrator/dependencyMap.js +18 -0
- package/dist/orchestrator/fileAnchors.d.ts +32 -0
- package/dist/orchestrator/fileAnchors.js +217 -0
- package/dist/orchestrator/internalExecutors.d.ts +1 -1
- package/dist/orchestrator/internalExecutors.js +120 -33
- package/dist/orchestrator/reviewPackets.d.ts +14 -0
- package/dist/orchestrator/reviewPackets.js +310 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +14 -0
- package/dist/orchestrator/selectiveDeepening.js +392 -0
- package/dist/orchestrator/state.js +6 -1
- package/dist/orchestrator/taskBuilder.d.ts +16 -0
- package/dist/orchestrator/taskBuilder.js +68 -11
- package/dist/prompts/renderWorkerPrompt.js +2 -1
- package/dist/providers/claudeCodeProvider.js +3 -1
- package/dist/providers/index.js +2 -1
- package/dist/supervisor/operatorHandoff.js +22 -11
- package/dist/types/graph.d.ts +1 -0
- package/dist/types/reviewPlanning.d.ts +41 -0
- package/dist/types/reviewPlanning.js +1 -0
- package/dist/types/sessionConfig.d.ts +1 -0
- package/dist/validation/artifacts.js +13 -0
- package/dist/validation/auditResults.js +50 -2
- package/dist/validation/sessionConfig.js +5 -0
- package/docs/agent-integrations.md +4 -1
- package/docs/bootstrap-install.md +3 -0
- package/docs/contract.md +3 -0
- package/docs/dispatch-implementation-plan.md +220 -489
- package/docs/next-steps.md +13 -8
- package/docs/product-direction.md +5 -3
- package/docs/run-flow.md +25 -30
- package/docs/session-config.md +15 -4
- package/docs/supervisor.md +5 -3
- package/docs/workflow-refactor-brief.md +114 -176
- package/package.json +1 -1
- package/schemas/finding.schema.json +1 -15
- package/schemas/graph_bundle.schema.json +16 -0
- package/skills/audit-code/audit-code.prompt.md +11 -6
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { LENS_ORDER } from "./unitBuilder.js";
|
|
3
|
+
const DEFAULT_MAX_TASKS_PER_PACKET = 6;
|
|
4
|
+
const DEFAULT_TARGET_PACKET_LINES = 2500;
|
|
5
|
+
const ESTIMATED_TOKENS_PER_LINE = 4;
|
|
6
|
+
const ESTIMATED_PACKET_PROMPT_TOKENS = 900;
|
|
7
|
+
function priorityRank(priority) {
|
|
8
|
+
switch (priority) {
|
|
9
|
+
case "high":
|
|
10
|
+
return 3;
|
|
11
|
+
case "medium":
|
|
12
|
+
return 2;
|
|
13
|
+
case "low":
|
|
14
|
+
default:
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function normalizePriority(priority) {
|
|
19
|
+
return priority ?? "low";
|
|
20
|
+
}
|
|
21
|
+
function sortLenses(lenses) {
|
|
22
|
+
const set = new Set(lenses);
|
|
23
|
+
return LENS_ORDER.filter((lens) => set.has(lens));
|
|
24
|
+
}
|
|
25
|
+
function lineCountForPath(task, path, lineIndex) {
|
|
26
|
+
return task.file_line_counts?.[path] ?? lineIndex?.[path] ?? 0;
|
|
27
|
+
}
|
|
28
|
+
function taskLineCount(task, lineIndex) {
|
|
29
|
+
return task.file_paths.reduce((sum, path) => sum + lineCountForPath(task, path, lineIndex), 0);
|
|
30
|
+
}
|
|
31
|
+
function taskFileSignature(task) {
|
|
32
|
+
return [...new Set(task.file_paths)].sort((a, b) => a.localeCompare(b)).join("\0");
|
|
33
|
+
}
|
|
34
|
+
function packetGroupingKey(task) {
|
|
35
|
+
const criticalFlowTag = task.tags?.find((tag) => tag.startsWith("critical_flow:"));
|
|
36
|
+
const scope = criticalFlowTag ?? task.unit_id;
|
|
37
|
+
return `${scope}\0${taskFileSignature(task)}`;
|
|
38
|
+
}
|
|
39
|
+
function normalizeGraphPath(path) {
|
|
40
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
function collectGraphEdges(graphBundle) {
|
|
46
|
+
if (!graphBundle?.graphs) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const edges = [];
|
|
50
|
+
for (const key of ["imports", "calls", "references"]) {
|
|
51
|
+
const raw = graphBundle.graphs[key];
|
|
52
|
+
if (!Array.isArray(raw)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const item of raw) {
|
|
56
|
+
if (!isRecord(item)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (typeof item.from !== "string" || typeof item.to !== "string") {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
edges.push({
|
|
63
|
+
from: item.from,
|
|
64
|
+
to: item.to,
|
|
65
|
+
kind: typeof item.kind === "string" ? item.kind : undefined,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return edges;
|
|
70
|
+
}
|
|
71
|
+
function isPacketExpansionEdge(edge) {
|
|
72
|
+
return edge.kind !== "heuristic-container-edge";
|
|
73
|
+
}
|
|
74
|
+
function sanitizeSegment(value) {
|
|
75
|
+
const sanitized = value
|
|
76
|
+
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
77
|
+
.replace(/^-+|-+$/g, "");
|
|
78
|
+
return sanitized.length > 0 ? sanitized : "packet";
|
|
79
|
+
}
|
|
80
|
+
function packetIdFor(tasks, packetIndex) {
|
|
81
|
+
const unit = sanitizeSegment(tasks[0]?.unit_id ?? "review");
|
|
82
|
+
const lenses = sortLenses(tasks.map((task) => task.lens)).join("-");
|
|
83
|
+
const hash = createHash("sha1")
|
|
84
|
+
.update(tasks.map((task) => task.task_id).join("\0"))
|
|
85
|
+
.digest("hex")
|
|
86
|
+
.slice(0, 10);
|
|
87
|
+
return `${unit}:${lenses}:packet-${packetIndex + 1}-${hash}`;
|
|
88
|
+
}
|
|
89
|
+
function compareTasksForPacket(a, b) {
|
|
90
|
+
const lensDelta = LENS_ORDER.indexOf(a.lens) - LENS_ORDER.indexOf(b.lens);
|
|
91
|
+
if (lensDelta !== 0)
|
|
92
|
+
return lensDelta;
|
|
93
|
+
return a.task_id.localeCompare(b.task_id);
|
|
94
|
+
}
|
|
95
|
+
function comparePackets(a, b) {
|
|
96
|
+
const priorityDelta = priorityRank(b.priority) - priorityRank(a.priority);
|
|
97
|
+
if (priorityDelta !== 0)
|
|
98
|
+
return priorityDelta;
|
|
99
|
+
const sizeDelta = b.task_ids.length - a.task_ids.length;
|
|
100
|
+
if (sizeDelta !== 0)
|
|
101
|
+
return sizeDelta;
|
|
102
|
+
return a.packet_id.localeCompare(b.packet_id);
|
|
103
|
+
}
|
|
104
|
+
function chunkPacketTasks(tasks, options) {
|
|
105
|
+
const chunks = [];
|
|
106
|
+
let current = [];
|
|
107
|
+
for (const task of tasks.sort(compareTasksForPacket)) {
|
|
108
|
+
const isolatedLargeFileTask = task.file_paths.length === 1 &&
|
|
109
|
+
taskLineCount(task, options.lineIndex) > options.targetPacketLines;
|
|
110
|
+
if (isolatedLargeFileTask) {
|
|
111
|
+
if (current.length > 0) {
|
|
112
|
+
chunks.push(current);
|
|
113
|
+
current = [];
|
|
114
|
+
}
|
|
115
|
+
chunks.push([task]);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const candidate = [...current, task];
|
|
119
|
+
const uniquePaths = new Set(candidate.flatMap((item) => item.file_paths));
|
|
120
|
+
const candidateLines = [...uniquePaths].reduce((sum, path) => {
|
|
121
|
+
const owner = candidate.find((item) => item.file_paths.includes(path));
|
|
122
|
+
return sum + (owner ? lineCountForPath(owner, path, options.lineIndex) : 0);
|
|
123
|
+
}, 0);
|
|
124
|
+
const wouldExceedTaskCount = current.length > 0 && candidate.length > options.maxTasksPerPacket;
|
|
125
|
+
const wouldExceedLines = current.length > 0 && candidateLines > options.targetPacketLines;
|
|
126
|
+
if (wouldExceedTaskCount || wouldExceedLines) {
|
|
127
|
+
chunks.push(current);
|
|
128
|
+
current = [];
|
|
129
|
+
}
|
|
130
|
+
current.push(task);
|
|
131
|
+
}
|
|
132
|
+
if (current.length > 0) {
|
|
133
|
+
chunks.push(current);
|
|
134
|
+
}
|
|
135
|
+
return chunks;
|
|
136
|
+
}
|
|
137
|
+
function mergeGraphConnectedGroups(groups, graphBundle) {
|
|
138
|
+
const groupKeys = [...groups.keys()];
|
|
139
|
+
const parent = new Map(groupKeys.map((key) => [key, key]));
|
|
140
|
+
const fileToGroupKeys = new Map();
|
|
141
|
+
function find(key) {
|
|
142
|
+
const current = parent.get(key) ?? key;
|
|
143
|
+
if (current === key)
|
|
144
|
+
return key;
|
|
145
|
+
const root = find(current);
|
|
146
|
+
parent.set(key, root);
|
|
147
|
+
return root;
|
|
148
|
+
}
|
|
149
|
+
function union(a, b) {
|
|
150
|
+
const rootA = find(a);
|
|
151
|
+
const rootB = find(b);
|
|
152
|
+
if (rootA === rootB)
|
|
153
|
+
return;
|
|
154
|
+
const [keep, move] = rootA.localeCompare(rootB) <= 0 ? [rootA, rootB] : [rootB, rootA];
|
|
155
|
+
parent.set(move, keep);
|
|
156
|
+
}
|
|
157
|
+
for (const [key, tasks] of groups) {
|
|
158
|
+
for (const path of new Set(tasks.flatMap((task) => task.file_paths))) {
|
|
159
|
+
const normalized = normalizeGraphPath(path);
|
|
160
|
+
const existing = fileToGroupKeys.get(normalized) ?? new Set();
|
|
161
|
+
existing.add(key);
|
|
162
|
+
fileToGroupKeys.set(normalized, existing);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const keys of fileToGroupKeys.values()) {
|
|
166
|
+
const [first, ...rest] = [...keys].sort((a, b) => a.localeCompare(b));
|
|
167
|
+
if (!first)
|
|
168
|
+
continue;
|
|
169
|
+
for (const key of rest) {
|
|
170
|
+
union(first, key);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const edge of collectGraphEdges(graphBundle)) {
|
|
174
|
+
if (!isPacketExpansionEdge(edge)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const fromGroups = fileToGroupKeys.get(normalizeGraphPath(edge.from));
|
|
178
|
+
const toGroups = fileToGroupKeys.get(normalizeGraphPath(edge.to));
|
|
179
|
+
if (!fromGroups || !toGroups) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
for (const fromKey of fromGroups) {
|
|
183
|
+
for (const toKey of toGroups) {
|
|
184
|
+
union(fromKey, toKey);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const merged = new Map();
|
|
189
|
+
for (const key of groupKeys) {
|
|
190
|
+
const root = find(key);
|
|
191
|
+
const current = merged.get(root) ?? [];
|
|
192
|
+
current.push(...(groups.get(key) ?? []));
|
|
193
|
+
merged.set(root, current);
|
|
194
|
+
}
|
|
195
|
+
return [...merged.values()];
|
|
196
|
+
}
|
|
197
|
+
function buildPacket(tasks, packetIndex, lineIndex) {
|
|
198
|
+
const filePaths = [...new Set(tasks.flatMap((task) => task.file_paths))].sort((a, b) => a.localeCompare(b));
|
|
199
|
+
const fileLineCounts = Object.fromEntries(filePaths.map((path) => {
|
|
200
|
+
const owner = tasks.find((task) => task.file_paths.includes(path));
|
|
201
|
+
return [path, owner ? lineCountForPath(owner, path, lineIndex) : 0];
|
|
202
|
+
}));
|
|
203
|
+
const totalLines = Object.values(fileLineCounts).reduce((sum, value) => sum + value, 0);
|
|
204
|
+
const priority = tasks.reduce((highest, task) => priorityRank(task.priority) > priorityRank(highest)
|
|
205
|
+
? normalizePriority(task.priority)
|
|
206
|
+
: highest, "low");
|
|
207
|
+
const lenses = sortLenses(tasks.map((task) => task.lens));
|
|
208
|
+
const tags = [
|
|
209
|
+
...new Set(tasks.flatMap((task) => task.tags ?? [])),
|
|
210
|
+
].sort((a, b) => a.localeCompare(b));
|
|
211
|
+
return {
|
|
212
|
+
packet_id: packetIdFor(tasks, packetIndex),
|
|
213
|
+
task_ids: tasks.map((task) => task.task_id),
|
|
214
|
+
unit_ids: [...new Set(tasks.map((task) => task.unit_id))].sort((a, b) => a.localeCompare(b)),
|
|
215
|
+
pass_ids: [...new Set(tasks.map((task) => task.pass_id))].sort((a, b) => a.localeCompare(b)),
|
|
216
|
+
lenses,
|
|
217
|
+
file_paths: filePaths,
|
|
218
|
+
file_line_counts: fileLineCounts,
|
|
219
|
+
total_lines: totalLines,
|
|
220
|
+
priority,
|
|
221
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
222
|
+
rationale: tasks.length === 1
|
|
223
|
+
? tasks[0].rationale
|
|
224
|
+
: `Review ${filePaths.length} related file(s) across ${lenses.length} lens(es): ${lenses.join(", ")}.`,
|
|
225
|
+
estimated_tokens: ESTIMATED_PACKET_PROMPT_TOKENS + totalLines * ESTIMATED_TOKENS_PER_LINE,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export function buildReviewPackets(tasks, options = {}) {
|
|
229
|
+
const maxTasksPerPacket = options.maxTasksPerPacket ?? DEFAULT_MAX_TASKS_PER_PACKET;
|
|
230
|
+
const targetPacketLines = options.targetPacketLines ?? DEFAULT_TARGET_PACKET_LINES;
|
|
231
|
+
const groups = new Map();
|
|
232
|
+
for (const task of tasks) {
|
|
233
|
+
const key = packetGroupingKey(task);
|
|
234
|
+
const group = groups.get(key) ?? [];
|
|
235
|
+
group.push(task);
|
|
236
|
+
groups.set(key, group);
|
|
237
|
+
}
|
|
238
|
+
const packets = [];
|
|
239
|
+
let packetIndex = 0;
|
|
240
|
+
const groupedTasks = mergeGraphConnectedGroups(groups, options.graphBundle).sort((a, b) => {
|
|
241
|
+
const aPriority = Math.max(...a.map((task) => priorityRank(task.priority)));
|
|
242
|
+
const bPriority = Math.max(...b.map((task) => priorityRank(task.priority)));
|
|
243
|
+
if (aPriority !== bPriority)
|
|
244
|
+
return bPriority - aPriority;
|
|
245
|
+
return (a[0]?.task_id ?? "").localeCompare(b[0]?.task_id ?? "");
|
|
246
|
+
});
|
|
247
|
+
for (const group of groupedTasks) {
|
|
248
|
+
for (const chunk of chunkPacketTasks(group, {
|
|
249
|
+
lineIndex: options.lineIndex,
|
|
250
|
+
maxTasksPerPacket,
|
|
251
|
+
targetPacketLines,
|
|
252
|
+
})) {
|
|
253
|
+
packets.push(buildPacket(chunk, packetIndex, options.lineIndex));
|
|
254
|
+
packetIndex += 1;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return packets.sort(comparePackets);
|
|
258
|
+
}
|
|
259
|
+
export function orderTasksForPacketReview(tasks, options = {}) {
|
|
260
|
+
const taskById = new Map(tasks.map((task) => [task.task_id, task]));
|
|
261
|
+
return buildReviewPackets(tasks, options).flatMap((packet) => packet.task_ids
|
|
262
|
+
.map((taskId) => taskById.get(taskId))
|
|
263
|
+
.filter((task) => task !== undefined));
|
|
264
|
+
}
|
|
265
|
+
export function buildAuditPlanMetrics(tasks, options = {}) {
|
|
266
|
+
const packets = buildReviewPackets(tasks, options);
|
|
267
|
+
const taskLineCounts = tasks.map((task) => taskLineCount(task, options.lineIndex));
|
|
268
|
+
const totalTaskLines = taskLineCounts.reduce((sum, value) => sum + value, 0);
|
|
269
|
+
const totalPacketLines = packets.reduce((sum, packet) => sum + packet.total_lines, 0);
|
|
270
|
+
const largestTaskIndex = taskLineCounts.reduce((largest, value, index) => (value > taskLineCounts[largest] ? index : largest), 0);
|
|
271
|
+
const largestPacket = packets.reduce((largest, packet) => !largest || packet.total_lines > largest.total_lines ? packet : largest, undefined);
|
|
272
|
+
const taskFileReferences = tasks.reduce((sum, task) => sum + task.file_paths.length, 0);
|
|
273
|
+
const uniqueFiles = new Set(tasks.flatMap((task) => task.file_paths));
|
|
274
|
+
const lensTaskCounts = {};
|
|
275
|
+
const priorityTaskCounts = {
|
|
276
|
+
high: 0,
|
|
277
|
+
medium: 0,
|
|
278
|
+
low: 0,
|
|
279
|
+
};
|
|
280
|
+
for (const task of tasks) {
|
|
281
|
+
lensTaskCounts[task.lens] = (lensTaskCounts[task.lens] ?? 0) + 1;
|
|
282
|
+
priorityTaskCounts[normalizePriority(task.priority)] += 1;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
generated_at: (options.generatedAt ?? new Date()).toISOString(),
|
|
286
|
+
task_count: tasks.length,
|
|
287
|
+
packet_count: packets.length,
|
|
288
|
+
estimated_agent_reduction: Math.max(0, tasks.length - packets.length),
|
|
289
|
+
estimated_agent_reduction_ratio: tasks.length === 0 ? 0 : Math.max(0, tasks.length - packets.length) / tasks.length,
|
|
290
|
+
unique_file_count: uniqueFiles.size,
|
|
291
|
+
task_file_reference_count: taskFileReferences,
|
|
292
|
+
repeated_file_reference_count: Math.max(0, taskFileReferences - uniqueFiles.size),
|
|
293
|
+
total_task_lines: totalTaskLines,
|
|
294
|
+
total_packet_lines: totalPacketLines,
|
|
295
|
+
repeated_line_reference_count: Math.max(0, totalTaskLines - totalPacketLines),
|
|
296
|
+
min_task_lines: taskLineCounts.length > 0 ? Math.min(...taskLineCounts) : 0,
|
|
297
|
+
max_task_lines: taskLineCounts.length > 0 ? Math.max(...taskLineCounts) : 0,
|
|
298
|
+
average_task_lines: taskLineCounts.length > 0 ? totalTaskLines / taskLineCounts.length : 0,
|
|
299
|
+
largest_task_id: tasks[largestTaskIndex]?.task_id,
|
|
300
|
+
largest_packet_id: largestPacket?.packet_id,
|
|
301
|
+
lens_task_counts: lensTaskCounts,
|
|
302
|
+
priority_task_counts: priorityTaskCounts,
|
|
303
|
+
packet_size: {
|
|
304
|
+
single_task_packets: packets.filter((packet) => packet.task_ids.length === 1).length,
|
|
305
|
+
multi_task_packets: packets.filter((packet) => packet.task_ids.length > 1).length,
|
|
306
|
+
max_tasks_per_packet: packets.length > 0 ? Math.max(...packets.map((packet) => packet.task_ids.length)) : 0,
|
|
307
|
+
max_files_per_packet: packets.length > 0 ? Math.max(...packets.map((packet) => packet.file_paths.length)) : 0,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuditResult, AuditTask } from "../types.js";
|
|
2
|
+
import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
|
|
3
|
+
export interface BuildSelectiveDeepeningTaskOptions {
|
|
4
|
+
existingTasks?: AuditTask[];
|
|
5
|
+
results: AuditResult[];
|
|
6
|
+
lineIndex?: Record<string, number>;
|
|
7
|
+
runtimeValidationTasks?: RuntimeValidationTaskManifest;
|
|
8
|
+
runtimeValidationReport?: RuntimeValidationReport;
|
|
9
|
+
maxTasks?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildSelectiveDeepeningTasks(options: BuildSelectiveDeepeningTaskOptions): AuditTask[];
|
|
12
|
+
export declare const selectiveDeepeningTestUtils: {
|
|
13
|
+
DEEPENING_TAG: string;
|
|
14
|
+
};
|