@erclx/canon 5.10.0 → 5.12.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 (45) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  3. package/claude/skills/auto-ship/SKILL.md +5 -8
  4. package/claude/skills/auto-ship/references/failure-recovery.md +0 -1
  5. package/claude/skills/auto-ship/references/ui-checklist.md +21 -0
  6. package/claude/skills/git-pr/references/evidence.md +7 -7
  7. package/claude/skills/git-ship/REQUIREMENT.md +1 -1
  8. package/claude/skills/git-ship/SKILL.md +1 -1
  9. package/claude/skills/review-craft/references/ui.md +1 -2
  10. package/claude/skills/review-pr/SKILL.md +1 -1
  11. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  12. package/claude/skills/ui-checklist/SKILL.md +1 -18
  13. package/docs/agents/gate.md +1 -1
  14. package/docs/agents/hooks.md +2 -0
  15. package/docs/agents/index.md +2 -2
  16. package/docs/agents/install-and-sync.md +2 -2
  17. package/docs/agents/pr-evidence.md +70 -22
  18. package/docs/agents/tasks-validate.md +3 -3
  19. package/docs/agents/worktrees.md +16 -1
  20. package/docs/target/migrations.md +1 -1
  21. package/docs/workflow/ai-workflow.md +2 -1
  22. package/docs/workflow/parallel-features.md +3 -3
  23. package/docs/workflow/skill-map.md +1 -1
  24. package/package.json +3 -4
  25. package/scripts/lib/sandbox-path.sh +1 -1
  26. package/src/commands/gate.ts +3 -2
  27. package/src/commands/pr.ts +93 -19
  28. package/src/commands/worktrees.ts +37 -0
  29. package/src/gate/sequencer.ts +0 -20
  30. package/src/gate/stages.ts +10 -3
  31. package/src/pr/evidence.ts +35 -0
  32. package/src/tasks/validate.ts +37 -27
  33. package/src/worktrees/bare-flag.ts +83 -0
  34. package/standards/tasks.md +1 -1
  35. package/tooling/base/configs/.husky/post-merge +30 -77
  36. package/tooling/base/configs/.husky/post-rewrite +3 -3
  37. package/tooling/base/reference.md +1 -1
  38. package/scripts/core/clean.sh +0 -36
  39. package/scripts/core/regen-agent-fixture.sh +0 -235
  40. package/scripts/core/regen-claude-copies.sh +0 -10
  41. package/scripts/core/regen-indexes.sh +0 -10
  42. package/scripts/core/repair-bare-flag.sh +0 -19
  43. package/scripts/core/snapshot.sh +0 -134
  44. package/scripts/core/update.sh +0 -35
  45. package/scripts/lib/worktree.sh +0 -20
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.10.0",
4
+ "version": "5.12.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -21,6 +21,7 @@ Review is the step that varies most. It gets skipped on a diff that needed one,
21
21
  - Own the review receipt's lifetime, since this chain writes it, cites it in its own closing block, and is the only body that can read whether the step keeping it is still using it
22
22
  - Delegate the ship sequence to `git-ship` rather than restating it, and name only what this chain adds to it
23
23
  - Open the pull request as a draft before the continuous integration watch begins, since a pull request marked after it is mergeable for the length of the run
24
+ - Continue past a produced UI checklist rather than stopping on it, and report its unchecked boxes in the closing block, since the draft mark already holds the merge and a stop there skipped review on the resumed path
24
25
  - Prove the draft target's head is the current branch and its state is open before marking it, and stop with nothing marked on a mismatch, since the number is retyped between steps and a draft mark on a release pull request holds that release
25
26
  - Name the recovery for the stop it took, since the value of stopping is that the user knows where to resume
26
27
 
@@ -6,7 +6,7 @@ disable-model-invocation: true
6
6
 
7
7
  # Auto ship
8
8
 
9
- Chain the post-plan pipeline in a single run. Every step has a stop condition. State is always recoverable on stop: code lives on the branch, review output on disk, plan still linked.
9
+ Chain the post-plan pipeline in a single run. Every step but the UI checklist has a stop condition. State is always recoverable on stop: code lives on the branch, review output on disk, plan still linked.
10
10
 
11
11
  ## Guards
12
12
 
@@ -95,11 +95,7 @@ Never read a missing subcommand as clean. Read `${CLAUDE_SKILL_DIR}/references/v
95
95
 
96
96
  ## Step 5: UI checklist (conditional)
97
97
 
98
- If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`.
99
-
100
- If `ui-checklist` produces a checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
101
-
102
- If there is nothing to verify visually and nothing shipping untested, continue.
98
+ If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`, then continue to Step 6 whatever it returns, in every run. The Step 8 draft mark holds the merge, so a stop here protects nothing. When it produces a checklist, read `${CLAUDE_SKILL_DIR}/references/ui-checklist.md` for the counts to hold before `git-pr` removes the file.
103
99
 
104
100
  ## Step 6: review
105
101
 
@@ -149,10 +145,11 @@ Emit the Output block on the wake after `git-ship`'s background CI watch exits o
149
145
 
150
146
  ## Output
151
147
 
152
- Respond with up to five lines:
148
+ Respond with up to six lines:
153
149
 
154
150
  ```plaintext
155
151
  ✅ Autoshipped (<state>): <PR url>
152
+ 🖼️ <N> visual boxes unchecked (<M> taste), owed on the evidence comment of <PR url> to the operator, or to the UI reviewer for the boxes a driver can run
156
153
  <N minor findings kept in .canon/review/branch-<slug>.md>
157
154
  <N facts routed to context entries>
158
155
  <N memories captured in .canon/memory/>
@@ -161,7 +158,7 @@ Respond with up to five lines:
161
158
 
162
159
  `<state>` is whatever the Step 8 read returned, being `draft` or `ready, unsupervised`, rather than the state the undo asked for.
163
160
 
164
- Omit the second line if there were no minor findings, and the third if nothing routed. Omit the fourth if `memory-capture` wrote no memory file this session, and the fifth if it returned no review line. Pass that line through verbatim, since a dispatched worker's controller relays it to the operator.
161
+ Fill the second line from the counts Step 5 held, and omit it when no checklist was produced. Omit the third line if there were no minor findings, and the fourth if nothing routed. Omit the fifth if `memory-capture` wrote no memory file this session, and the sixth if it returned no review line. Pass that line through verbatim, since a dispatched worker's controller relays it to the operator.
165
162
 
166
163
  This block replaces the one `git-ship` closes on rather than following it, since emitting both reports one run twice and buries the state under a `✅ Shipped` that does not name it.
167
164
 
@@ -20,7 +20,6 @@ Every stop point leaves recoverable state. The user resumes manually from the ap
20
20
  | Empty changed-file list | Re-run once the plan produces tracked output. Ship gitignored output outside the chain, never by tracking it. |
21
21
  | Branch collision on worktree entry | `session-worktree` Step 5 found `<slug>` already as a local branch. Resolve manually (rename or delete the stale branch), then re-run autoship. |
22
22
  | Verify fails | Read logs, fix manually, run `/git-ship` |
23
- | UI checklist | Verify visually, run `/git-ship` |
24
23
  | Inherited review findings | Fix findings, run `/git-ship` |
25
24
  | Self-introduced finding survived | Read the receipt for what the one repair pass left open, fix it, run `/git-ship` |
26
25
  | git-ship fails | Inspect hook or remote error, run again |
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: UI checklist counts
3
+ description: What auto-ship Step 5 holds when ui-checklist produces a checklist, so the closing block can name the unchecked visual boxes after git-pr has removed the file
4
+ ---
5
+
6
+ # Hold the checklist counts
7
+
8
+ Step 5 of `auto-ship`. The session reads this file when `ui-checklist` has produced a checklist.
9
+
10
+ The chain continues to Step 6 here in every run, dispatched or hand-run, since the skill cannot tell the two apart and the draft mark at Step 8 holds the merge either way. A stop here used to leave nothing committed, and resuming at `/git-ship` skipped the review at Step 6.
11
+
12
+ Count two things in `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root:
13
+
14
+ - `<N>`, the `- [ ]` lines
15
+ - `<M>`, how many of those end in `(taste)`
16
+
17
+ Hold both until the Output block. `git-pr` removes the file once it posts the evidence comment, so a count taken after Step 8 reads nothing.
18
+
19
+ The Output block's second line takes both counts. When every box is a taste box, `<N>` and `<M>` match and the boxes are owed to the operator alone, since a driver reports a taste box as needing a person's eyes rather than passing it, so say the operator and drop the UI reviewer clause.
20
+
21
+ When `ui-checklist` reports nothing to verify visually, it writes no file, Step 5 holds no count, and the Output block carries no boxes line.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Rendered-surface evidence
3
- description: How git-pr finds the UI checklist and the local server, posts the evidence comparison or the checklist alone, and adds the preview address, for a change that touches a rendered surface
3
+ description: How git-pr finds the UI checklist and the local server, posts the evidence comparison or the checklist alone as the fallback for a git refusal, and adds the preview address, for a change that touches a rendered surface
4
4
  ---
5
5
 
6
6
  # Rendered-surface evidence
@@ -13,7 +13,7 @@ The rendered-surface step of `git-pr`, reached after the final command printed t
13
13
 
14
14
  When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body in the skill's pre-publish scan.
15
15
 
16
- Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
16
+ Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a refused render.
17
17
 
18
18
  ## Find the local server
19
19
 
@@ -31,7 +31,7 @@ The verb reads the listening sockets on this machine, keeps those whose process
31
31
 
32
32
  Pass `--local` to the evidence step only on an `ok` here. The binary answering `ok` is the one carrying the flag, so a binary lacking the verb never meets a flag it would reject.
33
33
 
34
- With both `--checklist` and `--local`, a diff carrying no evidence image still renders `ok`, with the link opening a comment the checklist closes, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
34
+ With a checklist, a diff carrying no evidence image still renders `ok`, and a local link rides in the same comment above the checklist, so a reviewer gets both in one place. A local link with neither an evidence image nor a checklist still reports `no-evidence`, which keeps a branch that changed no rendered surface from getting a comment holding a link and nothing else.
35
35
 
36
36
  A seeded workflow replaces the line with a note once the pull request closes, so the link does not outlive the branch it points at.
37
37
 
@@ -43,13 +43,13 @@ Run the verb once against the number the skill's final command printed. Pass `--
43
43
  canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --local <url> --json
44
44
  ```
45
45
 
46
- One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into. The local link is the one exception, stated in the step above.
46
+ One call answers both questions because a checklist decides `no-evidence`. The verb renders `ok` on a diff carrying a checklist and no evidence image, with a marked body holding the checklist, so the checklist always lands in the one comment a later call finds and edits. Without a checklist, a diff carrying no evidence image reports `no-evidence`.
47
47
 
48
48
  Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
49
49
 
50
50
  Read `reason` on the record rather than the exit code.
51
51
 
52
- - `no-evidence`: nothing changed under an `evidence/` segment, so there is no comparison to post and no body was rendered. Say nothing about the evidence and fall through to the checklist step below.
52
+ - `no-evidence`: nothing changed under an `evidence/` segment and no checklist was passed, so no body was rendered. Say nothing about the evidence and move on.
53
53
  - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
54
54
 
55
55
  ```bash
@@ -68,7 +68,7 @@ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed
68
68
 
69
69
  ## Post the UI checklist alone
70
70
 
71
- Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. A local link sends the checklist through the evidence step instead, so this step runs on a branch with no evidence image only when the local step found no server. Post it as its own comment on `<number>`:
71
+ Run this step only as the fallback, when the evidence step above reported one of the git refusals and a checklist file exists, since every other branch with a checklist rendered `ok` and carried it. The comment this posts carries no marker, so a later `canon pr evidence` call reports no `checklist` for it. Post it as its own comment on `<number>`:
72
72
 
73
73
  ```bash
74
74
  gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
@@ -92,7 +92,7 @@ Deleting the file is what makes the later re-render safe. `git-followup` re-runs
92
92
 
93
93
  ## Post the preview address
94
94
 
95
- Run this step only when the evidence step above returned `ok` or a checklist was posted, by either step. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
95
+ Run this step only when the evidence step above returned `ok` or a checklist was posted by the fallback. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
96
96
 
97
97
  The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
98
98
 
@@ -9,7 +9,7 @@ description: What the ship chain is for, the gaps it closes, and why it does not
9
9
 
10
10
  Without this skill, the post-feature sequence runs from memory. Doc sync gets skipped, so the pull request ships with planning docs describing the previous scope, or it runs after staging has already closed and its output never reaches a commit. Chaining by hand is also where a session narrates between steps, which turns one flow into a conversation and invites a decision at every boundary.
11
11
 
12
- The sequence also reaches the remote on work nothing re-checked. This skill is the resume point `auto-ship` names at four of its stop points, including the one a failed verify takes, so the fix the user makes by hand after that stop is pushed with no suite run against it. Every branch shipped so far passed that verify on its first attempt, which is why the path has produced no instance rather than being closed.
12
+ The sequence also reaches the remote on work nothing re-checked. This skill is the resume point `auto-ship` names at three of its stop points, including the one a failed verify takes, so the fix the user makes by hand after that stop is pushed with no suite run against it. Every branch shipped so far passed that verify on its first attempt, which is why the path has produced no instance rather than being closed.
13
13
 
14
14
  ## Must
15
15
 
@@ -15,7 +15,7 @@ Run the verify commands `CLAUDE.md` names (lint, typecheck, tests) before the se
15
15
 
16
16
  When `CLAUDE.md` names no verify command, say so on one line and continue. A project with no suite is not a project with a failing one.
17
17
 
18
- Verify runs ahead of the sync skills so a stop leaves the tree exactly as the user left it. Re-running a suite the caller already ran costs one command, and the path it closes is the one that has no other guard: `auto-ship` verifies at its own Step 3 and then hands four of its stop points straight back here, so a fix made by hand after one of those stops otherwise reaches the remote with nothing re-run.
18
+ Verify runs ahead of the sync skills so a stop leaves the tree exactly as the user left it. Re-running a suite the caller already ran costs one command, and the path it closes is the one that has no other guard: `auto-ship` verifies at its own Step 3 and then hands three of its stop points straight back here, so a fix made by hand after one of those stops otherwise reaches the remote with nothing re-run.
19
19
 
20
20
  ## Pre-check
21
21
 
@@ -45,7 +45,7 @@ Take the sha from the row, so the base and head sides are exactly what the comme
45
45
 
46
46
  Open at most 12 images per pass, filled in this order:
47
47
 
48
- 1. Every image a ticked checklist box or the `Seen at:` line names
48
+ 1. Every image a ticked checklist box names
49
49
  2. The head side of each state at its narrowest and widest captured width, in one theme
50
50
  3. The base side, only for a state whose head image looks wrong or whose diff was meant to change appearance
51
51
 
@@ -65,7 +65,6 @@ The `## What to look at` block between the `pr-checklist` markers is the author'
65
65
 
66
66
  - Test each ticked box against an opened image showing the state and width it names. Flag a box whose image contradicts it, quoting the box.
67
67
  - A ticked box no image covers is untested rather than confirmed, and the finding says so.
68
- - Read the `**Widths:**` block's `Seen at:` line against the widths the images carry.
69
68
  - Test only the rows the current comment shows. A box ticked on an earlier head can describe images the comment no longer carries.
70
69
  - Leave unchecked boxes to the procedure's own read of the description.
71
70
 
@@ -73,7 +73,7 @@ Read each test file the branch added or changed against the final filter in `can
73
73
 
74
74
  Then read the description's `## Testing` section, which is the one part of a pull request body this session is positioned to falsify. The branch author cannot see what a sibling branch drove the same day, and the arm list is what settles the claim rather than the diff.
75
75
 
76
- Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`, which reserves an unchecked box for a capability the agent lacks. Raise the box when it names no human at all, when the human it names is a live agent session, or when it names a person for a step the repository ships a harness for, `scripts/sandbox/run.sh` and `scripts/eval/run.sh` being the two. Cost alone is not an answer, since authorizing a spend is the operator's and performing the run is not. A refusal the author actually met is an answer, and it names which one.
76
+ Test every unchecked box against the testing discipline in `${CLAUDE_SKILL_DIR}/../../standards/pr.md`, which reserves an unchecked box for a capability the agent lacks. Raise the box when it names no human at all, when the human it names is a live agent session, or when it names a person for a step the repository ships a harness for, such as `scripts/sandbox/run.sh`. Cost alone is not an answer, since authorizing a spend is the operator's and performing the run is not. A refusal the author actually met is an answer, and it names which one.
77
77
 
78
78
  Test every ticked box too, bounded to one naming a file or a command. Confirm what it names still exists at `<headRefOid>`, `git show <headRefOid>:<path>` for a file or `git grep <command> <headRefOid>` for a command, rather than re-running what it claims. A box naming neither stays untested, since nothing here can confirm a claim carrying no artifact. A box ticked before a later commit removed or replaced what it names is a `should-fix` finding on the body itself, filed under a `**PR body**` block ahead of the file blocks, since what it corrupts is the merge record rather than a file in the diff.
79
79
 
@@ -63,7 +63,7 @@ Report the state of play in the invocation block, and open every later sweep rep
63
63
  4. Hand off. Read `${CLAUDE_SKILL_DIR}/references/orchestrator-dispatch.md` and follow it: check the branch is unclaimed, check the row's file set against every track in flight, then dispatch a background worker with `claude --bg`. Fall back to the human-launch line it replaces when the check refuses, the sets overlap, or a stated reason serializes the row behind something already out.
64
64
  5. Review the PR. When a worker opens a PR, its first pass runs through `review-pr` in a reviewer dispatched under `role-reviewer` when `### The review dispatch` below fires, and in this session otherwise. Either is the deep, independent pass. The worker's autoship self-review was only the green gate.
65
65
  - Learning that a PR moved is the mechanical half, so read `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` and run the poll under the condition it states rather than checking the board by hand. That runbook holds the routing and the trigger, and a summary of it here is a second source that drifts from it.
66
- 6. Dispatch the handback. A pass posting anything owed, a finding at any severity or a testing question, tells the session holding that branch to run `review-address`, rather than waiting for a person to relay it. Re-review when the worker's own message says the address pass finished, per the channel `role-worker` states, rather than polling for an answer nothing else marks as landed, and send the re-review back to the dispatched reviewer when one took the first pass. Once a pass posts `## Review closed`, and once a UI close names the current head wherever the PR carries a checklist, lift the pull request's draft mark yourself, per Boundaries below. Then the human merges. Tell the trailing worker to rebase when its branch shares a seam with the merged one.
66
+ 6. Dispatch the handback. A pass posting anything owed, a finding at any severity or a testing question, tells the session holding that branch to run `review-address`, rather than waiting for a person to relay it. Re-review when the worker's own message says the address pass finished, per the channel `role-worker` states, rather than polling for an answer nothing else marks as landed, and send the re-review back to the dispatched reviewer when one took the first pass. Once a pass posts `## Review closed`, a UI close names the current head wherever the PR carries a checklist, and `--check` reports `settled`, lift the pull request's draft mark yourself, per Boundaries below. Then the human merges. Tell the trailing worker to rebase when its branch shares a seam with the merged one.
67
67
  - Read `${CLAUDE_SKILL_DIR}/references/orchestrator-handback.md` on reaching this step. It holds how the message is addressed and worded, what to do when no live session holds the branch, and where a worker's reply goes.
68
68
 
69
69
  A plan written here is written against a tree several branches are already changing, so it names the file set of every track in flight as a constraint, one set per track, read from the Touches column of that track's row. State for each set which of the two acts it forbids, per Constraints in `${CLAUDE_SKILL_DIR}/../../standards/plan.md`. A bare path list leaves the worker guessing, which is how a plan ends up forbidding the repair of a citation the change broke.
@@ -75,7 +75,7 @@ Stamp the block with the commit this session read the tree at, which the same se
75
75
  - Run one orchestrator at a time. The board is gitignored, so a second session sees none of this one's writes: two task files land minutes apart under different labels for the same work, one session archives a task mid-sweep in the other, and each archives a plan the other had retargeted. An Owner column does not fix this, since neither session can read the other's rows.
76
76
  - Do not implement features in this session. Hand the plan to a worker.
77
77
  - Do not merge. Recommend merge or changes. The human merges.
78
- - Lift a pull request's draft mark once its review closes, whether this session or a reviewer it dispatched closed it, acting directly on the pull request rather than dispatching a worker to do it. Where `canon pr evidence <number> --json` carries a `checklist`, hold it until a UI close names the current head. `role-worker` and `role-reviewer` state the mirroring refusal: neither can verify who is asking or whether review actually closed, so the act stays with this session.
78
+ - Lift a pull request's draft mark once its review closes, whether this session or a reviewer it dispatched closed it, acting directly on the pull request rather than dispatching a worker. Hold it on any `canon pr evidence <number> --check --json` reason but `settled`, reporting that reason, and where that record carries a `checklist`, until a UI close names the current head. `role-worker` and `role-reviewer` state the mirroring refusal: neither can verify who is asking or whether review actually closed, so the act stays with this session.
79
79
  - Do not spawn a worker with the Agent tool. An in-process subagent shares this session's context and cannot be steered or reached independently, which breaks the property this boundary protects rather than the mechanism it names. The launch in `orchestrator-launch.md` is a separate `claude --bg` process with its own worktree and its own PR, so it preserves that property instead.
80
80
  - Dispatch a background worker only once the collision check in `orchestrator-dispatch.md` clears and the row's file set is disjoint from every track in flight. Colliding with an existing worktree or session is what the check exists to catch rather than a judgment call this session makes case by case. No fixed count binds how many tracks run at once, and Parallelism below states what does.
81
81
  - Do not edit tracked files from this session, at any size. The boundary offers no proportionality exception and nothing enforces it.
@@ -34,30 +34,13 @@ Group visual items by feature area. Use `- [ ]` checkbox syntax, and write each
34
34
  Write each box so a session driving the running app can run it without guessing:
35
35
 
36
36
  - **Route.** Name where the box is driven, as a path or the control that reaches it. A box naming no route is driven from the address the driver opens, so a single-page app with no routing needs no invented path. Write "from the page the app opens on" when a state is reached by a sequence rather than a URL.
37
- - **Width.** Name the width a box is driven at only when the change can make it differ by width, the same test the widths block below uses for omission. A box about copy or color renders the same everywhere, so leave the width off rather than inventing one.
37
+ - **Width.** Name the width a box is driven at only when the change can make it differ by width, such as layout, spacing, or anything responsive. A box about copy, color, or a state that renders the same everywhere leaves the width off rather than inventing one.
38
38
  - **Verb.** Open on an action a driver performs, such as "scroll to", "click", "resize to", or "tab to". Never open on "read" or "look at", which name no action.
39
39
  - **Taste.** End the line with `(taste)` when the result is a judgment of look or feel rather than a fact a driver can observe. A driver reports such a box as needing a person's eyes instead of passing it.
40
40
  - **Setup.** A box needing a state the page does not open in states the setup inline. When a hosted preview cannot reach that state, such as a stopped service or a seeded record, end the line with `(local only)` instead.
41
41
 
42
42
  The checkbox stays `- [ ]` followed by the action and the result, since the evidence comment carries ticked boxes forward by that shape.
43
43
 
44
- ## The widths block
45
-
46
- A screenshot shows one width, so a reviewer cannot tell from the picture alone whether any other width was looked at. When a change touches layout, spacing, or anything responsive, put this block above the visual items, since it frames what the screenshots beside it can support:
47
-
48
- ```markdown
49
- **Widths:**
50
-
51
- Seen at: <the widths the reviewer looked at>
52
- Not seen at: <the widths nobody looked at, or "none">
53
- ```
54
-
55
- Leave both lines for the reviewer to complete. This session cannot verify a width, so the block asks what was seen and never asserts coverage. Fix no width list in the block, since which widths matter belongs to the project.
56
-
57
- Keep the `Not seen at:` line even when the answer is "none", so an omission reads as a claim rather than a gap.
58
-
59
- Omit the block when the change cannot differ by width, such as copy, color, or a state that renders the same everywhere, the way the missing-test heading is omitted below.
60
-
61
44
  ## The missing-test list
62
45
 
63
46
  Name every automatable change this branch ships with no test, one per line, with the layer `test-craft` would place it at. Do not write the test and do not read the project's test config to write one against.
@@ -5,7 +5,7 @@ description: Running the gate this repository verifies a branch with, what the s
5
5
 
6
6
  # Merge gate
7
7
 
8
- `canon gate run` runs every stage that guards a branch here, in order, stopping at the first stage that finds a fact. `bun run check` and `bun run check:ci` resolve to it, and `scripts/core/update.sh` calls it with `--nested` after a dependency update.
8
+ `canon gate run` runs every stage that guards a branch here, in order, stopping at the first stage that finds a fact. `bun run check` and `bun run check:ci` resolve to it, and the `update` package script runs `bun run check` after a dependency update.
9
9
 
10
10
  ```bash
11
11
  canon gate run
@@ -16,6 +16,8 @@ canon hooks post-merge --root "$root"
16
16
  | --------------- | ---------------------------------------------------------------------------------------------------- |
17
17
  | `--root <path>` | Main worktree root holding the board and the records, resolved from `git worktree list` when omitted |
18
18
 
19
+ The base stack ships the same call as its `.husky/post-merge`, behind the same board guard and upgrade bootstrap, so a target that syncs it runs these five steps.
20
+
19
21
  Exit codes: `0` on every path, since a hook failing aborts nothing useful. Lines go to stderr, and stdout stays empty. An ordinary merge prints nothing.
20
22
 
21
23
  ## The steps
@@ -35,7 +35,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
35
35
  - [Migrate](migrate.md): What each canon migrate verb moves and rewrites, what it refuses, the markers that keep an old spelling, and the idempotence check a second run gives
36
36
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
37
37
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
38
- - [The pull request evidence comparison](pr-evidence.md): What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, and why the comparison anchors at the merge base rather than the previous push
38
+ - [The pull request evidence comparison](pr-evidence.md): What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, what the --check read reports as owed, and why the comparison anchors at the merge base rather than the previous push
39
39
  - [The pull request local preview](pr-local.md): How canon pr local finds the server a worktree is running, why a server in another worktree is never reported, the refusal reasons it names, and how a close workflow removes the link with --remove
40
40
  - [The pull request preview deploy](pr-preview.md): How canon pr preview finds a fenced Cloudflare Pages deploy, dispatches it on a pull request's branch, reads the alias the run prints, the refusal reasons it names, and why an unfenced deploy is refused before anything runs
41
41
  - [Head-sensitive pull request reads](pr-reads.md): Resolving a branch tip from the remote rather than from the pull request object, reading what a review pass covered off its own marker rather than off GitHub's submission stamps, why an empty run list is not a pass, the refusal reasons each verb names, and what the remote read costs
@@ -64,4 +64,4 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
64
64
  - [Teach authoring](teach-authoring.md): Resolving what the next lesson needs before it is written, rendering a lesson body's blocks to HTML, and rewriting the root listing, a contents page, and each lesson's chrome and quiz stepper from its marker regions
65
65
  - [Teach](teach.md): Listing learning workspaces with what their records schedule next, opening one with its required files, recording sources and glossary terms, viewing one with its chrome refreshed, where the lesson authoring verbs are described, the refusal reasons, and why every write here runs through a verb
66
66
  - [Test order](test-order.md): Reading where an implementation reached history ahead of its test, how a pair is decided, the three verdicts, the coverage the pairing cannot reach, and why the check reports rather than gates
67
- - [Worktrees](worktrees.md): Reporting which worktrees are reclaimable, removing the ones that are, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
67
+ - [Worktrees](worktrees.md): Reporting which worktrees are reclaimable, removing the ones that are, the hidden verb that clears a stranded bare flag, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
@@ -139,8 +139,8 @@ since a target has no rule source of its own that ships nowhere.
139
139
 
140
140
  It takes `--root <path>` and defaults to the toolkit root, prints nothing on success, and
141
141
  reports the reason on stderr with exit 1 when the record names a stack or rule
142
- that does not resolve. `scripts/core/regen-claude-copies.sh` calls it, and the
143
- Consumed copies stage of `bun run check` asserts the result is committed.
142
+ that does not resolve. The Consumed copies stage of `bun run check` calls it and
143
+ asserts the result is committed.
144
144
 
145
145
  ## Whole-project sync
146
146
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: The pull request evidence comparison
3
- description: What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, and why the comparison anchors at the merge base rather than the previous push
3
+ description: What canon pr evidence compares, the marker that lets it edit its own comment rather than duplicate it, the refusal reasons it names, what the --check read reports as owed, and why the comparison anchors at the merge base rather than the previous push
4
4
  ---
5
5
 
6
6
  # The pull request evidence comparison
@@ -46,18 +46,19 @@ image or marks the case new.
46
46
 
47
47
  `reason` on the record is what a caller branches on, not the exit code:
48
48
 
49
- | Reason | What it means |
50
- | ---------------------- | ----------------------------------------------------------------------------- |
51
- | `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
52
- | `no-evidence` | Nothing in the diff carries an `evidence/` segment. An ordinary silent no-op. |
53
- | `unreadable-checklist` | `--checklist` named a path that could not be read, or one holding nothing. |
54
- | `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
55
- | `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
56
- | `no-branch` | The pull request carries no head branch name. |
57
- | `no-object-head` | The pull request object reported no head commit. |
58
- | `no-base` | GitHub reported no merge base for the pull request. |
59
- | `unreadable-changes` | GitHub could not list what the pull request changed. |
60
- | `would-empty` | The render holds no cases and the marked comment holds some. No body. |
49
+ | Reason | What it means |
50
+ | ---------------------- | --------------------------------------------------------------------------------------- |
51
+ | `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
52
+ | `no-evidence` | No `evidence/` segment in the diff and no `--checklist` or `--preview`. A silent no-op. |
53
+ | `unreadable-checklist` | `--checklist` named a path that could not be read, or one holding nothing. |
54
+ | `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
55
+ | `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
56
+ | `no-branch` | The pull request carries no head branch name. |
57
+ | `no-object-head` | The pull request object reported no head commit. |
58
+ | `no-base` | GitHub reported no merge base for the pull request. |
59
+ | `unreadable-changes` | GitHub could not list what the pull request changed. |
60
+ | `would-empty` | The render holds no cases and the marked comment holds some. No body. |
61
+ | `check-writes` | `--check` was passed with `--preview`, `--local`, or `--checklist`. |
61
62
 
62
63
  `no-evidence` is not a refusal a caller reports. A project on a stack that
63
64
  carries no evidence path, such as `base` or `python`, hits this reason on
@@ -75,8 +76,9 @@ shows a reviewer, each field present only when that comment holds it:
75
76
 
76
77
  These come from the comment already posted, never from the flags the call
77
78
  passed, so a skill driving a review reads which address and which checklist a
78
- reviewer has without parsing the comment itself. A checklist posted on its own
79
- carries no marker, so the record reports none of the three for it. Reading
79
+ reviewer has without parsing the comment itself. A checklist posted raw, which
80
+ only the fallback for a refused render does now, carries no marker, so the
81
+ record reports none of the three for it. Reading
80
82
  them means the thread is read before the `no-evidence` answer too, so an
81
83
  unreadable thread refuses as `gh-failed` on both reasons rather than
82
84
  reporting the fields absent.
@@ -155,11 +157,11 @@ already holds, the same way the preview address is carried. That is what keeps
155
157
  already ticked, since `git-pr` deletes the handoff once the first post reports
156
158
  success and no later call has a file to pass.
157
159
 
158
- The option does not decide `no-evidence` alone. A branch with a checklist and
159
- no changed evidence image still reports `no-evidence`, and the caller posts the
160
- checklist on its own, which is what it did before this option existed. A
161
- checklist passed together with `--local` is the exception, covered below. A path
162
- that cannot be read, or one holding nothing, refuses as
160
+ The option decides `no-evidence` on its own. A branch with a checklist and no
161
+ changed evidence image renders `ok` with a marked body holding the checklist,
162
+ so the checklist always lands in the one comment a later call finds and edits.
163
+ The raw post survives only as the caller's fallback when the render is refused.
164
+ A path that cannot be read, or one holding nothing, refuses as
163
165
  `unreadable-checklist` rather than rendering without it: the caller deletes the
164
166
  handoff once a post succeeds, so a silently dropped checklist is the only copy
165
167
  gone.
@@ -174,11 +176,57 @@ the local address the marked comment already holds, the same as the other two.
174
176
 
175
177
  The option does not turn `no-evidence` into `ok` on its own. A branch with a
176
178
  dev server running and no rendered change would otherwise get a comment holding
177
- a link and nothing else. Together with `--checklist` it does, so the link and
178
- the checklist land in one comment rather than the checklist going out alone.
179
+ a link and nothing else. Together with `--checklist` the link and the
180
+ checklist land in one comment.
179
181
  `canon docs pr-local` covers where the address comes from and how the line is
180
182
  removed at close.
181
183
 
184
+ ## What a pull request is still owed
185
+
186
+ `--check` reads the same changed set and the same thread, renders no body, and
187
+ reports which steps of the ship chain that run after the pull request opens
188
+ never landed on it. It reads the pull request rather than any session's account
189
+ of what it ran, so a session that stopped after opening the pull request leaves
190
+ a branch the check can tell apart from one that needed neither step.
191
+
192
+ ```bash
193
+ canon pr evidence 1341 --check --json
194
+ ```
195
+
196
+ | Reason | What it means |
197
+ | --------- | ------------------------------------------------------------------ |
198
+ | `settled` | Nothing is owed. `owed` is empty. |
199
+ | `owed` | `owed` lists `evidence`, `preview`, or both, always in that order. |
200
+
201
+ - `evidence` is owed when the changed set carries an evidence image and no
202
+ comment carries the marker.
203
+ - `preview` is owed when the pull request has a marked comment or an evidence
204
+ change, a deploy workflow resolves in the checkout the verb runs from, and
205
+ the marked comment's first line is no `**Preview:**` line. A
206
+ `**Local preview:**` line does not satisfy it. A project deploying nothing is
207
+ never owed a preview.
208
+
209
+ The record carries `owed`, `commentId` when a marked comment exists, and the
210
+ same `preview`, `local`, and `checklist` fields the other reasons carry, so a
211
+ caller reads one shape. It never carries `body`, so a writer branching on `ok`
212
+ and posting `body` cannot act on a check record. Passing `--preview`, `--local`,
213
+ or `--checklist` with it refuses as `check-writes` before any `gh` call, since
214
+ those ask a read to write.
215
+
216
+ The thread cannot separate a skipped preview step from a failed one. A pull
217
+ request whose deploy run failed or timed out reads as owing a preview, and the
218
+ session that ran it holds the cause. A checklist-only branch posts a marked
219
+ comment, so it reads as owing a preview once that comment exists and a deploy
220
+ workflow resolves. The deploy
221
+ workflow is read from the checkout rather than the pull request's head, so a
222
+ pull request that adds or removes its own deploy workflow reads against trunk's.
223
+
224
+ An unreadable thread refuses as `gh-failed` rather than reporting `settled` off
225
+ a thread it never read. The check skips the merge base read, which only the
226
+ render needs, so it never refuses `no-base`. The orchestrator's draft lift is
227
+ the caller, and lifts only on `settled`, so a refusal holds it the same as
228
+ `owed` does.
229
+
182
230
  ## What a collapsed comment still leaves to GitHub
183
231
 
184
232
  GitHub already draws its own before-and-after comparison on the Files Changed
@@ -17,7 +17,7 @@ Seven checks run. Collisions reaches one half of the `## Run now` test the board
17
17
  | Check | What it reports |
18
18
  | ---------- | ----------------------------------------------------------------------------------------------------------------------- |
19
19
  | Shape | A row whose cell count disagrees with its table's header, or one stranded behind a table a blank line already closed |
20
- | Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line links several plans |
20
+ | Plan | A row whose plan citation disagrees with its group or its task file, or a task whose `Plan:` line lists one plan twice |
21
21
  | Mapping | A row or backlog line naming no task file |
22
22
  | Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
23
23
  | Ordering | A `## Needs a plan` row whose stated position disagrees with where it actually sits, or which states no position at all |
@@ -26,11 +26,11 @@ Seven checks run. Collisions reaches one half of the `## Run now` test the board
26
26
 
27
27
  Shape runs before any other check reads a row, since a row failing it carries no dependable fields for the rest to check. A blank or prose line closes the table above it, so the walk treats the next pipe line as a fresh header candidate rather than as a continuation. That candidate counts as a header only when the line behind it is a separator carrying the same cell count, and one that fails is `row-untabled`, stranded behind a table that already closed. Cell count still has to match the header on every row that clears that test, and a row whose count disagrees is `row-misshapen`, the shape a dropped pipe or a merged column produces.
28
28
 
29
- The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair naming two different plans is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
29
+ The Plan check reads the row and the task file both, because the two are written by different hands and only the task's own `Plan:` line reaches the archive. A row carrying a plan whose task states none is `plan-uncited`, and a pair where the row's plan is none of the task's links is `plan-mismatched`. Both sides resolve against the board and against the project root before they compare, so a row writing `../plans/x.md` and a task writing `.canon/plans/x.md` name one file rather than two.
30
30
 
31
31
  The other two groups have no Plan column, so the same check reads the task file's own `Plan:` line against what the group name claims. A `## Needs a plan` row whose task cites a plan that sits live on disk is `plan-parked`, since the group says no such plan exists and the row is either misplaced or its plan is stale and belongs in the archive. An `## Up next` row whose task cites no live plan is `plan-absent`, since that group says one is written. A plan already archived, or a cited path with no file behind it, reads as not live on both sides. A row whose task file is gone is left to `task-unresolved`. An `## Up next` row with nothing left to build, waiting on an act only a person can take, reports `plan-absent` once its plan archives while the task stays live. That report is expected, since the check reads the task file and not the `Waiting on` prose, so a row in that state moves to `## Needs a plan` or closes.
32
32
 
33
- A task whose `Plan:` line links more than one plan is `plan-several` in every group, and it replaces the `plan-uncited` or `plan-absent` report the line would otherwise draw. The line is there, so saying it is missing would send the reader looking for the wrong fault. The line breaks the one-plan-per-task rule the tasks standard states instead, and `plan-mismatched` does not fit either, since that kind means the row and the task disagree while here the task alone is at fault. The message lists every plan the line links, and the fix is to split the task or drop the extra links.
33
+ A `Plan:` line linking several distinct plans is the shape a sliced task carries, so the checks read the whole line rather than reporting it. A `## Run now` row passes when its plan is any one of the links, and a parked row is judged on whether any link is live: `## Up next` needs one, and `## Needs a plan` reports `plan-parked` when one is. The check adds no sliced marker, so an unsliced task given a second link by hand reads as sliced. `plan-several` now means a line listing one plan twice, compared by the file each link resolves to, so `../plans/x.md` beside `.canon/plans/x.md` counts. It is reported in every group and replaces the `plan-uncited` or `plan-absent` report the line would otherwise draw. The message lists the links, and the fix is to drop the repeat.
34
34
 
35
35
  Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A row or a backlog line naming no task file is `task-unresolved`, and a file both surfaces name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. A task file neither surface names is `unplaced` rather than a finding, since that is the normal state between a session filing it and a live orchestrator placing it on the board. One check across both is what lets a task move between them without the move reading as a dropped file.
36
36
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Worktrees
3
- description: Reporting which worktrees are reclaimable, removing the ones that are, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
3
+ description: Reporting which worktrees are reclaimable, removing the ones that are, the hidden verb that clears a stranded bare flag, the record a hook reads back, why the reading keys on the pull request rather than on git ancestry, the refusals it names, and the two removal shapes
4
4
  ---
5
5
 
6
6
  # Worktrees
@@ -24,6 +24,21 @@ Exit codes: `0` every worktree was read, `1` refused. The refusal carries a `rea
24
24
 
25
25
  An exit code says nothing about a call made from a session, since a shell profile may wrap the binary in a function taking its status from a later command. Read the record's `reason` rather than the exit when a skill consumes this.
26
26
 
27
+ ## Repair the bare flag
28
+
29
+ `canon worktrees repair-bare-flag` clears a `core.bare` flag that Claude Code's worktree entry left set in the shared config. It is hidden, so no help listing shows it, because its one caller is a hook rather than a person.
30
+
31
+ ```bash
32
+ canon worktrees repair-bare-flag --root <path> --json
33
+ ```
34
+
35
+ | Option | Behavior |
36
+ | --------------- | ------------------------------------- |
37
+ | `--root <path>` | Repository to repair, default the cwd |
38
+ | `--json` | Print the record on stdout |
39
+
40
+ `--json` writes `{repaired, reason, message}` on stdout, once, whether or not anything was written. `reason` is `flag-unset`, `common-dir-unreadable`, `genuinely-bare`, or `write-failed` when `repaired` is false. `message` carries text when the flag is repaired and when the write failed, and is null on the three quiet skips. A `write-failed` record means the flag is still set, which a lock file or a read-only config causes. A repository whose common dir is not named `.git` keeps its flag. The exit code is always `0`, so a hook never fails a tool call. Without `--json`, a record carrying a message prints it on stderr and any other prints nothing.
41
+
27
42
  ## Reclaim
28
43
 
29
44
  `canon worktrees reclaim` removes every worktree the reading above called reclaimable, and the branch behind each one.
@@ -62,7 +62,7 @@ The first line reports the plan and the second applies it, relocating each rule
62
62
 
63
63
  Twenty-five plugin skills dropped their `claude-` prefix for two-word names, so `canon:claude-docs` answers as `canon:context-fold` and `canon:claude-tasks` as `canon:task-board`. A project that installed governance or tooling before that release holds files naming the old ones, and the plugin answers to none of them. A later release renamed `docs-fold` to `context-fold`, since the skill folds a session into the context entries and the task board rather than into `docs/`, and the same sweep carries a project across that rename too. <!-- canon-keep-retired -->
64
64
 
65
- Two of those files run rather than sit there. `.husky/post-merge` prints a command for a person to type, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
65
+ Two of those files run rather than sit there. `.husky/post-merge` runs the `canon hooks post-merge` verb, so a stale copy of the hook calls a binary that may lack it, and `.claude/hooks/pr-create-log.sh` hands a session a message naming a skill, so a stale copy tells someone to invoke something that no longer exists. A rule under `.claude/rules/canon/core/` names skills too, though a rule is read rather than run.
66
66
 
67
67
  Resync what the toolkit owns, then sweep what the project wrote:
68
68
 
@@ -87,7 +87,8 @@ When features are independent, run them in parallel, one worktree and one sessio
87
87
  For features on a mature stack, chain the post-plan pipeline in one session. Approve the plan, invoke `canon:auto-ship`, and the skill runs implement → verify → review → ship sequentially.
88
88
 
89
89
  - Use when the plan is tight and the stack has real verify commands and test coverage
90
- - Autoship stops on: verify failure after one fix attempt, UI manual checklist non-empty, an inherited review finding above minor, no diff baseline resolving against `main`, an empty changed-file list, or hook failure
90
+ - Autoship stops on: verify failure after one fix attempt, an inherited review finding above minor, no diff baseline resolving against `main`, an empty changed-file list, or hook failure
91
+ - A produced UI checklist does not stop it. The checklist rides to the draft pull request's evidence comment, and the closing block names how many visual boxes are still unchecked and who owes them
91
92
  - Every stop leaves recoverable state. Fix and resume with `/git-ship`
92
93
  - Skip autoship for auth, migrations, security-sensitive changes, or work where the plan itself is uncertain
93
94
 
@@ -70,8 +70,8 @@ Past that shape, it checks what a surviving row claims against what the tree hol
70
70
 
71
71
  Archiving a task carries its plan with it, in the same act. The merge is what settles a plan, and the hook below reaches the archive with nobody watching, so a second call after it would be a second failure point leaving the task archived and the plan live. A task whose plan a sibling still cites archives on its own and leaves the plan live, since moving it on the first task to close strands every other pointer at a path that has gone.
72
72
 
73
- Nothing chained that archive until the `post-merge` git hook landed. Every earlier step fires from `canon:auto-ship` or `canon:git-ship`, both of which finish while the pull request is still open, so a task archived there would close for work that may be abandoned. The board is gitignored, which rules out reading it from anywhere but the machine that pulled. The hook names the board's archive candidates and stays silent otherwise, including on a project with no board.
73
+ Nothing chained that archive until the `post-merge` git hook landed. Every earlier step fires from `canon:auto-ship` or `canon:git-ship`, both of which finish while the pull request is still open, so a task archived there would close for work that may be abandoned. The board is gitignored, which rules out reading it from anywhere but the machine that pulled. The hook archives the task a merged pull request closed, through the same verb the base stack ships to every target, and does nothing on a project with no board.
74
74
 
75
- Candidates rather than closed tasks, because outcomes are marked on the branch. A task can read all `[x]` while its pull request is still open, so `canon:task-board` confirms the work reached `main` before it moves anything, and the hook's own output says that check is still owed. A companion `post-rewrite` hook carries the same announcement for anyone pulling with rebase, which fires that event instead of `post-merge`.
75
+ The archive gates on a pull request number the merge carried and a closed task, because outcomes are marked on the branch and a task can read all `[x]` while its pull request is still open, so `canon:task-board` still confirms the work reached `main` when a person archives by hand. A companion `post-rewrite` hook carries the same steps for anyone pulling with rebase, which fires that event instead of `post-merge`.
76
76
 
77
- It announces and moves nothing, so `canon:task-board` stays the only writer. A shell-side archive would change a gitignored board with no diff to review and no session watching, and `index.md` regenerates from a session hook that a shell `mv` never fires. Both hooks ship with the `base` tooling stack, so a target project running this workflow gets the same trigger.
77
+ The hook archives through `canon tasks archive`, which carries the refusals above, so a task whose pull request has not merged stays where it is. Both hooks ship with the `base` tooling stack, so a target project running this workflow gets the same trigger, and `CANON_SKIP_RECLAIM`, `CANON_SKIP_UPGRADE`, and `CANON_SKIP_PLUGIN_UPDATE` turn off the three steps that act on the machine.
@@ -8,7 +8,7 @@ category: Workflow
8
8
 
9
9
  Groups run in the order a project meets them, so a reader at a known point scans to that group and reads across. The set reconciles the scenarios in [AI workflow](ai-workflow.md) with the lifecycle [target projects](../target/projects.md) describes, rather than inventing a third vocabulary beside those two, so a group name matches neither source exactly and every moment either one names has a group. Each row says when to reach for the skill. What it does is the skill's own description.
10
10
 
11
- This page is the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and a mention on any other page is prose rather than routing.
11
+ This page is the corpus the coverage claim is measured against: every name `canon claude skills list --names` reports takes exactly one row here. A skill serving two moments sits at the earlier one, and a mention on any other page is prose rather than routing. The landing page's skills field renders from this page, so its build refuses a skill with no row and a row naming no skill.
12
12
 
13
13
  ## Set up a project
14
14