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.
Files changed (104) hide show
  1. package/README.md +6 -0
  2. package/audit-code-wrapper-lib.mjs +1 -1
  3. package/dist/adapters/eslint.js +9 -5
  4. package/dist/cli.d.ts +42 -1
  5. package/dist/cli.js +234 -63
  6. package/dist/extractors/bucketing.d.ts +4 -0
  7. package/dist/extractors/bucketing.js +6 -2
  8. package/dist/extractors/disposition.d.ts +4 -0
  9. package/dist/extractors/disposition.js +6 -2
  10. package/dist/extractors/fileInventory.js +24 -28
  11. package/dist/extractors/flows.d.ts +5 -0
  12. package/dist/extractors/flows.js +18 -38
  13. package/dist/extractors/pathPatterns.d.ts +10 -3
  14. package/dist/extractors/pathPatterns.js +109 -61
  15. package/dist/extractors/surfaces.d.ts +4 -0
  16. package/dist/extractors/surfaces.js +11 -11
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +2 -1
  19. package/dist/io/artifacts.d.ts +55 -40
  20. package/dist/io/artifacts.js +73 -110
  21. package/dist/io/json.js +52 -21
  22. package/dist/io/runArtifacts.d.ts +1 -1
  23. package/dist/io/runArtifacts.js +26 -3
  24. package/dist/orchestrator/advance.js +83 -62
  25. package/dist/orchestrator/autoFixExecutor.js +32 -15
  26. package/dist/orchestrator/flowCoverage.js +11 -5
  27. package/dist/orchestrator/flowPlanning.d.ts +7 -2
  28. package/dist/orchestrator/flowPlanning.js +46 -21
  29. package/dist/orchestrator/flowRequeue.js +28 -8
  30. package/dist/orchestrator/internalExecutors.js +12 -8
  31. package/dist/orchestrator/localCommands.d.ts +14 -0
  32. package/dist/orchestrator/localCommands.js +124 -0
  33. package/dist/orchestrator/planning.js +25 -3
  34. package/dist/orchestrator/requeue.js +11 -1
  35. package/dist/orchestrator/syntaxResolutionExecutor.js +60 -59
  36. package/dist/orchestrator/taskBuilder.d.ts +4 -2
  37. package/dist/orchestrator/taskBuilder.js +153 -52
  38. package/dist/orchestrator/unitBuilder.d.ts +3 -1
  39. package/dist/orchestrator/unitBuilder.js +24 -16
  40. package/dist/prompts/renderWorkerPrompt.d.ts +1 -1
  41. package/dist/prompts/renderWorkerPrompt.js +16 -8
  42. package/dist/providers/claudeCodeProvider.d.ts +4 -1
  43. package/dist/providers/claudeCodeProvider.js +8 -5
  44. package/dist/providers/localSubprocessProvider.d.ts +4 -0
  45. package/dist/providers/localSubprocessProvider.js +7 -2
  46. package/dist/providers/spawnLoggedCommand.d.ts +9 -1
  47. package/dist/providers/spawnLoggedCommand.js +77 -29
  48. package/dist/reporting/synthesis.d.ts +2 -0
  49. package/dist/reporting/synthesis.js +12 -9
  50. package/dist/supervisor/operatorHandoff.d.ts +1 -1
  51. package/dist/supervisor/operatorHandoff.js +56 -18
  52. package/dist/supervisor/runLedger.d.ts +1 -1
  53. package/dist/supervisor/runLedger.js +112 -5
  54. package/dist/supervisor/sessionConfig.js +10 -10
  55. package/dist/types/externalAnalyzer.d.ts +3 -0
  56. package/dist/types/flowCoverage.d.ts +5 -1
  57. package/dist/types/flowCoverage.js +5 -1
  58. package/dist/types/flows.d.ts +5 -1
  59. package/dist/types/flows.js +1 -1
  60. package/dist/types/runLedger.d.ts +5 -1
  61. package/dist/types/runLedger.js +6 -1
  62. package/dist/types/runtimeValidation.d.ts +12 -3
  63. package/dist/types/runtimeValidation.js +16 -1
  64. package/dist/types/sessionConfig.d.ts +15 -2
  65. package/dist/types/sessionConfig.js +15 -1
  66. package/dist/types/surfaces.d.ts +4 -1
  67. package/dist/types/surfaces.js +1 -1
  68. package/dist/types/workerSession.d.ts +9 -0
  69. package/dist/types/workerSession.js +5 -1
  70. package/dist/validation/artifacts.d.ts +1 -1
  71. package/dist/validation/artifacts.js +33 -20
  72. package/dist/validation/auditResults.d.ts +2 -2
  73. package/dist/validation/auditResults.js +7 -15
  74. package/dist/validation/basic.d.ts +9 -1
  75. package/dist/validation/basic.js +40 -3
  76. package/dist/validation/sessionConfig.d.ts +4 -2
  77. package/dist/validation/sessionConfig.js +62 -15
  78. package/docs/agent-integrations.md +31 -11
  79. package/docs/next-steps.md +21 -4
  80. package/docs/packaging.md +14 -0
  81. package/docs/product-direction.md +22 -0
  82. package/docs/production-launch-bar.md +2 -0
  83. package/docs/releasing.md +17 -0
  84. package/docs/remediation-baseline.md +75 -0
  85. package/docs/run-flow.md +23 -11
  86. package/docs/session-config.md +53 -6
  87. package/docs/supervisor.md +7 -0
  88. package/docs/workflow-refactor-brief.md +177 -0
  89. package/package.json +1 -1
  90. package/schemas/audit-code-v1alpha1.schema.json +1 -0
  91. package/schemas/audit_result.schema.json +4 -1
  92. package/schemas/audit_task.schema.json +3 -1
  93. package/schemas/coverage_matrix.schema.json +3 -3
  94. package/schemas/critical_flows.schema.json +6 -2
  95. package/schemas/file_disposition.schema.json +2 -2
  96. package/schemas/finding.schema.json +9 -4
  97. package/schemas/flow_coverage.schema.json +2 -2
  98. package/schemas/repo_manifest.schema.json +4 -4
  99. package/schemas/risk_register.schema.json +2 -2
  100. package/schemas/runtime_validation_report.schema.json +2 -2
  101. package/schemas/runtime_validation_tasks.schema.json +8 -2
  102. package/schemas/surface_manifest.schema.json +6 -3
  103. package/schemas/unit_manifest.schema.json +3 -2
  104. 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
- export function buildFlowAwareTaskAugmentations(existingTasks, criticalFlows, _lineIndex) {
7
- const seenTaskIds = new Set(existingTasks.map((task) => task.task_id));
8
- // Signature: lens + sorted file list, so duplicate (flow, lens) combos across
9
- // different flow groupings are still deduplicated.
10
- const existingSignatures = new Set(existingTasks.map((t) => `${t.lens}|${[...t.file_paths].sort().join(",")}`));
11
- const extraTasks = [];
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 desiredLenses = flow.concerns.filter((concern) => DEFAULT_FLOW_LENS_PRIORITY.includes(concern));
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
- // One task per (flow, lens) with all flow paths together so the agent
16
- // can trace data across file boundaries in a single pass.
17
- const taskId = `flow:${flow.id}:${lens}`;
18
- const signature = `${lens}|${[...flow.paths].sort().join(",")}`;
19
- if (seenTaskIds.has(taskId) || existingSignatures.has(signature)) {
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
- extraTasks.push({
23
- task_id: taskId,
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: [...flow.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
- return extraTasks;
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(coverageMatrix, path, lens) {
23
- const record = coverageMatrix.files.find((file) => file.path === path);
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 = new Set((externalAnalyzerResults?.results ?? []).map((item) => item.path));
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 missingLenses = record.required_lenses.filter((lens) => !record.completed_lenses.includes(lens));
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
- continue;
62
+ throw new Error(`buildFlowRequeueTasks encountered unsupported lens "${String(lensName)}" for flow ${record.flow_id}.`);
43
63
  }
44
- for (const path of flow.paths) {
45
- if (!fileStillNeedsLens(coverageMatrix, path, lensName)) {
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, buildExternalSignalTasks, } from "./taskBuilder.js";
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 baseTasks = buildChunkedAuditTasks(bundle.unit_manifest, lineIndex, {
132
+ const auditTasks = buildChunkedAuditTasks(coverage, lineIndex, {
132
133
  external_analyzer_results: externalAnalyzerResults,
134
+ critical_flows: bundle.critical_flows,
133
135
  });
134
- const analyzerTasks = buildExternalSignalTasks(coverage, lineIndex, externalAnalyzerResults);
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: auditTasks,
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 ${auditTasks.length} tasks and ${requeuePayload.task_count} requeue tasks.` +
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
- for (const result of externalAnalyzerResults.results) {
25
- const record = coverage.files.find((file) => file.path === result.path);
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
- applyUnitCoverage(coverage, path, unit.unit_id, unit.required_lenses);
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 = new Set((externalAnalyzerResults?.results ?? []).map((item) => item.path));
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 { execSync } from "node:child_process";
1
+ import { join } from "node:path";
2
+ import { resolveNodeTool, runFirstAvailableCommand } from "./localCommands.js";
2
3
  function runTsc(root) {
3
4
  const results = [];
4
- try {
5
- execSync("npx tsc --noEmit", {
6
- cwd: root,
7
- stdio: "pipe",
8
- windowsHide: true,
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
- catch (error) {
12
- if (error.stdout) {
13
- const output = error.stdout.toString();
14
- const lines = output.split("\n");
15
- for (const line of lines) {
16
- const match = line.match(/^([^:]+)\((\d+),\d+\):\s+(error\s+TS\d+:.*)$/);
17
- if (match) {
18
- results.push({
19
- id: `tsc-${results.length}`,
20
- category: "correctness",
21
- severity: "error",
22
- path: match[1].replace(/\\/g, "/"),
23
- line_start: parseInt(match[2], 10),
24
- summary: match[3],
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
- try {
39
- execSync("npx eslint . --ext .ts,.js,.tsx,.jsx --format json", {
40
- cwd: root,
41
- stdio: "pipe",
42
- windowsHide: true,
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
- catch (error) {
46
- if (error.stdout) {
47
- try {
48
- const output = JSON.parse(error.stdout.toString());
49
- for (const fileResult of output) {
50
- for (const msg of fileResult.messages) {
51
- results.push({
52
- id: `eslint-${results.length}`,
53
- category: "maintainability",
54
- severity: msg.severity === 2 ? "error" : "warning",
55
- path: fileResult.filePath
56
- .replace(/\\/g, "/")
57
- .replace(root.replace(/\\/g, "/") + "/", ""),
58
- line_start: msg.line,
59
- summary: msg.message,
60
- rule: msg.ruleId || "eslint-error",
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
- else {
70
- process.stderr.write(`[syntax-resolution] eslint exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
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, UnitManifest } from "../types.js";
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(unitManifest: UnitManifest, unitLineIndex: UnitLineIndex, options?: BuildChunkedTaskOptions): AuditTask[];
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[];