@r1z4x/tezgah 1.9.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +415 -1
- package/MANIFEST +404 -144
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.md +5 -4
- package/README.tr.md +1 -1
- package/bin/codegen +5 -2
- package/bin/tezgah-design +288 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +39 -2
- package/bin/tezgah-import-ai-research +26 -12
- package/bin/tezgah-ocr-review +296 -0
- package/bin/tezgah-research +255 -44
- package/bin/tezgah-review-quote +185 -0
- package/bin/tezgah-setup +1365 -55
- package/bin/tezgah-skill +1 -1
- package/bin/tezgah-status +201 -3
- package/bin/tezgah-task +258 -20
- package/commands/adhd.md +2 -2
- package/commands/ponytail.md +4 -4
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +3 -3
- package/docs/clarity-baseline.json +11 -5
- package/docs/contract.md +91 -43
- package/docs/environment.md +5 -2
- package/docs/evidence.md +109 -17
- package/docs/feature-audit.md +2 -2
- package/docs/gate.md +60 -33
- package/docs/glossary.md +6 -3
- package/docs/hosts.md +313 -17
- package/docs/index.json +34 -8
- package/docs/judge.md +4 -2
- package/docs/layers.md +4 -4
- package/docs/models.md +37 -9
- package/docs/operations.md +141 -5
- package/docs/research.md +815 -8
- package/docs/skills.md +137 -13
- package/docs/status-line.md +121 -1
- package/docs/testing.md +3 -1
- package/hooks/hooks.json +2 -2
- package/hooks/lang/en.json +21 -0
- package/hooks/lang/tr.json +22 -1
- package/hooks/projects-stop.py +2 -1
- package/hooks/tezgah_agents.py +65 -25
- package/hooks/tezgah_apps.py +18 -0
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_components.py +4 -0
- package/hooks/tezgah_context.py +207 -55
- package/hooks/tezgah_extension.py +337 -0
- package/hooks/tezgah_gate.py +267 -79
- package/hooks/tezgah_integrity.py +864 -143
- package/hooks/tezgah_judge.py +6 -4
- package/hooks/tezgah_models.py +59 -19
- package/hooks/tezgah_orca.py +1 -1
- package/hooks/tezgah_paths.py +145 -8
- package/hooks/tezgah_policy.py +128 -57
- package/hooks/tezgah_rank.py +9 -6
- package/hooks/tezgah_replay.py +12 -6
- package/hooks/tezgah_research.py +1414 -76
- package/hooks/tezgah_research_capture.py +1544 -0
- package/hooks/tezgah_research_evidence.py +842 -0
- package/hooks/tezgah_research_stats.py +258 -0
- package/hooks/tezgah_skill_pick.py +408 -106
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_stopspec.py +8 -1
- package/hooks/tezgah_task.py +14 -5
- package/hooks/tezgah_taste_ledger.py +1 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +386 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +2 -1
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +31 -3
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +4 -1
- package/hosts/omp/tezgah-hook.ts.in +30 -7
- package/hosts/opencode/plugins/tezgah.js +309 -48
- package/hosts/opencode/tui/cli/tui.tsx +100 -0
- package/hosts/opencode/tui/tezgah-tui.tsx +2 -1
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +20 -15
- package/package.json +1 -1
- package/skills/ai-research/SKILL.md +21 -14
- package/skills/ai-research/SOURCE +7 -5
- package/skills/ai-research/index/1-frame.md +6 -6
- package/skills/ai-research/index/2-data.md +9 -9
- package/skills/ai-research/index/3-train.md +36 -36
- package/skills/ai-research/index/4-measure.md +13 -13
- package/skills/ai-research/index/5-run.md +11 -11
- package/skills/ai-research/index/6-write.md +23 -23
- package/skills/ai-research/library.json +104 -104
- package/skills/analyze-app/SKILL.md +17 -12
- package/skills/design-contract/SKILL.md +42 -7
- package/skills/design-contract/data-views.md +2 -2
- package/skills/design-library/EVALS.md +1 -1
- package/skills/design-library/INDEX.md +39 -39
- package/skills/design-library/SKILL.md +19 -13
- package/skills/design-library/SOURCE +45 -43
- package/skills/feature-audit/SKILL.md +20 -14
- package/skills/hallmark/LICENSE +21 -0
- package/skills/hallmark/SKILL.md +154 -0
- package/skills/hallmark/SOURCE +207 -0
- package/skills/hallmark/agents/openai.yaml +13 -0
- package/skills/hallmark/references/anatomy.md +71 -0
- package/skills/hallmark/references/brand-truth.md +40 -0
- package/skills/hallmark/references/comp.md +104 -0
- package/skills/hallmark/references/craft.md +116 -0
- package/skills/hallmark/references/dark-mode.md +49 -0
- package/skills/hallmark/references/data-viz.md +48 -0
- package/skills/hallmark/references/design-md.md +53 -0
- package/skills/hallmark/references/direction.md +52 -0
- package/skills/hallmark/references/export-formats.md +80 -0
- package/skills/hallmark/references/flow.md +131 -0
- package/skills/hallmark/references/fonts.md +58 -0
- package/skills/hallmark/references/gates.md +243 -0
- package/skills/hallmark/references/genres/atmospheric.md +24 -0
- package/skills/hallmark/references/genres/editorial.md +24 -0
- package/skills/hallmark/references/genres/modern-minimal.md +24 -0
- package/skills/hallmark/references/genres/playful.md +24 -0
- package/skills/hallmark/references/imagery.md +99 -0
- package/skills/hallmark/references/interaction-and-states.md +89 -0
- package/skills/hallmark/references/layout.md +104 -0
- package/skills/hallmark/references/macrostructures/01-bento-grid.md +21 -0
- package/skills/hallmark/references/macrostructures/02-long-document.md +35 -0
- package/skills/hallmark/references/macrostructures/03-marquee-hero.md +21 -0
- package/skills/hallmark/references/macrostructures/04-stat-led.md +21 -0
- package/skills/hallmark/references/macrostructures/05-workbench.md +21 -0
- package/skills/hallmark/references/macrostructures/06-conversational-faq.md +28 -0
- package/skills/hallmark/references/macrostructures/07-manifesto.md +28 -0
- package/skills/hallmark/references/macrostructures/08-photographic.md +21 -0
- package/skills/hallmark/references/macrostructures/09-quote-led.md +21 -0
- package/skills/hallmark/references/macrostructures/10-specimen.md +21 -0
- package/skills/hallmark/references/macrostructures/11-catalogue.md +21 -0
- package/skills/hallmark/references/macrostructures/12-letter.md +21 -0
- package/skills/hallmark/references/macrostructures/13-index-first.md +21 -0
- package/skills/hallmark/references/macrostructures/14-narrative-workflow.md +21 -0
- package/skills/hallmark/references/macrostructures/15-split-studio.md +21 -0
- package/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/skills/hallmark/references/macrostructures/17-type-specimen.md +19 -0
- package/skills/hallmark/references/macrostructures/18-portfolio-grid.md +21 -0
- package/skills/hallmark/references/macrostructures/19-map-diagram.md +21 -0
- package/skills/hallmark/references/macrostructures/20-ecosystem-index.md +19 -0
- package/skills/hallmark/references/macrostructures/21-component-playground.md +21 -0
- package/skills/hallmark/references/macrostructures.md +119 -0
- package/skills/hallmark/references/microinteractions.md +80 -0
- package/skills/hallmark/references/patterns.md +202 -0
- package/skills/hallmark/references/redesign.md +88 -0
- package/skills/hallmark/references/responsive.md +46 -0
- package/skills/hallmark/references/review-finish.md +19 -0
- package/skills/hallmark/references/review-hero.md +20 -0
- package/skills/hallmark/references/scroll-choreography.md +110 -0
- package/skills/hallmark/references/texture.md +90 -0
- package/skills/hallmark/references/themes/README.md +58 -0
- package/skills/hallmark/references/themes/almanac.jpg +0 -0
- package/skills/hallmark/references/themes/almanac.md +103 -0
- package/skills/hallmark/references/themes/arcade.jpg +0 -0
- package/skills/hallmark/references/themes/arcade.md +102 -0
- package/skills/hallmark/references/themes/atelier.jpg +0 -0
- package/skills/hallmark/references/themes/atelier.md +98 -0
- package/skills/hallmark/references/themes/bloom.jpg +0 -0
- package/skills/hallmark/references/themes/bloom.md +102 -0
- package/skills/hallmark/references/themes/blueprint.jpg +0 -0
- package/skills/hallmark/references/themes/blueprint.md +103 -0
- package/skills/hallmark/references/themes/broadside.jpg +0 -0
- package/skills/hallmark/references/themes/broadside.md +108 -0
- package/skills/hallmark/references/themes/brutal.jpg +0 -0
- package/skills/hallmark/references/themes/brutal.md +95 -0
- package/skills/hallmark/references/themes/carnival.jpg +0 -0
- package/skills/hallmark/references/themes/carnival.md +104 -0
- package/skills/hallmark/references/themes/cobalt.jpg +0 -0
- package/skills/hallmark/references/themes/cobalt.md +101 -0
- package/skills/hallmark/references/themes/console.jpg +0 -0
- package/skills/hallmark/references/themes/console.md +99 -0
- package/skills/hallmark/references/themes/coral.jpg +0 -0
- package/skills/hallmark/references/themes/coral.md +102 -0
- package/skills/hallmark/references/themes/darkroom.jpg +0 -0
- package/skills/hallmark/references/themes/darkroom.md +99 -0
- package/skills/hallmark/references/themes/dose.jpg +0 -0
- package/skills/hallmark/references/themes/dose.md +98 -0
- package/skills/hallmark/references/themes/dusk.jpg +0 -0
- package/skills/hallmark/references/themes/dusk.md +105 -0
- package/skills/hallmark/references/themes/editorial.jpg +0 -0
- package/skills/hallmark/references/themes/editorial.md +97 -0
- package/skills/hallmark/references/themes/formwork.jpg +0 -0
- package/skills/hallmark/references/themes/formwork.md +107 -0
- package/skills/hallmark/references/themes/furrow.jpg +0 -0
- package/skills/hallmark/references/themes/furrow.md +98 -0
- package/skills/hallmark/references/themes/garden.jpg +0 -0
- package/skills/hallmark/references/themes/garden.md +97 -0
- package/skills/hallmark/references/themes/grid.jpg +0 -0
- package/skills/hallmark/references/themes/grid.md +101 -0
- package/skills/hallmark/references/themes/halftone.jpg +0 -0
- package/skills/hallmark/references/themes/halftone.md +106 -0
- package/skills/hallmark/references/themes/hedgerow.jpg +0 -0
- package/skills/hallmark/references/themes/hedgerow.md +104 -0
- package/skills/hallmark/references/themes/hum.jpg +0 -0
- package/skills/hallmark/references/themes/hum.md +99 -0
- package/skills/hallmark/references/themes/index.json +2191 -0
- package/skills/hallmark/references/themes/ink.jpg +0 -0
- package/skills/hallmark/references/themes/ink.md +106 -0
- package/skills/hallmark/references/themes/kiln.jpg +0 -0
- package/skills/hallmark/references/themes/kiln.md +108 -0
- package/skills/hallmark/references/themes/loom.jpg +0 -0
- package/skills/hallmark/references/themes/loom.md +106 -0
- package/skills/hallmark/references/themes/lumen.jpg +0 -0
- package/skills/hallmark/references/themes/lumen.md +103 -0
- package/skills/hallmark/references/themes/manifesto.jpg +0 -0
- package/skills/hallmark/references/themes/manifesto.md +101 -0
- package/skills/hallmark/references/themes/midnight.jpg +0 -0
- package/skills/hallmark/references/themes/midnight.md +97 -0
- package/skills/hallmark/references/themes/newsprint.jpg +0 -0
- package/skills/hallmark/references/themes/newsprint.md +102 -0
- package/skills/hallmark/references/themes/overprint.jpg +0 -0
- package/skills/hallmark/references/themes/overprint.md +101 -0
- package/skills/hallmark/references/themes/pasteup.jpg +0 -0
- package/skills/hallmark/references/themes/pasteup.md +100 -0
- package/skills/hallmark/references/themes/piste.jpg +0 -0
- package/skills/hallmark/references/themes/piste.md +99 -0
- package/skills/hallmark/references/themes/ribbon.jpg +0 -0
- package/skills/hallmark/references/themes/ribbon.md +106 -0
- package/skills/hallmark/references/themes/rim.jpg +0 -0
- package/skills/hallmark/references/themes/rim.md +96 -0
- package/skills/hallmark/references/themes/riso.jpg +0 -0
- package/skills/hallmark/references/themes/riso.md +98 -0
- package/skills/hallmark/references/themes/solstice.jpg +0 -0
- package/skills/hallmark/references/themes/solstice.md +104 -0
- package/skills/hallmark/references/themes/specimen.jpg +0 -0
- package/skills/hallmark/references/themes/specimen.md +100 -0
- package/skills/hallmark/references/themes/spore.jpg +0 -0
- package/skills/hallmark/references/themes/spore.md +104 -0
- package/skills/hallmark/references/themes/sport.jpg +0 -0
- package/skills/hallmark/references/themes/sport.md +101 -0
- package/skills/hallmark/references/themes/squeeze.jpg +0 -0
- package/skills/hallmark/references/themes/squeeze.md +99 -0
- package/skills/hallmark/references/themes/sticker.jpg +0 -0
- package/skills/hallmark/references/themes/sticker.md +106 -0
- package/skills/hallmark/references/themes/studio.jpg +0 -0
- package/skills/hallmark/references/themes/studio.md +99 -0
- package/skills/hallmark/references/themes/terminal.jpg +0 -0
- package/skills/hallmark/references/themes/terminal.md +96 -0
- package/skills/hallmark/references/themes/terrazzo.jpg +0 -0
- package/skills/hallmark/references/themes/terrazzo.md +106 -0
- package/skills/hallmark/references/themes/ultraviolet.jpg +0 -0
- package/skills/hallmark/references/themes/ultraviolet.md +106 -0
- package/skills/hallmark/references/themes/vellum.jpg +0 -0
- package/skills/hallmark/references/themes/vellum.md +105 -0
- package/skills/hallmark/references/themes/wayfind.jpg +0 -0
- package/skills/hallmark/references/themes/wayfind.md +105 -0
- package/skills/hallmark/references/themes/werkstatt.jpg +0 -0
- package/skills/hallmark/references/themes/werkstatt.md +105 -0
- package/skills/hallmark/scripts/capture.mjs +125 -0
- package/skills/hallmark/scripts/fixtures/exemplars/atmospheric.html +324 -0
- package/skills/hallmark/scripts/fixtures/exemplars/editorial.html +363 -0
- package/skills/hallmark/scripts/fixtures/exemplars/modern-minimal.html +527 -0
- package/skills/hallmark/scripts/fixtures/exemplars/playful.html +353 -0
- package/skills/hallmark/scripts/fixtures/eyebrow-fail/index.html +118 -0
- package/skills/hallmark/scripts/fixtures/eyebrow-pass/index.html +137 -0
- package/skills/hallmark/scripts/fixtures/kept/after.html +27 -0
- package/skills/hallmark/scripts/fixtures/kept/before.html +26 -0
- package/skills/hallmark/scripts/fixtures/slopped.html +145 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/Feature.vue +14 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/Hero.tsx +33 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/index.html +74 -0
- package/skills/hallmark/scripts/fixtures/tailwind-fail/style.css +6 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/Hero.tsx +33 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/index.html +57 -0
- package/skills/hallmark/scripts/fixtures/tailwind-pass/style.css +15 -0
- package/skills/hallmark/scripts/fixtures/waiver/index.html +54 -0
- package/skills/hallmark/scripts/imagegen.mjs +85 -0
- package/skills/hallmark/scripts/install-hook.mjs +70 -0
- package/skills/hallmark/scripts/install.mjs +141 -0
- package/skills/hallmark/scripts/lint-hook.mjs +49 -0
- package/skills/hallmark/scripts/photos.json +1 -0
- package/skills/hallmark/scripts/picture.mjs +71 -0
- package/skills/hallmark/scripts/refcheck.mjs +352 -0
- package/skills/hallmark/scripts/sloplint-render.mjs +1252 -0
- package/skills/hallmark/scripts/sloplint.mjs +4422 -0
- package/skills/hallmark/scripts/theme.mjs +650 -0
- package/skills/harness/SKILL.md +17 -8
- package/skills/i-have-adhd/SKILL.md +32 -12
- package/skills/no-ai-slop/SKILL.md +15 -1
- package/skills/plan-add/SKILL.md +38 -27
- package/skills/plan-status/SKILL.md +13 -7
- package/skills/plan-sync/SKILL.md +18 -12
- package/skills/pm-frameworks/SKILL.md +19 -10
- package/skills/pm-frameworks/SOURCE +7 -5
- package/skills/ponytail/SKILL.md +19 -14
- package/skills/product-analysis/SKILL.md +22 -18
- package/skills/product-flow/SKILL.md +14 -9
- package/skills/research/SKILL.md +97 -48
- package/skills/rl-env/SKILL.md +25 -13
- package/skills/rl-env/SOURCE +8 -6
- package/skills/tezgah-contract/SKILL.md +65 -47
- package/statusline.py +20 -9
- package/workflows/graph-review.js +14 -2
- package/bin/tezgah-skill.py +0 -59
- /package/skills/ai-research/0-autoresearch-skill/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/litgpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/mamba/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/nanogpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/rwkv/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/01-model-architecture/torchtitan/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/02-tokenization/huggingface-tokenizers/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/02-tokenization/sentencepiece/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/axolotl/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/llama-factory/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/peft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/03-fine-tuning/unsloth/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/nnsight/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/pyvene/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/saelens/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/04-mechanistic-interpretability/transformer-lens/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/05-data-processing/nemo-curator/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/05-data-processing/ray-data/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/grpo-rl-training/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/miles/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/openrlhf/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/simpo/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/slime/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/torchforge/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/trl-fine-tuning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/06-post-training/verl/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/constitutional-ai/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/llamaguard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/nemo-guardrails/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/07-safety-alignment/prompt-guard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/accelerate/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/deepspeed/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/megatron-core/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/pytorch-fsdp2/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/pytorch-lightning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/08-distributed-training/ray-train/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/lambda-labs/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/modal/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/09-infrastructure/skypilot/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/awq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/bitsandbytes/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/flash-attention/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/gguf/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/gptq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/hqq/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/10-optimization/ml-training-recipes/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/bigcode-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/lm-evaluation-harness/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/11-evaluation/nemo-evaluator/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/llama-cpp/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/sglang/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/tensorrt-llm/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/12-inference-serving/vllm/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/mlflow/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/swanlab/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/tensorboard/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/13-mlops/weights-and-biases/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/a-evolve/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/autogpt/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/crewai/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/langchain/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/14-agents/llamaindex/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/chroma/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/faiss/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/pinecone/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/qdrant/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/15-rag/sentence-transformers/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/dspy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/guidance/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/instructor/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/16-prompt-engineering/outlines/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/17-observability/langsmith/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/17-observability/phoenix/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/audiocraft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/blip-2/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/clip/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/cosmos-policy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/llava/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/openpi/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/openvla-oft/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/segment-anything/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/stable-diffusion/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/18-multimodal/whisper/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/knowledge-distillation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/long-context/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/model-merging/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/model-pruning/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/moe-training/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/19-emerging-techniques/speculative-decoding/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/academic-plotting/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/ml-paper-writing/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/presenting-conference-talks/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/20-ml-paper-writing/systems-paper-writing/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/21-research-ideation/brainstorming-research-ideas/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/21-research-ideation/creative-thinking-for-research/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/compiler/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/research-manager/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/ai-research/22-agent-native-research-artifact/rigor-reviewer/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/colour-independence/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/flexible-typography/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/information-density/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/preference-audit/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/responsive-accessibility/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/responsive-review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/simplified-views/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/specify/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/adaptive-interfaces/skills/user-preference-respect/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/adaptive-personalisation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/assess-load/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/cognitive-load-assessment/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/contextual-help-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/error-prevention-recovery/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/focus-attention-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/memory-load-reduction/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/plain-language-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/simplify/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/cognitive-accessibility/skills/wayfinding-navigation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/design-research/skills/behavioural-analytics/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/design-research/skills/qual-quant-triangulation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/audit/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/design-flow/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/feedback-and-status/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/gesture-alternatives/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/keyboard-navigation/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/keyboard-review/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/motion-sensitivity/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/multi-modal-input/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/touch-target-design/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/inclusive-interaction/skills/voice-interaction/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-affordance/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-brand-consistency/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-color/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-composition/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-information-density/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-typography/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/design-library/visual-critique/skills/critique-visual-hierarchy/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/pm-frameworks/intended-vs-implemented/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/pm-frameworks/opportunity-solution-tree/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-nemo-gym-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-openenv-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-ors-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/generate-verifiers-env/{SKILL.md → ENTRY.md} +0 -0
- /package/skills/rl-env/rl-env-from-description/{SKILL.md → ENTRY.md} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join, dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
|
|
7
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const THEMES = join(HERE, '..', 'references', 'themes');
|
|
9
|
+
const [slug, pageArg, yArg] = process.argv.slice(2);
|
|
10
|
+
if (!slug || !pageArg) {
|
|
11
|
+
console.error('usage: node picture.mjs <theme slug> <page index.html> [y of the lower screen at 1440x900]');
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) { console.error(`not a theme slug: ${slug}`); process.exit(2); }
|
|
15
|
+
const page = resolve(pageArg);
|
|
16
|
+
const themeFile = join(THEMES, `${slug}.md`);
|
|
17
|
+
if (!existsSync(themeFile)) { console.error(`no theme at ${themeFile}`); process.exit(1); }
|
|
18
|
+
if (!existsSync(page)) { console.error(`no page at ${page}`); process.exit(1); }
|
|
19
|
+
|
|
20
|
+
let puppeteer = null;
|
|
21
|
+
const roots = [process.env.HALLMARK_PUPPETEER, process.cwd(), HERE].filter(Boolean);
|
|
22
|
+
try { const { execSync } = await import('node:child_process'); roots.push(dirname(execSync('npm root -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim())); } catch {}
|
|
23
|
+
for (const root of roots) {
|
|
24
|
+
for (const name of ['puppeteer-core', 'puppeteer']) {
|
|
25
|
+
try { puppeteer = (await import(pathToFileURL(createRequire(join(root, 'noop.js')).resolve(name)).href)).default; } catch {}
|
|
26
|
+
if (puppeteer) break;
|
|
27
|
+
}
|
|
28
|
+
if (puppeteer) break;
|
|
29
|
+
}
|
|
30
|
+
if (!puppeteer) { console.error('picture.mjs needs puppeteer-core or puppeteer (local, global or HALLMARK_PUPPETEER)'); process.exit(1); }
|
|
31
|
+
const CHROMES = [process.env.CHROME_PATH, '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/snap/bin/chromium', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'];
|
|
32
|
+
const findChrome = () => { const c = CHROMES.find((x) => x && existsSync(x)); if (c) return c; try { const b = typeof puppeteer.executablePath === 'function' ? puppeteer.executablePath() : null; if (typeof b === 'string' && existsSync(b)) return b; } catch {} return null; };
|
|
33
|
+
const chrome = findChrome();
|
|
34
|
+
if (!chrome) { console.error('no Chrome or Chromium found; set CHROME_PATH to its executable'); process.exit(1); }
|
|
35
|
+
const browser = await puppeteer.launch({ executablePath: chrome, headless: true });
|
|
36
|
+
const p = await browser.newPage();
|
|
37
|
+
await p.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]);
|
|
38
|
+
await p.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
|
|
39
|
+
await p.goto(pathToFileURL(page).href, { waitUntil: 'networkidle0', timeout: 45000 }).catch(() => {});
|
|
40
|
+
await p.evaluate(async () => { document.documentElement.classList.add('hallmark-shot'); await document.fonts.ready; for (const i of document.querySelectorAll('img[loading="lazy"]')) i.loading = 'eager'; for (let y = 0; y < document.documentElement.scrollHeight; y += 700) { window.scrollTo(0, y); await new Promise((r) => setTimeout(r, 40)); } window.scrollTo(0, 0); const waiting = [...document.images].filter((i) => !i.complete); await Promise.race([Promise.all(waiting.map((i) => new Promise((r) => { i.addEventListener('load', r, { once: true }); i.addEventListener('error', r, { once: true }); }))), new Promise((r) => setTimeout(r, 15000))]); await Promise.all([...document.images].map((i) => (i.complete && i.naturalWidth && i.decode ? i.decode().catch(() => {}) : null))); });
|
|
41
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
42
|
+
// the viewport itself, not a clip: a clipped capture re-lays the page out and drops content sized in container units
|
|
43
|
+
await p.bringToFront();
|
|
44
|
+
const first = await p.screenshot({ type: 'png' });
|
|
45
|
+
let y = Number(yArg);
|
|
46
|
+
if (!yArg || Number.isNaN(y)) y = await p.evaluate(() => { const s = [...document.querySelectorAll('section')].map((e) => e.getBoundingClientRect().top + scrollY).filter((t) => t > 700); return s.length ? Math.round(s[0] - 40) : 1800; });
|
|
47
|
+
await p.evaluate((v) => { window.scrollTo(0, v); for (const el of document.querySelectorAll('body *')) { const cs = getComputedStyle(el); if (/fixed|sticky/.test(cs.position) && el.getBoundingClientRect().top < 120) el.style.visibility = 'hidden'; } }, y);
|
|
48
|
+
await new Promise((r) => setTimeout(r, 700));
|
|
49
|
+
const lower = await p.screenshot({ type: 'png' });
|
|
50
|
+
const stamp = await p.evaluate(() => [...document.querySelectorAll('style')].map((s) => s.textContent.slice(0, 600)).join('\n'));
|
|
51
|
+
const cssPath = join(dirname(page), 'style.css');
|
|
52
|
+
const head = (existsSync(cssPath) ? readFileSync(cssPath, 'utf8').slice(0, 600) : '') + '\n' + stamp;
|
|
53
|
+
const shape = (/shape:\s*([^·*\n]+?)\s*[·*\n]/.exec(head) || [])[1];
|
|
54
|
+
const hero = (/hero:\s*([^·*\n]+?)\s*[·*\n]/.exec(head) || [])[1];
|
|
55
|
+
const c = await browser.newPage();
|
|
56
|
+
await c.setViewport({ width: 960, height: 1208, deviceScaleFactor: 1 });
|
|
57
|
+
await c.setContent(`<body style="margin:0;background:#8a8a8a"><img src="data:image/png;base64,${first.toString('base64')}" style="display:block;width:960px;height:600px"><div style="height:8px"></div><img src="data:image/png;base64,${lower.toString('base64')}" style="display:block;width:960px;height:600px"></body>`, { waitUntil: 'load' });
|
|
58
|
+
const out = join(THEMES, `${slug}.jpg`);
|
|
59
|
+
writeFileSync(out, await c.screenshot({ type: 'jpeg', quality: 80, clip: { x: 0, y: 0, width: 960, height: 1208 } }));
|
|
60
|
+
await browser.close();
|
|
61
|
+
if (shape && hero) {
|
|
62
|
+
let src = readFileSync(themeFile, 'utf8');
|
|
63
|
+
const short = shape.trim().split(/\s*[(;,:]/)[0].replace(/,\s*$/, '');
|
|
64
|
+
const way = (new RegExp(`\\b${slug}:\\s*([a-z][a-z-]*)`).exec(head) || [])[1];
|
|
65
|
+
const fallback = (/`([a-z][a-z-]*)`\s*\(default\)/.exec(src) || [])[1];
|
|
66
|
+
const line = `pictured: ${short}, ${hero.trim().split(/[ ,(]/)[0]} hero${way && fallback && way !== fallback ? `, in the ${way} colourway` : ''}`;
|
|
67
|
+
src = /^pictured:.*$/m.test(src) ? src.replace(/^pictured:.*$/m, line) : src.replace(/^(material:.*)$/m, `$1\n${line}`);
|
|
68
|
+
writeFileSync(themeFile, src);
|
|
69
|
+
try { const { execFileSync } = await import('node:child_process'); execFileSync(process.execPath, [join(HERE, 'theme.mjs'), 'index'], { stdio: 'ignore' }); } catch {}
|
|
70
|
+
}
|
|
71
|
+
console.log(`${out} (lower screen at y ${y}${shape ? `; ${shape.trim()}, ${hero ? hero.trim() : '?'} hero` : ''})`);
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, readdirSync, statSync, existsSync, mkdtempSync, copyFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, dirname, relative, basename, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const AS_JSON = args.includes('--json');
|
|
11
|
+
const ONLY_ERRORS = args.includes('--errors');
|
|
12
|
+
|
|
13
|
+
function walk(dir, out = []) {
|
|
14
|
+
for (const name of readdirSync(dir)) {
|
|
15
|
+
if (name === 'node_modules' || name === '.hallmark' || name.startsWith('.')) continue;
|
|
16
|
+
const p = join(dir, name);
|
|
17
|
+
const st = statSync(p);
|
|
18
|
+
if (st.isDirectory()) { if (name !== 'fixtures') walk(p, out); }
|
|
19
|
+
else out.push(p);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const FILES = walk(ROOT);
|
|
25
|
+
const MD = FILES.filter((f) => f.endsWith('.md'));
|
|
26
|
+
const TEXT = FILES.filter((f) => /\.(md|mjs|json|yaml|css|html)$/.test(f));
|
|
27
|
+
const BY_BASE = new Map();
|
|
28
|
+
for (const f of FILES) { const b = basename(f); if (!BY_BASE.has(b)) BY_BASE.set(b, []); BY_BASE.get(b).push(f); }
|
|
29
|
+
const read = (f) => readFileSync(f, 'utf8');
|
|
30
|
+
const findings = [];
|
|
31
|
+
const add = (level, file, line, rule, msg) => findings.push({ level, file: relative(ROOT, file), line, rule, msg });
|
|
32
|
+
const lineAt = (src, i) => src.slice(0, i).split('\n').length;
|
|
33
|
+
|
|
34
|
+
const SKILL = join(ROOT, 'SKILL.md');
|
|
35
|
+
const VERSION = (/^version:\s*([\d.]+)/m.exec(read(SKILL)) || [])[1];
|
|
36
|
+
|
|
37
|
+
const gatesSrc = read(join(ROOT, 'references', 'gates.md'));
|
|
38
|
+
const GATES = new Map();
|
|
39
|
+
for (const m of gatesSrc.matchAll(/^\|\s*(\d{1,2}(?:[a-d]|i{1,3})?)\s*\|\s*(Floor|Reflex|Ledger)\s*\|/gm)) GATES.set(m[1], m[2].toLowerCase());
|
|
40
|
+
const FCHECKS = new Set([...gatesSrc.matchAll(/\b(F\d{1,2})\s/g)].map((m) => m[1]));
|
|
41
|
+
const retiredLine = (/Retired numbers stay retired:\*\*\s*([\d, and]+) are never reused/.exec(gatesSrc) || [])[1] || '';
|
|
42
|
+
if (!retiredLine) findings.push({ level: 'error', file: 'references/gates.md', line: 1, rule: 'gate', msg: 'the retired-numbers sentence is missing or reworded; refcheck reads it' });
|
|
43
|
+
const RETIRED = new Set((retiredLine.match(/\d+/g) || []));
|
|
44
|
+
const NUMBERS = new Set([...GATES.keys()].map((k) => k.match(/^\d+/)[0]));
|
|
45
|
+
|
|
46
|
+
function gateProblem(id) {
|
|
47
|
+
if (/^F\d+$/.test(id)) return FCHECKS.has(id) ? null : `F-check ${id} does not exist`;
|
|
48
|
+
const num = id.match(/^\d+/)[0];
|
|
49
|
+
if (RETIRED.has(num)) return `gate ${num} is retired`;
|
|
50
|
+
if (GATES.has(id)) return null;
|
|
51
|
+
if (id === num && NUMBERS.has(num)) return null;
|
|
52
|
+
if (!NUMBERS.has(num)) return `gate ${num} does not exist`;
|
|
53
|
+
return `gate ${num} has no clause ${id.slice(num.length)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ID = String.raw`(?:F\d{1,2}|\d{1,2}(?:[a-d]|i{1,3})?)`;
|
|
57
|
+
const GATE_LIST = new RegExp(String.raw`\b(?:gates?|waive|answered)\s+(${ID}(?:(?:\s*,\s*|\s+and\s+|\s+to\s+|\s+or\s+)${ID})*)`, 'gi');
|
|
58
|
+
const PAREN_LIST = new RegExp(String.raw`\((${ID}(?:,\s*${ID})*)\)`, 'g');
|
|
59
|
+
|
|
60
|
+
for (const f of MD) {
|
|
61
|
+
const src = read(f);
|
|
62
|
+
const isGates = f.endsWith(join('references', 'gates.md'));
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const check = (list, at) => {
|
|
65
|
+
for (const id of list.split(/\s*,\s*|\s+and\s+|\s+to\s+|\s+or\s+/)) {
|
|
66
|
+
const key = `${id}@${at}`;
|
|
67
|
+
if (!id || seen.has(key)) continue;
|
|
68
|
+
seen.add(key);
|
|
69
|
+
const p = gateProblem(id);
|
|
70
|
+
if (p) add('error', f, lineAt(src, at), 'gate', p);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
for (const m of src.matchAll(GATE_LIST)) {
|
|
74
|
+
if (isGates && /were merged|stay retired|Retired numbers/.test(src.slice(Math.max(0, m.index - 200), m.index + 200))) continue;
|
|
75
|
+
check(m[1], m.index);
|
|
76
|
+
}
|
|
77
|
+
for (const m of src.matchAll(PAREN_LIST)) {
|
|
78
|
+
if (/[\w-]/.test(src[m.index - 1] || '')) continue;
|
|
79
|
+
if (/^\d+$/.test(m[1]) && Number(m[1]) > 99) continue;
|
|
80
|
+
check(m[1], m.index);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const PROJECT_FILES = new Set(['design.md', 'DESIGN.md', 'brand-spec.md', 'AGENTS.md', 'CLAUDE.md', 'brief.md', 'README.md', 'prompts.md', 'notes.md']);
|
|
85
|
+
const CODE_EXT = /\.(md|mjs)$/;
|
|
86
|
+
for (const f of MD) {
|
|
87
|
+
const src = read(f);
|
|
88
|
+
for (const m of src.matchAll(/\]\(([^)\s]+)\)/g)) {
|
|
89
|
+
const raw = m[1];
|
|
90
|
+
if (/^(https?:|mailto:|#)/.test(raw)) continue;
|
|
91
|
+
const target = raw.split('#')[0];
|
|
92
|
+
if (!target) continue;
|
|
93
|
+
const p = resolve(dirname(f), target);
|
|
94
|
+
if (!existsSync(p)) add('error', f, lineAt(src, m.index), 'link', `dead link ${raw}`);
|
|
95
|
+
}
|
|
96
|
+
for (const m of src.matchAll(/`([\w./-]+\.(?:md|mjs))`/g)) {
|
|
97
|
+
const name = m[1];
|
|
98
|
+
if (name.includes('<') || name.includes('*')) continue;
|
|
99
|
+
const b = basename(name);
|
|
100
|
+
if (PROJECT_FILES.has(b) && !name.includes('references/')) continue;
|
|
101
|
+
if (!CODE_EXT.test(b)) continue;
|
|
102
|
+
const found = BY_BASE.has(b) || existsSync(resolve(dirname(f), name)) || existsSync(join(ROOT, name));
|
|
103
|
+
if (!found) add('error', f, lineAt(src, m.index), 'mention', `names ${name}, which does not exist`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const headingsOf = new Map();
|
|
108
|
+
function headings(file) {
|
|
109
|
+
if (!headingsOf.has(file)) {
|
|
110
|
+
const src = read(file);
|
|
111
|
+
const hs = [...src.matchAll(/^(#{1,4})\s+(.+)$/gm)].map((m) => m[2].replace(/[*`]/g, '').trim());
|
|
112
|
+
const bold = [...src.matchAll(/^\s*(?:[-*]\s+|\d+\.\s+)?\*\*([^*]+)\*\*/gm)].map((m) => m[1].trim());
|
|
113
|
+
headingsOf.set(file, { hs, bold });
|
|
114
|
+
}
|
|
115
|
+
return headingsOf.get(file);
|
|
116
|
+
}
|
|
117
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
118
|
+
for (const f of MD) {
|
|
119
|
+
const src = read(f);
|
|
120
|
+
for (const m of src.matchAll(/§\s*([^\n§]{1,60})/g)) {
|
|
121
|
+
const lineStart = src.lastIndexOf('\n', m.index) + 1;
|
|
122
|
+
const back = src.slice(lineStart, m.index);
|
|
123
|
+
const mentions = [...back.matchAll(/([\w-]+(?:\/[\w-]+)*\.md)/g)];
|
|
124
|
+
const fm = mentions.length ? mentions[mentions.length - 1] : null;
|
|
125
|
+
let target = f;
|
|
126
|
+
if (fm) {
|
|
127
|
+
const cands = BY_BASE.get(basename(fm[1])) || [];
|
|
128
|
+
if (!cands.length) continue;
|
|
129
|
+
target = cands.find((c) => c.endsWith(fm[1])) || cands[0];
|
|
130
|
+
}
|
|
131
|
+
const rest = m[1].trim();
|
|
132
|
+
const { hs, bold } = headings(target);
|
|
133
|
+
const num = /^(\d+)\b/.exec(rest);
|
|
134
|
+
if (num) {
|
|
135
|
+
if (!hs.some((h) => new RegExp(`^${num[1]}[.\\s]`).test(h))) add('error', f, lineAt(src, m.index), 'section', `${relative(ROOT, target)} has no § ${num[1]}`);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const words = norm(rest.split(/[,.;:()]|\s(?:and|to|or|for|on|in|when|is|binds|says|owns)\s/)[0]).split(' ').slice(0, 3).join(' ');
|
|
139
|
+
if (!words || words.length < 3) continue;
|
|
140
|
+
const pool = [...hs, ...bold].map(norm);
|
|
141
|
+
const first = words.split(' ')[0];
|
|
142
|
+
if (!pool.some((h) => h.includes(words) || h.startsWith(first) || h.includes(` ${first}`))) add('warn', f, lineAt(src, m.index), 'section', `${relative(ROOT, target)} has no section like "§ ${rest.split(/[,.;:)]/)[0].trim()}"`);
|
|
143
|
+
}
|
|
144
|
+
for (const m of src.matchAll(/\bStep (\d+(?:\.\d+)?)\b/g)) {
|
|
145
|
+
const n = m[1];
|
|
146
|
+
if (n.includes('.') || Number(n) > 7) add('error', f, lineAt(src, m.index), 'step', `SKILL.md has no Step ${n} (steps run 0 to 7)`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const theme = read(join(ROOT, 'scripts', 'theme.mjs'));
|
|
151
|
+
const tokenNames = (s) => new Set([...s.matchAll(/(--[a-z][\w-]*)\s*:/g)].map((m) => m[1]));
|
|
152
|
+
const CANON = new Set();
|
|
153
|
+
for (const block of ['SCALE', 'CUSTOM_TOKENS']) {
|
|
154
|
+
const m = new RegExp(`const ${block} = \`([\\s\\S]*?)\`;`).exec(theme);
|
|
155
|
+
if (m) for (const t of tokenNames(m[1])) CANON.add(t);
|
|
156
|
+
}
|
|
157
|
+
const THEME_DIR = join(ROOT, 'references', 'themes');
|
|
158
|
+
const themeFiles = MD.filter((f) => dirname(f) === THEME_DIR && basename(f) !== 'README.md');
|
|
159
|
+
const themeCount = new Map();
|
|
160
|
+
for (const f of themeFiles) for (const t of tokenNames(read(f))) themeCount.set(t, (themeCount.get(t) || 0) + 1);
|
|
161
|
+
for (const [t, n] of themeCount) if (n >= Math.ceil(themeFiles.length * 0.9)) CANON.add(t);
|
|
162
|
+
for (const t of ['--banner-height', '--i', '--texture-opacity', '--logo-count']) CANON.add(t);
|
|
163
|
+
const PHANTOM = new Map(Object.entries({
|
|
164
|
+
'--page-gutter': '--gutter', '--page-max': '--container', '--ease-standard': '--ease-out', '--dur-mid': '--dur-long',
|
|
165
|
+
'--dur-fast': '--dur-micro', '--dur-base': '--dur-short', '--dur-slow': '--dur-long', '--banner-h': '--banner-height',
|
|
166
|
+
'--bar-h': 'a min-height', '--font-mono': '--font-outlier', '--font-label': '--font-body', '--font-wordmark': '--font-outlier',
|
|
167
|
+
'--tracking-label': 'nothing (labels are untracked, gate 67)', '--display': '--font-display', '--rule-fine': '--rule-bar',
|
|
168
|
+
}));
|
|
169
|
+
const codeOf = (src) => [...src.matchAll(/```[a-z]*\n([\s\S]*?)```/g)].map((m) => ({ text: m[1], at: m.index + m[0].indexOf(m[1]) })).concat([...src.matchAll(/`([^`\n]+)`/g)].map((m) => ({ text: m[1], at: m.index })));
|
|
170
|
+
for (const f of MD) {
|
|
171
|
+
const src = read(f);
|
|
172
|
+
const isTheme = dirname(f) === THEME_DIR;
|
|
173
|
+
const local = tokenNames(src);
|
|
174
|
+
for (const { text, at } of codeOf(src)) {
|
|
175
|
+
for (const m of text.matchAll(/var\(\s*(--[a-z][\w-]*)\s*(,)?/g)) {
|
|
176
|
+
const t = m[1];
|
|
177
|
+
const line = lineAt(src, at + m.index);
|
|
178
|
+
if (PHANTOM.has(t) && !local.has(t)) add('error', f, line, 'token', `${t} does not exist; use ${PHANTOM.get(t)}`);
|
|
179
|
+
else if (!m[2] && !CANON.has(t) && !local.has(t) && !isTheme) {
|
|
180
|
+
const n = themeCount.get(t) || 0;
|
|
181
|
+
add('warn', f, line, 'token', n ? `${t} exists only in ${n} theme(s); a shared file cannot rely on it` : `${t} is not in the token vocabulary (export-formats.md § The token set)`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stampRe = /Hallmark · (macrostructure|studied):/g;
|
|
188
|
+
for (const f of TEXT) {
|
|
189
|
+
const src = read(f);
|
|
190
|
+
if (/[\u2014\u2013]/.test(src)) for (const m of src.matchAll(/[\u2014\u2013]/g)) add('error', f, lineAt(src, m.index), 'dash', 'em or en dash');
|
|
191
|
+
for (const m of src.matchAll(stampRe)) add('error', f, lineAt(src, m.index), 'stamp', `old stamp field "${m[1]}:"; the stamp opens with shape, genre, theme`);
|
|
192
|
+
for (const m of src.matchAll(/(?:Hallmark[^\n]{0,200}|\*\/[^\n]{0,0})\bv(\d+\.\d+\.\d+)\b/g)) {
|
|
193
|
+
if (VERSION && m[1] !== VERSION && !/v0\.|v1\./.test(`v${m[1]}`)) add('error', f, lineAt(src, m.index), 'version', `v${m[1]} where the skill is v${VERSION}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const themeVersion = (/const VERSION = '([\d.]+)'/.exec(theme) || [])[1];
|
|
197
|
+
if (themeVersion !== VERSION) add('error', join(ROOT, 'scripts', 'theme.mjs'), 1, 'version', `theme.mjs VERSION ${themeVersion} where SKILL.md says ${VERSION}`);
|
|
198
|
+
|
|
199
|
+
for (const f of MD) {
|
|
200
|
+
const src = read(f);
|
|
201
|
+
for (const m of src.matchAll(/```html\n([\s\S]*?)```/g)) {
|
|
202
|
+
const html = m[1].replace(/<!--[\s\S]*?-->/g, '').replace(/<(script|style)[\s\S]*?<\/\1>/g, '');
|
|
203
|
+
for (const t of html.matchAll(/>([^<>]*)</g)) {
|
|
204
|
+
const text = t[1];
|
|
205
|
+
const line = lineAt(src, m.index + m[0].indexOf(m[1]) + t.index);
|
|
206
|
+
if (/[←→↗↘↩]|-->|->/.test(text)) add('error', f, line, '63', `arrow in sample copy: "${text.trim().slice(0, 50)}"`);
|
|
207
|
+
if (/\S\s·\s\S/.test(text) && !/marquee|ticker/i.test(html)) add('warn', f, line, '63', `middle-dot string in sample copy: "${text.trim().slice(0, 50)}"`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const m of src.matchAll(/```css\n([\s\S]*?)```/g)) {
|
|
211
|
+
for (const r of m[1].matchAll(/([^{}]+)\{([^}]*)\}/g)) {
|
|
212
|
+
const sel = r[1].trim(); const body = r[2];
|
|
213
|
+
const ls = /letter-spacing:\s*(-?[\d.]+)em/.exec(body);
|
|
214
|
+
if (/uppercase/.test(body) && ls && Number(ls[1]) > 0.04 && /label|caption|meta|tag|chip|eyebrow|kicker|th\b|nav|link|small|note/i.test(sel) && !/wordmark|logo|brand/i.test(sel)) add('error', f, lineAt(src, m.index + m[0].indexOf(m[1]) + r.index + r[0].length - r[0].trimStart().length), '67', `${sel} sets tracked capitals (${ls[1]}em)`);
|
|
215
|
+
if (/small-caps|smcp/.test(body)) add('error', f, lineAt(src, m.index + m[0].indexOf(m[1]) + r.index + r[0].length - r[0].trimStart().length), '67', `${sel} sets small caps`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const sloplint = read(join(ROOT, 'scripts', 'sloplint.mjs'));
|
|
221
|
+
const tiersBlock = (/const TIERS = new Map\(Object\.entries\(\{([\s\S]*?)\}\)\)/.exec(sloplint) || [])[1] || '';
|
|
222
|
+
const TIERS = new Map([...tiersBlock.matchAll(/'([\w]+)':\s*'(\w+)'/g)].map((m) => [m[1], m[2]]));
|
|
223
|
+
for (const [id, tier] of GATES) {
|
|
224
|
+
const t = TIERS.get(id);
|
|
225
|
+
if (t && t !== tier) add('error', join(ROOT, 'scripts', 'sloplint.mjs'), 1, 'tier', `sloplint tiers ${id} as ${t}; gates.md says ${tier}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const index = JSON.parse(read(join(THEME_DIR, 'index.json')));
|
|
229
|
+
const indexed = new Set(index.themes.map((t) => t.slug));
|
|
230
|
+
for (const f of themeFiles) if (!indexed.has(basename(f, '.md'))) add('error', f, 1, 'index', 'theme missing from index.json (run theme.mjs index)');
|
|
231
|
+
for (const t of index.themes) if (!existsSync(join(ROOT, t.file))) add('error', join(THEME_DIR, 'index.json'), 1, 'index', `index lists ${t.file}, which does not exist`);
|
|
232
|
+
|
|
233
|
+
const fontsSrc = read(join(ROOT, 'references', 'fonts.md'));
|
|
234
|
+
const unusedTable = fontsSrc.slice(fontsSrc.indexOf('## Display faces the catalog does not use'), fontsSrc.indexOf('## Body faces'));
|
|
235
|
+
const pairTable = fontsSrc.includes('## Pairings by tone') ? fontsSrc.slice(fontsSrc.indexOf('## Pairings by tone'), fontsSrc.indexOf('## The wordmark')) : '';
|
|
236
|
+
const catalogFaces = index.themes.map((t) => ({ name: t.name, face: t.display.face.toLowerCase().replace(/ italic$/, '') }));
|
|
237
|
+
for (const [label, table] of [['the faces-the-catalog-does-not-use table', unusedTable], ['the display column of the pairings table', pairTable.split('\n').map((l) => l.split('|')[2] || '').join('\n')]]) {
|
|
238
|
+
for (const { name, face } of catalogFaces) {
|
|
239
|
+
if (new RegExp(`(^|[\\s,|])${face.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?=[\\s,|(]|$)`, 'im').test(table.toLowerCase())) add('error', join(ROOT, 'references', 'fonts.md'), lineAt(fontsSrc, fontsSrc.toLowerCase().indexOf(face)), 'fonts', `${label} offers ${face}, which ${name} already sets as its display face`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const reach = new Set([SKILL]);
|
|
244
|
+
const queue = [SKILL];
|
|
245
|
+
while (queue.length) {
|
|
246
|
+
const f = queue.shift();
|
|
247
|
+
const src = read(f);
|
|
248
|
+
const names = new Set([...src.matchAll(/([\w-]+\.(?:md|mjs|json))/g)].map((m) => m[1]));
|
|
249
|
+
for (const n of names) for (const c of BY_BASE.get(n) || []) if (!reach.has(c)) { reach.add(c); if (c.endsWith('.md')) queue.push(c); }
|
|
250
|
+
}
|
|
251
|
+
for (const f of themeFiles) reach.add(f);
|
|
252
|
+
for (const f of [...reach]) {
|
|
253
|
+
if (!f.endsWith('.md')) continue;
|
|
254
|
+
for (const m of read(f).matchAll(/([\w-]+)\/<[\w-]+>\.md/g)) for (const c of MD) if (basename(dirname(c)) === m[1]) reach.add(c);
|
|
255
|
+
}
|
|
256
|
+
for (const f of MD) if (!reach.has(f) && basename(f) !== 'README.md') add('warn', f, 1, 'orphan', 'no file reachable from SKILL.md names this file');
|
|
257
|
+
|
|
258
|
+
const skillBytes = Buffer.byteLength(read(SKILL));
|
|
259
|
+
const front = /^---\n([\s\S]*?)\n---\n/.exec(readFileSync(SKILL, 'utf8'));
|
|
260
|
+
const fm = front ? Object.fromEntries(front[1].split('\n').map((l) => /^([a-z-]+):\s*(.*)$/.exec(l)).filter(Boolean).map((m) => [m[1], m[2]])) : {};
|
|
261
|
+
if (!fm.name || !fm.description) add('error', SKILL, 1, 'frontmatter', 'SKILL.md needs name and description in its frontmatter, or installers find no skill');
|
|
262
|
+
else if (!/^"(?:[^"\\]|\\.)*"$/.test(fm.description) || fm.description.length > 1026) add('error', SKILL, 2, 'frontmatter', 'the description must be one double-quoted line of 1,024 characters or fewer; an unquoted colon breaks strict YAML parsers');
|
|
263
|
+
if (skillBytes > 19000) add('error', SKILL, 1, 'size', `SKILL.md is ${skillBytes} bytes; keep it at 19,000 or under so the 5,000 tokens a harness re-attaches after compaction still hold Step 7`);
|
|
264
|
+
|
|
265
|
+
for (const f of themeFiles) {
|
|
266
|
+
const src = read(f);
|
|
267
|
+
const hint = /## Build hint[\s\S]*?```css\n([\s\S]*?)```/.exec(src);
|
|
268
|
+
if (!hint) continue;
|
|
269
|
+
const at = src.indexOf(hint[1]);
|
|
270
|
+
for (const m of hint[1].matchAll(/(?:^|[{;])\s*(?!--)([a-z-]+)\s*:([^;{}]*)/g)) {
|
|
271
|
+
if (/oklch\(|#[0-9a-fA-F]{3,8}\b|rgba?\(|hsla?\(/.test(m[2])) add('error', f, lineAt(src, at + m.index), 'literal', `build hint sets ${m[1]} with a colour literal; route it through a token (48)`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const exemplarDir = join(ROOT, 'scripts', 'fixtures', 'exemplars');
|
|
276
|
+
const clean = [...(existsSync(exemplarDir) ? readdirSync(exemplarDir).filter((n) => n.endsWith('.html')).map((n) => join(exemplarDir, n)) : []),
|
|
277
|
+
...['eyebrow-pass', 'tailwind-pass'].map((n) => join(ROOT, 'scripts', 'fixtures', n)).filter(existsSync)];
|
|
278
|
+
for (const target of clean) {
|
|
279
|
+
const run = spawnSync(process.execPath, [join(ROOT, 'scripts', 'sloplint.mjs'), target, '--json'], { encoding: 'utf8' });
|
|
280
|
+
let s = null;
|
|
281
|
+
try { s = JSON.parse(run.stdout).summary; } catch {}
|
|
282
|
+
if (!s) add('error', target, 1, 'lint', 'sloplint did not run on this file');
|
|
283
|
+
else if (s.fails || s.reflex || s.warns) add('error', target, 1, 'lint', `must lint clean, has ${s.fails} FAIL, ${s.reflex} REFLEX, ${s.warns} WARN`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const FIX = join(ROOT, 'scripts', 'fixtures');
|
|
287
|
+
const lintJson = (t) => { const r = spawnSync(process.execPath, [join(ROOT, 'scripts', 'sloplint.mjs'), t, '--json'], { encoding: 'utf8' }); try { return JSON.parse(r.stdout); } catch { return null; } };
|
|
288
|
+
const gatesOf = (j) => new Set(j.findings.map((x) => String(x.gate)));
|
|
289
|
+
const expectations = [
|
|
290
|
+
['eyebrow-fail', (j) => j.findings.filter((x) => x.gate === '54' && x.grade === 'FAIL').length === 6, 'gate 54 must FAIL six times'],
|
|
291
|
+
['eyebrow-pass', (j) => !j.findings.some((x) => x.gate === '54'), 'gate 54 must not fire'],
|
|
292
|
+
['waiver', (j) => ['fails:0', 'reflex:0', 'waived:5', 'rejected:2', 'answered:1', 'notes:1'].every((kv) => { const [k, v] = kv.split(':'); return j.summary[k] === Number(v); }), 'must report fails 0, reflex 0, waived 5, rejected 2, answered 1, notes 1'],
|
|
293
|
+
['slopped.html', (j) => ['1', '2a', '4', '7b', '10a', '15', '19', '20', '22', '46a', '48', '54'].every((g) => gatesOf(j).has(g)), 'must fire 1, 2a, 4, 7b, 10a, 15, 19, 20, 22, 46a, 48 and 54'],
|
|
294
|
+
['tailwind-fail', (j) => ['67', '54', '64', '10a', '12', '11', '2a', '2b', '29', '6a', '34', '48', '74', '3'].every((g) => gatesOf(j).has(g)), 'must fire 67, 54, 64, 10a, 12, 11, 2a, 2b, 29, 6a, 34, 48, 74 and 3'],
|
|
295
|
+
];
|
|
296
|
+
for (const [name, ok, msg] of existsSync(FIX) ? expectations : []) {
|
|
297
|
+
const target = join(FIX, name);
|
|
298
|
+
if (!existsSync(target)) { add('error', target, 1, 'fixture', 'missing'); continue; }
|
|
299
|
+
const j = lintJson(target);
|
|
300
|
+
if (!j) add('error', target, 1, 'fixture', 'sloplint did not run on this fixture');
|
|
301
|
+
else if (!ok(j)) add('error', target, 1, 'fixture', msg);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const keptDir = join(FIX, 'kept');
|
|
305
|
+
if (existsSync(keptDir)) {
|
|
306
|
+
const tmp = mkdtempSync(join(tmpdir(), 'hallmark-kept-'));
|
|
307
|
+
try {
|
|
308
|
+
const run = (flag) => spawnSync(process.execPath, [join(ROOT, 'scripts', 'sloplint.mjs'), 'index.html', flag, '--json'], { encoding: 'utf8', cwd: tmp });
|
|
309
|
+
copyFileSync(join(keptDir, 'before.html'), join(tmp, 'index.html'));
|
|
310
|
+
run('--before');
|
|
311
|
+
copyFileSync(join(keptDir, 'after.html'), join(tmp, 'index.html'));
|
|
312
|
+
let j = null;
|
|
313
|
+
try { j = JSON.parse(run('--kept').stdout); } catch {}
|
|
314
|
+
const kept = j ? j.findings.filter((x) => x.gate === 'kept') : [];
|
|
315
|
+
const fails = kept.filter((x) => x.grade === 'FAIL').map((x) => x.evidence).join(' | ');
|
|
316
|
+
const warns = kept.filter((x) => x.grade === 'WARN').map((x) => x.evidence).join(' | ');
|
|
317
|
+
const want = [[fails, 'rooms.html'], [fails, 'nights'], [fails, "track('book')"], [fails, 'book-button'], [fails, '#nav-toggle'], [warns, 'nav-toggle']];
|
|
318
|
+
if (!j) add('error', keptDir, 1, 'fixture', 'sloplint --kept did not run on the kept fixture');
|
|
319
|
+
else if (!want.every(([hay, needle]) => hay.includes(needle))) add('error', keptDir, 1, 'fixture', 'the kept check must fail the lost link, field, handler, test hook and script hook, and warn on the lost id');
|
|
320
|
+
else if (fails.includes('mailto:')) add('error', keptDir, 1, 'fixture', 'a link recorded as dropped in the stamp must not fail');
|
|
321
|
+
} finally { rmSync(tmp, { recursive: true, force: true }); }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const REPO = resolve(ROOT, '..', '..');
|
|
325
|
+
const pkgFile = join(REPO, 'package.json');
|
|
326
|
+
if (existsSync(pkgFile)) {
|
|
327
|
+
let pkg = null;
|
|
328
|
+
try { pkg = JSON.parse(read(pkgFile)); } catch {}
|
|
329
|
+
if (pkg && pkg.name === 'hallmark') {
|
|
330
|
+
if (pkg.version !== VERSION) add('error', pkgFile, 1, 'version', `package.json says ${pkg.version} where SKILL.md says ${VERSION}`);
|
|
331
|
+
const docs = ['README.md', 'ROADMAP.md', ...(existsSync(join(REPO, 'docs')) ? readdirSync(join(REPO, 'docs')).filter((n) => n.endsWith('.md')).map((n) => join('docs', n)) : [])].map((n) => join(REPO, n)).filter(existsSync);
|
|
332
|
+
for (const f of docs) {
|
|
333
|
+
const src = read(f);
|
|
334
|
+
for (const m of src.matchAll(/\]\(([^)\s]+)\)/g)) {
|
|
335
|
+
const t = m[1].split('#')[0].split('?')[0];
|
|
336
|
+
if (!t || /^(https?:|mailto:|data:)/.test(m[1])) continue;
|
|
337
|
+
if (!existsSync(resolve(dirname(f), t))) add('error', f, lineAt(src, m.index), 'link', `${m[1]} does not resolve`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const shown = ONLY_ERRORS ? findings.filter((x) => x.level === 'error') : findings;
|
|
344
|
+
const errors = findings.filter((x) => x.level === 'error').length;
|
|
345
|
+
const warns = findings.length - errors;
|
|
346
|
+
if (AS_JSON) console.log(JSON.stringify({ errors, warns, findings: shown }, null, 1));
|
|
347
|
+
else {
|
|
348
|
+
shown.sort((a, b) => (a.level === b.level ? a.file.localeCompare(b.file) || a.line - b.line : a.level === 'error' ? -1 : 1));
|
|
349
|
+
for (const x of shown) console.log(`${x.level === 'error' ? 'ERROR' : 'warn '} ${x.file}:${x.line} [${x.rule}] ${x.msg}`);
|
|
350
|
+
console.log(errors || warns ? `refcheck: ${errors} error(s), ${warns} warning(s) across ${MD.length} reference files` : `refcheck: clean across ${MD.length} reference files`);
|
|
351
|
+
}
|
|
352
|
+
process.exit(errors ? 1 : 0);
|