@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.
- package/MIGRATION.md +208 -10
- package/README.md +32 -2
- package/assets/claude-md-section.md +38 -2
- package/assets/commands/spec-connect.md +2 -2
- package/assets/commands/spec-live.md +2 -2
- package/assets/core/SETUP.md +21 -3
- package/assets/core/env.config.json.example +6 -3
- package/assets/core/env.config.md +77 -30
- package/assets/core/linear.config.md +58 -0
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +198 -10
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +33 -5
- package/assets/skills/spec-bug/SKILL.md +172 -9
- package/assets/skills/spec-cancel/SKILL.md +98 -21
- package/assets/skills/spec-claim/SKILL.md +114 -0
- package/assets/skills/spec-complete/SKILL.md +94 -25
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +172 -11
- package/assets/skills/spec-init/SKILL.md +34 -7
- package/assets/skills/spec-linear-setup/SKILL.md +55 -1
- package/assets/skills/spec-list/SKILL.md +218 -0
- package/assets/skills/spec-next/SKILL.md +299 -6
- package/assets/skills/spec-push/SKILL.md +32 -8
- package/assets/skills/spec-review/SKILL.md +40 -5
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +386 -106
- package/assets/skills/spec-status/SKILL.md +24 -2
- package/assets/skills/spec-sync/SKILL.md +40 -4
- package/assets/skills/spec-to-main/SKILL.md +28 -6
- package/package.json +11 -7
- package/src/cli.js +1513 -89
- package/src/env/building.js +143 -0
- package/src/env/config.js +42 -9
- package/src/env/provision.js +54 -15
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +295 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/init.js +96 -1
- package/src/vendor/linear/api.js +104 -1
- package/src/vendor/linear/cli-sync.js +854 -17
- package/src/vendor/linear/config.js +8 -0
- package/src/vendor/linear/credentials.js +94 -0
- package/src/vendor/linear/doctor.js +35 -0
- package/src/vendor/linear/identity.js +105 -0
- package/src/vendor/linear/mcp.js +26 -0
- package/src/vendor/sync-core/index.js +6 -2
- package/src/vendor/sync-core/src/compare.js +49 -3
- package/src/vendor/sync-core/src/normalize.js +30 -0
- package/src/vendor/sync-core/src/push.js +11 -1
- package/src/vendor/sync-core/src/write.js +38 -0
- 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,
|
|
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
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`/spec-complete`
|
|
395
|
-
|
|
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/`.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|