@erclx/canon 4.8.0 → 4.9.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.
Files changed (41) hide show
  1. package/README.md +55 -41
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/claude-docs/REQUIREMENT.md +2 -2
  4. package/claude/skills/claude-docs/SKILL.md +7 -36
  5. package/claude/skills/claude-orchestrate/references/orchestrator-dispatch.md +13 -8
  6. package/claude/skills/claude-tasks/SKILL.md +4 -5
  7. package/claude/skills/claude-teach/SKILL.md +11 -1
  8. package/claude/skills/claude-worker/SKILL.md +2 -2
  9. package/docs/agents/commands.md +8 -3
  10. package/docs/agents/install-and-sync.md +23 -1
  11. package/docs/agents/overview.md +3 -3
  12. package/docs/agents/tasks.md +10 -4
  13. package/docs/agents/teach.md +2 -0
  14. package/docs/ai-workflow.md +2 -4
  15. package/docs/visual-design-workflow.md +2 -0
  16. package/docs/zshrc-aliases.md +19 -7
  17. package/package.json +1 -1
  18. package/scripts/core/bootstrap.sh +4 -0
  19. package/src/commands/design.ts +147 -5
  20. package/src/commands/sync.ts +3 -5
  21. package/src/commands/tasks.ts +18 -1
  22. package/src/commands/teach.ts +72 -0
  23. package/src/design/adapter.ts +59 -0
  24. package/src/design/base.css +123 -0
  25. package/src/design/components.ts +118 -0
  26. package/src/design/contrast.ts +81 -0
  27. package/src/design/css.ts +142 -0
  28. package/src/design/document.ts +143 -0
  29. package/src/design/regen.ts +56 -0
  30. package/src/design/render.ts +43 -9
  31. package/src/design/tokens.ts +315 -0
  32. package/src/gate/stages.ts +28 -0
  33. package/src/slides/styles.ts +42 -15
  34. package/src/sync/check.ts +41 -0
  35. package/src/sync/engine.ts +28 -7
  36. package/src/sync/stamp.ts +5 -4
  37. package/src/sync/target.ts +4 -1
  38. package/src/tasks/archive.ts +91 -20
  39. package/src/tasks/validate.ts +76 -0
  40. package/src/teach/workspace.ts +57 -0
  41. package/standards/tasks.md +4 -4
package/README.md CHANGED
@@ -4,11 +4,23 @@
4
4
  [![CI status](https://img.shields.io/github/actions/workflow/status/erclx/canon/verify.yml?branch=main)](https://github.com/erclx/canon/actions/workflows/verify.yml)
5
5
  [![License MIT](https://img.shields.io/npm/l/@erclx/canon)](LICENSE)
6
6
 
7
- One source for your repos' AI conventions. Install once, sync everywhere.
7
+ canon is a CLI and Claude Code plugin that stops your AI conventions drifting apart across repositories. It keeps one authoritative copy and installs it into each project on demand.
8
8
 
9
9
  ![The canon catalog, listing skills, governance rules, and standards with the count each ships, the workflow skills named, and a sample of the rule and standard names](assets/hero.png)
10
10
 
11
- If you work across more than one repository and your AI setup has started to drift between them, this is for you. The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today.
11
+ The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today.
12
+
13
+ ## Why
14
+
15
+ If you work across more than one repository and your AI setup has started to drift between them, this is for you. Every AI coding setup accumulates the same assets. Prompts to reuse, rules agents should follow, slash commands, skills, seed docs, sync scripts. Once you have enough projects, your copies drift and your agents stop getting consistent signals.
16
+
17
+ Three design choices shape the toolkit.
18
+
19
+ - Agent-first: every command has a non-interactive path and a JSON catalog. If a Claude Code skill or any other agent cannot drive the CLI without prompts, the design is wrong.
20
+ - Text-native: conventions, rules, and prompts are authored as markdown that you and your agents read the same way. No hidden behavior, no compiled state.
21
+ - One source, many consumers: this repo is the authoritative copy. Your projects install and sync on demand, never author in place.
22
+
23
+ Two limits worth knowing before you install. Claude Code is the only agent runtime the plugin targets, and the CLI needs Bun on your path.
12
24
 
13
25
  ## Install
14
26
 
@@ -31,6 +43,31 @@ bun install --global @erclx/canon
31
43
 
32
44
  [Bun](https://bun.sh) is the CLI runtime and has to be on your path first. Confirm the install by resolving `canon --help`.
33
45
 
46
+ ## What is inside
47
+
48
+ Each domain has a canonical source in this repo and a thin install or sync CLI on your side.
49
+
50
+ - Claude Code plugin: skills that plan a feature, review a diff, sync the planning docs, and run the ship chain from branch through pull request
51
+ - Governance rules: coding and authoring rules that load into a Claude session when a matching path is edited, installed per project and refreshed by sync
52
+ - Standards: authoring conventions for commits, branches, plans, tasks, and markdown, read by name with `canon standards <name>` rather than copied into your project
53
+ - Snippets: reusable prompts fired by `@` reference in a Claude Code session, resolved live from the plugin with no install step
54
+ - Tooling stacks: golden configs, seeds, and a reference per framework, laid down by `canon init` and reconciled by `canon tooling sync`
55
+ - Design system: a `DESIGN.md` token format, a skill that drafts one from an existing project or from scratch, and a render command
56
+ - Slides: a `SLIDES.md` source format with a layout catalog, rendered to PowerPoint by `canon slides render`
57
+ - Transcripts: `canon transcripts` pulls a YouTube transcript with metadata frontmatter into any repo
58
+ - Sandbox: scenario-based scaffolds that provision an isolated project state for verifying each domain flow
59
+
60
+ ## Documentation
61
+
62
+ Scaffolding your first project? Start with target projects, then the AI workflow loop. Everything else answers questions that arrive later.
63
+
64
+ - [AI workflow](docs/ai-workflow.md): feature-development loop inside a toolkit-managed project
65
+ - [Operating model](docs/operating-model.md): orchestrator, planner, and worker roles for building across parallel sessions
66
+ - [Visual design workflow](docs/visual-design-workflow.md): tiered guide for design and wireframe authoring
67
+ - [Target projects](docs/target-projects.md): scaffold, add a domain later, sync upstream drift
68
+ - [Agents](docs/agents/index.md): CLI flags, exit codes, and JSON output shapes
69
+ - [Docs index](docs/index.md): every reference doc in this repo
70
+
34
71
  ## Update
35
72
 
36
73
  Nothing refreshes on its own. Claude Code ships auto-update off for third-party marketplaces, so an installed copy serves whatever version it was installed at until you refresh it.
@@ -49,43 +86,6 @@ You don't have to wait until something breaks to find out you're behind. `canon
49
86
 
50
87
  To stop doing this by hand, turn auto-update on once under `/plugin` in the Marketplaces tab. Confirm what you are running with `canon --version` and `claude plugin list`.
51
88
 
52
- ## Why
53
-
54
- Every AI coding setup accumulates the same assets. Prompts to reuse, rules agents should follow, slash commands, skills, seed docs, sync scripts. Once you have enough projects, your copies drift and your agents stop getting consistent signals.
55
-
56
- Three design choices shape the toolkit.
57
-
58
- - Agent-first: every command has a non-interactive path and a JSON catalog. If a Claude Code skill or any other agent cannot drive the CLI without prompts, the design is wrong.
59
- - Text-native: conventions, rules, and prompts are authored as markdown that you and your agents read the same way. No hidden behavior, no compiled state.
60
- - One source, many consumers: this repo is the authoritative copy. Your projects install and sync on demand, never author in place.
61
-
62
- Two limits worth knowing before you install. Claude Code is the only agent runtime the plugin targets, and the CLI needs Bun on your path. Snippets are the one surface that also travels to Gemini chat.
63
-
64
- ## What is inside
65
-
66
- Each domain has a canonical source in this repo and a thin install or sync CLI on your side. The links run to internal narrative, written for someone maintaining the toolkit rather than installing it, so skip them on a first pass.
67
-
68
- - [Claude Code plugin](.claude/context/claude-plugin/index.md): skills for planning, review, docs sync, and the git ship chain
69
- - [Governance rules](.claude/context/governance/index.md): Claude rules and stacks, installable per project
70
- - [Standards](.claude/context/standards/index.md): shared authoring conventions, read by name rather than installed
71
- - [Snippets](.claude/context/snippets.md): reusable prompts for Claude and Gemini chat
72
- - [Tooling stacks](.claude/context/tooling.md): golden configs, seeds, and references per framework
73
- - [Design system](.claude/context/design.md): `DESIGN.md` token shape, extract skill and its two paths, render command
74
- - [Slides](.claude/context/slides.md): `SLIDES.md` source, layout catalog, render command, draft skill
75
- - [Transcripts](.claude/context/transcripts.md): fetch a YouTube transcript with metadata frontmatter via `canon transcripts`
76
- - [Sandbox](.claude/context/sandbox/index.md): scenario-based scaffolds for verifying each domain flow
77
-
78
- ## Documentation
79
-
80
- Scaffolding your first project? Start with target projects, then the AI workflow loop. Everything else answers questions that arrive later.
81
-
82
- - [AI workflow](docs/ai-workflow.md): feature-development loop inside a toolkit-managed project
83
- - [Operating model](docs/operating-model.md): orchestrator, planner, and worker roles for building across parallel sessions
84
- - [Visual design workflow](docs/visual-design-workflow.md): tiered guide for design and wireframe authoring
85
- - [Target projects](docs/target-projects.md): scaffold, add a domain later, sync upstream drift
86
- - [Agents](docs/agents/index.md): CLI flags, exit codes, and JSON output shapes
87
- - [Docs index](docs/index.md): every reference doc in this repo
88
-
89
89
  ## Development
90
90
 
91
91
  Working on the toolkit starts from a clone. Running the CLI doesn't, since it installs from the registry. Skip this section unless you're changing the toolkit itself.
@@ -97,7 +97,7 @@ Working on the toolkit starts from a clone. Running the CLI doesn't, since it in
97
97
  - [GitHub CLI](https://cli.github.com) (optional) for ship flows
98
98
  - Shell: `zsh` or bash 4+ (`brew install bash` on macOS).
99
99
 
100
- Clone the repo, then run the bootstrap script. It installs dependencies, links the CLI globally, and adds the Claude Code shell aliases to your `~/.zshrc`.
100
+ Clone the repo, then run the bootstrap script. It installs dependencies, links the CLI globally, and appends a marked block of Claude Code shell aliases to your `~/.zshrc`.
101
101
 
102
102
  ```bash
103
103
  git clone https://github.com/erclx/canon.git
@@ -106,7 +106,7 @@ bun install
106
106
  bun run bootstrap
107
107
  ```
108
108
 
109
- The script is idempotent, so re-run it after pulling upstream changes without duplicating anything. It confirms the install by resolving `canon --help` on the last step. See [zshrc aliases](docs/zshrc-aliases.md) for what each alias does.
109
+ The script is idempotent, so re-run it after pulling upstream changes without duplicating anything. That also means it leaves an alias block you already have alone rather than refreshing it, so an alias added upstream needs the block deleted and the script re-run. It confirms the install by resolving `canon --help` on the last step. See [zshrc aliases](docs/zshrc-aliases.md) for what each alias does, how to pick up a new one, and how to opt out of the block.
110
110
 
111
111
  With the CLI linked, scaffold a fresh project.
112
112
 
@@ -120,6 +120,20 @@ canon init
120
120
 
121
121
  For the full journey from scaffold through adding a domain later to syncing upstream drift, see [target projects](docs/target-projects.md).
122
122
 
123
+ ### Internal narrative
124
+
125
+ Each domain carries an entry written for someone maintaining the toolkit rather than installing it. These paths resolve in a clone only. The published package ships `docs` and not `.claude`, so an installed copy does not carry them.
126
+
127
+ - [Claude Code plugin](.claude/context/claude-plugin/index.md)
128
+ - [Governance rules](.claude/context/governance/index.md)
129
+ - [Standards](.claude/context/standards/index.md)
130
+ - [Snippets](.claude/context/snippets.md)
131
+ - [Tooling stacks](.claude/context/tooling.md)
132
+ - [Design system](.claude/context/design.md)
133
+ - [Slides](.claude/context/slides.md)
134
+ - [Transcripts](.claude/context/transcripts.md)
135
+ - [Sandbox](.claude/context/sandbox/index.md)
136
+
123
137
  ## Contributing
124
138
 
125
139
  Portfolio project. Issues are welcome. Pull requests are accepted by invitation only, so open an issue rather than a branch. Read the [contributing guidelines](CONTRIBUTING.md) for the local loop, the authoring split, and the commit convention before you file anything.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.8.0",
4
+ "version": "4.9.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -38,10 +38,10 @@ The trigger side carries a gap of its own. "Sync the docs" names either corpus t
38
38
  ## Must not
39
39
 
40
40
  - Infer a new task from the diff. Only outcomes already on the board get marked.
41
- - Touch task files the session did not change, outside the board-wide plans sweep that exists to clear a missed archive
41
+ - Touch task files the session did not change
42
42
  - Widen what a writing step reads when the baseline is unusable. Widening a read is safe and widening a write stubs a surface for every file in the repository.
43
43
  - Edit `CLAUDE.md` inline. Every change there goes through a diff-and-approve gate, so this skill only flags.
44
- - Create a context entry or delete a plan
44
+ - Create a context entry, or move or delete a plan. A plan is settled by the merge, and `canon tasks archive` carries it.
45
45
  - Overwrite a file a promotion block routes to. A destination that already holds a page is a merge for a person, and folding over it discards work this skill never read.
46
46
  - Write an anchor onto a decision the run did not amend, or refresh one without re-reading the number. A date from a pass that measured nothing is the false confidence the marker exists to prevent.
47
47
  - Anchor an entry written before the rule, which dates it by blame rather than by a read
@@ -51,7 +51,7 @@ Read these in parallel from the current worktree root (`pwd`), not the main work
51
51
 
52
52
  Read the task board from the main worktree root instead, per Worktrees in `CLAUDE.md`. It is gitignored scratch and never commits with the branch:
53
53
 
54
- - `.canon/tasks/index.md` first, then the task files this session touched. That narrow read serves the marking step. The scratch sweep reads every file in the folder for its plans sweep and states that where it gives the instruction.
54
+ - `.canon/tasks/index.md` first, then the task files this session touched. That narrow read serves the marking step, which is the only step here that opens a task file.
55
55
 
56
56
  ## Step 2: identify what changed
57
57
 
@@ -102,7 +102,7 @@ For each doc with relevant changes, apply updates following these rules. Read a
102
102
 
103
103
  The verb resolves the board at the main worktree root in-process, which is the route because this is an edit inside an existing file and the file-editing tools refuse that path from a linked worktree.
104
104
 
105
- Read `ok` and `reason` out of that record rather than the exit, for the reason the plans sweep below states at length. A refusal arriving as success leaves the outcome unmarked while the chain moves on, so the board reports shipped work as open and the next session re-plans it.
105
+ Read `ok` and `reason` out of that record rather than the exit. An operator's shell profile may wrap `canon` in a function that runs the binary and then a second command and takes the second status, which flattens every non-zero exit to zero. A refusal arriving as success leaves the outcome unmarked while the chain moves on, so the board reports shipped work as open and the next session re-plans it.
106
106
 
107
107
  **REQUIREMENTS.md, ARCHITECTURE.md, DESIGN.md, `.claude/wireframes/<surface>.md`**
108
108
 
@@ -208,40 +208,13 @@ Report a block left unfolded rather than dropping it:
208
208
 
209
209
  `⚠ Skipped: <destination path> already exists. Merge by hand.`
210
210
 
211
- ## Step 9: sweep consumed scratch
211
+ ## Step 9: sweep consumed receipts
212
212
 
213
- Sweep reviews this session consumed, and sweep plans across the whole board. Resolve all paths at the main worktree root, not the current worktree. See Worktrees in `CLAUDE.md`.
213
+ Sweep the review and memory receipts this session consumed. Resolve all paths at the main worktree root, not the current worktree. See Worktrees in `CLAUDE.md`.
214
214
 
215
- Every move and delete below is a shell operation, so send each as a plain single `Bash` command rather than joining a `mkdir -p` to the `mv` with `&&`, which is refused as compound from a linked worktree. The one edit inside an existing file is the `Plan:` retarget, and no verb covers it: read the task file and write it back whole with a heredoc, which the file-editing tools refuse from a linked worktree and no shell stream editor may do.
215
+ Every delete below is a shell operation, so send each as a plain single `Bash` command rather than joining two with `&&`, which is refused as compound from a linked worktree.
216
216
 
217
- ### Plans
218
-
219
- Scan every file in `.canon/tasks/`, not only the ones this session touched. For each task file whose outcomes are now all `[x]`, check for a `Plan:` line directly under the title and parse the target.
220
-
221
- The line carries a markdown link, so read the target out of the parentheses rather than taking the rest of the line. A task still carrying the older bare-path form parses the same way once the link is absent, so accept both. Resolve the target against `.canon/tasks/` before routing on it, which lands `../plans/x.md` and `.canon/plans/x.md` on the same file.
222
-
223
- The bullets below name resolved locations, so an unresolved target falls to the last one and no plan is ever archived. Never delete a plan. `${CLAUDE_SKILL_DIR}/../../standards/plan.md` owns the archive destination and why a shipped plan is moved rather than removed.
224
-
225
- Board-wide scope is the one place this sweep reaches past Step 3's rule against touching task files the session did not change. A board carrying a task that closed while an earlier run missed its archive is the defect this exists to clear, and skipping those tasks would preserve it. Reaching them is safe because the archive moves the plan and points the task at the new path, so a task from unrelated work ends up with a working pointer rather than a broken one.
226
-
227
- Before moving anything, count the other citations. Scan every `.canon/tasks/*.md` file except the one being processed for a `Plan:` line naming the same plan. Compare the resolved target from the parse above, never the raw target string and never the filename alone.
228
-
229
- A board carrying one task written `../plans/x.md` and another written `.canon/plans/x.md` cites one plan, and a raw string comparison reads two, counts zero, and archives the file out from under a live task. Comparing filenames swaps that for the opposite error, since a live plan and an archived one share a basename whenever a closed task still points into `.canon/plans/archive/`, and the count then reads a citation that does not exist and archives nothing.
230
-
231
- Exclude the closing task explicitly. It sits on the board and cites the plan itself, so a scan that counts it never reaches zero and no plan is ever archived.
232
-
233
- `canon tasks plan-citations <stem> --json` answers this same question, and the archive gate already reads it. This body states the rule anyway rather than calling the verb, because a plugin skill reaches a target the moment it merges while the CLI reaches one only when a release publishes, so a target whose installed `canon` predates the verb gets no record back and routes on nothing. Measured against the `claude:docs` `board-sweep` arm, where calling the verb archived neither plan and created no `.canon/plans/archive/`.
234
-
235
- Nothing in the exit code reports that. Branch on the record's `ok` and `reason` fields and never on the exit, which is the rule every task verb already carries: an operator's shell profile may wrap `canon` in a function that runs the binary and then another command, taking its status from the second, and one measured here masks every non-zero exit rather than only an absent verb. The binary itself exits 1 for an unknown subcommand and 1 for an ordinary refusal alike. Switching this body to the verb needs a release that carries it and a read of the record rather than the exit, which together retire the duplication.
236
-
237
- A plan can serve more than one task, and archiving on the first task to close strands every other task's pointer at a path that has moved. `.canon/plans/` is gitignored, so that retarget would be the only record and there is nothing to recover it from.
238
-
239
- - Target resolves inside `.canon/plans/`, the file exists, and no other task file cites it: create `.canon/plans/archive/`, move the file there under its original name, overwriting any file already sitting at that name. Then rewrite the task file's `Plan:` line to the archive path, so a completed task still leads to the reasoning behind it.
240
- - Target resolves inside `.canon/plans/` and at least one other task file cites it: leave the plan where it is and retarget nothing. Report the shared citation.
241
- - Target resolves inside `.canon/plans/archive/`: skip silently. The plan was archived by an earlier pass and the task file is already correct.
242
- - Any other resolved target outside `.canon/plans/`: warn and skip.
243
-
244
- Write the retarget as a markdown link, `Plan: [feature-<slug>](../plans/archive/feature-<slug>.md)`, updating both halves so the text and the target stay in step. This branch is the only writer that produces a `Plan:` line nobody authored by hand, so a retarget that emits a bare path converts every task to the old form as it closes and drifts the board back to two shapes on its own.
217
+ Plans are not swept here. A plan is settled by the merge rather than by an outcome this run marked, and `canon tasks archive` moves it with the task the `post-merge` hook archives. Sweeping it from this step read a closure Step 3 had written moments earlier and moved a plan the branch was still building from.
245
218
 
246
219
  ### Reviews
247
220
 
@@ -253,7 +226,7 @@ Sweep the branch reports this session never opened. List `.canon/review/branch/r
253
226
 
254
227
  What that removes is a local-only review on a branch deleted before it opened a pull request. `claude-review` says so where a reader meets the report, and the sweep runs anyway rather than keeping every report against the one case, since nothing else ever clears them.
255
228
 
256
- Memory receipts sweep board-wide, like both halves of this step above them. Scan every `.canon/review/memory/memory-review-*.md`, not only the one matching this slug. `claude-memory-review` writes its receipt after this skill has run in every ship chain, so a sweep keyed on the current slug looks for a file that does not exist yet, and no later branch looks for it either because a slug is unique per feature. Scanning the folder is what makes the sweep fire at all.
229
+ Memory receipts sweep board-wide rather than by slug. Scan every `.canon/review/memory/memory-review-*.md`, not only the one matching this slug. `claude-memory-review` writes its receipt after this skill has run in every ship chain, so a sweep keyed on the current slug looks for a file that does not exist yet, and no later branch looks for it either because a slug is unique per feature. Scanning the folder is what makes the sweep fire at all.
257
230
 
258
231
  For each receipt, count the H2 items still marked 📝 pending:
259
232
 
@@ -266,8 +239,6 @@ Do not sweep `ui-checklist-*.md` (pending human verification), `ux-audit-*.md`,
266
239
 
267
240
  Output one line per file swept:
268
241
 
269
- - `📦 Archived: <path>` for a plan moved into `.canon/plans/archive/`
270
- - `⏭ Kept: <path>, still cited by <task-file>` for a plan another live task shares
271
242
  - `🧹 Deleted: <path>, branch gone` for a branch report whose branch no longer exists
272
243
  - `🧹 Deleted: <path>, folded <n> skips` for a swept memory receipt
273
244
  - `⏭ Kept: <path>, <n> items pending` for a memory receipt still holding decisions
@@ -73,13 +73,17 @@ Name `<model>` on the launch, and pick it against the task rather than copying w
73
73
  ## Dispatch
74
74
 
75
75
  ```bash
76
- claude --bg --model <model> -n "worker-<slug>" "Run /canon:claude-worktree <type>/<slug>, then /canon:claude-autoship <plan>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
76
+ claude --bg --model <model> -n "worker-<project>-<slug>" "Run /canon:claude-worktree <type>/<slug>, then /canon:claude-autoship <plan>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
77
77
  ```
78
78
 
79
79
  `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance the section above measured. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
80
80
 
81
81
  The prefix reads `worker-` because that is the role it marks. It read `orchestrator-` until 2026-08-31, and no controlling session ever carried it, so a worker filtering the roster for that string found a sibling or itself on every row. Nothing matches the prefix programmatically, which is what kept the rename down to three strings.
82
82
 
83
+ `<project>` is the basename of the main worktree root, not of wherever the dispatcher happens to be running. Resolve the main root first, the way `claude-worktree` Step 1 does, since a bare `git rev-parse --show-toplevel` inside a linked worktree returns the worktree path rather than the project's. See Worktrees in `CLAUDE.md`.
84
+
85
+ `claude agents` lists every session on the machine with no path column and no per-project filter, so `<project>` in the name is the only thing left telling two fleets apart, and a session named off the worktree path instead would carry the branch folder rather than the project. Two projects each dispatching a bare `worker-page-driver` used to read as one row in that view.
86
+
83
87
  Read `<dispatcher-id>` with `canon sessions list --self --json` and interpolate the `sessionId` that row carries. Carry the id rather than the name. A name is derived from whatever the session turned out to be doing, and across the 181 records stamping both fields, nine were renamed after launch at a median of 5.4 minutes and a maximum of 509. Three landed more than ten minutes in, which is inside the window a worker announces its pull request in, so a name written into the prompt is aimed at a send that happens after it goes stale.
84
88
 
85
89
  Where the installed CLI answers `--self` with an unknown option, that flag is newer than the release the target holds. Read the `sessionId` from the record the client writes for this session under its configuration directory, and say which route answered so the reader knows whether the id was read or inferred.
@@ -165,11 +169,11 @@ directly with `Bash`, `Read`, and `Edit` instead of retrying the tool, which
165
169
  is the route two workers already took today on two different branches.
166
170
 
167
171
  ```bash
168
- claude --bg --model <model> -n "worker-<slug>" "Enter the worktree for <branch> at .claude/worktrees/<slug>/, creating it from that branch if the folder is gone. Run /canon:claude-worker, then /canon:claude-address-review. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the address pass finishes, carrying what was addressed and the PR's CI state, and message it again if you stop on a question."
172
+ claude --bg --model <model> -n "worker-<project>-<slug>" "Enter the worktree for <branch> at .claude/worktrees/<slug>/, creating it from that branch if the folder is gone. Run /canon:claude-worker, then /canon:claude-address-review. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the address pass finishes, carrying what was addressed and the PR's CI state, and message it again if you stop on a question."
169
173
  ```
170
174
 
171
- `<dispatcher-id>` and `<model>` resolve the same way the build shape resolves
172
- them above.
175
+ `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build
176
+ shape resolves them above.
173
177
 
174
178
  Take this shape wherever a review needs answering and no live session already
175
179
  holds the branch. Where one does, message it to run `claude-address-review`
@@ -196,14 +200,15 @@ gitignored file at the main worktree root, so this shape names the row's task
196
200
  file rather than a branch and opens with the role instead of a worktree call.
197
201
 
198
202
  ```bash
199
- claude --bg --model <model> -n "planner-<slug>" "Run /canon:claude-planner, then /canon:claude-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
203
+ claude --bg --model <model> -n "planner-<project>-<slug>" "Run /canon:claude-planner, then /canon:claude-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
200
204
  ```
201
205
 
202
206
  `<task>` is the row's task file path and `<slug>` the slug its plan will take,
203
207
  resolved off the row the way the build shape resolves one off a plan.
204
- `<dispatcher-id>` and `<model>` resolve the same way they do above. The prefix
205
- reads `planner-` for the reason the worker's reads `worker-`, which is that it
206
- marks the role of the session it names rather than the one that launched it.
208
+ `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way they do
209
+ above. The prefix reads `planner-` for the reason the worker's reads `worker-`,
210
+ which is that it marks the role of the session it names rather than the one
211
+ that launched it.
207
212
 
208
213
  None of the three checks above binds this shape. The branch check has no
209
214
  candidate to read, and the disjointness gate has nothing to compare, since a
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: claude-tasks
3
- description: Creates a task file in `.canon/tasks/` with the filename, phase label, and frontmatter the standard requires, and archives a shipped one out of the folder. Use when asked to "add a task", "create a task", "queue this", "put this on the board", "archive that task", or "close out a shipped task". Do NOT use to mark an outcome `[x]` or to archive a plan. That is `claude-docs`.
3
+ description: Creates a task file in `.canon/tasks/` with the filename, phase label, and frontmatter the standard requires, and archives a shipped one out of the folder. Use when asked to "add a task", "create a task", "queue this", "put this on the board", "archive that task", or "close out a shipped task". Do NOT use to mark an outcome `[x]`. That is `claude-docs`.
4
4
  ---
5
5
 
6
6
  # Claude tasks
7
7
 
8
- Owns the two operations that bring a task file into existence and take it out of the folder. `claude-docs` edits the contents of a task that already exists, marking outcomes `[x]` and sweeping the plans those tasks cite. Do not mark outcomes here and do not archive a plan on its own.
8
+ Owns the two operations that bring a task file into existence and take it out of the folder. `claude-docs` edits the contents of a task that already exists, marking outcomes `[x]`. Do not mark outcomes here, and do not move a plan by hand: the archive carries it.
9
9
 
10
10
  Read `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` before writing any file. It holds the filename convention, the frontmatter contract, and the file format. Do not work them from memory.
11
11
 
@@ -104,19 +104,18 @@ canon tasks archive <stem> --json
104
104
 
105
105
  The command refuses rather than reports, and the refusal reaches this skill through the record rather than through the exit. Branch on `ok`, then on `reason`. An operator's shell profile may wrap `canon` in a function that runs the binary and then a second command and takes the second status, which masks every non-zero exit rather than only an absent verb. The binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike, so the record is the only signal that survives the wrapper.
106
106
 
107
- On success the record carries `from`, `to`, `priorityRowRemoved`, and `indexRegenerated`, which is what moved, what row it cleared, and whether the index changed.
107
+ On success the record carries `from`, `to`, `priorityRowRemoved`, and `indexRegenerated`, which is what moved, what row it cleared, and whether the index changed. It also carries `plan` when the task was the last live citation of a live plan, holding the `from` and `to` of the plan moved alongside it. A `plan` of `null` means the task cited no live plan, or that a sibling still holds it.
108
108
 
109
109
  ### Step 3: route on a refusal
110
110
 
111
111
  Each reason has one resolution and none of them is to archive around it:
112
112
 
113
113
  - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `claude-docs` when the work shipped and nothing marked it. Leave the task on the board when the outcome is real. Cut the outcomes first when the work is being abandoned, so the board records what was dropped.
114
- - `plan-unswept`: stop and route to `claude-docs`, which owns the plans sweep and the last-live-citation rule. `❌ Plan not yet swept. Run /claude-docs first, then archive.`
115
114
  - `ambiguous`: two tasks name one pull request, which is the misfile `${CLAUDE_SKILL_DIR}/../../standards/tasks.md` rules out. Resolve the citation by hand, since no sweep repairs it.
116
115
  - `no-match`: the stem or number names nothing on the board. Check the name against the listed stems.
117
116
  - `bad-input`: the command line was wrong rather than the board. Read the message, fix the arguments, and run it again. Nothing on the board needs repair, which is what separates this from the two above.
118
117
 
119
- Do not move a plan from this skill. `claude-docs` owns that move. Two skills relocating the same file drift into relocating it differently.
118
+ Do not move a plan by hand from this skill. The command carries the plan with the task when no other live task cites it, and retargets the archived task's `Plan:` line at the new path. A second mover drifts into relocating the same file differently.
120
119
 
121
120
  Leave `TASK-ARCHIVE.md` alone when it is present in the archive folder. It records the single-file era in the shape that era used, and splitting it would fabricate per-task files nobody wrote.
122
121
 
@@ -95,12 +95,22 @@ canon teach lesson <topic> --json \
95
95
  It writes nothing and reports four things:
96
96
 
97
97
  - `lesson`, the numbered path the lesson takes. Write it there.
98
- - `stylesheet` with `stylesheetExists` and `stylesheetHref`. Write the stylesheet only when it reports absent, which is the first lesson in the workspace. Every lesson after that links the file already on disk and adds to it rather than replacing it, since overwriting discards what the last lesson put there.
98
+ - `stylesheet` with `stylesheetExists` and `stylesheetHref`. Link it at `stylesheetHref` and seed it through the verb below when `stylesheetExists` is false, which is the first lesson in the workspace. Every lesson after that links the file already on disk and adds to it rather than replacing it, since overwriting discards what the last lesson put there.
99
99
  - `success`, the mission's success lines, carried here so Step 5 needs no second read.
100
100
  - `quiz`, one entry per question, carrying `order` and `answer`.
101
101
 
102
102
  Write the correct option first, then present the options in the order `order` reports, reading it as authored indices where `0` is the correct one. Take the order as given. Position drawn here rather than chosen is the whole reason the verb exists, and a lesson that reorders on its own judgment puts the answer back in the first slot.
103
103
 
104
+ Seed the stylesheet through the verb rather than authoring a palette, on the first lesson in a workspace:
105
+
106
+ ```bash
107
+ canon teach stylesheet <topic> --json
108
+ ```
109
+
110
+ It writes the design tokens as custom properties and the components built on them, from the one source every other rendered surface reads. Add lesson rules under the seed and reach a value through its property rather than restating the hex, which is what let each workspace fork the palette from every other. It refuses to overwrite, so running it again on a workspace that has grown its own rules is safe and reports `written` as false.
111
+
112
+ Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it. Do not fall back to writing a palette by hand.
113
+
104
114
  Add every term the lesson defines to `GLOSSARY.md` through the verb, which places the entries alphabetically in the shape the standard fixes:
105
115
 
106
116
  ```bash
@@ -72,8 +72,8 @@ Infer only where no operator is present. Read `canon sessions list --json` and
72
72
  take the sessions holding no feature branch as the candidates, since a
73
73
  controlling session holds none. Say the addressee was inferred so the reader can
74
74
  correct it. Never filter that roster by name prefix: every self-dispatched
75
- worker is named `worker-<slug>`, so a prefix scan returns a sibling or this
76
- session itself, which is the defect that sent messages owed to a controller
75
+ worker is named `worker-<project>-<slug>`, so a prefix scan returns a sibling or
76
+ this session itself, which is the defect that sent messages owed to a controller
77
77
  somewhere else.
78
78
 
79
79
  Report a resolution that returns nothing rather than falling back to a guess.
@@ -22,11 +22,15 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
22
22
  | `canon indexes regen` | Regenerate `index.md` files from sibling frontmatter |
23
23
  | `canon docs [topic]` | Emit toolkit reference docs (`list`, or a topic by name) |
24
24
  | `canon design render` | Render `.claude/DESIGN.md` tokens to HTML and CSS |
25
+ | `canon design regen` | Rewrite this repository's `.claude/DESIGN.md` and `src/design/base.css` from `src/design/tokens.ts` |
26
+ | `canon design css` | Emit the design tokens and components as CSS on stdout (`--no-components` for properties alone) |
27
+ | `canon design install` | Install the base stylesheet into a project at `.claude/design/base.css` |
28
+ | `canon design sync` | Update a base stylesheet already installed under `.claude/design/` |
25
29
  | `canon slides render` | Render a `.claude/SLIDES.md` source into a PowerPoint deck, reporting any unrecognized layout name on stderr |
26
30
  | `canon slides list` | List the available slide layouts (`--json` for the catalog) |
27
31
  | `canon feedback` | Write toolkit feedback from stdin to `.canon/review/feedback/`, or open a GitHub issue with `--github` |
28
32
  | `canon transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
29
- | `canon tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
33
+ | `canon tasks archive` | Move a shipped task and its plan off the board, clear its ordering row, and regenerate the index |
30
34
  | `canon tasks pull-request` | Record a pull request number on the task a branch closes, by stem or `--plan` (`--json`) |
31
35
  | `canon tasks outcome` | Mark outcomes `[x]` on a task by position, repeating `--close` (`--json`) |
32
36
  | `canon tasks validate` | Report board rows whose shape, order, plan, task file, group, file set, or blocker does not hold (`--json`) |
@@ -36,6 +40,7 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
36
40
  | `canon teach open` | Open a workspace at the next ordinal with its mission, resources, and glossary files (`--json`) |
37
41
  | `canon teach resource` | Record sources and leads in a workspace, repeating `--read` or `--lead` as `<title>=<url>` (`--json`) |
38
42
  | `canon teach glossary` | Add terms to a workspace glossary alphabetically, repeating `--term <term>=<definition>` (`--json`) |
43
+ | `canon teach stylesheet` | Seed a workspace stylesheet from the design source, leaving an existing one alone without `--force` (`--json`) |
39
44
  | `canon records validate` | Report a session record or a standard against the standard governing it, per kind (`--json`) |
40
45
  | `canon records migrate` | Rewrite the records a validate finding names a recoverable transform for (`--write`, `--json`) |
41
46
  | `canon records size` | Report what each record folder holds and how much of it is recent, heaviest first (`--json`) |
@@ -112,11 +117,11 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
112
117
  | `demo` | `compile`, `run` |
113
118
  | `inventory` | `run` |
114
119
  | `wiki` | `init` |
115
- | `design` | `render` |
120
+ | `design` | `regen`, `css`, `render`, `install`, `sync` |
116
121
  | `slides` | `render`, `list` |
117
122
  | `tasks` | `archive`, `validate` |
118
123
  | `intake` | `list`, `answer` |
119
- | `teach` | `list`, `open`, `resource`, `glossary` |
124
+ | `teach` | `list`, `open`, `resource`, `glossary`, `lesson`, `stylesheet` |
120
125
  | `comments` | `scan` |
121
126
  | `context` | `audit` |
122
127
  | `markdown` | `audit` |
@@ -27,7 +27,29 @@ number bands behind that placement, where `900-999` is the range a
27
27
  project-authored rule takes and everything below it belongs to the toolkit.
28
28
 
29
29
  `canon sync --check` does not report an orphaned entry. It skips every one, so
30
- the destination reaches `canon gov sync` alone, the one domain sync verb left.
30
+ the destination reaches `canon gov sync` alone among the two per-file domain
31
+ syncs, since design's own orphans are the target's overrides and are meant to
32
+ stay where they are.
33
+
34
+ `canon design install` copies one toolkit-owned file to
35
+ `.claude/design/base.css` and creates no override. A project overrides a value
36
+ by writing `.claude/design/project/` itself, which `canon design sync` never
37
+ touches, because that subfolder is project-authored by location the way
38
+ `.claude/rules/project/` is. An override named exactly like the shipped file is
39
+ still the project's.
40
+
41
+ The override ships absent rather than empty. An empty file is one the project
42
+ did not ask for and did not write, the reconciliation already handles a missing
43
+ side, and an empty override invites a target to fill it before it has an
44
+ opinion.
45
+
46
+ Nothing arrives on a project that has not run `canon design install`. The
47
+ domain is detected by that folder existing, so `canon sync` skips it entirely on
48
+ a target that never installed it, and the unstamped line at the end of
49
+ `canon sync --check` stays quiet about it for the same reason. Governance is
50
+ named there when it is absent and design is not, because a managed project
51
+ without governance has yet to install what every project carries, while one
52
+ without design chose that.
31
53
 
32
54
  When the target's install recorded a stack, `canon gov sync` also reports a
33
55
  rule that stack lists and `.claude/rules/` does not hold, as a `missing` entry
@@ -5,9 +5,9 @@ description: What this folder covers, the invocation rules every command inherit
5
5
 
6
6
  # Overview
7
7
 
8
- CLI catalog and invocation rules for agents working in this repository.
8
+ CLI catalog and invocation rules for an agent driving the `canon` CLI, in this repository or in any project that installed it.
9
9
 
10
- This folder is an index of what an agent can run and how to run it cleanly from a script. It does not cover domain behavior. Read `CLAUDE.md` for project behaviors and load the matching `.claude/skills/internal-*` skill when working inside a domain.
10
+ This folder is an index of what an agent can run and how to run it cleanly from a script. It does not cover domain behavior. Read the project's own `CLAUDE.md` for that. The `.claude/skills/internal-*` skills carry the per-domain editing guidance and live in the toolkit checkout alone, so a reader who installed the package resolves none of them.
11
11
 
12
12
  ## Invocation rules
13
13
 
@@ -22,5 +22,5 @@ See `CLAUDE.md` design principles. They apply to every command in this folder.
22
22
  ## Related
23
23
 
24
24
  - `CLAUDE.md`: project behaviors and design principles
25
- - `.claude/skills/internal-*`: domain-scoped guidance for editing work
25
+ - `.claude/skills/internal-*`: domain-scoped guidance for editing work, in the toolkit checkout only
26
26
  - `docs/index.md`: full docs directory
@@ -22,9 +22,13 @@ canon tasks archive --pull-request 673 --json
22
22
  | `--json` | Emit a machine-readable record on stdout |
23
23
  | `--root <path>` | Board root, defaulting to the main worktree |
24
24
 
25
- Exit codes: `0` archived, `1` refused. Every gate is a refusal rather than a warning, because `.husky/post-merge` calls this with nobody watching. The `reason` field carries which gate fired: `no-board`, `no-match`, `ambiguous`, `no-outcomes`, `open-outcomes`, `plan-unswept`, or `bad-input`.
25
+ Exit codes: `0` archived, `1` refused. Every gate is a refusal rather than a warning, because `.husky/post-merge` calls this with nobody watching. The `reason` field carries which gate fired: `no-board`, `no-match`, `ambiguous`, `no-outcomes`, `open-outcomes`, or `bad-input`.
26
26
 
27
- `plan-unswept` fires on the last task pointing at a live plan, never on every task pointing at one. The gate counts the other live tasks whose `Plan:` line resolves onto the same file, so a plan several tasks share archives its tasks freely and only the final one is held until `claude-docs` sweeps the plan. Reading the folder alone refused all of them, which deadlocked the board against a sweep correctly declining to move a plan another live task cites.
27
+ The task carries its plan with it. When the closing task is the last live one whose `Plan:` line resolves onto that file, the plan moves to `.canon/plans/archive/` under its own name and the archived task's line is rewritten as `Plan: [feature-<slug>](../../plans/archive/feature-<slug>.md)`, a folder deeper than the live task wrote it. The `plan` field on the success record carries that `from` and `to`, and is `null` when nothing moved.
28
+
29
+ A plan several tasks share stays where it is, and the task archives anyway. Moving it on the first task to close strands every sibling's pointer at a path that has gone, and `.canon/plans/` is gitignored so no history recovers the target. A `Plan:` line resolving to no file leaves the plan alone too, since a pointer somebody typed wrong is not a plan to move and holding the whole archive over it would park the board behind a repair the merge cannot make.
30
+
31
+ The merge is what settles a plan. `.husky/post-merge` reads the pull request number off the squash subject and calls this verb, so both halves close in one act with nobody naming a file. That is why the move sits inside this verb rather than in a second call the hook would make after it, which could leave the task archived and the plan live.
28
32
 
29
33
  `bad-input` covers a malformed command line, which all three task verbs answer the same way. It is separate from `ambiguous` and `no-match` because those describe the board, and a caller that passed two selectors would otherwise be sent to repair a task citation that is fine.
30
34
 
@@ -53,7 +57,7 @@ The record carries `location`, one of `unstated`, `live`, `archived`, or `outsid
53
57
 
54
58
  The target resolves against the board folder and against the project root both, so `../plans/x.md` and `.canon/plans/x.md` land on the same file and one plan two tasks spelled differently counts once. Containment is tested at both record roots rather than at the one this tree resolves at, since a line somebody wrote against a root the tree has since left is still a path into the plans folder, and reading it as outside would report a shipped plan as still live. `docs/agents/records.md` states the read order.
55
59
 
56
- `canon tasks archive` gates on this same answer, so a caller wanting the count reads it here rather than scanning the board. The `claude-docs` plans sweep is the exception and still states the rule in its own body, because a plugin skill reaches a target on merge while the CLI reaches one on release, so a sweep calling a verb the installed `canon` predates gets no record back and archives nothing.
60
+ `canon tasks archive` decides its plan move on this same answer, so a caller wanting the count reads it here rather than scanning the board.
57
61
 
58
62
  Branch on `reason` rather than on the exit code, which is the rule the archive section above already states and which this verb needs for a second reason. An operator's shell profile may wrap `canon` in a function that runs the binary and then another command and takes the second status, which masks every non-zero exit rather than only an absent verb. The binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike, so the record is the only signal that survives the wrapper.
59
63
 
@@ -159,7 +163,7 @@ Seven checks run. Plan and Collisions reach one half each of the `## Run now` te
159
163
  | Check | What it reports |
160
164
  | ---------- | -------------------------------------------------------------------------------------------------------------------- |
161
165
  | Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
162
- | Plan | A `## Run now` row whose Plan column carries no link, or one resolving to no file |
166
+ | Plan | A `## Run now` row whose Plan column carries no link, resolves to no file, or disagrees with the task's own line |
163
167
  | Mapping | A row or backlog line naming no task file, and a task file neither surface names |
164
168
  | Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
165
169
  | Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits |
@@ -168,6 +172,8 @@ Seven checks run. Plan and Collisions reach one half each of the `## Run now` te
168
172
 
169
173
  Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
170
174
 
175
+ The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair naming two different plans is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
176
+
171
177
  Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A task file either surface names is accounted for, a file neither names is `row-missing`, and a file both name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. One check across both is what lets a task move between them without the move reading as a dropped file.
172
178
 
173
179
  A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name is still reported as reaching neither surface. A project carrying no `backlog.md` reads as an empty backlog rather than a refusal, which leaves the one-to-one mapping this check ran before the second surface existed.
@@ -125,6 +125,8 @@ It reports four things. `lesson` is the numbered path the lesson takes, derived
125
125
 
126
126
  The stylesheet is reported rather than written. A verb that wrote it on every lesson would discard whatever the last lesson added, and the second lesson in a workspace is the one that needs the file the first one left.
127
127
 
128
+ `canon teach stylesheet <topic>` is what writes it, seeded from the design source so a workspace renders in the system every other surface does. It refuses to overwrite, reporting `written` as false where the workspace already carries one, and `--force` takes the seed back over it. Each workspace used to carry a hand-authored palette, which is how the course styling forked once per workspace, so a lesson adds its own rules under the seed and reaches a value through its custom property rather than restating the hex.
129
+
128
130
  Each `quiz` entry carries `order`, the authored option indices in presentation order where index `0` is the correct answer, and `answer`, the one-based position that answer lands in. Both travel together because a caller deriving the position itself is a caller that can derive it wrongly.
129
131
 
130
132
  The order is drawn here rather than instructed, and that is the point of the verb. An author told to vary the position still varies it by judgment, and the judgment settles on the first slot, which is the defect this design departs from. The draw is uniform over the options, so the position carries no information about which answer is correct.
@@ -108,12 +108,10 @@ A person points it at a private repository once and both verbs refuse until they
108
108
 
109
109
  `canon records size` reports what each of these folders holds, heaviest first, along with `.canon/tmp`. Each row carries the file count, the bytes, how many files were written in the last 7 and 30 days, and the dates of the least and most recently written one. Nothing fails on a number, because a record folder has no correct size. What the verb replaces is a reading somebody had to remember to take: the memory pen went from 44 entries to 236 between two counts made by hand a fortnight apart, and nothing reported the rate in between.
110
110
 
111
- A plan that ships is archived, never deleted. `canon:claude-docs` moves it to `.canon/plans/archive/` and retargets the task file's `Plan:` line at the new location, so a completed task still leads to the reasoning behind it. An archive sits inside the record folder it archives rather than beside it, so one ignore entry and one backed-folder entry cover a record and everything it has retired. The folder is gitignored, which is why a deleted plan had no recovery path. A plan cited by more than one task stays put until the last of them closes, since moving it early would strand every other pointer.
111
+ A plan that ships is archived, never deleted. `canon tasks archive` moves it to `.canon/plans/archive/` alongside the task it belonged to and retargets that task's `Plan:` line at the new location, so a completed task still leads to the reasoning behind it. An archive sits inside the record folder it archives rather than beside it, so one ignore entry and one backed-folder entry cover a record and everything it has retired. The folder is gitignored, which is why a deleted plan had no recovery path. A plan cited by more than one task stays put until the last of them closes, since moving it early would strand every other pointer.
112
112
 
113
113
  A branch review report takes the other route and is swept rather than archived. `claude-review` writes it to `.canon/review/branch/`, the session addressing it reads it once, and the durable record of what a review found is the comment `claude-pr-review` posts on the pull request, so `claude-docs` deletes any report whose branch is gone. The body that writes a report owns how long it lives, which leaves the shipping branch's own report on disk through the run that cites it and collects it a branch later. What that loses is a local-only review on a branch that never opened a pull request, which is why the report says so where a reader meets it.
114
114
 
115
- The plans sweep reads the whole board rather than the tasks the session touched. It is the one place the skill reaches past its own rule against editing a task file the session did not change, because a task that closed while an earlier run missed its archive is exactly what the sweep exists to clear. Reaching it is safe: the archive moves the plan and retargets the pointer in the same pass, so an untouched task ends up with a working link rather than a broken one.
116
-
117
115
  `canon:claude-docs` decides which task closed by reading the diff rather than the conversation. It resolves a merge base against `origin/main`, unions the committed diff with the working tree and untracked files, then matches unchecked outcomes on the board against what shipped. A task that shipped without ever being discussed still gets marked. Requirements, architecture, and design stay session-sourced, because a diff cannot carry a judgment.
118
116
 
119
117
  `.canon/tasks/` is gitignored and resolves at the main worktree root, so every session shares one board. One file per task is what keeps concurrent sessions from overwriting each other, since a gitignored board has no history to recover a lost write from. Its `index.md` is generated by a hook rather than by `bun run check`, because the whole-repo index walk skips gitignored folders.
@@ -126,7 +124,7 @@ Past that shape, it checks what a surviving row claims against what the tree hol
126
124
 
127
125
  `canon:claude-tasks` owns the two operations that bracket a task's life. It creates the file, holding the filename convention and the frontmatter contract so a malformed write cannot break the index for every sibling, and it moves a shipped task to `.canon/tasks/archive/`. Creation is where the origin invariant is enforced: every task names a plan, a groundwork folder, an intake folder, or an issue, since a task with no origin is either lost context or work nobody decided to do.
128
126
 
129
- Archiving a task leaves its plan alone, because `canon:claude-docs` owns the plans sweep and already holds the last-live-citation rule. That makes the order load-bearing, so the archive verb refuses the last task pointing at a live plan. The sweep only reaches tasks still in the live folder, and archiving that task first would strand the plan there with nothing citing it. A task whose plan a sibling still cites archives freely, because the sweep is correct to leave that plan where it is and a gate reading the folder instead would park every task sharing one plan behind a sweep that will never move it.
127
+ Archiving a task carries its plan with it, in the same act. The merge is what settles a plan, and the hook below reaches the archive with nobody watching, so a second call after it would be a second failure point leaving the task archived and the plan live. A task whose plan a sibling still cites archives on its own and leaves the plan live, since moving it on the first task to close strands every other pointer at a path that has gone.
130
128
 
131
129
  Nothing chained that archive until the `post-merge` git hook landed. Every earlier step fires from `canon:claude-autoship` or `canon:git-ship`, both of which finish while the pull request is still open, so a task archived there would close for work that may be abandoned. The board is gitignored, which rules out reading it from anywhere but the machine that pulled. The hook names the board's archive candidates and stays silent otherwise, including on a project with no board.
132
130
 
@@ -24,6 +24,8 @@ The toolkit seed in `tooling/claude/seeds/.claude/DESIGN.md` ships a token-table
24
24
 
25
25
  The `canon:claude-design-extract` skill drafts the file, sourcing tokens from a project's existing prose and CLI UI surfaces, or proposing them from `.claude/REQUIREMENTS.md` and a `## Personality` paragraph when no UI code exists yet. `canon design render` writes an HTML plus CSS preview to `.canon/review/design/` for eyeballing the current system without leaving Claude Code. See `.claude/context/design.md`.
26
26
 
27
+ A project wanting the toolkit's own values rather than its own runs `canon design install`, which copies one stylesheet to `.claude/design/base.css` carrying the token set as custom properties and two components built on them. That file is toolkit-owned and `canon design sync` refreshes it, so a project overrides a value in `.claude/design/project/` instead, which sync never touches. Nothing arrives without that install, and the two channels are independent: a record drafted by the extract skill is the project's own, and the installed stylesheet is the toolkit's.
28
+
27
29
  A cell no source anchors ends in `? verify`, and the preview shows that marker beside the value rather than folding it in, so a swatch and a font sample stay built from the value alone. A confidence line above the sections names how many cells are anchored against how many are tagged, which is what tells a reader whether they are looking at a record of the code or a proposal about it. It reads the columns a source could anchor and leaves out the row names, so the ratio is not diluted by cells no tag could ever reach. The proposal path tags nearly all of them, so that count reads low on day one by design.
28
30
 
29
31
  ### Tools