@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,196 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import { listRepositoryFiles } from '@/git-files'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Suppresses citation checking for the source line carrying it.
|
|
8
|
+
*
|
|
9
|
+
* Prose that teaches a naming pattern displays a path rather than pointing at
|
|
10
|
+
* one, and no syntax separates the two: an illustration and a reference are
|
|
11
|
+
* both inline code in a sentence. Location covers the fixture trees and fenced
|
|
12
|
+
* examples, and this covers what is left, which is a sentence naming a
|
|
13
|
+
* hypothetical entry to show the shape of the name.
|
|
14
|
+
*/
|
|
15
|
+
export const IGNORE_MARKER = 'audit-ignore-citations'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches the marker plus an optional comma-separated path list.
|
|
19
|
+
*
|
|
20
|
+
* `<!-- audit-ignore-citations: .claude/standards/X.md -->` narrows the skip
|
|
21
|
+
* to the paths named. The bare form with no colon skips every citation the
|
|
22
|
+
* line carries, which is the only shape a line with nothing else worth
|
|
23
|
+
* checking needs and the shape every marker predating this pattern still
|
|
24
|
+
* carries.
|
|
25
|
+
*/
|
|
26
|
+
const IGNORE_MARKER_LINE =
|
|
27
|
+
/<!--\s*audit-ignore-citations(?::\s*([^>]+?))?\s*-->/
|
|
28
|
+
|
|
29
|
+
interface IgnoredCitations {
|
|
30
|
+
readonly all: boolean
|
|
31
|
+
readonly paths: ReadonlySet<string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reads which citations on a line the marker excuses.
|
|
36
|
+
*
|
|
37
|
+
* A bare marker excuses every citation the line carries, matching the
|
|
38
|
+
* pre-named-path behavior. A named marker excuses only the paths listed,
|
|
39
|
+
* so a line carrying both a placeholder and a real reference keeps the real
|
|
40
|
+
* one checked rather than losing it to the placeholder beside it.
|
|
41
|
+
*/
|
|
42
|
+
function ignoredCitations(line: string): IgnoredCitations | undefined {
|
|
43
|
+
const match = line.match(IGNORE_MARKER_LINE)
|
|
44
|
+
if (!match) return undefined
|
|
45
|
+
if (match[1] === undefined) return { all: true, paths: new Set() }
|
|
46
|
+
|
|
47
|
+
const paths = match[1]
|
|
48
|
+
.split(',')
|
|
49
|
+
.map((path) => path.trim())
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
return { all: false, paths: new Set(paths) }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Trees holding content authored to be parsed rather than followed.
|
|
56
|
+
*
|
|
57
|
+
* Sandbox scenarios describe paths inside their own scratch fixtures and the
|
|
58
|
+
* eval harness names paths in its target project. Neither is a reference into
|
|
59
|
+
* this repository, so an unresolved path there is correct rather than stale.
|
|
60
|
+
*/
|
|
61
|
+
const FIXTURE_TREES: readonly string[] = ['scripts/sandbox/', 'scripts/eval/']
|
|
62
|
+
|
|
63
|
+
const FIXTURE_SEGMENTS: readonly string[] = ['fixtures', '__fixtures__']
|
|
64
|
+
|
|
65
|
+
const FENCE = /^\s*(```|~~~)/
|
|
66
|
+
|
|
67
|
+
export interface Citation {
|
|
68
|
+
readonly file: string
|
|
69
|
+
readonly line: number
|
|
70
|
+
readonly path: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isFixture(rel: string): boolean {
|
|
74
|
+
if (rel.endsWith('.test.ts')) return true
|
|
75
|
+
if (FIXTURE_TREES.some((tree) => rel.startsWith(tree))) return true
|
|
76
|
+
return rel.split('/').some((segment) => FIXTURE_SEGMENTS.includes(segment))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function citationPattern(folders: readonly string[]): RegExp {
|
|
80
|
+
const names = folders.map((name) =>
|
|
81
|
+
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
|
82
|
+
)
|
|
83
|
+
return new RegExp(
|
|
84
|
+
`\\.claude/(?:${names.join('|')})/[A-Za-z0-9._/-]+\\.md`,
|
|
85
|
+
'g',
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A backticked filename carrying no folder, the form a reference takes when it
|
|
91
|
+
* names a sibling rather than a path.
|
|
92
|
+
*
|
|
93
|
+
* `citationPattern` spells the `.claude/` prefix and cannot see this shape at
|
|
94
|
+
* all, which is the reason the form rule exists. Widening that expression to
|
|
95
|
+
* admit a bare name was the alternative and it puts one match in the position of
|
|
96
|
+
* answering two questions, since a spelled path is a reference by construction
|
|
97
|
+
* and a bare name is a candidate whichever caller found it still has to test
|
|
98
|
+
* against the folder it sits in.
|
|
99
|
+
*
|
|
100
|
+
* The backticks are required rather than incidental. A filename written into
|
|
101
|
+
* running prose without them is not a reference a reader follows, and matching
|
|
102
|
+
* one would report every sentence that happens to name a file.
|
|
103
|
+
*/
|
|
104
|
+
export const BARE_NAME = /`([A-Za-z0-9._-]+\.md)`/g
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pulls the cited paths out of one file's text.
|
|
108
|
+
*
|
|
109
|
+
* Fenced blocks are skipped for markdown only. A fence is markdown syntax, and
|
|
110
|
+
* applying it to a shell or TypeScript source would let a heredoc of triple
|
|
111
|
+
* backticks silently hide the rest of the file.
|
|
112
|
+
*/
|
|
113
|
+
export function collectCitations(
|
|
114
|
+
rel: string,
|
|
115
|
+
text: string,
|
|
116
|
+
pattern: RegExp,
|
|
117
|
+
): Citation[] {
|
|
118
|
+
const isMarkdown = rel.endsWith('.md')
|
|
119
|
+
const found: Citation[] = []
|
|
120
|
+
let fenced = false
|
|
121
|
+
|
|
122
|
+
for (const [index, line] of text.split('\n').entries()) {
|
|
123
|
+
if (isMarkdown && FENCE.test(line)) {
|
|
124
|
+
fenced = !fenced
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
if (fenced) continue
|
|
128
|
+
|
|
129
|
+
const ignored = ignoredCitations(line)
|
|
130
|
+
|
|
131
|
+
for (const match of line.matchAll(pattern)) {
|
|
132
|
+
if (ignored && (ignored.all || ignored.paths.has(match[0]))) continue
|
|
133
|
+
found.push({ file: rel, line: index + 1, path: match[0] })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return found
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `unavailable` is a distinct state from a clean scan.
|
|
142
|
+
*
|
|
143
|
+
* Finding nothing and being unable to look mean opposite things, and only one
|
|
144
|
+
* of them should let a push through.
|
|
145
|
+
*/
|
|
146
|
+
export type CitationReport =
|
|
147
|
+
| {
|
|
148
|
+
readonly kind: 'scanned'
|
|
149
|
+
readonly scanned: number
|
|
150
|
+
readonly total: number
|
|
151
|
+
readonly unresolved: readonly Citation[]
|
|
152
|
+
}
|
|
153
|
+
| { readonly kind: 'unavailable' }
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Resolves every cited path outside the fixture trees.
|
|
157
|
+
*
|
|
158
|
+
* This is the only check that gates the repository check, which makes it the
|
|
159
|
+
* only one whose false positives cost a contributor anything. It reports a
|
|
160
|
+
* finding solely for a path that does not resolve on disk, and the exclusions
|
|
161
|
+
* above are what keep that from firing on prose about paths.
|
|
162
|
+
*/
|
|
163
|
+
export async function auditCitations(
|
|
164
|
+
root: string,
|
|
165
|
+
folders: readonly string[],
|
|
166
|
+
): Promise<CitationReport> {
|
|
167
|
+
const pattern = citationPattern(folders)
|
|
168
|
+
const listed = await listRepositoryFiles(root)
|
|
169
|
+
if (!listed) return { kind: 'unavailable' }
|
|
170
|
+
|
|
171
|
+
const candidates = listed.filter((rel) => !isFixture(rel))
|
|
172
|
+
const citations: Citation[] = []
|
|
173
|
+
|
|
174
|
+
for (const rel of candidates) {
|
|
175
|
+
const path = resolve(root, rel)
|
|
176
|
+
if (!existsSync(path)) continue
|
|
177
|
+
|
|
178
|
+
let text: string
|
|
179
|
+
try {
|
|
180
|
+
text = await readFile(path, 'utf8')
|
|
181
|
+
} catch {
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
citations.push(...collectCitations(rel, text, pattern))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
kind: 'scanned',
|
|
190
|
+
scanned: candidates.length,
|
|
191
|
+
total: citations.length,
|
|
192
|
+
unresolved: citations.filter(
|
|
193
|
+
(citation) => !existsSync(resolve(root, citation.path)),
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path'
|
|
3
|
+
import { INDEX_FILE, listIndexes } from '@/indexes/walk'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Folder names under `.claude/` audited by default.
|
|
7
|
+
*
|
|
8
|
+
* A named list rather than the index-plus-entry contract read off disk, so a
|
|
9
|
+
* generated tree satisfying that contract is never measured against a rule
|
|
10
|
+
* written for per-domain narrative. This list names the folders
|
|
11
|
+
* `standards/context.md` governs, and `--folder` admits another without an edit
|
|
12
|
+
* here.
|
|
13
|
+
*
|
|
14
|
+
* It doubles as the citation check's scope, since `citationPattern` spells only
|
|
15
|
+
* these names. A `.claude/` folder left off the list is never resolved, so a
|
|
16
|
+
* path into one goes stale silently rather than failing a push.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_FOLDERS: readonly string[] = [
|
|
19
|
+
'context',
|
|
20
|
+
'diagrams',
|
|
21
|
+
'wireframes',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/** The base every folder in the default list sits under. */
|
|
25
|
+
const CLAUDE_BASE = '.claude'
|
|
26
|
+
|
|
27
|
+
/** The project root, reached only by a name the caller asked for. */
|
|
28
|
+
const ROOT_BASE = '.'
|
|
29
|
+
|
|
30
|
+
export interface AuditedFolder {
|
|
31
|
+
/**
|
|
32
|
+
* The requested folder name this was resolved under, which is what says
|
|
33
|
+
* which standard governs the entries. A nested split folder carries the name
|
|
34
|
+
* of the folder it sits beneath rather than its own, so
|
|
35
|
+
* `.claude/context/claude-plugin` is governed as `context`.
|
|
36
|
+
*/
|
|
37
|
+
readonly name: string
|
|
38
|
+
/**
|
|
39
|
+
* The base the name resolved under, which is what says whether the folder is
|
|
40
|
+
* in the citation check's scope.
|
|
41
|
+
*/
|
|
42
|
+
readonly base: string
|
|
43
|
+
/** Repo-relative folder path, used verbatim in every report line. */
|
|
44
|
+
readonly rel: string
|
|
45
|
+
readonly indexPath: string
|
|
46
|
+
/** Absolute paths of the folder's own entries, excluding its `index.md`. */
|
|
47
|
+
readonly entries: readonly string[]
|
|
48
|
+
/**
|
|
49
|
+
* Whether this is a sub-area a domain split into rather than the folder
|
|
50
|
+
* named under `.claude/`.
|
|
51
|
+
*
|
|
52
|
+
* The entries of a split folder describe one domain between them, so a rule
|
|
53
|
+
* about what a domain declares is answered by the folder. The entries of the
|
|
54
|
+
* named folder are one domain each, and a rule answered by a sibling there
|
|
55
|
+
* would let one entry stand in for domains it says nothing about.
|
|
56
|
+
*/
|
|
57
|
+
readonly nested: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Names the requested `.claude/` folders that actually exist, which is the
|
|
62
|
+
* citation check's scope.
|
|
63
|
+
*
|
|
64
|
+
* A skill or seed pointing into `.claude/wireframes/` is a live instruction for
|
|
65
|
+
* a project that carries the folder and says nothing about one that does not.
|
|
66
|
+
* Checking a path into an absent folder would fail eight shipped references
|
|
67
|
+
* here for the sole reason that this repository has no wireframes.
|
|
68
|
+
*
|
|
69
|
+
* A folder resolved at the project root is measured and stays out of this. The
|
|
70
|
+
* pattern the citation check builds spells the `.claude/` prefix, so admitting
|
|
71
|
+
* a root name there would check `.claude/<name>/` paths the audit never read.
|
|
72
|
+
* Widening the pattern to the root spelling is a separate change, since a bare
|
|
73
|
+
* `docs/x.md` appears in prose that references nothing.
|
|
74
|
+
*/
|
|
75
|
+
export function presentNames(folders: readonly AuditedFolder[]): string[] {
|
|
76
|
+
return [
|
|
77
|
+
...new Set(
|
|
78
|
+
folders
|
|
79
|
+
.filter((folder) => folder.base === CLAUDE_BASE)
|
|
80
|
+
.map((folder) => folder.name),
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function readEntries(dir: string): Promise<string[]> {
|
|
86
|
+
const paths: string[] = []
|
|
87
|
+
|
|
88
|
+
for await (const name of new Bun.Glob('*.md').scan({
|
|
89
|
+
cwd: dir,
|
|
90
|
+
onlyFiles: true,
|
|
91
|
+
dot: true,
|
|
92
|
+
})) {
|
|
93
|
+
if (name === INDEX_FILE) continue
|
|
94
|
+
paths.push(resolve(dir, name))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return paths.sort()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface FolderResolution {
|
|
101
|
+
/** Every folder that resolved, with the nested splits beneath each. */
|
|
102
|
+
readonly folders: readonly AuditedFolder[]
|
|
103
|
+
/**
|
|
104
|
+
* Requested names that resolved under no base, reported rather than dropped.
|
|
105
|
+
* Which absences are worth saying out loud is the caller's judgment: a
|
|
106
|
+
* default folder a project does not carry is ordinary, and a name passed by
|
|
107
|
+
* hand that resolves nowhere is a typo that would otherwise read as a pass.
|
|
108
|
+
*/
|
|
109
|
+
readonly missing: readonly string[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ResolveOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Whether a name may resolve at the project root when `.claude/` does not
|
|
115
|
+
* carry it. False for the default list, which names three folders a project
|
|
116
|
+
* is expected to hold under `.claude/` and nowhere else.
|
|
117
|
+
*/
|
|
118
|
+
readonly canResolveAtRoot?: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function locate(
|
|
122
|
+
root: string,
|
|
123
|
+
name: string,
|
|
124
|
+
bases: readonly string[],
|
|
125
|
+
): { readonly dir: string; readonly base: string } | undefined {
|
|
126
|
+
for (const base of bases) {
|
|
127
|
+
const dir = resolve(root, base, name)
|
|
128
|
+
if (existsSync(`${dir}/${INDEX_FILE}`)) return { dir, base }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Resolves the folders to audit under `root`.
|
|
136
|
+
*
|
|
137
|
+
* A named folder contributes its own entries plus those of every nested index
|
|
138
|
+
* folder beneath it, so a domain that outgrew one file and split is audited at
|
|
139
|
+
* the same grain as one that did not. Discovery of the nested folders runs
|
|
140
|
+
* through the shared walker, which is what keeps `.gitignore` and the vendored
|
|
141
|
+
* prune governing this scan as well as index regeneration. That prune is what
|
|
142
|
+
* lets a root folder be walked at all, since a name at the project root sits
|
|
143
|
+
* beside `node_modules` and a build output.
|
|
144
|
+
*
|
|
145
|
+
* The project root is reached only when the caller opts in, so a target holding
|
|
146
|
+
* a root `wireframes/` is not audited against a standard it never adopted by
|
|
147
|
+
* the mere act of running the command. `.claude/` still wins a name carried by
|
|
148
|
+
* both, and the scope line prints the resolved path so a caller reads which
|
|
149
|
+
* base was taken rather than inferring it.
|
|
150
|
+
*
|
|
151
|
+
* Nothing above this asks where a folder came from. A name that resolves at the
|
|
152
|
+
* root is measured by every rule that generalizes and gated out of the rules a
|
|
153
|
+
* single standard carries, which `governsContent` decides from the name.
|
|
154
|
+
*/
|
|
155
|
+
export async function resolveFolders(
|
|
156
|
+
root: string,
|
|
157
|
+
names: readonly string[] = DEFAULT_FOLDERS,
|
|
158
|
+
{ canResolveAtRoot = false }: ResolveOptions = {},
|
|
159
|
+
): Promise<FolderResolution> {
|
|
160
|
+
const bases = canResolveAtRoot ? [CLAUDE_BASE, ROOT_BASE] : [CLAUDE_BASE]
|
|
161
|
+
const folders: AuditedFolder[] = []
|
|
162
|
+
const missing: string[] = []
|
|
163
|
+
|
|
164
|
+
for (const name of names) {
|
|
165
|
+
const found = locate(root, name, bases)
|
|
166
|
+
if (!found) {
|
|
167
|
+
missing.push(name)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const dirs = [found.dir, ...(await listIndexes(found.dir)).map(dirname)]
|
|
172
|
+
|
|
173
|
+
for (const each of [...new Set(dirs)].sort()) {
|
|
174
|
+
folders.push({
|
|
175
|
+
name,
|
|
176
|
+
base: found.base,
|
|
177
|
+
rel: relative(root, each),
|
|
178
|
+
indexPath: `${each}/${INDEX_FILE}`,
|
|
179
|
+
entries: await readEntries(each),
|
|
180
|
+
nested: each !== found.dir,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { folders, missing }
|
|
186
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SectionFinding } from '@/context/audit'
|
|
2
|
+
import type { FolderDrift } from '@/context/index-drift'
|
|
3
|
+
|
|
4
|
+
export interface GateInput {
|
|
5
|
+
/** Cited paths that resolved to nothing, which gate under either mode. */
|
|
6
|
+
readonly unresolvedCitations: number
|
|
7
|
+
/**
|
|
8
|
+
* Whether the architecture record is longer than the ceiling it derives for
|
|
9
|
+
* itself, which gates under either mode for the reason a citation does.
|
|
10
|
+
*
|
|
11
|
+
* False when the project carries no record and false under
|
|
12
|
+
* `--citations-only`, which never measures it. That mode runs one check by
|
|
13
|
+
* construction, so widening it here would gate on a reading it never took.
|
|
14
|
+
*/
|
|
15
|
+
readonly recordOverLength: boolean
|
|
16
|
+
readonly sections: readonly SectionFinding[]
|
|
17
|
+
readonly drift: readonly FolderDrift[]
|
|
18
|
+
/**
|
|
19
|
+
* Whether the caller asked for the widened gate. False leaves a missing
|
|
20
|
+
* section and a drifted index advisory, which is what the project-root stage
|
|
21
|
+
* runs so a judgment threshold never fails a push.
|
|
22
|
+
*/
|
|
23
|
+
readonly widened: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Whether any folder disagrees with its own index. */
|
|
27
|
+
export function hasDrift(drift: readonly FolderDrift[]): boolean {
|
|
28
|
+
return drift.some(
|
|
29
|
+
(folder) => folder.unlisted.length > 0 || folder.missing.length > 0,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether the audit found something that should fail the caller.
|
|
35
|
+
*
|
|
36
|
+
* An unresolved citation is a broken pointer and gates unconditionally, and so
|
|
37
|
+
* does a record past its own ceiling: the record states the limit for itself
|
|
38
|
+
* and derives it from a count, which makes it the one measure here that is a
|
|
39
|
+
* fact rather than a threshold a reader weighs. The two findings `--gate` adds
|
|
40
|
+
* are the ones answerable from the file itself: a required section it does not
|
|
41
|
+
* declare, and an index disagreeing with its folder. Entry length, depth,
|
|
42
|
+
* bullet, table, provenance, and the record's claim coverage are judgments, so
|
|
43
|
+
* they stay out under both modes.
|
|
44
|
+
*/
|
|
45
|
+
export function isGating({
|
|
46
|
+
unresolvedCitations,
|
|
47
|
+
recordOverLength,
|
|
48
|
+
sections,
|
|
49
|
+
drift,
|
|
50
|
+
widened,
|
|
51
|
+
}: GateInput): boolean {
|
|
52
|
+
if (unresolvedCitations > 0) return true
|
|
53
|
+
if (recordOverLength) return true
|
|
54
|
+
if (!widened) return false
|
|
55
|
+
|
|
56
|
+
return sections.length > 0 || hasDrift(drift)
|
|
57
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
4
|
+
import type { AuditedFolder } from '@/context/folders'
|
|
5
|
+
|
|
6
|
+
const ENTRY_LINK = /^-\s+\[[^\]]*\]\(([^)]+)\)/
|
|
7
|
+
|
|
8
|
+
export interface FolderDrift {
|
|
9
|
+
readonly rel: string
|
|
10
|
+
/** Entry files present in the folder that the index does not link. */
|
|
11
|
+
readonly unlisted: readonly string[]
|
|
12
|
+
/** Names the index links that resolve to nothing on disk. */
|
|
13
|
+
readonly missing: readonly string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Collects the link targets of an index's catalog bullets. */
|
|
17
|
+
export function listedTargets(source: string): string[] {
|
|
18
|
+
const targets: string[] = []
|
|
19
|
+
|
|
20
|
+
for (const line of source.split('\n')) {
|
|
21
|
+
const match = line.match(ENTRY_LINK)
|
|
22
|
+
if (match) targets.push(match[1])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return targets
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compares one folder's index against its siblings in both directions.
|
|
30
|
+
*
|
|
31
|
+
* Both halves are reported because they fail differently. An unlisted entry is
|
|
32
|
+
* invisible to a session reading the catalog to choose what to open, while a
|
|
33
|
+
* listed name with no file sends one to a path that opens nothing. Neither
|
|
34
|
+
* surfaces from reading the index alone.
|
|
35
|
+
*/
|
|
36
|
+
export async function auditFolder(folder: AuditedFolder): Promise<FolderDrift> {
|
|
37
|
+
const dir = dirname(folder.indexPath)
|
|
38
|
+
const source = await readFile(folder.indexPath, 'utf8')
|
|
39
|
+
const targets = listedTargets(source)
|
|
40
|
+
|
|
41
|
+
// A sub-catalog is linked as `<name>/index.md` and its entries are audited
|
|
42
|
+
// as their own folder, so only the leading segment is compared here.
|
|
43
|
+
const listed = new Set(targets.map((target) => target.split('/')[0]))
|
|
44
|
+
|
|
45
|
+
const unlisted = folder.entries
|
|
46
|
+
.map((path) => basename(path))
|
|
47
|
+
.filter((name) => !listed.has(name))
|
|
48
|
+
|
|
49
|
+
const missing = targets.filter((target) => !existsSync(resolve(dir, target)))
|
|
50
|
+
|
|
51
|
+
return { rel: folder.rel, unlisted, missing }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function auditIndexes(
|
|
55
|
+
folders: readonly AuditedFolder[],
|
|
56
|
+
): Promise<FolderDrift[]> {
|
|
57
|
+
const drift: FolderDrift[] = []
|
|
58
|
+
|
|
59
|
+
for (const folder of folders) {
|
|
60
|
+
drift.push(await auditFolder(folder))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return drift
|
|
64
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The headings a rule carries to publish the two sets.
|
|
7
|
+
*
|
|
8
|
+
* Discovery is anchored on these rather than on a filename for the reason
|
|
9
|
+
* `src/comments/vocabulary.ts` anchors its own: governance rules are numbered,
|
|
10
|
+
* and a renumber would empty the sets while the check kept reporting clean.
|
|
11
|
+
* Both open with `Narration` so neither loader can read the other's list.
|
|
12
|
+
*/
|
|
13
|
+
export const PRONOUN_HEADING = '## Narration pronouns'
|
|
14
|
+
export const VERB_HEADING = '## Narration verbs'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Roots searched in order. The installed copy wins over the toolkit source, so
|
|
18
|
+
* a target project measures against the rule it actually has rather than one
|
|
19
|
+
* only the toolkit carries.
|
|
20
|
+
*/
|
|
21
|
+
const RULE_ROOTS = ['.claude/rules', 'governance/rules']
|
|
22
|
+
|
|
23
|
+
export interface NarrationTerms {
|
|
24
|
+
/** Back-reference openings, matched at the start of a bullet and cased. */
|
|
25
|
+
readonly pronouns: readonly string[]
|
|
26
|
+
/** Past-tense verbs, matched anywhere in the bullet and uncased. */
|
|
27
|
+
readonly verbs: readonly string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Absent is a distinct state from empty, the split the comment vocabulary
|
|
32
|
+
* draws for the same reason: a scan with no terms finds nothing, and reporting
|
|
33
|
+
* that as zero findings claims the corpus is clean when nothing was looked for.
|
|
34
|
+
*/
|
|
35
|
+
export type Narration =
|
|
36
|
+
| ({ readonly kind: 'loaded'; readonly source: string } & NarrationTerms)
|
|
37
|
+
| { readonly kind: 'absent' }
|
|
38
|
+
|
|
39
|
+
/** Pulls the backticked terms out of the bullets under one heading. */
|
|
40
|
+
export function parseTerms(
|
|
41
|
+
markdown: string,
|
|
42
|
+
heading: string,
|
|
43
|
+
): string[] | undefined {
|
|
44
|
+
const lines = markdown.split('\n')
|
|
45
|
+
const start = lines.findIndex((line) => line.trim() === heading)
|
|
46
|
+
if (start === -1) return undefined
|
|
47
|
+
|
|
48
|
+
const terms: string[] = []
|
|
49
|
+
|
|
50
|
+
for (const line of lines.slice(start + 1)) {
|
|
51
|
+
if (line.startsWith('## ')) break
|
|
52
|
+
for (const match of line.matchAll(/`([^`]+)`/g)) {
|
|
53
|
+
const term = match[1].trim()
|
|
54
|
+
if (term && !terms.includes(term)) terms.push(term)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return terms
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Finds the rule publishing both sets under `root`.
|
|
63
|
+
*
|
|
64
|
+
* Both come from one file rather than from whichever rule carries each. A
|
|
65
|
+
* pronoun set and a verb set are halves of one signal, and pairing them across
|
|
66
|
+
* files would let a renumber recombine them into a signal nobody wrote.
|
|
67
|
+
*/
|
|
68
|
+
export async function loadNarration(root: string): Promise<Narration> {
|
|
69
|
+
for (const ruleRoot of RULE_ROOTS) {
|
|
70
|
+
const dir = resolve(root, ruleRoot)
|
|
71
|
+
if (!existsSync(dir)) continue
|
|
72
|
+
|
|
73
|
+
const paths: string[] = []
|
|
74
|
+
for await (const rel of new Bun.Glob('**/*.md').scan({
|
|
75
|
+
cwd: dir,
|
|
76
|
+
onlyFiles: true,
|
|
77
|
+
})) {
|
|
78
|
+
paths.push(rel)
|
|
79
|
+
}
|
|
80
|
+
paths.sort()
|
|
81
|
+
|
|
82
|
+
for (const rel of paths) {
|
|
83
|
+
const source = await readFile(resolve(dir, rel), 'utf8')
|
|
84
|
+
const pronouns = parseTerms(source, PRONOUN_HEADING)
|
|
85
|
+
const verbs = parseTerms(source, VERB_HEADING)
|
|
86
|
+
|
|
87
|
+
if (pronouns && verbs && pronouns.length > 0 && verbs.length > 0) {
|
|
88
|
+
return {
|
|
89
|
+
kind: 'loaded',
|
|
90
|
+
source: `${ruleRoot}/${rel}`,
|
|
91
|
+
pronouns,
|
|
92
|
+
verbs,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { kind: 'absent' }
|
|
99
|
+
}
|
package/src/copy.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { chmod, copyFile, mkdir, stat } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Copies with `cp` semantics. Two behaviors matter and neither is the default
|
|
6
|
+
* in the obvious alternatives. `Bun.write` drops the executable bit, which
|
|
7
|
+
* would land husky hooks and `scripts/verify.sh` non-executable. Plain
|
|
8
|
+
* `copyFile` imposes the source mode on an existing destination, so a file the
|
|
9
|
+
* target deliberately restricted to 600, or an earlier stack set executable,
|
|
10
|
+
* comes back at the source's mode. `cp` leaves an existing destination's mode
|
|
11
|
+
* alone.
|
|
12
|
+
*
|
|
13
|
+
* Every domain that writes into a target routes through here. The tooling
|
|
14
|
+
* inject path found the trap first, and the sync engine inherits it for every
|
|
15
|
+
* adapter.
|
|
16
|
+
*/
|
|
17
|
+
export async function copyPreservingMode(
|
|
18
|
+
src: string,
|
|
19
|
+
dest: string,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
await mkdir(dirname(dest), { recursive: true })
|
|
22
|
+
|
|
23
|
+
const existingMode = await stat(dest)
|
|
24
|
+
.then((info) => info.mode)
|
|
25
|
+
.catch(() => undefined)
|
|
26
|
+
|
|
27
|
+
await copyFile(src, dest)
|
|
28
|
+
|
|
29
|
+
if (existingMode !== undefined) await chmod(dest, existingMode)
|
|
30
|
+
}
|