@skitterbyte/skitterspec-linear 5.0.0 → 7.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.
@@ -4,8 +4,8 @@ Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
4
4
  stack + host dev servers + a front-door proxy + an optional opener per
5
5
  in-progress spec). Provisioning is folded into `/spec-go`, teardown into
6
6
  `/spec-complete` · `/spec-cancel`, and traffic diversion is `/spec-connect`; the
7
- `skitterspec spec-env <up|down|dev|connect|integrate>` CLI is the engine beneath
8
- them.
7
+ `skitterspec spec-env <up|down|prune|dev|connect|integrate>` CLI is the engine
8
+ beneath them.
9
9
 
10
10
  **Once this file is present, isolation is the default policy:** `/spec-go` gives
11
11
  **every** in-progress spec its own git worktree automatically. Docker is a **per-
@@ -155,6 +155,20 @@ no live `env.config.json` was found.
155
155
  // refuses it (code-only v1 — use `/spec-connect` for those). Default: none.
156
156
  "live": {
157
157
  "migrations": []
158
+ },
159
+
160
+ // Hotfix landing (`spec-env hotfix land` / `/spec-complete` on a Type: Hotfix
161
+ // spec). A hotfix forks from a release tag and lands by tag + cherry-pick, not
162
+ // fast-forward. `bump` is the version-bump strategy for the new deploy tag (only
163
+ // "patch" today: v33.16.4 -> v33.16.5). `cherryPickMain` also cherry-picks the
164
+ // fix onto the base branch for the next release (default true). `targets` is an
165
+ // optional default list of extra base tags to also patch (test/demo lines);
166
+ // `--also <tag>` adds more at run time. Nothing is ever pushed — you push the
167
+ // deploy tag to trigger CI/CD. Default: patch, main, none.
168
+ "hotfix": {
169
+ "bump": "patch",
170
+ "cherryPickMain": true,
171
+ "targets": []
158
172
  }
159
173
  }
160
174
  ```
@@ -165,3 +179,26 @@ no live `env.config.json` was found.
165
179
  - `{repoSlug}` — `{repo}` lower-cased, non-alphanumerics collapsed to `-`
166
180
  (safe for a `COMPOSE_PROJECT_NAME`).
167
181
  - `{slug}` — the spec slug (folder name minus its `feat-`/`bug-` prefix).
182
+
183
+ ## Pruning orphaned test-DB volumes
184
+
185
+ `spec-env down` drops the finished spec's own Docker volume, but volumes leak
186
+ when that path is skipped — a declined/guard-aborted teardown, a manual
187
+ `git worktree remove`, or `--keep-volumes`. Over many worktrees these orphaned
188
+ DB volumes pile up and eat disk.
189
+
190
+ `skitterspec spec-env prune` reconciles the live volumes in the
191
+ `{repoSlug}_*` namespace against the specs that still have a **worktree** and
192
+ lists the orphans (volumes owned by no live spec) plus the `docker volume rm`
193
+ commands to remove them. It plans only — you run the printed commands — and it
194
+ frees any stale registry slot for a reaped spec.
195
+
196
+ - **Liveness = an existing worktree, not the registry** (the registry is what
197
+ goes stale). A spec with a live worktree — in any bucket, including one checked
198
+ out only on its branch — is always protected.
199
+ - **No backup.** Unlike `spec-env down`, prune does **not** run
200
+ `backupCommand`: an orphan has no running DB to dump. Add `--older-than <days>`
201
+ to only reap volumes older than a cutoff (volumes of unknown age are kept).
202
+ - `/spec-complete` and `/spec-cancel` run prune (confirm-first) as their last
203
+ teardown step, so orphans get swept as specs finish. You can also run it by
204
+ hand at any time to clear an existing backlog.
@@ -1,6 +1,6 @@
1
1
  # Spec Planning
2
2
 
3
- Spec-driven development is driven by seven lifecycle skills (plus `/spec-connect`
3
+ Spec-driven development is driven by nine lifecycle skills (plus `/spec-connect`
4
4
  when isolation is on) — use them rather than hand-rolling specs so the structure
5
5
  and lifecycle stay consistent. Each sets a status on the spec header
6
6
  (`> **Status:** …`):
@@ -9,8 +9,10 @@ and lifecycle stay consistent. Each sets a status on the spec header
9
9
  |-------|---------|--------|--------|
10
10
  | `/spec` | (Feature) Grill to a clear shared understanding, then write a groomed spec | `Ready` (or `Draft`) | `specs/backlog/` |
11
11
  | `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
12
+ | `/spec-hotfix` | (Hotfix) Fork a worktree from a release tag, red→green, land by tag + cherry-pick | `In Progress` | `specs/in-progress/` |
12
13
  | `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
13
14
  | `/spec-go` | Provision the env, bring dev servers up, implement the next phase | `In Progress` | `specs/in-progress/` |
15
+ | `/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) |
14
16
  | `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
15
17
  | `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
16
18
  | `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
@@ -18,8 +20,12 @@ and lifecycle stay consistent. Each sets a status on the spec header
18
20
  Status flow: `Ready → In Progress → Complete` (or `Cancelled` from any state).
19
21
  `/spec` grills to a **Ready** spec directly — there is no separate grooming
20
22
  command; it writes `Draft` only when open questions are deliberately left.
21
- `/spec-bug` is test-first and starts straight in `In Progress` (work begins
22
- immediately), so it skips Draft/Ready.
23
+ `/spec-bug` and `/spec-hotfix` are test-first and start straight in `In Progress`
24
+ (work begins immediately), so they skip Draft/Ready. `/spec-to-main` does **not**
25
+ move the spec through the flow at all — it lands the branch on the base branch
26
+ mid-spec (so the work can run in CI / a shared test env) while the spec stays
27
+ `In Progress` in `specs/in-progress/`; it's the intermediate, repeatable half of
28
+ `/spec-complete`'s landing, without the finalise-and-tear-down.
23
29
 
24
30
  **Per-spec isolation (opt-in to adopt, then the default policy).** When a project
25
31
  adopts isolation (`skitterspec init --isolation`, or `specs/.core/env.config.json`
@@ -35,9 +41,14 @@ your canonical `localhost` ports so you can test it at the normal URL
35
41
  move, header edits, the code) happens on the spec's branch in the worktree; `main`
36
42
  changes only when it merges. Teardown is folded into `/spec-complete` ·
37
43
  `/spec-cancel`. Beneath the skills, `skitterspec spec-env
38
- <up|down|dev|connect|integrate>` is the CLI engine. Isolation is **orthogonal to
39
- lifecycle status** and inactive when `env.config.json` is absent — every skill
40
- then behaves as it does today.
44
+ <up|down|prune|dev|connect|integrate|hotfix>` is the CLI engine. Teardown drops
45
+ the finished spec's own test-DB volume; `spec-env prune` additionally reaps
46
+ **orphaned** volumes left by declined/aborted teardowns, so `/spec-complete` and
47
+ `/spec-cancel` also sweep orphans (confirm-first). A **hotfix** is the one
48
+ exception to "fork from `main`": `/spec-hotfix` forks the worktree from a release
49
+ tag and `/spec-complete` lands it via `spec-env hotfix land` (tag + cherry-pick),
50
+ not a fast-forward. Isolation is **orthogonal to lifecycle status** and inactive
51
+ when `env.config.json` is absent — every skill then behaves as it does today.
41
52
 
42
53
  **Live overlay (`/spec-live`) — the light way to test a spec.** `/spec-connect`
43
54
  runs a spec's *own* dev stack and proxies the canonical ports to it (one stack per
@@ -77,16 +88,24 @@ consistent with the codebase:
77
88
  - **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
78
89
  (architecture, code style, testing, database, etc.) rather than restating them.
79
90
 
80
- ## Spec types — Feature vs Bug
91
+ ## Spec types — Feature, Bug, Hotfix
81
92
 
82
- Every spec is one of two types, recorded **both** in the header and the filename:
93
+ Every spec is one of three types, recorded **both** in the header and the filename:
83
94
 
84
- - **Header field:** `> **Type:** Feature` or `> **Type:** Bug` (authoritative,
85
- greppable: `grep -rl 'Type:.*Bug' specs/`).
86
- - **Filename prefix:** `feat-<name>` for features, `bug-<name>` for bugs
87
- (visible in listings; glob-safe never use `[BUG]`/`[FEATURE]` brackets).
95
+ - **Header field:** `> **Type:** Feature`, `> **Type:** Bug`, or
96
+ `> **Type:** Hotfix` (authoritative, greppable:
97
+ `grep -rl 'Type:.*Hotfix' specs/`).
98
+ - **Filename prefix:** `feat-<name>` for features, `bug-<name>` for bugs,
99
+ `hotfix-<name>` for hotfixes (visible in listings; glob-safe — never use
100
+ `[BUG]`/`[FEATURE]` brackets).
88
101
 
89
- Both types share the same lifecycle folders below type is orthogonal to status.
102
+ A **Hotfix** is a Bug fixed against a **released tag** rather than `main`: it
103
+ carries an extra `> **Base version:** <tag>` header, forks its worktree from that
104
+ tag, and lands by tagging a new patch + cherry-picking onto `main` (never a
105
+ fast-forward merge). `/spec-live` refuses a hotfix — test it with `/spec-connect`.
106
+
107
+ All three types share the same lifecycle folders below — type is orthogonal to
108
+ status.
90
109
 
91
110
  ## Header fields & State log (audit trail)
92
111
 
@@ -140,9 +159,11 @@ Every spec is a **folder** `specs/<bucket>/<name>/` — never a bare file, even
140
159
  simple changes. Inside it:
141
160
 
142
161
  - `00-overview.md` is the entry point / dashboard: header, Problem, Decisions,
143
- Solution overview, the **phase index** (a table linking to each phase file with
144
- its status), Open questions, State log, Changelog. **No per-phase task lists
145
- live here.**
162
+ Solution overview, the **Impact map** (a `Surface | Change | Detail` table
163
+ naming the concrete surfaces the spec touches endpoints, schemas, DB tables,
164
+ domain objects, routes, business rules — as the scannable blast radius), the
165
+ **phase index** (a table linking to each phase file with its status), Open
166
+ questions, State log, Changelog. **No per-phase task lists live here.**
146
167
  - **One file per phase** — `01-<phase-slug>.md`, `02-<phase-slug>.md`, … in
147
168
  execution order. Each holds that phase's goal, its task checkboxes (tests
148
169
  included), and any phase-specific notes. Even a single-phase spec gets `01-….md`
@@ -96,6 +96,26 @@ the spec — be specific.>
96
96
  <Short prose or bullets describing the chosen shape end-to-end. Optional small
97
97
  schema/grammar/output snippets where they remove ambiguity.>
98
98
 
99
+ ## Impact
100
+
101
+ <The concrete surfaces this spec touches — the scannable blast radius, so a
102
+ reader can eyeball where the spec got something wrong without reading prose.
103
+ `Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
104
+ values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
105
+ Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
106
+ this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
107
+ terse — names/signatures, not sentences. List **only** surfaces that actually
108
+ change; the heading is always present, but if nothing external changes write the
109
+ single line below instead of an empty table.>
110
+
111
+ | Surface | Change | Detail |
112
+ |---------|--------|--------|
113
+ | <e.g. Endpoint> | add | <e.g. POST /orders> |
114
+ | <e.g. DB> | update | <e.g. orders (+status col)> |
115
+
116
+ <_No external surface changes — internal refactor only._ — use this line in
117
+ place of the table when the spec touches no external surface.>
118
+
99
119
  ## Phases
100
120
 
101
121
  Each phase lives in its own file in this folder. Status: ⬜ not started ·
@@ -163,6 +183,13 @@ Rules for the spec body:
163
183
  phase index.
164
184
  - **Honour project conventions** when writing tasks — reference the relevant
165
185
  `.claude/rules/*.md` rather than re-explaining them.
186
+ - **The `## Impact` table is derived from Phase A items 3 (Affected areas) & 5
187
+ (Data/API impact)** — a structured place to record what those already surface,
188
+ not new grilling. It is the scannable substitute for spelling impact out in
189
+ prose: name the surfaces (endpoints, schemas, DB tables, domain objects,
190
+ routes, business rules) instead of describing them, keep `Detail` terse, and
191
+ let it — not paragraphs — carry the blast radius. It complements the `Area:`
192
+ header (files) by naming behavioural surfaces.
166
193
  - **Changelog** is mandatory and lives in the spec. Every later decision or
167
194
  course-correction gets a dated one-line entry. Convert relative dates to
168
195
  absolute.
@@ -120,6 +120,26 @@ because work is already underway. Keep it lean:
120
120
  commands — confirm no regressions.
121
121
  - [ ] <any follow-up hardening, or "None">
122
122
 
123
+ ## Impact
124
+
125
+ <The concrete surfaces this spec touches — the scannable blast radius, so a
126
+ reader can eyeball where the spec got something wrong without reading prose.
127
+ `Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
128
+ values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
129
+ Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
130
+ this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
131
+ terse — names/signatures, not sentences. List **only** surfaces that actually
132
+ change; the heading is always present, but if nothing external changes write the
133
+ single line below instead of an empty table. A bug fix often changes no external
134
+ surface — that's fine, use the one-liner.>
135
+
136
+ | Surface | Change | Detail |
137
+ |---------|--------|--------|
138
+ | <e.g. Endpoint> | update | <e.g. GET /orders (fix null total)> |
139
+
140
+ <_No external surface changes — internal refactor only._ — use this line in
141
+ place of the table when the spec touches no external surface.>
142
+
123
143
  ## State log
124
144
 
125
145
  | Date | Status | Folder | By |
@@ -64,5 +64,10 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
64
64
  3. `skitterspec spec-env down <name>` — then execute the printed commands to
65
65
  remove the worktree/stack and free the slot. It respects the teardown guards
66
66
  (won't destroy a dirty/unpushed worktree without `--force`).
67
+ 4. `skitterspec spec-env prune` — reap orphaned test-DB volumes that belong to no
68
+ live spec (leftovers from declined/aborted teardowns or manual worktree
69
+ removal). Show the orphan list and, **only on the user's confirmation**, run
70
+ the printed `docker volume rm` commands. Non-fatal: if it can't run or the user
71
+ declines, report and finish cancelling anyway.
67
72
 
68
73
  If `env.config.json` is absent, skip this entirely — behave exactly as before.
@@ -21,8 +21,9 @@ Before marking complete, confirm the work is actually finished:
21
21
  done in the code — tick it (`- [x]`) if so, or surface it if not.
22
22
  - Run the project's typecheck and test commands. The suite must be **green** to
23
23
  call a spec complete.
24
- - For a **Bug** spec (`Type: Bug`), confirm the originally-failing test named in
25
- the spec now passes — that test is the proof the bug is fixed.
24
+ - For a **Bug** or **Hotfix** spec (`Type: Bug` / `Type: Hotfix`), confirm the
25
+ originally-failing test named in the spec now passes — that test is the proof
26
+ the fix works.
26
27
  - If genuinely incomplete work remains, **stop and tell the user** rather than
27
28
  forcing completion. Offer to finish it (`/spec-go`) or to complete with the
28
29
  remaining items explicitly listed as deferred.
@@ -51,13 +52,45 @@ specs — `git log`/the per-spec State log give the completion order.
51
52
  Confirm the move, the final test result, and list anything deferred. Do **not**
52
53
  `git commit` unless the user asks.
53
54
 
54
- ## 6. Integrate onto the base branch (opt-in, only if isolated)
55
+ ## 6. Land the branch (opt-in, only if isolated)
55
56
 
56
57
  **Only when `specs/.core/env.config.json` exists and the spec is on a worktree**
57
- (it was provisioned by `/spec-go`). Otherwise skip this entirely — a non-isolated
58
- spec has nothing to land, and `/spec-complete` behaves exactly as before. When it
59
- applies, offer to land the finished branch on the base branch so the work reaches
60
- `main` (or your configured `baseBranch`) in one flow:
58
+ (it was provisioned by `/spec-go` or `/spec-hotfix`). Otherwise skip this entirely
59
+ — a non-isolated spec has nothing to land, and `/spec-complete` behaves exactly as
60
+ before. When it applies, offer to land the finished branch so the work reaches its
61
+ destination in one flow. **How it lands depends on the spec type:**
62
+
63
+ ### 6-hotfix. `Type: Hotfix` — tag + cherry-pick (not fast-forward)
64
+
65
+ A hotfix is built on an old release **tag**, so it can't fast-forward onto `main`.
66
+ Use the hotfix landing instead of the integrate steps below:
67
+
68
+ 1. **Require a clean worktree** — the completion edits (status flip, `git mv` to
69
+ `complete/`) must be committed first. If dirty, offer `/commit` and **stop**.
70
+ 2. **Plan + execute.** Run `skitterspec spec-env hotfix land <name>` — add
71
+ `--also <tag>` for each extra release line to patch (test/demo on their own
72
+ versions). Run the printed commands **in order**. It:
73
+ - tags the hotfix branch with the **patch-bumped base tag** (the deploy tag) —
74
+ **created locally; you push it** (`git push origin <tag>`) to trigger CI/CD;
75
+ - for each `--also` target, cherry-picks the fix onto a throwaway worktree at
76
+ that tag and re-tags it;
77
+ - cherry-picks the fix onto `main` for the next release.
78
+ On a **cherry-pick conflict** (non-zero exit), run `git -C <checkout>
79
+ cherry-pick --abort` there, relay the conflict, and **stop** — don't offer
80
+ teardown.
81
+ On a **no-op** ("nothing to land"), say so and continue.
82
+ 3. **Re-test on `main`** after the cherry-pick — it must be **green**.
83
+ 4. **Report** the deploy tag(s) and the `main` cherry-pick. It **never pushes** —
84
+ remind the user to `git push origin <deploy-tag>` to deploy. Then teardown
85
+ (step 7) applies: a tagged hotfix branch tears down with **no `--force`**.
86
+
87
+ ### 6-feature/bug. `Type: Feature` / `Type: Bug` — rebase + fast-forward
88
+
89
+ Land the finished branch on the base branch so the work reaches `main` (or your
90
+ configured `baseBranch`) in one flow. (Need the work on `main` *before* the spec
91
+ is finished — e.g. to run a later phase in CI or a shared test env? Use
92
+ **`/spec-to-main`**: same rebase + fast-forward, but it leaves the spec
93
+ `In Progress` and the worktree standing, and it's repeatable.)
61
94
 
62
95
  1. **Require a clean worktree.** The completion edits (status flip, the
63
96
  `git mv` to `complete/`) must be committed first — integrate refuses a dirty
@@ -68,6 +101,16 @@ applies, offer to land the finished branch on the base branch so the work reache
68
101
  the normal landing plan. Commit any live fixes to the branch first; it refuses
69
102
  if the primary checkout is dirty, or if a *different* spec holds it (release that
70
103
  one with `/spec-live main`). Teardown (step 7) is unchanged.
104
+ **Work-loss abort.** Before it ends the live session, `integrate` checks the
105
+ work is actually landable and **aborts loudly** rather than finalize a spec
106
+ having landed nothing. Two cases, both leaving the live session intact:
107
+ - *stranded commits* — commits sit on the worktree's **detached HEAD** (e.g. a
108
+ pre-fix `/spec-go` committed there instead of on the branch). It prints the
109
+ count, the sha, and a `git -C <worktree> branch <tmp> <sha>` recovery hint —
110
+ recover those commits onto the branch, then re-run.
111
+ - *no worktree* — the spec is live but its worktree is gone. Re-isolate it with
112
+ `skitterspec spec-env up <name>`, then re-run.
113
+ Relay the diagnostic to the user and **stop** — do not proceed to teardown.
71
114
  2. **Plan + execute.** Run `skitterspec spec-env integrate <name>` and run the
72
115
  printed commands **in order**:
73
116
  - `git -C <worktree> rebase <base>` — replay the branch onto base.
@@ -93,9 +136,20 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
93
136
  2. **Stop its host dev servers:** `skitterspec spec-env dev down <name>` (a
94
137
  no-op when none are running / configured).
95
138
  3. **Remove worktree + stack + slot:** run `skitterspec spec-env down <name>`
96
- and execute the commands it prints, in order. Post-integrate the branch is
97
- merged into base, so teardown needs **no `--force`** and deletes the branch
98
- (`git branch -d`) as part of the plan. It still respects the guards (won't
99
- destroy a dirty or unpushed-and-unmerged worktree without `--force`).
139
+ and execute the commands it prints, in order. After a **feature/bug** landing
140
+ the branch is merged into base, so teardown needs **no `--force`** and deletes
141
+ the branch (`git branch -d`). After a **hotfix** landing the branch isn't
142
+ merged (it was tagged + cherry-picked), but its head is captured by the deploy
143
+ tag, so teardown still needs no `--force` and drops the branch with
144
+ `git branch -D` (the tag holds the commits). It still respects the guards (won't
145
+ destroy a dirty, or unpushed-and-unlanded, worktree without `--force`).
146
+ 4. **Reap orphaned test-DB volumes:** run `skitterspec spec-env prune`. It lists
147
+ Docker volumes in the repo namespace that belong to **no live spec** (no
148
+ worktree) — leftovers from declined/aborted teardowns, manual
149
+ `git worktree remove`, or `--keep-volumes`. Show the user the orphan list and,
150
+ **only on their confirmation**, execute the printed `docker volume rm`
151
+ commands. Non-fatal: if prune can't run (Docker down) or the user declines,
152
+ report it and finish completing anyway — never block the spec on it. Skip when
153
+ Docker isn't in use (the command self-reports "no orphaned volumes").
100
154
 
101
155
  If `env.config.json` is absent, skip this entirely — behave exactly as before.
@@ -22,9 +22,24 @@ Diverting your browser to the spec is a separate explicit step — `/spec-connec
22
22
 
23
23
  ## 2. Move it into development
24
24
 
25
- **If per-spec isolation is enabled** (`specs/.core/env.config.json` exists) and
26
- the spec doesn't already have a worktree, provision it **first**, so all the
27
- housekeeping below lands on the spec's branch and never on `main`:
25
+ **Live check first (isolation only).** If per-spec isolation is enabled, before
26
+ provisioning run `skitterspec spec-env live status <name>` and read its `live:`
27
+ line. If it says **`live: yes`**, this spec is already checked out in the
28
+ **primary checkout** (you took it live with `/spec-live`) — **do not provision,
29
+ do not run `spec-env up`, and do not "work in the worktree"**. Its branch lives
30
+ in the primary checkout and its worktree is on a **detached HEAD**, so a commit
31
+ made in the worktree would strand on that detached HEAD and never reach the
32
+ branch. Instead skip the provisioning bullets and step 2b, leave the spec where
33
+ it is, and go straight to **step 4**, implementing the phase **in the primary
34
+ checkout on the branch** — edits and commits there advance the branch, and
35
+ `/spec-complete` lands them. (`spec-env up` refuses while live and says the same.
36
+ To return to an isolated worktree instead, run `/spec-live main` first, then
37
+ re-run `/spec-go`.)
38
+
39
+ **If per-spec isolation is enabled** (`specs/.core/env.config.json` exists), the
40
+ spec **isn't already live** (the check above), and it doesn't already have a
41
+ worktree, provision it **first**, so all the housekeeping below lands on the
42
+ spec's branch and never on `main`:
28
43
 
29
44
  **Opt-out:** if the user passes `--no-worktree` (or explicitly asks to work in
30
45
  place), skip the provisioning bullets below and build on the current branch — the
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: spec-hotfix
3
+ description: Fix a production bug on the exact released version — fork a worktree from a release tag, drive it red→green like /spec-bug, then land it by tagging a new patch (for CI/CD to deploy) and cherry-picking the fix back onto main. ALWAYS starts from a base tag and works on the hotfix's own branch, never on main. Creates specs/in-progress/hotfix-<name>/00-overview.md. Use when the user says "/spec-hotfix", "hotfix <tag>", "prod is broken on <version>", "patch the released version", or needs a fix shipped against a tagged release rather than main.
4
+ ---
5
+
6
+ # /spec-hotfix — fix a released version, tag it, cherry-pick back to main
7
+
8
+ This is the **hotfix** counterpart to `/spec-bug`. Same test-first discipline
9
+ (reproduce as a **failing test (RED)**, then drive to **GREEN**), but the base is
10
+ a **release tag**, not `main`: prod is running a tagged version, so the fix must
11
+ be built on **that** commit line, shipped as a **new patch tag** (your CI/CD
12
+ deploys tags), and only then cherry-picked onto `main` for the next release.
13
+
14
+ Spec type convention (see `.claude/rules/spec-planning.md`):
15
+ - Hotfix specs are named `hotfix-<kebab-name>`; every header carries
16
+ `> **Type:** Hotfix` and `> **Base version:** <tag>`.
17
+ - Branch is `hotfix/<slug>`, forked from the base tag.
18
+
19
+ **Isolation is required.** A hotfix forks a worktree from a tag and lands by
20
+ tag + cherry-pick — it needs the isolation engine (`specs/.core/env.config.json`).
21
+ If isolation is absent, say so and stop; there is no in-place path.
22
+
23
+ ## 1. Establish the base version (the tag)
24
+
25
+ - Take the release tag from the argument — `/spec-hotfix <tag> <name>` (e.g.
26
+ `/spec-hotfix v33.16.4 login-crash`). If it's missing, **ask which version prod
27
+ is running** — don't guess.
28
+ - **Verify the tag exists** before anything else:
29
+ `git rev-parse --verify <tag>^{commit}`. If it doesn't resolve, stop and ask.
30
+
31
+ ## 2. Reproduce & isolate (light investigation)
32
+
33
+ Hotfixes are concrete — confirm, don't over-grill. Establish:
34
+
35
+ - **Repro:** exact steps / input that triggers it on the released version.
36
+ - **Expected vs actual:** what *should* happen vs what does.
37
+ - **Root cause:** read the code **at the base tag** and trace it to `file:line`.
38
+ The fix belongs on the tag's line, so reason about that code, not `main`'s.
39
+
40
+ ## 3. Seed the stub, then provision the worktree from the tag
41
+
42
+ The engine forks the worktree from the spec's `Base version`, so the stub — with
43
+ that header — must exist **before** `spec-env up`:
44
+
45
+ - From the base branch (`main`), create
46
+ `specs/in-progress/hotfix-<name>/00-overview.md` with the header block
47
+ (including `> **Type:** Hotfix` and `> **Base version:** <tag>`) and the
48
+ `## Symptom` you established. It starts in `in-progress` — work begins now.
49
+ - Run `skitterspec spec-env up hotfix-<name>`. It prints a `git worktree add …
50
+ -b hotfix/<slug> <tag>` command (the branch forks from **the tag**, not
51
+ `main`), the worktree path, the opener, and any `in the worktree, run:`
52
+ bootstrap steps.
53
+ - Run the printed `git worktree add`. **The worktree is checked out at the tag,
54
+ so your uncommitted stub doesn't travel with it** — move it across so `main`
55
+ stays pristine:
56
+ `mv specs/in-progress/hotfix-<name> <worktreePath>/specs/in-progress/`.
57
+ - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
58
+ none of the repo's gitignored files (`.env`, local overrides). Run the printed
59
+ `in the worktree, run:` steps (file seeding, then setup) in order, before
60
+ anything else.
61
+ - **Trust the worktree for this session.** The engine wrote the printed
62
+ `trusted:` root into `.claude/settings.local.json`, but it won't hot-reload now
63
+ — run `/add-dir <trusted root>` before editing into the worktree, or the first
64
+ edits will prompt.
65
+ - **Do everything below in the worktree**, on the `hotfix/<slug>` branch — the red
66
+ test, the fix, and the rest of the spec. Act with absolute paths /
67
+ `git -C <worktreePath>`, or open a fresh session rooted there. `main` changes
68
+ only at `/spec-complete` (via cherry-pick, not merge).
69
+
70
+ ## 4. Write the failing test FIRST (RED) — mandatory
71
+
72
+ Encode the **correct** (expected) behaviour as a test, then run it and confirm it
73
+ **fails for the right reason**, on the hotfix branch:
74
+
75
+ - Put it where the suite already covers that area. Reuse existing test helpers /
76
+ factories; follow the project's test rules (see `.claude/rules/`). Never
77
+ hardcode dates — compute them relative to now.
78
+ - Run it with the project's test command. Quote the red output. A test that
79
+ passes before the fix proves nothing — keep refining until it genuinely
80
+ captures the bug on this version.
81
+
82
+ ## 5. Write the Hotfix spec
83
+
84
+ Flesh out `00-overview.md` in the worktree (you seeded the stub in §3). A hotfix
85
+ is usually a single-pass fix, so the `## Fix` block can live directly in
86
+ `00-overview.md`. Keep it lean:
87
+
88
+ ```markdown
89
+ # Hotfix: <short title>
90
+
91
+ > **Type:** Hotfix
92
+ > **Status:** In Progress — fixing (red test added)
93
+ > **Author:** <git user.name — who reported/captured it>
94
+ > **Developer:** <git user.name — you, since you're fixing it now>
95
+ > **Base version:** <tag prod is running, e.g. v33.16.4>
96
+ > **Raised:** <YYYY-MM-DD (today)>
97
+ > **Area:** <files/modules>
98
+
99
+ ## Symptom
100
+
101
+ <observed wrong behaviour on the released version + repro steps; paste any error>
102
+
103
+ ## Root cause
104
+
105
+ <the actual cause, at `file:line` on the base tag. One paragraph — be specific.>
106
+
107
+ ## Failing test (red)
108
+
109
+ <test name + path; what it asserts. How to run it. Paste the red failure line.>
110
+
111
+ ## Fix
112
+
113
+ - [ ] <the minimal change that addresses the root cause, not the symptom>
114
+ - [ ] Failing test now passes (GREEN); run the project's typecheck and test
115
+ commands — confirm no regressions.
116
+ - [ ] <any follow-up hardening, or "None">
117
+
118
+ ## Impact
119
+
120
+ <The concrete surfaces this spec touches — the scannable blast radius, so a
121
+ reader can eyeball where the spec got something wrong without reading prose.
122
+ `Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
123
+ values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
124
+ Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
125
+ this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
126
+ terse — names/signatures, not sentences. List **only** surfaces that actually
127
+ change; the heading is always present, but if nothing external changes write the
128
+ single line below instead of an empty table. A hotfix should be minimal — often
129
+ no external surface changes; that's fine, use the one-liner.>
130
+
131
+ | Surface | Change | Detail |
132
+ |---------|--------|--------|
133
+ | <e.g. Endpoint> | update | <e.g. GET /orders (fix null total)> |
134
+
135
+ <_No external surface changes — internal refactor only._ — use this line in
136
+ place of the table when the spec touches no external surface.>
137
+
138
+ ## Landing
139
+
140
+ - [ ] Deploy tag (patch bump of the base version) created at `/spec-complete`
141
+ and pushed **by you** to trigger CI/CD.
142
+ - [ ] Fix cherry-picked onto `main` (and any `--also` release lines).
143
+
144
+ ## State log
145
+
146
+ | Date | Status | Folder | By |
147
+ |------|--------|--------|----|
148
+ | <YYYY-MM-DD> | In Progress | in-progress | <developer> |
149
+
150
+ ## Changelog
151
+
152
+ - <YYYY-MM-DD> — Hotfix reproduced on <tag>; failing test added (red).
153
+ ```
154
+
155
+ Keep the **State log** (state transitions) separate from the **Changelog** (fix
156
+ narrative and decisions).
157
+
158
+ ## 6. Drive to GREEN
159
+
160
+ - Implement the **minimal, root-cause** fix on the branch. Match surrounding code;
161
+ honour all project rules (see `.claude/rules/`).
162
+ - Re-run the failing test → it must pass. Then run the project's typecheck and
163
+ test commands to confirm no regressions. Quote results.
164
+ - Commit the fix to the `hotfix/<slug>` branch (this commit is what gets tagged
165
+ and cherry-picked). Tick the Fix tasks; add a Changelog line.
166
+
167
+ ## 7. Report
168
+
169
+ Summarise: the base tag, root cause, the failing→passing test, the fix, and the
170
+ full test result. The spec stays in `in-progress`.
171
+
172
+ - **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
173
+ hot-reloading it onto the running dev server could break the shared instance.
174
+ To test it, use `/spec-connect` (its own isolated stack).
175
+ - Suggest **`/spec-complete`** to land it: it patch-bumps the base tag, tags the
176
+ hotfix branch **locally** (you push it to deploy), and cherry-picks the fix onto
177
+ `main`. Add `--also <tag>` at completion to also patch other release lines
178
+ (test/demo on their own versions).
179
+
180
+ Do **not** `git push` or `git tag`-and-push unless the user asks — deploying to
181
+ prod is theirs to trigger.
@@ -18,7 +18,10 @@ absent, say so and stop.
18
18
  **Code-only.** Live overlay refuses a **stateful** spec — one whose `> **Stack:**`
19
19
  is `worktree + docker`, or whose branch changes migrations (per
20
20
  `env.config.json` → `live.migrations`). Those keep their isolated stack; use
21
- `/spec-connect` for them. The engine enforces this and prints why.
21
+ `/spec-connect` for them. It also **always refuses a `Type: Hotfix` spec** — its
22
+ branch is built on an old release tag, so hot-reloading it onto the running dev
23
+ server could break the shared instance; test a hotfix with `/spec-connect`. The
24
+ engine enforces all of this and prints why.
22
25
 
23
26
  ## 1. Identify the target
24
27
 
@@ -26,6 +26,13 @@ trust the spec's own wording — verify:
26
26
  - **Referenced things still exist.** Grep/read for each `file:line`, module,
27
27
  function, route, model, type, or symbol the spec names. Flag anything renamed,
28
28
  moved, or deleted.
29
+ - **Impact map matches reality.** Walk every row of the `## Impact` table and
30
+ verify against the code: does the named surface (endpoint, schema/model, DB
31
+ table/migration, domain object, route, business rule) exist, and does its
32
+ stated `Change` (add/update/remove) still hold? A stale Impact map is the
33
+ highest-signal drift — each row is a named, checkable surface, so a wrong row
34
+ is exactly the "obvious mistake" the map exists to surface. Flag/refresh stale
35
+ rows (and add rows for surfaces the spec now touches but omits).
29
36
  - **Tasks already done.** For each `- [ ]`, check whether the code already
30
37
  implements it (it may have landed via other work). Tick `- [x]` what's done.
31
38
  - **Decisions still valid.** Re-check each `## Decisions` entry against the
@@ -47,9 +54,9 @@ reading the code, do that instead of asking.
47
54
 
48
55
  ## 4. Update the spec
49
56
 
50
- - Rewrite stale **Decisions** / **Solution overview** in `00-overview.md` and
51
- stale **tasks** in the phase files so they match the current code and the
52
- resolved questions. Add/remove tasks within a phase file; add a new phase by
57
+ - Rewrite stale **Decisions** / **Solution overview** and refresh the **Impact
58
+ map** in `00-overview.md`, and stale **tasks** in the phase files, so they
59
+ match the current code and the resolved questions. Add/remove tasks within a phase file; add a new phase by
53
60
  creating a `0N-<slug>.md` file **and** a matching overview index row, or drop a
54
61
  dead phase by removing both. Keep the index and files in sync; **preserve
55
62
  completed `[x]` history**.