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