@r1z4x/tezgah 1.9.2 → 1.11.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 (426) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/MANIFEST +382 -144
  3. package/README.de.md +1 -1
  4. package/README.es.md +1 -1
  5. package/README.fr.md +1 -1
  6. package/README.md +1 -1
  7. package/README.tr.md +1 -1
  8. package/bin/codegen +5 -2
  9. package/bin/tezgah-design +288 -1
  10. package/bin/tezgah-docs +1 -1
  11. package/bin/tezgah-doctor +39 -2
  12. package/bin/tezgah-import-ai-research +26 -12
  13. package/bin/tezgah-ocr-review +296 -0
  14. package/bin/tezgah-research +216 -40
  15. package/bin/tezgah-review-quote +185 -0
  16. package/bin/tezgah-setup +136 -33
  17. package/bin/tezgah-skill +1 -1
  18. package/bin/tezgah-status +201 -3
  19. package/bin/tezgah-task +203 -3
  20. package/commands/adhd.md +2 -2
  21. package/commands/ponytail.md +4 -4
  22. package/docs/architecture.md +2 -2
  23. package/docs/citations-baseline.json +2 -2
  24. package/docs/clarity-baseline.json +2 -2
  25. package/docs/contract.md +87 -42
  26. package/docs/environment.md +2 -0
  27. package/docs/evidence.md +28 -9
  28. package/docs/feature-audit.md +2 -2
  29. package/docs/gate.md +54 -31
  30. package/docs/glossary.md +5 -2
  31. package/docs/hosts.md +17 -3
  32. package/docs/index.json +34 -8
  33. package/docs/judge.md +4 -2
  34. package/docs/layers.md +3 -3
  35. package/docs/models.md +31 -9
  36. package/docs/operations.md +126 -1
  37. package/docs/research.md +786 -5
  38. package/docs/skills.md +137 -13
  39. package/docs/status-line.md +115 -1
  40. package/hooks/hooks.json +1 -1
  41. package/hooks/lang/en.json +10 -0
  42. package/hooks/lang/tr.json +11 -1
  43. package/hooks/tezgah_agents.py +38 -22
  44. package/hooks/tezgah_apps.py +18 -0
  45. package/hooks/tezgah_components.py +4 -0
  46. package/hooks/tezgah_context.py +189 -50
  47. package/hooks/tezgah_extension.py +337 -0
  48. package/hooks/tezgah_gate.py +188 -56
  49. package/hooks/tezgah_integrity.py +652 -105
  50. package/hooks/tezgah_judge.py +6 -4
  51. package/hooks/tezgah_models.py +59 -19
  52. package/hooks/tezgah_orca.py +1 -1
  53. package/hooks/tezgah_paths.py +102 -6
  54. package/hooks/tezgah_policy.py +94 -45
  55. package/hooks/tezgah_rank.py +9 -6
  56. package/hooks/tezgah_replay.py +4 -4
  57. package/hooks/tezgah_research.py +1260 -65
  58. package/hooks/tezgah_research_capture.py +1544 -0
  59. package/hooks/tezgah_research_evidence.py +842 -0
  60. package/hooks/tezgah_research_stats.py +258 -0
  61. package/hooks/tezgah_skill_pick.py +408 -106
  62. package/hooks/tezgah_stopspec.py +8 -1
  63. package/hooks/tezgah_task.py +12 -4
  64. package/hooks/tezgah_taste_ledger.py +1 -1
  65. package/hosts/cursor/hook.py +29 -2
  66. package/hosts/opencode/plugins/tezgah.js +307 -46
  67. package/hosts/opencode/tui/cli/tui.tsx +100 -0
  68. package/hosts/opencode/tui/tezgah-tui.tsx +2 -1
  69. package/output-styles/tezgah.md +10 -10
  70. package/package.json +1 -1
  71. package/skills/ai-research/SKILL.md +21 -14
  72. package/skills/ai-research/SOURCE +7 -5
  73. package/skills/ai-research/index/1-frame.md +6 -6
  74. package/skills/ai-research/index/2-data.md +9 -9
  75. package/skills/ai-research/index/3-train.md +36 -36
  76. package/skills/ai-research/index/4-measure.md +13 -13
  77. package/skills/ai-research/index/5-run.md +11 -11
  78. package/skills/ai-research/index/6-write.md +23 -23
  79. package/skills/ai-research/library.json +104 -104
  80. package/skills/analyze-app/SKILL.md +17 -12
  81. package/skills/design-contract/SKILL.md +42 -7
  82. package/skills/design-contract/data-views.md +2 -2
  83. package/skills/design-library/EVALS.md +1 -1
  84. package/skills/design-library/INDEX.md +39 -39
  85. package/skills/design-library/SKILL.md +19 -13
  86. package/skills/design-library/SOURCE +45 -43
  87. package/skills/feature-audit/SKILL.md +20 -14
  88. package/skills/hallmark/LICENSE +21 -0
  89. package/skills/hallmark/SKILL.md +154 -0
  90. package/skills/hallmark/SOURCE +207 -0
  91. package/skills/hallmark/agents/openai.yaml +13 -0
  92. package/skills/hallmark/references/anatomy.md +71 -0
  93. package/skills/hallmark/references/brand-truth.md +40 -0
  94. package/skills/hallmark/references/comp.md +104 -0
  95. package/skills/hallmark/references/craft.md +116 -0
  96. package/skills/hallmark/references/dark-mode.md +49 -0
  97. package/skills/hallmark/references/data-viz.md +48 -0
  98. package/skills/hallmark/references/design-md.md +53 -0
  99. package/skills/hallmark/references/direction.md +52 -0
  100. package/skills/hallmark/references/export-formats.md +80 -0
  101. package/skills/hallmark/references/flow.md +131 -0
  102. package/skills/hallmark/references/fonts.md +58 -0
  103. package/skills/hallmark/references/gates.md +243 -0
  104. package/skills/hallmark/references/genres/atmospheric.md +24 -0
  105. package/skills/hallmark/references/genres/editorial.md +24 -0
  106. package/skills/hallmark/references/genres/modern-minimal.md +24 -0
  107. package/skills/hallmark/references/genres/playful.md +24 -0
  108. package/skills/hallmark/references/imagery.md +99 -0
  109. package/skills/hallmark/references/interaction-and-states.md +89 -0
  110. package/skills/hallmark/references/layout.md +104 -0
  111. package/skills/hallmark/references/macrostructures/01-bento-grid.md +21 -0
  112. package/skills/hallmark/references/macrostructures/02-long-document.md +35 -0
  113. package/skills/hallmark/references/macrostructures/03-marquee-hero.md +21 -0
  114. package/skills/hallmark/references/macrostructures/04-stat-led.md +21 -0
  115. package/skills/hallmark/references/macrostructures/05-workbench.md +21 -0
  116. package/skills/hallmark/references/macrostructures/06-conversational-faq.md +28 -0
  117. package/skills/hallmark/references/macrostructures/07-manifesto.md +28 -0
  118. package/skills/hallmark/references/macrostructures/08-photographic.md +21 -0
  119. package/skills/hallmark/references/macrostructures/09-quote-led.md +21 -0
  120. package/skills/hallmark/references/macrostructures/10-specimen.md +21 -0
  121. package/skills/hallmark/references/macrostructures/11-catalogue.md +21 -0
  122. package/skills/hallmark/references/macrostructures/12-letter.md +21 -0
  123. package/skills/hallmark/references/macrostructures/13-index-first.md +21 -0
  124. package/skills/hallmark/references/macrostructures/14-narrative-workflow.md +21 -0
  125. package/skills/hallmark/references/macrostructures/15-split-studio.md +21 -0
  126. package/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
  127. package/skills/hallmark/references/macrostructures/17-type-specimen.md +19 -0
  128. package/skills/hallmark/references/macrostructures/18-portfolio-grid.md +21 -0
  129. package/skills/hallmark/references/macrostructures/19-map-diagram.md +21 -0
  130. package/skills/hallmark/references/macrostructures/20-ecosystem-index.md +19 -0
  131. package/skills/hallmark/references/macrostructures/21-component-playground.md +21 -0
  132. package/skills/hallmark/references/macrostructures.md +119 -0
  133. package/skills/hallmark/references/microinteractions.md +80 -0
  134. package/skills/hallmark/references/patterns.md +202 -0
  135. package/skills/hallmark/references/redesign.md +88 -0
  136. package/skills/hallmark/references/responsive.md +46 -0
  137. package/skills/hallmark/references/review-finish.md +19 -0
  138. package/skills/hallmark/references/review-hero.md +20 -0
  139. package/skills/hallmark/references/scroll-choreography.md +110 -0
  140. package/skills/hallmark/references/texture.md +90 -0
  141. package/skills/hallmark/references/themes/README.md +58 -0
  142. package/skills/hallmark/references/themes/almanac.jpg +0 -0
  143. package/skills/hallmark/references/themes/almanac.md +103 -0
  144. package/skills/hallmark/references/themes/arcade.jpg +0 -0
  145. package/skills/hallmark/references/themes/arcade.md +102 -0
  146. package/skills/hallmark/references/themes/atelier.jpg +0 -0
  147. package/skills/hallmark/references/themes/atelier.md +98 -0
  148. package/skills/hallmark/references/themes/bloom.jpg +0 -0
  149. package/skills/hallmark/references/themes/bloom.md +102 -0
  150. package/skills/hallmark/references/themes/blueprint.jpg +0 -0
  151. package/skills/hallmark/references/themes/blueprint.md +103 -0
  152. package/skills/hallmark/references/themes/broadside.jpg +0 -0
  153. package/skills/hallmark/references/themes/broadside.md +108 -0
  154. package/skills/hallmark/references/themes/brutal.jpg +0 -0
  155. package/skills/hallmark/references/themes/brutal.md +95 -0
  156. package/skills/hallmark/references/themes/carnival.jpg +0 -0
  157. package/skills/hallmark/references/themes/carnival.md +104 -0
  158. package/skills/hallmark/references/themes/cobalt.jpg +0 -0
  159. package/skills/hallmark/references/themes/cobalt.md +101 -0
  160. package/skills/hallmark/references/themes/console.jpg +0 -0
  161. package/skills/hallmark/references/themes/console.md +99 -0
  162. package/skills/hallmark/references/themes/coral.jpg +0 -0
  163. package/skills/hallmark/references/themes/coral.md +102 -0
  164. package/skills/hallmark/references/themes/darkroom.jpg +0 -0
  165. package/skills/hallmark/references/themes/darkroom.md +99 -0
  166. package/skills/hallmark/references/themes/dose.jpg +0 -0
  167. package/skills/hallmark/references/themes/dose.md +98 -0
  168. package/skills/hallmark/references/themes/dusk.jpg +0 -0
  169. package/skills/hallmark/references/themes/dusk.md +105 -0
  170. package/skills/hallmark/references/themes/editorial.jpg +0 -0
  171. package/skills/hallmark/references/themes/editorial.md +97 -0
  172. package/skills/hallmark/references/themes/formwork.jpg +0 -0
  173. package/skills/hallmark/references/themes/formwork.md +107 -0
  174. package/skills/hallmark/references/themes/furrow.jpg +0 -0
  175. package/skills/hallmark/references/themes/furrow.md +98 -0
  176. package/skills/hallmark/references/themes/garden.jpg +0 -0
  177. package/skills/hallmark/references/themes/garden.md +97 -0
  178. package/skills/hallmark/references/themes/grid.jpg +0 -0
  179. package/skills/hallmark/references/themes/grid.md +101 -0
  180. package/skills/hallmark/references/themes/halftone.jpg +0 -0
  181. package/skills/hallmark/references/themes/halftone.md +106 -0
  182. package/skills/hallmark/references/themes/hedgerow.jpg +0 -0
  183. package/skills/hallmark/references/themes/hedgerow.md +104 -0
  184. package/skills/hallmark/references/themes/hum.jpg +0 -0
  185. package/skills/hallmark/references/themes/hum.md +99 -0
  186. package/skills/hallmark/references/themes/index.json +2191 -0
  187. package/skills/hallmark/references/themes/ink.jpg +0 -0
  188. package/skills/hallmark/references/themes/ink.md +106 -0
  189. package/skills/hallmark/references/themes/kiln.jpg +0 -0
  190. package/skills/hallmark/references/themes/kiln.md +108 -0
  191. package/skills/hallmark/references/themes/loom.jpg +0 -0
  192. package/skills/hallmark/references/themes/loom.md +106 -0
  193. package/skills/hallmark/references/themes/lumen.jpg +0 -0
  194. package/skills/hallmark/references/themes/lumen.md +103 -0
  195. package/skills/hallmark/references/themes/manifesto.jpg +0 -0
  196. package/skills/hallmark/references/themes/manifesto.md +101 -0
  197. package/skills/hallmark/references/themes/midnight.jpg +0 -0
  198. package/skills/hallmark/references/themes/midnight.md +97 -0
  199. package/skills/hallmark/references/themes/newsprint.jpg +0 -0
  200. package/skills/hallmark/references/themes/newsprint.md +102 -0
  201. package/skills/hallmark/references/themes/overprint.jpg +0 -0
  202. package/skills/hallmark/references/themes/overprint.md +101 -0
  203. package/skills/hallmark/references/themes/pasteup.jpg +0 -0
  204. package/skills/hallmark/references/themes/pasteup.md +100 -0
  205. package/skills/hallmark/references/themes/piste.jpg +0 -0
  206. package/skills/hallmark/references/themes/piste.md +99 -0
  207. package/skills/hallmark/references/themes/ribbon.jpg +0 -0
  208. package/skills/hallmark/references/themes/ribbon.md +106 -0
  209. package/skills/hallmark/references/themes/rim.jpg +0 -0
  210. package/skills/hallmark/references/themes/rim.md +96 -0
  211. package/skills/hallmark/references/themes/riso.jpg +0 -0
  212. package/skills/hallmark/references/themes/riso.md +98 -0
  213. package/skills/hallmark/references/themes/solstice.jpg +0 -0
  214. package/skills/hallmark/references/themes/solstice.md +104 -0
  215. package/skills/hallmark/references/themes/specimen.jpg +0 -0
  216. package/skills/hallmark/references/themes/specimen.md +100 -0
  217. package/skills/hallmark/references/themes/spore.jpg +0 -0
  218. package/skills/hallmark/references/themes/spore.md +104 -0
  219. package/skills/hallmark/references/themes/sport.jpg +0 -0
  220. package/skills/hallmark/references/themes/sport.md +101 -0
  221. package/skills/hallmark/references/themes/squeeze.jpg +0 -0
  222. package/skills/hallmark/references/themes/squeeze.md +99 -0
  223. package/skills/hallmark/references/themes/sticker.jpg +0 -0
  224. package/skills/hallmark/references/themes/sticker.md +106 -0
  225. package/skills/hallmark/references/themes/studio.jpg +0 -0
  226. package/skills/hallmark/references/themes/studio.md +99 -0
  227. package/skills/hallmark/references/themes/terminal.jpg +0 -0
  228. package/skills/hallmark/references/themes/terminal.md +96 -0
  229. package/skills/hallmark/references/themes/terrazzo.jpg +0 -0
  230. package/skills/hallmark/references/themes/terrazzo.md +106 -0
  231. package/skills/hallmark/references/themes/ultraviolet.jpg +0 -0
  232. package/skills/hallmark/references/themes/ultraviolet.md +106 -0
  233. package/skills/hallmark/references/themes/vellum.jpg +0 -0
  234. package/skills/hallmark/references/themes/vellum.md +105 -0
  235. package/skills/hallmark/references/themes/wayfind.jpg +0 -0
  236. package/skills/hallmark/references/themes/wayfind.md +105 -0
  237. package/skills/hallmark/references/themes/werkstatt.jpg +0 -0
  238. package/skills/hallmark/references/themes/werkstatt.md +105 -0
  239. package/skills/hallmark/scripts/capture.mjs +125 -0
  240. package/skills/hallmark/scripts/fixtures/exemplars/atmospheric.html +324 -0
  241. package/skills/hallmark/scripts/fixtures/exemplars/editorial.html +363 -0
  242. package/skills/hallmark/scripts/fixtures/exemplars/modern-minimal.html +527 -0
  243. package/skills/hallmark/scripts/fixtures/exemplars/playful.html +353 -0
  244. package/skills/hallmark/scripts/fixtures/eyebrow-fail/index.html +118 -0
  245. package/skills/hallmark/scripts/fixtures/eyebrow-pass/index.html +137 -0
  246. package/skills/hallmark/scripts/fixtures/kept/after.html +27 -0
  247. package/skills/hallmark/scripts/fixtures/kept/before.html +26 -0
  248. package/skills/hallmark/scripts/fixtures/slopped.html +145 -0
  249. package/skills/hallmark/scripts/fixtures/tailwind-fail/Feature.vue +14 -0
  250. package/skills/hallmark/scripts/fixtures/tailwind-fail/Hero.tsx +33 -0
  251. package/skills/hallmark/scripts/fixtures/tailwind-fail/index.html +74 -0
  252. package/skills/hallmark/scripts/fixtures/tailwind-fail/style.css +6 -0
  253. package/skills/hallmark/scripts/fixtures/tailwind-pass/Hero.tsx +33 -0
  254. package/skills/hallmark/scripts/fixtures/tailwind-pass/index.html +57 -0
  255. package/skills/hallmark/scripts/fixtures/tailwind-pass/style.css +15 -0
  256. package/skills/hallmark/scripts/fixtures/waiver/index.html +54 -0
  257. package/skills/hallmark/scripts/imagegen.mjs +85 -0
  258. package/skills/hallmark/scripts/install-hook.mjs +70 -0
  259. package/skills/hallmark/scripts/install.mjs +141 -0
  260. package/skills/hallmark/scripts/lint-hook.mjs +49 -0
  261. package/skills/hallmark/scripts/photos.json +1 -0
  262. package/skills/hallmark/scripts/picture.mjs +71 -0
  263. package/skills/hallmark/scripts/refcheck.mjs +352 -0
  264. package/skills/hallmark/scripts/sloplint-render.mjs +1252 -0
  265. package/skills/hallmark/scripts/sloplint.mjs +4422 -0
  266. package/skills/hallmark/scripts/theme.mjs +650 -0
  267. package/skills/harness/SKILL.md +15 -8
  268. package/skills/i-have-adhd/SKILL.md +22 -11
  269. package/skills/no-ai-slop/SKILL.md +15 -1
  270. package/skills/plan-add/SKILL.md +33 -26
  271. package/skills/plan-status/SKILL.md +13 -7
  272. package/skills/plan-sync/SKILL.md +18 -12
  273. package/skills/pm-frameworks/SKILL.md +19 -10
  274. package/skills/pm-frameworks/SOURCE +7 -5
  275. package/skills/ponytail/SKILL.md +19 -14
  276. package/skills/product-analysis/SKILL.md +22 -18
  277. package/skills/product-flow/SKILL.md +14 -9
  278. package/skills/research/SKILL.md +75 -39
  279. package/skills/rl-env/SKILL.md +25 -13
  280. package/skills/rl-env/SOURCE +8 -6
  281. package/skills/tezgah-contract/SKILL.md +47 -42
  282. package/bin/tezgah-skill.py +0 -59
  283. /package/skills/ai-research/0-autoresearch-skill/{SKILL.md → ENTRY.md} +0 -0
  284. /package/skills/ai-research/01-model-architecture/litgpt/{SKILL.md → ENTRY.md} +0 -0
  285. /package/skills/ai-research/01-model-architecture/mamba/{SKILL.md → ENTRY.md} +0 -0
  286. /package/skills/ai-research/01-model-architecture/nanogpt/{SKILL.md → ENTRY.md} +0 -0
  287. /package/skills/ai-research/01-model-architecture/rwkv/{SKILL.md → ENTRY.md} +0 -0
  288. /package/skills/ai-research/01-model-architecture/torchtitan/{SKILL.md → ENTRY.md} +0 -0
  289. /package/skills/ai-research/02-tokenization/huggingface-tokenizers/{SKILL.md → ENTRY.md} +0 -0
  290. /package/skills/ai-research/02-tokenization/sentencepiece/{SKILL.md → ENTRY.md} +0 -0
  291. /package/skills/ai-research/03-fine-tuning/axolotl/{SKILL.md → ENTRY.md} +0 -0
  292. /package/skills/ai-research/03-fine-tuning/llama-factory/{SKILL.md → ENTRY.md} +0 -0
  293. /package/skills/ai-research/03-fine-tuning/peft/{SKILL.md → ENTRY.md} +0 -0
  294. /package/skills/ai-research/03-fine-tuning/unsloth/{SKILL.md → ENTRY.md} +0 -0
  295. /package/skills/ai-research/04-mechanistic-interpretability/nnsight/{SKILL.md → ENTRY.md} +0 -0
  296. /package/skills/ai-research/04-mechanistic-interpretability/pyvene/{SKILL.md → ENTRY.md} +0 -0
  297. /package/skills/ai-research/04-mechanistic-interpretability/saelens/{SKILL.md → ENTRY.md} +0 -0
  298. /package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/{SKILL.md → ENTRY.md} +0 -0
  299. /package/skills/ai-research/05-data-processing/nemo-curator/{SKILL.md → ENTRY.md} +0 -0
  300. /package/skills/ai-research/05-data-processing/ray-data/{SKILL.md → ENTRY.md} +0 -0
  301. /package/skills/ai-research/06-post-training/grpo-rl-training/{SKILL.md → ENTRY.md} +0 -0
  302. /package/skills/ai-research/06-post-training/miles/{SKILL.md → ENTRY.md} +0 -0
  303. /package/skills/ai-research/06-post-training/openrlhf/{SKILL.md → ENTRY.md} +0 -0
  304. /package/skills/ai-research/06-post-training/simpo/{SKILL.md → ENTRY.md} +0 -0
  305. /package/skills/ai-research/06-post-training/slime/{SKILL.md → ENTRY.md} +0 -0
  306. /package/skills/ai-research/06-post-training/torchforge/{SKILL.md → ENTRY.md} +0 -0
  307. /package/skills/ai-research/06-post-training/trl-fine-tuning/{SKILL.md → ENTRY.md} +0 -0
  308. /package/skills/ai-research/06-post-training/verl/{SKILL.md → ENTRY.md} +0 -0
  309. /package/skills/ai-research/07-safety-alignment/constitutional-ai/{SKILL.md → ENTRY.md} +0 -0
  310. /package/skills/ai-research/07-safety-alignment/llamaguard/{SKILL.md → ENTRY.md} +0 -0
  311. /package/skills/ai-research/07-safety-alignment/nemo-guardrails/{SKILL.md → ENTRY.md} +0 -0
  312. /package/skills/ai-research/07-safety-alignment/prompt-guard/{SKILL.md → ENTRY.md} +0 -0
  313. /package/skills/ai-research/08-distributed-training/accelerate/{SKILL.md → ENTRY.md} +0 -0
  314. /package/skills/ai-research/08-distributed-training/deepspeed/{SKILL.md → ENTRY.md} +0 -0
  315. /package/skills/ai-research/08-distributed-training/megatron-core/{SKILL.md → ENTRY.md} +0 -0
  316. /package/skills/ai-research/08-distributed-training/pytorch-fsdp2/{SKILL.md → ENTRY.md} +0 -0
  317. /package/skills/ai-research/08-distributed-training/pytorch-lightning/{SKILL.md → ENTRY.md} +0 -0
  318. /package/skills/ai-research/08-distributed-training/ray-train/{SKILL.md → ENTRY.md} +0 -0
  319. /package/skills/ai-research/09-infrastructure/lambda-labs/{SKILL.md → ENTRY.md} +0 -0
  320. /package/skills/ai-research/09-infrastructure/modal/{SKILL.md → ENTRY.md} +0 -0
  321. /package/skills/ai-research/09-infrastructure/skypilot/{SKILL.md → ENTRY.md} +0 -0
  322. /package/skills/ai-research/10-optimization/awq/{SKILL.md → ENTRY.md} +0 -0
  323. /package/skills/ai-research/10-optimization/bitsandbytes/{SKILL.md → ENTRY.md} +0 -0
  324. /package/skills/ai-research/10-optimization/flash-attention/{SKILL.md → ENTRY.md} +0 -0
  325. /package/skills/ai-research/10-optimization/gguf/{SKILL.md → ENTRY.md} +0 -0
  326. /package/skills/ai-research/10-optimization/gptq/{SKILL.md → ENTRY.md} +0 -0
  327. /package/skills/ai-research/10-optimization/hqq/{SKILL.md → ENTRY.md} +0 -0
  328. /package/skills/ai-research/10-optimization/ml-training-recipes/{SKILL.md → ENTRY.md} +0 -0
  329. /package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
  330. /package/skills/ai-research/11-evaluation/lm-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
  331. /package/skills/ai-research/11-evaluation/nemo-evaluator/{SKILL.md → ENTRY.md} +0 -0
  332. /package/skills/ai-research/12-inference-serving/llama-cpp/{SKILL.md → ENTRY.md} +0 -0
  333. /package/skills/ai-research/12-inference-serving/sglang/{SKILL.md → ENTRY.md} +0 -0
  334. /package/skills/ai-research/12-inference-serving/tensorrt-llm/{SKILL.md → ENTRY.md} +0 -0
  335. /package/skills/ai-research/12-inference-serving/vllm/{SKILL.md → ENTRY.md} +0 -0
  336. /package/skills/ai-research/13-mlops/mlflow/{SKILL.md → ENTRY.md} +0 -0
  337. /package/skills/ai-research/13-mlops/swanlab/{SKILL.md → ENTRY.md} +0 -0
  338. /package/skills/ai-research/13-mlops/tensorboard/{SKILL.md → ENTRY.md} +0 -0
  339. /package/skills/ai-research/13-mlops/weights-and-biases/{SKILL.md → ENTRY.md} +0 -0
  340. /package/skills/ai-research/14-agents/a-evolve/{SKILL.md → ENTRY.md} +0 -0
  341. /package/skills/ai-research/14-agents/autogpt/{SKILL.md → ENTRY.md} +0 -0
  342. /package/skills/ai-research/14-agents/crewai/{SKILL.md → ENTRY.md} +0 -0
  343. /package/skills/ai-research/14-agents/langchain/{SKILL.md → ENTRY.md} +0 -0
  344. /package/skills/ai-research/14-agents/llamaindex/{SKILL.md → ENTRY.md} +0 -0
  345. /package/skills/ai-research/15-rag/chroma/{SKILL.md → ENTRY.md} +0 -0
  346. /package/skills/ai-research/15-rag/faiss/{SKILL.md → ENTRY.md} +0 -0
  347. /package/skills/ai-research/15-rag/pinecone/{SKILL.md → ENTRY.md} +0 -0
  348. /package/skills/ai-research/15-rag/qdrant/{SKILL.md → ENTRY.md} +0 -0
  349. /package/skills/ai-research/15-rag/sentence-transformers/{SKILL.md → ENTRY.md} +0 -0
  350. /package/skills/ai-research/16-prompt-engineering/dspy/{SKILL.md → ENTRY.md} +0 -0
  351. /package/skills/ai-research/16-prompt-engineering/guidance/{SKILL.md → ENTRY.md} +0 -0
  352. /package/skills/ai-research/16-prompt-engineering/instructor/{SKILL.md → ENTRY.md} +0 -0
  353. /package/skills/ai-research/16-prompt-engineering/outlines/{SKILL.md → ENTRY.md} +0 -0
  354. /package/skills/ai-research/17-observability/langsmith/{SKILL.md → ENTRY.md} +0 -0
  355. /package/skills/ai-research/17-observability/phoenix/{SKILL.md → ENTRY.md} +0 -0
  356. /package/skills/ai-research/18-multimodal/audiocraft/{SKILL.md → ENTRY.md} +0 -0
  357. /package/skills/ai-research/18-multimodal/blip-2/{SKILL.md → ENTRY.md} +0 -0
  358. /package/skills/ai-research/18-multimodal/clip/{SKILL.md → ENTRY.md} +0 -0
  359. /package/skills/ai-research/18-multimodal/cosmos-policy/{SKILL.md → ENTRY.md} +0 -0
  360. /package/skills/ai-research/18-multimodal/llava/{SKILL.md → ENTRY.md} +0 -0
  361. /package/skills/ai-research/18-multimodal/openpi/{SKILL.md → ENTRY.md} +0 -0
  362. /package/skills/ai-research/18-multimodal/openvla-oft/{SKILL.md → ENTRY.md} +0 -0
  363. /package/skills/ai-research/18-multimodal/segment-anything/{SKILL.md → ENTRY.md} +0 -0
  364. /package/skills/ai-research/18-multimodal/stable-diffusion/{SKILL.md → ENTRY.md} +0 -0
  365. /package/skills/ai-research/18-multimodal/whisper/{SKILL.md → ENTRY.md} +0 -0
  366. /package/skills/ai-research/19-emerging-techniques/knowledge-distillation/{SKILL.md → ENTRY.md} +0 -0
  367. /package/skills/ai-research/19-emerging-techniques/long-context/{SKILL.md → ENTRY.md} +0 -0
  368. /package/skills/ai-research/19-emerging-techniques/model-merging/{SKILL.md → ENTRY.md} +0 -0
  369. /package/skills/ai-research/19-emerging-techniques/model-pruning/{SKILL.md → ENTRY.md} +0 -0
  370. /package/skills/ai-research/19-emerging-techniques/moe-training/{SKILL.md → ENTRY.md} +0 -0
  371. /package/skills/ai-research/19-emerging-techniques/speculative-decoding/{SKILL.md → ENTRY.md} +0 -0
  372. /package/skills/ai-research/20-ml-paper-writing/academic-plotting/{SKILL.md → ENTRY.md} +0 -0
  373. /package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/{SKILL.md → ENTRY.md} +0 -0
  374. /package/skills/ai-research/20-ml-paper-writing/presenting-conference-talks/{SKILL.md → ENTRY.md} +0 -0
  375. /package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/{SKILL.md → ENTRY.md} +0 -0
  376. /package/skills/ai-research/21-research-ideation/brainstorming-research-ideas/{SKILL.md → ENTRY.md} +0 -0
  377. /package/skills/ai-research/21-research-ideation/creative-thinking-for-research/{SKILL.md → ENTRY.md} +0 -0
  378. /package/skills/ai-research/22-agent-native-research-artifact/compiler/{SKILL.md → ENTRY.md} +0 -0
  379. /package/skills/ai-research/22-agent-native-research-artifact/research-manager/{SKILL.md → ENTRY.md} +0 -0
  380. /package/skills/ai-research/22-agent-native-research-artifact/rigor-reviewer/{SKILL.md → ENTRY.md} +0 -0
  381. /package/skills/design-library/adaptive-interfaces/skills/colour-independence/{SKILL.md → ENTRY.md} +0 -0
  382. /package/skills/design-library/adaptive-interfaces/skills/flexible-typography/{SKILL.md → ENTRY.md} +0 -0
  383. /package/skills/design-library/adaptive-interfaces/skills/information-density/{SKILL.md → ENTRY.md} +0 -0
  384. /package/skills/design-library/adaptive-interfaces/skills/preference-audit/{SKILL.md → ENTRY.md} +0 -0
  385. /package/skills/design-library/adaptive-interfaces/skills/responsive-accessibility/{SKILL.md → ENTRY.md} +0 -0
  386. /package/skills/design-library/adaptive-interfaces/skills/responsive-review/{SKILL.md → ENTRY.md} +0 -0
  387. /package/skills/design-library/adaptive-interfaces/skills/simplified-views/{SKILL.md → ENTRY.md} +0 -0
  388. /package/skills/design-library/adaptive-interfaces/skills/specify/{SKILL.md → ENTRY.md} +0 -0
  389. /package/skills/design-library/adaptive-interfaces/skills/user-preference-respect/{SKILL.md → ENTRY.md} +0 -0
  390. /package/skills/design-library/cognitive-accessibility/skills/adaptive-personalisation/{SKILL.md → ENTRY.md} +0 -0
  391. /package/skills/design-library/cognitive-accessibility/skills/assess-load/{SKILL.md → ENTRY.md} +0 -0
  392. /package/skills/design-library/cognitive-accessibility/skills/cognitive-load-assessment/{SKILL.md → ENTRY.md} +0 -0
  393. /package/skills/design-library/cognitive-accessibility/skills/contextual-help-design/{SKILL.md → ENTRY.md} +0 -0
  394. /package/skills/design-library/cognitive-accessibility/skills/error-prevention-recovery/{SKILL.md → ENTRY.md} +0 -0
  395. /package/skills/design-library/cognitive-accessibility/skills/focus-attention-design/{SKILL.md → ENTRY.md} +0 -0
  396. /package/skills/design-library/cognitive-accessibility/skills/memory-load-reduction/{SKILL.md → ENTRY.md} +0 -0
  397. /package/skills/design-library/cognitive-accessibility/skills/plain-language-design/{SKILL.md → ENTRY.md} +0 -0
  398. /package/skills/design-library/cognitive-accessibility/skills/review/{SKILL.md → ENTRY.md} +0 -0
  399. /package/skills/design-library/cognitive-accessibility/skills/simplify/{SKILL.md → ENTRY.md} +0 -0
  400. /package/skills/design-library/cognitive-accessibility/skills/wayfinding-navigation/{SKILL.md → ENTRY.md} +0 -0
  401. /package/skills/design-library/design-research/skills/behavioural-analytics/{SKILL.md → ENTRY.md} +0 -0
  402. /package/skills/design-library/design-research/skills/qual-quant-triangulation/{SKILL.md → ENTRY.md} +0 -0
  403. /package/skills/design-library/inclusive-interaction/skills/audit/{SKILL.md → ENTRY.md} +0 -0
  404. /package/skills/design-library/inclusive-interaction/skills/design-flow/{SKILL.md → ENTRY.md} +0 -0
  405. /package/skills/design-library/inclusive-interaction/skills/feedback-and-status/{SKILL.md → ENTRY.md} +0 -0
  406. /package/skills/design-library/inclusive-interaction/skills/gesture-alternatives/{SKILL.md → ENTRY.md} +0 -0
  407. /package/skills/design-library/inclusive-interaction/skills/keyboard-navigation/{SKILL.md → ENTRY.md} +0 -0
  408. /package/skills/design-library/inclusive-interaction/skills/keyboard-review/{SKILL.md → ENTRY.md} +0 -0
  409. /package/skills/design-library/inclusive-interaction/skills/motion-sensitivity/{SKILL.md → ENTRY.md} +0 -0
  410. /package/skills/design-library/inclusive-interaction/skills/multi-modal-input/{SKILL.md → ENTRY.md} +0 -0
  411. /package/skills/design-library/inclusive-interaction/skills/touch-target-design/{SKILL.md → ENTRY.md} +0 -0
  412. /package/skills/design-library/inclusive-interaction/skills/voice-interaction/{SKILL.md → ENTRY.md} +0 -0
  413. /package/skills/design-library/visual-critique/skills/critique-affordance/{SKILL.md → ENTRY.md} +0 -0
  414. /package/skills/design-library/visual-critique/skills/critique-brand-consistency/{SKILL.md → ENTRY.md} +0 -0
  415. /package/skills/design-library/visual-critique/skills/critique-color/{SKILL.md → ENTRY.md} +0 -0
  416. /package/skills/design-library/visual-critique/skills/critique-composition/{SKILL.md → ENTRY.md} +0 -0
  417. /package/skills/design-library/visual-critique/skills/critique-information-density/{SKILL.md → ENTRY.md} +0 -0
  418. /package/skills/design-library/visual-critique/skills/critique-typography/{SKILL.md → ENTRY.md} +0 -0
  419. /package/skills/design-library/visual-critique/skills/critique-visual-hierarchy/{SKILL.md → ENTRY.md} +0 -0
  420. /package/skills/pm-frameworks/intended-vs-implemented/{SKILL.md → ENTRY.md} +0 -0
  421. /package/skills/pm-frameworks/opportunity-solution-tree/{SKILL.md → ENTRY.md} +0 -0
  422. /package/skills/rl-env/generate-nemo-gym-env/{SKILL.md → ENTRY.md} +0 -0
  423. /package/skills/rl-env/generate-openenv-env/{SKILL.md → ENTRY.md} +0 -0
  424. /package/skills/rl-env/generate-ors-env/{SKILL.md → ENTRY.md} +0 -0
  425. /package/skills/rl-env/generate-verifiers-env/{SKILL.md → ENTRY.md} +0 -0
  426. /package/skills/rl-env/rl-env-from-description/{SKILL.md → ENTRY.md} +0 -0
@@ -0,0 +1,842 @@
1
+ """Rules 8 evidence: the certainty engine, the instrument ledger, release-check, calibration.
2
+
3
+ Spec: `.tezgah/analysis/rules8/spec.md` sections 1, 2, 3.1, 3.2, 3.4 and 4.3.
4
+
5
+ - `check(repo, slug, base, state, errors, warnings, strict, git, steps)` runs last in
6
+ `check_line`, over the `steps` dict (claim id -> step names) CAPTURE and CORE filled. It adds
7
+ the instrument steps (3.1, 3.2), computes each claim's certainty (1), appends the report lines
8
+ to `steps["#report"]` and the per-claim dict to `steps["#certainty"]`. It never prints.
9
+ - `conclude_problems(repo, slug, base, state)`: the 3.1 conclude-time switch-or-narrow rule.
10
+ - `COMMANDS`: the CLI handlers, handler(repo, args) -> exit code; each prints its own output.
11
+ """
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import re
16
+ import subprocess
17
+ import sys
18
+ import time
19
+
20
+ import tezgah_research as tr
21
+ import tezgah_research_stats as st
22
+
23
+ LEVELS = ("very-low", "low", "moderate", "high")
24
+ DEC, CEIL = "-1", "ceil"
25
+ # Section 2 as data: every step name `check` prints, with its effect.
26
+ STEPS = {
27
+ "header-mismatch": DEC, "unindexed-source": DEC, "online-drift": DEC,
28
+ "abstract-only": DEC, "source-not-captured": CEIL, "one-connector": DEC,
29
+ "no-counter-search": DEC, "no-stopping-rule": DEC, "stopping-not-met": DEC,
30
+ "known-item-missed": DEC, "fewer-than-two-rivals": DEC, "no-replication": DEC,
31
+ "no-power-plan": DEC, "below-planned-n": DEC, "no-interval": CEIL,
32
+ "interval-includes-zero": CEIL, "below-achieved-mde": CEIL,
33
+ "no-multiplicity-correction": DEC, "fails-multiplicity": CEIL, "no-sesoi": CEIL,
34
+ "tost-not-passed": CEIL, "untraced-number": DEC, "instrument-unvalidated": CEIL,
35
+ "instrument-rejected": CEIL, "judged-row-incomplete": DEC, "uncalibrated-verifier": CEIL,
36
+ "no-question-class": DEC, "class-unreviewed": DEC, "method-not-allowed": DEC,
37
+ "no-dual-screening": DEC, "no-recall-stopping": DEC, "no-positive-control": DEC,
38
+ "no-confounders": DEC, "no-population": DEC, "no-grey-quality": DEC, "no-amstar": DEC,
39
+ "no-codebook": DEC, "fewer-than-two-approaches": DEC,
40
+ "falsifier-not-evaluable": DEC, "one-provider": DEC, "screened-outside-search": DEC,
41
+ }
42
+ SOURCE_START = {"formal": 2, "grey": 1, "agent-report": 0}
43
+ KIND_START = {"code": 3, "derivation": 2}
44
+ VERDICT_BOUND = 0.10
45
+ OPENROUTER = "https://openrouter.ai/api/v1/chat/completions"
46
+ PROMPT = ("Instrument: %s\nConstruct: %s\nItem: %s\nText: %s\nAnswer 1 if the construct "
47
+ "holds for this item, else 0. Reply with the digit only.")
48
+ RUNGS = ("R1", "R2", "R3", "R4")
49
+ INSTRUMENTS = "instruments/INSTRUMENTS.jsonl"
50
+ GOLD = "instruments/GOLD.jsonl"
51
+ LABELS = "instruments/labels.jsonl"
52
+ SWITCHES = "instruments/switches.jsonl"
53
+ VERDICTS = "instruments/verdicts.jsonl"
54
+ KNOWN_TRUTH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
55
+ "tests", "fixtures", "rules8-known-truth")
56
+
57
+
58
+ def _now():
59
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
60
+
61
+
62
+ def _rows(base, rel):
63
+ return tr._row_objects(os.path.join(base, rel))
64
+
65
+
66
+ def _label(text, key):
67
+ """The first `key: value` line of a protocol, or None."""
68
+ found = re.search(r"(?im)^%s[ \t]*:[ \t]*(.*)$" % re.escape(key), text)
69
+ return found.group(1).strip() if found else None
70
+
71
+
72
+ def _protocol(base, h):
73
+ try:
74
+ with open(os.path.join(base, "experiments", h, "protocol.md"), encoding="utf-8") as fh:
75
+ return fh.read()
76
+ except (OSError, UnicodeDecodeError):
77
+ return ""
78
+
79
+
80
+ def _results(base, h):
81
+ """The experiment's rows that count (`tr._live`): one row per run, and aborted and
82
+ failed runs left out."""
83
+ return tr._live(_rows(base, "experiments/%s/results.jsonl" % h))
84
+
85
+
86
+ # --- instruments (3.1, 3.2) ---------------------------------------------------------
87
+ def _row_instrument(row):
88
+ name = row.get("instrument") or row.get("judge")
89
+ return name if isinstance(name, str) and name.strip() else None
90
+
91
+
92
+ def instrument_items(base):
93
+ """{name: {arm: {item: row}}} - the first row per item, in file order."""
94
+ out = {}
95
+ for h in tr._experiment_names(base):
96
+ for row in _results(base, h):
97
+ name = _row_instrument(row)
98
+ if name and row.get("item") is not None:
99
+ arm = str(row.get("arm") or "all")
100
+ out.setdefault(name, {}).setdefault(arm, {}).setdefault(str(row["item"]), row)
101
+ for row in _rows(base, VERDICTS):
102
+ name = _row_instrument(row)
103
+ if name and row.get("item") is not None:
104
+ out.setdefault(name, {}).setdefault("all", {}).setdefault(str(row["item"]), row)
105
+ return out
106
+
107
+
108
+ def _experiments_of(base):
109
+ """{instrument name: set of experiments whose rows name it}."""
110
+ out = {}
111
+ for h in tr._experiment_names(base):
112
+ for row in _results(base, h):
113
+ name = _row_instrument(row)
114
+ if name:
115
+ out.setdefault(name, set()).add(h)
116
+ return out
117
+
118
+
119
+ def _mde(text):
120
+ """The protocol's `mde:`, read as `check` reads it (`tr.protocol_number`); None when
121
+ absent or invalid, and `check` refuses the invalid one."""
122
+ return tr.protocol_number("mde", _label(text, "mde"))[0]
123
+
124
+
125
+ def seeded_draw(items, seed, arm, n):
126
+ """The arm's items sorted by sha256("<seed>:<arm>:<item>") hex, the first n."""
127
+ return sorted({str(i) for i in items}, key=lambda item: hashlib.sha256(
128
+ ("%s:%s:%s" % (seed, arm, item)).encode("utf-8")).hexdigest())[:n]
129
+
130
+
131
+ def gold_record(base, row):
132
+ """(record, mismatch) of a GOLD row: the gold file it names read as a dict, else None, and
133
+ the `differs from the seeded draw` refusal when the row and the file disagree on
134
+ instrument, arm, seed or n, else None."""
135
+ rel = row.get("file")
136
+ record, exc = tr._read_json(os.path.join(base, rel)) if isinstance(rel, str) \
137
+ else (None, "no file")
138
+ if exc or not isinstance(record, dict):
139
+ return None, None
140
+ said, held = ([r.get("instrument"), str(r.get("arm")), r.get("seed"), r.get("n")]
141
+ for r in (row, record))
142
+ if said == held:
143
+ return record, None
144
+ return record, ("%s differs from the seeded draw: its GOLD row names %s arm %s seed %s n %s, "
145
+ "the file %s arm %s seed %s n %s" % tuple([rel] + said + held))
146
+
147
+
148
+ def first_draws(base):
149
+ """{(instrument, arm): gold record} of the draw every rule reads: the latest GOLD row
150
+ the owner recorded as a redraw for that pair (a redraw is a correction), else the
151
+ first row, whose file reads and agrees with it."""
152
+ out, redrawn = {}, set()
153
+ for row in _rows(base, GOLD):
154
+ record, mismatch = gold_record(base, row)
155
+ if record is None or mismatch or not isinstance(record.get("items"), list):
156
+ continue
157
+ key = (record.get("instrument"), str(record.get("arm")))
158
+ if record.get("redraw") is True:
159
+ out[key] = record
160
+ redrawn.add(key)
161
+ elif key not in redrawn:
162
+ out.setdefault(key, record)
163
+ return out
164
+
165
+
166
+ def _is_label(row):
167
+ """A label row carries both `truth` and `output`; any other row labels nothing."""
168
+ return row.get("truth") is not None and row.get("output") is not None
169
+
170
+
171
+ def _replaced(base):
172
+ """{(instrument, arm, item): outcomes of the rows a refile replaced} - the rows
173
+ `tr._live` drops for a later row of the same run, arm, item and instrument."""
174
+ out = {}
175
+ for h in tr._experiment_names(base):
176
+ rows = _rows(base, "experiments/%s/results.jsonl" % h)
177
+ live = {id(r) for r in tr._live(rows)}
178
+ for row in rows:
179
+ name = _row_instrument(row)
180
+ if (name and row.get("item") is not None and row.get("run") is not None
181
+ and id(row) not in live and row.get("status") not in tr.DEAD_ROWS):
182
+ out.setdefault((name, str(row.get("arm") or "all"), str(row["item"])),
183
+ []).append(_outcome(row))
184
+ return out
185
+
186
+
187
+ def _stale(row, items, replaced):
188
+ """True when a label copied the outcome of a row a later refile replaced (plan 128
189
+ K7): it labels a run that no longer counts, so it does not count either."""
190
+ key = (row.get("instrument"), str(row.get("arm")), str(row.get("item")))
191
+ held = _outcome(items.get(key[0], {}).get(key[1], {}).get(key[2])) \
192
+ if isinstance(key[0], str) else None
193
+ return row["output"] != held and row["output"] in replaced.get(key, ())
194
+
195
+
196
+ def first_labels(base):
197
+ """({(instrument, arm, item): label row}, [repeated items]) - the first label counts;
198
+ a stale one (`_stale`) counts nowhere, so its item reads unlabelled."""
199
+ items, replaced = instrument_items(base), _replaced(base)
200
+ out, repeated = {}, []
201
+ for row in filter(_is_label, _rows(base, LABELS)):
202
+ if _stale(row, items, replaced):
203
+ continue
204
+ key = (row.get("instrument"), str(row.get("arm")), str(row.get("item")))
205
+ if key in out:
206
+ repeated.append(key)
207
+ else:
208
+ out[key] = row
209
+ return out, repeated
210
+
211
+
212
+ def label_problems(base):
213
+ """The `label output differs from its results row` refusals: `label` copies a label's
214
+ output from the results row's outcome, so a label holding anything else was not -
215
+ unless it holds the outcome of a row a refile replaced (`_stale`)."""
216
+ items, replaced = instrument_items(base), _replaced(base)
217
+ problems = []
218
+ for row in filter(_is_label, _rows(base, LABELS)):
219
+ name, arm, item = row.get("instrument"), str(row.get("arm")), str(row.get("item"))
220
+ held = _outcome(items.get(name, {}).get(arm, {}).get(item)) \
221
+ if isinstance(name, str) else None
222
+ if row["output"] != held and not _stale(row, items, replaced):
223
+ problems.append("label output differs from its results row: %s arm %s item %s "
224
+ "output %s, results row %s"
225
+ % (name, arm, item, row["output"], "none" if held is None else held))
226
+ return problems
227
+
228
+
229
+ def arm_score(record, labels, name, arm):
230
+ """(n, errors) of a labelled arm, or None when an item of its draw has no label."""
231
+ items = [str(i) for i in record["items"]]
232
+ got = [labels.get((name, arm, i)) for i in items]
233
+ if not items or any(g is None for g in got):
234
+ return None
235
+ return len(items), sum(1 for g in got if g.get("truth") != g.get("output"))
236
+
237
+
238
+ def proportion_threshold(threshold):
239
+ """True when a measure threshold reads as a difference of proportions (at most 1)."""
240
+ return threshold is not None and threshold <= 1
241
+
242
+
243
+ def judge_status(kind, uppers, threshold, floor=None, low=None):
244
+ """(status or None, aggregate) from the labelled arms' uppers ({arm: upper}), per 3.1:
245
+ rejected when the threshold fails over the labelled arms alone. A measure instrument
246
+ with no `mde:`, or one above 1 (the threshold reads a difference of proportions), has no
247
+ threshold, so it is never rejected and stays unvalidated; None leaves the choice between
248
+ validated and unvalidated to the caller. `floor` is the smallest sum of uppers the
249
+ sample could give (every arm at 0 errors) and `low` the sum of its lower bounds: when
250
+ the floor already reaches the threshold while the lower bounds still sit below it, the
251
+ sample cannot decide, so the instrument is unvalidated, not rejected (K6 rater Y6). A
252
+ sample whose lower bounds reach the threshold is rejected on its own evidence."""
253
+ if kind == "verdict":
254
+ agg = max(uppers.values()) if uppers else 0.0
255
+ failed = any(u > VERDICT_BOUND for u in uppers.values())
256
+ else:
257
+ agg = sum(uppers.values())
258
+ if not proportion_threshold(threshold):
259
+ return "unvalidated", agg
260
+ if (floor is not None and uppers and floor >= threshold
261
+ and (low is None or low < threshold)):
262
+ return "unvalidated", agg
263
+ failed = bool(uppers) and agg >= threshold
264
+ return ("rejected" if failed else None), agg
265
+
266
+
267
+ def instrument_report(base, slug, failed=None):
268
+ """({name: status}, report lines). A statistics error of an instrument's PPI line
269
+ goes to `failed` ({"experiment <h>": error}) for each experiment naming the
270
+ instrument (else "instrument <name>"), for `_check_rules8` to refuse (review round
271
+ two R2-1)."""
272
+ failed = {} if failed is None else failed
273
+ declared = {}
274
+ for row in _rows(base, INSTRUMENTS):
275
+ if isinstance(row.get("name"), str):
276
+ declared.setdefault(row["name"], row)
277
+ items = instrument_items(base)
278
+ used = _experiments_of(base)
279
+ draws = first_draws(base)
280
+ labels, _ = first_labels(base)
281
+ statuses, lines = {}, []
282
+ for name in sorted(set(declared) | set(items)):
283
+ spec = declared.get(name)
284
+ kind = spec.get("kind") if spec else "measure"
285
+ mdes = [m for m in (_mde(_protocol(base, h)) for h in used.get(name, ())) if m is not None]
286
+ threshold = VERDICT_BOUND if kind == "verdict" else (min(mdes) if mdes else None)
287
+ uppers, complete, sizes, lows = {}, True, {}, {}
288
+ for arm in sorted(items.get(name, {})):
289
+ record = draws.get((name, arm))
290
+ score = arm_score(record, labels, name, arm) if record else None
291
+ if score is None:
292
+ complete = False
293
+ continue
294
+ n, errors = score
295
+ sizes[arm] = n
296
+ uppers[arm] = st.clopper_pearson(errors, n)[1]
297
+ lows[arm] = st.clopper_pearson(errors, n)[0]
298
+ lines.append("instrument %s: %s arm %s n=%d errors=%d upper=%.4f"
299
+ % (slug, name, arm, n, errors, uppers[arm]))
300
+ if spec and spec.get("rung") == "R3":
301
+ try:
302
+ lines.extend(_ppi_line(slug, name, arm, record, labels, items[name][arm]))
303
+ except (ValueError, ArithmeticError) as exc:
304
+ for label in (["experiment %s" % h for h in sorted(used.get(name, ()))]
305
+ or ["instrument %s" % name]):
306
+ failed.setdefault(label, str(exc) or type(exc).__name__)
307
+ floor = (sum(st.clopper_pearson(0, n)[1] for n in sizes.values())
308
+ if sizes else None)
309
+ low = sum(lows.values()) if lows else None
310
+ status, agg = judge_status(kind, uppers, threshold, floor, low)
311
+ if status is None:
312
+ status = "validated" if spec and uppers and complete else "unvalidated"
313
+ statuses[name] = status
314
+ lines.append("instrument %s: %s %s %s=%.4f threshold=%s%s"
315
+ % (slug, name, status, "max" if kind == "verdict" else "sum", agg,
316
+ "none" if threshold is None else "%.4f" % threshold,
317
+ "" if threshold is None or proportion_threshold(threshold) else
318
+ " (mde above 1: the threshold reads a difference of proportions)"))
319
+ return statuses, lines
320
+
321
+
322
+ def _outcome(row):
323
+ """A row's numeric `outcome`; a JSON boolean counts as 1/0."""
324
+ value = row.get("outcome") if isinstance(row, dict) else None
325
+ if isinstance(value, bool):
326
+ return int(value)
327
+ return value if isinstance(value, (int, float)) else None
328
+
329
+
330
+ def _ppi_line(slug, name, arm, record, labels, arm_items):
331
+ drawn = [str(i) for i in record["items"]]
332
+ y = [labels[(name, arm, i)].get("truth") for i in drawn]
333
+ f = [labels[(name, arm, i)].get("output") for i in drawn]
334
+ rest = [_outcome(r) for i, r in sorted(arm_items.items()) if i not in drawn]
335
+ if not rest or None in rest or any(not isinstance(v, (int, float)) for v in y + f):
336
+ return []
337
+ lam, est, lo, hi = st.ppi_mean(y, f, rest)
338
+ return ["ppi %s: %s arm %s lambda=%.4f estimate=%.4f ci=%s"
339
+ % (slug, name, arm, lam, est, "none" if lo is None else tr._format_ci(lo, hi))]
340
+
341
+
342
+ def _items_at(base, rev):
343
+ """{name: {arm: set(items)}} from the results files and the verdicts ledger - the
344
+ pool `instrument_items` draws from (plan 128 K11) - as committed at `rev`, or as
345
+ they are on disk when `rev` is None."""
346
+ out = {}
347
+ rels = ["experiments/%s/results.jsonl" % h for h in tr._experiment_names(base)]
348
+ for rel in rels + [VERDICTS]:
349
+ if rev is None:
350
+ try:
351
+ with open(os.path.join(base, rel), encoding="utf-8") as fh:
352
+ text = fh.read()
353
+ except (OSError, UnicodeDecodeError):
354
+ continue
355
+ else:
356
+ done = subprocess.run(["git", "-C", base, "show", "%s:./%s" % (rev, rel)],
357
+ capture_output=True, text=True)
358
+ if done.returncode:
359
+ continue
360
+ text = done.stdout
361
+ rows = []
362
+ for raw in text.splitlines():
363
+ try:
364
+ row = json.loads(raw)
365
+ except ValueError:
366
+ continue
367
+ if isinstance(row, dict):
368
+ rows.append(row)
369
+ # verdict rows are read whole and under arm `all`, as `instrument_items` reads them
370
+ for row in (rows if rel == VERDICTS else tr._live(rows)):
371
+ if _row_instrument(row) and row.get("item") is not None:
372
+ out.setdefault(_row_instrument(row), {}).setdefault(
373
+ "all" if rel == VERDICTS else str(row.get("arm") or "all"),
374
+ set()).add(str(row["item"]))
375
+ return out
376
+
377
+
378
+ def _added_rev(base, rel):
379
+ try:
380
+ done = subprocess.run(["git", "-C", base, "log", "--diff-filter=A", "--format=%H",
381
+ "--", rel], capture_output=True, text=True)
382
+ except OSError:
383
+ return None
384
+ revs = done.stdout.split() if not done.returncode else []
385
+ return revs[-1] if revs else None
386
+
387
+
388
+ def gold_problems(base, git):
389
+ """The `differs from the seeded draw` refusals: a GOLD row and its gold file must agree,
390
+ and the file must be its seed's draw over the arm's items now, or over the items committed
391
+ when the file was added."""
392
+ problems = []
393
+ now = instrument_items(base)
394
+ for row in _rows(base, GOLD):
395
+ record, mismatch = gold_record(base, row)
396
+ if record is None:
397
+ continue
398
+ if mismatch:
399
+ problems.append(mismatch)
400
+ continue
401
+ rel = row["file"]
402
+ name, arm, seed = record.get("instrument"), str(record.get("arm")), record.get("seed")
403
+ drawn = [str(i) for i in record.get("items") or []]
404
+ n = record.get("n") if isinstance(record.get("n"), int) else len(drawn)
405
+
406
+ def drawn_from(pool):
407
+ return len(drawn) == n and seeded_draw(pool, seed, arm, n) == drawn
408
+ if drawn_from(now.get(name, {}).get(arm, {})):
409
+ continue
410
+ rev = _added_rev(base, rel) if git else None
411
+ if not (rev and drawn_from(_items_at(base, rev).get(name, {}).get(arm, set()))):
412
+ problems.append("%s differs from the seeded draw of seed %s over %s arm %s"
413
+ % (rel, seed, name, arm))
414
+ return problems
415
+
416
+
417
+ # --- certainty (1, 2) ----------------------------------------------------------------
418
+ def _experiment_claim(claim):
419
+ return tr._experiments_named(tr._cited(claim.get("proof")))
420
+
421
+
422
+ def start_level(claim, base, classes):
423
+ """The highest start over the parts the claim's proof cites (section 1 table)."""
424
+ levels = [KIND_START[claim.get("kind")]] if claim.get("kind") in KIND_START else []
425
+ for h in _experiment_claim(claim):
426
+ text = _protocol(base, h)
427
+ design = (_label(text, "design") or "").lower()
428
+ if not design:
429
+ design = "experiment" if _label(text, "change") is not None else "observational"
430
+ levels.append(3 if design.startswith("experiment") else 1)
431
+ import tezgah_research_capture as cap
432
+ for token in cap.cited_tokens(claim.get("proof")):
433
+ if token.startswith("literature/") and not token.startswith("literature/."):
434
+ levels.append(SOURCE_START.get(classes.get(token[len("literature/"):]), 0))
435
+ return max(levels) if levels else 0
436
+
437
+
438
+ def compute(start, names):
439
+ """(level, steps): decrements first, then ceilings; each step once, in order found."""
440
+ seen = list(dict.fromkeys(names))
441
+ level = max(0, start - sum(1 for s in seen if STEPS.get(s) == DEC))
442
+ if any(STEPS.get(s) == CEIL for s in seen):
443
+ level = min(level, 1)
444
+ return level, seen
445
+
446
+
447
+ def _add(steps, cid, name):
448
+ steps.setdefault(cid, [])
449
+ if name not in steps[cid]:
450
+ steps[cid].append(name)
451
+
452
+
453
+ def _report_line(text, cid, level):
454
+ """True when a report line names the claim id and its level word (and `exploratory`
455
+ below moderate)."""
456
+ word = re.compile(r"(?<![\w-])%s(?![\w-])" % re.escape(LEVELS[level]))
457
+ ident = re.compile(r"(?<![\w-])%s(?![\w-])" % re.escape(cid))
458
+ return any(ident.search(line) and word.search(line)
459
+ and (level >= 2 or "exploratory" in line) for line in text.splitlines())
460
+
461
+
462
+ def check(repo, slug, base, state, errors, warnings, strict, git, steps):
463
+ report = steps.setdefault("#report", [])
464
+ claims = [c for c in _rows(base, "claims.jsonl") if isinstance(c.get("id"), str)]
465
+ statuses, lines = instrument_report(base, slug, steps.setdefault("#stats", {}))
466
+ report.extend(lines)
467
+ errors.extend(gold_problems(base, git))
468
+ errors.extend(label_problems(base))
469
+ _, repeated = first_labels(base)
470
+ for _name, _arm, item in repeated:
471
+ warnings.append("label repeated for %s; the first counts" % item)
472
+ by_exp = _experiments_of(base)
473
+ for claim in claims:
474
+ cid = claim["id"]
475
+ for h in _experiment_claim(claim):
476
+ for name, exps in sorted(by_exp.items()):
477
+ if h in exps and statuses.get(name) in ("unvalidated", "rejected"):
478
+ _add(steps, cid, "instrument-" + statuses[name])
479
+ if any(r.get("judge") and not (r.get("generator") and r.get("order"))
480
+ for r in _results(base, h)):
481
+ _add(steps, cid, "judged-row-incomplete")
482
+ verifier = claim.get("verified_by")
483
+ if verifier:
484
+ spec = next((r for r in _rows(base, INSTRUMENTS) if r.get("name") == verifier), {})
485
+ if spec.get("kind") != "verdict" or statuses.get(verifier) != "validated":
486
+ _add(steps, cid, "uncalibrated-verifier")
487
+ classes = tr._index_classes(base)
488
+ certainty = steps.setdefault("#certainty", {})
489
+ try:
490
+ with open(os.path.join(base, "to_human", "report.md"), encoding="utf-8") as fh:
491
+ text = fh.read()
492
+ except (OSError, UnicodeDecodeError):
493
+ text = ""
494
+ concluded = tr._concluding(base, state)
495
+ for claim in claims:
496
+ cid = claim["id"]
497
+ start = start_level(claim, base, classes)
498
+ level, names = compute(start, steps.get(cid, []))
499
+ certainty[cid] = {"level": LEVELS[level], "start": LEVELS[start], "steps": names}
500
+ report.append("certainty %s: %s %s start=%s steps=%s"
501
+ % (slug, cid, LEVELS[level], LEVELS[start], ",".join(names) or "none"))
502
+ declared = claim.get("certainty")
503
+ if declared in LEVELS and LEVELS.index(declared) > level:
504
+ errors.append("claim %s declares certainty %s above the computed %s (%s)"
505
+ % (cid, declared, LEVELS[level], ",".join(names) or "none"))
506
+ if concluded and not _report_line(text, cid, level):
507
+ tr._soft(errors, warnings, strict, "report states no certainty for claim %s" % cid)
508
+ labels = _rows(base, LABELS)
509
+ human = sum(1 for r in labels if (r.get("rater") or {}).get("kind") == "human")
510
+ if concluded and human and not re.search(r"user-supplied:\s*\d+\s+of\s+\d+\s+labels", text):
511
+ tr._soft(errors, warnings, strict, "report states no user-supplied share of labels "
512
+ "(user-supplied: %d of %d labels)" % (human, len(first_labels(base)[0])))
513
+
514
+
515
+ def conclude_problems(repo, slug, base, state):
516
+ """A rejected instrument needs a switches.jsonl row before `conclude` (3.1)."""
517
+ statuses, _ = instrument_report(base, slug)
518
+ switched = {r.get("instrument") for r in _rows(base, SWITCHES)}
519
+ return ["instrument %s is rejected and has no switch or narrowing row: run `tezgah-research "
520
+ "switch %s %s --to <rung|method>|--narrow \"<what the claims now say>\" --reason "
521
+ "\"<why>\"`" % (name, slug, name)
522
+ for name, status in sorted(statuses.items())
523
+ if status == "rejected" and name not in switched]
524
+
525
+
526
+ # --- commands ----------------------------------------------------------------------
527
+ def _misuse(message):
528
+ print("tezgah-research: %s" % message, file=sys.stderr)
529
+ return 2
530
+
531
+
532
+ def _fail(slug, message):
533
+ print("FAIL %s: %s" % (slug, message))
534
+ return 1
535
+
536
+
537
+ def _flags(args, valued, usage):
538
+ """({flag: value}, None) or (None, exit code); every flag takes one value."""
539
+ out, rest = {}, list(args)
540
+ while rest:
541
+ flag = rest.pop(0)
542
+ if flag not in valued or not rest:
543
+ return None, _misuse(usage)
544
+ out[flag] = rest.pop(0)
545
+ return out, None
546
+
547
+
548
+ def _line(repo, slug):
549
+ """(base, None) or (None, exit code)."""
550
+ if not repo or slug not in tr.slugs(repo):
551
+ return None, _misuse("no such research line: %s" % slug)
552
+ base = tr.line_dir(repo, slug)
553
+ newer = tr.newer_rules(tr._state(base))
554
+ if newer:
555
+ return None, _fail(slug, newer)
556
+ return base, None
557
+
558
+
559
+ def _append(base, rel, rows):
560
+ import tezgah_research_capture as cap
561
+ cap.append_rows(base, rel, rows)
562
+
563
+
564
+ def _commit(repo, base, rels, command, slug):
565
+ import tezgah_research_capture as cap
566
+ err = cap.commit_capture(repo, base, rels, command, slug)
567
+ if err:
568
+ print("commit failed: %s" % err)
569
+ return 1
570
+ return 0
571
+
572
+
573
+ def cmd_instrument(repo, args):
574
+ usage = ('instrument add <slug> <name> --kind measure|verdict --rung R1|R2|R3|R4 '
575
+ '--construct "<text>"')
576
+ if len(args) < 3 or args[0] != "add":
577
+ return _misuse(usage)
578
+ slug, name = args[1], args[2]
579
+ flags, code = _flags(args[3:], ("--kind", "--rung", "--construct"), usage)
580
+ if code is not None:
581
+ return code
582
+ if flags.get("--kind") not in ("measure", "verdict") or flags.get("--rung") not in RUNGS \
583
+ or not flags.get("--construct", "").strip() or not name.strip():
584
+ return _misuse(usage)
585
+ base, code = _line(repo, slug)
586
+ if code is not None:
587
+ return code
588
+ if any(r.get("name") == name for r in _rows(base, INSTRUMENTS)):
589
+ return _fail(slug, "instrument %s is already in %s" % (name, INSTRUMENTS))
590
+ _append(base, INSTRUMENTS, [{"name": name, "kind": flags["--kind"], "rung": flags["--rung"],
591
+ "construct": flags["--construct"], "added_at": _now()}])
592
+ print("instrument %s added to %s" % (name, INSTRUMENTS))
593
+ return _commit(repo, base, [INSTRUMENTS], "instrument add", slug)
594
+
595
+
596
+ def cmd_gold(repo, args):
597
+ usage = ("gold <slug> <instrument> --arm <a> --n N --seed S "
598
+ '[--redraw --ack "<the user\'s words>"]')
599
+ redraw, ack = "--redraw" in args, ""
600
+ if redraw:
601
+ args = [a for a in args if a != "--redraw"]
602
+ if "--ack" in args:
603
+ i = args.index("--ack")
604
+ ack = args[i + 1] if i + 1 < len(args) else ""
605
+ args = args[:i] + args[i + 2:]
606
+ if len(args) < 2:
607
+ return _misuse(usage)
608
+ slug, name = args[0], args[1]
609
+ flags, code = _flags(args[2:], ("--arm", "--n", "--seed"), usage)
610
+ if code is not None:
611
+ return code
612
+ try:
613
+ n, seed = int(flags["--n"]), int(flags["--seed"])
614
+ arm = flags["--arm"]
615
+ except (KeyError, ValueError):
616
+ return _misuse(usage)
617
+ if n < 1:
618
+ return _misuse(usage)
619
+ if redraw and not ack.strip():
620
+ return _misuse('gold --redraw needs --ack "<the user\'s words>"')
621
+ if ack and not redraw:
622
+ return _misuse(usage)
623
+ base, code = _line(repo, slug)
624
+ if code is not None:
625
+ return code
626
+ if any(r.get("instrument") == name and str(r.get("arm")) == arm
627
+ for r in _rows(base, LABELS)) and not redraw:
628
+ return _fail(slug, "gold draw after labels: %s arm %s already has labels, so its first "
629
+ "draw stays the one every rule reads; the owner may record a redraw "
630
+ "(`gold ... --redraw --ack \"<the user's words>\"`), and the labels of "
631
+ "items the new draw drops then read stale" % (name, arm))
632
+ items = instrument_items(base).get(name, {}).get(arm, {})
633
+ if len(items) < n:
634
+ return _fail(slug, "%s arm %s has %d items, fewer than --n %d" % (name, arm, len(items), n))
635
+ # the draw has to be the one the rows committed with the gold file give, or `check`'s
636
+ # add-time reading misses it and the next row makes it `differs from the seeded draw`
637
+ # for good (K6 rater X2); no git work tree commits nothing, so nothing is frozen
638
+ if not tr._git_out(repo, "rev-parse", "--is-inside-work-tree")[1] \
639
+ and _items_at(base, None).get(name, {}).get(arm) \
640
+ != _items_at(base, "HEAD").get(name, {}).get(arm):
641
+ return _fail(slug, "%s arm %s has results or verdict rows that are not committed, "
642
+ "so the gold "
643
+ "file would differ from the seeded draw of the rows committed with it "
644
+ "- commit them first (`tezgah-research commit %s \"<message>\"`); "
645
+ "nothing was written" % (name, arm, slug))
646
+ record = {"instrument": name, "arm": arm, "seed": seed, "n": n,
647
+ "items": seeded_draw(items, seed, arm, n)}
648
+ if redraw:
649
+ record["redraw"] = True
650
+ record["ack"] = ack.strip()
651
+ data = (json.dumps(record, indent=2) + "\n").encode("utf-8")
652
+ rel = "instruments/gold/%s-%s-%d.json" % (name, arm, seed)
653
+ import tezgah_research_capture as cap
654
+ problem = cap.write_capture(base, rel, data)
655
+ if problem and redraw and "capture exists" in problem:
656
+ # a redraw with another seed is a new file; the same seed may not be rewritten, so
657
+ # the owner picks a seed the arm has not drawn, or the draw stands
658
+ return _fail(slug, problem)
659
+ if problem:
660
+ return _fail(slug, problem)
661
+ row = {"instrument": name, "arm": arm, "seed": seed, "n": n, "file": rel,
662
+ "sha256": hashlib.sha256(data).hexdigest(), "drawn_at": _now()}
663
+ if redraw:
664
+ row.update(redraw=True, ack=ack.strip())
665
+ _append(base, GOLD, [row])
666
+ print("gold %s: %d items of %s arm %s" % (rel, n, name, arm))
667
+ return _commit(repo, base, [rel, GOLD], "gold", slug)
668
+
669
+
670
+ def _ask_model(name, construct, item, text, model, key):
671
+ """(truth or None, the response's model)."""
672
+ import tezgah_research_capture as cap
673
+ body = json.dumps({"messages": [{"content": PROMPT % (name, construct, item, text),
674
+ "role": "user"}], "model": model, "temperature": 0},
675
+ sort_keys=True, separators=(",", ":")).encode("utf-8")
676
+ status, data = cap.fetch_url(OPENROUTER, body=body,
677
+ headers={"Authorization": "Bearer " + key,
678
+ "Content-Type": "application/json"})
679
+ try:
680
+ answer = json.loads(data.decode("utf-8"))
681
+ content = answer["choices"][0]["message"]["content"]
682
+ except (ValueError, KeyError, IndexError, TypeError, AttributeError, UnicodeDecodeError):
683
+ return None, None
684
+ found = re.search(r"[01]", content if status == 200 and isinstance(content, str) else "")
685
+ return (int(found.group(0)) if found else None), answer.get("model")
686
+
687
+
688
+ def cmd_label(repo, args):
689
+ usage = ('label <slug> <instrument> --arm <a> (--human "<name>" --ack "<the user\'s words>"'
690
+ ' | --provider openrouter --model <m>)')
691
+ if len(args) < 2:
692
+ return _misuse(usage)
693
+ slug, name = args[0], args[1]
694
+ flags, code = _flags(args[2:], ("--arm", "--human", "--ack", "--provider", "--model"), usage)
695
+ if code is not None:
696
+ return code
697
+ arm = flags.get("--arm")
698
+ human = "--human" in flags
699
+ if not arm or human == ("--provider" in flags):
700
+ return _misuse(usage)
701
+ if human and not flags.get("--ack", "").strip():
702
+ return _misuse("label --human needs --ack \"<the user's words>\"")
703
+ key = os.environ.get("OPENROUTER_API_KEY", "")
704
+ if not human:
705
+ if flags["--provider"] != "openrouter" or not flags.get("--model"):
706
+ return _misuse(usage)
707
+ if not key:
708
+ return _misuse("label --provider openrouter needs OPENROUTER_API_KEY")
709
+ base, code = _line(repo, slug)
710
+ if code is not None:
711
+ return code
712
+ record = first_draws(base).get((name, arm))
713
+ if record is None:
714
+ return _fail(slug, "%s arm %s has no gold draw: run `gold` first" % (name, arm))
715
+ drawn = [str(i) for i in record["items"]]
716
+ rows = instrument_items(base).get(name, {}).get(arm, {})
717
+ out = []
718
+ if human:
719
+ rater = {"kind": "human", "name": flags["--human"], "ack": flags["--ack"]}
720
+ for raw in sys.stdin.read().splitlines():
721
+ if not raw.strip():
722
+ continue
723
+ try:
724
+ given = json.loads(raw)
725
+ item, truth = str(given["item"]), given["truth"]
726
+ except (ValueError, KeyError, TypeError):
727
+ return _misuse("label --human reads JSONL {\"item\", \"truth\"} on stdin")
728
+ if truth not in (0, 1) or isinstance(truth, bool):
729
+ return _misuse("truth of %s is %r, not 0 or 1" % (item, truth))
730
+ if item not in drawn:
731
+ return _fail(slug, "%s is not in the gold sample of %s arm %s" % (item, name, arm))
732
+ out.append((item, truth, rater))
733
+ else:
734
+ spec = next((r for r in _rows(base, INSTRUMENTS) if r.get("name") == name), None)
735
+ if spec is None:
736
+ return _fail(slug, "instrument %s is not in %s" % (name, INSTRUMENTS))
737
+ for item in drawn:
738
+ text = (rows.get(item) or {}).get("text") or ""
739
+ truth, model = _ask_model(name, spec.get("construct", ""), item, text,
740
+ flags["--model"], key)
741
+ if truth is not None:
742
+ out.append((item, truth, {"kind": "model", "provider": "openrouter",
743
+ "model": model}))
744
+ when = _now()
745
+ _append(base, LABELS, [{"instrument": name, "arm": arm, "item": item, "truth": truth,
746
+ "output": _outcome(rows.get(item)), "rater": rater,
747
+ "labelled_at": when} for item, truth, rater in out])
748
+ print("labels %d of %d gold items of %s arm %s" % (len(out), len(drawn), name, arm))
749
+ return _commit(repo, base, [LABELS], "label", slug)
750
+
751
+
752
+ def cmd_switch(repo, args):
753
+ usage = ('switch <slug> <instrument> (--to R1|R2|R3|R4|<method> | --narrow "<what the '
754
+ 'claims now say>") --reason "<why>"')
755
+ if len(args) < 2:
756
+ return _misuse(usage)
757
+ slug, name = args[0], args[1]
758
+ flags, code = _flags(args[2:], ("--to", "--narrow", "--reason"), usage)
759
+ if code is not None:
760
+ return code
761
+ if ("--to" in flags) == ("--narrow" in flags) or not flags.get("--reason", "").strip() \
762
+ or not (flags.get("--to") or flags.get("--narrow") or "").strip():
763
+ return _misuse(usage)
764
+ base, code = _line(repo, slug)
765
+ if code is not None:
766
+ return code
767
+ row = {"instrument": name, "reason": flags["--reason"], "switched_at": _now()}
768
+ row.update({"to": flags["--to"]} if "--to" in flags else {"narrow": flags["--narrow"]})
769
+ _append(base, SWITCHES, [row])
770
+ print("switch recorded for %s" % name)
771
+ return _commit(repo, base, [SWITCHES], "switch", slug)
772
+
773
+
774
+ def case_flagged(case, root):
775
+ """True when `check`'s own functions refuse or cap a known-truth case."""
776
+ import tezgah_research_capture as cap
777
+ kind = case.get("kind")
778
+ if kind == "citation":
779
+ return cap.citation_problem(case) is not None
780
+ if kind == "quote":
781
+ with open(os.path.join(root, case["text_file"]), encoding="utf-8") as fh:
782
+ return cap.quote_problem(case["quote"], fh.read()) is not None
783
+ if kind == "instrument":
784
+ # the case row's own `kind` is "instrument", so the instrument's kind is
785
+ # `instrument_kind` (measure by default); a measure case stands for two equal arms
786
+ # of (n, errors), the 3.1 fixed points' shape
787
+ ikind = case.get("instrument_kind", "measure")
788
+ mde = case.get("mde") if ikind == "measure" else VERDICT_BOUND
789
+ upper = st.clopper_pearson(case["errors"], case["n"])[1]
790
+ arms = {"all": upper} if ikind == "verdict" else {"a": upper, "b": upper}
791
+ floor = (2 * st.clopper_pearson(0, case["n"])[1]) if ikind == "measure" else None
792
+ low = (2 * st.clopper_pearson(case["errors"], case["n"])[0]
793
+ if ikind == "measure" else None)
794
+ status, _ = judge_status(ikind, arms, mde, floor, low)
795
+ return status in ("rejected", "unvalidated")
796
+ raise ValueError("unknown case kind %r" % kind)
797
+
798
+
799
+ def cmd_release_check(repo, args):
800
+ if args and (args[0] != "--fixtures" or len(args) != 2):
801
+ return _misuse("release-check [--fixtures <dir>]")
802
+ root = os.path.abspath(args[1]) if args else KNOWN_TRUTH
803
+ try:
804
+ cases = [json.loads(line) for line in open(os.path.join(root, "cases.jsonl"),
805
+ encoding="utf-8") if line.strip()]
806
+ except (OSError, ValueError) as exc:
807
+ return _misuse("release-check: %s/cases.jsonl does not read (%s)" % (root, exc))
808
+ os.environ["TEZGAH_CAPTURE_MAP"] = os.path.join(root, "capture-map.json")
809
+ counts = {"defect": [0, 0], "sound": [0, 0]}
810
+ for case in cases:
811
+ flagged = case_flagged(case, root)
812
+ tally = counts.setdefault(case.get("truth"), [0, 0])
813
+ tally[1] += 1
814
+ tally[0] += (not flagged) if case.get("truth") == "defect" else flagged
815
+ worst = 0
816
+ for label, truth, bound in (("misses", "defect", 0.10), ("false alarms", "sound", 0.20)):
817
+ k, n = counts[truth]
818
+ upper = st.clopper_pearson(k, n)[1] if n else 1.0
819
+ print("%s: %d/%d upper=%.4f" % (label, k, n, upper))
820
+ worst = max(worst, upper > bound)
821
+ return 1 if worst else 0
822
+
823
+
824
+ def cmd_calibration(repo, args):
825
+ if args:
826
+ return _misuse("calibration takes no arguments")
827
+ pairs = []
828
+ for slug in tr.slugs(repo) if repo else []:
829
+ for h in tr._state(tr.line_dir(repo, slug)).get("hypotheses") or []:
830
+ p = h.get("probability") if isinstance(h, dict) else None
831
+ outcome = {"supported": 1, "refuted": 0}.get(h.get("status")) \
832
+ if isinstance(h, dict) else None
833
+ if isinstance(p, (int, float)) and not isinstance(p, bool) and outcome is not None:
834
+ pairs.append((p, outcome))
835
+ print("brier: %s over %d resolved hypotheses"
836
+ % ("%.4f" % st.brier(pairs) if pairs else "none", len(pairs)))
837
+ return 0
838
+
839
+
840
+ COMMANDS = {"instrument": cmd_instrument, "gold": cmd_gold, "label": cmd_label,
841
+ "switch": cmd_switch, "release-check": cmd_release_check,
842
+ "calibration": cmd_calibration}