@skitterbyte/skitterspec-linear 15.0.0 → 17.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 +218 -0
- package/README.md +34 -1
- package/assets/claude-md-section.md +29 -18
- package/assets/commands/spec-remote-review.md +22 -0
- package/assets/core/SETUP.md +10 -6
- package/assets/core/env.config.json.example +4 -2
- package/assets/core/env.config.md +100 -23
- package/assets/core/linear.config.json.example +2 -1
- package/assets/core/linear.config.md +49 -22
- package/assets/review/page.html +1044 -101
- package/assets/rules/spec-planning.md +35 -3
- package/assets/rules/spec-reports.md +131 -20
- package/assets/skills/spec/SKILL.md +161 -4
- package/assets/skills/spec-bug/SKILL.md +68 -30
- package/assets/skills/spec-cancel/SKILL.md +2 -2
- package/assets/skills/spec-claim/SKILL.md +12 -4
- package/assets/skills/spec-complete/SKILL.md +2 -2
- package/assets/skills/spec-diff/SKILL.md +131 -36
- package/assets/skills/spec-hotfix/SKILL.md +61 -25
- package/assets/skills/spec-linear-setup/SKILL.md +19 -11
- package/assets/skills/spec-next/SKILL.md +121 -58
- package/assets/skills/spec-push/SKILL.md +45 -0
- package/assets/skills/spec-review/SKILL.md +89 -2
- package/assets/skills/spec-reviewed/SKILL.md +31 -5
- package/assets/skills/spec-start/SKILL.md +26 -3
- package/assets/skills/spec-status/SKILL.md +20 -6
- package/assets/skills/spec-sync/SKILL.md +1 -0
- package/package.json +2 -2
- package/src/cli.js +913 -116
- package/src/env/classify.js +87 -2
- package/src/env/config.js +214 -17
- package/src/env/live.js +94 -0
- package/src/env/resolve.js +36 -2
- package/src/env/review.js +542 -21
- package/src/env/serve.js +298 -19
- package/src/env/supervise.js +8 -1
- package/src/init.js +60 -9
- package/src/vendor/linear/api.js +111 -2
- package/src/vendor/linear/cli-sync.js +661 -11
- package/src/vendor/linear/config.js +41 -13
- package/src/vendor/linear/doctor.js +6 -5
- package/src/vendor/sync-core/index.js +11 -3
- package/src/vendor/sync-core/src/compare.js +65 -0
- package/src/vendor/sync-core/src/normalize.js +26 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Spec Planning
|
|
2
2
|
|
|
3
3
|
Spec-driven development is driven by ten lifecycle skills (plus the
|
|
4
|
-
`/spec-connect` and `/spec-
|
|
4
|
+
`/spec-connect`, `/spec-live` and `/spec-remote-review` **commands** when isolation is on) — use them rather than hand-rolling specs so the structure
|
|
5
5
|
and lifecycle stay consistent. Each sets a status on the spec header
|
|
6
6
|
(`> **Status:** …`):
|
|
7
7
|
|
|
@@ -20,13 +20,24 @@ and lifecycle stay consistent. Each sets a status on the spec header
|
|
|
20
20
|
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
21
21
|
|
|
22
22
|
**Skills vs commands.** The table above lists **skills** — Claude reads them and
|
|
23
|
-
exercises judgment. `/spec-connect` and `/spec-
|
|
23
|
+
exercises judgment. `/spec-connect`, `/spec-live` and `/spec-remote-review` are
|
|
24
|
+
instead **slash commands**
|
|
24
25
|
(`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
|
|
25
26
|
relays its output, so there is no judgment to apply and no model turn spent
|
|
26
27
|
finding one. They are marked `disable-model-invocation`, meaning
|
|
27
28
|
**only you can run them** — a skill that wants one will tell you to type it
|
|
28
29
|
rather than invoking it.
|
|
29
30
|
|
|
31
|
+
**`/spec-remote-review`** is the newest and the smallest: it toggles
|
|
32
|
+
`review.allowRemote`, and takes `on`/`off` when you would rather say which.
|
|
33
|
+
It exists because a render's `remote:` line has to name something a person can
|
|
34
|
+
act on, and `skitterspec spec-env review allow remote` is not what anyone
|
|
35
|
+
reconstructs from a page they are reading on a phone. Making the line
|
|
36
|
+
*clickable* was considered and rejected — the only clickable thing markdown has
|
|
37
|
+
is a URL, and a URL that acts when it is **fetched** is one a link previewer or
|
|
38
|
+
a prefetcher fires with nobody involved. It **permits** publishing; it publishes
|
|
39
|
+
nothing.
|
|
40
|
+
|
|
30
41
|
`/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
|
|
31
42
|
convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
|
|
32
43
|
review pass is never claimed unasked. Because the model cannot invoke this
|
|
@@ -301,7 +312,7 @@ its verdicts and its fields.
|
|
|
301
312
|
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
302
313
|
| **Built** | POST /orders handler, orders schema |
|
|
303
314
|
| **Tests** | 128 passed · npm test |
|
|
304
|
-
| **Review** | 7 files, +212 −18 · [
|
|
315
|
+
| **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 |
|
|
305
316
|
| **Follow-ups** | none |
|
|
306
317
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
307
318
|
|
|
@@ -333,6 +344,27 @@ against a committed last-pushed snapshot and applies it) and `/spec-status`
|
|
|
333
344
|
workflow-state drifted), backed by a `spec-sync` CLI. There is no content pull —
|
|
334
345
|
the tracker is never read back or merged.
|
|
335
346
|
|
|
347
|
+
**One-way means the repo wins, and it now wins *loudly*.** Those are different
|
|
348
|
+
claims and only the first was ever in doubt. A ticket's description is generated
|
|
349
|
+
from the spec, so a person editing it there is writing something the next push
|
|
350
|
+
replaces — which is correct, and which used to happen in silence. The engine now
|
|
351
|
+
compares a hash of what the tracker holds against a hash of what the repo last
|
|
352
|
+
sent, and says *that* it changed and where to read it.
|
|
353
|
+
It **reports and never refuses**: only a person can tell a typo fix from a
|
|
354
|
+
paragraph worth keeping.
|
|
355
|
+
|
|
356
|
+
**This is still not a pull.** Nothing read is merged, written to a spec, or fed
|
|
357
|
+
into the projection, and the text never enters the conversation — the comparison
|
|
358
|
+
is hashes, and the output is one line for a human. It is the same thing
|
|
359
|
+
`compareStored` has always done for a different question.
|
|
360
|
+
|
|
361
|
+
**And adoption no longer costs the reporter their words.** When a provider
|
|
362
|
+
adopts an existing ticket — one a PM or colleague filed — the linking push
|
|
363
|
+
replaces its description with the spec. Before that happens the original is
|
|
364
|
+
posted back onto the ticket as a **comment**: a surface one-way sync never
|
|
365
|
+
touches, so no later push can clobber it. Where a provider supports it, this is
|
|
366
|
+
automatic and has an opt-out. See the provider's own docs.
|
|
367
|
+
|
|
336
368
|
A provider may also ship a **read-only listing** of what the tracker holds. That
|
|
337
369
|
is not a pull: nothing is merged back and no spec file is written. It exists
|
|
338
370
|
because the folder buckets are only the truth *on the branch you are standing
|
|
@@ -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 · [
|
|
39
|
+
| **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 |
|
|
40
40
|
| **Follow-ups** | none |
|
|
41
41
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
42
42
|
|
|
@@ -87,23 +87,95 @@ the options may be offered after the table.
|
|
|
87
87
|
|
|
88
88
|
## ⏸ Review ready — 7 files, +212 −18
|
|
89
89
|
|
|
90
|
-
**
|
|
90
|
+
- **local** — [http://127.0.0.1:7760/7e9e123e7540/feat-orders](http://127.0.0.1:7760/7e9e123e7540/feat-orders)
|
|
91
|
+
- **network** — [http://192.168.0.136:7760/7e9e123e7540/feat-orders](http://192.168.0.136:7760/7e9e123e7540/feat-orders)
|
|
92
|
+
- **remote** — off · `/spec-remote-review` turns it on
|
|
93
|
+
|
|
94
|
+
live: off — /spec-live to put it live
|
|
95
|
+
|
|
96
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
91
97
|
|
|
92
98
|
`/spec-reviewed` picks it up · `spec-env review skip "<reason>"` moves on
|
|
93
99
|
|
|
94
100
|
---
|
|
95
101
|
|
|
96
|
-
A rule, a heading naming the state and the size, the
|
|
97
|
-
saying the run is stopped
|
|
98
|
-
paragraph explaining it.
|
|
102
|
+
A rule, a heading naming the state and the size, **the stack**, the `live:`
|
|
103
|
+
line, one line saying the run is stopped and which tiers it covers, and the two
|
|
104
|
+
exits. Nothing else, and never a paragraph explaining it.
|
|
105
|
+
|
|
106
|
+
**The `live:` line, and why a rigid contract took a second amendment.** Reading
|
|
107
|
+
a diff is not the only way to judge a change — often the question is whether it
|
|
108
|
+
*works* — and the answer to that lives at the running URL. A reader deciding
|
|
109
|
+
whether to open the page at all wants to know whether it is already running
|
|
110
|
+
somewhere, and finding that out any other way is a trip to a terminal in the
|
|
111
|
+
middle of a review.
|
|
112
|
+
|
|
113
|
+
**Each line names a command a person types**, rather than describing a
|
|
114
|
+
capability. `live: off — the page can put it live` told the reader that a page
|
|
115
|
+
somewhere could do it and left them to find the verb — and the verb is what they
|
|
116
|
+
need when they are in a terminal rather than on the page. So the lines say
|
|
117
|
+
`/spec-live`, `/spec-live main`, `/spec-remote-review`. Making them *clickable*
|
|
118
|
+
was considered and rejected: the only clickable thing markdown has is a URL, and
|
|
119
|
+
a URL that acts when it is **fetched** is one a link previewer or a prefetcher
|
|
120
|
+
fires with nobody involved.
|
|
121
|
+
|
|
122
|
+
It is the engine's line, copied like the stack: `spec-env review` prints it and
|
|
123
|
+
`--json` carries the same answer as `live`, from one function, so the report and
|
|
124
|
+
the command cannot disagree. Four states, and only three of them are a line —
|
|
125
|
+
`on` (with the URL when the project configured one), `off`, `held` (naming the
|
|
126
|
+
spec holding the workbench). The fourth, `unavailable`,
|
|
127
|
+
**prints nothing at all**:
|
|
128
|
+
a project with no isolation and a spec with no worktree both land there,
|
|
129
|
+
and a line about a surface that does not exist is an accusation against a
|
|
130
|
+
healthy repo.
|
|
131
|
+
|
|
132
|
+
**This is the second amendment to this section in two specs**, which is worth
|
|
133
|
+
naming rather than letting pass. The contract earns its rigidity from real
|
|
134
|
+
failures, and two amendments in a row is how a rigid contract quietly becomes a
|
|
135
|
+
negotiable one. Both have carried their reason and both replaced a line rather
|
|
136
|
+
than adding a paragraph.
|
|
137
|
+
**A third should have to argue harder than either did.**
|
|
138
|
+
|
|
139
|
+
**The stack is the engine's, copied rather than composed.** `spec-env review`
|
|
140
|
+
prints one line per tier and `--json` carries the same thing as `tiers`, so take
|
|
141
|
+
it from there in the order it comes — and do not decide which tiers are worth
|
|
142
|
+
mentioning. The whole reason the stack exists is that the engine was being asked
|
|
143
|
+
to guess where the reader was sitting, and it guessed wrong three separate ways
|
|
144
|
+
in one day; a skill re-deciding that in prose is the same guess one layer up.
|
|
145
|
+
|
|
146
|
+
**Three tiers, always all three, in this order.**
|
|
147
|
+
|
|
148
|
+
| Tier | Is | Off when |
|
|
149
|
+
|------|----|----------|
|
|
150
|
+
| `local` | the loopback URL — the machine the run is on | never. With nothing served it is the `file://` page, and says it cannot send a verdict |
|
|
151
|
+
| `network` | the LAN URL — a phone on the same wifi | `review.allowNetwork` is false, or this machine has no network address |
|
|
152
|
+
| `remote` | a published page, for a reader off the network | `review.allowRemote` is false, or nothing has been published |
|
|
153
|
+
|
|
154
|
+
A tier that is off **keeps its line** and names the one command that turns it
|
|
155
|
+
on. Dropping the line is what left a reader who had walked out of the house with
|
|
156
|
+
nothing on screen to tell them a remote page was even possible — they could not
|
|
157
|
+
ask for what they could not see.
|
|
99
158
|
|
|
100
159
|
**Only promise a wait the transport can deliver.** The wait itself is real in
|
|
101
160
|
every case; what varies is what carries it back, not whether the run is
|
|
102
161
|
stopped. Three transports, three true sentences:
|
|
103
162
|
|
|
104
|
-
- **A page the engine SERVES** hands its pass to the local store,
|
|
105
|
-
|
|
163
|
+
- **A page the engine SERVES** hands its pass to the local store, and
|
|
164
|
+
`spec-env review wait` returns when it lands. The run is woken by the press:
|
|
106
165
|
*"I'm holding here until you send a verdict."*
|
|
166
|
+
|
|
167
|
+
**That sentence is only true for as long as the wait is actually running**, so
|
|
168
|
+
the wait is the engine's and never one composed for the occasion. A watcher
|
|
169
|
+
written per run is proven by nothing: one was written
|
|
170
|
+
`until [ -f "$P" ] && [ "$x" \> "$y" ]` — valid bash, a syntax error in zsh,
|
|
171
|
+
a predicate that could never be true — and it spun for five minutes while the
|
|
172
|
+
report underneath it said the run was holding.
|
|
173
|
+
**Silence is what made that expensive**:
|
|
174
|
+
a wait that cannot fire and one patiently working look identical,
|
|
175
|
+
so nothing seemed wrong until the reader asked. Give the wait no timeout
|
|
176
|
+
either; it lasts as long as the session, because a reader who walks away from
|
|
177
|
+
a diff is the normal case and a bounded watch once lost a verdict to a lunch
|
|
178
|
+
break.
|
|
107
179
|
- **A `file://` page** has no server to POST to, so the pass is copied and
|
|
108
180
|
pasted — the reader's next message is what carries it. The run is stopped just
|
|
109
181
|
the same, and says so:
|
|
@@ -112,10 +184,16 @@ stopped. Three transports, three true sentences:
|
|
|
112
184
|
not a lesser one, and it is not an excuse to ask without waiting.
|
|
113
185
|
- **A PUBLISHED page** writes to the artifact's own store, and nothing pushes
|
|
114
186
|
from there into the conversation. That is the one case where a verdict is
|
|
115
|
-
genuinely invisible until someone asks for it, so the
|
|
116
|
-
|
|
187
|
+
genuinely invisible until someone asks for it, so its line in the stack says
|
|
188
|
+
that rather than letting the holding line speak for it:
|
|
117
189
|
|
|
118
|
-
**
|
|
190
|
+
- **remote** — [https://…](https://…) · a verdict here needs `/spec-reviewed`
|
|
191
|
+
|
|
192
|
+
Where `remote` is the only tier a reader can reach, the holding line says so
|
|
193
|
+
too: *press a verdict, then type `/spec-reviewed` — I cannot see it until you
|
|
194
|
+
do.* Where a served tier is reachable as well, the holding line keeps naming
|
|
195
|
+
the tiers the wait covers, and `remote`'s own caveat sits on `remote`'s line
|
|
196
|
+
where it belongs.
|
|
119
197
|
|
|
120
198
|
**A harness with no file-watch does not get an exemption.** It once read
|
|
121
199
|
"change nothing" — keep the row, keep the question, do not wait — and that
|
|
@@ -126,12 +204,18 @@ every harness can do.
|
|
|
126
204
|
**The `Continue` ending, and why the banner's exits differ mid-run.** A page
|
|
127
205
|
rendered part-way through a run offers `Continue` — *I have read it, carry on* —
|
|
128
206
|
in place of the committing verdicts, because "commit" is the wrong verb for
|
|
129
|
-
unfinished work. Its banner names the same state and
|
|
207
|
+
unfinished work. Its banner names the same state and the same stack, and
|
|
208
|
+
different exits:
|
|
209
|
+
|
|
210
|
+
…the stack, unchanged…
|
|
130
211
|
|
|
131
|
-
|
|
212
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
132
213
|
|
|
133
214
|
`Continue` carries on · `Request changes` works them now
|
|
134
215
|
|
|
216
|
+
The stack is written out once, above, and referred to everywhere else. Two
|
|
217
|
+
copies of it is how the two come to disagree about which tiers exist.
|
|
218
|
+
|
|
135
219
|
**It is not the `none` verdict that was removed.** `none` recorded itself and
|
|
136
220
|
did nothing, which is the record-and-do-nothing ending this contract exists
|
|
137
221
|
against. `Continue` **resumes the run** — it names an action, which is the bar
|
|
@@ -145,13 +229,40 @@ block. *Waiting* is what any offer does, and it is what this section governs.
|
|
|
145
229
|
that is finished. A mid-run render waits without arming — walking away from it
|
|
146
230
|
costs nothing, which is exactly why `Continue` is safe to offer there.
|
|
147
231
|
|
|
148
|
-
**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
232
|
+
**One link per reachable store, each labelled.** This amends
|
|
233
|
+
*"exactly one link, never two"*, and the evidence that rule was written on is
|
|
234
|
+
the reason it still reads the way it does. Two links were handed over, the wait
|
|
235
|
+
stood behind only one of them, and a verdict was pressed on the published page
|
|
236
|
+
three times while each one sat unread under a line claiming the run was waiting.
|
|
237
|
+
Two links was not a convenience with a caveat; the caveat existing was the bug.
|
|
238
|
+
|
|
239
|
+
**What that evidence is about is the store, not the count.** A served page hands
|
|
240
|
+
its pass to the local pending store, which `spec-env review wait` watches; a
|
|
241
|
+
published page writes to the artifact's own store, which nothing here can see.
|
|
242
|
+
Two links into two stores asks the reader to know which door the run is standing
|
|
243
|
+
behind, and they cannot.
|
|
244
|
+
|
|
245
|
+
**Local and network are two doors into one room.** The page POSTs with
|
|
246
|
+
`fetch(location.pathname, …)`, so a page opened at `127.0.0.1:7760/…` and one
|
|
247
|
+
opened at `192.168.0.136:7760/…` reach the same server process and the same
|
|
248
|
+
pending store. One wait covers both, and a verdict pressed on either wakes the
|
|
249
|
+
same run. Between those two the objection does not apply — which is why both are
|
|
250
|
+
offered, and why offering them gives nothing up.
|
|
251
|
+
|
|
252
|
+
**So two things are required, and the second is what the old rule lacked.**
|
|
253
|
+
Every link is **labelled** with its tier, and one line says
|
|
254
|
+
**which tiers the wait covers**.
|
|
255
|
+
Two *unlabelled* links stay forbidden exactly as before, because
|
|
256
|
+
the reader still cannot tell them apart.
|
|
257
|
+
|
|
258
|
+
**`remote` keeps the caveat it always had.** It is listed so a reader off the
|
|
259
|
+
network knows the surface exists and what turns it on — never as a door the wait
|
|
260
|
+
is claimed to cover. Where it is on and published, its line says what is true of
|
|
261
|
+
it: *a verdict here needs `/spec-reviewed`*.
|
|
262
|
+
|
|
263
|
+
**Still forbidden**, in the words of the rule this amends: offering two links
|
|
264
|
+
into **different** stores without saying which one the wait is watching. That is
|
|
265
|
+
the failure that cost three unread verdicts, and nothing here touches it.
|
|
155
266
|
|
|
156
267
|
**This is not the old failure returning**, and the difference is the whole
|
|
157
268
|
justification. The offer used to be two quoted lines in the tail of a long
|
|
@@ -247,7 +358,7 @@ behind it — say nothing rather than reporting that there was nothing to report
|
|
|
247
358
|
| `Landed` | A fast-forward, a tag, a cherry-pick. |
|
|
248
359
|
| `Worktree` | A worktree provisioned, entered, or torn down. |
|
|
249
360
|
| `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
|
|
250
|
-
| `Review` | The rendered diff page: files, `+`/`−`, the
|
|
361
|
+
| `Review` | The rendered diff page: files, `+`/`−`, and the stack — the same tiers the banner lists, labelled, run together with `·` because a row is one cell, plus `live` where the engine reported one. **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. |
|
|
251
362
|
| `Follow-ups` | **Always.** `none`, or one line each. |
|
|
252
363
|
| `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
|
|
253
364
|
|
|
@@ -105,11 +105,40 @@ it happens differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step
|
|
|
105
105
|
description (an **update** to the existing issue, plus a sub-issue per phase).
|
|
106
106
|
Recording a snapshot here would declare the mirror already in sync and strand
|
|
107
107
|
the issue showing the raw report forever.
|
|
108
|
+
- **Preserve the original, before anything replaces it.** Run this the moment
|
|
109
|
+
the spec file exists and **before the linking push**:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
skitterspec spec-sync preserve <spec>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
It posts the issue's current description onto the issue as a comment — the one
|
|
116
|
+
surface one-way sync never touches, so no push can clobber it. On the API path
|
|
117
|
+
it reads the description itself; without a key it prints the body and the
|
|
118
|
+
marker, and you post it with the Linear comment tool after checking the
|
|
119
|
+
comments for that marker.
|
|
120
|
+
|
|
121
|
+
**The ordering is the whole correctness condition.** Run before the push, this
|
|
122
|
+
keeps what the reporter filed. Run after it, it would keep the generated spec
|
|
123
|
+
and report success — which is why it is a step of its own here rather than a
|
|
124
|
+
line in the push sequence. The engine warns when it can tell it is late, but it
|
|
125
|
+
cannot always tell.
|
|
126
|
+
|
|
127
|
+
**It never fails the adoption.** A project that set
|
|
128
|
+
`intake.preserveOriginal: false`, an unreachable Linear, an issue with no
|
|
129
|
+
description, a comment Linear refused — every one of them exits 0 and says so.
|
|
130
|
+
Relay what it printed and carry on.
|
|
108
131
|
- **Say what will happen** in the finish-up message. The linking step runs right
|
|
109
132
|
after the spec is written, so the issue's description is replaced by the spec
|
|
110
|
-
**then** — not on some later manual push.
|
|
111
|
-
|
|
112
|
-
|
|
133
|
+
**then** — not on some later manual push. Say both halves: the description
|
|
134
|
+
becomes the spec, **and** the original is preserved as a comment on the same
|
|
135
|
+
issue, so the reporter can still read what they wrote.
|
|
136
|
+
|
|
137
|
+
**Do not send anyone to the issue's history for it.** This used to say Linear
|
|
138
|
+
keeps the original there, and that is true and useless: history is a diff
|
|
139
|
+
viewer nobody opens, it is not quotable, and it degrades to "it is in there
|
|
140
|
+
somewhere". The comment is the answer, and the other half is the spec's own
|
|
141
|
+
**Problem** (or **Symptom**) section quoting the reporter.
|
|
113
142
|
|
|
114
143
|
## Phase A — reach a clear shared understanding (grill first)
|
|
115
144
|
|
|
@@ -339,6 +368,104 @@ open questions, `Draft` when you deliberately left some unresolved. Either way
|
|
|
339
368
|
the next step is `/spec-start`. The Report section below is where all of that
|
|
340
369
|
reaches the user — do not narrate it here as well.
|
|
341
370
|
|
|
371
|
+
## Phase C2 — render the spec, then wait for the verdict
|
|
372
|
+
|
|
373
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
374
|
+
present). Without it there is no page and this phase does not exist — skip it in
|
|
375
|
+
silence rather than explaining an absence.
|
|
376
|
+
|
|
377
|
+
A spec is the one artefact whose whole purpose is to be
|
|
378
|
+
**read before work begins**, and until now it was the only one with no reading
|
|
379
|
+
surface: `/spec`
|
|
380
|
+
finished, the spec sat uncommitted, and `/spec-start` had to point that out and
|
|
381
|
+
commit it as a side effect of provisioning. So this phase ends where every other
|
|
382
|
+
phase of the lifecycle ends — on a page, in a verdict.
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
skitterspec spec-env review <spec> --docs --buttons authoring
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`--docs` reads the spec's own documents from the tree you are standing in, so it
|
|
389
|
+
never wants a worktree — a backlog spec has none. It renders
|
|
390
|
+
**this spec's documents and nobody else's**: several specs are routinely
|
|
391
|
+
authored in one
|
|
392
|
+
checkout, and the file set comes from `spec-env stage`'s `owned` half precisely
|
|
393
|
+
so a colleague's spec cannot land on your page and then be committed under your
|
|
394
|
+
verdict.
|
|
395
|
+
|
|
396
|
+
**This is free.** The engine reads git and splices text into a template; the
|
|
397
|
+
diff never passes through you.
|
|
398
|
+
|
|
399
|
+
**Render nothing when nothing was written.** A `⏸` run — grilling that never
|
|
400
|
+
reached a shared understanding — has no spec to show, and must not ask for a
|
|
401
|
+
verdict on one.
|
|
402
|
+
|
|
403
|
+
**Arm nothing.** There is no `spec-env review arm` in this path, and that is a
|
|
404
|
+
decision rather than an omission: the gate asserts that *a phase which ended*
|
|
405
|
+
owes an answer, and a backlog spec owes no phase. Walking away from this page
|
|
406
|
+
costs nothing — the spec is simply still uncommitted, which `/spec-start` already
|
|
407
|
+
handles. A `/spec` run must leave `spec-env review gate --check` exiting 0.
|
|
408
|
+
|
|
409
|
+
### The two endings this page offers
|
|
410
|
+
|
|
411
|
+
`--buttons authoring` offers `Commit & Start` and `Commit` in place of the
|
|
412
|
+
committing pair, because "commit and build the next phase" is meaningless for a
|
|
413
|
+
spec with no phase in flight.
|
|
414
|
+
|
|
415
|
+
- **`commit-start`** — hand off to `review.commitWith` (`/commit` by default),
|
|
416
|
+
passing the pathspec the render reported on `docs.paths`, then run
|
|
417
|
+
**`/spec-start <name>`** and **stop there**. It never completes, lands or tears
|
|
418
|
+
anything down, exactly as `commit-continue` stops after `/spec-next`. This is
|
|
419
|
+
the `commit && /spec-start` that was typed by hand.
|
|
420
|
+
- **`commit`** — the same commit, then finish. The spec stays `Ready` in
|
|
421
|
+
`backlog`, ready for a `/spec-start` whenever it is wanted.
|
|
422
|
+
- **`changes`** — work the notes into the spec, record a resolution for each one
|
|
423
|
+
so the next render shows it struck through with what changed, then
|
|
424
|
+
**re-render, and wait again**. The reader is still holding a decision, so
|
|
425
|
+
ending the turn and
|
|
426
|
+
making them type `/spec-diff` would reopen the loop this exists to close.
|
|
427
|
+
- **`discuss`** — report and talk. Claim nothing, change nothing.
|
|
428
|
+
|
|
429
|
+
**Take the pathspec from the render, not from the tree.** A checkout is shared,
|
|
430
|
+
so the set of this spec's uncommitted documents can differ between the render and
|
|
431
|
+
the verdict — and the reader's conclusion is about what the page showed them.
|
|
432
|
+
|
|
433
|
+
### Then wait for it
|
|
434
|
+
|
|
435
|
+
**The wait is a command. Do not write one.**
|
|
436
|
+
|
|
437
|
+
1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
|
|
438
|
+
the whole scope of what you may claim.
|
|
439
|
+
2. **Run the engine's wait in the background, and end your turn:**
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
skitterspec spec-env review wait <spec> --since <the timestamp>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
It takes no timeout unless you pass one, and you must not pass one: a reader
|
|
446
|
+
who walks away from a spec is the normal case, and a bounded watch once lost a
|
|
447
|
+
verdict to a lunch break.
|
|
448
|
+
3. **On waking, let the engine pick:**
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
It claims the one pass that arrived inside the window, acts on nothing when
|
|
455
|
+
none did, and refuses to choose when two did.
|
|
456
|
+
4. **Route on the verdict** as above.
|
|
457
|
+
|
|
458
|
+
**Where the page is `file://`** — the engine could not serve — there is no server
|
|
459
|
+
to POST to, so the pass is copied and pasted and the reader's next message is
|
|
460
|
+
what carries it. **Do not start a watch that cannot fire**: a wait on the pending
|
|
461
|
+
store would spin forever while the report underneath it claimed to be holding,
|
|
462
|
+
which is the exact failure `.claude/rules/spec-reports.md` records. The wait is
|
|
463
|
+
the turn ending, and the banner says so.
|
|
464
|
+
|
|
465
|
+
Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
|
|
466
|
+
three, in that order — never the bare `page:` path. `.claude/rules/spec-reports.md`
|
|
467
|
+
carries the shape and why every tier is named rather than one being chosen.
|
|
468
|
+
|
|
342
469
|
## Phase D — record the isolation stack (only if configured)
|
|
343
470
|
|
|
344
471
|
**Only when `specs/.core/env.config.json` exists** (per-spec isolation is
|
|
@@ -468,9 +595,39 @@ the shape; this section carries only what is specific here.
|
|
|
468
595
|
**Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
|
|
469
596
|
|
|
470
597
|
`Built` is the spec's path and phase count; `Spec` is its status and bucket;
|
|
471
|
-
`Next` is `/spec-start <name>`, with the name spelled the way it must be typed.
|
|
472
598
|
`Tracker` appears only when a provider is installed and linked it.
|
|
473
599
|
|
|
600
|
+
**`Next` depends on whether Phase C2 rendered a page.** Where it did, the run is
|
|
601
|
+
waiting on a verdict, so the block **omits the `Review` row** and ends on the
|
|
602
|
+
banner defined in `.claude/rules/spec-reports.md` — counts, the tier stack, and
|
|
603
|
+
this page's two exits:
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## ⏸ Review ready — <N> files, +<a> −<d>
|
|
608
|
+
|
|
609
|
+
- **local** — <the `local:` URL>
|
|
610
|
+
- **network** — <the `network:` URL, or off with the command that turns it on>
|
|
611
|
+
- **remote** — <the `remote:` URL, or off with the command that turns it on>
|
|
612
|
+
|
|
613
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
614
|
+
|
|
615
|
+
`Commit & Start` puts it in flight · `Commit` keeps it for later
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
There `Next` names the page rather than a command, because the button is what
|
|
620
|
+
carries the work on.
|
|
621
|
+
|
|
622
|
+
Where no page was rendered — isolation is not configured, or nothing was
|
|
623
|
+
written — `Next` is `/spec-start <name>`, with the name spelled the way it must
|
|
624
|
+
be typed.
|
|
625
|
+
|
|
626
|
+
**A spec written without a page is still uncommitted**, so that `Next` relies on
|
|
627
|
+
`/spec-start` committing the spec when it is all that is uncommitted. Say
|
|
628
|
+
`/commit, then /spec-start <name>` when anything else is uncommitted too, or the
|
|
629
|
+
row sends the reader to a refusal.
|
|
630
|
+
|
|
474
631
|
**`Follow-ups` is almost always `none` here.** This skill's whole job is to
|
|
475
632
|
capture work, so anything it surfaced belongs in the spec it just wrote rather
|
|
476
633
|
than in a follow-up line beneath it.
|
|
@@ -104,11 +104,40 @@ it happens differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step
|
|
|
104
104
|
description (an **update** to the existing issue, plus a sub-issue per phase).
|
|
105
105
|
Recording a snapshot here would declare the mirror already in sync and strand
|
|
106
106
|
the issue showing the raw report forever.
|
|
107
|
+
- **Preserve the original, before anything replaces it.** Run this the moment
|
|
108
|
+
the spec file exists and **before the linking push**:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
skitterspec spec-sync preserve <spec>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
It posts the issue's current description onto the issue as a comment — the one
|
|
115
|
+
surface one-way sync never touches, so no push can clobber it. On the API path
|
|
116
|
+
it reads the description itself; without a key it prints the body and the
|
|
117
|
+
marker, and you post it with the Linear comment tool after checking the
|
|
118
|
+
comments for that marker.
|
|
119
|
+
|
|
120
|
+
**The ordering is the whole correctness condition.** Run before the push, this
|
|
121
|
+
keeps what the reporter filed. Run after it, it would keep the generated spec
|
|
122
|
+
and report success — which is why it is a step of its own here rather than a
|
|
123
|
+
line in the push sequence. The engine warns when it can tell it is late, but it
|
|
124
|
+
cannot always tell.
|
|
125
|
+
|
|
126
|
+
**It never fails the adoption.** A project that set
|
|
127
|
+
`intake.preserveOriginal: false`, an unreachable Linear, an issue with no
|
|
128
|
+
description, a comment Linear refused — every one of them exits 0 and says so.
|
|
129
|
+
Relay what it printed and carry on.
|
|
107
130
|
- **Say what will happen** in the finish-up message. The linking step runs right
|
|
108
131
|
after the spec is written, so the issue's description is replaced by the spec
|
|
109
|
-
**then** — not on some later manual push.
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
**then** — not on some later manual push. Say both halves: the description
|
|
133
|
+
becomes the spec, **and** the original is preserved as a comment on the same
|
|
134
|
+
issue, so the reporter can still read what they wrote.
|
|
135
|
+
|
|
136
|
+
**Do not send anyone to the issue's history for it.** This used to say Linear
|
|
137
|
+
keeps the original there, and that is true and useless: history is a diff
|
|
138
|
+
viewer nobody opens, it is not quotable, and it degrades to "it is in there
|
|
139
|
+
somewhere". The comment is the answer, and the other half is the spec's own
|
|
140
|
+
**Problem** (or **Symptom**) section quoting the reporter.
|
|
112
141
|
|
|
113
142
|
## 1. Reproduce & isolate (light investigation)
|
|
114
143
|
|
|
@@ -309,10 +338,14 @@ and **never auto-push git** — Linear's own automation reacts to real branch/PR
|
|
|
309
338
|
events later. Report the Linear issue URL as part of the skill's finish-up
|
|
310
339
|
message.
|
|
311
340
|
|
|
312
|
-
**Only when all three hold**: `specs/.core/linear.config.json` exists,
|
|
313
|
-
`
|
|
341
|
+
**Only when all three hold**: `specs/.core/linear.config.json` exists, the repo
|
|
342
|
+
**owns** `assignee` in its `sync.fieldOwnership`, and the spec carries a
|
|
314
343
|
`linear_identifier`. Any one missing → skip this step silently and carry on; a
|
|
315
|
-
project that
|
|
344
|
+
project that declined must see no trace of assignment.
|
|
345
|
+
|
|
346
|
+
Ownership is the **value**, not the key: `assignee` is owned by default, and
|
|
347
|
+
`"none"` is how a project declines it. Testing whether the key is *present* would
|
|
348
|
+
read as opted-in everywhere, since the default puts it in every config.
|
|
316
349
|
|
|
317
350
|
**Never blocks, never fails the skill.** Everything below is best-effort: the
|
|
318
351
|
branch is provisioned and the spec is moving either way, and an unassigned issue
|
|
@@ -474,11 +507,13 @@ skitterspec spec-env review arm <spec>
|
|
|
474
507
|
```
|
|
475
508
|
|
|
476
509
|
**Then wait**, and `/spec-next` §5 owns the sequence — follow it there rather
|
|
477
|
-
than reading a second copy here: note the moment
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
510
|
+
than reading a second copy here: note the moment, run
|
|
511
|
+
`skitterspec spec-env review wait <spec> --since <that moment>` in the
|
|
512
|
+
background, end your turn, and let `--claim-since` pick the one pass that
|
|
513
|
+
arrived inside the window. **Do not compose a watcher**, and do not give the
|
|
514
|
+
wait a timeout — the reasoning is in `/spec-next` §5 and is not repeated here.
|
|
515
|
+
The banner it describes is what this skill emits in place of a `Review` row,
|
|
516
|
+
and the routing on the verdict is `/spec-diff` §2 and §4, as it is everywhere.
|
|
482
517
|
|
|
483
518
|
**Why this skill arms as well as waits.** Waiting is what any offer does;
|
|
484
519
|
**arming** asserts an obligation that outlives the turn, and belongs only to
|
|
@@ -499,30 +534,33 @@ this worktree is refused until a verdict is sent or
|
|
|
499
534
|
`skitterspec spec-env review skip "<reason>"` records the decision to move on.
|
|
500
535
|
The exit is always one command, and one of them is *"I am moving on"*.
|
|
501
536
|
|
|
502
|
-
Relay the
|
|
503
|
-
|
|
537
|
+
Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
|
|
538
|
+
three — rather than the bare `page:` path: a path is not clickable in any
|
|
539
|
+
terminal, and a page nobody can open is a page nobody reads. Relay the `live:`
|
|
540
|
+
line with it where the engine printed one, and nothing where it did not.
|
|
541
|
+
`.claude/rules/spec-reports.md` carries the shape, including why a rigid
|
|
542
|
+
contract took that line.
|
|
504
543
|
|
|
505
544
|
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
506
545
|
something behind that this tooling cannot remove, so it is always an ask. A
|
|
507
546
|
`file://` link is no use on a phone, and saying so **is** the ask —
|
|
508
547
|
`/spec-diff` §6 owns how.
|
|
509
|
-
**
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
drift.
|
|
548
|
+
**The `reader:` line no longer decides anything here, and that is the point.**
|
|
549
|
+
It is still printed, and it is still the only place that question is answered —
|
|
550
|
+
but the offer does not change with it, because the stack lists every tier
|
|
551
|
+
whatever it says. Three reader states used to mean three different offers, and
|
|
552
|
+
that branching is exactly what produced a `file://` page on a session detected
|
|
553
|
+
`unknown`, a LAN URL for a phone off the network, and an address that changed
|
|
554
|
+
underneath a reader when detection flipped mid-session.
|
|
555
|
+
|
|
556
|
+
So: **relay all three tier lines, every time.** Any `also:` lines sit under
|
|
557
|
+
`network` — the other addresses this machine has, offered because the
|
|
558
|
+
best-guess one can be wrong — so pass them on rather than editing them out.
|
|
559
|
+
|
|
560
|
+
**Never read an environment variable to decide anything about the offer** — not
|
|
561
|
+
`SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
|
|
562
|
+
here for a detection to decide, and a second implementation of one could not be
|
|
563
|
+
tested and would drift.
|
|
526
564
|
|
|
527
565
|
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
528
566
|
authorises a local server — one process, ended by one flag, leaving nothing
|
|
@@ -76,8 +76,8 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
|
|
|
76
76
|
Do not roll anything back, and do not stop to ask.
|
|
77
77
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
78
78
|
unlinked, or failed with the reason.
|
|
79
|
-
- **There is no unassign step, and that is not an omission.**
|
|
80
|
-
`
|
|
79
|
+
- **There is no unassign step, and that is not an omission.** Where the repo owns
|
|
80
|
+
`assignee`, the projection derives it from the spec's lifecycle
|
|
81
81
|
bucket — so moving the folder to `complete`/`cancelled` is *itself* what
|
|
82
82
|
releases the issue, and the push above carries it like any other field. The
|
|
83
83
|
`linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
|