@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,407 @@
|
|
|
1
|
+
import { execaSync } from 'execa'
|
|
2
|
+
import { gitEnv } from '@/git-env'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Preferred first, matching `src/tasks/trunk.ts`. A clone with no remote still
|
|
6
|
+
* answers off its local trunk, and a local `main` trailing the remote widens
|
|
7
|
+
* the range rather than narrowing it, which over-reports instead of hiding.
|
|
8
|
+
*/
|
|
9
|
+
const TRUNK_REFS = ['origin/main', 'main'] as const
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The extensions this check can pair. A test sits beside its subject under one
|
|
13
|
+
* name throughout this corpus, so the weakest assumption available is also the
|
|
14
|
+
* one the corpus already honors. Every other extension is read past and named,
|
|
15
|
+
* since a bash script and its test are related by nothing a filename carries.
|
|
16
|
+
*/
|
|
17
|
+
export const SOURCE_EXTENSIONS = ['.ts', '.tsx'] as const
|
|
18
|
+
|
|
19
|
+
/** The suffix removed to derive the subject a test covers. */
|
|
20
|
+
export const TEST_SUFFIXES = ['.test.ts', '.test.tsx'] as const
|
|
21
|
+
|
|
22
|
+
/** A `%H` line, which is 40 hex characters under sha1 and 64 under sha256. */
|
|
23
|
+
const COMMIT = /^[0-9a-f]{40,64}$/
|
|
24
|
+
|
|
25
|
+
/** A `--name-status` line: a status letter, a tab, then one path. */
|
|
26
|
+
const NAME_STATUS = /^([A-Z])\d*\t(.+)$/
|
|
27
|
+
|
|
28
|
+
export type Verdict = 'satisfied' | 'implementation-first' | 'unclassified'
|
|
29
|
+
|
|
30
|
+
export interface PairRecord {
|
|
31
|
+
/** The implementation path, which is what a subject is named by. */
|
|
32
|
+
readonly subject: string
|
|
33
|
+
/** The test paired to it, or `null` when the pairing found no partner. */
|
|
34
|
+
readonly test: string | null
|
|
35
|
+
readonly verdict: Verdict
|
|
36
|
+
readonly implementationCommit: string | null
|
|
37
|
+
readonly testCommit: string | null
|
|
38
|
+
/** Why the verdict reads the way it does, in the report's own words. */
|
|
39
|
+
readonly reason: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Scope {
|
|
43
|
+
readonly extensions: readonly string[]
|
|
44
|
+
readonly testSuffixes: readonly string[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Every reason `canon gov test-order` refuses for.
|
|
49
|
+
*
|
|
50
|
+
* None is an ordinary absence. A depth-1 checkout falls back to the root
|
|
51
|
+
* commit and reports zero rather than reaching any of these, so what remains
|
|
52
|
+
* is a repository with no commit at all or a git operation that failed
|
|
53
|
+
* outright, both a broken checkout rather than a target's ordinary state.
|
|
54
|
+
*/
|
|
55
|
+
export type TestOrderRefusal =
|
|
56
|
+
| 'no-history'
|
|
57
|
+
| 'no-log'
|
|
58
|
+
| 'no-tree'
|
|
59
|
+
| 'bad-base'
|
|
60
|
+
| 'no-base'
|
|
61
|
+
|
|
62
|
+
export type TestOrderReport =
|
|
63
|
+
| {
|
|
64
|
+
readonly kind: 'measured'
|
|
65
|
+
readonly base: string
|
|
66
|
+
readonly head: string
|
|
67
|
+
readonly scope: Scope
|
|
68
|
+
readonly satisfied: readonly PairRecord[]
|
|
69
|
+
readonly findings: readonly PairRecord[]
|
|
70
|
+
readonly unclassified: readonly PairRecord[]
|
|
71
|
+
/** Changed paths outside the pairing's reach, named rather than counted. */
|
|
72
|
+
readonly ignored: readonly string[]
|
|
73
|
+
}
|
|
74
|
+
| {
|
|
75
|
+
readonly kind: 'unreadable'
|
|
76
|
+
readonly reason: TestOrderRefusal
|
|
77
|
+
readonly message: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TestOrderOptions {
|
|
81
|
+
/** The far side of the range, defaulting to the merge base against the trunk. */
|
|
82
|
+
readonly base?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** One path as one commit in the range touched it. */
|
|
86
|
+
interface Change {
|
|
87
|
+
readonly path: string
|
|
88
|
+
readonly status: string
|
|
89
|
+
readonly commit: string
|
|
90
|
+
/** Position in the range, oldest first, which is what orders a pair. */
|
|
91
|
+
readonly order: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reads `--name-status` log output, oldest commit first, into one entry per
|
|
96
|
+
* path per commit.
|
|
97
|
+
*
|
|
98
|
+
* The order field rather than the commit is what a comparison reads, since two
|
|
99
|
+
* commits carry no ordering a caller can derive from their hashes alone.
|
|
100
|
+
*/
|
|
101
|
+
function parseChanges(output: string): Change[] {
|
|
102
|
+
const changes: Change[] = []
|
|
103
|
+
let commit = ''
|
|
104
|
+
let order = -1
|
|
105
|
+
|
|
106
|
+
for (const line of output.split('\n')) {
|
|
107
|
+
const trimmed = line.trimEnd()
|
|
108
|
+
if (trimmed === '') continue
|
|
109
|
+
|
|
110
|
+
if (COMMIT.test(trimmed)) {
|
|
111
|
+
commit = trimmed
|
|
112
|
+
order += 1
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const match = NAME_STATUS.exec(trimmed)
|
|
117
|
+
if (match === null || commit === '') continue
|
|
118
|
+
|
|
119
|
+
changes.push({ path: match[2], status: match[1], commit, order })
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return changes
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Whether a path is a test this check can derive a subject from. */
|
|
126
|
+
function testSuffix(path: string): string | undefined {
|
|
127
|
+
return TEST_SUFFIXES.find((suffix) => path.endsWith(suffix))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The implementation a test covers, derived by removing the test suffix.
|
|
132
|
+
*
|
|
133
|
+
* A behavior split across two modules pairs wrongly or not at all under this,
|
|
134
|
+
* which is what the unclassified bucket exists to catch rather than hide.
|
|
135
|
+
*/
|
|
136
|
+
function subjectOf(path: string): string {
|
|
137
|
+
const suffix = testSuffix(path)
|
|
138
|
+
if (suffix === undefined) return path
|
|
139
|
+
return `${path.slice(0, -suffix.length)}${suffix.replace('.test', '')}`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The one test path that would cover `subject` under the beside convention. */
|
|
143
|
+
function testPathFor(subject: string): string {
|
|
144
|
+
return subject.replace(/\.(tsx?)$/, '.test.$1')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whether a path is an implementation this check can pair. A declaration file
|
|
149
|
+
* carries no behavior to test, so it is read past rather than reported as a
|
|
150
|
+
* module nothing covers.
|
|
151
|
+
*/
|
|
152
|
+
function isImplementation(path: string): boolean {
|
|
153
|
+
if (path.endsWith('.d.ts')) return false
|
|
154
|
+
if (testSuffix(path) !== undefined) return false
|
|
155
|
+
return SOURCE_EXTENSIONS.some((extension) => path.endsWith(extension))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The first commit in the range that added `path`, or `undefined`. */
|
|
159
|
+
function introduction(
|
|
160
|
+
changes: readonly Change[],
|
|
161
|
+
path: string,
|
|
162
|
+
): Change | undefined {
|
|
163
|
+
return changes.find((change) => change.path === path && change.status === 'A')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function classify(
|
|
167
|
+
changes: readonly Change[],
|
|
168
|
+
atBase: ReadonlySet<string>,
|
|
169
|
+
): {
|
|
170
|
+
satisfied: PairRecord[]
|
|
171
|
+
findings: PairRecord[]
|
|
172
|
+
unclassified: PairRecord[]
|
|
173
|
+
} {
|
|
174
|
+
const satisfied: PairRecord[] = []
|
|
175
|
+
const findings: PairRecord[] = []
|
|
176
|
+
const unclassified: PairRecord[] = []
|
|
177
|
+
|
|
178
|
+
// Keyed by subject so a module whose implementation and test both moved is
|
|
179
|
+
// one record rather than two, and so a test with no partner still reports
|
|
180
|
+
// under the implementation path a reader would go looking for.
|
|
181
|
+
const subjects = new Set<string>()
|
|
182
|
+
for (const change of changes) {
|
|
183
|
+
if (isImplementation(change.path)) subjects.add(change.path)
|
|
184
|
+
else if (testSuffix(change.path) !== undefined) {
|
|
185
|
+
subjects.add(subjectOf(change.path))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const subject of [...subjects].sort()) {
|
|
190
|
+
const candidate = testPathFor(subject)
|
|
191
|
+
const testChange = introduction(changes, candidate)
|
|
192
|
+
const testAtBase = atBase.has(candidate)
|
|
193
|
+
const test = testAtBase || testChange !== undefined ? candidate : undefined
|
|
194
|
+
|
|
195
|
+
const implementation = introduction(changes, subject)
|
|
196
|
+
|
|
197
|
+
if (implementation === undefined) {
|
|
198
|
+
unclassified.push({
|
|
199
|
+
subject,
|
|
200
|
+
test: test ?? null,
|
|
201
|
+
verdict: 'unclassified',
|
|
202
|
+
implementationCommit: null,
|
|
203
|
+
testCommit: null,
|
|
204
|
+
reason: atBase.has(subject)
|
|
205
|
+
? 'the implementation predates the range, so a change to it cannot be separated from a refactor'
|
|
206
|
+
: 'no implementation reached the range beside this test',
|
|
207
|
+
})
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (test === undefined) {
|
|
212
|
+
unclassified.push({
|
|
213
|
+
subject,
|
|
214
|
+
test: null,
|
|
215
|
+
verdict: 'unclassified',
|
|
216
|
+
implementationCommit: implementation.commit,
|
|
217
|
+
testCommit: null,
|
|
218
|
+
reason: 'no test names this module, so the ordering has no second side',
|
|
219
|
+
})
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// A paired test the range never added is one that already sat at the base
|
|
224
|
+
// commit, since those are the only two ways `test` gets a value at all.
|
|
225
|
+
if (testChange === undefined) {
|
|
226
|
+
satisfied.push({
|
|
227
|
+
subject,
|
|
228
|
+
test,
|
|
229
|
+
verdict: 'satisfied',
|
|
230
|
+
implementationCommit: implementation.commit,
|
|
231
|
+
testCommit: null,
|
|
232
|
+
reason: 'the test predates the range',
|
|
233
|
+
})
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// One commit carrying both sides counts as satisfied. The rule asks that
|
|
238
|
+
// the test not come after, and a single commit is the shape a small change
|
|
239
|
+
// takes here, so reporting it would flag most of the corpus.
|
|
240
|
+
const record: PairRecord = {
|
|
241
|
+
subject,
|
|
242
|
+
test,
|
|
243
|
+
verdict:
|
|
244
|
+
testChange.order <= implementation.order
|
|
245
|
+
? 'satisfied'
|
|
246
|
+
: 'implementation-first',
|
|
247
|
+
implementationCommit: implementation.commit,
|
|
248
|
+
testCommit: testChange.commit,
|
|
249
|
+
reason:
|
|
250
|
+
testChange.order <= implementation.order
|
|
251
|
+
? 'the test reached history no later than the implementation'
|
|
252
|
+
: 'the implementation reached history before the test covering it',
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (record.verdict === 'satisfied') satisfied.push(record)
|
|
256
|
+
else findings.push(record)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { satisfied, findings, unclassified }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Where an implementation reached a commit ahead of the test covering it,
|
|
264
|
+
* between `base` and the current `HEAD` of the tree at `root`.
|
|
265
|
+
*
|
|
266
|
+
* This reports and never gates. Pairing a test to an implementation is a
|
|
267
|
+
* judgment, so a change the pairing cannot read lands in `unclassified` with
|
|
268
|
+
* its reason stated rather than being counted as a pass. Coverage is narrower
|
|
269
|
+
* than the rule the check answers to, and `scope` and `ignored` are what say so
|
|
270
|
+
* on every run.
|
|
271
|
+
*/
|
|
272
|
+
export function readTestOrder(
|
|
273
|
+
root: string,
|
|
274
|
+
options: TestOrderOptions = {},
|
|
275
|
+
): TestOrderReport {
|
|
276
|
+
const head = revParse(root, 'HEAD')
|
|
277
|
+
if (head === undefined) {
|
|
278
|
+
return {
|
|
279
|
+
kind: 'unreadable',
|
|
280
|
+
reason: 'no-history',
|
|
281
|
+
message: `No git history under ${root}. History is the only surface carrying the ordering, so there is nothing to read.`,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const base = resolveBase(root, options.base, head)
|
|
286
|
+
if (typeof base !== 'string') return base
|
|
287
|
+
|
|
288
|
+
const log = git(root, [
|
|
289
|
+
'log',
|
|
290
|
+
'--reverse',
|
|
291
|
+
'--name-status',
|
|
292
|
+
'--no-renames',
|
|
293
|
+
'--format=%H',
|
|
294
|
+
`${base}..${head}`,
|
|
295
|
+
])
|
|
296
|
+
|
|
297
|
+
if (log === undefined) {
|
|
298
|
+
return {
|
|
299
|
+
kind: 'unreadable',
|
|
300
|
+
reason: 'no-log',
|
|
301
|
+
message: `Reading history between ${base} and HEAD failed under ${root}.`,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const tree = git(root, ['ls-tree', '-r', '--name-only', base])
|
|
306
|
+
if (tree === undefined) {
|
|
307
|
+
return {
|
|
308
|
+
kind: 'unreadable',
|
|
309
|
+
reason: 'no-tree',
|
|
310
|
+
message: `Reading the tree at ${base} failed under ${root}. Without it a test written before the range reads as absent.`,
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const changes = parseChanges(log)
|
|
315
|
+
const atBase = new Set(tree.split('\n').filter((line) => line !== ''))
|
|
316
|
+
|
|
317
|
+
const ignored = [
|
|
318
|
+
...new Set(
|
|
319
|
+
changes
|
|
320
|
+
.map((change) => change.path)
|
|
321
|
+
.filter(
|
|
322
|
+
(path) => !isImplementation(path) && testSuffix(path) === undefined,
|
|
323
|
+
),
|
|
324
|
+
),
|
|
325
|
+
].sort()
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
kind: 'measured',
|
|
329
|
+
base,
|
|
330
|
+
head,
|
|
331
|
+
scope: { extensions: SOURCE_EXTENSIONS, testSuffixes: TEST_SUFFIXES },
|
|
332
|
+
...classify(changes, atBase),
|
|
333
|
+
ignored,
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The far side of the range. A ref the caller named has to resolve, since
|
|
339
|
+
* falling back to the trunk there would measure a range nobody asked for. With
|
|
340
|
+
* no ref named, the merge base against the trunk scopes the run to the branch,
|
|
341
|
+
* and a repository carrying no trunk falls back to the root commit rather than
|
|
342
|
+
* refusing.
|
|
343
|
+
*/
|
|
344
|
+
function resolveBase(
|
|
345
|
+
root: string,
|
|
346
|
+
ref: string | undefined,
|
|
347
|
+
head: string,
|
|
348
|
+
): string | { kind: 'unreadable'; reason: TestOrderRefusal; message: string } {
|
|
349
|
+
if (ref !== undefined) {
|
|
350
|
+
const resolved = revParse(root, ref)
|
|
351
|
+
if (resolved === undefined) {
|
|
352
|
+
return {
|
|
353
|
+
kind: 'unreadable',
|
|
354
|
+
reason: 'bad-base',
|
|
355
|
+
message: `Ref ${ref} resolves to no commit in ${root}. Pass a commit this tree carries.`,
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return resolved
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const trunk of TRUNK_REFS) {
|
|
362
|
+
if (revParse(root, trunk) === undefined) continue
|
|
363
|
+
const merged = git(root, ['merge-base', head, trunk])
|
|
364
|
+
if (merged !== undefined && merged !== '') return merged
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const rootCommits = git(root, ['rev-list', '--max-parents=0', head])
|
|
368
|
+
if (rootCommits === undefined || rootCommits === '') {
|
|
369
|
+
return {
|
|
370
|
+
kind: 'unreadable',
|
|
371
|
+
reason: 'no-base',
|
|
372
|
+
message: `No base resolves against ${root}. Fetch origin or pass --base.`,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return rootCommits.split('\n')[0]
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* `execaSync` with git's repository-resolution variables stripped, so `-C`
|
|
381
|
+
* resolves against `root` and not against whatever repository a hook exported.
|
|
382
|
+
* `undefined` is the refusal, which every caller turns into its own reason.
|
|
383
|
+
*/
|
|
384
|
+
function git(root: string, args: readonly string[]): string | undefined {
|
|
385
|
+
const result = execaSync('git', ['-C', root, ...args], {
|
|
386
|
+
reject: false,
|
|
387
|
+
env: gitEnv(),
|
|
388
|
+
extendEnv: false,
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
return result.exitCode === 0 ? result.stdout.trimEnd() : undefined
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The commit a ref names. `^{commit}` is what turns a tag or a tree into the
|
|
396
|
+
* commit behind it, so a caller never compares a ref against another type.
|
|
397
|
+
*/
|
|
398
|
+
function revParse(root: string, ref: string): string | undefined {
|
|
399
|
+
const resolved = git(root, [
|
|
400
|
+
'rev-parse',
|
|
401
|
+
'--verify',
|
|
402
|
+
'--quiet',
|
|
403
|
+
`${ref}^{commit}`,
|
|
404
|
+
])
|
|
405
|
+
|
|
406
|
+
return resolved === undefined || resolved === '' ? undefined : resolved.trim()
|
|
407
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const FRONTMATTER = /^---\n([\s\S]*?)\n---/
|
|
2
|
+
|
|
3
|
+
export interface Frontmatter {
|
|
4
|
+
readonly raw: string
|
|
5
|
+
readonly fields: Readonly<Record<string, unknown>>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Splits a leading `---` block off a markdown source.
|
|
10
|
+
*
|
|
11
|
+
* `raw` keeps the delimiters so an index can be rewritten with its own
|
|
12
|
+
* frontmatter untouched, including comments and key order that a parse and
|
|
13
|
+
* re-emit would lose.
|
|
14
|
+
*/
|
|
15
|
+
export function parseFrontmatter(source: string): Frontmatter | undefined {
|
|
16
|
+
const match = source.match(FRONTMATTER)
|
|
17
|
+
if (!match) return undefined
|
|
18
|
+
|
|
19
|
+
const parsed = Bun.YAML.parse(match[1])
|
|
20
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
21
|
+
return undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
raw: match[0],
|
|
26
|
+
fields: parsed as Record<string, unknown>,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readField(
|
|
31
|
+
frontmatter: Frontmatter | undefined,
|
|
32
|
+
key: string,
|
|
33
|
+
): string | undefined {
|
|
34
|
+
const value = frontmatter?.fields[key]
|
|
35
|
+
if (typeof value === 'string') return value
|
|
36
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
37
|
+
return String(value)
|
|
38
|
+
}
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function readFrontmatter(
|
|
43
|
+
path: string,
|
|
44
|
+
): Promise<Frontmatter | undefined> {
|
|
45
|
+
return parseFrontmatter(await Bun.file(path).text())
|
|
46
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readField, readFrontmatter } from '@/indexes/frontmatter'
|
|
3
|
+
import { computeIndex } from '@/indexes/render'
|
|
4
|
+
import { INDEX_FILE } from '@/indexes/walk'
|
|
5
|
+
|
|
6
|
+
export const REGEN_ACTIONS = [
|
|
7
|
+
'written',
|
|
8
|
+
'would-write',
|
|
9
|
+
'unchanged',
|
|
10
|
+
'skipped',
|
|
11
|
+
'error',
|
|
12
|
+
] as const
|
|
13
|
+
|
|
14
|
+
export type RegenAction = (typeof REGEN_ACTIONS)[number]
|
|
15
|
+
|
|
16
|
+
export interface RegenResult {
|
|
17
|
+
readonly path: string
|
|
18
|
+
readonly action: RegenAction
|
|
19
|
+
readonly reason?: string
|
|
20
|
+
readonly errors?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RegenOptions {
|
|
24
|
+
readonly dryRun: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Regenerates one folder's index and reports what happened.
|
|
29
|
+
*
|
|
30
|
+
* A folder marked `auto: false` is skipped rather than errored, which is how
|
|
31
|
+
* hand-maintained catalogs opt out of the walker.
|
|
32
|
+
*/
|
|
33
|
+
export async function regenOne(
|
|
34
|
+
dir: string,
|
|
35
|
+
options: RegenOptions,
|
|
36
|
+
): Promise<RegenResult> {
|
|
37
|
+
const path = `${dir}/${INDEX_FILE}`
|
|
38
|
+
|
|
39
|
+
if (!existsSync(path)) {
|
|
40
|
+
return { path, action: 'error', reason: 'no index.md' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const auto = readField(await readFrontmatter(path), 'auto')
|
|
44
|
+
if (auto === 'false') {
|
|
45
|
+
return { path, action: 'skipped', reason: 'auto:false' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rendered = await computeIndex(dir)
|
|
49
|
+
if (!rendered.ok) {
|
|
50
|
+
return {
|
|
51
|
+
path,
|
|
52
|
+
action: 'error',
|
|
53
|
+
reason: 'frontmatter',
|
|
54
|
+
errors: rendered.errors,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (rendered.content === (await Bun.file(path).text())) {
|
|
59
|
+
return { path, action: 'unchanged' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options.dryRun) {
|
|
63
|
+
return { path, action: 'would-write' }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await Bun.write(path, rendered.content)
|
|
67
|
+
return { path, action: 'written' }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maps a run's results onto the documented exit codes: 0 clean, 1 error,
|
|
72
|
+
* 2 drift found under `--dry-run`. Skills branch on the JSON instead, but the
|
|
73
|
+
* codes are load-bearing for `bun run check`.
|
|
74
|
+
*/
|
|
75
|
+
export function exitCodeFor(
|
|
76
|
+
results: readonly RegenResult[],
|
|
77
|
+
options: RegenOptions,
|
|
78
|
+
): 0 | 1 | 2 {
|
|
79
|
+
if (results.some((result) => result.action === 'error')) return 1
|
|
80
|
+
if (options.dryRun && results.some((r) => r.action === 'would-write')) {
|
|
81
|
+
return 2
|
|
82
|
+
}
|
|
83
|
+
return 0
|
|
84
|
+
}
|