@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
package/src/sync/git.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { basename } from 'node:path'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import { gitEnv } from '@/git-env'
|
|
4
|
+
import type { SyncDomain } from '@/sync/target'
|
|
5
|
+
import { pipeOutput } from '@/ui'
|
|
6
|
+
|
|
7
|
+
const FILE_LIST_MAX = 3
|
|
8
|
+
|
|
9
|
+
export type DomainVerb = 'Add' | 'Remove' | 'Update'
|
|
10
|
+
|
|
11
|
+
export interface DomainChange {
|
|
12
|
+
readonly domain: SyncDomain
|
|
13
|
+
readonly verb: DomainVerb
|
|
14
|
+
readonly names: readonly string[]
|
|
15
|
+
readonly paths: readonly string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class GitCommandError extends Error {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pulls the path out of each porcelain line by taking the last whitespace
|
|
22
|
+
* separated field, which is what the bash `awk '{print $NF}'` did. A rename
|
|
23
|
+
* reports `old -> new` and so yields the destination, and a sync never
|
|
24
|
+
* produces one because the workflow refuses a dirty tree before it starts.
|
|
25
|
+
*/
|
|
26
|
+
export function changedPaths(status: string): string[] {
|
|
27
|
+
const paths: string[] = []
|
|
28
|
+
|
|
29
|
+
for (const line of status.split('\n')) {
|
|
30
|
+
const trimmed = line.trim()
|
|
31
|
+
if (trimmed === '') continue
|
|
32
|
+
const fields = trimmed.split(/\s+/)
|
|
33
|
+
paths.push(fields[fields.length - 1])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return paths
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function changedNames(status: string): string[] {
|
|
40
|
+
const names = changedPaths(status).map((path) => basename(path))
|
|
41
|
+
return [...new Set(names)].sort()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Classifies a domain's porcelain output as one verb for the pull request body.
|
|
46
|
+
* Only an all-untracked change reads as `Add` and only an all-deleted one as
|
|
47
|
+
* `Remove`, so any mix falls through to `Update`.
|
|
48
|
+
*/
|
|
49
|
+
export function classifyStatus(status: string): DomainVerb {
|
|
50
|
+
let hasModify = false
|
|
51
|
+
let hasNew = false
|
|
52
|
+
let hasDelete = false
|
|
53
|
+
|
|
54
|
+
for (const line of status.split('\n')) {
|
|
55
|
+
if (line === '') continue
|
|
56
|
+
const code = line.slice(0, 2)
|
|
57
|
+
|
|
58
|
+
if (code === '??') hasNew = true
|
|
59
|
+
else if (code === ' D' || code === 'D ' || code === 'DD') hasDelete = true
|
|
60
|
+
else hasModify = true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!hasModify && !hasDelete && hasNew) return 'Add'
|
|
64
|
+
if (!hasModify && !hasNew && hasDelete) return 'Remove'
|
|
65
|
+
return 'Update'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatFileList(files: readonly string[]): string {
|
|
69
|
+
if (files.length === 0) return ''
|
|
70
|
+
if (files.length <= FILE_LIST_MAX) return files.join(', ')
|
|
71
|
+
|
|
72
|
+
const head = files.slice(0, FILE_LIST_MAX).join(', ')
|
|
73
|
+
return `${head}, and ${files.length - FILE_LIST_MAX} more`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Names the branch at minute resolution, so two syncs inside the same minute
|
|
78
|
+
* collide by design and the collision check turns the second one away.
|
|
79
|
+
*/
|
|
80
|
+
export function syncBranchName(now: Date): string {
|
|
81
|
+
const pad = (value: number): string => String(value).padStart(2, '0')
|
|
82
|
+
const day = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
|
|
83
|
+
const minute = `${pad(now.getHours())}${pad(now.getMinutes())}`
|
|
84
|
+
|
|
85
|
+
return `chore/canon-sync-${day}-${minute}`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function commitMessage(domains: readonly SyncDomain[]): string {
|
|
89
|
+
return `chore(sync): update ${domains.join(', ')} from toolkit`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function pullRequestBody(changes: readonly DomainChange[]): string {
|
|
93
|
+
const domains = changes.map((change) => change.domain).join(', ')
|
|
94
|
+
const lines = [
|
|
95
|
+
'## Summary',
|
|
96
|
+
'',
|
|
97
|
+
`Sync ${domains} from toolkit.`,
|
|
98
|
+
'',
|
|
99
|
+
'## Key Changes',
|
|
100
|
+
'',
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
for (const change of changes) {
|
|
104
|
+
lines.push(
|
|
105
|
+
`- ${change.verb} \`${change.domain}/\` ${formatFileList(change.names)}.`,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return lines.join('\n')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The seam every git mutation goes through, so the workflow can be driven by a
|
|
114
|
+
* fake in tests without a repository behind it. Reads swallow failure the way
|
|
115
|
+
* the bash `2>/dev/null || true` did. Mutations throw, because a failed commit
|
|
116
|
+
* or push must not be reported as a completed sync.
|
|
117
|
+
*/
|
|
118
|
+
export interface GitRunner {
|
|
119
|
+
status(paths: readonly string[]): Promise<string>
|
|
120
|
+
currentBranch(): Promise<string>
|
|
121
|
+
branchExists(name: string): Promise<boolean>
|
|
122
|
+
remoteBranchExists(name: string): Promise<boolean>
|
|
123
|
+
createBranch(name: string): Promise<void>
|
|
124
|
+
stage(paths: readonly string[]): Promise<void>
|
|
125
|
+
commit(message: string): Promise<void>
|
|
126
|
+
push(branch: string): Promise<void>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface PullRequestOpener {
|
|
130
|
+
create(title: string, body: string): Promise<string>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function createGitRunner(target: string): GitRunner {
|
|
134
|
+
return {
|
|
135
|
+
status: (paths) => read(target, ['status', '--short', '--', ...paths]),
|
|
136
|
+
currentBranch: () => read(target, ['symbolic-ref', '--short', 'HEAD']),
|
|
137
|
+
branchExists: async (name) =>
|
|
138
|
+
(await read(target, ['branch', '--list', name])) !== '',
|
|
139
|
+
remoteBranchExists: async (name) =>
|
|
140
|
+
(await read(target, ['ls-remote', '--heads', 'origin', name])) !== '',
|
|
141
|
+
createBranch: async (name) => {
|
|
142
|
+
await mutate(target, ['checkout', '-b', name])
|
|
143
|
+
},
|
|
144
|
+
stage: async (paths) => {
|
|
145
|
+
await mutate(target, ['add', '--', ...paths])
|
|
146
|
+
},
|
|
147
|
+
commit: async (message) => {
|
|
148
|
+
await mutate(target, ['commit', '-m', message])
|
|
149
|
+
},
|
|
150
|
+
push: async (branch) => {
|
|
151
|
+
await mutate(target, ['push', '-u', 'origin', branch])
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Passes the body on stdin rather than through a temp file. The bash wrote one
|
|
158
|
+
* with `mktemp` and removed it afterwards, which left the file behind whenever
|
|
159
|
+
* `gh` failed.
|
|
160
|
+
*/
|
|
161
|
+
export function createPullRequestOpener(target: string): PullRequestOpener {
|
|
162
|
+
return {
|
|
163
|
+
create: async (title, body) => {
|
|
164
|
+
const result = await execa(
|
|
165
|
+
'gh',
|
|
166
|
+
['pr', 'create', '--title', title, '--body-file', '-'],
|
|
167
|
+
{
|
|
168
|
+
cwd: target,
|
|
169
|
+
input: body,
|
|
170
|
+
reject: false,
|
|
171
|
+
env: gitEnv(),
|
|
172
|
+
extendEnv: false,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if (result.exitCode !== 0) {
|
|
177
|
+
throw new GitCommandError(
|
|
178
|
+
`gh pr create failed: ${result.stderr || result.stdout}`,
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result.stdout.trim()
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function hasGh(): Promise<boolean> {
|
|
188
|
+
const result = await execa('command', ['-v', 'gh'], {
|
|
189
|
+
reject: false,
|
|
190
|
+
shell: true,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
return result.exitCode === 0
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Both seams strip git's repository-resolution variables, since a hook exports
|
|
198
|
+
* `GIT_DIR` into every process it runs and it beats `-C`. A sync driven from
|
|
199
|
+
* inside one would otherwise read the hook's repository and, through `mutate`,
|
|
200
|
+
* commit and push into it.
|
|
201
|
+
*/
|
|
202
|
+
async function read(target: string, args: readonly string[]): Promise<string> {
|
|
203
|
+
const result = await execa('git', ['-C', target, ...args], {
|
|
204
|
+
reject: false,
|
|
205
|
+
env: gitEnv(),
|
|
206
|
+
extendEnv: false,
|
|
207
|
+
})
|
|
208
|
+
return result.exitCode === 0 ? result.stdout : ''
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function mutate(target: string, args: readonly string[]): Promise<void> {
|
|
212
|
+
const result = await execa('git', ['-C', target, ...args], {
|
|
213
|
+
reject: false,
|
|
214
|
+
env: gitEnv(),
|
|
215
|
+
extendEnv: false,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
if (result.exitCode !== 0) {
|
|
219
|
+
throw new GitCommandError(
|
|
220
|
+
`git ${args[0]} failed: ${result.stderr || result.stdout}`,
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (result.stdout !== '') pipeOutput(result.stdout)
|
|
225
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { sep } from 'node:path'
|
|
4
|
+
import { execaSync } from 'execa'
|
|
5
|
+
import { gitEnv } from '@/git-env'
|
|
6
|
+
|
|
7
|
+
/** Blob SHA to the newest commit whose post-image for that path held it. */
|
|
8
|
+
export type PathHistory = ReadonlyMap<string, string>
|
|
9
|
+
|
|
10
|
+
/** Source path relative to the toolkit root, to that path's known blobs. */
|
|
11
|
+
export type HistoryIndex = ReadonlyMap<string, PathHistory>
|
|
12
|
+
|
|
13
|
+
const EMPTY_BLOB = '0000000000000000000000000000000000000000'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Git's own object name for a file's contents: sha1 over `blob <len>\0` and the
|
|
17
|
+
* bytes. Computed here rather than shelled out to `git hash-object`, which would
|
|
18
|
+
* cost a process per file inside the attribution pass.
|
|
19
|
+
*
|
|
20
|
+
* A repository normalizing line endings on checkout stores a blob the working
|
|
21
|
+
* tree never holds, so this returns a name that matches no historical version.
|
|
22
|
+
* The caller treats an unmatched file as unattributed, which is the safe label.
|
|
23
|
+
*/
|
|
24
|
+
export function gitBlobHash(content: Buffer): string {
|
|
25
|
+
const header = Buffer.from(`blob ${content.length}\0`, 'utf8')
|
|
26
|
+
return createHash('sha1').update(header).update(content).digest('hex')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads `--raw` log output into blob-to-commit maps, one per path.
|
|
31
|
+
*
|
|
32
|
+
* Reverse-chronological input means the first commit naming a blob is the
|
|
33
|
+
* newest that produced it, so an existing key is never overwritten. Content
|
|
34
|
+
* reverted and restored therefore reports the recent commit rather than the
|
|
35
|
+
* original, which is the one an operator can act on.
|
|
36
|
+
*/
|
|
37
|
+
export function parseRawLog(output: string): HistoryIndex {
|
|
38
|
+
const index = new Map<string, Map<string, string>>()
|
|
39
|
+
let commit = ''
|
|
40
|
+
|
|
41
|
+
for (const line of output.split('\n')) {
|
|
42
|
+
if (line === '') continue
|
|
43
|
+
|
|
44
|
+
if (!line.startsWith(':')) {
|
|
45
|
+
commit = line.trim()
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const [meta, path] = line.split('\t')
|
|
50
|
+
if (path === undefined || commit === '') continue
|
|
51
|
+
|
|
52
|
+
const blob = meta.trim().split(/\s+/)[3]
|
|
53
|
+
if (blob === undefined || blob === EMPTY_BLOB) continue
|
|
54
|
+
|
|
55
|
+
const blobs = index.get(path) ?? new Map<string, string>()
|
|
56
|
+
if (!blobs.has(blob)) blobs.set(blob, commit)
|
|
57
|
+
index.set(path, blobs)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return index
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Every historical version of the given toolkit-relative paths, or `undefined`
|
|
65
|
+
* when this toolkit has no history to read. A package installed from the
|
|
66
|
+
* registry ships source without `.git`, so absence is an ordinary state rather
|
|
67
|
+
* than a failure, and the caller degrades to the unattributed path.
|
|
68
|
+
*
|
|
69
|
+
* Renames are disabled so a raw line always carries exactly one path. Detection
|
|
70
|
+
* would emit a two-path form that the parser would read as an unknown blob.
|
|
71
|
+
*/
|
|
72
|
+
export function readHistoryIndex(
|
|
73
|
+
toolkitRoot: string,
|
|
74
|
+
paths: readonly string[],
|
|
75
|
+
): HistoryIndex | undefined {
|
|
76
|
+
if (paths.length === 0) return new Map()
|
|
77
|
+
|
|
78
|
+
const result = execaSync(
|
|
79
|
+
'git',
|
|
80
|
+
[
|
|
81
|
+
'-C',
|
|
82
|
+
toolkitRoot,
|
|
83
|
+
'log',
|
|
84
|
+
'--all',
|
|
85
|
+
'--raw',
|
|
86
|
+
'--no-renames',
|
|
87
|
+
'--no-abbrev',
|
|
88
|
+
'--relative',
|
|
89
|
+
'--format=%H',
|
|
90
|
+
'--',
|
|
91
|
+
...paths.map(toRepoPath),
|
|
92
|
+
],
|
|
93
|
+
{ reject: false, env: gitEnv(), extendEnv: false },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return result.exitCode === 0 ? parseRawLog(result.stdout) : undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Git prints and accepts posix separators on every platform, so a Windows
|
|
101
|
+
* caller's `relative()` output has to be converted before it can match a path
|
|
102
|
+
* the log emitted.
|
|
103
|
+
*/
|
|
104
|
+
export function toRepoPath(path: string): string {
|
|
105
|
+
return path.split(sep).join('/')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The commit whose version of `sourceRel` matches what sits at `installedPath`.
|
|
110
|
+
* A match proves the file is untouched since it was installed, so the toolkit is
|
|
111
|
+
* what moved. No match means the content matches nothing this toolkit ever
|
|
112
|
+
* published, which is a local edit and stays unattributed.
|
|
113
|
+
*/
|
|
114
|
+
export function findInstalledOrigin(
|
|
115
|
+
index: HistoryIndex,
|
|
116
|
+
sourceRel: string,
|
|
117
|
+
installedPath: string,
|
|
118
|
+
): string | undefined {
|
|
119
|
+
const blobs = index.get(toRepoPath(sourceRel))
|
|
120
|
+
if (blobs === undefined) return undefined
|
|
121
|
+
|
|
122
|
+
return blobs.get(gitBlobHash(readFileSync(installedPath)))
|
|
123
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import { SUBDIRS } from '@/claude/seeds'
|
|
4
|
+
import type { StampDomain } from '@/sync/stamp'
|
|
5
|
+
|
|
6
|
+
const CLAUDE_DIR = '.claude'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Domains an older toolkit installed at the project root, each with the source
|
|
10
|
+
* folder naming what it owns. Governance is absent because its rules have always
|
|
11
|
+
* landed under `.claude/rules/`, so there is no earlier location to be stranded
|
|
12
|
+
* at. Standards and snippets are absent because no copy installs into a target
|
|
13
|
+
* at all now, so a root `standards/` or `snippets/` folder there is the
|
|
14
|
+
* project's own authoring surface and reporting it as unmigrated would propose
|
|
15
|
+
* moving files nothing installed.
|
|
16
|
+
*
|
|
17
|
+
* A tuple array rather than a partial record, so the domain key stays typed
|
|
18
|
+
* without asserting an `Object.entries` result back into the union. Empty
|
|
19
|
+
* now that both former entries retired their install channels. A future
|
|
20
|
+
* domain installing at a target's root, the way an older toolkit installed
|
|
21
|
+
* standards and snippets there, would be the next entry to add.
|
|
22
|
+
*/
|
|
23
|
+
const ROOT_LAYOUTS: readonly (readonly [
|
|
24
|
+
StampDomain,
|
|
25
|
+
string,
|
|
26
|
+
(root: string) => string,
|
|
27
|
+
])[] = []
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A target file that a shipped seed folder replaced. Carries no source and
|
|
31
|
+
* queues no change, because the file holds content the project wrote and only
|
|
32
|
+
* the user can decide where it moves.
|
|
33
|
+
*/
|
|
34
|
+
export interface SupersededEntry {
|
|
35
|
+
readonly rel: string
|
|
36
|
+
readonly replacedBy: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A domain whose files sit at the root layout an older toolkit installed to,
|
|
41
|
+
* with nothing at the path the current one reads. Distinct from a domain that
|
|
42
|
+
* was never installed, which has neither.
|
|
43
|
+
*/
|
|
44
|
+
export interface UnmigratedDomain {
|
|
45
|
+
readonly domain: StampDomain
|
|
46
|
+
readonly rootPath: string
|
|
47
|
+
readonly installPath: string
|
|
48
|
+
/** Root files the toolkit ships under this domain, not every file present. */
|
|
49
|
+
readonly files: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Pairs each seed subdirectory against an uppercase-stem sibling in the target,
|
|
54
|
+
* so a project still holding `.claude/TASKS.md` is reported against the
|
|
55
|
+
* `.claude/tasks/` folder that replaced it.
|
|
56
|
+
*
|
|
57
|
+
* Deriving from the seed tree rather than from a fixed list means a folder
|
|
58
|
+
* added later is covered without editing this file. The cost is that only an
|
|
59
|
+
* exact stem matches, so a suffixed variant such as `TASKS-ARCHIVE.md` is not
|
|
60
|
+
* reported.
|
|
61
|
+
*/
|
|
62
|
+
export function collectSuperseded(target: string): SupersededEntry[] {
|
|
63
|
+
const entries: SupersededEntry[] = []
|
|
64
|
+
|
|
65
|
+
for (const subdir of SUBDIRS) {
|
|
66
|
+
const rel = join(CLAUDE_DIR, `${subdir.toUpperCase()}.md`)
|
|
67
|
+
if (!isFile(join(target, rel))) continue
|
|
68
|
+
|
|
69
|
+
entries.push({ rel, replacedBy: join(CLAUDE_DIR, subdir) })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return entries
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Domains the target holds at the root rather than under `.claude/`. Reported
|
|
77
|
+
* separately from the per-domain scan because that scan lists only domains it
|
|
78
|
+
* finds installed, so an unmigrated project would otherwise read as one that
|
|
79
|
+
* never installed the domain at all.
|
|
80
|
+
*
|
|
81
|
+
* A root folder is claimed only when it holds a file the toolkit ships under
|
|
82
|
+
* that domain. Presence of the folder alone is not evidence: a project can
|
|
83
|
+
* carry its own `snippets/` of prompts it wrote and never have installed the
|
|
84
|
+
* domain, and calling that unmigrated would fail `--exit-code` with no action
|
|
85
|
+
* that clears it.
|
|
86
|
+
*/
|
|
87
|
+
export function detectUnmigrated(
|
|
88
|
+
toolkitRoot: string,
|
|
89
|
+
target: string,
|
|
90
|
+
): UnmigratedDomain[] {
|
|
91
|
+
const found: UnmigratedDomain[] = []
|
|
92
|
+
|
|
93
|
+
for (const [domain, rootPath, sourceDir] of ROOT_LAYOUTS) {
|
|
94
|
+
const installPath = join(CLAUDE_DIR, rootPath)
|
|
95
|
+
if (isDirectoryWithFiles(join(target, installPath))) continue
|
|
96
|
+
|
|
97
|
+
const files = countToolkitOwned(
|
|
98
|
+
join(target, rootPath),
|
|
99
|
+
sourceDir(toolkitRoot),
|
|
100
|
+
)
|
|
101
|
+
if (files === 0) continue
|
|
102
|
+
|
|
103
|
+
found.push({ domain, rootPath, installPath, files })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return found
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Root files whose basename matches something the toolkit ships for this domain.
|
|
111
|
+
* Basenames rather than relative paths, because the root layout an older toolkit
|
|
112
|
+
* wrote is flat while the source nests by category, and the question here is only
|
|
113
|
+
* whether any file is toolkit-owned rather than which source each one came from.
|
|
114
|
+
*/
|
|
115
|
+
function countToolkitOwned(dir: string, sourceDir: string): number {
|
|
116
|
+
const owned = new Set(listMarkdown(sourceDir).map((rel) => basename(rel)))
|
|
117
|
+
if (owned.size === 0) return 0
|
|
118
|
+
|
|
119
|
+
return listMarkdown(dir).filter((rel) => owned.has(basename(rel))).length
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isFile(path: string): boolean {
|
|
123
|
+
return existsSync(path) && statSync(path).isFile()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isDirectoryWithFiles(path: string): boolean {
|
|
127
|
+
return listMarkdown(path).length > 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function listMarkdown(dir: string): string[] {
|
|
131
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) return []
|
|
132
|
+
|
|
133
|
+
return [
|
|
134
|
+
...new Bun.Glob('**/*.md').scanSync({
|
|
135
|
+
cwd: dir,
|
|
136
|
+
onlyFiles: true,
|
|
137
|
+
dot: true,
|
|
138
|
+
}),
|
|
139
|
+
]
|
|
140
|
+
}
|