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.
@@ -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
- "--dangerously-skip-permissions",
24
+ ...(this.config.dangerously_skip_permissions
25
+ ? ["--dangerously-skip-permissions"]
26
+ : []),
25
27
  ];
26
28
  return await this.launchCommand(command, args, input);
27
29
  }
@@ -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 [renderShellCommand(activeReviewRun.worker_command)];
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 = `audit-code worker-run --task ${params.activeReviewRun.task_path}`;
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
  };
@@ -10,6 +10,7 @@ export interface SubprocessTemplateConfig {
10
10
  export interface ClaudeCodeConfig {
11
11
  command?: string;
12
12
  extra_args?: string[];
13
+ dangerously_skip_permissions?: boolean;
13
14
  }
14
15
  export interface OpenCodeConfig {
15
16
  command?: string;
@@ -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, and VS Code assets inside the real products they target
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 another process, re-run with an explicit `--provider` flag after configuring the matching section in `.audit-artifacts/session-config.json`.
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, and VS Code assets
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