@erclx/canon 4.120.0 → 4.122.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 (152) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/SKILL.md +29 -115
  3. package/claude/skills/auto-ship/references/draft-mark.md +30 -0
  4. package/claude/skills/auto-ship/references/failure-recovery.md +27 -0
  5. package/claude/skills/auto-ship/references/tier-failures.md +26 -0
  6. package/claude/skills/auto-ship/references/verb-absent.md +37 -0
  7. package/claude/skills/auto-ship/references/worktree-name.md +22 -0
  8. package/claude/skills/backlog-triage/REQUIREMENT.md +47 -0
  9. package/claude/skills/backlog-triage/SKILL.md +139 -0
  10. package/claude/skills/bash-script/SKILL.md +1 -1
  11. package/claude/skills/bash-script/references/patterns.md +0 -99
  12. package/claude/skills/bash-script/references/prompts.md +100 -0
  13. package/claude/skills/canon-rollout/SKILL.md +6 -6
  14. package/claude/skills/context-fold/REQUIREMENT.md +75 -0
  15. package/claude/skills/context-fold/SKILL.md +162 -0
  16. package/claude/skills/{docs-fold → context-fold}/references/anchor-sweep.md +1 -1
  17. package/claude/skills/{docs-fold → context-fold}/references/classify.md +4 -2
  18. package/claude/skills/context-fold/references/context-refresh.md +48 -0
  19. package/claude/skills/context-fold/references/promoted-pages.md +29 -0
  20. package/claude/skills/context-fold/references/receipt-sweep.md +43 -0
  21. package/claude/skills/context-fold/references/unusable-baseline.md +25 -0
  22. package/claude/skills/{docs-fold → context-fold}/references/wireframe-sweep.md +1 -1
  23. package/claude/skills/create-skill/SKILL.md +1 -1
  24. package/claude/skills/create-snippet/SKILL.md +1 -1
  25. package/claude/skills/create-standard/SKILL.md +1 -1
  26. package/claude/skills/design-extract/SKILL.md +1 -1
  27. package/claude/skills/design-taste/REQUIREMENT.md +2 -2
  28. package/claude/skills/docs-fold/REQUIREMENT.md +8 -58
  29. package/claude/skills/docs-fold/SKILL.md +3 -278
  30. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  31. package/claude/skills/docs-sync/SKILL.md +2 -2
  32. package/claude/skills/draft-context/REQUIREMENT.md +2 -2
  33. package/claude/skills/draft-context/SKILL.md +4 -4
  34. package/claude/skills/draft-docs/REQUIREMENT.md +1 -1
  35. package/claude/skills/draft-docs/SKILL.md +1 -1
  36. package/claude/skills/draft-figure/SKILL.md +1 -1
  37. package/claude/skills/draft-readme/SKILL.md +1 -1
  38. package/claude/skills/draft-wiki/SKILL.md +1 -1
  39. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  40. package/claude/skills/draft-wireframes/SKILL.md +3 -3
  41. package/claude/skills/git-issue/SKILL.md +1 -1
  42. package/claude/skills/git-issue/references/issue.md +1 -1
  43. package/claude/skills/git-pr/SKILL.md +9 -147
  44. package/claude/skills/git-pr/references/evidence.md +114 -0
  45. package/claude/skills/git-pr/references/labels.md +20 -0
  46. package/claude/skills/git-pr/references/task-number.md +26 -0
  47. package/claude/skills/git-ship/SKILL.md +1 -1
  48. package/claude/skills/git-stage/SKILL.md +1 -1
  49. package/claude/skills/memory-capture/REQUIREMENT.md +2 -2
  50. package/claude/skills/memory-capture/SKILL.md +6 -6
  51. package/claude/skills/memory-review/REQUIREMENT.md +1 -1
  52. package/claude/skills/memory-review/SKILL.md +5 -101
  53. package/claude/skills/memory-review/references/later-phases.md +108 -0
  54. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  55. package/claude/skills/restate-plainly/REQUIREMENT.md +1 -1
  56. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  57. package/claude/skills/review-address/SKILL.md +5 -4
  58. package/claude/skills/review-branch/REQUIREMENT.md +1 -0
  59. package/claude/skills/review-branch/SKILL.md +6 -7
  60. package/claude/skills/review-craft/REQUIREMENT.md +43 -0
  61. package/claude/skills/review-craft/SKILL.md +50 -0
  62. package/claude/skills/review-craft/references/security.md +21 -0
  63. package/claude/skills/review-craft/references/ui.md +72 -0
  64. package/claude/skills/review-pr/REQUIREMENT.md +1 -0
  65. package/claude/skills/review-pr/SKILL.md +1 -1
  66. package/claude/skills/review-pr/references/post.md +1 -1
  67. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +1 -1
  68. package/claude/skills/role-orchestrator/references/orchestrator-parked.md +2 -0
  69. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  70. package/claude/skills/role-planner/REQUIREMENT.md +1 -1
  71. package/claude/skills/session-resume/REQUIREMENT.md +1 -1
  72. package/claude/skills/session-worktree/SKILL.md +4 -10
  73. package/claude/skills/standards-audit/SKILL.md +1 -1
  74. package/claude/skills/task-board/REQUIREMENT.md +1 -1
  75. package/claude/skills/task-board/SKILL.md +6 -80
  76. package/claude/skills/task-board/references/archive.md +54 -0
  77. package/claude/skills/task-board/references/decline.md +40 -0
  78. package/claude/skills/teach-workspace/SKILL.md +8 -105
  79. package/claude/skills/teach-workspace/references/lesson-craft.md +51 -1
  80. package/claude/skills/teach-workspace/references/promotion.md +41 -0
  81. package/claude/skills/write-human/REQUIREMENT.md +7 -4
  82. package/claude/skills/write-human/SKILL.md +4 -2
  83. package/claude/skills/write-human/references/machine-tells.md +1 -1
  84. package/claude/skills/write-human/references/source-material.md +5 -2
  85. package/docs/agents/architecture-record.md +26 -0
  86. package/docs/agents/audits.md +1 -1
  87. package/docs/agents/commands.md +87 -154
  88. package/docs/agents/context-audit-checks.md +2 -20
  89. package/docs/agents/context-audit.md +1 -1
  90. package/docs/agents/context-classify.md +1 -1
  91. package/docs/agents/drift-surfaces.md +192 -0
  92. package/docs/agents/index.md +12 -4
  93. package/docs/agents/install-and-sync.md +2 -181
  94. package/docs/agents/markdown-audit-weight.md +73 -0
  95. package/docs/agents/markdown-audit.md +11 -104
  96. package/docs/agents/migrate.md +34 -0
  97. package/docs/agents/pr-local.md +6 -3
  98. package/docs/agents/records-stale.md +62 -0
  99. package/docs/agents/records.md +5 -1
  100. package/docs/agents/sandbox.md +1 -1
  101. package/docs/agents/tasks-archive.md +81 -0
  102. package/docs/agents/tasks-plans.md +147 -0
  103. package/docs/agents/tasks-validate.md +131 -0
  104. package/docs/agents/tasks.md +6 -333
  105. package/docs/target-projects.md +4 -4
  106. package/docs/workflow/ai-workflow.md +33 -27
  107. package/governance/rules/claude/500-prose.md +1 -1
  108. package/governance/rules/claude/501-markdown.md +3 -3
  109. package/governance/rules/core/045-memory.md +1 -1
  110. package/package.json +1 -1
  111. package/src/autoship/paths.ts +3 -2
  112. package/src/claude/cases/setup.ts +1 -1
  113. package/src/claude/cases/workflow.ts +27 -1
  114. package/src/commands/audits.ts +2 -2
  115. package/src/commands/markdown.ts +7 -9
  116. package/src/commands/pr.ts +11 -4
  117. package/src/commands/records.ts +146 -0
  118. package/src/commands/sandbox.ts +6 -4
  119. package/src/context/classify/extract.ts +1 -1
  120. package/src/context/classify/ollama.ts +1 -1
  121. package/src/context/classify/run.ts +1 -1
  122. package/src/gate/measures.ts +5 -5
  123. package/src/gate/stages.ts +0 -1
  124. package/src/markdown/bans.ts +11 -68
  125. package/src/markdown/gate.ts +1 -1
  126. package/src/markdown/scan.ts +7 -88
  127. package/src/migrate/skill-names.ts +15 -2
  128. package/src/records/stale.ts +243 -0
  129. package/src/sandbox/expect.ts +1 -1
  130. package/src/tasks/archive.ts +28 -4
  131. package/src/tasks/record.ts +1 -1
  132. package/src/tasks/validate.ts +36 -3
  133. package/standards/docs.md +1 -1
  134. package/standards/index.md +1 -1
  135. package/standards/markdown.md +5 -7
  136. package/standards/memory.md +13 -1
  137. package/standards/pr.md +1 -1
  138. package/standards/publish.md +1 -1
  139. package/standards/readme.md +2 -2
  140. package/standards/tasks.md +1 -1
  141. package/tooling/astro/reference.md +1 -1
  142. package/tooling/base/configs/.husky/post-merge +1 -1
  143. package/tooling/base/configs/scripts/verify.sh +2 -2
  144. package/tooling/base/seeds/canon/context/development.md +1 -1
  145. package/tooling/claude/reference.md +1 -1
  146. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +5 -6
  147. package/tooling/go/reference.md +1 -1
  148. package/tooling/nextjs/reference.md +1 -1
  149. package/tooling/php/reference.md +1 -1
  150. package/tooling/python/reference.md +1 -1
  151. package/tooling/vite-react/reference.md +1 -1
  152. package/tooling/web/reference.md +1 -1
@@ -41,6 +41,8 @@ Clearing a row answers yes to that question. It is not a count of how long the r
41
41
 
42
42
  A row this test clears moves out of `backlog.md` and onto the bottom of `## Needs a plan`, carrying a `Waiting on` cell that states what changed and ends with the word `last`. `canon tasks validate`'s ordering check reads `last` as the ordinal bottom placement already claims, and a cell stating only what changed matches neither that check's ordinal reading nor its rank reading, which raises the finding this pass would then have written against its own row. The next refill sweep's promotion step still orders the row against the rest of that heading the way it orders every other row there, per step 5 under `## Refilling the ready queue` in `role-orchestrator`.
43
43
 
44
+ This pass only clears a row upward. Declining or archiving a backlog row is `backlog-triage`, which files a verdict for every row and applies the ones the operator approved.
45
+
44
46
  ## Two ways a re-test goes wrong
45
47
 
46
48
  Both return a confident wrong answer rather than an error, which is why each gets a step of its own.
@@ -14,7 +14,7 @@ It counts unclaimed plans against workers rather than reading the reserve in ste
14
14
  1. Run `gh pr list --state open` and `git log --oneline -8`. Report any pull request whose review has not been posted and stop for that one first.
15
15
  2. For each pull request merged since the last sweep, place every finding it produced. Route a finding that changes a rule to the standard or rule that states it, one that changes another task to that task's Findings, and one that overturns a groundwork lean to that folder marked answered. Never leave a finding in a pull request thread alone.
16
16
  3. Archive what closed.
17
- - A task whose outcomes are all `[x]` runs `docs-fold` for the plan sweep, then `task-board` to archive
17
+ - A task whose outcomes are all `[x]` runs `context-fold` for the plan sweep, then `task-board` to archive
18
18
  - A task whose outcomes describe standing policy rather than a deliverable never closes on its own, so hand it to a worker to encode the policy where it is enforced, then cut the outcomes with the reason recorded and archive once that branch merges. Encoding it from this session would write a tracked file, which `## Boundaries` in the skill body forbids.
19
19
  4. Read `.canon/tasks/priority.md` and count entries under its `## Run now` heading that carry a written plan. Keep one in reserve beyond what is running.
20
20
  5. Promote from the top of `## Needs a plan`, which is where the last sweep recorded what to plan next. Depart from that order when something has changed under it and say what changed, since a position nobody honors is the ordering going stale on the surface built to hold it. What sets the order in the first place is whether a task establishes functionality rather than how old it is, so prefer a task that adds or proves a mechanism over one that trims, tidies, or audits an existing surface.
@@ -53,4 +53,4 @@ Promoting, demoting, and archiving a row all write `.canon/tasks/priority.md`, a
53
53
  - Re-resolve every Plan pointer after anything archives a plan
54
54
  - Read the file back after writing it, since the row that lands is the row a worker acts on
55
55
 
56
- A Plan pointer goes stale from a branch this board never sees. `docs-fold` moves a plan to `.canon/plans/archive/` and rewrites the citation in the task file alone, so a row for a task still on the board keeps pointing into `.canon/plans/` at a file that has moved. Workers running the ship chain on their own branches archive plans this board still cites, and the board reads as correct until a pointer is followed.
56
+ A Plan pointer goes stale from a branch this board never sees. `context-fold` moves a plan to `.canon/plans/archive/` and rewrites the citation in the task file alone, so a row for a task still on the board keeps pointing into `.canon/plans/` at a file that has moved. Workers running the ship chain on their own branches archive plans this board still cites, and the board reads as correct until a pointer is followed.
@@ -41,7 +41,7 @@ confident merge order off a partial picture. Which rows collide, what merges
41
41
  before what, and whether a row should run at all stay with the controlling
42
42
  session, and no body states that boundary for the planning side.
43
43
 
44
- Nothing scans a plan for banned words either. `.gitignore` ignores
44
+ Nothing scans a plan for ban hits either. `.gitignore` ignores
45
45
  `.canon/plans/`, and the audit's default path set is what git lists, so no gate
46
46
  ever opens one. Six ban hits landed across the four trial plans and three of the
47
47
  four carried at least one, caught only because the planner ran
@@ -45,5 +45,5 @@ A resume is a read, and a session that treats it as a cleanup pass offers to arc
45
45
  - Writing a handoff, which happens at the close of a session rather than at its start. This skill names the standard that governs one and follows it no further.
46
46
  - The sections a role adds over the core handoff, which belong to that role's own surface
47
47
  - Archiving a shipped task out of the folder: `task-board`
48
- - Archiving a plan and marking an outcome, which `docs-fold` does when the work ships
48
+ - Archiving a plan and marking an outcome, which `context-fold` does when the work ships
49
49
  - Implementing the item it recommends, which is the next request rather than part of this one
@@ -47,21 +47,17 @@ Try each source in order. Stop at the first match.
47
47
  4b. **Session context.** When on `main` or `master` with no matching plan, read the current conversation to infer a kebab slug from the topic being discussed. Propose it: `Infer: <slug>. Confirm or rename?` Do not enter the worktree until the user confirms or provides a corrected name.
48
48
  5. **Ask.** None of the above applies. Ask the user for a name. Do not invent one.
49
49
 
50
- Tier 0 sits ahead of the inference because every tier below it answers from state the caller cannot set, and a caller that already knows the name has no way to say so. That gap is what a dispatched worker meets. It starts on `main`, so tier 1 cannot match, and a board carrying more than one plan puts tier 2 out of reach too, which lands the run on tier 3 and its instruction to ask somebody who is not there. A prompt naming the branch does not reach any of them, since no tier reads the prompt.
50
+ Tier 0 sits ahead of the inference because every tier below it answers from state the caller cannot set. A dispatched worker starts on `main` beside several plans, so without it the run lands on tier 3 and asks somebody who is not there.
51
51
 
52
52
  Validate the result: letters, digits, dots, underscores, dashes only, max 64 chars (`/` separators are also allowed). If the derived name violates the rule, sanitize by replacing invalid chars with `-` and truncating. Show the sanitized name in the preview before invoking.
53
53
 
54
54
  Resolve `<type>` here as well, since Step 3 previews it and Step 5 renames onto it, drawing the value from the type vocabulary in `${CLAUDE_SKILL_DIR}/../../standards/branch.md`. A type the caller spelled in tier 0 wins outright and no reading overrides it. A name that came from a plan, through tier 1 or tier 2, takes its type from `canon tasks plan-branch <plan> --json`, read off the record's `type` field. Every other case takes `feat`, which covers a name from a branch, a bare name from the user, and a plan the verb could not answer for.
55
55
 
56
- The verb is the reading rather than this body, because a type read off a plan's `## Summary` and `**Files to touch:**` lines is a judgment, and it has disagreed with itself in production. One dispatch checked `fix/path-form-hook` and the worker took `feat/path-form-hook`, both sides reading the same plan and grading it differently. The verb answers `feat` for every plan, so two sides calling it cannot part.
57
-
58
- The caller's type still wins over the verb's, because tier 0 is the one source that knows something no file states. The ordinary caller is `auto-ship` Step 0, which ran the verb itself and is handing over the answer it got, so nothing is overridden in that case either.
56
+ The verb is the reading rather than this body, because a type read off a plan's sections is a judgment that two sessions reading one plan can grade differently, and the verb answers the same for both. The caller's type still wins over the verb's, because tier 0 is the one source that knows something no file states.
59
57
 
60
58
  Branch on the record rather than on the exit code, which a shell function wrapping `canon` can flatten to zero. Take `feat` and say the verb did not answer where it refuses, where the record carries no `type` key, or where the installed binary carries no `plan-branch` subcommand.
61
59
 
62
- A wrong type is cheap because the branch type is cosmetic rather than because anything corrects it. `git-stage` reads a commit's type off the staged diff and `git-pr` reads a title off the diff too, so the semantics a release reads never pass through the branch name at all. Nothing downstream is wrong when a `feat/` sits over a fix, and a person scanning a worktree listing loses a signal.
63
-
64
- `git-branch` does not correct it, which three surfaces used to say it did. Its second guard reads `If branch name already follows conventions, stop`, and `${CLAUDE_SKILL_DIR}/../../standards/branch.md` makes the type vocabulary an axis without making the choice within that vocabulary one, so `feat/` over a fix conforms and the guard fires ahead of the Analysis line that would have re-derived the type.
60
+ A wrong type is cosmetic, since `git-stage` and `git-pr` read their types off the diff rather than the branch name. Nothing corrects it later either: `git-branch` stops on any name that already conforms, and `feat/` over a fix conforms.
65
61
 
66
62
  Then test both names the entry is about to claim. Neither read needs a worktree, and a stop after Step 4 leaves one built with the session sitting inside it, so both belong here rather than beside the rename:
67
63
 
@@ -74,7 +70,7 @@ The two tests catch different collisions. The branch test misses the one `${CLAU
74
70
 
75
71
  The branch test fires on the tier 1 and tier 4 sources whenever the branch the session started on is already conventional, since a name derived from that branch resolves back onto it. Stopping is the answer there. The concern already has a branch, git refuses a second under the same name, and the bare-name rename this replaces only carried the collision forward to the `git-branch` step. It fires on tier 0 as well, where a caller handed a name something already holds.
76
72
 
77
- It reads both ref spaces rather than the local head alone, and it reads them the way `checkClaim` does, so a name this skill clears and a branch a dispatcher cleared are one answer. `git show-ref --verify` is what that replaces. It sees no remote-tracking ref, so a branch pushed from elsewhere passed the test and collided at the first push, and its exit code cannot separate an absent ref from a tree it could not read, which reports a failed read as a free name.
73
+ It reads both ref spaces rather than the local head alone, the way `checkClaim` does, so a name this skill clears and a branch a dispatcher cleared are one answer, and a branch pushed from elsewhere is caught before the first push rather than at it.
78
74
 
79
75
  ## Step 3: preview
80
76
 
@@ -153,8 +149,6 @@ Then report the port this worktree derives, on a second line:
153
149
 
154
150
  Branch on the exit rather than on the output, since the helper prints nothing to stdout when it refuses and reading that as a number reports an offset of zero, which is the main checkout's.
155
151
 
156
- The helper refuses a folder left behind after its worktree was removed, which Step 4 cannot land on, since it registers whatever it creates. The branch is here so a refusal is never read back as an offset of zero, which is the main checkout's port and the collision the helper exists to prevent.
157
-
158
152
  The offset is what `role-orchestrator` sends a reader here to read rather than assign, and what an operator overrides through `WORKTREE_PORT_OFFSET` when two worktrees derive the same value. Deriving it correctly and printing it nowhere leaves both instructions naming a number no surface emits.
159
153
 
160
154
  Do not invoke `ExitWorktree` from this skill. Exit is the user's call.
@@ -75,7 +75,7 @@ For each changed file, audit against every rule:
75
75
  - **Pattern rules**: grep the file for every token the standard bans. Grep is authoritative. Reading alone misses occurrences.
76
76
  - **Judgment rules**: check each rule in context against the standard that states it.
77
77
 
78
- Every changed markdown file gets the pattern pass, since `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` applies to all of them. Take the banned tokens from that standard at read time rather than from a list held here. It carries the banned words, the banned spellings, and the banned characters together, so one read covers every token this pass greps for.
78
+ Every changed markdown file gets the pattern pass, since `${CLAUDE_SKILL_DIR}/../../standards/markdown.md` applies to all of them. Take the banned tokens from that standard at read time rather than from a list held here. It carries the banned characters, so one read covers every token this pass greps for.
79
79
 
80
80
  ## Step 4: report
81
81
 
@@ -42,6 +42,6 @@ Placing a row without checking for another writer collides the same way. Two ses
42
42
 
43
43
  ## Out of scope
44
44
 
45
- - Editing the contents of a task that already exists, which `docs-fold` owns along with marking outcomes and sweeping plans
45
+ - Editing the contents of a task that already exists, which `context-fold` owns along with marking outcomes and sweeping plans
46
46
  - Deciding what the task should argue. This owns the file's existence and its shape, not its content.
47
47
  - Relocating a plan, which one skill owns so two do not relocate it differently
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  name: task-board
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 `docs-fold`.
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 `context-fold`.
4
4
  ---
5
5
 
6
6
  # Task board
7
7
 
8
- Owns the two operations that bring a task file into existence and take it out of the folder. `docs-fold` 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.
8
+ Owns the two operations that bring a task file into existence and take it out of the folder. `context-fold` 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
 
12
12
  ## Guards
13
13
 
14
14
  - Resolve the board at the main worktree root, not `pwd`. Run `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd` outside a git repo. Every read and write below resolves against that root. The board is gitignored scratch shared across worktrees, so a linked worktree writing to its own `pwd` creates a second board nothing else reads.
15
- - A new task file is a main-root write, so it goes out as a heredoc. Archiving already runs through `canon tasks archive`, which resolves the root in-process. Marking an outcome shipped is `docs-fold` and runs through `canon tasks outcome`. Resolve that root and route the write the way `session-worktree` states.
15
+ - A new task file is a main-root write, so it goes out as a heredoc. Archiving already runs through `canon tasks archive`, which resolves the root in-process. Marking an outcome shipped is `context-fold` and runs through `canon tasks outcome`. Resolve that root and route the write the way `session-worktree` states.
16
16
  - If `.canon/tasks/` does not exist at that root, stop: `❌ No .canon/tasks/ board. Run canon claude init to set it up.`
17
17
  - Route on the request rather than on a flag. Creating names work that does not exist yet, archiving names a task file already shipped, and declining names one decided against. If the request fits none of the three, stop: `❌ Ambiguous. Say whether to create a task, archive one, or decline one.`
18
18
  - Never hand-edit `.canon/tasks/index.md`. A hook regenerates it from sibling frontmatter after a write. Do not run the regen command directly, except after a shell write from a linked worktree: the hook matches `Write|Edit|MultiEdit` and nothing fires on `Bash`, so that one case regenerates explicitly with `canon indexes regen --no-stage --root <main-root> <main-root>/.canon/tasks/index.md`.
@@ -84,85 +84,11 @@ Say the origins were read even when nothing comes back, which is the ordinary re
84
84
 
85
85
  ## Archive
86
86
 
87
- The `post-merge` git hook archives the task a merge closed, so a request arriving here is usually one the hook could not resolve on its own. Run the steps below against whatever the hook left in place.
88
-
89
- Do not move the file, edit `priority.md`, or regenerate the index by hand. `canon tasks archive` owns all three as one unit and the hook calls the same command, so a hand-rolled move here drifts from the unattended path.
90
-
91
- ### Step 1: confirm the work reached main
92
-
93
- `docs-fold` marks outcomes on the branch as step 1 of the ship chain, so an all-`[x]` task routinely describes a pull request that is still open. The command gates on the outcomes and cannot tell those two apart, which is what puts this check here:
94
-
95
- ```bash
96
- git fetch origin main --quiet && git log origin/main --oneline -20
97
- ```
98
-
99
- Match the shipped outcomes against that log, widening to `gh pr list --state merged --limit 20` when a remote is configured and the log does not settle it. When the work is not on `main`, name the task and stop: `❌ Work not on main. Archiving now loses the task if the pull request is abandoned.`
100
-
101
- The board is gitignored, so an archived task has no history behind it and nothing restores one archived early. Skip this check when the task carries a `Pull request:` line and the last number it lists is merged, since the number already proves what the log is being read for.
102
-
103
- ### Step 2: run the archive
104
-
105
- Pass the task's filename stem, or the pull request number when the request names one:
106
-
107
- ```bash
108
- canon tasks archive <stem> --json
109
- ```
110
-
111
- 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.
112
-
113
- 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.
114
-
115
- ### Step 3: route on a refusal
116
-
117
- Each reason has one resolution and none of them is to archive around it:
118
-
119
- - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `docs-fold` when the work shipped and nothing marked it. Leave the task on the board when the outcome is real. When the work is being abandoned, cut it by striking the body, `- ~~<outcome>~~ <why>`, whatever the checkbox holds, so the board records what was dropped rather than meeting this refusal a second time.
120
- - `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.
121
- - `earlier-slice`: the number is listed on the task but is not the last, so a later slice is still to merge. Leave the task on the board. Its last slice's merge archives it.
122
- - `no-match`: the stem or number names nothing on the board. Check the name against the listed stems.
123
- - `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.
124
-
125
- 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.
126
-
127
- 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.
128
-
129
- ### Step 4: clear prose naming the task
130
-
131
- The command drops the task's row from `.canon/tasks/priority.md` and leaves prose alone. Remove any sentence that still names the archived task or counts the rows that changed, since a stale count reads as board state.
87
+ Read `${CLAUDE_SKILL_DIR}/references/archive.md` when the request names a shipped task to archive. It carries the check that the work reached `main`, the `canon tasks archive` call, the route for each refusal, and the prose to clear afterwards. Never move the file, edit `priority.md`, or regenerate the index by hand, since the command owns all three.
132
88
 
133
89
  ## Decline
134
90
 
135
- A task decided against carries no `post-merge` hook of its own, so every decline request arrives here directly rather than through work the hook already did.
136
-
137
- Do not move the file, edit `priority.md` or `backlog.md`, or regenerate the index by hand. `canon tasks decline` owns all three as one unit.
138
-
139
- ### Step 1: gather the reason
140
-
141
- Ask for the reason when the request does not carry one, and stop rather than guessing: `❌ No reason. Say why the task is being declined.` The command takes it as `--reason <text>` and refuses without it, so gathering it here saves a round trip through that refusal.
142
-
143
- ### Step 2: run the decline
144
-
145
- Pass the task's filename stem:
146
-
147
- ```bash
148
- canon tasks decline <stem> --reason "<text>" [--by <name>] --json
149
- ```
150
-
151
- The command refuses rather than reports, and the refusal reaches this skill through the record rather than through the exit, the same wrapper hazard `canon tasks archive` carries. Branch on `ok`, then on `reason`.
152
-
153
- On success the record carries `from`, `to`, `priorityRowRemoved`, `backlogRowRemoved`, and `indexRegenerated`, where `backlogRowRemoved` is decline's own field since a task can be declined straight off `backlog.md` and archive never checks that file. 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, the same shape `canon tasks archive` uses for its own `plan` field.
154
-
155
- ### Step 3: route on a refusal
156
-
157
- - `no-match`: the stem does not name exactly one task. Either none matches, or exactly one starts with it and the full name is needed. Check the name against the listed stems.
158
- - `ambiguous`: the stem is a prefix more than one task starts with, unlike archive's own `ambiguous`, which fires on a shared pull request. Decline takes no pull-request selector, so this is the only route to it. Pass the full stem.
159
- - `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.
160
-
161
- 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 declined task's `Plan:` line at the new path. A second mover drifts into relocating the same file differently.
162
-
163
- ### Step 4: clear prose naming the task
164
-
165
- The command drops the task's row from `.canon/tasks/priority.md` or `.canon/tasks/backlog.md` and leaves prose alone. Remove any sentence that still names the declined task or counts the rows that changed, since a stale count reads as board state.
91
+ Read `${CLAUDE_SKILL_DIR}/references/decline.md` when the request names a task decided against. It carries gathering the reason, the `canon tasks decline` call, the route for each refusal, and the prose to clear afterwards. Never move the file, edit `priority.md` or `backlog.md`, or regenerate the index by hand, since the command owns all three.
166
92
 
167
93
  ## Output
168
94
 
@@ -198,4 +124,4 @@ Archive, reporting the paths the command returned:
198
124
  <ordering and index disposition in one line>
199
125
  ```
200
126
 
201
- A refusal reports the reason and the resolution Step 3 routes it to, on one line each.
127
+ A refusal reports the reason and the resolution the mode's reference routes it to under its Step 3, on one line each.
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: Archive a shipped task
3
+ description: How task-board confirms a task's work reached main, runs canon tasks archive, routes each refusal, and clears prose naming the archived task
4
+ ---
5
+
6
+ # Archive a shipped task
7
+
8
+ The Archive mode of `task-board`. The session reads this file when the request names a task file already shipped.
9
+
10
+ The `post-merge` git hook archives the task a merge closed, so a request arriving here is usually one the hook could not resolve on its own. Run the steps below against whatever the hook left in place.
11
+
12
+ Do not move the file, edit `priority.md`, or regenerate the index by hand. `canon tasks archive` owns all three as one unit and the hook calls the same command, so a hand-rolled move here drifts from the unattended path.
13
+
14
+ ## Step 1: confirm the work reached main
15
+
16
+ `context-fold` marks outcomes on the branch as step 1 of the ship chain, so an all-`[x]` task routinely describes a pull request that is still open. The command gates on the outcomes and cannot tell those two apart, which is what puts this check here:
17
+
18
+ ```bash
19
+ git fetch origin main --quiet && git log origin/main --oneline -20
20
+ ```
21
+
22
+ Match the shipped outcomes against that log, widening to `gh pr list --state merged --limit 20` when a remote is configured and the log does not settle it. When the work is not on `main`, name the task and stop: `❌ Work not on main. Archiving now loses the task if the pull request is abandoned.`
23
+
24
+ The board is gitignored, so an archived task has no history behind it and nothing restores one archived early. Skip this check when the task carries a `Pull request:` line and the last number it lists is merged, since the number already proves what the log is being read for.
25
+
26
+ ## Step 2: run the archive
27
+
28
+ Pass the task's filename stem, or the pull request number when the request names one:
29
+
30
+ ```bash
31
+ canon tasks archive <stem> --json
32
+ ```
33
+
34
+ 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.
35
+
36
+ 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.
37
+
38
+ ## Step 3: route on a refusal
39
+
40
+ Each reason has one resolution and none of them is to archive around it:
41
+
42
+ - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `context-fold` when the work shipped and nothing marked it. Leave the task on the board when the outcome is real. When the work is being abandoned, cut it by striking the body, `- ~~<outcome>~~ <why>`, whatever the checkbox holds, so the board records what was dropped rather than meeting this refusal a second time.
43
+ - `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.
44
+ - `earlier-slice`: the number is listed on the task but is not the last, so a later slice is still to merge. Leave the task on the board. Its last slice's merge archives it.
45
+ - `no-match`: the stem or number names nothing on the board. Check the name against the listed stems.
46
+ - `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.
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ ## Step 4: clear prose naming the task
53
+
54
+ The command drops the task's row from `.canon/tasks/priority.md` and leaves prose alone. Remove any sentence that still names the archived task or counts the rows that changed, since a stale count reads as board state.
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Decline a task
3
+ description: How task-board gathers the reason a task is decided against, runs canon tasks decline, routes each refusal, and clears prose naming the declined task
4
+ ---
5
+
6
+ # Decline a task
7
+
8
+ The Decline mode of `task-board`. The session reads this file when the request names a task decided against.
9
+
10
+ A task decided against carries no `post-merge` hook of its own, so every decline request arrives here directly rather than through work the hook already did.
11
+
12
+ Do not move the file, edit `priority.md` or `backlog.md`, or regenerate the index by hand. `canon tasks decline` owns all three as one unit.
13
+
14
+ ## Step 1: gather the reason
15
+
16
+ Ask for the reason when the request does not carry one, and stop rather than guessing: `❌ No reason. Say why the task is being declined.` The command takes it as `--reason <text>` and refuses without it, so gathering it here saves a round trip through that refusal.
17
+
18
+ ## Step 2: run the decline
19
+
20
+ Pass the task's filename stem:
21
+
22
+ ```bash
23
+ canon tasks decline <stem> --reason "<text>" [--by <name>] --json
24
+ ```
25
+
26
+ The command refuses rather than reports, and the refusal reaches this skill through the record rather than through the exit, the same wrapper hazard `canon tasks archive` carries. Branch on `ok`, then on `reason`.
27
+
28
+ On success the record carries `from`, `to`, `priorityRowRemoved`, `backlogRowRemoved`, and `indexRegenerated`, where `backlogRowRemoved` is decline's own field since a task can be declined straight off `backlog.md` and archive never checks that file. 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, the same shape `canon tasks archive` uses for its own `plan` field.
29
+
30
+ ## Step 3: route on a refusal
31
+
32
+ - `no-match`: the stem does not name exactly one task. Either none matches, or exactly one starts with it and the full name is needed. Check the name against the listed stems.
33
+ - `ambiguous`: the stem is a prefix more than one task starts with, unlike archive's own `ambiguous`, which fires on a shared pull request. Decline takes no pull-request selector, so this is the only route to it. Pass the full stem.
34
+ - `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.
35
+
36
+ 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 declined task's `Plan:` line at the new path. A second mover drifts into relocating the same file differently.
37
+
38
+ ## Step 4: clear prose naming the task
39
+
40
+ The command drops the task's row from `.canon/tasks/priority.md` or `.canon/tasks/backlog.md` and leaves prose alone. Remove any sentence that still names the declined task or counts the rows that changed, since a stale count reads as board state.
@@ -9,7 +9,7 @@ argument-hint: <subject to learn, or the topic of a workspace to resume or promo
9
9
 
10
10
  Run a learning workspace on one subject across sessions. The workspace holds what the learner has been through, so a session weeks later resumes from the folder rather than from the conversation.
11
11
 
12
- The shape of the workspace is fixed by `${CLAUDE_SKILL_DIR}/../../standards/teach.md`. Read it before writing anything into the folder. The glossary answers to `${CLAUDE_SKILL_DIR}/../../standards/glossary.md`, whose shape governs a glossary wherever it lands, since a promoted glossary keeps its shape at whatever path it reaches and no project folder covers all of them. The pedagogy sits in `${CLAUDE_SKILL_DIR}/references/pedagogy.md`, the lesson craft in `${CLAUDE_SKILL_DIR}/references/lesson-craft.md`, and the promotion routing in `${CLAUDE_SKILL_DIR}/references/promotion.md`.
12
+ The shape of the workspace is fixed by `${CLAUDE_SKILL_DIR}/../../standards/teach.md`. Read it before writing anything into the folder. The glossary answers to `${CLAUDE_SKILL_DIR}/../../standards/glossary.md` wherever it lands. The pedagogy sits in `${CLAUDE_SKILL_DIR}/references/pedagogy.md`, the lesson craft in `${CLAUDE_SKILL_DIR}/references/lesson-craft.md`, and the promotion routing in `${CLAUDE_SKILL_DIR}/references/promotion.md`.
13
13
 
14
14
  ## Guards
15
15
 
@@ -77,9 +77,7 @@ A claim nothing was read for is the failure this step exists against. Where no s
77
77
 
78
78
  Pick the next lesson from the learning records rather than from the subject's own order. The target is the band immediately past what the learner can already do unaided, which `${CLAUDE_SKILL_DIR}/references/pedagogy.md` states in full.
79
79
 
80
- Open on what `due` reports as overdue, oldest date first, before anything new. Where nothing is overdue, open on retrieval of what the last session got wrong. A learner who cannot retrieve the previous lesson is not ready for the next one, and moving on anyway buys fluency that decays.
81
-
82
- Reading the last record alone is what the schedule replaces. A topic missed three sessions ago and never revisited is invisible to that read and sits in `due` with a date already past, which is the whole reason the field exists.
80
+ Open on what `due` reports as overdue, oldest date first, before anything new. Where nothing is overdue, open on retrieval of what the last session got wrong. A learner who cannot retrieve the previous lesson is not ready for the next one, and moving on anyway buys fluency that decays. Never place from the last record alone, since a topic missed three sessions ago is invisible to that read and sits in `due` with a date already past.
83
81
 
84
82
  ## Step 4: write the lesson and the reference
85
83
 
@@ -103,69 +101,11 @@ It writes nothing and reports three things:
103
101
  - `success`, the mission's success lines, carried here so Step 5 needs no second read.
104
102
  - `quiz`, one entry per question, carrying `order` and `answer`.
105
103
 
106
- 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.
107
-
108
- ### The quiz and the teach-back block
109
-
110
- The quiz markup is a contract rather than a convention. `canon teach nav` splices a stepper into the lesson that gates on exactly the class names and nesting `${CLAUDE_SKILL_DIR}/references/lesson-craft.md` states under `## Quiz construction`, so a quiz in any other shape shows every question at once and nothing reports it. That is the leak this shape closes: a later stem naming what an earlier question asked for answers it while both sit on screen.
111
-
112
- Write each option as a `<label class="opt" data-k="<letter>">` holding a radio `<input>` and a `<span>`, every option in one question sharing a `name` and each question taking a different one, the correct option carrying `data-a="1"`, and one `.fb` block as the last child of the `.q`. Never write a `<button class="opt">` in a new lesson. That is the shape written before the stepper, kept working by an injected script the button itself still triggers, and a lesson mixing the two gets both mechanisms.
113
-
114
- Then carry a teach-back block beside the quiz. A quiz is recognition and the pedagogy prefers production, so a lesson offering only a quiz tests the weak form. Ask for an explanation to a named audience, and carry a `<details>` listing what a complete explanation covers, closed by default, so a learner reading with no session in the room can grade themselves. The reference states both shapes in full.
115
-
116
- Compose the quiz and the teach-back block as one string. Below, that string is the block list's last `raw` entry, followed only by `refs`, rather than markup written into the file by hand.
117
-
118
- ### Building the lesson body
119
-
120
- Write the chrome as four empty marker pairs rather than composing it by hand: `<!-- canon:teach:style -->`/`<!-- /canon:teach:style -->` inside `<head>`, and `<!-- canon:teach:header -->`, `<!-- canon:teach:footnav -->`, and `<!-- canon:teach:scripts -->` each with its own close marker, in that order in `<body>`.
121
-
122
- Build the authored `<h1>`, lede, body, and quiz as a JSON array of blocks rather than composing markup by hand, and render it through the verb rather than through a component import:
123
-
124
- ```bash
125
- echo '[
126
- {"type":"heading","level":1,"text":"<title>"},
127
- {"type":"paragraph","lede":true,"text":"<the dek>"},
128
- {"type":"paragraph","text":"<a body paragraph>"},
129
- {"type":"paragraph","text":"<a claim resting on a source>","cites":[1]},
130
- {"type":"list","ordered":true,"items":["<step one>","<step two>"]},
131
- {"type":"raw","html":"<the quiz and teach-back block composed above>"},
132
- {"type":"refs","items":[{"title":"<source name>","url":"<https URL>"}]}
133
- ]' | canon teach render --json
134
- ```
135
-
136
- A `heading`, a `paragraph` (`lede: true` for the dek, `cites` for a claim resting on a source), a `list`, and the one `refs` block the cites number into cover the structural body. Reach for `raw` only where none of those can carry the content, never as a shortcut around composing one, and give the quiz and teach-back block the array's last `raw` entry every time, followed only by `refs`, since their fixed contract is not a components concern. Take the call's `html` field and write it between the header's close marker and the footnav's open marker, and nothing else anywhere in the file.
137
-
138
- Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the lesson body by hand as a fallback. That is the state this section exists to end, and a target holds this skill body before it holds the verb, since a plugin skill reaches a target the moment it merges while the CLI reaches one only when a release publishes.
139
-
140
- Then run:
141
-
142
- ```bash
143
- canon teach nav <topic> --json
144
- ```
145
-
146
- It fills every marker pair from what the workspace holds on disk: the embedded stylesheet, the header with its breadcrumb and jump menus, the prev/next footer nav, and the behavior scripts, and it rewrites the workspace's contents page and the teach-root listing in the same run. It refuses a lesson missing one of the four marker pairs by name rather than guessing at the boundary, so a marker dropped while writing the lesson is caught here rather than read back later as a lesson nothing links to. Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the chrome by hand as a fallback.
147
-
148
- Seed the stylesheet through the verb rather than authoring a palette, on the first lesson in a workspace:
149
-
150
- ```bash
151
- canon teach stylesheet <topic> --json
152
- ```
153
-
154
- 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.
104
+ 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, since a lesson that reorders on its own judgment puts the answer back in the first slot.
155
105
 
156
- 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.
106
+ ### The quiz, the teach-back block, and the lesson body
157
107
 
158
- Add every term the lesson defines to `GLOSSARY.md` through the verb, which places the entries alphabetically in the shape the standard fixes:
159
-
160
- ```bash
161
- canon teach glossary <topic> --json \
162
- --term "<term>=<definition, written without using the term>" \
163
- --first-seen <the lesson or reference page this batch comes from>
164
- ```
165
-
166
- `--term` repeats and one call writes the file once, which is what keeps a batch of terms from racing on it. A term already defined is refused, since a definition the subject has moved under is a revision of the entry rather than a second one.
167
-
168
- Follow `${CLAUDE_SKILL_DIR}/references/lesson-craft.md` for what makes a lesson worth returning to. Keep every quiz answer the same length, so formatting leaks no clue about which one is correct.
108
+ Read `${CLAUDE_SKILL_DIR}/references/lesson-craft.md` before writing the lesson. `## Quiz construction` and `## Teach back` there fix the markup `canon teach nav`'s stepper gates on, so a quiz in any other shape shows every question at once and nothing reports it. `## Building the lesson body` carries the four marker pairs, the render, nav, stylesheet, and glossary verbs, and the rule against composing any of them by hand when a verb does not resolve. Keep every quiz answer the same length, so formatting leaks no clue about which one is correct.
169
109
 
170
110
  ### Hand over a link, never a path
171
111
 
@@ -209,37 +149,7 @@ Then restate the mission's success lines with what is now met, reading them from
209
149
 
210
150
  Run this when the invocation asks for it, or offer it in one line when a mission finishes, since that is when the workspace stops growing and its reference pages stop changing. Never run it unasked mid-course.
211
151
 
212
- Read `${CLAUDE_SKILL_DIR}/references/promotion.md` first. It carries what may be promoted, the routing test, both spellings of the wiki folder, the refusal when a project has none, and what each destination expects a page to carry.
213
-
214
- Propose and wait. A promoted page is public prose that needs a line naming who owns its subject, which is a judgment about ownership rather than a move a session makes on its own reading. Present one block per candidate page:
215
-
216
- ```plaintext
217
- reference/<slug>.md → <destination path>
218
- Subject owner: <who owns it, in a few words>
219
- Still owed: <what the destination expects that the page does not carry yet>
220
- ```
221
-
222
- Then stop and let the operator strike, redirect, or confirm each block.
223
-
224
- Write nothing to a destination here. One skill owns the durable writes, and two skills editing one file at one step is the failure that rule exists against. Record each confirmed block in `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root instead, appending when the file exists, with one H2 per destination naming its path, the source page beneath it, and the page body fenced:
225
-
226
- ````markdown
227
- ## <destination path>
228
-
229
- Source: .canon/teach/<nn>-<topic>/reference/<slug>.md
230
-
231
- ```markdown
232
- <the page body as it should land, with the source line the destination expects>
233
- ```
234
- ````
235
-
236
- The body is fenced rather than written bare because a reference page carries headings of its own, and the reader splits this file on its H2 lines. An unfenced body turns every section heading in the page into a destination naming no path. Open the body fence with four backticks so a page carrying a fenced code block of its own still closes where it should, and widen both fences together if it carries a four-backtick fence.
237
-
238
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
239
-
240
- The handoff is its own file rather than a shared one. The routed-facts file another skill writes is deleted by whichever pass folds it, so a second producer's unread work goes with it, and a sibling path costs the folding skill one more read and removes the interaction.
241
-
242
- An append is a whole-file operation, so send it as a heredoc, per Step 0. Then tell the operator that `/docs-fold` folds the file in from a branch. The proposal costs nothing tracked and runs anywhere, while the page it describes is a tracked file, so the fold is a worktree operation and the workspace it came from is not.
152
+ Read `${CLAUDE_SKILL_DIR}/references/promotion.md` first. It carries what may be promoted, the routing test, both spellings of the wiki folder, the refusal when a project has none, what each destination expects a page to carry, and under `## Proposing and handing off` the proposal block, the handoff file this step writes in place of any destination, and how the operator is told to fold it. Propose and wait, and write nothing to a destination here.
243
153
 
244
154
  ## Output
245
155
 
@@ -254,15 +164,8 @@ Open: [<the url the serve verb reported>](<the same url>)
254
164
 
255
165
  Omit the reference line where the lesson produced no durable page. The open line is the one line that is never omitted, since it is the only route the learner has into the page, and it carries what `canon serve` reported rather than a URL composed here. Where the verb refused, that line names the refusal instead of a link.
256
166
 
257
- Write that line as a markdown link carrying the URL as both its text and its target, rather than as a bare URL and never inside backticks. A code span renders as text the reader has to select and copy, which is the one thing the line exists to save them, and the path rule the project states governs a file path rather than a URL.
167
+ Write that line as a markdown link carrying the URL as both its text and its target, rather than as a bare URL and never inside backticks, since a code span is text the reader has to copy.
258
168
 
259
169
  Emit every path from the project root, in the form the project's instruction file sets.
260
170
 
261
- A promotion pass reports its own shape instead, one line per page the operator confirmed and one naming the handoff:
262
-
263
- ```plaintext
264
- ➡️ Promoting: .canon/teach/<nn>-<topic>/reference/<slug>.md → <destination path>
265
- → Confirmed pages wait at .canon/tmp/handoff/teach-promotion/<slug>.md. Run /docs-fold from a branch to fold them in.
266
- ```
267
-
268
- A pass where the operator confirmed nothing writes no handoff file and reports that alone.
171
+ A promotion pass reports the shape `${CLAUDE_SKILL_DIR}/references/promotion.md` gives at the end of `## Proposing and handing off` instead.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Lesson craft reference
3
- description: Typography, restraint, quiz construction, and what makes a lesson worth returning to
3
+ description: Typography, restraint, quiz construction, what makes a lesson worth returning to, and the verbs that build the lesson body
4
4
  ---
5
5
 
6
6
  # Lesson craft reference
@@ -121,3 +121,53 @@ Pick values the material needs rather than values the project happens to hold. I
121
121
  - The lesson states what it assumes. A returning reader can tell in one line whether they are in the right place.
122
122
  - The hard part is named as the hard part. Material that flattens everything to one difficulty gives a returning reader nothing to navigate by.
123
123
  - Nothing depends on the session it was written in. A lesson referring to what was discussed is unreadable a week later.
124
+
125
+ ## Building the lesson body
126
+
127
+ Step 4 of `teach-workspace` reads this section before writing a lesson, after the lesson verb has resolved its path and quiz order. Write the chrome as four empty marker pairs rather than composing it by hand: `<!-- canon:teach:style -->`/`<!-- /canon:teach:style -->` inside `<head>`, and `<!-- canon:teach:header -->`, `<!-- canon:teach:footnav -->`, and `<!-- canon:teach:scripts -->` each with its own close marker, in that order in `<body>`.
128
+
129
+ Build the authored `<h1>`, lede, body, and quiz as a JSON array of blocks rather than composing markup by hand, and render it through the verb rather than through a component import:
130
+
131
+ ```bash
132
+ echo '[
133
+ {"type":"heading","level":1,"text":"<title>"},
134
+ {"type":"paragraph","lede":true,"text":"<the dek>"},
135
+ {"type":"paragraph","text":"<a body paragraph>"},
136
+ {"type":"paragraph","text":"<a claim resting on a source>","cites":[1]},
137
+ {"type":"list","ordered":true,"items":["<step one>","<step two>"]},
138
+ {"type":"raw","html":"<the quiz and teach-back block composed above>"},
139
+ {"type":"refs","items":[{"title":"<source name>","url":"<https URL>"}]}
140
+ ]' | canon teach render --json
141
+ ```
142
+
143
+ `## The block list` above states each type and where `raw` belongs. Take the call's `html` field and write it between the header's close marker and the footnav's open marker, and nothing else anywhere in the file.
144
+
145
+ Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the lesson body by hand as a fallback. That is the state this section exists to end, and a target holds this skill body before it holds the verb, since a plugin skill reaches a target the moment it merges while the CLI reaches one only when a release publishes.
146
+
147
+ Then run:
148
+
149
+ ```bash
150
+ canon teach nav <topic> --json
151
+ ```
152
+
153
+ It fills every marker pair from what the workspace holds on disk: the embedded stylesheet, the header with its breadcrumb and jump menus, the prev/next footer nav, and the behavior scripts, and it rewrites the workspace's contents page and the teach-root listing in the same run. It refuses a lesson missing one of the four marker pairs by name rather than guessing at the boundary, so a marker dropped while writing the lesson is caught here rather than read back later as a lesson nothing links to. Report it rather than proceeding silently when the verb does not resolve, which is an installed CLI predating it, and never compose the chrome by hand as a fallback.
154
+
155
+ Seed the stylesheet through the verb rather than authoring a palette, on the first lesson in a workspace:
156
+
157
+ ```bash
158
+ canon teach stylesheet <topic> --json
159
+ ```
160
+
161
+ 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.
162
+
163
+ 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.
164
+
165
+ Add every term the lesson defines to `GLOSSARY.md` through the verb, which places the entries alphabetically in the shape the standard fixes:
166
+
167
+ ```bash
168
+ canon teach glossary <topic> --json \
169
+ --term "<term>=<definition, written without using the term>" \
170
+ --first-seen <the lesson or reference page this batch comes from>
171
+ ```
172
+
173
+ `--term` repeats and one call writes the file once, which is what keeps a batch of terms from racing on it. A term already defined is refused, since a definition the subject has moved under is a revision of the entry rather than a second one.
@@ -50,3 +50,44 @@ Each destination gates prose the workspace does not, so name what the page still
50
50
  - **The page promoted into a new context entry.** Entries are created deliberately, so a promotion proposing one is proposing a domain rather than moving a page.
51
51
  - **The glossary split across destinations.** Terms drawn from one subject move together or not at all, since an entry defining a term the other half uses strands both.
52
52
  - **The workspace emptied on promotion.** Promotion copies rather than moves. The workspace stays readable to the learner who built it, and the page's later life belongs to its destination.
53
+
54
+ ## Proposing and handing off
55
+
56
+ Step 6 of `teach-workspace` reads this section once the routing above has placed each page. Propose and wait. A promoted page is public prose that needs a line naming who owns its subject, which is a judgment about ownership rather than a move a session makes on its own reading. Present one block per candidate page:
57
+
58
+ ```plaintext
59
+ reference/<slug>.md → <destination path>
60
+ Subject owner: <who owns it, in a few words>
61
+ Still owed: <what the destination expects that the page does not carry yet>
62
+ ```
63
+
64
+ Then stop and let the operator strike, redirect, or confirm each block.
65
+
66
+ Write nothing to a destination here. One skill owns the durable writes, and two skills editing one file at one step is the failure that rule exists against. Record each confirmed block in `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root instead, appending when the file exists, with one H2 per destination naming its path, the source page beneath it, and the page body fenced:
67
+
68
+ ````markdown
69
+ ## <destination path>
70
+
71
+ Source: .canon/teach/<nn>-<topic>/reference/<slug>.md
72
+
73
+ ```markdown
74
+ <the page body as it should land, with the source line the destination expects>
75
+ ```
76
+ ````
77
+
78
+ The body is fenced rather than written bare because a reference page carries headings of its own, and the reader splits this file on its H2 lines. An unfenced body turns every section heading in the page into a destination naming no path. Open the body fence with four backticks so a page carrying a fenced code block of its own still closes where it should, and widen both fences together if it carries a four-backtick fence.
79
+
80
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
81
+
82
+ The handoff is its own file rather than a shared one. The routed-facts file another skill writes is deleted by whichever pass folds it, so a second producer's unread work goes with it, and a sibling path costs the folding skill one more read and removes the interaction.
83
+
84
+ An append is a whole-file operation, so send it as a heredoc, per the skill's Step 0. Then tell the operator that `/context-fold` folds the file in from a branch. The proposal costs nothing tracked and runs anywhere, while the page it describes is a tracked file, so the fold is a worktree operation and the workspace it came from is not.
85
+
86
+ A promotion pass reports this shape in place of the skill's Output block, one line per page the operator confirmed and one naming the handoff:
87
+
88
+ ```plaintext
89
+ ➡️ Promoting: .canon/teach/<nn>-<topic>/reference/<slug>.md → <destination path>
90
+ → Confirmed pages wait at .canon/tmp/handoff/teach-promotion/<slug>.md. Run /context-fold from a branch to fold them in.
91
+ ```
92
+
93
+ A pass where the operator confirmed nothing writes no handoff file and reports that alone.