@erclx/canon 5.70.0 → 5.72.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 (36) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/canon-feedback/REQUIREMENT.md +1 -1
  4. package/claude/skills/canon-feedback/SKILL.md +27 -2
  5. package/claude/skills/canon-feedback/references/template.md +22 -0
  6. package/claude/skills/git-pr/references/task-number.md +13 -0
  7. package/claude/skills/markdown-craft/references/markdown.md +1 -0
  8. package/claude/skills/plan-intake-answer/REQUIREMENT.md +4 -3
  9. package/claude/skills/plan-intake-answer/SKILL.md +35 -2
  10. package/claude/skills/review-ui/SKILL.md +1 -1
  11. package/claude/skills/review-ui/references/tick.md +17 -5
  12. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +1 -0
  13. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +6 -20
  14. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +10 -0
  15. package/claude/skills/role-orchestrator/references/orchestrator-talk-launch.md +30 -0
  16. package/claude/skills/role-orchestrator/scripts/poll.ts +1 -1
  17. package/claude/skills/role-reviewer/SKILL.md +4 -0
  18. package/claude/skills/task-board/references/tasks-record-lines.md +40 -0
  19. package/claude/skills/task-board/references/tasks.md +4 -8
  20. package/claude/skills/ui-film/references/pr-clip.md +26 -2
  21. package/package.json +1 -1
  22. package/src/commands/feedback.ts +83 -3
  23. package/src/commands/markdown.ts +6 -1
  24. package/src/commands/pr.ts +66 -21
  25. package/src/commands/tasks.ts +133 -0
  26. package/src/feedback/body.ts +50 -0
  27. package/src/feedback/github-format.ts +77 -0
  28. package/src/feedback/github.ts +51 -12
  29. package/src/markdown/colons.ts +102 -16
  30. package/src/pr/carry.ts +64 -0
  31. package/src/pr/evidence.ts +1 -1
  32. package/src/pr/lift.ts +29 -12
  33. package/src/pr/review-state.ts +2 -0
  34. package/src/tasks/archive.ts +54 -1
  35. package/src/tasks/record.ts +118 -0
  36. package/src/tasks/validate.ts +37 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.72.0](https://github.com/erclx/canon/compare/v5.71.0...v5.72.0) (2026-10-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **feedback:** post a checked follow-up on an open feedback issue ([#2805](https://github.com/erclx/canon/issues/2805)) ([b014d5e](https://github.com/erclx/canon/commit/b014d5ed5a5501b0902aa4adf794dd50d715d7aa))
9
+ * **feedback:** read each issue's comments in triage ([#2802](https://github.com/erclx/canon/issues/2802)) ([ab2c761](https://github.com/erclx/canon/commit/ab2c7616c094ba43f4045c48f9fdbbe7bfb5b976))
10
+ * **intake:** offer to close an issue when its item is declined ([#2807](https://github.com/erclx/canon/issues/2807)) ([95df7da](https://github.com/erclx/canon/commit/95df7da6e6769b240e2565e24b12dc35f9233993))
11
+ * **pr:** carry a UI close only across pushes no box can observe ([#2804](https://github.com/erclx/canon/issues/2804)) ([c5102ea](https://github.com/erclx/canon/commit/c5102eaf5e6367b7335b8b66650ad03ca06dad41))
12
+ * **ui-film:** film one beat per group and skip negative boxes ([#2806](https://github.com/erclx/canon/issues/2806)) ([fb24ed3](https://github.com/erclx/canon/commit/fb24ed36c23e1e3bcb5c34b3d89c2321707873cd))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **pr:** drive rendered markdown outside doc folders in carry rule ([#2808](https://github.com/erclx/canon/issues/2808)) ([c1f1582](https://github.com/erclx/canon/commit/c1f1582b284eb25ed359915223e52182af073d97))
18
+
19
+ ## [5.71.0](https://github.com/erclx/canon/compare/v5.70.0...v5.71.0) (2026-10-27)
20
+
21
+
22
+ ### Features
23
+
24
+ * **markdown:** read colon lists as labels ([#2797](https://github.com/erclx/canon/issues/2797)) ([f733a5f](https://github.com/erclx/canon/commit/f733a5f8d74e63cf5e257557a4d227b3478ca04a))
25
+ * **tasks:** name the sessions behind a task on a Sessions line ([#2799](https://github.com/erclx/canon/issues/2799)) ([acf80c4](https://github.com/erclx/canon/commit/acf80c4ce9d2c1adbc3e1ea2dfde433a08d46b4e))
26
+
3
27
  ## [5.70.0](https://github.com/erclx/canon/compare/v5.69.0...v5.70.0) (2026-10-27)
4
28
 
5
29
 
@@ -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.70.0",
4
+ "version": "5.72.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -14,7 +14,7 @@ Asked to report, a session also starts diagnosing, which spends the context the
14
14
  ## Must
15
15
 
16
16
  - Build the report from what the session already holds, name the surface and its type, and write the stated fallback for a field it cannot fill
17
- - Carry the whole request in the issue body alone, since a body and a comment that differ leave triage choosing
17
+ - Carry the whole request in the issue body, and what came after filing in a follow-up the CLI checks for links and for whether the fix still holds, never a hand-written comment
18
18
  - Open a GitHub issue on the toolkit repository through the CLI and print the returned URL on its own line
19
19
  - Surface the block and the reason when the issue call fails or the CLI is absent, so the report still exists
20
20
  - Hold a report the operator marks sensitive, since the issue is public
@@ -17,6 +17,19 @@ Format a `## Toolkit feedback` block from the current session, then open it as a
17
17
 
18
18
  ## Step 1: build the block
19
19
 
20
+ ### A follow-up or a new issue
21
+
22
+ Pick the form before filling any field, from what the session already holds.
23
+
24
+ - A follow-up, when the session cites an open feedback issue and the new case sits on its surface, such as a fix landing elsewhere, a second instance, or a finding that overturns the proposed fix
25
+ - A new issue, when no feedback issue is cited or the case sits on a different surface
26
+
27
+ For a follow-up, fill the `## Toolkit feedback follow-up` block in `${CLAUDE_SKILL_DIR}/references/template.md` and go to Step 2. For a new issue, build the report below.
28
+
29
+ A follow-up carries `### Observed`, `### Changes the fix`, and `### Links`, and the CLI refuses one missing any of them. `### Changes the fix` opens with yes or no, since a yes tells triage the body's fix no longer stands. `### Links` holds at least one URL or issue reference such as `frameboard#156`, never a bare name.
30
+
31
+ ### The report fields
32
+
20
33
  From the conversation so far, identify:
21
34
 
22
35
  - Target project name or kind, never its path, which names one machine's folder and nothing triage routes on. Name a private project by kind, such as `a Next.js app`, since reports leave the machine.
@@ -56,7 +69,7 @@ Keep Observed and Expected to one or two lines each, so the report scans. Length
56
69
 
57
70
  A GitHub issue form renders a submitted field as `### <label>`, so one shape reaches the toolkit whichever route a report takes, and the CLI parses both with one parser. The retired `**Surface:**` form is refused, so a block carrying it has to be rewritten.
58
71
 
59
- The issue body is the request. Put everything the toolkit needs to act on it there, since a comment is easy to miss and a body and a comment that differ leave triage choosing. A comment carries only what happens after filing.
72
+ The issue body is the request. Put everything the toolkit needs to act on it there, since a comment is easy to miss and a body and a comment that differ leave triage choosing. A comment carries only what happens after filing, and goes out as a follow-up through `canon feedback --on`, never a hand-written `gh issue comment`.
60
73
 
61
74
  ## Step 2: ship to the toolkit
62
75
 
@@ -78,14 +91,25 @@ cat <<'EOF' | canon feedback
78
91
  EOF
79
92
  ```
80
93
 
94
+ A follow-up pipes the same way with the issue number, and the CLI refuses a closed issue or one without the `feedback` label before posting anything:
95
+
96
+ ```bash
97
+ cat <<'EOF' | canon feedback --on <issue>
98
+ ## Toolkit feedback follow-up
99
+
100
+ ...
101
+ EOF
102
+ ```
103
+
81
104
  The CLI opens a GitHub issue on the toolkit repo, labeled `feedback` and each domain label the surface type names, such as `skills` for a plugin skill, and prints the issue URL for this skill to report on its own line. It needs `gh` authenticated, and `git-issue` is not the route, since it files on the current repository.
82
105
 
83
106
  ### Reading the outcome
84
107
 
85
108
  Read the outcome off stdout, never off the exit code, since a shell wrapper can flatten it. Stdout takes one of three shapes:
86
109
 
87
- - A line opening with `https://` is the filed issue. Report the URL.
110
+ - A line opening with `https://` is the filed issue, or the posted comment for a follow-up. Report the URL.
88
111
  - A block opening with `## Toolkit feedback` is a failed call. Show it in chat with the stderr reason and `📋 Paste this into an issue at https://github.com/erclx/canon/issues/new`.
112
+ - The same block on a follow-up is a failed comment. Show it the same way, pointing the paste line at the issue's own URL.
89
113
  - Empty stdout is a refusal. Step 3 says how to repair it from stderr.
90
114
 
91
115
  If `canon` is not on PATH, fall back: print the block in chat and tell the user `📋 Copy the block above into a toolkit-repo session.`
@@ -95,4 +119,5 @@ If `canon` is not on PATH, fall back: print the block in chat and tell the user
95
119
  A report that was not filed reads one of two ways on stdout, and stderr carries the reason in both:
96
120
 
97
121
  - Empty stdout, with a named missing field on stderr. Add that heading with a real value or its stated fallback, then re-run. Do not report the defect as filed.
122
+ - Empty stdout on a follow-up naming a field, an unreadable number, a closed issue, or a missing label. Repair the field or number, or file a new issue for a closed or unlabeled one.
98
123
  - The block on stdout, with `gh` absent or its call failed on stderr, which names which, since the two repairs differ. Show the block in chat with the reason and the paste line from Step 2.
@@ -51,3 +51,25 @@ Step 1 of `canon-feedback`. The session reads this file on reaching the point wh
51
51
 
52
52
  <one link per line>
53
53
  ```
54
+
55
+ ## Follow-up block
56
+
57
+ Step 1 fills this block instead of the report when the new case sits on an open feedback issue's surface, and Step 2 pipes it to `canon feedback --on <issue>`.
58
+
59
+ ```markdown
60
+ ## Toolkit feedback follow-up
61
+
62
+ ### Observed
63
+
64
+ <one or two lines on what happened after filing>
65
+
66
+ ### Changes the fix
67
+
68
+ <yes or no, then one line on what replaces the proposed fix when yes>
69
+
70
+ ### Links
71
+
72
+ <one URL or issue reference per line, such as frameboard#156>
73
+ ```
74
+
75
+ Keep Observed to one or two lines, as in the report. The CLI checks the fields and their openings but not their length, so the density is this template's rule alone.
@@ -29,6 +29,19 @@ The verb resolves the board at the main worktree root in-process, the root `sess
29
29
  - A line already reading as a list of `#NNN` entries takes the new number appended, reported as `appended`, so a task shipped in slices keeps every pull request
30
30
  - The same write drops this branch from the `Pending branch:` line `canon tasks outcome` wrote, which lets the merge-time archive close the task
31
31
 
32
+ ## Record the session
33
+
34
+ Beside the number, name this session on the same task's `Sessions:` line. It is the one record that covers a hand-launched worker, which no dispatcher saw start. Read the id from the roster's own row for the caller, then hand it to the verb under the same plan:
35
+
36
+ ```bash
37
+ id=$(canon sessions list --self --json | jq -r '.sessions[0].sessionId // empty')
38
+ [ -n "$id" ] && canon tasks session "$id" --plan feature-<slug> --json
39
+ ```
40
+
41
+ - A dispatched worker is already listed, since its dispatcher recorded it at launch, and the verb reports `unchanged` rather than writing it twice
42
+ - An empty `id` means the roster could not name the caller, so skip the write and report that no session was recorded
43
+ - Skip the same three refusals as the number below, and report `bad-input` or `no-name`, since either is a defect in this call
44
+
32
45
  ## Refusals
33
46
 
34
47
  Skip silently when the record is `ok: false` and `reason` is `no-board`, `no-match`, or `ambiguous`, meaning no board, no task naming the plan, or more than one. A guessed write would compound each, since a wrong match archives the wrong task unattended once the branch merges. Report any other refusal rather than swallowing it.
@@ -134,6 +134,7 @@ A bullet's punctuation follows its sentence count, and a list opens unannounced.
134
134
 
135
135
  - Do not use em dashes (`—`) or semicolons (`;`). Rewrite or restructure the sentence to avoid them. A colon or a comma standing where the dash was is the same sentence, so split it or rewrite it.
136
136
  - Do not use a colon to join two clauses in the middle of a sentence. A colon belongs after a label or at the end of a line introducing a list.
137
+ - The check misses a clause colon in a list of colon items, but it is wrong.
137
138
  - Do not use parenthetical asides in prose (`the config (which is optional) controls...`). Split into its own sentence or drop it. Parentheses in rule definitions for grouping examples are fine.
138
139
 
139
140
  ## Language
@@ -15,15 +15,16 @@ The write fails where no prose rule catches it. A worker's file-editing tools re
15
15
 
16
16
  - Offer every item with an empty slot, batched by cluster in the folder's numbering and capped at four items, so a stop leaves whole clusters unread
17
17
  - Open each question with a lead-in an operator who has not opened the file can follow, then rank the item's own suggestion first and give every option its cost
18
- - Land each selection through a verb that resolves the root in-process and rewrites the whole line, one cluster per call
19
- - Pass the cluster and the label exactly as the heading spells them, letter suffix included
18
+ - Land each selection through a verb that resolves the root in-process and rewrites the whole line, one cluster per call, with the label as the heading spells it
20
19
  - Report what stays unread by count after each cluster, and stop when there is no folder or no unread item
20
+ - Offer to close the issue an item headed `(#NNN)` names once its answer begins `decline`, with the drafted comment shown first, so closing takes one word
21
21
 
22
22
  ## Must not
23
23
 
24
24
  - Fill a slot the operator did not answer, or infer an answer from the conversation having happened
25
25
  - Overwrite a slot already carrying an answer
26
- - Write anywhere but an item's answer slot, the index included
26
+ - Write anywhere but an item's answer slot, the index included, bar one exception, which is closing a declined item's issue on GitHub after a yes given for that issue in the turn its draft was shown
27
+ - Close an issue when the operator says no to the offer, gives no answer, or when the only yes came from another issue
27
28
 
28
29
  ## Out of scope
29
30
 
@@ -21,7 +21,7 @@ Read `${CLAUDE_SKILL_DIR}/../plan-intake/references/intake.md` before writing an
21
21
  - If the named folder has no unread item, stop: `❌ Every item in <slug> carries an answer. Nothing to ask.`
22
22
  - Never fill a slot the operator did not answer. An abandoned batch leaves every unreached item unread, which is what the empty slot already means.
23
23
  - Never infer an answer from the conversation having happened. A selection reaches the file through the verb or not at all.
24
- - Do not promote an item, edit a verdict, or write outside the answer slots. Promoting runs through `task-board` after the answers land.
24
+ - Do not promote an item, edit a verdict, or write outside the answer slots, bar the close Step 5 offers on an explicit yes. Promoting runs through `task-board` after the answers land.
25
25
 
26
26
  ## Step 1: pick the folder
27
27
 
@@ -72,7 +72,37 @@ canon intake answer <slug> --cluster <file> --set <label>=<answer> --set <label>
72
72
 
73
73
  The verb refuses the whole batch when any item in it already carries an answer, since a filled slot is a decision already made. On that refusal, drop the named item and send the rest.
74
74
 
75
- ## Step 5: continue or stop
75
+ ## Step 5: offer to close declined issues
76
+
77
+ Run this after Step 4 lands a cluster, over the items that batch just answered. Closing is a public write, so each close waits on a yes given for that issue.
78
+
79
+ An item qualifies when both hold:
80
+
81
+ - The answer's first word is `decline`. Whatever follows it, as in `decline, but ask frameboard first`, stays on the item and still qualifies.
82
+ - The title ends in `(#NNN)`. Read that trailing number alone, so a title naming two issues, or a number sitting in prose, gets no offer.
83
+
84
+ Skip the whole step and say why when `gh auth status` fails or `gh` is absent. The answers already landed stay.
85
+
86
+ For each qualifying item:
87
+
88
+ 1. Run `gh issue view <NNN> --json state --jq .state`. When it reads `CLOSED`, name the issue as already closed and offer nothing.
89
+ 2. Draft the comment as one or two sentences condensed from the item's `problem` and `fix`, giving the reason and a pointer to what covers it.
90
+ 3. Run the scan in `${CLAUDE_SKILL_DIR}/../git-pr/references/publish.md` against the draft, since nothing else checks text on its way to the remote.
91
+ 4. Show the draft and ask for a yes or no on that issue in the same turn, through the question surface Step 3 used.
92
+ 5. On a yes, close with the draft passed through a quoted heredoc. On a no, or no answer, leave the issue open.
93
+
94
+ ```bash
95
+ gh issue close <NNN> --reason "not planned" --comment "$(cat <<'EOF'
96
+ <comment>
97
+ EOF
98
+ )"
99
+ ```
100
+
101
+ The quoted heredoc matters because `problem` and `fix` carry file paths in code spans. Inside a double-quoted argument the shell would run each backtick pair as a command and post a comment with the paths missing.
102
+
103
+ Never carry a yes from one issue or one turn to another.
104
+
105
+ ## Step 6: continue or stop
76
106
 
77
107
  After each cluster, state how many clusters still carry unread items and continue to the next. Stop when the operator says to stop, and report what is left rather than pressing on.
78
108
 
@@ -85,10 +115,13 @@ Report per cluster as it lands, then close with the folder's state:
85
115
  ```plaintext
86
116
  ✅ <N> answered in <file>
87
117
  <label>. <answer>
118
+ 🔒 Closed #<NNN>, or left open: #<NNN>
88
119
 
89
120
  📋 <N> cluster(s) still carrying unread items in <slug>
90
121
  ```
91
122
 
123
+ Omit the 🔒 line for a cluster that offered no close.
124
+
92
125
  Close the run with the path so the reader can open what changed:
93
126
 
94
127
  ```plaintext
@@ -26,7 +26,7 @@ A pass stops rather than repeat a verdict that already covers the head.
26
26
  - An open verdict at the head stops the same way, unless a `## Review response` or `## Post-review findings` comment is newer, the launch names an address, or it drove no box, as `post.md` defines
27
27
  - The hosted preview was not built from the head: stop and post nothing, naming `built`, `tip`, and `canon pr preview <number> --json` for the dispatcher
28
28
  - Post, then run `canon pr lift <number>` on a close. Never merge or ready the PR by hand, and edit the evidence comment only through `canon pr tick`.
29
- - A push after a close owes a re-drive only when it changed a painted path, since the lift carries the close across the rest.
29
+ - A push after a close owes a re-drive unless every path it changed is inert, the list `references/tick.md` gives, since the lift carries the close across those alone.
30
30
 
31
31
  ## Step 1: resolve the pull request
32
32
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Tick the passed boxes
3
- description: Step 8 of review-ui, which ticks each box that passed through canon pr tick once the verdict has posted, and reports a stale head instead of retrying
3
+ description: Step 8 of review-ui, which ticks each box that passed through canon pr tick once the verdict has posted, and reports an owed re-drive or a stale head instead of retrying
4
4
  ---
5
5
 
6
6
  # Tick the passed boxes
@@ -15,14 +15,26 @@ canon pr tick <number> --boxes <n,...> --head <head> --json
15
15
 
16
16
  Number the boxes off the `boxes` field Step 2 kept. Leave a box that failed, needs eyes, or was not driven empty.
17
17
 
18
- The verb refuses a taste box and a head that is no longer the remote tip, writing nothing in either case. Branch on the record's `reason`, which reads `ticked` on success.
18
+ ## When the tip moved
19
+
20
+ A push that landed while this pass drove carries the ticks to the tip when it changed only paths no box observes. The record then reads `ticked` with the head it came from in `carriedFrom`.
21
+
22
+ - Tests, stories, snapshots, evidence captures, and `canon/design/` carry.
23
+ - Markdown carries only in the root and under `canon/`, `.claude/`, `.github/`, and `docs/`.
24
+ - Markdown anywhere else, such as a page a stack renders, is driven.
25
+
26
+ The verb refuses a taste box and a head the tip moved past in any other way, writing nothing in either case. Branch on the record's `reason`.
19
27
 
20
28
  - `count-mismatch`: nothing was written. With `ticked` below `passed`, re-run once with every passed box. With it above, a box ticked earlier has failed, so report it on `Ticks:` for the operator.
21
- - `stale-head`: a push landed while this pass drove. Report it on the `Ticks:` line of Step 10 and never retry at the new tip, since the boxes were driven at the old one.
29
+ - `redrive-owed`: a push since `<head>` changed a path a box can observe. Report `driven`, `head`, and `drivenPaths` on the `Ticks:` line of Step 10, and never retry at the tip.
30
+ - `stale-head`: the driven head was rewritten away, by a force-push or a rebase, or the gap could not be read. Report it on the `Ticks:` line and never retry at the new tip.
22
31
  - `body-refused`: the comment is ticked and the body is not. Retry the same call once, then report the partial result on the `Ticks:` line rather than claiming both.
23
32
  - Any other refusal: report the `reason` on the `Ticks:` line and tick nothing further.
24
- - A binary whose `canon pr` lacks `tick` predates the verb. Report that on the `Ticks:` line and tick nothing.
25
33
 
26
- The record's `countCheck` reads `matched` when the verdict's passed count agreed with the ticks, and `unchecked` when the verdict did not cover the head or carried no count line. Report an `unchecked` on the `Ticks:` line.
34
+ A binary whose `canon pr` lacks `tick` predates the verb. Report that on the `Ticks:` line and tick nothing.
35
+
36
+ ## What a tick reports
37
+
38
+ The record's `countCheck` reads `matched` when the verdict's passed count agreed with the ticks, and `unchecked` when the verdict covered neither the tip nor the head a carry came from, or carried no count line. Report an `unchecked` on the `Ticks:` line.
27
39
 
28
40
  The same call also writes the one UI pass line in the body's `## Testing`, such as `UI pass, 17 of 17 boxes passed at <sha>, 1 taste box left to the operator`, so the operator sees the result without opening the evidence comment. Never edit that line by hand.
@@ -86,6 +86,7 @@ The readout is the text `Created cloud session: <title>`, unanchored since a ter
86
86
  - On a failed create, report the typescript's last lines and fall back to the human-launch line in `orchestrator-dispatch.md`
87
87
  - The wrapper is a stopgap any change to the interactive create screen breaks, which is why the readout fails loudly instead of guessing
88
88
  - Record `session` beside the row's branch in this pass's launched record, since that and the pull request list are the only readings that know it exists
89
+ - Name it on the row's task with `canon tasks session "$session" <task> --name cloud-<project>-<slug> --json`, since a cloud worker has no board and the roster never lists it
89
90
  - Report the dispatch as the local shape does, naming the row, the plan, the repository, and the session id in place of a session name
90
91
 
91
92
  ## Recognize the pull request
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Orchestrator launch runbook
3
- description: The launch every dispatch kind shares, being the build, cloud build, review-address, planning, and talk templates, the session name and controller id each carries, why the command sits at position zero, and what the brief may carry
3
+ description: The launch every dispatch kind shares, being the build, cloud build, review-address, and planning templates with a pointer to the talk variant, the session name and controller id each carries, why the command sits at position zero, and what the brief may carry
4
4
  ---
5
5
 
6
6
  Run this once `orchestrator-dispatch.md` has cleared a row, or from its planning or review-address shape below when that runbook's checks do not bind. The model each template names comes from `## Pick the model` in that runbook.
@@ -79,6 +79,10 @@ The prompt carries pointers and standing context, and stops there. The branch an
79
79
 
80
80
  Report the dispatch as loudly as the human-launch line it replaces, naming the branch, the model, the task, and the session name, so a person reading the transcript can follow what fired without watching it happen.
81
81
 
82
+ ### Record the worker on its task
83
+
84
+ Once a build or review-address launch returns, run `canon tasks session <worker-id> <task> --json` with the sessionId the launch output carries. On `no-name`, the worker already exited, so rerun with `--name` and the name passed to `-n`.
85
+
82
86
  ## Dispatch to a cloud worker
83
87
 
84
88
  Read `${CLAUDE_SKILL_DIR}/references/orchestrator-cloud-launch.md` when `orchestrator-dispatch.md` picked cloud for the row. It holds the prompt that carries the plan as text and announces over GitHub, the create under `script`, and the readout that refuses to record a missing session id.
@@ -137,25 +141,7 @@ Hold all planning of a row any talk session has said by message it is planning,
137
141
 
138
142
  ## Dispatch a talk session
139
143
 
140
- A planner or worker the operator will talk with takes the same launch as its cold shape above, with a `-talk` suffix on its name and one sentence added to its brief. A cold session works alone and reports back, and a talk session stays open after its plan or pull request lands so the operator can keep shaping the row.
141
-
142
- ```bash
143
- claude --bg --model <model> -n "planner-<project>-<slug>-talk" "Run /canon:role-planner, then /canon:plan-feature <task>. This is a talk session: the operator will talk with you here, so run your role's talk clause, and message your controller when the operator ends the conversation. Your controller is the session whose sessionId is <dispatcher-id>. ..."
144
- claude --bg --model <model> -n "worker-<project>-<slug>-talk" "/canon:auto-ship <plan>
145
- This is a talk session: the operator will talk with you here, so run your role's talk clause, and message your controller when the operator ends the conversation. Your controller is the session whose sessionId is <dispatcher-id>. ..."
146
- ```
147
-
148
- The ellipsis stands for the rest of the cold template's message clauses, unchanged. The build variant keeps `/canon:auto-ship <plan>` at position zero with nothing before it, per the rule above.
149
-
150
- - Keep the role prefix and add the suffix, since `orchestrator-handoff.md` separates dispatched sessions by `worker-` and `planner-` and a new prefix reads as a hand launch
151
- - A talk session is retired only on the operator's word, stated once in `orchestrator-handoff.md`, so do not restate it in a launch report
152
- - Reviewers get no talk shape, since a reviewer's pass holds no operator dialogue
153
-
154
- ### Recording the talk session
155
-
156
- Read the session's `sessionId` off the launch output, carry it in this session's own context, and write it into the map's `## Talk sessions held open` at the next handoff. The client can rename a session and drop the suffix, so the id is the durable marker and the suffix is only for display.
157
-
158
- The map exists only once the handoff writes it, so this session's own launches, read with the prior map, are what the first wind-down reads talk sessions from.
144
+ Read `${CLAUDE_SKILL_DIR}/references/orchestrator-talk-launch.md` when the operator will talk with a planner or worker rather than leave it to report back. It states the `-talk` name suffix, the one sentence the brief adds, and where the session's id is kept for the handoff.
159
145
 
160
146
  ## Hand launch
161
147
 
@@ -21,6 +21,16 @@ claude --bg --model <model> -n "reviewer-<project>-<number>" "Run /canon:role-re
21
21
  - Append the cross-branch facts after the controller clause, per `### What the brief may carry` in `orchestrator-launch.md`
22
22
  - Check `canon sessions list --json` for a live `reviewer-<project>-<number>` before launching, and message it instead when one holds the pull request
23
23
 
24
+ ### Record the reviewer on the task
25
+
26
+ A reviewer writes no task file, so this session names it on the task the pull request closes, through the sessionId the launch output carries:
27
+
28
+ ```bash
29
+ canon tasks session <reviewer-id> <task> --json
30
+ ```
31
+
32
+ Record the UI reviewer below the same way after its launch. A `no-name` refusal means the reviewer exited before the read, so rerun with `--name` and the name the launch passed to `-n`.
33
+
24
34
  ### The channel clauses
25
35
 
26
36
  The channel clauses restate what `role-reviewer` holds, so a reviewer whose plugin cache predates that body still sends the dispatch itself. The worker reads the reviewer's sessionId off that message and sends its re-review request back to it, since a reviewer holds the trunk branch and no branch lookup finds it.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Orchestrator talk launch
3
+ description: The talk variant of the planning and build launches, being the name suffix, the one added brief sentence, and where the talk session's id is recorded for the handoff
4
+ ---
5
+
6
+ Run this when the operator wants to talk with a planner or worker rather than leave it to report back. The rules every launch shares stay in `orchestrator-launch.md`.
7
+
8
+ # Talk launch
9
+
10
+ ## The launch
11
+
12
+ A planner or worker the operator will talk with takes the same launch as its cold shape in `orchestrator-launch.md`, with a `-talk` suffix on its name and one sentence added to its brief. A cold session works alone and reports back, and a talk session stays open after its plan or pull request lands so the operator can keep shaping the row.
13
+
14
+ ```bash
15
+ claude --bg --model <model> -n "planner-<project>-<slug>-talk" "Run /canon:role-planner, then /canon:plan-feature <task>. This is a talk session: the operator will talk with you here, so run your role's talk clause, and message your controller when the operator ends the conversation. Your controller is the session whose sessionId is <dispatcher-id>. ..."
16
+ claude --bg --model <model> -n "worker-<project>-<slug>-talk" "/canon:auto-ship <plan>
17
+ This is a talk session: the operator will talk with you here, so run your role's talk clause, and message your controller when the operator ends the conversation. Your controller is the session whose sessionId is <dispatcher-id>. ..."
18
+ ```
19
+
20
+ The ellipsis stands for the rest of the cold template's message clauses, unchanged. The build variant keeps `/canon:auto-ship <plan>` at position zero with nothing before it, per `### Position zero` in `orchestrator-launch.md`.
21
+
22
+ - Keep the role prefix and add the suffix, since `orchestrator-handoff.md` separates dispatched sessions by `worker-` and `planner-` and a new prefix reads as a hand launch
23
+ - A talk session is retired only on the operator's word, stated once in `orchestrator-handoff.md`, so do not restate it in a launch report
24
+ - Reviewers get no talk shape, since a reviewer's pass holds no operator dialogue
25
+
26
+ ## Recording the talk session
27
+
28
+ Read the session's `sessionId` off the launch output, carry it in this session's own context, and write it into the map's `## Talk sessions held open` at the next handoff. The client can rename a session and drop the suffix, so the id is the durable marker and the suffix is only for display.
29
+
30
+ The map exists only once the handoff writes it, so this session's own launches, read with the prior map, are what the first wind-down reads talk sessions from.
@@ -200,7 +200,7 @@ export const scopeFallback = (payload: Payload, now: number): Scope => {
200
200
 
201
201
  /**
202
202
  * Reads the UI family off the verb's record, so the poll and the lift agree on
203
- * which head a close covers, a close carried across unpainted pushes included.
203
+ * which head a close covers, a close carried across inert pushes included.
204
204
  * A record carrying no `ui` object is a refusal or a CLI predating the field,
205
205
  * and reads as unread, which leaves the baseline's value standing.
206
206
  */
@@ -74,6 +74,10 @@ Run `canon pr lift <number> --json` after posting `## Review closed` or `## UI r
74
74
 
75
75
  Skip that message when the reason names the other review as open or missing, since the other reviewer's close runs the verb again.
76
76
 
77
+ ### A re-drive owed
78
+
79
+ Message the controller when `canon pr tick` refuses `redrive-owed`, naming the driven commit, the tip in `head`, and the `drivenPaths`. A push the boxes can observe landed after the pass drove, so the next UI pass is a dispatch only the controller orders.
80
+
77
81
  ### Every other message
78
82
 
79
83
  The rest of the channel is what this session never sends.
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: Task record lines reference
3
+ description: The lines a verb writes on a task below its origin lines, being the Pull request line that lets a merge close its task, the Pending branch marker that holds that merge back, and the Sessions line naming who worked it
4
+ ---
5
+
6
+ # Task record lines reference
7
+
8
+ Applies to the lines a task carries under its origin lines that no session writes by hand. Each one has a `canon tasks` verb as its writer, and this file fixes what each line means and who calls that verb.
9
+
10
+ This is an attribute standard. It governs lines inside a task file rather than a file of its own, so it carries no template.
11
+
12
+ ## Scope
13
+
14
+ Governs the `Pull request:`, `Pending branch:`, and `Sessions:` lines.
15
+
16
+ Does not govern:
17
+
18
+ - The task file's layout, filename, format, origin lines, and archiving: `tasks.md`
19
+ - The `Declined:` line: `tasks-decline.md`
20
+
21
+ ## The `Pull request:` line
22
+
23
+ `Pull request:` lists every pull request that shipped part of the task as bare numbers, oldest first and comma-separated, as in `Pull request: #NNN, #NNN`. It is not an origin, so a task without one is well-formed, and `git-pr` appends to it when a pull request opens.
24
+
25
+ The line lets a merge close its own task. `canon tasks archive --pull-request <n>` finds the task by any listed number and archives it only on the last.
26
+
27
+ - One pull request, one task. Two tasks naming the same number refuse to archive rather than both moving.
28
+ - `Pending branch: <branch>` holds that merge back while a branch that ticked an outcome waits on its own number
29
+ - `canon tasks outcome` writes that line, skipping it when the branch's pull request is already listed, and `canon tasks pull-request` clears it
30
+
31
+ ## The `Sessions:` line
32
+
33
+ `Sessions:` names each session that built or reviewed the task, by roster name and full sessionId, as in `Sessions: <name> (<sessionId>), <name> (<sessionId>)`. A retro or a regression hunt reaches each transcript through `canon sessions export <sessionId>`, which takes the full id alone. It is not an origin either.
34
+
35
+ - `canon tasks session <sessionId> <task>` writes it, or `--plan <slug>` in place of the stem, so it is never hand-edited
36
+ - The verb reads the name off the live roster. A session that already exited takes `--name`, and so does a cloud session's `session_` id, since the roster never lists one.
37
+ - The id is the key. A repeat call for a listed id keeps the name first recorded, so two callers recording one worker write it once.
38
+ - The dispatcher records each worker and reviewer it launches, and `git-pr` records the session opening the pull request, which covers a hand launch
39
+
40
+ The line sits below the origin, `Pull request:`, and `Pending branch:` lines. `canon tasks validate` reports a line that does not parse as name and id pairs as `sessions-misshapen`.
@@ -29,6 +29,7 @@ Governs the task files under `.canon/tasks/`: folder layout, filenames, frontmat
29
29
  Does not govern:
30
30
 
31
31
  - **`tasks-decline.md`**: the declined folder, the `Declined:` line, and what declining does to a plan and a board row
32
+ - **`tasks-record-lines.md`**: the `Pull request:`, `Pending branch:`, and `Sessions:` lines a verb writes below the origin lines
32
33
  - **`board.md`**: execution order, readiness groups, the backlog, and the generated index beside the tasks
33
34
  - **`plan.md`**: the plan file a task cites, its sections, and its answer contract
34
35
  - **`versioning.md`**: phase-label format and which surfaces a label may appear on
@@ -112,6 +113,7 @@ Groundwork: [<slug>](../groundwork/<slug>/)
112
113
  Intake: [<slug>](../intake/<slug>/)
113
114
  Issue: #NNN
114
115
  Pull request: #NNN
116
+ Sessions: <name> (<sessionId>)
115
117
 
116
118
  Why this task exists and what it depends on.
117
119
 
@@ -165,15 +167,9 @@ Three rules cover the line itself:
165
167
  - Use `Intake:` rather than `Groundwork:` for an intake pass, so the line records which kind of pass produced the task
166
168
  - `Intake:` names the folder rather than an item, and item numbers belong in the task's `## Findings`
167
169
 
168
- ### The `Pull request:` line
170
+ ### The lines a verb writes
169
171
 
170
- `Pull request:` lists every pull request that shipped part of the task as bare numbers, oldest first and comma-separated, as in `Pull request: #NNN, #NNN`. It is not an origin, so a task without one is well-formed, and `git-pr` appends to it when a pull request opens.
171
-
172
- The line lets a merge close its own task. `canon tasks archive --pull-request <n>` finds the task by any listed number and archives it only on the last.
173
-
174
- - One pull request, one task. Two tasks naming the same number refuse to archive rather than both moving.
175
- - `Pending branch: <branch>` holds that merge back while a branch that ticked an outcome waits on its own number
176
- - `canon tasks outcome` writes that line, skipping it when the branch's pull request is already listed, and `canon tasks pull-request` clears it
172
+ `Pull request:` and `Sessions:` are not origins, so a task without them is well-formed. A verb writes each, and `${CLAUDE_SKILL_DIR}/references/tasks-record-lines.md` fixes what they mean and who calls the verb.
177
173
 
178
174
  ## What goes in
179
175
 
@@ -30,13 +30,37 @@ The mode holds only while the caller names it. An ordinary run that finds a pull
30
30
 
31
31
  ## Choose the beats
32
32
 
33
- Take a beat from each checklist box that asks the reviewer to click, fill, or scroll something, in checklist order, up to three beats.
33
+ Film where motion is the claim. Filter the boxes first, then spend the three beats so each feature area shows its change once before any area shows two.
34
+
35
+ ### Filter the boxes
36
+
37
+ A box is filmable when it asks the reviewer to click, fill, or scroll something and its result is a visible change.
34
38
 
35
39
  - Skip a box ending in `(taste)`, since no driver judges it
36
40
  - Skip a box that asks only to look at a state the page opens on, since a screenshot already shows it
41
+ - Skip a negative box, one asserting that something does not happen, such as "no drop line draws", since footage of nothing shows nothing
42
+
43
+ Read the box's text for the negative. A box asserting an unchanged result, such as "the board goes to all projects as before", is not negative, since the action still produces a visible change. Rank it behind a box in the same group that names new behavior.
44
+
45
+ ### Order the beats
46
+
47
+ A group is the bold feature-area label above at most five boxes, as `ui-checklist` defines it.
48
+
49
+ 1. Take one beat per group in checklist order, from the highest-ranked filmable box of each group, up to three beats.
50
+ 2. When beats remain after every group has one, take a second pass in checklist order over the boxes left, highest-ranked first within each group, up to three beats in all.
51
+
52
+ A checklist with one group gives up to three beats from it. A checklist with four groups gives three beats, one from each of the first three.
53
+
54
+ ### Plan the beats
55
+
56
+ Write one plan entry for each beat chosen, then skip when none was.
57
+
37
58
  - Name each beat after its box's text, cut to the action
38
59
  - Write the plan per `${CLAUDE_SKILL_DIR}/references/take-craft.md`, finding each target on the served page rather than guessing it
39
- - With no box left, skip as `no-interactive-box` and film nothing
60
+ - With no interactive box, skip as `no-interactive-box` and film nothing
61
+ - With interactive boxes that the filter removed, whether negative, static, or marked `(taste)`, skip as `no-visible-change` and film nothing
62
+
63
+ The negative test is a judgment over the box's text. A misread box costs a weaker reel and never a wrong pass, since the still check below guards the footage and the UI reviewer drives every box regardless.
40
64
 
41
65
  Keep the whole reel at or under 12 seconds. Each beat opens and closes on a 0.6 s hold, so give each action's `wait` only what its result needs to settle.
42
66
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.70.0",
4
+ "version": "5.72.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {