auditor-lambda 0.3.40 → 0.5.0

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 (193) hide show
  1. package/audit-code-wrapper-lib.mjs +20 -2
  2. package/dist/cli/args.d.ts +59 -0
  3. package/dist/cli/args.js +244 -0
  4. package/dist/cli/dispatch.d.ts +80 -0
  5. package/dist/cli/dispatch.js +532 -0
  6. package/dist/cli/prompts.d.ts +37 -0
  7. package/dist/cli/prompts.js +225 -0
  8. package/dist/cli/steps.d.ts +29 -0
  9. package/dist/cli/steps.js +30 -0
  10. package/dist/cli/waveManifest.d.ts +40 -0
  11. package/dist/cli/waveManifest.js +41 -0
  12. package/dist/cli/workerResult.d.ts +18 -0
  13. package/dist/cli/workerResult.js +42 -0
  14. package/dist/cli.d.ts +2 -22
  15. package/dist/cli.js +442 -975
  16. package/dist/extractors/analyzers/css.d.ts +2 -0
  17. package/dist/extractors/analyzers/css.js +101 -0
  18. package/dist/extractors/analyzers/html.d.ts +2 -0
  19. package/dist/extractors/analyzers/html.js +92 -0
  20. package/dist/extractors/analyzers/merge.d.ts +14 -0
  21. package/dist/extractors/analyzers/merge.js +85 -0
  22. package/dist/extractors/analyzers/python.d.ts +2 -0
  23. package/dist/extractors/analyzers/python.js +104 -0
  24. package/dist/extractors/analyzers/registry.d.ts +33 -0
  25. package/dist/extractors/analyzers/registry.js +100 -0
  26. package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
  27. package/dist/extractors/analyzers/resourceUrl.js +25 -0
  28. package/dist/extractors/analyzers/sql.d.ts +2 -0
  29. package/dist/extractors/analyzers/sql.js +19 -0
  30. package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
  31. package/dist/extractors/analyzers/treeSitter.js +111 -0
  32. package/dist/extractors/analyzers/types.d.ts +53 -0
  33. package/dist/extractors/analyzers/typescript.d.ts +2 -0
  34. package/dist/extractors/analyzers/typescript.js +257 -0
  35. package/dist/extractors/browserExtension.d.ts +1 -3
  36. package/dist/extractors/browserExtension.js +2 -2
  37. package/dist/extractors/designAssessment.d.ts +1 -3
  38. package/dist/extractors/disposition.d.ts +2 -1
  39. package/dist/extractors/disposition.js +11 -1
  40. package/dist/extractors/flows.d.ts +1 -3
  41. package/dist/extractors/flows.js +2 -2
  42. package/dist/extractors/graph.d.ts +2 -2
  43. package/dist/extractors/graph.js +171 -327
  44. package/dist/extractors/graphManifestEdges.d.ts +1 -1
  45. package/dist/extractors/graphPathUtils.d.ts +1 -1
  46. package/dist/extractors/graphPythonImports.d.ts +18 -0
  47. package/dist/extractors/graphPythonImports.js +362 -0
  48. package/dist/extractors/pathPatterns.d.ts +6 -0
  49. package/dist/extractors/pathPatterns.js +8 -0
  50. package/dist/extractors/risk.d.ts +1 -2
  51. package/dist/extractors/surfaces.d.ts +1 -3
  52. package/dist/extractors/surfaces.js +2 -2
  53. package/dist/io/artifacts.d.ts +12 -5
  54. package/dist/io/artifacts.js +13 -1
  55. package/dist/io/runArtifacts.js +1 -1
  56. package/dist/mcp/server.js +1 -1
  57. package/dist/orchestrator/advance.d.ts +21 -0
  58. package/dist/orchestrator/advance.js +69 -7
  59. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  60. package/dist/orchestrator/auditTaskUtils.js +27 -0
  61. package/dist/orchestrator/dependencyMap.js +27 -0
  62. package/dist/orchestrator/edgeReasoning.d.ts +39 -0
  63. package/dist/orchestrator/edgeReasoning.js +125 -0
  64. package/dist/orchestrator/executors.js +11 -1
  65. package/dist/orchestrator/fileAnchors.d.ts +1 -1
  66. package/dist/orchestrator/fileIntegrity.d.ts +7 -0
  67. package/dist/orchestrator/fileIntegrity.js +41 -0
  68. package/dist/orchestrator/flowCoverage.d.ts +1 -1
  69. package/dist/orchestrator/flowPlanning.d.ts +1 -1
  70. package/dist/orchestrator/flowRequeue.d.ts +1 -1
  71. package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
  72. package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
  73. package/dist/orchestrator/internalExecutors.d.ts +13 -2
  74. package/dist/orchestrator/internalExecutors.js +112 -16
  75. package/dist/orchestrator/localCommands.js +6 -25
  76. package/dist/orchestrator/nextStep.d.ts +2 -1
  77. package/dist/orchestrator/nextStep.js +3 -1
  78. package/dist/orchestrator/planning.d.ts +1 -1
  79. package/dist/orchestrator/requeueCommand.d.ts +1 -1
  80. package/dist/orchestrator/reviewPackets.d.ts +37 -4
  81. package/dist/orchestrator/reviewPackets.js +113 -158
  82. package/dist/orchestrator/runtimeValidation.d.ts +1 -1
  83. package/dist/orchestrator/runtimeValidation.js +4 -31
  84. package/dist/orchestrator/scope.d.ts +62 -0
  85. package/dist/orchestrator/scope.js +227 -0
  86. package/dist/orchestrator/state.js +2 -0
  87. package/dist/orchestrator/taskBuilder.d.ts +1 -1
  88. package/dist/orchestrator/taskBuilder.js +1 -12
  89. package/dist/orchestrator/unionFind.d.ts +7 -0
  90. package/dist/orchestrator/unionFind.js +32 -0
  91. package/dist/orchestrator/unitBuilder.d.ts +2 -2
  92. package/dist/orchestrator/unitBuilder.js +4 -18
  93. package/dist/prompts/renderWorkerPrompt.js +18 -1
  94. package/dist/providers/claudeCodeProvider.d.ts +4 -4
  95. package/dist/providers/claudeCodeProvider.js +9 -3
  96. package/dist/providers/constants.d.ts +1 -1
  97. package/dist/providers/constants.js +1 -1
  98. package/dist/providers/index.d.ts +1 -2
  99. package/dist/providers/index.js +5 -4
  100. package/dist/providers/localSubprocessProvider.d.ts +2 -2
  101. package/dist/providers/localSubprocessProvider.js +1 -1
  102. package/dist/providers/opencodeProvider.d.ts +4 -4
  103. package/dist/providers/opencodeProvider.js +7 -2
  104. package/dist/providers/spawnLoggedCommand.d.ts +3 -1
  105. package/dist/providers/spawnLoggedCommand.js +21 -0
  106. package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
  107. package/dist/providers/subprocessTemplateProvider.js +8 -3
  108. package/dist/providers/vscodeTaskProvider.d.ts +3 -4
  109. package/dist/providers/vscodeTaskProvider.js +2 -2
  110. package/dist/quota/discoveredLimits.js +1 -1
  111. package/dist/quota/hostLimits.d.ts +1 -2
  112. package/dist/quota/hostLimits.js +4 -46
  113. package/dist/quota/index.d.ts +18 -15
  114. package/dist/quota/index.js +4 -9
  115. package/dist/quota/scheduler.d.ts +1 -3
  116. package/dist/quota/scheduler.js +1 -2
  117. package/dist/reporting/synthesis.d.ts +37 -3
  118. package/dist/reporting/synthesis.js +97 -16
  119. package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
  120. package/dist/reporting/synthesisNarrativePrompt.js +60 -0
  121. package/dist/reporting/workBlocks.d.ts +2 -11
  122. package/dist/supervisor/operatorHandoff.js +1 -1
  123. package/dist/supervisor/runLedger.d.ts +1 -1
  124. package/dist/supervisor/runLedger.js +2 -2
  125. package/dist/supervisor/sessionConfig.d.ts +8 -1
  126. package/dist/supervisor/sessionConfig.js +22 -3
  127. package/dist/types/analyzerCapability.d.ts +16 -0
  128. package/dist/types/auditScope.d.ts +43 -0
  129. package/dist/types/auditScope.js +14 -0
  130. package/dist/types/reviewPlanning.d.ts +1 -1
  131. package/dist/types/synthesisNarrative.d.ts +7 -0
  132. package/dist/types/synthesisNarrative.js +5 -0
  133. package/dist/types/workerSession.d.ts +6 -0
  134. package/dist/types.d.ts +2 -19
  135. package/dist/validation/artifacts.d.ts +1 -1
  136. package/dist/validation/artifacts.js +10 -1
  137. package/dist/validation/auditResults.d.ts +1 -1
  138. package/dist/validation/auditResults.js +1 -1
  139. package/dist/validation/sessionConfig.d.ts +2 -3
  140. package/dist/validation/sessionConfig.js +25 -3
  141. package/package.json +7 -3
  142. package/schemas/analyzer_capability.schema.json +47 -0
  143. package/schemas/audit_findings.schema.json +141 -0
  144. package/schemas/finding.schema.json +2 -1
  145. package/schemas/graph_bundle.schema.json +5 -0
  146. package/schemas/scope.schema.json +46 -0
  147. package/scripts/postinstall.mjs +0 -1
  148. package/dist/io/json.d.ts +0 -10
  149. package/dist/io/json.js +0 -142
  150. package/dist/providers/types.d.ts +0 -33
  151. package/dist/quota/compositeQuotaSource.d.ts +0 -7
  152. package/dist/quota/compositeQuotaSource.js +0 -20
  153. package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
  154. package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
  155. package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
  156. package/dist/quota/errorParsers/genericErrorParser.js +0 -7
  157. package/dist/quota/errorParsers/index.d.ts +0 -5
  158. package/dist/quota/errorParsers/index.js +0 -12
  159. package/dist/quota/errorParsing.d.ts +0 -7
  160. package/dist/quota/errorParsing.js +0 -69
  161. package/dist/quota/fileLock.d.ts +0 -6
  162. package/dist/quota/fileLock.js +0 -64
  163. package/dist/quota/learnedQuotaSource.d.ts +0 -7
  164. package/dist/quota/learnedQuotaSource.js +0 -25
  165. package/dist/quota/limits.d.ts +0 -16
  166. package/dist/quota/limits.js +0 -77
  167. package/dist/quota/quotaSource.d.ts +0 -12
  168. package/dist/quota/slidingWindow.d.ts +0 -4
  169. package/dist/quota/slidingWindow.js +0 -28
  170. package/dist/quota/state.d.ts +0 -15
  171. package/dist/quota/state.js +0 -148
  172. package/dist/quota/types.d.ts +0 -67
  173. package/dist/quota/types.js +0 -1
  174. package/dist/reporting/rootCause.d.ts +0 -10
  175. package/dist/reporting/rootCause.js +0 -146
  176. package/dist/types/disposition.d.ts +0 -9
  177. package/dist/types/disposition.js +0 -1
  178. package/dist/types/flows.d.ts +0 -17
  179. package/dist/types/flows.js +0 -1
  180. package/dist/types/graph.d.ts +0 -22
  181. package/dist/types/graph.js +0 -1
  182. package/dist/types/risk.d.ts +0 -9
  183. package/dist/types/risk.js +0 -1
  184. package/dist/types/runLedger.d.ts +0 -17
  185. package/dist/types/runLedger.js +0 -6
  186. package/dist/types/sessionConfig.d.ts +0 -79
  187. package/dist/types/sessionConfig.js +0 -15
  188. package/dist/types/surfaces.d.ts +0 -15
  189. package/dist/types/surfaces.js +0 -1
  190. package/dist/validation/basic.d.ts +0 -13
  191. package/dist/validation/basic.js +0 -46
  192. /package/dist/{providers → extractors/analyzers}/types.js +0 -0
  193. /package/dist/{quota/quotaSource.js → types/analyzerCapability.js} +0 -0
@@ -1,22 +1,70 @@
1
1
  import { createHash } from "node:crypto";
2
- import { LENS_ORDER } from "./unitBuilder.js";
2
+ import { estimateTokensFromBytes, isRecord } from "@audit-tools/shared";
3
+ import { LENS_ORDER, priorityRank, sortLenses } from "./auditTaskUtils.js";
4
+ import { UnionFind } from "./unionFind.js";
3
5
  const DEFAULT_MAX_TASKS_PER_PACKET = 0;
4
6
  const DEFAULT_TARGET_PACKET_LINES = 8000;
5
7
  export const ESTIMATED_TOKENS_PER_LINE = 4;
6
8
  export const ESTIMATED_PACKET_PROMPT_TOKENS = 900;
7
- export function estimateTaskGroupTokens(tasks) {
8
- let totalLines = 0;
9
+ // Default per-packet content-token budget. Kept equal to the legacy
10
+ // line-target × per-line estimate so byte-derived sizing lands on the same
11
+ // thresholds as the old line-based sizing when the line fallback is in effect.
12
+ const DEFAULT_TARGET_PACKET_TOKENS = DEFAULT_TARGET_PACKET_LINES * ESTIMATED_TOKENS_PER_LINE;
13
+ /**
14
+ * Build a path → size_bytes index from a repo manifest. Byte counts are
15
+ * recorded during intake, so this never reads files. Review packet token
16
+ * estimates are derived from these bytes (Phase 2) instead of counted lines.
17
+ */
18
+ export function sizeIndexFromManifest(repoManifest) {
19
+ if (!repoManifest)
20
+ return {};
21
+ return Object.fromEntries(repoManifest.files.map((file) => [file.path, file.size_bytes]));
22
+ }
23
+ /**
24
+ * Estimated content tokens for a single file. Prefers a byte-based estimate
25
+ * from `sizeIndex` (sourced from the repo manifest); falls back to the legacy
26
+ * line-based estimate when no positive byte count is available (e.g. manually
27
+ * built tasks in tests, or paths absent from the manifest).
28
+ */
29
+ function pathContentTokens(owner, path, sizeIndex, lineIndex) {
30
+ const bytes = sizeIndex?.[path];
31
+ if (typeof bytes === "number" && bytes > 0) {
32
+ return estimateTokensFromBytes(bytes);
33
+ }
34
+ const lines = owner?.file_line_counts?.[path] ?? lineIndex?.[path] ?? 0;
35
+ return lines * ESTIMATED_TOKENS_PER_LINE;
36
+ }
37
+ /** Estimated content tokens for one task across all of its files. */
38
+ function taskContentTokens(task, sizeIndex, lineIndex) {
39
+ return task.file_paths.reduce((sum, path) => sum + pathContentTokens(task, path, sizeIndex, lineIndex), 0);
40
+ }
41
+ /**
42
+ * Estimated content tokens across a set of file paths, resolving an owning task
43
+ * per path so the line fallback can read its `file_line_counts`. Shared files
44
+ * are counted once.
45
+ */
46
+ function fileGroupContentTokens(filePaths, tasks, sizeIndex, lineIndex) {
47
+ let total = 0;
48
+ for (const path of filePaths) {
49
+ const owner = tasks.find((task) => task.file_paths.includes(path));
50
+ total += pathContentTokens(owner, path, sizeIndex, lineIndex);
51
+ }
52
+ return total;
53
+ }
54
+ export function estimateTaskGroupTokens(tasks, sizeIndex, lineIndex) {
55
+ let contentTokens = 0;
9
56
  for (const task of tasks) {
10
- if (task.file_line_counts) {
11
- for (const count of Object.values(task.file_line_counts)) {
12
- totalLines += count;
13
- }
14
- }
57
+ contentTokens += taskContentTokens(task, sizeIndex, lineIndex);
15
58
  }
16
- return ESTIMATED_PACKET_PROMPT_TOKENS + totalLines * ESTIMATED_TOKENS_PER_LINE;
59
+ return ESTIMATED_PACKET_PROMPT_TOKENS + contentTokens;
17
60
  }
18
61
  const PACKET_EXPANSION_MIN_CONFIDENCE = 0.65;
19
- const HIGH_FAN_DEGREE_THRESHOLD = 12;
62
+ /**
63
+ * Fan-in / fan-out degree above which a node is treated as a hub. Exported so
64
+ * the Phase 3 delta-scope expansion skips the same hubs that packet planning
65
+ * skips, preventing scope blow-up through highly-connected modules.
66
+ */
67
+ export const HIGH_FAN_DEGREE_THRESHOLD = 12;
20
68
  const HIGH_FAN_EXPANSION_CONFIDENCE = 0.99;
21
69
  const MAX_PACKET_KEY_EDGES = 8;
22
70
  const MAX_PACKET_BOUNDARY_FILES = 12;
@@ -57,24 +105,9 @@ const BROAD_ANALYZER_OWNERSHIP_ROOTS = new Set([
57
105
  "spec",
58
106
  "specs",
59
107
  ]);
60
- function priorityRank(priority) {
61
- switch (priority) {
62
- case "high":
63
- return 3;
64
- case "medium":
65
- return 2;
66
- case "low":
67
- default:
68
- return 1;
69
- }
70
- }
71
108
  function normalizePriority(priority) {
72
109
  return priority ?? "low";
73
110
  }
74
- function sortLenses(lenses) {
75
- const set = new Set(lenses);
76
- return LENS_ORDER.filter((lens) => set.has(lens));
77
- }
78
111
  function lineCountForPath(task, path, lineIndex) {
79
112
  return task.file_line_counts?.[path] ?? lineIndex?.[path] ?? 0;
80
113
  }
@@ -99,13 +132,10 @@ function buildTaskGroups(tasks) {
99
132
  }
100
133
  return groups;
101
134
  }
102
- function normalizeGraphPath(path) {
135
+ export function normalizeGraphPath(path) {
103
136
  return path.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
104
137
  }
105
- function isRecord(value) {
106
- return value !== null && typeof value === "object" && !Array.isArray(value);
107
- }
108
- function collectGraphEdges(graphBundle) {
138
+ export function collectGraphEdges(graphBundle) {
109
139
  if (!graphBundle?.graphs) {
110
140
  return [];
111
141
  }
@@ -141,7 +171,7 @@ function collectGraphEdges(graphBundle) {
141
171
  }
142
172
  return edges;
143
173
  }
144
- function graphEdgeConfidence(edge) {
174
+ export function graphEdgeConfidence(edge) {
145
175
  if (typeof edge.confidence === "number" && Number.isFinite(edge.confidence)) {
146
176
  return Math.min(1, Math.max(0, edge.confidence));
147
177
  }
@@ -156,7 +186,7 @@ function graphEdgeConfidence(edge) {
156
186
  function isConcreteGraphEdge(edge) {
157
187
  return edge.kind !== "heuristic-container-edge";
158
188
  }
159
- function buildGraphDegreeIndex(edges) {
189
+ export function buildGraphDegreeIndex(edges) {
160
190
  const fanIn = new Map();
161
191
  const fanOut = new Map();
162
192
  for (const edge of edges) {
@@ -255,33 +285,16 @@ function groupsOverlap(a, b) {
255
285
  }
256
286
  return false;
257
287
  }
258
- function buildGraphConnectedComponentIndex(groups, graphEdges) {
259
- const groupKeys = [...groups.keys()];
260
- const parent = new Map(groupKeys.map((key) => [key, key]));
288
+ function unionFindFromGroups(groups, graphEdges) {
289
+ const uf = new UnionFind(groups.keys());
261
290
  const fileToGroupKeys = buildFileToGroupKeys(groups);
262
291
  const degreeIndex = buildGraphDegreeIndex(graphEdges);
263
- function find(key) {
264
- const current = parent.get(key) ?? key;
265
- if (current === key)
266
- return key;
267
- const root = find(current);
268
- parent.set(key, root);
269
- return root;
270
- }
271
- function union(a, b) {
272
- const rootA = find(a);
273
- const rootB = find(b);
274
- if (rootA === rootB)
275
- return;
276
- const [keep, move] = rootA.localeCompare(rootB) <= 0 ? [rootA, rootB] : [rootB, rootA];
277
- parent.set(move, keep);
278
- }
279
292
  for (const keys of fileToGroupKeys.values()) {
280
293
  const [first, ...rest] = [...keys].sort((a, b) => a.localeCompare(b));
281
294
  if (!first)
282
295
  continue;
283
296
  for (const key of rest) {
284
- union(first, key);
297
+ uf.union(first, key);
285
298
  }
286
299
  }
287
300
  for (const edge of graphEdges) {
@@ -295,11 +308,15 @@ function buildGraphConnectedComponentIndex(groups, graphEdges) {
295
308
  }
296
309
  for (const fromKey of fromGroups) {
297
310
  for (const toKey of toGroups) {
298
- union(fromKey, toKey);
311
+ uf.union(fromKey, toKey);
299
312
  }
300
313
  }
301
314
  }
302
- return new Map(groupKeys.map((key) => [key, find(key)]));
315
+ return uf;
316
+ }
317
+ function buildGraphConnectedComponentIndex(groups, graphEdges) {
318
+ const uf = unionFindFromGroups(groups, graphEdges);
319
+ return new Map([...groups.keys()].map((key) => [key, uf.find(key)]));
303
320
  }
304
321
  function subsystemRootForPath(path) {
305
322
  const segments = normalizeGraphPath(path).split("/").filter(Boolean);
@@ -375,13 +392,11 @@ function buildBoundedClusterEdges(params) {
375
392
  const allFiles = new Set(componentEntries.flatMap((entry) => [...entry.filePaths]));
376
393
  const totalTasks = componentEntries.reduce((sum, entry) => sum + entry.taskCount, 0);
377
394
  const clusterTasks = entries.flatMap((entry) => entry.tasks);
378
- const totalLines = [...allFiles].reduce((sum, path) => {
379
- const owner = clusterTasks.find((task) => task.file_paths.includes(path));
380
- return sum + (owner ? lineCountForPath(owner, path, params.lineIndex) : 0);
381
- }, 0);
395
+ const totalContentTokens = fileGroupContentTokens(allFiles, clusterTasks, params.sizeIndex, params.lineIndex);
382
396
  if (allFiles.size > MAX_SUBSYSTEM_CLUSTER_FILES ||
383
397
  totalTasks > MAX_SUBSYSTEM_CLUSTER_TASKS ||
384
- totalLines > (params.targetPacketLines ?? DEFAULT_TARGET_PACKET_LINES)) {
398
+ totalContentTokens >
399
+ (params.targetPacketTokens ?? DEFAULT_TARGET_PACKET_TOKENS)) {
385
400
  continue;
386
401
  }
387
402
  for (let index = 1; index < componentEntries.length; index++) {
@@ -399,7 +414,7 @@ function buildBoundedClusterEdges(params) {
399
414
  }
400
415
  return clusterEdges.sort(compareGraphEdges);
401
416
  }
402
- function buildSubsystemClusterEdges(groups, graphEdges, lineIndex, targetPacketLines = DEFAULT_TARGET_PACKET_LINES) {
417
+ function buildSubsystemClusterEdges(groups, graphEdges, lineIndex, sizeIndex, targetPacketTokens = DEFAULT_TARGET_PACKET_TOKENS) {
403
418
  return buildBoundedClusterEdges({
404
419
  groups,
405
420
  graphEdges,
@@ -408,7 +423,8 @@ function buildSubsystemClusterEdges(groups, graphEdges, lineIndex, targetPacketL
408
423
  edgeConfidence: SUBSYSTEM_CLUSTER_CONFIDENCE,
409
424
  reasonForCluster: (root, fileCount) => `Bounded subsystem cluster '${root}' groups ${fileCount} file(s) without stronger graph evidence.`,
410
425
  lineIndex,
411
- targetPacketLines,
426
+ sizeIndex,
427
+ targetPacketTokens,
412
428
  });
413
429
  }
414
430
  function packageManifestRoot(path) {
@@ -435,39 +451,18 @@ function isCargoManifestPath(path) {
435
451
  function isMavenPomPath(path) {
436
452
  return normalizeGraphPath(path).split("/").at(-1) === "pom.xml";
437
453
  }
438
- function typescriptProjectRoot(path) {
439
- const segments = normalizeGraphPath(path).split("/").filter(Boolean);
440
- if (!isTypescriptProjectConfigPath(path) || segments.length < 2) {
441
- return undefined;
442
- }
443
- return segments.slice(0, -1).join("/");
444
- }
445
- function goModuleRoot(path) {
446
- const segments = normalizeGraphPath(path).split("/").filter(Boolean);
447
- if (!isGoModuleManifestPath(path) || segments.length < 2) {
448
- return undefined;
449
- }
450
- return segments.slice(0, -1).join("/");
451
- }
452
- function cargoModuleRoot(path) {
453
- const segments = normalizeGraphPath(path).split("/").filter(Boolean);
454
- if (!isCargoManifestPath(path) || segments.length < 2) {
455
- return undefined;
456
- }
457
- return segments.slice(0, -1).join("/");
458
- }
459
- function mavenModuleRoot(path) {
454
+ function configFileRoot(path, predicate) {
460
455
  const segments = normalizeGraphPath(path).split("/").filter(Boolean);
461
- if (!isMavenPomPath(path) || segments.length < 2) {
456
+ if (!predicate(path) || segments.length < 2) {
462
457
  return undefined;
463
458
  }
464
459
  return segments.slice(0, -1).join("/");
465
460
  }
466
461
  function moduleConfigRoot(path) {
467
- return (typescriptProjectRoot(path) ??
468
- goModuleRoot(path) ??
469
- cargoModuleRoot(path) ??
470
- mavenModuleRoot(path));
462
+ return (configFileRoot(path, isTypescriptProjectConfigPath) ??
463
+ configFileRoot(path, isGoModuleManifestPath) ??
464
+ configFileRoot(path, isCargoManifestPath) ??
465
+ configFileRoot(path, isMavenPomPath));
471
466
  }
472
467
  function analyzerOwnershipRoot(path) {
473
468
  const root = normalizeGraphPath(path).replace(/\/+$/, "");
@@ -529,7 +524,7 @@ function packageOwnershipRootForTasks(tasks, packageRoots) {
529
524
  const roots = new Set(rootsForFiles);
530
525
  return roots.size === 1 ? [...roots][0] : undefined;
531
526
  }
532
- function buildPackageOwnershipClusterEdges(groups, graphEdges, lineIndex, targetPacketLines = DEFAULT_TARGET_PACKET_LINES) {
527
+ function buildPackageOwnershipClusterEdges(groups, graphEdges, lineIndex, sizeIndex, targetPacketTokens = DEFAULT_TARGET_PACKET_TOKENS) {
533
528
  const packageRoots = collectPackageOwnershipRoots(groups, graphEdges);
534
529
  if (packageRoots.size === 0) {
535
530
  return [];
@@ -542,7 +537,8 @@ function buildPackageOwnershipClusterEdges(groups, graphEdges, lineIndex, target
542
537
  edgeConfidence: PACKAGE_OWNERSHIP_CLUSTER_CONFIDENCE,
543
538
  reasonForCluster: (root, fileCount) => `Package ownership root '${root}' groups ${fileCount} file(s) across bounded package subdirectories.`,
544
539
  lineIndex,
545
- targetPacketLines,
540
+ sizeIndex,
541
+ targetPacketTokens,
546
542
  });
547
543
  }
548
544
  function collectModuleOwnershipRoots(groups, graphEdges) {
@@ -588,7 +584,7 @@ function moduleOwnershipRootForTasks(tasks, moduleRoots) {
588
584
  const roots = new Set(rootsForFiles);
589
585
  return roots.size === 1 ? [...roots][0] : undefined;
590
586
  }
591
- function buildModuleOwnershipClusterEdges(groups, graphEdges, lineIndex, targetPacketLines = DEFAULT_TARGET_PACKET_LINES) {
587
+ function buildModuleOwnershipClusterEdges(groups, graphEdges, lineIndex, sizeIndex, targetPacketTokens = DEFAULT_TARGET_PACKET_TOKENS) {
592
588
  const moduleRoots = collectModuleOwnershipRoots(groups, graphEdges);
593
589
  if (moduleRoots.size === 0) {
594
590
  return [];
@@ -607,7 +603,8 @@ function buildModuleOwnershipClusterEdges(groups, graphEdges, lineIndex, targetP
607
603
  : `Module ownership root '${root}' from project configuration groups ${fileCount} file(s) across bounded subdirectories.`;
608
604
  },
609
605
  lineIndex,
610
- targetPacketLines,
606
+ sizeIndex,
607
+ targetPacketTokens,
611
608
  });
612
609
  }
613
610
  function buildEntrypointFlowBridgeEdges(groups, graphEdges, graphBundle) {
@@ -694,18 +691,18 @@ function buildEntrypointFlowBridgeEdges(groups, graphEdges, graphBundle) {
694
691
  }
695
692
  return [...bridgeEdges.values()].sort(compareGraphEdges);
696
693
  }
697
- function buildPlanningGraphEdges(groups, graphEdges, graphBundle, lineIndex, targetPacketLines = DEFAULT_TARGET_PACKET_LINES) {
694
+ function buildPlanningGraphEdges(groups, graphEdges, graphBundle, lineIndex, sizeIndex, targetPacketTokens = DEFAULT_TARGET_PACKET_TOKENS) {
698
695
  const bridgeEdges = buildEntrypointFlowBridgeEdges(groups, graphEdges, graphBundle);
699
696
  const graphWithBridges = bridgeEdges.length > 0 ? [...graphEdges, ...bridgeEdges] : graphEdges;
700
- const subsystemEdges = buildSubsystemClusterEdges(groups, graphWithBridges, lineIndex, targetPacketLines);
697
+ const subsystemEdges = buildSubsystemClusterEdges(groups, graphWithBridges, lineIndex, sizeIndex, targetPacketTokens);
701
698
  const graphWithSubsystems = subsystemEdges.length > 0
702
699
  ? [...graphWithBridges, ...subsystemEdges]
703
700
  : graphWithBridges;
704
- const packageOwnershipEdges = buildPackageOwnershipClusterEdges(groups, graphWithSubsystems, lineIndex, targetPacketLines);
701
+ const packageOwnershipEdges = buildPackageOwnershipClusterEdges(groups, graphWithSubsystems, lineIndex, sizeIndex, targetPacketTokens);
705
702
  const graphWithPackageOwnership = packageOwnershipEdges.length > 0
706
703
  ? [...graphWithSubsystems, ...packageOwnershipEdges]
707
704
  : graphWithSubsystems;
708
- const moduleOwnershipEdges = buildModuleOwnershipClusterEdges(groups, graphWithPackageOwnership, lineIndex, targetPacketLines);
705
+ const moduleOwnershipEdges = buildModuleOwnershipClusterEdges(groups, graphWithPackageOwnership, lineIndex, sizeIndex, targetPacketTokens);
709
706
  return moduleOwnershipEdges.length > 0
710
707
  ? [...graphWithPackageOwnership, ...moduleOwnershipEdges]
711
708
  : graphWithPackageOwnership;
@@ -831,7 +828,8 @@ function chunkPacketTasks(tasks, options) {
831
828
  let current = [];
832
829
  for (const task of tasks.sort(compareTasksForPacket)) {
833
830
  const isolatedLargeFileTask = task.file_paths.length === 1 &&
834
- taskLineCount(task, options.lineIndex) > options.targetPacketLines;
831
+ taskContentTokens(task, options.sizeIndex, options.lineIndex) >
832
+ options.targetPacketTokens;
835
833
  if (isolatedLargeFileTask) {
836
834
  if (current.length > 0) {
837
835
  chunks.push(current);
@@ -842,13 +840,10 @@ function chunkPacketTasks(tasks, options) {
842
840
  }
843
841
  const candidate = [...current, task];
844
842
  const uniquePaths = new Set(candidate.flatMap((item) => item.file_paths));
845
- const candidateLines = [...uniquePaths].reduce((sum, path) => {
846
- const owner = candidate.find((item) => item.file_paths.includes(path));
847
- return sum + (owner ? lineCountForPath(owner, path, options.lineIndex) : 0);
848
- }, 0);
843
+ const candidateContentTokens = fileGroupContentTokens(uniquePaths, candidate, options.sizeIndex, options.lineIndex);
849
844
  const wouldExceedTaskCount = options.maxTasksPerPacket > 0 && current.length > 0 && candidate.length > options.maxTasksPerPacket;
850
- const wouldExceedLines = current.length > 0 && candidateLines > options.targetPacketLines;
851
- if (wouldExceedTaskCount || wouldExceedLines) {
845
+ const wouldExceedTokens = current.length > 0 && candidateContentTokens > options.targetPacketTokens;
846
+ if (wouldExceedTaskCount || wouldExceedTokens) {
852
847
  chunks.push(current);
853
848
  current = [];
854
849
  }
@@ -860,59 +855,17 @@ function chunkPacketTasks(tasks, options) {
860
855
  return chunks;
861
856
  }
862
857
  function mergeGraphConnectedGroups(groups, graphEdges) {
863
- const groupKeys = [...groups.keys()];
864
- const parent = new Map(groupKeys.map((key) => [key, key]));
865
- const fileToGroupKeys = buildFileToGroupKeys(groups);
866
- const degreeIndex = buildGraphDegreeIndex(graphEdges);
867
- function find(key) {
868
- const current = parent.get(key) ?? key;
869
- if (current === key)
870
- return key;
871
- const root = find(current);
872
- parent.set(key, root);
873
- return root;
874
- }
875
- function union(a, b) {
876
- const rootA = find(a);
877
- const rootB = find(b);
878
- if (rootA === rootB)
879
- return;
880
- const [keep, move] = rootA.localeCompare(rootB) <= 0 ? [rootA, rootB] : [rootB, rootA];
881
- parent.set(move, keep);
882
- }
883
- for (const keys of fileToGroupKeys.values()) {
884
- const [first, ...rest] = [...keys].sort((a, b) => a.localeCompare(b));
885
- if (!first)
886
- continue;
887
- for (const key of rest) {
888
- union(first, key);
889
- }
890
- }
891
- for (const edge of graphEdges) {
892
- if (!isPacketExpansionEdge(edge, degreeIndex)) {
893
- continue;
894
- }
895
- const fromGroups = fileToGroupKeys.get(normalizeGraphPath(edge.from));
896
- const toGroups = fileToGroupKeys.get(normalizeGraphPath(edge.to));
897
- if (!fromGroups || !toGroups) {
898
- continue;
899
- }
900
- for (const fromKey of fromGroups) {
901
- for (const toKey of toGroups) {
902
- union(fromKey, toKey);
903
- }
904
- }
905
- }
858
+ const uf = unionFindFromGroups(groups, graphEdges);
906
859
  const merged = new Map();
907
- for (const key of groupKeys) {
908
- const root = find(key);
860
+ for (const key of groups.keys()) {
861
+ const root = uf.find(key);
909
862
  const current = merged.get(root) ?? [];
910
863
  current.push(...(groups.get(key) ?? []));
911
864
  merged.set(root, current);
912
865
  }
913
866
  return [...merged.values()];
914
867
  }
915
- function buildPacket(tasks, packetIndex, lineIndex, graphEdges = [], graphBundle) {
868
+ function buildPacket(tasks, packetIndex, lineIndex, sizeIndex, graphEdges = [], graphBundle) {
916
869
  const filePaths = [...new Set(tasks.flatMap((task) => task.file_paths))].sort((a, b) => a.localeCompare(b));
917
870
  const graphContext = buildPacketGraphContext(filePaths, graphEdges, graphBundle);
918
871
  const fileLineCounts = Object.fromEntries(filePaths.map((path) => {
@@ -920,6 +873,8 @@ function buildPacket(tasks, packetIndex, lineIndex, graphEdges = [], graphBundle
920
873
  return [path, owner ? lineCountForPath(owner, path, lineIndex) : 0];
921
874
  }));
922
875
  const totalLines = Object.values(fileLineCounts).reduce((sum, value) => sum + value, 0);
876
+ const estimatedTokens = ESTIMATED_PACKET_PROMPT_TOKENS +
877
+ fileGroupContentTokens(filePaths, tasks, sizeIndex, lineIndex);
923
878
  const priority = tasks.reduce((highest, task) => priorityRank(task.priority) > priorityRank(highest)
924
879
  ? normalizePriority(task.priority)
925
880
  : highest, "low");
@@ -955,19 +910,18 @@ function buildPacket(tasks, packetIndex, lineIndex, graphEdges = [], graphBundle
955
910
  : undefined,
956
911
  quality: graphContext.quality,
957
912
  rationale: `${baseRationale}${graphRationale}`,
958
- estimated_tokens: ESTIMATED_PACKET_PROMPT_TOKENS + totalLines * ESTIMATED_TOKENS_PER_LINE,
913
+ estimated_tokens: estimatedTokens,
959
914
  };
960
915
  }
961
916
  function buildReviewPacketPlanningData(tasks, options = {}) {
962
917
  const maxTasksPerPacket = options.maxTasksPerPacket ?? DEFAULT_MAX_TASKS_PER_PACKET;
963
- const configuredTargetLines = options.targetPacketLines ?? DEFAULT_TARGET_PACKET_LINES;
964
- const targetPacketLines = options.maxContextTokens != null
965
- ? Math.min(configuredTargetLines, Math.max(1, Math.floor((options.maxContextTokens - ESTIMATED_PACKET_PROMPT_TOKENS) /
966
- ESTIMATED_TOKENS_PER_LINE)))
967
- : configuredTargetLines;
918
+ const configuredTargetTokens = options.targetPacketTokens ?? DEFAULT_TARGET_PACKET_TOKENS;
919
+ const targetPacketTokens = options.maxContextTokens != null
920
+ ? Math.min(configuredTargetTokens, Math.max(1, options.maxContextTokens - ESTIMATED_PACKET_PROMPT_TOKENS))
921
+ : configuredTargetTokens;
968
922
  const graphEdges = collectGraphEdges(options.graphBundle);
969
923
  const groups = buildTaskGroups(tasks);
970
- const planningGraphEdges = buildPlanningGraphEdges(groups, graphEdges, options.graphBundle, options.lineIndex, targetPacketLines);
924
+ const planningGraphEdges = buildPlanningGraphEdges(groups, graphEdges, options.graphBundle, options.lineIndex, options.sizeIndex, targetPacketTokens);
971
925
  const packets = [];
972
926
  let packetIndex = 0;
973
927
  const groupedTasks = mergeGraphConnectedGroups(groups, planningGraphEdges).sort((a, b) => {
@@ -980,10 +934,11 @@ function buildReviewPacketPlanningData(tasks, options = {}) {
980
934
  for (const group of groupedTasks) {
981
935
  for (const chunk of chunkPacketTasks(group, {
982
936
  lineIndex: options.lineIndex,
937
+ sizeIndex: options.sizeIndex,
983
938
  maxTasksPerPacket,
984
- targetPacketLines,
939
+ targetPacketTokens,
985
940
  })) {
986
- packets.push(buildPacket(chunk, packetIndex, options.lineIndex, planningGraphEdges, options.graphBundle));
941
+ packets.push(buildPacket(chunk, packetIndex, options.lineIndex, options.sizeIndex, planningGraphEdges, options.graphBundle));
987
942
  packetIndex += 1;
988
943
  }
989
944
  }
@@ -1,6 +1,6 @@
1
1
  import type { UnitManifest } from "../types.js";
2
2
  import type { FlowCoverageManifest } from "../types/flowCoverage.js";
3
- import type { CriticalFlowManifest } from "../types/flows.js";
3
+ import type { CriticalFlowManifest } from "@audit-tools/shared";
4
4
  import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
5
5
  export declare function discoverRuntimeValidationCommand(root: string): Promise<string[] | undefined>;
6
6
  export declare function buildRuntimeValidationTasks(params: {
@@ -1,4 +1,4 @@
1
- import { access, readFile } from "node:fs/promises";
1
+ import { discoverProjectCommands } from "@audit-tools/shared";
2
2
  function checksForFlow(requiredLenses) {
3
3
  const checks = [];
4
4
  if (requiredLenses.includes("security")) {
@@ -15,37 +15,10 @@ function checksForFlow(requiredLenses) {
15
15
  }
16
16
  return checks;
17
17
  }
18
- async function exists(path) {
19
- try {
20
- await access(path);
21
- return true;
22
- }
23
- catch {
24
- return false;
25
- }
26
- }
27
18
  export async function discoverRuntimeValidationCommand(root) {
28
- const packageJsonPath = `${root}/package.json`;
29
- if (await exists(packageJsonPath)) {
30
- try {
31
- const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
32
- const testScript = packageJson.scripts?.test?.trim();
33
- if (testScript &&
34
- !/no test specified/i.test(testScript)) {
35
- return ["npm", "test"];
36
- }
37
- }
38
- catch {
39
- // ignore unreadable package.json for runtime discovery
40
- }
41
- }
42
- if (await exists(`${root}/go.mod`)) {
43
- return ["go", "test", "./..."];
44
- }
45
- if (await exists(`${root}/pyproject.toml`) || await exists(`${root}/pytest.ini`)) {
46
- return ["python", "-m", "pytest"];
47
- }
48
- return undefined;
19
+ // Shared discovery (Node test script → Go → Python) is the single source of
20
+ // truth; the runtime-validation command is the discovered test command.
21
+ return discoverProjectCommands(root).test;
49
22
  }
50
23
  export function buildRuntimeValidationTasks(params) {
51
24
  if (!params.command) {
@@ -0,0 +1,62 @@
1
+ import type { GraphBundle } from "@audit-tools/shared";
2
+ import type { ArtifactBundle } from "../io/artifacts.js";
3
+ import type { CoverageMatrix } from "../types.js";
4
+ import type { AuditScopeBudget, AuditScopeManifest } from "../types/auditScope.js";
5
+ /** Default cap on in-scope files (seeds + expanded) before expansion stops. */
6
+ export declare const DEFAULT_SCOPE_MAX_FILES = 200;
7
+ /** Graph edges below this confidence are never traversed during expansion. */
8
+ export declare const SCOPE_EDGE_CONFIDENCE_FLOOR = 0.5;
9
+ /**
10
+ * Expansion stops along a path once the accumulated path-confidence (the product
11
+ * of the traversed edge confidences) drops below this floor. With no fixed hop
12
+ * count, this — together with hub-skipping and the file budget — bounds the
13
+ * frontier deterministically.
14
+ */
15
+ export declare const SCOPE_MIN_FRONTIER_CONFIDENCE = 0.5;
16
+ export interface ComputeAuditScopeInput {
17
+ /** The git ref the delta is measured against. */
18
+ since: string;
19
+ /** Raw changed paths (git output, posix-relative). */
20
+ changed: string[];
21
+ /** Canonical auditable file paths (repo-manifest paths, non-excluded). */
22
+ includedFiles: string[];
23
+ /** Dependency graph used to expand from seeds to neighbours. */
24
+ graphBundle?: GraphBundle;
25
+ budget?: AuditScopeBudget;
26
+ }
27
+ /**
28
+ * Deterministic priority-frontier expansion (Phase 3). Starting from the changed
29
+ * files (seeds), walk the dependency graph outward, always visiting the neighbour
30
+ * with the highest accumulated path-confidence first (tie-broken by path). High
31
+ * fan-in/out hubs are skipped so a single change near a hub does not drag the
32
+ * whole repo into scope, low-confidence edges are dropped, and expansion halts at
33
+ * the file budget or when the best remaining frontier confidence falls below the
34
+ * floor. Same inputs → identical scope.
35
+ */
36
+ export declare function computeAuditScope(input: ComputeAuditScopeInput): AuditScopeManifest;
37
+ /** A full-audit scope (the default, and every fallback). */
38
+ export declare function fullAuditScope(budget?: AuditScopeBudget, droppedNote?: string): AuditScopeManifest;
39
+ export interface ResolveAuditScopeInput {
40
+ root?: string;
41
+ /** The `--since` ref, if any. Absent/empty → full audit. */
42
+ since?: string;
43
+ bundle: ArtifactBundle;
44
+ budget?: AuditScopeBudget;
45
+ }
46
+ /**
47
+ * Resolve the scope for a planning run. Returns a full-audit scope unless a
48
+ * `--since` ref was supplied against a real git repository; an unusable ref or
49
+ * missing root degrades to a full audit with an honest note. Reads the auditable
50
+ * file set from the repo manifest + disposition (the same lookup the graph
51
+ * extractor uses) and the dependency graph from the bundle.
52
+ */
53
+ export declare function resolveAuditScope(input: ResolveAuditScopeInput): AuditScopeManifest;
54
+ /**
55
+ * Apply a delta scope to a freshly-built coverage matrix. In-scope files (seeds
56
+ * + expanded neighbours) keep their fresh `pending` status to be re-audited.
57
+ * Out-of-scope files inherit a prior `complete` record verbatim when present (so
58
+ * previously-finished work is preserved, not re-run), and are otherwise excluded
59
+ * from this run with `classification_status: "out_of_scope_delta"`. Deterministic
60
+ * exclusions (non-auditable/trivial) are left untouched. A full scope is a no-op.
61
+ */
62
+ export declare function applyScopeToCoverage(coverage: CoverageMatrix, scope: AuditScopeManifest, priorCoverage?: CoverageMatrix): CoverageMatrix;