@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,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 — <N> files, +<a> −<d>
|
|
236
|
+
|
|
237
|
+
- **local** — <the `local:` URL>
|
|
238
|
+
- **network** — <the `network:` URL, or off with the command that turns it on>
|
|
239
|
+
- **remote** — <the `remote:` URL, or off with the command that turns it on>
|
|
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.
|