@skitterbyte/skitterspec-linear 12.0.0 → 14.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 (58) hide show
  1. package/MIGRATION.md +296 -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 +7 -3
  8. package/assets/core/env.config.md +90 -30
  9. package/assets/core/linear.config.md +58 -0
  10. package/assets/hooks/review-gate.js +141 -0
  11. package/assets/review/page.html +1787 -0
  12. package/assets/rules/spec-planning.md +250 -10
  13. package/assets/rules/spec-reports.md +321 -0
  14. package/assets/skills/spec/SKILL.md +33 -5
  15. package/assets/skills/spec-bug/SKILL.md +172 -9
  16. package/assets/skills/spec-cancel/SKILL.md +98 -21
  17. package/assets/skills/spec-claim/SKILL.md +114 -0
  18. package/assets/skills/spec-complete/SKILL.md +94 -25
  19. package/assets/skills/spec-diff/SKILL.md +678 -0
  20. package/assets/skills/spec-hotfix/SKILL.md +172 -11
  21. package/assets/skills/spec-init/SKILL.md +56 -7
  22. package/assets/skills/spec-linear-setup/SKILL.md +55 -1
  23. package/assets/skills/spec-list/SKILL.md +218 -0
  24. package/assets/skills/spec-next/SKILL.md +419 -7
  25. package/assets/skills/spec-push/SKILL.md +32 -8
  26. package/assets/skills/spec-review/SKILL.md +40 -5
  27. package/assets/skills/spec-reviewed/SKILL.md +258 -0
  28. package/assets/skills/spec-start/SKILL.md +386 -106
  29. package/assets/skills/spec-status/SKILL.md +24 -2
  30. package/assets/skills/spec-sync/SKILL.md +40 -4
  31. package/assets/skills/spec-to-main/SKILL.md +28 -6
  32. package/package.json +11 -7
  33. package/src/cli.js +1808 -89
  34. package/src/env/building.js +143 -0
  35. package/src/env/commitcmd.js +108 -0
  36. package/src/env/config.js +58 -9
  37. package/src/env/hooks.js +117 -0
  38. package/src/env/provision.js +54 -15
  39. package/src/env/proxy.js +34 -1
  40. package/src/env/render.js +3 -12
  41. package/src/env/resolve.js +295 -9
  42. package/src/env/review.js +1536 -0
  43. package/src/env/serve.js +573 -0
  44. package/src/env/teardown.js +13 -6
  45. package/src/init.js +150 -1
  46. package/src/vendor/linear/api.js +104 -1
  47. package/src/vendor/linear/cli-sync.js +854 -17
  48. package/src/vendor/linear/config.js +8 -0
  49. package/src/vendor/linear/credentials.js +94 -0
  50. package/src/vendor/linear/doctor.js +35 -0
  51. package/src/vendor/linear/identity.js +105 -0
  52. package/src/vendor/linear/mcp.js +26 -0
  53. package/src/vendor/sync-core/index.js +6 -2
  54. package/src/vendor/sync-core/src/compare.js +49 -3
  55. package/src/vendor/sync-core/src/normalize.js +30 -0
  56. package/src/vendor/sync-core/src/push.js +11 -1
  57. package/src/vendor/sync-core/src/write.js +38 -0
  58. package/LICENSE +0 -21
package/MIGRATION.md CHANGED
@@ -1,5 +1,297 @@
1
1
  # Migration guide
2
2
 
3
+ ## `@skitterbyte/skitterspec` v19 → v20 (a phase owes a verdict)
4
+
5
+ ### Breaking change
6
+
7
+ **A phase that has ended now refuses to go further until you send a verdict.**
8
+ `/spec-next` **arms** a gate when it finishes a phase and renders its review
9
+ page. While it is armed, two things refuse:
10
+
11
+ - `/spec-next` will not build the next phase.
12
+ - `git commit` inside **that spec's own worktree** is blocked by a harness hook
13
+ — which is what covers a bare `git commit`, a chained command, and
14
+ skittership's `/commit` without skitterspec editing any of them.
15
+
16
+ Exactly two things clear it, and both are one command:
17
+
18
+ ```
19
+ # press Commit or Commit & Continue on the review page — or:
20
+ skitterspec spec-env review skip "none: additive, nothing to revert"
21
+ ```
22
+
23
+ The skip is deliberately not silent: the reason goes into the review outcome
24
+ log, on the same reasoning as the `Gating:` header — a reason is a decision a
25
+ reviewer can argue with, where silence is an oversight.
26
+
27
+ **It is on by default** wherever isolation is configured (`env.config.json`
28
+ present). To turn it off for a project, add to `specs/.core/env.config.json`:
29
+
30
+ ```json
31
+ { "review": { "required": false } }
32
+ ```
33
+
34
+ Three things keep it a push rather than a wall, and they are worth knowing
35
+ before you reach for that setting. It is armed **only by a phase ending**, so a
36
+ mid-phase `/spec-diff` owes nothing. The exit is always one command. And it
37
+ accuses only on a positive signal — no engine, an unreadable payload, a commit
38
+ on the base branch or in another spec's tree, a repo with no isolation: every
39
+ cannot-tell lets the commit through.
40
+
41
+ ### Breaking change
42
+
43
+ **`.claude/settings.json` is now written by the installer.** `skitterspec init`
44
+ and `skitterspec update` copy `.claude/hooks/review-gate.js` and register it as
45
+ a `PreToolUse` hook in your project's **committed** settings file. That is a
46
+ tracked file in most repos, so expect it in `git status` after upgrading — and
47
+ commit it, because a hook only a fraction of the team has is a gate that holds
48
+ for a fraction of the team.
49
+
50
+ A settings file that cannot be parsed is **reported and left alone**, never
51
+ rewritten. The hook is an extra layer: the engine and `/spec-next` hold the gate
52
+ without it.
53
+
54
+ ### The review page can now reach your session — deliberately
55
+
56
+ This inverts an invariant the docs used to state outright: *a device that
57
+ reaches your page cannot reach your conversation*. It no longer holds, and the
58
+ change is the point. `/spec-next` now ends a phase by rendering the page and
59
+ **waiting** on it, so the button you press is what carries the work on — there
60
+ is no command to remember.
61
+
62
+ What replaced the old guard is two mechanisms and one rule:
63
+
64
+ - **The serve token** — 48 random bits in the URL path, minted per server —
65
+ decides who can POST at all.
66
+ - **The wait window** — only a pass that arrives *while the session is waiting*
67
+ is claimed for you, and two arrivals refuse rather than pick one.
68
+ - Outside that window nothing is claimed unasked. `/spec-reviewed`, or
69
+ `/spec-reviewed 324199` to name one exactly, is still how a pass sent when
70
+ nobody was waiting gets picked up — and it is still user-only, so the model
71
+ cannot claim a pass on its own.
72
+
73
+ A `file://` page has no server to talk to, so it copies and you paste, exactly
74
+ as before.
75
+
76
+ ### What to do
77
+
78
+ 1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
79
+ 2. **Commit `.claude/settings.json` and `.claude/hooks/review-gate.js`.** Both
80
+ are new in your working tree after the update.
81
+ 3. **Nothing else to configure.** `review.required` defaults to `true` and
82
+ `review.commitWith` defaults to `/commit`; neither needs adding unless you
83
+ are changing it.
84
+
85
+ ## `@skitterbyte/skitterspec-linear` v13 → v14 (a phase owes a verdict)
86
+
87
+ The same change as `@skitterbyte/skitterspec` v19 → v20 above — this
88
+ distribution composes the same lifecycle skills. Read that entry; nothing here
89
+ is Linear-specific.
90
+
91
+ ## `@skitterbyte/skitterspec` v18 → v19 (starting a spec offers phase 1)
92
+
93
+ ### Breaking change
94
+
95
+ **skitterspec now requires Node 22.13 or newer.** `engines.node` was `>=18`, and
96
+ that floor was a claim nobody was testing: the test suite cannot run without an
97
+ install, and pnpm 11.11 — the package manager this repo pins — itself requires
98
+ 22.13. A floor CI cannot exercise is a promise rather than a guarantee, so it was
99
+ raised to the version the toolchain actually needs.
100
+
101
+ On Node 18 or 20, `npm install` now warns — or fails, under `engine-strict`.
102
+ Upgrade Node, or stay on v18 of skitterspec. Nothing in your config changes
103
+ either way.
104
+
105
+ ### Releases now carry provenance
106
+
107
+ Every release from v19 onwards is built and signed by GitHub Actions through npm
108
+ Trusted Publishing, so no publish token exists anywhere to be leaked. Each
109
+ published version carries a signed attestation you can verify back to the commit
110
+ it was built from:
111
+
112
+ ```
113
+ npm view @skitterbyte/skitterspec@19.0.0 dist.attestations
114
+ ```
115
+
116
+ Nothing to do — it is a property of the package you receive.
117
+
118
+ ### Breaking change
119
+
120
+ **`/spec-start` no longer pushes the spec's branch.** It provisions the worktree
121
+ and commits the spec's move to `in-progress/` exactly as before, and then stops.
122
+ Publishing is yours to do, whenever you want the work somewhere other than your
123
+ machine:
124
+
125
+ ```
126
+ git -C <worktreePath> push -u origin <branch>
127
+ ```
128
+
129
+ Two things change for you, and neither is in your config:
130
+
131
+ - **Spec branches stop appearing on the remote.** Nothing is lost — the branch
132
+ and its commits are in the worktree — but a branch you have not pushed is on
133
+ one machine only, and that is now the default rather than something the
134
+ tooling quietly undid.
135
+ - **Cancelling a spec with unpublished work now refuses.** `/spec-cancel` has
136
+ always respected `guards.refuseTeardownIfUnpushed`, but the guard could never
137
+ fire while provisioning published every branch. It fires now, at the one moment
138
+ it was written for: the work really is about to be destroyed, and the worktree
139
+ is the only copy. `/spec-cancel` names both ways out — publish the branch and
140
+ re-run, or `spec-env down <name> --force` accepting the loss. Nothing was
141
+ removed from your config and nothing needs adding to it.
142
+
143
+ The justification for the old behaviour does not survive reading, which is why
144
+ it went rather than becoming a setting: it claimed to fire the tracker's branch
145
+ automation, and that needs `{identifier}` in `branch.pattern`, which the shipped
146
+ default does not carry. `/spec-bug` never pushed and `/spec-hotfix` forbids it,
147
+ so this also makes the three consistent.
148
+
149
+ **The `open.command` config key is gone.** It was the editor/terminal-agnostic
150
+ opener — `code {worktreePath}`, a `tmux` command, a `warp://` deeplink — that
151
+ `/spec-start` ran when it could not move your session into the worktree.
152
+
153
+ **Leaving it in `env.config.json` is harmless and silent** — which is the part
154
+ to watch. The config merge copies known keys only, so a leftover `open` block is
155
+ ignored rather than rejected: nothing errors, and your editor simply stops
156
+ opening. If you set it deliberately, that absence is the only signal you get.
157
+
158
+ **`/spec-start` no longer moves your session into the worktree either.** It
159
+ provisions the worktree, does the housekeeping there, prints the path, and stops.
160
+ This **supersedes the "opens a session in it" half of v17 → v18 below** — the
161
+ branch still never leaves its worktree, but nothing tries to relocate your shell
162
+ to reach it.
163
+
164
+ | v18 | v19 |
165
+ |-----|-----|
166
+ | Three paths through `/spec-start`: enter the session, or fall back two ways | **One path.** Provision, bootstrap, print the path. |
167
+ | Reaching the work meant getting a shell or a window into the worktree | **`/spec-diff`** renders the worktree's diff as a page you read anywhere |
168
+ | `open.command` opened an editor on the fallback path | Removed. Nothing opens anything. |
169
+
170
+ ### What replaced it
171
+
172
+ **`/spec-diff`** — a new skill, and the reason the opener had nothing left to do.
173
+ A phase is built in its own worktree, so `git diff` in your terminal answers
174
+ about the base branch. `/spec-diff` collects that worktree's changes with
175
+ `git -C` and writes a self-contained HTML page: whole-file context that folds
176
+ away, a file tree, untracked files included. Open it locally, or publish it and
177
+ read it on a phone.
178
+
179
+ The page lands in `.spec-env/reviews/<spec>.html` (gitignored), and
180
+ **the diff never passes through the model** — so it costs no context tokens
181
+ however large it is. The optional *written* review is the part that costs, and it
182
+ is offered rather than assumed. `/spec-next` writes the page at the end of every
183
+ phase. Beneath it, `skitterspec spec-env review <spec> [--branch]` is the engine.
184
+
185
+ ### `/spec-start` lands you in the worktree, and offers phase 1
186
+
187
+ Two changes to the same moment. In `worktree` mode `/spec-start` used to
188
+ provision the branch, print the path and leave your session where it was; opening
189
+ a session in the worktree was then yours to do. It now
190
+ **moves your session into the worktree** as part of bootstrapping it, and asks
191
+ whether to build phase 1:
192
+
193
+ ```
194
+ worktree ready — this session is now in it:
195
+ ../myrepo-wt/sort-inbox
196
+
197
+ build phase 1 now?
198
+ yes -> carries on into /spec-next
199
+ no -> you are already there; type /spec-next whenever you like
200
+ ```
201
+
202
+ **Your shell will not be where it was.** A session that was on `main` in the
203
+ primary checkout is standing in the spec's worktree afterwards, on the spec's
204
+ branch — so the next command you type runs there. That is the point of it, and it
205
+ is still a real change to plan for. The move is a plain `cd`: nothing prompts you
206
+ for approval, nothing opens a new terminal or window, and your primary checkout
207
+ is untouched and still on the base branch.
208
+
209
+ **`/spec-next` needs no argument now.** Say **yes** and `/spec-start` carries on
210
+ into a bare `/spec-next`; say **no** and typing `/spec-next` an hour later does
211
+ the same thing, because you are already standing in the right place. Neither is
212
+ assumed, because provisioning is cheap and reversible while a phase build is
213
+ neither.
214
+
215
+ **This is not a loosened refusal.** A bare `/spec-next` still refuses to build a
216
+ spec it is not standing in, exactly as before — what changed is where you are
217
+ standing, not how weakly the rule reads. `--worktree <path>` survives beside it as
218
+ the explicit way to build a spec you are *not* in, and a path you pass is still
219
+ not a path anything guessed.
220
+
221
+ **Leaving is a `cd` too.** `/spec-complete` and `/spec-cancel` delete the
222
+ worktree, which is now the directory you are standing in, so both tell you to `cd`
223
+ to the primary checkout first. `git worktree remove` **succeeds** on the tree you
224
+ occupy rather than refusing — the teardown looks fine and every command after it
225
+ dies with `Unable to read current working directory`.
226
+
227
+ **The `--worktree` build checks itself.** On that path, `/spec-next` first records
228
+ what your primary checkout looked like, and afterwards reports anything that
229
+ appeared in it — the signature of a relative path that missed the worktree. It
230
+ reports rather than accuses: it cannot know who wrote a file, so it names both
231
+ readings and deletes nothing.
232
+ `skitterspec spec-env resolve <spec> --record-primary` and
233
+ `--assert-primary-clean` are the engine underneath, usable on their own.
234
+
235
+ ### What to do
236
+
237
+ 1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
238
+ 2. **Delete the `open` block from `specs/.core/env.config.json`**, if you have
239
+ one. Optional — it is ignored either way — but leaving it implies a setting
240
+ that no longer does anything.
241
+ 3. **Answer the question `/spec-start` now asks.** In `worktree` mode it offers
242
+ phase 1 before it finishes. Take the offer and it is built there and then;
243
+ decline and you are left standing in the provisioned worktree, free to type
244
+ `/spec-next` whenever you like. Both endings are fully supported — decline when
245
+ the phase is a big one and you would rather spend a fresh context on it.
246
+ 4. **Expect your shell to move, whichever you answer.** Anything you had queued
247
+ for the primary checkout — a `git` command, a script, a relative path — now
248
+ runs in the worktree instead. `cd` back when you want the base branch, and note
249
+ that `/spec-complete` and `/spec-cancel` require exactly that before they tear
250
+ the worktree down.
251
+ 5. **Use `/spec-diff` to read the work** rather than reaching for a terminal in
252
+ the worktree. It is gated on nothing — half a phase, a hand edit, or a
253
+ colleague's branch are all ordinary inputs.
254
+
255
+ `checkout` mode is unchanged.
256
+
257
+ ## `@skitterbyte/skitterspec-linear` v12 → v13 (starting a spec offers phase 1)
258
+
259
+ The same change as `@skitterbyte/skitterspec` v18 → v19 above — this
260
+ distribution composes the same lifecycle skills. Read that entry first.
261
+
262
+ ### Breaking change
263
+
264
+ **`spec-sync push` is now `spec-sync plan`.** The verb computes a create/update
265
+ plan and performs no network I/O; `spec-sync apply` is what writes to Linear.
266
+ Calling it `push` put three unrelated things behind one word — this verb, the
267
+ `/spec-push` skill, and `git push` — and it was the one that pushes nothing.
268
+
269
+ ```
270
+ skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json
271
+ skitterspec spec-sync apply <spec> --plan plan.json
272
+ ```
273
+
274
+ The old name is **not** aliased. It is recognised and exits 1 naming its
275
+ replacement, so a script that calls it fails loudly with the fix in the message
276
+ rather than drifting on a name that will be removed later.
277
+
278
+ **Your `linear.config.json` needs no change.** The `"push"` values under
279
+ `sync.fieldOwnership` — `assignee: "push"`, `description: "push"`,
280
+ `workflowState: "push"` — are a different vocabulary: they name a direction of
281
+ ownership, not a subcommand. They are untouched and still mean what they meant.
282
+ Do not search-and-replace `push` in your config.
283
+
284
+ ### What else is here
285
+
286
+ **One thing here is Linear-specific.** `/spec-start` now pushes to Linear itself,
287
+ right after it commits the spec's move to `in-progress/`. It used to push nothing
288
+ and leave the mirror to the refresh `/spec-next` runs — immediate in `checkout`
289
+ mode, but in `worktree` mode hours away or never. Until it came, the issue sat in
290
+ its old workflow state with nobody assigned while the repo read `in-progress`
291
+ with a developer on it. Expect one more Linear call per `/spec-start`, and expect
292
+ the issue to be current the moment the spec is in flight. Nothing else changes:
293
+ sync is still one-way, and an unlinked spec is still skipped rather than minted.
294
+
3
295
  ## `@skitterbyte/skitterspec` v17 → v18 (a spec is built in its own worktree)
4
296
 
5
297
  ### Breaking change
@@ -90,8 +382,7 @@ landed and this one was missed.)
90
382
 
91
383
  ## `@skitterbyte/skitterspec-linear` v9 → v10 (`push` validates your issue states)
92
384
 
93
- **`spec-sync push` now refuses to run until the configured `states` names have
94
- been checked against your Linear workspace.** The check itself is not new — it
385
+ **`spec-sync push` now refuses to run until the configured `states` names have been checked against your Linear workspace.** The check itself is not new — it
95
386
  already existed on `spec-sync status --workspace-states` — but it was advisory,
96
387
  and skipping it sent a state name Linear **silently ignores**: the description
97
388
  lands, the issue never moves, and nothing errors. The base
@@ -105,8 +396,7 @@ lands, the issue never moves, and nothing errors. The base
105
396
  | A configured state absent from the workspace | pushed, silently no-op | **exits 1**, naming the workspace's real states |
106
397
 
107
398
  `/spec-push` handles this for you — it fetches the workspace's issue
108
- workflow-state names over MCP and passes them on. **Nothing changes if you drive
109
- sync through the skill.** Only a direct CLI caller needs updating.
399
+ workflow-state names over MCP and passes them on. **Nothing changes if you drive sync through the skill.** Only a direct CLI caller needs updating.
110
400
 
111
401
  ### What to do
112
402
 
@@ -156,8 +446,7 @@ sync through the skill.** Only a direct CLI caller needs updating.
156
446
  **v9 remaps the Linear mirror.** A spec is now a Linear **issue** (not a Project),
157
447
  each phase a **sub-issue** (not a Milestone), and **tasks are no longer synced**
158
448
  (they stay in the repo phase files). This collapses a large spec from ~1 project +
159
- N milestones + dozens of task-issues down to **one issue + one sub-issue per
160
- phase**. The base `@skitterbyte/skitterspec` is unaffected (still v15).
449
+ N milestones + dozens of task-issues down to **one issue + one sub-issue per phase**. The base `@skitterbyte/skitterspec` is unaffected (still v15).
161
450
 
162
451
  ### Breaking changes
163
452
 
@@ -274,10 +563,7 @@ both as optional — `/spec-review` adds them if you want them.
274
563
 
275
564
  ## `@skitterbyte/skitterspec` v2 → v3 (slimmer surface + local traffic diversion)
276
565
 
277
- **v3 shrinks the everyday command surface to five verbs — `spec → go → connect →
278
- commit → complete` — by folding provisioning, teardown, and grooming into the
279
- lifecycle skills, and adds `/spec-connect` for testing a worktree at your normal
280
- `localhost` URL.** (`@skitterbyte/skitterspec-linear` moves to v2.0.0 in lockstep.)
566
+ **v3 shrinks the everyday command surface to five verbs — `spec → go → connect → commit → complete` — by folding provisioning, teardown, and grooming into the lifecycle skills, and adds `/spec-connect` for testing a worktree at your normal `localhost` URL.** (`@skitterbyte/skitterspec-linear` moves to v2.0.0 in lockstep.)
281
567
 
282
568
  ### Removed skills (breaking) → where they went
283
569
 
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # @skitterbyte/skitterspec-linear
2
2
 
3
- Spec-driven development for [Claude Code](https://claude.com/claude-code), **with
4
- one-way Linear sync**. A strict **superset** of
3
+ Spec-driven development for [Claude Code](https://claude.com/claude-code), **with one-way Linear sync**. A strict **superset** of
5
4
  [`@skitterbyte/skitterspec`](https://www.npmjs.com/package/@skitterbyte/skitterspec):
6
5
  everything in the base filesystem workflow, plus one-way sync from a spec up to
7
6
  its linked Linear issue — the repo is canonical, Linear is a generated mirror.
@@ -114,6 +113,37 @@ also start **from** an existing issue — `/spec SKI-123`,
114
113
  `/spec-hotfix v33.16.4 SKI-123`, or `--from-issue` to browse the ones your web
115
114
  app filed. There is no pull — the repo is already canonical.
116
115
 
116
+ ## One ending, every skill — `.claude/rules/spec-reports.md`
117
+
118
+ Every skill finishes with the same block, and says nothing while it runs beyond
119
+ a question it cannot answer itself or a failure at the moment it happens:
120
+
121
+ ✅ **Phase 2 built** — `feat-orders`, 2 of 4
122
+
123
+ | | |
124
+ |---|---|
125
+ | **Tracker** | [SKS-88](https://linear.app/…/SKS-88) · `feat-orders` · phase 2 moved |
126
+ | **Branch** | `spec/feat-orders` · 3 commits, clean |
127
+ | **Built** | POST /orders handler, orders schema |
128
+ | **Tests** | 128 passed · npm test |
129
+ | **Review** | 7 files, +212 −18 · [open the page](file:///…) — want a written review before you commit? |
130
+ | **Follow-ups** | none |
131
+ | **Next** | `/spec-next` → phase 3 (Auth) |
132
+
133
+ Four verdicts, and the last two are different facts about your repo: `✅` done ·
134
+ `⚠️` done with caveats · `❌` failed part-way, so there is a mess to clear ·
135
+ `⏸` refused before acting, so nothing changed.
136
+ **A refusal emits the block too**, so "nothing happened" is a reported outcome
137
+ rather than an absent one.
138
+
139
+ Fields come from a fixed vocabulary in a fixed order, and a skill emits only the
140
+ ones it declares — `Tracker` first because the id is how you address the work
141
+ outside the repo, `Next` last because it is the only row you act on.
142
+ `Follow-ups` is always there: a recorded `none` is a decision where a missing
143
+ line is an oversight. The block covers **that run only** — what else is in
144
+ flight is a different question, and answering it here leaves you unable to tell
145
+ what followed from the run you just watched.
146
+
117
147
  ## Opt-in
118
148
 
119
149
  Linear sync is inert until `specs/.core/linear.config.json` exists — copy the
@@ -2,14 +2,50 @@
2
2
 
3
3
  Spec-driven development runs through the lifecycle **skills** — use them so
4
4
  structure and lifecycle stay consistent. The everyday loop is
5
- **`spec → start → next → commit → complete`**, with `/spec-connect` when you want to test
6
- the spec in a browser.
5
+ **`spec → start → next → commit → complete`**, with **`/spec-diff`** to read what
6
+ a phase changed and `/spec-connect` when you want to test the spec in a browser.
7
7
 
8
8
  **Skills vs commands.** The lifecycle skills are read by Claude, which exercises
9
9
  judgment. `/spec-connect` and `/spec-live` are **slash commands** instead — each
10
10
  pre-executes one `spec-env` verb and relays it, so only you can run them; a
11
11
  skill that wants one will tell you to type it.
12
12
 
13
+ **Seeing the work.** A phase is built in its own worktree, so `git diff` in your
14
+ terminal answers about the base branch. **`/spec-diff`** renders that worktree's
15
+ diff as a self-contained HTML page you open locally — or publish, and read on a
16
+ phone. The diff never passes through the model, so the page costs no context
17
+ tokens however large it is; the optional written review is the part that costs,
18
+ and it is offered rather than assumed. `/spec-next` writes the page at the end of
19
+ every phase.
20
+
21
+ **Handing the review back.** The page takes marks: tick `✓ accept` per file as
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**. Three buttons, each
24
+ carrying its own verdict:
25
+ `✓ Commit` commits it, `✓ Commit & Continue` commits and builds the next phase,
26
+ `↺ Request changes` sends it straight back to be worked, `… Discuss first` asks
27
+ you what's up. A **served** page hands the pass to
28
+ the engine, which **holds** it and shows a six-digit code. Type `/spec-reviewed`
29
+ and the waiting pass is picked up and acted on; paste the code after it
30
+ (`/spec-reviewed 324199`) to name one exactly, which matters only when two are
31
+ waiting. **Nothing pushes** — a device that reaches your page cannot reach this
32
+ conversation, which is what keeps a stray approval out of your review, and is
33
+ why the code is an address rather than a password. A `file://` page has no server to talk to, so it
34
+ copies and you paste, as before. Approve is unavailable while a note is open —
35
+ you asked for something, so it cannot also be fine — and it hands off to your
36
+ own commit skill (`review.commitWith`, `/commit` by default) rather than a copy
37
+ living here. Fixes come back as resolutions, so the next render shows each note
38
+ struck through with what changed. An accept remembers the file's content, so it
39
+ lapses by itself when that file changes again. The marks are information —
40
+ nothing gates on them; the verdict is the one thing you choose, once.
41
+
42
+ **One ending.** Every spec skill finishes with the same block — a verdict
43
+ (`✅` · `⚠️` · `❌` · `⏸`), then a table of the fields that skill declares,
44
+ ending on the one thing to do next. A refusal emits it too, so "nothing
45
+ happened" is reported rather than absent, and `Follow-ups` is always there
46
+ because a recorded `none` is a decision. Skills stay quiet while they run. The
47
+ shape lives in **`.claude/rules/spec-reports.md`**.
48
+
13
49
  The skill table, the spec type/folder conventions and the per-spec isolation
14
50
  model all live in **`.claude/rules/spec-planning.md`**, the canonical reference
15
51
  every spec skill points at. Tailor its per-phase test commands to this stack.
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Expose one spec's dev servers on the canonical localhost ports (or `main` to hand them back)
3
- argument-hint: "[spec|main]"
2
+ description: Expose one spec's dev servers on the canonical localhost ports bare uses the spec you are on, `main` hands them back
3
+ argument-hint: "[<spec> | main]"
4
4
  allowed-tools: Bash({{exec}} skitterspec spec-env connect:*)
5
5
  disable-model-invocation: true
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Put one spec live on the already-running dev server — `<spec>` takes it, a bare `take` takes the spec you are on, `main` hands the instance back
3
- argument-hint: "[<spec> | main | take | release | abort | status]"
2
+ description: Put one spec live on the already-running dev server — bare takes the spec you are on, `<spec>` takes that one, `main` hands the instance back
3
+ argument-hint: "[<spec> | main | status | release | abort]"
4
4
  allowed-tools: Bash({{exec}} skitterspec spec-env live:*)
5
5
  disable-model-invocation: true
6
6
  ---
@@ -20,9 +20,9 @@ Fresh installs can skip this section. **`MIGRATION.md` ships with the package**
20
20
  read the entry for the version you are coming from before upgrading a repo with a
21
21
  live mirror.
22
22
 
23
- **From 9.x — `push` now validates your issue states.** `spec-sync push` refuses
24
- to run until the configured `states` names have been checked against the
25
- workspace. `/spec-push` does that for you, so nothing changes if you drive sync
23
+ **From 9.x — the plan verb validates your issue states.** `spec-sync plan`
24
+ (named `spec-sync push` before v13) refuses to run until the configured `states`
25
+ names have been checked against the workspace. `/spec-push` does that for you, so nothing changes if you drive sync
26
26
  through the skill; a script calling the CLI directly must pass
27
27
  `--workspace-states <file>` (or `--skip-state-check`). See `MIGRATION.md`
28
28
  → "v9 → v10".
@@ -202,9 +202,14 @@ Two ways to get there:
202
202
  |---------|-----------|--------------|
203
203
  | `/spec-status` | — | Read-only drift report: what would push (create/update), and whether Linear's workflow-state drifted from the spec. Writes nothing. |
204
204
  | `/spec-push` | repo → Linear | Computes a create/update plan vs the last-pushed snapshot and applies it (issue description/state, phase sub-issues), stamping new ids back into the spec. |
205
+ | `/spec-claim` | repo → Linear | Take a spec over, `--release` it, or hand it `--to` a teammate. Records the owner in the spec, then pushes. Needs assignment enabled (below). |
206
+ | `/spec-list` | Linear → you | Read-only listing of every spec Linear holds — id, title, state, who holds it and the local spec name to start it by. Filters by state, backlog order, or assignee. Writes nothing. |
205
207
 
206
208
  Typical loop: edit the spec in-repo → `/spec-status` (what's pending) →
207
209
  `/spec-push` (send it up). There is no pull — Linear is a generated mirror.
210
+ `/spec-list` is the read side of that: it asks Linear what exists rather than
211
+ the repo, which is the only way to see a spec someone started on a branch you
212
+ have not got.
208
213
 
209
214
  ### What gets pushed
210
215
 
@@ -229,6 +234,19 @@ Sections listed in `sync.localOnlySections` (default: **State log**, **Changelog
229
234
  **Open questions**) are stripped from the pushed description — they never leave
230
235
  the repo.
231
236
 
237
+ ### Assignment (opt-in)
238
+
239
+ Add `"assignee": "push"` to `sync.fieldOwnership` and the spec issue is assigned
240
+ to whoever is building it: `/spec-start` records them, and the issue is released
241
+ automatically when the spec completes. Left out, nothing about it happens at all
242
+ — see **Assignment** in `linear.config.md` for the full rules.
243
+
244
+ You do not configure *who you are*: it comes from your own API key. Check it
245
+ with `skitterspec spec-sync whoami` (`--set` overrides it if the key is shared or
246
+ a bot's), and `skitterspec spec-sync users <name-or-email>` looks somebody up.
247
+ `skitterspec spec-sync doctor` reports the resolved identity, and stays quiet
248
+ about it in a project that has not opted in.
249
+
232
250
  ## 7. What to commit
233
251
 
234
252
  - **Commit** `specs/.core/linear-base/` — the last-pushed snapshots (content
@@ -25,9 +25,6 @@
25
25
  "enabled": true,
26
26
  "host": "127.0.0.1"
27
27
  },
28
- "open": {
29
- "command": ""
30
- },
31
28
  "registry": ".spec-env/registry.json",
32
29
  "branch": {
33
30
  "pattern": "{type}/{slug}",
@@ -41,6 +38,13 @@
41
38
  "refuseTeardownIfDirty": true,
42
39
  "refuseTeardownIfUnpushed": true
43
40
  },
41
+ "review": {
42
+ "reader": "detect",
43
+ "servePort": 7777,
44
+ "serveOnRemote": true,
45
+ "commitWith": "/commit",
46
+ "required": true
47
+ },
44
48
  "live": {
45
49
  "migrations": []
46
50
  }