@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
|
@@ -230,7 +230,7 @@ is a no-op.
|
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
|
|
233
|
-
## 6b. Render the page
|
|
233
|
+
## 6b. Render the page, arm the gate, then wait for the verdict
|
|
234
234
|
|
|
235
235
|
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
236
236
|
present). Without it there is no worktree to read and this step does not exist —
|
|
@@ -246,28 +246,33 @@ skitterspec spec-env review <spec>
|
|
|
246
246
|
**This is free.** The engine reads git and splices the patches into a template;
|
|
247
247
|
the diff never passes through you, so a 266KB patch costs nothing.
|
|
248
248
|
|
|
249
|
-
**Then
|
|
250
|
-
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
251
|
-
— and that spend is the operator's call, not a default.
|
|
249
|
+
**Then arm the gate**, so the fix now owes a verdict:
|
|
252
250
|
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
```
|
|
252
|
+
skitterspec spec-env review arm <spec>
|
|
253
|
+
```
|
|
255
254
|
|
|
256
|
-
|
|
255
|
+
**Then wait**, and `/spec-next` §5 owns the sequence — follow it there rather
|
|
256
|
+
than reading a second copy here: note the moment you start waiting, watch the
|
|
257
|
+
pending store, end your turn, and let `--claim-since` pick the one pass that
|
|
258
|
+
arrived inside the window. The banner it describes is what this skill emits in
|
|
259
|
+
place of a `Review` row, and the routing on the verdict is `/spec-diff` §2 and
|
|
260
|
+
§4, as it is everywhere.
|
|
257
261
|
|
|
258
|
-
**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
answerable. Both halves are load-bearing.
|
|
262
|
+
**Why this skill arms as well as waits.** Waiting is what any offer does;
|
|
263
|
+
**arming** asserts an obligation that outlives the turn, and belongs only to
|
|
264
|
+
work that is finished. A hotfix is a completed unit — red→green against a
|
|
265
|
+
released tag — so it qualifies, and a wait with nothing owed behind it is a
|
|
266
|
+
suggestion rather than a gate.
|
|
264
267
|
|
|
265
|
-
**
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
moves it out of the block, or separates the link from the question, undoes this
|
|
269
|
-
and should be read as a regression rather than tidying.
|
|
268
|
+
**A hotfix is the case where reading it matters most.** This change is about to
|
|
269
|
+
be tagged and shipped to production from a release line, not merged into a
|
|
270
|
+
branch someone else will read first. The verdict is the only review it gets.
|
|
270
271
|
|
|
272
|
+
**It is user-visible, and that is deliberate.** Once armed, a `git commit` in
|
|
273
|
+
this worktree is refused until a verdict is sent or
|
|
274
|
+
`skitterspec spec-env review skip "<reason>"` records the decision to move on.
|
|
275
|
+
The exit is always one command, and one of them is *"I am moving on"*.
|
|
271
276
|
|
|
272
277
|
Relay the **`open:`** line rather than the bare path: a path is not clickable in
|
|
273
278
|
any terminal, and a page nobody can open is a page nobody reads.
|
|
@@ -335,10 +340,16 @@ the shape; this section carries only what is specific here.
|
|
|
335
340
|
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
336
341
|
`Review` · `Follow-ups` · `Next`
|
|
337
342
|
|
|
343
|
+
**`Review` is emitted only where step 6b did not run** — a project with no
|
|
344
|
+
isolation, or a render that failed. Where the run is waiting, the banner carries
|
|
345
|
+
the whole subject and the row is dropped, per `.claude/rules/spec-reports.md`.
|
|
346
|
+
|
|
338
347
|
**The base tag goes in the verdict clause** — `✅ /spec-hotfix · hotfix-foo ·
|
|
339
348
|
green on v2.3.1`. Which released version this was fixed against is the first
|
|
340
349
|
thing anyone needs, and it is not a field: the clause is where the run says
|
|
341
350
|
where it got to.
|
|
342
351
|
|
|
343
|
-
Step 6b
|
|
344
|
-
|
|
352
|
+
Step 6b ends in the **banner**, not a paragraph and not a row — the run is
|
|
353
|
+
waiting on a verdict, and the banner is the shape that says so. Nothing follows
|
|
354
|
+
it. Where the run is not waiting it asks nothing at all: a `Review` row carries
|
|
355
|
+
the counts and the link, and no question (*asking implies waiting*).
|
|
@@ -105,6 +105,40 @@ Ensure it exists. If missing, create it documenting:
|
|
|
105
105
|
Read a sibling spec skill (e.g. `spec`, `spec-next`) for the canonical shapes
|
|
106
106
|
rather than inventing them. If the rule already exists, leave it unless stale.
|
|
107
107
|
|
|
108
|
+
## 4a. The review-gate hook (`.claude/hooks/review-gate.cjs`)
|
|
109
|
+
|
|
110
|
+
`skitterspec init` **and `skitterspec update`** install the hook script and
|
|
111
|
+
register it in the project's **committed** `.claude/settings.json` as a
|
|
112
|
+
`PreToolUse` hook on `Bash`. It runs one engine call per Bash tool call and
|
|
113
|
+
refuses a `git commit` in a worktree whose phase is still awaiting a verdict.
|
|
114
|
+
|
|
115
|
+
**Both commands, and that is not a detail.** Copying the script and registering
|
|
116
|
+
it are one operation, and they were once split across two code paths — so
|
|
117
|
+
`update` landed the file, reported `created:`, and wired nothing, on every
|
|
118
|
+
project that upgraded. Landing the script is not installing the hook.
|
|
119
|
+
|
|
120
|
+
**`.cjs`, and the extension is load-bearing.** The script lands inside the
|
|
121
|
+
target project, where *that* project's `package.json` decides how node parses a
|
|
122
|
+
`.js` — so CommonJS shipped as `.js` crashes in any `"type": "module"` project,
|
|
123
|
+
on every Bash tool call. `.cjs` settles it at the file. A registration left over
|
|
124
|
+
from the release that named `.js` is rewritten in place, keeping any wrapping
|
|
125
|
+
the operator added, rather than gaining a second entry beside it.
|
|
126
|
+
|
|
127
|
+
**Committed, not machine-local**, and the difference is the point: the trusted
|
|
128
|
+
worktree root is one machine's absolute path, while *a phase that ended owes an
|
|
129
|
+
answer* is the project's policy and should reach everyone who clones it. The
|
|
130
|
+
command is written with `${CLAUDE_PROJECT_DIR}`, so it holds in worktrees too.
|
|
131
|
+
|
|
132
|
+
**Say it is there, once.** A hook that blocks a commit with the operator not
|
|
133
|
+
knowing a hook exists reads as a broken git, so name it in the report when it is
|
|
134
|
+
newly registered — and say what turns it off (`review.required: false` in
|
|
135
|
+
`env.config.json`, which the hook defers to entirely).
|
|
136
|
+
|
|
137
|
+
**Never fatal, and never rewritten.** A settings file that is not parseable JSON
|
|
138
|
+
is reported and left exactly as it is — it is the operator's config, and
|
|
139
|
+
everything else in it would be lost. A settings file that already names this
|
|
140
|
+
script, however it was wrapped, is left alone rather than gaining a second copy.
|
|
141
|
+
|
|
108
142
|
## 5. CLAUDE.md
|
|
109
143
|
|
|
110
144
|
Ensure a `## Spec workflow` section exists. If absent, add one with the
|
|
@@ -116,7 +116,7 @@ A **name argument** is accepted, but it must *match* the spec in flight — it
|
|
|
116
116
|
narrows a re-run, it does not select a different spec. A mismatch refuses,
|
|
117
117
|
naming both.
|
|
118
118
|
|
|
119
|
-
## 2. Pre-flight —
|
|
119
|
+
## 2. Pre-flight — the last phase is committed, and was answered
|
|
120
120
|
|
|
121
121
|
Before writing any code for this phase, get the workspace clean:
|
|
122
122
|
|
|
@@ -128,6 +128,32 @@ Before writing any code for this phase, get the workspace clean:
|
|
|
128
128
|
next phase on top of an uncommitted one. (Skip if this is the first phase —
|
|
129
129
|
there's nothing prior to commit.)
|
|
130
130
|
|
|
131
|
+
- **Confirm the last phase's review was answered.** Ask the engine, never the
|
|
132
|
+
sidecar:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
skitterspec spec-env review gate <spec> --json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`state: "armed"` means a phase ended, its page was rendered, and nobody has
|
|
139
|
+
said what they concluded. **Refuse, with the `⏸` block**, and name the three
|
|
140
|
+
ways out in the `Next` row: read the page and send a verdict, type
|
|
141
|
+
`/spec-reviewed` if one is already waiting, or
|
|
142
|
+
`skitterspec spec-env review skip "<reason>"` to move on with the reason on
|
|
143
|
+
the record.
|
|
144
|
+
|
|
145
|
+
`state: "clear"` carries on.
|
|
146
|
+
**`state: "unknown"` also carries on, in silence** — it is the project opting out, a sidecar that could not be read, or
|
|
147
|
+
a spec the engine could not resolve, and none of those is evidence that
|
|
148
|
+
anything is owed (`.claude/rules/negative-checks.md`). Do not mention it: a
|
|
149
|
+
line about a gate nobody armed is an accusation against a healthy repo.
|
|
150
|
+
|
|
151
|
+
**This refusal counts nothing.** It is not a tally of ticked boxes — those
|
|
152
|
+
still gate nothing and still are not counted. It asserts one thing: a phase
|
|
153
|
+
that ended has an answer. And the exit is always **one command**, one of them
|
|
154
|
+
being *"I am moving on"*, which is what keeps this a push rather than a wall —
|
|
155
|
+
a gate with no exit gets switched off wholesale instead of answered.
|
|
156
|
+
|
|
131
157
|
## 3. Implement the phase
|
|
132
158
|
|
|
133
159
|
Identify the **first unfinished phase** from the `00-overview.md` phase index,
|
|
@@ -226,7 +252,7 @@ skitterspec spec-env resolve <spec> --assert-primary-clean
|
|
|
226
252
|
- **"cannot tell"** — no baseline, or one from another spec. It exits 0 and
|
|
227
253
|
claims nothing; say so in one line and carry on. An absence is not evidence.
|
|
228
254
|
|
|
229
|
-
## 5. Render the page
|
|
255
|
+
## 5. Render the page, arm the gate, then wait for the verdict
|
|
230
256
|
|
|
231
257
|
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
232
258
|
present). Without it there is no worktree to read and this step does not exist.
|
|
@@ -243,31 +269,137 @@ skitterspec spec-env review <spec>
|
|
|
243
269
|
the diff never passes through you, so a 266KB patch costs nothing. Report the
|
|
244
270
|
path it prints and move on.
|
|
245
271
|
|
|
272
|
+
**This render takes the committing button set**, which is the default — so
|
|
273
|
+
`--buttons` is not passed. That is a statement about the work, not about the
|
|
274
|
+
gate: the phase is finished, so `Commit` and `Commit & Continue` are the right
|
|
275
|
+
verbs for it. A render part-way through a run takes `--buttons midrun` and
|
|
276
|
+
offers `Continue` instead; `/spec-diff` owns that case.
|
|
277
|
+
|
|
278
|
+
**Then arm the gate**, so the phase now owes a verdict:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
skitterspec spec-env review arm <spec> --phase <n>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
It is idempotent within a phase, so a re-render does not restart the clock, and
|
|
285
|
+
it is **never fatal**: a project that opted out, or an engine that could not
|
|
286
|
+
resolve the spec, says so and the phase is still built. Nothing here counts
|
|
287
|
+
anything — the gate asserts that a phase which ended has an answer, and that is
|
|
288
|
+
all it asserts.
|
|
289
|
+
|
|
246
290
|
**Then offer `/spec-diff`. Do not run it.** The written review is the part that
|
|
247
291
|
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
248
292
|
— and that spend is the operator's call, not a default.
|
|
249
293
|
|
|
250
|
-
**
|
|
251
|
-
and
|
|
294
|
+
**Where you are going to wait, the offer is the banner after the block** —
|
|
295
|
+
defined in `.claude/rules/spec-reports.md`, and **the `Review` row is dropped**
|
|
296
|
+
so one subject lives in one place:
|
|
252
297
|
|
|
253
|
-
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## ⏸ Review ready — <N> files, +<a> −<d>
|
|
301
|
+
|
|
302
|
+
**[Open the page](<the `open:` URL>)** · I'm holding here until you send a verdict.
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
engine output, and it fired on every phase and was never once taken: two quoted
|
|
257
|
-
lines under the test counts, addressed to nobody, with the report then closing
|
|
258
|
-
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
259
|
-
so they did. A row in a labelled table is findable; a question in it is
|
|
260
|
-
answerable. Both halves are load-bearing.
|
|
304
|
+
`/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
|
|
261
305
|
|
|
262
|
-
**
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
306
|
+
**One link, and the engine has already chosen it.** Where it served, the
|
|
307
|
+
banner carries the served URL and the wait is real. Where it could not serve —
|
|
308
|
+
a busy port, a machine with no network address — that is the case publishing
|
|
309
|
+
exists for, and then the banner carries the published URL with what is true of
|
|
310
|
+
it: *press a verdict, then type `/spec-reviewed`*, because nothing pushes from
|
|
311
|
+
the artifact store into this conversation.
|
|
312
|
+
|
|
313
|
+
**Never offer both.** Publishing while the server is reachable adds a second
|
|
314
|
+
door the reader cannot tell apart from the first, and the wait only stands
|
|
315
|
+
behind one of them — that was done, and three verdicts were pressed on the
|
|
316
|
+
published page while each sat unread under a line saying I was holding. The
|
|
317
|
+
published page is for the reader the server cannot reach, and for nobody
|
|
318
|
+
else.
|
|
319
|
+
|
|
320
|
+
---
|
|
267
321
|
|
|
268
|
-
**
|
|
269
|
-
|
|
270
|
-
|
|
322
|
+
**Where you are not waiting, it stays the `Review` row** — the counts and the
|
|
323
|
+
page link, and **no question**:
|
|
324
|
+
|
|
325
|
+
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) |
|
|
326
|
+
|
|
327
|
+
At the end of a phase you are always waiting, so this shape belongs to the
|
|
328
|
+
renders that are not this step: a mid-phase `/spec-diff`, a page produced
|
|
329
|
+
alongside other work. A row cannot be waited on, so a question in one is
|
|
330
|
+
unanswerable however findable it is.
|
|
331
|
+
|
|
332
|
+
**Both shapes are addressed to someone, and that is the constraint.** The offer
|
|
333
|
+
was once a fenced block of engine output: two quoted lines under the test
|
|
334
|
+
counts, addressed to nobody, with the report then closing on *"commit this
|
|
335
|
+
first"* — the last instruction the reader got was to move on, so they did. A row
|
|
336
|
+
in a labelled table is findable; a banner says the work has stopped. What must
|
|
337
|
+
never come back is something unaddressed, unfindable, or fenced.
|
|
338
|
+
|
|
339
|
+
**The row asks nothing, and that is not a weakening of the above.** A row cannot
|
|
340
|
+
be waited on, so a question in one is unanswerable however findable it is —
|
|
341
|
+
which is the failure `spec-reports.md` records under *asking implies waiting*.
|
|
342
|
+
The row names the page; the banner is what asks, because the banner is the shape
|
|
343
|
+
the run is standing behind.
|
|
344
|
+
|
|
345
|
+
**Never bury it and never split it.** The row sits above the last two rows of
|
|
346
|
+
the block, and the page and its counts stay in the same row; the banner
|
|
347
|
+
replaces the row rather than joining it. Two places naming one page make the reader
|
|
348
|
+
resolve a distinction before acting on either — which is the same failure
|
|
349
|
+
whether the two places are adjacent rows or a row and a banner.
|
|
350
|
+
|
|
351
|
+
### Then wait for the verdict, where the harness can
|
|
352
|
+
|
|
353
|
+
The row is findable, but a row cannot make the continuation follow from the
|
|
354
|
+
reading — and that is the gap the whole gate exists to close.
|
|
355
|
+
**Where this harness can watch a file and wake the session on a change, use it.** The pass
|
|
356
|
+
arrives at the engine's holding area, the watch fires, and the verdict the
|
|
357
|
+
reader pressed is what carries the work on.
|
|
358
|
+
|
|
359
|
+
1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
|
|
360
|
+
the whole scope of what you may claim.
|
|
361
|
+
2. **Watch the pending store** for the spec —
|
|
362
|
+
`.spec-env/reviews/<spec>.pending.json` in the primary checkout — and
|
|
363
|
+
**end your turn**. Do not poll, and do not hold the turn open: the point is that
|
|
364
|
+
the reader has the terminal back while they read.
|
|
365
|
+
3. **On waking, let the engine pick**:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
skitterspec spec-env review <spec> --claim-since <the timestamp> --json
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
It claims the one pass that arrived inside the window, and acts on nothing
|
|
372
|
+
at all when none did (the watch can fire on a write that was not a pass) or
|
|
373
|
+
when two did (two sittings, or two people — the operator has the codes).
|
|
374
|
+
4. **Route on the verdict** exactly as `/spec-diff` §2 and §4 describe. Do not
|
|
375
|
+
restate that routing here.
|
|
376
|
+
|
|
377
|
+
**WHY THIS IS SAFE, AND WHAT IT COSTS.** It was once true that a device
|
|
378
|
+
reaching your page could not reach your conversation, and that fact was the
|
|
379
|
+
whole guard: a pass sat in the holding area until a person typed
|
|
380
|
+
`/spec-reviewed`. This replaces that guard rather than weakening it by
|
|
381
|
+
accident, and the replacement is two things together — **the serve token**,
|
|
382
|
+
48 unguessable bits minted per server, which is what decides who can POST at
|
|
383
|
+
all; and **the window**, which is what decides which pass is yours. A pass
|
|
384
|
+
already waiting when the wait began is never claimed by it, which is exactly
|
|
385
|
+
the stranger's pass the old rule was written about. What is genuinely given up
|
|
386
|
+
is that the page can now act, so the token has become a credential rather than
|
|
387
|
+
a convenience — and `--claim-since` refusing to choose between two passes is
|
|
388
|
+
what stops a race becoming a wrong commit.
|
|
389
|
+
|
|
390
|
+
**Where the harness cannot watch a file, you still wait** — the turn ending is
|
|
391
|
+
the wait. Say the page is rendered and that you are holding for the pass, then end
|
|
392
|
+
your turn; the reader's next message is what carries it, and `/spec-diff` picks
|
|
393
|
+
it up from the paste exactly as it always has. The gate holds either way: it is
|
|
394
|
+
the engine's, not the watch's.
|
|
395
|
+
|
|
396
|
+
This once read *"change nothing"* — keep the row, keep the question, do not
|
|
397
|
+
wait — and that exemption is the hatch a whole class of unanswerable questions
|
|
398
|
+
came through. Every harness can end a turn.
|
|
399
|
+
|
|
400
|
+
**It still does not break a chained run.** `/commit && /spec-next` is typed as
|
|
401
|
+
one line; by the time this step is reached the chain has finished, so waiting
|
|
402
|
+
here stops nothing that was still going to happen.
|
|
271
403
|
|
|
272
404
|
Relay the **`open:`** line the engine prints, not the bare path: a path is not
|
|
273
405
|
clickable in any terminal, and a page nobody can open is a page nobody reads.
|
|
@@ -296,7 +428,8 @@ drift.
|
|
|
296
428
|
|
|
297
429
|
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
298
430
|
authorises a local server — one process, ended by one flag, leaving nothing
|
|
299
|
-
behind — and authorises nothing else.
|
|
431
|
+
behind — and authorises nothing else. A reader the server CAN reach is not a
|
|
432
|
+
reason to publish as well: the page they can already open is the page to name. Publishing leaves a page this tooling
|
|
300
433
|
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
301
434
|
serve (a busy port, a machine with no network address) it falls back to the
|
|
302
435
|
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
@@ -321,7 +454,7 @@ the shape; this section carries only what is specific here.
|
|
|
321
454
|
- `❌` — the phase's tests are red, or it stopped part-way. Quote the failure.
|
|
322
455
|
- `⏸` — no spec in flight, or the name given does not match the one that is.
|
|
323
456
|
|
|
324
|
-
**Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `
|
|
457
|
+
**Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `Notes` · `Review` ·
|
|
325
458
|
`Follow-ups` · `Next`
|
|
326
459
|
|
|
327
460
|
## 6a. End in a picker
|
|
@@ -330,6 +463,12 @@ The block says what happened; this is what to do about it. Offer the same four
|
|
|
330
463
|
endings the review page carries, so a review finishes the same way wherever the
|
|
331
464
|
reader is standing — the page, a pasted code, or here.
|
|
332
465
|
|
|
466
|
+
**Not when you are waiting.** Where §5 set a watch and ended the turn, the
|
|
467
|
+
verdict is coming from the page and the picker would be a second way to answer
|
|
468
|
+
a question already asked — so the block ends on `Next`, which names the page.
|
|
469
|
+
The picker is for the run that did not wait: no watch available, or a render
|
|
470
|
+
the reader is expected to come back to in their own time.
|
|
471
|
+
|
|
333
472
|
| Option | Does |
|
|
334
473
|
|--------|------|
|
|
335
474
|
| `Reviewed` | Claims the waiting pass and routes on its verdict |
|
|
@@ -373,8 +512,10 @@ top of an uncommitted one — so a `Next` that names only `/spec-next` sends the
|
|
|
373
512
|
reader straight into that refusal. The two halves are four hundred lines apart,
|
|
374
513
|
which is exactly how they drifted.
|
|
375
514
|
|
|
376
|
-
**Step 5's offer
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
515
|
+
**Step 5's offer lands in one of two shapes, and never both.** Waiting on a
|
|
516
|
+
verdict → the **banner** after the block, and no `Review` row. Not waiting → the
|
|
517
|
+
`Review` row, and no banner. Neither is a paragraph: the ban on prose after the
|
|
518
|
+
block is untouched, and the banner is a control the contract names
|
|
519
|
+
(`.claude/rules/spec-reports.md`). Step 5 renders before the commit and this
|
|
520
|
+
step is where its offer lands, so the two must not disagree about which shape it
|
|
521
|
+
takes.
|
|
@@ -37,16 +37,25 @@ of `/spec-diff` step 0's central rule:
|
|
|
37
37
|
**never claim a pass you were not asked to claim**.
|
|
38
38
|
|
|
39
39
|
That rule exists because a review pass can be POSTed by anything that reaches
|
|
40
|
-
the page
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
the page. So a stranger's pass sits in the holding area — unless an agent goes
|
|
41
|
+
and fetches it, which is exactly what happened once: an agent found a waiting
|
|
42
|
+
approval, read its code off disk, claimed it, and reported the round-trip
|
|
43
|
+
working.
|
|
44
44
|
|
|
45
45
|
Prose alone did not prevent that. The harness does —
|
|
46
|
-
**the model cannot invoke this skill** — so a pass
|
|
47
|
-
because a person typed the command. Typing it *is* the human
|
|
48
|
-
model-invocable does not make it more
|
|
49
|
-
standing between a stray approval and
|
|
46
|
+
**the model cannot invoke this skill** — so a pass picked up *this way* is only
|
|
47
|
+
ever picked up because a person typed the command. Typing it *is* the human
|
|
48
|
+
signal. A later edit that makes this skill model-invocable does not make it more
|
|
49
|
+
convenient — it removes the only thing standing between a stray approval and
|
|
50
|
+
someone's repo.
|
|
51
|
+
|
|
52
|
+
**There is exactly one other way in, and it is bounded rather than trusting.**
|
|
53
|
+
A phase that ends waits on its page, and a pass arriving *during that wait* is
|
|
54
|
+
claimed by the engine (`--claim-since`, `/spec-diff` §4b): scoped to the window,
|
|
55
|
+
acting on nothing when no pass arrived, refusing when two did. A pass already
|
|
56
|
+
sitting there when the wait began is never swept up by it — which is the
|
|
57
|
+
stranger's pass this whole rule was written about. Everything outside that
|
|
58
|
+
window still comes through here.
|
|
50
59
|
|
|
51
60
|
## 1. Resolve the spec
|
|
52
61
|
|
|
@@ -141,10 +150,17 @@ everything a decision needs, so there is nothing to go looking for, and going
|
|
|
141
150
|
looking is the bypass step 0 forbids. The file is right there and readable; that
|
|
142
151
|
is precisely why the rule is written down rather than assumed.
|
|
143
152
|
|
|
144
|
-
**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
**Check the store before concluding nothing is waiting.** Only when this spec
|
|
154
|
+
has a published page (`--json` reports a `url`): a reader off the LAN sent their
|
|
155
|
+
pass to the artifact's own `passes` collection rather than to the engine, so an
|
|
156
|
+
empty holding area is not an empty answer yet. Read it with the Artifact tool's
|
|
157
|
+
`read_db`, merge the `blob` through `--notes`, and **delete the document** —
|
|
158
|
+
`/spec-diff` §6 owns the whole sequence, including that a claim consumes.
|
|
159
|
+
|
|
160
|
+
**Nothing waiting in either is an ordinary answer.** Say so and stop. Do not hunt
|
|
161
|
+
through other specs, and do not treat an empty holding area as a problem — a
|
|
162
|
+
`file://` page copies to the clipboard instead of sending, so mention that the
|
|
163
|
+
pass may be on their clipboard waiting to be pasted, and finish.
|
|
148
164
|
|
|
149
165
|
## 3. One pass waiting? Act on it
|
|
150
166
|
|
|
@@ -155,10 +171,11 @@ manufactured, because the model cannot invoke it.
|
|
|
155
171
|
|
|
156
172
|
**The code was never an authorisation.** A later edit must not restore it as
|
|
157
173
|
one. It earned its keep when the *page* pushed: the agent went looking, found
|
|
158
|
-
a pass, and had to prove which one it had.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
a pass, and had to prove which one it had. What stops a stranger's approval
|
|
175
|
+
reaching your repo is not those six digits — it is that this command cannot be
|
|
176
|
+
typed by anything but a person, and that the one automatic path (`--claim-since`)
|
|
177
|
+
is bounded by a window a stranger's pass falls outside of. The confirmation step
|
|
178
|
+
added nothing to either, and cost a round-trip on every review.
|
|
162
179
|
|
|
163
180
|
So the code has exactly one job left: **telling two passes apart**. That is
|
|
164
181
|
disambiguation, not a gate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "21.0.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",
|