@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,455 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import { gitEnv } from '@/git-env'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The folders a backup carries, relative to `.claude/`. Most of them are the
|
|
8
|
+
* `# Claude` group the claude manifest ships, minus three: `.claude/.tmp`,
|
|
9
|
+
* which is defined as deletable without loss, `.claude/worktrees/`, whose
|
|
10
|
+
* contents belong to the enclosing repository already, and
|
|
11
|
+
* `.claude/.records.git/`, which is the history the rest are pushed into. The
|
|
12
|
+
* list is spelled out rather than read off that group so adding an ignore entry
|
|
13
|
+
* cannot silently enlarge the payload.
|
|
14
|
+
*
|
|
15
|
+
* `diagrams` is the one name the manifest group does not carry, so a target
|
|
16
|
+
* tracks it where this repository ignores it. That is the second reason to
|
|
17
|
+
* spell the list out: the manifest bounds it rather than producing it, and no
|
|
18
|
+
* count taken off that group matches this one. `scripts/core/check-ignore-parity.sh`
|
|
19
|
+
* compares the two lists and holds the reason `diagrams` stays apart, so the
|
|
20
|
+
* one difference is enforced rather than assumed.
|
|
21
|
+
*
|
|
22
|
+
* The manifest group is the one this reads rather than the enclosing
|
|
23
|
+
* repository's own `.gitignore`, which spreads the same entries across two
|
|
24
|
+
* headers and carries `.claude/README.md` that no target receives.
|
|
25
|
+
*
|
|
26
|
+
* Each entry is a top-level record folder and every archive sits inside the one
|
|
27
|
+
* it archives, so the three former archive entries are covered by their parents
|
|
28
|
+
* rather than named here. That is what keeps this list at one line per surface
|
|
29
|
+
* as archives spread, which a sibling-per-archive layout could not.
|
|
30
|
+
*
|
|
31
|
+
* `RECORD_KINDS` in `validate.ts` overlaps this on five names and carries one
|
|
32
|
+
* more that no backup reaches. The two lists differ on purpose: one is what a
|
|
33
|
+
* standard governs, this is what a disk loss would take, and `standards` is
|
|
34
|
+
* tracked so a backup would carry a second copy of committed files.
|
|
35
|
+
*/
|
|
36
|
+
export const BACKED_FOLDERS = [
|
|
37
|
+
'diagrams',
|
|
38
|
+
'groundwork',
|
|
39
|
+
'intake',
|
|
40
|
+
'memory',
|
|
41
|
+
'plans',
|
|
42
|
+
'proposals',
|
|
43
|
+
'review',
|
|
44
|
+
'tasks',
|
|
45
|
+
'teach',
|
|
46
|
+
] as const
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Names that have left `BACKED_FOLDERS` and whose removal still has to reach a
|
|
50
|
+
* records history once.
|
|
51
|
+
*
|
|
52
|
+
* Dropping a name from the list above stops it entering the pathspec, so `add`
|
|
53
|
+
* never stages its deletion, the remote keeps the folder forever, and a `pull`
|
|
54
|
+
* onto another machine restores it beside whatever replaced it. These three are
|
|
55
|
+
* the archives that moved inside the records they archive, so the same files
|
|
56
|
+
* are already on the remote under their new paths.
|
|
57
|
+
*
|
|
58
|
+
* Retire a name here once no records history still carries it. Nothing measures
|
|
59
|
+
* that, so the cost of leaving one is three pathspec entries that match nothing
|
|
60
|
+
* and are filtered out before `add` ever sees them.
|
|
61
|
+
*/
|
|
62
|
+
const RETIRED_FOLDERS = [
|
|
63
|
+
'plans-archive',
|
|
64
|
+
'review-archive',
|
|
65
|
+
'task-archive',
|
|
66
|
+
] as const
|
|
67
|
+
|
|
68
|
+
/** Holds the records history beside the folders it tracks, ignored by the enclosing repository. */
|
|
69
|
+
const RECORDS_GIT_DIR = join('.claude', '.records.git')
|
|
70
|
+
|
|
71
|
+
const WORK_TREE = '.claude'
|
|
72
|
+
|
|
73
|
+
/** Both directions name the branch, so a machine whose `init.defaultBranch` differs still lands on it. */
|
|
74
|
+
const RECORDS_BRANCH = 'main'
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The records history is machine-written and nobody reads its authorship, so a
|
|
78
|
+
* fixed identity keeps `push` from failing inside a git hook on a machine where
|
|
79
|
+
* `user.email` was never configured.
|
|
80
|
+
*/
|
|
81
|
+
const COMMIT_IDENTITY = [
|
|
82
|
+
'-c',
|
|
83
|
+
'user.name=canon',
|
|
84
|
+
'-c',
|
|
85
|
+
'user.email=canon@local',
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
export const BACKUP_REFUSALS = [
|
|
89
|
+
'no-repository',
|
|
90
|
+
'no-remote',
|
|
91
|
+
'remote-unreadable',
|
|
92
|
+
'remote-shared',
|
|
93
|
+
'no-remote-records',
|
|
94
|
+
'local-changes',
|
|
95
|
+
'local-ahead',
|
|
96
|
+
'git-failed',
|
|
97
|
+
] as const
|
|
98
|
+
|
|
99
|
+
export type BackupRefusal = (typeof BACKUP_REFUSALS)[number]
|
|
100
|
+
|
|
101
|
+
export interface BackupRefused {
|
|
102
|
+
readonly ok: false
|
|
103
|
+
readonly reason: BackupRefusal
|
|
104
|
+
readonly message: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface PushReport {
|
|
108
|
+
readonly ok: true
|
|
109
|
+
readonly root: string
|
|
110
|
+
readonly folders: readonly string[]
|
|
111
|
+
readonly changed: number
|
|
112
|
+
readonly commit?: string
|
|
113
|
+
readonly pushed: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface PullReport {
|
|
117
|
+
readonly ok: true
|
|
118
|
+
readonly root: string
|
|
119
|
+
readonly folders: readonly string[]
|
|
120
|
+
readonly commit: string
|
|
121
|
+
readonly files: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type PushOutcome = PushReport | BackupRefused
|
|
125
|
+
export type PullOutcome = PullReport | BackupRefused
|
|
126
|
+
|
|
127
|
+
interface GitResult {
|
|
128
|
+
readonly ok: boolean
|
|
129
|
+
readonly text: string
|
|
130
|
+
readonly stderr: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Runs one git command against the records history, from the work tree.
|
|
135
|
+
*
|
|
136
|
+
* All three flags go on every call. `git --git-dir=<path> init` writes
|
|
137
|
+
* `core.bare = true`, and an explicit `--work-tree` is what overrides it, so
|
|
138
|
+
* dropping that flag on a single call reads the enclosing project as the tree
|
|
139
|
+
* and stages everything in it.
|
|
140
|
+
*
|
|
141
|
+
* `-C` is what makes a bare pathspec like `groundwork` mean the work-tree root
|
|
142
|
+
* wherever the caller stands. Git derives a pathspec prefix from the current
|
|
143
|
+
* directory, so without it a caller sitting inside `.claude/`, which is every
|
|
144
|
+
* session in a linked worktree under `.claude/worktrees/<name>/`, prefixes
|
|
145
|
+
* each name with its own path and matches nothing. The root a caller names
|
|
146
|
+
* does not reach that prefix, so `--root` cannot stand in for this.
|
|
147
|
+
*
|
|
148
|
+
* Both paths are absolute because `-C` takes effect before the other two flags
|
|
149
|
+
* are read, so a relative root would otherwise send them looking inside the
|
|
150
|
+
* work tree.
|
|
151
|
+
*/
|
|
152
|
+
async function records(root: string, args: string[]): Promise<GitResult> {
|
|
153
|
+
const gitDir = resolve(root, RECORDS_GIT_DIR)
|
|
154
|
+
const workTree = resolve(root, WORK_TREE)
|
|
155
|
+
|
|
156
|
+
const result =
|
|
157
|
+
await $`git -C ${workTree} --git-dir=${gitDir} --work-tree=${workTree} ${args}`
|
|
158
|
+
.env(gitEnv())
|
|
159
|
+
.quiet()
|
|
160
|
+
.nothrow()
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
ok: result.exitCode === 0,
|
|
164
|
+
text: result.stdout.toString().trim(),
|
|
165
|
+
stderr: result.stderr.toString().trim(),
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function refuse(reason: BackupRefusal, message: string): BackupRefused {
|
|
170
|
+
return { ok: false, reason, message }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function failed(action: string, result: GitResult): BackupRefused {
|
|
174
|
+
return refuse(
|
|
175
|
+
'git-failed',
|
|
176
|
+
`git ${action} failed against the records history: ${result.stderr || 'no output'}.`,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reduces a remote URL to `host/path`, so every spelling of one repository
|
|
182
|
+
* compares equal.
|
|
183
|
+
*
|
|
184
|
+
* Transport is what the reduction drops. `git@github.com:owner/repo.git` and
|
|
185
|
+
* `https://github.com/owner/repo` name the same repository, and comparing them
|
|
186
|
+
* as written passes a records origin that publishes the payload through the
|
|
187
|
+
* other protocol.
|
|
188
|
+
*/
|
|
189
|
+
function remoteIdentity(url: string): string {
|
|
190
|
+
return url
|
|
191
|
+
.trim()
|
|
192
|
+
.toLowerCase()
|
|
193
|
+
.replace(/^[a-z+]+:\/\//, '')
|
|
194
|
+
.replace(/^[^@/]+@/, '')
|
|
195
|
+
.replace(/^([^/:]+):/, '$1/')
|
|
196
|
+
.replace(/\/+$/, '')
|
|
197
|
+
.replace(/\.git$/, '')
|
|
198
|
+
.replace(/\/+$/, '')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Lists every remote of the enclosing project, or undefined when git cannot
|
|
203
|
+
* answer.
|
|
204
|
+
*
|
|
205
|
+
* The caller refuses on undefined rather than smoothing it into an empty list.
|
|
206
|
+
* An empty list clears the gate below for every URL, so a git that failed for
|
|
207
|
+
* any reason would publish the payload to whatever origin the records history
|
|
208
|
+
* happens to name. A project with no remotes answers `0` with an exit of zero,
|
|
209
|
+
* so the two states stay distinguishable.
|
|
210
|
+
*/
|
|
211
|
+
async function enclosingRemoteUrls(
|
|
212
|
+
root: string,
|
|
213
|
+
): Promise<string[] | undefined> {
|
|
214
|
+
const result = await $`git -C ${root} remote -v`
|
|
215
|
+
.env(gitEnv())
|
|
216
|
+
.quiet()
|
|
217
|
+
.nothrow()
|
|
218
|
+
if (result.exitCode !== 0) return undefined
|
|
219
|
+
|
|
220
|
+
return result.stdout
|
|
221
|
+
.toString()
|
|
222
|
+
.split('\n')
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.map((line) => line.split(/\s+/)[1] ?? '')
|
|
225
|
+
.filter(Boolean)
|
|
226
|
+
.map(remoteIdentity)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Clears the four gates both verbs share and returns the records remote URL.
|
|
231
|
+
*
|
|
232
|
+
* The last two are the ones the payload depends on. This repository is public,
|
|
233
|
+
* so a records branch on any of its remotes serves the memory pen and the
|
|
234
|
+
* groundwork trails to anyone who fetches all refs. Comparing the configured
|
|
235
|
+
* URL against every remote of the enclosing repository is what keeps a
|
|
236
|
+
* misconfigured `origin` from publishing them, and refusing when that list
|
|
237
|
+
* cannot be read is what keeps a failed comparison from reading as a pass.
|
|
238
|
+
*/
|
|
239
|
+
async function resolveRemote(root: string): Promise<string | BackupRefused> {
|
|
240
|
+
if (!existsSync(join(root, RECORDS_GIT_DIR))) {
|
|
241
|
+
return refuse(
|
|
242
|
+
'no-repository',
|
|
243
|
+
[
|
|
244
|
+
`No records history at ${RECORDS_GIT_DIR}. Create it once, against a private repository:`,
|
|
245
|
+
` git --git-dir=${join(root, RECORDS_GIT_DIR)} init`,
|
|
246
|
+
` git --git-dir=${join(root, RECORDS_GIT_DIR)} remote add origin <private-repo-url>`,
|
|
247
|
+
].join('\n'),
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const remote = await records(root, ['remote', 'get-url', 'origin'])
|
|
252
|
+
if (!remote.ok || remote.text.length === 0) {
|
|
253
|
+
return refuse(
|
|
254
|
+
'no-remote',
|
|
255
|
+
[
|
|
256
|
+
'The records history has no origin. Point it at a private repository:',
|
|
257
|
+
` git --git-dir=${join(root, RECORDS_GIT_DIR)} remote add origin <private-repo-url>`,
|
|
258
|
+
].join('\n'),
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const enclosing = await enclosingRemoteUrls(root)
|
|
263
|
+
if (!enclosing) {
|
|
264
|
+
return refuse(
|
|
265
|
+
'remote-unreadable',
|
|
266
|
+
`Cannot read the remotes of the project at ${root}, so the records origin cannot be checked against them. Records carry the memory pen and the groundwork trails, and an unchecked origin risks publishing them.`,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const url = remoteIdentity(remote.text)
|
|
271
|
+
if (enclosing.includes(url)) {
|
|
272
|
+
return refuse(
|
|
273
|
+
'remote-shared',
|
|
274
|
+
`The records origin ${remote.text} is a remote of this project. Records carry the memory pen and the groundwork trails, so they need a repository of their own.`,
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return url
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The subset of the backed and retired names a pathspec can name: on disk, or
|
|
283
|
+
* already in the records index.
|
|
284
|
+
*
|
|
285
|
+
* A pathspec matching neither fails the whole `add`, which is why the subset
|
|
286
|
+
* exists. The index half is what covers a folder deleted in full. Reading disk
|
|
287
|
+
* alone drops it from the pathspec, so its deletion never stages, the remote
|
|
288
|
+
* keeps it forever, and a later `pull` restores it past the gate that refuses
|
|
289
|
+
* every other unpushed deletion.
|
|
290
|
+
*
|
|
291
|
+
* The retired names are the same case one level up, where the folder left the
|
|
292
|
+
* backed list rather than the disk, and the index is the only side that still
|
|
293
|
+
* knows it existed.
|
|
294
|
+
*/
|
|
295
|
+
async function scopedFolders(root: string): Promise<string[]> {
|
|
296
|
+
const tracked = await records(root, ['ls-files'])
|
|
297
|
+
const indexed = new Set(
|
|
298
|
+
tracked.ok ? tracked.text.split('\n').filter(Boolean).map(topSegment) : [],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return [...BACKED_FOLDERS, ...RETIRED_FOLDERS].filter(
|
|
302
|
+
(folder) =>
|
|
303
|
+
existsSync(join(root, WORK_TREE, folder)) || indexed.has(folder),
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function topSegment(path: string): string {
|
|
308
|
+
return path.split('/')[0]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** What a report names, which is the folders a reader can go and open. */
|
|
312
|
+
function presentFolders(root: string): string[] {
|
|
313
|
+
return BACKED_FOLDERS.filter((folder) =>
|
|
314
|
+
existsSync(join(root, WORK_TREE, folder)),
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function countLines(text: string): number {
|
|
319
|
+
return text.split('\n').filter(Boolean).length
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Stages the backed folders, commits when any of them changed, and pushes.
|
|
324
|
+
*
|
|
325
|
+
* The push runs whether or not this call committed, because a previous run can
|
|
326
|
+
* have committed and then failed to reach the network. Skipping it would leave
|
|
327
|
+
* that commit on one disk, which is the state the whole verb exists to end.
|
|
328
|
+
*/
|
|
329
|
+
export async function pushRecords(root: string): Promise<PushOutcome> {
|
|
330
|
+
const remote = await resolveRemote(root)
|
|
331
|
+
if (typeof remote !== 'string') return remote
|
|
332
|
+
|
|
333
|
+
const scope = await scopedFolders(root)
|
|
334
|
+
|
|
335
|
+
if (scope.length > 0) {
|
|
336
|
+
// `-f` is what carries the payload: every backed folder is ignored by the
|
|
337
|
+
// enclosing repository, and the pathspecs are the whole list, so nothing
|
|
338
|
+
// outside them can enter the index however the ignore rules read.
|
|
339
|
+
const staged = await records(root, ['add', '-A', '-f', '--', ...scope])
|
|
340
|
+
if (!staged.ok) return failed('add', staged)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const diff = await records(root, [
|
|
344
|
+
'diff',
|
|
345
|
+
'--cached',
|
|
346
|
+
'--name-only',
|
|
347
|
+
'--',
|
|
348
|
+
...scope,
|
|
349
|
+
])
|
|
350
|
+
if (!diff.ok) return failed('diff', diff)
|
|
351
|
+
|
|
352
|
+
const changed = countLines(diff.text)
|
|
353
|
+
|
|
354
|
+
if (changed > 0) {
|
|
355
|
+
const stamp = new Date().toISOString().replace('T', ' ').slice(0, 16)
|
|
356
|
+
const commit = await records(root, [
|
|
357
|
+
...COMMIT_IDENTITY,
|
|
358
|
+
'commit',
|
|
359
|
+
'--quiet',
|
|
360
|
+
'-m',
|
|
361
|
+
`records: ${changed} changed at ${stamp}`,
|
|
362
|
+
])
|
|
363
|
+
if (!commit.ok) return failed('commit', commit)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const folders = presentFolders(root)
|
|
367
|
+
const head = await records(root, ['rev-parse', '--short', 'HEAD'])
|
|
368
|
+
if (!head.ok) {
|
|
369
|
+
return { ok: true, root, folders, changed, pushed: false }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const pushed = await records(root, [
|
|
373
|
+
'push',
|
|
374
|
+
'origin',
|
|
375
|
+
`HEAD:refs/heads/${RECORDS_BRANCH}`,
|
|
376
|
+
])
|
|
377
|
+
if (!pushed.ok) return failed('push', pushed)
|
|
378
|
+
|
|
379
|
+
return { ok: true, root, folders, changed, commit: head.text, pushed: true }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Fetches the records history and writes it into the backed folders.
|
|
384
|
+
*
|
|
385
|
+
* The two directions are not symmetric. A push only ever adds, while a pull
|
|
386
|
+
* onto a machine holding work that never reached the remote would discard it,
|
|
387
|
+
* so both gates below refuse rather than choosing a merge strategy. A person
|
|
388
|
+
* resolves by pushing first or by moving the local folders aside.
|
|
389
|
+
*/
|
|
390
|
+
export async function pullRecords(root: string): Promise<PullOutcome> {
|
|
391
|
+
const remote = await resolveRemote(root)
|
|
392
|
+
if (typeof remote !== 'string') return remote
|
|
393
|
+
|
|
394
|
+
const fetched = await records(root, [
|
|
395
|
+
'fetch',
|
|
396
|
+
'--quiet',
|
|
397
|
+
'origin',
|
|
398
|
+
`refs/heads/${RECORDS_BRANCH}`,
|
|
399
|
+
])
|
|
400
|
+
if (!fetched.ok) {
|
|
401
|
+
// A missing branch and an unreachable remote both fail the fetch, and only
|
|
402
|
+
// the first is an ordinary state a person resolves by pushing once.
|
|
403
|
+
if (fetched.stderr.includes("couldn't find remote ref")) {
|
|
404
|
+
return refuse(
|
|
405
|
+
'no-remote-records',
|
|
406
|
+
`The records origin carries no ${RECORDS_BRANCH} branch yet. Run canon records push from the machine holding the records.`,
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
return failed('fetch', fetched)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const target = await records(root, ['rev-parse', 'FETCH_HEAD'])
|
|
413
|
+
if (!target.ok) return failed('rev-parse', target)
|
|
414
|
+
|
|
415
|
+
const scope = await scopedFolders(root)
|
|
416
|
+
|
|
417
|
+
if (scope.length > 0) {
|
|
418
|
+
const dirty = await records(root, ['status', '--porcelain', '--', ...scope])
|
|
419
|
+
if (!dirty.ok) return failed('status', dirty)
|
|
420
|
+
|
|
421
|
+
if (dirty.text.length > 0) {
|
|
422
|
+
return refuse(
|
|
423
|
+
'local-changes',
|
|
424
|
+
`${countLines(dirty.text)} local record(s) are not in the records history. Run canon records push first, or move them aside.`,
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const head = await records(root, ['rev-parse', '--verify', '--quiet', 'HEAD'])
|
|
430
|
+
if (head.ok && head.text.length > 0) {
|
|
431
|
+
const ahead = await records(root, ['rev-list', `${target.text}..HEAD`])
|
|
432
|
+
if (!ahead.ok) return failed('rev-list', ahead)
|
|
433
|
+
|
|
434
|
+
if (ahead.text.length > 0) {
|
|
435
|
+
return refuse(
|
|
436
|
+
'local-ahead',
|
|
437
|
+
`${countLines(ahead.text)} local commit(s) have not reached the records origin. Run canon records push first.`,
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const reset = await records(root, ['reset', '--hard', '--quiet', target.text])
|
|
443
|
+
if (!reset.ok) return failed('reset', reset)
|
|
444
|
+
|
|
445
|
+
const files = await records(root, ['ls-files'])
|
|
446
|
+
if (!files.ok) return failed('ls-files', files)
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
ok: true,
|
|
450
|
+
root,
|
|
451
|
+
folders: presentFolders(root),
|
|
452
|
+
commit: target.text.slice(0, 7),
|
|
453
|
+
files: countLines(files.text),
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
2
|
+
import {
|
|
3
|
+
CATEGORY_BY_TYPE,
|
|
4
|
+
type FindingRemedy,
|
|
5
|
+
MEMORY_NAME,
|
|
6
|
+
memoryType,
|
|
7
|
+
} from '@/records/validate'
|
|
8
|
+
|
|
9
|
+
export const MIGRATE_REFUSALS = ['field-unrecoverable'] as const
|
|
10
|
+
|
|
11
|
+
export type MigrateRefusal = (typeof MIGRATE_REFUSALS)[number]
|
|
12
|
+
|
|
13
|
+
export interface MigrateRepair {
|
|
14
|
+
readonly ok: true
|
|
15
|
+
readonly text: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MigrateRefused {
|
|
19
|
+
readonly ok: false
|
|
20
|
+
readonly reason: MigrateRefusal
|
|
21
|
+
readonly message: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MigrateOutcome = MigrateRepair | MigrateRefused
|
|
25
|
+
|
|
26
|
+
function refuse(message: string): MigrateRefused {
|
|
27
|
+
return { ok: false, reason: 'field-unrecoverable', message }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Repairs the one recoverable shape of `frontmatter-incomplete`: a memory
|
|
32
|
+
* record missing `category` alone. The value sits in the filename's type
|
|
33
|
+
* prefix already, which is the same fact `checkMemory` derives it from, so a
|
|
34
|
+
* repair here writes back what the record already states rather than a value
|
|
35
|
+
* a transform invented. It re-reads the file rather than trusting a value
|
|
36
|
+
* carried on the `Finding`, so a transform and the check it repairs cannot
|
|
37
|
+
* drift apart.
|
|
38
|
+
*/
|
|
39
|
+
function categoryFromName(name: string, text: string): MigrateOutcome {
|
|
40
|
+
const frontmatter = parseFrontmatter(text)
|
|
41
|
+
if (!frontmatter) {
|
|
42
|
+
return refuse(`${name} carries no frontmatter block to add category to.`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (readField(frontmatter, 'category')) {
|
|
46
|
+
return refuse(`${name} already carries a category.`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const match = MEMORY_NAME.exec(name)
|
|
50
|
+
const type = match ? memoryType(match[1]) : undefined
|
|
51
|
+
if (!type) {
|
|
52
|
+
return refuse(
|
|
53
|
+
`${name} is not named <type>-<slug>.md, so category has no type to derive from.`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rewritten = frontmatter.raw.replace(
|
|
58
|
+
/\n---$/,
|
|
59
|
+
`\ncategory: ${CATEGORY_BY_TYPE[type]}\n---`,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return { ok: true, text: rewritten + text.slice(frontmatter.raw.length) }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TRANSFORMS: Readonly<
|
|
66
|
+
Record<FindingRemedy, (name: string, text: string) => MigrateOutcome>
|
|
67
|
+
> = {
|
|
68
|
+
'category-from-name': categoryFromName,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Applies the transform a finding's `remedy` names against its own text. */
|
|
72
|
+
export function migrateRecord(
|
|
73
|
+
remedy: FindingRemedy,
|
|
74
|
+
name: string,
|
|
75
|
+
text: string,
|
|
76
|
+
): MigrateOutcome {
|
|
77
|
+
return TRANSFORMS[remedy](name, text)
|
|
78
|
+
}
|