@r1z4x/tezgah 1.9.2 → 1.13.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 +415 -1
- package/MANIFEST +404 -144
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.md +5 -4
- 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 +255 -44
- package/bin/tezgah-review-quote +185 -0
- package/bin/tezgah-setup +1365 -55
- package/bin/tezgah-skill +1 -1
- package/bin/tezgah-status +201 -3
- package/bin/tezgah-task +258 -20
- package/commands/adhd.md +2 -2
- package/commands/ponytail.md +4 -4
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +3 -3
- package/docs/clarity-baseline.json +11 -5
- package/docs/contract.md +91 -43
- package/docs/environment.md +5 -2
- package/docs/evidence.md +109 -17
- package/docs/feature-audit.md +2 -2
- package/docs/gate.md +60 -33
- package/docs/glossary.md +6 -3
- package/docs/hosts.md +313 -17
- package/docs/index.json +34 -8
- package/docs/judge.md +4 -2
- package/docs/layers.md +4 -4
- package/docs/models.md +37 -9
- package/docs/operations.md +141 -5
- package/docs/research.md +815 -8
- package/docs/skills.md +137 -13
- package/docs/status-line.md +121 -1
- package/docs/testing.md +3 -1
- package/hooks/hooks.json +2 -2
- package/hooks/lang/en.json +21 -0
- package/hooks/lang/tr.json +22 -1
- package/hooks/projects-stop.py +2 -1
- package/hooks/tezgah_agents.py +65 -25
- package/hooks/tezgah_apps.py +18 -0
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_components.py +4 -0
- package/hooks/tezgah_context.py +207 -55
- package/hooks/tezgah_extension.py +337 -0
- package/hooks/tezgah_gate.py +267 -79
- package/hooks/tezgah_integrity.py +864 -143
- 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 +145 -8
- package/hooks/tezgah_policy.py +128 -57
- package/hooks/tezgah_rank.py +9 -6
- package/hooks/tezgah_replay.py +12 -6
- package/hooks/tezgah_research.py +1414 -76
- 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_stack.py +236 -1
- package/hooks/tezgah_stopspec.py +8 -1
- package/hooks/tezgah_task.py +14 -5
- package/hooks/tezgah_taste_ledger.py +1 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +386 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +2 -1
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +31 -3
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +4 -1
- package/hosts/omp/tezgah-hook.ts.in +30 -7
- package/hosts/opencode/plugins/tezgah.js +309 -48
- package/hosts/opencode/tui/cli/tui.tsx +100 -0
- package/hosts/opencode/tui/tezgah-tui.tsx +2 -1
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +20 -15
- package/package.json +1 -1
- 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 +17 -8
- package/skills/i-have-adhd/SKILL.md +32 -12
- package/skills/no-ai-slop/SKILL.md +15 -1
- package/skills/plan-add/SKILL.md +38 -27
- 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 +97 -48
- package/skills/rl-env/SKILL.md +25 -13
- package/skills/rl-env/SOURCE +8 -6
- package/skills/tezgah-contract/SKILL.md +65 -47
- package/statusline.py +20 -9
- package/workflows/graph-review.js +14 -2
- 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,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Command Code lifecycle hook. Reads Command Code's JSON on stdin and answers
|
|
3
|
+
in its own output shape, onto the shared tezgah core (hooks/).
|
|
4
|
+
|
|
5
|
+
Command Code fires four hook events (commandcode.ai/docs/hooks): SessionStart,
|
|
6
|
+
PreToolUse, PostToolUse and Stop (`bin/tezgah-setup::COMMANDCODE_HOOK_EVENTS`);
|
|
7
|
+
anything else is ignored. Inert outside the roots. Its settings hooks see only
|
|
8
|
+
four tools - shell, read, write, edit - so MCP, subagent and web calls never
|
|
9
|
+
reach this file, and plan mode skips every hook.
|
|
10
|
+
|
|
11
|
+
The always-on core is the managed block in `~/.commandcode/AGENTS.md`, the user
|
|
12
|
+
memory file Command Code puts in the system prompt, so SessionStart repeats it
|
|
13
|
+
only when that block is missing. There is no prompt event: no per-turn reminder
|
|
14
|
+
and no turn rows. PreToolUse translates the tool ids and input fields into the
|
|
15
|
+
gate's vocabulary (`gate_name`, `gate_input`) and denies with
|
|
16
|
+
`hookSpecificOutput.permissionDecision` on exit 0. PostToolUse's `tool_response`
|
|
17
|
+
is the text the model sees; a shell call's outcome is read off an `exit code N`
|
|
18
|
+
in it (`call_failed`).
|
|
19
|
+
|
|
20
|
+
Stop carries no reply text (`stop_hook_active` only), so it reads the last
|
|
21
|
+
assistant text from `transcript_path` (`last_reply`). When that read yields
|
|
22
|
+
nothing the turn end is a `reply_unread` row, never judged. The status segment
|
|
23
|
+
rides `systemMessage`, which Command Code shows in the TUI and never to the
|
|
24
|
+
model, at SessionStart and Stop (`hosts/codex/hook.py` does the same).
|
|
25
|
+
"""
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
import shlex
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
|
33
|
+
sys.path.insert(0, os.path.join(ROOT, "hooks"))
|
|
34
|
+
from tezgah_guard import attest_session, import_failed, safe # noqa: E402
|
|
35
|
+
try:
|
|
36
|
+
from tezgah_context import ( # noqa: E402
|
|
37
|
+
TOOL_USE_MEASURES, _tail_lines, command_text, context_for, health_lines,
|
|
38
|
+
record, shell_kind)
|
|
39
|
+
from tezgah_gate import cross_project_note, decision, graph_advice # noqa: E402
|
|
40
|
+
from tezgah_integrity import ( # noqa: E402
|
|
41
|
+
bind_session, note, note_tool, ran_nothing, stop_reason)
|
|
42
|
+
from tezgah_paths import HOST_DIRS, off, root_for # noqa: E402
|
|
43
|
+
from tezgah_taste import write_note # noqa: E402
|
|
44
|
+
from tezgah_untrusted import marks # noqa: E402
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
import_failed(exc)
|
|
47
|
+
|
|
48
|
+
# Command Code's canonical tool ids (hooks "Tool-call fields") -> the gate's
|
|
49
|
+
# vocabulary. `read_file` maps for the ledger only: the PreToolUse matcher never
|
|
50
|
+
# sends it here.
|
|
51
|
+
GATE_TOOLS = {
|
|
52
|
+
"shell_command": "Bash",
|
|
53
|
+
"write_file": "Write",
|
|
54
|
+
"edit_file": "Edit",
|
|
55
|
+
"read_file": "Read",
|
|
56
|
+
}
|
|
57
|
+
# The result text names no documented exit-code format; the shell tool reports
|
|
58
|
+
# the code to the model, so the last `exit code N` in the text is taken.
|
|
59
|
+
EXIT_CODE = re.compile(r"(?i)exit code[: ]+(-?\d+)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def gate_name(name):
|
|
63
|
+
"""The tool name the gate denies under and the ledger row records; both
|
|
64
|
+
halves map through here so a call hashes to one id."""
|
|
65
|
+
return GATE_TOOLS.get(name, name)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def gate_input(name, inp):
|
|
69
|
+
"""The tool input in the gate's fields. A shell call may split its command
|
|
70
|
+
into `command` + `args` (Command Code shell-quotes the args itself), and an
|
|
71
|
+
edit names its strings `old_value`/`new_value` in the hooks reference but
|
|
72
|
+
`old_string`/`new_string` in the tools reference, so either is read. `args`
|
|
73
|
+
is dropped once folded in: the gate reads an `args` key as a Task's object."""
|
|
74
|
+
inp = dict(inp) if isinstance(inp, dict) else {}
|
|
75
|
+
if name == "shell_command":
|
|
76
|
+
args = inp.pop("args", None)
|
|
77
|
+
if isinstance(args, list) and args and all(isinstance(a, str) for a in args):
|
|
78
|
+
inp["command"] = ("%s %s" % (inp.get("command") or "", shlex.join(args))).strip()
|
|
79
|
+
elif name == "edit_file":
|
|
80
|
+
for src, dst in (("old_value", "old_string"), ("new_value", "new_string")):
|
|
81
|
+
if src in inp and dst not in inp:
|
|
82
|
+
inp[dst] = inp[src]
|
|
83
|
+
return inp
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def classify(payload):
|
|
87
|
+
"""The used-tool kind for a tool payload, or None."""
|
|
88
|
+
if payload.get("tool_name") == "shell_command":
|
|
89
|
+
return shell_kind(command_text(gate_input("shell_command", payload.get("tool_input"))))
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def call_failed(payload):
|
|
94
|
+
"""`note_tool`'s `failed` for a shell call: the last `exit code N` in the
|
|
95
|
+
result, and None when the result names no code - a call that RAN, never a
|
|
96
|
+
pass."""
|
|
97
|
+
if payload.get("tool_name") != "shell_command":
|
|
98
|
+
return None
|
|
99
|
+
resp = payload.get("tool_response")
|
|
100
|
+
codes = EXIT_CODE.findall(resp) if isinstance(resp, str) else []
|
|
101
|
+
return int(codes[-1]) != 0 if codes else None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _text(content):
|
|
105
|
+
"""The text blocks of a message's content, a string or a block list."""
|
|
106
|
+
if isinstance(content, str):
|
|
107
|
+
return [content]
|
|
108
|
+
if not isinstance(content, list):
|
|
109
|
+
return []
|
|
110
|
+
return [b.get("text") for b in content if isinstance(b, dict)
|
|
111
|
+
and b.get("type") == "text" and isinstance(b.get("text"), str)]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def last_reply(path):
|
|
115
|
+
"""The text of the assistant reply that ends the transcript, or "".
|
|
116
|
+
|
|
117
|
+
Command Code documents its transcript only as JSONL. Two row shapes are
|
|
118
|
+
read: Claude's `{"type": "assistant", "message": {"content": [...]}}` and a
|
|
119
|
+
bare `{"role": "assistant", "content": ...}`. The reply is the text of the
|
|
120
|
+
trailing assistant rows; a transcript that ends on a user or tool row has
|
|
121
|
+
no reply written yet and answers ""."""
|
|
122
|
+
parts = []
|
|
123
|
+
for raw in reversed(_tail_lines(path)):
|
|
124
|
+
try:
|
|
125
|
+
row = json.loads(raw)
|
|
126
|
+
except ValueError:
|
|
127
|
+
continue
|
|
128
|
+
if not isinstance(row, dict):
|
|
129
|
+
continue
|
|
130
|
+
msg = row.get("message") if isinstance(row.get("message"), dict) else row
|
|
131
|
+
role = row.get("type") if row.get("type") in ("user", "assistant") else msg.get("role")
|
|
132
|
+
if role not in ("user", "assistant", "tool"):
|
|
133
|
+
continue
|
|
134
|
+
if role != "assistant":
|
|
135
|
+
break
|
|
136
|
+
parts[:0] = _text(msg.get("content"))
|
|
137
|
+
return "\n".join(t for t in parts if t.strip())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def rules_carry_core():
|
|
141
|
+
"""True when `AGENTS.md` in Command Code's home holds the managed block."""
|
|
142
|
+
try:
|
|
143
|
+
with open(os.path.join(HOST_DIRS["commandcode"], "AGENTS.md"),
|
|
144
|
+
encoding="utf-8") as fh:
|
|
145
|
+
return "<!-- tezgah:start" in fh.read()
|
|
146
|
+
except OSError:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def post_tool(payload, cwd, session_id):
|
|
151
|
+
"""The ledger row for one finished call, and the notice for the model."""
|
|
152
|
+
name = payload.get("tool_name", "") or ""
|
|
153
|
+
tool = gate_name(name)
|
|
154
|
+
inp = gate_input(name, payload.get("tool_input"))
|
|
155
|
+
safe(session_id, record, session_id, classify(payload))
|
|
156
|
+
if not root_for(cwd):
|
|
157
|
+
return None
|
|
158
|
+
result = payload.get("tool_response")
|
|
159
|
+
failed = call_failed(payload)
|
|
160
|
+
# read before this call's row lands: `source` is the taint's own mark
|
|
161
|
+
got = safe(session_id, marks, tool, inp, session_id, result)
|
|
162
|
+
source, notice = got or (None, None)
|
|
163
|
+
safe(session_id, note_tool, session_id, tool, inp, failed=failed,
|
|
164
|
+
source=source, cwd=cwd,
|
|
165
|
+
out_bytes=len(result) if isinstance(result, str) else None,
|
|
166
|
+
empty_run=ran_nothing(result))
|
|
167
|
+
advice = safe(session_id, graph_advice, tool, inp, cwd, "commandcode")
|
|
168
|
+
taste = cross = None
|
|
169
|
+
if not failed:
|
|
170
|
+
taste = safe(session_id, write_note, session_id, inp, cwd, tool)
|
|
171
|
+
cross = safe(session_id, cross_project_note, session_id, tool, inp, cwd)
|
|
172
|
+
return "\n".join(p for p in (notice, advice, taste, cross) if p)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def context(event, text):
|
|
176
|
+
return {"hookSpecificOutput": {"hookEventName": event,
|
|
177
|
+
"additionalContext": text}} if text else {}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def status(out, cwd, session_id):
|
|
181
|
+
"""Add the status segment as `systemMessage` (TUI only)."""
|
|
182
|
+
seg = safe(session_id, health_lines, cwd, session_id, observable=TOOL_USE_MEASURES)
|
|
183
|
+
if seg:
|
|
184
|
+
out["systemMessage"] = "tezgah " + seg
|
|
185
|
+
return out
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
try:
|
|
190
|
+
payload = json.load(sys.stdin)
|
|
191
|
+
except Exception:
|
|
192
|
+
return
|
|
193
|
+
if not isinstance(payload, dict):
|
|
194
|
+
return
|
|
195
|
+
event = payload.get("hook_event_name") or ""
|
|
196
|
+
cwd = payload.get("cwd") or os.getcwd()
|
|
197
|
+
session_id = payload.get("session_id")
|
|
198
|
+
safe(session_id, bind_session, session_id) # the switch latch (tezgah_paths.off)
|
|
199
|
+
out = {}
|
|
200
|
+
if event == "PreToolUse":
|
|
201
|
+
name = payload.get("tool_name", "") or ""
|
|
202
|
+
reason = safe(session_id, decision, gate_name(name),
|
|
203
|
+
gate_input(name, payload.get("tool_input")), cwd, session_id)
|
|
204
|
+
if reason:
|
|
205
|
+
out = {"hookSpecificOutput": {
|
|
206
|
+
"hookEventName": "PreToolUse",
|
|
207
|
+
"permissionDecision": "deny",
|
|
208
|
+
"permissionDecisionReason": reason,
|
|
209
|
+
}}
|
|
210
|
+
elif event == "PostToolUse":
|
|
211
|
+
out = context(event, post_tool(payload, cwd, session_id))
|
|
212
|
+
elif event == "SessionStart":
|
|
213
|
+
safe(session_id, attest_session, "commandcode", session_id, cwd)
|
|
214
|
+
out = status(context(event, safe(session_id, context_for, "session_start", cwd,
|
|
215
|
+
payload, with_core=not rules_carry_core())),
|
|
216
|
+
cwd, session_id)
|
|
217
|
+
elif event == "Stop":
|
|
218
|
+
# {"decision": "block", "reason": ...} retries the turn, at most 3 times
|
|
219
|
+
# (the host's cap); the reply after a block is judged again and refused
|
|
220
|
+
# at most once more (tezgah_integrity).
|
|
221
|
+
if not off("verify-off") and root_for(cwd):
|
|
222
|
+
path = payload.get("transcript_path")
|
|
223
|
+
text = safe(session_id, last_reply, path)
|
|
224
|
+
if text:
|
|
225
|
+
reason = safe(session_id, stop_reason, text, session_id, cwd=cwd,
|
|
226
|
+
after_block=bool(payload.get("stop_hook_active")),
|
|
227
|
+
mode=payload.get("permission_mode"))
|
|
228
|
+
if reason:
|
|
229
|
+
out = {"decision": "block", "reason": reason}
|
|
230
|
+
else:
|
|
231
|
+
safe(session_id, note, session_id, "reply_unread",
|
|
232
|
+
"no assistant text at the end of %s" % path if path
|
|
233
|
+
else "no transcript_path")
|
|
234
|
+
out = status(out, cwd, session_id)
|
|
235
|
+
if out:
|
|
236
|
+
print(json.dumps(out))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
main()
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""GitHub Copilot CLI lifecycle hook. Reads Copilot's JSON on stdin and answers
|
|
3
|
+
in its own output shape, onto the shared tezgah core (hooks/).
|
|
4
|
+
|
|
5
|
+
tezgah's entries live in their own file, `$COPILOT_HOME/hooks/tezgah.json`
|
|
6
|
+
(`bin/tezgah-setup::COPILOT_HOOK_EVENTS`). They use the PascalCase event names,
|
|
7
|
+
so the payloads carry snake_case fields and Claude tool names
|
|
8
|
+
(docs.github.com hooks-reference "Hook event input payloads"): SessionStart,
|
|
9
|
+
UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop and
|
|
10
|
+
SubagentStop. `subagentStart` has no PascalCase form; its camelCase payload
|
|
11
|
+
names no event, so its command passes the event as the first argument.
|
|
12
|
+
Anything else is ignored. Inert outside the roots.
|
|
13
|
+
|
|
14
|
+
The always-on core is the managed block in `$COPILOT_HOME/copilot-instructions.md`,
|
|
15
|
+
so SessionStart repeats it only when that block is missing. A command hook's
|
|
16
|
+
UserPromptSubmit output is dropped by Copilot, so there is no per-turn
|
|
17
|
+
reminder; the event still writes the turn row. PreToolUse translates the tool
|
|
18
|
+
names into the gate's vocabulary and denies with a top-level
|
|
19
|
+
`permissionDecision: deny` on exit 0. Copilot denies the call itself when this
|
|
20
|
+
hook crashes, and lets it through when the hook times out.
|
|
21
|
+
|
|
22
|
+
Copilot fires PostToolUse for a successful call and PostToolUseFailure for a
|
|
23
|
+
failed one, so the event is the outcome; a shell result's `exit code N` text
|
|
24
|
+
overrides it (`call_failed`). Stop carries no reply text, so it reads the last
|
|
25
|
+
`assistant.message` from `transcript_path` (`last_reply`); none there is a
|
|
26
|
+
`reply_unread` row, never judged. SubagentStop is judged record-only. The status
|
|
27
|
+
line is `tezgah-statusline --copilot`, Copilot's `statusLine` command.
|
|
28
|
+
"""
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
|
35
|
+
sys.path.insert(0, os.path.join(ROOT, "hooks"))
|
|
36
|
+
from tezgah_guard import attest_session, import_failed, safe # noqa: E402
|
|
37
|
+
try:
|
|
38
|
+
from tezgah_context import ( # noqa: E402
|
|
39
|
+
_tail_lines, command_text, context_for, record, shell_kind)
|
|
40
|
+
from tezgah_gate import cross_project_note, decision, graph_advice # noqa: E402
|
|
41
|
+
from tezgah_integrity import ( # noqa: E402
|
|
42
|
+
SUBAGENT_CHANNEL, bind_session, note, note_tool, ran_nothing, report_bytes,
|
|
43
|
+
stop_reason, untrusted_source)
|
|
44
|
+
from tezgah_paths import HOST_DIRS, off, root_for # noqa: E402
|
|
45
|
+
from tezgah_taste import write_note # noqa: E402
|
|
46
|
+
from tezgah_untrusted import marks # noqa: E402
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
import_failed(exc)
|
|
49
|
+
|
|
50
|
+
# Copilot's tool names -> the gate's vocabulary. A PascalCase PreToolUse
|
|
51
|
+
# reports the Claude name (hooks-reference "Claude-format matchers"); the
|
|
52
|
+
# runtime spellings map to the same names, since no doc says the other events
|
|
53
|
+
# translate theirs.
|
|
54
|
+
GATE_TOOLS = {
|
|
55
|
+
"bash": "Bash", "powershell": "Bash",
|
|
56
|
+
"create": "Write",
|
|
57
|
+
"edit": "Edit", "str_replace_editor": "Edit", "apply_patch": "Edit",
|
|
58
|
+
"Agent": "Task", "task": "Task",
|
|
59
|
+
"grep": "Grep", "rg": "Grep",
|
|
60
|
+
"view": "Read",
|
|
61
|
+
}
|
|
62
|
+
# The result text names no documented exit-code format; the last `exit code N`
|
|
63
|
+
# in it is taken.
|
|
64
|
+
EXIT_CODE = re.compile(r"(?i)exit code[: ]+(-?\d+)")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def mcp_servers():
|
|
68
|
+
"""The server names in `$COPILOT_HOME/mcp-config.json`, sanitized the way
|
|
69
|
+
Copilot builds a tool name (cli-command-reference "MCP tool name
|
|
70
|
+
sanitization"), longest first."""
|
|
71
|
+
try:
|
|
72
|
+
with open(os.path.join(HOST_DIRS["copilot"], "mcp-config.json"),
|
|
73
|
+
encoding="utf-8") as fh:
|
|
74
|
+
servers = json.load(fh).get("mcpServers")
|
|
75
|
+
except (OSError, ValueError, AttributeError):
|
|
76
|
+
return []
|
|
77
|
+
names = {re.sub(r"[^A-Za-z0-9_-]", "-", s) for s in servers or {}
|
|
78
|
+
if isinstance(s, str)}
|
|
79
|
+
return sorted(names, key=len, reverse=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def is_patch(name, inp):
|
|
83
|
+
"""An Edit that names no file but carries a patch string: `apply_patch`."""
|
|
84
|
+
if GATE_TOOLS.get(name, name) != "Edit":
|
|
85
|
+
return False
|
|
86
|
+
if isinstance(inp, str):
|
|
87
|
+
return True
|
|
88
|
+
return (isinstance(inp, dict) and not (inp.get("path") or inp.get("file_path"))
|
|
89
|
+
and any(isinstance(inp.get(k), str) for k in ("patch", "input")))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def gate_name(name, inp=None):
|
|
93
|
+
"""The tool name the gate denies under and the ledger row records; both
|
|
94
|
+
halves map through here so a call hashes to one id. An MCP tool arrives as
|
|
95
|
+
`<server>-<tool>` and leaves in the shared `mcp__<server>__<tool>` form when
|
|
96
|
+
the server is one `mcp-config.json` names."""
|
|
97
|
+
if is_patch(name, inp):
|
|
98
|
+
return "apply_patch"
|
|
99
|
+
if name in GATE_TOOLS:
|
|
100
|
+
return GATE_TOOLS[name]
|
|
101
|
+
for server in mcp_servers() if "-" in name else ():
|
|
102
|
+
if name.startswith(server + "-") and len(name) > len(server) + 1:
|
|
103
|
+
return "mcp__%s__%s" % (server, name[len(server) + 1:])
|
|
104
|
+
return name
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def gate_input(name, inp):
|
|
108
|
+
"""The tool input in the gate's fields. The gate already reads Copilot's
|
|
109
|
+
`path`, `file_text`, `old_str` and `new_str`; a patch is handed over as
|
|
110
|
+
`patch`."""
|
|
111
|
+
if is_patch(name, inp):
|
|
112
|
+
if isinstance(inp, str):
|
|
113
|
+
return {"patch": inp}
|
|
114
|
+
return dict(inp, patch=inp["patch"] if isinstance(inp.get("patch"), str)
|
|
115
|
+
else inp["input"])
|
|
116
|
+
return inp if isinstance(inp, dict) else {}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def classify(tool, inp):
|
|
120
|
+
"""The used-tool kind for a mapped call, or None."""
|
|
121
|
+
if tool.startswith("mcp__") and "codegraph" in tool:
|
|
122
|
+
return "graph"
|
|
123
|
+
if tool == "Task":
|
|
124
|
+
return "orch"
|
|
125
|
+
if tool == "Bash":
|
|
126
|
+
return shell_kind(command_text(inp))
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def subagent_id(payload):
|
|
131
|
+
"""The subagent id Copilot adds to a hook input fired inside a subagent."""
|
|
132
|
+
aid = payload.get("agent_id")
|
|
133
|
+
return aid if isinstance(aid, str) and aid else None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def result_text(payload):
|
|
137
|
+
"""The text the model saw: PostToolUse's `tool_result.text_result_for_llm`,
|
|
138
|
+
PostToolUseFailure's `error`."""
|
|
139
|
+
res = payload.get("tool_result")
|
|
140
|
+
if isinstance(res, dict) and isinstance(res.get("text_result_for_llm"), str):
|
|
141
|
+
return res["text_result_for_llm"]
|
|
142
|
+
err = payload.get("error")
|
|
143
|
+
return err if isinstance(err, str) else None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def call_failed(tool, failed, text):
|
|
147
|
+
"""`note_tool`'s `failed`: the event's outcome, unless a shell result names
|
|
148
|
+
an exit code. A shell success that names none is None (ran, never a pass):
|
|
149
|
+
a background launch returns before its command ends, with no code."""
|
|
150
|
+
if tool != "Bash":
|
|
151
|
+
return failed
|
|
152
|
+
codes = EXIT_CODE.findall(text) if isinstance(text, str) else []
|
|
153
|
+
if codes:
|
|
154
|
+
return int(codes[-1]) != 0
|
|
155
|
+
return failed or None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def last_reply(path):
|
|
159
|
+
"""The reply that ends the transcript, or "".
|
|
160
|
+
|
|
161
|
+
Copilot keeps a session's event log as JSONL (`session-state/<id>/events.jsonl`,
|
|
162
|
+
cli-config-dir-reference); an assistant reply is an `assistant.message` event
|
|
163
|
+
whose `data.content` is the text. A transcript whose last message is the
|
|
164
|
+
user's has no reply written yet."""
|
|
165
|
+
for raw in reversed(_tail_lines(path)):
|
|
166
|
+
try:
|
|
167
|
+
row = json.loads(raw)
|
|
168
|
+
except ValueError:
|
|
169
|
+
continue
|
|
170
|
+
kind = row.get("type") if isinstance(row, dict) else None
|
|
171
|
+
if kind == "user.message":
|
|
172
|
+
break
|
|
173
|
+
data = row.get("data") if kind == "assistant.message" else None
|
|
174
|
+
text = data.get("content") if isinstance(data, dict) else None
|
|
175
|
+
if isinstance(text, str) and text.strip():
|
|
176
|
+
return text
|
|
177
|
+
return ""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def rules_carry_core():
|
|
181
|
+
"""True when Copilot's personal instructions file holds the managed block."""
|
|
182
|
+
try:
|
|
183
|
+
with open(os.path.join(HOST_DIRS["copilot"], "copilot-instructions.md"),
|
|
184
|
+
encoding="utf-8") as fh:
|
|
185
|
+
return "<!-- tezgah:start" in fh.read()
|
|
186
|
+
except OSError:
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def post_tool(payload, cwd, session_id, failed):
|
|
191
|
+
"""The ledger row for one finished call, and the notice for the model."""
|
|
192
|
+
name = payload.get("tool_name", "") or ""
|
|
193
|
+
raw = payload.get("tool_input")
|
|
194
|
+
tool, inp = gate_name(name, raw), gate_input(name, raw)
|
|
195
|
+
safe(session_id, record, session_id, classify(tool, inp))
|
|
196
|
+
if not root_for(cwd):
|
|
197
|
+
return None
|
|
198
|
+
result = result_text(payload)
|
|
199
|
+
failed = call_failed(tool, failed, result)
|
|
200
|
+
# read before this call's row lands: `source` is the taint's own mark
|
|
201
|
+
got = safe(session_id, marks, tool, inp, session_id, result)
|
|
202
|
+
source, notice = got or (None, None)
|
|
203
|
+
safe(session_id, note_tool, session_id, tool, inp, failed=failed,
|
|
204
|
+
source=source, cwd=cwd,
|
|
205
|
+
out_bytes=(report_bytes(result)
|
|
206
|
+
if untrusted_source(tool, inp) == SUBAGENT_CHANNEL
|
|
207
|
+
else len(result) if isinstance(result, str) else None),
|
|
208
|
+
empty_run=ran_nothing(result), agent=subagent_id(payload))
|
|
209
|
+
advice = safe(session_id, graph_advice, tool, inp, cwd, "copilot")
|
|
210
|
+
taste = cross = None
|
|
211
|
+
if not failed:
|
|
212
|
+
taste = safe(session_id, write_note, session_id, inp, cwd, tool)
|
|
213
|
+
cross = safe(session_id, cross_project_note, session_id, tool, inp, cwd)
|
|
214
|
+
return "\n".join(p for p in (notice, advice, taste, cross) if p)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def context(text):
|
|
218
|
+
return {"additionalContext": text} if text else {}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main():
|
|
222
|
+
try:
|
|
223
|
+
payload = json.load(sys.stdin)
|
|
224
|
+
except Exception:
|
|
225
|
+
return
|
|
226
|
+
if not isinstance(payload, dict):
|
|
227
|
+
return
|
|
228
|
+
event = payload.get("hook_event_name") or (sys.argv[1] if len(sys.argv) > 1 else "")
|
|
229
|
+
cwd = payload.get("cwd") or os.getcwd()
|
|
230
|
+
session_id = payload.get("session_id") or payload.get("sessionId")
|
|
231
|
+
safe(session_id, bind_session, session_id) # the switch latch (tezgah_paths.off)
|
|
232
|
+
out = {}
|
|
233
|
+
if event == "PreToolUse":
|
|
234
|
+
name = payload.get("tool_name", "") or ""
|
|
235
|
+
raw = payload.get("tool_input")
|
|
236
|
+
reason = safe(session_id, decision, gate_name(name, raw), gate_input(name, raw),
|
|
237
|
+
cwd, session_id, agent=subagent_id(payload))
|
|
238
|
+
if reason:
|
|
239
|
+
# top level, not wrapped (hooks-reference "preToolUse decision control")
|
|
240
|
+
out = {"permissionDecision": "deny", "permissionDecisionReason": reason}
|
|
241
|
+
elif event in ("PostToolUse", "PostToolUseFailure"):
|
|
242
|
+
out = context(post_tool(payload, cwd, session_id, event == "PostToolUseFailure"))
|
|
243
|
+
elif event == "SessionStart":
|
|
244
|
+
safe(session_id, attest_session, "copilot", session_id, cwd)
|
|
245
|
+
out = context(safe(session_id, context_for, "session_start", cwd, payload,
|
|
246
|
+
with_core=not rules_carry_core()))
|
|
247
|
+
elif event == "UserPromptSubmit":
|
|
248
|
+
# the turn row only: Copilot drops a command hook's output here
|
|
249
|
+
safe(session_id, context_for, "user_prompt", cwd, payload)
|
|
250
|
+
elif event == "subagentStart":
|
|
251
|
+
# additionalContext is prepended to the subagent's first message
|
|
252
|
+
safe(session_id, record, session_id, "orch")
|
|
253
|
+
out = context(safe(session_id, context_for, "subagent_start", cwd,
|
|
254
|
+
dict(payload, session_id=session_id)))
|
|
255
|
+
elif event == "SubagentStop":
|
|
256
|
+
# a subagent's end is judged record-only (ADR 011): one `subagent_end`
|
|
257
|
+
# row from its last message, never a block
|
|
258
|
+
safe(session_id, record, session_id, "orch")
|
|
259
|
+
if not off("verify-off") and root_for(cwd):
|
|
260
|
+
text = payload.get("last_assistant_message")
|
|
261
|
+
safe(session_id, stop_reason, text if isinstance(text, str) else "",
|
|
262
|
+
session_id, cwd=cwd, subagent=True)
|
|
263
|
+
elif event == "Stop":
|
|
264
|
+
# {"decision": "block", "reason": ...} forces another turn; Copilot ends
|
|
265
|
+
# the turn anyway after 8 blocks in a row. The reply after a block is
|
|
266
|
+
# judged again and refused at most once more (tezgah_integrity).
|
|
267
|
+
if not off("verify-off") and root_for(cwd):
|
|
268
|
+
path = payload.get("transcript_path")
|
|
269
|
+
text = safe(session_id, last_reply, path)
|
|
270
|
+
if text:
|
|
271
|
+
reason = safe(session_id, stop_reason, text, session_id, cwd=cwd,
|
|
272
|
+
after_block=bool(payload.get("stop_hook_active")),
|
|
273
|
+
mode=payload.get("permission_mode"))
|
|
274
|
+
if reason:
|
|
275
|
+
out = {"decision": "block", "reason": reason}
|
|
276
|
+
else:
|
|
277
|
+
safe(session_id, note, session_id, "reply_unread",
|
|
278
|
+
"no assistant.message at the end of %s" % path if path
|
|
279
|
+
else "no transcript_path")
|
|
280
|
+
if out:
|
|
281
|
+
print(json.dumps(out))
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
main()
|
package/hosts/cursor/hook.py
CHANGED
|
@@ -24,7 +24,10 @@ so this adapter translates them onto the shared tezgah core:
|
|
|
24
24
|
({"decision": "block", "reason": ...}, which Cursor
|
|
25
25
|
documents as an automatic follow-up - the native
|
|
26
26
|
spelling is {"followup_message": ...})
|
|
27
|
-
beforeSubmitPrompt -> {"continue": true}
|
|
27
|
+
beforeSubmitPrompt -> {"continue": true, "additional_context": <per-turn>}
|
|
28
|
+
(the docs list only continue/user_message, but
|
|
29
|
+
cursor-agent 2026.10.01-2026.10.19 forwards
|
|
30
|
+
additional_context for this event; see CONTEXT_MAX)
|
|
28
31
|
Everything else answers "{}" and never blocks.
|
|
29
32
|
|
|
30
33
|
Cursor names its own tools, so `hooks.json` has to name them too: the matcher on
|
|
@@ -105,6 +108,30 @@ REINFORCE = ("tezgah contract active: keep using the code graph "
|
|
|
105
108
|
RECOVERY = ("tezgah: tool call failed. Read the error, fix the cause, then "
|
|
106
109
|
"re-run; prefer codegraph_explore/codegraph_callers over guessing "
|
|
107
110
|
"at code.")
|
|
111
|
+
# cursor-agent (2026.10.01-2026.10.19, bundled `HookAdditionalContextTooLargeError`)
|
|
112
|
+
# trims the additional_context of beforeSubmitPrompt, preToolUse, postToolUse and
|
|
113
|
+
# postToolUseFailure and drops it WHOLE when its JavaScript length - UTF-16 code
|
|
114
|
+
# units, so an emoji counts two - is over 10000; a busy turn (every conditional
|
|
115
|
+
# armed) measured 10288. Cutting the tail keeps the reminder and the first rules
|
|
116
|
+
# instead of nothing. sessionStart is not capped: its text goes to the request
|
|
117
|
+
# context (`hooksAdditionalContextPromise`) without that check, and stored Cursor
|
|
118
|
+
# chats carry 15000-character session contexts whole, so the core is not cut.
|
|
119
|
+
# Known limit: with a custom mode on, cursor-agent appends "\n\n" and that mode's
|
|
120
|
+
# text to beforeSubmitPrompt's context before the length check; the hook cannot
|
|
121
|
+
# see it, so a turn cut to exactly the limit is then dropped whole.
|
|
122
|
+
CONTEXT_MAX = 10000
|
|
123
|
+
CONTEXT_CUT = "\n[... cut to Cursor's %d-character hook limit]" % CONTEXT_MAX
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def fit(text):
|
|
127
|
+
"""The text as Cursor will carry it: at most CONTEXT_MAX UTF-16 code units."""
|
|
128
|
+
text = text.strip()
|
|
129
|
+
data = text.encode("utf-16-le", "surrogatepass")
|
|
130
|
+
if len(data) <= 2 * CONTEXT_MAX:
|
|
131
|
+
return text
|
|
132
|
+
keep = 2 * (CONTEXT_MAX - len(CONTEXT_CUT)) # the marker is ASCII: one unit a char
|
|
133
|
+
# "ignore" drops the high half of a pair the cut split, never adding a unit
|
|
134
|
+
return data[:keep].decode("utf-16-le", "ignore") + CONTEXT_CUT
|
|
108
135
|
|
|
109
136
|
|
|
110
137
|
def cwd_of(payload):
|
|
@@ -442,7 +469,8 @@ def dispatch(payload):
|
|
|
442
469
|
if (payload.get("status") in (None, "completed")
|
|
443
470
|
and not off("verify-off") and under(cwd)):
|
|
444
471
|
reason = stop_reason(last_answer(session_id), session_id, cwd=cwd,
|
|
445
|
-
after_block=asked(payload)
|
|
472
|
+
after_block=asked(payload),
|
|
473
|
+
mode=payload.get("permission_mode"))
|
|
446
474
|
if reason:
|
|
447
475
|
out = {"decision": "block", "reason": reason}
|
|
448
476
|
elif event == "beforeSubmitPrompt":
|
|
@@ -451,7 +479,7 @@ def dispatch(payload):
|
|
|
451
479
|
text = context_for("user_prompt", cwd, payload)
|
|
452
480
|
out = {"continue": True}
|
|
453
481
|
if text:
|
|
454
|
-
out["additional_context"] = text
|
|
482
|
+
out["additional_context"] = fit(text)
|
|
455
483
|
else:
|
|
456
484
|
out = {}
|
|
457
485
|
return out
|