@hawkeyexl/manni 4.5.0 → 4.6.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 +1 -1
- package/dist/batch-OZOACN4G.js +41 -0
- package/dist/build-Z5VOVADH.js +32 -0
- package/dist/calibrate-TZYT42DR.js +36 -0
- package/dist/capture-SL4MNDDP.js +34 -0
- package/dist/check-5OMA42Y6.js +26 -0
- package/dist/{check-ESNS7ZBC.js → check-OAJWC3WD.js} +7 -5
- package/dist/{check-RQIIIURI.js → check-PKY7KTQE.js} +13 -14
- package/dist/check-WB2QDHJQ.js +880 -0
- package/dist/check-WB2QDHJQ.js.map +1 -0
- package/dist/{chunk-F2OVHJNC.js → chunk-2JFPBZJK.js} +11 -11
- package/dist/{chunk-CP4ZM43V.js → chunk-2KRB3EG7.js} +2 -2
- package/dist/{chunk-QLUMKWQQ.js → chunk-2M6DVNI5.js} +10 -6
- package/dist/{chunk-QLUMKWQQ.js.map → chunk-2M6DVNI5.js.map} +1 -1
- package/dist/{chunk-HQFVJDEN.js → chunk-3NWX34ZI.js} +19 -19
- package/dist/{chunk-TCC737IS.js → chunk-5G53XATW.js} +4 -4
- package/dist/{chunk-B7XUEQF3.js → chunk-6KKZXB2O.js} +7 -5
- package/dist/{chunk-B7XUEQF3.js.map → chunk-6KKZXB2O.js.map} +1 -1
- package/dist/{chunk-SNEHX4VE.js → chunk-733Y2STG.js} +2 -2
- package/dist/{chunk-ELZ2IIOF.js → chunk-73TC4DYH.js} +2 -2
- package/dist/{chunk-QY3DOZMS.js → chunk-7WRP5FFP.js} +4 -4
- package/dist/{chunk-3R5Y3XOL.js → chunk-APKH2T6E.js} +29 -591
- package/dist/chunk-APKH2T6E.js.map +1 -0
- package/dist/{chunk-FLF6AECQ.js → chunk-B3TS4WRM.js} +17 -15
- package/dist/{chunk-FLF6AECQ.js.map → chunk-B3TS4WRM.js.map} +1 -1
- package/dist/chunk-BWCWTHYS.js +146 -0
- package/dist/chunk-BWCWTHYS.js.map +1 -0
- package/dist/{chunk-C6HZ6C5T.js → chunk-C3B2VJNC.js} +2 -2
- package/dist/{chunk-NN4ASVTY.js → chunk-C73RSUIC.js} +4 -4
- package/dist/chunk-CU57HT2E.js +116 -0
- package/dist/chunk-CU57HT2E.js.map +1 -0
- package/dist/chunk-CWXZ2MRT.js +222 -0
- package/dist/chunk-CWXZ2MRT.js.map +1 -0
- package/dist/{chunk-GAZFKZHM.js → chunk-DC5DDJVE.js} +6 -6
- package/dist/{chunk-52PDIZKV.js → chunk-ECVQNGTH.js} +3 -3
- package/dist/chunk-FLAO4ZS3.js +502 -0
- package/dist/chunk-FLAO4ZS3.js.map +1 -0
- package/dist/chunk-FQGLRJGX.js +174 -0
- package/dist/chunk-FQGLRJGX.js.map +1 -0
- package/dist/{chunk-XWFNO5PO.js → chunk-GAJIJKET.js} +3 -3
- package/dist/{chunk-QCBICNKC.js → chunk-GREQKO6M.js} +202 -54
- package/dist/chunk-GREQKO6M.js.map +1 -0
- package/dist/chunk-HX44GB4Z.js +30 -0
- package/dist/chunk-HX44GB4Z.js.map +1 -0
- package/dist/{chunk-7ZUCEMCY.js → chunk-IY7NYNPZ.js} +9 -11
- package/dist/{chunk-7ZUCEMCY.js.map → chunk-IY7NYNPZ.js.map} +1 -1
- package/dist/{chunk-527HHQPS.js → chunk-KCOFEWV5.js} +6 -4
- package/dist/{chunk-527HHQPS.js.map → chunk-KCOFEWV5.js.map} +1 -1
- package/dist/chunk-M2RLHZ4J.js +849 -0
- package/dist/chunk-M2RLHZ4J.js.map +1 -0
- package/dist/{chunk-K3U5F2QT.js → chunk-M6GSS72T.js} +12 -12
- package/dist/{chunk-UK2I3ACY.js → chunk-MHM4QEBV.js} +4 -4
- package/dist/{chunk-EL2BZTDL.js → chunk-MOWMCEOJ.js} +13 -11
- package/dist/{chunk-EL2BZTDL.js.map → chunk-MOWMCEOJ.js.map} +1 -1
- package/dist/{chunk-OIZ3XMKB.js → chunk-MWBJ3KTF.js} +4 -4
- package/dist/chunk-N7JSEDSC.js +233 -0
- package/dist/chunk-N7JSEDSC.js.map +1 -0
- package/dist/{chunk-MZC62HPZ.js → chunk-NBMPHD4I.js} +2 -2
- package/dist/{chunk-YA63PVWI.js → chunk-NHHOPZRA.js} +12 -10
- package/dist/{chunk-YA63PVWI.js.map → chunk-NHHOPZRA.js.map} +1 -1
- package/dist/{chunk-DX32P2DT.js → chunk-NTWW7J76.js} +2 -2
- package/dist/{chunk-6XTQDFGP.js → chunk-PCFG7E6B.js} +3 -3
- package/dist/{chunk-6XTQDFGP.js.map → chunk-PCFG7E6B.js.map} +1 -1
- package/dist/{chunk-SJO2FTPG.js → chunk-PL4UAF4S.js} +73 -1304
- package/dist/chunk-PL4UAF4S.js.map +1 -0
- package/dist/chunk-QCHVR4ZM.js +286 -0
- package/dist/chunk-QCHVR4ZM.js.map +1 -0
- package/dist/{chunk-JQU7FHYH.js → chunk-QW77LCOU.js} +10 -10
- package/dist/{chunk-IA7BMIPE.js → chunk-SM6NOMJS.js} +2 -2
- package/dist/{chunk-DE7JSWXV.js → chunk-SWSTHZBI.js} +8 -6
- package/dist/{chunk-DE7JSWXV.js.map → chunk-SWSTHZBI.js.map} +1 -1
- package/dist/{chunk-NGK3N6F2.js → chunk-T6SYGLWR.js} +3 -3
- package/dist/chunk-TH4JVQPF.js +133 -0
- package/dist/chunk-TH4JVQPF.js.map +1 -0
- package/dist/{chunk-TVIX653Q.js → chunk-TLXE7LYK.js} +4 -4
- package/dist/{chunk-HVFR66GQ.js → chunk-TUAAR3MM.js} +3280 -283
- package/dist/chunk-TUAAR3MM.js.map +1 -0
- package/dist/{chunk-TA42STP7.js → chunk-UAKNZVCD.js} +4 -4
- package/dist/{chunk-C3KI45RF.js → chunk-UJSGS2Y5.js} +4 -4
- package/dist/{chunk-ZX43SRZR.js → chunk-UWQ2UVHE.js} +8 -6
- package/dist/{chunk-ZX43SRZR.js.map → chunk-UWQ2UVHE.js.map} +1 -1
- package/dist/chunk-WQWKYAAK.js +86 -0
- package/dist/chunk-WQWKYAAK.js.map +1 -0
- package/dist/{chunk-LJ2RDH3F.js → chunk-XG6PVB5S.js} +1 -23
- package/dist/chunk-XG6PVB5S.js.map +1 -0
- package/dist/{chunk-Q35YIDBV.js → chunk-XKBEHKWO.js} +29 -264
- package/dist/chunk-XKBEHKWO.js.map +1 -0
- package/dist/chunk-XPEWUK7J.js +532 -0
- package/dist/chunk-XPEWUK7J.js.map +1 -0
- package/dist/{chunk-BOIF2XGG.js → chunk-XXDXIIQ4.js} +2 -2
- package/dist/chunk-YLK2J5YK.js +610 -0
- package/dist/chunk-YLK2J5YK.js.map +1 -0
- package/dist/{chunk-7OQAU4EA.js → chunk-Z4IZGEM3.js} +16 -12
- package/dist/{chunk-7OQAU4EA.js.map → chunk-Z4IZGEM3.js.map} +1 -1
- package/dist/{cli-E3H6ZP4I.js → cli-6EWVMEN6.js} +5 -3
- package/dist/{cli-E3H6ZP4I.js.map → cli-6EWVMEN6.js.map} +1 -1
- package/dist/{cli-MHTYPVOD.js → cli-6H7HVDMQ.js} +113 -20
- package/dist/{cli-MHTYPVOD.js.map → cli-6H7HVDMQ.js.map} +1 -1
- package/dist/{cli-HUIMNJT6.js → cli-EPVZHAZS.js} +19 -20
- package/dist/{cli-HUIMNJT6.js.map → cli-EPVZHAZS.js.map} +1 -1
- package/dist/{cli-6PGVHP7Y.js → cli-LUCSGTVI.js} +17 -19
- package/dist/{cli-6PGVHP7Y.js.map → cli-LUCSGTVI.js.map} +1 -1
- package/dist/{cli-PRZHQ3QB.js → cli-NIG4N3XM.js} +14 -15
- package/dist/{cli-PRZHQ3QB.js.map → cli-NIG4N3XM.js.map} +1 -1
- package/dist/{cli-TYGOHJOJ.js → cli-PSKNB66T.js} +225 -58
- package/dist/cli-PSKNB66T.js.map +1 -0
- package/dist/{cli-QKIJOXGW.js → cli-UO4NHIB3.js} +17 -17
- package/dist/{cli-AAG3N4NB.js → cli-UQ7A3YA2.js} +17 -18
- package/dist/{cli-AAG3N4NB.js.map → cli-UQ7A3YA2.js.map} +1 -1
- package/dist/{cli-YW6VWBHX.js → cli-YHYDLQAF.js} +10 -10
- package/dist/cli.js +11 -11
- package/dist/config-P4HDNTYD.js +26 -0
- package/dist/conformance-ZFCMKM6M.js +18 -0
- package/dist/docmeta.js +9 -9
- package/dist/github-7ZCH5PSO.js +26 -0
- package/dist/github-POT6724K.js +31 -0
- package/dist/host-6GURH5HE.js +23 -0
- package/dist/index.d.ts +72 -4
- package/dist/index.js +176 -166
- package/dist/index.js.map +1 -1
- package/dist/{json-4DCKDGUV.js → json-KT2K62L5.js} +12 -13
- package/dist/lint-5AQ5C2BV.js +29 -0
- package/dist/list-5JZKX2NK.js +28 -0
- package/dist/list-SWPFXQMU.js +30 -0
- package/dist/{page-QEDB7EVU.js → page-UPSXRP75.js} +11 -12
- package/dist/prepare-6GNEWCNH.js +155 -0
- package/dist/prepare-6GNEWCNH.js.map +1 -0
- package/dist/{pretty-SQX33UM2.js → pretty-Q6FQAOK4.js} +13 -14
- package/dist/{pretty-5DGEBZSV.js → pretty-SQRPFPSQ.js} +12 -13
- package/dist/provider-AMGRDY6V.js +34 -0
- package/dist/release-IECYCLTD.js +36 -0
- package/dist/release-IECYCLTD.js.map +1 -0
- package/dist/{reporters-OHSI3RMV.js → reporters-45MMC2ID.js} +11 -12
- package/dist/{reporters-YELDDYJD.js → reporters-XUV4MM64.js} +7 -5
- package/dist/reporters-XUV4MM64.js.map +1 -0
- package/dist/run-KMWY2IAN.js +32 -0
- package/dist/run-KMWY2IAN.js.map +1 -0
- package/dist/{term-2WMDZ4PY.js → term-PCJUCWNF.js} +16 -17
- package/dist/{term-2WMDZ4PY.js.map → term-PCJUCWNF.js.map} +1 -1
- package/dist/validate-IMNCZNWG.js +17 -0
- package/dist/validate-IMNCZNWG.js.map +1 -0
- package/package.json +2 -2
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/plugin/manni/hooks/hooks.json +47 -0
- package/dist/batch-GOE3UXOD.js +0 -36
- package/dist/build-M7BMEHL2.js +0 -33
- package/dist/calibrate-6RRAILUY.js +0 -31
- package/dist/capture-RJPQ6B4N.js +0 -32
- package/dist/check-BQD64X3N.js +0 -27
- package/dist/chunk-3R5Y3XOL.js.map +0 -1
- package/dist/chunk-3YHAYXBC.js +0 -164
- package/dist/chunk-3YHAYXBC.js.map +0 -1
- package/dist/chunk-HVFR66GQ.js.map +0 -1
- package/dist/chunk-LJ2RDH3F.js.map +0 -1
- package/dist/chunk-NAZRLJAK.js +0 -3090
- package/dist/chunk-NAZRLJAK.js.map +0 -1
- package/dist/chunk-Q35YIDBV.js.map +0 -1
- package/dist/chunk-QCBICNKC.js.map +0 -1
- package/dist/chunk-SJO2FTPG.js.map +0 -1
- package/dist/chunk-TU2WUBVZ.js +0 -2
- package/dist/cli-TYGOHJOJ.js.map +0 -1
- package/dist/github-3WMSTNTK.js +0 -32
- package/dist/github-OXSJQ4PS.js +0 -27
- package/dist/lint-YDHVVZHT.js +0 -30
- package/dist/list-3ZO2NIYO.js +0 -31
- package/dist/list-EJH6VAOW.js +0 -29
- package/dist/run-IP6NZHAG.js +0 -33
- package/dist/validate-G6HH3YXL.js +0 -16
- /package/dist/{batch-GOE3UXOD.js.map → batch-OZOACN4G.js.map} +0 -0
- /package/dist/{build-M7BMEHL2.js.map → build-Z5VOVADH.js.map} +0 -0
- /package/dist/{calibrate-6RRAILUY.js.map → calibrate-TZYT42DR.js.map} +0 -0
- /package/dist/{capture-RJPQ6B4N.js.map → capture-SL4MNDDP.js.map} +0 -0
- /package/dist/{check-BQD64X3N.js.map → check-5OMA42Y6.js.map} +0 -0
- /package/dist/{check-ESNS7ZBC.js.map → check-OAJWC3WD.js.map} +0 -0
- /package/dist/{check-RQIIIURI.js.map → check-PKY7KTQE.js.map} +0 -0
- /package/dist/{chunk-F2OVHJNC.js.map → chunk-2JFPBZJK.js.map} +0 -0
- /package/dist/{chunk-CP4ZM43V.js.map → chunk-2KRB3EG7.js.map} +0 -0
- /package/dist/{chunk-HQFVJDEN.js.map → chunk-3NWX34ZI.js.map} +0 -0
- /package/dist/{chunk-TCC737IS.js.map → chunk-5G53XATW.js.map} +0 -0
- /package/dist/{chunk-SNEHX4VE.js.map → chunk-733Y2STG.js.map} +0 -0
- /package/dist/{chunk-ELZ2IIOF.js.map → chunk-73TC4DYH.js.map} +0 -0
- /package/dist/{chunk-QY3DOZMS.js.map → chunk-7WRP5FFP.js.map} +0 -0
- /package/dist/{chunk-C6HZ6C5T.js.map → chunk-C3B2VJNC.js.map} +0 -0
- /package/dist/{chunk-NN4ASVTY.js.map → chunk-C73RSUIC.js.map} +0 -0
- /package/dist/{chunk-GAZFKZHM.js.map → chunk-DC5DDJVE.js.map} +0 -0
- /package/dist/{chunk-52PDIZKV.js.map → chunk-ECVQNGTH.js.map} +0 -0
- /package/dist/{chunk-XWFNO5PO.js.map → chunk-GAJIJKET.js.map} +0 -0
- /package/dist/{chunk-K3U5F2QT.js.map → chunk-M6GSS72T.js.map} +0 -0
- /package/dist/{chunk-UK2I3ACY.js.map → chunk-MHM4QEBV.js.map} +0 -0
- /package/dist/{chunk-OIZ3XMKB.js.map → chunk-MWBJ3KTF.js.map} +0 -0
- /package/dist/{chunk-MZC62HPZ.js.map → chunk-NBMPHD4I.js.map} +0 -0
- /package/dist/{chunk-DX32P2DT.js.map → chunk-NTWW7J76.js.map} +0 -0
- /package/dist/{chunk-JQU7FHYH.js.map → chunk-QW77LCOU.js.map} +0 -0
- /package/dist/{chunk-IA7BMIPE.js.map → chunk-SM6NOMJS.js.map} +0 -0
- /package/dist/{chunk-NGK3N6F2.js.map → chunk-T6SYGLWR.js.map} +0 -0
- /package/dist/{chunk-TVIX653Q.js.map → chunk-TLXE7LYK.js.map} +0 -0
- /package/dist/{chunk-TA42STP7.js.map → chunk-UAKNZVCD.js.map} +0 -0
- /package/dist/{chunk-C3KI45RF.js.map → chunk-UJSGS2Y5.js.map} +0 -0
- /package/dist/{chunk-BOIF2XGG.js.map → chunk-XXDXIIQ4.js.map} +0 -0
- /package/dist/{cli-QKIJOXGW.js.map → cli-UO4NHIB3.js.map} +0 -0
- /package/dist/{chunk-TU2WUBVZ.js.map → cli-YHYDLQAF.js.map} +0 -0
- /package/dist/{cli-YW6VWBHX.js.map → config-P4HDNTYD.js.map} +0 -0
- /package/dist/{github-3WMSTNTK.js.map → conformance-ZFCMKM6M.js.map} +0 -0
- /package/dist/{github-OXSJQ4PS.js.map → github-7ZCH5PSO.js.map} +0 -0
- /package/dist/{json-4DCKDGUV.js.map → github-POT6724K.js.map} +0 -0
- /package/dist/{lint-YDHVVZHT.js.map → host-6GURH5HE.js.map} +0 -0
- /package/dist/{list-3ZO2NIYO.js.map → json-KT2K62L5.js.map} +0 -0
- /package/dist/{list-EJH6VAOW.js.map → lint-5AQ5C2BV.js.map} +0 -0
- /package/dist/{page-QEDB7EVU.js.map → list-5JZKX2NK.js.map} +0 -0
- /package/dist/{pretty-5DGEBZSV.js.map → list-SWPFXQMU.js.map} +0 -0
- /package/dist/{pretty-SQX33UM2.js.map → page-UPSXRP75.js.map} +0 -0
- /package/dist/{reporters-OHSI3RMV.js.map → pretty-Q6FQAOK4.js.map} +0 -0
- /package/dist/{reporters-YELDDYJD.js.map → pretty-SQRPFPSQ.js.map} +0 -0
- /package/dist/{run-IP6NZHAG.js.map → provider-AMGRDY6V.js.map} +0 -0
- /package/dist/{validate-G6HH3YXL.js.map → reporters-45MMC2ID.js.map} +0 -0
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
paragraphAfter,
|
|
16
16
|
parseSrc,
|
|
17
17
|
ruleId
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-MHM4QEBV.js";
|
|
19
19
|
import {
|
|
20
20
|
findWindows,
|
|
21
21
|
hashLines,
|
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
spellLines,
|
|
25
25
|
toBodyLines,
|
|
26
26
|
toFileLines
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-TUAAR3MM.js";
|
|
28
28
|
import {
|
|
29
29
|
isErrorSeverity
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-KCOFEWV5.js";
|
|
31
31
|
|
|
32
32
|
// src/cite/core/spell.ts
|
|
33
33
|
function shortPin(integrity) {
|
|
@@ -619,4 +619,4 @@ export {
|
|
|
619
619
|
errorSite,
|
|
620
620
|
toValidationResult
|
|
621
621
|
};
|
|
622
|
-
//# sourceMappingURL=chunk-
|
|
622
|
+
//# sourceMappingURL=chunk-UAKNZVCD.js.map
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
REPORT_FORMAT_LIST,
|
|
17
17
|
renderJunit
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-5G53XATW.js";
|
|
19
19
|
import {
|
|
20
20
|
isErrorSeverity
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KCOFEWV5.js";
|
|
22
22
|
import {
|
|
23
23
|
palette
|
|
24
24
|
} from "./chunk-RNVYGWIC.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "./chunk-MNH7QO4Z.js";
|
|
29
29
|
import {
|
|
30
30
|
package_default
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-PCFG7E6B.js";
|
|
32
32
|
|
|
33
33
|
// src/lint/core/rule-id.ts
|
|
34
34
|
var RULE_ID_PREFIX = "manni:lint";
|
|
@@ -633,4 +633,4 @@ export {
|
|
|
633
633
|
renderTemplates,
|
|
634
634
|
renderTools
|
|
635
635
|
};
|
|
636
|
-
//# sourceMappingURL=chunk-
|
|
636
|
+
//# sourceMappingURL=chunk-UJSGS2Y5.js.map
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
assertProviderSelection,
|
|
4
3
|
renderCalibration,
|
|
5
4
|
resolveBatchTraces,
|
|
6
|
-
runBatch
|
|
7
|
-
|
|
8
|
-
} from "./chunk-SJO2FTPG.js";
|
|
5
|
+
runBatch
|
|
6
|
+
} from "./chunk-PL4UAF4S.js";
|
|
9
7
|
import {
|
|
10
8
|
discoverConfig,
|
|
11
9
|
labelKey,
|
|
12
10
|
loadLabels
|
|
13
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XPEWUK7J.js";
|
|
12
|
+
import {
|
|
13
|
+
assertProviderSelection,
|
|
14
|
+
selectProvider
|
|
15
|
+
} from "./chunk-BWCWTHYS.js";
|
|
14
16
|
import {
|
|
15
17
|
TracevalsError
|
|
16
18
|
} from "./chunk-UKZNLVJW.js";
|
|
@@ -376,4 +378,4 @@ export {
|
|
|
376
378
|
score,
|
|
377
379
|
runCalibrate
|
|
378
380
|
};
|
|
379
|
-
//# sourceMappingURL=chunk-
|
|
381
|
+
//# sourceMappingURL=chunk-UWQ2UVHE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tracevals/commands/calibrate.ts","../src/tracevals/calibrate/score.ts"],"sourcesContent":["/**\n * `manni tracevals calibrate <traces...> --labels <file>` (ADR 01022).\n *\n * The tool could report what it decided; it could not report whether it was\n * right. This command runs the batch (ADR 01018), joins the results against a\n * human's answers, and reports the three numbers the calibration page used to\n * ask people to count by hand — false passes, false fails, and review volume —\n * plus which eval disagreed.\n *\n * `--sweep` costs no inference beyond the one run it already made: a judged\n * eval's consensus carries the whole `JudgeRun[]`, so a different zone\n * threshold or a smaller ensemble is arithmetic over verdicts already on disk.\n * Read-only throughout — labels, traces, and artifacts are never written.\n */\nimport { writeFile } from \"node:fs/promises\";\nimport { basename, resolve } from \"node:path\";\nimport { discoverConfig } from \"../core/config.js\";\nimport { assertProviderSelection, selectProvider } from \"../judge/provider.js\";\nimport { renderCalibration, type ReportFormat } from \"../reporters/index.js\";\nimport { TracevalsError, type EvalResult } from \"../types.js\";\nimport { loadLabels, type EvalLabel } from \"../calibrate/labels.js\";\nimport { joinLabels, score, type AmbiguousLabel } from \"../calibrate/score.js\";\nimport type {\n CalibrationGate,\n CalibrationReport,\n JudgeSetting,\n SweepCell,\n} from \"../calibrate/types.js\";\nimport {\n resolveBatchTraces,\n runBatch,\n type BatchCommandOptions,\n} from \"./batch.js\";\n\nexport interface CalibrateCommandOptions extends BatchCommandOptions {\n /** Overrides `calibrate.labels`; resolved against the working directory. */\n labels?: string;\n /** Re-score the corpus across the configured grid. */\n sweep?: boolean;\n /** Thresholds; undefined defers to the config, which defaults to no gate. */\n maxFalsePass?: number;\n maxFalseFail?: number;\n maxReview?: number;\n}\n\nexport interface CalibrateCommandResult {\n report: CalibrationReport;\n rendered: string;\n}\n\n/** Largest ensemble the sweep needs cached, so every cell can be re-scored. */\nfunction sweepDepth(grid: number[], baseline: number): number {\n return Math.max(baseline, ...grid);\n}\n\nexport async function runCalibrate(\n options: CalibrateCommandOptions,\n): Promise<CalibrateCommandResult> {\n const start = Date.now();\n const { config, dir: configDir } = await discoverConfig(\n options.configDir ?? process.cwd(),\n {\n ...(options.config === undefined ? {} : { configPath: options.config }),\n ...(options.noConfig === undefined ? {} : { noConfig: options.noConfig }),\n },\n );\n\n // Before the labels file is read: a contradictory `--local --provider`, an\n // unknown provider name, or a model with no provider to own it is a usage\n // error, and a usage error must not arrive behind an unrelated one about a\n // file the run would never have needed.\n assertProviderSelection(\n selectProvider(config, {\n ...(options.provider !== undefined ? { provider: options.provider } : {}),\n ...(options.model !== undefined ? { model: options.model } : {}),\n ...(options.local !== undefined ? { local: options.local } : {}),\n }),\n );\n\n // A flag overrides the config rather than bypassing it (CLAUDE.md,\n // \"Config <-> CLI flags\"). An explicit --labels is a working-directory path,\n // the way every other path argument on the CLI is; the config default is a\n // project path and resolves against the config file's directory.\n const labelsFile =\n options.labels !== undefined\n ? resolve(options.labels)\n : resolve(configDir, config.calibrate.labels);\n const labels = await loadLabels(labelsFile);\n\n // Resolve the corpus once and hand the list to the batch, so the membership\n // check below cannot disagree with what actually ran.\n const corpus = (await resolveBatchTraces(options)).map((f) => resolve(f));\n assertLabelsInCorpus(labels, corpus, labelsFile);\n\n const baseline: JudgeSetting = {\n ensembleRuns: options.runs ?? config.judge.ensembleRuns,\n zones: config.judge.zones,\n };\n const grid = config.calibrate.sweep;\n // With --sweep the corpus is judged once at the deepest ensemble the grid\n // asks for, and every cell — the baseline included — is re-scored from those\n // runs. Judging at the configured depth instead would make the larger cells\n // unscoreable, and judging per cell would multiply the bill by the grid.\n const depth =\n options.sweep === true\n ? sweepDepth(grid.ensembleRuns, baseline.ensembleRuns)\n : baseline.ensembleRuns;\n\n const batch = await runBatch({\n ...options,\n traces: corpus,\n allProjects: undefined,\n newerThan: undefined,\n limit: undefined,\n runs: depth,\n // The calibration report is the deliverable; rendering the batch as well\n // would write the wrong thing to --output.\n format: \"json\",\n output: undefined,\n });\n\n const all: EvalResult[] = [];\n for (const outcome of batch.outcomes) {\n if (\"report\" in outcome) all.push(...outcome.report.evalResults);\n }\n const { joined, unmatched, ambiguous } = joinLabels(batch.outcomes, labels);\n assertNoUnmatched(unmatched, batch, labelsFile);\n assertNoAmbiguous(ambiguous, labelsFile);\n\n // Without a sweep the reported outcomes are used verbatim, so the headline\n // numbers are exactly the verdicts `run` produced. With one, the corpus was\n // judged deeper than the configured ensemble, so the baseline row has to be\n // re-scored back down to it.\n const scored = score(\n { joined, all },\n options.sweep === true ? baseline : undefined,\n );\n\n const sweep =\n options.sweep === true\n ? buildSweep({ joined, all }, baseline, grid)\n : undefined;\n\n const gates = buildGates(scored.counts, {\n falsePass: options.maxFalsePass ?? config.calibrate.maxFalsePass,\n falseFail: options.maxFalseFail ?? config.calibrate.maxFalseFail,\n review: options.maxReview ?? config.calibrate.maxReview,\n });\n\n const warnings = [...batch.report.warnings];\n if (scored.counts.insufficient > 0) {\n warnings.push(\n `${scored.counts.insufficient} labelled eval(s) had fewer cached runs than the setting asks for and were left at their reported outcome`,\n );\n }\n\n // A measurement that measured nothing must not report a pass.\n //\n // Every label joining to a `skipped` result — `--deterministic-only` over\n // ai-graded labels, a budget exhausted on the first trace — leaves `scored`\n // at 0, `agreement` at null, and each threshold reading \"0 of at most N, not\n // exceeded\". The gates are not lying; the denominator behind them is empty.\n // Saying so is the report's job, and exit 0 would say the opposite.\n const measuredNothing = scored.counts.scored === 0;\n if (measuredNothing) {\n warnings.push(\n `no labelled eval produced evidence: ${scored.counts.labels} label(s) all joined to a skipped result, ` +\n `so agreement and every --max-* threshold are computed over an empty denominator`,\n );\n }\n\n const report: CalibrationReport = {\n labelsFile,\n corpus,\n setting: baseline,\n counts: scored.counts,\n disagreements: scored.disagreements,\n unscored: scored.unscored,\n ...(sweep !== undefined ? { sweep } : {}),\n gates,\n batch: batch.report,\n warnings,\n // A calibration run is a measurement, not a gate: disagreement is the\n // finding, not a failure. Exit 1 only when a threshold was asked for and\n // missed, or when the measurement itself is incomplete — a trace lost, the\n // shared judge budget cut the corpus short, or nothing scored at all. An\n // incomplete number presented as a clean one is worse than no number.\n exitCode:\n gates.some((g) => g.exceeded) ||\n batch.report.summary.tracesErrored > 0 ||\n batch.report.budget !== undefined ||\n measuredNothing\n ? 1\n : 0,\n turns: batch.report.turns,\n durationMs: Date.now() - start,\n };\n\n const rendered = renderCalibration(report, options.format ?? \"pretty\", {\n color: options.color === true,\n });\n if (options.output) await writeFile(options.output, rendered, \"utf-8\");\n return { report, rendered };\n}\n\n/**\n * Every label must name a trace this run evaluates. Checked before the batch,\n * because the alternative is paying for a corpus and then being told the file\n * was misspelled.\n */\nfunction assertLabelsInCorpus(\n labels: EvalLabel[],\n corpus: string[],\n labelsFile: string,\n): void {\n const known = new Set(corpus);\n const missing = [...new Set(labels.filter((l) => !known.has(l.traceFile)).map((l) => l.trace))];\n if (missing.length === 0) return;\n throw new TracevalsError(\n `${labelsFile}: label(s) name trace(s) not in the corpus: ${missing.join(\", \")}. ` +\n `The corpus is: ${corpus.map((f) => basename(f)).join(\", \")}`,\n );\n}\n\n/**\n * A label that matched nothing is an error, not a quiet omission.\n *\n * It has to be: a typo'd eval id contributes to no count, so the report would\n * read as a cleaner corpus than it is — the same silent-nothing failure the\n * empty-selector rule exists to prevent (ADR 01018).\n */\nfunction assertNoUnmatched(\n unmatched: EvalLabel[],\n batch: { outcomes: { file: string; report?: { evalResults: EvalResult[] } }[] },\n labelsFile: string,\n): void {\n if (unmatched.length === 0) return;\n const lines = unmatched.map((label) => {\n const outcome = batch.outcomes.find((o) => o.file === label.traceFile);\n const results = outcome?.report?.evalResults ?? [];\n const sameArtifact = results.filter(\n (r) => r.artifactName === label.artifact,\n );\n const hint =\n sameArtifact.length > 0\n ? `that trace evaluated ${sameArtifact.map((r) => r.evalName).sort().join(\", \")} for \"${label.artifact}\"`\n : `that trace resolved no artifact named \"${label.artifact}\" — it evaluated ${\n [...new Set(results.map((r) => r.artifactName))].sort().join(\", \") ||\n \"nothing\"\n }`;\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} (${hint})`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) matched no eval result:\\n${lines.join(\"\\n\")}`,\n );\n}\n\n/**\n * A label that omits `type:` over a name two artifact kinds both answer cannot\n * be resolved, and must not be guessed. Binding it to whichever result was\n * indexed last would score agreement against a verdict belonging to a\n * different artifact — the report would read as measured when it is wrong,\n * which is the one thing this command exists not to do.\n *\n * Operational, like `assertNoUnmatched`: the labels file is under-specified,\n * so this is exit 2 (bad input), not exit 1 (a check failed).\n */\nfunction assertNoAmbiguous(\n ambiguous: AmbiguousLabel[],\n labelsFile: string,\n): void {\n if (ambiguous.length === 0) return;\n const lines = ambiguous.map(({ label, candidates }) => {\n const kinds = [...new Set(candidates.map((r) => r.artifactType))].sort();\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} matches ${kinds.join(\" and \")} — add \\`type:\\` to say which`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) name an artifact two kinds answer:\\n${lines.join(\"\\n\")}`,\n );\n}\n\nfunction buildGates(\n counts: { falsePass: number; falseFail: number; reviewVolume: number },\n limits: { falsePass?: number; falseFail?: number; review?: number },\n): CalibrationGate[] {\n const gates: CalibrationGate[] = [];\n const add = (\n name: CalibrationGate[\"name\"],\n limit: number | undefined,\n actual: number,\n ): void => {\n if (limit === undefined) return;\n gates.push({ name, limit, actual, exceeded: actual > limit });\n };\n add(\"falsePass\", limits.falsePass, counts.falsePass);\n add(\"falseFail\", limits.falseFail, counts.falseFail);\n add(\"review\", limits.review, counts.reviewVolume);\n return gates;\n}\n\n/**\n * One axis at a time, every other knob held at its configured value.\n *\n * A full cross-product would be dozens of rows and would answer a question\n * nobody asked; calibration is \"move one knob, re-run, recount\", and the\n * per-axis shape is what makes a row readable as *the effect of that knob*.\n */\nfunction buildSweep(\n input: Parameters<typeof score>[0],\n baseline: JudgeSetting,\n grid: { ensembleRuns: number[]; autoPass: number[]; autoFail: number[] },\n): SweepCell[] {\n const cell = (\n axis: SweepCell[\"axis\"],\n value: number,\n setting: JudgeSetting,\n ): SweepCell => ({\n axis,\n value,\n setting,\n counts: score(input, setting).counts,\n });\n\n const cells: SweepCell[] = [\n cell(\"baseline\", baseline.ensembleRuns, baseline),\n ];\n for (const runs of [...grid.ensembleRuns].sort((a, b) => a - b)) {\n cells.push(\n cell(\"ensembleRuns\", runs, { ensembleRuns: runs, zones: baseline.zones }),\n );\n }\n for (const autoPass of [...grid.autoPass].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoPass\", autoPass, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass, autoFail: baseline.zones.autoFail },\n }),\n );\n }\n for (const autoFail of [...grid.autoFail].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoFail\", autoFail, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass: baseline.zones.autoPass, autoFail },\n }),\n );\n }\n return cells;\n}\n\nexport type { ReportFormat };\n","/**\n * Joining labels to results, and re-scoring cached verdicts (ADR 01022).\n *\n * Everything here is pure: results and labels in, counts out. That is what\n * makes a sweep free. A judged eval's `ConsensusResult` carries the whole\n * `JudgeRun[]` it was built from, so a different zone threshold — or a smaller\n * ensemble — is a fresh call to `computeConsensus`/`zoneFor` over runs the\n * corpus already produced, not a fresh call to a provider.\n */\nimport { computeConsensus, zoneFor } from \"@hawkeyexl/inference\";\nimport type { BatchOutcome } from \"../aggregate.js\";\nimport type { EvalResult, Outcome } from \"../types.js\";\nimport { labelKey, type EvalLabel, type LabelOutcome } from \"./labels.js\";\nimport type {\n CalibrationCounts,\n Disagreement,\n JoinKind,\n JudgeSetting,\n} from \"./types.js\";\n\n/**\n * How one label met one result, over the full (expected × actual) grid.\n *\n * The order matters. `skipped` and `error` are tested *first*, so a labelled\n * eval that produced no evidence can never be counted as agreement — the same\n * rule that keeps an errored judge run from becoming a silent pass.\n */\nexport function classify(expected: LabelOutcome, actual: Outcome): JoinKind {\n if (actual === \"skipped\") return \"skipped\";\n if (actual === \"error\") return \"error\";\n if (actual === expected) return \"agree\";\n if (expected === \"fail\" && actual === \"pass\") return \"false-pass\";\n if (expected === \"pass\" && actual === \"fail\") return \"false-fail\";\n if (actual === \"needs-review\") return \"review\";\n return \"missed-review\";\n}\n\nexport interface JoinedLabel {\n label: EvalLabel;\n result: EvalResult;\n}\n\nexport interface JoinResult {\n joined: JoinedLabel[];\n /**\n * Labels that matched no result. Never silently dropped: a typo'd eval id\n * would otherwise deflate every count on the report by one.\n */\n unmatched: EvalLabel[];\n /**\n * Labels that omit `type:` over a name two artifact kinds both answer. Never\n * bound to one of them: a skill and the slash command that types it can share\n * a name in one trace, and picking whichever was indexed last would measure\n * agreement against a verdict belonging to something else — a false pass or\n * false fail reported with total confidence.\n */\n ambiguous: AmbiguousLabel[];\n}\n\n/** An untyped label and every result its name matched. */\nexport interface AmbiguousLabel {\n label: EvalLabel;\n candidates: EvalResult[];\n}\n\n/**\n * Match each label to the result its trace produced. The key is\n * `(trace file, artifact name, eval id)`, with the artifact *type* joining too\n * when the label states one — needed only for a skill and an agent sharing a\n * name, which is legal and does happen.\n */\nexport function joinLabels(\n outcomes: BatchOutcome[],\n labels: EvalLabel[],\n): JoinResult {\n // Typed keys are unique by construction. Untyped ones are not: two artifact\n // kinds may answer the same name, so keep every candidate rather than\n // letting the last one indexed overwrite the rest.\n const byType = new Map<string, EvalResult>();\n const byName = new Map<string, EvalResult[]>();\n for (const outcome of outcomes) {\n if (!(\"report\" in outcome)) continue;\n for (const result of outcome.report.evalResults) {\n byType.set(\n labelKey(\n outcome.file,\n result.artifactName,\n result.evalName,\n result.artifactType,\n ),\n result,\n );\n const key = labelKey(outcome.file, result.artifactName, result.evalName);\n const seen = byName.get(key);\n if (seen === undefined) byName.set(key, [result]);\n else seen.push(result);\n }\n }\n\n const joined: JoinedLabel[] = [];\n const unmatched: EvalLabel[] = [];\n const ambiguous: AmbiguousLabel[] = [];\n for (const label of labels) {\n if (label.type !== undefined) {\n const result = byType.get(\n labelKey(label.traceFile, label.artifact, label.eval, label.type),\n );\n if (result === undefined) unmatched.push(label);\n else joined.push({ label, result });\n continue;\n }\n const candidates =\n byName.get(labelKey(label.traceFile, label.artifact, label.eval)) ?? [];\n const kinds = new Set(candidates.map((r) => r.artifactType));\n if (candidates.length === 0) unmatched.push(label);\n else if (kinds.size > 1) ambiguous.push({ label, candidates });\n else joined.push({ label, result: candidates[0] as EvalResult });\n }\n return { joined, unmatched, ambiguous };\n}\n\nexport interface Rescored {\n outcome: Outcome;\n /** The cache held fewer runs than the setting asks for. */\n insufficient: boolean;\n /**\n * The arithmetic **at this setting**, not at the setting the corpus was\n * judged with. A sweep judges deeper than most of its cells, so reporting\n * the full cached ensemble beside a re-scored verdict would show a reader\n * evidence that does not add up to the outcome next to it.\n */\n consensus?: Disagreement[\"consensus\"];\n}\n\n/** The numbers a reader needs from a consensus, without the run bodies. */\nconst summarise = (\n consensus: NonNullable<EvalResult[\"consensus\"]>,\n): Disagreement[\"consensus\"] => ({\n votes: consensus.votes,\n agreement: consensus.agreement,\n meanConfidence: consensus.meanConfidence,\n runs: consensus.runs.length,\n});\n\n/**\n * What this result would have been at a different setting.\n *\n * A result with no `consensus` — deterministic, `human`, skipped, errored — is\n * returned untouched: zones and ensemble size do not reach it, and pretending\n * otherwise would make a sweep look like it moved things it cannot move.\n *\n * A smaller `ensembleRuns` re-scores the **first k** of the cached runs. That\n * is a sub-sample of the ensemble that was actually drawn, not a fresh k-run\n * ensemble; at temperature 0 the runs are independent samples of one prompt,\n * so it is an honest estimate, and the alternative is paying per grid cell.\n */\nexport function rescore(result: EvalResult, setting: JudgeSetting): Rescored {\n const consensus = result.consensus;\n if (consensus === undefined) {\n return { outcome: result.outcome, insufficient: false };\n }\n const runs = consensus.runs;\n if (runs.length < setting.ensembleRuns) {\n // Never scale up by guessing. Report the setting as unreachable and leave\n // the row at what it actually was.\n return { outcome: result.outcome, insufficient: true };\n }\n const slice = runs.slice(0, setting.ensembleRuns);\n const base = computeConsensus(slice);\n const zone = zoneFor(base, setting.zones);\n return {\n outcome:\n zone === \"auto-pass\" ? \"pass\" : zone === \"auto-fail\" ? \"fail\" : \"needs-review\",\n insufficient: false,\n consensus: {\n votes: base.votes,\n agreement: base.agreement,\n meanConfidence: base.meanConfidence,\n runs: slice.length,\n },\n };\n}\n\nconst emptyCounts = (): CalibrationCounts => ({\n labels: 0,\n scored: 0,\n agree: 0,\n falsePass: 0,\n falseFail: 0,\n review: 0,\n missedReview: 0,\n errored: 0,\n skipped: 0,\n reviewVolume: 0,\n insufficient: 0,\n agreement: null,\n});\n\nexport interface ScoreInput {\n joined: JoinedLabel[];\n /** Every eval result in the corpus, for the corpus-wide review volume. */\n all: EvalResult[];\n}\n\nexport interface ScoreResult {\n counts: CalibrationCounts;\n disagreements: Disagreement[];\n unscored: Disagreement[];\n}\n\n/**\n * Score the joined corpus, optionally at a setting other than the one it ran\n * at. Omit `setting` and the reported outcomes are used verbatim, so the\n * headline numbers of a plain `calibrate` are exactly the verdicts `run`\n * would have produced — no re-derivation standing between the two commands.\n */\nexport function score(input: ScoreInput, setting?: JudgeSetting): ScoreResult {\n const counts = emptyCounts();\n const disagreements: Disagreement[] = [];\n const unscored: Disagreement[] = [];\n\n for (const result of input.all) {\n const outcome =\n setting === undefined ? result.outcome : rescore(result, setting).outcome;\n if (outcome === \"needs-review\") counts.reviewVolume += 1;\n }\n\n for (const { label, result } of input.joined) {\n const scored: Rescored =\n setting === undefined\n ? { outcome: result.outcome, insufficient: false }\n : rescore(result, setting);\n if (scored.insufficient) counts.insufficient += 1;\n const kind = classify(label.expected, scored.outcome);\n counts.labels += 1;\n\n const row: Disagreement = {\n trace: label.traceFile,\n artifactType: result.artifactType,\n artifactName: result.artifactName,\n evalName: result.evalName,\n grader: result.grader,\n expected: label.expected,\n actual: scored.outcome,\n kind,\n ...(label.note !== undefined ? { note: label.note } : {}),\n ...(result.skipReason !== undefined\n ? { skipReason: result.skipReason }\n : {}),\n // At the setting being scored, falling back to the run's own numbers\n // when nothing was re-scored.\n ...(() => {\n const c =\n scored.consensus ??\n (result.consensus !== undefined ? summarise(result.consensus) : undefined);\n return c !== undefined ? { consensus: c } : {};\n })(),\n };\n\n switch (kind) {\n case \"skipped\":\n counts.skipped += 1;\n unscored.push(row);\n break;\n case \"agree\":\n counts.agree += 1;\n counts.scored += 1;\n break;\n case \"false-pass\":\n counts.falsePass += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"false-fail\":\n counts.falseFail += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"review\":\n counts.review += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"missed-review\":\n counts.missedReview += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"error\":\n counts.errored += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n }\n }\n\n counts.agreement = counts.scored > 0 ? counts.agree / counts.scored : null;\n // Byte comparison, so the ubuntu and windows legs order a report the same\n // way and two runs over one corpus stay diffable.\n const byRow = (a: Disagreement, b: Disagreement): number => {\n const ka = `${a.kind} ${a.trace} ${a.artifactName} ${a.evalName}`;\n const kb = `${b.kind} ${b.trace} ${b.artifactName} ${b.evalName}`;\n return ka < kb ? -1 : ka > kb ? 1 : 0;\n };\n disagreements.sort(byRow);\n unscored.sort(byRow);\n return { counts, disagreements, unscored };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/commands/calibrate.ts","../src/tracevals/calibrate/score.ts"],"sourcesContent":["/**\n * `manni tracevals calibrate <traces...> --labels <file>` (ADR 01022).\n *\n * The tool could report what it decided; it could not report whether it was\n * right. This command runs the batch (ADR 01018), joins the results against a\n * human's answers, and reports the three numbers the calibration page used to\n * ask people to count by hand — false passes, false fails, and review volume —\n * plus which eval disagreed.\n *\n * `--sweep` costs no inference beyond the one run it already made: a judged\n * eval's consensus carries the whole `JudgeRun[]`, so a different zone\n * threshold or a smaller ensemble is arithmetic over verdicts already on disk.\n * Read-only throughout — labels, traces, and artifacts are never written.\n */\nimport { writeFile } from \"node:fs/promises\";\nimport { basename, resolve } from \"node:path\";\nimport { discoverConfig } from \"../core/config.js\";\nimport { assertProviderSelection, selectProvider } from \"../judge/provider.js\";\nimport { renderCalibration, type ReportFormat } from \"../reporters/index.js\";\nimport { TracevalsError, type EvalResult } from \"../types.js\";\nimport { loadLabels, type EvalLabel } from \"../calibrate/labels.js\";\nimport { joinLabels, score, type AmbiguousLabel } from \"../calibrate/score.js\";\nimport type {\n CalibrationGate,\n CalibrationReport,\n JudgeSetting,\n SweepCell,\n} from \"../calibrate/types.js\";\nimport {\n resolveBatchTraces,\n runBatch,\n type BatchCommandOptions,\n} from \"./batch.js\";\n\nexport interface CalibrateCommandOptions extends BatchCommandOptions {\n /** Overrides `calibrate.labels`; resolved against the working directory. */\n labels?: string;\n /** Re-score the corpus across the configured grid. */\n sweep?: boolean;\n /** Thresholds; undefined defers to the config, which defaults to no gate. */\n maxFalsePass?: number;\n maxFalseFail?: number;\n maxReview?: number;\n}\n\nexport interface CalibrateCommandResult {\n report: CalibrationReport;\n rendered: string;\n}\n\n/** Largest ensemble the sweep needs cached, so every cell can be re-scored. */\nfunction sweepDepth(grid: number[], baseline: number): number {\n return Math.max(baseline, ...grid);\n}\n\nexport async function runCalibrate(\n options: CalibrateCommandOptions,\n): Promise<CalibrateCommandResult> {\n const start = Date.now();\n const { config, dir: configDir } = await discoverConfig(\n options.configDir ?? process.cwd(),\n {\n ...(options.config === undefined ? {} : { configPath: options.config }),\n ...(options.noConfig === undefined ? {} : { noConfig: options.noConfig }),\n },\n );\n\n // Before the labels file is read: a contradictory `--local --provider`, an\n // unknown provider name, or a model with no provider to own it is a usage\n // error, and a usage error must not arrive behind an unrelated one about a\n // file the run would never have needed.\n assertProviderSelection(\n selectProvider(config, {\n ...(options.provider !== undefined ? { provider: options.provider } : {}),\n ...(options.model !== undefined ? { model: options.model } : {}),\n ...(options.local !== undefined ? { local: options.local } : {}),\n }),\n );\n\n // A flag overrides the config rather than bypassing it (CLAUDE.md,\n // \"Config <-> CLI flags\"). An explicit --labels is a working-directory path,\n // the way every other path argument on the CLI is; the config default is a\n // project path and resolves against the config file's directory.\n const labelsFile =\n options.labels !== undefined\n ? resolve(options.labels)\n : resolve(configDir, config.calibrate.labels);\n const labels = await loadLabels(labelsFile);\n\n // Resolve the corpus once and hand the list to the batch, so the membership\n // check below cannot disagree with what actually ran.\n const corpus = (await resolveBatchTraces(options)).map((f) => resolve(f));\n assertLabelsInCorpus(labels, corpus, labelsFile);\n\n const baseline: JudgeSetting = {\n ensembleRuns: options.runs ?? config.judge.ensembleRuns,\n zones: config.judge.zones,\n };\n const grid = config.calibrate.sweep;\n // With --sweep the corpus is judged once at the deepest ensemble the grid\n // asks for, and every cell — the baseline included — is re-scored from those\n // runs. Judging at the configured depth instead would make the larger cells\n // unscoreable, and judging per cell would multiply the bill by the grid.\n const depth =\n options.sweep === true\n ? sweepDepth(grid.ensembleRuns, baseline.ensembleRuns)\n : baseline.ensembleRuns;\n\n const batch = await runBatch({\n ...options,\n traces: corpus,\n allProjects: undefined,\n newerThan: undefined,\n limit: undefined,\n runs: depth,\n // The calibration report is the deliverable; rendering the batch as well\n // would write the wrong thing to --output.\n format: \"json\",\n output: undefined,\n });\n\n const all: EvalResult[] = [];\n for (const outcome of batch.outcomes) {\n if (\"report\" in outcome) all.push(...outcome.report.evalResults);\n }\n const { joined, unmatched, ambiguous } = joinLabels(batch.outcomes, labels);\n assertNoUnmatched(unmatched, batch, labelsFile);\n assertNoAmbiguous(ambiguous, labelsFile);\n\n // Without a sweep the reported outcomes are used verbatim, so the headline\n // numbers are exactly the verdicts `run` produced. With one, the corpus was\n // judged deeper than the configured ensemble, so the baseline row has to be\n // re-scored back down to it.\n const scored = score(\n { joined, all },\n options.sweep === true ? baseline : undefined,\n );\n\n const sweep =\n options.sweep === true\n ? buildSweep({ joined, all }, baseline, grid)\n : undefined;\n\n const gates = buildGates(scored.counts, {\n falsePass: options.maxFalsePass ?? config.calibrate.maxFalsePass,\n falseFail: options.maxFalseFail ?? config.calibrate.maxFalseFail,\n review: options.maxReview ?? config.calibrate.maxReview,\n });\n\n const warnings = [...batch.report.warnings];\n if (scored.counts.insufficient > 0) {\n warnings.push(\n `${scored.counts.insufficient} labelled eval(s) had fewer cached runs than the setting asks for and were left at their reported outcome`,\n );\n }\n\n // A measurement that measured nothing must not report a pass.\n //\n // Every label joining to a `skipped` result — `--deterministic-only` over\n // ai-graded labels, a budget exhausted on the first trace — leaves `scored`\n // at 0, `agreement` at null, and each threshold reading \"0 of at most N, not\n // exceeded\". The gates are not lying; the denominator behind them is empty.\n // Saying so is the report's job, and exit 0 would say the opposite.\n const measuredNothing = scored.counts.scored === 0;\n if (measuredNothing) {\n warnings.push(\n `no labelled eval produced evidence: ${scored.counts.labels} label(s) all joined to a skipped result, ` +\n `so agreement and every --max-* threshold are computed over an empty denominator`,\n );\n }\n\n const report: CalibrationReport = {\n labelsFile,\n corpus,\n setting: baseline,\n counts: scored.counts,\n disagreements: scored.disagreements,\n unscored: scored.unscored,\n ...(sweep !== undefined ? { sweep } : {}),\n gates,\n batch: batch.report,\n warnings,\n // A calibration run is a measurement, not a gate: disagreement is the\n // finding, not a failure. Exit 1 only when a threshold was asked for and\n // missed, or when the measurement itself is incomplete — a trace lost, the\n // shared judge budget cut the corpus short, or nothing scored at all. An\n // incomplete number presented as a clean one is worse than no number.\n exitCode:\n gates.some((g) => g.exceeded) ||\n batch.report.summary.tracesErrored > 0 ||\n batch.report.budget !== undefined ||\n measuredNothing\n ? 1\n : 0,\n turns: batch.report.turns,\n durationMs: Date.now() - start,\n };\n\n const rendered = renderCalibration(report, options.format ?? \"pretty\", {\n color: options.color === true,\n });\n if (options.output) await writeFile(options.output, rendered, \"utf-8\");\n return { report, rendered };\n}\n\n/**\n * Every label must name a trace this run evaluates. Checked before the batch,\n * because the alternative is paying for a corpus and then being told the file\n * was misspelled.\n */\nfunction assertLabelsInCorpus(\n labels: EvalLabel[],\n corpus: string[],\n labelsFile: string,\n): void {\n const known = new Set(corpus);\n const missing = [...new Set(labels.filter((l) => !known.has(l.traceFile)).map((l) => l.trace))];\n if (missing.length === 0) return;\n throw new TracevalsError(\n `${labelsFile}: label(s) name trace(s) not in the corpus: ${missing.join(\", \")}. ` +\n `The corpus is: ${corpus.map((f) => basename(f)).join(\", \")}`,\n );\n}\n\n/**\n * A label that matched nothing is an error, not a quiet omission.\n *\n * It has to be: a typo'd eval id contributes to no count, so the report would\n * read as a cleaner corpus than it is — the same silent-nothing failure the\n * empty-selector rule exists to prevent (ADR 01018).\n */\nfunction assertNoUnmatched(\n unmatched: EvalLabel[],\n batch: { outcomes: { file: string; report?: { evalResults: EvalResult[] } }[] },\n labelsFile: string,\n): void {\n if (unmatched.length === 0) return;\n const lines = unmatched.map((label) => {\n const outcome = batch.outcomes.find((o) => o.file === label.traceFile);\n const results = outcome?.report?.evalResults ?? [];\n const sameArtifact = results.filter(\n (r) => r.artifactName === label.artifact,\n );\n const hint =\n sameArtifact.length > 0\n ? `that trace evaluated ${sameArtifact.map((r) => r.evalName).sort().join(\", \")} for \"${label.artifact}\"`\n : `that trace resolved no artifact named \"${label.artifact}\" — it evaluated ${\n [...new Set(results.map((r) => r.artifactName))].sort().join(\", \") ||\n \"nothing\"\n }`;\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} (${hint})`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) matched no eval result:\\n${lines.join(\"\\n\")}`,\n );\n}\n\n/**\n * A label that omits `type:` over a name two artifact kinds both answer cannot\n * be resolved, and must not be guessed. Binding it to whichever result was\n * indexed last would score agreement against a verdict belonging to a\n * different artifact — the report would read as measured when it is wrong,\n * which is the one thing this command exists not to do.\n *\n * Operational, like `assertNoUnmatched`: the labels file is under-specified,\n * so this is exit 2 (bad input), not exit 1 (a check failed).\n */\nfunction assertNoAmbiguous(\n ambiguous: AmbiguousLabel[],\n labelsFile: string,\n): void {\n if (ambiguous.length === 0) return;\n const lines = ambiguous.map(({ label, candidates }) => {\n const kinds = [...new Set(candidates.map((r) => r.artifactType))].sort();\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} matches ${kinds.join(\" and \")} — add \\`type:\\` to say which`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) name an artifact two kinds answer:\\n${lines.join(\"\\n\")}`,\n );\n}\n\nfunction buildGates(\n counts: { falsePass: number; falseFail: number; reviewVolume: number },\n limits: { falsePass?: number; falseFail?: number; review?: number },\n): CalibrationGate[] {\n const gates: CalibrationGate[] = [];\n const add = (\n name: CalibrationGate[\"name\"],\n limit: number | undefined,\n actual: number,\n ): void => {\n if (limit === undefined) return;\n gates.push({ name, limit, actual, exceeded: actual > limit });\n };\n add(\"falsePass\", limits.falsePass, counts.falsePass);\n add(\"falseFail\", limits.falseFail, counts.falseFail);\n add(\"review\", limits.review, counts.reviewVolume);\n return gates;\n}\n\n/**\n * One axis at a time, every other knob held at its configured value.\n *\n * A full cross-product would be dozens of rows and would answer a question\n * nobody asked; calibration is \"move one knob, re-run, recount\", and the\n * per-axis shape is what makes a row readable as *the effect of that knob*.\n */\nfunction buildSweep(\n input: Parameters<typeof score>[0],\n baseline: JudgeSetting,\n grid: { ensembleRuns: number[]; autoPass: number[]; autoFail: number[] },\n): SweepCell[] {\n const cell = (\n axis: SweepCell[\"axis\"],\n value: number,\n setting: JudgeSetting,\n ): SweepCell => ({\n axis,\n value,\n setting,\n counts: score(input, setting).counts,\n });\n\n const cells: SweepCell[] = [\n cell(\"baseline\", baseline.ensembleRuns, baseline),\n ];\n for (const runs of [...grid.ensembleRuns].sort((a, b) => a - b)) {\n cells.push(\n cell(\"ensembleRuns\", runs, { ensembleRuns: runs, zones: baseline.zones }),\n );\n }\n for (const autoPass of [...grid.autoPass].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoPass\", autoPass, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass, autoFail: baseline.zones.autoFail },\n }),\n );\n }\n for (const autoFail of [...grid.autoFail].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoFail\", autoFail, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass: baseline.zones.autoPass, autoFail },\n }),\n );\n }\n return cells;\n}\n\nexport type { ReportFormat };\n","/**\n * Joining labels to results, and re-scoring cached verdicts (ADR 01022).\n *\n * Everything here is pure: results and labels in, counts out. That is what\n * makes a sweep free. A judged eval's `ConsensusResult` carries the whole\n * `JudgeRun[]` it was built from, so a different zone threshold — or a smaller\n * ensemble — is a fresh call to `computeConsensus`/`zoneFor` over runs the\n * corpus already produced, not a fresh call to a provider.\n */\nimport { computeConsensus, zoneFor } from \"@hawkeyexl/inference\";\nimport type { BatchOutcome } from \"../aggregate.js\";\nimport type { EvalResult, Outcome } from \"../types.js\";\nimport { labelKey, type EvalLabel, type LabelOutcome } from \"./labels.js\";\nimport type {\n CalibrationCounts,\n Disagreement,\n JoinKind,\n JudgeSetting,\n} from \"./types.js\";\n\n/**\n * How one label met one result, over the full (expected × actual) grid.\n *\n * The order matters. `skipped` and `error` are tested *first*, so a labelled\n * eval that produced no evidence can never be counted as agreement — the same\n * rule that keeps an errored judge run from becoming a silent pass.\n */\nexport function classify(expected: LabelOutcome, actual: Outcome): JoinKind {\n if (actual === \"skipped\") return \"skipped\";\n if (actual === \"error\") return \"error\";\n if (actual === expected) return \"agree\";\n if (expected === \"fail\" && actual === \"pass\") return \"false-pass\";\n if (expected === \"pass\" && actual === \"fail\") return \"false-fail\";\n if (actual === \"needs-review\") return \"review\";\n return \"missed-review\";\n}\n\nexport interface JoinedLabel {\n label: EvalLabel;\n result: EvalResult;\n}\n\nexport interface JoinResult {\n joined: JoinedLabel[];\n /**\n * Labels that matched no result. Never silently dropped: a typo'd eval id\n * would otherwise deflate every count on the report by one.\n */\n unmatched: EvalLabel[];\n /**\n * Labels that omit `type:` over a name two artifact kinds both answer. Never\n * bound to one of them: a skill and the slash command that types it can share\n * a name in one trace, and picking whichever was indexed last would measure\n * agreement against a verdict belonging to something else — a false pass or\n * false fail reported with total confidence.\n */\n ambiguous: AmbiguousLabel[];\n}\n\n/** An untyped label and every result its name matched. */\nexport interface AmbiguousLabel {\n label: EvalLabel;\n candidates: EvalResult[];\n}\n\n/**\n * Match each label to the result its trace produced. The key is\n * `(trace file, artifact name, eval id)`, with the artifact *type* joining too\n * when the label states one — needed only for a skill and an agent sharing a\n * name, which is legal and does happen.\n */\nexport function joinLabels(\n outcomes: BatchOutcome[],\n labels: EvalLabel[],\n): JoinResult {\n // Typed keys are unique by construction. Untyped ones are not: two artifact\n // kinds may answer the same name, so keep every candidate rather than\n // letting the last one indexed overwrite the rest.\n const byType = new Map<string, EvalResult>();\n const byName = new Map<string, EvalResult[]>();\n for (const outcome of outcomes) {\n if (!(\"report\" in outcome)) continue;\n for (const result of outcome.report.evalResults) {\n byType.set(\n labelKey(\n outcome.file,\n result.artifactName,\n result.evalName,\n result.artifactType,\n ),\n result,\n );\n const key = labelKey(outcome.file, result.artifactName, result.evalName);\n const seen = byName.get(key);\n if (seen === undefined) byName.set(key, [result]);\n else seen.push(result);\n }\n }\n\n const joined: JoinedLabel[] = [];\n const unmatched: EvalLabel[] = [];\n const ambiguous: AmbiguousLabel[] = [];\n for (const label of labels) {\n if (label.type !== undefined) {\n const result = byType.get(\n labelKey(label.traceFile, label.artifact, label.eval, label.type),\n );\n if (result === undefined) unmatched.push(label);\n else joined.push({ label, result });\n continue;\n }\n const candidates =\n byName.get(labelKey(label.traceFile, label.artifact, label.eval)) ?? [];\n const kinds = new Set(candidates.map((r) => r.artifactType));\n if (candidates.length === 0) unmatched.push(label);\n else if (kinds.size > 1) ambiguous.push({ label, candidates });\n else joined.push({ label, result: candidates[0] as EvalResult });\n }\n return { joined, unmatched, ambiguous };\n}\n\nexport interface Rescored {\n outcome: Outcome;\n /** The cache held fewer runs than the setting asks for. */\n insufficient: boolean;\n /**\n * The arithmetic **at this setting**, not at the setting the corpus was\n * judged with. A sweep judges deeper than most of its cells, so reporting\n * the full cached ensemble beside a re-scored verdict would show a reader\n * evidence that does not add up to the outcome next to it.\n */\n consensus?: Disagreement[\"consensus\"];\n}\n\n/** The numbers a reader needs from a consensus, without the run bodies. */\nconst summarise = (\n consensus: NonNullable<EvalResult[\"consensus\"]>,\n): Disagreement[\"consensus\"] => ({\n votes: consensus.votes,\n agreement: consensus.agreement,\n meanConfidence: consensus.meanConfidence,\n runs: consensus.runs.length,\n});\n\n/**\n * What this result would have been at a different setting.\n *\n * A result with no `consensus` — deterministic, `human`, skipped, errored — is\n * returned untouched: zones and ensemble size do not reach it, and pretending\n * otherwise would make a sweep look like it moved things it cannot move.\n *\n * A smaller `ensembleRuns` re-scores the **first k** of the cached runs. That\n * is a sub-sample of the ensemble that was actually drawn, not a fresh k-run\n * ensemble; at temperature 0 the runs are independent samples of one prompt,\n * so it is an honest estimate, and the alternative is paying per grid cell.\n */\nexport function rescore(result: EvalResult, setting: JudgeSetting): Rescored {\n const consensus = result.consensus;\n if (consensus === undefined) {\n return { outcome: result.outcome, insufficient: false };\n }\n const runs = consensus.runs;\n if (runs.length < setting.ensembleRuns) {\n // Never scale up by guessing. Report the setting as unreachable and leave\n // the row at what it actually was.\n return { outcome: result.outcome, insufficient: true };\n }\n const slice = runs.slice(0, setting.ensembleRuns);\n const base = computeConsensus(slice);\n const zone = zoneFor(base, setting.zones);\n return {\n outcome:\n zone === \"auto-pass\" ? \"pass\" : zone === \"auto-fail\" ? \"fail\" : \"needs-review\",\n insufficient: false,\n consensus: {\n votes: base.votes,\n agreement: base.agreement,\n meanConfidence: base.meanConfidence,\n runs: slice.length,\n },\n };\n}\n\nconst emptyCounts = (): CalibrationCounts => ({\n labels: 0,\n scored: 0,\n agree: 0,\n falsePass: 0,\n falseFail: 0,\n review: 0,\n missedReview: 0,\n errored: 0,\n skipped: 0,\n reviewVolume: 0,\n insufficient: 0,\n agreement: null,\n});\n\nexport interface ScoreInput {\n joined: JoinedLabel[];\n /** Every eval result in the corpus, for the corpus-wide review volume. */\n all: EvalResult[];\n}\n\nexport interface ScoreResult {\n counts: CalibrationCounts;\n disagreements: Disagreement[];\n unscored: Disagreement[];\n}\n\n/**\n * Score the joined corpus, optionally at a setting other than the one it ran\n * at. Omit `setting` and the reported outcomes are used verbatim, so the\n * headline numbers of a plain `calibrate` are exactly the verdicts `run`\n * would have produced — no re-derivation standing between the two commands.\n */\nexport function score(input: ScoreInput, setting?: JudgeSetting): ScoreResult {\n const counts = emptyCounts();\n const disagreements: Disagreement[] = [];\n const unscored: Disagreement[] = [];\n\n for (const result of input.all) {\n const outcome =\n setting === undefined ? result.outcome : rescore(result, setting).outcome;\n if (outcome === \"needs-review\") counts.reviewVolume += 1;\n }\n\n for (const { label, result } of input.joined) {\n const scored: Rescored =\n setting === undefined\n ? { outcome: result.outcome, insufficient: false }\n : rescore(result, setting);\n if (scored.insufficient) counts.insufficient += 1;\n const kind = classify(label.expected, scored.outcome);\n counts.labels += 1;\n\n const row: Disagreement = {\n trace: label.traceFile,\n artifactType: result.artifactType,\n artifactName: result.artifactName,\n evalName: result.evalName,\n grader: result.grader,\n expected: label.expected,\n actual: scored.outcome,\n kind,\n ...(label.note !== undefined ? { note: label.note } : {}),\n ...(result.skipReason !== undefined\n ? { skipReason: result.skipReason }\n : {}),\n // At the setting being scored, falling back to the run's own numbers\n // when nothing was re-scored.\n ...(() => {\n const c =\n scored.consensus ??\n (result.consensus !== undefined ? summarise(result.consensus) : undefined);\n return c !== undefined ? { consensus: c } : {};\n })(),\n };\n\n switch (kind) {\n case \"skipped\":\n counts.skipped += 1;\n unscored.push(row);\n break;\n case \"agree\":\n counts.agree += 1;\n counts.scored += 1;\n break;\n case \"false-pass\":\n counts.falsePass += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"false-fail\":\n counts.falseFail += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"review\":\n counts.review += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"missed-review\":\n counts.missedReview += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"error\":\n counts.errored += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n }\n }\n\n counts.agreement = counts.scored > 0 ? counts.agree / counts.scored : null;\n // Byte comparison, so the ubuntu and windows legs order a report the same\n // way and two runs over one corpus stay diffable.\n const byRow = (a: Disagreement, b: Disagreement): number => {\n const ka = `${a.kind} ${a.trace} ${a.artifactName} ${a.evalName}`;\n const kb = `${b.kind} ${b.trace} ${b.artifactName} ${b.evalName}`;\n return ka < kb ? -1 : ka > kb ? 1 : 0;\n };\n disagreements.sort(byRow);\n unscored.sort(byRow);\n return { counts, disagreements, unscored };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_KEEP_ALIVE_MS
|
|
4
|
+
} from "./chunk-QCHVR4ZM.js";
|
|
5
|
+
|
|
6
|
+
// src/tracevals/rules/host.ts
|
|
7
|
+
import {
|
|
8
|
+
leaseModelHost,
|
|
9
|
+
ModelHostBusyError,
|
|
10
|
+
modelHostStatus,
|
|
11
|
+
releaseModelHost
|
|
12
|
+
} from "@hawkeyexl/inference";
|
|
13
|
+
var QUEUE_WAIT_MS = 12e4;
|
|
14
|
+
var library = {
|
|
15
|
+
lease: leaseModelHost,
|
|
16
|
+
status: modelHostStatus,
|
|
17
|
+
release: releaseModelHost
|
|
18
|
+
};
|
|
19
|
+
function hostSetting(inLoop, sessionId, keepAliveMs) {
|
|
20
|
+
if (!inLoop || sessionId === void 0) return { host: "connect" };
|
|
21
|
+
return {
|
|
22
|
+
host: "spawn",
|
|
23
|
+
session: sessionId,
|
|
24
|
+
keepAlive: keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS,
|
|
25
|
+
hostWaitMs: QUEUE_WAIT_MS
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function warmModel(lease, api = library) {
|
|
29
|
+
try {
|
|
30
|
+
const held = await api.lease({
|
|
31
|
+
model: lease.model,
|
|
32
|
+
session: lease.sessionId,
|
|
33
|
+
keepAlive: lease.keepAliveMs,
|
|
34
|
+
spawn: true,
|
|
35
|
+
hostWaitMs: QUEUE_WAIT_MS,
|
|
36
|
+
...lease.modelsDirectory !== void 0 ? { modelsDirectory: lease.modelsDirectory } : {}
|
|
37
|
+
});
|
|
38
|
+
return held !== null;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function releaseHost(target, api = library) {
|
|
44
|
+
return api.release("all" in target ? { all: true } : { session: target.sessionId });
|
|
45
|
+
}
|
|
46
|
+
async function hostStatus(api = library) {
|
|
47
|
+
const status = await api.status();
|
|
48
|
+
if (status === null) return null;
|
|
49
|
+
return {
|
|
50
|
+
pid: status.pid,
|
|
51
|
+
models: status.models.map(({ model, sessions, idleMs, queued: queued2 }) => ({
|
|
52
|
+
model,
|
|
53
|
+
sessions,
|
|
54
|
+
idleMs,
|
|
55
|
+
queued: queued2
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function hostHolds(model, api = library) {
|
|
60
|
+
try {
|
|
61
|
+
return (await hostStatus(api))?.models.some((m) => m.model === model) ?? false;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function queued(_model, run) {
|
|
67
|
+
try {
|
|
68
|
+
return { busy: false, value: await run() };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ModelHostBusyError || err instanceof Error && err.name === "ModelHostBusyError") {
|
|
71
|
+
return { busy: true };
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
QUEUE_WAIT_MS,
|
|
79
|
+
hostSetting,
|
|
80
|
+
warmModel,
|
|
81
|
+
releaseHost,
|
|
82
|
+
hostStatus,
|
|
83
|
+
hostHolds,
|
|
84
|
+
queued
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=chunk-WQWKYAAK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/rules/host.ts"],"sourcesContent":["/**\n * The model host seam (proposal 0079, \"Concurrency and the model host\").\n *\n * The host is one background process per machine and user that keeps a local\n * model loaded between hooks, with a queue, leases and keep-alive. The\n * inference library owns it. Every host-dependent behavior goes through this\n * module and no other, and the library's three calls are injectable so a test\n * never starts a real host.\n */\nimport {\n leaseModelHost,\n ModelHostBusyError,\n modelHostStatus,\n releaseModelHost,\n type LlamaCppProviderOptions,\n} from \"@hawkeyexl/inference\";\nimport { DEFAULT_KEEP_ALIVE_MS } from \"../../shared/providers.js\";\n\n/** A request that waits this long in the host's queue is withdrawn. */\nexport const QUEUE_WAIT_MS = 120_000;\n\n/** The library's host calls, as this module uses them. */\nexport interface HostApi {\n lease: typeof leaseModelHost;\n status: typeof modelHostStatus;\n release: typeof releaseModelHost;\n}\n\nconst library: HostApi = {\n lease: leaseModelHost,\n status: modelHostStatus,\n release: releaseModelHost,\n};\n\nexport interface ReleaseResult {\n /** Sessions whose lease was returned. */\n released: string[];\n /** Models the host unloaded. */\n unloaded: string[];\n hostStopped: boolean;\n}\n\n/** What `manni status` shows of a running host. */\nexport interface HostStatus {\n pid: number;\n models: { model: string; sessions: number; idleMs: number; queued: number }[];\n}\n\n/** The host fields of a `llama-cpp` provider spec. */\nexport type HostSetting = Pick<\n LlamaCppProviderOptions,\n \"host\" | \"session\" | \"keepAlive\" | \"hostWaitMs\"\n>;\n\n/**\n * How a provider reaches the host. Inside a hook it starts one if none runs,\n * under the session's lease. By hand it uses a running host and never starts\n * one.\n */\nexport function hostSetting(\n inLoop: boolean,\n sessionId: string | undefined,\n keepAliveMs: number | undefined,\n): HostSetting {\n if (!inLoop || sessionId === undefined) return { host: \"connect\" };\n return {\n host: \"spawn\",\n session: sessionId,\n keepAlive: keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS,\n hostWaitMs: QUEUE_WAIT_MS,\n };\n}\n\n/**\n * `prepare` under SessionStart: take the session's lease and load the model,\n * so the first Stop finds it warm. True when the model is now loaded. A host\n * that cannot start or load leaves the model cold, which the first Stop\n * handles, so it never fails `prepare`.\n */\nexport async function warmModel(\n lease: {\n sessionId: string;\n model: string;\n keepAliveMs: number;\n modelsDirectory?: string;\n },\n api: HostApi = library,\n): Promise<boolean> {\n try {\n const held = await api.lease({\n model: lease.model,\n session: lease.sessionId,\n keepAlive: lease.keepAliveMs,\n spawn: true,\n hostWaitMs: QUEUE_WAIT_MS,\n ...(lease.modelsDirectory !== undefined ? { modelsDirectory: lease.modelsDirectory } : {}),\n });\n return held !== null;\n } catch {\n return false;\n }\n}\n\n/** `release`: return one session's lease, or every lease with `all`. */\nexport function releaseHost(\n target: { sessionId: string } | { all: true },\n api: HostApi = library,\n): Promise<ReleaseResult> {\n return api.release(\"all\" in target ? { all: true } : { session: target.sessionId });\n}\n\n/** The running host, or null when there is none. */\nexport async function hostStatus(api: HostApi = library): Promise<HostStatus | null> {\n const status = await api.status();\n if (status === null) return null;\n return {\n pid: status.pid,\n models: status.models.map(({ model, sessions, idleMs, queued }) => ({\n model,\n sessions,\n idleMs,\n queued,\n })),\n };\n}\n\n/**\n * Whether the host already holds `model` loaded. A host that cannot be\n * reached, or that errors, holds nothing as far as a caller can tell.\n */\nexport async function hostHolds(model: string, api: HostApi = library): Promise<boolean> {\n try {\n return (await hostStatus(api))?.models.some((m) => m.model === model) ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * Run a request for `model`. The host's queue lives inside the provider, so\n * `busy` is its `ModelHostBusyError`: the call waited `QUEUE_WAIT_MS` and\n * nothing ran.\n */\nexport async function queued<T>(\n _model: string,\n run: () => Promise<T>,\n): Promise<{ busy: false; value: T } | { busy: true }> {\n try {\n return { busy: false, value: await run() };\n } catch (err) {\n if (err instanceof ModelHostBusyError || (err instanceof Error && err.name === \"ModelHostBusyError\")) {\n return { busy: true };\n }\n throw err;\n }\n}\n"],"mappings":";;;;;;AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAIA,IAAM,gBAAgB;AAS7B,IAAM,UAAmB;AAAA,EACvB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AACX;AA2BO,SAAS,YACd,QACA,WACA,aACa;AACb,MAAI,CAAC,UAAU,cAAc,OAAW,QAAO,EAAE,MAAM,UAAU;AACjE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,WAAW,eAAe;AAAA,IAC1B,YAAY;AAAA,EACd;AACF;AAQA,eAAsB,UACpB,OAMA,MAAe,SACG;AAClB,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,MAAM;AAAA,MAC3B,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,IAC1F,CAAC;AACD,WAAO,SAAS;AAAA,EAClB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,YACd,QACA,MAAe,SACS;AACxB,SAAO,IAAI,QAAQ,SAAS,SAAS,EAAE,KAAK,KAAK,IAAI,EAAE,SAAS,OAAO,UAAU,CAAC;AACpF;AAGA,eAAsB,WAAW,MAAe,SAAqC;AACnF,QAAM,SAAS,MAAM,IAAI,OAAO;AAChC,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO,OAAO,IAAI,CAAC,EAAE,OAAO,UAAU,QAAQ,QAAAA,QAAO,OAAO;AAAA,MAClE;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAAA;AAAA,IACF,EAAE;AAAA,EACJ;AACF;AAMA,eAAsB,UAAU,OAAe,MAAe,SAA2B;AACvF,MAAI;AACF,YAAQ,MAAM,WAAW,GAAG,IAAI,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK;AAAA,EAC3E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,OACpB,QACA,KACqD;AACrD,MAAI;AACF,WAAO,EAAE,MAAM,OAAO,OAAO,MAAM,IAAI,EAAE;AAAA,EAC3C,SAAS,KAAK;AACZ,QAAI,eAAe,sBAAuB,eAAe,SAAS,IAAI,SAAS,sBAAuB;AACpG,aAAO,EAAE,MAAM,KAAK;AAAA,IACtB;AACA,UAAM;AAAA,EACR;AACF;","names":["queued"]}
|
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/shared/duration.ts
|
|
4
|
-
var UNITS = {
|
|
5
|
-
m: 6e4,
|
|
6
|
-
h: 60 * 6e4,
|
|
7
|
-
d: 24 * 60 * 6e4,
|
|
8
|
-
w: 7 * 24 * 60 * 6e4
|
|
9
|
-
};
|
|
10
|
-
function durationMessage(value) {
|
|
11
|
-
return `--newer-than must be a duration such as 30m, 24h, 7d or 2w, got "${value}"`;
|
|
12
|
-
}
|
|
13
|
-
function parseDuration(text, makeError) {
|
|
14
|
-
const match = /^(\d+(?:\.\d+)?)([mhdw])$/.exec(text.trim());
|
|
15
|
-
const count = match?.[1];
|
|
16
|
-
const unit = match?.[2];
|
|
17
|
-
const ms = unit === void 0 ? void 0 : UNITS[unit];
|
|
18
|
-
if (count === void 0 || ms === void 0) {
|
|
19
|
-
throw makeError(durationMessage(text));
|
|
20
|
-
}
|
|
21
|
-
return Number(count) * ms;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
3
|
// src/shared/exec.ts
|
|
25
4
|
import { realExec } from "@hawkeyexl/inference";
|
|
26
5
|
function outputTail(result, maxChars = 400) {
|
|
@@ -41,9 +20,8 @@ function isTurnBudgetSkip(reason) {
|
|
|
41
20
|
export {
|
|
42
21
|
outputTail,
|
|
43
22
|
realExec,
|
|
44
|
-
parseDuration,
|
|
45
23
|
TURN_BUDGET_SKIP,
|
|
46
24
|
turnBudgetSkipReason,
|
|
47
25
|
isTurnBudgetSkip
|
|
48
26
|
};
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
27
|
+
//# sourceMappingURL=chunk-XG6PVB5S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/exec.ts","../src/docevals/judge/budget.ts"],"sourcesContent":["/**\n * Process execution for every grader in the family that runs a program. The\n * implementation lives in `@hawkeyexl/inference`: the judge's subprocess\n * provider and the command graders were running copies of the same\n * cross-spawn wrapper, and the Windows-specific parts (npm `.cmd` shim\n * resolution without `shell: true`, stdin piping past the ~32K command-line\n * limit, and StringDecoder-backed output so multi-byte UTF-8 survives chunk\n * boundaries) are exactly the parts worth having in one place.\n *\n * Shared rather than per domain: `manni docevals` and `manni tracevals` both\n * run `command` evals, and both reach the OS through this one wrapper.\n */\nimport type { ExecResult } from \"@hawkeyexl/inference\";\n\nexport { realExec } from \"@hawkeyexl/inference\";\n\n/** Truncate command output for finding messages. */\nexport function outputTail(result: ExecResult, maxChars = 400): string {\n const text = (result.stderr.trim() || result.stdout.trim()).trim();\n if (text.length <= maxChars) return text;\n return `…${text.slice(-maxChars)}`;\n}\n\n/**\n * How a finished process is described in a finding: \"exited 2\", or the truth\n * when there was no exit code.\n *\n * `code` is null when the process was killed by a signal rather than exiting.\n * \"exited null\" describes nothing a reader can act on, and it is the case a\n * timeout produces — so it is the one worth naming.\n */\nexport function exited(code: number | null): string {\n return code === null ? \"was killed before exiting\" : `exited ${code}`;\n}\n","/**\n * The turn-budget skip marker (ADR 01019), in one place.\n *\n * `judge.ts` produces the skip reason; `core/engine.ts` and `commands/\n * calibrate.ts` both have to tell a budget truncation apart from every other\n * reason an eval went unjudged, because that distinction is what stops a\n * partial run from reporting as a complete one. They did it by matching the\n * bare substring `\"turn budget\"` against a message written in a third file,\n * so a reword in the producer would have left both detectors matching nothing\n * — silently, since the tests that exercise truncation supply the skip reason\n * themselves rather than getting it from the judge.\n *\n * Kept in its own module rather than exported from `judge.ts`: the detectors\n * need the marker, not the judge, and `engine.ts` deliberately takes the judge\n * by injection.\n */\n\n/** The substring every budget-truncation skip reason carries. */\nexport const TURN_BUDGET_SKIP = \"turn budget\";\n\n/** The skip reason recorded for an eval the budget stopped before dispatch. */\nexport function turnBudgetSkipReason(maxTurns: number): string {\n return `judge ${TURN_BUDGET_SKIP} exhausted (${maxTurns})`;\n}\n\n/**\n * Whether a skip reason is the budget's doing rather than the judge's.\n *\n * A type predicate, not a plain boolean: a budget skip always carries a\n * reason, and callers interpolate it into the message they report.\n */\nexport function isTurnBudgetSkip(reason: string | undefined): reason is string {\n return reason !== undefined && reason.includes(TURN_BUDGET_SKIP);\n}\n"],"mappings":";;;AAcA,SAAS,gBAAgB;AAGlB,SAAS,WAAW,QAAoB,WAAW,KAAa;AACrE,QAAM,QAAQ,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,GAAG,KAAK;AACjE,MAAI,KAAK,UAAU,SAAU,QAAO;AACpC,SAAO,SAAI,KAAK,MAAM,CAAC,QAAQ,CAAC;AAClC;;;ACHO,IAAM,mBAAmB;AAGzB,SAAS,qBAAqB,UAA0B;AAC7D,SAAO,SAAS,gBAAgB,eAAe,QAAQ;AACzD;AAQO,SAAS,iBAAiB,QAA8C;AAC7E,SAAO,WAAW,UAAa,OAAO,SAAS,gBAAgB;AACjE;","names":[]}
|