auditor-lambda 0.3.41 → 0.5.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.
Files changed (74) hide show
  1. package/dist/cli/dispatch.js +5 -1
  2. package/dist/cli/prompts.d.ts +19 -0
  3. package/dist/cli/prompts.js +95 -0
  4. package/dist/cli/steps.d.ts +1 -1
  5. package/dist/cli.js +287 -7
  6. package/dist/extractors/analyzers/css.d.ts +2 -0
  7. package/dist/extractors/analyzers/css.js +101 -0
  8. package/dist/extractors/analyzers/html.d.ts +2 -0
  9. package/dist/extractors/analyzers/html.js +92 -0
  10. package/dist/extractors/analyzers/merge.d.ts +14 -0
  11. package/dist/extractors/analyzers/merge.js +85 -0
  12. package/dist/extractors/analyzers/python.d.ts +2 -0
  13. package/dist/extractors/analyzers/python.js +104 -0
  14. package/dist/extractors/analyzers/registry.d.ts +33 -0
  15. package/dist/extractors/analyzers/registry.js +100 -0
  16. package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
  17. package/dist/extractors/analyzers/resourceUrl.js +25 -0
  18. package/dist/extractors/analyzers/sql.d.ts +2 -0
  19. package/dist/extractors/analyzers/sql.js +19 -0
  20. package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
  21. package/dist/extractors/analyzers/treeSitter.js +111 -0
  22. package/dist/extractors/analyzers/types.d.ts +53 -0
  23. package/dist/extractors/analyzers/types.js +1 -0
  24. package/dist/extractors/analyzers/typescript.d.ts +2 -0
  25. package/dist/extractors/analyzers/typescript.js +257 -0
  26. package/dist/extractors/disposition.js +8 -1
  27. package/dist/extractors/graph.d.ts +1 -0
  28. package/dist/extractors/graph.js +167 -1
  29. package/dist/extractors/graphPythonImports.d.ts +15 -0
  30. package/dist/extractors/graphPythonImports.js +36 -0
  31. package/dist/extractors/pathPatterns.d.ts +6 -0
  32. package/dist/extractors/pathPatterns.js +8 -0
  33. package/dist/io/artifacts.d.ts +12 -1
  34. package/dist/io/artifacts.js +12 -0
  35. package/dist/orchestrator/advance.d.ts +20 -0
  36. package/dist/orchestrator/advance.js +61 -2
  37. package/dist/orchestrator/dependencyMap.js +27 -0
  38. package/dist/orchestrator/edgeReasoning.d.ts +39 -0
  39. package/dist/orchestrator/edgeReasoning.js +125 -0
  40. package/dist/orchestrator/executors.js +11 -1
  41. package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
  42. package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
  43. package/dist/orchestrator/internalExecutors.d.ts +10 -1
  44. package/dist/orchestrator/internalExecutors.js +89 -11
  45. package/dist/orchestrator/localCommands.js +6 -25
  46. package/dist/orchestrator/nextStep.js +2 -0
  47. package/dist/orchestrator/reviewPackets.d.ts +37 -4
  48. package/dist/orchestrator/reviewPackets.js +93 -46
  49. package/dist/orchestrator/runtimeValidation.js +4 -31
  50. package/dist/orchestrator/scope.d.ts +62 -0
  51. package/dist/orchestrator/scope.js +227 -0
  52. package/dist/orchestrator/state.js +2 -0
  53. package/dist/reporting/synthesis.d.ts +37 -2
  54. package/dist/reporting/synthesis.js +95 -16
  55. package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
  56. package/dist/reporting/synthesisNarrativePrompt.js +60 -0
  57. package/dist/reporting/workBlocks.d.ts +2 -10
  58. package/dist/supervisor/sessionConfig.d.ts +8 -1
  59. package/dist/supervisor/sessionConfig.js +22 -1
  60. package/dist/types/analyzerCapability.d.ts +16 -0
  61. package/dist/types/analyzerCapability.js +1 -0
  62. package/dist/types/auditScope.d.ts +43 -0
  63. package/dist/types/auditScope.js +14 -0
  64. package/dist/types/synthesisNarrative.d.ts +7 -0
  65. package/dist/types/synthesisNarrative.js +5 -0
  66. package/dist/types.d.ts +2 -19
  67. package/dist/validation/artifacts.js +9 -0
  68. package/dist/validation/sessionConfig.js +24 -1
  69. package/package.json +4 -2
  70. package/schemas/analyzer_capability.schema.json +47 -0
  71. package/schemas/audit_findings.schema.json +141 -0
  72. package/schemas/finding.schema.json +2 -1
  73. package/schemas/graph_bundle.schema.json +5 -0
  74. 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
- export declare function renderAuditReportMarkdown(model: AuditReportModel): string;
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
- export function renderAuditReportMarkdown(model) {
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 (model.work_blocks.length === 0) {
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 model.work_blocks) {
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 (model.findings.length === 0) {
157
+ if (report.findings.length === 0) {
91
158
  lines.push("No findings were recorded.", "");
92
159
  }
93
160
  else {
94
- for (const finding of model.findings) {
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
- lines.push("This report is deterministic output from the completed audit. Non-auditable files were excluded from scope before task generation.");
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 interface WorkBlock {
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,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,7 @@
1
+ export type SynthesisNarrativeStatus = "applied" | "omitted";
2
+ export interface SynthesisNarrativeRecord {
3
+ status: SynthesisNarrativeStatus;
4
+ theme_count: number;
5
+ executive_summary_present: boolean;
6
+ top_risk_count: number;
7
+ }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.3.41",
3
+ "version": "0.5.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
- "typescript": "^5.9.2"
74
+ "tree-sitter-wasms": "^0.1.13",
75
+ "typescript": "^5.9.2",
76
+ "web-tree-sitter": "^0.25.10"
75
77
  }
76
78
  }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "analyzer_capability.schema.json",
4
+ "title": "Analyzer Capability Record",
5
+ "description": "Marker artifact (analyzer_capability.json) recording the outcome of the optional Phase 5 graph-enrichment pass. Its presence and freshness against graph_bundle.json satisfy the graph_enrichment_current obligation. The merged edges live in graph_bundle.json.",
6
+ "type": "object",
7
+ "required": ["status", "analyzers"],
8
+ "properties": {
9
+ "status": {
10
+ "type": "string",
11
+ "enum": ["applied", "omitted"],
12
+ "description": "'applied' when at least one analyzer contributed edges/routes; 'omitted' otherwise (regex floor unchanged)."
13
+ },
14
+ "analyzers": {
15
+ "type": "array",
16
+ "items": {
17
+ "type": "object",
18
+ "required": ["id", "resolution", "setting", "edges_added", "routes_added"],
19
+ "properties": {
20
+ "id": { "type": "string", "minLength": 1 },
21
+ "resolution": {
22
+ "type": "string",
23
+ "enum": [
24
+ "repo",
25
+ "cache",
26
+ "installed",
27
+ "absent",
28
+ "skip",
29
+ "not_applicable"
30
+ ],
31
+ "description": "How the analyzer's dependency resolved, or why it did not run."
32
+ },
33
+ "setting": {
34
+ "type": "string",
35
+ "enum": ["repo", "ephemeral", "permanent", "skip", "auto"],
36
+ "description": "Resolved analyzers.<id> session-config setting."
37
+ },
38
+ "edges_added": { "type": "integer", "minimum": 0 },
39
+ "routes_added": { "type": "integer", "minimum": 0 },
40
+ "note": { "type": "string" }
41
+ },
42
+ "additionalProperties": false
43
+ }
44
+ }
45
+ },
46
+ "additionalProperties": false
47
+ }