auditor-lambda 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) 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 +114 -64
  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/flowCoverage.js +11 -5
  26. package/dist/orchestrator/flowPlanning.d.ts +7 -2
  27. package/dist/orchestrator/flowPlanning.js +46 -21
  28. package/dist/orchestrator/flowRequeue.js +28 -8
  29. package/dist/orchestrator/internalExecutors.js +12 -8
  30. package/dist/orchestrator/planning.js +25 -3
  31. package/dist/orchestrator/requeue.js +11 -1
  32. package/dist/orchestrator/taskBuilder.d.ts +4 -2
  33. package/dist/orchestrator/taskBuilder.js +153 -52
  34. package/dist/orchestrator/unitBuilder.d.ts +3 -1
  35. package/dist/orchestrator/unitBuilder.js +24 -16
  36. package/dist/prompts/renderWorkerPrompt.d.ts +1 -1
  37. package/dist/prompts/renderWorkerPrompt.js +16 -8
  38. package/dist/providers/claudeCodeProvider.d.ts +4 -1
  39. package/dist/providers/claudeCodeProvider.js +8 -5
  40. package/dist/providers/localSubprocessProvider.d.ts +4 -0
  41. package/dist/providers/localSubprocessProvider.js +7 -2
  42. package/dist/providers/spawnLoggedCommand.d.ts +9 -1
  43. package/dist/providers/spawnLoggedCommand.js +77 -29
  44. package/dist/reporting/synthesis.d.ts +2 -0
  45. package/dist/reporting/synthesis.js +12 -9
  46. package/dist/supervisor/operatorHandoff.js +48 -18
  47. package/dist/supervisor/runLedger.d.ts +1 -1
  48. package/dist/supervisor/runLedger.js +112 -5
  49. package/dist/supervisor/sessionConfig.js +10 -10
  50. package/dist/types/externalAnalyzer.d.ts +3 -0
  51. package/dist/types/flowCoverage.d.ts +5 -1
  52. package/dist/types/flowCoverage.js +5 -1
  53. package/dist/types/flows.d.ts +5 -1
  54. package/dist/types/flows.js +1 -1
  55. package/dist/types/runLedger.d.ts +5 -1
  56. package/dist/types/runLedger.js +6 -1
  57. package/dist/types/runtimeValidation.d.ts +12 -3
  58. package/dist/types/runtimeValidation.js +16 -1
  59. package/dist/types/sessionConfig.d.ts +15 -2
  60. package/dist/types/sessionConfig.js +15 -1
  61. package/dist/types/surfaces.d.ts +4 -1
  62. package/dist/types/surfaces.js +1 -1
  63. package/dist/types/workerSession.d.ts +9 -0
  64. package/dist/types/workerSession.js +5 -1
  65. package/dist/validation/artifacts.d.ts +1 -1
  66. package/dist/validation/artifacts.js +33 -20
  67. package/dist/validation/auditResults.d.ts +2 -2
  68. package/dist/validation/auditResults.js +7 -15
  69. package/dist/validation/basic.d.ts +9 -1
  70. package/dist/validation/basic.js +40 -3
  71. package/dist/validation/sessionConfig.d.ts +4 -2
  72. package/dist/validation/sessionConfig.js +62 -15
  73. package/docs/agent-integrations.md +29 -9
  74. package/docs/next-steps.md +21 -4
  75. package/docs/packaging.md +14 -0
  76. package/docs/product-direction.md +22 -0
  77. package/docs/production-launch-bar.md +2 -0
  78. package/docs/releasing.md +17 -0
  79. package/docs/remediation-baseline.md +75 -0
  80. package/docs/run-flow.md +23 -11
  81. package/docs/session-config.md +50 -5
  82. package/docs/supervisor.md +7 -0
  83. package/docs/workflow-refactor-brief.md +177 -0
  84. package/package.json +1 -1
  85. package/schemas/audit_result.schema.json +4 -1
  86. package/schemas/audit_task.schema.json +3 -1
  87. package/schemas/coverage_matrix.schema.json +3 -3
  88. package/schemas/critical_flows.schema.json +6 -2
  89. package/schemas/file_disposition.schema.json +2 -2
  90. package/schemas/finding.schema.json +9 -4
  91. package/schemas/flow_coverage.schema.json +2 -2
  92. package/schemas/repo_manifest.schema.json +4 -4
  93. package/schemas/risk_register.schema.json +2 -2
  94. package/schemas/runtime_validation_report.schema.json +2 -2
  95. package/schemas/runtime_validation_tasks.schema.json +8 -2
  96. package/schemas/surface_manifest.schema.json +6 -3
  97. package/schemas/unit_manifest.schema.json +3 -2
  98. 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: {
@@ -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,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[];
@@ -1,5 +1,12 @@
1
+ import { claimFlowReviewBlocks } from "./flowPlanning.js";
1
2
  import { isTrivialAuditPath } from "./trivialAudit.js";
2
- function taskPriority(hasExternalSignal, lens) {
3
+ import { LENS_ORDER } from "./unitBuilder.js";
4
+ function taskPriority(hasExternalSignal, lens, isCriticalFlow = false) {
5
+ if (isCriticalFlow) {
6
+ return lens === "security" || lens === "reliability" || lens === "correctness"
7
+ ? "high"
8
+ : "medium";
9
+ }
3
10
  if (hasExternalSignal &&
4
11
  (lens === "security" || lens === "data_integrity" || lens === "reliability")) {
5
12
  return "high";
@@ -39,68 +46,161 @@ function pickAnalyzerLens(category) {
39
46
  return "correctness";
40
47
  }
41
48
  const DEFAULT_FILE_SPLIT_THRESHOLD = 3000;
42
- export function buildChunkedAuditTasks(unitManifest, unitLineIndex, options = {}) {
49
+ function buildCoverageIndex(coverageMatrix) {
50
+ return new Map(coverageMatrix.files.map((file) => [file.path, file]));
51
+ }
52
+ function getExternalSignalPaths(externalAnalyzerResults) {
53
+ const results = Array.isArray(externalAnalyzerResults?.results)
54
+ ? externalAnalyzerResults.results
55
+ : [];
56
+ return new Set(results
57
+ .map((item) => item && typeof item.path === "string" && item.path.length > 0
58
+ ? item.path
59
+ : null)
60
+ .filter((path) => path !== null));
61
+ }
62
+ function getExternalSignalResults(externalAnalyzerResults) {
63
+ if (!Array.isArray(externalAnalyzerResults?.results)) {
64
+ return [];
65
+ }
66
+ return externalAnalyzerResults.results.filter((item) => Boolean(item) &&
67
+ typeof item.path === "string" &&
68
+ typeof item.category === "string" &&
69
+ typeof item.summary === "string" &&
70
+ typeof item.id === "string");
71
+ }
72
+ export function buildChunkedAuditTasks(coverageMatrix, unitLineIndex, options = {}) {
43
73
  const fileSplitThreshold = options.file_split_threshold ?? DEFAULT_FILE_SPLIT_THRESHOLD;
44
74
  const allowed = new Set(options.limit_lenses ?? []);
45
75
  const enforceLensFilter = allowed.size > 0;
46
76
  const tasks = [];
47
77
  const seen = new Set();
48
- const externalPaths = new Set((options.external_analyzer_results?.results ?? []).map((item) => item.path));
49
- for (const unit of unitManifest.units) {
50
- const minimumLenses = ["correctness", "security"];
51
- const lensesToRun = Array.isArray(unit.required_lenses) && unit.required_lenses.length >= 2
52
- ? unit.required_lenses
53
- : minimumLenses;
54
- for (const lens of lensesToRun) {
78
+ const externalPaths = getExternalSignalPaths(options.external_analyzer_results);
79
+ const coverageByPath = new Map(coverageMatrix.files.map((file) => [file.path, file]));
80
+ const pendingByLens = new Map();
81
+ for (const file of coverageMatrix.files) {
82
+ if (file.audit_status === "excluded") {
83
+ continue;
84
+ }
85
+ for (const lens of file.required_lenses) {
86
+ if (file.completed_lenses.includes(lens)) {
87
+ continue;
88
+ }
55
89
  if (enforceLensFilter && !allowed.has(lens)) {
56
90
  continue;
57
91
  }
58
- const hasExternalSignal = unit.files.some((f) => externalPaths.has(f));
59
- const priority = taskPriority(hasExternalSignal, lens);
60
- const tags = hasExternalSignal ? ["external_analyzer_signal"] : [];
61
- const candidateFiles = unit.files.filter((filePath) => !isTrivialAuditPath(filePath, unitLineIndex[filePath] ?? 0, externalPaths.has(filePath)));
62
- // Split files that are individually too large to group; everything else
63
- // goes into one task so the agent can reason across file boundaries.
64
- const oversizedFiles = fileSplitThreshold > 0
65
- ? candidateFiles.filter((f) => (unitLineIndex[f] ?? 0) > fileSplitThreshold)
66
- : [];
67
- const normalFiles = candidateFiles.filter((f) => !oversizedFiles.includes(f));
68
- // One task for all normal-sized files in this unit under this lens.
69
- if (normalFiles.length > 0) {
70
- const id = `${unit.unit_id}:${lens}`;
71
- if (!seen.has(id)) {
72
- seen.add(id);
73
- tasks.push({
74
- task_id: id,
75
- unit_id: unit.unit_id,
76
- pass_id: `pass:${lens}`,
77
- lens,
78
- file_paths: normalFiles,
79
- rationale: `Audit ${unit.unit_id} (${normalFiles.length} file${normalFiles.length === 1 ? "" : "s"}) under the ${lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
80
- priority,
81
- tags,
82
- });
83
- }
92
+ if (isTrivialAuditPath(file.path, unitLineIndex[file.path] ?? 0, externalPaths.has(file.path))) {
93
+ continue;
84
94
  }
85
- // Oversized files each get their own task so the agent isn't overwhelmed.
86
- for (const filePath of oversizedFiles) {
87
- const id = `${unit.unit_id}:${lens}:${filePath}`;
88
- if (seen.has(id))
89
- continue;
90
- seen.add(id);
91
- const fileHasSignal = externalPaths.has(filePath);
95
+ const pending = pendingByLens.get(lens) ?? new Set();
96
+ pending.add(file.path);
97
+ pendingByLens.set(lens, pending);
98
+ }
99
+ }
100
+ function addTaskBlock(params) {
101
+ const oversizedFiles = fileSplitThreshold > 0
102
+ ? params.filePaths.filter((path) => (unitLineIndex[path] ?? 0) > fileSplitThreshold)
103
+ : [];
104
+ const oversizedSet = new Set(oversizedFiles);
105
+ const normalFiles = params.filePaths.filter((path) => !oversizedSet.has(path));
106
+ if (normalFiles.length > 0) {
107
+ const taskId = `${params.scopeId}:${params.lens}`;
108
+ if (!seen.has(taskId)) {
109
+ seen.add(taskId);
92
110
  tasks.push({
93
- task_id: id,
94
- unit_id: unit.unit_id,
95
- pass_id: `pass:${lens}`,
96
- lens,
97
- file_paths: [filePath],
98
- rationale: `Audit ${filePath} (large file, split from unit) under the ${lens} lens.${fileHasSignal ? " External analyzer signals raise priority." : ""}`,
99
- priority: taskPriority(fileHasSignal, lens),
100
- tags: fileHasSignal ? ["external_analyzer_signal", "large_file"] : ["large_file"],
111
+ task_id: taskId,
112
+ unit_id: params.unitId,
113
+ pass_id: params.passId,
114
+ lens: params.lens,
115
+ file_paths: normalFiles,
116
+ rationale: params.rationale(normalFiles, false),
117
+ priority: params.priority,
118
+ tags: params.tags.length > 0 ? params.tags : undefined,
101
119
  });
102
120
  }
103
121
  }
122
+ for (const filePath of oversizedFiles) {
123
+ const taskId = `${params.scopeId}:${params.lens}:${filePath}`;
124
+ if (seen.has(taskId)) {
125
+ continue;
126
+ }
127
+ seen.add(taskId);
128
+ tasks.push({
129
+ task_id: taskId,
130
+ unit_id: params.unitId,
131
+ pass_id: params.passId,
132
+ lens: params.lens,
133
+ file_paths: [filePath],
134
+ rationale: params.rationale([filePath], true),
135
+ priority: params.priority,
136
+ tags: params.tags.length > 0
137
+ ? [...new Set([...params.tags, "large_file"])]
138
+ : ["large_file"],
139
+ });
140
+ }
141
+ }
142
+ const assigned = new Set();
143
+ const flowBlocks = options.critical_flows
144
+ ? claimFlowReviewBlocks(options.critical_flows, pendingByLens, assigned)
145
+ : [];
146
+ for (const block of flowBlocks) {
147
+ const hasExternalSignal = block.file_paths.some((path) => externalPaths.has(path));
148
+ addTaskBlock({
149
+ scopeId: `flow:${block.flow_id}`,
150
+ unitId: `flow:${block.flow_id}`,
151
+ passId: `flow-pass:${block.lens}`,
152
+ lens: block.lens,
153
+ filePaths: block.file_paths,
154
+ priority: taskPriority(hasExternalSignal, block.lens, true),
155
+ tags: hasExternalSignal
156
+ ? ["critical_flow", `critical_flow:${block.flow_id}`, "external_analyzer_signal"]
157
+ : ["critical_flow", `critical_flow:${block.flow_id}`],
158
+ rationale: (filePaths, splitFromBlock) => splitFromBlock
159
+ ? `Audit ${filePaths[0]} (large file from critical flow ${block.flow_id}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
160
+ : `Audit critical flow ${block.flow_id} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
161
+ });
162
+ }
163
+ const groupedRemainders = new Map();
164
+ for (const lens of LENS_ORDER) {
165
+ const pendingPaths = pendingByLens.get(lens);
166
+ if (!pendingPaths || pendingPaths.size === 0) {
167
+ continue;
168
+ }
169
+ for (const path of [...pendingPaths].sort((a, b) => a.localeCompare(b))) {
170
+ if (assigned.has(`${lens}:${path}`)) {
171
+ continue;
172
+ }
173
+ const record = coverageByPath.get(path);
174
+ const unitId = record?.unit_ids[0] ?? `review:${path.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
175
+ const key = `${lens}|${unitId}`;
176
+ const current = groupedRemainders.get(key) ?? {
177
+ lens,
178
+ unitId,
179
+ filePaths: [],
180
+ };
181
+ current.filePaths.push(path);
182
+ groupedRemainders.set(key, current);
183
+ }
184
+ }
185
+ for (const block of [...groupedRemainders.values()].sort((a, b) => {
186
+ const lensDelta = LENS_ORDER.indexOf(a.lens) - LENS_ORDER.indexOf(b.lens);
187
+ if (lensDelta !== 0)
188
+ return lensDelta;
189
+ return a.unitId.localeCompare(b.unitId);
190
+ })) {
191
+ const hasExternalSignal = block.filePaths.some((path) => externalPaths.has(path));
192
+ addTaskBlock({
193
+ scopeId: block.unitId,
194
+ unitId: block.unitId,
195
+ passId: `pass:${block.lens}`,
196
+ lens: block.lens,
197
+ filePaths: block.filePaths,
198
+ priority: taskPriority(hasExternalSignal, block.lens),
199
+ tags: hasExternalSignal ? ["external_analyzer_signal"] : [],
200
+ rationale: (filePaths, splitFromBlock) => splitFromBlock
201
+ ? `Audit ${filePaths[0]} (large file split from ${block.unitId}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`
202
+ : `Audit ${block.unitId} (${filePaths.length} file${filePaths.length === 1 ? "" : "s"}) under the ${block.lens} lens.${hasExternalSignal ? " External analyzer signals raise priority." : ""}`,
203
+ });
104
204
  }
105
205
  return tasks.sort((a, b) => {
106
206
  const priorityDelta = priorityRank(b.priority) - priorityRank(a.priority);
@@ -119,12 +219,13 @@ export function buildExternalSignalTasks(coverageMatrix, _unitLineIndex, externa
119
219
  }
120
220
  const tasks = [];
121
221
  const seen = new Set();
122
- for (const result of externalAnalyzerResults.results) {
222
+ const coverageByPath = buildCoverageIndex(coverageMatrix);
223
+ for (const result of getExternalSignalResults(externalAnalyzerResults)) {
123
224
  const safeCategory = sanitizeField(result.category, 80);
124
225
  const safePath = sanitizeField(result.path ?? "", 260);
125
226
  const safeSummary = sanitizeField(result.summary ?? "", 200);
126
227
  const lens = pickAnalyzerLens(safeCategory);
127
- const coverage = coverageMatrix.files.find((file) => file.path === result.path);
228
+ const coverage = coverageByPath.get(result.path);
128
229
  if (!coverage || coverage.audit_status === "excluded") {
129
230
  continue;
130
231
  }
@@ -1,3 +1,5 @@
1
- import type { RepoManifest, UnitManifest } from "../types.js";
1
+ import type { Lens, RepoManifest, UnitManifest } from "../types.js";
2
2
  import type { FileDisposition } from "../types/disposition.js";
3
+ export declare const LENS_ORDER: Lens[];
4
+ export declare function deriveRequiredLensesForPath(path: string): Lens[];
3
5
  export declare function buildUnitManifest(repoManifest: RepoManifest, disposition?: FileDisposition): UnitManifest;
@@ -60,20 +60,30 @@ function inferUnitId(path, kind) {
60
60
  }
61
61
  return `${kind}-${path.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
62
62
  }
63
+ export const LENS_ORDER = [
64
+ "security",
65
+ "correctness",
66
+ "reliability",
67
+ "data_integrity",
68
+ "performance",
69
+ "operability",
70
+ "config_deployment",
71
+ "maintainability",
72
+ "tests",
73
+ ];
63
74
  function sortLenses(lenses) {
64
- const order = [
65
- "security",
66
- "correctness",
67
- "reliability",
68
- "data_integrity",
69
- "performance",
70
- "operability",
71
- "config_deployment",
72
- "maintainability",
73
- "tests",
74
- ];
75
75
  const set = new Set(lenses);
76
- return order.filter((lens) => set.has(lens));
76
+ return LENS_ORDER.filter((lens) => set.has(lens));
77
+ }
78
+ export function deriveRequiredLensesForPath(path) {
79
+ const assignment = bucketFile(path);
80
+ const required = new Set();
81
+ for (const bucket of assignment.buckets) {
82
+ for (const lens of LENS_MAP[bucket]) {
83
+ required.add(lens);
84
+ }
85
+ }
86
+ return sortLenses(required);
77
87
  }
78
88
  function inferCriticalFlows(files, requiredLenses) {
79
89
  const flows = new Set();
@@ -123,10 +133,8 @@ export function buildUnitManifest(repoManifest, disposition) {
123
133
  }
124
134
  const assignment = bucketFile(file.path);
125
135
  const required = new Set(existing.required_lenses);
126
- for (const bucket of assignment.buckets) {
127
- for (const lens of LENS_MAP[bucket]) {
128
- required.add(lens);
129
- }
136
+ for (const lens of deriveRequiredLensesForPath(file.path)) {
137
+ required.add(lens);
130
138
  }
131
139
  existing.required_lenses = sortLenses(required);
132
140
  const riskScore = new Set(assignment.buckets).size +
@@ -1,2 +1,2 @@
1
- import type { WorkerTask } from "../types/workerSession.js";
1
+ import { type WorkerTask } from "../types/workerSession.js";
2
2
  export declare function renderWorkerPrompt(task: WorkerTask): string;