@iowarp/clio-coder 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +7 -3
  3. package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
  4. package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
  7. package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
  11. package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
  13. package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
  14. package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
  17. package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
  18. package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
  21. package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
  22. package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
  23. package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
  27. package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
  28. package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
  29. package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
  30. package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
  31. package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
  32. package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
  33. package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
  34. package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
  35. package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
  36. package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
  37. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  38. package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
  39. package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
  40. package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
  41. package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
  42. package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
  43. package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
  44. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  45. package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
  46. package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
  47. package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
  48. package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
  49. package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
  50. package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
  51. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  52. package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
  53. package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
  54. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  55. package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
  56. package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
  57. package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
  58. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  59. package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
  60. package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
  61. package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
  62. package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
  63. package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
  64. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  65. package/dist/chunk-RKKLTLYB.js +45 -0
  66. package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
  67. package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
  68. package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
  69. package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
  70. package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
  71. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  72. package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
  73. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  74. package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
  75. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  76. package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
  77. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  78. package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
  79. package/dist/chunk-YSEHGPCT.js +127 -0
  80. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  81. package/dist/cli/index.js +27 -27
  82. package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
  83. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
  84. package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
  85. package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
  86. package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
  87. package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
  88. package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
  89. package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
  90. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  91. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
  92. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
  93. package/dist/doctor-TWBWFK5V.js +165 -0
  94. package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
  95. package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
  96. package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
  97. package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
  98. package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
  99. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
  100. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
  101. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
  102. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
  103. package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
  104. package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
  105. package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
  106. package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
  107. package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
  108. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
  109. package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
  110. package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
  111. package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
  112. package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
  113. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
  114. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  115. package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
  116. package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
  117. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
  118. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  119. package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
  120. package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
  121. package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
  122. package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
  123. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
  124. package/dist/worker/entry.js +29 -33
  125. package/docs/alcf-provider.md +1 -1
  126. package/docs/architecture.md +1 -1
  127. package/docs/artifact-versions.md +6 -1
  128. package/docs/built-in-agents.md +1 -1
  129. package/docs/capacity-and-scheduling.md +23 -2
  130. package/docs/commands-and-modes.md +1 -1
  131. package/docs/configuration-and-targets.md +30 -3
  132. package/docs/context-engine.md +63 -4
  133. package/docs/documentation-coverage.md +3 -3
  134. package/docs/documentation-guide.md +1 -1
  135. package/docs/environment-variables.md +2 -0
  136. package/docs/eval-runner.md +262 -11
  137. package/docs/evals-internal.md +72 -2
  138. package/docs/evidence-and-memory.md +11 -10
  139. package/docs/evolution.md +1 -1
  140. package/docs/extensions-and-sharing.md +3 -1
  141. package/docs/fleet-dispatch.md +4 -4
  142. package/docs/installation-and-lifecycle.md +1 -1
  143. package/docs/middleware-and-components.md +1 -1
  144. package/docs/model-catalog.md +1 -1
  145. package/docs/observability.md +53 -2
  146. package/docs/proactive-memory.md +127 -14
  147. package/docs/prompt-envelope-and-tools.md +19 -1
  148. package/docs/provider-adapter-cookbook.md +1 -1
  149. package/docs/release-cut-checklist.md +19 -3
  150. package/docs/safety-model.md +1 -1
  151. package/docs/scientific-validation.md +1 -1
  152. package/docs/skills-marketplace.md +1 -1
  153. package/docs/tool-usage.md +1 -1
  154. package/docs/trace-store.md +1 -1
  155. package/docs/troubleshooting.md +87 -0
  156. package/docs/tui-design.md +1 -1
  157. package/docs/worker-dispatch-mechanics.md +1 -1
  158. package/package.json +2 -1
  159. package/src/cli/agents.ts +1 -1
  160. package/src/cli/config-inspect.ts +33 -6
  161. package/src/cli/config.ts +1 -1
  162. package/src/cli/doctor-state-size.ts +82 -0
  163. package/src/cli/doctor.ts +3 -1
  164. package/src/cli/eval.ts +80 -16
  165. package/src/cli/extensions.ts +5 -1
  166. package/src/cli/fleet.ts +32 -3
  167. package/src/cli/targets.ts +44 -13
  168. package/src/cli/trace.ts +63 -4
  169. package/src/cli/usage.ts +63 -14
  170. package/src/core/bus-events.ts +29 -1
  171. package/src/core/cache-telemetry.ts +42 -0
  172. package/src/core/config.ts +18 -0
  173. package/src/core/defaults.ts +36 -6
  174. package/src/core/endpoint-key.ts +27 -0
  175. package/src/core/residency-target-key.ts +25 -0
  176. package/src/core/response-schema.ts +36 -2
  177. package/src/domains/config/classify.ts +3 -0
  178. package/src/domains/context/codewiki/coordinator.ts +12 -4
  179. package/src/domains/dispatch/admission.ts +40 -3
  180. package/src/domains/dispatch/capacity-lease.ts +98 -9
  181. package/src/domains/dispatch/contract.ts +11 -0
  182. package/src/domains/dispatch/execution-plan.ts +44 -4
  183. package/src/domains/dispatch/extension.ts +166 -42
  184. package/src/domains/dispatch/fleet-run.ts +23 -3
  185. package/src/domains/dispatch/heartbeat.ts +32 -8
  186. package/src/domains/dispatch/index.ts +3 -0
  187. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  188. package/src/domains/dispatch/reservation-store.ts +116 -8
  189. package/src/domains/dispatch/state.ts +4 -0
  190. package/src/domains/dispatch/worker-spawn.ts +25 -11
  191. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  192. package/src/domains/dispatch/write-boundary.ts +62 -1
  193. package/src/domains/eval/artifacts/store.ts +62 -0
  194. package/src/domains/eval/compare/behavioral.ts +224 -0
  195. package/src/domains/eval/compare/compare.ts +355 -2
  196. package/src/domains/eval/compare/envelope.ts +128 -0
  197. package/src/domains/eval/compare/gates.ts +24 -6
  198. package/src/domains/eval/compare/thresholds.ts +30 -3
  199. package/src/domains/eval/execution-provenance.ts +240 -0
  200. package/src/domains/eval/metrics/aggregate.ts +136 -0
  201. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  202. package/src/domains/eval/metrics/tracked.ts +413 -0
  203. package/src/domains/eval/provenance.ts +117 -0
  204. package/src/domains/eval/reports/comparison.ts +128 -0
  205. package/src/domains/eval/reports/junit.ts +17 -3
  206. package/src/domains/eval/reports/markdown.ts +3 -3
  207. package/src/domains/eval/reports/text.ts +14 -0
  208. package/src/domains/eval/run-compare.ts +20 -0
  209. package/src/domains/eval/runners/clio-run.ts +127 -0
  210. package/src/domains/eval/runners/external-command.ts +28 -3
  211. package/src/domains/eval/schema/adapter.ts +111 -0
  212. package/src/domains/eval/schema/artifact.ts +20 -0
  213. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  214. package/src/domains/eval/schema/behavioral.ts +520 -0
  215. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  216. package/src/domains/eval/schema/serving.ts +74 -0
  217. package/src/domains/eval/schema/suite.ts +38 -8
  218. package/src/domains/eval/schema/validate.ts +58 -3
  219. package/src/domains/eval/schema/verdict.ts +237 -0
  220. package/src/domains/eval/suites/resolve.ts +2 -0
  221. package/src/domains/eval/suites/run.ts +264 -33
  222. package/src/domains/eval/verifiers/command.ts +2 -1
  223. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  224. package/src/domains/evidence/build.ts +2 -13
  225. package/src/domains/evidence/eval.ts +2 -12
  226. package/src/domains/evidence/findings-markdown.ts +33 -0
  227. package/src/domains/evidence/run-trust.ts +7 -113
  228. package/src/domains/evidence/trust-projection.ts +2 -2
  229. package/src/domains/extensions/compatibility.ts +285 -0
  230. package/src/domains/extensions/discovery.ts +38 -3
  231. package/src/domains/extensions/resources.ts +1 -1
  232. package/src/domains/extensions/state.ts +12 -3
  233. package/src/domains/extensions/types.ts +2 -0
  234. package/src/domains/lifecycle/doctor.ts +69 -1
  235. package/src/domains/memory/index.ts +14 -0
  236. package/src/domains/memory/task-bank-promotion.ts +64 -0
  237. package/src/domains/memory/task-memory-policy.ts +77 -8
  238. package/src/domains/memory/task-memory-spend.ts +131 -0
  239. package/src/domains/memory/task-memory-status.ts +7 -0
  240. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  241. package/src/domains/middleware/index.ts +1 -0
  242. package/src/domains/middleware/memory-intervention.ts +69 -5
  243. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  244. package/src/domains/observability/background-memory-usage.ts +140 -0
  245. package/src/domains/observability/cost.ts +1 -1
  246. package/src/domains/observability/index.ts +7 -0
  247. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  248. package/src/domains/observability/trace-store.ts +192 -2
  249. package/src/domains/prompts/compiler.ts +100 -13
  250. package/src/domains/providers/endpoint-capacity.ts +96 -0
  251. package/src/domains/providers/index.ts +10 -0
  252. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  253. package/src/domains/providers/runtime-resolution.ts +8 -1
  254. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  255. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  256. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  257. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  258. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  259. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  260. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  261. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  262. package/src/domains/providers/types/capability-flags.ts +2 -0
  263. package/src/domains/providers/types/target-descriptor.ts +2 -0
  264. package/src/domains/resources/prompts/loader.ts +95 -33
  265. package/src/domains/safety/call-target.ts +52 -0
  266. package/src/domains/safety/run-effects.ts +35 -4
  267. package/src/domains/session/context-accounting.ts +52 -1
  268. package/src/domains/session/context-ledger.ts +37 -13
  269. package/src/domains/session/index.ts +6 -0
  270. package/src/domains/session/prompt-cache.ts +140 -0
  271. package/src/domains/session/prompt-manifest.ts +42 -0
  272. package/src/engine/acp/adapter.ts +18 -3
  273. package/src/engine/ai.ts +35 -0
  274. package/src/engine/apis/llamacpp-residency.ts +55 -3
  275. package/src/engine/apis/lmstudio.ts +25 -5
  276. package/src/engine/apis/ollama-native.ts +2 -1
  277. package/src/engine/apis/openai-completions.ts +80 -17
  278. package/src/engine/apis/residency-lock.ts +3 -1
  279. package/src/engine/apis/residency.ts +34 -1
  280. package/src/engine/provider-payload.ts +29 -1
  281. package/src/entry/orchestrator.ts +176 -30
  282. package/src/interactive/chat-loop-messages.ts +26 -7
  283. package/src/interactive/chat-loop.ts +318 -41
  284. package/src/interactive/chat-panel.ts +62 -8
  285. package/src/interactive/clio-editor.ts +45 -8
  286. package/src/interactive/context-activity.ts +5 -1
  287. package/src/interactive/context-meter.ts +1 -1
  288. package/src/interactive/context-overlay.ts +40 -10
  289. package/src/interactive/cost-overlay.ts +64 -6
  290. package/src/interactive/dispatch-board.ts +84 -12
  291. package/src/interactive/fleet-run-preview.ts +41 -15
  292. package/src/interactive/handoff-round.ts +41 -2
  293. package/src/interactive/interactive-application.ts +24 -1
  294. package/src/interactive/interactive-input-runtime.ts +8 -0
  295. package/src/interactive/interactive-presentation.ts +4 -0
  296. package/src/interactive/interactive-shell.ts +20 -17
  297. package/src/interactive/interactive-slash-runtime.ts +27 -4
  298. package/src/interactive/memory-overlay.ts +8 -0
  299. package/src/interactive/mutation-preview.ts +295 -0
  300. package/src/interactive/overlay-general-openers.ts +16 -0
  301. package/src/interactive/overlay-key-routing.ts +38 -0
  302. package/src/interactive/overlay-lifecycle.ts +38 -5
  303. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  304. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  305. package/src/interactive/overlays/ask-user.ts +91 -19
  306. package/src/interactive/overlays/help-reference.ts +4 -0
  307. package/src/interactive/overlays/prompts.ts +11 -1
  308. package/src/interactive/overlays/settings.ts +35 -1
  309. package/src/interactive/permission-hint.ts +34 -2
  310. package/src/interactive/permission-overlay.ts +159 -9
  311. package/src/interactive/prewarm.ts +197 -0
  312. package/src/interactive/render-trace.ts +162 -15
  313. package/src/interactive/renderers/tool-execution.ts +4 -0
  314. package/src/interactive/side-question.ts +58 -1
  315. package/src/interactive/status/controller.ts +11 -0
  316. package/src/interactive/status/state-machine.ts +54 -2
  317. package/src/interactive/status/types.ts +7 -0
  318. package/src/interactive/terminal-lease.ts +2 -0
  319. package/src/interactive/turn-context.ts +299 -31
  320. package/src/interactive/turn-persistence.ts +14 -4
  321. package/src/interactive/turn-prewarm.ts +364 -0
  322. package/src/interactive/turn-queues.ts +7 -4
  323. package/src/interactive/turn-runtime.ts +8 -1
  324. package/src/interactive/turn-state.ts +23 -0
  325. package/src/interactive/view/view-overlay.ts +28 -3
  326. package/src/tools/ask-user.ts +43 -2
  327. package/src/tools/dispatch-plan.ts +17 -9
  328. package/src/tools/dispatch-scout.ts +1 -1
  329. package/src/tools/registry.ts +16 -0
  330. package/dist/chunk-AOCYTWAV.js +0 -449
  331. package/dist/chunk-HWUFFB6L.js +0 -83
  332. package/dist/chunk-R346GLFC.js +0 -31
  333. package/dist/doctor-M7YEDGAE.js +0 -91
@@ -0,0 +1,224 @@
1
+ import type { EvalArtifactResultV4, EvalArtifactV4 } from "../schema/artifact.js";
2
+ import {
3
+ EVAL_BEHAVIOR_METRIC_DEFINITIONS_V1,
4
+ type EvalBehaviorMetricDefinitionV1,
5
+ type EvalBehaviorMetricFamilyV1,
6
+ type EvalBehaviorMetricNameV1,
7
+ type EvalBehaviorMetricSourceV1,
8
+ } from "../schema/behavioral-metrics.js";
9
+ import type { EvalExecutionMatrixDimensionV1 } from "../schema/execution-envelope.js";
10
+ import {
11
+ compareEvalExecutionEnvelopesV1,
12
+ type EvalEnvelopeComparabilityV1,
13
+ type EvalEnvelopeMismatchV1,
14
+ } from "./envelope.js";
15
+
16
+ export type EvalMetricChangeV1 = "improved" | "regressed" | "unchanged" | "incomparable";
17
+
18
+ export interface EvalBehaviorMetricDistributionV1 {
19
+ observations: number;
20
+ measured: number;
21
+ unmeasured: number;
22
+ mean: number | null;
23
+ min: number | null;
24
+ max: number | null;
25
+ p90: number | null;
26
+ variance: number | null;
27
+ standardDeviation: number | null;
28
+ source: EvalBehaviorMetricSourceV1;
29
+ }
30
+
31
+ export interface EvalBehaviorMetricComparisonV1 {
32
+ scenarioId: string;
33
+ role: string;
34
+ target: { id: string; model: string | null };
35
+ metric: EvalBehaviorMetricNameV1;
36
+ family: EvalBehaviorMetricFamilyV1;
37
+ direction: "higher" | "lower";
38
+ hardGate: boolean;
39
+ baseline: EvalBehaviorMetricDistributionV1;
40
+ candidate: EvalBehaviorMetricDistributionV1;
41
+ change: EvalMetricChangeV1;
42
+ meanDelta: number | null;
43
+ varianceChange: EvalMetricChangeV1;
44
+ varianceDelta: number | null;
45
+ comparability: EvalEnvelopeComparabilityV1;
46
+ }
47
+
48
+ export interface EvalBehaviorHardGateV1 {
49
+ pass: boolean;
50
+ envelopeFailures: EvalEnvelopeMismatchV1[];
51
+ failures: Array<{
52
+ scenarioId: string;
53
+ role: string;
54
+ target: { id: string; model: string | null };
55
+ metric: EvalBehaviorMetricNameV1;
56
+ change: "regressed" | "incomparable";
57
+ }>;
58
+ }
59
+
60
+ interface BehaviorGroup {
61
+ scenarioId: string;
62
+ role: string;
63
+ target: { id: string; model: string | null };
64
+ results: EvalArtifactResultV4[];
65
+ }
66
+
67
+ export function compareEvalBehaviorMetricsV1(
68
+ baseline: EvalArtifactV4,
69
+ candidate: EvalArtifactV4,
70
+ ): {
71
+ comparisons: EvalBehaviorMetricComparisonV1[];
72
+ hardGate: EvalBehaviorHardGateV1;
73
+ envelopeMismatches: EvalEnvelopeMismatchV1[];
74
+ } {
75
+ const baselineGroups = behaviorGroups(baseline);
76
+ const candidateGroups = behaviorGroups(candidate);
77
+ const keys = new Set([...baselineGroups.keys(), ...candidateGroups.keys()]);
78
+ const comparisons: EvalBehaviorMetricComparisonV1[] = [];
79
+ const envelopeMismatches: EvalEnvelopeMismatchV1[] = [];
80
+ const baselineDimensions = baseline.matrix.dimensions ?? ([] as EvalExecutionMatrixDimensionV1[]);
81
+ const candidateDimensions = candidate.matrix.dimensions ?? ([] as EvalExecutionMatrixDimensionV1[]);
82
+ for (const key of [...keys].sort((left, right) => left.localeCompare(right))) {
83
+ const baselineGroup = baselineGroups.get(key);
84
+ const candidateGroup = candidateGroups.get(key);
85
+ const identity = baselineGroup ?? candidateGroup;
86
+ if (identity === undefined) continue;
87
+ const envelopeMismatch = compareEvalExecutionEnvelopesV1(
88
+ identity,
89
+ baselineGroup?.results ?? [],
90
+ candidateGroup?.results ?? [],
91
+ baselineDimensions,
92
+ candidateDimensions,
93
+ );
94
+ if (envelopeMismatch !== null) envelopeMismatches.push(envelopeMismatch);
95
+ const comparability: EvalEnvelopeComparabilityV1 = {
96
+ comparable: envelopeMismatch === null,
97
+ mismatchedFields: envelopeMismatch?.fields ?? [],
98
+ };
99
+ for (const definition of EVAL_BEHAVIOR_METRIC_DEFINITIONS_V1) {
100
+ const baselineDistribution = behaviorDistribution(baselineGroup?.results ?? [], definition);
101
+ const candidateDistribution = behaviorDistribution(candidateGroup?.results ?? [], definition);
102
+ comparisons.push({
103
+ scenarioId: identity.scenarioId,
104
+ role: identity.role,
105
+ target: identity.target,
106
+ metric: definition.name,
107
+ family: definition.family,
108
+ direction: definition.direction,
109
+ hardGate: definition.hardGate,
110
+ baseline: baselineDistribution,
111
+ candidate: candidateDistribution,
112
+ change:
113
+ envelopeMismatch === null
114
+ ? classifyChange(baselineDistribution.mean, candidateDistribution.mean, definition.direction)
115
+ : "incomparable",
116
+ meanDelta: subtractNullable(candidateDistribution.mean, baselineDistribution.mean),
117
+ varianceChange:
118
+ envelopeMismatch === null
119
+ ? classifyChange(baselineDistribution.variance, candidateDistribution.variance, "lower")
120
+ : "incomparable",
121
+ varianceDelta: subtractNullable(candidateDistribution.variance, baselineDistribution.variance),
122
+ comparability,
123
+ });
124
+ }
125
+ }
126
+ const failures = comparisons.flatMap((comparison) =>
127
+ comparison.hardGate &&
128
+ (comparison.change === "regressed" ||
129
+ (comparison.change === "incomparable" && comparison.baseline.mean !== null && comparison.candidate.mean === null))
130
+ ? [
131
+ {
132
+ scenarioId: comparison.scenarioId,
133
+ role: comparison.role,
134
+ target: comparison.target,
135
+ metric: comparison.metric,
136
+ change: comparison.change,
137
+ },
138
+ ]
139
+ : [],
140
+ );
141
+ return {
142
+ comparisons,
143
+ hardGate: {
144
+ pass: failures.length === 0 && envelopeMismatches.length === 0,
145
+ failures,
146
+ envelopeFailures: envelopeMismatches,
147
+ },
148
+ envelopeMismatches,
149
+ };
150
+ }
151
+
152
+ export function classifyChange(
153
+ baseline: number | null,
154
+ candidate: number | null,
155
+ direction: "higher" | "lower",
156
+ ): EvalMetricChangeV1 {
157
+ if (baseline === null || candidate === null) return "incomparable";
158
+ if (baseline === candidate) return "unchanged";
159
+ if (direction === "higher") return candidate > baseline ? "improved" : "regressed";
160
+ return candidate < baseline ? "improved" : "regressed";
161
+ }
162
+
163
+ function behaviorGroups(artifact: EvalArtifactV4): Map<string, BehaviorGroup> {
164
+ const groups = new Map<string, BehaviorGroup>();
165
+ for (const result of artifact.results) {
166
+ const behavioral = result.behavioralMetrics;
167
+ if (behavioral === undefined) continue;
168
+ const key = groupKey(behavioral.scenarioId, behavioral.role, behavioral.target);
169
+ const group = groups.get(key) ?? {
170
+ scenarioId: behavioral.scenarioId,
171
+ role: behavioral.role,
172
+ target: behavioral.target,
173
+ results: [],
174
+ };
175
+ group.results.push(result);
176
+ groups.set(key, group);
177
+ }
178
+ return groups;
179
+ }
180
+
181
+ function behaviorDistribution(
182
+ results: ReadonlyArray<EvalArtifactResultV4>,
183
+ definition: EvalBehaviorMetricDefinitionV1,
184
+ ): EvalBehaviorMetricDistributionV1 {
185
+ const observations = results.map((result) => result.behavioralMetrics?.metrics[definition.name].value ?? null);
186
+ const values = observations.flatMap((value) => (value === null ? [] : [value]));
187
+ if (values.length === 0) {
188
+ return {
189
+ observations: observations.length,
190
+ measured: 0,
191
+ unmeasured: observations.length,
192
+ mean: null,
193
+ min: null,
194
+ max: null,
195
+ p90: null,
196
+ variance: null,
197
+ standardDeviation: null,
198
+ source: definition.source,
199
+ };
200
+ }
201
+ const ordered = [...values].sort((left, right) => left - right);
202
+ const mean = ordered.reduce((sum, value) => sum + value, 0) / ordered.length;
203
+ const variance = ordered.reduce((sum, value) => sum + (value - mean) ** 2, 0) / ordered.length;
204
+ return {
205
+ observations: observations.length,
206
+ measured: values.length,
207
+ unmeasured: observations.length - values.length,
208
+ mean,
209
+ min: ordered[0] ?? null,
210
+ max: ordered.at(-1) ?? null,
211
+ p90: ordered[Math.max(0, Math.ceil(ordered.length * 0.9) - 1)] ?? null,
212
+ variance,
213
+ standardDeviation: Math.sqrt(variance),
214
+ source: definition.source,
215
+ };
216
+ }
217
+
218
+ function groupKey(scenarioId: string, role: string, target: { id: string; model: string | null }): string {
219
+ return JSON.stringify([scenarioId, role, target.id, target.model]);
220
+ }
221
+
222
+ function subtractNullable(left: number | null, right: number | null): number | null {
223
+ return left === null || right === null ? null : left - right;
224
+ }
@@ -1,33 +1,386 @@
1
+ import { aggregateEvalVerdicts, type EvalMetricDistributionV1 } from "../metrics/aggregate.js";
2
+ import { assertComparableTrackedMetricSources } from "../run-compare.js";
1
3
  import type { EvalArtifactV4 } from "../schema/artifact.js";
4
+ import {
5
+ type EvalServingConfigurationV1,
6
+ renderEvalServingConfiguration,
7
+ sameEvalServingConfiguration,
8
+ } from "../schema/serving.js";
9
+ import { EVAL_TRACKED_METRIC_NAMES } from "../schema/verdict.js";
10
+ import {
11
+ classifyChange,
12
+ compareEvalBehaviorMetricsV1,
13
+ type EvalBehaviorHardGateV1,
14
+ type EvalBehaviorMetricComparisonV1,
15
+ type EvalMetricChangeV1,
16
+ } from "./behavioral.js";
17
+ import type { EvalEnvelopeMismatchV1 } from "./envelope.js";
18
+
19
+ export interface EvalCompareV4Options {
20
+ metric?: string;
21
+ allowConfigDrift?: boolean;
22
+ }
23
+
24
+ export interface EvalTrackedMetricComparisonV1 {
25
+ scenarioId: string;
26
+ metric: string;
27
+ baseline: EvalMetricDistributionV1;
28
+ candidate: EvalMetricDistributionV1;
29
+ meanDelta: number | null;
30
+ p90Delta: number | null;
31
+ varianceDelta: number | null;
32
+ change: EvalMetricChangeV1;
33
+ varianceChange: EvalMetricChangeV1;
34
+ }
2
35
 
3
36
  export interface EvalCompareV4Summary {
4
37
  baselineEvalId: string;
5
38
  candidateEvalId: string;
39
+ baselineServingConfiguration: EvalServingConfigurationV1;
40
+ candidateServingConfiguration: EvalServingConfigurationV1;
41
+ configDrift: boolean;
6
42
  passRateDelta: number;
7
- /** Null when either side observed no usage: a delta against an unknown is not zero. */
43
+ /** Null when either side observed no usage because a delta against an unknown is not zero. */
8
44
  tokenDelta: number | null;
9
45
  wallTimeDelta: number;
46
+ trackedMetrics: EvalTrackedMetricComparisonV1[];
47
+ behavioralMetrics: EvalBehaviorMetricComparisonV1[];
48
+ hardGate: EvalBehaviorHardGateV1;
49
+ envelopeMismatches: EvalEnvelopeMismatchV1[];
50
+ scenarioReports: EvalBehaviorComparisonRollupV1[];
51
+ roleReports: EvalBehaviorComparisonRollupV1[];
52
+ affectedCorpusResults: Array<{ scenarioId: string; role: string; changedFields: string[] }>;
53
+ }
54
+
55
+ export interface EvalBehaviorComparisonRollupV1 {
56
+ id: string;
57
+ metrics: EvalChangeCountsV1;
58
+ variance: EvalChangeCountsV1;
59
+ }
60
+
61
+ export interface EvalChangeCountsV1 {
62
+ improved: number;
63
+ regressed: number;
64
+ unchanged: number;
65
+ incomparable: number;
66
+ }
67
+
68
+ export class EvalServingConfigurationDriftError extends Error {
69
+ readonly baseline: EvalServingConfigurationV1;
70
+ readonly candidate: EvalServingConfigurationV1;
71
+
72
+ constructor(baseline: EvalServingConfigurationV1, candidate: EvalServingConfigurationV1) {
73
+ super(
74
+ [
75
+ "serving configuration drift; pass --allow-config-drift to compare these runs",
76
+ `baseline serving: ${renderEvalServingConfiguration(baseline)}`,
77
+ `candidate serving: ${renderEvalServingConfiguration(candidate)}`,
78
+ ].join("\n"),
79
+ );
80
+ this.name = "EvalServingConfigurationDriftError";
81
+ this.baseline = baseline;
82
+ this.candidate = candidate;
83
+ }
10
84
  }
11
85
 
12
- export function compareEvalArtifactsV4(baseline: EvalArtifactV4, candidate: EvalArtifactV4): EvalCompareV4Summary {
86
+ export function compareEvalArtifactsV4(
87
+ baseline: EvalArtifactV4,
88
+ candidate: EvalArtifactV4,
89
+ options: EvalCompareV4Options = {},
90
+ ): EvalCompareV4Summary {
13
91
  const baselineTokens = baseline.summary.tokens;
14
92
  const candidateTokens = candidate.summary.tokens;
93
+ const baselineServing = servingConfigurationOf(baseline);
94
+ const candidateServing = servingConfigurationOf(candidate);
95
+ const configDrift = !sameEvalServingConfiguration(baselineServing, candidateServing);
96
+ if (configDrift && options.allowConfigDrift !== true) {
97
+ throw new EvalServingConfigurationDriftError(baselineServing, candidateServing);
98
+ }
99
+ const behavioral = compareEvalBehaviorMetricsV1(baseline, candidate);
100
+ const trackedMetrics = compareTrackedMetrics(baseline, candidate, options.metric);
101
+ const normalizedFilter = normalizeMetricFilter(options.metric);
102
+ const behavioralMetrics =
103
+ normalizedFilter === undefined
104
+ ? behavioral.comparisons
105
+ : behavioral.comparisons.filter((row) => row.metric === normalizedFilter || row.family === normalizedFilter);
106
+ if (options.metric !== undefined && trackedMetrics.length === 0 && behavioralMetrics.length === 0) {
107
+ throw new Error(`eval metric not found: ${options.metric}`);
108
+ }
15
109
  return {
16
110
  baselineEvalId: baseline.evalId,
17
111
  candidateEvalId: candidate.evalId,
112
+ baselineServingConfiguration: baselineServing,
113
+ candidateServingConfiguration: candidateServing,
114
+ configDrift,
18
115
  passRateDelta: candidate.summary.passRate - baseline.summary.passRate,
19
116
  tokenDelta: baselineTokens.measured && candidateTokens.measured ? candidateTokens.total - baselineTokens.total : null,
20
117
  wallTimeDelta: candidate.summary.wallTimeMs - baseline.summary.wallTimeMs,
118
+ trackedMetrics,
119
+ behavioralMetrics,
120
+ hardGate: behavioral.hardGate,
121
+ envelopeMismatches: behavioral.envelopeMismatches,
122
+ scenarioReports: behaviorRollups(behavioralMetrics, (row) => row.scenarioId),
123
+ roleReports: behaviorRollups(behavioralMetrics, (row) => row.role),
124
+ affectedCorpusResults: behavioral.envelopeMismatches.flatMap((mismatch) => {
125
+ const changedFields = mismatch.fields.filter((field) => field === "prompt" || field === "recipe");
126
+ return changedFields.length === 0 ? [] : [{ scenarioId: mismatch.scenarioId, role: mismatch.role, changedFields }];
127
+ }),
21
128
  };
22
129
  }
23
130
 
24
131
  export function renderEvalComparisonV4(summary: EvalCompareV4Summary): string {
132
+ const envelopeFailures = summary.envelopeMismatches.map(
133
+ (mismatch) =>
134
+ ` incomparable envelope: ${mismatch.scenarioId} ${mismatch.role} ${mismatch.target.id}/${mismatch.target.model ?? "none"} fields=${mismatch.fields.join(",")}`,
135
+ );
136
+ const affected = summary.affectedCorpusResults.map(
137
+ (result) =>
138
+ ` affected corpus result: ${result.scenarioId} role=${result.role} changed=${result.changedFields.join(",")}`,
139
+ );
140
+ const scenarioReports = renderRollups("per-scenario baseline/candidate report", summary.scenarioReports);
141
+ const roleReports = renderRollups("per-role baseline/candidate report", summary.roleReports);
142
+ const hardFailures = summary.hardGate.failures.map(
143
+ (failure) =>
144
+ ` hard failure: ${failure.scenarioId} ${failure.role} ${failure.target.id}/${failure.target.model ?? "none"} ${failure.metric} ${failure.change}`,
145
+ );
146
+ const tracked = summary.trackedMetrics.flatMap((row, index) => [
147
+ ...(index === 0
148
+ ? [
149
+ "tracked metrics:",
150
+ "scenario metric baseline_mean baseline_p90 baseline_variance candidate_mean candidate_p90 candidate_variance mean_delta p90_delta variance_delta change variance_change sources",
151
+ ]
152
+ : []),
153
+ [
154
+ row.scenarioId,
155
+ row.metric,
156
+ formatMetric(row.baseline.mean),
157
+ formatMetric(row.baseline.p90),
158
+ formatMetric(row.baseline.variance ?? null),
159
+ formatMetric(row.candidate.mean),
160
+ formatMetric(row.candidate.p90),
161
+ formatMetric(row.candidate.variance ?? null),
162
+ formatSignedMetric(row.meanDelta),
163
+ formatSignedMetric(row.p90Delta),
164
+ formatSignedMetric(row.varianceDelta),
165
+ row.change,
166
+ row.varianceChange,
167
+ `${row.baseline.sources.join("+") || "none"}->${row.candidate.sources.join("+") || "none"}`,
168
+ ].join(" "),
169
+ ]);
170
+ const behavioral = summary.behavioralMetrics.flatMap((row, index) => [
171
+ ...(index === 0
172
+ ? [
173
+ "behavioral metrics:",
174
+ "scenario role target model family metric baseline_mean baseline_variance baseline_coverage candidate_mean candidate_variance candidate_coverage mean_delta variance_delta change variance_change comparability gate source",
175
+ ]
176
+ : []),
177
+ [
178
+ row.scenarioId,
179
+ row.role,
180
+ row.target.id,
181
+ row.target.model ?? "none",
182
+ row.family,
183
+ row.metric,
184
+ formatMetric(row.baseline.mean),
185
+ formatMetric(row.baseline.variance),
186
+ `${row.baseline.measured}/${row.baseline.observations}`,
187
+ formatMetric(row.candidate.mean),
188
+ formatMetric(row.candidate.variance),
189
+ `${row.candidate.measured}/${row.candidate.observations}`,
190
+ formatSignedMetric(row.meanDelta),
191
+ formatSignedMetric(row.varianceDelta),
192
+ row.change,
193
+ row.varianceChange,
194
+ row.comparability.comparable ? "comparable" : `incomparable:${row.comparability.mismatchedFields.join(",")}`,
195
+ row.hardGate ? "hard" : "informational",
196
+ row.baseline.source,
197
+ ].join(" "),
198
+ ]);
25
199
  return [
26
200
  `baseline eval: ${summary.baselineEvalId}`,
27
201
  `candidate eval: ${summary.candidateEvalId}`,
202
+ `baseline serving: ${renderEvalServingConfiguration(summary.baselineServingConfiguration)}`,
203
+ `candidate serving: ${renderEvalServingConfiguration(summary.candidateServingConfiguration)}`,
204
+ `config drift: ${summary.configDrift ? "allowed" : "none"}`,
28
205
  `pass-rate delta: ${(summary.passRateDelta * 100).toFixed(2)}%`,
29
206
  `token delta: ${summary.tokenDelta === null ? "unmeasured" : summary.tokenDelta}`,
30
207
  `wall-time delta ms: ${summary.wallTimeDelta}`,
208
+ `behavioral hard gate: ${summary.hardGate.pass ? "pass" : `fail (${summary.hardGate.failures.length + summary.hardGate.envelopeFailures.length})`}`,
209
+ ...hardFailures,
210
+ ...envelopeFailures,
211
+ ...affected,
212
+ ...scenarioReports,
213
+ ...roleReports,
214
+ ...tracked,
215
+ ...behavioral,
31
216
  "",
32
217
  ].join("\n");
33
218
  }
219
+
220
+ function behaviorRollups(
221
+ rows: ReadonlyArray<EvalBehaviorMetricComparisonV1>,
222
+ keyOf: (row: EvalBehaviorMetricComparisonV1) => string,
223
+ ): EvalBehaviorComparisonRollupV1[] {
224
+ const groups = new Map<string, EvalBehaviorMetricComparisonV1[]>();
225
+ for (const row of rows) groups.set(keyOf(row), [...(groups.get(keyOf(row)) ?? []), row]);
226
+ return [...groups.entries()]
227
+ .sort(([left], [right]) => left.localeCompare(right))
228
+ .map(([id, grouped]) => ({
229
+ id,
230
+ metrics: changeCounts(grouped.map((row) => row.change)),
231
+ variance: changeCounts(grouped.map((row) => row.varianceChange)),
232
+ }));
233
+ }
234
+
235
+ function changeCounts(changes: ReadonlyArray<EvalMetricChangeV1>): EvalChangeCountsV1 {
236
+ return {
237
+ improved: changes.filter((change) => change === "improved").length,
238
+ regressed: changes.filter((change) => change === "regressed").length,
239
+ unchanged: changes.filter((change) => change === "unchanged").length,
240
+ incomparable: changes.filter((change) => change === "incomparable").length,
241
+ };
242
+ }
243
+
244
+ function renderRollups(title: string, reports: ReadonlyArray<EvalBehaviorComparisonRollupV1>): string[] {
245
+ if (reports.length === 0) return [];
246
+ return [
247
+ `${title}:`,
248
+ ...reports.map(
249
+ (report) =>
250
+ ` ${report.id}: metrics ${renderChangeCounts(report.metrics)}; variance ${renderChangeCounts(report.variance)}`,
251
+ ),
252
+ ];
253
+ }
254
+
255
+ function renderChangeCounts(counts: EvalChangeCountsV1): string {
256
+ return `improved=${counts.improved} regressed=${counts.regressed} unchanged=${counts.unchanged} incomparable=${counts.incomparable}`;
257
+ }
258
+
259
+ function compareTrackedMetrics(
260
+ baseline: EvalArtifactV4,
261
+ candidate: EvalArtifactV4,
262
+ metricFilter?: string,
263
+ ): EvalTrackedMetricComparisonV1[] {
264
+ const baselineAggregates =
265
+ baseline.aggregates ??
266
+ aggregateEvalVerdicts(baseline.results.flatMap((result) => (result.verdict === undefined ? [] : [result.verdict])));
267
+ const candidateAggregates =
268
+ candidate.aggregates ??
269
+ aggregateEvalVerdicts(candidate.results.flatMap((result) => (result.verdict === undefined ? [] : [result.verdict])));
270
+ const baselineByScenario = new Map(baselineAggregates.map((entry) => [entry.scenarioId, entry]));
271
+ const candidateByScenario = new Map(candidateAggregates.map((entry) => [entry.scenarioId, entry]));
272
+ const scenarioIds = [...baselineByScenario.keys()]
273
+ .filter((scenarioId) => candidateByScenario.has(scenarioId))
274
+ .sort((left, right) => left.localeCompare(right));
275
+ const filter = normalizeMetricFilter(metricFilter);
276
+ const rows: EvalTrackedMetricComparisonV1[] = [];
277
+ for (const scenarioId of scenarioIds) {
278
+ const baselineAggregate = baselineByScenario.get(scenarioId);
279
+ const candidateAggregate = candidateByScenario.get(scenarioId);
280
+ if (baselineAggregate === undefined || candidateAggregate === undefined) continue;
281
+ for (const metric of EVAL_TRACKED_METRIC_NAMES) {
282
+ if (filter !== undefined && filter !== metric) continue;
283
+ rows.push(
284
+ metricComparison(
285
+ scenarioId,
286
+ metric,
287
+ baselineAggregate.trackedMetrics[metric],
288
+ candidateAggregate.trackedMetrics[metric],
289
+ ),
290
+ );
291
+ }
292
+ const reasons = new Set([
293
+ ...Object.keys(baselineAggregate.trackedMetrics.expectedColdReasons),
294
+ ...Object.keys(candidateAggregate.trackedMetrics.expectedColdReasons),
295
+ ]);
296
+ for (const reason of [...reasons].sort((left, right) => left.localeCompare(right))) {
297
+ const metric = `expectedColdReasons.${reason}`;
298
+ if (filter !== undefined && filter !== metric && filter !== "expectedColdReasons") continue;
299
+ rows.push(
300
+ metricComparison(
301
+ scenarioId,
302
+ metric,
303
+ baselineAggregate.trackedMetrics.expectedColdReasons[reason] ?? zeroDistribution(baselineAggregate.k),
304
+ candidateAggregate.trackedMetrics.expectedColdReasons[reason] ?? zeroDistribution(candidateAggregate.k),
305
+ ),
306
+ );
307
+ }
308
+ }
309
+ return rows;
310
+ }
311
+
312
+ function metricComparison(
313
+ scenarioId: string,
314
+ metric: string,
315
+ baseline: EvalMetricDistributionV1,
316
+ candidate: EvalMetricDistributionV1,
317
+ ): EvalTrackedMetricComparisonV1 {
318
+ assertComparableTrackedMetricSources(`${scenarioId}.${metric}`, baseline.sources, candidate.sources);
319
+ const direction = trackedMetricDirection(metric);
320
+ return {
321
+ scenarioId,
322
+ metric,
323
+ baseline,
324
+ candidate,
325
+ meanDelta: subtractNullable(candidate.mean, baseline.mean),
326
+ p90Delta: subtractNullable(candidate.p90, baseline.p90),
327
+ varianceDelta: subtractNullable(candidate.variance ?? null, baseline.variance ?? null),
328
+ change: classifyChange(baseline.mean, candidate.mean, direction),
329
+ varianceChange: classifyChange(baseline.variance ?? null, candidate.variance ?? null, "lower"),
330
+ };
331
+ }
332
+
333
+ function servingConfigurationOf(artifact: EvalArtifactV4): EvalServingConfigurationV1 {
334
+ return (
335
+ artifact.servingConfiguration ?? {
336
+ targetId: artifact.matrix.target,
337
+ runtimeId: null,
338
+ modelId: artifact.matrix.model,
339
+ serverBuild: null,
340
+ total_slots: null,
341
+ thinkingLevel: artifact.matrix.thinking,
342
+ compiledPromptHash: null,
343
+ }
344
+ );
345
+ }
346
+
347
+ function normalizeMetricFilter(metric: string | undefined): string | undefined {
348
+ if (metric === undefined) return undefined;
349
+ const trimmed = metric.trim();
350
+ if (trimmed.startsWith("trackedMetrics.")) return trimmed.slice("trackedMetrics.".length);
351
+ if (trimmed.startsWith("behavioralMetrics.")) return trimmed.slice("behavioralMetrics.".length);
352
+ return trimmed;
353
+ }
354
+
355
+ function trackedMetricDirection(metric: string): "higher" | "lower" {
356
+ return metric === "cacheReadTokens" ? "higher" : "lower";
357
+ }
358
+
359
+ function zeroDistribution(observations: number): EvalMetricDistributionV1 {
360
+ return {
361
+ observations,
362
+ measured: observations,
363
+ unmeasured: 0,
364
+ mean: 0,
365
+ min: 0,
366
+ max: 0,
367
+ p90: 0,
368
+ variance: 0,
369
+ standardDeviation: 0,
370
+ sources: ["ledger"],
371
+ };
372
+ }
373
+
374
+ function subtractNullable(left: number | null, right: number | null): number | null {
375
+ return left === null || right === null ? null : left - right;
376
+ }
377
+
378
+ function formatMetric(value: number | null): string {
379
+ return value === null ? "null" : Number.isInteger(value) ? String(value) : value.toFixed(2);
380
+ }
381
+
382
+ function formatSignedMetric(value: number | null): string {
383
+ if (value === null) return "null";
384
+ const formatted = formatMetric(value);
385
+ return value > 0 ? `+${formatted}` : formatted;
386
+ }