@skitterbyte/skitterspec 22.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 (37) hide show
  1. package/README.md +14 -0
  2. package/assets/claude-md-section.md +13 -0
  3. package/assets/commands/allow-main.md +33 -0
  4. package/assets/commands/spec-skip.md +24 -0
  5. package/assets/core/env.config.json.example +2 -1
  6. package/assets/core/env.config.md +83 -2
  7. package/assets/hooks/main-guard.cjs +145 -0
  8. package/assets/hooks/review-gate.cjs +8 -3
  9. package/assets/review/page.html +132 -2
  10. package/assets/rules/offered-unblocks.md +100 -0
  11. package/assets/rules/spec-planning.md +160 -11
  12. package/assets/rules/spec-reports.md +50 -9
  13. package/assets/skills/no-spec/SKILL.md +219 -0
  14. package/assets/skills/spec/SKILL.md +135 -36
  15. package/assets/skills/spec-bug/SKILL.md +5 -0
  16. package/assets/skills/spec-cancel/SKILL.md +29 -0
  17. package/assets/skills/spec-complete/SKILL.md +25 -0
  18. package/assets/skills/spec-diff/SKILL.md +33 -1
  19. package/assets/skills/spec-init/SKILL.md +4 -2
  20. package/assets/skills/spec-next/SKILL.md +47 -2
  21. package/assets/skills/spec-reviewed/SKILL.md +3 -3
  22. package/assets/skills/spec-start/SKILL.md +14 -0
  23. package/package.json +1 -1
  24. package/src/cli.js +702 -59
  25. package/src/env/config.js +121 -4
  26. package/src/env/hooks.js +102 -33
  27. package/src/env/live.js +30 -1
  28. package/src/env/mainguard.js +240 -0
  29. package/src/env/provision.js +106 -10
  30. package/src/env/registry.js +70 -6
  31. package/src/env/resolve.js +92 -3
  32. package/src/env/review.js +324 -20
  33. package/src/env/reviewers/coderabbit.js +228 -0
  34. package/src/env/reviewers.js +517 -0
  35. package/src/env/serve.js +189 -44
  36. package/src/env/teardown.js +12 -1
  37. package/src/init.js +125 -15
@@ -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`.
@@ -266,26 +297,40 @@ phase of the lifecycle ends — on a page, in a verdict.
266
297
  skitterspec spec-env review <spec> --docs --buttons authoring
267
298
  ```
268
299
 
269
- `--docs` reads the spec's own documents from the tree you are standing in, so it
270
- never wants a worktree — a backlog spec has none. It renders
271
- **this spec's documents and nobody else's**: several specs are routinely
272
- authored in one
273
- checkout, and the file set comes from `spec-env stage`'s `owned` half precisely
274
- so a colleague's spec cannot land on your page and then be committed under your
275
- verdict.
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.
276
307
 
277
308
  **This is free.** The engine reads git and splices text into a template; the
278
309
  diff never passes through you.
279
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
+
280
317
  **Render nothing when nothing was written.** A `⏸` run — grilling that never
281
318
  reached a shared understanding — has no spec to show, and must not ask for a
282
319
  verdict on one.
283
320
 
284
321
  **Arm nothing.** There is no `spec-env review arm` in this path, and that is a
285
322
  decision rather than an omission: the gate asserts that *a phase which ended*
286
- owes an answer, and a backlog spec owes no phase. Walking away from this page
287
- costs nothing — the spec is simply still uncommitted, which `/spec-start` already
288
- handles. A `/spec` run must leave `spec-env review gate --check` exiting 0.
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.
289
334
 
290
335
  ### The two endings this page offers
291
336
 
@@ -294,12 +339,46 @@ committing pair, because "commit and build the next phase" is meaningless for a
294
339
  spec with no phase in flight.
295
340
 
296
341
  - **`commit-start`** — hand off to `review.commitWith` (`/commit` by default),
297
- passing the pathspec the render reported on `docs.paths`, then run
298
- **`/spec-start <name>`** and **stop there**. It never completes, lands or tears
299
- anything down, exactly as `commit-continue` stops after `/spec-next`. This is
300
- the `commit && /spec-start` that was typed by hand.
301
- - **`commit`** — the same commit, then finish. The spec stays `Ready` in
302
- `backlog`, ready for a `/spec-start` whenever it is wanted.
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.
303
382
  - **`changes`** — work the notes into the spec, record a resolution for each one
304
383
  so the next render shows it struck through with what changed, then
305
384
  **re-render, and wait again**. The reader is still holding a decision, so
@@ -328,6 +407,11 @@ the verdict — and the reader's conclusion is about what the page showed them.
328
407
  verdict to a lunch break.
329
408
  3. **On waking, let the engine pick:**
330
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
+
331
415
  ```
332
416
  skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json
333
417
  ```
@@ -391,15 +475,25 @@ the shape; this section carries only what is specific here.
391
475
 
392
476
  - `✅` — a `Ready` spec is written to `specs/backlog/<name>/`.
393
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.
394
481
  - `⏸` — grilling did not reach a shared understanding, so nothing was written.
395
482
  That is the skill working: a spec written over an unresolved requirement is
396
483
  the outcome Phase A exists to prevent.
397
484
 
398
- **Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
485
+ **Fields:** `Tracker` · `Spec` · `Built` · `Landed` · `Worktree` · `Follow-ups` ·
486
+ `Next`
399
487
 
400
488
  `Built` is the spec's path and phase count; `Spec` is its status and bucket;
401
489
  `Tracker` appears only when a provider is installed and linked it.
402
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
+
403
497
  **`Next` depends on whether Phase C2 rendered a page.** Where it did, the run is
404
498
  waiting on a verdict, so the block **omits the `Review` row** and ends on the
405
499
  banner defined in `.claude/rules/spec-reports.md` — counts, the tier stack, and
@@ -422,6 +516,11 @@ I'm holding here until you send a verdict — the wait covers local and network.
422
516
  There `Next` names the page rather than a command, because the button is what
423
517
  carries the work on.
424
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
+
425
524
  Where no page was rendered — isolation is not configured, or nothing was
426
525
  written — `Next` is `/spec-start <name>`, with the name spelled the way it must
427
526
  be typed.
@@ -239,6 +239,11 @@ wait a timeout — the reasoning is in `/spec-next` §5 and is not repeated here
239
239
  The banner it describes is what this skill emits in place of a `Review` row,
240
240
  and the routing on the verdict is `/spec-diff` §2 and §4, as it is everywhere.
241
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.
246
+
242
247
  **Why this skill arms as well as waits.** Waiting is what any offer does;
243
248
  **arming** asserts an obligation that outlives the turn, and belongs only to
244
249
  work that is finished. A bug fix is a completed unit — red→green, suite
@@ -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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spec-diff
3
- description: See what a spec's worktree changed — render its diff as a page you can mark up, take that review pass back, and act on it. Answers at any point, including half-way through a phase. Use when the user says "/spec-diff", "show me the diff", "what did this phase change", "review this spec's work", wants to read a worktree's changes away from the terminal, or hands back what the review page produced — a six-digit claim code, a bare verdict word, or the pasted JSON.
3
+ description: See what a spec's worktree changed — render its diff as a page you can mark up, take that review pass back, and act on it. Answers at any point, including half-way through a phase, and takes --reviewers to run the project's configured code reviewers. Use when the user says "/spec-diff", "show me the diff", "what did this phase change", "review this spec's work", "run the reviewers", or hands back what the review page produced — a six-digit claim code, a bare verdict word, or the pasted JSON.
4
4
  ---
5
5
 
6
6
  # /spec-diff — see the phase before you commit it
@@ -161,6 +161,13 @@ back, and these steps replace §3–§5 below.
161
161
  applies: it concluded nothing. Go to **§2b**. The engine refuses a pass
162
162
  carrying both, so there is never a choice to make between them.
163
163
 
164
+ **It is on the `claimed:` line** — `· action: live-on` at the end of it, and
165
+ `claimed.action` under `--json`. Read it there rather than inferring one
166
+ from a pass that merged nothing: a claim reporting no verdict and no counts
167
+ used to be the only trace a press left, which made every action look like a
168
+ reader who had concluded nothing. If the line carries no action, there was
169
+ none.
170
+
164
171
  3. **Say what you read, then ask what's up.** Report the accepted count, then
165
172
  each open comment as `file:line — note`, then the files you would touch.
166
173
  **Wait — unless the verdict already said otherwise.** Pasting on its own is
@@ -395,6 +402,14 @@ way out. **Never park another spec's live session** to make room — that is
395
402
  someone else's work, and freeing it is their decision. Re-render and wait, so
396
403
  the reader can choose something else.
397
404
 
405
+ **A refusal may declare the way out of itself.** Ask with
406
+ `skitterspec spec-env live take <spec> --json` and read its `offer` field:
407
+ where one is present, relay the refusal and **then** offer it, exactly as
408
+ `.claude/rules/offered-unblocks.md` describes. Do not re-decide from the reason
409
+ text, and do not infer an offer that is not there — the boundary that keeps
410
+ another spec's workbench out of this lives in that field rather than in prose
411
+ here.
412
+
398
413
  **`allow` writes a committed file, and that is worth a sentence.** It edits
399
414
  `specs/.core/env.config.json` in the **primary checkout**, so it changes
400
415
  behaviour for everyone who pulls and leaves that tree dirty — unlike the live
@@ -437,9 +452,26 @@ help someone give one.
437
452
  ```
438
453
  skitterspec spec-env review <spec> # uncommitted work (the default)
439
454
  skitterspec spec-env review <spec> --branch # everything since the base branch
455
+ skitterspec spec-env review <spec> --run-reviewers # …and run review.reviewers
440
456
  skitterspec spec-env review serve # every spec, on localhost
441
457
  ```
442
458
 
459
+ **`--run-reviewers` is opt-in here, and a BARE `/spec-diff` MUST NOT PASS IT.**
460
+ `/spec-next` runs the project's configured reviewers once per phase, where the
461
+ cost is paid against work that is finished. This skill answers at any point,
462
+ including several times in one phase — and each run spends a review against
463
+ whatever hourly limit the reviewer has, which on the tiers this was built for is
464
+ measured in single figures.
465
+
466
+ So pass it when the operator asked for it: `/spec-diff --reviewers`, or they said
467
+ in words that they want the second opinion. Otherwise render without it. The
468
+ cached findings from the phase-end run still show on the page whenever the diff
469
+ has not moved, so the usual mid-phase read loses nothing by not asking.
470
+
471
+ Where it does run, say what it cost — a reviewer takes 30s–3min — and nothing
472
+ else. The page's own strip reports each reviewer's outcome, and a reviewer that
473
+ could not run is that strip's business rather than yours.
474
+
443
475
  **There is no switch left to handle.** The engine serves and prints a **stack** —
444
476
  one line per tier, `local`, `network`, `remote`, each either a URL or the one
445
477
  command that turns it on. All of it is free and none of it publishes anything.
@@ -27,8 +27,10 @@ clobber customised content. Finish with a summary of created vs already-present.
27
27
  > **Leave alone**. Non-interactively it only adds what's missing; `--resync` /
28
28
  > `--reset` (reset needs `--yes`) drive the stronger actions.
29
29
 
30
- The system is **twelve skills**: `spec` (feature), `spec-bug` (bug),
31
- `spec-hotfix` (a bug fixed on a released tag), `spec-review`, `spec-start`,
30
+ The system is **thirteen skills**: `spec` (feature), `spec-bug` (bug),
31
+ `spec-hotfix` (a bug fixed on a released tag),
32
+ `no-spec` (work that genuinely has none — off the base branch all the same),
33
+ `spec-review`, `spec-start`,
32
34
  `spec-next`, `spec-diff` (read a spec's diff as a page),
33
35
  `spec-reviewed` (pick up a review you approved on that page), `spec-to-main`,
34
36
  `spec-complete`, `spec-cancel`, and this `spec-init`. The lifecycle is `backlog → in-progress → complete / cancelled`,
@@ -148,6 +148,12 @@ 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
@@ -280,13 +286,34 @@ the moment the page is about, so render it now — **after** the tests pass and
280
286
  **before** the commit:
281
287
 
282
288
  ```
283
- skitterspec spec-env review <spec>
289
+ skitterspec spec-env review <spec> --run-reviewers
284
290
  ```
285
291
 
286
292
  **This is free.** It is the engine reading git and splicing text into a template;
287
293
  the diff never passes through you, so a 266KB patch costs nothing. Report the
288
294
  path it prints and move on.
289
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
+
290
317
  **This render takes the committing button set**, which is the default — so
291
318
  `--buttons` is not passed. That is a statement about the work, not about the
292
319
  gate: the phase is finished, so `Commit` and `Commit & Continue` are the right
@@ -315,7 +342,7 @@ so one subject lives in one place:
315
342
 
316
343
  ---
317
344
 
318
- ## ⏸ Review ready — <N> files, +<a> −<d>
345
+ ## ⏸ Review ready — <N> files, +<a> −<d><, <n> flagged by <reviewer> — only where one found something>
319
346
 
320
347
  - **local** — <the `local:` URL>
321
348
  - **network** — <the `network:` URL, or off with the command that turns it on>
@@ -327,6 +354,19 @@ I'm holding here until you send a verdict — the wait covers local and network.
327
354
 
328
355
  `/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
329
356
 
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
+
330
370
  **The stack is the engine's — copy it, do not compose it.** `spec-env review`
331
371
  printed one line per tier, in that order, and `--json` carries the same thing as
332
372
  `tiers`. Relay all three whatever they say, including the ones that are off with
@@ -408,6 +448,11 @@ reading — and that is the gap the whole gate exists to close.
408
448
  a diff is the normal case rather than the edge one. Do not give it a duration
409
449
  of your own.
410
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
+
411
456
  Then **end your turn**. Do not poll it and do not hold the turn open: the
412
457
  point is that the reader has the terminal back while they read.
413
458
  3. **On waking, let the engine pick**:
@@ -82,9 +82,9 @@ A **six-digit code** is a pass the operator read off their own page. It says
82
82
  invocation does, above, and claim the code *there*. Then go to step 4 — a named
83
83
  pass has nothing to disambiguate.
84
84
 
85
- A **verdict word** — `commit`, `commit-continue`, `commit-start`, `continue`,
86
- `changes`, `discuss` — is the conclusion itself, arriving without a pass behind
87
- it. It is
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
88
  what a **`file://` page** hands over: that page has no server to POST to and no
89
89
  store to write to, so it copies a command instead of sending anything, and this
90
90
  is the command. Resolve the spec as a bare invocation does, then send the word
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec",
3
- "version": "22.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",