@skitterbyte/skitterspec-linear 11.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 (60) hide show
  1. package/MIGRATION.md +260 -10
  2. package/README.md +32 -2
  3. package/assets/claude-md-section.md +48 -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 +9 -3
  8. package/assets/core/env.config.md +102 -30
  9. package/assets/core/gating.config.json.example +4 -0
  10. package/assets/core/gating.config.md +81 -0
  11. package/assets/core/linear.config.md +67 -8
  12. package/assets/review/page.html +1501 -0
  13. package/assets/rules/spec-planning.md +224 -15
  14. package/assets/rules/spec-reports.md +269 -0
  15. package/assets/skills/spec/SKILL.md +64 -13
  16. package/assets/skills/spec-bug/SKILL.md +193 -27
  17. package/assets/skills/spec-cancel/SKILL.md +99 -8
  18. package/assets/skills/spec-claim/SKILL.md +114 -0
  19. package/assets/skills/spec-complete/SKILL.md +123 -22
  20. package/assets/skills/spec-diff/SKILL.md +564 -0
  21. package/assets/skills/spec-hotfix/SKILL.md +202 -22
  22. package/assets/skills/spec-init/SKILL.md +49 -9
  23. package/assets/skills/spec-linear-setup/SKILL.md +86 -7
  24. package/assets/skills/spec-list/SKILL.md +218 -0
  25. package/assets/skills/spec-next/SKILL.md +300 -7
  26. package/assets/skills/spec-push/SKILL.md +45 -22
  27. package/assets/skills/spec-review/SKILL.md +59 -11
  28. package/assets/skills/spec-reviewed/SKILL.md +241 -0
  29. package/assets/skills/spec-start/SKILL.md +426 -66
  30. package/assets/skills/spec-status/SKILL.md +24 -2
  31. package/assets/skills/spec-sync/SKILL.md +47 -11
  32. package/assets/skills/spec-to-main/SKILL.md +42 -20
  33. package/package.json +11 -7
  34. package/src/cli.js +1710 -80
  35. package/src/env/building.js +143 -0
  36. package/src/env/classify.js +91 -0
  37. package/src/env/config.js +57 -9
  38. package/src/env/provision.js +192 -19
  39. package/src/env/proxy.js +34 -1
  40. package/src/env/render.js +3 -12
  41. package/src/env/resolve.js +296 -9
  42. package/src/env/review.js +1329 -0
  43. package/src/env/serve.js +549 -0
  44. package/src/env/teardown.js +13 -6
  45. package/src/gating.js +155 -0
  46. package/src/init.js +124 -2
  47. package/src/prompts.js +10 -1
  48. package/src/vendor/linear/api.js +104 -1
  49. package/src/vendor/linear/cli-sync.js +874 -17
  50. package/src/vendor/linear/config.js +8 -0
  51. package/src/vendor/linear/credentials.js +94 -0
  52. package/src/vendor/linear/doctor.js +35 -0
  53. package/src/vendor/linear/identity.js +105 -0
  54. package/src/vendor/linear/mcp.js +26 -0
  55. package/src/vendor/sync-core/index.js +6 -2
  56. package/src/vendor/sync-core/src/compare.js +74 -5
  57. package/src/vendor/sync-core/src/normalize.js +30 -0
  58. package/src/vendor/sync-core/src/push.js +11 -1
  59. package/src/vendor/sync-core/src/write.js +38 -0
  60. package/LICENSE +0 -21
@@ -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.
@@ -5,6 +5,11 @@ description: Finish a spec — verify all phases are genuinely done, update prog
5
5
 
6
6
  # /spec-complete — verify, finalise, 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,
@@ -34,6 +39,23 @@ Before marking complete, confirm the work is actually finished:
34
39
  is its own, and step 4 commits that.
35
40
 
36
41
 
42
+ ### Release gating (only when configured)
43
+
44
+ **Only when `specs/.core/gating.config.json` exists.** Run
45
+ `skitterspec gating check <spec>` and report what it says. This is the last point
46
+ at which the question can still be answered cheaply, which is exactly why it is
47
+ asked here — a spec that reaches `Complete` with no decision recorded is the gap
48
+ the feature exists to close.
49
+
50
+ **Never refuse to complete over it.** Say plainly that no decision was recorded,
51
+ offer to record one now, and finish either way. The work is done; withholding the
52
+ status flip would punish the wrong thing.
53
+
54
+ **This check is advisory.** It reports; it never refuses, and nothing below is
55
+ conditional on it. A spec written before the project adopted gating has no header
56
+ and is not broken — turning this into a gate would accuse the very specs the
57
+ feature was designed not to disturb.
58
+
37
59
  ## 3. Update the spec
38
60
 
39
61
  - Tick all completed tasks in the phase files; flip every finished phase-file
@@ -73,6 +95,12 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
73
95
  Do not roll anything back, and do not stop to ask.
74
96
  - **Say what happened** in the skill's report: mirror updated, skipped as
75
97
  unlinked, or failed with the reason.
98
+ - **There is no unassign step, and that is not an omission.** With `assignee` in
99
+ `sync.fieldOwnership`, the projection derives it from the spec's lifecycle
100
+ bucket — so moving the folder to `complete`/`cancelled` is *itself* what
101
+ releases the issue, and the push above carries it like any other field. The
102
+ `linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
103
+ they record who actioned the work, which outlives who is holding it.
76
104
 
77
105
  ### Why it sits here
78
106
 
@@ -83,25 +111,74 @@ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
83
111
  its folder bucket. Push while the folder is still in `in-progress/` and the
84
112
  issue is set to the state the spec is *leaving*.
85
113
  - **Before the commit**, because the push stamps ids into the spec and writes a
86
- snapshot under `specs/.core/`. The `git add specs/` that follows sweeps both up
87
- with the status change; push after it instead and those files are left
114
+ snapshot under `specs/.core/`. Push after the commit instead and both are left
88
115
  uncommitted, which makes `spec-env integrate` refuse to land the branch.
89
116
 
117
+ The snapshot is reached **by name**, not by breadth. It is a declared
118
+ `spec.companionPaths` entry (`specs/.core/linear-base/{identifier}.base.json`),
119
+ so `spec-env stage` returns it among this spec's owned paths and the commit below
120
+ names it. This is why the commit no longer stages `specs/` wholesale: the one
121
+ file outside the spec's folder that had to be swept up is addressable, so the
122
+ breadth bought nothing and cost another session's work.
123
+
90
124
  Then **commit the completion edits** — steps 3–4 are this skill's own output, so
91
- it finishes its own work rather than handing you a dirty tree:
125
+ it finishes its own work rather than handing you a dirty tree. Ask the engine
126
+ which paths are this spec's, then stage and commit **exactly those**:
92
127
 
93
128
  ```
94
- git add specs/ && git commit -m "chore(spec): complete <name>"
129
+ skitterspec spec-env stage <name> # lists them; --json to consume
130
+ git add -- <the owned paths>
131
+ git commit -m "chore(spec): complete <name>" -- <the owned paths>
95
132
  ```
96
133
 
97
- Step 2 established the tree was otherwise clean, so this commits exactly the
98
- status flip and the move nothing of yours rides along. **This is what lets step
99
- 6 land:** `integrate` refuses a dirty worktree, so without committing here the
100
- skill would block on the very edits it just made. Do not `git push`.
134
+ **Never `git add specs/`.** That stages a *directory*, so a spec another
135
+ session is part-way through writing lands in this commit under this spec's
136
+ ticket. The `--` on the **commit** is the other half: a checkout has one
137
+ `.git/index`, shared by every session standing in it, so a bare `git commit`
138
+ takes whatever else is staged there however carefully you staged your own.
139
+ `.claude/rules/spec-planning.md` carries the full account.
140
+
141
+ So this commits exactly the status flip and the move, and nothing of yours — or
142
+ of anyone else's — rides along.
143
+ **This is what lets step 6 land:** `integrate` refuses a dirty worktree, so
144
+ without committing here the skill would block on the very edits it just made.
145
+ Do not `git push`.
101
146
 
102
147
  ## 5. Report
103
148
 
104
- Confirm the move, the commit, the final test result, and list anything deferred.
149
+ **The block is emitted when the run ends, not where this section sits.** The
150
+ sections below run after it, so their outcome belongs in the block — write it
151
+ once, at the end, with what actually happened.
152
+
153
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
154
+ the shape; this section carries only what is specific here.
155
+
156
+ **Verdicts**
157
+
158
+ - `✅` — every phase done, tests green, landed, torn down.
159
+ - `⚠️` — finished, with something worth knowing: teardown declined, a tracker
160
+ refresh that failed, a hotfix tag still waiting to be pushed.
161
+ - `❌` — it acted and stopped part-way — a conflicted rebase, a failed
162
+ cherry-pick, a red base after landing. Say what state the repo is in and quote
163
+ the failure; there is a mess to clear and the reader needs to know where.
164
+ - `⏸` — phases are unfinished, tests are red, or the tree is dirty. Nothing
165
+ moved.
166
+
167
+ **Fields:** `Tracker` · `Spec` · `Tests` · `Landed` · `Worktree` ·
168
+ `Follow-ups` · `Next`
169
+
170
+ **Report this spec and no other.** A completion is the moment it is most
171
+ tempting to survey what is left — what else is in flight, what is in the
172
+ backlog, which worktrees are still standing — and it is the moment it helps
173
+ least: the reader has just finished something and cannot tell whether a line
174
+ about another spec followed from it. `Next` is one action for this spec, and
175
+ `/spec-list` is where the wider picture lives if they want it.
176
+
177
+ `Landed` says how, because the two ways differ: a fast-forward names the base
178
+ and the commit, a hotfix names the deploy tag and the `main` cherry-pick — and
179
+ a deploy tag that has not been pushed is a `⚠️`, not a `✅`. `Worktree` says
180
+ what was reclaimed, including any orphaned volumes swept, or that teardown was
181
+ declined and the worktree still stands.
105
182
 
106
183
  ## 6. Land the branch (opt-in, only if isolated)
107
184
 
@@ -203,9 +280,23 @@ worktree and branch are being kept, and go straight to sub-step 4. Mention
203
280
  canonical ports go back to the primary checkout.
204
281
  2. **Stop its host dev servers:** `skitterspec spec-env dev down <name>` (a
205
282
  no-op when none are running / configured).
206
- **Standing in the worktree? Leave it before you tear it down.** If this
207
- session's cwd is inside the spec's own worktree, `cd` to the primary checkout
208
- **first**, then run the teardown commands.
283
+ **Standing in the worktree? Leave it before you tear it down.** If this session's
284
+ cwd is inside the spec's own worktree — the normal case in `worktree` mode, since
285
+ `/spec-start` moves you there — get out **first**, then run the teardown commands.
286
+ One instruction covers it:
287
+
288
+ ```
289
+ cd <primary checkout>
290
+ ```
291
+
292
+ That is the whole mechanism, and it does not matter how you got in: the session
293
+ was moved by a `cd` and it leaves by one. There is no tool to call here, and none
294
+ should be reached for — the move in is a plain `cd` precisely because a tool that
295
+ asks for approval is unusable on a phone.
296
+
297
+ **`spec-env down` stays the single thing that deletes a worktree.** The plan
298
+ below is the only deleter, because a second one is how the teardown guards get
299
+ bypassed.
209
300
 
210
301
  Not because git refuses — it does not. `git worktree remove` **succeeds** on the
211
302
  tree you are standing in, and that is the problem: the directory vanishes under
@@ -224,16 +315,26 @@ only ordering that survives.
224
315
  worktree without `--force`), so if it *does* refuse, relay that and stop
225
316
  rather than reaching for `--force`.
226
317
 
227
- **If the plan prints a `remote branch confirm with the user first:`
228
- section, ask before running it.** `/spec-start` pushed this branch when it
229
- provisioned, so the remote copy outlives teardown unless someone deletes it.
230
- The planner only ever offers this for a branch that has **landed**, so say so
231
- plainly when you ask the commits are on the base branch (or captured by the
232
- deploy tag), so deleting the remote branch loses nothing. On a yes, run the
233
- printed `git push <remote> --delete <branch>`; on a no, leave it and say the
234
- remote branch is still there. Never fold it into the `run these:` batch — a
235
- project that wants it unattended sets `teardown.deleteRemoteBranch: "always"`
236
- in `env.config.json`, and then it appears in `run these:` instead of here.
318
+ **The unpushed half of that guard cannot fire here, by construction.**
319
+ Step 6 landed the branch, so `merged` is true and `planDown` skips the check
320
+ entirely (`env/teardown.js`) completing a spec never meets it, however the
321
+ branch was or was not published. `/spec-cancel` is where it does fire, because
322
+ a cancelled spec is unlanded and its worktree really is the only copy; the
323
+ publish-or-`--force` path therefore lives in that skill and this one needs no
324
+ equivalent. A refusal reaching this step is about a **dirty tree** instead,
325
+ which means something wrote into the worktree after the landing read it,
326
+ don't force it.
327
+
328
+ **If the plan prints a `remote branch — confirm with the user first:` section, ask before running it.**
329
+ Nothing published this branch for you, so a remote copy exists only because
330
+ you pushed it by hand — and it outlives teardown unless someone deletes it. The planner only ever offers
331
+ this for a branch that has **landed**, so say so plainly when you ask — the
332
+ commits are on the base branch (or captured by the deploy tag), so deleting
333
+ the remote branch loses nothing. On a yes, run the printed
334
+ `git push <remote> --delete <branch>`; on a no, leave it and say the remote
335
+ branch is still there. Never fold it into the `run these:` batch — a project
336
+ that wants it unattended sets `teardown.deleteRemoteBranch: "always"` in
337
+ `env.config.json`, and then it appears in `run these:` instead of here.
237
338
  4. **Reap orphaned test-DB volumes:** run `skitterspec spec-env prune`. It lists
238
339
  Docker volumes in the repo namespace that belong to **no live spec** (no
239
340
  worktree) — leftovers from declined/aborted teardowns, manual