reeval 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -39
- package/THIRD_PARTY_NOTICES.md +75 -106
- package/bin/mcp-timer.cjs +224 -0
- package/dist/{adapter-DWlpOtRn.js → adapter-DigeVFWC.js} +3 -3
- package/dist/{adapter-DWlpOtRn.js.map → adapter-DigeVFWC.js.map} +1 -1
- package/dist/{agent-CaS_2Sce.js → agent-CatUEo5h.js} +21 -6
- package/dist/{agent-CaS_2Sce.js.map → agent-CatUEo5h.js.map} +1 -1
- package/dist/{assemble-B9Alds-z.js → assemble-CF063ms6.js} +19 -17
- package/dist/assemble-CF063ms6.js.map +1 -0
- package/dist/assemble-CJoD_Rzr.js +1 -0
- package/dist/bridge/index.mjs +15 -7
- package/dist/{capture-CNb9SuU5.js → capture-BiD7lVaM.js} +26 -18
- package/dist/{capture-CNb9SuU5.js.map → capture-BiD7lVaM.js.map} +1 -1
- package/dist/capture-Dp3q7K7Y.js +1 -0
- package/dist/{cli-D303Vj5N.js → cli-B5zQCR2r.js} +266 -547
- package/dist/cli-B5zQCR2r.js.map +1 -0
- package/dist/{cli-spec-BPZbQG2p.js → cli-spec-BXIBUlaK.js} +46 -18
- package/dist/cli-spec-BXIBUlaK.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{command-BT9a37ZR.js → command-BMHXHqMT.js} +17 -14
- package/dist/command-BMHXHqMT.js.map +1 -0
- package/dist/command-C94CQNh8.js +91 -0
- package/dist/command-C94CQNh8.js.map +1 -0
- package/dist/command-CVtgyqlM.js +335 -0
- package/dist/command-CVtgyqlM.js.map +1 -0
- package/dist/{command-BLfT7vnQ.js → command-CaZgCTIw.js} +13 -17
- package/dist/command-CaZgCTIw.js.map +1 -0
- package/dist/{command-CMJtHKRa.js → command-DTgBZR0k.js} +6 -6
- package/dist/{command-CMJtHKRa.js.map → command-DTgBZR0k.js.map} +1 -1
- package/dist/{commands-1p-bIWhN.js → commands-BXzIDB3X.js} +1 -1
- package/dist/commands-CjpGBUWr.js +666 -0
- package/dist/commands-CjpGBUWr.js.map +1 -0
- package/dist/{config-DEgnUiU-.js → config-Dv5MG7Rp.js} +31 -34
- package/dist/config-Dv5MG7Rp.js.map +1 -0
- package/dist/{context-C1mxXh80.js → context-Dgn48BqT.js} +13 -2
- package/dist/context-Dgn48BqT.js.map +1 -0
- package/dist/contrasts-CcoMLC_y.js +101 -0
- package/dist/contrasts-CcoMLC_y.js.map +1 -0
- package/dist/db-BeqRiWLl.js +602 -0
- package/dist/db-BeqRiWLl.js.map +1 -0
- package/dist/db-CJDRVQal.js +2 -0
- package/dist/{dist-B8hJImYt.js → dist-CMjp3qqF.js} +81 -14
- package/dist/dist-CMjp3qqF.js.map +1 -0
- package/dist/{docker-sZrNrr82.js → docker-BbTlMmYi.js} +2 -2
- package/dist/{docker-sZrNrr82.js.map → docker-BbTlMmYi.js.map} +1 -1
- package/dist/{docker-7b_2SaH7.js → docker-CCXdbREk.js} +20 -4
- package/dist/{docker-7b_2SaH7.js.map → docker-CCXdbREk.js.map} +1 -1
- package/dist/{docker-yauK-9X8.js → docker-DC69dKxB.js} +1 -1
- package/dist/{doctor-BYNok4UO.js → doctor-CqttrZcs.js} +56 -22
- package/dist/doctor-CqttrZcs.js.map +1 -0
- package/dist/{egress-C4PZTSSe.js → egress-BfK_O9ce.js} +435 -31
- package/dist/egress-BfK_O9ce.js.map +1 -0
- package/dist/{environment-DNjRk0nU.js → environment-BBz1Yes9.js} +225 -12
- package/dist/environment-BBz1Yes9.js.map +1 -0
- package/dist/{evaluate-D4zeRGBF.js → evaluate-CXQ1o-pd.js} +539 -562
- package/dist/evaluate-CXQ1o-pd.js.map +1 -0
- package/dist/{evaluate-C1UVL21l.d.ts → evaluate-yjZKUexW.d.ts} +2 -2
- package/dist/facts-DtENlf9s.js +898 -0
- package/dist/facts-DtENlf9s.js.map +1 -0
- package/dist/{files-BXUdY8tr.js → files-B-eUpCZ1.js} +30 -7
- package/dist/files-B-eUpCZ1.js.map +1 -0
- package/dist/files-BsQbYz2i.js +65 -0
- package/dist/files-BsQbYz2i.js.map +1 -0
- package/dist/{files-DQmHE7pb.js → files-COHQLEHE.js} +6 -17
- package/dist/files-COHQLEHE.js.map +1 -0
- package/dist/files-DqCBW7i8.js +1 -0
- package/dist/files-Ha-jsQXf.js +1 -0
- package/dist/generate-Cs2TMH_k.js +1647 -0
- package/dist/generate-Cs2TMH_k.js.map +1 -0
- package/dist/{git-DfG-YJ1u.js → git-DhykwgKF.js} +2 -2
- package/dist/{git-DfG-YJ1u.js.map → git-DhykwgKF.js.map} +1 -1
- package/dist/{harness-B8QC9u0O.js → harness-BAjw1fcr.js} +3 -3
- package/dist/{harness-B8QC9u0O.js.map → harness-BAjw1fcr.js.map} +1 -1
- package/dist/harness.d.ts +1 -1
- package/dist/harness.js +1 -1
- package/dist/{header-hK-Z_Po9.js → header-kSmWatcO.js} +10 -3
- package/dist/{header-hK-Z_Po9.js.map → header-kSmWatcO.js.map} +1 -1
- package/dist/{images-0On0cztu.js → images-BmJRuTpm.js} +61 -13
- package/dist/images-BmJRuTpm.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/init-Bwc9byQr.js +543 -0
- package/dist/init-Bwc9byQr.js.map +1 -0
- package/dist/init-D7Y1ny1z.js +1 -0
- package/dist/insights-B2Xmhz9o.js +64 -0
- package/dist/insights-B2Xmhz9o.js.map +1 -0
- package/dist/{invocations-DbGfBAA_.js → invocations-CQTbP092.js} +3 -3
- package/dist/{invocations-DbGfBAA_.js.map → invocations-CQTbP092.js.map} +1 -1
- package/dist/{judge-DW_GDOmB.js → judge-Dt6KW6_O.js} +5 -3
- package/dist/judge-Dt6KW6_O.js.map +1 -0
- package/dist/judge.d.ts +7 -6
- package/dist/judge.js +8 -7
- package/dist/judge.js.map +1 -1
- package/dist/{live-jlfszAbb.js → live-DYd2QEyR.js} +2 -2
- package/dist/{live-jlfszAbb.js.map → live-DYd2QEyR.js.map} +1 -1
- package/dist/{model-h_cDtcRr.js → model-B-mel-Gv.js} +46 -20
- package/dist/model-B-mel-Gv.js.map +1 -0
- package/dist/model-BGKYVpoj.js +1 -0
- package/dist/{model-DaVCY3rb.js → model-C7tSwT7f.js} +2 -2
- package/dist/{model-DaVCY3rb.js.map → model-C7tSwT7f.js.map} +1 -1
- package/dist/{model-oqcDplJG.js → model-CWlAqmbU.js} +150 -46
- package/dist/model-CWlAqmbU.js.map +1 -0
- package/dist/model-DHY-dfoO.js +1 -0
- package/dist/{node-B3ydHo6t.js → node-D0mtjRAO.js} +3 -3
- package/dist/{node-B3ydHo6t.js.map → node-D0mtjRAO.js.map} +1 -1
- package/dist/{options-Bh2W63dK.js → options-CPUW97_7.js} +9 -12
- package/dist/options-CPUW97_7.js.map +1 -0
- package/dist/{pairs-CnInRu-G.js → pairs-DP6XOJby.js} +2 -2
- package/dist/{pairs-CnInRu-G.js.map → pairs-DP6XOJby.js.map} +1 -1
- package/dist/{patch-Zac5aIRM.js → patch-D4oluWzH.js} +2 -2
- package/dist/{patch-Zac5aIRM.js.map → patch-D4oluWzH.js.map} +1 -1
- package/dist/{plan-C9Cy27Ay.js → plan-OkwUcJQ9.js} +31 -12
- package/dist/plan-OkwUcJQ9.js.map +1 -0
- package/dist/plugins-CUWMnSNb.js +654 -0
- package/dist/plugins-CUWMnSNb.js.map +1 -0
- package/dist/{preflight-CPWpQeBI.js → preflight-B6pY89bF.js} +1 -1
- package/dist/{preflight-D7iFPKeK.js → preflight-Bjm7_ylE.js} +14 -10
- package/dist/preflight-Bjm7_ylE.js.map +1 -0
- package/dist/{prepare-doGxaAGR.js → prepare-CQmK3SBn.js} +71 -24
- package/dist/prepare-CQmK3SBn.js.map +1 -0
- package/dist/prepare-DbdgtZKe.js +2 -0
- package/dist/{provenance-DmE9uPVt.js → provenance-B3Y17oCC.js} +2 -2
- package/dist/{provenance-DmE9uPVt.js.map → provenance-B3Y17oCC.js.map} +1 -1
- package/dist/providers-CXN2RFz-.js +1 -0
- package/dist/{providers-tAOD1NQN.js → providers-eoniqK6w.js} +24 -10
- package/dist/providers-eoniqK6w.js.map +1 -0
- package/dist/query-CLRZmWBT.js +139 -0
- package/dist/query-CLRZmWBT.js.map +1 -0
- package/dist/query-CmBDYt0_.js +1 -0
- package/dist/{questions-DjAwMxZ3.js → questions-1XOulevV.js} +6 -6
- package/dist/{questions-DjAwMxZ3.js.map → questions-1XOulevV.js.map} +1 -1
- package/dist/{record-BKmMvZ6O.js → record-kk9M1hj-.js} +8 -4
- package/dist/record-kk9M1hj-.js.map +1 -0
- package/dist/remarks-1521zwfW.js +38 -0
- package/dist/remarks-1521zwfW.js.map +1 -0
- package/dist/remarks-BhAV1H4V.js +1 -0
- package/dist/remarks-DlJCqWRs.js +278 -0
- package/dist/remarks-DlJCqWRs.js.map +1 -0
- package/dist/{remote-HDRt16ew.js → remote-cE2VWfkJ.js} +3 -3
- package/dist/{remote-HDRt16ew.js.map → remote-cE2VWfkJ.js.map} +1 -1
- package/dist/resolve-C4zeccCH.js +724 -0
- package/dist/resolve-C4zeccCH.js.map +1 -0
- package/dist/resolve-D30yZYmB.js +1 -0
- package/dist/{results-DuauQI3Z.js → results-Drtt0uFl.js} +33 -7
- package/dist/results-Drtt0uFl.js.map +1 -0
- package/dist/{resume-BRfa8E_s.js → resume-BEFazUfy.js} +4 -4
- package/dist/{resume-BRfa8E_s.js.map → resume-BEFazUfy.js.map} +1 -1
- package/dist/rules-BFzQqPIm.js +317 -0
- package/dist/rules-BFzQqPIm.js.map +1 -0
- package/dist/run-BeMy92Us.js +88 -0
- package/dist/run-BeMy92Us.js.map +1 -0
- package/dist/{sandbox-D3A8oUb7.js → sandbox-CQaJj7n9.js} +2 -2
- package/dist/{sandbox-D3A8oUb7.js.map → sandbox-CQaJj7n9.js.map} +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +1 -1
- package/dist/server-V4-2w0l8.js +1 -0
- package/dist/{server-XDleKaF_.js → server-xK0les4f.js} +8 -8
- package/dist/{server-XDleKaF_.js.map → server-xK0les4f.js.map} +1 -1
- package/dist/{shared-DYpIb1BI.js → shared-CA61BuPJ.js} +25 -3
- package/dist/shared-CA61BuPJ.js.map +1 -0
- package/dist/source-BDow_oW8.js +1 -0
- package/dist/{source-C8xumIWj.js → source-Q1hEKbCs.js} +2 -2
- package/dist/{source-C8xumIWj.js.map → source-Q1hEKbCs.js.map} +1 -1
- package/dist/src-B9L9yGKI.js +1 -0
- package/dist/{src-ofUaYbuY.js → src-Bcah6ZkT.js} +1 -1
- package/dist/{src-DvCg0NBs.js → src-BhmaorOU.js} +28 -6
- package/dist/{src-DvCg0NBs.js.map → src-BhmaorOU.js.map} +1 -1
- package/dist/{src-CF17Ug08.js → src-C0CeKiLU.js} +19 -4
- package/dist/{src-CF17Ug08.js.map → src-C0CeKiLU.js.map} +1 -1
- package/dist/src-C0xowZ3C.js +2 -0
- package/dist/src-CHIfIKUu.js +2 -0
- package/dist/src-DLtyz4w5.js +2 -0
- package/dist/src-DPIjmReZ.js +2 -0
- package/dist/{src-C_lPtowT.js → src-DXNh5mTr.js} +1 -1
- package/dist/{src-FGXhkEkH.js → src-DiLMkIr9.js} +142 -13
- package/dist/src-DiLMkIr9.js.map +1 -0
- package/dist/{src-B7treWa8.js → src-LekQrr_S.js} +324 -32
- package/dist/src-LekQrr_S.js.map +1 -0
- package/dist/src-bCnsA6Vp.js +2 -0
- package/dist/{src-u8_ixn_F.js → src-cbCb-MPy.js} +20 -4
- package/dist/src-cbCb-MPy.js.map +1 -0
- package/dist/{src-DvK0EIZx.js → src-qGoFkuJF.js} +55 -6
- package/dist/{src-DvK0EIZx.js.map → src-qGoFkuJF.js.map} +1 -1
- package/dist/{src-jzagbuqH.js → src-ynBVosfX.js} +1 -1
- package/dist/state-BQR_BCm4.js +1 -0
- package/dist/{state-C__9swoG.js → state-BmZ8QVZ8.js} +12 -12
- package/dist/{state-C__9swoG.js.map → state-BmZ8QVZ8.js.map} +1 -1
- package/dist/studio-app/assets/index-CnYJijyI.js +58 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/suite-BaHPmAGy.js +1 -0
- package/dist/suite-C8sw2pox.js +481 -0
- package/dist/suite-C8sw2pox.js.map +1 -0
- package/dist/summary-0oQNGskv.js +837 -0
- package/dist/summary-0oQNGskv.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/{text-gEcKjAyp.js → text-CrVy5ioL.js} +18 -2
- package/dist/text-CrVy5ioL.js.map +1 -0
- package/dist/{transcript-BNNANU8A.js → transcript-BTxJpW4R.js} +2 -2
- package/dist/{transcript-BNNANU8A.js.map → transcript-BTxJpW4R.js.map} +1 -1
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{types-g17bRAzt.d.ts → types-GraZtfHN.d.ts} +858 -39
- package/dist/usage-BDo2q_yu.js +1 -0
- package/dist/{command-Bge8F7Ko.js → validate-BdoXUuL_.js} +35 -383
- package/dist/validate-BdoXUuL_.js.map +1 -0
- package/dist/view/index.d.ts +20 -9
- package/dist/view/index.js +7 -6
- package/dist/views-BoUoX4_A.js +125 -0
- package/dist/views-BoUoX4_A.js.map +1 -0
- package/dist/views-noVQNCUP.js +2 -0
- package/dist/{worker-entry-yOchGYYv.js → worker-entry-D0-GErmU.js} +2 -2
- package/dist/{worker-entry-yOchGYYv.js.map → worker-entry-D0-GErmU.js.map} +1 -1
- package/dist/{workspace-D8sSgpiy.js → workspace-B90YDU_a.js} +1 -1
- package/dist/{workspace-CgyHEM0l.js → workspace-tjZFkjxC.js} +4 -4
- package/dist/{workspace-CgyHEM0l.js.map → workspace-tjZFkjxC.js.map} +1 -1
- package/package.json +10 -8
- package/skills/eval-rubric/SKILL.md +130 -0
- package/dist/agent-CURAwx_q.js +0 -334
- package/dist/agent-CURAwx_q.js.map +0 -1
- package/dist/assemble-B9Alds-z.js.map +0 -1
- package/dist/assemble-J5MukZFY.js +0 -1
- package/dist/capture-jquXZBKK.js +0 -1
- package/dist/cli-D303Vj5N.js.map +0 -1
- package/dist/cli-spec-BPZbQG2p.js.map +0 -1
- package/dist/command-BLfT7vnQ.js.map +0 -1
- package/dist/command-BT9a37ZR.js.map +0 -1
- package/dist/command-Bge8F7Ko.js.map +0 -1
- package/dist/command-BkFjijN2.js +0 -1
- package/dist/commands-BFx16WB8.js +0 -225
- package/dist/commands-BFx16WB8.js.map +0 -1
- package/dist/config-DEgnUiU-.js.map +0 -1
- package/dist/context-C1mxXh80.js.map +0 -1
- package/dist/db-CdHizc4_.js +0 -1406
- package/dist/db-CdHizc4_.js.map +0 -1
- package/dist/db-Dm1cRJJP.js +0 -2
- package/dist/dist-B8hJImYt.js.map +0 -1
- package/dist/doctor-BYNok4UO.js.map +0 -1
- package/dist/duration-CEOQww97.js +0 -26
- package/dist/duration-CEOQww97.js.map +0 -1
- package/dist/egress-C4PZTSSe.js.map +0 -1
- package/dist/environment-DNjRk0nU.js.map +0 -1
- package/dist/evaluate-D4zeRGBF.js.map +0 -1
- package/dist/family-D2b7WD10.js +0 -67
- package/dist/family-D2b7WD10.js.map +0 -1
- package/dist/files-BXUdY8tr.js.map +0 -1
- package/dist/files-BhyltlDB.js +0 -1
- package/dist/files-DQmHE7pb.js.map +0 -1
- package/dist/files-ZZPqfAo6.js +0 -1
- package/dist/images-0On0cztu.js.map +0 -1
- package/dist/init-B77Rpcqc.js +0 -360
- package/dist/init-B77Rpcqc.js.map +0 -1
- package/dist/init-BrOWin3W.js +0 -1
- package/dist/judge-DW_GDOmB.js.map +0 -1
- package/dist/model-NM3ID0uR.js +0 -1
- package/dist/model-h_cDtcRr.js.map +0 -1
- package/dist/model-nwWJvaUR.js +0 -1
- package/dist/model-oqcDplJG.js.map +0 -1
- package/dist/options-Bh2W63dK.js.map +0 -1
- package/dist/plan-C9Cy27Ay.js.map +0 -1
- package/dist/preflight-D7iFPKeK.js.map +0 -1
- package/dist/prepare-BGjePFQl.js +0 -2
- package/dist/prepare-doGxaAGR.js.map +0 -1
- package/dist/process-CSVO_jsc.js +0 -119
- package/dist/process-CSVO_jsc.js.map +0 -1
- package/dist/providers-gDMko8fl.js +0 -1
- package/dist/providers-tAOD1NQN.js.map +0 -1
- package/dist/query-Bes9Kh6Y.js +0 -91
- package/dist/query-Bes9Kh6Y.js.map +0 -1
- package/dist/query-C_G_svLU.js +0 -1
- package/dist/record-BKmMvZ6O.js.map +0 -1
- package/dist/results-DuauQI3Z.js.map +0 -1
- package/dist/server-C7sg1ndo.js +0 -1
- package/dist/shared-DYpIb1BI.js.map +0 -1
- package/dist/source-fGd0SaKP.js +0 -1
- package/dist/src-B7treWa8.js.map +0 -1
- package/dist/src-DNnG4e4R.js +0 -2
- package/dist/src-DTbrY1hi.js +0 -2
- package/dist/src-DdyJEh2v.js +0 -2
- package/dist/src-Dj9PAroc.js +0 -2
- package/dist/src-Du8H85Qq.js +0 -2
- package/dist/src-FGXhkEkH.js.map +0 -1
- package/dist/src-pJZOrYR9.js +0 -1
- package/dist/src-u8_ixn_F.js.map +0 -1
- package/dist/state-CgjQo29K.js +0 -1
- package/dist/studio-app/assets/index-DrQc2Yxd.js +0 -58
- package/dist/summary-BLAVLySU.js +0 -385
- package/dist/summary-BLAVLySU.js.map +0 -1
- package/dist/summary-DgyjUh_l.js +0 -192
- package/dist/summary-DgyjUh_l.js.map +0 -1
- package/dist/text-gEcKjAyp.js.map +0 -1
- package/dist/tools-GCyNYSc1.js +0 -249
- package/dist/tools-GCyNYSc1.js.map +0 -1
- package/dist/views-BW6oCDUQ.js +0 -2
- package/dist/views-BmQF4UCI.js +0 -225
- package/dist/views-BmQF4UCI.js.map +0 -1
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import { a as fromCwd, l as sha256 } from "./ids-Bp6ut7ZD.js";
|
|
2
|
+
import { u as hasAnalysisModel } from "./config-Dv5MG7Rp.js";
|
|
3
|
+
import { r as fenced } from "./text-CrVy5ioL.js";
|
|
4
|
+
import { n as gitText, t as git } from "./git-DhykwgKF.js";
|
|
5
|
+
import { St as compare, bt as addedRow, tt as effortChangeLabel, wt as stackChanges } from "./model-CWlAqmbU.js";
|
|
6
|
+
import { l as suiteResult } from "./suite-C8sw2pox.js";
|
|
7
|
+
import { a as fromModel } from "./insights-B2Xmhz9o.js";
|
|
8
|
+
import { _ as t, a as assertionOf, c as errorKind, d as labeller, f as plural, h as show, l as errorText, m as renderBlocks, n as renderSummary, o as checkCount, s as checkLabel, u as evalBlock, v as table } from "./summary-0oQNGskv.js";
|
|
9
|
+
import { a as listRuns, d as recordRun, p as scratchDir, s as readRecord } from "./files-B-eUpCZ1.js";
|
|
10
|
+
import { t as colors } from "./colors-QV9dFVhN.js";
|
|
11
|
+
import { a as pickBaseline, i as history, n as formatHistory, o as resolveRun, r as formatShow, s as unreachable } from "./views-BoUoX4_A.js";
|
|
12
|
+
import { i as runInsightsFile, n as readInsights } from "./files-BsQbYz2i.js";
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { readFile } from "node:fs/promises";
|
|
16
|
+
//#region src/ui/compare-layout.ts
|
|
17
|
+
const WORD = {
|
|
18
|
+
regressed: "regressed",
|
|
19
|
+
fixed: "fixed",
|
|
20
|
+
drift: "can't compare",
|
|
21
|
+
infra: "infra error",
|
|
22
|
+
added: "new",
|
|
23
|
+
removed: "removed",
|
|
24
|
+
same: "same"
|
|
25
|
+
};
|
|
26
|
+
/** The tally's order: what needs a look first. */
|
|
27
|
+
const TALLY = [
|
|
28
|
+
"regressed",
|
|
29
|
+
"fixed",
|
|
30
|
+
"drift",
|
|
31
|
+
"infra",
|
|
32
|
+
"added",
|
|
33
|
+
"removed",
|
|
34
|
+
"same"
|
|
35
|
+
];
|
|
36
|
+
const paintStatus = (c, s) => s === "regressed" ? c.red : s === "fixed" ? c.green : s === "same" ? c.dim : c.yellow;
|
|
37
|
+
const OUTCOME = {
|
|
38
|
+
pass: "pass",
|
|
39
|
+
fail: "fail",
|
|
40
|
+
infra_error: "infra error",
|
|
41
|
+
cancelled: "cancelled",
|
|
42
|
+
skipped: "skipped"
|
|
43
|
+
};
|
|
44
|
+
const paintOutcome = (c, o) => o === "pass" ? c.green : o === "fail" ? c.red : o === "infra_error" || o === "cancelled" ? c.yellow : c.dim;
|
|
45
|
+
/** One side's outcome as `reeval show` reads it: a fail the interrupt caused is `cancelled`. */
|
|
46
|
+
const sideOutcome = (row, side) => row.cancelled?.includes(side) ? "cancelled" : row[side];
|
|
47
|
+
/**
|
|
48
|
+
* Every eval and family case in a run, by eval id, in file-path then source order. A case is
|
|
49
|
+
* given as an eval of its own, its name the family's with the case filled in.
|
|
50
|
+
*/
|
|
51
|
+
function unitsOf(suite) {
|
|
52
|
+
const out = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const file of suite.files) for (const e of [...file.evals].sort((a, b) => a.order - b.order)) {
|
|
54
|
+
if (!e.cases) {
|
|
55
|
+
out.set(e.id, {
|
|
56
|
+
file: file.path,
|
|
57
|
+
result: e
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const k of e.cases) out.set(k.id, {
|
|
62
|
+
file: file.path,
|
|
63
|
+
result: caseAsEval(e, k)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const caseAsEval = (family, k) => ({
|
|
69
|
+
...family,
|
|
70
|
+
path: k.path,
|
|
71
|
+
id: k.id,
|
|
72
|
+
name: k.name,
|
|
73
|
+
template: null,
|
|
74
|
+
order: k.order,
|
|
75
|
+
family: false,
|
|
76
|
+
outcome: k.outcome,
|
|
77
|
+
cells: k.cells,
|
|
78
|
+
cases: null,
|
|
79
|
+
pooled: null,
|
|
80
|
+
questions: []
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* The rows file by file, in head's source order, each eval only base has placed after the eval
|
|
84
|
+
* before it in base's, so a removed eval sits where it was (beside the one renamed from it).
|
|
85
|
+
*/
|
|
86
|
+
function ordered(rows, units) {
|
|
87
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
88
|
+
const all = [...units.head.values(), ...units.base.values()];
|
|
89
|
+
const files = [...new Set(all.map((u) => u.file))].sort();
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
const inFile = (side) => [...side].filter(([id, u]) => u.file === file && byId.has(id));
|
|
93
|
+
const ids = inFile(units.head).map(([id]) => id);
|
|
94
|
+
let at = -1;
|
|
95
|
+
for (const [id] of inFile(units.base)) {
|
|
96
|
+
const i = ids.indexOf(id);
|
|
97
|
+
if (i >= 0) at = i;
|
|
98
|
+
else ids.splice(++at, 0, id);
|
|
99
|
+
}
|
|
100
|
+
for (const id of ids) {
|
|
101
|
+
const unit = units.head.get(id) ?? units.base.get(id);
|
|
102
|
+
out.push({
|
|
103
|
+
row: byId.get(id),
|
|
104
|
+
...unit ? { unit } : {}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const seen = new Set(out.map((e) => e.row.id));
|
|
109
|
+
for (const row of rows) if (!seen.has(row.id)) out.push({ row });
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
const nameOf = (row, unit) => {
|
|
113
|
+
if (!unit) return row.id;
|
|
114
|
+
const e = unit.result;
|
|
115
|
+
return `${e.group.length ? `${e.group.join(" › ")} › ` : ""}${e.name}`;
|
|
116
|
+
};
|
|
117
|
+
const headLine = (word, name) => `${show(word)}${" ".repeat(Math.max(2, 17 - word.text.length))}${name}`;
|
|
118
|
+
/**
|
|
119
|
+
* A check's state in one cell. A comparable eval runs the same checks on both sides, so a check
|
|
120
|
+
* the cell never recorded is one no attempt reached: not run, never a failure.
|
|
121
|
+
*/
|
|
122
|
+
function stateOf(k) {
|
|
123
|
+
if (!k || k.outcome.status === "not run") return "not run";
|
|
124
|
+
return k.outcome.failed + k.outcome.errored > 0 ? "fail" : "pass";
|
|
125
|
+
}
|
|
126
|
+
/** A check's side of a flip: its state, or its counts when a side answered it more than once. */
|
|
127
|
+
function sideText(k, counts) {
|
|
128
|
+
const state = stateOf(k);
|
|
129
|
+
return counts && k && state !== "not run" ? checkCount(k, false) : state;
|
|
130
|
+
}
|
|
131
|
+
const answered = (k) => k?.outcome.status === "ran" ? k.outcome.passed + k.outcome.failed + k.outcome.errored : 0;
|
|
132
|
+
/** Every check label either cell has, head's first. */
|
|
133
|
+
const labelsOf = (base, head) => [...new Set([...head.checks, ...base.checks].map((k) => k.label))];
|
|
134
|
+
/**
|
|
135
|
+
* A regressed cell: each check head didn't pass, as `pass → fail` (with its assertion) or `not
|
|
136
|
+
* run`, then the errors no check explains and how many more passed.
|
|
137
|
+
*/
|
|
138
|
+
function regressedItems(c, base, head, indent) {
|
|
139
|
+
const items = [];
|
|
140
|
+
let passed = 0;
|
|
141
|
+
for (const label of labelsOf(base, head)) {
|
|
142
|
+
const h = head.checks.find((k) => k.label === label);
|
|
143
|
+
const b = base.checks.find((k) => k.label === label);
|
|
144
|
+
const state = stateOf(h);
|
|
145
|
+
if (state === "pass") {
|
|
146
|
+
passed++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (state === "not run") {
|
|
150
|
+
items.push({
|
|
151
|
+
indent,
|
|
152
|
+
cells: [checkLabel(label), t("not run", c.dim)]
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const counts = answered(h) > 1 || answered(b) > 1;
|
|
157
|
+
const was = stateOf(b) === "fail" && !counts ? null : sideText(b, counts);
|
|
158
|
+
const now = sideText(h, counts);
|
|
159
|
+
items.push({
|
|
160
|
+
indent,
|
|
161
|
+
cells: [checkLabel(label), t(was ? `${was} → ${now}` : now, c.red)]
|
|
162
|
+
});
|
|
163
|
+
const assertion = h ? assertionOf(h) : "";
|
|
164
|
+
if (assertion) items.push({
|
|
165
|
+
indent: indent + 2,
|
|
166
|
+
free: t(assertion, c.dim)
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
for (const err of head.errors) items.push({
|
|
170
|
+
indent,
|
|
171
|
+
cells: [errorKind(err), t(errorText(err), c.dim)]
|
|
172
|
+
});
|
|
173
|
+
if (passed && items.length) items.push({
|
|
174
|
+
indent,
|
|
175
|
+
free: t(`+${passed} more passed`, c.dim)
|
|
176
|
+
});
|
|
177
|
+
return items;
|
|
178
|
+
}
|
|
179
|
+
/** A fixed cell: each check that passes now and didn't before, as `fail → pass` or `not run → pass`. */
|
|
180
|
+
function fixedItems(c, base, head, indent) {
|
|
181
|
+
const items = [];
|
|
182
|
+
for (const label of labelsOf(base, head)) {
|
|
183
|
+
const h = head.checks.find((k) => k.label === label);
|
|
184
|
+
const b = base.checks.find((k) => k.label === label);
|
|
185
|
+
if (stateOf(h) !== "pass" || stateOf(b) === "pass") continue;
|
|
186
|
+
const counts = answered(h) > 1 || answered(b) > 1;
|
|
187
|
+
items.push({
|
|
188
|
+
indent,
|
|
189
|
+
cells: [checkLabel(label), t(`${sideText(b, counts)} → ${sideText(h, counts)}`, c.green)]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return items;
|
|
193
|
+
}
|
|
194
|
+
/** The cells both runs ran, as `[base, head]`; under a heading each when there's more than one. */
|
|
195
|
+
function flips(c, base, head, rows) {
|
|
196
|
+
const before = new Map((base.cells ?? []).map((x) => [x.key, x]));
|
|
197
|
+
const pairs = (head.cells ?? []).flatMap((h) => {
|
|
198
|
+
const b = before.get(h.key);
|
|
199
|
+
return b ? [[b, h]] : [];
|
|
200
|
+
});
|
|
201
|
+
if (pairs.length === 1) {
|
|
202
|
+
const [b, h] = pairs[0];
|
|
203
|
+
return rows(b, h, 2);
|
|
204
|
+
}
|
|
205
|
+
const label = labeller(pairs.map(([, h]) => h), [
|
|
206
|
+
"harness",
|
|
207
|
+
"arm",
|
|
208
|
+
"sandbox"
|
|
209
|
+
]);
|
|
210
|
+
return pairs.flatMap(([b, h]) => {
|
|
211
|
+
const items = rows(b, h, 4);
|
|
212
|
+
return items.length ? [{
|
|
213
|
+
indent: 2,
|
|
214
|
+
free: t(label(h), c.dim)
|
|
215
|
+
}, ...items] : [];
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const idRow = (c, side, id, outcome) => ({
|
|
219
|
+
indent: 2,
|
|
220
|
+
cells: [
|
|
221
|
+
side,
|
|
222
|
+
id,
|
|
223
|
+
...outcome ? [t(OUTCOME[outcome], paintOutcome(c, outcome))] : []
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
/** `environment`, `evals/helpers.ts`: what made an eval not comparable, as the row says it. */
|
|
227
|
+
function changedParts(row, changes) {
|
|
228
|
+
const parts = (row.changes ?? []).map((p) => p.replace(/ changed$/, ""));
|
|
229
|
+
if (parts.length) return parts.join(", ");
|
|
230
|
+
return changes.filter((x) => x.outsideFingerprint).map((x) => x.label).join(", ");
|
|
231
|
+
}
|
|
232
|
+
/** The cells only one run ran, when the runs ran different cells of the eval. */
|
|
233
|
+
function cellRows(row) {
|
|
234
|
+
return [...row.cells?.added.length ? [{
|
|
235
|
+
indent: 2,
|
|
236
|
+
cells: ["cells added", row.cells.added.join(", ")]
|
|
237
|
+
}] : [], ...row.cells?.removed.length ? [{
|
|
238
|
+
indent: 2,
|
|
239
|
+
cells: ["cells removed", row.cells.removed.join(", ")]
|
|
240
|
+
}] : []];
|
|
241
|
+
}
|
|
242
|
+
/** The eval's own rows as `reeval run` prints them under its head: its evidence when it failed. */
|
|
243
|
+
const resultBody = (c, e) => e && e.outcome !== "pass" ? evalBlock(c, e).slice(1) : [];
|
|
244
|
+
const liftShort = (l) => l ? `${signed(l.points)} (${signed(l.lo)}…${signed(l.hi)})${l.withinNoise ? " noise" : ""}` : "–";
|
|
245
|
+
const signed = (x) => `${x >= 0 ? "+" : "−"}${Math.round(Math.abs(x) * 100)}`;
|
|
246
|
+
/** `-v`: a cell's lift and effort, base → head. */
|
|
247
|
+
function cellStats(cell) {
|
|
248
|
+
return [cell.base.lift || cell.head.lift ? `lift ${liftShort(cell.base.lift)} → ${liftShort(cell.head.lift)}` : "", cell.base.effort && cell.head.effort ? effortChangeLabel(cell.base.effort, cell.head.effort) : ""].filter(Boolean).join(" · ");
|
|
249
|
+
}
|
|
250
|
+
function statsRows(c, row) {
|
|
251
|
+
return (row.perCell ?? []).flatMap((cell) => {
|
|
252
|
+
const stats = cellStats(cell);
|
|
253
|
+
return stats ? [{
|
|
254
|
+
indent: 2,
|
|
255
|
+
cells: [cell.key, t(stats, c.dim)]
|
|
256
|
+
}] : [];
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function block(c, input, units, { row, unit }, opts) {
|
|
260
|
+
const base = units.base.get(row.id)?.result;
|
|
261
|
+
const head = units.head.get(row.id)?.result;
|
|
262
|
+
const word = t(WORD[row.status], paintStatus(c, row.status));
|
|
263
|
+
const top = headLine(word, nameOf(row, unit));
|
|
264
|
+
const baseId = input.base?.runId ?? "";
|
|
265
|
+
const headId = input.head.runId;
|
|
266
|
+
const stats = opts.verbose ? statsRows(c, row) : [];
|
|
267
|
+
if (row.status === "regressed" || row.status === "fixed") {
|
|
268
|
+
const evidence = base && head ? flips(c, base, head, (b, h, indent) => row.status === "regressed" ? regressedItems(c, b, h, indent) : fixedItems(c, b, h, indent)) : [];
|
|
269
|
+
const ids = row.status === "regressed";
|
|
270
|
+
const items = [
|
|
271
|
+
...ids ? [idRow(c, "base", baseId), idRow(c, "head", headId)] : [],
|
|
272
|
+
...cellRows(row),
|
|
273
|
+
...evidence
|
|
274
|
+
];
|
|
275
|
+
return {
|
|
276
|
+
lines: [
|
|
277
|
+
top,
|
|
278
|
+
...table(items),
|
|
279
|
+
...table(stats)
|
|
280
|
+
],
|
|
281
|
+
ids
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (row.status === "drift" || row.status === "infra") {
|
|
285
|
+
const changed = row.status === "drift" ? changedParts(row, input.changes) : "";
|
|
286
|
+
return {
|
|
287
|
+
lines: [
|
|
288
|
+
top,
|
|
289
|
+
...changed ? table([{
|
|
290
|
+
indent: 2,
|
|
291
|
+
cells: ["changed", changed]
|
|
292
|
+
}]) : [],
|
|
293
|
+
...table([idRow(c, "base", baseId, sideOutcome(row, "base")), idRow(c, "head", headId, sideOutcome(row, "head"))]),
|
|
294
|
+
...table([...cellRows(row), ...stats]),
|
|
295
|
+
...resultBody(c, head)
|
|
296
|
+
],
|
|
297
|
+
ids: true
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (row.status === "added") return {
|
|
301
|
+
lines: [
|
|
302
|
+
top,
|
|
303
|
+
...table([idRow(c, "head", headId, sideOutcome(row, "head"))]),
|
|
304
|
+
...resultBody(c, head)
|
|
305
|
+
],
|
|
306
|
+
ids: true
|
|
307
|
+
};
|
|
308
|
+
if (row.status === "removed") return {
|
|
309
|
+
lines: [top, ...table([idRow(c, "base", baseId, sideOutcome(row, "base"))])],
|
|
310
|
+
ids: true
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
lines: [top, ...table(stats)],
|
|
314
|
+
ids: false
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** `1 regressed · 1 fixed · 1 can't compare · 4 same`: what needs a look first, zeros left out. */
|
|
318
|
+
function compareTally(c, rows) {
|
|
319
|
+
const parts = TALLY.flatMap((s) => {
|
|
320
|
+
const n = rows.filter((r) => r.status === s).length;
|
|
321
|
+
return n ? [paintStatus(c, s)(`${n} ${WORD[s]}`)] : [];
|
|
322
|
+
});
|
|
323
|
+
return parts.length ? parts.join(c.dim(" · ")) : "no evals to compare";
|
|
324
|
+
}
|
|
325
|
+
const UNFINISHED = {
|
|
326
|
+
interrupted: "was interrupted, so some of its evals were cancelled before they finished",
|
|
327
|
+
budget: "stopped at its duration budget, so some of its evals never ran"
|
|
328
|
+
};
|
|
329
|
+
/** What makes the compare suspect: a run whose commit isn't in HEAD's history, or that didn't finish. */
|
|
330
|
+
function sideNotes(c, input) {
|
|
331
|
+
return ["base", "head"].flatMap((side) => {
|
|
332
|
+
const s = input[side];
|
|
333
|
+
if (!s) return [];
|
|
334
|
+
const unfinished = s.status && UNFINISHED[s.status];
|
|
335
|
+
return [...s.unreachable ? [`${side} ${s.runId} ran on a commit that isn't in HEAD's history (rebased or dropped?)`] : [], ...unfinished ? [`${side} ${s.runId} ${unfinished}`] : []].map((note) => c.yellow(note));
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function stackNotes(c, changes) {
|
|
339
|
+
return changes.map((x) => c.dim(`stack changed: ${x.label} ${x.base} → ${x.head}`));
|
|
340
|
+
}
|
|
341
|
+
/** Head on its own: no earlier recorded run, so every eval is new, shown as `reeval run` shows it. */
|
|
342
|
+
function noBaseline(c, input, opts) {
|
|
343
|
+
const { suite, runId } = input.head;
|
|
344
|
+
const blocks = renderBlocks(suite, {
|
|
345
|
+
color: opts.color,
|
|
346
|
+
verbose: opts.verbose,
|
|
347
|
+
hint: false
|
|
348
|
+
});
|
|
349
|
+
const counts = [
|
|
350
|
+
"fail",
|
|
351
|
+
"infra_error",
|
|
352
|
+
"cancelled",
|
|
353
|
+
"pass",
|
|
354
|
+
"skipped"
|
|
355
|
+
].flatMap((o) => {
|
|
356
|
+
const n = input.rows.filter((r) => sideOutcome(r, "head") === o).length;
|
|
357
|
+
return n ? [paintOutcome(c, o)(`${n} ${OUTCOME[o]}`)] : [];
|
|
358
|
+
});
|
|
359
|
+
return [
|
|
360
|
+
`No earlier recorded run to compare ${runId} with, so every eval is new.`,
|
|
361
|
+
...blocks.length ? ["", ...blocks] : [],
|
|
362
|
+
"",
|
|
363
|
+
...sideNotes(c, input),
|
|
364
|
+
`${plural(input.rows.length, "new eval")}${counts.length ? ` (${counts.join(c.dim(" · "))})` : ""}`
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
/** `reeval compare`'s lines: the blocks, a blank line around each, then the tally. */
|
|
368
|
+
function renderCompare(input, opts) {
|
|
369
|
+
const c = colors(opts.color);
|
|
370
|
+
if (!input.base) return noBaseline(c, input, opts);
|
|
371
|
+
const units = {
|
|
372
|
+
base: unitsOf(input.base.suite),
|
|
373
|
+
head: unitsOf(input.head.suite)
|
|
374
|
+
};
|
|
375
|
+
const blocks = [];
|
|
376
|
+
let ids = false;
|
|
377
|
+
let same = 0;
|
|
378
|
+
const flush = () => {
|
|
379
|
+
if (same) blocks.push([c.dim(`+${same} same`)]);
|
|
380
|
+
same = 0;
|
|
381
|
+
};
|
|
382
|
+
for (const entry of ordered(input.rows, units)) {
|
|
383
|
+
if (entry.row.status === "same" && !(opts.verbose && entry.row.perCell?.length)) {
|
|
384
|
+
same++;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
flush();
|
|
388
|
+
const b = block(c, input, units, entry, opts);
|
|
389
|
+
ids ||= b.ids;
|
|
390
|
+
blocks.push(b.lines);
|
|
391
|
+
}
|
|
392
|
+
flush();
|
|
393
|
+
if (!ids) blocks.unshift(table([{
|
|
394
|
+
indent: 0,
|
|
395
|
+
cells: ["base", input.base.runId]
|
|
396
|
+
}, {
|
|
397
|
+
indent: 0,
|
|
398
|
+
cells: ["head", input.head.runId]
|
|
399
|
+
}], 15));
|
|
400
|
+
const notes = [...sideNotes(c, input), ...opts.verbose ? stackNotes(c, input.changes) : []];
|
|
401
|
+
return [
|
|
402
|
+
...blocks.flatMap((b) => [...b, ""]),
|
|
403
|
+
...notes,
|
|
404
|
+
compareTally(c, input.rows)
|
|
405
|
+
];
|
|
406
|
+
}
|
|
407
|
+
/** `reeval compare --md`: a heading with both run ids, and the layout in a text block. */
|
|
408
|
+
function renderCompareMarkdown(input, opts) {
|
|
409
|
+
return [
|
|
410
|
+
input.base ? `### reeval compare: ${input.head.runId} vs ${input.base.runId}` : `### reeval compare: ${input.head.runId}`,
|
|
411
|
+
"",
|
|
412
|
+
...fenced(renderCompare(input, {
|
|
413
|
+
...opts,
|
|
414
|
+
color: false
|
|
415
|
+
}), "text"),
|
|
416
|
+
""
|
|
417
|
+
].join("\n");
|
|
418
|
+
}
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/commands.ts
|
|
421
|
+
var CommandError = class extends Error {
|
|
422
|
+
name = "CommandError";
|
|
423
|
+
};
|
|
424
|
+
const recorded = (config) => listRuns(config.root, config.history.dir, true);
|
|
425
|
+
const scratch = (config) => listRuns(config.root, scratchDir(config.root), false);
|
|
426
|
+
/** Recorded runs plus scratch runs that weren't recorded, sorted by run id. */
|
|
427
|
+
async function allRuns(config) {
|
|
428
|
+
const kept = await recorded(config);
|
|
429
|
+
const ids = new Set(kept.map((e) => e.runId));
|
|
430
|
+
return [...kept, ...(await scratch(config)).filter((e) => !ids.has(e.runId))].sort((a, b) => a.runId < b.runId ? -1 : 1);
|
|
431
|
+
}
|
|
432
|
+
async function lines(root, args) {
|
|
433
|
+
const r = await git(root, args);
|
|
434
|
+
if (r.code !== 0) return null;
|
|
435
|
+
return r.stdout.toString("utf8").split("\n").filter(Boolean);
|
|
436
|
+
}
|
|
437
|
+
/** Commits reachable from HEAD; null when git can't tell (no repo, or a shallow clone). */
|
|
438
|
+
async function reachability(root) {
|
|
439
|
+
const shallow = await lines(root, ["rev-parse", "--is-shallow-repository"]);
|
|
440
|
+
if (!shallow || shallow[0] === "true") return null;
|
|
441
|
+
const shas = await lines(root, [
|
|
442
|
+
"rev-list",
|
|
443
|
+
"--max-count=20000",
|
|
444
|
+
"HEAD"
|
|
445
|
+
]);
|
|
446
|
+
return shas ? new Set(shas) : null;
|
|
447
|
+
}
|
|
448
|
+
const rel = (path) => fromCwd(path) || path;
|
|
449
|
+
function parseLevel(value, fallback) {
|
|
450
|
+
if (value === void 0) return fallback;
|
|
451
|
+
if (value === "all" || value === "failures" || value === "none") return value;
|
|
452
|
+
throw new CommandError(`--artifacts is all, failures or none (got ${value})`);
|
|
453
|
+
}
|
|
454
|
+
async function record(config, runId, flags) {
|
|
455
|
+
const result = await recordRun({
|
|
456
|
+
root: config.root,
|
|
457
|
+
historyDir: config.history.dir,
|
|
458
|
+
runId,
|
|
459
|
+
artifacts: parseLevel(flags.artifacts, config.history.artifacts),
|
|
460
|
+
allowIncomplete: flags["allow-incomplete"],
|
|
461
|
+
skipSecretScan: flags["skip-secret-scan"],
|
|
462
|
+
hint: !hasAnalysisModel(config)
|
|
463
|
+
});
|
|
464
|
+
const where = rel(result.recordPath);
|
|
465
|
+
if (!result.created) return `${where} is already recorded`;
|
|
466
|
+
const kb = (result.bytes / 1024).toFixed(1);
|
|
467
|
+
const recorded = `recorded ${where}${result.artifactDir ? ` + ${result.files} artifact files in ${rel(result.artifactDir)}/` : ""} (${kb} KB). Commit it with git.`;
|
|
468
|
+
const ignored = result.ignored.length === 0 ? [] : [`warning: git ignores ${result.ignored.length} recorded file(s); they won't be committed:`, ...result.ignored.map((i) => ` ${i.path} ignored by ${i.rule} (${i.source})`)];
|
|
469
|
+
if (result.droppedPatch.length === 0) return ignored.length === 0 ? recorded : `${ignored.join("\n")}\n${recorded}`;
|
|
470
|
+
const artifacts = result.artifactDir ? `${rel(result.artifactDir)}/` : "";
|
|
471
|
+
return [
|
|
472
|
+
`warning: left worktree.patch out of ${runId}: it looks like it holds a secret.`,
|
|
473
|
+
...result.droppedPatch.map((line) => ` ${line}`),
|
|
474
|
+
" worktree.patch is your checkout's uncommitted changes when the run started (git status), not the agents' work.",
|
|
475
|
+
" Nothing was committed. Take the key out of those files, or add them to .gitignore, so a later commit doesn't carry it; rotate the key only if it's already been committed or pushed. The record says the patch was left out, so reeval checkout recreates the commit alone.",
|
|
476
|
+
` To keep it after checking by hand: delete ${where} and ${artifacts || "its artifacts"}, then reeval record ${runId} --skip-secret-scan.`,
|
|
477
|
+
...ignored,
|
|
478
|
+
recorded
|
|
479
|
+
].join("\n");
|
|
480
|
+
}
|
|
481
|
+
async function recordCommand(ctx, refs) {
|
|
482
|
+
const runs = await scratch(ctx.config);
|
|
483
|
+
for (const ref of refs.length ? refs : ["latest"]) ctx.out(await record(ctx.config, resolveRun(runs, ref).runId, ctx.flags));
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
/** `reeval history --json`: recent runs (recorded, or with `all` scratch ones too) and each eval's strip. */
|
|
487
|
+
async function historyView(config, options = {}) {
|
|
488
|
+
const entries = options.all ? await allRuns(config) : await recorded(config);
|
|
489
|
+
const limit = options.limit ?? 20;
|
|
490
|
+
if (!(limit > 0)) throw new CommandError(`--limit must be a number above 0 (got ${options.limit})`);
|
|
491
|
+
const reach = await reachability(config.root);
|
|
492
|
+
return history(entries, {
|
|
493
|
+
filter: options.filter || void 0,
|
|
494
|
+
limit,
|
|
495
|
+
reach
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
async function historyCommand(ctx, filter) {
|
|
499
|
+
const view = await historyView(ctx.config, {
|
|
500
|
+
filter: filter.join(" "),
|
|
501
|
+
limit: ctx.flags.limit ? Number(ctx.flags.limit) : void 0,
|
|
502
|
+
all: ctx.flags.all
|
|
503
|
+
});
|
|
504
|
+
ctx.out(ctx.flags.json ? JSON.stringify(view, null, 2) : formatHistory(view));
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
/** A listed run's record, from the history folder or the scratch runs. */
|
|
508
|
+
const recordOf = (config, entry) => readRecord(join(entry.recorded ? config.history.dir : scratchDir(config.root), `${entry.runId}.json`));
|
|
509
|
+
/** The record of the run `ref` names (default latest), recorded or scratch. */
|
|
510
|
+
async function showRecord(config, ref) {
|
|
511
|
+
return recordOf(config, resolveRun(await allRuns(config), ref));
|
|
512
|
+
}
|
|
513
|
+
async function showCommand(ctx, refs) {
|
|
514
|
+
const { config } = ctx;
|
|
515
|
+
const entry = resolveRun(await allRuns(config), refs[0]);
|
|
516
|
+
const dir = entry.recorded ? config.history.dir : scratchDir(config.root);
|
|
517
|
+
const run = await readRecord(join(dir, `${entry.runId}.json`));
|
|
518
|
+
if (ctx.flags.json) {
|
|
519
|
+
ctx.out(JSON.stringify(run, null, 2));
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
const insights = await readInsights(runInsightsFile(config, entry));
|
|
523
|
+
const hint = !(insights && fromModel(insights)) && !hasAnalysisModel(config);
|
|
524
|
+
if (ctx.flags.md) ctx.out(renderSummary(run, {
|
|
525
|
+
hint,
|
|
526
|
+
insights
|
|
527
|
+
}).trimEnd());
|
|
528
|
+
else ctx.out(formatShow(run, {
|
|
529
|
+
hint,
|
|
530
|
+
insights
|
|
531
|
+
}));
|
|
532
|
+
return 0;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Two runs and their rows: `base` and `head` when both are given, else `head` (default latest)
|
|
536
|
+
* against its nearest recorded ancestor. `json` is what `reeval compare --json` prints.
|
|
537
|
+
*/
|
|
538
|
+
async function compareView(config, refs) {
|
|
539
|
+
const { root } = config;
|
|
540
|
+
const entries = await allRuns(config);
|
|
541
|
+
const reach = await reachability(root);
|
|
542
|
+
let base;
|
|
543
|
+
let head;
|
|
544
|
+
if (refs.length >= 2) {
|
|
545
|
+
base = resolveRun(entries, refs[0]);
|
|
546
|
+
head = resolveRun(entries, refs[1]);
|
|
547
|
+
} else {
|
|
548
|
+
head = resolveRun(entries, refs[0] ?? "latest");
|
|
549
|
+
const ancestry = await lines(root, [
|
|
550
|
+
"rev-list",
|
|
551
|
+
"--max-count=5000",
|
|
552
|
+
head.git && (!reach || reach.has(head.git.sha)) ? head.git.sha : "HEAD"
|
|
553
|
+
]);
|
|
554
|
+
const candidates = entries.filter((e) => e.recorded && e.runId !== head.runId);
|
|
555
|
+
base = pickBaseline(candidates, head, ancestry);
|
|
556
|
+
}
|
|
557
|
+
const rows = base ? compare(base, head) : head.evals.map(addedRow);
|
|
558
|
+
const flag = (e) => ({
|
|
559
|
+
runId: e.runId,
|
|
560
|
+
recorded: e.recorded,
|
|
561
|
+
inHistory: !reach || !e.git ? null : reach.has(e.git.sha)
|
|
562
|
+
});
|
|
563
|
+
const json = base ? {
|
|
564
|
+
base: flag(base),
|
|
565
|
+
head: flag(head),
|
|
566
|
+
changes: stackChanges(base, head),
|
|
567
|
+
rows
|
|
568
|
+
} : {
|
|
569
|
+
base: null,
|
|
570
|
+
head: { runId: head.runId },
|
|
571
|
+
changes: [],
|
|
572
|
+
rows
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
base,
|
|
576
|
+
head,
|
|
577
|
+
rows,
|
|
578
|
+
reach,
|
|
579
|
+
json
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
async function compareCommand(ctx, refs) {
|
|
583
|
+
const { base, head, rows, reach, json } = await compareView(ctx.config, refs);
|
|
584
|
+
const failing = regressions(rows);
|
|
585
|
+
if (ctx.flags.json) ctx.out(JSON.stringify(json, null, 2));
|
|
586
|
+
else {
|
|
587
|
+
const side = async (e) => {
|
|
588
|
+
const record = await recordOf(ctx.config, e);
|
|
589
|
+
return {
|
|
590
|
+
runId: e.runId,
|
|
591
|
+
suite: suiteResult(record),
|
|
592
|
+
unreachable: unreachable(e, reach),
|
|
593
|
+
status: record.status
|
|
594
|
+
};
|
|
595
|
+
};
|
|
596
|
+
const input = {
|
|
597
|
+
base: base ? await side(base) : null,
|
|
598
|
+
head: await side(head),
|
|
599
|
+
rows,
|
|
600
|
+
changes: base ? stackChanges(base, head) : []
|
|
601
|
+
};
|
|
602
|
+
const opts = {
|
|
603
|
+
color: false,
|
|
604
|
+
verbose: !!ctx.flags.verbose?.length
|
|
605
|
+
};
|
|
606
|
+
ctx.out(ctx.flags.md ? renderCompareMarkdown(input, opts).trimEnd() : renderCompare(input, opts).join("\n"));
|
|
607
|
+
}
|
|
608
|
+
return ctx.flags["fail-on-regression"] && failing.length ? 1 : 0;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* What `--fail-on-regression` fails on: an eval that went from pass to fail, and a new or
|
|
612
|
+
* changed (can't compare) eval that fails, since nothing earlier vouches for it. A head the run's
|
|
613
|
+
* interrupt cancelled didn't fail.
|
|
614
|
+
*/
|
|
615
|
+
const regressions = (rows) => rows.filter((r) => r.status === "regressed" || (r.status === "added" || r.status === "drift") && r.head === "fail" && !r.cancelled?.includes("head"));
|
|
616
|
+
/** Recreates the state a run executed in: its commit, plus its uncommitted changes if any. */
|
|
617
|
+
async function checkoutCommand(ctx, args) {
|
|
618
|
+
const { root } = ctx.config;
|
|
619
|
+
const entry = resolveRun(await allRuns(ctx.config), args[0] ?? "latest");
|
|
620
|
+
const dir = entry.recorded ? ctx.config.history.dir : scratchDir(root);
|
|
621
|
+
const run = await readRecord(join(dir, `${entry.runId}.json`));
|
|
622
|
+
if (!run.git) throw new CommandError(`${run.runId} didn't run in a git repository`);
|
|
623
|
+
const { sha } = run.git;
|
|
624
|
+
if ((await git(root, [
|
|
625
|
+
"cat-file",
|
|
626
|
+
"-e",
|
|
627
|
+
`${sha}^{commit}`
|
|
628
|
+
])).code !== 0) throw new CommandError(`Commit ${sha.slice(0, 12)} isn't in this clone (rebased or not fetched). Fetch it, then try again.`);
|
|
629
|
+
const target = resolve(args[1] ?? join(root, ".reeval", "checkouts", run.runId));
|
|
630
|
+
if (existsSync(target)) throw new CommandError(`${rel(target)} already exists`);
|
|
631
|
+
const patchRef = run.git.worktreePatch;
|
|
632
|
+
const patchPath = patchRef && join(dir, run.runId, patchRef.path);
|
|
633
|
+
if (patchRef && patchPath) {
|
|
634
|
+
const bytes = await readFile(patchPath).catch((error) => {
|
|
635
|
+
if (error.code !== "ENOENT") throw error;
|
|
636
|
+
return null;
|
|
637
|
+
});
|
|
638
|
+
if (!bytes) throw new CommandError(`${rel(patchPath)} is missing, but the record says the run's uncommitted changes are in it. ${entry.recorded ? "Restore it from git, where it's committed with the record, then try again." : "It's gone from .reeval/runs, so those changes can't be recreated."}`);
|
|
639
|
+
if (sha256(bytes) !== patchRef.sha256) throw new CommandError(`${rel(patchPath)} doesn't match the hash in the record`);
|
|
640
|
+
}
|
|
641
|
+
await gitText(root, [
|
|
642
|
+
"worktree",
|
|
643
|
+
"add",
|
|
644
|
+
"--detach",
|
|
645
|
+
"-q",
|
|
646
|
+
target,
|
|
647
|
+
sha
|
|
648
|
+
]);
|
|
649
|
+
let applied = "";
|
|
650
|
+
if (patchPath) {
|
|
651
|
+
const r = await git(target, [
|
|
652
|
+
"apply",
|
|
653
|
+
"--binary",
|
|
654
|
+
"--whitespace=nowarn",
|
|
655
|
+
patchPath
|
|
656
|
+
]);
|
|
657
|
+
if (r.code !== 0) throw new CommandError(`Applying worktree.patch failed: ${r.stderr.trim()}`);
|
|
658
|
+
applied = " and applied worktree.patch";
|
|
659
|
+
} else if (run.git.dirty) applied = `, without the run's uncommitted changes: they weren't saved (${run.git.worktreePatchNote ?? "unknown"})`;
|
|
660
|
+
ctx.out(`checked out ${sha.slice(0, 12)} into ${rel(target)}${applied}.\nRemove it with: git worktree remove ${rel(target)}`);
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
664
|
+
export { compareView as a, lines as c, record as d, recordCommand as f, showRecord as h, compareCommand as i, parseLevel as l, showCommand as m, allRuns as n, historyCommand as o, regressions as p, checkoutCommand as r, historyView as s, CommandError as t, reachability as u };
|
|
665
|
+
|
|
666
|
+
//# sourceMappingURL=commands-CjpGBUWr.js.map
|