clinkx 0.2.3 → 1.0.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 (574) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +10 -0
  3. package/README.md +113 -18
  4. package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
  5. package/clinkx-workflows/dist/allowed-roots.js +5 -0
  6. package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
  7. package/clinkx-workflows/dist/artifacts.d.ts +20 -1
  8. package/clinkx-workflows/dist/artifacts.js +38 -9
  9. package/clinkx-workflows/dist/artifacts.js.map +1 -1
  10. package/clinkx-workflows/dist/backend.d.ts +4 -0
  11. package/clinkx-workflows/dist/citation-check.d.ts +40 -0
  12. package/clinkx-workflows/dist/citation-check.js +348 -0
  13. package/clinkx-workflows/dist/citation-check.js.map +1 -0
  14. package/clinkx-workflows/dist/clink-client.d.ts +2 -1
  15. package/clinkx-workflows/dist/clink-client.js +17 -4
  16. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  17. package/clinkx-workflows/dist/config.d.ts +145 -16
  18. package/clinkx-workflows/dist/config.js +384 -26
  19. package/clinkx-workflows/dist/config.js.map +1 -1
  20. package/clinkx-workflows/dist/contained-file.d.ts +172 -0
  21. package/clinkx-workflows/dist/contained-file.js +483 -0
  22. package/clinkx-workflows/dist/contained-file.js.map +1 -0
  23. package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
  24. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
  25. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
  26. package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
  27. package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
  28. package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
  29. package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
  30. package/clinkx-workflows/dist/context-cache-key.js +91 -0
  31. package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
  32. package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
  33. package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
  34. package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
  35. package/clinkx-workflows/dist/context-preflight.d.ts +184 -0
  36. package/clinkx-workflows/dist/context-preflight.js +91 -0
  37. package/clinkx-workflows/dist/context-preflight.js.map +1 -0
  38. package/clinkx-workflows/dist/council-expander.d.ts +1 -1
  39. package/clinkx-workflows/dist/council-expander.js +2 -1
  40. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
  42. package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -0
  43. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
  45. package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
  46. package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
  47. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +86 -0
  48. package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
  49. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
  51. package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -0
  52. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
  54. package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
  55. package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
  56. package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
  57. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
  58. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
  59. package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
  60. package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
  61. package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
  62. package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
  63. package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
  64. package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
  65. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
  66. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
  67. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
  68. package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
  69. package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
  70. package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
  71. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
  72. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
  73. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
  74. package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
  75. package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
  76. package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
  77. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +222 -0
  78. package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
  79. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
  80. package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
  81. package/clinkx-workflows/dist/dashboard/contracts.js +1157 -0
  82. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
  83. package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
  84. package/clinkx-workflows/dist/dashboard/generation.js +41 -0
  85. package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
  86. package/clinkx-workflows/dist/dashboard/index.d.ts +28 -0
  87. package/clinkx-workflows/dist/dashboard/index.js +16 -0
  88. package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
  89. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
  90. package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
  91. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
  92. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -0
  93. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
  94. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
  95. package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
  96. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +581 -0
  97. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
  98. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
  99. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
  100. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
  101. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +28 -0
  102. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
  103. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
  104. package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
  105. package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
  106. package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
  107. package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
  108. package/clinkx-workflows/dist/dashboard/request-admission.js +126 -0
  109. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
  110. package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
  111. package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
  112. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
  113. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
  114. package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
  115. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
  116. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
  117. package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -0
  118. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
  119. package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
  120. package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
  121. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
  122. package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
  123. package/clinkx-workflows/dist/dashboard/session-log-journal.js +522 -0
  124. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
  125. package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
  126. package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
  127. package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
  128. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +6 -0
  129. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -0
  130. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
  131. package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
  132. package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
  133. package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
  134. package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
  135. package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
  136. package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
  137. package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
  138. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
  139. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
  140. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  141. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  142. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  143. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  144. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  145. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  146. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  147. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  148. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  149. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  150. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +25 -0
  151. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  152. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  153. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  154. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  155. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +1 -0
  156. package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
  157. package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
  158. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
  159. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
  160. package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
  161. package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
  162. package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
  163. package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
  164. package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
  165. package/clinkx-workflows/dist/delegate-validate.d.ts +78 -0
  166. package/clinkx-workflows/dist/delegate-validate.js +97 -0
  167. package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
  168. package/clinkx-workflows/dist/engine.d.ts +141 -0
  169. package/clinkx-workflows/dist/engine.js +882 -28
  170. package/clinkx-workflows/dist/engine.js.map +1 -1
  171. package/clinkx-workflows/dist/errors.d.ts +2 -0
  172. package/clinkx-workflows/dist/errors.js.map +1 -1
  173. package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
  174. package/clinkx-workflows/dist/fingerprint.js +1 -1
  175. package/clinkx-workflows/dist/index.d.ts +19 -0
  176. package/clinkx-workflows/dist/index.js +15 -0
  177. package/clinkx-workflows/dist/index.js.map +1 -1
  178. package/clinkx-workflows/dist/loader.d.ts +9 -3
  179. package/clinkx-workflows/dist/loader.js +92 -21
  180. package/clinkx-workflows/dist/loader.js.map +1 -1
  181. package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
  182. package/clinkx-workflows/dist/plan-review/admission.js +290 -0
  183. package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
  184. package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
  185. package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
  186. package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
  187. package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
  188. package/clinkx-workflows/dist/plan-review/contract.js +289 -0
  189. package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
  190. package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
  191. package/clinkx-workflows/dist/plan-review/digest.js +241 -0
  192. package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
  193. package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
  194. package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
  195. package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
  196. package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
  197. package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
  198. package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
  199. package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
  200. package/clinkx-workflows/dist/plan-review/expander.js +243 -0
  201. package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
  202. package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
  203. package/clinkx-workflows/dist/plan-review/projector.js +437 -0
  204. package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
  205. package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
  206. package/clinkx-workflows/dist/plan-review/publication.js +182 -0
  207. package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
  208. package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
  209. package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
  210. package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
  211. package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
  212. package/clinkx-workflows/dist/plan-review/resume.js +122 -0
  213. package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
  214. package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
  215. package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
  216. package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
  217. package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
  218. package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
  219. package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
  220. package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
  221. package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
  222. package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
  223. package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
  224. package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
  225. package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
  226. package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
  227. package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
  228. package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
  229. package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
  230. package/clinkx-workflows/dist/post-processors/index.js +21 -0
  231. package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
  232. package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
  233. package/clinkx-workflows/dist/prompt-budget.js +7 -1
  234. package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
  235. package/clinkx-workflows/dist/reducers/index.js +2 -0
  236. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  237. package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
  238. package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
  239. package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
  240. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
  241. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
  242. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
  243. package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
  244. package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
  245. package/clinkx-workflows/dist/run-summary.d.ts +13 -0
  246. package/clinkx-workflows/dist/run-summary.js +29 -1
  247. package/clinkx-workflows/dist/run-summary.js.map +1 -1
  248. package/clinkx-workflows/dist/schema.d.ts +76 -21
  249. package/clinkx-workflows/dist/schema.js +10 -1
  250. package/clinkx-workflows/dist/schema.js.map +1 -1
  251. package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
  252. package/clinkx-workflows/dist/seed-terms.js +176 -0
  253. package/clinkx-workflows/dist/seed-terms.js.map +1 -0
  254. package/clinkx-workflows/dist/server.d.ts +5 -2
  255. package/clinkx-workflows/dist/server.js +82 -52
  256. package/clinkx-workflows/dist/server.js.map +1 -1
  257. package/clinkx-workflows/dist/shutdown.d.ts +18 -20
  258. package/clinkx-workflows/dist/shutdown.js +43 -61
  259. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  260. package/clinkx-workflows/dist/state-schema.d.ts +38 -0
  261. package/clinkx-workflows/dist/state.d.ts +170 -3
  262. package/clinkx-workflows/dist/state.js +1275 -28
  263. package/clinkx-workflows/dist/state.js.map +1 -1
  264. package/clinkx-workflows/dist/transport.d.ts +15 -0
  265. package/clinkx-workflows/dist/transport.js +19 -10
  266. package/clinkx-workflows/dist/transport.js.map +1 -1
  267. package/clinkx-workflows/dist/types.d.ts +14 -2
  268. package/clinkx-workflows/dist/validators/council.d.ts +318 -85
  269. package/clinkx-workflows/dist/validators/council.js +52 -13
  270. package/clinkx-workflows/dist/validators/council.js.map +1 -1
  271. package/clinkx-workflows/dist/validators/index.d.ts +1 -0
  272. package/clinkx-workflows/dist/validators/index.js +3 -0
  273. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  274. package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
  275. package/clinkx-workflows/dist/validators/plan-review.js +316 -0
  276. package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
  277. package/clinkx-workflows/dist/workflow-receipt.js +188 -4
  278. package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
  279. package/clinkx-workflows/dist/workflow-tools.d.ts +48 -3
  280. package/clinkx-workflows/dist/workflow-tools.js +1402 -59
  281. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  282. package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
  283. package/clinkx-workflows/dist/workspace-signature.js +388 -0
  284. package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
  285. package/clinkx-workflows/templates/council/answer-specialist.txt +2 -1
  286. package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
  287. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
  288. package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
  289. package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
  290. package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
  291. package/clinkx-workflows/templates/council/member.txt +2 -1
  292. package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
  293. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
  294. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -0
  295. package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
  296. package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
  297. package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
  298. package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
  299. package/clinkx-workflows/workflows/council-answer.yaml +97 -8
  300. package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
  301. package/clinkx-workflows/workflows/council-debug.yaml +97 -8
  302. package/clinkx-workflows/workflows/council-default.yaml +97 -8
  303. package/clinkx-workflows/workflows/council-discover.yaml +97 -8
  304. package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
  305. package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
  306. package/conf/adapters/agy.json +49 -0
  307. package/conf/adapters/claude.json +5 -0
  308. package/conf/adapters/codex-app-server.json +44 -0
  309. package/conf/adapters/codex.json +7 -2
  310. package/conf/adapters/glm.json +5 -0
  311. package/conf/adapters/grok.json +60 -0
  312. package/conf/adapters/hapi/agy.json +52 -0
  313. package/conf/adapters/hapi/claude.json +5 -0
  314. package/conf/adapters/hapi/codex.json +7 -2
  315. package/conf/adapters/hapi/glm.json +5 -0
  316. package/conf/prompts/json-codereviewer.txt +1 -0
  317. package/conf/prompts/json.txt +1 -0
  318. package/dist/activity-journal.d.ts +42 -0
  319. package/dist/activity-journal.js +381 -0
  320. package/dist/activity-journal.js.map +1 -0
  321. package/dist/allowed-roots.d.ts +53 -0
  322. package/dist/allowed-roots.js +216 -0
  323. package/dist/allowed-roots.js.map +1 -0
  324. package/dist/codex/activity-session-log.d.ts +14 -0
  325. package/dist/codex/activity-session-log.js +206 -0
  326. package/dist/codex/activity-session-log.js.map +1 -0
  327. package/dist/codex/agent-tools.d.ts +23 -0
  328. package/dist/codex/agent-tools.js +1270 -0
  329. package/dist/codex/agent-tools.js.map +1 -0
  330. package/dist/codex/app-server-client.d.ts +76 -0
  331. package/dist/codex/app-server-client.js +582 -0
  332. package/dist/codex/app-server-client.js.map +1 -0
  333. package/dist/codex/app-server-registry.d.ts +389 -0
  334. package/dist/codex/app-server-registry.js +1407 -0
  335. package/dist/codex/app-server-registry.js.map +1 -0
  336. package/dist/codex/client-types.d.ts +109 -0
  337. package/dist/codex/client-types.js +2 -0
  338. package/dist/codex/client-types.js.map +1 -0
  339. package/dist/codex/protocol.d.ts +5375 -0
  340. package/dist/codex/protocol.js +614 -0
  341. package/dist/codex/protocol.js.map +1 -0
  342. package/dist/codex/session.d.ts +263 -0
  343. package/dist/codex/session.js +466 -0
  344. package/dist/codex/session.js.map +1 -0
  345. package/dist/config.d.ts +185 -19
  346. package/dist/config.js +82 -16
  347. package/dist/config.js.map +1 -1
  348. package/dist/context-builder/anchors.d.ts +12 -0
  349. package/dist/context-builder/anchors.js +22 -0
  350. package/dist/context-builder/anchors.js.map +1 -0
  351. package/dist/context-builder/bundle.d.ts +109 -0
  352. package/dist/context-builder/bundle.js +1498 -0
  353. package/dist/context-builder/bundle.js.map +1 -0
  354. package/dist/context-builder/codemap.d.ts +22 -0
  355. package/dist/context-builder/codemap.js +675 -0
  356. package/dist/context-builder/codemap.js.map +1 -0
  357. package/dist/context-builder/config.d.ts +61 -0
  358. package/dist/context-builder/config.js +216 -0
  359. package/dist/context-builder/config.js.map +1 -0
  360. package/dist/context-builder/context-run-catalog.d.ts +17 -0
  361. package/dist/context-builder/context-run-catalog.js +99 -0
  362. package/dist/context-builder/context-run-catalog.js.map +1 -0
  363. package/dist/context-builder/delegate-packet.d.ts +219 -0
  364. package/dist/context-builder/delegate-packet.js +1190 -0
  365. package/dist/context-builder/delegate-packet.js.map +1 -0
  366. package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
  367. package/dist/context-builder/delegate-reconstruction.js +220 -0
  368. package/dist/context-builder/delegate-reconstruction.js.map +1 -0
  369. package/dist/context-builder/delegate-validation.d.ts +27 -0
  370. package/dist/context-builder/delegate-validation.js +479 -0
  371. package/dist/context-builder/delegate-validation.js.map +1 -0
  372. package/dist/context-builder/delegate.d.ts +26 -0
  373. package/dist/context-builder/delegate.js +134 -0
  374. package/dist/context-builder/delegate.js.map +1 -0
  375. package/dist/context-builder/diagnostics.d.ts +36 -0
  376. package/dist/context-builder/diagnostics.js +107 -0
  377. package/dist/context-builder/diagnostics.js.map +1 -0
  378. package/dist/context-builder/discovery.d.ts +146 -0
  379. package/dist/context-builder/discovery.js +2129 -0
  380. package/dist/context-builder/discovery.js.map +1 -0
  381. package/dist/context-builder/globs.d.ts +14 -0
  382. package/dist/context-builder/globs.js +175 -0
  383. package/dist/context-builder/globs.js.map +1 -0
  384. package/dist/context-builder/handler.d.ts +41 -0
  385. package/dist/context-builder/handler.js +2590 -0
  386. package/dist/context-builder/handler.js.map +1 -0
  387. package/dist/context-builder/lifecycle-contract.d.ts +424 -0
  388. package/dist/context-builder/lifecycle-contract.js +523 -0
  389. package/dist/context-builder/lifecycle-contract.js.map +1 -0
  390. package/dist/context-builder/lifecycle-journal.d.ts +62 -0
  391. package/dist/context-builder/lifecycle-journal.js +88 -0
  392. package/dist/context-builder/lifecycle-journal.js.map +1 -0
  393. package/dist/context-builder/lifecycle-observer.d.ts +102 -0
  394. package/dist/context-builder/lifecycle-observer.js +258 -0
  395. package/dist/context-builder/lifecycle-observer.js.map +1 -0
  396. package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
  397. package/dist/context-builder/lifecycle-reconcile.js +191 -0
  398. package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
  399. package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
  400. package/dist/context-builder/lifecycle-recovery.js +14 -0
  401. package/dist/context-builder/lifecycle-recovery.js.map +1 -0
  402. package/dist/context-builder/path-signals.d.ts +7 -0
  403. package/dist/context-builder/path-signals.js +70 -0
  404. package/dist/context-builder/path-signals.js.map +1 -0
  405. package/dist/context-builder/paths.d.ts +30 -0
  406. package/dist/context-builder/paths.js +153 -0
  407. package/dist/context-builder/paths.js.map +1 -0
  408. package/dist/context-builder/progress.d.ts +17 -0
  409. package/dist/context-builder/progress.js +93 -0
  410. package/dist/context-builder/progress.js.map +1 -0
  411. package/dist/context-builder/receipt.d.ts +91 -0
  412. package/dist/context-builder/receipt.js +180 -0
  413. package/dist/context-builder/receipt.js.map +1 -0
  414. package/dist/context-builder/refine-handler.d.ts +14 -0
  415. package/dist/context-builder/refine-handler.js +790 -0
  416. package/dist/context-builder/refine-handler.js.map +1 -0
  417. package/dist/context-builder/review-coverage.d.ts +84 -0
  418. package/dist/context-builder/review-coverage.js +437 -0
  419. package/dist/context-builder/review-coverage.js.map +1 -0
  420. package/dist/context-builder/run-id.d.ts +7 -0
  421. package/dist/context-builder/run-id.js +31 -0
  422. package/dist/context-builder/run-id.js.map +1 -0
  423. package/dist/context-builder/schema.d.ts +669 -0
  424. package/dist/context-builder/schema.js +399 -0
  425. package/dist/context-builder/schema.js.map +1 -0
  426. package/dist/context-builder/secrets.d.ts +41 -0
  427. package/dist/context-builder/secrets.js +1651 -0
  428. package/dist/context-builder/secrets.js.map +1 -0
  429. package/dist/context-builder/selection.d.ts +56 -0
  430. package/dist/context-builder/selection.js +1111 -0
  431. package/dist/context-builder/selection.js.map +1 -0
  432. package/dist/context-builder/session-log-identity.d.ts +1 -0
  433. package/dist/context-builder/session-log-identity.js +66 -0
  434. package/dist/context-builder/session-log-identity.js.map +1 -0
  435. package/dist/context-builder/slice-range-math.d.ts +16 -0
  436. package/dist/context-builder/slice-range-math.js +137 -0
  437. package/dist/context-builder/slice-range-math.js.map +1 -0
  438. package/dist/context-builder/status.d.ts +66 -0
  439. package/dist/context-builder/status.js +160 -0
  440. package/dist/context-builder/status.js.map +1 -0
  441. package/dist/context-builder/storage.d.ts +118 -0
  442. package/dist/context-builder/storage.js +1003 -0
  443. package/dist/context-builder/storage.js.map +1 -0
  444. package/dist/context-builder/token-estimate.d.ts +19 -0
  445. package/dist/context-builder/token-estimate.js +90 -0
  446. package/dist/context-builder/token-estimate.js.map +1 -0
  447. package/dist/context-builder/versions.d.ts +29 -0
  448. package/dist/context-builder/versions.js +30 -0
  449. package/dist/context-builder/versions.js.map +1 -0
  450. package/dist/context-builder/workflow-runner.d.ts +69 -0
  451. package/dist/context-builder/workflow-runner.js +1051 -0
  452. package/dist/context-builder/workflow-runner.js.map +1 -0
  453. package/dist/context-builder/write-watchdog.d.ts +14 -0
  454. package/dist/context-builder/write-watchdog.js +38 -0
  455. package/dist/context-builder/write-watchdog.js.map +1 -0
  456. package/dist/dashboard-adapter-catalog.d.ts +9 -0
  457. package/dist/dashboard-adapter-catalog.js +46 -0
  458. package/dist/dashboard-adapter-catalog.js.map +1 -0
  459. package/dist/dashboard-cli.d.ts +79 -0
  460. package/dist/dashboard-cli.js +585 -0
  461. package/dist/dashboard-cli.js.map +1 -0
  462. package/dist/dashboard-companion/client.d.ts +36 -0
  463. package/dist/dashboard-companion/client.js +210 -0
  464. package/dist/dashboard-companion/client.js.map +1 -0
  465. package/dist/dashboard-companion/config.d.ts +8 -0
  466. package/dist/dashboard-companion/config.js +52 -0
  467. package/dist/dashboard-companion/config.js.map +1 -0
  468. package/dist/dashboard-companion/http-proxy.d.ts +10 -0
  469. package/dist/dashboard-companion/http-proxy.js +234 -0
  470. package/dist/dashboard-companion/http-proxy.js.map +1 -0
  471. package/dist/dashboard-companion/protocol.d.ts +80 -0
  472. package/dist/dashboard-companion/protocol.js +455 -0
  473. package/dist/dashboard-companion/protocol.js.map +1 -0
  474. package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
  475. package/dist/dashboard-companion/standalone-assets.js +72 -0
  476. package/dist/dashboard-companion/standalone-assets.js.map +1 -0
  477. package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
  478. package/dist/dashboard-companion/standalone-entry.js +17 -0
  479. package/dist/dashboard-companion/standalone-entry.js.map +1 -0
  480. package/dist/dashboard-gateway/config.d.ts +9 -0
  481. package/dist/dashboard-gateway/config.js +44 -0
  482. package/dist/dashboard-gateway/config.js.map +1 -0
  483. package/dist/dashboard-gateway/index.d.ts +11 -0
  484. package/dist/dashboard-gateway/index.js +755 -0
  485. package/dist/dashboard-gateway/index.js.map +1 -0
  486. package/dist/dashboard-gateway/main.d.ts +1 -0
  487. package/dist/dashboard-gateway/main.js +25 -0
  488. package/dist/dashboard-gateway/main.js.map +1 -0
  489. package/dist/dashboard-pairing-token.d.ts +1 -0
  490. package/dist/dashboard-pairing-token.js +5 -0
  491. package/dist/dashboard-pairing-token.js.map +1 -0
  492. package/dist/elaris/client.d.ts +73 -0
  493. package/dist/elaris/client.js +825 -0
  494. package/dist/elaris/client.js.map +1 -0
  495. package/dist/elaris/config.d.ts +18 -0
  496. package/dist/elaris/config.js +75 -0
  497. package/dist/elaris/config.js.map +1 -0
  498. package/dist/elaris/errors.d.ts +41 -0
  499. package/dist/elaris/errors.js +205 -0
  500. package/dist/elaris/errors.js.map +1 -0
  501. package/dist/elaris/github-repo.d.ts +18 -0
  502. package/dist/elaris/github-repo.js +89 -0
  503. package/dist/elaris/github-repo.js.map +1 -0
  504. package/dist/elaris/index.d.ts +10 -0
  505. package/dist/elaris/index.js +11 -0
  506. package/dist/elaris/index.js.map +1 -0
  507. package/dist/elaris/redact.d.ts +89 -0
  508. package/dist/elaris/redact.js +423 -0
  509. package/dist/elaris/redact.js.map +1 -0
  510. package/dist/elaris/run-id.d.ts +5 -0
  511. package/dist/elaris/run-id.js +20 -0
  512. package/dist/elaris/run-id.js.map +1 -0
  513. package/dist/elaris/status.d.ts +45 -0
  514. package/dist/elaris/status.js +122 -0
  515. package/dist/elaris/status.js.map +1 -0
  516. package/dist/elaris/tool-schemas.d.ts +314 -0
  517. package/dist/elaris/tool-schemas.js +208 -0
  518. package/dist/elaris/tool-schemas.js.map +1 -0
  519. package/dist/elaris/tools.d.ts +73 -0
  520. package/dist/elaris/tools.js +1065 -0
  521. package/dist/elaris/tools.js.map +1 -0
  522. package/dist/elaris/types.d.ts +1541 -0
  523. package/dist/elaris/types.js +252 -0
  524. package/dist/elaris/types.js.map +1 -0
  525. package/dist/errors.d.ts +16 -0
  526. package/dist/errors.js +19 -0
  527. package/dist/errors.js.map +1 -1
  528. package/dist/handler.d.ts +4 -0
  529. package/dist/handler.js +10 -1
  530. package/dist/handler.js.map +1 -1
  531. package/dist/index.d.ts +2 -1
  532. package/dist/index.js +13 -3
  533. package/dist/index.js.map +1 -1
  534. package/dist/local-clink-backend.d.ts +3 -0
  535. package/dist/local-clink-backend.js +4 -0
  536. package/dist/local-clink-backend.js.map +1 -1
  537. package/dist/package-info.d.ts +2 -0
  538. package/dist/package-info.js +18 -0
  539. package/dist/package-info.js.map +1 -0
  540. package/dist/parsers/codex-jsonl.js +48 -3
  541. package/dist/parsers/codex-jsonl.js.map +1 -1
  542. package/dist/parsers/extract.d.ts +10 -1
  543. package/dist/parsers/extract.js +11 -3
  544. package/dist/parsers/extract.js.map +1 -1
  545. package/dist/paths.d.ts +8 -0
  546. package/dist/paths.js +31 -43
  547. package/dist/paths.js.map +1 -1
  548. package/dist/pipeline.d.ts +51 -3
  549. package/dist/pipeline.js +264 -47
  550. package/dist/pipeline.js.map +1 -1
  551. package/dist/run-dir.d.ts +24 -0
  552. package/dist/run-dir.js +201 -2
  553. package/dist/run-dir.js.map +1 -1
  554. package/dist/runner.d.ts +13 -0
  555. package/dist/runner.js +184 -15
  556. package/dist/runner.js.map +1 -1
  557. package/dist/safety.d.ts +4 -4
  558. package/dist/safety.js +4 -4
  559. package/dist/schema.d.ts +7 -7
  560. package/dist/server.d.ts +2 -0
  561. package/dist/server.js +396 -24
  562. package/dist/server.js.map +1 -1
  563. package/dist/session-log-stream.d.ts +18 -0
  564. package/dist/session-log-stream.js +319 -0
  565. package/dist/session-log-stream.js.map +1 -0
  566. package/package.json +47 -5
  567. package/conf/adapters/gemini.json +0 -44
  568. package/conf/adapters/hapi/gemini.json +0 -47
  569. package/dist/artifacts.d.ts +0 -9
  570. package/dist/artifacts.js +0 -24
  571. package/dist/artifacts.js.map +0 -1
  572. package/dist/parsers/gemini-json.d.ts +0 -2
  573. package/dist/parsers/gemini-json.js +0 -72
  574. package/dist/parsers/gemini-json.js.map +0 -1
@@ -0,0 +1,2129 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat as fsLstat, open as fsOpen, readdir as fsReaddir, realpath as fsRealpath, stat as fsStat, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { isSamePathOrDescendant } from "../allowed-roots.js";
5
+ import { CancellationError } from "../errors.js";
6
+ import { CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS, } from "./config.js";
7
+ import { CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON, requestedEvidenceOmission, } from "./diagnostics.js";
8
+ import { compileContextBuilderGlobs, CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS, } from "./globs.js";
9
+ import { normalizeContextBuilderRootRelativePath, rootRelativePathForAbsolutePath, } from "./paths.js";
10
+ import { directoryDistance, genericPathKind, isTestPath, normalizedPathStem, } from "./path-signals.js";
11
+ import { buildContextReviewObligations, } from "./review-coverage.js";
12
+ import { sanitizeHighConfidenceSecretContent, scanSecretPath, } from "./secrets.js";
13
+ import { CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS, } from "./schema.js";
14
+ const SEARCH_WINDOW_LINES_BEFORE = 19;
15
+ const SEARCH_WINDOW_LINES_AFTER = 20;
16
+ const SEARCH_BRIDGE_GAP_LINES = 20;
17
+ const SEARCH_MAX_MERGED_SPAN_LINES = 160;
18
+ const DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES = 48_000;
19
+ const DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES = 96_000;
20
+ // Non-silent, material signal for an explicitly-focused file that exceeded the
21
+ // bounded full-file focus size and had no per-file search evidence: instead of
22
+ // dropping it, we include a bounded head slice and record this reason so the
23
+ // receipt reflects a partial inclusion (not a silent omission).
24
+ const CONTEXT_BUILDER_FOCUS_SLICED_REASON = "focus_sliced";
25
+ // Non-silent, material signal for an explicitly-focused file whose FIRST logical
26
+ // line alone exceeds the bounded focus-slice cap. The line-range model cannot
27
+ // express a partial line, so emitting a head slice would blow the byte bound.
28
+ // We omit the file with this explicit material reason instead — honoring both
29
+ // the byte bound and "never silently drop an explicitly-focused file".
30
+ const CONTEXT_BUILDER_FOCUS_OVER_CAP_REASON = "focus_over_cap";
31
+ const IMPORT_EXPANSION_PASSES = 3;
32
+ const MAX_EXPANDED_SEARCH_TERMS = 128;
33
+ const MAX_EXPANDED_SEARCH_TERM_BYTES = 16_384;
34
+ const MAX_DERIVED_SEARCH_TERM_LENGTH = 256;
35
+ const PROJECT_SUPPORT_FILENAMES = Object.freeze([
36
+ "package.json",
37
+ "vitest.config.ts",
38
+ "vite.config.ts",
39
+ ]);
40
+ export async function discoverContextBuilderCandidates(options) {
41
+ const allowSecrets = options.allowSecrets ?? true;
42
+ const internalCaps = options.internal_caps ?? CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS;
43
+ const matcher = compileContextBuilderGlobs({
44
+ include: [...(options.include_globs ?? [])],
45
+ exclude: [
46
+ ...CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS,
47
+ ...(options.exclude_globs ?? []),
48
+ ],
49
+ });
50
+ const state = {
51
+ tree: [],
52
+ contentCandidates: [],
53
+ omissions: [],
54
+ requestedEvidenceOmissionKeys: new Set(),
55
+ processedFileKeys: new Set(),
56
+ traversedDirectoryKeys: new Set(),
57
+ focusScanExemptFiles: 0,
58
+ metrics: {
59
+ files_seen: 0,
60
+ files_considered: 0,
61
+ scan_cap_hit: false,
62
+ search_cap_hit: false,
63
+ discovery_byte_cap_hit: false,
64
+ discovery_candidate_cap_hit: false,
65
+ candidate_cap_hit: false,
66
+ bytes_read: 0,
67
+ },
68
+ stopped: false,
69
+ };
70
+ for (const root of options.roots) {
71
+ if (state.stopped) {
72
+ break;
73
+ }
74
+ assertNotAborted(options.signal);
75
+ await processExactFocusFiles({
76
+ root,
77
+ focusPaths: options.focus_paths ?? [],
78
+ allowSecrets,
79
+ matcher,
80
+ budget: options.budget,
81
+ internalCaps,
82
+ ...(options.signal == null ? {} : { signal: options.signal }),
83
+ fs: {
84
+ readdir: options.fs?.readdir ?? fsReaddir,
85
+ lstat: options.fs?.lstat ?? fsLstat,
86
+ open: options.fs?.open ?? fsOpen,
87
+ realpath: options.fs?.realpath ?? fsRealpath,
88
+ stat: options.fs?.stat ?? fsStat,
89
+ },
90
+ state,
91
+ });
92
+ await processFocusDirectories({
93
+ root,
94
+ focusPaths: options.focus_paths ?? [],
95
+ allowSecrets,
96
+ matcher,
97
+ budget: options.budget,
98
+ internalCaps,
99
+ excludedRealRoots: options.excluded_real_roots ?? [],
100
+ ...(options.signal == null ? {} : { signal: options.signal }),
101
+ fs: {
102
+ readdir: options.fs?.readdir ?? fsReaddir,
103
+ lstat: options.fs?.lstat ?? fsLstat,
104
+ open: options.fs?.open ?? fsOpen,
105
+ realpath: options.fs?.realpath ?? fsRealpath,
106
+ stat: options.fs?.stat ?? fsStat,
107
+ },
108
+ state,
109
+ });
110
+ await traverseDirectory({
111
+ root,
112
+ absoluteDir: root.path,
113
+ relativeDir: "",
114
+ matcher,
115
+ focusPaths: options.focus_paths ?? [],
116
+ allowSecrets,
117
+ budget: options.budget,
118
+ internalCaps,
119
+ excludedRealRoots: options.excluded_real_roots ?? [],
120
+ ...(options.signal == null ? {} : { signal: options.signal }),
121
+ fs: {
122
+ readdir: options.fs?.readdir ?? fsReaddir,
123
+ lstat: options.fs?.lstat ?? fsLstat,
124
+ open: options.fs?.open ?? fsOpen,
125
+ realpath: options.fs?.realpath ?? fsRealpath,
126
+ stat: options.fs?.stat ?? fsStat,
127
+ },
128
+ state,
129
+ });
130
+ await processImportAndSupportFiles({
131
+ root,
132
+ matcher,
133
+ focusPaths: options.focus_paths ?? [],
134
+ allowSecrets,
135
+ budget: options.budget,
136
+ internalCaps,
137
+ ...(options.signal == null ? {} : { signal: options.signal }),
138
+ fs: {
139
+ readdir: options.fs?.readdir ?? fsReaddir,
140
+ lstat: options.fs?.lstat ?? fsLstat,
141
+ open: options.fs?.open ?? fsOpen,
142
+ realpath: options.fs?.realpath ?? fsRealpath,
143
+ stat: options.fs?.stat ?? fsStat,
144
+ },
145
+ state,
146
+ });
147
+ }
148
+ state.tree.sort(compareTreeFiles);
149
+ state.contentCandidates.sort(compareContentCandidates);
150
+ const expandedSearchTerms = expandSearchTerms([...(options.search_terms ?? [])]);
151
+ const search = buildCandidateSearchRecords({
152
+ candidates: state.contentCandidates,
153
+ focusPaths: [...(options.focus_paths ?? [])],
154
+ searchTerms: expandedSearchTerms.terms,
155
+ callerTermCount: expandedSearchTerms.callerTermCount,
156
+ maxSearchResults: options.budget.max_search_results,
157
+ maxPreviewBytes: internalCaps.max_search_preview_bytes,
158
+ maxCandidateBytes: internalCaps.max_candidate_bytes,
159
+ ...(options.signal == null ? {} : { signal: options.signal }),
160
+ });
161
+ if (search.capHit) {
162
+ state.metrics.search_cap_hit = true;
163
+ }
164
+ const slices = buildCandidateSliceRecords({
165
+ candidates: state.contentCandidates,
166
+ focusPaths: [...(options.focus_paths ?? [])],
167
+ searchRecords: search.records,
168
+ maxCandidateSlices: internalCaps.max_candidate_slices,
169
+ maxCandidateBytes: Math.max(0, internalCaps.max_candidate_bytes - search.candidateBytes),
170
+ maxSearchSliceBytesPerCandidate: options.budget.max_bytes_per_file,
171
+ maxFullFocusSliceBytes: Math.min(options.budget.max_bytes_per_file, DEFAULT_MAX_FULL_IMPLEMENTATION_FOCUS_SLICE_BYTES),
172
+ ...(options.signal == null ? {} : { signal: options.signal }),
173
+ });
174
+ state.omissions.push(...slices.omissions);
175
+ if (slices.capHit) {
176
+ state.metrics.candidate_cap_hit = true;
177
+ }
178
+ const reviewObligations = buildContextReviewObligations({
179
+ candidates: state.contentCandidates,
180
+ focusPaths: (options.focus_paths ?? []).map((focus) => ({
181
+ root_index: focus.root_index,
182
+ path: focus.root_relative_path,
183
+ kind: focus.kind,
184
+ })),
185
+ searchRecords: search.records,
186
+ slices: slices.records,
187
+ policy: options.coverage_policy,
188
+ });
189
+ classifyContextEvidenceCandidates({
190
+ candidates: state.contentCandidates,
191
+ focusPaths: options.focus_paths ?? [],
192
+ searchRecords: search.records,
193
+ policy: options.coverage_policy,
194
+ obligations: reviewObligations,
195
+ });
196
+ auditScanCapFocusedPaths({
197
+ focusPaths: [...(options.focus_paths ?? [])],
198
+ state,
199
+ });
200
+ return {
201
+ tree: state.tree,
202
+ content_candidates: state.contentCandidates,
203
+ search: search.records,
204
+ slices: slices.records,
205
+ omissions: state.omissions,
206
+ metrics: state.metrics,
207
+ search_coverage: search.coverage,
208
+ ...(reviewObligations == null ? {} : { review_obligations: reviewObligations }),
209
+ };
210
+ }
211
+ export function expandSearchTerms(searchTerms) {
212
+ const expanded = [];
213
+ const seen = new Set();
214
+ let aggregateBytes = 0;
215
+ // Caller terms are admitted first and are never displaced by heuristic
216
+ // expansion. Public post-parse validation owns their individual/aggregate
217
+ // bounds; this function only bounds derived fan-out before stream creation.
218
+ for (const term of searchTerms) {
219
+ const added = addExpandedSearchTerm(expanded, seen, term);
220
+ aggregateBytes += added;
221
+ }
222
+ const callerTermCount = expanded.length;
223
+ for (const term of searchTerms) {
224
+ for (const derived of deriveGenericSearchTerms(term)) {
225
+ const normalized = derived.trim();
226
+ if (normalized.length > MAX_DERIVED_SEARCH_TERM_LENGTH ||
227
+ expanded.length >= MAX_EXPANDED_SEARCH_TERMS) {
228
+ continue;
229
+ }
230
+ const bytes = Buffer.byteLength(normalized, "utf8");
231
+ if (aggregateBytes + bytes > MAX_EXPANDED_SEARCH_TERM_BYTES) {
232
+ continue;
233
+ }
234
+ aggregateBytes += addExpandedSearchTerm(expanded, seen, normalized);
235
+ }
236
+ }
237
+ return { terms: expanded, callerTermCount };
238
+ }
239
+ function classifyContextEvidenceCandidates(input) {
240
+ if (input.policy?.id == null) {
241
+ return;
242
+ }
243
+ const directSearchKeys = new Set(input.searchRecords.map(candidateKey));
244
+ const focusedFileKeys = new Set(input.focusPaths
245
+ .filter((focus) => focus.kind === "file")
246
+ .map((focus) => candidateKey({
247
+ root_index: focus.root_index,
248
+ path: focus.root_relative_path,
249
+ })));
250
+ const targetCandidateIds = new Set(input.obligations?.targets.map((target) => target.candidate_id) ?? []);
251
+ const regressionCandidateIds = new Set(input.obligations?.targets.flatMap((target) => target.regression_links.map((link) => link.candidate_id)) ?? []);
252
+ for (const candidate of input.candidates) {
253
+ const classes = [];
254
+ const key = candidateKey(candidate);
255
+ const packetCandidateId = `c:${String(candidate.root_index)}:${candidate.path}`;
256
+ const isReviewTarget = targetCandidateIds.has(packetCandidateId);
257
+ if (isReviewTarget) {
258
+ classes.push("review_target");
259
+ }
260
+ if (isTestPath(candidate.path) && regressionCandidateIds.has(packetCandidateId)) {
261
+ classes.push("regression_test");
262
+ }
263
+ const hasDiscoveryEvidence = directSearchKeys.has(key) ||
264
+ focusedFileKeys.has(key) ||
265
+ candidate.selection_hint != null;
266
+ if (genericPathKind(candidate.path) === "config" && hasDiscoveryEvidence) {
267
+ classes.push("configuration");
268
+ }
269
+ if (candidate.selection_hint === "local_dependency") {
270
+ classes.push("dependency");
271
+ }
272
+ candidate.evidence_classes = classes;
273
+ }
274
+ }
275
+ function addExpandedSearchTerm(expanded, seen, term) {
276
+ const normalized = term.trim();
277
+ if (normalized.length === 0) {
278
+ return 0;
279
+ }
280
+ const key = normalized.toLowerCase();
281
+ if (seen.has(key)) {
282
+ return 0;
283
+ }
284
+ expanded.push(normalized);
285
+ seen.add(key);
286
+ return Buffer.byteLength(normalized, "utf8");
287
+ }
288
+ function deriveGenericSearchTerms(term) {
289
+ const tokens = tokenizeGenericSearchTerm(term);
290
+ const terms = new Set(tokens);
291
+ for (let index = 0; index < tokens.length - 1; index++) {
292
+ const current = tokens[index];
293
+ const next = tokens[index + 1];
294
+ if (current == null || next == null) {
295
+ continue;
296
+ }
297
+ terms.add(`${current}_${next}`);
298
+ terms.add(`${current}${next}`);
299
+ }
300
+ return [...terms].filter((value) => value.length >= 3).sort();
301
+ }
302
+ function tokenizeGenericSearchTerm(value) {
303
+ return value
304
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
305
+ .replace(/\.[A-Za-z0-9]+$/g, " ")
306
+ .split(/[^A-Za-z0-9]+/)
307
+ .map((word) => normalizeGenericSearchWord(word))
308
+ .filter((word) => word.length >= 3 && !GENERIC_SEARCH_STOPWORDS.has(word));
309
+ }
310
+ function normalizeGenericSearchWord(word) {
311
+ const lower = word.toLowerCase();
312
+ if (lower.length > 4 && lower.endsWith("ies")) {
313
+ return `${lower.slice(0, -3)}y`;
314
+ }
315
+ if (lower.length > 3 && lower.endsWith("s")) {
316
+ return lower.slice(0, -1);
317
+ }
318
+ return lower;
319
+ }
320
+ const GENERIC_SEARCH_STOPWORDS = new Set([
321
+ "the",
322
+ "and",
323
+ "for",
324
+ "with",
325
+ "that",
326
+ "this",
327
+ "from",
328
+ "into",
329
+ "without",
330
+ "change",
331
+ "fix",
332
+ "add",
333
+ "new",
334
+ "safe",
335
+ "safely",
336
+ "test",
337
+ "tests",
338
+ ]);
339
+ async function processImportAndSupportFiles(input) {
340
+ for (let pass = 0; pass < IMPORT_EXPANSION_PASSES; pass++) {
341
+ const before = input.state.contentCandidates.length;
342
+ const dependencies = dependencyPathChoicesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
343
+ for (const choices of dependencies) {
344
+ assertNotAborted(input.signal);
345
+ const relativePath = await firstExistingPath(input.root, choices, input.fs);
346
+ if (relativePath == null) {
347
+ continue;
348
+ }
349
+ await processFile({
350
+ root: input.root,
351
+ absolutePath: path.join(input.root.path, relativePath),
352
+ relativePath,
353
+ matcher: input.matcher,
354
+ focusPaths: input.focusPaths,
355
+ allowSecrets: input.allowSecrets,
356
+ budget: input.budget,
357
+ internalCaps: input.internalCaps,
358
+ ignoreIncludeGlob: true,
359
+ ignoreScanCap: true,
360
+ selectionHint: "local_dependency",
361
+ ...(input.signal == null ? {} : { signal: input.signal }),
362
+ fs: input.fs,
363
+ state: input.state,
364
+ });
365
+ }
366
+ if (input.state.contentCandidates.length === before) {
367
+ break;
368
+ }
369
+ }
370
+ const supportFiles = projectSupportFilesForCandidates(input.root, input.state.contentCandidates.filter((candidate) => candidate.root_index === input.root.root_index));
371
+ for (const relativePath of supportFiles) {
372
+ assertNotAborted(input.signal);
373
+ if ((await firstExistingPath(input.root, [relativePath], input.fs)) == null) {
374
+ continue;
375
+ }
376
+ await processFile({
377
+ root: input.root,
378
+ absolutePath: path.join(input.root.path, relativePath),
379
+ relativePath,
380
+ matcher: input.matcher,
381
+ focusPaths: input.focusPaths,
382
+ allowSecrets: input.allowSecrets,
383
+ budget: input.budget,
384
+ internalCaps: input.internalCaps,
385
+ ignoreIncludeGlob: true,
386
+ ignoreScanCap: true,
387
+ selectionHint: "project_support",
388
+ ...(input.signal == null ? {} : { signal: input.signal }),
389
+ fs: input.fs,
390
+ state: input.state,
391
+ });
392
+ }
393
+ }
394
+ function dependencyPathChoicesForCandidates(root, candidates) {
395
+ const choices = [];
396
+ const seen = new Set();
397
+ for (const candidate of candidates) {
398
+ for (const specifier of importSpecifiers(candidate.content)) {
399
+ const resolved = resolveImportSpecifierChoices(root, candidate.path, specifier);
400
+ const useful = resolved.filter((pathChoice) => isUsefulLocalDependency(candidate.path, pathChoice));
401
+ if (useful.length === 0) {
402
+ continue;
403
+ }
404
+ const key = useful.join("\u0000");
405
+ if (!seen.has(key)) {
406
+ choices.push(useful);
407
+ seen.add(key);
408
+ }
409
+ }
410
+ }
411
+ return choices.sort((left, right) => left[0].localeCompare(right[0]));
412
+ }
413
+ async function firstExistingPath(root, choices, fs) {
414
+ for (const relativePath of choices) {
415
+ try {
416
+ const stats = await fs.stat(path.join(root.path, relativePath));
417
+ if (stats.isFile()) {
418
+ return relativePath;
419
+ }
420
+ }
421
+ catch (error) {
422
+ if (isExpectedDescendantFilesystemError(error)) {
423
+ continue;
424
+ }
425
+ throw error;
426
+ }
427
+ }
428
+ return null;
429
+ }
430
+ function projectSupportFilesForCandidates(root, candidates) {
431
+ const prefixes = new Set();
432
+ for (const candidate of candidates) {
433
+ const prefix = projectPackagePrefix(candidate.path);
434
+ if (prefix != null) {
435
+ prefixes.add(prefix);
436
+ }
437
+ }
438
+ const paths = [];
439
+ for (const prefix of [...prefixes].sort()) {
440
+ for (const filename of PROJECT_SUPPORT_FILENAMES) {
441
+ const relativePath = prefix.length === 0 ? filename : `${prefix}/${filename}`;
442
+ if (isSamePathOrDescendant(path.join(root.path, relativePath), root.path)) {
443
+ paths.push(relativePath);
444
+ }
445
+ }
446
+ }
447
+ return paths;
448
+ }
449
+ function importSpecifiers(content) {
450
+ const specifiers = [];
451
+ const patterns = [
452
+ /\bimport\s+(?:type\s+)?(?:[^'";]+?\s+from\s+)?['"]([^'"]+)['"]/g,
453
+ /\bexport\s+(?:type\s+)?[^'";]+?\s+from\s+['"]([^'"]+)['"]/g,
454
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
455
+ /^\s*from\s+([A-Za-z_][\w.]*|\.+[\w.]*)\s+import\b/gm,
456
+ /^\s*import\s+(app(?:\.[A-Za-z_][\w]*)+)\b/gm,
457
+ ];
458
+ for (const pattern of patterns) {
459
+ let match;
460
+ while ((match = pattern.exec(content)) != null) {
461
+ const specifier = match[1];
462
+ if (specifier != null) {
463
+ specifiers.push(specifier);
464
+ }
465
+ }
466
+ }
467
+ return specifiers;
468
+ }
469
+ function resolveImportSpecifierChoices(root, importerPath, specifier) {
470
+ const base = resolveImportBase(importerPath, specifier);
471
+ if (base == null) {
472
+ return [];
473
+ }
474
+ const choices = [];
475
+ for (const candidate of importResolutionCandidates(base)) {
476
+ const absolute = path.join(root.path, candidate);
477
+ if (!isSamePathOrDescendant(absolute, root.path)) {
478
+ continue;
479
+ }
480
+ try {
481
+ const normalized = normalizeContextBuilderRootRelativePath(candidate);
482
+ if (normalized === candidate) {
483
+ choices.push(candidate);
484
+ }
485
+ }
486
+ catch {
487
+ continue;
488
+ }
489
+ }
490
+ return choices;
491
+ }
492
+ function resolveImportBase(importerPath, specifier) {
493
+ if (specifier.startsWith("@/")) {
494
+ const srcPrefix = sourceRootPrefix(importerPath);
495
+ return srcPrefix == null ? null : `${srcPrefix}/${specifier.slice(2)}`;
496
+ }
497
+ if (specifier.startsWith("app.") && importerPath.endsWith(".py")) {
498
+ const appPrefix = pythonAppRootPrefix(importerPath);
499
+ return appPrefix == null
500
+ ? null
501
+ : `${appPrefix}/${specifier.slice("app.".length).replaceAll(".", "/")}`;
502
+ }
503
+ if (specifier.startsWith(".") && importerPath.endsWith(".py")) {
504
+ return resolvePythonRelativeImportBase(importerPath, specifier);
505
+ }
506
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
507
+ return normalizeSlash(path.normalize(path.join(path.dirname(importerPath), specifier)));
508
+ }
509
+ return null;
510
+ }
511
+ function importResolutionCandidates(base) {
512
+ if (/\.(?:[cm]?[jt]sx?|json)$/.test(base)) {
513
+ return [base];
514
+ }
515
+ return [
516
+ `${base}.ts`,
517
+ `${base}.tsx`,
518
+ `${base}.js`,
519
+ `${base}.jsx`,
520
+ `${base}.py`,
521
+ `${base}.json`,
522
+ `${base}/index.ts`,
523
+ `${base}/index.tsx`,
524
+ `${base}/index.js`,
525
+ `${base}/index.jsx`,
526
+ `${base}/__init__.py`,
527
+ ];
528
+ }
529
+ // Adjacency radius (in directory hops) within which an imported file is treated
530
+ // as a useful local dependency regardless of stem/kind heuristics.
531
+ const LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE = 6;
532
+ function isUsefulLocalDependency(importerPath, dependencyPath) {
533
+ if (importerPath === dependencyPath) {
534
+ return false;
535
+ }
536
+ if (directoryDistance(importerPath, dependencyPath) <=
537
+ LOCAL_DEPENDENCY_MAX_DIRECTORY_DISTANCE) {
538
+ return true;
539
+ }
540
+ if (normalizedPathStem(importerPath) !== normalizedPathStem(dependencyPath)) {
541
+ return false;
542
+ }
543
+ return genericPathKind(importerPath) !== genericPathKind(dependencyPath);
544
+ }
545
+ function sourceRootPrefix(importerPath) {
546
+ const srcIndex = importerPath.indexOf("/src/");
547
+ if (srcIndex < 0) {
548
+ return importerPath.startsWith("src/") ? "src" : null;
549
+ }
550
+ return importerPath.slice(0, srcIndex + "/src".length);
551
+ }
552
+ function projectPackagePrefix(candidatePath) {
553
+ const srcIndex = candidatePath.indexOf("/src/");
554
+ if (srcIndex < 0) {
555
+ return null;
556
+ }
557
+ return candidatePath.slice(0, srcIndex);
558
+ }
559
+ function pythonAppRootPrefix(importerPath) {
560
+ const appIndex = importerPath.indexOf("/app/");
561
+ if (appIndex >= 0) {
562
+ return importerPath.slice(0, appIndex + "/app".length);
563
+ }
564
+ return importerPath.startsWith("app/") ? "app" : null;
565
+ }
566
+ function resolvePythonRelativeImportBase(importerPath, specifier) {
567
+ const match = /^(\.+)(.*)$/.exec(specifier);
568
+ if (match == null) {
569
+ return null;
570
+ }
571
+ const dots = match[1] ?? "";
572
+ const moduleTail = match[2] ?? "";
573
+ let baseDir = path.dirname(importerPath);
574
+ for (let index = 1; index < dots.length; index++) {
575
+ baseDir = path.dirname(baseDir);
576
+ }
577
+ const suffix = moduleTail.length === 0 ? "" : moduleTail.replaceAll(".", "/");
578
+ return normalizeSlash(path.normalize(suffix.length === 0 ? baseDir : path.join(baseDir, suffix)));
579
+ }
580
+ function normalizeSlash(value) {
581
+ return value.replaceAll(path.sep, "/");
582
+ }
583
+ async function processExactFocusFiles(input) {
584
+ const focusFiles = [...input.focusPaths]
585
+ .filter((focus) => focus.root_index === input.root.root_index)
586
+ .filter((focus) => focus.kind === "file")
587
+ .sort(compareFocusPathsForDiscovery);
588
+ for (const focus of focusFiles) {
589
+ assertNotAborted(input.signal);
590
+ await processFile({
591
+ root: input.root,
592
+ absolutePath: focus.absolute_path,
593
+ relativePath: focus.root_relative_path,
594
+ matcher: input.matcher,
595
+ focusPaths: input.focusPaths,
596
+ allowSecrets: input.allowSecrets,
597
+ budget: input.budget,
598
+ internalCaps: input.internalCaps,
599
+ focusScanExempt: true,
600
+ ...(input.signal == null ? {} : { signal: input.signal }),
601
+ fs: input.fs,
602
+ state: input.state,
603
+ });
604
+ }
605
+ }
606
+ // Pre-walk explicitly-focused directories before global traversal so a focus
607
+ // directory late in sort order cannot be starved by max_scan_files exhaustion
608
+ // from unrelated trees. Files processed here are focus-scan-exempt (they never
609
+ // erode traversal headroom) but remain bounded by the discovery byte and
610
+ // candidate caps; glob/secret/binary semantics are unchanged because the walk
611
+ // reuses traverseDirectory/processFile verbatim.
612
+ async function processFocusDirectories(input) {
613
+ const focusDirectories = [...input.focusPaths]
614
+ .filter((focus) => focus.root_index === input.root.root_index)
615
+ .filter((focus) => focus.kind === "directory")
616
+ .sort(compareFocusPathsForDiscovery);
617
+ for (const focus of focusDirectories) {
618
+ assertNotAborted(input.signal);
619
+ // A root-level focus gains nothing from a pre-walk (global traversal
620
+ // starts there immediately), and pre-walking it would exempt the entire
621
+ // root from max_scan_files.
622
+ if (focus.root_relative_path === "") {
623
+ continue;
624
+ }
625
+ // Leave include-glob-pruned focus directories to global traversal, which
626
+ // emits the existing requested-evidence omission for them.
627
+ if (!input.matcher.canContainIncludedPath(focus.root_relative_path)) {
628
+ continue;
629
+ }
630
+ await traverseDirectory({
631
+ root: input.root,
632
+ absoluteDir: focus.absolute_path,
633
+ relativeDir: focus.root_relative_path,
634
+ matcher: input.matcher,
635
+ focusPaths: input.focusPaths,
636
+ allowSecrets: input.allowSecrets,
637
+ budget: input.budget,
638
+ internalCaps: input.internalCaps,
639
+ excludedRealRoots: input.excludedRealRoots,
640
+ focusScanExempt: true,
641
+ ...(input.signal == null ? {} : { signal: input.signal }),
642
+ fs: input.fs,
643
+ state: input.state,
644
+ });
645
+ }
646
+ }
647
+ export function buildCandidateSearchRecords(input) {
648
+ const maxPreviewBytes = input.maxPreviewBytes ??
649
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_search_preview_bytes;
650
+ const maxCandidateBytes = input.maxCandidateBytes ??
651
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
652
+ const records = [];
653
+ let previewBytes = 0;
654
+ let candidateBytes = 0;
655
+ let capHit = false;
656
+ const callerTermCount = Math.max(0, Math.min(input.callerTermCount ?? 0, input.searchTerms.length));
657
+ const searchTerms = input.searchTerms
658
+ .map((term, index) => ({ term, index }))
659
+ .filter(({ term }) => term.trim().length > 0);
660
+ const callerTerms = searchTerms.filter(({ index }) => index < callerTermCount);
661
+ const derivedTerms = searchTerms.filter(({ index }) => index >= callerTermCount);
662
+ // Candidates whose content was examined for at least one term before a hard
663
+ // cap ended the walk. Key set is bounded by the candidate pool; only counts
664
+ // are surfaced.
665
+ const searchedCandidateKeys = new Set();
666
+ const callerTermCoverage = callerTerms.map(({ term, index }) => ({
667
+ term,
668
+ search_term_index: index,
669
+ hit_count: 0,
670
+ scan_complete: false,
671
+ }));
672
+ let callerRecordCount = 0;
673
+ const finalize = () => ({
674
+ records,
675
+ capHit,
676
+ candidateBytes,
677
+ coverage: {
678
+ eligible_candidate_count: input.candidates.length,
679
+ searched_candidate_count: searchedCandidateKeys.size,
680
+ unsearched_candidate_count: input.candidates.length - searchedCandidateKeys.size,
681
+ caller_term_count: callerTerms.length,
682
+ derived_term_count: derivedTerms.length,
683
+ caller_record_count: callerRecordCount,
684
+ derived_record_count: records.length - callerRecordCount,
685
+ caller_coverage_complete: callerTermCoverage.every((coverage) => coverage.scan_complete),
686
+ caller_terms: callerTermCoverage,
687
+ },
688
+ });
689
+ if (searchTerms.length === 0) {
690
+ return finalize();
691
+ }
692
+ const focusRank = new Map();
693
+ for (const focus of input.focusPaths ?? []) {
694
+ if (focus.kind !== "file") {
695
+ continue;
696
+ }
697
+ const key = candidateKey({
698
+ root_index: focus.root_index,
699
+ path: focus.root_relative_path,
700
+ });
701
+ if (!focusRank.has(key)) {
702
+ focusRank.set(key, focusRank.size);
703
+ }
704
+ }
705
+ const candidates = [...input.candidates].sort((left, right) => {
706
+ const leftRank = focusRank.get(candidateKey(left)) ?? Number.MAX_SAFE_INTEGER;
707
+ const rightRank = focusRank.get(candidateKey(right)) ?? Number.MAX_SAFE_INTEGER;
708
+ return (leftRank - rightRank ||
709
+ compareContentCandidates(left, right));
710
+ });
711
+ const candidateLines = [];
712
+ for (const candidate of candidates) {
713
+ assertNotAborted(input.signal);
714
+ candidateLines.push(splitTextLines(candidate.content));
715
+ }
716
+ // Emits one found hit unless a hard cap rejects it. Cap semantics match the
717
+ // pre-phasing scheduler: the record/byte caps are checked after the hit is
718
+ // found, and tripping either one ends the whole search with capHit set.
719
+ const emitSearchHit = (stream, hit) => {
720
+ if (records.length >= input.maxSearchResults) {
721
+ capHit = true;
722
+ return false;
723
+ }
724
+ const preview = boundedPreview(hit.line, hit.columnIndex, stream.searchTerm.term.length);
725
+ const previewSize = Buffer.byteLength(preview, "utf8");
726
+ const bounded = previewBytes + previewSize <= maxPreviewBytes ? preview : "";
727
+ const record = {
728
+ root_index: stream.candidate.root_index,
729
+ path: stream.candidate.path,
730
+ line: hit.lineIndex + 1,
731
+ column: hit.columnIndex + 1,
732
+ search_term: stream.searchTerm.term,
733
+ search_term_index: stream.searchTerm.index,
734
+ preview: bounded,
735
+ };
736
+ const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
737
+ if (candidateBytes + recordBytes > maxCandidateBytes) {
738
+ capHit = true;
739
+ return false;
740
+ }
741
+ previewBytes += Buffer.byteLength(bounded, "utf8");
742
+ candidateBytes += recordBytes;
743
+ records.push(record);
744
+ return true;
745
+ };
746
+ if (callerTerms.length > 0) {
747
+ // Phase 1 — exact caller terms get first claim on the record budget. One
748
+ // cursor per caller term walks the complete candidate list (focus rank
749
+ // first, one hit per candidate per pass), and the scheduler round-robins
750
+ // across the term cursors, so neither a common sibling term nor an early
751
+ // alphabetical candidate prefix can starve a rare late-path caller hit.
752
+ const cursors = callerTermCoverage.map((coverage) => ({
753
+ coverage,
754
+ streams: candidates.map((candidate, candidateIndex) => ({
755
+ candidate,
756
+ searchTerm: { term: coverage.term, index: coverage.search_term_index },
757
+ lines: candidateLines[candidateIndex] ?? [],
758
+ lineIndex: 0,
759
+ fromIndex: 0,
760
+ done: false,
761
+ })),
762
+ cursor: 0,
763
+ active: candidates.length,
764
+ exhausted: false,
765
+ }));
766
+ let liveCursors = cursors.length;
767
+ while (liveCursors > 0) {
768
+ for (const cursor of cursors) {
769
+ assertNotAborted(input.signal);
770
+ if (cursor.exhausted) {
771
+ continue;
772
+ }
773
+ const next = nextCallerTermHit(cursor, searchedCandidateKeys);
774
+ if (next == null) {
775
+ // Natural exhaustion: this term's cursor examined every candidate.
776
+ cursor.exhausted = true;
777
+ cursor.coverage.scan_complete = true;
778
+ liveCursors--;
779
+ continue;
780
+ }
781
+ if (!emitSearchHit(next.stream, next.hit)) {
782
+ return finalize();
783
+ }
784
+ cursor.coverage.hit_count++;
785
+ callerRecordCount++;
786
+ }
787
+ }
788
+ }
789
+ if (derivedTerms.length > 0) {
790
+ // Phase 2 — derived/heuristic terms spend whatever budget remains, using
791
+ // the pre-existing candidate-major one-hit-per-stream round-robin.
792
+ const streams = [];
793
+ for (const [candidateIndex, candidate] of candidates.entries()) {
794
+ assertNotAborted(input.signal);
795
+ const lines = candidateLines[candidateIndex] ?? [];
796
+ for (const searchTerm of derivedTerms) {
797
+ streams.push({
798
+ candidate,
799
+ searchTerm,
800
+ lines,
801
+ lineIndex: 0,
802
+ fromIndex: 0,
803
+ done: false,
804
+ });
805
+ }
806
+ }
807
+ let activeStreams = streams.length;
808
+ while (activeStreams > 0) {
809
+ let foundInRound = false;
810
+ for (const stream of streams) {
811
+ assertNotAborted(input.signal);
812
+ if (stream.done) {
813
+ continue;
814
+ }
815
+ searchedCandidateKeys.add(candidateKey(stream.candidate));
816
+ const hit = nextSearchHit(stream);
817
+ if (hit == null) {
818
+ stream.done = true;
819
+ activeStreams--;
820
+ continue;
821
+ }
822
+ foundInRound = true;
823
+ if (!emitSearchHit(stream, hit)) {
824
+ return finalize();
825
+ }
826
+ }
827
+ if (!foundInRound) {
828
+ break;
829
+ }
830
+ }
831
+ }
832
+ return finalize();
833
+ }
834
+ // Advances one caller term's cursor to its next hit, cycling the candidate
835
+ // list one hit per candidate per pass. Scanning is free — only emitted records
836
+ // consume the budget — so a rare term reaches late-sorting candidates even
837
+ // when earlier candidates are saturated with other terms' hits. Returns
838
+ // undefined exactly when every candidate stream is exhausted.
839
+ function nextCallerTermHit(cursor, searchedCandidateKeys) {
840
+ const total = cursor.streams.length;
841
+ for (let visited = 0; visited < total && cursor.active > 0; visited++) {
842
+ const stream = cursor.streams[cursor.cursor];
843
+ cursor.cursor = (cursor.cursor + 1) % total;
844
+ if (stream == null || stream.done) {
845
+ continue;
846
+ }
847
+ searchedCandidateKeys.add(candidateKey(stream.candidate));
848
+ const hit = nextSearchHit(stream);
849
+ if (hit == null) {
850
+ stream.done = true;
851
+ cursor.active--;
852
+ continue;
853
+ }
854
+ return { stream, hit };
855
+ }
856
+ return undefined;
857
+ }
858
+ export function buildCandidateSliceRecords(input) {
859
+ const maxCandidateSlices = input.maxCandidateSlices ??
860
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_slices;
861
+ const maxCandidateBytes = input.maxCandidateBytes ??
862
+ CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS.max_candidate_bytes;
863
+ const maxFullFocusSliceBytes = input.maxFullFocusSliceBytes ??
864
+ DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES;
865
+ const candidateByIdentity = new Map(input.candidates.map((candidate) => [candidateKey(candidate), candidate]));
866
+ const records = [];
867
+ const omissions = [];
868
+ let bytes = 0;
869
+ let capHit = false;
870
+ const searchSliceBytesByCandidate = new Map();
871
+ const focusedLargeSearchCandidates = new Map();
872
+ const expectedFocusedSearchRanges = new Map();
873
+ const survivedFocusedSearchRanges = new Map();
874
+ const focusSliceSurvivors = new Set();
875
+ const slicedLargeFocusedCandidates = new Set();
876
+ const omittedRequestedCandidates = new Set();
877
+ const pushRequestedCandidateOmission = (candidate, kind, message) => {
878
+ const key = candidateKey(candidate);
879
+ if (omittedRequestedCandidates.has(key)) {
880
+ return;
881
+ }
882
+ omissions.push(requestedEvidenceOmission({
883
+ root_index: candidate.root_index,
884
+ kind,
885
+ message,
886
+ paths: [candidate.path],
887
+ }));
888
+ omittedRequestedCandidates.add(key);
889
+ };
890
+ const pushMissingFocusedSearchOmissions = (keys) => {
891
+ for (const key of keys) {
892
+ if (focusedSearchRangesCovered(expectedFocusedSearchRanges.get(key) ?? [], survivedFocusedSearchRanges.get(key) ?? []) ||
893
+ omittedRequestedCandidates.has(key)) {
894
+ continue;
895
+ }
896
+ const candidate = focusedLargeSearchCandidates.get(key);
897
+ if (candidate == null) {
898
+ continue;
899
+ }
900
+ pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file had search evidence, but no bounded search slice survived max_bytes_per_file or candidate caps");
901
+ }
902
+ };
903
+ const pushMissingFocusedRangeOmissions = (keys) => {
904
+ for (const key of keys) {
905
+ if (focusSliceSurvivors.has(key) || omittedRequestedCandidates.has(key)) {
906
+ continue;
907
+ }
908
+ const candidate = candidateByIdentity.get(key);
909
+ if (candidate == null) {
910
+ continue;
911
+ }
912
+ pushRequestedCandidateOmission(candidate, "candidate_cap", "focused file slice could not be represented within candidate slice or byte caps");
913
+ }
914
+ };
915
+ const append = (record) => {
916
+ const key = candidateKey(record);
917
+ if (record.source === "search" &&
918
+ input.maxSearchSliceBytesPerCandidate != null) {
919
+ const contentBytes = Buffer.byteLength(record.content, "utf8");
920
+ const candidateBytes = searchSliceBytesByCandidate.get(key) ?? 0;
921
+ if (candidateBytes + contentBytes > input.maxSearchSliceBytesPerCandidate) {
922
+ capHit = true;
923
+ return true;
924
+ }
925
+ searchSliceBytesByCandidate.set(key, candidateBytes + contentBytes);
926
+ }
927
+ const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
928
+ if (records.length >= maxCandidateSlices || bytes + recordBytes > maxCandidateBytes) {
929
+ capHit = true;
930
+ return false;
931
+ }
932
+ records.push(record);
933
+ bytes += recordBytes;
934
+ if (record.source === "search") {
935
+ const survived = survivedFocusedSearchRanges.get(key);
936
+ if (survived != null) {
937
+ survived.push({
938
+ start_line: record.start_line,
939
+ end_line: record.end_line,
940
+ ...(record.search_term_index == null
941
+ ? {}
942
+ : { search_term_index: record.search_term_index }),
943
+ });
944
+ }
945
+ }
946
+ else {
947
+ focusSliceSurvivors.add(key);
948
+ }
949
+ return true;
950
+ };
951
+ const searchRanges = new Map();
952
+ for (const hit of [...input.searchRecords].sort(compareSearchRecords)) {
953
+ const candidate = candidateByIdentity.get(searchKey(hit));
954
+ if (candidate == null || candidate.line_count === 0) {
955
+ continue;
956
+ }
957
+ const ranges = searchRanges.get(candidateKey(candidate)) ?? [];
958
+ ranges.push({
959
+ start_line: Math.max(1, hit.line - SEARCH_WINDOW_LINES_BEFORE),
960
+ end_line: Math.min(candidate.line_count, hit.line + SEARCH_WINDOW_LINES_AFTER),
961
+ search_term_index: hit.search_term_index,
962
+ });
963
+ searchRanges.set(candidateKey(candidate), ranges);
964
+ }
965
+ const focusRanges = new Map();
966
+ for (const focus of [...input.focusPaths].sort(compareFocusPathsForDiscovery)) {
967
+ for (const candidate of input.candidates) {
968
+ if (candidate.root_index === focus.root_index &&
969
+ (candidate.path === focus.root_relative_path ||
970
+ (focus.kind === "directory" &&
971
+ isRootRelativeDescendant(candidate.path, focus.root_relative_path)))) {
972
+ const key = candidateKey(candidate);
973
+ if (candidate.byte_length > maxFullFocusSliceBytes) {
974
+ if (searchRanges.has(key)) {
975
+ focusedLargeSearchCandidates.set(key, candidate);
976
+ expectedFocusedSearchRanges.set(key, mergeLineRanges(searchRanges.get(key) ?? [], {
977
+ bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
978
+ maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
979
+ }));
980
+ survivedFocusedSearchRanges.set(key, []);
981
+ }
982
+ else if (!slicedLargeFocusedCandidates.has(key)) {
983
+ // Never silently drop an explicitly-focused file. With no per-file
984
+ // search evidence, include a bounded head slice up to the focus
985
+ // byte cap and record a material, non-silent partial-focus signal
986
+ // instead of a too_large omission.
987
+ if (firstLineExceedsCap(candidate.content, maxFullFocusSliceBytes)) {
988
+ // The first logical line alone exceeds the cap (e.g. a minified
989
+ // single-line file). The line-range model cannot express a partial
990
+ // line, so a head slice would blow the byte bound. Omit the file
991
+ // with an explicit material reason rather than exceeding the bound.
992
+ omissions.push(focusOverCapNotice(candidate, maxFullFocusSliceBytes));
993
+ slicedLargeFocusedCandidates.add(key);
994
+ continue;
995
+ }
996
+ const headEndLine = headSliceEndLine(candidate.content, maxFullFocusSliceBytes);
997
+ const ranges = focusRanges.get(key) ?? [];
998
+ ranges.push({ start_line: 1, end_line: headEndLine });
999
+ focusRanges.set(key, ranges);
1000
+ omissions.push(focusSlicedNotice(candidate, headEndLine));
1001
+ slicedLargeFocusedCandidates.add(key);
1002
+ }
1003
+ continue;
1004
+ }
1005
+ const ranges = focusRanges.get(candidateKey(candidate)) ?? [];
1006
+ ranges.push({ start_line: 1, end_line: Math.max(1, candidate.line_count) });
1007
+ focusRanges.set(candidateKey(candidate), ranges);
1008
+ }
1009
+ }
1010
+ }
1011
+ const dependencyRanges = new Map();
1012
+ for (const candidate of input.candidates) {
1013
+ if (candidate.selection_hint == null || candidate.line_count === 0) {
1014
+ continue;
1015
+ }
1016
+ if (candidate.byte_length >
1017
+ (input.maxSearchSliceBytesPerCandidate ?? Number.MAX_SAFE_INTEGER)) {
1018
+ continue;
1019
+ }
1020
+ dependencyRanges.set(candidateKey(candidate), [
1021
+ { start_line: 1, end_line: Math.max(1, candidate.line_count) },
1022
+ ]);
1023
+ }
1024
+ // Focus slices are appended FIRST so max_candidate_slices/max_candidate_bytes
1025
+ // pressure prunes search and dependency material before explicitly-focused
1026
+ // ranges — never the other way around.
1027
+ for (const [key, ranges] of [...focusRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1028
+ assertNotAborted(input.signal);
1029
+ const candidate = candidateByIdentity.get(key);
1030
+ if (candidate == null) {
1031
+ continue;
1032
+ }
1033
+ for (const range of mergeLineRanges(ranges)) {
1034
+ if (!append({
1035
+ root_index: candidate.root_index,
1036
+ path: candidate.path,
1037
+ start_line: range.start_line,
1038
+ end_line: range.end_line,
1039
+ source: "focus",
1040
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
1041
+ })) {
1042
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
1043
+ pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
1044
+ return { records, capHit, omissions };
1045
+ }
1046
+ }
1047
+ }
1048
+ for (const [key, ranges] of [...searchRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1049
+ assertNotAborted(input.signal);
1050
+ const candidate = candidateByIdentity.get(key);
1051
+ if (candidate == null || candidate.line_count === 0) {
1052
+ continue;
1053
+ }
1054
+ for (const range of mergeLineRanges(ranges, {
1055
+ bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
1056
+ maxMergedSpanLines: SEARCH_MAX_MERGED_SPAN_LINES,
1057
+ })) {
1058
+ if (!append({
1059
+ root_index: candidate.root_index,
1060
+ path: candidate.path,
1061
+ start_line: range.start_line,
1062
+ end_line: range.end_line,
1063
+ source: "search",
1064
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
1065
+ ...(range.search_term_index == null
1066
+ ? {}
1067
+ : { search_term_index: range.search_term_index }),
1068
+ })) {
1069
+ pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
1070
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
1071
+ return { records, capHit, omissions };
1072
+ }
1073
+ }
1074
+ }
1075
+ pushMissingFocusedSearchOmissions(focusedLargeSearchCandidates.keys());
1076
+ for (const [key, ranges] of [...dependencyRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
1077
+ if (searchRanges.has(key) || focusRanges.has(key)) {
1078
+ continue;
1079
+ }
1080
+ assertNotAborted(input.signal);
1081
+ const candidate = candidateByIdentity.get(key);
1082
+ if (candidate == null) {
1083
+ continue;
1084
+ }
1085
+ for (const range of mergeLineRanges(ranges)) {
1086
+ if (!append({
1087
+ root_index: candidate.root_index,
1088
+ path: candidate.path,
1089
+ start_line: range.start_line,
1090
+ end_line: range.end_line,
1091
+ source: "search",
1092
+ content: sliceContent(candidate.content, range.start_line, range.end_line),
1093
+ search_term_index: Number.MAX_SAFE_INTEGER - 1,
1094
+ })) {
1095
+ pushMissingFocusedRangeOmissions(focusRanges.keys());
1096
+ return { records, capHit, omissions };
1097
+ }
1098
+ }
1099
+ }
1100
+ return { records, capHit, omissions };
1101
+ }
1102
+ export function splitTextLines(text) {
1103
+ if (text.length === 0) {
1104
+ return [];
1105
+ }
1106
+ const withoutFinalNewline = text.endsWith("\n")
1107
+ ? text.slice(0, text.endsWith("\r\n") ? -2 : -1)
1108
+ : text;
1109
+ if (withoutFinalNewline.length === 0) {
1110
+ return [""];
1111
+ }
1112
+ return withoutFinalNewline.split(/\r\n|\n/);
1113
+ }
1114
+ function nextSearchHit(stream) {
1115
+ while (stream.lineIndex < stream.lines.length) {
1116
+ const line = stream.lines[stream.lineIndex] ?? "";
1117
+ const columnIndex = line.indexOf(stream.searchTerm.term, stream.fromIndex);
1118
+ if (columnIndex >= 0) {
1119
+ stream.fromIndex = columnIndex + stream.searchTerm.term.length;
1120
+ return { lineIndex: stream.lineIndex, columnIndex, line };
1121
+ }
1122
+ stream.lineIndex++;
1123
+ stream.fromIndex = 0;
1124
+ }
1125
+ return undefined;
1126
+ }
1127
+ export function mergeLineRanges(ranges, options = {}) {
1128
+ const bridgeGapLines = options.bridgeGapLines ?? 0;
1129
+ const maxMergedSpanLines = options.maxMergedSpanLines ?? Number.MAX_SAFE_INTEGER;
1130
+ const sorted = [...ranges].sort((left, right) => left.start_line - right.start_line ||
1131
+ left.end_line - right.end_line ||
1132
+ (left.search_term_index ?? Number.MAX_SAFE_INTEGER) -
1133
+ (right.search_term_index ?? Number.MAX_SAFE_INTEGER));
1134
+ const merged = [];
1135
+ for (const range of sorted) {
1136
+ const last = merged.at(-1);
1137
+ if (last != null &&
1138
+ range.start_line <= last.end_line + bridgeGapLines + 1 &&
1139
+ Math.max(last.end_line, range.end_line) - last.start_line + 1 <=
1140
+ maxMergedSpanLines) {
1141
+ last.end_line = Math.max(last.end_line, range.end_line);
1142
+ last.search_term_index = Math.min(last.search_term_index ?? Number.MAX_SAFE_INTEGER, range.search_term_index ?? Number.MAX_SAFE_INTEGER);
1143
+ if (last.search_term_index === Number.MAX_SAFE_INTEGER) {
1144
+ delete last.search_term_index;
1145
+ }
1146
+ continue;
1147
+ }
1148
+ merged.push({ ...range });
1149
+ }
1150
+ return merged;
1151
+ }
1152
+ // Largest 1-based line count whose cumulative UTF-8 bytes (lines joined by
1153
+ // "\n", matching sliceContent) stay within capBytes. Always returns >= 1 so an
1154
+ // explicitly-focused file is never reduced to nothing.
1155
+ // CALLER COUPLING: line 1 is admitted unconditionally (the `count > 0 &&` guard
1156
+ // skips the cap check for the first line), so callers MUST pre-screen with
1157
+ // firstLineExceedsCap — otherwise an over-cap first line silently breaks the
1158
+ // byte bound.
1159
+ function headSliceEndLine(content, capBytes) {
1160
+ const lines = splitTextLines(content);
1161
+ let bytes = 0;
1162
+ let count = 0;
1163
+ for (const line of lines) {
1164
+ const next = bytes + (count === 0 ? 0 : 1) + Buffer.byteLength(line, "utf8");
1165
+ if (count > 0 && next > capBytes) {
1166
+ break;
1167
+ }
1168
+ bytes = next;
1169
+ count++;
1170
+ }
1171
+ return Math.max(1, count);
1172
+ }
1173
+ // True when even the first logical line's own UTF-8 byte length exceeds capBytes,
1174
+ // i.e. no head slice (which always covers at least line 1) can fit within the cap.
1175
+ function firstLineExceedsCap(content, capBytes) {
1176
+ const lines = splitTextLines(content);
1177
+ const first = lines[0];
1178
+ if (first == null) {
1179
+ return false;
1180
+ }
1181
+ return Buffer.byteLength(first, "utf8") > capBytes;
1182
+ }
1183
+ function focusOverCapNotice(candidate, capBytes) {
1184
+ return {
1185
+ root_index: candidate.root_index,
1186
+ reason: CONTEXT_BUILDER_FOCUS_OVER_CAP_REASON,
1187
+ message: "focused file's first line alone exceeded the bounded focus-slice cap " +
1188
+ `(${capBytes} bytes); omitted rather than exceeding the byte bound`,
1189
+ paths: [candidate.path],
1190
+ };
1191
+ }
1192
+ function focusSlicedNotice(candidate, headEndLine) {
1193
+ return {
1194
+ root_index: candidate.root_index,
1195
+ reason: CONTEXT_BUILDER_FOCUS_SLICED_REASON,
1196
+ message: "focused file exceeded bounded full-file focus size and had no per-file " +
1197
+ `search evidence; included a bounded head slice (lines 1-${headEndLine}) ` +
1198
+ "instead of dropping it",
1199
+ paths: [candidate.path],
1200
+ };
1201
+ }
1202
+ function focusedSearchRangesCovered(expected, survived) {
1203
+ return expected.every((range) => rangeCoveredByRanges(range, survived));
1204
+ }
1205
+ function rangeCoveredByRanges(target, ranges) {
1206
+ let nextLine = target.start_line;
1207
+ for (const range of mergeLineRanges(ranges)) {
1208
+ if (range.end_line < nextLine) {
1209
+ continue;
1210
+ }
1211
+ if (range.start_line > nextLine) {
1212
+ return false;
1213
+ }
1214
+ nextLine = Math.max(nextLine, range.end_line + 1);
1215
+ if (nextLine > target.end_line) {
1216
+ return true;
1217
+ }
1218
+ }
1219
+ return false;
1220
+ }
1221
+ async function traverseDirectory(input) {
1222
+ assertNotAborted(input.signal);
1223
+ if (input.state.stopped) {
1224
+ return;
1225
+ }
1226
+ const directoryKey = candidateKey({
1227
+ root_index: input.root.root_index,
1228
+ path: input.relativeDir,
1229
+ });
1230
+ if (input.state.traversedDirectoryKeys.has(directoryKey)) {
1231
+ return;
1232
+ }
1233
+ input.state.traversedDirectoryKeys.add(directoryKey);
1234
+ if (input.relativeDir !== "" &&
1235
+ (input.matcher.excludes(input.relativeDir) ||
1236
+ input.matcher.excludes(`${input.relativeDir}/__dir__`))) {
1237
+ input.state.omissions.push({
1238
+ root_index: input.root.root_index,
1239
+ reason: "exclude_glob",
1240
+ message: "Directory omitted by exclude globs",
1241
+ paths: [input.relativeDir],
1242
+ });
1243
+ pushRequestedFocusOmission({
1244
+ root_index: input.root.root_index,
1245
+ relativePath: input.relativeDir,
1246
+ focusPaths: input.focusPaths,
1247
+ state: input.state,
1248
+ kind: "unavailable",
1249
+ message: "focused path was omitted by exclude globs",
1250
+ });
1251
+ return;
1252
+ }
1253
+ const initialVerification = await verifyDirectoryStillExpected(input);
1254
+ if (initialVerification.kind === "omitted") {
1255
+ pushDirectoryOmission(input, initialVerification.omission);
1256
+ pushRequestedFocusOmission({
1257
+ root_index: input.root.root_index,
1258
+ relativePath: input.relativeDir,
1259
+ focusPaths: input.focusPaths,
1260
+ state: input.state,
1261
+ kind: "unavailable",
1262
+ message: sanitizedFocusedOmissionMessage(initialVerification.omission.reason),
1263
+ });
1264
+ return;
1265
+ }
1266
+ if (isExcludedRealRoot(initialVerification.realPath, input.excludedRealRoots)) {
1267
+ return;
1268
+ }
1269
+ let entries;
1270
+ try {
1271
+ entries = await input.fs.readdir(input.absoluteDir, {
1272
+ withFileTypes: true,
1273
+ });
1274
+ }
1275
+ catch (error) {
1276
+ if (isExpectedDescendantFilesystemError(error)) {
1277
+ pushDirectoryOmission(input, filesystemOmission("Directory omitted after filesystem error", error));
1278
+ pushRequestedFocusOmission({
1279
+ root_index: input.root.root_index,
1280
+ relativePath: input.relativeDir,
1281
+ focusPaths: input.focusPaths,
1282
+ state: input.state,
1283
+ kind: "unavailable",
1284
+ message: sanitizedFocusedOmissionMessage("filesystem_error"),
1285
+ });
1286
+ return;
1287
+ }
1288
+ throw error;
1289
+ }
1290
+ assertNotAborted(input.signal);
1291
+ const finalVerification = await verifyDirectoryStillExpected(input);
1292
+ if (finalVerification.kind === "omitted") {
1293
+ pushDirectoryOmission(input, finalVerification.omission);
1294
+ pushRequestedFocusOmission({
1295
+ root_index: input.root.root_index,
1296
+ relativePath: input.relativeDir,
1297
+ focusPaths: input.focusPaths,
1298
+ state: input.state,
1299
+ kind: "unavailable",
1300
+ message: sanitizedFocusedOmissionMessage(finalVerification.omission.reason),
1301
+ });
1302
+ return;
1303
+ }
1304
+ if (isExcludedRealRoot(finalVerification.realPath, input.excludedRealRoots)) {
1305
+ return;
1306
+ }
1307
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1308
+ for (const entry of entries) {
1309
+ assertNotAborted(input.signal);
1310
+ if (input.state.stopped) {
1311
+ return;
1312
+ }
1313
+ const relativePath = normalizeContextBuilderRootRelativePath(input.relativeDir === "" ? entry.name : `${input.relativeDir}/${entry.name}`);
1314
+ const absolutePath = path.join(input.absoluteDir, entry.name);
1315
+ if (entry.isSymbolicLink()) {
1316
+ input.state.omissions.push({
1317
+ root_index: input.root.root_index,
1318
+ reason: "symlink_omitted",
1319
+ message: "Symlink omitted during context_builder discovery",
1320
+ paths: [relativePath],
1321
+ });
1322
+ pushRequestedFocusOmission({
1323
+ root_index: input.root.root_index,
1324
+ relativePath,
1325
+ focusPaths: input.focusPaths,
1326
+ state: input.state,
1327
+ kind: "unavailable",
1328
+ message: sanitizedFocusedOmissionMessage("symlink_omitted"),
1329
+ });
1330
+ continue;
1331
+ }
1332
+ if (entry.isDirectory()) {
1333
+ if (!input.matcher.canContainIncludedPath(relativePath)) {
1334
+ pushRequestedFocusOmission({
1335
+ root_index: input.root.root_index,
1336
+ relativePath,
1337
+ focusPaths: input.focusPaths,
1338
+ state: input.state,
1339
+ kind: "unavailable",
1340
+ message: sanitizedFocusedOmissionMessage("caller_include_glob"),
1341
+ });
1342
+ continue;
1343
+ }
1344
+ await traverseDirectory({
1345
+ ...input,
1346
+ absoluteDir: absolutePath,
1347
+ relativeDir: relativePath,
1348
+ });
1349
+ continue;
1350
+ }
1351
+ if (!entry.isFile()) {
1352
+ continue;
1353
+ }
1354
+ await processFile({
1355
+ ...input,
1356
+ absolutePath,
1357
+ relativePath,
1358
+ });
1359
+ }
1360
+ }
1361
+ function pushDirectoryOmission(input, omission) {
1362
+ input.state.omissions.push({
1363
+ root_index: input.root.root_index,
1364
+ reason: omission.reason,
1365
+ message: omission.message,
1366
+ ...(input.relativeDir === "" ? {} : { paths: [input.relativeDir] }),
1367
+ });
1368
+ }
1369
+ async function processFile(input) {
1370
+ const fileKey = candidateKey({
1371
+ root_index: input.root.root_index,
1372
+ path: input.relativePath,
1373
+ });
1374
+ const alreadyProcessed = input.state.processedFileKeys.has(fileKey);
1375
+ if (alreadyProcessed &&
1376
+ (input.ignoreIncludeGlob !== true ||
1377
+ input.state.contentCandidates.some((candidate) => candidateKey(candidate) === fileKey))) {
1378
+ return;
1379
+ }
1380
+ if (!alreadyProcessed) {
1381
+ input.state.processedFileKeys.add(fileKey);
1382
+ }
1383
+ const omitted = (reason, message, secretFindings) => {
1384
+ input.state.omissions.push({
1385
+ root_index: input.root.root_index,
1386
+ reason,
1387
+ message,
1388
+ paths: [input.relativePath],
1389
+ });
1390
+ pushRequestedFocusOmission({
1391
+ root_index: input.root.root_index,
1392
+ relativePath: input.relativePath,
1393
+ focusPaths: input.focusPaths,
1394
+ state: input.state,
1395
+ kind: requestedOmissionKindForDiscoveryReason(reason),
1396
+ message: sanitizedFocusedOmissionMessage(reason, secretFindings),
1397
+ });
1398
+ };
1399
+ const requestedOmitted = (kind, message) => {
1400
+ pushRequestedFocusOmission({
1401
+ root_index: input.root.root_index,
1402
+ relativePath: input.relativePath,
1403
+ focusPaths: input.focusPaths,
1404
+ state: input.state,
1405
+ kind,
1406
+ message,
1407
+ });
1408
+ };
1409
+ const pushTree = (file) => {
1410
+ input.state.tree.push({
1411
+ root_index: input.root.root_index,
1412
+ path: input.relativePath,
1413
+ size_bytes: file.size_bytes ?? 0,
1414
+ eligible: file.eligible,
1415
+ content_eligible: file.content_eligible,
1416
+ ...(file.omission_reason == null
1417
+ ? {}
1418
+ : { omission_reason: file.omission_reason }),
1419
+ });
1420
+ };
1421
+ if (!isSamePathOrDescendant(input.absolutePath, input.root.path)) {
1422
+ omitted("path_escape", "File path escaped root during discovery");
1423
+ return;
1424
+ }
1425
+ const normalizedPath = rootRelativePathForAbsolutePath(input.root, input.absolutePath);
1426
+ if (normalizedPath !== input.relativePath) {
1427
+ omitted("path_normalized_mismatch", "File path changed after normalization");
1428
+ return;
1429
+ }
1430
+ if (input.ignoreIncludeGlob !== true && !input.matcher.includes(input.relativePath)) {
1431
+ requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("caller_include_glob"));
1432
+ pushTree({
1433
+ eligible: false,
1434
+ content_eligible: false,
1435
+ omission_reason: "caller_include_glob",
1436
+ });
1437
+ return;
1438
+ }
1439
+ if (input.matcher.excludes(input.relativePath)) {
1440
+ requestedOmitted("unavailable", sanitizedFocusedOmissionMessage("exclude_glob"));
1441
+ pushTree({
1442
+ eligible: false,
1443
+ content_eligible: false,
1444
+ omission_reason: "exclude_glob",
1445
+ });
1446
+ return;
1447
+ }
1448
+ // files_seen stays accurate for observability, but focus-processed files are
1449
+ // subtracted from the cap check so explicit focus never consumes the
1450
+ // max_scan_files headroom reserved for global traversal.
1451
+ if (input.ignoreScanCap !== true &&
1452
+ input.focusScanExempt !== true &&
1453
+ input.state.metrics.files_seen - input.state.focusScanExemptFiles >=
1454
+ input.budget.max_scan_files) {
1455
+ input.state.metrics.scan_cap_hit = true;
1456
+ pushRequestedFocusOmission({
1457
+ root_index: input.root.root_index,
1458
+ relativePath: input.relativePath,
1459
+ focusPaths: input.focusPaths,
1460
+ state: input.state,
1461
+ kind: "unavailable",
1462
+ message: "focused path was not reached before max_scan_files stopped discovery",
1463
+ });
1464
+ input.state.stopped = true;
1465
+ return;
1466
+ }
1467
+ input.state.metrics.files_seen++;
1468
+ if (input.focusScanExempt === true) {
1469
+ input.state.focusScanExemptFiles++;
1470
+ }
1471
+ const pathSecrets = input.allowSecrets ? [] : scanSecretPath(input.relativePath);
1472
+ if (pathSecrets.length > 0) {
1473
+ omitted("secret_path", `Secret-looking path omitted (${pathSecrets[0]?.provenance})`, pathSecrets);
1474
+ pushTree({
1475
+ eligible: false,
1476
+ content_eligible: false,
1477
+ omission_reason: "secret_path",
1478
+ });
1479
+ return;
1480
+ }
1481
+ let stats;
1482
+ try {
1483
+ stats = await input.fs.lstat(input.absolutePath);
1484
+ }
1485
+ catch (error) {
1486
+ if (isExpectedDescendantFilesystemError(error)) {
1487
+ omittedFromError("File omitted after lstat error", error);
1488
+ return;
1489
+ }
1490
+ throw error;
1491
+ }
1492
+ assertNotAborted(input.signal);
1493
+ if (stats.isSymbolicLink()) {
1494
+ omitted("symlink_omitted", "Symlink omitted during context_builder discovery");
1495
+ return;
1496
+ }
1497
+ if (!stats.isFile()) {
1498
+ return;
1499
+ }
1500
+ const fileVerification = await verifyFileStillExpected({
1501
+ root: input.root,
1502
+ absolutePath: input.absolutePath,
1503
+ relativePath: input.relativePath,
1504
+ expectedStats: stats,
1505
+ fs: input.fs,
1506
+ });
1507
+ if (fileVerification.kind === "omitted") {
1508
+ omitted(fileVerification.omission.reason, fileVerification.omission.message);
1509
+ return;
1510
+ }
1511
+ const sizeBytes = saneFileSize(stats.size);
1512
+ if (sizeBytes == null) {
1513
+ omitted("file_changed_during_read", "File size could not be verified safely");
1514
+ return;
1515
+ }
1516
+ input.state.metrics.files_considered++;
1517
+ if (isNoisyFile(input.relativePath, sizeBytes)) {
1518
+ omitted("noisy_file", "Noisy generated file omitted");
1519
+ pushTree({
1520
+ size_bytes: sizeBytes,
1521
+ eligible: false,
1522
+ content_eligible: false,
1523
+ omission_reason: "noisy_file",
1524
+ });
1525
+ return;
1526
+ }
1527
+ if (input.state.metrics.bytes_read + sizeBytes > input.internalCaps.max_discovery_bytes) {
1528
+ input.state.metrics.discovery_byte_cap_hit = true;
1529
+ requestedOmitted("unavailable", "focused file could not be read before the aggregate discovery byte cap");
1530
+ pushTree({
1531
+ size_bytes: sizeBytes,
1532
+ eligible: true,
1533
+ content_eligible: false,
1534
+ omission_reason: "discovery_byte_cap",
1535
+ });
1536
+ return;
1537
+ }
1538
+ let read;
1539
+ try {
1540
+ read = await readBoundedRegularFile({
1541
+ absolutePath: input.absolutePath,
1542
+ relativePath: input.relativePath,
1543
+ root: input.root,
1544
+ maxBytes: Math.max(input.budget.max_bytes_per_file, CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS.max_bytes_per_file),
1545
+ expectedStats: stats,
1546
+ fs: input.fs,
1547
+ });
1548
+ }
1549
+ catch (error) {
1550
+ if (isExpectedDescendantFilesystemError(error)) {
1551
+ const omission = filesystemOmission("File omitted after open/read error", error);
1552
+ omitted(omission.reason, omission.message);
1553
+ pushTree({
1554
+ size_bytes: sizeBytes,
1555
+ eligible: true,
1556
+ content_eligible: false,
1557
+ omission_reason: omission.reason,
1558
+ });
1559
+ return;
1560
+ }
1561
+ throw error;
1562
+ }
1563
+ assertNotAborted(input.signal);
1564
+ if (read.kind === "omitted") {
1565
+ omitted(read.omission.reason, read.omission.message);
1566
+ requestedOmitted(read.omission.reason === "file_too_large" ? "too_large" : "unavailable", read.omission.message);
1567
+ pushTree({
1568
+ size_bytes: read.sizeBytes ?? sizeBytes,
1569
+ eligible: true,
1570
+ content_eligible: false,
1571
+ omission_reason: read.omission.reason,
1572
+ });
1573
+ return;
1574
+ }
1575
+ const contentBuffer = read.buffer;
1576
+ if (isBinaryBuffer(contentBuffer)) {
1577
+ omitted("binary_file", "Binary file omitted from content-bearing artifacts");
1578
+ pushTree({
1579
+ size_bytes: sizeBytes,
1580
+ eligible: true,
1581
+ content_eligible: false,
1582
+ omission_reason: "binary_file",
1583
+ });
1584
+ return;
1585
+ }
1586
+ const rawContent = contentBuffer.toString("utf8");
1587
+ input.state.metrics.bytes_read += contentBuffer.byteLength;
1588
+ const sanitized = input.allowSecrets
1589
+ ? { status: "unchanged", content: rawContent, findings: [] }
1590
+ : sanitizeHighConfidenceSecretContent(rawContent);
1591
+ if (sanitized.status === "blocked") {
1592
+ omitted("content_secret", `Content omitted due to high-confidence secret (${summarizeSecretFindings(sanitized.findings)})`, sanitized.findings);
1593
+ pushTree({
1594
+ size_bytes: read.sizeBytes,
1595
+ eligible: true,
1596
+ content_eligible: false,
1597
+ omission_reason: "content_secret",
1598
+ });
1599
+ return;
1600
+ }
1601
+ const content = sanitized.content;
1602
+ const contentBytes = Buffer.byteLength(content, "utf8");
1603
+ if (input.state.contentCandidates.length >= input.internalCaps.max_discovery_candidates) {
1604
+ input.state.metrics.discovery_candidate_cap_hit = true;
1605
+ requestedOmitted("discovery_candidate_cap", "focused file could not be represented because max_discovery_candidates was reached");
1606
+ pushTree({
1607
+ size_bytes: read.sizeBytes,
1608
+ eligible: true,
1609
+ content_eligible: false,
1610
+ omission_reason: "discovery_candidate_cap",
1611
+ });
1612
+ return;
1613
+ }
1614
+ pushTree({
1615
+ size_bytes: read.sizeBytes,
1616
+ eligible: true,
1617
+ content_eligible: true,
1618
+ });
1619
+ input.state.contentCandidates.push({
1620
+ root_index: input.root.root_index,
1621
+ path: input.relativePath,
1622
+ absolute_path: input.absolutePath,
1623
+ content,
1624
+ byte_length: contentBytes,
1625
+ line_count: splitTextLines(content).length,
1626
+ has_final_newline: content.endsWith("\n"),
1627
+ ...(input.selectionHint == null ? {} : { selection_hint: input.selectionHint }),
1628
+ });
1629
+ function omittedFromError(message, error) {
1630
+ const omission = filesystemOmission(message, error);
1631
+ omitted(omission.reason, omission.message);
1632
+ }
1633
+ }
1634
+ async function verifyDirectoryStillExpected(input) {
1635
+ let lstatStats;
1636
+ try {
1637
+ lstatStats = await input.fs.lstat(input.absoluteDir);
1638
+ }
1639
+ catch (error) {
1640
+ if (isExpectedDescendantFilesystemError(error)) {
1641
+ return {
1642
+ kind: "omitted",
1643
+ omission: filesystemOmission("Directory omitted after filesystem error", error),
1644
+ };
1645
+ }
1646
+ throw error;
1647
+ }
1648
+ if (lstatStats.isSymbolicLink()) {
1649
+ return {
1650
+ kind: "omitted",
1651
+ omission: {
1652
+ reason: "symlink_omitted",
1653
+ message: "Symlink omitted during context_builder discovery",
1654
+ },
1655
+ };
1656
+ }
1657
+ if (!lstatStats.isDirectory()) {
1658
+ return {
1659
+ kind: "omitted",
1660
+ omission: {
1661
+ reason: "file_changed_during_read",
1662
+ message: "Directory changed before it could be read safely",
1663
+ },
1664
+ };
1665
+ }
1666
+ const target = await verifyPathTarget({
1667
+ root: input.root,
1668
+ absolutePath: input.absoluteDir,
1669
+ relativePath: input.relativeDir,
1670
+ expectedStats: lstatStats,
1671
+ expectedKind: "directory",
1672
+ fs: input.fs,
1673
+ });
1674
+ if (target.kind === "omitted") {
1675
+ return target;
1676
+ }
1677
+ return { kind: "verified", realPath: target.realPath };
1678
+ }
1679
+ async function verifyFileStillExpected(input) {
1680
+ return verifyPathTarget({
1681
+ root: input.root,
1682
+ absolutePath: input.absolutePath,
1683
+ relativePath: input.relativePath,
1684
+ expectedStats: input.expectedStats,
1685
+ expectedKind: "file",
1686
+ fs: input.fs,
1687
+ });
1688
+ }
1689
+ async function verifyPathTarget(input) {
1690
+ let realPath;
1691
+ try {
1692
+ realPath = await input.fs.realpath(input.absolutePath);
1693
+ }
1694
+ catch (error) {
1695
+ if (isExpectedDescendantFilesystemError(error)) {
1696
+ return {
1697
+ kind: "omitted",
1698
+ omission: filesystemOmission("Path omitted after filesystem error", error),
1699
+ };
1700
+ }
1701
+ throw error;
1702
+ }
1703
+ if (!isSamePathOrDescendant(realPath, input.root.path)) {
1704
+ return {
1705
+ kind: "omitted",
1706
+ omission: {
1707
+ reason: "path_escape",
1708
+ message: "Path resolved outside root during discovery",
1709
+ },
1710
+ };
1711
+ }
1712
+ const normalizedPath = rootRelativePathForAbsolutePath(input.root, realPath);
1713
+ if (normalizedPath !== input.relativePath) {
1714
+ return {
1715
+ kind: "omitted",
1716
+ omission: {
1717
+ reason: "path_escape",
1718
+ message: "Path resolved to an unexpected root-relative target",
1719
+ },
1720
+ };
1721
+ }
1722
+ let targetStats;
1723
+ try {
1724
+ targetStats = await input.fs.stat(realPath);
1725
+ }
1726
+ catch (error) {
1727
+ if (isExpectedDescendantFilesystemError(error)) {
1728
+ return {
1729
+ kind: "omitted",
1730
+ omission: filesystemOmission("Path omitted after filesystem error", error),
1731
+ };
1732
+ }
1733
+ throw error;
1734
+ }
1735
+ const expectedKindMatches = input.expectedKind === "file"
1736
+ ? targetStats.isFile()
1737
+ : targetStats.isDirectory();
1738
+ if (!expectedKindMatches || !sameFileIdentity(input.expectedStats, targetStats)) {
1739
+ return {
1740
+ kind: "omitted",
1741
+ omission: {
1742
+ reason: "file_changed_during_read",
1743
+ message: "Path changed before it could be read safely",
1744
+ },
1745
+ };
1746
+ }
1747
+ return { kind: "verified", realPath };
1748
+ }
1749
+ function compareTreeFiles(left, right) {
1750
+ return left.root_index - right.root_index || left.path.localeCompare(right.path);
1751
+ }
1752
+ function compareContentCandidates(left, right) {
1753
+ return left.root_index - right.root_index || left.path.localeCompare(right.path);
1754
+ }
1755
+ function compareSearchRecords(left, right) {
1756
+ return (left.root_index - right.root_index ||
1757
+ left.path.localeCompare(right.path) ||
1758
+ left.line - right.line ||
1759
+ left.search_term_index - right.search_term_index ||
1760
+ left.column - right.column);
1761
+ }
1762
+ function compareFocusPathsForDiscovery(left, right) {
1763
+ return (left.root_index - right.root_index ||
1764
+ left.root_relative_path.localeCompare(right.root_relative_path) ||
1765
+ left.input_index - right.input_index);
1766
+ }
1767
+ function auditScanCapFocusedPaths(input) {
1768
+ if (!input.state.metrics.scan_cap_hit) {
1769
+ return;
1770
+ }
1771
+ for (const focus of input.focusPaths) {
1772
+ if (hasSurvivingFocusEvidence(input.state, focus) ||
1773
+ hasRequestedEvidenceOmissionForPath(input.state.omissions, focus.root_index, focus.root_relative_path)) {
1774
+ continue;
1775
+ }
1776
+ pushRequestedFocusOmission({
1777
+ root_index: focus.root_index,
1778
+ relativePath: focus.root_relative_path,
1779
+ focusPaths: input.focusPaths,
1780
+ state: input.state,
1781
+ kind: "unavailable",
1782
+ message: "focused path was not reached before max_scan_files stopped discovery",
1783
+ });
1784
+ }
1785
+ }
1786
+ function hasSurvivingFocusEvidence(state, focus) {
1787
+ return (state.contentCandidates.some((candidate) => focusPathCoversEvidencePath(focus, candidate.root_index, candidate.path)) ||
1788
+ state.tree.some((file) => focusPathCoversEvidencePath(focus, file.root_index, file.path)));
1789
+ }
1790
+ function focusPathCoversEvidencePath(focus, rootIndex, relativePath) {
1791
+ return (focus.root_index === rootIndex &&
1792
+ (relativePath === focus.root_relative_path ||
1793
+ (focus.kind === "directory" &&
1794
+ isRootRelativeDescendant(relativePath, focus.root_relative_path))));
1795
+ }
1796
+ function pushRequestedFocusOmission(input) {
1797
+ const paths = requestedEvidencePathsForFocusedOmission(input.root_index, input.relativePath, input.focusPaths).filter((requestedPath) => {
1798
+ const key = requestedEvidenceOmissionKey(input.root_index, requestedPath);
1799
+ if (input.state.requestedEvidenceOmissionKeys.has(key)) {
1800
+ return false;
1801
+ }
1802
+ if (hasRequestedEvidenceOmissionForPath(input.state.omissions, input.root_index, requestedPath)) {
1803
+ input.state.requestedEvidenceOmissionKeys.add(key);
1804
+ return false;
1805
+ }
1806
+ return true;
1807
+ });
1808
+ if (paths.length === 0) {
1809
+ return;
1810
+ }
1811
+ input.state.omissions.push(requestedEvidenceOmission({
1812
+ root_index: input.root_index,
1813
+ kind: input.kind,
1814
+ message: input.message,
1815
+ paths,
1816
+ }));
1817
+ for (const requestedPath of paths) {
1818
+ input.state.requestedEvidenceOmissionKeys.add(requestedEvidenceOmissionKey(input.root_index, requestedPath));
1819
+ }
1820
+ }
1821
+ function requestedEvidencePathsForFocusedOmission(rootIndex, relativePath, focusPaths) {
1822
+ const paths = new Set();
1823
+ for (const focus of focusPaths) {
1824
+ if (focus.root_index !== rootIndex) {
1825
+ continue;
1826
+ }
1827
+ if (relativePath === focus.root_relative_path) {
1828
+ paths.add(relativePath);
1829
+ continue;
1830
+ }
1831
+ if (focus.kind === "directory" &&
1832
+ isRootRelativeDescendant(relativePath, focus.root_relative_path)) {
1833
+ paths.add(relativePath);
1834
+ continue;
1835
+ }
1836
+ if (isRootRelativeDescendant(focus.root_relative_path, relativePath)) {
1837
+ paths.add(focus.root_relative_path);
1838
+ }
1839
+ }
1840
+ return [...paths].sort();
1841
+ }
1842
+ function hasRequestedEvidenceOmissionForPath(omissions, rootIndex, relativePath) {
1843
+ return omissions.some((omission) => omission.reason === CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON &&
1844
+ omission.root_index === rootIndex &&
1845
+ (omission.paths ?? []).includes(relativePath));
1846
+ }
1847
+ function requestedEvidenceOmissionKey(rootIndex, relativePath) {
1848
+ return `${rootIndex}\u0000${relativePath}`;
1849
+ }
1850
+ function requestedOmissionKindForDiscoveryReason(reason) {
1851
+ return reason === "file_too_large" ? "too_large" : "unavailable";
1852
+ }
1853
+ function sanitizedFocusedOmissionMessage(reason, secretFindings) {
1854
+ switch (reason) {
1855
+ case "caller_include_glob":
1856
+ return "focused path was outside include globs";
1857
+ case "exclude_glob":
1858
+ return "focused path was omitted by exclude globs";
1859
+ case "secret_path":
1860
+ return withSecretFindingProvenance("focused path was omitted because it matched a secret-path rule", secretFindings);
1861
+ case "symlink_omitted":
1862
+ return "focused path was omitted because symlinks are not followed";
1863
+ case "path_escape":
1864
+ case "path_normalized_mismatch":
1865
+ return "focused path could not be safely resolved within the root";
1866
+ case "filesystem_error":
1867
+ return "focused path could not be read due to an expected filesystem error";
1868
+ case "file_changed_during_read":
1869
+ return "focused path changed before it could be read safely";
1870
+ case "noisy_file":
1871
+ return "focused generated file was omitted as noisy";
1872
+ case "binary_file":
1873
+ return "focused binary file cannot be represented as text";
1874
+ case "content_secret":
1875
+ return withSecretFindingProvenance("focused file content was omitted because secret-like content was detected", secretFindings);
1876
+ case "file_too_large":
1877
+ return "focused file exceeded max_bytes_per_file";
1878
+ default:
1879
+ return "focused path could not be represented as local evidence";
1880
+ }
1881
+ }
1882
+ const MAX_SECRET_FINDING_PROVENANCE_DETAILS = 3;
1883
+ // Appends detector provenance labels and line numbers (never matched secret
1884
+ // text or values) to a sanitized focused-omission message, e.g.
1885
+ // "... (aws_access_key_id at line 12)".
1886
+ function withSecretFindingProvenance(base, secretFindings) {
1887
+ const details = [];
1888
+ for (const finding of secretFindings ?? []) {
1889
+ const detail = finding.line == null
1890
+ ? finding.provenance
1891
+ : `${finding.provenance} at line ${finding.line}`;
1892
+ if (!details.includes(detail)) {
1893
+ details.push(detail);
1894
+ }
1895
+ if (details.length >= MAX_SECRET_FINDING_PROVENANCE_DETAILS) {
1896
+ break;
1897
+ }
1898
+ }
1899
+ return details.length === 0 ? base : `${base} (${details.join(", ")})`;
1900
+ }
1901
+ function candidateKey(candidate) {
1902
+ return `${candidate.root_index}\u0000${candidate.path}`;
1903
+ }
1904
+ function searchKey(search) {
1905
+ return `${search.root_index}\u0000${search.path}`;
1906
+ }
1907
+ function isRootRelativeDescendant(child, parent) {
1908
+ if (parent === "") {
1909
+ return true;
1910
+ }
1911
+ return child === parent || child.startsWith(`${parent}/`);
1912
+ }
1913
+ function isExcludedRealRoot(realPath, excludedRealRoots) {
1914
+ return excludedRealRoots.some((root) => isSamePathOrDescendant(realPath, root));
1915
+ }
1916
+ function sliceContent(text, startLine, endLine) {
1917
+ return splitTextLines(text)
1918
+ .slice(startLine - 1, endLine)
1919
+ .join("\n");
1920
+ }
1921
+ function boundedPreview(line, columnIndex, termLength) {
1922
+ const radius = 80;
1923
+ const start = Math.max(0, columnIndex - radius);
1924
+ const end = Math.min(line.length, columnIndex + termLength + radius);
1925
+ const prefix = start > 0 ? "..." : "";
1926
+ const suffix = end < line.length ? "..." : "";
1927
+ return `${prefix}${line.slice(start, end)}${suffix}`;
1928
+ }
1929
+ function isBinaryBuffer(buffer) {
1930
+ if (buffer.length === 0) {
1931
+ return false;
1932
+ }
1933
+ if (buffer.includes(0)) {
1934
+ return true;
1935
+ }
1936
+ let suspicious = 0;
1937
+ const sampleLength = Math.min(buffer.length, 8_192);
1938
+ for (let index = 0; index < sampleLength; index++) {
1939
+ const byte = buffer[index] ?? 0;
1940
+ const allowedControl = byte === 0x09 || byte === 0x0a || byte === 0x0d || byte === 0x1b;
1941
+ if (byte < 0x20 && !allowedControl) {
1942
+ suspicious++;
1943
+ }
1944
+ }
1945
+ return suspicious / sampleLength > 0.05;
1946
+ }
1947
+ function isNoisyFile(rootRelativePath, sizeBytes) {
1948
+ const basename = path.posix.basename(rootRelativePath);
1949
+ return sizeBytes > 20_000 && (basename.endsWith(".min.js") || basename.endsWith(".map"));
1950
+ }
1951
+ function summarizeSecretFindings(findings) {
1952
+ return findings.map((finding) => finding.provenance).join(", ");
1953
+ }
1954
+ async function readBoundedRegularFile(input) {
1955
+ const initialVerification = await verifyFileStillExpected({
1956
+ root: input.root,
1957
+ absolutePath: input.absolutePath,
1958
+ relativePath: input.relativePath,
1959
+ expectedStats: input.expectedStats,
1960
+ fs: input.fs,
1961
+ });
1962
+ if (initialVerification.kind === "omitted") {
1963
+ return {
1964
+ kind: "omitted",
1965
+ omission: initialVerification.omission,
1966
+ };
1967
+ }
1968
+ let handle;
1969
+ try {
1970
+ handle = await input.fs.open(input.absolutePath, noFollowReadFlags());
1971
+ }
1972
+ catch (error) {
1973
+ if (filesystemErrorCode(error) === "ELOOP") {
1974
+ return {
1975
+ kind: "omitted",
1976
+ omission: {
1977
+ reason: "symlink_omitted",
1978
+ message: "Symlink omitted during context_builder discovery",
1979
+ },
1980
+ };
1981
+ }
1982
+ throw error;
1983
+ }
1984
+ try {
1985
+ const openedStats = await handle.stat();
1986
+ const openedVerification = await verifyFileStillExpected({
1987
+ root: input.root,
1988
+ absolutePath: input.absolutePath,
1989
+ relativePath: input.relativePath,
1990
+ expectedStats: openedStats,
1991
+ fs: input.fs,
1992
+ });
1993
+ if (openedVerification.kind === "omitted") {
1994
+ return {
1995
+ kind: "omitted",
1996
+ omission: openedVerification.omission,
1997
+ };
1998
+ }
1999
+ const openedSize = saneFileSize(openedStats.size);
2000
+ if (!openedStats.isFile() || openedSize == null) {
2001
+ return {
2002
+ kind: "omitted",
2003
+ omission: {
2004
+ reason: "file_changed_during_read",
2005
+ message: "File changed before it could be read safely",
2006
+ },
2007
+ };
2008
+ }
2009
+ if (!sameFileIdentity(input.expectedStats, openedStats)) {
2010
+ return {
2011
+ kind: "omitted",
2012
+ omission: {
2013
+ reason: "file_changed_during_read",
2014
+ message: "File changed before it could be read safely",
2015
+ },
2016
+ sizeBytes: openedSize,
2017
+ };
2018
+ }
2019
+ if (openedSize > input.maxBytes) {
2020
+ return {
2021
+ kind: "omitted",
2022
+ omission: {
2023
+ reason: "file_too_large",
2024
+ message: "File exceeded max_bytes_per_file while opening",
2025
+ },
2026
+ sizeBytes: openedSize,
2027
+ };
2028
+ }
2029
+ const readLength = Math.min(input.maxBytes + 1, openedSize + 1);
2030
+ const buffer = Buffer.alloc(readLength);
2031
+ const { bytesRead } = await handle.read(buffer, 0, readLength, 0);
2032
+ if (bytesRead !== openedSize) {
2033
+ return {
2034
+ kind: "omitted",
2035
+ omission: {
2036
+ reason: "file_changed_during_read",
2037
+ message: "File changed before it could be read safely",
2038
+ },
2039
+ sizeBytes: openedSize,
2040
+ };
2041
+ }
2042
+ const finalVerification = await verifyFileStillExpected({
2043
+ root: input.root,
2044
+ absolutePath: input.absolutePath,
2045
+ relativePath: input.relativePath,
2046
+ expectedStats: openedStats,
2047
+ fs: input.fs,
2048
+ });
2049
+ if (finalVerification.kind === "omitted") {
2050
+ return {
2051
+ kind: "omitted",
2052
+ omission: finalVerification.omission,
2053
+ sizeBytes: openedSize,
2054
+ };
2055
+ }
2056
+ return {
2057
+ kind: "read",
2058
+ buffer: buffer.subarray(0, bytesRead),
2059
+ sizeBytes: openedSize,
2060
+ };
2061
+ }
2062
+ finally {
2063
+ await handle.close();
2064
+ }
2065
+ }
2066
+ let _noFollowAvailable;
2067
+ function noFollowReadFlags() {
2068
+ if (_noFollowAvailable === undefined) {
2069
+ _noFollowAvailable = typeof fsConstants.O_NOFOLLOW === "number";
2070
+ }
2071
+ return _noFollowAvailable
2072
+ ? fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW
2073
+ : fsConstants.O_RDONLY;
2074
+ }
2075
+ function sameFileIdentity(left, right) {
2076
+ if (left.dev !== right.dev || left.ino !== right.ino) {
2077
+ return false;
2078
+ }
2079
+ if (left.isFile() || right.isFile()) {
2080
+ return left.isFile() && right.isFile()
2081
+ && left.size === right.size
2082
+ && left.mtimeMs === right.mtimeMs;
2083
+ }
2084
+ if (left.isDirectory() || right.isDirectory()) {
2085
+ return left.isDirectory() && right.isDirectory();
2086
+ }
2087
+ return false;
2088
+ }
2089
+ function saneFileSize(size) {
2090
+ return Number.isSafeInteger(size) && size >= 0 ? size : undefined;
2091
+ }
2092
+ const EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES = new Set([
2093
+ "EACCES",
2094
+ "EPERM",
2095
+ "ENOENT",
2096
+ "ENOTDIR",
2097
+ "ELOOP",
2098
+ ]);
2099
+ function isExpectedDescendantFilesystemError(error) {
2100
+ const code = filesystemErrorCode(error);
2101
+ return (code != null &&
2102
+ EXPECTED_DESCENDANT_FILESYSTEM_ERROR_CODES.has(code));
2103
+ }
2104
+ function filesystemOmission(message, error) {
2105
+ const code = filesystemErrorCode(error);
2106
+ if (code === "ELOOP") {
2107
+ return {
2108
+ reason: "symlink_omitted",
2109
+ message: "Symlink omitted during context_builder discovery",
2110
+ };
2111
+ }
2112
+ return {
2113
+ reason: "filesystem_error",
2114
+ message: code == null ? message : `${message} (${code})`,
2115
+ };
2116
+ }
2117
+ function filesystemErrorCode(error) {
2118
+ if (typeof error !== "object" || error == null || !("code" in error)) {
2119
+ return undefined;
2120
+ }
2121
+ const code = error.code;
2122
+ return typeof code === "string" ? code : undefined;
2123
+ }
2124
+ function assertNotAborted(signal) {
2125
+ if (signal?.aborted === true) {
2126
+ throw new CancellationError();
2127
+ }
2128
+ }
2129
+ //# sourceMappingURL=discovery.js.map