auditor-lambda 0.3.41 → 0.6.0
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/dist/cli/dispatch.js +5 -1
- package/dist/cli/prompts.d.ts +19 -0
- package/dist/cli/prompts.js +95 -0
- package/dist/cli/steps.d.ts +1 -1
- package/dist/cli.js +398 -78
- package/dist/extractors/analyzers/css.d.ts +2 -0
- package/dist/extractors/analyzers/css.js +101 -0
- package/dist/extractors/analyzers/html.d.ts +2 -0
- package/dist/extractors/analyzers/html.js +92 -0
- package/dist/extractors/analyzers/merge.d.ts +14 -0
- package/dist/extractors/analyzers/merge.js +85 -0
- package/dist/extractors/analyzers/python.d.ts +2 -0
- package/dist/extractors/analyzers/python.js +104 -0
- package/dist/extractors/analyzers/registry.d.ts +33 -0
- package/dist/extractors/analyzers/registry.js +100 -0
- package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
- package/dist/extractors/analyzers/resourceUrl.js +25 -0
- package/dist/extractors/analyzers/sql.d.ts +2 -0
- package/dist/extractors/analyzers/sql.js +19 -0
- package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
- package/dist/extractors/analyzers/treeSitter.js +111 -0
- package/dist/extractors/analyzers/types.d.ts +53 -0
- package/dist/extractors/analyzers/types.js +1 -0
- package/dist/extractors/analyzers/typescript.d.ts +2 -0
- package/dist/extractors/analyzers/typescript.js +257 -0
- package/dist/extractors/disposition.js +8 -1
- package/dist/extractors/graph.d.ts +1 -0
- package/dist/extractors/graph.js +167 -1
- package/dist/extractors/graphPythonImports.d.ts +15 -0
- package/dist/extractors/graphPythonImports.js +36 -0
- package/dist/extractors/pathPatterns.d.ts +6 -0
- package/dist/extractors/pathPatterns.js +8 -0
- package/dist/io/artifacts.d.ts +13 -1
- package/dist/io/artifacts.js +19 -3
- package/dist/mcp/server.js +3 -3
- package/dist/orchestrator/advance.d.ts +20 -0
- package/dist/orchestrator/advance.js +61 -2
- package/dist/orchestrator/dependencyMap.js +27 -0
- package/dist/orchestrator/edgeReasoning.d.ts +39 -0
- package/dist/orchestrator/edgeReasoning.js +125 -0
- package/dist/orchestrator/executors.js +11 -1
- package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
- package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
- package/dist/orchestrator/internalExecutors.d.ts +10 -1
- package/dist/orchestrator/internalExecutors.js +89 -11
- package/dist/orchestrator/localCommands.js +6 -25
- package/dist/orchestrator/nextStep.js +2 -0
- package/dist/orchestrator/reviewPackets.d.ts +37 -4
- package/dist/orchestrator/reviewPackets.js +93 -46
- package/dist/orchestrator/runtimeValidation.js +4 -31
- package/dist/orchestrator/scope.d.ts +62 -0
- package/dist/orchestrator/scope.js +227 -0
- package/dist/orchestrator/state.js +2 -0
- package/dist/reporting/synthesis.d.ts +37 -2
- package/dist/reporting/synthesis.js +95 -16
- package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
- package/dist/reporting/synthesisNarrativePrompt.js +60 -0
- package/dist/reporting/workBlocks.d.ts +2 -10
- package/dist/supervisor/operatorHandoff.d.ts +1 -1
- package/dist/supervisor/operatorHandoff.js +26 -16
- package/dist/supervisor/sessionConfig.d.ts +8 -1
- package/dist/supervisor/sessionConfig.js +22 -1
- package/dist/types/analyzerCapability.d.ts +16 -0
- package/dist/types/analyzerCapability.js +1 -0
- package/dist/types/auditScope.d.ts +43 -0
- package/dist/types/auditScope.js +14 -0
- package/dist/types/synthesisNarrative.d.ts +7 -0
- package/dist/types/synthesisNarrative.js +5 -0
- package/dist/types.d.ts +2 -19
- package/dist/validation/artifacts.js +9 -0
- package/dist/validation/sessionConfig.js +24 -1
- package/docs/contracts.md +10 -3
- package/package.json +4 -2
- package/schemas/analyzer_capability.schema.json +47 -0
- package/schemas/audit_findings.schema.json +141 -0
- package/schemas/finding.schema.json +2 -1
- package/schemas/graph_bundle.schema.json +5 -0
- package/schemas/scope.schema.json +46 -0
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import type { AuditResult, CoverageMatrix, Finding, UnitManifest } from "../types.js";
|
|
2
|
+
import type { AuditScopeManifest } from "../types/auditScope.js";
|
|
2
3
|
import type { DesignAssessment } from "../types/designAssessment.js";
|
|
3
4
|
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
4
|
-
import type { CriticalFlowManifest, GraphBundle } from "@audit-tools/shared";
|
|
5
|
+
import type { AuditFindingsReport, CriticalFlowManifest, Finding as SharedFinding, FindingTheme, GraphBundle, SynthesisNarrative } from "@audit-tools/shared";
|
|
5
6
|
import type { RuntimeValidationReport } from "../types/runtimeValidation.js";
|
|
6
7
|
import { type WorkBlock } from "./workBlocks.js";
|
|
8
|
+
/** Contract version stamped onto the canonical `audit-findings.json`. */
|
|
9
|
+
export declare const AUDIT_FINDINGS_CONTRACT_VERSION = "audit-tools/audit-findings/v1";
|
|
10
|
+
/**
|
|
11
|
+
* Anything renderable as the deterministic audit report. Both `AuditReportModel`
|
|
12
|
+
* (no narrative) and the canonical `AuditFindingsReport` (optionally carrying
|
|
13
|
+
* themes/executive_summary/top_risks) satisfy this shape, so the same renderer
|
|
14
|
+
* produces the base report and the narrative-enriched report.
|
|
15
|
+
*/
|
|
16
|
+
export interface RenderableAuditReport {
|
|
17
|
+
summary: AuditReportSummary;
|
|
18
|
+
findings: SharedFinding[];
|
|
19
|
+
work_blocks: WorkBlock[];
|
|
20
|
+
themes?: FindingTheme[];
|
|
21
|
+
executive_summary?: string;
|
|
22
|
+
top_risks?: string[];
|
|
23
|
+
}
|
|
7
24
|
export interface AuditReportSummary {
|
|
8
25
|
finding_count: number;
|
|
9
26
|
work_block_count: number;
|
|
@@ -27,4 +44,22 @@ export declare function buildAuditReportModel(params: {
|
|
|
27
44
|
externalAnalyzerResults?: ExternalAnalyzerResults;
|
|
28
45
|
designAssessment?: DesignAssessment;
|
|
29
46
|
}): AuditReportModel;
|
|
30
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Wrap the deterministic report model in the canonical `audit-findings.json`
|
|
49
|
+
* contract — the machine hand-off consumed by the remediator. Narrative fields
|
|
50
|
+
* are absent here; they are layered on later by {@link applyNarrative}.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildAuditFindingsReport(model: AuditReportModel): AuditFindingsReport;
|
|
53
|
+
/**
|
|
54
|
+
* Merge an LLM synthesis narrative into the canonical findings report: keep only
|
|
55
|
+
* themes whose `finding_ids` reference real findings, tag each covered finding
|
|
56
|
+
* with its (first-claiming) `theme_id`, and attach the executive summary / top
|
|
57
|
+
* risks. Deterministic and idempotent — the same narrative yields the same
|
|
58
|
+
* report.
|
|
59
|
+
*/
|
|
60
|
+
export declare function applyNarrative(report: AuditFindingsReport, narrative: SynthesisNarrative): AuditFindingsReport;
|
|
61
|
+
export interface RenderAuditReportOptions {
|
|
62
|
+
/** Scope manifest for the run; when delta, the report header reports it honestly. */
|
|
63
|
+
scope?: AuditScopeManifest;
|
|
64
|
+
}
|
|
65
|
+
export declare function renderAuditReportMarkdown(report: RenderableAuditReport, options?: RenderAuditReportOptions): string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AUDITOR_REPORT_MARKER } from "@audit-tools/shared";
|
|
2
2
|
import { buildWorkBlocks } from "./workBlocks.js";
|
|
3
3
|
import { mergeFindings } from "./mergeFindings.js";
|
|
4
|
+
/** Contract version stamped onto the canonical `audit-findings.json`. */
|
|
5
|
+
export const AUDIT_FINDINGS_CONTRACT_VERSION = "audit-tools/audit-findings/v1";
|
|
4
6
|
function countBy(items, selectKey) {
|
|
5
7
|
const breakdown = {};
|
|
6
8
|
for (const item of items) {
|
|
@@ -54,27 +56,92 @@ export function buildAuditReportModel(params) {
|
|
|
54
56
|
work_blocks: workBlocks,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Wrap the deterministic report model in the canonical `audit-findings.json`
|
|
61
|
+
* contract — the machine hand-off consumed by the remediator. Narrative fields
|
|
62
|
+
* are absent here; they are layered on later by {@link applyNarrative}.
|
|
63
|
+
*/
|
|
64
|
+
export function buildAuditFindingsReport(model) {
|
|
65
|
+
return {
|
|
66
|
+
contract_version: AUDIT_FINDINGS_CONTRACT_VERSION,
|
|
67
|
+
summary: { ...model.summary },
|
|
68
|
+
findings: model.findings,
|
|
69
|
+
work_blocks: model.work_blocks,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Merge an LLM synthesis narrative into the canonical findings report: keep only
|
|
74
|
+
* themes whose `finding_ids` reference real findings, tag each covered finding
|
|
75
|
+
* with its (first-claiming) `theme_id`, and attach the executive summary / top
|
|
76
|
+
* risks. Deterministic and idempotent — the same narrative yields the same
|
|
77
|
+
* report.
|
|
78
|
+
*/
|
|
79
|
+
export function applyNarrative(report, narrative) {
|
|
80
|
+
const validFindingIds = new Set(report.findings.map((finding) => finding.id));
|
|
81
|
+
const themeByFinding = new Map();
|
|
82
|
+
const themes = [];
|
|
83
|
+
for (const theme of narrative.themes ?? []) {
|
|
84
|
+
const findingIds = [
|
|
85
|
+
...new Set((theme.finding_ids ?? []).filter((id) => validFindingIds.has(id))),
|
|
86
|
+
];
|
|
87
|
+
themes.push({
|
|
88
|
+
theme_id: theme.theme_id,
|
|
89
|
+
title: theme.title,
|
|
90
|
+
root_cause: theme.root_cause,
|
|
91
|
+
finding_ids: findingIds,
|
|
92
|
+
suggested_fix_pattern: theme.suggested_fix_pattern,
|
|
93
|
+
});
|
|
94
|
+
for (const id of findingIds) {
|
|
95
|
+
if (!themeByFinding.has(id)) {
|
|
96
|
+
themeByFinding.set(id, theme.theme_id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const findings = report.findings.map((finding) => themeByFinding.has(finding.id)
|
|
101
|
+
? { ...finding, theme_id: themeByFinding.get(finding.id) }
|
|
102
|
+
: finding);
|
|
103
|
+
return {
|
|
104
|
+
...report,
|
|
105
|
+
findings,
|
|
106
|
+
themes,
|
|
107
|
+
executive_summary: narrative.executive_summary,
|
|
108
|
+
top_risks: narrative.top_risks,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function renderAuditReportMarkdown(report, options = {}) {
|
|
58
112
|
const lines = [
|
|
59
113
|
AUDITOR_REPORT_MARKER,
|
|
60
114
|
"# Audit Report",
|
|
61
115
|
"",
|
|
62
|
-
"## Summary",
|
|
63
|
-
"",
|
|
64
|
-
`- Findings: ${model.summary.finding_count}`,
|
|
65
|
-
`- Work blocks: ${model.summary.work_block_count}`,
|
|
66
|
-
`- Severity breakdown: ${formatSeverityList(model.summary.severity_breakdown)}`,
|
|
67
|
-
`- Fully audited files: ${model.summary.audited_file_count}`,
|
|
68
|
-
`- Excluded non-auditable files: ${model.summary.excluded_file_count}`,
|
|
69
|
-
"",
|
|
70
|
-
"## Work Blocks",
|
|
71
|
-
"",
|
|
72
116
|
];
|
|
73
|
-
if (
|
|
117
|
+
if (report.executive_summary && report.executive_summary.trim().length > 0) {
|
|
118
|
+
lines.push("## Executive Summary", "", report.executive_summary.trim(), "");
|
|
119
|
+
}
|
|
120
|
+
lines.push("## Summary", "", `- Findings: ${report.summary.finding_count}`, `- Work blocks: ${report.summary.work_block_count}`, `- Severity breakdown: ${formatSeverityList(report.summary.severity_breakdown)}`, `- Fully audited files: ${report.summary.audited_file_count}`, `- Excluded non-auditable files: ${report.summary.excluded_file_count}`, "");
|
|
121
|
+
if (report.top_risks && report.top_risks.length > 0) {
|
|
122
|
+
lines.push("## Top Risks", "");
|
|
123
|
+
for (const risk of report.top_risks) {
|
|
124
|
+
lines.push(`- ${risk}`);
|
|
125
|
+
}
|
|
126
|
+
lines.push("");
|
|
127
|
+
}
|
|
128
|
+
if (report.themes && report.themes.length > 0) {
|
|
129
|
+
lines.push("## Themes", "");
|
|
130
|
+
for (const theme of report.themes) {
|
|
131
|
+
lines.push(`### ${theme.theme_id} — ${theme.title}`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(`- Root cause: ${theme.root_cause}`);
|
|
134
|
+
lines.push(`- Findings: ${theme.finding_ids.length > 0 ? theme.finding_ids.join(", ") : "none"}`);
|
|
135
|
+
lines.push(`- Suggested fix pattern: ${theme.suggested_fix_pattern}`);
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
lines.push("## Work Blocks", "");
|
|
140
|
+
if (report.work_blocks.length === 0) {
|
|
74
141
|
lines.push("No remediation work blocks were generated.", "");
|
|
75
142
|
}
|
|
76
143
|
else {
|
|
77
|
-
for (const block of
|
|
144
|
+
for (const block of report.work_blocks) {
|
|
78
145
|
lines.push(`### ${block.id}`);
|
|
79
146
|
lines.push("");
|
|
80
147
|
lines.push(`- Max severity: ${block.max_severity}`);
|
|
@@ -87,16 +154,19 @@ export function renderAuditReportMarkdown(model) {
|
|
|
87
154
|
}
|
|
88
155
|
}
|
|
89
156
|
lines.push("## Findings", "");
|
|
90
|
-
if (
|
|
157
|
+
if (report.findings.length === 0) {
|
|
91
158
|
lines.push("No findings were recorded.", "");
|
|
92
159
|
}
|
|
93
160
|
else {
|
|
94
|
-
for (const finding of
|
|
161
|
+
for (const finding of report.findings) {
|
|
95
162
|
lines.push(`### ${finding.id} — ${finding.title}`);
|
|
96
163
|
lines.push("");
|
|
97
164
|
lines.push(`- Severity: ${finding.severity}`);
|
|
98
165
|
lines.push(`- Confidence: ${finding.confidence}`);
|
|
99
166
|
lines.push(`- Lens: ${finding.lens}`);
|
|
167
|
+
if (finding.theme_id) {
|
|
168
|
+
lines.push(`- Theme: ${finding.theme_id}`);
|
|
169
|
+
}
|
|
100
170
|
lines.push(`- Files: ${finding.affected_files.map((file) => file.path).join(", ")}`);
|
|
101
171
|
lines.push(`- Summary: ${finding.summary}`);
|
|
102
172
|
if (finding.evidence && finding.evidence.length > 0) {
|
|
@@ -109,7 +179,16 @@ export function renderAuditReportMarkdown(model) {
|
|
|
109
179
|
}
|
|
110
180
|
}
|
|
111
181
|
lines.push("## Scope and Coverage", "");
|
|
112
|
-
|
|
182
|
+
const scope = options.scope;
|
|
183
|
+
if (scope && scope.mode === "delta") {
|
|
184
|
+
lines.push(`**Delta audit since \`${scope.since}\`.** This run audited ${scope.seed_files.length} changed file(s) and ${scope.expanded_files.length} graph neighbour(s); all other auditable files were left out of scope (inherited from a prior audit where complete, otherwise excluded from this run). **A full audit is advised before release.**`);
|
|
185
|
+
if (scope.dropped_note) {
|
|
186
|
+
lines.push("", scope.dropped_note);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
lines.push("This report is deterministic output from the completed audit. Non-auditable files were excluded from scope before task generation.");
|
|
191
|
+
}
|
|
113
192
|
lines.push("");
|
|
114
193
|
return lines.join("\n");
|
|
115
194
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuditFindingsReport } from "@audit-tools/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt for the optional synthesis-narrative pass. The host groups the
|
|
4
|
+
* already-finalized deterministic findings into root-cause themes and writes a
|
|
5
|
+
* `SynthesisNarrative` JSON document — it does not re-audit or invent findings.
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderSynthesisNarrativePrompt(report: AuditFindingsReport): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const MAX_RENDERED_FINDINGS = 120;
|
|
2
|
+
function summarizeFinding(finding) {
|
|
3
|
+
const files = finding.affected_files
|
|
4
|
+
.map((file) => file.path)
|
|
5
|
+
.slice(0, 4)
|
|
6
|
+
.join(", ");
|
|
7
|
+
return `- ${finding.id} [${finding.severity}/${finding.lens}] ${finding.title} — ${finding.summary}${files ? ` (files: ${files})` : ""}`;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Prompt for the optional synthesis-narrative pass. The host groups the
|
|
11
|
+
* already-finalized deterministic findings into root-cause themes and writes a
|
|
12
|
+
* `SynthesisNarrative` JSON document — it does not re-audit or invent findings.
|
|
13
|
+
*/
|
|
14
|
+
export function renderSynthesisNarrativePrompt(report) {
|
|
15
|
+
const findings = report.findings;
|
|
16
|
+
const rendered = findings.slice(0, MAX_RENDERED_FINDINGS).map(summarizeFinding);
|
|
17
|
+
const overflowNote = findings.length > MAX_RENDERED_FINDINGS
|
|
18
|
+
? [` ... and ${findings.length - MAX_RENDERED_FINDINGS} more findings (see audit-findings.json).`]
|
|
19
|
+
: [];
|
|
20
|
+
return [
|
|
21
|
+
"# Synthesis narrative",
|
|
22
|
+
"",
|
|
23
|
+
"The deterministic audit is complete. Your job is to add an interpretive narrative on top of the finalized findings — group them into a small number of root-cause themes, write a short executive summary, and list the top risks.",
|
|
24
|
+
"",
|
|
25
|
+
"Do not re-audit the code, change severities, or invent new findings. Use only the findings below; reference them by their exact `id`.",
|
|
26
|
+
"",
|
|
27
|
+
"## Summary",
|
|
28
|
+
"",
|
|
29
|
+
`- Findings: ${report.summary.finding_count}`,
|
|
30
|
+
`- Work blocks: ${report.summary.work_block_count}`,
|
|
31
|
+
"",
|
|
32
|
+
"## Findings",
|
|
33
|
+
"",
|
|
34
|
+
...(rendered.length > 0 ? rendered : ["- (no findings were recorded)"]),
|
|
35
|
+
...overflowNote,
|
|
36
|
+
"",
|
|
37
|
+
"## Output format",
|
|
38
|
+
"",
|
|
39
|
+
"Write a single JSON object conforming to:",
|
|
40
|
+
"",
|
|
41
|
+
"```json",
|
|
42
|
+
"{",
|
|
43
|
+
' "themes": [',
|
|
44
|
+
" {",
|
|
45
|
+
' "theme_id": "T-001",',
|
|
46
|
+
' "title": "short root-cause title",',
|
|
47
|
+
' "root_cause": "what underlying cause ties these findings together",',
|
|
48
|
+
' "finding_ids": ["<finding id>", "..."],',
|
|
49
|
+
' "suggested_fix_pattern": "the shared remediation approach for this theme"',
|
|
50
|
+
" }",
|
|
51
|
+
" ],",
|
|
52
|
+
' "executive_summary": "2-4 sentence overview of the audit outcome",',
|
|
53
|
+
' "top_risks": ["highest-impact risk", "..."]',
|
|
54
|
+
"}",
|
|
55
|
+
"```",
|
|
56
|
+
"",
|
|
57
|
+
"Prefer a handful of substantive themes over many thin ones. Every `finding_ids` entry must be an id listed above; unknown ids are dropped. A finding may belong to at most one theme.",
|
|
58
|
+
"",
|
|
59
|
+
].join("\n");
|
|
60
|
+
}
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { Finding, UnitManifest } from "../types.js";
|
|
2
|
-
import type { CriticalFlowManifest, GraphBundle } from "@audit-tools/shared";
|
|
3
|
-
export
|
|
4
|
-
id: string;
|
|
5
|
-
finding_ids: string[];
|
|
6
|
-
unit_ids: string[];
|
|
7
|
-
owned_files: string[];
|
|
8
|
-
max_severity: Finding["severity"];
|
|
9
|
-
rationale: string;
|
|
10
|
-
depends_on: string[];
|
|
11
|
-
}
|
|
2
|
+
import type { CriticalFlowManifest, GraphBundle, WorkBlock } from "@audit-tools/shared";
|
|
3
|
+
export type { WorkBlock } from "@audit-tools/shared";
|
|
12
4
|
export declare function buildWorkBlocks(params: {
|
|
13
5
|
findings: Finding[];
|
|
14
6
|
unitManifest?: UnitManifest;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ArtifactBundle } from "../io/artifacts.js";
|
|
2
2
|
import type { AuditState, AuditTopLevelStatus } from "../types/auditState.js";
|
|
3
3
|
export interface AuditCodeHandoffInput {
|
|
4
4
|
flag: "--results" | "--batch-results" | "--updates" | "--external-analyzer-results";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeJsonFile } from "@audit-tools/shared";
|
|
4
|
+
import { AUDIT_REPORT_FILENAME } from "../io/artifacts.js";
|
|
4
5
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "../providers/constants.js";
|
|
5
6
|
export const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
6
7
|
const INCOMING_DIRNAME = "incoming";
|
|
@@ -11,8 +12,6 @@ const RUN_LEDGER_FILENAME = "run-ledger.json";
|
|
|
11
12
|
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
12
13
|
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
13
14
|
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
14
|
-
const CURRENT_SINGLE_TASK_FILENAME = "current-single-task.json";
|
|
15
|
-
const CURRENT_SINGLE_TASK_PROMPT_FILENAME = "current-single-task-prompt.md";
|
|
16
15
|
const AUDIT_TASKS_FILENAME = "audit_tasks.json";
|
|
17
16
|
const RUNTIME_VALIDATION_TASKS_FILENAME = "runtime_validation_tasks.json";
|
|
18
17
|
const BLOCKED_STATUS = "blocked";
|
|
@@ -123,6 +122,26 @@ function buildInteractiveProviderHint(status, providerName, sessionConfigPath, i
|
|
|
123
122
|
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
124
123
|
return `Provider: ${providerLabel}. This is a deterministic semantic-review handoff, not a failed audit. Use host subagents when the active toolset provides them; otherwise use the single-task fallback and stop after the worker command. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}; that is only needed for backend-launched review.`;
|
|
125
124
|
}
|
|
125
|
+
// Single source for which artifact paths render in the markdown handoff and how
|
|
126
|
+
// absent ones read. renderMarkdown and file_map both source paths from the
|
|
127
|
+
// artifact path model (AuditCodeHandoffArtifactPaths), so adding or renaming a
|
|
128
|
+
// handoff artifact is one edit here instead of coordinated edits across sites.
|
|
129
|
+
const ARTIFACT_PATH_RENDER_FIELDS = [
|
|
130
|
+
{ label: "operator handoff json", key: "operator_handoff_json" },
|
|
131
|
+
{ label: "operator handoff markdown", key: "operator_handoff_markdown" },
|
|
132
|
+
{ label: "incoming dir", key: "incoming_dir" },
|
|
133
|
+
{ label: "session config", key: "session_config" },
|
|
134
|
+
{ label: "run ledger", key: "run_ledger" },
|
|
135
|
+
{ label: "current task", key: "current_task", fallback: "not available" },
|
|
136
|
+
{ label: "current prompt", key: "current_prompt", fallback: "not available" },
|
|
137
|
+
{ label: "current tasks", key: "current_tasks", fallback: "not available" },
|
|
138
|
+
{ label: "audit tasks", key: "audit_tasks", fallback: "not available yet" },
|
|
139
|
+
{
|
|
140
|
+
label: "runtime validation tasks",
|
|
141
|
+
key: "runtime_validation_tasks",
|
|
142
|
+
fallback: "not available yet",
|
|
143
|
+
},
|
|
144
|
+
];
|
|
126
145
|
function renderMarkdown(handoff) {
|
|
127
146
|
const lines = [
|
|
128
147
|
"# audit-code operator handoff",
|
|
@@ -145,16 +164,10 @@ function renderMarkdown(handoff) {
|
|
|
145
164
|
}
|
|
146
165
|
}
|
|
147
166
|
lines.push("", "Useful artifact paths:");
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
lines.push(`- run ledger: ${handoff.artifact_paths.run_ledger}`);
|
|
153
|
-
lines.push(`- current task: ${handoff.artifact_paths.current_task ?? "not available"}`);
|
|
154
|
-
lines.push(`- current prompt: ${handoff.artifact_paths.current_prompt ?? "not available"}`);
|
|
155
|
-
lines.push(`- current tasks: ${handoff.artifact_paths.current_tasks ?? "not available"}`);
|
|
156
|
-
lines.push(`- audit tasks: ${handoff.artifact_paths.audit_tasks ?? "not available yet"}`);
|
|
157
|
-
lines.push(`- runtime validation tasks: ${handoff.artifact_paths.runtime_validation_tasks ?? "not available yet"}`);
|
|
167
|
+
for (const field of ARTIFACT_PATH_RENDER_FIELDS) {
|
|
168
|
+
const value = handoff.artifact_paths[field.key];
|
|
169
|
+
lines.push(`- ${field.label}: ${value ?? field.fallback ?? "not available"}`);
|
|
170
|
+
}
|
|
158
171
|
if (handoff.suggested_inputs.length > 0) {
|
|
159
172
|
lines.push("", "Suggested evidence inputs:");
|
|
160
173
|
for (const item of handoff.suggested_inputs) {
|
|
@@ -238,11 +251,8 @@ export function buildAuditCodeHandoff(params) {
|
|
|
238
251
|
handoff.file_map = {
|
|
239
252
|
current_task: artifactPaths.current_task,
|
|
240
253
|
current_prompt: artifactPaths.current_prompt,
|
|
241
|
-
single_task: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME),
|
|
242
|
-
single_task_prompt: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME),
|
|
243
|
-
dispatch_plan: join(params.artifactsDir, "runs", params.activeReviewRun.run_id, "dispatch-plan.json"),
|
|
244
254
|
audit_results: params.activeReviewRun.audit_results_path,
|
|
245
|
-
final_report: join(params.root,
|
|
255
|
+
final_report: join(params.root, AUDIT_REPORT_FILENAME),
|
|
246
256
|
};
|
|
247
257
|
}
|
|
248
258
|
return handoff;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { type SessionConfig } from "@audit-tools/shared";
|
|
1
|
+
import { type AnalyzerSetting, type SessionConfig } from "@audit-tools/shared";
|
|
2
2
|
export declare function getSessionConfigPath(artifactsDir: string): string;
|
|
3
3
|
export declare function readSessionConfigFile(artifactsDir: string): Promise<unknown | undefined>;
|
|
4
4
|
export declare function loadSessionConfig(artifactsDir: string): Promise<SessionConfig>;
|
|
5
|
+
/**
|
|
6
|
+
* Merge per-analyzer resolution decisions into `session-config.json`,
|
|
7
|
+
* preserving any unknown fields, validating, and persisting the result. Used by
|
|
8
|
+
* the conversation-first `analyzer_install` step to durably record the host's
|
|
9
|
+
* `{ephemeral|permanent|skip}` choices under `analyzers.<id>`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function persistAnalyzerSettings(artifactsDir: string, settings: Record<string, AnalyzerSetting>): Promise<SessionConfig>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { readOptionalJsonFile, writeJsonFile, formatValidationIssues, } from "@audit-tools/shared";
|
|
2
|
+
import { isRecord, readOptionalJsonFile, writeJsonFile, formatValidationIssues, } from "@audit-tools/shared";
|
|
3
3
|
import { validateSessionConfig } from "../validation/sessionConfig.js";
|
|
4
4
|
const SESSION_CONFIG_FILENAME = "session-config.json";
|
|
5
5
|
const DEFAULT_SESSION_CONFIG = { provider: "local-subprocess" };
|
|
@@ -25,3 +25,24 @@ export async function loadSessionConfig(artifactsDir) {
|
|
|
25
25
|
}
|
|
26
26
|
return rawConfig;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Merge per-analyzer resolution decisions into `session-config.json`,
|
|
30
|
+
* preserving any unknown fields, validating, and persisting the result. Used by
|
|
31
|
+
* the conversation-first `analyzer_install` step to durably record the host's
|
|
32
|
+
* `{ephemeral|permanent|skip}` choices under `analyzers.<id>`.
|
|
33
|
+
*/
|
|
34
|
+
export async function persistAnalyzerSettings(artifactsDir, settings) {
|
|
35
|
+
const configPath = getSessionConfigPath(artifactsDir);
|
|
36
|
+
const raw = (await readOptionalJsonFile(configPath)) ?? {
|
|
37
|
+
...DEFAULT_SESSION_CONFIG,
|
|
38
|
+
};
|
|
39
|
+
const base = isRecord(raw) ? raw : { ...DEFAULT_SESSION_CONFIG };
|
|
40
|
+
const current = isRecord(base.analyzers) ? base.analyzers : {};
|
|
41
|
+
const merged = { ...base, analyzers: { ...current, ...settings } };
|
|
42
|
+
const issues = validateSessionConfig(merged);
|
|
43
|
+
if (issues.length > 0) {
|
|
44
|
+
throw new Error(formatConfigValidationIssues(configPath, issues));
|
|
45
|
+
}
|
|
46
|
+
await writeJsonFile(configPath, merged);
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnalyzerSetting } from "@audit-tools/shared";
|
|
2
|
+
import type { AnalyzerResolution } from "../extractors/analyzers/types.js";
|
|
3
|
+
export type AnalyzerCapabilityStatus = "applied" | "omitted";
|
|
4
|
+
export interface AnalyzerCapabilityEntry {
|
|
5
|
+
id: string;
|
|
6
|
+
resolution: AnalyzerResolution;
|
|
7
|
+
setting: AnalyzerSetting;
|
|
8
|
+
edges_added: number;
|
|
9
|
+
routes_added: number;
|
|
10
|
+
note?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AnalyzerCapabilityRecord {
|
|
13
|
+
/** `applied` when ≥1 analyzer contributed edges/routes; `omitted` otherwise. */
|
|
14
|
+
status: AnalyzerCapabilityStatus;
|
|
15
|
+
analyzers: AnalyzerCapabilityEntry[];
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 3 — `--since` delta scope.
|
|
3
|
+
*
|
|
4
|
+
* `scope.json` records how the audit was scoped for a given run: a full audit
|
|
5
|
+
* (the default), or a delta audit measured against a git ref. In delta mode the
|
|
6
|
+
* orchestrator audits only the changed files (`seed_files`) and their nearest
|
|
7
|
+
* graph neighbours (`expanded_files`); every other auditable file inherits its
|
|
8
|
+
* prior completion or is excluded from this run. The artifact is a deterministic
|
|
9
|
+
* function of the inputs (the ref, the changed files, the graph) so the same
|
|
10
|
+
* inputs always yield the same scope, and it is recorded honestly in the report
|
|
11
|
+
* header and the run log. It sits upstream of `coverage_matrix.json` in the
|
|
12
|
+
* staleness DAG.
|
|
13
|
+
*/
|
|
14
|
+
export interface AuditScopeBudget {
|
|
15
|
+
/**
|
|
16
|
+
* Upper bound on the number of in-scope files (seeds + expanded). Seeds are
|
|
17
|
+
* always retained; expansion stops once this cap is reached.
|
|
18
|
+
*/
|
|
19
|
+
max_files: number;
|
|
20
|
+
}
|
|
21
|
+
export interface AuditScopeManifest {
|
|
22
|
+
/** `full` audits every auditable file; `delta` scopes to a changed neighbourhood. */
|
|
23
|
+
mode: "full" | "delta";
|
|
24
|
+
/** Git ref/SHA the delta was measured against; `null` in full mode. */
|
|
25
|
+
since: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Changed auditable files (relative to `since`) that exist in the repo
|
|
28
|
+
* manifest. Empty in full mode. Sorted for determinism.
|
|
29
|
+
*/
|
|
30
|
+
seed_files: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Auditable files pulled in by deterministic priority-frontier expansion over
|
|
33
|
+
* the dependency graph (graph neighbours of the seeds). Sorted for determinism.
|
|
34
|
+
*/
|
|
35
|
+
expanded_files: string[];
|
|
36
|
+
/** The budget applied during expansion. */
|
|
37
|
+
budget: AuditScopeBudget;
|
|
38
|
+
/**
|
|
39
|
+
* Human-readable note when the scope was truncated by the budget, or when a
|
|
40
|
+
* requested `--since` could not be honoured and the run fell back to full.
|
|
41
|
+
*/
|
|
42
|
+
dropped_note?: string;
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 3 — `--since` delta scope.
|
|
3
|
+
*
|
|
4
|
+
* `scope.json` records how the audit was scoped for a given run: a full audit
|
|
5
|
+
* (the default), or a delta audit measured against a git ref. In delta mode the
|
|
6
|
+
* orchestrator audits only the changed files (`seed_files`) and their nearest
|
|
7
|
+
* graph neighbours (`expanded_files`); every other auditable file inherits its
|
|
8
|
+
* prior completion or is excluded from this run. The artifact is a deterministic
|
|
9
|
+
* function of the inputs (the ref, the changed files, the graph) so the same
|
|
10
|
+
* inputs always yield the same scope, and it is recorded honestly in the report
|
|
11
|
+
* header and the run log. It sits upstream of `coverage_matrix.json` in the
|
|
12
|
+
* staleness DAG.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Marker artifact recording whether the optional Phase 6 synthesis-narrative
|
|
2
|
+
// pass was applied or deliberately omitted. Its presence (and freshness against
|
|
3
|
+
// `audit-findings.json`) satisfies the `synthesis_narrative_current` obligation;
|
|
4
|
+
// the narrative content itself lives in `audit-findings.json`.
|
|
5
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Finding as SharedFinding } from "@audit-tools/shared";
|
|
1
2
|
export type Lens = "correctness" | "architecture" | "maintainability" | "security" | "reliability" | "performance" | "data_integrity" | "tests" | "operability" | "config_deployment" | "observability";
|
|
2
3
|
export interface FileRecord {
|
|
3
4
|
path: string;
|
|
@@ -67,26 +68,8 @@ export interface AuditTask {
|
|
|
67
68
|
completed_at?: string;
|
|
68
69
|
completion_reason?: string;
|
|
69
70
|
}
|
|
70
|
-
export interface Finding {
|
|
71
|
-
id: string;
|
|
72
|
-
title: string;
|
|
73
|
-
category: string;
|
|
74
|
-
severity: "critical" | "high" | "medium" | "low" | "info";
|
|
75
|
-
confidence: "high" | "medium" | "low";
|
|
71
|
+
export interface Finding extends Omit<SharedFinding, "lens"> {
|
|
76
72
|
lens: Lens;
|
|
77
|
-
summary: string;
|
|
78
|
-
affected_files: Array<{
|
|
79
|
-
path: string;
|
|
80
|
-
line_start?: number;
|
|
81
|
-
line_end?: number;
|
|
82
|
-
symbol?: string;
|
|
83
|
-
}>;
|
|
84
|
-
impact?: string;
|
|
85
|
-
likelihood?: string;
|
|
86
|
-
evidence?: string[];
|
|
87
|
-
reproduction?: string[];
|
|
88
|
-
systemic?: boolean;
|
|
89
|
-
related_findings?: string[];
|
|
90
73
|
}
|
|
91
74
|
export interface AuditVerification {
|
|
92
75
|
verified: boolean;
|
|
@@ -19,6 +19,15 @@ export function validateArtifactBundle(bundle) {
|
|
|
19
19
|
if (bundle.coverage_matrix) {
|
|
20
20
|
issues.push(...requireKeys(bundle.coverage_matrix, "coverage_matrix", ["files"]));
|
|
21
21
|
}
|
|
22
|
+
if (bundle.scope) {
|
|
23
|
+
issues.push(...requireKeys(bundle.scope, "scope", [
|
|
24
|
+
"mode",
|
|
25
|
+
"since",
|
|
26
|
+
"seed_files",
|
|
27
|
+
"expanded_files",
|
|
28
|
+
"budget",
|
|
29
|
+
]));
|
|
30
|
+
}
|
|
22
31
|
if (bundle.graph_bundle) {
|
|
23
32
|
issues.push(...requireKeys(bundle.graph_bundle, "graph_bundle", ["graphs"]));
|
|
24
33
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { accessSync, constants } from "node:fs";
|
|
3
|
-
import { PROVIDER_NAMES, SESSION_UI_MODES, isRecord, pushValidationIssue, } from "@audit-tools/shared";
|
|
3
|
+
import { ANALYZER_SETTINGS, PROVIDER_NAMES, SESSION_UI_MODES, isRecord, pushValidationIssue, } from "@audit-tools/shared";
|
|
4
4
|
const VALID_PROVIDERS = new Set(PROVIDER_NAMES);
|
|
5
5
|
const VALID_UI_MODES = new Set(SESSION_UI_MODES);
|
|
6
|
+
const VALID_ANALYZER_SETTINGS = new Set(ANALYZER_SETTINGS);
|
|
6
7
|
function pushIssue(issues, path, message) {
|
|
7
8
|
pushValidationIssue(issues, path, message);
|
|
8
9
|
}
|
|
@@ -158,6 +159,28 @@ export function validateSessionConfig(value) {
|
|
|
158
159
|
validateTemplateProviderSection(value.vscode_task, "vscode_task", issues, provider === "vscode-task");
|
|
159
160
|
validateAgentProviderSection(value.claude_code, "claude_code", issues);
|
|
160
161
|
validateAgentProviderSection(value.opencode, "opencode", issues);
|
|
162
|
+
if (value.synthesis !== undefined) {
|
|
163
|
+
if (!isRecord(value.synthesis)) {
|
|
164
|
+
pushIssue(issues, "synthesis", "synthesis must be a JSON object.");
|
|
165
|
+
}
|
|
166
|
+
else if (value.synthesis.narrative !== undefined &&
|
|
167
|
+
typeof value.synthesis.narrative !== "boolean") {
|
|
168
|
+
pushIssue(issues, "synthesis.narrative", "synthesis.narrative must be a boolean when provided.");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (value.analyzers !== undefined) {
|
|
172
|
+
if (!isRecord(value.analyzers)) {
|
|
173
|
+
pushIssue(issues, "analyzers", "analyzers must be a JSON object mapping analyzer id to a setting.");
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
for (const [id, setting] of Object.entries(value.analyzers)) {
|
|
177
|
+
if (typeof setting !== "string" ||
|
|
178
|
+
!VALID_ANALYZER_SETTINGS.has(setting)) {
|
|
179
|
+
pushIssue(issues, `analyzers.${id}`, `analyzers.${id} must be one of: ${Array.from(VALID_ANALYZER_SETTINGS).join(", ")}.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
161
184
|
return issues;
|
|
162
185
|
}
|
|
163
186
|
export function validateConfiguredProviderEnvironment(sessionConfig, options = {}) {
|
package/docs/contracts.md
CHANGED
|
@@ -90,9 +90,16 @@ backend writes the current step contract to:
|
|
|
90
90
|
includes `step_kind`, `prompt_path`, `status`, `run_id`, `allowed_commands`,
|
|
91
91
|
`stop_condition`, `repo_root`, `artifacts_dir`, and relevant `artifact_paths`.
|
|
92
92
|
|
|
93
|
-
When semantic review is
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
When semantic review is reached, the backend resolves host dispatch capability
|
|
94
|
+
from `--host-can-dispatch-subagents true|false` (optional) → session config
|
|
95
|
+
(`host_can_dispatch_subagents`) → `AUDIT_CODE_HOST_CAN_DISPATCH` → default
|
|
96
|
+
`true`, then renders exactly one review path: packet dispatch (`dispatch_review`)
|
|
97
|
+
or the single-task fallback (`single_task_fallback`). No capability handshake is
|
|
98
|
+
required; pass the flag only to override the resolved default.
|
|
99
|
+
|
|
100
|
+
`run-to-completion` renders the same step when it reaches the semantic-review
|
|
101
|
+
boundary, so a host on the batch entrypoint can act on `steps/current-step.json`
|
|
102
|
+
directly instead of issuing a second `next-step`.
|
|
96
103
|
|
|
97
104
|
## Dispatch packets
|
|
98
105
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auditor-lambda",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Portable hybrid code-auditing framework for arbitrary repositories.",
|
|
6
6
|
"type": "module",
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
"@types/node": "^24.3.0",
|
|
72
72
|
"ajv": "^8.17.1",
|
|
73
73
|
"linguist-languages": "^9.3.2",
|
|
74
|
-
"
|
|
74
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
75
|
+
"typescript": "^5.9.2",
|
|
76
|
+
"web-tree-sitter": "^0.25.10"
|
|
75
77
|
}
|
|
76
78
|
}
|