@erclx/canon 5.32.0 → 5.34.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 (250) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/api-design/REQUIREMENT.md +1 -2
  3. package/claude/skills/api-design/SKILL.md +2 -3
  4. package/claude/skills/api-design/references/adopted.md +1 -1
  5. package/claude/skills/auto-ship/SKILL.md +1 -1
  6. package/claude/skills/auto-ship/references/worktree-name.md +1 -1
  7. package/claude/skills/backlog-triage/REQUIREMENT.md +2 -2
  8. package/claude/skills/backlog-triage/SKILL.md +1 -1
  9. package/claude/skills/canon-cli/REQUIREMENT.md +16 -13
  10. package/claude/skills/canon-cli/SKILL.md +75 -32
  11. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -0
  12. package/claude/skills/canon-feedback/SKILL.md +11 -29
  13. package/claude/skills/canon-feedback/references/template.md +53 -0
  14. package/claude/skills/{canvas → canvas-board}/REQUIREMENT.md +2 -2
  15. package/claude/skills/{canvas → canvas-board}/SKILL.md +6 -3
  16. package/claude/skills/{canvas → canvas-board}/references/canvas.md +26 -3
  17. package/{standards → claude/skills/canvas-board/references}/design.md +11 -2
  18. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  19. package/claude/skills/code-craft/SKILL.md +0 -1
  20. package/claude/skills/context-fold/REQUIREMENT.md +25 -33
  21. package/claude/skills/context-fold/SKILL.md +25 -70
  22. package/claude/skills/context-fold/references/classify.md +3 -3
  23. package/claude/skills/context-fold/references/completion-report.md +4 -4
  24. package/claude/skills/context-fold/references/context-refresh.md +5 -5
  25. package/{standards → claude/skills/context-fold/references}/context.md +16 -5
  26. package/claude/skills/context-fold/references/unusable-baseline.md +3 -5
  27. package/claude/skills/create-rule/SKILL.md +3 -3
  28. package/{standards → claude/skills/create-rule/references}/rule.md +15 -6
  29. package/claude/skills/create-skill/SKILL.md +2 -2
  30. package/{standards → claude/skills/create-skill/references}/skill.md +18 -15
  31. package/claude/skills/design-taste/REQUIREMENT.md +3 -3
  32. package/claude/skills/design-taste/SKILL.md +1 -1
  33. package/claude/skills/docs-sync/REQUIREMENT.md +1 -1
  34. package/claude/skills/docs-sync/SKILL.md +3 -3
  35. package/{standards → claude/skills/docs-sync/references}/docs.md +11 -0
  36. package/{standards → claude/skills/docs-sync/references}/readme.md +9 -2
  37. package/claude/skills/draft-and-pick/SKILL.md +5 -5
  38. package/claude/skills/draft-doc/SKILL.md +6 -6
  39. package/claude/skills/draft-figure/REQUIREMENT.md +2 -2
  40. package/claude/skills/draft-figure/SKILL.md +2 -2
  41. package/claude/skills/draft-figure/references/hand-drawn.md +1 -1
  42. package/claude/skills/draft-figure/references/mermaid-path.md +1 -1
  43. package/claude/skills/draft-identity/REQUIREMENT.md +2 -2
  44. package/claude/skills/draft-identity/SKILL.md +2 -2
  45. package/claude/skills/draft-ready/SKILL.md +1 -1
  46. package/claude/skills/git-branch/SKILL.md +1 -1
  47. package/{standards → claude/skills/git-branch/references}/branch.md +1 -1
  48. package/claude/skills/git-commit/SKILL.md +2 -2
  49. package/claude/skills/git-followup/REQUIREMENT.md +1 -1
  50. package/claude/skills/git-followup/SKILL.md +1 -1
  51. package/claude/skills/git-issue/SKILL.md +1 -1
  52. package/claude/skills/git-issue/references/issue.md +4 -1
  53. package/claude/skills/git-pr/SKILL.md +13 -13
  54. package/claude/skills/git-pr/references/evidence.md +2 -2
  55. package/claude/skills/git-pr/references/final-command.md +38 -14
  56. package/claude/skills/git-pr/references/labels.md +5 -1
  57. package/{standards → claude/skills/git-pr/references}/pr.md +19 -9
  58. package/{standards → claude/skills/git-pr/references}/publish.md +4 -2
  59. package/claude/skills/git-pr/references/task-number.md +1 -1
  60. package/claude/skills/git-ship/SKILL.md +15 -14
  61. package/claude/skills/git-split/SKILL.md +3 -3
  62. package/claude/skills/git-stage/SKILL.md +1 -1
  63. package/claude/skills/plan-feature/REQUIREMENT.md +1 -1
  64. package/claude/skills/review-address/SKILL.md +1 -1
  65. package/claude/skills/review-address/references/post-review-findings.md +1 -1
  66. package/claude/skills/review-craft/references/ui.md +1 -1
  67. package/claude/skills/review-pr/REQUIREMENT.md +1 -1
  68. package/claude/skills/review-pr/SKILL.md +3 -3
  69. package/claude/skills/review-pr/references/body-variants.md +2 -2
  70. package/claude/skills/review-pr/references/key-changes.md +3 -3
  71. package/claude/skills/review-pr/references/post.md +4 -2
  72. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  73. package/claude/skills/review-ui/SKILL.md +8 -6
  74. package/claude/skills/review-ui/references/driving.md +8 -8
  75. package/claude/skills/review-ui/references/post.md +53 -17
  76. package/claude/skills/role-orchestrator/REQUIREMENT.md +3 -0
  77. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  78. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +2 -2
  79. package/claude/skills/role-orchestrator/references/orchestrator-handoff.md +8 -5
  80. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +32 -1
  81. package/claude/skills/role-orchestrator/references/orchestrator-refill.md +2 -2
  82. package/claude/skills/role-orchestrator/references/orchestrator-resume.md +3 -0
  83. package/claude/skills/role-planner/REQUIREMENT.md +2 -1
  84. package/claude/skills/role-planner/SKILL.md +13 -2
  85. package/claude/skills/role-reviewer/SKILL.md +1 -1
  86. package/claude/skills/role-worker/REQUIREMENT.md +1 -0
  87. package/claude/skills/role-worker/SKILL.md +9 -1
  88. package/claude/skills/session-map/SKILL.md +1 -1
  89. package/claude/skills/session-resume/REQUIREMENT.md +2 -2
  90. package/claude/skills/session-worktree/SKILL.md +5 -5
  91. package/claude/skills/target-setup/SKILL.md +10 -4
  92. package/claude/skills/target-setup/references/detect.md +20 -3
  93. package/claude/skills/task-board/REQUIREMENT.md +7 -12
  94. package/claude/skills/task-board/SKILL.md +5 -17
  95. package/claude/skills/task-board/references/tasks.md +2 -2
  96. package/claude/skills/task-complete/REQUIREMENT.md +47 -0
  97. package/claude/skills/task-complete/SKILL.md +86 -0
  98. package/claude/skills/{task-board → task-complete}/references/archive.md +4 -4
  99. package/claude/skills/{context-fold → task-complete}/references/receipt-sweep.md +5 -5
  100. package/claude/skills/task-complete/references/unusable-baseline.md +19 -0
  101. package/claude/skills/teach-workspace/REQUIREMENT.md +6 -2
  102. package/claude/skills/teach-workspace/SKILL.md +8 -4
  103. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  104. package/claude/skills/{context-fold/references/promoted-pages.md → teach-workspace/references/promotion-fold.md} +3 -3
  105. package/claude/skills/teach-workspace/references/promotion.md +3 -3
  106. package/claude/skills/test-craft/REQUIREMENT.md +2 -3
  107. package/claude/skills/test-craft/SKILL.md +15 -3
  108. package/claude/skills/ui-checklist/SKILL.md +1 -1
  109. package/claude/skills/ux-audit/SKILL.md +1 -1
  110. package/claude/skills/ux-measure/SKILL.md +1 -1
  111. package/docs/agents/canvas.md +6 -2
  112. package/docs/agents/commands.md +4 -3
  113. package/docs/agents/context-audit-checks.md +4 -4
  114. package/docs/agents/gov-detect.md +47 -0
  115. package/docs/agents/index.md +7 -4
  116. package/docs/agents/indexes.md +1 -1
  117. package/docs/agents/install-and-sync.md +10 -11
  118. package/docs/agents/key-changes.md +5 -5
  119. package/docs/agents/label-coverage.md +11 -0
  120. package/docs/agents/migrate.md +1 -1
  121. package/docs/agents/pr-evidence-layout.md +70 -0
  122. package/docs/agents/pr-evidence.md +8 -9
  123. package/docs/agents/pr-frames.md +7 -2
  124. package/docs/agents/pr-lift.md +132 -0
  125. package/docs/agents/records-push.md +2 -2
  126. package/docs/agents/records-stale.md +12 -47
  127. package/docs/agents/records-validate.md +3 -7
  128. package/docs/agents/records.md +5 -27
  129. package/docs/agents/rule-citations.md +1 -1
  130. package/docs/agents/sandbox.md +4 -4
  131. package/docs/agents/scripting.md +2 -1
  132. package/docs/agents/skills-audit.md +1 -1
  133. package/docs/agents/tasks-plans.md +2 -2
  134. package/docs/agents/tasks.md +2 -2
  135. package/docs/target/migrations.md +10 -2
  136. package/docs/target/projects.md +4 -4
  137. package/docs/target/sync.md +1 -1
  138. package/docs/workflow/operating-model.md +2 -2
  139. package/docs/workflow/parallel-features.md +7 -7
  140. package/docs/workflow/skill-map-on-request.md +9 -9
  141. package/docs/workflow/skill-map.md +12 -11
  142. package/docs/workflow/visual-design-workflow.md +2 -2
  143. package/governance/detect.toml +201 -0
  144. package/governance/renames.toml +0 -11
  145. package/governance/rules/canon/context.md +13 -0
  146. package/governance/rules/claude/skill.md +17 -0
  147. package/governance/rules/code/000-code.md +1 -13
  148. package/governance/rules/code/010-testing.md +2 -30
  149. package/governance/rules/ui/400-ui.md +1 -1
  150. package/governance/rules/ui/480-canvas.md +13 -0
  151. package/governance/stacks/base.toml +2 -4
  152. package/package.json +4 -4
  153. package/scripts/core/install-check.sh +3 -3
  154. package/scripts/core/regen-hero.ts +0 -6
  155. package/scripts/lib/ui.sh +0 -168
  156. package/src/audits/catalog.ts +0 -1
  157. package/src/canvas/arrange.ts +66 -0
  158. package/src/canvas/capture.ts +20 -6
  159. package/src/canvas/content.ts +39 -0
  160. package/src/canvas/fit.ts +238 -0
  161. package/src/canvas/tokens.ts +28 -10
  162. package/src/capture/frame-values.ts +2 -32
  163. package/src/claude/cases/misc.ts +1 -1
  164. package/src/claude/cases/setup.ts +4 -0
  165. package/src/claude/cases/workflow.ts +6 -1
  166. package/src/claude/seeds.ts +1 -1
  167. package/src/claude/skills-audit.ts +2 -2
  168. package/src/cli/help.ts +3 -1
  169. package/src/cli/ui.ts +2 -3
  170. package/src/commands/canvas.ts +179 -1
  171. package/src/commands/claude.ts +1 -1
  172. package/src/commands/gov.ts +146 -16
  173. package/src/commands/labels.ts +326 -7
  174. package/src/commands/migrate.ts +6 -1
  175. package/src/commands/pr.ts +377 -2
  176. package/src/commands/records.ts +13 -399
  177. package/src/commands/sandbox.ts +3 -6
  178. package/src/commands/tasks.ts +3 -3
  179. package/src/context/audit.ts +5 -5
  180. package/src/context/folders.ts +1 -1
  181. package/src/design/budget.ts +1 -1
  182. package/src/design/components.ts +1 -1
  183. package/src/design/document.ts +1 -1
  184. package/src/design/regen.ts +1 -1
  185. package/src/design/render.ts +1 -1
  186. package/src/feedback/body.ts +26 -5
  187. package/src/gate/measures.ts +59 -45
  188. package/src/gate/sequencer.ts +56 -1
  189. package/src/gate/stages.ts +4 -11
  190. package/src/gov/detect.ts +301 -0
  191. package/src/gov/install.ts +2 -3
  192. package/src/gov/list.ts +23 -2
  193. package/src/gov/stacks.ts +10 -1
  194. package/src/hooks/post-merge.ts +1 -1
  195. package/src/labels/apply.ts +39 -0
  196. package/src/labels/format.ts +3 -3
  197. package/src/labels/phase.ts +3 -3
  198. package/src/labels/sections.ts +43 -0
  199. package/src/migrate/skill-names.ts +20 -1
  200. package/src/pr/evidence.ts +218 -45
  201. package/src/pr/frames.ts +23 -3
  202. package/src/pr/lift.ts +418 -0
  203. package/src/pr/paths.ts +4 -4
  204. package/src/pr/rest.ts +3 -0
  205. package/src/pr/review-scope.ts +76 -0
  206. package/src/records/backup.ts +16 -6
  207. package/src/records/stale-canonical.ts +31 -1
  208. package/src/records/validate.ts +2 -188
  209. package/src/sandbox/census.ts +1 -1
  210. package/src/sandbox/coverage.ts +2 -6
  211. package/src/sandbox/equivalence.ts +19 -51
  212. package/src/sandbox/expect.ts +4 -4
  213. package/src/sandbox/provision.ts +13 -36
  214. package/src/sandbox/scenario.ts +26 -48
  215. package/src/sandbox/tree.ts +2 -6
  216. package/src/standards/read.ts +14 -1
  217. package/src/sync/git.ts +1 -1
  218. package/src/tasks/archive.ts +5 -5
  219. package/src/tasks/branch.ts +3 -3
  220. package/src/tasks/label.ts +1 -1
  221. package/src/tasks/record.ts +2 -2
  222. package/src/tasks/validate.ts +1 -1
  223. package/standards/index.md +0 -13
  224. package/standards/standard.md +1 -7
  225. package/tooling/base/reference.md +1 -1
  226. package/tooling/base/seeds/.github/pull_request_template.md +2 -2
  227. package/tooling/claude/reference.md +1 -1
  228. package/tooling/claude/seeds/.claude/hooks/pr-create-log.sh +9 -1
  229. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +171 -0
  230. package/tooling/claude/seeds/.claude/settings.json +9 -0
  231. package/governance/rules/standards/context.md +0 -14
  232. package/governance/rules/standards/design.md +0 -12
  233. package/governance/rules/standards/readme.md +0 -12
  234. package/governance/rules/standards/rule.md +0 -13
  235. package/governance/rules/standards/skill.md +0 -21
  236. package/governance/rules/standards/standard.md +0 -13
  237. package/scripts/config.sh +0 -11
  238. package/scripts/lib/gov.sh +0 -14
  239. package/scripts/lib/sandbox-fixtures.sh +0 -191
  240. package/scripts/lib/sandbox-git.sh +0 -125
  241. package/scripts/lib/sandbox-path.sh +0 -50
  242. package/scripts/sandbox-hook.sh +0 -99
  243. package/src/gov/standard-rules.ts +0 -179
  244. package/src/records/migrate.ts +0 -78
  245. package/src/records/stale.ts +0 -243
  246. package/src/sandbox/hooks.ts +0 -128
  247. /package/{standards → claude/skills/draft-figure/references}/figures.md +0 -0
  248. /package/{standards → claude/skills/git-branch/references}/slug.md +0 -0
  249. /package/{standards → claude/skills/git-commit/references}/commit.md +0 -0
  250. /package/{standards → claude/skills/git-commit/references}/versioning.md +0 -0
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: task-complete
3
+ description: Why marking shipped outcomes and archiving a shipped task sit in one skill, why the diff decides completion alone, and why the ship chain marks but never archives
4
+ ---
5
+
6
+ # Task complete requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, the board describes the scope a branch opened with. An outcome the diff shipped stays unchecked, so the board reports finished work as open and the next session re-plans it. When marking rode inside the context fold, it kept a ten-step skill running on every ship for a write that had nothing to do with the context entries, and a session reading the fold's name never learned that it also wrote the board.
11
+
12
+ Archiving fails in two ways that both lose work. Moving the file, editing the ordering file, and regenerating the index as three separate acts drifts from the one command the merge hook calls, so the attended and unattended paths stop agreeing. And an all-`[x]` task gets archived while its pull request is still open, because marking happens on the branch as a ship-chain step. The board is gitignored, so nothing restores a task archived early.
13
+
14
+ Review receipts pile up the same way. A branch report is read once by the session addressing it, and nothing but an operator asking ever collected one whose branch was gone, so the folder grew per shipped branch and never shrank.
15
+
16
+ ## Must
17
+
18
+ - Take completion from the diff, since completion is a fact about the repository rather than about the conversation
19
+ - Match an outcome on the behavior it names, never on a filename or a commit subject
20
+ - Leave an outcome unchecked when the diff is ambiguous. An unmarked shipped outcome costs one manual edit and a wrongly marked one hides work that never happened.
21
+ - Close outcomes through `canon tasks outcome` and branch on the record's `ok` and `reason` rather than the exit
22
+ - Resolve the board at the main worktree root, since a linked worktree reading `pwd` finds no board
23
+ - Sweep a branch review report whose branch no longer exists, and leave the current branch's receipt alone, since its branch is still live
24
+ - Confirm the work reached the default branch before archiving
25
+ - Run the archive command and route on the reason it refuses, since each reason has one resolution
26
+
27
+ ## Must not
28
+
29
+ - Infer a new task from the diff. Only outcomes already on the board get marked.
30
+ - Touch a task file the diff does not relate to
31
+ - Archive from the ship chain. The pull request has not merged there, and the `post-merge` hook archives through the same command once it does.
32
+ - Hand-edit the index or the ordering file, or move a task file or a plan directly
33
+ - Archive around a refusal
34
+ - Split the legacy single-file archive into per-task files nobody wrote
35
+ - Widen what the marking step reads into a write when the baseline is unusable
36
+
37
+ ## Guards
38
+
39
+ - No task board at the main root: Mark reports one skip line and returns, since the ship chain runs it on every project, and Archive stops naming the command that sets one up
40
+ - Work not yet on the default branch when archiving: stop, because the board has no history to restore from
41
+
42
+ ## Out of scope
43
+
44
+ - Creating or declining a task, which `task-board` owns
45
+ - The context entries, which `context-fold` owns
46
+ - The unattended archive at merge, which the `post-merge` hook runs through `canon tasks archive`
47
+ - Regenerating the task index, owned by a hook
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: task-complete
3
+ description: Marks the outcomes a branch's diff shipped `[x]` on the task board, sweeps the review receipts whose branch is gone, and archives a shipped task by hand where the `post-merge` hook could not. Use when asked to "mark what shipped", "tick the outcomes", "close out a shipped task", or "archive that task", or as the ship-chain step after `context-fold`. Do NOT use to create or decline a task, which is `task-board`, or to update the context entries, which is `context-fold`.
4
+ metadata:
5
+ family: ship
6
+ ---
7
+
8
+ # Task complete
9
+
10
+ Owns the two operations that settle a task once its work exists: marking the outcomes the diff shipped, and archiving a task whose work reached `main`. `task-board` brings a task into existence and declines one, and `context-fold` writes the context entries. Do not create a task here, and do not move a plan by hand: the archive carries it.
11
+
12
+ ## Guards
13
+
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. The board is gitignored scratch shared across worktrees, so a linked worktree reading its own `pwd` finds no board at all.
15
+ - If `.canon/tasks/` does not exist at that root, Mark has nothing to mark: report `⏭️ No .canon/tasks/ board, so no outcomes to mark.` and return, still running Step 4's receipt sweep, since `git-ship` invokes Mark on every ship and a project with no board is not a failed one. Archive stops instead: `❌ No .canon/tasks/ board. Run canon claude init to set it up.`
16
+ - Route on the request rather than on a flag. A request naming a shipped task to archive, or a task file the `post-merge` hook left in place, takes Archive. Every other request takes Mark, which is what the ship chain invokes.
17
+ - Never hand-edit `.canon/tasks/index.md`. A hook regenerates it.
18
+
19
+ ## Mark
20
+
21
+ ### Step 1: resolve the diff baseline
22
+
23
+ Resolve the base ref once:
24
+
25
+ ```bash
26
+ git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main 2>/dev/null
27
+ ```
28
+
29
+ Take the union of `git diff --name-only <base> HEAD`, `git diff --name-only HEAD`, and `git ls-files --others --exclude-standard`. Read content with `git diff <base> HEAD` and `git diff HEAD`.
30
+
31
+ Prefer `origin/main` over local `main`. On `main` itself the local ref resolves to HEAD, so every committed change drops out of the set and this step goes blind to the work it is meant to read.
32
+
33
+ The baseline is unusable when no merge base resolves against either ref, or when the base came from local `main` and equals HEAD. On an unusable baseline, read `${CLAUDE_SKILL_DIR}/references/unusable-baseline.md` before Step 2 for what to read instead.
34
+
35
+ ### Step 2: match the diff against the board
36
+
37
+ Read `.canon/tasks/index.md` at the main worktree root, pick the task files whose title or description relates to the changed paths, and read them.
38
+
39
+ Path matching only chooses which files to open. Behavior decides each outcome. For each unchecked outcome, decide whether the diff shipped the behavior that outcome names.
40
+
41
+ Completion is a fact about the repository rather than a fact about the conversation, so the diff decides it and the session does not.
42
+
43
+ Keep the match conservative:
44
+
45
+ - Mark only outcomes already written on the board. Never infer a new task from the diff.
46
+ - Match on the behavior an outcome describes, not on filenames or commit subjects. The path match above only narrowed which task files to open.
47
+ - Leave an outcome `[ ]` when the diff is ambiguous. An unmarked shipped outcome costs one manual edit, while a wrongly marked one hides work that never happened.
48
+ - Do not touch task files the diff does not relate to.
49
+
50
+ ### Step 3: close the matched outcomes
51
+
52
+ Mark each completed outcome `[x]` in the task's own file through `canon tasks outcome <stem> --close <n> --json`, repeating `--close` for each. Positions count every outcome checkbox in file order from 1, which the read above already gives. Do not move or archive the file, even when every outcome now reads `[x]`, since the pull request has not merged and the `post-merge` hook archives the task once it does.
53
+
54
+ The verb resolves the board at the main worktree root in-process, which is the route `session-worktree` states for an edit inside an existing main-root file.
55
+
56
+ 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.
57
+
58
+ ### Step 4: sweep consumed receipts
59
+
60
+ Read `${CLAUDE_SKILL_DIR}/references/receipt-sweep.md` for which review receipts this run sweeps, which it keeps, and the output lines. Skip this step silently when nothing qualifies.
61
+
62
+ ## Archive
63
+
64
+ 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.
65
+
66
+ ## Output
67
+
68
+ Emit the full relative path from the project root for every file written or moved. A bare filename names a file the reader cannot open.
69
+
70
+ Mark, one line per outcome closed, then the sweep lines Step 4 names:
71
+
72
+ ```plaintext
73
+ ✅ Marked: .canon/tasks/vXX.Y-<slug>.md, outcome <n>
74
+ ```
75
+
76
+ When the diff closed nothing, output `✅ No outcomes shipped on this diff.` and nothing for Step 3. A refusal reports the record's `reason` on one line, naming the task.
77
+
78
+ Archive, reporting the paths the command returned:
79
+
80
+ ```plaintext
81
+ 📦 Archived: .canon/tasks/archive/vXX.Y-<slug>.md
82
+
83
+ <ordering and index disposition in one line>
84
+ ```
85
+
86
+ A refusal reports the reason and the resolution `references/archive.md` routes it to under its Step 3, on one line each.
@@ -1,11 +1,11 @@
1
1
  ---
2
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
3
+ description: How task-complete confirms a task's work reached main, runs canon tasks archive, routes each refusal, and clears prose naming the archived task
4
4
  ---
5
5
 
6
6
  # Archive a shipped task
7
7
 
8
- The Archive mode of `task-board`. The session reads this file when the request names a task file already shipped.
8
+ The Archive mode of `task-complete`. The session reads this file when the request names a task file already shipped.
9
9
 
10
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
11
 
@@ -13,7 +13,7 @@ Do not move the file, edit `priority.md`, or regenerate the index by hand. `cano
13
13
 
14
14
  ## Step 1: confirm the work reached main
15
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:
16
+ `task-complete` marks outcomes on the branch as step 2 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
17
 
18
18
  ```bash
19
19
  git fetch origin main --quiet && git log origin/main --oneline -20
@@ -39,7 +39,7 @@ On success the record carries `from`, `to`, `priorityRowRemoved`, and `indexRege
39
39
 
40
40
  Each reason has one resolution and none of them is to archive around it:
41
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.
42
+ - `open-outcomes`: the named outcomes are unmarked or genuinely open. Run `task-complete` 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
43
  - `ambiguous`: two tasks name one pull request, which is the misfile `${CLAUDE_SKILL_DIR}/references/tasks.md` rules out. Resolve the citation by hand, since no sweep repairs it.
44
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
45
  - `pending-branch`: a branch named in `detail` ticked an outcome before its own pull request number was recorded. Leave the task live, since that branch's merge archives it once `git-pr` records the number. When the operator decides a named branch was abandoned, archive by stem, which is the one case for it.
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  title: Sweep consumed receipts
3
- description: How context-fold Step 7 sweeps branch review reports whose branch is gone, and what it keeps and reports
3
+ description: How task-complete's marking step sweeps branch review reports whose branch is gone, and what it keeps and reports
4
4
  ---
5
5
 
6
6
  # Sweep consumed receipts
7
7
 
8
- Step 7 of `context-fold`, reached in order on every run.
8
+ Step 4 of `task-complete`'s Mark mode, reached in order on every marking run.
9
9
 
10
- Sweep the review receipts this session consumed. Resolve all paths at the main worktree root, not the current worktree, the way `session-worktree` does.
10
+ Sweep the review receipts a shipped branch left behind. Resolve all paths at the main worktree root, not the current worktree, the way `session-worktree` does.
11
11
 
12
12
  Every delete below is a plain `rm`, one per call, routed the way `session-worktree` states.
13
13
 
14
- 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.
14
+ 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 would read a closure Step 3 wrote moments earlier and move a plan the branch is still building from.
15
15
 
16
16
  ## Reviews
17
17
 
@@ -19,7 +19,7 @@ Leave the current branch's review receipt where it is. No skill writes one now,
19
19
 
20
20
  The body that writes a receipt owns its lifetime. This skill sweeps on behalf of whatever called it and has no way to read whether a file is still in use, where the chain that wrote this one cites it in its own output and knows. What reaps it is the branch sweep below, one branch later, once the branch it names is gone.
21
21
 
22
- Sweep the branch reports this session never opened. List `.canon/review/branch-*.md`, run the slug transform in `${CLAUDE_SKILL_DIR}/../../standards/slug.md` over every name `git branch --format='%(refname:short)'` prints, and delete a report whose slug matches none of them. Take the names from that format rather than from `git branch --list`, which marks the current branch with `* ` and a branch checked out in another worktree with `+ `, so a transform reading the marked lines as written turns a live branch into a slug nothing matches and sweeps a report a sibling worktree is still working from. A branch report is read once, by the session addressing it, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so a report outliving its branch is holding nothing. Skipping this leaves them accumulating for the life of the checkout, since a slug is unique per feature and no later branch ever looks for one.
22
+ Sweep the branch reports this session never opened. List `.canon/review/branch-*.md`, run the slug transform in `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md` over every name `git branch --format='%(refname:short)'` prints, and delete a report whose slug matches none of them. Take the names from that format rather than from `git branch --list`, which marks the current branch with `* ` and a branch checked out in another worktree with `+ `, so a transform reading the marked lines as written turns a live branch into a slug nothing matches and sweeps a report a sibling worktree is still working from. A branch report is read once, by the session addressing it, and the durable record of what a review found is the comment `review-pr` posts on the pull request, so a report outliving its branch is holding nothing. Skipping this leaves them accumulating for the life of the checkout, since a slug is unique per feature and no later branch ever looks for one.
23
23
 
24
24
  What that removes is a local-only review on a branch deleted before it opened a pull request. The sweep runs anyway rather than keeping every report against the one case, since nothing else ever clears them.
25
25
 
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: An unusable diff baseline
3
+ description: When task-complete's diff baseline is unusable, what the marking step reads to recover the committed half, and why that widened read is safe here
4
+ ---
5
+
6
+ # An unusable diff baseline
7
+
8
+ Step 1 of `task-complete`'s Mark mode. The session reads this file when no merge base resolves or the base came from local `main` and equals HEAD.
9
+
10
+ The baseline is unusable in two cases:
11
+
12
+ - No merge base resolves against either ref.
13
+ - The base came from local `main` and equals HEAD. Nothing is pushed to compare against, so a narrow read reports no changes rather than admitting it cannot see them.
14
+
15
+ An unusable baseline costs only the committed half. `git diff <base> HEAD` is empty by definition once the base equals HEAD, while `git diff HEAD` and `git ls-files --others --exclude-standard` still report uncommitted and untracked work at correct scope.
16
+
17
+ Recover the committed half. Read `git log -p -1`, widening to `git log -p -<n>` when the session spans several commits, and read the candidate task files against the working tree. That yields names and content both, which is what lets Step 2 decide on behavior rather than on filenames. A fresh `git init` on `main` with no remote is the ordinary shape of a scaffolded project, so this path carries the evidence rather than covering an edge case.
18
+
19
+ On a fresh `git init` project the last commit is the scaffold commit, so `git log -p -1` is the whole tree by another route. The marking step tolerates that because it only reads the diff, and it matches conservatively against outcomes already on the board. Widening what a step reads is safe. Never carry the widened set into a step that writes per file.
@@ -21,6 +21,8 @@ The lesson then reaches nobody. It is a page carrying a stylesheet and a script,
21
21
 
22
22
  The durable half then has nowhere to go. A reference page and a glossary carry no learner and are ordinary reference prose, so they belong wherever the project already keeps prose on that subject, and a workspace holding them is a gitignored folder one person reads. A session moving them by hand picks a destination from the reader's activity rather than from who owns the subject, drops a page into a corpus without the source line that corpus requires, and has nothing stopping it from carrying a lesson across.
23
23
 
24
+ A confirmed page then waits in gitignored scratch until something lands it, and the session that produced it is gone by the time a branch exists to carry the write. Left unfolded it reads as promoted while the destination holds nothing.
25
+
24
26
  Two failures land specifically on where the folder sits. A workspace resolved against the current directory forks into a copy per linked worktree, so the learning records split and no session sees the whole history. A body naming only the destination path reports success and loses the write, because the file-editing tools refuse a main-root path from a linked worktree and offer a worktree copy instead.
25
27
 
26
28
  ## Must
@@ -38,12 +40,14 @@ Two failures land specifically on where the folder sits. A workspace resolved ag
38
40
  - Hand the learner a link that opens the workspace in a real browser on every run, reading it back from the verb that served it rather than composing one, since the port a preview lands on is not the port it asked for
39
41
  - Propose a destination for each durable page by who owns its subject, and wait for the operator rather than routing on the session's own reading
40
42
  - Hand each confirmed page off through a named file of its own, so one skill owns the durable writes and no other producer's unread work is discarded with it
43
+ - Land each block of that handoff from a branch, at the destination its heading names, taking the destination as already decided, then delete the file so a later run does not fold it twice
41
44
  - Name what the destination expects that the page does not carry yet, since the workspace runs none of the gates the destination does
42
45
 
43
46
  ## Must not
44
47
 
45
48
  - Teach from recall without saying which claims rest on it
46
- - Write to a destination surface directly, since the skill owning that file makes the edit and two skills writing one file at one step is what the handoff exists against
49
+ - Write to a destination surface outside the fold mode, since the proposal and the landing are two steps a branch boundary separates and two writers at one step is what the handoff exists against
50
+ - 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.
47
51
  - Open a second workspace on a subject an existing one already covers
48
52
  - Renumber a workspace folder, a lesson, or a learning record, since the number is cited by files already written
49
53
  - Promote a lesson, at any age and on any request, since it is generated markup carrying a quiz and a learner into a corpus every other page passed a gate to enter
@@ -55,7 +59,7 @@ Two failures land specifically on where the folder sits. A workspace resolved ag
55
59
 
56
60
  ## Out of scope
57
61
 
58
- - Landing a promoted page at its destination, which belongs to the skill that owns durable writes there, and creating a wiki folder a project never chose
62
+ - Creating a wiki folder a project never chose
59
63
  - What the verbs do internally, which is the CLI domain's own contract rather than this skill's, and the records backup that carries the folder off one disk
60
64
  - What a lesson renders as and how a quiz behaves once rendered, which the lesson-craft reference shapes and a rendering layer executes
61
65
  - Deciding when to fire. The skill is user-invoked through `disable-model-invocation`, so opening a workspace is the learner's call rather than a description match.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: teach-workspace
3
- description: Opens and runs a learning workspace on one subject, holding a mission, resources, numbered lessons, reference pages, a glossary, and learning records that survive across sessions, and proposes where a durable page from one belongs once it outgrows the workspace. Use when asked to "teach me X", "open a learning workspace", "I want to learn X", "quiz me on this", "continue the lesson", "resume my workspace on X", or "promote this reference page". Do NOT use to write project documentation, which belongs to the surface owning that document, and do NOT use to answer one question, which is an ordinary reply.
3
+ description: Opens and runs a learning workspace on one subject, holding a mission, resources, numbered lessons, reference pages, a glossary, and learning records that survive across sessions, proposes where a durable page from one belongs once it outgrows the workspace, and lands the confirmed pages from a branch. Use when asked to "teach me X", "open a learning workspace", "I want to learn X", "quiz me on this", "continue the lesson", "resume my workspace on X", "promote this reference page", or "fold the promoted pages in". Do NOT use to write project documentation, which belongs to the surface owning that document, and do NOT use to answer one question, which is an ordinary reply.
4
4
  disable-model-invocation: true
5
- argument-hint: <subject to learn, or the topic of a workspace to resume or promote>
5
+ argument-hint: <subject to learn, the topic of a workspace to resume or promote, or fold>
6
6
  metadata:
7
7
  family: answer
8
8
  ---
@@ -17,7 +17,7 @@ The shape of the workspace is fixed by `${CLAUDE_SKILL_DIR}/references/teach.md`
17
17
 
18
18
  - If the invocation names no subject and no existing workspace matches, stop: `❌ No subject. Invoke with the subject to learn, or the topic of a workspace to resume.`
19
19
  - Never trust recall for what the subject says. Research first, cite what was read, and say what was not.
20
- - Write nothing outside the workspace folder, apart from the one handoff file Step 6 names. A durable page stays in `reference/` and is copied out by the skill that owns the destination, never by this one.
20
+ - Write nothing outside the workspace folder, apart from the one handoff file Step 6 names and the destinations the fold mode lands from that file. A durable page stays in `reference/` until the operator confirms a destination, and only the fold mode copies it out, from a branch.
21
21
  - Do not open a second workspace on a subject one already covers. Resume that one.
22
22
  - Never promote a lesson. It is generated markup carrying a quiz and a learner, and no request makes it promotable.
23
23
  - Never write a favicon link by hand. `canon teach nav` puts the toolkit's icon on every page it generates and inside a lesson's `style` region, so a hand-written one in a lesson's `<head>` gives the page two.
@@ -41,7 +41,7 @@ Every path under the main root is a main-root write, routed the way `session-wor
41
41
 
42
42
  ## Step 1: open or resume
43
43
 
44
- A topic the listing already carries is a resume, and anything else is a new workspace. An invocation asking to promote is neither: read the named workspace through the listing and go to Step 6, which teaches nothing and writes no lesson.
44
+ A topic the listing already carries is a resume, and anything else is a new workspace. An invocation asking to promote is neither: read the named workspace through the listing and go to Step 6, which teaches nothing and writes no lesson. An invocation asking to fold confirmed promotions in is neither as well: go to `## Fold mode`.
45
45
 
46
46
  On a resume, run `canon teach list <topic> --json` for the files behind each count, then read the highest-numbered learning record and `GLOSSARY.md`. Those carry where the learner stopped and what they got wrong. The listing record carries `success`, the mission's success lines, which are the exit criteria this workspace is finished against. Report them with what is already met before teaching anything.
47
47
 
@@ -141,6 +141,10 @@ Run this when the invocation asks for it, or offer it in one line when a mission
141
141
 
142
142
  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.
143
143
 
144
+ ## Fold mode
145
+
146
+ Run this only when the invocation asks to fold, and only from a branch in a linked worktree, since every destination is a tracked file that commits with the branch while the handoff sits in gitignored scratch at the main root. Read `${CLAUDE_SKILL_DIR}/references/promotion-fold.md` for how each block lands, the overwrite refusal, the handoff delete, and the output lines, which replace the Output block below.
147
+
144
148
  ## Output
145
149
 
146
150
  ```plaintext
@@ -50,7 +50,7 @@ Every element competes with the material for attention.
50
50
  Decide the figure once per lesson, before writing the body.
51
51
 
52
52
  1. Name the one relationship, boundary, path, or before-and-after the lesson is about. A lesson about none ends here, with `Figure: none, <why it names no structure>` on the reply.
53
- 2. Hold it against the bar in `${CLAUDE_SKILL_DIR}/../../standards/figures.md`, which also carries the drawing rules.
53
+ 2. Hold it against the bar in `${CLAUDE_SKILL_DIR}/../draft-figure/references/figures.md`, which also carries the drawing rules.
54
54
  3. Draw it hand-written through `canon:draft-figure` into a `raw` block before the quiz, never inside it or after its feedback, which breaks the stepper's gating. Report it, never draw it yourself, if the skill does not resolve.
55
55
  4. Or write one line on why prose carries it better, such as a structure an earlier lesson gave.
56
56
 
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  title: Fold promoted pages
3
- description: How context-fold Step 6 lands each page a teach promotion handoff carries, deletes the handoff, and reports what it landed or left unfolded
3
+ description: How teach-workspace's fold mode lands each page a teach promotion handoff carries from a branch, deletes the handoff, and reports what it landed or left unfolded
4
4
  ---
5
5
 
6
6
  # Fold promoted pages
7
7
 
8
- Step 6 of `context-fold`. The session reads this file when a teach promotion handoff exists.
8
+ The fold mode of `teach-workspace`, run from a branch in a worktree. The session reads this file when the invocation asks to fold confirmed promotions in.
9
9
 
10
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root. `teach-workspace` writes it, one H2 per destination naming the path, with a source line under the heading and the page body in a fenced block below that. Read the body out of the fence rather than off the heading level, since a reference page carries headings of its own and only the fence separates them from the next destination. Skip this step silently when the file is absent, which is every run where nothing was promoted.
10
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`, falling back to `latest` on an empty result, and read `.canon/tmp/handoff/teach-promotion/<slug>.md` at the main worktree root. `teach-workspace` writes it, one H2 per destination naming the path, with a source line under the heading and the page body in a fenced block below that. Read the body out of the fence rather than off the heading level, since a reference page carries headings of its own and only the fence separates them from the next destination. Stop when the file is absent, reporting `❌ No promotion handoff at .canon/tmp/handoff/teach-promotion/<slug>.md. Run the promotion step from the workspace first.`
11
11
 
12
12
  Each block is a page an operator already confirmed a destination for, so this step lands it rather than judging it again. Write to the destination the heading names, at `pwd` rather than at the main root, since every destination here is a tracked file that commits with the branch:
13
13
 
@@ -77,17 +77,17 @@ Source: .canon/teach/<nn>-<topic>/reference/<slug>.md
77
77
 
78
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
79
 
80
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
80
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`. Fall back to `latest` on an empty result.
81
81
 
82
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
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.
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 `/teach-workspace 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
85
 
86
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
87
 
88
88
  ```plaintext
89
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.
90
+ → Confirmed pages wait at .canon/tmp/handoff/teach-promotion/<slug>.md. Run /teach-workspace fold from a branch to fold them in.
91
91
  ```
92
92
 
93
93
  A pass where the operator confirmed nothing writes no handoff file and reports that alone.
@@ -15,7 +15,7 @@ The failure is measured on a real target. A single-page portfolio site carried 2
15
15
 
16
16
  A stated rule is also one a session can talk itself out of. Each test in that suite sat where it did for a reason that sounded fine at the time, such as the end to end spec already being open or a pause quieting a run that flaked under load, and nothing in front of the session answered the reason.
17
17
 
18
- Moving the guidance into a path-scoped rule does not reach the session that needs it. A path-scoped rule loads when a session reads a matching file and not when it creates one, measured in three headless runs, so a rule scoped to test files is silent on the first test of every feature.
18
+ Moving the guidance into a path-scoped rule does not reach the session that needs it. A path-scoped rule loads when a session reads or writes a matching file, and it is absent from the write that creates the first one, measured in three headless runs, so a rule scoped to test files is silent on the first test of every feature.
19
19
 
20
20
  ## Must
21
21
 
@@ -23,6 +23,7 @@ Moving the guidance into a path-scoped rule does not reach the session that need
23
23
  - Open every test with the question of which production change makes it fail and whether that change is a bug or a decision, since a test with no answer proves nothing
24
24
  - State the end to end budget in words: journeys and what only a real browser renders, never a state a component test reaches
25
25
  - Carry a final filter the session runs over its own tests, each item a shape it can check: mirror assertion, change detector, assertion on a mock, framework behavior, fixed pause, and the mutation check
26
+ - Own the structure floors every test carries, being arrange, act, assert, one behavior per test, no branching or loops in a body, no testing of private functions or internal state, no coverage targets, nesting at most two levels deep, independent tests that clean up, factories for test data, every async call awaited, and no snapshot verification. The testing rule only points here, so a later trim keeps them.
26
27
  - Hold in the body what every test decision pays for, and defer each layer's specifics to a reference loaded only when a test at that layer is being written
27
28
  - Promise a middle layer for behavior a simulated DOM cannot render, naming the real-browser component runners, so a static site is not left choosing between a simulated DOM that computes no layout and a whole-site run
28
29
  - Repeat in the end to end reference the two floors a session writing its first spec never loads through the path-scoped rule, being the `scrollend` settle and motion reduced by default
@@ -34,7 +35,6 @@ Moving the guidance into a path-scoped rule does not reach the session that need
34
35
 
35
36
  - Name a framework or runner in the body. Runner specifics live in the references and in the language testing rules, so the body holds across stacks.
36
37
  - Carry a numeric mix across layers. A codebase-wide ratio is a target a session writing one test cannot act on.
37
- - Restate the structure floors the testing rule already carries, such as arrange, act, assert, one behavior per test, isolation, and no snapshots
38
38
  - Restate the settle, guard, and run-scope rules the end to end rules carry, beyond the two floors named above
39
39
  - Run the red, green, refactor procedure or decide its order, which `test-first` owns
40
40
  - Import an external skill's tone, such as a law stated as absolute, or an excuse row rebutting an objection no session has raised
@@ -49,7 +49,6 @@ Moving the guidance into a path-scoped rule does not reach the session that need
49
49
 
50
50
  - The red, green, refactor loop and the order it runs in: `test-first`, which loads this skill at its first step
51
51
  - Reproducing a failure with no known cause: `systematic-debugging`
52
- - Structure floors for every test: the core testing rule
53
52
  - Runner choice, file suffix, placement, and query conventions: the TypeScript and Python testing rules
54
53
  - Settle, guard, and run-scope rules inside an end to end file: the end to end reliability and test scope rules
55
54
  - Writing a UI change's visual checklist, and naming the behaviors it ships untested for this skill to place: `ui-checklist`
@@ -45,10 +45,23 @@ Size a test by what it touches rather than by what it covers. Pick the first row
45
45
 
46
46
  ## Keep test code readable over clever
47
47
 
48
- - Prefer a descriptive, repeated arrange over a shared helper a reader has to open. A test is read when it fails, by someone who did not write it.
49
- - Assert through the public surface a caller uses. A test reaching into internals breaks on every refactor and catches nothing a caller sees.
48
+ - Prefer a descriptive, repeated arrange over a shared helper a reader has to open. A test is read when it fails, by someone who did not write it. Build test data through a factory or builder rather than an inline object literal repeated across tests.
49
+ - Assert through the public surface a caller uses, never on a private function or internal state directly. A test reaching into internals breaks on every refactor and catches nothing a caller sees.
50
50
  - Prefer a real collaborator, then a fake, then a stub, and a mock last. A mock asserts how the code is built rather than what it does.
51
51
 
52
+ ## Hold the structure floors
53
+
54
+ Every test carries these, at any layer and in any runner:
55
+
56
+ - Structure each test as arrange, act, assert.
57
+ - Verify one behavior per test.
58
+ - Keep conditional logic and loops out of a test body. A table of cases is one test per row, never a branch inside one.
59
+ - Set no coverage percentage as a target. Cover the behaviors and their edges.
60
+ - Group related tests with the framework's nesting mechanism, and keep it to two levels deep.
61
+ - Keep tests independent with no shared side effects. Clean up and restore state after each one.
62
+ - Await every async operation. Never leave a promise fire-and-forget in a test.
63
+ - Verify with assertions, never with a snapshot.
64
+
52
65
  ## Read the reference for the layer
53
66
 
54
67
  Read the one reference matching the layer the table picked, before writing the first test at it. Skip the other two.
@@ -92,6 +105,5 @@ Run the final filter over every test written this session, checking each line. A
92
105
 
93
106
  - The red, green, refactor procedure and its order: `test-first`, which loads this skill at its first step
94
107
  - Reproducing a failure with no known cause: `systematic-debugging`
95
- - Test structure floors such as arrange, act, assert, one behavior per test, isolation, and no snapshots: `010-testing`
96
108
  - Runner, file suffix, placement, and query conventions per language: `300-testing-ts`, `330-testing-py`, `335-testing-go`, and `336-testing-php`
97
109
  - Settle, guard, and run-scope rules inside an end to end file: `305-e2e-reliability` and `306-test-scope`
@@ -61,7 +61,7 @@ Omit the heading when every automatable change is covered.
61
61
 
62
62
  ## Persist the checklist
63
63
 
64
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
64
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`. Fall back to `latest` on an empty result.
65
65
 
66
66
  When a checklist is produced, write it directly to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite. This is a handoff file rather than a deliverable: `git-pr` renders it into the evidence comment once a pull request opens, then removes it, and nothing here talks to `gh` directly.
67
67
 
@@ -61,7 +61,7 @@ If nothing is wrong, use: `✅ No observations.`
61
61
 
62
62
  ### Persist
63
63
 
64
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
64
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`. Fall back to `latest` on an empty result.
65
65
 
66
66
  Write the full report directly to `.canon/review/ux-audit-<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite.
67
67
 
@@ -108,7 +108,7 @@ Report the reading and stop there. A remedy for a poor verdict is a change with
108
108
 
109
109
  ### Persist
110
110
 
111
- Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
111
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../git-branch/references/slug.md`. Fall back to `latest` on an empty result.
112
112
 
113
113
  Write the full reading directly to `.canon/review/ux-measure-<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite.
114
114
 
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: Canvas
3
- description: The canvas server, its content format, and the verbs that list, arrange, select, edit, mark, and capture frames
3
+ description: The canvas server, its content format, and the verbs that list, arrange, fit, select, edit, mark, and capture frames
4
4
  ---
5
5
 
6
6
  # Canvas
7
7
 
8
- `canon canvas` serves a local canvas of pages and HTML frames and gives an agent the verbs the operator's mouse has. A page is a folder under `.canon/canvas/`, a frame is one HTML file in it, and `layout.json` beside them holds each frame's box. `claude/skills/canvas/references/canvas.md` fixes that content format, and the `canvas` skill carries the procedure a session follows to drive the canvas.
8
+ `canon canvas` serves a local canvas of pages and HTML frames and gives an agent the verbs the operator's mouse has. A page is a folder under `.canon/canvas/`, a frame is one HTML file in it, and `layout.json` beside them holds each frame's box. `claude/skills/canvas-board/references/canvas.md` fixes that content format, and the `canvas-board` skill carries the procedure a session follows to drive the canvas.
9
9
 
10
10
  ## Serve
11
11
 
@@ -29,6 +29,10 @@ The write routes the shell posts to also refuse a request carrying an `Origin` o
29
29
 
30
30
  `frame resize <page> <name> --width <px> --height <px>` rewrites one frame's whole box in one write, and `--x` and `--y` move it at the same time. An omitted flag keeps the frame's current value. It refuses a width or height that is not a positive number as `invalid-size`, a position that is not a number as `invalid-position`, and a missing frame, a missing page, or a layout that does not parse as `frame move` does.
31
31
 
32
+ `canvas arrange <page>` re-lays a page's frames. It keeps the rows they already sit in, where boxes whose vertical spans overlap share a row, orders each row left to right by `x`, places the boxes on the 80px frame gap from the page's leftmost edge, and stacks the rows on their tallest box plus the gap from the page's top edge. It also drops every layout entry whose frame file is gone. `--check` writes nothing. `--json` emits `{ ok, page, written, moved, dropped }`, where `moved` lists each frame with its `from` and `to` position and `dropped` lists the entries with no file. Sizes never change. Under `--check` it exits 2 when a frame would move or an entry would drop, and it refuses a missing page as `no-page` and a layout that does not parse as `malformed-layout`.
33
+
34
+ `canvas fit <page>/<frame>` and `canvas fit <page>` set each frame's height to its content. The content is measured in the capture browser at the frame's box width, through the served address, after fonts and the load event settle, and the height is that measure rounded up with no margin. The write goes through the same writer as `frame resize`. `--check` writes nothing. `--json` emits `{ ok, written, frames }`, one row per frame carrying `height`, `fitted`, `wide`, `font`, `viewport`, `family`, and `owed`. `wide` is true when the content is wider than the box, which fit reports and never widens, since the width is the frame's declared render width. `font` is true when the first family of the body text is not one the served sheet declares a face for, being the toolkit faces `Geist Variable`, `Noto Sans Mono`, and the hand-drawn pair, plus any face the project declares, because a height fitted against a machine family may not hold in another browser. `viewport` is true when the frame's height depends on the viewport, as a height in `vh` does. The lowest element edge is read with the viewport at the box height and at twice it, and an edge that moved means the content measure is not the height a viewer draws, so its height is kept and nothing is written for it. Under `--check` it exits 2 when any row is owed, and it refuses as `no-page`, `no-frame`, `no-server`, or `capture-failed` the way `canvas capture` does. Rows below a frame that grew do not move, so run `canvas arrange` after a fit.
35
+
32
36
  The shell writes through the same writer when the operator drags a frame or one of its corner handles. Both take a lock file beside the layout, read it, merge their change, and replace it, so a drag released while a verb writes loses neither position. A writer that cannot get the lock within two seconds refuses with reason `busy`.
33
37
 
34
38
  `canvas selection --json` emits `{ ok, selection }` with the page, frame, file, box, and path of the frame the operator selected, or `selection: null` when nothing is selected or the selected frame has since been removed. Read it when the operator says "this one".
@@ -103,12 +103,12 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
103
103
  - `sessions`, in `sessions.md`: `list`, `export`, `import`
104
104
  - `worktrees`, in `worktrees.md`: `list`, `reclaim`
105
105
  - `hooks`, in `hooks.md`: `post-merge`
106
- - `pr`, in `pr-reads.md`: `key-changes`, `head`, `checks`, `review-state`, `evidence`, `preview`, `local`, `frames`
106
+ - `pr`, in `pr-reads.md`: `key-changes`, `head`, `checks`, `review-state`, `evidence`, `preview`, `local`, `frames`, `lift` (in `pr-lift.md`)
107
107
  - `audits`, in `audits.md`: `run`, `list`
108
108
  - `gate`, in `gate.md`: `run`
109
109
  - `secrets`, in `state-scoped-risk.md`: `scan`
110
110
  - `deps`, in `state-scoped-risk.md`: `audit`
111
- - `labels`, in `label-coverage.md`: `audit`, `scan`
111
+ - `labels`, in `label-coverage.md`: `audit`, `apply`, `scan`
112
112
  - `comments`, in `comments.md`: `scan`
113
113
  - `context`, in `context-audit.md`: `audit`, `classify diff`, `classify sweep`, `classifier show`, `classifier set`
114
114
  - `markdown`, in `markdown-audit.md`: `audit`
@@ -129,7 +129,8 @@ Subcommands no other page describes:
129
129
  - `slides render [deck]` takes a deck name or a folder, defaulting to the only deck, and lays out each `.html` file in filename order in Chromium at 1280 by 720 and rebuilds it as editable shapes. Each element drawn as a picture prints one `✗` line on stderr naming the slide, the selector, and the properties that forced it. A link left out, being a `#slide-N` past the deck's end or any target that is neither that nor an absolute URL, prints one too, and the exit stays 0.
130
130
  - A deck folder's `deck.json` and the faces it embeds, its per-slide `<body>` attributes, transitions and entrances, speaker notes, and charts are described in `slides.md`.
131
131
  - `canvas`, with its verbs, content format, and write routes, is described in `canvas.md`.
132
- - `labels scan` fails a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, a title word no dictionary holds, or a title breaking `standards/pr.md`'s format, casing, or length rule (`--event`, `--body-file`, `--json`).
132
+ - `labels apply <number>` adds the labels this branch earns to its open pull request, refusing a pull request whose head is not the current branch or that is not open, and never creating a label the remote lacks or removing one a person added (`--check` writes nothing and exits 2 when labels are owed, `--base`, `--json`).
133
+ - `labels scan` fails a pull request or a posted review whose title, body, or review comment carries a phase label, a label a code span quotes, a gitignored record path, a session link, a banned character, a body heading outside the closed section list or a missing `## Summary` or `## Key changes`, a title word no dictionary holds, or a title breaking `claude/skills/git-pr/references/pr.md`'s format, casing, or length rule (`--event`, `--body-file`, `--json`).
133
134
 
134
135
  ## Version skew
135
136
 
@@ -9,7 +9,7 @@ What each finding from `canon context audit` means. The command surface, its fla
9
9
 
10
10
  ## Required sections
11
11
 
12
- The required-section check reports what does not declare the sections its folder's standard requires. A context entry owes `## Overview` and `## Layout`, the two sections `standards/context.md` marks required. The lists are held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
12
+ The required-section check reports what does not declare the sections its folder's standard requires. A context entry owes `## Overview` and `## Layout`, the two sections `claude/skills/context-fold/references/context.md` marks required. The lists are held in code beside the numeric checkpoints rather than parsed out of the standard, so it fails on a defect rather than on a rewrite of the wording around it. A heading at any level counts, because a domain that split into a folder carries its overview in a sibling named `overview.md` where the section is the `#` title and an `##` beneath it would repeat the filename. Matching exactly is what keeps `## Layout catalog` from satisfying `Layout`.
13
13
 
14
14
  Which unit answers depends on the folder. Entries of the folder named under `.claude/` are one domain each, so each answers for itself and a finding names the entry. Entries of a folder a domain split into describe that one domain between them, so any sibling answers and a finding names the folder. Holding a split folder to the rule per file would report every child beside its `overview.md`, and rolling the named folder up would let one conforming entry stand in for every other domain sitting next to it.
15
15
 
@@ -19,7 +19,7 @@ It reports rather than gates by default, the closer call because a missing secti
19
19
 
20
20
  ## Reference form
21
21
 
22
- The reference-form check reports an entry naming a sibling entry by bare filename where `standards/context.md` asks for the path it sits at. A bare name resolves against whichever folder its reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A path is checkable, which is what makes the form rule worth measuring at all: once a reference spells its path, the citation gate resolves it and a split that moves the file fails the push.
22
+ The reference-form check reports an entry naming a sibling entry by bare filename where `claude/skills/context-fold/references/context.md` asks for the path it sits at. A bare name resolves against whichever folder its reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A path is checkable, which is what makes the form rule worth measuring at all: once a reference spells its path, the citation gate resolves it and a split that moves the file fails the push.
23
23
 
24
24
  A finding is a backticked filename carrying no folder, matched against the entries beside the one that wrote it. The backticks are required, since a filename in running prose is not a reference a reader follows.
25
25
 
@@ -33,7 +33,7 @@ It reports and never gates, which is the split the citation check already draws.
33
33
 
34
34
  ## Length
35
35
 
36
- Length quotes its checkpoint from `standards/context.md`: roughly 150 rendered lines for an entry, applied to each of the 60 entries the audit measures, 51 of them under `canon/context/` and 9 diagram files beside them. It counts rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights.
36
+ Length quotes its checkpoint from `claude/skills/context-fold/references/context.md`: roughly 150 rendered lines for an entry, applied to each of the 60 entries the audit measures, 51 of them under `canon/context/` and 9 diagram files beside them. It counts rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights.
37
37
 
38
38
  A line is wrapped at the width it renders at, so a link counts as its anchor text and a backticked path counts whole, which parts the rendered count from a source count wherever an entry cites a path in backticks rather than as a link. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see.
39
39
 
@@ -71,7 +71,7 @@ The audit reads `canon/REQUIREMENTS.md` under its own step rather than as a fold
71
71
 
72
72
  The provenance check covers `canon/context/` alone, the reference-form check covers the split folders inside it, the required-section check covers `canon/context/` alone, and length and the table finding reach every audited folder.
73
73
 
74
- What narrows the provenance check is stated in `standards/context.md`, which opens its scope by handing figures to the `draft-figure` skill, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
74
+ What narrows the provenance check is stated in `claude/skills/context-fold/references/context.md`, which opens its scope by handing figures to the `draft-figure` skill, and the sibling standards do not restate it. A marker reported in a diagram entry would cite a rule that entry's own standard routes elsewhere. The split is between kinds of rule rather than kinds of folder, and what decides it is which tier states the rule rather than what the check measures.
75
75
 
76
76
  Length and the table finding generalize as judgments about how far a reader travels, so both reach wherever the audit is pointed. Required sections narrow for a plainer reason: each list is one standard's own, so it reaches the folder that standard governs. The context standard states a set, and an older install's diagrams folder has no standard to state one.
77
77