@r1z4x/tezgah 1.9.0 → 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.
- package/CHANGELOG.md +98 -1
- package/MANIFEST +382 -144
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/bin/codegen +5 -2
- package/bin/tezgah-design +288 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +39 -2
- package/bin/tezgah-import-ai-research +26 -12
- package/bin/tezgah-ocr-review +296 -0
- package/bin/tezgah-research +216 -40
- package/bin/tezgah-review-quote +185 -0
- package/bin/tezgah-setup +136 -33
- package/bin/tezgah-skill +1 -1
- package/bin/tezgah-status +201 -3
- package/bin/tezgah-task +203 -3
- package/commands/adhd.md +2 -2
- package/commands/ponytail.md +4 -4
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +2 -2
- package/docs/clarity-baseline.json +2 -2
- package/docs/contract.md +87 -42
- package/docs/environment.md +5 -3
- package/docs/evidence.md +28 -9
- package/docs/feature-audit.md +2 -2
- package/docs/gate.md +54 -31
- package/docs/glossary.md +5 -2
- package/docs/hosts.md +17 -3
- package/docs/index.json +34 -8
- package/docs/judge.md +4 -2
- package/docs/layers.md +3 -3
- package/docs/models.md +31 -9
- package/docs/operations.md +148 -8
- package/docs/research.md +786 -5
- package/docs/skills.md +137 -13
- package/docs/status-line.md +115 -1
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +10 -0
- package/hooks/lang/tr.json +11 -1
- package/hooks/tezgah_agents.py +38 -22
- package/hooks/tezgah_apps.py +18 -0
- package/hooks/tezgah_components.py +4 -0
- package/hooks/tezgah_context.py +189 -50
- package/hooks/tezgah_extension.py +337 -0
- package/hooks/tezgah_gate.py +188 -56
- package/hooks/tezgah_integrity.py +652 -105
- package/hooks/tezgah_judge.py +6 -4
- package/hooks/tezgah_models.py +59 -19
- package/hooks/tezgah_orca.py +1 -1
- package/hooks/tezgah_paths.py +102 -6
- package/hooks/tezgah_policy.py +94 -45
- package/hooks/tezgah_rank.py +9 -6
- package/hooks/tezgah_replay.py +4 -4
- package/hooks/tezgah_research.py +1260 -65
- package/hooks/tezgah_research_capture.py +1544 -0
- package/hooks/tezgah_research_evidence.py +842 -0
- package/hooks/tezgah_research_stats.py +258 -0
- package/hooks/tezgah_skill_pick.py +408 -106
- package/hooks/tezgah_stopspec.py +8 -1
- package/hooks/tezgah_task.py +12 -4
- package/hooks/tezgah_taste_ledger.py +1 -1
- package/hosts/cursor/hook.py +29 -2
- package/hosts/opencode/plugins/tezgah.js +307 -46
- package/hosts/opencode/tui/cli/tui.tsx +100 -0
- package/hosts/opencode/tui/tezgah-tui.tsx +2 -1
- package/output-styles/tezgah.md +10 -10
- package/package.json +1 -1
- package/packaging/install.ps1 +36 -3
- package/packaging/install.sh +13 -3
- package/packaging/upgrade.sh +61 -7
- package/skills/ai-research/SKILL.md +21 -14
- package/skills/ai-research/SOURCE +7 -5
- package/skills/ai-research/index/1-frame.md +6 -6
- package/skills/ai-research/index/2-data.md +9 -9
- package/skills/ai-research/index/3-train.md +36 -36
- package/skills/ai-research/index/4-measure.md +13 -13
- package/skills/ai-research/index/5-run.md +11 -11
- package/skills/ai-research/index/6-write.md +23 -23
- package/skills/ai-research/library.json +104 -104
- package/skills/analyze-app/SKILL.md +17 -12
- package/skills/design-contract/SKILL.md +42 -7
- package/skills/design-contract/data-views.md +2 -2
- package/skills/design-library/EVALS.md +1 -1
- package/skills/design-library/INDEX.md +39 -39
- package/skills/design-library/SKILL.md +19 -13
- package/skills/design-library/SOURCE +45 -43
- package/skills/feature-audit/SKILL.md +20 -14
- package/skills/hallmark/LICENSE +21 -0
- package/skills/hallmark/SKILL.md +154 -0
- package/skills/hallmark/SOURCE +207 -0
- package/skills/hallmark/agents/openai.yaml +13 -0
- package/skills/hallmark/references/anatomy.md +71 -0
- package/skills/hallmark/references/brand-truth.md +40 -0
- package/skills/hallmark/references/comp.md +104 -0
- package/skills/hallmark/references/craft.md +116 -0
- package/skills/hallmark/references/dark-mode.md +49 -0
- package/skills/hallmark/references/data-viz.md +48 -0
- package/skills/hallmark/references/design-md.md +53 -0
- package/skills/hallmark/references/direction.md +52 -0
- package/skills/hallmark/references/export-formats.md +80 -0
- package/skills/hallmark/references/flow.md +131 -0
- package/skills/hallmark/references/fonts.md +58 -0
- package/skills/hallmark/references/gates.md +243 -0
- package/skills/hallmark/references/genres/atmospheric.md +24 -0
- package/skills/hallmark/references/genres/editorial.md +24 -0
- package/skills/hallmark/references/genres/modern-minimal.md +24 -0
- package/skills/hallmark/references/genres/playful.md +24 -0
- package/skills/hallmark/references/imagery.md +99 -0
- package/skills/hallmark/references/interaction-and-states.md +89 -0
- package/skills/hallmark/references/layout.md +104 -0
- package/skills/hallmark/references/macrostructures/01-bento-grid.md +21 -0
- package/skills/hallmark/references/macrostructures/02-long-document.md +35 -0
- package/skills/hallmark/references/macrostructures/03-marquee-hero.md +21 -0
- package/skills/hallmark/references/macrostructures/04-stat-led.md +21 -0
- package/skills/hallmark/references/macrostructures/05-workbench.md +21 -0
- package/skills/hallmark/references/macrostructures/06-conversational-faq.md +28 -0
- package/skills/hallmark/references/macrostructures/07-manifesto.md +28 -0
- package/skills/hallmark/references/macrostructures/08-photographic.md +21 -0
- package/skills/hallmark/references/macrostructures/09-quote-led.md +21 -0
- package/skills/hallmark/references/macrostructures/10-specimen.md +21 -0
- package/skills/hallmark/references/macrostructures/11-catalogue.md +21 -0
- package/skills/hallmark/references/macrostructures/12-letter.md +21 -0
- package/skills/hallmark/references/macrostructures/13-index-first.md +21 -0
- package/skills/hallmark/references/macrostructures/14-narrative-workflow.md +21 -0
- package/skills/hallmark/references/macrostructures/15-split-studio.md +21 -0
- package/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/skills/hallmark/references/macrostructures/17-type-specimen.md +19 -0
- package/skills/hallmark/references/macrostructures/18-portfolio-grid.md +21 -0
- package/skills/hallmark/references/macrostructures/19-map-diagram.md +21 -0
- package/skills/hallmark/references/macrostructures/20-ecosystem-index.md +19 -0
- package/skills/hallmark/references/macrostructures/21-component-playground.md +21 -0
- package/skills/hallmark/references/macrostructures.md +119 -0
- package/skills/hallmark/references/microinteractions.md +80 -0
- package/skills/hallmark/references/patterns.md +202 -0
- package/skills/hallmark/references/redesign.md +88 -0
- package/skills/hallmark/references/responsive.md +46 -0
- package/skills/hallmark/references/review-finish.md +19 -0
- package/skills/hallmark/references/review-hero.md +20 -0
- package/skills/hallmark/references/scroll-choreography.md +110 -0
- package/skills/hallmark/references/texture.md +90 -0
- package/skills/hallmark/references/themes/README.md +58 -0
- package/skills/hallmark/references/themes/almanac.jpg +0 -0
- package/skills/hallmark/references/themes/almanac.md +103 -0
- package/skills/hallmark/references/themes/arcade.jpg +0 -0
- package/skills/hallmark/references/themes/arcade.md +102 -0
- package/skills/hallmark/references/themes/atelier.jpg +0 -0
- package/skills/hallmark/references/themes/atelier.md +98 -0
- package/skills/hallmark/references/themes/bloom.jpg +0 -0
- package/skills/hallmark/references/themes/bloom.md +102 -0
- package/skills/hallmark/references/themes/blueprint.jpg +0 -0
- package/skills/hallmark/references/themes/blueprint.md +103 -0
- package/skills/hallmark/references/themes/broadside.jpg +0 -0
- package/skills/hallmark/references/themes/broadside.md +108 -0
- package/skills/hallmark/references/themes/brutal.jpg +0 -0
- package/skills/hallmark/references/themes/brutal.md +95 -0
- package/skills/hallmark/references/themes/carnival.jpg +0 -0
- package/skills/hallmark/references/themes/carnival.md +104 -0
- package/skills/hallmark/references/themes/cobalt.jpg +0 -0
- package/skills/hallmark/references/themes/cobalt.md +101 -0
- package/skills/hallmark/references/themes/console.jpg +0 -0
- package/skills/hallmark/references/themes/console.md +99 -0
- package/skills/hallmark/references/themes/coral.jpg +0 -0
- package/skills/hallmark/references/themes/coral.md +102 -0
- package/skills/hallmark/references/themes/darkroom.jpg +0 -0
- package/skills/hallmark/references/themes/darkroom.md +99 -0
- package/skills/hallmark/references/themes/dose.jpg +0 -0
- package/skills/hallmark/references/themes/dose.md +98 -0
- package/skills/hallmark/references/themes/dusk.jpg +0 -0
- package/skills/hallmark/references/themes/dusk.md +105 -0
- package/skills/hallmark/references/themes/editorial.jpg +0 -0
- package/skills/hallmark/references/themes/editorial.md +97 -0
- package/skills/hallmark/references/themes/formwork.jpg +0 -0
- package/skills/hallmark/references/themes/formwork.md +107 -0
- package/skills/hallmark/references/themes/furrow.jpg +0 -0
- package/skills/hallmark/references/themes/furrow.md +98 -0
- package/skills/hallmark/references/themes/garden.jpg +0 -0
- package/skills/hallmark/references/themes/garden.md +97 -0
- package/skills/hallmark/references/themes/grid.jpg +0 -0
- package/skills/hallmark/references/themes/grid.md +101 -0
- package/skills/hallmark/references/themes/halftone.jpg +0 -0
- package/skills/hallmark/references/themes/halftone.md +106 -0
- package/skills/hallmark/references/themes/hedgerow.jpg +0 -0
- package/skills/hallmark/references/themes/hedgerow.md +104 -0
- package/skills/hallmark/references/themes/hum.jpg +0 -0
- package/skills/hallmark/references/themes/hum.md +99 -0
- package/skills/hallmark/references/themes/index.json +2191 -0
- package/skills/hallmark/references/themes/ink.jpg +0 -0
- package/skills/hallmark/references/themes/ink.md +106 -0
- package/skills/hallmark/references/themes/kiln.jpg +0 -0
- package/skills/hallmark/references/themes/kiln.md +108 -0
- package/skills/hallmark/references/themes/loom.jpg +0 -0
- package/skills/hallmark/references/themes/loom.md +106 -0
- package/skills/hallmark/references/themes/lumen.jpg +0 -0
- package/skills/hallmark/references/themes/lumen.md +103 -0
- package/skills/hallmark/references/themes/manifesto.jpg +0 -0
- package/skills/hallmark/references/themes/manifesto.md +101 -0
- package/skills/hallmark/references/themes/midnight.jpg +0 -0
- package/skills/hallmark/references/themes/midnight.md +97 -0
- package/skills/hallmark/references/themes/newsprint.jpg +0 -0
- package/skills/hallmark/references/themes/newsprint.md +102 -0
- package/skills/hallmark/references/themes/overprint.jpg +0 -0
- package/skills/hallmark/references/themes/overprint.md +101 -0
- package/skills/hallmark/references/themes/pasteup.jpg +0 -0
- package/skills/hallmark/references/themes/pasteup.md +100 -0
- package/skills/hallmark/references/themes/piste.jpg +0 -0
- package/skills/hallmark/references/themes/piste.md +99 -0
- package/skills/hallmark/references/themes/ribbon.jpg +0 -0
- package/skills/hallmark/references/themes/ribbon.md +106 -0
- package/skills/hallmark/references/themes/rim.jpg +0 -0
- package/skills/hallmark/references/themes/rim.md +96 -0
- package/skills/hallmark/references/themes/riso.jpg +0 -0
- package/skills/hallmark/references/themes/riso.md +98 -0
- package/skills/hallmark/references/themes/solstice.jpg +0 -0
- package/skills/hallmark/references/themes/solstice.md +104 -0
- package/skills/hallmark/references/themes/specimen.jpg +0 -0
- package/skills/hallmark/references/themes/specimen.md +100 -0
- package/skills/hallmark/references/themes/spore.jpg +0 -0
- package/skills/hallmark/references/themes/spore.md +104 -0
- package/skills/hallmark/references/themes/sport.jpg +0 -0
- package/skills/hallmark/references/themes/sport.md +101 -0
- package/skills/hallmark/references/themes/squeeze.jpg +0 -0
- package/skills/hallmark/references/themes/squeeze.md +99 -0
- package/skills/hallmark/references/themes/sticker.jpg +0 -0
- package/skills/hallmark/references/themes/sticker.md +106 -0
- package/skills/hallmark/references/themes/studio.jpg +0 -0
- package/skills/hallmark/references/themes/studio.md +99 -0
- package/skills/hallmark/references/themes/terminal.jpg +0 -0
- package/skills/hallmark/references/themes/terminal.md +96 -0
- package/skills/hallmark/references/themes/terrazzo.jpg +0 -0
- package/skills/hallmark/references/themes/terrazzo.md +106 -0
- package/skills/hallmark/references/themes/ultraviolet.jpg +0 -0
- package/skills/hallmark/references/themes/ultraviolet.md +106 -0
- package/skills/hallmark/references/themes/vellum.jpg +0 -0
- package/skills/hallmark/references/themes/vellum.md +105 -0
- package/skills/hallmark/references/themes/wayfind.jpg +0 -0
- package/skills/hallmark/references/themes/wayfind.md +105 -0
- package/skills/hallmark/references/themes/werkstatt.jpg +0 -0
- package/skills/hallmark/references/themes/werkstatt.md +105 -0
- package/skills/hallmark/scripts/capture.mjs +125 -0
- package/skills/hallmark/scripts/fixtures/exemplars/atmospheric.html +324 -0
- package/skills/hallmark/scripts/fixtures/exemplars/editorial.html +363 -0
- package/skills/hallmark/scripts/fixtures/exemplars/modern-minimal.html +527 -0
- package/skills/hallmark/scripts/fixtures/exemplars/playful.html +353 -0
- package/skills/hallmark/scripts/fixtures/eyebrow-fail/index.html +118 -0
- package/skills/hallmark/scripts/fixtures/eyebrow-pass/index.html +137 -0
- package/skills/hallmark/scripts/fixtures/kept/after.html +27 -0
- package/skills/hallmark/scripts/fixtures/kept/before.html +26 -0
- package/skills/hallmark/scripts/fixtures/slopped.html +145 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/Feature.vue +14 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/Hero.tsx +33 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/index.html +74 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/style.css +6 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/Hero.tsx +33 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/index.html +57 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/style.css +15 -0
- package/skills/hallmark/scripts/fixtures/waiver/index.html +54 -0
- package/skills/hallmark/scripts/imagegen.mjs +85 -0
- package/skills/hallmark/scripts/install-hook.mjs +70 -0
- package/skills/hallmark/scripts/install.mjs +141 -0
- package/skills/hallmark/scripts/lint-hook.mjs +49 -0
- package/skills/hallmark/scripts/photos.json +1 -0
- package/skills/hallmark/scripts/picture.mjs +71 -0
- package/skills/hallmark/scripts/refcheck.mjs +352 -0
- package/skills/hallmark/scripts/sloplint-render.mjs +1252 -0
- package/skills/hallmark/scripts/sloplint.mjs +4422 -0
- package/skills/hallmark/scripts/theme.mjs +650 -0
- package/skills/harness/SKILL.md +15 -8
- package/skills/i-have-adhd/SKILL.md +22 -11
- package/skills/no-ai-slop/SKILL.md +15 -1
- package/skills/plan-add/SKILL.md +33 -26
- package/skills/plan-status/SKILL.md +13 -7
- package/skills/plan-sync/SKILL.md +18 -12
- package/skills/pm-frameworks/SKILL.md +19 -10
- package/skills/pm-frameworks/SOURCE +7 -5
- package/skills/ponytail/SKILL.md +19 -14
- package/skills/product-analysis/SKILL.md +22 -18
- package/skills/product-flow/SKILL.md +14 -9
- package/skills/research/SKILL.md +75 -39
- package/skills/rl-env/SKILL.md +25 -13
- package/skills/rl-env/SOURCE +8 -6
- package/skills/tezgah-contract/SKILL.md +47 -42
- package/bin/tezgah-skill.py +0 -59
- /package/skills/ai-research/0-autoresearch-skill/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/litgpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/mamba/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/nanogpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/rwkv/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/torchtitan/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/02-tokenization/huggingface-tokenizers/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/02-tokenization/sentencepiece/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/axolotl/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/llama-factory/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/peft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/unsloth/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/nnsight/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/pyvene/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/saelens/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/05-data-processing/nemo-curator/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/05-data-processing/ray-data/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/grpo-rl-training/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/miles/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/openrlhf/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/simpo/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/slime/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/torchforge/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/trl-fine-tuning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/verl/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/constitutional-ai/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/llamaguard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/nemo-guardrails/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/prompt-guard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/accelerate/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/deepspeed/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/megatron-core/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/pytorch-fsdp2/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/pytorch-lightning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/ray-train/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/lambda-labs/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/modal/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/skypilot/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/awq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/bitsandbytes/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/flash-attention/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/gguf/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/gptq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/hqq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/ml-training-recipes/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/lm-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/nemo-evaluator/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/llama-cpp/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/sglang/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/tensorrt-llm/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/vllm/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/mlflow/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/swanlab/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/tensorboard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/weights-and-biases/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/a-evolve/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/autogpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/crewai/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/langchain/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/llamaindex/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/chroma/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/faiss/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/pinecone/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/qdrant/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/sentence-transformers/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/dspy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/guidance/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/instructor/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/outlines/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/17-observability/langsmith/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/17-observability/phoenix/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/audiocraft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/blip-2/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/clip/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/cosmos-policy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/llava/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/openpi/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/openvla-oft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/segment-anything/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/stable-diffusion/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/whisper/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/knowledge-distillation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/long-context/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/model-merging/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/model-pruning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/moe-training/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/speculative-decoding/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/academic-plotting/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/presenting-conference-talks/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/21-research-ideation/brainstorming-research-ideas/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/21-research-ideation/creative-thinking-for-research/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/compiler/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/research-manager/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/rigor-reviewer/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/colour-independence/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/flexible-typography/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/information-density/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/preference-audit/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/responsive-accessibility/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/responsive-review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/simplified-views/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/specify/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/user-preference-respect/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/adaptive-personalisation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/assess-load/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/cognitive-load-assessment/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/contextual-help-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/error-prevention-recovery/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/focus-attention-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/memory-load-reduction/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/plain-language-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/simplify/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/wayfinding-navigation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/design-research/skills/behavioural-analytics/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/design-research/skills/qual-quant-triangulation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/audit/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/design-flow/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/feedback-and-status/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/gesture-alternatives/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/keyboard-navigation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/keyboard-review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/motion-sensitivity/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/multi-modal-input/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/touch-target-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/voice-interaction/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-affordance/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-brand-consistency/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-color/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-composition/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-information-density/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-typography/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-visual-hierarchy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/pm-frameworks/intended-vs-implemented/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/pm-frameworks/opportunity-solution-tree/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-nemo-gym-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-openenv-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-ors-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-verifiers-env/{SKILL.md → ENTRY.md} +0 -0
- /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}
|