@erclx/canon 4.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/LICENSE +21 -0
- package/README.md +129 -0
- package/claude/.claude-plugin/plugin.json +19 -0
- package/claude/skills/bash-cli-script/REQUIREMENT.md +42 -0
- package/claude/skills/bash-cli-script/SKILL.md +48 -0
- package/claude/skills/bash-cli-script/references/template.md +43 -0
- package/claude/skills/bash-script/REQUIREMENT.md +36 -0
- package/claude/skills/bash-script/SKILL.md +100 -0
- package/claude/skills/bash-script/references/patterns.md +349 -0
- package/claude/skills/canon-cli/REQUIREMENT.md +41 -0
- package/claude/skills/canon-cli/SKILL.md +103 -0
- package/claude/skills/canon-feedback-file/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-file/SKILL.md +80 -0
- package/claude/skills/canon-feedback-triage/REQUIREMENT.md +40 -0
- package/claude/skills/canon-feedback-triage/SKILL.md +63 -0
- package/claude/skills/canon-operator/REQUIREMENT.md +61 -0
- package/claude/skills/canon-operator/SKILL.md +108 -0
- package/claude/skills/canon-rollout/REQUIREMENT.md +59 -0
- package/claude/skills/canon-rollout/SKILL.md +147 -0
- package/claude/skills/canon-screencast/REQUIREMENT.md +39 -0
- package/claude/skills/canon-screencast/SKILL.md +167 -0
- package/claude/skills/canon-slides-draft/REQUIREMENT.md +39 -0
- package/claude/skills/canon-slides-draft/SKILL.md +62 -0
- package/claude/skills/ci-workflow/REQUIREMENT.md +40 -0
- package/claude/skills/ci-workflow/SKILL.md +65 -0
- package/claude/skills/ci-workflow/references/workflows.md +98 -0
- package/claude/skills/claude-address-review/REQUIREMENT.md +57 -0
- package/claude/skills/claude-address-review/SKILL.md +212 -0
- package/claude/skills/claude-address-review/references/rebase-conflicts.md +39 -0
- package/claude/skills/claude-autoship/REQUIREMENT.md +50 -0
- package/claude/skills/claude-autoship/SKILL.md +207 -0
- package/claude/skills/claude-design-extract/REQUIREMENT.md +42 -0
- package/claude/skills/claude-design-extract/SKILL.md +102 -0
- package/claude/skills/claude-diagram/REQUIREMENT.md +45 -0
- package/claude/skills/claude-diagram/SKILL.md +177 -0
- package/claude/skills/claude-docs/REQUIREMENT.md +60 -0
- package/claude/skills/claude-docs/SKILL.md +287 -0
- package/claude/skills/claude-docs/references/anchor-sweep.md +58 -0
- package/claude/skills/claude-docs/references/wireframe-sweep.md +45 -0
- package/claude/skills/claude-feature/REQUIREMENT.md +36 -0
- package/claude/skills/claude-feature/SKILL.md +115 -0
- package/claude/skills/claude-groundwork/REQUIREMENT.md +48 -0
- package/claude/skills/claude-groundwork/SKILL.md +142 -0
- package/claude/skills/claude-intake/REQUIREMENT.md +49 -0
- package/claude/skills/claude-intake/SKILL.md +114 -0
- package/claude/skills/claude-intake-answer/REQUIREMENT.md +48 -0
- package/claude/skills/claude-intake-answer/SKILL.md +90 -0
- package/claude/skills/claude-markdown-propose/REQUIREMENT.md +48 -0
- package/claude/skills/claude-markdown-propose/SKILL.md +118 -0
- package/claude/skills/claude-markdown-propose/references/format.md +107 -0
- package/claude/skills/claude-memory-capture/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-capture/SKILL.md +101 -0
- package/claude/skills/claude-memory-review/REQUIREMENT.md +50 -0
- package/claude/skills/claude-memory-review/SKILL.md +210 -0
- package/claude/skills/claude-memory-review/references/receipt-format.md +48 -0
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +121 -0
- package/claude/skills/claude-orchestrate/SKILL.md +241 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +132 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-handoff.md +32 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-parked.md +72 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-poll.md +87 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-resume.md +30 -0
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +21 -0
- package/claude/skills/claude-orchestrate/scripts/poll.sh +373 -0
- package/claude/skills/claude-orchestrate/scripts/watch.sh +181 -0
- package/claude/skills/claude-pr-review/REQUIREMENT.md +47 -0
- package/claude/skills/claude-pr-review/SKILL.md +295 -0
- package/claude/skills/claude-review/REQUIREMENT.md +39 -0
- package/claude/skills/claude-review/SKILL.md +142 -0
- package/claude/skills/claude-seed-sync/REQUIREMENT.md +45 -0
- package/claude/skills/claude-seed-sync/SKILL.md +156 -0
- package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
- package/claude/skills/claude-standards-audit/SKILL.md +99 -0
- package/claude/skills/claude-tasks/REQUIREMENT.md +43 -0
- package/claude/skills/claude-tasks/SKILL.md +161 -0
- package/claude/skills/claude-teach/REQUIREMENT.md +56 -0
- package/claude/skills/claude-teach/SKILL.md +196 -0
- package/claude/skills/claude-teach/references/lesson-craft.md +59 -0
- package/claude/skills/claude-teach/references/pedagogy.md +67 -0
- package/claude/skills/claude-teach/references/promotion.md +54 -0
- package/claude/skills/claude-ui-test/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ui-test/SKILL.md +77 -0
- package/claude/skills/claude-ux-audit/REQUIREMENT.md +40 -0
- package/claude/skills/claude-ux-audit/SKILL.md +79 -0
- package/claude/skills/claude-ux-measure/REQUIREMENT.md +48 -0
- package/claude/skills/claude-ux-measure/SKILL.md +122 -0
- package/claude/skills/claude-worker/REQUIREMENT.md +54 -0
- package/claude/skills/claude-worker/SKILL.md +96 -0
- package/claude/skills/claude-worktree/REQUIREMENT.md +58 -0
- package/claude/skills/claude-worktree/SKILL.md +136 -0
- package/claude/skills/create-rule/REQUIREMENT.md +45 -0
- package/claude/skills/create-rule/SKILL.md +68 -0
- package/claude/skills/create-skill/REQUIREMENT.md +38 -0
- package/claude/skills/create-skill/SKILL.md +32 -0
- package/claude/skills/create-snippet/REQUIREMENT.md +39 -0
- package/claude/skills/create-snippet/SKILL.md +30 -0
- package/claude/skills/create-standard/REQUIREMENT.md +35 -0
- package/claude/skills/create-standard/SKILL.md +29 -0
- package/claude/skills/decision-escalate/REQUIREMENT.md +45 -0
- package/claude/skills/decision-escalate/SKILL.md +79 -0
- package/claude/skills/docs-sync/REQUIREMENT.md +41 -0
- package/claude/skills/docs-sync/SKILL.md +95 -0
- package/claude/skills/git-branch/REQUIREMENT.md +38 -0
- package/claude/skills/git-branch/SKILL.md +60 -0
- package/claude/skills/git-commit/REQUIREMENT.md +36 -0
- package/claude/skills/git-commit/SKILL.md +49 -0
- package/claude/skills/git-followup/REQUIREMENT.md +43 -0
- package/claude/skills/git-followup/SKILL.md +48 -0
- package/claude/skills/git-issue/REQUIREMENT.md +38 -0
- package/claude/skills/git-issue/SKILL.md +65 -0
- package/claude/skills/git-pr/REQUIREMENT.md +50 -0
- package/claude/skills/git-pr/SKILL.md +164 -0
- package/claude/skills/git-pr/references/labels.md +95 -0
- package/claude/skills/git-ship/REQUIREMENT.md +42 -0
- package/claude/skills/git-ship/SKILL.md +55 -0
- package/claude/skills/git-split/REQUIREMENT.md +39 -0
- package/claude/skills/git-split/SKILL.md +162 -0
- package/claude/skills/git-stage/REQUIREMENT.md +39 -0
- package/claude/skills/git-stage/SKILL.md +73 -0
- package/claude/skills/git-worktree/REQUIREMENT.md +38 -0
- package/claude/skills/git-worktree/SKILL.md +130 -0
- package/claude/skills/migration-claude-md/REQUIREMENT.md +40 -0
- package/claude/skills/migration-claude-md/SKILL.md +76 -0
- package/claude/skills/migration-context/REQUIREMENT.md +36 -0
- package/claude/skills/migration-context/SKILL.md +95 -0
- package/claude/skills/migration-standards-drop/REQUIREMENT.md +55 -0
- package/claude/skills/migration-standards-drop/SKILL.md +113 -0
- package/claude/skills/migration-superseded/REQUIREMENT.md +44 -0
- package/claude/skills/migration-superseded/SKILL.md +115 -0
- package/claude/skills/project-commands/REQUIREMENT.md +42 -0
- package/claude/skills/project-commands/SKILL.md +85 -0
- package/claude/skills/restate-plainly/REQUIREMENT.md +41 -0
- package/claude/skills/restate-plainly/SKILL.md +39 -0
- package/claude/skills/session-map/REQUIREMENT.md +57 -0
- package/claude/skills/session-map/SKILL.md +70 -0
- package/claude/skills/session-resume/REQUIREMENT.md +49 -0
- package/claude/skills/session-resume/SKILL.md +51 -0
- package/claude/skills/setup-gov/REQUIREMENT.md +37 -0
- package/claude/skills/setup-gov/SKILL.md +77 -0
- package/claude/skills/setup-indexes/REQUIREMENT.md +45 -0
- package/claude/skills/setup-indexes/SKILL.md +153 -0
- package/claude/skills/setup-init/REQUIREMENT.md +45 -0
- package/claude/skills/setup-init/SKILL.md +127 -0
- package/claude/skills/setup-plugins/REQUIREMENT.md +42 -0
- package/claude/skills/setup-plugins/SKILL.md +81 -0
- package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
- package/claude/skills/setup-verify/REQUIREMENT.md +39 -0
- package/claude/skills/setup-verify/SKILL.md +51 -0
- package/claude/skills/systematic-debugging/REQUIREMENT.md +41 -0
- package/claude/skills/systematic-debugging/SKILL.md +70 -0
- package/claude/skills/write-human/REQUIREMENT.md +46 -0
- package/claude/skills/write-human/SKILL.md +68 -0
- package/claude/skills/write-human/references/density.md +38 -0
- package/claude/skills/write-human/references/machine-tells.md +107 -0
- package/claude/skills/write-human/references/source-material.md +37 -0
- package/claude/skills/youtube-transcripts/REQUIREMENT.md +38 -0
- package/claude/skills/youtube-transcripts/SKILL.md +34 -0
- package/docs/agents/audits.md +98 -0
- package/docs/agents/capture.md +37 -0
- package/docs/agents/census.md +23 -0
- package/docs/agents/commands.md +146 -0
- package/docs/agents/comments.md +34 -0
- package/docs/agents/context-audit-checks.md +120 -0
- package/docs/agents/context-audit.md +83 -0
- package/docs/agents/counts.md +76 -0
- package/docs/agents/demo.md +86 -0
- package/docs/agents/docs.md +17 -0
- package/docs/agents/gate.md +84 -0
- package/docs/agents/index.md +47 -0
- package/docs/agents/indexes.md +35 -0
- package/docs/agents/install-and-sync.md +385 -0
- package/docs/agents/intake.md +81 -0
- package/docs/agents/key-changes.md +103 -0
- package/docs/agents/label-coverage.md +73 -0
- package/docs/agents/markdown-audit.md +197 -0
- package/docs/agents/output-shape.md +70 -0
- package/docs/agents/overview.md +26 -0
- package/docs/agents/records.md +170 -0
- package/docs/agents/restated.md +81 -0
- package/docs/agents/review-classification.md +77 -0
- package/docs/agents/routing.md +61 -0
- package/docs/agents/rule-citations.md +98 -0
- package/docs/agents/sandbox.md +71 -0
- package/docs/agents/scripting.md +149 -0
- package/docs/agents/sessions.md +120 -0
- package/docs/agents/skills-audit.md +94 -0
- package/docs/agents/skills-reach.md +64 -0
- package/docs/agents/standards-audit.md +38 -0
- package/docs/agents/state-scoped-risk.md +105 -0
- package/docs/agents/superseded.md +85 -0
- package/docs/agents/targets.md +83 -0
- package/docs/agents/tasks.md +200 -0
- package/docs/agents/teach.md +158 -0
- package/docs/agents/test-order.md +56 -0
- package/docs/agents/worktrees.md +62 -0
- package/docs/ai-workflow.md +317 -0
- package/docs/index.md +23 -0
- package/docs/operating-model.md +223 -0
- package/docs/target-projects.md +258 -0
- package/docs/visual-design-workflow.md +151 -0
- package/docs/zshrc-aliases.md +65 -0
- package/governance/rules/ci/700-ci-workflow.md +44 -0
- package/governance/rules/claude/500-prose.md +15 -0
- package/governance/rules/claude/501-markdown.md +14 -0
- package/governance/rules/claude/510-context.md +28 -0
- package/governance/rules/claude/511-indexes.md +14 -0
- package/governance/rules/claude/520-wireframes.md +12 -0
- package/governance/rules/claude/530-requirements.md +11 -0
- package/governance/rules/claude/540-architecture.md +11 -0
- package/governance/rules/claude/550-design.md +11 -0
- package/governance/rules/claude/555-tasks.md +12 -0
- package/governance/rules/claude/556-groundwork.md +11 -0
- package/governance/rules/claude/557-intake.md +11 -0
- package/governance/rules/claude/558-plan.md +22 -0
- package/governance/rules/claude/559-memory.md +11 -0
- package/governance/rules/claude/560-diagrams.md +18 -0
- package/governance/rules/claude/561-teach.md +13 -0
- package/governance/rules/claude/562-session.md +15 -0
- package/governance/rules/claude/570-skill.md +24 -0
- package/governance/rules/claude/575-hooks.md +17 -0
- package/governance/rules/claude/576-settings.md +14 -0
- package/governance/rules/claude/580-readme.md +11 -0
- package/governance/rules/claude/590-rule-authoring.md +12 -0
- package/governance/rules/claude/591-standard-authoring.md +12 -0
- package/governance/rules/claude/592-claude-md.md +19 -0
- package/governance/rules/core/000-constitution.md +30 -0
- package/governance/rules/core/005-behavior.md +27 -0
- package/governance/rules/core/010-testing.md +35 -0
- package/governance/rules/core/015-output.md +20 -0
- package/governance/rules/core/020-concurrency.md +22 -0
- package/governance/rules/core/025-indexes.md +9 -0
- package/governance/rules/core/030-error-handling.md +31 -0
- package/governance/rules/core/035-tasks.md +13 -0
- package/governance/rules/core/040-performance.md +20 -0
- package/governance/rules/core/045-memory.md +12 -0
- package/governance/rules/core/050-logging.md +20 -0
- package/governance/rules/core/055-scratch.md +9 -0
- package/governance/rules/core/060-naming.md +19 -0
- package/governance/rules/core/065-spelling.md +19 -0
- package/governance/rules/core/070-planning.md +18 -0
- package/governance/rules/core/075-dependencies.md +25 -0
- package/governance/rules/core/080-config-comments.md +22 -0
- package/governance/rules/core/085-worktrees.md +17 -0
- package/governance/rules/core/087-git.md +11 -0
- package/governance/rules/core/090-code-comments.md +39 -0
- package/governance/rules/framework/200-react.md +51 -0
- package/governance/rules/framework/210-astro.md +41 -0
- package/governance/rules/framework/220-fastapi.md +43 -0
- package/governance/rules/framework/230-nextjs.md +48 -0
- package/governance/rules/framework/250-tailwind.md +32 -0
- package/governance/rules/framework/260-shadcn.md +34 -0
- package/governance/rules/lang/100-typescript.md +40 -0
- package/governance/rules/lang/110-python.md +42 -0
- package/governance/rules/lang/120-bash.md +19 -0
- package/governance/rules/lib/300-testing-ts.md +39 -0
- package/governance/rules/lib/305-e2e-reliability.md +34 -0
- package/governance/rules/lib/306-test-scope.md +25 -0
- package/governance/rules/lib/310-zod.md +25 -0
- package/governance/rules/lib/320-tanstack-query.md +32 -0
- package/governance/rules/lib/330-testing-py.md +44 -0
- package/governance/rules/lib/340-pydantic.md +38 -0
- package/governance/rules/lib/350-security-web.md +32 -0
- package/governance/rules/lib/360-security-server.md +39 -0
- package/governance/rules/lib/370-database.md +35 -0
- package/governance/rules/snippets/505-at-references.md +9 -0
- package/governance/rules/ui/400-ui.md +36 -0
- package/governance/rules/ui/410-a11y.md +48 -0
- package/governance/rules/ui/420-forms.md +36 -0
- package/governance/rules/ui/430-ux-completeness.md +65 -0
- package/governance/rules/ui/440-surface-capture.md +34 -0
- package/governance/rules/ui/450-link-behavior.md +19 -0
- package/governance/stacks/astro.toml +2 -0
- package/governance/stacks/base.toml +8 -0
- package/governance/stacks/node-server.toml +2 -0
- package/governance/stacks/node.toml +2 -0
- package/governance/stacks/python-fastapi.toml +2 -0
- package/governance/stacks/python.toml +2 -0
- package/governance/stacks/react.toml +2 -0
- package/package.json +69 -0
- package/scripts/config.sh +11 -0
- package/scripts/core/bootstrap.sh +81 -0
- package/scripts/core/check-color-source.sh +41 -0
- package/scripts/core/check-ignore-parity.sh +162 -0
- package/scripts/core/check-plugin-boundary.sh +45 -0
- package/scripts/core/check-seed-independence.sh +59 -0
- package/scripts/core/check-skill-paths.sh +24 -0
- package/scripts/core/clean.sh +36 -0
- package/scripts/core/install-check.sh +101 -0
- package/scripts/core/list-seed-roots.sh +18 -0
- package/scripts/core/regen-claude-copies.sh +10 -0
- package/scripts/core/regen-hero.sh +217 -0
- package/scripts/core/regen-indexes.sh +10 -0
- package/scripts/core/regen-tooling-paths.sh +61 -0
- package/scripts/core/repair-bare-flag.sh +19 -0
- package/scripts/core/snapshot.sh +134 -0
- package/scripts/core/update.sh +35 -0
- package/scripts/docs/list.sh +165 -0
- package/scripts/lib/frontmatter.sh +30 -0
- package/scripts/lib/gov.sh +14 -0
- package/scripts/lib/sandbox-fixtures.sh +191 -0
- package/scripts/lib/sandbox-git.sh +125 -0
- package/scripts/lib/sandbox-path.sh +206 -0
- package/scripts/lib/tooling.sh +35 -0
- package/scripts/lib/ui.sh +266 -0
- package/scripts/lib/worktree.sh +20 -0
- package/scripts/manage-sandbox.sh +466 -0
- package/scripts/snippets/create.sh +156 -0
- package/scripts/standards/list.sh +115 -0
- package/scripts/tooling/create.sh +109 -0
- package/scripts/tooling/verify.sh +179 -0
- package/snippets/align.md +12 -0
- package/snippets/claude/decision-memo.md +39 -0
- package/snippets/claude/feature-recap.md +19 -0
- package/snippets/claude/figma-steps.md +24 -0
- package/snippets/compact-summary.md +5 -0
- package/snippets/decision-help.md +6 -0
- package/snippets/meta-prompt.md +14 -0
- package/snippets/research-prompt.md +7 -0
- package/snippets/session-notes.md +11 -0
- package/snippets/snippets.toml +5 -0
- package/snippets/step-by-step.md +10 -0
- package/snippets/web-research.md +21 -0
- package/src/audits/baseline.ts +201 -0
- package/src/audits/catalog.ts +876 -0
- package/src/audits/run.ts +204 -0
- package/src/autoship/classify.ts +75 -0
- package/src/autoship/paths.ts +51 -0
- package/src/binary.ts +16 -0
- package/src/browser/engine.ts +40 -0
- package/src/census/count.ts +113 -0
- package/src/claude/cases/all.ts +24 -0
- package/src/claude/cases/authoring.ts +53 -0
- package/src/claude/cases/claude-workflow.ts +158 -0
- package/src/claude/cases/git.ts +44 -0
- package/src/claude/cases/misc.ts +27 -0
- package/src/claude/cases/setup.ts +94 -0
- package/src/claude/gitignore.ts +51 -0
- package/src/claude/routing.ts +283 -0
- package/src/claude/seeds-list.ts +47 -0
- package/src/claude/seeds.ts +150 -0
- package/src/claude/settings.ts +151 -0
- package/src/claude/skills-audit.ts +228 -0
- package/src/claude/skills-drift.ts +156 -0
- package/src/claude/skills-list.ts +99 -0
- package/src/claude/skills-rank.ts +320 -0
- package/src/claude/skills-reach.ts +227 -0
- package/src/cli-run.ts +43 -0
- package/src/cli.ts +200 -0
- package/src/commands/audits.ts +350 -0
- package/src/commands/autoship.ts +129 -0
- package/src/commands/capture.ts +133 -0
- package/src/commands/census.ts +105 -0
- package/src/commands/claude.ts +1286 -0
- package/src/commands/comments.ts +240 -0
- package/src/commands/context.ts +857 -0
- package/src/commands/demo.ts +389 -0
- package/src/commands/deps.ts +173 -0
- package/src/commands/design.ts +36 -0
- package/src/commands/docs.ts +60 -0
- package/src/commands/feedback-format.ts +23 -0
- package/src/commands/feedback.ts +112 -0
- package/src/commands/gate.ts +189 -0
- package/src/commands/gov.ts +1265 -0
- package/src/commands/indexes.ts +184 -0
- package/src/commands/init.ts +113 -0
- package/src/commands/intake.ts +406 -0
- package/src/commands/inventory.ts +256 -0
- package/src/commands/labels.ts +361 -0
- package/src/commands/markdown.ts +544 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/pass-through.ts +39 -0
- package/src/commands/pr.ts +411 -0
- package/src/commands/records.ts +728 -0
- package/src/commands/sandbox.ts +468 -0
- package/src/commands/secrets.ts +132 -0
- package/src/commands/serve.ts +159 -0
- package/src/commands/sessions.ts +408 -0
- package/src/commands/slides.ts +126 -0
- package/src/commands/snippets.ts +84 -0
- package/src/commands/standards.ts +247 -0
- package/src/commands/sync.ts +428 -0
- package/src/commands/targets.ts +319 -0
- package/src/commands/tasks.ts +743 -0
- package/src/commands/teach.ts +786 -0
- package/src/commands/tooling.ts +573 -0
- package/src/commands/transcripts.ts +44 -0
- package/src/commands/upgrade.ts +231 -0
- package/src/commands/wiki.ts +100 -0
- package/src/commands/worktrees.ts +191 -0
- package/src/comments/scan.ts +338 -0
- package/src/comments/trend.ts +207 -0
- package/src/comments/vocabulary.ts +85 -0
- package/src/context/architecture.ts +364 -0
- package/src/context/audit.ts +790 -0
- package/src/context/citations.ts +196 -0
- package/src/context/folders.ts +186 -0
- package/src/context/gate.ts +57 -0
- package/src/context/index-drift.ts +64 -0
- package/src/context/narration.ts +99 -0
- package/src/copy.ts +30 -0
- package/src/counts/catalogs.ts +96 -0
- package/src/counts/numbers.ts +79 -0
- package/src/counts/scan.ts +314 -0
- package/src/demo/beats.ts +135 -0
- package/src/demo/compile.ts +326 -0
- package/src/demo/container.ts +63 -0
- package/src/demo/cursors.ts +55 -0
- package/src/demo/drive.ts +357 -0
- package/src/demo/pointer.ts +178 -0
- package/src/demo/theme.ts +112 -0
- package/src/deps/audit.ts +153 -0
- package/src/design/parse.ts +116 -0
- package/src/design/render.ts +249 -0
- package/src/docs/read.ts +77 -0
- package/src/exec.ts +16 -0
- package/src/exempt-marker.ts +43 -0
- package/src/frontmatter.ts +13 -0
- package/src/gate/measures.ts +682 -0
- package/src/gate/sequencer.ts +386 -0
- package/src/gate/stages.ts +412 -0
- package/src/git-env.ts +36 -0
- package/src/git-files.ts +105 -0
- package/src/git-ignore.ts +46 -0
- package/src/github-format.ts +13 -0
- package/src/github.ts +24 -0
- package/src/gov/adapter.ts +103 -0
- package/src/gov/citations.ts +514 -0
- package/src/gov/consumed.ts +129 -0
- package/src/gov/install.ts +132 -0
- package/src/gov/list.ts +106 -0
- package/src/gov/payload.ts +39 -0
- package/src/gov/restated.ts +814 -0
- package/src/gov/stacks.ts +205 -0
- package/src/gov/superseded.ts +415 -0
- package/src/gov/test-order.ts +407 -0
- package/src/indexes/frontmatter.ts +46 -0
- package/src/indexes/regen.ts +84 -0
- package/src/indexes/render.ts +201 -0
- package/src/indexes/walk.ts +83 -0
- package/src/init/flags.ts +60 -0
- package/src/init/plan.ts +114 -0
- package/src/init/run.ts +46 -0
- package/src/init/steps.ts +77 -0
- package/src/intake/folder.ts +320 -0
- package/src/intake/items.ts +174 -0
- package/src/inventory/config.ts +117 -0
- package/src/inventory/group.ts +76 -0
- package/src/inventory/subjects.ts +114 -0
- package/src/inventory/walk.ts +129 -0
- package/src/labels/audit.ts +82 -0
- package/src/labels/coverage.ts +79 -0
- package/src/labels/map.ts +101 -0
- package/src/labels/phase.ts +95 -0
- package/src/markdown/bans.ts +94 -0
- package/src/markdown/files.ts +102 -0
- package/src/markdown/gate.ts +28 -0
- package/src/markdown/scan.ts +295 -0
- package/src/markdown/structure.ts +730 -0
- package/src/migrate/apply.ts +115 -0
- package/src/migrate/plan.ts +103 -0
- package/src/migrate/rename.ts +183 -0
- package/src/pr/bijection.ts +145 -0
- package/src/pr/paths.ts +335 -0
- package/src/process/harness.ts +167 -0
- package/src/project-root.ts +19 -0
- package/src/records/backup.ts +455 -0
- package/src/records/migrate.ts +78 -0
- package/src/records/size.ts +260 -0
- package/src/records/validate.ts +1162 -0
- package/src/sandbox/census.ts +228 -0
- package/src/sandbox/coverage.ts +115 -0
- package/src/sandbox/expect.ts +629 -0
- package/src/sandbox/tree.ts +47 -0
- package/src/secrets/marker.ts +30 -0
- package/src/secrets/patterns.ts +142 -0
- package/src/secrets/scan.ts +123 -0
- package/src/secrets/shipped.ts +111 -0
- package/src/seed-marker.ts +74 -0
- package/src/serve/static.ts +322 -0
- package/src/sessions/claim.ts +85 -0
- package/src/sessions/live.ts +79 -0
- package/src/sessions/registry.ts +137 -0
- package/src/sessions/resolve.ts +333 -0
- package/src/slides/layouts.ts +391 -0
- package/src/slides/open.ts +18 -0
- package/src/slides/parse.ts +84 -0
- package/src/slides/render.ts +88 -0
- package/src/slides/styles.ts +44 -0
- package/src/snippets/categories.ts +66 -0
- package/src/snippets/list.ts +32 -0
- package/src/snippets/presets.ts +50 -0
- package/src/standards/audit.ts +132 -0
- package/src/standards/read.ts +100 -0
- package/src/sync/check.ts +692 -0
- package/src/sync/engine.ts +576 -0
- package/src/sync/git.ts +225 -0
- package/src/sync/history.ts +123 -0
- package/src/sync/layout.ts +140 -0
- package/src/sync/reverse.ts +268 -0
- package/src/sync/seeds-report.ts +126 -0
- package/src/sync/stamp.ts +358 -0
- package/src/sync/target.ts +70 -0
- package/src/sync/workflow.ts +200 -0
- package/src/target.ts +43 -0
- package/src/targets/pulls.ts +250 -0
- package/src/targets/registry.ts +203 -0
- package/src/targets/resolve.ts +145 -0
- package/src/targets/sweep.ts +246 -0
- package/src/tasks/archive.ts +506 -0
- package/src/tasks/record.ts +311 -0
- package/src/tasks/trunk.ts +89 -0
- package/src/tasks/validate.ts +1001 -0
- package/src/teach/lesson.ts +180 -0
- package/src/teach/workspace.ts +842 -0
- package/src/tooling/gitignore.ts +122 -0
- package/src/tooling/inject.ts +193 -0
- package/src/tooling/list.ts +39 -0
- package/src/tooling/manifest.ts +176 -0
- package/src/tooling/package.ts +166 -0
- package/src/tooling/read.ts +65 -0
- package/src/tooling/scan.ts +147 -0
- package/src/tooling/stamp.ts +44 -0
- package/src/transcripts/fetch.ts +156 -0
- package/src/transcripts/metadata.ts +54 -0
- package/src/transcripts/vtt.ts +114 -0
- package/src/ui.ts +266 -0
- package/src/version/compare.ts +53 -0
- package/src/version/installed.ts +40 -0
- package/src/version/manager.ts +67 -0
- package/src/version/skew.ts +192 -0
- package/src/wiki/init.ts +85 -0
- package/src/worktree.ts +143 -0
- package/src/worktrees/reclaim.ts +306 -0
- package/standards/architecture.md +72 -0
- package/standards/branch.md +59 -0
- package/standards/commit.md +72 -0
- package/standards/context.md +151 -0
- package/standards/design.md +93 -0
- package/standards/diagrams.md +152 -0
- package/standards/glossary.md +75 -0
- package/standards/groundwork.md +211 -0
- package/standards/index.md +36 -0
- package/standards/intake.md +192 -0
- package/standards/issue.md +94 -0
- package/standards/markdown.md +137 -0
- package/standards/memory.md +144 -0
- package/standards/plan.md +172 -0
- package/standards/pr.md +139 -0
- package/standards/publish.md +51 -0
- package/standards/readme.md +208 -0
- package/standards/requirements.md +70 -0
- package/standards/rule.md +118 -0
- package/standards/session.md +109 -0
- package/standards/skill.md +300 -0
- package/standards/slug.md +39 -0
- package/standards/snippets.md +76 -0
- package/standards/standard.md +170 -0
- package/standards/tasks.md +254 -0
- package/standards/teach.md +153 -0
- package/standards/versioning.md +71 -0
- package/standards/wireframes.md +113 -0
- package/tooling/astro/configs/astro.config.mjs +31 -0
- package/tooling/astro/configs/eslint.config.js +79 -0
- package/tooling/astro/configs/playwright.config.ts +26 -0
- package/tooling/astro/configs/tsconfig.json +12 -0
- package/tooling/astro/configs/vitest.config.ts +22 -0
- package/tooling/astro/manifest.toml +34 -0
- package/tooling/astro/reference.md +60 -0
- package/tooling/base/configs/.editorconfig +5 -0
- package/tooling/base/configs/.github/pull_request_template.md +18 -0
- package/tooling/base/configs/.github/workflows/verify.yml +35 -0
- package/tooling/base/configs/.husky/commit-msg +1 -0
- package/tooling/base/configs/.husky/post-merge +61 -0
- package/tooling/base/configs/.husky/post-rewrite +21 -0
- package/tooling/base/configs/.husky/pre-commit +1 -0
- package/tooling/base/configs/.husky/pre-push +1 -0
- package/tooling/base/configs/.prettierrc +12 -0
- package/tooling/base/configs/.shellcheckrc +1 -0
- package/tooling/base/configs/.vscode/extensions.json +9 -0
- package/tooling/base/configs/.vscode/settings.json +3 -0
- package/tooling/base/configs/commitlint.config.js +11 -0
- package/tooling/base/configs/scripts/verify.sh +64 -0
- package/tooling/base/manifest.toml +30 -0
- package/tooling/base/reference.md +98 -0
- package/tooling/base/seeds/.claude/context/ci.md +33 -0
- package/tooling/base/seeds/.claude/context/development.md +37 -0
- package/tooling/base/seeds/.claude/context/index.md +11 -0
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/base/seeds/.lintstagedrc +8 -0
- package/tooling/base/seeds/.prettierignore +0 -0
- package/tooling/base/seeds/cspell.json +20 -0
- package/tooling/claude/manifest.toml +14 -0
- package/tooling/claude/reference.md +79 -0
- package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
- package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
- package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
- package/tooling/claude/seeds/.claude/diagrams/index.md +8 -0
- package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +50 -0
- package/tooling/claude/seeds/.claude/hooks/memory-index.sh +68 -0
- package/tooling/claude/seeds/.claude/hooks/path-form.sh +57 -0
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +52 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +86 -0
- package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +71 -0
- package/tooling/claude/seeds/.claude/memory/index.md +8 -0
- package/tooling/claude/seeds/.claude/settings.json +47 -0
- package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
- package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
- package/tooling/claude/seeds/CLAUDE.md +29 -0
- package/tooling/claude/user/settings.template.json +10 -0
- package/tooling/claude/user/statusline-command.sh +53 -0
- package/tooling/python/configs/.coveragerc +14 -0
- package/tooling/python/configs/.python-version +1 -0
- package/tooling/python/configs/mypy.ini +6 -0
- package/tooling/python/configs/pytest.ini +4 -0
- package/tooling/python/configs/ruff.toml +15 -0
- package/tooling/python/configs/scripts/verify.sh +77 -0
- package/tooling/python/manifest.toml +16 -0
- package/tooling/python/reference.md +66 -0
- package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
- package/tooling/python/seeds/tests/test_smoke.py +2 -0
- package/tooling/vite-react/configs/playwright.config.ts +26 -0
- package/tooling/vite-react/configs/tsconfig.json +35 -0
- package/tooling/vite-react/configs/vite.config.ts +24 -0
- package/tooling/vite-react/configs/vitest.config.ts +26 -0
- package/tooling/vite-react/manifest.toml +23 -0
- package/tooling/vite-react/reference.md +55 -0
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
- package/tooling/web/configs/.github/workflows/verify.yml +134 -0
- package/tooling/web/configs/.vscode/extensions.json +13 -0
- package/tooling/web/configs/.vscode/settings.json +10 -0
- package/tooling/web/configs/e2e/home.spec.ts +6 -0
- package/tooling/web/configs/e2e/screenshot.ts +53 -0
- package/tooling/web/configs/eslint.config.js +82 -0
- package/tooling/web/configs/scripts/screenshot.sh +28 -0
- package/tooling/web/configs/scripts/verify.sh +80 -0
- package/tooling/web/configs/scripts/worktree-port.sh +74 -0
- package/tooling/web/configs/src/test/setup.ts +8 -0
- package/tooling/web/manifest.toml +58 -0
- package/tooling/web/reference.md +115 -0
- package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { execa } from 'execa'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The severities the advisory index publishes, worst first.
|
|
7
|
+
*
|
|
8
|
+
* `info` is this module's own floor rather than one of theirs. An advisory
|
|
9
|
+
* arriving under a severity nothing here recognizes still exists, and dropping
|
|
10
|
+
* it would shrink the count on the day the vocabulary changed.
|
|
11
|
+
*/
|
|
12
|
+
export const SEVERITIES = [
|
|
13
|
+
'critical',
|
|
14
|
+
'high',
|
|
15
|
+
'moderate',
|
|
16
|
+
'low',
|
|
17
|
+
'info',
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
export type Severity = (typeof SEVERITIES)[number]
|
|
21
|
+
|
|
22
|
+
export interface Advisory {
|
|
23
|
+
readonly package: string
|
|
24
|
+
readonly id: number
|
|
25
|
+
readonly title: string
|
|
26
|
+
readonly url: string
|
|
27
|
+
readonly severity: Severity
|
|
28
|
+
readonly vulnerableVersions?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Why no advisory list was produced, which is never the same as a clean one.
|
|
33
|
+
*
|
|
34
|
+
* `no-lockfile` is split out from `no-record` because the two take different
|
|
35
|
+
* remedies. An unreachable index is retried, and a project whose dependencies
|
|
36
|
+
* were never resolved is installed first, so one message naming the network
|
|
37
|
+
* would send half the readers at the wrong cause.
|
|
38
|
+
*/
|
|
39
|
+
export type AuditRefusal = 'no-manifest' | 'no-lockfile' | 'no-record'
|
|
40
|
+
|
|
41
|
+
/** The lockfiles a resolved dependency set leaves behind, in any manager. */
|
|
42
|
+
const LOCKFILES = [
|
|
43
|
+
'bun.lock',
|
|
44
|
+
'bun.lockb',
|
|
45
|
+
'package-lock.json',
|
|
46
|
+
'yarn.lock',
|
|
47
|
+
'pnpm-lock.yaml',
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
export type DepsAudit =
|
|
51
|
+
| { readonly kind: 'audited'; readonly advisories: readonly Advisory[] }
|
|
52
|
+
| {
|
|
53
|
+
readonly kind: 'refused'
|
|
54
|
+
readonly reason: AuditRefusal
|
|
55
|
+
readonly message?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function severityOf(value: unknown): Severity {
|
|
59
|
+
return SEVERITIES.includes(value as Severity) ? (value as Severity) : 'info'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reads the record `bun audit --json` writes to stdout.
|
|
64
|
+
*
|
|
65
|
+
* The shape is an object keyed by package name, each holding that package's
|
|
66
|
+
* advisories, which this flattens into one list carrying the name on every
|
|
67
|
+
* entry. Returning nothing on unreadable output is what keeps an unreachable
|
|
68
|
+
* index from reporting as a clean tree, and the caller turns it into a stated
|
|
69
|
+
* refusal rather than a zero.
|
|
70
|
+
*/
|
|
71
|
+
export function parseAdvisories(stdout: string): Advisory[] | undefined {
|
|
72
|
+
let record: unknown
|
|
73
|
+
try {
|
|
74
|
+
record = JSON.parse(stdout)
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof record !== 'object' || record === null || Array.isArray(record)) {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const advisories: Advisory[] = []
|
|
84
|
+
for (const [name, entries] of Object.entries(record)) {
|
|
85
|
+
if (!Array.isArray(entries)) return undefined
|
|
86
|
+
|
|
87
|
+
for (const raw of entries) {
|
|
88
|
+
if (typeof raw !== 'object' || raw === null) return undefined
|
|
89
|
+
const entry = raw as Record<string, unknown>
|
|
90
|
+
|
|
91
|
+
advisories.push({
|
|
92
|
+
package: name,
|
|
93
|
+
id: typeof entry.id === 'number' ? entry.id : 0,
|
|
94
|
+
title: typeof entry.title === 'string' ? entry.title : '',
|
|
95
|
+
url: typeof entry.url === 'string' ? entry.url : '',
|
|
96
|
+
severity: severityOf(entry.severity),
|
|
97
|
+
...(typeof entry.vulnerable_versions === 'string' && {
|
|
98
|
+
vulnerableVersions: entry.vulnerable_versions,
|
|
99
|
+
}),
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return advisories
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function countBySeverity(
|
|
108
|
+
advisories: readonly Advisory[],
|
|
109
|
+
): Record<Severity, number> {
|
|
110
|
+
const counts = Object.fromEntries(
|
|
111
|
+
SEVERITIES.map((severity) => [severity, 0]),
|
|
112
|
+
) as Record<Severity, number>
|
|
113
|
+
|
|
114
|
+
for (const advisory of advisories) counts[advisory.severity] += 1
|
|
115
|
+
return counts
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Shells the runtime's own advisory command rather than carrying an index.
|
|
120
|
+
*
|
|
121
|
+
* A vendored advisory database is a second corpus to keep current, and what
|
|
122
|
+
* this check is worth is the report rather than the data. The cost is the one
|
|
123
|
+
* failure mode no other audit here carries: the command reaches a network, so
|
|
124
|
+
* an unreachable index has to be told from a tree with nothing against it.
|
|
125
|
+
* That split is the return value, and the exit code is deliberately not read.
|
|
126
|
+
* `bun audit` exits non-zero on advisories found and on a lookup that failed,
|
|
127
|
+
* so the record on stdout is the only thing that separates them.
|
|
128
|
+
*/
|
|
129
|
+
export async function auditDependencies(root: string): Promise<DepsAudit> {
|
|
130
|
+
if (!existsSync(join(root, 'package.json'))) {
|
|
131
|
+
return { kind: 'refused', reason: 'no-manifest' }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!LOCKFILES.some((name) => existsSync(join(root, name)))) {
|
|
135
|
+
return { kind: 'refused', reason: 'no-lockfile' }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = await execa('bun', ['audit', '--json'], {
|
|
139
|
+
cwd: root,
|
|
140
|
+
reject: false,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const advisories = parseAdvisories(result.stdout)
|
|
144
|
+
if (advisories === undefined) {
|
|
145
|
+
return {
|
|
146
|
+
kind: 'refused',
|
|
147
|
+
reason: 'no-record',
|
|
148
|
+
message: result.stderr.trim().split('\n').pop() ?? 'no output on stdout',
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { kind: 'audited', advisories }
|
|
153
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One table cell, split into the value a renderer emits and whether the record
|
|
5
|
+
* marked it as unsourced.
|
|
6
|
+
*
|
|
7
|
+
* The marker sits inside the cell rather than in a trailing column, because a
|
|
8
|
+
* trailing marker breaks the table parse. Splitting it out here is what keeps a
|
|
9
|
+
* swatch or a font sample built from the value alone.
|
|
10
|
+
*/
|
|
11
|
+
export interface Cell {
|
|
12
|
+
value: string
|
|
13
|
+
tagged: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type Row = Record<string, Cell>
|
|
17
|
+
|
|
18
|
+
export interface DesignDoc {
|
|
19
|
+
personality: string
|
|
20
|
+
color: Row[]
|
|
21
|
+
typography: Row[]
|
|
22
|
+
spacing: Row[]
|
|
23
|
+
borders: Row[]
|
|
24
|
+
motion: string
|
|
25
|
+
iconography: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const VERIFY_TAG = /\s*\?\s*verify\s*$/
|
|
29
|
+
|
|
30
|
+
/** A cell whose whole content is one balanced code span and nothing else. */
|
|
31
|
+
const CODE_SPAN = /^`([^`]*)`$/
|
|
32
|
+
|
|
33
|
+
export function parseDesignDoc(path: string): DesignDoc {
|
|
34
|
+
const raw = readFileSync(path, 'utf8')
|
|
35
|
+
const sections = splitSections(raw)
|
|
36
|
+
return {
|
|
37
|
+
personality: prose(sections['Personality']),
|
|
38
|
+
color: table(sections['Color']),
|
|
39
|
+
typography: table(sections['Typography']),
|
|
40
|
+
spacing: table(sections['Spacing']),
|
|
41
|
+
borders: table(sections['Borders']),
|
|
42
|
+
motion: prose(sections['Motion']),
|
|
43
|
+
iconography: prose(sections['Iconography']),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function splitSections(raw: string): Record<string, string> {
|
|
48
|
+
const out: Record<string, string> = {}
|
|
49
|
+
const lines = raw.split('\n')
|
|
50
|
+
let current: string | null = null
|
|
51
|
+
let buffer: string[] = []
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const match = line.match(/^##\s+(.+?)\s*$/)
|
|
54
|
+
if (match) {
|
|
55
|
+
if (current) out[current] = buffer.join('\n')
|
|
56
|
+
current = match[1]
|
|
57
|
+
buffer = []
|
|
58
|
+
} else if (current) {
|
|
59
|
+
buffer.push(line)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (current) out[current] = buffer.join('\n')
|
|
63
|
+
return out
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function prose(body: string | undefined): string {
|
|
67
|
+
if (!body) return ''
|
|
68
|
+
return body
|
|
69
|
+
.split('\n')
|
|
70
|
+
.filter((l) => l.trim() && !l.startsWith('|') && !l.startsWith('<'))
|
|
71
|
+
.join('\n')
|
|
72
|
+
.trim()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function table(body: string | undefined): Row[] {
|
|
76
|
+
if (!body) return []
|
|
77
|
+
const rows = body.split('\n').filter((l) => l.trim().startsWith('|'))
|
|
78
|
+
if (rows.length < 2) return []
|
|
79
|
+
const headers = splitRow(rows[0]).map((c) => c.value)
|
|
80
|
+
const data = rows.slice(2)
|
|
81
|
+
return data.map((line) => {
|
|
82
|
+
const cells = splitRow(line)
|
|
83
|
+
const row: Row = {}
|
|
84
|
+
headers.forEach((h, i) => {
|
|
85
|
+
row[h] = cells[i] ?? emptyCell()
|
|
86
|
+
})
|
|
87
|
+
return row
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function emptyCell(): Cell {
|
|
92
|
+
return { value: '', tagged: false }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function splitRow(line: string): Cell[] {
|
|
96
|
+
return line
|
|
97
|
+
.replace(/^\s*\|/, '')
|
|
98
|
+
.replace(/\|\s*$/, '')
|
|
99
|
+
.split('|')
|
|
100
|
+
.map(parseCell)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The tag is tested against the cell with any surrounding code span removed,
|
|
105
|
+
* since a value wrapping itself in backticks puts one after the tag and an
|
|
106
|
+
* end-anchored test misses it there. The span is restored around the clean
|
|
107
|
+
* value so an untagged cell and a tagged one carry the same formatting.
|
|
108
|
+
*/
|
|
109
|
+
function parseCell(raw: string): Cell {
|
|
110
|
+
const trimmed = raw.trim()
|
|
111
|
+
const span = trimmed.match(CODE_SPAN)
|
|
112
|
+
const inner = span ? span[1] : trimmed
|
|
113
|
+
if (!VERIFY_TAG.test(inner)) return { value: trimmed, tagged: false }
|
|
114
|
+
const value = inner.replace(VERIFY_TAG, '')
|
|
115
|
+
return { value: span ? `\`${value}\`` : value, tagged: true }
|
|
116
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { Cell, DesignDoc, Row } from '@/design/parse'
|
|
4
|
+
import { parseDesignDoc } from '@/design/parse'
|
|
5
|
+
|
|
6
|
+
export interface RenderResult {
|
|
7
|
+
htmlPath: string
|
|
8
|
+
cssPath: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function renderDesignDoc(
|
|
12
|
+
sourcePath: string,
|
|
13
|
+
outDir: string,
|
|
14
|
+
): RenderResult {
|
|
15
|
+
const doc = parseDesignDoc(sourcePath)
|
|
16
|
+
mkdirSync(outDir, { recursive: true })
|
|
17
|
+
const cssPath = join(outDir, 'design.css')
|
|
18
|
+
const htmlPath = join(outDir, 'index.html')
|
|
19
|
+
writeFileSync(cssPath, buildCss(doc))
|
|
20
|
+
writeFileSync(htmlPath, buildHtml(doc))
|
|
21
|
+
return { htmlPath, cssPath }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function slug(s: string): string {
|
|
25
|
+
return s
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
28
|
+
.replace(/(^-|-$)/g, '')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cell(row: Row, key: string): Cell {
|
|
32
|
+
return row[key] ?? { value: '', tagged: false }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The value alone. Every swatch, sample, and custom property is built from it. */
|
|
36
|
+
function val(row: Row, key: string): string {
|
|
37
|
+
return cell(row, key).value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The marker, rendered beside a value rather than inside it. */
|
|
41
|
+
function mark(row: Row, key: string): string {
|
|
42
|
+
return cell(row, key).tagged
|
|
43
|
+
? ' <span class="verify" title="No source anchors this value">? verify</span>'
|
|
44
|
+
: ''
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A displayed cell: its escaped text, then its marker when it carries one. */
|
|
48
|
+
function cellText(row: Row, key: string): string {
|
|
49
|
+
return escape(val(row, key)) + mark(row, key)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Confidence {
|
|
53
|
+
tagged: number
|
|
54
|
+
total: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The columns the confidence ratio reads, fixed by `standards/design.md`. The
|
|
59
|
+
* first column of each table names its row, and `Multiplier` and `When used`
|
|
60
|
+
* restate what the row already carries, so none of them is something a source
|
|
61
|
+
* could anchor and none belongs in the denominator.
|
|
62
|
+
*/
|
|
63
|
+
const ANCHORABLE = {
|
|
64
|
+
borders: ['Radius', 'Width'],
|
|
65
|
+
color: ['Intent', 'Value'],
|
|
66
|
+
spacing: ['Value'],
|
|
67
|
+
typography: ['Family', 'Weight', 'Size', 'Line height'],
|
|
68
|
+
} as const
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A cell counts when it carries a tag, or when it holds a value in a column a
|
|
72
|
+
* source could anchor. A blank the record left unfilled is neither, and so is a
|
|
73
|
+
* row name. Counting a tagged cell whichever column it sits in is what keeps a
|
|
74
|
+
* marker the preview draws from sitting outside the ratio printed beside it.
|
|
75
|
+
*/
|
|
76
|
+
function confidence(doc: DesignDoc): Confidence {
|
|
77
|
+
const tables: ReadonlyArray<readonly [Row[], readonly string[]]> = [
|
|
78
|
+
[doc.color, ANCHORABLE.color],
|
|
79
|
+
[doc.typography, ANCHORABLE.typography],
|
|
80
|
+
[doc.spacing, ANCHORABLE.spacing],
|
|
81
|
+
[doc.borders, ANCHORABLE.borders],
|
|
82
|
+
]
|
|
83
|
+
let tagged = 0
|
|
84
|
+
let total = 0
|
|
85
|
+
for (const [rows, columns] of tables) {
|
|
86
|
+
for (const row of rows) {
|
|
87
|
+
for (const [key, c] of Object.entries(row)) {
|
|
88
|
+
if (!c.tagged && (!c.value || !columns.includes(key))) continue
|
|
89
|
+
total += 1
|
|
90
|
+
if (c.tagged) tagged += 1
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { tagged, total }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildCss(doc: DesignDoc): string {
|
|
98
|
+
const lines: string[] = [':root {']
|
|
99
|
+
for (const row of doc.color) {
|
|
100
|
+
if (val(row, 'Value')) {
|
|
101
|
+
lines.push(` --color-${slug(val(row, 'Role'))}: ${val(row, 'Value')};`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const row of doc.spacing) {
|
|
105
|
+
if (val(row, 'Value')) {
|
|
106
|
+
lines.push(` --space-${slug(val(row, 'Step'))}: ${val(row, 'Value')};`)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const row of doc.typography) {
|
|
110
|
+
if (val(row, 'Size')) {
|
|
111
|
+
lines.push(
|
|
112
|
+
` --type-${slug(val(row, 'Role'))}-size: ${val(row, 'Size')};`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (val(row, 'Line height')) {
|
|
116
|
+
lines.push(
|
|
117
|
+
` --type-${slug(val(row, 'Role'))}-lh: ${val(row, 'Line height')};`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const row of doc.borders) {
|
|
122
|
+
if (val(row, 'Radius')) {
|
|
123
|
+
lines.push(` --radius-${slug(val(row, 'Role'))}: ${val(row, 'Radius')};`)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
lines.push('}')
|
|
127
|
+
return lines.join('\n') + '\n'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function escape(s: string): string {
|
|
131
|
+
return s
|
|
132
|
+
.replace(/&/g, '&')
|
|
133
|
+
.replace(/</g, '<')
|
|
134
|
+
.replace(/>/g, '>')
|
|
135
|
+
.replace(/"/g, '"')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildHtml(doc: DesignDoc): string {
|
|
139
|
+
const sections = [
|
|
140
|
+
sectionPersonality(doc.personality),
|
|
141
|
+
sectionColor(doc.color),
|
|
142
|
+
sectionTypography(doc.typography),
|
|
143
|
+
sectionSpacing(doc.spacing),
|
|
144
|
+
sectionBorders(doc.borders),
|
|
145
|
+
sectionLine('Motion', doc.motion),
|
|
146
|
+
sectionLine('Iconography', doc.iconography),
|
|
147
|
+
]
|
|
148
|
+
const { tagged, total } = confidence(doc)
|
|
149
|
+
const verifyStyle = tagged
|
|
150
|
+
? '\n .verify { color: #a4471c; font-size: 12px; font-weight: 600; margin-left: 0.35rem; white-space: nowrap; }'
|
|
151
|
+
: ''
|
|
152
|
+
const verb = tagged === 1 ? 'carries' : 'carry'
|
|
153
|
+
const summary = tagged
|
|
154
|
+
? `\n<p class="note">${total - tagged} of ${total} cells are anchored to a source. The other ${tagged} ${verb} <code>? verify</code>, so nothing anchors them yet.</p>`
|
|
155
|
+
: ''
|
|
156
|
+
return `<!doctype html>
|
|
157
|
+
<html lang="en">
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="utf-8">
|
|
160
|
+
<title>Design tokens</title>
|
|
161
|
+
<link rel="stylesheet" href="design.css">
|
|
162
|
+
<style>
|
|
163
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 960px; color: #222; }
|
|
164
|
+
h1 { margin-top: 0; }
|
|
165
|
+
h2 { margin-top: 2rem; border-bottom: 1px solid #ddd; padding-bottom: 0.25rem; }
|
|
166
|
+
table { border-collapse: collapse; width: 100%; margin-top: 0.5rem; }
|
|
167
|
+
th, td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid #eee; font-size: 14px; }
|
|
168
|
+
th { background: #f7f7f7; font-weight: 600; }
|
|
169
|
+
.swatch { display: inline-block; width: 1.5rem; height: 1.5rem; border-radius: 4px; border: 1px solid #ddd; vertical-align: middle; margin-right: 0.5rem; }
|
|
170
|
+
.bar { display: inline-block; height: 1rem; background: #888; border-radius: 2px; vertical-align: middle; }
|
|
171
|
+
.note { color: #666; font-size: 13px; margin-top: 0.5rem; }
|
|
172
|
+
.empty { color: #999; font-style: italic; }${verifyStyle}
|
|
173
|
+
</style>
|
|
174
|
+
</head>
|
|
175
|
+
<body>
|
|
176
|
+
<h1>Design tokens</h1>
|
|
177
|
+
<p class="note">Generated from <code>.claude/DESIGN.md</code> by <code>canon design render</code>. Token preview only, not a screen mock.</p>${summary}
|
|
178
|
+
${sections.join('\n')}
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function sectionPersonality(text: string): string {
|
|
185
|
+
if (!text) return ''
|
|
186
|
+
return `<h2>Personality</h2>\n<p>${escape(text)}</p>`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function sectionColor(rows: Row[]): string {
|
|
190
|
+
if (!rows.length) return ''
|
|
191
|
+
const body = rows
|
|
192
|
+
.map((r) => {
|
|
193
|
+
const value = val(r, 'Value')
|
|
194
|
+
const swatch = value
|
|
195
|
+
? `<span class="swatch" style="background:${escape(value)}"></span>`
|
|
196
|
+
: '<span class="swatch"></span>'
|
|
197
|
+
const shown = value ? escape(value) : '<span class="empty">unset</span>'
|
|
198
|
+
return `<tr><td>${swatch}${cellText(r, 'Role')}</td><td>${cellText(r, 'Intent')}</td><td><code>${shown}</code>${mark(r, 'Value')}</td></tr>`
|
|
199
|
+
})
|
|
200
|
+
.join('\n')
|
|
201
|
+
return `<h2>Color</h2>\n<table><thead><tr><th>Role</th><th>Intent</th><th>Value</th></tr></thead><tbody>${body}</tbody></table>`
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function sectionTypography(rows: Row[]): string {
|
|
205
|
+
if (!rows.length) return ''
|
|
206
|
+
const body = rows
|
|
207
|
+
.map((r) => {
|
|
208
|
+
const family = val(r, 'Family') || 'system-ui'
|
|
209
|
+
const weight = val(r, 'Weight') || '400'
|
|
210
|
+
const size = val(r, 'Size') || '16px'
|
|
211
|
+
const lh = val(r, 'Line height') || '1.4'
|
|
212
|
+
const sample = `<span style="font-family:${escape(family)};font-weight:${escape(weight)};font-size:${escape(size)};line-height:${escape(lh)}">The quick brown fox</span>`
|
|
213
|
+
return `<tr><td>${cellText(r, 'Role')}</td><td>${escape(family)}${mark(r, 'Family')}</td><td>${escape(weight)}${mark(r, 'Weight')}</td><td>${escape(size)}${mark(r, 'Size')}</td><td>${escape(lh)}${mark(r, 'Line height')}</td><td>${sample}</td></tr>`
|
|
214
|
+
})
|
|
215
|
+
.join('\n')
|
|
216
|
+
return `<h2>Typography</h2>\n<table><thead><tr><th>Role</th><th>Family</th><th>Weight</th><th>Size</th><th>Line height</th><th>Sample</th></tr></thead><tbody>${body}</tbody></table>`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sectionSpacing(rows: Row[]): string {
|
|
220
|
+
if (!rows.length) return ''
|
|
221
|
+
const body = rows
|
|
222
|
+
.map((r) => {
|
|
223
|
+
const value = val(r, 'Value')
|
|
224
|
+
const bar = value
|
|
225
|
+
? `<span class="bar" style="width:${escape(value)}"></span>`
|
|
226
|
+
: '<span class="empty">unset</span>'
|
|
227
|
+
return `<tr><td>${cellText(r, 'Step')}</td><td>${cellText(r, 'Multiplier')}</td><td><code>${escape(value || 'unset')}</code>${mark(r, 'Value')}</td><td>${bar}</td></tr>`
|
|
228
|
+
})
|
|
229
|
+
.join('\n')
|
|
230
|
+
return `<h2>Spacing</h2>\n<table><thead><tr><th>Step</th><th>Multiplier</th><th>Value</th><th>Sample</th></tr></thead><tbody>${body}</tbody></table>`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function sectionBorders(rows: Row[]): string {
|
|
234
|
+
if (!rows.length) return ''
|
|
235
|
+
const body = rows
|
|
236
|
+
.map((r) => {
|
|
237
|
+
const radius = val(r, 'Radius') || '0'
|
|
238
|
+
const width = val(r, 'Width') || '1px'
|
|
239
|
+
const sample = `<span style="display:inline-block;width:2rem;height:1.5rem;background:#eee;border:${escape(width)} solid #888;border-radius:${escape(radius)};vertical-align:middle"></span>`
|
|
240
|
+
return `<tr><td>${cellText(r, 'Role')}</td><td><code>${escape(radius)}</code>${mark(r, 'Radius')}</td><td><code>${escape(width)}</code>${mark(r, 'Width')}</td><td>${cellText(r, 'When used')}</td><td>${sample}</td></tr>`
|
|
241
|
+
})
|
|
242
|
+
.join('\n')
|
|
243
|
+
return `<h2>Borders</h2>\n<table><thead><tr><th>Role</th><th>Radius</th><th>Width</th><th>When used</th><th>Sample</th></tr></thead><tbody>${body}</tbody></table>`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sectionLine(title: string, text: string): string {
|
|
247
|
+
if (!text) return ''
|
|
248
|
+
return `<h2>${title}</h2>\n<p>${escape(text)}</p>`
|
|
249
|
+
}
|
package/src/docs/read.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { basename, dirname, join } from 'node:path'
|
|
3
|
+
import { stripFrontmatter } from '@/frontmatter'
|
|
4
|
+
|
|
5
|
+
const INDEX_TOPIC = 'index'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The two roots a topic resolves against, in precedence order. `docs/` holds
|
|
9
|
+
* consumer-facing reference and `.claude/context/` holds per-domain internal
|
|
10
|
+
* narrative, so a name present in both resolves to the consumer-facing copy.
|
|
11
|
+
*/
|
|
12
|
+
const ROOTS: readonly string[] = ['docs', join('.claude', 'context')]
|
|
13
|
+
|
|
14
|
+
export interface ResolvedTopic {
|
|
15
|
+
readonly path: string
|
|
16
|
+
readonly rel: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A domain too large for one file splits into `<domain>/` with a generated
|
|
21
|
+
* `index.md`, so a topic names either a sibling file or such a folder. Both
|
|
22
|
+
* spellings resolve to the same name a caller types.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveTopic(
|
|
25
|
+
root: string,
|
|
26
|
+
topic: string,
|
|
27
|
+
): ResolvedTopic | undefined {
|
|
28
|
+
for (const dir of ROOTS) {
|
|
29
|
+
const candidates = [
|
|
30
|
+
join(dir, `${topic}.md`),
|
|
31
|
+
join(dir, topic, `${INDEX_TOPIC}.md`),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for (const rel of candidates) {
|
|
35
|
+
const path = join(root, rel)
|
|
36
|
+
if (existsSync(path)) return { path, rel }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Names every topic a `get` could resolve, listed per root in the order the
|
|
45
|
+
* roots are searched. Shown when a topic misses, so it doubles as the answer to
|
|
46
|
+
* what the caller should have typed.
|
|
47
|
+
*/
|
|
48
|
+
export function listTopics(root: string): string[] {
|
|
49
|
+
const topics: string[] = []
|
|
50
|
+
|
|
51
|
+
for (const dir of ROOTS) {
|
|
52
|
+
const cwd = join(root, dir)
|
|
53
|
+
if (!existsSync(cwd)) continue
|
|
54
|
+
|
|
55
|
+
const files = [
|
|
56
|
+
...new Bun.Glob('*.md').scanSync({ cwd, onlyFiles: true, dot: true }),
|
|
57
|
+
]
|
|
58
|
+
.map((name) => basename(name, '.md'))
|
|
59
|
+
.filter((name) => name !== INDEX_TOPIC)
|
|
60
|
+
|
|
61
|
+
const folders = [
|
|
62
|
+
...new Bun.Glob(`*/${INDEX_TOPIC}.md`).scanSync({
|
|
63
|
+
cwd,
|
|
64
|
+
onlyFiles: true,
|
|
65
|
+
dot: true,
|
|
66
|
+
}),
|
|
67
|
+
].map((name) => dirname(name))
|
|
68
|
+
|
|
69
|
+
topics.push(...[...files, ...folders].sort())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return topics
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function readTopic(topic: ResolvedTopic): string {
|
|
76
|
+
return stripFrontmatter(readFileSync(topic.path, 'utf8'))
|
|
77
|
+
}
|
package/src/exec.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import { PROJECT_ROOT } from '@/project-root'
|
|
4
|
+
|
|
5
|
+
export async function execScript(
|
|
6
|
+
script: string,
|
|
7
|
+
args: string[],
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const scriptPath = join(PROJECT_ROOT, 'scripts', script)
|
|
10
|
+
const result = await execa(scriptPath, args, {
|
|
11
|
+
stdio: 'inherit',
|
|
12
|
+
env: { ...process.env, PROJECT_ROOT },
|
|
13
|
+
reject: false,
|
|
14
|
+
})
|
|
15
|
+
process.exit(result.exitCode ?? 1)
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapes every character a regular expression would read as syntax.
|
|
3
|
+
*
|
|
4
|
+
* The token became a parameter when this was extracted, and the two callers
|
|
5
|
+
* pass letters and hyphens alone. That is what makes escaping cheap here rather
|
|
6
|
+
* than a fix for a live defect: an unescaped token holding a dot matches the
|
|
7
|
+
* wrong lines and one holding a parenthesis throws, and neither failure is the
|
|
8
|
+
* caller's to anticipate.
|
|
9
|
+
*/
|
|
10
|
+
function escapeForPattern(token: string): string {
|
|
11
|
+
return token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether the line at `index` carries `token` with a reason after it, reading
|
|
16
|
+
* itself and the line above.
|
|
17
|
+
*
|
|
18
|
+
* Extracted from `src/secrets/marker.ts`, which shaped it, so the two sweeps
|
|
19
|
+
* that mute a line share one answer to where a marker may sit. Two lines rather
|
|
20
|
+
* than one, because a value is as often introduced by a preceding comment as
|
|
21
|
+
* annotated inline, and a format taking no trailing comment has nowhere else to
|
|
22
|
+
* put the marker. Nothing further up counts, so a marker cannot silence a block
|
|
23
|
+
* it does not sit on.
|
|
24
|
+
*
|
|
25
|
+
* Only a marker naming a reason counts. A bare token is read as a line that
|
|
26
|
+
* meant to say something and did not, and honoring it would let a typo mute a
|
|
27
|
+
* finding, where the reason is the whole value of an exemption a later reader
|
|
28
|
+
* has to weigh.
|
|
29
|
+
*/
|
|
30
|
+
export function isMarked(
|
|
31
|
+
lines: readonly string[],
|
|
32
|
+
index: number,
|
|
33
|
+
token: string,
|
|
34
|
+
): boolean {
|
|
35
|
+
const pattern = new RegExp(`${escapeForPattern(token)}:[ \\t]*\\S`)
|
|
36
|
+
const own = lines[index]
|
|
37
|
+
const above = index > 0 ? lines[index - 1] : undefined
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
(own !== undefined && pattern.test(own)) ||
|
|
41
|
+
(above !== undefined && pattern.test(above))
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const FRONTMATTER = /^---\n[\s\S]*?\n---\n?/
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drops a leading `---` block. Only a block starting on the first line counts,
|
|
5
|
+
* so a horizontal rule further down the document is left in place.
|
|
6
|
+
*
|
|
7
|
+
* The bash this replaced treated the first `---` on any line as an opening
|
|
8
|
+
* fence, which silently swallowed everything between two horizontal rules in a
|
|
9
|
+
* body. Anchoring to the first line is the correct reading.
|
|
10
|
+
*/
|
|
11
|
+
export function stripFrontmatter(source: string): string {
|
|
12
|
+
return source.replace(FRONTMATTER, '')
|
|
13
|
+
}
|