@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,506 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { regenOne } from '@/indexes/regen'
|
|
5
|
+
|
|
6
|
+
const TASKS_DIR = join('.claude', 'tasks')
|
|
7
|
+
const ARCHIVE_DIR = join(TASKS_DIR, 'archive')
|
|
8
|
+
const PLANS_DIR = join('.claude', 'plans')
|
|
9
|
+
const PLANS_ARCHIVE_DIR = join(PLANS_DIR, 'archive')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Siblings that sit on the board without being tasks: the generated index, the
|
|
13
|
+
* hand-maintained ordering, the unordered backlog beside it, and a handoff a
|
|
14
|
+
* session wrote before the file took one name per session. `validate` reads the
|
|
15
|
+
* same list, so neither verb can count a sibling as a task the other does not.
|
|
16
|
+
*/
|
|
17
|
+
export const RESERVED_STEMS = [
|
|
18
|
+
'index',
|
|
19
|
+
'priority',
|
|
20
|
+
'backlog',
|
|
21
|
+
'session',
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
/** The pre-compaction handoff takes one file per session, so its stems vary. */
|
|
25
|
+
const SESSION_MAP_PREFIX = 'session-'
|
|
26
|
+
|
|
27
|
+
export function isReservedStem(stem: string): boolean {
|
|
28
|
+
const reserved: readonly string[] = RESERVED_STEMS
|
|
29
|
+
|
|
30
|
+
return reserved.includes(stem) || stem.startsWith(SESSION_MAP_PREFIX)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `bad-input` describes the command line rather than the board, which is the
|
|
35
|
+
* split `record.ts` draws for the same reason. A caller naming two selectors
|
|
36
|
+
* answered as `ambiguous` sends a session to repair a task citation that is
|
|
37
|
+
* fine, so the three task verbs answer one mistake one way.
|
|
38
|
+
*/
|
|
39
|
+
export const ARCHIVE_REFUSALS = [
|
|
40
|
+
'no-board',
|
|
41
|
+
'no-match',
|
|
42
|
+
'ambiguous',
|
|
43
|
+
'no-outcomes',
|
|
44
|
+
'open-outcomes',
|
|
45
|
+
'plan-unswept',
|
|
46
|
+
'bad-input',
|
|
47
|
+
] as const
|
|
48
|
+
|
|
49
|
+
export type ArchiveRefusal = (typeof ARCHIVE_REFUSALS)[number]
|
|
50
|
+
|
|
51
|
+
export type TaskSelector =
|
|
52
|
+
| { readonly kind: 'stem'; readonly stem: string }
|
|
53
|
+
| { readonly kind: 'pull-request'; readonly number: number }
|
|
54
|
+
|
|
55
|
+
export interface ArchiveSuccess {
|
|
56
|
+
readonly ok: true
|
|
57
|
+
readonly stem: string
|
|
58
|
+
readonly from: string
|
|
59
|
+
readonly to: string
|
|
60
|
+
readonly priorityRowRemoved: boolean
|
|
61
|
+
readonly indexRegenerated: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ArchiveRefused {
|
|
65
|
+
readonly ok: false
|
|
66
|
+
readonly reason: ArchiveRefusal
|
|
67
|
+
readonly message: string
|
|
68
|
+
readonly detail: readonly string[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type ArchiveOutcome = ArchiveSuccess | ArchiveRefused
|
|
72
|
+
|
|
73
|
+
export interface TaskOutcomes {
|
|
74
|
+
readonly open: readonly string[]
|
|
75
|
+
readonly closed: readonly string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function tasksDir(root: string): string {
|
|
79
|
+
return join(root, TASKS_DIR)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function archiveDir(root: string): string {
|
|
83
|
+
return join(root, ARCHIVE_DIR)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const OUTCOME_PATTERN = /^- \[([ xX])\] ?(.*)$/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Marks the lines sitting inside a fenced block, the fence delimiters included.
|
|
90
|
+
* A checkbox in a sample a task displays is not an outcome the task claims, and
|
|
91
|
+
* counting one shifts every position after it. Every reader of the outcome list
|
|
92
|
+
* shares this so none of them can disagree about what the list holds.
|
|
93
|
+
*/
|
|
94
|
+
export function fenceMask(lines: readonly string[]): boolean[] {
|
|
95
|
+
let inside = false
|
|
96
|
+
|
|
97
|
+
return lines.map((line) => {
|
|
98
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
99
|
+
inside = !inside
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
return inside
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Splits a task's outcome list by checkbox state. The board format puts every
|
|
108
|
+
* outcome at the top level of `## Outcomes`, so an anchored match is enough and
|
|
109
|
+
* no heading tracking is needed.
|
|
110
|
+
*/
|
|
111
|
+
export function readOutcomes(text: string): TaskOutcomes {
|
|
112
|
+
const open: string[] = []
|
|
113
|
+
const closed: string[] = []
|
|
114
|
+
const lines = text.split('\n')
|
|
115
|
+
const fenced = fenceMask(lines)
|
|
116
|
+
|
|
117
|
+
for (const [index, line] of lines.entries()) {
|
|
118
|
+
if (fenced[index]) continue
|
|
119
|
+
|
|
120
|
+
const match = OUTCOME_PATTERN.exec(line)
|
|
121
|
+
if (!match) continue
|
|
122
|
+
|
|
123
|
+
const [, box, body] = match
|
|
124
|
+
if (box === ' ') {
|
|
125
|
+
open.push(body.trim())
|
|
126
|
+
} else {
|
|
127
|
+
closed.push(body.trim())
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { open, closed }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reads the `Pull request:` line `git-pr` writes when a pull request opens.
|
|
136
|
+
* The number is a bare `#NNN` the way `Issue:` is, since neither is a path and
|
|
137
|
+
* a full URL would write the remote into a gitignored file.
|
|
138
|
+
*/
|
|
139
|
+
export function readPullRequest(text: string): number | undefined {
|
|
140
|
+
const match = /^Pull request:\s*#(\d+)\s*$/m.exec(text)
|
|
141
|
+
return match ? Number(match[1]) : undefined
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Reads the `Plan:` target out of a markdown link, falling back to the older
|
|
146
|
+
* bare-path form. The path is returned as written, relative to the board.
|
|
147
|
+
*/
|
|
148
|
+
export function readPlanTarget(text: string): string | undefined {
|
|
149
|
+
const match = /^Plan:\s*(?:\[[^\]]*\]\(([^)]+)\)|(\S+))\s*$/m.exec(text)
|
|
150
|
+
if (!match) return undefined
|
|
151
|
+
return match[1] ?? match[2]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Drops the archived task's row from the ordering table. Rows are matched by
|
|
156
|
+
* the link they carry rather than by a line pattern, because a row holds links
|
|
157
|
+
* and prose that a stream edit against it would rewrite in place.
|
|
158
|
+
*/
|
|
159
|
+
export function removePriorityRow(
|
|
160
|
+
text: string,
|
|
161
|
+
stem: string,
|
|
162
|
+
): { readonly text: string; readonly removed: boolean } {
|
|
163
|
+
const target = `](${stem}.md)`
|
|
164
|
+
const lines = text.split('\n')
|
|
165
|
+
const kept = lines.filter((line) => !isRowFor(line, target))
|
|
166
|
+
|
|
167
|
+
return { text: kept.join('\n'), removed: kept.length !== lines.length }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A row is about a task when its first cell links to it. A later cell naming
|
|
172
|
+
* another task is a reference, such as a blocker pointing at what it waits on,
|
|
173
|
+
* and matching anywhere in the line would delete that task's row alongside it.
|
|
174
|
+
*/
|
|
175
|
+
function isRowFor(line: string, target: string): boolean {
|
|
176
|
+
const trimmed = line.trimStart()
|
|
177
|
+
if (!trimmed.startsWith('|')) return false
|
|
178
|
+
|
|
179
|
+
const [, first] = trimmed.split('|')
|
|
180
|
+
return first !== undefined && first.includes(target)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Tests containment rather than a string prefix, so a sibling whose name merely
|
|
185
|
+
* extends the folder's is not read as being inside it.
|
|
186
|
+
*/
|
|
187
|
+
function isUnder(path: string, dir: string): boolean {
|
|
188
|
+
return path === dir || path.startsWith(`${dir}${sep}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resolves the `Plan:` target against the board and against the project root
|
|
193
|
+
* both, which is how `claude-docs` reads the same line. It accepts `../plans/x.md`
|
|
194
|
+
* and `.claude/plans/x.md` as one file, so a gate reading only the first form
|
|
195
|
+
* would pass the second and strand the plan this exists to protect.
|
|
196
|
+
*
|
|
197
|
+
* The resolved path is returned rather than a boolean, because the citation
|
|
198
|
+
* count below compares two tasks by where their targets land and not by the
|
|
199
|
+
* strings they wrote. A target outside the live plans folder yields nothing,
|
|
200
|
+
* which is an archived plan or a pointer into somewhere else entirely.
|
|
201
|
+
*
|
|
202
|
+
* The archive sits inside the folder it archives, so containment alone reads an
|
|
203
|
+
* archived plan as live. Subtracting it is what keeps a closed task from being
|
|
204
|
+
* counted as a citation the sweep has yet to make.
|
|
205
|
+
*/
|
|
206
|
+
export function resolveLivePlan(
|
|
207
|
+
target: string,
|
|
208
|
+
dir: string,
|
|
209
|
+
root: string,
|
|
210
|
+
): string | undefined {
|
|
211
|
+
const plans = join(root, PLANS_DIR)
|
|
212
|
+
const archive = join(root, PLANS_ARCHIVE_DIR)
|
|
213
|
+
const fromBoard = resolve(dir, target)
|
|
214
|
+
const fromRoot = resolve(root, target)
|
|
215
|
+
|
|
216
|
+
if (isUnder(fromBoard, plans) && !isUnder(fromBoard, archive)) {
|
|
217
|
+
return fromBoard
|
|
218
|
+
}
|
|
219
|
+
if (isUnder(fromRoot, plans) && !isUnder(fromRoot, archive)) return fromRoot
|
|
220
|
+
return undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Names the other live tasks whose `Plan:` line lands on the same file. This is
|
|
225
|
+
* the rule `claude-docs` applies before it archives a plan, held here so one
|
|
226
|
+
* question has one implementation: a plan another live task still cites is a
|
|
227
|
+
* plan the sweep is correct to leave, and a guard that read the folder instead
|
|
228
|
+
* refused every task sharing one plan and deadlocked the board against the
|
|
229
|
+
* sweep that was behaving correctly.
|
|
230
|
+
*
|
|
231
|
+
* The closing task is excluded by name. It cites the plan itself, so counting
|
|
232
|
+
* it would never reach zero and the count would answer nothing.
|
|
233
|
+
*/
|
|
234
|
+
export async function otherTasksCitingPlan(
|
|
235
|
+
dir: string,
|
|
236
|
+
root: string,
|
|
237
|
+
plan: string,
|
|
238
|
+
closing: string,
|
|
239
|
+
): Promise<string[]> {
|
|
240
|
+
const stems = (await listTaskStems(dir)).filter((stem) => stem !== closing)
|
|
241
|
+
|
|
242
|
+
const read = await Promise.all(
|
|
243
|
+
stems.map(async (stem) => {
|
|
244
|
+
const target = readPlanTarget(
|
|
245
|
+
await readFile(join(dir, `${stem}.md`), 'utf8'),
|
|
246
|
+
)
|
|
247
|
+
const resolved = target && resolveLivePlan(target, dir, root)
|
|
248
|
+
return resolved === plan ? stem : undefined
|
|
249
|
+
}),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return read.filter((stem): stem is string => stem !== undefined)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Where a task's `Plan:` target resolves, which is what decides whether the
|
|
257
|
+
* plan is the sweep's to move. `unstated` is a task carrying no line at all,
|
|
258
|
+
* and it is distinct from a line resolving somewhere unexpected.
|
|
259
|
+
*/
|
|
260
|
+
export const CITATION_LOCATIONS = [
|
|
261
|
+
'unstated',
|
|
262
|
+
'live',
|
|
263
|
+
'archived',
|
|
264
|
+
'outside',
|
|
265
|
+
] as const
|
|
266
|
+
|
|
267
|
+
export type CitationLocation = (typeof CITATION_LOCATIONS)[number]
|
|
268
|
+
|
|
269
|
+
export interface PlanCitations {
|
|
270
|
+
readonly ok: true
|
|
271
|
+
readonly stem: string
|
|
272
|
+
readonly target: string | undefined
|
|
273
|
+
readonly location: CitationLocation
|
|
274
|
+
/** Other live tasks landing on the same file. Empty unless `location` is `live`. */
|
|
275
|
+
readonly citedBy: readonly string[]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type CitationOutcome = PlanCitations | ArchiveRefused
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Answers where one task's plan sits and who else holds it, which is the whole
|
|
282
|
+
* of the last-live-citation rule. `claude-docs` reads this rather than scanning
|
|
283
|
+
* the board itself, so the sweep that moves a plan and the gate that refuses a
|
|
284
|
+
* task archive cannot drift into disagreeing about which plan is free.
|
|
285
|
+
*
|
|
286
|
+
* It reports and never writes. The move, the retarget, and the ordering the two
|
|
287
|
+
* happen in belong to the caller, and a verb that performed them would be
|
|
288
|
+
* deciding a question the sweep is there to decide.
|
|
289
|
+
*/
|
|
290
|
+
export async function planCitations(
|
|
291
|
+
root: string,
|
|
292
|
+
stem: string,
|
|
293
|
+
): Promise<CitationOutcome> {
|
|
294
|
+
const dir = tasksDir(root)
|
|
295
|
+
|
|
296
|
+
if (!existsSync(dir)) {
|
|
297
|
+
return refuse('no-board', `No task board at ${relative(root, dir)}.`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const stems = await listTaskStems(dir)
|
|
301
|
+
if (!stems.includes(stem)) {
|
|
302
|
+
return refuse('no-match', `No task named ${stem} on the board.`, stems)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const target = readPlanTarget(await readFile(join(dir, `${stem}.md`), 'utf8'))
|
|
306
|
+
if (!target) {
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
stem,
|
|
310
|
+
target: undefined,
|
|
311
|
+
location: 'unstated',
|
|
312
|
+
citedBy: [],
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const live = resolveLivePlan(target, dir, root)
|
|
317
|
+
if (!live) {
|
|
318
|
+
const location = resolvesUnder(target, dir, root, PLANS_ARCHIVE_DIR)
|
|
319
|
+
? 'archived'
|
|
320
|
+
: 'outside'
|
|
321
|
+
return { ok: true, stem, target, location, citedBy: [] }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
stem,
|
|
327
|
+
target,
|
|
328
|
+
location: 'live',
|
|
329
|
+
citedBy: await otherTasksCitingPlan(dir, root, live, stem),
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Runs the two-spelling resolution `resolveLivePlan` applies against a folder
|
|
335
|
+
* other than the live one, so an archived plan is read as archived whichever
|
|
336
|
+
* root the task wrote its path against.
|
|
337
|
+
*/
|
|
338
|
+
function resolvesUnder(
|
|
339
|
+
target: string,
|
|
340
|
+
dir: string,
|
|
341
|
+
root: string,
|
|
342
|
+
folder: string,
|
|
343
|
+
): boolean {
|
|
344
|
+
const resolved = join(root, folder)
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
isUnder(resolve(dir, target), resolved) ||
|
|
348
|
+
isUnder(resolve(root, target), resolved)
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export async function listTaskStems(dir: string): Promise<string[]> {
|
|
353
|
+
const entries = await readdir(dir)
|
|
354
|
+
|
|
355
|
+
return entries
|
|
356
|
+
.filter((entry) => entry.endsWith('.md'))
|
|
357
|
+
.map((entry) => entry.slice(0, -'.md'.length))
|
|
358
|
+
.filter((stem) => !isReservedStem(stem))
|
|
359
|
+
.sort()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function matchByPullRequest(
|
|
363
|
+
dir: string,
|
|
364
|
+
stems: readonly string[],
|
|
365
|
+
number: number,
|
|
366
|
+
): Promise<string[]> {
|
|
367
|
+
const read = await Promise.all(
|
|
368
|
+
stems.map(async (stem) => ({
|
|
369
|
+
stem,
|
|
370
|
+
number: readPullRequest(await readFile(join(dir, `${stem}.md`), 'utf8')),
|
|
371
|
+
})),
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return read.filter((entry) => entry.number === number).map(({ stem }) => stem)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function refuse(
|
|
378
|
+
reason: ArchiveRefusal,
|
|
379
|
+
message: string,
|
|
380
|
+
detail: readonly string[] = [],
|
|
381
|
+
): ArchiveRefused {
|
|
382
|
+
return { ok: false, reason, message, detail }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function resolveStem(
|
|
386
|
+
dir: string,
|
|
387
|
+
selector: TaskSelector,
|
|
388
|
+
): Promise<string | ArchiveRefused> {
|
|
389
|
+
const stems = await listTaskStems(dir)
|
|
390
|
+
|
|
391
|
+
if (selector.kind === 'stem') {
|
|
392
|
+
if (!stems.includes(selector.stem)) {
|
|
393
|
+
return refuse(
|
|
394
|
+
'no-match',
|
|
395
|
+
`No task named ${selector.stem} on the board.`,
|
|
396
|
+
stems,
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
return selector.stem
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const matched = await matchByPullRequest(dir, stems, selector.number)
|
|
403
|
+
|
|
404
|
+
if (matched.length === 0) {
|
|
405
|
+
return refuse('no-match', `No task names pull request #${selector.number}.`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (matched.length > 1) {
|
|
409
|
+
return refuse(
|
|
410
|
+
'ambiguous',
|
|
411
|
+
`${matched.length} tasks name pull request #${selector.number}. One task, one pull request.`,
|
|
412
|
+
matched,
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return matched[0]
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Archives one task as a single unit: the move, the ordering-row removal, and
|
|
421
|
+
* the index regen. The hook and `claude-tasks` both call this, so every gate
|
|
422
|
+
* refuses rather than reports. A caller with nobody watching cannot act on a
|
|
423
|
+
* warning, and two callers gating differently is the drift this exists to stop.
|
|
424
|
+
*/
|
|
425
|
+
export async function archiveTask(
|
|
426
|
+
root: string,
|
|
427
|
+
selector: TaskSelector,
|
|
428
|
+
): Promise<ArchiveOutcome> {
|
|
429
|
+
const dir = tasksDir(root)
|
|
430
|
+
|
|
431
|
+
if (!existsSync(dir)) {
|
|
432
|
+
return refuse('no-board', `No task board at ${relative(root, dir)}.`)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const resolved = await resolveStem(dir, selector)
|
|
436
|
+
if (typeof resolved !== 'string') return resolved
|
|
437
|
+
|
|
438
|
+
const stem = resolved
|
|
439
|
+
const from = join(dir, `${stem}.md`)
|
|
440
|
+
const text = await readFile(from, 'utf8')
|
|
441
|
+
|
|
442
|
+
const { open, closed } = readOutcomes(text)
|
|
443
|
+
|
|
444
|
+
if (open.length > 0) {
|
|
445
|
+
return refuse(
|
|
446
|
+
'open-outcomes',
|
|
447
|
+
`${stem} has ${open.length} open outcome(s). Close them or cut them from the task, then archive.`,
|
|
448
|
+
open,
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (closed.length === 0) {
|
|
453
|
+
return refuse(
|
|
454
|
+
'no-outcomes',
|
|
455
|
+
`${stem} carries no outcomes, so nothing marks it shipped.`,
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const planTarget = readPlanTarget(text)
|
|
460
|
+
const livePlan = planTarget && resolveLivePlan(planTarget, dir, root)
|
|
461
|
+
|
|
462
|
+
// A live plan is unswept only when nothing else on the board holds it. A plan
|
|
463
|
+
// several tasks share stays live by design, so refusing on the folder alone
|
|
464
|
+
// parked every one of those tasks behind a sweep that was right to decline.
|
|
465
|
+
if (livePlan) {
|
|
466
|
+
const shared = await otherTasksCitingPlan(dir, root, livePlan, stem)
|
|
467
|
+
|
|
468
|
+
if (shared.length === 0) {
|
|
469
|
+
return refuse(
|
|
470
|
+
'plan-unswept',
|
|
471
|
+
`${stem} is the last task pointing at a live plan. Run /claude-docs to sweep it first, then archive.`,
|
|
472
|
+
[planTarget],
|
|
473
|
+
)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const destination = archiveDir(root)
|
|
478
|
+
await mkdir(destination, { recursive: true })
|
|
479
|
+
const to = join(destination, `${stem}.md`)
|
|
480
|
+
await rename(from, to)
|
|
481
|
+
|
|
482
|
+
const priorityRowRemoved = await clearPriorityRow(dir, stem)
|
|
483
|
+
const regen = await regenOne(dir, { dryRun: false })
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
ok: true,
|
|
487
|
+
stem,
|
|
488
|
+
from,
|
|
489
|
+
to,
|
|
490
|
+
priorityRowRemoved,
|
|
491
|
+
indexRegenerated: regen.action === 'written',
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function clearPriorityRow(dir: string, stem: string): Promise<boolean> {
|
|
496
|
+
const path = join(dir, 'priority.md')
|
|
497
|
+
if (!existsSync(path)) return false
|
|
498
|
+
|
|
499
|
+
const { text, removed } = removePriorityRow(
|
|
500
|
+
await readFile(path, 'utf8'),
|
|
501
|
+
stem,
|
|
502
|
+
)
|
|
503
|
+
if (removed) await writeFile(path, text)
|
|
504
|
+
|
|
505
|
+
return removed
|
|
506
|
+
}
|