@skitterbyte/skitterspec 21.0.0 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/MIGRATION.md +218 -0
  2. package/README.md +127 -6
  3. package/assets/claude-md-section.md +42 -18
  4. package/assets/commands/allow-main.md +33 -0
  5. package/assets/commands/spec-remote-review.md +22 -0
  6. package/assets/commands/spec-skip.md +24 -0
  7. package/assets/core/env.config.json.example +6 -3
  8. package/assets/core/env.config.md +183 -25
  9. package/assets/hooks/main-guard.cjs +145 -0
  10. package/assets/hooks/review-gate.cjs +8 -3
  11. package/assets/review/page.html +1174 -101
  12. package/assets/rules/offered-unblocks.md +100 -0
  13. package/assets/rules/spec-planning.md +195 -14
  14. package/assets/rules/spec-reports.md +181 -29
  15. package/assets/skills/no-spec/SKILL.md +219 -0
  16. package/assets/skills/spec/SKILL.md +248 -21
  17. package/assets/skills/spec-bug/SKILL.md +34 -24
  18. package/assets/skills/spec-cancel/SKILL.md +29 -0
  19. package/assets/skills/spec-complete/SKILL.md +25 -0
  20. package/assets/skills/spec-diff/SKILL.md +164 -37
  21. package/assets/skills/spec-hotfix/SKILL.md +22 -19
  22. package/assets/skills/spec-init/SKILL.md +4 -2
  23. package/assets/skills/spec-next/SKILL.md +166 -58
  24. package/assets/skills/spec-review/SKILL.md +87 -0
  25. package/assets/skills/spec-reviewed/SKILL.md +31 -5
  26. package/assets/skills/spec-start/SKILL.md +33 -0
  27. package/package.json +1 -1
  28. package/src/cli.js +1612 -172
  29. package/src/env/classify.js +87 -2
  30. package/src/env/config.js +333 -19
  31. package/src/env/hooks.js +102 -33
  32. package/src/env/live.js +124 -1
  33. package/src/env/mainguard.js +240 -0
  34. package/src/env/provision.js +106 -10
  35. package/src/env/registry.js +70 -6
  36. package/src/env/resolve.js +128 -5
  37. package/src/env/review.js +854 -29
  38. package/src/env/reviewers/coderabbit.js +228 -0
  39. package/src/env/reviewers.js +517 -0
  40. package/src/env/serve.js +474 -50
  41. package/src/env/supervise.js +8 -1
  42. package/src/env/teardown.js +12 -1
  43. package/src/init.js +185 -24
@@ -35,8 +35,8 @@ A verdict sentence, a blank line, then a two-column table:
35
35
  | **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · phase 2 moved |
36
36
  | **Branch** | `spec/feat-foo` · 3 commits, clean |
37
37
  | **Built** | POST /orders handler, orders schema |
38
- | **Tests** | 128 passed · npm test |
39
- | **Review** | 7 files, +212 −18 · [open the page](file:///…) |
38
+ | **Tests** | 128 passed · dotnet test |
39
+ | **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) · **remote** off |
40
40
  | **Follow-ups** | none |
41
41
  | **Next** | `/spec-next` → phase 3 (Auth) |
42
42
 
@@ -87,23 +87,95 @@ the options may be offered after the table.
87
87
 
88
88
  ## ⏸ Review ready — 7 files, +212 −18
89
89
 
90
- **[Open the page](http://…)** · I'm holding here until you send a verdict.
90
+ - **local** — [http://127.0.0.1:7760/7e9e123e7540/feat-orders](http://127.0.0.1:7760/7e9e123e7540/feat-orders)
91
+ - **network** — [http://192.168.0.136:7760/7e9e123e7540/feat-orders](http://192.168.0.136:7760/7e9e123e7540/feat-orders)
92
+ - **remote** — off · `/spec-remote-review` turns it on
93
+
94
+ live: off — /spec-live to put it live
95
+
96
+ I'm holding here until you send a verdict — the wait covers local and network.
91
97
 
92
98
  `/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
93
99
 
94
100
  ---
95
101
 
96
- A rule, a heading naming the state and the size, the link in bold, one line
97
- saying the run is stopped, and the two exits. Nothing else, and never a
98
- paragraph explaining it.
102
+ A rule, a heading naming the state and the size, **the stack**, the `live:`
103
+ line, one line saying the run is stopped and which tiers it covers, and the two
104
+ exits. Nothing else, and never a paragraph explaining it.
105
+
106
+ **The `live:` line, and why a rigid contract took a second amendment.** Reading
107
+ a diff is not the only way to judge a change — often the question is whether it
108
+ *works* — and the answer to that lives at the running URL. A reader deciding
109
+ whether to open the page at all wants to know whether it is already running
110
+ somewhere, and finding that out any other way is a trip to a terminal in the
111
+ middle of a review.
112
+
113
+ **Each line names a command a person types**, rather than describing a
114
+ capability. `live: off — the page can put it live` told the reader that a page
115
+ somewhere could do it and left them to find the verb — and the verb is what they
116
+ need when they are in a terminal rather than on the page. So the lines say
117
+ `/spec-live`, `/spec-live main`, `/spec-remote-review`. Making them *clickable*
118
+ was considered and rejected: the only clickable thing markdown has is a URL, and
119
+ a URL that acts when it is **fetched** is one a link previewer or a prefetcher
120
+ fires with nobody involved.
121
+
122
+ It is the engine's line, copied like the stack: `spec-env review` prints it and
123
+ `--json` carries the same answer as `live`, from one function, so the report and
124
+ the command cannot disagree. Four states, and only three of them are a line —
125
+ `on` (with the URL when the project configured one), `off`, `held` (naming the
126
+ spec holding the workbench). The fourth, `unavailable`,
127
+ **prints nothing at all**:
128
+ a project with no isolation and a spec with no worktree both land there,
129
+ and a line about a surface that does not exist is an accusation against a
130
+ healthy repo.
131
+
132
+ **This is the second amendment to this section in two specs**, which is worth
133
+ naming rather than letting pass. The contract earns its rigidity from real
134
+ failures, and two amendments in a row is how a rigid contract quietly becomes a
135
+ negotiable one. Both have carried their reason and both replaced a line rather
136
+ than adding a paragraph.
137
+ **A third should have to argue harder than either did.**
138
+
139
+ **The stack is the engine's, copied rather than composed.** `spec-env review`
140
+ prints one line per tier and `--json` carries the same thing as `tiers`, so take
141
+ it from there in the order it comes — and do not decide which tiers are worth
142
+ mentioning. The whole reason the stack exists is that the engine was being asked
143
+ to guess where the reader was sitting, and it guessed wrong three separate ways
144
+ in one day; a skill re-deciding that in prose is the same guess one layer up.
145
+
146
+ **Three tiers, always all three, in this order.**
147
+
148
+ | Tier | Is | Off when |
149
+ |------|----|----------|
150
+ | `local` | the loopback URL — the machine the run is on | never. With nothing served it is the `file://` page, and says it cannot send a verdict |
151
+ | `network` | the LAN URL — a phone on the same wifi | `review.allowNetwork` is false, or this machine has no network address |
152
+ | `remote` | a published page, for a reader off the network | `review.allowRemote` is false, or nothing has been published |
153
+
154
+ A tier that is off **keeps its line** and names the one command that turns it
155
+ on. Dropping the line is what left a reader who had walked out of the house with
156
+ nothing on screen to tell them a remote page was even possible — they could not
157
+ ask for what they could not see.
99
158
 
100
159
  **Only promise a wait the transport can deliver.** The wait itself is real in
101
160
  every case; what varies is what carries it back, not whether the run is
102
161
  stopped. Three transports, three true sentences:
103
162
 
104
- - **A page the engine SERVES** hands its pass to the local store, which a
105
- file-watch sees. The run is woken by the press:
163
+ - **A page the engine SERVES** hands its pass to the local store, and
164
+ `spec-env review wait` returns when it lands. The run is woken by the press:
106
165
  *"I'm holding here until you send a verdict."*
166
+
167
+ **That sentence is only true for as long as the wait is actually running**, so
168
+ the wait is the engine's and never one composed for the occasion. A watcher
169
+ written per run is proven by nothing: one was written
170
+ `until [ -f "$P" ] && [ "$x" \> "$y" ]` — valid bash, a syntax error in zsh,
171
+ a predicate that could never be true — and it spun for five minutes while the
172
+ report underneath it said the run was holding.
173
+ **Silence is what made that expensive**:
174
+ a wait that cannot fire and one patiently working look identical,
175
+ so nothing seemed wrong until the reader asked. Give the wait no timeout
176
+ either; it lasts as long as the session, because a reader who walks away from
177
+ a diff is the normal case and a bounded watch once lost a verdict to a lunch
178
+ break.
107
179
  - **A `file://` page** has no server to POST to, so the pass is copied and
108
180
  pasted — the reader's next message is what carries it. The run is stopped just
109
181
  the same, and says so:
@@ -112,10 +184,16 @@ stopped. Three transports, three true sentences:
112
184
  not a lesser one, and it is not an excuse to ask without waiting.
113
185
  - **A PUBLISHED page** writes to the artifact's own store, and nothing pushes
114
186
  from there into the conversation. That is the one case where a verdict is
115
- genuinely invisible until someone asks for it, so the banner says that instead
116
- of claiming a watch:
187
+ genuinely invisible until someone asks for it, so its line in the stack says
188
+ that rather than letting the holding line speak for it:
189
+
190
+ - **remote** — [https://…](https://…) · a verdict here needs `/spec-reviewed`
117
191
 
118
- **[Open the page](https://…)** · press a verdict, then type `/spec-reviewed` — I cannot see it until you do.
192
+ Where `remote` is the only tier a reader can reach, the holding line says so
193
+ too: *press a verdict, then type `/spec-reviewed` — I cannot see it until you
194
+ do.* Where a served tier is reachable as well, the holding line keeps naming
195
+ the tiers the wait covers, and `remote`'s own caveat sits on `remote`'s line
196
+ where it belongs.
119
197
 
120
198
  **A harness with no file-watch does not get an exemption.** It once read
121
199
  "change nothing" — keep the row, keep the question, do not wait — and that
@@ -123,15 +201,57 @@ escape hatch is what let a whole class of runs go on asking questions nobody was
123
201
  listening for. Where a watch is unavailable the wait is the turn ending, which
124
202
  every harness can do.
125
203
 
204
+ **A watch that dies is re-armed, silently, on the same window.** A backgrounded
205
+ wait does not always survive a long idle: three sessions lost theirs over one
206
+ lunch break, and two in another were killed at 43 and 30 minutes. So a wait that
207
+ ends **without a verdict** and **was not stopped by the operator** is started
208
+ again on the **same** `--since`. Not a fresh one — a pass that arrived during
209
+ the gap is still inside the original window, and re-arming on a new timestamp
210
+ would strand exactly that pass.
211
+
212
+ **Say nothing when you do it.** Nothing happened and the window is unchanged; a
213
+ line on every death turns a working loop into a stream of non-events.
214
+
215
+ **An operator who stopped it is not a death.** Re-arming over a deliberate stop
216
+ makes the wait unstoppable, which is worse than a wait that stops. Where the two
217
+ cannot be told apart, treat it as deliberate and leave it
218
+ (`.claude/rules/negative-checks.md` rule 4): the cost of being wrong is one
219
+ `/spec-reviewed`, against a loop nobody can break.
220
+
221
+ **It is bounded by the age of the window, not by a count of retries.** Keep
222
+ re-arming while the window is younger than **12 hours**, then stop. A count means
223
+ something different depending on whether deaths come five minutes or forty-five
224
+ apart, and the observed interval already varies that much. Twelve hours
225
+ deliberately does not cover an overnight gap — the pass is never lost, and
226
+ `/spec-reviewed` is the designed recovery — because the alternative is waking
227
+ the session every half hour all night for a review nobody is returning to
228
+ before morning.
229
+
230
+ **Past the bound the banner degrades**, to the sentence this file already
231
+ defines for a transport that cannot push into the conversation:
232
+
233
+ *press a verdict, then type `/spec-reviewed` — I cannot see it until you do.*
234
+
235
+ That is a new **state** for an existing sentence, not a new sentence. The
236
+ holding line is only ever printed while a wait is actually running, which is the
237
+ rule this section has always carried: *only promise a wait the transport can
238
+ deliver.*
239
+
126
240
  **The `Continue` ending, and why the banner's exits differ mid-run.** A page
127
241
  rendered part-way through a run offers `Continue` — *I have read it, carry on* —
128
242
  in place of the committing verdicts, because "commit" is the wrong verb for
129
- unfinished work. Its banner names the same state and different exits:
243
+ unfinished work. Its banner names the same state and the same stack, and
244
+ different exits:
245
+
246
+ …the stack, unchanged…
130
247
 
131
- **[Open the page](http://…)** · I'm holding here until you send a verdict.
248
+ I'm holding here until you send a verdict — the wait covers local and network.
132
249
 
133
250
  `Continue` carries on · `Request changes` works them now
134
251
 
252
+ The stack is written out once, above, and referred to everywhere else. Two
253
+ copies of it is how the two come to disagree about which tiers exist.
254
+
135
255
  **It is not the `none` verdict that was removed.** `none` recorded itself and
136
256
  did nothing, which is the record-and-do-nothing ending this contract exists
137
257
  against. `Continue` **resumes the run** — it names an action, which is the bar
@@ -145,13 +265,40 @@ block. *Waiting* is what any offer does, and it is what this section governs.
145
265
  that is finished. A mid-run render waits without arming — walking away from it
146
266
  costs nothing, which is exactly why `Continue` is safe to offer there.
147
267
 
148
- **Exactly one link, never two.** The banner offers the page the reader can
149
- actually use, and the engine has already decided which that is — a served URL
150
- when it could serve, a published one only when it could not. Handing over both
151
- asks the reader to know which door the run is standing behind, and they cannot:
152
- it was done, and a verdict was pressed on the published page three times while
153
- each one sat unread under a line claiming the run was waiting. Two links is not
154
- a convenience with a caveat; it is the caveat existing at all.
268
+ **One link per reachable store, each labelled.** This amends
269
+ *"exactly one link, never two"*, and the evidence that rule was written on is
270
+ the reason it still reads the way it does. Two links were handed over, the wait
271
+ stood behind only one of them, and a verdict was pressed on the published page
272
+ three times while each one sat unread under a line claiming the run was waiting.
273
+ Two links was not a convenience with a caveat; the caveat existing was the bug.
274
+
275
+ **What that evidence is about is the store, not the count.** A served page hands
276
+ its pass to the local pending store, which `spec-env review wait` watches; a
277
+ published page writes to the artifact's own store, which nothing here can see.
278
+ Two links into two stores asks the reader to know which door the run is standing
279
+ behind, and they cannot.
280
+
281
+ **Local and network are two doors into one room.** The page POSTs with
282
+ `fetch(location.pathname, …)`, so a page opened at `127.0.0.1:7760/…` and one
283
+ opened at `192.168.0.136:7760/…` reach the same server process and the same
284
+ pending store. One wait covers both, and a verdict pressed on either wakes the
285
+ same run. Between those two the objection does not apply — which is why both are
286
+ offered, and why offering them gives nothing up.
287
+
288
+ **So two things are required, and the second is what the old rule lacked.**
289
+ Every link is **labelled** with its tier, and one line says
290
+ **which tiers the wait covers**.
291
+ Two *unlabelled* links stay forbidden exactly as before, because
292
+ the reader still cannot tell them apart.
293
+
294
+ **`remote` keeps the caveat it always had.** It is listed so a reader off the
295
+ network knows the surface exists and what turns it on — never as a door the wait
296
+ is claimed to cover. Where it is on and published, its line says what is true of
297
+ it: *a verdict here needs `/spec-reviewed`*.
298
+
299
+ **Still forbidden**, in the words of the rule this amends: offering two links
300
+ into **different** stores without saying which one the wait is watching. That is
301
+ the failure that cost three unread verdicts, and nothing here touches it.
155
302
 
156
303
  **This is not the old failure returning**, and the difference is the whole
157
304
  justification. The offer used to be two quoted lines in the tail of a long
@@ -247,7 +394,7 @@ behind it — say nothing rather than reporting that there was nothing to report
247
394
  | `Landed` | A fast-forward, a tag, a cherry-pick. |
248
395
  | `Worktree` | A worktree provisioned, entered, or torn down. |
249
396
  | `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
250
- | `Review` | The rendered diff page: files, `+`/`−`, the link. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
397
+ | `Review` | The rendered diff page: files, `+`/`−`, and the stack — the same tiers the banner lists, labelled, run together with `·` because a row is one cell, plus `live` where the engine reported one. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
251
398
  | `Follow-ups` | **Always.** `none`, or one line each. |
252
399
  | `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
253
400
 
@@ -311,10 +458,10 @@ fenced block **above** the table, where a fence means what a fence should mean:
311
458
 
312
459
  | | |
313
460
  |---|---|
314
- | **Why** | 3 assertions fail in `orders.test.js`; nothing committed |
461
+ | **Why** | 3 assertions fail in `test_orders.py`; nothing committed |
315
462
  | **Branch** | `spec/feat-foo` · 2 commits, dirty |
316
463
  | **Built** | POST /orders handler, orders schema |
317
- | **Tests** | 125 passed, 3 failed · npm test |
464
+ | **Tests** | 125 passed, 3 failed · pytest |
318
465
  | **Follow-ups** | none |
319
466
  | **Next** | fix the failures, then `/spec-next` to finish phase 2 |
320
467
 
@@ -341,11 +488,16 @@ finish, so it outlives the session:
341
488
  canonical port; not in scope here.
342
489
  ```
343
490
 
344
- **Say where the spec would be written.** These skills run inside the spec's own
345
- worktree, and a spec authored there physically lives on that branch — invisible
346
- from the base branch, and cancelled along with its host if the host is
347
- cancelled. So the offer names the primary checkout as the place to write it.
348
- `spec-planning.md` and `commit-trailers.md` carry the reasoning.
491
+ **The offer needs no caveat about where.** These skills run inside the spec's own
492
+ worktree, and a follow-up spec authored *there* would physically live on that
493
+ branch — invisible from the base branch, and cancelled along with its host. That
494
+ used to make the offer name the primary checkout as the place to write it.
495
+ It no longer does: `/spec` provisions the **new** spec's own worktree before it
496
+ writes anything and moves the session there, so the follow-up lands on its own
497
+ branch and its own ticket without anyone naming a location. Take the offer and
498
+ let `/spec` handle it. Where isolation is absent there is no worktree to move to,
499
+ and the old caveat applies — `spec-planning.md` and `commit-trailers.md` carry
500
+ the reasoning.
349
501
 
350
502
  ## Worked examples
351
503
 
@@ -357,7 +509,7 @@ cancelled. So the offer names the primary checkout as the place to write it.
357
509
  |---|---|
358
510
  | **Branch** | `spec/feat-foo` · 4 commits, clean |
359
511
  | **Built** | Auth middleware, session table |
360
- | **Tests** | 140 passed · npm test |
512
+ | **Tests** | 140 passed · go test ./... |
361
513
  | **Follow-ups** | Session expiry is read from two places; phase 4 leans on one |
362
514
  | **Next** | `/spec-next` → phase 4 (Docs) |
363
515
 
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: no-spec
3
+ description: Do a piece of work that genuinely has no spec — a version bump, a lockfile refresh, a rename — on its own branch in its own worktree, reviewed on a page and landed on the verdict. Never on the base branch. Use when the user says "/no-spec", or asks for mechanical work that does not warrant a spec and should not sit on main.
4
+ ---
5
+
6
+ # /no-spec — work with no spec, off the base branch
7
+
8
+ > Stay silent while this runs — speak only to ask something you cannot answer
9
+ > yourself, or to report a failure at the moment it happens. Read
10
+ > `.claude/rules/spec-reports.md` before reporting; it defines the block this
11
+ > skill ends with.
12
+
13
+ Some work has no spec and should not have one. Bumping four projects to a new
14
+ version, refreshing a lockfile, a rename across a package: writing a one-phase
15
+ document for it produces a spec nobody reads, and `spec --quick` would have been
16
+ that under a friendlier name.
17
+
18
+ **What it is not is an excuse to work on the base branch.** Two things follow
19
+ from doing it there, and both cost someone else something: the tree is left
20
+ dirty, so every in-flight spec has to replay over it and a release cannot be
21
+ cut; and no page is ever rendered, so the work gets no review at all. This skill
22
+ is the same lane every spec uses — branch, worktree, page, verdict, land —
23
+ with the document left out because there is nothing to put in it.
24
+
25
+ **It writes no spec, moves nothing through the lifecycle, and mints no ticket.**
26
+ There is no bucket, no status, no phase index and no `Refs:` trailer. If you
27
+ find yourself wanting any of those, the work wanted `/spec`.
28
+
29
+ ## 1. Name it
30
+
31
+ Take a kebab-case name from the invocation. It becomes the branch
32
+ (`chore/<name>`) and the worktree folder, so it should describe the work:
33
+ `bump-deps`, `rename-widget-props`, `lockfile-refresh`.
34
+
35
+ **Ask if none was given.** Do not invent one — a name nobody chose is a branch
36
+ nobody recognises a week later, and this is the one thing the skill cannot infer
37
+ from the work.
38
+
39
+ **Refuse a name that is already a spec**, which the engine does for you: two
40
+ things answering to one name is how a teardown removes the wrong tree.
41
+
42
+ ## 2. Provision
43
+
44
+ ```
45
+ skitterspec spec-env nospec <name>
46
+ ```
47
+
48
+ It records the name as a **specless branch** and prints the plan. Run the
49
+ commands it prints, then move this session into the worktree with a plain `cd`
50
+ — the Bash working directory persists between calls, so from here on the work
51
+ happens on its own branch.
52
+
53
+ **Confirm the move landed** rather than reading silence as success
54
+ (`.claude/rules/negative-checks.md` rule 1): `skitterspec spec-env resolve` with
55
+ no argument must name this branch, and report `no spec` as its bucket. If it
56
+ names something else, stop — say so and do nothing further, because the failure
57
+ this guards against is a session that writes a branch's worth of work into the
58
+ primary checkout while everything looks normal at the time.
59
+
60
+ **The record is not bookkeeping.** It is the only thing that makes a name with
61
+ nothing under `specs/**` resolvable, so without it `review`, `integrate`, `down`
62
+ and a bare `resolve` would all refuse this branch as a spec that does not exist.
63
+
64
+ **Where isolation is not configured** (`specs/.core/env.config.json` absent)
65
+ there is no worktree to make. Say so in one line and stop: this skill exists to
66
+ move work off the base branch, and without isolation there is nowhere to move it
67
+ to. Do not silently do the work in place — that is the outcome it exists to
68
+ prevent.
69
+
70
+ ## 3. Do the work
71
+
72
+ Follow the project's rules in `.claude/rules/*.md` and `CLAUDE.md`, exactly as a
73
+ phase would. Tests are part of the work and not after it: extend or add whatever
74
+ covers the change, then run the project's typecheck and test commands. Do not
75
+ proceed to the page until they are green — and if they cannot be made green, say
76
+ so where it happens and report `❌`.
77
+
78
+ **Mechanical does not mean untested.** A version bump that breaks a build is the
79
+ commonest thing in this lane, which is why the gate is the same one a phase has.
80
+
81
+ ## 4. Render the page and arm the gate
82
+
83
+ The work is done, the tests are green, and nothing is committed yet:
84
+
85
+ ```
86
+ skitterspec spec-env review <name> --buttons nospec --run-reviewers
87
+ ```
88
+
89
+ **This is free.** The engine reads git and splices text into a template; the
90
+ diff never passes through you however large it is.
91
+
92
+ `--buttons nospec` offers `Commit & Land` and `Commit`. It is its own set
93
+ because neither of the usual pairs fits: there is no next phase to continue and
94
+ nothing to put in flight.
95
+
96
+ **Then arm the gate:**
97
+
98
+ ```
99
+ skitterspec spec-env review arm <name>
100
+ ```
101
+
102
+ **This skill arms, and `/spec` does not.** That asymmetry is the rule in
103
+ `.claude/rules/spec-reports.md` rather than a choice made here: the gate asserts
104
+ that *finished work* owes an answer, and this is finished work. A written spec
105
+ owes no phase, so authoring arms nothing.
106
+
107
+ **Then offer `/spec-diff`. Do not run it.** The written review costs real tokens
108
+ because writing it means reading the diff, and that spend is the user's call.
109
+
110
+ ## 5. Wait for the verdict
111
+
112
+ **The wait is a command. Do not write one.**
113
+
114
+ 1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
115
+ the whole scope of what you may claim.
116
+ 2. **Run the engine's wait in the background, and end your turn:**
117
+
118
+ ```
119
+ skitterspec spec-env review wait <name> --since <the timestamp>
120
+ ```
121
+
122
+ Pass no timeout. A reader who walks away from a diff is the normal case, and
123
+ a bounded watch once lost a verdict to a lunch break.
124
+ 3. **On waking, let the engine pick:**
125
+
126
+ **A watch that dies is re-armed on the same window**, silently and bounded by
127
+ the age of that window — `.claude/rules/spec-reports.md` carries the contract,
128
+ including when to stop and what the banner says instead. Do not restate it
129
+ here.
130
+
131
+ ```
132
+ skitterspec spec-env review <name> --claim-since <the timestamp> --json
133
+ ```
134
+
135
+ It claims the one pass that arrived inside the window, acts on nothing when
136
+ none did, and refuses to choose when two did.
137
+
138
+ Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
139
+ three, in that order — never the bare `page:` path.
140
+ `.claude/rules/spec-reports.md` carries the shape and why every tier is named.
141
+
142
+ ### Route on the verdict
143
+
144
+ - **`commit-land`** — hand off to `review.commitWith` (`/commit` by default),
145
+ then land it, then tear the worktree down:
146
+
147
+ ```
148
+ skitterspec spec-env integrate <name>
149
+ skitterspec spec-env down <name>
150
+ ```
151
+
152
+ `integrate` plans a rebase onto the base branch and a `merge --ff-only`; run
153
+ the commands it prints. `down` removes the worktree, deletes the branch and
154
+ **forgets the specless record**, which is what stops a torn-down name going on
155
+ answering to every verb with a path that is no longer there.
156
+
157
+ **If the rebase conflicts, stop.** `git rebase --abort`, leave the commit on
158
+ the branch, tear nothing down, and report `❌` with the conflict — there is a
159
+ standing worktree, which is what separates `❌` from `⏸`.
160
+
161
+ - **`commit`** — the same commit, and **stop there**. The branch stands with the
162
+ work on it, which is the right ending for work that turned out to be bigger
163
+ than one sitting. Say in `Next` that `/spec-to-main` lands it later.
164
+ - **`changes`** — work the notes, record a resolution for each one so the next
165
+ render shows it struck through with what changed, then
166
+ **re-render and wait again**. The reader is still holding a decision.
167
+ - **`discuss`** — report and talk. Claim nothing, change nothing, tear nothing
168
+ down.
169
+
170
+ **Never tear the worktree down on anything but `commit-land`.** It is the only
171
+ verdict that says the work is finished, and the cost of being wrong is somebody
172
+ else's branch removed from disk.
173
+
174
+ ## Report
175
+
176
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
177
+ the shape; this section carries only what is specific here.
178
+
179
+ **Verdicts**
180
+
181
+ - `✅` — the work is done, the tests are green, and the verdict was acted on.
182
+ - `⚠️` — done and green, with something worth knowing.
183
+ - `❌` — the tests are red, or the land failed part-way. Quote the failure and
184
+ say what is standing.
185
+ - `⏸` — refused before acting: no name given, the name is already a spec, the
186
+ `cd` did not take, or the project has no isolation. Nothing changed.
187
+
188
+ **Fields:** `Branch` · `Built` · `Tests` · `Notes` · `Landed` · `Worktree` ·
189
+ `Review` · `Follow-ups` · `Next`
190
+
191
+ **No `Tracker` and no `Spec`, ever.** There is no ticket and no document, and a
192
+ row saying so would report an absence with nothing behind it — which
193
+ `spec-reports.md` forbids for exactly this reason.
194
+
195
+ `Landed` and `Worktree` appear only once a verdict has been acted on; a run
196
+ waiting on the page has neither, and **omits the `Review` row** in favour of the
197
+ banner, which carries the counts, the stack and the two exits:
198
+
199
+ ---
200
+
201
+ ## ⏸ Review ready — &lt;N&gt; files, +&lt;a&gt; −&lt;d&gt;
202
+
203
+ - **local** — &lt;the `local:` URL&gt;
204
+ - **network** — &lt;the `network:` URL, or off with the command that turns it on&gt;
205
+ - **remote** — &lt;the `remote:` URL, or off with the command that turns it on&gt;
206
+
207
+ I'm holding here until you send a verdict — the wait covers local and network.
208
+
209
+ `Commit & Land` finishes it · `Commit` keeps the branch
210
+
211
+ ---
212
+
213
+ There `Next` names the page rather than a command, because the button is what
214
+ carries the work on.
215
+
216
+ **`Follow-ups` is where a spec gets offered.** This lane is for work with no
217
+ spec, so the thing it most often surfaces is work that *does* want one. Offer to
218
+ `/spec` it on the spot — and `/spec` provisions its own worktree, so there is
219
+ no caveat about where to write it.