@skitterbyte/skitterspec-jira 0.0.0-stage → 2.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 +1266 -0
- package/README.md +44 -2
- package/assets/claude-md-section.md +85 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/commands/spec-remote-review.md +37 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/SETUP.md +192 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +606 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/jira.config.json.example +18 -0
- package/assets/core/jira.config.md +88 -0
- package/assets/hooks/main-guard.cjs +162 -0
- package/assets/hooks/review-gate.cjs +146 -0
- package/assets/review/page.html +3384 -0
- package/assets/rules/commit-trailers.md +96 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +702 -0
- package/assets/rules/spec-reports.md +572 -0
- package/assets/skills/no-spec/SKILL.md +249 -0
- package/assets/skills/spec/SKILL.md +714 -0
- package/assets/skills/spec-bug/SKILL.md +585 -0
- package/assets/skills/spec-cancel/SKILL.md +261 -0
- package/assets/skills/spec-complete/SKILL.md +451 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +607 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-jira-setup/SKILL.md +221 -0
- package/assets/skills/spec-list/SKILL.md +173 -0
- package/assets/skills/spec-next/SKILL.md +678 -0
- package/assets/skills/spec-push/SKILL.md +306 -0
- package/assets/skills/spec-review/SKILL.md +256 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +496 -0
- package/assets/skills/spec-status/SKILL.md +114 -0
- package/assets/skills/spec-sync/SKILL.md +220 -0
- package/assets/skills/spec-to-main/SKILL.md +141 -0
- package/bin/skitterspec-jira.js +68 -0
- package/package.json +40 -4
- package/src/cli.js +5967 -0
- package/src/deprecate.js +138 -0
- package/src/env/bootstrap.js +188 -0
- package/src/env/building.js +143 -0
- package/src/env/classify.js +176 -0
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +850 -0
- package/src/env/dev.js +63 -0
- package/src/env/glob.js +34 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +413 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +167 -0
- package/src/env/live.js +479 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +225 -0
- package/src/env/prune.js +119 -0
- package/src/env/registry.js +159 -0
- package/src/env/render.js +17 -0
- package/src/env/resolve.js +716 -0
- package/src/env/review.js +2622 -0
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +1044 -0
- package/src/env/supervise.js +157 -0
- package/src/env/teardown.js +248 -0
- package/src/env/trust.js +87 -0
- package/src/gating.js +155 -0
- package/src/init.js +1150 -0
- package/src/lines-diff.js +114 -0
- package/src/prompts.js +129 -0
- package/src/vendor/jira/adf.js +414 -0
- package/src/vendor/jira/api.js +295 -0
- package/src/vendor/jira/cli-sync.js +1252 -0
- package/src/vendor/jira/commands.js +44 -0
- package/src/vendor/jira/config.js +286 -0
- package/src/vendor/jira/credentials.js +108 -0
- package/src/vendor/jira/dialect.js +45 -0
- package/src/vendor/jira/mcp.js +129 -0
- package/src/vendor/provider-kit/index.js +41 -0
- package/src/vendor/provider-kit/src/adapter.js +23 -0
- package/src/vendor/provider-kit/src/apply.js +207 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +251 -0
- package/src/vendor/provider-kit/src/credentials.js +404 -0
- package/src/vendor/provider-kit/src/doctor.js +35 -0
- package/src/vendor/provider-kit/src/http.js +63 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/list-assignee.js +71 -0
- package/src/vendor/provider-kit/src/list-rows.js +35 -0
- package/src/vendor/provider-kit/src/md-compare.js +39 -0
- package/src/vendor/provider-kit/src/md-tables.js +94 -0
- package/src/vendor/provider-kit/src/prompt.js +50 -0
- package/src/vendor/provider-kit/src/released.js +219 -0
- package/src/vendor/provider-kit/src/specs.js +248 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +92 -0
- package/src/vendor/sync-core/src/base.js +81 -0
- package/src/vendor/sync-core/src/compare.js +338 -0
- package/src/vendor/sync-core/src/dialect.js +51 -0
- package/src/vendor/sync-core/src/fields.js +36 -0
- package/src/vendor/sync-core/src/legacy.js +100 -0
- package/src/vendor/sync-core/src/normalize.js +971 -0
- package/src/vendor/sync-core/src/push.js +85 -0
- package/src/vendor/sync-core/src/retarget.js +278 -0
- package/src/vendor/sync-core/src/task-block.js +308 -0
- package/src/vendor/sync-core/src/verify.js +67 -0
- package/src/vendor/sync-core/src/write.js +191 -0
|
@@ -0,0 +1,607 @@
|
|
|
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, then land it by cutting the release, pushing it on consent, and carrying the fix onto main. ALWAYS starts from a base tag and works on the hotfix's own branch, never on main. 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, release it, carry it back to main
|
|
7
|
+
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
13
|
+
This is the **hotfix** counterpart to `/spec-bug`. Same test-first discipline
|
|
14
|
+
(reproduce as a **failing test (RED)**, then drive to **GREEN**), but the base is
|
|
15
|
+
a **release tag**, not `main`: prod is running a tagged version, so the fix must
|
|
16
|
+
be built on **that** commit line, shipped as a **new patch tag** (your CI/CD
|
|
17
|
+
deploys tags), and only then cherry-picked onto `main` for the next release.
|
|
18
|
+
|
|
19
|
+
Spec type convention (see `.claude/rules/spec-planning.md`):
|
|
20
|
+
- Hotfix specs are named `hotfix-<kebab-name>`; every header carries
|
|
21
|
+
`> **Type:** Hotfix` and `> **Base version:** <tag>`.
|
|
22
|
+
- Branch is `hotfix/<slug>`, forked from the base tag.
|
|
23
|
+
|
|
24
|
+
**Isolation is required.** A hotfix forks a worktree from a tag and lands by
|
|
25
|
+
tag + cherry-pick — it needs the isolation engine (`specs/.core/env.config.json`).
|
|
26
|
+
If isolation is absent, say so and stop; there is no in-place path.
|
|
27
|
+
|
|
28
|
+
## Phase 0 — start from a Jira issue (only when asked)
|
|
29
|
+
|
|
30
|
+
**Only when `specs/.core/jira.config.json` exists** *and* the invocation names an
|
|
31
|
+
issue. Otherwise skip this phase entirely and grill from the user's own words.
|
|
32
|
+
|
|
33
|
+
Two ways in:
|
|
34
|
+
|
|
35
|
+
- **`<ISSUE-REF>`** (`PROJ-123`, an issue URL, or a bare key anywhere in the
|
|
36
|
+
arguments) — adopt that issue.
|
|
37
|
+
- **`--from-issue [query]`** — browse the intake inbox: issues carrying
|
|
38
|
+
`intake.label` (what the web app files under). With a `query`, filter that list
|
|
39
|
+
case-insensitively by title. Show the shortlist and let the user pick one.
|
|
40
|
+
|
|
41
|
+
Then:
|
|
42
|
+
|
|
43
|
+
1. **Exclude what's already adopted.** Run `skitterspec spec-sync linked --json`
|
|
44
|
+
and drop any issue whose identifier is already stamped on a spec. If the user
|
|
45
|
+
named such an issue directly, **stop** and point them at that spec — one issue
|
|
46
|
+
never becomes two specs.
|
|
47
|
+
2. **Read the issue** with the discovered issue-read tool (`getJiraIssue`):
|
|
48
|
+
title, description, labels, reporter, URL. If Jira isn't connected, say so
|
|
49
|
+
and offer to carry on without it — a spec written from the user's own
|
|
50
|
+
description is still a spec.
|
|
51
|
+
3. **Route the issue to the right skill.** Match the issue's labels
|
|
52
|
+
(case-insensitively) against two lists, **checking `hotfixLabels` first**:
|
|
53
|
+
|
|
54
|
+
- **`intake.hotfixLabels`** → this is broken in **production**, so it needs a
|
|
55
|
+
fix against the released version, not `main`. Say so, name the matching
|
|
56
|
+
label, and tell the user to run **`/spec-hotfix <ISSUE-REF>`** — then stop.
|
|
57
|
+
- **`intake.bugLabels`** → a bug report, not a feature request. Say so, name
|
|
58
|
+
the label, and tell the user to run **`/spec-bug <ISSUE-REF>`** — then stop,
|
|
59
|
+
without authoring a Feature spec.
|
|
60
|
+
|
|
61
|
+
**Hotfix wins when an issue carries both**, and deliberately: production is the
|
|
62
|
+
more specific destination, and the two mistakes are not equally costly. Routing
|
|
63
|
+
a prod issue to `/spec-bug` produces a fix that lands on `main` and never
|
|
64
|
+
reaches the running version — discovered only when someone asks why it hasn't
|
|
65
|
+
shipped. The reverse is a hotfix branch for something that could have waited,
|
|
66
|
+
which is merely wasteful.
|
|
67
|
+
|
|
68
|
+
With a list unset, nothing routes through it; with both unset every issue is
|
|
69
|
+
treated as a feature request, exactly as before.
|
|
70
|
+
|
|
71
|
+
**Which checks run depends on where you are**, because a skip is about not
|
|
72
|
+
bouncing someone to the skill they are already in — not about ignoring an
|
|
73
|
+
escalation:
|
|
74
|
+
|
|
75
|
+
- **In `/spec`** — both checks run.
|
|
76
|
+
- **In `/spec-bug`** — the bug check is skipped (it would route you to
|
|
77
|
+
yourself), but the **hotfix check still runs**. A bug report labelled for
|
|
78
|
+
production is not "already handled" by being in the bug path: `/spec-bug`
|
|
79
|
+
fixes on `main`, and prod would stay broken. Say so and hand off.
|
|
80
|
+
- **In `/spec-hotfix`** — both are skipped. It is already the most specific
|
|
81
|
+
destination; there is nowhere left to route.
|
|
82
|
+
|
|
83
|
+
Then adopt the issue and reproduce it as usual: the issue body is your repro
|
|
84
|
+
material, and the failing test comes before the spec exactly as normal.
|
|
85
|
+
4. **Seed, don't skip, the grill.** The issue's title becomes the working spec
|
|
86
|
+
title and its description the starting material for **Problem** — or
|
|
87
|
+
**Symptom** in a bug or hotfix spec, which is where a report belongs. Quote the
|
|
88
|
+
reporter's words rather than paraphrasing them away. Note the reporter and the
|
|
89
|
+
issue URL for context. Everything after this runs exactly as normal: an issue
|
|
90
|
+
is a *request*, not a groomed spec, so grill it as hard as anything else.
|
|
91
|
+
|
|
92
|
+
**In `/spec-hotfix`, also mine the issue for a version.** Any release-shaped
|
|
93
|
+
string in the report is a *suggestion* for the base tag, offered when the skill
|
|
94
|
+
asks which version prod is running — never used as a default. The reporter's
|
|
95
|
+
version is the one they saw the bug on, which is often not what is deployed.
|
|
96
|
+
|
|
97
|
+
### Adopting the issue
|
|
98
|
+
|
|
99
|
+
The issue **becomes** the spec's issue — it is not copied and no second issue is
|
|
100
|
+
minted. This is identical in `/spec`, `/spec-bug` and `/spec-hotfix`; only *when*
|
|
101
|
+
it happens differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4,
|
|
102
|
+
`/spec-hotfix` in its step 5). Once the spec file exists:
|
|
103
|
+
|
|
104
|
+
- **Stamp `jira_identifier` and `jira_url`** in `00-overview.md` frontmatter
|
|
105
|
+
from the adopted issue. That is the whole link: every later skill
|
|
106
|
+
(`/spec-push`, `/spec-status`, `/spec-start`) keys off it being present.
|
|
107
|
+
- **Adoption is not a mint.** The issue was filed somewhere deliberately — it
|
|
108
|
+
stays where it is, whatever `jira.projectKey` says, and nothing re-homes it.
|
|
109
|
+
- **Do not write a base sidecar.** Leaving `sync.baseDir` empty for this spec is
|
|
110
|
+
what makes the **linking push** send the spec over the reporter's original
|
|
111
|
+
description (an **update** to the existing issue, plus a subtask per phase).
|
|
112
|
+
Recording a snapshot here would declare the mirror already in sync and strand
|
|
113
|
+
the issue showing the raw report forever.
|
|
114
|
+
- **Preserve the original, before anything replaces it.** Run this the moment
|
|
115
|
+
the spec file exists and **before the linking push**:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
skitterspec spec-sync preserve <spec>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
It posts the issue's current description onto the issue as a comment — the one
|
|
122
|
+
surface one-way sync never touches, so no push can clobber it. On the API path
|
|
123
|
+
it reads the description itself; without credentials it prints the body and
|
|
124
|
+
the marker, and you post it with the discovered Jira comment tool after
|
|
125
|
+
checking the comments for that marker.
|
|
126
|
+
|
|
127
|
+
**The ordering is the whole correctness condition.** Run before the push, this
|
|
128
|
+
keeps what the reporter filed. Run after it, it would keep the generated spec
|
|
129
|
+
and report success — which is why it is a step of its own here rather than a
|
|
130
|
+
line in the push sequence. The engine warns when it can tell it is late, but it
|
|
131
|
+
cannot always tell.
|
|
132
|
+
|
|
133
|
+
**It never fails the adoption.** A project that set
|
|
134
|
+
`intake.preserveOriginal: false`, an unreachable Jira, an issue with no
|
|
135
|
+
description, a comment Jira refused — every one of them exits 0 and says so.
|
|
136
|
+
Relay what it printed and carry on.
|
|
137
|
+
- **Say what will happen** in the finish-up message. The linking step runs right
|
|
138
|
+
after the spec is written, so the issue's description is replaced by the spec
|
|
139
|
+
**then** — not on some later manual push. Say both halves: the description
|
|
140
|
+
becomes the spec, **and** the original is preserved as a comment on the same
|
|
141
|
+
issue, so the reporter can still read what they wrote.
|
|
142
|
+
|
|
143
|
+
**Do not send anyone to the issue's history for it.** This used to say the
|
|
144
|
+
tracker keeps the original there, and that is true and useless: history is a
|
|
145
|
+
diff viewer nobody opens, it is not quotable, and it degrades to "it is in
|
|
146
|
+
there somewhere". The comment is the answer, and the other half is the spec's
|
|
147
|
+
own **Problem** (or **Symptom**) section quoting the reporter.
|
|
148
|
+
|
|
149
|
+
## 1. Establish the base version (the tag)
|
|
150
|
+
|
|
151
|
+
- **Read the arguments.** `/spec-hotfix <tag> <name>` (e.g.
|
|
152
|
+
`/spec-hotfix v33.16.4 login-crash`). An argument shaped like an issue
|
|
153
|
+
reference — letters, a hyphen, digits (`SKI-123`) —
|
|
154
|
+
is **always a reference, never a name**, so `/spec-hotfix v33.16.4 SKI-123` and
|
|
155
|
+
`/spec-hotfix SKI-123`
|
|
156
|
+
both mean "adopt that issue". Release tags don't take that shape, so the two
|
|
157
|
+
can't be confused. With no name and no reference, ask what to call it.
|
|
158
|
+
- Take the release tag from the argument. If it's missing,
|
|
159
|
+
**ask which version prod is running** — don't guess. When an issue was adopted
|
|
160
|
+
above, **offer any versions it mentions** as suggestions — clearly labelled as
|
|
161
|
+
the reporter's words, not a default — and still wait for the answer. A reporter
|
|
162
|
+
usually names
|
|
163
|
+
the version they *saw* the bug on, which is not necessarily what is deployed,
|
|
164
|
+
and a hotfix forked from the wrong tag fails late.
|
|
165
|
+
**A hotfix tag is a valid base**: prod may be running `v36.4.0-hotfix.2`, and a
|
|
166
|
+
hotfix on it continues that line (`-hotfix.3` under `hotfix.bump:
|
|
167
|
+
"hotfix-suffix"` or skittership's release).
|
|
168
|
+
- **Verify the tag exists** before anything else:
|
|
169
|
+
`git rev-parse --verify <tag>^{commit}`. If it doesn't resolve, stop and ask.
|
|
170
|
+
|
|
171
|
+
## 2. Reproduce & isolate (light investigation)
|
|
172
|
+
|
|
173
|
+
Hotfixes are concrete — confirm, don't over-grill. Establish:
|
|
174
|
+
|
|
175
|
+
- **Repro:** exact steps / input that triggers it on the released version.
|
|
176
|
+
- **Expected vs actual:** what *should* happen vs what does.
|
|
177
|
+
- **Root cause:** read the code **at the base tag** and trace it to `file:line`.
|
|
178
|
+
The fix belongs on the tag's line, so reason about that code, not `main`'s.
|
|
179
|
+
|
|
180
|
+
## 3. Provision the worktree from the tag, then write in it
|
|
181
|
+
|
|
182
|
+
**Provision before you write, not after.** §1 settled the tag and the name and
|
|
183
|
+
§2 put nothing on disk, so this is the one moment where a worktree costs nothing
|
|
184
|
+
to obtain and everything — the red test, the fix, the spec — is still ahead of
|
|
185
|
+
it:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
skitterspec spec-env up hotfix-<name> --docs --from <tag>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**`--from` is how the tag reaches the engine, and it is not the header twice.**
|
|
192
|
+
Everywhere else `up` forks a hotfix from the spec's own
|
|
193
|
+
`> **Base version:**` line — but here the spec that would carry it does not
|
|
194
|
+
exist yet, which is the whole of what this lane is for. Without the flag the
|
|
195
|
+
branch would fork from `main`: the fix gets written against code prod is not
|
|
196
|
+
running, while the tag and the cherry-pick downstream go on believing it is on
|
|
197
|
+
the release line, and nothing says otherwise until someone asks why the patch
|
|
198
|
+
did not take. The engine refuses a ref git does not know rather than forking
|
|
199
|
+
from somewhere else quietly; where git cannot answer at all it passes the ref
|
|
200
|
+
through to `git worktree add`, which refuses far more loudly than a planner
|
|
201
|
+
guessing from an absence.
|
|
202
|
+
|
|
203
|
+
Run what it prints — the `git worktree add … -b hotfix/<slug> <tag>` is checked
|
|
204
|
+
out **at the tag**, not at `main`, and the
|
|
205
|
+
`skitterspec spec-env bootstrap hotfix-<name> --docs` under `then, run:` where
|
|
206
|
+
there is one — then move this session into the worktree
|
|
207
|
+
with a plain `cd`; the Bash working directory persists between calls, so from
|
|
208
|
+
here on the test, the fix and the spec are all written on the hotfix's own
|
|
209
|
+
branch. Confirm the move landed rather than reading silence as success
|
|
210
|
+
(`.claude/rules/negative-checks.md` rule 1): `skitterspec spec-env resolve` with
|
|
211
|
+
no argument must name this spec. If it does not, say so and stop rather than
|
|
212
|
+
writing into a tree you cannot name.
|
|
213
|
+
|
|
214
|
+
**Then run `up` again, without the flags:**
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
skitterspec spec-env up hotfix-<name>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`--docs` skips the `setup` commands — everything whose only purpose is making a
|
|
221
|
+
tree *runnable* — because provisioning somewhere to write markdown should cost a
|
|
222
|
+
`git worktree add` and nothing else. A hotfix has no `/spec-start` to leave them
|
|
223
|
+
to: §4 runs the project's suite in this same tree, and
|
|
224
|
+
**a tree that cannot run the suite cannot go red**.
|
|
225
|
+
The flagless re-run re-attaches the worktree that now exists (no second `-b`
|
|
226
|
+
fork, and no fork point — the branch is already on the tag's line) and prints
|
|
227
|
+
`skitterspec spec-env bootstrap hotfix-<name>` — run it, and it runs the steps
|
|
228
|
+
the flag deferred.
|
|
229
|
+
|
|
230
|
+
**The worktree steps are the engine's to run, never yours.** Seeding the
|
|
231
|
+
gitignored files, the env file, `setup` and the stack all happen inside
|
|
232
|
+
`skitterspec spec-env bootstrap <name>` — one call that names no file. Do not
|
|
233
|
+
reconstruct them as shell: they used to be printed that way, an agent's copy was
|
|
234
|
+
never faithful, and a project's secrets hook refused every rewrite that named a
|
|
235
|
+
`.env` path. `bootstrap --plan` says what it would do, for reading only.
|
|
236
|
+
|
|
237
|
+
**Why nothing is seeded on the base branch, and nothing is moved.** This step
|
|
238
|
+
used to write `specs/in-progress/hotfix-<name>/00-overview.md` in the primary
|
|
239
|
+
checkout for the engine to resolve, and then `mv` the folder across into a
|
|
240
|
+
worktree checked out at a tag — a move with a hazard paragraph of its own, since
|
|
241
|
+
`specs/in-progress/` is usually absent at an old release tag. Both halves are
|
|
242
|
+
gone. `main` is where work **lands**, not where it happens: where the project
|
|
243
|
+
installs `.claude/hooks/main-guard.cjs` that first write is refused outright,
|
|
244
|
+
and a step whose documented opening the repo will not perform is not a step. The
|
|
245
|
+
spec is written in the worktree at §5, on the branch it belongs to.
|
|
246
|
+
|
|
247
|
+
**§5 records the same tag you passed to `--from`**, on the spec's
|
|
248
|
+
`> **Base version:**` header. That is not bookkeeping: `/spec-complete` cuts the
|
|
249
|
+
release and carries the fix off the header, so a worktree forked from one ref and
|
|
250
|
+
a header naming another is this whole bug again with the halves swapped.
|
|
251
|
+
|
|
252
|
+
- **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
|
|
253
|
+
none of the repo's gitignored files (`.env`, local overrides). Run the printed
|
|
254
|
+
`in the worktree, run:` steps (file seeding, then `setup`) in order, before
|
|
255
|
+
anything else.
|
|
256
|
+
- **Trust the worktree for this session.** The engine wrote the printed
|
|
257
|
+
`trusted:` root into `.claude/settings.local.json`, but it won't hot-reload now
|
|
258
|
+
— run `/add-dir <trusted root>` before editing into the worktree, or the first
|
|
259
|
+
edits will prompt.
|
|
260
|
+
- **Do everything below in the worktree**, on the `hotfix/<slug>` branch — the red
|
|
261
|
+
test, the fix, and the rest of the spec. The `cd` above is what makes that the
|
|
262
|
+
default rather than something to remember; where a step has to reach out of
|
|
263
|
+
the tree, use an absolute path or `git -C <worktreePath>`. `main` changes
|
|
264
|
+
only at `/spec-complete` (via cherry-pick, not merge).
|
|
265
|
+
|
|
266
|
+
## 4. Write the failing test FIRST (RED) — mandatory
|
|
267
|
+
|
|
268
|
+
Encode the **correct** (expected) behaviour as a test, then run it and confirm it
|
|
269
|
+
**fails for the right reason**, on the hotfix branch:
|
|
270
|
+
|
|
271
|
+
- Put it where the suite already covers that area. Reuse existing test helpers /
|
|
272
|
+
factories; follow the project's test rules (see `.claude/rules/`). Never
|
|
273
|
+
hardcode dates — compute them relative to now.
|
|
274
|
+
- Run it with the project's test command. Quote the red output. A test that
|
|
275
|
+
passes before the fix proves nothing — keep refining until it genuinely
|
|
276
|
+
captures the bug on this version.
|
|
277
|
+
|
|
278
|
+
## 5. Write the Hotfix spec
|
|
279
|
+
|
|
280
|
+
Write `00-overview.md` in the worktree — §3 provisioned it at the tag and moved
|
|
281
|
+
you there, so create the folder and write it here. A hotfix
|
|
282
|
+
is usually a single-pass fix, so the `## Fix` block can live directly in
|
|
283
|
+
`00-overview.md`. Keep it lean:
|
|
284
|
+
|
|
285
|
+
```markdown
|
|
286
|
+
# Hotfix: <short title>
|
|
287
|
+
|
|
288
|
+
> **Type:** Hotfix
|
|
289
|
+
> **Name:** hotfix-<kebab-name> (the spec folder name — the handle you paste into `/spec-start`)
|
|
290
|
+
> **Status:** In Progress — fixing (red test added)
|
|
291
|
+
> **Author:** <git user.name — who reported/captured it>
|
|
292
|
+
> **Developer:** <git user.name — you, since you're fixing it now>
|
|
293
|
+
> **Base version:** <tag prod is running, e.g. v33.16.4>
|
|
294
|
+
> **Raised:** <YYYY-MM-DD (today)>
|
|
295
|
+
> **Area:** <files/modules>
|
|
296
|
+
> **Gating:** <pre-filled "none: hotfix — restoring released behaviour"; only
|
|
297
|
+
> when release gating is configured, and overridable — see below>
|
|
298
|
+
|
|
299
|
+
## Symptom
|
|
300
|
+
|
|
301
|
+
<observed wrong behaviour on the released version + repro steps; paste any error>
|
|
302
|
+
|
|
303
|
+
## Root cause
|
|
304
|
+
|
|
305
|
+
<the actual cause, at `file:line` on the base tag. One paragraph — be specific.>
|
|
306
|
+
|
|
307
|
+
## Failing test (red)
|
|
308
|
+
|
|
309
|
+
<test name + path; what it asserts. How to run it. Paste the red failure line.>
|
|
310
|
+
|
|
311
|
+
## Fix
|
|
312
|
+
|
|
313
|
+
- [ ] <the minimal change that addresses the root cause, not the symptom>
|
|
314
|
+
- [ ] Failing test now passes (GREEN); run the project's typecheck and test
|
|
315
|
+
commands — confirm no regressions.
|
|
316
|
+
- [ ] <any follow-up hardening, or "None">
|
|
317
|
+
|
|
318
|
+
## Impact
|
|
319
|
+
|
|
320
|
+
<The concrete surfaces this spec touches — the scannable blast radius, so a
|
|
321
|
+
reader can eyeball where the spec got something wrong without reading prose.
|
|
322
|
+
`Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
|
|
323
|
+
values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
|
|
324
|
+
Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
|
|
325
|
+
this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
|
|
326
|
+
terse — names/signatures, not sentences. List **only** surfaces that actually
|
|
327
|
+
change; the heading is always present, but if nothing external changes write the
|
|
328
|
+
single line below instead of an empty table.>
|
|
329
|
+
|
|
330
|
+
<A hotfix should be minimal — often no external surface changes; that's
|
|
331
|
+
fine, use the one-liner.>
|
|
332
|
+
|
|
333
|
+
| Surface | Change | Detail |
|
|
334
|
+
|---------|--------|--------|
|
|
335
|
+
| <e.g. Endpoint> | update | <e.g. GET /orders (fix null total)> |
|
|
336
|
+
|
|
337
|
+
<_No external surface changes — internal refactor only._ — use this line in
|
|
338
|
+
place of the table when the spec touches no external surface.>
|
|
339
|
+
|
|
340
|
+
## Landing
|
|
341
|
+
|
|
342
|
+
- [ ] Release cut at `/spec-complete` (`hotfix.releaseWith`, else the
|
|
343
|
+
`hotfix.bump` tag) and pushed — branch, then tag — on your say-so, which
|
|
344
|
+
triggers CI/CD.
|
|
345
|
+
- [ ] Fix carried onto `main` without the version commit (and tagged onto any
|
|
346
|
+
`--also` release lines).
|
|
347
|
+
- [ ] Deploy confirmed, then `/spec-complete` again to run `hotfix finish`.
|
|
348
|
+
|
|
349
|
+
## State log
|
|
350
|
+
|
|
351
|
+
| Date | Status | Folder | By |
|
|
352
|
+
|------|--------|--------|----|
|
|
353
|
+
| <YYYY-MM-DD> | In Progress | in-progress | <developer> |
|
|
354
|
+
|
|
355
|
+
## Changelog
|
|
356
|
+
|
|
357
|
+
- <YYYY-MM-DD> — Hotfix reproduced on <tag>; failing test added (red).
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Keep the **State log** (state transitions) separate from the **Changelog** (fix
|
|
361
|
+
narrative and decisions).
|
|
362
|
+
|
|
363
|
+
**Only when `specs/.core/jira.config.json` exists** (Jira sync is opted in).
|
|
364
|
+
If it's absent, skip this entirely — the spec stays local-only and the skill
|
|
365
|
+
behaves exactly as above. When present, link the spec you just wrote so status
|
|
366
|
+
and discussion live in Jira while the repo stays the source of truth. A spec is
|
|
367
|
+
a Jira **issue**; each phase is a **subtask**, carrying that phase's tasks in
|
|
368
|
+
its description as a read-only checklist.
|
|
369
|
+
|
|
370
|
+
**Linking is just the first push**, so it runs the same engine path `/spec-push`
|
|
371
|
+
does — you never create the issue by hand:
|
|
372
|
+
|
|
373
|
+
1. **Pick the transport.** `skitterspec spec-sync states --json`. With Jira
|
|
374
|
+
credentials set it answers `api` and prints the project's status names;
|
|
375
|
+
without them it answers `mcp` and you do the MCP work `/spec-push` describes.
|
|
376
|
+
Write the status names to a file for step 2.
|
|
377
|
+
2. **Get the plan.**
|
|
378
|
+
`skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
|
|
379
|
+
— the spec is unlinked, so this plan is all-creates: the issue and one
|
|
380
|
+
subtask per phase.
|
|
381
|
+
3. **Apply it.**
|
|
382
|
+
`skitterspec spec-sync apply <spec> --plan plan.json`. There is no project
|
|
383
|
+
to pick: the issue lands in the configured `jira.projectKey` by construction.
|
|
384
|
+
That creates the issue and its subtasks and checks what Jira stored. It then
|
|
385
|
+
**stamps the keys into the spec** — `jira_identifier`/`jira_url` on the
|
|
386
|
+
overview, `jira_issue_id` on each phase — and **records the base snapshot**,
|
|
387
|
+
so `/spec-status` reports in-sync immediately. There is no hand-editing of
|
|
388
|
+
frontmatter and no separate `stamp` or `record` call.
|
|
389
|
+
|
|
390
|
+
If it prints `transport = mcp`, it wrote nothing: apply the plan over MCP as
|
|
391
|
+
`/spec-push` steps 4a–5 describe, ending with `spec-sync stamp` and
|
|
392
|
+
`spec-sync record`. That path is fully supported — it is what anyone without
|
|
393
|
+
credentials uses.
|
|
394
|
+
4. **Echo the branch name** from `branch.pattern` so the user knows what
|
|
395
|
+
`/spec-start` will fork.
|
|
396
|
+
|
|
397
|
+
**If Jira can't be reached**, say so in one line and leave the spec written and
|
|
398
|
+
local — it is still a perfectly good spec, and `/spec-push` links it later. Do
|
|
399
|
+
nothing destructive.
|
|
400
|
+
|
|
401
|
+
**A spec that adopted an existing issue** (see the intake step) is already
|
|
402
|
+
stamped, so its plan is an **update**, not a create: applying it replaces the
|
|
403
|
+
reporter's description with the spec. That is the one-way rule working as
|
|
404
|
+
intended — the repo is canonical, and the original was preserved as a comment
|
|
405
|
+
on the issue by the intake step. Nothing re-homes an adopted issue: where it
|
|
406
|
+
lives stays Jira's.
|
|
407
|
+
|
|
408
|
+
Leave committing to the existing convention (the user commits the spec as usual)
|
|
409
|
+
and **never auto-push git** — Jira's own automation reacts to real branch/PR
|
|
410
|
+
events later. Report the Jira issue URL as part of the skill's finish-up
|
|
411
|
+
message.
|
|
412
|
+
|
|
413
|
+
Assignment sync is **not yet supported by the Jira provider**: the issue's
|
|
414
|
+
assignee is never read or written, and there is no `/spec-claim`. The
|
|
415
|
+
`> **Developer:**` header is still set by the base skill's own step — that is
|
|
416
|
+
the repo's record and it needs nothing from the tracker. Nothing to run here.
|
|
417
|
+
|
|
418
|
+
Jira has no project picker: a minted issue lands in the configured
|
|
419
|
+
`jira.projectKey` by construction, decided once in `specs/.core/jira.config.json`.
|
|
420
|
+
There is nothing to ask.
|
|
421
|
+
|
|
422
|
+
### Release gating (only when configured)
|
|
423
|
+
|
|
424
|
+
**Only when `specs/.core/gating.config.json` exists.** A hotfix is the one spec
|
|
425
|
+
type that does **not** ask the question cold: it writes
|
|
426
|
+
`none: hotfix — restoring released behaviour` and asks only for confirmation.
|
|
427
|
+
|
|
428
|
+
The default differs on purpose. A hotfix restores behaviour a release already
|
|
429
|
+
had, under time pressure, and the fix is captured by a deploy tag rather than
|
|
430
|
+
riding the next release — so a flag has nothing to gate and nothing to roll back
|
|
431
|
+
to. Making someone answer a design question mid-incident buys nothing.
|
|
432
|
+
|
|
433
|
+
It is a **default, not a rule**: say what you are writing and let the user
|
|
434
|
+
override it. If they name a flag, record that instead. Skip entirely when the
|
|
435
|
+
config is absent.
|
|
436
|
+
|
|
437
|
+
## 6. Drive to GREEN
|
|
438
|
+
|
|
439
|
+
- Implement the **minimal, root-cause** fix on the branch. Match surrounding code;
|
|
440
|
+
honour all project rules (see `.claude/rules/`).
|
|
441
|
+
- Re-run the failing test → it must pass. Then run the project's typecheck and
|
|
442
|
+
test commands to confirm no regressions. Quote results.
|
|
443
|
+
- Commit the fix to the `hotfix/<slug>` branch (this commit is what gets tagged
|
|
444
|
+
and cherry-picked). Tick the Fix tasks; add a Changelog line.
|
|
445
|
+
|
|
446
|
+
**Then refresh the mirror (only if a provider is installed).** The Fix tasks are
|
|
447
|
+
ticked, so the repo is now the truth about this fix — and this skill can take a
|
|
448
|
+
bug all the way to green without `/spec-next` ever running. Without a provider this
|
|
449
|
+
is a no-op.
|
|
450
|
+
|
|
451
|
+
**Only when `specs/.core/jira.config.json` exists** and the spec carries a
|
|
452
|
+
`jira_identifier`. Either missing → **skip**, in one line
|
|
453
|
+
(`not linked to Jira — /spec-push to mirror it`), and carry on.
|
|
454
|
+
|
|
455
|
+
**Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
|
|
456
|
+
become the truth about this phase's progress, and progress is what the mirror
|
|
457
|
+
exists to show. Deferring it to `/spec-complete` is what makes every phase
|
|
458
|
+
subtask jump from To Do straight to Done, with nothing visible in between.
|
|
459
|
+
|
|
460
|
+
- **Never mint.** An unlinked spec is skipped, not created.
|
|
461
|
+
- **Never fatal.** If the push fails — offline, no credentials, a Jira error —
|
|
462
|
+
say so and **finish the operation anyway**. The phase is done in the repo
|
|
463
|
+
regardless; the mirror is disposable and the next push repairs it.
|
|
464
|
+
- **Expect a dirty `specs/.core/` afterwards.** The push writes a base snapshot
|
|
465
|
+
and stamps any new keys, and these skills do not commit. The next `/commit`
|
|
466
|
+
sweeps it up with the phase's own work.
|
|
467
|
+
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
468
|
+
unlinked, or failed with the reason.
|
|
469
|
+
|
|
470
|
+
## 6b. Render the page, arm the gate, then wait for the verdict
|
|
471
|
+
|
|
472
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
473
|
+
present). Without it there is no worktree to read and this step does not exist —
|
|
474
|
+
skip it in silence rather than explaining an absence.
|
|
475
|
+
|
|
476
|
+
The fix is green and nothing is committed yet. That is the moment the page is
|
|
477
|
+
about, so render it now — **after** the tests pass and **before** the commit:
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
skitterspec spec-env review <spec> --buttons fix
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**This is free.** The engine reads git and splices the patches into a template;
|
|
484
|
+
the diff never passes through you, so a 266KB patch costs nothing.
|
|
485
|
+
|
|
486
|
+
**`--buttons fix` drops `Commit & Continue`**, because a hotfix has no next
|
|
487
|
+
phase for `/spec-next` to build and that verb would name work which does not
|
|
488
|
+
exist. There is no exception to this the way there is in `/spec-bug`: a hotfix
|
|
489
|
+
is a single pass against a released tag by construction, so it never has phase
|
|
490
|
+
files and the flag is unconditional here.
|
|
491
|
+
|
|
492
|
+
**Then arm the gate**, so the fix now owes a verdict:
|
|
493
|
+
|
|
494
|
+
```
|
|
495
|
+
skitterspec spec-env review arm <spec>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Then wait**, and `/spec-next` §5 owns the sequence — follow it there rather
|
|
499
|
+
than reading a second copy here: note the moment you start waiting, watch the
|
|
500
|
+
pending store, end your turn, and let `--claim-since` pick the one pass that
|
|
501
|
+
arrived inside the window. The banner it describes is what this skill emits in
|
|
502
|
+
place of a `Review` row, and the routing on the verdict is `/spec-diff` §2 and
|
|
503
|
+
§4, as it is everywhere.
|
|
504
|
+
|
|
505
|
+
**Why this skill arms as well as waits.** Waiting is what any offer does;
|
|
506
|
+
**arming** asserts an obligation that outlives the turn, and belongs only to
|
|
507
|
+
work that is finished. A hotfix is a completed unit — red→green against a
|
|
508
|
+
released tag — so it qualifies, and a wait with nothing owed behind it is a
|
|
509
|
+
suggestion rather than a gate.
|
|
510
|
+
|
|
511
|
+
**A hotfix is the case where reading it matters most.** This change is about to
|
|
512
|
+
be tagged and shipped to production from a release line, not merged into a
|
|
513
|
+
branch someone else will read first. The verdict is the only review it gets.
|
|
514
|
+
|
|
515
|
+
**It is user-visible, and that is deliberate.** Once armed, a `git commit` in
|
|
516
|
+
this worktree is refused until a verdict is sent or
|
|
517
|
+
`skitterspec spec-env review skip "<reason>"` records the decision to move on.
|
|
518
|
+
The exit is always one command, and one of them is *"I am moving on"*.
|
|
519
|
+
|
|
520
|
+
Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
|
|
521
|
+
three — rather than the bare `page:` path: a path is not clickable in any
|
|
522
|
+
terminal, and a page nobody can open is a page nobody reads. Relay the `live:`
|
|
523
|
+
line with it where the engine printed one, and nothing where it did not.
|
|
524
|
+
`.claude/rules/spec-reports.md` carries the shape, including why a rigid
|
|
525
|
+
contract took that line.
|
|
526
|
+
|
|
527
|
+
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
528
|
+
something behind that this tooling cannot remove, so it is always an ask. A
|
|
529
|
+
`file://` link is no use on a phone, and saying so **is** the ask —
|
|
530
|
+
`/spec-diff` §6 owns how.
|
|
531
|
+
**The `reader:` line no longer decides anything here, and that is the point.**
|
|
532
|
+
It is still printed, and it is still the only place that question is answered —
|
|
533
|
+
but the offer does not change with it, because the stack lists every tier
|
|
534
|
+
whatever it says. Three reader states used to mean three different offers, and
|
|
535
|
+
that branching is exactly what produced a `file://` page on a session detected
|
|
536
|
+
`unknown`, a LAN URL for a phone off the network, and an address that changed
|
|
537
|
+
underneath a reader when detection flipped mid-session.
|
|
538
|
+
|
|
539
|
+
So: **relay all three tier lines, every time.** Any `also:` lines sit under
|
|
540
|
+
`network` — the other addresses this machine has, offered because the
|
|
541
|
+
best-guess one can be wrong — so pass them on rather than editing them out: in
|
|
542
|
+
the banner they are indented sub-bullets under **network**, labels
|
|
543
|
+
(`(tailnet)`) included, exactly as `.claude/rules/spec-reports.md` shows.
|
|
544
|
+
|
|
545
|
+
**Never read an environment variable to decide anything about the offer** — not
|
|
546
|
+
`SSH_CONNECTION`, not `CLAUDE_CODE_*`, not a tty check. There is nothing left
|
|
547
|
+
here for a detection to decide, and a second implementation of one could not be
|
|
548
|
+
tested and would drift.
|
|
549
|
+
|
|
550
|
+
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
551
|
+
authorises a local server — one process, ended by one flag, leaving nothing
|
|
552
|
+
behind — and authorises nothing else. Publishing leaves a page this tooling
|
|
553
|
+
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
554
|
+
serve (a busy port, a machine with no network address) it falls back to the
|
|
555
|
+
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
556
|
+
|
|
557
|
+
- **Never fatal.** A failed render — no worktree, a git error — is one line, and
|
|
558
|
+
the fix is still done. The page is a convenience; the repo is the record.
|
|
559
|
+
|
|
560
|
+
**The range is measured from the base tag, not from `main`.** A hotfix forks its
|
|
561
|
+
worktree from its `> **Base version:**` tag, and the engine reads that header, so
|
|
562
|
+
the header line says `since <tag>` — do not relay it as `since main`, and do not
|
|
563
|
+
reach for a branch view expecting the base branch.
|
|
564
|
+
|
|
565
|
+
## 7. Report
|
|
566
|
+
|
|
567
|
+
Do **not** `git push` or `git tag`-and-push unless the user asks — deploying to
|
|
568
|
+
prod is theirs to trigger. The spec stays in `in-progress`.
|
|
569
|
+
|
|
570
|
+
- **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
|
|
571
|
+
hot-reloading it onto the running dev server could break the shared instance.
|
|
572
|
+
To test it, the user runs `/spec-connect` (its own isolated stack).
|
|
573
|
+
- `Next` points at **`/spec-complete`** to land it. That cuts the configured
|
|
574
|
+
release — `hotfix.releaseWith` (e.g. skittership's
|
|
575
|
+
`node scripts/release-hotfix.cjs --json`) or the built-in tagger's `hotfix.bump`
|
|
576
|
+
tag — asks before pushing the branch and then the tag, and carries the fix onto
|
|
577
|
+
`main` without the version commit. The worktree stays until the deploy is
|
|
578
|
+
confirmed; a second `/spec-complete` then runs `spec-env hotfix finish`. Add
|
|
579
|
+
`--also <tag>` at completion to also tag other release lines (test/demo,
|
|
580
|
+
tag-only).
|
|
581
|
+
|
|
582
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
583
|
+
the shape; this section carries only what is specific here.
|
|
584
|
+
|
|
585
|
+
**Verdicts**
|
|
586
|
+
|
|
587
|
+
- `✅` — red→green on the base tag, fix in, suite passing.
|
|
588
|
+
- `⚠️` — green, with something worth knowing.
|
|
589
|
+
- `❌` — still red, or a later test broke. Quote the failure.
|
|
590
|
+
- `⏸` — no reproduction on that tag, or the tag could not be established.
|
|
591
|
+
|
|
592
|
+
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
593
|
+
`Review` · `Follow-ups` · `Next`
|
|
594
|
+
|
|
595
|
+
**`Review` is emitted only where step 6b did not run** — a project with no
|
|
596
|
+
isolation, or a render that failed. Where the run is waiting, the banner carries
|
|
597
|
+
the whole subject and the row is dropped, per `.claude/rules/spec-reports.md`.
|
|
598
|
+
|
|
599
|
+
**The base tag goes in the verdict clause** — `✅ /spec-hotfix · hotfix-foo ·
|
|
600
|
+
green on v2.3.1`. Which released version this was fixed against is the first
|
|
601
|
+
thing anyone needs, and it is not a field: the clause is where the run says
|
|
602
|
+
where it got to.
|
|
603
|
+
|
|
604
|
+
Step 6b ends in the **banner**, not a paragraph and not a row — the run is
|
|
605
|
+
waiting on a verdict, and the banner is the shape that says so. Nothing follows
|
|
606
|
+
it. Where the run is not waiting it asks nothing at all: a `Review` row carries
|
|
607
|
+
the counts and the link, and no question (*asking implies waiting*).
|