@erclx/canon 5.75.0 → 5.76.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.76.0](https://github.com/erclx/canon/compare/v5.75.0...v5.76.0) (2026-10-31)
4
+
5
+
6
+ ### Features
7
+
8
+ * **capture:** render light and dark social cards in design-identity ([#2861](https://github.com/erclx/canon/issues/2861)) ([de990f5](https://github.com/erclx/canon/commit/de990f543f7970c04548aa4f793766db5d87b68a))
9
+ * **claude:** allow the exact lease push forms in seeded settings ([#2859](https://github.com/erclx/canon/issues/2859)) ([2f5a6e8](https://github.com/erclx/canon/commit/2f5a6e801e51cefef98f46a7b33b1cd6df7717a3))
10
+ * **orchestrate:** run the review poll in the background until movement ([#2860](https://github.com/erclx/canon/issues/2860)) ([28101da](https://github.com/erclx/canon/commit/28101da96a8ad19e8ded474fad0de15344f4ac41))
11
+ * **skills:** add issue-triage for a repository's open GitHub issues ([#2856](https://github.com/erclx/canon/issues/2856)) ([910372f](https://github.com/erclx/canon/commit/910372fe7026b9e3f5e2b17648a86dd685081acf))
12
+ * **skills:** offer a follow-up on a duplicate issue before filing ([#2857](https://github.com/erclx/canon/issues/2857)) ([ddcbf69](https://github.com/erclx/canon/commit/ddcbf69d5c7cb60242b3d7b0bfb23ffbd4fd858e))
13
+
3
14
  ## [5.75.0](https://github.com/erclx/canon/compare/v5.74.0...v5.75.0) (2026-10-29)
4
15
 
5
16
 
@@ -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.75.0",
4
+ "version": "5.76.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -17,7 +17,7 @@ A session building this alone restates the render-and-pick loop from scratch, wh
17
17
  - Every arm is drafted inside the card frame, so one pick settles the mark's shape and its composition together
18
18
  - The render, hand-off, pick, and loop follow `design-pick` rather than a copy of it
19
19
  - The final mark lands as its own SVG source, not only baked into a raster
20
- - Every final size and the card render in one capture call, each landing at its literal target size
20
+ - Every final icon size renders in one capture call and the card renders once per theme, each landing at its literal target size
21
21
 
22
22
  ## Must not
23
23
 
@@ -65,6 +65,7 @@ Follow `${CLAUDE_SKILL_DIR}/../design-pick/SKILL.md` Step 1, with the decision f
65
65
  - Draft each arm inside the 1200x630 card frame, mark and type together, and add it with `frameboard frame add <page>/arm-<id> --width 1200 --height 630`, so one pick settles shape and composition
66
66
  - Arm 0 is the current mark when the Step 3 folder already holds a logo file (`favicon.svg`, `favicon.ico`, `logo.svg`, or similar). Arms start at 1 otherwise.
67
67
  - Structure every arm as a `.mark` element, the inline SVG alone, nested inside a `.card` element, the full composition, so Step 6 can address either
68
+ - Give every arm a light and a dark set keyed on the `data-theme` attribute, as `design-pick` themes any arm. Step 7 captures the picked card once per theme.
68
69
 
69
70
  ## Step 5: draft, render, pick, and loop
70
71
 
@@ -94,6 +95,11 @@ The card leaves this batch and takes Step 7 instead. An icon is the mark alone a
94
95
 
95
96
  A card captured from a page this run deletes leaves a PNG and nothing that produced it, so renaming the project means another pick loop to move one string. Write the composition into a route the project keeps, and capture it through a running server so it reads the real stylesheet, tokens, and fonts.
96
97
 
98
+ Both paths write two cards, each captured with `--color-scheme` set to its theme. An `og:image` tag names one URL that every viewer sees whatever their own theme, so only one is wired:
99
+
100
+ - `og-image.png`, the light card, goes into `og:image`, keeping the name an existing tag already points at
101
+ - `og-image-dark.png` stays beside it, so a later switch is a meta tag edit rather than another pick loop
102
+
97
103
  This is Astro-shaped for now, because Astro is the one web stack whose config gives a route a structural build exclusion. A project already holding the card route scaffold takes the route path, and everything else takes the page path. Say which one decided, since the two leave the project different things.
98
104
 
99
105
  ### The route path
@@ -102,17 +108,18 @@ Read `${CLAUDE_SKILL_DIR}/references/card-route.md` on reaching this path, for t
102
108
 
103
109
  ### The page path
104
110
 
105
- Where the project holds no route to write into, write `og-image.html` into `<dest>/render/` with its `.render` wrapper sized 600x315, then capture that one page:
111
+ Where the project holds no route to write into, write the picked composition into `<dest>/card/` twice, as `og-image.html` and `og-image-dark.html`, each with its `.render` wrapper sized 600x315. A file source names its PNG after the page, so the two names are what land the two cards. Capture each page in its own theme:
106
112
 
107
113
  ```bash
108
- canon capture <dest>/render/og-image.html --selector .render --out <write-folder>
114
+ canon capture <dest>/card/og-image.html --selector .render --color-scheme light --out <write-folder>
115
+ canon capture <dest>/card/og-image-dark.html --selector .render --color-scheme dark --out <write-folder>
109
116
  ```
110
117
 
111
- It takes its own call because Step 6's batch is already captured by the time this step picks a path. Say that the card shipped as a PNG with no source, and that the project gains one when it grows a route to hold the composition.
118
+ These take their own calls because Step 6's batch is already captured by the time this step picks a path, and one call renders one scheme. Say that the card shipped as a PNG with no source, and that the project gains one when it grows a route to hold the composition.
112
119
 
113
120
  ### Closing the scratch folder and the board
114
121
 
115
- Delete `<dest>` once both captures land, then close the board through `design-pick` Step 6 items 3 to 8, which archive every arm and delete the pages the run added. The route is not a leftover variant, since it holds the arm the operator picked.
122
+ Delete `<dest>` once the icon and card captures land, then close the board through `design-pick` Step 6 items 3 to 8, which archive every arm and delete the pages the run added. The route is not a leftover variant, since it holds the arm the operator picked.
116
123
 
117
124
  A project whose card was generated before this route existed keeps its PNG. Say so rather than regenerating it, since the pick loop spends attention on a file nobody complained about, and the next real edit picks up the route.
118
125
 
@@ -123,9 +130,11 @@ A project whose card was generated before this route existed keeps its PNG. Say
123
130
  📝 Wrote <write-folder>/icon-16x16.png (16x16)
124
131
  📝 Wrote card-src/pages/og-card.astro (route path only)
125
132
  📝 Wrote <write-folder>/og-image.png (1200x630)
133
+ 📝 Wrote <write-folder>/og-image-dark.png (1200x630)
126
134
 
127
135
  Write folder: <tier that decided>. Move the files if this project's own convention differs.
128
136
  Card source: <the route, captured through the card server|a scratch page, since this project holds no card route>.
137
+ Wire og-image.png into og:image. The dark card stays beside it for a later switch.
129
138
  ```
130
139
 
131
140
  ## What this delegates
@@ -29,16 +29,23 @@ Never run that sync to obtain the route, since it writes the stack's configs bes
29
29
 
30
30
  ## Writing and capturing the route
31
31
 
32
- 1. Write the picked arm's `.card` markup into the route's `.card` element, leaving the marker, the stylesheet import, and the surrounding document alone.
32
+ 1. Write the arm's `.card` markup into the route's `.card` and its `[data-theme]` rules into the route's `<style>`, leaving the rest of the route alone.
33
33
  2. Keep the element at 600x315, half the 1200x630 target, since the capture's 2x scale factor is fixed and a 1200x630 element captures at 2400x1260.
34
34
  3. Start the card server in the background with `bun run card:dev` and read the port off its own output.
35
- 4. Capture the route, naming the destination PNG rather than a directory, which is what a URL source takes for `--out`.
36
- 5. Stop the server. Report the dimensions `canon capture` printed, confirming 1200x630.
35
+ 4. Capture the route twice while the server is up, once per theme, naming each destination PNG rather than a directory, which is what a URL source takes for `--out`.
36
+ 5. Stop the server. Report the dimensions `canon capture` printed for both cards, confirming 1200x630.
37
37
 
38
38
  ```bash
39
- canon capture http://localhost:<port>/og-card --selector .card --out <write-folder>/og-image.png
39
+ canon capture http://localhost:<port>/og-card --selector .card --color-scheme light --out <write-folder>/og-image.png
40
+ canon capture http://localhost:<port>/og-card --selector .card --color-scheme dark --out <write-folder>/og-image-dark.png
40
41
  ```
41
42
 
43
+ The two captures differ only where something keys a second theme:
44
+
45
+ - The scaffold's `.card` reads two color properties a stylesheet may set for one theme only, so the arm's rules from step 1 give the dark capture its dark set
46
+ - `--color-scheme` writes the theme onto the route's root after it loads, so rules keyed on `data-theme`, a class, or the media query all answer to it
47
+ - With no second set in the arm or the stylesheet, the PNGs match. Compare the image digests in their `.stamp` files, and on a match say the project got one card twice.
48
+
42
49
  ### Port and font refusals
43
50
 
44
51
  The card server runs under a strict port, so it refuses rather than walking to the next free one. Where something already holds that port, set `WORKTREE_PORT_OFFSET` by hand and start it again.
@@ -17,16 +17,18 @@ Prose bans hold on files the hook watches and not on text leaving through `gh`,
17
17
  - Each issue carries exactly one label mapped from its type, asked once when the type is ambiguous between a bug and a task
18
18
  - A remote and an authenticated `gh` are confirmed before any body is composed
19
19
  - The body passes through a file that is removed afterward
20
+ - Open issues are searched for the same defect before a body is composed, and a match is offered as a comment in place of a new issue
20
21
 
21
22
  ## Must not
22
23
 
23
24
  - Invent an issue from a thin session
24
25
  - File anywhere other than the current origin
25
26
  - Open a pull request
26
- - Emit anything after the result line
27
+ - Emit anything after the result lines
28
+ - Comment on an existing issue without a yes given for that issue
27
29
 
28
30
  ## Out of scope
29
31
 
30
32
  - Reporting a toolkit defect from a target project, which `canon-feedback` owns
31
- - Triaging the toolkit's feedback issues, which its maintainers do in its own repository
33
+ - Triaging a repository's open issues, which `issue-triage` owns
32
34
  - Opening a pull request, which `git-pr` owns
@@ -27,7 +27,34 @@ Then gather repo context in parallel:
27
27
  - If nothing in session context describes a concrete bug or task, stop: `❌ No issue in session context. Describe the bug or task, then re-invoke.`
28
28
  - If `git remote get-url origin` returns `NO_REMOTE`, stop: `❌ No GitHub remote. gh issue create needs an origin.`
29
29
  - If `gh auth status` returns `NO_AUTH`, stop: `❌ gh is not authenticated. Run gh auth login.`
30
- - If the type is ambiguous between a bug and a task, ask one line before formatting.
30
+ - If the type is ambiguous between a bug and a task, ask one line before formatting, after the duplicate question below.
31
+
32
+ ## Duplicate check
33
+
34
+ Search the open issues before composing anything, since a duplicate costs the repository's triage a second item and a second answer.
35
+
36
+ ```bash
37
+ gh issue list --state open --search "<keywords> in:title,body" --limit 5 --json number,title,url
38
+ ```
39
+
40
+ - Take keywords from the defect's distinctive terms, such as a command or a path, and quote one holding a colon or a space
41
+ - Keep a candidate only when it describes the same defect, reading its body with `gh issue view <NNN> --json body` when the title leaves it open
42
+ - With none kept, go to the preview and file a new issue
43
+ - With one or more kept, ask once whether to add the findings to one of them as a comment or file a new issue, naming each by number and title
44
+ - With no one to answer, as in a headless run, file nothing and name each kept candidate, since a new issue there is the duplicate this check exists to stop
45
+
46
+ ### Comment on the match
47
+
48
+ On a yes for an issue, write the comment in the `### Follow-up` shape `${CLAUDE_SKILL_DIR}/references/issue.md` gives, run the pre-publish scan below over it, then post it in place of the final command. Post nothing on any issue the answer did not name.
49
+
50
+ ```bash
51
+ mkdir -p .canon/tmp/issue
52
+ cat <<'BODY' > .canon/tmp/issue/comment.md
53
+ <comment following the Follow-up section>
54
+ BODY
55
+ gh api -X POST 'repos/{owner}/{repo}/issues/<NNN>/comments' -F body=@.canon/tmp/issue/comment.md --jq .html_url
56
+ rm -rf .canon/tmp/issue
57
+ ```
31
58
 
32
59
  ## Response format
33
60
 
@@ -63,6 +90,8 @@ rm -rf .canon/tmp/issue
63
90
 
64
91
  ## After execution
65
92
 
66
- Respond with exactly one line and add no other text:
93
+ Respond with the one line for what was posted:
67
94
 
68
- `✅ Issue: <url>`
95
+ - A new issue: `✅ Issue: <url>`
96
+ - A comment on a match: `✅ Commented: <url>`
97
+ - Nothing, with candidates kept and nobody to ask: `⏸️ Not filed. Possible duplicate:`, then one line per candidate as `#<NNN> <title>`
@@ -55,6 +55,13 @@ A bug or task body carries up to four `##` sections, in this order.
55
55
  - `## Context`: for a bug, repro steps or commands. For a task, the driving reason, or `none`.
56
56
  - `## Proposed` (optional): one line naming a fix or approach. Omit when open.
57
57
 
58
+ ### Follow-up
59
+
60
+ A comment on an open issue carries what the session adds and nothing the issue already says.
61
+
62
+ - `## Follow-up`: one line naming what is new, such as a second instance, a repro, or a narrowed cause.
63
+ - `## Details`: the observed behavior and the command or path that shows it.
64
+
58
65
  ### Feedback
59
66
 
60
67
  One `## Toolkit feedback` wrapper holds every field as a nested `###` heading. A flat `##` field is not recognized.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: issue-triage
3
+ description: Why a repository's open issues are filed as measured intake items rather than routed or fixed from the issue text, and why one pass is capped
4
+ ---
5
+
6
+ # Issue triage requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a repository's issue queue fills and nothing drains it into decisions. An issue reads as a request, so a session routes it straight to a fix from a paragraph and skips the scope argument.
11
+
12
+ Triage done by hand fails three ways. A self-contradicting report gets a guess instead of a question. The same issue gets filed twice because two passes cannot see the board carries it. A queue of hundreds files in one pass that no answering session can get through.
13
+
14
+ ## Must
15
+
16
+ - Read the queue through the `gh` CLI, and stop when it is absent, unauthenticated, or the checkout has no GitHub repository
17
+ - File one measured item per kept issue into a folder the CLI claimed, headed so an answer finds its issue, and name each skipped issue with its carrier
18
+ - Measure each claim in an issue's body and comments against the tree rather than carrying the issue's figures forward
19
+ - Cap one pass, oldest first, and name what was left over rather than reading a capped queue as drained
20
+ - Report an empty queue as nothing open rather than widening the label to find work
21
+
22
+ ## Must not
23
+
24
+ - Post to the remote, whether a comment, a close, or a label
25
+ - Implement a fix or write a plan
26
+ - Fill an answer slot, or read an empty one as agreement
27
+ - Write outside the folder it claimed
28
+ - Carry a step only one repository's queue needs, such as stripping a reporting project's specifics, since a caller wrapping this skill adds those around it
29
+
30
+ ## Out of scope
31
+
32
+ - Opening a new issue, which `git-issue` owns
33
+ - Answering the filed items, which `plan-intake-answer` owns
34
+ - Writing the plan a plan-worthy item needs, which `plan-feature` owns
35
+ - Pruning the task backlog, which `backlog-triage` owns
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: issue-triage
3
+ description: Files the open GitHub issues on the current repository into an intake folder as one measured item each, narrowed by an optional label, capped per pass, and skipping any issue the board already carries. Use when asked to "triage the issues", "work through the open issues", "file the GitHub issues", "triage the bug label", or "what issues are open". Do NOT use to open a new issue, which is `git-issue`, to answer the filed items, which is `plan-intake-answer`, or to triage the task backlog, which is `backlog-triage`.
4
+ metadata:
5
+ family: decide
6
+ ---
7
+
8
+ # Issue triage
9
+
10
+ An issue queue only grows while nothing reads it against the tree. This pass measures each open issue and files it as one item for the operator, who answers it before anything is built.
11
+
12
+ The record is an ordinary intake folder at `.canon/intake/<nn>-<slug>-triage/`. Read `${CLAUDE_SKILL_DIR}/../plan-intake/references/intake.md` before writing any file in it, since it holds the item format, the frontmatter, the index shape, and the answer contract this skill is bound by.
13
+
14
+ - Read GitHub issues and write only inside the claimed folder, posting no comment, close, or label to GitHub
15
+ - Measure each issue against this repository, since the repository it was filed on is the one its fix lands in
16
+
17
+ ## Arguments
18
+
19
+ Both are optional and come in either order.
20
+
21
+ - A label narrows the queue to issues carrying it. With none, every open issue is in the queue and the slug is `issue`.
22
+ - A label sets the slug too, lowercased with each run of other characters made one hyphen, so `good first issue` gives `good-first-issue`. Pass the raw label, quoted, to `--label`.
23
+ - A number sets the cap, the most issues one pass files. With none, the cap is 25, about what one answering session gets through.
24
+
25
+ ## Guards
26
+
27
+ - Resolve `.canon/tasks/` and `.canon/intake/` at the main worktree root, from `git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd` outside git
28
+ - If `gh` is not on PATH, stop: `❌ gh CLI not found. Install it to read the issue queue.`
29
+ - If `gh auth status` fails, stop: `❌ gh is not authenticated. Run gh auth login.`
30
+ - If `gh repo view --json nameWithOwner` fails, stop: `❌ No GitHub repository here. Run from a checkout with a GitHub remote.`
31
+ - If no open issue matches, stop: `✅ No open issues.`
32
+
33
+ ### Slots and records
34
+
35
+ Two more guards hold through every step.
36
+
37
+ - Never fill a `You:` slot, and never read an empty one as agreement. Answering runs through `plan-intake-answer` or the operator's own edit.
38
+ - Branch on each `canon` record's `ok` and `reason` rather than on the exit code, which a shell function wrapping `canon` can flatten to zero.
39
+
40
+ ## Step 1: list the queue
41
+
42
+ ```bash
43
+ gh issue list --state open --limit 1000 --search "sort:created-asc" --json number,title
44
+ ```
45
+
46
+ Add `--label "<label>"` when one was given, and name that label in the empty-queue stop. The list carries no bodies, so a large queue costs a line per issue rather than its whole text.
47
+
48
+ Sorting by creation keeps the oldest issues when the queue passes the limit. When the list returns 1000 rows, the queue may hold more, so Step 3 writes its left-over count as a floor.
49
+
50
+ ## Step 2: drop what the board already carries
51
+
52
+ An issue is carried when its number appears in a live or archived task's `Issue:` line under `.canon/tasks/`, or in an item heading of an existing `.canon/intake/` folder, written `(#NNN)`. Search both at the main root.
53
+
54
+ Set each carried issue aside with its carrier, a task path or a folder and item.
55
+
56
+ - Name every one in the overview, so a re-run while an earlier folder sits unanswered files nothing twice
57
+ - Read its comments with `gh issue view <NNN> --json comments`, and name their count and newest date beside its carrier, so a follow-up posted after the carrier was filed is seen
58
+ - File no item for a comment on a carried issue, since an item would file the issue twice against a live task
59
+
60
+ When every issue is carried, stop: `✅ Every open issue is already on the board.`
61
+
62
+ ## Step 3: apply the cap
63
+
64
+ Sort the remaining issues by number, oldest first, and keep the first issues up to the cap, so the reports that waited longest drain first. Count the issues past the cap as left over, and name them in the overview and the output.
65
+
66
+ - Give the count and the lowest number left over, so a drained queue and a capped one never read the same
67
+ - Write the count as `at least <n>` when Step 1 hit its limit
68
+
69
+ Then read each kept issue whole, since a follow-up posted as a comment is part of the report:
70
+
71
+ ```bash
72
+ gh issue view <NNN> --json number,title,body,labels,comments
73
+ ```
74
+
75
+ ## Step 4: claim the folder
76
+
77
+ Run `canon records ordinal intake <slug>-triage --claim --json` and take its `name`. The verb creates the folder in the same act, so two sessions opening at once never share one. Where the installed binary carries no such subcommand, report it and stop rather than picking an ordinal by hand.
78
+
79
+ ## Step 5: measure each issue
80
+
81
+ For each kept issue, measure its claim against the tree during this pass, never carrying a figure from the issue forward, since it states what was true when filed.
82
+
83
+ Measure each comment's claim the same way, whoever posted it, and fold it into the issue's one item rather than filing a second. When a comment contradicts the body, measure the later claim and name the comment as the source on `Problem:`.
84
+
85
+ - Grep for the construct its defect names and count the sites
86
+ - Check whether the behavior it asks for exists
87
+ - Search the log for a commit or pull request that shipped it under another task
88
+ - Run the issue's repro in a form that writes nothing when it is one command that works here, and say on `Problem:` what it showed
89
+ - Mark the item `not reproduced here` when the repro needs more, per `## Repro versus groundwork` in the intake standard
90
+
91
+ Classify each against this repository's own surfaces:
92
+
93
+ - **Direct fix.** A size, not an answer, so it suggests `plan` and `Fix:` says a short plan covers it. One surface, one file, no architectural choice. A typo, a stale reference, a one-line correction.
94
+ - **Plan-worthy.** Multiple files, a new module or command, a behavior change, or a cross-surface move.
95
+ - **Needs clarification.** The observed and expected behavior conflict or the surface is unnamed. The item asks the operator for the missing detail.
96
+
97
+ Name the commit the pass measured against in the overview.
98
+
99
+ ## Step 6: write the items
100
+
101
+ Write one item per issue in the intake item format, one cluster file per domain the fix touches, numbered in read order. Head each item `### N. <defect> (#NNN)` so an answer finds its issue.
102
+
103
+ - `Problem:` states what this pass measured, with the count, path, or commit behind it
104
+ - `Fix:` states the class from Step 5 and the change it names
105
+ - `Open:` reads `plan, ask, or decline?` on every item
106
+ - `Suggested:` opens with exactly one of those tokens, then the reason
107
+ - `You:` ships empty
108
+
109
+ Write `00-overview.md` last, per the intake standard. It carries the counts by token, an open-questions list linking each item, the commit measured against, every issue skipped in Step 2 with its carrier, and the left-over count from Step 3. Where the pass runs out of context before the index, write `99-next-session.md` naming the last issue filed.
110
+
111
+ ## Output
112
+
113
+ ```plaintext
114
+ 📂 Opened .canon/intake/<nn>-<slug>-triage/
115
+
116
+ **Filed:** <N> issues across <N> clusters, measured against <sha>
117
+ **Skipped:** <n> already carried
118
+ **Left over:** <n> past the cap of <cap>, from #<lowest>
119
+ **Suggested:** <n> plan, <n> ask, <n> decline
120
+
121
+ Next: /canon:plan-intake-answer to answer the `You:` slots
122
+ ```
123
+
124
+ Drop the `Left over:` line when nothing was past the cap.
125
+
126
+ An answered plan item promotes through `task-board` with an `Issue: #NNN` line, and the shipping pull request's `Closes #NNN` drains the queue. This skill still closes nothing. `plan-intake-answer` offers the close of a declined issue when the decline is answered, and the operator's yes posts it.
127
+
128
+ ## Notes
129
+
130
+ - This skill files and never reimplements. `plan-intake-answer` owns the answers and `task-board` owns the task a plan item becomes.
@@ -48,7 +48,7 @@ The human asks for each side of a compaction, and this skill reads the runbook.
48
48
 
49
49
  ### The poll and the parked rows
50
50
 
51
- The review trigger takes the same shape. `references/orchestrator-poll.md` holds the loop prompt and its condition, and `scripts/poll.ts` is what the prompt invokes. No hook starts or cancels that loop, so the condition holds only while whoever holds it applies it.
51
+ The review trigger takes the same shape. `references/orchestrator-poll.md` holds the background start, the routing, and the condition, and `scripts/poll.ts --wait` is the command it runs. No hook starts or stops that poll, so the condition holds only while whoever holds it applies it.
52
52
 
53
53
  A board that is not moving is a third such moment. On a request to re-test the parked rows, follow `${CLAUDE_SKILL_DIR}/references/orchestrator-parked.md`, which re-tests every blocker and backlog row against the tree and plans what it clears. The refill sweep fires on a merge instead.
54
54
 
@@ -69,7 +69,7 @@ Settle all three steps below before the door writes, so one write carries the co
69
69
 
70
70
  Resume by loading the orchestrator skill and asking it to resume after a compaction. This repository spells that `/canon:role-orchestrator` followed by the request. Following <RESUME_RUNBOOK> reaches the same place with no skill loaded at all.
71
71
 
72
- That resume reads the board and stops. It restarts nothing, so the review poll is a second thing owed here, and <POLL_RUNBOOK> holds the prompt and the condition. Do not reach for `session-resume`, which reads tracked work and reports the newest map without restarting this loop.
72
+ That resume reads the board and stops. It restarts nothing, so the review poll is a second thing owed here, and <POLL_RUNBOOK> holds the background start and the condition. Do not reach for `session-resume`, which reads tracked work and reports the newest map without restarting this poll.
73
73
  ```
74
74
 
75
75
  ### What the decisions section leaves out
@@ -23,28 +23,28 @@ The script reads pull requests and a building worker has none, so every interval
23
23
 
24
24
  ### Who starts and stops it
25
25
 
26
- A session holding a recurring-prompt scheduler starts the loop on that condition and cancels it on the same test, without waiting for the operator. A runbook stating only the start leaves the always-on failure unaddressed on the side that causes it.
26
+ The orchestrator starts the poll on that condition and stops re-arming it on the same test, without waiting for the operator. A runbook stating only the start leaves the always-on failure unaddressed on the side that causes it.
27
27
 
28
- - Nothing enforces this. No hook starts the poll and no check stops it, so the condition holds only while the loop's holder applies it.
29
- - Left always-on, it fires into an empty board through every gap between a dispatch and its push
30
- - The poll is session-scoped and dies with its session, so restart it after a compaction, taking the prompt from this file rather than a transcript
31
-
32
- ### Editing the prompt
33
-
34
- A running loop holds whatever wording it was started with, and a scheduled prompt cannot be edited, so every routing fix is a cancel and a re-create. Three fixes made inside one session once reached the running loop and none reached this file.
35
-
36
- Edit the block here first and re-create the loop from what this file then says. Rewriting the block from memory drops what the shipped one covered, which is how a `SEEN` report once fell through to no rule at all.
28
+ - Nothing enforces this. No hook starts the poll and no check stops it, so the condition holds only while the session holding it applies it.
29
+ - Stop it at once by stopping that background command, or let the running wait end and start no other
30
+ - The poll is session-scoped and dies with its session, so restart it after a compaction, taking the routing from this file rather than a transcript
37
31
 
38
32
  ## The prompt
39
33
 
40
- The requirement is a recurring prompt at roughly three minutes carrying the block below. `/loop 3m <the block>` is this repository's mechanism and one example among the schedulers a client may hold, so a client without it schedules whatever recurring prompt it can.
34
+ Start the poll in the background, act on what it prints when it exits by the block below, then start it again. It reads every 180 seconds by itself and exits on movement, so an idle board costs no turn and no message queues behind a run. The block is read on every wake, so edit it here and never from memory.
35
+
36
+ ```bash
37
+ bun <POLL_SCRIPT> --wait 1200
38
+ ```
41
39
 
42
- - The interval belongs to the schedule, since one run is a six-second snapshot, and relaunching on completion once fired every few seconds for 35 minutes
43
- - Paste the absolute path of `${CLAUDE_SKILL_DIR}/scripts/poll.ts` in place of `<POLL_SCRIPT>` below
44
- - The variable expands while this runbook renders and not in a standalone loop turn, so a block carrying it reaches the session as a literal and the run improvises
40
+ - Run it with the client's background option, `run_in_background` here, and start the next one as the last step of every wake, a `No movement.` at the cap included
41
+ - A failed read, of one pull request or the open list, holds to the cap with its `poll:` lines rather than spinning the restart
42
+ - Start with `--wait 180` while a cloud reviewer holds a pull request, since the rule below reads its pass once per wake
43
+ - Paste the absolute path of `${CLAUDE_SKILL_DIR}/scripts/poll.ts` for `<POLL_SCRIPT>`, since the variable expands only while this runbook renders
44
+ - Add `--no-record` to time or inspect a run without writing the baseline, and a client with no background command schedules a plain run every three minutes instead
45
45
 
46
46
  ```plaintext
47
- Poll GitHub for pull request movement by running `bun <POLL_SCRIPT>`, then act on what it reports.
47
+ Act on what the poll printed when it exited, then start it again.
48
48
 
49
49
  - A release pull request, whatever state follows it: report it and stop. Its sweep carries no findings, so no pass is owed. Test this before any rule below, since a release pull request is reported OPENED like any other and would otherwise match that rule first.
50
50
  - MOVED or RESPONSE on a pull request already reviewed: when a live local reviewer holds it, report it and stop, since the worker asks that reviewer for the re-review directly. When none does, which a cloud reviewer always leaves, launch a fresh reviewer under the review dispatch runbook of the canon:role-orchestrator skill. Never run the canon:review-pr skill here. Re-reviews read prior..head and gain nothing from waiting.