clinkx 0.3.0 → 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 (555) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +10 -0
  3. package/README.md +82 -26
  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/dist/activity-journal.d.ts +42 -0
  317. package/dist/activity-journal.js +381 -0
  318. package/dist/activity-journal.js.map +1 -0
  319. package/dist/allowed-roots.d.ts +41 -0
  320. package/dist/allowed-roots.js +125 -3
  321. package/dist/allowed-roots.js.map +1 -1
  322. package/dist/codex/activity-session-log.d.ts +14 -0
  323. package/dist/codex/activity-session-log.js +206 -0
  324. package/dist/codex/activity-session-log.js.map +1 -0
  325. package/dist/codex/agent-tools.d.ts +23 -0
  326. package/dist/codex/agent-tools.js +1270 -0
  327. package/dist/codex/agent-tools.js.map +1 -0
  328. package/dist/codex/app-server-client.d.ts +76 -0
  329. package/dist/codex/app-server-client.js +582 -0
  330. package/dist/codex/app-server-client.js.map +1 -0
  331. package/dist/codex/app-server-registry.d.ts +389 -0
  332. package/dist/codex/app-server-registry.js +1407 -0
  333. package/dist/codex/app-server-registry.js.map +1 -0
  334. package/dist/codex/client-types.d.ts +109 -0
  335. package/dist/codex/client-types.js +2 -0
  336. package/dist/codex/client-types.js.map +1 -0
  337. package/dist/codex/protocol.d.ts +5375 -0
  338. package/dist/codex/protocol.js +614 -0
  339. package/dist/codex/protocol.js.map +1 -0
  340. package/dist/codex/session.d.ts +263 -0
  341. package/dist/codex/session.js +466 -0
  342. package/dist/codex/session.js.map +1 -0
  343. package/dist/config.d.ts +185 -19
  344. package/dist/config.js +82 -16
  345. package/dist/config.js.map +1 -1
  346. package/dist/context-builder/anchors.d.ts +1 -0
  347. package/dist/context-builder/anchors.js +3 -0
  348. package/dist/context-builder/anchors.js.map +1 -1
  349. package/dist/context-builder/bundle.d.ts +35 -2
  350. package/dist/context-builder/bundle.js +342 -2417
  351. package/dist/context-builder/bundle.js.map +1 -1
  352. package/dist/context-builder/codemap.d.ts +5 -0
  353. package/dist/context-builder/codemap.js +387 -13
  354. package/dist/context-builder/codemap.js.map +1 -1
  355. package/dist/context-builder/config.d.ts +1 -0
  356. package/dist/context-builder/config.js +12 -0
  357. package/dist/context-builder/config.js.map +1 -1
  358. package/dist/context-builder/context-run-catalog.d.ts +17 -0
  359. package/dist/context-builder/context-run-catalog.js +99 -0
  360. package/dist/context-builder/context-run-catalog.js.map +1 -0
  361. package/dist/context-builder/delegate-packet.d.ts +37 -0
  362. package/dist/context-builder/delegate-packet.js +359 -67
  363. package/dist/context-builder/delegate-packet.js.map +1 -1
  364. package/dist/context-builder/delegate-validation.d.ts +10 -1
  365. package/dist/context-builder/delegate-validation.js +153 -41
  366. package/dist/context-builder/delegate-validation.js.map +1 -1
  367. package/dist/context-builder/delegate.d.ts +2 -0
  368. package/dist/context-builder/delegate.js +1 -0
  369. package/dist/context-builder/delegate.js.map +1 -1
  370. package/dist/context-builder/diagnostics.d.ts +11 -2
  371. package/dist/context-builder/diagnostics.js +39 -6
  372. package/dist/context-builder/diagnostics.js.map +1 -1
  373. package/dist/context-builder/discovery.d.ts +29 -1
  374. package/dist/context-builder/discovery.js +475 -175
  375. package/dist/context-builder/discovery.js.map +1 -1
  376. package/dist/context-builder/globs.js +10 -10
  377. package/dist/context-builder/globs.js.map +1 -1
  378. package/dist/context-builder/handler.d.ts +26 -2
  379. package/dist/context-builder/handler.js +1158 -2416
  380. package/dist/context-builder/handler.js.map +1 -1
  381. package/dist/context-builder/lifecycle-contract.d.ts +424 -0
  382. package/dist/context-builder/lifecycle-contract.js +523 -0
  383. package/dist/context-builder/lifecycle-contract.js.map +1 -0
  384. package/dist/context-builder/lifecycle-journal.d.ts +62 -0
  385. package/dist/context-builder/lifecycle-journal.js +88 -0
  386. package/dist/context-builder/lifecycle-journal.js.map +1 -0
  387. package/dist/context-builder/lifecycle-observer.d.ts +102 -0
  388. package/dist/context-builder/lifecycle-observer.js +258 -0
  389. package/dist/context-builder/lifecycle-observer.js.map +1 -0
  390. package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
  391. package/dist/context-builder/lifecycle-reconcile.js +191 -0
  392. package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
  393. package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
  394. package/dist/context-builder/lifecycle-recovery.js +14 -0
  395. package/dist/context-builder/lifecycle-recovery.js.map +1 -0
  396. package/dist/context-builder/path-signals.js +9 -5
  397. package/dist/context-builder/path-signals.js.map +1 -1
  398. package/dist/context-builder/progress.d.ts +3 -1
  399. package/dist/context-builder/progress.js +23 -0
  400. package/dist/context-builder/progress.js.map +1 -1
  401. package/dist/context-builder/receipt.d.ts +6 -4
  402. package/dist/context-builder/receipt.js +12 -1
  403. package/dist/context-builder/receipt.js.map +1 -1
  404. package/dist/context-builder/refine-handler.d.ts +14 -0
  405. package/dist/context-builder/refine-handler.js +790 -0
  406. package/dist/context-builder/refine-handler.js.map +1 -0
  407. package/dist/context-builder/review-coverage.d.ts +84 -0
  408. package/dist/context-builder/review-coverage.js +437 -0
  409. package/dist/context-builder/review-coverage.js.map +1 -0
  410. package/dist/context-builder/schema.d.ts +239 -1
  411. package/dist/context-builder/schema.js +112 -6
  412. package/dist/context-builder/schema.js.map +1 -1
  413. package/dist/context-builder/secrets.d.ts +28 -0
  414. package/dist/context-builder/secrets.js +1473 -44
  415. package/dist/context-builder/secrets.js.map +1 -1
  416. package/dist/context-builder/selection.d.ts +7 -3
  417. package/dist/context-builder/selection.js +200 -738
  418. package/dist/context-builder/selection.js.map +1 -1
  419. package/dist/context-builder/session-log-identity.d.ts +1 -0
  420. package/dist/context-builder/session-log-identity.js +66 -0
  421. package/dist/context-builder/session-log-identity.js.map +1 -0
  422. package/dist/context-builder/slice-range-math.d.ts +16 -0
  423. package/dist/context-builder/slice-range-math.js +137 -0
  424. package/dist/context-builder/slice-range-math.js.map +1 -0
  425. package/dist/context-builder/status.d.ts +22 -1
  426. package/dist/context-builder/status.js +59 -4
  427. package/dist/context-builder/status.js.map +1 -1
  428. package/dist/context-builder/storage.d.ts +35 -2
  429. package/dist/context-builder/storage.js +271 -14
  430. package/dist/context-builder/storage.js.map +1 -1
  431. package/dist/context-builder/token-estimate.d.ts +13 -2
  432. package/dist/context-builder/token-estimate.js +77 -4
  433. package/dist/context-builder/token-estimate.js.map +1 -1
  434. package/dist/context-builder/versions.d.ts +29 -0
  435. package/dist/context-builder/versions.js +30 -0
  436. package/dist/context-builder/versions.js.map +1 -0
  437. package/dist/context-builder/workflow-runner.d.ts +69 -0
  438. package/dist/context-builder/workflow-runner.js +1051 -0
  439. package/dist/context-builder/workflow-runner.js.map +1 -0
  440. package/dist/context-builder/write-watchdog.d.ts +14 -0
  441. package/dist/context-builder/write-watchdog.js +38 -0
  442. package/dist/context-builder/write-watchdog.js.map +1 -0
  443. package/dist/dashboard-adapter-catalog.d.ts +9 -0
  444. package/dist/dashboard-adapter-catalog.js +46 -0
  445. package/dist/dashboard-adapter-catalog.js.map +1 -0
  446. package/dist/dashboard-cli.d.ts +79 -0
  447. package/dist/dashboard-cli.js +585 -0
  448. package/dist/dashboard-cli.js.map +1 -0
  449. package/dist/dashboard-companion/client.d.ts +36 -0
  450. package/dist/dashboard-companion/client.js +210 -0
  451. package/dist/dashboard-companion/client.js.map +1 -0
  452. package/dist/dashboard-companion/config.d.ts +8 -0
  453. package/dist/dashboard-companion/config.js +52 -0
  454. package/dist/dashboard-companion/config.js.map +1 -0
  455. package/dist/dashboard-companion/http-proxy.d.ts +10 -0
  456. package/dist/dashboard-companion/http-proxy.js +234 -0
  457. package/dist/dashboard-companion/http-proxy.js.map +1 -0
  458. package/dist/dashboard-companion/protocol.d.ts +80 -0
  459. package/dist/dashboard-companion/protocol.js +455 -0
  460. package/dist/dashboard-companion/protocol.js.map +1 -0
  461. package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
  462. package/dist/dashboard-companion/standalone-assets.js +72 -0
  463. package/dist/dashboard-companion/standalone-assets.js.map +1 -0
  464. package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
  465. package/dist/dashboard-companion/standalone-entry.js +17 -0
  466. package/dist/dashboard-companion/standalone-entry.js.map +1 -0
  467. package/dist/dashboard-gateway/config.d.ts +9 -0
  468. package/dist/dashboard-gateway/config.js +44 -0
  469. package/dist/dashboard-gateway/config.js.map +1 -0
  470. package/dist/dashboard-gateway/index.d.ts +11 -0
  471. package/dist/dashboard-gateway/index.js +755 -0
  472. package/dist/dashboard-gateway/index.js.map +1 -0
  473. package/dist/dashboard-gateway/main.d.ts +1 -0
  474. package/dist/dashboard-gateway/main.js +25 -0
  475. package/dist/dashboard-gateway/main.js.map +1 -0
  476. package/dist/dashboard-pairing-token.d.ts +1 -0
  477. package/dist/dashboard-pairing-token.js +5 -0
  478. package/dist/dashboard-pairing-token.js.map +1 -0
  479. package/dist/elaris/client.d.ts +73 -0
  480. package/dist/elaris/client.js +825 -0
  481. package/dist/elaris/client.js.map +1 -0
  482. package/dist/elaris/config.d.ts +18 -0
  483. package/dist/elaris/config.js +75 -0
  484. package/dist/elaris/config.js.map +1 -0
  485. package/dist/elaris/errors.d.ts +41 -0
  486. package/dist/elaris/errors.js +205 -0
  487. package/dist/elaris/errors.js.map +1 -0
  488. package/dist/elaris/github-repo.d.ts +18 -0
  489. package/dist/elaris/github-repo.js +89 -0
  490. package/dist/elaris/github-repo.js.map +1 -0
  491. package/dist/elaris/index.d.ts +10 -0
  492. package/dist/elaris/index.js +11 -0
  493. package/dist/elaris/index.js.map +1 -0
  494. package/dist/elaris/redact.d.ts +89 -0
  495. package/dist/elaris/redact.js +423 -0
  496. package/dist/elaris/redact.js.map +1 -0
  497. package/dist/elaris/run-id.d.ts +5 -0
  498. package/dist/elaris/run-id.js +20 -0
  499. package/dist/elaris/run-id.js.map +1 -0
  500. package/dist/elaris/status.d.ts +45 -0
  501. package/dist/elaris/status.js +122 -0
  502. package/dist/elaris/status.js.map +1 -0
  503. package/dist/elaris/tool-schemas.d.ts +314 -0
  504. package/dist/elaris/tool-schemas.js +208 -0
  505. package/dist/elaris/tool-schemas.js.map +1 -0
  506. package/dist/elaris/tools.d.ts +73 -0
  507. package/dist/elaris/tools.js +1065 -0
  508. package/dist/elaris/tools.js.map +1 -0
  509. package/dist/elaris/types.d.ts +1541 -0
  510. package/dist/elaris/types.js +252 -0
  511. package/dist/elaris/types.js.map +1 -0
  512. package/dist/errors.d.ts +16 -0
  513. package/dist/errors.js +19 -0
  514. package/dist/errors.js.map +1 -1
  515. package/dist/handler.d.ts +4 -0
  516. package/dist/handler.js +10 -1
  517. package/dist/handler.js.map +1 -1
  518. package/dist/index.d.ts +2 -1
  519. package/dist/index.js +13 -3
  520. package/dist/index.js.map +1 -1
  521. package/dist/local-clink-backend.d.ts +3 -0
  522. package/dist/local-clink-backend.js +4 -0
  523. package/dist/local-clink-backend.js.map +1 -1
  524. package/dist/package-info.d.ts +2 -0
  525. package/dist/package-info.js +18 -0
  526. package/dist/package-info.js.map +1 -0
  527. package/dist/parsers/codex-jsonl.js +48 -3
  528. package/dist/parsers/codex-jsonl.js.map +1 -1
  529. package/dist/parsers/extract.d.ts +10 -1
  530. package/dist/parsers/extract.js +11 -3
  531. package/dist/parsers/extract.js.map +1 -1
  532. package/dist/pipeline.d.ts +4 -0
  533. package/dist/pipeline.js +129 -8
  534. package/dist/pipeline.js.map +1 -1
  535. package/dist/run-dir.d.ts +24 -0
  536. package/dist/run-dir.js +201 -2
  537. package/dist/run-dir.js.map +1 -1
  538. package/dist/runner.d.ts +13 -0
  539. package/dist/runner.js +184 -15
  540. package/dist/runner.js.map +1 -1
  541. package/dist/safety.d.ts +4 -4
  542. package/dist/safety.js +4 -4
  543. package/dist/schema.d.ts +7 -7
  544. package/dist/server.d.ts +1 -0
  545. package/dist/server.js +359 -17
  546. package/dist/server.js.map +1 -1
  547. package/dist/session-log-stream.d.ts +18 -0
  548. package/dist/session-log-stream.js +319 -0
  549. package/dist/session-log-stream.js.map +1 -0
  550. package/package.json +46 -5
  551. package/conf/adapters/gemini.json +0 -44
  552. package/conf/adapters/hapi/gemini.json +0 -47
  553. package/dist/parsers/gemini-json.d.ts +0 -2
  554. package/dist/parsers/gemini-json.js +0 -72
  555. package/dist/parsers/gemini-json.js.map +0 -1
@@ -8,7 +8,8 @@ import { CONTEXT_BUILDER_REQUESTED_EVIDENCE_OMISSION_REASON, requestedEvidenceOm
8
8
  import { compileContextBuilderGlobs, CONTEXT_BUILDER_DEFAULT_EXCLUDE_GLOBS, } from "./globs.js";
9
9
  import { normalizeContextBuilderRootRelativePath, rootRelativePathForAbsolutePath, } from "./paths.js";
10
10
  import { directoryDistance, genericPathKind, isTestPath, normalizedPathStem, } from "./path-signals.js";
11
- import { isBlockingSecretFinding, redactHighConfidenceAssignmentSecrets, scanHighConfidenceSecretContent, scanSecretPath, } from "./secrets.js";
11
+ import { buildContextReviewObligations, } from "./review-coverage.js";
12
+ import { sanitizeHighConfidenceSecretContent, scanSecretPath, } from "./secrets.js";
12
13
  import { CONTEXT_BUILDER_PUBLIC_BUDGET_HARD_LIMITS, } from "./schema.js";
13
14
  const SEARCH_WINDOW_LINES_BEFORE = 19;
14
15
  const SEARCH_WINDOW_LINES_AFTER = 20;
@@ -16,13 +17,28 @@ const SEARCH_BRIDGE_GAP_LINES = 20;
16
17
  const SEARCH_MAX_MERGED_SPAN_LINES = 160;
17
18
  const DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES = 48_000;
18
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";
19
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;
20
35
  const PROJECT_SUPPORT_FILENAMES = Object.freeze([
21
36
  "package.json",
22
37
  "vitest.config.ts",
23
38
  "vite.config.ts",
24
39
  ]);
25
40
  export async function discoverContextBuilderCandidates(options) {
41
+ const allowSecrets = options.allowSecrets ?? true;
26
42
  const internalCaps = options.internal_caps ?? CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS;
27
43
  const matcher = compileContextBuilderGlobs({
28
44
  include: [...(options.include_globs ?? [])],
@@ -37,6 +53,8 @@ export async function discoverContextBuilderCandidates(options) {
37
53
  omissions: [],
38
54
  requestedEvidenceOmissionKeys: new Set(),
39
55
  processedFileKeys: new Set(),
56
+ traversedDirectoryKeys: new Set(),
57
+ focusScanExemptFiles: 0,
40
58
  metrics: {
41
59
  files_seen: 0,
42
60
  files_considered: 0,
@@ -57,6 +75,7 @@ export async function discoverContextBuilderCandidates(options) {
57
75
  await processExactFocusFiles({
58
76
  root,
59
77
  focusPaths: options.focus_paths ?? [],
78
+ allowSecrets,
60
79
  matcher,
61
80
  budget: options.budget,
62
81
  internalCaps,
@@ -70,12 +89,31 @@ export async function discoverContextBuilderCandidates(options) {
70
89
  },
71
90
  state,
72
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
+ });
73
110
  await traverseDirectory({
74
111
  root,
75
112
  absoluteDir: root.path,
76
113
  relativeDir: "",
77
114
  matcher,
78
115
  focusPaths: options.focus_paths ?? [],
116
+ allowSecrets,
79
117
  budget: options.budget,
80
118
  internalCaps,
81
119
  excludedRealRoots: options.excluded_real_roots ?? [],
@@ -93,6 +131,7 @@ export async function discoverContextBuilderCandidates(options) {
93
131
  root,
94
132
  matcher,
95
133
  focusPaths: options.focus_paths ?? [],
134
+ allowSecrets,
96
135
  budget: options.budget,
97
136
  internalCaps,
98
137
  ...(options.signal == null ? {} : { signal: options.signal }),
@@ -108,11 +147,12 @@ export async function discoverContextBuilderCandidates(options) {
108
147
  }
109
148
  state.tree.sort(compareTreeFiles);
110
149
  state.contentCandidates.sort(compareContentCandidates);
150
+ const expandedSearchTerms = expandSearchTerms([...(options.search_terms ?? [])]);
111
151
  const search = buildCandidateSearchRecords({
112
152
  candidates: state.contentCandidates,
113
- ...(options.task == null ? {} : { task: options.task }),
114
153
  focusPaths: [...(options.focus_paths ?? [])],
115
- searchTerms: expandSearchTerms([...(options.search_terms ?? [])]),
154
+ searchTerms: expandedSearchTerms.terms,
155
+ callerTermCount: expandedSearchTerms.callerTermCount,
116
156
  maxSearchResults: options.budget.max_search_results,
117
157
  maxPreviewBytes: internalCaps.max_search_preview_bytes,
118
158
  maxCandidateBytes: internalCaps.max_candidate_bytes,
@@ -135,6 +175,24 @@ export async function discoverContextBuilderCandidates(options) {
135
175
  if (slices.capHit) {
136
176
  state.metrics.candidate_cap_hit = true;
137
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
+ });
138
196
  auditScanCapFocusedPaths({
139
197
  focusPaths: [...(options.focus_paths ?? [])],
140
198
  state,
@@ -146,30 +204,86 @@ export async function discoverContextBuilderCandidates(options) {
146
204
  slices: slices.records,
147
205
  omissions: state.omissions,
148
206
  metrics: state.metrics,
207
+ search_coverage: search.coverage,
208
+ ...(reviewObligations == null ? {} : { review_obligations: reviewObligations }),
149
209
  };
150
210
  }
151
- function expandSearchTerms(searchTerms) {
211
+ export function expandSearchTerms(searchTerms) {
152
212
  const expanded = [];
153
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;
154
223
  for (const term of searchTerms) {
155
- addExpandedSearchTerm(expanded, seen, term);
156
224
  for (const derived of deriveGenericSearchTerms(term)) {
157
- addExpandedSearchTerm(expanded, seen, derived);
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");
158
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;
159
273
  }
160
- return expanded;
161
274
  }
162
275
  function addExpandedSearchTerm(expanded, seen, term) {
163
276
  const normalized = term.trim();
164
277
  if (normalized.length === 0) {
165
- return;
278
+ return 0;
166
279
  }
167
280
  const key = normalized.toLowerCase();
168
281
  if (seen.has(key)) {
169
- return;
282
+ return 0;
170
283
  }
171
284
  expanded.push(normalized);
172
285
  seen.add(key);
286
+ return Buffer.byteLength(normalized, "utf8");
173
287
  }
174
288
  function deriveGenericSearchTerms(term) {
175
289
  const tokens = tokenizeGenericSearchTerm(term);
@@ -238,6 +352,7 @@ async function processImportAndSupportFiles(input) {
238
352
  relativePath,
239
353
  matcher: input.matcher,
240
354
  focusPaths: input.focusPaths,
355
+ allowSecrets: input.allowSecrets,
241
356
  budget: input.budget,
242
357
  internalCaps: input.internalCaps,
243
358
  ignoreIncludeGlob: true,
@@ -264,6 +379,7 @@ async function processImportAndSupportFiles(input) {
264
379
  relativePath,
265
380
  matcher: input.matcher,
266
381
  focusPaths: input.focusPaths,
382
+ allowSecrets: input.allowSecrets,
267
383
  budget: input.budget,
268
384
  internalCaps: input.internalCaps,
269
385
  ignoreIncludeGlob: true,
@@ -477,9 +593,51 @@ async function processExactFocusFiles(input) {
477
593
  relativePath: focus.root_relative_path,
478
594
  matcher: input.matcher,
479
595
  focusPaths: input.focusPaths,
596
+ allowSecrets: input.allowSecrets,
480
597
  budget: input.budget,
481
598
  internalCaps: input.internalCaps,
482
- ignoreScanCap: true,
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,
483
641
  ...(input.signal == null ? {} : { signal: input.signal }),
484
642
  fs: input.fs,
485
643
  state: input.state,
@@ -495,14 +653,41 @@ export function buildCandidateSearchRecords(input) {
495
653
  let previewBytes = 0;
496
654
  let candidateBytes = 0;
497
655
  let capHit = false;
498
- if (input.searchTerms.length === 0) {
499
- return { records, capHit, candidateBytes };
500
- }
656
+ const callerTermCount = Math.max(0, Math.min(input.callerTermCount ?? 0, input.searchTerms.length));
501
657
  const searchTerms = input.searchTerms
502
658
  .map((term, index) => ({ term, index }))
503
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
+ });
504
689
  if (searchTerms.length === 0) {
505
- return { records, capHit, candidateBytes };
690
+ return finalize();
506
691
  }
507
692
  const focusRank = new Map();
508
693
  for (const focus of input.focusPaths ?? []) {
@@ -521,70 +706,154 @@ export function buildCandidateSearchRecords(input) {
521
706
  const leftRank = focusRank.get(candidateKey(left)) ?? Number.MAX_SAFE_INTEGER;
522
707
  const rightRank = focusRank.get(candidateKey(right)) ?? Number.MAX_SAFE_INTEGER;
523
708
  return (leftRank - rightRank ||
524
- discoveryTaskClosureRank(left.path, input.task ?? "") -
525
- discoveryTaskClosureRank(right.path, input.task ?? "") ||
526
709
  compareContentCandidates(left, right));
527
710
  });
528
- const streams = [];
711
+ const candidateLines = [];
529
712
  for (const candidate of candidates) {
530
713
  assertNotAborted(input.signal);
531
- const lines = splitTextLines(candidate.content);
532
- for (const searchTerm of searchTerms) {
533
- streams.push({
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) => ({
534
755
  candidate,
535
- searchTerm,
536
- lines,
756
+ searchTerm: { term: coverage.term, index: coverage.search_term_index },
757
+ lines: candidateLines[candidateIndex] ?? [],
537
758
  lineIndex: 0,
538
759
  fromIndex: 0,
539
760
  done: false,
540
- });
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
+ }
541
787
  }
542
788
  }
543
- let activeStreams = streams.length;
544
- while (activeStreams > 0) {
545
- let foundInRound = false;
546
- for (const stream of streams) {
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()) {
547
794
  assertNotAborted(input.signal);
548
- if (stream.done) {
549
- continue;
550
- }
551
- const hit = nextSearchHit(stream);
552
- if (hit == null) {
553
- stream.done = true;
554
- activeStreams--;
555
- continue;
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
+ });
556
805
  }
557
- foundInRound = true;
558
- if (records.length >= input.maxSearchResults) {
559
- capHit = true;
560
- return { records, capHit, candidateBytes };
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
+ }
561
826
  }
562
- const preview = boundedPreview(hit.line, hit.columnIndex, stream.searchTerm.term.length);
563
- const previewSize = Buffer.byteLength(preview, "utf8");
564
- const bounded = previewBytes + previewSize <= maxPreviewBytes ? preview : "";
565
- const record = {
566
- root_index: stream.candidate.root_index,
567
- path: stream.candidate.path,
568
- line: hit.lineIndex + 1,
569
- column: hit.columnIndex + 1,
570
- search_term: stream.searchTerm.term,
571
- search_term_index: stream.searchTerm.index,
572
- preview: bounded,
573
- };
574
- const recordBytes = Buffer.byteLength(`${JSON.stringify(record)}\n`, "utf8");
575
- if (candidateBytes + recordBytes > maxCandidateBytes) {
576
- capHit = true;
577
- return { records, capHit, candidateBytes };
827
+ if (!foundInRound) {
828
+ break;
578
829
  }
579
- previewBytes += Buffer.byteLength(bounded, "utf8");
580
- candidateBytes += recordBytes;
581
- records.push(record);
582
830
  }
583
- if (!foundInRound) {
584
- break;
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;
585
853
  }
854
+ return { stream, hit };
586
855
  }
587
- return { records, capHit, candidateBytes };
856
+ return undefined;
588
857
  }
589
858
  export function buildCandidateSliceRecords(input) {
590
859
  const maxCandidateSlices = input.maxCandidateSlices ??
@@ -603,7 +872,7 @@ export function buildCandidateSliceRecords(input) {
603
872
  const expectedFocusedSearchRanges = new Map();
604
873
  const survivedFocusedSearchRanges = new Map();
605
874
  const focusSliceSurvivors = new Set();
606
- const omittedLargeFocusedCandidates = new Set();
875
+ const slicedLargeFocusedCandidates = new Set();
607
876
  const omittedRequestedCandidates = new Set();
608
877
  const pushRequestedCandidateOmission = (candidate, kind, message) => {
609
878
  const key = candidateKey(candidate);
@@ -701,12 +970,8 @@ export function buildCandidateSliceRecords(input) {
701
970
  (focus.kind === "directory" &&
702
971
  isRootRelativeDescendant(candidate.path, focus.root_relative_path)))) {
703
972
  const key = candidateKey(candidate);
704
- if (candidate.byte_length > fullFocusSliceByteLimit(candidate.path, maxFullFocusSliceBytes)) {
705
- if (!searchRanges.has(key) && !omittedLargeFocusedCandidates.has(key)) {
706
- pushRequestedCandidateOmission(candidate, "too_large", "focused file exceeded bounded full-file focus size and had no per-file search evidence");
707
- omittedLargeFocusedCandidates.add(key);
708
- }
709
- else if (searchRanges.has(key)) {
973
+ if (candidate.byte_length > maxFullFocusSliceBytes) {
974
+ if (searchRanges.has(key)) {
710
975
  focusedLargeSearchCandidates.set(key, candidate);
711
976
  expectedFocusedSearchRanges.set(key, mergeLineRanges(searchRanges.get(key) ?? [], {
712
977
  bridgeGapLines: SEARCH_BRIDGE_GAP_LINES,
@@ -714,6 +979,27 @@ export function buildCandidateSliceRecords(input) {
714
979
  }));
715
980
  survivedFocusedSearchRanges.set(key, []);
716
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
+ }
717
1003
  continue;
718
1004
  }
719
1005
  const ranges = focusRanges.get(candidateKey(candidate)) ?? [];
@@ -735,6 +1021,30 @@ export function buildCandidateSliceRecords(input) {
735
1021
  { start_line: 1, end_line: Math.max(1, candidate.line_count) },
736
1022
  ]);
737
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
+ }
738
1048
  for (const [key, ranges] of [...searchRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
739
1049
  assertNotAborted(input.signal);
740
1050
  const candidate = candidateByIdentity.get(key);
@@ -787,26 +1097,6 @@ export function buildCandidateSliceRecords(input) {
787
1097
  }
788
1098
  }
789
1099
  }
790
- for (const [key, ranges] of [...focusRanges.entries()].sort(([a], [b]) => a.localeCompare(b))) {
791
- assertNotAborted(input.signal);
792
- const candidate = candidateByIdentity.get(key);
793
- if (candidate == null) {
794
- continue;
795
- }
796
- for (const range of mergeLineRanges(ranges)) {
797
- if (!append({
798
- root_index: candidate.root_index,
799
- path: candidate.path,
800
- start_line: range.start_line,
801
- end_line: range.end_line,
802
- source: "focus",
803
- content: sliceContent(candidate.content, range.start_line, range.end_line),
804
- })) {
805
- pushMissingFocusedRangeOmissions(focusRanges.keys());
806
- return { records, capHit, omissions };
807
- }
808
- }
809
- }
810
1100
  return { records, capHit, omissions };
811
1101
  }
812
1102
  export function splitTextLines(text) {
@@ -859,11 +1149,55 @@ export function mergeLineRanges(ranges, options = {}) {
859
1149
  }
860
1150
  return merged;
861
1151
  }
862
- function fullFocusSliceByteLimit(path, maxFullFocusSliceBytes) {
863
- if (isTestPath(path)) {
864
- return Math.min(maxFullFocusSliceBytes, DEFAULT_MAX_FULL_FOCUS_SLICE_BYTES);
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;
865
1180
  }
866
- return maxFullFocusSliceBytes;
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
+ };
867
1201
  }
868
1202
  function focusedSearchRangesCovered(expected, survived) {
869
1203
  return expected.every((range) => rangeCoveredByRanges(range, survived));
@@ -889,6 +1223,14 @@ async function traverseDirectory(input) {
889
1223
  if (input.state.stopped) {
890
1224
  return;
891
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);
892
1234
  if (input.relativeDir !== "" &&
893
1235
  (input.matcher.excludes(input.relativeDir) ||
894
1236
  input.matcher.excludes(`${input.relativeDir}/__dir__`))) {
@@ -1038,7 +1380,7 @@ async function processFile(input) {
1038
1380
  if (!alreadyProcessed) {
1039
1381
  input.state.processedFileKeys.add(fileKey);
1040
1382
  }
1041
- const omitted = (reason, message) => {
1383
+ const omitted = (reason, message, secretFindings) => {
1042
1384
  input.state.omissions.push({
1043
1385
  root_index: input.root.root_index,
1044
1386
  reason,
@@ -1051,7 +1393,7 @@ async function processFile(input) {
1051
1393
  focusPaths: input.focusPaths,
1052
1394
  state: input.state,
1053
1395
  kind: requestedOmissionKindForDiscoveryReason(reason),
1054
- message: sanitizedFocusedOmissionMessage(reason),
1396
+ message: sanitizedFocusedOmissionMessage(reason, secretFindings),
1055
1397
  });
1056
1398
  };
1057
1399
  const requestedOmitted = (kind, message) => {
@@ -1103,8 +1445,13 @@ async function processFile(input) {
1103
1445
  });
1104
1446
  return;
1105
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.
1106
1451
  if (input.ignoreScanCap !== true &&
1107
- input.state.metrics.files_seen >= input.budget.max_scan_files) {
1452
+ input.focusScanExempt !== true &&
1453
+ input.state.metrics.files_seen - input.state.focusScanExemptFiles >=
1454
+ input.budget.max_scan_files) {
1108
1455
  input.state.metrics.scan_cap_hit = true;
1109
1456
  pushRequestedFocusOmission({
1110
1457
  root_index: input.root.root_index,
@@ -1118,9 +1465,12 @@ async function processFile(input) {
1118
1465
  return;
1119
1466
  }
1120
1467
  input.state.metrics.files_seen++;
1121
- const pathSecrets = scanSecretPath(input.relativePath);
1468
+ if (input.focusScanExempt === true) {
1469
+ input.state.focusScanExemptFiles++;
1470
+ }
1471
+ const pathSecrets = input.allowSecrets ? [] : scanSecretPath(input.relativePath);
1122
1472
  if (pathSecrets.length > 0) {
1123
- omitted("secret_path", `Secret-looking path omitted (${pathSecrets[0]?.provenance})`);
1473
+ omitted("secret_path", `Secret-looking path omitted (${pathSecrets[0]?.provenance})`, pathSecrets);
1124
1474
  pushTree({
1125
1475
  eligible: false,
1126
1476
  content_eligible: false,
@@ -1235,10 +1585,11 @@ async function processFile(input) {
1235
1585
  }
1236
1586
  const rawContent = contentBuffer.toString("utf8");
1237
1587
  input.state.metrics.bytes_read += contentBuffer.byteLength;
1238
- const contentSecrets = scanHighConfidenceSecretContent(rawContent);
1239
- const blockingSecrets = contentSecrets.filter(isBlockingSecretFinding);
1240
- if (blockingSecrets.length > 0) {
1241
- omitted("content_secret", `Content omitted due to high-confidence secret (${summarizeSecretFindings(blockingSecrets)})`);
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);
1242
1593
  pushTree({
1243
1594
  size_bytes: read.sizeBytes,
1244
1595
  eligible: true,
@@ -1247,9 +1598,7 @@ async function processFile(input) {
1247
1598
  });
1248
1599
  return;
1249
1600
  }
1250
- const content = contentSecrets.length > 0
1251
- ? redactHighConfidenceAssignmentSecrets(rawContent)
1252
- : rawContent;
1601
+ const content = sanitized.content;
1253
1602
  const contentBytes = Buffer.byteLength(content, "utf8");
1254
1603
  if (input.state.contentCandidates.length >= input.internalCaps.max_discovery_candidates) {
1255
1604
  input.state.metrics.discovery_candidate_cap_hit = true;
@@ -1403,74 +1752,6 @@ function compareTreeFiles(left, right) {
1403
1752
  function compareContentCandidates(left, right) {
1404
1753
  return left.root_index - right.root_index || left.path.localeCompare(right.path);
1405
1754
  }
1406
- function discoveryTaskClosureRank(path, task) {
1407
- const normalizedTask = task.toLowerCase();
1408
- if (normalizedTask.length === 0) {
1409
- return 0;
1410
- }
1411
- const taskTokens = new Set(tokenizeGenericSearchTerm(normalizedTask));
1412
- const normalizedPath = path.toLowerCase();
1413
- if (/\b(discover|discovery)\b.*\b(provider-specific|provider\s+semantics|providers?|claude|agent|symmetry)\b|\b(provider-specific|provider\s+semantics|providers?|claude|agent|symmetry)\b.*\b(discover|discovery)\b/.test(normalizedTask) &&
1414
- hasAnyToken(taskTokens, ["provider", "runtime", "claude", "agent", "symmetry"]) &&
1415
- [/\/claude_agent\.py$/, /\/runtime\/base\.py$/].some((pattern) => pattern.test(normalizedPath))) {
1416
- return -30;
1417
- }
1418
- if (/\b(discover|discovery)\b/.test(normalizedTask) &&
1419
- hasAnyToken(taskTokens, ["queue", "migration", "runtime", "schema", "setting", "selector"]) &&
1420
- [
1421
- /\/tasks\/chat_processor\.py$/,
1422
- /\/hooks\/usemessageactions\.ts$/,
1423
- /\/hooks\/usechatstreaming\.ts$/,
1424
- /\/hooks\/usechatruntimesettings\.ts$/,
1425
- /\/components\/chat\/chat-window\/chat\.tsx$/,
1426
- /\/components\/chat\/message-input\/input\.tsx$/,
1427
- /\/components\/chat\/message-input\/inputsettingspopover\.tsx$/,
1428
- /\/pages\/chatpage\.tsx$/,
1429
- /\/landingpage\.tsx$/,
1430
- /\/models\/db_models\/chat\.py$/,
1431
- /\/[^/]*(permission|thinking).*selector[^/]*\.(ts|tsx)$/,
1432
- /\/tests\/test_chat\.py$/,
1433
- /\/tests?\/.*runtime.*\.(py|ts|tsx)$/,
1434
- ].some((pattern) => pattern.test(normalizedPath))) {
1435
- return -24;
1436
- }
1437
- if (/\breview\b/.test(normalizedTask) &&
1438
- hasAnyToken(taskTokens, ["codex", "approval", "permission", "session", "runtime"]) &&
1439
- [
1440
- /\/api\/endpoints\/permissions\.py$/,
1441
- /\/services\/runtime\/codex_approval\.py$/,
1442
- /\/services\/permission_manager\.py$/,
1443
- /\/services\/chat\.py$/,
1444
- /\/api\/endpoints\/chat\.py$/,
1445
- /\/models\/db_models\/ai_model\.py$/,
1446
- /\/services\/chatservice\.ts$/,
1447
- /\/types\/chat\.types\.ts$/,
1448
- /\/core\/config\.py$/,
1449
- /\/constants\.py$/,
1450
- /\/hooks\/usepermissionrequest\.ts$/,
1451
- /\/tests\/test_permissions\.py$/,
1452
- /(^|\/)docs?\/.*(codex|approval|permission|session|runtime).*\.md$/,
1453
- ].some((pattern) => pattern.test(normalizedPath))) {
1454
- return -24;
1455
- }
1456
- if (/\b(debug|debugging|diagnose|diagnosis|diagnostic)\b/.test(normalizedTask) &&
1457
- hasAnyToken(taskTokens, ["devtool", "trace", "archive", "message"]) &&
1458
- [
1459
- /\/api\/endpoints\/devtools\.py$/,
1460
- /\/services\/session_trace_archive\.py$/,
1461
- /\/models\/db_models\/session_trace_archive\.py$/,
1462
- /\/.*trace.*archive.*\.test\.(ts|tsx|py)$/,
1463
- /\/tests?\/.*trace.*archive.*\.(py|ts|tsx)$/,
1464
- /\/messagerenderer\.tsx$/,
1465
- /\/devtools\.types\.ts$/,
1466
- ].some((pattern) => pattern.test(normalizedPath))) {
1467
- return -24;
1468
- }
1469
- return 0;
1470
- }
1471
- function hasAnyToken(tokens, candidates) {
1472
- return candidates.some((candidate) => tokens.has(candidate));
1473
- }
1474
1755
  function compareSearchRecords(left, right) {
1475
1756
  return (left.root_index - right.root_index ||
1476
1757
  left.path.localeCompare(right.path) ||
@@ -1569,14 +1850,14 @@ function requestedEvidenceOmissionKey(rootIndex, relativePath) {
1569
1850
  function requestedOmissionKindForDiscoveryReason(reason) {
1570
1851
  return reason === "file_too_large" ? "too_large" : "unavailable";
1571
1852
  }
1572
- function sanitizedFocusedOmissionMessage(reason) {
1853
+ function sanitizedFocusedOmissionMessage(reason, secretFindings) {
1573
1854
  switch (reason) {
1574
1855
  case "caller_include_glob":
1575
1856
  return "focused path was outside include globs";
1576
1857
  case "exclude_glob":
1577
1858
  return "focused path was omitted by exclude globs";
1578
1859
  case "secret_path":
1579
- return "focused path was omitted because it matched a secret-path rule";
1860
+ return withSecretFindingProvenance("focused path was omitted because it matched a secret-path rule", secretFindings);
1580
1861
  case "symlink_omitted":
1581
1862
  return "focused path was omitted because symlinks are not followed";
1582
1863
  case "path_escape":
@@ -1591,13 +1872,32 @@ function sanitizedFocusedOmissionMessage(reason) {
1591
1872
  case "binary_file":
1592
1873
  return "focused binary file cannot be represented as text";
1593
1874
  case "content_secret":
1594
- return "focused file content was omitted because secret-like content was detected";
1875
+ return withSecretFindingProvenance("focused file content was omitted because secret-like content was detected", secretFindings);
1595
1876
  case "file_too_large":
1596
1877
  return "focused file exceeded max_bytes_per_file";
1597
1878
  default:
1598
1879
  return "focused path could not be represented as local evidence";
1599
1880
  }
1600
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
+ }
1601
1901
  function candidateKey(candidate) {
1602
1902
  return `${candidate.root_index}\u0000${candidate.path}`;
1603
1903
  }