auditor-lambda 0.2.1 → 0.2.2
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/audit-code-wrapper-lib.mjs +229 -289
- package/dist/adapters/eslint.js +4 -2
- package/dist/adapters/npmAudit.js +1 -1
- package/dist/cli.js +82 -45
- package/dist/extractors/bucketing.js +14 -35
- package/dist/extractors/disposition.js +8 -9
- package/dist/extractors/fileInventory.js +0 -2
- package/dist/extractors/flows.js +14 -23
- package/dist/extractors/pathPatterns.d.ts +19 -0
- package/dist/extractors/pathPatterns.js +87 -0
- package/dist/extractors/surfaces.js +2 -7
- package/dist/io/artifacts.d.ts +23 -1
- package/dist/io/artifacts.js +4 -3
- package/dist/io/runArtifacts.js +1 -1
- package/dist/orchestrator/advance.js +53 -71
- package/dist/orchestrator/flowCoverage.js +1 -2
- package/dist/orchestrator/internalExecutors.js +4 -6
- package/dist/orchestrator/planning.js +12 -20
- package/dist/orchestrator/resultIngestion.js +3 -2
- package/dist/orchestrator/runtimeValidation.js +5 -0
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
- package/dist/orchestrator/taskBuilder.js +15 -28
- package/dist/prompts/renderWorkerPrompt.js +2 -1
- package/dist/providers/claudeCodeProvider.js +1 -2
- package/dist/providers/constants.d.ts +1 -0
- package/dist/providers/constants.js +1 -0
- package/dist/providers/index.js +7 -3
- package/dist/providers/opencodeProvider.js +1 -6
- package/dist/providers/spawnLoggedCommand.js +4 -0
- package/dist/providers/types.d.ts +0 -1
- package/dist/supervisor/operatorHandoff.d.ts +2 -0
- package/dist/supervisor/operatorHandoff.js +21 -9
- package/dist/supervisor/runLedger.js +7 -8
- package/dist/supervisor/sessionConfig.js +1 -0
- package/dist/types/flowCoverage.d.ts +1 -1
- package/dist/types/runLedger.d.ts +1 -1
- package/dist/types/runtimeValidation.d.ts +2 -1
- package/dist/types/sessionConfig.d.ts +0 -6
- package/dist/types/surfaces.d.ts +2 -1
- package/dist/types/workerSession.d.ts +2 -0
- package/dist/types.d.ts +0 -1
- package/dist/validation/sessionConfig.js +1 -15
- package/package.json +1 -1
- package/schemas/audit-code-v1alpha1.schema.json +4 -0
- package/schemas/audit_result.schema.json +9 -3
- package/schemas/audit_state.schema.json +2 -2
- package/schemas/audit_task.schema.json +14 -3
- package/schemas/blind_spot_register.schema.json +13 -3
- package/schemas/coverage_matrix.schema.json +16 -4
- package/schemas/critical_flows.schema.json +6 -3
- package/schemas/external_analyzer_results.schema.json +10 -4
- package/schemas/finding.schema.json +31 -3
- package/schemas/flow_coverage.schema.json +12 -3
- package/schemas/graph_bundle.schema.json +12 -6
- package/schemas/merged_findings.schema.json +7 -2
- package/schemas/risk_register.schema.json +5 -1
- package/schemas/root_cause_clusters.schema.json +2 -1
- package/schemas/runtime_validation_tasks.schema.json +4 -1
- package/schemas/surface_manifest.schema.json +4 -1
- package/schemas/unit_manifest.schema.json +10 -3
- package/skills/audit-code/audit-code.prompt.md +0 -2
package/dist/io/artifacts.js
CHANGED
|
@@ -22,8 +22,10 @@ export const ARTIFACT_FILE_TO_BUNDLE_KEY = {
|
|
|
22
22
|
"audit_state.json": "audit_state",
|
|
23
23
|
"artifact_metadata.json": "artifact_metadata",
|
|
24
24
|
};
|
|
25
|
+
const _bundleKeyCoverage = true;
|
|
25
26
|
export function getArtifactValue(bundle, artifactName) {
|
|
26
|
-
const
|
|
27
|
+
const map = ARTIFACT_FILE_TO_BUNDLE_KEY;
|
|
28
|
+
const key = map[artifactName];
|
|
27
29
|
return key ? bundle[key] : undefined;
|
|
28
30
|
}
|
|
29
31
|
export async function loadArtifactBundle(root) {
|
|
@@ -51,8 +53,7 @@ export async function loadArtifactBundle(root) {
|
|
|
51
53
|
bundle.root_cause_clusters = await readOptionalJsonFile(`${root}/root_cause_clusters.json`);
|
|
52
54
|
bundle.synthesis_report = await readOptionalJsonFile(`${root}/synthesis_report.json`);
|
|
53
55
|
bundle.audit_state = await readOptionalJsonFile(`${root}/audit_state.json`);
|
|
54
|
-
bundle.artifact_metadata =
|
|
55
|
-
await readOptionalJsonFile(`${root}/artifact_metadata.json`);
|
|
56
|
+
bundle.artifact_metadata = await readOptionalJsonFile(`${root}/artifact_metadata.json`);
|
|
56
57
|
return bundle;
|
|
57
58
|
}
|
|
58
59
|
export async function writeCoreArtifacts(root, bundle) {
|
package/dist/io/runArtifacts.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { writeJsonFile } from "./json.js";
|
|
4
4
|
export function buildRunId(obligationId, index) {
|
|
5
5
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
6
|
-
const obligation = obligationId ?? "terminal";
|
|
6
|
+
const obligation = (obligationId ?? "terminal").replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
7
7
|
return `${timestamp}_${obligation}_${String(index).padStart(3, "0")}`;
|
|
8
8
|
}
|
|
9
9
|
export function getRunPaths(artifactsDir, runId) {
|
|
@@ -13,7 +13,7 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
13
13
|
: decision.selected_obligation;
|
|
14
14
|
if (!selectedExecutor) {
|
|
15
15
|
const state = deriveAuditState(bundle);
|
|
16
|
-
|
|
16
|
+
// Do not overwrite last_executor when no executor was selected — preserve prior value
|
|
17
17
|
state.last_obligation = selectedObligation ?? undefined;
|
|
18
18
|
return {
|
|
19
19
|
audit_state: state,
|
|
@@ -27,76 +27,58 @@ export async function advanceAudit(bundle, options = {}) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
let run;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
const state = deriveAuditState(bundle);
|
|
88
|
-
state.last_executor = selectedExecutor;
|
|
89
|
-
state.last_obligation = selectedObligation ?? undefined;
|
|
90
|
-
return {
|
|
91
|
-
audit_state: state,
|
|
92
|
-
selected_obligation: selectedObligation,
|
|
93
|
-
selected_executor: selectedExecutor,
|
|
94
|
-
progress_made: false,
|
|
95
|
-
artifacts_written: [],
|
|
96
|
-
progress_summary: `Executor ${selectedExecutor} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
97
|
-
next_likely_step: selectedObligation,
|
|
98
|
-
updated_bundle: { ...bundle, audit_state: state },
|
|
99
|
-
};
|
|
30
|
+
switch (selectedExecutor) {
|
|
31
|
+
case "intake_executor":
|
|
32
|
+
if (!options.root)
|
|
33
|
+
throw new Error("advanceAudit intake_executor requires root");
|
|
34
|
+
run = await runIntakeExecutor(bundle, options.root);
|
|
35
|
+
break;
|
|
36
|
+
case "structure_executor":
|
|
37
|
+
run = runStructureExecutor(bundle);
|
|
38
|
+
break;
|
|
39
|
+
case "planning_executor":
|
|
40
|
+
run = runPlanningExecutor(bundle, options.lineIndex ?? {});
|
|
41
|
+
break;
|
|
42
|
+
case "result_ingestion_executor":
|
|
43
|
+
run = runResultIngestionExecutor(bundle, options.auditResults ?? bundle.audit_results ?? []);
|
|
44
|
+
break;
|
|
45
|
+
case "synthesis_executor":
|
|
46
|
+
run = runSynthesisExecutor(bundle, options.auditResults);
|
|
47
|
+
break;
|
|
48
|
+
case "runtime_validation_update_executor":
|
|
49
|
+
if (!options.runtimeValidationUpdates)
|
|
50
|
+
throw new Error("advanceAudit runtime_validation_update_executor requires runtimeValidationUpdates");
|
|
51
|
+
run = runRuntimeValidationUpdateExecutor(bundle, options.runtimeValidationUpdates);
|
|
52
|
+
break;
|
|
53
|
+
case "external_analyzer_import_executor":
|
|
54
|
+
if (!options.externalAnalyzerResults)
|
|
55
|
+
throw new Error("advanceAudit external_analyzer_import_executor requires externalAnalyzerResults");
|
|
56
|
+
run = runExternalAnalyzerImportExecutor(bundle, options.externalAnalyzerResults);
|
|
57
|
+
break;
|
|
58
|
+
case "auto_fix_executor":
|
|
59
|
+
if (!options.root)
|
|
60
|
+
throw new Error("advanceAudit auto_fix_executor requires root");
|
|
61
|
+
run = runAutoFixExecutor(bundle, options.root);
|
|
62
|
+
break;
|
|
63
|
+
case "syntax_resolution_executor":
|
|
64
|
+
if (!options.root)
|
|
65
|
+
throw new Error("advanceAudit syntax_resolution_executor requires root");
|
|
66
|
+
run = runSyntaxResolutionExecutor(bundle, options.root);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
const state = deriveAuditState(bundle);
|
|
70
|
+
state.last_executor = selectedExecutor;
|
|
71
|
+
state.last_obligation = selectedObligation ?? undefined;
|
|
72
|
+
return {
|
|
73
|
+
audit_state: state,
|
|
74
|
+
selected_obligation: selectedObligation,
|
|
75
|
+
selected_executor: selectedExecutor,
|
|
76
|
+
progress_made: false,
|
|
77
|
+
artifacts_written: ["audit_state.json"],
|
|
78
|
+
progress_summary: `Executor ${selectedExecutor} is selected but not yet dispatched through advance-audit.`,
|
|
79
|
+
next_likely_step: selectedObligation,
|
|
80
|
+
updated_bundle: { ...bundle, audit_state: state },
|
|
81
|
+
};
|
|
100
82
|
}
|
|
101
83
|
const metadata = computeArtifactMetadata(run.updated, bundle.artifact_metadata);
|
|
102
84
|
const metadataBundle = { ...run.updated, artifact_metadata: metadata };
|
|
@@ -10,12 +10,11 @@ function lensSetForFlow(concerns) {
|
|
|
10
10
|
return concerns.filter((concern) => allowed.includes(concern));
|
|
11
11
|
}
|
|
12
12
|
export function buildFlowCoverage(criticalFlows, coverageMatrix) {
|
|
13
|
-
const fileIndex = new Map(coverageMatrix.files.map((file) => [file.path, file]));
|
|
14
13
|
const flows = criticalFlows.flows.map((flow) => {
|
|
15
14
|
const required = lensSetForFlow(flow.concerns);
|
|
16
15
|
const completed = new Set();
|
|
17
16
|
for (const path of flow.paths) {
|
|
18
|
-
const record =
|
|
17
|
+
const record = coverageMatrix.files.find((file) => file.path === path);
|
|
19
18
|
if (!record || record.audit_status === "excluded") {
|
|
20
19
|
continue;
|
|
21
20
|
}
|
|
@@ -118,9 +118,9 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
118
118
|
if (!bundle.coverage_matrix) {
|
|
119
119
|
throw new Error("Cannot ingest results without coverage_matrix");
|
|
120
120
|
}
|
|
121
|
-
ingestAuditResults(bundle.coverage_matrix, results);
|
|
121
|
+
const updatedCoverageMatrix = ingestAuditResults(bundle.coverage_matrix, results);
|
|
122
122
|
const flowCoverage = bundle.critical_flows
|
|
123
|
-
? buildFlowCoverage(bundle.critical_flows,
|
|
123
|
+
? buildFlowCoverage(bundle.critical_flows, updatedCoverageMatrix)
|
|
124
124
|
: bundle.flow_coverage;
|
|
125
125
|
const runtimeValidationTasks = bundle.unit_manifest
|
|
126
126
|
? buildRuntimeValidationTasks(bundle.unit_manifest, bundle.critical_flows, flowCoverage)
|
|
@@ -128,15 +128,13 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
128
128
|
const runtimeValidationReport = runtimeValidationTasks
|
|
129
129
|
? preserveOrPlaceholder(runtimeValidationTasks, bundle.runtime_validation_report)
|
|
130
130
|
: bundle.runtime_validation_report;
|
|
131
|
-
const requeuePayload = bundle.
|
|
132
|
-
? buildRequeuePayload(bundle.coverage_matrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results)
|
|
133
|
-
: { tasks: [], task_count: 0 };
|
|
131
|
+
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results);
|
|
134
132
|
const mergedResults = [...(bundle.audit_results ?? []), ...results];
|
|
135
133
|
const synthesisReport = buildSynthesisReport(mergedResults, runtimeValidationReport, bundle.external_analyzer_results);
|
|
136
134
|
return {
|
|
137
135
|
updated: {
|
|
138
136
|
...bundle,
|
|
139
|
-
coverage_matrix:
|
|
137
|
+
coverage_matrix: updatedCoverageMatrix,
|
|
140
138
|
flow_coverage: flowCoverage,
|
|
141
139
|
runtime_validation_tasks: runtimeValidationTasks,
|
|
142
140
|
runtime_validation_report: runtimeValidationReport,
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { applyUnitCoverage, createCoverageMatrix, markExcludedPath, } from "../coverage.js";
|
|
2
2
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
+
const CATEGORY_LENS_TABLE = [
|
|
4
|
+
[["security", "secret"], ["security", "correctness"]],
|
|
5
|
+
[["dependency", "vuln"], ["security", "config_deployment"]],
|
|
6
|
+
[["tests", "coverage"], ["tests"]],
|
|
7
|
+
[["data"], ["data_integrity", "correctness"]],
|
|
8
|
+
[["reliability", "concurrency"], ["reliability", "correctness"]],
|
|
9
|
+
[["maintainability", "lint", "style"], ["maintainability"]],
|
|
10
|
+
];
|
|
3
11
|
function analyzerCategoryToLenses(category) {
|
|
4
12
|
const normalized = category.toLowerCase();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return ["security", "config_deployment"];
|
|
10
|
-
}
|
|
11
|
-
if (normalized.includes("tests") || normalized.includes("coverage")) {
|
|
12
|
-
return ["tests"];
|
|
13
|
-
}
|
|
14
|
-
if (normalized.includes("data")) {
|
|
15
|
-
return ["data_integrity", "correctness"];
|
|
16
|
-
}
|
|
17
|
-
if (normalized.includes("reliability") ||
|
|
18
|
-
normalized.includes("concurrency")) {
|
|
19
|
-
return ["reliability", "correctness"];
|
|
20
|
-
}
|
|
21
|
-
if (normalized.includes("maintainability") ||
|
|
22
|
-
normalized.includes("lint") ||
|
|
23
|
-
normalized.includes("style")) {
|
|
24
|
-
return ["maintainability"];
|
|
13
|
+
for (const [keywords, lenses] of CATEGORY_LENS_TABLE) {
|
|
14
|
+
if (keywords.some((kw) => normalized.includes(kw))) {
|
|
15
|
+
return lenses;
|
|
16
|
+
}
|
|
25
17
|
}
|
|
26
18
|
return ["correctness"];
|
|
27
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyReviewedRanges } from "../coverage.js";
|
|
2
2
|
export function ingestAuditResults(coverageMatrix, results) {
|
|
3
|
+
const matrix = JSON.parse(JSON.stringify(coverageMatrix));
|
|
3
4
|
const reviewedRanges = results.flatMap((result) => result.reviewed_ranges.map((range) => ({
|
|
4
5
|
path: range.path,
|
|
5
6
|
start: range.start,
|
|
@@ -8,6 +9,6 @@ export function ingestAuditResults(coverageMatrix, results) {
|
|
|
8
9
|
lens: result.lens,
|
|
9
10
|
agent_role: result.agent_role,
|
|
10
11
|
})));
|
|
11
|
-
applyReviewedRanges(
|
|
12
|
-
return
|
|
12
|
+
applyReviewedRanges(matrix, reviewedRanges);
|
|
13
|
+
return matrix;
|
|
13
14
|
}
|
|
@@ -63,6 +63,11 @@ export function buildRuntimeValidationTasks(unitManifest, criticalFlows, flowCov
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
const RUNTIME_TASK_CAP = 100;
|
|
67
|
+
if (tasks.length > RUNTIME_TASK_CAP) {
|
|
68
|
+
process.stderr.write(`[runtime-validation] generated ${tasks.length} tasks which exceeds the cap of ${RUNTIME_TASK_CAP}; truncating to avoid runaway expansion\n`);
|
|
69
|
+
tasks.splice(RUNTIME_TASK_CAP);
|
|
70
|
+
}
|
|
66
71
|
return { tasks };
|
|
67
72
|
}
|
|
68
73
|
export function buildPlaceholderRuntimeValidationReport(tasks) {
|
|
@@ -20,12 +20,16 @@ function runTsc(root) {
|
|
|
20
20
|
category: "correctness",
|
|
21
21
|
severity: "error",
|
|
22
22
|
path: match[1].replace(/\\/g, "/"),
|
|
23
|
+
line_start: parseInt(match[2], 10),
|
|
23
24
|
summary: match[3],
|
|
24
25
|
rule: "tsc",
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
else {
|
|
31
|
+
process.stderr.write(`[syntax-resolution] tsc exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
return results;
|
|
31
35
|
}
|
|
@@ -51,6 +55,7 @@ function runEslint(root) {
|
|
|
51
55
|
path: fileResult.filePath
|
|
52
56
|
.replace(/\\/g, "/")
|
|
53
57
|
.replace(root.replace(/\\/g, "/") + "/", ""),
|
|
58
|
+
line_start: msg.line,
|
|
54
59
|
summary: msg.message,
|
|
55
60
|
rule: msg.ruleId || "eslint-error",
|
|
56
61
|
});
|
|
@@ -58,9 +63,12 @@ function runEslint(root) {
|
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
catch (e) {
|
|
61
|
-
|
|
66
|
+
process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${(error.stdout?.toString() ?? "").slice(0, 200)}\n`);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
69
|
+
else {
|
|
70
|
+
process.stderr.write(`[syntax-resolution] eslint exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
return results;
|
|
66
74
|
}
|
|
@@ -78,7 +86,7 @@ export function runSyntaxResolutionExecutor(bundle, root) {
|
|
|
78
86
|
const seen = new Set();
|
|
79
87
|
const deduped = [];
|
|
80
88
|
for (const r of merged) {
|
|
81
|
-
const key = `${r.path}:${r.rule}:${r.summary}`;
|
|
89
|
+
const key = `${r.path}:${r.line_start ?? ""}:${r.rule}:${r.summary}`;
|
|
82
90
|
if (!seen.has(key)) {
|
|
83
91
|
seen.add(key);
|
|
84
92
|
deduped.push(r);
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
function modelHintForLens(lens) {
|
|
2
|
-
switch (lens) {
|
|
3
|
-
case "security":
|
|
4
|
-
case "correctness":
|
|
5
|
-
case "reliability":
|
|
6
|
-
case "data_integrity":
|
|
7
|
-
return "capable";
|
|
8
|
-
case "architecture":
|
|
9
|
-
case "maintainability":
|
|
10
|
-
case "tests":
|
|
11
|
-
return "balanced";
|
|
12
|
-
default:
|
|
13
|
-
return "fast";
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
1
|
function taskPriority(hasExternalSignal, lens) {
|
|
17
2
|
if (hasExternalSignal &&
|
|
18
3
|
(lens === "security" || lens === "data_integrity" || lens === "reliability")) {
|
|
@@ -52,8 +37,9 @@ function pickAnalyzerLens(category) {
|
|
|
52
37
|
return "maintainability";
|
|
53
38
|
return "correctness";
|
|
54
39
|
}
|
|
40
|
+
const DEFAULT_FILE_SPLIT_THRESHOLD = 3000;
|
|
55
41
|
export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}) {
|
|
56
|
-
const fileSplitThreshold = options.file_split_threshold ??
|
|
42
|
+
const fileSplitThreshold = options.file_split_threshold ?? DEFAULT_FILE_SPLIT_THRESHOLD;
|
|
57
43
|
const allowed = new Set(options.limit_lenses ?? []);
|
|
58
44
|
const enforceLensFilter = allowed.size > 0;
|
|
59
45
|
const tasks = [];
|
|
@@ -91,7 +77,6 @@ export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}
|
|
|
91
77
|
rationale: `Audit ${unit.unit_id} (${normalFiles.length} file${normalFiles.length === 1 ? "" : "s"}) under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
|
|
92
78
|
priority,
|
|
93
79
|
tags,
|
|
94
|
-
model_hint: modelHintForLens(lens),
|
|
95
80
|
});
|
|
96
81
|
}
|
|
97
82
|
}
|
|
@@ -110,10 +95,7 @@ export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}
|
|
|
110
95
|
file_paths: [filePath],
|
|
111
96
|
rationale: `Audit ${filePath} (large file, split from unit) under the ${lens} lens.${fileHasSignal ? " External analyzer signals raise priority." : ""}`,
|
|
112
97
|
priority: taskPriority(fileHasSignal, lens),
|
|
113
|
-
tags: fileHasSignal
|
|
114
|
-
? ["external_analyzer_signal", "large_file"]
|
|
115
|
-
: ["large_file"],
|
|
116
|
-
model_hint: modelHintForLens(lens),
|
|
98
|
+
tags: fileHasSignal ? ["external_analyzer_signal", "large_file"] : ["large_file"],
|
|
117
99
|
});
|
|
118
100
|
}
|
|
119
101
|
}
|
|
@@ -125,37 +107,42 @@ export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}
|
|
|
125
107
|
return a.task_id.localeCompare(b.task_id);
|
|
126
108
|
});
|
|
127
109
|
}
|
|
110
|
+
/** Strip control characters and newlines, then truncate to maxLen. */
|
|
111
|
+
function sanitizeField(value, maxLen) {
|
|
112
|
+
return value.replace(/[\x00-\x1f\x7f]/g, " ").slice(0, maxLen).trimEnd();
|
|
113
|
+
}
|
|
128
114
|
export function buildExternalSignalTasks(coverageMatrix, _unitLineIndex, externalAnalyzerResults) {
|
|
129
115
|
if (!externalAnalyzerResults) {
|
|
130
116
|
return [];
|
|
131
117
|
}
|
|
132
118
|
const tasks = [];
|
|
133
119
|
const seen = new Set();
|
|
134
|
-
const coverageIndex = new Map(coverageMatrix.files.map((file) => [file.path, file]));
|
|
135
120
|
for (const result of externalAnalyzerResults.results) {
|
|
136
|
-
const
|
|
137
|
-
const
|
|
121
|
+
const safeCategory = sanitizeField(result.category, 80);
|
|
122
|
+
const safePath = sanitizeField(result.path ?? "", 260);
|
|
123
|
+
const safeSummary = sanitizeField(result.summary ?? "", 200);
|
|
124
|
+
const lens = pickAnalyzerLens(safeCategory);
|
|
125
|
+
const coverage = coverageMatrix.files.find((file) => file.path === result.path);
|
|
138
126
|
if (!coverage || coverage.audit_status === "excluded") {
|
|
139
127
|
continue;
|
|
140
128
|
}
|
|
141
|
-
const id = `analyzer:${externalAnalyzerResults.tool}:${lens}:${
|
|
129
|
+
const id = `analyzer:${externalAnalyzerResults.tool}:${lens}:${safePath}:${result.id}`;
|
|
142
130
|
if (seen.has(id)) {
|
|
143
131
|
continue;
|
|
144
132
|
}
|
|
145
133
|
seen.add(id);
|
|
146
134
|
tasks.push({
|
|
147
135
|
task_id: id,
|
|
148
|
-
unit_id: coverage.unit_ids[0] ?? `analyzer:${
|
|
136
|
+
unit_id: coverage.unit_ids[0] ?? `analyzer:${safePath}`,
|
|
149
137
|
pass_id: `analyzer:${externalAnalyzerResults.tool}:${lens}`,
|
|
150
138
|
lens,
|
|
151
139
|
file_paths: [result.path],
|
|
152
|
-
rationale: `Analyzer follow-up for ${
|
|
140
|
+
rationale: `Analyzer follow-up for ${safePath} under the ${lens} lens because ${externalAnalyzerResults.tool} reported: ${safeSummary}`,
|
|
153
141
|
priority: "high",
|
|
154
142
|
tags: [
|
|
155
143
|
"external_analyzer_signal",
|
|
156
144
|
`external_tool:${externalAnalyzerResults.tool}`,
|
|
157
145
|
],
|
|
158
|
-
model_hint: modelHintForLens(lens),
|
|
159
146
|
});
|
|
160
147
|
}
|
|
161
148
|
return tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
|
@@ -4,7 +4,8 @@ function shellQuote(arg) {
|
|
|
4
4
|
export function renderWorkerPrompt(task) {
|
|
5
5
|
const command = task.worker_command.map(shellQuote).join(" ");
|
|
6
6
|
if (task.preferred_executor === "agent" && task.audit_results_path) {
|
|
7
|
-
const tasksPath = task.pending_audit_tasks_path ??
|
|
7
|
+
const tasksPath = task.pending_audit_tasks_path ??
|
|
8
|
+
`${task.artifacts_dir}/audit_tasks.json`;
|
|
8
9
|
const lines = [
|
|
9
10
|
"You are executing one bounded audit task for audit-code.",
|
|
10
11
|
`Run ID: ${task.run_id}`,
|
|
@@ -9,7 +9,7 @@ export class ClaudeCodeProvider {
|
|
|
9
9
|
async launch(input) {
|
|
10
10
|
if (process.env.CLAUDECODE) {
|
|
11
11
|
throw new Error("claude-code provider cannot be used inside an active Claude Code session. " +
|
|
12
|
-
|
|
12
|
+
"Set provider to \"local-subprocess\" in .audit-artifacts/session-config.json, " +
|
|
13
13
|
"then run /audit-code conversationally and follow the dispatch prompts manually.");
|
|
14
14
|
}
|
|
15
15
|
const prompt = await readFile(input.promptPath, "utf8");
|
|
@@ -17,7 +17,6 @@ export class ClaudeCodeProvider {
|
|
|
17
17
|
const args = [
|
|
18
18
|
"-p",
|
|
19
19
|
prompt,
|
|
20
|
-
...(input.model ? ["--model", input.model] : []),
|
|
21
20
|
...(this.config.extra_args ?? []),
|
|
22
21
|
"--dangerously-skip-permissions",
|
|
23
22
|
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const LOCAL_SUBPROCESS_PROVIDER_NAME: "local-subprocess";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LOCAL_SUBPROCESS_PROVIDER_NAME = "local-subprocess";
|
package/dist/providers/index.js
CHANGED
|
@@ -39,9 +39,7 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
|
|
|
39
39
|
const opencodeCommand = sessionConfig.opencode?.command ?? "opencode";
|
|
40
40
|
const claudeAvailable = !insideClaudeCode && lookupCommand(claudeCommand);
|
|
41
41
|
const opencodeAvailable = lookupCommand(opencodeCommand);
|
|
42
|
-
if (!insideClaudeCode &&
|
|
43
|
-
hasConfiguredClaudeCode(sessionConfig) &&
|
|
44
|
-
claudeAvailable) {
|
|
42
|
+
if (!insideClaudeCode && hasConfiguredClaudeCode(sessionConfig) && claudeAvailable) {
|
|
45
43
|
return "claude-code";
|
|
46
44
|
}
|
|
47
45
|
if (hasConfiguredOpenCode(sessionConfig) && opencodeAvailable) {
|
|
@@ -57,6 +55,12 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
|
|
|
57
55
|
}
|
|
58
56
|
export function createFreshSessionProvider(name, sessionConfig = {}) {
|
|
59
57
|
const providerName = resolveFreshSessionProviderName(name, sessionConfig);
|
|
58
|
+
if (providerName === "local-subprocess" &&
|
|
59
|
+
(name ?? sessionConfig.provider) === "auto") {
|
|
60
|
+
process.stderr.write("audit-code: auto provider resolved to local-subprocess — no capable agent provider detected. " +
|
|
61
|
+
"Agent tasks will require manual dispatch. Configure claude-code, opencode, or subprocess-template " +
|
|
62
|
+
"in session-config.json to automate them.\n");
|
|
63
|
+
}
|
|
60
64
|
switch (providerName) {
|
|
61
65
|
case "local-subprocess":
|
|
62
66
|
return new LocalSubprocessProvider();
|
|
@@ -9,12 +9,7 @@ export class OpenCodeProvider {
|
|
|
9
9
|
async launch(input) {
|
|
10
10
|
const prompt = await readFile(input.promptPath, "utf8");
|
|
11
11
|
const command = this.config.command ?? "opencode";
|
|
12
|
-
const args = [
|
|
13
|
-
"run",
|
|
14
|
-
prompt,
|
|
15
|
-
...(input.model ? ["--model", input.model] : []),
|
|
16
|
-
...(this.config.extra_args ?? []),
|
|
17
|
-
];
|
|
12
|
+
const args = ["run", prompt, ...(this.config.extra_args ?? [])];
|
|
18
13
|
return await spawnLoggedCommand(command, args, input);
|
|
19
14
|
}
|
|
20
15
|
}
|
|
@@ -3,6 +3,10 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
function tee(write, chunk) {
|
|
4
4
|
write.write(chunk);
|
|
5
5
|
}
|
|
6
|
+
// On Windows `command` must be the resolved .cmd / .exe path because `spawn`
|
|
7
|
+
// does not consult PATH for executables without a shell. Callers should use
|
|
8
|
+
// `platformCommand()` (scripts/smoke-packaged-audit-code.mjs) or similar to
|
|
9
|
+
// supply the correct command form for the host OS.
|
|
6
10
|
export async function spawnLoggedCommand(command, args, input, env) {
|
|
7
11
|
return await new Promise((resolve, reject) => {
|
|
8
12
|
const stdoutLog = createWriteStream(input.stdoutPath, { flags: "a" });
|
|
@@ -14,6 +14,7 @@ export interface AuditCodeHandoffArtifactPaths {
|
|
|
14
14
|
audit_tasks: string | null;
|
|
15
15
|
runtime_validation_tasks: string | null;
|
|
16
16
|
}
|
|
17
|
+
export declare const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
17
18
|
export interface AuditCodeHandoff {
|
|
18
19
|
status: AuditTopLevelStatus;
|
|
19
20
|
repo_root: string;
|
|
@@ -33,5 +34,6 @@ export declare function buildAuditCodeHandoff(params: {
|
|
|
33
34
|
bundle: ArtifactBundle;
|
|
34
35
|
providerName?: string | null;
|
|
35
36
|
progressSummary: string;
|
|
37
|
+
isConfigError?: boolean;
|
|
36
38
|
}): AuditCodeHandoff;
|
|
37
39
|
export declare function writeAuditCodeHandoffArtifacts(handoff: AuditCodeHandoff): Promise<void>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { writeJsonFile } from "../io/json.js";
|
|
4
|
+
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "../providers/constants.js";
|
|
5
|
+
export const CONFIG_ERROR_BLOCKER_PREFIX = "config-error:";
|
|
4
6
|
function quoteShellPath(path) {
|
|
5
7
|
return `"${path.replace(/"/g, '\\"')}"`;
|
|
6
8
|
}
|
|
@@ -23,8 +25,8 @@ function buildSummary(status, providerName, fallbackSummary) {
|
|
|
23
25
|
? `Automatic work can continue under ${providerName}. Re-run the same wrapper or inspect the listed artifacts if you need operator context.`
|
|
24
26
|
: "Automatic work can continue. Re-run the same wrapper or inspect the listed artifacts if you need operator context.";
|
|
25
27
|
}
|
|
26
|
-
function buildSuggestedInputs(artifactsDir, status) {
|
|
27
|
-
if (status !== "blocked") {
|
|
28
|
+
function buildSuggestedInputs(artifactsDir, status, isConfigError) {
|
|
29
|
+
if (status !== "blocked" || isConfigError) {
|
|
28
30
|
return [];
|
|
29
31
|
}
|
|
30
32
|
const incomingDir = join(artifactsDir, "incoming");
|
|
@@ -52,11 +54,14 @@ function buildSuggestedCommands(suggestedInputs, status) {
|
|
|
52
54
|
}
|
|
53
55
|
return suggestedInputs.map((item) => `audit-code ${item.flag} ${quoteShellPath(item.suggested_path)}`);
|
|
54
56
|
}
|
|
55
|
-
function buildInteractiveProviderHint(status, providerName, sessionConfigPath) {
|
|
57
|
+
function buildInteractiveProviderHint(status, providerName, sessionConfigPath, isConfigError) {
|
|
56
58
|
if (status !== "blocked") {
|
|
57
59
|
return null;
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
if (isConfigError) {
|
|
62
|
+
return `A project configuration issue is blocking the audit. Verify that --root points to the repository root containing a project file (package.json, go.mod, etc.), then run audit-code again.`;
|
|
63
|
+
}
|
|
64
|
+
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
60
65
|
return `Current provider is ${providerLabel}. If you want the backend to continue through an interactive provider instead of importing results manually, set "provider" in ${sessionConfigPath} to "auto", "claude-code", "opencode", "subprocess-template", or "vscode-task", then run audit-code again from the repository root.`;
|
|
61
66
|
}
|
|
62
67
|
function renderMarkdown(handoff) {
|
|
@@ -109,6 +114,7 @@ function renderMarkdown(handoff) {
|
|
|
109
114
|
return lines.join("\n");
|
|
110
115
|
}
|
|
111
116
|
export function buildAuditCodeHandoff(params) {
|
|
117
|
+
const isConfigError = params.isConfigError ?? false;
|
|
112
118
|
const incomingDir = join(params.artifactsDir, "incoming");
|
|
113
119
|
const artifactPaths = {
|
|
114
120
|
incoming_dir: incomingDir,
|
|
@@ -123,7 +129,7 @@ export function buildAuditCodeHandoff(params) {
|
|
|
123
129
|
? join(params.artifactsDir, "runtime_validation_tasks.json")
|
|
124
130
|
: null,
|
|
125
131
|
};
|
|
126
|
-
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status);
|
|
132
|
+
const suggestedInputs = buildSuggestedInputs(params.artifactsDir, params.state.status, isConfigError);
|
|
127
133
|
return {
|
|
128
134
|
status: params.state.status,
|
|
129
135
|
repo_root: params.root,
|
|
@@ -133,12 +139,18 @@ export function buildAuditCodeHandoff(params) {
|
|
|
133
139
|
pending_obligations: buildPendingObligations(params.state),
|
|
134
140
|
suggested_inputs: suggestedInputs,
|
|
135
141
|
suggested_commands: buildSuggestedCommands(suggestedInputs, params.state.status),
|
|
136
|
-
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config),
|
|
142
|
+
interactive_provider_hint: buildInteractiveProviderHint(params.state.status, params.providerName ?? null, artifactPaths.session_config, isConfigError),
|
|
137
143
|
artifact_paths: artifactPaths,
|
|
138
144
|
};
|
|
139
145
|
}
|
|
140
146
|
export async function writeAuditCodeHandoffArtifacts(handoff) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
try {
|
|
148
|
+
await mkdir(handoff.artifact_paths.incoming_dir, { recursive: true });
|
|
149
|
+
await writeJsonFile(handoff.artifact_paths.operator_handoff_json, handoff);
|
|
150
|
+
await writeFile(handoff.artifact_paths.operator_handoff_markdown, renderMarkdown(handoff), "utf8");
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new Error(`Failed to write operator handoff artifacts: ${message}`);
|
|
155
|
+
}
|
|
144
156
|
}
|