@erclx/canon 4.26.3 → 4.27.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.26.3",
4
+ "version": "4.27.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -35,7 +35,7 @@ Then output the state of play so the human knows what to launch, review, and mer
35
35
 
36
36
  `priority.md` is the ordering source. `index.md` sorts by filename and says nothing about order, so read the sequence from the first and never infer it from the second. When `priority.md` is absent, report the queue and say the order is unrecorded.
37
37
 
38
- Row position under `## Needs a plan` is what gets planned next, top first, per `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`. `backlog.md` carries what nobody is scheduling and is explicitly unordered, so read it for what exists and never as a queue. Report a backlog count rather than its rows, since listing them puts the length back in front of the reader the cut took it away from.
38
+ Row position under `## Needs a plan` is what gets planned next, top first, per `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`. `backlog.md` carries what nobody is scheduling and is explicitly unordered, so read it for what exists and never as a queue. Report a backlog count rather than its rows, since listing them puts the length back in front of the reader the cut took it away from. This read reports how many rows exist and nothing about whether each still belongs there. `references/orchestrator-parked.md` is what re-tests a backlog row's currency, on its own trigger below.
39
39
 
40
40
  No surface carries cross-version sequencing, so report none. A row's `Waiting on` cell states why that row sits where it does, and reasoning spanning several rows reaches this session only through whoever remembers it. Say nothing about an active version, since nothing in the tree states one and a version asserted from the board is the unsourced claim this omission exists against.
41
41
 
@@ -47,7 +47,7 @@ On a request to resume after a compaction, read `${CLAUDE_SKILL_DIR}/references/
47
47
 
48
48
  The review trigger takes the same shape. `references/orchestrator-poll.md` holds the loop prompt and the condition under which the poll runs, and `scripts/poll.sh` is what the prompt invokes. A session holding a recurring-prompt scheduler starts and cancels that loop itself, and no hook or check does, so the condition holds only while whoever holds the loop applies it.
49
49
 
50
- A board that is not moving is a third such moment. On a request to re-test the parked rows, read `${CLAUDE_SKILL_DIR}/references/orchestrator-parked.md` and follow it. It re-tests every `## Up next` and `## Needs a plan` blocker against the current tree, writes what each test showed into the row, and plans what it clears. Its trigger is the inverse of the refill sweep's below, which fires on a merge and asks what to promote next rather than whether a row already parked is still parked for a reason.
50
+ A board that is not moving is a third such moment. On a request to re-test the parked rows, read `${CLAUDE_SKILL_DIR}/references/orchestrator-parked.md` and follow it. It re-tests every `## Up next` and `## Needs a plan` blocker against the current tree, walks `backlog.md` on the same pass, writes what each test showed into the row, and plans what it clears. Its trigger is the inverse of the refill sweep's below, which fires on a merge and asks what to promote next rather than whether a row already parked is still parked for a reason.
51
51
 
52
52
  That routing lives in this body and this skill is user-invoked, so a session that has dropped the body routes nothing and the request lands as ordinary conversation. Approaching a compaction is when a long session is likeliest to have dropped it, which is the same moment the handoff exists for. Re-invoke `/canon:claude-orchestrate` first whenever the session has run long or the ask goes unanswered. The three runbooks sit at `references/orchestrator-handoff.md`, `references/orchestrator-resume.md`, and `references/orchestrator-parked.md` inside this skill's own folder, so a person who knows their plugin root opens any one of them directly and follows it without this skill loaded at all.
53
53
 
@@ -174,7 +174,7 @@ It counts unclaimed plans against workers rather than reading the reserve in ste
174
174
  - A task whose outcomes describe standing policy rather than a deliverable never closes on its own, so hand it to a worker to encode the policy where it is enforced, then cut the outcomes with the reason recorded and archive once that branch merges. Encoding it from this session would write a tracked file, which Boundaries forbids.
175
175
  4. Read `.canon/tasks/priority.md` and count entries under its `## Run now` heading that carry a written plan. Keep one in reserve beyond what is running.
176
176
  5. Promote from the top of `## Needs a plan`, which is where the last sweep recorded what to plan next. Depart from that order when something has changed under it and say what changed, since a position nobody honors is the ordering going stale on the surface built to hold it. What sets the order in the first place is whether a task establishes functionality rather than how old it is, so prefer a task that adds or proves a mechanism over one that trims, tidies, or audits an existing surface.
177
- - Re-take the board-or-backlog call while the file is open. A row that has stopped being near-term moves to `backlog.md`, and a backlogged task the last wave made near-term moves onto the board. Both are one line removed from one surface and written to the other, and the standard states the test.
177
+ - Re-take the demotion half of the board-or-backlog call while the file is open. A row that has stopped being near-term moves to `backlog.md`, one line removed from `priority.md` and written there, per the standard's test. The promotion half is `references/orchestrator-parked.md`'s to run as a full walk over every backlog row, so this step does not repeat it. A row that pass clears already sits at the bottom of `## Needs a plan` before this sweep reads the heading.
178
178
  6. Before promoting a candidate, list the files it touches against every task already running, per Parallelism below. Name the overlap and serialize when the sets are not disjoint.
179
179
  - A candidate held by something outside the tree stays where it is whatever those sets show. A collision is one of the reasons a task cannot start, so disjointness clears that reason alone and leaves an external condition standing.
180
180
  7. Write a plan for each newly promoted task with `claude-feature`, carrying the in-flight constraint that The loop above states, then report:
@@ -73,7 +73,8 @@ Name `<model>` on the launch, and pick it against the task rather than copying w
73
73
  ## Dispatch
74
74
 
75
75
  ```bash
76
- claude --bg --model <model> -n "worker-<project>-<slug>" "Run /canon:claude-worktree <type>/<slug>, then /canon:claude-autoship <plan>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
76
+ claude --bg --model <model> -n "worker-<project>-<slug>" "/canon:claude-autoship <plan>
77
+ Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the pull request opens, carrying the number, the branch, the head sha, the CI state, and every point you departed from the plan on, and message it again if you stop on a question."
77
78
  ```
78
79
 
79
80
  `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `canon sessions list`, and `--model` overrides the inheritance the section above measured. Pass `-n` on every dispatch rather than letting the client derive one. A launch that omits it leaves the session named for a fragment of its own identifier, which is both its address on the send channel and the whole of what the operator sees for it in agent view.
@@ -90,47 +91,76 @@ Where the installed CLI answers `--self` with an unknown option, that flag is ne
90
91
 
91
92
  The worker resolves that id back to a name through `canon sessions list --json`, which carries `sessionId` per row, rather than through the agent listing, which prints a name and a short ref and no id at all. A worker reaching for the listing first therefore finds no lookup and can conclude there is none. That failure is silent in both directions: the session has nothing useful to do with the message it owes and goes idle holding it, and nothing on this side reports the quiet, so the loss surfaces as a missing worktree or a pull request that never opens rather than as anything watching for it.
92
93
 
93
- The worktree call comes first and carries the branch as its argument, which is tier 0 of `claude-worktree` Step 2 and the only tier a caller can reach. `claude-autoship` Step 0 then finds the session already in a linked worktree and continues, which is a path it already documents. The autoship call carries `<plan>`, the same file this runbook already read to derive the branch, so its Step 1 takes it as the caller-supplied plan rather than re-deriving one from the slug the worker's branch happens to carry.
94
-
95
- Naming the branch in prose instead was tried and closes nothing, because no tier of that ladder reads the prompt. A worker launched onto `main` cannot match tier 1, a board carrying more than one plan puts tier 2 out of reach, and tier 3 tells it to ask a person who is not there. Four workers took the right branch that way, by inference rather than by contract, which is the same judgment both live disagreements came from.
96
-
97
- ### The prompt expands its leading slash command and nothing after it
98
-
99
- The client expands the first slash command in a launch prompt as a user
100
- invocation, which is the route `disable-model-invocation: true` permits. Every
101
- later command in the same prompt reaches the session as prose, leaving the model
102
- to invoke it through the `Skill` tool, and that route answers a flagged skill
103
- inconsistently. `claude-autoship` has carried the flag since `erclx/canon#365` and seven
104
- other shipped skills carry it too.
105
-
106
- The block above therefore leads with the call that does not need the user route
107
- and leaves the one that does to the tool. Four sessions made the same tool call
108
- against the same plugin cache on 2026-08-31. Two were answered with the body and
109
- shipped, and two were refused with `Skill canon:claude-autoship cannot be used
110
- with Skill tool due to disable-model-invocation`. Prefixing separated nothing,
111
- since three of the four carried the namespace and those three landed on both
112
- answers, so nothing a dispatcher writes predicts which answer a launch gets.
94
+ The template carries no worktree call. `claude-autoship` Step 0 invokes `canon:claude-worker` and then `canon:claude-worktree` itself, and neither carries the flag, so both are reachable through the `Skill` tool regardless of where a call to them would sit in a prompt. The autoship call carries `<plan>`, the same file this runbook already read to derive the branch, so its Step 1 takes it as the caller-supplied plan rather than re-deriving one from the slug the worker's branch happens to carry.
95
+
96
+ Dropping the argument does not hand `claude-worktree` a formal one in its place. A worker launched onto `main` cannot match tier 1, a board carrying more than one plan puts tier 2 out of reach, and tier 3 tells it to ask a person who is not there, so the ladder alone still closes nothing.
97
+
98
+ What closes it is the same inference four workers already took before this template existed: the session already holds `<plan>` and derives `claude-worktree`'s name from it directly, rather than waiting on a tier to supply one. That is a judgment rather than a contract, and it is the same judgment both live disagreements came from, so read it as the residual risk this template still carries rather than as solved.
99
+
100
+ ### Expansion needs position zero and a clean delimiter, not leading order alone
101
+
102
+ The client expands a slash command at position zero of a launch prompt as a
103
+ user invocation, which is the route `disable-model-invocation: true` permits
104
+ and gates. Everything that reaches the session as prose instead falls to the
105
+ model, which invokes it through the `Skill` tool, and that route answers a
106
+ flagged skill inconsistently. `claude-autoship` has carried the flag since
107
+ `erclx/canon#365` and seven other shipped skills carry it too.
108
+
109
+ Three launches on 2026-08-31 and 2026-09-02 bound what makes a command take
110
+ that route. Observation A is the first refused worker, launched as `Run
111
+ /canon:claude-worktree ..., then /canon:claude-autoship ...`, which expanded
112
+ nothing. Observation B is a re-dispatch launched as `/canon:claude-autoship
113
+ <plan>` with a space before the path, which expanded and shipped
114
+ `erclx/canon#1382`.
115
+
116
+ Observation C is a planning dispatch launched as `/canon:claude-planner, then
117
+ /canon:claude-feature <task>` with a comma glued to the command name at
118
+ position zero, which expanded nothing and reached both bodies through the
119
+ `Skill` tool instead. A rules out leading order alone, C rules out position
120
+ zero on its own, and the only visible difference between B and C is the
121
+ delimiter after the command token: a space in B, a comma in C.
122
+
123
+ Read that delimiter reading as a candidate with a falsifier rather than as
124
+ settled. The cheapest test is one dispatch leading with a bare command whose
125
+ name is followed directly by a period, and the next real dispatch can carry it
126
+ at no extra cost. Until it fails, the operational rule is the conjunction the
127
+ three observations support: put the flagged command at position zero, followed
128
+ by a space and its argument, with nothing before it.
129
+
130
+ Four sessions made the same tool call against the same plugin cache on
131
+ 2026-08-31. Two were answered with the body and shipped, and two were refused
132
+ with `Skill canon:claude-autoship cannot be used with Skill tool due to
133
+ disable-model-invocation`. Prefixing separated nothing, since three of the
134
+ four carried the namespace and those three landed on both answers, so nothing
135
+ a dispatcher writes predicts which answer a launch through the tool gets.
113
136
 
114
137
  Read that as a route a dispatch may not depend on rather than one that usually
115
- works. The refusal closes the fallback in the same message, telling the session
116
- not to replicate the workflow by other means, so a refused worker has no route
117
- left and stops with a clean worktree. Both failed dispatches produced nothing
118
- rather than a degraded run, which is the correct outcome and not a thing to
119
- soften.
138
+ works. The refusal closes the fallback in the same message, telling the
139
+ session not to replicate the workflow by other means, so a refused worker has
140
+ no route left and stops with a clean worktree. Both failed dispatches produced
141
+ nothing rather than a degraded run, which is the correct outcome and not a
142
+ thing to soften.
120
143
 
121
144
  Recovery belongs to whoever writes the next prompt, since a blocked session
122
- cannot replay its own launch. The refusal is sticky inside a session rather than
123
- something a retry clears, measured when one refused worker repeated the identical
124
- prefixed call and got the byte-identical error back. So re-dispatch onto the same
125
- branch with the autoship call leading the prompt, which puts the one command the
126
- first launch left as prose in the position the client expands.
127
-
128
- A launch that leads with `/canon:claude-autoship <plan>` and names no worktree
129
- call is the candidate for closing this on the first dispatch, since that chain's
130
- Step 0 enters the worktree itself and `claude-worktree` carries no flag to
131
- refuse the tool route. Nobody has run it. Treat it as untested rather than as the
132
- shape to switch to, because a launch expansion cannot be read from inside the
133
- session it launched.
145
+ cannot replay its own launch. The refusal is sticky inside a session rather
146
+ than something a retry clears, measured when one refused worker repeated the
147
+ identical prefixed call and got the byte-identical error back. Re-dispatch
148
+ onto the same branch with the build template above, which already leads with
149
+ the one command that needs the expansion route.
150
+
151
+ The review shape and the planning shape below depend on no expansion at all.
152
+ None of `claude-worker`, `claude-address-review`, `claude-planner`, or
153
+ `claude-feature` carries the flag, so both correctly keep their leading word
154
+ regardless of the delimiter or the position it sits at.
155
+
156
+ The same collapse reaches a human relay rather than a `claude --bg` string. A
157
+ controller that hands an operator two chained blocks to paste as separate
158
+ messages risks both landing as one, where everything after the first
159
+ command's name is read as that command's own argument and the second command
160
+ never fires, measured four times out of four on 2026-09-02.
161
+ `.claude/context/claude-plugin/skill-lifecycle.md` carries the mechanism. The
162
+ fix is what the template above already takes: one message, one command, at
163
+ position zero.
134
164
 
135
165
  ### What the brief may carry
136
166
 
@@ -169,7 +199,7 @@ directly with `Bash`, `Read`, and `Edit` instead of retrying the tool, which
169
199
  is the route two workers already took today on two different branches.
170
200
 
171
201
  ```bash
172
- claude --bg --model <model> -n "worker-<project>-<slug>" "Enter the worktree for <branch> at .claude/worktrees/<slug>/, creating it from that branch if the folder is gone. Run /canon:claude-worker, then /canon:claude-address-review. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the address pass finishes, carrying what was addressed and the PR's CI state, and message it again if you stop on a question."
202
+ claude --bg --model <model> -n "worker-<project>-<slug>" "Enter the worktree for <branch> at .claude/worktrees/<slug>/, creating it from that branch if the folder is gone. Run /canon:claude-worker, then /canon:claude-address-review. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the address pass finishes, carrying what was addressed and the PR's CI state, and message it again if you stop on a question."
173
203
  ```
174
204
 
175
205
  `<dispatcher-id>`, `<model>`, and `<project>` resolve the same way the build
@@ -200,7 +230,7 @@ gitignored file at the main worktree root, so this shape names the row's task
200
230
  file rather than a branch and opens with the role instead of a worktree call.
201
231
 
202
232
  ```bash
203
- claude --bg --model <model> -n "planner-<project>-<slug>" "Run /canon:claude-planner, then /canon:claude-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
233
+ claude --bg --model <model> -n "planner-<project>-<slug>" "Run /canon:claude-planner, then /canon:claude-feature <task>. Your controller is the session whose sessionId is <dispatcher-id>. Resolve its current name from that id through canon sessions list --json, which carries sessionId per row, at the moment you send, and never resolve an addressee by name prefix. Message it when the plan lands, carrying the path and what the task file got wrong, and message it again if you stop on a question."
204
234
  ```
205
235
 
206
236
  `<task>` is the row's task file path and `<slug>` the slug its plan will take,
@@ -7,7 +7,7 @@ Re-test every parked row as orchestrator. A blocker cell is a measurement taken
7
7
 
8
8
  Two triggers start this pass. `orchestrator-sweep.md` ends by sending the rows `canon tasks validate` listed as untested here, because a merge changes the tree under every parked row at once rather than under the rows naming it. The other is an idle session: nothing merged, workers are building, no pull request is waiting on a first pass, and the board is not moving.
9
9
 
10
- The two differ in scope rather than in procedure. A merge sends the untested rows, since the validator already re-took the two kinds it can settle. An idle session walks every parked row, because no event narrowed which of them to look at.
10
+ The two differ in scope rather than in procedure. A merge sends the untested rows, since the validator already re-took the two kinds it can settle. An idle session walks every parked row, because no event narrowed which of them to look at, and it alone also walks `backlog.md`: a merge names no backlog row as untested, since `canon tasks validate` re-tests blocker cells and a backlog row carries none.
11
11
 
12
12
  Neither trigger is a scheduler. `orchestrator-poll.md` owns the one recurring trigger this skill has, and a second loop firing into a static board is the always-on failure that file already warns about.
13
13
 
@@ -15,9 +15,9 @@ The sweep's own question stays distinct from this one. It asks which parked row
15
15
 
16
16
  ## Scope
17
17
 
18
- Every row under `## Up next` and `## Needs a plan` in `.canon/tasks/priority.md`. A `## Run now` row carries no blocker by definition, so the pass skips it. Resolve the board and each task file at the main worktree root, the way `claude-worktree` does.
18
+ Every row under `## Up next` and `## Needs a plan` in `.canon/tasks/priority.md`. A `## Run now` row carries no blocker by definition, so the pass skips it. On the idle trigger, also every row in `.canon/tasks/backlog.md` when the file exists, per the trigger split above. Resolve the board, the backlog, and each task file at the main worktree root, the way `claude-worktree` does.
19
19
 
20
- Take the rows in board order and finish one before opening the next. Clearing a row changes what the next row collides with, so a pass that measures every row first and writes afterwards writes against a board it has already invalidated.
20
+ Take the board rows in board order and finish one before opening the next. Clearing a row changes what the next row collides with, so a pass that measures every row first and writes afterwards writes against a board it has already invalidated. On the idle trigger, walk the backlog after the board, in the file's own filename order, since `standards/tasks.md` fixes that file as unordered and nothing about a row's position there means anything to preserve mid-pass.
21
21
 
22
22
  ## Re-testing a row
23
23
 
@@ -33,6 +33,14 @@ Every measurement this pass takes is a blocker re-test on a row already filed, a
33
33
 
34
34
  Write the result into the row. A re-test reported in chat is lost at the next compaction and the next pass measures the same thing again. Rewrite a blocker cell whose test no longer holds, move the row to the group its new state puts it in, and record the measurement in that task's `## Findings` with the date it was taken. `### Writing the board` in `claude-orchestrate` owns the method, and `canon tasks validate` runs once the board is rewritten and before the report.
35
35
 
36
+ ## Re-testing a backlog row
37
+
38
+ A backlog row carries no blocker cell, since `standards/tasks.md` fixes `backlog.md` as a flat list of links with no state of its own. The five kinds above test what a blocker names, and a backlog row names none, so this pass asks the board-or-backlog question the standard already states instead: has the work in front of the row landed, or has the world changed under it. Read the answer from the task file's own `## Findings` and its origin line (`Plan:`, `Groundwork:`, `Intake:`, or `Issue:`), which name what the row was filed against and whether the fact behind it still holds.
39
+
40
+ Clearing a row answers yes to that question. It is not a count of how long the row has waited: `## Two results that are not a re-test` below refuses age as evidence for a board row, and it refuses it here on the same ground, since a row untouched for weeks is not more clearable than one added yesterday.
41
+
42
+ A row this test clears moves out of `backlog.md` and onto the bottom of `## Needs a plan`, carrying a `Waiting on` cell that states what changed and ends with the word `last`. `canon tasks validate`'s ordering check reads `last` as the ordinal bottom placement already claims, and a cell stating only what changed matches neither that check's ordinal reading nor its rank reading, which raises the finding this pass would then have written against its own row. The next refill sweep's promotion step still orders the row against the rest of that heading the way it orders every other row there, per step 5 under `## Refilling the ready queue` in `claude-orchestrate`.
43
+
36
44
  ## Two ways a re-test goes wrong
37
45
 
38
46
  Both return a confident wrong answer rather than an error, which is why each gets a step of its own.
@@ -67,6 +75,8 @@ Cleared: <row>, now <group>, plan at <path>
67
75
  Still parked: <row>, <blocker> re-confirmed against <what was measured>
68
76
  Untestable: <row>, waits on <operator action>
69
77
  Split: <task> into <tasks>, file set collided with everything by construction
78
+ Backlog cleared: <row>, now bottom of ## Needs a plan, waiting on <what changed>
79
+ Still backlogged: <row>, <what was checked> found unchanged
70
80
  ```
71
81
 
72
82
  Omit any row with nothing in it. Name what was measured rather than the verdict alone, since a re-test the reader cannot check is the same claim the row already carried.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Route every git operation through the canon git skills rather than built-in commit and pull request behavior
2
+ description: Route every git operation through the canon git skills, and run one only when something actually calls for it
3
3
  ---
4
4
 
5
5
  # Git standards
@@ -9,3 +9,7 @@ description: Route every git operation through the canon git skills rather than
9
9
  - Route every git operation through the `canon:git-*` skills.
10
10
  - Do not follow built-in commit, pull request, or branch instructions for an operation a `git-*` skill covers.
11
11
  - Report it rather than proceeding silently when no `git-*` skill resolves. They ship with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have them.
12
+
13
+ ## Whether to act
14
+
15
+ - A git operation runs only when the user asked for it, a task states it, or it executes as a step of a chain the user invoked. The bullets above decide which surface performs an operation already decided, never whether to take one.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.26.3",
4
+ "version": "4.27.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {