@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
@@ -5,22 +5,30 @@ description: Put a spec in flight — provision its branch, move it to in-progre
5
5
 
6
6
  # /spec-start — put a spec in flight
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
  One checkout, one spec in flight. This skill is how a spec gets there:
9
14
  provision, move it to `in-progress`, refresh the tracker, then hand straight on
10
15
  to `/spec-next` for phase 1. Continuing a spec afterwards is `/spec-next`;
11
16
  finishing it is `/spec-complete`.
12
17
 
13
- ## 1. The gate — refuse unless the workbench is free
18
+ ## 1. The gate — what each mode demands of the tree
14
19
 
15
20
  **Check this first, before resolving anything or touching a file.** What the
16
21
  gate demands depends on the mode, because the two modes hold work in different
17
22
  places — read `mode` from `specs/.core/env.config.json` (default `worktree`).
18
23
 
19
- **`worktree` mode — the tree must be clean, and that is all.** The spec is built
20
- in its own worktree, so another spec being in flight is not a conflict; it is the
21
- parallelism the mode exists for. The only requirement is that this checkout has
22
- no uncommitted work *except* the spec you are starting, which `spec-env up`
23
- commits for you (see below). Nothing is switched here and nothing is parked.
24
+ **`worktree` mode — there is no tree gate.** The spec is built in its own
25
+ worktree, so neither another spec being in flight nor its uncommitted files are a
26
+ conflict; both are the parallelism the mode exists for. `git worktree add`
27
+ carries nothing and forks from a commit, and the one thing this run writes into
28
+ the checkout the spec's own commit, which `spec-env up` plans for you — names
29
+ its paths on both the `add` and the `commit`, so it cannot reach a file that is
30
+ not this spec's. Work belonging to someone else is **reported and left alone**,
31
+ never a refusal. Nothing is switched here and nothing is parked.
24
32
 
25
33
  **`checkout` mode — the workbench must be free**: on the base branch (`main`, or
26
34
  the configured `baseBranch`) and clean, since the branch is built right here and
@@ -37,21 +45,30 @@ and a half-built phase are each a decision someone must make deliberately — an
37
45
  the cost of guessing is another spec's work moved without its author asking. A
38
46
  refusal costs one command; the alternative can cost an afternoon.
39
47
 
40
- **The one exception is the spec you are starting.** `spec-env up` classifies the
41
- uncommitted tree against the target spec and answers one of three ways — relay
42
- what it says rather than deciding for yourself:
48
+ **What `spec-env up` does with the tree**, in both modes relay what it says
49
+ rather than deciding for yourself:
43
50
 
44
- | What it found | What it does |
45
- |---------------|--------------|
46
- | clean | provisions, as always |
47
- | every path belongs to this spec | plans `git add` + `git commit` **first**, then the fork |
48
- | any path does not | refuses, naming the paths that disqualified it |
51
+ | What it found | `worktree` | `checkout` |
52
+ |---------------|-------------|-------------|
53
+ | clean | provisions | provisions |
54
+ | every path is this spec's | commits those paths **first**, then forks | same, then switches |
55
+ | some path is not | commits this spec's, provisions, **reports the rest** | refuses, naming them |
49
56
 
50
57
  That is membership in an exactly-known set — the spec's own folder plus the
51
58
  project's `spec.companionPaths` — and **not** a judgement about whether the
52
- changes look important. The gate still never decides that. When it plans the
53
- commit, the paths are printed above the commands, so run them as printed; when it
54
- refuses, relay the reason and stop.
59
+ changes look important. It never decides that.
60
+
61
+ **The last row is the only real difference, and it is mechanical.**
62
+ `git switch -c` carries the working tree onto the new branch, so in
63
+ `checkout` mode a colleague's files really would be moved without them asking.
64
+ `git worktree add` carries nothing, so in `worktree` mode the same files are
65
+ simply not this run's business — and refusing over them fired on the commonest
66
+ tree this workflow produces: a second spec authored while the first is still
67
+ uncommitted. When it reports them, say how many and whose in the `Untouched` row
68
+ and **keep the verdict `✅`** — nothing went wrong.
69
+
70
+ When it plans the commit, the paths are printed above the commands, so run them
71
+ as printed; when it refuses, relay the reason and stop.
55
72
 
56
73
  It also refuses a **clean** tree whose spec is not in the commit the worktree
57
74
  would fork from — otherwise you get a branch missing the very spec it is for.
@@ -71,69 +88,131 @@ would fork from — otherwise you get a branch missing the very spec it is for.
71
88
  probably still exists, so this is a re-attach: say so rather than reporting a
72
89
  fresh start, and skip the housekeeping that is already done.
73
90
 
91
+ ## 2b. Bring the review server up — from here, before anything else
92
+
93
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
94
+ present). Without it there is no review server and this step does not exist —
95
+ skip it in silence rather than explaining an absence.
96
+
97
+ ```
98
+ skitterspec spec-env review serve --host 0.0.0.0
99
+ ```
100
+
101
+ **Here is the point.** Right now this session is standing in the
102
+ **primary checkout**, and in a moment step 3 `cd`s into a worktree and stays
103
+ there. A
104
+ daemon started after that `cd` is started *by the worktree's copy of the code* —
105
+ and when `/spec-complete` removes that worktree, the daemon keeps answering on
106
+ its port and fails on every page it is asked for, for every spec. The engine
107
+ defends against that now, but the cheapest fix is to never create the situation:
108
+ start it while you are still somewhere that outlives the spec.
109
+
110
+ It is also what lets **several specs be reviewed at once**. Reviews all render
111
+ into the primary checkout's `.spec-env/reviews/`, so one server serves every
112
+ provisioned spec — including specs another agent is building in another
113
+ worktree. Starting it here means that one server belongs to the checkout none of
114
+ them can delete.
115
+
116
+ **Say nothing when it is already up.** The usual outcome is adoption — a server
117
+ is running and this changes nothing — and a line per `/spec-start` about a
118
+ daemon nobody asked about is the narration `.claude/rules/spec-reports.md`
119
+ forbids. Speak only if it could not start.
120
+
121
+ **Never fatal, never a gate.** A busy port, no network address, a refused
122
+ spawn — say it in one line and **carry on**; provisioning is not conditional on
123
+ it, and the page falls back to its `file://` URL exactly as it does today.
124
+
125
+ **`--host 0.0.0.0` is the deliberate half.** It binds the server to this
126
+ machine's network addresses so the page opens on a phone, and the engine mints a
127
+ token with that bind as its only guard. On a machine you would rather not expose,
128
+ drop the flag — the server is then reachable from this machine alone and the
129
+ render says so. On **`--plan`** this step does not run at all.
130
+
74
131
  ## 3. Build its branch
75
132
 
76
133
  ### `worktree` mode
77
134
 
135
+ **One path. There is no branching here and none should be added back.**
136
+
78
137
  1. **Provision.** Run `skitterspec spec-env up <name>` — a planner, so run the
79
- `to provision, run:` commands it prints and confirm they succeeded, **except**
80
- the `open.command` line, which belongs to the hand-off in 3 below.
81
-
82
- 2. **Enter the worktree this session, no new window.** The spec is built in its
83
- worktree; that is what the mode is for, and it is why `main` stays free and
84
- several specs run at once. So put *this* session there rather than opening
85
- another one: call **`EnterWorktree`** with the worktree path.
86
-
87
- **Do it immediately after `git worktree add`, before bootstrap and before the step 4 housekeeping.**
88
- Once the session is the worktree, `setup` runs in place and the housekeeping
89
- is plain `git` the `cd` and the `git -C <worktreePath>` prefix both
90
- disappear. **Never `cd` into the worktree first:** `EnterWorktree` refuses a
91
- path that is already the working directory
92
- (`is the current working directory`), so a bootstrap `cd` does not merely
93
- make the call redundant, it makes it fail.
94
-
95
- Then run the planner's **`then, in the worktree, run:`** steps in order (file
96
- seeding, then `setup`) — a fresh worktree has no dependencies and none of the
97
- repo's gitignored files, so hooks, typechecks and tests fail until they are
98
- there. Do the step 4 housekeeping, then say to run **`/spec-next`**, here.
99
-
100
- **Do not move the branch into this checkout**, and do not ask the operator to.
101
- Entering the worktree is the opposite of moving the branch out of it — the
102
- branch never leaves. `/spec-live` is for testing a finished-enough spec on the
103
- already-running dev server; it is not the way work gets started, and reaching
104
- for it here is what used to split a start across two invocations.
105
-
106
- 3. **When you cannot enter, hand off as before.** Two cases, and
107
- **decide from cwd before calling**, not by calling and catching — an error
108
- surfaced mid-skill reads as a bug, and both conditions are knowable in
109
- advance:
110
-
111
- - **This session's cwd is already inside a worktree.** `EnterWorktree` only
112
- allows a worktree→worktree switch when the target lives under
113
- `.claude/worktrees/`, and a project's worktree root usually does not
114
- (`../{repo}-wt` is the shipped shape). Do not "fix" this by relocating the
115
- root: starting a spec from the base branch is the documented path anyway.
116
- - **`EnterWorktree` is unavailable** — a harness without it. There is no CLI
117
- counterpart to fall back on, so the skill degrades rather than fails.
118
-
119
- On either, do exactly what this skill did before: bootstrap with
120
- `cd "<worktreePath>"`, housekeep with `git -C <worktreePath>`, run
121
- `open.command` if one is configured, print the worktree path, and say to run
122
- **`/spec-next`** from a session in it.
123
- **Trust the worktree first** on this path: `spec-env up` wrote the printed
124
- `trusted:` root into `.claude/settings.local.json`, but that file will not
125
- hot-reload in this session — run `/add-dir <trusted root>` before editing into
126
- the worktree, or the first write prompts. (Entering the worktree makes both
127
- moot: the writes are then in-cwd.)
128
-
129
- `EnterWorktree` is gated on being told to work in a worktree by the user or by
130
- project instructions. A lifecycle skill directing it **is** project
131
- instruction, so the call is in contract here.
132
-
133
- **`/spec-next` is unchanged by this.** Its rule 2 "the worktree you are
134
- standing in" is what answers once the session has actually moved; nothing about
135
- its resolution is loosened, and it must not be. The refusal exists so the wrong
136
- branch is never built.
138
+ `to provision, run:` commands it prints and confirm they succeeded.
139
+
140
+ 2. **Trust the worktree.** `spec-env up` wrote the printed `trusted:` root into
141
+ `.claude/settings.local.json`, but that file does not hot-reload in this
142
+ session run **`/add-dir <trusted root>`** before editing into the worktree,
143
+ or the first write prompts. This is not tab machinery: worktrees live outside
144
+ the checkout, and the trust entry is what stops the prompt.
145
+
146
+ 3. **Bootstrap it, and move into it.** The `cd` is in the command itself, and it
147
+ **moves this session** — that is what it is for, not a side effect of it. The
148
+ Bash working directory persists between calls, so from here on this session is
149
+ standing in the worktree, which is what lets a bare `/spec-next` resolve the
150
+ spec on its own:
151
+
152
+ ```
153
+ cd "<worktreePath>" && <the planner's "then, in the worktree, run:" steps>
154
+ ```
155
+
156
+ Run the seeding steps before `setup`: a fresh worktree has no dependencies and
157
+ none of the repo's gitignored files, so hooks, typechecks and tests fail until
158
+ both have happened.
159
+
160
+ **Then confirm the move landed never assume it.** Ask for a positive signal
161
+ rather than reading silence as success (`.claude/rules/negative-checks.md`
162
+ rule 1): run `skitterspec spec-env resolve` with **no argument** and read the
163
+ `spec:` line it prints.
164
+
165
+ ```
166
+ skitterspec spec-env resolve # must name this spec
167
+ ```
168
+
169
+ Three states, not two. It names this spec → carry on. It names something else,
170
+ or resolves nothing → **the `cd` did not take**. Say so plainly and fall back
171
+ to the stop-here ending in step 6, printing the path so the operator can open
172
+ a session there themselves; do not build a phase from a session whose location
173
+ you could not confirm. A failed `cd` leaves you in the primary checkout on the
174
+ base branch, where a phase's worth of code looks entirely normal at the time.
175
+
176
+ 4. **Housekeep with `git -C <worktreePath>`** step 4 below, against the
177
+ worktree.
178
+
179
+ **Keep the `-C` prefix**, even though the session is inside the worktree now
180
+ and a bare `git` would usually do the same thing. It is immune to the one
181
+ failure this sequence can have — a `cd` that silently did not take — where a
182
+ bare `git` would instead write the spec's move into the primary checkout on
183
+ the base branch. It costs nothing and removes a whole failure mode, so do not
184
+ tidy it away.
185
+
186
+ 5. **Print the worktree path.** What happens next is step 6 — it is offered
187
+ there, not decided here.
188
+
189
+ **The session moves into the worktree, and nothing opens a window.** Those are
190
+ two different claims and both are load-bearing. The move is real, and the `cd` in
191
+ step 3 is its whole mechanism — no tool call, because an approval prompt is
192
+ unusable on a phone and leaves the session stuck. Nothing is *spawned*: no new
193
+ terminal, no tab, no editor sent anywhere, because that machinery had nothing
194
+ left to do and was removed deliberately.
195
+
196
+ **Do not move the branch into this checkout**, and do not ask the operator to.
197
+ `/spec-live` is for testing a finished-enough spec on the already-running dev
198
+ server; it is not how work gets started.
199
+
200
+ **`/spec-next`'s refusal is unchanged by this.** Its rule 2 — "the worktree you
201
+ are standing in" — is what answers afterwards, and step 3 is what puts the session
202
+ there; nothing about rules 1 to 3 is loosened, and it must stay that way, because
203
+ the refusal exists so the wrong branch is never built. What changed is where the
204
+ session stands, not how weakly the rules read: a bare `/spec-next` typed from
205
+ somewhere that is neither a worktree nor a live checkout still refuses exactly as
206
+ it did. `--worktree <path>` survives untouched beside it — it answers before those
207
+ rules and cannot be reached by guessing.
208
+
209
+ **And the `cd` is a convenience, not the only thing holding this together.**
210
+ `/spec-next`'s **rule 4** asks the engine for the sole provisioned spec, so a
211
+ bare `/spec-next` resolves this spec from anywhere in the repo — after a
212
+ `/clear`, from a new tab, tomorrow morning. That is what makes step 6's
213
+ stop-here ending an honest offer rather than a promise only this session can
214
+ keep. Do not delete rule 4 as redundant with the `cd`: the `cd` is session state,
215
+ and rule 4 is what is left once it is gone.
137
216
 
138
217
  ### `checkout` mode
139
218
 
@@ -145,20 +224,149 @@ refusal and stop.
145
224
  ## 4. Move the spec into development
146
225
 
147
226
  **Do this before you report anything**, so no path can end with a provisioned
148
- worktree and a spec still reading `Ready` in `specs/backlog/`. Having entered the
149
- worktree (step 3.2) this is plain `git` you are standing in it. On the hand-off
150
- path (step 3.3) run it against the worktree with `git -C <worktreePath>` instead;
151
- in `checkout` mode the branch is already here.
227
+ worktree and a spec still reading `Ready` in `specs/backlog/`. In `worktree` mode
228
+ run it against the worktree with `git -C <worktreePath>`; in `checkout` mode the
229
+ branch is already here.
152
230
 
153
231
  - `git mv "specs/backlog/<name>" "specs/in-progress/<name>"` if it isn't there
154
232
  already (`mkdir -p specs/in-progress` first). Use `git mv` to keep history.
155
233
  - Set the **Status** header: `> **Status:** In Progress — Phase 1 (started <YYYY-MM-DD>)`.
156
234
  - Set **Developer** if it is still `—` (`git config user.name`).
235
+
236
+ **Only when all three hold**: `specs/.core/linear.config.json` exists, its
237
+ `sync.fieldOwnership` includes `assignee`, and the spec carries a
238
+ `linear_identifier`. Any one missing → skip this step silently and carry on; a
239
+ project that has not opted in must see no trace of assignment.
240
+
241
+ **Never blocks, never fails the skill.** Everything below is best-effort: the
242
+ branch is provisioned and the spec is moving either way, and an unassigned issue
243
+ is a cosmetic gap that `/spec-claim` closes later.
244
+
245
+ 1. **Work out who you are.** Run `skitterspec spec-sync whoami --json`.
246
+ - `ok: true` → use `id` and `name`. Nothing to ask.
247
+ - `source: "mcp"` or the command reports no API key → call the discovered
248
+ user-read tool with `me`, then cache it:
249
+ `skitterspec spec-sync whoami --set <id> --name "<name>"`.
250
+ - `ok: false` → identity is **unknown**, which is an ordinary state (a shared
251
+ or bot key, an offline machine). Go to step 2.
252
+ 2. **Unknown identity — three states, not two.** Decide by what is actually
253
+ reachable, and route the third to inaction:
254
+ - **Linear reachable *and* this is an interactive session** → offer a short
255
+ user search (`skitterspec spec-sync users <name-or-email>`, or the
256
+ user-list tool on MCP), let the operator pick, and offer to cache it with
257
+ `whoami --set` so this is asked once per machine rather than once per spec.
258
+ - **Not reachable, or not interactive** → say so in one line
259
+ (`assignment skipped — no Linear identity`) and **carry on**. Do not prompt
260
+ for something you could not act on, and never stall a `/spec-start` on it.
261
+ 3. **Record it on the spec** — through the engine, never by hand-editing
262
+ frontmatter:
263
+
264
+ ```
265
+ skitterspec spec-sync assign <spec> --to <user-id> --name "<display name>"
266
+ ```
267
+
268
+ 4. **Leave `> **Developer:**` as `git config user.name`** — the step above this
269
+ seam already set it, and this seam must not overwrite it with the tracker's
270
+ display name.
271
+
272
+ Those two names are the same person and often not the same string, and the
273
+ git one is the one everything else in the spec already uses: `Author:`, every
274
+ **State log** `By` row, and every commit. Writing the tracker's name into this
275
+ one field would leave a spec whose own audit trail contradicts its header —
276
+ a worse problem than the one it would solve. Anyone wanting a different name
277
+ on their specs sets `git config user.name`, and it stays consistent
278
+ everywhere.
279
+
280
+ The header names **who is building this**; the stamp in step 3 names
281
+ **which tracker account it is assigned to**. They answer different questions,
282
+ so they need not be the same string. The one place that reasoning does not hold
283
+ is handing a spec to *someone else* — there is no local git name for them —
284
+ and that case belongs to `/spec-claim --to`, which sets the header from the
285
+ tracker deliberately.
286
+
287
+ **Nothing is pushed here.** `assign` writes the repo only, and the refresh these
288
+ skills already run sends it. Assignment is an ordinary field of the projection,
289
+ not a side errand with its own network call.
290
+
291
+ **There is no unassign step anywhere.** The projection derives the assignee from
292
+ the spec's lifecycle bucket, so `/spec-complete` and `/spec-cancel` release the
293
+ issue through the push they already make. The stamp deliberately stays in the
294
+ file: who actioned the work outlives who is currently holding it.
295
+
157
296
  - Append a **State log** row: `| <YYYY-MM-DD> | In Progress | in-progress | <git user.name> |`.
158
- - **Commit it, and push the branch.** One commit, the spec's own — it records the
159
- in-progress state for everyone and fires the tracker's automation. Do this
160
- *before* the tracker refresh below, so the snapshot that refresh writes is
161
- swept up by the phase's own commit rather than left dirty.
297
+
298
+ **Only when `specs/.core/linear.config.json` exists** and the spec's overview
299
+ carries a `linear_identifier`. Either missing **skip**, in one line
300
+ (`not linked to Linear /spec-push to mirror it`), and carry on. Nothing else in
301
+ this step changes.
302
+
303
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The spec has just
304
+ moved to `in-progress/` and been stamped with a developer — two real state
305
+ changes, and the tracker is a generated mirror of them. It costs one engine call
306
+ and no model tokens when a Linear API key is set (see `apply.transport` in
307
+ `linear.config.md`).
308
+
309
+ - **Never mint.** An unlinked spec is skipped, not created. A spec kept
310
+ deliberately local should stay that way — `/spec-push` is how someone opts in.
311
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
312
+ and **finish the operation anyway**. The spec is in flight in the repo
313
+ regardless; the mirror is disposable and the next push repairs it. Do not roll
314
+ the provisioning back, and do not stop to ask.
315
+ - **Say what happened** in the skill's report: mirror updated, skipped as
316
+ unlinked, or failed with the reason.
317
+
318
+ ### Why it sits here
319
+
320
+ Pinned **after the `git mv` and before the commit**, and both halves matter:
321
+
322
+ - **After the move**, because the projection reads a spec's workflow state from
323
+ its folder bucket. Push while the folder is still in `backlog/` and the issue
324
+ is set to the state the spec is *leaving*.
325
+ - **Before the commit**, because the push stamps ids into the spec and writes a
326
+ snapshot under `specs/.core/`. The `git add` that follows sweeps both up with
327
+ the status change; push after it instead and those files are left uncommitted —
328
+ which strands a dirty worktree on the hand-off, and makes `spec-env integrate`
329
+ refuse to land the branch.
330
+
331
+ ### Why this is not the refresh `/spec-next` already runs
332
+
333
+ It was once reasoned that a push here would send the same thing twice, one commit
334
+ apart, because `/spec-next` refreshes the moment it starts. That holds only in
335
+ `checkout` mode, where `/spec-next` follows immediately. In `worktree` mode the
336
+ spec is built elsewhere and the refresh can be hours away or never come — and
337
+ until it does, the issue sits in its old workflow state with nobody assigned
338
+ while the repo reads `in-progress` with a developer on it.
339
+
340
+ The two pushes are not duplicates either way. This one carries the issue's
341
+ workflow state and its assignee; the one `/spec-next` runs carries phase 1
342
+ starting. Every other lifecycle skill mirrors the state change it makes, and this
343
+ is the seam that stops `/spec-start` being the exception.
344
+
345
+ - **Commit it.** One commit, the spec's own — it records the in-progress state on
346
+ the branch, and sweeps up everything above so the worktree is clean when you
347
+ hand it over.
348
+
349
+ **Publishing that branch is yours to do, and nothing here does it for you.**
350
+ Whenever you want the work somewhere other than this machine:
351
+
352
+ ```
353
+ git -C <worktreePath> push -u origin <branch>
354
+ ```
355
+
356
+ Print it if it is useful; never run it. This skill once pushed here, justified as
357
+ recording the state "for everyone" and firing the tracker's automation — and
358
+ neither half survives reading. The automation needs `{identifier}` in
359
+ `branch.pattern` (see `env.config.md`) to put an issue id in the branch name, and
360
+ the shipped default carries none, so the tracker has nothing to match. Nor was it
361
+ an invariant: `/spec-bug` provisions a worktree the same way and has never
362
+ pushed, and `/spec-hotfix` forbids it outright. This was the odd one out.
363
+
364
+ **It also kept a guard from ever firing.** `refuseTeardownIfUnpushed` blocks
365
+ teardown on commits that are unpushed and unlanded — which described no branch at
366
+ all while this skill published every one of them at provisioning. It can fire now,
367
+ and the place it does is `/spec-cancel`, where the work really is about to be
368
+ destroyed: that skill relays the refusal and offers both ways out. Nothing to do
369
+ here beyond knowing it is no longer dead code.
162
370
 
163
371
  A spec ideally arrives `Ready` from `/spec`; a `Draft` works too — sanity-check
164
372
  it is well-formed first.
@@ -193,18 +401,75 @@ marks phase 1 started, refreshes the mirror again, builds it with tests and
193
401
  reports. Do not stop and ask the operator to run it: the branch is here and they
194
402
  asked to start the spec.
195
403
 
196
- **`worktree` mode — the spec is built in its worktree**, and step 3.2 put this
197
- session there, so end by saying the worktree path is now the session's and to run
198
- **`/spec-next`** here. On the hand-off path (step 3.3) the session did not move:
199
- say so plainly, print the path, and tell them to run `/spec-next` from a session
200
- in it.
201
-
202
- `/spec-next` resolves the spec it is *standing in* the live spec of the
203
- checkout, the worktree its cwd is inside, or the branch in `checkout` mode — and
204
- a name argument narrows a re-run rather than selecting a spec elsewhere. That
205
- refusal is deliberate: building the wrong spec's phase writes commits on a branch
206
- nobody asked for. **Do not work around it**, and do not offer to build the phase
207
- from here.
404
+ **`worktree` mode — offer it, then do what they say.** Step 3 left this session
405
+ standing in the worktree, so both endings happen right here and neither needs a
406
+ second session opened anywhere. In prose: say the worktree is ready and name its
407
+ path, then ask **"build phase 1 now?"** recommending that they do, and naming
408
+ both endings so the decline is a real answer rather than a formality.
409
+
410
+ **Never fence that question.** A grey box is read as an artefact to skim rather
411
+ than as something someone is being asked, which is the whole reason
412
+ `.claude/rules/spec-reports.md` bans fencing a message to the reader. This step
413
+ prescribed a fenced block for a long time and an operator read straight past it,
414
+ which is the evidence, not a preference.
415
+
416
+ - **Build it here** — carry on into a bare **`/spec-next`**. Bare is right: the
417
+ session is in the worktree, so its rule 2 resolves this spec with nothing
418
+ passed and nothing guessed.
419
+ - **Stop here** — the operator is left standing in the worktree on a provisioned
420
+ branch, which is a perfectly good place to leave things. Nothing has to be
421
+ reopened or handed anywhere, and `/spec-next` typed an hour later — from this
422
+ session or a fresh one — does exactly what it would have done now.
423
+
424
+ **Ask rather than deciding for them, and mean it.** Provisioning is cheap and
425
+ reversible; a phase build is neither, and one yes should not cover both. A large
426
+ phase is often better started in a session of its own with a whole context budget
427
+ to spend, and only the operator knows which this is. On **`--plan`** this step
428
+ does not run at all — nothing was provisioned to build in.
429
+
430
+ **`--worktree <path>` is still there, and is still not a way around the refusal.**
431
+ It builds a spec the session is *not* standing in, which after step 3 is the
432
+ exception rather than the normal path. Reach for it in exactly two cases: the
433
+ confirm in step 3 reported the `cd` did not take, or you deliberately mean to
434
+ build some other spec's phase from here. A path someone typed is not a path
435
+ anything guessed, which is why it was never a loosening of the refusal and still
436
+ is not.
437
+
438
+ ## 7. Report
439
+
440
+ **When step 6 carried on into `/spec-next`, emit no block here.** That skill ends
441
+ with its own, and its `Branch` and `Spec` fields already carry everything this
442
+ one would say. Two blocks for one run is the noise the contract exists to
443
+ remove — this section is for the run that stops at step 6.
444
+
445
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
446
+ the shape; this section carries only what is specific here.
447
+
448
+ **Verdicts**
449
+
450
+ - `✅` — the branch is provisioned, the spec is `in-progress`, the session is
451
+ standing in the worktree.
452
+ - `⚠️` — provisioned, with something worth knowing (dev servers that did not
453
+ come up, a mirror that did not refresh, a missing gating decision).
454
+ - `❌` — provisioning failed part-way and left something behind. Say what, and
455
+ where.
456
+ - `⏸` — the gate refused: in `checkout` mode a dirty tree or someone else's
457
+ unfinished work, in either mode a spec whose own files are not in the commit
458
+ the worktree would fork from. Nothing changed. Another spec's uncommitted work
459
+ is **not** on this list in `worktree` mode — it is an `Untouched` row on a
460
+ `✅`.
461
+
462
+ **Fields:** `Tracker` · `Branch` · `Spec` · `Worktree` · `Untouched` · `Follow-ups` · `Next`
463
+
464
+ `Worktree` carries the path, because the session is now standing in it and the
465
+ operator's next command depends on knowing that. `Next` is `/spec-next`.
466
+
467
+ `Untouched` appears only when `spec-env up` reported uncommitted work that was
468
+ not this spec's — say how much and whose, and **keep the verdict `✅`**. Nothing
469
+ went wrong: a worktree carries nothing, so that work was never in play.
470
+
471
+ On **`--plan`** nothing was provisioned, so the verdict is `⏸` and `Built`
472
+ carries the plan rather than a claim about the repo.
208
473
 
209
474
  ## Opt-outs
210
475
 
@@ -213,14 +478,29 @@ from here.
213
478
  that the work lands wherever you are (usually the base branch); reserve it for
214
479
  a trivial change or an explicit request.
215
480
 
216
- There is no `--here`: `/spec-start` **is** here. It puts the branch in the
217
- checkout you are in, which is what the old opt-out was reaching for.
218
-
219
- ## Why there is no tracker seam here
220
-
221
- This skill creates no spec and mints no issue, so it has nothing to link — the
222
- intake and picker steps belong to `/spec`, `/spec-bug` and `/spec-hotfix`. The
223
- state change it *does* make (the spec moving to `in-progress`) is mirrored by
224
- the refresh `/spec-next` runs the moment it starts, which pushes the issue state
225
- and the phase states together. Adding a push here would send the same thing
226
- twice, one commit apart.
481
+ There is no `--here`. It existed to ask for the branch in the checkout you are
482
+ standing in and in `checkout` mode that is already what happens, while in
483
+ `worktree` mode `--no-worktree` is the way to say it.
484
+
485
+ ## Why this skill links nothing, but does mirror what it changes
486
+
487
+ This skill creates no spec and mints no issue, so it has **nothing to link** —
488
+ the intake and picker steps belong to `/spec`, `/spec-bug` and `/spec-hotfix`.
489
+
490
+ It does push, though, and that is step 4's seam. The state change it makes the
491
+ spec moving to `in-progress` with a developer stamped on it — is mirrored by the
492
+ skill that makes it, exactly as `/spec-complete`, `/spec-cancel` and
493
+ `/spec-review` mirror theirs. It was once reasoned that the refresh `/spec-next`
494
+ runs would cover it, so a push here would send the same thing twice one commit
495
+ apart. That only ever held in `checkout` mode, where `/spec-next` follows
496
+ immediately; in `worktree` mode it can be hours away or never come, and the
497
+ issue sits in its old state with nobody assigned meanwhile.
498
+
499
+ The **assignment** seam just above it is still not a push, and does not need to
500
+ be. This is the one moment in the lifecycle where "who is building this" is
501
+ actually decided — the branch is being provisioned for someone, and that someone
502
+ is at the keyboard. It stamps the spec file and stops there, so it costs no
503
+ tracker call and rides out on the push below like every other field. Deferring
504
+ the stamp to `/spec-next` would be worse than untidy: in `worktree` mode the two
505
+ can be separated by hours, and a spec in flight with nobody named on it is
506
+ exactly the gap assignment exists to close.
@@ -6,6 +6,11 @@ disable-model-invocation: true
6
6
 
7
7
  # /spec-status — one-way sync drift report
8
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
+
9
14
  Read-only. Reports two things and writes nothing:
10
15
 
11
16
  1. **Pending push** — has the spec changed since the last push (is there an issue
@@ -67,8 +72,25 @@ a hand edit misses some (it has, twice).
67
72
 
68
73
  ## 4. Report
69
74
 
70
- Relay the engine's output verbatim. Suggest `/spec-push` if a push is pending.
71
- Never write to either side.
75
+ Relay the engine's output **verbatim, above the block** it is the answer, and
76
+ the block is the verdict on it. Never write to either side.
77
+
78
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
79
+ the shape; this section carries only what is specific here.
80
+
81
+ **Verdicts**
82
+
83
+ - `✅` — in sync; nothing would push.
84
+ - `⚠️` — drift: N objects would push, or Linear's workflow state was moved by
85
+ hand. Neither is an error — the repo wins on the next push — but both are the
86
+ reason someone ran this.
87
+ - `⏸` — no config, or the spec is not linked. Nothing to compare.
88
+
89
+ **Fields:** `Tracker` · `Follow-ups` · `Next`
90
+
91
+ `Tracker` is the drift in one line — what would push, and whether the tracker's
92
+ state diverged. `Next` is `/spec-push` when a push is pending, and nothing to do
93
+ when it is not.
72
94
 
73
95
  A **`phases: <mode>`** line names the phase mode that resolved for this spec's
74
96
  lifecycle bucket, and appears only when it is not the default `subissue`.