@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,159 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { relative } from 'node:path'
|
|
3
|
+
import { DEFAULT_PORT, type ServeOutcome, startServer } from '@/serve/static'
|
|
4
|
+
import { intro, logError, logInfo, logStep, logWarn, outro } from '@/ui'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_DIR = '.'
|
|
7
|
+
|
|
8
|
+
interface ServeCommandOptions {
|
|
9
|
+
readonly entry?: string
|
|
10
|
+
readonly json?: boolean
|
|
11
|
+
readonly port?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function register(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('serve')
|
|
17
|
+
.description('Serve a directory over localhost and print the preview link')
|
|
18
|
+
.argument('[dir]', 'Directory to serve', DEFAULT_DIR)
|
|
19
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
20
|
+
.option('--port <number>', `Port to try first, default ${DEFAULT_PORT}`)
|
|
21
|
+
.option('--entry <path>', 'Page the printed link opens, default index.html')
|
|
22
|
+
.option('--json', 'Emit a machine-readable record on stdout')
|
|
23
|
+
.addHelpText(
|
|
24
|
+
'after',
|
|
25
|
+
[
|
|
26
|
+
'',
|
|
27
|
+
'Exit codes:',
|
|
28
|
+
' 0 the server stopped after running',
|
|
29
|
+
' 1 refused, with the reason on stderr or in the JSON record',
|
|
30
|
+
'',
|
|
31
|
+
'The server binds 127.0.0.1 and nothing else, and it sends no cache',
|
|
32
|
+
'headers, so an edited stylesheet is never served stale. A port in use',
|
|
33
|
+
'is not a failure: the next free one is taken and the link says which.',
|
|
34
|
+
'',
|
|
35
|
+
'It runs until interrupted. A session wanting the link without waiting',
|
|
36
|
+
'starts it in the background and reads the record off stdout.',
|
|
37
|
+
'',
|
|
38
|
+
'Examples:',
|
|
39
|
+
' canon serve .claude/teach',
|
|
40
|
+
' canon serve .claude/teach --entry 03-fde-system-design/index.html',
|
|
41
|
+
' canon serve dist --port 4000 --json',
|
|
42
|
+
'',
|
|
43
|
+
].join('\n'),
|
|
44
|
+
)
|
|
45
|
+
.action(async (dir: string, opts: ServeCommandOptions) => {
|
|
46
|
+
process.exitCode = await runServe(dir, opts)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runServe(
|
|
51
|
+
dir: string,
|
|
52
|
+
opts: ServeCommandOptions,
|
|
53
|
+
): Promise<number> {
|
|
54
|
+
const emitJson = opts.json ?? false
|
|
55
|
+
|
|
56
|
+
const port = parsePort(opts.port)
|
|
57
|
+
if (port === undefined) {
|
|
58
|
+
return report(
|
|
59
|
+
{ ok: false, reason: 'no-port', detail: `${opts.port} is not a port` },
|
|
60
|
+
emitJson,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const outcome = startServer(dir, { port, entry: opts.entry })
|
|
65
|
+
const code = report(outcome, emitJson)
|
|
66
|
+
if (!outcome.ok) return code
|
|
67
|
+
|
|
68
|
+
await waitForInterrupt(outcome.stop)
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The relative path where it stays inside the working directory, and the
|
|
74
|
+
* absolute one where it climbs out. A run from a linked worktree serving the
|
|
75
|
+
* main root reports `../../teach`, which names the directory without giving a
|
|
76
|
+
* reader anything they can open.
|
|
77
|
+
*/
|
|
78
|
+
function displayPath(root: string): string {
|
|
79
|
+
const near = relative(process.cwd(), root)
|
|
80
|
+
if (near === '') return '.'
|
|
81
|
+
return near.startsWith('..') ? root : near
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Undefined for a value that is not a port, distinct from an absent flag,
|
|
86
|
+
* which takes the default. A `Number` of a typo is `NaN`, and passing that on
|
|
87
|
+
* asks the runtime to bind a port nobody named.
|
|
88
|
+
*/
|
|
89
|
+
function parsePort(raw: string | undefined): number | undefined {
|
|
90
|
+
if (raw === undefined) return DEFAULT_PORT
|
|
91
|
+
const value = Number(raw)
|
|
92
|
+
if (!Number.isInteger(value) || value < 0 || value > 65535) return undefined
|
|
93
|
+
return value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function report(outcome: ServeOutcome, emitJson: boolean): number {
|
|
97
|
+
if (!outcome.ok) {
|
|
98
|
+
if (emitJson) {
|
|
99
|
+
process.stdout.write(
|
|
100
|
+
`${JSON.stringify({ ok: false, reason: outcome.reason, detail: outcome.detail })}\n`,
|
|
101
|
+
)
|
|
102
|
+
return 1
|
|
103
|
+
}
|
|
104
|
+
intro('canon serve')
|
|
105
|
+
logError(outcome.detail)
|
|
106
|
+
outro()
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (emitJson) {
|
|
111
|
+
process.stdout.write(
|
|
112
|
+
`${JSON.stringify({
|
|
113
|
+
ok: true,
|
|
114
|
+
root: outcome.root,
|
|
115
|
+
host: outcome.host,
|
|
116
|
+
port: outcome.port,
|
|
117
|
+
entry: outcome.entry,
|
|
118
|
+
url: outcome.url,
|
|
119
|
+
entryExists: outcome.entryExists,
|
|
120
|
+
})}\n`,
|
|
121
|
+
)
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
intro('canon serve')
|
|
126
|
+
logStep('Serving')
|
|
127
|
+
logInfo(displayPath(outcome.root))
|
|
128
|
+
logStep('Open')
|
|
129
|
+
logInfo(outcome.url)
|
|
130
|
+
|
|
131
|
+
if (!outcome.entryExists) {
|
|
132
|
+
logStep('No entry page')
|
|
133
|
+
logWarn(
|
|
134
|
+
`${outcome.entry} is not in that directory, so the link opens a 404`,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logStep('Stop')
|
|
139
|
+
logInfo('Ctrl-C')
|
|
140
|
+
return 0
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolves when the process is asked to stop. The frame is closed here rather
|
|
145
|
+
* than in the reporter, because the run is the serving rather than the start,
|
|
146
|
+
* and closing at start would print the frame's end while the server ran on.
|
|
147
|
+
*/
|
|
148
|
+
function waitForInterrupt(stop: () => Promise<void>): Promise<void> {
|
|
149
|
+
return new Promise((settle) => {
|
|
150
|
+
const finish = () => {
|
|
151
|
+
void stop().then(() => {
|
|
152
|
+
outro()
|
|
153
|
+
settle()
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
process.once('SIGINT', finish)
|
|
157
|
+
process.once('SIGTERM', finish)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { Command } from 'commander'
|
|
3
|
+
import { checkClaim, type ClaimReport } from '@/sessions/claim'
|
|
4
|
+
import {
|
|
5
|
+
callerIdentity,
|
|
6
|
+
repositoryOf,
|
|
7
|
+
type ResolvedSession,
|
|
8
|
+
resolveSessions,
|
|
9
|
+
type SelfReport,
|
|
10
|
+
type SessionReport,
|
|
11
|
+
selfOf,
|
|
12
|
+
} from '@/sessions/resolve'
|
|
13
|
+
import {
|
|
14
|
+
intro,
|
|
15
|
+
logInfo,
|
|
16
|
+
logStep,
|
|
17
|
+
logWarn,
|
|
18
|
+
outro,
|
|
19
|
+
pipeOutput,
|
|
20
|
+
plural,
|
|
21
|
+
} from '@/ui'
|
|
22
|
+
|
|
23
|
+
interface ListCommandOptions {
|
|
24
|
+
readonly json?: boolean
|
|
25
|
+
readonly branch?: string
|
|
26
|
+
readonly repository?: string
|
|
27
|
+
readonly self?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const REASONS: Record<string, string> = {
|
|
31
|
+
'not-a-repository': 'working outside any git repository',
|
|
32
|
+
'detached-head': 'detached HEAD, so the worktree holds no branch name',
|
|
33
|
+
'git-unavailable': 'git is not on the path, so nothing could be read',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function register(program: Command): void {
|
|
37
|
+
const sessions = program
|
|
38
|
+
.command('sessions')
|
|
39
|
+
.description(
|
|
40
|
+
'Resolve live peer sessions to the worktree and branch each holds',
|
|
41
|
+
)
|
|
42
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
43
|
+
|
|
44
|
+
sessions
|
|
45
|
+
.command('list')
|
|
46
|
+
.description(
|
|
47
|
+
'Report every live session with its working directory and branch',
|
|
48
|
+
)
|
|
49
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
50
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
51
|
+
.option(
|
|
52
|
+
'--branch <name>',
|
|
53
|
+
'Report only the sessions holding this branch in this repository',
|
|
54
|
+
)
|
|
55
|
+
.option(
|
|
56
|
+
'--repository <path>',
|
|
57
|
+
'Answer about this project rather than the working one',
|
|
58
|
+
)
|
|
59
|
+
.option(
|
|
60
|
+
'--self',
|
|
61
|
+
"Report the caller's own row, and refuse where the roster holds none",
|
|
62
|
+
)
|
|
63
|
+
.addHelpText(
|
|
64
|
+
'after',
|
|
65
|
+
[
|
|
66
|
+
'',
|
|
67
|
+
'Exit codes:',
|
|
68
|
+
' 0 the roster was read',
|
|
69
|
+
' 1 refused, with the reason on stderr',
|
|
70
|
+
'',
|
|
71
|
+
'--branch scopes the match to one repository, since a branch name',
|
|
72
|
+
'identifies a branch there and nothing across a machine. That is the',
|
|
73
|
+
'repository the command runs in unless --repository names another.',
|
|
74
|
+
'A bare run reports every repository and carries the repository field,',
|
|
75
|
+
'so a caller filtering by hand has something that identifies one.',
|
|
76
|
+
'',
|
|
77
|
+
'--repository moves every reading to the project at that path, the',
|
|
78
|
+
'session match and the worktree and ref reads alike. The roster is',
|
|
79
|
+
'machine-wide already, so this is what lets a dispatcher in one project',
|
|
80
|
+
'see a branch a live session holds in another rather than reading it as',
|
|
81
|
+
'unclaimed and sending a second session onto it.',
|
|
82
|
+
'',
|
|
83
|
+
'With --branch, the JSON also carries "worktree" (the path of any',
|
|
84
|
+
'worktree already checked out to it, or null), "refs" (the refs that',
|
|
85
|
+
'already name it, local head and origin remote-tracking alike), and',
|
|
86
|
+
'"claimed" (true when a worktree, a live session, or an existing ref',
|
|
87
|
+
'holds it). A dispatcher reads "claimed" rather than composing the',
|
|
88
|
+
'three fields itself, since each alone misses a real claim: a worktree',
|
|
89
|
+
'can outlive the session that made it, a session can hold a branch',
|
|
90
|
+
'before a worktree exists for it, and a branch behind a merged pull',
|
|
91
|
+
'request has neither while still being taken.',
|
|
92
|
+
'',
|
|
93
|
+
'"sessionsReadable" is false when the session roster could not be read',
|
|
94
|
+
'and "refsReadable" is false when the ref read failed. Either one',
|
|
95
|
+
'leaves "claimed" covering the readings around it alone. Treat that',
|
|
96
|
+
'case as unverified rather than as a clean "false".',
|
|
97
|
+
'',
|
|
98
|
+
'The ref read sees the remote at whatever the last fetch left, so a',
|
|
99
|
+
'branch pushed from another machine since then reads absent here.',
|
|
100
|
+
'',
|
|
101
|
+
'The match can return more than one session. Read the count rather than',
|
|
102
|
+
'the first row, since two sessions can hold one branch.',
|
|
103
|
+
'',
|
|
104
|
+
"--self narrows the report to the caller's own row, which is what a",
|
|
105
|
+
'dispatcher reads to learn the sessionId it carries into a launch. It',
|
|
106
|
+
'joins on CLAUDE_CODE_SESSION_ID first, falls back to CLAUDE_PID, and',
|
|
107
|
+
'falls back again to the pid the messaging socket path spells. It never',
|
|
108
|
+
'reads CLAUDE_CODE_HOST_SESSION_ID, which holds a value from another',
|
|
109
|
+
'namespace that matches no row.',
|
|
110
|
+
'',
|
|
111
|
+
'It refuses with reason "no-self-identity" when the environment states',
|
|
112
|
+
'none of the three, and "no-self-row" when it states one and no live',
|
|
113
|
+
'row carries it. The second is the ordinary answer for a session',
|
|
114
|
+
'driving from Remote Control, which is addressable on the message',
|
|
115
|
+
'channel and holds no local process record for the roster to report.',
|
|
116
|
+
'',
|
|
117
|
+
'Each session writes its own working directory beside its own name, so a',
|
|
118
|
+
'name from a session listing joins to a branch by an exact match rather',
|
|
119
|
+
'than by ordering the roster on start time.',
|
|
120
|
+
'',
|
|
121
|
+
'The confidence field says how the liveness of a row was decided.',
|
|
122
|
+
'"confirmed" matched the running process against the start time the',
|
|
123
|
+
'record stamped. "unverified" means only that the pid answers, which',
|
|
124
|
+
'cannot rule out a pid handed to an unrelated process, so a roster',
|
|
125
|
+
'reported that way is a candidate list rather than an identity.',
|
|
126
|
+
'',
|
|
127
|
+
'The JSON record also carries "statusUpdatedAt" (the stamp the client',
|
|
128
|
+
'wrote beside "status", or null where its record carries none) and',
|
|
129
|
+
'"statusDwellMs" (the elapsed milliseconds since that stamp, falling',
|
|
130
|
+
'back to the coarser "updatedAt" where the narrower one is absent,',
|
|
131
|
+
'computed at read time and clamped at zero against clock skew). The',
|
|
132
|
+
'framed listing renders the same dwell beside the status, at the',
|
|
133
|
+
'coarsest unit that keeps it a whole number.',
|
|
134
|
+
'',
|
|
135
|
+
'Examples:',
|
|
136
|
+
' canon sessions list',
|
|
137
|
+
' canon sessions list --json',
|
|
138
|
+
' canon sessions list --branch feat/parser --json',
|
|
139
|
+
' canon sessions list --branch chore/agents --repository ../caret --json',
|
|
140
|
+
' canon sessions list --self --json',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n'),
|
|
143
|
+
)
|
|
144
|
+
.action(async (opts: ListCommandOptions) => {
|
|
145
|
+
process.exitCode = await runList(opts)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function runList(opts: ListCommandOptions): Promise<number> {
|
|
150
|
+
const report = await resolveSessions()
|
|
151
|
+
|
|
152
|
+
if (report.kind === 'absent') {
|
|
153
|
+
intro('canon sessions list')
|
|
154
|
+
logStep('Refused')
|
|
155
|
+
logWarn(
|
|
156
|
+
`No session registry at ${report.dir}. Nothing was read, so this is not a machine with no sessions.`,
|
|
157
|
+
)
|
|
158
|
+
outro()
|
|
159
|
+
|
|
160
|
+
if (opts.json) {
|
|
161
|
+
process.stdout.write(
|
|
162
|
+
`${JSON.stringify({ dir: report.dir, reason: 'no-registry', sessions: [] })}\n`,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The roster read returns every row and marks none of them as the caller, so
|
|
170
|
+
// the join runs here, ahead of any scope. Resolving it after the branch
|
|
171
|
+
// filter would answer "no row" for a caller whose row was merely filtered
|
|
172
|
+
// out, which is a different failure wearing the same reason.
|
|
173
|
+
const own = opts.self ? selfOf(report.sessions, callerIdentity()) : null
|
|
174
|
+
|
|
175
|
+
if (own?.kind === 'unresolved') {
|
|
176
|
+
intro('canon sessions list')
|
|
177
|
+
logStep('Refused')
|
|
178
|
+
logWarn(selfRefusal(own))
|
|
179
|
+
outro()
|
|
180
|
+
|
|
181
|
+
if (opts.json) {
|
|
182
|
+
process.stdout.write(
|
|
183
|
+
`${JSON.stringify({
|
|
184
|
+
dir: report.dir,
|
|
185
|
+
reason:
|
|
186
|
+
own.reason === 'no-identity' ? 'no-self-identity' : 'no-self-row',
|
|
187
|
+
sessions: [],
|
|
188
|
+
})}\n`,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const pool = own === null ? report.sessions : [own.session]
|
|
196
|
+
|
|
197
|
+
// A branch name identifies a branch inside one repository and nothing across
|
|
198
|
+
// a machine, so an unscoped match reaches a session working in a different
|
|
199
|
+
// project. `main` is the name that collides on every machine running two.
|
|
200
|
+
//
|
|
201
|
+
// Which repository that is is the caller's to name. The roster this filters
|
|
202
|
+
// is machine-wide already, so a dispatcher asking about another project was
|
|
203
|
+
// answered "unclaimed" about a branch a live session there was holding.
|
|
204
|
+
const at = opts.repository ? resolve(opts.repository) : process.cwd()
|
|
205
|
+
const repository = opts.branch ? await repositoryOf(at) : null
|
|
206
|
+
|
|
207
|
+
if (opts.branch && repository === null) {
|
|
208
|
+
intro('canon sessions list')
|
|
209
|
+
logStep('Refused')
|
|
210
|
+
logWarn(
|
|
211
|
+
`--branch scopes the match to one repository, and none resolved at ${at}. Run it inside one, name another with --repository, or read the whole roster and filter on the repository field.`,
|
|
212
|
+
)
|
|
213
|
+
outro()
|
|
214
|
+
|
|
215
|
+
if (opts.json) {
|
|
216
|
+
process.stdout.write(
|
|
217
|
+
`${JSON.stringify({ dir: report.dir, reason: 'no-repository', sessions: [] })}\n`,
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return 1
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const shown = opts.branch
|
|
225
|
+
? pool.filter(
|
|
226
|
+
(session) =>
|
|
227
|
+
session.branch === opts.branch && session.repository === repository,
|
|
228
|
+
)
|
|
229
|
+
: pool
|
|
230
|
+
|
|
231
|
+
const claim = opts.branch
|
|
232
|
+
? await checkClaim(opts.branch, { cwd: at, resolve: async () => report })
|
|
233
|
+
: null
|
|
234
|
+
|
|
235
|
+
intro('canon sessions list')
|
|
236
|
+
reportConfidence(report)
|
|
237
|
+
reportSessions(shown, opts.branch, repository, own !== null)
|
|
238
|
+
if (claim) reportClaim(claim)
|
|
239
|
+
outro()
|
|
240
|
+
|
|
241
|
+
if (opts.json) {
|
|
242
|
+
process.stdout.write(
|
|
243
|
+
`${JSON.stringify({
|
|
244
|
+
dir: report.dir,
|
|
245
|
+
confidence: report.confidence,
|
|
246
|
+
branch: opts.branch ?? null,
|
|
247
|
+
repository,
|
|
248
|
+
worktree: claim?.worktree ?? null,
|
|
249
|
+
refs: claim?.refs ?? null,
|
|
250
|
+
claimed: claim?.claimed ?? null,
|
|
251
|
+
sessionsReadable: claim?.sessionsReadable ?? null,
|
|
252
|
+
refsReadable: claim?.refsReadable ?? null,
|
|
253
|
+
sessions: shown,
|
|
254
|
+
})}\n`,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return 0
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Separates a client that states no identity from a roster holding no row for
|
|
263
|
+
* one it does state, since the two send a reader to different places.
|
|
264
|
+
*/
|
|
265
|
+
function selfRefusal(own: Extract<SelfReport, { kind: 'unresolved' }>): string {
|
|
266
|
+
if (own.reason === 'no-identity') {
|
|
267
|
+
return 'Nothing in the environment identifies this session, so --self has nothing to match against. A client setting none of CLAUDE_CODE_SESSION_ID, CLAUDE_PID, or CLAUDE_CODE_MESSAGING_SOCKET cannot be located on the roster at all.'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const held =
|
|
271
|
+
own.identity.sessionId ??
|
|
272
|
+
(own.identity.pid === null ? 'nothing' : `pid ${own.identity.pid}`)
|
|
273
|
+
|
|
274
|
+
return `The environment identifies this session as ${held}, and no live row carries it. The roster holds local process records alone, so a session driving through Remote Control never appears here, and a record whose session has ended is dropped ahead of the match.`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* States how liveness was decided on every run, including the run that decided
|
|
279
|
+
* it the strong way.
|
|
280
|
+
*
|
|
281
|
+
* A caller reading a roster has no other way to tell a confirmed identity from
|
|
282
|
+
* a pid that merely answered, and the two support different actions: the first
|
|
283
|
+
* addresses a session directly and the second opens by asking to be corrected.
|
|
284
|
+
*/
|
|
285
|
+
function reportConfidence(
|
|
286
|
+
report: Extract<SessionReport, { kind: 'resolved' }>,
|
|
287
|
+
): void {
|
|
288
|
+
logStep('Liveness')
|
|
289
|
+
|
|
290
|
+
// A roster of none decided nothing, so the confirmed line would claim a check
|
|
291
|
+
// over rows that do not exist. The registry is never pruned and holds a record
|
|
292
|
+
// per session ever run, which is what makes the empty result worth stating.
|
|
293
|
+
if (report.sessions.length === 0) {
|
|
294
|
+
logInfo(
|
|
295
|
+
'No row to decide. Every record belongs to a session that has ended.',
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (report.confidence === 'confirmed') {
|
|
301
|
+
logInfo(
|
|
302
|
+
'Every row matched a running process against the start time its record stamped.',
|
|
303
|
+
)
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
logWarn(
|
|
308
|
+
'Start times could not be read, so rows rest on a pid answering alone. Treat the mapping as inferred and open by asking to be corrected.',
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function reportSessions(
|
|
313
|
+
sessions: readonly ResolvedSession[],
|
|
314
|
+
branch: string | undefined,
|
|
315
|
+
repository: string | null,
|
|
316
|
+
scoped: boolean,
|
|
317
|
+
): void {
|
|
318
|
+
logStep('Sessions')
|
|
319
|
+
|
|
320
|
+
if (branch) {
|
|
321
|
+
logInfo(
|
|
322
|
+
`Scoped to ${repository}, since a branch name identifies one there.`,
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// An empty result under --self says nothing about the roster, since the pool
|
|
327
|
+
// was narrowed to one row before the branch filter ran. Reporting the wider
|
|
328
|
+
// answer there would claim a reading this run never took.
|
|
329
|
+
if (sessions.length === 0) {
|
|
330
|
+
if (branch) {
|
|
331
|
+
logInfo(
|
|
332
|
+
scoped
|
|
333
|
+
? `This session does not hold ${branch}.`
|
|
334
|
+
: `No live session in this repository holds ${branch}.`,
|
|
335
|
+
)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
logInfo(
|
|
340
|
+
'No live session. Every record in the registry belongs to a session that has ended.',
|
|
341
|
+
)
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// The count is what a dispatch turns on. One row is a target and several are
|
|
346
|
+
// candidates, and the caller cannot tell them apart from a roster alone.
|
|
347
|
+
if (branch && sessions.length > 1) {
|
|
348
|
+
logWarn(
|
|
349
|
+
`${sessions.length} sessions hold ${branch}. Confirm which one before addressing it.`,
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
logInfo(plural(sessions.length, 'live session'))
|
|
354
|
+
pipeOutput(
|
|
355
|
+
sessions
|
|
356
|
+
.map((session) => {
|
|
357
|
+
const held =
|
|
358
|
+
session.branch ??
|
|
359
|
+
`unresolved: ${REASONS[session.unresolved ?? ''] ?? 'unknown'}`
|
|
360
|
+
const dwell = formatDwell(session.statusDwellMs)
|
|
361
|
+
const status = dwell ? `${session.status} ${dwell}` : session.status
|
|
362
|
+
return `${session.name} ${status} ${held}\n ${session.cwd}`
|
|
363
|
+
})
|
|
364
|
+
.join('\n'),
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Renders the dwell at the coarsest unit that keeps it a whole number, since a
|
|
370
|
+
* reader scanning a roster wants an age at a glance rather than a millisecond
|
|
371
|
+
* count. An absent dwell renders as nothing, folding a status carrying no
|
|
372
|
+
* stamp back to the bare status line the reader already knew.
|
|
373
|
+
*/
|
|
374
|
+
function formatDwell(ms: number | null): string {
|
|
375
|
+
if (ms === null) return ''
|
|
376
|
+
const seconds = Math.round(ms / 1000)
|
|
377
|
+
if (seconds < 60) return `${seconds}s`
|
|
378
|
+
const minutes = Math.round(seconds / 60)
|
|
379
|
+
if (minutes < 60) return `${minutes}m`
|
|
380
|
+
const hours = Math.round(minutes / 60)
|
|
381
|
+
return `${hours}h`
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function reportClaim(claim: ClaimReport): void {
|
|
385
|
+
logStep('Claim')
|
|
386
|
+
|
|
387
|
+
if (claim.worktree) {
|
|
388
|
+
logInfo(`Worktree: ${claim.worktree}`)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (claim.refs.length > 0) {
|
|
392
|
+
logInfo(`Refs: ${claim.refs.join(', ')}`)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
logInfo(claim.claimed ? 'Claimed.' : 'Unclaimed.')
|
|
396
|
+
|
|
397
|
+
if (!claim.sessionsReadable) {
|
|
398
|
+
logWarn(
|
|
399
|
+
'The session roster could not be read, so this leaves the session half out. Treat it as unverified rather than clear.',
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!claim.refsReadable) {
|
|
404
|
+
logWarn(
|
|
405
|
+
'The ref read failed, so this cannot say whether the branch already exists. Treat it as unverified rather than clear.',
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import type { Command } from 'commander'
|
|
4
|
+
import { LAYOUTS } from '@/slides/layouts'
|
|
5
|
+
import { openDeck } from '@/slides/open'
|
|
6
|
+
import { renderSlidesDoc } from '@/slides/render'
|
|
7
|
+
import type { Variant } from '@/slides/styles'
|
|
8
|
+
import { intro, outro, palette } from '@/ui'
|
|
9
|
+
|
|
10
|
+
export function register(program: Command): void {
|
|
11
|
+
const slides = program
|
|
12
|
+
.command('slides')
|
|
13
|
+
.description('Slide deck commands (render, list)')
|
|
14
|
+
|
|
15
|
+
slides
|
|
16
|
+
.command('render')
|
|
17
|
+
.description('Render a SLIDES.md source into a PowerPoint deck')
|
|
18
|
+
.option('-s, --source <path>', 'Source SLIDES.md path', '.claude/SLIDES.md')
|
|
19
|
+
.option('-o, --out <path>', 'Output directory', '.claude/review/slides')
|
|
20
|
+
.option('-v, --variant <variant>', 'Override variant (light or dark)')
|
|
21
|
+
.option(
|
|
22
|
+
'-m, --mirror <path>',
|
|
23
|
+
'Copy the deck to this directory after writing',
|
|
24
|
+
)
|
|
25
|
+
.option('--open', 'Open the deck after writing')
|
|
26
|
+
.action(
|
|
27
|
+
async (opts: {
|
|
28
|
+
source: string
|
|
29
|
+
out: string
|
|
30
|
+
variant?: string
|
|
31
|
+
mirror?: string
|
|
32
|
+
open?: boolean
|
|
33
|
+
}) => {
|
|
34
|
+
try {
|
|
35
|
+
const sourcePath = resolve(process.cwd(), opts.source)
|
|
36
|
+
const outDir = resolve(process.cwd(), opts.out)
|
|
37
|
+
if (!existsSync(sourcePath)) {
|
|
38
|
+
fail(`${opts.source} not found`)
|
|
39
|
+
}
|
|
40
|
+
const variant = parseVariant(opts.variant)
|
|
41
|
+
const mirror = resolveMirror(opts.mirror)
|
|
42
|
+
const { GREEN, GREY, NC, RED } = palette(process.stderr)
|
|
43
|
+
intro('Render slides')
|
|
44
|
+
const result = await renderSlidesDoc(sourcePath, outDir, {
|
|
45
|
+
variant,
|
|
46
|
+
mirror,
|
|
47
|
+
})
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
`${GREY}│${NC} ${GREEN}✓${NC} ${result.slideCount} slides\n${GREY}│${NC} ${GREEN}✓${NC} ${result.pptxPath}\n`,
|
|
50
|
+
)
|
|
51
|
+
if (result.mirrorPath) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`${GREY}│${NC} ${GREEN}✓${NC} mirrored to ${result.mirrorPath}\n`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
if (opts.open) {
|
|
57
|
+
const target = result.mirrorPath ?? result.pptxPath
|
|
58
|
+
const opened = await openDeck(target)
|
|
59
|
+
const mark = opened ? `${GREEN}✓${NC}` : `${RED}✗${NC}`
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`${GREY}│${NC} ${mark} ${opened ? 'opened' : 'could not open'} ${target}\n`,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
outro()
|
|
65
|
+
} catch (error) {
|
|
66
|
+
reportFailure(error)
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
slides
|
|
72
|
+
.command('list')
|
|
73
|
+
.description('List the available slide layouts')
|
|
74
|
+
.option('--json', 'Output the layout catalog as JSON')
|
|
75
|
+
.action((opts: { json?: boolean }) => {
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
process.stdout.write(`${JSON.stringify(LAYOUTS)}\n`)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
const { GREEN, GREY, NC } = palette(process.stderr)
|
|
81
|
+
intro('Slide layouts')
|
|
82
|
+
for (const layout of LAYOUTS) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`${GREY}│${NC} ${GREEN}✓${NC} ${layout.name} ${GREY}${layout.description}${NC}\n`,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
outro()
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseVariant(value: string | undefined): Variant | undefined {
|
|
92
|
+
if (value === undefined) return undefined
|
|
93
|
+
if (value === 'light' || value === 'dark') return value
|
|
94
|
+
fail(`Invalid variant "${value}". Use light or dark.`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveMirror(value: string | undefined): string | undefined {
|
|
98
|
+
const mirror = value ?? process.env.CANON_SLIDES_MIRROR
|
|
99
|
+
return mirror ? resolve(process.cwd(), mirror) : undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Carries a fail-fast message from a validation helper to the action that
|
|
104
|
+
* called it. `fail` has to keep its `never` return, since that is what makes
|
|
105
|
+
* `parseVariant` exhaustive to the compiler, and a helper deep in the call
|
|
106
|
+
* stack cannot set `process.exitCode` and unwind on its own.
|
|
107
|
+
*/
|
|
108
|
+
class SlidesError extends Error {}
|
|
109
|
+
|
|
110
|
+
function fail(message: string): never {
|
|
111
|
+
throw new SlidesError(message)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* `src/cli.ts` calls `program.parse()` without awaiting it, so a rejected
|
|
116
|
+
* action promise reaches no handler and Bun prints a stack trace. Every action
|
|
117
|
+
* that calls `fail` catches at its own boundary.
|
|
118
|
+
*/
|
|
119
|
+
function reportFailure(error: unknown): void {
|
|
120
|
+
if (!(error instanceof SlidesError)) throw error
|
|
121
|
+
const { GREY, NC, RED } = palette(process.stderr)
|
|
122
|
+
process.stderr.write(
|
|
123
|
+
`${GREY}┌${NC}\n${GREY}│${NC} ${RED}✗${NC} ${error.message}\n${GREY}└${NC}\n`,
|
|
124
|
+
)
|
|
125
|
+
process.exitCode = 1
|
|
126
|
+
}
|