@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,306 @@
1
+ ---
2
+ name: spec-push
3
+ description: Push a spec up to its linked Jira issue (repo → Jira, one-way). The repo is the source of truth and Jira a generated mirror, so content is never read back or merged. A spec is an issue and each phase a subtask. Opt-in — needs specs/.core/jira.config.json. Use when the user says "/spec-push", "push to Jira", "update the Jira issue from this spec".
4
+ ---
5
+
6
+ # /spec-push — send a spec up to Jira (one-way)
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
+ Repo → Jira. The repo is the **source of truth**; Jira is a
14
+ **generated mirror**. A spec is a Jira **issue**; each phase is a **subtask**,
15
+ and the phase file is mirrored into that subtask's description as written — its
16
+ prose, its sections, and its tasks as a read-only checklist, never as issues of
17
+ their own. Only the phase's h1 and `> **Status:**` line are left out, because
18
+ both are pushed as fields of their own (the subtask's summary and status). This
19
+ skill computes what changed since the last push and applies it — it never reads
20
+ Jira content back or merges. A person editing the mirror in Jira will see it
21
+ overwritten on the next push.
22
+
23
+ **Opt-in**: only runs when `specs/.core/jira.config.json` exists. If absent,
24
+ tell the user how to enable Jira sync and stop.
25
+
26
+ ## 1. Identify the target spec
27
+
28
+ Use the argument, else the spec in context; ask if unclear.
29
+
30
+ ## 2. Pick the transport, and get the project's statuses
31
+
32
+ **Ask the engine first — it decides, not you:**
33
+
34
+ ```
35
+ skitterspec spec-sync states --json
36
+ ```
37
+
38
+ With Jira credentials set (`JIRA_EMAIL` + `JIRA_API_TOKEN`, or whatever
39
+ `auth.emailEnv`/`auth.tokenEnv` name) the engine talks to Jira Cloud directly.
40
+ That is the **fast path**, and it is the default whenever credentials resolve:
41
+ descriptions never pass through you, in either direction. Without them it falls
42
+ back to MCP and everything below works as it always has.
43
+
44
+ - **`transport = api`** → the command prints the project's status names as a
45
+ JSON array. Write it to a file and carry on to step 3.
46
+ **Skip the MCP tool discovery entirely** — you make no Jira calls here.
47
+ - **`transport = mcp`** → do the MCP work: discover the issue
48
+ **read + create/update** tools at runtime on Atlassian's official remote MCP
49
+ server (mcp.atlassian.com) — `getJiraIssue`, `createJiraIssue`,
50
+ `editJiraIssue`, plus `transitionJiraIssue` for status moves. If Jira isn't
51
+ connected or a needed tool is missing, relay the fix and stop,
52
+ **writing nothing**. Then fetch the project's status **names** yourself and
53
+ write them to a file as a JSON array
54
+ (e.g. `["To Do","In Progress","Done"]`).
55
+
56
+ Either way you end up with a states file. Step 3 requires it:
57
+ `push` **refuses to run** without one, because a Jira status is reached by
58
+ **workflow transition** — the engine resolves the transition to the mapped
59
+ status and refuses loudly when none reaches it, and it never path-finds through
60
+ intermediate statuses. A configured name the project lacks would therefore fail
61
+ mid-apply, after the description already landed, so it is checked before
62
+ anything is written. If the check reports a name that isn't in the project,
63
+ stop and fix `specs/.core/jira.config.json`.
64
+
65
+ **If the check refuses, offer to fix it.** The refusal lists every configured
66
+ name the project lacks, the project's real status names, and — where the bucket
67
+ makes it unambiguous — which one to use instead. Relay that, then offer to
68
+ apply it to `specs/.core/jira.config.json` → `states`, and do so on the user's
69
+ confirmation. Never edit their config without asking, and never guess a bucket
70
+ the refusal made no suggestion for — ask which status they want.
71
+
72
+ `--skip-state-check` exists for the deliberate exception; do not reach for it to
73
+ get past a failing check.
74
+
75
+ ## 3. Get the plan from the engine
76
+
77
+ ```
78
+ skitterspec spec-sync plan <spec> --workspace-states <file> --json
79
+ ```
80
+
81
+ The engine prints a JSON **plan** (no network, no remote read):
82
+
83
+ ```json
84
+ {
85
+ "issue": { "description": "…", "state": "in-progress" },
86
+ "subIssues": {
87
+ "create": [{ "ref": "01-outbox", "name": "Outbox", "goal": "…", "state": "backlog" }],
88
+ "update": [{ "id": "PROJ-2", "name": "…", "goal": "…", "state": "in-progress" }]
89
+ }
90
+ }
91
+ ```
92
+
93
+ An empty plan (no `issue`, no subtask create/update) means the mirror is up to
94
+ date — say so and stop. The plan's `subIssues` key is where the subtasks live.
95
+ `state` values are local buckets
96
+ (`backlog`/`in-progress`/`complete`/`cancelled`); map each to the Jira STATUS
97
+ name via `config.states` at apply time.
98
+
99
+ A **`phasesDeferred`** field means `mapping.phases` is `"deferred"` and this spec
100
+ has not started, so its phases are deliberately absent from the plan — the issue
101
+ pushes alone and the subtasks are minted by the push that follows `/spec-start`.
102
+ Relay the count; it is not a sign the phase files failed to parse. Nothing else
103
+ about applying the plan changes.
104
+
105
+ The plan always carries a **`phaseMode`** field — `subissue`, `deferred` or
106
+ `inline` — the mode that resolved for THIS spec's lifecycle bucket.
107
+ `mapping.phases` may be a per-bucket map, so the config alone no longer tells you
108
+ which mode a given spec got. Relay it whenever it is not `subissue`:
109
+
110
+ - **`inline`** — no subtasks are created at all. Each unlinked phase is a
111
+ section of the spec issue's own description, and the `## Phases` index stays as
112
+ its table of contents. A plan with no subtask creates is the expected shape,
113
+ not a parse failure. A phase that already carries an id keeps its subtask and
114
+ still appears under `subIssues` — apply those normally.
115
+ - **`deferred`** — as above; the `phasesDeferred` count says how many are waiting.
116
+
117
+ Nothing about how you apply the plan changes in either mode: apply exactly the
118
+ `issue` and `subIssues` the plan lists.
119
+
120
+ ## 4. Apply the plan
121
+
122
+ **On `transport = api`, this is one command:**
123
+
124
+ ```
125
+ skitterspec spec-sync apply <spec> --plan plan.json
126
+ ```
127
+
128
+ It creates and updates the issue and its subtasks, reads each description back
129
+ and runs the same check as step 4b, stamps every returned key into the spec, and
130
+ records the snapshot. **Steps 4a–5 below are then already done** — skip to step 6
131
+ and report what it printed, including the transport.
132
+
133
+ Two things it guarantees, so you don't have to manage them:
134
+
135
+ - **It writes nothing until everything checkable has been checked** — a missing
136
+ key or a `config.states` name the project lacks fails before the first write.
137
+ - **It stamps each key the moment its object exists.** If a run is interrupted,
138
+ re-run the same command: the objects it already created are linked, so the new
139
+ plan sees them as updates and no duplicate is minted. Never "start again" by
140
+ hand.
141
+
142
+ If it prints **`transport = mcp`** it has written nothing and is telling you to
143
+ apply the plan yourself — do steps 4a, 4b and 5 below. Pass `--via mcp` to force
144
+ that deliberately.
145
+
146
+ There is no project to pick, on either path: a minted issue lands in the
147
+ configured `jira.projectKey` by construction.
148
+
149
+ ## 4a. Apply it yourself — the MCP path (order matters)
150
+
151
+ **First, check whether you are about to overwrite someone.** On the API path
152
+ `apply` does this for itself, off the read-back it already makes. On the MCP
153
+ path nothing has read the issue yet, so ask before you write:
154
+
155
+ ```
156
+ skitterspec spec-sync status <spec> --remote <issuefile>
157
+ ```
158
+
159
+ using the same issue JSON step 2 fetched — it must carry the `description`. A
160
+ `drift: … description was edited on Jira since the last push` line means a
161
+ person wrote something there that step 1 below replaces.
162
+
163
+ **It is a warning and never a gate.** The repo is the source of truth and the
164
+ push still goes ahead; relay the line, name the issue, and carry on. What is
165
+ forbidden is doing it *silently* — someone's paragraph disappearing with nothing
166
+ said about it is the failure this exists to end. Nothing printed means nothing to
167
+ say, which includes every case the engine could not tell.
168
+
169
+ 1. **Spec issue** → if the overview has no `jira_identifier`, this push
170
+ **mints** it: create it with `createJiraIssue` (`project` = `jira.projectKey`;
171
+ issue type = `jira.issueType`; summary from the spec title; description from
172
+ `plan.issue.description`), then move it with `transitionJiraIssue` to the
173
+ status `config.states` maps `plan.issue.state` to. Keep the returned key and
174
+ url for step 5 — don't hand-edit frontmatter. If it already exists and
175
+ `plan.issue` is present, **update it by key** with `editJiraIssue`, and
176
+ transition only when the status actually differs.
177
+ 2. **Subtasks create** → for each, `createJiraIssue` with `parent` = the spec
178
+ issue key and issue type = `jira.subtaskType` (`name` → summary, `goal` →
179
+ description), then transition to the mapped status. Keep each returned key
180
+ against its `ref` (the phase-file basename).
181
+ 3. **Subtasks update** → `editJiraIssue` by key (summary/description), plus
182
+ `transitionJiraIssue` where the status changed.
183
+
184
+ Priority, labels, sprints and comments are Jira-native triage — do **not** push
185
+ them; they're the PM's.
186
+
187
+ ## 4b. Verify what Jira actually stored — the MCP path
188
+
189
+ *(On the API path `apply` already did this. Skip.)*
190
+
191
+ Jira stores descriptions as **ADF** (Atlassian Document Format), not markdown:
192
+ the adapter renders markdown to ADF on push and reduces what Jira holds back to
193
+ text on read-back, so the comparison is **words rather than bytes**. Check
194
+ before you record the push as good.
195
+
196
+ For each issue you created or updated in step 4a, read its `description` back
197
+ (`getJiraIssue`) and write what you got to a JSON file:
198
+
199
+ ```json
200
+ { "issue": "<stored description>", "subIssues": { "01-outbox": "<stored>" } }
201
+ ```
202
+
203
+ Then:
204
+
205
+ ```
206
+ skitterspec spec-sync verify <spec> --stored <file>
207
+ ```
208
+
209
+ It compares against what the engine sent, ignoring the formatting that
210
+ legitimately does not survive the ADF round-trip and reporting only lost or
211
+ altered **word characters**. Relay any divergence — it prints both sides around
212
+ the first difference. It exits 0 either way: the repo is unaffected and still
213
+ correct, so this is a warning, not a failure.
214
+
215
+ This is **not a pull**. Nothing read here is merged, stamped, or written
216
+ anywhere; the repo remains the only source of truth. Do it before step 5 so a
217
+ corrupted push is visible before the snapshot records it as good.
218
+
219
+ ## 5. Stamp the keys, then record the snapshot — the MCP path
220
+
221
+ *(On the API path `apply` already did both. Skip.)*
222
+
223
+ Write every key you collected back into the spec in **one** call — the engine
224
+ does the file edits, so there is no hand-editing of frontmatter:
225
+
226
+ ```
227
+ skitterspec spec-sync stamp <spec> \
228
+ --issue PROJ-11 --url https://your-site.atlassian.net/browse/PROJ-11 \
229
+ --sub 01-outbox=PROJ-12 --sub 02-api=PROJ-13
230
+ ```
231
+
232
+ Pass `--issue`/`--url` only on the push that minted the spec issue; pass one
233
+ `--sub <ref>=<key>` for every subtask **created** in step 4a.2 (updates already
234
+ have their key). It validates every ref and key **before** writing anything and
235
+ exits non-zero having changed nothing if any is wrong — so a typo can't leave the
236
+ spec half-stamped, pointing at an issue that isn't there. Fix what it reports and
237
+ re-run; it is safe to repeat.
238
+
239
+ Then record what was pushed:
240
+
241
+ ```
242
+ skitterspec spec-sync record <spec>
243
+ ```
244
+
245
+ This writes the last-pushed snapshot from the now-stamped files, so the next
246
+ `/spec-push` produces an empty plan. Commit the stamped spec + snapshot into the
247
+ branch so the mirror-link rides in the PR.
248
+
249
+ ## 5b. A refused mint, and the way back
250
+
251
+ Two refusals reach you from `apply`, and neither is a failure to retry.
252
+
253
+ **`N already exists and no spec claims it`** — the push was about to mint, and
254
+ an issue with **exactly** this title is sitting there unheld. That is almost
255
+ always the spec's own issue, whose link was lost: a bad merge, a hand edit, a
256
+ stray `sed` over the frontmatter. Adopt it rather than creating a twin. The
257
+ Linear provider ships `reattach` for this; the Jira provider does not yet, so
258
+ the fix is the manual escape hatch — read the issue key back to the user,
259
+ confirm it is the spec's own issue, then:
260
+
261
+ ```
262
+ skitterspec spec-sync stamp <spec> --issue <PROJ-N>
263
+ ```
264
+
265
+ **`N exists in Jira but could not be recorded`** — the create landed and the
266
+ stamp did not, so Jira holds an issue the repo does not point at.
267
+ **Do not re-run**:
268
+ the plan still reads the spec as unlinked, so a re-run mints a second.
269
+ `stamp` with the key the refusal named is the fix here too.
270
+
271
+ Everything else — a rate limit, a refusal that says waiting will not help — is
272
+ reported with Jira's own explanation and whether retrying can help. Relay it;
273
+ do not paraphrase.
274
+
275
+ ## 6. Report
276
+
277
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
278
+ the shape; this section carries only what is specific here.
279
+
280
+ **Verdicts**
281
+
282
+ - `✅` — the mirror matches the repo and the snapshot is recorded.
283
+ - `⚠️` — pushed, with something worth knowing: Jira stored different words, or
284
+ `phasesDeferred` phases not yet minted.
285
+ - `❌` — it wrote some objects and failed on another. Name what exists in Jira
286
+ now; `apply` stamps each key as it goes, so the re-run is an update, never a
287
+ duplicate.
288
+ - `⏸` — no config, no `jira_identifier`, or an unvalidated status list. Nothing
289
+ was written.
290
+
291
+ **Fields:** `Tracker` · `Follow-ups` · `Next`
292
+
293
+ `Tracker` carries what was created and updated — the spec issue and its
294
+ subtasks — **the transport that did it**, and that the snapshot was recorded.
295
+
296
+ **Say the transport, every time.** On the API path you never saw the
297
+ descriptions, so "pushed 12 subtasks" is the engine's report rather than your
298
+ observation, and that distinction is the difference between a fact and a
299
+ paraphrase. If it warned that Jira stored different words, relay it: the repo
300
+ is still correct and a re-push overwrites the mirror.
301
+
302
+ There is no pull — Jira is a generated mirror.
303
+
304
+ Jira has no project picker: a minted issue lands in the configured
305
+ `jira.projectKey` by construction, decided once in `specs/.core/jira.config.json`.
306
+ There is nothing to ask.
@@ -0,0 +1,256 @@
1
+ ---
2
+ name: spec-review
3
+ description: Re-validate an existing spec against the current codebase — detect drift (renamed/removed files, changed APIs, tasks already done in code, stale decisions), grill where a decision is needed, and update the spec so it's relevant again. Targets a spec by name (arg) or the spec in context. Use when the user says "/spec-review", "is this spec still accurate", "this spec has gone stale", or before picking up an old backlog spec.
4
+ ---
5
+
6
+ # /spec-review — bring a spec back in sync with the codebase
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
+ Specs rot: the code moves on while a spec sits in the backlog or pauses
14
+ mid-build. This skill re-validates a spec against the **current** code and
15
+ rewrites the stale parts so it's safe to act on. It plans only — it does not
16
+ implement anything (that's `/spec-next`).
17
+
18
+ ## 1. Identify the target spec
19
+
20
+ - Use the name/path argument if given, else the spec **in context**. If unclear,
21
+ ask which spec.
22
+ - Locate the spec folder under `specs/` (any bucket). Entry point is its
23
+ `00-overview.md`; read it **and every phase file** (`01-<slug>.md`, `02-…`)
24
+ listed in its phase index (legacy specs may be a bare `<name>.md`).
25
+
26
+ ## 2. Validate against the codebase — evidence first
27
+
28
+ Read the spec, then check every claim it makes against the real code. Don't
29
+ trust the spec's own wording — verify:
30
+
31
+ - **Referenced things still exist.** Grep/read for each `file:line`, module,
32
+ function, route, model, type, or symbol the spec names. Flag anything renamed,
33
+ moved, or deleted.
34
+ - **Impact map matches reality.** Walk every row of the `## Impact` table and
35
+ verify against the code: does the named surface (endpoint, schema/model, DB
36
+ table/migration, domain object, route, business rule) exist, and does its
37
+ stated `Change` (add/update/remove) still hold? A stale Impact map is the
38
+ highest-signal drift — each row is a named, checkable surface, so a wrong row
39
+ is exactly the "obvious mistake" the map exists to surface. Flag/refresh stale
40
+ rows (and add rows for surfaces the spec now touches but omits).
41
+ - **Tasks already done.** For each `- [ ]`, check whether the code already
42
+ implements it (it may have landed via other work). Tick `- [x]` what's done.
43
+ - **Decisions still valid.** Re-check each `## Decisions` entry against the
44
+ current architecture and `.claude/rules/*.md`. Flag any that now conflict with
45
+ how the codebase actually works (a changed convention, a superseded approach).
46
+ - **New constraints.** Note rules/patterns introduced since the spec was written
47
+ that it must now honour (e.g. a new required field, a new error type, a new
48
+ pattern the rest of the code now follows).
49
+ - **Backward compatibility.** Re-confirm the planned API/schema changes are still
50
+ additive/safe given the current code (see the project's compatibility rules).
51
+
52
+ ### Release gating (only when configured)
53
+
54
+ **Only when `specs/.core/gating.config.json` exists.** Run
55
+ `skitterspec gating check <spec>`. A spec it names has no recorded gating
56
+ decision — or a bare `none` that records nothing — and that is
57
+ **drift like any other**: the question was never asked, or its answer was lost. Grill for it in
58
+ step 3 and write the header in step 4, exactly as you would a stale file path.
59
+
60
+ **This check is advisory.** It reports; it never refuses, and nothing below is
61
+ conditional on it. A spec written before the project adopted gating has no header
62
+ and is not broken — turning this into a gate would accuse the very specs the
63
+ feature was designed not to disturb.
64
+
65
+ ## 3. Grill only where a decision is needed
66
+
67
+ Where the drift forces a choice (the old approach no longer fits, a referenced
68
+ thing is gone, scope is now ambiguous), grill the user like `/spec` Phase A —
69
+ **batching independent questions, each with a recommended answer** — but scoped
70
+ tightly to the drift. Don't re-litigate decisions that still hold. If you can
71
+ resolve it by reading the code, do that instead of asking.
72
+
73
+ ## 4. Update the spec
74
+
75
+ - Rewrite stale **Decisions** / **Solution overview** and refresh the
76
+ **Impact map** in `00-overview.md`, and stale **tasks** in the phase files,
77
+ so they match the current code and the resolved questions. Add/remove tasks
78
+ within a phase file; add a new phase by creating a `0N-<slug>.md` file
79
+ **and** a matching overview index row, or drop a dead phase by removing both.
80
+ Keep the index and files in sync; **preserve completed `[x]` history**.
81
+ - **A phase's status is the `⬜`/`🔄`/`✅` on its H1** — `# Phase 2 — <goal> 🔄`.
82
+ It is mirrored in that file's `> **Status:**` line and in the matching
83
+ `00-overview.md` phase-index row, but the **heading is the authoritative one**:
84
+ tooling reads it, and a heading carrying no emoji reads as *not started*. So a
85
+ finished phase marked only by a `**Status:** done` line is silently wrong
86
+ everywhere except the file itself. Set all three together.
87
+ - **Especially when you create phase files.** Migrating a legacy spec (a bare
88
+ `<name>.md`, or a `00-overview.md` with inline phases) into the folder +
89
+ phase-file form means authoring `0N-<slug>.md` headings from scratch — carry
90
+ each phase's real status onto its H1 as you split it out, rather than
91
+ defaulting every new file to `⬜`. A ticketing provider projects that emoji as
92
+ the phase's state in the tracker, so getting it wrong mirrors a
93
+ complete phase as not-started.
94
+ - Tick tasks already satisfied by the code; re-open `## Open questions` for
95
+ anything still undecided.
96
+ - Add a dated **Changelog** entry summarising the review (e.g. `- <date> —
97
+ Reviewed vs codebase: retargeted Phase 2 onto X, dropped Y (removed), ticked Z
98
+ (already done)`).
99
+ - **Status:** if drift is minor, leave status as-is. If a `Ready` spec needs real
100
+ re-grooming, knock it back to `Draft` (set `> **Status:** Draft` and append a
101
+ **State log** row).
102
+
103
+ **Only when `specs/.core/jira.config.json` exists** and the spec's overview
104
+ carries a `jira_identifier`. Either missing → **skip**, in one line
105
+ (`not linked to Jira — /spec-push to mirror it`), and carry on. Nothing else in
106
+ this step changes.
107
+
108
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
109
+ become the truth about this spec's state, and the tracker is a generated mirror
110
+ of it — leaving them to diverge until someone remembers to push by hand is the
111
+ gap this exists to close. It costs one engine call and no model tokens when
112
+ Jira credentials are set (see `apply.transport` in `jira.config.md`).
113
+
114
+ - **Never mint.** An unlinked spec is skipped, not created. An issue born `Done`
115
+ is tracker noise, and a spec kept deliberately local should stay that way —
116
+ `/spec-push` is how someone opts in.
117
+ - **Never fatal.** If the push fails — offline, no credentials, a Jira error —
118
+ say so and **finish the operation anyway**. The spec is
119
+ complete/cancelled/reviewed in the repo regardless; the mirror is disposable
120
+ and the next push repairs it. Do not roll anything back, and do not stop to
121
+ ask.
122
+ - **Say what happened** in the skill's report: mirror updated, skipped as
123
+ unlinked, or failed with the reason.
124
+ - **There is no assignment to release here.** Assignment sync is not yet
125
+ supported by the Jira provider; the `> **Developer:**` header stays put as the
126
+ repo's own record of who actioned the work.
127
+
128
+ ### Why it sits here
129
+
130
+ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
131
+ **after the `git mv` and before the commit**, and both halves matter:
132
+
133
+ - **After the move**, because the projection reads a spec's workflow status from
134
+ its folder bucket. Push while the folder is still in `in-progress/` and the
135
+ issue is set to the status the spec is *leaving*.
136
+ - **Before the commit**, because the push stamps keys into the spec and writes a
137
+ snapshot under `specs/.core/`. Push after the commit instead and both are left
138
+ uncommitted, which makes `spec-env integrate` refuse to land the branch.
139
+
140
+ The snapshot is reached **by name**, not by breadth. It is a declared
141
+ `spec.companionPaths` entry (`specs/.core/jira-base/{identifier}.base.json`),
142
+ so `spec-env stage` returns it among this spec's owned paths and the commit below
143
+ names it. This is why the commit no longer stages `specs/` wholesale: the one
144
+ file outside the spec's folder that had to be swept up is addressable, so the
145
+ breadth bought nothing and cost another session's work.
146
+
147
+ ## 4b. Hand the refresh back on a page, then wait
148
+
149
+ **Only when the project has per-spec isolation** (`specs/.core/env.config.json`
150
+ present) **and this review actually changed something.** Either missing → skip
151
+ the whole step in silence.
152
+
153
+ A refreshed spec is a rewrite of a document someone is about to work from, so it
154
+ gets read before it is committed — the same ending `/spec` now has:
155
+
156
+ ```
157
+ skitterspec spec-env review <spec> --docs --buttons refresh
158
+ ```
159
+
160
+ `--docs` reads the spec's own documents from the tree you are standing in, so it
161
+ wants no worktree and works for a spec in any bucket. These documents are
162
+ **tracked**, so the diff against `HEAD` is a patch rather than a set of new
163
+ files — which is exactly what the reader wants here: what drifted, and what you
164
+ rewrote.
165
+
166
+ **Render nothing when nothing changed.** `/spec-review` frequently concludes a
167
+ spec is still accurate, and that is a success rather than a gap. A page showing
168
+ an empty diff under a commit button asks for a verdict on nothing, and the
169
+ engine refuses it for the same reason — `nothing to review`. Take that refusal
170
+ as the answer and report the `⏸`/`✅` you already had.
171
+
172
+ **Arm nothing.** No `review arm` here: the gate asserts that a phase which ended
173
+ owes an answer, and a re-validated spec has ended no phase. Walking away leaves
174
+ an uncommitted spec, which is the state this skill has always finished in.
175
+
176
+ ### The three endings
177
+
178
+ `--buttons refresh` offers `Commit`, `Request changes` and `Discuss`.
179
+
180
+ **No start verdict, deliberately.** The spec this skill refreshed may already be
181
+ in progress — that is half of what it is used for — so `Commit & Start` would
182
+ offer to provision a worktree for a spec that already has one.
183
+
184
+ - **`commit`** — hand off to `review.commitWith` (`/commit` by default) with the
185
+ pathspec the render reported on `docs.paths`, then finish. The spec keeps
186
+ whatever status step 4 left it in.
187
+ - **`changes`** — work the notes into the spec, record a resolution for each so
188
+ the next render strikes it through with what changed, re-render, and wait
189
+ again.
190
+ - **`discuss`** — report and talk. Claim nothing, change nothing.
191
+
192
+ ### Then wait for it
193
+
194
+ **The wait is a command. Do not write one.** Note the moment you start waiting
195
+ as an ISO timestamp, run the engine's wait in the background, and end your turn:
196
+
197
+ ```
198
+ skitterspec spec-env review wait <spec> --since <the timestamp>
199
+ ```
200
+
201
+ Pass no timeout. On waking, let the engine pick the pass —
202
+ `skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json`
203
+ — and route on the verdict above.
204
+
205
+ **Where the page is `file://`** the pass is copied and pasted, so the wait is the
206
+ turn ending and the banner says so. Do not start a watch that cannot fire.
207
+
208
+ ## 5. Report
209
+
210
+ Do **not** `git commit` unless the user asks.
211
+
212
+ End with the block defined in `.claude/rules/spec-reports.md`. That file carries
213
+ the shape; this section carries only what is specific here.
214
+
215
+ **Verdicts**
216
+
217
+ - `✅` — checked against the code and brought up to date; safe to `/spec-start`.
218
+ - `⚠️` — updated, but dropped back to `Draft`: questions are open that only the
219
+ operator can answer. Name them.
220
+ - `⏸` — no such spec, or nothing to review. Nothing changed.
221
+
222
+ **Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
223
+
224
+ `Built` is the drift found and what you changed about it — a renamed file, an
225
+ API that moved, a task the code already does. `Spec` carries the status the
226
+ review leaves it in, which is the answer to "can I start this?".
227
+
228
+ **Where step 4b rendered a page the run is waiting**, so the block omits the
229
+ `Review` row and ends on the banner from `.claude/rules/spec-reports.md` — the
230
+ tier stack copied from the engine's own lines, never one link chosen for the
231
+ reader:
232
+
233
+ ---
234
+
235
+ ## ⏸ Review ready — &lt;N&gt; files, +&lt;a&gt; −&lt;d&gt;
236
+
237
+ - **local** — &lt;the `local:` URL&gt;
238
+ - **network** — &lt;the `network:` URL, or off with the command that turns it on&gt;
239
+ - **remote** — &lt;the `remote:` URL, or off with the command that turns it on&gt;
240
+
241
+ I'm holding here until you send a verdict — the wait covers local and network.
242
+
243
+ `Commit` commits the refresh · `Request changes` works them now
244
+
245
+ ---
246
+
247
+ `Next` then names the page rather than a command, because the button is what
248
+ carries the work on.
249
+
250
+ Where nothing was rendered — no isolation, or no drift to show — `Next` is
251
+ `/commit, then /spec-start <name>`, since this skill does not commit and
252
+ `/spec-start` refuses a tree holding anything that is not the spec's.
253
+
254
+ **Drift found and not fixed is a `Follow-up`, not a silence.** A spec this skill
255
+ declared reviewed is one nobody will re-read; something it noticed and left
256
+ undone has to leave the session in writing or it did not happen.