@skitterbyte/skitterspec 12.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/assets/core/env.config.md +39 -2
- package/assets/rules/spec-planning.md +37 -16
- package/assets/skills/spec/SKILL.md +27 -0
- package/assets/skills/spec-bug/SKILL.md +20 -0
- package/assets/skills/spec-cancel/SKILL.md +5 -0
- package/assets/skills/spec-complete/SKILL.md +65 -11
- package/assets/skills/spec-go/SKILL.md +18 -3
- package/assets/skills/spec-hotfix/SKILL.md +181 -0
- package/assets/skills/spec-live/SKILL.md +4 -1
- package/assets/skills/spec-review/SKILL.md +10 -3
- package/assets/skills/spec-to-main/SKILL.md +99 -0
- package/package.json +1 -1
- package/src/cli.js +333 -6
- package/src/env/config.js +18 -0
- package/src/env/hotfix.js +133 -0
- package/src/env/live.js +12 -3
- package/src/env/provision.js +5 -1
- package/src/env/prune.js +119 -0
- package/src/env/resolve.js +28 -4
- package/src/env/teardown.js +20 -13
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ Spec-driven development for [Claude Code](https://claude.com/claude-code) — a
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Ships the spec-lifecycle skills (`/spec`, `/spec-go`, `/spec-complete`,
|
|
12
|
-
`/spec-cancel`, `/spec-bug`, `/spec-review`, `/spec-init`) plus
|
|
13
|
-
**isolation** — a git worktree per in-progress spec, Docker on demand,
|
|
14
|
-
servers on reserved ports, and `/spec-connect` to test a worktree at your
|
|
15
|
-
`localhost` URL.
|
|
12
|
+
`/spec-cancel`, `/spec-bug`, `/spec-hotfix`, `/spec-review`, `/spec-init`) plus
|
|
13
|
+
per-spec **isolation** — a git worktree per in-progress spec, Docker on demand,
|
|
14
|
+
host dev servers on reserved ports, and `/spec-connect` to test a worktree at your
|
|
15
|
+
normal `localhost` URL.
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
npx @skitterbyte/skitterspec init
|
|
@@ -44,6 +44,17 @@ no external install), so you test at the exact URL you always use.
|
|
|
44
44
|
`/spec-connect main` hands the ports back. Exclusive: one spec at a time. See
|
|
45
45
|
`specs/.core/env.config.md` for the `dev`/`proxy` config.
|
|
46
46
|
|
|
47
|
+
## Production hotfixes — `/spec-hotfix`
|
|
48
|
+
|
|
49
|
+
When prod is on a tagged release, a fix must be built on **that** version, not
|
|
50
|
+
`main`. `/spec-hotfix <tag> <name>` forks a worktree from the tag, then works
|
|
51
|
+
test-first like `/spec-bug`. `/spec-complete` lands it by patch-bumping the tag
|
|
52
|
+
and tagging the branch (your CI/CD deploys the tag — you push it) and
|
|
53
|
+
cherry-picking the fix onto `main`; `--also <tag>` patches extra release lines
|
|
54
|
+
(test/demo). Hotfixes refuse `/spec-live` (their old-tag branch could break the
|
|
55
|
+
running instance) — test them with `/spec-connect`. Tune the `hotfix` block in
|
|
56
|
+
`specs/.core/env.config.md`.
|
|
57
|
+
|
|
47
58
|
## v3 — slimmer surface + `/spec-connect`
|
|
48
59
|
|
|
49
60
|
**3.0** folds provisioning into `/spec-go`, teardown into
|
|
@@ -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
|
|
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
|
|
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`
|
|
22
|
-
immediately), so
|
|
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.
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
91
|
+
## Spec types — Feature, Bug, Hotfix
|
|
81
92
|
|
|
82
|
-
Every spec is one of
|
|
93
|
+
Every spec is one of three types, recorded **both** in the header and the filename:
|
|
83
94
|
|
|
84
|
-
- **Header field:** `> **Type:** Feature
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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 **
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
25
|
-
the spec now passes — that test is the proof
|
|
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.
|
|
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
|
|
58
|
-
spec has nothing to land, and `/spec-complete` behaves exactly as
|
|
59
|
-
applies, offer to land the finished branch
|
|
60
|
-
|
|
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.
|
|
97
|
-
merged into base, so teardown needs **no `--force`** and deletes
|
|
98
|
-
(`git branch -d`)
|
|
99
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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**
|
|
51
|
-
stale **tasks** in the phase files so they
|
|
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**.
|