auditor-lambda 0.9.0 → 0.9.2
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 +5 -0
- package/dispatch/merge-results.mjs +1 -1
- package/dist/cli/mergeAndIngestCommand.js +45 -8
- package/dist/cli/nextStepCommand.js +40 -14
- package/dist/cli/reviewRun.js +1 -1
- package/dist/cli/runToCompletion.js +21 -8
- package/dist/supervisor/operatorHandoff.js +6 -1
- package/package.json +2 -2
|
@@ -1277,6 +1277,11 @@ async function copyClaudeDesktopBundleFiles(bundleRoot, serverRoot) {
|
|
|
1277
1277
|
await mkdir(serverRoot, { recursive: true });
|
|
1278
1278
|
await cp(distEntry.replace(/dist[\\/]index\.js$/, 'dist'), join(bundleRoot, 'dist'), { recursive: true, force: true });
|
|
1279
1279
|
await cp(join(repoRoot, 'schemas'), join(bundleRoot, 'schemas'), { recursive: true, force: true });
|
|
1280
|
+
// dist/cli/dispatch.js reads dispatch/lens-definitions.json from the package
|
|
1281
|
+
// root at runtime, so the bundle must ship the top-level dispatch/ data dir
|
|
1282
|
+
// (lens-definitions.json + the standalone validate/merge helpers) the same way
|
|
1283
|
+
// the npm `files` array does. Omitting it ENOENTs the first dispatch step.
|
|
1284
|
+
await cp(join(repoRoot, 'dispatch'), join(bundleRoot, 'dispatch'), { recursive: true, force: true });
|
|
1280
1285
|
await cp(join(repoRoot, 'skills', 'audit-code'), join(bundleRoot, 'skills', 'audit-code'), { recursive: true, force: true });
|
|
1281
1286
|
await writeFile(join(bundleRoot, 'audit-code.mjs'), await readFile(join(repoRoot, 'audit-code.mjs')));
|
|
1282
1287
|
await writeFile(join(bundleRoot, 'audit-code-wrapper-lib.mjs'), await readFile(join(repoRoot, 'audit-code-wrapper-lib.mjs')));
|
|
@@ -15,7 +15,7 @@ const artifactsDir = artifactsDirIdx !== -1 && process.argv[artifactsDirIdx + 1]
|
|
|
15
15
|
: join(process.cwd(), ".audit-artifacts");
|
|
16
16
|
|
|
17
17
|
const taskResultsDir = join(artifactsDir, "runs", runId, "task-results");
|
|
18
|
-
const auditResultsPath = join(artifactsDir, "runs", runId, "
|
|
18
|
+
const auditResultsPath = join(artifactsDir, "runs", runId, "run-results.json");
|
|
19
19
|
const failedTasksPath = join(artifactsDir, "runs", runId, "failed-tasks.json");
|
|
20
20
|
const tasksPath = join(artifactsDir, "runs", runId, "pending-audit-tasks.json");
|
|
21
21
|
|
|
@@ -14,9 +14,28 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
14
14
|
const artifactsDir = getArtifactsDir(argv);
|
|
15
15
|
const runDir = join(artifactsDir, "runs", runId);
|
|
16
16
|
const taskResultsDir = join(runDir, "task-results");
|
|
17
|
-
const auditResultsPath = join(runDir, "
|
|
17
|
+
const auditResultsPath = join(runDir, "run-results.json");
|
|
18
18
|
const taskPath = join(runDir, "task.json");
|
|
19
19
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
20
|
+
const mergeCompletePath = join(runDir, "merge-complete.json");
|
|
21
|
+
// Idempotency: a fully-merged run is terminal. A stray re-invocation for the
|
|
22
|
+
// same run-id (e.g. after the run already advanced to the next deepening
|
|
23
|
+
// round, which rewrites this run dir's pending-audit-tasks.json to the *next*
|
|
24
|
+
// round's tasks) must be a clean no-op — not a spurious "all results missing"
|
|
25
|
+
// hard failure that also truncates the transient results file. Replay the
|
|
26
|
+
// recorded summary and exit 0.
|
|
27
|
+
let priorSummary = null;
|
|
28
|
+
try {
|
|
29
|
+
priorSummary = await readJsonFile(mergeCompletePath);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (!isFileMissingError(e))
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
if (priorSummary) {
|
|
36
|
+
console.log(JSON.stringify({ ...priorSummary, idempotent_replay: true }, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
20
39
|
const workerTask = await readJsonFile(taskPath);
|
|
21
40
|
const resultMap = await loadDispatchResultMap(runDir);
|
|
22
41
|
if (!resultMap) {
|
|
@@ -42,7 +61,7 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
42
61
|
const passing = [];
|
|
43
62
|
const failing = [];
|
|
44
63
|
const seenTaskIds = new Set();
|
|
45
|
-
|
|
64
|
+
const spuriousFiles = [];
|
|
46
65
|
const fallbackByTaskId = new Map();
|
|
47
66
|
for (const filename of files) {
|
|
48
67
|
const filePath = resolve(join(taskResultsDir, filename));
|
|
@@ -68,10 +87,16 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
68
87
|
// task-results/ dir are legitimate and must not inflate the count or bury
|
|
69
88
|
// the real stray-file signal (3 -> 191 over a run before this fix).
|
|
70
89
|
if (!isCanonicalResultFilename(filename)) {
|
|
71
|
-
|
|
72
|
-
process.stderr.write(`[merge-and-ingest] Warning: unexpected file in task-results/: ${filename}\n`);
|
|
90
|
+
spuriousFiles.push(filename);
|
|
73
91
|
}
|
|
74
92
|
}
|
|
93
|
+
// Collapse stray-file warnings into a single stderr line so the real summary
|
|
94
|
+
// (emitted as the sole stdout JSON payload) is never buried under a wall of
|
|
95
|
+
// per-file warnings.
|
|
96
|
+
if (spuriousFiles.length > 0) {
|
|
97
|
+
process.stderr.write(`[merge-and-ingest] Warning: ${spuriousFiles.length} unexpected file(s) in ` +
|
|
98
|
+
`task-results/ ignored: ${spuriousFiles.join(", ")}\n`);
|
|
99
|
+
}
|
|
75
100
|
for (const task of allTasks) {
|
|
76
101
|
const entry = entryByTaskId.get(task.task_id);
|
|
77
102
|
if (!entry) {
|
|
@@ -134,14 +159,18 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
134
159
|
failing.push({ task_id: taskId ?? task.task_id, errors: resultErrors });
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
|
-
await writeJsonFile(auditResultsPath, passing);
|
|
138
162
|
const failedTasksPath = join(runDir, "failed-tasks.json");
|
|
139
163
|
if (failing.length > 0) {
|
|
140
164
|
await writeJsonFile(failedTasksPath, failing);
|
|
141
165
|
}
|
|
142
166
|
if (passing.length === 0 && failing.length > 0) {
|
|
167
|
+
// Nothing merged and at least one failure: a blocked no-op. Do NOT write the
|
|
168
|
+
// transient results file here — truncating it to [] reads as catastrophic
|
|
169
|
+
// data loss on a re-run when the cumulative audit_results.jsonl store is in
|
|
170
|
+
// fact intact and the first merge had simply already succeeded.
|
|
143
171
|
throw new Error(`All ${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
|
|
144
172
|
}
|
|
173
|
+
await writeJsonFile(auditResultsPath, passing);
|
|
145
174
|
const findingCount = passing.reduce((sum, result) => sum + result.findings.length, 0);
|
|
146
175
|
let result = null;
|
|
147
176
|
if (passing.length > 0) {
|
|
@@ -197,12 +226,12 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
197
226
|
errors: [],
|
|
198
227
|
});
|
|
199
228
|
await writeJsonFile(workerTask.result_path, workerResult);
|
|
200
|
-
|
|
229
|
+
const summaryPayload = {
|
|
201
230
|
run_id: runId,
|
|
202
231
|
status,
|
|
203
232
|
accepted_count: passing.length,
|
|
204
233
|
rejected_count: failing.length,
|
|
205
|
-
spurious_file_count:
|
|
234
|
+
spurious_file_count: spuriousFiles.length,
|
|
206
235
|
finding_count: findingCount,
|
|
207
236
|
audit_results_path: auditResultsPath,
|
|
208
237
|
...(retryDispatchPath ? { retry_dispatch_path: retryDispatchPath } : {}),
|
|
@@ -212,7 +241,15 @@ export async function cmdMergeAndIngest(argv) {
|
|
|
212
241
|
progress_summary: workerResult.summary,
|
|
213
242
|
next_likely_step: workerResult.next_likely_step,
|
|
214
243
|
} : {}),
|
|
215
|
-
}
|
|
244
|
+
};
|
|
245
|
+
// Record a completion marker for a fully-merged run so a stray re-invocation
|
|
246
|
+
// replays this summary (above) instead of re-processing — and possibly
|
|
247
|
+
// clobbering — terminal state. Only on full success: a partial merge is meant
|
|
248
|
+
// to be re-run after the failed packets are retried, so it stays replayable.
|
|
249
|
+
if (failing.length === 0) {
|
|
250
|
+
await writeJsonFile(mergeCompletePath, summaryPayload);
|
|
251
|
+
}
|
|
252
|
+
console.log(JSON.stringify(summaryPayload, null, 2));
|
|
216
253
|
if (failing.length > 0) {
|
|
217
254
|
process.exitCode = 2;
|
|
218
255
|
}
|
|
@@ -35,6 +35,42 @@ async function runDeterministicForNextStep(params) {
|
|
|
35
35
|
const FINALIZATION_CYCLE_TOLERANCE = 16;
|
|
36
36
|
const seenStateSignatures = new Set();
|
|
37
37
|
const obligationTrail = [];
|
|
38
|
+
// Build the terminal step for a deterministic loop that has stopped advancing
|
|
39
|
+
// (hit the run backstop or the finalization cycle guard). A rendered report is
|
|
40
|
+
// the deliverable: if synthesis already produced one — or the state is formally
|
|
41
|
+
// complete — present it instead of reporting the stopped loop as a bare
|
|
42
|
+
// "blocked" failure. A completed audit must never surface as blocked just
|
|
43
|
+
// because finalization kept churning (e.g. a runtime_validation <-> synthesis
|
|
44
|
+
// ping-pong, or revision churn from filesystem retries) after the report was
|
|
45
|
+
// written. With no report yet, the stop is a genuine block.
|
|
46
|
+
async function terminalStep(bundle, state, blockedReason) {
|
|
47
|
+
const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
|
|
48
|
+
await writeHandoffOnly({
|
|
49
|
+
root: params.root,
|
|
50
|
+
artifactsDir: params.artifactsDir,
|
|
51
|
+
bundle,
|
|
52
|
+
audit_state: state,
|
|
53
|
+
progress_summary: reportRendered && state.status !== "complete"
|
|
54
|
+
? `Audit report already rendered; ending run. ${blockedReason}`
|
|
55
|
+
: blockedReason,
|
|
56
|
+
providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
|
|
57
|
+
});
|
|
58
|
+
if (!reportRendered) {
|
|
59
|
+
return { kind: "blocked", state, bundle, reason: blockedReason };
|
|
60
|
+
}
|
|
61
|
+
const promoted = await promoteFinalAuditReport({
|
|
62
|
+
artifactsDir: params.artifactsDir,
|
|
63
|
+
repoRoot: params.root,
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
kind: "complete",
|
|
67
|
+
state,
|
|
68
|
+
bundle,
|
|
69
|
+
finalReportPath: promoted.promoted
|
|
70
|
+
? join(params.root, AUDIT_REPORT_FILENAME)
|
|
71
|
+
: join(params.artifactsDir, AUDIT_REPORT_FILENAME),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
38
74
|
for (let index = 0; index < params.maxRuns; index++) {
|
|
39
75
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
40
76
|
const decision = decideNextStep(bundle);
|
|
@@ -318,24 +354,14 @@ async function runDeterministicForNextStep(params) {
|
|
|
318
354
|
`progress; stopping. Cycling obligations: ${cycle.join(" -> ")}.`,
|
|
319
355
|
timestamp: new Date().toISOString(),
|
|
320
356
|
});
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
bundle: result.updated_bundle,
|
|
325
|
-
reason: "Finalization is not converging: deterministic executors kept revisiting " +
|
|
326
|
-
`prior artifact states (${cycle.join(" -> ")}). The report has been ` +
|
|
327
|
-
"rendered; review whether these obligations are erroneously invalidating each other.",
|
|
328
|
-
};
|
|
357
|
+
return await terminalStep(result.updated_bundle, result.audit_state, "Finalization is not converging: deterministic executors kept revisiting " +
|
|
358
|
+
`prior artifact states (${cycle.join(" -> ")}). Review whether these ` +
|
|
359
|
+
"obligations are erroneously invalidating each other.");
|
|
329
360
|
}
|
|
330
361
|
}
|
|
331
362
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
332
363
|
const state = deriveAuditState(bundle);
|
|
333
|
-
return {
|
|
334
|
-
kind: "blocked",
|
|
335
|
-
state,
|
|
336
|
-
bundle,
|
|
337
|
-
reason: `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`,
|
|
338
|
-
};
|
|
364
|
+
return await terminalStep(bundle, state, `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`);
|
|
339
365
|
}
|
|
340
366
|
export async function cmdNextStep(argv) {
|
|
341
367
|
const root = getRootDir(argv);
|
package/dist/cli/reviewRun.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function ensureSemanticReviewRun(params) {
|
|
|
90
90
|
const paths = getRunPaths(params.artifactsDir, runId);
|
|
91
91
|
const pendingTasks = await addFileLineCountHints(params.root, buildPendingAuditTasks(params.bundle));
|
|
92
92
|
const pendingTasksPath = join(paths.runDir, "pending-audit-tasks.json");
|
|
93
|
-
const auditResultsPath = join(paths.runDir, "
|
|
93
|
+
const auditResultsPath = join(paths.runDir, "run-results.json");
|
|
94
94
|
const taskReadPaths = new Set();
|
|
95
95
|
for (const pt of pendingTasks) {
|
|
96
96
|
for (const fp of pt.file_paths)
|
|
@@ -70,7 +70,7 @@ async function buildParallelWaveSlots(params) {
|
|
|
70
70
|
runCount += 1;
|
|
71
71
|
const slotRunId = buildRunId(obligationId, runCount);
|
|
72
72
|
const slotPaths = getRunPaths(artifactsDir, slotRunId);
|
|
73
|
-
const slotAuditResultsPath = join(slotPaths.runDir, "
|
|
73
|
+
const slotAuditResultsPath = join(slotPaths.runDir, "run-results.json");
|
|
74
74
|
const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
|
|
75
75
|
const slotReadPaths = new Set();
|
|
76
76
|
for (const t of group) {
|
|
@@ -398,7 +398,7 @@ async function runSingleWorkerStep(params) {
|
|
|
398
398
|
? join(paths.runDir, "pending-audit-tasks.json")
|
|
399
399
|
: undefined;
|
|
400
400
|
const providerAuditResultsPath = preferredExecutor === "agent"
|
|
401
|
-
? join(paths.runDir, "
|
|
401
|
+
? join(paths.runDir, "run-results.json")
|
|
402
402
|
: auditResultsPath;
|
|
403
403
|
const providerReadPaths = new Set();
|
|
404
404
|
if (pendingAuditTasks) {
|
|
@@ -694,7 +694,7 @@ export async function cmdRunToCompletion(argv) {
|
|
|
694
694
|
const blockPaths = getRunPaths(artifactsDir, blockRunId);
|
|
695
695
|
const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
|
|
696
696
|
const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
|
|
697
|
-
const blockAuditResultsPath = join(blockPaths.runDir, "
|
|
697
|
+
const blockAuditResultsPath = join(blockPaths.runDir, "run-results.json");
|
|
698
698
|
const blockReadPaths = new Set();
|
|
699
699
|
for (const pt of blockPendingTasks) {
|
|
700
700
|
for (const fp of pt.file_paths)
|
|
@@ -1031,23 +1031,36 @@ export async function cmdRunToCompletion(argv) {
|
|
|
1031
1031
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1032
1032
|
const decision = decideNextStep(bundle);
|
|
1033
1033
|
const state = decision.state;
|
|
1034
|
-
if
|
|
1034
|
+
// A rendered report is the deliverable: if synthesis already produced one (or
|
|
1035
|
+
// the state is formally complete), finish the run on it instead of stranding
|
|
1036
|
+
// it in the artifacts dir behind a bare "max run limit" non-completion. This
|
|
1037
|
+
// mirrors next-step's terminalStep so both loops present a completed audit the
|
|
1038
|
+
// same way, even when finalization churned (runtime_validation <-> synthesis
|
|
1039
|
+
// ping-pong, or filesystem-retry revision churn) up to the backstop. With no
|
|
1040
|
+
// report yet, the run limit is a genuine non-terminal stop.
|
|
1041
|
+
const reportRendered = state.status === "complete" || Boolean(bundle.audit_report);
|
|
1042
|
+
if (reportRendered) {
|
|
1035
1043
|
await clearDispatchFiles(artifactsDir);
|
|
1036
1044
|
}
|
|
1045
|
+
const terminalState = reportRendered && state.status !== "complete"
|
|
1046
|
+
? { ...state, status: "complete" }
|
|
1047
|
+
: state;
|
|
1037
1048
|
await emitEnvelope({
|
|
1038
1049
|
root,
|
|
1039
1050
|
artifactsDir,
|
|
1040
1051
|
bundle,
|
|
1041
|
-
audit_state:
|
|
1052
|
+
audit_state: terminalState,
|
|
1042
1053
|
selected_obligation: lastResult?.obligation_id ?? decision.selected_obligation,
|
|
1043
1054
|
selected_executor: lastResult?.selected_executor ?? decision.selected_executor,
|
|
1044
1055
|
progress_made: anyProgress,
|
|
1045
1056
|
artifacts_written: Array.from(artifactsWritten),
|
|
1046
|
-
progress_summary:
|
|
1047
|
-
|
|
1057
|
+
progress_summary: reportRendered && state.status !== "complete"
|
|
1058
|
+
? `Audit report already rendered; completing the run after reaching the max run limit (${maxRuns}) during finalization.`
|
|
1059
|
+
: `Reached max run limit (${maxRuns}) before terminal state.`,
|
|
1060
|
+
next_likely_step: reportRendered ? null : decision.selected_obligation,
|
|
1048
1061
|
providerName: provider.name,
|
|
1049
1062
|
});
|
|
1050
|
-
if (
|
|
1063
|
+
if (reportRendered) {
|
|
1051
1064
|
await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
|
|
1052
1065
|
}
|
|
1053
1066
|
}
|
|
@@ -252,7 +252,12 @@ export function buildAuditCodeHandoff(params) {
|
|
|
252
252
|
current_task: artifactPaths.current_task,
|
|
253
253
|
current_prompt: artifactPaths.current_prompt,
|
|
254
254
|
audit_results: params.activeReviewRun.audit_results_path,
|
|
255
|
-
|
|
255
|
+
// Synthesis writes the report into the artifacts dir; it is only promoted
|
|
256
|
+
// to <repo-root>/audit-report.md at completion (which then removes the
|
|
257
|
+
// artifacts dir). A blocked-for-review handoff happens before that, so the
|
|
258
|
+
// advertised deliverable must point at its real mid-run location, not the
|
|
259
|
+
// repo-root path that does not exist yet.
|
|
260
|
+
final_report: join(params.artifactsDir, AUDIT_REPORT_FILENAME),
|
|
256
261
|
};
|
|
257
262
|
}
|
|
258
263
|
return handoff;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auditor-lambda",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Portable hybrid code-auditing framework for arbitrary repositories.",
|
|
6
6
|
"type": "module",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
|
-
"homepage": "https://github.com/OhOkThisIsFine/audit-tools/tree/
|
|
56
|
+
"homepage": "https://github.com/OhOkThisIsFine/audit-tools/tree/main/packages/audit-code#readme",
|
|
57
57
|
"bugs": {
|
|
58
58
|
"url": "https://github.com/OhOkThisIsFine/audit-tools/issues"
|
|
59
59
|
},
|