@hawkeyexl/manni 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +1 -1
  2. package/dist/batch-U6S2JUUN.js +41 -0
  3. package/dist/build-4RN6WBCP.js +32 -0
  4. package/dist/calibrate-6VIZYR4L.js +36 -0
  5. package/dist/capture-DP3NXAZE.js +34 -0
  6. package/dist/{check-RQIIIURI.js → check-ERVNGKLC.js} +13 -14
  7. package/dist/check-IWHUPAZM.js +886 -0
  8. package/dist/check-IWHUPAZM.js.map +1 -0
  9. package/dist/{check-ESNS7ZBC.js → check-OAJWC3WD.js} +7 -5
  10. package/dist/check-STHXZIZO.js +26 -0
  11. package/dist/{chunk-TA42STP7.js → chunk-2ZWTSRE5.js} +4 -4
  12. package/dist/{chunk-F2OVHJNC.js → chunk-4Z2MN5FR.js} +11 -11
  13. package/dist/{chunk-HVFR66GQ.js → chunk-5UB47BEV.js} +3280 -283
  14. package/dist/chunk-5UB47BEV.js.map +1 -0
  15. package/dist/{chunk-TCC737IS.js → chunk-5VWDIWOH.js} +4 -4
  16. package/dist/{chunk-B7XUEQF3.js → chunk-6KKZXB2O.js} +7 -5
  17. package/dist/{chunk-B7XUEQF3.js.map → chunk-6KKZXB2O.js.map} +1 -1
  18. package/dist/{chunk-ELZ2IIOF.js → chunk-6URSCTTX.js} +2 -2
  19. package/dist/{chunk-K3U5F2QT.js → chunk-6VDBP5UV.js} +12 -12
  20. package/dist/chunk-7EL5LLKD.js +233 -0
  21. package/dist/chunk-7EL5LLKD.js.map +1 -0
  22. package/dist/{chunk-C3KI45RF.js → chunk-7XZ6INKO.js} +4 -4
  23. package/dist/{chunk-ZX43SRZR.js → chunk-AMEILOVV.js} +8 -6
  24. package/dist/{chunk-ZX43SRZR.js.map → chunk-AMEILOVV.js.map} +1 -1
  25. package/dist/{chunk-SNEHX4VE.js → chunk-B5CMM57M.js} +2 -2
  26. package/dist/chunk-BWCWTHYS.js +146 -0
  27. package/dist/chunk-BWCWTHYS.js.map +1 -0
  28. package/dist/{chunk-QLUMKWQQ.js → chunk-C2TJG5KV.js} +10 -6
  29. package/dist/{chunk-QLUMKWQQ.js.map → chunk-C2TJG5KV.js.map} +1 -1
  30. package/dist/{chunk-GAZFKZHM.js → chunk-CEVU3GFF.js} +6 -6
  31. package/dist/chunk-CI55ABCK.js +502 -0
  32. package/dist/chunk-CI55ABCK.js.map +1 -0
  33. package/dist/{chunk-QCBICNKC.js → chunk-CR3HZERW.js} +202 -54
  34. package/dist/chunk-CR3HZERW.js.map +1 -0
  35. package/dist/chunk-CU57HT2E.js +116 -0
  36. package/dist/chunk-CU57HT2E.js.map +1 -0
  37. package/dist/chunk-CWXZ2MRT.js +222 -0
  38. package/dist/chunk-CWXZ2MRT.js.map +1 -0
  39. package/dist/chunk-D2LBN44V.js +134 -0
  40. package/dist/chunk-D2LBN44V.js.map +1 -0
  41. package/dist/chunk-D6RO54QD.js +674 -0
  42. package/dist/chunk-D6RO54QD.js.map +1 -0
  43. package/dist/{chunk-52PDIZKV.js → chunk-ECVQNGTH.js} +3 -3
  44. package/dist/chunk-FQGLRJGX.js +174 -0
  45. package/dist/chunk-FQGLRJGX.js.map +1 -0
  46. package/dist/{chunk-NGK3N6F2.js → chunk-GCC52YZD.js} +3 -3
  47. package/dist/chunk-HX44GB4Z.js +30 -0
  48. package/dist/chunk-HX44GB4Z.js.map +1 -0
  49. package/dist/{chunk-NN4ASVTY.js → chunk-IIFADWWI.js} +4 -4
  50. package/dist/{chunk-OIZ3XMKB.js → chunk-IOL74S4J.js} +4 -4
  51. package/dist/{chunk-UK2I3ACY.js → chunk-IZBBXKMJ.js} +4 -4
  52. package/dist/{chunk-YA63PVWI.js → chunk-K52CS4KT.js} +12 -10
  53. package/dist/{chunk-YA63PVWI.js.map → chunk-K52CS4KT.js.map} +1 -1
  54. package/dist/{chunk-7OQAU4EA.js → chunk-LDKIIZDW.js} +16 -12
  55. package/dist/{chunk-7OQAU4EA.js.map → chunk-LDKIIZDW.js.map} +1 -1
  56. package/dist/{chunk-QY3DOZMS.js → chunk-LONRQBGV.js} +4 -4
  57. package/dist/{chunk-EL2BZTDL.js → chunk-LXJEAV2V.js} +13 -11
  58. package/dist/{chunk-EL2BZTDL.js.map → chunk-LXJEAV2V.js.map} +1 -1
  59. package/dist/{chunk-XWFNO5PO.js → chunk-MIBWI5H7.js} +3 -3
  60. package/dist/{chunk-527HHQPS.js → chunk-MSMB6XOS.js} +6 -4
  61. package/dist/{chunk-527HHQPS.js.map → chunk-MSMB6XOS.js.map} +1 -1
  62. package/dist/{chunk-C6HZ6C5T.js → chunk-NIFEEIGQ.js} +2 -2
  63. package/dist/{chunk-JQU7FHYH.js → chunk-NZ6TNSCU.js} +10 -10
  64. package/dist/{chunk-3R5Y3XOL.js → chunk-OOEMKOX6.js} +29 -591
  65. package/dist/chunk-OOEMKOX6.js.map +1 -0
  66. package/dist/{chunk-MZC62HPZ.js → chunk-PAOVVHQX.js} +2 -2
  67. package/dist/chunk-PNX43DYW.js +849 -0
  68. package/dist/chunk-PNX43DYW.js.map +1 -0
  69. package/dist/chunk-QCHVR4ZM.js +286 -0
  70. package/dist/chunk-QCHVR4ZM.js.map +1 -0
  71. package/dist/{chunk-SJO2FTPG.js → chunk-QP5X66TO.js} +73 -1304
  72. package/dist/chunk-QP5X66TO.js.map +1 -0
  73. package/dist/{chunk-HQFVJDEN.js → chunk-QQNPVW3J.js} +19 -19
  74. package/dist/chunk-TPVOOTMN.js +532 -0
  75. package/dist/chunk-TPVOOTMN.js.map +1 -0
  76. package/dist/{chunk-IA7BMIPE.js → chunk-UZKQSWHJ.js} +2 -2
  77. package/dist/chunk-WQWKYAAK.js +86 -0
  78. package/dist/chunk-WQWKYAAK.js.map +1 -0
  79. package/dist/{chunk-CP4ZM43V.js → chunk-WTEILBKC.js} +2 -2
  80. package/dist/{chunk-LJ2RDH3F.js → chunk-XG6PVB5S.js} +1 -23
  81. package/dist/chunk-XG6PVB5S.js.map +1 -0
  82. package/dist/{chunk-Q35YIDBV.js → chunk-XKBEHKWO.js} +29 -264
  83. package/dist/chunk-XKBEHKWO.js.map +1 -0
  84. package/dist/{chunk-BOIF2XGG.js → chunk-XXDXIIQ4.js} +2 -2
  85. package/dist/{chunk-6XTQDFGP.js → chunk-YSCZL4U5.js} +3 -3
  86. package/dist/{chunk-6XTQDFGP.js.map → chunk-YSCZL4U5.js.map} +1 -1
  87. package/dist/{chunk-7ZUCEMCY.js → chunk-ZEXGGGLW.js} +9 -11
  88. package/dist/{chunk-7ZUCEMCY.js.map → chunk-ZEXGGGLW.js.map} +1 -1
  89. package/dist/{chunk-DX32P2DT.js → chunk-ZKAKA54B.js} +2 -2
  90. package/dist/{chunk-TVIX653Q.js → chunk-ZKCNPKUO.js} +4 -4
  91. package/dist/{chunk-DE7JSWXV.js → chunk-ZNE25EAR.js} +8 -6
  92. package/dist/{chunk-DE7JSWXV.js.map → chunk-ZNE25EAR.js.map} +1 -1
  93. package/dist/{chunk-FLF6AECQ.js → chunk-ZPP75QWG.js} +17 -15
  94. package/dist/{chunk-FLF6AECQ.js.map → chunk-ZPP75QWG.js.map} +1 -1
  95. package/dist/{cli-PRZHQ3QB.js → cli-37SRAYNE.js} +14 -15
  96. package/dist/{cli-PRZHQ3QB.js.map → cli-37SRAYNE.js.map} +1 -1
  97. package/dist/{cli-TYGOHJOJ.js → cli-HXWYUKGJ.js} +225 -58
  98. package/dist/cli-HXWYUKGJ.js.map +1 -0
  99. package/dist/{cli-HUIMNJT6.js → cli-IRHPPUFJ.js} +19 -20
  100. package/dist/{cli-HUIMNJT6.js.map → cli-IRHPPUFJ.js.map} +1 -1
  101. package/dist/{cli-6PGVHP7Y.js → cli-KIID535Y.js} +17 -19
  102. package/dist/{cli-6PGVHP7Y.js.map → cli-KIID535Y.js.map} +1 -1
  103. package/dist/{cli-AAG3N4NB.js → cli-MTDGQIIV.js} +17 -18
  104. package/dist/{cli-AAG3N4NB.js.map → cli-MTDGQIIV.js.map} +1 -1
  105. package/dist/{cli-E3H6ZP4I.js → cli-NM6FZ54R.js} +5 -3
  106. package/dist/{cli-E3H6ZP4I.js.map → cli-NM6FZ54R.js.map} +1 -1
  107. package/dist/{cli-MHTYPVOD.js → cli-XIODOEUG.js} +113 -20
  108. package/dist/{cli-MHTYPVOD.js.map → cli-XIODOEUG.js.map} +1 -1
  109. package/dist/{cli-YW6VWBHX.js → cli-YL3IDR52.js} +10 -10
  110. package/dist/{cli-QKIJOXGW.js → cli-ZQUJYQBA.js} +17 -17
  111. package/dist/cli.js +11 -11
  112. package/dist/config-O6UBGEDK.js +26 -0
  113. package/dist/conformance-ALLPNGEQ.js +18 -0
  114. package/dist/docmeta.js +9 -9
  115. package/dist/github-UCYLM3DR.js +26 -0
  116. package/dist/github-VEJFO6JJ.js +31 -0
  117. package/dist/host-6GURH5HE.js +23 -0
  118. package/dist/index.d.ts +72 -4
  119. package/dist/index.js +176 -166
  120. package/dist/index.js.map +1 -1
  121. package/dist/{json-4DCKDGUV.js → json-6F52FSXZ.js} +12 -13
  122. package/dist/lint-X2GNINK6.js +29 -0
  123. package/dist/list-DP3O3CWB.js +30 -0
  124. package/dist/list-P77RZFZF.js +28 -0
  125. package/dist/{page-QEDB7EVU.js → page-WQCMBNRD.js} +11 -12
  126. package/dist/prepare-52QOK7M4.js +155 -0
  127. package/dist/prepare-52QOK7M4.js.map +1 -0
  128. package/dist/{pretty-SQX33UM2.js → pretty-5MMMNSWF.js} +13 -14
  129. package/dist/{pretty-5DGEBZSV.js → pretty-H4DQREMW.js} +12 -13
  130. package/dist/provider-AMGRDY6V.js +34 -0
  131. package/dist/release-CTYRHZL4.js +36 -0
  132. package/dist/release-CTYRHZL4.js.map +1 -0
  133. package/dist/{reporters-YELDDYJD.js → reporters-SU3VUKKX.js} +7 -5
  134. package/dist/{reporters-OHSI3RMV.js → reporters-WIHBEMPG.js} +11 -12
  135. package/dist/reporters-WIHBEMPG.js.map +1 -0
  136. package/dist/run-L2DWJ26K.js +32 -0
  137. package/dist/run-L2DWJ26K.js.map +1 -0
  138. package/dist/{term-2WMDZ4PY.js → term-PJIJQZNF.js} +16 -17
  139. package/dist/{term-2WMDZ4PY.js.map → term-PJIJQZNF.js.map} +1 -1
  140. package/dist/validate-XCDSPURG.js +17 -0
  141. package/dist/validate-XCDSPURG.js.map +1 -0
  142. package/package.json +2 -2
  143. package/plugin/manni/.claude-plugin/plugin.json +1 -1
  144. package/plugin/manni/hooks/hooks.json +47 -0
  145. package/dist/batch-GOE3UXOD.js +0 -36
  146. package/dist/build-M7BMEHL2.js +0 -33
  147. package/dist/calibrate-6RRAILUY.js +0 -31
  148. package/dist/capture-RJPQ6B4N.js +0 -32
  149. package/dist/check-BQD64X3N.js +0 -27
  150. package/dist/chunk-3R5Y3XOL.js.map +0 -1
  151. package/dist/chunk-3YHAYXBC.js +0 -164
  152. package/dist/chunk-3YHAYXBC.js.map +0 -1
  153. package/dist/chunk-HVFR66GQ.js.map +0 -1
  154. package/dist/chunk-LJ2RDH3F.js.map +0 -1
  155. package/dist/chunk-NAZRLJAK.js +0 -3090
  156. package/dist/chunk-NAZRLJAK.js.map +0 -1
  157. package/dist/chunk-Q35YIDBV.js.map +0 -1
  158. package/dist/chunk-QCBICNKC.js.map +0 -1
  159. package/dist/chunk-SJO2FTPG.js.map +0 -1
  160. package/dist/chunk-TU2WUBVZ.js +0 -2
  161. package/dist/cli-TYGOHJOJ.js.map +0 -1
  162. package/dist/github-3WMSTNTK.js +0 -32
  163. package/dist/github-OXSJQ4PS.js +0 -27
  164. package/dist/lint-YDHVVZHT.js +0 -30
  165. package/dist/list-3ZO2NIYO.js +0 -31
  166. package/dist/list-EJH6VAOW.js +0 -29
  167. package/dist/run-IP6NZHAG.js +0 -33
  168. package/dist/validate-G6HH3YXL.js +0 -16
  169. /package/dist/{batch-GOE3UXOD.js.map → batch-U6S2JUUN.js.map} +0 -0
  170. /package/dist/{build-M7BMEHL2.js.map → build-4RN6WBCP.js.map} +0 -0
  171. /package/dist/{calibrate-6RRAILUY.js.map → calibrate-6VIZYR4L.js.map} +0 -0
  172. /package/dist/{capture-RJPQ6B4N.js.map → capture-DP3NXAZE.js.map} +0 -0
  173. /package/dist/{check-BQD64X3N.js.map → check-ERVNGKLC.js.map} +0 -0
  174. /package/dist/{check-ESNS7ZBC.js.map → check-OAJWC3WD.js.map} +0 -0
  175. /package/dist/{check-RQIIIURI.js.map → check-STHXZIZO.js.map} +0 -0
  176. /package/dist/{chunk-TA42STP7.js.map → chunk-2ZWTSRE5.js.map} +0 -0
  177. /package/dist/{chunk-F2OVHJNC.js.map → chunk-4Z2MN5FR.js.map} +0 -0
  178. /package/dist/{chunk-TCC737IS.js.map → chunk-5VWDIWOH.js.map} +0 -0
  179. /package/dist/{chunk-ELZ2IIOF.js.map → chunk-6URSCTTX.js.map} +0 -0
  180. /package/dist/{chunk-K3U5F2QT.js.map → chunk-6VDBP5UV.js.map} +0 -0
  181. /package/dist/{chunk-C3KI45RF.js.map → chunk-7XZ6INKO.js.map} +0 -0
  182. /package/dist/{chunk-SNEHX4VE.js.map → chunk-B5CMM57M.js.map} +0 -0
  183. /package/dist/{chunk-GAZFKZHM.js.map → chunk-CEVU3GFF.js.map} +0 -0
  184. /package/dist/{chunk-52PDIZKV.js.map → chunk-ECVQNGTH.js.map} +0 -0
  185. /package/dist/{chunk-NGK3N6F2.js.map → chunk-GCC52YZD.js.map} +0 -0
  186. /package/dist/{chunk-NN4ASVTY.js.map → chunk-IIFADWWI.js.map} +0 -0
  187. /package/dist/{chunk-OIZ3XMKB.js.map → chunk-IOL74S4J.js.map} +0 -0
  188. /package/dist/{chunk-UK2I3ACY.js.map → chunk-IZBBXKMJ.js.map} +0 -0
  189. /package/dist/{chunk-QY3DOZMS.js.map → chunk-LONRQBGV.js.map} +0 -0
  190. /package/dist/{chunk-XWFNO5PO.js.map → chunk-MIBWI5H7.js.map} +0 -0
  191. /package/dist/{chunk-C6HZ6C5T.js.map → chunk-NIFEEIGQ.js.map} +0 -0
  192. /package/dist/{chunk-JQU7FHYH.js.map → chunk-NZ6TNSCU.js.map} +0 -0
  193. /package/dist/{chunk-MZC62HPZ.js.map → chunk-PAOVVHQX.js.map} +0 -0
  194. /package/dist/{chunk-HQFVJDEN.js.map → chunk-QQNPVW3J.js.map} +0 -0
  195. /package/dist/{chunk-IA7BMIPE.js.map → chunk-UZKQSWHJ.js.map} +0 -0
  196. /package/dist/{chunk-CP4ZM43V.js.map → chunk-WTEILBKC.js.map} +0 -0
  197. /package/dist/{chunk-BOIF2XGG.js.map → chunk-XXDXIIQ4.js.map} +0 -0
  198. /package/dist/{chunk-DX32P2DT.js.map → chunk-ZKAKA54B.js.map} +0 -0
  199. /package/dist/{chunk-TVIX653Q.js.map → chunk-ZKCNPKUO.js.map} +0 -0
  200. /package/dist/{chunk-TU2WUBVZ.js.map → cli-YL3IDR52.js.map} +0 -0
  201. /package/dist/{cli-QKIJOXGW.js.map → cli-ZQUJYQBA.js.map} +0 -0
  202. /package/dist/{cli-YW6VWBHX.js.map → config-O6UBGEDK.js.map} +0 -0
  203. /package/dist/{github-3WMSTNTK.js.map → conformance-ALLPNGEQ.js.map} +0 -0
  204. /package/dist/{github-OXSJQ4PS.js.map → github-UCYLM3DR.js.map} +0 -0
  205. /package/dist/{json-4DCKDGUV.js.map → github-VEJFO6JJ.js.map} +0 -0
  206. /package/dist/{lint-YDHVVZHT.js.map → host-6GURH5HE.js.map} +0 -0
  207. /package/dist/{list-3ZO2NIYO.js.map → json-6F52FSXZ.js.map} +0 -0
  208. /package/dist/{list-EJH6VAOW.js.map → lint-X2GNINK6.js.map} +0 -0
  209. /package/dist/{page-QEDB7EVU.js.map → list-DP3O3CWB.js.map} +0 -0
  210. /package/dist/{pretty-5DGEBZSV.js.map → list-P77RZFZF.js.map} +0 -0
  211. /package/dist/{pretty-SQX33UM2.js.map → page-WQCMBNRD.js.map} +0 -0
  212. /package/dist/{reporters-OHSI3RMV.js.map → pretty-5MMMNSWF.js.map} +0 -0
  213. /package/dist/{reporters-YELDDYJD.js.map → pretty-H4DQREMW.js.map} +0 -0
  214. /package/dist/{run-IP6NZHAG.js.map → provider-AMGRDY6V.js.map} +0 -0
  215. /package/dist/{validate-G6HH3YXL.js.map → reporters-SU3VUKKX.js.map} +0 -0
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ pathKey
4
+ } from "./chunk-D6RO54QD.js";
5
+ import {
6
+ DEFAULT_CAPTURE_DIR,
7
+ makeRedactor
8
+ } from "./chunk-CU57HT2E.js";
9
+
10
+ // src/tracevals/rules/ledger.ts
11
+ import { createHash } from "node:crypto";
12
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
13
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
+ var LEDGER_ENTRIES = 10;
15
+ var HISTORY_ENTRIES = 5;
16
+ var NOTE_CHARS = 200;
17
+ var FACT_TURNS = 30;
18
+ var COMMAND_CHARS = 120;
19
+ var LIMIT = { commands: 10, wrote: 10, read: 5, skills: 10, agents: 10 };
20
+ var GAP_RUNS = 3;
21
+ var sha256 = (s) => createHash("sha256").update(s).digest("hex");
22
+ function idHash(id) {
23
+ return sha256(id).slice(0, 32);
24
+ }
25
+ function ledgerPath(projectDir, sessionId, agentId) {
26
+ const agent = agentId === null ? "" : `.${idHash(agentId)}`;
27
+ return join(projectDir, DEFAULT_CAPTURE_DIR, `${idHash(sessionId)}${agent}.ledger.json`);
28
+ }
29
+ var emptyLedger = () => ({ version: 1, turns: [], rules: {} });
30
+ var OUTCOMES = /* @__PURE__ */ new Set(["followed", "broken", "needs-review", "not-applicable"]);
31
+ function isEntry(v) {
32
+ if (typeof v !== "object" || v === null) return false;
33
+ const e = v;
34
+ return typeof e.turn === "number" && OUTCOMES.has(e.outcome) && typeof e.note === "string";
35
+ }
36
+ var isStrings = (v) => Array.isArray(v) && v.every((s) => typeof s === "string");
37
+ var FACT_LISTS = ["sources", "commands", "wrote", "read", "skills", "agents"];
38
+ function isFacts(v) {
39
+ if (typeof v !== "object" || v === null) return false;
40
+ const f = v;
41
+ return typeof f.turn === "number" && typeof f.inScope === "boolean" && FACT_LISTS.every((k) => isStrings(f[k]));
42
+ }
43
+ async function readLedger(path) {
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(await readFile(path, "utf-8"));
47
+ } catch {
48
+ return emptyLedger();
49
+ }
50
+ if (typeof parsed !== "object" || parsed === null) return emptyLedger();
51
+ const { version, turns, rules } = parsed;
52
+ if (version !== 1 || typeof rules !== "object" || rules === null) return emptyLedger();
53
+ const ledger = emptyLedger();
54
+ if (Array.isArray(turns)) ledger.turns = turns.filter(isFacts);
55
+ for (const [key, value] of Object.entries(rules)) {
56
+ if (typeof value !== "object" || value === null) continue;
57
+ const { text, entries } = value;
58
+ if (typeof text !== "string" || !Array.isArray(entries)) continue;
59
+ ledger.rules[key] = { text, entries: entries.filter(isEntry) };
60
+ }
61
+ return ledger;
62
+ }
63
+ var sourceOf = (key) => key.slice(0, key.lastIndexOf("#"));
64
+ function keyEntries(entries) {
65
+ const broken = entries.filter((e) => e.outcome === "broken").reverse();
66
+ const picked = [entries.at(-1), entries.find((e) => e.outcome === "followed"), ...broken];
67
+ return [...new Set(picked.filter((e) => e !== void 0))].slice(0, HISTORY_ENTRIES).sort((a, b) => a.turn - b.turn);
68
+ }
69
+ function gaps(turns, source) {
70
+ const runs = [];
71
+ let open;
72
+ for (const t of turns) {
73
+ if (t.sources.includes(source)) {
74
+ open = void 0;
75
+ } else if (open !== void 0) {
76
+ open.to = t.turn;
77
+ } else {
78
+ open = { from: t.turn, to: t.turn };
79
+ runs.push(open);
80
+ }
81
+ }
82
+ return runs.slice(-GAP_RUNS);
83
+ }
84
+ function historyBlock(ledger, key, text, before) {
85
+ const rule = ledger.rules[key];
86
+ if (rule?.text !== sha256(text)) return "";
87
+ const shown = keyEntries(
88
+ rule.entries.filter((e) => e.turn < before && e.outcome !== "not-applicable")
89
+ );
90
+ if (shown.length === 0) return "";
91
+ const entries = shown.map((e) => ({
92
+ turn: e.turn,
93
+ line: `- turn ${String(e.turn)}: ${e.outcome}.${e.note !== "" ? ` ${e.note}` : ""}`
94
+ }));
95
+ const holes = gaps(
96
+ ledger.turns.filter((t) => t.turn < before),
97
+ sourceOf(key)
98
+ ).map((g) => ({
99
+ turn: g.from,
100
+ line: g.from === g.to ? `- (turn ${String(g.from)}: not in scope)` : `- (turns ${String(g.from)}\u2013${String(g.to)}: not in scope)`
101
+ }));
102
+ const lines = [...entries, ...holes].sort((a, b) => a.turn - b.turn).map((l) => l.line);
103
+ return `# This rule earlier in this session
104
+
105
+ ${lines.join("\n")}`;
106
+ }
107
+ var posix = (p) => p.split(sep).join("/");
108
+ function projectPath(path, cwd, root) {
109
+ const abs = resolve(cwd, path);
110
+ const r = relative(root, abs);
111
+ return posix(r === "" || r.startsWith("..") || isAbsolute(r) ? abs : r);
112
+ }
113
+ var clipTo = (s, max) => s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
114
+ function turnFacts(trace, turn, sources, opts) {
115
+ const w = turn.window;
116
+ const scrub = makeRedactor(opts.redact);
117
+ const take = (items, n = Number.POSITIVE_INFINITY) => [...new Set(items)].slice(0, n);
118
+ const clip = (s) => clipTo(s, COMMAND_CHARS);
119
+ const rel = (path) => projectPath(path, opts.cwd, opts.root);
120
+ const commands = w.toolCalls.flatMap(
121
+ (c) => c.name === "Bash" && typeof c.input.command === "string" ? [clip(scrub(c.input.command.replace(/\s+/g, " ").trim()))] : []
122
+ );
123
+ const reads = new Set(
124
+ w.fileAccesses.filter((a) => a.op === "read").map((a) => pathKey(resolve(opts.cwd, a.path)))
125
+ );
126
+ return {
127
+ turn: turn.from,
128
+ inScope: sources.length > 0,
129
+ sources: take(sources.map((s) => s.displayPath)),
130
+ commands: take(commands, LIMIT.commands),
131
+ wrote: take(
132
+ w.fileAccesses.filter((a) => a.op !== "read").map((a) => scrub(rel(a.path))),
133
+ LIMIT.wrote
134
+ ),
135
+ read: take(
136
+ // A source with no file (the typed prompts, an inline plan) has path "",
137
+ // which would resolve to the cwd; it is never read.
138
+ sources.filter((s) => s.path !== "" && reads.has(pathKey(s.path))).map((s) => s.displayPath),
139
+ LIMIT.read
140
+ ),
141
+ skills: take(
142
+ w.skillInvocations.map((s) => s.name),
143
+ LIMIT.skills
144
+ ),
145
+ agents: w.empty ? [] : take(
146
+ trace.agentSpawns.filter((a) => a.index >= turn.from && a.index <= turn.to).map((a) => a.subagentType),
147
+ LIMIT.agents
148
+ )
149
+ };
150
+ }
151
+ function recordFacts(ledger, facts) {
152
+ const turns = [...ledger.turns.filter((t) => t.turn !== facts.turn), facts].sort((a, b) => a.turn - b.turn).slice(-FACT_TURNS);
153
+ return { ...ledger, turns };
154
+ }
155
+ function trim(entries) {
156
+ const first = entries.find((e) => e.outcome === "followed");
157
+ const out = [...entries];
158
+ while (out.length > LEDGER_ENTRIES) {
159
+ const last = out.length - 1;
160
+ const loose = out.findIndex((e, i) => e !== first && e.outcome !== "broken" && i !== last);
161
+ out.splice(loose >= 0 ? loose : out.findIndex((e) => e !== first), 1);
162
+ }
163
+ return out;
164
+ }
165
+ function recordTurn(ledger, turn, results) {
166
+ const next = { ...ledger, rules: { ...ledger.rules } };
167
+ for (const r of results) {
168
+ const hash = sha256(r.text);
169
+ const prior = next.rules[r.key];
170
+ const kept = prior?.text === hash ? prior.entries.filter((e) => e.turn !== turn) : [];
171
+ const entries = trim(
172
+ [...kept, { turn, outcome: r.outcome, note: r.note.slice(0, NOTE_CHARS) }].sort(
173
+ (a, b) => a.turn - b.turn
174
+ )
175
+ );
176
+ next.rules[r.key] = { text: hash, entries };
177
+ }
178
+ return next;
179
+ }
180
+ function rollup(ledger) {
181
+ const keys = Object.keys(ledger.rules);
182
+ if (ledger.turns.length === 0 && keys.length === 0) return null;
183
+ const rules = keys.flatMap((key) => {
184
+ const entries = ledger.rules[key]?.entries ?? [];
185
+ const last = entries.at(-1);
186
+ if (last === void 0) return [];
187
+ const source = sourceOf(key);
188
+ const n = (o) => entries.filter((e) => e.outcome === o).length;
189
+ return [
190
+ {
191
+ source,
192
+ rule: key.slice(source.length + 1),
193
+ followed: n("followed"),
194
+ broken: n("broken"),
195
+ repaired: entries.filter(
196
+ (e, i) => e.outcome === "broken" && entries.slice(i + 1).some((f) => f.outcome === "followed")
197
+ ).length,
198
+ needsReview: n("needs-review"),
199
+ notApplicable: n("not-applicable"),
200
+ notInScope: ledger.turns.filter((t) => !t.sources.includes(source)).length,
201
+ last: { turn: last.turn, outcome: last.outcome }
202
+ }
203
+ ];
204
+ });
205
+ return { turns: ledger.turns.length, rules };
206
+ }
207
+ async function writeLedger(path, ledger) {
208
+ await mkdir(dirname(path), { recursive: true });
209
+ const tmp = `${path}.${String(process.pid)}.tmp`;
210
+ await writeFile(tmp, `${JSON.stringify(ledger, null, 2)}
211
+ `, "utf-8");
212
+ try {
213
+ await rename(tmp, path);
214
+ } catch (err) {
215
+ await rm(tmp, { force: true });
216
+ throw err;
217
+ }
218
+ }
219
+
220
+ export {
221
+ idHash,
222
+ ledgerPath,
223
+ readLedger,
224
+ historyBlock,
225
+ projectPath,
226
+ clipTo,
227
+ turnFacts,
228
+ recordFacts,
229
+ recordTurn,
230
+ rollup,
231
+ writeLedger
232
+ };
233
+ //# sourceMappingURL=chunk-7EL5LLKD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tracevals/rules/ledger.ts"],"sourcesContent":["/**\n * The session ledger (proposal 0079, \"The ledger\"). What each turn of one\n * session did, and what the in-loop judge found for each rule, so a rule\n * about a procedure that spans turns is judged with what earlier turns did.\n *\n * Only the hook writes it, one file per session and one per subagent, so\n * parallel SubagentStops never share a file. `check` by hand reads it and\n * never writes it. A missing or unreadable ledger is an empty history.\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { DEFAULT_CAPTURE_DIR } from \"../capture/types.js\";\nimport { makeRedactor } from \"../judge/redact.js\";\nimport type { Trace } from \"../trace/types.js\";\nimport { pathKey, type RuleSource } from \"./sources.js\";\nimport type { TurnSlice } from \"./turn.js\";\n\nexport type LedgerOutcome = \"followed\" | \"broken\" | \"needs-review\" | \"not-applicable\";\n\nexport interface LedgerEntry {\n /** The turn's first event, as an ordinal in the trace. */\n turn: number;\n outcome: LedgerOutcome;\n /** The judge's reasoning, cut to `NOTE_CHARS`. */\n note: string;\n}\n\n/** What one turn did, read from the trace with no model. */\nexport interface TurnFacts {\n turn: number;\n /** Whether any rule source governed the turn. */\n inScope: boolean;\n /** The rule sources in scope, by display path. */\n sources: string[];\n /** Bash commands, each clipped to `COMMAND_CHARS`. */\n commands: string[];\n /** Files written or edited, relative to the project. */\n wrote: string[];\n /** Rule sources the turn read. */\n read: string[];\n skills: string[];\n /** Subagent types the turn spawned. */\n agents: string[];\n}\n\nexport interface Ledger {\n version: 1;\n /** The last `FACT_TURNS` turns the hook saw, oldest first. */\n turns: TurnFacts[];\n /** Keyed `<source>#<id>`. `text` is the rule text's sha256, so an edited rule starts fresh. */\n rules: Record<string, { text: string; entries: LedgerEntry[] }>;\n}\n\nexport const LEDGER_ENTRIES = 10;\nexport const HISTORY_ENTRIES = 5;\nexport const NOTE_CHARS = 200;\nexport const FACT_TURNS = 30;\nexport const COMMAND_CHARS = 120;\nconst LIMIT = { commands: 10, wrote: 10, read: 5, skills: 10, agents: 10 } as const;\n/** Runs of turns a rule's source missed, shown in its history. */\nconst GAP_RUNS = 3;\n\nconst sha256 = (s: string): string => createHash(\"sha256\").update(s).digest(\"hex\");\n\n/** A session or agent id, hashed so nothing in it can climb out of the directory. */\nexport function idHash(id: string): string {\n return sha256(id).slice(0, 32);\n}\n\n/** `<project>/.manni/tracevals/sessions/<session hash>[.<agent id hash>].ledger.json` */\nexport function ledgerPath(projectDir: string, sessionId: string, agentId: string | null): string {\n const agent = agentId === null ? \"\" : `.${idHash(agentId)}`;\n return join(projectDir, DEFAULT_CAPTURE_DIR, `${idHash(sessionId)}${agent}.ledger.json`);\n}\n\nexport const emptyLedger = (): Ledger => ({ version: 1, turns: [], rules: {} });\n\nconst OUTCOMES = new Set<unknown>([\"followed\", \"broken\", \"needs-review\", \"not-applicable\"]);\n\nfunction isEntry(v: unknown): v is LedgerEntry {\n if (typeof v !== \"object\" || v === null) return false;\n const e = v as Record<string, unknown>;\n return typeof e.turn === \"number\" && OUTCOMES.has(e.outcome) && typeof e.note === \"string\";\n}\n\nconst isStrings = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((s) => typeof s === \"string\");\n\nconst FACT_LISTS = [\"sources\", \"commands\", \"wrote\", \"read\", \"skills\", \"agents\"] as const;\n\nfunction isFacts(v: unknown): v is TurnFacts {\n if (typeof v !== \"object\" || v === null) return false;\n const f = v as Record<string, unknown>;\n return (\n typeof f.turn === \"number\" &&\n typeof f.inScope === \"boolean\" &&\n FACT_LISTS.every((k) => isStrings(f[k]))\n );\n}\n\n/** Never throws: anything but a well-formed ledger reads as an empty one. */\nexport async function readLedger(path: string): Promise<Ledger> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(path, \"utf-8\"));\n } catch {\n return emptyLedger();\n }\n if (typeof parsed !== \"object\" || parsed === null) return emptyLedger();\n const { version, turns, rules } = parsed as Record<string, unknown>;\n if (version !== 1 || typeof rules !== \"object\" || rules === null) return emptyLedger();\n const ledger = emptyLedger();\n if (Array.isArray(turns)) ledger.turns = turns.filter(isFacts);\n for (const [key, value] of Object.entries(rules)) {\n if (typeof value !== \"object\" || value === null) continue;\n const { text, entries } = value as Record<string, unknown>;\n if (typeof text !== \"string\" || !Array.isArray(entries)) continue;\n ledger.rules[key] = { text, entries: entries.filter(isEntry) };\n }\n return ledger;\n}\n\n/** The source half of a `<source>#<id>` key. */\nconst sourceOf = (key: string): string => key.slice(0, key.lastIndexOf(\"#\"));\n\n/**\n * The entries a rule's history keeps in view: its latest, its first\n * `followed`, then its `broken` ones from newest back, at most\n * `HISTORY_ENTRIES`, oldest first.\n */\nfunction keyEntries(entries: LedgerEntry[]): LedgerEntry[] {\n const broken = entries.filter((e) => e.outcome === \"broken\").reverse();\n const picked = [entries.at(-1), entries.find((e) => e.outcome === \"followed\"), ...broken];\n return [...new Set(picked.filter((e): e is LedgerEntry => e !== undefined))]\n .slice(0, HISTORY_ENTRIES)\n .sort((a, b) => a.turn - b.turn);\n}\n\n/** The last `GAP_RUNS` runs of consecutive recorded turns whose sources leave `source` out. */\nfunction gaps(turns: TurnFacts[], source: string): { from: number; to: number }[] {\n const runs: { from: number; to: number }[] = [];\n let open: { from: number; to: number } | undefined;\n for (const t of turns) {\n if (t.sources.includes(source)) {\n open = undefined;\n } else if (open !== undefined) {\n open.to = t.turn;\n } else {\n open = { from: t.turn, to: t.turn };\n runs.push(open);\n }\n }\n return runs.slice(-GAP_RUNS);\n}\n\n/**\n * The block a rule's item carries, or \"\" when the ledger holds nothing for it\n * before `before` other than turns it did not apply to. Runs of recorded\n * turns where its source was not in scope are marked, so a hole in the\n * history does not read as quiet.\n */\nexport function historyBlock(ledger: Ledger, key: string, text: string, before: number): string {\n const rule = ledger.rules[key];\n if (rule?.text !== sha256(text)) return \"\";\n const shown = keyEntries(\n rule.entries.filter((e) => e.turn < before && e.outcome !== \"not-applicable\"),\n );\n if (shown.length === 0) return \"\";\n const entries = shown.map((e) => ({\n turn: e.turn,\n line: `- turn ${String(e.turn)}: ${e.outcome}.${e.note !== \"\" ? ` ${e.note}` : \"\"}`,\n }));\n const holes = gaps(\n ledger.turns.filter((t) => t.turn < before),\n sourceOf(key),\n ).map((g) => ({\n turn: g.from,\n line:\n g.from === g.to\n ? `- (turn ${String(g.from)}: not in scope)`\n : `- (turns ${String(g.from)}–${String(g.to)}: not in scope)`,\n }));\n const lines = [...entries, ...holes].sort((a, b) => a.turn - b.turn).map((l) => l.line);\n return `# This rule earlier in this session\\n\\n${lines.join(\"\\n\")}`;\n}\n\nconst posix = (p: string): string => p.split(sep).join(\"/\");\n\n/** A path as the turn named it, relative to the project root when it is under it. */\nexport function projectPath(path: string, cwd: string, root: string): string {\n const abs = resolve(cwd, path);\n const r = relative(root, abs);\n return posix(r === \"\" || r.startsWith(\"..\") || isAbsolute(r) ? abs : r);\n}\n\n/** `s` cut to at most `max` characters, the last an ellipsis when cut. */\nexport const clipTo = (s: string, max: number): string =>\n s.length <= max ? s : `${s.slice(0, max - 1)}…`;\n\n/**\n * What the turn did, read from its window. Commands and paths pass through\n * the judge's redactor. `sources` are the rule sources in scope, none for a\n * turn that stopped before they were resolved.\n */\nexport function turnFacts(\n trace: Trace,\n turn: TurnSlice,\n sources: Pick<RuleSource, \"path\" | \"displayPath\">[],\n opts: { cwd: string; root: string; redact: readonly string[] },\n): TurnFacts {\n const w = turn.window;\n const scrub = makeRedactor(opts.redact);\n const take = (items: string[], n = Number.POSITIVE_INFINITY): string[] =>\n [...new Set(items)].slice(0, n);\n const clip = (s: string): string => clipTo(s, COMMAND_CHARS);\n const rel = (path: string): string => projectPath(path, opts.cwd, opts.root);\n const commands = w.toolCalls.flatMap((c) =>\n c.name === \"Bash\" && typeof c.input.command === \"string\"\n ? [clip(scrub(c.input.command.replace(/\\s+/g, \" \").trim()))]\n : [],\n );\n const reads = new Set(\n w.fileAccesses.filter((a) => a.op === \"read\").map((a) => pathKey(resolve(opts.cwd, a.path))),\n );\n return {\n turn: turn.from,\n inScope: sources.length > 0,\n sources: take(sources.map((s) => s.displayPath)),\n commands: take(commands, LIMIT.commands),\n wrote: take(\n w.fileAccesses.filter((a) => a.op !== \"read\").map((a) => scrub(rel(a.path))),\n LIMIT.wrote,\n ),\n read: take(\n // A source with no file (the typed prompts, an inline plan) has path \"\",\n // which would resolve to the cwd; it is never read.\n sources.filter((s) => s.path !== \"\" && reads.has(pathKey(s.path))).map((s) => s.displayPath),\n LIMIT.read,\n ),\n skills: take(\n w.skillInvocations.map((s) => s.name),\n LIMIT.skills,\n ),\n agents: w.empty\n ? []\n : take(\n trace.agentSpawns\n .filter((a) => a.index >= turn.from && a.index <= turn.to)\n .map((a) => a.subagentType),\n LIMIT.agents,\n ),\n };\n}\n\n/** The ledger with this turn's facts, replacing any earlier record of the same turn. */\nexport function recordFacts(ledger: Ledger, facts: TurnFacts): Ledger {\n const turns = [...ledger.turns.filter((t) => t.turn !== facts.turn), facts]\n .sort((a, b) => a.turn - b.turn)\n .slice(-FACT_TURNS);\n return { ...ledger, turns };\n}\n\nexport interface TurnResult {\n key: string;\n text: string;\n outcome: LedgerOutcome;\n note: string;\n}\n\n/**\n * Cut a rule's entries to `LEDGER_ENTRIES`. The first `followed`, every\n * `broken` and the latest are pinned, so the others go first, oldest first.\n * Only when every entry is pinned does the oldest `broken` go.\n */\nfunction trim(entries: LedgerEntry[]): LedgerEntry[] {\n const first = entries.find((e) => e.outcome === \"followed\");\n const out = [...entries];\n while (out.length > LEDGER_ENTRIES) {\n const last = out.length - 1;\n const loose = out.findIndex((e, i) => e !== first && e.outcome !== \"broken\" && i !== last);\n out.splice(loose >= 0 ? loose : out.findIndex((e) => e !== first), 1);\n }\n return out;\n}\n\n/** The ledger with this turn's results, replacing any earlier record of the same turn. */\nexport function recordTurn(ledger: Ledger, turn: number, results: TurnResult[]): Ledger {\n const next: Ledger = { ...ledger, rules: { ...ledger.rules } };\n for (const r of results) {\n const hash = sha256(r.text);\n const prior = next.rules[r.key];\n const kept = prior?.text === hash ? prior.entries.filter((e) => e.turn !== turn) : [];\n const entries = trim(\n [...kept, { turn, outcome: r.outcome, note: r.note.slice(0, NOTE_CHARS) }].sort(\n (a, b) => a.turn - b.turn,\n ),\n );\n next.rules[r.key] = { text: hash, entries };\n }\n return next;\n}\n\nexport interface RuleRollup {\n source: string;\n rule: string;\n followed: number;\n broken: number;\n /** Broken entries that a later `followed` entry came after. */\n repaired: number;\n needsReview: number;\n notApplicable: number;\n /** Recorded turns where the rule's source was not in scope. */\n notInScope: number;\n last: { turn: number; outcome: LedgerOutcome };\n}\n\nexport interface SessionRollup {\n /** Turns the ledger records. */\n turns: number;\n rules: RuleRollup[];\n}\n\n/** Each rule's record over the session, or null when the ledger holds nothing. */\nexport function rollup(ledger: Ledger): SessionRollup | null {\n const keys = Object.keys(ledger.rules);\n if (ledger.turns.length === 0 && keys.length === 0) return null;\n const rules = keys.flatMap((key): RuleRollup[] => {\n const entries = ledger.rules[key]?.entries ?? [];\n const last = entries.at(-1);\n if (last === undefined) return [];\n const source = sourceOf(key);\n const n = (o: LedgerOutcome): number => entries.filter((e) => e.outcome === o).length;\n return [\n {\n source,\n rule: key.slice(source.length + 1),\n followed: n(\"followed\"),\n broken: n(\"broken\"),\n repaired: entries.filter(\n (e, i) =>\n e.outcome === \"broken\" && entries.slice(i + 1).some((f) => f.outcome === \"followed\"),\n ).length,\n needsReview: n(\"needs-review\"),\n notApplicable: n(\"not-applicable\"),\n notInScope: ledger.turns.filter((t) => !t.sources.includes(source)).length,\n last: { turn: last.turn, outcome: last.outcome },\n },\n ];\n });\n return { turns: ledger.turns.length, rules };\n}\n\n/** Temp file and rename, so a reader never sees half a ledger. Throws on failure. */\nexport async function writeLedger(path: string, ledger: Ledger): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.${String(process.pid)}.tmp`;\n await writeFile(tmp, `${JSON.stringify(ledger, null, 2)}\\n`, \"utf-8\");\n try {\n await rename(tmp, path);\n } catch (err) {\n await rm(tmp, { force: true });\n throw err;\n }\n}\n"],"mappings":"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","names":[]}
@@ -15,10 +15,10 @@ import {
15
15
  import {
16
16
  REPORT_FORMAT_LIST,
17
17
  renderJunit
18
- } from "./chunk-TCC737IS.js";
18
+ } from "./chunk-5VWDIWOH.js";
19
19
  import {
20
20
  isErrorSeverity
21
- } from "./chunk-527HHQPS.js";
21
+ } from "./chunk-MSMB6XOS.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-6XTQDFGP.js";
31
+ } from "./chunk-YSCZL4U5.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-C3KI45RF.js.map
636
+ //# sourceMappingURL=chunk-7XZ6INKO.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
- selectProvider
8
- } from "./chunk-SJO2FTPG.js";
5
+ runBatch
6
+ } from "./chunk-QP5X66TO.js";
9
7
  import {
10
8
  discoverConfig,
11
9
  labelKey,
12
10
  loadLabels
13
- } from "./chunk-3R5Y3XOL.js";
11
+ } from "./chunk-TPVOOTMN.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-ZX43SRZR.js.map
381
+ //# sourceMappingURL=chunk-AMEILOVV.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":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadTerms
4
- } from "./chunk-NN4ASVTY.js";
4
+ } from "./chunk-IIFADWWI.js";
5
5
 
6
6
  // src/term/commands/list.ts
7
7
  var LIST_FORMATS = ["pretty", "json", "csv"];
@@ -14,4 +14,4 @@ export {
14
14
  LIST_FORMATS,
15
15
  runList
16
16
  };
17
- //# sourceMappingURL=chunk-SNEHX4VE.js.map
17
+ //# sourceMappingURL=chunk-B5CMM57M.js.map