auditor-lambda 0.2.8 → 0.2.10
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 +234 -63
- 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/autoFixExecutor.js +32 -15
- 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/localCommands.d.ts +14 -0
- package/dist/orchestrator/localCommands.js +124 -0
- package/dist/orchestrator/planning.js +25 -3
- package/dist/orchestrator/requeue.js +11 -1
- package/dist/orchestrator/syntaxResolutionExecutor.js +60 -59
- 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.d.ts +1 -1
- package/dist/supervisor/operatorHandoff.js +56 -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 +31 -11
- 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 +53 -6
- package/docs/supervisor.md +7 -0
- package/docs/workflow-refactor-brief.md +177 -0
- package/package.json +1 -1
- package/schemas/audit-code-v1alpha1.schema.json +1 -0
- 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: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface LocalCommandCandidate {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
display?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LocalCommandResult {
|
|
7
|
+
candidate: LocalCommandCandidate;
|
|
8
|
+
exitCode: number | null;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
error?: Error;
|
|
12
|
+
}
|
|
13
|
+
export declare function runFirstAvailableCommand(root: string, candidates: LocalCommandCandidate[]): LocalCommandResult | null;
|
|
14
|
+
export declare function resolveNodeTool(root: string, relativePath: string, args: string[], display: string): LocalCommandCandidate[];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
function isWindowsBatchCommand(path) {
|
|
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
|
+
}
|
|
14
|
+
function toSpawnTuple(candidate) {
|
|
15
|
+
if (!isWindowsBatchCommand(candidate.command)) {
|
|
16
|
+
return {
|
|
17
|
+
command: candidate.command,
|
|
18
|
+
args: candidate.args,
|
|
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
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function resolveFromPath(command) {
|
|
32
|
+
if (command.trim().length === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (command.includes("\\") ||
|
|
36
|
+
command.includes("/") ||
|
|
37
|
+
isAbsolute(command)) {
|
|
38
|
+
return existsSync(command) ? command : null;
|
|
39
|
+
}
|
|
40
|
+
const pathValue = process.env.PATH ?? "";
|
|
41
|
+
const pathEntries = pathValue
|
|
42
|
+
.split(delimiter)
|
|
43
|
+
.map((entry) => entry.trim())
|
|
44
|
+
.filter((entry) => entry.length > 0);
|
|
45
|
+
const extensions = process.platform === "win32"
|
|
46
|
+
? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
47
|
+
.split(";")
|
|
48
|
+
.map((ext) => ext.toLowerCase())
|
|
49
|
+
: [""];
|
|
50
|
+
for (const dir of pathEntries) {
|
|
51
|
+
const directPath = join(dir, command);
|
|
52
|
+
if (existsSync(directPath)) {
|
|
53
|
+
return directPath;
|
|
54
|
+
}
|
|
55
|
+
for (const ext of extensions) {
|
|
56
|
+
const candidatePath = join(dir, `${command}${ext}`);
|
|
57
|
+
if (existsSync(candidatePath)) {
|
|
58
|
+
return candidatePath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function resolveCandidate(root, candidate) {
|
|
65
|
+
if (candidate.command === process.execPath) {
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
const resolvedPath = resolveFromPath(candidate.command);
|
|
69
|
+
if (resolvedPath) {
|
|
70
|
+
return {
|
|
71
|
+
...candidate,
|
|
72
|
+
command: resolvedPath,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const repoLocalPath = join(root, candidate.command);
|
|
76
|
+
if (existsSync(repoLocalPath)) {
|
|
77
|
+
return {
|
|
78
|
+
...candidate,
|
|
79
|
+
command: repoLocalPath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
export function runFirstAvailableCommand(root, candidates) {
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const resolved = resolveCandidate(root, candidate);
|
|
87
|
+
if (!resolved) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const spawnTarget = toSpawnTuple(resolved);
|
|
91
|
+
const result = spawnSync(spawnTarget.command, spawnTarget.args, {
|
|
92
|
+
cwd: root,
|
|
93
|
+
env: process.env,
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
stdio: "pipe",
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
candidate: {
|
|
100
|
+
...resolved,
|
|
101
|
+
display: candidate.display ?? [candidate.command, ...candidate.args].join(" "),
|
|
102
|
+
},
|
|
103
|
+
exitCode: result.status,
|
|
104
|
+
stdout: result.stdout ?? "",
|
|
105
|
+
stderr: result.stderr ?? "",
|
|
106
|
+
error: result.error
|
|
107
|
+
? new Error(result.error.message, { cause: result.error })
|
|
108
|
+
: undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
export function resolveNodeTool(root, relativePath, args, display) {
|
|
114
|
+
const localToolPath = join(root, relativePath);
|
|
115
|
+
const candidates = [];
|
|
116
|
+
if (existsSync(localToolPath)) {
|
|
117
|
+
candidates.push({
|
|
118
|
+
command: process.execPath,
|
|
119
|
+
args: [localToolPath, ...args],
|
|
120
|
+
display,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return candidates;
|
|
124
|
+
}
|
|
@@ -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,74 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveNodeTool, runFirstAvailableCommand } from "./localCommands.js";
|
|
2
3
|
function runTsc(root) {
|
|
3
4
|
const results = [];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const command = runFirstAvailableCommand(root, [
|
|
6
|
+
...resolveNodeTool(root, join("node_modules", "typescript", "bin", "tsc"), ["--noEmit"], "tsc --noEmit"),
|
|
7
|
+
{ command: "tsc", args: ["--noEmit"], display: "tsc --noEmit" },
|
|
8
|
+
]);
|
|
9
|
+
if (!command || command.error) {
|
|
10
|
+
return results;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rule: "tsc",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
process.stderr.write(`[syntax-resolution] tsc exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
|
|
12
|
+
const output = [command.stdout, command.stderr].filter(Boolean).join("\n");
|
|
13
|
+
const lines = output.split("\n");
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
const match = line.match(/^([^:]+)\((\d+),\d+\):\s+(error\s+TS\d+:.*)$/);
|
|
16
|
+
if (match) {
|
|
17
|
+
results.push({
|
|
18
|
+
id: `tsc-${results.length}`,
|
|
19
|
+
category: "correctness",
|
|
20
|
+
severity: "error",
|
|
21
|
+
path: match[1].replace(/\\/g, "/"),
|
|
22
|
+
line_start: parseInt(match[2], 10),
|
|
23
|
+
summary: match[3],
|
|
24
|
+
rule: "tsc",
|
|
25
|
+
});
|
|
32
26
|
}
|
|
33
27
|
}
|
|
28
|
+
if (command.exitCode === 0 && output.trim().length === 0) {
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
if (results.length === 0 && output.trim().length > 0) {
|
|
32
|
+
process.stderr.write(`[syntax-resolution] tsc output could not be parsed: ${output.slice(0, 200)}\n`);
|
|
33
|
+
}
|
|
34
34
|
return results;
|
|
35
35
|
}
|
|
36
36
|
function runEslint(root) {
|
|
37
37
|
const results = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const command = runFirstAvailableCommand(root, [
|
|
39
|
+
...resolveNodeTool(root, join("node_modules", "eslint", "bin", "eslint.js"), [".", "--ext", ".ts,.js,.tsx,.jsx", "--format", "json"], "eslint . --ext .ts,.js,.tsx,.jsx --format json"),
|
|
40
|
+
{
|
|
41
|
+
command: "eslint",
|
|
42
|
+
args: [".", "--ext", ".ts,.js,.tsx,.jsx", "--format", "json"],
|
|
43
|
+
display: "eslint . --ext .ts,.js,.tsx,.jsx --format json",
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
if (!command || command.error) {
|
|
47
|
+
return results;
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (e) {
|
|
66
|
-
process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${(error.stdout?.toString() ?? "").slice(0, 200)}\n`);
|
|
49
|
+
const output = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
|
|
50
|
+
if (output.length === 0) {
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(output);
|
|
55
|
+
for (const fileResult of parsed) {
|
|
56
|
+
for (const msg of fileResult.messages) {
|
|
57
|
+
results.push({
|
|
58
|
+
id: `eslint-${results.length}`,
|
|
59
|
+
category: "maintainability",
|
|
60
|
+
severity: msg.severity === 2 ? "error" : "warning",
|
|
61
|
+
path: fileResult.filePath
|
|
62
|
+
.replace(/\\/g, "/")
|
|
63
|
+
.replace(root.replace(/\\/g, "/") + "/", ""),
|
|
64
|
+
line_start: msg.line,
|
|
65
|
+
summary: msg.message,
|
|
66
|
+
rule: msg.ruleId || "eslint-error",
|
|
67
|
+
});
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${output.slice(0, 200)}\n`);
|
|
72
73
|
}
|
|
73
74
|
return results;
|
|
74
75
|
}
|
|
@@ -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[];
|