@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,205 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
installedRuleNames,
|
|
5
|
+
listRuleSourcePaths,
|
|
6
|
+
lookupRules,
|
|
7
|
+
type RuleSource,
|
|
8
|
+
rulesSourceDir,
|
|
9
|
+
} from '@/gov/install'
|
|
10
|
+
|
|
11
|
+
export interface GovStack {
|
|
12
|
+
readonly name: string
|
|
13
|
+
readonly parent?: string
|
|
14
|
+
readonly rules: readonly string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type RuleResolution =
|
|
18
|
+
| { readonly ok: true; readonly rules: string[] }
|
|
19
|
+
| { readonly ok: false; readonly missingStack: string }
|
|
20
|
+
|
|
21
|
+
function stacksDir(root: string): string {
|
|
22
|
+
return join(root, 'governance', 'stacks')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function govStackPath(root: string, stack: string): string {
|
|
26
|
+
return join(stacksDir(root), `${stack}.toml`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function govStackExists(root: string, stack: string): boolean {
|
|
30
|
+
return existsSync(govStackPath(root, stack))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Lists stack names the way `find -name "*.toml" -exec basename {} .toml` did.
|
|
35
|
+
*/
|
|
36
|
+
export function listGovStacks(root: string): string[] {
|
|
37
|
+
const dir = stacksDir(root)
|
|
38
|
+
if (!existsSync(dir)) return []
|
|
39
|
+
|
|
40
|
+
return [...new Bun.Glob('*.toml').scanSync({ cwd: dir, onlyFiles: true })]
|
|
41
|
+
.map((entry) => entry.slice(0, -'.toml'.length))
|
|
42
|
+
.sort()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Reads one stack file. `Bun.TOML.parse` replaces a `BASH_REMATCH` loop that
|
|
47
|
+
* consumed each rules line with `sed` as it walked, which is the hand-rolled
|
|
48
|
+
* parser shape the tooling manifest already moved off.
|
|
49
|
+
*/
|
|
50
|
+
export function loadGovStack(
|
|
51
|
+
root: string,
|
|
52
|
+
stack: string,
|
|
53
|
+
): GovStack | undefined {
|
|
54
|
+
const path = govStackPath(root, stack)
|
|
55
|
+
if (!existsSync(path)) return undefined
|
|
56
|
+
|
|
57
|
+
const parsed = Bun.TOML.parse(readFileSync(path, 'utf8')) as Record<
|
|
58
|
+
string,
|
|
59
|
+
unknown
|
|
60
|
+
>
|
|
61
|
+
const parent = typeof parsed.extends === 'string' ? parsed.extends : ''
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
name: stack,
|
|
65
|
+
parent: parent === '' ? undefined : parent,
|
|
66
|
+
rules: Array.isArray(parsed.rules)
|
|
67
|
+
? parsed.rules.filter(
|
|
68
|
+
(rule): rule is string => typeof rule === 'string' && rule !== '',
|
|
69
|
+
)
|
|
70
|
+
: [],
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Expands one stack entry. An entry naming a directory under
|
|
76
|
+
* `governance/rules/` resolves to every rule inside it, sorted, and any other
|
|
77
|
+
* entry resolves to itself, so a folder and a slug reach the caller as one
|
|
78
|
+
* shape rather than two the caller has to tell apart.
|
|
79
|
+
*
|
|
80
|
+
* The directory wins over a rule file of the same name. They cannot collide
|
|
81
|
+
* while `standards/rule.md` requires a numeric prefix on a rule slug, since a
|
|
82
|
+
* band folder carries none.
|
|
83
|
+
*/
|
|
84
|
+
export function expandStackEntry(root: string, entry: string): string[] {
|
|
85
|
+
const dir = join(rulesSourceDir(root), entry)
|
|
86
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) return [entry]
|
|
87
|
+
|
|
88
|
+
return [...new Bun.Glob('**/*.md').scanSync({ cwd: dir, onlyFiles: true })]
|
|
89
|
+
.sort()
|
|
90
|
+
.map((rel) => basename(rel, '.md'))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walks `extends` ancestors first, then the stack's own rules, deduped by
|
|
95
|
+
* first appearance. Tooling's `resolveChain` returns full manifests nearest
|
|
96
|
+
* first and carries `skipStack` truncation, so the two walks stay separate
|
|
97
|
+
* rather than fitting one shape to both.
|
|
98
|
+
*
|
|
99
|
+
* Dedupe runs on expanded names rather than on the entries, so a stack naming
|
|
100
|
+
* a folder and an ancestor naming a rule inside it yield that rule once.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveRules(root: string, stack: string): RuleResolution {
|
|
103
|
+
const rules: string[] = []
|
|
104
|
+
const seen = new Set<string>()
|
|
105
|
+
const visited = new Set<string>()
|
|
106
|
+
|
|
107
|
+
const walk = (current: string): string | undefined => {
|
|
108
|
+
if (visited.has(current)) return undefined
|
|
109
|
+
visited.add(current)
|
|
110
|
+
|
|
111
|
+
const loaded = loadGovStack(root, current)
|
|
112
|
+
if (!loaded) return current
|
|
113
|
+
|
|
114
|
+
if (loaded.parent !== undefined) {
|
|
115
|
+
const missing = walk(loaded.parent)
|
|
116
|
+
if (missing !== undefined) return missing
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const entry of loaded.rules) {
|
|
120
|
+
for (const rule of expandStackEntry(root, entry)) {
|
|
121
|
+
if (seen.has(rule)) continue
|
|
122
|
+
seen.add(rule)
|
|
123
|
+
rules.push(rule)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const missingStack = walk(stack)
|
|
131
|
+
if (missingStack !== undefined) return { ok: false, missingStack }
|
|
132
|
+
|
|
133
|
+
return { ok: true, rules }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Names every rule no stack reaches, sorted. A rule outside every stack still
|
|
138
|
+
* installs through `--add`, so this reports an opt-in library and an oversight
|
|
139
|
+
* alike and leaves telling them apart to the reader.
|
|
140
|
+
*
|
|
141
|
+
* A stack whose `extends` does not resolve contributes nothing rather than
|
|
142
|
+
* aborting the sweep, or one broken stack would report the whole catalog as
|
|
143
|
+
* unreferenced.
|
|
144
|
+
*/
|
|
145
|
+
export function unreferencedRules(root: string): string[] {
|
|
146
|
+
const reached = new Set<string>()
|
|
147
|
+
|
|
148
|
+
for (const stack of listGovStacks(root)) {
|
|
149
|
+
const resolution = resolveRules(root, stack)
|
|
150
|
+
if (!resolution.ok) continue
|
|
151
|
+
for (const rule of resolution.rules) reached.add(rule)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return listRuleSourcePaths(root)
|
|
155
|
+
.map((rel) => basename(rel, '.md'))
|
|
156
|
+
.filter((rule) => !reached.has(rule))
|
|
157
|
+
.sort()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Rules the target's recorded chain entitles it to that its installed tree
|
|
162
|
+
* does not hold. `resolveRules` already walks a stack's `extends` ancestors,
|
|
163
|
+
* so reading its leaf entry is enough; no second walk resolves the chain
|
|
164
|
+
* itself. A stack the toolkit no longer ships resolves to nothing rather than
|
|
165
|
+
* throwing, the same way `readNewRules`'s band fallback already treats it.
|
|
166
|
+
*/
|
|
167
|
+
export function resolveMissingRules(
|
|
168
|
+
root: string,
|
|
169
|
+
target: string,
|
|
170
|
+
chain: readonly string[],
|
|
171
|
+
): readonly RuleSource[] {
|
|
172
|
+
const stack = chain[0]
|
|
173
|
+
if (stack === undefined) return []
|
|
174
|
+
|
|
175
|
+
const resolution = resolveRules(root, stack)
|
|
176
|
+
if (!resolution.ok) return []
|
|
177
|
+
|
|
178
|
+
const { found } = lookupRules(root, resolution.rules)
|
|
179
|
+
const held = installedRuleNames(target)
|
|
180
|
+
|
|
181
|
+
return found
|
|
182
|
+
.filter((source) => !held.has(source.rule))
|
|
183
|
+
.sort((left, right) => left.rule.localeCompare(right.rule))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Layers `--add` names on top of a resolved stack. The bash trimmed a single
|
|
188
|
+
* leading and trailing space per entry; trimming fully is the same result for
|
|
189
|
+
* every input that parsed before.
|
|
190
|
+
*/
|
|
191
|
+
export function mergeExtraRules(
|
|
192
|
+
rules: readonly string[],
|
|
193
|
+
add: string,
|
|
194
|
+
): string[] {
|
|
195
|
+
const merged = [...rules]
|
|
196
|
+
|
|
197
|
+
for (const raw of add.split(',')) {
|
|
198
|
+
const extra = raw.trim()
|
|
199
|
+
if (extra === '') continue
|
|
200
|
+
if (merged.includes(extra)) continue
|
|
201
|
+
merged.push(extra)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return merged
|
|
205
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { isBinary } from '@/binary'
|
|
4
|
+
import { isMarked } from '@/exempt-marker'
|
|
5
|
+
import { listRepositoryFiles } from '@/git-files'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The inline token exempting one line from this sweep, shaped on the
|
|
9
|
+
* `canon-allow-secret` precedent and read by the same two-line rule.
|
|
10
|
+
*
|
|
11
|
+
* A declaration disagreeing with a convention for a stated reason is the case
|
|
12
|
+
* question 3 of the plan settled against gating on, and the marker is where
|
|
13
|
+
* that reason goes. A bare token names nothing a later reader can weigh, so it
|
|
14
|
+
* mutes nothing.
|
|
15
|
+
*/
|
|
16
|
+
export const SUPERSEDED_MARKER = 'canon-allow-superseded'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What matched at one column.
|
|
20
|
+
*
|
|
21
|
+
* `literal` is the superseded value itself. The other three are the stem
|
|
22
|
+
* followed by a glob, by an angle-bracket placeholder, or by nothing that
|
|
23
|
+
* continues a name, which are the three forms this corpus writes a family in.
|
|
24
|
+
* A literal comparison reaches none of them, which is why a rename running the
|
|
25
|
+
* verb once per name reported clean over seven stale citations.
|
|
26
|
+
*/
|
|
27
|
+
export type SupersededMatch = 'literal' | 'glob' | 'placeholder' | 'prefix'
|
|
28
|
+
|
|
29
|
+
export interface SupersededHit {
|
|
30
|
+
readonly file: string
|
|
31
|
+
/** One-based, matching the `file:line` form a reader clicks. */
|
|
32
|
+
readonly line: number
|
|
33
|
+
readonly column: number
|
|
34
|
+
readonly match: SupersededMatch
|
|
35
|
+
/**
|
|
36
|
+
* The nearest heading above the hit in a markdown file, absent elsewhere and
|
|
37
|
+
* above the first heading.
|
|
38
|
+
*
|
|
39
|
+
* A line reads differently under the section holding it. `Use the canon-*
|
|
40
|
+
* prefix on an internal skill` is a prohibition under `## Must not` and an
|
|
41
|
+
* instruction anywhere else, and a reviewer reading the line alone made
|
|
42
|
+
* exactly that misreading against this tree.
|
|
43
|
+
*/
|
|
44
|
+
readonly heading: string | undefined
|
|
45
|
+
/**
|
|
46
|
+
* Whether the replacement sits on the same line, outside the superseded
|
|
47
|
+
* occurrences themselves.
|
|
48
|
+
*
|
|
49
|
+
* Read outside them because the replacement is routinely a substring of the
|
|
50
|
+
* value it replaced, which is what a convention dropping a segment produces.
|
|
51
|
+
* A plain containment test answers true for every line under that shape and
|
|
52
|
+
* separates nothing.
|
|
53
|
+
*
|
|
54
|
+
* A reading aid rather than a filter. The line stating the change carries
|
|
55
|
+
* both values and so does a fixture halfway through a repair, and nothing
|
|
56
|
+
* here can tell those apart. Suppressing on it would hide the second case,
|
|
57
|
+
* which is the whole class this sweep exists to reach.
|
|
58
|
+
*/
|
|
59
|
+
readonly carriesReplacement: boolean
|
|
60
|
+
readonly preview: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SupersededOptions {
|
|
64
|
+
/** The value the convention used to produce, matched as a literal substring. */
|
|
65
|
+
readonly superseded: string
|
|
66
|
+
/** What it produces now, reported so a finding names what to write instead. */
|
|
67
|
+
readonly replacement: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The segment pair a templated citation is matched on, reported so a run states
|
|
72
|
+
* the net it cast rather than only what the net caught.
|
|
73
|
+
*/
|
|
74
|
+
export interface SupersededStems {
|
|
75
|
+
readonly superseded: string
|
|
76
|
+
readonly replacement: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type SupersededReport =
|
|
80
|
+
| {
|
|
81
|
+
readonly kind: 'measured'
|
|
82
|
+
readonly superseded: string
|
|
83
|
+
readonly replacement: string
|
|
84
|
+
/** Absent when the two values yield no bounded stem to match on. */
|
|
85
|
+
readonly stems: SupersededStems | undefined
|
|
86
|
+
/** Everything git listed, so the report states its own bound. */
|
|
87
|
+
readonly listed: number
|
|
88
|
+
/** Files opened, which is what the verdict actually covers. */
|
|
89
|
+
readonly files: number
|
|
90
|
+
/** Binary or unreadable, counted so a pass is not claimed over them. */
|
|
91
|
+
readonly skipped: number
|
|
92
|
+
readonly findings: readonly SupersededHit[]
|
|
93
|
+
readonly exempt: readonly SupersededHit[]
|
|
94
|
+
}
|
|
95
|
+
| { readonly kind: 'unreadable'; readonly reason: string }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The longest preview a hit carries. A generated or minified line is one line
|
|
99
|
+
* to git and a screen of noise to a reader, and the report prints one hit per
|
|
100
|
+
* line found.
|
|
101
|
+
*/
|
|
102
|
+
const PREVIEW_LIMIT = 200
|
|
103
|
+
|
|
104
|
+
/** The separator a family name is built from across this corpus. */
|
|
105
|
+
const SEGMENT_SEPARATOR = '-'
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A character that continues a name, so `canon-` inside `canon-cli` is read as
|
|
109
|
+
* one name rather than as the family prefix written bare.
|
|
110
|
+
*/
|
|
111
|
+
const NAME_CHARACTER = /[A-Za-z0-9]/
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A character that puts the stem mid-name when it sits directly before it, so
|
|
115
|
+
* a stem is only read where a name starts.
|
|
116
|
+
*
|
|
117
|
+
* The separator is here and not in `NAME_CHARACTER` because it decides one side
|
|
118
|
+
* only. `canon-check-toolkit-` is a temp-directory prefix and matched the stem
|
|
119
|
+
* `toolkit` on four fixtures before this, where `claude/skills/toolkit-*` is a
|
|
120
|
+
* path and has to keep matching.
|
|
121
|
+
*/
|
|
122
|
+
const SEGMENT_CONTINUATION = /[A-Za-z0-9_-]/
|
|
123
|
+
|
|
124
|
+
const HEADING = /^#{1,6}\s+\S/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A fence opening or closing a code block, tracked so a shell comment inside
|
|
128
|
+
* one is not read as the section a hit below it sits under. A `# Install` line
|
|
129
|
+
* in a bash block is the ordinary shape of that.
|
|
130
|
+
*/
|
|
131
|
+
const FENCE = /^\s*(?:```|~~~)/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The segment the two values differ on, carried with everything they share
|
|
135
|
+
* ahead of it.
|
|
136
|
+
*
|
|
137
|
+
* The shared prefix alone is what this exists against. `canon-cli` and
|
|
138
|
+
* `canon-feedback-file` share `canon`, so a stem cut there matches every sibling
|
|
139
|
+
* and reports the whole family on a rename of one folder. Including the
|
|
140
|
+
* differing segment bounds the net to what actually changed, which leaves
|
|
141
|
+
* `canon-cli` to `canon-shell` matching neither sibling and `toolkit-operator` to
|
|
142
|
+
* `canon-operator` matching the family prefix that did move.
|
|
143
|
+
*
|
|
144
|
+
* An empty replacement yields nothing. Retiring a value outright leaves no
|
|
145
|
+
* second value to diverge from, so every stem would run to the first segment
|
|
146
|
+
* and match the family the retirement never touched.
|
|
147
|
+
*/
|
|
148
|
+
export function deriveStems(
|
|
149
|
+
options: SupersededOptions,
|
|
150
|
+
): SupersededStems | undefined {
|
|
151
|
+
if (options.replacement === '') return undefined
|
|
152
|
+
|
|
153
|
+
const supersededSegments = options.superseded.split(SEGMENT_SEPARATOR)
|
|
154
|
+
const replacementSegments = options.replacement.split(SEGMENT_SEPARATOR)
|
|
155
|
+
|
|
156
|
+
let index = 0
|
|
157
|
+
while (
|
|
158
|
+
index < supersededSegments.length &&
|
|
159
|
+
index < replacementSegments.length &&
|
|
160
|
+
supersededSegments[index] === replacementSegments[index]
|
|
161
|
+
) {
|
|
162
|
+
index += 1
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const superseded = trimSeparators(
|
|
166
|
+
supersededSegments.slice(0, index + 1).join(SEGMENT_SEPARATOR),
|
|
167
|
+
)
|
|
168
|
+
const replacement = trimSeparators(
|
|
169
|
+
replacementSegments.slice(0, index + 1).join(SEGMENT_SEPARATOR),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if (superseded === '' || replacement === '') return undefined
|
|
173
|
+
if (superseded === replacement) return undefined
|
|
174
|
+
|
|
175
|
+
return { superseded, replacement }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function trimSeparators(value: string): string {
|
|
179
|
+
let end = value.length
|
|
180
|
+
while (end > 0 && value[end - 1] === SEGMENT_SEPARATOR) end -= 1
|
|
181
|
+
return value.slice(0, end)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Which templated form sits at `column`, or nothing when the stem there
|
|
186
|
+
* continues into an ordinary name.
|
|
187
|
+
*
|
|
188
|
+
* The character after `<stem>-` decides all three, so one scan reads every
|
|
189
|
+
* form. A name character means a sibling spelled out, which the literal
|
|
190
|
+
* comparison already answers for or correctly ignores.
|
|
191
|
+
*/
|
|
192
|
+
function classifyStem(
|
|
193
|
+
line: string,
|
|
194
|
+
column: number,
|
|
195
|
+
stem: string,
|
|
196
|
+
): SupersededMatch | undefined {
|
|
197
|
+
const before = column === 0 ? '' : line[column - 1]
|
|
198
|
+
if (before !== '' && SEGMENT_CONTINUATION.test(before)) return undefined
|
|
199
|
+
|
|
200
|
+
const after = line[column + stem.length + 1] ?? ''
|
|
201
|
+
if (after === '*') return 'glob'
|
|
202
|
+
if (after === '<') return 'placeholder'
|
|
203
|
+
if (after !== '' && NAME_CHARACTER.test(after)) return undefined
|
|
204
|
+
return 'prefix'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Whether the value replacing whatever matched appears on the line somewhere
|
|
209
|
+
* other than inside the matched occurrences, which is what makes the flag mean
|
|
210
|
+
* anything when one value contains the other.
|
|
211
|
+
*
|
|
212
|
+
* An empty replacement carries nothing, so it answers false rather than the
|
|
213
|
+
* true every line returns from a containment test against the empty string.
|
|
214
|
+
* Retiring a value outright is what passes one, and reporting every finding as
|
|
215
|
+
* carrying its replacement there says the opposite of what happened.
|
|
216
|
+
*
|
|
217
|
+
* A templated hit reads the stem pair instead, since the line repairing
|
|
218
|
+
* `toolkit-*` carries `canon-*` and never the full name either value spells.
|
|
219
|
+
*/
|
|
220
|
+
function carriesReplacement(
|
|
221
|
+
line: string,
|
|
222
|
+
options: SupersededOptions,
|
|
223
|
+
stems: SupersededStems | undefined,
|
|
224
|
+
match: SupersededMatch,
|
|
225
|
+
): boolean {
|
|
226
|
+
if (match === 'literal') {
|
|
227
|
+
if (options.replacement === '') return false
|
|
228
|
+
return line.split(options.superseded).join('').includes(options.replacement)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (stems === undefined) return false
|
|
232
|
+
const matched = `${stems.superseded}${SEGMENT_SEPARATOR}`
|
|
233
|
+
return line
|
|
234
|
+
.split(matched)
|
|
235
|
+
.join('')
|
|
236
|
+
.includes(`${stems.replacement}${SEGMENT_SEPARATOR}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Every column in one line carrying the superseded value or its family stem,
|
|
241
|
+
* ordered left to right.
|
|
242
|
+
*
|
|
243
|
+
* A stem occurrence sharing a column with a literal one is dropped. The stem is
|
|
244
|
+
* a prefix of the value it derives from whenever only the last segment moved,
|
|
245
|
+
* so the same text would otherwise report twice under two kinds.
|
|
246
|
+
*/
|
|
247
|
+
function matchLine(
|
|
248
|
+
line: string,
|
|
249
|
+
options: SupersededOptions,
|
|
250
|
+
stems: SupersededStems | undefined,
|
|
251
|
+
): { column: number; match: SupersededMatch }[] {
|
|
252
|
+
const matches: { column: number; match: SupersededMatch }[] = []
|
|
253
|
+
|
|
254
|
+
let column = line.indexOf(options.superseded)
|
|
255
|
+
while (column !== -1) {
|
|
256
|
+
matches.push({ column, match: 'literal' })
|
|
257
|
+
column = line.indexOf(options.superseded, column + 1)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (stems !== undefined) {
|
|
261
|
+
const pattern = `${stems.superseded}${SEGMENT_SEPARATOR}`
|
|
262
|
+
let at = line.indexOf(pattern)
|
|
263
|
+
while (at !== -1) {
|
|
264
|
+
const form = classifyStem(line, at, stems.superseded)
|
|
265
|
+
if (form !== undefined && !matches.some((hit) => hit.column === at)) {
|
|
266
|
+
matches.push({ column: at, match: form })
|
|
267
|
+
}
|
|
268
|
+
at = line.indexOf(pattern, at + 1)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return matches.sort((first, second) => first.column - second.column)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Every occurrence of `superseded` or of its family stem in one file's text,
|
|
277
|
+
* exemptions separated.
|
|
278
|
+
*/
|
|
279
|
+
export function sweepText(
|
|
280
|
+
file: string,
|
|
281
|
+
text: string,
|
|
282
|
+
options: SupersededOptions,
|
|
283
|
+
): { findings: SupersededHit[]; exempt: SupersededHit[] } {
|
|
284
|
+
const lines = text.split('\n')
|
|
285
|
+
const findings: SupersededHit[] = []
|
|
286
|
+
const exempt: SupersededHit[] = []
|
|
287
|
+
const stems = deriveStems(options)
|
|
288
|
+
const sectioned = file.endsWith('.md')
|
|
289
|
+
let heading: string | undefined
|
|
290
|
+
let fenced = false
|
|
291
|
+
|
|
292
|
+
for (const [index, line] of lines.entries()) {
|
|
293
|
+
if (sectioned) {
|
|
294
|
+
if (FENCE.test(line)) fenced = !fenced
|
|
295
|
+
else if (!fenced && HEADING.test(line)) heading = line.trim()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const matches = matchLine(line, options, stems)
|
|
299
|
+
if (matches.length === 0) continue
|
|
300
|
+
|
|
301
|
+
const muted = isMarked(lines, index, SUPERSEDED_MARKER)
|
|
302
|
+
const bucket = muted ? exempt : findings
|
|
303
|
+
const trimmed = line.trim()
|
|
304
|
+
const preview =
|
|
305
|
+
trimmed.length > PREVIEW_LIMIT
|
|
306
|
+
? `${trimmed.slice(0, PREVIEW_LIMIT)}…`
|
|
307
|
+
: trimmed
|
|
308
|
+
|
|
309
|
+
for (const { column, match } of matches) {
|
|
310
|
+
bucket.push({
|
|
311
|
+
file,
|
|
312
|
+
line: index + 1,
|
|
313
|
+
column: column + 1,
|
|
314
|
+
match,
|
|
315
|
+
heading,
|
|
316
|
+
carriesReplacement: carriesReplacement(line, options, stems, match),
|
|
317
|
+
preview,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { findings, exempt }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Where the tree still asserts a value the convention behind it no longer
|
|
327
|
+
* produces, keyed on the value rather than on the file stating the rule.
|
|
328
|
+
*
|
|
329
|
+
* The file-scoped map is what this exists against. A fixture asserting an old
|
|
330
|
+
* output names neither the rule nor the standard, so nothing reaches it from
|
|
331
|
+
* the change that superseded it, and the value it carries is the only key both
|
|
332
|
+
* sides share.
|
|
333
|
+
*
|
|
334
|
+
* This reports and never gates. A string appears for reasons unrelated to the
|
|
335
|
+
* convention, so the output is a reading a person settles, and the counts of
|
|
336
|
+
* what was listed, opened, and skipped are what keep it from reading as a
|
|
337
|
+
* verdict over the whole tree.
|
|
338
|
+
*
|
|
339
|
+
* Beside the literal comparison it matches the family stem the two values
|
|
340
|
+
* differ on, so a citation writing the family as a pattern enters the report.
|
|
341
|
+
* The trade is false positives, bounded at one across a sixteen-folder rename,
|
|
342
|
+
* which is why this reports rather than gates.
|
|
343
|
+
*
|
|
344
|
+
* What it cannot see is a prose reference that went stale without carrying the
|
|
345
|
+
* value, such as a declaration citing the wrong standard for the transform, and
|
|
346
|
+
* a family written in a form neither the value nor the three stem shapes reach.
|
|
347
|
+
* A value sweep closes most of this class and no part of either one.
|
|
348
|
+
*/
|
|
349
|
+
export async function readSuperseded(
|
|
350
|
+
root: string,
|
|
351
|
+
options: SupersededOptions,
|
|
352
|
+
): Promise<SupersededReport> {
|
|
353
|
+
if (options.superseded === '') {
|
|
354
|
+
return {
|
|
355
|
+
kind: 'unreadable',
|
|
356
|
+
reason:
|
|
357
|
+
'The superseded value is empty, which matches every line in the tree rather than a convention.',
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (options.superseded === options.replacement) {
|
|
362
|
+
return {
|
|
363
|
+
kind: 'unreadable',
|
|
364
|
+
reason: `The superseded value and its replacement are both ${options.superseded}, so no convention changed and there is nothing to sweep for.`,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const listed = await listRepositoryFiles(root)
|
|
369
|
+
if (listed === undefined) {
|
|
370
|
+
return {
|
|
371
|
+
kind: 'unreadable',
|
|
372
|
+
reason: `Git listed no corpus under ${root}. An empty list passes each of its zero files, so a tree git cannot read refuses rather than reporting clean.`,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const findings: SupersededHit[] = []
|
|
377
|
+
const exempt: SupersededHit[] = []
|
|
378
|
+
let files = 0
|
|
379
|
+
let skipped = 0
|
|
380
|
+
|
|
381
|
+
for (const path of listed) {
|
|
382
|
+
let text: string
|
|
383
|
+
try {
|
|
384
|
+
text = await readFile(join(root, path), 'utf8')
|
|
385
|
+
} catch {
|
|
386
|
+
// A listed path that will not open is a symlink leaving the tree or a
|
|
387
|
+
// file removed since git answered. Counted rather than reported, so the
|
|
388
|
+
// run still states that it measured less than it listed.
|
|
389
|
+
skipped += 1
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (isBinary(text)) {
|
|
394
|
+
skipped += 1
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
files += 1
|
|
399
|
+
const swept = sweepText(path, text, options)
|
|
400
|
+
findings.push(...swept.findings)
|
|
401
|
+
exempt.push(...swept.exempt)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
kind: 'measured',
|
|
406
|
+
superseded: options.superseded,
|
|
407
|
+
replacement: options.replacement,
|
|
408
|
+
stems: deriveStems(options),
|
|
409
|
+
listed: listed.length,
|
|
410
|
+
files,
|
|
411
|
+
skipped,
|
|
412
|
+
findings,
|
|
413
|
+
exempt,
|
|
414
|
+
}
|
|
415
|
+
}
|