@skitterbyte/skitterspec 17.0.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATION.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Migration guide
2
2
 
3
+ ## `@skitterbyte/skitterspec` v17 → v18 (a spec is built in its own worktree)
4
+
5
+ ### Breaking change
6
+
7
+ **`/spec-start` no longer moves the branch into your checkout.** In `worktree`
8
+ mode it provisions the spec's worktree, does the housekeeping there, opens a
9
+ session in it, and stops. The spec is built where it was provisioned — which is
10
+ what worktrees are for, and why `main` stays free.
11
+
12
+ This **supersedes the "one checkout holds one spec in flight" rule** described
13
+ under v16 → v17 below. That rule was a consequence of moving the branch into the
14
+ primary checkout; with the move gone, so is the restriction:
15
+
16
+ | v17 | v18 |
17
+ |-----|-----|
18
+ | `/spec-start` refused while another spec held your checkout | In `worktree` mode it only requires a **clean tree**. Several specs in flight is what the mode is for. |
19
+ | Starting a spec took two invocations, with a `/spec-live <name>` you typed in between | **One invocation.** No hand-off command, no re-run. |
20
+ | `/spec-live main` was one of the ways out of the gate | The ways out are `/spec-complete` and `/spec-cancel`. Parking to free a workbench is a one-workbench answer, and only `checkout` mode holds one spec now. |
21
+
22
+ **`/spec-live` is for testing only** — reusing your running dev server to reach a
23
+ spec at the canonical URL. It was never meant to be how work gets started, and no
24
+ lifecycle skill calls it.
25
+
26
+ `checkout` mode is unchanged: the branch is built in the primary checkout, the
27
+ gate still requires the workbench free, and `/spec-start` carries straight on
28
+ into phase 1 in the same session.
29
+
30
+ ### What to do
31
+
32
+ 1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
33
+ 2. **Expect a session, not a swap.** After `/spec-start` in `worktree` mode, run
34
+ `/spec-next` from the session it opens in the worktree. `/spec-next` builds
35
+ the spec it is *standing in* and refuses to build one from elsewhere.
36
+ 3. **Nothing to configure.** `spec.companionPaths` in `env.config.json` is new
37
+ and optional — it names paths that belong to a spec alongside its own folder
38
+ (a tracker's per-spec snapshot), so `/spec-start` can commit them together.
39
+ Empty by default; `/spec-linear-setup` sets it for you.
40
+
41
+ ## `@skitterbyte/skitterspec-linear` v11 → v12 (a spec is built in its own worktree)
42
+
43
+ The same change as `@skitterbyte/skitterspec` v17 → v18 above — this
44
+ distribution composes the same lifecycle skills. Read that entry; nothing here
45
+ is Linear-specific.
46
+
3
47
  ## `@skitterbyte/skitterspec` v16 → v17 (`/spec-go` splits in two)
4
48
 
5
49
  ### Breaking change
@@ -36,6 +80,14 @@ branch nobody asked for.
36
80
  provider distribution must rename its fragment file to match, or the build
37
81
  fails on an orphaned seam.
38
82
 
83
+ ## `@skitterbyte/skitterspec-linear` v10 → v11 (`/spec-go` splits in two)
84
+
85
+ The same change as `@skitterbyte/skitterspec` v16 → v17 above — this
86
+ distribution composes the same lifecycle skills, so `/spec-go` is removed here
87
+ too and replaced by `/spec-start` + `/spec-next`. Read that entry; nothing here
88
+ is Linear-specific. (Backfilled: the base entry was written when the change
89
+ landed and this one was missed.)
90
+
39
91
  ## `@skitterbyte/skitterspec-linear` v9 → v10 (`push` validates your issue states)
40
92
 
41
93
  **`spec-sync push` now refuses to run until the configured `states` names have
@@ -13,3 +13,13 @@ skill that wants one will tell you to type it.
13
13
  The skill table, the spec type/folder conventions and the per-spec isolation
14
14
  model all live in **`.claude/rules/spec-planning.md`**, the canonical reference
15
15
  every spec skill points at. Tailor its per-phase test commands to this stack.
16
+
17
+ **Release gating** *(only when `specs/.core/gating.config.json` exists)* — each
18
+ spec records whether it ships behind a feature flag, as
19
+ `> **Gating:** <flag name>` or `> **Gating:** none: <one-line reason>`. `/spec`,
20
+ `/spec-bug` and `/spec-hotfix` ask; `/spec-review`, `/spec-start` and
21
+ `/spec-complete` report a spec that has no answer, and never block over it.
22
+ `skitterspec gating check` lists them and always exits 0. The point is that the
23
+ question is **on the record**: a missing line is an oversight, a reason is a
24
+ decision. Skitterspec never reads your flag system — it asks and cites the doc
25
+ you point it at. Without that config, none of this appears.
@@ -33,6 +33,9 @@
33
33
  "pattern": "{type}/{slug}",
34
34
  "identifierField": ""
35
35
  },
36
+ "spec": {
37
+ "companionPaths": []
38
+ },
36
39
  "baseBranch": "",
37
40
  "guards": {
38
41
  "refuseTeardownIfDirty": true,
@@ -8,11 +8,11 @@ in-progress spec). Provisioning is folded into `/spec-start`, teardown into
8
8
  beneath them.
9
9
 
10
10
  **Once this file is present, isolation is the default policy:** `/spec-start` gives
11
- **every** in-progress spec its own git worktree automatically. Docker is a **per-
12
- spec escalation** — a spec brings up a stack only when its `> **Stack:**` header
13
- is `worktree + docker` (set at `/spec` when it touches the DB / stateful
14
- services). A `worktree`-only spec takes no registry slot, no port block, and no
15
- `.env`.
11
+ **every** in-progress spec its own git worktree automatically. Docker is a
12
+ **per- spec escalation** — a spec brings up a stack only when its
13
+ `> **Stack:**` header is `worktree + docker` (set at `/spec` when it touches
14
+ the DB / stateful services). A `worktree`-only spec takes no registry slot, no
15
+ port block, and no `.env`.
16
16
 
17
17
  **Adopt it** with `skitterspec init --isolation` (or copy
18
18
  `env.config.json.example` → `env.config.json` here) and edit the values. While
@@ -124,13 +124,18 @@ no live `env.config.json` was found.
124
124
  "host": "127.0.0.1" // bind host for the canonical ports
125
125
  },
126
126
 
127
- // Optional, editor/terminal-agnostic opener. `/spec-start` RUNS it when it hands
128
- // you into a new worktree after provisioning and bootstrap, so the session
129
- // opens onto a tree that is ready to work in. The template is expanded with
130
- // {worktreePath}, {slug}, {branch}, {projectName}, {portOffset}.
127
+ // Optional, editor/terminal-agnostic opener the FALLBACK for reaching a
128
+ // worktree. `/spec-start` normally moves the session you typed into into the
129
+ // worktree, and then there is nothing to open: it runs this only when it
130
+ // could not switch in place (the session is already inside another worktree,
131
+ // or the harness cannot move it). Run after provisioning and bootstrap, so
132
+ // the session opens onto a tree that is ready to work in. The template is
133
+ // expanded with {worktreePath}, {slug}, {branch}, {projectName},
134
+ // {portOffset}.
131
135
  // Empty = nothing is opened (the path is just printed), which is how you turn
132
- // the auto-open off. A non-interactive run skips it either way an opened
133
- // window nobody is sitting at helps no one.
136
+ // the auto-open off, and the right value unless you actually want a second
137
+ // window on the fallback path. A non-interactive run skips it either way — an
138
+ // opened window nobody is sitting at helps no one.
134
139
  // Examples: "code {worktreePath}", "tmux new-window -c {worktreePath}",
135
140
  // or a "warp://..." deeplink for Warp users.
136
141
  "open": {
@@ -153,6 +158,26 @@ no live `env.config.json` was found.
153
158
  "identifierField": ""
154
159
  },
155
160
 
161
+ // Paths that belong to a spec ALONGSIDE its own `specs/<bucket>/<name>/`
162
+ // folder. `/spec-start` uses this to tell "the spec you just wrote, not yet
163
+ // committed" apart from someone else's uncommitted work: if every dirty path
164
+ // belongs to the spec being started it is committed for you, and if a single
165
+ // path does not, the start is refused as before.
166
+ //
167
+ // Provider-neutral by design — the base engine must not know that any
168
+ // particular tracker exists — so you declare the shape here. `{slug}` and
169
+ // `{identifier}` expand exactly as in `branch.pattern` above, `{identifier}`
170
+ // via `branch.identifierField`.
171
+ //
172
+ // A pattern using {identifier} matches NOTHING when no identifier resolves
173
+ // (no `identifierField` set, or a spec never pushed to a tracker). That is
174
+ // deliberate: the file it names then belongs to some other spec, and the safe
175
+ // failure is a refusal you clear with /commit, not a stranger's file swept
176
+ // into your commit. Default: none — a spec owns only its own folder.
177
+ "spec": {
178
+ "companionPaths": []
179
+ },
180
+
156
181
  // Integration base branch — the branch specs fork from and land back onto
157
182
  // (used by the teardown "merged?" guard and, later, the integrate step).
158
183
  // Empty = auto-detect: origin/HEAD → main → master. Set it when your default
@@ -0,0 +1,4 @@
1
+ {
2
+ "guidance": "",
3
+ "default": "none: <reason>"
4
+ }
@@ -0,0 +1,81 @@
1
+ # `gating.config.json` — release-gating config
2
+
3
+ Opt-in config for **release gating**: whether each spec records a decision about
4
+ shipping behind a feature flag.
5
+
6
+ The problem it solves is not "we forgot to use a flag" — it is that
7
+ **nobody can tell whether the question was asked**. A spec can go from `/spec`
8
+ through
9
+ implementation to `/spec-complete` with no flag and no mention of one, and that
10
+ is indistinguishable from "we considered it and decided against". Prose guidance
11
+ in a project rule has already been shown not to close that gap; a required header
12
+ does, because its absence is visible.
13
+
14
+ **Skitterspec bakes in the offer, never the mechanism.** How your project does
15
+ flags — a resolver, an admin toggle, env precedence, a vendor SDK — is none of
16
+ its business, and it never reads your flag code. It asks the question, cites your
17
+ own documentation, and records the answer.
18
+
19
+ **Adopt it** by copying `gating.config.json.example` → `gating.config.json` here
20
+ (or `skitterspec init --gating`). While this file is absent the feature is
21
+ entirely unused: no grill question, no header, nothing to check — which is read
22
+ as "this project does not use feature flags".
23
+
24
+ ## Fields
25
+
26
+ ```jsonc
27
+ {
28
+ // Where THIS project documents how its flags work — a repo-relative path,
29
+ // cited when the question is asked so the answer is an informed one.
30
+ // Skitterspec never reads the file; it only names it. Empty = say nothing.
31
+ "guidance": ".claude/rules/feature-flags.md",
32
+
33
+ // The value written when the user declines a flag, so a project can
34
+ // standardise its wording. Must keep the `none: <reason>` shape — the reason
35
+ // half is the whole point (see below). Empty falls back to `none: <reason>`.
36
+ "default": "none: <reason>"
37
+ }
38
+ ```
39
+
40
+ ## The header it drives
41
+
42
+ With this file present, every spec `/spec`, `/spec-bug` and `/spec-hotfix` write
43
+ carries a `Gating:` field beside `Stack:` in `00-overview.md`:
44
+
45
+ ```
46
+ > **Gating:** search-ranking-v2
47
+ > **Gating:** none: additive, nothing to revert
48
+ ```
49
+
50
+ Two valid shapes, and one that is not:
51
+
52
+ | Value | Meaning |
53
+ |-------|---------|
54
+ | a flag name | ships behind that flag |
55
+ | `none: <reason>` | deliberately not flagged, and why |
56
+ | *missing, empty, or a bare `none`* | **not a decision** — nobody answered |
57
+
58
+ The reason half is load-bearing. `none: additive, nothing to revert` is a
59
+ decision a reviewer can disagree with; a bare `none` is a shrug, and a missing
60
+ line is an oversight. Distinguishing those three is the entire feature.
61
+
62
+ ## What it never does
63
+
64
+ - **It never blocks.** `skitterspec gating check` reports and exits 0;
65
+ `/spec-start` and `/spec-complete` mention a missing header and carry on.
66
+ Nothing here can stop a spec being started, completed or landed.
67
+ - **It never accuses an old spec.** Only `specs/backlog/` and
68
+ `specs/in-progress/` are read. Specs finished or abandoned before you adopted
69
+ gating are out of range by construction, not by a filter someone has to
70
+ remember.
71
+ - **It never learns your flag system.** `guidance` is a path it prints, nothing
72
+ more.
73
+
74
+ ## Checking
75
+
76
+ ```
77
+ skitterspec gating check # the spec in flight here
78
+ skitterspec gating check <spec> # one named spec
79
+ skitterspec gating check --all # every active spec
80
+ skitterspec gating check --json # for tooling
81
+ ```
@@ -11,8 +11,8 @@ and lifecycle stay consistent. Each sets a status on the spec header
11
11
  | `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
12
12
  | `/spec-hotfix` | (Hotfix) Fork a worktree from a release tag, red→green, land by tag + cherry-pick | `In Progress` | `specs/in-progress/` |
13
13
  | `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
14
- | `/spec-start` | Put a spec in flight on this checkout, then build phase 1 | `In Progress` | `specs/in-progress/` |
15
- | `/spec-next` | Build the next phase of the spec in flight (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
14
+ | `/spec-start` | Put a spec in flight provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
15
+ | `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
16
16
  | `/spec-to-main` | Land the branch on the base (rebase + ff) **without** finishing — for running the work in CI / a shared env mid-spec; repeatable | `In Progress` (unchanged) | (unchanged) |
17
17
  | `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
18
18
  | `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
@@ -22,9 +22,9 @@ and lifecycle stay consistent. Each sets a status on the spec header
22
22
  exercises judgment. `/spec-connect` and `/spec-live` are instead **slash commands**
23
23
  (`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
24
24
  relays its output, so there is no judgment to apply and no model turn spent
25
- finding one. They are marked `disable-model-invocation`, meaning **only you can
26
- run them** — a skill that wants one will tell you to type it rather than invoking
27
- it.
25
+ finding one. They are marked `disable-model-invocation`, meaning
26
+ **only you can run them** — a skill that wants one will tell you to type it
27
+ rather than invoking it.
28
28
 
29
29
  `/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
30
30
  real judgment (green tests before a land; an MCP fetch and a team-key check; ten
@@ -44,10 +44,11 @@ mid-spec (so the work can run in CI / a shared test env) while the spec stays
44
44
 
45
45
  **Two workspace modes.** `specs/.core/env.config.json` → `mode` decides where a
46
46
  spec's branch is built. **`worktree`** (the default) gives each spec its own
47
- checkout — several specs at once and `main` left free, at the cost of one
48
- terminal session per spec, which `/spec-start` sets up for you. **`checkout`** builds
49
- the branch in the primary checkout instead: one spec at a time, but no second
50
- session and no hand-off, so the terminal you are already in follows the work.
47
+ checkout — several specs at once and `main` left free and `/spec-start` moves
48
+ the session you typed into *into* that checkout, so the work carries on in the
49
+ same terminal. **`checkout`** builds the branch in the primary checkout instead:
50
+ one spec at a time, and nothing to move into. Neither mode costs you a second
51
+ terminal; the difference is how many specs can be in flight at once.
51
52
  Pick it for how you work rather than for what the project contains — a repo with
52
53
  no dev servers may still want several specs in flight. In `checkout` mode
53
54
  `/spec-connect` and `/spec-live` do not apply and say so: both exist to reach
@@ -86,7 +87,8 @@ feature at the normal URL — no second stack, no proxy. The branch checked out
86
87
  the primary checkout **is** the lock: exactly one spec is live at a time, and
87
88
  `/spec-live main` hands the instance back (fixes you make while live commit
88
89
  straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
89
- thumb: **live overlay is the light default for code-only specs**; it *refuses*
90
+ thumb: **live overlay is the light default for *testing* a code-only spec** it
91
+ is not how work gets started, and no lifecycle skill invokes it; it *refuses*
90
92
  stateful ones (`Stack: worktree + docker`, or a branch touching migrations) — keep
91
93
  `/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
92
94
  Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
@@ -118,6 +120,21 @@ With no provider installed the seams are empty and every skill behaves as a plai
118
120
  filesystem workflow. See the provider package's own docs for its config and field
119
121
  reference.
120
122
 
123
+ **Release gating (opt-in, config-gated).** With
124
+ `specs/.core/gating.config.json` present, every spec `/spec`, `/spec-bug` and
125
+ `/spec-hotfix` write carries a `> **Gating:**` header recording one decision:
126
+ does this ship behind a feature flag, or land live? The value is a **flag name**,
127
+ or **`none: <one-line reason>`** — and the reason half is the load-bearing part,
128
+ because `none: additive, nothing to revert` is a decision a reviewer can argue
129
+ with while a bare `none` is a shrug and a missing line is an oversight.
130
+
131
+ Skitterspec bakes in **the offer, never the mechanism**: it asks, cites the
132
+ project's own doc (`guidance` in that config), and records the answer. It never
133
+ reads your flag code. `skitterspec gating check` reports specs with no decision
134
+ and **always exits 0** — it reads only `backlog/` and `in-progress/`, so specs
135
+ finished before you adopted gating are out of range by construction. With the
136
+ config absent nothing appears at all: no question, no header, no check.
137
+
121
138
  ## Project conventions (fill this in)
122
139
 
123
140
  The spec skills tell you to run "your project's typecheck and test commands" and
@@ -160,6 +177,10 @@ Every spec header carries:
160
177
  to `git config user.name`).
161
178
  - `> **Developer:**` — who implements it (`—` until `/spec-start` starts work, then
162
179
  set to `git config user.name`; `/spec-bug` sets it immediately).
180
+ - `> **Gating:**` — the release-gating decision,
181
+ **only when `specs/.core/gating.config.json` exists**: a flag name, or
182
+ `none: <one-line reason>`. Absent entirely in a project that has not adopted
183
+ gating.
163
184
 
164
185
  Every spec also has a **State log** table — the audit trail of folder/status
165
186
  transitions. Each lifecycle skill appends exactly one row when it changes state:
@@ -49,7 +49,17 @@ not write the spec until this is resolved.
49
49
  `worktree + docker` only when it must. This sets the `> **Stack:**` header
50
50
  that `/spec-start` acts on (it can be escalated later). Skip when isolation
51
51
  isn't enabled — leave the default `worktree`.
52
- 10. **Open questions** anything still undecided.
52
+ 10. **Release gating** *(only when `specs/.core/gating.config.json` exists)* —
53
+ should this ship behind a feature flag, or land live?
54
+ **Offer, don't impose**: the user decides and you raise it, so a spec
55
+ never reaches
56
+ `/spec-complete` with the question unasked. Cite the project's own
57
+ `guidance` path from that config when it names one — skitterspec knows
58
+ nothing about how this project does flags, and must not guess. Record the
59
+ answer **either way**: a flag name, or `none: <one-line reason>`. "No" is a
60
+ decision and belongs in the header; silence is not. Skip entirely when the
61
+ config is absent — that project does not use flags.
62
+ 11. **Open questions** — anything still undecided.
53
63
 
54
64
  Stop grilling when there are no unresolved branches that would change the spec.
55
65
  Briefly play back the agreed understanding before writing.
@@ -108,6 +118,9 @@ the codebase, link rather than duplicate):
108
118
  > **Area:** <comma-separated files/modules this touches>
109
119
  > **Stack:** <worktree — or "worktree + docker" if it touches the DB/stateful
110
120
  > services; only acted on when isolation is enabled — see Phase A item 9>
121
+ > **Gating:** <flag name — or "none: <one-line reason>". Only when release gating
122
+ > is configured; omit the line entirely otherwise. An empty value or a bare
123
+ > "none" is not a valid outcome — see Phase A item 10>
111
124
 
112
125
  ## Problem
113
126
 
@@ -211,13 +224,14 @@ Rules for the spec body:
211
224
  phase index.
212
225
  - **Honour project conventions** when writing tasks — reference the relevant
213
226
  `.claude/rules/*.md` rather than re-explaining them.
214
- - **The `## Impact` table is derived from Phase A items 3 (Affected areas) & 5
215
- (Data/API impact)** a structured place to record what those already surface,
216
- not new grilling. It is the scannable substitute for spelling impact out in
217
- prose: name the surfaces (endpoints, schemas, DB tables, domain objects,
218
- routes, business rules) instead of describing them, keep `Detail` terse, and
219
- let it not paragraphs carry the blast radius. It complements the `Area:`
220
- header (files) by naming behavioural surfaces.
227
+ -
228
+ **The `## Impact` table is derived from Phase A items 3 (Affected areas) & 5 (Data/API impact)**
229
+ a structured place to record what those already surface, not new grilling.
230
+ It is the scannable substitute for spelling impact out in prose: name the
231
+ surfaces (endpoints, schemas, DB tables, domain objects, routes, business
232
+ rules) instead of describing them, keep `Detail` terse, and let it not
233
+ paragraphs carry the blast radius. It complements the `Area:` header
234
+ (files) by naming behavioural surfaces.
221
235
  - **Changelog** is mandatory and lives in the spec. Every later decision or
222
236
  course-correction gets a dated one-line entry. Convert relative dates to
223
237
  absolute.
@@ -242,6 +256,15 @@ run `skitterspec spec-env up <name>` to add Docker to an existing worktree). If
242
256
  `env.config.json` is absent, isolation is off — leave the default `worktree` and
243
257
  finish as above.
244
258
 
259
+ ## Phase D2 — record the gating decision (only if configured)
260
+
261
+ **Only when `specs/.core/gating.config.json` exists.** Make sure the
262
+ `> **Gating:**` header carries the Phase A item 10 answer — a flag name, or
263
+ `none: <reason>` using the config's `default` wording if it sets one. Nothing is
264
+ provisioned or enforced by this: the header exists so the decision is
265
+ **on the record and reviewable**, and `skitterspec gating check` reports a spec that has
266
+ none. It never blocks. If the config is absent, do not write the line at all.
267
+
245
268
  ## Phase E — link to a ticketing provider (only if one is installed)
246
269
 
247
270
  **Only when a ticketing provider is installed and configured** (it ships the
@@ -51,24 +51,12 @@ provision from — you'll flesh it out in §4:
51
51
  - Run `skitterspec spec-env up bug-<name>` (the `spec-env` CLI engine). It prints
52
52
  the `git worktree add … -b bug/<name>` command (a branch forked from `main`),
53
53
  the worktree path, the opener, and any `in the worktree, run:` bootstrap steps.
54
- - Run the printed `git worktree add`. **The worktree forks from `main`'s last
55
- commit, so your uncommitted stub doesn't travel with it**move it across so
56
- `main` is left pristine. **Create the destination bucket first:**
57
-
58
- ```
59
- mkdir -p <worktreePath>/specs/in-progress
60
- mv specs/in-progress/bug-<name> <worktreePath>/specs/in-progress/
61
- ```
62
-
63
- The `mkdir -p` is not belt-and-braces. Git does not store empty directories,
64
- so `specs/in-progress/` is **absent** from a fresh worktree whenever that
65
- bucket happens to be empty on `main` — the common case, since it empties every
66
- time the last in-progress spec completes. `mv` into a missing destination
67
- renames your spec folder **to** `specs/in-progress`, silently: the spec's files
68
- end up one level too high, `00-overview.md` sits where the bucket should be,
69
- and every later step still appears to work until something cannot find the
70
- spec. Confirm the result before carrying on — you want
71
- `<worktreePath>/specs/in-progress/bug-<name>/00-overview.md`.
54
+ - Run the printed commands in order. The plan **commits the stub first** — the
55
+ worktree forks from `main`'s last commit, so the stub has to be in it — and
56
+ then adds the worktree. Nothing to move afterwards: the spec is already there.
57
+ The commit is planned, not silent; it appears in the printed plan above the
58
+ `git worktree add`, and `spec-env up` refuses outright if anything *other* than
59
+ this spec is uncommitted.
72
60
  - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
73
61
  none of the repo's gitignored files (`.env`, local overrides). Run the printed
74
62
  `in the worktree, run:` steps (file seeding, then `setup`) in order, before
@@ -117,6 +105,8 @@ because work is already underway. Keep it lean:
117
105
  > **Developer:** <git user.name — you, since you're fixing it now>
118
106
  > **Raised:** <YYYY-MM-DD (today)>
119
107
  > **Area:** <files/modules>
108
+ > **Gating:** <flag name — or "none: <one-line reason>". Only when release gating
109
+ > is configured; omit the line entirely otherwise>
120
110
 
121
111
  ## Symptom
122
112
 
@@ -178,6 +168,19 @@ fix narrative and decisions — keep them separate.
178
168
 
179
169
 
180
170
 
171
+ ### Release gating (only when configured)
172
+
173
+ **Only when `specs/.core/gating.config.json` exists.** Before writing the spec,
174
+ ask: should this fix ship behind a feature flag, or land live?
175
+ **Offer, don't impose** — cite the project's own `guidance` path when the config
176
+ names one, and
177
+ record the answer either way, as a flag name or `none: <one-line reason>`.
178
+
179
+ A bug fix ships in the next release exactly like a feature, so it gets the same
180
+ question rather than an assumption. A risky rewrite of a broken path is precisely
181
+ where a kill-switch earns its keep; a one-line null check is precisely where
182
+ `none:` is the honest answer. Skip entirely when the config is absent.
183
+
181
184
  ## 5. Drive to GREEN
182
185
 
183
186
  - Implement the **minimal, root-cause** fix. Match surrounding code; honour all
@@ -79,8 +79,22 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
79
79
  main` first to free the canonical ports.
80
80
  2. `skitterspec spec-env dev down <name>` — stop its host dev servers.
81
81
  **Standing in the worktree? Leave it before you tear it down.** If this
82
- session's cwd is inside the spec's own worktree, `cd` to the primary checkout
83
- **first**, then run the teardown commands.
82
+ session's cwd is inside the spec's own worktree, get out **first**, then run the
83
+ teardown commands. How you leave depends on how you got in:
84
+
85
+ - **`/spec-start` moved this session in** — the normal path in `worktree` mode.
86
+ Call **`ExitWorktree`** with `action: "keep"`. It restores the session to the
87
+ directory it started from *and* clears the caches still pointing at the
88
+ worktree, which a bare `cd` does not: after a `cd` the session stays registered
89
+ against the tree and asks you about it again when it ends.
90
+ - **You opened the terminal yourself** — `cd` to the primary checkout, as always.
91
+ `ExitWorktree` is a no-op outside a session it moved, so trying it costs
92
+ nothing, but the `cd` is what relocates you.
93
+
94
+ **Always `keep`, never `remove`.** `ExitWorktree` refuses to remove a worktree
95
+ entered by path in any case, and the `spec-env down` plan below has to stay the
96
+ single thing that deletes — a second deleter is how the teardown guards get
97
+ bypassed.
84
98
 
85
99
  Not because git refuses — it does not. `git worktree remove` **succeeds** on the
86
100
  tree you are standing in, and that is the problem: the directory vanishes under
@@ -34,6 +34,23 @@ Before marking complete, confirm the work is actually finished:
34
34
  is its own, and step 4 commits that.
35
35
 
36
36
 
37
+ ### Release gating (only when configured)
38
+
39
+ **Only when `specs/.core/gating.config.json` exists.** Run
40
+ `skitterspec gating check <spec>` and report what it says. This is the last point
41
+ at which the question can still be answered cheaply, which is exactly why it is
42
+ asked here — a spec that reaches `Complete` with no decision recorded is the gap
43
+ the feature exists to close.
44
+
45
+ **Never refuse to complete over it.** Say plainly that no decision was recorded,
46
+ offer to record one now, and finish either way. The work is done; withholding the
47
+ status flip would punish the wrong thing.
48
+
49
+ **This check is advisory.** It reports; it never refuses, and nothing below is
50
+ conditional on it. A spec written before the project adopted gating has no header
51
+ and is not broken — turning this into a gate would accuse the very specs the
52
+ feature was designed not to disturb.
53
+
37
54
  ## 3. Update the spec
38
55
 
39
56
  - Tick all completed tasks in the phase files; flip every finished phase-file
@@ -63,9 +80,10 @@ git add specs/ && git commit -m "chore(spec): complete <name>"
63
80
  ```
64
81
 
65
82
  Step 2 established the tree was otherwise clean, so this commits exactly the
66
- status flip and the move — nothing of yours rides along. **This is what lets step
67
- 6 land:** `integrate` refuses a dirty worktree, so without committing here the
68
- skill would block on the very edits it just made. Do not `git push`.
83
+ status flip and the move — nothing of yours rides along.
84
+ **This is what lets step 6 land:** `integrate` refuses a dirty worktree, so
85
+ without committing here the skill would block on the very edits it just made.
86
+ Do not `git push`.
69
87
 
70
88
  ## 5. Report
71
89
 
@@ -172,8 +190,22 @@ worktree and branch are being kept, and go straight to sub-step 4. Mention
172
190
  2. **Stop its host dev servers:** `skitterspec spec-env dev down <name>` (a
173
191
  no-op when none are running / configured).
174
192
  **Standing in the worktree? Leave it before you tear it down.** If this
175
- session's cwd is inside the spec's own worktree, `cd` to the primary checkout
176
- **first**, then run the teardown commands.
193
+ session's cwd is inside the spec's own worktree, get out **first**, then run the
194
+ teardown commands. How you leave depends on how you got in:
195
+
196
+ - **`/spec-start` moved this session in** — the normal path in `worktree` mode.
197
+ Call **`ExitWorktree`** with `action: "keep"`. It restores the session to the
198
+ directory it started from *and* clears the caches still pointing at the
199
+ worktree, which a bare `cd` does not: after a `cd` the session stays registered
200
+ against the tree and asks you about it again when it ends.
201
+ - **You opened the terminal yourself** — `cd` to the primary checkout, as always.
202
+ `ExitWorktree` is a no-op outside a session it moved, so trying it costs
203
+ nothing, but the `cd` is what relocates you.
204
+
205
+ **Always `keep`, never `remove`.** `ExitWorktree` refuses to remove a worktree
206
+ entered by path in any case, and the `spec-env down` plan below has to stay the
207
+ single thing that deletes — a second deleter is how the teardown guards get
208
+ bypassed.
177
209
 
178
210
  Not because git refuses — it does not. `git worktree remove` **succeeds** on the
179
211
  tree you are standing in, and that is the problem: the directory vanishes under
@@ -192,16 +224,16 @@ only ordering that survives.
192
224
  worktree without `--force`), so if it *does* refuse, relay that and stop
193
225
  rather than reaching for `--force`.
194
226
 
195
- **If the plan prints a `remote branch — confirm with the user first:`
196
- section, ask before running it.** `/spec-start` pushed this branch when it
197
- provisioned, so the remote copy outlives teardown unless someone deletes it.
198
- The planner only ever offers this for a branch that has **landed**, so say so
199
- plainly when you ask — the commits are on the base branch (or captured by the
200
- deploy tag), so deleting the remote branch loses nothing. On a yes, run the
201
- printed `git push <remote> --delete <branch>`; on a no, leave it and say the
202
- remote branch is still there. Never fold it into the `run these:` batch — a
203
- project that wants it unattended sets `teardown.deleteRemoteBranch: "always"`
204
- in `env.config.json`, and then it appears in `run these:` instead of here.
227
+ **If the plan prints a `remote branch — confirm with the user first:` section, ask before running it.**
228
+ `/spec-start` pushed this branch when it provisioned, so the remote copy
229
+ outlives teardown unless someone deletes it. The planner only ever offers
230
+ this for a branch that has **landed**, so say so plainly when you ask — the
231
+ commits are on the base branch (or captured by the deploy tag), so deleting
232
+ the remote branch loses nothing. On a yes, run the printed
233
+ `git push <remote> --delete <branch>`; on a no, leave it and say the remote
234
+ branch is still there. Never fold it into the `run these:` batch — a project
235
+ that wants it unattended sets `teardown.deleteRemoteBranch: "always"` in
236
+ `env.config.json`, and then it appears in `run these:` instead of here.
205
237
  4. **Reap orphaned test-DB volumes:** run `skitterspec spec-env prune`. It lists
206
238
  Docker volumes in the repo namespace that belong to **no live spec** (no
207
239
  worktree) — leftovers from declined/aborted teardowns, manual