@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,576 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { rm } from 'node:fs/promises'
|
|
3
|
+
import { isAbsolute, relative, resolve } from 'node:path'
|
|
4
|
+
import { copyPreservingMode } from '@/copy'
|
|
5
|
+
import { findInstalledOrigin, readHistoryIndex } from '@/sync/history'
|
|
6
|
+
import {
|
|
7
|
+
type DomainHashes,
|
|
8
|
+
hashFile,
|
|
9
|
+
readStamp,
|
|
10
|
+
type StampSource,
|
|
11
|
+
stampedHashes,
|
|
12
|
+
toStampKey,
|
|
13
|
+
writeStamp,
|
|
14
|
+
} from '@/sync/stamp'
|
|
15
|
+
import { isDirectory } from '@/target'
|
|
16
|
+
import {
|
|
17
|
+
intro,
|
|
18
|
+
isNonInteractive,
|
|
19
|
+
logAdd,
|
|
20
|
+
logError,
|
|
21
|
+
logInfo,
|
|
22
|
+
logStep,
|
|
23
|
+
logWarn,
|
|
24
|
+
outro,
|
|
25
|
+
palette,
|
|
26
|
+
select,
|
|
27
|
+
} from '@/ui'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One installed file in three path flavours: absolute, relative to the
|
|
31
|
+
* domain's installed root, and relative to the target. Adapters match on
|
|
32
|
+
* `relToRoot` and every log line prints `rel`.
|
|
33
|
+
*/
|
|
34
|
+
export interface InstalledFile {
|
|
35
|
+
readonly path: string
|
|
36
|
+
readonly relToRoot: string
|
|
37
|
+
readonly rel: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RetiredSurface {
|
|
41
|
+
readonly path: string
|
|
42
|
+
readonly rel: string
|
|
43
|
+
readonly notice: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type SyncChange =
|
|
47
|
+
| {
|
|
48
|
+
readonly kind: 'copy'
|
|
49
|
+
readonly source: string
|
|
50
|
+
readonly dest: string
|
|
51
|
+
readonly rel: string
|
|
52
|
+
}
|
|
53
|
+
| { readonly kind: 'delete'; readonly dest: string; readonly rel: string }
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* How an installed file compares to its toolkit source, and when it differs,
|
|
57
|
+
* who moved it. `stale` and `customized` need the stamp to tell apart, so
|
|
58
|
+
* `drifted` stays the verdict for a difference no stamp covers.
|
|
59
|
+
*
|
|
60
|
+
* `orphaned` and `stranded` both mean the walk found no source, and they are
|
|
61
|
+
* separate because they need opposite treatment. A project-authored file is
|
|
62
|
+
* orphaned and stays that way forever. A stamped file the toolkit no longer
|
|
63
|
+
* installs to is stranded, which is a relocation waiting on a decision.
|
|
64
|
+
*
|
|
65
|
+
* `missing` is the one state the walk cannot produce on its own, since the
|
|
66
|
+
* walk only iterates files that exist. It comes from `collectMissing`
|
|
67
|
+
* instead, an adapter naming an entitled file the target does not hold.
|
|
68
|
+
*/
|
|
69
|
+
export type EntryState =
|
|
70
|
+
| 'matching'
|
|
71
|
+
| 'stale'
|
|
72
|
+
| 'customized'
|
|
73
|
+
| 'drifted'
|
|
74
|
+
| 'orphaned'
|
|
75
|
+
| 'stranded'
|
|
76
|
+
| 'missing'
|
|
77
|
+
|
|
78
|
+
export interface ScanEntry {
|
|
79
|
+
readonly state: EntryState
|
|
80
|
+
readonly rel: string
|
|
81
|
+
/** Toolkit revision this file's content came from, when history proved it. */
|
|
82
|
+
readonly since?: string
|
|
83
|
+
/**
|
|
84
|
+
* Overrides `report`'s generic text for this entry's state. Two producers
|
|
85
|
+
* write one: `collectMissing`, since a stack name is only known to the
|
|
86
|
+
* adapter that resolved it, and an orphan sitting outside the declared
|
|
87
|
+
* project subfolder, since the generic line names no destination.
|
|
88
|
+
*/
|
|
89
|
+
readonly notice?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface SyncPlan {
|
|
93
|
+
readonly entries: readonly ScanEntry[]
|
|
94
|
+
readonly retired: readonly RetiredSurface[]
|
|
95
|
+
readonly changes: readonly SyncChange[]
|
|
96
|
+
/** Set when a file needed history to attribute it and this toolkit has none. */
|
|
97
|
+
readonly historyUnavailable: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* What a headless run does once the plan has changes. Domains whose files the
|
|
102
|
+
* toolkit owns apply them. Domains whose files a project is expected to edit
|
|
103
|
+
* refuse, because an unattended overwrite of a customized file is data loss
|
|
104
|
+
* with no prompt in front of it.
|
|
105
|
+
*/
|
|
106
|
+
export type NonInteractivePolicy =
|
|
107
|
+
| { readonly kind: 'apply' }
|
|
108
|
+
| {
|
|
109
|
+
readonly kind: 'refuse'
|
|
110
|
+
readonly message: string
|
|
111
|
+
readonly hint: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The two holes every domain sync leaves open: where a destination file's
|
|
116
|
+
* source lives, and what counts as a change beyond a plain content diff.
|
|
117
|
+
* Everything else in a sync is identical across gov, snippets, and standards,
|
|
118
|
+
* so it lives in the engine.
|
|
119
|
+
*
|
|
120
|
+
* The optional members exist because one adapter needed each of them. Leaving
|
|
121
|
+
* all three unset reproduces the behavior gov and snippets already had.
|
|
122
|
+
*/
|
|
123
|
+
export interface SyncAdapter {
|
|
124
|
+
readonly banner: string
|
|
125
|
+
/** Rendered as `Scanning <label>`. */
|
|
126
|
+
readonly label: string
|
|
127
|
+
readonly missingMessage: string
|
|
128
|
+
/** Noun for the completion count, e.g. `changes`. */
|
|
129
|
+
readonly unit: string
|
|
130
|
+
installedRoot(target: string): string
|
|
131
|
+
locateSource(file: InstalledFile): string | undefined
|
|
132
|
+
/** Surfaces the file walk cannot see, such as a retired doc to delete. */
|
|
133
|
+
collectRetired?(target: string): RetiredSurface[]
|
|
134
|
+
/**
|
|
135
|
+
* Entitled files the walk cannot see because they do not exist yet.
|
|
136
|
+
* Reported as `missing` and queued as no change, since installing one
|
|
137
|
+
* changes what the project is governed by and stays a separate command.
|
|
138
|
+
*/
|
|
139
|
+
collectMissing?(target: string): RetiredSurface[]
|
|
140
|
+
/** Dropped from the walk, so neither matching nor orphaned. */
|
|
141
|
+
isExcluded?(file: InstalledFile): boolean
|
|
142
|
+
/**
|
|
143
|
+
* Top-level folder under `installedRoot` that is project-authored by
|
|
144
|
+
* location rather than by the name inference `locateSource` runs.
|
|
145
|
+
* Checked before `locateSource`, so a file here is orphaned even when its
|
|
146
|
+
* name also matches a toolkit source, and never enters the stamp.
|
|
147
|
+
*
|
|
148
|
+
* Declaring it also gives the report a destination to name when the name
|
|
149
|
+
* inference orphans a file sitting anywhere else.
|
|
150
|
+
*/
|
|
151
|
+
readonly projectSubdir?: string
|
|
152
|
+
/** Defaults to applying. */
|
|
153
|
+
readonly nonInteractive?: NonInteractivePolicy
|
|
154
|
+
/** Runs on a completed sync, including one with no changes. */
|
|
155
|
+
onComplete?(target: string): Promise<void>
|
|
156
|
+
/** Where this domain's hashes are stamped. Unset domains go unstamped. */
|
|
157
|
+
readonly stamp?: StampSource
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Lists installed markdown, dotfiles included. `Bun.Glob` skips entries
|
|
162
|
+
* beginning with a dot unless `dot` is set, and every domain installs under
|
|
163
|
+
* `.claude/`, so a nested dot-directory would silently drop out of the walk.
|
|
164
|
+
*/
|
|
165
|
+
export function listInstalled(root: string, target: string): InstalledFile[] {
|
|
166
|
+
if (!existsSync(root)) return []
|
|
167
|
+
|
|
168
|
+
return [
|
|
169
|
+
...new Bun.Glob('**/*.md').scanSync({
|
|
170
|
+
cwd: root,
|
|
171
|
+
onlyFiles: true,
|
|
172
|
+
dot: true,
|
|
173
|
+
}),
|
|
174
|
+
]
|
|
175
|
+
.sort()
|
|
176
|
+
.map((relToRoot) => {
|
|
177
|
+
const path = resolve(root, relToRoot)
|
|
178
|
+
return { path, relToRoot, rel: relative(target, path) }
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Classifies every installed file against its source without writing anything.
|
|
184
|
+
* A file with no source is left alone rather than deleted, which is what keeps
|
|
185
|
+
* project-authored rules alive across a sync.
|
|
186
|
+
*/
|
|
187
|
+
export function planSync(adapter: SyncAdapter, target: string): SyncPlan {
|
|
188
|
+
const entries: ScanEntry[] = []
|
|
189
|
+
const changes: SyncChange[] = []
|
|
190
|
+
const unattributed: UnattributedFile[] = []
|
|
191
|
+
|
|
192
|
+
const hashes = stampedHashes(readStamp(target), adapter.stamp?.domain)
|
|
193
|
+
const walked = new Set<string>()
|
|
194
|
+
|
|
195
|
+
for (const file of listInstalled(adapter.installedRoot(target), target)) {
|
|
196
|
+
if (adapter.isExcluded?.(file) === true) continue
|
|
197
|
+
walked.add(toStampKey(file.rel))
|
|
198
|
+
|
|
199
|
+
if (isProjectAuthored(adapter, file)) {
|
|
200
|
+
entries.push({ state: 'orphaned', rel: file.rel })
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const source = adapter.locateSource(file)
|
|
205
|
+
|
|
206
|
+
if (source === undefined || !existsSync(source)) {
|
|
207
|
+
entries.push(misplacedOrphan(adapter, target, file))
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (sameContent(source, file.path)) {
|
|
212
|
+
entries.push({ state: 'matching', rel: file.rel })
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const state = attribute(hashes, file)
|
|
217
|
+
if (state === 'drifted') {
|
|
218
|
+
unattributed.push({ index: entries.length, source, path: file.path })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
entries.push({ state, rel: file.rel })
|
|
222
|
+
changes.push({
|
|
223
|
+
kind: 'copy',
|
|
224
|
+
source,
|
|
225
|
+
dest: file.path,
|
|
226
|
+
rel: file.rel,
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const historyUnavailable = recoverAttribution(adapter, entries, unattributed)
|
|
231
|
+
|
|
232
|
+
entries.push(...strandedByRelocation(target, hashes, walked))
|
|
233
|
+
|
|
234
|
+
for (const surface of adapter.collectMissing?.(target) ?? []) {
|
|
235
|
+
entries.push({ state: 'missing', rel: surface.rel, notice: surface.notice })
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const retired = adapter.collectRetired?.(target) ?? []
|
|
239
|
+
for (const surface of retired) {
|
|
240
|
+
changes.push({ kind: 'delete', dest: surface.path, rel: surface.rel })
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { entries, retired, changes, historyUnavailable }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface UnattributedFile {
|
|
247
|
+
readonly index: number
|
|
248
|
+
readonly source: string
|
|
249
|
+
readonly path: string
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Second pass over the files the stamp could not attribute, which is every file
|
|
254
|
+
* in a target installed before stamping shipped. Matching the installed content
|
|
255
|
+
* against the toolkit's own history recovers the fact a stamp would have held,
|
|
256
|
+
* and a file matching no published version stays unattributed.
|
|
257
|
+
*
|
|
258
|
+
* Runs as one git call for the whole domain rather than one per file, and only
|
|
259
|
+
* when the first pass left something to attribute. Reports whether history was
|
|
260
|
+
* readable at all, so a registry install can say why it fell short instead of
|
|
261
|
+
* reporting every file as a local edit.
|
|
262
|
+
*/
|
|
263
|
+
function recoverAttribution(
|
|
264
|
+
adapter: SyncAdapter,
|
|
265
|
+
entries: ScanEntry[],
|
|
266
|
+
unattributed: readonly UnattributedFile[],
|
|
267
|
+
): boolean {
|
|
268
|
+
const toolkitRoot = adapter.stamp?.toolkitRoot
|
|
269
|
+
if (toolkitRoot === undefined || unattributed.length === 0) return false
|
|
270
|
+
|
|
271
|
+
const index = readHistoryIndex(
|
|
272
|
+
toolkitRoot,
|
|
273
|
+
unattributed.map((file) => relative(toolkitRoot, file.source)),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if (index === undefined) return true
|
|
277
|
+
|
|
278
|
+
for (const file of unattributed) {
|
|
279
|
+
const since = findInstalledOrigin(
|
|
280
|
+
index,
|
|
281
|
+
relative(toolkitRoot, file.source),
|
|
282
|
+
file.path,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if (since === undefined) continue
|
|
286
|
+
entries[file.index] = { ...entries[file.index], state: 'stale', since }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return false
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function applyChanges(
|
|
293
|
+
changes: readonly SyncChange[],
|
|
294
|
+
): Promise<void> {
|
|
295
|
+
logStep('Applying changes')
|
|
296
|
+
|
|
297
|
+
for (const change of changes) {
|
|
298
|
+
if (change.kind === 'copy') {
|
|
299
|
+
await copyPreservingMode(change.source, change.dest)
|
|
300
|
+
logAdd(change.rel)
|
|
301
|
+
continue
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
await rm(change.dest, { force: true })
|
|
305
|
+
logWarn(`removed ${change.rel}`)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface SyncRunOptions {
|
|
310
|
+
/** Path the sync refuses to run against, normally the toolkit root. */
|
|
311
|
+
readonly protectedRoot: string
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Runs one domain sync end to end and returns the process exit code. Callers
|
|
316
|
+
* register a command, build an adapter, and hand both to this function.
|
|
317
|
+
*/
|
|
318
|
+
export async function runDomainSync(
|
|
319
|
+
adapter: SyncAdapter,
|
|
320
|
+
target: string,
|
|
321
|
+
options: SyncRunOptions,
|
|
322
|
+
): Promise<number> {
|
|
323
|
+
intro(adapter.banner)
|
|
324
|
+
|
|
325
|
+
const resolved = resolve(target)
|
|
326
|
+
|
|
327
|
+
if (!isDirectory(resolved)) {
|
|
328
|
+
logError(`Target directory not found: ${target}`)
|
|
329
|
+
outro()
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (resolved === options.protectedRoot) {
|
|
334
|
+
logError(
|
|
335
|
+
'Cannot run against toolkit root. Files here are the source of truth.',
|
|
336
|
+
)
|
|
337
|
+
outro()
|
|
338
|
+
return 1
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const plan = planSync(adapter, resolved)
|
|
342
|
+
const { GREEN, GREY, NC } = palette(process.stderr)
|
|
343
|
+
|
|
344
|
+
if (
|
|
345
|
+
!existsSync(adapter.installedRoot(resolved)) &&
|
|
346
|
+
plan.retired.length === 0
|
|
347
|
+
) {
|
|
348
|
+
logWarn(adapter.missingMessage)
|
|
349
|
+
outro()
|
|
350
|
+
return 0
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
report(adapter, plan)
|
|
354
|
+
|
|
355
|
+
const count = plan.changes.length
|
|
356
|
+
if (count === 0) {
|
|
357
|
+
await adapter.onComplete?.(resolved)
|
|
358
|
+
await recordStamp(adapter, resolved, new Date())
|
|
359
|
+
outro()
|
|
360
|
+
process.stderr.write(`${GREEN}✓ Everything up to date${NC}\n`)
|
|
361
|
+
return 0
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const policy = adapter.nonInteractive ?? { kind: 'apply' }
|
|
365
|
+
if (
|
|
366
|
+
policy.kind === 'refuse' &&
|
|
367
|
+
isNonInteractive() &&
|
|
368
|
+
hasUnattributedDrift(plan)
|
|
369
|
+
) {
|
|
370
|
+
logWarn(policy.message)
|
|
371
|
+
logInfo(policy.hint)
|
|
372
|
+
outro()
|
|
373
|
+
return 0
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const shouldApply = await select({
|
|
377
|
+
message: `Apply ${count} changes?`,
|
|
378
|
+
options: [
|
|
379
|
+
{ value: true, label: 'Apply all' },
|
|
380
|
+
{ value: false, label: 'Cancel' },
|
|
381
|
+
],
|
|
382
|
+
nonInteractiveDefault: true,
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
if (!shouldApply) {
|
|
386
|
+
logWarn('Sync cancelled')
|
|
387
|
+
outro()
|
|
388
|
+
return 0
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
await applyChanges(plan.changes)
|
|
392
|
+
await adapter.onComplete?.(resolved)
|
|
393
|
+
await recordStamp(adapter, resolved, new Date())
|
|
394
|
+
|
|
395
|
+
outro()
|
|
396
|
+
process.stderr.write(
|
|
397
|
+
`${GREEN}✓ Sync complete${NC} ${GREY}(${count} ${adapter.unit})${NC}\n`,
|
|
398
|
+
)
|
|
399
|
+
return 0
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Reports in walk order rather than grouped by state, so a long rule tree
|
|
404
|
+
* reads as a directory listing with drift called out in place.
|
|
405
|
+
*/
|
|
406
|
+
function report(adapter: SyncAdapter, plan: SyncPlan): void {
|
|
407
|
+
logStep(`Scanning ${adapter.label}`)
|
|
408
|
+
|
|
409
|
+
for (const entry of plan.entries) {
|
|
410
|
+
if (entry.state === 'matching') logInfo(entry.rel)
|
|
411
|
+
else if (entry.state === 'drifted') logWarn(entry.rel)
|
|
412
|
+
else if (entry.state === 'stale')
|
|
413
|
+
logWarn(
|
|
414
|
+
entry.since === undefined
|
|
415
|
+
? `${entry.rel} (toolkit updated)`
|
|
416
|
+
: `${entry.rel} (toolkit updated since ${entry.since.slice(0, 7)})`,
|
|
417
|
+
)
|
|
418
|
+
else if (entry.state === 'customized')
|
|
419
|
+
logWarn(`${entry.rel} (locally customized)`)
|
|
420
|
+
else if (entry.state === 'stranded')
|
|
421
|
+
logWarn(`${entry.rel} (installed here by an older toolkit, now moved)`)
|
|
422
|
+
else if (entry.state === 'missing')
|
|
423
|
+
logWarn(
|
|
424
|
+
entry.notice ?? `${entry.rel} (listed by the stack, not installed)`,
|
|
425
|
+
)
|
|
426
|
+
else
|
|
427
|
+
logWarn(entry.notice ?? `${entry.rel} (not in toolkit source, skipping)`)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
for (const surface of plan.retired) {
|
|
431
|
+
logWarn(surface.notice)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (plan.historyUnavailable) {
|
|
435
|
+
logWarn(
|
|
436
|
+
'Attribution unavailable: this toolkit has no git history to match against.',
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function sameContent(left: string, right: string): boolean {
|
|
442
|
+
return readFileSync(left).equals(readFileSync(right))
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Splits a difference by cause. Matching the stamp means the file is untouched
|
|
447
|
+
* since install and the toolkit is what moved, so the update is mechanical.
|
|
448
|
+
* Anything else is a local edit, and an uncovered file stays unattributed.
|
|
449
|
+
*/
|
|
450
|
+
function attribute(hashes: DomainHashes, file: InstalledFile): EntryState {
|
|
451
|
+
const stamped = hashes[toStampKey(file.rel)]
|
|
452
|
+
if (stamped === undefined) return 'drifted'
|
|
453
|
+
|
|
454
|
+
return stamped === hashFile(file.path) ? 'stale' : 'customized'
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Stamped paths the walk never reached, because the toolkit installs to a root
|
|
459
|
+
* it no longer uses. Reporting them is what makes a relocation visible instead
|
|
460
|
+
* of silent. They are left alone, and queue no change.
|
|
461
|
+
*
|
|
462
|
+
* A key escaping the target is dropped rather than reported, so a hand-edited
|
|
463
|
+
* stamp cannot make the report name paths outside the project.
|
|
464
|
+
*/
|
|
465
|
+
function strandedByRelocation(
|
|
466
|
+
target: string,
|
|
467
|
+
hashes: DomainHashes,
|
|
468
|
+
walked: ReadonlySet<string>,
|
|
469
|
+
): ScanEntry[] {
|
|
470
|
+
const entries: ScanEntry[] = []
|
|
471
|
+
|
|
472
|
+
for (const key of Object.keys(hashes).sort()) {
|
|
473
|
+
if (walked.has(key)) continue
|
|
474
|
+
|
|
475
|
+
const path = resolve(target, key)
|
|
476
|
+
if (!isInside(target, path) || !existsSync(path)) continue
|
|
477
|
+
|
|
478
|
+
entries.push({ state: 'stranded', rel: relative(target, path) })
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return entries
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* `Bun.Glob` reports `relToRoot` with `/` separators regardless of platform,
|
|
486
|
+
* so the declared subfolder is compared against the walk's first segment
|
|
487
|
+
* rather than through a path-aware join.
|
|
488
|
+
*/
|
|
489
|
+
function isProjectAuthored(adapter: SyncAdapter, file: InstalledFile): boolean {
|
|
490
|
+
if (adapter.projectSubdir === undefined) return false
|
|
491
|
+
return file.relToRoot.split('/')[0] === adapter.projectSubdir
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* An orphan the name inference caught rather than the location test. No source
|
|
496
|
+
* name matched, which is what a project-authored file looks like and also what
|
|
497
|
+
* a file the toolkit shipped and later renamed looks like, so the line offers
|
|
498
|
+
* the destination on a condition rather than asserting who wrote the file.
|
|
499
|
+
* Moving a toolkit leftover into the project subfolder would mark it the
|
|
500
|
+
* project's permanently, and only the operator can tell the two apart.
|
|
501
|
+
*
|
|
502
|
+
* The stamp cannot tell them apart either. `recordStamp` skips a file whose
|
|
503
|
+
* source is gone and `writeStamp` replaces the domain's whole `files` map, so
|
|
504
|
+
* a renamed rule's entry survives exactly one sync past the rename, and a
|
|
505
|
+
* target installed before stamping shipped has no entry to read at all.
|
|
506
|
+
*
|
|
507
|
+
* Naming the destination is all this does. Moving the file rewrites a path the
|
|
508
|
+
* project's own rules, skills, and docs may cite, so the sync leaves it where
|
|
509
|
+
* it is.
|
|
510
|
+
*/
|
|
511
|
+
function misplacedOrphan(
|
|
512
|
+
adapter: SyncAdapter,
|
|
513
|
+
target: string,
|
|
514
|
+
file: InstalledFile,
|
|
515
|
+
): ScanEntry {
|
|
516
|
+
const subdir = adapter.projectSubdir
|
|
517
|
+
if (subdir === undefined) return { state: 'orphaned', rel: file.rel }
|
|
518
|
+
|
|
519
|
+
const belongs = relative(
|
|
520
|
+
target,
|
|
521
|
+
resolve(adapter.installedRoot(target), subdir, file.relToRoot),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
state: 'orphaned',
|
|
526
|
+
rel: file.rel,
|
|
527
|
+
notice: `${file.rel} (not in toolkit source, skipping. Move it to ${belongs} if the project authored it.)`,
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function isInside(target: string, path: string): boolean {
|
|
532
|
+
const rel = relative(target, path)
|
|
533
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Drift a refusing domain must not touch unattended. A file proven to match
|
|
538
|
+
* what was installed carries no local edit to lose, so it does not count.
|
|
539
|
+
*/
|
|
540
|
+
function hasUnattributedDrift(plan: SyncPlan): boolean {
|
|
541
|
+
return plan.entries.some(
|
|
542
|
+
(entry) => entry.state === 'customized' || entry.state === 'drifted',
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Records what the toolkit placed, after the copies land, so a partial apply
|
|
548
|
+
* that throws leaves the previous stamp rather than a claim the target does not
|
|
549
|
+
* meet. A file with no source, or one orphaned by location, is
|
|
550
|
+
* project-authored and stays out.
|
|
551
|
+
*
|
|
552
|
+
* Reads the installed tree rather than the caller's file list, so a partial
|
|
553
|
+
* install still stamps the domain's whole installed set.
|
|
554
|
+
*/
|
|
555
|
+
export async function recordStamp(
|
|
556
|
+
adapter: SyncAdapter,
|
|
557
|
+
target: string,
|
|
558
|
+
now: Date,
|
|
559
|
+
): Promise<void> {
|
|
560
|
+
const stampSource = adapter.stamp
|
|
561
|
+
if (stampSource === undefined) return
|
|
562
|
+
|
|
563
|
+
const hashes: Record<string, string> = {}
|
|
564
|
+
|
|
565
|
+
for (const file of listInstalled(adapter.installedRoot(target), target)) {
|
|
566
|
+
if (adapter.isExcluded?.(file) === true) continue
|
|
567
|
+
if (isProjectAuthored(adapter, file)) continue
|
|
568
|
+
|
|
569
|
+
const source = adapter.locateSource(file)
|
|
570
|
+
if (source === undefined || !existsSync(source)) continue
|
|
571
|
+
|
|
572
|
+
hashes[toStampKey(file.rel)] = hashFile(file.path)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await writeStamp(target, stampSource, hashes, now)
|
|
576
|
+
}
|