@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
@@ -5,6 +5,11 @@ description: Investigate a bug, capture it as a Bug-type spec, and drive it red
5
5
 
6
6
  # /spec-bug — investigate a bug, prove it with a failing test, fix it
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
  This is the **bug** counterpart to `/spec` (which is for **features**, plan-only).
9
14
  Unlike `/spec`, this skill is hands-on and test-first: it reproduces the bug as a
10
15
  **failing test (RED)**, captures a lean Bug spec, then works the test to **GREEN**.
@@ -140,7 +145,7 @@ provision from — you'll flesh it out in §4:
140
145
  `## Symptom` you established above.
141
146
  - Run `skitterspec spec-env up bug-<name>` (the `spec-env` CLI engine). It prints
142
147
  the `git worktree add … -b bug/<name>` command (a branch forked from `main`),
143
- the worktree path, the opener, and any `in the worktree, run:` bootstrap steps.
148
+ the worktree path, and any `in the worktree, run:` bootstrap steps.
144
149
  - Run the printed commands in order. The plan **commits the stub first** — the
145
150
  worktree forks from `main`'s last commit, so the stub has to be in it — and
146
151
  then adds the worktree. Nothing to move afterwards: the spec is already there.
@@ -157,8 +162,8 @@ provision from — you'll flesh it out in §4:
157
162
  edits will prompt.
158
163
  - **Do everything below in the worktree**, on the branch — the red test, the fix,
159
164
  and the rest of the spec. Act on the worktree with absolute paths /
160
- `git -C <worktreePath>`, or open a fresh session rooted there (the printed
161
- opener). `main` changes only when the branch merges (at `/spec-complete`).
165
+ `git -C <worktreePath>`, or open a session rooted at the printed path. `main`
166
+ changes only when the branch merges (at `/spec-complete`).
162
167
 
163
168
  ## 3. Write the failing test FIRST (RED) — mandatory
164
169
 
@@ -271,7 +276,7 @@ does — you never create the issue by hand:
271
276
  2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
272
277
  Keep the chosen id for step 4.
273
278
  3. **Get the plan.**
274
- `skitterspec spec-sync push <spec> --workspace-states <file> --json > plan.json`
279
+ `skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
275
280
  — the spec is unlinked, so this plan is all-creates: the issue and one
276
281
  sub-issue per phase.
277
282
  4. **Apply it.**
@@ -304,6 +309,66 @@ and **never auto-push git** — Linear's own automation reacts to real branch/PR
304
309
  events later. Report the Linear issue URL as part of the skill's finish-up
305
310
  message.
306
311
 
312
+ **Only when all three hold**: `specs/.core/linear.config.json` exists, its
313
+ `sync.fieldOwnership` includes `assignee`, and the spec carries a
314
+ `linear_identifier`. Any one missing → skip this step silently and carry on; a
315
+ project that has not opted in must see no trace of assignment.
316
+
317
+ **Never blocks, never fails the skill.** Everything below is best-effort: the
318
+ branch is provisioned and the spec is moving either way, and an unassigned issue
319
+ is a cosmetic gap that `/spec-claim` closes later.
320
+
321
+ 1. **Work out who you are.** Run `skitterspec spec-sync whoami --json`.
322
+ - `ok: true` → use `id` and `name`. Nothing to ask.
323
+ - `source: "mcp"` or the command reports no API key → call the discovered
324
+ user-read tool with `me`, then cache it:
325
+ `skitterspec spec-sync whoami --set <id> --name "<name>"`.
326
+ - `ok: false` → identity is **unknown**, which is an ordinary state (a shared
327
+ or bot key, an offline machine). Go to step 2.
328
+ 2. **Unknown identity — three states, not two.** Decide by what is actually
329
+ reachable, and route the third to inaction:
330
+ - **Linear reachable *and* this is an interactive session** → offer a short
331
+ user search (`skitterspec spec-sync users <name-or-email>`, or the
332
+ user-list tool on MCP), let the operator pick, and offer to cache it with
333
+ `whoami --set` so this is asked once per machine rather than once per spec.
334
+ - **Not reachable, or not interactive** → say so in one line
335
+ (`assignment skipped — no Linear identity`) and **carry on**. Do not prompt
336
+ for something you could not act on, and never stall a `/spec-start` on it.
337
+ 3. **Record it on the spec** — through the engine, never by hand-editing
338
+ frontmatter:
339
+
340
+ ```
341
+ skitterspec spec-sync assign <spec> --to <user-id> --name "<display name>"
342
+ ```
343
+
344
+ 4. **Leave `> **Developer:**` as `git config user.name`** — the step above this
345
+ seam already set it, and this seam must not overwrite it with the tracker's
346
+ display name.
347
+
348
+ Those two names are the same person and often not the same string, and the
349
+ git one is the one everything else in the spec already uses: `Author:`, every
350
+ **State log** `By` row, and every commit. Writing the tracker's name into this
351
+ one field would leave a spec whose own audit trail contradicts its header —
352
+ a worse problem than the one it would solve. Anyone wanting a different name
353
+ on their specs sets `git config user.name`, and it stays consistent
354
+ everywhere.
355
+
356
+ The header names **who is building this**; the stamp in step 3 names
357
+ **which tracker account it is assigned to**. They answer different questions,
358
+ so they need not be the same string. The one place that reasoning does not hold
359
+ is handing a spec to *someone else* — there is no local git name for them —
360
+ and that case belongs to `/spec-claim --to`, which sets the header from the
361
+ tracker deliberately.
362
+
363
+ **Nothing is pushed here.** `assign` writes the repo only, and the refresh these
364
+ skills already run sends it. Assignment is an ordinary field of the projection,
365
+ not a side errand with its own network call.
366
+
367
+ **There is no unassign step anywhere.** The projection derives the assignee from
368
+ the spec's lifecycle bucket, so `/spec-complete` and `/spec-cancel` release the
369
+ issue through the push they already make. The stamp deliberately stays in the
370
+ file: who actioned the work outlives who is currently holding it.
371
+
307
372
  ### Picking the Linear Project
308
373
 
309
374
  Run this **only when minting a spec issue** — creating it for the first time. On
@@ -386,10 +451,108 @@ sub-issue jump from Backlog straight to Done, with nothing visible in between.
386
451
  - **Say what happened** in the skill's report: mirror updated, skipped as
387
452
  unlinked, or failed with the reason.
388
453
 
454
+ ## 5b. Render the page — then offer the review, never write it
455
+
456
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
457
+ present). Without it there is no worktree to read and this step does not exist —
458
+ skip it in silence rather than explaining an absence.
459
+
460
+ The fix is green and nothing is committed yet. That is the moment the page is
461
+ about, so render it now — **after** the tests pass and **before** the commit:
462
+
463
+ ```
464
+ skitterspec spec-env review <spec>
465
+ ```
466
+
467
+ **This is free.** The engine reads git and splices the patches into a template;
468
+ the diff never passes through you, so a 266KB patch costs nothing.
469
+
470
+ **Then offer `/spec-diff`. Do not run it.** The written review is the part that
471
+ costs — roughly **700 output tokens**, because writing it means reading the diff
472
+ — and that spend is the operator's call, not a default.
473
+
474
+ **The offer is the `Review` row of step 6's block** — the counts, the page link
475
+ and a question, in one row:
476
+
477
+ | **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
478
+
479
+ **It ends in a question, addressed to someone.** It was once a fenced block of
480
+ engine output, and it fired on every phase and was never once taken: two quoted
481
+ lines under the test counts, addressed to nobody, with the report then closing
482
+ on *"commit this first"* — the last instruction the reader got was to move on,
483
+ so they did. A row in a labelled table is findable; a question in it is
484
+ answerable. Both halves are load-bearing.
485
+
486
+ **Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
487
+ the page and the question stay in the same row: two adjacent rows about one page
488
+ make the reader resolve a distinction before acting on either. A later edit that
489
+ moves it out of the block, or separates the link from the question, undoes this
490
+ and should be read as a regression rather than tidying.
491
+
492
+
493
+ Relay the **`open:`** line rather than the bare path: a path is not clickable in
494
+ any terminal, and a page nobody can open is a page nobody reads.
495
+
496
+ - **Never write the review unasked**, and **never publish**. Publishing leaves
497
+ something behind that this tooling cannot remove, so it is always an ask. A
498
+ `file://` link is no use on a phone, and saying so **is** the ask —
499
+ `/spec-diff` §6 owns how.
500
+ **Follow the `reader:` line the engine printed — do not sniff for it.** It
501
+ answers where the person reading this is sitting, and the offer changes with it:
502
+
503
+ - **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
504
+ unknown is the ordinary state of a local machine, and a warning there is an
505
+ accusation against a healthy session.
506
+ - **`local`** — the `file://` URL.
507
+ - **`remote`** — the engine has already stood its local server up and put a URL
508
+ the reader can open on `open:`. So there is **nothing special to say**: relay
509
+ that line like any other. Any `also:` lines under it are the other addresses
510
+ this machine has, offered because the best-guess one can be wrong — pass them
511
+ on rather than editing them out.
512
+
513
+ **Never read an environment variable to decide this** — not `SSH_CONNECTION`,
514
+ not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
515
+ and in `--json`, and a second implementation here could not be tested and would
516
+ drift.
517
+
518
+ **Serving is the engine's to do; publishing is never.** A `remote` reader
519
+ authorises a local server — one process, ended by one flag, leaving nothing
520
+ behind — and authorises nothing else. Publishing leaves a page this tooling
521
+ cannot remove, so it stays an ask in every case, always. If the engine could not
522
+ serve (a busy port, a machine with no network address) it falls back to the
523
+ `file://` URL with its marker, and that is when publishing is worth naming.
524
+
525
+ - **Never fatal.** A failed render — no worktree, a git error — is one line, and
526
+ the fix is still done. The page is a convenience; the repo is the record.
527
+
389
528
  ## 6. Report
390
529
 
391
- Summarise: root cause, the failing→passing test, the fix, and the full test
392
- result. The spec stays in `in-progress`; suggest `/spec-complete` to verify and
393
- archive it (**when isolated**, the fix lives on the bug's branch, and
394
- `/spec-complete` merges it back to `main`). Do **not** `git commit` unless the
395
- user asks.
530
+ Do **not** `git commit` unless the user asks. The spec stays in `in-progress`;
531
+ `Next` names the commit and then `/spec-complete`, as
532
+ `/commit, then /spec-complete` this skill leaves the fix uncommitted, and
533
+ `/spec-complete` §2 refuses on pre-existing uncommitted changes, so naming only
534
+ the second half sends the reader into a refusal. `/spec-complete` then verifies
535
+ and archives it (**when isolated**, the fix lives on the bug's branch, and it
536
+ merges that back to `main`).
537
+
538
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
539
+ the shape; this section carries only what is specific here.
540
+
541
+ **Verdicts**
542
+
543
+ - `✅` — red→green, the fix is in and the suite passes.
544
+ - `⚠️` — green, with something worth knowing (a narrowed repro, a mirror that
545
+ did not refresh).
546
+ - `❌` — the test is still red, or a later test broke. Quote the failure.
547
+ - `⏸` — the bug could not be reproduced, so nothing was written. Say what was
548
+ tried; an unreproduced bug is a finding, not a failed run.
549
+
550
+ **Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
551
+ `Review` · `Follow-ups` · `Next`
552
+
553
+ `Cause` is the root cause in one clause and `Built` is the fix — the
554
+ failing→passing test belongs in `Tests`, named, so the evidence is a test name
555
+ rather than an adjective.
556
+
557
+ Step 5b's offer is the `Review` row, not a paragraph after the block — the
558
+ counts, the link and the question in one row. Nothing follows the block.
@@ -5,6 +5,11 @@ description: Cancel a spec — capture the reason, record final progress, stamp
5
5
 
6
6
  # /spec-cancel — record, stamp a reason, archive a 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
  ## 1. Identify the target spec
9
14
 
10
15
  - Use the name/path argument if given, else the spec **in context**. If unclear,
@@ -71,6 +76,12 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
71
76
  Do not roll anything back, and do not stop to ask.
72
77
  - **Say what happened** in the skill's report: mirror updated, skipped as
73
78
  unlinked, or failed with the reason.
79
+ - **There is no unassign step, and that is not an omission.** With `assignee` in
80
+ `sync.fieldOwnership`, the projection derives it from the spec's lifecycle
81
+ bucket — so moving the folder to `complete`/`cancelled` is *itself* what
82
+ releases the issue, and the push above carries it like any other field. The
83
+ `linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
84
+ they record who actioned the work, which outlives who is holding it.
74
85
 
75
86
  ### Why it sits here
76
87
 
@@ -81,17 +92,33 @@ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
81
92
  its folder bucket. Push while the folder is still in `in-progress/` and the
82
93
  issue is set to the state the spec is *leaving*.
83
94
  - **Before the commit**, because the push stamps ids into the spec and writes a
84
- snapshot under `specs/.core/`. The `git add specs/` that follows sweeps both up
85
- with the status change; push after it instead and those files are left
95
+ snapshot under `specs/.core/`. Push after the commit instead and both are left
86
96
  uncommitted, which makes `spec-env integrate` refuse to land the branch.
87
97
 
98
+ The snapshot is reached **by name**, not by breadth. It is a declared
99
+ `spec.companionPaths` entry (`specs/.core/linear-base/{identifier}.base.json`),
100
+ so `spec-env stage` returns it among this spec's owned paths and the commit below
101
+ names it. This is why the commit no longer stages `specs/` wholesale: the one
102
+ file outside the spec's folder that had to be swept up is addressable, so the
103
+ breadth bought nothing and cost another session's work.
104
+
88
105
  Then **commit the cancellation edits** — this skill wrote them, so it commits
89
- them:
106
+ them. Ask the engine which paths are this spec's, then stage and commit
107
+ **exactly those**:
90
108
 
91
109
  ```
92
- git add specs/ && git commit -m "chore(spec): cancel <name>"
110
+ skitterspec spec-env stage <name> # lists them; --json to consume
111
+ git add -- <the owned paths>
112
+ git commit -m "chore(spec): cancel <name>" -- <the owned paths>
93
113
  ```
94
114
 
115
+ **Never `git add specs/`.** That stages a *directory*, so a spec another
116
+ session is part-way through writing lands in this commit under this spec's
117
+ ticket. The `--` on the **commit** is the other half: a checkout has one
118
+ `.git/index`, shared by every session standing in it, so a bare `git commit`
119
+ takes whatever else is staged there however carefully you staged your own.
120
+ `.claude/rules/spec-planning.md` carries the full account.
121
+
95
122
  **This matters more here than anywhere else.** Teardown (step 7) refuses a dirty
96
123
  worktree and offers `--force` as the way through — and forcing would destroy the
97
124
  cancellation record this skill just wrote. Committing first means teardown never
@@ -99,7 +126,31 @@ needs `--force`. Do not `git push`.
99
126
 
100
127
  ## 6. Report
101
128
 
102
- Confirm the cancellation, the reason recorded, the new location, and the commit.
129
+ **The block is emitted when the run ends, not where this section sits.** The
130
+ sections below run after it, so their outcome belongs in the block — write it
131
+ once, at the end, with what actually happened.
132
+
133
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
134
+ the shape; this section carries only what is specific here.
135
+
136
+ **Verdicts**
137
+
138
+ - `✅` — stamped, moved to `cancelled`, committed, torn down.
139
+ - `⚠️` — cancelled, with something worth knowing: teardown declined, a tracker
140
+ refresh that failed, unpushed commits the operator chose to let go.
141
+ - `❌` — it acted and stopped part-way. Say what is where.
142
+ - `⏸` — no reason given, or no such spec. Nothing changed.
143
+
144
+ **Fields:** `Tracker` · `Spec` · `Worktree` · `Follow-ups` · `Next`
145
+
146
+ **The reason goes in the verdict clause** — `✅ /spec-cancel · feat-foo ·
147
+ superseded by feat-bar`. It is the one thing anyone reading this later wants,
148
+ and a cancellation is an ordinary successful run, so it must not be mistaken for
149
+ a `Why`: that field is for a run that did not do what it set out to.
150
+
151
+ `Worktree` says what was reclaimed, or that teardown was declined and the
152
+ worktree still stands — on a cancelled spec that worktree may hold the only copy
153
+ of the work, so an unreclaimed one is worth a line rather than a silence.
103
154
 
104
155
  ## 7. Tear down the environment (opt-in, only if configured)
105
156
 
@@ -110,22 +161,22 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
110
161
  1. If `.spec-env/connected` names this spec, run `skitterspec spec-env connect
111
162
  main` first to free the canonical ports.
112
163
  2. `skitterspec spec-env dev down <name>` — stop its host dev servers.
113
- **Standing in the worktree? Leave it before you tear it down.** If this
114
- session's cwd is inside the spec's own worktree, get out **first**, then run the
115
- teardown commands. How you leave depends on how you got in:
116
-
117
- - **`/spec-start` moved this session in** — the normal path in `worktree` mode.
118
- Call **`ExitWorktree`** with `action: "keep"`. It restores the session to the
119
- directory it started from *and* clears the caches still pointing at the
120
- worktree, which a bare `cd` does not: after a `cd` the session stays registered
121
- against the tree and asks you about it again when it ends.
122
- - **You opened the terminal yourself** `cd` to the primary checkout, as always.
123
- `ExitWorktree` is a no-op outside a session it moved, so trying it costs
124
- nothing, but the `cd` is what relocates you.
125
-
126
- **Always `keep`, never `remove`.** `ExitWorktree` refuses to remove a worktree
127
- entered by path in any case, and the `spec-env down` plan below has to stay the
128
- single thing that deletes a second deleter is how the teardown guards get
164
+ **Standing in the worktree? Leave it before you tear it down.** If this session's
165
+ cwd is inside the spec's own worktree the normal case in `worktree` mode, since
166
+ `/spec-start` moves you there get out **first**, then run the teardown commands.
167
+ One instruction covers it:
168
+
169
+ ```
170
+ cd <primary checkout>
171
+ ```
172
+
173
+ That is the whole mechanism, and it does not matter how you got in: the session
174
+ was moved by a `cd` and it leaves by one. There is no tool to call here, and none
175
+ should be reached for — the move in is a plain `cd` precisely because a tool that
176
+ asks for approval is unusable on a phone.
177
+
178
+ **`spec-env down` stays the single thing that deletes a worktree.** The plan
179
+ below is the only deleter, because a second one is how the teardown guards get
129
180
  bypassed.
130
181
 
131
182
  Not because git refuses — it does not. `git worktree remove` **succeeds** on the
@@ -140,6 +191,32 @@ only ordering that survives.
140
191
  remove the worktree/stack and free the slot. It respects the teardown guards
141
192
  (won't destroy a dirty/unpushed worktree without `--force`).
142
193
 
194
+ **When it refuses over unpushed commits, relay both ways out.**
195
+ A cancelled spec is normally unlanded, so this is the one moment in the
196
+ lifecycle where that guard genuinely fires — and it fires about real loss.
197
+ The worktree is the only copy of this work: the branch is on no remote and
198
+ not merged into the base branch, so removing it ends it. Say that plainly,
199
+ relay the engine's reason, and give both endings:
200
+
201
+ ```
202
+ publish it first — keeps the work reachable, then re-run /spec-cancel:
203
+ git -C <worktreePath> push -u origin <branch>
204
+
205
+ or accept the loss (the worktree and its commits go):
206
+ skitterspec spec-env down <name> --force
207
+ ```
208
+
209
+ **Print the push; never run it.** Publishing abandoned work to a shared
210
+ remote is the same unasked-for act this workflow took out of `/spec-start`,
211
+ and it is no more wanted here — someone may well want this branch to exist
212
+ nowhere but their own machine. Offer the command and wait for an answer.
213
+
214
+ **Never reach for `--force` yourself either.** The engine's own message names
215
+ only that half, which is the whole reason this step exists: meeting a wall
216
+ labelled *--force to tear down anyway* at the exact moment a backup is still
217
+ cheap is how work gets thrown away. Both options, then stop — the choice is
218
+ a decision about someone's work, and it is theirs.
219
+
143
220
  If the plan prints a `remote branch — confirm with the user first:` section,
144
221
  **ask before running that line** — it is a `git push <remote> --delete`, and
145
222
  the branch is merged, so it loses nothing. Usually there is nothing to ask:
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: spec-claim
3
+ description: Take ownership of a spec, hand it back, or give it to a teammate — the spec records who is building it and its Linear issue is assigned to them. Opt-in — needs specs/.core/linear.config.json with assignee in sync.fieldOwnership. Use when the user says "/spec-claim", "claim this spec", "take ownership of this", "I'm picking this up", "hand this back", or "assign this spec to someone".
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # /spec-claim — take a spec, hand it back, or hand it over
8
+
9
+ > Stay silent while this runs — speak only to ask something you cannot answer
10
+ > yourself, or to report a failure at the moment it happens. Read
11
+ > `.claude/rules/spec-reports.md` before reporting; it defines the block this
12
+ > skill ends with.
13
+
14
+ Ownership lives in the repo: the spec's frontmatter records who is building it,
15
+ and the push that follows tells Linear. This skill is how that record changes
16
+ after `/spec-start` has set it — a hand-off mid-flight, a spec picked up from
17
+ someone who moved on, or work a lead is distributing.
18
+
19
+ **Opt-in.** Needs `specs/.core/linear.config.json` *and* `assignee` in its
20
+ `sync.fieldOwnership`. If either is missing, say which one and stop — without the
21
+ field the stamp would sit in the file doing nothing.
22
+
23
+ ## 1. Identify the target spec
24
+
25
+ The argument, else the spec in flight for this session, else ask. Unlike
26
+ `/spec-next` this does not refuse a spec you are not standing in: reassigning a
27
+ colleague's spec from your own checkout is a normal thing to want, and it writes
28
+ no code.
29
+
30
+ ## 2. Work out the mode
31
+
32
+ | Invocation | Mode |
33
+ |------------|------|
34
+ | `/spec-claim [<spec>]` | **take** — you are building it |
35
+ | `/spec-claim [<spec>] --release` | **release** — nobody is |
36
+ | `/spec-claim [<spec>] --to <name-or-email>` | **hand over** — they are |
37
+
38
+ ### take
39
+
40
+ 1. `skitterspec spec-sync whoami --json`. If it answers, use that `id`/`name`.
41
+ If it reports `transport = mcp`, call the user-read tool with `me` and cache
42
+ the answer with `whoami --set <id> --name "<name>"`.
43
+ 2. **If the spec already records someone else, confirm before taking it** —
44
+ name them and say what will happen. Taking a colleague's spec by accident is
45
+ the one mistake here worth a round trip; every other path is reversible with
46
+ a second `/spec-claim`.
47
+ 3. Stamp it: `skitterspec spec-sync assign <spec> --to <id> --name "<name>"`.
48
+ 4. Set `> **Developer:**` to **`git config user.name`** — not the Linear display
49
+ name. You are taking this spec yourself, so the header should name you the
50
+ way `Author:`, the **State log** and your commits already do. The Linear name
51
+ belongs to the *assignment*, which step 3 just recorded; the two answer
52
+ different questions.
53
+
54
+ ### release
55
+
56
+ `skitterspec spec-sync assign <spec> --release`. This removes the stamp and, on
57
+ the next push, unassigns the Linear issue.
58
+
59
+ **It does not clear `> **Developer:**`** — that header records who *actioned*
60
+ the work, which outlives who is currently holding it. If nobody will pick the
61
+ spec up, `/spec-cancel` is the honest move; releasing only says "not me, for
62
+ now".
63
+
64
+ ### hand over (`--to`)
65
+
66
+ 1. **Resolve the person through a search, never a typed id.** Run
67
+ `skitterspec spec-sync users <name-or-email>` (or the user-list tool on MCP)
68
+ and **confirm the match** before writing. This is the one path where the repo
69
+ writes into somebody else's Linear inbox, so a mistyped id would assign a
70
+ stranger and nothing downstream would notice.
71
+ 2. On several matches, show them and let the user pick. On none, say so and
72
+ stop — do not fall back to the raw argument as an id.
73
+ 3. Stamp it with the resolved id and display name.
74
+ 4. Set `> **Developer:**` to that person's **Linear display name**.
75
+
76
+ This is the **one** place the tracker's name reaches the header, and the
77
+ exception is the whole reason it exists: there is no `git config user.name`
78
+ for somebody else's machine, so without it the header would go on crediting
79
+ you for work you just gave away. Everywhere else — `/spec-start`, the
80
+ `/spec-next` backfill, and taking a spec above — the git name wins, because
81
+ there the person *is* you and the rest of the spec already names you that way.
82
+
83
+ ## 3. Record it in the spec
84
+
85
+ Add a dated line to the **Changelog** in `00-overview.md` — who it moved to (or
86
+ from), and why if the user said. Ownership is a course-correction, not a
87
+ lifecycle transition, so it belongs there and **not** in the State log.
88
+
89
+ ## 4. Push
90
+
91
+ Run `/spec-push`. The stamp is repo state, and the mirror catches up like any
92
+ other edit.
93
+
94
+ **If the push fails, the claim still stands.** Say so and stop: the repo is
95
+ correct, the mirror is disposable, and the next push repairs it. Do not roll the
96
+ stamp back — that would throw away the one durable half of the change.
97
+
98
+ ## 5. Report
99
+
100
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
101
+ the shape; this section carries only what is specific here.
102
+
103
+ **Verdicts**
104
+
105
+ - `✅` — the spec records the new owner and Linear agrees.
106
+ - `⚠️` — the spec records it; Linear does not yet. The spec file is the record
107
+ and the ticket is the mirror, so this is a lag rather than a conflict — say
108
+ which way round it is.
109
+ - `⏸` — nothing to claim, or no provider configured.
110
+
111
+ **Fields:** `Tracker` · `Follow-ups` · `Next`
112
+
113
+ `Tracker` is who owns it now and whether Linear agrees yet — the whole answer,
114
+ in one row.