auditor-lambda 0.2.3 → 0.2.4
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.js +7 -1
- package/dist/extractors/pathPatterns.js +2 -8
- package/dist/io/artifacts.d.ts +1 -0
- package/dist/io/artifacts.js +37 -0
- package/dist/orchestrator/nextStep.js +10 -0
- package/dist/reporting/synthesis.d.ts +3 -0
- package/dist/reporting/synthesis.js +4 -0
- package/dist/reporting/workBlocks.d.ts +9 -0
- package/dist/reporting/workBlocks.js +109 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { buildUnitManifest } from "./orchestrator/unitBuilder.js";
|
|
|
9
9
|
import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
|
|
10
10
|
import { buildRuntimeValidationTasks, buildPlaceholderRuntimeValidationReport, } from "./orchestrator/runtimeValidation.js";
|
|
11
11
|
import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
|
|
12
|
-
import { loadArtifactBundle, writeCoreArtifacts, } from "./io/artifacts.js";
|
|
12
|
+
import { loadArtifactBundle, writeCoreArtifacts, cleanupIntermediateArtifacts, } from "./io/artifacts.js";
|
|
13
13
|
import { readJsonFile, writeJsonFile } from "./io/json.js";
|
|
14
14
|
import { validateArtifactBundle } from "./validation/artifacts.js";
|
|
15
15
|
import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
|
|
@@ -345,6 +345,9 @@ async function cmdAdvanceAudit(argv) {
|
|
|
345
345
|
next_likely_step: result.next_likely_step,
|
|
346
346
|
providerName,
|
|
347
347
|
});
|
|
348
|
+
if (result.audit_state.status === "complete") {
|
|
349
|
+
await cleanupIntermediateArtifacts(artifactsDir);
|
|
350
|
+
}
|
|
348
351
|
}
|
|
349
352
|
async function cmdRunToCompletion(argv) {
|
|
350
353
|
const root = getRootDir(argv);
|
|
@@ -495,6 +498,9 @@ async function cmdRunToCompletion(argv) {
|
|
|
495
498
|
next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
|
|
496
499
|
providerName: provider.name,
|
|
497
500
|
});
|
|
501
|
+
if (state.status === "complete") {
|
|
502
|
+
await cleanupIntermediateArtifacts(artifactsDir);
|
|
503
|
+
}
|
|
498
504
|
return;
|
|
499
505
|
}
|
|
500
506
|
if (preferredExecutor === "agent" && parallelWorkers > 1) {
|
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
* already-lower-cased form of the path.
|
|
5
5
|
*/
|
|
6
6
|
export function isNodeModulesOrGit(normalized) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
normalized.includes("/node_modules/") ||
|
|
10
|
-
normalized.endsWith("/node_modules") ||
|
|
11
|
-
normalized === ".git" ||
|
|
12
|
-
normalized.startsWith(".git/") ||
|
|
13
|
-
normalized.includes("/.git/") ||
|
|
14
|
-
normalized.endsWith("/.git"));
|
|
7
|
+
const segments = normalized.split(/[/\\]/);
|
|
8
|
+
return segments.includes("node_modules") || segments.includes(".git");
|
|
15
9
|
}
|
|
16
10
|
export function isBuildOutput(normalized) {
|
|
17
11
|
return normalized.startsWith("dist/") || normalized.startsWith("build/");
|
package/dist/io/artifacts.d.ts
CHANGED
|
@@ -58,3 +58,4 @@ export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: {
|
|
|
58
58
|
export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
|
|
59
59
|
export declare function loadArtifactBundle(root: string): Promise<ArtifactBundle>;
|
|
60
60
|
export declare function writeCoreArtifacts(root: string, bundle: ArtifactBundle): Promise<void>;
|
|
61
|
+
export declare function cleanupIntermediateArtifacts(root: string): Promise<string[]>;
|
package/dist/io/artifacts.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { unlink } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { writeJsonFile, readOptionalJsonFile, readOptionalNdjsonFile, writeNdjsonFile, } from "./json.js";
|
|
2
4
|
export const ARTIFACT_FILE_TO_BUNDLE_KEY = {
|
|
3
5
|
"repo_manifest.json": "repo_manifest",
|
|
@@ -100,3 +102,38 @@ export async function writeCoreArtifacts(root, bundle) {
|
|
|
100
102
|
if (bundle.artifact_metadata)
|
|
101
103
|
await writeJsonFile(`${root}/artifact_metadata.json`, bundle.artifact_metadata);
|
|
102
104
|
}
|
|
105
|
+
// Intermediate files deleted after synthesis completes. The final outputs
|
|
106
|
+
// (synthesis_report.json, merged_findings.json, root_cause_clusters.json,
|
|
107
|
+
// audit_state.json) are retained.
|
|
108
|
+
const INTERMEDIATE_ARTIFACTS = [
|
|
109
|
+
"repo_manifest.json",
|
|
110
|
+
"file_disposition.json",
|
|
111
|
+
"auto_fixes_applied.json",
|
|
112
|
+
"unit_manifest.json",
|
|
113
|
+
"graph_bundle.json",
|
|
114
|
+
"surface_manifest.json",
|
|
115
|
+
"critical_flows.json",
|
|
116
|
+
"flow_coverage.json",
|
|
117
|
+
"risk_register.json",
|
|
118
|
+
"coverage_matrix.json",
|
|
119
|
+
"runtime_validation_tasks.json",
|
|
120
|
+
"runtime_validation_report.json",
|
|
121
|
+
"external_analyzer_results.json",
|
|
122
|
+
"audit_results.jsonl",
|
|
123
|
+
"audit_tasks.json",
|
|
124
|
+
"requeue_tasks.json",
|
|
125
|
+
"artifact_metadata.json",
|
|
126
|
+
];
|
|
127
|
+
export async function cleanupIntermediateArtifacts(root) {
|
|
128
|
+
const deleted = [];
|
|
129
|
+
for (const name of INTERMEDIATE_ARTIFACTS) {
|
|
130
|
+
try {
|
|
131
|
+
await unlink(join(root, name));
|
|
132
|
+
deleted.push(name);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// file absent — nothing to delete
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return deleted;
|
|
139
|
+
}
|
|
@@ -22,6 +22,16 @@ function findObligation(obligations) {
|
|
|
22
22
|
return undefined;
|
|
23
23
|
}
|
|
24
24
|
export function decideNextStep(bundle) {
|
|
25
|
+
// After intermediate artifacts are cleaned up, trust the persisted complete
|
|
26
|
+
// state so re-runs don't attempt to rebuild from an empty bundle.
|
|
27
|
+
if (bundle.audit_state?.status === "complete") {
|
|
28
|
+
return {
|
|
29
|
+
state: bundle.audit_state,
|
|
30
|
+
selected_obligation: null,
|
|
31
|
+
selected_executor: null,
|
|
32
|
+
reason: "All known obligations are currently satisfied.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
25
35
|
const state = deriveAuditState(bundle);
|
|
26
36
|
const next = findObligation(state.obligations);
|
|
27
37
|
if (!next) {
|
|
@@ -3,10 +3,12 @@ import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
|
3
3
|
import type { RuntimeValidationReport } from "../types/runtimeValidation.js";
|
|
4
4
|
import { mergeFindings } from "./mergeFindings.js";
|
|
5
5
|
import { buildRootCauseClusters } from "./rootCause.js";
|
|
6
|
+
import { buildWorkBlocks } from "./workBlocks.js";
|
|
6
7
|
export interface SynthesisReport {
|
|
7
8
|
summary: {
|
|
8
9
|
finding_count: number;
|
|
9
10
|
cluster_count: number;
|
|
11
|
+
work_block_count: number;
|
|
10
12
|
runtime_validation_status_breakdown: Record<string, number>;
|
|
11
13
|
notes: string[];
|
|
12
14
|
severity_breakdown: Record<string, number>;
|
|
@@ -17,5 +19,6 @@ export interface SynthesisReport {
|
|
|
17
19
|
};
|
|
18
20
|
merged_findings: ReturnType<typeof mergeFindings>;
|
|
19
21
|
root_cause_clusters: ReturnType<typeof buildRootCauseClusters>;
|
|
22
|
+
work_blocks: ReturnType<typeof buildWorkBlocks>;
|
|
20
23
|
}
|
|
21
24
|
export declare function buildSynthesisReport(results: AuditResult[], runtimeReport?: RuntimeValidationReport, externalAnalyzerResults?: ExternalAnalyzerResults): SynthesisReport;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mergeFindings } from "./mergeFindings.js";
|
|
2
2
|
import { buildRootCauseClusters } from "./rootCause.js";
|
|
3
|
+
import { buildWorkBlocks } from "./workBlocks.js";
|
|
3
4
|
function statusBreakdown(report) {
|
|
4
5
|
const breakdown = {};
|
|
5
6
|
for (const result of report?.results ?? []) {
|
|
@@ -43,6 +44,7 @@ function externalSummary(results) {
|
|
|
43
44
|
export function buildSynthesisReport(results, runtimeReport, externalAnalyzerResults) {
|
|
44
45
|
const merged_findings = mergeFindings(results, runtimeReport, externalAnalyzerResults);
|
|
45
46
|
const root_cause_clusters = buildRootCauseClusters(merged_findings, runtimeReport, externalAnalyzerResults);
|
|
47
|
+
const work_blocks = buildWorkBlocks(merged_findings);
|
|
46
48
|
const runtimeBreakdown = statusBreakdown(runtimeReport);
|
|
47
49
|
const sevBreakdown = severityBreakdown(merged_findings);
|
|
48
50
|
const extSummary = externalSummary(externalAnalyzerResults);
|
|
@@ -50,6 +52,7 @@ export function buildSynthesisReport(results, runtimeReport, externalAnalyzerRes
|
|
|
50
52
|
summary: {
|
|
51
53
|
finding_count: merged_findings.length,
|
|
52
54
|
cluster_count: root_cause_clusters.length,
|
|
55
|
+
work_block_count: work_blocks.length,
|
|
53
56
|
runtime_validation_status_breakdown: runtimeBreakdown,
|
|
54
57
|
severity_breakdown: sevBreakdown,
|
|
55
58
|
external_analyzer_summary: extSummary,
|
|
@@ -69,5 +72,6 @@ export function buildSynthesisReport(results, runtimeReport, externalAnalyzerRes
|
|
|
69
72
|
},
|
|
70
73
|
merged_findings,
|
|
71
74
|
root_cause_clusters,
|
|
75
|
+
work_blocks,
|
|
72
76
|
};
|
|
73
77
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Finding } from "../types.js";
|
|
2
|
+
export interface WorkBlock {
|
|
3
|
+
id: string;
|
|
4
|
+
finding_ids: string[];
|
|
5
|
+
affected_files: string[];
|
|
6
|
+
max_severity: Finding["severity"];
|
|
7
|
+
rationale: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildWorkBlocks(findings: Finding[]): WorkBlock[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
function severityRank(severity) {
|
|
2
|
+
switch (severity) {
|
|
3
|
+
case "critical":
|
|
4
|
+
return 5;
|
|
5
|
+
case "high":
|
|
6
|
+
return 4;
|
|
7
|
+
case "medium":
|
|
8
|
+
return 3;
|
|
9
|
+
case "low":
|
|
10
|
+
return 2;
|
|
11
|
+
case "info":
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function buildWorkBlocks(findings) {
|
|
16
|
+
if (findings.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const parent = new Map();
|
|
19
|
+
function find(id) {
|
|
20
|
+
if (!parent.has(id))
|
|
21
|
+
parent.set(id, id);
|
|
22
|
+
const p = parent.get(id);
|
|
23
|
+
if (p === id)
|
|
24
|
+
return id;
|
|
25
|
+
const root = find(p);
|
|
26
|
+
parent.set(id, root);
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
function union(a, b) {
|
|
30
|
+
const ra = find(a);
|
|
31
|
+
const rb = find(b);
|
|
32
|
+
if (ra !== rb)
|
|
33
|
+
parent.set(ra, rb);
|
|
34
|
+
}
|
|
35
|
+
for (const finding of findings)
|
|
36
|
+
find(finding.id);
|
|
37
|
+
// Union findings that share affected files
|
|
38
|
+
const fileToIds = new Map();
|
|
39
|
+
for (const finding of findings) {
|
|
40
|
+
for (const af of finding.affected_files) {
|
|
41
|
+
const ids = fileToIds.get(af.path) ?? [];
|
|
42
|
+
ids.push(finding.id);
|
|
43
|
+
fileToIds.set(af.path, ids);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const ids of fileToIds.values()) {
|
|
47
|
+
for (let i = 1; i < ids.length; i++) {
|
|
48
|
+
union(ids[0], ids[i]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Union findings explicitly linked via related_findings
|
|
52
|
+
const knownIds = new Set(findings.map((f) => f.id));
|
|
53
|
+
for (const finding of findings) {
|
|
54
|
+
for (const relId of finding.related_findings ?? []) {
|
|
55
|
+
if (knownIds.has(relId))
|
|
56
|
+
union(finding.id, relId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Collect connected components
|
|
60
|
+
const groups = new Map();
|
|
61
|
+
for (const finding of findings) {
|
|
62
|
+
const root = find(finding.id);
|
|
63
|
+
const group = groups.get(root) ?? [];
|
|
64
|
+
group.push(finding);
|
|
65
|
+
groups.set(root, group);
|
|
66
|
+
}
|
|
67
|
+
const blocks = [];
|
|
68
|
+
for (const group of groups.values()) {
|
|
69
|
+
// Within a block: severity desc, systemic first, then title
|
|
70
|
+
const sorted = [...group].sort((a, b) => {
|
|
71
|
+
const sevDelta = severityRank(b.severity) - severityRank(a.severity);
|
|
72
|
+
if (sevDelta !== 0)
|
|
73
|
+
return sevDelta;
|
|
74
|
+
const sysA = a.systemic ? 1 : 0;
|
|
75
|
+
const sysB = b.systemic ? 1 : 0;
|
|
76
|
+
if (sysA !== sysB)
|
|
77
|
+
return sysB - sysA;
|
|
78
|
+
return a.title.localeCompare(b.title);
|
|
79
|
+
});
|
|
80
|
+
const affectedFiles = [
|
|
81
|
+
...new Set(sorted.flatMap((f) => f.affected_files.map((af) => af.path))),
|
|
82
|
+
].sort();
|
|
83
|
+
const maxSeverity = sorted[0].severity;
|
|
84
|
+
const rationale = affectedFiles.length === 1
|
|
85
|
+
? `${sorted.length} finding(s) affect the same file — fix together to avoid conflicts.`
|
|
86
|
+
: `${sorted.length} finding(s) share ${affectedFiles.length} file(s) or are linked via related_findings — fix as a unit so one change does not invalidate another.`;
|
|
87
|
+
blocks.push({
|
|
88
|
+
id: "",
|
|
89
|
+
finding_ids: sorted.map((f) => f.id),
|
|
90
|
+
affected_files: affectedFiles,
|
|
91
|
+
max_severity: maxSeverity,
|
|
92
|
+
rationale,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Sort blocks: highest severity first, then largest group, then stable by first finding id
|
|
96
|
+
blocks.sort((a, b) => {
|
|
97
|
+
const sevDelta = severityRank(b.max_severity) - severityRank(a.max_severity);
|
|
98
|
+
if (sevDelta !== 0)
|
|
99
|
+
return sevDelta;
|
|
100
|
+
const lenDelta = b.finding_ids.length - a.finding_ids.length;
|
|
101
|
+
if (lenDelta !== 0)
|
|
102
|
+
return lenDelta;
|
|
103
|
+
return (a.finding_ids[0] ?? "").localeCompare(b.finding_ids[0] ?? "");
|
|
104
|
+
});
|
|
105
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
106
|
+
blocks[i].id = `block-${i + 1}`;
|
|
107
|
+
}
|
|
108
|
+
return blocks;
|
|
109
|
+
}
|