@skitterbyte/skitterspec-linear 10.1.0 → 10.3.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.
@@ -36,28 +36,58 @@ Then:
36
36
  2. **Read the issue** with the discovered issue-read tool: title, description,
37
37
  labels, reporter, URL. If Linear isn't connected, say so and offer to carry on
38
38
  without it — a spec written from the user's own description is still a spec.
39
- 3. **Route bugs away in `/spec` only.** If any of the issue's labels matches
40
- `intake.bugLabels` (case-insensitive), this is a bug report, not a feature
41
- request. Say so, name the matching label, and tell the user to run
42
- **`/spec-bug <ISSUE-REF>`** then stop, without authoring a Feature spec.
43
- With `intake.bugLabels` unset nothing is routed and every issue is treated as a
44
- feature request.
45
-
46
- **In `/spec-bug` this step is skipped** you are already in the bug path.
47
- Adopt the issue, then reproduce it as usual: the issue body is your repro
39
+ 3. **Route the issue to the right skill.** Match the issue's labels
40
+ (case-insensitively) against two lists, **checking `hotfixLabels` first**:
41
+
42
+ - **`intake.hotfixLabels`** this is broken in **production**, so it needs a
43
+ fix against the released version, not `main`. Say so, name the matching
44
+ label, and tell the user to run **`/spec-hotfix <ISSUE-REF>`** — then stop.
45
+ - **`intake.bugLabels`** → a bug report, not a feature request. Say so, name
46
+ the label, and tell the user to run **`/spec-bug <ISSUE-REF>`** then stop,
47
+ without authoring a Feature spec.
48
+
49
+ **Hotfix wins when an issue carries both**, and deliberately: production is the
50
+ more specific destination, and the two mistakes are not equally costly. Routing
51
+ a prod issue to `/spec-bug` produces a fix that lands on `main` and never
52
+ reaches the running version — discovered only when someone asks why it hasn't
53
+ shipped. The reverse is a hotfix branch for something that could have waited,
54
+ which is merely wasteful.
55
+
56
+ With a list unset, nothing routes through it; with both unset every issue is
57
+ treated as a feature request, exactly as before.
58
+
59
+ **Which checks run depends on where you are**, because a skip is about not
60
+ bouncing someone to the skill they are already in — not about ignoring an
61
+ escalation:
62
+
63
+ - **In `/spec`** — both checks run.
64
+ - **In `/spec-bug`** — the bug check is skipped (it would route you to
65
+ yourself), but the **hotfix check still runs**. A bug report labelled for
66
+ production is not "already handled" by being in the bug path: `/spec-bug`
67
+ fixes on `main`, and prod would stay broken. Say so and hand off.
68
+ - **In `/spec-hotfix`** — both are skipped. It is already the most specific
69
+ destination; there is nowhere left to route.
70
+
71
+ Then adopt the issue and reproduce it as usual: the issue body is your repro
48
72
  material, and the failing test comes before the spec exactly as normal.
49
73
  4. **Seed, don't skip, the grill.** The issue's title becomes the working spec
50
- title and its description the starting material for **Problem** — quote the
74
+ title and its description the starting material for **Problem** — or
75
+ **Symptom** in a bug or hotfix spec, which is where a report belongs. Quote the
51
76
  reporter's words rather than paraphrasing them away. Note the reporter and the
52
77
  issue URL for context. Everything after this runs exactly as normal: an issue
53
78
  is a *request*, not a groomed spec, so grill it as hard as anything else.
54
79
 
80
+ **In `/spec-hotfix`, also mine the issue for a version.** Any release-shaped
81
+ string in the report is a *suggestion* for the base tag, offered when the skill
82
+ asks which version prod is running — never used as a default. The reporter's
83
+ version is the one they saw the bug on, which is often not what is deployed.
84
+
55
85
  ### Adopting the issue
56
86
 
57
87
  The issue **becomes** the spec's issue — it is not copied and no second issue is
58
- minted. This is identical in `/spec` and `/spec-bug`; only *when* it happens
59
- differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4). Once the
60
- spec file exists:
88
+ minted. This is identical in `/spec`, `/spec-bug` and `/spec-hotfix`; only *when*
89
+ it happens differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4,
90
+ `/spec-hotfix` in its step 5). Once the spec file exists:
61
91
 
62
92
  - **Stamp `linear_identifier` and `linear_url`** in `00-overview.md` frontmatter
63
93
  from the adopted issue. That is the whole link: every later skill
@@ -66,13 +96,15 @@ spec file exists:
66
96
  somewhere deliberately — where it lives is Linear's business, and adoption is
67
97
  not a mint.
68
98
  - **Do not write a base sidecar.** Leaving `sync.baseDir` empty for this spec is
69
- what makes the first `/spec-push` push the spec over the reporter's original
99
+ what makes the **linking push** send the spec over the reporter's original
70
100
  description (an **update** to the existing issue, plus a sub-issue per phase).
71
101
  Recording a snapshot here would declare the mirror already in sync and strand
72
102
  the issue showing the raw report forever.
73
- - **Say what will happen** in the finish-up message: the first `/spec-push` will
74
- overwrite the issue's description with the spec, and the original report is
75
- preserved in the spec's **Problem** section.
103
+ - **Say what will happen** in the finish-up message. The linking step runs right
104
+ after the spec is written, so the issue's description is replaced by the spec
105
+ **then** not on some later manual push. The reporter's words are not lost:
106
+ they are quoted in the spec's **Problem** (or **Symptom**) section, and Linear
107
+ keeps the original in the issue's history.
76
108
 
77
109
  ## Phase A — reach a clear shared understanding (grill first)
78
110
 
@@ -286,49 +318,54 @@ while the repo stays the source of truth — follow the provider's link steps
286
318
  below (nothing to do here without one).
287
319
 
288
320
  **Only when `specs/.core/linear.config.json` exists** (Linear sync is opted in).
289
- If it's absent, skip this phase entirely — the spec stays local-only and `/spec`
290
- behaves exactly as above. When present, after writing the spec, link it to Linear
291
- so status and discussion live there while the repo stays the co-authoring surface.
292
- A spec is a Linear **issue**; each phase is a **sub-issue**, carrying that
293
- phase's tasks in its description as a read-only checklist:
294
-
295
- - **Discover the Linear MCP tools at runtime** (don't hardcode names). If Linear
296
- isn't connected/authed, relay the fix and stop — leave the spec written and
297
- local; the user can link it later with `/spec-push`. Do nothing destructive.
298
- - **Pick the Project** run the picker in **Picking the Linear Project** below,
299
- then **create the Issue** from the spec: `title` from the spec title,
300
- `description` from the `00-overview.md` plan, `team` = `linear.teamId`, and
301
- `project` = the picked id (omitted when the user chose None).
302
- - **Create a sub-issue per phase** (the `mapping.phases` target `subissue` by
303
- default): a child issue with `parentId` = the spec issue, named from each phase
304
- file, in execution order.
305
- - **Stamp the ids** so the spec is linkable one call, no hand-edited
306
- frontmatter:
307
-
308
- ```
309
- skitterspec spec-sync stamp <spec> \
310
- --issue TEAM-123 --url https://linear.app/… \
311
- --sub 01-<slug>=TEAM-124 --sub 02-<slug>=TEAM-125
312
- ```
313
-
314
- It writes `linear_identifier`/`linear_url` onto `00-overview.md` and each phase
315
- file's `linear_issue_id`, validating every ref and id **before** touching a
316
- file on any problem it changes nothing and exits non-zero, so a typo can't
317
- leave the spec pointing at an issue that isn't there. (Status is not stored in
318
- frontmatter — it comes from the spec's lifecycle folder.)
319
- - **Write the initial base sidecar** so the spec starts clean and non-diverged —
320
- run `skitterspec spec-sync record <spec>` to capture the local snapshot as the
321
- committed base (`sync.baseDir`). `/spec-status` should report in-sync right after.
322
- (`record` is the writer; `normalize` only *prints* the projection.) Skip this
323
- when the spec **adopted** an existing issue see Phase 0 because the issue's
324
- description is still the reporter's, not the spec.
325
- - **Echo the branch name** from `branch.pattern` so the user knows what `/spec-go`
326
- will fork.
321
+ If it's absent, skip this entirely — the spec stays local-only and the skill
322
+ behaves exactly as above. When present, link the spec you just wrote so status
323
+ and discussion live in Linear while the repo stays the source of truth. A spec is
324
+ a Linear **issue**; each phase is a **sub-issue**, carrying that phase's tasks in
325
+ its description as a read-only checklist.
326
+
327
+ **Linking is just the first push**, so it runs the same engine path `/spec-push`
328
+ does you never create the issue by hand:
329
+
330
+ 1. **Pick the transport.** `skitterspec spec-sync states --json`. With a Linear
331
+ API key set it answers `api` and prints the workspace's state names; without
332
+ one it answers `mcp` and you do the MCP work `/spec-push` describes. Write the
333
+ state names to a file for step 3.
334
+ 2. **Pick the Project** run the picker in **Picking the Linear Project** below.
335
+ Keep the chosen id for step 4.
336
+ 3. **Get the plan.**
337
+ `skitterspec spec-sync push <spec> --workspace-states <file> --json > plan.json`
338
+ — the spec is unlinked, so this plan is all-creates: the issue and one
339
+ sub-issue per phase.
340
+ 4. **Apply it.**
341
+ `skitterspec spec-sync apply <spec> --plan plan.json --project <chosen id>`.
342
+ That creates the issue and its sub-issues and checks what Linear stored. It
343
+ then **stamps the ids into the spec** — `linear_identifier`/`linear_url` on the
344
+ overview, `linear_issue_id` on each phase — and **records the base snapshot**,
345
+ so `/spec-status` reports in-sync immediately. There is no hand-editing of
346
+ frontmatter and no separate `stamp` or `record` call.
347
+
348
+ If it prints `transport = mcp`, it wrote nothing: apply the plan over MCP as
349
+ `/spec-push` steps 4a–5 describe, ending with `spec-sync stamp` and
350
+ `spec-sync record`. That path is fully supported — it is what anyone without
351
+ an API key uses.
352
+ 5. **Echo the branch name** from `branch.pattern` so the user knows what
353
+ `/spec-go` will fork.
354
+
355
+ **If Linear can't be reached**, say so in one line and leave the spec written and
356
+ local — it is still a perfectly good spec, and `/spec-push` links it later. Do
357
+ nothing destructive.
358
+
359
+ **A spec that adopted an existing issue** (see the intake step) is already
360
+ stamped, so its plan is an **update**, not a create: applying it replaces the
361
+ reporter's description with the spec. That is the one-way rule working as
362
+ intended — the repo is canonical and the original text stays in Linear's history.
363
+ Skip the project picker for an adopted issue: its placement is Linear's.
327
364
 
328
365
  Leave committing to the existing convention (the user commits the spec as usual)
329
366
  and **never auto-push git** — Linear's own automation reacts to real branch/PR
330
- events later. Report the Linear issue URL and the base as part of Phase C's
331
- finish-up message.
367
+ events later. Report the Linear issue URL as part of the skill's finish-up
368
+ message.
332
369
 
333
370
  ### Picking the Linear Project
334
371
 
@@ -338,8 +375,10 @@ placement is **Linear's to own**: never send `project` on an update, and never
338
375
  record the choice in the spec file or the snapshot. A PM re-homing a spec issue
339
376
  must not show up as drift or be overwritten on the next push.
340
377
 
341
- 1. **List the candidates.** Call the discovered project-list tool for
342
- `linear.teamId`. Drop archived / completed projects they can't take new work.
378
+ 1. **List the candidates.** Ask the engine:
379
+ `skitterspec spec-sync projects --json`. On the API path it returns the team's
380
+ projects; on the MCP path it says so, and you call the discovered project-list
381
+ tool instead. Drop archived / completed projects — they can't take new work.
343
382
  2. **Offer them.** Show the names (most recently updated first is fine), plus an
344
383
  explicit **None (team only)** option. Pre-select `linear.projectId` from
345
384
  `linear.config.json` when it's set and still in the list; otherwise pre-select
@@ -348,13 +387,14 @@ must not show up as drift or be overwritten on the next push.
348
387
  the list case-insensitively by name and re-offer. Don't re-fetch.
349
388
  4. **Never offer to create a project.** Projects are the PM's surface — if none
350
389
  fits, that's **None (team only)**, and someone makes the project in Linear.
351
- 5. **Pass it once.** Include `project: <chosen id>` on the issue-create call.
352
- Chose None omit the key entirely (do not send an empty string).
353
-
354
- **Degrade, never block.** If Linear isn't connected, or the server exposes no
355
- project-list tool, say so in one line*"project picker unavailable; creating the
356
- issue without a project"* and carry on with `linear.projectId` if it's set, else
357
- no project at all. A missing picker must never fail `/spec` or `/spec-push`.
390
+ 5. **Pass it once**, to whichever thing mints the issue: `--project <chosen id>`
391
+ on `spec-sync apply`, or `project: <chosen id>` on the MCP issue-create call.
392
+ Chose None → omit it entirely (do not pass an empty string).
393
+
394
+ **Degrade, never block.** If the list can't be fetched Linear not connected, no
395
+ project-list tool, no API key, or `spec-sync projects` reporting it couldn't ask
396
+ say so in one line *"project picker unavailable"* and carry on with
397
+ `linear.projectId` if it's set, else no project at all. A missing picker must never fail the skill that called it.
358
398
 
359
399
  If `linear.projectId` is set but that Project is archived or missing, relay
360
400
  Linear's error and stop rather than silently minting an unparented issue.
@@ -35,28 +35,58 @@ Then:
35
35
  2. **Read the issue** with the discovered issue-read tool: title, description,
36
36
  labels, reporter, URL. If Linear isn't connected, say so and offer to carry on
37
37
  without it — a spec written from the user's own description is still a spec.
38
- 3. **Route bugs away in `/spec` only.** If any of the issue's labels matches
39
- `intake.bugLabels` (case-insensitive), this is a bug report, not a feature
40
- request. Say so, name the matching label, and tell the user to run
41
- **`/spec-bug <ISSUE-REF>`** then stop, without authoring a Feature spec.
42
- With `intake.bugLabels` unset nothing is routed and every issue is treated as a
43
- feature request.
44
-
45
- **In `/spec-bug` this step is skipped** you are already in the bug path.
46
- Adopt the issue, then reproduce it as usual: the issue body is your repro
38
+ 3. **Route the issue to the right skill.** Match the issue's labels
39
+ (case-insensitively) against two lists, **checking `hotfixLabels` first**:
40
+
41
+ - **`intake.hotfixLabels`** this is broken in **production**, so it needs a
42
+ fix against the released version, not `main`. Say so, name the matching
43
+ label, and tell the user to run **`/spec-hotfix <ISSUE-REF>`** — then stop.
44
+ - **`intake.bugLabels`** → a bug report, not a feature request. Say so, name
45
+ the label, and tell the user to run **`/spec-bug <ISSUE-REF>`** then stop,
46
+ without authoring a Feature spec.
47
+
48
+ **Hotfix wins when an issue carries both**, and deliberately: production is the
49
+ more specific destination, and the two mistakes are not equally costly. Routing
50
+ a prod issue to `/spec-bug` produces a fix that lands on `main` and never
51
+ reaches the running version — discovered only when someone asks why it hasn't
52
+ shipped. The reverse is a hotfix branch for something that could have waited,
53
+ which is merely wasteful.
54
+
55
+ With a list unset, nothing routes through it; with both unset every issue is
56
+ treated as a feature request, exactly as before.
57
+
58
+ **Which checks run depends on where you are**, because a skip is about not
59
+ bouncing someone to the skill they are already in — not about ignoring an
60
+ escalation:
61
+
62
+ - **In `/spec`** — both checks run.
63
+ - **In `/spec-bug`** — the bug check is skipped (it would route you to
64
+ yourself), but the **hotfix check still runs**. A bug report labelled for
65
+ production is not "already handled" by being in the bug path: `/spec-bug`
66
+ fixes on `main`, and prod would stay broken. Say so and hand off.
67
+ - **In `/spec-hotfix`** — both are skipped. It is already the most specific
68
+ destination; there is nowhere left to route.
69
+
70
+ Then adopt the issue and reproduce it as usual: the issue body is your repro
47
71
  material, and the failing test comes before the spec exactly as normal.
48
72
  4. **Seed, don't skip, the grill.** The issue's title becomes the working spec
49
- title and its description the starting material for **Problem** — quote the
73
+ title and its description the starting material for **Problem** — or
74
+ **Symptom** in a bug or hotfix spec, which is where a report belongs. Quote the
50
75
  reporter's words rather than paraphrasing them away. Note the reporter and the
51
76
  issue URL for context. Everything after this runs exactly as normal: an issue
52
77
  is a *request*, not a groomed spec, so grill it as hard as anything else.
53
78
 
79
+ **In `/spec-hotfix`, also mine the issue for a version.** Any release-shaped
80
+ string in the report is a *suggestion* for the base tag, offered when the skill
81
+ asks which version prod is running — never used as a default. The reporter's
82
+ version is the one they saw the bug on, which is often not what is deployed.
83
+
54
84
  ### Adopting the issue
55
85
 
56
86
  The issue **becomes** the spec's issue — it is not copied and no second issue is
57
- minted. This is identical in `/spec` and `/spec-bug`; only *when* it happens
58
- differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4). Once the
59
- spec file exists:
87
+ minted. This is identical in `/spec`, `/spec-bug` and `/spec-hotfix`; only *when*
88
+ it happens differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4,
89
+ `/spec-hotfix` in its step 5). Once the spec file exists:
60
90
 
61
91
  - **Stamp `linear_identifier` and `linear_url`** in `00-overview.md` frontmatter
62
92
  from the adopted issue. That is the whole link: every later skill
@@ -65,13 +95,15 @@ spec file exists:
65
95
  somewhere deliberately — where it lives is Linear's business, and adoption is
66
96
  not a mint.
67
97
  - **Do not write a base sidecar.** Leaving `sync.baseDir` empty for this spec is
68
- what makes the first `/spec-push` push the spec over the reporter's original
98
+ what makes the **linking push** send the spec over the reporter's original
69
99
  description (an **update** to the existing issue, plus a sub-issue per phase).
70
100
  Recording a snapshot here would declare the mirror already in sync and strand
71
101
  the issue showing the raw report forever.
72
- - **Say what will happen** in the finish-up message: the first `/spec-push` will
73
- overwrite the issue's description with the spec, and the original report is
74
- preserved in the spec's **Problem** section.
102
+ - **Say what will happen** in the finish-up message. The linking step runs right
103
+ after the spec is written, so the issue's description is replaced by the spec
104
+ **then** not on some later manual push. The reporter's words are not lost:
105
+ they are quoted in the spec's **Problem** (or **Symptom**) section, and Linear
106
+ keeps the original in the issue's history.
75
107
 
76
108
  ## 1. Reproduce & isolate (light investigation)
77
109
 
@@ -216,6 +248,56 @@ The **State log** is the folder/status audit trail; later transitions
216
248
  (`/spec-complete`, `/spec-cancel`) append a row. The **Changelog** is for the
217
249
  fix narrative and decisions — keep them separate.
218
250
 
251
+ **Only when `specs/.core/linear.config.json` exists** (Linear sync is opted in).
252
+ If it's absent, skip this entirely — the spec stays local-only and the skill
253
+ behaves exactly as above. When present, link the spec you just wrote so status
254
+ and discussion live in Linear while the repo stays the source of truth. A spec is
255
+ a Linear **issue**; each phase is a **sub-issue**, carrying that phase's tasks in
256
+ its description as a read-only checklist.
257
+
258
+ **Linking is just the first push**, so it runs the same engine path `/spec-push`
259
+ does — you never create the issue by hand:
260
+
261
+ 1. **Pick the transport.** `skitterspec spec-sync states --json`. With a Linear
262
+ API key set it answers `api` and prints the workspace's state names; without
263
+ one it answers `mcp` and you do the MCP work `/spec-push` describes. Write the
264
+ state names to a file for step 3.
265
+ 2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
266
+ Keep the chosen id for step 4.
267
+ 3. **Get the plan.**
268
+ `skitterspec spec-sync push <spec> --workspace-states <file> --json > plan.json`
269
+ — the spec is unlinked, so this plan is all-creates: the issue and one
270
+ sub-issue per phase.
271
+ 4. **Apply it.**
272
+ `skitterspec spec-sync apply <spec> --plan plan.json --project <chosen id>`.
273
+ That creates the issue and its sub-issues and checks what Linear stored. It
274
+ then **stamps the ids into the spec** — `linear_identifier`/`linear_url` on the
275
+ overview, `linear_issue_id` on each phase — and **records the base snapshot**,
276
+ so `/spec-status` reports in-sync immediately. There is no hand-editing of
277
+ frontmatter and no separate `stamp` or `record` call.
278
+
279
+ If it prints `transport = mcp`, it wrote nothing: apply the plan over MCP as
280
+ `/spec-push` steps 4a–5 describe, ending with `spec-sync stamp` and
281
+ `spec-sync record`. That path is fully supported — it is what anyone without
282
+ an API key uses.
283
+ 5. **Echo the branch name** from `branch.pattern` so the user knows what
284
+ `/spec-go` will fork.
285
+
286
+ **If Linear can't be reached**, say so in one line and leave the spec written and
287
+ local — it is still a perfectly good spec, and `/spec-push` links it later. Do
288
+ nothing destructive.
289
+
290
+ **A spec that adopted an existing issue** (see the intake step) is already
291
+ stamped, so its plan is an **update**, not a create: applying it replaces the
292
+ reporter's description with the spec. That is the one-way rule working as
293
+ intended — the repo is canonical and the original text stays in Linear's history.
294
+ Skip the project picker for an adopted issue: its placement is Linear's.
295
+
296
+ Leave committing to the existing convention (the user commits the spec as usual)
297
+ and **never auto-push git** — Linear's own automation reacts to real branch/PR
298
+ events later. Report the Linear issue URL as part of the skill's finish-up
299
+ message.
300
+
219
301
  ## 5. Drive to GREEN
220
302
 
221
303
  - Implement the **minimal, root-cause** fix. Match surrounding code; honour all
@@ -51,6 +51,40 @@ Add a **Changelog** entry:
51
51
  `git mv "specs/<bucket>/<name>" "specs/cancelled/<name>"` (preserve history;
52
52
  move the whole folder).
53
53
 
54
+ **Only when `specs/.core/linear.config.json` exists** and the spec's overview
55
+ carries a `linear_identifier`. Either missing → **skip**, in one line
56
+ (`not linked to Linear — /spec-push to mirror it`), and carry on. Nothing else in
57
+ this step changes.
58
+
59
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
60
+ become the truth about this spec's state, and the tracker is a generated mirror
61
+ of it — leaving them to diverge until someone remembers to push by hand is the
62
+ gap this exists to close. It costs one engine call and no model tokens when a
63
+ Linear API key is set (see `apply.transport` in `linear.config.md`).
64
+
65
+ - **Never mint.** An unlinked spec is skipped, not created. An issue born `Done`
66
+ or `Canceled` is tracker noise, and a spec kept deliberately local should stay
67
+ that way — `/spec-push` is how someone opts in.
68
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
69
+ and **finish the operation anyway**. The spec is complete/cancelled/reviewed in
70
+ the repo regardless; the mirror is disposable and the next push repairs it.
71
+ Do not roll anything back, and do not stop to ask.
72
+ - **Say what happened** in the skill's report: mirror updated, skipped as
73
+ unlinked, or failed with the reason.
74
+
75
+ ### Why it sits here
76
+
77
+ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
78
+ **after the `git mv` and before the commit**, and both halves matter:
79
+
80
+ - **After the move**, because the projection reads a spec's workflow state from
81
+ its folder bucket. Push while the folder is still in `in-progress/` and the
82
+ issue is set to the state the spec is *leaving*.
83
+ - **Before the commit**, because the push stamps ids into the spec and writes a
84
+ snapshot under `specs/.core/`. The `git add specs/` that follows sweeps both up
85
+ with the status change; push after it instead and those files are left
86
+ uncommitted, which makes `spec-env integrate` refuse to land the branch.
87
+
54
88
  Then **commit the cancellation edits** — this skill wrote them, so it commits
55
89
  them:
56
90
 
@@ -53,6 +53,40 @@ Before marking complete, confirm the work is actually finished:
53
53
  move the whole folder). The `specs/complete/` folder is the record of finished
54
54
  specs — `git log`/the per-spec State log give the completion order.
55
55
 
56
+ **Only when `specs/.core/linear.config.json` exists** and the spec's overview
57
+ carries a `linear_identifier`. Either missing → **skip**, in one line
58
+ (`not linked to Linear — /spec-push to mirror it`), and carry on. Nothing else in
59
+ this step changes.
60
+
61
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
62
+ become the truth about this spec's state, and the tracker is a generated mirror
63
+ of it — leaving them to diverge until someone remembers to push by hand is the
64
+ gap this exists to close. It costs one engine call and no model tokens when a
65
+ Linear API key is set (see `apply.transport` in `linear.config.md`).
66
+
67
+ - **Never mint.** An unlinked spec is skipped, not created. An issue born `Done`
68
+ or `Canceled` is tracker noise, and a spec kept deliberately local should stay
69
+ that way — `/spec-push` is how someone opts in.
70
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
71
+ and **finish the operation anyway**. The spec is complete/cancelled/reviewed in
72
+ the repo regardless; the mirror is disposable and the next push repairs it.
73
+ Do not roll anything back, and do not stop to ask.
74
+ - **Say what happened** in the skill's report: mirror updated, skipped as
75
+ unlinked, or failed with the reason.
76
+
77
+ ### Why it sits here
78
+
79
+ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
80
+ **after the `git mv` and before the commit**, and both halves matter:
81
+
82
+ - **After the move**, because the projection reads a spec's workflow state from
83
+ its folder bucket. Push while the folder is still in `in-progress/` and the
84
+ issue is set to the state the spec is *leaving*.
85
+ - **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
88
+ uncommitted, which makes `spec-env integrate` refuse to land the branch.
89
+
56
90
  Then **commit the completion edits** — steps 3–4 are this skill's own output, so
57
91
  it finishes its own work rather than handing you a dirty tree:
58
92
 
@@ -128,14 +162,14 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
128
162
  recover those commits onto the branch, then re-run.
129
163
  - *no worktree* — the spec is live but its worktree is gone. Re-isolate it with
130
164
  `skitterspec spec-env up <name>`, then re-run.
131
- Relay the diagnostic to the user and **stop** — do not proceed to teardown.
165
+ Relay the diagnostic to the user and **stop** — do not tear anything down.
132
166
  2. **Plan + execute.** Run `skitterspec spec-env integrate <name>` and run the
133
167
  printed commands **in order**:
134
168
  - `git -C <worktree> rebase <base>` — replay the branch onto base.
135
169
  - `git -C <mainRepoPath> merge --ff-only <branch>` — fast-forward base.
136
170
  On a **rebase conflict** (non-zero exit), run
137
171
  `git -C <worktree> rebase --abort`, relay the conflict, and **stop** — leave it
138
- to the user; do not offer teardown.
172
+ to the user; do not tear anything down.
139
173
  On a **no-op** ("already landed"), just say so and continue.
140
174
  3. **Re-test on base.** Run the project's test command from the primary checkout;
141
175
  it must be **green** before you call the landing done.
@@ -144,9 +178,22 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
144
178
 
145
179
  ## 7. Tear down the environment (opt-in, only if configured)
146
180
 
147
- **Only when `specs/.core/env.config.json` exists**, offer don't force — to
148
- reclaim the finished spec's environment. On confirmation, run the `spec-env` CLI
149
- directly (the old `/spec-env-down` skill is gone — teardown is folded in here):
181
+ **Only when `specs/.core/env.config.json` exists.** Reclaiming the environment is
182
+ what completing a spec *is*, so sub-steps 1–3 run **automatically do not ask**.
183
+ Run the `spec-env` CLI directly (the old `/spec-env-down` skill is gone —
184
+ teardown is folded in here).
185
+
186
+ **The precondition is that the work actually landed.** Only tear down when step 6
187
+ completed: it landed (or reported "already landed") **and** the base suite came
188
+ back green. A rebase conflict, a work-loss abort or a red suite means step 6 told
189
+ you to stop — tear nothing down, because the worktree is where the user picks the
190
+ problem up. That precondition is what makes a confirmation redundant: by the time
191
+ you get here the branch is an ancestor of base and the engine's guards have
192
+ nothing left to protect.
193
+
194
+ **Opt-out:** if the user passed **`--keep-env`**, skip sub-steps 1–3, say the
195
+ worktree and branch are being kept, and go straight to sub-step 4. Mention
196
+ `skitterspec spec-env up <name>` re-attaches it later either way.
150
197
 
151
198
  1. **Disconnect the proxy if this spec is connected.** If `.spec-env/connected`
152
199
  names this spec, run `skitterspec spec-env connect main` first so the
@@ -154,20 +201,28 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
154
201
  2. **Stop its host dev servers:** `skitterspec spec-env dev down <name>` (a
155
202
  no-op when none are running / configured).
156
203
  3. **Remove worktree + stack + slot:** run `skitterspec spec-env down <name>`
157
- and execute the commands it prints, in order. After a **feature/bug** landing
158
- the branch is merged into base, so teardown needs **no `--force`** and deletes
159
- the branch (`git branch -d`). After a **hotfix** landing the branch isn't
160
- merged (it was tagged + cherry-picked), but its head is captured by the deploy
161
- tag, so teardown still needs no `--force` and drops the branch with
162
- `git branch -D` (the tag holds the commits). It still respects the guards (won't
163
- destroy a dirty, or unpushed-and-unlanded, worktree without `--force`).
204
+ and execute the commands it prints, in order. After a landing — merged into
205
+ base for a **feature/bug**, captured by the deploy tag for a **hotfix**
206
+ teardown needs **no `--force`** and drops the branch with `git branch -D`,
207
+ which is safe precisely because the commits are already somewhere else. It
208
+ still respects the guards (won't destroy a dirty, or unpushed-and-unlanded,
209
+ worktree without `--force`), so if it *does* refuse, relay that and stop
210
+ rather than reaching for `--force`.
164
211
  4. **Reap orphaned test-DB volumes:** run `skitterspec spec-env prune`. It lists
165
212
  Docker volumes in the repo namespace that belong to **no live spec** (no
166
213
  worktree) — leftovers from declined/aborted teardowns, manual
167
214
  `git worktree remove`, or `--keep-volumes`. Show the user the orphan list and,
168
215
  **only on their confirmation**, execute the printed `docker volume rm`
169
- commands. Non-fatal: if prune can't run (Docker down) or the user declines,
170
- report it and finish completing anyway never block the spec on it. Skip when
171
- Docker isn't in use (the command self-reports "no orphaned volumes").
216
+ commands. **This one still asks**, unlike 1–3: it reaps volumes belonging to
217
+ *other* specs, and this spec having landed cleanly says nothing about those.
218
+ Non-fatal: if prune can't run (Docker down) or the user declines, report it and
219
+ finish completing anyway — never block the spec on it. Skip when Docker isn't
220
+ in use (the command self-reports "no orphaned volumes").
221
+
222
+ **Say what you reclaimed.** With no confirmation step the user never saw this
223
+ coming, so the final report must name the worktree path removed and the branch
224
+ deleted (or, under `--keep-env`, that both were kept). A teardown nobody
225
+ authorised and nobody was told about is the one way this step can lose someone's
226
+ place.
172
227
 
173
228
  If `env.config.json` is absent, skip this entirely — behave exactly as before.