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.
- 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 +287 -7
- 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 +12 -1
- package/dist/io/artifacts.js +12 -0
- 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/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/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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { installToCache, resolveAnalyzerDep } from "@audit-tools/shared";
|
|
2
|
+
import { buildDispositionMap } from "../extractors/disposition.js";
|
|
3
|
+
import { buildPathLookup } from "../extractors/graph.js";
|
|
4
|
+
import { mergeAnalyzerEdges } from "../extractors/analyzers/merge.js";
|
|
5
|
+
import { ANALYZER_REGISTRY } from "../extractors/analyzers/registry.js";
|
|
6
|
+
import { applyEdgeReasoning, } from "./edgeReasoning.js";
|
|
7
|
+
const BUCKET_BY_KIND = {
|
|
8
|
+
"ts-import": "imports",
|
|
9
|
+
"ts-reexport": "imports",
|
|
10
|
+
"ts-call": "calls",
|
|
11
|
+
"ts-extends": "references",
|
|
12
|
+
"ts-implements": "references",
|
|
13
|
+
// Python (tree-sitter) imports merge into the imports bucket alongside the
|
|
14
|
+
// regex floor's python-* edges.
|
|
15
|
+
"py-import": "imports",
|
|
16
|
+
"py-from-import": "imports",
|
|
17
|
+
// HTML/CSS (tree-sitter) resource references live with the floor's
|
|
18
|
+
// html-resource-link / reference edges.
|
|
19
|
+
"html-resource": "references",
|
|
20
|
+
"css-import": "references",
|
|
21
|
+
"css-url": "references",
|
|
22
|
+
};
|
|
23
|
+
function bucketForKind(kind) {
|
|
24
|
+
const bucket = kind ? BUCKET_BY_KIND[kind] : undefined;
|
|
25
|
+
return bucket ?? "references";
|
|
26
|
+
}
|
|
27
|
+
function settingFor(analyzers, id) {
|
|
28
|
+
return analyzers?.[id] ?? "auto";
|
|
29
|
+
}
|
|
30
|
+
function routeSignature(route) {
|
|
31
|
+
return `${route.method ?? ""}\0${route.path}\0${route.handler}`;
|
|
32
|
+
}
|
|
33
|
+
function mergeRoutes(floor, analyzer) {
|
|
34
|
+
const deduped = new Map();
|
|
35
|
+
for (const route of [...floor, ...analyzer]) {
|
|
36
|
+
deduped.set(routeSignature(route), route);
|
|
37
|
+
}
|
|
38
|
+
return [...deduped.values()].sort((a, b) => a.path.localeCompare(b.path) ||
|
|
39
|
+
a.handler.localeCompare(b.handler) ||
|
|
40
|
+
(a.method ?? "").localeCompare(b.method ?? ""));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a dependency for actual execution (may install for ephemeral/permanent).
|
|
44
|
+
* `auto`/`repo` with an absent dependency falls back to the regex floor.
|
|
45
|
+
*/
|
|
46
|
+
function resolveForRun(analyzer, root, setting, cacheRoot) {
|
|
47
|
+
if (!analyzer.dependency) {
|
|
48
|
+
return { resolution: "repo" };
|
|
49
|
+
}
|
|
50
|
+
const options = cacheRoot ? { cacheRoot } : {};
|
|
51
|
+
const resolved = resolveAnalyzerDep(analyzer.dependency, root, options);
|
|
52
|
+
if (resolved.via === "repo" || resolved.via === "cache") {
|
|
53
|
+
return { resolution: resolved.via, path: resolved.path };
|
|
54
|
+
}
|
|
55
|
+
if (setting === "ephemeral" || setting === "permanent") {
|
|
56
|
+
const install = installToCache(analyzer.dependency, options);
|
|
57
|
+
if (install.ok && install.path) {
|
|
58
|
+
return { resolution: "installed", path: install.path };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
resolution: "absent",
|
|
62
|
+
note: `Install of '${analyzer.dependency}' failed: ${install.error ?? "unknown error"}.`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
resolution: "absent",
|
|
67
|
+
note: `Dependency '${analyzer.dependency}' not resolvable; kept regex floor.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the optional graph-enrichment obligation. Layers language-analyzer
|
|
72
|
+
* edges onto the deterministic regex floor in `graph_bundle.json`
|
|
73
|
+
* (higher-confidence-kind-wins) and records provenance in
|
|
74
|
+
* `analyzer_capability.json`. With no root, or when every analyzer skips / is
|
|
75
|
+
* absent / not-applicable, the graph bundle is left byte-identical to the floor
|
|
76
|
+
* and only the marker is written.
|
|
77
|
+
*/
|
|
78
|
+
export async function runGraphEnrichmentExecutor(bundle, options = {}) {
|
|
79
|
+
const floor = bundle.graph_bundle;
|
|
80
|
+
if (!floor) {
|
|
81
|
+
throw new Error("Cannot run graph enrichment without graph_bundle");
|
|
82
|
+
}
|
|
83
|
+
const registry = options.registry ?? ANALYZER_REGISTRY;
|
|
84
|
+
const root = options.root;
|
|
85
|
+
const disposition = bundle.file_disposition;
|
|
86
|
+
const dispositionMap = buildDispositionMap(disposition);
|
|
87
|
+
const pathLookup = bundle.repo_manifest
|
|
88
|
+
? buildPathLookup(bundle.repo_manifest, dispositionMap)
|
|
89
|
+
: new Map();
|
|
90
|
+
const includedFiles = [...new Set(pathLookup.values())].sort((a, b) => a.localeCompare(b));
|
|
91
|
+
const entries = [];
|
|
92
|
+
const bucketEdges = { imports: [], calls: [], references: [] };
|
|
93
|
+
const routeEdges = [];
|
|
94
|
+
const analyzersUsed = [];
|
|
95
|
+
for (const analyzer of registry) {
|
|
96
|
+
const setting = settingFor(options.analyzers, analyzer.id);
|
|
97
|
+
const supportedFiles = includedFiles.filter((file) => analyzer.supports(file));
|
|
98
|
+
if (supportedFiles.length === 0) {
|
|
99
|
+
entries.push({ id: analyzer.id, resolution: "not_applicable", setting, edges_added: 0, routes_added: 0 });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (setting === "skip") {
|
|
103
|
+
entries.push({ id: analyzer.id, resolution: "skip", setting, edges_added: 0, routes_added: 0, note: "Analyzer disabled via session config." });
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!root || !bundle.repo_manifest) {
|
|
107
|
+
entries.push({ id: analyzer.id, resolution: "absent", setting, edges_added: 0, routes_added: 0, note: "No repository root available for analysis." });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const run = resolveForRun(analyzer, root, setting, options.cacheRoot);
|
|
111
|
+
if (run.resolution === "absent") {
|
|
112
|
+
entries.push({ id: analyzer.id, resolution: "absent", setting, edges_added: 0, routes_added: 0, note: run.note });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
let edges = [];
|
|
116
|
+
let routes = [];
|
|
117
|
+
try {
|
|
118
|
+
const output = await analyzer.analyze(supportedFiles, {
|
|
119
|
+
root,
|
|
120
|
+
repoManifest: bundle.repo_manifest,
|
|
121
|
+
disposition,
|
|
122
|
+
includedFiles,
|
|
123
|
+
pathLookup,
|
|
124
|
+
dependencyPath: run.path,
|
|
125
|
+
});
|
|
126
|
+
edges = output.edges ?? [];
|
|
127
|
+
routes = output.routes ?? [];
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
entries.push({
|
|
131
|
+
id: analyzer.id,
|
|
132
|
+
resolution: run.resolution,
|
|
133
|
+
setting,
|
|
134
|
+
edges_added: 0,
|
|
135
|
+
routes_added: 0,
|
|
136
|
+
note: `Analyzer failed: ${error instanceof Error ? error.message : String(error)}.`,
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
for (const edge of edges) {
|
|
141
|
+
bucketEdges[bucketForKind(edge.kind)].push(edge);
|
|
142
|
+
}
|
|
143
|
+
routeEdges.push(...routes);
|
|
144
|
+
if (edges.length + routes.length > 0) {
|
|
145
|
+
analyzersUsed.push(analyzer.id);
|
|
146
|
+
}
|
|
147
|
+
entries.push({ id: analyzer.id, resolution: run.resolution, setting, edges_added: edges.length, routes_added: routes.length });
|
|
148
|
+
}
|
|
149
|
+
const applied = analyzersUsed.length > 0;
|
|
150
|
+
const record = {
|
|
151
|
+
status: applied ? "applied" : "omitted",
|
|
152
|
+
analyzers: entries,
|
|
153
|
+
};
|
|
154
|
+
// The graph this obligation produces: the enriched bundle when analyzers
|
|
155
|
+
// contributed, otherwise the regex floor. Phase 4B may then rewrite the
|
|
156
|
+
// reasons of low-confidence edges on whichever graph stands — the floor's
|
|
157
|
+
// heuristic edges exist regardless of analyzers.
|
|
158
|
+
const graphBundle = applied
|
|
159
|
+
? {
|
|
160
|
+
...floor,
|
|
161
|
+
graphs: {
|
|
162
|
+
...floor.graphs,
|
|
163
|
+
imports: mergeAnalyzerEdges(floor.graphs.imports ?? [], bucketEdges.imports),
|
|
164
|
+
calls: mergeAnalyzerEdges(floor.graphs.calls ?? [], bucketEdges.calls),
|
|
165
|
+
references: mergeAnalyzerEdges(floor.graphs.references ?? [], bucketEdges.references),
|
|
166
|
+
...(routeEdges.length > 0
|
|
167
|
+
? { routes: mergeRoutes(floor.graphs.routes ?? [], routeEdges) }
|
|
168
|
+
: {}),
|
|
169
|
+
},
|
|
170
|
+
analyzers_used: [...new Set(analyzersUsed)].sort(),
|
|
171
|
+
}
|
|
172
|
+
: floor;
|
|
173
|
+
let reasoned = { rewritten: 0, candidates: 0 };
|
|
174
|
+
if (options.llmEdgeReasoning === true && options.edgeReasoning) {
|
|
175
|
+
reasoned = applyEdgeReasoning(graphBundle, options.edgeReasoning);
|
|
176
|
+
}
|
|
177
|
+
const graphChanged = applied || reasoned.rewritten > 0;
|
|
178
|
+
const reasonSuffix = reasoned.rewritten > 0
|
|
179
|
+
? ` Edge reasoning rewrote ${reasoned.rewritten} reason(s).`
|
|
180
|
+
: "";
|
|
181
|
+
if (!graphChanged) {
|
|
182
|
+
return {
|
|
183
|
+
updated: { ...bundle, analyzer_capability: record },
|
|
184
|
+
artifacts_written: ["analyzer_capability.json"],
|
|
185
|
+
progress_summary: "Graph enrichment omitted; deterministic regex graph retained.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const totalEdges = entries.reduce((sum, entry) => sum + entry.edges_added, 0);
|
|
189
|
+
return {
|
|
190
|
+
updated: { ...bundle, graph_bundle: graphBundle, analyzer_capability: record },
|
|
191
|
+
artifacts_written: ["graph_bundle.json", "analyzer_capability.json"],
|
|
192
|
+
progress_summary: applied
|
|
193
|
+
? `Graph enrichment applied ${totalEdges} analyzer edge(s) from ${analyzersUsed.join(", ")}.${reasonSuffix}`
|
|
194
|
+
: `Graph enrichment omitted analyzers; edge reasoning rewrote ${reasoned.rewritten} reason(s).`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -2,6 +2,8 @@ import type { ArtifactBundle } from "../io/artifacts.js";
|
|
|
2
2
|
import type { AuditResult } from "../types.js";
|
|
3
3
|
import type { RuntimeValidationReport } from "../types/runtimeValidation.js";
|
|
4
4
|
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
5
|
+
import type { AuditScopeManifest } from "../types/auditScope.js";
|
|
6
|
+
import type { SynthesisNarrative } from "@audit-tools/shared";
|
|
5
7
|
export interface ExecutorRunResult {
|
|
6
8
|
updated: ArtifactBundle;
|
|
7
9
|
artifacts_written: string[];
|
|
@@ -15,11 +17,18 @@ export declare function runIntakeExecutor(bundle: ArtifactBundle, root: string):
|
|
|
15
17
|
export declare function runStructureExecutor(bundle: ArtifactBundle, root?: string): Promise<ExecutorRunResult>;
|
|
16
18
|
export declare function runDesignAssessmentExecutor(bundle: ArtifactBundle): ExecutorRunResult;
|
|
17
19
|
export declare function runDesignReviewAutoComplete(bundle: ArtifactBundle): ExecutorRunResult;
|
|
18
|
-
export declare function runPlanningExecutor(bundle: ArtifactBundle, root: string, lineIndex?: Record<string, number
|
|
20
|
+
export declare function runPlanningExecutor(bundle: ArtifactBundle, root: string, lineIndex?: Record<string, number>, sizeIndex?: Record<string, number>, scope?: AuditScopeManifest): Promise<ExecutorRunResult>;
|
|
19
21
|
export declare function runResultIngestionExecutor(bundle: ArtifactBundle, results: AuditResult[]): ExecutorRunResult;
|
|
20
22
|
export declare function runRuntimeValidationExecutor(bundle: ArtifactBundle, root: string, options?: {
|
|
21
23
|
opentoken?: boolean;
|
|
22
24
|
}): Promise<ExecutorRunResult>;
|
|
23
25
|
export declare function runRuntimeValidationUpdateExecutor(bundle: ArtifactBundle, updates: RuntimeValidationReport): ExecutorRunResult;
|
|
24
26
|
export declare function runSynthesisExecutor(bundle: ArtifactBundle, results?: AuditResult[]): ExecutorRunResult;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the optional synthesis-narrative obligation. When a host/provider
|
|
29
|
+
* narrative is supplied it is merged into the canonical findings report and the
|
|
30
|
+
* human report is re-rendered with themes/executive-summary/top-risks; without
|
|
31
|
+
* one the narrative is recorded as omitted and the deterministic report stands.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runSynthesisNarrativeExecutor(bundle: ArtifactBundle, narrative?: SynthesisNarrative): ExecutorRunResult;
|
|
25
34
|
export declare function runExternalAnalyzerImportExecutor(bundle: ArtifactBundle, externalResults: ExternalAnalyzerResults): ExecutorRunResult;
|
|
@@ -5,12 +5,13 @@ import { buildCriticalFlowManifest } from "../extractors/flows.js";
|
|
|
5
5
|
import { buildRiskRegister } from "../extractors/risk.js";
|
|
6
6
|
import { buildSurfaceManifest } from "../extractors/surfaces.js";
|
|
7
7
|
import { initializeCoverageFromPlan } from "./planning.js";
|
|
8
|
+
import { applyScopeToCoverage, fullAuditScope } from "./scope.js";
|
|
8
9
|
import { buildFlowCoverage } from "./flowCoverage.js";
|
|
9
10
|
import { buildRequeuePayload } from "./requeueCommand.js";
|
|
10
11
|
import { buildRuntimeValidationTasks, discoverRuntimeValidationCommand, mergeRuntimeValidationReport, } from "./runtimeValidation.js";
|
|
11
|
-
import { buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
|
|
12
|
+
import { applyNarrative, buildAuditFindingsReport, buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
|
|
12
13
|
import { buildChunkedAuditTasks, } from "./taskBuilder.js";
|
|
13
|
-
import { buildAuditPlanMetrics, buildReviewPackets, } from "./reviewPackets.js";
|
|
14
|
+
import { buildAuditPlanMetrics, buildReviewPackets, sizeIndexFromManifest, } from "./reviewPackets.js";
|
|
14
15
|
import { buildUnitManifest } from "./unitBuilder.js";
|
|
15
16
|
import { buildRepoManifestFromFs } from "../extractors/fsIntake.js";
|
|
16
17
|
import { loadIgnoreFile } from "../extractors/ignore.js";
|
|
@@ -27,6 +28,7 @@ function appendSelectiveDeepeningTasks(params) {
|
|
|
27
28
|
return { bundle: params.bundle, taskCount: 0, artifacts: [] };
|
|
28
29
|
}
|
|
29
30
|
const lineIndex = lineIndexFromTasks(params.bundle.audit_tasks);
|
|
31
|
+
const sizeIndex = sizeIndexFromManifest(params.bundle.repo_manifest);
|
|
30
32
|
const selectiveDeepeningTasks = buildSelectiveDeepeningTasks({
|
|
31
33
|
existingTasks: params.bundle.audit_tasks,
|
|
32
34
|
results: params.results,
|
|
@@ -46,10 +48,12 @@ function appendSelectiveDeepeningTasks(params) {
|
|
|
46
48
|
audit_plan_metrics: buildAuditPlanMetrics(auditTasks, {
|
|
47
49
|
graphBundle: params.bundle.graph_bundle,
|
|
48
50
|
lineIndex,
|
|
51
|
+
sizeIndex,
|
|
49
52
|
}),
|
|
50
53
|
review_packets: buildReviewPackets(auditTasks, {
|
|
51
54
|
graphBundle: params.bundle.graph_bundle,
|
|
52
55
|
lineIndex,
|
|
56
|
+
sizeIndex,
|
|
53
57
|
}),
|
|
54
58
|
},
|
|
55
59
|
taskCount: selectiveDeepeningTasks.length,
|
|
@@ -238,10 +242,11 @@ export function runDesignReviewAutoComplete(bundle) {
|
|
|
238
242
|
progress_summary: "Design review auto-completed (host-agent review available via next-step).",
|
|
239
243
|
};
|
|
240
244
|
}
|
|
241
|
-
export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
245
|
+
export async function runPlanningExecutor(bundle, root, lineIndex = {}, sizeIndex, scope) {
|
|
242
246
|
if (!bundle.repo_manifest) {
|
|
243
247
|
throw new Error("Cannot run planning executor without repo_manifest");
|
|
244
248
|
}
|
|
249
|
+
const resolvedSizeIndex = sizeIndex ?? sizeIndexFromManifest(bundle.repo_manifest);
|
|
245
250
|
if (!bundle.file_disposition ||
|
|
246
251
|
!bundle.unit_manifest ||
|
|
247
252
|
!bundle.surface_manifest ||
|
|
@@ -249,9 +254,13 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
249
254
|
!bundle.risk_register) {
|
|
250
255
|
throw new Error("Cannot run planning executor without current structure artifacts");
|
|
251
256
|
}
|
|
257
|
+
const resolvedScope = scope ?? fullAuditScope();
|
|
252
258
|
const externalAnalyzerResults = bundle.external_analyzer_results;
|
|
253
259
|
const coverage = initializeCoverageFromPlan(bundle.repo_manifest, bundle.unit_manifest, bundle.file_disposition, externalAnalyzerResults);
|
|
254
260
|
const skippedTrivialPaths = autoCompleteTrivialCoverage(coverage, lineIndex, externalAnalyzerResults);
|
|
261
|
+
// Delta scope: only seed + expanded files stay pending; the rest inherit prior
|
|
262
|
+
// completion or are excluded from this run. Full scope is a no-op.
|
|
263
|
+
applyScopeToCoverage(coverage, resolvedScope, bundle.coverage_matrix);
|
|
255
264
|
const flowCoverage = buildFlowCoverage(bundle.critical_flows, coverage);
|
|
256
265
|
const runtimeCommand = await discoverRuntimeValidationCommand(root);
|
|
257
266
|
const runtimeValidationTasks = buildRuntimeValidationTasks({
|
|
@@ -274,15 +283,21 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
274
283
|
const reviewPackets = buildReviewPackets(taggedAuditTasks, {
|
|
275
284
|
graphBundle: bundle.graph_bundle,
|
|
276
285
|
lineIndex,
|
|
286
|
+
sizeIndex: resolvedSizeIndex,
|
|
277
287
|
});
|
|
278
288
|
const auditPlanMetrics = buildAuditPlanMetrics(taggedAuditTasks, {
|
|
279
289
|
graphBundle: bundle.graph_bundle,
|
|
280
290
|
lineIndex,
|
|
291
|
+
sizeIndex: resolvedSizeIndex,
|
|
281
292
|
});
|
|
282
293
|
const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
|
|
294
|
+
const scopeSummary = resolvedScope.mode === "delta"
|
|
295
|
+
? ` Delta scope since ${resolvedScope.since}: ${resolvedScope.seed_files.length} changed file(s) + ${resolvedScope.expanded_files.length} graph neighbour(s) queued; a full audit is advised before release.`
|
|
296
|
+
: "";
|
|
283
297
|
return {
|
|
284
298
|
updated: {
|
|
285
299
|
...bundle,
|
|
300
|
+
scope: resolvedScope,
|
|
286
301
|
coverage_matrix: coverage,
|
|
287
302
|
flow_coverage: flowCoverage,
|
|
288
303
|
runtime_validation_tasks: runtimeValidationTasks,
|
|
@@ -294,6 +309,7 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
294
309
|
audit_report: undefined,
|
|
295
310
|
},
|
|
296
311
|
artifacts_written: [
|
|
312
|
+
"scope.json",
|
|
297
313
|
"coverage_matrix.json",
|
|
298
314
|
"flow_coverage.json",
|
|
299
315
|
"runtime_validation_tasks.json",
|
|
@@ -304,6 +320,7 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
304
320
|
"requeue_tasks.json",
|
|
305
321
|
],
|
|
306
322
|
progress_summary: `Built planning artifacts; generated ${taggedAuditTasks.length} review tasks in ${reviewPackets.length} packet(s) and ${requeuePayload.task_count} requeue tasks.` +
|
|
323
|
+
scopeSummary +
|
|
307
324
|
(skippedTrivialPaths.length > 0
|
|
308
325
|
? ` Skipped ${skippedTrivialPaths.length} trivial path${skippedTrivialPaths.length === 1 ? "" : "s"} from semantic review.`
|
|
309
326
|
: "") +
|
|
@@ -459,10 +476,9 @@ export function runRuntimeValidationUpdateExecutor(bundle, updates) {
|
|
|
459
476
|
: ""),
|
|
460
477
|
};
|
|
461
478
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
results: finalResults,
|
|
479
|
+
function buildBaseFindingsReport(bundle, results) {
|
|
480
|
+
return buildAuditFindingsReport(buildAuditReportModel({
|
|
481
|
+
results,
|
|
466
482
|
unitManifest: bundle.unit_manifest,
|
|
467
483
|
graphBundle: bundle.graph_bundle,
|
|
468
484
|
criticalFlows: bundle.critical_flows,
|
|
@@ -470,15 +486,77 @@ export function runSynthesisExecutor(bundle, results) {
|
|
|
470
486
|
runtimeValidationReport: bundle.runtime_validation_report,
|
|
471
487
|
externalAnalyzerResults: bundle.external_analyzer_results,
|
|
472
488
|
designAssessment: bundle.design_assessment,
|
|
473
|
-
});
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
export function runSynthesisExecutor(bundle, results) {
|
|
492
|
+
const finalResults = results ?? bundle.audit_results ?? [];
|
|
493
|
+
// Emit the canonical machine contract and render the human report from it.
|
|
494
|
+
// No narrative yet — that is layered by the synthesis-narrative obligation.
|
|
495
|
+
const findings = buildBaseFindingsReport(bundle, finalResults);
|
|
474
496
|
return {
|
|
475
497
|
updated: {
|
|
476
498
|
...bundle,
|
|
477
499
|
audit_results: finalResults,
|
|
478
|
-
|
|
500
|
+
audit_findings: findings,
|
|
501
|
+
audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope }),
|
|
502
|
+
},
|
|
503
|
+
artifacts_written: ["audit-findings.json", "audit-report.md"],
|
|
504
|
+
progress_summary: `Rendered deterministic audit report and canonical findings for ${finalResults.length} audit result entries.`,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Resolve the optional synthesis-narrative obligation. When a host/provider
|
|
509
|
+
* narrative is supplied it is merged into the canonical findings report and the
|
|
510
|
+
* human report is re-rendered with themes/executive-summary/top-risks; without
|
|
511
|
+
* one the narrative is recorded as omitted and the deterministic report stands.
|
|
512
|
+
*/
|
|
513
|
+
export function runSynthesisNarrativeExecutor(bundle, narrative) {
|
|
514
|
+
const baseReport = bundle.audit_findings ??
|
|
515
|
+
buildBaseFindingsReport(bundle, bundle.audit_results ?? []);
|
|
516
|
+
const needsBaseWrite = !bundle.audit_findings;
|
|
517
|
+
const hasNarrative = Boolean(narrative &&
|
|
518
|
+
((narrative.themes?.length ?? 0) > 0 ||
|
|
519
|
+
(narrative.executive_summary?.trim().length ?? 0) > 0 ||
|
|
520
|
+
(narrative.top_risks?.length ?? 0) > 0));
|
|
521
|
+
if (!hasNarrative) {
|
|
522
|
+
const record = {
|
|
523
|
+
status: "omitted",
|
|
524
|
+
theme_count: 0,
|
|
525
|
+
executive_summary_present: false,
|
|
526
|
+
top_risk_count: 0,
|
|
527
|
+
};
|
|
528
|
+
return {
|
|
529
|
+
updated: {
|
|
530
|
+
...bundle,
|
|
531
|
+
audit_findings: baseReport,
|
|
532
|
+
synthesis_narrative: record,
|
|
533
|
+
},
|
|
534
|
+
artifacts_written: needsBaseWrite
|
|
535
|
+
? ["audit-findings.json", "synthesis-narrative.json"]
|
|
536
|
+
: ["synthesis-narrative.json"],
|
|
537
|
+
progress_summary: "Synthesis narrative omitted; deterministic findings report retained.",
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const enriched = applyNarrative(baseReport, narrative);
|
|
541
|
+
const record = {
|
|
542
|
+
status: "applied",
|
|
543
|
+
theme_count: enriched.themes?.length ?? 0,
|
|
544
|
+
executive_summary_present: (enriched.executive_summary?.trim().length ?? 0) > 0,
|
|
545
|
+
top_risk_count: enriched.top_risks?.length ?? 0,
|
|
546
|
+
};
|
|
547
|
+
return {
|
|
548
|
+
updated: {
|
|
549
|
+
...bundle,
|
|
550
|
+
audit_findings: enriched,
|
|
551
|
+
audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope }),
|
|
552
|
+
synthesis_narrative: record,
|
|
479
553
|
},
|
|
480
|
-
artifacts_written: [
|
|
481
|
-
|
|
554
|
+
artifacts_written: [
|
|
555
|
+
"audit-findings.json",
|
|
556
|
+
"audit-report.md",
|
|
557
|
+
"synthesis-narrative.json",
|
|
558
|
+
],
|
|
559
|
+
progress_summary: `Synthesis narrative applied: ${record.theme_count} theme(s), ${record.top_risk_count} top risk(s).`,
|
|
482
560
|
};
|
|
483
561
|
}
|
|
484
562
|
export function runExternalAnalyzerImportExecutor(bundle, externalResults) {
|
|
@@ -1,32 +1,13 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { delimiter, extname, isAbsolute, join } from "node:path";
|
|
4
|
-
|
|
5
|
-
return process.platform === "win32" && /\.(cmd|bat)$/i.test(path);
|
|
6
|
-
}
|
|
7
|
-
function quoteForCmd(arg) {
|
|
8
|
-
if (arg.length === 0)
|
|
9
|
-
return '""';
|
|
10
|
-
if (!/[\s"]/u.test(arg))
|
|
11
|
-
return arg;
|
|
12
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
13
|
-
}
|
|
4
|
+
import { resolveExecArgv } from "@audit-tools/shared";
|
|
14
5
|
function toSpawnTuple(candidate) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
command: process.env.ComSpec ?? "cmd.exe",
|
|
23
|
-
args: [
|
|
24
|
-
"/d",
|
|
25
|
-
"/s",
|
|
26
|
-
"/c",
|
|
27
|
-
[candidate.command, ...candidate.args].map(quoteForCmd).join(" "),
|
|
28
|
-
],
|
|
29
|
-
};
|
|
6
|
+
// Shared resolver applies the single Windows `.cmd`/`.bat` wrapping impl.
|
|
7
|
+
// The candidate command is already PATH-resolved (absolute path or
|
|
8
|
+
// process.execPath), so package-manager shim mapping is a no-op here.
|
|
9
|
+
const resolved = resolveExecArgv([candidate.command, ...candidate.args]);
|
|
10
|
+
return { command: resolved[0], args: resolved.slice(1) };
|
|
30
11
|
}
|
|
31
12
|
function resolveFromPath(command) {
|
|
32
13
|
if (command.trim().length === 0) {
|
|
@@ -6,6 +6,7 @@ const PRIORITY = [
|
|
|
6
6
|
"auto_fixes_applied",
|
|
7
7
|
"syntax_resolved",
|
|
8
8
|
"structure_artifacts",
|
|
9
|
+
"graph_enrichment_current",
|
|
9
10
|
"design_assessment_current",
|
|
10
11
|
"design_review_completed",
|
|
11
12
|
"planning_artifacts",
|
|
@@ -13,6 +14,7 @@ const PRIORITY = [
|
|
|
13
14
|
"audit_results_ingested",
|
|
14
15
|
"runtime_validation_current",
|
|
15
16
|
"synthesis_current",
|
|
17
|
+
"synthesis_narrative_current",
|
|
16
18
|
];
|
|
17
19
|
export function findObligation(obligations) {
|
|
18
20
|
for (const id of PRIORITY) {
|
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
import type { AuditTask } from "../types.js";
|
|
2
2
|
import type { AuditPlanMetrics, ReviewPacket } from "../types/reviewPlanning.js";
|
|
3
|
-
import type { GraphBundle } from "@audit-tools/shared";
|
|
3
|
+
import type { GraphBundle, GraphEdge } from "@audit-tools/shared";
|
|
4
4
|
export declare const ESTIMATED_TOKENS_PER_LINE = 4;
|
|
5
5
|
export declare const ESTIMATED_PACKET_PROMPT_TOKENS = 900;
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Build a path → size_bytes index from a repo manifest. Byte counts are
|
|
8
|
+
* recorded during intake, so this never reads files. Review packet token
|
|
9
|
+
* estimates are derived from these bytes (Phase 2) instead of counted lines.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sizeIndexFromManifest(repoManifest?: {
|
|
12
|
+
files: ReadonlyArray<{
|
|
13
|
+
path: string;
|
|
14
|
+
size_bytes: number;
|
|
15
|
+
}>;
|
|
16
|
+
}): Record<string, number>;
|
|
17
|
+
export declare function estimateTaskGroupTokens(tasks: AuditTask[], sizeIndex?: Record<string, number>, lineIndex?: Record<string, number>): number;
|
|
18
|
+
/**
|
|
19
|
+
* Fan-in / fan-out degree above which a node is treated as a hub. Exported so
|
|
20
|
+
* the Phase 3 delta-scope expansion skips the same hubs that packet planning
|
|
21
|
+
* skips, preventing scope blow-up through highly-connected modules.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HIGH_FAN_DEGREE_THRESHOLD = 12;
|
|
7
24
|
export interface BuildReviewPacketOptions {
|
|
8
25
|
graphBundle?: GraphBundle;
|
|
9
26
|
lineIndex?: Record<string, number>;
|
|
27
|
+
/** Path → size_bytes (from the repo manifest); drives byte-based token sizing. */
|
|
28
|
+
sizeIndex?: Record<string, number>;
|
|
10
29
|
maxTasksPerPacket?: number;
|
|
11
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Soft per-packet content-token budget. Defaults to
|
|
32
|
+
* DEFAULT_TARGET_PACKET_TOKENS. A packet is split when its estimated content
|
|
33
|
+
* tokens would exceed this budget.
|
|
34
|
+
*/
|
|
35
|
+
targetPacketTokens?: number;
|
|
12
36
|
/**
|
|
13
37
|
* Available context budget in tokens (context_tokens − reserved_output_tokens).
|
|
14
|
-
* When provided,
|
|
38
|
+
* When provided, targetPacketTokens is capped to fit within this budget so a
|
|
39
|
+
* packet's estimated tokens never exceed it.
|
|
15
40
|
*/
|
|
16
41
|
maxContextTokens?: number;
|
|
17
42
|
}
|
|
43
|
+
export declare function normalizeGraphPath(path: string): string;
|
|
44
|
+
export declare function collectGraphEdges(graphBundle?: GraphBundle): GraphEdge[];
|
|
45
|
+
export declare function graphEdgeConfidence(edge: GraphEdge): number;
|
|
46
|
+
export interface GraphDegreeIndex {
|
|
47
|
+
fanIn: Map<string, number>;
|
|
48
|
+
fanOut: Map<string, number>;
|
|
49
|
+
}
|
|
50
|
+
export declare function buildGraphDegreeIndex(edges: GraphEdge[]): GraphDegreeIndex;
|
|
18
51
|
export declare function buildReviewPackets(tasks: AuditTask[], options?: BuildReviewPacketOptions): ReviewPacket[];
|
|
19
52
|
export declare function orderTasksForPacketReview(tasks: AuditTask[], options?: BuildReviewPacketOptions): AuditTask[];
|
|
20
53
|
export declare function buildAuditPlanMetrics(tasks: AuditTask[], options?: BuildReviewPacketOptions & {
|