auditor-lambda 0.3.2 → 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 +205 -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/io/json.js +3 -3
- package/dist/io/runArtifacts.js +4 -0
- package/dist/mcp/server.js +24 -11
- 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/orchestrator.js +53 -2
- package/dist/prompts/renderWorkerPrompt.js +11 -4
- package/dist/providers/index.js +1 -1
- package/dist/supervisor/sessionConfig.js +1 -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/dist/validation/sessionConfig.js +1 -1
- package/docs/agent-integrations.md +17 -8
- 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 +10 -3
- package/docs/supervisor.md +12 -4
- package/docs/workflow-refactor-brief.md +85 -147
- package/package.json +1 -1
- package/schemas/audit_results.schema.json +10 -0
- package/schemas/finding.schema.json +1 -15
- package/schemas/graph_bundle.schema.json +16 -0
- package/skills/audit-code/SKILL.md +12 -3
- package/skills/audit-code/audit-code.prompt.md +87 -57
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));
|
|
@@ -1312,6 +1318,9 @@ async function cmdRunToCompletion(argv) {
|
|
|
1312
1318
|
next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
|
|
1313
1319
|
providerName: provider.name,
|
|
1314
1320
|
});
|
|
1321
|
+
if (state.status === "complete") {
|
|
1322
|
+
await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
|
|
1323
|
+
}
|
|
1315
1324
|
}
|
|
1316
1325
|
async function cmdWorkerRun(argv) {
|
|
1317
1326
|
const taskPath = getFlag(argv, "--task");
|
|
@@ -1405,92 +1414,169 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1405
1414
|
const taskResultsDir = join(runDir, "task-results");
|
|
1406
1415
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
1407
1416
|
const tasks = await readJsonFile(tasksPath);
|
|
1417
|
+
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1408
1418
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
1409
1419
|
const lensDefs = await readJsonFile(lensDefsPath);
|
|
1410
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
|
+
]));
|
|
1411
1435
|
const plan = [];
|
|
1412
|
-
let
|
|
1436
|
+
let largestPacketId = null;
|
|
1413
1437
|
let largestLines = 0;
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
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, "_");
|
|
1417
1442
|
const promptPath = join(taskResultsDir, `${sanitized}.prompt.md`);
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
|
|
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;
|
|
1421
1450
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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
|
+
});
|
|
1426
1456
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
+
}
|
|
1429
1464
|
}
|
|
1430
|
-
const fileList =
|
|
1431
|
-
const lines =
|
|
1432
|
-
return `- ${
|
|
1465
|
+
const fileList = packet.file_paths.map((path) => {
|
|
1466
|
+
const lines = packet.file_line_counts[path] ?? 0;
|
|
1467
|
+
return `- ${path} (${lines} lines)`;
|
|
1433
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
|
+
]));
|
|
1434
1490
|
const prompt = [
|
|
1435
|
-
"You are a code auditor. Review
|
|
1491
|
+
"You are a code auditor. Review this packet once, then produce one result file per listed task.",
|
|
1436
1492
|
"",
|
|
1437
|
-
"##
|
|
1438
|
-
`
|
|
1439
|
-
`
|
|
1440
|
-
`
|
|
1441
|
-
`
|
|
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}`,
|
|
1442
1498
|
"",
|
|
1443
1499
|
"## Files to read",
|
|
1444
1500
|
"Use your Read tool. Paths are repo-relative from the current working directory.",
|
|
1445
1501
|
fileList,
|
|
1446
1502
|
"",
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
"",
|
|
1450
|
-
`Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
|
|
1451
|
-
"",
|
|
1503
|
+
"## Tasks",
|
|
1504
|
+
...taskSections,
|
|
1452
1505
|
"## Output",
|
|
1453
|
-
|
|
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,",
|
|
1508
|
+
"remediate findings, create extra task results, or run unrelated audits.",
|
|
1454
1509
|
"",
|
|
1455
|
-
"Required fields:",
|
|
1456
|
-
" task_id copy from task metadata
|
|
1457
|
-
" unit_id copy from task metadata
|
|
1458
|
-
" pass_id copy from task metadata
|
|
1459
|
-
" lens copy from task metadata
|
|
1460
|
-
" file_coverage [{path, total_lines}]
|
|
1461
|
-
" 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",
|
|
1462
1517
|
"",
|
|
1463
1518
|
"Each finding object:",
|
|
1464
1519
|
" id unique ID, e.g. \"COR-001\"",
|
|
1465
1520
|
" title short title",
|
|
1466
|
-
" category
|
|
1521
|
+
" category specific finding category, such as missing-validation or command-execution",
|
|
1467
1522
|
" severity critical|high|medium|low|info",
|
|
1468
1523
|
" confidence high|medium|low",
|
|
1469
|
-
|
|
1470
|
-
" summary 1
|
|
1471
|
-
" affected_files [{path, line_start?, line_end?, symbol?}]
|
|
1472
|
-
" 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",
|
|
1473
1528
|
"",
|
|
1474
1529
|
"Constraints:",
|
|
1475
|
-
"1. line_end must not exceed the file's actual line count
|
|
1476
|
-
"2. affected_files entries are
|
|
1477
|
-
"3. Only reference files from the
|
|
1478
|
-
"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.",
|
|
1479
1534
|
"",
|
|
1480
1535
|
"## Validate",
|
|
1481
|
-
"After writing
|
|
1482
|
-
|
|
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.",
|
|
1483
1540
|
"",
|
|
1484
|
-
"
|
|
1541
|
+
"## Final response",
|
|
1542
|
+
`After every validation command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
|
|
1485
1543
|
].join("\n");
|
|
1486
1544
|
await writeFile(promptPath, prompt, "utf8");
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
+
});
|
|
1489
1557
|
}
|
|
1490
1558
|
await writeJsonFile(dispatchPlanPath, plan);
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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));
|
|
1494
1580
|
}
|
|
1495
1581
|
async function cmdMergeAndIngest(argv) {
|
|
1496
1582
|
const runId = getFlag(argv, "--run-id");
|
|
@@ -1502,12 +1588,13 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1502
1588
|
const auditResultsPath = join(runDir, "audit-results.json");
|
|
1503
1589
|
const taskPath = join(runDir, "task.json");
|
|
1504
1590
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1591
|
+
const workerTask = await readJsonFile(taskPath);
|
|
1505
1592
|
let allTasks = [];
|
|
1506
1593
|
try {
|
|
1507
1594
|
allTasks = await readJsonFile(tasksPath);
|
|
1508
1595
|
}
|
|
1509
1596
|
catch { /* may not exist */ }
|
|
1510
|
-
const
|
|
1597
|
+
const lineIndex = Object.fromEntries(allTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
1511
1598
|
let files;
|
|
1512
1599
|
try {
|
|
1513
1600
|
files = (await readdir(taskResultsDir)).filter(f => f.endsWith(".json")).sort();
|
|
@@ -1517,6 +1604,7 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1517
1604
|
}
|
|
1518
1605
|
const passing = [];
|
|
1519
1606
|
const failing = [];
|
|
1607
|
+
const seenTaskIds = new Set();
|
|
1520
1608
|
for (const filename of files) {
|
|
1521
1609
|
const filePath = join(taskResultsDir, filename);
|
|
1522
1610
|
let obj;
|
|
@@ -1527,26 +1615,76 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1527
1615
|
failing.push({ task_id: filename, errors: [`Invalid JSON: ${e.message}`] });
|
|
1528
1616
|
continue;
|
|
1529
1617
|
}
|
|
1530
|
-
const
|
|
1531
|
-
?
|
|
1532
|
-
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1535
|
-
|
|
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 = [];
|
|
1624
|
+
if (taskId) {
|
|
1625
|
+
if (seenTaskIds.has(taskId)) {
|
|
1626
|
+
resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
|
|
1627
|
+
}
|
|
1628
|
+
else {
|
|
1629
|
+
seenTaskIds.add(taskId);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
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) {
|
|
1536
1637
|
passing.push(obj);
|
|
1537
1638
|
}
|
|
1538
1639
|
else {
|
|
1539
|
-
failing.push({ task_id: taskId ?? filename, errors:
|
|
1640
|
+
failing.push({ task_id: taskId ?? filename, errors: resultErrors });
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
for (const task of allTasks) {
|
|
1644
|
+
if (!seenTaskIds.has(task.task_id)) {
|
|
1645
|
+
failing.push({
|
|
1646
|
+
task_id: task.task_id,
|
|
1647
|
+
errors: ["Missing audit result for assigned task."],
|
|
1648
|
+
});
|
|
1540
1649
|
}
|
|
1541
1650
|
}
|
|
1542
1651
|
await writeJsonFile(auditResultsPath, passing);
|
|
1543
1652
|
if (failing.length > 0) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1653
|
+
const failedTasksPath = join(runDir, "failed-tasks.json");
|
|
1654
|
+
await writeJsonFile(failedTasksPath, failing);
|
|
1655
|
+
throw new Error(`${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
|
|
1546
1656
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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));
|
|
1550
1688
|
}
|
|
1551
1689
|
async function cmdValidateResult(argv) {
|
|
1552
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;
|