@rulemetric/cli 0.13.0 → 0.14.1

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 (335) hide show
  1. package/dist/base-command.js +1 -1
  2. package/dist/chunk-2UK4DZFW.js +12 -0
  3. package/dist/{chunk-3UPHCQWI.js → chunk-35YVHBLL.js} +1 -1
  4. package/dist/{chunk-VH2EAP4J.js → chunk-3FNTV2TW.js} +1 -1
  5. package/dist/{chunk-WQX4RRBQ.js → chunk-3IMRLHJJ.js} +1 -1
  6. package/dist/chunk-42A4PUW7.js +48 -0
  7. package/dist/chunk-43VKTVM3.js +1 -0
  8. package/dist/{chunk-CYYDQFRY.js → chunk-44TYRMIS.js} +1 -1
  9. package/dist/{chunk-25RDT32M.js → chunk-4FJYVYCU.js} +1 -1
  10. package/dist/{chunk-I3M5VBLT.js → chunk-5BT375C7.js} +1 -1
  11. package/dist/{chunk-7AWFSFR7.js → chunk-5Z3H5JHK.js} +1 -1
  12. package/dist/{chunk-25Q3Y7TC.js → chunk-6ANODRDT.js} +1 -1
  13. package/dist/{chunk-APDRR6FS.js → chunk-76UKAFNM.js} +1 -1
  14. package/dist/{chunk-VXUKXGRX.js → chunk-7MTYUTUN.js} +2 -2
  15. package/dist/{chunk-JWFRI32Y.js → chunk-AGEZAKCC.js} +1 -1
  16. package/dist/chunk-AHLVY52I.js +1 -0
  17. package/dist/{chunk-MNQPCNGQ.js → chunk-AIYFT4P6.js} +1 -1
  18. package/dist/{chunk-UYPBIXF2.js → chunk-B5X6HCBK.js} +1 -1
  19. package/dist/chunk-BAITMD76.js +1 -0
  20. package/dist/{chunk-GJBS6QYC.js → chunk-BFQVAU6N.js} +1 -1
  21. package/dist/{chunk-UE76F2NQ.js → chunk-BMT5WMEV.js} +20 -20
  22. package/dist/chunk-CT6GJQIR.js +1 -0
  23. package/dist/chunk-CT7FWSIU.js +3 -0
  24. package/dist/chunk-DHX4U6EC.js +13 -0
  25. package/dist/chunk-DQYYHX6K.js +1 -0
  26. package/dist/{chunk-WH3IPXXQ.js → chunk-DVU5PHPM.js} +5 -5
  27. package/dist/chunk-E7GQJML3.js +1 -0
  28. package/dist/{chunk-4N43IBXH.js → chunk-EMLLGASZ.js} +1 -1
  29. package/dist/{chunk-Y7QIOPN5.js → chunk-EUHRTEVJ.js} +1 -1
  30. package/dist/chunk-FBSZ6KCM.js +1 -0
  31. package/dist/{chunk-5XK6LB2T.js → chunk-FKTT7A4H.js} +1 -1
  32. package/dist/{chunk-V2USQH27.js → chunk-FYZ6HHCV.js} +1 -1
  33. package/dist/chunk-GH4EZK2O.js +1 -0
  34. package/dist/{chunk-DUBBL57L.js → chunk-GKVHFXUQ.js} +1 -1
  35. package/dist/chunk-GPBN4ROE.js +2 -0
  36. package/dist/chunk-GYNZ3OZF.js +1 -0
  37. package/dist/chunk-H6FVXVGG.js +1 -0
  38. package/dist/{chunk-6GAIYJ6K.js → chunk-HNYHZO2J.js} +1 -1
  39. package/dist/{chunk-RJX75Y7J.js → chunk-I6ZQOMZS.js} +1 -1
  40. package/dist/{chunk-O2HCD6XL.js → chunk-IO46ICZY.js} +1 -1
  41. package/dist/chunk-IQEZ3T5A.js +1 -0
  42. package/dist/{chunk-KV5A77XW.js → chunk-ISFPPDDB.js} +1 -1
  43. package/dist/chunk-J4W6GNCF.js +2 -0
  44. package/dist/chunk-JP6ULOEX.js +2 -0
  45. package/dist/{chunk-J6QMBMC2.js → chunk-JTYVBR5H.js} +1 -1
  46. package/dist/{chunk-BG55WDDX.js → chunk-KOYHJFB3.js} +1 -1
  47. package/dist/chunk-LJRJ2RAC.js +4 -0
  48. package/dist/chunk-MHVHGKDK.js +3 -0
  49. package/dist/{chunk-PVGUDJD7.js → chunk-MMLRWCBW.js} +1 -1
  50. package/dist/chunk-MMM4B3KH.js +1 -0
  51. package/dist/{chunk-ZGW3N3A6.js → chunk-MU7KKU3O.js} +1 -1
  52. package/dist/{chunk-YI2OWO6R.js → chunk-MXHAXY66.js} +1 -1
  53. package/dist/chunk-N4YPDZJF.js +9 -0
  54. package/dist/{chunk-VIQ25ORK.js → chunk-NYNHX7OX.js} +1 -1
  55. package/dist/{chunk-RK663O4M.js → chunk-OP4U3XBZ.js} +6 -6
  56. package/dist/{chunk-6W2GSV4O.js → chunk-OVS7ZDJ4.js} +1 -1
  57. package/dist/chunk-PIVEJTNF.js +1 -0
  58. package/dist/chunk-POEMCITL.js +1 -0
  59. package/dist/chunk-PPAHIVG5.js +1 -0
  60. package/dist/{chunk-NBEZ5FAU.js → chunk-Q36QSFSB.js} +1 -1
  61. package/dist/chunk-Q776YUBD.js +1 -0
  62. package/dist/chunk-QAMHZY2P.js +1 -0
  63. package/dist/chunk-QI3FJKGK.js +1 -0
  64. package/dist/{chunk-WOJ733IB.js → chunk-QRIJ3AHP.js} +1 -1
  65. package/dist/chunk-RAQLVO2X.js +1 -0
  66. package/dist/{chunk-M2VVRQKA.js → chunk-RG3R4EAB.js} +1 -1
  67. package/dist/{chunk-WY3VVX3R.js → chunk-RMRVUI7B.js} +1 -1
  68. package/dist/{chunk-GBWQENAH.js → chunk-RXZLBSDO.js} +1 -1
  69. package/dist/chunk-SCQK52SL.js +1 -0
  70. package/dist/{chunk-BZS5QRWB.js → chunk-SG4UDDHB.js} +1 -1
  71. package/dist/chunk-SWSOEYC7.js +1 -0
  72. package/dist/chunk-T75KAEJO.js +25 -0
  73. package/dist/{chunk-U7TBEC3I.js → chunk-V55ROUNP.js} +1 -1
  74. package/dist/{chunk-SHYP27B7.js → chunk-VALRPZXH.js} +1 -1
  75. package/dist/{chunk-P2WSDHIA.js → chunk-VIFOLTZT.js} +1 -1
  76. package/dist/{chunk-6QLZT7EP.js → chunk-VU24IH5S.js} +1 -1
  77. package/dist/{chunk-6ZPSVZNH.js → chunk-VXL5H3VZ.js} +1 -1
  78. package/dist/{chunk-QFKUSNUB.js → chunk-W23OYQXI.js} +1 -1
  79. package/dist/{chunk-QTCWOIT4.js → chunk-WWQWHC4K.js} +1 -1
  80. package/dist/{chunk-LTQ5BJYL.js → chunk-XGMOVQ5G.js} +1 -1
  81. package/dist/{chunk-4COLIGD7.js → chunk-XJ25HFIT.js} +1 -1
  82. package/dist/{chunk-3MDEGOUG.js → chunk-XMA6SWOT.js} +1 -1
  83. package/dist/{chunk-WJZY5Y22.js → chunk-XMTKP74H.js} +1 -1
  84. package/dist/chunk-XQYAVSUQ.js +1 -0
  85. package/dist/{chunk-2EX27VJ3.js → chunk-YK2IEHLB.js} +1 -1
  86. package/dist/chunk-ZK3ACE4T.js +1 -0
  87. package/dist/commands/antigravity/backfill.js +1 -1
  88. package/dist/commands/auth/create-key.js +1 -1
  89. package/dist/commands/auth/login.js +1 -1
  90. package/dist/commands/auth/logout.js +1 -1
  91. package/dist/commands/auth/revoke-key.js +1 -1
  92. package/dist/commands/auth/signup.js +1 -1
  93. package/dist/commands/auth/status.js +1 -1
  94. package/dist/commands/capture/scope.js +1 -1
  95. package/dist/commands/catalog/index.js +1 -1
  96. package/dist/commands/catalog/recommend.js +1 -1
  97. package/dist/commands/crons/list.js +1 -1
  98. package/dist/commands/crons/run.js +2 -2
  99. package/dist/commands/dashboard.js +1 -1
  100. package/dist/commands/diagnostics.js +1 -1
  101. package/dist/commands/eval-targets/create.js +1 -1
  102. package/dist/commands/eval-targets/list.js +1 -1
  103. package/dist/commands/evals/agent.js +3 -3
  104. package/dist/commands/evals/auto-run.js +1 -1
  105. package/dist/commands/evals/benchmark.js +1 -1
  106. package/dist/commands/evals/compare.js +1 -1
  107. package/dist/commands/evals/create.js +1 -1
  108. package/dist/commands/evals/optimize-description.js +1 -1
  109. package/dist/commands/evals/optimize.js +1 -1
  110. package/dist/commands/evals/refine-cases.js +1 -1
  111. package/dist/commands/evals/run.js +1 -1
  112. package/dist/commands/evals/validate-judge.js +1 -1
  113. package/dist/commands/experiments/list.js +1 -1
  114. package/dist/commands/gateway/ensure.js +1 -1
  115. package/dist/commands/hooks/emit-instructions.js +1 -1
  116. package/dist/commands/hooks/install.js +1 -1
  117. package/dist/commands/hooks/run.js +1 -1
  118. package/dist/commands/hooks/uninstall.js +1 -1
  119. package/dist/commands/instructions/create.js +1 -1
  120. package/dist/commands/instructions/dedupe.js +1 -1
  121. package/dist/commands/instructions/delete.js +1 -1
  122. package/dist/commands/instructions/fork.js +1 -1
  123. package/dist/commands/instructions/get.js +1 -1
  124. package/dist/commands/instructions/list.js +1 -1
  125. package/dist/commands/instructions/promote.js +1 -1
  126. package/dist/commands/instructions/pull.js +1 -1
  127. package/dist/commands/instructions/restore.js +1 -1
  128. package/dist/commands/instructions/retract.js +1 -1
  129. package/dist/commands/instructions/upstream.js +1 -1
  130. package/dist/commands/instructions/versions.js +1 -1
  131. package/dist/commands/launcher.js +1 -1
  132. package/dist/commands/local/down.js +1 -1
  133. package/dist/commands/local/up.js +1 -1
  134. package/dist/commands/org/current.js +1 -1
  135. package/dist/commands/org/list.js +1 -1
  136. package/dist/commands/org/switch.js +1 -1
  137. package/dist/commands/packs/add-instruction.js +1 -1
  138. package/dist/commands/packs/apply.js +1 -1
  139. package/dist/commands/packs/create.js +1 -1
  140. package/dist/commands/packs/fork.js +1 -1
  141. package/dist/commands/packs/get.js +1 -1
  142. package/dist/commands/packs/list.js +1 -1
  143. package/dist/commands/packs/remove-instruction.js +1 -1
  144. package/dist/commands/proxy/env.js +1 -1
  145. package/dist/commands/proxy/logs.js +1 -1
  146. package/dist/commands/proxy/restart.js +1 -1
  147. package/dist/commands/proxy/setup.js +1 -1
  148. package/dist/commands/proxy/start.js +1 -1
  149. package/dist/commands/proxy/status.js +1 -1
  150. package/dist/commands/proxy/stop.js +1 -1
  151. package/dist/commands/research/backfill.js +1 -1
  152. package/dist/commands/research/tail-transcript.js +1 -1
  153. package/dist/commands/service/install.js +18 -18
  154. package/dist/commands/service/status.js +1 -1
  155. package/dist/commands/service/uninstall.js +1 -1
  156. package/dist/commands/sessions/analyze.js +1 -1
  157. package/dist/commands/sessions/delete.js +1 -1
  158. package/dist/commands/sessions/end.js +1 -1
  159. package/dist/commands/sessions/import.js +1 -1
  160. package/dist/commands/sessions/list.js +1 -1
  161. package/dist/commands/sessions/start.js +1 -1
  162. package/dist/commands/setup.js +1 -1
  163. package/dist/commands/skills/export-approved.js +1 -1
  164. package/dist/commands/skills/install.js +1 -1
  165. package/dist/commands/skills/list.js +1 -1
  166. package/dist/commands/skills/publish.js +1 -1
  167. package/dist/commands/skills/search.js +1 -1
  168. package/dist/commands/skills/update.js +1 -1
  169. package/dist/commands/suggestions/accept.js +1 -1
  170. package/dist/commands/suggestions/auto.js +1 -1
  171. package/dist/commands/suggestions/dismiss.js +1 -1
  172. package/dist/commands/suggestions/list.js +1 -1
  173. package/dist/commands/suggestions/refresh.js +1 -1
  174. package/dist/commands/suggestions/undo.js +1 -1
  175. package/dist/dashboard/Dashboard.js +1 -1
  176. package/dist/dashboard/data.js +1 -1
  177. package/dist/dashboard/launcher/LauncherOverlay.js +1 -1
  178. package/dist/dashboard/launcher/api.js +1 -1
  179. package/dist/dashboard/launcher/useLaunchJobs.js +1 -1
  180. package/dist/dashboard/panels/measurement.js +1 -0
  181. package/dist/dist-GSHUC7UU.js +1 -0
  182. package/dist/harbor/cost_delta.py +377 -0
  183. package/dist/harbor/mcp_container_shell.py +156 -0
  184. package/dist/harbor/provenance.py +361 -0
  185. package/dist/harbor/rm-batch-task/environment/Dockerfile +6 -0
  186. package/dist/harbor/rm-batch-task/environment/items.jsonl +60 -0
  187. package/dist/harbor/rm-batch-task/inject.md +8 -0
  188. package/dist/harbor/rm-batch-task/instruction.md +8 -0
  189. package/dist/harbor/rm-batch-task/solution/solve.sh +68 -0
  190. package/dist/harbor/rm-batch-task/task.toml +30 -0
  191. package/dist/harbor/rm-batch-task/tests/test.sh +23 -0
  192. package/dist/harbor/rm-batch-task/tests/test_outputs.py +32 -0
  193. package/dist/harbor/rm-locality-task/environment/Dockerfile +8 -0
  194. package/dist/harbor/rm-locality-task/environment/workspace/deploy/prod/ingest.yaml +3 -0
  195. package/dist/harbor/rm-locality-task/environment/workspace/deploy/staging/ingest.yaml +3 -0
  196. package/dist/harbor/rm-locality-task/environment/workspace/docs/architecture/ingest.md +6 -0
  197. package/dist/harbor/rm-locality-task/environment/workspace/legacy/ingest_v1/config.yaml +2 -0
  198. package/dist/harbor/rm-locality-task/environment/workspace/packages/shared/defaults.py +4 -0
  199. package/dist/harbor/rm-locality-task/environment/workspace/services/ingest/config/limits.defaults.yaml +4 -0
  200. package/dist/harbor/rm-locality-task/environment/workspace/services/ingest/config/limits.yaml +3 -0
  201. package/dist/harbor/rm-locality-task/environment/workspace/services/ingest/loader.py +15 -0
  202. package/dist/harbor/rm-locality-task/environment/workspace/tests/fixtures/ingest_config.yaml +3 -0
  203. package/dist/harbor/rm-locality-task/inject.md +12 -0
  204. package/dist/harbor/rm-locality-task/instruction.md +4 -0
  205. package/dist/harbor/rm-locality-task/solution/solve.sh +14 -0
  206. package/dist/harbor/rm-locality-task/task.toml +30 -0
  207. package/dist/harbor/rm-locality-task/tests/test.sh +23 -0
  208. package/dist/harbor/rm-locality-task/tests/test_outputs.py +19 -0
  209. package/dist/harbor/rm-migrations-task/environment/Dockerfile +7 -0
  210. package/dist/harbor/rm-migrations-task/environment/applied.txt +2 -0
  211. package/dist/harbor/rm-migrations-task/environment/migrations/00001_init.sql +1 -0
  212. package/dist/harbor/rm-migrations-task/environment/migrations/00002_add_email.sql +1 -0
  213. package/dist/harbor/rm-migrations-task/inject.md +7 -0
  214. package/dist/harbor/rm-migrations-task/instruction.md +8 -0
  215. package/dist/harbor/rm-migrations-task/solution/solve.sh +8 -0
  216. package/dist/harbor/rm-migrations-task/task.toml +30 -0
  217. package/dist/harbor/rm-migrations-task/tests/test.sh +23 -0
  218. package/dist/harbor/rm-migrations-task/tests/test_outputs.py +36 -0
  219. package/dist/harbor/rm-pagination-task/environment/Dockerfile +6 -0
  220. package/dist/harbor/rm-pagination-task/environment/store.py +29 -0
  221. package/dist/harbor/rm-pagination-task/inject.md +8 -0
  222. package/dist/harbor/rm-pagination-task/instruction.md +5 -0
  223. package/dist/harbor/rm-pagination-task/solution/solve.sh +7 -0
  224. package/dist/harbor/rm-pagination-task/task.toml +30 -0
  225. package/dist/harbor/rm-pagination-task/tests/test.sh +23 -0
  226. package/dist/harbor/rm-pagination-task/tests/test_outputs.py +14 -0
  227. package/dist/harbor/rm_host_claude.py +264 -0
  228. package/dist/harbor/rm_host_codex.py +208 -0
  229. package/dist/harbor/run.sh +268 -0
  230. package/dist/harbor/summarize.py +248 -0
  231. package/dist/lib/active-org-refresh.js +1 -1
  232. package/dist/lib/agent-loop.js +1 -1
  233. package/dist/lib/antigravity-transcript.js +1 -1
  234. package/dist/lib/api-client.js +1 -1
  235. package/dist/lib/autorun-batch.js +1 -0
  236. package/dist/lib/capture-scope-config.js +1 -1
  237. package/dist/lib/codex-capture-scope.js +1 -1
  238. package/dist/lib/codex-exec.js +1 -1
  239. package/dist/lib/ensure-api-key.js +1 -1
  240. package/dist/lib/ensure-fresh-token.js +1 -1
  241. package/dist/lib/eval-accumulation.js +1 -0
  242. package/dist/lib/eval-analyzer.js +1 -1
  243. package/dist/lib/eval-benchmark.js +1 -1
  244. package/dist/lib/eval-candidate-run.js +1 -1
  245. package/dist/lib/eval-case-generator.js +1 -1
  246. package/dist/lib/eval-conversation-handler.js +1 -1
  247. package/dist/lib/eval-executor.js +1 -1
  248. package/dist/lib/eval-grader.js +1 -1
  249. package/dist/lib/eval-meta-judge.js +1 -1
  250. package/dist/lib/eval-resume.js +1 -1
  251. package/dist/lib/eval-run-batch.js +1 -1
  252. package/dist/lib/eval-trigger-tester.js +1 -1
  253. package/dist/lib/evolution-enrolment.js +1 -1
  254. package/dist/lib/harbor-readiness.js +1 -1
  255. package/dist/lib/hooks-capture-gate.js +1 -1
  256. package/dist/lib/insights/generation/installed-asks.js +1 -1
  257. package/dist/lib/insights/nomination/nominate.js +1 -1
  258. package/dist/lib/instruction-proposer.js +1 -1
  259. package/dist/lib/instruction-snapshot.js +1 -1
  260. package/dist/lib/instructions/acceptance/accept-events.js +1 -1
  261. package/dist/lib/instructions/acceptance/accept-hook.js +1 -1
  262. package/dist/lib/instructions/acceptance/agent-instruction-files.js +1 -1
  263. package/dist/lib/instructions/acceptance/hook-install.js +1 -1
  264. package/dist/lib/instructions/acceptance/reverse-applications.js +1 -1
  265. package/dist/lib/instructions/acceptance/suggestion-accept.js +1 -1
  266. package/dist/lib/jobs/handlers/cron-auto-accept-suggestions.js +1 -1
  267. package/dist/lib/jobs/handlers/cron-eval-autorun.js +1 -1
  268. package/dist/lib/jobs/handlers/cron-instruction-evolution.js +1 -1
  269. package/dist/lib/jobs/handlers/cron-instruction-training.js +1 -1
  270. package/dist/lib/jobs/handlers/cron-reconcile-attribution.js +1 -1
  271. package/dist/lib/jobs/handlers/cron-refine-cases.js +1 -1
  272. package/dist/lib/jobs/handlers/cron-refresh-pricing.js +1 -1
  273. package/dist/lib/jobs/handlers/cron-retire-unused-skills.js +1 -1
  274. package/dist/lib/jobs/handlers/cron-suggest-instructions.js +1 -1
  275. package/dist/lib/jobs/handlers/distill-memories.js +1 -1
  276. package/dist/lib/jobs/handlers/harbor-ab-verdict.js +1 -1
  277. package/dist/lib/jobs/handlers/harbor-ab.js +1 -1
  278. package/dist/lib/jobs/handlers/process-changelog.js +1 -1
  279. package/dist/lib/jobs/handlers/process-conversation.js +1 -1
  280. package/dist/lib/jobs/handlers/process-eval.js +1 -1
  281. package/dist/lib/jobs/handlers/process-insights.js +1 -1
  282. package/dist/lib/jobs/handlers/process-judge-review.js +1 -1
  283. package/dist/lib/jobs/handlers/process-launch.js +1 -1
  284. package/dist/lib/jobs/handlers/process-memory-ab.js +1 -1
  285. package/dist/lib/jobs/handlers/process-run-cleanup.js +1 -1
  286. package/dist/lib/jobs/handlers/process-session-goal.js +1 -1
  287. package/dist/lib/jobs/shed-retry.js +1 -1
  288. package/dist/lib/judge-review-runner.js +1 -1
  289. package/dist/lib/llm-client.js +1 -1
  290. package/dist/lib/llm-reply-parsers.js +1 -0
  291. package/dist/lib/manual-tasks.js +1 -1
  292. package/dist/lib/measurement-llm.js +1 -0
  293. package/dist/lib/pi-exec.js +1 -0
  294. package/dist/lib/projects/workspace/eval-workspace.js +1 -1
  295. package/dist/lib/projects/workspace/worktree.js +1 -1
  296. package/dist/lib/refine-cases.js +1 -1
  297. package/dist/lib/research-client.js +1 -1
  298. package/dist/lib/retraction.js +1 -1
  299. package/dist/lib/reviewer-detect.js +1 -1
  300. package/dist/lib/service-environment.js +1 -0
  301. package/dist/lib/setup-steps.js +1 -1
  302. package/dist/lib/telemetry.js +1 -1
  303. package/dist/lib/terminal-target-heartbeat.js +1 -1
  304. package/dist/lib/tool-cli-map.js +1 -0
  305. package/dist/lib/transcript-instruction-linking.js +1 -1
  306. package/dist/lib/transcript-sync.js +1 -1
  307. package/dist/lib/work-report.js +1 -1
  308. package/oclif.manifest.json +7 -1
  309. package/package.json +5 -4
  310. package/dist/chunk-4FSQSETR.js +0 -12
  311. package/dist/chunk-6WQG6C7F.js +0 -1
  312. package/dist/chunk-C42NTI2V.js +0 -3
  313. package/dist/chunk-F6DAHCJC.js +0 -20
  314. package/dist/chunk-GN3ZHQ46.js +0 -1
  315. package/dist/chunk-H3BCWSYO.js +0 -1
  316. package/dist/chunk-HLNHBZLI.js +0 -1
  317. package/dist/chunk-HLQZR4XJ.js +0 -13
  318. package/dist/chunk-J5HW45PZ.js +0 -48
  319. package/dist/chunk-JNFB22FD.js +0 -1
  320. package/dist/chunk-KCOJLTVX.js +0 -1
  321. package/dist/chunk-MLV2RHRE.js +0 -1
  322. package/dist/chunk-N7GK33PU.js +0 -1
  323. package/dist/chunk-NRYLYMDM.js +0 -1
  324. package/dist/chunk-OZNKMJ5B.js +0 -1
  325. package/dist/chunk-PPXOC7IV.js +0 -1
  326. package/dist/chunk-QXPYGN7I.js +0 -1
  327. package/dist/chunk-R4QIPG4V.js +0 -2
  328. package/dist/chunk-RGUCR7T6.js +0 -1
  329. package/dist/chunk-RO3ZZVCG.js +0 -1
  330. package/dist/chunk-SBGXIPRN.js +0 -2
  331. package/dist/chunk-SN3CHV66.js +0 -1
  332. package/dist/chunk-U3XKXJ3A.js +0 -5
  333. package/dist/chunk-UM4CBCXI.js +0 -1
  334. package/dist/chunk-WHQ4BDSM.js +0 -4
  335. package/dist/dist-45KJH5M7.js +0 -1
@@ -1 +1 @@
1
- import{a as p}from"../../chunk-ZGW3N3A6.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-5XK6LB2T.js";import"../../chunk-P2WSDHIA.js";import"../../chunk-25RDT32M.js";import"../../chunk-3AV3GTMP.js";import{e as u,h as l}from"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import{a as c}from"../../chunk-ZLLER3HP.js";import{Flags as t}from"@oclif/core";var d=class r extends p{static{c(this,"SuggestionsAuto")}static description="Show or change automatic adoption of high-confidence suggestions into CLAUDE.md";static examples=["<%= config.bin %> suggestions auto","<%= config.bin %> suggestions auto --off","<%= config.bin %> suggestions auto --on","<%= config.bin %> suggestions auto --off --project <project-id>"];static flags={on:t.boolean({description:"Enable automatic adoption",exclusive:["off"]}),off:t.boolean({description:"Disable automatic adoption",exclusive:["on"]}),project:t.string({description:"Apply to one project only (overrides the account default)"}),"max-per-window":t.integer({description:"How many rules the loop may adopt per project per window (0 stops it adopting)"}),"max-per-run":t.integer({description:"How many rules one nightly run may adopt"}),"window-days":t.integer({description:"Length of the budget window, in days"}),"min-score":t.string({description:"Minimum suggestion score the loop may adopt (0\u20131)"}),reset:t.boolean({description:"Clear the budget overrides at this level and fall back to the shipped defaults"})};async run(){let{flags:e}=await this.parse(r);this.requireAuth();let i=e.reset?{minScore:null,maxPerRun:null,maxPerWindow:null,windowDays:null}:{...e["min-score"]!==void 0?{minScore:Number(e["min-score"])}:{},...e["max-per-run"]!==void 0?{maxPerRun:e["max-per-run"]}:{},...e["max-per-window"]!==void 0?{maxPerWindow:e["max-per-window"]}:{},...e["window-days"]!==void 0?{windowDays:e["window-days"]}:{}},n=Object.keys(i).length>0;if(e.on||e.off||n){let s=e.on||e.off?!!e.on:void 0,g=await l("/api/settings/auto-accept",{...s!==void 0?{enabled:s}:{},...n?{limits:i}:{},...e.project?{projectId:e.project}:{}});if(n){let a=g.limits;this.log(`Budget for ${e.project?`project ${e.project}`:"your account"}: ${a.maxPerRun} per night, ${a.maxPerWindow} per ${a.windowDays} days, score >= ${a.minScore}.`)}if(s===void 0)return;let m=e.project?`project ${e.project}`:"your account";this.log(`Automatic adoption is now ${s?"ON":"OFF"} for ${m}.`),s||(this.log("Already-adopted rules stay in CLAUDE.md \u2014 remove one with:"),this.log(" rulemetric suggestions undo <instruction-id>"));return}let o=await u("/api/settings/auto-accept"+(e.project?`?projectId=${encodeURIComponent(e.project)}`:""));this.log(`Automatic adoption: ${o.effective?"ON":"OFF"} (from ${o.source})`),this.log(` account default: ${o.accountDefault?"on":"off"}`),e.project&&this.log(` project override: ${o.projectOverride===null?"none":o.projectOverride?"on":"off"}`),this.log(""),this.log("When on, the nightly loop may add \u2014 and only add \u2014 suggestions that:"),this.log(` \u2022 score >= ${o.limits.minScore} (already injected into every session)`),this.log(` \u2022 are '${o.limits.kind}' proposals; removals and trims always wait for you`),this.log(" \u2022 contain no unfilled placeholders"),this.log(` \u2022 at most ${o.limits.maxPerRun} per night, ${o.limits.maxPerWindow} per ${o.limits.windowDays} days \u2014 per PROJECT, counting every checkout of it (${f(o.limits.source)})`),this.log(""),this.log("Change the budget: rulemetric suggestions auto --max-per-window 20"),this.log("Back to defaults: rulemetric suggestions auto --reset"),this.log(""),this.log("Every adopted rule is a tagged block in CLAUDE.md; undo one with:"),this.log(" rulemetric suggestions undo <instruction-id>"),this.log("Turn it off entirely with: rulemetric suggestions auto --off")}};function f(r){let e=new Set(Object.values(r));if(e.size===1){let i=[...e][0];return i==="default"?"shipped defaults":`set on your ${i}`}return Object.entries(r).map(([i,n])=>`${i}: ${n}`).join(", ")}c(f,"describeSource");export{d as default};
1
+ import{a as p}from"../../chunk-MU7KKU3O.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-FKTT7A4H.js";import"../../chunk-VIFOLTZT.js";import"../../chunk-4FJYVYCU.js";import"../../chunk-3AV3GTMP.js";import{e as u,h as l}from"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import{a as c}from"../../chunk-ZLLER3HP.js";import{Flags as t}from"@oclif/core";var d=class r extends p{static{c(this,"SuggestionsAuto")}static description="Show or change automatic adoption of high-confidence suggestions into CLAUDE.md";static examples=["<%= config.bin %> suggestions auto","<%= config.bin %> suggestions auto --off","<%= config.bin %> suggestions auto --on","<%= config.bin %> suggestions auto --off --project <project-id>"];static flags={on:t.boolean({description:"Enable automatic adoption",exclusive:["off"]}),off:t.boolean({description:"Disable automatic adoption",exclusive:["on"]}),project:t.string({description:"Apply to one project only (overrides the account default)"}),"max-per-window":t.integer({description:"How many rules the loop may adopt per project per window (0 stops it adopting)"}),"max-per-run":t.integer({description:"How many rules one nightly run may adopt"}),"window-days":t.integer({description:"Length of the budget window, in days"}),"min-score":t.string({description:"Minimum suggestion score the loop may adopt (0\u20131)"}),reset:t.boolean({description:"Clear the budget overrides at this level and fall back to the shipped defaults"})};async run(){let{flags:e}=await this.parse(r);this.requireAuth();let i=e.reset?{minScore:null,maxPerRun:null,maxPerWindow:null,windowDays:null}:{...e["min-score"]!==void 0?{minScore:Number(e["min-score"])}:{},...e["max-per-run"]!==void 0?{maxPerRun:e["max-per-run"]}:{},...e["max-per-window"]!==void 0?{maxPerWindow:e["max-per-window"]}:{},...e["window-days"]!==void 0?{windowDays:e["window-days"]}:{}},n=Object.keys(i).length>0;if(e.on||e.off||n){let s=e.on||e.off?!!e.on:void 0,g=await l("/api/settings/auto-accept",{...s!==void 0?{enabled:s}:{},...n?{limits:i}:{},...e.project?{projectId:e.project}:{}});if(n){let a=g.limits;this.log(`Budget for ${e.project?`project ${e.project}`:"your account"}: ${a.maxPerRun} per night, ${a.maxPerWindow} per ${a.windowDays} days, score >= ${a.minScore}.`)}if(s===void 0)return;let m=e.project?`project ${e.project}`:"your account";this.log(`Automatic adoption is now ${s?"ON":"OFF"} for ${m}.`),s||(this.log("Already-adopted rules stay in CLAUDE.md \u2014 remove one with:"),this.log(" rulemetric suggestions undo <instruction-id>"));return}let o=await u("/api/settings/auto-accept"+(e.project?`?projectId=${encodeURIComponent(e.project)}`:""));this.log(`Automatic adoption: ${o.effective?"ON":"OFF"} (from ${o.source})`),this.log(` account default: ${o.accountDefault?"on":"off"}`),e.project&&this.log(` project override: ${o.projectOverride===null?"none":o.projectOverride?"on":"off"}`),this.log(""),this.log("When on, the nightly loop may add \u2014 and only add \u2014 suggestions that:"),this.log(` \u2022 score >= ${o.limits.minScore} (already injected into every session)`),this.log(` \u2022 are '${o.limits.kind}' proposals; removals and trims always wait for you`),this.log(" \u2022 contain no unfilled placeholders"),this.log(` \u2022 at most ${o.limits.maxPerRun} per night, ${o.limits.maxPerWindow} per ${o.limits.windowDays} days \u2014 per PROJECT, counting every checkout of it (${f(o.limits.source)})`),this.log(""),this.log("Change the budget: rulemetric suggestions auto --max-per-window 20"),this.log("Back to defaults: rulemetric suggestions auto --reset"),this.log(""),this.log("Every adopted rule is a tagged block in CLAUDE.md; undo one with:"),this.log(" rulemetric suggestions undo <instruction-id>"),this.log("Turn it off entirely with: rulemetric suggestions auto --off")}};function f(r){let e=new Set(Object.values(r));if(e.size===1){let i=[...e][0];return i==="default"?"shipped defaults":`set on your ${i}`}return Object.entries(r).map(([i,n])=>`${i}: ${n}`).join(", ")}c(f,"describeSource");export{d as default};
@@ -1 +1 @@
1
- import{a}from"../../chunk-WLVQJH4E.js";import{a as r}from"../../chunk-MODXEGCU.js";import{a as o}from"../../chunk-ZGW3N3A6.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-5XK6LB2T.js";import"../../chunk-P2WSDHIA.js";import"../../chunk-25RDT32M.js";import"../../chunk-3AV3GTMP.js";import{f as t}from"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import{a as e}from"../../chunk-ZLLER3HP.js";import{Args as m}from"@oclif/core";var s=class n extends o{static{e(this,"SuggestionsDismiss")}static description="Dismiss a suggestion so it will not re-surface for 30 days";static examples=["<%= config.bin %> suggestions dismiss <id>"];static args={id:m.string({description:"Suggestion UUID from `suggestions list`",required:!0})};async run(){let{args:i}=await this.parse(n);this.requireAuth(),a(i.id,"suggestion ID");let d=r(process.cwd());await t(`/api/instruction-suggestions/${encodeURIComponent(i.id)}/events`,{eventType:"dismissed",projectPath:d}),this.log("Dismissed. Won't re-surface for 30 days.")}};export{s as default};
1
+ import{a}from"../../chunk-WLVQJH4E.js";import{a as r}from"../../chunk-MODXEGCU.js";import{a as o}from"../../chunk-MU7KKU3O.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-FKTT7A4H.js";import"../../chunk-VIFOLTZT.js";import"../../chunk-4FJYVYCU.js";import"../../chunk-3AV3GTMP.js";import{f as t}from"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import{a as e}from"../../chunk-ZLLER3HP.js";import{Args as m}from"@oclif/core";var s=class n extends o{static{e(this,"SuggestionsDismiss")}static description="Dismiss a suggestion so it will not re-surface for 30 days";static examples=["<%= config.bin %> suggestions dismiss <id>"];static args={id:m.string({description:"Suggestion UUID from `suggestions list`",required:!0})};async run(){let{args:i}=await this.parse(n);this.requireAuth(),a(i.id,"suggestion ID");let d=r(process.cwd());await t(`/api/instruction-suggestions/${encodeURIComponent(i.id)}/events`,{eventType:"dismissed",projectPath:d}),this.log("Dismissed. Won't re-surface for 30 days.")}};export{s as default};
@@ -1,3 +1,3 @@
1
- import{a as u}from"../../chunk-MODXEGCU.js";import{a as g}from"../../chunk-ZGW3N3A6.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-5XK6LB2T.js";import"../../chunk-P2WSDHIA.js";import"../../chunk-25RDT32M.js";import"../../chunk-3AV3GTMP.js";import{e as d}from"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import{a}from"../../chunk-ZLLER3HP.js";import{Flags as j}from"@oclif/core";import{printTable as v}from"@oclif/table";var r=class p extends g{static{a(this,"SuggestionsList")}static description="List instruction suggestions for a project";static examples=["<%= config.bin %> suggestions list","<%= config.bin %> suggestions list --project /path/to/project"];static flags={project:j.string({char:"p",description:"Project path (defaults to current directory)"})};async run(){let{flags:m}=await this.parse(p);this.requireAuth();let l=u(m.project??process.cwd()),f=encodeURIComponent(l),s=await d(`/api/instruction-suggestions?projectPath=${f}&limit=10&groupByKind=true`);if(s.suggestions.length===0){this.log("No suggestions for this project. They are recomputed daily at 04:30 UTC by a server-side cron and run on your background worker \u2014 make sure one is running (`rulemetric evals agent`, or `rulemetric service install --worker-only`).");return}v({data:s.suggestions.map(e=>({ID:e.id,Action:(e.kind??"add")==="add"?"add":(e.kind??"add").toUpperCase(),Scope:e.instructionScope,Name:e.name,Tokens:e.estimatedTokens?String(e.estimatedTokens):"-",Score:e.score.toFixed(2),Reason:e.reason.length>60?e.reason.slice(0,59)+"\u2026":e.reason})),columns:["ID","Action","Scope","Name","Tokens","Score","Reason"]});let o=s.suggestions.filter(e=>e.kind==="remove");if(o.length>0){let e=o.reduce((h,k)=>h+(k.estimatedTokens??0),0);this.log(`
1
+ import{a as u}from"../../chunk-MODXEGCU.js";import{a as g}from"../../chunk-MU7KKU3O.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-FKTT7A4H.js";import"../../chunk-VIFOLTZT.js";import"../../chunk-4FJYVYCU.js";import"../../chunk-3AV3GTMP.js";import{e as d}from"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import{a}from"../../chunk-ZLLER3HP.js";import{Flags as j}from"@oclif/core";import{printTable as v}from"@oclif/table";var r=class p extends g{static{a(this,"SuggestionsList")}static description="List instruction suggestions for a project";static examples=["<%= config.bin %> suggestions list","<%= config.bin %> suggestions list --project /path/to/project"];static flags={project:j.string({char:"p",description:"Project path (defaults to current directory)"})};async run(){let{flags:m}=await this.parse(p);this.requireAuth();let l=u(m.project??process.cwd()),f=encodeURIComponent(l),s=await d(`/api/instruction-suggestions?projectPath=${f}&limit=10&groupByKind=true`);if(s.suggestions.length===0){this.log("No suggestions for this project. They are recomputed daily at 04:30 UTC by a server-side cron and run on your background worker \u2014 make sure one is running (`rulemetric evals agent`, or `rulemetric service install --worker-only`).");return}v({data:s.suggestions.map(e=>({ID:e.id,Action:(e.kind??"add")==="add"?"add":(e.kind??"add").toUpperCase(),Scope:e.instructionScope,Name:e.name,Tokens:e.estimatedTokens?String(e.estimatedTokens):"-",Score:e.score.toFixed(2),Reason:e.reason.length>60?e.reason.slice(0,59)+"\u2026":e.reason})),columns:["ID","Action","Scope","Name","Tokens","Score","Reason"]});let o=s.suggestions.filter(e=>e.kind==="remove");if(o.length>0){let e=o.reduce((h,k)=>h+(k.estimatedTokens??0),0);this.log(`
2
2
  ${o.length} retirement proposal(s) \u2014 accepting one archives the instruction (reversible: rulemetric instructions restore <instruction-id>).`+(e>0?` Together they cost roughly ${e} tokens of context per session.`:""))}let t=s.stats??{},i=(t.surfaced??0)+(t.surfaced_with_content??0)+(t.proxy_injected??0),n=(t.accepted??0)+(t.promoted??0),c=t.dismissed??0;i+n+c>0&&this.log(`
3
3
  Lifetime for this project: ${i} surfaced \xB7 ${n} accepted \xB7 ${c} dismissed`)}};export{r as default};
@@ -1 +1 @@
1
- import{a as i}from"../../chunk-ZGW3N3A6.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-5XK6LB2T.js";import"../../chunk-P2WSDHIA.js";import"../../chunk-25RDT32M.js";import"../../chunk-3AV3GTMP.js";import"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import{a as r}from"../../chunk-ZLLER3HP.js";var e=class extends i{static{r(this,"SuggestionsRefresh")}static description="Information about refreshing instruction suggestions";static examples=["<%= config.bin %> suggestions refresh"];async run(){this.log("Suggestions are recomputed automatically: a server-side cron enqueues a job daily at 04:30 UTC, and your background worker picks it up \u2014 there is no manual trigger."),this.log("Just make sure a worker is running: `rulemetric evals agent` (foreground), or install it persistently with `rulemetric service install --worker-only`.")}};export{e as default};
1
+ import{a as i}from"../../chunk-MU7KKU3O.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-FKTT7A4H.js";import"../../chunk-VIFOLTZT.js";import"../../chunk-4FJYVYCU.js";import"../../chunk-3AV3GTMP.js";import"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import{a as r}from"../../chunk-ZLLER3HP.js";var e=class extends i{static{r(this,"SuggestionsRefresh")}static description="Information about refreshing instruction suggestions";static examples=["<%= config.bin %> suggestions refresh"];async run(){this.log("Suggestions are recomputed automatically: a server-side cron enqueues a job daily at 04:30 UTC, and your background worker picks it up \u2014 there is no manual trigger."),this.log("Just make sure a worker is running: `rulemetric evals agent` (foreground), or install it persistently with `rulemetric service install --worker-only`.")}};export{e as default};
@@ -1 +1 @@
1
- import{a as f,c as g}from"../../chunk-6QLZT7EP.js";import{a as l}from"../../chunk-WLVQJH4E.js";import{d as p}from"../../chunk-4FSQSETR.js";import"../../chunk-VH2EAP4J.js";import"../../chunk-J6QMBMC2.js";import"../../chunk-UYPBIXF2.js";import{d as u}from"../../chunk-OZNKMJ5B.js";import"../../chunk-LJRE3LZ2.js";import{a as m}from"../../chunk-MODXEGCU.js";import"../../chunk-OZQNSELV.js";import"../../chunk-HFGO5762.js";import"../../chunk-VS5ROGDP.js";import"../../chunk-UPFH72PF.js";import{a as d}from"../../chunk-ZGW3N3A6.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-5XK6LB2T.js";import"../../chunk-P2WSDHIA.js";import"../../chunk-25RDT32M.js";import"../../chunk-3AV3GTMP.js";import{f as a}from"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import{a as c}from"../../chunk-ZLLER3HP.js";import{relative as h}from"node:path";import{Args as P}from"@oclif/core";var o=class I extends d{static{c(this,"SuggestionsUndo")}static description="Remove an adopted instruction from this project's agent instruction files and stop it being re-proposed";static examples=["<%= config.bin %> suggestions undo <instruction-id>"];static args={instructionId:P.string({description:"Instruction ID from the rulemetric:start marker in the instruction file",required:!0})};async run(){let{args:r}=await this.parse(I);this.requireAuth(),l(r.instructionId,"instruction ID");let e=m(process.cwd()),i=process.cwd(),v=u(i).map(t=>h(i,t.path)),w=p(i,r.instructionId).map(t=>h(i,t.path)),A=await f({dir:process.cwd(),instructionId:r.instructionId,projectPath:e}),n=null,s=null;try{n=(await a("/api/instruction-suggestions/undo",{projectPath:e,instructionId:r.instructionId})).name}catch(t){s=t instanceof Error?t.message:String(t)}for(let t of g({stripped:w,searched:v,reversals:A,name:n,instructionId:r.instructionId,dismissalError:s}))this.log(t);s&&this.exit(1)}};export{o as default};
1
+ import{a as f,c as g}from"../../chunk-VU24IH5S.js";import{a as l}from"../../chunk-WLVQJH4E.js";import{d as p}from"../../chunk-2UK4DZFW.js";import"../../chunk-3FNTV2TW.js";import"../../chunk-JTYVBR5H.js";import"../../chunk-B5X6HCBK.js";import"../../chunk-LJRE3LZ2.js";import{a as m}from"../../chunk-MODXEGCU.js";import"../../chunk-OZQNSELV.js";import"../../chunk-HFGO5762.js";import"../../chunk-VS5ROGDP.js";import{d as u}from"../../chunk-GYNZ3OZF.js";import"../../chunk-UPFH72PF.js";import{a as d}from"../../chunk-MU7KKU3O.js";import"../../chunk-IZTIYR5Q.js";import"../../chunk-FKTT7A4H.js";import"../../chunk-VIFOLTZT.js";import"../../chunk-4FJYVYCU.js";import"../../chunk-3AV3GTMP.js";import{f as a}from"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import{a as c}from"../../chunk-ZLLER3HP.js";import{relative as h}from"node:path";import{Args as P}from"@oclif/core";var o=class I extends d{static{c(this,"SuggestionsUndo")}static description="Remove an adopted instruction from this project's agent instruction files and stop it being re-proposed";static examples=["<%= config.bin %> suggestions undo <instruction-id>"];static args={instructionId:P.string({description:"Instruction ID from the rulemetric:start marker in the instruction file",required:!0})};async run(){let{args:r}=await this.parse(I);this.requireAuth(),l(r.instructionId,"instruction ID");let e=m(process.cwd()),i=process.cwd(),v=u(i).map(t=>h(i,t.path)),w=p(i,r.instructionId).map(t=>h(i,t.path)),A=await f({dir:process.cwd(),instructionId:r.instructionId,projectPath:e}),n=null,s=null;try{n=(await a("/api/instruction-suggestions/undo",{projectPath:e,instructionId:r.instructionId})).name}catch(t){s=t instanceof Error?t.message:String(t)}for(let t of g({stripped:w,searched:v,reversals:A,name:n,instructionId:r.instructionId,dismissalError:s}))this.log(t);s&&this.exit(1)}};export{o as default};
@@ -1 +1 @@
1
- import{a as F}from"../chunk-DVN7GFJG.js";import{a as M}from"../chunk-O2SDD2WW.js";import{a as E}from"../chunk-GBMF3TP6.js";import{a as O}from"../chunk-WSCK4WNK.js";import{a as D}from"../chunk-4Z5ECCJE.js";import"../chunk-ARNZQBMX.js";import{a as B,b as S,c as R}from"../chunk-3UPHCQWI.js";import{a as $}from"../chunk-WJZY5Y22.js";import"../chunk-7AWFSFR7.js";import"../chunk-NBTC5FNV.js";import"../chunk-TU2AR6XO.js";import{a as u,b as T,d as a,e as v}from"../chunk-NJ5UED3H.js";import"../chunk-CYYDQFRY.js";import"../chunk-ZXF5NE54.js";import"../chunk-F6DAHCJC.js";import{a as X}from"../chunk-3AV3GTMP.js";import"../chunk-APDRR6FS.js";import"../chunk-J5HW45PZ.js";import"../chunk-PZNWH4VQ.js";import{a as m}from"../chunk-ZLLER3HP.js";import{useState as w,useEffect as nt,useCallback as G,useRef as st}from"react";import{render as U,Box as c,Text as o,useInput as at,useApp as lt}from"ink";import{Fragment as ht,jsx as t,jsxs as h}from"react/jsx-runtime";var dt=90;function J(){let s=process.stdout.columns||80,n=Math.min(160,s),i=n>=dt,d=i?Math.floor(n/2):n,b=Math.max(6,Math.min(10,d-34));return{dashWidth:n,wide:i,halfWidth:d,barWidth:b}}m(J,"getLayout");function z({active:s,providerName:n}){return h(c,{justifyContent:"space-between",paddingX:1,children:[t(c,{gap:1,children:u.map(i=>t(o,{bold:s===i,color:s===i?a:v,children:s===i?`[ ${T[i]} ]`:` ${T[i]} `},i))}),n&&t(o,{color:a,bold:!0,children:n})]})}m(z,"PeriodTabs");function H({width:s,insightsFocused:n=!1}){return t(c,{borderStyle:"round",borderColor:v,width:s,justifyContent:"center",paddingX:1,children:h(o,{children:[t(o,{color:a,bold:!0,children:"<"}),t(o,{color:a,children:">"}),t(o,{dimColor:!0,children:" switch "}),t(o,{color:a,bold:!0,children:"q"}),t(o,{dimColor:!0,children:" quit "}),t(o,{color:a,bold:!0,children:"s"}),h(o,{dimColor:!0,children:[" ",n?"back":"suggestions"," "]}),t(o,{color:a,bold:!0,children:"1"}),t(o,{dimColor:!0,children:"-"}),t(o,{color:a,bold:!0,children:"5"}),t(o,{dimColor:!0,children:" period "}),t(o,{color:a,bold:!0,children:"r"}),t(o,{dimColor:!0,children:" refresh "}),t(o,{color:a,bold:!0,children:"n"}),t(o,{dimColor:!0,children:" new session "}),t(o,{color:a,bold:!0,children:"l"}),t(o,{dimColor:!0,children:" launcher"})]})})}m(H,"StatusBar");function ct({wide:s,width:n,children:i}){return s?t(c,{width:n,children:i}):t(ht,{children:i})}m(ct,"Row");function ut({initialLocal:s,initialApi:n,initialInstructions:i,initialPeriod:d,initialLauncherFocused:b=!1}){let{exit:j}=lt(),[e,L]=w(d),[K,Q]=w(s),[W,V]=w(n),[Z,tt]=w(i),[ot,k]=w(!1),[p,I]=w(!1),[Y,_]=w(b),et=X()?.orgSlug??null,y=st(null),it=J(),g=G(async()=>{k(!0);let[r,x,P]=await Promise.all([B(),S(),R()]);Q(r),V(x),tt(P),k(!1)},[]);nt(()=>{let r=setInterval(g,3e4);return()=>clearInterval(r)},[g]);let f=G(r=>{r!==e&&(L(r),y.current&&clearTimeout(y.current),y.current=setTimeout(g,300))},[e,g]);at((r,x)=>{if(Y)return;if(r==="q"){if(p){I(!1);return}j();return}if(r==="s"){I(A=>!A);return}if(x.escape&&p){I(!1);return}if(r==="l"&&!p){_(!0);return}if(p)return;if(r==="r"){g();return}if(r==="n"){import("../dist-45KJH5M7.js").then(({getLauncher:A})=>{A("claude_code").launch({projectPath:process.cwd(),mode:"interactive"})});return}let P=u.indexOf(e);x.leftArrow?f(u[(P-1+u.length)%u.length]):x.rightArrow||x.tab?f(u[(P+1)%u.length]):r==="1"?f("today"):r==="2"?f("week"):r==="3"?f("30days"):r==="4"?f("month"):r==="5"&&f("all")});let{dashWidth:l,wide:q,halfWidth:rt,barWidth:C}=it,N=q?rt:l;return ot?h(c,{flexDirection:"column",width:l,children:[t(z,{active:e}),t(c,{borderStyle:"round",borderColor:a,paddingX:1,width:l,children:h(o,{dimColor:!0,children:["Loading ",T[e],"..."]})}),t(H,{width:l})]}):Y?t(c,{flexDirection:"column",width:l,children:t($,{onClose:()=>_(!1),activeOrg:et,defaultProjectPath:process.cwd(),width:l})}):h(c,{flexDirection:"column",width:l,children:[t(z,{active:e}),t(D,{local:K,api:W,label:T[e],width:l}),h(ct,{wide:q,width:l,children:[t(O,{api:W,width:N,barWidth:C}),t(F,{api:W,width:N,barWidth:C})]}),t(E,{instructions:Z,width:l,barWidth:C}),t(M,{api:W,width:l,focused:p}),t(H,{width:l,insightsFocused:p})]})}m(ut,"InteractiveDashboard");async function yt(s="week",n={}){let[i,d,b]=await Promise.all([B(),S(),R()]);if(process.stdin.isTTY&&process.stdout.isTTY){let{waitUntilExit:e}=U(t(ut,{initialLocal:i,initialApi:d,initialInstructions:b,initialPeriod:s,initialLauncherFocused:n.openLauncher??!1}));await e()}else{let e=J(),{unmount:L}=U(h(c,{flexDirection:"column",width:e.dashWidth,children:[t(D,{local:i,api:d,label:T[s],width:e.dashWidth}),t(O,{api:d,width:e.dashWidth,barWidth:e.barWidth}),t(F,{api:d,width:e.dashWidth,barWidth:e.barWidth}),t(E,{instructions:b,width:e.dashWidth,barWidth:e.barWidth}),t(M,{api:d,width:e.dashWidth})]}),{patchConsole:!1});L()}}m(yt,"renderDashboard");export{yt as renderDashboard};
1
+ import{a as O}from"../chunk-DVN7GFJG.js";import{a as E}from"../chunk-O2SDD2WW.js";import{a as M}from"../chunk-GBMF3TP6.js";import{a as U}from"../chunk-IQEZ3T5A.js";import{a as D}from"../chunk-WSCK4WNK.js";import{a as R}from"../chunk-4Z5ECCJE.js";import"../chunk-ARNZQBMX.js";import{a as B,b as S,c as F}from"../chunk-35YVHBLL.js";import{a as z}from"../chunk-XMTKP74H.js";import"../chunk-5Z3H5JHK.js";import"../chunk-NBTC5FNV.js";import"../chunk-TU2AR6XO.js";import{a as u,b as T,d as a,e as v}from"../chunk-NJ5UED3H.js";import"../chunk-44TYRMIS.js";import"../chunk-ZXF5NE54.js";import"../chunk-T75KAEJO.js";import{a as G}from"../chunk-3AV3GTMP.js";import"../chunk-76UKAFNM.js";import"../chunk-42A4PUW7.js";import"../chunk-XQYAVSUQ.js";import"../chunk-PZNWH4VQ.js";import{a as b}from"../chunk-ZLLER3HP.js";import{useState as h,useEffect as lt,useCallback as H,useRef as dt}from"react";import{render as J,Box as c,Text as e,useInput as ct,useApp as ut}from"ink";import{Fragment as wt,jsx as t,jsxs as f}from"react/jsx-runtime";var ht=90;function V(){let s=process.stdout.columns||80,n=Math.min(160,s),r=n>=ht,d=r?Math.floor(n/2):n,x=Math.max(6,Math.min(10,d-34));return{dashWidth:n,wide:r,halfWidth:d,barWidth:x}}b(V,"getLayout");function K({active:s,providerName:n}){return f(c,{justifyContent:"space-between",paddingX:1,children:[t(c,{gap:1,children:u.map(r=>t(e,{bold:s===r,color:s===r?a:v,children:s===r?`[ ${T[r]} ]`:` ${T[r]} `},r))}),n&&t(e,{color:a,bold:!0,children:n})]})}b(K,"PeriodTabs");function Q({width:s,insightsFocused:n=!1}){return t(c,{borderStyle:"round",borderColor:v,width:s,justifyContent:"center",paddingX:1,children:f(e,{children:[t(e,{color:a,bold:!0,children:"<"}),t(e,{color:a,children:">"}),t(e,{dimColor:!0,children:" switch "}),t(e,{color:a,bold:!0,children:"q"}),t(e,{dimColor:!0,children:" quit "}),t(e,{color:a,bold:!0,children:"s"}),f(e,{dimColor:!0,children:[" ",n?"back":"suggestions"," "]}),t(e,{color:a,bold:!0,children:"1"}),t(e,{dimColor:!0,children:"-"}),t(e,{color:a,bold:!0,children:"5"}),t(e,{dimColor:!0,children:" period "}),t(e,{color:a,bold:!0,children:"r"}),t(e,{dimColor:!0,children:" refresh "}),t(e,{color:a,bold:!0,children:"n"}),t(e,{dimColor:!0,children:" new session "}),t(e,{color:a,bold:!0,children:"m"}),t(e,{dimColor:!0,children:" measurement "}),t(e,{color:a,bold:!0,children:"l"}),t(e,{dimColor:!0,children:" launcher"})]})})}b(Q,"StatusBar");function ft({wide:s,width:n,children:r}){return s?t(c,{width:n,children:r}):t(wt,{children:r})}b(ft,"Row");function mt({initialLocal:s,initialApi:n,initialInstructions:r,initialPeriod:d,initialLauncherFocused:x=!1}){let{exit:j}=ut(),[o,L]=h(d),[Z,tt]=h(s),[W,et]=h(n),[ot,it]=h(r),[rt,k]=h(!1),[Y,_]=h(!1),[m,I]=h(!1),[q,N]=h(x),nt=G()?.orgSlug??null,y=dt(null),st=V(),g=H(async()=>{k(!0);let[i,p,P]=await Promise.all([B(),S(),F()]);tt(i),et(p),it(P),k(!1)},[]);lt(()=>{let i=setInterval(g,3e4);return()=>clearInterval(i)},[g]);let w=H(i=>{i!==o&&(L(i),y.current&&clearTimeout(y.current),y.current=setTimeout(g,300))},[o,g]);ct((i,p)=>{if(q||Y)return;if(i==="q"){if(m){I(!1);return}j();return}if(i==="s"){I(A=>!A);return}if(p.escape&&m){I(!1);return}if(i==="m"&&!m){_(!0);return}if(i==="l"&&!m){N(!0);return}if(m)return;if(i==="r"){g();return}if(i==="n"){import("../dist-GSHUC7UU.js").then(({getLauncher:A})=>{A("claude_code").launch({projectPath:process.cwd(),mode:"interactive"})});return}let P=u.indexOf(o);p.leftArrow?w(u[(P-1+u.length)%u.length]):p.rightArrow||p.tab?w(u[(P+1)%u.length]):i==="1"?w("today"):i==="2"?w("week"):i==="3"?w("30days"):i==="4"?w("month"):i==="5"&&w("all")});let{dashWidth:l,wide:X,halfWidth:at,barWidth:C}=st,$=X?at:l;return Y?t(U,{width:l,onClose:()=>_(!1)}):rt?f(c,{flexDirection:"column",width:l,children:[t(K,{active:o}),t(c,{borderStyle:"round",borderColor:a,paddingX:1,width:l,children:f(e,{dimColor:!0,children:["Loading ",T[o],"..."]})}),t(Q,{width:l})]}):q?t(c,{flexDirection:"column",width:l,children:t(z,{onClose:()=>N(!1),activeOrg:nt,defaultProjectPath:process.cwd(),width:l})}):f(c,{flexDirection:"column",width:l,children:[t(K,{active:o}),t(R,{local:Z,api:W,label:T[o],width:l}),f(ft,{wide:X,width:l,children:[t(D,{api:W,width:$,barWidth:C}),t(O,{api:W,width:$,barWidth:C})]}),t(M,{instructions:ot,width:l,barWidth:C}),t(E,{api:W,width:l,focused:m}),t(Q,{width:l,insightsFocused:m})]})}b(mt,"InteractiveDashboard");async function Bt(s="week",n={}){let[r,d,x]=await Promise.all([B(),S(),F()]);if(process.stdin.isTTY&&process.stdout.isTTY){let{waitUntilExit:o}=J(t(mt,{initialLocal:r,initialApi:d,initialInstructions:x,initialPeriod:s,initialLauncherFocused:n.openLauncher??!1}));await o()}else{let o=V(),{unmount:L}=J(f(c,{flexDirection:"column",width:o.dashWidth,children:[t(R,{local:r,api:d,label:T[s],width:o.dashWidth}),t(D,{api:d,width:o.dashWidth,barWidth:o.barWidth}),t(O,{api:d,width:o.dashWidth,barWidth:o.barWidth}),t(M,{instructions:x,width:o.dashWidth,barWidth:o.barWidth}),t(E,{api:d,width:o.dashWidth})]}),{patchConsole:!1});L()}}b(Bt,"renderDashboard");export{Bt as renderDashboard};
@@ -1 +1 @@
1
- import{a,b,c}from"../chunk-3UPHCQWI.js";import"../chunk-F6DAHCJC.js";import"../chunk-APDRR6FS.js";import"../chunk-J5HW45PZ.js";import"../chunk-PZNWH4VQ.js";import"../chunk-ZLLER3HP.js";export{b as loadApiInsights,c as loadLinkedInstructions,a as loadLocalStats};
1
+ import{a,b,c}from"../chunk-35YVHBLL.js";import"../chunk-T75KAEJO.js";import"../chunk-76UKAFNM.js";import"../chunk-42A4PUW7.js";import"../chunk-XQYAVSUQ.js";import"../chunk-PZNWH4VQ.js";import"../chunk-ZLLER3HP.js";export{b as loadApiInsights,c as loadLinkedInstructions,a as loadLocalStats};
@@ -1 +1 @@
1
- import{a}from"../../chunk-WJZY5Y22.js";import"../../chunk-7AWFSFR7.js";import"../../chunk-NBTC5FNV.js";import"../../chunk-TU2AR6XO.js";import"../../chunk-NJ5UED3H.js";import"../../chunk-CYYDQFRY.js";import"../../chunk-ZXF5NE54.js";import"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{a as LauncherOverlay};
1
+ import{a}from"../../chunk-XMTKP74H.js";import"../../chunk-5Z3H5JHK.js";import"../../chunk-NBTC5FNV.js";import"../../chunk-TU2AR6XO.js";import"../../chunk-NJ5UED3H.js";import"../../chunk-44TYRMIS.js";import"../../chunk-ZXF5NE54.js";import"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{a as LauncherOverlay};
@@ -1 +1 @@
1
- import{a,b,c,d}from"../../chunk-CYYDQFRY.js";import"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{c as cancelLaunchJob,b as createLaunchJob,a as fetchLaunchJobs,d as listLaunchers};
1
+ import{a,b,c,d}from"../../chunk-44TYRMIS.js";import"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{c as cancelLaunchJob,b as createLaunchJob,a as fetchLaunchJobs,d as listLaunchers};
@@ -1 +1 @@
1
- import{a}from"../../chunk-7AWFSFR7.js";import"../../chunk-CYYDQFRY.js";import"../../chunk-APDRR6FS.js";import"../../chunk-J5HW45PZ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{a as useLaunchJobs};
1
+ import{a}from"../../chunk-5Z3H5JHK.js";import"../../chunk-44TYRMIS.js";import"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{a as useLaunchJobs};
@@ -0,0 +1 @@
1
+ import{a}from"../../chunk-IQEZ3T5A.js";import"../../chunk-NJ5UED3H.js";import"../../chunk-76UKAFNM.js";import"../../chunk-42A4PUW7.js";import"../../chunk-XQYAVSUQ.js";import"../../chunk-PZNWH4VQ.js";import"../../chunk-ZLLER3HP.js";export{a as MeasurementPanel};
@@ -0,0 +1 @@
1
+ import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x}from"./chunk-T75KAEJO.js";import"./chunk-ZLLER3HP.js";export{c as ClaudeCodeAdapter,i as CodexAdapter,f as CopilotAgentAdapter,d as CursorAdapter,j as PiAdapter,g as RuleMetricMcpAdapter,b as SESSION_EVENT_TYPES,a as SESSION_TOOLS,e as VSCodeCopilotAdapter,t as buildTmuxAttachCommand,s as buildTmuxHasSessionArgs,q as buildTmuxLaunchArgs,r as buildTmuxSendKeysArgs,l as detectTool,n as discoverAllSessions,h as freshCodexState,k as getAdapter,m as getAllProviders,v as getAvailableLaunchers,u as getLauncher,o as getProvider,w as sanitizeBranch,p as sanitizeTmuxName,x as worktreePathFor};
@@ -0,0 +1,377 @@
1
+ #!/usr/bin/env python3
2
+ """The COST half of a Harbor A/B: a per-trial cost delta with an interval.
3
+
4
+ python3 cost_delta.py <baseline-job-dir> <with-job-dir> [--json]
5
+
6
+ `verdict_bridge.py` gives the REWARD half of an A/B a Newcombe CI, a Fisher p
7
+ and a graduation gate that refuses rather than publish an undecided result.
8
+ Until 2026-09-02 the cost half had none of that: `summarize.py` summed cost over
9
+ trials and `run.sh` printed the difference of two sums. That is wrong twice.
10
+
11
+ 1. A sum has no variance, so "the with-arm was $0.09 cheaper" could never be
12
+ told apart from noise. Cost is right-skewed -- one flailing trial costs
13
+ several times a clean one -- so a difference of totals is exactly the shape
14
+ that reads as a finding and is a coin flip.
15
+ 2. A sum is confounded by TRIAL COUNT. Unequal `n_trials` between arms (a
16
+ crashed trial, a different `-l` cap) puts "this arm ran more trials" into
17
+ the number and calls it cost. The per-trial MEAN is the comparable unit.
18
+
19
+ Estimator: percentile bootstrap on the difference of per-trial means. Not a
20
+ t-interval -- that assumes a symmetry cost does not have. Seeded, so a published
21
+ verdict is reproducible from the same job dirs.
22
+
23
+ The gate mirrors verdict_bridge.py on purpose: below COST_MIN_N per arm, or a CI
24
+ spanning zero, the decision is `undecided`. "We could not tell" must never be
25
+ published as "it is cheaper".
26
+
27
+ Two honesty rules are load-bearing:
28
+ * An arm with no per-trial cost output (the remote agent bills the key
29
+ directly) is UNKNOWN, and this tool exits non-zero. Reading it as $0 would
30
+ print the largest saving the tool is capable of printing.
31
+ * The REWARD delta travels with the cost number. Cheaper because the with-arm
32
+ gave up early is not a saving, and the person writing the case study must
33
+ not have to look that up separately.
34
+
35
+ Dependency-free and importable, so the contract test can drive it on synthetic
36
+ job dirs with no harbor install.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import json
42
+ import random
43
+ import sys
44
+ from pathlib import Path
45
+ from typing import Any
46
+
47
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
48
+ from summarize import summarize # noqa: E402
49
+
50
+ # Same floor as verdict_bridge.py's GRADUATION_MIN_N. A cost saving and a reward
51
+ # gain are published side by side; letting cost graduate on a smaller n would
52
+ # make the cheaper number the easy one to quote.
53
+ import provenance
54
+
55
+ COST_MIN_N = 25
56
+ # Harbor rewards on these tasks are pass/fail. A partial reward is not a success:
57
+ # you cannot redo 60% of a task, so the redo it would price is a whole trial.
58
+ SUCCESS_THRESHOLD = 1.0
59
+ # Above this share of bootstrap resamples with no successes in an arm, the ratio
60
+ # has no sampling distribution worth reporting and the verdict is refused by name.
61
+ MAX_UNDEFINED_FRACTION = 0.05
62
+ RESAMPLES = 10000
63
+ SEED = 20260902
64
+
65
+
66
+ def arm(job_dir: Path) -> dict[str, Any]:
67
+ """Per-trial cost series for one arm, plus its reward."""
68
+ s = summarize(job_dir)
69
+ trials = s.get("trials") or []
70
+ costs = [float(t["cost_usd"]) for t in trials]
71
+ # Paired (cost, reward) for the per-success ratio. A trial whose reward is
72
+ # UNKNOWN (harbor wrote no verdict) is dropped from this pairing rather than
73
+ # scored as a failure -- a crashed trial must not read as an instruction that
74
+ # did not work. It still counts in the per-trial cost above: it was paid for.
75
+ paired = [(float(t["cost_usd"]), float(t["reward"])) for t in trials if t.get("reward") is not None]
76
+ successes = sum(1 for _, r in paired if r >= SUCCESS_THRESHOLD)
77
+ return {
78
+ "job": s["job"],
79
+ "n": len(costs),
80
+ "n_trials_reported": s.get("n_trials"),
81
+ "costs": costs,
82
+ "cost_sum_usd": round(sum(costs), 6),
83
+ "cost_mean_usd": round(sum(costs) / len(costs), 6) if costs else None,
84
+ "duration_mean_s": round(sum(t["duration_ms"] for t in trials) / len(trials) / 1000, 3)
85
+ if trials
86
+ else None,
87
+ "turns_mean": round(sum(t["turns"] for t in trials) / len(trials), 3) if trials else None,
88
+ "agent_errors": sum(1 for t in trials if t["is_error"]) if trials else 0,
89
+ "reward_mean": s.get("reward_mean"),
90
+ "paired": paired,
91
+ "n_scored": len(paired),
92
+ "successes": successes,
93
+ # None, not infinity: an arm that never passed has no cost PER SUCCESS.
94
+ # Every trial was paid for and none of them produced one.
95
+ "cost_per_success_usd": round(sum(c for c, _ in paired) / successes, 6) if successes else None,
96
+ }
97
+
98
+
99
+ def bootstrap_ci(a: list[float], b: list[float], resamples: int = RESAMPLES, seed: int = SEED):
100
+ """Percentile 95% CI for mean(b) - mean(a). Seeded: same input, same interval."""
101
+ rng = random.Random(seed)
102
+ na, nb = len(a), len(b)
103
+ deltas = []
104
+ for _ in range(resamples):
105
+ ma = sum(a[rng.randrange(na)] for _ in range(na)) / na
106
+ mb = sum(b[rng.randrange(nb)] for _ in range(nb)) / nb
107
+ deltas.append(mb - ma)
108
+ deltas.sort()
109
+ lo = deltas[int(0.025 * (resamples - 1))]
110
+ hi = deltas[int(0.975 * (resamples - 1))]
111
+ return round(lo, 6), round(hi, 6)
112
+
113
+
114
+
115
+ def per_success_ci(
116
+ a: list[tuple[float, float]],
117
+ b: list[tuple[float, float]],
118
+ resamples: int = RESAMPLES,
119
+ seed: int = SEED,
120
+ ):
121
+ """95% CI for cost-per-success(b) - cost-per-success(a), and the share of
122
+ resamples where the ratio was undefined.
123
+
124
+ Resampling is PAIRED within an arm -- the trial that cost this much is the
125
+ trial that did or did not pass -- so the ratio's numerator and denominator
126
+ move together the way they do in a real run. Resamples where either arm drew
127
+ no successes have no ratio; they are counted, not quietly dropped, because
128
+ "the interval came from the 40% of resamples that happened to work" is not an
129
+ interval anyone should publish.
130
+ """
131
+ rng = random.Random(seed + 1)
132
+ na, nb = len(a), len(b)
133
+ deltas: list[float] = []
134
+ undefined = 0
135
+ for _ in range(resamples):
136
+ sa = [a[rng.randrange(na)] for _ in range(na)]
137
+ sb = [b[rng.randrange(nb)] for _ in range(nb)]
138
+ ka = sum(1 for _, r in sa if r >= SUCCESS_THRESHOLD)
139
+ kb = sum(1 for _, r in sb if r >= SUCCESS_THRESHOLD)
140
+ if ka == 0 or kb == 0:
141
+ undefined += 1
142
+ continue
143
+ deltas.append(sum(c for c, _ in sb) / kb - sum(c for c, _ in sa) / ka)
144
+ fraction = undefined / resamples
145
+ if not deltas:
146
+ return None, None, fraction
147
+ deltas.sort()
148
+ n = len(deltas)
149
+ return round(deltas[int(0.025 * (n - 1))], 6), round(deltas[int(0.975 * (n - 1))], 6), fraction
150
+
151
+
152
+ def per_success_block(b: dict[str, Any], w: dict[str, Any], min_n: int, resamples: int):
153
+ """The cost-per-success half of the report, with its own gate.
154
+
155
+ Separate from the per-trial gate on purpose: an instruction routinely makes a
156
+ trial cost MORE and a success cost LESS, and one decision cannot carry both
157
+ without one of them being buried.
158
+ """
159
+ reasons: list[str] = []
160
+ bs, ws = b["cost_per_success_usd"], w["cost_per_success_usd"]
161
+
162
+ if b["n_scored"] < min_n or w["n_scored"] < min_n:
163
+ reasons.append(
164
+ f"below COST_MIN_N={min_n} scored trial(s) per arm "
165
+ f"(baseline {b['n_scored']}, with {w['n_scored']})"
166
+ )
167
+
168
+ delta = ci = pct = None
169
+ ci_pct = [None, None]
170
+ fraction = None
171
+ if b["successes"] == 0 or w["successes"] == 0:
172
+ which = "baseline" if b["successes"] == 0 else "with-arm"
173
+ reasons.append(
174
+ f"{which} never succeeded — its cost per success is unbounded, not a ratio "
175
+ f"(every trial was paid for and none passed); compare the pass rates instead"
176
+ )
177
+ else:
178
+ delta = round(ws - bs, 6)
179
+ pct = round(delta / bs * 100, 4) if bs else None
180
+ lo, hi, fraction = per_success_ci(b["paired"], w["paired"], resamples=resamples)
181
+ if lo is None:
182
+ reasons.append("cost per success was undefined in every resample")
183
+ else:
184
+ ci = [lo, hi]
185
+ ci_pct = [round(lo / bs * 100, 4), round(hi / bs * 100, 4)] if bs else [None, None]
186
+ if fraction > MAX_UNDEFINED_FRACTION:
187
+ reasons.append(
188
+ f"cost per success undefined in {fraction:.1%} of resamples "
189
+ f"(> {MAX_UNDEFINED_FRACTION:.0%}) — too few successes to price one"
190
+ )
191
+ elif lo <= 0 <= hi:
192
+ reasons.append("CI spans zero — undecided, refusing rather than claiming a saving")
193
+
194
+ if reasons:
195
+ decision = "undecided"
196
+ else:
197
+ decision = "cheaper" if delta < 0 else "costlier"
198
+
199
+ return {
200
+ "baseline_usd": bs,
201
+ "with_usd": ws,
202
+ "baseline_successes": f"{b['successes']}/{b['n_scored']}",
203
+ "with_successes": f"{w['successes']}/{w['n_scored']}",
204
+ "delta_usd": delta,
205
+ "delta_pct": pct,
206
+ "ci_usd": ci,
207
+ "ci_pct": ci_pct,
208
+ "undefined_fraction": round(fraction, 4) if fraction is not None else None,
209
+ "decision": decision,
210
+ "refused_reasons": reasons,
211
+ }
212
+
213
+
214
+ def compare(baseline_dir: Path, with_dir: Path, min_n: int = COST_MIN_N, resamples: int = RESAMPLES,
215
+ allow_cross=(), allow_unstamped: bool = False):
216
+ # Provenance FIRST: if the two arms were not the same harness, the
217
+ # numbers below are a comparison of harnesses, and no interval around
218
+ # them means what it appears to mean (#415, 2026-08-31 -> 2026-09-02).
219
+ prov = provenance.compare(
220
+ baseline_dir, with_dir, allow_cross=allow_cross, allow_unstamped=allow_unstamped
221
+ )
222
+ b = arm(baseline_dir)
223
+ w = arm(with_dir)
224
+
225
+ for a in (b, w):
226
+ if a["n"] == 0:
227
+ raise ValueError(
228
+ f"no per-trial cost measured in {a['job']} — an unmeasured arm is UNKNOWN, "
229
+ "not $0.00 (the remote agent bills the key directly and writes no "
230
+ "claude-output.json; a host claude arm would have written one per trial)"
231
+ )
232
+
233
+ delta = w["cost_mean_usd"] - b["cost_mean_usd"]
234
+ lo, hi = bootstrap_ci(b["costs"], w["costs"], resamples=resamples)
235
+
236
+ def pct(x):
237
+ return round(x / b["cost_mean_usd"] * 100, 4) if b["cost_mean_usd"] else None
238
+
239
+ reasons = []
240
+ if b["n"] < min_n or w["n"] < min_n:
241
+ reasons.append(f"below COST_MIN_N={min_n} per arm (baseline {b['n']}, with {w['n']})")
242
+ if lo <= 0 <= hi:
243
+ reasons.append("CI spans zero — undecided, refusing rather than claiming a saving")
244
+ if not prov["comparable"]:
245
+ reasons.extend(prov["reasons"])
246
+
247
+ if reasons:
248
+ decision = "undecided"
249
+ else:
250
+ decision = "cheaper" if delta < 0 else "costlier"
251
+
252
+ # per_success_block builds its OWN decision, so the provenance refusal has to
253
+ # reach it too. Otherwise an incomparable pair could refuse per-trial and
254
+ # still carry a confident "cheaper per success" onto the job row.
255
+ per_success = per_success_block(b, w, min_n=min_n, resamples=resamples)
256
+ if per_success is not None and not prov["comparable"]:
257
+ per_success = dict(
258
+ per_success,
259
+ decision="undecided",
260
+ refused_reasons=list(per_success.get("refused_reasons", [])) + prov["reasons"],
261
+ )
262
+
263
+ rb, rw = b["reward_mean"], w["reward_mean"]
264
+ regressed = rb is not None and rw is not None and rw < rb - 1e-9
265
+
266
+ drop = ("costs", "paired")
267
+ return {
268
+ "baseline": {k: v for k, v in b.items() if k not in drop},
269
+ "with": {k: v for k, v in w.items() if k not in drop},
270
+ # The metric that can show an instruction making work CHEAPER. Per-trial
271
+ # cost prices a failed trial and a passing one alike; a baseline that
272
+ # fails is not cheap, it is a trial you pay for and then pay again.
273
+ "per_success": per_success,
274
+ "delta_usd": round(delta, 6),
275
+ "delta_pct": pct(delta),
276
+ "ci_usd": [lo, hi],
277
+ "ci_pct": [pct(lo), pct(hi)],
278
+ "decision": decision,
279
+ "refused_reasons": reasons,
280
+ # Carried whether or not it refused: a verdict that pooled across a key
281
+ # by --allow-cross, or could not verify at all, must say so wherever it
282
+ # is read -- including the scheduled handler, which reads this JSON.
283
+ "provenance": prov,
284
+ "reward": {
285
+ "baseline": rb,
286
+ "with": rw,
287
+ "delta": round(rw - rb, 6) if rb is not None and rw is not None else None,
288
+ # A cost win under a reward regression is not a win. Stated here AND
289
+ # in the headline so it cannot be dropped on the way to a slide.
290
+ "regressed": regressed,
291
+ },
292
+ "min_n": min_n,
293
+ "resamples": resamples,
294
+ "seed": SEED,
295
+ }
296
+
297
+
298
+ def pretty(r: dict[str, Any], brief: bool = False) -> str:
299
+ """Human form. `brief` drops the per-arm lines for run.sh, which already
300
+ prints its own arm summaries — the cost verdict is what it lacks."""
301
+ b, w = r["baseline"], r["with"]
302
+ lines = [] if brief else [
303
+ f"baseline : {b['n']} trial(s) · ${b['cost_mean_usd']:.4f}/trial "
304
+ f"(${b['cost_sum_usd']:.4f} total) · {b['duration_mean_s']:.0f}s · {b['turns_mean']:.1f} turns "
305
+ f"· reward {b['reward_mean']}",
306
+ f"with-arm : {w['n']} trial(s) · ${w['cost_mean_usd']:.4f}/trial "
307
+ f"(${w['cost_sum_usd']:.4f} total) · {w['duration_mean_s']:.0f}s · {w['turns_mean']:.1f} turns "
308
+ f"· reward {w['reward_mean']}",
309
+ ]
310
+ lines += [
311
+ f"cost : {r['delta_usd']:+.4f} USD/trial ({r['delta_pct']:+.1f}%) "
312
+ f"95% CI [{r['ci_usd'][0]:+.4f}, {r['ci_usd'][1]:+.4f}]",
313
+ f"DECISION : {r['decision']} (per trial)",
314
+ ]
315
+ for reason in r["refused_reasons"]:
316
+ lines.append(f" refused: {reason}")
317
+
318
+ ps = r["per_success"]
319
+ bs = "n/a" if ps["baseline_usd"] is None else f"${ps['baseline_usd']:.4f}"
320
+ ws = "n/a" if ps["with_usd"] is None else f"${ps['with_usd']:.4f}"
321
+ lines.append(
322
+ f"per-succ : {bs} → {ws} per success "
323
+ f"(passed {ps['baseline_successes']} → {ps['with_successes']})"
324
+ )
325
+ if ps["delta_usd"] is not None and ps["ci_usd"]:
326
+ lines.append(
327
+ f" {ps['delta_usd']:+.4f} USD/success ({ps['delta_pct']:+.1f}%) "
328
+ f"95% CI [{ps['ci_usd'][0]:+.4f}, {ps['ci_usd'][1]:+.4f}]"
329
+ )
330
+ lines.append(f"DECISION : {ps['decision']} (per success)")
331
+ for reason in ps["refused_reasons"]:
332
+ lines.append(f" refused: {reason}")
333
+ if r["reward"]["regressed"]:
334
+ lines.append(
335
+ f" WARNING: reward regressed ({r['reward']['baseline']} → {r['reward']['with']}) — "
336
+ "a cheaper arm that solves less is not a saving; do not quote the cost delta alone"
337
+ )
338
+ return "\n".join(lines)
339
+
340
+
341
+ def main(argv: list[str]) -> int:
342
+ args = [a for a in argv[1:] if not a.startswith("--")]
343
+ if len(args) < 2:
344
+ print("usage: cost_delta.py <baseline-job-dir> <with-job-dir> [--json]", file=sys.stderr)
345
+ return 2
346
+
347
+ def opt(name, default):
348
+ for i, a in enumerate(argv):
349
+ if a == name and i + 1 < len(argv):
350
+ return int(argv[i + 1])
351
+ return default
352
+
353
+ try:
354
+ def strval(name, default=""):
355
+ for i, a in enumerate(argv):
356
+ if a == name and i + 1 < len(argv):
357
+ return argv[i + 1]
358
+ return default
359
+
360
+ r = compare(
361
+ Path(args[0]),
362
+ Path(args[1]),
363
+ min_n=opt("--min-n", COST_MIN_N),
364
+ resamples=opt("--resamples", RESAMPLES),
365
+ allow_cross=[k.strip() for k in strval("--allow-cross").split(",") if k.strip()],
366
+ allow_unstamped="--allow-unstamped" in argv,
367
+ )
368
+ except ValueError as e:
369
+ print(str(e), file=sys.stderr)
370
+ return 3
371
+
372
+ print(json.dumps(r) if "--json" in argv[1:] else pretty(r, brief="--brief" in argv[1:]))
373
+ return 0
374
+
375
+
376
+ if __name__ == "__main__":
377
+ sys.exit(main(sys.argv))