auditor-lambda 0.1.0 → 0.2.2
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 +2 -1
- package/audit-code-wrapper-lib.mjs +205 -187
- package/dist/adapters/eslint.js +4 -2
- package/dist/adapters/npmAudit.js +1 -1
- package/dist/cli.js +296 -12
- package/dist/coverage.d.ts +0 -1
- package/dist/coverage.js +3 -34
- package/dist/extractors/bucketing.js +14 -35
- package/dist/extractors/disposition.js +8 -9
- package/dist/extractors/flows.js +14 -23
- package/dist/extractors/pathPatterns.d.ts +19 -0
- package/dist/extractors/pathPatterns.js +87 -0
- package/dist/extractors/surfaces.js +2 -7
- package/dist/io/artifacts.d.ts +23 -1
- package/dist/io/artifacts.js +3 -1
- package/dist/io/runArtifacts.js +1 -1
- package/dist/orchestrator/advance.js +1 -1
- package/dist/orchestrator/flowPlanning.d.ts +1 -1
- package/dist/orchestrator/flowPlanning.js +21 -28
- package/dist/orchestrator/internalExecutors.js +4 -7
- package/dist/orchestrator/planning.js +12 -20
- package/dist/orchestrator/resultIngestion.js +3 -2
- package/dist/orchestrator/runtimeValidation.js +5 -0
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
- package/dist/orchestrator/taskBuilder.d.ts +7 -2
- package/dist/orchestrator/taskBuilder.js +47 -52
- package/dist/prompts/renderWorkerPrompt.js +33 -0
- package/dist/providers/claudeCodeProvider.js +5 -0
- package/dist/providers/constants.d.ts +1 -0
- package/dist/providers/constants.js +1 -0
- package/dist/providers/index.js +9 -2
- package/dist/providers/spawnLoggedCommand.js +4 -0
- package/dist/reporting/mergeFindings.js +0 -7
- package/dist/reporting/rootCause.d.ts +0 -1
- package/dist/reporting/rootCause.js +0 -6
- package/dist/reporting/synthesis.js +18 -0
- package/dist/supervisor/operatorHandoff.d.ts +2 -0
- package/dist/supervisor/operatorHandoff.js +21 -9
- package/dist/supervisor/runLedger.js +6 -3
- package/dist/supervisor/sessionConfig.js +1 -0
- package/dist/types/flowCoverage.d.ts +1 -1
- package/dist/types/runLedger.d.ts +1 -1
- package/dist/types/runtimeValidation.d.ts +2 -1
- package/dist/types/sessionConfig.d.ts +2 -0
- package/dist/types/surfaces.d.ts +2 -1
- package/dist/types/workerSession.d.ts +4 -0
- package/dist/types.d.ts +0 -2
- package/dist/validation/auditResults.d.ts +11 -0
- package/dist/validation/auditResults.js +118 -0
- package/docs/agent-integrations.md +61 -56
- package/docs/agent-roles.md +69 -69
- package/docs/architecture.md +90 -90
- package/docs/artifacts.md +69 -69
- package/docs/bootstrap-install.md +1 -1
- package/docs/model-selection.md +86 -86
- package/docs/next-steps.md +11 -9
- package/docs/packaging.md +3 -3
- package/docs/pipeline.md +152 -152
- package/docs/production-readiness.md +6 -5
- package/docs/repo-layout.md +18 -18
- package/docs/run-flow.md +5 -5
- package/docs/session-config.md +216 -210
- package/docs/supervisor.md +70 -70
- package/docs/windows-setup.md +139 -139
- package/package.json +56 -56
- package/schemas/audit-code-v1alpha1.schema.json +80 -76
- package/schemas/audit_result.schema.json +54 -48
- package/schemas/audit_state.schema.json +2 -2
- package/schemas/audit_task.schema.json +60 -49
- package/schemas/blind_spot_register.schema.json +13 -3
- package/schemas/coverage_matrix.schema.json +14 -17
- package/schemas/critical_flows.schema.json +6 -3
- package/schemas/external_analyzer_results.schema.json +10 -4
- package/schemas/file_disposition.schema.json +33 -33
- package/schemas/finding.schema.json +86 -62
- package/schemas/flow_coverage.schema.json +53 -44
- package/schemas/graph_bundle.schema.json +12 -6
- package/schemas/merged_findings.schema.json +7 -2
- package/schemas/risk_register.schema.json +5 -1
- package/schemas/root_cause_clusters.schema.json +2 -5
- package/schemas/runtime_validation_report.schema.json +34 -34
- package/schemas/runtime_validation_tasks.schema.json +4 -1
- package/schemas/surface_manifest.schema.json +4 -1
- package/schemas/synthesis_report.schema.json +61 -61
- package/schemas/unit_manifest.schema.json +10 -3
- package/skills/audit-code/SKILL.md +37 -37
- package/skills/audit-code/audit-code.prompt.md +54 -54
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
function chunkRanges(totalLines, chunkSize) {
|
|
2
|
-
const ranges = [];
|
|
3
|
-
let start = 1;
|
|
4
|
-
while (start <= totalLines) {
|
|
5
|
-
const end = Math.min(start + chunkSize - 1, totalLines);
|
|
6
|
-
ranges.push({ start, end });
|
|
7
|
-
start = end + 1;
|
|
8
|
-
}
|
|
9
|
-
return ranges;
|
|
10
|
-
}
|
|
11
1
|
function taskPriority(hasExternalSignal, lens) {
|
|
12
2
|
if (hasExternalSignal &&
|
|
13
3
|
(lens === "security" || lens === "data_integrity" || lens === "reliability")) {
|
|
@@ -47,8 +37,9 @@ function pickAnalyzerLens(category) {
|
|
|
47
37
|
return "maintainability";
|
|
48
38
|
return "correctness";
|
|
49
39
|
}
|
|
40
|
+
const DEFAULT_FILE_SPLIT_THRESHOLD = 3000;
|
|
50
41
|
export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}) {
|
|
51
|
-
const
|
|
42
|
+
const fileSplitThreshold = options.file_split_threshold ?? DEFAULT_FILE_SPLIT_THRESHOLD;
|
|
52
43
|
const allowed = new Set(options.limit_lenses ?? []);
|
|
53
44
|
const enforceLensFilter = allowed.size > 0;
|
|
54
45
|
const tasks = [];
|
|
@@ -63,49 +54,50 @@ export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}
|
|
|
63
54
|
if (enforceLensFilter && !allowed.has(lens)) {
|
|
64
55
|
continue;
|
|
65
56
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
unit_id: unit.unit_id,
|
|
80
|
-
pass_id: `pass:${lens}`,
|
|
81
|
-
lens,
|
|
82
|
-
file_paths: [filePath],
|
|
83
|
-
rationale: `Audit ${filePath} under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority for this path." : ""}`,
|
|
84
|
-
priority,
|
|
85
|
-
tags,
|
|
86
|
-
});
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
for (const range of ranges) {
|
|
90
|
-
const id = `${unit.unit_id}:${lens}:${filePath}:${range.start}-${range.end}`;
|
|
91
|
-
if (seen.has(id))
|
|
92
|
-
continue;
|
|
57
|
+
const hasExternalSignal = unit.files.some((f) => externalPaths.has(f));
|
|
58
|
+
const priority = taskPriority(hasExternalSignal, lens);
|
|
59
|
+
const tags = hasExternalSignal ? ["external_analyzer_signal"] : [];
|
|
60
|
+
// Split files that are individually too large to group; everything else
|
|
61
|
+
// goes into one task so the agent can reason across file boundaries.
|
|
62
|
+
const oversizedFiles = fileSplitThreshold > 0
|
|
63
|
+
? unit.files.filter((f) => (unitLineIndex[f] ?? 0) > fileSplitThreshold)
|
|
64
|
+
: [];
|
|
65
|
+
const normalFiles = unit.files.filter((f) => !oversizedFiles.includes(f));
|
|
66
|
+
// One task for all normal-sized files in this unit under this lens.
|
|
67
|
+
if (normalFiles.length > 0) {
|
|
68
|
+
const id = `${unit.unit_id}:${lens}`;
|
|
69
|
+
if (!seen.has(id)) {
|
|
93
70
|
seen.add(id);
|
|
94
71
|
tasks.push({
|
|
95
72
|
task_id: id,
|
|
96
73
|
unit_id: unit.unit_id,
|
|
97
74
|
pass_id: `pass:${lens}`,
|
|
98
75
|
lens,
|
|
99
|
-
file_paths:
|
|
100
|
-
|
|
101
|
-
{ path: filePath, start: range.start, end: range.end },
|
|
102
|
-
],
|
|
103
|
-
rationale: `Audit ${filePath} lines ${range.start}-${range.end} under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority for this path." : ""}`,
|
|
76
|
+
file_paths: normalFiles,
|
|
77
|
+
rationale: `Audit ${unit.unit_id} (${normalFiles.length} file${normalFiles.length === 1 ? "" : "s"}) under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
|
|
104
78
|
priority,
|
|
105
79
|
tags,
|
|
106
80
|
});
|
|
107
81
|
}
|
|
108
82
|
}
|
|
83
|
+
// Oversized files each get their own task so the agent isn't overwhelmed.
|
|
84
|
+
for (const filePath of oversizedFiles) {
|
|
85
|
+
const id = `${unit.unit_id}:${lens}:${filePath}`;
|
|
86
|
+
if (seen.has(id))
|
|
87
|
+
continue;
|
|
88
|
+
seen.add(id);
|
|
89
|
+
const fileHasSignal = externalPaths.has(filePath);
|
|
90
|
+
tasks.push({
|
|
91
|
+
task_id: id,
|
|
92
|
+
unit_id: unit.unit_id,
|
|
93
|
+
pass_id: `pass:${lens}`,
|
|
94
|
+
lens,
|
|
95
|
+
file_paths: [filePath],
|
|
96
|
+
rationale: `Audit ${filePath} (large file, split from unit) under the ${lens} lens.${fileHasSignal ? " External analyzer signals raise priority." : ""}`,
|
|
97
|
+
priority: taskPriority(fileHasSignal, lens),
|
|
98
|
+
tags: fileHasSignal ? ["external_analyzer_signal", "large_file"] : ["large_file"],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
109
101
|
}
|
|
110
102
|
}
|
|
111
103
|
return tasks.sort((a, b) => {
|
|
@@ -115,34 +107,37 @@ export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}
|
|
|
115
107
|
return a.task_id.localeCompare(b.task_id);
|
|
116
108
|
});
|
|
117
109
|
}
|
|
118
|
-
|
|
110
|
+
/** Strip control characters and newlines, then truncate to maxLen. */
|
|
111
|
+
function sanitizeField(value, maxLen) {
|
|
112
|
+
return value.replace(/[\x00-\x1f\x7f]/g, " ").slice(0, maxLen).trimEnd();
|
|
113
|
+
}
|
|
114
|
+
export function buildExternalSignalTasks(coverageMatrix, _unitLineIndex, externalAnalyzerResults) {
|
|
119
115
|
if (!externalAnalyzerResults) {
|
|
120
116
|
return [];
|
|
121
117
|
}
|
|
122
118
|
const tasks = [];
|
|
123
119
|
const seen = new Set();
|
|
124
120
|
for (const result of externalAnalyzerResults.results) {
|
|
125
|
-
const
|
|
121
|
+
const safeCategory = sanitizeField(result.category, 80);
|
|
122
|
+
const safePath = sanitizeField(result.path ?? "", 260);
|
|
123
|
+
const safeSummary = sanitizeField(result.summary ?? "", 200);
|
|
124
|
+
const lens = pickAnalyzerLens(safeCategory);
|
|
126
125
|
const coverage = coverageMatrix.files.find((file) => file.path === result.path);
|
|
127
126
|
if (!coverage || coverage.audit_status === "excluded") {
|
|
128
127
|
continue;
|
|
129
128
|
}
|
|
130
|
-
const
|
|
131
|
-
const id = `analyzer:${externalAnalyzerResults.tool}:${lens}:${result.path}:${result.id}`;
|
|
129
|
+
const id = `analyzer:${externalAnalyzerResults.tool}:${lens}:${safePath}:${result.id}`;
|
|
132
130
|
if (seen.has(id)) {
|
|
133
131
|
continue;
|
|
134
132
|
}
|
|
135
133
|
seen.add(id);
|
|
136
134
|
tasks.push({
|
|
137
135
|
task_id: id,
|
|
138
|
-
unit_id: coverage.unit_ids[0] ?? `analyzer:${
|
|
136
|
+
unit_id: coverage.unit_ids[0] ?? `analyzer:${safePath}`,
|
|
139
137
|
pass_id: `analyzer:${externalAnalyzerResults.tool}:${lens}`,
|
|
140
138
|
lens,
|
|
141
139
|
file_paths: [result.path],
|
|
142
|
-
|
|
143
|
-
? [{ path: result.path, start: 1, end: lineCount }]
|
|
144
|
-
: undefined,
|
|
145
|
-
rationale: `Analyzer follow-up for ${result.path} under the ${lens} lens because ${externalAnalyzerResults.tool} reported: ${result.summary}`,
|
|
140
|
+
rationale: `Analyzer follow-up for ${safePath} under the ${lens} lens because ${externalAnalyzerResults.tool} reported: ${safeSummary}`,
|
|
146
141
|
priority: "high",
|
|
147
142
|
tags: [
|
|
148
143
|
"external_analyzer_signal",
|
|
@@ -3,6 +3,39 @@ function shellQuote(arg) {
|
|
|
3
3
|
}
|
|
4
4
|
export function renderWorkerPrompt(task) {
|
|
5
5
|
const command = task.worker_command.map(shellQuote).join(" ");
|
|
6
|
+
if (task.preferred_executor === "agent" && task.audit_results_path) {
|
|
7
|
+
const tasksPath = task.pending_audit_tasks_path ??
|
|
8
|
+
`${task.artifacts_dir}/audit_tasks.json`;
|
|
9
|
+
const lines = [
|
|
10
|
+
"You are executing one bounded audit task for audit-code.",
|
|
11
|
+
`Run ID: ${task.run_id}`,
|
|
12
|
+
`Repository root: ${task.repo_root}`,
|
|
13
|
+
"",
|
|
14
|
+
`Read the task file: ${tasksPath}`,
|
|
15
|
+
"It contains the task(s) assigned to this run.",
|
|
16
|
+
"",
|
|
17
|
+
"For each task:",
|
|
18
|
+
" 1. Read every file listed in file_paths in full using your file-reading tool.",
|
|
19
|
+
" If line_ranges are present, they are a focus hint — still read the whole file.",
|
|
20
|
+
" 2. Review the content under the specified lens.",
|
|
21
|
+
" 3. Emit one AuditResult with:",
|
|
22
|
+
" task_id, unit_id, pass_id, lens",
|
|
23
|
+
" reviewed_ranges: [{path, start, end}] covering what you read",
|
|
24
|
+
" findings: array (empty if nothing found)",
|
|
25
|
+
" Each finding must include:",
|
|
26
|
+
" id, title, category, severity, confidence, lens, summary, affected_files,",
|
|
27
|
+
" evidence (at least one excerpt or line reference from the file you read)",
|
|
28
|
+
" Optional finding fields: impact, likelihood, reproduction, systemic, related_findings",
|
|
29
|
+
`Write the AuditResult[] JSON array to: ${task.audit_results_path}`,
|
|
30
|
+
];
|
|
31
|
+
if (task.skip_worker_command) {
|
|
32
|
+
lines.push("", "Stop after writing the results file.");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push("", "Then run this command exactly:", command, "Stop after the command completes.");
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
6
39
|
return [
|
|
7
40
|
"You are executing one bounded audit step for audit-code.",
|
|
8
41
|
`Run ID: ${task.run_id}`,
|
|
@@ -7,6 +7,11 @@ export class ClaudeCodeProvider {
|
|
|
7
7
|
this.config = config;
|
|
8
8
|
}
|
|
9
9
|
async launch(input) {
|
|
10
|
+
if (process.env.CLAUDECODE) {
|
|
11
|
+
throw new Error("claude-code provider cannot be used inside an active Claude Code session. " +
|
|
12
|
+
"Set provider to \"local-subprocess\" in .audit-artifacts/session-config.json, " +
|
|
13
|
+
"then run /audit-code conversationally and follow the dispatch prompts manually.");
|
|
14
|
+
}
|
|
10
15
|
const prompt = await readFile(input.promptPath, "utf8");
|
|
11
16
|
const command = this.config.command ?? "claude";
|
|
12
17
|
const args = [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const LOCAL_SUBPROCESS_PROVIDER_NAME: "local-subprocess";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LOCAL_SUBPROCESS_PROVIDER_NAME = "local-subprocess";
|
package/dist/providers/index.js
CHANGED
|
@@ -28,6 +28,7 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
|
|
|
28
28
|
const env = options.env ?? process.env;
|
|
29
29
|
const lookupCommand = options.commandExists ?? commandExists;
|
|
30
30
|
const inVSCode = (env.TERM_PROGRAM ?? "").toLowerCase() === "vscode";
|
|
31
|
+
const insideClaudeCode = Boolean(env.CLAUDECODE);
|
|
31
32
|
if (inVSCode && hasEntries(sessionConfig.vscode_task?.command_template)) {
|
|
32
33
|
return "vscode-task";
|
|
33
34
|
}
|
|
@@ -36,9 +37,9 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
|
|
|
36
37
|
}
|
|
37
38
|
const claudeCommand = sessionConfig.claude_code?.command ?? "claude";
|
|
38
39
|
const opencodeCommand = sessionConfig.opencode?.command ?? "opencode";
|
|
39
|
-
const claudeAvailable = lookupCommand(claudeCommand);
|
|
40
|
+
const claudeAvailable = !insideClaudeCode && lookupCommand(claudeCommand);
|
|
40
41
|
const opencodeAvailable = lookupCommand(opencodeCommand);
|
|
41
|
-
if (hasConfiguredClaudeCode(sessionConfig) && claudeAvailable) {
|
|
42
|
+
if (!insideClaudeCode && hasConfiguredClaudeCode(sessionConfig) && claudeAvailable) {
|
|
42
43
|
return "claude-code";
|
|
43
44
|
}
|
|
44
45
|
if (hasConfiguredOpenCode(sessionConfig) && opencodeAvailable) {
|
|
@@ -54,6 +55,12 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
|
|
|
54
55
|
}
|
|
55
56
|
export function createFreshSessionProvider(name, sessionConfig = {}) {
|
|
56
57
|
const providerName = resolveFreshSessionProviderName(name, sessionConfig);
|
|
58
|
+
if (providerName === "local-subprocess" &&
|
|
59
|
+
(name ?? sessionConfig.provider) === "auto") {
|
|
60
|
+
process.stderr.write("audit-code: auto provider resolved to local-subprocess — no capable agent provider detected. " +
|
|
61
|
+
"Agent tasks will require manual dispatch. Configure claude-code, opencode, or subprocess-template " +
|
|
62
|
+
"in session-config.json to automate them.\n");
|
|
63
|
+
}
|
|
57
64
|
switch (providerName) {
|
|
58
65
|
case "local-subprocess":
|
|
59
66
|
return new LocalSubprocessProvider();
|
|
@@ -3,6 +3,10 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
function tee(write, chunk) {
|
|
4
4
|
write.write(chunk);
|
|
5
5
|
}
|
|
6
|
+
// On Windows `command` must be the resolved .cmd / .exe path because `spawn`
|
|
7
|
+
// does not consult PATH for executables without a shell. Callers should use
|
|
8
|
+
// `platformCommand()` (scripts/smoke-packaged-audit-code.mjs) or similar to
|
|
9
|
+
// supply the correct command form for the host OS.
|
|
6
10
|
export async function spawnLoggedCommand(command, args, input, env) {
|
|
7
11
|
return await new Promise((resolve, reject) => {
|
|
8
12
|
const stdoutLog = createWriteStream(input.stdoutPath, { flags: "a" });
|
|
@@ -80,7 +80,6 @@ export function mergeFindings(results, runtimeReport, externalAnalyzerResults) {
|
|
|
80
80
|
...analyzerEvidence,
|
|
81
81
|
]),
|
|
82
82
|
],
|
|
83
|
-
remediation: [...new Set(finding.remediation ?? [])],
|
|
84
83
|
related_findings: [
|
|
85
84
|
...new Set([...(finding.related_findings ?? []), finding.id]),
|
|
86
85
|
],
|
|
@@ -109,12 +108,6 @@ export function mergeFindings(results, runtimeReport, externalAnalyzerResults) {
|
|
|
109
108
|
...analyzerEvidence,
|
|
110
109
|
]),
|
|
111
110
|
];
|
|
112
|
-
existing.remediation = [
|
|
113
|
-
...new Set([
|
|
114
|
-
...(existing.remediation ?? []),
|
|
115
|
-
...(finding.remediation ?? []),
|
|
116
|
-
]),
|
|
117
|
-
];
|
|
118
111
|
existing.related_findings = [
|
|
119
112
|
...new Set([
|
|
120
113
|
...(existing.related_findings ?? []),
|
|
@@ -6,6 +6,5 @@ export interface RootCauseCluster {
|
|
|
6
6
|
title: string;
|
|
7
7
|
summary: string;
|
|
8
8
|
finding_ids: string[];
|
|
9
|
-
recommended_actions: string[];
|
|
10
9
|
}
|
|
11
10
|
export declare function buildRootCauseClusters(findings: Finding[], runtimeReport?: RuntimeValidationReport, externalAnalyzerResults?: ExternalAnalyzerResults): RootCauseCluster[];
|
|
@@ -52,17 +52,11 @@ export function buildRootCauseClusters(findings, runtimeReport, externalAnalyzer
|
|
|
52
52
|
.map(([key, grouped], index) => {
|
|
53
53
|
const highestSeverity = grouped.reduce((max, finding) => Math.max(max, severityRank(finding.severity)), 1);
|
|
54
54
|
const systemicCount = grouped.filter((finding) => finding.systemic).length;
|
|
55
|
-
const uniqueRemediations = [
|
|
56
|
-
...new Set(grouped.flatMap((finding) => finding.remediation ?? [])),
|
|
57
|
-
].slice(0, 5);
|
|
58
55
|
return {
|
|
59
56
|
id: `cluster-${index + 1}`,
|
|
60
57
|
title: titleForCluster(key),
|
|
61
58
|
summary: `Grouped ${grouped.length} finding(s); highest severity ${highestSeverity}; systemic flags ${systemicCount}. Runtime validation status: ${runtimeSummary}. External analyzer summary: ${externalSummary}.`,
|
|
62
59
|
finding_ids: grouped.map((finding) => finding.id),
|
|
63
|
-
recommended_actions: uniqueRemediations.length > 0
|
|
64
|
-
? uniqueRemediations
|
|
65
|
-
: [`Review systemic causes behind ${titleForCluster(key)}.`],
|
|
66
60
|
};
|
|
67
61
|
})
|
|
68
62
|
.sort((a, b) => a.title.localeCompare(b.title));
|
|
@@ -14,6 +14,23 @@ function severityBreakdown(findings) {
|
|
|
14
14
|
}
|
|
15
15
|
return breakdown;
|
|
16
16
|
}
|
|
17
|
+
function zeroFindingLensNotes(results) {
|
|
18
|
+
const tasksByLens = new Map();
|
|
19
|
+
const findingsByLens = new Map();
|
|
20
|
+
for (const result of results) {
|
|
21
|
+
tasksByLens.set(result.lens, (tasksByLens.get(result.lens) ?? 0) + 1);
|
|
22
|
+
findingsByLens.set(result.lens, (findingsByLens.get(result.lens) ?? 0) + result.findings.length);
|
|
23
|
+
}
|
|
24
|
+
const zeroLenses = [...tasksByLens.entries()]
|
|
25
|
+
.filter(([lens, count]) => count > 0 && (findingsByLens.get(lens) ?? 0) === 0)
|
|
26
|
+
.map(([lens]) => lens)
|
|
27
|
+
.sort();
|
|
28
|
+
if (zeroLenses.length === 0)
|
|
29
|
+
return [];
|
|
30
|
+
return [
|
|
31
|
+
`Zero findings across all reviewed tasks for lens(es): ${zeroLenses.join(", ")}. Verify these tasks were genuinely reviewed rather than batch-generated.`,
|
|
32
|
+
];
|
|
33
|
+
}
|
|
17
34
|
function externalSummary(results) {
|
|
18
35
|
if (!results) {
|
|
19
36
|
return { tool_count: 0, result_count: 0 };
|
|
@@ -47,6 +64,7 @@ export function buildSynthesisReport(results, runtimeReport, externalAnalyzerRes
|
|
|
47
64
|
`External analyzer signals incorporated: ${extSummary.result_count} result(s) from ${externalAnalyzerResults?.tool}.`,
|
|
48
65
|
]
|
|
49
66
|
: []),
|
|
67
|
+
...zeroFindingLensNotes(results),
|
|
50
68
|
],
|
|
51
69
|
},
|
|
52
70
|
merged_findings,
|
|
@@ -14,6 +14,7 @@ export interface AuditCodeHandoffArtifactPaths {
|
|
|
14
14
|
audit_tasks: string | null;
|
|
15
15
|
runtime_validation_tasks: string | null;
|
|
16
16
|
}
|
|
17
|
+
export declare const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
17
18
|
export interface AuditCodeHandoff {
|
|
18
19
|
status: AuditTopLevelStatus;
|
|
19
20
|
repo_root: string;
|
|
@@ -33,5 +34,6 @@ export declare function buildAuditCodeHandoff(params: {
|
|
|
33
34
|
bundle: ArtifactBundle;
|
|
34
35
|
providerName?: string | null;
|
|
35
36
|
progressSummary: string;
|
|
37
|
+
isConfigError?: boolean;
|
|
36
38
|
}): AuditCodeHandoff;
|
|
37
39
|
export declare function writeAuditCodeHandoffArtifacts(handoff: AuditCodeHandoff): Promise<void>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeJsonFile } from "../io/json.js";
|
|
4
|
+
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "../providers/constants.js";
|
|
5
|
+
export const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
4
6
|
function quoteShellPath(path) {
|
|
5
7
|
return `"${path.replace(/"/g, '\\"')}"`;
|
|
6
8
|
}
|
|
@@ -23,8 +25,8 @@ function buildSummary(status, providerName, fallbackSummary) {
|
|
|
23
25
|
? `Automatic work can continue under ${providerName}. Re-run the same wrapper or inspect the listed artifacts if you need operator context.`
|
|
24
26
|
: "Automatic work can continue. Re-run the same wrapper or inspect the listed artifacts if you need operator context.";
|
|
25
27
|
}
|
|
26
|
-
function buildSuggestedInputs(artifactsDir, status) {
|
|
27
|
-
if (status !== "blocked") {
|
|
28
|
+
function buildSuggestedInputs(artifactsDir, status, isConfigError) {
|
|
29
|
+
if (status !== "blocked" || isConfigError) {
|
|
28
30
|
return [];
|
|
29
31
|
}
|
|
30
32
|
const incomingDir = join(artifactsDir, "incoming");
|
|
@@ -52,11 +54,14 @@ function buildSuggestedCommands(suggestedInputs, status) {
|
|
|
52
54
|
}
|
|
53
55
|
return suggestedInputs.map((item) => `audit-code ${item.flag} ${quoteShellPath(item.suggested_path)}`);
|
|
54
56
|
}
|
|
55
|
-
function buildInteractiveProviderHint(status, providerName, sessionConfigPath) {
|
|
57
|
+
function buildInteractiveProviderHint(status, providerName, sessionConfigPath, isConfigError) {
|
|
56
58
|
if (status !== "blocked") {
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
if (isConfigError) {
|
|
62
|
+
return `A project configuration issue is blocking the audit. Verify that --root points to the repository root containing a project file (package.json, go.mod, etc.), then run audit-code again.`;
|
|
63
|
+
}
|
|
64
|
+
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
60
65
|
return `Current provider is ${providerLabel}. If you want the backend to continue through an interactive provider instead of importing results manually, set "provider" in ${sessionConfigPath} to "auto", "claude-code", "opencode", "subprocess-template", or "vscode-task", then run audit-code again from the repository root.`;
|
|
61
66
|
}
|
|
62
67
|
function renderMarkdown(handoff) {
|
|
@@ -109,6 +114,7 @@ function renderMarkdown(handoff) {
|
|
|
109
114
|
return lines.join("\n");
|
|
110
115
|
}
|
|
111
116
|
export function buildAuditCodeHandoff(params) {
|
|
117
|
+
const isConfigError = params.isConfigError ?? false;
|
|
112
118
|
const incomingDir = join(params.artifactsDir, "incoming");
|
|
113
119
|
const artifactPaths = {
|
|
114
120
|
incoming_dir: incomingDir,
|
|
@@ -123,7 +129,7 @@ export function buildAuditCodeHandoff(params) {
|
|
|
123
129
|
? join(params.artifactsDir, "runtime_validation_tasks.json")
|
|
124
130
|
: null,
|
|
125
131
|
};
|
|
126
|
-
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status);
|
|
132
|
+
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status, isConfigError);
|
|
127
133
|
return {
|
|
128
134
|
status: params.state.status,
|
|
129
135
|
repo_root: params.root,
|
|
@@ -133,12 +139,18 @@ export function buildAuditCodeHandoff(params) {
|
|
|
133
139
|
pending_obligations: buildPendingObligations(params.state),
|
|
134
140
|
suggested_inputs: suggestedInputs,
|
|
135
141
|
suggested_commands: buildSuggestedCommands(suggestedInputs, params.state.status),
|
|
136
|
-
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config),
|
|
142
|
+
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config, isConfigError),
|
|
137
143
|
artifact_paths: artifactPaths,
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
export async function writeAuditCodeHandoffArtifacts(handoff) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
try {
|
|
148
|
+
await mkdir(handoff.artifact_paths.incoming_dir, { recursive: true });
|
|
149
|
+
await writeJsonFile(handoff.artifact_paths.operator_handoff_json, handoff);
|
|
150
|
+
await writeFile(handoff.artifact_paths.operator_handoff_markdown, renderMarkdown(handoff), "utf8");
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new Error(`Failed to write operator handoff artifacts: ${message}`);
|
|
155
|
+
}
|
|
144
156
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readJsonFile, writeJsonFile } from "../io/json.js";
|
|
1
|
+
import { isFileMissingError, readJsonFile, writeJsonFile } from "../io/json.js";
|
|
2
2
|
function ledgerPath(artifactsDir) {
|
|
3
3
|
return `${artifactsDir}/run-ledger.json`;
|
|
4
4
|
}
|
|
@@ -6,8 +6,11 @@ export async function loadRunLedger(artifactsDir) {
|
|
|
6
6
|
try {
|
|
7
7
|
return await readJsonFile(ledgerPath(artifactsDir));
|
|
8
8
|
}
|
|
9
|
-
catch {
|
|
10
|
-
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (isFileMissingError(error)) {
|
|
11
|
+
return { runs: [] };
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
export async function appendRunLedgerEntry(artifactsDir, entry) {
|
|
@@ -16,6 +16,7 @@ export async function loadSessionConfig(artifactsDir) {
|
|
|
16
16
|
const configPath = getSessionConfigPath(artifactsDir);
|
|
17
17
|
const rawConfig = await readOptionalJsonFile(configPath);
|
|
18
18
|
if (rawConfig === undefined) {
|
|
19
|
+
process.stderr.write(`[session-config] no session-config.json found at ${configPath}; using empty defaults\n`);
|
|
19
20
|
return {};
|
|
20
21
|
}
|
|
21
22
|
const issues = validateSessionConfig(rawConfig);
|
package/dist/types/surfaces.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export interface WorkerTask {
|
|
|
8
8
|
result_path: string;
|
|
9
9
|
worker_command: string[];
|
|
10
10
|
audit_results_path?: string;
|
|
11
|
+
pending_audit_tasks_path?: string;
|
|
11
12
|
runtime_updates_path?: string;
|
|
12
13
|
external_analyzer_results_path?: string;
|
|
14
|
+
skip_worker_command?: boolean;
|
|
15
|
+
timeout_ms?: number;
|
|
16
|
+
max_retries?: number;
|
|
13
17
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -43,7 +43,6 @@ export interface CoverageFileRecord {
|
|
|
43
43
|
audit_status: string;
|
|
44
44
|
required_lenses: Lens[];
|
|
45
45
|
completed_lenses: Lens[];
|
|
46
|
-
reviewed_line_ranges: ReviewedLineRange[];
|
|
47
46
|
}
|
|
48
47
|
export interface CoverageMatrix {
|
|
49
48
|
files: CoverageFileRecord[];
|
|
@@ -82,7 +81,6 @@ export interface Finding {
|
|
|
82
81
|
likelihood?: string;
|
|
83
82
|
evidence?: string[];
|
|
84
83
|
reproduction?: string[];
|
|
85
|
-
remediation?: string[];
|
|
86
84
|
systemic?: boolean;
|
|
87
85
|
related_findings?: string[];
|
|
88
86
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AuditResult, AuditTask } from "../types.js";
|
|
2
|
+
export type IssueSeverity = "error" | "warning";
|
|
3
|
+
export interface AuditResultIssue {
|
|
4
|
+
result_index: number;
|
|
5
|
+
task_id: string;
|
|
6
|
+
severity: IssueSeverity;
|
|
7
|
+
field: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function validateAuditResults(results: AuditResult[], tasks: AuditTask[]): AuditResultIssue[];
|
|
11
|
+
export declare function formatAuditResultIssues(issues: AuditResultIssue[]): string;
|