auditor-lambda 0.1.0 → 0.2.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/README.md +2 -1
- package/audit-code-wrapper-lib.mjs +205 -187
- package/dist/adapters/eslint.js +4 -2
- package/dist/adapters/npmAudit.js +1 -1
- package/dist/cli.js +296 -12
- package/dist/coverage.d.ts +0 -1
- package/dist/coverage.js +3 -34
- package/dist/extractors/bucketing.js +14 -35
- package/dist/extractors/disposition.js +8 -9
- package/dist/extractors/flows.js +14 -23
- package/dist/extractors/pathPatterns.d.ts +19 -0
- package/dist/extractors/pathPatterns.js +87 -0
- package/dist/extractors/surfaces.js +2 -7
- package/dist/io/artifacts.d.ts +23 -1
- package/dist/io/artifacts.js +3 -1
- package/dist/io/runArtifacts.js +1 -1
- package/dist/orchestrator/advance.js +1 -1
- package/dist/orchestrator/flowPlanning.d.ts +1 -1
- package/dist/orchestrator/flowPlanning.js +21 -28
- package/dist/orchestrator/internalExecutors.js +4 -7
- package/dist/orchestrator/planning.js +12 -20
- package/dist/orchestrator/resultIngestion.js +3 -2
- package/dist/orchestrator/runtimeValidation.js +5 -0
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
- package/dist/orchestrator/taskBuilder.d.ts +7 -2
- package/dist/orchestrator/taskBuilder.js +47 -52
- package/dist/prompts/renderWorkerPrompt.js +33 -0
- package/dist/providers/claudeCodeProvider.js +5 -0
- package/dist/providers/constants.d.ts +1 -0
- package/dist/providers/constants.js +1 -0
- package/dist/providers/index.js +9 -2
- package/dist/providers/spawnLoggedCommand.js +4 -0
- package/dist/reporting/mergeFindings.js +0 -7
- package/dist/reporting/rootCause.d.ts +0 -1
- package/dist/reporting/rootCause.js +0 -6
- package/dist/reporting/synthesis.js +18 -0
- package/dist/supervisor/operatorHandoff.d.ts +2 -0
- package/dist/supervisor/operatorHandoff.js +21 -9
- package/dist/supervisor/runLedger.js +6 -3
- package/dist/supervisor/sessionConfig.js +1 -0
- package/dist/types/flowCoverage.d.ts +1 -1
- package/dist/types/runLedger.d.ts +1 -1
- package/dist/types/runtimeValidation.d.ts +2 -1
- package/dist/types/sessionConfig.d.ts +2 -0
- package/dist/types/surfaces.d.ts +2 -1
- package/dist/types/workerSession.d.ts +4 -0
- package/dist/types.d.ts +0 -2
- package/dist/validation/auditResults.d.ts +11 -0
- package/dist/validation/auditResults.js +118 -0
- package/docs/agent-integrations.md +61 -56
- package/docs/agent-roles.md +69 -69
- package/docs/architecture.md +90 -90
- package/docs/artifacts.md +69 -69
- package/docs/bootstrap-install.md +1 -1
- package/docs/model-selection.md +86 -86
- package/docs/next-steps.md +11 -9
- package/docs/packaging.md +3 -3
- package/docs/pipeline.md +152 -152
- package/docs/production-readiness.md +6 -5
- package/docs/repo-layout.md +18 -18
- package/docs/run-flow.md +5 -5
- package/docs/session-config.md +216 -210
- package/docs/supervisor.md +70 -70
- package/docs/windows-setup.md +139 -139
- package/package.json +56 -56
- package/schemas/audit-code-v1alpha1.schema.json +80 -76
- package/schemas/audit_result.schema.json +54 -48
- package/schemas/audit_state.schema.json +2 -2
- package/schemas/audit_task.schema.json +60 -49
- package/schemas/blind_spot_register.schema.json +13 -3
- package/schemas/coverage_matrix.schema.json +14 -17
- package/schemas/critical_flows.schema.json +6 -3
- package/schemas/external_analyzer_results.schema.json +10 -4
- package/schemas/file_disposition.schema.json +33 -33
- package/schemas/finding.schema.json +86 -62
- package/schemas/flow_coverage.schema.json +53 -44
- package/schemas/graph_bundle.schema.json +12 -6
- package/schemas/merged_findings.schema.json +7 -2
- package/schemas/risk_register.schema.json +5 -1
- package/schemas/root_cause_clusters.schema.json +2 -5
- package/schemas/runtime_validation_report.schema.json +34 -34
- package/schemas/runtime_validation_tasks.schema.json +4 -1
- package/schemas/surface_manifest.schema.json +4 -1
- package/schemas/synthesis_report.schema.json +61 -61
- package/schemas/unit_manifest.schema.json +10 -3
- package/skills/audit-code/SKILL.md +37 -37
- package/skills/audit-code/audit-code.prompt.md +54 -54
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { buildRepoManifest } from "./extractors/fileInventory.js";
|
|
5
5
|
import { buildFileDisposition } from "./extractors/disposition.js";
|
|
6
6
|
import { buildCriticalFlowManifest } from "./extractors/flows.js";
|
|
@@ -12,6 +12,7 @@ import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
|
|
|
12
12
|
import { loadArtifactBundle, writeCoreArtifacts, } from "./io/artifacts.js";
|
|
13
13
|
import { readJsonFile, writeJsonFile } from "./io/json.js";
|
|
14
14
|
import { validateArtifactBundle } from "./validation/artifacts.js";
|
|
15
|
+
import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
|
|
15
16
|
import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
|
|
16
17
|
import { buildSynthesisReport } from "./reporting/synthesis.js";
|
|
17
18
|
import { deriveAuditState } from "./orchestrator/state.js";
|
|
@@ -23,8 +24,11 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
|
|
|
23
24
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
24
25
|
import { buildRunId, ensureSupervisorDirs, getRunPaths, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
25
26
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
27
|
+
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
26
28
|
const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
|
|
27
29
|
const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
|
|
30
|
+
const DEFAULT_MAX_RUNS = 1000;
|
|
31
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
28
32
|
const sampleFiles = [
|
|
29
33
|
{ path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
|
|
30
34
|
{ path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
|
|
@@ -47,8 +51,39 @@ function getRootDir(argv) {
|
|
|
47
51
|
return resolve(getFlag(argv, "--root", "."));
|
|
48
52
|
}
|
|
49
53
|
function getMaxRuns(argv) {
|
|
50
|
-
const raw = Number(getFlag(argv, "--max-runs",
|
|
51
|
-
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) :
|
|
54
|
+
const raw = Number(getFlag(argv, "--max-runs", String(DEFAULT_MAX_RUNS)));
|
|
55
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_MAX_RUNS;
|
|
56
|
+
}
|
|
57
|
+
function getAgentBatchSize(argv, sessionConfig) {
|
|
58
|
+
const fromArg = getFlag(argv, "--agent-batch-size");
|
|
59
|
+
if (fromArg !== undefined) {
|
|
60
|
+
const parsed = Number(fromArg);
|
|
61
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
62
|
+
return Math.floor(parsed);
|
|
63
|
+
}
|
|
64
|
+
if (typeof sessionConfig.agent_task_batch_size === "number" && sessionConfig.agent_task_batch_size > 0) {
|
|
65
|
+
return Math.floor(sessionConfig.agent_task_batch_size);
|
|
66
|
+
}
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
function getParallelWorkers(argv, sessionConfig) {
|
|
70
|
+
const fromArg = getFlag(argv, "--parallel");
|
|
71
|
+
if (fromArg !== undefined) {
|
|
72
|
+
const parsed = Number(fromArg);
|
|
73
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
74
|
+
return Math.floor(parsed);
|
|
75
|
+
}
|
|
76
|
+
if (typeof sessionConfig.parallel_workers === "number" && sessionConfig.parallel_workers > 0) {
|
|
77
|
+
return Math.floor(sessionConfig.parallel_workers);
|
|
78
|
+
}
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
function chunkArray(arr, size) {
|
|
82
|
+
const chunks = [];
|
|
83
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
84
|
+
chunks.push(arr.slice(i, i + size));
|
|
85
|
+
}
|
|
86
|
+
return chunks;
|
|
52
87
|
}
|
|
53
88
|
function getUiMode(argv, fallback = "headless") {
|
|
54
89
|
const raw = getFlag(argv, "--ui");
|
|
@@ -79,6 +114,7 @@ async function emitEnvelope(params) {
|
|
|
79
114
|
bundle: params.bundle,
|
|
80
115
|
providerName: params.providerName,
|
|
81
116
|
progressSummary: params.progress_summary,
|
|
117
|
+
isConfigError: params.isConfigError,
|
|
82
118
|
});
|
|
83
119
|
await writeAuditCodeHandoffArtifacts(handoff);
|
|
84
120
|
console.log(JSON.stringify(buildEnvelope({
|
|
@@ -93,7 +129,7 @@ async function emitEnvelope(params) {
|
|
|
93
129
|
}), null, 2));
|
|
94
130
|
}
|
|
95
131
|
function buildManualReviewBlocker(providerName) {
|
|
96
|
-
return providerName ===
|
|
132
|
+
return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
|
|
97
133
|
? "Automatic local-subprocess work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider such as claude-code, opencode, or subprocess-template."
|
|
98
134
|
: "Automatic work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider.";
|
|
99
135
|
}
|
|
@@ -125,8 +161,9 @@ function buildBlockedAuditState(params) {
|
|
|
125
161
|
}
|
|
126
162
|
async function countLines(path) {
|
|
127
163
|
return new Promise((resolve, reject) => {
|
|
128
|
-
let lines =
|
|
164
|
+
let lines = 0;
|
|
129
165
|
let byteCount = 0;
|
|
166
|
+
let lastByte = -1;
|
|
130
167
|
const stream = createReadStream(path);
|
|
131
168
|
stream.on("data", (chunk) => {
|
|
132
169
|
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
@@ -134,9 +171,15 @@ async function countLines(path) {
|
|
|
134
171
|
for (let i = 0; i < buffer.length; ++i) {
|
|
135
172
|
if (buffer[i] === 10)
|
|
136
173
|
lines++;
|
|
174
|
+
lastByte = buffer[i];
|
|
137
175
|
}
|
|
138
176
|
});
|
|
139
|
-
stream.on("end", () =>
|
|
177
|
+
stream.on("end", () => {
|
|
178
|
+
if (byteCount === 0)
|
|
179
|
+
return resolve(0);
|
|
180
|
+
// Files not ending with \n have one final line not counted above
|
|
181
|
+
resolve(lastByte !== 10 ? lines + 1 : lines);
|
|
182
|
+
});
|
|
140
183
|
stream.on("error", reject);
|
|
141
184
|
});
|
|
142
185
|
}
|
|
@@ -160,6 +203,34 @@ async function buildLineIndex(root, repoManifest) {
|
|
|
160
203
|
}
|
|
161
204
|
return Object.fromEntries(entries);
|
|
162
205
|
}
|
|
206
|
+
const PROJECT_SIGNALS = [
|
|
207
|
+
"package.json",
|
|
208
|
+
"go.mod",
|
|
209
|
+
"Cargo.toml",
|
|
210
|
+
"pom.xml",
|
|
211
|
+
"build.gradle",
|
|
212
|
+
"pyproject.toml",
|
|
213
|
+
"setup.py",
|
|
214
|
+
"setup.cfg",
|
|
215
|
+
"Makefile",
|
|
216
|
+
"CMakeLists.txt",
|
|
217
|
+
];
|
|
218
|
+
async function detectProjectRoot(root) {
|
|
219
|
+
for (const signal of PROJECT_SIGNALS) {
|
|
220
|
+
try {
|
|
221
|
+
await access(join(root, signal));
|
|
222
|
+
return signal;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// not found, try next
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
function buildPendingAuditTasks(bundle) {
|
|
231
|
+
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
232
|
+
return (bundle.audit_tasks ?? []).filter((task) => !completedTaskIds.has(task.task_id));
|
|
233
|
+
}
|
|
163
234
|
async function runAuditStep(options) {
|
|
164
235
|
const bundle = await loadArtifactBundle(options.artifactsDir);
|
|
165
236
|
const auditResults = options.auditResultsPath
|
|
@@ -282,10 +353,41 @@ async function cmdRunToCompletion(argv) {
|
|
|
282
353
|
const provider = createFreshSessionProvider(getFlag(argv, "--provider"), sessionConfig);
|
|
283
354
|
const uiMode = getUiMode(argv, sessionConfig.ui_mode ?? "headless");
|
|
284
355
|
const maxRuns = getMaxRuns(argv);
|
|
285
|
-
const
|
|
356
|
+
const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
|
|
357
|
+
const parallelWorkers = getParallelWorkers(argv, sessionConfig);
|
|
358
|
+
const timeoutMs = sessionConfig.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
286
359
|
const selfCliPath = resolve(process.argv[1] ?? "");
|
|
287
360
|
await mkdir(artifactsDir, { recursive: true });
|
|
288
361
|
await ensureSupervisorDirs(artifactsDir);
|
|
362
|
+
const earlyBundle = await loadArtifactBundle(artifactsDir);
|
|
363
|
+
if (!earlyBundle.unit_manifest) {
|
|
364
|
+
const foundSignal = await detectProjectRoot(root);
|
|
365
|
+
if (!foundSignal) {
|
|
366
|
+
const blocker = `No recognisable project signals found in ${root}. Expected one of: ${PROJECT_SIGNALS.join(", ")}. Check that --root points to the repository root, not a subdirectory or an unrelated path.`;
|
|
367
|
+
const earlyState = deriveAuditState(earlyBundle);
|
|
368
|
+
const blockedState = buildBlockedAuditState({
|
|
369
|
+
state: earlyState,
|
|
370
|
+
obligationId: null,
|
|
371
|
+
executor: null,
|
|
372
|
+
blocker,
|
|
373
|
+
});
|
|
374
|
+
await emitEnvelope({
|
|
375
|
+
root,
|
|
376
|
+
artifactsDir,
|
|
377
|
+
bundle: { ...earlyBundle, audit_state: blockedState },
|
|
378
|
+
audit_state: blockedState,
|
|
379
|
+
selected_obligation: null,
|
|
380
|
+
selected_executor: null,
|
|
381
|
+
progress_made: false,
|
|
382
|
+
artifacts_written: [],
|
|
383
|
+
progress_summary: blocker,
|
|
384
|
+
next_likely_step: null,
|
|
385
|
+
providerName: provider.name,
|
|
386
|
+
isConfigError: true,
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
289
391
|
let pendingAuditResultsPath = getFlag(argv, "--results");
|
|
290
392
|
let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
|
|
291
393
|
let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
|
|
@@ -316,7 +418,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
316
418
|
obligationId = "runtime_validation_current";
|
|
317
419
|
runtimeUpdatesPath = pendingRuntimeUpdatesPath;
|
|
318
420
|
}
|
|
319
|
-
if (preferredExecutor === "agent" && provider.name ===
|
|
421
|
+
if (preferredExecutor === "agent" && provider.name === LOCAL_SUBPROCESS_PROVIDER_NAME) {
|
|
320
422
|
const blocker = buildManualReviewBlocker(provider.name);
|
|
321
423
|
const blockedState = buildBlockedAuditState({
|
|
322
424
|
state: bundle.audit_state ?? decision.state,
|
|
@@ -328,6 +430,32 @@ async function cmdRunToCompletion(argv) {
|
|
|
328
430
|
...bundle,
|
|
329
431
|
audit_state: blockedState,
|
|
330
432
|
});
|
|
433
|
+
const blockRunId = buildRunId(obligationId, runCount + 1);
|
|
434
|
+
const blockPaths = getRunPaths(artifactsDir, blockRunId);
|
|
435
|
+
const blockPendingTasks = buildPendingAuditTasks(bundle).slice(0, agentBatchSize);
|
|
436
|
+
const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
|
|
437
|
+
const blockAuditResultsPath = join(blockPaths.runDir, "audit-results.json");
|
|
438
|
+
const blockTask = {
|
|
439
|
+
contract_version: "audit-code-worker/v1alpha1",
|
|
440
|
+
run_id: blockRunId,
|
|
441
|
+
repo_root: root,
|
|
442
|
+
artifacts_dir: artifactsDir,
|
|
443
|
+
obligation_id: obligationId,
|
|
444
|
+
preferred_executor: preferredExecutor,
|
|
445
|
+
result_path: blockPaths.resultPath,
|
|
446
|
+
worker_command: [
|
|
447
|
+
process.execPath,
|
|
448
|
+
selfCliPath,
|
|
449
|
+
"worker-run",
|
|
450
|
+
"--task",
|
|
451
|
+
blockPaths.taskPath,
|
|
452
|
+
],
|
|
453
|
+
audit_results_path: blockAuditResultsPath,
|
|
454
|
+
pending_audit_tasks_path: blockPendingTasksPath,
|
|
455
|
+
};
|
|
456
|
+
const blockPrompt = renderWorkerPrompt(blockTask);
|
|
457
|
+
await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir);
|
|
458
|
+
await writeJsonFile(blockPendingTasksPath, blockPendingTasks);
|
|
331
459
|
await emitEnvelope({
|
|
332
460
|
root,
|
|
333
461
|
artifactsDir,
|
|
@@ -369,9 +497,132 @@ async function cmdRunToCompletion(argv) {
|
|
|
369
497
|
});
|
|
370
498
|
return;
|
|
371
499
|
}
|
|
500
|
+
if (preferredExecutor === "agent" && parallelWorkers > 1) {
|
|
501
|
+
const allPendingTasks = buildPendingAuditTasks(bundle);
|
|
502
|
+
const taskGroups = chunkArray(allPendingTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
|
|
503
|
+
const workerSlots = [];
|
|
504
|
+
for (const group of taskGroups) {
|
|
505
|
+
runCount += 1;
|
|
506
|
+
const slotRunId = buildRunId(obligationId, runCount);
|
|
507
|
+
const slotPaths = getRunPaths(artifactsDir, slotRunId);
|
|
508
|
+
const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
|
|
509
|
+
const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
|
|
510
|
+
const slotTask = {
|
|
511
|
+
contract_version: "audit-code-worker/v1alpha1",
|
|
512
|
+
run_id: slotRunId,
|
|
513
|
+
repo_root: root,
|
|
514
|
+
artifacts_dir: artifactsDir,
|
|
515
|
+
obligation_id: obligationId,
|
|
516
|
+
preferred_executor: "agent",
|
|
517
|
+
result_path: slotPaths.resultPath,
|
|
518
|
+
worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
|
|
519
|
+
audit_results_path: slotAuditResultsPath,
|
|
520
|
+
pending_audit_tasks_path: slotPendingTasksPath,
|
|
521
|
+
skip_worker_command: true,
|
|
522
|
+
};
|
|
523
|
+
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
524
|
+
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir);
|
|
525
|
+
await writeJsonFile(slotPendingTasksPath, group);
|
|
526
|
+
workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
|
|
527
|
+
}
|
|
528
|
+
const parallelStartedAt = new Date().toISOString();
|
|
529
|
+
await Promise.allSettled(workerSlots.map((slot) => provider.launch({
|
|
530
|
+
repoRoot: root,
|
|
531
|
+
runId: slot.runId,
|
|
532
|
+
obligationId,
|
|
533
|
+
promptPath: slot.paths.promptPath,
|
|
534
|
+
taskPath: slot.paths.taskPath,
|
|
535
|
+
resultPath: slot.paths.resultPath,
|
|
536
|
+
stdoutPath: slot.paths.stdoutPath,
|
|
537
|
+
stderrPath: slot.paths.stderrPath,
|
|
538
|
+
uiMode,
|
|
539
|
+
timeoutMs,
|
|
540
|
+
})));
|
|
541
|
+
// Result ingestion is intentionally sequential even though agent launch
|
|
542
|
+
// was parallel. Writing to coverage_matrix.json is not atomic, so
|
|
543
|
+
// concurrent ingest calls would race and corrupt coverage state.
|
|
544
|
+
let batchProgress = false;
|
|
545
|
+
for (const slot of workerSlots) {
|
|
546
|
+
const parallelEndedAt = new Date().toISOString();
|
|
547
|
+
let slotStatus = "no_progress";
|
|
548
|
+
try {
|
|
549
|
+
const auditResults = await readJsonFile(slot.auditResultsPath);
|
|
550
|
+
const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
|
|
551
|
+
const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
|
|
552
|
+
if (slot.group.length > 0 && matchedCount === 0) {
|
|
553
|
+
throw new Error("Worker did not emit any audit results for the assigned tasks.");
|
|
554
|
+
}
|
|
555
|
+
const issues = validateAuditResults(auditResults, slot.group);
|
|
556
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
557
|
+
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
558
|
+
if (warnings.length > 0) {
|
|
559
|
+
process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
|
|
560
|
+
formatAuditResultIssues(warnings) + "\n");
|
|
561
|
+
}
|
|
562
|
+
if (errors.length > 0) {
|
|
563
|
+
throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
|
|
564
|
+
formatAuditResultIssues(errors));
|
|
565
|
+
}
|
|
566
|
+
const stepResult = await runAuditStep({
|
|
567
|
+
root,
|
|
568
|
+
artifactsDir,
|
|
569
|
+
preferredExecutor: "result_ingestion_executor",
|
|
570
|
+
auditResultsPath: slot.auditResultsPath,
|
|
571
|
+
});
|
|
572
|
+
slotStatus = stepResult.progress_made ? "completed" : "no_progress";
|
|
573
|
+
batchProgress ||= stepResult.progress_made;
|
|
574
|
+
if (stepResult.progress_made)
|
|
575
|
+
anyProgress = true;
|
|
576
|
+
for (const a of stepResult.artifacts_written)
|
|
577
|
+
artifactsWritten.add(a);
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
slotStatus = "failed";
|
|
581
|
+
}
|
|
582
|
+
await appendRunLedgerEntry(artifactsDir, {
|
|
583
|
+
run_id: slot.runId,
|
|
584
|
+
provider: provider.name,
|
|
585
|
+
obligation_id: obligationId,
|
|
586
|
+
selected_executor: "agent",
|
|
587
|
+
status: slotStatus,
|
|
588
|
+
started_at: parallelStartedAt,
|
|
589
|
+
ended_at: parallelEndedAt,
|
|
590
|
+
result_path: slot.paths.resultPath,
|
|
591
|
+
});
|
|
592
|
+
artifactsWritten.add("run-ledger.json");
|
|
593
|
+
}
|
|
594
|
+
if (!batchProgress) {
|
|
595
|
+
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
596
|
+
const state = bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
|
|
597
|
+
await emitEnvelope({
|
|
598
|
+
root,
|
|
599
|
+
artifactsDir,
|
|
600
|
+
bundle: bundleAfter,
|
|
601
|
+
audit_state: state,
|
|
602
|
+
selected_obligation: obligationId,
|
|
603
|
+
selected_executor: "agent",
|
|
604
|
+
progress_made: anyProgress,
|
|
605
|
+
artifacts_written: Array.from(artifactsWritten),
|
|
606
|
+
progress_summary: "Parallel worker batch made no progress.",
|
|
607
|
+
next_likely_step: obligationId,
|
|
608
|
+
providerName: provider.name,
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
372
614
|
runCount += 1;
|
|
373
615
|
const runId = buildRunId(obligationId, runCount);
|
|
374
616
|
const paths = getRunPaths(artifactsDir, runId);
|
|
617
|
+
const pendingAuditTasks = preferredExecutor === "agent"
|
|
618
|
+
? buildPendingAuditTasks(bundle).slice(0, agentBatchSize)
|
|
619
|
+
: undefined;
|
|
620
|
+
const pendingAuditTasksPath = preferredExecutor === "agent"
|
|
621
|
+
? join(paths.runDir, "pending-audit-tasks.json")
|
|
622
|
+
: undefined;
|
|
623
|
+
const providerAuditResultsPath = preferredExecutor === "agent"
|
|
624
|
+
? join(paths.runDir, "audit-results.json")
|
|
625
|
+
: auditResultsPath;
|
|
375
626
|
const task = {
|
|
376
627
|
contract_version: "audit-code-worker/v1alpha1",
|
|
377
628
|
run_id: runId,
|
|
@@ -387,12 +638,16 @@ async function cmdRunToCompletion(argv) {
|
|
|
387
638
|
"--task",
|
|
388
639
|
paths.taskPath,
|
|
389
640
|
],
|
|
390
|
-
audit_results_path:
|
|
641
|
+
audit_results_path: providerAuditResultsPath,
|
|
642
|
+
pending_audit_tasks_path: pendingAuditTasksPath,
|
|
391
643
|
runtime_updates_path: runtimeUpdatesPath,
|
|
392
644
|
external_analyzer_results_path: externalAnalyzerPath,
|
|
393
645
|
};
|
|
394
646
|
const prompt = renderWorkerPrompt(task);
|
|
395
647
|
await writeWorkerTaskFiles(task, prompt, paths, artifactsDir);
|
|
648
|
+
if (pendingAuditTasksPath && pendingAuditTasks) {
|
|
649
|
+
await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
|
|
650
|
+
}
|
|
396
651
|
const startedAt = new Date().toISOString();
|
|
397
652
|
let workerResult;
|
|
398
653
|
try {
|
|
@@ -459,7 +714,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
459
714
|
artifactsWritten.add("run-ledger.json");
|
|
460
715
|
if (externalAnalyzerPath)
|
|
461
716
|
pendingExternalAnalyzerPath = undefined;
|
|
462
|
-
if (
|
|
717
|
+
if (providerAuditResultsPath)
|
|
463
718
|
pendingAuditResultsPath = undefined;
|
|
464
719
|
if (runtimeUpdatesPath)
|
|
465
720
|
pendingRuntimeUpdatesPath = undefined;
|
|
@@ -509,10 +764,39 @@ async function cmdWorkerRun(argv) {
|
|
|
509
764
|
const task = await readJsonFile(taskPath);
|
|
510
765
|
let workerResult;
|
|
511
766
|
try {
|
|
767
|
+
if (task.preferred_executor === "agent" && !task.audit_results_path) {
|
|
768
|
+
throw new Error("agent worker-run requires audit_results_path so provider-assisted review can be ingested.");
|
|
769
|
+
}
|
|
770
|
+
if (task.preferred_executor === "agent" && task.audit_results_path) {
|
|
771
|
+
const pendingTasks = task.pending_audit_tasks_path
|
|
772
|
+
? await readJsonFile(task.pending_audit_tasks_path)
|
|
773
|
+
: [];
|
|
774
|
+
const auditResults = await readJsonFile(task.audit_results_path);
|
|
775
|
+
const pendingTaskIds = new Set(pendingTasks.map((item) => item.task_id));
|
|
776
|
+
const matchedResultCount = auditResults.filter((result) => pendingTaskIds.has(result.task_id)).length;
|
|
777
|
+
if (pendingTasks.length > 0 && matchedResultCount === 0) {
|
|
778
|
+
throw new Error("Provider-assisted review did not emit any audit results for the pending audit tasks.");
|
|
779
|
+
}
|
|
780
|
+
const issues = validateAuditResults(auditResults, pendingTasks);
|
|
781
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
782
|
+
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
783
|
+
if (warnings.length > 0) {
|
|
784
|
+
process.stderr.write(`audit-results validation: ${warnings.length} warning(s):\n` +
|
|
785
|
+
formatAuditResultIssues(warnings) +
|
|
786
|
+
"\n");
|
|
787
|
+
}
|
|
788
|
+
if (errors.length > 0) {
|
|
789
|
+
throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
|
|
790
|
+
formatAuditResultIssues(errors));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const preferredExecutor = task.preferred_executor === "agent"
|
|
794
|
+
? "result_ingestion_executor"
|
|
795
|
+
: task.preferred_executor;
|
|
512
796
|
const result = await runAuditStep({
|
|
513
797
|
root: task.repo_root,
|
|
514
798
|
artifactsDir: task.artifacts_dir,
|
|
515
|
-
preferredExecutor
|
|
799
|
+
preferredExecutor,
|
|
516
800
|
auditResultsPath: task.audit_results_path,
|
|
517
801
|
runtimeUpdatesPath: task.runtime_updates_path,
|
|
518
802
|
externalAnalyzerPath: task.external_analyzer_results_path,
|
package/dist/coverage.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CoverageFileRecord, CoverageMatrix, Lens, ReviewedLineRange } from "./types.js";
|
|
2
|
-
export declare function mergeRanges(ranges: ReviewedLineRange[]): ReviewedLineRange[];
|
|
3
2
|
export declare function createCoverageMatrix(paths: string[]): CoverageMatrix;
|
|
4
3
|
export declare function markExcludedPath(matrix: CoverageMatrix, path: string, classificationStatus: string): void;
|
|
5
4
|
export declare function applyUnitCoverage(matrix: CoverageMatrix, path: string, unitId: string, requiredLenses: Lens[]): void;
|
package/dist/coverage.js
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
function sortRanges(ranges) {
|
|
2
|
-
return [...ranges].sort((a, b) => {
|
|
3
|
-
if (a.path !== b.path)
|
|
4
|
-
return a.path.localeCompare(b.path);
|
|
5
|
-
if (a.start !== b.start)
|
|
6
|
-
return a.start - b.start;
|
|
7
|
-
return a.end - b.end;
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
export function mergeRanges(ranges) {
|
|
11
|
-
const sorted = sortRanges(ranges);
|
|
12
|
-
const merged = [];
|
|
13
|
-
for (const range of sorted) {
|
|
14
|
-
const last = merged[merged.length - 1];
|
|
15
|
-
if (!last || last.path !== range.path || range.start > last.end + 1) {
|
|
16
|
-
merged.push({ ...range });
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
last.end = Math.max(last.end, range.end);
|
|
20
|
-
last.pass_id = `${last.pass_id},${range.pass_id}`;
|
|
21
|
-
last.agent_role = [last.agent_role, range.agent_role]
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
.join(",");
|
|
24
|
-
}
|
|
25
|
-
return merged;
|
|
26
|
-
}
|
|
27
1
|
export function createCoverageMatrix(paths) {
|
|
28
2
|
return {
|
|
29
3
|
files: paths.map((path) => ({
|
|
@@ -33,7 +7,6 @@ export function createCoverageMatrix(paths) {
|
|
|
33
7
|
audit_status: "pending",
|
|
34
8
|
required_lenses: [],
|
|
35
9
|
completed_lenses: [],
|
|
36
|
-
reviewed_line_ranges: [],
|
|
37
10
|
})),
|
|
38
11
|
};
|
|
39
12
|
}
|
|
@@ -45,7 +18,6 @@ export function markExcludedPath(matrix, path, classificationStatus) {
|
|
|
45
18
|
record.audit_status = "excluded";
|
|
46
19
|
record.required_lenses = [];
|
|
47
20
|
record.completed_lenses = [];
|
|
48
|
-
record.reviewed_line_ranges = [];
|
|
49
21
|
record.unit_ids = [];
|
|
50
22
|
}
|
|
51
23
|
export function applyUnitCoverage(matrix, path, unitId, requiredLenses) {
|
|
@@ -65,21 +37,18 @@ export function applyReviewedRanges(matrix, reviewedRanges) {
|
|
|
65
37
|
const record = matrix.files.find((file) => file.path === range.path);
|
|
66
38
|
if (!record || record.audit_status === "excluded")
|
|
67
39
|
continue;
|
|
68
|
-
record.reviewed_line_ranges.push(range);
|
|
69
|
-
record.reviewed_line_ranges = mergeRanges(record.reviewed_line_ranges);
|
|
70
40
|
if (range.lens && !record.completed_lenses.includes(range.lens)) {
|
|
71
41
|
record.completed_lenses.push(range.lens);
|
|
72
42
|
}
|
|
73
43
|
}
|
|
74
44
|
for (const file of matrix.files) {
|
|
75
|
-
|
|
76
|
-
if (file.audit_status === "excluded") {
|
|
45
|
+
if (file.audit_status === "excluded")
|
|
77
46
|
continue;
|
|
78
|
-
|
|
47
|
+
const hasAllRequired = file.required_lenses.every((lens) => file.completed_lenses.includes(lens));
|
|
79
48
|
if (hasAllRequired && file.required_lenses.length > 0) {
|
|
80
49
|
file.audit_status = "complete";
|
|
81
50
|
}
|
|
82
|
-
else if (file.
|
|
51
|
+
else if (file.completed_lenses.length > 0) {
|
|
83
52
|
file.audit_status = "partial";
|
|
84
53
|
}
|
|
85
54
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNodeModulesOrGit, isTestPath, isInterfacePath, isDataLayerPath, isSecuritySensitivePath, isConcurrencyPath, isScriptPath, isDeploymentConfigPath, isDocPath, isGeneratedPath, } from "./pathPatterns.js";
|
|
1
2
|
function addBucket(buckets, rationale, bucket, reason) {
|
|
2
3
|
if (!buckets.has(bucket)) {
|
|
3
4
|
buckets.add(bucket);
|
|
@@ -8,57 +9,35 @@ export function bucketFile(path) {
|
|
|
8
9
|
const normalized = path.toLowerCase();
|
|
9
10
|
const buckets = new Set();
|
|
10
11
|
const rationale = [];
|
|
11
|
-
if (normalized
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (isNodeModulesOrGit(normalized)) {
|
|
13
|
+
addBucket(buckets, rationale, "generated_vendor", "node_modules or .git excluded by convention");
|
|
14
|
+
return { path, buckets: [...buckets], rationale };
|
|
15
|
+
}
|
|
16
|
+
if (isTestPath(normalized)) {
|
|
14
17
|
addBucket(buckets, rationale, "tests", "path suggests tests");
|
|
15
18
|
}
|
|
16
|
-
if (normalized
|
|
17
|
-
normalized.includes("controller") ||
|
|
18
|
-
normalized.includes("handler") ||
|
|
19
|
-
normalized.includes("api/")) {
|
|
19
|
+
if (isInterfacePath(normalized)) {
|
|
20
20
|
addBucket(buckets, rationale, "interface", "path suggests interface code");
|
|
21
21
|
}
|
|
22
|
-
if (normalized
|
|
23
|
-
normalized.includes("schema") ||
|
|
24
|
-
normalized.includes("migration") ||
|
|
25
|
-
normalized.includes("seed") ||
|
|
26
|
-
normalized.includes("db/")) {
|
|
22
|
+
if (isDataLayerPath(normalized)) {
|
|
27
23
|
addBucket(buckets, rationale, "data_layer", "path suggests data-layer code");
|
|
28
24
|
}
|
|
29
|
-
if (normalized
|
|
30
|
-
normalized.includes("secret") ||
|
|
31
|
-
normalized.includes("token") ||
|
|
32
|
-
normalized.includes("permission") ||
|
|
33
|
-
normalized.includes("session")) {
|
|
25
|
+
if (isSecuritySensitivePath(normalized)) {
|
|
34
26
|
addBucket(buckets, rationale, "security_sensitive", "path suggests security-sensitive code");
|
|
35
27
|
}
|
|
36
|
-
if (normalized
|
|
37
|
-
normalized.includes("worker") ||
|
|
38
|
-
normalized.includes("job") ||
|
|
39
|
-
normalized.includes("cache") ||
|
|
40
|
-
normalized.includes("retry") ||
|
|
41
|
-
normalized.includes("lock")) {
|
|
28
|
+
if (isConcurrencyPath(normalized)) {
|
|
42
29
|
addBucket(buckets, rationale, "concurrency_state", "path suggests concurrency or stateful behavior");
|
|
43
30
|
}
|
|
44
|
-
if (normalized
|
|
45
|
-
normalized.startsWith("scripts/") ||
|
|
46
|
-
normalized.startsWith("bin/")) {
|
|
31
|
+
if (isScriptPath(normalized)) {
|
|
47
32
|
addBucket(buckets, rationale, "tooling_scripts", "path suggests tooling or scripts");
|
|
48
33
|
}
|
|
49
|
-
if (normalized
|
|
50
|
-
normalized.includes("terraform") ||
|
|
51
|
-
normalized.includes("deploy") ||
|
|
52
|
-
normalized.includes("workflow") ||
|
|
53
|
-
normalized.includes("k8s") ||
|
|
54
|
-
normalized.endsWith(".yml") ||
|
|
55
|
-
normalized.endsWith(".yaml")) {
|
|
34
|
+
if (isDeploymentConfigPath(normalized)) {
|
|
56
35
|
addBucket(buckets, rationale, "config_deployment", "path suggests config or deployment artifact");
|
|
57
36
|
}
|
|
58
|
-
if (
|
|
37
|
+
if (isDocPath(normalized)) {
|
|
59
38
|
addBucket(buckets, rationale, "docs_specs", "path suggests documentation");
|
|
60
39
|
}
|
|
61
|
-
if (
|
|
40
|
+
if (isGeneratedPath(normalized)) {
|
|
62
41
|
addBucket(buckets, rationale, "generated_vendor", "path suggests generated or vendored content");
|
|
63
42
|
}
|
|
64
43
|
if (buckets.size === 0) {
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
+
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isDocPath, } from "./pathPatterns.js";
|
|
1
2
|
function inferDisposition(path) {
|
|
2
3
|
const normalized = path.toLowerCase();
|
|
3
|
-
if (
|
|
4
|
+
if (isNodeModulesOrGit(normalized)) {
|
|
5
|
+
return { path, status: "excluded", reason: "node_modules or .git excluded by convention." };
|
|
6
|
+
}
|
|
7
|
+
if (isBuildOutput(normalized)) {
|
|
4
8
|
return { path, status: "generated", reason: "Build output path." };
|
|
5
9
|
}
|
|
6
|
-
if (
|
|
10
|
+
if (isVendorPath(normalized)) {
|
|
7
11
|
return { path, status: "vendor", reason: "Vendor or third-party path." };
|
|
8
12
|
}
|
|
9
|
-
if (normalized
|
|
10
|
-
normalized.endsWith(".jpg") ||
|
|
11
|
-
normalized.endsWith(".jpeg") ||
|
|
12
|
-
normalized.endsWith(".gif") ||
|
|
13
|
-
normalized.endsWith(".pdf") ||
|
|
14
|
-
normalized.endsWith(".zip")) {
|
|
13
|
+
if (isBinaryArtifact(normalized)) {
|
|
15
14
|
return {
|
|
16
15
|
path,
|
|
17
16
|
status: "binary",
|
|
18
17
|
reason: "Non-source binary-like artifact.",
|
|
19
18
|
};
|
|
20
19
|
}
|
|
21
|
-
if (
|
|
20
|
+
if (isDocPath(normalized)) {
|
|
22
21
|
return { path, status: "doc_only", reason: "Documentation artifact." };
|
|
23
22
|
}
|
|
24
23
|
return {
|