@skitterbyte/skitterspec-linear 11.0.0 → 13.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 +260 -10
- package/README.md +32 -2
- package/assets/claude-md-section.md +48 -2
- package/assets/commands/spec-connect.md +2 -2
- package/assets/commands/spec-live.md +2 -2
- package/assets/core/SETUP.md +21 -3
- package/assets/core/env.config.json.example +9 -3
- package/assets/core/env.config.md +102 -30
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/linear.config.md +67 -8
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +224 -15
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +64 -13
- package/assets/skills/spec-bug/SKILL.md +193 -27
- package/assets/skills/spec-cancel/SKILL.md +99 -8
- package/assets/skills/spec-claim/SKILL.md +114 -0
- package/assets/skills/spec-complete/SKILL.md +123 -22
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +202 -22
- package/assets/skills/spec-init/SKILL.md +49 -9
- package/assets/skills/spec-linear-setup/SKILL.md +86 -7
- package/assets/skills/spec-list/SKILL.md +218 -0
- package/assets/skills/spec-next/SKILL.md +300 -7
- package/assets/skills/spec-push/SKILL.md +45 -22
- package/assets/skills/spec-review/SKILL.md +59 -11
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +426 -66
- package/assets/skills/spec-status/SKILL.md +24 -2
- package/assets/skills/spec-sync/SKILL.md +47 -11
- package/assets/skills/spec-to-main/SKILL.md +42 -20
- package/package.json +11 -7
- package/src/cli.js +1710 -80
- package/src/env/building.js +143 -0
- package/src/env/classify.js +91 -0
- package/src/env/config.js +57 -9
- package/src/env/provision.js +192 -19
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +296 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/gating.js +155 -0
- package/src/init.js +124 -2
- package/src/prompts.js +10 -1
- package/src/vendor/linear/api.js +104 -1
- package/src/vendor/linear/cli-sync.js +874 -17
- package/src/vendor/linear/config.js +8 -0
- package/src/vendor/linear/credentials.js +94 -0
- package/src/vendor/linear/doctor.js +35 -0
- package/src/vendor/linear/identity.js +105 -0
- package/src/vendor/linear/mcp.js +26 -0
- package/src/vendor/sync-core/index.js +6 -2
- package/src/vendor/sync-core/src/compare.js +74 -5
- package/src/vendor/sync-core/src/normalize.js +30 -0
- package/src/vendor/sync-core/src/push.js +11 -1
- package/src/vendor/sync-core/src/write.js +38 -0
- package/LICENSE +0 -21
|
@@ -5,6 +5,11 @@ description: Create a new spec-driven-development spec. Grills the user to a cle
|
|
|
5
5
|
|
|
6
6
|
# /spec — author a new spec
|
|
7
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
|
+
|
|
8
13
|
Produce ONE concise spec in `specs/backlog/`. Do not start coding — this skill
|
|
9
14
|
plans only. Implementation happens later via `/spec-start`.
|
|
10
15
|
|
|
@@ -139,7 +144,17 @@ not write the spec until this is resolved.
|
|
|
139
144
|
`worktree + docker` only when it must. This sets the `> **Stack:**` header
|
|
140
145
|
that `/spec-start` acts on (it can be escalated later). Skip when isolation
|
|
141
146
|
isn't enabled — leave the default `worktree`.
|
|
142
|
-
10. **
|
|
147
|
+
10. **Release gating** *(only when `specs/.core/gating.config.json` exists)* —
|
|
148
|
+
should this ship behind a feature flag, or land live?
|
|
149
|
+
**Offer, don't impose**: the user decides and you raise it, so a spec
|
|
150
|
+
never reaches
|
|
151
|
+
`/spec-complete` with the question unasked. Cite the project's own
|
|
152
|
+
`guidance` path from that config when it names one — skitterspec knows
|
|
153
|
+
nothing about how this project does flags, and must not guess. Record the
|
|
154
|
+
answer **either way**: a flag name, or `none: <one-line reason>`. "No" is a
|
|
155
|
+
decision and belongs in the header; silence is not. Skip entirely when the
|
|
156
|
+
config is absent — that project does not use flags.
|
|
157
|
+
11. **Open questions** — anything still undecided.
|
|
143
158
|
|
|
144
159
|
Stop grilling when there are no unresolved branches that would change the spec.
|
|
145
160
|
Briefly play back the agreed understanding before writing.
|
|
@@ -198,6 +213,9 @@ the codebase, link rather than duplicate):
|
|
|
198
213
|
> **Area:** <comma-separated files/modules this touches>
|
|
199
214
|
> **Stack:** <worktree — or "worktree + docker" if it touches the DB/stateful
|
|
200
215
|
> services; only acted on when isolation is enabled — see Phase A item 9>
|
|
216
|
+
> **Gating:** <flag name — or "none: <one-line reason>". Only when release gating
|
|
217
|
+
> is configured; omit the line entirely otherwise. An empty value or a bare
|
|
218
|
+
> "none" is not a valid outcome — see Phase A item 10>
|
|
201
219
|
|
|
202
220
|
## Problem
|
|
203
221
|
|
|
@@ -301,13 +319,14 @@ Rules for the spec body:
|
|
|
301
319
|
phase index.
|
|
302
320
|
- **Honour project conventions** when writing tasks — reference the relevant
|
|
303
321
|
`.claude/rules/*.md` rather than re-explaining them.
|
|
304
|
-
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
322
|
+
-
|
|
323
|
+
**The `## Impact` table is derived from Phase A items 3 (Affected areas) & 5 (Data/API impact)**
|
|
324
|
+
— a structured place to record what those already surface, not new grilling.
|
|
325
|
+
It is the scannable substitute for spelling impact out in prose: name the
|
|
326
|
+
surfaces (endpoints, schemas, DB tables, domain objects, routes, business
|
|
327
|
+
rules) instead of describing them, keep `Detail` terse, and let it — not
|
|
328
|
+
paragraphs — carry the blast radius. It complements the `Area:` header
|
|
329
|
+
(files) by naming behavioural surfaces.
|
|
311
330
|
- **Changelog** is mandatory and lives in the spec. Every later decision or
|
|
312
331
|
course-correction gets a dated one-line entry. Convert relative dates to
|
|
313
332
|
absolute.
|
|
@@ -315,10 +334,10 @@ Rules for the spec body:
|
|
|
315
334
|
|
|
316
335
|
## Phase C — finish up
|
|
317
336
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
337
|
+
Decide the status: **`Ready`** in `backlog` when grilling in Phase A resolved the
|
|
338
|
+
open questions, `Draft` when you deliberately left some unresolved. Either way
|
|
339
|
+
the next step is `/spec-start`. The Report section below is where all of that
|
|
340
|
+
reaches the user — do not narrate it here as well.
|
|
322
341
|
|
|
323
342
|
## Phase D — record the isolation stack (only if configured)
|
|
324
343
|
|
|
@@ -332,6 +351,15 @@ run `skitterspec spec-env up <name>` to add Docker to an existing worktree). If
|
|
|
332
351
|
`env.config.json` is absent, isolation is off — leave the default `worktree` and
|
|
333
352
|
finish as above.
|
|
334
353
|
|
|
354
|
+
## Phase D2 — record the gating decision (only if configured)
|
|
355
|
+
|
|
356
|
+
**Only when `specs/.core/gating.config.json` exists.** Make sure the
|
|
357
|
+
`> **Gating:**` header carries the Phase A item 10 answer — a flag name, or
|
|
358
|
+
`none: <reason>` using the config's `default` wording if it sets one. Nothing is
|
|
359
|
+
provisioned or enforced by this: the header exists so the decision is
|
|
360
|
+
**on the record and reviewable**, and `skitterspec gating check` reports a spec that has
|
|
361
|
+
none. It never blocks. If the config is absent, do not write the line at all.
|
|
362
|
+
|
|
335
363
|
## Phase E — link to a ticketing provider (only if one is installed)
|
|
336
364
|
|
|
337
365
|
**Only when a ticketing provider is installed and configured** (it ships the
|
|
@@ -359,7 +387,7 @@ does — you never create the issue by hand:
|
|
|
359
387
|
2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
|
|
360
388
|
Keep the chosen id for step 4.
|
|
361
389
|
3. **Get the plan.**
|
|
362
|
-
`skitterspec spec-sync
|
|
390
|
+
`skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
|
|
363
391
|
— the spec is unlinked, so this plan is all-creates: the issue and one
|
|
364
392
|
sub-issue per phase.
|
|
365
393
|
4. **Apply it.**
|
|
@@ -423,3 +451,26 @@ say so in one line — *"project picker unavailable"* — and carry on with
|
|
|
423
451
|
|
|
424
452
|
If `linear.projectId` is set but that Project is archived or missing, relay
|
|
425
453
|
Linear's error and stop rather than silently minting an unparented issue.
|
|
454
|
+
|
|
455
|
+
## Report
|
|
456
|
+
|
|
457
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
458
|
+
the shape; this section carries only what is specific here.
|
|
459
|
+
|
|
460
|
+
**Verdicts**
|
|
461
|
+
|
|
462
|
+
- `✅` — a `Ready` spec is written to `specs/backlog/<name>/`.
|
|
463
|
+
- `⚠️` — written as `Draft`; open questions were deliberately left. Name them.
|
|
464
|
+
- `⏸` — grilling did not reach a shared understanding, so nothing was written.
|
|
465
|
+
That is the skill working: a spec written over an unresolved requirement is
|
|
466
|
+
the outcome Phase A exists to prevent.
|
|
467
|
+
|
|
468
|
+
**Fields:** `Tracker` · `Spec` · `Built` · `Follow-ups` · `Next`
|
|
469
|
+
|
|
470
|
+
`Built` is the spec's path and phase count; `Spec` is its status and bucket;
|
|
471
|
+
`Next` is `/spec-start <name>`, with the name spelled the way it must be typed.
|
|
472
|
+
`Tracker` appears only when a provider is installed and linked it.
|
|
473
|
+
|
|
474
|
+
**`Follow-ups` is almost always `none` here.** This skill's whole job is to
|
|
475
|
+
capture work, so anything it surfaced belongs in the spec it just wrote rather
|
|
476
|
+
than in a follow-up line beneath it.
|
|
@@ -5,6 +5,11 @@ description: Investigate a bug, capture it as a Bug-type spec, and drive it red
|
|
|
5
5
|
|
|
6
6
|
# /spec-bug — investigate a bug, prove it with a failing test, fix it
|
|
7
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
|
+
|
|
8
13
|
This is the **bug** counterpart to `/spec` (which is for **features**, plan-only).
|
|
9
14
|
Unlike `/spec`, this skill is hands-on and test-first: it reproduces the bug as a
|
|
10
15
|
**failing test (RED)**, captures a lean Bug spec, then works the test to **GREEN**.
|
|
@@ -140,25 +145,13 @@ provision from — you'll flesh it out in §4:
|
|
|
140
145
|
`## Symptom` you established above.
|
|
141
146
|
- Run `skitterspec spec-env up bug-<name>` (the `spec-env` CLI engine). It prints
|
|
142
147
|
the `git worktree add … -b bug/<name>` command (a branch forked from `main`),
|
|
143
|
-
the worktree path,
|
|
144
|
-
- Run the printed
|
|
145
|
-
commit, so
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
mv specs/in-progress/bug-<name> <worktreePath>/specs/in-progress/
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
The `mkdir -p` is not belt-and-braces. Git does not store empty directories,
|
|
154
|
-
so `specs/in-progress/` is **absent** from a fresh worktree whenever that
|
|
155
|
-
bucket happens to be empty on `main` — the common case, since it empties every
|
|
156
|
-
time the last in-progress spec completes. `mv` into a missing destination
|
|
157
|
-
renames your spec folder **to** `specs/in-progress`, silently: the spec's files
|
|
158
|
-
end up one level too high, `00-overview.md` sits where the bucket should be,
|
|
159
|
-
and every later step still appears to work until something cannot find the
|
|
160
|
-
spec. Confirm the result before carrying on — you want
|
|
161
|
-
`<worktreePath>/specs/in-progress/bug-<name>/00-overview.md`.
|
|
148
|
+
the worktree path, and any `in the worktree, run:` bootstrap steps.
|
|
149
|
+
- Run the printed commands in order. The plan **commits the stub first** — the
|
|
150
|
+
worktree forks from `main`'s last commit, so the stub has to be in it — and
|
|
151
|
+
then adds the worktree. Nothing to move afterwards: the spec is already there.
|
|
152
|
+
The commit is planned, not silent; it appears in the printed plan above the
|
|
153
|
+
`git worktree add`, and `spec-env up` refuses outright if anything *other* than
|
|
154
|
+
this spec is uncommitted.
|
|
162
155
|
- **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
|
|
163
156
|
none of the repo's gitignored files (`.env`, local overrides). Run the printed
|
|
164
157
|
`in the worktree, run:` steps (file seeding, then `setup`) in order, before
|
|
@@ -169,8 +162,8 @@ provision from — you'll flesh it out in §4:
|
|
|
169
162
|
edits will prompt.
|
|
170
163
|
- **Do everything below in the worktree**, on the branch — the red test, the fix,
|
|
171
164
|
and the rest of the spec. Act on the worktree with absolute paths /
|
|
172
|
-
`git -C <worktreePath>`, or open a
|
|
173
|
-
|
|
165
|
+
`git -C <worktreePath>`, or open a session rooted at the printed path. `main`
|
|
166
|
+
changes only when the branch merges (at `/spec-complete`).
|
|
174
167
|
|
|
175
168
|
## 3. Write the failing test FIRST (RED) — mandatory
|
|
176
169
|
|
|
@@ -207,6 +200,8 @@ because work is already underway. Keep it lean:
|
|
|
207
200
|
> **Developer:** <git user.name — you, since you're fixing it now>
|
|
208
201
|
> **Raised:** <YYYY-MM-DD (today)>
|
|
209
202
|
> **Area:** <files/modules>
|
|
203
|
+
> **Gating:** <flag name — or "none: <one-line reason>". Only when release gating
|
|
204
|
+
> is configured; omit the line entirely otherwise>
|
|
210
205
|
|
|
211
206
|
## Symptom
|
|
212
207
|
|
|
@@ -281,7 +276,7 @@ does — you never create the issue by hand:
|
|
|
281
276
|
2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
|
|
282
277
|
Keep the chosen id for step 4.
|
|
283
278
|
3. **Get the plan.**
|
|
284
|
-
`skitterspec spec-sync
|
|
279
|
+
`skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
|
|
285
280
|
— the spec is unlinked, so this plan is all-creates: the issue and one
|
|
286
281
|
sub-issue per phase.
|
|
287
282
|
4. **Apply it.**
|
|
@@ -314,6 +309,66 @@ and **never auto-push git** — Linear's own automation reacts to real branch/PR
|
|
|
314
309
|
events later. Report the Linear issue URL as part of the skill's finish-up
|
|
315
310
|
message.
|
|
316
311
|
|
|
312
|
+
**Only when all three hold**: `specs/.core/linear.config.json` exists, its
|
|
313
|
+
`sync.fieldOwnership` includes `assignee`, and the spec carries a
|
|
314
|
+
`linear_identifier`. Any one missing → skip this step silently and carry on; a
|
|
315
|
+
project that has not opted in must see no trace of assignment.
|
|
316
|
+
|
|
317
|
+
**Never blocks, never fails the skill.** Everything below is best-effort: the
|
|
318
|
+
branch is provisioned and the spec is moving either way, and an unassigned issue
|
|
319
|
+
is a cosmetic gap that `/spec-claim` closes later.
|
|
320
|
+
|
|
321
|
+
1. **Work out who you are.** Run `skitterspec spec-sync whoami --json`.
|
|
322
|
+
- `ok: true` → use `id` and `name`. Nothing to ask.
|
|
323
|
+
- `source: "mcp"` or the command reports no API key → call the discovered
|
|
324
|
+
user-read tool with `me`, then cache it:
|
|
325
|
+
`skitterspec spec-sync whoami --set <id> --name "<name>"`.
|
|
326
|
+
- `ok: false` → identity is **unknown**, which is an ordinary state (a shared
|
|
327
|
+
or bot key, an offline machine). Go to step 2.
|
|
328
|
+
2. **Unknown identity — three states, not two.** Decide by what is actually
|
|
329
|
+
reachable, and route the third to inaction:
|
|
330
|
+
- **Linear reachable *and* this is an interactive session** → offer a short
|
|
331
|
+
user search (`skitterspec spec-sync users <name-or-email>`, or the
|
|
332
|
+
user-list tool on MCP), let the operator pick, and offer to cache it with
|
|
333
|
+
`whoami --set` so this is asked once per machine rather than once per spec.
|
|
334
|
+
- **Not reachable, or not interactive** → say so in one line
|
|
335
|
+
(`assignment skipped — no Linear identity`) and **carry on**. Do not prompt
|
|
336
|
+
for something you could not act on, and never stall a `/spec-start` on it.
|
|
337
|
+
3. **Record it on the spec** — through the engine, never by hand-editing
|
|
338
|
+
frontmatter:
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
skitterspec spec-sync assign <spec> --to <user-id> --name "<display name>"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
4. **Leave `> **Developer:**` as `git config user.name`** — the step above this
|
|
345
|
+
seam already set it, and this seam must not overwrite it with the tracker's
|
|
346
|
+
display name.
|
|
347
|
+
|
|
348
|
+
Those two names are the same person and often not the same string, and the
|
|
349
|
+
git one is the one everything else in the spec already uses: `Author:`, every
|
|
350
|
+
**State log** `By` row, and every commit. Writing the tracker's name into this
|
|
351
|
+
one field would leave a spec whose own audit trail contradicts its header —
|
|
352
|
+
a worse problem than the one it would solve. Anyone wanting a different name
|
|
353
|
+
on their specs sets `git config user.name`, and it stays consistent
|
|
354
|
+
everywhere.
|
|
355
|
+
|
|
356
|
+
The header names **who is building this**; the stamp in step 3 names
|
|
357
|
+
**which tracker account it is assigned to**. They answer different questions,
|
|
358
|
+
so they need not be the same string. The one place that reasoning does not hold
|
|
359
|
+
is handing a spec to *someone else* — there is no local git name for them —
|
|
360
|
+
and that case belongs to `/spec-claim --to`, which sets the header from the
|
|
361
|
+
tracker deliberately.
|
|
362
|
+
|
|
363
|
+
**Nothing is pushed here.** `assign` writes the repo only, and the refresh these
|
|
364
|
+
skills already run sends it. Assignment is an ordinary field of the projection,
|
|
365
|
+
not a side errand with its own network call.
|
|
366
|
+
|
|
367
|
+
**There is no unassign step anywhere.** The projection derives the assignee from
|
|
368
|
+
the spec's lifecycle bucket, so `/spec-complete` and `/spec-cancel` release the
|
|
369
|
+
issue through the push they already make. The stamp deliberately stays in the
|
|
370
|
+
file: who actioned the work outlives who is currently holding it.
|
|
371
|
+
|
|
317
372
|
### Picking the Linear Project
|
|
318
373
|
|
|
319
374
|
Run this **only when minting a spec issue** — creating it for the first time. On
|
|
@@ -346,6 +401,19 @@ say so in one line — *"project picker unavailable"* — and carry on with
|
|
|
346
401
|
If `linear.projectId` is set but that Project is archived or missing, relay
|
|
347
402
|
Linear's error and stop rather than silently minting an unparented issue.
|
|
348
403
|
|
|
404
|
+
### Release gating (only when configured)
|
|
405
|
+
|
|
406
|
+
**Only when `specs/.core/gating.config.json` exists.** Before writing the spec,
|
|
407
|
+
ask: should this fix ship behind a feature flag, or land live?
|
|
408
|
+
**Offer, don't impose** — cite the project's own `guidance` path when the config
|
|
409
|
+
names one, and
|
|
410
|
+
record the answer either way, as a flag name or `none: <one-line reason>`.
|
|
411
|
+
|
|
412
|
+
A bug fix ships in the next release exactly like a feature, so it gets the same
|
|
413
|
+
question rather than an assumption. A risky rewrite of a broken path is precisely
|
|
414
|
+
where a kill-switch earns its keep; a one-line null check is precisely where
|
|
415
|
+
`none:` is the honest answer. Skip entirely when the config is absent.
|
|
416
|
+
|
|
349
417
|
## 5. Drive to GREEN
|
|
350
418
|
|
|
351
419
|
- Implement the **minimal, root-cause** fix. Match surrounding code; honour all
|
|
@@ -383,10 +451,108 @@ sub-issue jump from Backlog straight to Done, with nothing visible in between.
|
|
|
383
451
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
384
452
|
unlinked, or failed with the reason.
|
|
385
453
|
|
|
454
|
+
## 5b. Render the page — then offer the review, never write it
|
|
455
|
+
|
|
456
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
457
|
+
present). Without it there is no worktree to read and this step does not exist —
|
|
458
|
+
skip it in silence rather than explaining an absence.
|
|
459
|
+
|
|
460
|
+
The fix is green and nothing is committed yet. That is the moment the page is
|
|
461
|
+
about, so render it now — **after** the tests pass and **before** the commit:
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
skitterspec spec-env review <spec>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**This is free.** The engine reads git and splices the patches into a template;
|
|
468
|
+
the diff never passes through you, so a 266KB patch costs nothing.
|
|
469
|
+
|
|
470
|
+
**Then offer `/spec-diff`. Do not run it.** The written review is the part that
|
|
471
|
+
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
472
|
+
— and that spend is the operator's call, not a default.
|
|
473
|
+
|
|
474
|
+
**The offer is the `Review` row of step 6's block** — the counts, the page link
|
|
475
|
+
and a question, in one row:
|
|
476
|
+
|
|
477
|
+
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
|
|
478
|
+
|
|
479
|
+
**It ends in a question, addressed to someone.** It was once a fenced block of
|
|
480
|
+
engine output, and it fired on every phase and was never once taken: two quoted
|
|
481
|
+
lines under the test counts, addressed to nobody, with the report then closing
|
|
482
|
+
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
483
|
+
so they did. A row in a labelled table is findable; a question in it is
|
|
484
|
+
answerable. Both halves are load-bearing.
|
|
485
|
+
|
|
486
|
+
**Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
|
|
487
|
+
the page and the question stay in the same row: two adjacent rows about one page
|
|
488
|
+
make the reader resolve a distinction before acting on either. A later edit that
|
|
489
|
+
moves it out of the block, or separates the link from the question, undoes this
|
|
490
|
+
and should be read as a regression rather than tidying.
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
Relay the **`open:`** line rather than the bare path: a path is not clickable in
|
|
494
|
+
any terminal, and a page nobody can open is a page nobody reads.
|
|
495
|
+
|
|
496
|
+
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
497
|
+
something behind that this tooling cannot remove, so it is always an ask. A
|
|
498
|
+
`file://` link is no use on a phone, and saying so **is** the ask —
|
|
499
|
+
`/spec-diff` §6 owns how.
|
|
500
|
+
**Follow the `reader:` line the engine printed — do not sniff for it.** It
|
|
501
|
+
answers where the person reading this is sitting, and the offer changes with it:
|
|
502
|
+
|
|
503
|
+
- **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
|
|
504
|
+
unknown is the ordinary state of a local machine, and a warning there is an
|
|
505
|
+
accusation against a healthy session.
|
|
506
|
+
- **`local`** — the `file://` URL.
|
|
507
|
+
- **`remote`** — the engine has already stood its local server up and put a URL
|
|
508
|
+
the reader can open on `open:`. So there is **nothing special to say**: relay
|
|
509
|
+
that line like any other. Any `also:` lines under it are the other addresses
|
|
510
|
+
this machine has, offered because the best-guess one can be wrong — pass them
|
|
511
|
+
on rather than editing them out.
|
|
512
|
+
|
|
513
|
+
**Never read an environment variable to decide this** — not `SSH_CONNECTION`,
|
|
514
|
+
not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
|
|
515
|
+
and in `--json`, and a second implementation here could not be tested and would
|
|
516
|
+
drift.
|
|
517
|
+
|
|
518
|
+
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
519
|
+
authorises a local server — one process, ended by one flag, leaving nothing
|
|
520
|
+
behind — and authorises nothing else. Publishing leaves a page this tooling
|
|
521
|
+
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
522
|
+
serve (a busy port, a machine with no network address) it falls back to the
|
|
523
|
+
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
524
|
+
|
|
525
|
+
- **Never fatal.** A failed render — no worktree, a git error — is one line, and
|
|
526
|
+
the fix is still done. The page is a convenience; the repo is the record.
|
|
527
|
+
|
|
386
528
|
## 6. Report
|
|
387
529
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
`/spec-complete`
|
|
392
|
-
|
|
530
|
+
Do **not** `git commit` unless the user asks. The spec stays in `in-progress`;
|
|
531
|
+
`Next` names the commit and then `/spec-complete`, as
|
|
532
|
+
`/commit, then /spec-complete` — this skill leaves the fix uncommitted, and
|
|
533
|
+
`/spec-complete` §2 refuses on pre-existing uncommitted changes, so naming only
|
|
534
|
+
the second half sends the reader into a refusal. `/spec-complete` then verifies
|
|
535
|
+
and archives it (**when isolated**, the fix lives on the bug's branch, and it
|
|
536
|
+
merges that back to `main`).
|
|
537
|
+
|
|
538
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
539
|
+
the shape; this section carries only what is specific here.
|
|
540
|
+
|
|
541
|
+
**Verdicts**
|
|
542
|
+
|
|
543
|
+
- `✅` — red→green, the fix is in and the suite passes.
|
|
544
|
+
- `⚠️` — green, with something worth knowing (a narrowed repro, a mirror that
|
|
545
|
+
did not refresh).
|
|
546
|
+
- `❌` — the test is still red, or a later test broke. Quote the failure.
|
|
547
|
+
- `⏸` — the bug could not be reproduced, so nothing was written. Say what was
|
|
548
|
+
tried; an unreproduced bug is a finding, not a failed run.
|
|
549
|
+
|
|
550
|
+
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
551
|
+
`Review` · `Follow-ups` · `Next`
|
|
552
|
+
|
|
553
|
+
`Cause` is the root cause in one clause and `Built` is the fix — the
|
|
554
|
+
failing→passing test belongs in `Tests`, named, so the evidence is a test name
|
|
555
|
+
rather than an adjective.
|
|
556
|
+
|
|
557
|
+
Step 5b's offer is the `Review` row, not a paragraph after the block — the
|
|
558
|
+
counts, the link and the question in one row. Nothing follows the block.
|
|
@@ -5,6 +5,11 @@ description: Cancel a spec — capture the reason, record final progress, stamp
|
|
|
5
5
|
|
|
6
6
|
# /spec-cancel — record, stamp a reason, archive a spec
|
|
7
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
|
+
|
|
8
13
|
## 1. Identify the target spec
|
|
9
14
|
|
|
10
15
|
- Use the name/path argument if given, else the spec **in context**. If unclear,
|
|
@@ -71,6 +76,12 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
|
|
|
71
76
|
Do not roll anything back, and do not stop to ask.
|
|
72
77
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
73
78
|
unlinked, or failed with the reason.
|
|
79
|
+
- **There is no unassign step, and that is not an omission.** With `assignee` in
|
|
80
|
+
`sync.fieldOwnership`, the projection derives it from the spec's lifecycle
|
|
81
|
+
bucket — so moving the folder to `complete`/`cancelled` is *itself* what
|
|
82
|
+
releases the issue, and the push above carries it like any other field. The
|
|
83
|
+
`linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
|
|
84
|
+
they record who actioned the work, which outlives who is holding it.
|
|
74
85
|
|
|
75
86
|
### Why it sits here
|
|
76
87
|
|
|
@@ -81,17 +92,33 @@ In `/spec-complete` and `/spec-cancel` this step is deliberately pinned
|
|
|
81
92
|
its folder bucket. Push while the folder is still in `in-progress/` and the
|
|
82
93
|
issue is set to the state the spec is *leaving*.
|
|
83
94
|
- **Before the commit**, because the push stamps ids into the spec and writes a
|
|
84
|
-
snapshot under `specs/.core/`.
|
|
85
|
-
with the status change; push after it instead and those files are left
|
|
95
|
+
snapshot under `specs/.core/`. Push after the commit instead and both are left
|
|
86
96
|
uncommitted, which makes `spec-env integrate` refuse to land the branch.
|
|
87
97
|
|
|
98
|
+
The snapshot is reached **by name**, not by breadth. It is a declared
|
|
99
|
+
`spec.companionPaths` entry (`specs/.core/linear-base/{identifier}.base.json`),
|
|
100
|
+
so `spec-env stage` returns it among this spec's owned paths and the commit below
|
|
101
|
+
names it. This is why the commit no longer stages `specs/` wholesale: the one
|
|
102
|
+
file outside the spec's folder that had to be swept up is addressable, so the
|
|
103
|
+
breadth bought nothing and cost another session's work.
|
|
104
|
+
|
|
88
105
|
Then **commit the cancellation edits** — this skill wrote them, so it commits
|
|
89
|
-
them
|
|
106
|
+
them. Ask the engine which paths are this spec's, then stage and commit
|
|
107
|
+
**exactly those**:
|
|
90
108
|
|
|
91
109
|
```
|
|
92
|
-
|
|
110
|
+
skitterspec spec-env stage <name> # lists them; --json to consume
|
|
111
|
+
git add -- <the owned paths>
|
|
112
|
+
git commit -m "chore(spec): cancel <name>" -- <the owned paths>
|
|
93
113
|
```
|
|
94
114
|
|
|
115
|
+
**Never `git add specs/`.** That stages a *directory*, so a spec another
|
|
116
|
+
session is part-way through writing lands in this commit under this spec's
|
|
117
|
+
ticket. The `--` on the **commit** is the other half: a checkout has one
|
|
118
|
+
`.git/index`, shared by every session standing in it, so a bare `git commit`
|
|
119
|
+
takes whatever else is staged there however carefully you staged your own.
|
|
120
|
+
`.claude/rules/spec-planning.md` carries the full account.
|
|
121
|
+
|
|
95
122
|
**This matters more here than anywhere else.** Teardown (step 7) refuses a dirty
|
|
96
123
|
worktree and offers `--force` as the way through — and forcing would destroy the
|
|
97
124
|
cancellation record this skill just wrote. Committing first means teardown never
|
|
@@ -99,7 +126,31 @@ needs `--force`. Do not `git push`.
|
|
|
99
126
|
|
|
100
127
|
## 6. Report
|
|
101
128
|
|
|
102
|
-
|
|
129
|
+
**The block is emitted when the run ends, not where this section sits.** The
|
|
130
|
+
sections below run after it, so their outcome belongs in the block — write it
|
|
131
|
+
once, at the end, with what actually happened.
|
|
132
|
+
|
|
133
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
134
|
+
the shape; this section carries only what is specific here.
|
|
135
|
+
|
|
136
|
+
**Verdicts**
|
|
137
|
+
|
|
138
|
+
- `✅` — stamped, moved to `cancelled`, committed, torn down.
|
|
139
|
+
- `⚠️` — cancelled, with something worth knowing: teardown declined, a tracker
|
|
140
|
+
refresh that failed, unpushed commits the operator chose to let go.
|
|
141
|
+
- `❌` — it acted and stopped part-way. Say what is where.
|
|
142
|
+
- `⏸` — no reason given, or no such spec. Nothing changed.
|
|
143
|
+
|
|
144
|
+
**Fields:** `Tracker` · `Spec` · `Worktree` · `Follow-ups` · `Next`
|
|
145
|
+
|
|
146
|
+
**The reason goes in the verdict clause** — `✅ /spec-cancel · feat-foo ·
|
|
147
|
+
superseded by feat-bar`. It is the one thing anyone reading this later wants,
|
|
148
|
+
and a cancellation is an ordinary successful run, so it must not be mistaken for
|
|
149
|
+
a `Why`: that field is for a run that did not do what it set out to.
|
|
150
|
+
|
|
151
|
+
`Worktree` says what was reclaimed, or that teardown was declined and the
|
|
152
|
+
worktree still stands — on a cancelled spec that worktree may hold the only copy
|
|
153
|
+
of the work, so an unreclaimed one is worth a line rather than a silence.
|
|
103
154
|
|
|
104
155
|
## 7. Tear down the environment (opt-in, only if configured)
|
|
105
156
|
|
|
@@ -110,9 +161,23 @@ directly (the old `/spec-env-down` skill is gone — teardown is folded in here)
|
|
|
110
161
|
1. If `.spec-env/connected` names this spec, run `skitterspec spec-env connect
|
|
111
162
|
main` first to free the canonical ports.
|
|
112
163
|
2. `skitterspec spec-env dev down <name>` — stop its host dev servers.
|
|
113
|
-
**Standing in the worktree? Leave it before you tear it down.** If this
|
|
114
|
-
|
|
115
|
-
**first**, then run the teardown commands.
|
|
164
|
+
**Standing in the worktree? Leave it before you tear it down.** If this session's
|
|
165
|
+
cwd is inside the spec's own worktree — the normal case in `worktree` mode, since
|
|
166
|
+
`/spec-start` moves you there — get out **first**, then run the teardown commands.
|
|
167
|
+
One instruction covers it:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
cd <primary checkout>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
That is the whole mechanism, and it does not matter how you got in: the session
|
|
174
|
+
was moved by a `cd` and it leaves by one. There is no tool to call here, and none
|
|
175
|
+
should be reached for — the move in is a plain `cd` precisely because a tool that
|
|
176
|
+
asks for approval is unusable on a phone.
|
|
177
|
+
|
|
178
|
+
**`spec-env down` stays the single thing that deletes a worktree.** The plan
|
|
179
|
+
below is the only deleter, because a second one is how the teardown guards get
|
|
180
|
+
bypassed.
|
|
116
181
|
|
|
117
182
|
Not because git refuses — it does not. `git worktree remove` **succeeds** on the
|
|
118
183
|
tree you are standing in, and that is the problem: the directory vanishes under
|
|
@@ -126,6 +191,32 @@ only ordering that survives.
|
|
|
126
191
|
remove the worktree/stack and free the slot. It respects the teardown guards
|
|
127
192
|
(won't destroy a dirty/unpushed worktree without `--force`).
|
|
128
193
|
|
|
194
|
+
**When it refuses over unpushed commits, relay both ways out.**
|
|
195
|
+
A cancelled spec is normally unlanded, so this is the one moment in the
|
|
196
|
+
lifecycle where that guard genuinely fires — and it fires about real loss.
|
|
197
|
+
The worktree is the only copy of this work: the branch is on no remote and
|
|
198
|
+
not merged into the base branch, so removing it ends it. Say that plainly,
|
|
199
|
+
relay the engine's reason, and give both endings:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
publish it first — keeps the work reachable, then re-run /spec-cancel:
|
|
203
|
+
git -C <worktreePath> push -u origin <branch>
|
|
204
|
+
|
|
205
|
+
or accept the loss (the worktree and its commits go):
|
|
206
|
+
skitterspec spec-env down <name> --force
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Print the push; never run it.** Publishing abandoned work to a shared
|
|
210
|
+
remote is the same unasked-for act this workflow took out of `/spec-start`,
|
|
211
|
+
and it is no more wanted here — someone may well want this branch to exist
|
|
212
|
+
nowhere but their own machine. Offer the command and wait for an answer.
|
|
213
|
+
|
|
214
|
+
**Never reach for `--force` yourself either.** The engine's own message names
|
|
215
|
+
only that half, which is the whole reason this step exists: meeting a wall
|
|
216
|
+
labelled *--force to tear down anyway* at the exact moment a backup is still
|
|
217
|
+
cheap is how work gets thrown away. Both options, then stop — the choice is
|
|
218
|
+
a decision about someone's work, and it is theirs.
|
|
219
|
+
|
|
129
220
|
If the plan prints a `remote branch — confirm with the user first:` section,
|
|
130
221
|
**ask before running that line** — it is a `git push <remote> --delete`, and
|
|
131
222
|
the branch is merged, so it loses nothing. Usually there is nothing to ask:
|