auditor-lambda 0.3.4 → 0.3.6
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 +327 -242
- package/dist/cli.js +418 -54
- package/dist/orchestrator/fileAnchors.d.ts +32 -0
- package/dist/orchestrator/fileAnchors.js +217 -0
- package/dist/orchestrator/reviewPackets.js +10 -0
- package/dist/providers/claudeCodeProvider.js +3 -1
- package/dist/providers/index.js +2 -1
- package/dist/supervisor/operatorHandoff.js +22 -11
- package/dist/types/sessionConfig.d.ts +1 -0
- package/dist/validation/auditResults.js +50 -2
- package/dist/validation/sessionConfig.js +5 -0
- package/docs/agent-integrations.md +5 -2
- package/docs/bootstrap-install.md +6 -1
- package/docs/contract.md +3 -0
- package/docs/dispatch-implementation-plan.md +74 -23
- package/docs/github-copilot.md +1 -1
- package/docs/model-selection.md +11 -0
- package/docs/next-steps.md +2 -2
- package/docs/packaging.md +4 -2
- package/docs/production-launch-bar.md +3 -1
- package/docs/production-readiness.md +6 -6
- package/docs/run-flow.md +5 -3
- package/docs/session-config.md +11 -3
- package/docs/supervisor.md +5 -3
- package/docs/workflow-refactor-brief.md +14 -5
- package/package.json +1 -1
- package/skills/audit-code/SKILL.md +4 -0
- package/skills/audit-code/audit-code.prompt.md +16 -6
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { access, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { buildRepoManifest } from "./extractors/fileInventory.js";
|
|
6
8
|
import { buildFileDisposition } from "./extractors/disposition.js";
|
|
@@ -11,7 +13,7 @@ import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
|
|
|
11
13
|
import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
|
|
12
14
|
import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
|
|
13
15
|
import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
|
|
14
|
-
import { readJsonFile, writeJsonFile } from "./io/json.js";
|
|
16
|
+
import { isFileMissingError, readJsonFile, writeJsonFile } from "./io/json.js";
|
|
15
17
|
import { validateArtifactBundle } from "./validation/artifacts.js";
|
|
16
18
|
import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
|
|
17
19
|
import { prefixValidationIssues } from "./validation/basic.js";
|
|
@@ -27,11 +29,16 @@ import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from
|
|
|
27
29
|
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
28
30
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
29
31
|
import { buildReviewPackets, orderTasksForPacketReview, } from "./orchestrator/reviewPackets.js";
|
|
32
|
+
import { buildFileAnchorSummary, } from "./orchestrator/fileAnchors.js";
|
|
30
33
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
31
34
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
32
35
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
33
36
|
const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
|
|
34
37
|
const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
|
|
38
|
+
const LARGE_FILE_PACKET_TARGET_LINES = 2500;
|
|
39
|
+
const SMALL_MODEL_HINT_MAX_LINES = 500;
|
|
40
|
+
const SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS = 3000;
|
|
41
|
+
const DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS = 9000;
|
|
35
42
|
const DIRECT_CLI_DEFAULTS = {
|
|
36
43
|
rootDir: ".",
|
|
37
44
|
artifactsDir: ".artifacts",
|
|
@@ -65,6 +72,45 @@ function getFlag(argv, name, fallback) {
|
|
|
65
72
|
function hasFlag(argv, name) {
|
|
66
73
|
return argv.includes(name);
|
|
67
74
|
}
|
|
75
|
+
function toBase64Url(value) {
|
|
76
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
77
|
+
}
|
|
78
|
+
function fromBase64Url(value) {
|
|
79
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
80
|
+
}
|
|
81
|
+
function digestId(value) {
|
|
82
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
83
|
+
}
|
|
84
|
+
function safeArtifactStem(value) {
|
|
85
|
+
const sanitized = value
|
|
86
|
+
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
|
87
|
+
.replace(/^_+|_+$/g, "")
|
|
88
|
+
.slice(0, 80);
|
|
89
|
+
return sanitized.length > 0 ? sanitized : "artifact";
|
|
90
|
+
}
|
|
91
|
+
function artifactNameForId(value, extension) {
|
|
92
|
+
return `${safeArtifactStem(value)}_${digestId(value)}.${extension}`;
|
|
93
|
+
}
|
|
94
|
+
function taskResultPath(taskResultsDir, taskId) {
|
|
95
|
+
return join(taskResultsDir, artifactNameForId(taskId, "json"));
|
|
96
|
+
}
|
|
97
|
+
function packetPromptPath(taskResultsDir, packetId) {
|
|
98
|
+
return join(taskResultsDir, artifactNameForId(packetId, "prompt.md"));
|
|
99
|
+
}
|
|
100
|
+
async function readStdinText() {
|
|
101
|
+
if (process.stdin.isTTY) {
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
return await new Promise((resolveInput, reject) => {
|
|
105
|
+
let input = "";
|
|
106
|
+
process.stdin.setEncoding("utf8");
|
|
107
|
+
process.stdin.on("data", (chunk) => {
|
|
108
|
+
input += chunk;
|
|
109
|
+
});
|
|
110
|
+
process.stdin.on("end", () => resolveInput(input));
|
|
111
|
+
process.stdin.on("error", reject);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
68
114
|
function resolveFlagPath(argv, name, fallback) {
|
|
69
115
|
return resolve(getFlag(argv, name, fallback));
|
|
70
116
|
}
|
|
@@ -1404,6 +1450,112 @@ async function cmdWorkerRun(argv) {
|
|
|
1404
1450
|
process.exitCode = 1;
|
|
1405
1451
|
}
|
|
1406
1452
|
}
|
|
1453
|
+
const DISPATCH_RESULT_MAP_FILENAME = "dispatch-result-map.json";
|
|
1454
|
+
function dispatchResultMapPath(runDir) {
|
|
1455
|
+
return join(runDir, DISPATCH_RESULT_MAP_FILENAME);
|
|
1456
|
+
}
|
|
1457
|
+
function resolveRunScopedArg(argv, rawFlag, b64Flag) {
|
|
1458
|
+
const raw = getFlag(argv, rawFlag);
|
|
1459
|
+
const encoded = getFlag(argv, b64Flag);
|
|
1460
|
+
return raw ?? (encoded ? fromBase64Url(encoded) : undefined);
|
|
1461
|
+
}
|
|
1462
|
+
async function loadDispatchResultMap(runDir) {
|
|
1463
|
+
try {
|
|
1464
|
+
return await readJsonFile(dispatchResultMapPath(runDir));
|
|
1465
|
+
}
|
|
1466
|
+
catch (error) {
|
|
1467
|
+
if (!isFileMissingError(error)) {
|
|
1468
|
+
throw error;
|
|
1469
|
+
}
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function entriesByTaskId(entries) {
|
|
1474
|
+
return new Map(entries.map((entry) => [entry.task_id, entry]));
|
|
1475
|
+
}
|
|
1476
|
+
function isIsolatedLargeFilePacket(packet) {
|
|
1477
|
+
return (packet.file_paths.length === 1 &&
|
|
1478
|
+
packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES);
|
|
1479
|
+
}
|
|
1480
|
+
function buildDispatchComplexity(packet, largeFileMode) {
|
|
1481
|
+
return {
|
|
1482
|
+
priority: packet.priority,
|
|
1483
|
+
task_count: packet.task_ids.length,
|
|
1484
|
+
file_count: packet.file_paths.length,
|
|
1485
|
+
total_lines: packet.total_lines,
|
|
1486
|
+
estimated_tokens: packet.estimated_tokens,
|
|
1487
|
+
lenses: packet.lenses,
|
|
1488
|
+
tags: packet.tags ?? [],
|
|
1489
|
+
large_file_mode: largeFileMode,
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function buildDispatchModelHint(complexity) {
|
|
1493
|
+
const deepReasons = [];
|
|
1494
|
+
if (complexity.priority === "high")
|
|
1495
|
+
deepReasons.push("high_priority");
|
|
1496
|
+
if (complexity.large_file_mode)
|
|
1497
|
+
deepReasons.push("isolated_large_file");
|
|
1498
|
+
if (complexity.estimated_tokens >= DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS) {
|
|
1499
|
+
deepReasons.push("high_estimated_tokens");
|
|
1500
|
+
}
|
|
1501
|
+
if (complexity.tags.some((tag) => tag === "critical_flow" || tag.startsWith("critical_flow:"))) {
|
|
1502
|
+
deepReasons.push("critical_flow");
|
|
1503
|
+
}
|
|
1504
|
+
if (complexity.tags.some((tag) => tag === "external_analyzer_signal" || tag.startsWith("external_tool:"))) {
|
|
1505
|
+
deepReasons.push("external_analyzer_signal");
|
|
1506
|
+
}
|
|
1507
|
+
if (deepReasons.length > 0) {
|
|
1508
|
+
return { tier: "deep", reasons: deepReasons };
|
|
1509
|
+
}
|
|
1510
|
+
const sensitiveLenses = new Set(["security", "data_integrity", "reliability"]);
|
|
1511
|
+
const hasSensitiveLens = complexity.lenses.some((lens) => sensitiveLenses.has(lens));
|
|
1512
|
+
if (complexity.priority === "low" &&
|
|
1513
|
+
complexity.total_lines <= SMALL_MODEL_HINT_MAX_LINES &&
|
|
1514
|
+
complexity.estimated_tokens <= SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS &&
|
|
1515
|
+
!hasSensitiveLens &&
|
|
1516
|
+
complexity.tags.length === 0) {
|
|
1517
|
+
return { tier: "small", reasons: ["small_low_priority_packet"] };
|
|
1518
|
+
}
|
|
1519
|
+
const reasons = [];
|
|
1520
|
+
if (complexity.priority === "medium")
|
|
1521
|
+
reasons.push("medium_priority");
|
|
1522
|
+
if (hasSensitiveLens)
|
|
1523
|
+
reasons.push("sensitive_lens");
|
|
1524
|
+
if (complexity.total_lines > SMALL_MODEL_HINT_MAX_LINES) {
|
|
1525
|
+
reasons.push("moderate_size");
|
|
1526
|
+
}
|
|
1527
|
+
return {
|
|
1528
|
+
tier: "standard",
|
|
1529
|
+
reasons: reasons.length > 0 ? reasons : ["default_review_packet"],
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function withinRoot(root, path) {
|
|
1533
|
+
const rootPath = resolve(root);
|
|
1534
|
+
const absolutePath = resolve(rootPath, path);
|
|
1535
|
+
const relativePath = relative(rootPath, absolutePath);
|
|
1536
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
1537
|
+
throw new Error(`Path '${path}' escapes repository root '${rootPath}'.`);
|
|
1538
|
+
}
|
|
1539
|
+
return absolutePath;
|
|
1540
|
+
}
|
|
1541
|
+
function renderAnchorPreview(summary, anchorPath) {
|
|
1542
|
+
const preview = summary.anchors.slice(0, 24).map((anchor) => {
|
|
1543
|
+
const location = anchor.line ? `${summary.path}:${anchor.line}` : summary.path;
|
|
1544
|
+
const detail = anchor.detail ? ` - ${anchor.detail}` : "";
|
|
1545
|
+
return `- ${location} [${anchor.kind}] ${anchor.name}${detail}`;
|
|
1546
|
+
});
|
|
1547
|
+
return [
|
|
1548
|
+
"## Large File Review Mode",
|
|
1549
|
+
"This packet is intentionally isolated because it covers one large file.",
|
|
1550
|
+
"Use targeted reads/searches within this file, guided by the mechanical anchors.",
|
|
1551
|
+
"Do not read unrelated files unless a finding cannot be evidenced without a direct boundary check.",
|
|
1552
|
+
`Anchor file: ${anchorPath}`,
|
|
1553
|
+
`Anchor counts: symbols=${summary.counts.symbols}, routes=${summary.counts.routes}, keywords=${summary.counts.keywords}, graph_edges=${summary.counts.graph_edges}, analyzer_signals=${summary.counts.analyzer_signals}, omitted=${summary.omitted_anchor_count}`,
|
|
1554
|
+
"Anchor preview:",
|
|
1555
|
+
...(preview.length > 0 ? preview : ["- no anchors extracted beyond file boundaries"]),
|
|
1556
|
+
"",
|
|
1557
|
+
];
|
|
1558
|
+
}
|
|
1407
1559
|
async function cmdPrepareDispatch(argv) {
|
|
1408
1560
|
const runId = getFlag(argv, "--run-id");
|
|
1409
1561
|
if (!runId)
|
|
@@ -1413,6 +1565,17 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1413
1565
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1414
1566
|
const taskResultsDir = join(runDir, "task-results");
|
|
1415
1567
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
1568
|
+
const explicitRoot = getFlag(argv, "--root") ? getRootDir(argv) : undefined;
|
|
1569
|
+
let reviewRoot = explicitRoot;
|
|
1570
|
+
try {
|
|
1571
|
+
const workerTask = await readJsonFile(join(runDir, "task.json"));
|
|
1572
|
+
reviewRoot ??= workerTask.repo_root;
|
|
1573
|
+
}
|
|
1574
|
+
catch (error) {
|
|
1575
|
+
if (!isFileMissingError(error)) {
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1416
1579
|
const tasks = await readJsonFile(tasksPath);
|
|
1417
1580
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1418
1581
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
@@ -1428,18 +1591,22 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1428
1591
|
lineIndex,
|
|
1429
1592
|
});
|
|
1430
1593
|
const tasksById = new Map(orderedTasks.map((task) => [task.task_id, task]));
|
|
1431
|
-
const
|
|
1594
|
+
const resultPathByTaskId = new Map(orderedTasks.map((task) => [
|
|
1432
1595
|
task.task_id,
|
|
1433
|
-
|
|
1596
|
+
taskResultPath(taskResultsDir, task.task_id),
|
|
1434
1597
|
]));
|
|
1598
|
+
const resultPathSet = new Set(resultPathByTaskId.values());
|
|
1599
|
+
if (resultPathSet.size !== resultPathByTaskId.size) {
|
|
1600
|
+
throw new Error("prepare-dispatch generated duplicate result paths; task ids must be uniquely addressable.");
|
|
1601
|
+
}
|
|
1435
1602
|
const plan = [];
|
|
1603
|
+
const resultMapEntries = [];
|
|
1436
1604
|
let largestPacketId = null;
|
|
1437
1605
|
let largestLines = 0;
|
|
1438
1606
|
let largestEstimatedTokens = 0;
|
|
1439
1607
|
const warnings = [];
|
|
1440
1608
|
for (const packet of packets) {
|
|
1441
|
-
const
|
|
1442
|
-
const promptPath = join(taskResultsDir, `${sanitized}.prompt.md`);
|
|
1609
|
+
const promptPath = packetPromptPath(taskResultsDir, packet.packet_id);
|
|
1443
1610
|
const packetTasks = packet.task_ids
|
|
1444
1611
|
.map((taskId) => tasksById.get(taskId))
|
|
1445
1612
|
.filter((task) => task !== undefined);
|
|
@@ -1448,7 +1615,8 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1448
1615
|
largestEstimatedTokens = packet.estimated_tokens;
|
|
1449
1616
|
largestPacketId = packet.packet_id;
|
|
1450
1617
|
}
|
|
1451
|
-
|
|
1618
|
+
const largeFileMode = isIsolatedLargeFilePacket(packet);
|
|
1619
|
+
if (packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES && !largeFileMode) {
|
|
1452
1620
|
warnings.push({
|
|
1453
1621
|
code: "large_packet",
|
|
1454
1622
|
message: `large packet ${packet.packet_id} (~${packet.total_lines} lines) may hit quota limits`,
|
|
@@ -1466,15 +1634,57 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1466
1634
|
const lines = packet.file_line_counts[path] ?? 0;
|
|
1467
1635
|
return `- ${path} (${lines} lines)`;
|
|
1468
1636
|
}).join("\n");
|
|
1637
|
+
let anchorPath = null;
|
|
1638
|
+
let anchorSummary = null;
|
|
1639
|
+
if (largeFileMode) {
|
|
1640
|
+
const filePath = packet.file_paths[0];
|
|
1641
|
+
if (!reviewRoot) {
|
|
1642
|
+
warnings.push({
|
|
1643
|
+
code: "large_file_anchor_unavailable",
|
|
1644
|
+
message: `large single-file packet ${packet.packet_id} has no repo root available for anchor extraction`,
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
try {
|
|
1649
|
+
const totalLines = packet.file_line_counts[filePath] ?? packet.total_lines;
|
|
1650
|
+
const content = await readFile(withinRoot(reviewRoot, filePath), "utf8");
|
|
1651
|
+
anchorSummary = buildFileAnchorSummary({
|
|
1652
|
+
path: filePath,
|
|
1653
|
+
content,
|
|
1654
|
+
totalLines,
|
|
1655
|
+
graphBundle: bundle.graph_bundle,
|
|
1656
|
+
externalAnalyzerResults: bundle.external_analyzer_results,
|
|
1657
|
+
});
|
|
1658
|
+
anchorPath = join(taskResultsDir, artifactNameForId(packet.packet_id, "anchors.json"));
|
|
1659
|
+
await writeJsonFile(anchorPath, anchorSummary);
|
|
1660
|
+
}
|
|
1661
|
+
catch (error) {
|
|
1662
|
+
warnings.push({
|
|
1663
|
+
code: "large_file_anchor_failed",
|
|
1664
|
+
message: `large single-file packet ${packet.packet_id} could not be anchored mechanically: ` +
|
|
1665
|
+
(error instanceof Error ? error.message : String(error)),
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
const largeFileSection = anchorSummary && anchorPath
|
|
1671
|
+
? renderAnchorPreview(anchorSummary, anchorPath)
|
|
1672
|
+
: largeFileMode
|
|
1673
|
+
? [
|
|
1674
|
+
"## Large File Review Mode",
|
|
1675
|
+
"This packet is intentionally isolated because it covers one large file.",
|
|
1676
|
+
"Use targeted reads/searches within this file only.",
|
|
1677
|
+
"No mechanical anchor file was available, so rely on targeted symbol and keyword searches before reading broad ranges.",
|
|
1678
|
+
"",
|
|
1679
|
+
]
|
|
1680
|
+
: [];
|
|
1469
1681
|
const taskSections = packetTasks.flatMap((task) => {
|
|
1470
1682
|
const lensDef = lensDefs[task.lens];
|
|
1471
|
-
const outputPath = outputPathByTaskId.get(task.task_id);
|
|
1472
1683
|
return [
|
|
1473
1684
|
`### ${task.task_id}`,
|
|
1474
1685
|
`unit_id: ${task.unit_id}`,
|
|
1475
1686
|
`pass_id: ${task.pass_id}`,
|
|
1476
1687
|
`lens: ${task.lens}`,
|
|
1477
|
-
`output_path: ${outputPath}`,
|
|
1478
1688
|
`rationale: ${task.rationale}`,
|
|
1479
1689
|
"",
|
|
1480
1690
|
`Lens guidance: ${lensDef?.description ?? task.lens}`,
|
|
@@ -1482,13 +1692,20 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1482
1692
|
"",
|
|
1483
1693
|
];
|
|
1484
1694
|
});
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1695
|
+
const submitCommand = `"${process.execPath}" "${join(packageRoot, "audit-code.mjs")}" submit-packet ` +
|
|
1696
|
+
`--run-id-b64 ${toBase64Url(runId)} ` +
|
|
1697
|
+
`--packet-id-b64 ${toBase64Url(packet.packet_id)} ` +
|
|
1698
|
+
`--artifacts-dir-b64 ${toBase64Url(artifactsDir)}`;
|
|
1699
|
+
const complexity = buildDispatchComplexity(packet, largeFileMode);
|
|
1700
|
+
for (const task of packetTasks) {
|
|
1701
|
+
resultMapEntries.push({
|
|
1702
|
+
packet_id: packet.packet_id,
|
|
1703
|
+
task_id: task.task_id,
|
|
1704
|
+
result_path: resultPathByTaskId.get(task.task_id),
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1490
1707
|
const prompt = [
|
|
1491
|
-
"You are a code auditor. Review this packet once, then
|
|
1708
|
+
"You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
|
|
1492
1709
|
"",
|
|
1493
1710
|
"## Packet",
|
|
1494
1711
|
`packet_id: ${packet.packet_id}`,
|
|
@@ -1497,15 +1714,18 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1497
1714
|
`estimated_tokens: ${packet.estimated_tokens}`,
|
|
1498
1715
|
"",
|
|
1499
1716
|
"## Files to read",
|
|
1500
|
-
|
|
1717
|
+
largeFileMode
|
|
1718
|
+
? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
|
|
1719
|
+
: "Use your Read tool. Paths are repo-relative from the current working directory.",
|
|
1501
1720
|
fileList,
|
|
1502
1721
|
"",
|
|
1722
|
+
...largeFileSection,
|
|
1503
1723
|
"## Tasks",
|
|
1504
1724
|
...taskSections,
|
|
1505
1725
|
"## Output",
|
|
1506
|
-
"
|
|
1507
|
-
"Do not combine the task results into one file. Do not edit source files,",
|
|
1726
|
+
"Do not write files directly. Do not use a Write tool, create temp files, edit source files,",
|
|
1508
1727
|
"remediate findings, create extra task results, or run unrelated audits.",
|
|
1728
|
+
"Produce one JSON array containing exactly one AuditResult object for each listed task.",
|
|
1509
1729
|
"",
|
|
1510
1730
|
"Required AuditResult fields:",
|
|
1511
1731
|
" task_id copy from the task metadata",
|
|
@@ -1532,30 +1752,32 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1532
1752
|
"3. Only reference files from the packet unless a finding genuinely crosses a boundary.",
|
|
1533
1753
|
"4. findings: [] is correct when you find nothing genuine.",
|
|
1534
1754
|
"",
|
|
1535
|
-
"##
|
|
1536
|
-
"
|
|
1537
|
-
|
|
1755
|
+
"## Submit",
|
|
1756
|
+
"Pipe the JSON array on stdin to this command:",
|
|
1757
|
+
` ${submitCommand}`,
|
|
1538
1758
|
"",
|
|
1539
|
-
"
|
|
1759
|
+
"The command validates and writes the packet-owned result files. Exit 0 means accepted.",
|
|
1760
|
+
"Non-zero: read the errors, fix the JSON, and run the same submit command again. Retry up to 3 times.",
|
|
1540
1761
|
"",
|
|
1541
1762
|
"## Final response",
|
|
1542
|
-
`After
|
|
1763
|
+
`After the submit command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
|
|
1543
1764
|
].join("\n");
|
|
1544
1765
|
await writeFile(promptPath, prompt, "utf8");
|
|
1545
1766
|
plan.push({
|
|
1546
1767
|
packet_id: packet.packet_id,
|
|
1547
|
-
|
|
1548
|
-
|
|
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,
|
|
1768
|
+
description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)` +
|
|
1769
|
+
(largeFileMode ? " [isolated large-file mode]" : ""),
|
|
1551
1770
|
prompt_path: promptPath,
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
total_lines: packet.total_lines,
|
|
1555
|
-
estimated_tokens: packet.estimated_tokens,
|
|
1771
|
+
complexity,
|
|
1772
|
+
model_hint: buildDispatchModelHint(complexity),
|
|
1556
1773
|
});
|
|
1557
1774
|
}
|
|
1558
1775
|
await writeJsonFile(dispatchPlanPath, plan);
|
|
1776
|
+
await writeJsonFile(dispatchResultMapPath(runDir), {
|
|
1777
|
+
contract_version: "audit-code-dispatch-results/v1alpha1",
|
|
1778
|
+
run_id: runId,
|
|
1779
|
+
entries: resultMapEntries,
|
|
1780
|
+
});
|
|
1559
1781
|
const warningsPath = warnings.length > 0
|
|
1560
1782
|
? join(runDir, "dispatch-warnings.json")
|
|
1561
1783
|
: null;
|
|
@@ -1578,6 +1800,106 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1578
1800
|
dispatch_warnings_path: warningsPath,
|
|
1579
1801
|
}, null, 2));
|
|
1580
1802
|
}
|
|
1803
|
+
async function cmdSubmitPacket(argv) {
|
|
1804
|
+
const runId = resolveRunScopedArg(argv, "--run-id", "--run-id-b64");
|
|
1805
|
+
const packetId = resolveRunScopedArg(argv, "--packet-id", "--packet-id-b64");
|
|
1806
|
+
const artifactsDirB64 = getFlag(argv, "--artifacts-dir-b64");
|
|
1807
|
+
const artifactsDir = artifactsDirB64
|
|
1808
|
+
? resolve(fromBase64Url(artifactsDirB64))
|
|
1809
|
+
: getArtifactsDir(argv);
|
|
1810
|
+
if (!runId || !packetId) {
|
|
1811
|
+
throw new Error("submit-packet requires --run-id and --packet-id (or --run-id-b64/--packet-id-b64)");
|
|
1812
|
+
}
|
|
1813
|
+
const runDir = join(artifactsDir, "runs", runId);
|
|
1814
|
+
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1815
|
+
const resultMap = await loadDispatchResultMap(runDir);
|
|
1816
|
+
if (!resultMap) {
|
|
1817
|
+
throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
|
|
1818
|
+
}
|
|
1819
|
+
const packetEntries = resultMap.entries.filter((entry) => entry.packet_id === packetId);
|
|
1820
|
+
if (packetEntries.length === 0) {
|
|
1821
|
+
throw new Error(`Unknown packet_id '${packetId}' for run ${runId}.`);
|
|
1822
|
+
}
|
|
1823
|
+
if (entriesByTaskId(packetEntries).size !== packetEntries.length) {
|
|
1824
|
+
throw new Error(`Dispatch result map has duplicate task entries for packet '${packetId}'.`);
|
|
1825
|
+
}
|
|
1826
|
+
const allTasks = await readJsonFile(tasksPath);
|
|
1827
|
+
const taskById = new Map(allTasks.map((task) => [task.task_id, task]));
|
|
1828
|
+
const packetTasks = packetEntries.map((entry) => taskById.get(entry.task_id));
|
|
1829
|
+
const missingTask = packetEntries.find((entry, index) => !packetTasks[index]);
|
|
1830
|
+
if (missingTask) {
|
|
1831
|
+
throw new Error(`Dispatch result map references unknown task '${missingTask.task_id}'.`);
|
|
1832
|
+
}
|
|
1833
|
+
const tasks = packetTasks;
|
|
1834
|
+
const expectedTaskIds = new Set(tasks.map((task) => task.task_id));
|
|
1835
|
+
const lineIndex = Object.fromEntries(tasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
1836
|
+
const encodedResults = getFlag(argv, "--results-b64");
|
|
1837
|
+
const raw = encodedResults ? fromBase64Url(encodedResults) : await readStdinText();
|
|
1838
|
+
if (raw.trim().length === 0) {
|
|
1839
|
+
throw new Error("submit-packet requires an AuditResult[] JSON payload on stdin or --results-b64.");
|
|
1840
|
+
}
|
|
1841
|
+
let payload;
|
|
1842
|
+
try {
|
|
1843
|
+
payload = JSON.parse(raw);
|
|
1844
|
+
}
|
|
1845
|
+
catch (error) {
|
|
1846
|
+
throw new Error(`Invalid submit-packet JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
1847
|
+
}
|
|
1848
|
+
const resultErrors = [];
|
|
1849
|
+
const issues = validateAuditResults(payload, tasks, { lineIndex });
|
|
1850
|
+
const validationErrors = issues.filter((issue) => issue.severity === "error");
|
|
1851
|
+
const validationWarnings = issues.filter((issue) => issue.severity === "warning");
|
|
1852
|
+
if (validationWarnings.length > 0) {
|
|
1853
|
+
process.stderr.write(`audit-results validation: ${validationWarnings.length} warning(s):\n` +
|
|
1854
|
+
formatAuditResultIssues(validationWarnings) +
|
|
1855
|
+
"\n");
|
|
1856
|
+
}
|
|
1857
|
+
if (validationErrors.length > 0) {
|
|
1858
|
+
resultErrors.push(formatAuditResultIssues(validationErrors));
|
|
1859
|
+
}
|
|
1860
|
+
if (Array.isArray(payload)) {
|
|
1861
|
+
const seen = new Set();
|
|
1862
|
+
for (const [index, result] of payload.entries()) {
|
|
1863
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
const taskId = result.task_id;
|
|
1867
|
+
if (typeof taskId !== "string" || taskId.trim().length === 0) {
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
if (seen.has(taskId)) {
|
|
1871
|
+
resultErrors.push(`Duplicate audit result for assigned task '${taskId}'.`);
|
|
1872
|
+
}
|
|
1873
|
+
seen.add(taskId);
|
|
1874
|
+
if (!expectedTaskIds.has(taskId)) {
|
|
1875
|
+
resultErrors.push(`Result at index ${index} uses task_id '${taskId}', which is not assigned to packet '${packetId}'.`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
for (const task of tasks) {
|
|
1879
|
+
if (!seen.has(task.task_id)) {
|
|
1880
|
+
resultErrors.push(`Missing audit result for assigned task '${task.task_id}'.`);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
if (resultErrors.length > 0) {
|
|
1885
|
+
throw new Error(`submit-packet rejected ${packetId}:\n${resultErrors.join("\n")}`);
|
|
1886
|
+
}
|
|
1887
|
+
const entryByTaskId = entriesByTaskId(packetEntries);
|
|
1888
|
+
for (const result of payload) {
|
|
1889
|
+
const entry = entryByTaskId.get(result.task_id);
|
|
1890
|
+
if (!entry) {
|
|
1891
|
+
throw new Error(`Internal error: no result path for accepted task '${result.task_id}'.`);
|
|
1892
|
+
}
|
|
1893
|
+
await writeJsonFile(entry.result_path, result);
|
|
1894
|
+
}
|
|
1895
|
+
const findingCount = payload.reduce((sum, result) => sum + result.findings.length, 0);
|
|
1896
|
+
console.log(JSON.stringify({
|
|
1897
|
+
run_id: runId,
|
|
1898
|
+
packet_id: packetId,
|
|
1899
|
+
accepted_count: payload.length,
|
|
1900
|
+
finding_count: findingCount,
|
|
1901
|
+
}, null, 2));
|
|
1902
|
+
}
|
|
1581
1903
|
async function cmdMergeAndIngest(argv) {
|
|
1582
1904
|
const runId = getFlag(argv, "--run-id");
|
|
1583
1905
|
if (!runId)
|
|
@@ -1589,12 +1911,20 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1589
1911
|
const taskPath = join(runDir, "task.json");
|
|
1590
1912
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1591
1913
|
const workerTask = await readJsonFile(taskPath);
|
|
1914
|
+
const resultMap = await loadDispatchResultMap(runDir);
|
|
1915
|
+
if (!resultMap) {
|
|
1916
|
+
throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
|
|
1917
|
+
}
|
|
1592
1918
|
let allTasks = [];
|
|
1593
1919
|
try {
|
|
1594
1920
|
allTasks = await readJsonFile(tasksPath);
|
|
1595
1921
|
}
|
|
1596
1922
|
catch { /* may not exist */ }
|
|
1597
|
-
const
|
|
1923
|
+
const entryByTaskId = entriesByTaskId(resultMap.entries);
|
|
1924
|
+
if (entryByTaskId.size !== resultMap.entries.length) {
|
|
1925
|
+
throw new Error(`Dispatch result map for run ${runId} contains duplicate task entries.`);
|
|
1926
|
+
}
|
|
1927
|
+
const expectedPaths = new Set(resultMap.entries.map((entry) => resolve(entry.result_path)));
|
|
1598
1928
|
let files;
|
|
1599
1929
|
try {
|
|
1600
1930
|
files = (await readdir(taskResultsDir)).filter(f => f.endsWith(".json")).sort();
|
|
@@ -1606,13 +1936,38 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1606
1936
|
const failing = [];
|
|
1607
1937
|
const seenTaskIds = new Set();
|
|
1608
1938
|
for (const filename of files) {
|
|
1609
|
-
const filePath = join(taskResultsDir, filename);
|
|
1939
|
+
const filePath = resolve(join(taskResultsDir, filename));
|
|
1940
|
+
if (!expectedPaths.has(filePath)) {
|
|
1941
|
+
failing.push({
|
|
1942
|
+
task_id: filename,
|
|
1943
|
+
errors: ["Unexpected task result file; only backend-assigned result paths may be ingested."],
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
for (const task of allTasks) {
|
|
1948
|
+
const entry = entryByTaskId.get(task.task_id);
|
|
1949
|
+
if (!entry) {
|
|
1950
|
+
failing.push({
|
|
1951
|
+
task_id: task.task_id,
|
|
1952
|
+
errors: ["Missing dispatch result-map entry for assigned task."],
|
|
1953
|
+
});
|
|
1954
|
+
continue;
|
|
1955
|
+
}
|
|
1956
|
+
const filePath = entry.result_path;
|
|
1610
1957
|
let obj;
|
|
1611
1958
|
try {
|
|
1612
1959
|
obj = JSON.parse(await readFile(filePath, "utf8"));
|
|
1613
1960
|
}
|
|
1614
1961
|
catch (e) {
|
|
1615
|
-
|
|
1962
|
+
if (isFileMissingError(e)) {
|
|
1963
|
+
failing.push({
|
|
1964
|
+
task_id: task.task_id,
|
|
1965
|
+
errors: ["Missing audit result for assigned task."],
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
else {
|
|
1969
|
+
failing.push({ task_id: task.task_id, errors: [`Invalid JSON: ${e.message}`] });
|
|
1970
|
+
}
|
|
1616
1971
|
continue;
|
|
1617
1972
|
}
|
|
1618
1973
|
const record = obj && typeof obj === "object" && !Array.isArray(obj)
|
|
@@ -1628,8 +1983,11 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1628
1983
|
else {
|
|
1629
1984
|
seenTaskIds.add(taskId);
|
|
1630
1985
|
}
|
|
1986
|
+
if (taskId !== task.task_id) {
|
|
1987
|
+
resultErrors.push(`Result file is assigned to '${task.task_id}' but contains task_id '${taskId}'.`);
|
|
1988
|
+
}
|
|
1631
1989
|
}
|
|
1632
|
-
const issues = validateAuditResults([obj],
|
|
1990
|
+
const issues = validateAuditResults([obj], [task], { lineIndex: task.file_line_counts ?? {} });
|
|
1633
1991
|
resultErrors.push(...issues
|
|
1634
1992
|
.filter(i => i.severity === "error")
|
|
1635
1993
|
.map(i => i.message));
|
|
@@ -1637,15 +1995,7 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1637
1995
|
passing.push(obj);
|
|
1638
1996
|
}
|
|
1639
1997
|
else {
|
|
1640
|
-
failing.push({ task_id: taskId ??
|
|
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
|
-
});
|
|
1998
|
+
failing.push({ task_id: taskId ?? task.task_id, errors: resultErrors });
|
|
1649
1999
|
}
|
|
1650
2000
|
}
|
|
1651
2001
|
await writeJsonFile(auditResultsPath, passing);
|
|
@@ -1687,14 +2037,25 @@ async function cmdMergeAndIngest(argv) {
|
|
|
1687
2037
|
}, null, 2));
|
|
1688
2038
|
}
|
|
1689
2039
|
async function cmdValidateResult(argv) {
|
|
1690
|
-
const
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
const
|
|
1695
|
-
const
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
2040
|
+
const rawRunId = getFlag(argv, "--run-id");
|
|
2041
|
+
const runIdB64 = getFlag(argv, "--run-id-b64");
|
|
2042
|
+
const rawTaskId = getFlag(argv, "--task-id");
|
|
2043
|
+
const artifactsDirB64 = getFlag(argv, "--artifacts-dir-b64");
|
|
2044
|
+
const runId = rawRunId ?? (runIdB64 ? fromBase64Url(runIdB64) : undefined);
|
|
2045
|
+
const taskIdB64 = getFlag(argv, "--task-id-b64");
|
|
2046
|
+
const taskId = rawTaskId ?? (taskIdB64 ? fromBase64Url(taskIdB64) : undefined);
|
|
2047
|
+
const artifactsDir = artifactsDirB64
|
|
2048
|
+
? resolve(fromBase64Url(artifactsDirB64))
|
|
2049
|
+
: getArtifactsDir(argv);
|
|
2050
|
+
if (!runId || !taskId) {
|
|
2051
|
+
throw new Error("validate-result requires --run-id and --task-id (or --run-id-b64/--task-id-b64)");
|
|
2052
|
+
}
|
|
2053
|
+
const runDir = join(artifactsDir, "runs", runId);
|
|
2054
|
+
const taskResultsDir = join(runDir, "task-results");
|
|
2055
|
+
const resultMap = await loadDispatchResultMap(runDir);
|
|
2056
|
+
const resultPath = resultMap?.entries.find((entry) => entry.task_id === taskId)?.result_path ??
|
|
2057
|
+
taskResultPath(taskResultsDir, taskId);
|
|
2058
|
+
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1698
2059
|
let raw;
|
|
1699
2060
|
try {
|
|
1700
2061
|
raw = await readFile(resultPath, "utf8");
|
|
@@ -1867,7 +2228,7 @@ async function cmdValidate(argv) {
|
|
|
1867
2228
|
...providerIssues,
|
|
1868
2229
|
];
|
|
1869
2230
|
const resolvedProvider = rawSessionConfig === undefined
|
|
1870
|
-
? "
|
|
2231
|
+
? "local-subprocess"
|
|
1871
2232
|
: sessionConfigIssues.length > 0
|
|
1872
2233
|
? null
|
|
1873
2234
|
: resolveFreshSessionProviderName(undefined, rawSessionConfig);
|
|
@@ -1986,12 +2347,15 @@ async function main(argv) {
|
|
|
1986
2347
|
case "merge-and-ingest":
|
|
1987
2348
|
await cmdMergeAndIngest(argv);
|
|
1988
2349
|
return;
|
|
2350
|
+
case "submit-packet":
|
|
2351
|
+
await cmdSubmitPacket(argv);
|
|
2352
|
+
return;
|
|
1989
2353
|
case "validate-result":
|
|
1990
2354
|
await cmdValidateResult(argv);
|
|
1991
2355
|
return;
|
|
1992
2356
|
default:
|
|
1993
2357
|
console.error(`Unknown command: ${command}`);
|
|
1994
|
-
console.error("Available commands: sample-run, advance-audit, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, mcp, prepare-dispatch, merge-and-ingest, validate-result");
|
|
2358
|
+
console.error("Available commands: sample-run, advance-audit, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result");
|
|
1995
2359
|
process.exitCode = 1;
|
|
1996
2360
|
}
|
|
1997
2361
|
}
|