@skitterbyte/skitterspec-linear 12.0.0 → 13.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.
Files changed (55) hide show
  1. package/MIGRATION.md +208 -10
  2. package/README.md +32 -2
  3. package/assets/claude-md-section.md +38 -2
  4. package/assets/commands/spec-connect.md +2 -2
  5. package/assets/commands/spec-live.md +2 -2
  6. package/assets/core/SETUP.md +21 -3
  7. package/assets/core/env.config.json.example +6 -3
  8. package/assets/core/env.config.md +77 -30
  9. package/assets/core/linear.config.md +58 -0
  10. package/assets/review/page.html +1501 -0
  11. package/assets/rules/spec-planning.md +198 -10
  12. package/assets/rules/spec-reports.md +269 -0
  13. package/assets/skills/spec/SKILL.md +33 -5
  14. package/assets/skills/spec-bug/SKILL.md +172 -9
  15. package/assets/skills/spec-cancel/SKILL.md +98 -21
  16. package/assets/skills/spec-claim/SKILL.md +114 -0
  17. package/assets/skills/spec-complete/SKILL.md +94 -25
  18. package/assets/skills/spec-diff/SKILL.md +564 -0
  19. package/assets/skills/spec-hotfix/SKILL.md +172 -11
  20. package/assets/skills/spec-init/SKILL.md +34 -7
  21. package/assets/skills/spec-linear-setup/SKILL.md +55 -1
  22. package/assets/skills/spec-list/SKILL.md +218 -0
  23. package/assets/skills/spec-next/SKILL.md +299 -6
  24. package/assets/skills/spec-push/SKILL.md +32 -8
  25. package/assets/skills/spec-review/SKILL.md +40 -5
  26. package/assets/skills/spec-reviewed/SKILL.md +241 -0
  27. package/assets/skills/spec-start/SKILL.md +386 -106
  28. package/assets/skills/spec-status/SKILL.md +24 -2
  29. package/assets/skills/spec-sync/SKILL.md +40 -4
  30. package/assets/skills/spec-to-main/SKILL.md +28 -6
  31. package/package.json +11 -7
  32. package/src/cli.js +1513 -89
  33. package/src/env/building.js +143 -0
  34. package/src/env/config.js +42 -9
  35. package/src/env/provision.js +54 -15
  36. package/src/env/proxy.js +34 -1
  37. package/src/env/render.js +3 -12
  38. package/src/env/resolve.js +295 -9
  39. package/src/env/review.js +1329 -0
  40. package/src/env/serve.js +549 -0
  41. package/src/env/teardown.js +13 -6
  42. package/src/init.js +96 -1
  43. package/src/vendor/linear/api.js +104 -1
  44. package/src/vendor/linear/cli-sync.js +854 -17
  45. package/src/vendor/linear/config.js +8 -0
  46. package/src/vendor/linear/credentials.js +94 -0
  47. package/src/vendor/linear/doctor.js +35 -0
  48. package/src/vendor/linear/identity.js +105 -0
  49. package/src/vendor/linear/mcp.js +26 -0
  50. package/src/vendor/sync-core/index.js +6 -2
  51. package/src/vendor/sync-core/src/compare.js +49 -3
  52. package/src/vendor/sync-core/src/normalize.js +30 -0
  53. package/src/vendor/sync-core/src/push.js +11 -1
  54. package/src/vendor/sync-core/src/write.js +38 -0
  55. package/LICENSE +0 -21
@@ -1,6 +1,6 @@
1
1
  # Spec Planning
2
2
 
3
- Spec-driven development is driven by nine lifecycle skills (plus the
3
+ Spec-driven development is driven by ten lifecycle skills (plus the
4
4
  `/spec-connect` and `/spec-live` **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:** …`):
@@ -13,6 +13,7 @@ and lifecycle stay consistent. Each sets a status on the spec header
13
13
  | `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
14
14
  | `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
15
15
  | `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
16
+ | `/spec-reviewed` | Pick up a review approved on the page — offered by its code, claimed on your word | (unchanged) | (unchanged) |
16
17
  | `/spec-to-main` | Land the branch on the base (rebase + ff) **without** finishing — for running the work in CI / a shared env mid-spec; repeatable | `In Progress` (unchanged) | (unchanged) |
17
18
  | `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
18
19
  | `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
@@ -26,6 +27,14 @@ finding one. They are marked `disable-model-invocation`, meaning
26
27
  **only you can run them** — a skill that wants one will tell you to type it
27
28
  rather than invoking it.
28
29
 
30
+ `/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
31
+ convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
32
+ review pass is never claimed unasked. A pass can be POSTed by anything that
33
+ reaches the page; what it cannot reach is the conversation. Because the model
34
+ cannot invoke this skill, a pass is only ever picked up because a person typed
35
+ the command, and typing it **is** the human signal. Prose alone did not hold
36
+ that line once already.
37
+
29
38
  `/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
30
39
  real judgment (green tests before a land; an MCP fetch and a team-key check; ten
31
40
  subcommands) — but they are marked user-only too, since nobody reaches them
@@ -44,11 +53,15 @@ mid-spec (so the work can run in CI / a shared test env) while the spec stays
44
53
 
45
54
  **Two workspace modes.** `specs/.core/env.config.json` → `mode` decides where a
46
55
  spec's branch is built. **`worktree`** (the default) gives each spec its own
47
- checkout — several specs at once and `main` left free — and `/spec-start` moves
48
- the session you typed into *into* that checkout, so the work carries on in the
49
- same terminal. **`checkout`** builds the branch in the primary checkout instead:
50
- one spec at a time, and nothing to move into. Neither mode costs you a second
51
- terminal; the difference is how many specs can be in flight at once.
56
+ checkout — several specs at once and `main` left free. `/spec-start` builds the
57
+ branch there, **moves your session into it** with a plain `cd`, and then asks:
58
+ build phase 1 now? Say yes and it carries on into a bare `/spec-next`; say no and
59
+ you are already standing in the worktree, so `/spec-next` typed later does the
60
+ same thing. The primary checkout stays on the base branch throughout the
61
+ session followed the spec, nothing was checked out anywhere.
62
+ **`checkout`** builds the branch in the primary checkout instead: one spec at a
63
+ time, nowhere else to stand, and the build always carries straight on.
64
+ The difference is how many specs can be in flight at once.
52
65
  Pick it for how you work rather than for what the project contains — a repo with
53
66
  no dev servers may still want several specs in flight. In `checkout` mode
54
67
  `/spec-connect` and `/spec-live` do not apply and say so: both exist to reach
@@ -68,8 +81,11 @@ your canonical `localhost` ports so you can test it at the normal URL
68
81
  move, header edits, the code) happens on the spec's branch in the worktree; `main`
69
82
  changes only when it merges. Teardown is folded into `/spec-complete` ·
70
83
  `/spec-cancel`. Beneath the skills, `skitterspec spec-env
71
- <up|down|prune|dev|connect|integrate|hotfix|live|status|resolve>` is the CLI
72
- engine (omit the spec name and it uses the worktree you are standing in). Teardown drops
84
+ <up|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
85
+ engine. **Omit the spec name anywhere and it uses the worktree you are standing in**,
86
+ else the sole provisioned spec — with no exceptions left: a bare
87
+ `/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
88
+ `main`. Teardown drops
73
89
  the finished spec's own test-DB volume; `spec-env prune` additionally reaps
74
90
  **orphaned** volumes left by declined/aborted teardowns, so `/spec-complete` and
75
91
  `/spec-cancel` also sweep orphans (confirm-first). A **hotfix** is the one
@@ -81,9 +97,12 @@ when `env.config.json` is absent — every skill then behaves as it does today.
81
97
  **Live overlay (`/spec-live`, a command) — the light way to test a spec.** `/spec-connect`
82
98
  runs a spec's *own* dev stack and proxies the canonical ports to it (one stack per
83
99
  spec). **Live overlay** instead reuses the one dev server you already have running:
84
- `/spec-live <spec>` rebases the branch onto base, frees it from its worktree, and
100
+ `/spec-live <spec>` or a bare `/spec-live`, which takes the spec you are
101
+ standing on — rebases the branch onto base, frees it from its worktree, and
85
102
  checks it out **in the primary checkout**, so your running server hot-reloads the
86
- feature at the normal URL — no second stack, no proxy. The branch checked out in
103
+ feature at the normal URL — no second stack, no proxy. Bare only acts when there
104
+ is exactly one answer *and* the workbench is free; anything else prints the
105
+ status report rather than guessing. The branch checked out in
87
106
  the primary checkout **is** the lock: exactly one spec is live at a time, and
88
107
  `/spec-live main` hands the instance back (fixes you make while live commit
89
108
  straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
@@ -93,6 +112,157 @@ stateful ones (`Stack: worktree + docker`, or a branch touching migrations) —
93
112
  `/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
94
113
  Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
95
114
 
115
+ **Staging one spec and not another (`spec-env stage`).** Several specs are
116
+ authored at once — that is what the worktree mode is for — and they all land in
117
+ the same `specs/` folder until they are committed. A `git add specs/` there
118
+ stages a *directory*, sweeping whatever another session happens to have written
119
+ into this spec's commit, under this spec's ticket trailer.
120
+ `skitterspec spec-env stage [<spec>] [--json]` is how a skill asks instead of
121
+ guessing: it splits the uncommitted tree into the paths that are this spec's —
122
+ its folder in **any** bucket, so a tree mid-`git mv` is handled, plus each
123
+ `spec.companionPaths` entry the project declares — and the paths that are not.
124
+ It reads **the tree you are standing in**, which is the worktree inside one and
125
+ the primary checkout outside, and prints which. The `owned` half is the spec's
126
+ **documents**, never its code: a phase's own implementation is `foreign`, because
127
+ the commits this bounds are the lifecycle ones. Nothing here refuses, writes, or
128
+ exits non-zero — `foreign` is a list of paths to leave alone, not an accusation.
129
+
130
+ **Naming the paths is necessary and not sufficient**, which is why every spec
131
+ commit is also pathspec-limited: `git add -- <paths>` **and**
132
+ `git commit -m "…" -- <paths>`. A checkout has one `.git/index` and every session
133
+ standing in it shares that index, so a bare `git commit` takes whatever another
134
+ session has already staged no matter how carefully this one staged its own. The
135
+ `add` is still needed — `git commit` cannot take a path git has never seen, which
136
+ is every brand-new spec folder — but it is the `--` on the **commit** that bounds
137
+ what lands. The two failures are different and the fix needs both halves.
138
+
139
+ **Reading a spec's diff (`/spec-diff`) — seeing the work, not running it.**
140
+ `/spec-connect` and `/spec-live` both exist to reach a *running app*.
141
+ **`/spec-diff`** answers the other question: what did this actually change? A
142
+ phase is built in its own worktree, so `git diff` in your terminal answers about
143
+ the base branch — and a 350-line diff read as terminal text is scrolling, not
144
+ review. The engine collects the worktree's changes with `git -C` and writes a
145
+ self-contained HTML page (`.spec-env/reviews/<spec>.html`, gitignored) with
146
+ whole-file context that folds away, a file tree, and new files included. You open
147
+ it locally, or publish it and read it on a phone.
148
+ **The diff never passes through the model**, so the page is free to produce
149
+ however large it is; the *written*
150
+ review — a short read plus `flag`/`confirm`/`good` notes — is the part that costs
151
+ tokens, and it is offered rather than assumed. It is a **skill**, model-invocable,
152
+ and it is gated on nothing: half a phase, a hand edit and a colleague's branch are
153
+ all ordinary inputs. `/spec-next` renders the page at the end of a phase and
154
+ offers the review; `skitterspec spec-env review <spec> [--branch]` is the engine.
155
+
156
+ **The page is not read-only — it takes a review pass back.** Tick `✓ accept` per
157
+ file as you read, write notes against a line or a whole file, answer the
158
+ questions a written review asked, then **end it in a decision** and hand it
159
+ back. Three buttons, each carrying its own verdict:
160
+ **`✓ Approve`** (commit it), **`↺ Request changes`** (work them now),
161
+ **`… Discuss first`** (report and talk). `/spec-diff` stores it (`--notes`),
162
+ plays back what it read, and routes on the verdict — `changes` **is** the
163
+ go-ahead, so it works the commented files immediately, while `discuss`, a
164
+ refused approval and a pass carrying no verdict at all report and wait, which
165
+ is what a bare paste has always done. Accepted files stay unopened either way.
166
+
167
+ **Two ways back, and both are ordinary.** A **served** page POSTs the pass to
168
+ its own URL; the engine holds it in a gitignored `.pending.json` beside the
169
+ notes sidecar and answers with a **six-digit code**. Read the code out and
170
+ `/spec-diff` claims it (`--claim`), merging exactly the pass that code names.
171
+ A `file://` page has no server to talk to — the File System Access API is
172
+ Chrome-only and absent on iOS Safari — so it copies to the clipboard and you
173
+ paste, exactly as before. The clipboard path is **not legacy**: it is the whole
174
+ story for a local reader.
175
+
176
+ The code is **not a secret** — it is printed on the page, and it cannot be a
177
+ gate against Claude either, because the store is a file Claude can read. What
178
+ actually holds is narrower and stronger:
179
+ **a device that reaches your page cannot reach your conversation.**
180
+ So a pass it queues sits in the holding area
181
+ forever, and the rule that keeps it there is that
182
+ **Claude never claims a pass it was not asked to** — stated in `/spec-diff` step
183
+ 0, because nothing enforces it.
184
+
185
+ What the code *does* is let you **tell two passes apart** — it is an address,
186
+ never a password. One waiting pass is claimed and acted on the moment you type
187
+ `/spec-reviewed`, with nothing read back: the round-trip verified *which* pass
188
+ in a design where the page pushed and the agent went looking, and that design is
189
+ gone. Two waiting is a refusal to guess — the one case where a stranger's pass
190
+ sits beside yours, and the one case where six digits are worth anyone's time.
191
+ Pasting the code (`/spec-reviewed 324199`) skips even that. A pass you disown is `--drop <code>`; left there it is reported on every
192
+ render until you stop reading the line.
193
+
194
+ A code that matches nothing refuses, **names nothing**, and never falls back to
195
+ "the only one" — that fallback is precisely what would let an unread pass
196
+ through. Claiming **consumes**: a code works once. A second pass from the same
197
+ page render supersedes the first, so the code on screen is always the pass on
198
+ screen; passes from different renders stand alongside each other.
199
+
200
+ **And a claimed pass never enters the model's context.** The engine holds it,
201
+ merges it, and reports the counts — six digits is what reaches Claude, where a
202
+ pasted blob costs context in proportion to how much you wrote. That is the same
203
+ rule the diff already follows, applied to the one place it used to break. What it did comes back as a **resolution** (`--resolve`),
204
+ so the next render shows each note struck through with a one-line account and you
205
+ verify the fix instead of trusting it. An accept is keyed to the file's
206
+ **content hash**, so it survives the commit that ends the phase and lapses by
207
+ itself when that file changes again — announced as `accepted earlier — changed since`, never
208
+ silently. All of it lives beside the page in gitignored `.spec-env/`, and
209
+ **the marks are information, never a gate**: nothing counts them and nothing
210
+ refuses on them.
211
+
212
+ **The verdict is chosen, never derived.** A verdict is one person's conclusion,
213
+ sent once per pass; a counting gate is a refusal computed from how many boxes
214
+ are ticked, and it stays forbidden. The one refusal here is not a count of
215
+ ticks: **committing** is unavailable while any **comment** is unresolved,
216
+ because you asked for something and it therefore cannot also be fine. Files you
217
+ never ticked block nothing — an unticked file is something you said nothing
218
+ about, and requiring every one of them would be the tally this design exists to
219
+ avoid.
220
+
221
+ **The verdict names the action**, and that is why it is `commit` rather than
222
+ `approve`: a review is the guard in front of an action, and an approval that
223
+ only recorded itself was the one control on the page that did not describe what
224
+ it does. Both committing verdicts hand off to the project's own commit skill
225
+ (`review.commitWith` in `env.config.json`, `/commit` by default) — skitterspec
226
+ never vendors one, because `/commit` belongs to skittership.
227
+ **There is no off switch**: `"none"` existed and was removed, since it produced
228
+ exactly the record-and-do-nothing verdict this design is against.
229
+ `Commit & Continue` then runs `/spec-next` and **stops there** — it never
230
+ completes, lands or tears anything down.
231
+
232
+ A verdict is **consumed**, not stored: a commit verdict is spent by the commit
233
+ and a changes one by the work, so neither can go stale and later act on
234
+ something nobody read. What survives is a one-line outcome log, shown on the
235
+ next render as history.
236
+
237
+ **One ending, every skill (`.claude/rules/spec-reports.md`).** Every skill in
238
+ the table above finishes with the same block — a verdict, then a table of only
239
+ the fields that skill declares — and says nothing while it runs beyond a
240
+ question it cannot answer itself or a failure at the moment it happens. The
241
+ shape is defined once in that rule; each skill's `## Report` section names only
242
+ its verdicts and its fields.
243
+
244
+ ✅ **Phase 2 built** — `feat-orders`, 2 of 4
245
+
246
+ | | |
247
+ |---|---|
248
+ | **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-orders` · phase 2 moved |
249
+ | **Branch** | `spec/feat-orders` · 3 commits, clean |
250
+ | **Built** | POST /orders handler, orders schema |
251
+ | **Tests** | 128 passed · npm test |
252
+ | **Review** | 7 files, +212 −18 · [open the page](file:///…) — want a written review before you commit? |
253
+ | **Follow-ups** | none |
254
+ | **Next** | `/spec-next` → phase 3 (Auth) |
255
+
256
+ Four verdicts, and the last two are different facts about the repo: `✅` done ·
257
+ `⚠️` done with caveats · `❌` failed part-way, so there is a mess to clear ·
258
+ `⏸` refused before acting, so nothing changed.
259
+ **A refusal emits the block too** — "nothing happened" is a reported outcome
260
+ rather than an absent one.
261
+ `Follow-ups` is always present, because a recorded `none` is a decision where a
262
+ missing line is an oversight. And the block covers **that run only**: what else
263
+ is in flight is another question, and volunteering it here leaves the reader
264
+ unable to tell what followed from the run they just watched.
265
+
96
266
  **Ticketing-provider sync (opt-in, a separate package).** The base is
97
267
  tracker-free: it knows nothing about any specific ticketing system. A
98
268
  ticketing provider is installed as its own distribution that plugs into named
@@ -111,6 +281,24 @@ against a committed last-pushed snapshot and applies it) and `/spec-status`
111
281
  workflow-state drifted), backed by a `spec-sync` CLI. There is no content pull —
112
282
  the tracker is never read back or merged.
113
283
 
284
+ A provider may also ship a **read-only listing** of what the tracker holds. That
285
+ is not a pull: nothing is merged back and no spec file is written. It exists
286
+ because the folder buckets are only the truth *on the branch you are standing
287
+ on* — `/spec-start` moves a spec to
288
+ `specs/in-progress/` on that spec's own branch, so on the base branch an
289
+ in-flight spec still reads `backlog` and a teammate's unlanded spec is not on
290
+ disk at all. The tracker knows both, and each row carries the spec's folder name
291
+ so `/spec-start <name>` is a copy-paste away. See the provider's own docs for
292
+ what it calls the command.
293
+
294
+ A provider may also mirror **who is building a spec**, as one more field the repo
295
+ owns. Where it does, `/spec-start` records the developer and the tracker's ticket
296
+ is assigned to them, the assignment is released when the spec completes, and
297
+ `/spec-claim` moves ownership mid-flight — take it, hand it back, or hand it to a
298
+ teammate. Like everything else here it is opt-in and one-way: the spec file is
299
+ the record, and the ticket is the mirror. See the provider's own docs for how to
300
+ turn it on.
301
+
114
302
  **Every skill that moves a spec through the lifecycle carries a seam**, so the
115
303
  mirror keeps up without anyone remembering to push: `/spec`, `/spec-bug` and
116
304
  `/spec-hotfix` link the spec they create; `/spec-next` refreshes it as work starts;
@@ -0,0 +1,269 @@
1
+ # Spec Reports
2
+
3
+ Every lifecycle skill ends with the same block. This file is its whole
4
+ definition — a skill's `## Report` section names only which fields it emits and
5
+ any behaviour that was always there.
6
+
7
+ Two things it buys: a run's outcome is scannable in one shape rather than
8
+ composed fresh each time, and "nothing happened" becomes a reported outcome
9
+ instead of an absent one.
10
+
11
+ ## Silence during the run
12
+
13
+ **Do not narrate.** Not what you are about to do, not why, not which step you
14
+ are on. The tool calls are already on screen, so a line per step re-states the
15
+ transcript in words that never vary.
16
+
17
+ Speak mid-run for exactly two reasons:
18
+
19
+ - **A question you cannot answer yourself** — a decision the spec left open, a
20
+ confirmation a guard demands, an ambiguity with more than one defensible
21
+ answer.
22
+ - **A failure, at the moment it happens** — a red test, a refused command, a
23
+ conflict. Say it where it occurs; do not hold it back for the block.
24
+
25
+ Everything else waits.
26
+
27
+ ## The block
28
+
29
+ A verdict sentence, a blank line, then a two-column table:
30
+
31
+ ✅ **Phase 2 built** — `feat-foo`, 2 of 4
32
+
33
+ | | |
34
+ |---|---|
35
+ | **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · phase 2 moved |
36
+ | **Branch** | `spec/feat-foo` · 3 commits, clean |
37
+ | **Built** | POST /orders handler, orders schema |
38
+ | **Tests** | 128 passed · npm test |
39
+ | **Review** | 7 files, +212 −18 · [open the page](file:///…) — want a written review before you commit? |
40
+ | **Follow-ups** | none |
41
+ | **Next** | `/spec-next` → phase 3 (Auth) |
42
+
43
+ **A table, and never a fenced block.** An aligned label column is only
44
+ achievable inside a fence, because markdown collapses runs of spaces outside
45
+ one — so alignment and being recognised as a report are mutually exclusive, and
46
+ the report wins. A fence renders as the same grey box as a code sample, and a
47
+ reader who has been shown code all session reads one more box as code: the first
48
+ person to receive one of these did not register it as output at all, and missed
49
+ the `Next` row inside it. That is the exact scanning failure this contract exists
50
+ to fix, so producing it in the contract's own format is not a cosmetic mistake.
51
+
52
+ **The verdict sentence** is `<state> **<one clause>** — <spec>, <where it got
53
+ to>`. The clause says what happened; the tail says to what, and how far. Name
54
+ the skill in the clause when the run's identity is not obvious from the
55
+ conversation — a refusal, or a skill invoked by another. A run with no spec to
56
+ name drops that segment rather than inventing one.
57
+
58
+ **The header row is empty.** The labels are the left column, and a header over
59
+ them would only be able to say "field", which no one needs told.
60
+
61
+ **The block is the last thing in the message.** Nothing follows it — not a
62
+ closing line, not a next step, not the offer. Everything the reader must act on
63
+ is a row, which is what earns the block its position.
64
+
65
+ **And that is a rule about the destination, not only the prohibition.** It was
66
+ stated plainly and broken repeatedly, by runs that had something worth saying
67
+ and nowhere in the block to say it — so the paragraph went after the table,
68
+ where it buried the rows it was meant to sit beside. A missing destination reads
69
+ as a rule that does not fit the work, and a rule that does not fit gets ignored.
70
+ So: **if it is worth telling the reader, it is a row.**
71
+ **If it is not a row, it is not worth telling them.**
72
+ `Snags` is the row most of that content belongs in.
73
+
74
+ **One control may follow the block: a picker.** Where the run ends in a choice
75
+ the reader must make, the options may be offered after the table. That is not
76
+ an exception squeezed into the rule — the ban exists because *prose* after the
77
+ block competes with it for the reader's attention and loses them the rows. A
78
+ picker does not compete: it is the `Next` row made actionable, in the place the
79
+ reader's eye already finishes. Prose after the block stays banned, and adding a
80
+ sentence alongside the picker is the same violation wearing a control's clothes.
81
+
82
+ **Never fence a message to the reader.** A fenced block is for a command to
83
+ run, code, or engine output quoted verbatim — things the reader copies or
84
+ compares. A question, an offer, a hand-off is prose. The same mechanism that
85
+ made the old report unreadable applies to anything else in a grey box: it is
86
+ read as an artefact to skim rather than as something addressed to someone, so a
87
+ question inside one goes unanswered and an instruction inside one goes unread.
88
+
89
+ **It reports this run and nothing else.** Not what else is in flight, not the
90
+ other worktrees, not the backlog, not the state of the repo at large. A reader
91
+ finishing one piece of work cannot tell whether a line about some other spec is
92
+ a consequence of what just happened or an unrelated aside, and has to stop and
93
+ work it out — which is the cost this block exists to remove. If they want the
94
+ wider picture there is a skill that answers it; volunteering it here muddies the
95
+ one thing they asked about.
96
+
97
+ `Next` is the single next action **for this work**, not a menu of what else
98
+ could be done.
99
+
100
+ **And it must be runnable from the state the run actually leaves behind.** Most
101
+ of these skills deliberately do not commit — they finish, verify, and wait — so
102
+ the tree they hand back is dirty, and several of the commands they would name
103
+ next refuse on a dirty tree. A `Next` that omits the commit therefore sends the
104
+ reader to a refusal: `/spec-next` leaves a phase uncommitted and its own §2
105
+ refuses on exactly that, and `/spec-bug` leaves a fix uncommitted where
106
+ `/spec-complete` refuses the same way. Where the skill does not commit, the row
107
+ says `/commit, then <the thing>`.
108
+
109
+ This is not a style rule. `Next` is the one row a reader is meant to act on, and
110
+ it closes the block because the closing row should be the one that moves the
111
+ work on. A row that cannot be run spends the trust the position was designed to
112
+ earn — and the two skills that got it wrong got it wrong independently, which is
113
+ what makes it a missing constraint rather than two slips.
114
+
115
+ ## Field vocabulary, in this order
116
+
117
+ A skill emits only the fields it declares, in this order, skipping the rest.
118
+ `Follow-ups` is the exception: always present, second to last.
119
+
120
+ `Tracker` is the conditional one: a skill may declare it and still never emit
121
+ it, because no ticketing provider is installed. That is an absence with nothing
122
+ behind it — say nothing rather than reporting that there was nothing to report.
123
+
124
+ | Field | Carries |
125
+ |-------|---------|
126
+ | `Tracker` | **First.** The ticket id, linked where the spec has a url, the spec's folder name beside it, and what changed there. |
127
+ | `Why` | **Non-`✅` only.** What stopped it, in one clause. |
128
+ | `Branch` | Branch name, commit count, clean or dirty. |
129
+ | `Spec` | The spec document's own state: status, bucket, phase. |
130
+ | `Cause` | The root cause, for work that diagnosed one. |
131
+ | `Built` | What the run produced — the code, the edits, the spec written. |
132
+ | `Tests` | The result and the command that produced it. |
133
+ | `Snags` | What this run hit and handled — one short paragraph. A wrong turn, a guard that fired, a check of yours that turned out to be a false negative. Not a caveat on the outcome (`⚠️` and `Why` carry those) and not future work (`Follow-ups` carries that): this is what happened on the way. |
134
+ | `Landed` | A fast-forward, a tag, a cherry-pick. |
135
+ | `Worktree` | A worktree provisioned, entered, or torn down. |
136
+ | `Untouched` | Uncommitted work the run deliberately left alone — whose, and how much. |
137
+ | `Review` | The rendered diff page: files, `+`/`−`, the link — and the offer of a written review, in the same row. |
138
+ | `Follow-ups` | **Always.** `none`, or one line each. |
139
+ | `Next` | **Last.** The single next action for this work — runnable from the state the run leaves behind. |
140
+
141
+ The order runs identity → context → what happened → where it went → what to do.
142
+ **`Tracker` is first** because the id is what addresses this work outside the
143
+ repo, and **`Next` is last** because it is the only row the reader acts on: the
144
+ closing row should be the one that moves the work on.
145
+
146
+ **`Untouched` is a fact, not a caveat.** A run that provisions beside another
147
+ spec's uncommitted work has done nothing wrong and nothing partial — it left
148
+ alone something that was never its business — so it stays `✅` with a row, and
149
+ does not become `⚠️`. Several specs in flight at once is what the worktree mode
150
+ is *for*; spending the "something is worth knowing" verdict on the normal case
151
+ is how that verdict stops meaning anything. It is still reported rather than
152
+ silent, because "I provisioned, and your four files are still sitting there" is
153
+ a different sentence from "I provisioned".
154
+
155
+ **`Review` is one row, not two.** The page and the offer to read it are the same
156
+ subject, and splitting them made the reader resolve a distinction before acting
157
+ on either. The offer does not need the final row once it has a label column to
158
+ be found by.
159
+
160
+ ## The four verdicts
161
+
162
+ | State | Means |
163
+ |-------|-------|
164
+ | `✅` | Done. Everything the skill set out to do happened. |
165
+ | `⚠️` | Done with caveats — it finished, but something is worth knowing. |
166
+ | `❌` | Failed part-way. It acted, and the repo is mid-something. |
167
+ | `⏸` | Refused before acting. Nothing changed. |
168
+
169
+ **`❌` and `⏸` are different facts about the repo**, and the difference decides
170
+ what happens next. A failed landing leaves a conflicted rebase and a standing
171
+ worktree — there is a mess to clear. A refusal leaves nothing at all, and the
172
+ only thing to do is supply what was missing. Collapsing them sends people
173
+ looking for wreckage that is not there, or past wreckage that is.
174
+
175
+ **A refusal still emits the block.** That is the point of having four states:
176
+ every invocation ends the same way, so an outcome is never inferred from
177
+ silence.
178
+
179
+ ## Where the block may grow
180
+
181
+ One place only. A non-`✅` verdict adds a `Why` row, and quotes the output that
182
+ justifies it — the failing assertion, the conflict, the engine's refusal — in a
183
+ fenced block **above** the table, where a fence means what a fence should mean:
184
+
185
+ ```
186
+ ✖ rejects a negative quantity
187
+ expected 422, got 500
188
+ ```
189
+
190
+ ❌ **Phase 2 stopped — tests red** — `feat-foo`, 2 of 4
191
+
192
+ | | |
193
+ |---|---|
194
+ | **Why** | 3 assertions fail in `orders.test.js`; nothing committed |
195
+ | **Branch** | `spec/feat-foo` · 2 commits, dirty |
196
+ | **Built** | POST /orders handler, orders schema |
197
+ | **Tests** | 125 passed, 3 failed · npm test |
198
+ | **Follow-ups** | none |
199
+ | **Next** | fix the failures, then `/spec-next` to finish phase 2 |
200
+
201
+ Everywhere else the block stays the size it is. Prose after it, an extra row of
202
+ your own invention, a second block — all three are the shape drifting back to
203
+ what this replaced.
204
+
205
+ ## Follow-ups
206
+
207
+ **Always present.** A recorded `none` is a decision; a missing line is an
208
+ oversight — the same logic as the `Gating` header.
209
+
210
+ The bar is deliberately high. A follow-up is something *this* work surfaced
211
+ that *this* spec will not fix — a wrong assumption, a missing guard, a decision
212
+ the spec deferred and the code then leaned on. It is **not** "could be tidier",
213
+ and it is **not** anything already sitting in the spec's Open questions.
214
+
215
+ When there is one, **offer to `/spec` it on the spot**. If the user does not
216
+ take the offer, write one dated line into the spec's Changelog before you
217
+ finish, so it outlives the session:
218
+
219
+ ```
220
+ - 2026-01-09 — Follow-up surfaced: connect's port picker assumes a free
221
+ canonical port; not in scope here.
222
+ ```
223
+
224
+ **Say where the spec would be written.** These skills run inside the spec's own
225
+ worktree, and a spec authored there physically lives on that branch — invisible
226
+ from the base branch, and cancelled along with its host if the host is
227
+ cancelled. So the offer names the primary checkout as the place to write it.
228
+ `spec-planning.md` and `commit-trailers.md` carry the reasoning.
229
+
230
+ ## Worked examples
231
+
232
+ **`⚠️` — it finished, with something worth knowing.**
233
+
234
+ ⚠️ **Phase 3 built; tracker not updated** — `feat-foo`, 3 of 4
235
+
236
+ | | |
237
+ |---|---|
238
+ | **Branch** | `spec/feat-foo` · 4 commits, clean |
239
+ | **Built** | Auth middleware, session table |
240
+ | **Tests** | 140 passed · npm test |
241
+ | **Follow-ups** | Session expiry is read from two places; phase 4 leans on one |
242
+ | **Next** | `/spec-next` → phase 4 (Docs) |
243
+
244
+ **`⏸` — refused, nothing changed.**
245
+
246
+ ⏸ **`/spec-next` refused — no spec in flight**
247
+
248
+ | | |
249
+ |---|---|
250
+ | **Why** | Not standing in a worktree, and 2 specs are provisioned |
251
+ | **Follow-ups** | none |
252
+ | **Next** | `/spec-start <name>`, or `cd` into `../repo-wt/feat-orders` or `../repo-wt/feat-auth` |
253
+
254
+ **`✅` — a spec finished and landed.**
255
+
256
+ ✅ **Landed and torn down** — `feat-foo`, complete
257
+
258
+ | | |
259
+ |---|---|
260
+ | **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · moved to its done state |
261
+ | **Spec** | Complete · `specs/complete/feat-foo` |
262
+ | **Tests** | 131 passed · npm test |
263
+ | **Landed** | base fast-forwarded to 4a1c9e2 |
264
+ | **Worktree** | removed · `../repo-wt/feat-foo` |
265
+ | **Follow-ups** | none |
266
+ | **Next** | pick the next spec from `specs/backlog/` |
267
+
268
+ The `❌` example is in **Where the block may grow** above, since the failing
269
+ output it quotes is the thing that example exists to show.
@@ -5,6 +5,11 @@ description: Create a new spec-driven-development spec. Grills the user to a cle
5
5
 
6
6
  # /spec — author a new spec
7
7
 
8
+ > Stay silent while this runs — speak only to ask something you cannot answer
9
+ > yourself, or to report a failure at the moment it happens. Read
10
+ > `.claude/rules/spec-reports.md` before reporting; it defines the block this
11
+ > skill ends with.
12
+
8
13
  Produce ONE concise spec in `specs/backlog/`. Do not start coding — this skill
9
14
  plans only. Implementation happens later via `/spec-start`.
10
15
 
@@ -329,10 +334,10 @@ Rules for the spec body:
329
334
 
330
335
  ## Phase C — finish up
331
336
 
332
- After writing, tell the user the path and that it's **`Ready`** in `backlog`
333
- (grilling in Phase A resolved the open questions). If you deliberately left open
334
- questions unresolved, write `Draft` instead and say what still needs deciding.
335
- Either way, the next step is `/spec-start` to start building.
337
+ Decide the status: **`Ready`** in `backlog` when grilling in Phase A resolved the
338
+ open questions, `Draft` when you deliberately left some unresolved. Either way
339
+ the next step is `/spec-start`. The Report section below is where all of that
340
+ reaches the user do not narrate it here as well.
336
341
 
337
342
  ## Phase D — record the isolation stack (only if configured)
338
343
 
@@ -382,7 +387,7 @@ does — you never create the issue by hand:
382
387
  2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
383
388
  Keep the chosen id for step 4.
384
389
  3. **Get the plan.**
385
- `skitterspec spec-sync push <spec> --workspace-states <file> --json > plan.json`
390
+ `skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
386
391
  — the spec is unlinked, so this plan is all-creates: the issue and one
387
392
  sub-issue per phase.
388
393
  4. **Apply it.**
@@ -446,3 +451,26 @@ say so in one line — *"project picker unavailable"* — and carry on with
446
451
 
447
452
  If `linear.projectId` is set but that Project is archived or missing, relay
448
453
  Linear's error and stop rather than silently minting an unparented issue.
454
+
455
+ ## Report
456
+
457
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
458
+ the shape; this section carries only what is specific here.
459
+
460
+ **Verdicts**
461
+
462
+ - `✅` — a `Ready` spec is written to `specs/backlog/<name>/`.
463
+ - `⚠️` — written as `Draft`; open questions were deliberately left. Name them.
464
+ - `⏸` — grilling did not reach a shared understanding, so nothing was written.
465
+ That is the skill working: a spec written over an unresolved requirement is
466
+ the outcome Phase A exists to prevent.
467
+
468
+ **Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
469
+
470
+ `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
+ `Tracker` appears only when a provider is installed and linked it.
473
+
474
+ **`Follow-ups` is almost always `none` here.** This skill's whole job is to
475
+ capture work, so anything it surfaced belongs in the spec it just wrote rather
476
+ than in a follow-up line beneath it.