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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
2
|
+
import type { GraphBundle } from "../types/graph.js";
|
|
3
|
+
export type FileAnchorKind = "boundary" | "import" | "export" | "symbol" | "route" | "keyword" | "graph" | "analyzer_signal";
|
|
4
|
+
export interface FileAnchor {
|
|
5
|
+
kind: FileAnchorKind;
|
|
6
|
+
name: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
detail?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FileAnchorSummary {
|
|
11
|
+
contract_version: "audit-code-file-anchors/v1alpha1";
|
|
12
|
+
path: string;
|
|
13
|
+
total_lines: number;
|
|
14
|
+
review_mode: "isolated_large_file";
|
|
15
|
+
scope_basis: string[];
|
|
16
|
+
anchors: FileAnchor[];
|
|
17
|
+
omitted_anchor_count: number;
|
|
18
|
+
counts: {
|
|
19
|
+
symbols: number;
|
|
20
|
+
routes: number;
|
|
21
|
+
keywords: number;
|
|
22
|
+
graph_edges: number;
|
|
23
|
+
analyzer_signals: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function buildFileAnchorSummary(params: {
|
|
27
|
+
path: string;
|
|
28
|
+
content: string;
|
|
29
|
+
totalLines: number;
|
|
30
|
+
graphBundle?: GraphBundle;
|
|
31
|
+
externalAnalyzerResults?: ExternalAnalyzerResults;
|
|
32
|
+
}): FileAnchorSummary;
|
|
@@ -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";
|
|
@@ -254,7 +254,7 @@ The current implementation shipped the shared installer and MCP substrate. The r
|
|
|
254
254
|
|
|
255
255
|
Highest-value follow-through:
|
|
256
256
|
|
|
257
|
-
1. validate the generated Codex, Claude Desktop, OpenCode,
|
|
257
|
+
1. validate the generated Codex, Claude Desktop, OpenCode, VS Code, and Antigravity assets inside the real products they target
|
|
258
258
|
2. tighten generated quick-start guidance anywhere those host smoke tests expose ambiguity
|
|
259
259
|
3. document exactly how Antigravity artifacts should map into `import_results` and `import_runtime_updates`
|
|
260
260
|
4. keep host claims conservative until those end-to-end product checks are complete
|
|
@@ -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.
|
|
@@ -10,6 +10,9 @@ That command installs the repo-local `/audit-code` surfaces we can automate toda
|
|
|
10
10
|
It is also the single refresh path: rerun `audit-code install` after prompt or
|
|
11
11
|
skill updates to rewrite the shared install assets and every generated
|
|
12
12
|
host-specific surface from the same source files.
|
|
13
|
+
The generated manifest records the canonical prompt and skill source paths so
|
|
14
|
+
host surfaces can be checked against one shared source of truth instead of
|
|
15
|
+
drifting independently.
|
|
13
16
|
|
|
14
17
|
After bootstrap, run:
|
|
15
18
|
|
|
@@ -28,6 +31,7 @@ Installed shared surfaces:
|
|
|
28
31
|
- `.audit-code/install/GETTING-STARTED.md`
|
|
29
32
|
- `.audit-code/install/manifest.json`
|
|
30
33
|
- `.audit-code/install/run-mcp-server.mjs`
|
|
34
|
+
- `.audit-artifacts/session-config.json` when no backend fallback config exists yet
|
|
31
35
|
|
|
32
36
|
Installed host-specific surfaces:
|
|
33
37
|
|
|
@@ -76,6 +80,7 @@ without supplying extra root paths, provider flags, or model-selection arguments
|
|
|
76
80
|
## What is fully automated today
|
|
77
81
|
|
|
78
82
|
- shared installer output, manifest generation, and repo-local MCP launcher generation
|
|
83
|
+
- default backend fallback session-config creation when no config exists yet
|
|
79
84
|
- Codex skill-bundle and AGENTS-oriented install output
|
|
80
85
|
- OpenCode command, skill, prompt, and config generation
|
|
81
86
|
- VS Code prompt, custom-agent, instruction, and MCP config generation
|
|
@@ -84,7 +89,7 @@ without supplying extra root paths, provider flags, or model-selection arguments
|
|
|
84
89
|
|
|
85
90
|
## What is not fully automated today
|
|
86
91
|
|
|
87
|
-
- product-level smoke validation for the generated Codex, Claude Desktop, OpenCode,
|
|
92
|
+
- product-level smoke validation for the generated Codex, Claude Desktop, OpenCode, VS Code, and Antigravity assets
|
|
88
93
|
- one-click proof that the generated Claude Desktop bundle installs cleanly in a real Desktop environment
|
|
89
94
|
- documented Antigravity artifact round-tripping back through `import_results` and `import_runtime_updates`
|
|
90
95
|
|
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
|
|