@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.
Files changed (113) hide show
  1. package/MIGRATION.md +1266 -0
  2. package/README.md +44 -2
  3. package/assets/claude-md-section.md +85 -0
  4. package/assets/commands/allow-main.md +33 -0
  5. package/assets/commands/spec-connect.md +13 -0
  6. package/assets/commands/spec-live.md +14 -0
  7. package/assets/commands/spec-remote-review.md +37 -0
  8. package/assets/commands/spec-skip.md +24 -0
  9. package/assets/core/SETUP.md +192 -0
  10. package/assets/core/env.config.json.example +56 -0
  11. package/assets/core/env.config.md +606 -0
  12. package/assets/core/gating.config.json.example +4 -0
  13. package/assets/core/gating.config.md +81 -0
  14. package/assets/core/jira.config.json.example +18 -0
  15. package/assets/core/jira.config.md +88 -0
  16. package/assets/hooks/main-guard.cjs +162 -0
  17. package/assets/hooks/review-gate.cjs +146 -0
  18. package/assets/review/page.html +3384 -0
  19. package/assets/rules/commit-trailers.md +96 -0
  20. package/assets/rules/negative-checks.md +69 -0
  21. package/assets/rules/offered-unblocks.md +100 -0
  22. package/assets/rules/spec-planning.md +702 -0
  23. package/assets/rules/spec-reports.md +572 -0
  24. package/assets/skills/no-spec/SKILL.md +249 -0
  25. package/assets/skills/spec/SKILL.md +714 -0
  26. package/assets/skills/spec-bug/SKILL.md +585 -0
  27. package/assets/skills/spec-cancel/SKILL.md +261 -0
  28. package/assets/skills/spec-complete/SKILL.md +451 -0
  29. package/assets/skills/spec-diff/SKILL.md +870 -0
  30. package/assets/skills/spec-hotfix/SKILL.md +607 -0
  31. package/assets/skills/spec-init/SKILL.md +178 -0
  32. package/assets/skills/spec-jira-setup/SKILL.md +221 -0
  33. package/assets/skills/spec-list/SKILL.md +173 -0
  34. package/assets/skills/spec-next/SKILL.md +678 -0
  35. package/assets/skills/spec-push/SKILL.md +306 -0
  36. package/assets/skills/spec-review/SKILL.md +256 -0
  37. package/assets/skills/spec-reviewed/SKILL.md +284 -0
  38. package/assets/skills/spec-start/SKILL.md +496 -0
  39. package/assets/skills/spec-status/SKILL.md +114 -0
  40. package/assets/skills/spec-sync/SKILL.md +220 -0
  41. package/assets/skills/spec-to-main/SKILL.md +141 -0
  42. package/bin/skitterspec-jira.js +68 -0
  43. package/package.json +40 -4
  44. package/src/cli.js +5967 -0
  45. package/src/deprecate.js +138 -0
  46. package/src/env/bootstrap.js +188 -0
  47. package/src/env/building.js +143 -0
  48. package/src/env/classify.js +176 -0
  49. package/src/env/commitcmd.js +108 -0
  50. package/src/env/config.js +850 -0
  51. package/src/env/dev.js +63 -0
  52. package/src/env/glob.js +34 -0
  53. package/src/env/hooks.js +226 -0
  54. package/src/env/hotfix.js +413 -0
  55. package/src/env/hotfixland.js +302 -0
  56. package/src/env/integrate.js +167 -0
  57. package/src/env/live.js +479 -0
  58. package/src/env/mainguard.js +299 -0
  59. package/src/env/provision.js +466 -0
  60. package/src/env/proxy.js +225 -0
  61. package/src/env/prune.js +119 -0
  62. package/src/env/registry.js +159 -0
  63. package/src/env/render.js +17 -0
  64. package/src/env/resolve.js +716 -0
  65. package/src/env/review.js +2622 -0
  66. package/src/env/reviewers/coderabbit.js +228 -0
  67. package/src/env/reviewers.js +517 -0
  68. package/src/env/serve.js +1044 -0
  69. package/src/env/supervise.js +157 -0
  70. package/src/env/teardown.js +248 -0
  71. package/src/env/trust.js +87 -0
  72. package/src/gating.js +155 -0
  73. package/src/init.js +1150 -0
  74. package/src/lines-diff.js +114 -0
  75. package/src/prompts.js +129 -0
  76. package/src/vendor/jira/adf.js +414 -0
  77. package/src/vendor/jira/api.js +295 -0
  78. package/src/vendor/jira/cli-sync.js +1252 -0
  79. package/src/vendor/jira/commands.js +44 -0
  80. package/src/vendor/jira/config.js +286 -0
  81. package/src/vendor/jira/credentials.js +108 -0
  82. package/src/vendor/jira/dialect.js +45 -0
  83. package/src/vendor/jira/mcp.js +129 -0
  84. package/src/vendor/provider-kit/index.js +41 -0
  85. package/src/vendor/provider-kit/src/adapter.js +23 -0
  86. package/src/vendor/provider-kit/src/apply.js +207 -0
  87. package/src/vendor/provider-kit/src/args.js +68 -0
  88. package/src/vendor/provider-kit/src/assignment.js +277 -0
  89. package/src/vendor/provider-kit/src/config.js +251 -0
  90. package/src/vendor/provider-kit/src/credentials.js +404 -0
  91. package/src/vendor/provider-kit/src/doctor.js +35 -0
  92. package/src/vendor/provider-kit/src/http.js +63 -0
  93. package/src/vendor/provider-kit/src/identity.js +93 -0
  94. package/src/vendor/provider-kit/src/list-assignee.js +71 -0
  95. package/src/vendor/provider-kit/src/list-rows.js +35 -0
  96. package/src/vendor/provider-kit/src/md-compare.js +39 -0
  97. package/src/vendor/provider-kit/src/md-tables.js +94 -0
  98. package/src/vendor/provider-kit/src/prompt.js +50 -0
  99. package/src/vendor/provider-kit/src/released.js +219 -0
  100. package/src/vendor/provider-kit/src/specs.js +248 -0
  101. package/src/vendor/provider-kit/src/transport.js +22 -0
  102. package/src/vendor/sync-core/index.js +92 -0
  103. package/src/vendor/sync-core/src/base.js +81 -0
  104. package/src/vendor/sync-core/src/compare.js +338 -0
  105. package/src/vendor/sync-core/src/dialect.js +51 -0
  106. package/src/vendor/sync-core/src/fields.js +36 -0
  107. package/src/vendor/sync-core/src/legacy.js +100 -0
  108. package/src/vendor/sync-core/src/normalize.js +971 -0
  109. package/src/vendor/sync-core/src/push.js +85 -0
  110. package/src/vendor/sync-core/src/retarget.js +278 -0
  111. package/src/vendor/sync-core/src/task-block.js +308 -0
  112. package/src/vendor/sync-core/src/verify.js +67 -0
  113. 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*).