auditor-lambda 0.2.8 → 0.2.9
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/README.md +6 -0
- package/audit-code-wrapper-lib.mjs +1 -1
- package/dist/adapters/eslint.js +9 -5
- package/dist/cli.d.ts +42 -1
- package/dist/cli.js +114 -64
- package/dist/extractors/bucketing.d.ts +4 -0
- package/dist/extractors/bucketing.js +6 -2
- package/dist/extractors/disposition.d.ts +4 -0
- package/dist/extractors/disposition.js +6 -2
- package/dist/extractors/fileInventory.js +24 -28
- package/dist/extractors/flows.d.ts +5 -0
- package/dist/extractors/flows.js +18 -38
- package/dist/extractors/pathPatterns.d.ts +10 -3
- package/dist/extractors/pathPatterns.js +109 -61
- package/dist/extractors/surfaces.d.ts +4 -0
- package/dist/extractors/surfaces.js +11 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/io/artifacts.d.ts +55 -40
- package/dist/io/artifacts.js +73 -110
- package/dist/io/json.js +52 -21
- package/dist/io/runArtifacts.d.ts +1 -1
- package/dist/io/runArtifacts.js +26 -3
- package/dist/orchestrator/advance.js +83 -62
- package/dist/orchestrator/flowCoverage.js +11 -5
- package/dist/orchestrator/flowPlanning.d.ts +7 -2
- package/dist/orchestrator/flowPlanning.js +46 -21
- package/dist/orchestrator/flowRequeue.js +28 -8
- package/dist/orchestrator/internalExecutors.js +12 -8
- package/dist/orchestrator/planning.js +25 -3
- package/dist/orchestrator/requeue.js +11 -1
- package/dist/orchestrator/taskBuilder.d.ts +4 -2
- package/dist/orchestrator/taskBuilder.js +153 -52
- package/dist/orchestrator/unitBuilder.d.ts +3 -1
- package/dist/orchestrator/unitBuilder.js +24 -16
- package/dist/prompts/renderWorkerPrompt.d.ts +1 -1
- package/dist/prompts/renderWorkerPrompt.js +16 -8
- package/dist/providers/claudeCodeProvider.d.ts +4 -1
- package/dist/providers/claudeCodeProvider.js +8 -5
- package/dist/providers/localSubprocessProvider.d.ts +4 -0
- package/dist/providers/localSubprocessProvider.js +7 -2
- package/dist/providers/spawnLoggedCommand.d.ts +9 -1
- package/dist/providers/spawnLoggedCommand.js +77 -29
- package/dist/reporting/synthesis.d.ts +2 -0
- package/dist/reporting/synthesis.js +12 -9
- package/dist/supervisor/operatorHandoff.js +48 -18
- package/dist/supervisor/runLedger.d.ts +1 -1
- package/dist/supervisor/runLedger.js +112 -5
- package/dist/supervisor/sessionConfig.js +10 -10
- package/dist/types/externalAnalyzer.d.ts +3 -0
- package/dist/types/flowCoverage.d.ts +5 -1
- package/dist/types/flowCoverage.js +5 -1
- package/dist/types/flows.d.ts +5 -1
- package/dist/types/flows.js +1 -1
- package/dist/types/runLedger.d.ts +5 -1
- package/dist/types/runLedger.js +6 -1
- package/dist/types/runtimeValidation.d.ts +12 -3
- package/dist/types/runtimeValidation.js +16 -1
- package/dist/types/sessionConfig.d.ts +15 -2
- package/dist/types/sessionConfig.js +15 -1
- package/dist/types/surfaces.d.ts +4 -1
- package/dist/types/surfaces.js +1 -1
- package/dist/types/workerSession.d.ts +9 -0
- package/dist/types/workerSession.js +5 -1
- package/dist/validation/artifacts.d.ts +1 -1
- package/dist/validation/artifacts.js +33 -20
- package/dist/validation/auditResults.d.ts +2 -2
- package/dist/validation/auditResults.js +7 -15
- package/dist/validation/basic.d.ts +9 -1
- package/dist/validation/basic.js +40 -3
- package/dist/validation/sessionConfig.d.ts +4 -2
- package/dist/validation/sessionConfig.js +62 -15
- package/docs/agent-integrations.md +29 -9
- package/docs/next-steps.md +21 -4
- package/docs/packaging.md +14 -0
- package/docs/product-direction.md +22 -0
- package/docs/production-launch-bar.md +2 -0
- package/docs/releasing.md +17 -0
- package/docs/remediation-baseline.md +75 -0
- package/docs/run-flow.md +23 -11
- package/docs/session-config.md +50 -5
- package/docs/supervisor.md +7 -0
- package/docs/workflow-refactor-brief.md +177 -0
- package/package.json +1 -1
- package/schemas/audit_result.schema.json +4 -1
- package/schemas/audit_task.schema.json +3 -1
- package/schemas/coverage_matrix.schema.json +3 -3
- package/schemas/critical_flows.schema.json +6 -2
- package/schemas/file_disposition.schema.json +2 -2
- package/schemas/finding.schema.json +9 -4
- package/schemas/flow_coverage.schema.json +2 -2
- package/schemas/repo_manifest.schema.json +4 -4
- package/schemas/risk_register.schema.json +2 -2
- package/schemas/runtime_validation_report.schema.json +2 -2
- package/schemas/runtime_validation_tasks.schema.json +8 -2
- package/schemas/surface_manifest.schema.json +6 -3
- package/schemas/unit_manifest.schema.json +3 -2
- package/skills/audit-code/SKILL.md +5 -0
|
@@ -3,33 +3,58 @@ const DEFAULT_FLOW_LENS_PRIORITY = [
|
|
|
3
3
|
"reliability",
|
|
4
4
|
"correctness",
|
|
5
5
|
];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
6
|
+
function lensPathKey(lens, path) {
|
|
7
|
+
return `${lens}:${path}`;
|
|
8
|
+
}
|
|
9
|
+
function flowLensPriority(lens) {
|
|
10
|
+
const index = DEFAULT_FLOW_LENS_PRIORITY.indexOf(lens);
|
|
11
|
+
return index >= 0 ? index : DEFAULT_FLOW_LENS_PRIORITY.length;
|
|
12
|
+
}
|
|
13
|
+
export function claimFlowReviewBlocks(criticalFlows, pendingByLens, assigned) {
|
|
14
|
+
const candidates = [];
|
|
12
15
|
for (const flow of criticalFlows.flows) {
|
|
13
|
-
const
|
|
16
|
+
const flowPaths = [...new Set(flow.paths)].sort((a, b) => a.localeCompare(b));
|
|
17
|
+
const desiredLenses = flow.concerns
|
|
18
|
+
.filter((concern) => DEFAULT_FLOW_LENS_PRIORITY.includes(concern))
|
|
19
|
+
.sort((a, b) => flowLensPriority(a) - flowLensPriority(b));
|
|
14
20
|
for (const lens of desiredLenses) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
const pendingPaths = pendingByLens.get(lens);
|
|
22
|
+
if (!pendingPaths || pendingPaths.size === 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const filePaths = flowPaths.filter((path) => pendingPaths.has(path));
|
|
26
|
+
if (filePaths.length === 0) {
|
|
20
27
|
continue;
|
|
21
28
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
unit_id: `flow:${flow.id}`,
|
|
25
|
-
pass_id: `flow-pass:${lens}`,
|
|
29
|
+
candidates.push({
|
|
30
|
+
flow_id: flow.id,
|
|
26
31
|
lens,
|
|
27
|
-
file_paths:
|
|
28
|
-
rationale: `Flow-aware audit for critical flow "${flow.id}" (${flow.paths.length} file${flow.paths.length === 1 ? "" : "s"}) under the ${lens} lens.`,
|
|
32
|
+
file_paths: filePaths,
|
|
29
33
|
});
|
|
30
|
-
seenTaskIds.add(taskId);
|
|
31
|
-
existingSignatures.add(signature);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
|
|
36
|
+
candidates.sort((a, b) => {
|
|
37
|
+
const sizeDelta = b.file_paths.length - a.file_paths.length;
|
|
38
|
+
if (sizeDelta !== 0)
|
|
39
|
+
return sizeDelta;
|
|
40
|
+
const lensDelta = flowLensPriority(a.lens) - flowLensPriority(b.lens);
|
|
41
|
+
if (lensDelta !== 0)
|
|
42
|
+
return lensDelta;
|
|
43
|
+
return a.flow_id.localeCompare(b.flow_id);
|
|
44
|
+
});
|
|
45
|
+
const blocks = [];
|
|
46
|
+
for (const candidate of candidates) {
|
|
47
|
+
const unclaimedPaths = candidate.file_paths.filter((path) => !assigned.has(lensPathKey(candidate.lens, path)));
|
|
48
|
+
if (unclaimedPaths.length === 0) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const path of unclaimedPaths) {
|
|
52
|
+
assigned.add(lensPathKey(candidate.lens, path));
|
|
53
|
+
}
|
|
54
|
+
blocks.push({
|
|
55
|
+
...candidate,
|
|
56
|
+
file_paths: unclaimedPaths,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return blocks;
|
|
35
60
|
}
|
|
@@ -10,7 +10,17 @@ function isLens(value) {
|
|
|
10
10
|
"tests",
|
|
11
11
|
"operability",
|
|
12
12
|
"config_deployment",
|
|
13
|
-
].includes(value);
|
|
13
|
+
].includes(String(value));
|
|
14
|
+
}
|
|
15
|
+
function getExternalSignalPaths(externalAnalyzerResults) {
|
|
16
|
+
const results = Array.isArray(externalAnalyzerResults?.results)
|
|
17
|
+
? externalAnalyzerResults.results
|
|
18
|
+
: [];
|
|
19
|
+
return new Set(results
|
|
20
|
+
.map((item) => item && typeof item.path === "string" && item.path.length > 0
|
|
21
|
+
? item.path
|
|
22
|
+
: null)
|
|
23
|
+
.filter((path) => path !== null));
|
|
14
24
|
}
|
|
15
25
|
function taskPriority(hasExternalSignal, lens) {
|
|
16
26
|
if (hasExternalSignal &&
|
|
@@ -19,8 +29,8 @@ function taskPriority(hasExternalSignal, lens) {
|
|
|
19
29
|
}
|
|
20
30
|
return hasExternalSignal ? "medium" : "low";
|
|
21
31
|
}
|
|
22
|
-
function fileStillNeedsLens(
|
|
23
|
-
const record =
|
|
32
|
+
function fileStillNeedsLens(coverageByPath, path, lens) {
|
|
33
|
+
const record = coverageByPath.get(path);
|
|
24
34
|
if (!record || record.audit_status === "excluded") {
|
|
25
35
|
return false;
|
|
26
36
|
}
|
|
@@ -28,21 +38,31 @@ function fileStillNeedsLens(coverageMatrix, path, lens) {
|
|
|
28
38
|
}
|
|
29
39
|
export function buildFlowRequeueTasks(criticalFlows, flowCoverage, coverageMatrix, externalAnalyzerResults) {
|
|
30
40
|
const flowMap = new Map(criticalFlows.flows.map((flow) => [flow.id, flow]));
|
|
41
|
+
const coverageByPath = new Map(coverageMatrix.files.map((file) => [file.path, file]));
|
|
31
42
|
const tasks = [];
|
|
32
43
|
const seen = new Set();
|
|
33
|
-
const externalPaths =
|
|
44
|
+
const externalPaths = getExternalSignalPaths(externalAnalyzerResults);
|
|
34
45
|
for (const record of flowCoverage.flows) {
|
|
35
46
|
const flow = flowMap.get(record.flow_id);
|
|
36
47
|
if (!flow) {
|
|
37
48
|
continue;
|
|
38
49
|
}
|
|
39
|
-
const
|
|
50
|
+
const requiredLenses = Array.isArray(record.required_lenses)
|
|
51
|
+
? record.required_lenses.filter((lens) => typeof lens === "string")
|
|
52
|
+
: [];
|
|
53
|
+
const completedLenses = new Set(Array.isArray(record.completed_lenses)
|
|
54
|
+
? record.completed_lenses.filter((lens) => typeof lens === "string")
|
|
55
|
+
: []);
|
|
56
|
+
const missingLenses = requiredLenses.filter((lens) => !completedLenses.has(lens));
|
|
57
|
+
const flowPaths = Array.isArray(flow.paths)
|
|
58
|
+
? flow.paths.filter((path) => typeof path === "string")
|
|
59
|
+
: [];
|
|
40
60
|
for (const lensName of missingLenses) {
|
|
41
61
|
if (!isLens(lensName)) {
|
|
42
|
-
|
|
62
|
+
throw new Error(`buildFlowRequeueTasks encountered unsupported lens "${String(lensName)}" for flow ${record.flow_id}.`);
|
|
43
63
|
}
|
|
44
|
-
for (const path of
|
|
45
|
-
if (!fileStillNeedsLens(
|
|
64
|
+
for (const path of flowPaths) {
|
|
65
|
+
if (!fileStillNeedsLens(coverageByPath, path, lensName)) {
|
|
46
66
|
continue;
|
|
47
67
|
}
|
|
48
68
|
const signature = `${flow.id}|${lensName}|${path}`;
|
|
@@ -6,16 +6,16 @@ import { buildRiskRegister } from "../extractors/risk.js";
|
|
|
6
6
|
import { buildSurfaceManifest } from "../extractors/surfaces.js";
|
|
7
7
|
import { initializeCoverageFromPlan } from "./planning.js";
|
|
8
8
|
import { buildFlowCoverage } from "./flowCoverage.js";
|
|
9
|
-
import { buildFlowAwareTaskAugmentations } from "./flowPlanning.js";
|
|
10
9
|
import { buildRequeuePayload } from "./requeueCommand.js";
|
|
11
10
|
import { buildRuntimeValidationTasks, discoverRuntimeValidationCommand, mergeRuntimeValidationReport, } from "./runtimeValidation.js";
|
|
12
11
|
import { buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
|
|
13
|
-
import { buildChunkedAuditTasks,
|
|
12
|
+
import { buildChunkedAuditTasks, } from "./taskBuilder.js";
|
|
14
13
|
import { buildUnitManifest } from "./unitBuilder.js";
|
|
15
14
|
import { buildRepoManifestFromFs } from "../extractors/fsIntake.js";
|
|
16
15
|
import { loadIgnoreFile } from "../extractors/ignore.js";
|
|
17
16
|
import { ingestAuditResults, updateAuditTaskStatuses, } from "./resultIngestion.js";
|
|
18
17
|
import { updateRuntimeValidationReport } from "./runtimeValidationUpdate.js";
|
|
18
|
+
import { autoCompleteTrivialCoverage } from "./trivialAudit.js";
|
|
19
19
|
async function runCommand(command, cwd) {
|
|
20
20
|
return await new Promise((resolve) => {
|
|
21
21
|
const child = spawn(command[0], command.slice(1), {
|
|
@@ -117,6 +117,7 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
117
117
|
}
|
|
118
118
|
const externalAnalyzerResults = bundle.external_analyzer_results;
|
|
119
119
|
const coverage = initializeCoverageFromPlan(bundle.repo_manifest, bundle.unit_manifest, bundle.file_disposition, externalAnalyzerResults);
|
|
120
|
+
const skippedTrivialPaths = autoCompleteTrivialCoverage(coverage, lineIndex, externalAnalyzerResults);
|
|
120
121
|
const flowCoverage = buildFlowCoverage(bundle.critical_flows, coverage);
|
|
121
122
|
const runtimeCommand = await discoverRuntimeValidationCommand(root);
|
|
122
123
|
const runtimeValidationTasks = buildRuntimeValidationTasks({
|
|
@@ -128,12 +129,11 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
128
129
|
const runtimeValidationReport = runtimeValidationTasks.tasks.length > 0
|
|
129
130
|
? mergeRuntimeValidationReport(runtimeValidationTasks, bundle.runtime_validation_report)
|
|
130
131
|
: undefined;
|
|
131
|
-
const
|
|
132
|
+
const auditTasks = buildChunkedAuditTasks(coverage, lineIndex, {
|
|
132
133
|
external_analyzer_results: externalAnalyzerResults,
|
|
134
|
+
critical_flows: bundle.critical_flows,
|
|
133
135
|
});
|
|
134
|
-
const
|
|
135
|
-
const flowTasks = buildFlowAwareTaskAugmentations([...baseTasks, ...analyzerTasks], bundle.critical_flows, lineIndex);
|
|
136
|
-
const auditTasks = [...baseTasks, ...analyzerTasks, ...flowTasks].map((task) => ({
|
|
136
|
+
const taggedAuditTasks = auditTasks.map((task) => ({
|
|
137
137
|
...task,
|
|
138
138
|
status: task.status ?? "pending",
|
|
139
139
|
}));
|
|
@@ -145,7 +145,7 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
145
145
|
flow_coverage: flowCoverage,
|
|
146
146
|
runtime_validation_tasks: runtimeValidationTasks,
|
|
147
147
|
runtime_validation_report: runtimeValidationReport,
|
|
148
|
-
audit_tasks:
|
|
148
|
+
audit_tasks: taggedAuditTasks,
|
|
149
149
|
requeue_tasks: requeuePayload.tasks,
|
|
150
150
|
audit_report: undefined,
|
|
151
151
|
},
|
|
@@ -157,7 +157,10 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
157
157
|
"audit_tasks.json",
|
|
158
158
|
"requeue_tasks.json",
|
|
159
159
|
],
|
|
160
|
-
progress_summary: `Built planning artifacts; generated ${
|
|
160
|
+
progress_summary: `Built planning artifacts; generated ${taggedAuditTasks.length} review blocks and ${requeuePayload.task_count} requeue tasks.` +
|
|
161
|
+
(skippedTrivialPaths.length > 0
|
|
162
|
+
? ` Skipped ${skippedTrivialPaths.length} trivial path${skippedTrivialPaths.length === 1 ? "" : "s"} from semantic review.`
|
|
163
|
+
: "") +
|
|
161
164
|
(runtimeCommand
|
|
162
165
|
? ` Runtime validation will use: ${runtimeCommand.join(" ")}.`
|
|
163
166
|
: " No deterministic runtime validation command was discovered."),
|
|
@@ -265,6 +268,7 @@ export function runSynthesisExecutor(bundle, results) {
|
|
|
265
268
|
criticalFlows: bundle.critical_flows,
|
|
266
269
|
coverageMatrix: bundle.coverage_matrix,
|
|
267
270
|
runtimeValidationReport: bundle.runtime_validation_report,
|
|
271
|
+
externalAnalyzerResults: bundle.external_analyzer_results,
|
|
268
272
|
});
|
|
269
273
|
return {
|
|
270
274
|
updated: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyUnitCoverage, createCoverageMatrix, markExcludedPath, } from "../coverage.js";
|
|
2
2
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
+
import { deriveRequiredLensesForPath } from "./unitBuilder.js";
|
|
3
4
|
const CATEGORY_LENS_TABLE = [
|
|
4
5
|
[["security", "secret"], ["security", "correctness"]],
|
|
5
6
|
[["dependency", "vuln"], ["security", "config_deployment"]],
|
|
@@ -21,8 +22,17 @@ function applyAnalyzerCoverage(coverage, externalAnalyzerResults) {
|
|
|
21
22
|
if (!externalAnalyzerResults) {
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const coverageByPath = new Map(coverage.files.map((file) => [file.path, file]));
|
|
26
|
+
const results = Array.isArray(externalAnalyzerResults.results)
|
|
27
|
+
? externalAnalyzerResults.results
|
|
28
|
+
: [];
|
|
29
|
+
for (const result of results) {
|
|
30
|
+
if (!result ||
|
|
31
|
+
typeof result.path !== "string" ||
|
|
32
|
+
typeof result.category !== "string") {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const record = coverageByPath.get(result.path);
|
|
26
36
|
if (!record || record.audit_status === "excluded") {
|
|
27
37
|
continue;
|
|
28
38
|
}
|
|
@@ -44,9 +54,21 @@ export function initializeCoverageFromPlan(repoManifest, unitManifest, dispositi
|
|
|
44
54
|
markExcludedPath(coverage, file.path, status);
|
|
45
55
|
}
|
|
46
56
|
}
|
|
57
|
+
const unitIdsByPath = new Map();
|
|
47
58
|
for (const unit of unitManifest.units) {
|
|
48
59
|
for (const path of unit.files) {
|
|
49
|
-
|
|
60
|
+
const existing = unitIdsByPath.get(path) ?? [];
|
|
61
|
+
if (!existing.includes(unit.unit_id)) {
|
|
62
|
+
existing.push(unit.unit_id);
|
|
63
|
+
}
|
|
64
|
+
unitIdsByPath.set(path, existing);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const file of repoManifest.files) {
|
|
68
|
+
const unitIds = unitIdsByPath.get(file.path) ?? [];
|
|
69
|
+
const requiredLenses = deriveRequiredLensesForPath(file.path);
|
|
70
|
+
for (const unitId of unitIds) {
|
|
71
|
+
applyUnitCoverage(coverage, file.path, unitId, requiredLenses);
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
applyAnalyzerCoverage(coverage, externalAnalyzerResults);
|
|
@@ -7,10 +7,20 @@ function taskPriority(hasExternalSignal, lens) {
|
|
|
7
7
|
}
|
|
8
8
|
return "low";
|
|
9
9
|
}
|
|
10
|
+
function getExternalSignalPaths(externalAnalyzerResults) {
|
|
11
|
+
const results = Array.isArray(externalAnalyzerResults?.results)
|
|
12
|
+
? externalAnalyzerResults.results
|
|
13
|
+
: [];
|
|
14
|
+
return new Set(results
|
|
15
|
+
.map((item) => item && typeof item.path === "string" && item.path.length > 0
|
|
16
|
+
? item.path
|
|
17
|
+
: null)
|
|
18
|
+
.filter((path) => path !== null));
|
|
19
|
+
}
|
|
10
20
|
export function buildRequeueTasks(matrix, externalAnalyzerResults) {
|
|
11
21
|
const targets = buildRequeueTargets(matrix);
|
|
12
22
|
const tasks = [];
|
|
13
|
-
const externalPaths =
|
|
23
|
+
const externalPaths = getExternalSignalPaths(externalAnalyzerResults);
|
|
14
24
|
for (const target of targets) {
|
|
15
25
|
for (const lens of target.missing_lenses) {
|
|
16
26
|
const hasExternalSignal = externalPaths.has(target.path);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
|
|
2
|
-
import type { AuditTask, CoverageMatrix, Lens
|
|
2
|
+
import type { AuditTask, CoverageMatrix, Lens } from "../types.js";
|
|
3
|
+
import type { CriticalFlowManifest } from "../types/flows.js";
|
|
3
4
|
export interface UnitLineIndex {
|
|
4
5
|
[path: string]: number;
|
|
5
6
|
}
|
|
@@ -12,6 +13,7 @@ export interface BuildChunkedTaskOptions {
|
|
|
12
13
|
file_split_threshold?: number;
|
|
13
14
|
limit_lenses?: Lens[];
|
|
14
15
|
external_analyzer_results?: ExternalAnalyzerResults;
|
|
16
|
+
critical_flows?: CriticalFlowManifest;
|
|
15
17
|
}
|
|
16
|
-
export declare function buildChunkedAuditTasks(
|
|
18
|
+
export declare function buildChunkedAuditTasks(coverageMatrix: CoverageMatrix, unitLineIndex: UnitLineIndex, options?: BuildChunkedTaskOptions): AuditTask[];
|
|
17
19
|
export declare function buildExternalSignalTasks(coverageMatrix: CoverageMatrix, _unitLineIndex: UnitLineIndex, externalAnalyzerResults?: ExternalAnalyzerResults): AuditTask[];
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { claimFlowReviewBlocks } from "./flowPlanning.js";
|
|
1
2
|
import { isTrivialAuditPath } from "./trivialAudit.js";
|
|
2
|
-
|
|
3
|
+
import { LENS_ORDER } from "./unitBuilder.js";
|
|
4
|
+
function taskPriority(hasExternalSignal, lens, isCriticalFlow = false) {
|
|
5
|
+
if (isCriticalFlow) {
|
|
6
|
+
return lens === "security" || lens === "reliability" || lens === "correctness"
|
|
7
|
+
? "high"
|
|
8
|
+
: "medium";
|
|
9
|
+
}
|
|
3
10
|
if (hasExternalSignal &&
|
|
4
11
|
(lens === "security" || lens === "data_integrity" || lens === "reliability")) {
|
|
5
12
|
return "high";
|
|
@@ -39,68 +46,161 @@ function pickAnalyzerLens(category) {
|
|
|
39
46
|
return "correctness";
|
|
40
47
|
}
|
|
41
48
|
const DEFAULT_FILE_SPLIT_THRESHOLD = 3000;
|
|
42
|
-
|
|
49
|
+
function buildCoverageIndex(coverageMatrix) {
|
|
50
|
+
return new Map(coverageMatrix.files.map((file) => [file.path, file]));
|
|
51
|
+
}
|
|
52
|
+
function getExternalSignalPaths(externalAnalyzerResults) {
|
|
53
|
+
const results = Array.isArray(externalAnalyzerResults?.results)
|
|
54
|
+
? externalAnalyzerResults.results
|
|
55
|
+
: [];
|
|
56
|
+
return new Set(results
|
|
57
|
+
.map((item) => item && typeof item.path === "string" && item.path.length > 0
|
|
58
|
+
? item.path
|
|
59
|
+
: null)
|
|
60
|
+
.filter((path) => path !== null));
|
|
61
|
+
}
|
|
62
|
+
function getExternalSignalResults(externalAnalyzerResults) {
|
|
63
|
+
if (!Array.isArray(externalAnalyzerResults?.results)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return externalAnalyzerResults.results.filter((item) => Boolean(item) &&
|
|
67
|
+
typeof item.path === "string" &&
|
|
68
|
+
typeof item.category === "string" &&
|
|
69
|
+
typeof item.summary === "string" &&
|
|
70
|
+
typeof item.id === "string");
|
|
71
|
+
}
|
|
72
|
+
export function buildChunkedAuditTasks(coverageMatrix, unitLineIndex, options = {}) {
|
|
43
73
|
const fileSplitThreshold = options.file_split_threshold ?? DEFAULT_FILE_SPLIT_THRESHOLD;
|
|
44
74
|
const allowed = new Set(options.limit_lenses ?? []);
|
|
45
75
|
const enforceLensFilter = allowed.size > 0;
|
|
46
76
|
const tasks = [];
|
|
47
77
|
const seen = new Set();
|
|
48
|
-
const externalPaths =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
const externalPaths = getExternalSignalPaths(options.external_analyzer_results);
|
|
79
|
+
const coverageByPath = new Map(coverageMatrix.files.map((file) => [file.path, file]));
|
|
80
|
+
const pendingByLens = new Map();
|
|
81
|
+
for (const file of coverageMatrix.files) {
|
|
82
|
+
if (file.audit_status === "excluded") {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const lens of file.required_lenses) {
|
|
86
|
+
if (file.completed_lenses.includes(lens)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
55
89
|
if (enforceLensFilter && !allowed.has(lens)) {
|
|
56
90
|
continue;
|
|
57
91
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const tags = hasExternalSignal ? ["external_analyzer_signal"] : [];
|
|
61
|
-
const candidateFiles = unit.files.filter((filePath) => !isTrivialAuditPath(filePath, unitLineIndex[filePath] ?? 0, externalPaths.has(filePath)));
|
|
62
|
-
// Split files that are individually too large to group; everything else
|
|
63
|
-
// goes into one task so the agent can reason across file boundaries.
|
|
64
|
-
const oversizedFiles = fileSplitThreshold > 0
|
|
65
|
-
? candidateFiles.filter((f) => (unitLineIndex[f] ?? 0) > fileSplitThreshold)
|
|
66
|
-
: [];
|
|
67
|
-
const normalFiles = candidateFiles.filter((f) => !oversizedFiles.includes(f));
|
|
68
|
-
// One task for all normal-sized files in this unit under this lens.
|
|
69
|
-
if (normalFiles.length > 0) {
|
|
70
|
-
const id = `${unit.unit_id}:${lens}`;
|
|
71
|
-
if (!seen.has(id)) {
|
|
72
|
-
seen.add(id);
|
|
73
|
-
tasks.push({
|
|
74
|
-
task_id: id,
|
|
75
|
-
unit_id: unit.unit_id,
|
|
76
|
-
pass_id: `pass:${lens}`,
|
|
77
|
-
lens,
|
|
78
|
-
file_paths: normalFiles,
|
|
79
|
-
rationale: `Audit ${unit.unit_id} (${normalFiles.length} file${normalFiles.length === 1 ? "" : "s"}) under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
|
|
80
|
-
priority,
|
|
81
|
-
tags,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
92
|
+
if (isTrivialAuditPath(file.path, unitLineIndex[file.path] ?? 0, externalPaths.has(file.path))) {
|
|
93
|
+
continue;
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
const pending = pendingByLens.get(lens) ?? new Set();
|
|
96
|
+
pending.add(file.path);
|
|
97
|
+
pendingByLens.set(lens, pending);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function addTaskBlock(params) {
|
|
101
|
+
const oversizedFiles = fileSplitThreshold > 0
|
|
102
|
+
? params.filePaths.filter((path) => (unitLineIndex[path] ?? 0) > fileSplitThreshold)
|
|
103
|
+
: [];
|
|
104
|
+
const oversizedSet = new Set(oversizedFiles);
|
|
105
|
+
const normalFiles = params.filePaths.filter((path) => !oversizedSet.has(path));
|
|
106
|
+
if (normalFiles.length > 0) {
|
|
107
|
+
const taskId = `${params.scopeId}:${params.lens}`;
|
|
108
|
+
if (!seen.has(taskId)) {
|
|
109
|
+
seen.add(taskId);
|
|
92
110
|
tasks.push({
|
|
93
|
-
task_id:
|
|
94
|
-
unit_id:
|
|
95
|
-
pass_id:
|
|
96
|
-
lens,
|
|
97
|
-
file_paths:
|
|
98
|
-
rationale:
|
|
99
|
-
priority:
|
|
100
|
-
tags:
|
|
111
|
+
task_id: taskId,
|
|
112
|
+
unit_id: params.unitId,
|
|
113
|
+
pass_id: params.passId,
|
|
114
|
+
lens: params.lens,
|
|
115
|
+
file_paths: normalFiles,
|
|
116
|
+
rationale: params.rationale(normalFiles, false),
|
|
117
|
+
priority: params.priority,
|
|
118
|
+
tags: params.tags.length > 0 ? params.tags : undefined,
|
|
101
119
|
});
|
|
102
120
|
}
|
|
103
121
|
}
|
|
122
|
+
for (const filePath of oversizedFiles) {
|
|
123
|
+
const taskId = `${params.scopeId}:${params.lens}:${filePath}`;
|
|
124
|
+
if (seen.has(taskId)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
seen.add(taskId);
|
|
128
|
+
tasks.push({
|
|
129
|
+
task_id: taskId,
|
|
130
|
+
unit_id: params.unitId,
|
|
131
|
+
pass_id: params.passId,
|
|
132
|
+
lens: params.lens,
|
|
133
|
+
file_paths: [filePath],
|
|
134
|
+
rationale: params.rationale([filePath], true),
|
|
135
|
+
priority: params.priority,
|
|
136
|
+
tags: params.tags.length > 0
|
|
137
|
+
? [...new Set([...params.tags, "large_file"])]
|
|
138
|
+
: ["large_file"],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const assigned = new Set();
|
|
143
|
+
const flowBlocks = options.critical_flows
|
|
144
|
+
? claimFlowReviewBlocks(options.critical_flows, pendingByLens, assigned)
|
|
145
|
+
: [];
|
|
146
|
+
for (const block of flowBlocks) {
|
|
147
|
+
const hasExternalSignal = block.file_paths.some((path) => externalPaths.has(path));
|
|
148
|
+
addTaskBlock({
|
|
149
|
+
scopeId: `flow:${block.flow_id}`,
|
|
150
|
+
unitId: `flow:${block.flow_id}`,
|
|
151
|
+
passId: `flow-pass:${block.lens}`,
|
|
152
|
+
lens: block.lens,
|
|
153
|
+
filePaths: block.file_paths,
|
|
154
|
+
priority: taskPriority(hasExternalSignal, block.lens, true),
|
|
155
|
+
tags: hasExternalSignal
|
|
156
|
+
? ["critical_flow", `critical_flow:${block.flow_id}`, "external_analyzer_signal"]
|
|
157
|
+
: ["critical_flow", `critical_flow:${block.flow_id}`],
|
|
158
|
+
rationale: (filePaths, splitFromBlock) => splitFromBlock
|
|
159
|
+
? `Audit ${filePaths[0]} (large file from critical flow ${block.flow_id}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
|
|
160
|
+
: `Audit critical flow ${block.flow_id} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const groupedRemainders = new Map();
|
|
164
|
+
for (const lens of LENS_ORDER) {
|
|
165
|
+
const pendingPaths = pendingByLens.get(lens);
|
|
166
|
+
if (!pendingPaths || pendingPaths.size === 0) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
for (const path of [...pendingPaths].sort((a, b) => a.localeCompare(b))) {
|
|
170
|
+
if (assigned.has(`${lens}:${path}`)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const record = coverageByPath.get(path);
|
|
174
|
+
const unitId = record?.unit_ids[0] ?? `review:${path.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
175
|
+
const key = `${lens}|${unitId}`;
|
|
176
|
+
const current = groupedRemainders.get(key) ?? {
|
|
177
|
+
lens,
|
|
178
|
+
unitId,
|
|
179
|
+
filePaths: [],
|
|
180
|
+
};
|
|
181
|
+
current.filePaths.push(path);
|
|
182
|
+
groupedRemainders.set(key, current);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const block of [...groupedRemainders.values()].sort((a, b) => {
|
|
186
|
+
const lensDelta = LENS_ORDER.indexOf(a.lens) - LENS_ORDER.indexOf(b.lens);
|
|
187
|
+
if (lensDelta !== 0)
|
|
188
|
+
return lensDelta;
|
|
189
|
+
return a.unitId.localeCompare(b.unitId);
|
|
190
|
+
})) {
|
|
191
|
+
const hasExternalSignal = block.filePaths.some((path) => externalPaths.has(path));
|
|
192
|
+
addTaskBlock({
|
|
193
|
+
scopeId: block.unitId,
|
|
194
|
+
unitId: block.unitId,
|
|
195
|
+
passId: `pass:${block.lens}`,
|
|
196
|
+
lens: block.lens,
|
|
197
|
+
filePaths: block.filePaths,
|
|
198
|
+
priority: taskPriority(hasExternalSignal, block.lens),
|
|
199
|
+
tags: hasExternalSignal ? ["external_analyzer_signal"] : [],
|
|
200
|
+
rationale: (filePaths, splitFromBlock) => splitFromBlock
|
|
201
|
+
? `Audit ${filePaths[0]} (large file split from ${block.unitId}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
|
|
202
|
+
: `Audit ${block.unitId} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
|
|
203
|
+
});
|
|
104
204
|
}
|
|
105
205
|
return tasks.sort((a, b) => {
|
|
106
206
|
const priorityDelta = priorityRank(b.priority) - priorityRank(a.priority);
|
|
@@ -119,12 +219,13 @@ export function buildExternalSignalTasks(coverageMatrix, _unitLineIndex, externa
|
|
|
119
219
|
}
|
|
120
220
|
const tasks = [];
|
|
121
221
|
const seen = new Set();
|
|
122
|
-
|
|
222
|
+
const coverageByPath = buildCoverageIndex(coverageMatrix);
|
|
223
|
+
for (const result of getExternalSignalResults(externalAnalyzerResults)) {
|
|
123
224
|
const safeCategory = sanitizeField(result.category, 80);
|
|
124
225
|
const safePath = sanitizeField(result.path ?? "", 260);
|
|
125
226
|
const safeSummary = sanitizeField(result.summary ?? "", 200);
|
|
126
227
|
const lens = pickAnalyzerLens(safeCategory);
|
|
127
|
-
const coverage =
|
|
228
|
+
const coverage = coverageByPath.get(result.path);
|
|
128
229
|
if (!coverage || coverage.audit_status === "excluded") {
|
|
129
230
|
continue;
|
|
130
231
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import type { RepoManifest, UnitManifest } from "../types.js";
|
|
1
|
+
import type { Lens, RepoManifest, UnitManifest } from "../types.js";
|
|
2
2
|
import type { FileDisposition } from "../types/disposition.js";
|
|
3
|
+
export declare const LENS_ORDER: Lens[];
|
|
4
|
+
export declare function deriveRequiredLensesForPath(path: string): Lens[];
|
|
3
5
|
export declare function buildUnitManifest(repoManifest: RepoManifest, disposition?: FileDisposition): UnitManifest;
|
|
@@ -60,20 +60,30 @@ function inferUnitId(path, kind) {
|
|
|
60
60
|
}
|
|
61
61
|
return `${kind}-${path.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
62
62
|
}
|
|
63
|
+
export const LENS_ORDER = [
|
|
64
|
+
"security",
|
|
65
|
+
"correctness",
|
|
66
|
+
"reliability",
|
|
67
|
+
"data_integrity",
|
|
68
|
+
"performance",
|
|
69
|
+
"operability",
|
|
70
|
+
"config_deployment",
|
|
71
|
+
"maintainability",
|
|
72
|
+
"tests",
|
|
73
|
+
];
|
|
63
74
|
function sortLenses(lenses) {
|
|
64
|
-
const order = [
|
|
65
|
-
"security",
|
|
66
|
-
"correctness",
|
|
67
|
-
"reliability",
|
|
68
|
-
"data_integrity",
|
|
69
|
-
"performance",
|
|
70
|
-
"operability",
|
|
71
|
-
"config_deployment",
|
|
72
|
-
"maintainability",
|
|
73
|
-
"tests",
|
|
74
|
-
];
|
|
75
75
|
const set = new Set(lenses);
|
|
76
|
-
return
|
|
76
|
+
return LENS_ORDER.filter((lens) => set.has(lens));
|
|
77
|
+
}
|
|
78
|
+
export function deriveRequiredLensesForPath(path) {
|
|
79
|
+
const assignment = bucketFile(path);
|
|
80
|
+
const required = new Set();
|
|
81
|
+
for (const bucket of assignment.buckets) {
|
|
82
|
+
for (const lens of LENS_MAP[bucket]) {
|
|
83
|
+
required.add(lens);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return sortLenses(required);
|
|
77
87
|
}
|
|
78
88
|
function inferCriticalFlows(files, requiredLenses) {
|
|
79
89
|
const flows = new Set();
|
|
@@ -123,10 +133,8 @@ export function buildUnitManifest(repoManifest, disposition) {
|
|
|
123
133
|
}
|
|
124
134
|
const assignment = bucketFile(file.path);
|
|
125
135
|
const required = new Set(existing.required_lenses);
|
|
126
|
-
for (const
|
|
127
|
-
|
|
128
|
-
required.add(lens);
|
|
129
|
-
}
|
|
136
|
+
for (const lens of deriveRequiredLensesForPath(file.path)) {
|
|
137
|
+
required.add(lens);
|
|
130
138
|
}
|
|
131
139
|
existing.required_lenses = sortLenses(required);
|
|
132
140
|
const riskScore = new Set(assignment.buckets).size +
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WorkerTask } from "../types/workerSession.js";
|
|
2
2
|
export declare function renderWorkerPrompt(task: WorkerTask): string;
|