auditor-lambda 0.3.4 → 0.3.5
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 +9 -2
- package/dist/cli.js +359 -53
- 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 +4 -1
- package/docs/contract.md +3 -0
- package/docs/dispatch-implementation-plan.md +57 -24
- 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/audit-code.prompt.md +11 -6
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const MAX_ANCHORS = 160;
|
|
2
|
+
const KEYWORD_PATTERN = /\b(auth|token|password|secret|permission|role|sql|query|exec|spawn|eval|deserialize|encrypt|decrypt|cache|retry|timeout|transaction|lock|race|TODO|FIXME)\b/i;
|
|
3
|
+
const SYMBOL_PATTERNS = [
|
|
4
|
+
{
|
|
5
|
+
kind: "import",
|
|
6
|
+
pattern: /^\s*import\s+(?:type\s+)?(?:[^"'()]*?\s+from\s+)?["']([^"']+)["']/,
|
|
7
|
+
label: "import",
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
kind: "symbol",
|
|
11
|
+
pattern: /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\b/,
|
|
12
|
+
label: "function",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
kind: "symbol",
|
|
16
|
+
pattern: /^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b/,
|
|
17
|
+
label: "class",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
kind: "symbol",
|
|
21
|
+
pattern: /^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)\b/,
|
|
22
|
+
label: "interface",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
kind: "symbol",
|
|
26
|
+
pattern: /^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\b/,
|
|
27
|
+
label: "type",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
kind: "symbol",
|
|
31
|
+
pattern: /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/,
|
|
32
|
+
label: "binding",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
kind: "export",
|
|
36
|
+
pattern: /^\s*export\s+(?:type\s+)?(?:[^"'()]*?\s+from\s+["']([^"']+)["']|(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type)\s+([A-Za-z_$][\w$-]*))/,
|
|
37
|
+
label: "export",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
kind: "symbol",
|
|
41
|
+
pattern: /^\s*(?:export\s+)?def\s+([A-Za-z_][\w]*)\b/,
|
|
42
|
+
label: "function",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: "symbol",
|
|
46
|
+
pattern: /^\s*(?:export\s+)?func\s+(?:\([^)]+\)\s*)?([A-Za-z_][\w]*)\b/,
|
|
47
|
+
label: "function",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
kind: "symbol",
|
|
51
|
+
pattern: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\b/,
|
|
52
|
+
label: "function",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
kind: "route",
|
|
56
|
+
pattern: /\b(?:app|router|server)\s*\.\s*(get|post|put|patch|delete|use)\s*\(/i,
|
|
57
|
+
label: "route",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
kind: "route",
|
|
61
|
+
pattern: /^\s*@(?:Get|Post|Put|Patch|Delete|Route|Controller)\b/,
|
|
62
|
+
label: "route",
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
function normalizePath(path) {
|
|
66
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
67
|
+
}
|
|
68
|
+
function truncate(value, maxLength) {
|
|
69
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
70
|
+
return normalized.length > maxLength
|
|
71
|
+
? `${normalized.slice(0, maxLength - 3)}...`
|
|
72
|
+
: normalized;
|
|
73
|
+
}
|
|
74
|
+
function addAnchor(anchors, seen, anchor) {
|
|
75
|
+
const key = `${anchor.kind}\0${anchor.line ?? ""}\0${anchor.name}\0${anchor.detail ?? ""}`;
|
|
76
|
+
if (seen.has(key)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
seen.add(key);
|
|
80
|
+
anchors.push(anchor);
|
|
81
|
+
}
|
|
82
|
+
function collectGraphEdges(graphBundle, path) {
|
|
83
|
+
if (!graphBundle?.graphs) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const normalizedPath = normalizePath(path).toLowerCase();
|
|
87
|
+
const edges = [];
|
|
88
|
+
for (const key of ["imports", "calls", "references"]) {
|
|
89
|
+
const raw = graphBundle.graphs[key];
|
|
90
|
+
if (!Array.isArray(raw)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
for (const item of raw) {
|
|
94
|
+
const record = item;
|
|
95
|
+
if (item &&
|
|
96
|
+
typeof item === "object" &&
|
|
97
|
+
!Array.isArray(item) &&
|
|
98
|
+
typeof record.from === "string" &&
|
|
99
|
+
typeof record.to === "string") {
|
|
100
|
+
const from = normalizePath(record.from).toLowerCase();
|
|
101
|
+
const to = normalizePath(record.to).toLowerCase();
|
|
102
|
+
if (from === normalizedPath || to === normalizedPath) {
|
|
103
|
+
edges.push({
|
|
104
|
+
from: record.from,
|
|
105
|
+
to: record.to,
|
|
106
|
+
kind: typeof record.kind === "string" ? record.kind : key,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return edges.sort((a, b) => (a.kind ?? "").localeCompare(b.kind ?? "") ||
|
|
113
|
+
a.from.localeCompare(b.from) ||
|
|
114
|
+
a.to.localeCompare(b.to));
|
|
115
|
+
}
|
|
116
|
+
export function buildFileAnchorSummary(params) {
|
|
117
|
+
const anchors = [];
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const path = normalizePath(params.path);
|
|
120
|
+
const lines = params.content.split(/\r?\n/);
|
|
121
|
+
let symbolCount = 0;
|
|
122
|
+
let routeCount = 0;
|
|
123
|
+
let keywordCount = 0;
|
|
124
|
+
addAnchor(anchors, seen, {
|
|
125
|
+
kind: "boundary",
|
|
126
|
+
name: "file_start",
|
|
127
|
+
line: 1,
|
|
128
|
+
detail: "Start of isolated large-file review boundary.",
|
|
129
|
+
});
|
|
130
|
+
if (params.totalLines > 1) {
|
|
131
|
+
addAnchor(anchors, seen, {
|
|
132
|
+
kind: "boundary",
|
|
133
|
+
name: "file_end",
|
|
134
|
+
line: params.totalLines,
|
|
135
|
+
detail: "End of isolated large-file review boundary.",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
lines.forEach((line, index) => {
|
|
139
|
+
const lineNumber = index + 1;
|
|
140
|
+
for (const { kind, pattern, label } of SYMBOL_PATTERNS) {
|
|
141
|
+
const match = line.match(pattern);
|
|
142
|
+
if (!match) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const name = match.slice(1).find((value) => value && value.trim().length > 0) ?? label;
|
|
146
|
+
if (kind === "route") {
|
|
147
|
+
routeCount += 1;
|
|
148
|
+
}
|
|
149
|
+
else if (kind === "symbol") {
|
|
150
|
+
symbolCount += 1;
|
|
151
|
+
}
|
|
152
|
+
addAnchor(anchors, seen, {
|
|
153
|
+
kind,
|
|
154
|
+
name: truncate(name, 80),
|
|
155
|
+
line: lineNumber,
|
|
156
|
+
detail: truncate(`${label}: ${line}`, 180),
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
if (KEYWORD_PATTERN.test(line)) {
|
|
161
|
+
keywordCount += 1;
|
|
162
|
+
addAnchor(anchors, seen, {
|
|
163
|
+
kind: "keyword",
|
|
164
|
+
name: truncate(line.match(KEYWORD_PATTERN)?.[1] ?? "keyword", 80),
|
|
165
|
+
line: lineNumber,
|
|
166
|
+
detail: truncate(line, 180),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const graphEdges = collectGraphEdges(params.graphBundle, path);
|
|
171
|
+
for (const edge of graphEdges) {
|
|
172
|
+
addAnchor(anchors, seen, {
|
|
173
|
+
kind: "graph",
|
|
174
|
+
name: edge.kind ?? "edge",
|
|
175
|
+
detail: normalizePath(edge.from).toLowerCase() === path.toLowerCase()
|
|
176
|
+
? `outbound: ${edge.to}`
|
|
177
|
+
: `inbound: ${edge.from}`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const analyzerSignals = (params.externalAnalyzerResults?.results ?? [])
|
|
181
|
+
.filter((result) => normalizePath(result.path).toLowerCase() === path.toLowerCase())
|
|
182
|
+
.sort((a, b) => (a.line_start ?? 0) - (b.line_start ?? 0) ||
|
|
183
|
+
a.id.localeCompare(b.id));
|
|
184
|
+
for (const signal of analyzerSignals) {
|
|
185
|
+
addAnchor(anchors, seen, {
|
|
186
|
+
kind: "analyzer_signal",
|
|
187
|
+
name: truncate(signal.rule ?? signal.category, 80),
|
|
188
|
+
line: signal.line_start,
|
|
189
|
+
detail: truncate(signal.summary, 180),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const sorted = anchors.sort((a, b) => (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER) ||
|
|
193
|
+
a.kind.localeCompare(b.kind) ||
|
|
194
|
+
a.name.localeCompare(b.name));
|
|
195
|
+
const boundedAnchors = sorted.slice(0, MAX_ANCHORS);
|
|
196
|
+
return {
|
|
197
|
+
contract_version: "audit-code-file-anchors/v1alpha1",
|
|
198
|
+
path,
|
|
199
|
+
total_lines: params.totalLines,
|
|
200
|
+
review_mode: "isolated_large_file",
|
|
201
|
+
scope_basis: [
|
|
202
|
+
"single assigned file",
|
|
203
|
+
"single review packet",
|
|
204
|
+
"mechanically extracted symbols, routes, graph edges, keywords, and analyzer signals",
|
|
205
|
+
"backend-owned submit-packet result write path",
|
|
206
|
+
],
|
|
207
|
+
anchors: boundedAnchors,
|
|
208
|
+
omitted_anchor_count: Math.max(0, sorted.length - boundedAnchors.length),
|
|
209
|
+
counts: {
|
|
210
|
+
symbols: symbolCount,
|
|
211
|
+
routes: routeCount,
|
|
212
|
+
keywords: keywordCount,
|
|
213
|
+
graph_edges: graphEdges.length,
|
|
214
|
+
analyzer_signals: analyzerSignals.length,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -105,6 +105,16 @@ function chunkPacketTasks(tasks, options) {
|
|
|
105
105
|
const chunks = [];
|
|
106
106
|
let current = [];
|
|
107
107
|
for (const task of tasks.sort(compareTasksForPacket)) {
|
|
108
|
+
const isolatedLargeFileTask = task.file_paths.length === 1 &&
|
|
109
|
+
taskLineCount(task, options.lineIndex) > options.targetPacketLines;
|
|
110
|
+
if (isolatedLargeFileTask) {
|
|
111
|
+
if (current.length > 0) {
|
|
112
|
+
chunks.push(current);
|
|
113
|
+
current = [];
|
|
114
|
+
}
|
|
115
|
+
chunks.push([task]);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
108
118
|
const candidate = [...current, task];
|
|
109
119
|
const uniquePaths = new Set(candidate.flatMap((item) => item.file_paths));
|
|
110
120
|
const candidateLines = [...uniquePaths].reduce((sum, path) => {
|
|
@@ -21,7 +21,9 @@ export class ClaudeCodeProvider {
|
|
|
21
21
|
"-p",
|
|
22
22
|
prompt,
|
|
23
23
|
...(this.config.extra_args ?? []),
|
|
24
|
-
|
|
24
|
+
...(this.config.dangerously_skip_permissions
|
|
25
|
+
? ["--dangerously-skip-permissions"]
|
|
26
|
+
: []),
|
|
25
27
|
];
|
|
26
28
|
return await this.launchCommand(command, args, input);
|
|
27
29
|
}
|
package/dist/providers/index.js
CHANGED
|
@@ -9,7 +9,8 @@ function hasEntries(values) {
|
|
|
9
9
|
}
|
|
10
10
|
function hasConfiguredClaudeCode(sessionConfig) {
|
|
11
11
|
return (Boolean(sessionConfig.claude_code?.command?.trim()) ||
|
|
12
|
-
hasEntries(sessionConfig.claude_code?.extra_args)
|
|
12
|
+
hasEntries(sessionConfig.claude_code?.extra_args) ||
|
|
13
|
+
sessionConfig.claude_code?.dangerously_skip_permissions === true);
|
|
13
14
|
}
|
|
14
15
|
function hasConfiguredOpenCode(sessionConfig) {
|
|
15
16
|
return (Boolean(sessionConfig.opencode?.command?.trim()) ||
|
|
@@ -69,13 +69,7 @@ function buildSuggestedInputs(artifactsDir, status, isConfigError, activeReviewR
|
|
|
69
69
|
return [];
|
|
70
70
|
}
|
|
71
71
|
if (activeReviewRun) {
|
|
72
|
-
return [
|
|
73
|
-
{
|
|
74
|
-
flag: "--results",
|
|
75
|
-
suggested_path: activeReviewRun.audit_results_path,
|
|
76
|
-
description: "Write structured audit-review results for the currently dispatched run, then execute the exact worker command below to ingest them.",
|
|
77
|
-
},
|
|
78
|
-
];
|
|
72
|
+
return [];
|
|
79
73
|
}
|
|
80
74
|
const incomingDir = join(artifactsDir, INCOMING_DIRNAME);
|
|
81
75
|
return [
|
|
@@ -101,12 +95,21 @@ function buildSuggestedInputs(artifactsDir, status, isConfigError, activeReviewR
|
|
|
101
95
|
},
|
|
102
96
|
];
|
|
103
97
|
}
|
|
104
|
-
function buildSuggestedCommands(suggestedInputs, status, activeReviewRun) {
|
|
98
|
+
function buildSuggestedCommands(artifactsDir, suggestedInputs, status, activeReviewRun) {
|
|
105
99
|
if (status !== BLOCKED_STATUS) {
|
|
106
100
|
return [];
|
|
107
101
|
}
|
|
108
102
|
if (activeReviewRun) {
|
|
109
|
-
return [
|
|
103
|
+
return [
|
|
104
|
+
renderShellCommand([
|
|
105
|
+
"audit-code",
|
|
106
|
+
"prepare-dispatch",
|
|
107
|
+
"--run-id",
|
|
108
|
+
activeReviewRun.run_id,
|
|
109
|
+
"--artifacts-dir",
|
|
110
|
+
artifactsDir,
|
|
111
|
+
]),
|
|
112
|
+
];
|
|
110
113
|
}
|
|
111
114
|
return suggestedInputs.map((item) => `audit-code ${item.flag} ${quoteShellPath(item.suggested_path)}`);
|
|
112
115
|
}
|
|
@@ -216,17 +219,25 @@ export function buildAuditCodeHandoff(params) {
|
|
|
216
219
|
summary: buildSummary(params.state.status, params.providerName ?? null, params.progressSummary),
|
|
217
220
|
pending_obligations: buildPendingObligations(params.state),
|
|
218
221
|
suggested_inputs: suggestedInputs,
|
|
219
|
-
suggested_commands: buildSuggestedCommands(suggestedInputs, params.state.status, params.activeReviewRun),
|
|
222
|
+
suggested_commands: buildSuggestedCommands(params.artifactsDir, suggestedInputs, params.state.status, params.activeReviewRun),
|
|
220
223
|
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config, isConfigError),
|
|
221
224
|
artifact_paths: artifactPaths,
|
|
222
225
|
active_review_run: params.activeReviewRun,
|
|
223
226
|
};
|
|
224
227
|
// Add quick_start command and file map when blocked for review
|
|
225
228
|
if (params.state.status === BLOCKED_STATUS && params.activeReviewRun) {
|
|
226
|
-
handoff.quick_start =
|
|
229
|
+
handoff.quick_start = renderShellCommand([
|
|
230
|
+
"audit-code",
|
|
231
|
+
"prepare-dispatch",
|
|
232
|
+
"--run-id",
|
|
233
|
+
params.activeReviewRun.run_id,
|
|
234
|
+
"--artifacts-dir",
|
|
235
|
+
params.artifactsDir,
|
|
236
|
+
]);
|
|
227
237
|
handoff.file_map = {
|
|
228
238
|
current_task: artifactPaths.current_task,
|
|
229
239
|
current_prompt: artifactPaths.current_prompt,
|
|
240
|
+
dispatch_plan: join(params.artifactsDir, "runs", params.activeReviewRun.run_id, "dispatch-plan.json"),
|
|
230
241
|
audit_results: params.activeReviewRun.audit_results_path,
|
|
231
242
|
final_report: join(params.root, "audit-report.md"),
|
|
232
243
|
};
|
|
@@ -57,6 +57,20 @@ function validateRequiredStringField(value, label, taskId, resultIndex, issues)
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
function validateExpectedStringField(value, label, expected, taskId, resultIndex, issues) {
|
|
61
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (value !== expected) {
|
|
65
|
+
pushIssue(issues, {
|
|
66
|
+
result_index: resultIndex,
|
|
67
|
+
task_id: taskId,
|
|
68
|
+
field: label,
|
|
69
|
+
message: `${label} must match the assigned task metadata ` +
|
|
70
|
+
`(expected '${expected}', got '${value}').`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
60
74
|
function validateFinding(finding, label, taskId, resultIndex) {
|
|
61
75
|
const issues = [];
|
|
62
76
|
if (!isRecord(finding)) {
|
|
@@ -237,6 +251,11 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
237
251
|
message: `Invalid lens '${result.lens}'. Must be one of: ${[...VALID_LENSES].join(", ")}.`,
|
|
238
252
|
});
|
|
239
253
|
}
|
|
254
|
+
if (task) {
|
|
255
|
+
validateExpectedStringField(result.unit_id, "unit_id", task.unit_id, taskId, i, issues);
|
|
256
|
+
validateExpectedStringField(result.pass_id, "pass_id", task.pass_id, taskId, i, issues);
|
|
257
|
+
validateExpectedStringField(result.lens, "lens", task.lens, taskId, i, issues);
|
|
258
|
+
}
|
|
240
259
|
if (tasks.length > 0 && !task) {
|
|
241
260
|
pushIssue(issues, {
|
|
242
261
|
result_index: i,
|
|
@@ -248,6 +267,7 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
248
267
|
}
|
|
249
268
|
const fileCoverage = result.file_coverage;
|
|
250
269
|
const normalizedFileCoverage = [];
|
|
270
|
+
const declaredAssignedCoveragePaths = new Set();
|
|
251
271
|
if (!Array.isArray(fileCoverage) || fileCoverage.length === 0) {
|
|
252
272
|
pushIssue(issues, {
|
|
253
273
|
result_index: i,
|
|
@@ -281,7 +301,6 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
281
301
|
pushIssue(issues, {
|
|
282
302
|
result_index: i,
|
|
283
303
|
task_id: taskId,
|
|
284
|
-
severity: "warning",
|
|
285
304
|
field: `file_coverage[${j}].path`,
|
|
286
305
|
message: `file_coverage path '${entry.path}' is not listed in the task file_paths.`,
|
|
287
306
|
});
|
|
@@ -297,6 +316,10 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
297
316
|
else {
|
|
298
317
|
seenCoveragePaths.add(entry.path);
|
|
299
318
|
}
|
|
319
|
+
if (isNonEmptyString(entry.path) &&
|
|
320
|
+
(!task || task.file_paths.includes(entry.path))) {
|
|
321
|
+
declaredAssignedCoveragePaths.add(entry.path);
|
|
322
|
+
}
|
|
300
323
|
if (!Number.isInteger(entry.total_lines)) {
|
|
301
324
|
pushIssue(issues, {
|
|
302
325
|
result_index: i,
|
|
@@ -330,7 +353,8 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
330
353
|
}
|
|
331
354
|
if (isNonEmptyString(entry.path) &&
|
|
332
355
|
Number.isInteger(entry.total_lines) &&
|
|
333
|
-
Number(entry.total_lines) >= 0
|
|
356
|
+
Number(entry.total_lines) >= 0 &&
|
|
357
|
+
(!task || task.file_paths.includes(entry.path))) {
|
|
334
358
|
normalizedFileCoverage.push({
|
|
335
359
|
path: entry.path,
|
|
336
360
|
total_lines: Number(entry.total_lines),
|
|
@@ -367,11 +391,35 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
367
391
|
if (!isRecord(finding) || !Array.isArray(finding.affected_files)) {
|
|
368
392
|
continue;
|
|
369
393
|
}
|
|
394
|
+
const expectedFindingLens = task?.lens ??
|
|
395
|
+
(typeof result.lens === "string" && VALID_LENSES.has(result.lens)
|
|
396
|
+
? result.lens
|
|
397
|
+
: undefined);
|
|
398
|
+
if (expectedFindingLens &&
|
|
399
|
+
typeof finding.lens === "string" &&
|
|
400
|
+
finding.lens !== expectedFindingLens) {
|
|
401
|
+
pushIssue(issues, {
|
|
402
|
+
result_index: i,
|
|
403
|
+
task_id: taskId,
|
|
404
|
+
field: `${label}.lens`,
|
|
405
|
+
message: `${label}.lens must match the assigned task lens ` +
|
|
406
|
+
`(expected '${expectedFindingLens}', got '${finding.lens}').`,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
370
409
|
for (let k = 0; k < finding.affected_files.length; k++) {
|
|
371
410
|
const affected = finding.affected_files[k];
|
|
372
411
|
if (!isRecord(affected) || !isNonEmptyString(affected.path)) {
|
|
373
412
|
continue;
|
|
374
413
|
}
|
|
414
|
+
if (!declaredAssignedCoveragePaths.has(affected.path)) {
|
|
415
|
+
pushIssue(issues, {
|
|
416
|
+
result_index: i,
|
|
417
|
+
task_id: taskId,
|
|
418
|
+
field: `${label}.affected_files[${k}].path`,
|
|
419
|
+
message: `affected_files path '${affected.path}' is not in the declared assigned file_coverage.`,
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
375
423
|
if (!Number.isInteger(affected.line_start)) {
|
|
376
424
|
continue;
|
|
377
425
|
}
|
|
@@ -74,6 +74,11 @@ function validateAgentProviderSection(value, path, issues) {
|
|
|
74
74
|
if (value.extra_args !== undefined) {
|
|
75
75
|
validateStringArray(value.extra_args, `${path}.extra_args`, "extra_args", issues, { allowEmptyArray: true });
|
|
76
76
|
}
|
|
77
|
+
if (path === "claude_code" &&
|
|
78
|
+
value.dangerously_skip_permissions !== undefined &&
|
|
79
|
+
typeof value.dangerously_skip_permissions !== "boolean") {
|
|
80
|
+
pushIssue(issues, `${path}.dangerously_skip_permissions`, "dangerously_skip_permissions must be a boolean when provided.");
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
function commandExists(command) {
|
|
79
84
|
const lookupCommand = process.platform === "win32" ? "where" : "which";
|
|
@@ -277,4 +277,7 @@ For a polished operator experience today:
|
|
|
277
277
|
4. prefer `local-subprocess` unless you explicitly want a backend provider bridge
|
|
278
278
|
5. use `subprocess-template` only when integrating a non-native editor or launcher surface
|
|
279
279
|
|
|
280
|
-
If you intentionally want the backend fallback to bridge semantic review into
|
|
280
|
+
If you intentionally want the backend fallback to bridge semantic review into
|
|
281
|
+
another process, set the matching provider in
|
|
282
|
+
`.audit-artifacts/session-config.json` or re-run with an explicit `--provider`
|
|
283
|
+
flag after configuring the matching provider section.
|
package/docs/contract.md
CHANGED
|
@@ -25,7 +25,10 @@ Workers submit `AuditResult[]` shaped by `schemas/audit_result.schema.json`.
|
|
|
25
25
|
Important rules:
|
|
26
26
|
|
|
27
27
|
- `file_coverage` is required and must include every assigned file.
|
|
28
|
+
- `file_coverage` must not include files outside the assigned task.
|
|
28
29
|
- `file_coverage[].total_lines` must match the current file line count.
|
|
30
|
+
- `task_id`, `unit_id`, `pass_id`, and `lens` must match the assigned task.
|
|
31
|
+
- each finding lens must match the assigned task lens.
|
|
29
32
|
- `findings[].affected_files` must be objects, not strings.
|
|
30
33
|
- `findings[].evidence` must be an array of plain strings.
|
|
31
34
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
This document describes the implemented review-dispatch path for `/audit-code`.
|
|
4
4
|
The original dispatch plan was one agent per audit task. The current path keeps
|
|
5
5
|
the existing `AuditTask` and `AuditResult` contracts, but groups related tasks
|
|
6
|
-
into review packets so a worker can read a coherent file set once and
|
|
7
|
-
one validated result
|
|
6
|
+
into review packets so a worker can read a coherent file set once and submit
|
|
7
|
+
one validated result for each assigned task through a backend-owned write path.
|
|
8
8
|
|
|
9
9
|
## Current Workflow
|
|
10
10
|
|
|
@@ -15,15 +15,16 @@ one validated result file for each assigned task.
|
|
|
15
15
|
|
|
16
16
|
2. audit-code prepare-dispatch --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
17
17
|
-> reads pending-audit-tasks.json and review planning artifacts
|
|
18
|
-
-> writes dispatch-plan.json
|
|
18
|
+
-> writes a slim dispatch-plan.json
|
|
19
|
+
-> writes backend-owned dispatch-result-map.json
|
|
19
20
|
-> writes one packet prompt per dispatch-plan entry
|
|
20
21
|
-> prints one compact JSON envelope
|
|
21
22
|
|
|
22
23
|
3. Conversation orchestrator reads only dispatch-plan.json
|
|
23
24
|
-> launches one subagent per packet
|
|
24
25
|
-> each subagent reads its packet prompt and assigned files
|
|
25
|
-
-> each subagent
|
|
26
|
-
->
|
|
26
|
+
-> each subagent pipes AuditResult[] to the submit-packet command in the prompt
|
|
27
|
+
-> submit-packet validates and writes only backend-assigned result files
|
|
27
28
|
-> each subagent replies: valid: <packet_id>, findings=<n>
|
|
28
29
|
|
|
29
30
|
4. audit-code merge-and-ingest --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
@@ -62,6 +63,7 @@ Packet planning is deterministic and compatibility-preserving:
|
|
|
62
63
|
- graph edges from imports, calls, and references can merge related task groups
|
|
63
64
|
- heuristic container edges do not force packet expansion
|
|
64
65
|
- packet chunking respects task-count and line-budget limits
|
|
66
|
+
- a single file that exceeds the packet target is isolated rather than split
|
|
65
67
|
- high-priority packets sort ahead of lower-priority packets
|
|
66
68
|
|
|
67
69
|
Generated packets include:
|
|
@@ -88,7 +90,10 @@ audit-code prepare-dispatch --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
|
88
90
|
Artifacts:
|
|
89
91
|
|
|
90
92
|
- `<artifacts_dir>/runs/<run_id>/dispatch-plan.json`
|
|
93
|
+
- `<artifacts_dir>/runs/<run_id>/dispatch-result-map.json`
|
|
91
94
|
- `<artifacts_dir>/runs/<run_id>/task-results/<packet_id>.prompt.md`
|
|
95
|
+
- `<artifacts_dir>/runs/<run_id>/task-results/<packet_id>.anchors.json`,
|
|
96
|
+
only for isolated large-file packets
|
|
92
97
|
- `<artifacts_dir>/runs/<run_id>/dispatch-warnings.json`, only when warnings
|
|
93
98
|
exist
|
|
94
99
|
|
|
@@ -115,18 +120,8 @@ The command prints a compact JSON envelope:
|
|
|
115
120
|
```json
|
|
116
121
|
{
|
|
117
122
|
"packet_id": "src-auth:security-correctness:packet-1-...",
|
|
118
|
-
"task_id": "src-auth:security-correctness:packet-1-...",
|
|
119
|
-
"task_ids": ["src-auth:security", "src-auth:correctness"],
|
|
120
123
|
"description": "Audit 2 file(s), 2 task(s), 2 lens(es) (~70 lines)",
|
|
121
|
-
"
|
|
122
|
-
"src-auth:security": ".audit-artifacts/runs/run-1/task-results/src-auth_security.json",
|
|
123
|
-
"src-auth:correctness": ".audit-artifacts/runs/run-1/task-results/src-auth_correctness.json"
|
|
124
|
-
},
|
|
125
|
-
"prompt_path": ".audit-artifacts/runs/run-1/task-results/src-auth_security-correctness_packet-1.prompt.md",
|
|
126
|
-
"lenses": ["security", "correctness"],
|
|
127
|
-
"file_paths": ["src/api/auth.ts", "src/lib/session.ts"],
|
|
128
|
-
"total_lines": 70,
|
|
129
|
-
"estimated_tokens": 1180
|
|
124
|
+
"prompt_path": ".audit-artifacts/runs/run-1/task-results/src-auth_security-correctness_packet-1_ab12cd34ef56.prompt.md"
|
|
130
125
|
}
|
|
131
126
|
```
|
|
132
127
|
|
|
@@ -134,6 +129,27 @@ The orchestrator should launch one subagent per entry with the entry
|
|
|
134
129
|
description and a prompt that tells the subagent to read and follow
|
|
135
130
|
`entry.prompt_path`.
|
|
136
131
|
|
|
132
|
+
## Large File Mode
|
|
133
|
+
|
|
134
|
+
The workflow does not impose a hard single-file size limit. When a packet is
|
|
135
|
+
large because it contains one large file, `prepare-dispatch` keeps that file in
|
|
136
|
+
an isolated packet and writes a mechanical anchor summary next to the packet
|
|
137
|
+
prompt. The anchor summary may include:
|
|
138
|
+
|
|
139
|
+
- file boundaries
|
|
140
|
+
- imports and exports
|
|
141
|
+
- top-level symbols
|
|
142
|
+
- route-like declarations
|
|
143
|
+
- risk keywords
|
|
144
|
+
- graph edges
|
|
145
|
+
- external analyzer signals
|
|
146
|
+
|
|
147
|
+
The packet prompt points the worker at the anchor file and asks for targeted
|
|
148
|
+
reads/searches within the assigned file. The backend still validates and writes
|
|
149
|
+
results through `submit-packet`. This keeps large-file review bounded by
|
|
150
|
+
mechanically generated structure without slicing files into arbitrary line
|
|
151
|
+
ranges.
|
|
152
|
+
|
|
137
153
|
## Packet Prompt Contract
|
|
138
154
|
|
|
139
155
|
Each packet prompt tells the worker to:
|
|
@@ -141,20 +157,31 @@ Each packet prompt tells the worker to:
|
|
|
141
157
|
- review the packet once
|
|
142
158
|
- read only the listed repo-relative files
|
|
143
159
|
- produce one JSON object per listed task
|
|
144
|
-
-
|
|
160
|
+
- pipe one JSON array to the prompt's `submit-packet` command
|
|
145
161
|
- preserve the existing `AuditResult` fields:
|
|
146
162
|
`task_id`, `unit_id`, `pass_id`, `lens`, `file_coverage`, `findings`
|
|
147
163
|
- keep `file_coverage[]` as `{ path, total_lines }`
|
|
148
164
|
- keep every finding lens equal to the task lens
|
|
149
|
-
- avoid source edits, remediation, extra task results, and
|
|
150
|
-
|
|
151
|
-
- reply exactly `valid: <packet_id>, findings=<total finding count>` after
|
|
152
|
-
|
|
165
|
+
- avoid direct file writes, source edits, remediation, extra task results, and
|
|
166
|
+
unrelated audits
|
|
167
|
+
- reply exactly `valid: <packet_id>, findings=<total finding count>` after the
|
|
168
|
+
submit command accepts the packet
|
|
153
169
|
|
|
154
170
|
This keeps packet review efficient while leaving merge and ingestion
|
|
155
171
|
mechanically deterministic.
|
|
156
172
|
|
|
157
|
-
## Validation
|
|
173
|
+
## Submission and Validation
|
|
174
|
+
|
|
175
|
+
Packet submission is exposed through:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
audit-code submit-packet --run-id <run_id> --packet-id <packet_id> --artifacts-dir <artifacts_dir>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The command reads `AuditResult[]` from stdin, validates the complete assigned
|
|
182
|
+
packet, and writes only the backend-assigned per-task result paths from
|
|
183
|
+
`dispatch-result-map.json`. This keeps result writes out of the LLM prompt and
|
|
184
|
+
prevents swapped or unknown task result files from being ingested.
|
|
158
185
|
|
|
159
186
|
Per-task validation is exposed through:
|
|
160
187
|
|
|
@@ -162,6 +189,10 @@ Per-task validation is exposed through:
|
|
|
162
189
|
audit-code validate-result --run-id <run_id> --task-id <task_id> --artifacts-dir <artifacts_dir>
|
|
163
190
|
```
|
|
164
191
|
|
|
192
|
+
Generated packet prompts may pass run ids, packet ids, task ids, and artifact paths through
|
|
193
|
+
base64url flags such as `--run-id-b64`, `--packet-id-b64`, `--task-id-b64`, and
|
|
194
|
+
`--artifacts-dir-b64` when raw values could contain shell-sensitive characters.
|
|
195
|
+
|
|
165
196
|
The validator checks the result against the assigned task set and enforces the
|
|
166
197
|
mechanical constraints that matter for ingestion:
|
|
167
198
|
|
|
@@ -171,7 +202,7 @@ mechanical constraints that matter for ingestion:
|
|
|
171
202
|
- line spans do not exceed known `total_lines`
|
|
172
203
|
- result fields conform to the shipped schemas
|
|
173
204
|
|
|
174
|
-
Workers should retry
|
|
205
|
+
Workers should retry rejected submissions up to the bounded retry count in the prompt.
|
|
175
206
|
|
|
176
207
|
## `merge-and-ingest` Output
|
|
177
208
|
|
|
@@ -183,7 +214,9 @@ audit-code merge-and-ingest --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
|
183
214
|
|
|
184
215
|
Merge behavior:
|
|
185
216
|
|
|
186
|
-
- validates every
|
|
217
|
+
- validates every backend-assigned result-map path
|
|
218
|
+
- rejects unexpected JSON files under `task-results/`
|
|
219
|
+
- rejects task IDs that appear in the wrong assigned result path
|
|
187
220
|
- rejects duplicate task results
|
|
188
221
|
- rejects unknown task IDs
|
|
189
222
|
- rejects missing assigned task results
|
package/docs/run-flow.md
CHANGED
|
@@ -14,11 +14,13 @@ This document describes the backend execution flow that supports that conversati
|
|
|
14
14
|
4. Build `review_packets.json` and `audit_plan_metrics.json` from those tasks.
|
|
15
15
|
5. Stop at semantic review with an active run handoff.
|
|
16
16
|
6. `prepare-dispatch` writes a small run-scoped `dispatch-plan.json` and one
|
|
17
|
-
prompt per review packet.
|
|
17
|
+
prompt per review packet, plus a backend-owned result map.
|
|
18
|
+
Isolated large-file packets also get mechanical anchor summaries for
|
|
19
|
+
targeted review.
|
|
18
20
|
7. The active conversation orchestrator launches one bounded subagent per
|
|
19
21
|
packet when the host supports subagents.
|
|
20
|
-
8. Each subagent
|
|
21
|
-
|
|
22
|
+
8. Each subagent pipes `AuditResult[]` to the packet's `submit-packet` command;
|
|
23
|
+
the backend validates and writes assigned result files.
|
|
22
24
|
9. `merge-and-ingest` validates the full assigned task set and ingests the
|
|
23
25
|
existing `AuditResult[]` shape.
|
|
24
26
|
10. Result ingestion updates coverage, requeue, runtime-validation state, and
|