@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
@@ -148,12 +148,36 @@ Before writing any code for this phase, get the workspace clean:
148
148
  anything is owed (`.claude/rules/negative-checks.md`). Do not mention it: a
149
149
  line about a gate nobody armed is an accusation against a healthy repo.
150
150
 
151
+ **It may also declare the way out.** An `armed` answer can carry an `offer`,
152
+ and where it does, relay the refusal and **then** offer it — once — exactly as
153
+ `.claude/rules/offered-unblocks.md` describes, spending it with
154
+ `skitterspec spec-env review gate <spec> --offered` as you raise it. Where
155
+ there is no `offer`, the three exits in the `Next` row are the whole answer.
156
+
151
157
  **This refusal counts nothing.** It is not a tally of ticked boxes — those
152
158
  still gate nothing and still are not counted. It asserts one thing: a phase
153
159
  that ended has an answer. And the exit is always **one command**, one of them
154
160
  being *"I am moving on"*, which is what keeps this a push rather than a wall —
155
161
  a gate with no exit gets switched off wholesale instead of answered.
156
162
 
163
+ - **Report anything left waiting, and claim none of it.** Ask the engine once:
164
+
165
+ ```
166
+ skitterspec spec-env review waiting
167
+ ```
168
+
169
+ A pass listed here arrived when nothing was watching — a wait that never ran,
170
+ a session cleared, a terminal closed overnight — and no watcher can recover
171
+ those, however good. Relay what it prints: the spec, the code, the verdict and
172
+ the age, with `/spec-reviewed <code>` to pick one up and `--drop <code>` to
173
+ disown it.
174
+
175
+ **It is information, not a gate.** It never refuses, nothing here is
176
+ conditional on it, and **you never claim one** — `/spec-diff` §0 stands
177
+ unchanged, and a pass sitting there when you arrived was not sent to you.
178
+ Silent when nothing is waiting, which is the usual case: say nothing rather
179
+ than reporting that there was nothing to report.
180
+
157
181
  ## 3. Implement the phase
158
182
 
159
183
  Identify the **first unfinished phase** from the `00-overview.md` phase index,
@@ -262,13 +286,34 @@ the moment the page is about, so render it now — **after** the tests pass and
262
286
  **before** the commit:
263
287
 
264
288
  ```
265
- skitterspec spec-env review <spec>
289
+ skitterspec spec-env review <spec> --run-reviewers
266
290
  ```
267
291
 
268
292
  **This is free.** It is the engine reading git and splicing text into a template;
269
293
  the diff never passes through you, so a 266KB patch costs nothing. Report the
270
294
  path it prints and move on.
271
295
 
296
+ **`--run-reviewers` is free to *you* as well, and costs wall-clock.** Where the
297
+ project configured any (`review.reviewers` — empty in most projects, and then
298
+ this flag does nothing at all), the engine runs each one against the diff before
299
+ rendering, so the page is already populated when it is opened. Their findings
300
+ come back as JSON and land on the page as **checks**; the patch still never
301
+ passes through you. What it spends is 30s–3min once, after the tests are green —
302
+ which is the cheap half of a step that is about to wait on a person for far
303
+ longer. A re-render of unchanged work reuses the cached findings rather than
304
+ spending another review.
305
+
306
+ **Findings never gate.** A check is not a comment, so a page carrying twenty of
307
+ them still offers `Commit`; a reader who replies to one creates a comment, and
308
+ that does gate, because then a person has asked for something. Nothing here
309
+ counts them, and nothing here acts on them — you render and wait exactly as you
310
+ would have.
311
+
312
+ **A reviewer that could not run is reported by the page**, not by you. Do not
313
+ re-state its outcome, do not investigate it, and never treat it as a failure of
314
+ the phase: the strip on the page says `clean` or `did not run — <why>` for each
315
+ one, which is where a reader deciding whether to commit will look.
316
+
272
317
  **This render takes the committing button set**, which is the default — so
273
318
  `--buttons` is not passed. That is a statement about the work, not about the
274
319
  gate: the phase is finished, so `Commit` and `Commit & Continue` are the right
@@ -297,32 +342,66 @@ so one subject lives in one place:
297
342
 
298
343
  ---
299
344
 
300
- ## ⏸ Review ready — <N> files, +<a> −<d>
345
+ ## ⏸ Review ready — <N> files, +<a> −<d><, <n> flagged by <reviewer> — only where one found something>
301
346
 
302
- **[Open the page](<the `open:` URL>)** · I'm holding here until you send a verdict.
347
+ - **local** — <the `local:` URL>
348
+ - **network** — <the `network:` URL, or off with the command that turns it on>
349
+ - **remote** — <the `remote:` URL, or off with the command that turns it on>
303
350
 
304
- `/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
351
+ <the `live:` line, exactly as the engine printed it — omitted when it printed none>
352
+
353
+ I'm holding here until you send a verdict — the wait covers local and network.
305
354
 
306
- **One link, and the engine has already chosen it.** Where it served, the
307
- banner carries the served URL and the wait is real. Where it could not serve —
308
- a busy port, a machine with no network address — that is the case publishing
309
- exists for, and then the banner carries the published URL with what is true of
310
- it: *press a verdict, then type `/spec-reviewed`*, because nothing pushes from
311
- the artifact store into this conversation.
355
+ `/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
312
356
 
313
- **Never offer both.** Publishing while the server is reachable adds a second
314
- door the reader cannot tell apart from the first, and the wait only stands
315
- behind one of them — that was done, and three verdicts were pressed on the
316
- published page while each sat unread under a line saying I was holding. The
317
- published page is for the reader the server cannot reach, and for nobody
318
- else.
357
+ **The findings count goes in the COUNTS, and nowhere else.** Where `--json`
358
+ reported a reviewer in the `findings` state, its count joins the heading — the
359
+ slot that already says `7 files, +212 −18` — and that is the whole of it. No
360
+ extra line, no extra row, and nothing at all when every reviewer was `clean`,
361
+ `cached` or could not run: the page's own strip is where the per-reviewer
362
+ outcomes belong, and repeating them here would be the second place naming one
363
+ subject that `.claude/rules/spec-reports.md` exists to prevent.
364
+
365
+ **This is not a third amendment to that contract.** The counts slot is already
366
+ there and already carries numbers about the same diff; nothing about the
367
+ banner's shape, its lines or its exits changes. A reader who has configured no
368
+ reviewer sees the heading they have always seen.
369
+
370
+ **The stack is the engine's — copy it, do not compose it.** `spec-env review`
371
+ printed one line per tier, in that order, and `--json` carries the same thing as
372
+ `tiers`. Relay all three whatever they say, including the ones that are off with
373
+ the command that turns them on: a reader who has left the house cannot ask for a
374
+ remote page they were never shown.
375
+
376
+ **Do not pick one for them.** The engine used to, and the guess failed three
377
+ separate ways in one day. `.claude/rules/spec-reports.md` carries the shape and
378
+ why the *one link, never two* rule now reads
379
+ **one link per reachable store, each labelled** —
380
+ local and network are two doors into one room, so the wait
381
+ covers both. `remote` is a second store, so its own line carries its own caveat:
382
+ *a verdict here needs `/spec-reviewed`*, because nothing pushes from
383
+ the artifact store into this conversation. Never let the holding line promise a
384
+ wait over that tier.
385
+
386
+ **Publishing is still never yours to do.** `remote` reading `off` is the normal
387
+ state, and the fix for it is the command on that line — typed by the operator,
388
+ or pressed on the page, because a published page is one this tooling cannot
389
+ remove and permitting it is not publishing it.
390
+
391
+ **The `live:` line goes with the stack**, and it is the engine's line too —
392
+ copied as printed, and **absent when the engine printed none**. The page can act
393
+ on it: a press puts the spec live or takes it down and hands the reader back the
394
+ same page, which is `/spec-diff` §2b's routing and not this skill's. What
395
+ matters here is that an action **never clears the gate this phase armed** — the
396
+ reader has looked at it running and concluded nothing, so the phase still owes a
397
+ verdict.
319
398
 
320
399
  ---
321
400
 
322
401
  **Where you are not waiting, it stays the `Review` row** — the counts and the
323
402
  page link, and **no question**:
324
403
 
325
- | **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) |
404
+ | **Review** | <N> files, +<a> −<d> · **local** <URL> · **network** <URL> · **remote** off |
326
405
 
327
406
  At the end of a phase you are always waiting, so this shape belongs to the
328
407
  renders that are not this step: a mid-phase `/spec-diff`, a page produced
@@ -352,16 +431,30 @@ whether the two places are adjacent rows or a row and a banner.
352
431
 
353
432
  The row is findable, but a row cannot make the continuation follow from the
354
433
  reading — and that is the gap the whole gate exists to close.
355
- **Where this harness can watch a file and wake the session on a change, use it.** The pass
356
- arrives at the engine's holding area, the watch fires, and the verdict the
357
- reader pressed is what carries the work on.
434
+
435
+ **The wait is a command. Do not write one.**
358
436
 
359
437
  1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
360
438
  the whole scope of what you may claim.
361
- 2. **Watch the pending store** for the spec —
362
- `.spec-env/reviews/<spec>.pending.json` in the primary checkout — and
363
- **end your turn**. Do not poll, and do not hold the turn open: the point is that
364
- the reader has the terminal back while they read.
439
+ 2. **Run the engine's wait in the background, and end your turn:**
440
+
441
+ ```
442
+ skitterspec spec-env review wait <spec> --since <the timestamp>
443
+ ```
444
+
445
+ It returns when a pass arrives inside that window, and
446
+ **it takes no timeout unless you pass one** —
447
+ the wait lasts as long as your session, because a reader who walks away from
448
+ a diff is the normal case rather than the edge one. Do not give it a duration
449
+ of your own.
450
+
451
+ **A watch that dies is re-armed on the same window**, silently and bounded by
452
+ the age of that window — `.claude/rules/spec-reports.md` carries the contract,
453
+ including when to stop and what the banner says instead. Do not restate it
454
+ here.
455
+
456
+ Then **end your turn**. Do not poll it and do not hold the turn open: the
457
+ point is that the reader has the terminal back while they read.
365
458
  3. **On waking, let the engine pick**:
366
459
 
367
460
  ```
@@ -369,11 +462,26 @@ reader pressed is what carries the work on.
369
462
  ```
370
463
 
371
464
  It claims the one pass that arrived inside the window, and acts on nothing
372
- at all when none did (the watch can fire on a write that was not a pass) or
373
- when two did (two sittings, or two people — the operator has the codes).
465
+ at all when none did (the wait can be woken by something that was not a
466
+ pass) or when two did (two sittings, or two people — the operator has the
467
+ codes).
374
468
  4. **Route on the verdict** exactly as `/spec-diff` §2 and §4 describe. Do not
375
469
  restate that routing here.
376
470
 
471
+ **WHY A COMMAND RATHER THAN A LOOP YOU COMPOSE.** This step used to say "watch
472
+ the pending store" and stop, so every run invented its own watcher in shell —
473
+ and three failed in two days, each reaching the operator as *"I pressed the
474
+ button and nothing happened"*. The worst wrote
475
+ `until [ -f "$P" ] && [ "$x" \> "$y" ]`: valid bash, a syntax error in zsh, a
476
+ predicate that could never be true. It spun for five minutes writing to a
477
+ stderr nobody reads.
478
+
479
+ **The bug is not the lesson — the silence is.** A watcher that can never fire
480
+ and one patiently working are indistinguishable from outside, so nothing about
481
+ that run looked wrong until the operator asked. `review wait` says it has
482
+ started, is written once, and is tested against a store that gains a pass
483
+ mid-flight. A predicate composed fresh each time is proven by nothing.
484
+
377
485
  **WHY THIS IS SAFE, AND WHAT IT COSTS.** It was once true that a device
378
486
  reaching your page could not reach your conversation, and that fact was the
379
487
  whole guard: a pass sat in the holding area until a person typed
@@ -387,11 +495,11 @@ is that the page can now act, so the token has become a credential rather than
387
495
  a convenience — and `--claim-since` refusing to choose between two passes is
388
496
  what stops a race becoming a wrong commit.
389
497
 
390
- **Where the harness cannot watch a file, you still wait** — the turn ending is
391
- the wait. Say the page is rendered and that you are holding for the pass, then end
392
- your turn; the reader's next message is what carries it, and `/spec-diff` picks
393
- it up from the paste exactly as it always has. The gate holds either way: it is
394
- the engine's, not the watch's.
498
+ **Where the harness cannot run something in the background, you still wait** —
499
+ the turn ending is the wait. Say the page is rendered and that you are holding
500
+ for the pass, then end your turn; the reader's next message is what carries it,
501
+ and `/spec-diff` picks it up from the paste exactly as it always has. The gate
502
+ holds either way: it is the engine's, not the wait's.
395
503
 
396
504
  This once read *"change nothing"* — keep the row, keep the question, do not
397
505
  wait — and that exemption is the hatch a whole class of unanswerable questions
@@ -401,38 +509,38 @@ came through. Every harness can end a turn.
401
509
  one line; by the time this step is reached the chain has finished, so waiting
402
510
  here stops nothing that was still going to happen.
403
511
 
404
- Relay the **`open:`** line the engine prints, not the bare path: a path is not
405
- clickable in any terminal, and a page nobody can open is a page nobody reads.
512
+ Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines —
513
+ never the bare `page:` path: a path is not clickable in any terminal, and a page
514
+ nobody can open is a page nobody reads.
406
515
 
407
516
  - **Never write the review unasked**, and **never publish**. Publishing leaves
408
517
  something behind that this tooling cannot remove, so it is always something
409
518
  someone asks for. A `file://` link is no use on a phone, and saying so **is**
410
519
  the ask — publishing is the answer to it, and `/spec-diff` §6 owns how.
411
- **Follow the `reader:` line the engine printed — do not sniff for it.** It
412
- answers where the person reading this is sitting, and the offer changes with it:
413
-
414
- - **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
415
- unknown is the ordinary state of a local machine, and a warning there is an
416
- accusation against a healthy session.
417
- - **`local`** — the `file://` URL.
418
- - **`remote`** — the engine has already stood its local server up and put a URL
419
- the reader can open on `open:`. So there is **nothing special to say**: relay
420
- that line like any other. Any `also:` lines under it are the other addresses
421
- this machine has, offered because the best-guess one can be wrong — pass them
422
- on rather than editing them out.
423
-
424
- **Never read an environment variable to decide this** — not `SSH_CONNECTION`,
425
- not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
426
- and in `--json`, and a second implementation here could not be tested and would
427
- drift.
428
-
429
- **Serving is the engine's to do; publishing is never.** A `remote` reader
430
- authorises a local server — one process, ended by one flag, leaving nothing
431
- behind — and authorises nothing else. A reader the server CAN reach is not a
432
- reason to publish as well: the page they can already open is the page to name. Publishing leaves a page this tooling
433
- cannot remove, so it stays an ask in every case, always. If the engine could not
434
- serve (a busy port, a machine with no network address) it falls back to the
435
- `file://` URL with its marker, and that is when publishing is worth naming.
520
+ **The `reader:` line no longer decides anything here, and that is the point.**
521
+ It is still printed, and it is still the only place that question is answered —
522
+ but the offer does not change with it, because the stack lists every tier
523
+ whatever it says. Three reader states used to mean three different offers, and
524
+ that branching is exactly what produced a `file://` page on a session detected
525
+ `unknown`, a LAN URL for a phone off the network, and an address that changed
526
+ underneath a reader when detection flipped mid-session.
527
+
528
+ So: **relay all three tier lines, every time.** Any `also:` lines sit under
529
+ `network` — the other addresses this machine has, offered because the
530
+ best-guess one can be wrong — so pass them on rather than editing them out.
531
+
532
+ **Never read an environment variable to decide anything about the offer** — not
533
+ `SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
534
+ here for a detection to decide, and a second implementation of one could not be
535
+ tested and would drift.
536
+
537
+ **Serving is the engine's to do; publishing is never.** The server is one
538
+ process, ended by one flag, leaving nothing behind, and `review.allowNetwork`
539
+ decides whether it binds wide — no detection involved. A reader the server CAN
540
+ reach is not a reason to publish as well: the tiers they can already open are on
541
+ the stack. Publishing leaves a page this tooling cannot remove, so it stays an
542
+ ask in every case, always — which is why `remote` shows the command rather than
543
+ a URL until someone types it.
436
544
 
437
545
  - **Never fatal.** A failed render — no worktree, a git error — is one line and
438
546
  the phase is still done. The page is a convenience; the repo is the record.
@@ -102,6 +102,67 @@ resolve it by reading the code, do that instead of asking.
102
102
 
103
103
 
104
104
 
105
+ ## 4b. Hand the refresh back on a page, then wait
106
+
107
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
108
+ present) **and this review actually changed something.** Either missing → skip
109
+ the whole step in silence.
110
+
111
+ A refreshed spec is a rewrite of a document someone is about to work from, so it
112
+ gets read before it is committed — the same ending `/spec` now has:
113
+
114
+ ```
115
+ skitterspec spec-env review <spec> --docs --buttons refresh
116
+ ```
117
+
118
+ `--docs` reads the spec's own documents from the tree you are standing in, so it
119
+ wants no worktree and works for a spec in any bucket. These documents are
120
+ **tracked**, so the diff against `HEAD` is a patch rather than a set of new
121
+ files — which is exactly what the reader wants here: what drifted, and what you
122
+ rewrote.
123
+
124
+ **Render nothing when nothing changed.** `/spec-review` frequently concludes a
125
+ spec is still accurate, and that is a success rather than a gap. A page showing
126
+ an empty diff under a commit button asks for a verdict on nothing, and the
127
+ engine refuses it for the same reason — `nothing to review`. Take that refusal
128
+ as the answer and report the `⏸`/`✅` you already had.
129
+
130
+ **Arm nothing.** No `review arm` here: the gate asserts that a phase which ended
131
+ owes an answer, and a re-validated spec has ended no phase. Walking away leaves
132
+ an uncommitted spec, which is the state this skill has always finished in.
133
+
134
+ ### The three endings
135
+
136
+ `--buttons refresh` offers `Commit`, `Request changes` and `Discuss`.
137
+
138
+ **No start verdict, deliberately.** The spec this skill refreshed may already be
139
+ in progress — that is half of what it is used for — so `Commit & Start` would
140
+ offer to provision a worktree for a spec that already has one.
141
+
142
+ - **`commit`** — hand off to `review.commitWith` (`/commit` by default) with the
143
+ pathspec the render reported on `docs.paths`, then finish. The spec keeps
144
+ whatever status step 4 left it in.
145
+ - **`changes`** — work the notes into the spec, record a resolution for each so
146
+ the next render strikes it through with what changed, re-render, and wait
147
+ again.
148
+ - **`discuss`** — report and talk. Claim nothing, change nothing.
149
+
150
+ ### Then wait for it
151
+
152
+ **The wait is a command. Do not write one.** Note the moment you start waiting
153
+ as an ISO timestamp, run the engine's wait in the background, and end your turn:
154
+
155
+ ```
156
+ skitterspec spec-env review wait <spec> --since <the timestamp>
157
+ ```
158
+
159
+ Pass no timeout. On waking, let the engine pick the pass —
160
+ `skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json`
161
+ — and route on the verdict above.
162
+
163
+ **Where the page is `file://`** the pass is copied and pasted, so the wait is the
164
+ turn ending and the banner says so. Do not start a watch that cannot fire.
165
+
105
166
  ## 5. Report
106
167
 
107
168
  Do **not** `git commit` unless the user asks.
@@ -122,6 +183,32 @@ the shape; this section carries only what is specific here.
122
183
  API that moved, a task the code already does. `Spec` carries the status the
123
184
  review leaves it in, which is the answer to "can I start this?".
124
185
 
186
+ **Where step 4b rendered a page the run is waiting**, so the block omits the
187
+ `Review` row and ends on the banner from `.claude/rules/spec-reports.md` — the
188
+ tier stack copied from the engine's own lines, never one link chosen for the
189
+ reader:
190
+
191
+ ---
192
+
193
+ ## ⏸ Review ready — &lt;N&gt; files, +&lt;a&gt; −&lt;d&gt;
194
+
195
+ - **local** — &lt;the `local:` URL&gt;
196
+ - **network** — &lt;the `network:` URL, or off with the command that turns it on&gt;
197
+ - **remote** — &lt;the `remote:` URL, or off with the command that turns it on&gt;
198
+
199
+ I'm holding here until you send a verdict — the wait covers local and network.
200
+
201
+ `Commit` commits the refresh · `Request changes` works them now
202
+
203
+ ---
204
+
205
+ `Next` then names the page rather than a command, because the button is what
206
+ carries the work on.
207
+
208
+ Where nothing was rendered — no isolation, or no drift to show — `Next` is
209
+ `/commit, then /spec-start <name>`, since this skill does not commit and
210
+ `/spec-start` refuses a tree holding anything that is not the spec's.
211
+
125
212
  **Drift found and not fixed is a `Follow-up`, not a silence.** A spec this skill
126
213
  declared reviewed is one nobody will re-read; something it noticed and left
127
214
  undone has to leave the session in writing or it did not happen.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spec-reviewed
3
- description: Pick up the review you approved on the page — run it bare to pick up the single waiting pass, paste the six-digit code off the page ("/spec-reviewed 608223") to name one exactly, and be asked which only when two are waiting. Use when the user says "/spec-reviewed", "I approved it", "I've reviewed it", "pick up my review", "I pressed approve", or otherwise says they have finished reviewing a rendered diff.
3
+ description: Pick up the review you approved on the page — run it bare to pick up the single waiting pass, paste the six-digit code off the page ("/spec-reviewed 608223") to name one exactly, or pass the verdict itself ("/spec-reviewed commit") when the page could not send and copied you a command instead. Use when the user says "/spec-reviewed", "I approved it", "I've reviewed it", "pick up my review", "I pressed approve", or otherwise says they have finished reviewing a rendered diff.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
@@ -70,16 +70,42 @@ Several provisioned and none resolved is a refusal — relay its list and stop,
70
70
  never pick from it. See `.claude/rules/spec-planning.md`; do not restate the
71
71
  rule here.
72
72
 
73
- **Three argument shapes, and they cannot collide.** A **six-digit code**
74
- matches `^\d{6}$`; a **tracker id** carries a letter and a hyphen; a
75
- **spec name** is neither. So the parse needs no flag, and nothing has to be
76
- guessed at from context.
73
+ **Four argument shapes, and they cannot collide.** A **six-digit code**
74
+ matches `^\d{6}$`; a **verdict** is one of a closed list of six words;
75
+ a **tracker id** carries a letter and a hyphen;
76
+ a **spec name** is none of them and always carries a lifecycle prefix
77
+ (`feat-`, `bug-`, `hotfix-`), which no verdict does. So the parse needs no flag,
78
+ and nothing has to be guessed at from context.
77
79
 
78
80
  A **six-digit code** is a pass the operator read off their own page. It says
79
81
  **which pass**, not which spec — so resolve the spec exactly as a bare
80
82
  invocation does, above, and claim the code *there*. Then go to step 4 — a named
81
83
  pass has nothing to disambiguate.
82
84
 
85
+ A **verdict word** — `commit`, `commit-continue`, `commit-start`,
86
+ `commit-land`, `continue`, `changes`, `discuss` — is the conclusion itself,
87
+ arriving without a pass behind it. It is
88
+ what a **`file://` page** hands over: that page has no server to POST to and no
89
+ store to write to, so it copies a command instead of sending anything, and this
90
+ is the command. Resolve the spec as a bare invocation does, then send the word
91
+ through the engine:
92
+
93
+ ```
94
+ skitterspec spec-env review <spec> --verdict <word>
95
+ ```
96
+
97
+ It joins the same merge a claimed pass goes through, so the routing in step 4
98
+ is unchanged: a commit over open notes is refused exactly as it would be, the
99
+ outcome log records it, and the gate a phase armed is cleared. Then go to step
100
+ 4 — there is nothing to disambiguate.
101
+
102
+ **A word carries a verdict and nothing else**, and that is a property of the
103
+ transport, not a shortcut. Accepts and comments do not fit on a command line,
104
+ so the page only ever offers the words while the reader has marked nothing —
105
+ the moment they tick an accept or write a note it goes back to handing over the
106
+ blob. If someone tells you they marked things up *and* gives you a word, ask
107
+ for the pass: the word would land a verdict with their notes silently dropped.
108
+
83
109
  A **name** targets that spec instead. A **tracker id** does too, but only
84
110
  through a **provider seam**: the base knows nothing about tracker ids, so
85
111
  resolve one by asking whichever provider is installed for its listing —
@@ -73,6 +73,20 @@ as printed; when it refuses, relay the reason and stop.
73
73
  It also refuses a **clean** tree whose spec is not in the commit the worktree
74
74
  would fork from — otherwise you get a branch missing the very spec it is for.
75
75
 
76
+ **A spec that `/spec` already landed is the HEALTHY case of that check.**
77
+ It is not the refusal. `/spec` authors in its own `--docs` worktree and its committing
78
+ verdict fast-forwards the base branch, so by the time `/spec-start` runs the
79
+ spec is already in the commit the worktree forks from and the tree is clean —
80
+ which is exactly what that check is looking for. Do not read "clean tree,
81
+ nothing to commit" as a sign something was skipped: it means the landing worked.
82
+
83
+ **And the worktree may already exist**, because `Commit & Start` keeps it. That
84
+ is a re-attach, not a clash: `spec-env up` plans the attach form rather than a
85
+ second `-b` fork, and its `then, in the worktree, run:` step is where the
86
+ `setup` commands `--docs` deliberately skipped finally run. So run the plan as
87
+ printed — the install belongs to this moment, the first at which the tree is
88
+ used for code — and report a re-attach rather than a fresh provision.
89
+
76
90
  ## 2. Identify the spec
77
91
 
78
92
  - Use the name/path argument when given.
@@ -268,6 +282,25 @@ here beyond knowing it is no longer dead code.
268
282
  A spec ideally arrives `Ready` from `/spec`; a `Draft` works too — sanity-check
269
283
  it is well-formed first.
270
284
 
285
+ - **Report anything left waiting, and claim none of it.** Ask the engine once:
286
+
287
+ ```
288
+ skitterspec spec-env review waiting
289
+ ```
290
+
291
+ A pass listed here arrived when nothing was watching — a wait that never ran,
292
+ a session cleared, a terminal closed overnight — and no watcher can recover
293
+ those, however good. Relay what it prints: the spec, the code, the verdict and
294
+ the age, with `/spec-reviewed <code>` to pick one up and `--drop <code>` to
295
+ disown it.
296
+
297
+ **It is information, not a gate.** It never refuses, nothing here is
298
+ conditional on it, and **you never claim one** — `/spec-diff` §0 stands
299
+ unchanged, and a pass sitting there when you arrived was not sent to you.
300
+ Silent when nothing is waiting, which is the usual case: say nothing rather
301
+ than reporting that there was nothing to report.
302
+
303
+
271
304
  ## 4b. Note a missing gating decision (only if configured)
272
305
 
273
306
  **Only when `specs/.core/gating.config.json` exists.** Run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec",
3
- "version": "21.0.0",
3
+ "version": "22.1.0",
4
4
  "description": "Spec-driven development for Claude Code \u2014 a tracker-free filesystem workflow: lifecycle skills and per-spec isolation. For Linear sync, install @skitterbyte/skitterspec-linear instead.",
5
5
  "keywords": [
6
6
  "claude",