@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.
Files changed (60) hide show
  1. package/MIGRATION.md +260 -10
  2. package/README.md +32 -2
  3. package/assets/claude-md-section.md +48 -2
  4. package/assets/commands/spec-connect.md +2 -2
  5. package/assets/commands/spec-live.md +2 -2
  6. package/assets/core/SETUP.md +21 -3
  7. package/assets/core/env.config.json.example +9 -3
  8. package/assets/core/env.config.md +102 -30
  9. package/assets/core/gating.config.json.example +4 -0
  10. package/assets/core/gating.config.md +81 -0
  11. package/assets/core/linear.config.md +67 -8
  12. package/assets/review/page.html +1501 -0
  13. package/assets/rules/spec-planning.md +224 -15
  14. package/assets/rules/spec-reports.md +269 -0
  15. package/assets/skills/spec/SKILL.md +64 -13
  16. package/assets/skills/spec-bug/SKILL.md +193 -27
  17. package/assets/skills/spec-cancel/SKILL.md +99 -8
  18. package/assets/skills/spec-claim/SKILL.md +114 -0
  19. package/assets/skills/spec-complete/SKILL.md +123 -22
  20. package/assets/skills/spec-diff/SKILL.md +564 -0
  21. package/assets/skills/spec-hotfix/SKILL.md +202 -22
  22. package/assets/skills/spec-init/SKILL.md +49 -9
  23. package/assets/skills/spec-linear-setup/SKILL.md +86 -7
  24. package/assets/skills/spec-list/SKILL.md +218 -0
  25. package/assets/skills/spec-next/SKILL.md +300 -7
  26. package/assets/skills/spec-push/SKILL.md +45 -22
  27. package/assets/skills/spec-review/SKILL.md +59 -11
  28. package/assets/skills/spec-reviewed/SKILL.md +241 -0
  29. package/assets/skills/spec-start/SKILL.md +426 -66
  30. package/assets/skills/spec-status/SKILL.md +24 -2
  31. package/assets/skills/spec-sync/SKILL.md +47 -11
  32. package/assets/skills/spec-to-main/SKILL.md +42 -20
  33. package/package.json +11 -7
  34. package/src/cli.js +1710 -80
  35. package/src/env/building.js +143 -0
  36. package/src/env/classify.js +91 -0
  37. package/src/env/config.js +57 -9
  38. package/src/env/provision.js +192 -19
  39. package/src/env/proxy.js +34 -1
  40. package/src/env/render.js +3 -12
  41. package/src/env/resolve.js +296 -9
  42. package/src/env/review.js +1329 -0
  43. package/src/env/serve.js +549 -0
  44. package/src/env/teardown.js +13 -6
  45. package/src/gating.js +155 -0
  46. package/src/init.js +124 -2
  47. package/src/prompts.js +10 -1
  48. package/src/vendor/linear/api.js +104 -1
  49. package/src/vendor/linear/cli-sync.js +874 -17
  50. package/src/vendor/linear/config.js +8 -0
  51. package/src/vendor/linear/credentials.js +94 -0
  52. package/src/vendor/linear/doctor.js +35 -0
  53. package/src/vendor/linear/identity.js +105 -0
  54. package/src/vendor/linear/mcp.js +26 -0
  55. package/src/vendor/sync-core/index.js +6 -2
  56. package/src/vendor/sync-core/src/compare.js +74 -5
  57. package/src/vendor/sync-core/src/normalize.js +30 -0
  58. package/src/vendor/sync-core/src/push.js +11 -1
  59. package/src/vendor/sync-core/src/write.js +38 -0
  60. 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,11 +156,13 @@ 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, the opener, and any `in the worktree, run:`
155
- bootstrap steps.
156
- - Run the printed `git worktree add`. **The worktree is checked out at the tag,
157
- so your uncommitted stub doesn't travel with it** move it across so `main`
158
- stays pristine. **Create the destination bucket first:**
159
+ `main`), the worktree path, and any `in the worktree, run:` bootstrap steps.
160
+ - Run the printed `git worktree add`, then **move the stub across yourself.**
161
+ This is where a hotfix differs from `/spec-bug`, which no longer needs the move:
162
+ that skill's worktree forks from `main`, so committing the stub puts it there,
163
+ while **this worktree is checked out at the tag** a commit on `main` is not in
164
+ it and never will be. The move is not redundant here; keep it.
165
+ **Create the destination bucket first:**
159
166
 
160
167
  ```
161
168
  mkdir -p <worktreePath>/specs/in-progress
@@ -164,14 +171,13 @@ that header — must exist **before** `spec-env up`:
164
171
 
165
172
  The `mkdir -p` is not belt-and-braces. Git does not store empty directories, so
166
173
  `specs/in-progress/` is **absent** from the worktree whenever nothing was in
167
- progress at that point in history — and here that point is an **old release
168
- tag**, where it is absent more often than not. `mv` into a missing destination
169
- renames your spec folder **to** `specs/in-progress`, silently: the spec's files
170
- end up one level too high, `00-overview.md` sits where the bucket should be, and
171
- every later step still appears to work until something cannot find the spec.
172
- Confirm the result before carrying on — you want
173
- `<worktreePath>/specs/in-progress/hotfix-<name>/00-overview.md`.
174
- - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
174
+ progress at that point in history — and here that point is an
175
+ **old release tag**, where it is absent more often than not. `mv` into a
176
+ missing destination renames your spec folder **to** `specs/in-progress`,
177
+ silently: the spec's files end up one level too high, `00-overview.md` sits
178
+ where the bucket should be, and every later step still appears to work until
179
+ something cannot find the spec. Confirm the result before carrying on — you
180
+ want `<worktreePath>/specs/in-progress/hotfix-<name>/00-overview.md`. - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
175
181
  none of the repo's gitignored files (`.env`, local overrides). Run the printed
176
182
  `in the worktree, run:` steps (file seeding, then `setup`) in order, before
177
183
  anything else.
@@ -213,6 +219,8 @@ is usually a single-pass fix, so the `## Fix` block can live directly in
213
219
  > **Base version:** <tag prod is running, e.g. v33.16.4>
214
220
  > **Raised:** <YYYY-MM-DD (today)>
215
221
  > **Area:** <files/modules>
222
+ > **Gating:** <pre-filled "none: hotfix — restoring released behaviour"; only
223
+ > when release gating is configured, and overridable — see below>
216
224
 
217
225
  ## Symptom
218
226
 
@@ -292,7 +300,7 @@ does — you never create the issue by hand:
292
300
  2. **Pick the Project** — run the picker in **Picking the Linear Project** below.
293
301
  Keep the chosen id for step 4.
294
302
  3. **Get the plan.**
295
- `skitterspec spec-sync push <spec> --workspace-states <file> --json > plan.json`
303
+ `skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json`
296
304
  — the spec is unlinked, so this plan is all-creates: the issue and one
297
305
  sub-issue per phase.
298
306
  4. **Apply it.**
@@ -325,6 +333,66 @@ and **never auto-push git** — Linear's own automation reacts to real branch/PR
325
333
  events later. Report the Linear issue URL as part of the skill's finish-up
326
334
  message.
327
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
+
328
396
  ### Picking the Linear Project
329
397
 
330
398
  Run this **only when minting a spec issue** — creating it for the first time. On
@@ -357,6 +425,21 @@ say so in one line — *"project picker unavailable"* — and carry on with
357
425
  If `linear.projectId` is set but that Project is archived or missing, relay
358
426
  Linear's error and stop rather than silently minting an unparented issue.
359
427
 
428
+ ### Release gating (only when configured)
429
+
430
+ **Only when `specs/.core/gating.config.json` exists.** A hotfix is the one spec
431
+ type that does **not** ask the question cold: it writes
432
+ `none: hotfix — restoring released behaviour` and asks only for confirmation.
433
+
434
+ The default differs on purpose. A hotfix restores behaviour a release already
435
+ had, under time pressure, and the fix is captured by a deploy tag rather than
436
+ riding the next release — so a flag has nothing to gate and nothing to roll back
437
+ to. Making someone answer a design question mid-incident buys nothing.
438
+
439
+ It is a **default, not a rule**: say what you are writing and let the user
440
+ override it. If they name a flag, record that instead. Skip entirely when the
441
+ config is absent.
442
+
360
443
  ## 6. Drive to GREEN
361
444
 
362
445
  - Implement the **minimal, root-cause** fix on the branch. Match surrounding code;
@@ -390,18 +473,115 @@ sub-issue jump from Backlog straight to Done, with nothing visible in between.
390
473
  - **Say what happened** in the skill's report: mirror updated, skipped as
391
474
  unlinked, or failed with the reason.
392
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
+
393
555
  ## 7. Report
394
556
 
395
- Summarise: the base tag, root cause, the failing→passing test, the fix, and the
396
- full test result. The spec stays in `in-progress`.
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`.
397
559
 
398
560
  - **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
399
561
  hot-reloading it onto the running dev server could break the shared instance.
400
562
  To test it, the user runs `/spec-connect` (its own isolated stack).
401
- - Suggest **`/spec-complete`** to land it: it patch-bumps the base tag, tags the
402
- hotfix branch **locally** (you push it to deploy), and cherry-picks the fix onto
403
- `main`. Add `--also <tag>` at completion to also patch other release lines
404
- (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).
405
567
 
406
- Do **not** `git push` or `git tag`-and-push unless the user asks — deploying to
407
- prod is theirs to trigger.
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 **ten skills**: `spec` (feature), `spec-bug` (bug),
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-to-main`, `spec-complete`, `spec-cancel`, and this
28
- `spec-init`. The lifecycle is `backlog in-progress complete / cancelled`,
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)
@@ -37,6 +43,19 @@ checkout you are already in; one spec at a time, no hand-off). It is a question
37
43
  about how the operator works, not about what the repo contains, so ask rather
38
44
  than infer it from whether dev servers or Docker are configured.
39
45
 
46
+ ## 0b. Release gating (optional, and separate)
47
+
48
+ Ask whether specs should record a **release-gating** decision — does this change
49
+ ship behind a feature flag, or land live? Adopting it copies
50
+ `specs/.core/gating.config.json.example` → `gating.config.json` and sets
51
+ `guidance` to wherever this project documents its flags.
52
+
53
+ It is **orthogonal to isolation**: a project can adopt either, both, or neither.
54
+ Skitterspec never learns how the flags work — it asks the question, cites that
55
+ path, and records the answer on each spec. Leave it off and nothing appears:
56
+ no question, no header, no check. Off is a perfectly good answer for a project
57
+ that does not use flags.
58
+
40
59
  ## 1. Folders
41
60
 
42
61
  Create any that are missing; drop a `.gitkeep` into ones that would otherwise be
@@ -80,8 +99,8 @@ Ensure it exists. If missing, create it documenting:
80
99
  - the **Author** / **Developer** header fields;
81
100
  - the **State log** audit table (folder/status transitions), kept separate from
82
101
  the **Changelog** (decisions);
83
- - the project's concrete typecheck/test commands, and the rule that **every
84
- phase ends with creating + running tests**.
102
+ - the project's concrete typecheck/test commands, and the rule that
103
+ **every phase ends with creating + running tests**.
85
104
 
86
105
  Read a sibling spec skill (e.g. `spec`, `spec-next`) for the canonical shapes
87
106
  rather than inventing them. If the rule already exists, leave it unless stale.
@@ -96,7 +115,28 @@ the section exists, refresh only stale folder/skill names — don't rewrite it.
96
115
 
97
116
  ## 6. Report
98
117
 
99
- Summarise per area folders, `.gitignore` lines, tooling-ignore negations,
100
- skills (present/missing), rule file, CLAUDE.md section — as created / updated /
101
- already-present, plus the `git check-ignore` result for `.core`. Do **not**
102
- `git commit` unless the user asks.
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
 
@@ -198,6 +229,31 @@ project's own, so ask rather than guess.
198
229
  the workspace — that report is the evidence the setup is right, so don't
199
230
  paraphrase it into "done".
200
231
 
232
+ ## 8b. Let `/spec-start` commit the snapshot with its spec
233
+
234
+ **Only when `specs/.core/env.config.json` exists** (per-spec isolation is on). If
235
+ it is absent, skip this — there is nothing to configure.
236
+
237
+ `/spec-start` commits an uncommitted spec for you, but only the paths it can
238
+ prove belong to that spec. Its own folder always does; this repo's per-spec
239
+ Linear snapshot (`sync.baseDir`, `specs/.core/linear-base/<ID>.base.json`) does
240
+ not, because the base engine is deliberately tracker-free and cannot know that
241
+ path exists. Declare it once, in `env.config.json`:
242
+
243
+ ```json
244
+ "spec": {
245
+ "companionPaths": ["specs/.core/linear-base/{identifier}.base.json"]
246
+ }
247
+ ```
248
+
249
+ `{identifier}` resolves through `branch.identifierField`, so **set that too** if
250
+ it is still empty — `"linear_identifier"` is the field `/spec` stamps. Without
251
+ it the pattern expands to nothing and the snapshot is treated as another spec's
252
+ file: `/spec-start` then refuses the tree instead of committing it, which is the
253
+ safe failure but a needless one.
254
+
255
+ Leave both alone if the user has already set them.
256
+
201
257
  ## 9. Report and hand off
202
258
 
203
259
  **Finish by checking, not by describing.** First write down what the MCP server
@@ -254,13 +310,36 @@ more detail if you need it. If the key is missing, tell the user to run this
254
310
  skitterspec spec-sync credentials set
255
311
  ```
256
312
 
257
- **Do not ask the user to paste an API key into this conversation, and do not
258
- offer to store one for them.** A key pasted into a chat enters the transcript,
259
- is sent to the model, and may be logged — moving where a key is stored is
260
- pointless if it travels through the conversation to get there. Your job is to
261
- confirm the setup is ready and name the command; the value is theirs alone. The
262
- key is optional: without one, pushes go over MCP.
313
+ **Do not ask the user to paste an API key into this conversation, and do not offer to store one for them.**
314
+ A key pasted into a chat enters the transcript, is sent to the model, and may
315
+ be logged — moving where a key is stored is pointless if it travels through the
316
+ conversation to get there. Your job is to confirm the setup is ready and name
317
+ the command; the value is theirs alone. The key is optional: without one,
318
+ pushes go over MCP.
263
319
 
264
320
  Mention what setup did **not** configure, so the defaults aren't mistaken for
265
321
  decisions: phase mapping (`mapping.phases` — sub-issue per phase by default) and
266
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.