@tangle-network/agent-eval 1.0.19 → 1.0.21

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 (284) hide show
  1. package/CHANGELOG.md +19 -27
  2. package/dist/adapters/http.d.ts +2 -2
  3. package/dist/{agent-profile-CPv4tENR.d.ts → agent-profile-BO8ruI6R.d.ts} +3 -3
  4. package/dist/{agent-profile-CPv4tENR.d.ts.map → agent-profile-BO8ruI6R.d.ts.map} +1 -1
  5. package/dist/analyst/index.d.ts +14 -14
  6. package/dist/analyst/index.js +11 -12
  7. package/dist/analyst/index.js.map +1 -1
  8. package/dist/{analyze-runs-pJ4fhnYg.d.ts → analyze-runs-DYYe18Gw.d.ts} +4 -4
  9. package/dist/{analyze-runs-pJ4fhnYg.d.ts.map → analyze-runs-DYYe18Gw.d.ts.map} +1 -1
  10. package/dist/{analyze-runs-C7vWBDhl.js → analyze-runs-jLTRKuoK.js} +6 -110
  11. package/dist/analyze-runs-jLTRKuoK.js.map +1 -0
  12. package/dist/app-grade/index.d.ts +4 -4
  13. package/dist/app-grade/index.js +3 -3
  14. package/dist/{benchmark-command-DSJQeYom.js → benchmark-command-CoNndRCk.js} +13 -14
  15. package/dist/benchmark-command-CoNndRCk.js.map +1 -0
  16. package/dist/benchmarks/index.d.ts +4 -4
  17. package/dist/benchmarks/index.js +4 -5
  18. package/dist/benchmarks/index.js.map +1 -1
  19. package/dist/builder-eval/index.js +1 -1
  20. package/dist/{calibration-CbZPApIO.js → calibration-Cf-wpqqO.js} +2 -2
  21. package/dist/{calibration-CbZPApIO.js.map → calibration-Cf-wpqqO.js.map} +1 -1
  22. package/dist/campaign/index.d.ts +7 -7
  23. package/dist/campaign/index.js +10 -10
  24. package/dist/{campaign-BABipFel.js → campaign-Bl07uIaf.js} +14 -15
  25. package/dist/{campaign-BABipFel.js.map → campaign-Bl07uIaf.js.map} +1 -1
  26. package/dist/{campaign-evidence-DONEWvVY.js → campaign-evidence-NtH29ctt.js} +3 -3
  27. package/dist/{campaign-evidence-DONEWvVY.js.map → campaign-evidence-NtH29ctt.js.map} +1 -1
  28. package/dist/{chat-client-D0me8iwq.js → chat-client-Zz97O3NB.js} +2 -2
  29. package/dist/{chat-client-D0me8iwq.js.map → chat-client-Zz97O3NB.js.map} +1 -1
  30. package/dist/{chat-json-call-C473CwTt.js → chat-json-call-Dgi7-RH-.js} +2 -2
  31. package/dist/{chat-json-call-C473CwTt.js.map → chat-json-call-Dgi7-RH-.js.map} +1 -1
  32. package/dist/cli.js +7 -7
  33. package/dist/{types-0KglnQ9o.d.ts → cluster-Cd0MRbr9.d.ts} +44 -2
  34. package/dist/cluster-Cd0MRbr9.d.ts.map +1 -0
  35. package/dist/cluster-bootstrap-B0MTOBRU.js +24 -0
  36. package/dist/cluster-bootstrap-B0MTOBRU.js.map +1 -0
  37. package/dist/{completion-verifier-7KEfroqx.d.ts → completion-verifier-BEpsr3N4.d.ts} +2 -2
  38. package/dist/{completion-verifier-7KEfroqx.d.ts.map → completion-verifier-BEpsr3N4.d.ts.map} +1 -1
  39. package/dist/{completion-verifier-ClmJV_Yc.js → completion-verifier-DFyYFezn.js} +3 -3
  40. package/dist/{completion-verifier-ClmJV_Yc.js.map → completion-verifier-DFyYFezn.js.map} +1 -1
  41. package/dist/contract/index.d.ts +10 -10
  42. package/dist/contract/index.js +14 -15
  43. package/dist/contract/index.js.map +1 -1
  44. package/dist/{cost-ledger-CS8R3IZj.js → cost-ledger-DfhStGcZ.js} +2 -2
  45. package/dist/{cost-ledger-CS8R3IZj.js.map → cost-ledger-DfhStGcZ.js.map} +1 -1
  46. package/dist/{decision-point-CNyoKOkz.js → decision-point-Di2LATNH.js} +4 -4
  47. package/dist/{decision-point-CNyoKOkz.js.map → decision-point-Di2LATNH.js.map} +1 -1
  48. package/dist/{default-registry-BB9_jzyH.js → default-registry-CdFbxPnr.js} +5 -5
  49. package/dist/{default-registry-BB9_jzyH.js.map → default-registry-CdFbxPnr.js.map} +1 -1
  50. package/dist/{default-registry-BbXr18AT.d.ts → default-registry-aPvwtzEM.d.ts} +7 -7
  51. package/dist/{default-registry-BbXr18AT.d.ts.map → default-registry-aPvwtzEM.d.ts.map} +1 -1
  52. package/dist/{define-agent-eval-Q4FdXGj7.js → define-agent-eval-BROmIwn1.js} +6 -6
  53. package/dist/{define-agent-eval-Q4FdXGj7.js.map → define-agent-eval-BROmIwn1.js.map} +1 -1
  54. package/dist/{define-agent-eval-D_DQMH04.d.ts → define-agent-eval-Bx3b9B01.d.ts} +7 -7
  55. package/dist/{define-agent-eval-D_DQMH04.d.ts.map → define-agent-eval-Bx3b9B01.d.ts.map} +1 -1
  56. package/dist/diagnosis/index.d.ts +2 -2
  57. package/dist/diagnosis/index.js +1 -1
  58. package/dist/{dspy-rlm-engine-CqQc0R2l.js → dspy-rlm-engine-cWyn8G1a.js} +4 -4
  59. package/dist/{dspy-rlm-engine-CqQc0R2l.js.map → dspy-rlm-engine-cWyn8G1a.js.map} +1 -1
  60. package/dist/{engine-rfV8Ff8V.d.ts → engine-BF8naBn7.d.ts} +2 -2
  61. package/dist/{engine-rfV8Ff8V.d.ts.map → engine-BF8naBn7.d.ts.map} +1 -1
  62. package/dist/{eval-campaign-CZSCjxVb.js → eval-campaign-Dq8DEKmy.js} +2 -2
  63. package/dist/{eval-campaign-CZSCjxVb.js.map → eval-campaign-Dq8DEKmy.js.map} +1 -1
  64. package/dist/{evaluation-SDXKL6Fx.d.ts → evaluation-CFKj_IlJ.d.ts} +4 -4
  65. package/dist/{evaluation-SDXKL6Fx.d.ts.map → evaluation-CFKj_IlJ.d.ts.map} +1 -1
  66. package/dist/{evaluation-BBI37mRe.js → evaluation-bU_bhlv-.js} +2 -2
  67. package/dist/{evaluation-BBI37mRe.js.map → evaluation-bU_bhlv-.js.map} +1 -1
  68. package/dist/evaluation.d.ts +1 -1
  69. package/dist/evaluation.js +1 -1
  70. package/dist/{exact-types-CaLLU1Pz.d.ts → exact-types-VAFjtOQN.d.ts} +2 -2
  71. package/dist/{exact-types-CaLLU1Pz.d.ts.map → exact-types-VAFjtOQN.d.ts.map} +1 -1
  72. package/dist/experiment/index.d.ts +3 -5
  73. package/dist/experiment/index.d.ts.map +1 -1
  74. package/dist/experiment/index.js +7 -12
  75. package/dist/experiment/index.js.map +1 -1
  76. package/dist/{exporters-BYBG9b1n.d.ts → exporters-dQKYD_Zo.d.ts} +2 -2
  77. package/dist/{exporters-BYBG9b1n.d.ts.map → exporters-dQKYD_Zo.d.ts.map} +1 -1
  78. package/dist/{external-optimizer-process-CfrY-nJT.js → external-optimizer-process-2ImGH_LR.js} +2 -2
  79. package/dist/{external-optimizer-process-CfrY-nJT.js.map → external-optimizer-process-2ImGH_LR.js.map} +1 -1
  80. package/dist/{external-optimizer-subprocess-BtJk8UcU.js → external-optimizer-subprocess-_C_2yriN.js} +3 -3
  81. package/dist/{external-optimizer-subprocess-BtJk8UcU.js.map → external-optimizer-subprocess-_C_2yriN.js.map} +1 -1
  82. package/dist/{feedback-trajectory-P37ju-gW.d.ts → feedback-trajectory-DTj8eVTR.d.ts} +3 -3
  83. package/dist/{feedback-trajectory-P37ju-gW.d.ts.map → feedback-trajectory-DTj8eVTR.d.ts.map} +1 -1
  84. package/dist/{findings-store-BnYN57-h.js → findings-store-BSuaV3dN.js} +7 -24
  85. package/dist/findings-store-BSuaV3dN.js.map +1 -0
  86. package/dist/fuzz.js +2 -2
  87. package/dist/{index-D4lT7aFu.d.ts → index-7tWvC0aW.d.ts} +28 -128
  88. package/dist/index-7tWvC0aW.d.ts.map +1 -0
  89. package/dist/{index-C3t17kHu.d.ts → index-B62BaRzH.d.ts} +7 -100
  90. package/dist/index-B62BaRzH.d.ts.map +1 -0
  91. package/dist/{index-HuSMvqR2.d.ts → index-BlTbxHlY.d.ts} +5 -5
  92. package/dist/{index-HuSMvqR2.d.ts.map → index-BlTbxHlY.d.ts.map} +1 -1
  93. package/dist/{index-AmfM3usI.d.ts → index-CM12lr9E.d.ts} +8 -8
  94. package/dist/{index-AmfM3usI.d.ts.map → index-CM12lr9E.d.ts.map} +1 -1
  95. package/dist/{index-DLVbfss9.d.ts → index-DFso1b2W.d.ts} +8 -8
  96. package/dist/{index-DLVbfss9.d.ts.map → index-DFso1b2W.d.ts.map} +1 -1
  97. package/dist/index-DNXFzSpc.d.ts +1101 -0
  98. package/dist/index-DNXFzSpc.d.ts.map +1 -0
  99. package/dist/{index-BPmj-_ta.d.ts → index-DfqAy8ct.d.ts} +10 -9
  100. package/dist/index-DfqAy8ct.d.ts.map +1 -0
  101. package/dist/index.d.ts +29 -31
  102. package/dist/index.js +28 -164
  103. package/dist/index.js.map +1 -1
  104. package/dist/{integrity-BAdVzT8u.d.ts → integrity-CrmnGB2T.d.ts} +2 -2
  105. package/dist/{integrity-BAdVzT8u.d.ts.map → integrity-CrmnGB2T.d.ts.map} +1 -1
  106. package/dist/{judge-gate-CIlBrn6q.js → judge-gate-CrN0PaAL.js} +3 -3
  107. package/dist/{judge-gate-CIlBrn6q.js.map → judge-gate-CrN0PaAL.js.map} +1 -1
  108. package/dist/{kind-factory-Cfz8DUCc.js → kind-factory-CV0FNCMM.js} +2 -2
  109. package/dist/{kind-factory-Cfz8DUCc.js.map → kind-factory-CV0FNCMM.js.map} +1 -1
  110. package/dist/{llm-client-BdSiZ4qz.js → llm-client-DMMSg-Ro.js} +2 -2
  111. package/dist/{llm-client-BdSiZ4qz.js.map → llm-client-DMMSg-Ro.js.map} +1 -1
  112. package/dist/{llm-judge-VD_MI6kd.js → llm-judge-C813vmpc.js} +13 -14
  113. package/dist/{llm-judge-VD_MI6kd.js.map → llm-judge-C813vmpc.js.map} +1 -1
  114. package/dist/{matrix-Yg1lHIGI.d.ts → matrix-C-GxIgeJ.d.ts} +2 -2
  115. package/dist/{matrix-Yg1lHIGI.d.ts.map → matrix-C-GxIgeJ.d.ts.map} +1 -1
  116. package/dist/meta-eval/index.d.ts +2 -2
  117. package/dist/meta-eval/index.js +7 -7
  118. package/dist/{metrics-DNCAUtuB.js → metrics-zALlR17s.js} +16 -17
  119. package/dist/metrics-zALlR17s.js.map +1 -0
  120. package/dist/multishot/golden/index.d.ts +1 -1
  121. package/dist/multishot/index.d.ts +2 -2
  122. package/dist/multishot/index.js +1 -1
  123. package/dist/openapi.json +1 -1
  124. package/dist/{outcome-observations-DZu5hLYF.js → outcome-observations-DPkH-LHQ.js} +2 -3
  125. package/dist/{outcome-observations-DZu5hLYF.js.map → outcome-observations-DPkH-LHQ.js.map} +1 -1
  126. package/dist/paired-arms-DjMEtYI9.js +338 -0
  127. package/dist/paired-arms-DjMEtYI9.js.map +1 -0
  128. package/dist/{paired-promotion-decision-CiueaTTj.js → paired-promotion-decision-Bi9__RQS.js} +2 -3
  129. package/dist/{paired-promotion-decision-CiueaTTj.js.map → paired-promotion-decision-Bi9__RQS.js.map} +1 -1
  130. package/dist/{paired-promotion-decision-BDnqepZ1.d.ts → paired-promotion-decision-DJU3fZxH.d.ts} +3 -113
  131. package/dist/paired-promotion-decision-DJU3fZxH.d.ts.map +1 -0
  132. package/dist/{pareto-a3EOmjfe.js → pareto-fJZg8pXm.js} +3 -6
  133. package/dist/{pareto-a3EOmjfe.js.map → pareto-fJZg8pXm.js.map} +1 -1
  134. package/dist/pipelines/index.js +1 -1
  135. package/dist/{prime-protocol-DKRk4l4U.d.ts → prime-protocol-D1NkJRuD.d.ts} +2 -2
  136. package/dist/{prime-protocol-DKRk4l4U.d.ts.map → prime-protocol-D1NkJRuD.d.ts.map} +1 -1
  137. package/dist/{promotion-gate-DOkgJcLW.d.ts → promotion-gate-Da0xV4sa.d.ts} +3 -67
  138. package/dist/promotion-gate-Da0xV4sa.d.ts.map +1 -0
  139. package/dist/{promotion-policy-DLPcEnCf.js → promotion-policy-DCgftbnd.js} +4 -4
  140. package/dist/{promotion-policy-DLPcEnCf.js.map → promotion-policy-DCgftbnd.js.map} +1 -1
  141. package/dist/{proposal-findings-BfzzZ_lN.js → proposal-findings-DWs94yT8.js} +2 -2
  142. package/dist/{proposal-findings-BfzzZ_lN.js.map → proposal-findings-DWs94yT8.js.map} +1 -1
  143. package/dist/{registry-Ck7w-rr5.d.ts → registry-BAcEbzKm.d.ts} +4 -4
  144. package/dist/{registry-Ck7w-rr5.d.ts.map → registry-BAcEbzKm.d.ts.map} +1 -1
  145. package/dist/{release-confidence-nfQNh3eb.js → release-confidence-DGtuPDxJ.js} +2 -2
  146. package/dist/{release-confidence-nfQNh3eb.js.map → release-confidence-DGtuPDxJ.js.map} +1 -1
  147. package/dist/reporting.d.ts +4 -5
  148. package/dist/reporting.d.ts.map +1 -1
  149. package/dist/reporting.js +5 -7
  150. package/dist/reporting.js.map +1 -1
  151. package/dist/{researcher-CLnNJ7Or.d.ts → researcher-CSlc84Bp.d.ts} +4 -4
  152. package/dist/{researcher-CLnNJ7Or.d.ts.map → researcher-CSlc84Bp.d.ts.map} +1 -1
  153. package/dist/{reward-hacking-B88f-Kh7.js → reward-hacking-BYiDFbIl.js} +2 -3
  154. package/dist/{reward-hacking-B88f-Kh7.js.map → reward-hacking-BYiDFbIl.js.map} +1 -1
  155. package/dist/rl.d.ts +9 -8
  156. package/dist/rl.d.ts.map +1 -1
  157. package/dist/rl.js +8 -10
  158. package/dist/rl.js.map +1 -1
  159. package/dist/rollout/index.d.ts +4 -4
  160. package/dist/rollout/index.js +1 -1
  161. package/dist/{rollout-Cu7kHGIe.js → rollout-DKa54qer.js} +2 -2
  162. package/dist/{rollout-Cu7kHGIe.js.map → rollout-DKa54qer.js.map} +1 -1
  163. package/dist/{rubric-predictive-validity-D_6ZPTNR.js → rubric-predictive-validity-DL7gJ51F.js} +2 -2
  164. package/dist/{rubric-predictive-validity-D_6ZPTNR.js.map → rubric-predictive-validity-DL7gJ51F.js.map} +1 -1
  165. package/dist/{run-record-CbUi2adH.js → run-record-CFbnkE1K.js} +2 -2
  166. package/dist/{run-record-CbUi2adH.js.map → run-record-CFbnkE1K.js.map} +1 -1
  167. package/dist/{runtime-rollouts-kGn1K5ut.js → runtime-rollouts-BNrm5nC_.js} +2 -2
  168. package/dist/{runtime-rollouts-kGn1K5ut.js.map → runtime-rollouts-BNrm5nC_.js.map} +1 -1
  169. package/dist/{schema-CfoA9U_U.d.ts → schema-PDKQ_iNE.d.ts} +2 -131
  170. package/dist/schema-PDKQ_iNE.d.ts.map +1 -0
  171. package/dist/search/index.d.ts +2 -2
  172. package/dist/search/index.js +2 -2
  173. package/dist/{search-kernel-DjqaPJqL.d.ts → search-kernel-CQGLbmgn.d.ts} +3 -3
  174. package/dist/{search-kernel-DjqaPJqL.d.ts.map → search-kernel-CQGLbmgn.d.ts.map} +1 -1
  175. package/dist/{search-summary-RJ5zXjHN.js → search-summary-sPWiYtSl.js} +4 -4
  176. package/dist/{search-summary-RJ5zXjHN.js.map → search-summary-sPWiYtSl.js.map} +1 -1
  177. package/dist/{sequential-BPbvgzfe.js → sequential-9TVb5Nu5.js} +3 -3
  178. package/dist/{sequential-BPbvgzfe.js.map → sequential-9TVb5Nu5.js.map} +1 -1
  179. package/dist/{sequential-Bb_pdTqG.d.ts → sequential-B9p6uoV8.d.ts} +9 -2
  180. package/dist/{sequential-Bb_pdTqG.d.ts.map → sequential-B9p6uoV8.d.ts.map} +1 -1
  181. package/dist/{sequential-Bjs5roiK.js → sequential-CAksw6EU.js} +32 -2
  182. package/dist/{sequential-Bjs5roiK.js.map → sequential-CAksw6EU.js.map} +1 -1
  183. package/dist/{server-CPKyf3bz.js → server-DFVwfoZq.js} +3 -3
  184. package/dist/{server-CPKyf3bz.js.map → server-DFVwfoZq.js.map} +1 -1
  185. package/dist/{skillopt-optimization-method-D9Hlmhjg.js → skillopt-optimization-method-QO1nU8Kz.js} +9 -9
  186. package/dist/{skillopt-optimization-method-D9Hlmhjg.js.map → skillopt-optimization-method-QO1nU8Kz.js.map} +1 -1
  187. package/dist/{statistical-heldout-C-tgpUuu.d.ts → statistical-heldout-CvOwNqt9.d.ts} +6 -282
  188. package/dist/statistical-heldout-CvOwNqt9.d.ts.map +1 -0
  189. package/dist/{statistical-heldout-NBxC7jGo.js → statistical-heldout-DLO4Ebos.js} +3 -3
  190. package/dist/{statistical-heldout-NBxC7jGo.js.map → statistical-heldout-DLO4Ebos.js.map} +1 -1
  191. package/dist/statistics-CvvC-MPv.js +2634 -0
  192. package/dist/statistics-CvvC-MPv.js.map +1 -0
  193. package/dist/statistics.d.ts +3 -0
  194. package/dist/statistics.js +3 -0
  195. package/dist/{store-otlp-BfPRa2p8.js → store-otlp-C-jyj3WX.js} +2 -2
  196. package/dist/{store-otlp-BfPRa2p8.js.map → store-otlp-C-jyj3WX.js.map} +1 -1
  197. package/dist/{store-tool-spans-BgwVHXma.d.ts → store-tool-spans-B3z_Qb5u.d.ts} +5 -5
  198. package/dist/{store-tool-spans-BgwVHXma.d.ts.map → store-tool-spans-B3z_Qb5u.d.ts.map} +1 -1
  199. package/dist/{store-tool-spans-BrSWHB9O.js → store-tool-spans-DalWlKY8.js} +3 -3
  200. package/dist/{store-tool-spans-BrSWHB9O.js.map → store-tool-spans-DalWlKY8.js.map} +1 -1
  201. package/dist/{summary-report-C3IJvp0i.js → summary-report-BEhqoxrA.js} +4 -52
  202. package/dist/summary-report-BEhqoxrA.js.map +1 -0
  203. package/dist/{summary-report-0La5vejY.d.ts → summary-report-DyZquTaH.d.ts} +2 -2
  204. package/dist/{summary-report-0La5vejY.d.ts.map → summary-report-DyZquTaH.d.ts.map} +1 -1
  205. package/dist/supervisor-run/index.d.ts +2 -2
  206. package/dist/supervisor-run/index.js +1 -1
  207. package/dist/{systemone-CpDppX8s.d.ts → systemone-DVk-Crtu.d.ts} +7 -7
  208. package/dist/{systemone-CpDppX8s.d.ts.map → systemone-DVk-Crtu.d.ts.map} +1 -1
  209. package/dist/{systemone-review-nbuMxxmi.d.ts → systemone-review-CToBl1oa.d.ts} +2 -2
  210. package/dist/{systemone-review-nbuMxxmi.d.ts.map → systemone-review-CToBl1oa.d.ts.map} +1 -1
  211. package/dist/systemone.d.ts +3 -3
  212. package/dist/systemone.js +2 -2
  213. package/dist/{tool-groups-BQsjZD9F.d.ts → tool-groups-R5RO9OHn.d.ts} +3 -3
  214. package/dist/tool-groups-R5RO9OHn.d.ts.map +1 -0
  215. package/dist/{tool-waste-Czt3aWID.js → tool-waste-BqSmmPUz.js} +2 -3
  216. package/dist/{tool-waste-Czt3aWID.js.map → tool-waste-BqSmmPUz.js.map} +1 -1
  217. package/dist/{trace-questions-CpTrtEcm.d.ts → trace-questions-BY6taxnl.d.ts} +3 -3
  218. package/dist/{trace-questions-CpTrtEcm.d.ts.map → trace-questions-BY6taxnl.d.ts.map} +1 -1
  219. package/dist/trace-repair/index.d.ts +1 -1
  220. package/dist/trace-repair/index.js +1 -2
  221. package/dist/trace-repair/index.js.map +1 -1
  222. package/dist/traces.d.ts +5 -5
  223. package/dist/traces.js +3 -3
  224. package/dist/{tree-DIHp6UQ0.js → tree-C-N2kP-8.js} +4 -6
  225. package/dist/{tree-DIHp6UQ0.js.map → tree-C-N2kP-8.js.map} +1 -1
  226. package/dist/{types-DsuOcYsE.d.ts → types-BsdcSWnf.d.ts} +2 -2
  227. package/dist/{types-DsuOcYsE.d.ts.map → types-BsdcSWnf.d.ts.map} +1 -1
  228. package/dist/{types-DNB9hIXa.d.ts → types-DUVhW5V1.d.ts} +3 -2
  229. package/dist/types-DUVhW5V1.d.ts.map +1 -0
  230. package/dist/{types-wDBk14lj.d.ts → types-ip2D3-Ib.d.ts} +5 -4
  231. package/dist/types-ip2D3-Ib.d.ts.map +1 -0
  232. package/dist/{types-B9s9Xo5_.d.ts → types-kBMNdo1X.d.ts} +3 -9
  233. package/dist/types-kBMNdo1X.d.ts.map +1 -0
  234. package/dist/wire/index.d.ts +4 -4
  235. package/dist/wire/index.js +1 -1
  236. package/docs/design/statistics-decisions.md +45 -364
  237. package/docs/design.md +1 -1
  238. package/docs/research-report-methodology.md +1 -1
  239. package/package.json +6 -1
  240. package/dist/agreement-irr-DXcXEt4I.js +0 -182
  241. package/dist/agreement-irr-DXcXEt4I.js.map +0 -1
  242. package/dist/analyze-runs-C7vWBDhl.js.map +0 -1
  243. package/dist/benchmark-command-DSJQeYom.js.map +0 -1
  244. package/dist/cluster-bootstrap-BteuR1hg.js +0 -65
  245. package/dist/cluster-bootstrap-BteuR1hg.js.map +0 -1
  246. package/dist/descriptive-B3Eb7tX0.js +0 -229
  247. package/dist/descriptive-B3Eb7tX0.js.map +0 -1
  248. package/dist/findings-store-BnYN57-h.js.map +0 -1
  249. package/dist/index-BPmj-_ta.d.ts.map +0 -1
  250. package/dist/index-C3t17kHu.d.ts.map +0 -1
  251. package/dist/index-D4lT7aFu.d.ts.map +0 -1
  252. package/dist/judge-calibration-DKR6Xi-w.js +0 -319
  253. package/dist/judge-calibration-DKR6Xi-w.js.map +0 -1
  254. package/dist/judge-calibration-DnrbQaXI.d.ts +0 -144
  255. package/dist/judge-calibration-DnrbQaXI.d.ts.map +0 -1
  256. package/dist/mean-D1d6gedE.js +0 -284
  257. package/dist/mean-D1d6gedE.js.map +0 -1
  258. package/dist/metrics-DNCAUtuB.js.map +0 -1
  259. package/dist/multiplicity-DAd4xaDC.js +0 -100
  260. package/dist/multiplicity-DAd4xaDC.js.map +0 -1
  261. package/dist/multiplicity-D_SEsc5j.d.ts +0 -41
  262. package/dist/multiplicity-D_SEsc5j.d.ts.map +0 -1
  263. package/dist/paired-arms-3-tsXvvC.js +0 -740
  264. package/dist/paired-arms-3-tsXvvC.js.map +0 -1
  265. package/dist/paired-binary-CMzmSSd5.js +0 -372
  266. package/dist/paired-binary-CMzmSSd5.js.map +0 -1
  267. package/dist/paired-promotion-decision-BDnqepZ1.d.ts.map +0 -1
  268. package/dist/paired-tests-Buh6feJ4.js +0 -211
  269. package/dist/paired-tests-Buh6feJ4.js.map +0 -1
  270. package/dist/power-and-mde-g3vOn0XK.js +0 -99
  271. package/dist/power-and-mde-g3vOn0XK.js.map +0 -1
  272. package/dist/promotion-gate-DOkgJcLW.d.ts.map +0 -1
  273. package/dist/random-BN150Q_X.d.ts +0 -79
  274. package/dist/random-BN150Q_X.d.ts.map +0 -1
  275. package/dist/schema-CfoA9U_U.d.ts.map +0 -1
  276. package/dist/sequential-eprocess-C1YLwFaz.js +0 -129
  277. package/dist/sequential-eprocess-C1YLwFaz.js.map +0 -1
  278. package/dist/statistical-heldout-C-tgpUuu.d.ts.map +0 -1
  279. package/dist/summary-report-C3IJvp0i.js.map +0 -1
  280. package/dist/tool-groups-BQsjZD9F.d.ts.map +0 -1
  281. package/dist/types-0KglnQ9o.d.ts.map +0 -1
  282. package/dist/types-B9s9Xo5_.d.ts.map +0 -1
  283. package/dist/types-DNB9hIXa.d.ts.map +0 -1
  284. package/dist/types-wDBk14lj.d.ts.map +0 -1
@@ -1,366 +1,47 @@
1
1
  # Statistics: adoption decisions
2
2
 
3
- This is the standing decision record for `src/statistics/` and the estimators layered on it.
4
- It answers three questions: which statistic is trustworthy, where each number comes from, and what a caller is allowed to ask for at 3–10 repetitions per arm.
5
- The API itself is documented in [`concepts.md`](../concepts.md); this file records *why* each statistic is kept, fixed, or refused.
6
-
7
- ## Verdict
8
-
9
- Keep the statistics in-house, fix them here, and take no statistics package as a runtime dependency.
10
-
11
- Of the 38 exported statistics, 17 were correct, 15 were defective, and 6 are correct mathematics applied in a regime where they mislead.
12
- The defects concentrated in exactly the functions a promotion gate reads: the paired rank tests, the bootstrap interval, and the multiple-comparison boundary.
13
- All 15 are repaired; the 6 regime limits are now declared in the result rather than left for a caller to infer.
14
- The largest single defect is a standard-normal CDF that mixed the arguments of the Abramowitz–Stegun error-function approximation, giving up to `3.7189e-2` absolute CDF error at `x = 0.567` where a correct implementation is bounded by `7.5e-8`.
15
- That one line made every affected p-value too small by 26–36 % relative, so the module's real type-I error rate was 6.53 % at a nominal 5 %.
16
-
17
- The reason to stay in-house is not preference.
18
- Every statistic a JavaScript library gets right is one this module already gets right, and every statistic this module gets wrong at 3–10 reps is one no JavaScript library gets right either.
19
- A survey of 13 installed packages, tested by execution rather than documentation, found no npm package that computes an exact rank test under ties, Cliff's delta, or a paired bootstrap interval.
20
-
21
- ## How the numbers below were produced
22
-
23
- Every measurement in this document was produced by executing the code, not by reading it.
24
-
25
- The pristine baseline is `origin/main` blob `md5 15ca1ed9fbdd10215b5d0b682d99946c`, extracted with `git archive` and bundled with `esbuild` so that concurrent edits to the working tree could not move it.
26
- The comparison build is the same bundle taken from the working tree.
27
- References are `scipy 1.13.1` (`stats.norm`, `stats.t`, `stats.mannwhitneyu`, `stats.wilcoxon`, `stats.binomtest`, `stats.pearsonr`, `stats.spearmanr`) and, for the exact conditional null, an independent enumeration of every split of the observed data that agreed with scipy's permutation test to four decimals on 9 of 9 two-sample cases.
28
- Monte Carlo figures are 4,000 trials per cell with a seeded generator unless stated otherwise.
29
-
30
- Where a number in this document differs from one recorded elsewhere, the difference is almost always which build was measured.
31
- The same call `mannWhitneyU([1,2,3],[4,5,6]).p` returns `0.03769147` on `origin/main` and `0.04953448` once the CDF is repaired; both are correctly reported values of different code.
32
-
33
- ## Decision key
34
-
35
- - **keep-and-fix** — the statistic belongs in this package, the implementation is wrong or incomplete, and the fix is ours to write.
36
- - **keep-with-CI-oracle** — the implementation is correct and stays as-is, pinned against a scipy-generated golden fixture so it cannot silently regress.
37
- - **replace-with-library** — a maintained package does this better than we do.
38
- - **delete** — the statistic should not be callable.
39
-
40
- No statistic in this module is marked **replace-with-library**.
41
- That is the survey's conclusion, not an oversight, and the argument is in [Dependency verdict](#dependency-verdict).
42
-
43
- ## Defective
44
-
45
- These 15 produced a wrong number, hung, or fabricated a verdict.
46
- All are repaired, each with a regression test that fails on the pristine blob and passes after.
47
-
48
- | Statistic | Status | Decision | Measured evidence | Regime caveat |
49
- | --- | --- | --- | --- | --- |
50
- | `normalCdf` | defective, **fixed** | keep-and-fix | `t = 1/(1+p·|x|)` used the unscaled argument while the exponential used `exp(-x²/2) = exp(-z²)` with `z = x/√2`; max abs CDF error `3.7189e-2` at `x = 0.567`. Repaired to evaluate both halves at `z = |x|/√2`; worst deviation from `scipy.stats.norm.cdf` now `4e-10` at `x = 1.6` and `3.4e-8` at `x = 0.565`, inside the A&S 7.1.26 bound of `7.5e-8`. | None once fixed. |
51
- | `normalCdf` duplicate in `src/baseline.ts` | defective, **fixed** | keep-and-fix | A byte-identical second copy of the same defect reached production verdicts through `welchsTTest → studentTCdf`. Deleted with its duplicated `studentTCdf`/`incompleteBeta`/`lnGamma`; the shared math now lives in `src/math/normal.ts`, `src/math/student-t.ts` and `src/math/special-functions.ts`. A `df = 298` case moved from `p = 0.010887` to the true Student-t `0.015150`. | Now one implementation repo-wide: `grep 0.3275911` returns a single site. |
52
- | `mcnemarPower` | defective, **fixed** | keep-and-fix | Reached the normal distribution through the broken forward CDF while its documented inverse `mcnemarRequiredN` reached it through `zQuantile`, so the two were not inverses. `mcnemarPower({p10:0.2, p01:0.1, nPairs:200})` returned `0.773437` against a true `0.736522`; it now returns `0.736522`. | Power was **overstated** above 0.5 and understated below it, so pre-registered N read off this function was too small. |
53
- | `studentTCdf` `df > 100` branch | defective, **deleted** | delete | The branch short-circuited to `normalCdf`, which changed a two-sided `df = 102, t = 1.98` result from the true `0.050398` to `0.047703` and flipped a 5% decision. | Every finite degree of freedom now uses the regularized incomplete beta. |
54
- | `studentTCdf` / `incompleteBeta` at `df ≤ 100` | defective, **fixed** | keep-and-fix | The continued fraction omits the mandatory symmetry branch (`x < (a+1)/(a+b+2)`, else `1 − I(1−x, b, a)`), so it is evaluated outside its convergence domain for small `|t|` and collapses as `x → 1`. `studentTCdf(0.005, 100)` returns `0.89152130` against a true `0.50198972`, an error of `0.3895`. Through `pairedTTest` at `df = 7`: `t = 0.001` reports `p = 0.15130173` against a true `0.99923002`, and `t = 1e-6` reports `p = 0.00015251`. There is a discontinuity at zero — `t = 1e-8` returns exactly `p = 1.0` because `x ≥ 1` short-circuits. | A perfectly null paired result reports `p < 0.05`. The materially wrong band is `|t| ≲ 0.02` at `df = 2–10`, widening to `|t| ≲ 0.058` near `df = 100`; outside it `pairedTTest` matched `scipy.stats.ttest_rel` to `≤ 1e-6` relative across 166 cases. |
55
- | `interRaterReliability` | defective, **fixed** | keep-and-fix | The grouping loop iterates judge-major and opens a new item bucket whenever the last holds `judgeScores.length` entries, so each bucket collects consecutive scores from the *same* judge. It measures within-judge spread, not between-judge agreement, and is anti-correlated with the truth. Two identical judges scoring `[0,100]` return **`−0.500000`** where the true α is `+1.0`; two maximally disagreeing judges (`[0,0]` versus `[100,100]`) return **`+1.000000`** where the true α is `−0.5`. Perfect agreement on three items returns `−0.250000`, on four items `+0.650000`. | The result is also unstable in the item count, because bucket boundaries depend on how the item count divides by the judge count. Zero test coverage repo-wide; consumed by `src/pipelines/judge-agreement.ts:73`. |
56
- | `mannWhitneyU` (NaN input) | defective, **fixed** | keep-and-fix | The tie-grouping loop advances via `while (j < len && combined[j].v === combined[i].v) j++` then `i = j`. `NaN === NaN` is false, so `j` never leaves `i` and the process spins forever. `mannWhitneyU([1,2,3,4,5,6],[NaN,2,3,4,5,6])` did not return within 8 s and blocked the event loop hard enough that a 4 s `setTimeout` never fired. | A single NaN score hangs a campaign rather than failing it. `ranks` is safe only because it uses `i = j + 1`. |
57
- | `wilcoxonSignedRank` (NaN input) | defective, **fixed** | keep-and-fix | The same construct at the absolute-rank grouping loop, same hang. | Reachable from `HeldOutGate.decidePairedDelta` in `held-out-gate.ts`, so a NaN in a holdout set hangs the gate. |
58
- | `mannWhitneyU` (tie and continuity correction) | defective, **fixed** | keep-and-fix | The variance uses the no-ties formula `n₁n₂(n₁+n₂+1)/12` and there is no continuity correction. `[1,2,3]` versus `[4,5,6]` (zero ties) and `[0,0,0]` versus `[1,1,1]` (maximal ties) both return the byte-identical `p = 0.04953448`. scipy's tie-and-continuity-corrected asymptotic separates them at `0.080856` and `0.046854`. | Both omissions push p downward, so they compound the CDF defect rather than offsetting it. `u` is returned as `min(u1,u2)`, which discards the direction of the effect. |
59
- | `wilcoxonSignedRank` (tie and continuity correction) | defective, **fixed** | keep-and-fix | The variance uses `n(n+1)(2n+1)/24` with no tie term `−(1/48)Σ(t³−t)` and no continuity correction, despite the function computing average ranks for ties. | Same compounding direction. The statistic convention also differs from scipy without being documented: this returns `W⁺`, scipy returns `min(W⁺, W⁻)`. |
60
- | `wilcoxonSignedRank` (`n < 6` branch) | defective, **fixed** | **branch deleted** | Line 219 hard-returns `{w: 0, p: 1}` below six non-zero differences, with no exception, no flag, and nothing in the result to distinguish it from a measured null. A clean `+0.5` on all five of five pairs returns `p = 1` where the exact answer is `0.0625`. Because zero deltas are dropped first, ties silently push `n` under the threshold: ten pairs with five zero deltas also returned `p = 1`. | This is the single most consequential open defect at 3–10 reps, and it violates the package's own *no fallbacks, fail loud* rule. It is a false-negative generator precisely in the target regime. |
61
- | `bonferroni` | defective, **fixed** | keep-and-fix | Adjusted values are correct (`min(1, p·k)` matched R `p.adjust` on 8 case sets), but the rejection boundary uses strict `<` where the rule is `p ≤ α/k`. With `p = [0.0125]×4` and `α = 0.05` it returns `significant = [false,false,false,false]`; `holm` on the identical input returns `[true,true,true,true]`. It also has no input validation, unlike `holm`: `bonferroni([-0.1, 0.2], 0.05)` returns `{adjusted: [-0.2, 0.4], significant: [true, false]}` — a negative p-value declared significant. | Two corrections in one module disagree at their shared boundary. |
62
- | `benjaminiHochberg` | defective, **fixed** | keep-and-fix | q-values are correct (matched R `p.adjust('BH')` on 8 case sets including the 15-value worked example and ties), same two rule defects. `benjaminiHochberg([0.05, 0.05], 0.05)` returns `significant = [false, false]` where BH rejects iff `q ≤ α`. `benjaminiHochberg([-0.1, 0.2])` returns `qValues = [-0.2, 0.2], significant = [true, false]`. | Drives `rl/contamination.ts:162` and `summary-report.ts:159`. |
63
- | `pairedTTest` (zero-variance branch) | defective, **fixed** | keep-and-fix | Line 199 returns `p = 0` when the standard error is zero. `pairedTTest([0, 0, 0.5], [0.5, 0.5, 1])` — a constant `+0.5` shift on three pairs — returns `{t: null, df: 2, p: 0}`. That is absolute certainty from three observations where the exact signed-rank floor at `n = 3` is `0.25`, and the object is internally inconsistent (`t` serialized as `null` alongside `p = 0`). | `pairedCohensDz` handles the identical condition correctly by returning `null` and documents why. The module should answer the degenerate case once, the same way, everywhere. |
64
- | `cohensD` (degenerate branches) | defective, **fixed** | keep-and-fix | Returns a silent `0` twice: when either sample has fewer than two observations, and when the pooled standard deviation is zero. `cohensD([1,1,1],[2,2,2])` returns `0` — "no effect" for a maximal, zero-variance separation. | Third distinct answer to the same degenerate condition, after `pairedTTest`'s `p = 0` and `pairedCohensDz`'s `null`. Only `pairedCohensDz` is right. |
65
- | `mulberry32` | defective, **fixed** | keep-and-fix | `let s = seed \| 0 \|\| 0x9e3779b9` collapses seed `0` to the golden-ratio constant. `mulberry32(0)` and `mulberry32(0x9e3779b9 \| 0)` both emit `[0.3588899802, 0.1059032613, 0.6752904793]`. | Seed `0` is a common default, so two runs a caller believes are independent replicates are the same run. |
66
- | `makeRng` / bootstrap seeding | defective, **fixed** | keep-and-fix | `makeRng` returns raw `Math.random` when `opts.seed` is undefined. Two back-to-back `confidenceInterval` calls on identical input returned `lower = 0.30000000000000004` and `lower = 0.3`, `upper = 0.6749999999999999` and `upper = 0.6499999999999999`. Seeding works when supplied (`seed: 7` reproduced exactly). | This contradicts `mulberry32`'s own docstring, which states that a seed is required because unseeded randomness in gate verdicts is non-reproducible by construction. `src/contract/analyze-runs.ts` is the live call site that passes no seed; the gates in `held-out-gate.ts`, `promotion-policy.ts`, `statistical-heldout.ts`, `measured-comparison.ts` and `summary-report.ts` all thread one through. |
67
-
68
- ## Correct mathematics, misleading regime
69
-
70
- These 5 compute what they claim.
71
- They mislead at 3–10 repetitions because the asymptotic assumption does not hold there, and no change to the implementation fixes that.
72
-
73
- | Statistic | Status | Decision | Measured evidence | Regime caveat |
74
- | --- | --- | --- | --- | --- |
75
- | `mannWhitneyU` small-n | approximation limit, **exact path added** | keep-and-fix (add exact path) | The normal approximation was applied unconditionally, including `n = 1`, with no switchover threshold. At three per group the exact minimum attainable two-sided p is `0.100000`, yet complete separation returned `p = 0.04953448`. The default now selects exact computation from the dynamic program's actual state and work, so balanced 12+12 and imbalanced 1+24 designs are both exact. Above that limit, the automatic permutation seed is invariant to observation order and group order; the prior seed changed a two-sided result from `0.04960` to `0.04420` when the groups were swapped. | Repairing the CDF did not fix this; only an exact test does. Discreteness is the binding constraint, and tie-aware `pFloor` now reports it on every result. |
76
- | `wilcoxonSignedRank` small-n | approximation limit, **exact path added** | keep-and-fix (add exact path) | Where the approximation ran it was anti-conservative at `n = 6–7` (minimum attainable `p = 0.0209` against an exact `0.0312` at `n = 6`) and conservative from `n ≥ 8`. The default is now exact by sign-flip enumeration at `n ≤ 20`. | The `n < 6` hard return is gone. `pFloor = 2^(1−n)` is reported on every result, so a design that cannot reach alpha says so. |
77
- | `confidenceInterval` / `pairedBootstrap` | approximation limit, **floor declared** | keep-and-fix (add an n floor) | Percentile bootstrap, correctly constructed. The gate-relevant quantity is `P(low > 0)` under a true null against a nominal 2.5 %. Measured over 4,000 seeded trials on a continuous null: **13.53 % at `n = 3`**, 3.33 % at `n = 5`, 4.45 % at `n = 8`, 3.52 % at `n = 10`, 3.10 % at `n = 20` for the median statistic; 13.85 %, 7.95 %, 5.80 %, 4.90 %, 3.80 % for the mean statistic. On a five-value discrete grid resembling judge scores: 6.02 % at `n = 3` (median), 6.68 % (mean), still 3.43 % at `n = 10` (mean). | This is an intrinsic limit of bootstrapping three points, not an implementation error — scipy's BCa on the same `n = 3` data gives 16.0 %. The defect is the docstring, which states that `low > threshold` means the gain is real at the confidence level. At `n = 3` that claim is wrong by more than 5×. Consumed by `promotion-policy.ts:133`, `held-out-gate.ts:272`, `statistical-heldout.ts:174`, `measured-comparison.ts:995`, `analyze-runs.ts:908`. `pairedBootstrap` now returns `gateEligible`, false below `BOOTSTRAP_GATE_MIN_N = 20`. |
78
- | `pairedRiskDifference` | approximation limit | keep-and-fix | The point estimate and the variance formula both matched the closed form exactly on 6 configurations, but the interval is Wald: empirical coverage against a nominal 95 % is 73.80 % at `n = 5`, 86.48 % at `n = 10`, 93.80 % at `n = 20`, 94.73 % at `n = 200`. With no discordant pairs the variance is exactly zero, so `pairedRiskDifference([0,0,0],[1,1,1])` returns `riskDifference = 1, lower = 1, upper = 1` — a zero-width 95 % interval asserting certainty from three observations, and ten concordant pairs return `[0, 0]`. | The module already ships `wilson` and its own comment block argues against exactly this Wald approach for proportions. A Wilson-style or Tango score interval is the standard fix. |
79
- | `requiredSampleSize`, `requiredPairedSampleSize`, `pairedMde` | approximation limit, **documented** | keep-with-CI-oracle | All three match their stated normal-approximation formulas exactly against `scipy.stats.norm.ppf` closed forms, and are numerically unchanged by the CDF fix because they route through `zQuantile` rather than the forward CDF (`requiredSampleSize({effect: 0.5}) = 63` before and after). They use normal quantiles with no t correction, so they understate required n where n is small: `requiredPairedSampleSize({effect: 0.5})` returns **32** against an exact t-based **34**, and `{effect: 0.8}` returns **13** against **15**. | A 6–13 % shortfall, precisely in the range a caller consults to decide whether 3–10 reps suffice. Both docstrings now say "treat as a lower bound". |
80
-
81
- ## Correct
82
-
83
- These 17 matched their references with zero mismatches and stay as they are, pinned against a golden fixture so they cannot regress silently.
84
-
85
- | Statistic | Decision | Measured evidence |
86
- | --- | --- | --- |
87
- | `pairedTTest` (normal range) | keep-with-CI-oracle | Across 166 cases the t statistic matched `scipy.stats.ttest_rel` to `≤ 1.5e-15` and p to `≤ 1e-6` relative in all but 4. Type-I under a true null over 20,000 replicates: 5.20 % at `n = 3`, 4.74 % at `n = 4`, 4.97 % at `n = 6`, 5.17 % at `n = 8`, 5.03 % at `n = 10` — correctly calibrated at this package's actual sample sizes. |
88
- | `mcnemar` | keep-with-CI-oracle | Exact two-sided binomial on discordant pairs; matched `scipy.stats.binomtest` two-sided to `< 1e-9` on all 13 `(b,c)` configurations including `(0,0)`, `(10,0)`, `(100,70)`. A `b = 1, c = 5` case returns `pValue = 0.21875`. Log-space accumulation via `lnGamma` keeps it stable at large counts. |
89
- | `pairedSignTest` | keep-with-CI-oracle | Exact one-sided binomial; matched `scipy.stats.binomtest(..., alternative='greater')` to `< 1e-12` on all 9 cases. `pairedSignTest([0.5,0.5,0.5], 'greater')` returns `0.125`. Ties are excluded from the denominator and still reported; `alternative` must be passed explicitly and an invalid value throws, which prevents post-hoc direction selection. |
90
- | `wilson` | keep-with-CI-oracle | Matched the closed form to `< 1e-8` on all 13 `(successes, n)` pairs including `0/1`, `10/10`, `1/1000`, `0/0`. Correctly asymmetric at the boundaries: `wilson(0, 10)` returns `[0, 0.27753280]`. |
91
- | `passAtK` | keep-with-CI-oracle | Chen et al. 2021 unbiased estimator, exhaustively verified for every `(n, c, k)` with `n = 1..6` against exact integer `math.comb`: zero mismatches. Stable at scale (`n=1000, c=3, k=100` agreed to `1.11e-16`). `passAtK(10, 3, 5) = 0.9166666667`. |
92
- | `corpusInterRaterAgreement` | keep-with-CI-oracle | The ICC(2,1) it surfaces matched a hand-derived two-way random-effects ANOVA reference to `< 1e-9` on 5 matrices, including the inverted case at `−1.959459`. This is a genuinely different and correct computation from `interRaterReliability`: it pivots to a proper items × judges matrix and delegates to `continuousAgreement`. Its fail-loud contract behaves — empty input, fewer than two judges, fewer than two common items, duplicate records, and absent dimensions all throw `ValidationError`. |
93
- | `eProcess` | keep-with-CI-oracle | Betting test-martingale. Empirical type-I over 4,000 sequences of 200 observations at `α = 0.05`: 2.40 % at the null boundary, 0.00 % in the null interior, 1.33 % on continuous uniform — all inside Ville's bound. Power at `E[x] = 0.7` is 99.775 %. The predictability invariant holds: the first update leaves wealth at exactly 1. Restart reconstruction: `state()` carries the running sums (`sumX`, `varSum`) next to wealth and n, and `eProcess({ resume })` rebuilds the process from that snapshot; a process interrupted at any n and resumed from a JSON round-trip of its state reproduces the uninterrupted wealth sequence and decision exactly (`sequential-eprocess.test.ts`, interruptions at n = 0, 1, 7, 30, 119 and at the crossing), and `sequentialPairedGate({ resume })` does the same for the gate's observe-stream. A snapshot recorded under other parameters, or one whose fields cannot all be true at once, is refused. |
94
- | `holm` | keep-with-CI-oracle | Matched `statsmodels.multipletests` to `1e-9` on a 6-value reference vector, and uses `≤` at the boundary, which is the correct rule. It also validates both `alpha` and the p range, which `bonferroni` does not. |
95
- | `zQuantile` | keep-with-CI-oracle | Acklam inverse-normal, structurally independent of `normalCdf`. This independence is why the sample-size functions were untouched by the CDF defect, and why `mcnemarPower`'s failure to invert `mcnemarRequiredN` was a valid detector of it. |
96
- | `mcnemarRequiredN` | keep-with-CI-oracle | Matched the Lachin closed form exactly on all 4 parameter sets: `234`, `77`, `155`, `Infinity`. Unchanged by the CDF fix. Round-trip against the repaired `mcnemarPower` now holds across 16 configurations: power at `requiredN` meets the target with overshoot `≤ 0.0049`, and power at `requiredN − 1` is below target in all 16. |
97
- | `ranks` | keep-with-CI-oracle | Correct average-rank-with-ties. `ranks([3,1,1,2])` returns `[4, 1.5, 1.5, 3]`, matching `scipy.stats.rankdata`. Safe against NaN input because it advances with `i = j + 1`. |
98
- | `pearsonR` | keep-with-CI-oracle | Matched `scipy.stats.pearsonr` to 8 decimals (`0.99061012` on an 8-point case). |
99
- | `spearmanR` | keep-with-CI-oracle | Matched `scipy.stats.spearmanr` to 8 decimals (`0.99402980` on the same case). |
100
- | `cliffsDelta` | keep-with-CI-oracle | Matched a hand-computed `(#gt − #lt)/(n₁n₂)` reference exactly (`0.312500`). Note the orientation is *after over before*, the opposite sign to the textbook `δ` written over `(a, b)`; the docstring states this and the parameter names `(before, after)` carry it. |
101
- | `pairedCohensDz` | keep-with-CI-oracle | Matched `mean(d)/sd(d)` exactly (`2.184070`). It is the only function in the module that handles the zero-variance degenerate case correctly, returning `null` with a docstring explaining that the standardized effect is undefined rather than an arbitrarily large finite number. Treat it as the reference behaviour the other degenerate branches should adopt. |
102
- | `weightedMean`, `partialCredit`, `weightedComposite`, `interpretCliffs`, `normalizeScores` | keep-with-CI-oracle | Arithmetic and thresholding, verified by direct evaluation (`weightedMean` of `[{1,w2},{4,w1}]` is `2.000000`; `partialCredit(3,4)` is `0.750000`). |
103
- | `welchsTTest`, `compareToBaseline` (`src/baseline.ts`) | keep-with-CI-oracle, **covered** | Correct, and previously unguarded: no test file imported either, which is the structural reason a duplicated broken CDF survived here. It is exported publicly and it gates improved / regressed / stable verdicts. Now pinned against `scipy.stats.ttest_ind(equal_var=False)` in the oracle fixture and exercised through `compareToBaseline` in a statistics test file that is no longer in the tree. |
104
-
105
- ## Public surface change
106
-
107
- `normalCdf` and `studentTCdf` were private on `origin/main` and are now exported with explicit accuracy contracts.
108
- `baseline.ts` owns the single Welch implementation, and `src/contract/analyze-runs.ts` consumes that result instead of carrying another normal-approximation copy.
109
- That is the right trade: one implementation with one accuracy contract beats three copies of which two were wrong.
110
- It also means both functions are now public API and owe callers a stated accuracy bound.
111
- `normalCdf` is documented at `7.5e-8` absolute.
112
- `studentTCdf` is exact to the incomplete beta's own precision at every finite degree of freedom and matches `scipy.stats.t.cdf` to `1e-9` across the pinned oracle cases; the residual floor near `t = 0` is float64 cancellation in `x = df/(df + t²)`, not the approximation.
113
-
114
- ## Dependency verdict
115
-
116
- **Take no statistics package as a runtime dependency.**
117
- **Use scipy as a CI oracle.**
118
- **Implement the exact small-n rank tests here, because nothing else does.**
119
-
120
- ### What we must implement ourselves
121
-
122
- Four things the survey found no correct implementation of in npm, at any package, under ties:
123
-
124
- | Needed | Library that does it correctly |
125
- | --- | --- |
126
- | Exact two-sample rank test under ties | none |
127
- | Exact paired signed-rank test under ties | none |
128
- | Cliff's delta | no package exists in npm at all |
129
- | Paired bootstrap confidence interval | none |
130
-
131
- Those four rows are the entire bottleneck, and they are the reason this decision is not close.
132
- `lib-r-math.js` comes closest — it is the only source of the exact two-sample rank-sum null distribution in JavaScript, and it reproduces every exact floor we care about (`pwilcox(0, 3, 3) = 0.100000`, `psignrank` at `n = 5` gives `0.062500`, at `n = 8` gives `0.007813`) at a cost of 2 packages and 1.2 MB.
133
- But its signature is `(q, m, n)` with no tie vector, so ties are structurally unrepresentable, exactly as in R where `wilcox.test` warns and falls back.
134
- It ships distributions, not tests, so the test wrapper, the tie conditioning, and the effect sizes remain ours regardless.
135
-
136
- The cost of writing them ourselves is small and was measured, not estimated.
137
- Enumerating every split of the observed data takes 0.1 ms at 3 versus 3 (20 splits), **3.7 ms at 10 versus 10** (184,756 splits), and 33 ms at 12 versus 12 (2,704,156 splits).
138
- Paired sign-flip enumeration is `2ⁿ`: 1,024 at `n = 10`, about 1 M at `n = 20`.
139
- The entire stated 3–10 repetition regime runs exact in under 4 ms.
140
- This is roughly 150 lines, not a research project.
141
-
142
- ### What we must not take as a runtime dependency
143
-
144
- The libraries that are correct are correct at things this module already gets right.
145
-
146
- `@stdlib/stats-padjust` matches `statsmodels.multipletests` to `1e-9` on `holm`, `bh`, and `bonferroni`, and so does this module's own `holm`, `benjaminiHochberg`, and `bonferroni` on the same frozen reference vectors.
147
- It was removed even as a development dependency because it bought zero additional coverage for 190 locked packages.
148
- `@stdlib/stats-ranks` and `jstat.rank` both do average-rank-with-ties correctly, and so does `ranks` here.
149
- `@sipemu/anofox-statistics`'s count-based exact tests are correct (`fisherExact([[3,0],[0,3]]) = 0.09999999999999992`, `binomTest(3,3,0.5) = 0.25000000000000006`, `mcnemarExact = 0.21875000000000008`), and so are `mcnemar` and `pairedSignTest` here.
150
-
151
- Meanwhile the libraries that cover the missing statistics are wrong in this regime, sometimes worse than the incumbent:
152
-
153
- - `@stdlib/stats-kruskal-test` matches `scipy.kruskal` to `1e-9` but is anti-conservative against the exact conditional truth in 7 of 8 cases: `p = 0.0495` against an exact `0.1000` at 3 versus 3, and `p = 0.4945` against an exact `1.0000` on a binary grid — off by `0.5055`. It returns a silent `NaN` on all-tied input rather than throwing. Measured cost: 286 packages, 11,997,061 bytes, 4,033 files.
154
- - `@sipemu/anofox-statistics`'s `mannWhitneyU` returns `p_value = 0` for two **identical** samples, verified on four inputs including `x = y = [0.5,0.5,0.5]`, where the truth is `p = 1.0`. Its documented `exact` flag is a silent no-op under ties: all five tied cases returned byte-identical p for `exact: true` and `exact: false`.
155
- - `@stdlib/stats-wilcoxon` has a genuine exact path but silently switches to the normal approximation when the differences contain ties, with nothing in the result to signal it — the `method` string is identical either way. Holding the statistic constant at `W = 15, n = 5`, untied input returns the exact `0.062500` and tied input returns `0.053337`, a p-value below the exact floor and therefore one that cannot exist at `n = 5`.
156
- - `simple-statistics`'s `wilcoxonRankSum` returns the wrong rank sum on 5 of 9 regime cases; every case with two or more tie groups is corrupted. The tie accumulator is reset only in the single-element branch, so a multi-element group's average spans from the previous group's start. Upstream PR #809 carries a fix and a regression test, opened 2026-07-08, still open with no maintainer response.
157
- - `mann-whitney-utest` and `@tainakanchu/mann-whitney-utest` (byte-identical source) compare a U statistic against a z-score in their significance decision, so the comparison is dimensionally meaningless. Complete separation at 3 versus 3 reports `significant = true` where no result at that size can reach `α = 0.05`.
158
- - `jstat` (last published 2022-11-21) has no Mann-Whitney, no Wilcoxon, no Kruskal, no p-adjust. `science.js` (last published 2015-08-20) has zero hypothesis tests.
159
-
160
- Dependency weight is a real cost, not a stylistic one.
161
- This package's entire runtime dependency set is 7 packages.
162
- Adding 190–286 for statistics we already compute correctly would be inherited by every consumer of `@tangle-network/agent-eval`.
163
-
164
- ### What scipy is for
165
-
166
- scipy is the CI oracle and never ships.
167
-
168
- Pin scipy-generated golden values as a JSON fixture under `tests/`, regenerated by a checked-in script, and assert every **keep-with-CI-oracle** statistic against it.
169
- That is `scripts/generate-statistics-oracle.py` → `tests/fixtures/statistics-oracle.json`, asserted by `tests/statistics-oracle.test.ts`: 154 cases across 22 statistics, each carrying its own tolerance (`7.5e-8` where A&S bounds it, `1e-8` where Acklam's inverse normal does, `1e-12` elsewhere).
170
- scipy 1.13.1 was the reference for every number in this document and it catches exactly the class of defect found here: a hand-rolled approximation that is plausible on inspection and wrong by `3.7e-2`.
171
- `lib-r-math.js` is a **devDependency only**, cross-checking the untied exact null distributions in `tests/statistics-library-crosscheck.test.ts`.
172
- The three correction functions are independently covered by the statsmodels-generated fixture.
173
- Neither reference implementation is imported by `src/`.
174
- `fast-check` is already a devDependency, so the invariants that no fixture can express — a p-value never below the attainable floor, monotonicity of p in the statistic, an exact and an asymptotic path agreeing as `n` grows — belong there.
175
-
176
- ## Exact versus asymptotic policy
177
-
178
- The governing fact is combinatorial, not numerical.
179
- At 3 versus 3 there are only 20 possible splits, so the attainable two-sided p-grid is `{0.1, 0.2, …}` and `0.05` is unreachable.
180
- `[1,2,3]` versus `[4,5,6]` and `[0,0,0]` versus `[1,1,1]` both have an exact `p = 0.1000`, while scipy's tie-corrected asymptotic gives `0.080856` and `0.046854` — **adding the tie correction makes the answer worse.**
181
- No better approximation reaches the right answer here; only an exact test does.
182
-
183
- ### Switchover thresholds
184
-
185
- | Test | Exact by enumeration | Seeded Monte Carlo permutation | Asymptotic |
186
- | --- | --- | --- | --- |
187
- | Two-sample rank (`mannWhitneyU`) | dynamic program up to 8,192 cells and 250,000 transitions; includes 12 v 12 and 1 v 24 | above that, default 100,000 permutations | never the default; only on explicit request above the exact work limits |
188
- | Paired signed-rank (`wilcoxonSignedRank`) | `n ≤ 20` — `2²⁰ = 1,048,576` sign flips | above that, default 100,000 sign flips | same |
189
- | Paired sign test (`pairedSignTest`) | always exact — binomial, already correct | — | never |
190
- | McNemar (`mcnemar`) | always exact — binomial, already correct | — | never |
191
- | Paired mean difference (`pairedTTest`) | — | — | valid from `n ≥ 3`, now that the `incompleteBeta` symmetry branch is in place |
192
- | Bootstrap interval (`confidenceInterval`, `pairedBootstrap`) | — | — | **not a valid gate below `n = 20`** |
193
- | One-sample mean interval (`meanConfidenceInterval`, `studentTCritical`) | — | — | exact Student-t quantile at `df = n − 1`; null bounds below `n = 2`; assumes roughly normal means |
194
-
195
- The bootstrap row is the strongest recommendation here and the one most likely to be resisted.
196
- Measured `P(low > 0)` under a true null against a nominal 2.5 % never reaches nominal in the tested range: 13.53 % at `n = 3`, 3.52 % at `n = 10`, 3.10 % at `n = 20` for the median statistic, and 13.85 % / 4.90 % / 3.80 % for the mean.
197
- Below `n = 20` the bootstrap interval should be reported as descriptive spread and must not be the leg a promotion turns on; the exact sign test or exact signed-rank should carry the decision instead.
198
-
199
- ### What to do when the caller asks for a misleading number
200
-
201
- Refuse, loudly, in the package's own idiom.
202
-
203
- Every rank test takes `method: 'exact' | 'asymptotic' | 'auto'`, defaulting to `'auto'`.
204
- `'auto'` selects exact whenever the design is inside the enumeration threshold, Monte Carlo permutation above it, and asymptotic never.
205
- An explicit `method: 'asymptotic'` inside the exact-feasible range **throws a `ValidationError`** naming the smallest attainable p at that design and the exact work limits.
206
- An explicit `method: 'exact'` ABOVE the threshold throws too, rather than enumerating a distribution whose cost is unbounded.
207
-
208
- This is a refusal, not a warning, and the reason is the package's own doctrine.
209
- A warning on `stderr` does not reach the JSON a gate reads, does not reach a CI log a human skims, and does not survive serialization into a run record.
210
- An anti-conservative p that a gate silently believes is exactly the class of silent fallback that *no fallbacks, fail loud* exists to prevent, and the `wilcoxonSignedRank` `n < 6` branch was the proof: it returned `p = 1` for real effects from v0.1.0 to 0.133.0 and nothing downstream could tell.
211
-
212
- The cost of the refusal is real and is stated here rather than discovered later: an explicit-asymptotic caller at 3 v 3 who was reading `0.0495` now gets an exception, and the same design read through the default now reports `0.1000`.
213
- A historical verdict that turned on the difference was never valid.
214
-
215
- Two supporting requirements make the refusal usable rather than merely obstructive.
216
-
217
- Every rank-test result carries `method` and `pFloor` — the method actually used and the smallest attainable p at that design — so a downstream gate can see the discreteness rather than infer it.
218
- This is the field `@stdlib/stats-wilcoxon` omits, which is why its silent exact-to-approximate switch is undetectable by a caller.
219
- `pairedBootstrap` carries the same signal as `gateEligible`.
220
-
221
- Still to do: every gate that consumes a rank test should state its minimum n at construction and fail its own precondition check when the data is smaller, rather than accepting whatever the test returns.
222
- A gate that cannot reach its alpha at the n it was handed should report *underpowered*, which is a true statement about the experiment, not *not significant*, which is a false statement about the effect.
223
- `pFloor` and `gateEligible` make that check expressible.
224
- Promotion paths now use `pairedDeltaTest`: an exact one-sided sign test carries decisions from 6 through 19 pairs, and the bootstrap interval carries them from 20 onward.
225
-
226
- ## Consumer notice
227
-
228
- Every published version from **0.1.0 (2026-04-20)** through **0.133.0 (2026-07-27)** reported p-values that are too small from the functions listed below.
229
- The defect entered at the initial commit (`7d5032b`, in the then single-file statistics module) and was present in every release since.
230
-
231
- The normal CDF itself was corrected in 0.133.1; every other row below was still open at that release.
232
-
233
- ### Which functions, and by how much
234
-
235
- | Function | Path to the defect | Direction | Measured |
236
- | --- | --- | --- | --- |
237
- | `mannWhitneyU` | `normalCdf`, then the asymptotic path itself | p too small | `[1,2,3]` v `[4,5,6]`: reported `0.03769147`. Repairing the CDF alone gives `0.04953448` (1.31×); the shipped exact answer is `0.10000000` (2.65×), and `0.05` is unreachable at 3 v 3 in the first place. `[1..5]` v `[10..14]`: reported `0.00671001`, shipped exact `0.00793651` (1.18×). |
238
- | `wilcoxonSignedRank` | `normalCdf`, then the asymptotic path itself | p too small, or fabricated as 1 | `n = 8` constant shift: reported `0.00873623`, CDF-repaired `0.01171872`, shipped exact `0.00781250`. Below six non-zero differences the old code returned `p = 1` regardless of the data: a clean 5-of-5 shift reported `1.0` where the exact answer is `0.0625`. |
239
- | `pairedTTest` at `df > 100` | deleted `studentTCdf` normal shortcut | p too small | A `df = 298` case: reported `0.010887`, correct Student-t `0.015150`. |
240
- | `welchsTTest`, `compareToBaseline` | duplicate `normalCdf` in `baseline.ts` | p too small | Same `df = 298` case, same shift. This drives the improved / regressed / stable verdict at `baseline.ts:97`. |
241
- | `mcnemarPower` | `normalCdf` | power **overstated** above 0.5, understated below | `{p10: 0.2, p01: 0.1, nPairs: 200}`: reported `0.773437`, correct `0.736522`. At `n = 80`: `0.9368` against `0.9197`. At `n = 20`: `0.3294` against a correct `0.3627`. |
242
- | `pairedTTest` at `df ≤ 100`, small `\|t\|` | `incompleteBeta` — **fixed** | p wildly too small near `t = 0` | `df = 7, t = 0.001`: reported `0.15130173`, correct `0.99923002`. `df = 100, t = 1e-6`: reported `0.00004411`, correct ≈ `1.0`. |
243
- | `interRaterReliability` | grouping loop — **fixed** | sign inverted | Identical judges return `−0.500000`; maximally disagreeing judges return `+1.000000`. |
244
-
245
- `requiredSampleSize`, `requiredPairedSampleSize`, `pairedMde`, `mcnemarRequiredN`, `mcnemar`, `pairedSignTest`, `wilson`, `passAtK`, `corpusInterRaterAgreement`, `eProcess`, `holm`, `ranks`, `pearsonR`, `spearmanR`, `cliffsDelta`, and `pairedCohensDz` are **unaffected** — verified numerically identical before and after (`requiredSampleSize({effect: 0.5}) = 63`, `mcnemarRequiredN({p10: 0.2, p01: 0.1}) = 234` both ways).
246
-
247
- ### Sizing the paired promotion decision (0.186.0)
248
-
249
- `decidePairedPromotion` is several channels behind one verdict, so no single closed form here sizes it: `mcnemarRequiredN` is Lachin's normal approximation over discordant pairs (0.65 exact power at its 14-pair floor for p 0.85, not 0.8), `requiredPairedSampleSize` and `pairedMde` size a paired t the decision never runs, and none of them sees the sufficiency floor, the zero-width refusal, the McNemar veto or a second sealed call on the same episodes.
250
- `pairedPromotionPower` and `requiredPairsForPairedPromotion` size the decision by running it: seeded draws from a registered joint law (cell first, delta given the cell), every configured call run exactly as sealed, the promotion rate counted, with the Monte Carlo error and the per-channel and per-method rates reported.
251
- The closed forms stay exported as the floors they are; a preregistration sizes on the simulation and records the seed.
252
-
253
- ### How to re-check a decision you already made
254
-
255
- The defect is monotone in `|z|`, so the affected band is exact and narrow.
256
-
257
- The broken code crossed `p = 0.05` at `|z| = 1.843031` instead of the correct `1.959964`, and at that true critical value it reported `p = 0.038053`.
258
- Therefore:
259
-
260
- - **Any recorded p in `[0.038053, 0.050000)` from an affected function crossed a 5 % gate that it should not have crossed.**
261
- - At `α = 0.01` the band is `[0.007443, 0.010000)`.
262
- - At `α = 0.10` the band is `[0.077398, 0.100000)`.
263
- - A recorded p below `0.038053` was significant at 5 % either way; a recorded p at or above `0.05` was not significant either way. Neither needs re-checking.
264
-
265
- The practical size of the error: the module's real type-I error rate was **6.53 % at a nominal 5 %** and **1.34 % at a nominal 1 %**, confirmed by 20,000 null replicates at `n = 8` per group where `mannWhitneyU` rejected at 6.47 % as shipped against 4.86 % with the same U and z fed a correct CDF.
266
-
267
- Three further cautions for anyone auditing an old verdict.
268
-
269
- A promotion that turned on a `pairedBootstrap` `low > 0` check at fewer than 10 pairs was never valid at the stated confidence, independent of this defect — the measured false-positive rate is 13.53 % at `n = 3` against a nominal 2.5 %.
270
-
271
- A `wilcoxonSignedRank` leg that reported `p = 1` on fewer than six non-zero differences measured nothing.
272
- It is not evidence of no effect, and re-running it on this release returns a real exact p.
273
- Note that exact ties are dropped before ranking, so ten pairs with five tied deltas also fell into that branch.
274
-
275
- Any bootstrap interval recorded from a release at or before 0.133.0 through `analyze-runs.ts` is not reproducible: that call site passed no seed and `makeRng` fell back to `Math.random`.
276
- Re-running it against the old release will not give the same interval. From this release the seed is derived from the data when the caller supplies none, so it is reproducible either way — but an interval recorded earlier cannot be reconstructed.
277
-
278
- Mirror this section into `CHANGELOG.md` at the release that carries the fix, with the affected version range and the re-check bands, so a consumer who never reads this file still gets the notice.
279
-
280
- ## Random-source audit
281
-
282
- Every `Math.random` reference in `src/`, what it feeds, and whether a reported number depends on it.
283
- The rule this audit enforces: **no result-bearing draw may come from an unseeded source.**
284
- A result-bearing draw is one whose value reaches a reported statistic, an interval, a gate verdict, or an allocation a caller acts on.
285
-
286
- The canonical seeded generator is `mulberry32` in `src/statistics/random.ts`, reached through `makeRng(seed, ...series)` in `src/statistics/internal.ts`.
287
- `makeRng` derives the seed from the observations when the caller supplies none, so an unseeded call is still reproducible: same input, same interval.
288
- That is why the option stays optional — a required seed would push the caller into inventing one, and an invented seed is not more honest than a derived one.
289
-
290
- ### Result-bearing draws — all seeded
291
-
292
- | Site | Formula | Consumer | Seed source |
293
- | --- | --- | --- | --- |
294
- | `src/statistics/internal.ts` `makeRng` | mulberry32 | every resampling path in `src/statistics` | caller seed, else FNV-1a over the IEEE-754 bytes of the observations |
295
- | `src/statistics/descriptive.ts` `confidenceInterval` | percentile bootstrap | reported interval | `makeRng(opts.seed, scores)` |
296
- | `src/statistics/paired-tests.ts` `pairedBootstrap` | paired percentile bootstrap | promotion intervals | `makeRng(opts.seed, deltas)` |
297
- | `src/statistics/rank-tests.ts` `mannWhitneyU`, `wilcoxonSignedRank` | permutation null | exact rank-test p | `makeRng(opts.seed, …)`, order-independent for the symmetric two-sample case |
298
- | `promotion-gate.ts` | bootstrap over the two arms | ship / hold verdict | `options.seed`, else `hashSeed(baseline, candidate)` |
299
- | `held-out-gate.ts` | delegates to `pairedBootstrap` | held-out gate verdict | caller seed, else the paired observations |
300
- | `summary-report.ts` `bayesianBootstrapMeanSamples` | Dirichlet-weight bootstrap | reported posterior and gain histogram | `makeRng(seed, deltas)` |
301
- | `src/meta-eval/rubric-predictive-validity.ts` `bootstrapCi` | percentile bootstrap of Pearson r | rubric verdict (`load_bearing` / `informative`) | `makeRng(input.seed, xs, ys)` |
302
- | `src/meta-eval/correlation-study.ts` `bootstrapPearsonCi` | percentile bootstrap of Pearson r | reported correlation CI | `makeRng(options.seed, xs, ys)` |
303
- | `src/campaign/search-policy.ts` `aide` | Thompson draw over node posteriors, the debug coin, the debug and uniform draws | the parent an expansion extends (spends budget, claims nothing) | `mulberry32(seed ^ imul(completed expansions + 1, 0x9e3779b1) ^ salt)`, a pure function of the ledger |
304
- | `src/rl/adaptation-eval.ts` `compareAdaptationCurves` | bootstrap of per-k mean deltas | per-k CI and AUC delta | `makeRng(opts.seed, a-means, b-means)` |
305
- | `src/campaign/gates/sequential.ts` | seeded Fisher-Yates over paired deltas | exchangeability guard before the e-process | `shuffleSeed`, default 1337, data-independent by construction |
306
-
307
- ### Draws that reach no result
308
-
309
- These stay on `Math.random`. Each produces an identifier or a retry delay; no reported number reads them, and seeding them would make two concurrent runs collide on the same identifier.
310
-
311
- | Site | Formula | What it feeds |
312
- | --- | --- | --- |
313
- | `llm-client.ts:1050` | `Date.now().toString(36)` + random suffix | per-call identifier in the ledger tag |
314
- | `trace/emitter.ts:350` | same | span identifier |
315
- | `builder-eval/builder-session.ts:255` | same | builder session identifier |
316
- | `campaign/worktree/index.ts:697` | same, 4-character suffix | worktree branch name |
317
- | `matrix/runner.ts:67` | 8 hex characters | matrix run identifier |
318
- | `adapters/http.ts:152,162` | `2^attempt * 200 + random * 200` | retry backoff jitter |
319
- | `hosted/client.ts:128,138` | same | retry backoff jitter |
320
-
321
- ### What changed in this pass
322
-
323
- Five byte-identical private copies of mulberry32 existed alongside the canonical one, in `src/meta-eval/rubric-predictive-validity.ts`, `src/rl/active-curriculum.ts`, `src/rl/adaptation-eval.ts`, `summary-report.ts`, and `promotion-gate.ts`.
324
- Four of them fell back to `Math.random` when the caller passed no seed, so four reported statistics were silently non-reproducible.
325
- `src/meta-eval/correlation-study.ts` called `Math.random` directly inside its bootstrap and had no seed option at all.
326
- All six now route through `makeRng`, and `correlationStudy` gained the `seed` option its sibling already had.
327
- `Math.random` references in `src/` fell from 19 to 10, and none of the remaining ten is result-bearing.
328
-
329
- ## Test and documentation classification
330
-
331
- The audit in #411 counted assertions that prove nothing, module mocks, and conditional skips. The disposition:
332
-
333
- **No-throw assertions.** 66 `expect(...).not.toThrow()` sites across 36 files. They split in two:
334
-
335
- - *A guard's accept case.* `assertLlmRoute`, `assertNoHiddenLeak`, `assertCapabilityHeadroom`, `assertMultishotShotResult`, `assertMatchedMethodLimits`, `assertGateReport`, `assertAnalystBenchmarkObservation`, `assertDeterministicOracle`, `assertDenominatorIntact`, and `assertJsonValue` return nothing. For these the no-throw IS the assertion: it pins the false-positive boundary of a fail-closed gate, and a gate that refuses a legal input is as broken as one that admits an illegal one. Each sits next to a `toThrow` sibling that proves the refusal. Kept.
336
- - *A value-returning call.* `validateRunRecord` and friends return the validated value, so the no-throw discarded the only thing worth checking. Converted to assert the returned value.
337
-
338
- **Module mocks.** 5 sites, each now carrying a one-line justification in its file header: four wrap a `node:` builtin to reproduce a race or an absent native binding that a real filesystem cannot be timed into (`src/trace/store.test.ts`, `src/analyst/benchmark-verification-artifacts.test.ts`, `src/analyst/benchmark-command-public-data.test.ts`, the opencode-sqlite lazy-reader test (no longer in the tree)); one replaces the provider call in the wire handler test, which is about request validation and error mapping (`tests/wire/handlers.test.ts`).
339
-
340
- **Conditional skips.** 6 sites, all environment-gated, none silent:
341
-
342
- | Site | Gate | Why |
343
- | --- | --- | --- |
344
- | `command-runner.test.ts:148` | `process.platform === 'win32' \|\| getuid() === 0` | POSIX permission bits do not constrain root or Windows |
345
- | `ledger-core/trusted-head.test.ts:473` | same, as `runIf` | the same permission assumption |
346
- | `analyst/benchmark-command-public-data.test.ts:72` | `process.platform === 'win32'` | POSIX path semantics |
347
- | `analyst/kinds/skill-usage.test.ts:198` | `SKILL_USAGE_REAL` | needs a real skill corpus on disk |
348
- | `tests/campaign/gepa-official-integration.test.ts:22` | `AGENT_EVAL_TEST_PYTHON` | needs the official GEPA python environment; CI sets it |
349
- | `tests/campaign/skillopt-official-integration.test.ts:22` | `AGENT_EVAL_TEST_PYTHON` | needs the official SkillOpt python environment; CI sets it |
350
-
351
- The two python-gated files run in CI, which installs the environment; the platform gates never skip on CI's Linux runner.
352
-
353
- ## Runnable-fence sweep
354
-
355
- A one-time pass, not a build gate. The 73 Markdown files under `docs/`, `examples/`, the two READMEs, `CLAUDE.md`, and the maintainer skill hold 66 fences marked `ts`.
356
- Each was extracted to a file and compiled with the examples compiler options, with `@tangle-network/agent-eval` and every subpath mapped to `src/`, so the check reads the surface this repository ships rather than the version installed in `node_modules`.
357
- Reading the installed copy is the trap: it reports every export added since the last release as missing, which is how a fence check produces false alarms and gets switched off.
358
-
359
- Result:
360
-
361
- - **3 fences were not TypeScript** and are now marked `text`: an object fragment in `campaign-proposers.md`, a call elided as `{ ... }` in `feedback-trajectories.md`, and a method sketch in `examples/benchmarks/README.md`.
362
- - **2 fences imported a path that cannot resolve** — `@tangle-network/agent-eval/../src/trace-repair` in `trace-repair-admission.md` and `trace-repair-continuation.md` — now `@tangle-network/agent-eval/trace-repair`.
363
- - **0 of the remaining 63 name an export that does not exist.** Every symbol a fence imports is on the current surface.
364
- - The remaining compiler complaints are inherent to an excerpt: a name declared in the prose around it, a shorthand property, top-level `await` outside a module. Three fences import a deliberate placeholder (`your-text-optimizer`, `./my-engine`).
365
-
366
- There is no fence checker in `scripts/` and no CI job. Compiling illustrative snippets fails builds for prose edits, and the failure mode it catches — a doc naming a symbol that no longer exists — is what the export census and review already cover. Repeat this pass by hand after a large surface change.
3
+ `src/statistics/` owns estimators and intervals. Product callers own their decision rules.
4
+ The public API is `@tangle-network/agent-eval/statistics`; internal paths are unsupported.
5
+
6
+ ## Evidence and dependencies
7
+
8
+ Keep statistics in-house with independent numerical oracles, rather than adding runtime dependencies.
9
+ `scripts/generate-statistics-oracle.py` generates `tests/fixtures/statistics-oracle.json`;
10
+ `tests/statistics-oracle.test.ts` checks the scipy and statsmodels reference values.
11
+ `tests/statistics-library-crosscheck.test.ts` checks untied exact null distributions against R through the development-only `lib-r-math.js`.
12
+ Historical audits and affected-version notices live in `CHANGELOG.md`.
13
+
14
+ ## Small samples
15
+
16
+ Exact rank tests and sign tests own decisions where their attainable p-value permits one.
17
+ `mannWhitneyU` and `wilcoxonSignedRank` default to exact enumeration within their work limits,
18
+ then seeded Monte Carlo permutation. Explicit asymptotic requests within the exact-feasible
19
+ regime are refused. Read `method` and `pFloor` from the result; source owns the limits.
20
+
21
+ A percentile bootstrap below twenty independent clusters is descriptive spread, not a
22
+ promotion gate. `pairedBootstrap.gateEligible` exposes that restriction.
23
+ `pairedDeltaTest` uses the exact sign test for small samples and the bootstrap afterward.
24
+ `pairedPromotionPower` sizes the complete sealed decision by simulation; closed-form power
25
+ functions cannot account for its sufficiency gates, vetoes or repeated reads.
26
+
27
+ ## Clustered observations and time drift
28
+
29
+ `clusterBootstrap` redraws whole clusters with replacement, preserving each stratum's cluster
30
+ count. A cluster spanning strata is invalid. Its statistic can be a mean, percentile, share
31
+ or ratio; degenerate resamples are omitted and no finite draws leaves NaN bounds. `clusterBootstrapMean` delegates to this engine.
32
+ The mean wrapper keeps its data-derived seed; its bounds now use the shared interpolated
33
+ quantiles, so previously recorded endpoints may differ. One cluster carries no between-cluster
34
+ information even if its interval collapses.
35
+
36
+ `placeboBand` takes same-cell history, a sealed cluster count and the exact credit estimator,
37
+ including winsorization and failure scoring. Hourly windows exclude sealed steps; fewer than
38
+ 72 hours or 39 eligible boundaries yields `uncalibrated` with null bounds. It returns K, n,
39
+ boundaries and deltas; bounds invert as `[delta - q975, delta - q025]`.
40
+ The [recorded contract](../../tests/cluster-recorded.integration.test.ts) labels constructed replay; callers validate cadence, cell, coverage, clusters and estimator.
41
+
42
+ `independentRiskDifference(beforeSuccesses, beforeN, afterSuccesses, afterN, confidence)`
43
+ combines the existing Wilson limits with Newcombe's method 10 (no continuity correction).
44
+ It returns after-minus-before `delta`, `lower`, `upper`, confidence and both counted arms;
45
+ either empty arm gives a null delta and full difference support. This interval assumes
46
+ independent binomial observations; repeated attempts do not become independent trials.
47
+ Reference: [Newcombe (1998), method 10 and Table II](https://doi.org/10.1002/(SICI)1097-0258(19980430)17:8%3C873::AID-SIM779%3E3.0.CO;2-I).
package/docs/design.md CHANGED
@@ -65,7 +65,7 @@ They are not adoption reference:
65
65
 
66
66
  - [`building-doctrine.md`](./building-doctrine.md): conventions our agents follow when consuming this package (reachable model defaults, probe-before-debug, experiment integrity checklist)
67
67
  - [`design/loop-taxonomy.md`](./design/loop-taxonomy.md): the internal vocabulary for execution drivers, workers, measurements, and proposers
68
- - [`design/statistics-decisions.md`](./design/statistics-decisions.md): per-statistic trust status, the no-runtime-dependency verdict, and the exact-versus-asymptotic policy at 3–10 repetitions
68
+ - [`design/statistics-decisions.md`](./design/statistics-decisions.md): statistics ownership, independent oracles, cluster intervals and small-sample policy
69
69
  - [`design/mlbenchmarks-book-review.md`](./design/mlbenchmarks-book-review.md): book review, source-backed gaps, and proposals for automated evaluation engineering at a pinned repository revision
70
70
  - [`research-report-methodology.md`](./research-report-methodology.md): the evidence standard our own research reports are held to
71
71
  - [`.claude/skills/agent-eval/SKILL.md`](../.claude/skills/agent-eval/SKILL.md): directives for LLM agents writing integration code, encoding bug classes we have already shipped and fixed once
@@ -75,7 +75,7 @@ Each posterior draw assigns the observed paired deltas Dirichlet(1, ..., 1) weig
75
75
  The posterior summaries apply to the **mean** delta.
76
76
  The separate frequentist bootstrap CI applies to the **median** delta because the median is more robust to heavy-tailed agent scores.
77
77
  It is descriptive spread below `BOOTSTRAP_GATE_MIN_N = 20` pairs, which the result reports as `gateEligible: false`; the exact signed-rank or sign test carries the decision there.
78
- See [`design/statistics-decisions.md`](./design/statistics-decisions.md) for the measured false-positive rates and the exact-versus-asymptotic policy.
78
+ See [`design/statistics-decisions.md`](./design/statistics-decisions.md) for interval and small-sample policy; historical calibration results are in [`CHANGELOG.md`](../CHANGELOG.md).
79
79
 
80
80
  ## MDE
81
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
5
5
  "homepage": "https://github.com/tangle-network/agent-eval#readme",
6
6
  "repository": {
@@ -59,6 +59,11 @@
59
59
  "import": "./dist/traces.js",
60
60
  "default": "./dist/traces.js"
61
61
  },
62
+ "./statistics": {
63
+ "types": "./dist/statistics.d.ts",
64
+ "import": "./dist/statistics.js",
65
+ "default": "./dist/statistics.js"
66
+ },
62
67
  "./trace-attributes": {
63
68
  "types": "./dist/trace-attributes.d.ts",
64
69
  "import": "./dist/trace-attributes.js",