@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,175 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { listRepositoryFiles } from '@/git-files'
|
|
3
|
+
import { applyRename, readSources } from '@/migrate/apply'
|
|
4
|
+
import { isToolkitOwned, planRename, type RenamePlan } from '@/migrate/plan'
|
|
5
|
+
import { logError, logInfo, logStep, logWarn, pipeOutput, plural } from '@/ui'
|
|
6
|
+
|
|
7
|
+
interface RenameOptions {
|
|
8
|
+
readonly json?: boolean
|
|
9
|
+
readonly write?: boolean
|
|
10
|
+
readonly root?: string
|
|
11
|
+
readonly scope?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SCOPES = ['self', 'target'] as const
|
|
15
|
+
|
|
16
|
+
type Scope = (typeof SCOPES)[number]
|
|
17
|
+
|
|
18
|
+
function isScope(value: string): value is Scope {
|
|
19
|
+
return (SCOPES as readonly string[]).includes(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reports rather than writes without `--write`, matching `canon records
|
|
24
|
+
* migrate`. A rename touching this many files has no undo short of the branch
|
|
25
|
+
* it ran on, so the safe outcome sits on the default path.
|
|
26
|
+
*/
|
|
27
|
+
async function runRename(opts: RenameOptions): Promise<number> {
|
|
28
|
+
const root = opts.root ?? process.cwd()
|
|
29
|
+
const scope = opts.scope ?? 'self'
|
|
30
|
+
|
|
31
|
+
if (!isScope(scope)) {
|
|
32
|
+
logError(`Unknown scope ${scope}. Use one of ${SCOPES.join(', ')}.`)
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const files = await listRepositoryFiles(root)
|
|
37
|
+
if (files === undefined) {
|
|
38
|
+
logError(`Could not list files under ${root}. Is it a git repository?`)
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const scoped = scope === 'target' ? files.filter(isToolkitOwned) : files
|
|
43
|
+
const sources = await readSources(root, scoped)
|
|
44
|
+
const plan = planRename(sources)
|
|
45
|
+
|
|
46
|
+
// A target scope reports the citations it did not rewrite, since prose the
|
|
47
|
+
// project wrote is theirs to change and a sweep editing it underneath them
|
|
48
|
+
// is the failure this scope exists to avoid.
|
|
49
|
+
const citations =
|
|
50
|
+
scope === 'target'
|
|
51
|
+
? planRename(
|
|
52
|
+
await readSources(
|
|
53
|
+
root,
|
|
54
|
+
files.filter((f) => !isToolkitOwned(f)),
|
|
55
|
+
),
|
|
56
|
+
).entries.length
|
|
57
|
+
: 0
|
|
58
|
+
|
|
59
|
+
if (opts.json) {
|
|
60
|
+
pipeOutput(
|
|
61
|
+
JSON.stringify(toRecord(plan, scope, citations, opts.write), null, 2),
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
report(plan, scope, citations)
|
|
66
|
+
|
|
67
|
+
if (plan.entries.length === 0) return 0
|
|
68
|
+
if (!opts.write) {
|
|
69
|
+
logWarn('Nothing was written. Pass --write to apply this plan.')
|
|
70
|
+
return 2
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const applied = await applyRename(root, plan)
|
|
74
|
+
logStep(
|
|
75
|
+
`Rewrote ${plural(applied.written, 'file')} and moved ${plural(applied.moved, 'path')}.`,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (applied.failed.length > 0) {
|
|
79
|
+
logError(`Could not move ${plural(applied.failed.length, 'path')}.`)
|
|
80
|
+
for (const path of applied.failed) logError(` ${path}`)
|
|
81
|
+
return 1
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function report(plan: RenamePlan, scope: Scope, citations: number): void {
|
|
88
|
+
logInfo(`Scope ${scope}.`)
|
|
89
|
+
logInfo(
|
|
90
|
+
`${plural(plan.entries.length, 'file')} to change, ${plural(plan.renamed, 'occurrence')} to rewrite.`,
|
|
91
|
+
)
|
|
92
|
+
logInfo(`${plural(plan.moves, 'path')} to move.`)
|
|
93
|
+
logInfo(
|
|
94
|
+
`${plural(plan.protectedCount, 'occurrence')} protected and left alone.`,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (plan.excluded.length > 0) {
|
|
98
|
+
logInfo(`Excluded: ${plan.excluded.join(', ')}.`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (citations > 0) {
|
|
102
|
+
logWarn(
|
|
103
|
+
`${plural(citations, 'file')} outside toolkit-owned folders still cite the old name. They are yours to change.`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function toRecord(
|
|
109
|
+
plan: RenamePlan,
|
|
110
|
+
scope: Scope,
|
|
111
|
+
citations: number,
|
|
112
|
+
wrote: boolean | undefined,
|
|
113
|
+
): unknown {
|
|
114
|
+
return {
|
|
115
|
+
scope,
|
|
116
|
+
wrote: wrote === true,
|
|
117
|
+
files: plan.entries.length,
|
|
118
|
+
renamed: plan.renamed,
|
|
119
|
+
moves: plan.moves,
|
|
120
|
+
protected: plan.protectedCount,
|
|
121
|
+
excluded: plan.excluded,
|
|
122
|
+
citations,
|
|
123
|
+
paths: plan.entries.map((entry) => ({
|
|
124
|
+
path: entry.path,
|
|
125
|
+
...(entry.movesTo === undefined ? {} : { movesTo: entry.movesTo }),
|
|
126
|
+
renamed: entry.renamed,
|
|
127
|
+
})),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function register(program: Command): void {
|
|
132
|
+
const migrate = program
|
|
133
|
+
.command('migrate')
|
|
134
|
+
.description('Move a project off the retired aitk name')
|
|
135
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
136
|
+
|
|
137
|
+
migrate
|
|
138
|
+
.command('rename')
|
|
139
|
+
.description('Rewrite every unprotected aitk token to canon')
|
|
140
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
141
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
142
|
+
.option('--write', 'Apply the plan rather than reporting it')
|
|
143
|
+
.option(
|
|
144
|
+
'--root <path>',
|
|
145
|
+
'Project root, defaulting to the working directory',
|
|
146
|
+
)
|
|
147
|
+
.option('--scope <scope>', `What to rewrite: ${SCOPES.join(', ')}`, 'self')
|
|
148
|
+
.addHelpText(
|
|
149
|
+
'after',
|
|
150
|
+
[
|
|
151
|
+
'',
|
|
152
|
+
'Scopes:',
|
|
153
|
+
' self every tracked file, for the toolkit repository itself',
|
|
154
|
+
' target toolkit-owned folders only, reporting the rest as citations',
|
|
155
|
+
'',
|
|
156
|
+
'Exit codes:',
|
|
157
|
+
' 0 nothing to rewrite, or --write applied the whole plan',
|
|
158
|
+
' 1 refused, or a move failed',
|
|
159
|
+
' 2 a plan exists and --write was not passed',
|
|
160
|
+
'',
|
|
161
|
+
'The changelog is never rewritten. Its entries record what shipped',
|
|
162
|
+
'under the old name, and GitHub redirects the links they carry.',
|
|
163
|
+
'aitk-sandbox is a separate repository and is left alone.',
|
|
164
|
+
'',
|
|
165
|
+
'Examples:',
|
|
166
|
+
' canon migrate rename',
|
|
167
|
+
' canon migrate rename --write',
|
|
168
|
+
' canon migrate rename --scope target --json',
|
|
169
|
+
'',
|
|
170
|
+
].join('\n'),
|
|
171
|
+
)
|
|
172
|
+
.action(async (opts: RenameOptions) => {
|
|
173
|
+
process.exitCode = await runRename(opts)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { execScript } from '@/exec'
|
|
3
|
+
import { intro } from '@/ui'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Registers the verbs a domain has not migrated yet, each forwarding to
|
|
7
|
+
* `scripts/<domain>/<verb>.sh`. Naming them instead of catching everything on
|
|
8
|
+
* the parent gives each verb its own `--help` and lets a migrated sibling sit
|
|
9
|
+
* alongside them.
|
|
10
|
+
*/
|
|
11
|
+
export function registerPassThroughVerbs(
|
|
12
|
+
parent: Command,
|
|
13
|
+
domain: string,
|
|
14
|
+
verbs: readonly string[],
|
|
15
|
+
): void {
|
|
16
|
+
for (const verb of verbs) {
|
|
17
|
+
parent
|
|
18
|
+
.command(verb)
|
|
19
|
+
.description(`Run the ${domain} ${verb} command`)
|
|
20
|
+
.allowUnknownOption()
|
|
21
|
+
.allowExcessArguments(true)
|
|
22
|
+
.passThroughOptions()
|
|
23
|
+
.helpOption(false)
|
|
24
|
+
.action(async (_opts: unknown, cmd: Command) => {
|
|
25
|
+
if (!isHelpRequest(cmd.args)) intro(`canon ${domain}`)
|
|
26
|
+
await execScript(`${domain}/${verb}.sh`, cmd.args)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Commander resolves `--help` before an action runs, so a pass-through verb
|
|
33
|
+
* that keeps the built-in help option prints a one-line stub instead of the
|
|
34
|
+
* script's usage. Disabling it lets the flag reach the script, which owns the
|
|
35
|
+
* real flag surface, and this guard keeps the frame from opening around it.
|
|
36
|
+
*/
|
|
37
|
+
function isHelpRequest(args: readonly string[]): boolean {
|
|
38
|
+
return args.includes('-h') || args.includes('--help')
|
|
39
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import type { Command } from 'commander'
|
|
5
|
+
import { execa } from 'execa'
|
|
6
|
+
import { gitEnv } from '@/git-env'
|
|
7
|
+
import {
|
|
8
|
+
listChangedFiles,
|
|
9
|
+
listRepositoryFiles,
|
|
10
|
+
resolveBaseRef,
|
|
11
|
+
} from '@/git-files'
|
|
12
|
+
import {
|
|
13
|
+
type Bijection,
|
|
14
|
+
type BijectionRefusal,
|
|
15
|
+
compareKeyChanges,
|
|
16
|
+
treeRoots,
|
|
17
|
+
} from '@/pr/bijection'
|
|
18
|
+
import { KEY_CHANGES } from '@/pr/paths'
|
|
19
|
+
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
|
|
20
|
+
|
|
21
|
+
const GH_TIMEOUT_MS = 30_000
|
|
22
|
+
|
|
23
|
+
/** How many unnamed files the frame prints before it names a count instead. */
|
|
24
|
+
const UNNAMED_PRINT_LIMIT = 10
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Where `gh pr view --json files` stops.
|
|
28
|
+
*
|
|
29
|
+
* It pages the underlying query once and returns at most this many rows with
|
|
30
|
+
* nothing on the record saying so, which was measured against `#1250`: the
|
|
31
|
+
* pull request carries 101 files and the view reports 100. A set silently one
|
|
32
|
+
* short is the worst input this comparison can take, since the missing file is
|
|
33
|
+
* exactly what a claim would then be accused of inventing.
|
|
34
|
+
*/
|
|
35
|
+
const GH_VIEW_FILE_CAP = 100
|
|
36
|
+
|
|
37
|
+
interface KeyChangesOptions {
|
|
38
|
+
readonly body?: string
|
|
39
|
+
readonly base?: string
|
|
40
|
+
readonly root?: string
|
|
41
|
+
readonly json?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Why the read produced no comparison, ahead of the ones the compare owns. */
|
|
45
|
+
type SourceRefusal =
|
|
46
|
+
| 'gh-missing'
|
|
47
|
+
| 'gh-failed'
|
|
48
|
+
| 'gh-truncated'
|
|
49
|
+
| 'unreadable-body'
|
|
50
|
+
| 'unreadable-tree'
|
|
51
|
+
| 'no-base'
|
|
52
|
+
| 'bad-base'
|
|
53
|
+
| 'unreadable-changes'
|
|
54
|
+
|
|
55
|
+
type Refusal = SourceRefusal | BijectionRefusal
|
|
56
|
+
|
|
57
|
+
/** What a reader does about each way this produced no reading. */
|
|
58
|
+
const REFUSALS: Record<Refusal, string> = {
|
|
59
|
+
'gh-missing':
|
|
60
|
+
'gh is not on the path, so no pull request body could be read. Pass --body <path> to read one off disk instead.',
|
|
61
|
+
'gh-failed':
|
|
62
|
+
'gh could not answer for this branch. Name the pull request number, or pass --body <path>.',
|
|
63
|
+
'gh-truncated': `gh returned the first ${GH_VIEW_FILE_CAP} changed files and the paginated read that would complete the set failed, so a claim could be accused of naming a file this read never saw.`,
|
|
64
|
+
'unreadable-body': 'The file named by --body could not be read.',
|
|
65
|
+
'unreadable-tree':
|
|
66
|
+
'git could not list this repository, so no path could be judged whole rather than partial.',
|
|
67
|
+
'no-base': 'No base resolves against the trunk. Fetch origin or pass --base.',
|
|
68
|
+
'bad-base':
|
|
69
|
+
'The ref passed to --base resolves to no commit here. Pass one this tree carries.',
|
|
70
|
+
'unreadable-changes':
|
|
71
|
+
'git could not list what this branch changed, so the set is unknown.',
|
|
72
|
+
'no-section': `This body carries no ## ${KEY_CHANGES} section, so it claims nothing to compare.`,
|
|
73
|
+
'no-claims': `The ## ${KEY_CHANGES} section carried no path this reader could resolve. That is the extractor failing over prose rather than the body being wrong, so nothing is raised.`,
|
|
74
|
+
'no-changes':
|
|
75
|
+
'The pull request changed no files, so there is nothing for a claim to answer.',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function register(program: Command): void {
|
|
79
|
+
const pr = program
|
|
80
|
+
.command('pr')
|
|
81
|
+
.description('Read a pull request body against the change it describes')
|
|
82
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
83
|
+
|
|
84
|
+
pr.command('key-changes')
|
|
85
|
+
.description(
|
|
86
|
+
`Compare the files a body's ## ${KEY_CHANGES} names against its own diff`,
|
|
87
|
+
)
|
|
88
|
+
.argument('[number]', 'Pull request to read, defaulting to this branch')
|
|
89
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
90
|
+
.option(
|
|
91
|
+
'--body <path>',
|
|
92
|
+
'Read the body from a file rather than the API, ignoring any number',
|
|
93
|
+
)
|
|
94
|
+
.option(
|
|
95
|
+
'--base <ref>',
|
|
96
|
+
'Far side of the range when --body supplies the body',
|
|
97
|
+
)
|
|
98
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
99
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
100
|
+
.addHelpText(
|
|
101
|
+
'after',
|
|
102
|
+
[
|
|
103
|
+
'',
|
|
104
|
+
'This repository squash-merges, so a pull request body becomes the commit',
|
|
105
|
+
'message and the record on the trunk once the branch is gone. A bullet',
|
|
106
|
+
'claiming a change nobody made corrupts that record, and a changed file no',
|
|
107
|
+
'bullet names leaves it incomplete.',
|
|
108
|
+
'',
|
|
109
|
+
'The two directions carry different weight:',
|
|
110
|
+
' unmet a whole path the body claims and the diff does not carry,',
|
|
111
|
+
' which is the graded direction',
|
|
112
|
+
' unnamed a changed file no bullet reached, reported without a grade,',
|
|
113
|
+
' since a lockfile or a generated asset earns no bullet',
|
|
114
|
+
' unresolved a path written partially, which can credit a changed file',
|
|
115
|
+
' and never accuse one',
|
|
116
|
+
'',
|
|
117
|
+
`Only ## ${KEY_CHANGES} is read. ## Technical Context legitimately names`,
|
|
118
|
+
'files a branch never touched, so widening the read manufactures findings.',
|
|
119
|
+
'',
|
|
120
|
+
'One class survives the reader: a bullet citing where something is defined',
|
|
121
|
+
'while claiming an edit elsewhere puts a real path in the claim region and',
|
|
122
|
+
'points the change at a locative the path does not name. Read the bullet on',
|
|
123
|
+
"the record's preview before filing an unmet path as a stale claim.",
|
|
124
|
+
'',
|
|
125
|
+
'Exit codes:',
|
|
126
|
+
' 0 every claimed path is in the diff',
|
|
127
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
128
|
+
' 2 at least one claimed path is absent from the diff',
|
|
129
|
+
'',
|
|
130
|
+
'Examples:',
|
|
131
|
+
' canon pr key-changes',
|
|
132
|
+
' canon pr key-changes 1265 --json',
|
|
133
|
+
' canon pr key-changes --body .claude/.tmp/body.md --base origin/main',
|
|
134
|
+
'',
|
|
135
|
+
].join('\n'),
|
|
136
|
+
)
|
|
137
|
+
.action(async (number: string | undefined, opts: KeyChangesOptions) => {
|
|
138
|
+
process.exitCode = await runKeyChanges(number, opts)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface PullRequestRead {
|
|
143
|
+
readonly body: string
|
|
144
|
+
readonly changed: readonly string[]
|
|
145
|
+
readonly head: string | undefined
|
|
146
|
+
readonly number: number | undefined
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type SourceRead =
|
|
150
|
+
| { readonly kind: 'read'; readonly source: PullRequestRead }
|
|
151
|
+
| { readonly kind: 'refused'; readonly reason: SourceRefusal }
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Reads the body and the changed set from the pull request the caller named,
|
|
155
|
+
* or from the one open on this branch.
|
|
156
|
+
*
|
|
157
|
+
* One call wherever the file list fits inside it. The body and the file list
|
|
158
|
+
* have to describe the same head, and reading them separately leaves a window
|
|
159
|
+
* where a push between them compares a body against another commit's files. A
|
|
160
|
+
* pull request at the view's cap takes the second read anyway, because a set
|
|
161
|
+
* short by an unknown number is worse than a set read a moment later.
|
|
162
|
+
*/
|
|
163
|
+
async function readFromApi(
|
|
164
|
+
cwd: string,
|
|
165
|
+
number: string | undefined,
|
|
166
|
+
): Promise<SourceRead> {
|
|
167
|
+
if (Bun.which('gh') === null) {
|
|
168
|
+
return { kind: 'refused', reason: 'gh-missing' }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const args = ['pr', 'view']
|
|
172
|
+
if (number !== undefined) args.push(number)
|
|
173
|
+
args.push('--json', 'body,files,headRefOid,number')
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// See src/worktrees/reclaim.ts for why gh needs the stripped environment:
|
|
177
|
+
// it resolves its repository through the same variables git does and they
|
|
178
|
+
// beat `cwd`, so a run from inside a hook would answer for another
|
|
179
|
+
// repository and compare this branch's claims against its files.
|
|
180
|
+
const result = await execa('gh', args, {
|
|
181
|
+
cwd,
|
|
182
|
+
timeout: GH_TIMEOUT_MS,
|
|
183
|
+
env: gitEnv(),
|
|
184
|
+
extendEnv: false,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const row = JSON.parse(result.stdout) as {
|
|
188
|
+
body?: string
|
|
189
|
+
files?: readonly { path: string }[]
|
|
190
|
+
headRefOid?: string
|
|
191
|
+
number?: number
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const viewed = (row.files ?? []).map((file) => file.path)
|
|
195
|
+
const changed =
|
|
196
|
+
viewed.length < GH_VIEW_FILE_CAP || row.number === undefined
|
|
197
|
+
? viewed
|
|
198
|
+
: await listFilesByPage(cwd, row.number)
|
|
199
|
+
|
|
200
|
+
if (changed === undefined) {
|
|
201
|
+
return { kind: 'refused', reason: 'gh-truncated' }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
kind: 'read',
|
|
206
|
+
source: {
|
|
207
|
+
body: row.body ?? '',
|
|
208
|
+
changed: [...changed].sort(),
|
|
209
|
+
head: row.headRefOid,
|
|
210
|
+
number: row.number,
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
return { kind: 'refused', reason: 'gh-failed' }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Every file a pull request changed, read through the paginated endpoint.
|
|
220
|
+
*
|
|
221
|
+
* Only reached when the view came back at the cap, since it costs a request per
|
|
222
|
+
* page and nearly every pull request here fits in one view. Returns undefined
|
|
223
|
+
* when the follow-up fails, which refuses rather than falling back to the
|
|
224
|
+
* capped set: a comparison run against a set known to be short would accuse a
|
|
225
|
+
* correct bullet of naming a file nobody changed.
|
|
226
|
+
*/
|
|
227
|
+
async function listFilesByPage(
|
|
228
|
+
cwd: string,
|
|
229
|
+
number: number,
|
|
230
|
+
): Promise<string[] | undefined> {
|
|
231
|
+
try {
|
|
232
|
+
// See src/worktrees/reclaim.ts for why gh needs the stripped environment.
|
|
233
|
+
const result = await execa(
|
|
234
|
+
'gh',
|
|
235
|
+
[
|
|
236
|
+
'api',
|
|
237
|
+
'--paginate',
|
|
238
|
+
`repos/{owner}/{repo}/pulls/${number}/files`,
|
|
239
|
+
'--jq',
|
|
240
|
+
'.[].filename',
|
|
241
|
+
],
|
|
242
|
+
{ cwd, timeout: GH_TIMEOUT_MS, env: gitEnv(), extendEnv: false },
|
|
243
|
+
)
|
|
244
|
+
return result.stdout.split('\n').filter(Boolean)
|
|
245
|
+
} catch {
|
|
246
|
+
return undefined
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Reads the body off disk and the changed set from git, which is the shape a
|
|
252
|
+
* fixture and a body still being drafted both need.
|
|
253
|
+
*/
|
|
254
|
+
async function readFromFile(
|
|
255
|
+
root: string,
|
|
256
|
+
path: string,
|
|
257
|
+
base: string | undefined,
|
|
258
|
+
): Promise<SourceRead> {
|
|
259
|
+
let body: string
|
|
260
|
+
try {
|
|
261
|
+
body = await readFile(resolve(root, path), 'utf8')
|
|
262
|
+
} catch {
|
|
263
|
+
return { kind: 'refused', reason: 'unreadable-body' }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const resolved = await resolveBaseRef(root, base)
|
|
267
|
+
if (resolved === undefined) {
|
|
268
|
+
return {
|
|
269
|
+
kind: 'refused',
|
|
270
|
+
reason: base === undefined ? 'no-base' : 'bad-base',
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const changed = await listChangedFiles(root, resolved)
|
|
275
|
+
if (changed === undefined) {
|
|
276
|
+
return { kind: 'refused', reason: 'unreadable-changes' }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const head = await $`git -C ${root} rev-parse HEAD`
|
|
280
|
+
.env(gitEnv())
|
|
281
|
+
.quiet()
|
|
282
|
+
.nothrow()
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
kind: 'read',
|
|
286
|
+
source: {
|
|
287
|
+
body,
|
|
288
|
+
changed,
|
|
289
|
+
head: head.exitCode === 0 ? head.text().trim() : undefined,
|
|
290
|
+
number: undefined,
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function runKeyChanges(
|
|
296
|
+
number: string | undefined,
|
|
297
|
+
opts: KeyChangesOptions,
|
|
298
|
+
): Promise<number> {
|
|
299
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
300
|
+
const emitJson = opts.json ?? false
|
|
301
|
+
|
|
302
|
+
intro('canon pr key-changes')
|
|
303
|
+
|
|
304
|
+
const source =
|
|
305
|
+
opts.body === undefined
|
|
306
|
+
? await readFromApi(root, number)
|
|
307
|
+
: await readFromFile(root, opts.body, opts.base)
|
|
308
|
+
|
|
309
|
+
if (source.kind === 'refused') return refuse(source.reason, emitJson, root)
|
|
310
|
+
|
|
311
|
+
const tracked = await listRepositoryFiles(root)
|
|
312
|
+
if (tracked === undefined) return refuse('unreadable-tree', emitJson, root)
|
|
313
|
+
|
|
314
|
+
const report: Bijection = compareKeyChanges({
|
|
315
|
+
body: source.source.body,
|
|
316
|
+
changed: source.source.changed,
|
|
317
|
+
roots: treeRoots(tracked, source.source.changed),
|
|
318
|
+
...(source.source.head !== undefined && { head: source.source.head }),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
if (report.kind === 'refused') return refuse(report.reason, emitJson, root)
|
|
322
|
+
|
|
323
|
+
logStep('Scope')
|
|
324
|
+
logInfo(
|
|
325
|
+
`${plural(report.claims.length, 'claim')} against ${plural(report.changed.length, 'changed file')}${
|
|
326
|
+
report.head === undefined ? '' : ` at ${report.head.slice(0, 8)}`
|
|
327
|
+
}`,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
logStep(report.unmet.length === 0 ? 'Claimed' : 'Unmet')
|
|
331
|
+
if (report.unmet.length === 0) {
|
|
332
|
+
logInfo('every claimed path is in the diff')
|
|
333
|
+
} else {
|
|
334
|
+
logWarn(
|
|
335
|
+
`${plural(report.unmet.length, 'claimed path')} the diff does not carry. Correct the bullet, or make the change it describes.`,
|
|
336
|
+
)
|
|
337
|
+
for (const claim of report.unmet) {
|
|
338
|
+
logWarn(`${claim.path} — ${claim.preview}`)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Named rather than counted into the verdict. A generated asset, a lockfile,
|
|
343
|
+
// and a regenerated index all change without earning a bullet, so grading
|
|
344
|
+
// this direction would fire on nearly every branch.
|
|
345
|
+
logStep('Unnamed')
|
|
346
|
+
if (report.unnamed.length === 0) {
|
|
347
|
+
logInfo('every changed file is reached by a bullet')
|
|
348
|
+
} else {
|
|
349
|
+
logInfo(
|
|
350
|
+
`${plural(report.unnamed.length, 'changed file')} no bullet reached. Add one where the change is worth a reader knowing about.`,
|
|
351
|
+
)
|
|
352
|
+
// Capped in the frame and whole in the record. A rename branch measured
|
|
353
|
+
// here left 71 of its 100 files unnamed, correctly, and printing all of
|
|
354
|
+
// them buries the graded direction above under a list nobody reads.
|
|
355
|
+
for (const path of report.unnamed.slice(0, UNNAMED_PRINT_LIMIT)) {
|
|
356
|
+
logInfo(path)
|
|
357
|
+
}
|
|
358
|
+
if (report.unnamed.length > UNNAMED_PRINT_LIMIT) {
|
|
359
|
+
logInfo(
|
|
360
|
+
`…and ${report.unnamed.length - UNNAMED_PRINT_LIMIT} more, whole in the --json record.`,
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (report.unresolved.length > 0) {
|
|
366
|
+
logStep('Unresolved')
|
|
367
|
+
logInfo(
|
|
368
|
+
`${plural(report.unresolved.length, 'path')} written partially, so neither direction judged it.`,
|
|
369
|
+
)
|
|
370
|
+
for (const claim of report.unresolved) logInfo(claim.path)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
outro()
|
|
374
|
+
|
|
375
|
+
if (emitJson) {
|
|
376
|
+
process.stdout.write(
|
|
377
|
+
`${JSON.stringify({
|
|
378
|
+
root,
|
|
379
|
+
...(source.source.number !== undefined && {
|
|
380
|
+
number: source.source.number,
|
|
381
|
+
}),
|
|
382
|
+
...(report.head !== undefined && { head: report.head }),
|
|
383
|
+
changed: report.changed,
|
|
384
|
+
claims: report.claims,
|
|
385
|
+
unmet: report.unmet,
|
|
386
|
+
unnamed: report.unnamed,
|
|
387
|
+
unresolved: report.unresolved,
|
|
388
|
+
})}\n`,
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return report.unmet.length === 0 ? 0 : 2
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Frames a refusal on stderr in both modes and puts the record on stdout alone,
|
|
397
|
+
* so an operator reading the terminal sees the reason rather than a command
|
|
398
|
+
* that appeared to do nothing.
|
|
399
|
+
*/
|
|
400
|
+
function refuse(reason: Refusal, emitJson: boolean, root: string): number {
|
|
401
|
+
logStep('Refused')
|
|
402
|
+
logWarn(REFUSALS[reason])
|
|
403
|
+
outro()
|
|
404
|
+
|
|
405
|
+
if (emitJson) {
|
|
406
|
+
process.stdout.write(
|
|
407
|
+
`${JSON.stringify({ root, reason, message: REFUSALS[reason] })}\n`,
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
return 1
|
|
411
|
+
}
|