@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,692 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { basename, join, sep } from 'node:path'
|
|
3
|
+
import { execa } from 'execa'
|
|
4
|
+
import { gitEnv } from '@/git-env'
|
|
5
|
+
import { createGovAdapter, rulesSourceDir } from '@/gov/adapter'
|
|
6
|
+
import { loadGovStack, resolveMissingRules, resolveRules } from '@/gov/stacks'
|
|
7
|
+
import { planSync, type ScanEntry, type SyncAdapter } from '@/sync/engine'
|
|
8
|
+
import {
|
|
9
|
+
collectSuperseded,
|
|
10
|
+
detectUnmigrated,
|
|
11
|
+
type SupersededEntry,
|
|
12
|
+
type UnmigratedDomain,
|
|
13
|
+
} from '@/sync/layout'
|
|
14
|
+
import {
|
|
15
|
+
buildReverseReport,
|
|
16
|
+
emptyReverseReport,
|
|
17
|
+
type ReverseReport,
|
|
18
|
+
} from '@/sync/reverse'
|
|
19
|
+
import { buildSeedsReport, type SeedsReport } from '@/sync/seeds-report'
|
|
20
|
+
import {
|
|
21
|
+
isLegacyStamped,
|
|
22
|
+
readStamp,
|
|
23
|
+
type Stamp,
|
|
24
|
+
stampedChain,
|
|
25
|
+
stampedCommit,
|
|
26
|
+
type StampDomain,
|
|
27
|
+
} from '@/sync/stamp'
|
|
28
|
+
import { isDirectory } from '@/target'
|
|
29
|
+
import { loadManifest } from '@/tooling/manifest'
|
|
30
|
+
import { scan } from '@/tooling/scan'
|
|
31
|
+
import { readSkew, type SkewReport } from '@/version/skew'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Domains the sync engine walks file by file. Tooling is a stamp domain without
|
|
35
|
+
* being one of these, because `src/tooling/` never calls `planSync`, so the
|
|
36
|
+
* three lookups below have no entry to offer it. Standards and snippets left
|
|
37
|
+
* the list with their install channels: nothing writes either corpus into a
|
|
38
|
+
* target, so there is no installed copy to attribute.
|
|
39
|
+
*/
|
|
40
|
+
export const SCANNED_DOMAINS = [
|
|
41
|
+
'governance',
|
|
42
|
+
] as const satisfies readonly StampDomain[]
|
|
43
|
+
|
|
44
|
+
export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The toolkit path whose commits change what each domain holds. `claude/skills/`
|
|
48
|
+
* is deliberately absent: skills load live from the plugin directory, so they
|
|
49
|
+
* never go stale and belong in the read-only section instead.
|
|
50
|
+
*/
|
|
51
|
+
const SYNCED_SOURCES: Record<ScannedDomain, string> = {
|
|
52
|
+
governance: 'governance/rules/',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ADAPTERS: Record<ScannedDomain, (root: string) => SyncAdapter> = {
|
|
56
|
+
governance: createGovAdapter,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const INSTALL_MARKERS: Record<ScannedDomain, readonly string[]> = {
|
|
60
|
+
governance: ['.claude', 'rules'],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface StateCounts {
|
|
64
|
+
readonly matching: number
|
|
65
|
+
readonly stale: number
|
|
66
|
+
readonly customized: number
|
|
67
|
+
readonly drifted: number
|
|
68
|
+
readonly orphaned: number
|
|
69
|
+
readonly stranded: number
|
|
70
|
+
readonly missing: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DomainReport {
|
|
74
|
+
readonly domain: ScannedDomain
|
|
75
|
+
readonly stamped: boolean
|
|
76
|
+
/** This domain's own anchor, not the target's most recent sync. */
|
|
77
|
+
readonly commit?: string
|
|
78
|
+
readonly syncedAt?: string
|
|
79
|
+
readonly counts: StateCounts
|
|
80
|
+
readonly entries: readonly ScanEntry[]
|
|
81
|
+
readonly upstream: readonly UpstreamCommit[]
|
|
82
|
+
/**
|
|
83
|
+
* Separates a toolkit that could not attribute from one that attributed and
|
|
84
|
+
* found a local edit. Only the first is a capability the install lacks.
|
|
85
|
+
*/
|
|
86
|
+
readonly historyUnavailable: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface UpstreamCommit {
|
|
90
|
+
readonly sha: string
|
|
91
|
+
readonly subject: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Pending changes per category, from the same scan `canon tooling sync` reads. */
|
|
95
|
+
export interface ToolingCounts {
|
|
96
|
+
readonly configs: number
|
|
97
|
+
readonly seeds: number
|
|
98
|
+
readonly scripts: number
|
|
99
|
+
readonly deps: number
|
|
100
|
+
readonly gitignore: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Tooling's own section. `measured` is the field the report exists for: without
|
|
105
|
+
* it a target that never installed tooling and a target whose tooling is current
|
|
106
|
+
* both render as zero changes, which is a claim rather than an absence of one.
|
|
107
|
+
*/
|
|
108
|
+
export interface ToolingReport {
|
|
109
|
+
readonly measured: boolean
|
|
110
|
+
/**
|
|
111
|
+
* Stack names the install resolved, nearest first. Carried even when the
|
|
112
|
+
* report is unmeasured, so a chain naming stacks this toolkit no longer ships
|
|
113
|
+
* stays distinguishable from a target that recorded none.
|
|
114
|
+
*/
|
|
115
|
+
readonly chain: readonly string[]
|
|
116
|
+
readonly commit?: string
|
|
117
|
+
readonly syncedAt?: string
|
|
118
|
+
readonly counts: ToolingCounts
|
|
119
|
+
readonly changes: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const UNMEASURED_TOOLING: ToolingReport = {
|
|
123
|
+
measured: false,
|
|
124
|
+
chain: [],
|
|
125
|
+
counts: {
|
|
126
|
+
configs: 0,
|
|
127
|
+
seeds: 0,
|
|
128
|
+
scripts: 0,
|
|
129
|
+
deps: 0,
|
|
130
|
+
gitignore: 0,
|
|
131
|
+
},
|
|
132
|
+
changes: 0,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CheckReport {
|
|
136
|
+
readonly covers: readonly StampDomain[]
|
|
137
|
+
/**
|
|
138
|
+
* True when the stamp `readStamp` found sits at one of the retired paths
|
|
139
|
+
* rather than the current one. Read only: nothing in the check migrates a
|
|
140
|
+
* target's config as a side effect of reporting it.
|
|
141
|
+
*
|
|
142
|
+
* canon-keep-retired
|
|
143
|
+
* The two are `.claude/aitk/config.json` and `.claude/aitk.json`.
|
|
144
|
+
*/
|
|
145
|
+
readonly stampAtLegacyPath: boolean
|
|
146
|
+
/** False when the target is not a toolkit project, so every section stays empty. */
|
|
147
|
+
readonly managed: boolean
|
|
148
|
+
readonly domains: readonly DomainReport[]
|
|
149
|
+
/**
|
|
150
|
+
* Reported beside the domains rather than as one of them, because tooling
|
|
151
|
+
* carries no per-file entries and no upstream range, so it fills almost none
|
|
152
|
+
* of `DomainReport`.
|
|
153
|
+
*/
|
|
154
|
+
readonly tooling: ToolingReport
|
|
155
|
+
/**
|
|
156
|
+
* Reported beside the domains rather than as one of them, because seeds carry
|
|
157
|
+
* no stamp and produce no change. See `@/sync/seeds-report`.
|
|
158
|
+
*/
|
|
159
|
+
readonly seeds: SeedsReport
|
|
160
|
+
readonly superseded: readonly SupersededEntry[]
|
|
161
|
+
readonly unmigrated: readonly UnmigratedDomain[]
|
|
162
|
+
readonly newSkills: readonly string[]
|
|
163
|
+
/**
|
|
164
|
+
* Rules the toolkit authored after this target's governance anchor, filtered
|
|
165
|
+
* to what the target could receive. It rides beside `newSkills` rather than
|
|
166
|
+
* folding into it because the two answer the same question about different
|
|
167
|
+
* corpora and a reader acting on one runs a different command from the other.
|
|
168
|
+
*/
|
|
169
|
+
readonly newRules: readonly string[]
|
|
170
|
+
/**
|
|
171
|
+
* The one section built by walking the target rather than the catalog. It
|
|
172
|
+
* reports beside `superseded`, `unmigrated`, and `newSkills` rather than
|
|
173
|
+
* absorbing them, because each of those already answers a narrower version of
|
|
174
|
+
* the same question correctly. See `@/sync/reverse`.
|
|
175
|
+
*/
|
|
176
|
+
readonly reverse: ReverseReport
|
|
177
|
+
/**
|
|
178
|
+
* The binary running the check, not the target. It reports on an unmanaged
|
|
179
|
+
* target too, since a reader told to run `canon init` is better off knowing
|
|
180
|
+
* first whether the binary about to install is the current one.
|
|
181
|
+
*
|
|
182
|
+
* `hasDrift` deliberately ignores it. A registry lookup inside a check that
|
|
183
|
+
* gates would fail CI on an offline machine for a condition the check never
|
|
184
|
+
* measured, and the state reaching the reader is the point rather than the
|
|
185
|
+
* exit code.
|
|
186
|
+
*/
|
|
187
|
+
readonly skew: SkewReport
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function installedStampDomains(target: string): ScannedDomain[] {
|
|
191
|
+
return SCANNED_DOMAINS.filter((domain) =>
|
|
192
|
+
isDirectory(join(target, ...INSTALL_MARKERS[domain])),
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Reads the chain the install recorded and scans against those stacks rather
|
|
198
|
+
* than re-resolving from the leaf. A run that passed `--skip` installed fewer
|
|
199
|
+
* layers than the leaf's own chain reproduces, so re-resolving would report
|
|
200
|
+
* drift against a layer the target deliberately does not carry.
|
|
201
|
+
*
|
|
202
|
+
* A recorded stack the toolkit no longer ships resolves to nothing, and a chain
|
|
203
|
+
* where none resolve reads as unmeasured. Scanning the survivors would measure
|
|
204
|
+
* against a chain neither side agrees on.
|
|
205
|
+
*/
|
|
206
|
+
export function buildToolingReport(
|
|
207
|
+
toolkitRoot: string,
|
|
208
|
+
target: string,
|
|
209
|
+
stamp: Stamp | undefined,
|
|
210
|
+
): ToolingReport {
|
|
211
|
+
const chain = stampedChain(stamp, 'tooling')
|
|
212
|
+
const manifests = chain
|
|
213
|
+
.map((name) => loadManifest(toolkitRoot, name))
|
|
214
|
+
.filter((manifest) => manifest !== undefined)
|
|
215
|
+
|
|
216
|
+
if (manifests.length === 0) return { ...UNMEASURED_TOOLING, chain }
|
|
217
|
+
|
|
218
|
+
const result = scan(manifests, target)
|
|
219
|
+
const record = stamp?.domains.tooling
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
measured: true,
|
|
223
|
+
chain,
|
|
224
|
+
commit: record?.commit,
|
|
225
|
+
syncedAt: record?.syncedAt,
|
|
226
|
+
counts: {
|
|
227
|
+
configs: result.configs.filter((entry) => entry.state !== 'matching')
|
|
228
|
+
.length,
|
|
229
|
+
seeds: result.seeds.filter((entry) => entry.state === 'missing').length,
|
|
230
|
+
scripts: result.scripts.filter((entry) => entry.state !== 'matching')
|
|
231
|
+
.length,
|
|
232
|
+
deps: result.deps.filter((entry) => entry.state === 'missing').length,
|
|
233
|
+
gitignore: result.gitignore.filter((entry) => entry.state === 'missing')
|
|
234
|
+
.length,
|
|
235
|
+
},
|
|
236
|
+
changes: result.totalChanges,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Whether the target is a toolkit-managed project at all. Seeds are enumerated
|
|
242
|
+
* from the source rather than from what a target installed, so without this gate
|
|
243
|
+
* a directory the toolkit has never touched reports every seed as `missing` and
|
|
244
|
+
* routes to a skill that reconciles section by section. `installedStampDomains`
|
|
245
|
+
* gates the three scanned domains the same way, which is why they stay quiet on
|
|
246
|
+
* the same directory.
|
|
247
|
+
*
|
|
248
|
+
* An unmigrated domain counts as a marker in its own right. `detectUnmigrated`
|
|
249
|
+
* fires only on root files whose basename the toolkit ships, so it firing proves
|
|
250
|
+
* the toolkit installed here before the layout moved under `.claude/`. Reading
|
|
251
|
+
* only the markers would report such a target as unmanaged while the same report
|
|
252
|
+
* carried its unmigrated domain, and a consumer reading the JSON would route to
|
|
253
|
+
* the relocation while the rendered half routed to install.
|
|
254
|
+
*/
|
|
255
|
+
export function isManagedTarget(
|
|
256
|
+
target: string,
|
|
257
|
+
unmigrated: readonly UnmigratedDomain[],
|
|
258
|
+
): boolean {
|
|
259
|
+
if (unmigrated.length > 0) return true
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
isDirectory(join(target, '.claude')) ||
|
|
263
|
+
existsSync(join(target, 'CLAUDE.md'))
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function countStates(entries: readonly ScanEntry[]): StateCounts {
|
|
268
|
+
return {
|
|
269
|
+
matching: count(entries, 'matching'),
|
|
270
|
+
stale: count(entries, 'stale'),
|
|
271
|
+
customized: count(entries, 'customized'),
|
|
272
|
+
drifted: count(entries, 'drifted'),
|
|
273
|
+
orphaned: count(entries, 'orphaned'),
|
|
274
|
+
stranded: count(entries, 'stranded'),
|
|
275
|
+
missing: count(entries, 'missing'),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Whether the target has diverged from the toolkit in a way a sync could close.
|
|
281
|
+
* Orphaned files are excluded: a project-authored rule never converges, and
|
|
282
|
+
* counting it would leave `--exit-code` failing forever with no remedy.
|
|
283
|
+
*
|
|
284
|
+
* An unmigrated domain counts, because running the relocation closes it. A
|
|
285
|
+
* superseded artifact does not, for the same reason orphaned files do not: only
|
|
286
|
+
* the user can move content they wrote, so failing a job on it leaves the job
|
|
287
|
+
* red with no mechanical remedy. Seeds are excluded on the same grounds, since
|
|
288
|
+
* every seed a project edits would otherwise fail the check forever.
|
|
289
|
+
*
|
|
290
|
+
* Tooling is excluded on exactly the seeds grounds: a golden config is one the
|
|
291
|
+
* project is expected to edit, so a job counting it stays red with no remedy.
|
|
292
|
+
* Being unmeasured is not what excludes it, since an unmeasured report carries
|
|
293
|
+
* zero changes and would pass a count either way.
|
|
294
|
+
*
|
|
295
|
+
* The reverse report is excluded because every entry in it is a judgment about
|
|
296
|
+
* a file the project may own. `detectUnmigrated` already shipped that exact
|
|
297
|
+
* false positive once, failing a push with no action that cleared it, and a
|
|
298
|
+
* walk that reports `unattributed` by design would repeat it.
|
|
299
|
+
*
|
|
300
|
+
* `missing` is excluded on the same grounds `newRules` already reports on: a
|
|
301
|
+
* sync that adds a rule silently changes what a project is governed by, and
|
|
302
|
+
* nobody chose that, so gating CI on the count would pressure a target into
|
|
303
|
+
* adopting a rule nobody picked.
|
|
304
|
+
*/
|
|
305
|
+
export function hasDrift(report: CheckReport): boolean {
|
|
306
|
+
if (report.unmigrated.length > 0) return true
|
|
307
|
+
|
|
308
|
+
return report.domains.some(
|
|
309
|
+
(domain) =>
|
|
310
|
+
domain.counts.stale +
|
|
311
|
+
domain.counts.customized +
|
|
312
|
+
domain.counts.drifted +
|
|
313
|
+
domain.counts.stranded >
|
|
314
|
+
0,
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Bounds each domain's upstream read by that domain's own anchor and its own
|
|
320
|
+
* source path. A shared anchor would let a gov sync advance the revision
|
|
321
|
+
* snippets measures from, silently dropping a snippets change out of the read.
|
|
322
|
+
*/
|
|
323
|
+
export async function buildCheckReport(
|
|
324
|
+
toolkitRoot: string,
|
|
325
|
+
target: string,
|
|
326
|
+
): Promise<CheckReport> {
|
|
327
|
+
const stamp = readStamp(target)
|
|
328
|
+
|
|
329
|
+
// Started before the local scan and awaited after it, so the network wait
|
|
330
|
+
// overlaps work the report needs anyway rather than adding to it.
|
|
331
|
+
const skewRead = readSkew()
|
|
332
|
+
|
|
333
|
+
const domains = await Promise.all(
|
|
334
|
+
installedStampDomains(target).map((domain) =>
|
|
335
|
+
buildDomainReport(toolkitRoot, target, stamp, domain),
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
const anchors = domains
|
|
340
|
+
.map((domain) => domain.commit)
|
|
341
|
+
.filter((commit): commit is string => commit !== undefined)
|
|
342
|
+
|
|
343
|
+
const unmigrated = detectUnmigrated(toolkitRoot, target)
|
|
344
|
+
const managed = isManagedTarget(target, unmigrated)
|
|
345
|
+
|
|
346
|
+
if (!managed) {
|
|
347
|
+
return {
|
|
348
|
+
covers: [],
|
|
349
|
+
stampAtLegacyPath: isLegacyStamped(target),
|
|
350
|
+
managed,
|
|
351
|
+
domains: [],
|
|
352
|
+
tooling: UNMEASURED_TOOLING,
|
|
353
|
+
seeds: { entries: [], historyUnavailable: false },
|
|
354
|
+
superseded: [],
|
|
355
|
+
unmigrated: [],
|
|
356
|
+
newSkills: [],
|
|
357
|
+
newRules: [],
|
|
358
|
+
reverse: emptyReverseReport(),
|
|
359
|
+
skew: await skewRead,
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
covers: stamp?.covers ?? [],
|
|
365
|
+
stampAtLegacyPath: isLegacyStamped(target),
|
|
366
|
+
managed,
|
|
367
|
+
domains,
|
|
368
|
+
tooling: buildToolingReport(toolkitRoot, target, stamp),
|
|
369
|
+
seeds: buildSeedsReport(toolkitRoot, target),
|
|
370
|
+
superseded: collectSuperseded(target),
|
|
371
|
+
unmigrated,
|
|
372
|
+
newSkills: await readNewSkills(toolkitRoot, anchors),
|
|
373
|
+
newRules: await readNewRules(toolkitRoot, target, stamp),
|
|
374
|
+
reverse: buildReverseReport(toolkitRoot, target),
|
|
375
|
+
skew: await skewRead,
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function buildDomainReport(
|
|
380
|
+
toolkitRoot: string,
|
|
381
|
+
target: string,
|
|
382
|
+
stamp: Stamp | undefined,
|
|
383
|
+
domain: ScannedDomain,
|
|
384
|
+
): Promise<DomainReport> {
|
|
385
|
+
const plan = planSync(ADAPTERS[domain](toolkitRoot), target)
|
|
386
|
+
const record = stamp?.domains[domain]
|
|
387
|
+
const since = stampedCommit(stamp, domain)
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
domain,
|
|
391
|
+
stamped: record !== undefined,
|
|
392
|
+
commit: since,
|
|
393
|
+
syncedAt: record?.syncedAt,
|
|
394
|
+
counts: countStates(plan.entries),
|
|
395
|
+
entries: plan.entries,
|
|
396
|
+
historyUnavailable: plan.historyUnavailable,
|
|
397
|
+
upstream:
|
|
398
|
+
since === undefined
|
|
399
|
+
? []
|
|
400
|
+
: await readUpstream(toolkitRoot, since, SYNCED_SOURCES[domain]),
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function parseUpstream(log: string): UpstreamCommit[] {
|
|
405
|
+
const commits: UpstreamCommit[] = []
|
|
406
|
+
|
|
407
|
+
for (const line of log.split('\n')) {
|
|
408
|
+
const trimmed = line.trim()
|
|
409
|
+
if (trimmed === '') continue
|
|
410
|
+
|
|
411
|
+
const boundary = trimmed.indexOf(' ')
|
|
412
|
+
if (boundary === -1) continue
|
|
413
|
+
|
|
414
|
+
commits.push({
|
|
415
|
+
sha: trimmed.slice(0, boundary),
|
|
416
|
+
subject: trimmed.slice(boundary + 1),
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return commits
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** A skill is new when its `SKILL.md` was added, not when a support file was. */
|
|
424
|
+
export function parseNewSkills(paths: string): string[] {
|
|
425
|
+
const names = paths
|
|
426
|
+
.split('\n')
|
|
427
|
+
.map((line) => line.trim())
|
|
428
|
+
.filter((line) => line.endsWith('/SKILL.md'))
|
|
429
|
+
.map((line) => line.split('/').at(-2))
|
|
430
|
+
.filter((name): name is string => name !== undefined)
|
|
431
|
+
|
|
432
|
+
return [...new Set(names)].sort()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* What the target's `.claude/rules/` proves about its entitlement. `held` is
|
|
437
|
+
* every rule name it carries and `bands` every band folder those names sit in.
|
|
438
|
+
*
|
|
439
|
+
* Read off the installed tree rather than off a stack name, because
|
|
440
|
+
* `gov install` records file hashes and never the stack it resolved, so the
|
|
441
|
+
* chain a target consumed survives nowhere else. `installRules` copies each
|
|
442
|
+
* rule into the subdirectory it was authored in, which is what makes the band
|
|
443
|
+
* folders legible as evidence.
|
|
444
|
+
*/
|
|
445
|
+
export function readInstalledRules(target: string): {
|
|
446
|
+
held: Set<string>
|
|
447
|
+
bands: Set<string>
|
|
448
|
+
} {
|
|
449
|
+
const dir = join(target, ...INSTALL_MARKERS.governance)
|
|
450
|
+
const held = new Set<string>()
|
|
451
|
+
const bands = new Set<string>()
|
|
452
|
+
if (!isDirectory(dir)) return { held, bands }
|
|
453
|
+
|
|
454
|
+
for (const rel of new Bun.Glob('**/*.md').scanSync({
|
|
455
|
+
cwd: dir,
|
|
456
|
+
onlyFiles: true,
|
|
457
|
+
dot: true,
|
|
458
|
+
})) {
|
|
459
|
+
const posix = rel.split(sep).join('/')
|
|
460
|
+
const boundary = posix.indexOf('/')
|
|
461
|
+
|
|
462
|
+
held.add(basename(posix, '.md'))
|
|
463
|
+
if (boundary > 0) bands.add(posix.slice(0, boundary))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return { held, bands }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Narrows rules added upstream to the ones this target could receive and does
|
|
471
|
+
* not already hold.
|
|
472
|
+
*
|
|
473
|
+
* The band test is the entitlement filter, and `bands` carries two sources: the
|
|
474
|
+
* folders the target already holds, and the folders the base stack takes whole.
|
|
475
|
+
* A rule authored under `lang/` or `ui/` is named by an individual stack, so it
|
|
476
|
+
* belongs to some targets and not others, and listing every added file would
|
|
477
|
+
* tell a base consumer about rules it was never entitled to. The test
|
|
478
|
+
* over-reports inside a band a target already carries, since one folder can be
|
|
479
|
+
* reached by more than one stack, and that costs a line where under-reporting
|
|
480
|
+
* would cost the whole point of the section.
|
|
481
|
+
*
|
|
482
|
+
* The `held` test is what keeps a rule that moved bands upstream out. A rename
|
|
483
|
+
* reaches this diff as an addition, and the target already has the file under
|
|
484
|
+
* its old folder, so matching by name is what tells the two apart.
|
|
485
|
+
*/
|
|
486
|
+
export function selectNewRules(
|
|
487
|
+
paths: string,
|
|
488
|
+
held: ReadonlySet<string>,
|
|
489
|
+
bands: ReadonlySet<string>,
|
|
490
|
+
): string[] {
|
|
491
|
+
const prefix = 'governance/rules/'
|
|
492
|
+
const names = new Set<string>()
|
|
493
|
+
|
|
494
|
+
for (const line of paths.split('\n')) {
|
|
495
|
+
const trimmed = line.trim()
|
|
496
|
+
if (!trimmed.startsWith(prefix) || !trimmed.endsWith('.md')) continue
|
|
497
|
+
|
|
498
|
+
const rel = trimmed.slice(prefix.length)
|
|
499
|
+
const boundary = rel.indexOf('/')
|
|
500
|
+
const band = boundary === -1 ? '' : rel.slice(0, boundary)
|
|
501
|
+
const name = basename(rel, '.md')
|
|
502
|
+
|
|
503
|
+
if (held.has(name)) continue
|
|
504
|
+
if (band !== '' && !bands.has(band)) continue
|
|
505
|
+
|
|
506
|
+
names.add(name)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return [...names].sort()
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Band folders the base stack takes whole. Every governance stack extends base,
|
|
514
|
+
* so a rule authored under one of these is entitled to every target.
|
|
515
|
+
*
|
|
516
|
+
* This is what covers a band no target can carry yet. Entitlement is otherwise
|
|
517
|
+
* read off folders the target already holds, and a folder added to base later
|
|
518
|
+
* exists in no installed tree, so without this the rules inside it would reach
|
|
519
|
+
* nobody. Read from the stack file rather than fixed, so that addition needs no
|
|
520
|
+
* code change here.
|
|
521
|
+
*
|
|
522
|
+
* A folder only a leaf stack names is deliberately absent. It is entitled to
|
|
523
|
+
* some targets and not others, which is the distinction the band test makes and
|
|
524
|
+
* the installed tree is the only evidence of.
|
|
525
|
+
*/
|
|
526
|
+
export function baseBands(root: string): Set<string> {
|
|
527
|
+
const stack = loadGovStack(root, 'base')
|
|
528
|
+
if (stack === undefined) return new Set()
|
|
529
|
+
|
|
530
|
+
return new Set(
|
|
531
|
+
stack.rules.filter((entry) =>
|
|
532
|
+
isDirectory(join(rulesSourceDir(root), entry)),
|
|
533
|
+
),
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* A recorded chain answers this without the anchor at all: `resolveMissingRules`
|
|
539
|
+
* compares the entitled set against what the target holds right now, so a rule
|
|
540
|
+
* that shipped before the target's anchor is not a permanent blind spot the
|
|
541
|
+
* way the diff below leaves it. This is the primary path, and it is also what
|
|
542
|
+
* `collectMissing` reports per file through the domain scan, so a rule the
|
|
543
|
+
* chain names and the target lacks reaches both surfaces the same way.
|
|
544
|
+
*
|
|
545
|
+
* The diff-and-bands path stays as the fallback for a target stamped before
|
|
546
|
+
* governance recorded a chain. Rules are domain-scoped there too, so it
|
|
547
|
+
* measures from governance's own anchor rather than from the oldest anchor
|
|
548
|
+
* across domains the way `readNewSkills` does. A shared anchor would let
|
|
549
|
+
* another domain's sync move the revision rules are measured from and drop a
|
|
550
|
+
* rule out of the read.
|
|
551
|
+
*
|
|
552
|
+
* A target carrying no chain and no governance anchor reports nothing. It has
|
|
553
|
+
* no date to measure against, and diffing from the beginning of history would
|
|
554
|
+
* read every rule the toolkit ships as new.
|
|
555
|
+
*
|
|
556
|
+
* An anchor this clone cannot resolve reports nothing by a different route and
|
|
557
|
+
* says so nowhere. `read` yields an empty string on a non-zero exit, so a stamp
|
|
558
|
+
* naming a revision a registry install or a shallow clone has never seen reads
|
|
559
|
+
* as a target holding everything. `readNewSkills` carries the same gap, and
|
|
560
|
+
* neither has the `historyUnavailable` flag the per-domain scan uses to tell an
|
|
561
|
+
* unmeasured result from a clean one.
|
|
562
|
+
*
|
|
563
|
+
* A recorded chain naming a stack the toolkit no longer ships falls through to
|
|
564
|
+
* the band-based path below rather than reporting the empty list an
|
|
565
|
+
* unresolved chain would otherwise produce. That empty list reads exactly
|
|
566
|
+
* like a target holding everything, which is the same failure this function
|
|
567
|
+
* exists to close, so a retired stack name is read the same as no chain at
|
|
568
|
+
* all instead of reintroducing it.
|
|
569
|
+
*/
|
|
570
|
+
export async function readNewRules(
|
|
571
|
+
root: string,
|
|
572
|
+
target: string,
|
|
573
|
+
stamp: Stamp | undefined,
|
|
574
|
+
): Promise<string[]> {
|
|
575
|
+
const chain = stampedChain(stamp, 'governance')
|
|
576
|
+
const stack = chain[0]
|
|
577
|
+
|
|
578
|
+
if (stack !== undefined && resolveRules(root, stack).ok) {
|
|
579
|
+
return resolveMissingRules(root, target, chain)
|
|
580
|
+
.map((source) => source.rule)
|
|
581
|
+
.sort()
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const since = stampedCommit(stamp, 'governance')
|
|
585
|
+
if (since === undefined) return []
|
|
586
|
+
|
|
587
|
+
const paths = await read(root, [
|
|
588
|
+
'diff',
|
|
589
|
+
'--name-only',
|
|
590
|
+
'--diff-filter=A',
|
|
591
|
+
`${since}..HEAD`,
|
|
592
|
+
'--',
|
|
593
|
+
'governance/rules/',
|
|
594
|
+
])
|
|
595
|
+
|
|
596
|
+
const { held, bands } = readInstalledRules(target)
|
|
597
|
+
return selectNewRules(paths, held, bands.union(baseBands(root)))
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function readUpstream(
|
|
601
|
+
root: string,
|
|
602
|
+
since: string,
|
|
603
|
+
sourcePath: string,
|
|
604
|
+
): Promise<UpstreamCommit[]> {
|
|
605
|
+
const log = await read(root, [
|
|
606
|
+
'log',
|
|
607
|
+
'--oneline',
|
|
608
|
+
'--no-decorate',
|
|
609
|
+
`${since}..HEAD`,
|
|
610
|
+
'--',
|
|
611
|
+
sourcePath,
|
|
612
|
+
])
|
|
613
|
+
|
|
614
|
+
return parseUpstream(log)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Skills are not domain-scoped, so the read runs from the oldest anchor across
|
|
619
|
+
* domains. Over-reporting a skill costs a line, while measuring from the newest
|
|
620
|
+
* would hide one that arrived before the most recent domain sync.
|
|
621
|
+
*/
|
|
622
|
+
async function readNewSkills(
|
|
623
|
+
root: string,
|
|
624
|
+
anchors: readonly string[],
|
|
625
|
+
): Promise<string[]> {
|
|
626
|
+
const since = await oldestAnchor(root, anchors)
|
|
627
|
+
if (since === undefined) return []
|
|
628
|
+
|
|
629
|
+
const paths = await read(root, [
|
|
630
|
+
'diff',
|
|
631
|
+
'--name-only',
|
|
632
|
+
'--diff-filter=A',
|
|
633
|
+
`${since}..HEAD`,
|
|
634
|
+
'--',
|
|
635
|
+
'claude/skills/',
|
|
636
|
+
])
|
|
637
|
+
|
|
638
|
+
return parseNewSkills(paths)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function oldestAnchor(
|
|
642
|
+
root: string,
|
|
643
|
+
anchors: readonly string[],
|
|
644
|
+
): Promise<string | undefined> {
|
|
645
|
+
const unique = [...new Set(anchors)]
|
|
646
|
+
let oldest = unique[0]
|
|
647
|
+
|
|
648
|
+
for (const candidate of unique.slice(1)) {
|
|
649
|
+
if (await isAncestor(root, candidate, oldest)) oldest = candidate
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return oldest
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function isAncestor(
|
|
656
|
+
root: string,
|
|
657
|
+
candidate: string,
|
|
658
|
+
reference: string,
|
|
659
|
+
): Promise<boolean> {
|
|
660
|
+
const result = await execa(
|
|
661
|
+
'git',
|
|
662
|
+
['-C', root, 'merge-base', '--is-ancestor', candidate, reference],
|
|
663
|
+
{ reject: false, env: gitEnv(), extendEnv: false },
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
return result.exitCode === 0
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* A toolkit outside a git clone, or a stamped revision this clone has never
|
|
671
|
+
* seen, yields no range. The per-file report still stands on its own.
|
|
672
|
+
*
|
|
673
|
+
* Scrubbed through `gitEnv` because a git hook exports `GIT_DIR` and its
|
|
674
|
+
* siblings into every process it runs and they outrank `-C`. A check invoked
|
|
675
|
+
* from a hook would otherwise diff the hook's repository and report a range for
|
|
676
|
+
* a tree nobody asked about, which reads as an ordinary answer.
|
|
677
|
+
*/
|
|
678
|
+
async function read(root: string, args: readonly string[]): Promise<string> {
|
|
679
|
+
const result = await execa('git', ['-C', root, ...args], {
|
|
680
|
+
reject: false,
|
|
681
|
+
env: gitEnv(),
|
|
682
|
+
extendEnv: false,
|
|
683
|
+
})
|
|
684
|
+
return result.exitCode === 0 ? result.stdout : ''
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function count(
|
|
688
|
+
entries: readonly ScanEntry[],
|
|
689
|
+
state: ScanEntry['state'],
|
|
690
|
+
): number {
|
|
691
|
+
return entries.filter((entry) => entry.state === state).length
|
|
692
|
+
}
|