@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
@@ -73,25 +73,56 @@ Briefly play back the agreed understanding before writing.
73
73
 
74
74
  This skill is for **features**. For bugs, use `/spec-bug` (test-first, red→green).
75
75
 
76
- ### Write it from the base branch
77
-
78
- **Check where you are before creating the folder.** A backlog spec belongs on
79
- the base branch (`main`). If you are on another spec's branch — most likely
80
- inside its worktree, because a design question came up part-way through
81
- implementing it — say so before writing, and offer to author the spec from the
82
- primary checkout instead.
83
-
84
- It matters for more than tidiness: a spec written inside another spec's worktree
85
- **physically lives on that branch**. It is not on `main` until that spec lands,
86
- it is invisible to anyone listing `specs/backlog/` meanwhile, and if that spec is
87
- cancelled the new spec is cancelled with it. Committing it there also mis-stamps
88
- the commit's ticket trailer, since that is resolved from the branch (see
89
- `.claude/rules/commit-trailers.md`, installed with a ticketing provider).
90
-
91
- **Warn, don't refuse** — doing this deliberately is legitimate, and the user may
92
- have a reason. If they continue, carry on exactly as normal, and mention that
93
- the trailer for that commit wants `spec-sync ref <new-spec-name>` rather than the
94
- bare form.
76
+ ### Write it in its own worktree
77
+
78
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
79
+ present). Without it there is no worktree to make and this section does not
80
+ apply — write the folder where you are standing, exactly as before.
81
+
82
+ **Provision before you write, not after.** Grilling put nothing on disk and the
83
+ spec's name is settled by the time Phase A ends, so this is the one moment where
84
+ a worktree costs nothing to obtain and everything is still ahead of it:
85
+
86
+ ```
87
+ skitterspec spec-env up <name> --docs
88
+ ```
89
+
90
+ Run the commands it prints, then move this session into the worktree with a
91
+ plain `cd` — the Bash working directory persists between calls, so from here on
92
+ the spec is written on its own branch. Confirm the move landed rather than
93
+ reading silence as success (`.claude/rules/negative-checks.md` rule 1):
94
+ `skitterspec spec-env resolve` with no argument must name this spec. If it does
95
+ not, say so and write the spec where you are standing instead; a spec in the
96
+ wrong tree is worse than a spec with no worktree.
97
+
98
+ **`--docs` is what makes this cheap.** It skips the `setup` commands and the
99
+ Docker stack — everything whose only purpose is making a tree *runnable* — so
100
+ provisioning a worktree to write markdown in is a `git worktree add` and nothing
101
+ else. `/spec-start` re-runs `up` over the same worktree without the flag, and the
102
+ setup runs then, at the first moment the tree is used for code.
103
+
104
+ **Why the base branch is no longer where this happens.** `main` is where work
105
+ **lands**, not where it happens. Authoring on it meant the spec sat uncommitted
106
+ there for as long as Phase C2's page went unanswered — which is unbounded by
107
+ design, because a reader walking away from a diff is the normal case — and a
108
+ release could not be cut through it. The verdict lands the spec instead, so
109
+ `main` gains it as one commit and is never dirty on the way.
110
+
111
+ **A spec written in ANOTHER spec's worktree is still the mistake it always was**,
112
+ and this section does not soften that. It physically lives on that branch: not on
113
+ `main` until that spec lands, invisible to anyone listing `specs/backlog/`
114
+ meanwhile, and cancelled along with its host. Committing it there also mis-stamps
115
+ the ticket trailer, which is resolved from the branch (see
116
+ `.claude/rules/commit-trailers.md`, installed with a ticketing provider). The
117
+ difference is whose worktree: **its own** is now the normal path, and someone
118
+ else's is the accident.
119
+
120
+ So if Phase A ran inside another spec's worktree — most likely because a design
121
+ question came up part-way through implementing it — the provision above is the
122
+ fix rather than something to warn about: it moves the session out of that tree
123
+ and into this spec's own. Where isolation is off and there is no worktree to
124
+ move to, **warn, don't refuse**, and say the trailer for that commit wants
125
+ `spec-sync ref <new-spec-name>` rather than the bare form.
95
126
 
96
127
  - **Every spec is a folder** — never a bare file, even for a one-line change:
97
128
  `specs/backlog/feat-<kebab-name>/`. Create it with `mkdir -p`.
@@ -249,6 +280,157 @@ open questions, `Draft` when you deliberately left some unresolved. Either way
249
280
  the next step is `/spec-start`. The Report section below is where all of that
250
281
  reaches the user — do not narrate it here as well.
251
282
 
283
+ ## Phase C2 — render the spec, then wait for the verdict
284
+
285
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
286
+ present). Without it there is no page and this phase does not exist — skip it in
287
+ silence rather than explaining an absence.
288
+
289
+ A spec is the one artefact whose whole purpose is to be
290
+ **read before work begins**, and until now it was the only one with no reading
291
+ surface: `/spec`
292
+ finished, the spec sat uncommitted, and `/spec-start` had to point that out and
293
+ commit it as a side effect of provisioning. So this phase ends where every other
294
+ phase of the lifecycle ends — on a page, in a verdict.
295
+
296
+ ```
297
+ skitterspec spec-env review <spec> --docs --buttons authoring
298
+ ```
299
+
300
+ `--docs` reads the spec's own documents from the tree you are standing in, which
301
+ after Phase B is **this spec's own worktree**. It renders
302
+ **this spec's documents and nobody else's**: the `owned` half of
303
+ `spec-env stage` is what supplies the file set, and it stays load-bearing even
304
+ now that the tree holds one spec — a project's `spec.companionPaths` and a
305
+ hand-edited `specs/.core/` both land in the same tree, and only the spec's own
306
+ documents belong under this verdict.
307
+
308
+ **This is free.** The engine reads git and splices text into a template; the
309
+ diff never passes through you.
310
+
311
+ **Never pass `--run-reviewers` here.** A spec is prose, and the reviewers
312
+ `review.reviewers` names read code — spending a rate-limited review on a
313
+ paragraph buys nothing. The engine refuses it on a `--docs` render whatever this
314
+ skill asks for, so this is a note about intent rather than a guard you are
315
+ holding: do not add the flag on the assumption that it was an oversight.
316
+
317
+ **Render nothing when nothing was written.** A `⏸` run — grilling that never
318
+ reached a shared understanding — has no spec to show, and must not ask for a
319
+ verdict on one.
320
+
321
+ **Arm nothing.** There is no `spec-env review arm` in this path, and that is a
322
+ decision rather than an omission: the gate asserts that *a phase which ended*
323
+ owes an answer, and a backlog spec owes no phase. A `/spec` run must leave
324
+ `spec-env review gate --check` exiting 0.
325
+
326
+ **And walking away from this page now costs nobody anything**, which is a
327
+ stronger reason than the one that used to sit here. It read *"the spec is simply
328
+ still uncommitted, which `/spec-start` already handles"* — true, and a
329
+ concession: an uncommitted spec meant a dirty base branch, which is everyone's
330
+ problem, so "arm nothing" was tolerating a mess rather than describing a clean
331
+ state. Since Phase B the unanswered spec sits on its own branch in its own
332
+ worktree, where it is in nothing's way: no release is blocked, no other spec has
333
+ to replay over it, and it is still exactly where it was tomorrow morning.
334
+
335
+ ### The two endings this page offers
336
+
337
+ `--buttons authoring` offers `Commit & Start` and `Commit` in place of the
338
+ committing pair, because "commit and build the next phase" is meaningless for a
339
+ spec with no phase in flight.
340
+
341
+ - **`commit-start`** — hand off to `review.commitWith` (`/commit` by default),
342
+ passing the pathspec the render reported on `docs.paths`; **land it** (below);
343
+ then **keep the worktree** and run **`/spec-start <name>`**, and **stop there**.
344
+ It never completes or tears anything down, exactly as `commit-continue` stops
345
+ after `/spec-next`. This is the `commit && /spec-start` that was typed by hand.
346
+ - **`commit`** — the same commit and the same land, then
347
+ **tear the worktree down** and finish. The spec stays `Ready` in `backlog` —
348
+ on the base branch, where anyone can see it — ready for a `/spec-start`
349
+ whenever it is wanted.
350
+
351
+ **Both committing verdicts land, and that is what keeps the backlog findable.**
352
+ `ls specs/backlog/` is how specs are found, and a folder bucket is only the truth
353
+ on the branch you are standing on — so a spec left on its own branch would be
354
+ invisible to everyone, including the next `/spec` run. The commit goes on the
355
+ branch and then the branch goes onto the base:
356
+
357
+ ```
358
+ skitterspec spec-env integrate <name>
359
+ ```
360
+
361
+ It plans a rebase onto the base branch in the worktree, then a
362
+ `merge --ff-only` in the primary checkout — run the commands it prints. It
363
+ refuses on a dirty tree, which is correct here: the commit ran first, so anything
364
+ still uncommitted at this point is not this spec's and the land must not proceed.
365
+
366
+ **If the rebase conflicts, stop and say so.** It is unlikely on a
367
+ documents-only branch and not impossible — two specs authored at once can both
368
+ touch `specs/.core/`. Run `git rebase --abort`, leave the commit sitting on the
369
+ branch, and report `❌` with the conflict: there is a standing worktree to clear,
370
+ which is what separates `❌` from `⏸`. Never resolve someone else's conflict to
371
+ get the land through.
372
+
373
+ **The worktree lifetime follows the verdict, and the asymmetry is deliberate.**
374
+ `commit-start` keeps it because you are carrying straight on, and `/spec-start`
375
+ re-runs `up` over it without `--docs` — which is where the setup commands
376
+ Phase B skipped finally run. Plain `commit` tears it down with
377
+ `skitterspec spec-env down <name>`, because a spec parked in the backlog should
378
+ hold no worktree and no `node_modules`; `/spec-start` provisions from scratch
379
+ whenever it is wanted. Always-keep would leave five backlog specs holding five
380
+ checkouts, and always-teardown would have `commit-start` re-provision seconds
381
+ after destroying the tree it needed.
382
+ - **`changes`** — work the notes into the spec, record a resolution for each one
383
+ so the next render shows it struck through with what changed, then
384
+ **re-render, and wait again**. The reader is still holding a decision, so
385
+ ending the turn and
386
+ making them type `/spec-diff` would reopen the loop this exists to close.
387
+ - **`discuss`** — report and talk. Claim nothing, change nothing.
388
+
389
+ **Take the pathspec from the render, not from the tree.** A checkout is shared,
390
+ so the set of this spec's uncommitted documents can differ between the render and
391
+ the verdict — and the reader's conclusion is about what the page showed them.
392
+
393
+ ### Then wait for it
394
+
395
+ **The wait is a command. Do not write one.**
396
+
397
+ 1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
398
+ the whole scope of what you may claim.
399
+ 2. **Run the engine's wait in the background, and end your turn:**
400
+
401
+ ```
402
+ skitterspec spec-env review wait <spec> --since <the timestamp>
403
+ ```
404
+
405
+ It takes no timeout unless you pass one, and you must not pass one: a reader
406
+ who walks away from a spec is the normal case, and a bounded watch once lost a
407
+ verdict to a lunch break.
408
+ 3. **On waking, let the engine pick:**
409
+
410
+ **A watch that dies is re-armed on the same window**, silently and bounded by
411
+ the age of that window — `.claude/rules/spec-reports.md` carries the contract,
412
+ including when to stop and what the banner says instead. Do not restate it
413
+ here.
414
+
415
+ ```
416
+ skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json
417
+ ```
418
+
419
+ It claims the one pass that arrived inside the window, acts on nothing when
420
+ none did, and refuses to choose when two did.
421
+ 4. **Route on the verdict** as above.
422
+
423
+ **Where the page is `file://`** — the engine could not serve — there is no server
424
+ to POST to, so the pass is copied and pasted and the reader's next message is
425
+ what carries it. **Do not start a watch that cannot fire**: a wait on the pending
426
+ store would spin forever while the report underneath it claimed to be holding,
427
+ which is the exact failure `.claude/rules/spec-reports.md` records. The wait is
428
+ the turn ending, and the banner says so.
429
+
430
+ Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
431
+ three, in that order — never the bare `page:` path. `.claude/rules/spec-reports.md`
432
+ carries the shape and why every tier is named rather than one being chosen.
433
+
252
434
  ## Phase D — record the isolation stack (only if configured)
253
435
 
254
436
  **Only when `specs/.core/env.config.json` exists** (per-spec isolation is
@@ -293,16 +475,61 @@ the shape; this section carries only what is specific here.
293
475
 
294
476
  - `✅` — a `Ready` spec is written to `specs/backlog/<name>/`.
295
477
  - `⚠️` — written as `Draft`; open questions were deliberately left. Name them.
478
+ - `❌` — the spec was committed and the land failed part-way: the rebase
479
+ conflicted, or the fast-forward was refused. There is a standing worktree and a
480
+ branch to deal with, so say where both are.
296
481
  - `⏸` — grilling did not reach a shared understanding, so nothing was written.
297
482
  That is the skill working: a spec written over an unresolved requirement is
298
483
  the outcome Phase A exists to prevent.
299
484
 
300
- **Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
485
+ **Fields:** `Tracker` · `Spec` · `Built` · `Landed` · `Worktree` · `Follow-ups` ·
486
+ `Next`
301
487
 
302
488
  `Built` is the spec's path and phase count; `Spec` is its status and bucket;
303
- `Next` is `/spec-start <name>`, with the name spelled the way it must be typed.
304
489
  `Tracker` appears only when a provider is installed and linked it.
305
490
 
491
+ `Landed` and `Worktree` belong to the **verdict** half of the run and appear
492
+ only once one has been acted on — a run that is still waiting on the page has
493
+ neither. `Landed` says the base branch fast-forwarded and to what; `Worktree`
494
+ says kept (on `commit-start`) or removed (on `commit`), with the path. Neither
495
+ appears where isolation is off: nothing was provisioned and nothing was landed.
496
+
497
+ **`Next` depends on whether Phase C2 rendered a page.** Where it did, the run is
498
+ waiting on a verdict, so the block **omits the `Review` row** and ends on the
499
+ banner defined in `.claude/rules/spec-reports.md` — counts, the tier stack, and
500
+ this page's two exits:
501
+
502
+ ---
503
+
504
+ ## ⏸ Review ready — &lt;N&gt; files, +&lt;a&gt; −&lt;d&gt;
505
+
506
+ - **local** — &lt;the `local:` URL&gt;
507
+ - **network** — &lt;the `network:` URL, or off with the command that turns it on&gt;
508
+ - **remote** — &lt;the `remote:` URL, or off with the command that turns it on&gt;
509
+
510
+ I'm holding here until you send a verdict — the wait covers local and network.
511
+
512
+ `Commit & Start` puts it in flight · `Commit` keeps it for later
513
+
514
+ ---
515
+
516
+ There `Next` names the page rather than a command, because the button is what
517
+ carries the work on.
518
+
519
+ **Neither exit leaves the spec anywhere awkward**, and the banner does not
520
+ explain that — the whole point of Phase B is that there is nothing to warn
521
+ about. Both buttons commit and land, so either way the spec reaches the base
522
+ branch as one commit; the only difference is whether the worktree stays.
523
+
524
+ Where no page was rendered — isolation is not configured, or nothing was
525
+ written — `Next` is `/spec-start <name>`, with the name spelled the way it must
526
+ be typed.
527
+
528
+ **A spec written without a page is still uncommitted**, so that `Next` relies on
529
+ `/spec-start` committing the spec when it is all that is uncommitted. Say
530
+ `/commit, then /spec-start <name>` when anything else is uncommitted too, or the
531
+ row sends the reader to a refusal.
532
+
306
533
  **`Follow-ups` is almost always `none` here.** This skill's whole job is to
307
534
  capture work, so anything it surfaced belongs in the spec it just wrote rather
308
535
  than in a follow-up line beneath it.
@@ -231,11 +231,18 @@ skitterspec spec-env review arm <spec>
231
231
  ```
232
232
 
233
233
  **Then wait**, and `/spec-next` §5 owns the sequence — follow it there rather
234
- than reading a second copy here: note the moment you start waiting, watch the
235
- pending store, end your turn, and let `--claim-since` pick the one pass that
236
- arrived inside the window. The banner it describes is what this skill emits in
237
- place of a `Review` row, and the routing on the verdict is `/spec-diff` §2 and
238
- §4, as it is everywhere.
234
+ than reading a second copy here: note the moment, run
235
+ `skitterspec spec-env review wait <spec> --since <that moment>` in the
236
+ background, end your turn, and let `--claim-since` pick the one pass that
237
+ arrived inside the window. **Do not compose a watcher**, and do not give the
238
+ wait a timeout — the reasoning is in `/spec-next` §5 and is not repeated here.
239
+ The banner it describes is what this skill emits in place of a `Review` row,
240
+ and the routing on the verdict is `/spec-diff` §2 and §4, as it is everywhere.
241
+
242
+ **A watch that dies is re-armed on the same window**, silently and bounded by
243
+ the age of that window — `.claude/rules/spec-reports.md` carries the contract,
244
+ including when to stop and what the banner says instead. Do not restate it
245
+ here.
239
246
 
240
247
  **Why this skill arms as well as waits.** Waiting is what any offer does;
241
248
  **arming** asserts an obligation that outlives the turn, and belongs only to
@@ -256,30 +263,33 @@ this worktree is refused until a verdict is sent or
256
263
  `skitterspec spec-env review skip "<reason>"` records the decision to move on.
257
264
  The exit is always one command, and one of them is *"I am moving on"*.
258
265
 
259
- Relay the **`open:`** line rather than the bare path: a path is not clickable in
260
- any terminal, and a page nobody can open is a page nobody reads.
266
+ Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
267
+ three — rather than the bare `page:` path: a path is not clickable in any
268
+ terminal, and a page nobody can open is a page nobody reads. Relay the `live:`
269
+ line with it where the engine printed one, and nothing where it did not.
270
+ `.claude/rules/spec-reports.md` carries the shape, including why a rigid
271
+ contract took that line.
261
272
 
262
273
  - **Never write the review unasked**, and **never publish**. Publishing leaves
263
274
  something behind that this tooling cannot remove, so it is always an ask. A
264
275
  `file://` link is no use on a phone, and saying so **is** the ask —
265
276
  `/spec-diff` §6 owns how.
266
- **Follow the `reader:` line the engine printed — do not sniff for it.** It
267
- answers where the person reading this is sitting, and the offer changes with it:
268
-
269
- - **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
270
- unknown is the ordinary state of a local machine, and a warning there is an
271
- accusation against a healthy session.
272
- - **`local`** — the `file://` URL.
273
- - **`remote`** — the engine has already stood its local server up and put a URL
274
- the reader can open on `open:`. So there is **nothing special to say**: relay
275
- that line like any other. Any `also:` lines under it are the other addresses
276
- this machine has, offered because the best-guess one can be wrong — pass them
277
- on rather than editing them out.
278
-
279
- **Never read an environment variable to decide this** — not `SSH_CONNECTION`,
280
- not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
281
- and in `--json`, and a second implementation here could not be tested and would
282
- drift.
277
+ **The `reader:` line no longer decides anything here, and that is the point.**
278
+ It is still printed, and it is still the only place that question is answered —
279
+ but the offer does not change with it, because the stack lists every tier
280
+ whatever it says. Three reader states used to mean three different offers, and
281
+ that branching is exactly what produced a `file://` page on a session detected
282
+ `unknown`, a LAN URL for a phone off the network, and an address that changed
283
+ underneath a reader when detection flipped mid-session.
284
+
285
+ So: **relay all three tier lines, every time.** Any `also:` lines sit under
286
+ `network` — the other addresses this machine has, offered because the
287
+ best-guess one can be wrong — so pass them on rather than editing them out.
288
+
289
+ **Never read an environment variable to decide anything about the offer** — not
290
+ `SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
291
+ here for a detection to decide, and a second implementation of one could not be
292
+ tested and would drift.
283
293
 
284
294
  **Serving is the engine's to do; publishing is never.** A `remote` reader
285
295
  authorises a local server — one process, ended by one flag, leaving nothing
@@ -114,6 +114,35 @@ of the work, so an unreclaimed one is worth a line rather than a silence.
114
114
  reclaim the cancelled spec's environment. On confirmation, run the `spec-env` CLI
115
115
  directly (the old `/spec-env-down` skill is gone — teardown is folded in here):
116
116
 
117
+ 0. **Name a pass still waiting for THIS spec — before anything is removed.**
118
+
119
+ ```
120
+ skitterspec spec-env review waiting --json
121
+ ```
122
+
123
+ Filter to the spec being cancelled and say nothing about any other: a run
124
+ reports itself and nothing else (`.claude/rules/spec-reports.md`). For each
125
+ one, give the code, the verdict and the age, then the two exits — claim it
126
+ now with `/spec-reviewed <code>` **while the worktree still stands**, or
127
+ disown it with `skitterspec spec-env review <spec> --drop <code>`.
128
+
129
+ **Why here and not anywhere else.** Nothing is about to become unreachable —
130
+ a pass can be disowned long after its spec is gone. What is about to pass is
131
+ the last moment the verdict can still be **honoured**: before teardown a
132
+ `commit` pass can be claimed and acted on, after it there is no branch left
133
+ to commit to and disowning is all that remains. On a cancelled spec that is
134
+ sharper still — the worktree may hold the only copy of the work, so a pass
135
+ approving it is the one thing that might argue for keeping it.
136
+
137
+ **It reports, and it never blocks.** No refusal, no confirmation of its own,
138
+ no non-zero exit — this step already asks before it reclaims anything, and a
139
+ waiting pass is information rather than a second gate.
140
+ And **it never claims** — reporting a pass is not taking one, and
141
+ `/spec-diff` §0 is untouched by this step.
142
+
143
+ Put what you found in the report's `Notes` row.
144
+ Silent when nothing is waiting, which is the usual case.
145
+
117
146
  1. If `.spec-env/connected` names this spec, run `skitterspec spec-env connect
118
147
  main` first to free the canonical ports.
119
148
  2. `skitterspec spec-env dev down <name>` — stop its host dev servers.
@@ -231,6 +231,31 @@ nothing left to protect.
231
231
  worktree and branch are being kept, and go straight to sub-step 4. Mention
232
232
  `skitterspec spec-env up <name>` re-attaches it later either way.
233
233
 
234
+ 0. **Name a pass still waiting for THIS spec — before anything is removed.**
235
+
236
+ ```
237
+ skitterspec spec-env review waiting --json
238
+ ```
239
+
240
+ Filter to the spec being completed and say nothing about any other: a run
241
+ reports itself and nothing else (`.claude/rules/spec-reports.md`). For each
242
+ one, give the code, the verdict and the age, then the two exits — claim it
243
+ now with `/spec-reviewed <code>` **while the worktree still stands**, or
244
+ disown it with `skitterspec spec-env review <spec> --drop <code>`.
245
+
246
+ **Why here and not anywhere else.** Nothing is about to become unreachable —
247
+ a pass can be disowned long after its spec is gone. What is about to pass is
248
+ the last moment the verdict can still be **honoured**: before teardown a
249
+ `commit` pass can be claimed and acted on, after it there is no branch left
250
+ to commit to and disowning is all that remains.
251
+
252
+ **It reports, and it never blocks.** No refusal, no confirmation of its own,
253
+ no non-zero exit. And **it never claims** — reporting a pass is not taking
254
+ one, and `/spec-diff` §0 is untouched by this step.
255
+
256
+ Put what you found in the report's `Notes` row.
257
+ Silent when nothing is waiting, which is the usual case.
258
+
234
259
  1. **Disconnect the proxy if this spec is connected.** If `.spec-env/connected`
235
260
  names this spec, run `skitterspec spec-env connect main` first so the
236
261
  canonical ports go back to the primary checkout.