@r1z4x/tezgah 1.0.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 +738 -0
- package/LICENSE +21 -0
- package/MANIFEST +753 -0
- package/README.de.md +169 -0
- package/README.es.md +168 -0
- package/README.fr.md +166 -0
- package/README.md +161 -0
- package/README.tr.md +163 -0
- package/agents/tezgah-reviewer.md +70 -0
- package/bin/codegen +337 -0
- package/bin/consult +797 -0
- package/bin/tezgah-adhd +50 -0
- package/bin/tezgah-agents +45 -0
- package/bin/tezgah-capture +66 -0
- package/bin/tezgah-context +96 -0
- package/bin/tezgah-decisions +213 -0
- package/bin/tezgah-design +953 -0
- package/bin/tezgah-docs +1199 -0
- package/bin/tezgah-doctor +497 -0
- package/bin/tezgah-dsh +37 -0
- package/bin/tezgah-dsh.cmd +70 -0
- package/bin/tezgah-gate +75 -0
- package/bin/tezgah-import-ai-research +439 -0
- package/bin/tezgah-index +32 -0
- package/bin/tezgah-lessons +148 -0
- package/bin/tezgah-mcp +322 -0
- package/bin/tezgah-migrate +324 -0
- package/bin/tezgah-pony +50 -0
- package/bin/tezgah-research +772 -0
- package/bin/tezgah-rollback +93 -0
- package/bin/tezgah-route +124 -0
- package/bin/tezgah-setup +5798 -0
- package/bin/tezgah-status +383 -0
- package/bin/tezgah-task +607 -0
- package/bin/tezgah-taste +423 -0
- package/bin/tezgah-triage +485 -0
- package/bin/tezgah.js +36 -0
- package/commands/adhd.md +21 -0
- package/commands/ponytail.md +21 -0
- package/docs/README.md +112 -0
- package/docs/architecture.md +226 -0
- package/docs/citations-baseline.json +59 -0
- package/docs/clarity-baseline.json +116 -0
- package/docs/contract.md +416 -0
- package/docs/environment.md +141 -0
- package/docs/evidence.md +831 -0
- package/docs/feature-audit.md +139 -0
- package/docs/gate.md +407 -0
- package/docs/glossary.md +201 -0
- package/docs/hosts.md +400 -0
- package/docs/index.json +471 -0
- package/docs/judge.md +193 -0
- package/docs/layers.md +189 -0
- package/docs/models.md +160 -0
- package/docs/operations.md +666 -0
- package/docs/research.md +809 -0
- package/docs/results/paired-outcome-2026-10.jsonl +254 -0
- package/docs/results/paired-outcome-2026-10.md +204 -0
- package/docs/skills.md +236 -0
- package/docs/status-line.md +315 -0
- package/docs/testing.md +287 -0
- package/docs/vision.md +117 -0
- package/hooks/hooks.json +33 -0
- package/hooks/projects-auto-init.py +98 -0
- package/hooks/projects-posttooluse.py +149 -0
- package/hooks/projects-pretooluse.py +40 -0
- package/hooks/projects-stop.py +60 -0
- package/hooks/tezgah_agents.py +817 -0
- package/hooks/tezgah_apps.py +266 -0
- package/hooks/tezgah_attest.py +255 -0
- package/hooks/tezgah_clarity.py +171 -0
- package/hooks/tezgah_components.py +140 -0
- package/hooks/tezgah_context.py +2953 -0
- package/hooks/tezgah_embed.py +765 -0
- package/hooks/tezgah_gate.py +2736 -0
- package/hooks/tezgah_guard.py +177 -0
- package/hooks/tezgah_index.py +124 -0
- package/hooks/tezgah_integrity.py +5141 -0
- package/hooks/tezgah_judge.py +487 -0
- package/hooks/tezgah_lang.py +230 -0
- package/hooks/tezgah_lessons.py +210 -0
- package/hooks/tezgah_models.py +855 -0
- package/hooks/tezgah_paths.py +1132 -0
- package/hooks/tezgah_policy.py +1039 -0
- package/hooks/tezgah_rank.py +77 -0
- package/hooks/tezgah_replay.py +1109 -0
- package/hooks/tezgah_research.py +5176 -0
- package/hooks/tezgah_shapes.py +305 -0
- package/hooks/tezgah_skill_pick.py +227 -0
- package/hooks/tezgah_snapshot.py +490 -0
- package/hooks/tezgah_stopspec.py +320 -0
- package/hooks/tezgah_task.py +458 -0
- package/hooks/tezgah_taste.py +201 -0
- package/hooks/tezgah_untrusted.py +112 -0
- package/hooks/tezgah_update.py +430 -0
- package/hosts/codex/hook.py +268 -0
- package/hosts/codex/hooks.json +26 -0
- package/hosts/cursor/hook.py +461 -0
- package/hosts/cursor/hooks.json +44 -0
- package/hosts/dsh/hooks.json +21 -0
- package/hosts/dsh/statusline/lib/client.js +138 -0
- package/hosts/dsh/statusline/lib/index.js +104 -0
- package/hosts/dsh/statusline/package.json +28 -0
- package/hosts/omp/hook.py +277 -0
- package/hosts/omp/tezgah-hook.ts.in +701 -0
- package/hosts/opencode/plugins/tezgah.js +2742 -0
- package/hosts/opencode/tui/tezgah-tui.tsx +151 -0
- package/output-styles/tezgah.md +142 -0
- package/package.json +32 -0
- package/packaging/build.sh +342 -0
- package/packaging/install.ps1 +166 -0
- package/packaging/install.sh +56 -0
- package/packaging/upgrade.sh +142 -0
- package/skills/ai-research/0-autoresearch-skill/SKILL.md +411 -0
- package/skills/ai-research/0-autoresearch-skill/references/agent-continuity.md +95 -0
- package/skills/ai-research/0-autoresearch-skill/references/progress-reporting.md +166 -0
- package/skills/ai-research/0-autoresearch-skill/references/skill-routing.md +219 -0
- package/skills/ai-research/0-autoresearch-skill/templates/findings.md +44 -0
- package/skills/ai-research/0-autoresearch-skill/templates/research-log.md +41 -0
- package/skills/ai-research/01-model-architecture/litgpt/SKILL.md +469 -0
- package/skills/ai-research/01-model-architecture/litgpt/references/custom-models.md +569 -0
- package/skills/ai-research/01-model-architecture/litgpt/references/distributed-training.md +452 -0
- package/skills/ai-research/01-model-architecture/litgpt/references/supported-models.md +337 -0
- package/skills/ai-research/01-model-architecture/litgpt/references/training-recipes.md +620 -0
- package/skills/ai-research/01-model-architecture/mamba/SKILL.md +260 -0
- package/skills/ai-research/01-model-architecture/mamba/references/architecture-details.md +207 -0
- package/skills/ai-research/01-model-architecture/mamba/references/benchmarks.md +256 -0
- package/skills/ai-research/01-model-architecture/mamba/references/training-guide.md +389 -0
- package/skills/ai-research/01-model-architecture/nanogpt/SKILL.md +290 -0
- package/skills/ai-research/01-model-architecture/nanogpt/references/architecture.md +383 -0
- package/skills/ai-research/01-model-architecture/nanogpt/references/data.md +477 -0
- package/skills/ai-research/01-model-architecture/nanogpt/references/training.md +565 -0
- package/skills/ai-research/01-model-architecture/rwkv/SKILL.md +260 -0
- package/skills/ai-research/01-model-architecture/rwkv/references/architecture-details.md +345 -0
- package/skills/ai-research/01-model-architecture/rwkv/references/rwkv7.md +387 -0
- package/skills/ai-research/01-model-architecture/rwkv/references/state-management.md +370 -0
- package/skills/ai-research/01-model-architecture/torchtitan/SKILL.md +358 -0
- package/skills/ai-research/01-model-architecture/torchtitan/references/checkpoint.md +182 -0
- package/skills/ai-research/01-model-architecture/torchtitan/references/custom-models.md +259 -0
- package/skills/ai-research/01-model-architecture/torchtitan/references/float8.md +134 -0
- package/skills/ai-research/01-model-architecture/torchtitan/references/fsdp.md +127 -0
- package/skills/ai-research/02-tokenization/huggingface-tokenizers/SKILL.md +516 -0
- package/skills/ai-research/02-tokenization/huggingface-tokenizers/references/algorithms.md +654 -0
- package/skills/ai-research/02-tokenization/huggingface-tokenizers/references/integration.md +638 -0
- package/skills/ai-research/02-tokenization/huggingface-tokenizers/references/pipeline.md +724 -0
- package/skills/ai-research/02-tokenization/huggingface-tokenizers/references/training.md +566 -0
- package/skills/ai-research/02-tokenization/sentencepiece/SKILL.md +235 -0
- package/skills/ai-research/02-tokenization/sentencepiece/references/algorithms.md +201 -0
- package/skills/ai-research/02-tokenization/sentencepiece/references/training.md +305 -0
- package/skills/ai-research/03-fine-tuning/axolotl/SKILL.md +158 -0
- package/skills/ai-research/03-fine-tuning/axolotl/references/api.md +5549 -0
- package/skills/ai-research/03-fine-tuning/axolotl/references/dataset-formats.md +1030 -0
- package/skills/ai-research/03-fine-tuning/axolotl/references/index.md +16 -0
- package/skills/ai-research/03-fine-tuning/axolotl/references/other.md +3564 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/SKILL.md +80 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/references/_images.md +24 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/references/advanced.md +1056 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/references/getting_started.md +350 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/references/index.md +20 -0
- package/skills/ai-research/03-fine-tuning/llama-factory/references/other.md +32 -0
- package/skills/ai-research/03-fine-tuning/peft/SKILL.md +431 -0
- package/skills/ai-research/03-fine-tuning/peft/references/advanced-usage.md +515 -0
- package/skills/ai-research/03-fine-tuning/peft/references/troubleshooting.md +481 -0
- package/skills/ai-research/03-fine-tuning/unsloth/SKILL.md +80 -0
- package/skills/ai-research/03-fine-tuning/unsloth/references/index.md +8 -0
- package/skills/ai-research/03-fine-tuning/unsloth/references/llms.md +83 -0
- package/skills/ai-research/04-mechanistic-interpretability/nnsight/SKILL.md +436 -0
- package/skills/ai-research/04-mechanistic-interpretability/nnsight/references/README.md +79 -0
- package/skills/ai-research/04-mechanistic-interpretability/nnsight/references/api.md +345 -0
- package/skills/ai-research/04-mechanistic-interpretability/nnsight/references/tutorials.md +301 -0
- package/skills/ai-research/04-mechanistic-interpretability/pyvene/SKILL.md +473 -0
- package/skills/ai-research/04-mechanistic-interpretability/pyvene/references/README.md +74 -0
- package/skills/ai-research/04-mechanistic-interpretability/pyvene/references/api.md +384 -0
- package/skills/ai-research/04-mechanistic-interpretability/pyvene/references/tutorials.md +377 -0
- package/skills/ai-research/04-mechanistic-interpretability/saelens/SKILL.md +386 -0
- package/skills/ai-research/04-mechanistic-interpretability/saelens/references/README.md +71 -0
- package/skills/ai-research/04-mechanistic-interpretability/saelens/references/api.md +334 -0
- package/skills/ai-research/04-mechanistic-interpretability/saelens/references/tutorials.md +319 -0
- package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/SKILL.md +346 -0
- package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/references/README.md +55 -0
- package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/references/api.md +363 -0
- package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/references/tutorials.md +340 -0
- package/skills/ai-research/05-data-processing/nemo-curator/SKILL.md +383 -0
- package/skills/ai-research/05-data-processing/nemo-curator/references/deduplication.md +88 -0
- package/skills/ai-research/05-data-processing/nemo-curator/references/filtering.md +103 -0
- package/skills/ai-research/05-data-processing/ray-data/SKILL.md +326 -0
- package/skills/ai-research/05-data-processing/ray-data/references/integration.md +83 -0
- package/skills/ai-research/05-data-processing/ray-data/references/transformations.md +84 -0
- package/skills/ai-research/06-post-training/grpo-rl-training/README.md +98 -0
- package/skills/ai-research/06-post-training/grpo-rl-training/SKILL.md +572 -0
- package/skills/ai-research/06-post-training/miles/SKILL.md +315 -0
- package/skills/ai-research/06-post-training/miles/references/api-reference.md +142 -0
- package/skills/ai-research/06-post-training/miles/references/troubleshooting.md +353 -0
- package/skills/ai-research/06-post-training/openrlhf/SKILL.md +249 -0
- package/skills/ai-research/06-post-training/openrlhf/references/algorithm-comparison.md +405 -0
- package/skills/ai-research/06-post-training/openrlhf/references/custom-rewards.md +531 -0
- package/skills/ai-research/06-post-training/openrlhf/references/hybrid-engine.md +288 -0
- package/skills/ai-research/06-post-training/openrlhf/references/multi-node-training.md +455 -0
- package/skills/ai-research/06-post-training/simpo/SKILL.md +219 -0
- package/skills/ai-research/06-post-training/simpo/references/datasets.md +479 -0
- package/skills/ai-research/06-post-training/simpo/references/hyperparameters.md +453 -0
- package/skills/ai-research/06-post-training/simpo/references/loss-functions.md +351 -0
- package/skills/ai-research/06-post-training/slime/SKILL.md +464 -0
- package/skills/ai-research/06-post-training/slime/references/api-reference.md +393 -0
- package/skills/ai-research/06-post-training/slime/references/troubleshooting.md +387 -0
- package/skills/ai-research/06-post-training/torchforge/SKILL.md +433 -0
- package/skills/ai-research/06-post-training/torchforge/references/api-reference.md +328 -0
- package/skills/ai-research/06-post-training/torchforge/references/troubleshooting.md +410 -0
- package/skills/ai-research/06-post-training/trl-fine-tuning/SKILL.md +455 -0
- package/skills/ai-research/06-post-training/trl-fine-tuning/references/dpo-variants.md +228 -0
- package/skills/ai-research/06-post-training/trl-fine-tuning/references/online-rl.md +83 -0
- package/skills/ai-research/06-post-training/trl-fine-tuning/references/reward-modeling.md +123 -0
- package/skills/ai-research/06-post-training/trl-fine-tuning/references/sft-training.md +169 -0
- package/skills/ai-research/06-post-training/verl/SKILL.md +391 -0
- package/skills/ai-research/06-post-training/verl/references/api-reference.md +302 -0
- package/skills/ai-research/06-post-training/verl/references/troubleshooting.md +392 -0
- package/skills/ai-research/07-safety-alignment/constitutional-ai/SKILL.md +290 -0
- package/skills/ai-research/07-safety-alignment/llamaguard/SKILL.md +337 -0
- package/skills/ai-research/07-safety-alignment/nemo-guardrails/SKILL.md +297 -0
- package/skills/ai-research/07-safety-alignment/prompt-guard/SKILL.md +312 -0
- package/skills/ai-research/08-distributed-training/accelerate/SKILL.md +332 -0
- package/skills/ai-research/08-distributed-training/accelerate/references/custom-plugins.md +454 -0
- package/skills/ai-research/08-distributed-training/accelerate/references/megatron-integration.md +490 -0
- package/skills/ai-research/08-distributed-training/accelerate/references/performance.md +526 -0
- package/skills/ai-research/08-distributed-training/deepspeed/SKILL.md +141 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/08.md +18 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/09.md +174 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/2020.md +379 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/2023.md +280 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/assets.md +180 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/index.md +36 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/mii.md +119 -0
- package/skills/ai-research/08-distributed-training/deepspeed/references/other.md +1192 -0
- package/skills/ai-research/08-distributed-training/megatron-core/SKILL.md +366 -0
- package/skills/ai-research/08-distributed-training/megatron-core/references/benchmarks.md +250 -0
- package/skills/ai-research/08-distributed-training/megatron-core/references/parallelism-guide.md +405 -0
- package/skills/ai-research/08-distributed-training/megatron-core/references/production-examples.md +474 -0
- package/skills/ai-research/08-distributed-training/megatron-core/references/training-recipes.md +548 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/SKILL.md +231 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_dcp_async_recipe.md +14 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_dcp_overview.md +19 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_dcp_recipe.md +18 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_ddp_notes.md +10 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_device_mesh_tutorial.md +22 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_examples_fsdp2.md +15 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_fsdp1_api.md +9 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_fsdp2_tutorial.md +45 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_fully_shard_api.md +49 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/pytorch_tp_tutorial.md +22 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/ray_train_fsdp2_example.md +13 -0
- package/skills/ai-research/08-distributed-training/pytorch-fsdp2/references/torchtitan_fsdp_notes.md +14 -0
- package/skills/ai-research/08-distributed-training/pytorch-lightning/SKILL.md +346 -0
- package/skills/ai-research/08-distributed-training/pytorch-lightning/references/callbacks.md +437 -0
- package/skills/ai-research/08-distributed-training/pytorch-lightning/references/distributed.md +491 -0
- package/skills/ai-research/08-distributed-training/pytorch-lightning/references/hyperparameter-tuning.md +557 -0
- package/skills/ai-research/08-distributed-training/ray-train/SKILL.md +406 -0
- package/skills/ai-research/08-distributed-training/ray-train/references/multi-node.md +629 -0
- package/skills/ai-research/09-infrastructure/lambda-labs/SKILL.md +545 -0
- package/skills/ai-research/09-infrastructure/lambda-labs/references/advanced-usage.md +612 -0
- package/skills/ai-research/09-infrastructure/lambda-labs/references/troubleshooting.md +531 -0
- package/skills/ai-research/09-infrastructure/modal/SKILL.md +341 -0
- package/skills/ai-research/09-infrastructure/modal/references/advanced-usage.md +504 -0
- package/skills/ai-research/09-infrastructure/modal/references/troubleshooting.md +495 -0
- package/skills/ai-research/09-infrastructure/skypilot/SKILL.md +509 -0
- package/skills/ai-research/09-infrastructure/skypilot/references/advanced-usage.md +492 -0
- package/skills/ai-research/09-infrastructure/skypilot/references/troubleshooting.md +571 -0
- package/skills/ai-research/10-optimization/awq/SKILL.md +310 -0
- package/skills/ai-research/10-optimization/awq/references/advanced-usage.md +325 -0
- package/skills/ai-research/10-optimization/awq/references/troubleshooting.md +345 -0
- package/skills/ai-research/10-optimization/bitsandbytes/SKILL.md +411 -0
- package/skills/ai-research/10-optimization/bitsandbytes/references/memory-optimization.md +522 -0
- package/skills/ai-research/10-optimization/bitsandbytes/references/qlora-training.md +522 -0
- package/skills/ai-research/10-optimization/bitsandbytes/references/quantization-formats.md +448 -0
- package/skills/ai-research/10-optimization/flash-attention/SKILL.md +367 -0
- package/skills/ai-research/10-optimization/flash-attention/references/benchmarks.md +216 -0
- package/skills/ai-research/10-optimization/flash-attention/references/transformers-integration.md +294 -0
- package/skills/ai-research/10-optimization/gguf/SKILL.md +427 -0
- package/skills/ai-research/10-optimization/gguf/references/advanced-usage.md +505 -0
- package/skills/ai-research/10-optimization/gguf/references/troubleshooting.md +443 -0
- package/skills/ai-research/10-optimization/gptq/SKILL.md +450 -0
- package/skills/ai-research/10-optimization/gptq/references/calibration.md +338 -0
- package/skills/ai-research/10-optimization/gptq/references/integration.md +130 -0
- package/skills/ai-research/10-optimization/gptq/references/troubleshooting.md +96 -0
- package/skills/ai-research/10-optimization/hqq/SKILL.md +445 -0
- package/skills/ai-research/10-optimization/hqq/references/advanced-usage.md +529 -0
- package/skills/ai-research/10-optimization/hqq/references/troubleshooting.md +504 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/SKILL.md +319 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/architecture.md +320 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/biomedical.md +498 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/domain-specific.md +626 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/experiment-loop.md +129 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/optimizers.md +330 -0
- package/skills/ai-research/10-optimization/ml-training-recipes/references/scaling-and-selection.md +428 -0
- package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/SKILL.md +405 -0
- package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/references/benchmarks.md +394 -0
- package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/references/custom-tasks.md +425 -0
- package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/references/issues.md +395 -0
- package/skills/ai-research/11-evaluation/lm-evaluation-harness/SKILL.md +490 -0
- package/skills/ai-research/11-evaluation/lm-evaluation-harness/references/api-evaluation.md +491 -0
- package/skills/ai-research/11-evaluation/lm-evaluation-harness/references/benchmark-guide.md +489 -0
- package/skills/ai-research/11-evaluation/lm-evaluation-harness/references/custom-tasks.md +603 -0
- package/skills/ai-research/11-evaluation/lm-evaluation-harness/references/distributed-eval.md +520 -0
- package/skills/ai-research/11-evaluation/nemo-evaluator/SKILL.md +494 -0
- package/skills/ai-research/11-evaluation/nemo-evaluator/references/adapter-system.md +341 -0
- package/skills/ai-research/11-evaluation/nemo-evaluator/references/configuration.md +448 -0
- package/skills/ai-research/11-evaluation/nemo-evaluator/references/custom-benchmarks.md +316 -0
- package/skills/ai-research/11-evaluation/nemo-evaluator/references/execution-backends.md +362 -0
- package/skills/ai-research/12-inference-serving/llama-cpp/SKILL.md +258 -0
- package/skills/ai-research/12-inference-serving/llama-cpp/references/optimization.md +90 -0
- package/skills/ai-research/12-inference-serving/llama-cpp/references/quantization.md +214 -0
- package/skills/ai-research/12-inference-serving/llama-cpp/references/server.md +126 -0
- package/skills/ai-research/12-inference-serving/sglang/SKILL.md +442 -0
- package/skills/ai-research/12-inference-serving/sglang/references/deployment.md +491 -0
- package/skills/ai-research/12-inference-serving/sglang/references/radix-attention.md +414 -0
- package/skills/ai-research/12-inference-serving/sglang/references/structured-generation.md +542 -0
- package/skills/ai-research/12-inference-serving/tensorrt-llm/SKILL.md +187 -0
- package/skills/ai-research/12-inference-serving/tensorrt-llm/references/multi-gpu.md +299 -0
- package/skills/ai-research/12-inference-serving/tensorrt-llm/references/optimization.md +243 -0
- package/skills/ai-research/12-inference-serving/tensorrt-llm/references/serving.md +471 -0
- package/skills/ai-research/12-inference-serving/vllm/SKILL.md +364 -0
- package/skills/ai-research/12-inference-serving/vllm/references/optimization.md +227 -0
- package/skills/ai-research/12-inference-serving/vllm/references/quantization.md +285 -0
- package/skills/ai-research/12-inference-serving/vllm/references/server-deployment.md +256 -0
- package/skills/ai-research/12-inference-serving/vllm/references/troubleshooting.md +448 -0
- package/skills/ai-research/13-mlops/mlflow/SKILL.md +704 -0
- package/skills/ai-research/13-mlops/mlflow/references/deployment.md +745 -0
- package/skills/ai-research/13-mlops/mlflow/references/model-registry.md +771 -0
- package/skills/ai-research/13-mlops/mlflow/references/tracking.md +681 -0
- package/skills/ai-research/13-mlops/swanlab/SKILL.md +406 -0
- package/skills/ai-research/13-mlops/swanlab/references/integrations.md +305 -0
- package/skills/ai-research/13-mlops/swanlab/references/visualization.md +234 -0
- package/skills/ai-research/13-mlops/tensorboard/SKILL.md +629 -0
- package/skills/ai-research/13-mlops/tensorboard/references/integrations.md +639 -0
- package/skills/ai-research/13-mlops/tensorboard/references/profiling.md +546 -0
- package/skills/ai-research/13-mlops/tensorboard/references/visualization.md +621 -0
- package/skills/ai-research/13-mlops/weights-and-biases/SKILL.md +590 -0
- package/skills/ai-research/13-mlops/weights-and-biases/references/artifacts.md +585 -0
- package/skills/ai-research/13-mlops/weights-and-biases/references/integrations.md +701 -0
- package/skills/ai-research/13-mlops/weights-and-biases/references/sweeps.md +848 -0
- package/skills/ai-research/14-agents/a-evolve/SKILL.md +383 -0
- package/skills/ai-research/14-agents/a-evolve/references/README.md +831 -0
- package/skills/ai-research/14-agents/a-evolve/references/api.md +594 -0
- package/skills/ai-research/14-agents/a-evolve/references/architecture.md +417 -0
- package/skills/ai-research/14-agents/a-evolve/references/design-patterns.md +462 -0
- package/skills/ai-research/14-agents/a-evolve/references/examples.md +352 -0
- package/skills/ai-research/14-agents/a-evolve/references/issues.md +591 -0
- package/skills/ai-research/14-agents/a-evolve/references/releases.md +112 -0
- package/skills/ai-research/14-agents/a-evolve/references/tutorials.md +819 -0
- package/skills/ai-research/14-agents/autogpt/SKILL.md +403 -0
- package/skills/ai-research/14-agents/autogpt/references/advanced-usage.md +536 -0
- package/skills/ai-research/14-agents/autogpt/references/troubleshooting.md +421 -0
- package/skills/ai-research/14-agents/crewai/SKILL.md +498 -0
- package/skills/ai-research/14-agents/crewai/references/flows.md +439 -0
- package/skills/ai-research/14-agents/crewai/references/tools.md +430 -0
- package/skills/ai-research/14-agents/crewai/references/troubleshooting.md +481 -0
- package/skills/ai-research/14-agents/langchain/SKILL.md +480 -0
- package/skills/ai-research/14-agents/langchain/references/agents.md +500 -0
- package/skills/ai-research/14-agents/langchain/references/integration.md +563 -0
- package/skills/ai-research/14-agents/langchain/references/rag.md +601 -0
- package/skills/ai-research/14-agents/llamaindex/SKILL.md +569 -0
- package/skills/ai-research/14-agents/llamaindex/references/agents.md +84 -0
- package/skills/ai-research/14-agents/llamaindex/references/data_connectors.md +109 -0
- package/skills/ai-research/14-agents/llamaindex/references/query_engines.md +407 -0
- package/skills/ai-research/15-rag/chroma/SKILL.md +406 -0
- package/skills/ai-research/15-rag/chroma/references/integration.md +39 -0
- package/skills/ai-research/15-rag/faiss/SKILL.md +221 -0
- package/skills/ai-research/15-rag/faiss/references/index_types.md +281 -0
- package/skills/ai-research/15-rag/pinecone/SKILL.md +358 -0
- package/skills/ai-research/15-rag/pinecone/references/deployment.md +182 -0
- package/skills/ai-research/15-rag/qdrant/SKILL.md +493 -0
- package/skills/ai-research/15-rag/qdrant/references/advanced-usage.md +649 -0
- package/skills/ai-research/15-rag/qdrant/references/troubleshooting.md +632 -0
- package/skills/ai-research/15-rag/sentence-transformers/SKILL.md +255 -0
- package/skills/ai-research/15-rag/sentence-transformers/references/models.md +124 -0
- package/skills/ai-research/16-prompt-engineering/dspy/SKILL.md +590 -0
- package/skills/ai-research/16-prompt-engineering/dspy/references/examples.md +664 -0
- package/skills/ai-research/16-prompt-engineering/dspy/references/modules.md +476 -0
- package/skills/ai-research/16-prompt-engineering/dspy/references/optimizers.md +567 -0
- package/skills/ai-research/16-prompt-engineering/guidance/SKILL.md +572 -0
- package/skills/ai-research/16-prompt-engineering/guidance/references/backends.md +555 -0
- package/skills/ai-research/16-prompt-engineering/guidance/references/constraints.md +675 -0
- package/skills/ai-research/16-prompt-engineering/guidance/references/examples.md +768 -0
- package/skills/ai-research/16-prompt-engineering/instructor/SKILL.md +740 -0
- package/skills/ai-research/16-prompt-engineering/instructor/references/examples.md +108 -0
- package/skills/ai-research/16-prompt-engineering/instructor/references/providers.md +71 -0
- package/skills/ai-research/16-prompt-engineering/instructor/references/validation.md +607 -0
- package/skills/ai-research/16-prompt-engineering/outlines/SKILL.md +652 -0
- package/skills/ai-research/16-prompt-engineering/outlines/references/backends.md +616 -0
- package/skills/ai-research/16-prompt-engineering/outlines/references/examples.md +774 -0
- package/skills/ai-research/16-prompt-engineering/outlines/references/json_generation.md +653 -0
- package/skills/ai-research/17-observability/langsmith/SKILL.md +422 -0
- package/skills/ai-research/17-observability/langsmith/references/advanced-usage.md +549 -0
- package/skills/ai-research/17-observability/langsmith/references/troubleshooting.md +538 -0
- package/skills/ai-research/17-observability/phoenix/SKILL.md +475 -0
- package/skills/ai-research/17-observability/phoenix/references/advanced-usage.md +620 -0
- package/skills/ai-research/17-observability/phoenix/references/troubleshooting.md +539 -0
- package/skills/ai-research/18-multimodal/audiocraft/SKILL.md +564 -0
- package/skills/ai-research/18-multimodal/audiocraft/references/advanced-usage.md +667 -0
- package/skills/ai-research/18-multimodal/audiocraft/references/troubleshooting.md +505 -0
- package/skills/ai-research/18-multimodal/blip-2/SKILL.md +564 -0
- package/skills/ai-research/18-multimodal/blip-2/references/advanced-usage.md +681 -0
- package/skills/ai-research/18-multimodal/blip-2/references/troubleshooting.md +527 -0
- package/skills/ai-research/18-multimodal/clip/SKILL.md +253 -0
- package/skills/ai-research/18-multimodal/clip/references/applications.md +208 -0
- package/skills/ai-research/18-multimodal/cosmos-policy/SKILL.md +402 -0
- package/skills/ai-research/18-multimodal/cosmos-policy/references/libero-commands.md +175 -0
- package/skills/ai-research/18-multimodal/cosmos-policy/references/robocasa-commands.md +147 -0
- package/skills/ai-research/18-multimodal/llava/SKILL.md +304 -0
- package/skills/ai-research/18-multimodal/llava/references/training.md +198 -0
- package/skills/ai-research/18-multimodal/openpi/SKILL.md +480 -0
- package/skills/ai-research/18-multimodal/openpi/references/checkpoints-and-env-map.md +86 -0
- package/skills/ai-research/18-multimodal/openpi/references/config-recipes.md +50 -0
- package/skills/ai-research/18-multimodal/openpi/references/pytorch-gotchas.md +49 -0
- package/skills/ai-research/18-multimodal/openpi/references/remote-client-pattern.md +48 -0
- package/skills/ai-research/18-multimodal/openpi/references/training-debugging.md +46 -0
- package/skills/ai-research/18-multimodal/openvla-oft/SKILL.md +441 -0
- package/skills/ai-research/18-multimodal/openvla-oft/references/aloha-workflow.md +141 -0
- package/skills/ai-research/18-multimodal/openvla-oft/references/config-troubleshooting.md +97 -0
- package/skills/ai-research/18-multimodal/openvla-oft/references/libero-workflow.md +115 -0
- package/skills/ai-research/18-multimodal/openvla-oft/references/paper-and-checkpoints.md +84 -0
- package/skills/ai-research/18-multimodal/segment-anything/SKILL.md +500 -0
- package/skills/ai-research/18-multimodal/segment-anything/references/advanced-usage.md +590 -0
- package/skills/ai-research/18-multimodal/segment-anything/references/troubleshooting.md +485 -0
- package/skills/ai-research/18-multimodal/stable-diffusion/SKILL.md +519 -0
- package/skills/ai-research/18-multimodal/stable-diffusion/references/advanced-usage.md +717 -0
- package/skills/ai-research/18-multimodal/stable-diffusion/references/troubleshooting.md +556 -0
- package/skills/ai-research/18-multimodal/whisper/SKILL.md +317 -0
- package/skills/ai-research/18-multimodal/whisper/references/languages.md +190 -0
- package/skills/ai-research/19-emerging-techniques/knowledge-distillation/SKILL.md +458 -0
- package/skills/ai-research/19-emerging-techniques/knowledge-distillation/references/minillm.md +335 -0
- package/skills/ai-research/19-emerging-techniques/long-context/SKILL.md +536 -0
- package/skills/ai-research/19-emerging-techniques/long-context/references/extension_methods.md +469 -0
- package/skills/ai-research/19-emerging-techniques/long-context/references/fine_tuning.md +612 -0
- package/skills/ai-research/19-emerging-techniques/long-context/references/rope.md +403 -0
- package/skills/ai-research/19-emerging-techniques/model-merging/SKILL.md +497 -0
- package/skills/ai-research/19-emerging-techniques/model-merging/references/coefficient-tuning.md +415 -0
- package/skills/ai-research/19-emerging-techniques/model-merging/references/evaluation.md +463 -0
- package/skills/ai-research/19-emerging-techniques/model-merging/references/examples.md +429 -0
- package/skills/ai-research/19-emerging-techniques/model-merging/references/methods.md +353 -0
- package/skills/ai-research/19-emerging-techniques/model-pruning/SKILL.md +495 -0
- package/skills/ai-research/19-emerging-techniques/model-pruning/references/wanda.md +348 -0
- package/skills/ai-research/19-emerging-techniques/moe-training/SKILL.md +526 -0
- package/skills/ai-research/19-emerging-techniques/moe-training/references/architectures.md +433 -0
- package/skills/ai-research/19-emerging-techniques/moe-training/references/inference.md +349 -0
- package/skills/ai-research/19-emerging-techniques/moe-training/references/training.md +426 -0
- package/skills/ai-research/19-emerging-techniques/speculative-decoding/SKILL.md +467 -0
- package/skills/ai-research/19-emerging-techniques/speculative-decoding/references/lookahead.md +310 -0
- package/skills/ai-research/19-emerging-techniques/speculative-decoding/references/medusa.md +351 -0
- package/skills/ai-research/20-ml-paper-writing/academic-plotting/SKILL.md +479 -0
- package/skills/ai-research/20-ml-paper-writing/academic-plotting/references/data-visualization.md +509 -0
- package/skills/ai-research/20-ml-paper-writing/academic-plotting/references/diagram-generation.md +395 -0
- package/skills/ai-research/20-ml-paper-writing/academic-plotting/references/style-guide.md +305 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/SKILL.md +982 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/references/checklists.md +348 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/references/citation-workflow.md +563 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/references/reviewer-guidelines.md +349 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/references/sources.md +165 -0
- package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/references/writing-guide.md +477 -0
- package/skills/ai-research/20-ml-paper-writing/presenting-conference-talks/SKILL.md +298 -0
- package/skills/ai-research/20-ml-paper-writing/presenting-conference-talks/references/slide-templates.md +505 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/SKILL.md +270 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/references/checklist.md +279 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/references/reviewer-guidelines.md +116 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/references/section-blueprints.md +400 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/references/systems-conferences.md +261 -0
- package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/references/writing-patterns.md +231 -0
- package/skills/ai-research/21-research-ideation/brainstorming-research-ideas/SKILL.md +384 -0
- package/skills/ai-research/21-research-ideation/creative-thinking-for-research/SKILL.md +366 -0
- package/skills/ai-research/22-agent-native-research-artifact/compiler/SKILL.md +245 -0
- package/skills/ai-research/22-agent-native-research-artifact/compiler/references/ara-schema.md +439 -0
- package/skills/ai-research/22-agent-native-research-artifact/compiler/references/exploration-tree-spec.md +125 -0
- package/skills/ai-research/22-agent-native-research-artifact/compiler/references/validation-checklist.md +149 -0
- package/skills/ai-research/22-agent-native-research-artifact/research-manager/SKILL.md +324 -0
- package/skills/ai-research/22-agent-native-research-artifact/research-manager/references/event-taxonomy.md +124 -0
- package/skills/ai-research/22-agent-native-research-artifact/research-manager/references/provenance-tags.md +190 -0
- package/skills/ai-research/22-agent-native-research-artifact/research-manager/references/session-protocol.md +162 -0
- package/skills/ai-research/22-agent-native-research-artifact/rigor-reviewer/SKILL.md +322 -0
- package/skills/ai-research/22-agent-native-research-artifact/rigor-reviewer/references/review-dimensions.md +182 -0
- package/skills/ai-research/SKILL.md +101 -0
- package/skills/ai-research/SOURCE +38 -0
- package/skills/ai-research/index/1-frame.md +18 -0
- package/skills/ai-research/index/2-data.md +21 -0
- package/skills/ai-research/index/3-train.md +48 -0
- package/skills/ai-research/index/4-measure.md +25 -0
- package/skills/ai-research/index/5-run.md +23 -0
- package/skills/ai-research/index/6-write.md +35 -0
- package/skills/ai-research/library.json +3456 -0
- package/skills/analyze-app/EVALS.md +82 -0
- package/skills/analyze-app/SKILL.md +188 -0
- package/skills/design-contract/EVALS.md +80 -0
- package/skills/design-contract/SKILL.md +171 -0
- package/skills/design-library/EVALS.md +85 -0
- package/skills/design-library/INDEX.md +59 -0
- package/skills/design-library/SKILL.md +58 -0
- package/skills/design-library/SOURCE +131 -0
- package/skills/design-library/adaptive-interfaces/skills/colour-independence/SKILL.md +90 -0
- package/skills/design-library/adaptive-interfaces/skills/flexible-typography/SKILL.md +83 -0
- package/skills/design-library/adaptive-interfaces/skills/information-density/SKILL.md +87 -0
- package/skills/design-library/adaptive-interfaces/skills/preference-audit/SKILL.md +70 -0
- package/skills/design-library/adaptive-interfaces/skills/responsive-accessibility/SKILL.md +86 -0
- package/skills/design-library/adaptive-interfaces/skills/responsive-review/SKILL.md +76 -0
- package/skills/design-library/adaptive-interfaces/skills/simplified-views/SKILL.md +97 -0
- package/skills/design-library/adaptive-interfaces/skills/specify/SKILL.md +63 -0
- package/skills/design-library/adaptive-interfaces/skills/user-preference-respect/SKILL.md +100 -0
- package/skills/design-library/cognitive-accessibility/skills/adaptive-personalisation/SKILL.md +61 -0
- package/skills/design-library/cognitive-accessibility/skills/assess-load/SKILL.md +48 -0
- package/skills/design-library/cognitive-accessibility/skills/cognitive-load-assessment/SKILL.md +60 -0
- package/skills/design-library/cognitive-accessibility/skills/contextual-help-design/SKILL.md +61 -0
- package/skills/design-library/cognitive-accessibility/skills/error-prevention-recovery/SKILL.md +71 -0
- package/skills/design-library/cognitive-accessibility/skills/focus-attention-design/SKILL.md +67 -0
- package/skills/design-library/cognitive-accessibility/skills/memory-load-reduction/SKILL.md +60 -0
- package/skills/design-library/cognitive-accessibility/skills/plain-language-design/SKILL.md +78 -0
- package/skills/design-library/cognitive-accessibility/skills/review/SKILL.md +56 -0
- package/skills/design-library/cognitive-accessibility/skills/simplify/SKILL.md +39 -0
- package/skills/design-library/cognitive-accessibility/skills/wayfinding-navigation/SKILL.md +64 -0
- package/skills/design-library/design-research/skills/behavioural-analytics/SKILL.md +33 -0
- package/skills/design-library/design-research/skills/qual-quant-triangulation/SKILL.md +33 -0
- package/skills/design-library/inclusive-interaction/skills/audit/SKILL.md +50 -0
- package/skills/design-library/inclusive-interaction/skills/design-flow/SKILL.md +58 -0
- package/skills/design-library/inclusive-interaction/skills/feedback-and-status/SKILL.md +76 -0
- package/skills/design-library/inclusive-interaction/skills/gesture-alternatives/SKILL.md +70 -0
- package/skills/design-library/inclusive-interaction/skills/keyboard-navigation/SKILL.md +99 -0
- package/skills/design-library/inclusive-interaction/skills/keyboard-review/SKILL.md +54 -0
- package/skills/design-library/inclusive-interaction/skills/motion-sensitivity/SKILL.md +79 -0
- package/skills/design-library/inclusive-interaction/skills/multi-modal-input/SKILL.md +70 -0
- package/skills/design-library/inclusive-interaction/skills/touch-target-design/SKILL.md +71 -0
- package/skills/design-library/inclusive-interaction/skills/voice-interaction/SKILL.md +68 -0
- package/skills/design-library/visual-critique/skills/critique-affordance/SKILL.md +45 -0
- package/skills/design-library/visual-critique/skills/critique-brand-consistency/SKILL.md +42 -0
- package/skills/design-library/visual-critique/skills/critique-color/SKILL.md +44 -0
- package/skills/design-library/visual-critique/skills/critique-composition/SKILL.md +46 -0
- package/skills/design-library/visual-critique/skills/critique-information-density/SKILL.md +45 -0
- package/skills/design-library/visual-critique/skills/critique-typography/SKILL.md +45 -0
- package/skills/design-library/visual-critique/skills/critique-visual-hierarchy/SKILL.md +40 -0
- package/skills/feature-audit/EVALS.md +83 -0
- package/skills/feature-audit/SKILL.md +326 -0
- package/skills/harness/EVALS.md +81 -0
- package/skills/harness/SKILL.md +88 -0
- package/skills/i-have-adhd/EVALS.md +80 -0
- package/skills/i-have-adhd/SKILL.md +183 -0
- package/skills/no-ai-slop/EVALS.md +89 -0
- package/skills/no-ai-slop/SKILL.md +209 -0
- package/skills/no-ai-slop/eval.md +52 -0
- package/skills/plan-add/EVALS.md +79 -0
- package/skills/plan-add/SKILL.md +179 -0
- package/skills/plan-add/render_table.py +72 -0
- package/skills/plan-status/EVALS.md +78 -0
- package/skills/plan-status/SKILL.md +52 -0
- package/skills/plan-sync/EVALS.md +80 -0
- package/skills/plan-sync/SKILL.md +69 -0
- package/skills/pm-frameworks/SKILL.md +33 -0
- package/skills/pm-frameworks/SOURCE +39 -0
- package/skills/pm-frameworks/intended-vs-implemented/SKILL.md +42 -0
- package/skills/pm-frameworks/opportunity-solution-tree/SKILL.md +64 -0
- package/skills/ponytail/EVALS.md +80 -0
- package/skills/ponytail/SKILL.md +124 -0
- package/skills/product-analysis/EVALS.md +103 -0
- package/skills/product-analysis/SKILL.md +361 -0
- package/skills/research/EVALS.md +104 -0
- package/skills/research/SKILL.md +570 -0
- package/skills/rl-env/SKILL.md +53 -0
- package/skills/rl-env/SOURCE +41 -0
- package/skills/rl-env/generate-nemo-gym-env/SKILL.md +169 -0
- package/skills/rl-env/generate-nemo-gym-env/references/architecture.md +114 -0
- package/skills/rl-env/generate-openenv-env/SKILL.md +183 -0
- package/skills/rl-env/generate-openenv-env/references/architecture.md +82 -0
- package/skills/rl-env/generate-ors-env/SKILL.md +209 -0
- package/skills/rl-env/generate-ors-env/references/architecture.md +113 -0
- package/skills/rl-env/generate-verifiers-env/SKILL.md +188 -0
- package/skills/rl-env/generate-verifiers-env/references/architecture.md +119 -0
- package/skills/rl-env/rl-env-from-description/SKILL.md +124 -0
- package/skills/rl-env/rl-env-from-description/references/interview.md +75 -0
- package/skills/rl-env/rl-env-from-description/references/nemo_gym.md +41 -0
- package/skills/rl-env/rl-env-from-description/references/openenv.md +35 -0
- package/skills/rl-env/rl-env-from-description/references/ors.md +50 -0
- package/skills/rl-env/rl-env-from-description/references/verifiers.md +39 -0
- package/skills/tezgah-contract/EVALS.md +78 -0
- package/skills/tezgah-contract/SKILL.md +792 -0
- package/statusline.py +129 -0
- package/workflows/graph-impact.js +153 -0
- package/workflows/graph-map.js +158 -0
- package/workflows/graph-review.js +170 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""tezgah-design derive|check - the per-repo design contract, and the check that
|
|
3
|
+
can fail.
|
|
4
|
+
|
|
5
|
+
tezgah-design derive --repo DIR [--out FILE] [--json]
|
|
6
|
+
tezgah-design check --contract FILE --measured FILE|- [--json]
|
|
7
|
+
|
|
8
|
+
The contract is the floor a UI turn is judged against: the colour roles, the
|
|
9
|
+
type scale, the spacing unit and the state set a repository already uses, plus
|
|
10
|
+
the component inventory it owns. `derive` writes a first `.tezgah/design-contract.md`
|
|
11
|
+
from the repository's own token source (CSS custom properties, a Tailwind config,
|
|
12
|
+
a tokens/theme JSON) and exits 2 rather than invent a palette it did not find.
|
|
13
|
+
`check` compares a measurement - the per-component computed styles and observed
|
|
14
|
+
states the analyze-app loop produces - against that file, prints every violation
|
|
15
|
+
with its component and its rule, and exits non-zero when there is at least one.
|
|
16
|
+
It names how many components it judged, and exits 2 when that is zero: `0
|
|
17
|
+
violations` over a measurement that carries no component would say the app is
|
|
18
|
+
clean when nothing was looked at.
|
|
19
|
+
|
|
20
|
+
Stdlib only, deterministic, no network and no credential: the same tree and the
|
|
21
|
+
same measurement print the same bytes, with no clock and no path outside the
|
|
22
|
+
repository in the artifact.
|
|
23
|
+
|
|
24
|
+
derive exit 0 the contract was written; 2 no token source; 1 usage or IO
|
|
25
|
+
check exit 0 `0 violations` over at least one component judged; 1 one or
|
|
26
|
+
more violations; 2 usage, IO, a bad contract or nothing judged
|
|
27
|
+
"""
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
# The state set every interactive control and every data view owes: seven per
|
|
34
|
+
# control, eight per data view, thirteen distinct states - `loading` and `error`
|
|
35
|
+
# are the two the halves share, and the shipped lists name them twice rather than
|
|
36
|
+
# once. Pinned equal to the three lists the repository already ships
|
|
37
|
+
# (tests/test_design_cli.py:StateSetPin) rather than left to drift as a fourth
|
|
38
|
+
# copy.
|
|
39
|
+
STATES_INTERACTIVE = ("default", "hover", "focus", "active", "disabled",
|
|
40
|
+
"loading", "error")
|
|
41
|
+
STATES_DATA = ("empty", "loading", "skeleton", "error", "offline", "partial",
|
|
42
|
+
"long-text", "permission-denied")
|
|
43
|
+
STATES = STATES_INTERACTIVE + tuple(s for s in STATES_DATA
|
|
44
|
+
if s not in STATES_INTERACTIVE)
|
|
45
|
+
KINDS = ("interactive", "data")
|
|
46
|
+
|
|
47
|
+
# The floors that are not this repository's to choose: WCAG 2.2 SC 2.5.8 (the
|
|
48
|
+
# 24x24 CSS px target size) and SC 1.4.3 (4.5:1, or 3:1 for large text, which is
|
|
49
|
+
# 24px and up, or 18.66px bold).
|
|
50
|
+
TAP_TARGET = 24
|
|
51
|
+
CONTRAST_NORMAL = 4.5
|
|
52
|
+
CONTRAST_LARGE = 3.0
|
|
53
|
+
LARGE_TEXT = 24
|
|
54
|
+
|
|
55
|
+
# The two floors the vendored design library names and a measurement can decide:
|
|
56
|
+
# a focus indication that survives the outline being switched off (SC 2.4.7 Focus
|
|
57
|
+
# Visible, Level AA - skills/design-library's `keyboard-navigation` and
|
|
58
|
+
# `focus-attention-design`), and motion that has a declared way out (SC 2.3.3
|
|
59
|
+
# Animation from Interactions, Level AAA - `motion-sensitivity`). Their shape is
|
|
60
|
+
# habitat's component schema, where `focus` is a required part of a component's
|
|
61
|
+
# `accessibility` block: the measurement says what is there, the contract says
|
|
62
|
+
# what is allowed. Both read only declarations the measurement makes, so a
|
|
63
|
+
# measurement that says nothing about focus or motion is not judged at all.
|
|
64
|
+
# What counts as a focus indication beside a dead outline: a treatment that
|
|
65
|
+
# paints without it. `outline-color` is NOT one - a colour with `outline-style:
|
|
66
|
+
# none` and `outline-width: 0` paints nothing, and every resolved computed-style
|
|
67
|
+
# dump carries one, so accepting it would make the rule silent on the shape a
|
|
68
|
+
# real measurement has.
|
|
69
|
+
FOCUS_PROPS = ("outline", "outline-style", "outline-width", "outline-color")
|
|
70
|
+
# The props that can paint: a value among these is what the rule concludes from.
|
|
71
|
+
FOCUS_PAINTS = ("outline", "outline-style", "outline-width")
|
|
72
|
+
# A box-shadow paints on its own, so it settles the rule by itself. A border
|
|
73
|
+
# does not: `border-style`'s initial value is `none`, whose colour and width are
|
|
74
|
+
# ignored, and a computed width is 0 whenever the style is none or hidden - so a
|
|
75
|
+
# resolved computed-style dump of a border-less element carries
|
|
76
|
+
# `border-width: 0px` and a colour. `border_paints()` reads those.
|
|
77
|
+
FOCUS_ALTERNATIVES = ("box-shadow",)
|
|
78
|
+
FOCUS_BORDER_PAINTS = ("border", "border-style", "border-width")
|
|
79
|
+
# The style words that draw nothing at all, and the shape of a length token.
|
|
80
|
+
STYLE_OFF = frozenset(("none", "hidden"))
|
|
81
|
+
LENGTH_TOKEN = re.compile(r"^[-+]?\d*\.?\d+[a-z%]*$")
|
|
82
|
+
# A length that is zero in every entry, in any unit and any number of leading
|
|
83
|
+
# zeroes: a drawn border or outline needs one that is not.
|
|
84
|
+
ZERO = re.compile(r"^[-+]?0+\.?0*[a-z%]*$")
|
|
85
|
+
MOTION_PROPS = ("transition-duration", "animation-duration")
|
|
86
|
+
|
|
87
|
+
SKIP_DIRS = frozenset((".git", "node_modules", "dist", "build", ".venv", "venv",
|
|
88
|
+
"vendor", "__pycache__", ".next", ".cache", "coverage",
|
|
89
|
+
".tezgah", ".codegraph"))
|
|
90
|
+
CSS_SUFFIXES = (".css", ".scss", ".sass", ".less")
|
|
91
|
+
TAILWIND_NAMES = ("tailwind.config.js", "tailwind.config.cjs",
|
|
92
|
+
"tailwind.config.mjs", "tailwind.config.ts",
|
|
93
|
+
"tailwind.config.json")
|
|
94
|
+
THEME_NAMES = ("theme.json", "tokens.json", "design-tokens.json")
|
|
95
|
+
FILE_CAP = 4000
|
|
96
|
+
CSS_CAP = 200
|
|
97
|
+
|
|
98
|
+
DECL = re.compile(r"(--[A-Za-z0-9_-]+)\s*:\s*([^;{}]+)")
|
|
99
|
+
BLOCK = re.compile(r"\b%s\s*:\s*\{")
|
|
100
|
+
PAIR = re.compile(r"^\s*['\"]?([A-Za-z0-9_-]+)['\"]?\s*:\s*(.+?)\s*$")
|
|
101
|
+
HEX3 = re.compile(r"^#([0-9a-f])([0-9a-f])([0-9a-f])$", re.I)
|
|
102
|
+
HEX6 = re.compile(r"^#([0-9a-f]{6})$", re.I)
|
|
103
|
+
RGB = re.compile(r"^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)"
|
|
104
|
+
r"(?:[\s,/]+([0-9.]+%?))?\s*\)$", re.I)
|
|
105
|
+
LENGTH = re.compile(r"^(-?\d*\.?\d+)(px|rem|pt|em|%)?$")
|
|
106
|
+
# Values that make no claim about a token: an absent value said out loud.
|
|
107
|
+
NOTHING = frozenset(("none", "transparent", "currentcolor", "inherit",
|
|
108
|
+
"initial", "unset", "auto", "normal"))
|
|
109
|
+
COLOUR_PROPS = ("color", "background-color", "border-color", "outline-color")
|
|
110
|
+
SPACING_PROPS = ("padding", "padding-top", "padding-right", "padding-bottom",
|
|
111
|
+
"padding-left", "margin", "margin-top", "margin-right",
|
|
112
|
+
"margin-bottom", "margin-left", "gap", "row-gap", "column-gap")
|
|
113
|
+
SPACE_NAMES = ("space-unit", "spacing-unit", "space-base", "grid-unit",
|
|
114
|
+
"base-unit", "space", "spacing", "unit", "rhythm")
|
|
115
|
+
TYPE_NAME = re.compile(r"^(?:font|text|type)[-_]?size|^fs(?:[-_]|$)"
|
|
116
|
+
r"|^(?:font|text)[-_]?scale", re.I)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# --- values -----------------------------------------------------------------
|
|
120
|
+
def normalise_colour(value):
|
|
121
|
+
"""`#rrggbb` for a colour literal, or None. `transparent` and its family are
|
|
122
|
+
None without being a claim: they say there is no colour to judge."""
|
|
123
|
+
text = str(value or "").strip().lower()
|
|
124
|
+
if not text or text in NOTHING or text.startswith("var("):
|
|
125
|
+
return None
|
|
126
|
+
m = HEX3.match(text)
|
|
127
|
+
if m:
|
|
128
|
+
return "#" + "".join(c * 2 for c in m.groups())
|
|
129
|
+
if HEX6.match(text):
|
|
130
|
+
return text
|
|
131
|
+
m = RGB.match(text)
|
|
132
|
+
if m:
|
|
133
|
+
if m.group(4) and m.group(4).endswith("%"):
|
|
134
|
+
try:
|
|
135
|
+
if float(m.group(4)[:-1]) == 0:
|
|
136
|
+
return None
|
|
137
|
+
except ValueError:
|
|
138
|
+
return None
|
|
139
|
+
elif m.group(4) and float(m.group(4)) == 0:
|
|
140
|
+
return None
|
|
141
|
+
try:
|
|
142
|
+
return "#%02x%02x%02x" % tuple(min(255, int(m.group(i)))
|
|
143
|
+
for i in (1, 2, 3))
|
|
144
|
+
except ValueError:
|
|
145
|
+
return None
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def length_px(value):
|
|
150
|
+
"""The value in CSS px, or None when it is not an absolute length. `rem` is
|
|
151
|
+
read at the root size (16px), `pt` at 4/3 px; a unitless value is a length
|
|
152
|
+
only at zero, and `em`/`%` depend on a context this file does not have."""
|
|
153
|
+
m = LENGTH.match(str(value or "").strip().lower())
|
|
154
|
+
if not m:
|
|
155
|
+
return None
|
|
156
|
+
n, unit = float(m.group(1)), m.group(2)
|
|
157
|
+
if unit == "px":
|
|
158
|
+
return n
|
|
159
|
+
if unit is None:
|
|
160
|
+
return 0.0 if n == 0 else None
|
|
161
|
+
if unit == "rem":
|
|
162
|
+
return n * 16
|
|
163
|
+
if unit == "pt":
|
|
164
|
+
return n * 4 / 3
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _luminance(hex_colour):
|
|
169
|
+
parts = [int(hex_colour[i:i + 2], 16) / 255 for i in (1, 3, 5)]
|
|
170
|
+
parts = [p / 12.92 if p <= 0.03928 else ((p + 0.055) / 1.055) ** 2.4
|
|
171
|
+
for p in parts]
|
|
172
|
+
return 0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def contrast_ratio(fore, back):
|
|
176
|
+
"""The WCAG 2.2 contrast ratio between two `#rrggbb` colours."""
|
|
177
|
+
a, b = _luminance(fore), _luminance(back)
|
|
178
|
+
hi, lo = max(a, b), min(a, b)
|
|
179
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def durations_ms(value):
|
|
183
|
+
"""(the longest readable time in ms, the count of entries that were not a
|
|
184
|
+
time) for a CSS time or a comma list.
|
|
185
|
+
|
|
186
|
+
A list takes its longest entry - that is the one a person sits through and
|
|
187
|
+
the one a guard has to cover - and it is read entry by entry, so one entry
|
|
188
|
+
nobody can parse neither hides a readable one nor lets the value read as
|
|
189
|
+
settled."""
|
|
190
|
+
worst, unreadable = None, 0
|
|
191
|
+
for part in str(value or "").split(","):
|
|
192
|
+
m = re.fullmatch(r"\s*(-?\d*\.?\d+)\s*(ms|s)\s*", part)
|
|
193
|
+
if not m:
|
|
194
|
+
unreadable += 1
|
|
195
|
+
continue
|
|
196
|
+
ms = float(m.group(1)) * (1 if m.group(2) == "ms" else 1000)
|
|
197
|
+
worst = ms if worst is None else max(worst, ms)
|
|
198
|
+
return worst, unreadable
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def paints_nothing(value):
|
|
202
|
+
"""True when a declaration draws nothing: every entry is a zero length, or a
|
|
203
|
+
word that claims nothing (`none`, `hidden`, `transparent` and its family).
|
|
204
|
+
|
|
205
|
+
A resolved computed-style dump carries these where nothing is drawn -
|
|
206
|
+
`outline-width: 0px`, `border-width: 0px`, `box-shadow: none` - so reading
|
|
207
|
+
one as evidence of a treatment is how a rule goes silent on the measurement
|
|
208
|
+
shape it was written for."""
|
|
209
|
+
parts = str(value or "").lower().replace(",", " ").split()
|
|
210
|
+
return bool(parts) and all(p in NOTHING or p == "hidden" or ZERO.match(p)
|
|
211
|
+
for p in parts)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def declaration_draws(value):
|
|
215
|
+
"""True when one measured declaration draws something.
|
|
216
|
+
|
|
217
|
+
A lone longhand is judged by its own value (`2px` draws, `0px` does not).
|
|
218
|
+
The `border` and `outline` shorthands carry width, style and colour in one
|
|
219
|
+
string, so they draw only when no component of them says otherwise: a
|
|
220
|
+
`none`/`hidden` style word, or a width that is zero - `border: 0 solid #111`
|
|
221
|
+
draws nothing whatever the style says, which is a border-less element's
|
|
222
|
+
computed shape on a Tailwind page, and `outline: 0 solid #111` is focus with
|
|
223
|
+
nothing to see."""
|
|
224
|
+
parts = str(value or "").lower().replace(",", " ").split()
|
|
225
|
+
if not parts:
|
|
226
|
+
return False
|
|
227
|
+
if any(part == "auto" for part in parts):
|
|
228
|
+
# `outline-style: auto` is the focus ring the UA draws, so it belongs on
|
|
229
|
+
# the drawing side even though `auto` claims nothing for most props.
|
|
230
|
+
return True
|
|
231
|
+
if any(part in STYLE_OFF for part in parts):
|
|
232
|
+
return False
|
|
233
|
+
if all(paints_nothing(part) for part in parts):
|
|
234
|
+
return False
|
|
235
|
+
widths = [part for part in parts if LENGTH_TOKEN.match(part)]
|
|
236
|
+
return not (widths and all(ZERO.match(width) for width in widths))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def border_paints(styles):
|
|
240
|
+
"""True when the measurement names a border that draws something.
|
|
241
|
+
|
|
242
|
+
Every declared part of it has to draw: a live `border-style` beside
|
|
243
|
+
`border-width: 0px` draws nothing, and that pair is the computed shape of a
|
|
244
|
+
border-less element on a Tailwind page, so either half read alone would let
|
|
245
|
+
the rule settle on a border nobody sees."""
|
|
246
|
+
values = [styles[prop] for prop in FOCUS_BORDER_PAINTS if prop in styles]
|
|
247
|
+
return bool(values) and all(declaration_draws(value) for value in values)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def focus_outline_off(styles):
|
|
251
|
+
"""The declarations that switch focus off with nothing beside them to show
|
|
252
|
+
it, or [].
|
|
253
|
+
|
|
254
|
+
`outline: none` is a violation only when the same measurement names no other
|
|
255
|
+
indication: a `box-shadow` that draws, or a border that can paint, is a focus
|
|
256
|
+
treatment by another route. An `outline-color`, a bare `border-color` and a
|
|
257
|
+
zero-width border are not: a resolved computed-style dump carries all three
|
|
258
|
+
while nothing is drawn."""
|
|
259
|
+
off = ["%s: %s" % (prop, styles[prop])
|
|
260
|
+
for prop in FOCUS_PAINTS
|
|
261
|
+
if prop in styles and not declaration_draws(styles[prop])]
|
|
262
|
+
if not off:
|
|
263
|
+
return []
|
|
264
|
+
if any(prop in styles and not paints_nothing(styles[prop])
|
|
265
|
+
for prop in FOCUS_ALTERNATIVES):
|
|
266
|
+
return []
|
|
267
|
+
if border_paints(styles):
|
|
268
|
+
return []
|
|
269
|
+
return off
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# --- reading the repository's own tokens ------------------------------------
|
|
273
|
+
def walk(root):
|
|
274
|
+
"""Every file under `root`, repo-relative and sorted, with the directories a
|
|
275
|
+
token source is never in skipped and the count bounded."""
|
|
276
|
+
out = []
|
|
277
|
+
for base, dirs, names in os.walk(root):
|
|
278
|
+
dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)
|
|
279
|
+
for name in sorted(names):
|
|
280
|
+
rel = os.path.relpath(os.path.join(base, name), root)
|
|
281
|
+
out.append(rel.replace(os.sep, "/"))
|
|
282
|
+
if len(out) >= FILE_CAP:
|
|
283
|
+
return out
|
|
284
|
+
return out
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def read_text(root, rel):
|
|
288
|
+
try:
|
|
289
|
+
with open(os.path.join(root, rel), encoding="utf-8",
|
|
290
|
+
errors="replace") as fh:
|
|
291
|
+
return fh.read()
|
|
292
|
+
except OSError:
|
|
293
|
+
return ""
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def css_declarations(root, files):
|
|
297
|
+
"""{name: value} from the custom properties of this repository's stylesheets,
|
|
298
|
+
later declarations winning, files in sorted order."""
|
|
299
|
+
out, sources = {}, []
|
|
300
|
+
seen = 0
|
|
301
|
+
for rel in files:
|
|
302
|
+
if not rel.lower().endswith(CSS_SUFFIXES):
|
|
303
|
+
continue
|
|
304
|
+
seen += 1
|
|
305
|
+
if seen > CSS_CAP:
|
|
306
|
+
break
|
|
307
|
+
text = read_text(root, rel)
|
|
308
|
+
found = False
|
|
309
|
+
for name, value in DECL.findall(text):
|
|
310
|
+
out[name.lstrip("-")] = value.strip()
|
|
311
|
+
found = True
|
|
312
|
+
if found:
|
|
313
|
+
sources.append(rel)
|
|
314
|
+
return out, sources
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _block_body(text, key):
|
|
318
|
+
"""Every `key: { ... }` body in `text`, brace-balanced, in file order."""
|
|
319
|
+
out = []
|
|
320
|
+
for m in re.finditer(BLOCK.pattern % re.escape(key), text):
|
|
321
|
+
i, depth = m.end(), 1
|
|
322
|
+
while i < len(text) and depth:
|
|
323
|
+
if text[i] == "{":
|
|
324
|
+
depth += 1
|
|
325
|
+
elif text[i] == "}":
|
|
326
|
+
depth -= 1
|
|
327
|
+
i += 1
|
|
328
|
+
out.append(text[m.end():i - 1])
|
|
329
|
+
return out
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _pairs(body):
|
|
333
|
+
"""The `key: value` pairs at the top level of a config block. A nested
|
|
334
|
+
object is skipped, not flattened: its leaves are a scale of their own and
|
|
335
|
+
this reader does not claim to know its shape."""
|
|
336
|
+
items, depth, buf = [], 0, []
|
|
337
|
+
for ch in body:
|
|
338
|
+
if ch in "{[":
|
|
339
|
+
depth += 1
|
|
340
|
+
elif ch in "}]":
|
|
341
|
+
depth -= 1
|
|
342
|
+
if ch == "," and depth == 0:
|
|
343
|
+
items.append("".join(buf))
|
|
344
|
+
buf = []
|
|
345
|
+
else:
|
|
346
|
+
buf.append(ch)
|
|
347
|
+
items.append("".join(buf))
|
|
348
|
+
out = []
|
|
349
|
+
for item in items:
|
|
350
|
+
if "{" in item:
|
|
351
|
+
continue
|
|
352
|
+
m = PAIR.match(item)
|
|
353
|
+
if m:
|
|
354
|
+
out.append((m.group(1), m.group(2).strip().strip("'\"")))
|
|
355
|
+
return out
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def tailwind_tokens(root, files):
|
|
359
|
+
"""The tokens a Tailwind config declares, read from its `colors`, `spacing`
|
|
360
|
+
and `fontSize` blocks."""
|
|
361
|
+
rel = next((f for f in files if os.path.basename(f) in TAILWIND_NAMES), None)
|
|
362
|
+
if rel is None:
|
|
363
|
+
return {}, []
|
|
364
|
+
text = read_text(root, rel)
|
|
365
|
+
colours, space, scale = {}, [], []
|
|
366
|
+
for body in _block_body(text, "colors"):
|
|
367
|
+
for key, value in _pairs(body):
|
|
368
|
+
hexed = normalise_colour(value)
|
|
369
|
+
if hexed:
|
|
370
|
+
colours[key] = hexed
|
|
371
|
+
for body in _block_body(text, "spacing"):
|
|
372
|
+
for key, value in _pairs(body):
|
|
373
|
+
px = length_px(value)
|
|
374
|
+
if px:
|
|
375
|
+
space.append((key in ("unit", "base"), px))
|
|
376
|
+
for body in _block_body(text, "fontSize"):
|
|
377
|
+
for _, value in _pairs(body):
|
|
378
|
+
for token in re.findall(r"['\"]?([\d.]+(?:px|rem|pt))", value):
|
|
379
|
+
px = length_px(token)
|
|
380
|
+
if px:
|
|
381
|
+
scale.append(int(round(px)))
|
|
382
|
+
out = {}
|
|
383
|
+
# A key named `unit`/`base` states the unit; otherwise the greatest common
|
|
384
|
+
# divisor of the scale the config declares is the unit it is built on.
|
|
385
|
+
named = sorted((px for explicit, px in space if explicit))
|
|
386
|
+
if named:
|
|
387
|
+
out["space_unit"] = int(named[0])
|
|
388
|
+
elif space:
|
|
389
|
+
out["space_unit"] = int(_gcd([px for _, px in space]))
|
|
390
|
+
if scale:
|
|
391
|
+
out["type_scale"] = sorted(set(scale))
|
|
392
|
+
if colours:
|
|
393
|
+
out["colors"] = colours
|
|
394
|
+
return out, [rel]
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def theme_tokens(root, files):
|
|
398
|
+
"""The tokens a tokens/theme JSON declares, found by key anywhere in it."""
|
|
399
|
+
rel = next((f for f in files if os.path.basename(f) in THEME_NAMES), None)
|
|
400
|
+
if rel is None:
|
|
401
|
+
return {}, []
|
|
402
|
+
try:
|
|
403
|
+
data = json.loads(read_text(root, rel))
|
|
404
|
+
except ValueError:
|
|
405
|
+
return {}, []
|
|
406
|
+
colours, space, scale = {}, {}, []
|
|
407
|
+
|
|
408
|
+
def visit(node):
|
|
409
|
+
if isinstance(node, dict):
|
|
410
|
+
for key, value in node.items():
|
|
411
|
+
low = str(key).lower()
|
|
412
|
+
if isinstance(value, dict):
|
|
413
|
+
if low in ("colors", "color", "palette", "colour"):
|
|
414
|
+
for name, item in value.items():
|
|
415
|
+
if isinstance(item, str) and normalise_colour(item):
|
|
416
|
+
colours[str(name)] = normalise_colour(item)
|
|
417
|
+
if low in ("spacing", "space"):
|
|
418
|
+
for name, item in value.items():
|
|
419
|
+
px = length_px(item) if isinstance(item, str) else None
|
|
420
|
+
if px:
|
|
421
|
+
space[str(name)] = px
|
|
422
|
+
if low in ("fontsize", "font-size", "font_sizes",
|
|
423
|
+
"font-sizes"):
|
|
424
|
+
for _, item in value.items():
|
|
425
|
+
token = item[0] if isinstance(item, list) and item \
|
|
426
|
+
else item
|
|
427
|
+
px = length_px(token) if isinstance(token, str) else None
|
|
428
|
+
if px:
|
|
429
|
+
scale.append(int(round(px)))
|
|
430
|
+
visit(value)
|
|
431
|
+
elif isinstance(node, list):
|
|
432
|
+
for item in node:
|
|
433
|
+
visit(item)
|
|
434
|
+
|
|
435
|
+
visit(data)
|
|
436
|
+
out = {}
|
|
437
|
+
if space:
|
|
438
|
+
out["space_unit"] = int(_gcd(list(space.values())))
|
|
439
|
+
if scale:
|
|
440
|
+
out["type_scale"] = sorted(set(scale))
|
|
441
|
+
if colours:
|
|
442
|
+
out["colors"] = colours
|
|
443
|
+
return out, [rel]
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _gcd(values):
|
|
447
|
+
values = [int(round(v)) for v in values if v]
|
|
448
|
+
if not values:
|
|
449
|
+
return 0
|
|
450
|
+
out = values[0]
|
|
451
|
+
for v in values[1:]:
|
|
452
|
+
while v:
|
|
453
|
+
out, v = v, out % v
|
|
454
|
+
return out
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _roles(declarations):
|
|
458
|
+
"""{role: hex} for the custom properties that carry a colour literal."""
|
|
459
|
+
out = {}
|
|
460
|
+
for name, value in declarations.items():
|
|
461
|
+
hexed = normalise_colour(value)
|
|
462
|
+
if not hexed:
|
|
463
|
+
continue
|
|
464
|
+
role = re.sub(r"^(?:colou?r|clr|c)[-_]", "", name, flags=re.I)
|
|
465
|
+
role = re.sub(r"[-_]?colou?r$", "", role, flags=re.I)
|
|
466
|
+
out[(role or "color").replace("_", "-").strip("-")] = hexed
|
|
467
|
+
return out
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def css_tokens(root, files):
|
|
471
|
+
"""The tokens a repository's CSS custom properties carry."""
|
|
472
|
+
decls, sources = css_declarations(root, files)
|
|
473
|
+
if not decls:
|
|
474
|
+
return {}, []
|
|
475
|
+
out = {}
|
|
476
|
+
colours = _roles(decls)
|
|
477
|
+
if colours:
|
|
478
|
+
out["colors"] = colours
|
|
479
|
+
# A property named for the unit states it; otherwise the greatest common
|
|
480
|
+
# divisor of the spacing scale the file declares is the unit it is built on.
|
|
481
|
+
for key, value in decls.items():
|
|
482
|
+
bare = re.sub(r"[-_]unit$", "", key).strip("-")
|
|
483
|
+
if key in SPACE_NAMES or bare in SPACE_NAMES:
|
|
484
|
+
px = length_px(value)
|
|
485
|
+
if px:
|
|
486
|
+
out["space_unit"] = int(round(px))
|
|
487
|
+
break
|
|
488
|
+
if "space_unit" not in out:
|
|
489
|
+
scale = [length_px(v) for k, v in decls.items()
|
|
490
|
+
if re.match(r"^(?:space|spacing|gap)[-_]", k)]
|
|
491
|
+
scale = [px for px in scale if px]
|
|
492
|
+
if scale:
|
|
493
|
+
out["space_unit"] = int(_gcd(scale))
|
|
494
|
+
scale = []
|
|
495
|
+
for key, value in decls.items():
|
|
496
|
+
if TYPE_NAME.search(key):
|
|
497
|
+
px = length_px(value)
|
|
498
|
+
if px:
|
|
499
|
+
scale.append(int(round(px)))
|
|
500
|
+
if scale:
|
|
501
|
+
out["type_scale"] = sorted(set(scale))
|
|
502
|
+
return out, sources
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def derive(root):
|
|
506
|
+
"""(tokens, source, read from) from the repository's own token source, or
|
|
507
|
+
(None, reason, []) when it carries none."""
|
|
508
|
+
files = walk(root)
|
|
509
|
+
for name, reader in (("css-custom-properties", css_tokens),
|
|
510
|
+
("tailwind-config", tailwind_tokens),
|
|
511
|
+
("theme-json", theme_tokens)):
|
|
512
|
+
tokens, sources = reader(root, files)
|
|
513
|
+
if tokens.get("colors"):
|
|
514
|
+
return tokens, name, sources
|
|
515
|
+
return None, ("no token source: looked for CSS custom properties, a "
|
|
516
|
+
"Tailwind config and a tokens/theme JSON"), []
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def contract_data(tokens, source):
|
|
520
|
+
"""The machine half of the contract: the derived tokens, the shipped state
|
|
521
|
+
set, and the component inventory the repository owns."""
|
|
522
|
+
return {
|
|
523
|
+
"version": 1,
|
|
524
|
+
"source": source,
|
|
525
|
+
"tokens": {
|
|
526
|
+
"space_unit": tokens.get("space_unit"),
|
|
527
|
+
"type_scale": tokens.get("type_scale") or [],
|
|
528
|
+
"colors": tokens.get("colors") or {},
|
|
529
|
+
"tap_target": TAP_TARGET,
|
|
530
|
+
"contrast": {"normal": CONTRAST_NORMAL, "large": CONTRAST_LARGE},
|
|
531
|
+
},
|
|
532
|
+
"states": {"interactive": list(STATES_INTERACTIVE),
|
|
533
|
+
"data": list(STATES_DATA)},
|
|
534
|
+
"components": [],
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def contract_text(data, sources, derived_note):
|
|
539
|
+
"""The artifact: what a reader opens, and the block `check` reads. Field
|
|
540
|
+
names and prose stay English; nothing here carries a clock or a machine
|
|
541
|
+
path.
|
|
542
|
+
|
|
543
|
+
The inventory line is always written, and says plainly when it is empty:
|
|
544
|
+
`derive` reads tokens, never components - the measurement names a component,
|
|
545
|
+
the repository names files - so a derived contract's `component-inventory`
|
|
546
|
+
rule is inert until someone fills the list in. A reader who assumed the
|
|
547
|
+
floor judged the inventory would read every derived contract as stricter
|
|
548
|
+
than it is."""
|
|
549
|
+
read = ", ".join(sources[:3]) if sources else "(none)"
|
|
550
|
+
names = [str(c.get("name")) for c in (data.get("components") or [])
|
|
551
|
+
if isinstance(c, dict)]
|
|
552
|
+
inventory = ", ".join(names) if names else "(empty)"
|
|
553
|
+
rule = ("""- `components` is the inventory this contract owns, and it is EMPTY:
|
|
554
|
+
`derive` reads tokens and never components, so the `component-inventory` rule
|
|
555
|
+
is inert on this contract - nothing below judges a component that is not
|
|
556
|
+
listed. Add one `{name, kind}` per component the repository owns."""
|
|
557
|
+
if not names else
|
|
558
|
+
"""- `components` is the inventory this contract owns. A component the
|
|
559
|
+
measurement shows that is not listed here is a violation, so a new component
|
|
560
|
+
is added here before the turn closes.""")
|
|
561
|
+
return """# Design contract
|
|
562
|
+
|
|
563
|
+
source: %s
|
|
564
|
+
read from: %s
|
|
565
|
+
component inventory: %s
|
|
566
|
+
|
|
567
|
+
The floor this repository's screens are judged against. `bin/tezgah-design check`
|
|
568
|
+
compares a measurement - the per-component computed styles and observed states
|
|
569
|
+
the analyze-app loop produces - against the block below and exits non-zero on
|
|
570
|
+
every disagreement. The shape, the state set and the derivation rule live in the
|
|
571
|
+
`design-contract` skill.
|
|
572
|
+
|
|
573
|
+
- Colour roles are this repository's own token names; nothing here was invented.
|
|
574
|
+
- `tap_target` and `contrast` are WCAG 2.2 floors (SC 2.5.8, SC 1.4.3), not this
|
|
575
|
+
repository's choices.
|
|
576
|
+
- `space_unit` is the unit the repository's own spacing tokens are built on; a
|
|
577
|
+
measured spacing that is not a multiple of it is a violation.
|
|
578
|
+
%s
|
|
579
|
+
- %s
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
%s
|
|
583
|
+
```
|
|
584
|
+
""" % (data["source"], read, inventory, rule, derived_note,
|
|
585
|
+
json.dumps(data, indent=2, sort_keys=True))
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# --- the check ---------------------------------------------------------------
|
|
589
|
+
def read_contract(path):
|
|
590
|
+
"""(data, reason): the fenced JSON block of a contract file, or why not."""
|
|
591
|
+
try:
|
|
592
|
+
with open(path, encoding="utf-8") as fh:
|
|
593
|
+
text = fh.read()
|
|
594
|
+
except OSError as exc:
|
|
595
|
+
return None, "cannot read the contract: %s" % exc
|
|
596
|
+
m = re.search(r"```json\s*\n(.*?)\n```", text, re.S)
|
|
597
|
+
if not m:
|
|
598
|
+
return None, ("the contract carries no ```json block: the machine half "
|
|
599
|
+
"of the artifact is missing")
|
|
600
|
+
try:
|
|
601
|
+
data = json.loads(m.group(1))
|
|
602
|
+
except ValueError as exc:
|
|
603
|
+
return None, "the contract's json block does not parse: %s" % exc
|
|
604
|
+
if not isinstance(data, dict):
|
|
605
|
+
return None, "the contract's json block is not an object"
|
|
606
|
+
return data, None
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _violation(component, rule, detail):
|
|
610
|
+
return {"component": component, "rule": rule, "detail": detail}
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def check(data, measured):
|
|
614
|
+
"""(violations, unjudged, judged): every disagreement between a measurement
|
|
615
|
+
and the contract, each naming its component and its rule, the count of
|
|
616
|
+
measured values the contract could not judge at all, and how many components
|
|
617
|
+
it judged at all.
|
|
618
|
+
|
|
619
|
+
`judged` is what keeps "judged everything, found nothing" apart from "judged
|
|
620
|
+
nothing": a measurement that carries no component the contract can judge is
|
|
621
|
+
not a clean app, and the caller refuses to report it as one. A component
|
|
622
|
+
counts only when a rule actually judged it - the presence of a dict is not a
|
|
623
|
+
judgement, so `{"components": [{}]}` judges nothing, and neither is a name the
|
|
624
|
+
contract's inventory lists: `component-inventory` is a finding about the name
|
|
625
|
+
and nothing more, so a component that arrived with only a name is not counted
|
|
626
|
+
as judged. The contract-level rules above run either way - an unusable
|
|
627
|
+
contract is a finding whether or not anything was measured against it."""
|
|
628
|
+
out, unjudged, judged = [], 0, 0
|
|
629
|
+
|
|
630
|
+
def bad(component, rule, detail):
|
|
631
|
+
out.append(_violation(component, rule, detail))
|
|
632
|
+
|
|
633
|
+
if not str(data.get("source") or "").strip():
|
|
634
|
+
bad("contract", "contract-source",
|
|
635
|
+
"the contract carries no `source:` line, so nobody can say whether "
|
|
636
|
+
"it was read from this repository or invented")
|
|
637
|
+
tokens = data.get("tokens") if isinstance(data.get("tokens"), dict) else {}
|
|
638
|
+
colours = {str(k): normalise_colour(v)
|
|
639
|
+
for k, v in (tokens.get("colors") or {}).items()}
|
|
640
|
+
palette = sorted({v for v in colours.values() if v})
|
|
641
|
+
if not palette:
|
|
642
|
+
bad("contract", "contract-palette",
|
|
643
|
+
"the contract names no colour role, so no colour can be judged "
|
|
644
|
+
"against it")
|
|
645
|
+
space = tokens.get("space_unit")
|
|
646
|
+
scale = [int(v) for v in (tokens.get("type_scale") or []) if v is not None]
|
|
647
|
+
tap = tokens.get("tap_target") or TAP_TARGET
|
|
648
|
+
reduced = bool(tokens.get("reduced_motion"))
|
|
649
|
+
floors = tokens.get("contrast") or {}
|
|
650
|
+
normal = float(floors.get("normal") or CONTRAST_NORMAL)
|
|
651
|
+
large = float(floors.get("large") or CONTRAST_LARGE)
|
|
652
|
+
states = data.get("states") if isinstance(data.get("states"), dict) else {}
|
|
653
|
+
for kind, shipped in (("interactive", STATES_INTERACTIVE),
|
|
654
|
+
("data", STATES_DATA)):
|
|
655
|
+
got = states.get(kind)
|
|
656
|
+
if not isinstance(got, list) or len(got) != len(shipped) \
|
|
657
|
+
or any(str(a) != b for a, b in zip(got, shipped)):
|
|
658
|
+
bad("contract", "state-set",
|
|
659
|
+
"the contract's `%s` states are not the shipped set (%s): a "
|
|
660
|
+
"floor that drops a state is not the floor" % (kind, ", ".join(shipped)))
|
|
661
|
+
inventory = [str(c.get("name")) for c in (data.get("components") or [])
|
|
662
|
+
if isinstance(c, dict)]
|
|
663
|
+
|
|
664
|
+
for comp in (measured.get("components") or []):
|
|
665
|
+
if not isinstance(comp, dict):
|
|
666
|
+
unjudged += 1
|
|
667
|
+
continue
|
|
668
|
+
# A rule that read a value of this component sets this; a dict no rule
|
|
669
|
+
# could read anything out of is not a judged component, however many
|
|
670
|
+
# keys it arrived with.
|
|
671
|
+
hit = False
|
|
672
|
+
name = str(comp.get("name") or "?")
|
|
673
|
+
styles = {str(k).lower(): v for k, v in (comp.get("styles") or {}).items()}
|
|
674
|
+
for prop in COLOUR_PROPS:
|
|
675
|
+
if prop not in styles:
|
|
676
|
+
continue
|
|
677
|
+
hexed = normalise_colour(styles[prop])
|
|
678
|
+
if not hexed:
|
|
679
|
+
if str(styles[prop]).strip().lower() not in NOTHING:
|
|
680
|
+
unjudged += 1
|
|
681
|
+
continue
|
|
682
|
+
hit = True
|
|
683
|
+
if palette and hexed not in palette:
|
|
684
|
+
bad(name, "palette",
|
|
685
|
+
"%s %s is not a colour role in the contract (%s)"
|
|
686
|
+
% (prop, styles[prop], ", ".join(palette)))
|
|
687
|
+
for prop in ("font-size",):
|
|
688
|
+
if prop not in styles:
|
|
689
|
+
continue
|
|
690
|
+
px = length_px(styles[prop])
|
|
691
|
+
if px is None or not scale:
|
|
692
|
+
unjudged += 1
|
|
693
|
+
else:
|
|
694
|
+
hit = True
|
|
695
|
+
if int(round(px)) not in scale:
|
|
696
|
+
bad(name, "type-scale",
|
|
697
|
+
"%s %s is off the contract's type scale (%s)"
|
|
698
|
+
% (prop, styles[prop],
|
|
699
|
+
", ".join("%dpx" % s for s in scale)))
|
|
700
|
+
for prop, value in sorted(styles.items()):
|
|
701
|
+
if prop not in SPACING_PROPS:
|
|
702
|
+
continue
|
|
703
|
+
for part in str(value).split():
|
|
704
|
+
px = length_px(part)
|
|
705
|
+
if px is None or not space:
|
|
706
|
+
unjudged += 1
|
|
707
|
+
else:
|
|
708
|
+
hit = True
|
|
709
|
+
if px % space:
|
|
710
|
+
bad(name, "spacing-rhythm",
|
|
711
|
+
"%s %s is not a multiple of the %spx spacing unit"
|
|
712
|
+
% (prop, value, space))
|
|
713
|
+
kind = comp.get("kind")
|
|
714
|
+
if kind == "interactive":
|
|
715
|
+
for prop in ("width", "height"):
|
|
716
|
+
px = length_px(styles.get(prop))
|
|
717
|
+
if px is None:
|
|
718
|
+
unjudged += 1
|
|
719
|
+
else:
|
|
720
|
+
hit = True
|
|
721
|
+
if px < tap:
|
|
722
|
+
bad(name, "tap-target",
|
|
723
|
+
"%s %s is below the %dpx target floor (WCAG 2.2 "
|
|
724
|
+
"SC 2.5.8)" % (prop, styles[prop], tap))
|
|
725
|
+
claims = [prop for prop in FOCUS_PROPS if prop in styles]
|
|
726
|
+
if claims:
|
|
727
|
+
off = focus_outline_off(styles)
|
|
728
|
+
if off:
|
|
729
|
+
hit = True
|
|
730
|
+
bad(name, "focus-visible",
|
|
731
|
+
"%s, with no other focus indication measured beside it "
|
|
732
|
+
"(a box-shadow, a border that draws): a keyboard user "
|
|
733
|
+
"cannot see where they are (WCAG 2.2 SC 2.4.7 Focus "
|
|
734
|
+
"Visible, Level AA)" % ", ".join(off))
|
|
735
|
+
elif (any(prop in styles and declaration_draws(styles[prop])
|
|
736
|
+
for prop in FOCUS_PAINTS) or border_paints(styles)):
|
|
737
|
+
hit = True
|
|
738
|
+
else:
|
|
739
|
+
# an outline-colour with no outline to paint says nothing a
|
|
740
|
+
# rule can conclude from: a value, not a judgement
|
|
741
|
+
unjudged += 1
|
|
742
|
+
moving, judged_motion = [], False
|
|
743
|
+
for prop in MOTION_PROPS:
|
|
744
|
+
if prop not in styles:
|
|
745
|
+
continue
|
|
746
|
+
ms, unreadable = durations_ms(styles[prop])
|
|
747
|
+
unjudged += unreadable
|
|
748
|
+
if ms is None:
|
|
749
|
+
continue
|
|
750
|
+
judged_motion = True
|
|
751
|
+
if ms > 0:
|
|
752
|
+
moving.append("%s: %s" % (prop, styles[prop]))
|
|
753
|
+
if judged_motion:
|
|
754
|
+
hit = True
|
|
755
|
+
if moving and not reduced:
|
|
756
|
+
bad(name, "reduced-motion",
|
|
757
|
+
"%s is measured and the contract declares no reduced-motion "
|
|
758
|
+
"guard (`tokens.reduced_motion`): motion with no way out "
|
|
759
|
+
"(WCAG 2.2 SC 2.3.3 Animation from Interactions, Level AAA)"
|
|
760
|
+
% ", ".join(moving))
|
|
761
|
+
fore = normalise_colour(styles.get("color"))
|
|
762
|
+
back = normalise_colour(styles.get("background-color"))
|
|
763
|
+
if fore and back:
|
|
764
|
+
hit = True
|
|
765
|
+
size = length_px(styles.get("font-size"))
|
|
766
|
+
floor = large if size is not None and size >= LARGE_TEXT else normal
|
|
767
|
+
ratio = contrast_ratio(fore, back)
|
|
768
|
+
if ratio + 1e-9 < floor:
|
|
769
|
+
bad(name, "contrast",
|
|
770
|
+
"%.2f:1 for %s on %s is below the %.1f:1 floor (WCAG 2.2 "
|
|
771
|
+
"SC 1.4.3)" % (ratio, styles.get("color"),
|
|
772
|
+
styles.get("background-color"), floor))
|
|
773
|
+
if kind in KINDS:
|
|
774
|
+
hit = True
|
|
775
|
+
required = states.get(kind) or []
|
|
776
|
+
shown = [str(s) for s in (comp.get("states") or [])]
|
|
777
|
+
missing = [s for s in required if s not in shown]
|
|
778
|
+
if missing:
|
|
779
|
+
bad(name, "state-coverage",
|
|
780
|
+
"states not observed: %s" % ", ".join(missing))
|
|
781
|
+
elif kind is None:
|
|
782
|
+
unjudged += 1
|
|
783
|
+
if inventory and name not in inventory:
|
|
784
|
+
bad(name, "component-inventory",
|
|
785
|
+
"not in the contract's component inventory")
|
|
786
|
+
if hit:
|
|
787
|
+
judged += 1
|
|
788
|
+
out.sort(key=lambda v: (v["component"], v["rule"], v["detail"]))
|
|
789
|
+
return out, unjudged, judged
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
# --- the command line --------------------------------------------------------
|
|
793
|
+
class Usage(Exception):
|
|
794
|
+
pass
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def parse(argv, names):
|
|
798
|
+
"""`--flag value` pairs for the flags this verb takes; an unknown flag or a
|
|
799
|
+
missing value is a usage error, never a silently ignored argument."""
|
|
800
|
+
out, i = {}, 0
|
|
801
|
+
while i < len(argv):
|
|
802
|
+
arg = argv[i]
|
|
803
|
+
if arg == "--json":
|
|
804
|
+
out["json"] = True
|
|
805
|
+
elif arg in names:
|
|
806
|
+
if i + 1 >= len(argv):
|
|
807
|
+
raise Usage("%s wants a value" % arg)
|
|
808
|
+
out[arg.lstrip("-")] = argv[i + 1]
|
|
809
|
+
i += 1
|
|
810
|
+
else:
|
|
811
|
+
raise Usage("unknown argument: %s" % arg)
|
|
812
|
+
i += 1
|
|
813
|
+
return out
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _print_json(payload):
|
|
817
|
+
sys.stdout.write(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def derive_cli(opts):
|
|
821
|
+
root = os.path.abspath(opts.get("repo") or "")
|
|
822
|
+
if not os.path.isdir(root):
|
|
823
|
+
raise Usage("--repo must name a directory")
|
|
824
|
+
out = opts.get("out") or os.path.join(root, ".tezgah", "design-contract.md")
|
|
825
|
+
if os.path.exists(out):
|
|
826
|
+
# A contract that exists is the repository's own decision: it carries
|
|
827
|
+
# the component inventory someone filled in, so overwriting it would
|
|
828
|
+
# throw that away. Re-deriving over a token change is a hand edit, or a
|
|
829
|
+
# deliberate `rm` first.
|
|
830
|
+
raise Usage("the contract already exists at %s: a contract is edited, "
|
|
831
|
+
"not overwritten - remove it first or pass --out elsewhere"
|
|
832
|
+
% os.path.relpath(out, root))
|
|
833
|
+
tokens, source, sources = derive(root)
|
|
834
|
+
if tokens is None:
|
|
835
|
+
if opts.get("json"):
|
|
836
|
+
_print_json({"ok": False, "error": source})
|
|
837
|
+
sys.stderr.write("tezgah-design: %s\nWrite the contract by hand with "
|
|
838
|
+
"`source: derived` naming where the floor came from "
|
|
839
|
+
"(the design-contract skill says how), or point "
|
|
840
|
+
"`--repo` at a repository that has one.\n" % source)
|
|
841
|
+
return 2
|
|
842
|
+
data = contract_data(tokens, source)
|
|
843
|
+
note = ("Derived from the repository's own token source; refresh it by "
|
|
844
|
+
"removing this file and re-running `tezgah-design derive`." if sources else
|
|
845
|
+
"Derived, not read: name here what the floor came from.")
|
|
846
|
+
text = contract_text(data, sources, note)
|
|
847
|
+
try:
|
|
848
|
+
os.makedirs(os.path.dirname(os.path.abspath(out)), exist_ok=True)
|
|
849
|
+
with open(out, "w", encoding="utf-8") as fh:
|
|
850
|
+
fh.write(text)
|
|
851
|
+
except OSError as exc:
|
|
852
|
+
raise Usage("cannot write %s: %s" % (out, exc))
|
|
853
|
+
shown = os.path.relpath(out, root)
|
|
854
|
+
if opts.get("json"):
|
|
855
|
+
_print_json({"ok": True, "out": shown, "source": source,
|
|
856
|
+
"colors": len(data["tokens"]["colors"]),
|
|
857
|
+
"space_unit": data["tokens"]["space_unit"],
|
|
858
|
+
"type_scale": data["tokens"]["type_scale"],
|
|
859
|
+
"components": len(data["components"])})
|
|
860
|
+
else:
|
|
861
|
+
sys.stdout.write(
|
|
862
|
+
"wrote %s (source: %s, %d colour roles, %s, %d type steps)\n"
|
|
863
|
+
% (shown, source, len(data["tokens"]["colors"]),
|
|
864
|
+
"%spx unit" % data["tokens"]["space_unit"]
|
|
865
|
+
if data["tokens"]["space_unit"] else "no spacing unit found",
|
|
866
|
+
len(data["tokens"]["type_scale"])))
|
|
867
|
+
sys.stdout.write(
|
|
868
|
+
"component inventory: empty - `derive` reads tokens and never "
|
|
869
|
+
"components, so the `component-inventory` rule is inert on this "
|
|
870
|
+
"contract until the list is filled in\n")
|
|
871
|
+
return 0
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
def check_cli(opts):
|
|
875
|
+
contract = opts.get("contract")
|
|
876
|
+
measured = opts.get("measured")
|
|
877
|
+
if not contract or not measured:
|
|
878
|
+
raise Usage("check wants --contract FILE and --measured FILE|-")
|
|
879
|
+
data, reason = read_contract(contract)
|
|
880
|
+
if data is None:
|
|
881
|
+
raise Usage(reason)
|
|
882
|
+
if measured == "-":
|
|
883
|
+
raw = sys.stdin.read()
|
|
884
|
+
else:
|
|
885
|
+
try:
|
|
886
|
+
with open(measured, encoding="utf-8") as fh:
|
|
887
|
+
raw = fh.read()
|
|
888
|
+
except OSError as exc:
|
|
889
|
+
raise Usage("cannot read the measurement: %s" % exc)
|
|
890
|
+
if not raw.strip():
|
|
891
|
+
raise Usage("the measurement at %s is empty" % measured)
|
|
892
|
+
try:
|
|
893
|
+
got = json.loads(raw)
|
|
894
|
+
except ValueError as exc:
|
|
895
|
+
raise Usage("the measurement does not parse: %s" % exc)
|
|
896
|
+
if isinstance(got, list):
|
|
897
|
+
got = {"components": got}
|
|
898
|
+
if not isinstance(got, dict):
|
|
899
|
+
raise Usage("the measurement is not an object with a `components` list")
|
|
900
|
+
violations, unjudged, judged = check(data, got)
|
|
901
|
+
if not judged:
|
|
902
|
+
# `0 violations` over a measurement with nothing in it says the app is
|
|
903
|
+
# clean when nothing was looked at, and the Stop rule reads that output
|
|
904
|
+
# as the floor having been applied. It is a usage error, not a pass.
|
|
905
|
+
message = (
|
|
906
|
+
"nothing was judged: %s, so `0 violations` would report a clean app "
|
|
907
|
+
"when nothing was looked at. Pass the per-component styles and "
|
|
908
|
+
"states the analyze-app read produces."
|
|
909
|
+
% ("the measurement carries no `components` list"
|
|
910
|
+
if not got.get("components") else
|
|
911
|
+
"no entry in `components` is one the contract could judge"))
|
|
912
|
+
if opts.get("json"):
|
|
913
|
+
_print_json({"ok": False, "count": len(violations),
|
|
914
|
+
"violations": violations, "unjudged": unjudged,
|
|
915
|
+
"judged": 0, "error": message})
|
|
916
|
+
else:
|
|
917
|
+
sys.stderr.write("tezgah-design: %s\n" % message)
|
|
918
|
+
return 2
|
|
919
|
+
if opts.get("json"):
|
|
920
|
+
_print_json({"ok": not violations, "count": len(violations),
|
|
921
|
+
"violations": violations, "unjudged": unjudged,
|
|
922
|
+
"judged": judged})
|
|
923
|
+
else:
|
|
924
|
+
sys.stdout.write("%d violations\n" % len(violations))
|
|
925
|
+
for v in violations:
|
|
926
|
+
sys.stdout.write("%s: %s - %s\n"
|
|
927
|
+
% (v["component"], v["rule"], v["detail"]))
|
|
928
|
+
if unjudged:
|
|
929
|
+
sys.stdout.write("%d measured values the contract could not judge\n"
|
|
930
|
+
% unjudged)
|
|
931
|
+
sys.stdout.write("%d component%s judged\n"
|
|
932
|
+
% (judged, "" if judged == 1 else "s"))
|
|
933
|
+
return 1 if violations else 0
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def main(argv):
|
|
937
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
938
|
+
sys.stdout.write(__doc__)
|
|
939
|
+
return 0 if argv else 2
|
|
940
|
+
verb, rest = argv[0], argv[1:]
|
|
941
|
+
try:
|
|
942
|
+
if verb == "derive":
|
|
943
|
+
return derive_cli(parse(rest, ("--repo", "--out")))
|
|
944
|
+
if verb == "check":
|
|
945
|
+
return check_cli(parse(rest, ("--contract", "--measured")))
|
|
946
|
+
raise Usage("unknown verb: %s (derive|check)" % verb)
|
|
947
|
+
except Usage as exc:
|
|
948
|
+
sys.stderr.write("tezgah-design: %s\n" % exc)
|
|
949
|
+
return 2
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
if __name__ == "__main__":
|
|
953
|
+
sys.exit(main(sys.argv[1:]))
|