auditor-lambda 0.3.3 → 0.3.4
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 +78 -5
- package/dist/cli.js +187 -67
- 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/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 +300 -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/types/graph.d.ts +1 -0
- package/dist/types/reviewPlanning.d.ts +41 -0
- package/dist/types/reviewPlanning.js +1 -0
- package/dist/validation/artifacts.js +13 -0
- package/docs/bootstrap-install.md +3 -0
- package/docs/dispatch-implementation-plan.md +179 -481
- package/docs/next-steps.md +13 -8
- package/docs/product-direction.md +5 -3
- package/docs/run-flow.md +23 -30
- package/docs/session-config.md +4 -1
- package/docs/workflow-refactor-brief.md +83 -154
- package/package.json +1 -1
- package/schemas/finding.schema.json +1 -15
- package/schemas/graph_bundle.schema.json +16 -0
package/README.md
CHANGED
|
@@ -36,6 +36,10 @@ That bootstraps repo-local `/audit-code` surfaces for the hosts we can automate
|
|
|
36
36
|
- VS Code prompt, custom agent, Copilot instructions, and `.vscode/mcp.json`
|
|
37
37
|
- Antigravity planning-mode guidance plus the shared repo-local MCP launcher
|
|
38
38
|
|
|
39
|
+
Re-run the same `audit-code install` command whenever the packaged prompt or
|
|
40
|
+
skill changes. It is the single supported refresh path for the shared
|
|
41
|
+
`.audit-code/install/*` assets and every generated host surface.
|
|
42
|
+
|
|
39
43
|
After bootstrap, you can smoke-test the generated host assets and launcher from the repository root:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
@@ -172,7 +176,8 @@ The next implementation work is tracked in:
|
|
|
172
176
|
|
|
173
177
|
The short version is:
|
|
174
178
|
|
|
175
|
-
-
|
|
179
|
+
- keep the packet dispatch workflow verified in real host environments
|
|
180
|
+
- benchmark `/audit-code` packet counts and warning counts against nontrivial external repositories
|
|
176
181
|
- prove the generated Codex, Claude Desktop, OpenCode, VS Code, and Antigravity guidance in real host flows
|
|
177
182
|
- tighten the repo-local MCP-first bootstrap where host smoke tests expose friction
|
|
178
183
|
- polish provider-assisted continuation and failure guidance
|
|
@@ -1526,16 +1526,30 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1526
1526
|
|
|
1527
1527
|
await collectVerifyCheck(generalChecks, 'installed_prompt', async () => {
|
|
1528
1528
|
await ensureFile(assetPaths.installedPromptPath, 'Installed prompt asset');
|
|
1529
|
+
const installedPrompt = await readFile(assetPaths.installedPromptPath, 'utf8');
|
|
1530
|
+
const sourcePrompt = await readFile(promptAssetPath, 'utf8');
|
|
1531
|
+
if (installedPrompt !== sourcePrompt) {
|
|
1532
|
+
throw new Error(
|
|
1533
|
+
`Installed prompt is out of sync with the source prompt. Run "audit-code install" from ${root}.`,
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1529
1536
|
return {
|
|
1530
|
-
summary: 'Installed prompt asset is present.',
|
|
1537
|
+
summary: 'Installed prompt asset is present and matches the source prompt.',
|
|
1531
1538
|
path: assetPaths.installedPromptPath,
|
|
1532
1539
|
};
|
|
1533
1540
|
});
|
|
1534
1541
|
|
|
1535
1542
|
await collectVerifyCheck(generalChecks, 'installed_skill', async () => {
|
|
1536
1543
|
await ensureFile(assetPaths.installedSkillPath, 'Installed skill asset');
|
|
1544
|
+
const installedSkill = (await readFile(assetPaths.installedSkillPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
1545
|
+
const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
1546
|
+
if (installedSkill !== sourceSkill) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`Installed skill is out of sync with the source skill. Run "audit-code install" from ${root}.`,
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1537
1551
|
return {
|
|
1538
|
-
summary: 'Installed skill asset is present.',
|
|
1552
|
+
summary: 'Installed skill asset is present and matches the source skill.',
|
|
1539
1553
|
path: assetPaths.installedSkillPath,
|
|
1540
1554
|
};
|
|
1541
1555
|
});
|
|
@@ -1599,11 +1613,30 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1599
1613
|
if (!content.includes('# audit-code skill')) {
|
|
1600
1614
|
throw new Error(`Codex skill file is missing the expected heading: ${assetPaths.codexSkillPath}`);
|
|
1601
1615
|
}
|
|
1616
|
+
const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
1617
|
+
if (content.replace(/\r\n/g, '\n') !== sourceSkill) {
|
|
1618
|
+
throw new Error(
|
|
1619
|
+
`Codex skill is out of sync with the source skill. Run "audit-code install --host codex" or "audit-code install".`,
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1602
1622
|
return {
|
|
1603
|
-
summary: 'Codex skill bundle is present.',
|
|
1623
|
+
summary: 'Codex skill bundle is present and matches the source skill.',
|
|
1604
1624
|
path: assetPaths.codexSkillPath,
|
|
1605
1625
|
};
|
|
1606
1626
|
});
|
|
1627
|
+
await collectVerifyCheck(checks, 'codex_prompt', async () => {
|
|
1628
|
+
const content = await readFile(assetPaths.codexPromptPath, 'utf8');
|
|
1629
|
+
const sourcePrompt = await readFile(promptAssetPath, 'utf8');
|
|
1630
|
+
if (content !== sourcePrompt) {
|
|
1631
|
+
throw new Error(
|
|
1632
|
+
`Codex prompt is out of sync with the source prompt. Run "audit-code install --host codex" or "audit-code install".`,
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
summary: 'Codex prompt bundle is present and matches the source prompt.',
|
|
1637
|
+
path: assetPaths.codexPromptPath,
|
|
1638
|
+
};
|
|
1639
|
+
});
|
|
1607
1640
|
await collectVerifyCheck(checks, 'codex_mcp_setup', async () => {
|
|
1608
1641
|
const content = await readFile(assetPaths.codexMcpSetupPath, 'utf8');
|
|
1609
1642
|
if (!content.includes(MCP_LAUNCHER_FILENAME)) {
|
|
@@ -1701,11 +1734,44 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1701
1734
|
if (!content.includes('agent: auditor')) {
|
|
1702
1735
|
throw new Error(`OpenCode command file is missing the auditor agent frontmatter: ${assetPaths.opencodeCommandPath}`);
|
|
1703
1736
|
}
|
|
1737
|
+
const { body: commandBody } = splitFrontmatter(content);
|
|
1738
|
+
const { body: sourceBody } = splitFrontmatter(await readFile(promptAssetPath, 'utf8'));
|
|
1739
|
+
if (commandBody !== sourceBody.trimStart()) {
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
`OpenCode command prompt body is out of sync with the source prompt. Run "audit-code install --host opencode" or "audit-code install".`,
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1704
1744
|
return {
|
|
1705
|
-
summary: 'OpenCode command file is present.',
|
|
1745
|
+
summary: 'OpenCode command file is present and uses the source prompt body.',
|
|
1706
1746
|
path: assetPaths.opencodeCommandPath,
|
|
1707
1747
|
};
|
|
1708
1748
|
});
|
|
1749
|
+
await collectVerifyCheck(checks, 'opencode_skill', async () => {
|
|
1750
|
+
const content = (await readFile(assetPaths.opencodeSkillPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
1751
|
+
const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
1752
|
+
if (content !== sourceSkill) {
|
|
1753
|
+
throw new Error(
|
|
1754
|
+
`OpenCode skill is out of sync with the source skill. Run "audit-code install --host opencode" or "audit-code install".`,
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
return {
|
|
1758
|
+
summary: 'OpenCode skill is present and matches the source skill.',
|
|
1759
|
+
path: assetPaths.opencodeSkillPath,
|
|
1760
|
+
};
|
|
1761
|
+
});
|
|
1762
|
+
await collectVerifyCheck(checks, 'opencode_prompt', async () => {
|
|
1763
|
+
const content = await readFile(assetPaths.opencodePromptPath, 'utf8');
|
|
1764
|
+
const sourcePrompt = await readFile(promptAssetPath, 'utf8');
|
|
1765
|
+
if (content !== sourcePrompt) {
|
|
1766
|
+
throw new Error(
|
|
1767
|
+
`OpenCode prompt is out of sync with the source prompt. Run "audit-code install --host opencode" or "audit-code install".`,
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
return {
|
|
1771
|
+
summary: 'OpenCode prompt is present and matches the source prompt.',
|
|
1772
|
+
path: assetPaths.opencodePromptPath,
|
|
1773
|
+
};
|
|
1774
|
+
});
|
|
1709
1775
|
await collectVerifyCheck(checks, 'opencode_config', async () => {
|
|
1710
1776
|
const config = await readJson(assetPaths.opencodeConfigPath, 'OpenCode project config');
|
|
1711
1777
|
const command = config?.mcp?.auditor?.command;
|
|
@@ -1730,8 +1796,15 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1730
1796
|
if (!content.includes('name: audit-code')) {
|
|
1731
1797
|
throw new Error(`VS Code prompt file is missing the expected frontmatter name: ${assetPaths.vscodePromptPath}`);
|
|
1732
1798
|
}
|
|
1799
|
+
const { body: promptBody } = splitFrontmatter(content);
|
|
1800
|
+
const { body: sourceBody } = splitFrontmatter(await readFile(promptAssetPath, 'utf8'));
|
|
1801
|
+
if (promptBody !== sourceBody.trimStart()) {
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
`VS Code prompt body is out of sync with the source prompt. Run "audit-code install --host vscode" or "audit-code install".`,
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1733
1806
|
return {
|
|
1734
|
-
summary: 'VS Code prompt file is present.',
|
|
1807
|
+
summary: 'VS Code prompt file is present and uses the source prompt body.',
|
|
1735
1808
|
path: assetPaths.vscodePromptPath,
|
|
1736
1809
|
};
|
|
1737
1810
|
});
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
|
|
|
26
26
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
27
27
|
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
28
28
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
29
|
+
import { buildReviewPackets, orderTasksForPacketReview, } from "./orchestrator/reviewPackets.js";
|
|
29
30
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
30
31
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
31
32
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -35,7 +36,7 @@ const DIRECT_CLI_DEFAULTS = {
|
|
|
35
36
|
rootDir: ".",
|
|
36
37
|
artifactsDir: ".artifacts",
|
|
37
38
|
maxRuns: 1000,
|
|
38
|
-
agentBatchSize:
|
|
39
|
+
agentBatchSize: 6,
|
|
39
40
|
parallelWorkers: 1,
|
|
40
41
|
timeoutMs: 30 * 60 * 1000, // 30 minutes
|
|
41
42
|
uiMode: "headless",
|
|
@@ -287,7 +288,12 @@ async function detectProjectRoot(root) {
|
|
|
287
288
|
}
|
|
288
289
|
function buildPendingAuditTasks(bundle) {
|
|
289
290
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
290
|
-
|
|
291
|
+
const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
|
|
292
|
+
const lineIndex = Object.fromEntries(pendingTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
293
|
+
return orderTasksForPacketReview(pendingTasks, {
|
|
294
|
+
graphBundle: bundle.graph_bundle,
|
|
295
|
+
lineIndex,
|
|
296
|
+
});
|
|
291
297
|
}
|
|
292
298
|
async function addFileLineCountHints(root, tasks) {
|
|
293
299
|
const lineIndex = await buildLineIndexForPaths(root, tasks.flatMap((task) => task.file_paths));
|
|
@@ -1408,94 +1414,169 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1408
1414
|
const taskResultsDir = join(runDir, "task-results");
|
|
1409
1415
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
1410
1416
|
const tasks = await readJsonFile(tasksPath);
|
|
1417
|
+
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1411
1418
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
1412
1419
|
const lensDefs = await readJsonFile(lensDefsPath);
|
|
1413
1420
|
await mkdir(taskResultsDir, { recursive: true });
|
|
1421
|
+
const lineIndex = Object.fromEntries(tasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
1422
|
+
const orderedTasks = orderTasksForPacketReview(tasks, {
|
|
1423
|
+
graphBundle: bundle.graph_bundle,
|
|
1424
|
+
lineIndex,
|
|
1425
|
+
});
|
|
1426
|
+
const packets = buildReviewPackets(orderedTasks, {
|
|
1427
|
+
graphBundle: bundle.graph_bundle,
|
|
1428
|
+
lineIndex,
|
|
1429
|
+
});
|
|
1430
|
+
const tasksById = new Map(orderedTasks.map((task) => [task.task_id, task]));
|
|
1431
|
+
const outputPathByTaskId = new Map(orderedTasks.map((task) => [
|
|
1432
|
+
task.task_id,
|
|
1433
|
+
join(taskResultsDir, `${task.task_id.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`),
|
|
1434
|
+
]));
|
|
1414
1435
|
const plan = [];
|
|
1415
|
-
let
|
|
1436
|
+
let largestPacketId = null;
|
|
1416
1437
|
let largestLines = 0;
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1438
|
+
let largestEstimatedTokens = 0;
|
|
1439
|
+
const warnings = [];
|
|
1440
|
+
for (const packet of packets) {
|
|
1441
|
+
const sanitized = packet.packet_id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1420
1442
|
const promptPath = join(taskResultsDir, `${sanitized}.prompt.md`);
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1443
|
+
const packetTasks = packet.task_ids
|
|
1444
|
+
.map((taskId) => tasksById.get(taskId))
|
|
1445
|
+
.filter((task) => task !== undefined);
|
|
1446
|
+
if (packet.total_lines > largestLines) {
|
|
1447
|
+
largestLines = packet.total_lines;
|
|
1448
|
+
largestEstimatedTokens = packet.estimated_tokens;
|
|
1449
|
+
largestPacketId = packet.packet_id;
|
|
1424
1450
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1451
|
+
if (packet.total_lines > 2500) {
|
|
1452
|
+
warnings.push({
|
|
1453
|
+
code: "large_packet",
|
|
1454
|
+
message: `large packet ${packet.packet_id} (~${packet.total_lines} lines) may hit quota limits`,
|
|
1455
|
+
});
|
|
1429
1456
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1457
|
+
for (const task of packetTasks) {
|
|
1458
|
+
if (!lensDefs[task.lens]) {
|
|
1459
|
+
warnings.push({
|
|
1460
|
+
code: "missing_lens_definition",
|
|
1461
|
+
message: `no lens definition for '${task.lens}' (task ${task.task_id})`,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1432
1464
|
}
|
|
1433
|
-
const fileList =
|
|
1434
|
-
const lines =
|
|
1435
|
-
return `- ${
|
|
1465
|
+
const fileList = packet.file_paths.map((path) => {
|
|
1466
|
+
const lines = packet.file_line_counts[path] ?? 0;
|
|
1467
|
+
return `- ${path} (${lines} lines)`;
|
|
1436
1468
|
}).join("\n");
|
|
1469
|
+
const taskSections = packetTasks.flatMap((task) => {
|
|
1470
|
+
const lensDef = lensDefs[task.lens];
|
|
1471
|
+
const outputPath = outputPathByTaskId.get(task.task_id);
|
|
1472
|
+
return [
|
|
1473
|
+
`### ${task.task_id}`,
|
|
1474
|
+
`unit_id: ${task.unit_id}`,
|
|
1475
|
+
`pass_id: ${task.pass_id}`,
|
|
1476
|
+
`lens: ${task.lens}`,
|
|
1477
|
+
`output_path: ${outputPath}`,
|
|
1478
|
+
`rationale: ${task.rationale}`,
|
|
1479
|
+
"",
|
|
1480
|
+
`Lens guidance: ${lensDef?.description ?? task.lens}`,
|
|
1481
|
+
`Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
|
|
1482
|
+
"",
|
|
1483
|
+
];
|
|
1484
|
+
});
|
|
1485
|
+
const validationCommands = packetTasks.map((task) => ` "${process.execPath}" "${join(packageRoot, "audit-code.mjs")}" validate-result --run-id ${runId} --task-id ${task.task_id} --artifacts-dir "${artifactsDir}"`);
|
|
1486
|
+
const outputPaths = Object.fromEntries(packetTasks.map((task) => [
|
|
1487
|
+
task.task_id,
|
|
1488
|
+
outputPathByTaskId.get(task.task_id) ?? "",
|
|
1489
|
+
]));
|
|
1437
1490
|
const prompt = [
|
|
1438
|
-
"You are a code auditor. Review
|
|
1491
|
+
"You are a code auditor. Review this packet once, then produce one result file per listed task.",
|
|
1439
1492
|
"",
|
|
1440
|
-
"##
|
|
1441
|
-
`
|
|
1442
|
-
`
|
|
1443
|
-
`
|
|
1444
|
-
`
|
|
1493
|
+
"## Packet",
|
|
1494
|
+
`packet_id: ${packet.packet_id}`,
|
|
1495
|
+
`task_count: ${packet.task_ids.length}`,
|
|
1496
|
+
`lenses: ${packet.lenses.join(", ")}`,
|
|
1497
|
+
`estimated_tokens: ${packet.estimated_tokens}`,
|
|
1445
1498
|
"",
|
|
1446
1499
|
"## Files to read",
|
|
1447
1500
|
"Use your Read tool. Paths are repo-relative from the current working directory.",
|
|
1448
1501
|
fileList,
|
|
1449
1502
|
"",
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
"",
|
|
1453
|
-
`Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
|
|
1454
|
-
"",
|
|
1503
|
+
"## Tasks",
|
|
1504
|
+
...taskSections,
|
|
1455
1505
|
"## Output",
|
|
1456
|
-
|
|
1457
|
-
"
|
|
1506
|
+
"Write exactly one JSON object for each task to that task's output_path.",
|
|
1507
|
+
"Do not combine the task results into one file. Do not edit source files,",
|
|
1458
1508
|
"remediate findings, create extra task results, or run unrelated audits.",
|
|
1459
1509
|
"",
|
|
1460
|
-
"Required fields:",
|
|
1461
|
-
" task_id copy from task metadata
|
|
1462
|
-
" unit_id copy from task metadata
|
|
1463
|
-
" pass_id copy from task metadata
|
|
1464
|
-
" lens copy from task metadata
|
|
1465
|
-
" file_coverage [{path, total_lines}]
|
|
1466
|
-
" findings [] or array of finding objects
|
|
1510
|
+
"Required AuditResult fields:",
|
|
1511
|
+
" task_id copy from the task metadata",
|
|
1512
|
+
" unit_id copy from the task metadata",
|
|
1513
|
+
" pass_id copy from the task metadata",
|
|
1514
|
+
" lens copy from the task metadata",
|
|
1515
|
+
" file_coverage [{path, total_lines}] - one entry per assigned file; use the line counts listed above",
|
|
1516
|
+
" findings [] or array of finding objects",
|
|
1467
1517
|
"",
|
|
1468
1518
|
"Each finding object:",
|
|
1469
1519
|
" id unique ID, e.g. \"COR-001\"",
|
|
1470
1520
|
" title short title",
|
|
1471
|
-
" category
|
|
1521
|
+
" category specific finding category, such as missing-validation or command-execution",
|
|
1472
1522
|
" severity critical|high|medium|low|info",
|
|
1473
1523
|
" confidence high|medium|low",
|
|
1474
|
-
|
|
1475
|
-
" summary 1
|
|
1476
|
-
" affected_files [{path, line_start?, line_end?, symbol?}]
|
|
1477
|
-
" evidence [\"path/to/file.ts:42
|
|
1524
|
+
" lens must match the task lens exactly",
|
|
1525
|
+
" summary 1-2 sentence description",
|
|
1526
|
+
" affected_files [{path, line_start?, line_end?, symbol?}] - objects, not strings; min 1 entry",
|
|
1527
|
+
" evidence [\"path/to/file.ts:42 - description of what you see there\"] - min 1 entry",
|
|
1478
1528
|
"",
|
|
1479
1529
|
"Constraints:",
|
|
1480
|
-
"1. line_end must not exceed the file's actual line count
|
|
1481
|
-
"2. affected_files entries are
|
|
1482
|
-
"3. Only reference files from the
|
|
1483
|
-
"4. findings: [] is correct when you find nothing genuine",
|
|
1530
|
+
"1. line_end must not exceed the file's actual line count.",
|
|
1531
|
+
"2. affected_files entries are objects with a path key, not plain strings.",
|
|
1532
|
+
"3. Only reference files from the packet unless a finding genuinely crosses a boundary.",
|
|
1533
|
+
"4. findings: [] is correct when you find nothing genuine.",
|
|
1484
1534
|
"",
|
|
1485
1535
|
"## Validate",
|
|
1486
|
-
"After writing
|
|
1487
|
-
|
|
1536
|
+
"After writing every result, run:",
|
|
1537
|
+
...validationCommands,
|
|
1538
|
+
"",
|
|
1539
|
+
"Exit 0 means valid. Non-zero: read the errors, fix the JSON, rewrite the file, run again. Retry up to 3 times.",
|
|
1488
1540
|
"",
|
|
1489
|
-
"
|
|
1541
|
+
"## Final response",
|
|
1542
|
+
`After every validation command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
|
|
1490
1543
|
].join("\n");
|
|
1491
1544
|
await writeFile(promptPath, prompt, "utf8");
|
|
1492
|
-
|
|
1493
|
-
|
|
1545
|
+
plan.push({
|
|
1546
|
+
packet_id: packet.packet_id,
|
|
1547
|
+
task_id: packet.task_ids.length === 1 ? packet.task_ids[0] : packet.packet_id,
|
|
1548
|
+
task_ids: packet.task_ids,
|
|
1549
|
+
description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)`,
|
|
1550
|
+
output_paths: outputPaths,
|
|
1551
|
+
prompt_path: promptPath,
|
|
1552
|
+
lenses: packet.lenses,
|
|
1553
|
+
file_paths: packet.file_paths,
|
|
1554
|
+
total_lines: packet.total_lines,
|
|
1555
|
+
estimated_tokens: packet.estimated_tokens,
|
|
1556
|
+
});
|
|
1494
1557
|
}
|
|
1495
1558
|
await writeJsonFile(dispatchPlanPath, plan);
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1559
|
+
const warningsPath = warnings.length > 0
|
|
1560
|
+
? join(runDir, "dispatch-warnings.json")
|
|
1561
|
+
: null;
|
|
1562
|
+
if (warningsPath) {
|
|
1563
|
+
await writeJsonFile(warningsPath, warnings);
|
|
1564
|
+
}
|
|
1565
|
+
console.log(JSON.stringify({
|
|
1566
|
+
run_id: runId,
|
|
1567
|
+
dispatch_plan_path: dispatchPlanPath,
|
|
1568
|
+
packet_count: plan.length,
|
|
1569
|
+
task_count: orderedTasks.length,
|
|
1570
|
+
largest_packet: largestPacketId
|
|
1571
|
+
? {
|
|
1572
|
+
packet_id: largestPacketId,
|
|
1573
|
+
total_lines: largestLines,
|
|
1574
|
+
estimated_tokens: largestEstimatedTokens,
|
|
1575
|
+
}
|
|
1576
|
+
: null,
|
|
1577
|
+
warning_count: warnings.length,
|
|
1578
|
+
dispatch_warnings_path: warningsPath,
|
|
1579
|
+
}, null, 2));
|
|
1499
1580
|
}
|
|
1500
1581
|
async function cmdMergeAndIngest(argv) {
|
|
1501
1582
|
const runId = getFlag(argv, "--run-id");
|
|
@@ -1507,12 +1588,13 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1507
1588
|
const auditResultsPath = join(runDir, "audit-results.json");
|
|
1508
1589
|
const taskPath = join(runDir, "task.json");
|
|
1509
1590
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1591
|
+
const workerTask = await readJsonFile(taskPath);
|
|
1510
1592
|
let allTasks = [];
|
|
1511
1593
|
try {
|
|
1512
1594
|
allTasks = await readJsonFile(tasksPath);
|
|
1513
1595
|
}
|
|
1514
1596
|
catch { /* may not exist */ }
|
|
1515
|
-
const
|
|
1597
|
+
const lineIndex = Object.fromEntries(allTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
1516
1598
|
let files;
|
|
1517
1599
|
try {
|
|
1518
1600
|
files = (await readdir(taskResultsDir)).filter(f => f.endsWith(".json")).sort();
|
|
@@ -1533,19 +1615,29 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1533
1615
|
failing.push({ task_id: filename, errors: [`Invalid JSON: ${e.message}`] });
|
|
1534
1616
|
continue;
|
|
1535
1617
|
}
|
|
1536
|
-
const
|
|
1537
|
-
?
|
|
1618
|
+
const record = obj && typeof obj === "object" && !Array.isArray(obj)
|
|
1619
|
+
? obj
|
|
1620
|
+
: undefined;
|
|
1621
|
+
const taskId = typeof record?.task_id === "string"
|
|
1622
|
+
? String(record.task_id) : undefined;
|
|
1623
|
+
const resultErrors = [];
|
|
1538
1624
|
if (taskId) {
|
|
1539
|
-
seenTaskIds.
|
|
1625
|
+
if (seenTaskIds.has(taskId)) {
|
|
1626
|
+
resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
|
|
1627
|
+
}
|
|
1628
|
+
else {
|
|
1629
|
+
seenTaskIds.add(taskId);
|
|
1630
|
+
}
|
|
1540
1631
|
}
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1632
|
+
const issues = validateAuditResults([obj], allTasks, { lineIndex });
|
|
1633
|
+
resultErrors.push(...issues
|
|
1634
|
+
.filter(i => i.severity === "error")
|
|
1635
|
+
.map(i => i.message));
|
|
1636
|
+
if (resultErrors.length === 0) {
|
|
1545
1637
|
passing.push(obj);
|
|
1546
1638
|
}
|
|
1547
1639
|
else {
|
|
1548
|
-
failing.push({ task_id: taskId ?? filename, errors:
|
|
1640
|
+
failing.push({ task_id: taskId ?? filename, errors: resultErrors });
|
|
1549
1641
|
}
|
|
1550
1642
|
}
|
|
1551
1643
|
for (const task of allTasks) {
|
|
@@ -1562,9 +1654,37 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1562
1654
|
await writeJsonFile(failedTasksPath, failing);
|
|
1563
1655
|
throw new Error(`${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
|
|
1564
1656
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1657
|
+
const findingCount = passing.reduce((sum, result) => sum + result.findings.length, 0);
|
|
1658
|
+
const result = await runAuditStep({
|
|
1659
|
+
root: workerTask.repo_root,
|
|
1660
|
+
artifactsDir,
|
|
1661
|
+
preferredExecutor: "result_ingestion_executor",
|
|
1662
|
+
auditResultsPath,
|
|
1663
|
+
});
|
|
1664
|
+
const workerResult = buildWorkerResult({
|
|
1665
|
+
runId,
|
|
1666
|
+
obligationId: workerTask.obligation_id,
|
|
1667
|
+
status: result.progress_made ? "completed" : "no_progress",
|
|
1668
|
+
progressMade: result.progress_made,
|
|
1669
|
+
selectedExecutor: result.selected_executor,
|
|
1670
|
+
artifactsWritten: result.artifacts_written,
|
|
1671
|
+
summary: result.progress_summary,
|
|
1672
|
+
nextLikelyStep: result.next_likely_step,
|
|
1673
|
+
errors: [],
|
|
1674
|
+
});
|
|
1675
|
+
await writeJsonFile(workerTask.result_path, workerResult);
|
|
1676
|
+
console.log(JSON.stringify({
|
|
1677
|
+
run_id: runId,
|
|
1678
|
+
status: workerResult.status,
|
|
1679
|
+
accepted_count: passing.length,
|
|
1680
|
+
rejected_count: 0,
|
|
1681
|
+
finding_count: findingCount,
|
|
1682
|
+
audit_results_path: auditResultsPath,
|
|
1683
|
+
selected_executor: workerResult.selected_executor,
|
|
1684
|
+
progress_made: workerResult.progress_made,
|
|
1685
|
+
progress_summary: workerResult.summary,
|
|
1686
|
+
next_likely_step: workerResult.next_likely_step,
|
|
1687
|
+
}, null, 2));
|
|
1568
1688
|
}
|
|
1569
1689
|
async function cmdValidateResult(argv) {
|
|
1570
1690
|
const runId = getFlag(argv, "--run-id");
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { RepoManifest } from "../types.js";
|
|
2
2
|
import type { FileDisposition } from "../types/disposition.js";
|
|
3
3
|
import type { GraphBundle } from "../types/graph.js";
|
|
4
|
-
export
|
|
4
|
+
export interface BuildGraphBundleOptions {
|
|
5
|
+
fileContents?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildGraphBundleFromFs(repoManifest: RepoManifest, root: string, disposition?: FileDisposition): Promise<GraphBundle>;
|
|
8
|
+
export declare function buildGraphBundle(repoManifest: RepoManifest, disposition?: FileDisposition, options?: BuildGraphBundleOptions): GraphBundle;
|