@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
package/bin/tezgah-docs
ADDED
|
@@ -0,0 +1,1199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""tezgah-docs [QUERY] - reach the docs/ page that answers a question.
|
|
3
|
+
|
|
4
|
+
Reads docs/index.json and prints the matching page paths, so an agent picks a
|
|
5
|
+
page instead of guessing and a script can assert the layer is intact.
|
|
6
|
+
|
|
7
|
+
tezgah-docs every page, one line each
|
|
8
|
+
tezgah-docs <words...> the pages matching every word of the query
|
|
9
|
+
tezgah-docs --json [QUERY] the matching entries as JSON
|
|
10
|
+
tezgah-docs --read QUERY print the first matching page in full
|
|
11
|
+
tezgah-docs --citations the citations that no longer show what they name,
|
|
12
|
+
the files with more unjudged citations than
|
|
13
|
+
docs/citations-baseline.json, the rule ledger's rows
|
|
14
|
+
and the code-derived inventories that no longer hold
|
|
15
|
+
(`--citations --update` rewrites the baseline)
|
|
16
|
+
tezgah-docs --clarity the clarity ratchet: per-file prose rule counts
|
|
17
|
+
against docs/clarity-baseline.json; 1 when a file
|
|
18
|
+
exceeds it (`--clarity --update` rewrites it)
|
|
19
|
+
|
|
20
|
+
The options above are options anywhere before `--`. An unknown `--word` is a
|
|
21
|
+
usage error before the first query word and a query word after it, so
|
|
22
|
+
`tezgah-docs why was --no-verify blocked --json` is a JSON query; after `--`
|
|
23
|
+
every word is part of the query.
|
|
24
|
+
|
|
25
|
+
Matching is word-based and case-insensitive against an entry's title, its
|
|
26
|
+
questions, its sources and its path; every word must match, so a second word
|
|
27
|
+
narrows. A query that matches nothing is the last resort of one TypeSafe Choice
|
|
28
|
+
question over the pages themselves; it answers `none` for a query no page
|
|
29
|
+
covers. When that question gives no judgement - unavailable (`docs-judge-off`,
|
|
30
|
+
the seam's `judge-off`, no credential) or a call that failed or came back
|
|
31
|
+
unreadable - the pages are ranked by shared words instead (BM25,
|
|
32
|
+
`hooks/tezgah_rank.py`) and the best few printed; a judged `none` is not
|
|
33
|
+
overruled. Exit codes: 0 with matches, 1 with none, 2 usage.
|
|
34
|
+
"""
|
|
35
|
+
import ast
|
|
36
|
+
import bisect
|
|
37
|
+
import glob
|
|
38
|
+
import io
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
import sys
|
|
43
|
+
import tokenize
|
|
44
|
+
|
|
45
|
+
HERE = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
46
|
+
sys.path.insert(0, os.path.join(HERE, "hooks"))
|
|
47
|
+
import tezgah_integrity as ti # noqa: E402
|
|
48
|
+
import tezgah_judge # noqa: E402
|
|
49
|
+
import tezgah_paths as tp # noqa: E402
|
|
50
|
+
import tezgah_embed # noqa: E402
|
|
51
|
+
import tezgah_clarity # noqa: E402
|
|
52
|
+
|
|
53
|
+
INDEX = os.path.join(HERE, "docs", "index.json")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def entries():
|
|
57
|
+
"""The index entries, or [] when the index is missing or malformed."""
|
|
58
|
+
try:
|
|
59
|
+
with open(INDEX, encoding="utf-8") as fh:
|
|
60
|
+
data = json.load(fh)
|
|
61
|
+
except (OSError, ValueError):
|
|
62
|
+
return []
|
|
63
|
+
pages = data.get("pages")
|
|
64
|
+
if not isinstance(pages, list):
|
|
65
|
+
return []
|
|
66
|
+
return [p for p in pages if isinstance(p, dict) and p.get("path")]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def blob(entry):
|
|
70
|
+
"""Everything a query is matched against, lowercased."""
|
|
71
|
+
parts = [str(entry.get("path") or ""), str(entry.get("title") or "")]
|
|
72
|
+
parts += [str(a) for a in (entry.get("answers") or [])]
|
|
73
|
+
parts += [str(s) for s in (entry.get("sources") or [])]
|
|
74
|
+
return " ".join(parts).lower()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def matches(query):
|
|
78
|
+
"""The entries every word of `query` appears in; [] for an empty query."""
|
|
79
|
+
words = query.lower().split()
|
|
80
|
+
if not words:
|
|
81
|
+
return []
|
|
82
|
+
return [e for e in entries() if all(w in blob(e) for w in words)]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- the citation audit -----------------------------------------------------
|
|
86
|
+
# A page's `path:line` is a promise a reader can open. `tests/test_docs.py`
|
|
87
|
+
# checks the half a script can (the file exists, the line is inside it) and says
|
|
88
|
+
# why it stops there: whether the line still shows the thing the sentence names
|
|
89
|
+
# is a judgement about prose, and a test that guessed at it would fail on
|
|
90
|
+
# rewording rather than on drift. That judgement is mechanical in one case - a
|
|
91
|
+
# citation that names a symbol whose body the range must lie in - and that case
|
|
92
|
+
# is where the drift actually lands, because the numbers were written against a
|
|
93
|
+
# revision of the file and nothing regenerates them when lines move under them.
|
|
94
|
+
# A gate, not only a report: CI runs `bin/tezgah-docs --citations` and `main()`
|
|
95
|
+
# exits 1 on a citation outside its symbol, on a file with more unjudged
|
|
96
|
+
# citations than `docs/citations-baseline.json` allows, and on a ledger or
|
|
97
|
+
# inventory row that does not hold. It reads docs/*.md and the comments and
|
|
98
|
+
# docstrings of the Python code files; a string literal (a test's fixture, a
|
|
99
|
+
# captured output) is data, not a citation, and is not read.
|
|
100
|
+
|
|
101
|
+
CODE_FILES = ("hooks/*.py", "bin/*", "statusline.py", "hosts/*/*.py",
|
|
102
|
+
"hosts/*/*.ts.in", "hosts/*/plugins/*.js", "tests/*.py")
|
|
103
|
+
STRING = re.compile(r'"""(?:.|\n)*?"""|\'\'\'(?:.|\n)*?\'\'\'')
|
|
104
|
+
TOP = re.compile(r"^(?:def|class)\s+(\w+)|^([A-Za-z_][A-Za-z0-9_]*)\s*(?::[^=]+)?=")
|
|
105
|
+
RUN = re.compile(r"((?:`[A-Za-z_][A-Za-z0-9_]*(?:\(\))?`\s*[/,]?\s*){1,5})$")
|
|
106
|
+
CITE = re.compile(r"`([\w.-]+(?:/[\w.-]+)+|[\w-]+\.\w+):(\d+)(?:-(\d+))?`"
|
|
107
|
+
r"|`(:(\d+)(?:-(\d+))?)`")
|
|
108
|
+
# The symbol written after the citation instead of before it, the way a table
|
|
109
|
+
# cell reads: `hooks/tezgah_policy.py:634-853` (`CORE`).
|
|
110
|
+
AFTER = re.compile(r"\s*\(`([A-Za-z_][A-Za-z0-9_]*)(?:\(\))?`\)")
|
|
111
|
+
# Any `path:line`, backticked or not: what CITE does not match of these (a
|
|
112
|
+
# bare token, a comma list `x.py:12,40`) is counted as unjudged, so a citation
|
|
113
|
+
# written in a shape the audit cannot read is still a number someone sees.
|
|
114
|
+
# Scanned as one token run and then shape-checked: the shape alternation as
|
|
115
|
+
# the scanner (nested `+` under a look-behind) cost a third of a second a pass.
|
|
116
|
+
LOOSE = re.compile(r"(?<![\w./:-])([\w./-]*\w):(\d+)(?:-(\d+))?(?!\w)")
|
|
117
|
+
LOOSE_SHAPE = re.compile(r"(?:[\w.-]+/)+[\w.-]*\w|[\w-]+\.\w+")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def loose(text):
|
|
121
|
+
"""The LOOSE matches whose path has a file's shape (a `/`, or `name.ext`)."""
|
|
122
|
+
return (m for m in LOOSE.finditer(text) if LOOSE_SHAPE.fullmatch(m.group(1)))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# A symbol anchor, `path::name` or `path::Class.method`: no line number, so an
|
|
126
|
+
# edit above or inside the symbol cannot move it. What can still go stale is the
|
|
127
|
+
# name, and the AST of the cited file answers that: a name it does not define
|
|
128
|
+
# is flagged like a line outside its symbol.
|
|
129
|
+
SYMCITE = re.compile(r"`([\w.-]+(?:/[\w.-]+)+|[\w-]+\.\w+)::"
|
|
130
|
+
r"([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)`")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def qualnames(path):
|
|
134
|
+
"""{dotted name: (start, end, method)} for every def and class of a Python
|
|
135
|
+
file, nested ones as `Class.method` / `outer.inner`, and its module-level
|
|
136
|
+
assignments; `method` is True for a def whose direct parent is a class.
|
|
137
|
+
None for a file that does not parse as Python."""
|
|
138
|
+
try:
|
|
139
|
+
tree = ast.parse(source(path))
|
|
140
|
+
except (SyntaxError, ValueError, UnicodeDecodeError):
|
|
141
|
+
return None
|
|
142
|
+
out = {}
|
|
143
|
+
|
|
144
|
+
def walk(body, prefix, in_class=False):
|
|
145
|
+
for node in body:
|
|
146
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
147
|
+
name = prefix + node.name
|
|
148
|
+
start = min([node.lineno] + [d.lineno for d in node.decorator_list])
|
|
149
|
+
out.setdefault(name, (start, node.end_lineno,
|
|
150
|
+
in_class and not isinstance(node, ast.ClassDef)))
|
|
151
|
+
walk(node.body, name + ".", isinstance(node, ast.ClassDef))
|
|
152
|
+
continue
|
|
153
|
+
if not prefix and isinstance(node, (ast.Assign, ast.AnnAssign)):
|
|
154
|
+
for target in getattr(node, "targets", None) or [node.target]:
|
|
155
|
+
if isinstance(target, ast.Name):
|
|
156
|
+
out.setdefault(target.id, (node.lineno, node.end_lineno, False))
|
|
157
|
+
for field in ("body", "orelse", "finalbody"):
|
|
158
|
+
walk(getattr(node, field, None) or [], prefix, in_class)
|
|
159
|
+
for handler in getattr(node, "handlers", None) or []:
|
|
160
|
+
walk(handler.body, prefix, in_class)
|
|
161
|
+
|
|
162
|
+
walk(tree.body, "")
|
|
163
|
+
return out
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def anchor_span(names, qual):
|
|
167
|
+
"""The (start, end, method) `qual` names in a qualnames() table, or None. A
|
|
168
|
+
name without a dot may also be the one method of that name, the way a test
|
|
169
|
+
pin names `test_x` inside its TestCase class; a def local to a function
|
|
170
|
+
never stands in, or a deleted top-level symbol would pass on a helper."""
|
|
171
|
+
if qual in names:
|
|
172
|
+
return names[qual]
|
|
173
|
+
if "." not in qual:
|
|
174
|
+
hits = [span for name, span in names.items()
|
|
175
|
+
if span[2] and name.rsplit(".", 1)[-1] == qual]
|
|
176
|
+
if len(hits) == 1:
|
|
177
|
+
return hits[0]
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
CITATION_BASELINE = os.path.join("docs", "citations-baseline.json")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def code_symbols(path):
|
|
185
|
+
"""{name: [(start, end)]} for this file's top-level defs, classes and
|
|
186
|
+
assignments. A triple-quoted body is masked first, so a line inside a
|
|
187
|
+
contract string cannot read as a definition and cut another one short."""
|
|
188
|
+
text = open(path, encoding="utf-8", errors="replace").read()
|
|
189
|
+
chars = list(text)
|
|
190
|
+
for m in STRING.finditer(text):
|
|
191
|
+
for i in range(m.start(), m.end()):
|
|
192
|
+
if chars[i] != "\n":
|
|
193
|
+
chars[i] = " "
|
|
194
|
+
lines = "".join(chars).splitlines()
|
|
195
|
+
starts = [(i, m.group(1) or m.group(2))
|
|
196
|
+
for i, line in enumerate(lines, 1) if (m := TOP.match(line))]
|
|
197
|
+
out = {}
|
|
198
|
+
for k, (start, name) in enumerate(starts):
|
|
199
|
+
end = starts[k + 1][0] - 1 if k + 1 < len(starts) else len(lines)
|
|
200
|
+
out.setdefault(name, []).append((start, end))
|
|
201
|
+
return out
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def code_files():
|
|
205
|
+
"""Every file CODE_FILES globs, relative to HERE, symlinks left out: every
|
|
206
|
+
bin/ script has a `.py` twin pointing at it so tests can import it, and
|
|
207
|
+
counting both made every bin symbol "ambiguous" - the audit (M-11a)
|
|
208
|
+
measured 0 stale citations reported while 25 were."""
|
|
209
|
+
out = []
|
|
210
|
+
for pattern in CODE_FILES:
|
|
211
|
+
for path in sorted(glob.glob(os.path.join(HERE, pattern))):
|
|
212
|
+
if os.path.isfile(path) and not os.path.islink(path):
|
|
213
|
+
out.append(os.path.relpath(path, HERE))
|
|
214
|
+
return out
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def symbols():
|
|
218
|
+
"""{name: (path, [(start, end)])} over every code file, for the names
|
|
219
|
+
exactly one file defines: an ambiguous name is skipped, not guessed at."""
|
|
220
|
+
seen = {}
|
|
221
|
+
for rel in code_files():
|
|
222
|
+
for name, spans in code_symbols(os.path.join(HERE, rel)).items():
|
|
223
|
+
seen.setdefault(name, []).append((rel, spans))
|
|
224
|
+
return {name: only[0] for name, only in seen.items() if len(only) == 1}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def is_python(path):
|
|
228
|
+
"""True for a `.py` file or a script whose first line names python."""
|
|
229
|
+
if path.endswith(".py"):
|
|
230
|
+
return True
|
|
231
|
+
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
232
|
+
return "python" in fh.readline()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def comment_text(path):
|
|
236
|
+
"""A Python file's comments and docstrings at their own offsets, every other
|
|
237
|
+
non-blank character turned into NUL, or None for a file that is not Python.
|
|
238
|
+
|
|
239
|
+
The offsets are kept so a match's line is the file's line, and the code is
|
|
240
|
+
NUL rather than blank so a symbol in one comment block never pairs with a
|
|
241
|
+
citation in the next one across the code between them. A docstring is a
|
|
242
|
+
string that is a statement of its own; a string inside an expression is a
|
|
243
|
+
value (a fixture, a captured output) and is blanked with the code. A file
|
|
244
|
+
holding no citation-shaped token at all is "": no comment of it could hold
|
|
245
|
+
one either, so it is neither tokenized nor masked."""
|
|
246
|
+
if not is_python(path):
|
|
247
|
+
return None
|
|
248
|
+
text = source(path)
|
|
249
|
+
if not (CITE.search(text) or SYMCITE.search(text) or next(loose(text), None)):
|
|
250
|
+
return ""
|
|
251
|
+
masked = re.sub(r"\S", "\0", text)
|
|
252
|
+
starts = [0]
|
|
253
|
+
for line in text.splitlines(keepends=True):
|
|
254
|
+
starts.append(starts[-1] + len(line))
|
|
255
|
+
try:
|
|
256
|
+
tokens = list(tokenize.generate_tokens(io.StringIO(text).readline))
|
|
257
|
+
except (tokenize.TokenError, SyntaxError):
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
def span(tok, skip=0):
|
|
261
|
+
return (starts[tok.start[0] - 1] + tok.start[1] + skip,
|
|
262
|
+
starts[tok.end[0] - 1] + tok.end[1])
|
|
263
|
+
|
|
264
|
+
lead = (tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT, tokenize.ENCODING)
|
|
265
|
+
real = [t for t in tokens if t.type not in (tokenize.NL, tokenize.COMMENT)]
|
|
266
|
+
kept = [span(t, 1) for t in tokens if t.type == tokenize.COMMENT]
|
|
267
|
+
kept += [span(t) for k, t in enumerate(real)
|
|
268
|
+
if t.type == tokenize.STRING and (k == 0 or real[k - 1].type in lead)
|
|
269
|
+
and k + 1 < len(real)
|
|
270
|
+
and real[k + 1].type in (tokenize.NEWLINE, tokenize.ENDMARKER)]
|
|
271
|
+
parts, at = [], 0
|
|
272
|
+
for lo, hi in sorted(kept):
|
|
273
|
+
parts += [masked[at:lo], text[lo:hi]]
|
|
274
|
+
at = hi
|
|
275
|
+
return "".join(parts) + masked[at:]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def sources():
|
|
279
|
+
"""(relpath, text) for every page under docs/ and every Python code file's
|
|
280
|
+
comments and docstrings (`comment_text`)."""
|
|
281
|
+
for path in sorted(glob.glob(os.path.join(HERE, "docs", "*.md"))):
|
|
282
|
+
yield os.path.relpath(path, HERE), source(path)
|
|
283
|
+
for rel in code_files():
|
|
284
|
+
text = comment_text(os.path.join(HERE, rel))
|
|
285
|
+
if text is not None:
|
|
286
|
+
yield rel, text
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def citations(known):
|
|
290
|
+
"""(flagged, judged, unjudged, invisible): the citations whose named symbol
|
|
291
|
+
does not contain the cited range, as (`file:line`, token, symbol file, low,
|
|
292
|
+
high), and the symbol anchors naming no symbol, as (`file:line`, token,
|
|
293
|
+
cited file, 0, 0); how many were judgeable; {file: count} of the ones that
|
|
294
|
+
were not; and how many of those CITE could not read at all (`LOOSE`).
|
|
295
|
+
|
|
296
|
+
A citation that writes its own path is judged against the symbol in that
|
|
297
|
+
file alone: a sentence naming `note_tool` beside `hooks/x.py:12` means the
|
|
298
|
+
`note_tool` in `hooks/x.py`, so a same-named symbol elsewhere is not the
|
|
299
|
+
thing being cited and the row is left unjudged rather than misjudged. A bare
|
|
300
|
+
file name (`tezgah_gate.py:12`) is that path when exactly one code file has
|
|
301
|
+
the name, and unjudged otherwise. A symbol anchor (`SYMCITE`) is always
|
|
302
|
+
judged: against the qualnames() of the file it names."""
|
|
303
|
+
names = {}
|
|
304
|
+
for rel in code_files():
|
|
305
|
+
names.setdefault(os.path.basename(rel), []).append(rel)
|
|
306
|
+
|
|
307
|
+
def resolve(path):
|
|
308
|
+
if "/" in path:
|
|
309
|
+
return path
|
|
310
|
+
found = names.get(path, ())
|
|
311
|
+
return found[0] if len(found) == 1 else None
|
|
312
|
+
|
|
313
|
+
flagged, judged, unjudged, invisible, tables = [], 0, {}, 0, {}
|
|
314
|
+
for rel, text in sources():
|
|
315
|
+
read = []
|
|
316
|
+
for m in CITE.finditer(text):
|
|
317
|
+
read.append(m.span())
|
|
318
|
+
# RUN is anchored at the end and five names fit in far fewer than
|
|
319
|
+
# 600 characters: slicing the whole page per citation was the cost.
|
|
320
|
+
pre = text[max(0, m.start() - 600):m.start()].rstrip().rstrip("(") \
|
|
321
|
+
.rstrip().rstrip(",")
|
|
322
|
+
run = RUN.search(pre.rstrip()) if pre.rstrip().endswith("`") else None
|
|
323
|
+
named = re.findall(r"`([A-Za-z_][A-Za-z0-9_]*)(?:\(\))?`",
|
|
324
|
+
run.group(1)) if run else []
|
|
325
|
+
after = AFTER.match(text, m.end())
|
|
326
|
+
if not named and after:
|
|
327
|
+
named = [after.group(1)]
|
|
328
|
+
cited = resolve(m.group(1)) if m.group(1) else None
|
|
329
|
+
spans, where = [], None
|
|
330
|
+
for name in named:
|
|
331
|
+
hit = known.get(name)
|
|
332
|
+
if not hit or (m.group(1) and hit[0] != cited):
|
|
333
|
+
continue
|
|
334
|
+
where, spans = hit[0], spans + hit[1]
|
|
335
|
+
if not spans:
|
|
336
|
+
unjudged[rel] = unjudged.get(rel, 0) + 1
|
|
337
|
+
continue
|
|
338
|
+
lo = int(m.group(2) or m.group(5))
|
|
339
|
+
hi = int(m.group(3) or m.group(6) or lo)
|
|
340
|
+
low, high = min(s for s, _ in spans), max(e for _, e in spans)
|
|
341
|
+
judged += 1
|
|
342
|
+
if lo >= low and hi <= high:
|
|
343
|
+
continue
|
|
344
|
+
line = text.count("\n", 0, m.start()) + 1
|
|
345
|
+
flagged.append(("%s:%d" % (rel, line), m.group(0), where, low, high))
|
|
346
|
+
for m in SYMCITE.finditer(text):
|
|
347
|
+
judged += 1
|
|
348
|
+
path = resolve(m.group(1))
|
|
349
|
+
full = os.path.join(HERE, path) if path else None
|
|
350
|
+
if full and os.path.isfile(full):
|
|
351
|
+
if path not in tables:
|
|
352
|
+
tables[path] = qualnames(full) or {}
|
|
353
|
+
if anchor_span(tables[path], m.group(2)):
|
|
354
|
+
continue
|
|
355
|
+
line = text.count("\n", 0, m.start()) + 1
|
|
356
|
+
flagged.append(("%s:%d" % (rel, line), m.group(0), path or m.group(1), 0, 0))
|
|
357
|
+
starts = [a for a, _ in read]
|
|
358
|
+
for m in loose(text):
|
|
359
|
+
k = bisect.bisect_right(starts, m.start()) - 1
|
|
360
|
+
if k >= 0 and m.end() <= read[k][1]:
|
|
361
|
+
continue
|
|
362
|
+
path = resolve(m.group(1))
|
|
363
|
+
if path and os.path.isfile(os.path.join(HERE, path)):
|
|
364
|
+
invisible += 1
|
|
365
|
+
unjudged[rel] = unjudged.get(rel, 0) + 1
|
|
366
|
+
return flagged, judged, unjudged, invisible
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def ratchet_baseline():
|
|
370
|
+
"""The committed {file: {"unjudged": n}} the ratchet compares against."""
|
|
371
|
+
return tezgah_clarity.load(os.path.join(HERE, CITATION_BASELINE))
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def ratchet_drift(unjudged, baseline):
|
|
375
|
+
"""[(file, count, allowed)] for each file whose unjudged count is not its
|
|
376
|
+
baseline (absent: 0). Over is a new citation nobody can judge; under is a
|
|
377
|
+
fixed one whose lower count was not written down, which would let the next
|
|
378
|
+
new one in silently. Read-only: CI runs `--citations` on a checkout, so the
|
|
379
|
+
baseline moves only by `--citations --update`, never as a side effect."""
|
|
380
|
+
allowed = {rel: row.get("unjudged", 0) for rel, row in baseline.items()}
|
|
381
|
+
return [(rel, unjudged.get(rel, 0), allowed.get(rel, 0))
|
|
382
|
+
for rel in sorted(set(unjudged) | set(allowed))
|
|
383
|
+
if unjudged.get(rel, 0) != allowed.get(rel, 0)]
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# --- the rule ledger --------------------------------------------------------
|
|
387
|
+
# Every refusal has a rule name and an origin, and until now both lived only as
|
|
388
|
+
# prose spread through docs/gate.md: the rule name in the section, the incident
|
|
389
|
+
# in a sentence inside it. The table under that page's `## Rule provenance` is
|
|
390
|
+
# that record moved to where a machine reads it - one row per refused rule, with
|
|
391
|
+
# the incident that produced it, where that incident is written down, and the
|
|
392
|
+
# test that guards it. This check is the
|
|
393
|
+
# citation audit's sibling and rides the same `--citations` pass, because both
|
|
394
|
+
# answer "is what the page promises still true" and both are re-run after a
|
|
395
|
+
# change to the module a page documents.
|
|
396
|
+
#
|
|
397
|
+
# Two things are refused, and both are drift a reader cannot see: a rule name
|
|
398
|
+
# the gate can still produce that has no row (a rule landed, or was renamed,
|
|
399
|
+
# with no incident and no case written down), and a row whose `pin` names a test
|
|
400
|
+
# that does not exist (the case was deleted or renamed and the row goes on
|
|
401
|
+
# naming it). Like the rest of the pass this is a gate: the ledger is small and
|
|
402
|
+
# every row is checkable, so a wrong one is a failure.
|
|
403
|
+
#
|
|
404
|
+
# The rule names are read off the source rather than restated here, the way the
|
|
405
|
+
# audit reads a symbol: the gate's rules are the `_deny(session_id, "<rule>",
|
|
406
|
+
# ...)` calls in hooks/tezgah_gate.py, and the Stop rule's triggers are the
|
|
407
|
+
# classes `_stop_block` returns - including the four `_shape_block` and the six
|
|
408
|
+
# `_evidence_block` hand back, which is why those two functions are read too.
|
|
409
|
+
|
|
410
|
+
GATE = os.path.join(HERE, "hooks", "tezgah_gate.py")
|
|
411
|
+
STOP = os.path.join(HERE, "hooks", "tezgah_integrity.py")
|
|
412
|
+
LEDGER = os.path.join(HERE, "docs", "gate.md")
|
|
413
|
+
LEDGER_COLUMNS = ("rule", "incident", "evidence", "pin")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def source(path):
|
|
417
|
+
"""A module's text, read once (closed, so the suite sees no leak warning)."""
|
|
418
|
+
with open(path, encoding="utf-8") as fh:
|
|
419
|
+
return fh.read()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def returned_classes(node):
|
|
423
|
+
"""The string classes a decision function returns as its first element.
|
|
424
|
+
|
|
425
|
+
The class is the first element of the returned tuple (`("check failed",
|
|
426
|
+
"…")`), and a `(None, None)` or a name (`return shaped`) is not one."""
|
|
427
|
+
out = set()
|
|
428
|
+
for sub in ast.walk(node):
|
|
429
|
+
if not isinstance(sub, ast.Return) or sub.value is None:
|
|
430
|
+
continue
|
|
431
|
+
first = sub.value.elts[0] if isinstance(sub.value, ast.Tuple) \
|
|
432
|
+
and sub.value.elts else sub.value
|
|
433
|
+
if isinstance(first, ast.Constant) and isinstance(first.value, str):
|
|
434
|
+
out.add(first.value)
|
|
435
|
+
return out
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def gate_rules(text=None):
|
|
439
|
+
"""The rule names `_deny` writes into the ledger, read off the source."""
|
|
440
|
+
tree = ast.parse(source(GATE) if text is None else text, filename=GATE)
|
|
441
|
+
out = set()
|
|
442
|
+
for node in ast.walk(tree):
|
|
443
|
+
if not (isinstance(node, ast.Call)
|
|
444
|
+
and isinstance(node.func, ast.Name) and node.func.id == "_deny"):
|
|
445
|
+
continue
|
|
446
|
+
if len(node.args) > 1 and isinstance(node.args[1], ast.Constant) \
|
|
447
|
+
and isinstance(node.args[1].value, str):
|
|
448
|
+
out.add(node.args[1].value)
|
|
449
|
+
return out
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def stop_triggers():
|
|
453
|
+
"""The classes `_stop_block` can return, the shape and evidence halves
|
|
454
|
+
included."""
|
|
455
|
+
tree = ast.parse(source(STOP))
|
|
456
|
+
out = set()
|
|
457
|
+
for node in ast.walk(tree):
|
|
458
|
+
if isinstance(node, ast.FunctionDef) and node.name in (
|
|
459
|
+
"_stop_block", "_shape_block", "_evidence_block"):
|
|
460
|
+
out |= returned_classes(node)
|
|
461
|
+
return out
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def ledger_rows():
|
|
465
|
+
"""The rule-provenance rows in docs/gate.md, as {column: value} dicts."""
|
|
466
|
+
return table_rows(LEDGER, LEDGER_COLUMNS)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def table_rows(path, columns):
|
|
470
|
+
"""The rows of the table in `path` whose header is `columns`, as {column:
|
|
471
|
+
value} dicts, or [] when the page is missing.
|
|
472
|
+
|
|
473
|
+
The header row is the table's own signature, so no other table on the page
|
|
474
|
+
is read; a cell written as a code span (`` `explorer` ``) loses its backticks here."""
|
|
475
|
+
try:
|
|
476
|
+
with open(path, encoding="utf-8") as fh:
|
|
477
|
+
lines = fh.read().splitlines()
|
|
478
|
+
except OSError:
|
|
479
|
+
return []
|
|
480
|
+
rows, header = [], False
|
|
481
|
+
for line in lines:
|
|
482
|
+
if not line.lstrip().startswith("|"):
|
|
483
|
+
header = False
|
|
484
|
+
continue
|
|
485
|
+
cells = [c.strip().strip("`") for c in line.strip().strip("|").split("|")]
|
|
486
|
+
if tuple(cells) == columns:
|
|
487
|
+
header = True
|
|
488
|
+
continue
|
|
489
|
+
if not header or all(c and set(c) <= set("-") for c in cells):
|
|
490
|
+
continue
|
|
491
|
+
rows.append(dict(zip(columns, cells)))
|
|
492
|
+
return rows
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def pin_exists(pin):
|
|
496
|
+
"""True when `pin` names a test that exists, as `<file>.py::<test_name>`."""
|
|
497
|
+
path, sep, name = pin.partition("::")
|
|
498
|
+
if not sep or not name:
|
|
499
|
+
return False
|
|
500
|
+
full = os.path.join(HERE, path)
|
|
501
|
+
if not os.path.isfile(full):
|
|
502
|
+
return False
|
|
503
|
+
try:
|
|
504
|
+
with open(full, encoding="utf-8", errors="replace") as fh:
|
|
505
|
+
text = fh.read()
|
|
506
|
+
except OSError:
|
|
507
|
+
return False
|
|
508
|
+
return re.search(r"^\s*def %s\(" % re.escape(name), text, re.M) is not None
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def ledger_failures(rows):
|
|
512
|
+
"""The two ways the record lies: a rule with no row, a pin that names no
|
|
513
|
+
test. An empty list is the record being true."""
|
|
514
|
+
out = []
|
|
515
|
+
known = {row["rule"] for row in rows}
|
|
516
|
+
for name in sorted(gate_rules() | stop_triggers()):
|
|
517
|
+
if name not in known:
|
|
518
|
+
out.append("rule `%s` has no provenance row in docs/gate.md" % name)
|
|
519
|
+
for row in rows:
|
|
520
|
+
if not pin_exists(row["pin"]):
|
|
521
|
+
out.append("row `%s` names no test that exists: %s"
|
|
522
|
+
% (row["rule"], row["pin"]))
|
|
523
|
+
return out
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# --- the inventories --------------------------------------------------------
|
|
527
|
+
# Three lists a page keeps by hand, each read off the code by AST rather than
|
|
528
|
+
# restated in a literal here (one definition per concept): the gate's rules in
|
|
529
|
+
# the order `decision` checks them, the programs that call the judge seam, and
|
|
530
|
+
# the kill switches the code reads. They ride the `--citations` pass for the
|
|
531
|
+
# ledger's reason: each answers "is what the page promises still true".
|
|
532
|
+
|
|
533
|
+
JUDGE_PAGE = os.path.join(HERE, "docs", "judge.md")
|
|
534
|
+
CONTRACT_SKILL = os.path.join(HERE, "skills", "tezgah-contract", "SKILL.md")
|
|
535
|
+
SETUP = os.path.join(HERE, "bin", "tezgah-setup")
|
|
536
|
+
NUMBERS = ("zero", "one", "two", "three", "four", "five", "six", "seven",
|
|
537
|
+
"eight", "nine", "ten")
|
|
538
|
+
# ADR 007 (2026-10-05) leaves these unclassified: two `off()` names the CORE
|
|
539
|
+
# kill-switch paragraph does not list and two opt-in markers read with
|
|
540
|
+
# `armed()`. Until the owner decides whether they are rule switches the check
|
|
541
|
+
# pins CORE against the code and carries these as the one explicit exception.
|
|
542
|
+
UNCLASSIFIED_SWITCHES = frozenset((
|
|
543
|
+
"agents-off", "update-check-off", "taste-on", "skill-suggest-on"))
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def rule_sites(text=None):
|
|
547
|
+
"""[(rule, first line, last line, switches, tool classes)] for every
|
|
548
|
+
`return _deny(session_id, "<rule>", ...)` statement in `decision`, in source
|
|
549
|
+
order. A switch is an enclosing `if not off("<name>")` guard, a tool class
|
|
550
|
+
an enclosing `t in <NAME>` test; the line span is the statement's own, which
|
|
551
|
+
is what tests/neuter_matrix.py replaces to revert the rule (it passes HEAD's
|
|
552
|
+
`text`, the tree it mutates)."""
|
|
553
|
+
tree = ast.parse(source(GATE) if text is None else text, filename=GATE)
|
|
554
|
+
body = next(n for n in tree.body
|
|
555
|
+
if isinstance(n, ast.FunctionDef) and n.name == "decision").body
|
|
556
|
+
out = []
|
|
557
|
+
|
|
558
|
+
def guards(test):
|
|
559
|
+
switches, tools = [], []
|
|
560
|
+
for node in ast.walk(test):
|
|
561
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not) \
|
|
562
|
+
and isinstance(node.operand, ast.Call) \
|
|
563
|
+
and getattr(node.operand.func, "id", "") == "off":
|
|
564
|
+
switches.append(node.operand.args[0].value)
|
|
565
|
+
if isinstance(node, ast.Compare) and getattr(node.left, "id", "") == "t" \
|
|
566
|
+
and isinstance(node.ops[0], ast.In):
|
|
567
|
+
tools += [n.id for n in ast.walk(node.comparators[0])
|
|
568
|
+
if isinstance(n, ast.Name)]
|
|
569
|
+
return switches, tools
|
|
570
|
+
|
|
571
|
+
def walk(stmts, switches, tools):
|
|
572
|
+
for stmt in stmts:
|
|
573
|
+
if isinstance(stmt, ast.If):
|
|
574
|
+
more, classes = guards(stmt.test)
|
|
575
|
+
walk(stmt.body, switches + more, tools + classes)
|
|
576
|
+
walk(stmt.orelse, switches, tools)
|
|
577
|
+
elif isinstance(stmt, ast.Return) and isinstance(stmt.value, ast.Call) \
|
|
578
|
+
and getattr(stmt.value.func, "id", "") == "_deny":
|
|
579
|
+
out.append((stmt.value.args[1].value, stmt.lineno, stmt.end_lineno,
|
|
580
|
+
tuple(dict.fromkeys(switches)),
|
|
581
|
+
tuple(dict.fromkeys(tools))))
|
|
582
|
+
else:
|
|
583
|
+
for field in ("body", "orelse", "finalbody", "handlers", "cases"):
|
|
584
|
+
walk(getattr(stmt, field, []), switches, tools)
|
|
585
|
+
walk(body, [], [])
|
|
586
|
+
return out
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def rule_site_failures(text=None):
|
|
590
|
+
"""A rule `_deny` names that `rule_sites` cannot place - a call outside a
|
|
591
|
+
`return _deny(...)` statement (`out = _deny(...); return out`) or outside
|
|
592
|
+
`decision` - has no line span for the neuter matrix and no place in the
|
|
593
|
+
rule order, so it is refused here instead of vanishing from both."""
|
|
594
|
+
placed = {site[0] for site in rule_sites(text)}
|
|
595
|
+
return ["rule `%s` is denied somewhere `rule_sites` cannot read: return "
|
|
596
|
+
"`_deny(...)` directly inside `decision`" % name
|
|
597
|
+
for name in sorted(gate_rules(text) - placed)]
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def sections(text, parent, level="### "):
|
|
601
|
+
"""[(heading, body)] of the `level` headings under the `## <parent>` one."""
|
|
602
|
+
out, inside = [], False
|
|
603
|
+
for line in text.splitlines():
|
|
604
|
+
if line.startswith("## "):
|
|
605
|
+
inside = line.startswith("## " + parent)
|
|
606
|
+
elif inside and line.startswith(level):
|
|
607
|
+
out.append((line[len(level):], []))
|
|
608
|
+
elif inside and out:
|
|
609
|
+
out[-1][1].append(line)
|
|
610
|
+
return [(head, "\n".join(body)) for head, body in out]
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def rule_order_failures(text=None):
|
|
614
|
+
"""The ways docs/gate.md's rule list disagrees with `decision`: a rule with
|
|
615
|
+
no `###` heading, headings in another order than the rules' first deny
|
|
616
|
+
sites, and a rule section that does not name a switch guarding it. A
|
|
617
|
+
heading is matched to the rule its first word starts with (`Language` is
|
|
618
|
+
`lang`); one that matches none (`Nudge`, no `_deny`) is not a rule."""
|
|
619
|
+
sites = rule_sites()
|
|
620
|
+
order, switches = [], {}
|
|
621
|
+
for rule, _lo, _hi, guard, _tools in sites:
|
|
622
|
+
if rule not in order:
|
|
623
|
+
order.append(rule)
|
|
624
|
+
switches.setdefault(rule, set()).update(guard)
|
|
625
|
+
headed = []
|
|
626
|
+
for head, body in sections(source(LEDGER) if text is None else text,
|
|
627
|
+
"The rules, in the order"):
|
|
628
|
+
word = head.split()[0].lower() if head.split() else ""
|
|
629
|
+
rule = next((r for r in order if word.startswith(r)), None)
|
|
630
|
+
if rule:
|
|
631
|
+
headed.append((rule, body))
|
|
632
|
+
out = ["rule `%s` has no `###` heading in docs/gate.md's rule list" % r
|
|
633
|
+
for r in order if r not in [h for h, _ in headed]]
|
|
634
|
+
if [h for h, _ in headed] != [r for r in order if r in dict(headed)]:
|
|
635
|
+
out.append("docs/gate.md heads the rules in the order %s; `decision` "
|
|
636
|
+
"checks them in the order %s"
|
|
637
|
+
% (", ".join(h for h, _ in headed), ", ".join(order)))
|
|
638
|
+
for rule, body in headed:
|
|
639
|
+
for name in sorted(switches[rule]):
|
|
640
|
+
if "`%s`" % name not in body:
|
|
641
|
+
out.append("docs/gate.md's `%s` section does not name its switch "
|
|
642
|
+
"`%s`" % (rule, name))
|
|
643
|
+
return out
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
_PROGRAMS = {}
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def python_programs():
|
|
650
|
+
"""The Python programs under hooks/ and bin/ that call `off`, `armed` or the
|
|
651
|
+
judge seam - the only ones the two checks below read - relative to HERE,
|
|
652
|
+
each with its parsed tree; parsed once per HERE."""
|
|
653
|
+
if HERE not in _PROGRAMS:
|
|
654
|
+
out = []
|
|
655
|
+
for rel in code_files():
|
|
656
|
+
path = os.path.join(HERE, rel)
|
|
657
|
+
if not rel.startswith(("hooks/", "bin/")) or not is_python(path):
|
|
658
|
+
continue
|
|
659
|
+
text = source(path)
|
|
660
|
+
if "off(" in text or "armed(" in text or "tezgah_judge" in text:
|
|
661
|
+
out.append((rel, ast.parse(text, filename=path)))
|
|
662
|
+
_PROGRAMS[HERE] = out
|
|
663
|
+
return _PROGRAMS[HERE]
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def judge_callers():
|
|
667
|
+
"""The programs that reach `tezgah_judge.ask` (called, or handed on as the
|
|
668
|
+
route's `ask`), sorted."""
|
|
669
|
+
return sorted(rel for rel, tree in python_programs()
|
|
670
|
+
if "tezgah_judge" in source(os.path.join(HERE, rel))
|
|
671
|
+
and any(isinstance(n, ast.Attribute) and n.attr == "ask"
|
|
672
|
+
and getattr(n.value, "id", "") == "tezgah_judge"
|
|
673
|
+
for n in ast.walk(tree)))
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def judge_caller_failures(text=None):
|
|
677
|
+
"""docs/judge.md against `judge_callers()`: the callers table names every
|
|
678
|
+
program, and the count in the page title and the table heading is right."""
|
|
679
|
+
text = source(JUDGE_PAGE) if text is None else text
|
|
680
|
+
callers = judge_callers()
|
|
681
|
+
word = NUMBERS[len(callers)] if len(callers) < len(NUMBERS) else str(len(callers))
|
|
682
|
+
table = re.search(r"^## The (\w+) callers\n(.*?)(?=^## )", text, re.M | re.S)
|
|
683
|
+
if not table:
|
|
684
|
+
return ["docs/judge.md has no `## The <n> callers` section"]
|
|
685
|
+
out = ["docs/judge.md's callers table has no row for `%s`" % rel
|
|
686
|
+
for rel in callers if "| `%s` |" % rel not in table.group(2)]
|
|
687
|
+
if table.group(1) != word:
|
|
688
|
+
out.append("docs/judge.md heads %s callers; %s programs call the seam"
|
|
689
|
+
% (table.group(1), word))
|
|
690
|
+
if "its %s callers" % word not in text.splitlines()[0]:
|
|
691
|
+
out.append("docs/judge.md's title does not say its %s callers" % word)
|
|
692
|
+
return out
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def code_switches():
|
|
696
|
+
"""({off names}, {armed names}): the first argument of every `off(...)` and
|
|
697
|
+
`armed(...)` call under hooks/ and bin/, a module-level string constant
|
|
698
|
+
(`armed(ARM)`) resolved to its value."""
|
|
699
|
+
found = {"off": set(), "armed": set()}
|
|
700
|
+
for _rel, tree in python_programs():
|
|
701
|
+
consts = {t.id: n.value.value for n in tree.body if isinstance(n, ast.Assign)
|
|
702
|
+
and isinstance(n.value, ast.Constant) and isinstance(n.value.value, str)
|
|
703
|
+
for t in n.targets if isinstance(t, ast.Name)}
|
|
704
|
+
for node in ast.walk(tree):
|
|
705
|
+
if not isinstance(node, ast.Call) or not node.args:
|
|
706
|
+
continue
|
|
707
|
+
name = getattr(node.func, "id", None) or getattr(node.func, "attr", None)
|
|
708
|
+
arg = node.args[0]
|
|
709
|
+
value = arg.value if isinstance(arg, ast.Constant) else consts.get(
|
|
710
|
+
getattr(arg, "id", None))
|
|
711
|
+
if name in found and isinstance(value, str):
|
|
712
|
+
found[name].add(value)
|
|
713
|
+
return found["off"], found["armed"]
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def switch_names(text):
|
|
717
|
+
"""The `-off`/`.off` names a kill-switch paragraph spells in backticks."""
|
|
718
|
+
return {n for n in re.findall(r"`([^`\s]+)`", text) if n.endswith(("-off", ".off"))}
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def core_switches():
|
|
722
|
+
"""The switches CORE's `**Kill switches:**` paragraph names."""
|
|
723
|
+
import tezgah_policy
|
|
724
|
+
core = tezgah_policy.CORE
|
|
725
|
+
return switch_names(core[core.index("**Kill switches:**"):].split("\n\n")[0])
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def switch_failures(core=None):
|
|
729
|
+
"""The kill-switch inventory against the code: every CORE name is read by
|
|
730
|
+
an `off()` call, every name the code reads is CORE or one of the
|
|
731
|
+
UNCLASSIFIED_SWITCHES, the contract skill lists CORE exactly, and
|
|
732
|
+
bin/tezgah-setup's OFF_SWITCH_NAMES (what an uninstall sweeps) is every
|
|
733
|
+
`off()` name."""
|
|
734
|
+
core = core_switches() if core is None else core
|
|
735
|
+
off, armed = code_switches()
|
|
736
|
+
out = ["CORE names `%s`, which no `off()` call reads" % n for n in sorted(core - off)]
|
|
737
|
+
out += ["the code reads `%s`, which neither CORE nor the unclassified set names"
|
|
738
|
+
% n for n in sorted((off | armed) - core - UNCLASSIFIED_SWITCHES)]
|
|
739
|
+
out += ["the unclassified switch `%s` is no longer read by the code" % n
|
|
740
|
+
for n in sorted(UNCLASSIFIED_SWITCHES - off - armed)]
|
|
741
|
+
skill = source(CONTRACT_SKILL)
|
|
742
|
+
block = skill[skill.index("## Kill switches"):].split("\n## ")[0]
|
|
743
|
+
if switch_names(block) != core:
|
|
744
|
+
out.append("skills/tezgah-contract/SKILL.md lists %s; CORE lists %s"
|
|
745
|
+
% (sorted(switch_names(block)), sorted(core)))
|
|
746
|
+
tree = ast.parse(source(SETUP), filename=SETUP)
|
|
747
|
+
names = next((ast.literal_eval(n.value) for n in tree.body if isinstance(n, ast.Assign)
|
|
748
|
+
and getattr(n.targets[0], "id", "") == "OFF_SWITCH_NAMES"), ())
|
|
749
|
+
if set(names) != off:
|
|
750
|
+
out.append("bin/tezgah-setup's OFF_SWITCH_NAMES lacks %s and adds %s"
|
|
751
|
+
% (sorted(off - set(names)), sorted(set(names) - off)))
|
|
752
|
+
return out
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
# --- the enforcement matrix -------------------------------------------------
|
|
756
|
+
# Every always-on CORE rule against what enforces it: a gate rule or a Stop
|
|
757
|
+
# class that refuses, a report-only shape flag that only records, or none. A
|
|
758
|
+
# link is read off the code wherever the code states one - an enforcer named
|
|
759
|
+
# like the rule's CORE_RULES key, or guarded by a switch whose `switches()`
|
|
760
|
+
# branch drops the rule's paragraph - and HAND_LINKS carries the few it cannot
|
|
761
|
+
# state. Each enforcer also carries its guards, the enclosing `if` tests that
|
|
762
|
+
# can switch it off, so a rule whose every refusal vanishes while its text stays
|
|
763
|
+
# reads `partial` rather than `enforced`. A rule with no refusing enforcer, or
|
|
764
|
+
# one whose enforcers cover only part of it, has a row in docs/contract.md's
|
|
765
|
+
# table saying why (or "no decision").
|
|
766
|
+
|
|
767
|
+
CONTEXT = os.path.join(HERE, "hooks", "tezgah_context.py")
|
|
768
|
+
STOP_HOOK = os.path.join(HERE, "hooks", "projects-stop.py")
|
|
769
|
+
CONTRACT_PAGE = os.path.join(HERE, "docs", "contract.md")
|
|
770
|
+
PROSE_ONLY_COLUMNS = ("rule", "decision")
|
|
771
|
+
# `explorer` refuses a grep-only subagent and names the code-graph tools
|
|
772
|
+
# (EXPLORE_DENY) under no switch; `retry` is the loop rule's "three attempts is
|
|
773
|
+
# the ceiling" (RETRY_CEILING) but sits under `verify-off`, which drops only the
|
|
774
|
+
# integrity paragraph; `placating opener` enforces the Deliver paragraph's
|
|
775
|
+
# "Never placate" under the output-shape rule's switch; the shape flags
|
|
776
|
+
# (`flag:*`) measure the shape `_shape_block` enforces for the output rule, but
|
|
777
|
+
# are recorded on every judged reply, under the Stop hook's switch only.
|
|
778
|
+
HAND_LINKS = {"gate:explorer": "graph", "gate:retry": "loop",
|
|
779
|
+
"stop:placating opener": "fidelity", "flag:*": "adhd"}
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def _function(path, name):
|
|
783
|
+
"""The top-level `def name` of the Python file at `path`."""
|
|
784
|
+
tree = ast.parse(source(path), filename=path)
|
|
785
|
+
return next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == name)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def _switch_words(node):
|
|
789
|
+
"""The switches a guard expression reads: `off("<name>")` calls and
|
|
790
|
+
`"<mark>" in marks` tests."""
|
|
791
|
+
out = []
|
|
792
|
+
for n in ast.walk(node):
|
|
793
|
+
if isinstance(n, ast.Call) and getattr(n.func, "id", "") == "off" and n.args \
|
|
794
|
+
and isinstance(n.args[0], ast.Constant):
|
|
795
|
+
out.append(n.args[0].value)
|
|
796
|
+
elif isinstance(n, ast.Compare) and isinstance(n.left, ast.Constant) \
|
|
797
|
+
and isinstance(n.left.value, str):
|
|
798
|
+
out.append(n.left.value)
|
|
799
|
+
return out
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def switch_drops():
|
|
803
|
+
"""{switch or repo mark: {CORE_RULES key}}, read off
|
|
804
|
+
`tezgah_context.switches`: each `if` branch's switches and the keys its
|
|
805
|
+
body `drop.add`s."""
|
|
806
|
+
out = {}
|
|
807
|
+
for node in ast.walk(_function(CONTEXT, "switches")):
|
|
808
|
+
if isinstance(node, ast.If):
|
|
809
|
+
keys = {c.args[0].value for s in node.body for c in ast.walk(s)
|
|
810
|
+
if isinstance(c, ast.Call) and getattr(c.func, "attr", "") == "add"}
|
|
811
|
+
for name in _switch_words(node.test):
|
|
812
|
+
out.setdefault(name, set()).update(keys)
|
|
813
|
+
return out
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def core_rules(core=None):
|
|
817
|
+
"""[(key, label)] for every CORE paragraph opening with a bold head, in
|
|
818
|
+
order, the kill-switch list left out. The label is the policy text's own;
|
|
819
|
+
the key is its `tezgah_context.CORE_RULES` id (None when that table lacks it)."""
|
|
820
|
+
import tezgah_policy
|
|
821
|
+
core = tezgah_policy.CORE if core is None else core
|
|
822
|
+
table = next(n.value for n in ast.parse(source(CONTEXT), filename=CONTEXT).body
|
|
823
|
+
if isinstance(n, ast.Assign) and getattr(n.targets[0], "id", "") == "CORE_RULES")
|
|
824
|
+
keys = {label: key for key, label in ast.literal_eval(table)}
|
|
825
|
+
out = []
|
|
826
|
+
for para in core.split("\n\n"):
|
|
827
|
+
m = re.match(r"\s*(\*\*(.+?)\*\*)", para)
|
|
828
|
+
if m and not m.group(2).startswith("Kill switches"):
|
|
829
|
+
out.append((keys.get(m.group(1)), m.group(2)))
|
|
830
|
+
return out
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def _guarded_returns(func, funcs, pick):
|
|
834
|
+
"""[(name, switches, conditions)] for every `return` in `func` that `pick`
|
|
835
|
+
names, with what its enclosing `if` tests guard it by. A conjunct of a test
|
|
836
|
+
that reads a switch (`_switch_words`, or a call to a function in `funcs`
|
|
837
|
+
that reads one: `_adhd_armed`) is a switch guard; a conjunct that names no
|
|
838
|
+
argument or local of `func` is a condition on the environment (`reply_lang()
|
|
839
|
+
== "tr"`); a conjunct reading the call's own data is the trigger itself and
|
|
840
|
+
is not a guard. The switch a whole hook checks first (`pretooluse-off`, the
|
|
841
|
+
Stop hook's `verify-off`) is outside these functions and is not counted."""
|
|
842
|
+
own = {a.arg for a in func.args.args + func.args.kwonlyargs} | {
|
|
843
|
+
n.id for n in ast.walk(func) if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Store)}
|
|
844
|
+
out = []
|
|
845
|
+
|
|
846
|
+
def split(test):
|
|
847
|
+
parts = test.values if isinstance(test, ast.BoolOp) and isinstance(test.op, ast.And) \
|
|
848
|
+
else [test]
|
|
849
|
+
switches, conditions = [], []
|
|
850
|
+
for part in parts:
|
|
851
|
+
words = _switch_words(part) + [
|
|
852
|
+
w for n in ast.walk(part) if isinstance(n, ast.Call)
|
|
853
|
+
and getattr(n.func, "id", "") in funcs for w in _switch_words(funcs[n.func.id])]
|
|
854
|
+
if words:
|
|
855
|
+
switches += words
|
|
856
|
+
elif not any(isinstance(n, ast.Name) and n.id in own for n in ast.walk(part)):
|
|
857
|
+
conditions.append(ast.unparse(part))
|
|
858
|
+
return switches, conditions
|
|
859
|
+
|
|
860
|
+
def walk(stmts, switches, conditions):
|
|
861
|
+
for stmt in stmts:
|
|
862
|
+
if isinstance(stmt, ast.If):
|
|
863
|
+
more, extra = split(stmt.test)
|
|
864
|
+
walk(stmt.body, switches + more, conditions + extra)
|
|
865
|
+
walk(stmt.orelse, switches, conditions)
|
|
866
|
+
elif isinstance(stmt, ast.Return):
|
|
867
|
+
out.extend((name, tuple(dict.fromkeys(switches)),
|
|
868
|
+
tuple(dict.fromkeys(conditions))) for name in pick(stmt))
|
|
869
|
+
else:
|
|
870
|
+
for field in ("body", "orelse", "finalbody", "handlers"):
|
|
871
|
+
walk(getattr(stmt, field, []), switches, conditions)
|
|
872
|
+
walk(func.body, [], [])
|
|
873
|
+
return out
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def stop_sites():
|
|
877
|
+
"""[(class, switches, conditions)] for every class `_shape_block`,
|
|
878
|
+
`_stop_block` and `_evidence_block` return (`_guarded_returns`)."""
|
|
879
|
+
tree = ast.parse(source(STOP), filename=STOP)
|
|
880
|
+
funcs = {n.name: n for n in tree.body if isinstance(n, ast.FunctionDef)}
|
|
881
|
+
return [site for name in ("_shape_block", "_stop_block", "_evidence_block")
|
|
882
|
+
for site in _guarded_returns(funcs[name], funcs,
|
|
883
|
+
lambda r: sorted(returned_classes(r)))]
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def gate_sites():
|
|
887
|
+
"""[(rule, switches, conditions)] for every `return _deny(...)` in
|
|
888
|
+
`decision` (`_guarded_returns`)."""
|
|
889
|
+
def pick(ret):
|
|
890
|
+
call = ret.value
|
|
891
|
+
return [call.args[1].value] if isinstance(call, ast.Call) \
|
|
892
|
+
and getattr(call.func, "id", "") == "_deny" else []
|
|
893
|
+
return _guarded_returns(_function(GATE, "decision"), {}, pick)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def enforcer_links():
|
|
897
|
+
"""{enforcer id: {"rules", "switches", "conditions"}} for every gate rule
|
|
898
|
+
(`gate:<rule>`), Stop class (`stop:<class>`) and report-only shape flag
|
|
899
|
+
(`flag:<flag>`), in that order: the CORE_RULES keys it is linked to, and the
|
|
900
|
+
guards every one of its sites shares. A Stop class with no switch of its own
|
|
901
|
+
is linked through the Stop hook's switch; a flag rides only that switch."""
|
|
902
|
+
drops = switch_drops()
|
|
903
|
+
keys = {key for key, _ in core_rules()}
|
|
904
|
+
found = {}
|
|
905
|
+
|
|
906
|
+
def link(eid, name, switches, conditions, via):
|
|
907
|
+
row = found.get(eid)
|
|
908
|
+
if row is None:
|
|
909
|
+
row = found[eid] = {"rules": set(), "switches": switches,
|
|
910
|
+
"conditions": conditions}
|
|
911
|
+
row["switches"] = tuple(s for s in row["switches"] if s in switches)
|
|
912
|
+
row["conditions"] = tuple(c for c in row["conditions"] if c in conditions)
|
|
913
|
+
row["rules"].update({name} & keys)
|
|
914
|
+
for s in via:
|
|
915
|
+
row["rules"] |= drops.get(s, set())
|
|
916
|
+
for rule, switches, conditions in gate_sites():
|
|
917
|
+
link("gate:" + rule, rule, switches, conditions, switches)
|
|
918
|
+
hook = [w for n in ast.walk(_function(STOP_HOOK, "main")) if isinstance(n, ast.If)
|
|
919
|
+
for w in _switch_words(n.test)]
|
|
920
|
+
for cls, switches, conditions in stop_sites():
|
|
921
|
+
link("stop:" + cls, cls, switches, conditions, switches or hook)
|
|
922
|
+
for c in ast.walk(_function(STOP, "shape_flags")):
|
|
923
|
+
if isinstance(c, ast.Call) and getattr(c.func, "attr", "") == "append":
|
|
924
|
+
link("flag:" + c.args[0].value, None, (), (), ())
|
|
925
|
+
for eid, row in found.items():
|
|
926
|
+
hand = HAND_LINKS.get(eid) or HAND_LINKS.get(eid.split(":")[0] + ":*")
|
|
927
|
+
row["rules"].update({hand} if hand else set())
|
|
928
|
+
return found
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def prose_only_rows():
|
|
932
|
+
"""The record in docs/contract.md, as {rule, decision} dicts: why a rule has
|
|
933
|
+
no refusing enforcer, or which part of it no enforcer covers."""
|
|
934
|
+
return table_rows(CONTRACT_PAGE, PROSE_ONLY_COLUMNS)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def enforcers(core=None, record=None, links=None):
|
|
938
|
+
"""[{rule, label, status, enforcers, guards, decision}], one per always-on
|
|
939
|
+
CORE rule in policy order. `status` is `enforced`, `partial` (every refusing
|
|
940
|
+
enforcer has a guard that can remove it while the rule's text stays, or the
|
|
941
|
+
record names a part no enforcer covers), `records` (only shape flags, which
|
|
942
|
+
refuse nothing) or `prose-only`; `guards` maps each enforcer to its switches
|
|
943
|
+
and conditions; `decision` is the record's row (None without one)."""
|
|
944
|
+
record = prose_only_rows() if record is None else record
|
|
945
|
+
decisions = {r["rule"]: r["decision"] for r in record}
|
|
946
|
+
links = enforcer_links() if links is None else links
|
|
947
|
+
drops = switch_drops()
|
|
948
|
+
rows = []
|
|
949
|
+
for key, label in core_rules(core):
|
|
950
|
+
ids = [eid for eid, row in links.items() if key in row["rules"]]
|
|
951
|
+
refusing = [eid for eid in ids if not eid.startswith("flag:")]
|
|
952
|
+
conditional = all(
|
|
953
|
+
links[eid]["conditions"] or any(key not in drops.get(s, set())
|
|
954
|
+
for s in links[eid]["switches"])
|
|
955
|
+
for eid in refusing)
|
|
956
|
+
decision = decisions.get(key)
|
|
957
|
+
status = ("prose-only" if not ids else "records" if not refusing
|
|
958
|
+
else "partial" if conditional or decision is not None else "enforced")
|
|
959
|
+
rows.append({"rule": key, "label": label, "status": status, "enforcers": ids,
|
|
960
|
+
"guards": {eid: list(links[eid]["switches"] + links[eid]["conditions"])
|
|
961
|
+
for eid in ids},
|
|
962
|
+
"decision": decision})
|
|
963
|
+
return rows
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
def enforcer_failures(core=None, record=None, links=None):
|
|
967
|
+
"""The ways the matrix is not whole: a CORE rule that nothing refuses on
|
|
968
|
+
(no enforcer, or shape flags only) with no row in the record, and a row
|
|
969
|
+
naming no CORE rule."""
|
|
970
|
+
record = prose_only_rows() if record is None else record
|
|
971
|
+
rows = enforcers(core, record, links)
|
|
972
|
+
out = ["CORE rule `%s` (%s) is %s and has no row in docs/contract.md"
|
|
973
|
+
% (row["rule"], row["label"], "recorded only by %s" % ", ".join(row["enforcers"])
|
|
974
|
+
if row["status"] == "records" else "enforced by nothing")
|
|
975
|
+
for row in rows if row["status"] in ("records", "prose-only")
|
|
976
|
+
and row["decision"] is None]
|
|
977
|
+
named = {row["rule"] for row in rows}
|
|
978
|
+
out += ["record row `%s` names no CORE rule" % r["rule"]
|
|
979
|
+
for r in record if r["rule"] not in named]
|
|
980
|
+
return out
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def enforcement_report():
|
|
984
|
+
"""The matrix as `tezgah-status --enforcers` prints it: the rows, the
|
|
985
|
+
enforcers tied to no CORE rule, and the failures."""
|
|
986
|
+
return {"rows": enforcers(),
|
|
987
|
+
"unattached": sorted(e for e, row in enforcer_links().items() if not row["rules"]),
|
|
988
|
+
"failures": enforcer_failures()}
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
INVENTORY_CHECKS = (rule_site_failures, rule_order_failures, judge_caller_failures,
|
|
992
|
+
switch_failures, enforcer_failures)
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def inventory_failures():
|
|
996
|
+
"""Every inventory check above, one list."""
|
|
997
|
+
return [f for check in INVENTORY_CHECKS for f in check()]
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
NONE = "none"
|
|
1001
|
+
# `none` is an option on purpose: without it the question has to pick a page, and
|
|
1002
|
+
# a query that names nothing in the layer (a typo, a nonsense word) would come
|
|
1003
|
+
# back with an arbitrary page instead of the "nothing matches" this command
|
|
1004
|
+
# promises. It is also what keeps `tezgah-docs zzz-nothing-matches` exiting 1.
|
|
1005
|
+
ASK = ("Which page of this project's docs answers the reader's question? The "
|
|
1006
|
+
"reader may name a thing with a different word from the pages' own - the "
|
|
1007
|
+
"same subject under a synonym - so match the topic rather than the wording, "
|
|
1008
|
+
"and pick the page whose answers would settle the question. Answer 'none' "
|
|
1009
|
+
"when the question names nothing any page answers - a typo, a nonsense "
|
|
1010
|
+
"string, or a topic outside this layer.")
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def off():
|
|
1014
|
+
"""True when this command must not ask: its own switch first, then the seam's.
|
|
1015
|
+
|
|
1016
|
+
`docs-judge-off` answers for this command alone - the triage and the skill
|
|
1017
|
+
picker keep judging - and `judge-off`, or an unresolvable credential, turns the
|
|
1018
|
+
whole path off. Both are read before the request, so an armed switch makes no
|
|
1019
|
+
request at all rather than one whose answer is thrown away."""
|
|
1020
|
+
return tp.off("docs-judge-off") or not tezgah_judge.available()
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def record(result):
|
|
1024
|
+
"""One `judge` ledger row for the call just paid for, in the shape the status
|
|
1025
|
+
counters fold on: which caller, which model, and what it cost.
|
|
1026
|
+
|
|
1027
|
+
A cost row, never a step and never a check - nothing here may license a done
|
|
1028
|
+
claim - and best effort: no session id, or an unwritable ledger, changes
|
|
1029
|
+
nothing this command prints."""
|
|
1030
|
+
usage = result["usage"]
|
|
1031
|
+
ti.note(os.environ.get("TEZGAH_SESSION"), "judge",
|
|
1032
|
+
"tezgah-docs %s in=%d out=%d ms=%d"
|
|
1033
|
+
% (result.get("model", tezgah_judge.MODEL),
|
|
1034
|
+
usage["input_tokens"], usage["output_tokens"],
|
|
1035
|
+
result["latency_ms"]))
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def judge_pick(query):
|
|
1039
|
+
"""(judged, page): whether a TypeSafe Choice question came back with one of
|
|
1040
|
+
its options, and the page it named (None for `none`).
|
|
1041
|
+
|
|
1042
|
+
The last resort for a query the index could not place: a word the index does
|
|
1043
|
+
not carry is usually a word nobody wrote down, and a model reading the pages'
|
|
1044
|
+
own questions can still land the reader on the right one. Three things keep
|
|
1045
|
+
it from standing in for the match above - it runs only when that match found
|
|
1046
|
+
nothing, the question offers `none`, and every failure (no key, `judge-off`,
|
|
1047
|
+
a refused or unreadable reply, an option it was not offered) reads as not
|
|
1048
|
+
judged, so the caller falls back to the word ranking instead of trusting it.
|
|
1049
|
+
|
|
1050
|
+
Measured over 39 live queries the index could not place (33 paraphrases of
|
|
1051
|
+
the pages' own answers plus the queries naming nothing in the layer): 37 pages
|
|
1052
|
+
right, 0 wrong, 2 missed, 0 pages picked for a query outside the layer. All
|
|
1053
|
+
three of the misses the first wording produced were the reader's word rather
|
|
1054
|
+
than the page's, which is what ASK now speaks to - that instruction recovered
|
|
1055
|
+
one of them and cost nothing - and the two that remain (a client where the
|
|
1056
|
+
page says host, "support one more" where it says add) stay missed because the
|
|
1057
|
+
layer carries no synonym list to judge them against."""
|
|
1058
|
+
pages = entries()
|
|
1059
|
+
if not query or not pages or off():
|
|
1060
|
+
return False, None
|
|
1061
|
+
criteria = {NONE: "no page answers this question"}
|
|
1062
|
+
for page in pages:
|
|
1063
|
+
criteria[page["path"]] = "%s. Answers: %s" % (
|
|
1064
|
+
page.get("title") or "", "; ".join(page.get("answers") or []))
|
|
1065
|
+
result = tezgah_judge.ask(query, {"page": {
|
|
1066
|
+
"type": "choice", "instructions": ASK, "criteria": criteria}})
|
|
1067
|
+
if result is not None:
|
|
1068
|
+
record(result)
|
|
1069
|
+
chosen = tezgah_judge.choice(result, "page")
|
|
1070
|
+
page = next((p for p in pages if p["path"] == chosen), None)
|
|
1071
|
+
return bool(page) or chosen == NONE, page
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
# How many pages the word ranking prints: the right page was in the top three for
|
|
1075
|
+
# 18 of 20 measured questions (10 of 10 English, 8 of 10 Turkish, the Turkish
|
|
1076
|
+
# phrasings included) and first for 12.
|
|
1077
|
+
RANKED = 3
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def ranked(query):
|
|
1081
|
+
"""The pages sharing the most query words, best first: the fallback when the
|
|
1082
|
+
judge gave no judgement (unavailable, or a call that failed). A page is
|
|
1083
|
+
ranked on its title and answers and on their Turkish phrasings (`title_tr`,
|
|
1084
|
+
`answers_tr`), which the word match above does not read. A query sharing no
|
|
1085
|
+
word with any page gets [], so `tezgah-docs zzz-nothing-matches` still exits
|
|
1086
|
+
1 - unless the opt-in embedding feature is on: then pages are ranked by
|
|
1087
|
+
meaning alone (`tezgah_embed.nearest`, 0.91 against the word-and-meaning
|
|
1088
|
+
fusion's 0.75 on held-out Turkish phrasings), which places every page."""
|
|
1089
|
+
pages = entries()
|
|
1090
|
+
groups = [[str(p.get("title") or "")] + [str(a) for a in (p.get("answers") or [])]
|
|
1091
|
+
+ [str(p.get("title_tr") or "")]
|
|
1092
|
+
+ [str(a) for a in (p.get("answers_tr") or [])] for p in pages]
|
|
1093
|
+
texts = [" ".join(g + [str(p["path"])]) for g, p in zip(groups, pages)]
|
|
1094
|
+
# an empty string embeds as the zero vector: a free cosine of 0.0
|
|
1095
|
+
groups = [[s for s in g if s] for g in groups]
|
|
1096
|
+
return [pages[i] for i in tezgah_embed.nearest(query, texts, RANKED, groups)]
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
KNOWN = ("--json", "--read", "--citations", "--clarity", "--update", "--help", "-h")
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def parse(argv):
|
|
1103
|
+
"""(flags, query words, unknown options). A known flag is a flag wherever it
|
|
1104
|
+
stands before `--`; an unknown `--word` is an error before the first query
|
|
1105
|
+
word and a query word after it (`--no-verify`); `--` ends the options."""
|
|
1106
|
+
flags, words, unknown = set(), [], []
|
|
1107
|
+
for i, a in enumerate(argv):
|
|
1108
|
+
if a == "--":
|
|
1109
|
+
return flags, words + argv[i + 1:], unknown
|
|
1110
|
+
if a in KNOWN:
|
|
1111
|
+
flags.add(a)
|
|
1112
|
+
elif a.startswith("--") and not words:
|
|
1113
|
+
unknown.append(a)
|
|
1114
|
+
else:
|
|
1115
|
+
words.append(a)
|
|
1116
|
+
return flags, words, unknown
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def main(argv):
|
|
1120
|
+
flags, words, unknown = parse(argv)
|
|
1121
|
+
if flags & {"-h", "--help"}:
|
|
1122
|
+
print(__doc__.strip())
|
|
1123
|
+
return 0
|
|
1124
|
+
if unknown:
|
|
1125
|
+
sys.stderr.write("tezgah-docs: unknown option(s): %s (try --help)\n"
|
|
1126
|
+
% ", ".join(sorted(unknown)))
|
|
1127
|
+
return 2
|
|
1128
|
+
if "--update" in flags and not flags & {"--clarity", "--citations"}:
|
|
1129
|
+
sys.stderr.write("tezgah-docs: --update needs --clarity or --citations\n")
|
|
1130
|
+
return 2
|
|
1131
|
+
if "--clarity" in flags:
|
|
1132
|
+
return tezgah_clarity.report(HERE, update="--update" in flags)
|
|
1133
|
+
if "--citations" in flags:
|
|
1134
|
+
flagged, judged, unjudged, invisible = citations(symbols())
|
|
1135
|
+
for where, token, symbol_in, low, high in flagged:
|
|
1136
|
+
if not low:
|
|
1137
|
+
print("%s %s -> %s defines no such symbol" % (where, token, symbol_in))
|
|
1138
|
+
continue
|
|
1139
|
+
print("%s %s -> the symbol it names lives at %s:%d-%d"
|
|
1140
|
+
% (where, token, symbol_in, low, high))
|
|
1141
|
+
print("citations: %d judged (%d outside the symbol they name or naming none), "
|
|
1142
|
+
"%d not judgeable (no single symbol named beside them; %d of them "
|
|
1143
|
+
"in a shape the audit cannot read)"
|
|
1144
|
+
% (judged, len(flagged), sum(unjudged.values()), invisible))
|
|
1145
|
+
if "--update" in flags:
|
|
1146
|
+
tezgah_clarity.save(os.path.join(HERE, CITATION_BASELINE), {
|
|
1147
|
+
rel: {"unjudged": n} for rel, n in unjudged.items() if n})
|
|
1148
|
+
print("citations: baseline rewritten (%s)" % CITATION_BASELINE)
|
|
1149
|
+
over = []
|
|
1150
|
+
else:
|
|
1151
|
+
over = ratchet_drift(unjudged, ratchet_baseline())
|
|
1152
|
+
for rel, count, allowed in over:
|
|
1153
|
+
print("citations: %s has %d unjudged, baseline %d - %s"
|
|
1154
|
+
% (rel, count, allowed,
|
|
1155
|
+
"name the symbol beside each new citation" if count > allowed
|
|
1156
|
+
else "run `bin/tezgah-docs --citations --update` to write the "
|
|
1157
|
+
"lower count down"))
|
|
1158
|
+
ledger = ledger_failures(ledger_rows())
|
|
1159
|
+
for problem in ledger:
|
|
1160
|
+
print("rule ledger: %s" % problem)
|
|
1161
|
+
if ledger:
|
|
1162
|
+
print("rule ledger: %d row(s) of the provenance table do not hold"
|
|
1163
|
+
% len(ledger))
|
|
1164
|
+
inventory = inventory_failures()
|
|
1165
|
+
for problem in inventory:
|
|
1166
|
+
print("inventory: %s" % problem)
|
|
1167
|
+
return 1 if flagged or over or ledger or inventory else 0
|
|
1168
|
+
query = " ".join(words).strip()
|
|
1169
|
+
found = matches(query) if query else entries()
|
|
1170
|
+
if not found:
|
|
1171
|
+
judged, pick = judge_pick(query)
|
|
1172
|
+
found = [pick] if pick else []
|
|
1173
|
+
if not judged:
|
|
1174
|
+
found = ranked(query)
|
|
1175
|
+
if not found:
|
|
1176
|
+
sys.stderr.write("tezgah-docs: nothing matches %r - the bare call lists "
|
|
1177
|
+
"every page\n" % query)
|
|
1178
|
+
return 1
|
|
1179
|
+
if "--json" in flags:
|
|
1180
|
+
print(json.dumps(found, indent=2, sort_keys=True))
|
|
1181
|
+
return 0
|
|
1182
|
+
if "--read" in flags:
|
|
1183
|
+
try:
|
|
1184
|
+
with open(os.path.join(HERE, found[0]["path"]), encoding="utf-8") as fh:
|
|
1185
|
+
sys.stdout.write(fh.read())
|
|
1186
|
+
except OSError as exc:
|
|
1187
|
+
sys.stderr.write("tezgah-docs: %s\n" % exc)
|
|
1188
|
+
return 1
|
|
1189
|
+
return 0
|
|
1190
|
+
for entry in found:
|
|
1191
|
+
print("%s %s" % (entry["path"], entry.get("title") or ""))
|
|
1192
|
+
answers = "; ".join(entry.get("answers") or [])
|
|
1193
|
+
if answers:
|
|
1194
|
+
print(" answers: %s" % answers)
|
|
1195
|
+
return 0
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
if __name__ == "__main__":
|
|
1199
|
+
sys.exit(main(sys.argv[1:]))
|