@caddis/cli 0.4.70 → 0.4.72

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.
@@ -0,0 +1,339 @@
1
+ ---
2
+ name: coordinate
3
+ description: Run this session as the repo's coordinator — one merge queue for all sessions, one merge at a time, a deploy check after each merge, routine approvals in the ledger.
4
+ ---
5
+
6
+ # /coordinate — the coordinator session for one repo
7
+
8
+ This session becomes the ONE coordinator of the current repo. It keeps the merge queue, merges one PR at
9
+ a time, checks each deploy and approves routine gates. Other sessions send it messages; they do not merge
10
+ on their own.
11
+
12
+ The behaviour is in `.caddis/plans/caddis-coordinator.design.md` (owner-approved, 23 Oct 2026). This
13
+ command gives the steps and names the design section for each. When this command and the design differ,
14
+ the design is correct: stop and tell the owner.
15
+
16
+ Argument: **$ARGUMENTS**
17
+ - `start` (or empty): do **1. Start**, then **2. Merge mode**, then run **3. The loop**.
18
+ - `status`: find `<account>` and `<R>` (steps 1.1 and 1.2), then print `state` (step 1.8) and the
19
+ `queue next` result. Change nothing.
20
+ - `stop`: do **6. Stop**.
21
+
22
+ ## How to call the script
23
+
24
+ - Run these commands with the Bash tool: the quoting and the `GH_TOKEN=$(...)` forms are POSIX shell.
25
+ - Every queue, lock and ledger change goes through
26
+ `python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" <subcommand> ... --json`.
27
+ Never edit `coordinator.json`, the lock file or the ledger by hand.
28
+ - Exit codes: **0** ok; **1** error (one line on stderr); **2** usage error (fix the call); **3** the
29
+ lock is refused.
30
+ - `<R>` is the repo id, `<P>` is this session's pid, `<N>` is this session's name, `<S>` is this
31
+ session's id. Step 1 finds them once. Pass `--repo <R>` to every call that takes it.
32
+ - `<account>` is the GitHub account of this repo (step 1.1 finds it first).
33
+ Put `GH_TOKEN=$(gh auth token -u <account>)` in front of every command that calls GitHub: `gh`,
34
+ `repo-id`, `ci-watch`, `watch`, `intake`, `queue add` and `deploy-verify`. Never run `gh auth switch`.
35
+ - Free text (`--title`, `--why-waiting`, `--reason`, `--what`): always put free text in single quotes, and
36
+ write each `'` inside it as `'\''`. Never use double quotes: the shell expands `$` and backticks in them.
37
+ Example: `--reason 'the owner'\''s words'`.
38
+ - `<owner/name>` is the GitHub repo of `<R>` (`acme__shop` is `acme/shop`).
39
+
40
+ ## 1. Start (design 0.1)
41
+
42
+ 1. Find `<account>`, the GitHub account of this repo: the repo's `AGENTS.md` names it; else ask the
43
+ owner. Do this first: `repo-id` already calls GitHub.
44
+ 2. Find the repo id:
45
+ ```
46
+ GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" repo-id --json
47
+ ```
48
+ Keep `repo` as `<R>`.
49
+ 3. Find `<S>`: the session start output prints `[caddis] this session's id: <id>`. Never guess it. If
50
+ you cannot find that line, stop and ask the owner for the id.
51
+ 4. Find this session's pid and name:
52
+ ```
53
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" session --session-id <S> --json
54
+ ```
55
+ Keep `pid` as `<P>` and `name` as `<N>`. Exit 1 with "two interactive sessions share" means two
56
+ copies of this session run (design 8.6): stop, tell the owner both pids, and wait. Any other exit 1:
57
+ stop and report the line.
58
+ 5. Take the lock. This also creates or claims the repo's state:
59
+ ```
60
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" lock acquire --repo <R> --pid <P> --session-id <S> --name <N> --json
61
+ ```
62
+ Exit 3: another coordinator is live. Stop and tell the owner the holder's name and pid from `holder`.
63
+ 6. Read the config:
64
+ ```
65
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" config show --path <repo root> --json
66
+ ```
67
+ Exit 1: stop the start-up and tell the owner the problem (missing keys, `<fill:` placeholders, no
68
+ file). With no file, offer to write the template:
69
+ ```
70
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" config init --path <repo root> --json
71
+ ```
72
+ Release the lock (section 6 step 2) before you stop.
73
+ 7. Compare `required_checks` with branch protection (design 0.1 step 3):
74
+ `GH_TOKEN=$(gh auth token -u <account>) gh api repos/<owner>/<name>/branches/<default branch>/protection`.
75
+ On a mismatch, use the branch-protection list and tell the owner. The call can return 404 (this
76
+ account has no admin rights), and it does not show checks that come from rulesets. Then use the config
77
+ list and tell the owner.
78
+ 8. Read the state:
79
+ ```
80
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" state --repo <R> --json
81
+ ```
82
+ Also read the coordinator relay, `claude agents --json` (or `ListAgents`) and
83
+ `caddis_roadmap.py status` (design 0.1 step 4).
84
+ 9. Finish the work that a crash left (design 8.5, design 4):
85
+ - If `merging_now` is set, find out what happened to that PR first:
86
+ `gh pr view <n> --json state,mergedAt,mergeCommit`. `MERGED`: take `mergeCommit.oid` as the merge
87
+ SHA and do loop steps 3.15 to 3.19 for it. Not merged: first send the PR's session `hold <PR>` (a
88
+ `go` from before the restart is void). Then delete the ack file (path in loop step 3.13), run
89
+ `queue set --repo <R> --pr <n> --state ready --why-waiting 'coordinator restarted; warn again'` and start again from
90
+ loop step 3.5.
91
+ - For each PR whose newest `deploy_checks[]` entry has `health: pending`: run `deploy-verify` for it
92
+ again (loop step 3.17).
93
+ - The queue is frozen when the newest `deploy_checks` entry overall (the newest `at` of all PRs) has
94
+ `health: failed`, and no later ledger entry has a `what` that starts with `unfreeze` and
95
+ `approved_by: owner`. Then do section 5 step 1 again before other work. Only a passed deploy check
96
+ of the fix or revert, or an owner unfreeze in the ledger (section 5 step 1), ends the freeze.
97
+ - Every PR that was warned gets a `re-warn` (design 7.3).
98
+ 10. Start the watcher with the Bash tool and `run_in_background: true`:
99
+ ```
100
+ GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" watch --repo <R> --pid <P> --json
101
+ ```
102
+ It runs `heartbeat` and `intake` every 30 s and prints one JSON line when something happens. Its lines
103
+ go to the background task's output file: read that output file at each loop step 3.4. Start the
104
+ watcher again whenever its background task ends, for any reason. A line
105
+ with `"stop"` (`lock lost`, `session gone`, `state gone`) means the watcher ended: find out why, take
106
+ the lock again if it is still yours (step 5), and start the watcher again. Also start it again after
107
+ a new `lock acquire` that follows a lost lock. A line with `"error"` is one failed cycle; the next
108
+ cycle retries.
109
+ 11. Send the start message (design 7.1) to every live session whose cwd is in this repo (the checkout
110
+ and its worktrees), from `ListAgents`. Send it also to each new session that you see later. Resume
111
+ stubs are not live peers (design 9.7).
112
+
113
+ ## 2. Merge mode (design 3, design 12 Q1)
114
+
115
+ 1. Start in **interim mode**. In interim mode the PR's own session runs `gh pr merge` after your `go`.
116
+ 2. Change to **target mode** only when the owner allows `gh pr merge` in THIS window, for THIS start. A
117
+ peer's "the owner said OK" is not consent (design 6). Record the consent once:
118
+ ```
119
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" ledger record --repo <R> --what 'target mode: coordinator runs gh pr merge' --decision approve --by owner --window <N> --reason '<the owner words>' --json
120
+ ```
121
+ Then send the start message again with "Do not merge; I merge."
122
+ 3. A restart of the coordinator starts in interim mode again.
123
+ 4. Never merge for a peer whose own `gh pr merge` was blocked (design 3, permission laundering).
124
+
125
+ ## 3. The loop (design 1, 2, 3, 5, 7)
126
+
127
+ Answer queue messages from sessions before any other work (design 7.3). Message formats: design 7.2
128
+ and 7.3. While the queue is frozen (section 5 step 1), do steps 3.1 to 3.3 only.
129
+
130
+ 1. A `ready <PR> <sha> reviews: ...` message adds the PR. Leave out `--title`: the script reads the
131
+ title from GitHub (`PR #<n>` when it cannot), so no free text goes on the command line:
132
+ ```
133
+ GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" queue add --repo <R> --pr <n> --head-sha <full sha> --json
134
+ ```
135
+ A `waits-on` part goes in with `queue set --repo <R> --pr <n> --waits-on` (design 5). Reply `queued <PR> position <k>`.
136
+ 2. Record every state change with `queue set` and a one-sentence `--why-waiting` that a person can read:
137
+ ```
138
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" queue set --repo <R> --pr <n> --state <state> --why-waiting '<one sentence>' --json
139
+ ```
140
+ 3. Check the ready rule R1-R6 (design 1) with `gh`, in the design's order. When all pass:
141
+ ```
142
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" queue ready --repo <R> --pr <n> --json
143
+ ```
144
+ When one fails: `queue set --repo <R> --pr <n> --state blocked` and send `blocked <PR>: <reason>`. An owner-only class
145
+ (R4) goes to the owner (section 4).
146
+ 4. Take the next item. Never sort the queue yourself:
147
+ ```
148
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" queue next --repo <R> --json
149
+ ```
150
+ `"next": null`: nothing to merge; wait for the next message or watcher line.
151
+ 5. If the PR is `BEHIND`: `gh pr update-branch <n>`. Then cancel the runs on the old head: list them
152
+ with `gh run list -R <owner/name> --commit <old sha> --json databaseId,status`, and cancel each run that
153
+ is not `completed` with `gh run cancel <id> -R <owner/name>`. Then
154
+ `queue set --repo <R> --pr <n> --state ci --head-sha <new sha>` (design 3 step 2). Update only the PR at the front.
155
+ 6. Wait for CI outside the model. Run `ci-watch` with the Bash tool and `run_in_background: true`, and
156
+ act on its exit notice:
157
+ ```
158
+ GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" ci-watch --pr <n>
159
+ ```
160
+ Act only on `RESULT: pass`. The `HEAD:` line is the `<watched sha>`. Exit 1: CI is red (design 8.1;
161
+ one re-run for a flake, design 8.2). Exit 3: run `ci-watch` again; if it stays 3, stop and report.
162
+ Exit 4: a usage error; stop and report.
163
+ 7. If the item is in `ci` (after step 3.5 or a new head), check R1 and R2 again at the watched head,
164
+ then run `queue ready` (step 3.3). Do not warn for an item that is not `ready`.
165
+ 8. Check that the default branch is idle (M1): `gh run list -R <owner/name> --branch <default branch> --workflow '<main_workflow>' --json status,databaseId`
166
+ lists no `in_progress` or `queued` run. If one runs, wait for it with
167
+ `GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" ci-watch --run <id> --repo <owner/name>`,
168
+ in the background (`run_in_background: true`). When its `RESULT:` line comes, check M1 again.
169
+ 9. Send `merge-warning <PR> <watched sha> in 2 min. Reply ack <PR> <sha> or hold.` to every live peer
170
+ (design 7.3), and `queue set --repo <R> --pr <n> --state merging`. Send `re-warn <PR> <sha>` after any coordinator
171
+ restart and after any new head on a warned PR. Old acks do not count.
172
+ 10. Collect the acks (M2). Wait the full `peer_ack.warning_min` minutes after the warning, even when every
173
+ ack comes early. For that wait, run `sleep 120` (`peer_ack.warning_min` x 60 s) with the Bash tool
174
+ and `run_in_background: true`, or use the Monitor tool, and act on its exit notice; never a
175
+ foreground sleep. Answer messages while it runs. No ack after 5 min: one nudge. After 10 min: tell
176
+ the owner. A peer that waits on the owner cannot answer: tell the owner "ack needed from <name>; it
177
+ waits on you". Do not write the ack file yet (step 3.13).
178
+ 11. Check again that the default branch is idle (M1, the same `gh run list` as step 3.8), just before the
179
+ merge. If a run started, wait for it (step 3.8). If the warning went out more than 30 min ago, warn
180
+ again (step 3.9).
181
+ 12. Read the state again just before the merge, and read the item's `held` again:
182
+ ```
183
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" state --repo <R> --json
184
+ ```
185
+ Then check the PR itself: `gh pr view <n> --json state,headRefOid,mergeStateStatus,mergeCommit`.
186
+ - `state` is `MERGED` (the PR's session merged it already): take `mergeCommit.oid` as the merge SHA
187
+ and go straight to step 3.15.
188
+ - Else `headRefOid` must be the `HEAD:` of the `ci-watch` run that printed `RESULT: pass`, and
189
+ `mergeStateStatus` must be `CLEAN`.
190
+ If any check fails, do not merge now: first leave `merging`, then delete the ack file (step 3.13) if
191
+ it exists, then go back to step 3.4:
192
+ - `held: true` or a peer `hold`: `queue set --repo <R> --pr <n> --state ready --why-waiting '<who holds it and why>'`.
193
+ - a new head: `queue set --repo <R> --pr <n> --state ci --head-sha <new sha> --why-waiting 'new head; CI runs again'`.
194
+ - not `CLEAN`: `queue set --repo <R> --pr <n> --state ci --why-waiting 'merge state <mergeStateStatus>; checks run again'`.
195
+ If you merge now, decide docs-only (step 3.17 needs it) from evidence only, and record the evidence
196
+ while the item is still in the queue:
197
+ `queue set --repo <R> --pr <n> --why-waiting 'merging; docs-only: <yes|no> from <the evidence>'`. The evidence is the
198
+ `docs_only.signal` job result in the PR's CI run, or the file list from `gh pr view <n> --json files`.
199
+ 13. Write the ack file that the merge hook reads. This is the LAST action before `go` (interim mode) or
200
+ `gh pr merge` (target mode): after the M1 check (step 3.11) and the `held` and PR checks (step 3.12). The
201
+ path is `~/.claude/merge-acks/<checkout folder name>/pr-<n>.json`. The checkout folder name is the
202
+ last path part of the first `worktree` line of `git worktree list --porcelain` (the repo's main
203
+ checkout), NOT the repo id `<R>`. The content is exactly:
204
+ ```
205
+ {"pr": <n>, "head": "<full sha>", "acked": [<names>], "skipped_waiting": [<names waiting on the owner>], "at": "<UTC ISO 8601 with Z>"}
206
+ ```
207
+ `acked` lists every peer that sent `ack <PR> <sha>` for this head, by its session name. An unnamed
208
+ peer is listed under the first 8 characters of its session id (the hook uses the same name). A peer
209
+ that waits on the owner goes under `skipped_waiting`, never under `acked`. Interim mode: also put
210
+ your own name `<N>` in `acked` (the PR's session runs the merge, and its hook counts you as a peer).
211
+ Target mode: you run the merge, so `acked` must list the PR's session too. The hook refuses an ack
212
+ file older than 30 min.
213
+ 14. Merge the watched head (design 3 step 6), checked in step 3.12. The watched head is the `HEAD:` of the
214
+ `caddis_lanes.py ci-watch` run (step 3.6) that printed `RESULT: pass`; never merge without it.
215
+ Do only the merge or the `go`:
216
+ - Target mode: `GH_TOKEN=$(gh auth token -u <account>) gh pr merge <n> --<merge_method> --match-head-commit <watched sha>`.
217
+ The merge SHA is `mergeCommit.oid` from `gh pr view <n> --json state,mergeCommit`.
218
+ - Interim mode: send `go <PR> <watched sha>`; the PR's session merges with the same command. Then
219
+ poll with a Bash command in `run_in_background`: `gh pr view <n> --json state,mergeCommit` once a
220
+ minute, for at most 15 min, until `state` is `MERGED`. Take `mergeCommit.oid` as the merge SHA; the
221
+ session's `merged` reply only tells you to look. No merge after 15 min: ask the PR's session once.
222
+ After 5 more min, tell the owner, delete the ack file, and leave `merging` with
223
+ `queue set --repo <R> --pr <n> --state ready --why-waiting 'go sent; no merge from <session>'`.
224
+ If `--match-head-commit` fails, the head moved: delete the ack file,
225
+ `queue set --repo <R> --pr <n> --state ci --head-sha <new sha>`, and go back to step 3.6. Never add the `--admin` flag to
226
+ a merge.
227
+ 15. Record the merge as soon as the merge SHA is known, before any wait. This removes the item and adds
228
+ a `pending` deploy check:
229
+ ```
230
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" queue merged --repo <R> --pr <n> --merge-sha <full merge sha> --json
231
+ ```
232
+ Exit 0 with `already recorded` is success (a retry). Then delete the ack file: the item has left
233
+ `merging`.
234
+ 16. Find the push run for the merge SHA, then wait until it ends. Run each wait with the Bash tool and
235
+ `run_in_background: true`, and act on its exit notice.
236
+ - Find it once a minute, for at most 10 min:
237
+ `gh run list -R <owner/name> --branch <default branch> --event push --workflow '<main_workflow>' --json databaseId,headSha`,
238
+ and match `headSha` to the merge SHA. No push run after 10 min: go to step 3.17 now;
239
+ `deploy-verify` records D1 failed.
240
+ - Wait for it:
241
+ `GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" ci-watch --run <id> --repo <owner/name>`.
242
+ It prints a `RESULT:` line when the run ends (pass or fail); then go to step 3.17, which reads the
243
+ result itself. Exit 3: run it again. Exit 4: a usage error; stop and report.
244
+ 17. Run the deploy check D1-D5 (design 4). Add `--docs-only` only when the evidence that step 3.12
245
+ recorded in `--why-waiting` says docs-only: the `docs_only.signal` job result in the PR's CI run, or
246
+ the file list from `gh pr view <n> --json files`. Never decide it from the title or a session's word.
247
+ When that evidence is not available (for example after a restart), decide again from
248
+ `gh pr view <n> --json files`: docs-only only when every changed file ends in `.md` (or is under a docs
249
+ path that the config's `docs_only` block names). Say in your report that you decided it again. When in
250
+ doubt, do not pass `--docs-only`.
251
+ ```
252
+ GH_TOKEN=$(gh auth token -u <account>) python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" deploy-verify --repo <R> --pr <n> --merge-sha <full merge sha> --config-path <repo root> --json
253
+ ```
254
+ It records the result. Exit 0 (`health: ok`): pass. Exit 1 with `health: pending`: the run has not
255
+ ended; wait for it (step 3.16) and call it again. Exit 1 with `health: failed`: section 5 step 1.
256
+ Exit 1 with no JSON on stdout (an `error:` line on stderr): a usage or state problem, for example a
257
+ merge SHA that is not the recorded one. Stop and report the line. Exit 2: fix the call.
258
+ 18. D6 (render check) comes from the PR's session (`render-check ...`). D7 (a post-deploy data job)
259
+ is recorded when the job ends:
260
+ ```
261
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" deploy-check set --repo <R> --pr <n> --health <ok|failed> --json
262
+ ```
263
+ A failed D6 or D7: section 5 step 1.
264
+ 19. Tell the PR's session and every live peer: `merged <PR> <merge sha>`, then `deployed <PR> ok`. Tell a
265
+ stacked PR's session to retarget to the default branch (design 3). Go back to step 3.4.
266
+
267
+ Stalls (design 7.4): one nudge for a busy, silent session that holds a queue item, and for a green,
268
+ `CLEAN` PR with no `ready` message; tell the owner when a waiting session blocks the queue.
269
+
270
+ ## 4. Approvals (design 6)
271
+
272
+ 1. Routine gates are yours. Record each one:
273
+ ```
274
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" ledger record --repo <R> --what '<gate>' --decision approve --by coordinator --window <N> --reason '<why it passes>' --json
275
+ ```
276
+ 2. The owner-only list (design 6, plus config `owner_reserved`) goes to the owner. Never approve it
277
+ yourself. Send it with:
278
+ ```
279
+ recommendation: approve | hold
280
+ reason: <one or two sentences>
281
+ evidence: <cost estimate | test or CI result | risk | how to undo>
282
+ ```
283
+ 3. A recommendation is never an approval. Only the owner's click in the hub or the owner's own words in
284
+ this window approve. Record the owner's answer with `ledger record --by owner --window <N>`.
285
+ 4. Hotfix marks come only from the owner (design 2). You may recommend one.
286
+
287
+ ## 5. Failures (design 8) and stalls (design 7.4)
288
+
289
+ 1. A failed deploy check (D1-D7, design 4, design 8.4): freeze the queue. Send no merge warnings and
290
+ no `go`, write no ack file, and do no merges. For an item in `merging`, first run
291
+ `gh pr view <n> --json state,mergeCommit`. `MERGED`: do step 3.15 (`queue merged`) for it before you
292
+ freeze, then its deploy check (steps 3.16 and 3.17). Not merged: delete the ack file and move it to
293
+ `ready`. Set every item's why-waiting with
294
+ `queue set --repo <R> --pr <n> --why-waiting 'queue frozen: deploy check failed for #<n>'`, tell every live session
295
+ `frozen: <reason>`, read the per-job conclusions, and send the owner a recommendation (revert or
296
+ hold). The owner decides a rollback; a rollback is a revert PR through the queue. Record an incident
297
+ with `/caddis:rca`. Unfreeze only when the fix or revert passes its own deploy check, or the owner
298
+ says so. Record an owner unfreeze in the ledger (step 1.9 reads it after a restart), then send
299
+ `unfrozen`:
300
+ ```
301
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" ledger record --repo <R> --by owner --what 'unfreeze after #<n>' --decision approve --reason '<owner words>' --window <N> --json
302
+ ```
303
+ 2. CI red (design 8.1): `queue set --repo <R> --pr <n> --state blocked` with the failed job's name. One re-run for a flaky
304
+ job (`gh run rerun <id> --failed`, design 8.2).
305
+ 3. A run stuck or a runner suspected (design 8.3): check
306
+ `https://www.githubstatus.com/api/v2/summary.json` first. During a GitHub incident, wait. Never
307
+ restart a runner or a service.
308
+ 4. Duplicate copies (design 8.6) show as `session` exit 1. Tell the owner the other pid. Stop a process
309
+ only when the owner asks in this window, and record that in the ledger.
310
+ 5. A single exit 1 from `heartbeat` or `intake` (a watcher `"error"` line) can be transient. Act only
311
+ when it repeats.
312
+ 6. Pilot notes. D3 runs `git fetch --tags`, which can need git credentials in this session; then D3
313
+ fails. A short gh or curl failure in D1 or D5 records `failed` and freezes the queue. That is the
314
+ safe direction: the owner checks the deploy and unfreezes (step 5.1).
315
+
316
+ ## 6. Stop
317
+
318
+ 1. Stop the watcher: stop its background Bash task.
319
+ 2. Release the lock:
320
+ ```
321
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_coordinate.py" lock release --repo <R> --pid <P> --json
322
+ ```
323
+ 3. Send every live session on the repo: `coordinator <N> stopped for <repo>. Do not merge until a coordinator is live again.`
324
+
325
+ ## 7. Rules that never bend
326
+
327
+ - Merge only on `ci-watch` `RESULT: pass` at the watched head, with `--match-head-commit <full sha>`.
328
+ Never merge while a default-branch CI run is in progress.
329
+ - Never write `coordinator.json` by hand; `lock acquire` creates or claims the state. Exit 3 means
330
+ stop. Always pass `--repo <R>`.
331
+ - Use `queue next`; never sort the queue yourself. Read `held` again just before a merge.
332
+ - Write the ack file last, just before `go` or `gh pr merge`. Delete it when the item leaves `merging`.
333
+ - Free text goes in single quotes. Long waits run in the background, never a foreground sleep.
334
+ - `queue merged` exit 0 `already recorded` is success.
335
+ - Liveness is the lock file and `heartbeat_at`, not a peer's word.
336
+ - Owner-only approvals go to the owner. Never self-approve them.
337
+ - No stress tests and no load tests.
338
+ - `GH_TOKEN=$(gh auth token -u <account>)` per repo account. Never `gh auth switch`.
339
+ - Never add the `--admin` flag to a merge. Never force push. Never restart services.
@@ -12,6 +12,9 @@ If `$ARGUMENTS` is empty, ask what to plan and stop.
12
12
  The plan file is the **durable spine of the harness** — it must let any future session (or another
13
13
  agent, on any tool) resume with zero re-discovery. Optimize for that.
14
14
 
15
+ This command is the only plan writer in caddis. The task-level shape, Global Constraints, Review Focus and
16
+ the self-review checklist are adapted from obra/superpowers `writing-plans` 7.0.0 (MIT); see NOTICE.md.
17
+
15
18
  ## Headless mode
16
19
  When the invocation contains the marker **`HEADLESS RUN RULES`**, **or `CADDIS_HEADLESS` is set in
17
20
  the environment** (the caddis OSS launchers export it on claude's `-p`/`--print`) — a non-interactive
@@ -186,6 +189,29 @@ internal repo path into stakeholder-facing copy, and set a `live` status flag th
186
189
  framework's own "Soon" badge). `tsc` clean, 632/632 tests green, cross-review CLEAN, exit gate
187
190
  satisfied. A headless run following it literally would have committed both, entirely green.
188
191
 
192
+ ### Choose how each phase's tasks run (`Execution:`)
193
+ Write one `Execution:` line per phase, next to its `Lane:` and model lines. A fixed rule picks it:
194
+ - **`Execution: per-task`** when the phase has 3 or more tasks that consume each other's interfaces,
195
+ OR when a mistake would be costly: security, data loss, money, merges or deploys, concurrency.
196
+ - Otherwise `Execution: inline`.
197
+
198
+ `inline` means today's behaviour: one implementer does the whole phase. `per-task` means the executor
199
+ gives each task a fresh implementer (on the phase's model tier and lane) and a short task review (spec
200
+ compliance, then quality) before the next task starts. The task review does not replace the phase's review by another vendor:
201
+ `review-tier` and `/caddis:cross-review` still run once per phase.
202
+
203
+ A `per-task` phase is split into tasks. Each task must have:
204
+ - **`Files:`** — every file to create, modify and test, with exact paths.
205
+ - **`Interfaces:`** — `Consumes:` (what it uses from earlier tasks) and `Produces:` (what later tasks
206
+ use): exact names, parameters and return types. An implementer sees only its own task; this block is
207
+ how it learns the names that its neighbours use.
208
+ - **Steps** — write the failing test (its name, its assertions and the exact values from the spec); run
209
+ it, expected FAIL; implement the named signature; run it, expected PASS; commit with named files.
210
+
211
+ A task is the smallest unit that has its own test cycle and is worth its own review. Put setup, config
212
+ and docs steps in the task whose deliverable needs them. An `inline` phase may keep the lighter shape of
213
+ the phase template.
214
+
189
215
  ### Group phases into MILESTONES — only when the plan is big enough
190
216
  **Add milestones when the plan has ~8+ phases OR spans multiple sessions.** Below that, phases and a
191
217
  Tracker are enough; a three-phase feature gains nothing from ceremony.
@@ -240,6 +266,16 @@ Creating Model: <model-id>
240
266
  ## Constraints & decisions
241
267
  - <key tech decision + rationale>
242
268
 
269
+ ## Global Constraints
270
+ <The project-wide requirements: version floors, dependency limits, naming and copy rules, platform needs.
271
+ One line each, with the exact values copied from the spec or PRD. Every phase's requirements include
272
+ this section.>
273
+
274
+ ## Review Focus
275
+ <Up to five inputs or failure modes that the spec implies but no task test exercises, most likely first.
276
+ One line each: the input or condition, and the behaviour a user expects. Each line gets a test, added
277
+ to the phase or task that owns the code. Write "none found" only after you checked.>
278
+
243
279
  ## Phases
244
280
 
245
281
  ### Phase 1 — <name> ⏳
@@ -249,6 +285,7 @@ Creating Model: <model-id>
249
285
  **Model:** <tier> (`/model <alias>`) — <one-line rationale tied to this phase's difficulty>
250
286
  **Lane:** claude — this session | glm-headless — `claude-glm -p "/caddis:implement <this plan's path> — Phase 1 only"`
251
287
  **Type:** backend | hard | ui | tests | docs *(required when the Lane runs through caddis_lanes.py)*
288
+ **Execution:** inline | per-task *(the rule in Step 2; per-task phases list their tasks below)*
252
289
  **Session:** continue | **fresh** (start a new session for this phase — note why: lane change / heavy context)
253
290
  **Risk:** high — token-verification seam; a wrong answer here is a silent auth bypass *(omit this line when normal)*
254
291
  **Goal:** <one sentence>
@@ -264,6 +301,18 @@ Creating Model: <model-id>
264
301
  **Then:** update the Tracker row (status + hash). If this phase ends the session, WRITE the handoff
265
302
  yourself (relay + Tracker) before stopping — do not hand back "run `/caddis:handoff`" as the next action.
266
303
 
304
+ #### Task 1.1 — <name> *(per-task phases only)*
305
+ **Files:** create `<path>` · modify `<path>` · test `<test path>`
306
+ **Interfaces:**
307
+ - Consumes: `<name>(<params>) -> <return type>` from Task <N.M>, or "none"
308
+ - Produces: `<name>(<params>) -> <return type>`
309
+ **Steps:**
310
+ 1. Write the failing test `<test path>::<case>`: assert `<call>` == `<exact value from the spec>`.
311
+ 2. Run `<literal test command>`: expected FAIL (<the reason, not an import error>).
312
+ 3. Implement `<name>(<params>) -> <return type>` in `<path>`.
313
+ 4. Run `<literal test command>`: expected PASS.
314
+ 5. Commit: `git add <named files>` then `git commit -m "<message>" -- <named files>`.
315
+
267
316
  ### Phase 2 — <name> 🔲
268
317
  <same structure>
269
318
 
@@ -278,6 +327,8 @@ Three rules: one attempt, one review.
278
327
 
279
328
  - One phase at a time: read this plan → run the phase on its **Lane** → RED→GREEN→VERIFY→CROSS-REVIEW→
280
329
  COMMIT → update the **Tracker** row. The Tracker is the resume signal for every future session.
330
+ An `Execution: per-task` phase runs task by task: a fresh implementer per task, and a task review
331
+ before the next task. The cross-review still runs once for the phase.
281
332
  - **Session boundaries:** the executor WRITES the handoff (durable `relay.md` + Tracker) after any
282
333
  phase that ends a sitting — it performs the work, it does not suggest the command. Start a FRESH
283
334
  session (or `/clear`) before any phase marked `Session: fresh`, before a lane change, or whenever
@@ -360,6 +411,8 @@ done:
360
411
  spawning another one forever; the `Phase N only` suffix is what stops the child running to the end
361
412
  of the plan. The command is executed verbatim by a model with no slack to notice a missing flag,
362
413
  and `/caddis:implement` now refuses a command without `-p` rather than running it.
414
+ - **Execution named** — every phase has an `Execution:` line set by the rule in Step 2, and every task
415
+ of a `per-task` phase has `Files:`, `Interfaces:` and its five steps.
363
416
  - **caddis_lanes command carries --phase** — a `caddis_lanes.py` launch command must name `--phase N`
364
417
  and `--type`; lane-check refuses one without `--phase`.
365
418
  - **Gate-detectable** — if this phase's instruction were WRONG, its exit gate would go red. A phase
@@ -369,6 +422,16 @@ done:
369
422
 
370
423
  If any phase relies on the implementer *reasoning out* a gap, close the gap in the plan now.
371
424
 
425
+ **Self-review (run it yourself after you write the plan, then fix what it finds in place):**
426
+ - **Spec coverage** — each requirement in the spec or PRD maps to a phase or task. Add a task for a gap.
427
+ - **Step scan** — no step that decides nothing ("TBD", "handle edge cases", "add validation", a name
428
+ that no task defines). No full function body that the signature and the tests already determine.
429
+ - **Name consistency** — a name, signature or field in a later task matches the one an earlier task's
430
+ `Produces:` defines.
431
+ - **Review Focus coverage** — each Review Focus line has its test in the task that owns the code.
432
+ - **Proportion** — a plan several times longer than its spec is a transcript of the code. Replace the
433
+ bodies with signatures, test names and assertions.
434
+
372
435
  A `risk: high` phase gets two independent review runs: run by the roadmap/plan runner automatically (from caddis-next Phase 11), and by hand in an interactive `/caddis:implement` run (`caddis_review.py --plan <plan> --phase <n> --runs 2`, from Phase 10). Only a `blocking` finding fails that review (exit 1); `should-fix` and `nit` findings go on a follow-up list and do not stop the merge. A phase gets 3 completed review rounds (a run that did not complete uses none); the 4th ends with exit 5, "stop, the owner decides". The advisory hub is retired; do not plan a separate validation session.
373
436
 
374
437
  ## Programme triggers
@@ -381,7 +444,7 @@ Small work that does not meet a trigger skips its block.
381
444
 
382
445
  ## Step 4 — Report
383
446
  Output the plan path (`.caddis/plans/<feature-slug>.md`), the phase list (one line each, with each
384
- phase's model tier + lane), confirm the local-coder gate passed (or list the phases that need
447
+ phase's model tier + lane + execution), confirm the local-coder gate passed (or list the phases that need
385
448
  tightening), and: *"To start: `read the plan and implement Phase 1`. To resume later: `/handoff` at
386
449
  session end, then `read relay.md` next time. The plan's Execution protocol carries every command —
387
450
  nothing to memorize."*
@@ -236,6 +236,17 @@ report — do not redo completed work.
236
236
  every phase after the first, which is the original bug wearing a different hat. For a
237
237
  `caddis_lanes.py` launch command, follow "Multi-lane phases" above instead of spawning the command
238
238
  blind.
239
+ - **EXECUTION** — read the phase's `Execution:` line. A missing line means `inline` (older plans
240
+ have none). `inline`: run the steps below for the whole phase, as before. `Execution: per-task`:
241
+ run the phase's tasks in order. Give each task a fresh implementer: a subagent on the phase's
242
+ lane and model, with a brief that holds the task's `Files:`, `Interfaces:` and steps, the plan's
243
+ Global Constraints and the test-economy lines (Multi-lane item 7b). If you cannot dispatch a
244
+ subagent, implement the task yourself and note that in the review file. After each task, run a
245
+ short task review before the next task: first spec compliance (the task's files, names and tests
246
+ match the plan), then quality (the author self-check). Fix blocking findings, then go on. Record
247
+ each task (name, commit, review result) under `## Task progress` in the review file. Then run the
248
+ phase's normal gates: VERIFY, the `review-tier` review by another vendor, COMMIT and the Tracker.
249
+ The task reviews never replace that review.
239
250
  - **RED** — write the failing test(s) the phase names (`<test file>::<case>`). Run them; confirm they
240
251
  fail for the right reason (the missing behavior, not an import error). If a phase genuinely has no
241
252
  testable surface, say so in the review file and implement the minimal change directly.
@@ -348,6 +359,9 @@ branch: <current branch>
348
359
  ## Assumptions made
349
360
  - <any gap the plan left that you decided — or "none">
350
361
 
362
+ ## Task progress *(only for `Execution: per-task` phases)*
363
+ - <phase N task N.M — commit <hash> — task review: clean | fixed <finding>>
364
+
351
365
  ## Not done / follow-ups
352
366
  - <phases skipped/blocked and why — or "none; all phases complete">
353
367
 
@@ -33,6 +33,15 @@ impactful finding** — the one most likely to reduce rate-limit consumption or
33
33
 
34
34
  Do NOT paraphrase every finding in prose — the report is already the output.
35
35
 
36
+ ## Step 2b — check the borrowed upstream skills
37
+
38
+ Run `python "<scripts folder>/caddis_upstream_check.py"` from the project root (same folder as
39
+ `usage_review.py`). It compares the upstream skills that caddis borrowed ideas from with the copies
40
+ installed on this machine. Show every `DRIFT` line in the report under **Upstream drift**. A `DRIFT`
41
+ line means: review the upstream diff, port what fits, then update `.github/upstream-sources.json`.
42
+ Say nothing for `UP-TO-DATE`, `NOT-INSTALLED` or `NO-SOURCES` (no record: not the caddis source repo).
43
+ The check never fails the review.
44
+
36
45
  ## Step 3 — offer to apply
37
46
 
38
47
  For each finding marked **[apply]** in the report, ask the user whether to apply it.