@skitterbyte/skitterspec 19.0.0 → 21.0.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.
- package/MIGRATION.md +170 -0
- package/assets/core/env.config.json.example +2 -1
- package/assets/core/env.config.md +17 -3
- package/assets/hooks/review-gate.cjs +141 -0
- package/assets/review/page.html +366 -30
- package/assets/rules/spec-planning.md +65 -13
- package/assets/rules/spec-reports.md +136 -16
- package/assets/skills/spec-bug/SKILL.md +38 -23
- package/assets/skills/spec-diff/SKILL.md +171 -8
- package/assets/skills/spec-hotfix/SKILL.md +31 -20
- package/assets/skills/spec-init/SKILL.md +34 -0
- package/assets/skills/spec-next/SKILL.md +167 -26
- package/assets/skills/spec-reviewed/SKILL.md +33 -16
- package/package.json +1 -1
- package/src/cli.js +326 -4
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +17 -1
- package/src/env/hooks.js +157 -0
- package/src/env/review.js +249 -3
- package/src/env/serve.js +28 -4
- package/src/init.js +78 -0
|
@@ -29,11 +29,17 @@ rather than invoking it.
|
|
|
29
29
|
|
|
30
30
|
`/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
|
|
31
31
|
convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
|
|
32
|
-
review pass is never claimed unasked.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
review pass is never claimed unasked. Because the model cannot invoke this
|
|
33
|
+
skill, a pass named this way is only ever picked up because a person typed the
|
|
34
|
+
command, and typing it **is** the human signal. Prose alone did not hold that
|
|
35
|
+
line once already.
|
|
36
|
+
|
|
37
|
+
It is **no longer the only way in**, and the difference is worth stating
|
|
38
|
+
precisely. A phase that ends now *waits* on its page, and a pass arriving inside
|
|
39
|
+
that wait is claimed by the engine (`--claim-since`) without anyone typing
|
|
40
|
+
anything — scoped to the window, refusing when two arrive. `/spec-reviewed` is
|
|
41
|
+
what answers everything outside it: a pass sent when nobody was waiting, two
|
|
42
|
+
passes to choose between, and every harness with no file-watch to wait with.
|
|
37
43
|
|
|
38
44
|
`/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
|
|
39
45
|
real judgment (green tests before a land; an MCP fetch and a team-key check; ten
|
|
@@ -174,13 +180,24 @@ paste, exactly as before. The clipboard path is **not legacy**: it is the whole
|
|
|
174
180
|
story for a local reader.
|
|
175
181
|
|
|
176
182
|
The code is **not a secret** — it is printed on the page, and it cannot be a
|
|
177
|
-
gate against Claude either, because the store is a file Claude can read.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
gate against Claude either, because the store is a file Claude can read.
|
|
184
|
+
|
|
185
|
+
It was once true that a device reaching your page
|
|
186
|
+
**could not reach your conversation**, and that fact was the whole guard: a pass sat in the holding
|
|
187
|
+
area until you typed `/spec-reviewed`. It is no longer true, deliberately —
|
|
188
|
+
a phase that ends **waits** for its verdict, so the button you press on the page
|
|
189
|
+
is what carries the work on. What replaced the guard is two mechanisms and one
|
|
190
|
+
rule. The **serve token** — 48 bits of randomness in the URL path, minted per
|
|
191
|
+
server — decides who can POST at all. The **wait window** decides which pass may
|
|
192
|
+
be claimed without you naming it: the engine's `--claim-since` takes the one
|
|
193
|
+
pass that arrived *while this session was waiting*, acts on nothing when none
|
|
194
|
+
did, and refuses to choose when two did. And outside that window the rule stands
|
|
195
|
+
unchanged and absolute: **Claude never claims a pass it was not asked to**
|
|
196
|
+
(`/spec-diff` step 0, because nothing enforces it).
|
|
197
|
+
|
|
198
|
+
What you give up is real and worth naming: the page can now act. What you get is
|
|
199
|
+
the loop closing without anyone remembering a command — which is the failure the
|
|
200
|
+
old design traded it for.
|
|
184
201
|
|
|
185
202
|
What the code *does* is let you **tell two passes apart** — it is an address,
|
|
186
203
|
never a password. One waiting pass is claimed and acted on the moment you type
|
|
@@ -218,6 +235,41 @@ never ticked block nothing — an unticked file is something you said nothing
|
|
|
218
235
|
about, and requiring every one of them would be the tally this design exists to
|
|
219
236
|
avoid.
|
|
220
237
|
|
|
238
|
+
**The gate — a phase that ended owes an answer.** That is the second refusal,
|
|
239
|
+
and it is a different kind: not a count, and not about the marks at all.
|
|
240
|
+
`/spec-next` **arms** it when a phase ends and renders its page, and exactly two
|
|
241
|
+
things clear it — a **committing verdict**, or
|
|
242
|
+
`skitterspec spec-env review skip "<reason>"`. Until one of them happens,
|
|
243
|
+
`/spec-next` refuses to build the next phase and (where the hook is installed)
|
|
244
|
+
`git commit` refuses in that worktree. `skitterspec spec-env review gate
|
|
245
|
+
[--check] [--json]` is what both ask.
|
|
246
|
+
|
|
247
|
+
**The hook is the half that cannot be talked past.** `skitterspec init` and
|
|
248
|
+
`skitterspec update` install `.claude/hooks/review-gate.cjs` and register it in
|
|
249
|
+
the project's committed `.claude/settings.json`, so a `git commit` is refused by
|
|
250
|
+
the harness rather than by prose — which is what covers a bare `git commit`, a chained command, and
|
|
251
|
+
skittership's own `/commit` without skitterspec ever editing it. It decides
|
|
252
|
+
nothing itself: it hands the command line to
|
|
253
|
+
`spec-env review gate --check --for-command` and turns one exit status into an
|
|
254
|
+
answer. It refuses only where the commit is running
|
|
255
|
+
**inside that spec's own worktree** — a commit on the base branch, or in
|
|
256
|
+
another spec's tree, is not this obligation's business — and it fails **open** on everything else: no engine, an
|
|
257
|
+
unreadable payload, a crash, a timeout, a repo with no isolation.
|
|
258
|
+
|
|
259
|
+
Three things keep it a push rather than a wall. It is armed
|
|
260
|
+
**only by a phase ending**, so reading your own half-finished work mid-phase
|
|
261
|
+
owes nothing.
|
|
262
|
+
It has an **exit that is always one command**, and one of them is
|
|
263
|
+
*"I am moving on"* — with a reason, because `none: additive, nothing to revert` is a decision
|
|
264
|
+
a reviewer can argue with while silence is an oversight. And it accuses
|
|
265
|
+
**only on a positive signal**: an unreadable sidecar, a project that set
|
|
266
|
+
`review.required: false`, a spec the engine could not resolve — every
|
|
267
|
+
cannot-tell exits 0 and says nothing (`.claude/rules/negative-checks.md`).
|
|
268
|
+
|
|
269
|
+
It is on by default wherever isolation is configured. That is the point: the
|
|
270
|
+
push toward reading the diff is the normal path, and stepping off it is the
|
|
271
|
+
thing you have to do deliberately.
|
|
272
|
+
|
|
221
273
|
**The verdict names the action**, and that is why it is `commit` rather than
|
|
222
274
|
`approve`: a review is the guard in front of an action, and an approval that
|
|
223
275
|
only recorded itself was the one control on the page that did not describe what
|
|
@@ -249,7 +301,7 @@ its verdicts and its fields.
|
|
|
249
301
|
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
250
302
|
| **Built** | POST /orders handler, orders schema |
|
|
251
303
|
| **Tests** | 128 passed · npm test |
|
|
252
|
-
| **Review** | 7 files, +212 −18 · [open the page](file:///…)
|
|
304
|
+
| **Review** | 7 files, +212 −18 · [open the page](file:///…) |
|
|
253
305
|
| **Follow-ups** | none |
|
|
254
306
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
255
307
|
|
|
@@ -36,7 +36,7 @@ A verdict sentence, a blank line, then a two-column table:
|
|
|
36
36
|
| **Branch** | `spec/feat-foo` · 3 commits, clean |
|
|
37
37
|
| **Built** | POST /orders handler, orders schema |
|
|
38
38
|
| **Tests** | 128 passed · npm test |
|
|
39
|
-
| **Review** | 7 files, +212 −18 · [open the page](file:///…)
|
|
39
|
+
| **Review** | 7 files, +212 −18 · [open the page](file:///…) |
|
|
40
40
|
| **Follow-ups** | none |
|
|
41
41
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
42
42
|
|
|
@@ -69,15 +69,128 @@ where it buried the rows it was meant to sit beside. A missing destination reads
|
|
|
69
69
|
as a rule that does not fit the work, and a rule that does not fit gets ignored.
|
|
70
70
|
So: **if it is worth telling the reader, it is a row.**
|
|
71
71
|
**If it is not a row, it is not worth telling them.**
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
`Notes` is the row most of that content belongs in.
|
|
73
|
+
|
|
74
|
+
**Two controls may follow the block, and nothing else ever.** The ban exists
|
|
75
|
+
because *prose* after the block competes with it for the reader's attention and
|
|
76
|
+
loses them the rows. A control does not compete: it is a row made actionable, in
|
|
77
|
+
the place the reader's eye already finishes. Prose after either of them is the
|
|
78
|
+
same violation wearing a control's clothes.
|
|
79
|
+
|
|
80
|
+
**The first is a picker.** Where the run ends in a choice the reader must make,
|
|
81
|
+
the options may be offered after the table.
|
|
82
|
+
|
|
83
|
+
**The second is the review call-to-action**, and only where the run is actually
|
|
84
|
+
**waiting** on a verdict. It is a banner, not a sentence:
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ⏸ Review ready — 7 files, +212 −18
|
|
89
|
+
|
|
90
|
+
**[Open the page](http://…)** · I'm holding here until you send a verdict.
|
|
91
|
+
|
|
92
|
+
`/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
A rule, a heading naming the state and the size, the link in bold, one line
|
|
97
|
+
saying the run is stopped, and the two exits. Nothing else, and never a
|
|
98
|
+
paragraph explaining it.
|
|
99
|
+
|
|
100
|
+
**Only promise a wait the transport can deliver.** The wait itself is real in
|
|
101
|
+
every case; what varies is what carries it back, not whether the run is
|
|
102
|
+
stopped. Three transports, three true sentences:
|
|
103
|
+
|
|
104
|
+
- **A page the engine SERVES** hands its pass to the local store, which a
|
|
105
|
+
file-watch sees. The run is woken by the press:
|
|
106
|
+
*"I'm holding here until you send a verdict."*
|
|
107
|
+
- **A `file://` page** has no server to POST to, so the pass is copied and
|
|
108
|
+
pasted — the reader's next message is what carries it. The run is stopped just
|
|
109
|
+
the same, and says so:
|
|
110
|
+
*"I'm holding here — paste the pass when you have it."*
|
|
111
|
+
This is the same wait, carried by the conversation instead of a watch. It is
|
|
112
|
+
not a lesser one, and it is not an excuse to ask without waiting.
|
|
113
|
+
- **A PUBLISHED page** writes to the artifact's own store, and nothing pushes
|
|
114
|
+
from there into the conversation. That is the one case where a verdict is
|
|
115
|
+
genuinely invisible until someone asks for it, so the banner says that instead
|
|
116
|
+
of claiming a watch:
|
|
117
|
+
|
|
118
|
+
**[Open the page](https://…)** · press a verdict, then type `/spec-reviewed` — I cannot see it until you do.
|
|
119
|
+
|
|
120
|
+
**A harness with no file-watch does not get an exemption.** It once read
|
|
121
|
+
"change nothing" — keep the row, keep the question, do not wait — and that
|
|
122
|
+
escape hatch is what let a whole class of runs go on asking questions nobody was
|
|
123
|
+
listening for. Where a watch is unavailable the wait is the turn ending, which
|
|
124
|
+
every harness can do.
|
|
125
|
+
|
|
126
|
+
**The `Continue` ending, and why the banner's exits differ mid-run.** A page
|
|
127
|
+
rendered part-way through a run offers `Continue` — *I have read it, carry on* —
|
|
128
|
+
in place of the committing verdicts, because "commit" is the wrong verb for
|
|
129
|
+
unfinished work. Its banner names the same state and different exits:
|
|
130
|
+
|
|
131
|
+
**[Open the page](http://…)** · I'm holding here until you send a verdict.
|
|
132
|
+
|
|
133
|
+
`Continue` carries on · `Request changes` works them now
|
|
134
|
+
|
|
135
|
+
**It is not the `none` verdict that was removed.** `none` recorded itself and
|
|
136
|
+
did nothing, which is the record-and-do-nothing ending this contract exists
|
|
137
|
+
against. `Continue` **resumes the run** — it names an action, which is the bar
|
|
138
|
+
every verdict has to clear. What it does not do is commit, and it can never
|
|
139
|
+
clear the gate a finished phase armed: that gate is discharged by a committing
|
|
140
|
+
verdict or a recorded skip, and by nothing else.
|
|
141
|
+
|
|
142
|
+
**So waiting and arming are separate**, and only one of them is about this
|
|
143
|
+
block. *Waiting* is what any offer does, and it is what this section governs.
|
|
144
|
+
*Arming* asserts an obligation that outlives the turn, and belongs only to work
|
|
145
|
+
that is finished. A mid-run render waits without arming — walking away from it
|
|
146
|
+
costs nothing, which is exactly why `Continue` is safe to offer there.
|
|
147
|
+
|
|
148
|
+
**Exactly one link, never two.** The banner offers the page the reader can
|
|
149
|
+
actually use, and the engine has already decided which that is — a served URL
|
|
150
|
+
when it could serve, a published one only when it could not. Handing over both
|
|
151
|
+
asks the reader to know which door the run is standing behind, and they cannot:
|
|
152
|
+
it was done, and a verdict was pressed on the published page three times while
|
|
153
|
+
each one sat unread under a line claiming the run was waiting. Two links is not
|
|
154
|
+
a convenience with a caveat; it is the caveat existing at all.
|
|
155
|
+
|
|
156
|
+
**This is not the old failure returning**, and the difference is the whole
|
|
157
|
+
justification. The offer used to be two quoted lines in the tail of a long
|
|
158
|
+
report, addressed to nobody, under a closing line that told the reader to move
|
|
159
|
+
on — and it was never once taken. It was moved into a labelled row to make it
|
|
160
|
+
findable, and that worked. What changed since is that the run now *waits*: the
|
|
161
|
+
reader is not being offered something optional, they are being told the work has
|
|
162
|
+
stopped until they answer. A row cannot carry that, because a row is scanned at
|
|
163
|
+
the same weight as every other row. So the state gets the loudest shape on
|
|
164
|
+
screen, at the end, where reading finishes.
|
|
165
|
+
|
|
166
|
+
**ASKING IMPLIES WAITING — one rule, and the two shapes follow from it.** Any
|
|
167
|
+
render that asks the reader for a verdict ends the turn watching for one. A run
|
|
168
|
+
that does not intend to wait does not ask: its `Review` row keeps the counts and
|
|
169
|
+
the link and **loses the question**.
|
|
170
|
+
|
|
171
|
+
So there are two shapes and no third:
|
|
172
|
+
|
|
173
|
+
- **Asking** → the banner above, and the run waits.
|
|
174
|
+
- **Not asking** → the `Review` row: counts and link, no question.
|
|
175
|
+
|
|
176
|
+
**What this guards against is a reader taught that the button is decorative.**
|
|
177
|
+
`/spec-bug` and `/spec-hotfix` used to render the page, emit a row
|
|
178
|
+
asking *"want a written review before you commit?"*, and finish — with nothing
|
|
179
|
+
watching. A verdict pressed on that page landed in the holding area and stayed
|
|
180
|
+
there, because the run had told the reader the page was **ready** rather than
|
|
181
|
+
that it was **waiting**. It happened twice on one spec, and the second press
|
|
182
|
+
existed only because the first appeared to do nothing. An offer that cannot be
|
|
183
|
+
answered costs more than never having offered.
|
|
184
|
+
|
|
185
|
+
**Rejected: keeping the question and adding a caveat** — *"press a verdict, but
|
|
186
|
+
I will not see it until you ask"*. That is exactly the shape that stranded those
|
|
187
|
+
two passes, and a truthful caveat does not make an unanswerable question worth
|
|
188
|
+
asking. Either watch for the answer, or do not ask.
|
|
189
|
+
|
|
190
|
+
**A render nobody is being held for is still worth reporting** — a mid-phase
|
|
191
|
+
`/spec-diff`, a page produced alongside other work. It gets the row, because
|
|
192
|
+
giving it a banner teaches the reader to scroll past banners. The shape marks
|
|
193
|
+
the difference between *here is a page* and *nothing proceeds until you answer*.
|
|
81
194
|
|
|
82
195
|
**Never fence a message to the reader.** A fenced block is for a command to
|
|
83
196
|
run, code, or engine output quoted verbatim — things the reader copies or
|
|
@@ -130,11 +243,11 @@ behind it — say nothing rather than reporting that there was nothing to report
|
|
|
130
243
|
| `Cause` | The root cause, for work that diagnosed one. |
|
|
131
244
|
| `Built` | What the run produced — the code, the edits, the spec written. |
|
|
132
245
|
| `Tests` | The result and the command that produced it. |
|
|
133
|
-
| `
|
|
246
|
+
| `Notes` | What this run hit and handled — one short paragraph. Named `Snags` once, which announced a problem before the reader had read one; most of what belongs here is neither good nor bad, just worth knowing. A wrong turn, a guard that fired, a check of yours that turned out to be a false negative. Not a caveat on the outcome (`⚠️` and `Why` carry those) and not future work (`Follow-ups` carries that): this is what happened on the way. |
|
|
134
247
|
| `Landed` | A fast-forward, a tag, a cherry-pick. |
|
|
135
248
|
| `Worktree` | A worktree provisioned, entered, or torn down. |
|
|
136
249
|
| `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
|
|
137
|
-
| `Review` | The rendered diff page: files, `+`/`−`, the link —
|
|
250
|
+
| `Review` | The rendered diff page: files, `+`/`−`, the link. **No question** — a row cannot be waited on, so a question in one is unanswerable by construction (see *asking implies waiting*). **Omitted entirely when the run is waiting on a verdict**: the banner after the block carries it instead, and a row saying the same thing beside it splits the reader's attention across two places. |
|
|
138
251
|
| `Follow-ups` | **Always.** `none`, or one line each. |
|
|
139
252
|
| `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
|
|
140
253
|
|
|
@@ -152,10 +265,17 @@ is how that verdict stops meaning anything. It is still reported rather than
|
|
|
152
265
|
silent, because "I provisioned, and your four files are still sitting there" is
|
|
153
266
|
a different sentence from "I provisioned".
|
|
154
267
|
|
|
155
|
-
**`Review` is one row, not two
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
268
|
+
**`Review` is one row, not two** — and where the run is waiting, it is no rows
|
|
269
|
+
at all. The page and what to do with it are the same subject, and splitting them
|
|
270
|
+
made the reader resolve a distinction before acting on either. A waiting run
|
|
271
|
+
promotes the whole subject into the banner; keeping the row as well would
|
|
272
|
+
recreate that split with the two halves further apart than ever.
|
|
273
|
+
|
|
274
|
+
**The row carries no question, and that is the rule above applied here.** It
|
|
275
|
+
once ended *"want a written review before you commit?"* — a question addressed
|
|
276
|
+
to a reader the run was not waiting for. Putting it in a findable row fixed
|
|
277
|
+
where it was; it did not fix that nothing was listening. A run that wants that
|
|
278
|
+
question answered waits for it, and waiting means the banner.
|
|
159
279
|
|
|
160
280
|
## The four verdicts
|
|
161
281
|
|
|
@@ -208,7 +208,7 @@ is a no-op.
|
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
|
|
211
|
-
## 5b. Render the page
|
|
211
|
+
## 5b. Render the page, arm the gate, then wait for the verdict
|
|
212
212
|
|
|
213
213
|
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
214
214
|
present). Without it there is no worktree to read and this step does not exist —
|
|
@@ -224,28 +224,37 @@ skitterspec spec-env review <spec>
|
|
|
224
224
|
**This is free.** The engine reads git and splices the patches into a template;
|
|
225
225
|
the diff never passes through you, so a 266KB patch costs nothing.
|
|
226
226
|
|
|
227
|
-
**Then
|
|
228
|
-
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
229
|
-
— and that spend is the operator's call, not a default.
|
|
227
|
+
**Then arm the gate**, so the fix now owes a verdict:
|
|
230
228
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
|
|
235
|
-
|
|
236
|
-
**It ends in a question, addressed to someone.** It was once a fenced block of
|
|
237
|
-
engine output, and it fired on every phase and was never once taken: two quoted
|
|
238
|
-
lines under the test counts, addressed to nobody, with the report then closing
|
|
239
|
-
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
240
|
-
so they did. A row in a labelled table is findable; a question in it is
|
|
241
|
-
answerable. Both halves are load-bearing.
|
|
242
|
-
|
|
243
|
-
**Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
|
|
244
|
-
the page and the question stay in the same row: two adjacent rows about one page
|
|
245
|
-
make the reader resolve a distinction before acting on either. A later edit that
|
|
246
|
-
moves it out of the block, or separates the link from the question, undoes this
|
|
247
|
-
and should be read as a regression rather than tidying.
|
|
229
|
+
```
|
|
230
|
+
skitterspec spec-env review arm <spec>
|
|
231
|
+
```
|
|
248
232
|
|
|
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.
|
|
239
|
+
|
|
240
|
+
**Why this skill arms as well as waits.** Waiting is what any offer does;
|
|
241
|
+
**arming** asserts an obligation that outlives the turn, and belongs only to
|
|
242
|
+
work that is finished. A bug fix is a completed unit — red→green, suite
|
|
243
|
+
passing — so it qualifies, and a wait with nothing owed behind it is a
|
|
244
|
+
suggestion rather than a gate. A mid-run render waits without arming, and
|
|
245
|
+
walking away from that costs nothing.
|
|
246
|
+
|
|
247
|
+
**This is the gap this step existed inside.** It used to render the page, emit a
|
|
248
|
+
row asking *"want a written review before you commit?"*, and finish — with
|
|
249
|
+
nothing watching. A verdict pressed on that page landed in the holding area and
|
|
250
|
+
stayed there, because the run had said the page was **ready** rather than that
|
|
251
|
+
it was **waiting**. Two were pressed on one spec and both were stranded; the
|
|
252
|
+
second existed only because the first appeared to do nothing.
|
|
253
|
+
|
|
254
|
+
**It is user-visible, and that is deliberate.** Once armed, a `git commit` in
|
|
255
|
+
this worktree is refused until a verdict is sent or
|
|
256
|
+
`skitterspec spec-env review skip "<reason>"` records the decision to move on.
|
|
257
|
+
The exit is always one command, and one of them is *"I am moving on"*.
|
|
249
258
|
|
|
250
259
|
Relay the **`open:`** line rather than the bare path: a path is not clickable in
|
|
251
260
|
any terminal, and a page nobody can open is a page nobody reads.
|
|
@@ -307,9 +316,15 @@ the shape; this section carries only what is specific here.
|
|
|
307
316
|
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
308
317
|
`Review` · `Follow-ups` · `Next`
|
|
309
318
|
|
|
319
|
+
**`Review` is emitted only where step 5b did not run** — a project with no
|
|
320
|
+
isolation, or a render that failed. Where the run is waiting, the banner carries
|
|
321
|
+
the whole subject and the row is dropped, per `.claude/rules/spec-reports.md`.
|
|
322
|
+
|
|
310
323
|
`Cause` is the root cause in one clause and `Built` is the fix — the
|
|
311
324
|
failing→passing test belongs in `Tests`, named, so the evidence is a test name
|
|
312
325
|
rather than an adjective.
|
|
313
326
|
|
|
314
|
-
Step 5b
|
|
315
|
-
|
|
327
|
+
Step 5b ends in the **banner**, not a paragraph and not a row — the run is
|
|
328
|
+
waiting on a verdict, and the banner is the shape that says so. Nothing follows
|
|
329
|
+
it. Where the run is not waiting it asks nothing at all: a `Review` row carries
|
|
330
|
+
the counts and the link, and no question (*asking implies waiting*).
|
|
@@ -52,14 +52,20 @@ A review comes back to you one of two ways, and **both are ordinary**:
|
|
|
52
52
|
Either way, **this is not a request to render anything**: it is a review coming
|
|
53
53
|
back, and these steps replace §3–§5 below.
|
|
54
54
|
|
|
55
|
-
0. **Never claim a pass you were not asked to claim.**
|
|
55
|
+
0. **Never claim a pass you were not asked to claim.** And never go looking
|
|
56
|
+
for one.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
reaches the
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
A device that reaches the page can POST all day. What decides whether one of
|
|
59
|
+
those passes reaches the operator's review is this rule, and — since the wait
|
|
60
|
+
in §4b — two mechanical facts beside it: the **serve token**, 48 unguessable
|
|
61
|
+
bits minted per server, which decides who can POST at all; and the wait
|
|
62
|
+
**window**, which decides which pass a watch may claim. There is exactly one
|
|
63
|
+
way a pass is taken without a person naming it, `--claim-since`, it is
|
|
64
|
+
described in §4b, and it acts on nothing unless precisely one pass arrived
|
|
65
|
+
while this session was waiting for it.
|
|
66
|
+
|
|
67
|
+
Everything else here is unchanged. A pass that was already sitting there when
|
|
68
|
+
you arrived is never yours to take.
|
|
63
69
|
|
|
64
70
|
**Do not read the code out of `.spec-env/reviews/<spec>.pending.json`.** The
|
|
65
71
|
store is a file you can open, so nothing stops you; that is precisely why the
|
|
@@ -342,6 +348,17 @@ is no. The page is free to produce and changes nothing. The same goes for the
|
|
|
342
348
|
marks: a spec with unread files or open comments is an ordinary spec, and
|
|
343
349
|
nothing here may start counting them.
|
|
344
350
|
|
|
351
|
+
**And this skill never arms the gate.** Arming is `/spec-next`'s, at the one
|
|
352
|
+
moment that means something: a phase ended. Rendering mid-phase — the common
|
|
353
|
+
case this skill exists for — must not create an obligation, because then
|
|
354
|
+
looking at your own work halfway through would owe you a verdict on it. Reading
|
|
355
|
+
is free; ending a phase is what is answerable.
|
|
356
|
+
|
|
357
|
+
An **already-armed** gate is a different matter, and it is not this skill's to
|
|
358
|
+
enforce either: `/spec-next` §2 is where that refusal lives. Here it is only
|
|
359
|
+
context — the render says a verdict is owed, and this skill's whole job is to
|
|
360
|
+
help someone give one.
|
|
361
|
+
|
|
345
362
|
## 4. Render the page — or serve it
|
|
346
363
|
|
|
347
364
|
```
|
|
@@ -384,6 +401,31 @@ Add `--json` to get the file list, totals and the page path back as data. The
|
|
|
384
401
|
page is written to `.spec-env/reviews/<spec>.html`, which is gitignored — it
|
|
385
402
|
leaves no trace in the branch under review.
|
|
386
403
|
|
|
404
|
+
**Declare the button set when the work is unfinished.** A page rendered part-way
|
|
405
|
+
through a run takes `--buttons midrun`, and offers `Continue` — *I have read it,
|
|
406
|
+
carry on* — in place of `Commit` and `Commit & Continue`:
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
skitterspec spec-env review <spec> --buttons midrun
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**It is a statement about the work, never a reading of the gate.** Ask whether
|
|
413
|
+
the thing you just rendered is *finished*: a phase that ended, a bug fix that is
|
|
414
|
+
green, a spec about to land — committing set, which is the default, so pass
|
|
415
|
+
nothing. Half a phase, a hand edit, a colleague's branch mid-flight, anything
|
|
416
|
+
the operator asked to look at while it is still moving — `midrun`.
|
|
417
|
+
|
|
418
|
+
**Do not derive it from whether the gate is armed.** That is tidier and wrong: a
|
|
419
|
+
project running `review.required: false` never arms at all, so every one of its
|
|
420
|
+
pages would lose the committing buttons and its reader could never commit from
|
|
421
|
+
the page. The caller knows what it rendered; the gate only knows whether the
|
|
422
|
+
project opted into gating.
|
|
423
|
+
|
|
424
|
+
`Continue` is not the removed `none` verdict. `none` recorded itself and did
|
|
425
|
+
nothing; `Continue` **resumes the run**, so it still names an action. What it
|
|
426
|
+
cannot do is commit, or clear a gate a finished phase armed — that takes a
|
|
427
|
+
committing verdict or a recorded skip.
|
|
428
|
+
|
|
387
429
|
**On `--page-only`, stop here** and report the path.
|
|
388
430
|
|
|
389
431
|
## 4a. Read the `reader:` line — never sniff for it yourself
|
|
@@ -422,6 +464,87 @@ operator settle where they are reading, and an explicit value is believed withou
|
|
|
422
464
|
sniffing. `review.serveOnRemote` (default `true`) settles whether the engine may
|
|
423
465
|
act on it.
|
|
424
466
|
|
|
467
|
+
## 4b. Wait for the verdict — because asking for one means waiting for it
|
|
468
|
+
|
|
469
|
+
A served page can hand its pass back the moment it is pressed. Without a wait,
|
|
470
|
+
that pass lands in the holding area and stops — nothing happens until someone
|
|
471
|
+
types `/spec-reviewed`, so the review and the work carrying on are two separate
|
|
472
|
+
acts joined only by the operator remembering.
|
|
473
|
+
**This is the step that joins them**, and it is this skill's, so `/spec-next` can point here rather than
|
|
474
|
+
keeping a second copy.
|
|
475
|
+
|
|
476
|
+
**The rule is `.claude/rules/spec-reports.md`'s: asking implies waiting.** If
|
|
477
|
+
this render asks the reader for a verdict, it waits for one. If it is not going
|
|
478
|
+
to wait, it does not ask — the report names the page and its size and stops
|
|
479
|
+
there. There is no third option, and in particular there is no *ask now, notice
|
|
480
|
+
later*: that is the shape that stranded two passes on one spec, where the run
|
|
481
|
+
said the page was **ready** rather than that it was **waiting**.
|
|
482
|
+
|
|
483
|
+
**So the question is not "can I watch a file".** It is "am I asking?" — and the
|
|
484
|
+
transport only decides what carries the answer back:
|
|
485
|
+
|
|
486
|
+
- **A served page** posts to the local store; a file-watch sees it, and the
|
|
487
|
+
steps below are that watch.
|
|
488
|
+
- **A `file://` page** has nothing to post to, so the reader pastes the pass and
|
|
489
|
+
their next message carries it. Say you are holding and **end the turn** — that
|
|
490
|
+
is the same wait, carried by the conversation. It is not a lesser one.
|
|
491
|
+
- **A published page** writes to the artifact's own store, which nothing reaches
|
|
492
|
+
from here. That is the one case where the honest sentence is *press a verdict,
|
|
493
|
+
then type `/spec-reviewed`* — see the published-page paragraph below.
|
|
494
|
+
|
|
495
|
+
**Do not ask, and therefore do not wait, on a render nobody is waiting behind** —
|
|
496
|
+
a bare `--page-only`, a page produced alongside other work, a pass that has
|
|
497
|
+
already arrived. Those get the `Review` row, no question, and nothing is owed.
|
|
498
|
+
|
|
499
|
+
1. **Note the moment**, as an ISO timestamp, before you start. That instant is
|
|
500
|
+
the entire scope of what may be claimed without a person naming it.
|
|
501
|
+
2. **Watch** `.spec-env/reviews/<spec>.pending.json` in the primary checkout,
|
|
502
|
+
and **end the turn**. Not a poll and not a held-open turn: the operator has
|
|
503
|
+
their terminal back, and the session costs nothing while they read.
|
|
504
|
+
3. **On waking, let the engine choose:**
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
skitterspec spec-env review <spec> --claim-since <timestamp> --json
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Three answers, and only one of them acts. One pass in the window — that is
|
|
511
|
+
the pass, claimed and merged exactly as `--claim` would. **None** — ordinary;
|
|
512
|
+
the file changed for some other reason, so say nothing and wait again or
|
|
513
|
+
stop. **Two or more** — it refuses and names the count, never the codes; that
|
|
514
|
+
is §2 step 0's two-passes case, so offer them from the render and ask.
|
|
515
|
+
4. **Route on the verdict** through §2 step 2 onward. A claim is a delivery
|
|
516
|
+
mechanism; nothing downstream may behave differently because a watch woke
|
|
517
|
+
you rather than a person typing.
|
|
518
|
+
|
|
519
|
+
**What holds this up.** Not "the page cannot reach the conversation" — after
|
|
520
|
+
this step it can, deliberately. Two things replace it. The **serve token** is
|
|
521
|
+
48 bits of `crypto` randomness in the URL path, minted per server, and it
|
|
522
|
+
decides who can POST at all. The **window** decides which pass is yours: a pass
|
|
523
|
+
sitting there before you started waiting is never swept up, which is precisely
|
|
524
|
+
the stranger's pass the old rule was written about, and two arrivals refuse
|
|
525
|
+
rather than pick. What is left of the old rule is unchanged and still absolute —
|
|
526
|
+
outside this window, a pass is claimed because a person named it.
|
|
527
|
+
|
|
528
|
+
**The wait covers the SERVED page and nothing else.** It watches the engine's
|
|
529
|
+
local store, which is where a served page POSTs. A **published** page writes to
|
|
530
|
+
the artifact's own store instead, and no watch of any kind reaches that — so a
|
|
531
|
+
verdict pressed there is invisible until someone asks for it. Never start a
|
|
532
|
+
wait and then hand over a published link under it: that reads as a promise to
|
|
533
|
+
notice, and it was made three times in a row while three verdicts sat unread.
|
|
534
|
+
Where the page is published, say plainly that `/spec-reviewed` is what picks it
|
|
535
|
+
up.
|
|
536
|
+
|
|
537
|
+
**`/spec-reviewed` is not replaced by this.** It stays the way in for a pass
|
|
538
|
+
that arrived when nobody was waiting, for the two-passes case, and for every
|
|
539
|
+
published page. It is user-only, and that is still the
|
|
540
|
+
enforcement that makes a named claim a person's decision.
|
|
541
|
+
|
|
542
|
+
**A harness with no file-watch is no longer on that list**, and that is the
|
|
543
|
+
change. It used to be — the row and `/spec-reviewed` were "the whole story",
|
|
544
|
+
which read as permission to ask without waiting. Now the turn ending is the
|
|
545
|
+
wait, and every harness can end a turn; `/spec-reviewed` remains available
|
|
546
|
+
there, as it is everywhere, for a pass nobody was holding for.
|
|
547
|
+
|
|
425
548
|
## 5. Offer the written review — say what it costs first
|
|
426
549
|
|
|
427
550
|
The page is free. The **written review is not**, and it costs in two separate
|
|
@@ -505,6 +628,46 @@ The engine knows nothing about publishing and cannot do it. It writes a file and
|
|
|
505
628
|
reads a URL back as an opaque string; everything about what that string means
|
|
506
629
|
lives here.
|
|
507
630
|
|
|
631
|
+
### Publish it so the buttons still work
|
|
632
|
+
|
|
633
|
+
A published page is the answer for a reader **no local server can reach** — a
|
|
634
|
+
phone on mobile data, anyone away from the LAN the engine serves on. It is also
|
|
635
|
+
the one surface where the verdict buttons have nowhere to POST: the page's own
|
|
636
|
+
URL is on claude.ai, so a POST fails and the reader gets "could not reach the
|
|
637
|
+
server" after pressing a verdict they meant.
|
|
638
|
+
|
|
639
|
+
So **publish it with a store**, and the page uses it:
|
|
640
|
+
|
|
641
|
+
- Declare `capabilities: {db: {}}`. The page checks for `window.claude.use` and
|
|
642
|
+
writes the pass into the artifact's own `passes` collection instead of
|
|
643
|
+
POSTing. Nothing else about it changes — same marks, same verdicts, same
|
|
644
|
+
refusal to commit over an open comment.
|
|
645
|
+
- **Do not declare `user`.** The pass is not per-viewer private state, a shared
|
|
646
|
+
collection is what it wants, and a declaration nothing uses is a grant asked
|
|
647
|
+
for nothing.
|
|
648
|
+
- **Same file path every time**, so a redeploy reuses the URL. That is what
|
|
649
|
+
answers the clear-down worry: one page per spec, replaced at each render,
|
|
650
|
+
rather than an artifact per phase accumulating in the gallery.
|
|
651
|
+
- **Send a push notification with the URL** when you publish. The reader is by
|
|
652
|
+
definition somewhere else — that is why it was published.
|
|
653
|
+
|
|
654
|
+
### Taking a stored pass back
|
|
655
|
+
|
|
656
|
+
A pass in the store is claimed the same way in spirit and a different way in
|
|
657
|
+
mechanism, because the engine cannot see it:
|
|
658
|
+
|
|
659
|
+
1. Read the `passes` collection with the Artifact tool's `read_db`.
|
|
660
|
+
2. **Apply step 0 unchanged.** One waiting pass, offered by what it says and
|
|
661
|
+
when it was sent; two is a refusal to guess. Nothing here is automatic —
|
|
662
|
+
there is no wait window on this transport, because nothing pushes from the
|
|
663
|
+
store to this conversation.
|
|
664
|
+
3. Write the pass's `blob` to a scratch file **verbatim** and merge it with
|
|
665
|
+
`spec-env review <spec> --notes <file>`. It rejoins the ordinary path at §2
|
|
666
|
+
step 1, so the verdict is judged and routed exactly as any other.
|
|
667
|
+
4. **Delete the document** with `write_db` once it is merged. A claim consumes:
|
|
668
|
+
a pass left in the store is claimable twice, which is the one property the
|
|
669
|
+
six-digit code has always had and this transport must not lose.
|
|
670
|
+
|
|
508
671
|
## 7. Report
|
|
509
672
|
|
|
510
673
|
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
@@ -520,7 +683,7 @@ the shape; this section carries only what is specific here.
|
|
|
520
683
|
- `⏸` — the spec has no worktree. Say that plainly: a spec that has not been
|
|
521
684
|
started has nothing to diff, which is an ordinary state and not an error.
|
|
522
685
|
|
|
523
|
-
**Fields:** `Built` · `Tests` · `
|
|
686
|
+
**Fields:** `Built` · `Tests` · `Notes` · `Review` · `Follow-ups` · `Next`
|
|
524
687
|
|
|
525
688
|
## 7a. End in a picker
|
|
526
689
|
|