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
@@ -1,17 +1,20 @@
1
1
  import { boundContextBuilderOmissions, boundContextBuilderPathList, boundContextBuilderWarnings, sanitizeDiagnosticText, } from "./diagnostics.js";
2
2
  import { createContextBuilderReceipt, cloneContextBuilderDelegateReceiptMetadata, } from "./receipt.js";
3
- import { assertNoHighConfidenceSecretsInTask, hasHighConfidenceSecret, } from "./secrets.js";
3
+ import { assertTaskSecretsGated, hasConservativeResidualSecret, } from "./secrets.js";
4
4
  import { capEventsFromContextBuilderMetrics, reduceContextBuilderStatus, } from "./status.js";
5
5
  import { writeContextBuilderArtifactsAndCommit, } from "./storage.js";
6
6
  import { contextBuilderTokenCountingMethod, estimateContextBuilderTokens, } from "./token-estimate.js";
7
7
  import { contextBuilderInternalCapsForManifest, } from "./config.js";
8
8
  import { buildFileCodemap, remapCodemapToSourceCoordinates, renderCodemapJsonl, } from "./codemap.js";
9
+ // Folded into the cache key so bundle markdown format changes invalidate stale cache entries.
10
+ export const CONTEXT_BUILDER_RENDERER_VERSION = 2;
9
11
  export function renderContextBuilderBundle(input) {
10
- assertNoHighConfidenceSecretsInTask(input.task);
11
- const selected = input.selected_context.map(sanitizeSelectedFile);
12
- const tree = input.tree.map(sanitizeTreeFile);
13
- const search = input.search.map(sanitizeSearchRecord);
14
- const slices = input.slices.map(sanitizeSliceRecord);
12
+ const allowSecrets = input.allowSecrets ?? true;
13
+ assertTaskSecretsGated(input.task, allowSecrets);
14
+ const selected = input.selected_context.map((file) => sanitizeSelectedFile(file, allowSecrets));
15
+ const tree = input.tree.map((file) => sanitizeTreeFile(file, allowSecrets));
16
+ const search = input.search.map((record) => sanitizeSearchRecord(record, allowSecrets));
17
+ const slices = input.slices.map((record) => sanitizeSliceRecord(record, allowSecrets));
15
18
  const presentationSelected = promoteTaskPresentationEvidence(filterTaskPresentationSelected(selected, input.task), input.task, slices);
16
19
  const omissions = boundContextBuilderOmissions([...(input.omissions ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps));
17
20
  const warnings = boundContextBuilderWarnings([...(input.warnings ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps));
@@ -27,9 +30,9 @@ export function renderContextBuilderBundle(input) {
27
30
  "",
28
31
  "## How To Use This Bundle",
29
32
  "",
30
- "- Start with top must-read ranges, then verify the generated answer against the source evidence immediately below it.",
33
+ "- Start with top must-read ranges and verify the cited evidence directly.",
31
34
  "- Inspect selected files, repository shape, and search evidence before editing.",
32
- "- Treat remaining omissions and open questions as follow-up checks before editing.",
35
+ "- Treat omissions and open questions as follow-up checks, not as resolved facts.",
33
36
  "",
34
37
  "## Top Must-Read Ranges",
35
38
  "",
@@ -40,33 +43,6 @@ export function renderContextBuilderBundle(input) {
40
43
  showRootLabels,
41
44
  }),
42
45
  "",
43
- "## Generated Answer",
44
- "",
45
- renderGeneratedAnswer({
46
- task: input.task,
47
- selected: presentationSelected,
48
- omissions: presentationOmissions,
49
- showRootLabels,
50
- }),
51
- "",
52
- "### Source Evidence",
53
- "",
54
- renderGeneratedAnswerSourceEvidence({
55
- task: input.task,
56
- selected: presentationSelected,
57
- slices,
58
- showRootLabels,
59
- }),
60
- "",
61
- "## Relationships And Risks",
62
- "",
63
- renderRelationshipsAndRisks({
64
- task: input.task,
65
- selected: presentationSelected,
66
- omissions: presentationOmissions,
67
- showRootLabels,
68
- }),
69
- "",
70
46
  "## Selected Files",
71
47
  "",
72
48
  renderSelectedFilesList({
@@ -127,7 +103,9 @@ export function buildContextBuilderStatusEvidence(input, boundedDiagnostics = {}
127
103
  ...(input.status_evidence?.warnings ?? []),
128
104
  ],
129
105
  caps: [
130
- ...capEventsFromContextBuilderMetrics(input.metrics),
106
+ ...capEventsFromContextBuilderMetrics(input.metrics, input.search_coverage == null
107
+ ? {}
108
+ : { search_coverage: input.search_coverage }),
131
109
  ...(input.status_evidence?.caps ?? []),
132
110
  ],
133
111
  storage: [
@@ -145,17 +123,19 @@ export function buildContextBuilderStatusEvidence(input, boundedDiagnostics = {}
145
123
  };
146
124
  }
147
125
  export function renderContextBuilderArtifactContents(input) {
148
- assertNoHighConfidenceSecretsInTask(input.task);
149
- const tree = input.tree.map(sanitizeTreeFile);
150
- const search = input.search.map(sanitizeSearchRecord);
151
- const slices = input.slices.map(sanitizeSliceRecord);
152
- const selectedContext = input.selected_context.map(sanitizeSelectedFile);
126
+ const allowSecrets = input.allowSecrets ?? true;
127
+ assertTaskSecretsGated(input.task, allowSecrets);
128
+ const tree = input.tree.map((file) => sanitizeTreeFile(file, allowSecrets));
129
+ const search = input.search.map((record) => sanitizeSearchRecord(record, allowSecrets));
130
+ const slices = input.slices.map((record) => sanitizeSliceRecord(record, allowSecrets));
131
+ const selectedContext = input.selected_context.map((file) => sanitizeSelectedFile(file, allowSecrets));
153
132
  const summary = buildContextBuilderSummary(input);
154
133
  return {
155
134
  bundle_md: renderContextBuilderBundle(input),
135
+ selected_context_jsonl: renderSelectedContextJsonl(selectedContext),
156
136
  tree_json: `${JSON.stringify({
157
137
  schema_version: 1,
158
- roots: input.roots.map(sanitizeRoot),
138
+ roots: input.roots.map((root) => sanitizeRoot(root, allowSecrets)),
159
139
  files: tree,
160
140
  })}\n`,
161
141
  search_jsonl: renderJsonl(search),
@@ -168,6 +148,7 @@ export function renderContextBuilderArtifactContents(input) {
168
148
  };
169
149
  }
170
150
  export function buildContextBuilderManifestDraft(input) {
151
+ const allowSecrets = input.allowSecrets ?? true;
171
152
  const internalCaps = contextBuilderInternalCapsForManifest(input.internal_caps);
172
153
  const omissions = boundContextBuilderOmissions([...(input.omissions ?? [])], internalCaps);
173
154
  const warnings = boundContextBuilderWarnings([...(input.warnings ?? [])], internalCaps);
@@ -175,21 +156,48 @@ export function buildContextBuilderManifestDraft(input) {
175
156
  return {
176
157
  status,
177
158
  created_at: input.created_at,
178
- task: validateTaskForRendering(input.task),
179
- roots: input.roots.map(sanitizeRoot),
180
- inputs: sanitizeJsonValue(input.inputs),
159
+ task: validateTaskForRendering(input.task, allowSecrets),
160
+ // Echo the RESOLVED secret profile (per-call > workflow > env > true) so
161
+ // artifact-only readers do not have to infer it from omission behavior.
162
+ // Additive: manifest validation is required-key based and every known
163
+ // reader picks named keys.
164
+ allow_secrets_resolved: allowSecrets,
165
+ roots: input.roots.map((root) => sanitizeRoot(root, allowSecrets)),
166
+ inputs: sanitizeJsonValue(input.inputs, allowSecrets),
181
167
  budget: { ...input.budget },
182
168
  internal_caps: internalCaps,
183
169
  token_counting: contextBuilderTokenCountingMethod(),
184
170
  selected_context: input.selected_context
185
- .map(sanitizeSelectedFile)
171
+ .map((file) => sanitizeSelectedFile(file, allowSecrets))
186
172
  .map(stripSelectedContent),
187
173
  omissions,
188
174
  warnings,
189
175
  metrics: { ...input.metrics },
176
+ ...(input.search_coverage == null
177
+ ? {}
178
+ : {
179
+ search_coverage: sanitizeSearchCoverage(input.search_coverage, allowSecrets),
180
+ }),
181
+ ...(input.coverage == null ? {} : { coverage: input.coverage }),
182
+ ...(input.parent_run_id == null
183
+ ? {}
184
+ : { parent_run_id: input.parent_run_id }),
185
+ ...(input.refine_ops == null
186
+ ? {}
187
+ : { refine_ops: input.refine_ops.map((op) => ({ ...op })) }),
188
+ ...(input.file_fingerprints == null
189
+ ? {}
190
+ : {
191
+ file_fingerprints: input.file_fingerprints.map((fingerprint) => ({
192
+ ...fingerprint,
193
+ })),
194
+ }),
190
195
  ...(input.delegate == null
191
196
  ? {}
192
197
  : { delegate: cloneContextBuilderDelegateReceiptMetadata(input.delegate) }),
198
+ ...(input.observability == null
199
+ ? {}
200
+ : { observability: { ...input.observability } }),
193
201
  ...(input.debug === true
194
202
  ? {
195
203
  debug_diagnostics: boundDebugDiagnostics(input.debug_diagnostics, internalCaps),
@@ -204,6 +212,9 @@ export async function writeContextBuilderFinalArtifacts(storage, input, options
204
212
  ...(options.signal == null ? {} : { signal: options.signal }),
205
213
  ...(options.ownerNonce == null ? {} : { ownerNonce: options.ownerNonce }),
206
214
  ...(options.ops == null ? {} : { ops: options.ops }),
215
+ ...(options.beforeManifestCommit == null
216
+ ? {}
217
+ : { beforeManifestCommit: options.beforeManifestCommit }),
207
218
  });
208
219
  const internalCaps = contextBuilderInternalCapsForManifest(input.internal_caps);
209
220
  const warnings = boundContextBuilderWarnings(input.warnings == null ? [] : [...input.warnings], internalCaps);
@@ -241,7 +252,8 @@ function renderContentsWithStableByteMetric(input) {
241
252
  return { contents, metrics };
242
253
  }
243
254
  function buildContextBuilderSummary(input) {
244
- const selected = input.selected_context.map(sanitizeSelectedFile);
255
+ const allowSecrets = input.allowSecrets ?? true;
256
+ const selected = input.selected_context.map((file) => sanitizeSelectedFile(file, allowSecrets));
245
257
  const omissions = reconcileSelectedOmissions(filterTaskPresentationOmissions(boundContextBuilderOmissions([...(input.omissions ?? [])], contextBuilderInternalCapsForManifest(input.internal_caps)), input.task), selected);
246
258
  const openQuestions = buildPresentationOpenQuestions(boundOpenQuestions(input.open_questions ?? []), selected, omissions);
247
259
  return {
@@ -249,28 +261,38 @@ function buildContextBuilderSummary(input) {
249
261
  run_id: input.run_id,
250
262
  created_at: input.created_at,
251
263
  status: reduceContextBuilderStatus(buildContextBuilderStatusEvidence(input)),
264
+ // Mirror of the manifest's resolved secret-profile echo, so summary-only
265
+ // readers get the same explicit benchmark/run profile.
266
+ allow_secrets_resolved: allowSecrets,
252
267
  token_counting: contextBuilderTokenCountingMethod(),
253
268
  metrics: { ...input.metrics },
254
269
  selected_file_count: input.selected_context.length,
255
270
  warning_count: input.warnings?.length ?? 0,
256
271
  omission_count: omissions.length,
257
272
  open_questions: openQuestions,
273
+ ...(input.search_coverage == null
274
+ ? {}
275
+ : {
276
+ search_coverage: sanitizeSearchCoverage(input.search_coverage, allowSecrets),
277
+ }),
258
278
  ...(input.delegate == null
259
279
  ? {}
260
280
  : { delegate: cloneContextBuilderDelegateReceiptMetadata(input.delegate) }),
261
281
  };
262
282
  }
263
- function sanitizeRoot(root) {
283
+ function sanitizeRoot(root, allowSecrets) {
264
284
  return {
265
285
  root_index: root.root_index,
266
- ...(root.input_path == null ? {} : { input_path: safePath(root.input_path) }),
267
- path: safePath(root.path),
286
+ ...(root.input_path == null
287
+ ? {}
288
+ : { input_path: safePath(root.input_path, allowSecrets) }),
289
+ path: safePath(root.path, allowSecrets),
268
290
  };
269
291
  }
270
- function sanitizeTreeFile(file) {
292
+ function sanitizeTreeFile(file, allowSecrets) {
271
293
  return {
272
294
  root_index: file.root_index,
273
- path: safePath(file.path),
295
+ path: safePath(file.path, allowSecrets),
274
296
  size_bytes: file.size_bytes,
275
297
  eligible: file.eligible,
276
298
  content_eligible: file.content_eligible,
@@ -279,41 +301,65 @@ function sanitizeTreeFile(file) {
279
301
  : { omission_reason: sanitizeDiagnosticText(file.omission_reason) }),
280
302
  };
281
303
  }
282
- function sanitizeSearchRecord(record) {
304
+ function sanitizeSearchRecord(record, allowSecrets) {
283
305
  return {
284
306
  root_index: record.root_index,
285
- path: safePath(record.path),
307
+ path: safePath(record.path, allowSecrets),
286
308
  line: record.line,
287
309
  column: record.column,
288
- search_term: sanitizeDiagnosticText(record.search_term),
310
+ search_term: sanitizeDiagnosticText(record.search_term, allowSecrets),
289
311
  search_term_index: record.search_term_index,
290
- preview: safeContent(record.preview),
312
+ preview: safeContent(record.preview, allowSecrets),
313
+ };
314
+ }
315
+ // Defensive bound matching the public caller search-term cap; T1's coverage
316
+ // rows are already bounded by it, so this only guards a malformed producer.
317
+ const MAX_SEARCH_COVERAGE_CALLER_TERM_ROWS = 20;
318
+ function sanitizeSearchCoverage(coverage, allowSecrets) {
319
+ return {
320
+ eligible_candidate_count: coverage.eligible_candidate_count,
321
+ searched_candidate_count: coverage.searched_candidate_count,
322
+ unsearched_candidate_count: coverage.unsearched_candidate_count,
323
+ caller_term_count: coverage.caller_term_count,
324
+ derived_term_count: coverage.derived_term_count,
325
+ caller_record_count: coverage.caller_record_count,
326
+ derived_record_count: coverage.derived_record_count,
327
+ caller_coverage_complete: coverage.caller_coverage_complete,
328
+ caller_terms: coverage.caller_terms
329
+ .slice(0, MAX_SEARCH_COVERAGE_CALLER_TERM_ROWS)
330
+ .map((term) => ({
331
+ term: sanitizeDiagnosticText(term.term, allowSecrets),
332
+ search_term_index: term.search_term_index,
333
+ hit_count: term.hit_count,
334
+ scan_complete: term.scan_complete,
335
+ })),
291
336
  };
292
337
  }
293
- function sanitizeSliceRecord(record) {
338
+ function sanitizeSliceRecord(record, allowSecrets) {
294
339
  return {
295
340
  root_index: record.root_index,
296
- path: safePath(record.path),
341
+ path: safePath(record.path, allowSecrets),
297
342
  start_line: record.start_line,
298
343
  end_line: record.end_line,
299
344
  source: record.source,
300
- content: safeContent(record.content),
345
+ content: safeContent(record.content, allowSecrets),
301
346
  ...(record.search_term_index == null
302
347
  ? {}
303
348
  : { search_term_index: record.search_term_index }),
304
349
  };
305
350
  }
306
- function sanitizeSelectedFile(file) {
351
+ function sanitizeSelectedFile(file, allowSecrets) {
307
352
  return {
308
353
  root_index: file.root_index,
309
- path: safePath(file.path),
354
+ path: safePath(file.path, allowSecrets),
310
355
  ranges: file.ranges.map(sanitizeRange),
311
- content: safeContent(file.content),
356
+ content: safeContent(file.content, allowSecrets),
312
357
  bytes: file.bytes,
313
358
  estimated_tokens: file.estimated_tokens > 0
314
359
  ? file.estimated_tokens
315
360
  : estimateContextBuilderTokens(file.content),
316
361
  source: file.source,
362
+ ...(file.disposition == null ? {} : { disposition: file.disposition }),
317
363
  };
318
364
  }
319
365
  function sanitizeRange(range) {
@@ -330,6 +376,7 @@ function stripSelectedContent(file) {
330
376
  bytes: file.bytes,
331
377
  estimated_tokens: file.estimated_tokens,
332
378
  source: file.source,
379
+ ...(file.disposition == null ? {} : { disposition: file.disposition }),
333
380
  };
334
381
  }
335
382
  function renderSelectedFilesList(input) {
@@ -348,16 +395,13 @@ function renderSelectedFilesList(input) {
348
395
  })
349
396
  .join("\n");
350
397
  return hiddenCount > 0
351
- ? `${rendered}\n- ${hiddenCount} more selected files are listed in manifest.json and slices.jsonl.`
398
+ ? `${rendered}\n- ${hiddenCount} more selected files are listed in manifest.json and selected-context.jsonl.`
352
399
  : rendered;
353
400
  }
354
- function selectedFilePurpose(task, file, search) {
355
- const action = taskActionCue(task);
401
+ function selectedFilePurpose(_task, file, search) {
356
402
  const source = file.source === "focus" ? "requested focus evidence" : "matched task anchors";
357
- const role = taskSpecificFileRole(task, file.path);
358
403
  const clauses = [
359
- action == null ? source : `${source} for ${action}`,
360
- ...(role == null ? [] : [role]),
404
+ source,
361
405
  pathPurpose(file.path),
362
406
  rangePurpose(file.ranges),
363
407
  ];
@@ -396,17 +440,7 @@ function selectedFileRangeNotes(task, file, search) {
396
440
  });
397
441
  }
398
442
  function rangeEvidenceSummary(input) {
399
- const semantic = taskSpecificRangeRole(input.task, input.path);
400
443
  const hitSummary = rangeHitSummary(input.rangeHits ?? []);
401
- if (semantic != null) {
402
- return [
403
- semantic,
404
- ...(hitSummary == null ? [] : [hitSummary]),
405
- ...(input.symbols.length === 0
406
- ? []
407
- : [`key symbols ${input.symbols.slice(0, 4).join(", ")}`]),
408
- ].join("; ");
409
- }
410
444
  if (hitSummary != null) {
411
445
  return hitSummary;
412
446
  }
@@ -463,6 +497,22 @@ function splitSelectedFileContentByRange(file) {
463
497
  }
464
498
  return output;
465
499
  }
500
+ function renderSelectedContextJsonl(selected) {
501
+ return renderJsonl(selected.flatMap((file) => {
502
+ const rangeBodies = splitSelectedFileContentByRange(file);
503
+ return file.ranges.map((range, index) => ({
504
+ root_index: file.root_index,
505
+ path: file.path,
506
+ start_line: range.start_line,
507
+ end_line: range.end_line,
508
+ source: file.source,
509
+ ...(file.disposition == null ? {} : { disposition: file.disposition }),
510
+ content: isCodemapOnlyDisposition(file.disposition)
511
+ ? ""
512
+ : (rangeBodies[index] ?? []).join("\n"),
513
+ }));
514
+ }));
515
+ }
466
516
  function splitTextLinesForBundle(text) {
467
517
  if (text.length === 0) {
468
518
  return [];
@@ -529,222 +579,6 @@ function selectedFileSearchTerms(file, search) {
529
579
  }
530
580
  return terms;
531
581
  }
532
- function taskSpecificFileRole(task, path) {
533
- const normalizedTask = task.toLowerCase();
534
- const branch = classifyTaskBranch(normalizedTask);
535
- const normalized = path.toLowerCase();
536
- if (branch === "runtime") {
537
- if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
538
- return "gates model exposure and Codex availability from provider metadata";
539
- }
540
- if (normalized.includes("/runtime/resolver.py") || normalized.includes("ai_model.py")) {
541
- return "routes model/provider metadata into Claude vs Codex runtime selection";
542
- }
543
- if (normalized.includes("thinkingmodeselector") || normalized.includes("permissionmodeselector")) {
544
- return "defines visible effort/permission controls whose values feed runtime settings";
545
- }
546
- if (normalized.includes("message-input") || normalized.includes("usechatruntime")) {
547
- return "captures frontend runtime-setting controls";
548
- }
549
- if (normalized.includes("messagequeue") || normalized.includes("queue")) {
550
- return "checks queued-send persistence and retry behavior";
551
- }
552
- if (normalized.endsWith("/api/endpoints/chat.py")) {
553
- return "validates backend chat request ingestion";
554
- }
555
- if (normalized.endsWith("/services/chat.py")) {
556
- return "connects persisted settings to completion dispatch";
557
- }
558
- if (normalized.includes("test_claude_agent")) {
559
- return "tests Claude permission and effort translation semantics";
560
- }
561
- if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
562
- return "anchors provider-specific permission and effort semantics";
563
- }
564
- }
565
- if (branch === "review") {
566
- if (normalized.includes("codex_approval") || normalized.includes("permissions")) {
567
- return "anchors approval and permission contract risk";
568
- }
569
- if (normalized.includes("/runtime/") || normalized.includes("runtime_session")) {
570
- return "anchors Codex runtime/session behavior";
571
- }
572
- if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
573
- return "anchors frontend provider and attachment contract";
574
- }
575
- }
576
- if (branch === "debug") {
577
- if (normalized.includes("rightpaneldevtools") || normalized.includes("devtoolsnavstore")) {
578
- return "anchors trace row identity and navigation state";
579
- }
580
- if (normalized.includes("trace_archive") || normalized.includes("devtools.py")) {
581
- return "anchors backend trace entry shape";
582
- }
583
- if (normalized.includes("chat-window") || normalized.includes("message") || normalized.includes("tool")) {
584
- return "anchors message or tool highlight target resolution";
585
- }
586
- }
587
- return undefined;
588
- }
589
- function taskSpecificRangeRole(task, path) {
590
- const normalizedTask = task.toLowerCase();
591
- const branch = classifyTaskBranch(normalizedTask);
592
- const normalized = path.toLowerCase();
593
- if (branch === "runtime") {
594
- if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
595
- return "checks model exposure and provider capability gating for Codex runtime";
596
- }
597
- if (normalized.includes("/runtime/resolver.py") || normalized.includes("ai_model.py")) {
598
- return "resolves selected model metadata into the provider runtime boundary";
599
- }
600
- if (normalized.includes("modelprovider.ts")) {
601
- return "maps frontend model/provider capability checks driving runtime controls";
602
- }
603
- if (normalized.includes("thinkingmodeselector") || normalized.includes("permissionmodeselector")) {
604
- return "owns the visible effort/permission mode choices and stale-value reset behavior";
605
- }
606
- if (normalized.includes("input.tsx")) {
607
- return "owns composer send wiring and runtime-setting handoff from UI state";
608
- }
609
- if (normalized.includes("chatpage")) {
610
- return "connects chat creation, initial prompt dispatch, and page-level runtime state";
611
- }
612
- if (normalized.includes("composerruntimecontrols")) {
613
- return "defines visible permission and effort controls";
614
- }
615
- if (normalized.includes("inputsettingspopover")) {
616
- return "maps stored runtime settings into composer controls";
617
- }
618
- if (normalized.includes("usechatruntime")) {
619
- return "reads and writes chat-scoped runtime snapshots";
620
- }
621
- if (normalized.includes("usemessageactions")) {
622
- return "serializes runtime settings on normal send and queued append";
623
- }
624
- if (normalized.includes("chatservice")) {
625
- return "defines create-chat runtime fields and send/queue compatibility fields";
626
- }
627
- if (normalized.includes("messagequeue") || normalized.includes("queueservice")) {
628
- return "preserves permission and effort across local queue replay";
629
- }
630
- if (normalized.includes("streamservice")) {
631
- return "streams backend responses after send/queue runtime settings are accepted";
632
- }
633
- if (normalized.endsWith("/api/endpoints/chat.py")) {
634
- return "parses create-chat runtime fields and send/queue compatibility fields";
635
- }
636
- if (normalized.endsWith("/models/schemas/chat.py") || normalized.endsWith("chat.types.ts")) {
637
- return "defines the request/response contract fields that must not drift";
638
- }
639
- if (normalized.endsWith("queue.types.ts")) {
640
- return "defines queued-send payload fields carried through replay";
641
- }
642
- if (normalized.endsWith("/services/chat.py")) {
643
- return "persists runtime snapshots and redispatches pending initial prompts";
644
- }
645
- if (normalized.includes("test_queue")) {
646
- return "guards queue replay of runtime permission and effort payloads";
647
- }
648
- if (normalized.includes("test_effort_level") || normalized.includes("test_permission_mode")) {
649
- return "guards backend validation of runtime setting values";
650
- }
651
- if (normalized.includes("test_codex_runtime") || normalized.includes("test_codex_effort")) {
652
- return "guards Codex runtime translation and migration behavior";
653
- }
654
- if (normalized.includes("usepermissionrequest")) {
655
- return "shows frontend permission request consumption during runtime approval flows";
656
- }
657
- if (normalized.includes("usesettingsqueries")) {
658
- return "shows how persisted settings are fetched back into runtime controls";
659
- }
660
- if (normalized.includes("test_claude_agent")) {
661
- return "verifies Claude permission mapping, bypass sentinel handling, and effort levels";
662
- }
663
- if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
664
- return "translates normalized runtime settings into provider-specific options";
665
- }
666
- }
667
- if (branch === "review") {
668
- if (normalized.includes("codex_approval")) {
669
- return "maps SDK approval requests to permission API payloads and timeouts";
670
- }
671
- if (normalized.includes("permission_manager") || normalized.includes("permissions")) {
672
- return "stores, fetches, and resolves approval requests across Redis and HTTP";
673
- }
674
- if (normalized.includes("runtime_session")) {
675
- return "decides whether Codex runtime sessions resume or start fresh";
676
- }
677
- if (normalized.includes("codex_stream_processor")) {
678
- return "accounts for Codex stream lifecycle and usage snapshots";
679
- }
680
- if (normalized.includes("/runtime/codex.py")) {
681
- return "owns Codex runtime process lifecycle, approval callbacks, and stream reads";
682
- }
683
- if (normalized.includes("/runtime/resolver.py")) {
684
- return "decides Codex-vs-Claude runtime routing from model/provider metadata";
685
- }
686
- if (normalized.includes("/runtime/base.py") || normalized.includes("/runtime/claude.py")) {
687
- return "anchors shared runtime contracts used to compare Codex behavior";
688
- }
689
- if (normalized.includes("model_provider_auth") || normalized.includes("ai_models.py")) {
690
- return "checks provider metadata and Codex availability gates";
691
- }
692
- if (normalized.includes("test_ai_models_codex_gate") || normalized.includes("test_model_provider_auth")) {
693
- return "guards provider gating and metadata fail-closed behavior";
694
- }
695
- if (normalized.includes("modelprovider.ts")) {
696
- return "maps frontend model/provider capability checks";
697
- }
698
- if (normalized.includes("input.tsx")) {
699
- return "owns primary send-site attachment/runtime handoff behavior";
700
- }
701
- if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
702
- return "guards frontend attachment behavior for Codex provider constraints";
703
- }
704
- if (normalized.includes("chatservice") || normalized.endsWith("/api/endpoints/chat.py") || normalized.endsWith("/services/chat.py")) {
705
- return "checks chat ingress and persistence paths touched by runtime/provider decisions";
706
- }
707
- }
708
- if (branch === "debug") {
709
- if (normalized.includes("rightpaneldevtools")) {
710
- return "tracks filtered rows, virtualizer keys, and clicked trace-entry identity";
711
- }
712
- if (normalized.includes("devtools-helpers")) {
713
- return "classifies trace entries into message and tool navigation targets";
714
- }
715
- if (normalized.includes("devtoolsnavstore")) {
716
- return "queues and delivers right-panel navigation requests";
717
- }
718
- if (normalized.includes("chat-window/chat")) {
719
- return "resolves pending navigation against cached messages and scroll state";
720
- }
721
- if (normalized.includes("message-bubble/message.tsx")) {
722
- return "wraps message highlight lifecycle after navigation resolves";
723
- }
724
- if (normalized.includes("messagecacheorder")) {
725
- return "keeps cached messages in stable chronological order for lookup";
726
- }
727
- if (normalized.includes("messagerenderer")) {
728
- return "turns message and tool identifiers into highlightable anchors";
729
- }
730
- if (normalized.includes("toolcard")) {
731
- return "expands nested tool cards and exposes anchor ids";
732
- }
733
- if (normalized.includes("usedevtoolsqueries") || normalized.includes("querykeys")) {
734
- return "defines trace query cache keys and freshness boundaries";
735
- }
736
- if (normalized.includes("right-panel-devtools.test")) {
737
- return "guards filtered row click-through and trace navigation behavior";
738
- }
739
- if (normalized.includes("devtoolsnavstore.test")) {
740
- return "guards navigation request delivery and highlight clearing behavior";
741
- }
742
- if (normalized.includes("devtools.py") || normalized.includes("session_trace")) {
743
- return "defines backend trace/archive response shape and expiry fallback";
744
- }
745
- }
746
- return undefined;
747
- }
748
582
  function pathPurpose(path) {
749
583
  const normalized = path.toLowerCase();
750
584
  const basename = normalized.split("/").at(-1) ?? normalized;
@@ -774,1478 +608,158 @@ function languagePurpose(basename) {
774
608
  return "TSX";
775
609
  }
776
610
  if (basename.endsWith(".ts")) {
777
- return "TypeScript";
778
- }
779
- if (basename.endsWith(".jsx")) {
780
- return "JSX";
781
- }
782
- if (basename.endsWith(".js")) {
783
- return "JavaScript";
784
- }
785
- if (basename.endsWith(".py")) {
786
- return "Python";
787
- }
788
- if (basename.endsWith(".rs")) {
789
- return "Rust";
790
- }
791
- if (basename.endsWith(".md") || basename.endsWith(".mdx")) {
792
- return "Markdown";
793
- }
794
- if (basename.endsWith(".json")) {
795
- return "JSON";
796
- }
797
- if (basename.endsWith(".yml") || basename.endsWith(".yaml")) {
798
- return "YAML";
799
- }
800
- return "file";
801
- }
802
- function isTestPath(path, basename) {
803
- return (path.startsWith("test/") ||
804
- path.startsWith("tests/") ||
805
- path.includes("/test/") ||
806
- path.includes("/tests/") ||
807
- path.includes("/__tests__/") ||
808
- basename.includes(".test.") ||
809
- basename.includes(".spec.") ||
810
- basename.startsWith("test_"));
811
- }
812
- function rangePurpose(ranges) {
813
- if (ranges.length === 0) {
814
- return "full-file context";
815
- }
816
- const rendered = ranges
817
- .slice(0, 3)
818
- .map((range) => range.start_line === range.end_line
819
- ? `${range.start_line}`
820
- : `${range.start_line}-${range.end_line}`);
821
- const suffix = ranges.length > rendered.length ? ` +${ranges.length - rendered.length}` : "";
822
- return `ranges ${rendered.join(", ")}${suffix}`;
823
- }
824
- function renderTaskGuidance(input) {
825
- const normalizedTask = input.task.toLowerCase();
826
- const branch = classifyTaskBranch(normalizedTask);
827
- const selected = selectedPathSet(input.selected);
828
- if (branch === "runtime") {
829
- return [
830
- `- Top targets: ${guidanceCoverage(selected, input.showRootLabels, [
831
- "frontend/src/hooks/useChatRuntimeSettings.ts",
832
- "frontend/src/hooks/useMessageActions.ts",
833
- "frontend/src/store/messageQueueStore.ts",
834
- "backend/app/api/endpoints/chat.py",
835
- "backend/app/services/chat.py",
836
- "backend/app/services/queue.py",
837
- "backend/app/services/streaming/orchestrator.py",
838
- "backend/app/services/runtime/codex.py",
839
- "backend/app/services/runtime/claude.py",
840
- ])}.`,
841
- "- Flow to verify: composer controls and chat-scoped settings produce permission/effort values; create-chat sends runtime_permission_mode/runtime_effort_level, normal send and queue paths send permission_mode/effort_level compatibility fields, and backend code normalizes/persists one runtime snapshot before dispatch.",
842
- "- Provider semantics to pin down: Codex effort values collapse to its advertised levels, Claude permission modes can map to bypass-style options, legacy/internal sentinel values must normalize before persistence, and unsupported settings must fail loudly rather than vanish.",
843
- "- Validation checklist: create-chat stores runtime_permission_mode/runtime_effort_level; queued local append and backend upsert preserve permission_mode/effort_level until API/service normalization; pending initial prompt redispatch reads the stored snapshot; provider adapter tests cover translation drift.",
844
- ].join("\n");
845
- }
846
- if (branch === "review") {
847
- return [
848
- `- Top review targets: ${guidanceCoverage(selected, input.showRootLabels, [
849
- "backend/app/services/runtime/resolver.py",
850
- "backend/app/services/runtime/codex.py",
851
- "backend/app/services/runtime/codex_approval.py",
852
- "backend/app/services/runtime/codex_stream_processor.py",
853
- "backend/app/services/runtime_session_service.py",
854
- "backend/app/api/endpoints/permissions.py",
855
- "frontend/src/hooks/usePermissionRequest.ts",
856
- "frontend/src/hooks/useMessageActions.ts",
857
- ])}.`,
858
- "- Review scope: this packet contains selected current-source context, not a git diff; separate confirmed source facts, likely risks, and needs-verification items before labeling anything a finding.",
859
- "- Likely risks to test first: fail-open runtime routing when Codex support metadata is absent; wrong-chat or stale RuntimeSessionService resume decisions; approval timeout/Redis TTL mismatches; denied or expired permission responses mutating the wrong pending request; frontend file-upload filtering drifting from provider capabilities.",
860
- "- Evidence map: pair runtime/session files with codex_approval.py and permission endpoints, then check frontend hooks/tests for the user-visible approval and attachment behavior.",
861
- "- Missing-test checklist: live SDK approval payload shape, timeout cleanup of reader tasks, cross-chat permission denial, expired permission UI behavior, and provider-specific attachment filtering.",
862
- ].join("\n");
863
- }
864
- if (branch === "debug") {
865
- return [
866
- `- Top debug targets: ${guidanceCoverage(selected, input.showRootLabels, [
867
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
868
- "frontend/src/components/right-panel/devtools-helpers.ts",
869
- "frontend/src/store/devtoolsNavStore.ts",
870
- "frontend/src/components/chat/chat-window/Chat.tsx",
871
- "frontend/src/utils/messageCacheOrder.ts",
872
- "frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
873
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
874
- "backend/app/api/endpoints/devtools.py",
875
- ])}.`,
876
- "- Confirmed source fact to preserve: selected RightPanelDevtools source may already carry originalIndex from the filtered row into TraceEntry and handleEntryClick, so originalIndex/filter-index alone is not proven as the main root cause.",
877
- "- Likely checks: deferred search/navigation staleness, virtualizer key reuse, layout-effect timing after filter/search changes, cached-message dedupe before target resolution, and MessageRenderer/ToolCard anchor expansion.",
878
- "- Validation checklist: filtered views click the intended message/tool, virtualized rows keep stable keys after filter changes, layout effects remeasure at the right time, cached message flattening preserves the target, and regression tests cover both message and nested tool anchors.",
879
- ].join("\n");
880
- }
881
- return "- Use focus files first, then closure contracts and tests; material omissions or open questions should be resolved before editing.";
882
- }
883
- function guidanceCoverage(selected, showRootLabels, expectedPaths) {
884
- const present = expectedPaths
885
- .filter((path) => selected.has(path))
886
- .slice(0, 10)
887
- .map((path) => renderRootPrefix(0, showRootLabels) + path);
888
- return present.length === 0
889
- ? "no expected top target selected; inspect selected files and omissions"
890
- : present.join(", ");
891
- }
892
- function renderContextSynthesis(input) {
893
- const lines = [];
894
- const selectedPaths = new Set(input.selected.map((file) => `${file.root_index}\u0000${file.path}`));
895
- const selectedSearch = input.search.filter((record) => selectedPaths.has(`${record.root_index}\u0000${record.path}`));
896
- const selectedTerms = [
897
- ...new Set(selectedSearch.map((record) => record.search_term).filter(Boolean)),
898
- ].slice(0, 8);
899
- const materialOmissions = input.omissions.filter(isMaterialOmission);
900
- const taskShape = renderTaskShapeSummary({
901
- task: input.task,
902
- selected: input.selected,
903
- selectedSearch,
904
- materialOmissions,
905
- showRootLabels: input.showRootLabels,
906
- });
907
- lines.push(`- Coverage: ${input.metrics.files_selected} selected files, ${input.omissions.length} recorded omissions, ${input.metrics.estimated_tokens} estimated tokens.`);
908
- lines.push(...taskShape);
909
- if (selectedTerms.length > 0) {
910
- lines.push(`- Search evidence: selected-file hits cover ${selectedTerms.length} task anchors; exact hit lines are in search.jsonl.`);
911
- }
912
- const groups = renderSelectedPathGroups(input.selected);
913
- if (groups.length > 0) {
914
- lines.push(`- Selected areas: ${groups.join("; ")}.`);
915
- }
916
- const closure = closureFiles(input.selected, input.showRootLabels);
917
- if (closure.length > 0) {
918
- lines.push(`- Contract and closure files: ${closure.join(", ")}.`);
919
- }
920
- if (materialOmissions.length > 0) {
921
- const rendered = materialOmissions
922
- .slice(0, 5)
923
- .map((omission) => {
924
- return `${humanOmissionReason(omission.reason)}${renderOmissionPathSuffix(omission.paths, 3)}`;
925
- })
926
- .join("; ");
927
- lines.push(`- Material follow-ups: ${rendered}.`);
928
- }
929
- else {
930
- lines.push("- Material follow-ups: none recorded.");
931
- }
932
- return lines.join("\n");
933
- }
934
- function renderTaskShapeSummary(input) {
935
- const normalizedTask = input.task.toLowerCase();
936
- const selected = selectedPathSet(input.selected);
937
- const branch = classifyTaskBranch(normalizedTask);
938
- const taskBullets = branch === "runtime"
939
- ? runtimeSettingsSynthesisBullets(normalizedTask, selected)
940
- : branch === "review"
941
- ? codexApprovalsSynthesisBullets(normalizedTask, selected)
942
- : branch === "debug"
943
- ? devtoolsTraceSynthesisBullets(normalizedTask, selected)
944
- : [
945
- ...runtimeSettingsSynthesisBullets(normalizedTask, selected),
946
- ...codexApprovalsSynthesisBullets(normalizedTask, selected),
947
- ...devtoolsTraceSynthesisBullets(normalizedTask, selected),
948
- ];
949
- if (taskBullets.length > 0) {
950
- return taskBullets;
951
- }
952
- const action = taskActionCue(input.task) ?? "not inferred";
953
- const relationshipCues = relationshipCuesFromSelected(input.selected);
954
- const riskCues = riskCuesFromContext({
955
- selected: input.selected,
956
- selectedSearch: input.selectedSearch,
957
- materialOmissions: input.materialOmissions,
958
- showRootLabels: input.showRootLabels,
959
- });
960
- return [
961
- `- Task shape: action=${action}; relationships=${relationshipCues}; risks=${riskCues}.`,
962
- ];
963
- }
964
- function renderTopMustReadRanges(input) {
965
- if (input.selected.length === 0) {
966
- return "- No selected ranges available.";
967
- }
968
- const candidates = topMustReadRangeCandidates(input)
969
- .sort((left, right) => right.score - left.score || left.order - right.order)
970
- .slice(0, 8);
971
- return candidates
972
- .map((candidate, index) => {
973
- const header = `${index + 1}. ${renderRootPrefix(candidate.file.root_index, input.showRootLabels)}${candidate.file.path}:${singleRangeLabel(candidate.range)} - ${candidate.reason}`;
974
- const snippet = renderTopMustReadSnippet(candidate, index, input.task);
975
- return snippet.length === 0 ? header : `${header}\n\n${snippet}`;
976
- })
977
- .join("\n");
978
- }
979
- function topMustReadRangeCandidates(input) {
980
- const branch = classifyTaskBranch(input.task.toLowerCase());
981
- const branchLabel = `${branch === "generic" ? "general" : branch} task`;
982
- const candidates = [];
983
- let order = 0;
984
- for (const file of input.selected) {
985
- const searchTerms = selectedFileSearchTerms(file, input.search);
986
- const searchHitCount = input.search.filter((record) => record.root_index === file.root_index && record.path === file.path).length;
987
- const sourceLines = splitSelectedFileContentByRange(file);
988
- for (const [rangeIndex, range] of file.ranges.entries()) {
989
- const symbols = symbolsFromLines(sourceLines[rangeIndex] ?? []);
990
- const semantic = taskSpecificRangeRole(input.task, file.path)
991
- ?? taskSpecificFileRole(input.task, file.path);
992
- const summary = semantic
993
- ?? rangeEvidenceSummary({
994
- task: input.task,
995
- path: file.path,
996
- source: file.source,
997
- symbols,
998
- searchTerms,
999
- });
1000
- const score = (file.source === "focus" ? 40 : 20)
1001
- + (semantic == null ? 0 : 30)
1002
- + Math.min(searchHitCount, 6) * 3
1003
- + (pathLooksLikeTest(file.path) ? 8 : 0)
1004
- + (branch === "generic" ? 0 : 4)
1005
- - rangeIndex;
1006
- const hitText = searchHitCount === 0
1007
- ? ""
1008
- : `; ${searchHitCount} selected-file search hit${searchHitCount === 1 ? "" : "s"}`;
1009
- candidates.push({
1010
- file,
1011
- range,
1012
- score,
1013
- order,
1014
- reason: `${branchLabel}; ${summary}${hitText}`,
1015
- });
1016
- order += 1;
1017
- }
1018
- }
1019
- return candidates;
1020
- }
1021
- function singleRangeLabel(range) {
1022
- return range.start_line === range.end_line
1023
- ? `${range.start_line}`
1024
- : `${range.start_line}-${range.end_line}`;
1025
- }
1026
- function renderTopMustReadSnippet(candidate, index, task) {
1027
- if (index >= 3) {
1028
- return "";
1029
- }
1030
- const rangeIndex = candidate.file.ranges.findIndex((range) => range.start_line === candidate.range.start_line &&
1031
- range.end_line === candidate.range.end_line);
1032
- const rangeLines = splitSelectedFileContentByRange(candidate.file)[rangeIndex] ?? [];
1033
- const numbered = topMustReadSnippetLines(candidate.range, rangeLines, evidenceTermsForTask(task), candidate.file.path);
1034
- if (numbered.length === 0) {
1035
- return "";
1036
- }
1037
- return ["```text", ...numbered, "```"].join("\n");
1038
- }
1039
- function topMustReadSnippetLines(range, rangeLines, terms, path) {
1040
- const evidenceLines = rangeLines
1041
- .map((line, lineIndex) => ({
1042
- lineNumber: range.start_line + lineIndex,
1043
- rangeIndex: 0,
1044
- text: line.trimEnd(),
1045
- }))
1046
- .filter((line) => line.text.trim().length > 0);
1047
- const scored = evidenceLines
1048
- .map((line, index) => ({
1049
- index,
1050
- score: evidenceLineScore(line.text, terms, path),
1051
- }))
1052
- .filter((candidate) => candidate.score > 0)
1053
- .sort((left, right) => right.score - left.score ||
1054
- evidenceLines[left.index].lineNumber -
1055
- evidenceLines[right.index].lineNumber);
1056
- if (scored.length > 0) {
1057
- const rendered = renderEvidenceLineWindows(mergedEvidenceWindows(evidenceLines, scored.slice(0, 2).map((candidate) => candidate.index)), evidenceLines)
1058
- .split("\n")
1059
- .filter((line) => line.length > 0)
1060
- .slice(0, 8);
1061
- if (rendered.length > 0) {
1062
- return rendered;
1063
- }
1064
- }
1065
- return evidenceLines
1066
- .slice(0, 8)
1067
- .map((line) => `${line.lineNumber}: ${trimEvidenceLine(line.text)}`);
1068
- }
1069
- function pathLooksLikeTest(path) {
1070
- return /(?:^|\/)(?:tests?|__tests__)\//.test(path) || /\.test\.[^.]+$/.test(path);
1071
- }
1072
- function runtimeSettingsSynthesisBullets(normalizedTask, selected) {
1073
- if (!mentionsAny(normalizedTask, ["runtime settings", "runtime_permission_mode", "runtime_effort_level", "queue dispatch"])) {
1074
- return [];
1075
- }
1076
- return [
1077
- `- Discovery/runtime settings: follow user-facing runtime controls through send/queue state into backend chat schemas, persistence, and runtime adapter dispatch. Selected coverage: ${selectedCoverage(selected, [
1078
- "frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
1079
- "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
1080
- "frontend/src/hooks/useChatRuntimeSettings.ts",
1081
- "frontend/src/hooks/useMessageActions.ts",
1082
- "frontend/src/services/chatService.ts",
1083
- "frontend/src/services/queueService.ts",
1084
- "backend/app/api/endpoints/chat.py",
1085
- "backend/app/models/schemas/chat.py",
1086
- "backend/app/services/runtime/codex.py",
1087
- "backend/app/services/runtime/resolver.py",
1088
- "backend/app/services/model_provider_auth.py",
1089
- "backend/app/services/claude_agent.py",
1090
- "frontend/src/components/chat/model-selector/ModelSelector.tsx",
1091
- "frontend/src/hooks/queries/useModelQueries.ts",
1092
- "frontend/src/utils/modelProvider.ts",
1093
- ])}.`,
1094
- "- Architecture: Composer controls and chat-scoped hooks produce permission/effort values; create-chat serializes runtime_permission_mode/runtime_effort_level; normal send and queued-send paths serialize permission_mode/effort_level compatibility fields; backend chat endpoints normalize them; ChatService and queue storage persist snapshots; workers/orchestrators hand normalized values to provider runtimes.",
1095
- "- Key invariants: create-chat must persist runtime_permission_mode/runtime_effort_level, queued sends must preserve permission_mode/effort_level through local append and backend upsert until normalization, pending initial prompts must redispatch with the stored runtime snapshot, and provider adapters must translate permission/effort semantics without silently dropping unsupported values.",
1096
- "- Likely risks to verify: payload names can drift between chatService/useMessageActions and chat.py schemas, ChatService can rehydrate defaults instead of the stored snapshot, queued work can replay stale effort names, and Claude/Codex adapters can diverge on permission semantics.",
1097
- "- Focused tests to add or inspect first: create-chat persistence, update_chat runtime snapshot persistence, queued send replay, pending initial prompt redispatch, and provider permission/effort translation.",
1098
- ];
1099
- }
1100
- function codexApprovalsSynthesisBullets(normalizedTask, selected) {
1101
- if (!mentionsAny(normalizedTask, ["code review", "approval", "codex approval", "runtime/session", "permission modes", "file upload"])) {
1102
- return [];
1103
- }
1104
- return [
1105
- `- Review/Codex approvals: map provider selection and Codex runtime session state to approval-bridge requests, permission APIs, and frontend approval consumers. Selected coverage: ${selectedCoverage(selected, [
1106
- "backend/app/services/runtime/codex.py",
1107
- "backend/app/services/runtime/codex_approval.py",
1108
- "backend/app/services/runtime_session_service.py",
1109
- "backend/app/api/endpoints/permissions.py",
1110
- "backend/app/models/schemas/permissions.py",
1111
- "frontend/src/hooks/usePermissionRequest.ts",
1112
- "frontend/src/utils/modelProvider.ts",
1113
- ])}.`,
1114
- "- Architecture: model/provider resolution chooses Codex runtime; RuntimeSessionService decides resume versus new session; CodexRuntime streams SDK events; approval callbacks normalize SDK requests into Redis-backed permission records; HTTP permission endpoints return decisions to frontend hooks.",
1115
- "- Review risk model: check fail-closed routing when Codex capability metadata is absent, wrong-session resume/upsert decisions, SDK approval normalization drift, Redis TTL and timeout coupling, denied/expired approval propagation, stuck reader tasks after permission timeout, and frontend provider/file-upload gating.",
1116
- "- Review plan: start with confirmed source facts, then likely Codex routing/session risks, then approval TTL/wait-window coupling, permission response ownership, and provider-specific attachment gating; call an item a finding only after checking the paired test coverage.",
1117
- "- Regression tests to expect: absent Codex support metadata fails closed, cross-chat denial cannot mutate another request, expired approvals clean up pending state, and live SDK approval payload shape remains normalized by the bridge.",
1118
- ];
1119
- }
1120
- function devtoolsTraceSynthesisBullets(normalizedTask, selected) {
1121
- if (!mentionsAny(normalizedTask, ["devtools", "trace row", "row-to-message", "click-through", "highlight"])) {
1122
- return [];
1123
- }
1124
- return [
1125
- `- Debugging/devtools trace mapping: connect backend trace/archive entries to right-panel row identity, devtools navigation state, chat cache lookup, and message/tool anchor expansion. Selected coverage: ${selectedCoverage(selected, [
1126
- "backend/app/api/endpoints/devtools.py",
1127
- "backend/app/services/session_trace_archive.py",
1128
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
1129
- "frontend/src/components/right-panel/devtools-helpers.ts",
1130
- "frontend/src/store/devtoolsNavStore.ts",
1131
- "frontend/src/components/chat/chat-window/Chat.tsx",
1132
- "frontend/src/hooks/queries/queryKeys.ts",
1133
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
1134
- ])}.`,
1135
- "- Architecture: backend trace entries carry durable ids/indexes; RightPanelDevtools filters and virtualizes those entries; click handlers publish a devtools navigation request; Chat resolves it against cached messages; MessageRenderer and ToolCard expose the final message/tool anchors.",
1136
- "- Confirmed source fact: if selected RightPanelDevtools code already carries originalIndex alongside filtered rows, do not blame originalIndex/filter-index as the main root cause without a failing repro.",
1137
- "- Likely checks: deferred search/navigation staleness, virtualizer getItemKey/key reuse, layout-effect remeasure timing after filter/search changes, cached message lookup with duplicate timestamps, and nested tool-card expansion from a devtools navigation request.",
1138
- "- Focused tests to add or inspect first: filtered trace row click-through, row key stability after filter changes, layout-effect remeasure/reset behavior, cached message dedupe, and nested tool-card expansion from a devtools navigation request.",
1139
- ];
1140
- }
1141
- function renderGeneratedAnswer(input) {
1142
- const normalizedTask = input.task.toLowerCase();
1143
- const branch = classifyTaskBranch(normalizedTask);
1144
- const completenessGate = renderCompletenessGate(input.omissions);
1145
- const lineGroundedActionMap = renderLineGroundedActionMap({
1146
- task: input.task,
1147
- selected: input.selected,
1148
- showRootLabels: input.showRootLabels,
1149
- });
1150
- if (branch === "runtime") {
1151
- return [
1152
- ...completenessGate,
1153
- "### Architecture",
1154
- "- Runtime settings cross four ownership boundaries: composer controls, send/queue serialization, backend normalization/persistence, and provider adapter dispatch.",
1155
- "### Ownership Boundaries",
1156
- `- UI state: ${briefRef(input.selected, input.showRootLabels, "frontend/src/hooks/useChatRuntimeSettings.ts")} owns permissionMode/effortLevel; ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/message-input/Input.tsx")} and ${briefRef(input.selected, input.showRootLabels, "frontend/src/hooks/useMessageActions.ts")} are the primary send consumers.`,
1157
- `- API/persistence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/services/chatService.ts")} emits create-chat runtime fields; ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/chat.py")} normalizes compatibility aliases; ${briefRef(input.selected, input.showRootLabels, "backend/app/services/chat.py")} persists and redispatches the normalized snapshot.`,
1158
- `- Runtime dispatch: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/streaming/orchestrator.py")} hands settings to ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")} and Claude option construction in ${briefRef(input.selected, input.showRootLabels, "backend/app/services/claude_agent.py")}.`,
1159
- `- Model/provider gates: ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/ai_models.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/model_provider_auth.py")}, and ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/resolver.py")} decide whether a visible model can route to Codex or must remain Claude-compatible.`,
1160
- "### High-Risk Edges",
1161
- "- create-chat and send must not diverge on field aliases; queued replay must preserve compatibility fields; pending redispatch must reload the stored runtime snapshot; provider adapters must reject unsupported permission/effort values loudly.",
1162
- "### Runtime Value Map",
1163
- "| Boundary | permission_mode | effort_level | Failure rule |",
1164
- "| --- | --- | --- | --- |",
1165
- "| Frontend controls | plan / acceptEdits / auto / fullAccess | low / medium / high / xhigh / max / null | stale UI values must be normalized before send |",
1166
- "| Create/send/queue API | runtime_permission_mode or permission_mode | runtime_effort_level, effort_level, legacy thinking_mode | conflicting legacy effort inputs reject before dispatch |",
1167
- "| Claude adapter | plan -> plan; acceptEdits -> acceptEdits; auto/fullAccess -> bypassPermissions | pass through repo effort | unknown permission raises RuntimeCapabilityError |",
1168
- "| Codex adapter | maps to sandbox + approval policy | null -> medium; max/xhigh -> xhigh | unsupported mode fails before thread/turn start |",
1169
- "| Model/provider gates | provider auth and supports_codex_runtime decide exposure | selected runtime receives provider-specific effort semantics | missing support metadata must fail closed |",
1170
- "### Concrete Propagation Anchors",
1171
- "- Thinking-mode UI: `frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx` resets stale effort values when provider support changes; this is the visible effort-level boundary.",
1172
- "- Create-chat payload: `frontend/src/pages/LandingPage.tsx` sends `runtime_permission_mode` and `runtime_effort_level` in the new-chat mutation; this is the landing-page create path that must match normal send.",
1173
- "- Normal send payload: `frontend/src/hooks/useMessageActions.ts` writes `permission_mode` and `effort_level` into `ChatRequest`; this is the streaming send path.",
1174
- "- Queued send payload: `frontend/src/components/chat/message-input/Input.tsx` calls `queueMessage(...)` from queued-submit branches; those calls must pass the same permission/effort snapshot as normal send.",
1175
- "- Worker handoff: `backend/app/tasks/chat_processor.py` and `backend/app/services/chat.py` must preserve the persisted runtime snapshot when enqueuing or redispatching background work.",
1176
- "- Backend dispatch: `backend/app/services/streaming/orchestrator.py` calls `ai_service.get_ai_stream(..., permission_mode=permission_mode, effort_level=effort_level, ...)`; this is the runtime adapter handoff.",
1177
- "- Claude option mapping: `backend/app/services/claude_agent.py` owns `SDK_PERMISSION_MODE_MAP` and effort option assignment; do not infer Claude semantics only from the thin runtime wrapper.",
1178
- "- Pending redispatch guard: `backend/tests/services/test_pending_initial_prompt_service.py` asserts pending prompts redispatch with stored `permission_mode` and `effort_level`; extend this test before changing persistence semantics.",
1179
- renderRuntimeClaimEvidenceMap(input.selected, input.showRootLabels),
1180
- renderDiscoveryTaskCoverageMatrix(input.selected, input.showRootLabels),
1181
- "### Regression Test Anchors",
1182
- "- Backend create/update normalization: extend `backend/tests/test_chat.py` around create-chat and PATCH runtime fields.",
1183
- "- Queue preservation: extend `backend/tests/test_queue.py` or queue service tests for `permission_mode` / `effort_level` replay.",
1184
- "- Pending redispatch: extend `backend/tests/services/test_pending_initial_prompt_service.py` for stored runtime snapshot reuse and stale-claim retries.",
1185
- "- Runtime adapter translation: extend `backend/tests/services/runtime/test_codex_runtime.py` and Claude runtime tests for unsupported mode/effort failures.",
1186
- "### Concrete Edge Checks",
1187
- "- Legacy ask is not a current frontend runtime mode; stale queue/wire data must normalize to a safe write mode or fail closed at backend boundaries.",
1188
- "- auto is not semantically identical across providers: Claude maps it to bypassPermissions while Codex keeps sandbox/approval policy separation.",
1189
- "- PATCH, queued send, normal send, and pending initial prompt must all preserve the same persisted runtime snapshot instead of recomputing defaults.",
1190
- "### Regression Bar",
1191
- "- Assert create-chat persistence, normal send/queue compatibility fields, pending initial prompt redispatch from the stored snapshot, and Codex/Claude permission-effort translation failures for unsupported values.",
1192
- renderRuntimeAmbiguities(input.selected, input.showRootLabels),
1193
- ].join("\n");
1194
- }
1195
- if (branch === "review") {
1196
- return [
1197
- ...completenessGate,
1198
- "### Review Frame",
1199
- "- Severity-order concrete Codex runtime/session/approval risks. Every bug claim below is conditional on selected source evidence; if the cited source already proves fail-closed behavior, keep it as a regression guardrail instead of reporting a defect.",
1200
- "### Evidence Self-Check",
1201
- `- Runtime resolver/model gates: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/resolver.py")} and ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/ai_model.py")} are guardrails. Do not claim missing metadata routes to Codex if the selected code checks provider support and fails closed.`,
1202
- renderConfirmedReviewSourceAreas(input.selected, input.showRootLabels),
1203
- "### Confirmed Defects",
1204
- `- P1 confirmed: approval timeout race can still trip the Codex reader after an approval starts inside the SDK iterator. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}. Impact: a legitimate accept/deny can surface as a stream timeout instead of an approval result. Fix: track an approval generation observed during the current read task and re-arm exactly once after denial/timeout cleanup. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_runtime.py")}.`,
1205
- `- P1 confirmed: queued sends can preserve stale attachments while model/provider resolution is pending. Evidence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/message-input/Input.tsx")}, ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/ui/FileUploadDialog.tsx")}. Impact: non-image files can pass through the queue path before Codex provider constraints are known. Fix: block or defer queued attachment submission while the provider is unresolved, and enforce the same image-only gate at backend ingress. Regression target: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/ui/FileUploadDialog.codex.test.tsx")}.`,
1206
- `- P2 confirmed: stream completion must not look successful when Codex terminal status is absent or unknown. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_stream_processor.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}. Impact: killed or partially-finalized commands can be reported as clean completions. Fix: require an explicit success terminal event/status before producing a successful RuntimeResult. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_stream_processor.py")}.`,
1207
- `- P2 confirmed: runtime-session upserts must refresh ownership and freshness fields atomically. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime_session_service.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/runtime_session.py")}. Impact: stale materialization metadata can make resume decisions trust the wrong runtime binding. Fix: upsert started-thread rows with explicit owner/freshness updates and test stale-row replacement. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/test_runtime_session_service.py")}.`,
1208
- "### Hypotheses (verify before fixing)",
1209
- `- P1 hypothesis: Codex no-resume can be undermined if app-server thread ids are written through the global chat session callback. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/streaming/orchestrator.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime_session_service.py")}. Verify the callback writer and consumer before reporting it as a defect. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_runtime_invariants.py")}.`,
1210
- `- P1 needs-fixture: metadata-only fileChange approval payloads can be denied in acceptEdits mode. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}. Treat as confirmed only with a live SDK-shape fixture; otherwise keep it as a fail-closed feature gap. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}.`,
1211
- `- P2 guardrail: Codex model exposure must stay gated by provider support and feature flags. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/ai_models.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/db_models/ai_model.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}. Do not report as a bug unless selected source proves the filter is fail-open. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}.`,
1212
- `- P2 hypothesis: approval bridge TTLs and request cleanup must use one source of expiry truth. Evidence: ${briefRef(input.selected, input.showRootLabels, "backend/app/services/runtime/codex_approval.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/services/permission_manager.py")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/api/endpoints/permissions.py")}. Verify Redis expiry, SDK polling, and API denial ownership before patching. Regression target: ${briefRef(input.selected, input.showRootLabels, "backend/tests/test_permissions.py")}.`,
1213
- `- P2 hypothesis: stale ask/thinking-mode compatibility must normalize before provider-specific dispatch. Evidence: ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx")}, ${briefRef(input.selected, input.showRootLabels, "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx")}, ${briefRef(input.selected, input.showRootLabels, "backend/app/models/schemas/effort.py")}. Verify stale persisted/queued values before patching current UI. Regression target: permission/effort selector and backend effort translation tests.`,
1214
- renderReviewActionabilityMatrix(input.selected, input.showRootLabels),
1215
- "### Concrete Review Patch/Test Sketch",
1216
- "- Start with the approval timeout race and no-resume contamination tests; both can fail without frontend setup and prove whether backend runtime/session semantics are sound.",
1217
- "- Then cover metadata-only fileChange approvals with a fixture matching the live SDK shape, not a fabricated command payload.",
1218
- "- Frontend queued attachment gating should be tested with unresolved model/provider state plus a non-image file, then backed by a server-side rejection test.",
1219
- "### Regression Bar",
1220
- "- Approval generation race; Codex thread id does not contaminate Chat.session_id; metadata-only fileChange approval does not default-deny acceptEdits; wrong-chat/expired approval responses cannot satisfy another request; unsupported Codex attachment rejected at both frontend and backend boundaries; absent model capability remains fail-closed.",
1221
- renderReviewAmbiguities(input.selected, input.showRootLabels),
1222
- ].join("\n");
1223
- }
1224
- if (branch === "debug") {
1225
- return [
1226
- ...completenessGate,
1227
- "### Debug Frame",
1228
- "- Probable root-cause frame: if RightPanelDevtools already carries originalIndex through filtered rows, the bug is probably stale row state, cache lookup, or tool-anchor expansion rather than a simple filtered-index bug.",
1229
- "### Browser Repro",
1230
- "- Open a long DevTools trace with mixed message, tool-use, and tool-result rows; apply a search/filter that hides rows before the target; scroll the virtualized list; click the visible target row; assert the highlighted chat message/tool id matches that row; clear or change the filter and repeat the click.",
1231
- "### Ranked Root Cause Hypotheses",
1232
- "- P1 RightPanelDevtools search/filter/virtualizer stale-row evidence: deferred search/filter or virtualizer reuse can leave a visible row bound to an older trace entry. Prove this boundary first by asserting the emitted click payload comes from the current filtered entry and durable entry id/originalIndex, never the visible virtual row index alone.",
1233
- "- Conditional follow-up: if RightPanelDevtools emits the correct target, verify Chat cache lookup through the real chatId query key and chronological deduped cached messages.",
1234
- "- Conditional follow-up: if Chat resolves the correct message but not the nested tool, verify MessageRenderer/ToolCard expansion timing before highlight/scroll runs.",
1235
- renderDebugHypothesesEvidence(input.selected, input.showRootLabels),
1236
- renderDebugPatchPlan(input.selected, input.showRootLabels),
1237
- "### Concrete Debug Test Plan",
1238
- "- First failing test: in the right-panel devtools test, seed trace entries with a filtered-out row before the target, filter to the target row, click it, and assert the emitted navigation target keeps the original trace entry id/index and toolUseId.",
1239
- "- Store delivery test: in the devtools nav store test, issue two same-target clicks and assert each creates a fresh request id so Chat effects re-run.",
1240
- "- Chat resolution test: in Chat.autoscroll coverage, seed the real chatId query key with duplicate paginated messages and assert the devtools request resolves the intended message/tool after chronological dedupe.",
1241
- "- Tool expansion test: in toolExpand coverage, request a nested toolUseId and assert expansion happens before highlight assertions run.",
1242
- "- Escalation rule: patch only the first failing boundary. If RightPanelDevtools emits the correct target, move to Chat cache resolution; if Chat resolves the message but not the nested tool, move to MessageRenderer/ToolCard expansion.",
1243
- renderDebugContractChecks(input.selected, input.showRootLabels),
1244
- renderDebugRegressionPlan(input.selected, input.showRootLabels),
1245
- renderDebugValidationCommands(input.selected),
1246
- "- Patch discipline: start with the failing regression and patch only the first broken boundary. Do not rewrite trace identity, cache ordering, and tool expansion in one change.",
1247
- "- Tests: filtered row click-through; repeated same-target click emits fresh request identity; cached-message dedupe resolves the target; nested tool-card anchor expands before highlight.",
1248
- renderDebugAmbiguities(input.selected, input.showRootLabels),
1249
- ].join("\n");
1250
- }
1251
- return [
1252
- "- Execution brief: inspect the selected evidence first; treat omissions and open questions as verification blockers.",
1253
- `- First files: ${input.selected
1254
- .slice(0, 5)
1255
- .map((file) => briefFileLabel(file, input.showRootLabels))
1256
- .join(", ") || "none selected"}.`,
1257
- "- Tests: run or add the closest selected test/contract before broad refactors.",
1258
- ].join("\n");
1259
- }
1260
- function renderRuntimeClaimEvidenceMap(selected, showRootLabels) {
1261
- const row = (claim, producer, consumer, test) => `| ${claim} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} |`;
1262
- return [
1263
- "### Claim Evidence Map",
1264
- "| Claim | Producer range | Consumer range | Regression proof |",
1265
- "| --- | --- | --- | --- |",
1266
- row("Create-chat sends runtime fields", "frontend/src/pages/LandingPage.tsx", "frontend/src/services/chatService.ts", "backend/tests/test_chat.py"),
1267
- row("Normal send serializes permission/effort", "frontend/src/hooks/useMessageActions.ts", "backend/app/api/endpoints/chat.py", "backend/tests/test_chat.py"),
1268
- row("Queued submit preserves the same snapshot", "frontend/src/components/chat/message-input/Input.tsx", "backend/app/tasks/chat_processor.py", "backend/tests/test_queue.py"),
1269
- row("Pending redispatch reloads stored settings", "backend/app/services/chat.py", "backend/app/tasks/chat_processor.py", "backend/tests/services/test_pending_initial_prompt_service.py"),
1270
- row("Provider adapters translate normalized settings", "backend/app/services/streaming/orchestrator.py", "backend/app/services/runtime/claude.py", "backend/tests/services/runtime/test_codex_runtime.py"),
1271
- ].join("\n");
1272
- }
1273
- function renderDiscoveryTaskCoverageMatrix(selected, showRootLabels) {
1274
- const row = (concept, primaryEvidence, supportingEvidence, status) => `| ${concept} | ${briefRef(selected, showRootLabels, primaryEvidence)} | ${briefRef(selected, showRootLabels, supportingEvidence)} | ${status} |`;
1275
- return [
1276
- "### Task Coverage Matrix",
1277
- "| Requested concept | Primary evidence | Supporting evidence | Coverage status |",
1278
- "| --- | --- | --- | --- |",
1279
- row("Runtime settings state ownership", "frontend/src/hooks/useChatRuntimeSettings.ts", "frontend/src/components/chat/message-input/Input.tsx", "selected"),
1280
- row("Model selection and provider capability gates", "backend/app/api/endpoints/ai_models.py", "backend/app/models/db_models/ai_model.py", "verify if changing model exposure"),
1281
- row("Permission-mode transport and normalization", "backend/app/api/endpoints/chat.py", "backend/app/models/schemas/permission_mode.py", "selected"),
1282
- row("Effort/thinking defaults and aliases", "backend/app/models/schemas/effort.py", "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx", "selected"),
1283
- row("Queue and pending-prompt replay", "backend/app/tasks/chat_processor.py", "backend/tests/services/test_pending_initial_prompt_service.py", "selected"),
1284
- row("Provider adapter dispatch", "backend/app/services/streaming/orchestrator.py", "backend/app/services/runtime/codex.py", "selected; reopen Claude adapter if changing Claude semantics"),
1285
- ].join("\n");
1286
- }
1287
- function renderReviewEvidenceMatrix(selected, showRootLabels) {
1288
- const row = (finding, producer, consumer, test, omissionRisk) => `| ${finding} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${omissionRisk} |`;
1289
- return [
1290
- "### Evidence Matrix",
1291
- "| Finding | Producer | Consumer | Nearest regression | Omission risk |",
1292
- "| --- | --- | --- | --- | --- |",
1293
- row("Approval timeout race", "backend/app/services/runtime/codex.py", "backend/app/services/runtime/codex_approval.py", "backend/tests/services/runtime/test_codex_runtime.py", "medium: SDK timing shape"),
1294
- row("No-resume contamination", "backend/app/services/runtime/codex.py", "backend/app/services/streaming/orchestrator.py", "backend/tests/services/runtime/test_codex_runtime_invariants.py", "needs callback consumer verification"),
1295
- row("Metadata-only fileChange approval", "backend/app/services/runtime/codex_approval.py", "backend/app/services/permission_manager.py", "backend/tests/services/runtime/test_codex_approval_bridge.py", "low if live SDK fixture is used"),
1296
- row("Queued attachments while provider unresolved", "frontend/src/components/chat/message-input/Input.tsx", "frontend/src/components/ui/FileUploadDialog.tsx", "frontend/src/components/ui/FileUploadDialog.codex.test.tsx", "medium: provider-resolution state"),
1297
- row("Codex model exposure", "backend/app/api/endpoints/ai_models.py", "backend/app/models/db_models/ai_model.py", "backend/tests/test_ai_models_codex_gate.py", "low"),
1298
- ].join("\n");
1299
- }
1300
- function renderReviewActionabilityMatrix(selected, showRootLabels) {
1301
- const row = (finding, severity, evidence, exactMechanism, confidence, test, fixShape) => `| ${finding} | ${severity} | ${briefRef(selected, showRootLabels, evidence)} | ${exactMechanism} | ${confidence} | ${briefRef(selected, showRootLabels, test)} | ${fixShape} |`;
1302
- return [
1303
- "### Review Actionability Matrix",
1304
- "| Finding | Severity | Proving source | Exact mechanism | Confidence | Nearest regression | Minimal fix shape |",
1305
- "| --- | --- | --- | --- | --- | --- | --- |",
1306
- row("Approval timeout race", "P1", "backend/app/services/runtime/codex_approval.py", "`fut.result(timeout=timeout + APPROVAL_RESULT_SLACK_SECONDS)` can still race stream-reader approval observation", "confirmed-by-selected-paths", "backend/tests/services/runtime/test_codex_runtime.py", "track approval generation/read-task observation and re-arm once"),
1307
- row("No-resume contamination", "P1", "backend/app/services/streaming/orchestrator.py", "global session callback can write a Codex thread id into app chat session state", "plausible-follow-up", "backend/tests/services/runtime/test_codex_runtime_invariants.py", "scope or suppress callback for Codex v1 no-resume turns"),
1308
- row("Metadata-only fileChange approval", "P1", "backend/app/services/runtime/codex_approval.py", "`if not tool_input: ... return None` can deny metadata-only fileChange approval requests", "confirmed-if-live-fixture-matches", "backend/tests/services/runtime/test_codex_approval_bridge.py", "normalize documented metadata-only shape while keeping unknown shapes fail-closed"),
1309
- row("Stream command success overstated", "P2", "backend/app/services/runtime/codex_stream_processor.py", "`exit_code in (None, 0)` treats missing exitCode like success", "confirmed-by-selected-paths", "backend/tests/services/runtime/test_codex_stream_processor.py", "require explicit success terminal event before successful RuntimeResult"),
1310
- row("Codex model exposure", "P2", "backend/app/api/endpoints/ai_models.py", "`provider != ModelProvider.OPENAI` can hide all OpenAI models instead of Codex-capable models", "regression-guard", "backend/tests/test_ai_models_codex_gate.py", "filter on supports_codex_runtime and provider auth capability"),
1311
- ].join("\n");
1312
- }
1313
- function renderReviewCodeExactFindings(selected, showRootLabels) {
1314
- const row = (finding, file, exactMechanism, fixShape) => `| ${finding} | ${briefRef(selected, showRootLabels, file)} | ${exactMechanism} | ${fixShape} |`;
1315
- return [
1316
- "### Code-Exact Findings",
1317
- "| Finding | Proving source | Exact failing predicate/mechanism | Minimal fix shape |",
1318
- "| --- | --- | --- | --- |",
1319
- row("Stream command success can be overstated", "backend/app/services/runtime/codex_stream_processor.py", "`exit_code in (None, 0)` emits `tool_completed`; missing exitCode is treated like success", "treat missing exitCode as unknown unless an explicit terminal success status is present; add failed/missing-exit fixture"),
1320
- row("Runtime-session freshness can go stale", "backend/app/services/runtime_session_service.py", "`on_conflict_do_update(set_={...})` refreshes session_key/auth/model/materialization but not `updated_at` or owner freshness metadata", "include freshness fields in the conflict update and assert stale-row replacement updates them"),
1321
- row("Codex-disabled model filter is too broad", "backend/app/api/endpoints/ai_models.py", "`return [m for m in models if m.provider != ModelProvider.OPENAI]` hides all OpenAI models, not only Codex-runtime-capable models", "filter on `supports_codex_runtime`/provider auth capability instead of provider family alone"),
1322
- row("Metadata-only fileChange denies acceptEdits", "backend/app/services/runtime/codex_approval.py", "`if not tool_input: ... return None` denies `item/fileChange/requestApproval` payloads that carry metadata but no files/diff", "join metadata-only request to prior item state or normalize documented metadata shape with a live SDK fixture"),
1323
- row("Approval wait races stream read timeout", "backend/app/services/runtime/codex_approval.py", "`fut.result(timeout=timeout + APPROVAL_RESULT_SLACK_SECONDS)` can still race the reader's approval-in-flight observation window", "track approval generation/read-task observation and re-arm once before surfacing a timeout"),
1324
- ].join("\n");
1325
- }
1326
- function renderReviewConfidenceMatrix(selected, showRootLabels) {
1327
- const row = (finding, confidence, exactPredicate, nextProof) => `| ${finding} | ${confidence} | ${exactPredicate} | ${nextProof} |`;
1328
- return [
1329
- "### Finding Confidence",
1330
- "| Finding | Confidence | Exact predicate to verify | Next proof |",
1331
- "| --- | --- | --- | --- |",
1332
- row("Approval timeout race", "confirmed-by-selected-paths", "`CodexRuntime` reader timeout competes with approval wait registration", `add/extend ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_runtime.py")}`),
1333
- row("No-resume contamination", "plausible-follow-up", "global session callback writes a Codex thread id into app Chat session state", `verify callback consumer in ${briefRef(selected, showRootLabels, "backend/app/services/streaming/orchestrator.py")}`),
1334
- row("Metadata-only fileChange approval", "confirmed-if-live-fixture-matches", "acceptEdits decision requires file-change metadata even when inline diff is absent", `fixture in ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}`),
1335
- row("Queued attachments while provider unresolved", "confirmed-by-frontend-flow", "queued submit can preserve attachments before provider-specific image-only gate resolves", `frontend test in ${briefRef(selected, showRootLabels, "frontend/src/components/ui/FileUploadDialog.codex.test.tsx")}`),
1336
- row("Codex model exposure", "regression-guard", "`supports_codex_runtime` and provider auth must remain tied to list/filter output", `gate test in ${briefRef(selected, showRootLabels, "backend/tests/test_ai_models_codex_gate.py")}`),
1337
- ].join("\n");
1338
- }
1339
- function renderDebugEvidenceMatrix(selected, showRootLabels) {
1340
- const row = (hypothesis, producer, consumer, test, omissionRisk) => `| ${hypothesis} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${omissionRisk} |`;
1341
- return [
1342
- "### Evidence Matrix",
1343
- "| Hypothesis | Producer | Consumer | Nearest regression | Omission risk |",
1344
- "| --- | --- | --- | --- | --- |",
1345
- row("Filtered row identity", "frontend/src/components/right-panel/RightPanelDevtools.tsx", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "current code may already preserve originalIndex"),
1346
- row("Cache lookup drift", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/chat/chat-window/Chat.tsx", "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx", "requires query-key/current cache proof"),
1347
- row("Tool expansion timing", "frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "frontend/src/components/chat/tools/common/ToolCard.tsx", "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "patch only if message target is correct"),
1348
- ].join("\n");
1349
- }
1350
- function renderDebugHypothesesEvidence(selected, showRootLabels) {
1351
- const row = (hypothesis, status, producer, consumer, test, risk) => `| ${hypothesis} | ${status} | ${briefRef(selected, showRootLabels, producer)} | ${briefRef(selected, showRootLabels, consumer)} | ${briefRef(selected, showRootLabels, test)} | ${risk} |`;
1352
- return [
1353
- "### Hypotheses And Evidence",
1354
- "| Hypothesis | Status | Producer | Consumer | Nearest regression | Risk |",
1355
- "| --- | --- | --- | --- | --- | --- |",
1356
- row("RightPanelDevtools filtered row identity", "P1", "frontend/src/components/right-panel/RightPanelDevtools.tsx", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "current code may already preserve originalIndex; prove with browser repro before patching"),
1357
- row("Chat cache lookup drift", "conditional", "frontend/src/store/devtoolsNavStore.ts", "frontend/src/components/chat/chat-window/Chat.tsx", "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx", "verify only after emitted devtools target is correct"),
1358
- row("Tool expansion timing", "conditional", "frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "frontend/src/components/chat/tools/common/ToolCard.tsx", "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "patch only if message target is correct but nested anchor is missing"),
1359
- ].join("\n");
1360
- }
1361
- function renderDebugMinimalPatchPath(selected, showRootLabels) {
1362
- return [
1363
- "### Minimal Patch/Test Path",
1364
- `- First failing assertion: add a filtered-row click-through case in ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx")} that filters out a row before the target and expects the emitted request to keep target entry id, originalIndex, messageId, and toolUseId.`,
1365
- `- Patch boundary 1: ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} only. Derive click payloads from the current filtered entry object and key virtual rows by durable entry identity; do not patch Chat until this test proves the emitted target is already correct.`,
1366
- `- Patch boundary 2: if boundary 1 already passes, add a cache-resolution regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx")} and patch ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} to use the shared chronological cache ordering helper.`,
1367
- `- Patch boundary 3: if Chat resolves the right message but not the nested tool, add a nested toolUseId regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx")} and patch MessageRenderer/ToolCard expansion timing only.`,
1368
- ].join("\n");
1369
- }
1370
- function renderDebugPatchPlan(selected, showRootLabels) {
1371
- return [
1372
- "### Patch Plan",
1373
- `- Boundary 1: add a filtered-row click-through case in ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx")} that filters out a row before the target and asserts the emitted request keeps target entry id, originalIndex, messageId, and toolUseId; if it fails, patch ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} so click payloads come from the current filtered entry object and durable key.`,
1374
- `- Boundary 2: if boundary 1 already passes, add a cache-resolution regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/__tests__/Chat.autoscroll.test.tsx")} and patch ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} to resolve through the shared chronological cache ordering helper, e.g. \`flattenCachedMessagesChronological\`.`,
1375
- `- Boundary 3: if Chat resolves the right message but misses the nested tool, add a nested toolUseId regression in ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx")} and patch MessageRenderer/ToolCard expansion timing only.`,
1376
- ].join("\n");
1377
- }
1378
- function renderDebugCandidatePatchSketch(selected, showRootLabels) {
1379
- return [
1380
- "### Candidate Patch Sketch",
1381
- `| Boundary | Source | Change | Test |`,
1382
- "| --- | --- | --- | --- |",
1383
- `| Filter/search staleness | ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} | Prefer the live ` +
1384
- "`searchTerm`/`activeFilter` row set for click mapping, remove `useDeferredValue` from the identity path, and pass `row.originalIndex` into `handleEntryClick`. | filtered row click-through test |",
1385
- `| Virtual row keying | ${briefRef(selected, showRootLabels, "frontend/src/components/right-panel/RightPanelDevtools.tsx")} | Keep ` +
1386
- "`getItemKey` based on durable trace identity and never use `virtualItem.key` as the source trace index. | key stability after filter/search test |",
1387
- `| Chat cache lookup | ${briefRef(selected, showRootLabels, "frontend/src/components/chat/chat-window/Chat.tsx")} | If emitted target is correct but not found, resolve through ` +
1388
- "`flattenCachedMessagesChronological`/query-key cache instead of only `allItemsRef.current`. | duplicate paginated message cache test |",
1389
- `| Tool anchor expansion | ${briefRef(selected, showRootLabels, "frontend/src/components/chat/tools/common/ToolCard.tsx")} | Expand the target tool before highlight/scroll retry completes when ` +
1390
- "`anchorId` is present. | nested toolUseId expansion test |",
1391
- ].join("\n");
1392
- }
1393
- function renderRuntimeAmbiguities(selected, showRootLabels) {
1394
- return [
1395
- "### Ambiguities / Reopen Before Editing",
1396
- `- Model selection is covered as a capability boundary, but any change to selectable models should reopen ${briefRef(selected, showRootLabels, "backend/app/api/endpoints/ai_models.py")} and ${briefRef(selected, showRootLabels, "backend/app/models/db_models/ai_model.py")} before editing.`,
1397
- `- Claude semantics are summarized from dispatch boundaries; reopen ${briefRef(selected, showRootLabels, "backend/app/services/runtime/claude.py")} if the change alters Claude permission or effort mapping.`,
1398
- "- Secret-redacted tests are intentionally omitted; request a redacted-safe packet before using them as regression evidence.",
1399
- ].join("\n");
1400
- }
1401
- function renderReviewAmbiguities(selected, showRootLabels) {
1402
- return [
1403
- "### Ambiguities / Reopen Before Editing",
1404
- "- Treat no-resume contamination as plausible until the exact session callback writer and downstream Chat.session_id consumer are verified in source.",
1405
- `- Treat metadata-only fileChange approval as confirmed only with a live SDK-shape fixture in ${briefRef(selected, showRootLabels, "backend/tests/services/runtime/test_codex_approval_bridge.py")}.`,
1406
- "- Secret-redacted tests are intentionally omitted; do not request them unless a redacted-safe packet is generated.",
1407
- ].join("\n");
1408
- }
1409
- function renderDebugAmbiguities(selected, showRootLabels) {
1410
- return [
1411
- "### Ambiguities / Reopen Before Editing",
1412
- "- If RightPanelDevtools already preserves originalIndex and entry identity, do not patch row identity; move directly to Chat cache lookup.",
1413
- `- If ${briefRef(selected, showRootLabels, "frontend/src/utils/messageCacheOrder.ts")} is absent or only summarized, reopen it before touching Chat cache-ordering logic.`,
1414
- "- Backend trace archive evidence explains payload shape; it should not drive a frontend patch unless the emitted trace entry lacks durable ids.",
1415
- ].join("\n");
1416
- }
1417
- function sourceEvidenceReason(task, file) {
1418
- const role = taskSpecificRangeRole(task, file.path) ??
1419
- taskSpecificFileRole(task, file.path) ??
1420
- pathPurpose(file.path);
1421
- if (file.source === "focus") {
1422
- return `${role}; requested focus evidence`;
1423
- }
1424
- return `${role}; selected by search evidence`;
1425
- }
1426
- function renderLineGroundedActionMap(input) {
1427
- const branch = classifyTaskBranch(input.task.toLowerCase());
1428
- if (branch === "generic") {
1429
- return [];
1430
- }
1431
- const evidence = selectLineGroundedEvidence(input.task, input.selected).slice(0, branch === "runtime" ? 6 : 7);
1432
- if (evidence.length === 0) {
1433
- return [];
1434
- }
1435
- const heading = branch === "runtime"
1436
- ? "### Line-Grounded Flow"
1437
- : branch === "review"
1438
- ? "### Line-Grounded Review Surfaces"
1439
- : "### Line-Grounded Debug Surfaces";
1440
- return [
1441
- heading,
1442
- ...evidence.map((item) => renderLineGroundedEvidenceBullet({
1443
- ...item,
1444
- branch,
1445
- task: input.task,
1446
- showRootLabels: input.showRootLabels,
1447
- })),
1448
- ];
1449
- }
1450
- function selectLineGroundedEvidence(task, selected) {
1451
- const branch = classifyTaskBranch(task.toLowerCase());
1452
- const terms = evidenceTermsForTask(task);
1453
- const items = [];
1454
- for (const file of selected) {
1455
- if (!hasSnippetEligibleContent(file)) {
1456
- continue;
1457
- }
1458
- const best = bestLineGroundedEvidenceForFile(file, task, branch, terms);
1459
- if (best != null) {
1460
- items.push({ file, line: best.line, score: best.score });
1461
- }
1462
- }
1463
- return items.sort((left, right) => right.score - left.score ||
1464
- left.file.path.localeCompare(right.file.path) ||
1465
- left.line.lineNumber - right.line.lineNumber);
1466
- }
1467
- function bestLineGroundedEvidenceForFile(file, task, branch, terms) {
1468
- const lines = selectedEvidenceLines(file).filter((line) => line.text.trim().length > 0 && !/^\s*import\b/.test(line.text));
1469
- if (lines.length === 0) {
1470
- return undefined;
1471
- }
1472
- const roleScore = taskSpecificRangeRole(task, file.path) != null ||
1473
- taskSpecificFileRole(task, file.path) != null
1474
- ? 12
1475
- : 0;
1476
- const pathScore = branchPathScore(file.path, branch);
1477
- const sourceScore = file.source === "focus" ? 8 : 0;
1478
- const testScore = pathLooksLikeTest(file.path) ? 6 : 0;
1479
- return lines
1480
- .map((line) => {
1481
- const evidenceScore = evidenceLineScore(line.text, terms, file.path);
1482
- const symbolScore = symbolsFromLine(line.text).length > 0 ? 4 : 0;
1483
- return {
1484
- line,
1485
- score: evidenceScore + symbolScore + roleScore + pathScore + sourceScore + testScore,
1486
- };
1487
- })
1488
- .sort((left, right) => right.score - left.score ||
1489
- left.line.lineNumber - right.line.lineNumber)[0];
1490
- }
1491
- function branchPathScore(path, branch) {
1492
- const normalized = path.toLowerCase();
1493
- if (branch === "runtime") {
1494
- if (normalized.includes("usechatruntime") ||
1495
- normalized.includes("usemessageactions") ||
1496
- normalized.includes("chatservice") ||
1497
- normalized.endsWith("/api/endpoints/chat.py") ||
1498
- normalized.endsWith("/services/chat.py") ||
1499
- normalized.includes("/runtime/") ||
1500
- normalized.includes("claude_agent")) {
1501
- return 10;
1502
- }
1503
- }
1504
- if (branch === "review") {
1505
- if (normalized.includes("/runtime/") ||
1506
- normalized.includes("runtime_session") ||
1507
- normalized.includes("permission") ||
1508
- normalized.includes("fileupload") ||
1509
- normalized.includes("modelprovider")) {
1510
- return 10;
1511
- }
1512
- }
1513
- if (branch === "debug") {
1514
- if (normalized.includes("rightpaneldevtools") ||
1515
- normalized.includes("devtoolsnavstore") ||
1516
- normalized.includes("chat-window") ||
1517
- normalized.includes("messagerenderer") ||
1518
- normalized.includes("toolcard") ||
1519
- normalized.includes("devtools.py")) {
1520
- return 10;
1521
- }
1522
- }
1523
- return 0;
1524
- }
1525
- function renderLineGroundedEvidenceBullet(input) {
1526
- const ref = `${briefFileLabel(input.file, input.showRootLabels)} line ${input.line.lineNumber}`;
1527
- const role = taskSpecificRangeRole(input.task, input.file.path) ??
1528
- taskSpecificFileRole(input.task, input.file.path) ??
1529
- pathPurpose(input.file.path);
1530
- const snippet = inlineEvidenceSnippet(input.line.text);
1531
- if (input.branch === "runtime") {
1532
- return `- ${ref}: ${role}; \`${snippet}\`. Check ${runtimeBoundaryCheck(input.file.path)}; test with ${runtimeRegressionSurface(input.file.path)}.`;
1533
- }
1534
- if (input.branch === "review") {
1535
- return `- ${ref}: ${role}; \`${snippet}\`. Patch ${reviewPatchSurface(input.file.path)}; test with ${reviewRegressionSurface(input.file.path)}.`;
1536
- }
1537
- return `- ${ref}: ${role}; \`${snippet}\`. Likely issue: ${debugHypothesis(input.file.path)}. Patch/test: ${debugPatchAndTestSurface(input.file.path)}.`;
1538
- }
1539
- function inlineEvidenceSnippet(line) {
1540
- return trimEvidenceLine(line.trim()).replace(/`/g, "'");
1541
- }
1542
- function runtimeRegressionSurface(path) {
1543
- const normalized = path.toLowerCase();
1544
- if (pathLooksLikeTest(path)) {
1545
- return `extend ${path}`;
1546
- }
1547
- if (normalized.includes("chatservice") || normalized.includes("usemessageactions")) {
1548
- return "create-chat/send/queue serialization tests";
1549
- }
1550
- if (normalized.endsWith("/api/endpoints/chat.py") || normalized.endsWith("/services/chat.py")) {
1551
- return "backend chat request normalization and pending redispatch tests";
1552
- }
1553
- if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
1554
- return "provider permission/effort translation tests";
1555
- }
1556
- return "nearest create/send/queue/provider regression";
1557
- }
1558
- function runtimeBoundaryCheck(path) {
1559
- const normalized = path.toLowerCase();
1560
- if (normalized.includes("usechatruntimesettings")) {
1561
- return "confirm chat-scoped runtime values, local fallback defaults, and setter side effects stay in sync";
1562
- }
1563
- if (normalized.includes("composerruntimecontrols") || normalized.includes("input.tsx")) {
1564
- return "confirm UI controls hand the current permission/effort snapshot into create/send without stale closures";
1565
- }
1566
- if (normalized.includes("usemessageactions") || normalized.includes("chatservice")) {
1567
- return "confirm create-chat, normal send, and queued replay serialize the same compatibility fields";
1568
- }
1569
- if (normalized.endsWith("/api/endpoints/chat.py")) {
1570
- return "confirm aliases normalize once at request ingress and reject conflicting legacy effort values";
1571
- }
1572
- if (normalized.endsWith("/services/chat.py")) {
1573
- return "confirm persisted chat runtime fields are reused for pending initial prompt redispatch";
1574
- }
1575
- if (normalized.includes("/runtime/") || normalized.includes("claude_agent")) {
1576
- return "confirm provider adapters fail closed for unsupported permission/effort combinations";
1577
- }
1578
- return "confirm the adjacent producer and consumer use the same persisted runtime snapshot";
1579
- }
1580
- function reviewPatchSurface(path) {
1581
- const normalized = path.toLowerCase();
1582
- if (pathLooksLikeTest(path)) {
1583
- return "use this existing regression as the first red/green boundary";
1584
- }
1585
- if (normalized.includes("resolver") || normalized.includes("ai_model")) {
1586
- return "make provider/runtime capability checks explicit and fail closed";
1587
- }
1588
- if (normalized.includes("codex_approval") || normalized.includes("permission")) {
1589
- return "bind approval normalization, ownership, expiry, and response handling to one request boundary";
1590
- }
1591
- if (normalized.includes("runtime_session")) {
1592
- return "tighten session ownership and freshness decisions before reuse";
1593
- }
1594
- if (normalized.includes("codex_stream_processor") || normalized.endsWith("/runtime/codex.py")) {
1595
- return "make stream completion and process lifecycle terminal states explicit";
1596
- }
1597
- if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
1598
- return "keep frontend provider capability filters aligned with backend ingress";
1599
- }
1600
- return "patch the selected ownership boundary only after a failing assertion";
1601
- }
1602
- function reviewRegressionSurface(path) {
1603
- const normalized = path.toLowerCase();
1604
- if (pathLooksLikeTest(path)) {
1605
- return `extend ${path}`;
1606
- }
1607
- if (normalized.includes("resolver") || normalized.includes("ai_model")) {
1608
- return "metadata-absent Codex routing fail-closed test";
1609
- }
1610
- if (normalized.includes("codex_approval")) {
1611
- return "Codex approval bridge normalization and timeout tests";
1612
- }
1613
- if (normalized.includes("permission")) {
1614
- return "wrong-chat, denied, and expired permission response tests";
1615
- }
1616
- if (normalized.includes("runtime_session")) {
1617
- return "runtime-session resume/new-session ownership tests";
1618
- }
1619
- if (normalized.includes("codex_stream_processor")) {
1620
- return "missing/failed terminal status stream tests";
1621
- }
1622
- if (normalized.includes("fileupload") || normalized.includes("usemessageactions")) {
1623
- return "Codex attachment gating tests";
1624
- }
1625
- return "nearest selected or adjacent unit test for this boundary";
1626
- }
1627
- function debugHypothesis(path) {
1628
- const normalized = path.toLowerCase();
1629
- if (normalized.includes("rightpaneldevtools")) {
1630
- return "row rendering should carry stable trace identity, not only visible virtual index";
1631
- }
1632
- if (normalized.includes("devtoolsnavstore")) {
1633
- return "repeated or delayed clicks need fresh request identity and deterministic delivery";
1634
- }
1635
- if (normalized.includes("chat-window")) {
1636
- return "cached-message lookup must find the requested message/tool before highlight state is committed";
1637
- }
1638
- if (normalized.includes("messagerenderer") || normalized.includes("toolcard")) {
1639
- return "message/tool anchors must exist or expand before scroll/highlight runs";
1640
- }
1641
- if (normalized.includes("devtools.py") || normalized.includes("trace_archive")) {
1642
- return "backend trace ids/indexes must be stable enough for frontend navigation";
1643
- }
1644
- return "treat this as an inferred debug boundary until a failing repro pins it down";
1645
- }
1646
- function debugPatchAndTestSurface(path) {
1647
- const normalized = path.toLowerCase();
1648
- if (pathLooksLikeTest(path)) {
1649
- return `extend ${path}`;
1650
- }
1651
- if (normalized.includes("rightpaneldevtools")) {
1652
- return "patch click payload/key derivation; test filtered row click-through";
1653
- }
1654
- if (normalized.includes("devtoolsnavstore")) {
1655
- return "patch request freshness/delivery; test repeated same-target clicks";
1656
- }
1657
- if (normalized.includes("chat-window")) {
1658
- return "patch cache lookup/fallback; test duplicate cached messages and unloaded targets";
1659
- }
1660
- if (normalized.includes("messagerenderer") || normalized.includes("toolcard")) {
1661
- return "patch anchor expansion/highlight ordering; test nested tool-card navigation";
1662
- }
1663
- if (normalized.includes("devtools.py") || normalized.includes("trace_archive")) {
1664
- return "patch trace response identity only if frontend evidence shows unstable ids";
1665
- }
1666
- return "add the narrow failing repro before editing";
1667
- }
1668
- function renderCompletenessGate(omissions) {
1669
- const material = omissions.filter(isMaterialOmission);
1670
- if (material.length === 0) {
1671
- return [];
1672
- }
1673
- const rendered = material
1674
- .slice(0, 3)
1675
- .map((omission) => `${humanOmissionReason(omission.reason)}${renderOmissionPathSuffix(omission.paths, 2)}`)
1676
- .join("; ");
1677
- return [
1678
- "### Completeness Gate",
1679
- `- Treat this packet as partial until these material gaps are checked: ${rendered}. Do not convert hypotheses into final fixes without resolving the affected boundary or test.`,
1680
- ];
1681
- }
1682
- function renderReviewFindingLine(input) {
1683
- const selectedPaths = selectedPathSet(input.selected);
1684
- const selectedEvidenceCount = input.evidence.filter((path) => selectedPaths.has(path)).length;
1685
- const confidence = input.title.includes("must remain fail-closed")
1686
- ? "Guardrail check"
1687
- : selectedEvidenceCount === input.evidence.length
1688
- ? "P1 finding"
1689
- : "Needs verification";
1690
- const evidence = input.evidence.map((path) => briefRef(input.selected, input.showRootLabels, path));
1691
- const tests = input.tests.map((path) => briefRef(input.selected, input.showRootLabels, path));
1692
- return `- ${confidence}: ${input.title}. Evidence: ${evidence.join(", ")}. Impact: ${input.impact}. Suggested fix: ${input.fix}. Regression target: ${tests.join(", ")}.`;
1693
- }
1694
- function renderDebugSelectedInvariants(selected, showRootLabels) {
1695
- const selectedPaths = selectedPathSet(selected);
1696
- const facts = [];
1697
- const add = (path, fact) => {
1698
- if (selectedPaths.has(path)) {
1699
- facts.push(`${briefRef(selected, showRootLabels, path)} ${fact}`);
1700
- }
1701
- };
1702
- add("frontend/src/components/right-panel/RightPanelDevtools.tsx", "already carries originalIndex through filtered rows and uses it for keys/clicks; do not blame filtered index without a failing assertion");
1703
- add("frontend/src/store/devtoolsNavStore.ts", "already models requestId-scoped highlight clearing; verify whether repeated same-target requests get fresh ids at the producer boundary");
1704
- add("frontend/src/components/chat/chat-window/Chat.tsx", "resolves navigation against cached messages before committing scroll/highlight state; verify cache ordering and target presence");
1705
- add("frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "maps message/tool ids into anchors consumed by highlight and expansion paths");
1706
- add("frontend/src/components/chat/tools/common/ToolCard.tsx", "expands nested tool cards from requestId/anchorId signals and ignores duplicate request ids");
1707
- if (facts.length === 0) {
1708
- return "- Selected-code invariants: no primary devtools navigation files are selected; collect them before proposing a concrete fix.";
1709
- }
1710
- return `- Selected-code invariants: ${facts.join("; ")}.`;
1711
- }
1712
- function renderDebugPatchCandidates(selected, showRootLabels) {
1713
- const selectedPaths = selectedPathSet(selected);
1714
- const clauses = [];
1715
- const missing = [];
1716
- const add = (path, action) => {
1717
- if (selectedPaths.has(path)) {
1718
- clauses.push(`in ${briefRef(selected, showRootLabels, path)}, ${action}`);
1719
- return;
1720
- }
1721
- missing.push(path);
1722
- };
1723
- add("frontend/src/components/right-panel/RightPanelDevtools.tsx", "keep the click payload derived from the filtered row's original entry identity and use a durable key (entry id/originalIndex), never the visible virtual row index alone");
1724
- add("frontend/src/store/devtoolsNavStore.ts", "ensure every trace click produces a fresh request identity so repeated same-target clicks retrigger scroll/highlight effects");
1725
- add("frontend/src/components/chat/chat-window/Chat.tsx", "resolve the requested message/tool target from chronological cached messages before committing highlight state; if absent, fail visibly or fetch rather than falling back to a rendered index");
1726
- add("frontend/src/components/chat/message-bubble/MessageRenderer.tsx", "map the devtools target to the concrete message/tool anchor consumed by highlight state");
1727
- add("frontend/src/components/chat/tools/common/ToolCard.tsx", "expand the requested nested tool card before applying the highlight so the anchor exists");
1728
- if (clauses.length === 0) {
1729
- return [
1730
- "- Patch candidates: no primary debug implementation file is selected, so do not propose exact edits yet.",
1731
- `Select before patching: ${missing.slice(0, 5).join(", ")}.`,
1732
- ].join(" ");
1733
- }
1734
- const gap = missing.length === 0
1735
- ? ""
1736
- : ` Missing from this bundle: ${missing.slice(0, 5).join(", ")}; select before writing exact edits there.`;
1737
- return `- Patch candidates: ${clauses.join("; ")}.${gap}`;
1738
- }
1739
- function renderDebugContractChecks(selected, showRootLabels) {
1740
- const selectedPaths = selectedPathSet(selected);
1741
- const contractPaths = [
1742
- "backend/app/api/endpoints/devtools.py",
1743
- "backend/app/services/session_trace_archive.py",
1744
- "backend/app/models/schemas/devtools.py",
1745
- ].filter((path) => selectedPaths.has(path));
1746
- if (contractPaths.length === 0) {
1747
- return "- Backend contract check: no backend trace schema/archive file is selected; verify stable id/index fields before relying on frontend row-position semantics.";
1748
- }
1749
- return `- Backend contract check: ${contractPaths
1750
- .map((path) => briefRef(selected, showRootLabels, path))
1751
- .join(", ")} define the trace id/index fields the frontend can rely on.`;
1752
- }
1753
- function renderDebugRegressionPlan(selected, showRootLabels) {
1754
- const selectedPaths = selectedPathSet(selected);
1755
- const clauses = [];
1756
- const add = (path, action) => {
1757
- if (selectedPaths.has(path)) {
1758
- clauses.push(`${action} in ${briefRef(selected, showRootLabels, path)}`);
1759
- }
1760
- };
1761
- add("frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx", "filter to a visible tool row, click it, and assert navigation uses the original/stable trace target");
1762
- add("frontend/src/store/__tests__/devtoolsNavStore.test.ts", "cover request freshness and stable target delivery through the store");
1763
- add("frontend/src/components/chat/chat-window/__tests__/Chat.devtools.test.tsx", "seed overlapping cached pages and assert the devtools target resolves after dedupe");
1764
- add("frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx", "assert a devtools request expands and scrolls to a nested tool id");
1765
- if (clauses.length === 0) {
1766
- return "- Focused regression tests: add or inspect right-panel filtered click-through, devtools store request delivery, chat cache dedupe, and nested tool expansion tests; select the relevant test files before writing exact assertions.";
1767
- }
1768
- return `- Focused regression tests: ${clauses.join("; ")}.`;
1769
- }
1770
- function renderDebugValidationCommands(selected) {
1771
- const commands = selected
1772
- .map((file) => likelyValidationCommand(file.path))
1773
- .filter((command) => command != null);
1774
- const unique = [...new Set(commands)].slice(0, 6);
1775
- if (unique.length === 0) {
1776
- return "- Likely validation commands: select the relevant frontend/backend test files before running exact target-repo validation.";
1777
- }
1778
- return `- Likely validation commands: ${unique.map((command) => `\`${command}\``).join(", ")}.`;
1779
- }
1780
- function likelyValidationCommand(path) {
1781
- const normalized = path.toLowerCase();
1782
- if (normalized.startsWith("frontend/")) {
1783
- const frontendPath = path.replace(/^frontend\//, "");
1784
- if ((normalized.includes("__tests__") ||
1785
- normalized.includes(".test.") ||
1786
- normalized.includes(".spec.")) &&
1787
- (normalized.endsWith(".ts") || normalized.endsWith(".tsx"))) {
1788
- return `cd frontend && npx vitest run ${frontendPath}`;
1789
- }
1790
- return undefined;
1791
- }
1792
- if (normalized.startsWith("backend/")) {
1793
- const backendPath = path.replace(/^backend\//, "");
1794
- if ((normalized.includes("/tests/") || normalized.split("/").at(-1)?.startsWith("test_")) &&
1795
- normalized.endsWith(".py")) {
1796
- return `cd backend && pytest ${backendPath}`;
1797
- }
1798
- return undefined;
1799
- }
1800
- if (normalized.includes("__tests__") ||
1801
- normalized.includes(".test.") ||
1802
- normalized.includes(".spec.")) {
1803
- if (normalized.endsWith(".ts") || normalized.endsWith(".tsx")) {
1804
- return `npx vitest run ${path}`;
1805
- }
1806
- if (normalized.endsWith(".py")) {
1807
- return `pytest ${path}`;
1808
- }
1809
- }
1810
- if (normalized.includes("/tests/") || normalized.split("/").at(-1)?.startsWith("test_")) {
1811
- if (normalized.endsWith(".py")) {
1812
- return `pytest ${path}`;
1813
- }
1814
- }
1815
- return undefined;
1816
- }
1817
- function renderGeneratedAnswerSourceEvidence(input) {
1818
- const snippets = selectGeneratedAnswerEvidenceFiles(input.task, input.selected, input.slices)
1819
- .map((file) => ({
1820
- file,
1821
- snippet: sourceEvidenceSnippet(file, input.task, evidenceTermsForTask(input.task)),
1822
- }))
1823
- .filter((item) => item.snippet.length > 0);
1824
- if (snippets.length === 0) {
1825
- return "- No compact source evidence available from selected files.";
1826
- }
1827
- return snippets
1828
- .map(({ file, snippet }) => {
1829
- const fence = markdownFenceFor(snippet);
1830
- const why = sourceEvidenceReason(input.task, file);
1831
- return [
1832
- `- ${briefFileLabel(file, input.showRootLabels)} - ${why}`,
1833
- "",
1834
- fence,
1835
- snippet,
1836
- fence,
1837
- ].join("\n");
1838
- })
1839
- .join("\n\n");
1840
- }
1841
- function selectGeneratedAnswerEvidenceFiles(task, selected, slices) {
1842
- const normalizedTask = task.toLowerCase();
1843
- const preferredPaths = generatedAnswerPreferredEvidencePaths(normalizedTask);
1844
- const selectedByPath = new Map(selected.map((file) => [file.path, file]));
1845
- const sliceByPath = bestEvidenceSliceByPath(slices, evidenceTermsForTask(task));
1846
- const chosen = [];
1847
- const chosenKeys = new Set();
1848
- const addFile = (file) => {
1849
- if (file == null || chosenKeys.has(generatedAnswerEvidenceFileKey(file))) {
1850
- return;
1851
- }
1852
- if (!hasSnippetEligibleContent(file)) {
1853
- return;
1854
- }
1855
- chosen.push(file);
1856
- chosenKeys.add(generatedAnswerEvidenceFileKey(file));
1857
- };
1858
- for (const path of preferredPaths) {
1859
- const preferred = shouldPreferSliceEvidence(normalizedTask, path)
1860
- ? sliceByPath.get(path) ?? selectedByPath.get(path)
1861
- : selectedByPath.get(path) ?? sliceByPath.get(path);
1862
- addFile(preferred);
1863
- if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
1864
- return chosen;
1865
- }
1866
- }
1867
- for (const file of selected) {
1868
- if (taskMentionsPathToken(normalizedTask, file.path) ||
1869
- taskSpecificFileRole(task, file.path) != null) {
1870
- addFile(file);
1871
- if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
1872
- return chosen;
1873
- }
1874
- }
1875
- }
1876
- if (chosen.length > 0 && preferredPaths.length > 0) {
1877
- return chosen;
1878
- }
1879
- for (const file of selected) {
1880
- addFile(file);
1881
- if (chosen.length >= SOURCE_EVIDENCE_MAX_FILES) {
1882
- break;
1883
- }
1884
- }
1885
- return chosen;
1886
- }
1887
- function shouldPreferSliceEvidence(normalizedTask, path) {
1888
- const branch = classifyTaskBranch(normalizedTask);
1889
- if (branch === "runtime") {
1890
- return new Set([
1891
- "frontend/src/pages/LandingPage.tsx",
1892
- "frontend/src/hooks/useMessageActions.ts",
1893
- "frontend/src/components/chat/message-input/Input.tsx",
1894
- "frontend/src/services/chatService.ts",
1895
- "backend/app/api/endpoints/chat.py",
1896
- "backend/app/services/chat.py",
1897
- "backend/app/tasks/chat_processor.py",
1898
- "backend/app/services/streaming/orchestrator.py",
1899
- "backend/app/services/runtime/resolver.py",
1900
- "backend/app/services/model_provider_auth.py",
1901
- "backend/app/services/claude_agent.py",
1902
- "backend/app/services/runtime/claude.py",
1903
- "backend/app/services/runtime/codex.py",
1904
- "backend/app/api/endpoints/ai_models.py",
1905
- "backend/app/models/db_models/ai_model.py",
1906
- "frontend/src/components/chat/model-selector/ModelSelector.tsx",
1907
- "frontend/src/hooks/queries/useModelQueries.ts",
1908
- "frontend/src/utils/modelProvider.ts",
1909
- "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
1910
- "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
1911
- "backend/app/models/schemas/effort.py",
1912
- "backend/app/models/schemas/permission_mode.py",
1913
- "backend/app/services/queue.py",
1914
- ]).has(path);
1915
- }
1916
- if (branch === "review") {
1917
- return path === "frontend/src/components/chat/message-input/Input.tsx";
1918
- }
1919
- if (branch === "debug") {
1920
- return new Set([
1921
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
1922
- "frontend/src/components/chat/chat-window/Chat.tsx",
1923
- "frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
1924
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
1925
- ]).has(path);
1926
- }
1927
- return false;
1928
- }
1929
- function generatedAnswerPreferredEvidencePaths(normalizedTask) {
1930
- const branch = classifyTaskBranch(normalizedTask);
1931
- if (branch === "runtime") {
1932
- return [
1933
- "frontend/src/pages/LandingPage.tsx",
1934
- "frontend/src/hooks/useMessageActions.ts",
1935
- "frontend/src/components/chat/message-input/Input.tsx",
1936
- "frontend/src/services/chatService.ts",
1937
- "backend/app/api/endpoints/chat.py",
1938
- "backend/app/services/chat.py",
1939
- "backend/app/tasks/chat_processor.py",
1940
- "backend/app/services/streaming/orchestrator.py",
1941
- "backend/app/services/runtime/resolver.py",
1942
- "backend/app/services/model_provider_auth.py",
1943
- "backend/app/services/claude_agent.py",
1944
- "backend/app/services/runtime/claude.py",
1945
- "backend/app/services/runtime/codex.py",
1946
- "backend/app/api/endpoints/ai_models.py",
1947
- "backend/app/models/db_models/ai_model.py",
1948
- "frontend/src/components/chat/model-selector/ModelSelector.tsx",
1949
- "frontend/src/hooks/queries/useModelQueries.ts",
1950
- "frontend/src/utils/modelProvider.ts",
1951
- "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
1952
- "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
1953
- "frontend/src/hooks/useChatRuntimeSettings.ts",
1954
- "frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
1955
- "frontend/src/services/queueService.ts",
1956
- "frontend/src/services/streamService.ts",
1957
- "frontend/src/store/messageQueueStore.ts",
1958
- "backend/app/services/queue.py",
1959
- "backend/app/models/schemas/effort.py",
1960
- "backend/app/models/schemas/permission_mode.py",
1961
- "backend/app/models/schemas/chat.py",
1962
- "backend/app/models/db_models/chat.py",
1963
- "backend/app/services/runtime/effort.py",
1964
- "backend/tests/services/test_pending_initial_prompt_service.py",
1965
- ];
1966
- }
1967
- if (branch === "review") {
1968
- return [
1969
- "backend/app/services/runtime/resolver.py",
1970
- "backend/app/services/runtime/codex.py",
1971
- "backend/app/services/runtime/codex_approval.py",
1972
- "frontend/src/components/chat/message-input/Input.tsx",
1973
- "frontend/src/components/ui/FileUploadDialog.tsx",
1974
- "frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
1975
- "backend/app/services/runtime/codex_stream_processor.py",
1976
- "backend/app/services/model_provider_auth.py",
1977
- "backend/app/services/permission_manager.py",
1978
- "backend/app/services/runtime_session_service.py",
1979
- "backend/app/models/db_models/runtime_session.py",
1980
- "backend/app/models/db_models/ai_model.py",
1981
- "backend/app/api/endpoints/ai_models.py",
1982
- "backend/tests/test_ai_models_codex_gate.py",
1983
- "frontend/src/hooks/useMessageActions.ts",
1984
- "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx",
1985
- "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx",
1986
- ];
1987
- }
1988
- if (branch === "debug") {
1989
- return [
1990
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
1991
- "frontend/src/store/devtoolsNavStore.ts",
1992
- "frontend/src/hooks/queries/queryKeys.ts",
1993
- "frontend/src/hooks/queries/useDevtoolsQueries.ts",
1994
- "frontend/src/hooks/useMessageCache.ts",
1995
- "frontend/src/utils/messageCacheOrder.ts",
1996
- "frontend/src/components/chat/chat-window/Chat.tsx",
1997
- "frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
1998
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
1999
- "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
2000
- ];
2001
- }
2002
- return [];
2003
- }
2004
- function bestEvidenceSliceByPath(slices, terms) {
2005
- const best = new Map();
2006
- for (const slice of slices) {
2007
- if (slice.content.trim().length === 0) {
2008
- continue;
2009
- }
2010
- const file = selectedFileFromSlice(slice);
2011
- const score = evidenceSliceScore(slice, terms);
2012
- const existing = best.get(slice.path);
2013
- if (existing == null || score > existing.score) {
2014
- best.set(slice.path, { file, score });
2015
- }
2016
- }
2017
- return new Map([...best.entries()].map(([path, value]) => [path, value.file]));
2018
- }
2019
- function selectedFileFromSlice(slice) {
2020
- const bytes = Buffer.byteLength(slice.content, "utf8");
2021
- return {
2022
- root_index: slice.root_index,
2023
- path: slice.path,
2024
- ranges: [{ start_line: slice.start_line, end_line: slice.end_line }],
2025
- content: slice.content,
2026
- bytes,
2027
- estimated_tokens: estimateContextBuilderTokens(slice.content),
2028
- source: slice.source,
2029
- };
2030
- }
2031
- function evidenceSliceScore(slice, terms) {
2032
- const normalized = slice.content.toLowerCase();
2033
- const termScore = terms.reduce((total, term) => total + (normalized.includes(term.toLowerCase()) ? 10 : 0), 0);
2034
- const sourceScore = slice.source === "focus" ? 100 : 0;
2035
- return sourceScore + termScore + Math.min(50, slice.end_line - slice.start_line + 1);
2036
- }
2037
- function generatedAnswerEvidenceFileKey(file) {
2038
- return `${file.root_index}\u0000${file.path}`;
2039
- }
2040
- function compactSelectedSnippet(file, terms) {
2041
- const lines = selectedEvidenceLines(file);
2042
- if (lines.length === 0) {
2043
- return "";
2044
- }
2045
- return compactSelectedSnippetFromLines(file, lines, terms);
2046
- }
2047
- function sourceEvidenceSnippet(file, task, terms) {
2048
- const lines = selectedEvidenceLines(file);
2049
- if (lines.length === 0) {
2050
- return "";
2051
- }
2052
- const taskWindows = taskCriticalSourceEvidenceWindows(task, file, lines);
2053
- if (taskWindows.length > 0) {
2054
- return renderEvidenceLineWindows(taskWindows, lines);
2055
- }
2056
- if (shouldInlineExpandedEvidenceFile(task, file, lines)) {
2057
- return renderEvidenceLineWindows([
2058
- { startIndex: 0, endIndex: lines.length - 1 },
2059
- ], lines);
2060
- }
2061
- return compactSelectedSnippetFromLines(file, lines, terms);
2062
- }
2063
- function taskCriticalSourceEvidenceWindows(task, file, lines) {
2064
- const branch = classifyTaskBranch(task.toLowerCase());
2065
- const path = file.path;
2066
- const ranges = [];
2067
- if (branch === "runtime") {
2068
- if (path === "frontend/src/pages/LandingPage.tsx") {
2069
- ranges.push({ start_line: 150, end_line: 170 });
2070
- }
2071
- else if (path === "frontend/src/hooks/useMessageActions.ts") {
2072
- ranges.push({ start_line: 80, end_line: 122 });
2073
- }
2074
- else if (path === "frontend/src/components/chat/message-input/Input.tsx") {
2075
- ranges.push({ start_line: 480, end_line: 540 }, { start_line: 560, end_line: 610 }, { start_line: 629, end_line: 655 });
2076
- }
2077
- else if (path === "frontend/src/services/chatService.ts") {
2078
- ranges.push({ start_line: 45, end_line: 60 }, { start_line: 186, end_line: 232 });
2079
- }
2080
- else if (path === "backend/app/api/endpoints/chat.py") {
2081
- ranges.push({ start_line: 396, end_line: 460 }, { start_line: 529, end_line: 560 }, { start_line: 1299, end_line: 1383 });
2082
- }
2083
- else if (path === "backend/app/tasks/chat_processor.py") {
2084
- ranges.push({ start_line: 85, end_line: 140 });
2085
- }
2086
- else if (path === "backend/app/services/streaming/orchestrator.py") {
2087
- ranges.push({ start_line: 930, end_line: 960 }, { start_line: 979, end_line: 1120 });
2088
- }
2089
- else if (path === "backend/app/services/runtime/resolver.py") {
2090
- ranges.push({ start_line: 16, end_line: 45 });
2091
- }
2092
- else if (path === "backend/app/services/model_provider_auth.py") {
2093
- ranges.push({ start_line: 42, end_line: 95 });
2094
- }
2095
- else if (path === "backend/app/services/claude_agent.py") {
2096
- ranges.push({ start_line: 56, end_line: 75 }, { start_line: 829, end_line: 872 });
2097
- }
2098
- else if (path === "frontend/src/components/chat/thinking-mode-selector/ThinkingModeSelector.tsx") {
2099
- ranges.push({ start_line: 16, end_line: 83 });
2100
- }
2101
- else if (path === "frontend/src/components/chat/permission-mode-selector/PermissionModeSelector.tsx") {
2102
- ranges.push({ start_line: 1, end_line: 120 });
2103
- }
2104
- else if (path === "frontend/src/components/chat/model-selector/ModelSelector.tsx") {
2105
- ranges.push({ start_line: 1, end_line: 160 });
2106
- }
2107
- else if (path === "frontend/src/hooks/queries/useModelQueries.ts") {
2108
- ranges.push({ start_line: 1, end_line: 160 });
2109
- }
2110
- else if (path === "frontend/src/utils/modelProvider.ts") {
2111
- ranges.push({ start_line: 1, end_line: 160 });
2112
- }
2113
- else if (path === "backend/app/services/queue.py") {
2114
- ranges.push({ start_line: 1, end_line: 220 });
2115
- }
2116
- else if (path === "backend/app/models/schemas/effort.py" ||
2117
- path === "backend/app/models/schemas/permission_mode.py") {
2118
- ranges.push({ start_line: 1, end_line: 160 });
2119
- }
2120
- else if (path === "backend/app/services/runtime/claude.py") {
2121
- ranges.push({ start_line: 1, end_line: 92 });
2122
- }
2123
- else if (path === "backend/app/services/runtime/codex.py") {
2124
- ranges.push({ start_line: 111, end_line: 180 }, { start_line: 212, end_line: 264 });
2125
- }
2126
- else if (path === "backend/app/api/endpoints/ai_models.py") {
2127
- ranges.push({ start_line: 24, end_line: 38 });
2128
- }
2129
- else if (path === "backend/app/models/db_models/ai_model.py") {
2130
- ranges.push({ start_line: 22, end_line: 38 });
2131
- }
611
+ return "TypeScript";
2132
612
  }
2133
- else if (branch === "review" &&
2134
- path === "frontend/src/components/chat/message-input/Input.tsx") {
2135
- ranges.push({ start_line: 430, end_line: 650 }, { start_line: 880, end_line: 1060 });
613
+ if (basename.endsWith(".jsx")) {
614
+ return "JSX";
2136
615
  }
2137
- else if (branch === "debug" &&
2138
- path === "frontend/src/components/right-panel/RightPanelDevtools.tsx") {
2139
- ranges.push({ start_line: 560, end_line: 725 }, { start_line: 900, end_line: 930 });
616
+ if (basename.endsWith(".js")) {
617
+ return "JavaScript";
2140
618
  }
2141
- else if (branch === "debug" &&
2142
- path === "frontend/src/components/chat/chat-window/Chat.tsx") {
2143
- ranges.push({ start_line: 450, end_line: 615 }, { start_line: 670, end_line: 750 });
619
+ if (basename.endsWith(".py")) {
620
+ return "Python";
2144
621
  }
2145
- if (ranges.length === 0) {
2146
- return [];
622
+ if (basename.endsWith(".rs")) {
623
+ return "Rust";
624
+ }
625
+ if (basename.endsWith(".md") || basename.endsWith(".mdx")) {
626
+ return "Markdown";
627
+ }
628
+ if (basename.endsWith(".json")) {
629
+ return "JSON";
630
+ }
631
+ if (basename.endsWith(".yml") || basename.endsWith(".yaml")) {
632
+ return "YAML";
2147
633
  }
2148
- return evidenceWindowsForLineRanges(lines, ranges);
634
+ return "file";
2149
635
  }
2150
- function evidenceWindowsForLineRanges(lines, ranges) {
2151
- const windows = [];
2152
- for (const range of ranges) {
2153
- const indexes = lines
2154
- .map((line, index) => ({ line, index }))
2155
- .filter(({ line }) => line.lineNumber >= range.start_line && line.lineNumber <= range.end_line)
2156
- .map(({ index }) => index);
2157
- if (indexes.length === 0) {
2158
- continue;
2159
- }
2160
- windows.push({
2161
- startIndex: Math.min(...indexes),
2162
- endIndex: Math.max(...indexes),
2163
- });
636
+ function isTestPath(path, basename) {
637
+ return (path.startsWith("test/") ||
638
+ path.startsWith("tests/") ||
639
+ path.includes("/test/") ||
640
+ path.includes("/tests/") ||
641
+ path.includes("/__tests__/") ||
642
+ basename.includes(".test.") ||
643
+ basename.includes(".spec.") ||
644
+ basename.startsWith("test_"));
645
+ }
646
+ function rangePurpose(ranges) {
647
+ if (ranges.length === 0) {
648
+ return "full-file context";
2164
649
  }
2165
- return windows;
650
+ const rendered = ranges
651
+ .slice(0, 3)
652
+ .map((range) => range.start_line === range.end_line
653
+ ? `${range.start_line}`
654
+ : `${range.start_line}-${range.end_line}`);
655
+ const suffix = ranges.length > rendered.length ? ` +${ranges.length - rendered.length}` : "";
656
+ return `ranges ${rendered.join(", ")}${suffix}`;
2166
657
  }
2167
- function compactSelectedSnippetFromLines(file, lines, terms) {
2168
- if (shouldInlineSmallEvidenceFile(file, lines)) {
2169
- return renderEvidenceLineWindows([
2170
- { startIndex: 0, endIndex: lines.length - 1 },
2171
- ], lines);
658
+ function renderTopMustReadRanges(input) {
659
+ if (input.selected.length === 0) {
660
+ return "- No selected ranges available.";
2172
661
  }
662
+ const candidates = topMustReadRangeCandidates(input)
663
+ .sort((left, right) => right.score - left.score || left.order - right.order)
664
+ .slice(0, 8);
665
+ return candidates
666
+ .map((candidate, index) => {
667
+ const header = `${index + 1}. ${renderRootPrefix(candidate.file.root_index, input.showRootLabels)}${candidate.file.path}:${singleRangeLabel(candidate.range)} - ${candidate.reason}`;
668
+ const snippet = renderTopMustReadSnippet(candidate, index, input.task);
669
+ return snippet.length === 0 ? header : `${header}\n\n${snippet}`;
670
+ })
671
+ .join("\n");
672
+ }
673
+ function topMustReadRangeCandidates(input) {
2173
674
  const candidates = [];
2174
- for (const [index, line] of lines.entries()) {
2175
- if (line.text.trim().length === 0) {
2176
- continue;
2177
- }
2178
- const score = evidenceLineScore(line.text, terms, file.path);
2179
- if (terms.length > 0 && score <= 0) {
2180
- continue;
675
+ let order = 0;
676
+ for (const file of input.selected) {
677
+ const searchTerms = selectedFileSearchTerms(file, input.search);
678
+ const searchHitCount = input.search.filter((record) => record.root_index === file.root_index && record.path === file.path).length;
679
+ const sourceLines = splitSelectedFileContentByRange(file);
680
+ for (const [rangeIndex, range] of file.ranges.entries()) {
681
+ const symbols = symbolsFromLines(sourceLines[rangeIndex] ?? []);
682
+ const summary = rangeEvidenceSummary({
683
+ task: input.task,
684
+ path: file.path,
685
+ source: file.source,
686
+ symbols,
687
+ searchTerms,
688
+ });
689
+ const score = (file.source === "focus" ? 40 : 20)
690
+ + Math.min(searchHitCount, 6) * 3
691
+ + (pathLooksLikeTest(file.path) ? 8 : 0)
692
+ - rangeIndex;
693
+ const hitText = searchHitCount === 0
694
+ ? ""
695
+ : `; ${searchHitCount} selected-file search hit${searchHitCount === 1 ? "" : "s"}`;
696
+ candidates.push({
697
+ file,
698
+ range,
699
+ score,
700
+ order,
701
+ reason: `${summary}${hitText}`,
702
+ });
703
+ order += 1;
2181
704
  }
2182
- candidates.push({ index, score });
2183
705
  }
2184
- if (candidates.length > 0) {
2185
- const anchors = candidates
2186
- .sort((left, right) => right.score - left.score ||
2187
- lines[left.index].lineNumber - lines[right.index].lineNumber ||
2188
- left.index - right.index)
2189
- .slice(0, SOURCE_EVIDENCE_MAX_WINDOWS)
2190
- .map((candidate) => candidate.index);
2191
- return renderEvidenceLineWindows(mergedEvidenceWindows(lines, anchors), lines);
2192
- }
2193
- return renderEvidenceLineWindows([{ startIndex: 0, endIndex: Math.min(lines.length - 1, SOURCE_EVIDENCE_FALLBACK_LINES - 1) }], lines);
706
+ return candidates;
2194
707
  }
2195
- function shouldInlineExpandedEvidenceFile(task, file, lines) {
2196
- if (classifyTaskBranch(task.toLowerCase()) === "generic") {
2197
- return false;
708
+ function singleRangeLabel(range) {
709
+ return range.start_line === range.end_line
710
+ ? `${range.start_line}`
711
+ : `${range.start_line}-${range.end_line}`;
712
+ }
713
+ function renderTopMustReadSnippet(candidate, index, task) {
714
+ if (isCodemapOnlyDisposition(candidate.file.disposition)) {
715
+ return "";
2198
716
  }
2199
- if (lines.length > SOURCE_EVIDENCE_EXPANDED_FILE_MAX_LINES) {
2200
- return false;
717
+ if (index >= 3) {
718
+ return "";
2201
719
  }
2202
- if (file.bytes > SOURCE_EVIDENCE_EXPANDED_FILE_MAX_BYTES) {
2203
- return false;
720
+ const rangeIndex = candidate.file.ranges.findIndex((range) => range.start_line === candidate.range.start_line &&
721
+ range.end_line === candidate.range.end_line);
722
+ const rangeLines = splitSelectedFileContentByRange(candidate.file)[rangeIndex] ?? [];
723
+ const numbered = topMustReadSnippetLines(candidate.range, rangeLines, evidenceTermsForTask(task), candidate.file.path);
724
+ if (numbered.length === 0) {
725
+ return "";
2204
726
  }
2205
- const preferredPaths = new Set(generatedAnswerPreferredEvidencePaths(task.toLowerCase()));
2206
- return preferredPaths.has(file.path) || taskSpecificFileRole(task, file.path) != null;
727
+ return ["```text", ...numbered, "```"].join("\n");
2207
728
  }
2208
- const SOURCE_EVIDENCE_WINDOW_RADIUS = 4;
2209
- const SOURCE_EVIDENCE_MAX_FILES = 10;
2210
- const SOURCE_EVIDENCE_MAX_WINDOWS = 6;
2211
- const SOURCE_EVIDENCE_SMALL_FILE_MAX_BYTES = 2_000;
2212
- const SOURCE_EVIDENCE_SMALL_FILE_MAX_LINES = 20;
2213
- const SOURCE_EVIDENCE_FALLBACK_LINES = 32;
2214
- const SOURCE_EVIDENCE_EXPANDED_FILE_MAX_BYTES = 60_000;
2215
- const SOURCE_EVIDENCE_EXPANDED_FILE_MAX_LINES = 650;
2216
- function selectedEvidenceLines(file) {
2217
- const linesByRange = splitSelectedFileContentByRange(file);
2218
- const lines = [];
2219
- for (const [rangeIndex, rangeLines] of linesByRange.entries()) {
2220
- const range = file.ranges[rangeIndex];
2221
- if (range == null) {
2222
- continue;
2223
- }
2224
- for (const [lineOffset, rawLine] of rangeLines.entries()) {
2225
- lines.push({
2226
- lineNumber: range.start_line + lineOffset,
2227
- rangeIndex,
2228
- text: rawLine.trimEnd(),
2229
- });
729
+ function topMustReadSnippetLines(range, rangeLines, terms, path) {
730
+ const evidenceLines = rangeLines
731
+ .map((line, lineIndex) => ({
732
+ lineNumber: range.start_line + lineIndex,
733
+ rangeIndex: 0,
734
+ text: line.trimEnd(),
735
+ }))
736
+ .filter((line) => line.text.trim().length > 0);
737
+ const scored = evidenceLines
738
+ .map((line, index) => ({
739
+ index,
740
+ score: evidenceLineScore(line.text, terms, path),
741
+ }))
742
+ .filter((candidate) => candidate.score > 0)
743
+ .sort((left, right) => right.score - left.score ||
744
+ evidenceLines[left.index].lineNumber -
745
+ evidenceLines[right.index].lineNumber);
746
+ if (scored.length > 0) {
747
+ const rendered = renderEvidenceLineWindows(mergedEvidenceWindows(evidenceLines, scored.slice(0, 2).map((candidate) => candidate.index)), evidenceLines)
748
+ .split("\n")
749
+ .filter((line) => line.length > 0)
750
+ .slice(0, 8);
751
+ if (rendered.length > 0) {
752
+ return rendered;
2230
753
  }
2231
754
  }
2232
- return trimBlankEvidenceEdges(lines);
2233
- }
2234
- function trimBlankEvidenceEdges(lines) {
2235
- let start = 0;
2236
- let end = lines.length;
2237
- while (start < end && lines[start].text.trim().length === 0) {
2238
- start += 1;
2239
- }
2240
- while (end > start && lines[end - 1].text.trim().length === 0) {
2241
- end -= 1;
2242
- }
2243
- return lines.slice(start, end);
755
+ return evidenceLines
756
+ .slice(0, 8)
757
+ .map((line) => `${line.lineNumber}: ${trimEvidenceLine(line.text)}`);
2244
758
  }
2245
- function shouldInlineSmallEvidenceFile(file, lines) {
2246
- return (file.bytes <= SOURCE_EVIDENCE_SMALL_FILE_MAX_BYTES &&
2247
- lines.length <= SOURCE_EVIDENCE_SMALL_FILE_MAX_LINES);
759
+ function pathLooksLikeTest(path) {
760
+ return /(?:^|\/)(?:tests?|__tests__)\//.test(path) || /\.test\.[^.]+$/.test(path);
2248
761
  }
762
+ const SOURCE_EVIDENCE_WINDOW_RADIUS = 4;
2249
763
  function mergedEvidenceWindows(lines, anchors) {
2250
764
  const windows = anchors
2251
765
  .map((anchor) => evidenceWindowAroundAnchor(lines, anchor))
@@ -2308,86 +822,13 @@ function renderEvidenceLineWindows(windows, lines) {
2308
822
  return rendered.join("\n");
2309
823
  }
2310
824
  function evidenceTermsForTask(task) {
2311
- const normalizedTask = task.toLowerCase();
2312
- const branch = classifyTaskBranch(normalizedTask);
2313
- if (branch === "runtime") {
2314
- return [
2315
- "runtime_permission_mode",
2316
- "runtime_effort_level",
2317
- "permission_mode",
2318
- "effort_level",
2319
- "permissionMode",
2320
- "effortLevel",
2321
- "ComposerRuntimeControls",
2322
- "SDK_PERMISSION_MODE_MAP",
2323
- "_build_claude_options",
2324
- "ModelSelector",
2325
- "useModelQueries",
2326
- "modelProvider",
2327
- "PermissionModeSelector",
2328
- "ThinkingModeSelector",
2329
- "pending_initial_prompt",
2330
- "createChat",
2331
- "sendMessage",
2332
- "messageQueue",
2333
- "normalize_permission_mode",
2334
- "normalize_effort_level",
2335
- ];
2336
- }
2337
- if (branch === "review") {
2338
- return [
2339
- "codex",
2340
- "approval",
2341
- "permission",
2342
- "session_id",
2343
- "RuntimeSession",
2344
- "runtime_session",
2345
- "CodexRuntime",
2346
- "process_event",
2347
- "FileUploadDialog",
2348
- "SDK_PERMISSION_MODE_MAP",
2349
- "_build_claude_options",
2350
- "ModelSelector",
2351
- "useModelQueries",
2352
- "modelProvider",
2353
- "supports",
2354
- "file",
2355
- ];
2356
- }
2357
- if (branch === "debug") {
2358
- return [
2359
- "filteredEntries",
2360
- "originalIndex",
2361
- "entry_id",
2362
- "getItemKey",
2363
- "handleEntryClick",
2364
- "pendingScroll",
2365
- "toolUseId",
2366
- "queryKeys",
2367
- "useDevtoolsQueries",
2368
- "useMessageCache",
2369
- "messageCacheOrder",
2370
- "virtualizer",
2371
- ];
2372
- }
2373
825
  return task
2374
826
  .toLowerCase()
2375
827
  .split(/[^a-z0-9_]+/)
2376
828
  .filter((term) => term.length >= 4)
2377
829
  .slice(0, 8);
2378
830
  }
2379
- function hasSnippetEligibleContent(file) {
2380
- const trimmed = file.content.trim();
2381
- return (trimmed.length > 0 &&
2382
- trimmed !== "[redacted-sensitive-content]" &&
2383
- !hasHighConfidenceSecret(trimmed));
2384
- }
2385
- function taskMentionsPathToken(task, path) {
2386
- const basename = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
2387
- const stem = basename.replace(/\.[^.]+$/, "");
2388
- return stem.length >= 4 && task.includes(stem);
2389
- }
2390
- function evidenceLineScore(line, terms, path) {
831
+ function evidenceLineScore(line, terms, _path) {
2391
832
  const normalized = line.toLowerCase();
2392
833
  let score = 0;
2393
834
  for (const term of terms) {
@@ -2407,64 +848,6 @@ function evidenceLineScore(line, terms, path) {
2407
848
  if (/[.=:(]\s*[^=]|=>|append|values|request|response|onChange|value=/.test(line)) {
2408
849
  score += 3;
2409
850
  }
2410
- if (/\b(runtime_|permission|effort|approval|session|entry_id|originalIndex|getItemKey|handleEntryClick|filteredEntries|toolUseId)\b/i.test(line)) {
2411
- score += 4;
2412
- }
2413
- score += pathAwareEvidenceBoost(path, line);
2414
- return score;
2415
- }
2416
- function pathAwareEvidenceBoost(path, line) {
2417
- if (path == null) {
2418
- return 0;
2419
- }
2420
- const normalizedPath = path.toLowerCase();
2421
- const normalizedLine = line.toLowerCase();
2422
- let score = 0;
2423
- if (normalizedPath.endsWith("/components/chat/message-input/input.tsx")) {
2424
- if (normalizedLine.includes("await queuemessage("))
2425
- score += 80;
2426
- if (normalizedLine.includes("handlesubmit"))
2427
- score += 55;
2428
- if (normalizedLine.includes("permissionmode") && !normalizedLine.includes("import"))
2429
- score += 35;
2430
- if (normalizedLine.includes("effortlevel") && !normalizedLine.includes("import"))
2431
- score += 35;
2432
- if (normalizedLine.includes("onmessagesend("))
2433
- score += 30;
2434
- }
2435
- if (normalizedPath.endsWith("/pages/chatpage.tsx")) {
2436
- if (normalizedLine.includes("usechatstreaming"))
2437
- score += 65;
2438
- if (normalizedLine.includes("onmessagesend") || normalizedLine.includes("handlemessagesend")) {
2439
- score += 45;
2440
- }
2441
- if (normalizedLine.includes("pendinginitialprompt"))
2442
- score += 40;
2443
- }
2444
- if (normalizedPath.endsWith("/services/streaming/orchestrator.py")) {
2445
- if (normalizedLine.includes("ai_service.get_ai_stream"))
2446
- score += 90;
2447
- if (normalizedLine.includes("permission_mode=permission_mode"))
2448
- score += 70;
2449
- if (normalizedLine.includes("effort_level=effort_level"))
2450
- score += 70;
2451
- if (normalizedLine.includes("resolve_runtime_for_model"))
2452
- score += 55;
2453
- if (normalizedLine.includes("run_chat_stream"))
2454
- score += 40;
2455
- }
2456
- if (normalizedPath.endsWith("/tests/services/test_pending_initial_prompt_service.py")) {
2457
- if (normalizedLine.includes("assert request.permission_mode"))
2458
- score += 80;
2459
- if (normalizedLine.includes("assert request.effort_level"))
2460
- score += 80;
2461
- if (normalizedLine.includes("dispatch_kwargs"))
2462
- score += 65;
2463
- if (normalizedLine.includes("runtime_permission_mode="))
2464
- score += 55;
2465
- if (normalizedLine.includes("runtime_effort_level="))
2466
- score += 55;
2467
- }
2468
851
  return score;
2469
852
  }
2470
853
  function trimEvidenceLine(line) {
@@ -2474,368 +857,6 @@ function trimEvidenceLine(line) {
2474
857
  }
2475
858
  return `${line.slice(0, maxLength - 3)}...`;
2476
859
  }
2477
- function briefRef(selected, showRootLabels, path) {
2478
- const file = selected.find((item) => item.path === path);
2479
- if (file == null) {
2480
- return path;
2481
- }
2482
- return briefFileLabel(file, showRootLabels);
2483
- }
2484
- function briefFileLabel(file, showRootLabels) {
2485
- return `${renderRootPrefix(file.root_index, showRootLabels)}${file.path}:${briefRangeLabel(file.ranges)}`;
2486
- }
2487
- function briefRangeLabel(ranges) {
2488
- if (ranges.length === 0) {
2489
- return "full";
2490
- }
2491
- return ranges
2492
- .slice(0, 3)
2493
- .map((range) => range.start_line === range.end_line
2494
- ? `${range.start_line}`
2495
- : `${range.start_line}-${range.end_line}`)
2496
- .join(",");
2497
- }
2498
- function selectedCoverage(selected, expectedPaths) {
2499
- const present = expectedPaths.filter((path) => selected.has(path)).slice(0, 5);
2500
- if (present.length > 0) {
2501
- return present.join(", ");
2502
- }
2503
- return "no expected path selected; use selected areas and omissions below to find the gap";
2504
- }
2505
- function renderRelationshipsAndRisks(input) {
2506
- const normalizedTask = input.task.toLowerCase();
2507
- const branch = classifyTaskBranch(normalizedTask);
2508
- const selected = selectedPathSet(input.selected);
2509
- const lines = [];
2510
- if (branch === "runtime") {
2511
- lines.push("- Architecture: ComposerRuntimeControls/useChatRuntimeSettings define the user intent, Input/useMessageActions/chatService serialize it, queueService/messageQueueStore preserve it offline, chat.py/ChatService persist it, and chat_processor/orchestrator/runtime adapters execute it.");
2512
- lines.push(relationshipLine("Frontend settings and send path", selected, input.showRootLabels, [
2513
- "frontend/src/components/chat/message-input/ComposerRuntimeControls.tsx",
2514
- "frontend/src/hooks/useChatRuntimeSettings.ts",
2515
- "frontend/src/components/chat/message-input/Input.tsx",
2516
- "frontend/src/hooks/useMessageActions.ts",
2517
- "frontend/src/hooks/useChatStreaming.ts",
2518
- "frontend/src/services/chatService.ts",
2519
- "frontend/src/services/queueService.ts",
2520
- "frontend/src/store/messageQueueStore.ts",
2521
- ]));
2522
- lines.push(relationshipLine("Backend validation, persistence, and runtime dispatch", selected, input.showRootLabels, [
2523
- "backend/app/api/endpoints/chat.py",
2524
- "backend/app/services/chat.py",
2525
- "backend/app/services/queue.py",
2526
- "backend/app/tasks/chat_processor.py",
2527
- "backend/app/services/streaming/orchestrator.py",
2528
- "backend/app/services/runtime/codex.py",
2529
- ]));
2530
- lines.push(relationshipLine("Contracts and provider semantics", selected, input.showRootLabels, [
2531
- "backend/app/models/schemas/chat.py",
2532
- "backend/app/models/db_models/chat.py",
2533
- "backend/app/models/schemas/effort.py",
2534
- "backend/app/models/schemas/permission_mode.py",
2535
- "backend/app/services/runtime/base.py",
2536
- "backend/app/services/runtime/claude.py",
2537
- ]));
2538
- lines.push("- Risks: create-chat can persist defaults while send/queue carries updated settings; queued work can replay stale effort names; Codex and Claude permission modes diverge; pending initial prompts can redispatch without the stored snapshot.");
2539
- }
2540
- else if (branch === "review") {
2541
- lines.push("- Architecture: resolver selects Codex, CodexRuntime streams SDK events, codex_approval bridges approval callbacks into PermissionManager records, permissions endpoints resolve them, and frontend hooks surface the decision. Treat RuntimeSessionService as a review target, not as proven-live wiring: selected source may show Codex v1 no-resume/deferred runtime_sessions integration.");
2542
- lines.push(relationshipLine("Runtime/session core", selected, input.showRootLabels, [
2543
- "backend/app/services/runtime/codex.py",
2544
- "backend/app/services/runtime/resolver.py",
2545
- "backend/app/services/runtime_session_service.py",
2546
- "backend/app/services/runtime/codex_stream_processor.py",
2547
- "backend/app/services/streaming/orchestrator.py",
2548
- ]));
2549
- lines.push(relationshipLine("Approval and permission contracts", selected, input.showRootLabels, [
2550
- "backend/app/services/runtime/codex_approval.py",
2551
- "backend/app/services/permission_manager.py",
2552
- "backend/app/api/endpoints/permissions.py",
2553
- "backend/app/models/schemas/permissions.py",
2554
- "backend/app/core/config.py",
2555
- "backend/app/constants.py",
2556
- "frontend/src/hooks/usePermissionRequest.ts",
2557
- ]));
2558
- lines.push(relationshipLine("Frontend provider/upload consumers and tests", selected, input.showRootLabels, [
2559
- "frontend/src/utils/modelProvider.ts",
2560
- "frontend/src/components/ui/FileUploadDialog.tsx",
2561
- "frontend/src/hooks/useMessageActions.ts",
2562
- "frontend/src/hooks/__tests__/useMessageActions.codex.test.tsx",
2563
- "backend/tests/services/runtime/test_codex_approval_bridge.py",
2564
- "backend/tests/test_permissions.py",
2565
- ]));
2566
- lines.push("- Risks: Codex capability metadata must remain fail-closed, approval timeouts can leave reader tasks waiting, Redis TTLs can disagree with SDK wait windows, denied/cross-chat responses can mutate the wrong request, and unit tests may miss live SDK payload drift.");
2567
- }
2568
- else if (branch === "debug") {
2569
- lines.push("- Architecture: devtools.py/session_trace_archive produce trace entries, RightPanelDevtools filters and virtualizes them, devtoolsNavStore carries the selected entry target, Chat resolves cached messages, and MessageRenderer/ToolCard expose scroll and expansion anchors.");
2570
- lines.push(relationshipLine("Trace fetch and archive shape", selected, input.showRootLabels, [
2571
- "backend/app/api/endpoints/devtools.py",
2572
- "backend/app/services/session_trace_archive.py",
2573
- "backend/app/models/db_models/session_trace_archive.py",
2574
- "backend/app/services/codex_trace.py",
2575
- "frontend/src/types/devtools.types.ts",
2576
- ]));
2577
- lines.push(relationshipLine("Right-panel row identity and navigation", selected, input.showRootLabels, [
2578
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
2579
- "frontend/src/components/right-panel/devtools-helpers.ts",
2580
- "frontend/src/components/right-panel/RightPanel.tsx",
2581
- "frontend/src/store/devtoolsNavStore.ts",
2582
- "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
2583
- "frontend/src/components/right-panel/__tests__/devtools-helpers.test.ts",
2584
- ]));
2585
- lines.push(relationshipLine("Chat cache, message lookup, and tool expansion", selected, input.showRootLabels, [
2586
- "frontend/src/components/chat/chat-window/Chat.tsx",
2587
- "frontend/src/hooks/queries/queryKeys.ts",
2588
- "frontend/src/utils/messageCacheOrder.ts",
2589
- "frontend/src/types/chat.types.ts",
2590
- "frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
2591
- "frontend/src/components/chat/tools/common/ToolExpandContext.ts",
2592
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
2593
- ]));
2594
- lines.push("- Risks: filtered row position can be confused with original trace index, virtualizer keys can recycle stale rows, cached-message deduping can hide the target message, and tool expansion can resolve the message while missing the nested tool anchor.");
2595
- }
2596
- else {
2597
- lines.push("- Architecture: selected files are grouped by source area; inspect focus files first, then contract/type/test closure.");
2598
- lines.push("- Risks: any material omissions listed below may require follow-up discovery before editing.");
2599
- }
2600
- const omissionRisk = omissionRiskLine(branch, selected, input.omissions);
2601
- if (omissionRisk != null) {
2602
- lines.push(omissionRisk);
2603
- }
2604
- const material = input.omissions.filter(isMaterialOmission).slice(0, 3);
2605
- if (material.length > 0) {
2606
- lines.push(`- Known gaps: ${material
2607
- .map((omission) => {
2608
- const paths = omission.paths?.slice(0, 2).join(", ");
2609
- const reason = humanOmissionReason(omission.reason);
2610
- return paths == null || paths.length === 0 ? reason : `${reason} (${paths})`;
2611
- })
2612
- .join("; ")}.`);
2613
- }
2614
- return lines.filter((line) => line.trim().length > 0).join("\n");
2615
- }
2616
- function omissionRiskLine(branch, selected, omissions) {
2617
- const omitted = new Set(omissions.flatMap((omission) => omission.paths ?? []));
2618
- const missing = (paths) => paths.filter((path) => !selected.has(path) || omitted.has(path));
2619
- if (branch === "runtime") {
2620
- return formatOmissionRiskEntries([
2621
- ["normal/queued send parity", missing([
2622
- "frontend/src/components/chat/message-input/Input.tsx",
2623
- "frontend/src/hooks/useChatStreaming.ts",
2624
- "frontend/src/hooks/useMessageActions.ts",
2625
- "frontend/src/services/queueService.ts",
2626
- ])],
2627
- ["pending redispatch and worker persistence", missing([
2628
- "backend/app/services/chat.py",
2629
- "backend/app/tasks/chat_processor.py",
2630
- "backend/tests/services/test_pending_initial_prompt_service.py",
2631
- ])],
2632
- ["runtime adapter translation", missing([
2633
- "backend/app/services/runtime/codex.py",
2634
- "backend/app/services/runtime/claude.py",
2635
- "backend/tests/services/runtime/test_codex_runtime.py",
2636
- ])],
2637
- ]);
2638
- }
2639
- if (branch === "review") {
2640
- return formatOmissionRiskEntries([
2641
- ["security/approval TTLs", missing([
2642
- "backend/app/services/runtime/codex_approval.py",
2643
- "backend/app/api/endpoints/permissions.py",
2644
- "backend/tests/services/runtime/test_codex_approval_bridge.py",
2645
- ])],
2646
- ["runtime-session contamination", missing([
2647
- "backend/app/services/runtime/codex.py",
2648
- "backend/app/services/streaming/orchestrator.py",
2649
- "backend/app/services/runtime_session_service.py",
2650
- "backend/tests/services/runtime/test_codex_runtime_invariants.py",
2651
- ])],
2652
- ["frontend attachment gating", missing([
2653
- "frontend/src/components/chat/message-input/Input.tsx",
2654
- "frontend/src/components/ui/FileUploadDialog.tsx",
2655
- "frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
2656
- ])],
2657
- ]);
2658
- }
2659
- if (branch === "debug") {
2660
- return formatOmissionRiskEntries([
2661
- ["row identity and virtualizer tests", missing([
2662
- "frontend/src/components/right-panel/RightPanelDevtools.tsx",
2663
- "frontend/src/components/right-panel/__tests__/right-panel-devtools.test.tsx",
2664
- ])],
2665
- ["chat cache lookup", missing([
2666
- "frontend/src/components/chat/chat-window/Chat.tsx",
2667
- "frontend/src/hooks/queries/queryKeys.ts",
2668
- "frontend/src/utils/messageCacheOrder.ts",
2669
- ])],
2670
- ["message/tool anchor expansion", missing([
2671
- "frontend/src/components/chat/message-bubble/MessageRenderer.tsx",
2672
- "frontend/src/components/chat/tools/common/ToolCard.tsx",
2673
- "frontend/src/components/chat/tools/common/__tests__/toolExpand.test.tsx",
2674
- ])],
2675
- ]);
2676
- }
2677
- return undefined;
2678
- }
2679
- function formatOmissionRiskEntries(entries) {
2680
- const risks = entries.filter((entry) => entry[1].length > 0);
2681
- if (risks.length === 0) {
2682
- return undefined;
2683
- }
2684
- return `- Omission risk: ${risks
2685
- .slice(0, 3)
2686
- .map(([label, paths]) => `${label} needs ${paths.slice(0, 2).join(", ")}`)
2687
- .join("; ")}.`;
2688
- }
2689
- function mentionsAny(input, needles) {
2690
- return needles.some((needle) => input.includes(needle));
2691
- }
2692
- function renderConfirmedReviewSourceAreas(selected, showRootLabels) {
2693
- const expected = [
2694
- "backend/app/services/runtime/resolver.py",
2695
- "backend/app/services/runtime/codex_approval.py",
2696
- "backend/app/models/schemas/permissions.py",
2697
- "backend/tests/services/runtime/test_codex_approval_bridge.py",
2698
- ];
2699
- const selectedPaths = selectedPathSet(selected);
2700
- const present = expected
2701
- .filter((path) => selectedPaths.has(path))
2702
- .map((path) => briefRef(selected, showRootLabels, path));
2703
- const missing = expected.filter((path) => !selectedPaths.has(path));
2704
- if (present.length === 0) {
2705
- return `- Confirmed source areas: none of the preferred review guardrails are selected. Verify before quoting: ${missing.join(", ")}.`;
2706
- }
2707
- const suffix = missing.length === 0
2708
- ? ""
2709
- : ` Additional verification targets: ${missing.join(", ")}.`;
2710
- return `- Confirmed source areas: selected ranges around ${present.join(", ")} are the guardrails to quote as facts.${suffix}`;
2711
- }
2712
- const REVIEW_TASK_KEYWORDS = [
2713
- "code review",
2714
- "review",
2715
- "codex approval",
2716
- ];
2717
- const DEBUG_TASK_KEYWORDS = [
2718
- "devtools",
2719
- "trace row",
2720
- "row-to-message",
2721
- "click-through",
2722
- "highlight",
2723
- ];
2724
- const RUNTIME_TASK_KEYWORDS = [
2725
- "runtime settings",
2726
- "runtime_permission_mode",
2727
- "runtime_effort_level",
2728
- "queue dispatch",
2729
- ];
2730
- function classifyTaskBranch(normalizedTask) {
2731
- if (mentionsAny(normalizedTask, DEBUG_TASK_KEYWORDS)) {
2732
- return "debug";
2733
- }
2734
- if (mentionsAny(normalizedTask, REVIEW_TASK_KEYWORDS)) {
2735
- return "review";
2736
- }
2737
- if (mentionsAny(normalizedTask, RUNTIME_TASK_KEYWORDS)) {
2738
- return "runtime";
2739
- }
2740
- return "generic";
2741
- }
2742
- function selectedPathSet(selected) {
2743
- return new Set(selected.map((file) => file.path));
2744
- }
2745
- function relationshipLine(label, selected, showRootLabels, paths) {
2746
- const present = paths.filter((path) => selected.has(path));
2747
- const missing = paths.filter((path) => !selected.has(path));
2748
- const chain = present.length === 0
2749
- ? "unavailable; no expected anchors selected"
2750
- : present
2751
- .slice(0, 8)
2752
- .map((path) => `${renderRootPrefix(0, showRootLabels)}${path}`)
2753
- .join(" -> ");
2754
- return `- ${label} chain: ${chain}.`;
2755
- }
2756
- function taskActionCue(task) {
2757
- const verbs = [
2758
- "add",
2759
- "audit",
2760
- "build",
2761
- "debug",
2762
- "diagnose",
2763
- "explain",
2764
- "fix",
2765
- "harden",
2766
- "implement",
2767
- "inspect",
2768
- "investigate",
2769
- "plan",
2770
- "refactor",
2771
- "remove",
2772
- "review",
2773
- "summarize",
2774
- "test",
2775
- "update",
2776
- "verify",
2777
- ];
2778
- const words = new Set(task.toLowerCase().match(/[a-z][a-z0-9_-]*/g) ?? []);
2779
- return verbs.find((verb) => words.has(verb));
2780
- }
2781
- function relationshipCuesFromSelected(selected) {
2782
- if (selected.length === 0) {
2783
- return "no selected files";
2784
- }
2785
- const sourceCounts = new Map();
2786
- for (const file of selected) {
2787
- sourceCounts.set(file.source, (sourceCounts.get(file.source) ?? 0) + 1);
2788
- }
2789
- const sources = [...sourceCounts.entries()]
2790
- .sort(([left], [right]) => left.localeCompare(right))
2791
- .map(([source, count]) => `${source}:${count}`)
2792
- .join(", ");
2793
- const areas = renderSelectedPathGroups(selected).slice(0, 3).join("; ");
2794
- return `${sources}${areas.length === 0 ? "" : ` across ${areas}`}`;
2795
- }
2796
- function riskCuesFromContext(input) {
2797
- if (input.materialOmissions.length > 0) {
2798
- return input.materialOmissions
2799
- .slice(0, 3)
2800
- .map((omission) => humanOmissionReason(omission.reason))
2801
- .join(", ");
2802
- }
2803
- const closure = closureFiles(input.selected, input.showRootLabels).slice(0, 3);
2804
- if (closure.length > 0) {
2805
- return `contract files selected (${closure.join(", ")})`;
2806
- }
2807
- if (input.selectedSearch.length === 0) {
2808
- return "no selected-file search hits";
2809
- }
2810
- return "none recorded";
2811
- }
2812
- function renderSelectedPathGroups(selected) {
2813
- const counts = new Map();
2814
- for (const file of selected) {
2815
- const group = pathGroup(file.path);
2816
- counts.set(group, (counts.get(group) ?? 0) + 1);
2817
- }
2818
- return [...counts.entries()]
2819
- .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
2820
- .slice(0, 5)
2821
- .map(([group, count]) => `${group} (${count})`);
2822
- }
2823
- function pathGroup(path) {
2824
- const parts = path.split("/");
2825
- if (path.startsWith("backend/app/") && parts.length >= 4) {
2826
- return parts.slice(0, 4).join("/");
2827
- }
2828
- if (path.startsWith("frontend/src/") && parts.length >= 4) {
2829
- return parts.slice(0, 4).join("/");
2830
- }
2831
- return parts.slice(0, Math.min(parts.length, 2)).join("/");
2832
- }
2833
- function closureFiles(selected, showRootLabels) {
2834
- return selected
2835
- .filter((file) => isClosurePath(file.path))
2836
- .slice(0, 8)
2837
- .map((file) => `${renderRootPrefix(file.root_index, showRootLabels)}${file.path}`);
2838
- }
2839
860
  function isClosurePath(path) {
2840
861
  const normalized = path.toLowerCase();
2841
862
  const basename = normalized.split("/").at(-1) ?? normalized;
@@ -2852,9 +873,7 @@ function isClosurePath(path) {
2852
873
  basename === "constants.py" ||
2853
874
  basename === "config.ts" ||
2854
875
  basename === "config.py" ||
2855
- basename === "querykeys.ts" ||
2856
- basename === "permission_mode.py" ||
2857
- basename === "effort.py");
876
+ basename === "querykeys.ts");
2858
877
  }
2859
878
  function renderRepositoryShape(input) {
2860
879
  const { tree, selected, showRootLabels } = input;
@@ -2928,7 +947,7 @@ function renderFileContext(selected, search, showRootLabels) {
2928
947
  return `- ${renderRootPrefix(file.root_index, showRootLabels)}${file.path}: ${ranges}${suffix}`;
2929
948
  });
2930
949
  const hidden = selected.length > previewFiles.length
2931
- ? [`- ${selected.length - previewFiles.length} more selected files are listed in manifest.json and slices.jsonl.`]
950
+ ? [`- ${selected.length - previewFiles.length} more selected files are listed in manifest.json and selected-context.jsonl.`]
2932
951
  : [];
2933
952
  const selectedHitPreviews = renderSelectedHitPreviews({
2934
953
  selected,
@@ -2941,8 +960,14 @@ function renderFileContext(selected, search, showRootLabels) {
2941
960
  selected,
2942
961
  showRootLabels,
2943
962
  });
963
+ const selectedCodemapOutlines = selected.length > 12
964
+ ? []
965
+ : renderSelectedCodemapOutlines({
966
+ selected,
967
+ showRootLabels,
968
+ });
2944
969
  return [
2945
- "- Full selected source is also stored in `slices.jsonl`; the task-critical evidence is inlined above for bundle-first review.",
970
+ "- Full selected source is stored in `selected-context.jsonl`; `slices.jsonl` is the bounded candidate/debug universe and may include unselected files or ranges.",
2946
971
  `- Selected source payload: ${selected.length} files, ${totalRanges} ranges, ${totalBytes} bytes before artifact serialization.`,
2947
972
  "- Range index:",
2948
973
  ...previewFiles,
@@ -2950,6 +975,9 @@ function renderFileContext(selected, search, showRootLabels) {
2950
975
  ...(selectedRangeLandmarks.length === 0
2951
976
  ? []
2952
977
  : ["", "- Range landmarks:", ...selectedRangeLandmarks]),
978
+ ...(selectedCodemapOutlines.length === 0
979
+ ? []
980
+ : ["", "- Codemap outlines:", ...selectedCodemapOutlines]),
2953
981
  ...(selected.length > 12 || selectedHitPreviews.length === 0
2954
982
  ? []
2955
983
  : ["", "- Selected hit previews:", ...selectedHitPreviews]),
@@ -2959,6 +987,9 @@ function renderSelectedRangeLandmarks(input) {
2959
987
  const rendered = [];
2960
988
  const seen = new Set();
2961
989
  for (const file of input.selected) {
990
+ if (isCodemapOnlyDisposition(file.disposition)) {
991
+ continue;
992
+ }
2962
993
  let fileCount = 0;
2963
994
  const rangeLines = splitSelectedFileContentByRange(file);
2964
995
  file.ranges.forEach((range, rangeIndex) => {
@@ -2987,13 +1018,46 @@ function renderSelectedRangeLandmarks(input) {
2987
1018
  return rendered.slice(0, 48);
2988
1019
  }
2989
1020
  function isRangeLandmarkLine(line) {
2990
- if (symbolsFromLine(line).length > 0) {
2991
- return true;
1021
+ return symbolsFromLine(line).length > 0;
1022
+ }
1023
+ function renderSelectedCodemapOutlines(input) {
1024
+ const rendered = [];
1025
+ for (const file of input.selected) {
1026
+ if (!isCodemapOnlyDisposition(file.disposition)) {
1027
+ continue;
1028
+ }
1029
+ const map = selectedFileCodemap(file);
1030
+ const prefix = renderRootPrefix(file.root_index, input.showRootLabels);
1031
+ if (map.symbols.length === 0) {
1032
+ rendered.push(` - ${prefix}${file.path}: no symbols detected`);
1033
+ continue;
1034
+ }
1035
+ rendered.push(` - ${prefix}${file.path}:`);
1036
+ rendered.push(...map.symbols
1037
+ .slice(0, 24)
1038
+ .map((symbol) => ` - ${renderCodemapSymbolLine(symbol)}`));
1039
+ if (map.symbols.length > 24) {
1040
+ rendered.push(` - +${map.symbols.length - 24} more symbols`);
1041
+ }
2992
1042
  }
2993
- return /\b(?:boundary|needle|requestId|originalIndex|write_active_asset)\b/.test(line);
1043
+ return rendered;
1044
+ }
1045
+ function selectedFileCodemap(file) {
1046
+ return remapCodemapToSourceCoordinates(buildFileCodemap(file.path, file.content), file.ranges);
1047
+ }
1048
+ function renderCodemapSymbolLine(symbol) {
1049
+ return `${symbol.kind} ${symbol.name} ${singleRangeLabel({
1050
+ start_line: symbol.start_line,
1051
+ end_line: symbol.end_line,
1052
+ })}`;
1053
+ }
1054
+ function isCodemapOnlyDisposition(disposition) {
1055
+ return disposition === "codemap_only";
2994
1056
  }
2995
1057
  function renderSelectedHitPreviews(input) {
2996
- const selectedPaths = new Set(input.selected.map((file) => `${file.root_index}\u0000${file.path}`));
1058
+ const selectedPaths = new Set(input.selected
1059
+ .filter((file) => !isCodemapOnlyDisposition(file.disposition))
1060
+ .map((file) => `${file.root_index}\u0000${file.path}`));
2997
1061
  const rendered = [];
2998
1062
  const seen = new Set();
2999
1063
  const perFileCount = new Map();
@@ -3171,84 +1235,14 @@ function reconcileSelectedOmissions(omissions, selected) {
3171
1235
  }
3172
1236
  return reconciled;
3173
1237
  }
3174
- function filterTaskPresentationOmissions(omissions, task) {
3175
- const branch = classifyTaskBranch(task.toLowerCase());
3176
- return omissions
3177
- .map((omission) => {
3178
- if (omission.paths == null) {
3179
- return omission;
3180
- }
3181
- const paths = omission.paths.filter((path) => !isTangentialPresentationOmissionPath(path, branch));
3182
- return { ...omission, paths };
3183
- })
3184
- .filter((omission) => {
3185
- if ((omission.paths?.length ?? 0) > 0) {
3186
- return true;
3187
- }
3188
- return !isMaterialOmission(omission);
3189
- });
3190
- }
3191
- function filterTaskPresentationSelected(selected, task) {
3192
- const branch = classifyTaskBranch(task.toLowerCase());
3193
- return selected.filter((file) => !isTangentialPresentationSelectedPath(file.path, branch));
3194
- }
3195
- function promoteTaskPresentationEvidence(selected, task, slices) {
3196
- const branch = classifyTaskBranch(task.toLowerCase());
3197
- const required = branch === "review"
3198
- ? [
3199
- "frontend/src/components/chat/message-input/Input.tsx",
3200
- "frontend/src/components/ui/FileUploadDialog.codex.test.tsx",
3201
- ]
3202
- : [];
3203
- if (required.length === 0) {
3204
- return [...selected];
3205
- }
3206
- const selectedPaths = selectedPathSet(selected);
3207
- const evidenceByPath = bestEvidenceSliceByPath(slices, evidenceTermsForTask(task));
3208
- const promoted = [];
3209
- for (const path of required) {
3210
- if (selectedPaths.has(path)) {
3211
- continue;
3212
- }
3213
- const evidence = evidenceByPath.get(path);
3214
- if (evidence != null && hasSnippetEligibleContent(evidence)) {
3215
- promoted.push(evidence);
3216
- }
3217
- }
3218
- if (promoted.length === 0) {
3219
- return [...selected];
3220
- }
3221
- return [...selected, ...promoted];
1238
+ function filterTaskPresentationOmissions(omissions, _task) {
1239
+ return [...omissions];
3222
1240
  }
3223
- function isTangentialPresentationSelectedPath(path, branch) {
3224
- const normalized = path.toLowerCase();
3225
- if (branch === "runtime") {
3226
- return normalized.endsWith("/components/chat/chat-window/chat.tsx");
3227
- }
3228
- if (branch === "review") {
3229
- return (normalized.endsWith("/services/sandbox/provider.py") ||
3230
- normalized.includes("providerauth") ||
3231
- normalized.includes("provider_auth"));
3232
- }
3233
- return false;
1241
+ function filterTaskPresentationSelected(selected, _task) {
1242
+ return [...selected];
3234
1243
  }
3235
- function isTangentialPresentationOmissionPath(path, branch) {
3236
- const normalized = path.toLowerCase();
3237
- if (branch === "runtime") {
3238
- return (normalized.includes("/components/settings/") ||
3239
- normalized.endsWith("/api/endpoints/settings.py") ||
3240
- normalized.endsWith("/models/schemas/settings.py") ||
3241
- normalized.includes("/services/nightshift/") ||
3242
- normalized.includes("/tests/services/nightshift/"));
3243
- }
3244
- if (branch === "review") {
3245
- return (normalized.includes("/services/checks/") ||
3246
- normalized.endsWith("/api/endpoints/checks.py") ||
3247
- normalized.endsWith("/models/schemas/checks.py") ||
3248
- normalized.includes("/tests/checks/") ||
3249
- normalized.includes("/components/right-panel/checks/"));
3250
- }
3251
- return false;
1244
+ function promoteTaskPresentationEvidence(selected, _task, _slices) {
1245
+ return [...selected];
3252
1246
  }
3253
1247
  function reconcileSelectedOpenQuestions(questions, selected) {
3254
1248
  if (questions.length === 0 || selected.length === 0) {
@@ -3303,87 +1297,7 @@ function renderOmissionQuestionPaths(paths) {
3303
1297
  const omitted = uniquePaths.length - rendered.length;
3304
1298
  return `${rendered.join(", ")}${omitted > 0 ? ` +${omitted} more` : ""}`;
3305
1299
  }
3306
- function isConceptualQuestionAnsweredBySelectedContext(question, selected) {
3307
- const normalized = question.toLowerCase();
3308
- const paths = selectedPathSet(selected);
3309
- if (mentionsAny(normalized, [
3310
- "filtered trace",
3311
- "filtered index",
3312
- "filtered indices",
3313
- "original index",
3314
- "original trace indices",
3315
- "entry id",
3316
- "virtualizer key",
3317
- "click target",
3318
- ]) &&
3319
- paths.has("frontend/src/components/right-panel/RightPanelDevtools.tsx") &&
3320
- paths.has("frontend/src/store/devtoolsNavStore.ts") &&
3321
- paths.has("frontend/src/components/chat/chat-window/Chat.tsx")) {
3322
- return true;
3323
- }
3324
- if (mentionsAny(normalized, [
3325
- "cached message lookup",
3326
- "cached messages",
3327
- "older paginated messages",
3328
- "wrong rendered index",
3329
- "virtualized scrolling",
3330
- "disambiguate by chatid",
3331
- "messages not currently loaded",
3332
- "infinite-query cache",
3333
- ]) &&
3334
- paths.has("frontend/src/components/chat/chat-window/Chat.tsx") &&
3335
- (paths.has("frontend/src/utils/messageCacheOrder.ts") ||
3336
- paths.has("frontend/src/hooks/queries/queryKeys.ts"))) {
3337
- return true;
3338
- }
3339
- if (mentionsAny(normalized, ["tool-result", "tool result", "tool-use", "tool use"]) &&
3340
- paths.has("frontend/src/components/chat/message-bubble/MessageRenderer.tsx") &&
3341
- paths.has("frontend/src/components/chat/tools/common/ToolCard.tsx")) {
3342
- return true;
3343
- }
3344
- if (mentionsAny(normalized, ["highlight", "scroll effects", "repeated clicks"]) &&
3345
- paths.has("frontend/src/store/devtoolsNavStore.ts") &&
3346
- paths.has("frontend/src/components/chat/chat-window/Chat.tsx")) {
3347
- return true;
3348
- }
3349
- if (mentionsAny(normalized, [
3350
- "queued messages",
3351
- "frontend field names",
3352
- "runtime_permission_mode",
3353
- "runtime_effort_level",
3354
- ]) &&
3355
- paths.has("frontend/src/services/chatService.ts") &&
3356
- paths.has("backend/app/services/chat.py")) {
3357
- return true;
3358
- }
3359
- if (mentionsAny(normalized, [
3360
- "effort settings",
3361
- "effort_level",
3362
- "codex/openai",
3363
- "claude/openrouter",
3364
- "non-codex providers",
3365
- ]) &&
3366
- paths.has("backend/app/services/runtime/codex.py") &&
3367
- paths.has("backend/app/services/runtime/claude.py")) {
3368
- return true;
3369
- }
3370
- if (mentionsAny(normalized, [
3371
- "codex trace",
3372
- "archived claude",
3373
- "message_id/tool_use_id",
3374
- "failed clicks",
3375
- "assistant messages",
3376
- "tool_use",
3377
- "tool_result",
3378
- "virtualizer keys",
3379
- "visible indices",
3380
- "original trace entry ids",
3381
- ]) &&
3382
- paths.has("frontend/src/components/right-panel/RightPanelDevtools.tsx") &&
3383
- paths.has("backend/app/api/endpoints/devtools.py") &&
3384
- paths.has("backend/app/services/session_trace_archive.py")) {
3385
- return true;
3386
- }
1300
+ function isConceptualQuestionAnsweredBySelectedContext(_question, _selected) {
3387
1301
  return false;
3388
1302
  }
3389
1303
  function isSelectedEvidenceQuestion(question) {
@@ -3481,6 +1395,9 @@ function omissionEventBaseFromReason(reason) {
3481
1395
  reason === "default_noisy_exclude") {
3482
1396
  return { reason: "default_noisy_exclude", material: false };
3483
1397
  }
1398
+ if (reason === "focus_sliced" || reason === "focus_over_cap") {
1399
+ return { reason: "budget_omission", material: true };
1400
+ }
3484
1401
  if (reason.includes("secret")) {
3485
1402
  return { reason: "secret_skip", material: true };
3486
1403
  }
@@ -3514,7 +1431,7 @@ function boundDebugDiagnostics(diagnostics, caps) {
3514
1431
  : {
3515
1432
  notes: diagnostics.notes
3516
1433
  .slice(0, caps.max_warnings)
3517
- .map(sanitizeDiagnosticText),
1434
+ .map((note) => sanitizeDiagnosticText(note)),
3518
1435
  }),
3519
1436
  ...(diagnostics.counters == null
3520
1437
  ? {}
@@ -3533,35 +1450,43 @@ function renderJsonl(records) {
3533
1450
  return records.map((record) => JSON.stringify(record)).join("\n")
3534
1451
  + (records.length === 0 ? "" : "\n");
3535
1452
  }
3536
- function validateTaskForRendering(task) {
3537
- assertNoHighConfidenceSecretsInTask(task);
1453
+ function validateTaskForRendering(task, allowSecrets) {
1454
+ assertTaskSecretsGated(task, allowSecrets);
3538
1455
  return task;
3539
1456
  }
3540
- function safePath(path) {
1457
+ function safePath(path, allowSecrets) {
1458
+ if (allowSecrets) {
1459
+ return path;
1460
+ }
3541
1461
  return boundContextBuilderPathList([path], 1)[0] ?? "[redacted-sensitive-path]";
3542
1462
  }
3543
- function safeContent(content) {
3544
- return hasHighConfidenceSecret(content) ? "[redacted-sensitive-content]" : content;
1463
+ function safeContent(content, allowSecrets) {
1464
+ if (allowSecrets) {
1465
+ return content;
1466
+ }
1467
+ return hasConservativeResidualSecret(content)
1468
+ ? "[redacted-sensitive-content]"
1469
+ : content;
3545
1470
  }
3546
- function sanitizeJsonValue(value) {
1471
+ function sanitizeJsonValue(value, allowSecrets) {
3547
1472
  if (typeof value === "string") {
3548
- const pathSafe = safePath(value);
1473
+ const pathSafe = safePath(value, allowSecrets);
3549
1474
  return pathSafe === "[redacted-sensitive-path]"
3550
1475
  ? pathSafe
3551
- : sanitizeDiagnosticText(value);
1476
+ : sanitizeDiagnosticText(value, allowSecrets);
3552
1477
  }
3553
1478
  if (typeof value === "number" || typeof value === "boolean" || value == null) {
3554
1479
  return value;
3555
1480
  }
3556
1481
  if (Array.isArray(value)) {
3557
- return value.slice(0, 200).map(sanitizeJsonValue);
1482
+ return value.slice(0, 200).map((item) => sanitizeJsonValue(item, allowSecrets));
3558
1483
  }
3559
1484
  if (typeof value === "object") {
3560
1485
  return Object.fromEntries(Object.entries(value)
3561
1486
  .slice(0, 200)
3562
1487
  .map(([key, entryValue]) => [
3563
- sanitizeDiagnosticText(key),
3564
- sanitizeJsonValue(entryValue),
1488
+ sanitizeDiagnosticText(key, allowSecrets),
1489
+ sanitizeJsonValue(entryValue, allowSecrets),
3565
1490
  ]));
3566
1491
  }
3567
1492
  return String(value);