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 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 >= maxTasks || existingIds.has(task.task_id)) {
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 (!hasRequiredCoverage &&
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
- ? "present"
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 command = this.config.command ?? "opencode";
12
- const args = ["run", prompt, ...(this.config.extra_args ?? [])];
13
- return await spawnLoggedCommand(command, args, input);
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
- mergeAffectedFiles(survivor, absorbed);
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
- return deduplicateCrossLens([...merged.values()]).sort((a, b) => {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.3.34",
3
+ "version": "0.3.36",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",