@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,18 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
|
|
4
|
+
export async function openDeck(path: string): Promise<boolean> {
|
|
5
|
+
const windowsPath = await execa('wslpath', ['-w', path], { reject: false })
|
|
6
|
+
if (windowsPath.exitCode === 0) {
|
|
7
|
+
const cwd = existsSync('/mnt/c') ? '/mnt/c' : undefined
|
|
8
|
+
const result = await execa(
|
|
9
|
+
'cmd.exe',
|
|
10
|
+
['/c', 'start', '', windowsPath.stdout.trim()],
|
|
11
|
+
{ cwd, reject: false },
|
|
12
|
+
)
|
|
13
|
+
return result.exitCode === 0
|
|
14
|
+
}
|
|
15
|
+
const opener = process.platform === 'darwin' ? 'open' : 'xdg-open'
|
|
16
|
+
const result = await execa(opener, [path], { reject: false })
|
|
17
|
+
return result.exitCode === 0
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import type { Variant } from '@/slides/styles'
|
|
3
|
+
|
|
4
|
+
export interface DeckMeta {
|
|
5
|
+
title: string
|
|
6
|
+
variant: Variant
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Slide {
|
|
10
|
+
layout: string
|
|
11
|
+
title: string
|
|
12
|
+
content: string[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Deck {
|
|
16
|
+
meta: DeckMeta
|
|
17
|
+
slides: Slide[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEFAULT_META: DeckMeta = {
|
|
21
|
+
title: '',
|
|
22
|
+
variant: 'light',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseSlidesDoc(path: string): Deck {
|
|
26
|
+
return parseSlides(readFileSync(path, 'utf8'))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseSlides(raw: string): Deck {
|
|
30
|
+
const { meta, body } = splitFrontmatter(raw)
|
|
31
|
+
const slides = body
|
|
32
|
+
.split(/^---\s*$/m)
|
|
33
|
+
.map(parseSlide)
|
|
34
|
+
.filter((slide): slide is Slide => slide !== null)
|
|
35
|
+
return { meta, slides }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function splitFrontmatter(raw: string): { meta: DeckMeta; body: string } {
|
|
39
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n?/)
|
|
40
|
+
if (!match) return { meta: { ...DEFAULT_META }, body: raw }
|
|
41
|
+
return { meta: parseMeta(match[1]), body: raw.slice(match[0].length) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseMeta(block: string): DeckMeta {
|
|
45
|
+
const meta: DeckMeta = { ...DEFAULT_META }
|
|
46
|
+
for (const line of block.split('\n')) {
|
|
47
|
+
const match = line.match(/^(\w+):\s*(.+?)\s*$/)
|
|
48
|
+
if (!match) continue
|
|
49
|
+
const [, key, value] = match
|
|
50
|
+
if (key === 'title') meta.title = value
|
|
51
|
+
if (key === 'variant' && (value === 'light' || value === 'dark')) {
|
|
52
|
+
meta.variant = value
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return meta
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseSlide(chunk: string): Slide | null {
|
|
59
|
+
let title = ''
|
|
60
|
+
let layout = ''
|
|
61
|
+
const content: string[] = []
|
|
62
|
+
for (const line of chunk.split('\n')) {
|
|
63
|
+
const heading = line.match(/^#\s+(.+?)\s*$/)
|
|
64
|
+
const declared = line.match(/^layout:\s*(.+?)\s*$/)
|
|
65
|
+
if (heading && !title) {
|
|
66
|
+
title = heading[1]
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
if (declared && !layout) {
|
|
70
|
+
layout = declared[1].trim()
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
content.push(line)
|
|
74
|
+
}
|
|
75
|
+
const trimmed = content.filter((line) => line.trim())
|
|
76
|
+
if (!title && !layout && trimmed.length === 0) return null
|
|
77
|
+
return { layout: layout || inferLayout(trimmed), title, content: trimmed }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function inferLayout(content: string[]): string {
|
|
81
|
+
return content.some((line) => line.trim().startsWith('-'))
|
|
82
|
+
? 'bullets'
|
|
83
|
+
: 'title'
|
|
84
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { copyFileSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import PptxGenJS from 'pptxgenjs'
|
|
4
|
+
import {
|
|
5
|
+
addFooter,
|
|
6
|
+
type DeckNav,
|
|
7
|
+
renderDeckSlide,
|
|
8
|
+
renderToc,
|
|
9
|
+
} from '@/slides/layouts'
|
|
10
|
+
import { type Deck, parseSlidesDoc } from '@/slides/parse'
|
|
11
|
+
import { buildTheme, type Variant } from '@/slides/styles'
|
|
12
|
+
|
|
13
|
+
export interface RenderResult {
|
|
14
|
+
pptxPath: string
|
|
15
|
+
mirrorPath?: string
|
|
16
|
+
slideCount: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RenderOptions {
|
|
20
|
+
variant?: Variant
|
|
21
|
+
mirror?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function renderSlidesDoc(
|
|
25
|
+
sourcePath: string,
|
|
26
|
+
outDir: string,
|
|
27
|
+
options: RenderOptions = {},
|
|
28
|
+
): Promise<RenderResult> {
|
|
29
|
+
const deck = parseSlidesDoc(sourcePath)
|
|
30
|
+
const variant = options.variant ?? deck.meta.variant
|
|
31
|
+
const theme = buildTheme(variant)
|
|
32
|
+
|
|
33
|
+
const pptx = new PptxGenJS()
|
|
34
|
+
pptx.layout = 'LAYOUT_WIDE'
|
|
35
|
+
if (deck.meta.title) pptx.title = deck.meta.title
|
|
36
|
+
|
|
37
|
+
const nav = buildNav(deck)
|
|
38
|
+
for (const slide of deck.slides) {
|
|
39
|
+
const target = pptx.addSlide()
|
|
40
|
+
target.background = { color: theme.background }
|
|
41
|
+
if (slide.layout === 'toc') {
|
|
42
|
+
renderToc(target, slide, theme, nav)
|
|
43
|
+
} else {
|
|
44
|
+
renderDeckSlide(target, slide, theme)
|
|
45
|
+
}
|
|
46
|
+
addFooter(target, slide, theme, nav)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mkdirSync(outDir, { recursive: true })
|
|
50
|
+
const fileName = outputName(sourcePath)
|
|
51
|
+
const pptxPath = join(outDir, fileName)
|
|
52
|
+
await pptx.writeFile({ fileName: pptxPath })
|
|
53
|
+
|
|
54
|
+
const mirrorPath = options.mirror
|
|
55
|
+
? mirrorDeck(pptxPath, options.mirror, fileName)
|
|
56
|
+
: undefined
|
|
57
|
+
return { pptxPath, mirrorPath, slideCount: deck.slides.length }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function mirrorDeck(
|
|
61
|
+
pptxPath: string,
|
|
62
|
+
mirrorDir: string,
|
|
63
|
+
fileName: string,
|
|
64
|
+
): string {
|
|
65
|
+
mkdirSync(mirrorDir, { recursive: true })
|
|
66
|
+
const mirrorPath = join(mirrorDir, fileName)
|
|
67
|
+
copyFileSync(pptxPath, mirrorPath)
|
|
68
|
+
return mirrorPath
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildNav(deck: Deck): DeckNav {
|
|
72
|
+
let tocSlide: number | undefined
|
|
73
|
+
const sections: DeckNav['sections'] = []
|
|
74
|
+
deck.slides.forEach((slide, index) => {
|
|
75
|
+
const slideNumber = index + 1
|
|
76
|
+
if (slide.layout === 'toc' && tocSlide === undefined) {
|
|
77
|
+
tocSlide = slideNumber
|
|
78
|
+
}
|
|
79
|
+
if (slide.layout === 'section') {
|
|
80
|
+
sections.push({ title: slide.title, slideNumber })
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
return { deckTitle: deck.meta.title, tocSlide, sections }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function outputName(sourcePath: string): string {
|
|
87
|
+
return basename(sourcePath).replace(/\.md$/, '') + '.pptx'
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type Variant = 'light' | 'dark'
|
|
2
|
+
|
|
3
|
+
export interface Theme {
|
|
4
|
+
background: string
|
|
5
|
+
surface: string
|
|
6
|
+
ink: string
|
|
7
|
+
muted: string
|
|
8
|
+
accent: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const LIGHT: Theme = {
|
|
12
|
+
background: 'FAF7F2',
|
|
13
|
+
surface: 'F4EFE6',
|
|
14
|
+
ink: '1A1815',
|
|
15
|
+
muted: '7A736A',
|
|
16
|
+
accent: 'A4471C',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DARK: Theme = {
|
|
20
|
+
background: '1A1815',
|
|
21
|
+
surface: '23201C',
|
|
22
|
+
ink: 'F4EFE6',
|
|
23
|
+
muted: 'A39C92',
|
|
24
|
+
accent: 'C8602E',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const FONTS = {
|
|
28
|
+
heading: 'Arial',
|
|
29
|
+
body: 'Calibri',
|
|
30
|
+
} as const
|
|
31
|
+
|
|
32
|
+
export const TYPE = {
|
|
33
|
+
cover: 44,
|
|
34
|
+
section: 40,
|
|
35
|
+
title: 30,
|
|
36
|
+
body: 16,
|
|
37
|
+
stat: 54,
|
|
38
|
+
quote: 30,
|
|
39
|
+
caption: 11,
|
|
40
|
+
} as const
|
|
41
|
+
|
|
42
|
+
export function buildTheme(variant: Variant): Theme {
|
|
43
|
+
return variant === 'dark' ? DARK : LIGHT
|
|
44
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { isDirectory } from '@/target'
|
|
4
|
+
|
|
5
|
+
export const BASE_CATEGORY = 'base'
|
|
6
|
+
/** A real category resolving to zero entries, for a caller declining on purpose. */
|
|
7
|
+
export const NONE_CATEGORY = 'none'
|
|
8
|
+
|
|
9
|
+
export function snippetsSourceDir(root: string): string {
|
|
10
|
+
return join(root, 'snippets')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Lists the folder categories. `base` is the synthetic name for snippets
|
|
15
|
+
* sitting directly under `snippets/`, so it is not a directory and callers
|
|
16
|
+
* prepend it themselves.
|
|
17
|
+
*
|
|
18
|
+
* Nothing is filtered: toolkit-internal snippets are authored under `internal/`,
|
|
19
|
+
* which this never reads, so every category found here is publishable.
|
|
20
|
+
*/
|
|
21
|
+
export function listFolderCategories(root: string): string[] {
|
|
22
|
+
const dir = snippetsSourceDir(root)
|
|
23
|
+
if (!isDirectory(dir)) return []
|
|
24
|
+
|
|
25
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
26
|
+
.filter((entry) => entry.isDirectory())
|
|
27
|
+
.map((entry) => entry.name)
|
|
28
|
+
.sort()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function listCategories(root: string): string[] {
|
|
32
|
+
return [BASE_CATEGORY, ...listFolderCategories(root)]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function categoryDir(root: string, category: string): string {
|
|
36
|
+
const source = snippetsSourceDir(root)
|
|
37
|
+
return category === BASE_CATEGORY ? source : join(source, category)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tests for a directory rather than mere existence. `snippets/` holds files
|
|
42
|
+
* alongside its category folders, so an argument naming one of them resolves
|
|
43
|
+
* to a real path that cannot be scanned as a category.
|
|
44
|
+
*
|
|
45
|
+
* An empty category refuses rather than resolving, since `join(source, '')`
|
|
46
|
+
* is `source` and would otherwise report the base directory as the category,
|
|
47
|
+
* widening an unset value to every base entry instead of naming none.
|
|
48
|
+
*/
|
|
49
|
+
export function categoryExists(root: string, category: string): boolean {
|
|
50
|
+
if (category === '') return false
|
|
51
|
+
return isDirectory(categoryDir(root, category))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Lists one category's entry slugs. Only the immediate level is read, matching
|
|
56
|
+
* the `find -maxdepth 1` the bash used, so a nested folder is its own category
|
|
57
|
+
* rather than part of its parent.
|
|
58
|
+
*/
|
|
59
|
+
export function listEntries(root: string, category: string): string[] {
|
|
60
|
+
const dir = categoryDir(root, category)
|
|
61
|
+
if (!isDirectory(dir)) return []
|
|
62
|
+
|
|
63
|
+
return [...new Bun.Glob('*.md').scanSync({ cwd: dir, onlyFiles: true })]
|
|
64
|
+
.map((name) => name.slice(0, -'.md'.length))
|
|
65
|
+
.sort()
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { listCategories, listEntries } from '@/snippets/categories'
|
|
2
|
+
import { loadPresets } from '@/snippets/presets'
|
|
3
|
+
|
|
4
|
+
export interface CategoryCatalogEntry {
|
|
5
|
+
readonly name: string
|
|
6
|
+
readonly entries: readonly string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SnippetsCatalog {
|
|
10
|
+
readonly presets: readonly {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly slugs: readonly string[]
|
|
13
|
+
}[]
|
|
14
|
+
readonly categories: readonly CategoryCatalogEntry[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds the catalog both output modes read, so the human list and the `--json`
|
|
19
|
+
* one cannot describe different corpora.
|
|
20
|
+
*/
|
|
21
|
+
export function buildSnippetsCatalog(root: string): SnippetsCatalog {
|
|
22
|
+
return {
|
|
23
|
+
presets: loadPresets(root).map((preset) => ({
|
|
24
|
+
name: preset.name,
|
|
25
|
+
slugs: preset.slugs,
|
|
26
|
+
})),
|
|
27
|
+
categories: listCategories(root).map((name) => ({
|
|
28
|
+
name,
|
|
29
|
+
entries: listEntries(root, name),
|
|
30
|
+
})),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { snippetsSourceDir } from '@/snippets/categories'
|
|
4
|
+
|
|
5
|
+
export interface Preset {
|
|
6
|
+
readonly name: string
|
|
7
|
+
readonly slugs: readonly string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function presetsPath(root: string): string {
|
|
11
|
+
return join(snippetsSourceDir(root), 'snippets.toml')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reads every preset from `snippets.toml`. The bash read section headers with
|
|
16
|
+
* `grep '^\['` and then re-walked the file per preset with a `BASH_REMATCH`
|
|
17
|
+
* loop, so a name carrying a bracket or a slug split across lines parsed by
|
|
18
|
+
* accident rather than by rule.
|
|
19
|
+
*/
|
|
20
|
+
export function loadPresets(root: string): Preset[] {
|
|
21
|
+
const path = presetsPath(root)
|
|
22
|
+
if (!existsSync(path)) return []
|
|
23
|
+
|
|
24
|
+
const parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
|
|
25
|
+
string,
|
|
26
|
+
unknown
|
|
27
|
+
>
|
|
28
|
+
const presets: Preset[] = []
|
|
29
|
+
|
|
30
|
+
for (const [name, table] of Object.entries(parsed)) {
|
|
31
|
+
if (typeof table !== 'object' || table === null || Array.isArray(table)) {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
const names = (table as Record<string, unknown>).names
|
|
35
|
+
presets.push({
|
|
36
|
+
name,
|
|
37
|
+
slugs: Array.isArray(names)
|
|
38
|
+
? names.filter(
|
|
39
|
+
(slug): slug is string => typeof slug === 'string' && slug !== '',
|
|
40
|
+
)
|
|
41
|
+
: [],
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return presets.sort((left, right) => left.name.localeCompare(right.name))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function findPreset(root: string, name: string): Preset | undefined {
|
|
49
|
+
return loadPresets(root).find((preset) => preset.name === name)
|
|
50
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import { gitEnv } from '@/git-env'
|
|
5
|
+
import { resolveBaseRef } from '@/git-files'
|
|
6
|
+
import { INDEX_FILE, standardsSourceDir } from '@/standards/read'
|
|
7
|
+
|
|
8
|
+
/** Returned when a standard new to this branch carries no `## Success criterion` section, the gating check. */
|
|
9
|
+
export const EXIT_MISSING_CRITERION = 2
|
|
10
|
+
|
|
11
|
+
/** Matched at any casing, level-2 only, per the heading `standards/standard.md` itself uses. */
|
|
12
|
+
const CRITERION_HEADING = /^##\s+success criterion\s*$/im
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The reasons an audit produces no reading. `no-corpus` is the ordinary state
|
|
16
|
+
* of a target that authors no standards of its own, the same absence the
|
|
17
|
+
* skills audit reads as its own `no-corpus`. The other two are a broken git
|
|
18
|
+
* invocation rather than a project stating nothing.
|
|
19
|
+
*/
|
|
20
|
+
export type StandardsAuditRefusal =
|
|
21
|
+
| 'no-corpus'
|
|
22
|
+
| 'no-base'
|
|
23
|
+
| 'unreadable-arrivals'
|
|
24
|
+
|
|
25
|
+
export type StandardsAudit =
|
|
26
|
+
| {
|
|
27
|
+
readonly kind: 'measured'
|
|
28
|
+
readonly base: string
|
|
29
|
+
readonly standards: readonly string[]
|
|
30
|
+
readonly withCriterion: readonly string[]
|
|
31
|
+
readonly withoutCriterion: readonly string[]
|
|
32
|
+
readonly arrivals: readonly string[]
|
|
33
|
+
readonly arrivalsWithoutCriterion: readonly string[]
|
|
34
|
+
}
|
|
35
|
+
| { readonly kind: 'refused'; readonly reason: StandardsAuditRefusal }
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Measures the corpus authored at `standards/` under `root` against the
|
|
39
|
+
* `## Success criterion` gate `standards/standard.md` states, and names which
|
|
40
|
+
* of those files are new since the branch's merge base.
|
|
41
|
+
*
|
|
42
|
+
* Reads the working-root corpus alone, never the packaged fallback
|
|
43
|
+
* `src/standards/read.ts` falls through to for a name lookup, since a target
|
|
44
|
+
* with no authored standards of its own has nothing here to gate.
|
|
45
|
+
*/
|
|
46
|
+
export async function auditStandards(root: string): Promise<StandardsAudit> {
|
|
47
|
+
const dir = standardsSourceDir(root)
|
|
48
|
+
if (!existsSync(dir)) return { kind: 'refused', reason: 'no-corpus' }
|
|
49
|
+
|
|
50
|
+
const standards = readdirSync(dir, { withFileTypes: true })
|
|
51
|
+
.filter(
|
|
52
|
+
(entry) =>
|
|
53
|
+
entry.isFile() &&
|
|
54
|
+
entry.name.endsWith('.md') &&
|
|
55
|
+
entry.name !== INDEX_FILE,
|
|
56
|
+
)
|
|
57
|
+
.map((entry) => entry.name)
|
|
58
|
+
.sort()
|
|
59
|
+
|
|
60
|
+
const withCriterion: string[] = []
|
|
61
|
+
const withoutCriterion: string[] = []
|
|
62
|
+
for (const name of standards) {
|
|
63
|
+
const body = readFileSync(join(dir, name), 'utf8')
|
|
64
|
+
;(CRITERION_HEADING.test(body) ? withCriterion : withoutCriterion).push(
|
|
65
|
+
name,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const base = await resolveBaseRef(root)
|
|
70
|
+
if (base === undefined) return { kind: 'refused', reason: 'no-base' }
|
|
71
|
+
|
|
72
|
+
const arrived = await arrivedStandards(root, base)
|
|
73
|
+
if (arrived === undefined) {
|
|
74
|
+
return { kind: 'refused', reason: 'unreadable-arrivals' }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const arrivals = standards.filter((name) => arrived.has(name))
|
|
78
|
+
const arrivalsWithoutCriterion = arrivals.filter((name) =>
|
|
79
|
+
withoutCriterion.includes(name),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
kind: 'measured',
|
|
84
|
+
base,
|
|
85
|
+
standards,
|
|
86
|
+
withCriterion,
|
|
87
|
+
withoutCriterion,
|
|
88
|
+
arrivals,
|
|
89
|
+
arrivalsWithoutCriterion,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Only an arrival missing the section sets a failing code. Every other
|
|
95
|
+
* standard without one is a known gap `standards/standard.md` names rather
|
|
96
|
+
* than a violation, so failing the push on the 26 already there teaches
|
|
97
|
+
* contributors to route around the stage.
|
|
98
|
+
*/
|
|
99
|
+
export function auditExitCode(audit: StandardsAudit): number {
|
|
100
|
+
if (audit.kind === 'refused') return 1
|
|
101
|
+
return audit.arrivalsWithoutCriterion.length > 0 ? EXIT_MISSING_CRITERION : 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Filenames under `standards/` present in the working tree and absent at
|
|
106
|
+
* `base`: a plain add, with rename detection forced off so a standard moved
|
|
107
|
+
* into the corpus from elsewhere counts the same as one authored fresh.
|
|
108
|
+
*/
|
|
109
|
+
async function arrivedStandards(
|
|
110
|
+
root: string,
|
|
111
|
+
base: string,
|
|
112
|
+
): Promise<Set<string> | undefined> {
|
|
113
|
+
const [added, untracked] = await Promise.all([
|
|
114
|
+
$`git -C ${root} diff --no-renames --name-only --diff-filter=A ${base} -- standards`
|
|
115
|
+
.env(gitEnv())
|
|
116
|
+
.quiet()
|
|
117
|
+
.nothrow(),
|
|
118
|
+
$`git -C ${root} ls-files --others --exclude-standard -- standards`
|
|
119
|
+
.env(gitEnv())
|
|
120
|
+
.quiet()
|
|
121
|
+
.nothrow(),
|
|
122
|
+
])
|
|
123
|
+
|
|
124
|
+
if (added.exitCode !== 0 || untracked.exitCode !== 0) return undefined
|
|
125
|
+
|
|
126
|
+
const paths = [
|
|
127
|
+
...added.text().split('\n'),
|
|
128
|
+
...untracked.text().split('\n'),
|
|
129
|
+
].filter(Boolean)
|
|
130
|
+
|
|
131
|
+
return new Set(paths.map((path) => basename(path)))
|
|
132
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import { stripFrontmatter } from '@/frontmatter'
|
|
4
|
+
import { PROJECT_ROOT } from '@/project-root'
|
|
5
|
+
|
|
6
|
+
export const INDEX_FILE = 'index.md'
|
|
7
|
+
|
|
8
|
+
/** The folder a repository authors standards in, toolkit or project alike. */
|
|
9
|
+
export function standardsSourceDir(root: string): string {
|
|
10
|
+
return join(root, 'standards')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Spells the package root in a report where every other root spells a
|
|
15
|
+
* project-relative path. The `source` field promises a path inside the project
|
|
16
|
+
* and a package copy is the one source that promise cannot cover, so the
|
|
17
|
+
* spelling has to be one nothing will join to a project root.
|
|
18
|
+
*/
|
|
19
|
+
const PACKAGE_LABEL = '<canon>'
|
|
20
|
+
|
|
21
|
+
export interface StandardRoot {
|
|
22
|
+
/** Absolute directory to search. */
|
|
23
|
+
readonly dir: string
|
|
24
|
+
/** How a report spells a copy found under this root. */
|
|
25
|
+
readonly label: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResolvedStandard {
|
|
29
|
+
readonly path: string
|
|
30
|
+
/** The label of the root that won, joined to the filename. */
|
|
31
|
+
readonly source: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The roots a standard resolves against. No toolkit standard installs into a
|
|
36
|
+
* project any more, so `.claude/standards/` is gone from the list, no repository
|
|
37
|
+
* generates one, and a target
|
|
38
|
+
* carries neither entry below: the package corpus is the only root that
|
|
39
|
+
* answers there, which is what leaves no precedence to reason about.
|
|
40
|
+
*
|
|
41
|
+
* The authoring root stays ahead of the package corpus for the repository that
|
|
42
|
+
* writes standards, where an edit in progress has to win over the copy
|
|
43
|
+
* `package.json` shipped. A project that authors standards of its own is the
|
|
44
|
+
* same case under the same folder.
|
|
45
|
+
*/
|
|
46
|
+
export function standardRoots(root: string): StandardRoot[] {
|
|
47
|
+
return [
|
|
48
|
+
{ dir: standardsSourceDir(root), label: 'standards' },
|
|
49
|
+
{
|
|
50
|
+
dir: standardsSourceDir(PROJECT_ROOT),
|
|
51
|
+
label: join(PACKAGE_LABEL, 'standards'),
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function filename(name: string): string {
|
|
57
|
+
return name.endsWith('.md') ? name : `${name}.md`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Resolves a standard by name, with or without the extension. */
|
|
61
|
+
export function resolveStandard(
|
|
62
|
+
root: string,
|
|
63
|
+
name: string,
|
|
64
|
+
): ResolvedStandard | undefined {
|
|
65
|
+
const file = filename(name)
|
|
66
|
+
|
|
67
|
+
for (const { dir, label } of standardRoots(root)) {
|
|
68
|
+
const path = join(dir, file)
|
|
69
|
+
if (existsSync(path)) return { path, source: join(label, file) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Names every standard a resolve could reach, deduplicated across the roots and
|
|
77
|
+
* sorted. Shown when a name misses, so it doubles as the answer to what the
|
|
78
|
+
* caller should have typed.
|
|
79
|
+
*/
|
|
80
|
+
export function listStandards(root: string): string[] {
|
|
81
|
+
const names = new Set<string>()
|
|
82
|
+
|
|
83
|
+
for (const { dir } of standardRoots(root)) {
|
|
84
|
+
if (!existsSync(dir)) continue
|
|
85
|
+
|
|
86
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
87
|
+
if (!entry.isFile()) continue
|
|
88
|
+
if (!entry.name.endsWith('.md')) continue
|
|
89
|
+
if (entry.name === INDEX_FILE) continue
|
|
90
|
+
|
|
91
|
+
names.add(basename(entry.name, '.md'))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [...names].sort()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function readStandard(standard: ResolvedStandard): string {
|
|
99
|
+
return stripFrontmatter(readFileSync(standard.path, 'utf8'))
|
|
100
|
+
}
|