@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,256 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { INSTALL_BROWSER, isEngineMissing } from '@/browser/engine'
|
|
4
|
+
import {
|
|
5
|
+
CONFIG_REL,
|
|
6
|
+
type ConfigRefusal,
|
|
7
|
+
readInventoryConfig,
|
|
8
|
+
} from '@/inventory/config'
|
|
9
|
+
import { abbreviate, groupByTreatment } from '@/inventory/group'
|
|
10
|
+
import { findSubject, SUBJECTS } from '@/inventory/subjects'
|
|
11
|
+
import type { WalkRefusal } from '@/inventory/walk'
|
|
12
|
+
import { intro, logError, logInfo, logStep, logWarn, outro, plural } from '@/ui'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Holds wiring only. Every browser reference sits behind `loadWalker`, because
|
|
16
|
+
* `src/cli.ts` imports this module at startup and resolving the engine there
|
|
17
|
+
* would put a browser launch in front of every other command.
|
|
18
|
+
*/
|
|
19
|
+
type Walker = typeof import('@/inventory/walk')
|
|
20
|
+
|
|
21
|
+
interface RunOptions {
|
|
22
|
+
readonly root?: string
|
|
23
|
+
readonly baseUrl?: string
|
|
24
|
+
readonly json?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a reader does about each way the config produced no walk. */
|
|
28
|
+
const CONFIG_REFUSALS: Record<ConfigRefusal, string> = {
|
|
29
|
+
'no-config': `No ${CONFIG_REL} here, so no routes and no element query are declared.`,
|
|
30
|
+
'unreadable-config': `${CONFIG_REL} is not valid TOML, so no route could be read.`,
|
|
31
|
+
'no-routes': `${CONFIG_REL} carries no route under routes, so there is nothing to walk.`,
|
|
32
|
+
'no-base-url': `${CONFIG_REL} carries no base-url, so no route resolves to an address.`,
|
|
33
|
+
'no-subjects': `${CONFIG_REL} declares no subject, and the element query comes from this project rather than from the toolkit.`,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const WALK_REFUSALS: Record<WalkRefusal, string> = {
|
|
37
|
+
'browser-missing': 'The browser binary is not installed in this project.',
|
|
38
|
+
'server-unreachable':
|
|
39
|
+
'Nothing answered at the base URL, so no route was read.',
|
|
40
|
+
'walk-failed': 'The walk failed against a running server.',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function register(program: Command): void {
|
|
44
|
+
const inventory = program
|
|
45
|
+
.command('inventory')
|
|
46
|
+
.description('Report one computed property across every route of a project')
|
|
47
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
48
|
+
|
|
49
|
+
inventory
|
|
50
|
+
.command('run', { isDefault: true })
|
|
51
|
+
.description(
|
|
52
|
+
'Walk every route and group its elements by the answer each gives',
|
|
53
|
+
)
|
|
54
|
+
.argument('[subject]', 'Treatment to read, defaulting to focus', 'focus')
|
|
55
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
56
|
+
.option('--root <path>', 'Project to read, defaulting to the cwd')
|
|
57
|
+
.option('--base-url <url>', 'Address to walk, overriding the config')
|
|
58
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
59
|
+
.addHelpText(
|
|
60
|
+
'after',
|
|
61
|
+
[
|
|
62
|
+
'',
|
|
63
|
+
`Reads ${CONFIG_REL} for the base URL, the routes, and the element query`,
|
|
64
|
+
'each subject runs over, so what gets walked comes from the project',
|
|
65
|
+
'rather than from the toolkit.',
|
|
66
|
+
'',
|
|
67
|
+
'It reports a listing and never a verdict. The value is seeing how many',
|
|
68
|
+
'different answers one site gives, and a gate collapses that to one bit.',
|
|
69
|
+
'',
|
|
70
|
+
'Needs a running server and a browser binary. Install the browser with:',
|
|
71
|
+
` ${INSTALL_BROWSER}`,
|
|
72
|
+
'',
|
|
73
|
+
'Subjects:',
|
|
74
|
+
...SUBJECTS.map((subject) => ` ${subject.name} ${subject.summary}`),
|
|
75
|
+
'',
|
|
76
|
+
'Exit codes:',
|
|
77
|
+
' 0 the walk read at least one element and reported its rows',
|
|
78
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
79
|
+
'',
|
|
80
|
+
'Examples:',
|
|
81
|
+
' canon inventory focus',
|
|
82
|
+
' canon inventory focus --json',
|
|
83
|
+
' canon inventory focus --base-url http://localhost:3000',
|
|
84
|
+
'',
|
|
85
|
+
].join('\n'),
|
|
86
|
+
)
|
|
87
|
+
.action(async (subject: string, opts: RunOptions) => {
|
|
88
|
+
process.exitCode = await runInventory(subject, opts)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runInventory(
|
|
93
|
+
subjectName: string,
|
|
94
|
+
opts: RunOptions,
|
|
95
|
+
): Promise<number> {
|
|
96
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
97
|
+
const emitJson = opts.json ?? false
|
|
98
|
+
|
|
99
|
+
intro(`canon inventory ${subjectName}`)
|
|
100
|
+
|
|
101
|
+
const subject = findSubject(subjectName)
|
|
102
|
+
if (!subject) {
|
|
103
|
+
const known = SUBJECTS.map((entry) => entry.name).join(', ')
|
|
104
|
+
return refuse(
|
|
105
|
+
emitJson,
|
|
106
|
+
root,
|
|
107
|
+
'unknown-subject',
|
|
108
|
+
`No reader named ${subjectName}. This build ships: ${known}.`,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const read = readInventoryConfig(root)
|
|
113
|
+
if (read.kind === 'refused') {
|
|
114
|
+
return refuse(emitJson, root, read.reason, CONFIG_REFUSALS[read.reason])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const declared = read.config.subjects.find(
|
|
118
|
+
(entry) => entry.name === subjectName,
|
|
119
|
+
)
|
|
120
|
+
if (!declared) {
|
|
121
|
+
const named = read.config.subjects.map((entry) => entry.name).join(', ')
|
|
122
|
+
return refuse(
|
|
123
|
+
emitJson,
|
|
124
|
+
root,
|
|
125
|
+
'undeclared-subject',
|
|
126
|
+
`${CONFIG_REL} declares no query for ${subjectName}. It declares: ${named}.`,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const baseUrl = opts.baseUrl ?? read.config.baseUrl
|
|
131
|
+
|
|
132
|
+
logStep('Scope')
|
|
133
|
+
logInfo(
|
|
134
|
+
`${plural(read.config.routes.length, 'route')} from ${baseUrl}, matching ${declared.query}`,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const walker = await loadWalker()
|
|
138
|
+
if (!walker) {
|
|
139
|
+
logStep('Browser')
|
|
140
|
+
logError('the browser engine is not installed in this project')
|
|
141
|
+
logWarn(`Install it with: ${INSTALL_BROWSER}`)
|
|
142
|
+
outro()
|
|
143
|
+
emit(emitJson, {
|
|
144
|
+
root,
|
|
145
|
+
subject: subjectName,
|
|
146
|
+
reason: 'engine-missing',
|
|
147
|
+
install: INSTALL_BROWSER,
|
|
148
|
+
})
|
|
149
|
+
return 1
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = await walker.walk({
|
|
153
|
+
baseUrl,
|
|
154
|
+
routes: read.config.routes,
|
|
155
|
+
subject,
|
|
156
|
+
query: declared.query,
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
if (result.status === 'failed') {
|
|
160
|
+
logStep('Refused')
|
|
161
|
+
logWarn(WALK_REFUSALS[result.reason])
|
|
162
|
+
if (result.reason === 'server-unreachable') {
|
|
163
|
+
logWarn(`Start the project at ${baseUrl}, then run this again.`)
|
|
164
|
+
}
|
|
165
|
+
if (result.reason === 'browser-missing') {
|
|
166
|
+
logWarn(`Install the browser binary with: ${INSTALL_BROWSER}`)
|
|
167
|
+
}
|
|
168
|
+
logWarn(result.message.split('\n')[0] ?? '')
|
|
169
|
+
outro()
|
|
170
|
+
emit(emitJson, {
|
|
171
|
+
root,
|
|
172
|
+
subject: subjectName,
|
|
173
|
+
baseUrl,
|
|
174
|
+
reason: result.reason,
|
|
175
|
+
message: result.message,
|
|
176
|
+
...(result.reason === 'browser-missing'
|
|
177
|
+
? { install: INSTALL_BROWSER }
|
|
178
|
+
: {}),
|
|
179
|
+
})
|
|
180
|
+
return 1
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const groups = groupByTreatment(result.readings)
|
|
184
|
+
|
|
185
|
+
logStep('Routes')
|
|
186
|
+
for (const route of result.routes) {
|
|
187
|
+
logInfo(`${route.route} ${plural(route.elements, 'element')}`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// A walk that matched nothing refuses rather than printing an empty listing,
|
|
191
|
+
// because no rows and one row read the same to anything counting them, and
|
|
192
|
+
// the first says the query reached nothing while the second says the site
|
|
193
|
+
// gives one consistent answer.
|
|
194
|
+
if (groups.length === 0) {
|
|
195
|
+
return refuse(
|
|
196
|
+
emitJson,
|
|
197
|
+
root,
|
|
198
|
+
'no-elements',
|
|
199
|
+
`No element matched ${declared.query} on any of the ${plural(result.routes.length, 'route')} walked.`,
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
logStep('Treatments')
|
|
204
|
+
logInfo(
|
|
205
|
+
`${plural(groups.length, 'answer')} across ${plural(result.readings.length, 'element')}`,
|
|
206
|
+
)
|
|
207
|
+
for (const group of groups) {
|
|
208
|
+
logInfo(` ${group.count}x ${group.treatment}`)
|
|
209
|
+
logInfo(
|
|
210
|
+
` ${abbreviate(group.samples, group.count)} on ${abbreviate(group.routes, group.routes.length)}`,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
outro()
|
|
214
|
+
|
|
215
|
+
emit(emitJson, {
|
|
216
|
+
root,
|
|
217
|
+
subject: subjectName,
|
|
218
|
+
baseUrl,
|
|
219
|
+
routes: result.routes,
|
|
220
|
+
elements: result.readings.length,
|
|
221
|
+
treatments: groups,
|
|
222
|
+
durationMs: result.durationMs,
|
|
223
|
+
})
|
|
224
|
+
return 0
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Frames a refusal on stderr and puts the record on stdout, so an operator
|
|
229
|
+
* reading the terminal sees the reason rather than a command that appeared to
|
|
230
|
+
* do nothing.
|
|
231
|
+
*/
|
|
232
|
+
function refuse(
|
|
233
|
+
emitJson: boolean,
|
|
234
|
+
root: string,
|
|
235
|
+
reason: string,
|
|
236
|
+
message: string,
|
|
237
|
+
): number {
|
|
238
|
+
logStep('Refused')
|
|
239
|
+
logWarn(message)
|
|
240
|
+
outro()
|
|
241
|
+
emit(emitJson, { root, reason, message })
|
|
242
|
+
return 1
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function loadWalker(): Promise<Walker | undefined> {
|
|
246
|
+
try {
|
|
247
|
+
return await import('@/inventory/walk')
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (isEngineMissing(error)) return undefined
|
|
250
|
+
throw error
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function emit(json: boolean, record: unknown): void {
|
|
255
|
+
if (json) process.stdout.write(`${JSON.stringify(record)}\n`)
|
|
256
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { type LabelAuditRefusal, auditLabels } from '@/labels/audit'
|
|
5
|
+
import { MAP_REL } from '@/labels/map'
|
|
6
|
+
import { scanPhaseLabels } from '@/labels/phase'
|
|
7
|
+
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/ui'
|
|
8
|
+
|
|
9
|
+
interface AuditOptions {
|
|
10
|
+
readonly base?: string
|
|
11
|
+
readonly root?: string
|
|
12
|
+
readonly json?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ScanOptions {
|
|
16
|
+
readonly event?: string
|
|
17
|
+
readonly title?: string
|
|
18
|
+
readonly body?: string
|
|
19
|
+
readonly head?: string
|
|
20
|
+
readonly json?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What a reader does about each way the audit produced no reading. */
|
|
24
|
+
const REFUSALS: Record<LabelAuditRefusal, string> = {
|
|
25
|
+
// An answer rather than a fault. A project declaring no map is labelled
|
|
26
|
+
// silently by design, so a refusal reading as a break would make the map
|
|
27
|
+
// mandatory for every target.
|
|
28
|
+
'no-map': `No ${MAP_REL} here, so this project labels nothing and declares no surfaces to cover.`,
|
|
29
|
+
'unreadable-map': `${MAP_REL} is not valid TOML, so no row could be read.`,
|
|
30
|
+
'no-domains': `${MAP_REL} carries no usable row under [domains], so every path would read as uncovered.`,
|
|
31
|
+
'no-base': 'No base resolves against the trunk. Fetch origin or pass --base.',
|
|
32
|
+
'bad-base':
|
|
33
|
+
'The ref passed to --base resolves to no commit here. Pass one this tree carries.',
|
|
34
|
+
'unreadable-changes':
|
|
35
|
+
'git could not list what this branch changed, so the set is unknown.',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function register(program: Command): void {
|
|
39
|
+
const labels = program
|
|
40
|
+
.command('labels')
|
|
41
|
+
.description('Resolve a changed set against the pull request label map')
|
|
42
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
43
|
+
|
|
44
|
+
labels
|
|
45
|
+
.command('audit')
|
|
46
|
+
.description(
|
|
47
|
+
'Report the labels a changed set earns and the paths no row reaches',
|
|
48
|
+
)
|
|
49
|
+
.argument(
|
|
50
|
+
'[paths...]',
|
|
51
|
+
'Changed set to read, defaulting to the branch range',
|
|
52
|
+
)
|
|
53
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
54
|
+
.option('--base <ref>', 'Far side of the range, defaulting to the trunk')
|
|
55
|
+
.option('--root <path>', 'Repository to read, defaulting to the cwd')
|
|
56
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
57
|
+
.addHelpText(
|
|
58
|
+
'after',
|
|
59
|
+
[
|
|
60
|
+
'',
|
|
61
|
+
`Reads ${MAP_REL} and matches it prefix-anchored, which is the rule the`,
|
|
62
|
+
"map's own census was measured against. It reports and never gates,",
|
|
63
|
+
'because whether an uncovered surface deserves a label is a judgment.',
|
|
64
|
+
'',
|
|
65
|
+
'What it separates:',
|
|
66
|
+
' uncovered a surface no row reaches, which is a gap wanting a row',
|
|
67
|
+
' declined a path a [declined] row names, which is a decision already taken',
|
|
68
|
+
'',
|
|
69
|
+
'What it does not measure:',
|
|
70
|
+
' a prefix reaching no path, which is the map going stale from the other',
|
|
71
|
+
' side and a second measure rather than this one',
|
|
72
|
+
'',
|
|
73
|
+
'Exit codes:',
|
|
74
|
+
' 0 every changed path is labelled or declined',
|
|
75
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
76
|
+
' 2 at least one changed path is reached by no row',
|
|
77
|
+
'',
|
|
78
|
+
'Examples:',
|
|
79
|
+
' canon labels audit',
|
|
80
|
+
' canon labels audit --json',
|
|
81
|
+
' canon labels audit --base origin/main',
|
|
82
|
+
' canon labels audit src/cli.ts docs/index.md --json',
|
|
83
|
+
'',
|
|
84
|
+
].join('\n'),
|
|
85
|
+
)
|
|
86
|
+
.action(async (paths: string[], opts: AuditOptions) => {
|
|
87
|
+
process.exitCode = await runAudit(paths, opts)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
labels
|
|
91
|
+
.command('scan')
|
|
92
|
+
.description(
|
|
93
|
+
'Fail a pull request whose title or body carries a phase label',
|
|
94
|
+
)
|
|
95
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
96
|
+
.option(
|
|
97
|
+
'--event <path>',
|
|
98
|
+
'GitHub pull_request event payload to read, such as $GITHUB_EVENT_PATH',
|
|
99
|
+
)
|
|
100
|
+
.option('--title <text>', 'Title to scan, overriding the event payload')
|
|
101
|
+
.option('--body <text>', 'Body to scan, overriding the event payload')
|
|
102
|
+
.option('--head <ref>', 'Head branch, overriding the event payload')
|
|
103
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
104
|
+
.addHelpText(
|
|
105
|
+
'after',
|
|
106
|
+
[
|
|
107
|
+
'',
|
|
108
|
+
'Reads canon standards versioning for the two namespaces and sorts every',
|
|
109
|
+
'version-shaped token this pull request carries into the one the pull',
|
|
110
|
+
'request is allowed to hold. A release-please pull request, read off its',
|
|
111
|
+
'own fixed head branch and title, may carry semver references. Every',
|
|
112
|
+
'other pull request may carry neither, so any token found there is a',
|
|
113
|
+
'leaked phase label.',
|
|
114
|
+
'',
|
|
115
|
+
'Exit codes:',
|
|
116
|
+
' 0 no phase label found',
|
|
117
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
118
|
+
' 2 the title or body carries a phase label',
|
|
119
|
+
'',
|
|
120
|
+
'Examples:',
|
|
121
|
+
' canon labels scan --event "$GITHUB_EVENT_PATH"',
|
|
122
|
+
' canon labels scan --title "feat: x" --body "planned under v68.5" --head feat/x',
|
|
123
|
+
'',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
)
|
|
126
|
+
.action(async (opts: ScanOptions) => {
|
|
127
|
+
process.exitCode = await runScan(opts)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function runAudit(paths: string[], opts: AuditOptions): Promise<number> {
|
|
132
|
+
const root = resolve(opts.root ?? process.cwd())
|
|
133
|
+
const emitJson = opts.json ?? false
|
|
134
|
+
|
|
135
|
+
const report = await auditLabels(root, {
|
|
136
|
+
base: opts.base,
|
|
137
|
+
...(paths.length > 0 && { paths }),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
intro('canon labels audit')
|
|
141
|
+
|
|
142
|
+
// The frame renders on stderr in both modes and the record goes to stdout
|
|
143
|
+
// alone, so an operator reading the terminal sees the refusal rather than a
|
|
144
|
+
// command that appeared to do nothing.
|
|
145
|
+
if (report.kind === 'refused') {
|
|
146
|
+
logStep('Refused')
|
|
147
|
+
logWarn(REFUSALS[report.reason])
|
|
148
|
+
outro()
|
|
149
|
+
|
|
150
|
+
if (emitJson) {
|
|
151
|
+
process.stdout.write(
|
|
152
|
+
`${JSON.stringify({
|
|
153
|
+
root,
|
|
154
|
+
reason: report.reason,
|
|
155
|
+
message: REFUSALS[report.reason],
|
|
156
|
+
})}\n`,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
return 1
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { coverage } = report
|
|
163
|
+
|
|
164
|
+
logStep('Scope')
|
|
165
|
+
logInfo(
|
|
166
|
+
report.base === undefined
|
|
167
|
+
? `${plural(report.changed.length, 'path')} supplied by the caller`
|
|
168
|
+
: `${plural(report.changed.length, 'path')} changed since ${report.base.slice(0, 8)}`,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
logStep('Labels')
|
|
172
|
+
logInfo(
|
|
173
|
+
coverage.labels.length === 0
|
|
174
|
+
? 'this set earns no label'
|
|
175
|
+
: coverage.labels.join(', '),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// Named rather than counted into the uncovered line. A path somebody decided
|
|
179
|
+
// against wants nothing done, and folding it in would ask for a row that was
|
|
180
|
+
// already refused.
|
|
181
|
+
logStep('Declined')
|
|
182
|
+
if (coverage.declined.length === 0) {
|
|
183
|
+
logInfo('no changed path is deliberately unlabelled')
|
|
184
|
+
} else {
|
|
185
|
+
for (const entry of coverage.declined) {
|
|
186
|
+
logInfo(`${entry.path}: ${entry.reason}`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
logStep(coverage.uncovered.length === 0 ? 'Covered' : 'Uncovered')
|
|
191
|
+
if (coverage.uncovered.length === 0) {
|
|
192
|
+
logInfo('every changed path is reached by a row')
|
|
193
|
+
} else {
|
|
194
|
+
logWarn(
|
|
195
|
+
`${plural(coverage.uncovered.length, 'path')} reached by no row. Give each a prefix on the row that owns its subject, or a [declined] row with the reason it earns none.`,
|
|
196
|
+
)
|
|
197
|
+
for (const path of coverage.uncovered) logWarn(path)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
outro()
|
|
201
|
+
|
|
202
|
+
if (emitJson) {
|
|
203
|
+
process.stdout.write(
|
|
204
|
+
`${JSON.stringify({
|
|
205
|
+
root,
|
|
206
|
+
...(report.base !== undefined && { base: report.base }),
|
|
207
|
+
changed: report.changed,
|
|
208
|
+
labels: coverage.labels,
|
|
209
|
+
declined: coverage.declined,
|
|
210
|
+
uncovered: coverage.uncovered,
|
|
211
|
+
})}\n`,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return coverage.uncovered.length === 0 ? 0 : 2
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Why `runScan` had no title and body to hand `scanPhaseLabels`. */
|
|
219
|
+
type ScanInputRefusal = 'no-input' | 'unreadable-event' | 'not-a-pull-request'
|
|
220
|
+
|
|
221
|
+
type ResolvedScanInput =
|
|
222
|
+
| {
|
|
223
|
+
readonly kind: 'resolved'
|
|
224
|
+
readonly title: string
|
|
225
|
+
readonly body: string
|
|
226
|
+
readonly headRefName: string
|
|
227
|
+
}
|
|
228
|
+
| {
|
|
229
|
+
readonly kind: 'refused'
|
|
230
|
+
readonly reason: ScanInputRefusal
|
|
231
|
+
readonly message: string
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Reads a title, a body, and a head branch from explicit flags first and the
|
|
236
|
+
* named event payload second, so a caller testing the wiring by hand never
|
|
237
|
+
* needs a real GitHub event file on disk.
|
|
238
|
+
*/
|
|
239
|
+
function resolveScanInput(opts: ScanOptions): ResolvedScanInput {
|
|
240
|
+
let title = opts.title
|
|
241
|
+
let body = opts.body
|
|
242
|
+
let headRefName = opts.head
|
|
243
|
+
|
|
244
|
+
if (opts.event !== undefined) {
|
|
245
|
+
let raw: string
|
|
246
|
+
try {
|
|
247
|
+
raw = readFileSync(opts.event, 'utf8')
|
|
248
|
+
} catch {
|
|
249
|
+
return {
|
|
250
|
+
kind: 'refused',
|
|
251
|
+
reason: 'unreadable-event',
|
|
252
|
+
message: `${opts.event} could not be read, so no payload was there to scan.`,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let payload: unknown
|
|
257
|
+
try {
|
|
258
|
+
payload = JSON.parse(raw)
|
|
259
|
+
} catch {
|
|
260
|
+
return {
|
|
261
|
+
kind: 'refused',
|
|
262
|
+
reason: 'unreadable-event',
|
|
263
|
+
message: `${opts.event} is not valid JSON, so no payload was there to scan.`,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const pullRequest =
|
|
268
|
+
typeof payload === 'object' && payload !== null
|
|
269
|
+
? (payload as Record<string, unknown>).pull_request
|
|
270
|
+
: undefined
|
|
271
|
+
|
|
272
|
+
if (typeof pullRequest !== 'object' || pullRequest === null) {
|
|
273
|
+
return {
|
|
274
|
+
kind: 'refused',
|
|
275
|
+
reason: 'not-a-pull-request',
|
|
276
|
+
message: `${opts.event} carries no pull_request, so no title or body exists to scan.`,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const record = pullRequest as Record<string, unknown>
|
|
281
|
+
const head = record.head
|
|
282
|
+
title ??= typeof record.title === 'string' ? record.title : undefined
|
|
283
|
+
body ??= typeof record.body === 'string' ? record.body : undefined
|
|
284
|
+
headRefName ??=
|
|
285
|
+
typeof head === 'object' &&
|
|
286
|
+
head !== null &&
|
|
287
|
+
typeof (head as Record<string, unknown>).ref === 'string'
|
|
288
|
+
? ((head as Record<string, unknown>).ref as string)
|
|
289
|
+
: undefined
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (title === undefined) {
|
|
293
|
+
return {
|
|
294
|
+
kind: 'refused',
|
|
295
|
+
reason: 'no-input',
|
|
296
|
+
message:
|
|
297
|
+
'No --event, --title, or --body given, so there is nothing to scan.',
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
kind: 'resolved',
|
|
303
|
+
title,
|
|
304
|
+
body: body ?? '',
|
|
305
|
+
headRefName: headRefName ?? '',
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function runScan(opts: ScanOptions): Promise<number> {
|
|
310
|
+
const emitJson = opts.json ?? false
|
|
311
|
+
|
|
312
|
+
intro('canon labels scan')
|
|
313
|
+
|
|
314
|
+
const resolved = resolveScanInput(opts)
|
|
315
|
+
|
|
316
|
+
if (resolved.kind === 'refused') {
|
|
317
|
+
logStep('Refused')
|
|
318
|
+
logWarn(resolved.message)
|
|
319
|
+
outro()
|
|
320
|
+
|
|
321
|
+
if (emitJson) {
|
|
322
|
+
process.stdout.write(
|
|
323
|
+
`${JSON.stringify({ reason: resolved.reason, message: resolved.message })}\n`,
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
return 1
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const result = scanPhaseLabels(resolved)
|
|
330
|
+
|
|
331
|
+
logStep('Pull request')
|
|
332
|
+
logInfo(
|
|
333
|
+
result.cutsRelease
|
|
334
|
+
? 'reads as a release-please pull request'
|
|
335
|
+
: 'reads as an ordinary pull request',
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
logStep(result.phaseLabels.length === 0 ? 'Clean' : 'Phase label found')
|
|
339
|
+
if (result.phaseLabels.length === 0) {
|
|
340
|
+
logInfo('no phase label in the title or body')
|
|
341
|
+
} else {
|
|
342
|
+
logWarn(
|
|
343
|
+
`${plural(result.phaseLabels.length, 'phase label')} in the title or body. Describe the change itself and drop the internal label before publishing.`,
|
|
344
|
+
)
|
|
345
|
+
for (const label of result.phaseLabels) logWarn(label)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
outro()
|
|
349
|
+
|
|
350
|
+
if (emitJson) {
|
|
351
|
+
process.stdout.write(
|
|
352
|
+
`${JSON.stringify({
|
|
353
|
+
cutsRelease: result.cutsRelease,
|
|
354
|
+
phaseLabels: result.phaseLabels,
|
|
355
|
+
semverTags: result.semverTags,
|
|
356
|
+
})}\n`,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result.phaseLabels.length === 0 ? 0 : 2
|
|
361
|
+
}
|