@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,122 @@
|
|
|
1
|
+
import type { GitignoreSection } from '@/tooling/manifest'
|
|
2
|
+
|
|
3
|
+
export interface MergeResult {
|
|
4
|
+
readonly content: string
|
|
5
|
+
readonly added: readonly string[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PruneResult {
|
|
9
|
+
readonly content: string
|
|
10
|
+
readonly removed: readonly string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Splits a gitignore body the way `while IFS= read -r line` did, so a file
|
|
15
|
+
* with or without a trailing newline yields the same logical lines.
|
|
16
|
+
*/
|
|
17
|
+
function toLines(content: string): string[] {
|
|
18
|
+
if (content === '') return []
|
|
19
|
+
const lines = content.split('\n')
|
|
20
|
+
if (lines[lines.length - 1] === '') lines.pop()
|
|
21
|
+
return lines
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fromLines(lines: readonly string[]): string {
|
|
25
|
+
if (lines.length === 0) return ''
|
|
26
|
+
return `${lines.join('\n')}\n`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function withoutTrailingSlash(entry: string): string {
|
|
30
|
+
return entry.endsWith('/') ? entry.slice(0, -1) : entry
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasEntry(lines: readonly string[], entry: string): boolean {
|
|
34
|
+
return lines.includes(entry) || lines.includes(withoutTrailingSlash(entry))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Appends every manifest entry the file does not already carry.
|
|
39
|
+
*
|
|
40
|
+
* Two behaviors are load-bearing and preserved from `merge_gitignore`. The
|
|
41
|
+
* section header is written only when the whole section is missing, so a
|
|
42
|
+
* partially present section appends its entries bare at the end of the file.
|
|
43
|
+
* Presence is re-checked against the growing file, so an entry added by an
|
|
44
|
+
* earlier section or an earlier stack in the chain is not added twice.
|
|
45
|
+
*/
|
|
46
|
+
export function mergeSections(
|
|
47
|
+
content: string,
|
|
48
|
+
sections: readonly GitignoreSection[],
|
|
49
|
+
): MergeResult {
|
|
50
|
+
let next = content
|
|
51
|
+
const added: string[] = []
|
|
52
|
+
|
|
53
|
+
for (const section of sections) {
|
|
54
|
+
const lines = toLines(next)
|
|
55
|
+
const missing = section.entries.filter((entry) => !hasEntry(lines, entry))
|
|
56
|
+
if (missing.length === 0) continue
|
|
57
|
+
|
|
58
|
+
if (missing.length === section.entries.length) {
|
|
59
|
+
next += `\n${section.header}\n`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const entry of missing) {
|
|
63
|
+
next += `${entry}\n`
|
|
64
|
+
added.push(entry)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { content: next, added }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Drops entries that sit under a managed header but are no longer in the
|
|
73
|
+
* manifest. A section runs from its header line until the first blank line or
|
|
74
|
+
* the next comment, matching `prune_gitignore_section`.
|
|
75
|
+
*/
|
|
76
|
+
export function pruneSections(
|
|
77
|
+
content: string,
|
|
78
|
+
sections: readonly GitignoreSection[],
|
|
79
|
+
): PruneResult {
|
|
80
|
+
let lines = toLines(content)
|
|
81
|
+
const removed: string[] = []
|
|
82
|
+
|
|
83
|
+
for (const section of sections) {
|
|
84
|
+
const kept: string[] = []
|
|
85
|
+
let inSection = false
|
|
86
|
+
|
|
87
|
+
for (const line of lines) {
|
|
88
|
+
if (line === section.header) {
|
|
89
|
+
inSection = true
|
|
90
|
+
kept.push(line)
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!inSection) {
|
|
95
|
+
kept.push(line)
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (line === '' || line.startsWith('#')) {
|
|
100
|
+
inSection = false
|
|
101
|
+
kept.push(line)
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isAllowed = section.entries.some(
|
|
106
|
+
(entry) =>
|
|
107
|
+
line === entry ||
|
|
108
|
+
withoutTrailingSlash(line) === withoutTrailingSlash(entry),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if (isAllowed) {
|
|
112
|
+
kept.push(line)
|
|
113
|
+
} else {
|
|
114
|
+
removed.push(line)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lines = kept
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { content: removed.length > 0 ? fromLines(lines) : content, removed }
|
|
122
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
import { $ } from 'bun'
|
|
5
|
+
import { copyPreservingMode } from '@/copy'
|
|
6
|
+
import { rewritesOnInstall, stripSeedMarker } from '@/seed-marker'
|
|
7
|
+
import { mergeSections, pruneSections } from '@/tooling/gitignore'
|
|
8
|
+
import { ancestorsFirst, listFiles, type Manifest } from '@/tooling/manifest'
|
|
9
|
+
import {
|
|
10
|
+
applyScripts,
|
|
11
|
+
collectDeps,
|
|
12
|
+
readPackage,
|
|
13
|
+
serializePackage,
|
|
14
|
+
} from '@/tooling/package'
|
|
15
|
+
import { logAdd, logRemove, logStep } from '@/ui'
|
|
16
|
+
|
|
17
|
+
export async function injectConfigs(
|
|
18
|
+
chain: readonly Manifest[],
|
|
19
|
+
target: string,
|
|
20
|
+
): Promise<string[]> {
|
|
21
|
+
const applied: string[] = []
|
|
22
|
+
|
|
23
|
+
for (const manifest of ancestorsFirst(chain)) {
|
|
24
|
+
const files = listFiles(manifest.configsDir)
|
|
25
|
+
if (files.length === 0) continue
|
|
26
|
+
|
|
27
|
+
logStep(`Applying ${manifest.name} configs`)
|
|
28
|
+
for (const rel of files) {
|
|
29
|
+
await copyPreservingMode(
|
|
30
|
+
join(manifest.configsDir, rel),
|
|
31
|
+
join(target, rel),
|
|
32
|
+
)
|
|
33
|
+
logAdd(rel)
|
|
34
|
+
applied.push(rel)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return applied
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Writes a seed to a target that does not have it, dropping the stub marker on
|
|
43
|
+
* the way. The marker is toolkit bookkeeping read by the seed gate, so a target
|
|
44
|
+
* receiving it would carry a field its own tooling never reads. Only markdown
|
|
45
|
+
* carries frontmatter, and every other seed copies byte for byte.
|
|
46
|
+
*/
|
|
47
|
+
async function writeSeed(src: string, dest: string): Promise<void> {
|
|
48
|
+
if (!rewritesOnInstall(src)) {
|
|
49
|
+
await copyFile(src, dest)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await writeFile(dest, stripSeedMarker(await readFile(src, 'utf8')))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Copies a seed when the target lacks it. When the target already has one and
|
|
58
|
+
* the seed is a `.txt` word list, missing lines are appended and the file is
|
|
59
|
+
* re-sorted. Every other existing file is left untouched.
|
|
60
|
+
*/
|
|
61
|
+
async function mergeSeedFile(src: string, dest: string): Promise<void> {
|
|
62
|
+
await mkdir(dirname(dest), { recursive: true })
|
|
63
|
+
|
|
64
|
+
if (!existsSync(dest)) {
|
|
65
|
+
await writeSeed(src, dest)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!src.endsWith('.txt')) return
|
|
70
|
+
|
|
71
|
+
const existing = (await readFile(dest, 'utf8')).split('\n')
|
|
72
|
+
const incoming = (await readFile(src, 'utf8')).split('\n')
|
|
73
|
+
const missing = incoming.filter(
|
|
74
|
+
(word) => word !== '' && !existing.includes(word),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (missing.length === 0) return
|
|
78
|
+
|
|
79
|
+
const merged = [...existing.filter((line) => line !== ''), ...missing].sort()
|
|
80
|
+
await writeFile(dest, `${merged.join('\n')}\n`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function injectSeeds(
|
|
84
|
+
chain: readonly Manifest[],
|
|
85
|
+
target: string,
|
|
86
|
+
): Promise<string[]> {
|
|
87
|
+
const applied: string[] = []
|
|
88
|
+
|
|
89
|
+
for (const manifest of ancestorsFirst(chain)) {
|
|
90
|
+
const files = listFiles(manifest.seedsDir)
|
|
91
|
+
if (files.length === 0) continue
|
|
92
|
+
|
|
93
|
+
logStep(`Applying ${manifest.name} seeds`)
|
|
94
|
+
for (const rel of files) {
|
|
95
|
+
await mergeSeedFile(join(manifest.seedsDir, rel), join(target, rel))
|
|
96
|
+
logAdd(rel)
|
|
97
|
+
applied.push(rel)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return applied
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function injectGitignore(
|
|
105
|
+
chain: readonly Manifest[],
|
|
106
|
+
target: string,
|
|
107
|
+
): Promise<string[]> {
|
|
108
|
+
const path = join(target, '.gitignore')
|
|
109
|
+
const added: string[] = []
|
|
110
|
+
|
|
111
|
+
for (const manifest of ancestorsFirst(chain)) {
|
|
112
|
+
if (manifest.gitignore.length === 0) continue
|
|
113
|
+
|
|
114
|
+
const content = existsSync(path) ? await readFile(path, 'utf8') : ''
|
|
115
|
+
const result = mergeSections(content, manifest.gitignore)
|
|
116
|
+
if (result.added.length === 0) continue
|
|
117
|
+
|
|
118
|
+
logStep(`Applying ${manifest.name} gitignore`)
|
|
119
|
+
await writeFile(path, result.content)
|
|
120
|
+
for (const entry of result.added) {
|
|
121
|
+
logAdd(entry)
|
|
122
|
+
added.push(entry)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (added.length === 0 && !existsSync(path)) await writeFile(path, '')
|
|
127
|
+
|
|
128
|
+
return added
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function pruneGitignore(
|
|
132
|
+
chain: readonly Manifest[],
|
|
133
|
+
target: string,
|
|
134
|
+
): Promise<string[]> {
|
|
135
|
+
const path = join(target, '.gitignore')
|
|
136
|
+
if (!existsSync(path)) return []
|
|
137
|
+
|
|
138
|
+
const removed: string[] = []
|
|
139
|
+
|
|
140
|
+
for (const manifest of ancestorsFirst(chain)) {
|
|
141
|
+
if (manifest.gitignore.length === 0) continue
|
|
142
|
+
|
|
143
|
+
const content = await readFile(path, 'utf8')
|
|
144
|
+
const result = pruneSections(content, manifest.gitignore)
|
|
145
|
+
if (result.removed.length === 0) continue
|
|
146
|
+
|
|
147
|
+
await writeFile(path, result.content)
|
|
148
|
+
for (const entry of result.removed) {
|
|
149
|
+
logRemove(entry)
|
|
150
|
+
removed.push(entry)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return removed
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Installs missing dev dependencies, fills package scripts, and merges
|
|
159
|
+
* gitignore entries. This is the `inject_tooling_manifest` sequence, which
|
|
160
|
+
* assumed a target that already has a package.json.
|
|
161
|
+
*/
|
|
162
|
+
export async function injectManifest(
|
|
163
|
+
chain: readonly Manifest[],
|
|
164
|
+
target: string,
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const packagePath = join(target, 'package.json')
|
|
167
|
+
const pkg = readPackage(packagePath)
|
|
168
|
+
|
|
169
|
+
if (pkg) {
|
|
170
|
+
const missing = collectDeps(chain, pkg)
|
|
171
|
+
.filter((dep) => dep.state === 'missing')
|
|
172
|
+
.map((dep) => dep.spec)
|
|
173
|
+
|
|
174
|
+
if (missing.length > 0) {
|
|
175
|
+
logStep('Installing missing dependencies')
|
|
176
|
+
await $`bun add -D ${missing}`.cwd(target)
|
|
177
|
+
for (const spec of missing) logAdd(spec)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const fresh = readPackage(packagePath)
|
|
181
|
+
if (fresh) {
|
|
182
|
+
const result = applyScripts(fresh, chain)
|
|
183
|
+
if (result.added.length > 0 || result.overridden.length > 0) {
|
|
184
|
+
logStep('Applying scripts')
|
|
185
|
+
for (const key of result.added) logAdd(key)
|
|
186
|
+
for (const key of result.overridden) logAdd(key)
|
|
187
|
+
await writeFile(packagePath, serializePackage(result.pkg))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await injectGitignore(chain, target)
|
|
193
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { listStacks, loadManifest } from '@/tooling/manifest'
|
|
2
|
+
|
|
3
|
+
export interface StackSummary {
|
|
4
|
+
readonly name: string
|
|
5
|
+
readonly extends: string | null
|
|
6
|
+
readonly devDeps: number
|
|
7
|
+
readonly scripts: number
|
|
8
|
+
readonly gitignoreGroups: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Summarizes each installable stack. The counts come from `loadManifest`
|
|
13
|
+
* rather than the three `awk` programs the bash carried, so the list and the
|
|
14
|
+
* sync it previews read the same parse.
|
|
15
|
+
*/
|
|
16
|
+
export function buildStackSummaries(root: string): StackSummary[] {
|
|
17
|
+
const summaries: StackSummary[] = []
|
|
18
|
+
|
|
19
|
+
for (const stack of listStacks(root)) {
|
|
20
|
+
const manifest = loadManifest(root, stack)
|
|
21
|
+
if (!manifest) continue
|
|
22
|
+
|
|
23
|
+
summaries.push({
|
|
24
|
+
name: manifest.name,
|
|
25
|
+
extends: manifest.parent ?? null,
|
|
26
|
+
devDeps: manifest.devPackages.length,
|
|
27
|
+
scripts: Object.keys(manifest.scripts).length,
|
|
28
|
+
gitignoreGroups: manifest.gitignore.length,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return summaries
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function describeStack(summary: StackSummary): string {
|
|
36
|
+
const counts = `${summary.devDeps} dev deps, ${summary.scripts} scripts, ${summary.gitignoreGroups} gitignore groups`
|
|
37
|
+
const prefix = summary.extends === null ? '' : `extends: ${summary.extends}, `
|
|
38
|
+
return `${summary.name} (${prefix}${counts})`
|
|
39
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const EXCLUDED_STACKS = ['claude']
|
|
5
|
+
|
|
6
|
+
export interface GitignoreSection {
|
|
7
|
+
readonly header: string
|
|
8
|
+
readonly entries: readonly string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Manifest {
|
|
12
|
+
readonly name: string
|
|
13
|
+
readonly parent?: string
|
|
14
|
+
readonly dir: string
|
|
15
|
+
readonly configsDir: string
|
|
16
|
+
readonly seedsDir: string
|
|
17
|
+
readonly scripts: Readonly<Record<string, string>>
|
|
18
|
+
readonly scriptOverrides: Readonly<Record<string, string>>
|
|
19
|
+
readonly gitignore: readonly GitignoreSection[]
|
|
20
|
+
readonly devPackages: readonly string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ChainOptions {
|
|
24
|
+
readonly skipStack?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toolingDir(root: string): string {
|
|
28
|
+
return join(root, 'tooling')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function manifestPath(root: string, stack: string): string {
|
|
32
|
+
return join(toolingDir(root), stack, 'manifest.toml')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Lists every file under a directory, dotfiles included. Bun.Glob skips
|
|
37
|
+
* entries beginning with a dot unless `dot` is set, and tooling configs are
|
|
38
|
+
* almost entirely dotfiles, so omitting it silently matches nothing.
|
|
39
|
+
*/
|
|
40
|
+
export function listFiles(dir: string): string[] {
|
|
41
|
+
if (!existsSync(dir)) return []
|
|
42
|
+
return [
|
|
43
|
+
...new Bun.Glob('**/*').scanSync({ cwd: dir, onlyFiles: true, dot: true }),
|
|
44
|
+
].sort()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Reverses a chain so the furthest ancestor is applied first and nearer
|
|
49
|
+
* stacks overwrite it.
|
|
50
|
+
*/
|
|
51
|
+
export function ancestorsFirst(chain: readonly Manifest[]): Manifest[] {
|
|
52
|
+
return [...chain].reverse()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function stackExists(root: string, stack: string): boolean {
|
|
56
|
+
return existsSync(join(toolingDir(root), stack))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isStackExcluded(stack: string): boolean {
|
|
60
|
+
return EXCLUDED_STACKS.includes(stack)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Lists installable stack names, filtering the `claude` stack the way
|
|
65
|
+
* `is_tooling_stack_excluded` in `scripts/lib/tooling.sh` did. Claude is
|
|
66
|
+
* managed by `canon claude`, not `canon tooling`.
|
|
67
|
+
*/
|
|
68
|
+
export function listStacks(root: string): string[] {
|
|
69
|
+
const dir = toolingDir(root)
|
|
70
|
+
if (!existsSync(dir)) return []
|
|
71
|
+
|
|
72
|
+
return [...new Bun.Glob('*/manifest.toml').scanSync({ cwd: dir })]
|
|
73
|
+
.map((entry) => entry.split('/')[0])
|
|
74
|
+
.filter((name) => !isStackExcluded(name))
|
|
75
|
+
.sort()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reads one manifest into a typed shape. Returns undefined when the file is
|
|
80
|
+
* absent, matching the bash guards that silently skipped a missing manifest.
|
|
81
|
+
*/
|
|
82
|
+
export function loadManifest(
|
|
83
|
+
root: string,
|
|
84
|
+
stack: string,
|
|
85
|
+
): Manifest | undefined {
|
|
86
|
+
const path = manifestPath(root, stack)
|
|
87
|
+
if (!existsSync(path)) return undefined
|
|
88
|
+
|
|
89
|
+
const parsed = asTable(Bun.TOML.parse(readFileSync(path, 'utf8')))
|
|
90
|
+
const stackTable = asTable(parsed.stack)
|
|
91
|
+
const parent =
|
|
92
|
+
typeof stackTable.extends === 'string' ? stackTable.extends : ''
|
|
93
|
+
const scriptsTable = asTable(parsed.scripts)
|
|
94
|
+
const dir = join(toolingDir(root), stack)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
name: stack,
|
|
98
|
+
parent: parent === '' ? undefined : parent,
|
|
99
|
+
dir,
|
|
100
|
+
configsDir: join(dir, 'configs'),
|
|
101
|
+
seedsDir: join(dir, 'seeds'),
|
|
102
|
+
scripts: pickStrings(scriptsTable),
|
|
103
|
+
scriptOverrides: pickStrings(asTable(scriptsTable.override)),
|
|
104
|
+
gitignore: readGitignoreSections(asTable(parsed.gitignore)),
|
|
105
|
+
devPackages: readDevPackages(parsed.dependencies),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Walks the `extends` chain once, self first. Callers that apply parent
|
|
111
|
+
* values before child values reverse the result. Replaces the seventeen
|
|
112
|
+
* copies of `grep '^extends' | cut -d'"' -f2` the bash carried.
|
|
113
|
+
*
|
|
114
|
+
* A stack matching `skipStack` truncates the chain at that point, mirroring
|
|
115
|
+
* the `[ "$stack" = "${SKIP_STACK:-}" ] && return` guard.
|
|
116
|
+
*/
|
|
117
|
+
export function resolveChain(
|
|
118
|
+
root: string,
|
|
119
|
+
stack: string,
|
|
120
|
+
options: ChainOptions = {},
|
|
121
|
+
): Manifest[] {
|
|
122
|
+
const chain: Manifest[] = []
|
|
123
|
+
const seen = new Set<string>()
|
|
124
|
+
let current: string | undefined = stack
|
|
125
|
+
|
|
126
|
+
while (current !== undefined) {
|
|
127
|
+
if (current === options.skipStack) break
|
|
128
|
+
if (seen.has(current)) break
|
|
129
|
+
seen.add(current)
|
|
130
|
+
|
|
131
|
+
const manifest: Manifest | undefined = loadManifest(root, current)
|
|
132
|
+
if (!manifest) break
|
|
133
|
+
|
|
134
|
+
chain.push(manifest)
|
|
135
|
+
current = manifest.parent
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return chain
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function asTable(value: unknown): Record<string, unknown> {
|
|
142
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
143
|
+
return {}
|
|
144
|
+
}
|
|
145
|
+
return value as Record<string, unknown>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function pickStrings(table: Record<string, unknown>): Record<string, string> {
|
|
149
|
+
const result: Record<string, string> = {}
|
|
150
|
+
for (const [key, value] of Object.entries(table)) {
|
|
151
|
+
if (typeof value === 'string') result[key] = value
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readGitignoreSections(
|
|
157
|
+
table: Record<string, unknown>,
|
|
158
|
+
): GitignoreSection[] {
|
|
159
|
+
const sections: GitignoreSection[] = []
|
|
160
|
+
for (const [header, value] of Object.entries(table)) {
|
|
161
|
+
if (!Array.isArray(value)) continue
|
|
162
|
+
const entries = value.filter(
|
|
163
|
+
(entry): entry is string => typeof entry === 'string' && entry !== '',
|
|
164
|
+
)
|
|
165
|
+
sections.push({ header, entries })
|
|
166
|
+
}
|
|
167
|
+
return sections
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function readDevPackages(dependencies: unknown): string[] {
|
|
171
|
+
const dev = asTable(asTable(dependencies).dev)
|
|
172
|
+
if (!Array.isArray(dev.packages)) return []
|
|
173
|
+
return dev.packages.filter(
|
|
174
|
+
(entry): entry is string => typeof entry === 'string' && entry !== '',
|
|
175
|
+
)
|
|
176
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import type { Manifest } from '@/tooling/manifest'
|
|
3
|
+
|
|
4
|
+
export interface PackageJson {
|
|
5
|
+
readonly dependencies?: Record<string, string>
|
|
6
|
+
readonly devDependencies?: Record<string, string>
|
|
7
|
+
readonly scripts?: Record<string, string>
|
|
8
|
+
readonly [key: string]: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ScriptState {
|
|
12
|
+
readonly key: string
|
|
13
|
+
readonly state: 'missing' | 'drifted' | 'matching'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DepState {
|
|
17
|
+
readonly spec: string
|
|
18
|
+
readonly name: string
|
|
19
|
+
readonly state: 'missing' | 'present'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readPackage(path: string): PackageJson | undefined {
|
|
23
|
+
if (!existsSync(path)) return undefined
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(path, 'utf8')) as PackageJson
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function serializePackage(pkg: PackageJson): string {
|
|
32
|
+
return `${JSON.stringify(pkg, null, 2)}\n`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Splits a dependency spec into its package name, handling the scoped case
|
|
37
|
+
* where the leading `@` is part of the name rather than a version separator.
|
|
38
|
+
* `@scope/pkg@^1.2.3` yields `@scope/pkg`, `react@19` yields `react`.
|
|
39
|
+
*/
|
|
40
|
+
export function parseSpec(spec: string): { name: string; pin: string } {
|
|
41
|
+
let name = spec
|
|
42
|
+
|
|
43
|
+
if (spec.startsWith('@')) {
|
|
44
|
+
const rest = spec.slice(1)
|
|
45
|
+
if (rest.includes('@')) name = `@${rest.slice(0, rest.indexOf('@'))}`
|
|
46
|
+
} else {
|
|
47
|
+
name = spec.split('@')[0]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const pin = spec === name ? '' : spec.slice(name.length + 1)
|
|
51
|
+
return { name, pin }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function firstInteger(value: string): string | undefined {
|
|
55
|
+
return value.match(/[0-9]+/)?.[0]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compares a manifest pin against what package.json records. A spec counts as
|
|
60
|
+
* missing when absent, or when the pinned major differs from the installed
|
|
61
|
+
* major. Anything looser than a major mismatch is left alone.
|
|
62
|
+
*/
|
|
63
|
+
function isSatisfied(installed: string | undefined, pin: string): boolean {
|
|
64
|
+
if (installed === undefined) return false
|
|
65
|
+
if (pin === '') return true
|
|
66
|
+
|
|
67
|
+
const pinMajor = firstInteger(pin)
|
|
68
|
+
const installedMajor = firstInteger(installed)
|
|
69
|
+
if (pinMajor === undefined) return true
|
|
70
|
+
|
|
71
|
+
return pinMajor === installedMajor
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolves dev dependencies across the chain, ancestors first, deduplicated by
|
|
76
|
+
* package name so the outermost stack that names a package wins.
|
|
77
|
+
*/
|
|
78
|
+
export function collectDeps(
|
|
79
|
+
chain: readonly Manifest[],
|
|
80
|
+
pkg: PackageJson,
|
|
81
|
+
): DepState[] {
|
|
82
|
+
const installed = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
83
|
+
const seen = new Set<string>()
|
|
84
|
+
const states: DepState[] = []
|
|
85
|
+
|
|
86
|
+
for (const manifest of [...chain].reverse()) {
|
|
87
|
+
for (const spec of manifest.devPackages) {
|
|
88
|
+
const { name, pin } = parseSpec(spec)
|
|
89
|
+
if (name === '' || seen.has(name)) continue
|
|
90
|
+
seen.add(name)
|
|
91
|
+
|
|
92
|
+
states.push({
|
|
93
|
+
spec,
|
|
94
|
+
name,
|
|
95
|
+
state: isSatisfied(installed[name], pin) ? 'present' : 'missing',
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return states
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Compares manifest scripts against package.json, nearest stack first, so a
|
|
105
|
+
* child definition shadows the parent it extends.
|
|
106
|
+
*/
|
|
107
|
+
export function collectScripts(
|
|
108
|
+
chain: readonly Manifest[],
|
|
109
|
+
pkg: PackageJson,
|
|
110
|
+
): ScriptState[] {
|
|
111
|
+
const scripts = pkg.scripts ?? {}
|
|
112
|
+
const seen = new Set<string>()
|
|
113
|
+
const states: ScriptState[] = []
|
|
114
|
+
|
|
115
|
+
for (const manifest of chain) {
|
|
116
|
+
for (const [key, value] of Object.entries(manifest.scripts)) {
|
|
117
|
+
if (seen.has(key)) continue
|
|
118
|
+
seen.add(key)
|
|
119
|
+
|
|
120
|
+
const current = scripts[key]
|
|
121
|
+
if (current === undefined) {
|
|
122
|
+
states.push({ key, state: 'missing' })
|
|
123
|
+
} else if (current !== value) {
|
|
124
|
+
states.push({ key, state: 'drifted' })
|
|
125
|
+
} else {
|
|
126
|
+
states.push({ key, state: 'matching' })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return states
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Fills scripts the target does not define, nearest stack first, then replays
|
|
136
|
+
* `[scripts.override]` from the furthest ancestor inward so the nearest stack
|
|
137
|
+
* has the last word. This is the ordering `apply_stack_scripts` produced
|
|
138
|
+
* through its recursion.
|
|
139
|
+
*/
|
|
140
|
+
export function applyScripts(
|
|
141
|
+
pkg: PackageJson,
|
|
142
|
+
chain: readonly Manifest[],
|
|
143
|
+
): { pkg: PackageJson; added: string[]; overridden: string[] } {
|
|
144
|
+
const scripts: Record<string, string> = { ...pkg.scripts }
|
|
145
|
+
const added: string[] = []
|
|
146
|
+
const overridden: string[] = []
|
|
147
|
+
|
|
148
|
+
for (const manifest of chain) {
|
|
149
|
+
for (const [key, value] of Object.entries(manifest.scripts)) {
|
|
150
|
+
if (scripts[key] === undefined) {
|
|
151
|
+
scripts[key] = value
|
|
152
|
+
added.push(key)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const manifest of [...chain].reverse()) {
|
|
158
|
+
for (const [key, value] of Object.entries(manifest.scriptOverrides)) {
|
|
159
|
+
if (scripts[key] === value) continue
|
|
160
|
+
scripts[key] = value
|
|
161
|
+
if (!overridden.includes(key)) overridden.push(key)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { pkg: { ...pkg, scripts }, added, overridden }
|
|
166
|
+
}
|