@skitterbyte/skitterspec 20.0.0 → 22.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 +304 -4
- package/README.md +113 -6
- package/assets/claude-md-section.md +29 -18
- package/assets/commands/spec-remote-review.md +22 -0
- package/assets/core/env.config.json.example +4 -2
- package/assets/core/env.config.md +103 -25
- package/assets/review/page.html +1101 -108
- package/assets/rules/spec-planning.md +39 -7
- package/assets/rules/spec-reports.md +210 -31
- package/assets/skills/spec/SKILL.md +129 -1
- package/assets/skills/spec-bug/SKILL.md +63 -43
- package/assets/skills/spec-diff/SKILL.md +183 -39
- package/assets/skills/spec-hotfix/SKILL.md +57 -43
- package/assets/skills/spec-init/SKILL.md +18 -6
- package/assets/skills/spec-next/SKILL.md +145 -60
- package/assets/skills/spec-review/SKILL.md +87 -0
- package/assets/skills/spec-reviewed/SKILL.md +31 -5
- package/assets/skills/spec-start/SKILL.md +19 -0
- package/package.json +1 -1
- package/src/cli.js +940 -116
- package/src/env/classify.js +87 -2
- package/src/env/config.js +214 -17
- package/src/env/hooks.js +49 -9
- package/src/env/live.js +94 -0
- package/src/env/resolve.js +36 -2
- package/src/env/review.js +581 -21
- package/src/env/serve.js +298 -19
- package/src/env/supervise.js +8 -1
- package/src/init.js +88 -13
- /package/assets/hooks/{review-gate.js → review-gate.cjs} +0 -0
package/MIGRATION.md
CHANGED
|
@@ -1,5 +1,303 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## `@skitterbyte/skitterspec` v21 → v22 (a review port per repo, and every render serves)
|
|
4
|
+
|
|
5
|
+
### Breaking change 1 — `review.serveOnRemote` is now `review.serve`
|
|
6
|
+
|
|
7
|
+
**Every render now stands the review server up**, and hands back an `http://`
|
|
8
|
+
URL instead of a `file://` one. It used to serve only when it detected that you
|
|
9
|
+
were reading from somewhere else — and that detection reads three environment
|
|
10
|
+
variables, so a plain local terminal got a `file://` page. A `file://` page has
|
|
11
|
+
**no server to POST to**, so the verdict buttons on it had nowhere to go: the
|
|
12
|
+
press-a-button-and-the-work-continues loop was missing from exactly the sessions
|
|
13
|
+
that are easiest to use.
|
|
14
|
+
|
|
15
|
+
**Which interfaces it listens on is `review.allowNetwork`** — see breaking
|
|
16
|
+
change 3, below. The change here is narrower than it looks: `file://` →
|
|
17
|
+
`http://127.0.0.1`, a page that opens on the machine holding it and, unlike
|
|
18
|
+
`file://`, can answer. Serving more never means listening wider; that is one
|
|
19
|
+
setting, and it is not this one.
|
|
20
|
+
|
|
21
|
+
**What to change.** `review.serveOnRemote: false` is still read as the new
|
|
22
|
+
`review.serve: "never"`, so a config that turned serving off keeps working. The
|
|
23
|
+
key is renamed because the old name would now claim to govern remote renders
|
|
24
|
+
while governing every one of them:
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
// specs/.core/env.config.json — before
|
|
28
|
+
"review": { "serveOnRemote": false }
|
|
29
|
+
// after
|
|
30
|
+
"review": { "serve": "never" }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`serveOnRemote: true` needs no translation — `"always"` is the default and does
|
|
34
|
+
what it did. Note what `"never"` costs you: the page falls back to `file://`,
|
|
35
|
+
and its buttons copy a command for you to paste rather than sending anything.
|
|
36
|
+
|
|
37
|
+
**A render that could not serve now says why** — `review.serve is "never"`, or
|
|
38
|
+
the failure naming the port to free — on the `open:` line and in `--json` as
|
|
39
|
+
`notServed`. A `file://` link with nothing said about it used to be the ordinary
|
|
40
|
+
outcome; now it can only mean the ask did not land.
|
|
41
|
+
|
|
42
|
+
**A spec with no worktree is served too.** A spec you have only just written has
|
|
43
|
+
no branch of its own, and the served route used to refuse it — so its page
|
|
44
|
+
existed and 404'd. It is now served as its own documents, listed in the index,
|
|
45
|
+
and accepts a verdict.
|
|
46
|
+
|
|
47
|
+
### Breaking change 2 — a review port per repo
|
|
48
|
+
|
|
49
|
+
**`review.servePort` now defaults to `"auto"`, not `7777`.** A repo that does
|
|
50
|
+
not pin the key moves to a port derived from its own path —
|
|
51
|
+
`7700 + hash(realpath(repoRoot)) % 100` — the first time `spec-env review serve`
|
|
52
|
+
starts after the upgrade.
|
|
53
|
+
|
|
54
|
+
**The one-time break is a link, and only a link.** A review page open on a
|
|
55
|
+
phone at that moment points at `7777`, and `7777` is no longer this repo's
|
|
56
|
+
server. The verdict you press there goes nowhere. **Re-render the page** —
|
|
57
|
+
`/spec-diff`, or `/spec-next`'s own render — and the new link works. Nothing on
|
|
58
|
+
disk is lost, no pass is dropped, and this happens once.
|
|
59
|
+
|
|
60
|
+
**Why not simply keep 7777.** It was shared by every repo on the machine, so the
|
|
61
|
+
second repo to start was refused, whoever hit that passed `--port`, and the
|
|
62
|
+
links they had already handed out pointed at another repo's daemon — a `404`,
|
|
63
|
+
reported in a grey line in the page footer. A derived port is stable for a given
|
|
64
|
+
tree across a restart, a reboot and a `--stop`, which is what a link handed out
|
|
65
|
+
yesterday needs.
|
|
66
|
+
|
|
67
|
+
### To keep a fixed port
|
|
68
|
+
|
|
69
|
+
Pin it, and nothing derives:
|
|
70
|
+
|
|
71
|
+
```jsonc
|
|
72
|
+
// specs/.core/env.config.json
|
|
73
|
+
"review": { "servePort": 7777 }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
An explicit number always wins, exactly as it did before. Pin one when you want
|
|
77
|
+
a port you can memorise, or when two repos happen to derive the same one — a
|
|
78
|
+
hundred slots is a small chance of that, not no chance, and the server still
|
|
79
|
+
refuses a busy port rather than moving itself aside.
|
|
80
|
+
|
|
81
|
+
`spec-env review serve --status` now prints the port **and which of the three chose it** —
|
|
82
|
+
`--port`, `review.servePort`, or the derivation.
|
|
83
|
+
|
|
84
|
+
### Breaking change 3 — the reader no longer decides the bind: `review.allowNetwork` does
|
|
85
|
+
|
|
86
|
+
**Two new keys, and one of them changes a default.** The engine used to guess
|
|
87
|
+
where you were reading and pick a surface for you. It guessed wrong three
|
|
88
|
+
separate ways in one day — a `file://` page on a session detected `unknown`, a
|
|
89
|
+
LAN URL for a phone that had left the network, and an address that changed
|
|
90
|
+
underneath a reader when detection flipped mid-session. So it stopped guessing:
|
|
91
|
+
every render now lists every tier, labelled, in a fixed order.
|
|
92
|
+
|
|
93
|
+
```jsonc
|
|
94
|
+
// specs/.core/env.config.json — the new keys, with their defaults
|
|
95
|
+
"review": {
|
|
96
|
+
"allowNetwork": true, // the server binds every interface, so the page opens on your phone
|
|
97
|
+
"allowRemote": false // publishing is permitted at all — it permits, it never publishes
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**`allowNetwork` defaults to `true`, and that is the behaviour change to read before upgrading.**
|
|
102
|
+
Before, a local or unknown reader got a loopback bind and a
|
|
103
|
+
remote one got every interface; now the setting decides, and its default is the
|
|
104
|
+
wide bind — which is what a remote reader already got, applied to every render.
|
|
105
|
+
The URL still carries the **serve token**, 48 random bits minted per server,
|
|
106
|
+
which remains the only thing deciding who can POST a verdict. A machine you
|
|
107
|
+
would rather not expose sets `"allowNetwork": false`, and `network:` then reads
|
|
108
|
+
`off` with the command that turns it back on.
|
|
109
|
+
|
|
110
|
+
**`allowRemote` defaults to `false`** and permits publishing without performing
|
|
111
|
+
it — a published page is one skitterspec cannot delete, so it stays an explicit
|
|
112
|
+
ask either way.
|
|
113
|
+
|
|
114
|
+
**`review.reader` still exists** and now decides only how a page's location is
|
|
115
|
+
*worded*. It no longer chooses the bind and no longer chooses which tiers appear.
|
|
116
|
+
|
|
117
|
+
### `/spec-remote-review` — a new slash command
|
|
118
|
+
|
|
119
|
+
Toggles `review.allowRemote`, and takes `on`/`off` when you would rather say
|
|
120
|
+
which. It exists because a render's `remote:` line has to name something you can
|
|
121
|
+
act on, and `skitterspec spec-env review allow remote` is not what anyone
|
|
122
|
+
reconstructs from a page they are reading on a phone. `init` and `update`
|
|
123
|
+
install it with the other commands; like them it is user-only, so nothing but you
|
|
124
|
+
can run it.
|
|
125
|
+
|
|
126
|
+
**It writes `specs/.core/env.config.json` in the primary checkout** — a
|
|
127
|
+
committed file, so it changes for everyone who pulls and leaves that tree dirty.
|
|
128
|
+
The engine says so when it does.
|
|
129
|
+
|
|
130
|
+
## `@skitterbyte/skitterspec-linear` v16 → v17 (a review port per repo, and every render serves)
|
|
131
|
+
|
|
132
|
+
The same two changes as `@skitterbyte/skitterspec` v21 → v22 above — this
|
|
133
|
+
distribution composes the same engine. Read that entry; nothing here is
|
|
134
|
+
Linear-specific.
|
|
135
|
+
|
|
136
|
+
**If you are coming from v15, read the v15 → v16 entry below as well.** 16.0.0
|
|
137
|
+
was bumped in the repo and never published, so npm went 15 → 17 and both
|
|
138
|
+
entries apply to you.
|
|
139
|
+
|
|
140
|
+
## `@skitterbyte/skitterspec-linear` v15 → v16 (assignment is on by default)
|
|
141
|
+
|
|
142
|
+
### Breaking change
|
|
143
|
+
|
|
144
|
+
**A spec's Linear issue is now assigned to whoever is building it.**
|
|
145
|
+
`sync.fieldOwnership.assignee` defaults to `"push"`; it was absent
|
|
146
|
+
before, and absent meant inert. From this version `/spec-start` stamps the
|
|
147
|
+
developer on the spec and the push assigns the issue, and `/spec-complete`
|
|
148
|
+
releases it — on every repo that has not said otherwise.
|
|
149
|
+
|
|
150
|
+
**To decline it**, one line in `specs/.core/linear.config.json`:
|
|
151
|
+
|
|
152
|
+
```jsonc
|
|
153
|
+
"sync": {
|
|
154
|
+
"fieldOwnership": {
|
|
155
|
+
"assignee": "none"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`none` is a new value in the ownership enum (`both|pull|push|none`) and means
|
|
161
|
+
*this repo does not own the field*: nothing is written, no hash is recorded in a
|
|
162
|
+
snapshot, and `spec-sync status` prints no assignee line — identical in every
|
|
163
|
+
respect to the old behaviour of never listing it. It has to be a value rather
|
|
164
|
+
than an omitted key, because the map merges **per key** onto the defaults: once
|
|
165
|
+
a field is owned by default, there is nothing an absent key can subtract.
|
|
166
|
+
|
|
167
|
+
**An explicit `"assignee": "push"` you already had is now redundant and harmless.**
|
|
168
|
+
Nothing rewrites it and nothing needs to.
|
|
169
|
+
|
|
170
|
+
### It cannot overwrite a PM's triage, and that is by construction
|
|
171
|
+
|
|
172
|
+
The concern this default has to answer is whether upgrading re-assigns or
|
|
173
|
+
un-assigns issues someone else owns. It cannot, for two reasons that predate it:
|
|
174
|
+
|
|
175
|
+
- **A spec that records no assignee sends none.** Only a spec the repo itself
|
|
176
|
+
stamped is ever pushed, so an issue a PM assigned in Linear is left alone.
|
|
177
|
+
- **A snapshot with no assignee key means "never pushed", not "was null".** Every
|
|
178
|
+
spec linked before this existed has exactly such a snapshot, and the diff emits
|
|
179
|
+
a *clear* only against an assignee it has a recorded hash for.
|
|
180
|
+
|
|
181
|
+
So the flip widens who gets **assigned** — specs you start from now on — and
|
|
182
|
+
cannot widen who gets **unassigned**. There is a regression test pinning this
|
|
183
|
+
(`sync-assignee-projection.test.js`, "THE UPGRADE CASE").
|
|
184
|
+
|
|
185
|
+
### `spec-sync init-config --assign` is gone; `--no-assign` replaces it
|
|
186
|
+
|
|
187
|
+
The flag's meaning inverted with the default, so it is removed rather than kept
|
|
188
|
+
as a silent no-op — a flag that still parses and does nothing would read as
|
|
189
|
+
"assignment configured" to anyone who wrote it before v16. `--assign` now fails
|
|
190
|
+
as an unknown flag, which is the louder and more useful answer. `--no-assign`
|
|
191
|
+
writes the `"none"` opt-out above.
|
|
192
|
+
|
|
193
|
+
### `spec-sync assign` now refuses a field the repo does not own
|
|
194
|
+
|
|
195
|
+
Previously it stamped the frontmatter in any repo and printed
|
|
196
|
+
`next: push it, so Linear agrees` — while `toFieldSet` dropped the field, so the
|
|
197
|
+
push never carried it. It now refuses, naming `sync.fieldOwnership.assignee` and
|
|
198
|
+
the value that enables it. Only `/spec-claim` enforced this before, so a script
|
|
199
|
+
or a direct CLI call got the untrue sentence.
|
|
200
|
+
|
|
201
|
+
## `@skitterbyte/skitterspec` v20 → v21 (the gate installs, and asking implies waiting)
|
|
202
|
+
|
|
203
|
+
If you upgraded to v20 and the commit gate never once fired, this is why. Two
|
|
204
|
+
independent bugs, either of which alone made it a no-op.
|
|
205
|
+
|
|
206
|
+
### Breaking change
|
|
207
|
+
|
|
208
|
+
**The hook ships as `.claude/hooks/review-gate.cjs`**, renamed from
|
|
209
|
+
`review-gate.js`. The script is CommonJS and it is copied *into your* project,
|
|
210
|
+
where your `package.json` decides how node parses a `.js` — so in any
|
|
211
|
+
`"type": "module"` project it died on its own first `require`, printing a stack
|
|
212
|
+
trace on **every Bash tool call**. `.cjs` settles the parse mode at the file,
|
|
213
|
+
which is the only place independent of the one file skitterspec does not
|
|
214
|
+
control. An ESM rewrite would have inverted the same problem onto CommonJS
|
|
215
|
+
projects, which are still the default for anything with no `"type"` set.
|
|
216
|
+
|
|
217
|
+
The upgrade migrates you: your existing `PreToolUse` entry has its path
|
|
218
|
+
**rewritten in place** — so a wrapper, a flag or a different interpreter you
|
|
219
|
+
added all survive — and the retired `review-gate.js` is deleted. If you edited
|
|
220
|
+
that file yourself it is **kept**, with a warning, and left for you to remove.
|
|
221
|
+
|
|
222
|
+
**Check whether you already had a `review-gate.cjs` of your own.** `.cjs` is the
|
|
223
|
+
obvious way to work around the v20 crash, so anyone who fixed it by hand most
|
|
224
|
+
likely picked this exact filename — and this upgrade is the moment skitterspec
|
|
225
|
+
starts managing that path. A file already sitting there is **kept**, the real
|
|
226
|
+
hook is therefore never installed, and `review-gate.js` is pruned out from under
|
|
227
|
+
it. A hand-written shim that only `require`d the old script then fails open, so
|
|
228
|
+
the gate is silently absent under an update that reported success.
|
|
229
|
+
|
|
230
|
+
Two lines settle it:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
head -3 .claude/hooks/review-gate.cjs # ours opens with a 'use strict' + a doc comment
|
|
234
|
+
npx @skitterbyte/skitterspec update --check # a line naming this path means yours was kept
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If it is yours, take ours — `npx @skitterbyte/skitterspec update --force`, or
|
|
238
|
+
delete the file and re-run `update`. Later versions report this case in its own
|
|
239
|
+
words rather than as `your edit — kept`, but the manifest records the path after
|
|
240
|
+
the first upgrade, so that wording cannot reach anyone who has already run one.
|
|
241
|
+
|
|
242
|
+
### Bug fix
|
|
243
|
+
|
|
244
|
+
**`skitterspec update` now registers the hook.** v20's notes said `init` and
|
|
245
|
+
`update` both did; only `init` did. `update` copied the script, reported
|
|
246
|
+
`created: .claude/hooks/review-gate.js`, and wired nothing — so every project
|
|
247
|
+
that upgraded into v20 got a hook file and no hook, with nothing saying so.
|
|
248
|
+
|
|
249
|
+
### What to do
|
|
250
|
+
|
|
251
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
252
|
+
2. **Commit `.claude/settings.json` and `.claude/hooks/review-gate.cjs`**, and
|
|
253
|
+
the deletion of `.claude/hooks/review-gate.js`. A hook only a fraction of the
|
|
254
|
+
team has is a gate that holds for a fraction of the team.
|
|
255
|
+
3. **Check it is actually on** — the update reports
|
|
256
|
+
`updated: .claude/settings.json (review-gate hook)` the first time, and
|
|
257
|
+
`unchanged` afterwards. If it says neither, your settings file could not be
|
|
258
|
+
parsed; it was left untouched and the hook is not registered.
|
|
259
|
+
|
|
260
|
+
### Behaviour change — a bug fix and a hotfix now owe a verdict
|
|
261
|
+
|
|
262
|
+
**`/spec-bug` and `/spec-hotfix` now arm the review gate**, as `/spec-next`
|
|
263
|
+
already did. Both render the page at the end of red→green work, and both now
|
|
264
|
+
wait for your verdict instead of finishing — so in that spec's worktree a
|
|
265
|
+
`git commit` is **refused** until one of two things happens:
|
|
266
|
+
|
|
267
|
+
- you send a verdict from the page (or `/spec-reviewed` picks up one already
|
|
268
|
+
waiting), or
|
|
269
|
+
- you run `skitterspec spec-env review skip "<reason>"`, which records the
|
|
270
|
+
decision to move on.
|
|
271
|
+
|
|
272
|
+
The gate is still only armed by work that **finished**, it still fails open on
|
|
273
|
+
every cannot-tell, and `review.required: false` in `specs/.core/env.config.json`
|
|
274
|
+
still turns it off for a project.
|
|
275
|
+
|
|
276
|
+
**Why.** Those two skills used to render the page, ask *"want a written review
|
|
277
|
+
before you commit?"* and finish with nothing watching. A verdict pressed on that
|
|
278
|
+
page sat in the holding area until someone typed `/spec-reviewed` — which they
|
|
279
|
+
had no reason to do, because the run had said the page was *ready* rather than
|
|
280
|
+
that it was *waiting*. Two verdicts were stranded that way on one spec, and the
|
|
281
|
+
second existed only because the first appeared to do nothing.
|
|
282
|
+
|
|
283
|
+
### New, and not breaking — the `Continue` verdict
|
|
284
|
+
|
|
285
|
+
**A page rendered part-way through a run offers `Continue`** — *I have read it,
|
|
286
|
+
carry on* — instead of the committing buttons, via
|
|
287
|
+
`spec-env review <spec> --buttons midrun`. The default is unchanged, so a caller
|
|
288
|
+
that passes nothing renders exactly the page it rendered before. `Continue` can
|
|
289
|
+
never clear an armed gate: that still takes a committing verdict or a recorded
|
|
290
|
+
skip.
|
|
291
|
+
|
|
292
|
+
Nothing else changes: `review.required` still defaults to `true`, and the engine
|
|
293
|
+
and `/spec-next` held the gate throughout regardless of the hook.
|
|
294
|
+
|
|
295
|
+
## `@skitterbyte/skitterspec-linear` v14 → v15 (the gate installs, and asking implies waiting)
|
|
296
|
+
|
|
297
|
+
The same change as `@skitterbyte/skitterspec` v20 → v21 above — this
|
|
298
|
+
distribution composes the same lifecycle skills. Read that entry; nothing here
|
|
299
|
+
is Linear-specific.
|
|
300
|
+
|
|
3
301
|
## `@skitterbyte/skitterspec` v19 → v20 (a phase owes a verdict)
|
|
4
302
|
|
|
5
303
|
### Breaking change
|
|
@@ -41,8 +339,10 @@ cannot-tell lets the commit through.
|
|
|
41
339
|
### Breaking change
|
|
42
340
|
|
|
43
341
|
**`.claude/settings.json` is now written by the installer.** `skitterspec init`
|
|
44
|
-
and `skitterspec update` copy
|
|
45
|
-
|
|
342
|
+
and `skitterspec update` copy the hook script and register it as a `PreToolUse`
|
|
343
|
+
hook in your project's **committed** settings file. (In v20 `update` copied
|
|
344
|
+
without registering, and the script was named `review-gate.js` — both fixed in
|
|
345
|
+
v21; read that entry above if you are landing on the current release.) That is a
|
|
46
346
|
tracked file in most repos, so expect it in `git status` after upgrading — and
|
|
47
347
|
commit it, because a hook only a fraction of the team has is a gate that holds
|
|
48
348
|
for a fraction of the team.
|
|
@@ -76,8 +376,8 @@ as before.
|
|
|
76
376
|
### What to do
|
|
77
377
|
|
|
78
378
|
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
79
|
-
2. **Commit `.claude/settings.json` and
|
|
80
|
-
|
|
379
|
+
2. **Commit `.claude/settings.json` and the hook script.** Both are new in your
|
|
380
|
+
working tree after the update.
|
|
81
381
|
3. **Nothing else to configure.** `review.required` defaults to `true` and
|
|
82
382
|
`review.commitWith` defaults to `/commit`; neither needs adding unless you
|
|
83
383
|
are changing it.
|
package/README.md
CHANGED
|
@@ -9,11 +9,31 @@ Spec-driven development for [Claude Code](https://claude.com/claude-code) — a
|
|
|
9
9
|
↳ /spec-diff — read what the phase changed
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Ships
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
Ships per-spec **isolation** — a git worktree per in-progress spec, Docker on
|
|
13
|
+
demand, host dev servers on reserved ports — and everything below.
|
|
14
|
+
|
|
15
|
+
<!-- commands:start -->
|
|
16
|
+
| | |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `/spec` | Grill to a shared understanding, then write a groomed spec |
|
|
19
|
+
| `/spec-bug` | Reproduce with a failing test, then drive it red→green |
|
|
20
|
+
| `/spec-hotfix` | Fix the released version: fork from a tag, land by tag + cherry-pick |
|
|
21
|
+
| `/spec-review` | Re-validate a spec against the code; refresh what drifted |
|
|
22
|
+
| `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 |
|
|
23
|
+
| `/spec-next` | Build the next phase of the spec in flight |
|
|
24
|
+
| `/spec-diff` | Read what a phase changed, on a page you can mark up |
|
|
25
|
+
| `/spec-reviewed` | Pick up a verdict pressed when nothing was waiting for it |
|
|
26
|
+
| `/spec-to-main` | Land the branch mid-spec, without finishing it |
|
|
27
|
+
| `/spec-complete` | Verify, land, tear down |
|
|
28
|
+
| `/spec-cancel` | Record why, stamp the header, tear down |
|
|
29
|
+
| `/spec-init` | Bootstrap or repair the workflow in a project |
|
|
30
|
+
| `/spec-connect` | Point your canonical `localhost` ports at one spec's stack |
|
|
31
|
+
| `/spec-live` | Check one spec out in the primary checkout, so your running server reloads it |
|
|
32
|
+
| `/spec-remote-review` | Permit (or forbid) reviewing from off your network |
|
|
33
|
+
<!-- commands:end -->
|
|
34
|
+
|
|
35
|
+
The last three are **slash commands** rather than skills: each pre-executes one
|
|
36
|
+
`skitterspec spec-env` verb and relays it, so only you can run them.
|
|
17
37
|
|
|
18
38
|
```sh
|
|
19
39
|
npx @skitterbyte/skitterspec init
|
|
@@ -78,6 +98,93 @@ is; the optional *written* review (a short read plus `flag`/`confirm`/`good`
|
|
|
78
98
|
notes) is the part that costs, and it is offered rather than assumed. Publishing
|
|
79
99
|
is always opt-in, and one page per spec — later phases update the same link.
|
|
80
100
|
|
|
101
|
+
### The page takes a verdict back, and the phase waits for it
|
|
102
|
+
|
|
103
|
+
Reading is half of it. The page also takes marks — tick `✓ accept` per file as
|
|
104
|
+
you read, write a note against a line or a whole file, answer the questions a
|
|
105
|
+
written review asked — and then **ends in a decision**:
|
|
106
|
+
|
|
107
|
+
| Button | Does |
|
|
108
|
+
|--------|------|
|
|
109
|
+
| `✓ Commit` | commits the phase, and stops |
|
|
110
|
+
| `✓ Commit & Continue` | commits, then builds the next phase — and stops there |
|
|
111
|
+
| `✓ Commit & Start` | on a freshly written spec: commits it and puts it in flight. The `commit && /spec-start` you would otherwise type |
|
|
112
|
+
| `↺ Request changes` | sends your notes straight back to be worked |
|
|
113
|
+
| `… Discuss first` | reports what you wrote and asks what's up |
|
|
114
|
+
|
|
115
|
+
The last one appears only on a spec's own page — a spec with no phase in flight
|
|
116
|
+
has nothing to continue — and `▶ Put it live` sits above them all, which is not
|
|
117
|
+
a verdict: it commits the phase, checks the branch out where your dev server can
|
|
118
|
+
see it, and hands you back the same page. It clears no gate.
|
|
119
|
+
|
|
120
|
+
**`/spec-next` ends a phase by rendering the page and then watching for that press**,
|
|
121
|
+
so the button is what carries the work on — there is no command to
|
|
122
|
+
remember. A press on a page the engine served reaches it directly; a `file://`
|
|
123
|
+
page has no server to talk to, so it copies the pass and you paste it.
|
|
124
|
+
|
|
125
|
+
**A verdict sent when nothing was watching is not lost.** Type
|
|
126
|
+
**`/spec-reviewed`** to pick it up — bare when one is waiting, or
|
|
127
|
+
`/spec-reviewed 324199` to name one exactly, which matters only when two are.
|
|
128
|
+
It is the only path the harness itself enforces: the model cannot run it, so a
|
|
129
|
+
pass picked up that way is only ever picked up because a person asked.
|
|
130
|
+
|
|
131
|
+
Fixes come back as **resolutions**, so the next render shows each note struck
|
|
132
|
+
through with a one-line account of what changed — you verify the fix rather than
|
|
133
|
+
trusting it. An accept remembers the file's content, so it lapses by itself when
|
|
134
|
+
that file changes again, announced rather than silent.
|
|
135
|
+
**The marks are information, never a gate**:
|
|
136
|
+
nothing counts them, and the verdict is the one thing you choose, once.
|
|
137
|
+
|
|
138
|
+
### A phase that ended owes an answer
|
|
139
|
+
|
|
140
|
+
The **gate** is armed when a phase ends and its page is rendered, and exactly two
|
|
141
|
+
things clear it: a committing verdict, or
|
|
142
|
+
`skitterspec spec-env review skip "<reason>"`. While it is armed `/spec-next`
|
|
143
|
+
refuses to build the next phase, and — where the hook is installed —
|
|
144
|
+
`git commit` refuses in that worktree. Mid-phase renders arm nothing, every
|
|
145
|
+
cannot-tell exits 0 and says nothing, and `review.required: false` turns it off
|
|
146
|
+
for a project. The exit is always one command, and one of them is *"I am moving
|
|
147
|
+
on"* with the reason on the record.
|
|
148
|
+
|
|
149
|
+
### Where the page can be read
|
|
150
|
+
|
|
151
|
+
Every render prints one line per surface, labelled, so you pick the one that
|
|
152
|
+
reaches you rather than the engine guessing:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
local: http://127.0.0.1:7760/7e9e123e7540/feat-orders
|
|
156
|
+
network: http://192.168.0.136:7760/7e9e123e7540/feat-orders
|
|
157
|
+
remote: off — turn on with: /spec-remote-review
|
|
158
|
+
live: off — /spec-live to put it live
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`local` and `network` are two doors into one room — the same server, the same
|
|
162
|
+
waiting verdict — so the wait covers both. `remote` is a published page, which
|
|
163
|
+
is a separate store: a verdict there needs `/spec-reviewed`, and it is off until
|
|
164
|
+
you type `/spec-remote-review`. The `live:` line is the other way to judge a
|
|
165
|
+
change: whether it is also **running**, and the page has a button to put it
|
|
166
|
+
there.
|
|
167
|
+
|
|
168
|
+
The random-looking segment in the URL is a **serve token** — 48 bits minted per
|
|
169
|
+
server, and the only thing deciding who can POST a verdict at all. That, and why
|
|
170
|
+
`/spec-reviewed` is user-only, are written up in
|
|
171
|
+
`.claude/rules/spec-reports.md` and `.claude/rules/spec-planning.md`, which
|
|
172
|
+
`init` installs into your project.
|
|
173
|
+
|
|
174
|
+
### The `review.*` config keys
|
|
175
|
+
|
|
176
|
+
All optional, in `specs/.core/env.config.json`:
|
|
177
|
+
|
|
178
|
+
| Key | Default | Does |
|
|
179
|
+
|-----|---------|------|
|
|
180
|
+
| `review.serve` | `"always"` | whether a render stands the local server up. `"never"` gives you a `file://` page, whose buttons copy a command instead of sending |
|
|
181
|
+
| `review.allowNetwork` | `true` | whether that server binds every interface, so the page opens on your phone |
|
|
182
|
+
| `review.allowRemote` | `false` | whether publishing is permitted at all. `/spec-remote-review` toggles it; it permits publishing, it does not publish |
|
|
183
|
+
| `review.required` | `true` | whether a finished phase owes a verdict |
|
|
184
|
+
| `review.commitWith` | `"/commit"` | the skill a committing verdict hands off to |
|
|
185
|
+
| `review.reader` | `"detect"` | where you are reading — `local`, `remote`, or detect it |
|
|
186
|
+
| `review.servePort` | derived | the port, normally derived from the repo's path |
|
|
187
|
+
|
|
81
188
|
`/spec-next` writes the page at the end of every phase. Nothing about it depends
|
|
82
189
|
on where your shell is.
|
|
83
190
|
|
|
@@ -93,7 +200,7 @@ a question it cannot answer itself or a failure at the moment it happens:
|
|
|
93
200
|
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
94
201
|
| **Built** | POST /orders handler, orders schema |
|
|
95
202
|
| **Tests** | 128 passed · npm test |
|
|
96
|
-
| **Review** | 7 files, +212 −18 · [
|
|
203
|
+
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) · **remote** off |
|
|
97
204
|
| **Follow-ups** | none |
|
|
98
205
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
99
206
|
|
|
@@ -20,24 +20,35 @@ every phase.
|
|
|
20
20
|
|
|
21
21
|
**Handing the review back.** The page takes marks: tick `✓ accept` per file as
|
|
22
22
|
you read, note anything against a line or a whole file, answer the questions a
|
|
23
|
-
written review asked — then **end it in a decision**.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
you
|
|
28
|
-
the engine, which **holds** it and shows a six-digit code.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
written review asked — then **end it in a decision**. At the end of a phase the
|
|
24
|
+
buttons are `✓ Commit`, `✓ Commit & Continue` (commit, then build the next
|
|
25
|
+
phase), `↺ Request changes` and `… Discuss first`. A spec that has just been
|
|
26
|
+
written gets `✓ Commit & Start` instead — the `commit-start` verdict, which is
|
|
27
|
+
the `commit && /spec-start` you would otherwise type. A **served** page hands
|
|
28
|
+
the pass to the engine, which **holds** it and shows a six-digit code. Committing
|
|
29
|
+
is unavailable while a note is open — you asked for something, so it cannot also
|
|
30
|
+
be fine — and it hands off to your own commit skill (`review.commitWith`,
|
|
31
|
+
`/commit` by default) rather than a copy living here. Fixes come back as
|
|
32
|
+
resolutions, so the next render shows each note struck through with what changed.
|
|
33
|
+
An accept remembers the file's content, so it lapses by itself when that file
|
|
34
|
+
changes again. The marks are information — nothing gates on them; the verdict is
|
|
35
|
+
the one thing you choose, once.
|
|
36
|
+
|
|
37
|
+
**Above the verdicts sits one line that is not a verdict.** `▶ Put it live`
|
|
38
|
+
commits the phase, checks the branch out where your dev server can see it, and
|
|
39
|
+
hands you back the same page with the same options — so you can judge the change
|
|
40
|
+
by *using* it, not only by reading it. It clears no gate: you have looked at it
|
|
41
|
+
running and concluded nothing, so the phase still owes an answer.
|
|
42
|
+
|
|
43
|
+
**Every render lists where the page can be read**, labelled, in a fixed order —
|
|
44
|
+
`local`, `network`, `remote` — each either a URL or the one command that turns it
|
|
45
|
+
on, plus a `live:` line saying whether the change is also running. `local` and
|
|
46
|
+
`network` are two doors into one room: the same server and the same waiting
|
|
47
|
+
verdict, so one wait covers both. `remote` is a published page, a second store —
|
|
48
|
+
a verdict there needs `/spec-reviewed`, and it stays off until someone types
|
|
49
|
+
**`/spec-remote-review`**, which toggles it. Publishing is permitted by that
|
|
50
|
+
command, never performed by it: a published page is one this tooling cannot
|
|
51
|
+
remove.
|
|
41
52
|
|
|
42
53
|
**One ending.** Every spec skill finishes with the same block — a verdict
|
|
43
54
|
(`✅` · `⚠️` · `❌` · `⏸`), then a table of the fields that skill declares,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Permit or forbid publishing a review for a reader off your network — bare toggles it, `on`/`off` set it explicitly
|
|
3
|
+
argument-hint: "[on | off]"
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env review allow remote:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env review allow remote --set "$ARGUMENTS"`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing and run nothing else.
|
|
10
|
+
|
|
11
|
+
**Bare toggles.** That is the common case: you are looking at a render whose
|
|
12
|
+
`remote:` line says which way it currently is, and what you want is the other
|
|
13
|
+
one. `on` and `off` are there for when you are not looking at it.
|
|
14
|
+
|
|
15
|
+
**It permits publishing; it publishes nothing.** A published page is one
|
|
16
|
+
skitterspec cannot delete, so publishing stays an explicit ask
|
|
17
|
+
(`/spec-diff` §6) — and a verdict pressed on one needs `/spec-reviewed`, because
|
|
18
|
+
nothing pushes from an artifact's store into a conversation.
|
|
19
|
+
|
|
20
|
+
**It writes a committed file.** `specs/.core/env.config.json` in the primary
|
|
21
|
+
checkout, whichever tree you ran this from — so it changes for everyone who
|
|
22
|
+
pulls and leaves that tree dirty. The engine says so; do not soften it.
|