@skitterbyte/skitterspec-linear 12.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 +208 -10
- package/README.md +32 -2
- package/assets/claude-md-section.md +38 -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 +6 -3
- package/assets/core/env.config.md +77 -30
- package/assets/core/linear.config.md +58 -0
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +198 -10
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +33 -5
- package/assets/skills/spec-bug/SKILL.md +172 -9
- package/assets/skills/spec-cancel/SKILL.md +98 -21
- package/assets/skills/spec-claim/SKILL.md +114 -0
- package/assets/skills/spec-complete/SKILL.md +94 -25
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +172 -11
- package/assets/skills/spec-init/SKILL.md +34 -7
- package/assets/skills/spec-linear-setup/SKILL.md +55 -1
- package/assets/skills/spec-list/SKILL.md +218 -0
- package/assets/skills/spec-next/SKILL.md +299 -6
- package/assets/skills/spec-push/SKILL.md +32 -8
- package/assets/skills/spec-review/SKILL.md +40 -5
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +386 -106
- package/assets/skills/spec-status/SKILL.md +24 -2
- package/assets/skills/spec-sync/SKILL.md +40 -4
- package/assets/skills/spec-to-main/SKILL.md +28 -6
- package/package.json +11 -7
- package/src/cli.js +1513 -89
- package/src/env/building.js +143 -0
- package/src/env/config.js +42 -9
- package/src/env/provision.js +54 -15
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +295 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/init.js +96 -1
- package/src/vendor/linear/api.js +104 -1
- package/src/vendor/linear/cli-sync.js +854 -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 +49 -3
- 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: Fix a production bug on the exact released version — fork a workt
|
|
|
5
5
|
|
|
6
6
|
# /spec-hotfix — fix a released version, tag it, cherry-pick back to main
|
|
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 **hotfix** counterpart to `/spec-bug`. Same test-first discipline
|
|
9
14
|
(reproduce as a **failing test (RED)**, then drive to **GREEN**), but the base is
|
|
10
15
|
a **release tag**, not `main`: prod is running a tagged version, so the fix must
|
|
@@ -151,8 +156,7 @@ that header — must exist **before** `spec-env up`:
|
|
|
151
156
|
`## Symptom` you established. It starts in `in-progress` — work begins now.
|
|
152
157
|
- Run `skitterspec spec-env up hotfix-<name>`. It prints a `git worktree add …
|
|
153
158
|
-b hotfix/<slug> <tag>` command (the branch forks from **the tag**, not
|
|
154
|
-
`main`), the worktree path,
|
|
155
|
-
bootstrap steps.
|
|
159
|
+
`main`), the worktree path, and any `in the worktree, run:` bootstrap steps.
|
|
156
160
|
- Run the printed `git worktree add`, then **move the stub across yourself.**
|
|
157
161
|
This is where a hotfix differs from `/spec-bug`, which no longer needs the move:
|
|
158
162
|
that skill's worktree forks from `main`, so committing the stub puts it there,
|
|
@@ -296,7 +300,7 @@ does — you never create the issue by hand:
|
|
|
296
300
|
2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
|
|
297
301
|
Keep the chosen id for step 4.
|
|
298
302
|
3. **Get the plan.**
|
|
299
|
-
`skitterspec spec-sync
|
|
303
|
+
`skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
|
|
300
304
|
— the spec is unlinked, so this plan is all-creates: the issue and one
|
|
301
305
|
sub-issue per phase.
|
|
302
306
|
4. **Apply it.**
|
|
@@ -329,6 +333,66 @@ and **never auto-push git** — Linear's own automation reacts to real branch/PR
|
|
|
329
333
|
events later. Report the Linear issue URL as part of the skill's finish-up
|
|
330
334
|
message.
|
|
331
335
|
|
|
336
|
+
**Only when all three hold**: `specs/.core/linear.config.json` exists, its
|
|
337
|
+
`sync.fieldOwnership` includes `assignee`, and the spec carries a
|
|
338
|
+
`linear_identifier`. Any one missing → skip this step silently and carry on; a
|
|
339
|
+
project that has not opted in must see no trace of assignment.
|
|
340
|
+
|
|
341
|
+
**Never blocks, never fails the skill.** Everything below is best-effort: the
|
|
342
|
+
branch is provisioned and the spec is moving either way, and an unassigned issue
|
|
343
|
+
is a cosmetic gap that `/spec-claim` closes later.
|
|
344
|
+
|
|
345
|
+
1. **Work out who you are.** Run `skitterspec spec-sync whoami --json`.
|
|
346
|
+
- `ok: true` → use `id` and `name`. Nothing to ask.
|
|
347
|
+
- `source: "mcp"` or the command reports no API key → call the discovered
|
|
348
|
+
user-read tool with `me`, then cache it:
|
|
349
|
+
`skitterspec spec-sync whoami --set <id> --name "<name>"`.
|
|
350
|
+
- `ok: false` → identity is **unknown**, which is an ordinary state (a shared
|
|
351
|
+
or bot key, an offline machine). Go to step 2.
|
|
352
|
+
2. **Unknown identity — three states, not two.** Decide by what is actually
|
|
353
|
+
reachable, and route the third to inaction:
|
|
354
|
+
- **Linear reachable *and* this is an interactive session** → offer a short
|
|
355
|
+
user search (`skitterspec spec-sync users <name-or-email>`, or the
|
|
356
|
+
user-list tool on MCP), let the operator pick, and offer to cache it with
|
|
357
|
+
`whoami --set` so this is asked once per machine rather than once per spec.
|
|
358
|
+
- **Not reachable, or not interactive** → say so in one line
|
|
359
|
+
(`assignment skipped — no Linear identity`) and **carry on**. Do not prompt
|
|
360
|
+
for something you could not act on, and never stall a `/spec-start` on it.
|
|
361
|
+
3. **Record it on the spec** — through the engine, never by hand-editing
|
|
362
|
+
frontmatter:
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
skitterspec spec-sync assign <spec> --to <user-id> --name "<display name>"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
4. **Leave `> **Developer:**` as `git config user.name`** — the step above this
|
|
369
|
+
seam already set it, and this seam must not overwrite it with the tracker's
|
|
370
|
+
display name.
|
|
371
|
+
|
|
372
|
+
Those two names are the same person and often not the same string, and the
|
|
373
|
+
git one is the one everything else in the spec already uses: `Author:`, every
|
|
374
|
+
**State log** `By` row, and every commit. Writing the tracker's name into this
|
|
375
|
+
one field would leave a spec whose own audit trail contradicts its header —
|
|
376
|
+
a worse problem than the one it would solve. Anyone wanting a different name
|
|
377
|
+
on their specs sets `git config user.name`, and it stays consistent
|
|
378
|
+
everywhere.
|
|
379
|
+
|
|
380
|
+
The header names **who is building this**; the stamp in step 3 names
|
|
381
|
+
**which tracker account it is assigned to**. They answer different questions,
|
|
382
|
+
so they need not be the same string. The one place that reasoning does not hold
|
|
383
|
+
is handing a spec to *someone else* — there is no local git name for them —
|
|
384
|
+
and that case belongs to `/spec-claim --to`, which sets the header from the
|
|
385
|
+
tracker deliberately.
|
|
386
|
+
|
|
387
|
+
**Nothing is pushed here.** `assign` writes the repo only, and the refresh these
|
|
388
|
+
skills already run sends it. Assignment is an ordinary field of the projection,
|
|
389
|
+
not a side errand with its own network call.
|
|
390
|
+
|
|
391
|
+
**There is no unassign step anywhere.** The projection derives the assignee from
|
|
392
|
+
the spec's lifecycle bucket, so `/spec-complete` and `/spec-cancel` release the
|
|
393
|
+
issue through the push they already make. The stamp deliberately stays in the
|
|
394
|
+
file: who actioned the work outlives who is currently holding it.
|
|
395
|
+
|
|
332
396
|
### Picking the Linear Project
|
|
333
397
|
|
|
334
398
|
Run this **only when minting a spec issue** — creating it for the first time. On
|
|
@@ -409,18 +473,115 @@ sub-issue jump from Backlog straight to Done, with nothing visible in between.
|
|
|
409
473
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
410
474
|
unlinked, or failed with the reason.
|
|
411
475
|
|
|
476
|
+
## 6b. Render the page — then offer the review, never write it
|
|
477
|
+
|
|
478
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
479
|
+
present). Without it there is no worktree to read and this step does not exist —
|
|
480
|
+
skip it in silence rather than explaining an absence.
|
|
481
|
+
|
|
482
|
+
The fix is green and nothing is committed yet. That is the moment the page is
|
|
483
|
+
about, so render it now — **after** the tests pass and **before** the commit:
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
skitterspec spec-env review <spec>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**This is free.** The engine reads git and splices the patches into a template;
|
|
490
|
+
the diff never passes through you, so a 266KB patch costs nothing.
|
|
491
|
+
|
|
492
|
+
**Then offer `/spec-diff`. Do not run it.** The written review is the part that
|
|
493
|
+
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
494
|
+
— and that spend is the operator's call, not a default.
|
|
495
|
+
|
|
496
|
+
**The offer is the `Review` row of step 6's block** — the counts, the page link
|
|
497
|
+
and a question, in one row:
|
|
498
|
+
|
|
499
|
+
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
|
|
500
|
+
|
|
501
|
+
**It ends in a question, addressed to someone.** It was once a fenced block of
|
|
502
|
+
engine output, and it fired on every phase and was never once taken: two quoted
|
|
503
|
+
lines under the test counts, addressed to nobody, with the report then closing
|
|
504
|
+
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
505
|
+
so they did. A row in a labelled table is findable; a question in it is
|
|
506
|
+
answerable. Both halves are load-bearing.
|
|
507
|
+
|
|
508
|
+
**Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
|
|
509
|
+
the page and the question stay in the same row: two adjacent rows about one page
|
|
510
|
+
make the reader resolve a distinction before acting on either. A later edit that
|
|
511
|
+
moves it out of the block, or separates the link from the question, undoes this
|
|
512
|
+
and should be read as a regression rather than tidying.
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
Relay the **`open:`** line rather than the bare path: a path is not clickable in
|
|
516
|
+
any terminal, and a page nobody can open is a page nobody reads.
|
|
517
|
+
|
|
518
|
+
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
519
|
+
something behind that this tooling cannot remove, so it is always an ask. A
|
|
520
|
+
`file://` link is no use on a phone, and saying so **is** the ask —
|
|
521
|
+
`/spec-diff` §6 owns how.
|
|
522
|
+
**Follow the `reader:` line the engine printed — do not sniff for it.** It
|
|
523
|
+
answers where the person reading this is sitting, and the offer changes with it:
|
|
524
|
+
|
|
525
|
+
- **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
|
|
526
|
+
unknown is the ordinary state of a local machine, and a warning there is an
|
|
527
|
+
accusation against a healthy session.
|
|
528
|
+
- **`local`** — the `file://` URL.
|
|
529
|
+
- **`remote`** — the engine has already stood its local server up and put a URL
|
|
530
|
+
the reader can open on `open:`. So there is **nothing special to say**: relay
|
|
531
|
+
that line like any other. Any `also:` lines under it are the other addresses
|
|
532
|
+
this machine has, offered because the best-guess one can be wrong — pass them
|
|
533
|
+
on rather than editing them out.
|
|
534
|
+
|
|
535
|
+
**Never read an environment variable to decide this** — not `SSH_CONNECTION`,
|
|
536
|
+
not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
|
|
537
|
+
and in `--json`, and a second implementation here could not be tested and would
|
|
538
|
+
drift.
|
|
539
|
+
|
|
540
|
+
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
541
|
+
authorises a local server — one process, ended by one flag, leaving nothing
|
|
542
|
+
behind — and authorises nothing else. Publishing leaves a page this tooling
|
|
543
|
+
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
544
|
+
serve (a busy port, a machine with no network address) it falls back to the
|
|
545
|
+
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
546
|
+
|
|
547
|
+
- **Never fatal.** A failed render — no worktree, a git error — is one line, and
|
|
548
|
+
the fix is still done. The page is a convenience; the repo is the record.
|
|
549
|
+
|
|
550
|
+
**The range is measured from the base tag, not from `main`.** A hotfix forks its
|
|
551
|
+
worktree from its `> **Base version:**` tag, and the engine reads that header, so
|
|
552
|
+
the header line says `since <tag>` — do not relay it as `since main`, and do not
|
|
553
|
+
reach for a branch view expecting the base branch.
|
|
554
|
+
|
|
412
555
|
## 7. Report
|
|
413
556
|
|
|
414
|
-
|
|
415
|
-
|
|
557
|
+
Do **not** `git push` or `git tag`-and-push unless the user asks — deploying to
|
|
558
|
+
prod is theirs to trigger. The spec stays in `in-progress`.
|
|
416
559
|
|
|
417
560
|
- **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
|
|
418
561
|
hot-reloading it onto the running dev server could break the shared instance.
|
|
419
562
|
To test it, the user runs `/spec-connect` (its own isolated stack).
|
|
420
|
-
-
|
|
421
|
-
hotfix branch **locally** (you push it to deploy), and cherry-picks the
|
|
422
|
-
`main`. Add `--also <tag>` at completion to also patch other release
|
|
423
|
-
(test/demo on their own versions).
|
|
563
|
+
- `Next` points at **`/spec-complete`** to land it: it patch-bumps the base tag,
|
|
564
|
+
tags the hotfix branch **locally** (you push it to deploy), and cherry-picks the
|
|
565
|
+
fix onto `main`. Add `--also <tag>` at completion to also patch other release
|
|
566
|
+
lines (test/demo on their own versions).
|
|
424
567
|
|
|
425
|
-
|
|
426
|
-
|
|
568
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
569
|
+
the shape; this section carries only what is specific here.
|
|
570
|
+
|
|
571
|
+
**Verdicts**
|
|
572
|
+
|
|
573
|
+
- `✅` — red→green on the base tag, fix in, suite passing.
|
|
574
|
+
- `⚠️` — green, with something worth knowing.
|
|
575
|
+
- `❌` — still red, or a later test broke. Quote the failure.
|
|
576
|
+
- `⏸` — no reproduction on that tag, or the tag could not be established.
|
|
577
|
+
|
|
578
|
+
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
579
|
+
`Review` · `Follow-ups` · `Next`
|
|
580
|
+
|
|
581
|
+
**The base tag goes in the verdict clause** — `✅ /spec-hotfix · hotfix-foo ·
|
|
582
|
+
green on v2.3.1`. Which released version this was fixed against is the first
|
|
583
|
+
thing anyone needs, and it is not a field: the clause is where the run says
|
|
584
|
+
where it got to.
|
|
585
|
+
|
|
586
|
+
Step 6b's offer is the `Review` row, not a paragraph after the block — the
|
|
587
|
+
counts, the link and the question in one row. Nothing follows the block.
|
|
@@ -5,6 +5,11 @@ description: Bootstrap the spec-driven-development workflow in the current proje
|
|
|
5
5
|
|
|
6
6
|
# /spec-init — set up the spec-driven-development workflow
|
|
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
|
Bootstrap (or repair) everything this project needs to run the spec lifecycle.
|
|
9
14
|
**Idempotent:** detect what already exists, create only what's missing, and never
|
|
10
15
|
clobber customised content. Finish with a summary of created vs already-present.
|
|
@@ -22,10 +27,11 @@ clobber customised content. Finish with a summary of created vs already-present.
|
|
|
22
27
|
> **Leave alone**. Non-interactively it only adds what's missing; `--resync` /
|
|
23
28
|
> `--reset` (reset needs `--yes`) drive the stronger actions.
|
|
24
29
|
|
|
25
|
-
The system is **
|
|
30
|
+
The system is **twelve skills**: `spec` (feature), `spec-bug` (bug),
|
|
26
31
|
`spec-hotfix` (a bug fixed on a released tag), `spec-review`, `spec-start`,
|
|
27
|
-
`spec-next`, `spec-
|
|
28
|
-
`spec-
|
|
32
|
+
`spec-next`, `spec-diff` (read a spec's diff as a page),
|
|
33
|
+
`spec-reviewed` (pick up a review you approved on that page), `spec-to-main`,
|
|
34
|
+
`spec-complete`, `spec-cancel`, and this `spec-init`. The lifecycle is `backlog → in-progress → complete / cancelled`,
|
|
29
35
|
with `.core` holding always-apply project rules.
|
|
30
36
|
|
|
31
37
|
## 0. Workspace mode (only when adopting isolation)
|
|
@@ -109,7 +115,28 @@ the section exists, refresh only stale folder/skill names — don't rewrite it.
|
|
|
109
115
|
|
|
110
116
|
## 6. Report
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
Do **not** `git commit` unless the user asks.
|
|
119
|
+
|
|
120
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
121
|
+
the shape; this section carries only what is specific here.
|
|
122
|
+
|
|
123
|
+
**Verdicts**
|
|
124
|
+
|
|
125
|
+
- `✅` — every area is in place.
|
|
126
|
+
- `⚠️` — bootstrapped, with something worth knowing: a skill that could not be
|
|
127
|
+
installed, a CLAUDE.md that was left alone, a `.core` that is still ignored.
|
|
128
|
+
- `❌` — it wrote some areas and failed on another. Name which, so the re-run is
|
|
129
|
+
informed; this skill is idempotent and re-running it is the fix.
|
|
130
|
+
- `⏸` — not a git repo, or nothing it could safely write into.
|
|
131
|
+
|
|
132
|
+
**Fields:** `Built` · `Follow-ups` · `Next`
|
|
133
|
+
|
|
134
|
+
**`Built` is per area, one line each** — folders, `.gitignore` lines,
|
|
135
|
+
tooling-ignore negations, skills (present/missing), rule files, CLAUDE.md
|
|
136
|
+
section — as created / updated / already-present, plus the `git check-ignore`
|
|
137
|
+
result for `.core`. This is the one skill whose `Built` is a list rather than a
|
|
138
|
+
clause, because "what is now true of this project" is the entire answer it
|
|
139
|
+
exists to give.
|
|
140
|
+
|
|
141
|
+
**There is no spec to name**, so the verdict line drops that segment:
|
|
142
|
+
`✅ /spec-init · every area in place`.
|
|
@@ -5,6 +5,11 @@ description: Configure Linear sync for this repo by interview rather than by han
|
|
|
5
5
|
|
|
6
6
|
# /spec-linear-setup — configure Linear sync by interview
|
|
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
|
Writes `specs/.core/linear.config.json`, the file that switches one-way Linear
|
|
9
14
|
sync on for this repo. You **discover and interview**; the engine
|
|
10
15
|
(`spec-sync init-config`) **validates and writes**. Keep that split: composing the
|
|
@@ -173,13 +178,39 @@ a state from the `list_issue_statuses` names you already have:
|
|
|
173
178
|
automation closes them and a problem otherwise — `spec-sync doctor` warns about
|
|
174
179
|
it either way, so say which it is.
|
|
175
180
|
|
|
181
|
+
## 7b. Should a spec's issue be assigned to whoever is building it? (optional)
|
|
182
|
+
|
|
183
|
+
Ask it plainly, in one line: with this on, `/spec-start` records the developer
|
|
184
|
+
and their Linear issue is assigned to them, released again when the spec
|
|
185
|
+
completes. Off, nothing about assignment happens at all.
|
|
186
|
+
|
|
187
|
+
**Offer once and take "no" for an answer.** It is off by default deliberately —
|
|
188
|
+
a team whose PM owns assignment in Linear does not want the repo writing that
|
|
189
|
+
field, and the cost of guessing wrong is somebody else's triage being
|
|
190
|
+
overwritten.
|
|
191
|
+
|
|
192
|
+
If they want it, pass **`--assign`** in step 8. Then settle who *they* are, so
|
|
193
|
+
`/spec-start` never has to stop and ask:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
skitterspec spec-sync whoami
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
It answers straight from the API key. If it reports `transport = mcp`, read `me`
|
|
200
|
+
with the user tool and cache the answer
|
|
201
|
+
(`spec-sync whoami --set <id> --name "<name>"`); if it reports unknown — a shared
|
|
202
|
+
or bot key — look the person up with `spec-sync users <name-or-email>` and cache
|
|
203
|
+
their id the same way. **Never write a user id into `linear.config.json`**: that
|
|
204
|
+
file is committed, so it would assign every teammate's specs to whoever ran
|
|
205
|
+
setup.
|
|
206
|
+
|
|
176
207
|
## 8. Write it
|
|
177
208
|
|
|
178
209
|
```
|
|
179
210
|
skitterspec spec-sync init-config \
|
|
180
211
|
--team-id <uuid> [--team-key KEY] [--project-id <uuid>] \
|
|
181
212
|
[--intake-label <name>] [--bug-labels a,b] [--hotfix-labels a,b] \
|
|
182
|
-
[--state <bucket>=<name> …] \
|
|
213
|
+
[--state <bucket>=<name> …] [--assign] \
|
|
183
214
|
--states <statesfile> [--force] [--json]
|
|
184
215
|
```
|
|
185
216
|
|
|
@@ -289,3 +320,26 @@ pushes go over MCP.
|
|
|
289
320
|
Mention what setup did **not** configure, so the defaults aren't mistaken for
|
|
290
321
|
decisions: phase mapping (`mapping.phases` — sub-issue per phase by default) and
|
|
291
322
|
field ownership. Point at `linear.config.md` for those.
|
|
323
|
+
|
|
324
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
325
|
+
the shape; this section carries only what is specific here.
|
|
326
|
+
|
|
327
|
+
**The `doctor` table goes above the block, verbatim.** It is a check rather than
|
|
328
|
+
a summary, and every row that needs attention names its own fix — paraphrasing
|
|
329
|
+
it into fields would lose exactly that.
|
|
330
|
+
|
|
331
|
+
**Verdicts**
|
|
332
|
+
|
|
333
|
+
- `✅` — every layer `doctor` checked is configured and working.
|
|
334
|
+
- `⚠️` — configured, with a `missing` row: an opt-in nobody took, which is fine.
|
|
335
|
+
Report it; do not treat it as a failure.
|
|
336
|
+
- `❌` — a `broken` row: configured but wrong. On a broken `mcp` row **stop** —
|
|
337
|
+
which side is correct is the user's to say.
|
|
338
|
+
- `⏸` — an existing config was reviewed and left alone, or the workspace could
|
|
339
|
+
not be discovered. Nothing was written.
|
|
340
|
+
|
|
341
|
+
**Fields:** `Built` · `Follow-ups` · `Next`
|
|
342
|
+
|
|
343
|
+
`Built` is the config written or reviewed, plus what was deliberately left
|
|
344
|
+
unset. **The key is never a field** — `doctor`'s `key` row already reports
|
|
345
|
+
whether one is set, masked, and the value is the user's alone.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-list
|
|
3
|
+
description: List every spec Linear holds — id, title, workflow state, who holds it, and the local spec folder name to paste into /spec-start. Read-only, and it starts nothing. Falls back to the repo's own listing when Linear is unreachable. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-list", "what specs are there", "what is in the backlog", "what is Jane working on", or "list specs from Linear".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /spec-list — every spec Linear holds
|
|
8
|
+
|
|
9
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
10
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
11
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
12
|
+
> skill ends with.
|
|
13
|
+
|
|
14
|
+
Read-only. It answers a question and writes nothing — no branch, no folder move,
|
|
15
|
+
no Linear write.
|
|
16
|
+
|
|
17
|
+
Why ask Linear rather than `ls specs/`? Because `/spec-start` moves a spec to
|
|
18
|
+
`specs/in-progress/` **on that spec's own branch**. On the base branch an
|
|
19
|
+
in-flight spec still reads `backlog`, and a teammate's unlanded spec is not on
|
|
20
|
+
disk at all. Linear knows what is actually in progress and who holds it; the repo
|
|
21
|
+
knows what each issue is **called on disk**. The listing joins the two.
|
|
22
|
+
|
|
23
|
+
**Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
|
|
24
|
+
tell the user how to enable Linear sync (`/spec-linear-setup`) and stop.
|
|
25
|
+
|
|
26
|
+
## 1. Turn the question into flags
|
|
27
|
+
|
|
28
|
+
The user asks in plain language; the engine takes flags. Map, then run:
|
|
29
|
+
|
|
30
|
+
| They ask | Flags |
|
|
31
|
+
|----------|-------|
|
|
32
|
+
| "what specs are there", "/spec-list" | *(none — the live default)* |
|
|
33
|
+
| "what's done", "everything" | `--all` |
|
|
34
|
+
| "what's cancelled", a named state | `--state "<name>"` (repeatable) |
|
|
35
|
+
| "what's next", "the next few in the backlog" | `--next N` |
|
|
36
|
+
| "what am I on", "assigned to me" | `--mine` |
|
|
37
|
+
| "what is Jane on", "Jane's specs" | `--by "Jane"` |
|
|
38
|
+
| "what's in flight", "what's being worked on" | `--in-progress` |
|
|
39
|
+
| "just the first few" | `--limit N` |
|
|
40
|
+
| "include the archived ones" | `--archived` |
|
|
41
|
+
|
|
42
|
+
The default scope is **live** — whatever `config.states` maps `backlog` and
|
|
43
|
+
`in-progress` to. It is deliberately not everything: in a workspace with any
|
|
44
|
+
history, Done dwarfs the rows anyone wanted.
|
|
45
|
+
|
|
46
|
+
`--mine` and `--by` filter by assignee and stack with any one scope flag —
|
|
47
|
+
`--next 5 --mine` and `--in-progress --by "Jane"` both read naturally. The scope
|
|
48
|
+
flags themselves (`--state`, `--all`, `--next`, `--in-progress`) are
|
|
49
|
+
**alternatives**, and the engine refuses any two of them rather than letting one
|
|
50
|
+
win silently.
|
|
51
|
+
|
|
52
|
+
`--next N` is **backlog-only and ordered**, so it does not combine with
|
|
53
|
+
`--state` or `--all`; the engine refuses that pair rather than picking a winner.
|
|
54
|
+
It reproduces Linear's own Backlog order — priority first, then the manual
|
|
55
|
+
drag-order — and when nothing is prioritised it says so, because that order is
|
|
56
|
+
then a person's arrangement rather than a ranking.
|
|
57
|
+
|
|
58
|
+
## 2. Run the engine
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
skitterspec spec-sync list [--state <name> …|--all] [--limit N] [--archived] [--json]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **`transport = api`** (a key is set) → it queried Linear and printed the
|
|
65
|
+
listing. **Relay its output verbatim** and stop. Do not re-format it and do not
|
|
66
|
+
make Linear calls of your own — the engine already joined the local spec
|
|
67
|
+
folders on.
|
|
68
|
+
- **`transport = mcp`** → the engine made no call. Go to step 3.
|
|
69
|
+
- Anything else it prints on one `spec-sync list:` line is a **degradation**, not
|
|
70
|
+
a crash — go to step 4.
|
|
71
|
+
|
|
72
|
+
**These two failures are not the same, and the engine says so.**
|
|
73
|
+
Relay the difference rather than flattening it. `--mine` when nobody can say
|
|
74
|
+
who you are (a shared or bot key, an offline machine) is an ordinary state: it
|
|
75
|
+
prints one line, lists nothing and exits 0. `--by "someone"` that matches no
|
|
76
|
+
user, or matches several, is a wrong argument: it lists the candidates or says
|
|
77
|
+
there are none, and exits non-zero. Neither ever falls back to the whole team —
|
|
78
|
+
a heading promising one person's work over everyone's is the failure both are
|
|
79
|
+
written to avoid.
|
|
80
|
+
|
|
81
|
+
## 3. The MCP path
|
|
82
|
+
|
|
83
|
+
Only when the engine said `transport = mcp`. Discover the issue **list** tool at
|
|
84
|
+
runtime the way `/spec-push` describes (`list_issues`). If Linear isn't connected
|
|
85
|
+
or the tool is missing, go to step 4 — do not stop with nothing.
|
|
86
|
+
|
|
87
|
+
Then, per state you are listing:
|
|
88
|
+
|
|
89
|
+
- **One call per state.** The tool's `state` takes a single state name, not a
|
|
90
|
+
list, so the live default is **two** calls (`backlog` and `in-progress` from
|
|
91
|
+
`config.states`). Merge the results and de-duplicate by identifier.
|
|
92
|
+
- **Ask for the fields you print** — `fields: ["title", "status", "assignee",
|
|
93
|
+
"url", "parentId"]`. `parentId` is not optional: it is the discriminator in the
|
|
94
|
+
next bullet, and it is absent from the default response.
|
|
95
|
+
- **Filter out phase sub-issues yourself.** The tool can filter *to* a parent
|
|
96
|
+
(`parentId`) but has no "parentless" filter, so **you** must drop every issue
|
|
97
|
+
that came back carrying a `parentId`. A spec issue has none; a phase sub-issue
|
|
98
|
+
does.
|
|
99
|
+
- **Page, and never cap silently.** `limit` defaults to 50 and maxes at 250;
|
|
100
|
+
follow `cursor` until it runs out. If you stop early, say so on its own line
|
|
101
|
+
and say what you stopped at.
|
|
102
|
+
- Pass `includeArchived` only when the user asked for `--archived`.
|
|
103
|
+
- **Assignee needs no identity lookup here.** The tool takes `assignee` as a
|
|
104
|
+
user id, name, email **or the literal `"me"`** — so `--mine` is
|
|
105
|
+
`assignee: "me"` and `--by "Jane"` is `assignee: "Jane"`. Do **not** call
|
|
106
|
+
`spec-sync whoami` on this path; it exists for the API path, which needs an
|
|
107
|
+
id. If the name matches nobody, say so and list nothing.
|
|
108
|
+
|
|
109
|
+
**`--next` cannot be fully reproduced over MCP, and you must say so.** The tool
|
|
110
|
+
returns `priority` as a field, so order by that; but `sortOrder` — Linear's
|
|
111
|
+
manual drag-order — is **not among the fields it can return** at all, and its
|
|
112
|
+
`orderBy` offers only `createdAt` and `updatedAt`. So within one priority you
|
|
113
|
+
are printing Linear's default order, not the Backlog order. Say that on its own
|
|
114
|
+
line rather than letting the rows imply an order they do not have:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
sortOrder is unavailable over MCP — within a priority these are in Linear's
|
|
118
|
+
default order, not the Backlog drag-order. Set a Linear API key for the real one.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**The current phase, on in-progress rows only.** For each row whose state is the
|
|
122
|
+
in-progress one, call the list tool again with `parentId` set to that issue and
|
|
123
|
+
`fields: ["title", "status"]`. Order the children by **identifier**, numerically
|
|
124
|
+
— `sortOrder` is unavailable here for the same reason it is under `--next`, and
|
|
125
|
+
sub-issues are minted in phase order, so the identifier carries it. Print the
|
|
126
|
+
live one as `2/5 — <title>`. Three shapes, none of them guessed at:
|
|
127
|
+
|
|
128
|
+
- **No child in progress** — print nothing extra. A spec sits between phases all
|
|
129
|
+
the time; that is not a missing phase.
|
|
130
|
+
- **More than one** — print the lowest-numbered, then append
|
|
131
|
+
`(+N more in progress)`. Two people on one spec is real, not an error.
|
|
132
|
+
- **`mapping.phases` is `inline` for this spec's bucket** — skip the call
|
|
133
|
+
entirely. Those phases live in the spec issue's own description, so there are
|
|
134
|
+
no children, and asking would report "no phase in progress" for a spec that is
|
|
135
|
+
mid-build.
|
|
136
|
+
|
|
137
|
+
Do **not** make this call for backlog, done or cancelled rows — one lookup per
|
|
138
|
+
in-progress row is the budget, and there are rarely many.
|
|
139
|
+
|
|
140
|
+
Then join locally — no second Linear call:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
skitterspec spec-sync linked --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
That is `[{ spec, bucket, identifier }]`. Key it by `identifier` and attach each
|
|
147
|
+
issue's local `spec` folder name. Format the rows exactly as the API path does
|
|
148
|
+
(step 5), including the archived-exclusion line and the `showing N of M` count.
|
|
149
|
+
|
|
150
|
+
## 4. Degrade — never leave them with nothing
|
|
151
|
+
|
|
152
|
+
Linear unreachable, no credential, no MCP tool, a Linear error: **do not stop**.
|
|
153
|
+
Print the local listing instead:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
skitterspec spec-sync linked --json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Show `spec`, `bucket` and `identifier`, under a one-line banner naming what is
|
|
160
|
+
missing and why it may be wrong:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Linear unreachable (<reason>) — this is the repo's own listing.
|
|
164
|
+
Buckets are this branch's; a spec someone else has started still reads "backlog" here.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
That caveat is the point of the banner. A local bucket is not evidence of a
|
|
168
|
+
spec's real state — it is evidence of what this branch knows, which is exactly
|
|
169
|
+
what the command exists to go beyond. A query command that fails closed is one
|
|
170
|
+
people stop typing.
|
|
171
|
+
|
|
172
|
+
## 5. Report
|
|
173
|
+
|
|
174
|
+
Relay the listing. Whatever the path, three things must survive into what the
|
|
175
|
+
user reads:
|
|
176
|
+
|
|
177
|
+
- **The count, as `showing N of M`.** If they are not the same number, say what
|
|
178
|
+
was left out and how to see it.
|
|
179
|
+
- **The archived line.** Excluded by default, and said so — a blind spot named
|
|
180
|
+
rather than left to be discovered.
|
|
181
|
+
- **The phase, on in-progress rows** — `2/5 — <title>`, after the assignee.
|
|
182
|
+
- **A row with no local match stays in**, marked `— (not linked here)`. It is not
|
|
183
|
+
noise: a teammate's unlanded spec, or one authored inside another spec's
|
|
184
|
+
worktree, is precisely what the repo could not have told them.
|
|
185
|
+
|
|
186
|
+
Finish with the hand-off, because every row carries the folder name for it —
|
|
187
|
+
as a sentence, not a fenced block: say that `/spec-start <name>` starts any of
|
|
188
|
+
them, with the name copied from a row.
|
|
189
|
+
|
|
190
|
+
**Never start it yourself.** `/spec-start` provisions a branch, moves a folder
|
|
191
|
+
and commits, and it has dirty-tree refusals this skill does not reproduce.
|
|
192
|
+
Offering the name is the whole job.
|
|
193
|
+
|
|
194
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
195
|
+
the shape; this section carries only what is specific here.
|
|
196
|
+
|
|
197
|
+
**The listing goes above the block, unflattened.** It is rows of other people's
|
|
198
|
+
work; the block is the verdict on this run of the query. Do not fold the listing
|
|
199
|
+
into fields, and do not repeat it there.
|
|
200
|
+
|
|
201
|
+
**Verdicts**
|
|
202
|
+
|
|
203
|
+
- `✅` — the listing is Linear's, joined to the local folder names.
|
|
204
|
+
- `⚠️` — listed, with a caveat that changes how the rows read: a paging cap you
|
|
205
|
+
stopped at, or `sortOrder` unavailable over MCP so `--next` is not really
|
|
206
|
+
Backlog order.
|
|
207
|
+
- `⏸` — `--mine` with no identity available. It lists nothing and that is an
|
|
208
|
+
ordinary state, not a failure.
|
|
209
|
+
- `❌` — the query was wrong: `--by` matching nobody or several, or two scope
|
|
210
|
+
flags together.
|
|
211
|
+
|
|
212
|
+
**Fields:** `Follow-ups` · `Next`
|
|
213
|
+
|
|
214
|
+
**No `Tracker` row.** This skill names many specs and owns none, so an id row
|
|
215
|
+
would have to pick one. The counts — `showing N of M` — and the archived
|
|
216
|
+
exclusion belong to the listing above, where the engine already prints them.
|
|
217
|
+
|
|
218
|
+
`Next` is the hand-off: `/spec-start <name>`, with a name copied from a row.
|