auditor-lambda 0.2.1 → 0.2.3

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.
Files changed (62) hide show
  1. package/audit-code-wrapper-lib.mjs +229 -289
  2. package/dist/adapters/eslint.js +4 -2
  3. package/dist/adapters/npmAudit.js +1 -1
  4. package/dist/cli.js +82 -45
  5. package/dist/extractors/bucketing.js +14 -35
  6. package/dist/extractors/disposition.js +8 -9
  7. package/dist/extractors/fileInventory.js +0 -2
  8. package/dist/extractors/flows.js +14 -23
  9. package/dist/extractors/fsIntake.js +4 -1
  10. package/dist/extractors/pathPatterns.d.ts +19 -0
  11. package/dist/extractors/pathPatterns.js +91 -0
  12. package/dist/extractors/surfaces.js +2 -7
  13. package/dist/io/artifacts.d.ts +23 -1
  14. package/dist/io/artifacts.js +4 -3
  15. package/dist/io/runArtifacts.js +1 -1
  16. package/dist/orchestrator/advance.js +53 -71
  17. package/dist/orchestrator/flowCoverage.js +1 -2
  18. package/dist/orchestrator/internalExecutors.js +4 -6
  19. package/dist/orchestrator/planning.js +12 -20
  20. package/dist/orchestrator/resultIngestion.js +3 -2
  21. package/dist/orchestrator/runtimeValidation.js +5 -0
  22. package/dist/orchestrator/syntaxResolutionExecutor.js +10 -2
  23. package/dist/orchestrator/taskBuilder.js +15 -28
  24. package/dist/prompts/renderWorkerPrompt.js +2 -1
  25. package/dist/providers/claudeCodeProvider.js +1 -2
  26. package/dist/providers/constants.d.ts +1 -0
  27. package/dist/providers/constants.js +1 -0
  28. package/dist/providers/index.js +7 -3
  29. package/dist/providers/opencodeProvider.js +1 -6
  30. package/dist/providers/spawnLoggedCommand.js +4 -0
  31. package/dist/providers/types.d.ts +0 -1
  32. package/dist/supervisor/operatorHandoff.d.ts +2 -0
  33. package/dist/supervisor/operatorHandoff.js +21 -9
  34. package/dist/supervisor/runLedger.js +7 -8
  35. package/dist/supervisor/sessionConfig.js +1 -0
  36. package/dist/types/flowCoverage.d.ts +1 -1
  37. package/dist/types/runLedger.d.ts +1 -1
  38. package/dist/types/runtimeValidation.d.ts +2 -1
  39. package/dist/types/sessionConfig.d.ts +0 -6
  40. package/dist/types/surfaces.d.ts +2 -1
  41. package/dist/types/workerSession.d.ts +2 -0
  42. package/dist/types.d.ts +0 -1
  43. package/dist/validation/sessionConfig.js +1 -15
  44. package/package.json +1 -1
  45. package/schemas/audit-code-v1alpha1.schema.json +4 -0
  46. package/schemas/audit_result.schema.json +9 -3
  47. package/schemas/audit_state.schema.json +2 -2
  48. package/schemas/audit_task.schema.json +14 -3
  49. package/schemas/blind_spot_register.schema.json +13 -3
  50. package/schemas/coverage_matrix.schema.json +16 -4
  51. package/schemas/critical_flows.schema.json +6 -3
  52. package/schemas/external_analyzer_results.schema.json +10 -4
  53. package/schemas/finding.schema.json +31 -3
  54. package/schemas/flow_coverage.schema.json +12 -3
  55. package/schemas/graph_bundle.schema.json +12 -6
  56. package/schemas/merged_findings.schema.json +7 -2
  57. package/schemas/risk_register.schema.json +5 -1
  58. package/schemas/root_cause_clusters.schema.json +2 -1
  59. package/schemas/runtime_validation_tasks.schema.json +4 -1
  60. package/schemas/surface_manifest.schema.json +4 -1
  61. package/schemas/unit_manifest.schema.json +10 -3
  62. package/skills/audit-code/audit-code.prompt.md +0 -2
@@ -32,7 +32,29 @@ export interface ArtifactBundle {
32
32
  audit_state?: AuditState;
33
33
  artifact_metadata?: ArtifactMetadataManifest;
34
34
  }
35
- export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: Record<string, keyof ArtifactBundle>;
35
+ export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: {
36
+ readonly "repo_manifest.json": "repo_manifest";
37
+ readonly "file_disposition.json": "file_disposition";
38
+ readonly "auto_fixes_applied.json": "auto_fixes_applied";
39
+ readonly "unit_manifest.json": "unit_manifest";
40
+ readonly "graph_bundle.json": "graph_bundle";
41
+ readonly "surface_manifest.json": "surface_manifest";
42
+ readonly "critical_flows.json": "critical_flows";
43
+ readonly "flow_coverage.json": "flow_coverage";
44
+ readonly "risk_register.json": "risk_register";
45
+ readonly "coverage_matrix.json": "coverage_matrix";
46
+ readonly "runtime_validation_tasks.json": "runtime_validation_tasks";
47
+ readonly "runtime_validation_report.json": "runtime_validation_report";
48
+ readonly "external_analyzer_results.json": "external_analyzer_results";
49
+ readonly "audit_results.jsonl": "audit_results";
50
+ readonly "audit_tasks.json": "audit_tasks";
51
+ readonly "requeue_tasks.json": "requeue_tasks";
52
+ readonly "merged_findings.json": "merged_findings";
53
+ readonly "root_cause_clusters.json": "root_cause_clusters";
54
+ readonly "synthesis_report.json": "synthesis_report";
55
+ readonly "audit_state.json": "audit_state";
56
+ readonly "artifact_metadata.json": "artifact_metadata";
57
+ };
36
58
  export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
37
59
  export declare function loadArtifactBundle(root: string): Promise<ArtifactBundle>;
38
60
  export declare function writeCoreArtifacts(root: string, bundle: ArtifactBundle): Promise<void>;
@@ -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 key = ARTIFACT_FILE_TO_BUNDLE_KEY[artifactName];
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) {
@@ -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
- state.last_executor = selectedExecutor ?? undefined;
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
- try {
31
- switch (selectedExecutor) {
32
- case "intake_executor":
33
- if (!options.root)
34
- throw new Error("advanceAudit intake_executor requires root");
35
- run = await runIntakeExecutor(bundle, options.root);
36
- break;
37
- case "structure_executor":
38
- run = runStructureExecutor(bundle);
39
- break;
40
- case "planning_executor":
41
- run = runPlanningExecutor(bundle, options.lineIndex ?? {});
42
- break;
43
- case "result_ingestion_executor":
44
- run = runResultIngestionExecutor(bundle, options.auditResults ?? bundle.audit_results ?? []);
45
- break;
46
- case "synthesis_executor":
47
- run = runSynthesisExecutor(bundle, options.auditResults);
48
- break;
49
- case "runtime_validation_update_executor":
50
- if (!options.runtimeValidationUpdates)
51
- throw new Error("advanceAudit runtime_validation_update_executor requires runtimeValidationUpdates");
52
- run = runRuntimeValidationUpdateExecutor(bundle, options.runtimeValidationUpdates);
53
- break;
54
- case "external_analyzer_import_executor":
55
- if (!options.externalAnalyzerResults)
56
- throw new Error("advanceAudit external_analyzer_import_executor requires externalAnalyzerResults");
57
- run = runExternalAnalyzerImportExecutor(bundle, options.externalAnalyzerResults);
58
- break;
59
- case "auto_fix_executor":
60
- if (!options.root)
61
- throw new Error("advanceAudit auto_fix_executor requires root");
62
- run = runAutoFixExecutor(bundle, options.root);
63
- break;
64
- case "syntax_resolution_executor":
65
- if (!options.root)
66
- throw new Error("advanceAudit syntax_resolution_executor requires root");
67
- run = runSyntaxResolutionExecutor(bundle, options.root);
68
- break;
69
- default: {
70
- const state = deriveAuditState(bundle);
71
- state.last_executor = selectedExecutor;
72
- state.last_obligation = selectedObligation ?? undefined;
73
- return {
74
- audit_state: state,
75
- selected_obligation: selectedObligation,
76
- selected_executor: selectedExecutor,
77
- progress_made: false,
78
- artifacts_written: ["audit_state.json"],
79
- progress_summary: `Executor ${selectedExecutor} is selected but not yet dispatched through advance-audit.`,
80
- next_likely_step: selectedObligation,
81
- updated_bundle: { ...bundle, audit_state: state },
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 = fileIndex.get(path);
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, bundle.coverage_matrix)
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.coverage_matrix
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: bundle.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
- if (normalized.includes("security") || normalized.includes("secret")) {
6
- return ["security", "correctness"];
7
- }
8
- if (normalized.includes("dependency") || normalized.includes("vuln")) {
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(coverageMatrix, reviewedRanges);
12
- return coverageMatrix;
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
- // failed to parse
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 ?? 3000;
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 lens = pickAnalyzerLens(result.category);
137
- const coverage = coverageIndex.get(result.path);
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}:${result.path}:${result.id}`;
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:${result.path}`,
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 ${result.path} under the ${lens} lens because ${externalAnalyzerResults.tool} reported: ${result.summary}`,
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 ?? `${task.artifacts_dir}/audit_tasks.json`;
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
- 'Set provider to "local-subprocess" in .audit-artifacts/session-config.json, ' +
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";
@@ -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" });
@@ -9,7 +9,6 @@ export interface LaunchFreshSessionInput {
9
9
  stderrPath: string;
10
10
  uiMode: "visible" | "headless";
11
11
  timeoutMs: number;
12
- model?: string;
13
12
  }
14
13
  export interface LaunchFreshSessionResult {
15
14
  accepted: boolean;
@@ -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>;