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
|
@@ -279,6 +279,42 @@ function addPythonImportEdge(edges, fromPath, target, kind, specifier) {
|
|
|
279
279
|
reason: `Resolved Python import specifier '${specifier}'.`,
|
|
280
280
|
}));
|
|
281
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Resolve a single `import <spec>` module specifier to a repo file, or
|
|
284
|
+
* undefined. Shared with the tree-sitter Python analyzer so AST-extracted
|
|
285
|
+
* imports resolve to exactly the same targets as the regex floor.
|
|
286
|
+
*/
|
|
287
|
+
export function resolvePythonImportTarget(fromPath, specifier, pathLookup) {
|
|
288
|
+
if (!isPythonAbsoluteModuleSpecifier(specifier)) {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
return resolvePythonModuleSpecifier(fromPath, specifier, pathLookup);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Resolve a `from <module> import <names>` statement to repo files. Mirrors the
|
|
295
|
+
* floor: prefer submodule files (`module.name`), else the module itself. Shared
|
|
296
|
+
* with the tree-sitter Python analyzer.
|
|
297
|
+
*/
|
|
298
|
+
export function resolvePythonFromImportTargets(fromPath, moduleSpecifier, importedNames, pathLookup) {
|
|
299
|
+
if (!isPythonModuleSpecifier(moduleSpecifier)) {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
const submoduleTargets = importedNames
|
|
303
|
+
.filter((name) => name !== "*" && isPythonIdentifier(name))
|
|
304
|
+
.map((name) => appendPythonImportedSpecifier(moduleSpecifier, name))
|
|
305
|
+
.map((specifier) => ({
|
|
306
|
+
specifier,
|
|
307
|
+
target: resolvePythonModuleSpecifier(fromPath, specifier, pathLookup),
|
|
308
|
+
}))
|
|
309
|
+
.filter((item) => item.target !== undefined && item.target !== fromPath);
|
|
310
|
+
if (submoduleTargets.length > 0) {
|
|
311
|
+
return submoduleTargets;
|
|
312
|
+
}
|
|
313
|
+
const moduleTarget = resolvePythonModuleSpecifier(fromPath, moduleSpecifier, pathLookup);
|
|
314
|
+
return moduleTarget && moduleTarget !== fromPath
|
|
315
|
+
? [{ specifier: moduleSpecifier, target: moduleTarget }]
|
|
316
|
+
: [];
|
|
317
|
+
}
|
|
282
318
|
export function extractPythonImportEdges(fromPath, content, pathLookup) {
|
|
283
319
|
if (!isPythonSourcePath(fromPath)) {
|
|
284
320
|
return [];
|
|
@@ -8,6 +8,12 @@ export declare const EXTRACTOR_HEURISTIC_NOTE = "Heuristic path classification n
|
|
|
8
8
|
export declare function normalizeExtractorPath(path: string): string;
|
|
9
9
|
export declare function pathTokens(normalized: string): string[];
|
|
10
10
|
export declare function isNodeModulesOrGit(normalized: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* `.tmp/` holds transient scratch and bundled tool copies (e.g. a vendored
|
|
13
|
+
* `.tmp/opentoken`). These are not the audited project's source — excluding
|
|
14
|
+
* them keeps the self-audit from auditing its own bundled dependencies.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isTmpPath(normalized: string): boolean;
|
|
11
17
|
export declare function isBuildOutput(normalized: string): boolean;
|
|
12
18
|
export declare function isVendorPath(normalized: string): boolean;
|
|
13
19
|
export declare function isBinaryArtifact(normalized: string): boolean;
|
|
@@ -156,6 +156,14 @@ function hasToken(normalized, values) {
|
|
|
156
156
|
export function isNodeModulesOrGit(normalized) {
|
|
157
157
|
return hasSegment(normalized, "node_modules") || hasSegment(normalized, ".git");
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* `.tmp/` holds transient scratch and bundled tool copies (e.g. a vendored
|
|
161
|
+
* `.tmp/opentoken`). These are not the audited project's source — excluding
|
|
162
|
+
* them keeps the self-audit from auditing its own bundled dependencies.
|
|
163
|
+
*/
|
|
164
|
+
export function isTmpPath(normalized) {
|
|
165
|
+
return hasSegment(normalized, ".tmp");
|
|
166
|
+
}
|
|
159
167
|
export function isBuildOutput(normalized) {
|
|
160
168
|
return hasSegment(normalized, "dist") || hasSegment(normalized, "build");
|
|
161
169
|
}
|
package/dist/io/artifacts.d.ts
CHANGED
|
@@ -2,12 +2,15 @@ import { cp, rm } from "node:fs/promises";
|
|
|
2
2
|
import type { AuditResult, AuditTask, CoverageMatrix, RepoManifest, UnitManifest } from "../types.js";
|
|
3
3
|
import type { AuditState } from "../types/auditState.js";
|
|
4
4
|
import type { ArtifactMetadataManifest } from "../types/artifactMetadata.js";
|
|
5
|
-
import type { FileDisposition, CriticalFlowManifest, GraphBundle, RiskRegister, SurfaceManifest } from "@audit-tools/shared";
|
|
5
|
+
import type { AuditFindingsReport, FileDisposition, CriticalFlowManifest, GraphBundle, RiskRegister, SurfaceManifest } from "@audit-tools/shared";
|
|
6
|
+
import type { SynthesisNarrativeRecord } from "../types/synthesisNarrative.js";
|
|
6
7
|
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
7
8
|
import type { FlowCoverageManifest } from "../types/flowCoverage.js";
|
|
8
9
|
import type { AuditPlanMetrics, ReviewPacket } from "../types/reviewPlanning.js";
|
|
9
10
|
import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
|
|
10
11
|
import type { DesignAssessment } from "../types/designAssessment.js";
|
|
12
|
+
import type { AnalyzerCapabilityRecord } from "../types/analyzerCapability.js";
|
|
13
|
+
import type { AuditScopeManifest } from "../types/auditScope.js";
|
|
11
14
|
import type { ToolingManifest } from "../types/toolingManifest.js";
|
|
12
15
|
type ArtifactPayloadMap = {
|
|
13
16
|
repo_manifest: RepoManifest;
|
|
@@ -20,6 +23,8 @@ type ArtifactPayloadMap = {
|
|
|
20
23
|
flow_coverage: FlowCoverageManifest;
|
|
21
24
|
risk_register: RiskRegister;
|
|
22
25
|
design_assessment: DesignAssessment;
|
|
26
|
+
analyzer_capability: AnalyzerCapabilityRecord;
|
|
27
|
+
scope: AuditScopeManifest;
|
|
23
28
|
coverage_matrix: CoverageMatrix;
|
|
24
29
|
runtime_validation_tasks: RuntimeValidationTaskManifest;
|
|
25
30
|
runtime_validation_report: RuntimeValidationReport;
|
|
@@ -31,6 +36,8 @@ type ArtifactPayloadMap = {
|
|
|
31
36
|
review_packets: ReviewPacket[];
|
|
32
37
|
requeue_tasks: AuditTask[];
|
|
33
38
|
audit_report: string;
|
|
39
|
+
audit_findings: AuditFindingsReport;
|
|
40
|
+
synthesis_narrative: SynthesisNarrativeRecord;
|
|
34
41
|
audit_state: AuditState;
|
|
35
42
|
artifact_metadata: ArtifactMetadataManifest;
|
|
36
43
|
tooling_manifest: ToolingManifest;
|
|
@@ -59,6 +66,8 @@ export declare const ARTIFACT_DEFINITIONS: {
|
|
|
59
66
|
readonly flow_coverage: ArtifactDefinition<"flow_coverage">;
|
|
60
67
|
readonly risk_register: ArtifactDefinition<"risk_register">;
|
|
61
68
|
readonly design_assessment: ArtifactDefinition<"design_assessment">;
|
|
69
|
+
readonly analyzer_capability: ArtifactDefinition<"analyzer_capability">;
|
|
70
|
+
readonly scope: ArtifactDefinition<"scope">;
|
|
62
71
|
readonly coverage_matrix: ArtifactDefinition<"coverage_matrix">;
|
|
63
72
|
readonly runtime_validation_tasks: ArtifactDefinition<"runtime_validation_tasks">;
|
|
64
73
|
readonly runtime_validation_report: ArtifactDefinition<"runtime_validation_report">;
|
|
@@ -70,6 +79,8 @@ export declare const ARTIFACT_DEFINITIONS: {
|
|
|
70
79
|
readonly review_packets: ArtifactDefinition<"review_packets">;
|
|
71
80
|
readonly requeue_tasks: ArtifactDefinition<"requeue_tasks">;
|
|
72
81
|
readonly audit_report: ArtifactDefinition<"audit_report">;
|
|
82
|
+
readonly audit_findings: ArtifactDefinition<"audit_findings">;
|
|
83
|
+
readonly synthesis_narrative: ArtifactDefinition<"synthesis_narrative">;
|
|
73
84
|
readonly audit_state: ArtifactDefinition<"audit_state">;
|
|
74
85
|
readonly artifact_metadata: ArtifactDefinition<"artifact_metadata">;
|
|
75
86
|
readonly tooling_manifest: ArtifactDefinition<"tooling_manifest">;
|
package/dist/io/artifacts.js
CHANGED
|
@@ -37,6 +37,8 @@ export const ARTIFACT_DEFINITIONS = {
|
|
|
37
37
|
flow_coverage: jsonArtifact("flow_coverage.json", "analysis"),
|
|
38
38
|
risk_register: jsonArtifact("risk_register.json", "analysis"),
|
|
39
39
|
design_assessment: jsonArtifact("design_assessment.json", "analysis"),
|
|
40
|
+
analyzer_capability: jsonArtifact("analyzer_capability.json", "analysis"),
|
|
41
|
+
scope: jsonArtifact("scope.json", "execution"),
|
|
40
42
|
coverage_matrix: jsonArtifact("coverage_matrix.json", "execution"),
|
|
41
43
|
runtime_validation_tasks: jsonArtifact("runtime_validation_tasks.json", "execution"),
|
|
42
44
|
runtime_validation_report: jsonArtifact("runtime_validation_report.json", "execution"),
|
|
@@ -48,6 +50,8 @@ export const ARTIFACT_DEFINITIONS = {
|
|
|
48
50
|
review_packets: jsonArtifact("review_packets.json", "execution"),
|
|
49
51
|
requeue_tasks: jsonArtifact("requeue_tasks.json", "execution"),
|
|
50
52
|
audit_report: textArtifact("audit-report.md", "reporting"),
|
|
53
|
+
audit_findings: jsonArtifact("audit-findings.json", "reporting"),
|
|
54
|
+
synthesis_narrative: jsonArtifact("synthesis-narrative.json", "reporting"),
|
|
51
55
|
audit_state: jsonArtifact("audit_state.json", "supervisor"),
|
|
52
56
|
artifact_metadata: jsonArtifact("artifact_metadata.json", "supervisor"),
|
|
53
57
|
tooling_manifest: jsonArtifact("tooling_manifest.json", "supervisor"),
|
|
@@ -113,6 +117,14 @@ export async function promoteFinalAuditReport(params, options = {}) {
|
|
|
113
117
|
warn(warning);
|
|
114
118
|
return { promoted: false, cleaned: false, warning };
|
|
115
119
|
}
|
|
120
|
+
// Promote the canonical machine contract alongside the human report. Missing
|
|
121
|
+
// (e.g. legacy bundle) or unreadable: best-effort, never blocks completion.
|
|
122
|
+
try {
|
|
123
|
+
await copy(join(params.artifactsDir, "audit-findings.json"), join(params.repoRoot, "audit-findings.json"), { force: true });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// audit-findings.json is optional output; absence must not fail promotion.
|
|
127
|
+
}
|
|
116
128
|
try {
|
|
117
129
|
await remove(params.artifactsDir, { recursive: true, force: true });
|
|
118
130
|
return { promoted: true, cleaned: true };
|
|
@@ -3,14 +3,34 @@ import type { AuditState } from "../types/auditState.js";
|
|
|
3
3
|
import type { AuditResult } from "../types.js";
|
|
4
4
|
import type { RuntimeValidationReport } from "../types/runtimeValidation.js";
|
|
5
5
|
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
6
|
+
import { RunLogger } from "@audit-tools/shared";
|
|
7
|
+
import type { AnalyzerSetting, SynthesisNarrative } from "@audit-tools/shared";
|
|
8
|
+
import type { EdgeReasoningResults } from "./edgeReasoning.js";
|
|
6
9
|
export interface AdvanceAuditOptions {
|
|
7
10
|
root?: string;
|
|
8
11
|
lineIndex?: Record<string, number>;
|
|
12
|
+
/** Path → size_bytes (from the repo manifest); drives byte-based packet token sizing. */
|
|
13
|
+
sizeIndex?: Record<string, number>;
|
|
9
14
|
auditResults?: AuditResult[];
|
|
10
15
|
runtimeValidationUpdates?: RuntimeValidationReport;
|
|
11
16
|
externalAnalyzerResults?: ExternalAnalyzerResults;
|
|
17
|
+
/** Host/provider-supplied synthesis narrative; merged by synthesis_narrative_executor. */
|
|
18
|
+
narrativeResults?: SynthesisNarrative;
|
|
19
|
+
/** Per-analyzer resolution policy for the optional graph-enrichment pass. */
|
|
20
|
+
analyzers?: Record<string, AnalyzerSetting>;
|
|
21
|
+
/** Phase 4B gate (session-config `graph.llm_edge_reasoning`); default off. */
|
|
22
|
+
graphLlmEdgeReasoning?: boolean;
|
|
23
|
+
/** Phase 4B host-supplied reason rewrites for low-confidence graph edges. */
|
|
24
|
+
edgeReasoningResults?: EdgeReasoningResults;
|
|
25
|
+
/**
|
|
26
|
+
* Git ref for Phase 3 delta mode (the `--since` flag). When set and resolvable
|
|
27
|
+
* against a git repo, planning scopes coverage to the changed files and their
|
|
28
|
+
* graph neighbours; otherwise the run is a full audit.
|
|
29
|
+
*/
|
|
30
|
+
since?: string;
|
|
12
31
|
preferredExecutor?: string;
|
|
13
32
|
opentoken?: boolean;
|
|
33
|
+
runLogger?: RunLogger;
|
|
14
34
|
}
|
|
15
35
|
export interface AdvanceAuditResult {
|
|
16
36
|
audit_state: AuditState;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { decideNextStep, findObligation } from "./nextStep.js";
|
|
2
2
|
import { deriveAuditState } from "./state.js";
|
|
3
3
|
import { computeArtifactMetadata } from "./artifactMetadata.js";
|
|
4
|
-
import { runIntakeExecutor, runStructureExecutor, runPlanningExecutor, runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runSynthesisExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, runExternalAnalyzerImportExecutor, } from "./internalExecutors.js";
|
|
4
|
+
import { runIntakeExecutor, runStructureExecutor, runPlanningExecutor, runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runSynthesisExecutor, runSynthesisNarrativeExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, runExternalAnalyzerImportExecutor, } from "./internalExecutors.js";
|
|
5
5
|
import { runAutoFixExecutor } from "./autoFixExecutor.js";
|
|
6
6
|
import { runSyntaxResolutionExecutor } from "./syntaxResolutionExecutor.js";
|
|
7
|
+
import { runGraphEnrichmentExecutor } from "./graphEnrichmentExecutor.js";
|
|
8
|
+
import { resolveAuditScope } from "./scope.js";
|
|
9
|
+
import { RunLogger } from "@audit-tools/shared";
|
|
7
10
|
function cloneState(state) {
|
|
8
11
|
return {
|
|
9
12
|
...state,
|
|
@@ -18,12 +21,19 @@ function formatExecutorFailure(selectedExecutor, selectedObligation, error) {
|
|
|
18
21
|
});
|
|
19
22
|
}
|
|
20
23
|
export async function advanceAudit(bundle, options = {}) {
|
|
24
|
+
const log = options.runLogger ?? RunLogger.disabled();
|
|
21
25
|
const decision = decideNextStep(bundle);
|
|
22
26
|
const forcedExecutor = options.preferredExecutor ?? null;
|
|
23
27
|
const selectedExecutor = forcedExecutor ?? decision.selected_executor;
|
|
24
28
|
const selectedObligation = forcedExecutor
|
|
25
29
|
? `forced:${forcedExecutor}`
|
|
26
30
|
: decision.selected_obligation;
|
|
31
|
+
log.event({
|
|
32
|
+
phase: "advance",
|
|
33
|
+
kind: "obligation",
|
|
34
|
+
obligation: selectedObligation ?? undefined,
|
|
35
|
+
note: decision.reason,
|
|
36
|
+
});
|
|
27
37
|
if (!selectedExecutor) {
|
|
28
38
|
const state = cloneState(decision.state);
|
|
29
39
|
state.last_executor = bundle.audit_state?.last_executor ?? state.last_executor;
|
|
@@ -43,6 +53,14 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
43
53
|
};
|
|
44
54
|
}
|
|
45
55
|
let run;
|
|
56
|
+
let plannedScope;
|
|
57
|
+
const executorStartedAt = Date.now();
|
|
58
|
+
log.event({
|
|
59
|
+
phase: "advance",
|
|
60
|
+
kind: "executor_start",
|
|
61
|
+
obligation: selectedObligation ?? undefined,
|
|
62
|
+
note: selectedExecutor,
|
|
63
|
+
});
|
|
46
64
|
try {
|
|
47
65
|
switch (selectedExecutor) {
|
|
48
66
|
case "intake_executor":
|
|
@@ -53,6 +71,14 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
53
71
|
case "structure_executor":
|
|
54
72
|
run = await runStructureExecutor(bundle, options.root);
|
|
55
73
|
break;
|
|
74
|
+
case "graph_enrichment_executor":
|
|
75
|
+
run = await runGraphEnrichmentExecutor(bundle, {
|
|
76
|
+
root: options.root,
|
|
77
|
+
analyzers: options.analyzers,
|
|
78
|
+
llmEdgeReasoning: options.graphLlmEdgeReasoning,
|
|
79
|
+
edgeReasoning: options.edgeReasoningResults,
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
56
82
|
case "design_assessment_executor":
|
|
57
83
|
run = runDesignAssessmentExecutor(bundle);
|
|
58
84
|
break;
|
|
@@ -62,7 +88,12 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
62
88
|
case "planning_executor":
|
|
63
89
|
if (!options.root)
|
|
64
90
|
throw new Error("advanceAudit planning_executor requires root");
|
|
65
|
-
|
|
91
|
+
plannedScope = resolveAuditScope({
|
|
92
|
+
root: options.root,
|
|
93
|
+
since: options.since,
|
|
94
|
+
bundle,
|
|
95
|
+
});
|
|
96
|
+
run = await runPlanningExecutor(bundle, options.root, options.lineIndex ?? {}, options.sizeIndex, plannedScope);
|
|
66
97
|
break;
|
|
67
98
|
case "result_ingestion_executor":
|
|
68
99
|
run = runResultIngestionExecutor(bundle, options.auditResults ?? bundle.audit_results ?? []);
|
|
@@ -77,6 +108,9 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
77
108
|
case "synthesis_executor":
|
|
78
109
|
run = runSynthesisExecutor(bundle, options.auditResults);
|
|
79
110
|
break;
|
|
111
|
+
case "synthesis_narrative_executor":
|
|
112
|
+
run = runSynthesisNarrativeExecutor(bundle, options.narrativeResults);
|
|
113
|
+
break;
|
|
80
114
|
case "runtime_validation_update_executor":
|
|
81
115
|
if (!options.runtimeValidationUpdates)
|
|
82
116
|
throw new Error("advanceAudit runtime_validation_update_executor requires runtimeValidationUpdates");
|
|
@@ -117,6 +151,31 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
117
151
|
catch (error) {
|
|
118
152
|
throw formatExecutorFailure(selectedExecutor, selectedObligation, error);
|
|
119
153
|
}
|
|
154
|
+
log.event({
|
|
155
|
+
phase: "advance",
|
|
156
|
+
kind: "executor_end",
|
|
157
|
+
obligation: selectedObligation ?? undefined,
|
|
158
|
+
note: selectedExecutor,
|
|
159
|
+
duration_ms: Date.now() - executorStartedAt,
|
|
160
|
+
});
|
|
161
|
+
if (plannedScope) {
|
|
162
|
+
log.event({
|
|
163
|
+
phase: "advance",
|
|
164
|
+
kind: "scope",
|
|
165
|
+
obligation: selectedObligation ?? undefined,
|
|
166
|
+
note: plannedScope.mode === "delta"
|
|
167
|
+
? `delta since ${plannedScope.since}: ${plannedScope.seed_files.length} changed + ${plannedScope.expanded_files.length} neighbors; full audit advised before release`
|
|
168
|
+
: "full audit scope",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
for (const artifact of run.artifacts_written) {
|
|
172
|
+
log.event({
|
|
173
|
+
phase: "advance",
|
|
174
|
+
kind: "artifact_write",
|
|
175
|
+
obligation: selectedObligation ?? undefined,
|
|
176
|
+
artifact,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
120
179
|
const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata, [...run.artifacts_written, "tooling_manifest.json"]);
|
|
121
180
|
const metadataBundle = {
|
|
122
181
|
...run.updated,
|
|
@@ -19,6 +19,15 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
19
19
|
"runtime_validation_report.json",
|
|
20
20
|
"audit-report.md",
|
|
21
21
|
],
|
|
22
|
+
// The optional graph-enrichment pass layers analyzer edges onto graph_bundle
|
|
23
|
+
// and records provenance in analyzer_capability.json. A re-built (structure)
|
|
24
|
+
// graph re-stales the marker so enrichment re-runs. No cycle: the enrichment
|
|
25
|
+
// executor writes graph_bundle AND the marker in one advanceAudit call, and
|
|
26
|
+
// computeArtifactMetadata is dependency-first, so the marker records the
|
|
27
|
+
// post-enrichment graph_bundle revision (mirrors audit-findings → narrative).
|
|
28
|
+
"graph_bundle.json": [
|
|
29
|
+
"analyzer_capability.json",
|
|
30
|
+
],
|
|
22
31
|
"file_disposition.json": [
|
|
23
32
|
"unit_manifest.json",
|
|
24
33
|
"surface_manifest.json",
|
|
@@ -91,6 +100,17 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
91
100
|
"runtime_validation_report.json",
|
|
92
101
|
"audit-report.md",
|
|
93
102
|
],
|
|
103
|
+
// Phase 3 delta scope. scope.json is produced by the planning executor (full
|
|
104
|
+
// or delta) and gates coverage: in delta mode it decides which coverage
|
|
105
|
+
// entries are (re)queued vs. inherited-complete/excluded. A changed scope
|
|
106
|
+
// (different `--since`/seed set → new content hash) re-stales coverage so the
|
|
107
|
+
// plan rebuilds. No cycle: planning writes scope.json AND coverage_matrix.json
|
|
108
|
+
// in one advanceAudit call, and computeArtifactMetadata is dependency-first,
|
|
109
|
+
// so coverage records the post-write scope revision (mirrors graph_bundle →
|
|
110
|
+
// analyzer_capability and audit-findings → narrative).
|
|
111
|
+
"scope.json": [
|
|
112
|
+
"coverage_matrix.json",
|
|
113
|
+
],
|
|
94
114
|
"coverage_matrix.json": [
|
|
95
115
|
"flow_coverage.json",
|
|
96
116
|
"audit_plan_metrics.json",
|
|
@@ -115,4 +135,11 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
115
135
|
"runtime_validation_report.json": [
|
|
116
136
|
"audit-report.md",
|
|
117
137
|
],
|
|
138
|
+
// The canonical machine contract is co-produced with audit-report.md by the
|
|
139
|
+
// synthesis executor. The optional narrative pass tracks its revision: a fresh
|
|
140
|
+
// (re-synthesized) audit-findings.json re-stales the narrative marker so the
|
|
141
|
+
// themes/executive-summary/top-risks regenerate. See spec/dependency-map.md.
|
|
142
|
+
"audit-findings.json": [
|
|
143
|
+
"synthesis-narrative.json",
|
|
144
|
+
],
|
|
118
145
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GraphBundle, GraphEdge } from "@audit-tools/shared";
|
|
2
|
+
export declare const DEFAULT_EDGE_CONFIDENCE_FLOOR = 0.65;
|
|
3
|
+
/** Bound the candidate set so one pathological repo cannot balloon the call. */
|
|
4
|
+
export declare const MAX_REASONED_EDGES = 200;
|
|
5
|
+
/** One host-supplied reason rewrite, matched to an edge by (from, to, kind). */
|
|
6
|
+
export interface EdgeReasonRewrite {
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
/** Optional; when omitted the rewrite matches any candidate with from+to. */
|
|
10
|
+
kind?: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
export interface EdgeReasoningResults {
|
|
14
|
+
rewrites: EdgeReasonRewrite[];
|
|
15
|
+
}
|
|
16
|
+
export interface EdgeReasoningOptions {
|
|
17
|
+
/** Edges strictly below this confidence are candidates (default 0.65). */
|
|
18
|
+
confidenceFloor?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface EdgeReasoningSummary {
|
|
21
|
+
rewritten: number;
|
|
22
|
+
candidates: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Collect the low-confidence edges (the actual edge objects, so the caller can
|
|
26
|
+
* mutate `reason` in place) in a deterministic order. Routes are excluded — they
|
|
27
|
+
* carry no `reason`/`confidence`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectLowConfidenceEdges(bundle: GraphBundle, floor?: number): GraphEdge[];
|
|
30
|
+
/** Stable content hash of the candidate edge set, for host-side call caching. */
|
|
31
|
+
export declare function edgeReasoningContentHash(candidates: GraphEdge[]): string;
|
|
32
|
+
/** The single bounded prompt a host runs to produce {@link EdgeReasoningResults}. */
|
|
33
|
+
export declare function buildEdgeReasoningPrompt(candidates: GraphEdge[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Apply host-supplied reason rewrites to `bundle` (mutated in place). Only edges
|
|
36
|
+
* below the confidence floor are eligible; a rewrite that matches no eligible
|
|
37
|
+
* edge is ignored. Returns a summary; the edge set itself is invariant.
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyEdgeReasoning(bundle: GraphBundle, results: EdgeReasoningResults | undefined, options?: EdgeReasoningOptions): EdgeReasoningSummary;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 4B — optional, bounded edge-reasoning pass.
|
|
4
|
+
*
|
|
5
|
+
* A deterministic transform that may only rewrite the human-readable `reason`
|
|
6
|
+
* of existing low-confidence graph edges. It never adds, removes, re-targets, or
|
|
7
|
+
* re-weights an edge: the `(from, to, kind, confidence, direction)` identity of
|
|
8
|
+
* every edge is preserved exactly. Rewrites are host/provider-supplied (the same
|
|
9
|
+
* conversation-first pattern as the Phase 6 synthesis narrative) — no in-process
|
|
10
|
+
* LLM call. No rewrites (or the config flag off) → no-op, leaving the
|
|
11
|
+
* deterministic graph byte-identical. This is part of the
|
|
12
|
+
* `graph_enrichment_current` obligation.
|
|
13
|
+
*
|
|
14
|
+
* The pass is bounded to edges below a confidence floor (default 0.65) because
|
|
15
|
+
* those are exactly the heuristic edges whose terse machine reason benefits from
|
|
16
|
+
* a clearer explanation; high-confidence compiler/import edges are left alone.
|
|
17
|
+
* {@link buildEdgeReasoningPrompt} and {@link edgeReasoningContentHash} let a
|
|
18
|
+
* host produce and cache that single rewriting call by edge-set content hash.
|
|
19
|
+
*/
|
|
20
|
+
const EDGE_REASONING_VERSION = 1;
|
|
21
|
+
export const DEFAULT_EDGE_CONFIDENCE_FLOOR = 0.65;
|
|
22
|
+
/** Bound the candidate set so one pathological repo cannot balloon the call. */
|
|
23
|
+
export const MAX_REASONED_EDGES = 200;
|
|
24
|
+
function confidenceOf(edge) {
|
|
25
|
+
return typeof edge.confidence === "number" && Number.isFinite(edge.confidence)
|
|
26
|
+
? edge.confidence
|
|
27
|
+
: 0;
|
|
28
|
+
}
|
|
29
|
+
function edgeSignature(edge) {
|
|
30
|
+
return `${edge.from}\0${edge.to}\0${edge.kind ?? ""}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Collect the low-confidence edges (the actual edge objects, so the caller can
|
|
34
|
+
* mutate `reason` in place) in a deterministic order. Routes are excluded — they
|
|
35
|
+
* carry no `reason`/`confidence`.
|
|
36
|
+
*/
|
|
37
|
+
export function collectLowConfidenceEdges(bundle, floor = DEFAULT_EDGE_CONFIDENCE_FLOOR) {
|
|
38
|
+
const candidates = [];
|
|
39
|
+
for (const bucket of [
|
|
40
|
+
bundle.graphs.imports,
|
|
41
|
+
bundle.graphs.calls,
|
|
42
|
+
bundle.graphs.references,
|
|
43
|
+
]) {
|
|
44
|
+
for (const edge of bucket ?? []) {
|
|
45
|
+
if (confidenceOf(edge) < floor) {
|
|
46
|
+
candidates.push(edge);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return candidates
|
|
51
|
+
.sort((a, b) => edgeSignature(a).localeCompare(edgeSignature(b)))
|
|
52
|
+
.slice(0, MAX_REASONED_EDGES);
|
|
53
|
+
}
|
|
54
|
+
/** Stable content hash of the candidate edge set, for host-side call caching. */
|
|
55
|
+
export function edgeReasoningContentHash(candidates) {
|
|
56
|
+
const basis = JSON.stringify({
|
|
57
|
+
version: EDGE_REASONING_VERSION,
|
|
58
|
+
edges: candidates.map((edge) => ({
|
|
59
|
+
from: edge.from,
|
|
60
|
+
to: edge.to,
|
|
61
|
+
kind: edge.kind ?? "",
|
|
62
|
+
confidence: confidenceOf(edge),
|
|
63
|
+
reason: edge.reason ?? "",
|
|
64
|
+
})),
|
|
65
|
+
});
|
|
66
|
+
return createHash("sha256").update(basis).digest("hex");
|
|
67
|
+
}
|
|
68
|
+
/** The single bounded prompt a host runs to produce {@link EdgeReasoningResults}. */
|
|
69
|
+
export function buildEdgeReasoningPrompt(candidates) {
|
|
70
|
+
const lines = candidates.map((edge) => `- from: ${edge.from} | to: ${edge.to} | kind: ${edge.kind ?? "?"} | confidence: ${confidenceOf(edge).toFixed(2)} | current: ${edge.reason ?? "(none)"}`);
|
|
71
|
+
return [
|
|
72
|
+
"You are improving the human-readable 'reason' for low-confidence edges in a code dependency graph.",
|
|
73
|
+
"Each edge links a source file to a target file by a relationship 'kind'.",
|
|
74
|
+
"For each edge you can improve, write one clear, specific sentence explaining why that relationship plausibly holds.",
|
|
75
|
+
"Do NOT invent new edges, drop edges, or change which files are linked — only rewrite the reason text.",
|
|
76
|
+
"Omit any edge whose reason you cannot improve.",
|
|
77
|
+
"",
|
|
78
|
+
"Edges:",
|
|
79
|
+
...lines,
|
|
80
|
+
"",
|
|
81
|
+
'Respond with JSON only: {"rewrites":[{"from":"...","to":"...","kind":"...","reason":"..."}]}',
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Apply host-supplied reason rewrites to `bundle` (mutated in place). Only edges
|
|
86
|
+
* below the confidence floor are eligible; a rewrite that matches no eligible
|
|
87
|
+
* edge is ignored. Returns a summary; the edge set itself is invariant.
|
|
88
|
+
*/
|
|
89
|
+
export function applyEdgeReasoning(bundle, results, options = {}) {
|
|
90
|
+
const floor = options.confidenceFloor ?? DEFAULT_EDGE_CONFIDENCE_FLOOR;
|
|
91
|
+
const candidates = collectLowConfidenceEdges(bundle, floor);
|
|
92
|
+
if (!results || !Array.isArray(results.rewrites) || candidates.length === 0) {
|
|
93
|
+
return { rewritten: 0, candidates: candidates.length };
|
|
94
|
+
}
|
|
95
|
+
const bySignature = new Map();
|
|
96
|
+
const byEndpoints = new Map();
|
|
97
|
+
for (const edge of candidates) {
|
|
98
|
+
bySignature.set(edgeSignature(edge), edge);
|
|
99
|
+
const endpoints = `${edge.from}\0${edge.to}`;
|
|
100
|
+
if (!byEndpoints.has(endpoints)) {
|
|
101
|
+
byEndpoints.set(endpoints, edge);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let rewritten = 0;
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
for (const rewrite of results.rewrites) {
|
|
107
|
+
if (!rewrite ||
|
|
108
|
+
typeof rewrite.from !== "string" ||
|
|
109
|
+
typeof rewrite.to !== "string" ||
|
|
110
|
+
typeof rewrite.reason !== "string" ||
|
|
111
|
+
rewrite.reason.trim().length === 0) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const edge = rewrite.kind !== undefined
|
|
115
|
+
? bySignature.get(`${rewrite.from}\0${rewrite.to}\0${rewrite.kind}`)
|
|
116
|
+
: byEndpoints.get(`${rewrite.from}\0${rewrite.to}`);
|
|
117
|
+
if (!edge || seen.has(edge)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
edge.reason = rewrite.reason.trim();
|
|
121
|
+
seen.add(edge);
|
|
122
|
+
rewritten += 1;
|
|
123
|
+
}
|
|
124
|
+
return { rewritten, candidates: candidates.length };
|
|
125
|
+
}
|
|
@@ -9,6 +9,11 @@ export const EXECUTOR_REGISTRY = [
|
|
|
9
9
|
obligation_ids: ["structure_artifacts"],
|
|
10
10
|
description: "Build structure artifacts such as units, surfaces, graphs, flows, and risk.",
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
id: "graph_enrichment_executor",
|
|
14
|
+
obligation_ids: ["graph_enrichment_current"],
|
|
15
|
+
description: "Layer optional language-analyzer edges onto the deterministic graph (regex floor preserved); record analyzer provenance.",
|
|
16
|
+
},
|
|
12
17
|
{
|
|
13
18
|
id: "design_assessment_executor",
|
|
14
19
|
obligation_ids: ["design_assessment_current"],
|
|
@@ -42,7 +47,12 @@ export const EXECUTOR_REGISTRY = [
|
|
|
42
47
|
{
|
|
43
48
|
id: "synthesis_executor",
|
|
44
49
|
obligation_ids: ["synthesis_current"],
|
|
45
|
-
description: "
|
|
50
|
+
description: "Emit the canonical audit-findings.json and render the deterministic Markdown audit report.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "synthesis_narrative_executor",
|
|
54
|
+
obligation_ids: ["synthesis_narrative_current"],
|
|
55
|
+
description: "Resolve the optional synthesis narrative (themes, executive summary, top risks); omit deterministically without a provider.",
|
|
46
56
|
},
|
|
47
57
|
{
|
|
48
58
|
id: "external_analyzer_import_executor",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
|
+
import type { ExecutorRunResult } from "./internalExecutors.js";
|
|
3
|
+
import type { AnalyzerSetting } from "@audit-tools/shared";
|
|
4
|
+
import type { LanguageAnalyzer } from "../extractors/analyzers/types.js";
|
|
5
|
+
import { type EdgeReasoningResults } from "./edgeReasoning.js";
|
|
6
|
+
export interface GraphEnrichmentOptions {
|
|
7
|
+
root?: string;
|
|
8
|
+
analyzers?: Record<string, AnalyzerSetting>;
|
|
9
|
+
/** Injectable for tests; defaults to the global registry. */
|
|
10
|
+
registry?: LanguageAnalyzer[];
|
|
11
|
+
/** Injectable analyzer-cache root; defaults to ~/.audit-tools/analyzer-cache. */
|
|
12
|
+
cacheRoot?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Phase 4B: gate for the optional edge-reasoning pass (mirrors
|
|
15
|
+
* session-config `graph.llm_edge_reasoning`; default off).
|
|
16
|
+
*/
|
|
17
|
+
llmEdgeReasoning?: boolean;
|
|
18
|
+
/** Phase 4B: host-supplied reason rewrites for low-confidence edges. */
|
|
19
|
+
edgeReasoning?: EdgeReasoningResults;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the optional graph-enrichment obligation. Layers language-analyzer
|
|
23
|
+
* edges onto the deterministic regex floor in `graph_bundle.json`
|
|
24
|
+
* (higher-confidence-kind-wins) and records provenance in
|
|
25
|
+
* `analyzer_capability.json`. With no root, or when every analyzer skips / is
|
|
26
|
+
* absent / not-applicable, the graph bundle is left byte-identical to the floor
|
|
27
|
+
* and only the marker is written.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runGraphEnrichmentExecutor(bundle: ArtifactBundle, options?: GraphEnrichmentOptions): Promise<ExecutorRunResult>;
|