auditor-lambda 0.2.5 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -7
- package/audit-code-wrapper-lib.mjs +1612 -331
- package/dist/cli.js +397 -38
- package/dist/coverage.d.ts +2 -2
- package/dist/coverage.js +5 -5
- package/dist/extractors/disposition.js +10 -1
- package/dist/extractors/flows.js +7 -1
- package/dist/extractors/pathPatterns.d.ts +3 -0
- package/dist/extractors/pathPatterns.js +15 -0
- package/dist/extractors/risk.js +7 -1
- package/dist/io/artifacts.d.ts +6 -6
- package/dist/io/artifacts.js +14 -17
- package/dist/io/json.d.ts +2 -0
- package/dist/io/json.js +15 -0
- package/dist/io/runArtifacts.d.ts +3 -1
- package/dist/io/runArtifacts.js +20 -5
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +579 -0
- package/dist/orchestrator/advance.js +9 -2
- package/dist/orchestrator/dependencyMap.js +9 -13
- package/dist/orchestrator/executors.js +7 -2
- package/dist/orchestrator/flowRequeue.d.ts +2 -2
- package/dist/orchestrator/flowRequeue.js +16 -3
- package/dist/orchestrator/internalExecutors.d.ts +2 -1
- package/dist/orchestrator/internalExecutors.js +129 -48
- package/dist/orchestrator/requeue.js +10 -4
- package/dist/orchestrator/requeueCommand.js +15 -2
- package/dist/orchestrator/resultIngestion.d.ts +2 -1
- package/dist/orchestrator/resultIngestion.js +26 -6
- package/dist/orchestrator/runtimeValidation.d.ts +7 -2
- package/dist/orchestrator/runtimeValidation.js +61 -49
- package/dist/orchestrator/runtimeValidationUpdate.js +2 -4
- package/dist/orchestrator/state.js +28 -14
- package/dist/orchestrator/taskBuilder.js +4 -2
- package/dist/orchestrator/trivialAudit.d.ts +4 -0
- package/dist/orchestrator/trivialAudit.js +49 -0
- package/dist/prompts/renderWorkerPrompt.js +6 -2
- package/dist/providers/spawnLoggedCommand.js +17 -0
- package/dist/reporting/mergeFindings.js +3 -11
- package/dist/reporting/rootCause.js +92 -9
- package/dist/reporting/synthesis.d.ts +25 -22
- package/dist/reporting/synthesis.js +92 -59
- package/dist/reporting/workBlocks.d.ts +12 -3
- package/dist/reporting/workBlocks.js +124 -70
- package/dist/supervisor/sessionConfig.js +4 -2
- package/dist/types/flows.d.ts +2 -0
- package/dist/types/runtimeValidation.d.ts +2 -1
- package/dist/types.d.ts +8 -6
- package/dist/validation/auditResults.d.ts +5 -2
- package/dist/validation/auditResults.js +335 -43
- package/docs/agent-integrations.md +38 -29
- package/docs/artifacts.md +18 -51
- package/docs/bootstrap-install.md +60 -30
- package/docs/contract.md +25 -117
- package/docs/field-trial-bug-report.md +237 -0
- package/docs/next-steps.md +59 -44
- package/docs/packaging.md +13 -3
- package/docs/production-launch-bar.md +2 -2
- package/docs/production-readiness.md +9 -5
- package/docs/releasing.md +81 -0
- package/docs/session-config.md +20 -1
- package/docs/usage.md +22 -0
- package/package.json +4 -1
- package/schemas/audit_result.schema.json +4 -5
- package/schemas/audit_task.schema.json +10 -0
- package/schemas/runtime_validation_report.schema.json +1 -1
- package/skills/audit-code/SKILL.md +11 -2
- package/skills/audit-code/audit-code.prompt.md +11 -10
- package/schemas/merged_findings.schema.json +0 -19
- package/schemas/root_cause_clusters.schema.json +0 -28
- package/schemas/synthesis_report.schema.json +0 -61
|
@@ -9,77 +9,193 @@ const REQUIRED_FINDING_FIELDS = [
|
|
|
9
9
|
];
|
|
10
10
|
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low", "info"]);
|
|
11
11
|
const VALID_CONFIDENCES = new Set(["high", "medium", "low"]);
|
|
12
|
+
const VALID_LENSES = new Set([
|
|
13
|
+
"correctness",
|
|
14
|
+
"architecture",
|
|
15
|
+
"maintainability",
|
|
16
|
+
"security",
|
|
17
|
+
"reliability",
|
|
18
|
+
"performance",
|
|
19
|
+
"data_integrity",
|
|
20
|
+
"tests",
|
|
21
|
+
"operability",
|
|
22
|
+
"config_deployment",
|
|
23
|
+
]);
|
|
24
|
+
function pushIssue(issues, params) {
|
|
25
|
+
issues.push({
|
|
26
|
+
...params,
|
|
27
|
+
severity: params.severity ?? "error",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function describeValue(value) {
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return "array";
|
|
33
|
+
}
|
|
34
|
+
if (value === null) {
|
|
35
|
+
return "null";
|
|
36
|
+
}
|
|
37
|
+
return typeof value;
|
|
38
|
+
}
|
|
39
|
+
function isRecord(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
function isNonEmptyString(value) {
|
|
43
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
44
|
+
}
|
|
45
|
+
function issueTaskId(record, resultIndex) {
|
|
46
|
+
const taskId = record.task_id;
|
|
47
|
+
return typeof taskId === "string" && taskId.trim().length > 0
|
|
48
|
+
? taskId
|
|
49
|
+
: `result[${resultIndex}]`;
|
|
50
|
+
}
|
|
51
|
+
function validateRequiredStringField(value, label, taskId, resultIndex, issues) {
|
|
52
|
+
if (typeof value !== "string") {
|
|
53
|
+
pushIssue(issues, {
|
|
54
|
+
result_index: resultIndex,
|
|
55
|
+
task_id: taskId,
|
|
56
|
+
field: label,
|
|
57
|
+
message: `${label} must be a string, got ${describeValue(value)}.`,
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (value.trim().length === 0) {
|
|
62
|
+
pushIssue(issues, {
|
|
63
|
+
result_index: resultIndex,
|
|
64
|
+
task_id: taskId,
|
|
65
|
+
field: label,
|
|
66
|
+
message: `${label} must not be empty.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
12
70
|
function validateFinding(finding, label, taskId, resultIndex) {
|
|
13
71
|
const issues = [];
|
|
72
|
+
if (!isRecord(finding)) {
|
|
73
|
+
pushIssue(issues, {
|
|
74
|
+
result_index: resultIndex,
|
|
75
|
+
task_id: taskId,
|
|
76
|
+
field: label,
|
|
77
|
+
message: `${label} must be an object, got ${describeValue(finding)}.`,
|
|
78
|
+
});
|
|
79
|
+
return issues;
|
|
80
|
+
}
|
|
14
81
|
for (const field of REQUIRED_FINDING_FIELDS) {
|
|
15
|
-
|
|
16
|
-
if (value === undefined || value === null || String(value).trim() === "") {
|
|
17
|
-
issues.push({
|
|
18
|
-
result_index: resultIndex,
|
|
19
|
-
task_id: taskId,
|
|
20
|
-
severity: "error",
|
|
21
|
-
field: `${label}.${field}`,
|
|
22
|
-
message: `Required field '${field}' is missing or empty.`,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
82
|
+
validateRequiredStringField(finding[field], `${label}.${field}`, taskId, resultIndex, issues);
|
|
25
83
|
}
|
|
26
|
-
if (finding.severity
|
|
27
|
-
|
|
84
|
+
if (typeof finding.severity === "string" &&
|
|
85
|
+
!VALID_SEVERITIES.has(finding.severity)) {
|
|
86
|
+
pushIssue(issues, {
|
|
28
87
|
result_index: resultIndex,
|
|
29
88
|
task_id: taskId,
|
|
30
|
-
severity: "error",
|
|
31
89
|
field: `${label}.severity`,
|
|
32
90
|
message: `Invalid severity '${finding.severity}'. Must be one of: ${[...VALID_SEVERITIES].join(", ")}.`,
|
|
33
91
|
});
|
|
34
92
|
}
|
|
35
|
-
if (finding.confidence
|
|
36
|
-
|
|
93
|
+
if (typeof finding.confidence === "string" &&
|
|
94
|
+
!VALID_CONFIDENCES.has(finding.confidence)) {
|
|
95
|
+
pushIssue(issues, {
|
|
37
96
|
result_index: resultIndex,
|
|
38
97
|
task_id: taskId,
|
|
39
|
-
severity: "error",
|
|
40
98
|
field: `${label}.confidence`,
|
|
41
99
|
message: `Invalid confidence '${finding.confidence}'. Must be one of: ${[...VALID_CONFIDENCES].join(", ")}.`,
|
|
42
100
|
});
|
|
43
101
|
}
|
|
44
|
-
if (
|
|
45
|
-
issues
|
|
102
|
+
if (typeof finding.lens === "string" && !VALID_LENSES.has(finding.lens)) {
|
|
103
|
+
pushIssue(issues, {
|
|
104
|
+
result_index: resultIndex,
|
|
105
|
+
task_id: taskId,
|
|
106
|
+
field: `${label}.lens`,
|
|
107
|
+
message: `Invalid lens '${finding.lens}'. Must be one of: ${[...VALID_LENSES].join(", ")}.`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const affectedFiles = finding.affected_files;
|
|
111
|
+
if (!Array.isArray(affectedFiles) || affectedFiles.length === 0) {
|
|
112
|
+
pushIssue(issues, {
|
|
46
113
|
result_index: resultIndex,
|
|
47
114
|
task_id: taskId,
|
|
48
|
-
severity: "error",
|
|
49
115
|
field: `${label}.affected_files`,
|
|
50
|
-
message: "affected_files
|
|
116
|
+
message: "affected_files must be a non-empty array.",
|
|
51
117
|
});
|
|
52
118
|
}
|
|
53
119
|
else {
|
|
54
|
-
for (let k = 0; k <
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
issues
|
|
120
|
+
for (let k = 0; k < affectedFiles.length; k++) {
|
|
121
|
+
const item = affectedFiles[k];
|
|
122
|
+
if (!isRecord(item)) {
|
|
123
|
+
pushIssue(issues, {
|
|
124
|
+
result_index: resultIndex,
|
|
125
|
+
task_id: taskId,
|
|
126
|
+
field: `${label}.affected_files[${k}]`,
|
|
127
|
+
message: `affected_files[${k}] must be an object, got ${describeValue(item)}.`,
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!isNonEmptyString(item.path)) {
|
|
132
|
+
pushIssue(issues, {
|
|
58
133
|
result_index: resultIndex,
|
|
59
134
|
task_id: taskId,
|
|
60
|
-
severity: "error",
|
|
61
135
|
field: `${label}.affected_files[${k}].path`,
|
|
62
136
|
message: "affected_files entry has an empty path.",
|
|
63
137
|
});
|
|
64
138
|
}
|
|
139
|
+
if (item.line_start !== undefined &&
|
|
140
|
+
!Number.isInteger(item.line_start)) {
|
|
141
|
+
pushIssue(issues, {
|
|
142
|
+
result_index: resultIndex,
|
|
143
|
+
task_id: taskId,
|
|
144
|
+
field: `${label}.affected_files[${k}].line_start`,
|
|
145
|
+
message: `affected_files[${k}].line_start must be an integer, got ${describeValue(item.line_start)}.`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (item.line_end !== undefined &&
|
|
149
|
+
!Number.isInteger(item.line_end)) {
|
|
150
|
+
pushIssue(issues, {
|
|
151
|
+
result_index: resultIndex,
|
|
152
|
+
task_id: taskId,
|
|
153
|
+
field: `${label}.affected_files[${k}].line_end`,
|
|
154
|
+
message: `affected_files[${k}].line_end must be an integer, got ${describeValue(item.line_end)}.`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (Number.isInteger(item.line_start) &&
|
|
158
|
+
Number.isInteger(item.line_end) &&
|
|
159
|
+
Number(item.line_start) > Number(item.line_end)) {
|
|
160
|
+
pushIssue(issues, {
|
|
161
|
+
result_index: resultIndex,
|
|
162
|
+
task_id: taskId,
|
|
163
|
+
field: `${label}.affected_files[${k}]`,
|
|
164
|
+
message: "affected_files line_start must be less than or equal to line_end.",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
65
167
|
}
|
|
66
168
|
}
|
|
67
|
-
|
|
68
|
-
|
|
169
|
+
const evidence = finding.evidence;
|
|
170
|
+
if (!Array.isArray(evidence) || evidence.length === 0) {
|
|
171
|
+
pushIssue(issues, {
|
|
69
172
|
result_index: resultIndex,
|
|
70
173
|
task_id: taskId,
|
|
71
|
-
severity: "error",
|
|
72
174
|
field: `${label}.evidence`,
|
|
73
|
-
message: "evidence is empty —
|
|
175
|
+
message: "evidence is empty — provide an array of plain strings such as \"src/foo.ts:42 - variable overwritten before use\".",
|
|
74
176
|
});
|
|
75
177
|
}
|
|
76
178
|
else {
|
|
77
|
-
|
|
179
|
+
let hasSubstantiveEntry = false;
|
|
180
|
+
for (let k = 0; k < evidence.length; k++) {
|
|
181
|
+
const entry = evidence[k];
|
|
182
|
+
if (typeof entry !== "string") {
|
|
183
|
+
pushIssue(issues, {
|
|
184
|
+
result_index: resultIndex,
|
|
185
|
+
task_id: taskId,
|
|
186
|
+
field: `${label}.evidence[${k}]`,
|
|
187
|
+
message: `evidence[${k}] must be a string, got ${describeValue(entry)}.`,
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (entry.trim().length > 0) {
|
|
192
|
+
hasSubstantiveEntry = true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
78
195
|
if (!hasSubstantiveEntry) {
|
|
79
|
-
issues
|
|
196
|
+
pushIssue(issues, {
|
|
80
197
|
result_index: resultIndex,
|
|
81
198
|
task_id: taskId,
|
|
82
|
-
severity: "error",
|
|
83
199
|
field: `${label}.evidence`,
|
|
84
200
|
message: "All evidence entries are empty strings.",
|
|
85
201
|
});
|
|
@@ -87,26 +203,202 @@ function validateFinding(finding, label, taskId, resultIndex) {
|
|
|
87
203
|
}
|
|
88
204
|
return issues;
|
|
89
205
|
}
|
|
90
|
-
|
|
206
|
+
function coversAffectedSpan(coverage, path, start, end) {
|
|
207
|
+
return coverage.some((entry) => entry.path === path &&
|
|
208
|
+
start > 0 &&
|
|
209
|
+
end > 0 &&
|
|
210
|
+
end <= entry.total_lines);
|
|
211
|
+
}
|
|
212
|
+
export function validateAuditResults(results, tasks, options = {}) {
|
|
91
213
|
const issues = [];
|
|
92
|
-
const taskMap = new Map(tasks.map((
|
|
214
|
+
const taskMap = new Map(tasks.map((task) => [task.task_id, task]));
|
|
215
|
+
if (!Array.isArray(results)) {
|
|
216
|
+
pushIssue(issues, {
|
|
217
|
+
result_index: -1,
|
|
218
|
+
task_id: "results",
|
|
219
|
+
field: "results",
|
|
220
|
+
message: `Audit results payload must be a JSON array, got ${describeValue(results)}.`,
|
|
221
|
+
});
|
|
222
|
+
return issues;
|
|
223
|
+
}
|
|
93
224
|
for (let i = 0; i < results.length; i++) {
|
|
94
225
|
const result = results[i];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
226
|
+
if (!isRecord(result)) {
|
|
227
|
+
pushIssue(issues, {
|
|
228
|
+
result_index: i,
|
|
229
|
+
task_id: `result[${i}]`,
|
|
230
|
+
field: `results[${i}]`,
|
|
231
|
+
message: `Each audit result must be an object, got ${describeValue(result)}.`,
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const taskId = issueTaskId(result, i);
|
|
236
|
+
const task = taskMap.get(taskId);
|
|
237
|
+
validateRequiredStringField(result.task_id, "task_id", taskId, i, issues);
|
|
238
|
+
validateRequiredStringField(result.unit_id, "unit_id", taskId, i, issues);
|
|
239
|
+
validateRequiredStringField(result.pass_id, "pass_id", taskId, i, issues);
|
|
240
|
+
validateRequiredStringField(result.lens, "lens", taskId, i, issues);
|
|
241
|
+
if (typeof result.lens === "string" &&
|
|
242
|
+
!VALID_LENSES.has(result.lens)) {
|
|
243
|
+
pushIssue(issues, {
|
|
244
|
+
result_index: i,
|
|
245
|
+
task_id: taskId,
|
|
246
|
+
field: "lens",
|
|
247
|
+
message: `Invalid lens '${result.lens}'. Must be one of: ${[...VALID_LENSES].join(", ")}.`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (tasks.length > 0 && !task) {
|
|
251
|
+
pushIssue(issues, {
|
|
252
|
+
result_index: i,
|
|
253
|
+
task_id: taskId,
|
|
254
|
+
field: "task_id",
|
|
255
|
+
message: `Unknown task_id '${taskId}'. Use the active task manifest for valid ids: ` +
|
|
256
|
+
tasks.map((item) => item.task_id).join(", "),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const fileCoverage = result.file_coverage;
|
|
260
|
+
const normalizedFileCoverage = [];
|
|
261
|
+
if (!Array.isArray(fileCoverage) || fileCoverage.length === 0) {
|
|
262
|
+
pushIssue(issues, {
|
|
98
263
|
result_index: i,
|
|
99
264
|
task_id: taskId,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
message: "reviewed_ranges is empty — no proof of file reading was recorded. " +
|
|
103
|
-
"Each result must include the line ranges actually read.",
|
|
265
|
+
field: "file_coverage",
|
|
266
|
+
message: "file_coverage is empty — each result must declare every assigned file it reviewed and the file's total line count.",
|
|
104
267
|
});
|
|
105
268
|
}
|
|
106
|
-
|
|
107
|
-
const
|
|
269
|
+
else {
|
|
270
|
+
const seenCoveragePaths = new Set();
|
|
271
|
+
for (let j = 0; j < fileCoverage.length; j++) {
|
|
272
|
+
const entry = fileCoverage[j];
|
|
273
|
+
if (!isRecord(entry)) {
|
|
274
|
+
pushIssue(issues, {
|
|
275
|
+
result_index: i,
|
|
276
|
+
task_id: taskId,
|
|
277
|
+
field: `file_coverage[${j}]`,
|
|
278
|
+
message: `file_coverage[${j}] must be an object, got ${describeValue(entry)}.`,
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (!isNonEmptyString(entry.path)) {
|
|
283
|
+
pushIssue(issues, {
|
|
284
|
+
result_index: i,
|
|
285
|
+
task_id: taskId,
|
|
286
|
+
field: `file_coverage[${j}].path`,
|
|
287
|
+
message: "file_coverage entry has an empty path.",
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
else if (task && !task.file_paths.includes(entry.path)) {
|
|
291
|
+
pushIssue(issues, {
|
|
292
|
+
result_index: i,
|
|
293
|
+
task_id: taskId,
|
|
294
|
+
severity: "warning",
|
|
295
|
+
field: `file_coverage[${j}].path`,
|
|
296
|
+
message: `file_coverage path '${entry.path}' is not listed in the task file_paths.`,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else if (seenCoveragePaths.has(entry.path)) {
|
|
300
|
+
pushIssue(issues, {
|
|
301
|
+
result_index: i,
|
|
302
|
+
task_id: taskId,
|
|
303
|
+
field: `file_coverage[${j}].path`,
|
|
304
|
+
message: `file_coverage path '${entry.path}' is duplicated. Declare each file once.`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
seenCoveragePaths.add(entry.path);
|
|
309
|
+
}
|
|
310
|
+
if (!Number.isInteger(entry.total_lines)) {
|
|
311
|
+
pushIssue(issues, {
|
|
312
|
+
result_index: i,
|
|
313
|
+
task_id: taskId,
|
|
314
|
+
field: `file_coverage[${j}].total_lines`,
|
|
315
|
+
message: `file_coverage[${j}].total_lines must be an integer, got ${describeValue(entry.total_lines)}.`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (Number.isInteger(entry.total_lines) &&
|
|
319
|
+
Number(entry.total_lines) <= 0) {
|
|
320
|
+
pushIssue(issues, {
|
|
321
|
+
result_index: i,
|
|
322
|
+
task_id: taskId,
|
|
323
|
+
field: `file_coverage[${j}].total_lines`,
|
|
324
|
+
message: "file_coverage total_lines must be greater than zero.",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const expectedLineCount = typeof entry.path === "string"
|
|
328
|
+
? options.lineIndex?.[entry.path]
|
|
329
|
+
: undefined;
|
|
330
|
+
if (Number.isInteger(entry.total_lines) &&
|
|
331
|
+
typeof expectedLineCount === "number" &&
|
|
332
|
+
Number(entry.total_lines) !== expectedLineCount) {
|
|
333
|
+
pushIssue(issues, {
|
|
334
|
+
result_index: i,
|
|
335
|
+
task_id: taskId,
|
|
336
|
+
field: `file_coverage[${j}].total_lines`,
|
|
337
|
+
message: `file_coverage[${j}].total_lines must match the current file line count for '${entry.path}' ` +
|
|
338
|
+
`(expected ${expectedLineCount}, got ${entry.total_lines}).`,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (isNonEmptyString(entry.path) &&
|
|
342
|
+
Number.isInteger(entry.total_lines) &&
|
|
343
|
+
Number(entry.total_lines) > 0) {
|
|
344
|
+
normalizedFileCoverage.push({
|
|
345
|
+
path: entry.path,
|
|
346
|
+
total_lines: Number(entry.total_lines),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (task) {
|
|
351
|
+
for (const path of task.file_paths) {
|
|
352
|
+
if (!seenCoveragePaths.has(path)) {
|
|
353
|
+
pushIssue(issues, {
|
|
354
|
+
result_index: i,
|
|
355
|
+
task_id: taskId,
|
|
356
|
+
field: "file_coverage",
|
|
357
|
+
message: `file_coverage must include every assigned file. Missing '${path}'.`,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const findings = result.findings;
|
|
364
|
+
if (!Array.isArray(findings)) {
|
|
365
|
+
pushIssue(issues, {
|
|
366
|
+
result_index: i,
|
|
367
|
+
task_id: taskId,
|
|
368
|
+
field: "findings",
|
|
369
|
+
message: `findings must be an array, got ${describeValue(findings)}.`,
|
|
370
|
+
});
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
for (let j = 0; j < findings.length; j++) {
|
|
108
374
|
const label = `findings[${j}]`;
|
|
375
|
+
const finding = findings[j];
|
|
109
376
|
issues.push(...validateFinding(finding, label, taskId, i));
|
|
377
|
+
if (!isRecord(finding) || !Array.isArray(finding.affected_files)) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
for (let k = 0; k < finding.affected_files.length; k++) {
|
|
381
|
+
const affected = finding.affected_files[k];
|
|
382
|
+
if (!isRecord(affected) || !isNonEmptyString(affected.path)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!Number.isInteger(affected.line_start)) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const start = Number(affected.line_start);
|
|
389
|
+
const end = Number.isInteger(affected.line_end)
|
|
390
|
+
? Number(affected.line_end)
|
|
391
|
+
: start;
|
|
392
|
+
if (!coversAffectedSpan(normalizedFileCoverage, affected.path, start, end)) {
|
|
393
|
+
pushIssue(issues, {
|
|
394
|
+
result_index: i,
|
|
395
|
+
task_id: taskId,
|
|
396
|
+
field: `${label}.affected_files[${k}]`,
|
|
397
|
+
message: `affected_files line span ${affected.path}:${start}-${end} falls outside the declared file_coverage. ` +
|
|
398
|
+
"Fix the affected_files location or correct file_coverage.total_lines.",
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
110
402
|
}
|
|
111
403
|
}
|
|
112
404
|
return issues;
|
|
@@ -26,8 +26,8 @@ The preferred bootstrap path is:
|
|
|
26
26
|
audit-code install
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
That installs repo-local `/audit-code` surfaces
|
|
30
|
-
It also writes `.audit-code/install/GETTING-STARTED.md` with dedicated quick-start sections for
|
|
29
|
+
That installs repo-local `/audit-code` surfaces and MCP-oriented support assets for Codex, Claude Desktop, OpenCode, VS Code, and Antigravity.
|
|
30
|
+
It also writes `.audit-code/install/GETTING-STARTED.md` with dedicated quick-start sections for each host plus `.audit-code/install/manifest.json` and a shared repo-local MCP launcher.
|
|
31
31
|
|
|
32
32
|
Use one of these supported ways to obtain the raw prompt asset directly when you need prompt import instead:
|
|
33
33
|
|
|
@@ -44,58 +44,56 @@ This is the intended product surface.
|
|
|
44
44
|
|
|
45
45
|
Use `/audit-code` in conversation, treat the active conversation model as the default model, and treat project files plus attached repository context as the default context.
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### Codex
|
|
48
48
|
|
|
49
|
-
Use `audit-code install` from the target repository root.
|
|
49
|
+
Use `audit-code install --host codex` or the default `audit-code install` from the target repository root.
|
|
50
50
|
|
|
51
|
-
That writes
|
|
52
|
-
The generated
|
|
51
|
+
That writes a repo-local Codex skill bundle, updates `AGENTS.md` through a managed block when needed, and emits Codex-specific MCP setup guidance plus an automation recipe in `.audit-code/install/codex/`.
|
|
52
|
+
The intended operator flow is still conversational first, with the generated skill and AGENTS guidance steering the active Codex session toward `/audit-code` and the MCP-backed workflow.
|
|
53
53
|
|
|
54
|
-
The
|
|
54
|
+
The Codex automation recipe should still be treated as optional follow-through after the basic local flow is validated in the real app.
|
|
55
55
|
|
|
56
|
-
###
|
|
56
|
+
### Claude Desktop
|
|
57
57
|
|
|
58
|
-
Use `audit-code install` from the target repository root.
|
|
58
|
+
Use `audit-code install --host claude-desktop` or the default `audit-code install` from the target repository root.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
The generated OpenCode command now sets `agent: build` and keeps the current model selection, which makes the slash command behave more like the intended autonomous editing flow.
|
|
62
|
-
The generated `.audit-code/install/GETTING-STARTED.md` file also includes an OpenCode-specific quick start so the repo-local command path is obvious after bootstrap.
|
|
60
|
+
This repository now treats Claude Desktop as an MCP-first host. The installer writes:
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
- `.audit-code/install/claude-desktop/PROJECT-TEMPLATE.md`
|
|
63
|
+
- `.audit-code/install/claude-desktop/remote-mcp-connector.json`
|
|
64
|
+
- generated local bundle artifacts including `auditor-lambda.dxt` and `auditor-lambda.mcpb`
|
|
65
|
+
|
|
66
|
+
The intended path is to install or reference the generated local MCP bundle, then use the shared prompt and project-template guidance to run `/audit-code` conversationally.
|
|
67
|
+
Manual prompt import remains a fallback, not the primary documented path.
|
|
68
|
+
|
|
69
|
+
### OpenCode
|
|
65
70
|
|
|
66
71
|
Use `audit-code install` from the target repository root.
|
|
67
72
|
|
|
68
|
-
That writes `.
|
|
69
|
-
The generated
|
|
73
|
+
That writes `.opencode/commands/audit-code.md`, a repo-local OpenCode skill bundle, and `opencode.json` so `/audit-code` is available in the repository with no extra provider flags.
|
|
74
|
+
The generated OpenCode assets also point OpenCode toward the shared auditor MCP server instead of rebuilding backend state ad hoc.
|
|
70
75
|
|
|
71
76
|
### VS Code
|
|
72
77
|
|
|
73
78
|
Run `audit-code install` from the target repository root, then open `.audit-code/install/GETTING-STARTED.md` if you want the exact repo-local path that bootstrap created for VS Code chat surfaces.
|
|
74
79
|
|
|
80
|
+
That writes `.github/prompts/audit-code.prompt.md`, `.github/copilot-instructions.md`, `.github/agents/auditor.agent.md`, and `.vscode/mcp.json`.
|
|
75
81
|
The expected happy path is still to invoke `/audit-code` from chat, not to start from the backend CLI.
|
|
76
82
|
|
|
77
|
-
### Claude Desktop
|
|
78
|
-
|
|
79
|
-
Run `audit-code install` from the target repository root, then open `.audit-code/install/GETTING-STARTED.md`.
|
|
80
|
-
|
|
81
|
-
There is no verified project-local slash-command install surface for Claude Desktop in this repository today, so the intended path is:
|
|
82
|
-
|
|
83
|
-
1. import `.audit-code/install/audit-code.import.md` into Claude Desktop's prompt or instruction surface
|
|
84
|
-
2. invoke `/audit-code` conversationally inside Claude Desktop
|
|
85
|
-
|
|
86
83
|
### Antigravity
|
|
87
84
|
|
|
88
85
|
Run `audit-code install` from the target repository root, then open `.audit-code/install/GETTING-STARTED.md`.
|
|
89
86
|
|
|
90
|
-
There is no
|
|
87
|
+
There is still no documented native repo-local saved-workflow surface for Antigravity in this repository today, so the intended path is:
|
|
91
88
|
|
|
92
|
-
1.
|
|
93
|
-
2. invoke `/audit-code` conversationally inside Antigravity
|
|
94
|
-
3.
|
|
89
|
+
1. use the generated planning-mode and MCP setup guidance
|
|
90
|
+
2. invoke `/audit-code` conversationally inside Antigravity when the host surface allows it
|
|
91
|
+
3. use the shared MCP tools and resources when structured state exchange is needed
|
|
92
|
+
4. fall back to `audit-code` from an Antigravity-managed terminal only when you intentionally need the repo-local backend wrapper
|
|
95
93
|
|
|
96
94
|
### Similar manual-import hosts
|
|
97
95
|
|
|
98
|
-
Use the same installed prompt asset and repo-local guide pattern as Claude Desktop
|
|
96
|
+
Use the same installed prompt asset and repo-local guide pattern as Antigravity, or the same MCP-first bundle pattern as Claude Desktop, depending on what the host actually supports.
|
|
99
97
|
|
|
100
98
|
The backend CLI remains optional fallback infrastructure.
|
|
101
99
|
|
|
@@ -224,6 +222,17 @@ Current recommended usage is one of these:
|
|
|
224
222
|
|
|
225
223
|
That keeps the product usable in Antigravity now without pretending that a native adapter already exists.
|
|
226
224
|
|
|
225
|
+
## Remaining steps
|
|
226
|
+
|
|
227
|
+
The current implementation shipped the shared installer and MCP substrate. The remaining work is operational validation and fit-and-finish, not a fresh redesign.
|
|
228
|
+
|
|
229
|
+
Highest-value follow-through:
|
|
230
|
+
|
|
231
|
+
1. validate the generated Codex, Claude Desktop, OpenCode, and VS Code assets inside the real products they target
|
|
232
|
+
2. tighten generated quick-start guidance anywhere those host smoke tests expose ambiguity
|
|
233
|
+
3. document exactly how Antigravity artifacts should map into `import_results` and `import_runtime_updates`
|
|
234
|
+
4. keep host claims conservative until those end-to-end product checks are complete
|
|
235
|
+
|
|
227
236
|
## Model-selection rule
|
|
228
237
|
|
|
229
238
|
The product direction remains skill-first:
|
package/docs/artifacts.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# Core
|
|
1
|
+
# Core Artifacts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document follows [audit-goals.md](C:/Code/auditor-lambda/spec/audit-goals.md).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Incomplete-run artifacts
|
|
6
|
+
|
|
7
|
+
During an incomplete or blocked audit, `.audit-artifacts/` may contain:
|
|
6
8
|
|
|
7
9
|
- `repo_manifest.json`
|
|
8
10
|
- `file_disposition.json`
|
|
@@ -13,57 +15,22 @@ These JSON artifacts are the stable contract between deterministic tooling and L
|
|
|
13
15
|
- `flow_coverage.json`
|
|
14
16
|
- `risk_register.json`
|
|
15
17
|
- `coverage_matrix.json`
|
|
16
|
-
- `runtime_validation_tasks.json`
|
|
17
|
-
- `runtime_validation_report.json`
|
|
18
|
+
- `runtime_validation_tasks.json` when deterministic runtime validation is planned
|
|
19
|
+
- `runtime_validation_report.json` when runtime validation has executed or been updated
|
|
18
20
|
- `external_analyzer_results.json`
|
|
19
|
-
- `
|
|
21
|
+
- `audit_tasks.json`
|
|
22
|
+
- `audit_results.jsonl`
|
|
20
23
|
- `requeue_tasks.json`
|
|
21
|
-
-
|
|
22
|
-
- `root_cause_clusters.json`
|
|
23
|
-
- `synthesis_report.json`
|
|
24
|
-
|
|
25
|
-
## Design rule
|
|
26
|
-
|
|
27
|
-
Tool-specific collectors should write into these normalized formats so that the agent layer can remain portable across runtimes.
|
|
28
|
-
|
|
29
|
-
## Coverage rule
|
|
30
|
-
|
|
31
|
-
Coverage is not based only on test instrumentation. It is based on explicit audit accounting:
|
|
32
|
-
|
|
33
|
-
- file classification
|
|
34
|
-
- file disposition
|
|
35
|
-
- unit assignment
|
|
36
|
-
- required lenses
|
|
37
|
-
- reviewed source ranges
|
|
38
|
-
- completed passes
|
|
39
|
-
- requeue targets for missing review
|
|
40
|
-
- critical-flow coverage state
|
|
41
|
-
|
|
42
|
-
## Excluded artifact behavior
|
|
43
|
-
|
|
44
|
-
Files marked as generated, vendor, binary, doc-only, or explicitly excluded should remain visible in manifests and disposition tracking, but should not receive normal audit-unit assignment or requeue tasks.
|
|
45
|
-
|
|
46
|
-
## Critical flow role
|
|
47
|
-
|
|
48
|
-
`critical_flows.json` is intended to bridge deterministic planning and higher-order semantic review. It gives LLM agents a bounded way to inspect important end-to-end paths without reading the entire repository at once.
|
|
49
|
-
|
|
50
|
-
`flow_coverage.json` tracks whether those important paths have received the intended lenses, which allows the planner to treat critical-flow review as a first-class coverage requirement rather than a loose advisory layer.
|
|
51
|
-
|
|
52
|
-
## Runtime validation role
|
|
53
|
-
|
|
54
|
-
`runtime_validation_tasks.json` turns unresolved high-risk units and incomplete critical flows into explicit dynamic follow-up work.
|
|
55
|
-
|
|
56
|
-
`runtime_validation_report.json` is where evidence from those checks should land so that later synthesis can distinguish confirmed, not-confirmed, and inconclusive concerns.
|
|
24
|
+
- dispatch files for the currently active worker task
|
|
57
25
|
|
|
58
|
-
##
|
|
26
|
+
## Scope rule
|
|
59
27
|
|
|
60
|
-
|
|
28
|
+
Excluded files remain visible in deterministic intake/disposition where useful,
|
|
29
|
+
but they must not create audit work. This includes logs, licenses, lockfiles,
|
|
30
|
+
generated artifacts, vendored artifacts, binaries, and trivial non-code files.
|
|
61
31
|
|
|
62
|
-
|
|
32
|
+
## Completion rule
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
- dedicated analyzer follow-up tasks
|
|
68
|
-
- requeue priority
|
|
69
|
-
- synthesis evidence and summaries
|
|
34
|
+
These artifacts are transient implementation state only. When the audit
|
|
35
|
+
completes, `.audit-artifacts/` is removed and only repo-root `audit-report.md`
|
|
36
|
+
remains.
|