@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,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*).
|