auditor-lambda 0.3.34 → 0.3.36
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/dist/cli.js +13 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +2 -0
- package/dist/orchestrator/selectiveDeepening.js +10 -1
- package/dist/orchestrator/state.js +2 -17
- package/dist/providers/opencodeProvider.js +23 -3
- package/dist/providers/spawnLoggedCommand.js +0 -5
- package/dist/reporting/mergeFindings.js +115 -23
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1282,12 +1282,25 @@ async function cmdRunToCompletion(argv) {
|
|
|
1282
1282
|
let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
|
|
1283
1283
|
let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
|
|
1284
1284
|
let runCount = 0;
|
|
1285
|
+
let deepeningCycles = 0;
|
|
1286
|
+
const MAX_DEEPENING_CYCLES = 3;
|
|
1285
1287
|
let anyProgress = false;
|
|
1286
1288
|
let lastResult = null;
|
|
1287
1289
|
const artifactsWritten = new Set();
|
|
1288
1290
|
while (runCount < maxRuns) {
|
|
1289
1291
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1290
1292
|
const decision = decideNextStep(bundle);
|
|
1293
|
+
if (decision.selected_executor === "agent" &&
|
|
1294
|
+
bundle.audit_tasks?.some((t) => t.tags?.includes("selective_deepening") &&
|
|
1295
|
+
t.status !== "complete") &&
|
|
1296
|
+
!bundle.audit_tasks?.some((t) => !t.tags?.includes("selective_deepening") &&
|
|
1297
|
+
t.status !== "complete")) {
|
|
1298
|
+
deepeningCycles++;
|
|
1299
|
+
if (deepeningCycles > MAX_DEEPENING_CYCLES) {
|
|
1300
|
+
process.stderr.write(`[audit-code] Reached max deepening cycles (${MAX_DEEPENING_CYCLES}). Stopping to prevent churn.\n`);
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1291
1304
|
let preferredExecutor = decision.selected_executor;
|
|
1292
1305
|
let obligationId = decision.selected_obligation;
|
|
1293
1306
|
let auditResultsPath;
|
|
@@ -9,10 +9,12 @@ export interface BuildSelectiveDeepeningTaskOptions {
|
|
|
9
9
|
runtimeValidationReport?: RuntimeValidationReport;
|
|
10
10
|
externalAnalyzerResults?: ExternalAnalyzerResults;
|
|
11
11
|
maxTasks?: number;
|
|
12
|
+
maxTotalDeepeningTasks?: number;
|
|
12
13
|
}
|
|
13
14
|
export declare function buildSelectiveDeepeningTasks(options: BuildSelectiveDeepeningTaskOptions): AuditTask[];
|
|
14
15
|
export declare const selectiveDeepeningTestUtils: {
|
|
15
16
|
DEEPENING_TAG: string;
|
|
16
17
|
LENS_VERIFICATION_TAG: string;
|
|
17
18
|
LENS_VERIFICATION_FOLLOWUP_TAG: string;
|
|
19
|
+
DEFAULT_MAX_TOTAL_DEEPENING_TASKS: number;
|
|
18
20
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
const DEFAULT_MAX_DEEPENING_TASKS = 6;
|
|
3
|
+
const DEFAULT_MAX_TOTAL_DEEPENING_TASKS = 24;
|
|
3
4
|
const DEEPENING_TAG = "selective_deepening";
|
|
4
5
|
const LENS_VERIFICATION_TAG = "lens_verification";
|
|
5
6
|
const LENS_VERIFICATION_FOLLOWUP_TAG = "lens_verification_followup";
|
|
@@ -649,9 +650,16 @@ export function buildSelectiveDeepeningTasks(options) {
|
|
|
649
650
|
const existingTasks = options.existingTasks ?? [];
|
|
650
651
|
const existingIds = new Set(taskById.keys());
|
|
651
652
|
const maxTasks = options.maxTasks ?? DEFAULT_MAX_DEEPENING_TASKS;
|
|
653
|
+
const maxTotalDeepeningTasks = options.maxTotalDeepeningTasks ?? DEFAULT_MAX_TOTAL_DEEPENING_TASKS;
|
|
654
|
+
const existingDeepeningCount = existingTasks.filter((task) => isDeepeningTask(task)).length;
|
|
655
|
+
if (existingDeepeningCount >= maxTotalDeepeningTasks) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
const remainingBudget = maxTotalDeepeningTasks - existingDeepeningCount;
|
|
659
|
+
const effectiveMax = Math.min(maxTasks, remainingBudget);
|
|
652
660
|
const created = [];
|
|
653
661
|
function pushIfNew(task) {
|
|
654
|
-
if (created.length >=
|
|
662
|
+
if (created.length >= effectiveMax || existingIds.has(task.task_id)) {
|
|
655
663
|
return;
|
|
656
664
|
}
|
|
657
665
|
existingIds.add(task.task_id);
|
|
@@ -748,4 +756,5 @@ export const selectiveDeepeningTestUtils = {
|
|
|
748
756
|
DEEPENING_TAG,
|
|
749
757
|
LENS_VERIFICATION_TAG,
|
|
750
758
|
LENS_VERIFICATION_FOLLOWUP_TAG,
|
|
759
|
+
DEFAULT_MAX_TOTAL_DEEPENING_TASKS,
|
|
751
760
|
};
|
|
@@ -42,31 +42,16 @@ export function deriveAuditState(bundle) {
|
|
|
42
42
|
"audit_tasks.json",
|
|
43
43
|
"requeue_tasks.json",
|
|
44
44
|
], planningReady)));
|
|
45
|
-
const hasRequiredCoverage = bundle.coverage_matrix?.files.every((f) => f.required_lenses.every((req) => f.completed_lenses.includes(req))) ?? true;
|
|
46
45
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
47
46
|
const hasPendingAuditTasks = bundle.audit_tasks?.some((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id)) ?? false;
|
|
48
|
-
const hasCompletedTaskStatuses = bundle.audit_tasks?.length
|
|
49
|
-
? bundle.audit_tasks.every((task) => task.status === "complete")
|
|
50
|
-
: false;
|
|
51
|
-
const hasResultForEveryTask = bundle.audit_tasks?.length && bundle.audit_results
|
|
52
|
-
? bundle.audit_tasks.every((task) => bundle.audit_results?.some((result) => result.task_id === task.task_id))
|
|
53
|
-
: false;
|
|
54
47
|
if (hasPendingAuditTasks) {
|
|
55
48
|
obligations.push(obligation("audit_tasks_completed", "missing"));
|
|
56
49
|
}
|
|
57
|
-
else if (
|
|
58
|
-
!hasCompletedTaskStatuses &&
|
|
59
|
-
!hasResultForEveryTask &&
|
|
60
|
-
has(bundle.audit_tasks) &&
|
|
61
|
-
(bundle.audit_tasks?.length ?? 0) > 0) {
|
|
62
|
-
obligations.push(obligation("audit_tasks_completed", "missing"));
|
|
63
|
-
}
|
|
64
|
-
else if ((hasRequiredCoverage || hasCompletedTaskStatuses || hasResultForEveryTask) &&
|
|
65
|
-
has(bundle.audit_tasks)) {
|
|
50
|
+
else if (has(bundle.audit_tasks)) {
|
|
66
51
|
obligations.push(obligation("audit_tasks_completed", "satisfied"));
|
|
67
52
|
}
|
|
68
53
|
obligations.push(obligation("audit_results_ingested", (bundle.audit_tasks?.length ?? 0) === 0 || has(bundle.audit_results)
|
|
69
|
-
? "
|
|
54
|
+
? "satisfied"
|
|
70
55
|
: "missing"));
|
|
71
56
|
const runtimeTasks = bundle.runtime_validation_tasks?.tasks ?? [];
|
|
72
57
|
const runtimeResults = bundle.runtime_validation_report?.results ?? [];
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { spawnLoggedCommand } from "./spawnLoggedCommand.js";
|
|
3
|
+
function resolveOpenCodeSpawnCommand(command, args, platform = process.platform, shellCommand = process.env.ComSpec ?? "cmd.exe") {
|
|
4
|
+
if (platform !== "win32") {
|
|
5
|
+
return { command, args };
|
|
6
|
+
}
|
|
7
|
+
const base = command.replace(/\.(cmd|bat|exe)$/i, "").toLowerCase();
|
|
8
|
+
if (base === "opencode" || base === "npx" || command.endsWith(".cmd")) {
|
|
9
|
+
return {
|
|
10
|
+
command: shellCommand,
|
|
11
|
+
args: ["/d", "/s", "/c", [command, ...args].map(quoteCmdArg).join(" ")],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { command, args };
|
|
15
|
+
}
|
|
16
|
+
function quoteCmdArg(value) {
|
|
17
|
+
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return `"${value.replace(/(["^&|<>%])/g, "^$1")}"`;
|
|
21
|
+
}
|
|
3
22
|
export class OpenCodeProvider {
|
|
4
23
|
name = "opencode";
|
|
5
24
|
config;
|
|
@@ -8,8 +27,9 @@ export class OpenCodeProvider {
|
|
|
8
27
|
}
|
|
9
28
|
async launch(input) {
|
|
10
29
|
const prompt = await readFile(input.promptPath, "utf8");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
30
|
+
const baseCommand = this.config.command ?? "opencode";
|
|
31
|
+
const baseArgs = ["run", prompt, ...(this.config.extra_args ?? [])];
|
|
32
|
+
const resolved = resolveOpenCodeSpawnCommand(baseCommand, baseArgs);
|
|
33
|
+
return await spawnLoggedCommand(resolved.command, resolved.args, input);
|
|
14
34
|
}
|
|
15
35
|
}
|
|
@@ -152,13 +152,8 @@ export async function spawnLoggedCommand(command, args, input, env, options = {}
|
|
|
152
152
|
});
|
|
153
153
|
spawnedChild.on("error", fail);
|
|
154
154
|
spawnedChild.on("exit", (code, signal) => {
|
|
155
|
-
if (!timedOut) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
childClosed = true;
|
|
159
155
|
closeCode = code;
|
|
160
156
|
closeSignal = signal;
|
|
161
|
-
maybeSettleFromClose();
|
|
162
157
|
});
|
|
163
158
|
spawnedChild.on("close", (code, signal) => {
|
|
164
159
|
childClosed = true;
|
|
@@ -92,6 +92,78 @@ function mergeAffectedFiles(existing, incoming) {
|
|
|
92
92
|
}
|
|
93
93
|
existing.affected_files.sort((a, b) => a.path.localeCompare(b.path) || (a.line_start ?? 0) - (b.line_start ?? 0));
|
|
94
94
|
}
|
|
95
|
+
function absorbFinding(survivor, absorbed) {
|
|
96
|
+
mergeAffectedFiles(survivor, absorbed);
|
|
97
|
+
survivor.evidence = [
|
|
98
|
+
...new Set([
|
|
99
|
+
...(survivor.evidence ?? []),
|
|
100
|
+
...(absorbed.evidence ?? []),
|
|
101
|
+
]),
|
|
102
|
+
];
|
|
103
|
+
survivor.systemic = Boolean(survivor.systemic || absorbed.systemic);
|
|
104
|
+
if (absorbed.summary.length > survivor.summary.length) {
|
|
105
|
+
survivor.summary = absorbed.summary;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function lineRangeOverlaps(a, b) {
|
|
109
|
+
const aFile = a.affected_files[0];
|
|
110
|
+
const bFile = b.affected_files[0];
|
|
111
|
+
if (!aFile || !bFile)
|
|
112
|
+
return false;
|
|
113
|
+
if (aFile.path !== bFile.path)
|
|
114
|
+
return false;
|
|
115
|
+
const aStart = aFile.line_start ?? 0;
|
|
116
|
+
const aEnd = aFile.line_end ?? aStart;
|
|
117
|
+
const bStart = bFile.line_start ?? 0;
|
|
118
|
+
const bEnd = bFile.line_end ?? bStart;
|
|
119
|
+
if (aEnd === 0 && bEnd === 0)
|
|
120
|
+
return true;
|
|
121
|
+
return aStart <= bEnd && bStart <= aEnd;
|
|
122
|
+
}
|
|
123
|
+
function deduplicateSameLens(findings) {
|
|
124
|
+
const groups = new Map();
|
|
125
|
+
for (const finding of findings) {
|
|
126
|
+
const key = `${normalizeText(finding.lens)}:${primaryPath(finding)}`;
|
|
127
|
+
const group = groups.get(key);
|
|
128
|
+
if (group) {
|
|
129
|
+
group.push(finding);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
groups.set(key, [finding]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const removed = new Set();
|
|
136
|
+
for (const group of groups.values()) {
|
|
137
|
+
if (group.length < 2)
|
|
138
|
+
continue;
|
|
139
|
+
for (let i = 0; i < group.length; i++) {
|
|
140
|
+
if (removed.has(group[i]))
|
|
141
|
+
continue;
|
|
142
|
+
for (let j = i + 1; j < group.length; j++) {
|
|
143
|
+
if (removed.has(group[j]))
|
|
144
|
+
continue;
|
|
145
|
+
const a = group[i];
|
|
146
|
+
const b = group[j];
|
|
147
|
+
const titleSim = wordJaccard(a.title, b.title);
|
|
148
|
+
const catMatch = normalizeText(a.category) === normalizeText(b.category);
|
|
149
|
+
const threshold = catMatch ? 0.35 : 0.45;
|
|
150
|
+
if (titleSim < threshold)
|
|
151
|
+
continue;
|
|
152
|
+
if (!lineRangeOverlaps(a, b) && filePathOverlap(a, b) < 0.5)
|
|
153
|
+
continue;
|
|
154
|
+
const aSev = severityRank(a.severity);
|
|
155
|
+
const bSev = severityRank(b.severity);
|
|
156
|
+
const aConf = confidenceRank(a.confidence);
|
|
157
|
+
const bConf = confidenceRank(b.confidence);
|
|
158
|
+
const keepA = aSev > bSev || (aSev === bSev && aConf >= bConf);
|
|
159
|
+
const [survivor, absorbed] = keepA ? [a, b] : [b, a];
|
|
160
|
+
absorbFinding(survivor, absorbed);
|
|
161
|
+
removed.add(absorbed);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return findings.filter((f) => !removed.has(f));
|
|
166
|
+
}
|
|
95
167
|
function deduplicateCrossLens(findings) {
|
|
96
168
|
const groups = new Map();
|
|
97
169
|
for (const finding of findings) {
|
|
@@ -131,27 +203,41 @@ function deduplicateCrossLens(findings) {
|
|
|
131
203
|
const bConf = confidenceRank(b.confidence);
|
|
132
204
|
const keepA = aSev > bSev || (aSev === bSev && aConf >= bConf);
|
|
133
205
|
const [survivor, absorbed] = keepA ? [a, b] : [b, a];
|
|
134
|
-
|
|
135
|
-
survivor.evidence = [
|
|
136
|
-
...new Set([
|
|
137
|
-
...(survivor.evidence ?? []),
|
|
138
|
-
...(absorbed.evidence ?? []),
|
|
139
|
-
]),
|
|
140
|
-
];
|
|
141
|
-
survivor.systemic = Boolean(survivor.systemic || absorbed.systemic);
|
|
142
|
-
if (absorbed.summary.length > survivor.summary.length) {
|
|
143
|
-
survivor.summary = absorbed.summary;
|
|
144
|
-
}
|
|
206
|
+
absorbFinding(survivor, absorbed);
|
|
145
207
|
removed.add(absorbed);
|
|
146
208
|
}
|
|
147
209
|
}
|
|
148
210
|
}
|
|
149
211
|
return findings.filter((f) => !removed.has(f));
|
|
150
212
|
}
|
|
213
|
+
function relevantRuntimeEvidence(finding, report) {
|
|
214
|
+
if (!report)
|
|
215
|
+
return [];
|
|
216
|
+
const findingPaths = new Set(finding.affected_files.map((f) => f.path));
|
|
217
|
+
return report.results
|
|
218
|
+
.filter((result) => result.status !== "pending")
|
|
219
|
+
.filter((result) => {
|
|
220
|
+
const taskPaths = result.notes
|
|
221
|
+
?.flatMap((note) => {
|
|
222
|
+
const match = note.match(/Target paths:\s*(.+)/);
|
|
223
|
+
return match ? match[1].split(",").map((p) => p.trim()) : [];
|
|
224
|
+
}) ?? [];
|
|
225
|
+
if (taskPaths.length === 0)
|
|
226
|
+
return true;
|
|
227
|
+
return taskPaths.some((p) => findingPaths.has(p));
|
|
228
|
+
})
|
|
229
|
+
.map((result) => `${result.task_id}: ${result.status} — ${result.summary}`);
|
|
230
|
+
}
|
|
231
|
+
function relevantExternalEvidence(finding, results) {
|
|
232
|
+
if (!results)
|
|
233
|
+
return [];
|
|
234
|
+
const findingPaths = new Set(finding.affected_files.map((f) => f.path));
|
|
235
|
+
return results.results
|
|
236
|
+
.filter((item) => findingPaths.has(item.path))
|
|
237
|
+
.map((item) => `external:${results.tool}:${item.path}:${item.summary}`);
|
|
238
|
+
}
|
|
151
239
|
export function mergeFindings(results, runtimeReport, externalAnalyzerResults) {
|
|
152
240
|
const merged = new Map();
|
|
153
|
-
const runtimeEvidence = runtimeSummary(runtimeReport);
|
|
154
|
-
const analyzerEvidence = externalSummary(externalAnalyzerResults);
|
|
155
241
|
for (const result of results) {
|
|
156
242
|
for (const finding of result.findings) {
|
|
157
243
|
const key = findingKey(finding);
|
|
@@ -160,13 +246,7 @@ export function mergeFindings(results, runtimeReport, externalAnalyzerResults) {
|
|
|
160
246
|
merged.set(key, {
|
|
161
247
|
...finding,
|
|
162
248
|
affected_files: [...finding.affected_files],
|
|
163
|
-
evidence: [
|
|
164
|
-
...new Set([
|
|
165
|
-
...(finding.evidence ?? []),
|
|
166
|
-
...runtimeEvidence,
|
|
167
|
-
...analyzerEvidence,
|
|
168
|
-
]),
|
|
169
|
-
],
|
|
249
|
+
evidence: [...(finding.evidence ?? [])],
|
|
170
250
|
});
|
|
171
251
|
continue;
|
|
172
252
|
}
|
|
@@ -188,13 +268,25 @@ export function mergeFindings(results, runtimeReport, externalAnalyzerResults) {
|
|
|
188
268
|
...new Set([
|
|
189
269
|
...(existing.evidence ?? []),
|
|
190
270
|
...(finding.evidence ?? []),
|
|
191
|
-
...runtimeEvidence,
|
|
192
|
-
...analyzerEvidence,
|
|
193
271
|
]),
|
|
194
272
|
];
|
|
195
273
|
}
|
|
196
274
|
}
|
|
197
|
-
|
|
275
|
+
for (const finding of merged.values()) {
|
|
276
|
+
const runtimeEv = relevantRuntimeEvidence(finding, runtimeReport);
|
|
277
|
+
const externalEv = relevantExternalEvidence(finding, externalAnalyzerResults);
|
|
278
|
+
if (runtimeEv.length > 0 || externalEv.length > 0) {
|
|
279
|
+
finding.evidence = [
|
|
280
|
+
...new Set([
|
|
281
|
+
...(finding.evidence ?? []),
|
|
282
|
+
...runtimeEv,
|
|
283
|
+
...externalEv,
|
|
284
|
+
]),
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const dedupedSameLens = deduplicateSameLens([...merged.values()]);
|
|
289
|
+
return deduplicateCrossLens(dedupedSameLens).sort((a, b) => {
|
|
198
290
|
const severityDelta = severityRank(b.severity) - severityRank(a.severity);
|
|
199
291
|
if (severityDelta !== 0)
|
|
200
292
|
return severityDelta;
|