@skitterbyte/skitterspec-linear 8.0.5 → 9.1.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/README.md CHANGED
@@ -4,7 +4,7 @@ Spec-driven development for [Claude Code](https://claude.com/claude-code), **wit
4
4
  one-way Linear sync**. A strict **superset** of
5
5
  [`@skitterbyte/skitterspec`](https://www.npmjs.com/package/@skitterbyte/skitterspec):
6
6
  everything in the base filesystem workflow, plus one-way sync from a spec up to
7
- its linked Linear project — the repo is canonical, Linear is a generated mirror.
7
+ its linked Linear issue — the repo is canonical, Linear is a generated mirror.
8
8
 
9
9
  ```sh
10
10
  npx @skitterbyte/skitterspec-linear init
@@ -46,7 +46,7 @@ fuller guide):
46
46
  That file is the opt-in gate — until it exists, everything below is inert and
47
47
  the package behaves exactly like the base.
48
48
 
49
- 4. **Link and push** — `/spec` creates a linked Linear Project (a Milestone per
49
+ 4. **Link and push** — `/spec` creates a linked Linear issue (a sub-issue per
50
50
  phase) and stamps the id; then `/spec-push` publishes the spec up and
51
51
  `/spec-status` reports what would push. Sync is **one-way**: the repo is the
52
52
  source of truth and Linear is a generated mirror.
@@ -58,14 +58,17 @@ On top of the base skills (`/spec`, `/spec-go`, isolation, …):
58
58
  - **`/spec-status`** — read-only drift report: what the next push would create /
59
59
  update, plus any workflow-state drift. Changes nothing.
60
60
  - **`/spec-push`** — repo → Linear, one-way. Diffs the spec against a committed
61
- last-pushed snapshot and applies only what changed (project description +
62
- status, milestones, issues), stamping the returned ids back into the spec.
61
+ last-pushed snapshot and applies only what changed (issue description + state,
62
+ phase sub-issues), stamping the returned ids back into the spec.
63
63
  - **`spec-sync` CLI** (`skitterspec-linear spec-sync …`) — the deterministic
64
64
  engine behind the skills, for CI / local runs.
65
65
 
66
- The shared `/spec` and `/spec-go` skills come composed with the Linear steps
67
- filled in: `/spec` links a new spec to a Linear Project (a Milestone per phase).
68
- There is no pull the repo is already canonical, so `/spec-go` just builds.
66
+ The shared `/spec`, `/spec-bug` and `/spec-go` skills come composed with the
67
+ Linear steps filled in: `/spec` asks which Linear **Project** the spec belongs to,
68
+ then links it to a Linear issue (a sub-issue per phase). `/spec` and `/spec-bug`
69
+ can also start **from** an existing issue — `/spec SKI-123`, or `/spec
70
+ --from-issue` to browse the ones your web app filed. There is no pull — the repo
71
+ is already canonical, so `/spec-go` just builds.
69
72
 
70
73
  ## Opt-in
71
74
 
@@ -77,15 +80,30 @@ behaves exactly like the base.
77
80
  setup guide — connecting the `linear` MCP server, finding your team id, linking a
78
81
  spec, and a smoke test. Per-field docs live in `specs/.core/linear.config.md`.
79
82
 
80
- **What pushes:** the spec body travels as the Linear Project **`description`**,
81
- phases as **Milestones**, tasks as **Issues** (a short first-sentence title, the
82
- full task text as the description), and the lifecycle bucket sets the project's
83
- **workflow state**. Priority, labels, cycles and comments are **Linear-native
84
- triage** the PM's to set in Linear; one-way sync neither pushes nor reads them,
85
- so they're never clobbered. A workflow-state a teammate moves in Linear is
86
- surfaced by `/spec-status` as drift and overwritten on the next push. **Last-pushed
87
- snapshots** (`specs/.core/linear-base/`, content hashes) are committed so push
88
- sends only what changed.
83
+ **What pushes:** the spec is one Linear **issue** — the spec body travels as its
84
+ **`description`**, each phase as a **sub-issue** (phase name title, `**Goal:**`
85
+ description, phase emoji state), and the spec's lifecycle folder sets the
86
+ issue's **workflow state**. Tasks are **not** synced they stay in the repo phase
87
+ files. Priority, labels, cycles and comments are **Linear-native triage** the
88
+ PM's to set in Linear; one-way sync neither pushes nor reads them, so they're
89
+ never clobbered. A workflow-state a teammate moves in Linear is surfaced by
90
+ `/spec-status` as drift and overwritten on the next push. **Last-pushed snapshots**
91
+ (`specs/.core/linear-base/`, content hashes) are committed so push sends only what
92
+ changed.
93
+
94
+ **Which Project a spec lands in** is asked once, when the issue is first created
95
+ — a filterable list of your team's projects, defaulting to `linear.projectId` and
96
+ always offering *None*. It's passed on the create call only and never stored, so
97
+ re-homing a spec issue in Linear sticks: it won't read as drift and won't be moved
98
+ back on the next push.
99
+
100
+ **Starting from an issue** (`intake.label` / `intake.bugLabels` in the config):
101
+ `/spec SKI-123` adopts that issue, `/spec --from-issue [query]` browses the inbox.
102
+ The issue *becomes* the spec's issue — the reporter's thread, comments and links
103
+ stay put, their words are carried into the spec's **Problem**, and the first push
104
+ replaces the description with the spec. A bug-labelled issue routes to
105
+ `/spec-bug`, which adopts it the same way. `skitterspec-linear spec-sync linked`
106
+ lists what's already adopted, so an issue never becomes two specs.
89
107
 
90
108
  Branch naming that embeds the Linear id lives in the isolation config
91
109
  (`env.config.json` → `branch.pattern` with `{identifier}`, `branch.identifierField:
@@ -2,10 +2,10 @@
2
2
 
3
3
  A start-to-finish guide to getting `/spec-status` and `/spec-push` working against
4
4
  a real Linear workspace. Sync is **one-way**: the repo is the source of truth and
5
- the linked Linear project is a **generated mirror** — content is pushed up, never
6
- read back or merged. Covers the **Linear side** (connecting the MCP server,
7
- finding your team) that the config reference (`linear.config.md`) assumes you
8
- already have.
5
+ the linked Linear **issue** is a **generated mirror** — content is pushed up, never
6
+ read back or merged. A spec is a Linear issue and each phase a sub-issue; tasks are
7
+ not synced. Covers the **Linear side** (connecting the MCP server, finding your
8
+ team) that the config reference (`linear.config.md`) assumes you already have.
9
9
 
10
10
  > The whole feature is **opt-in**: until `specs/.core/linear.config.json` exists,
11
11
  > everything below is inert and the package behaves exactly like the base
@@ -65,9 +65,10 @@ It calls the Linear `list_teams` tool and returns rows like:
65
65
  Skitterspec — e07c2b54-dcf6-4b6e-81bd-175a9bc79868 (key: SKI)
66
66
  ```
67
67
 
68
- Copy the `id` (the UUID). The `key` (e.g. `SKI`) is the short human handle. If you
69
- want an **Initiative** to group your specs, ask "list my Linear initiatives" and
70
- copy that id too (optional).
68
+ Copy the `id` (the UUID). The `key` (e.g. `SKI`) is the short human handle. If most
69
+ of your specs belong to one **Project**, ask "list my Linear projects" and copy
70
+ that id into `linear.projectId` — it becomes the *default* the project picker
71
+ pre-selects, not a fixed destination.
71
72
 
72
73
  ## 4. Scaffold the config
73
74
 
@@ -80,7 +81,11 @@ ids from step 3 — the team id is the only required field:
80
81
  "linear": {
81
82
  "teamKey": "SKI",
82
83
  "teamId": "e07c2b54-dcf6-4b6e-81bd-175a9bc79868",
83
- "initiativeId": "" // optional
84
+ "projectId": "" // optional: the project picker's default
85
+ },
86
+ "intake": { // optional: starting a spec from an issue
87
+ "label": "web-app", // the inbox `/spec --from-issue` browses
88
+ "bugLabels": ["bug"] // issues with these route to /spec-bug
84
89
  }
85
90
  }
86
91
  ```
@@ -89,20 +94,25 @@ Everything else (state names, field ownership) has sensible defaults — see
89
94
  `linear.config.md` to customise. The moment this file exists, the Linear steps in
90
95
  `/spec` and `/spec-go` and the three sync skills switch on.
91
96
 
92
- ## 5. Link a spec to a Linear project
97
+ ## 5. Link a spec to a Linear issue
93
98
 
94
- A spec syncs once its `00-overview.md` frontmatter carries a `linear_project_id`.
99
+ A spec syncs once its `00-overview.md` frontmatter carries a `linear_identifier`.
95
100
  Two ways to get there:
96
101
 
97
- - **New spec:** run `/spec` — with Linear configured it offers to create a linked
98
- Linear **Project** (one Milestone per phase) and stamps the id for you.
99
- - **Existing spec / existing Linear project:** add the id by hand. Ask Claude to
100
- "create a Linear project for this spec" (or find an existing one's id via
101
- `list_projects`), then set the frontmatter:
102
+ - **New spec:** run `/spec` — with Linear configured it asks which Project the
103
+ spec belongs to, creates a linked Linear **issue** (one sub-issue per phase) and
104
+ stamps the id for you.
105
+ - **From an existing issue:** run `/spec SKI-123` to adopt that issue, or
106
+ `/spec --from-issue` to browse the `intake.label` inbox and pick one. The issue
107
+ becomes the spec's issue — nothing is duplicated. A bug-labelled issue routes to
108
+ `/spec-bug` instead.
109
+ - **Existing spec / existing Linear issue:** add the id by hand. Ask Claude to
110
+ "create a Linear issue for this spec" (or find an existing one's id), then set
111
+ the frontmatter:
102
112
 
103
113
  ```yaml
104
114
  ---
105
- linear_project_id: "640bcb1a-28cd-46b5-b2f8-ff47ce494ed1"
115
+ linear_identifier: "SKI-123"
106
116
  ---
107
117
  ```
108
118
 
@@ -111,7 +121,7 @@ Two ways to get there:
111
121
  | Command | Direction | What it does |
112
122
  |---------|-----------|--------------|
113
123
  | `/spec-status` | — | Read-only drift report: what would push (create/update), and whether Linear's workflow-state drifted from the spec. Writes nothing. |
114
- | `/spec-push` | repo → Linear | Computes a create/update plan vs the last-pushed snapshot and applies it (project description/status, milestones, issues), stamping new ids back into the spec. |
124
+ | `/spec-push` | repo → Linear | Computes a create/update plan vs the last-pushed snapshot and applies it (issue description/state, phase sub-issues), stamping new ids back into the spec. |
115
125
 
116
126
  Typical loop: edit the spec in-repo → `/spec-status` (what's pending) →
117
127
  `/spec-push` (send it up). There is no pull — Linear is a generated mirror.
@@ -120,20 +130,20 @@ Typical loop: edit the spec in-repo → `/spec-status` (what's pending) →
120
130
 
121
131
  | Field | What |
122
132
  |-------|------|
123
- | `description` | the spec body (problem, solution, acceptance criteria) as the project description |
124
- | `milestones` | one per phase (name + goal) |
125
- | `issues` | one per task first-sentence **title**, full task text as the **description** |
126
- | `workflowState` → project status | the spec's lifecycle bucket, mapped via `states` |
133
+ | `description` | the spec body (problem, solution) as the issue description |
134
+ | `subIssues` | one per phase — phase name as **title**, `**Goal:**` as the **description**, phase emoji → **state** |
135
+ | `workflowState` issue state | the spec's lifecycle folder bucket, mapped via `states` |
127
136
 
128
- Priority, labels, cycles and comments are **Linear-native triage** — the PM's to
129
- set in Linear. One-way sync neither pushes nor reads them, so they're never
130
- clobbered. A workflow-state a teammate moves in Linear is surfaced by
131
- `/spec-status` as drift and overwritten on the next push.
137
+ Tasks, priority, labels, cycles and comments are **Linear-native triage** (or
138
+ repo-only) never pushed, so they're never clobbered. A workflow-state a teammate
139
+ moves in Linear is surfaced by `/spec-status` as drift and overwritten on the next
140
+ push.
132
141
 
133
- Phases push as **Milestones** and tasks as **Issues** by default. The link ids
134
- live in the phase-file frontmatter (`linear_milestone_id`) and inline on task
135
- lines (`- [ ] do it (SKI-123)`); `/spec-push` stamps them the first time it
136
- creates each object, so later pushes update instead of recreate.
142
+ The spec is one issue and each phase a **sub-issue** (`parentId` = the spec
143
+ issue). The link ids live in frontmatter `linear_identifier` on the overview,
144
+ `linear_issue_id` on each phase file; `/spec-push` stamps them the first time it
145
+ creates each object, so later pushes update instead of recreate. Set
146
+ `linear.projectId` to add every spec issue to a grouping Linear Project.
137
147
 
138
148
  Sections listed in `sync.localOnlySections` (default: **State log**, **Changelog**,
139
149
  **Open questions**) are stripped from the pushed description — they never leave
@@ -150,22 +160,22 @@ the repo.
150
160
  With a linked spec, confirm push end-to-end:
151
161
 
152
162
  1. `/spec-status` → shows what would push (`pending — N to create, M to update`).
153
- 2. `/spec-push` → creates the project's milestones/issues and sets the
154
- description/status; ids are stamped back into the spec.
163
+ 2. `/spec-push` → creates the spec issue and its phase sub-issues and sets the
164
+ description/state; ids are stamped back into the spec.
155
165
  3. `/spec-status` again → **up to date** (nothing changed since the last push).
156
- 4. Edit a task locally, `/spec-push` → the matching issue updates;
166
+ 4. Edit a phase goal locally, `/spec-push` → the matching sub-issue updates;
157
167
  `/spec-status` returns to **up to date**.
158
168
 
159
169
  ## Troubleshooting
160
170
 
161
171
  - **"connect the `linear` MCP server"** — the server isn't connected/authed for
162
172
  this session. Re-run step 2; remember a fresh add needs a Claude Code restart.
163
- - **"missing required tools: projectUpdate"** — you're on the read-only endpoint
173
+ - **"missing required tools: issueCreate"** — you're on the read-only endpoint
164
174
  (or a restricted API key). Use `https://mcp.linear.app/mcp` for push.
165
- - **A configured status name silently does nothing** — Linear ignores an unknown
166
- project status. Run `/spec-status` (it validates the `states` names against the
167
- workspace) and fix `linear.config.json` to the real project-status names
168
- (`Backlog / Planned / In Progress / Completed / Canceled`).
175
+ - **A configured state name silently does nothing** — Linear ignores an unknown
176
+ issue state. Run `/spec-status` (it validates the `states` names against the
177
+ workspace) and fix `linear.config.json` to the real issue-state names
178
+ (`Backlog / Todo / In Progress / Done / Canceled`).
169
179
  - **Reconnecting doesn't switch workspace** — Linear ties the OAuth session to one
170
180
  workspace. Remove and re-add the server to authenticate against another.
171
181
  - **Bold around an inline code span renders oddly in Linear** — Linear moves the
@@ -2,17 +2,21 @@
2
2
  "linear": {
3
3
  "teamKey": "",
4
4
  "teamId": "",
5
- "initiativeId": ""
5
+ "projectId": ""
6
+ },
7
+ "intake": {
8
+ "label": "",
9
+ "bugLabels": []
6
10
  },
7
11
  "mapping": {
8
- "specFolder": "project",
9
- "phases": "milestone",
10
- "tasks": "issue"
12
+ "specFolder": "issue",
13
+ "phases": "subissue",
14
+ "tasks": "none"
11
15
  },
12
16
  "states": {
13
17
  "backlog": "Backlog",
14
18
  "in-progress": "In Progress",
15
- "complete": "Completed",
19
+ "complete": "Done",
16
20
  "cancelled": "Canceled"
17
21
  },
18
22
  "snapshot": {
@@ -25,8 +29,7 @@
25
29
  "baseDir": "specs/.core/linear-base",
26
30
  "fieldOwnership": {
27
31
  "description": "push",
28
- "milestones": "push",
29
- "tasks": "push",
32
+ "subIssues": "push",
30
33
  "workflowState": "push"
31
34
  },
32
35
  "localOnlySections": ["State log", "Changelog", "Open questions"],
@@ -2,7 +2,8 @@
2
2
 
3
3
  Opt-in config for the Linear sync (`/spec-status`, `/spec-push`, and the
4
4
  Linear-aware paths of `/spec` and `/spec-go`). Sync is **one-way**: the repo is
5
- the source of truth and the Linear project is a **generated mirror**. Content is
5
+ the source of truth and the Linear **issue** is a **generated mirror**. A spec is
6
+ a Linear issue and each phase a sub-issue; tasks are not synced. Content is
6
7
  pushed up and never read back or merged — `/spec-push` diffs the spec against a
7
8
  committed **last-pushed snapshot** and applies only what changed; `/spec-status`
8
9
  is a read-only drift report. The `sync.fieldOwnership` map now just selects the
@@ -12,7 +13,7 @@ projection field set (every field is repo-owned and pushed).
12
13
  is absent the feature is simply unused — `/spec`, `/spec-go`, and the CLI's
13
14
  `spec-sync` subcommands behave exactly as they do today (local-only). Adopt it by
14
15
  copying `linear.config.json.example` → `linear.config.json` here and filling in
15
- your team / initiative IDs.
16
+ your team ID (and an optional grouping project).
16
17
 
17
18
  The loader (`src/sync/config.js` → `loadLinearConfig`) merges your file over the
18
19
  frozen defaults below and returns `{ config, present }`; `present:false` means no
@@ -23,30 +24,38 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
23
24
 
24
25
  ```jsonc
25
26
  {
26
- // Which Linear team/initiative specs sync into. IDs are read by the Phase 2
27
- // MCP adapter; leave blank until you connect the `linear` MCP server.
27
+ // Which Linear team specs sync into, and an optional Project to group them.
28
+ // IDs are read by the MCP adapter; leave blank until you connect the `linear`
29
+ // MCP server.
28
30
  "linear": {
29
31
  "teamKey": "", // human-facing key, e.g. "ENG" (optional)
30
- "teamId": "", // Linear team UUID (create target)
31
- "initiativeId": "" // optional Initiative that groups these specs
32
+ "teamId": "", // Linear team UUID (the issue's team)
33
+ "projectId": "" // DEFAULT for the project picker (see below)
32
34
  },
33
35
 
34
- // How a spec's parts map onto Linear objects. Defaults mirror Decision 7:
35
- // spec folder Project, phases → Milestones, tasks → Issues. `phases` may be
36
- // switched to "issue" if your workspace doesn't expose project milestones.
36
+ // Issue intake: `/spec <ISSUE-REF>` adopts any issue; `/spec --from-issue`
37
+ // browses the inbox. Both optional.
38
+ "intake": {
39
+ "label": "", // inbox filter — the label the web app files under
40
+ "bugLabels": [] // e.g. ["bug"] — these route to /spec-bug instead
41
+ },
42
+
43
+ // How a spec's parts map onto Linear objects: a spec is an Issue, each phase a
44
+ // sub-issue (a child issue), tasks are not synced. These are the defaults.
37
45
  "mapping": {
38
- "specFolder": "project",
39
- "phases": "milestone", // "milestone" | "issue"
40
- "tasks": "issue"
46
+ "specFolder": "issue",
47
+ "phases": "subissue",
48
+ "tasks": "none"
41
49
  },
42
50
 
43
- // Map the spec's lifecycle bucket → the Linear workflow-state name. Used when
44
- // translating workflowState across the boundary (Linear owns status `pull`).
51
+ // Map the spec's lifecycle bucket → the Linear ISSUE workflow-state name. Used
52
+ // for the spec issue's state (from its folder) AND each sub-issue's state (from
53
+ // the phase emoji). Names must match the workspace's issue states exactly.
45
54
  "states": {
46
55
  "backlog": "Backlog",
47
56
  "in-progress": "In Progress",
48
57
  "complete": "Done",
49
- "cancelled": "Cancelled"
58
+ "cancelled": "Canceled"
50
59
  },
51
60
 
52
61
  // The spec's entry-point file the local snapshot + frontmatter live in.
@@ -69,15 +78,14 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
69
78
  "baseDir": "specs/.core/linear-base",
70
79
 
71
80
  // The pushed projection field set (repo → Linear, one-way). The `push` marker
72
- // is retained for shape; there is no pull. The default set is the project
73
- // `description`, `milestones` (one per phase), `tasks` (one issue each), and
74
- // the lifecycle `workflowState`. Priority, labels, cycles and comments are
75
- // Linear-native triage deliberately NOT here, so a PM's triage is never
76
- // touched. Any key you add joins the pushed projection.
81
+ // is retained for shape; there is no pull. The default set is the spec
82
+ // issue's `description`, its `subIssues` (one per phase name + goal +
83
+ // state), and the lifecycle `workflowState`. Tasks, priority, labels, cycles
84
+ // and comments are NOT here, so a PM's triage is never touched. Any key you
85
+ // add joins the pushed projection.
77
86
  "fieldOwnership": {
78
87
  "description": "push",
79
- "milestones": "push",
80
- "tasks": "push",
88
+ "subIssues": "push",
81
89
  "workflowState": "push"
82
90
  },
83
91
 
@@ -85,31 +93,65 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
85
93
  // are stripped from the pushed `description` (never sent to Linear).
86
94
  "localOnlySections": ["State log", "Changelog", "Open questions"],
87
95
 
88
- // Reserved. Milestones and tasks are always projected per item (each phase
89
- // a Milestone, each task an Issue), so this no longer needs setting; it is
90
- // validated but unused. Leave it `{}`.
96
+ // Reserved. Sub-issues are always projected per phase, so this no longer
97
+ // needs setting; it is validated but unused. Leave it `{}`.
91
98
  "keyedFields": {}
92
99
  }
93
100
  }
94
101
  ```
95
102
 
96
- ## PhasesMilestones, tasksIssues
103
+ ## SpecIssue, phasessub-issues
97
104
 
98
105
  Push maps the spec's structure to Linear's, keyed by id so it updates rather than
99
106
  recreates:
100
107
 
101
- - **PhasesMilestones.** Each phase file maps to a Linear Milestone. The link id
102
- lives in the phase file's frontmatter (`linear_milestone_id`); its name ← the
103
- phase h1, its description the phase `**Goal:**` line. The `Phases` index is
104
- stripped from the pushed `description` (no duplication).
105
- - **TasksIssues.** Each `- [ ]` task line maps to a Linear Issue. The link id
106
- is carried **inline** on the line `- [ ] do the thing (SKI-123)`. The issue
107
- **title** is the task's first sentence; the **description** is the full task
108
- text; `[x]`/`[ ]` a completed / non-completed issue state.
109
-
110
- Unlinked local items (a new phase with no `linear_milestone_id`, a task with no
111
- inline id) are created in Linear on the next `/spec-push`, which stamps the new id
112
- back so they link from then on.
108
+ - **SpecIssue.** The spec is one Linear issue. Its id lives in the overview
109
+ frontmatter (`linear_identifier`); its `description` ← the overview plan (with
110
+ the `Phases` index and local-only sections stripped); its workflow-state the
111
+ spec's lifecycle folder (`specs/<bucket>/`).
112
+ - **Phasessub-issues.** Each phase file maps to a child issue (`parentId` = the
113
+ spec issue). The link id lives in the phase file's frontmatter
114
+ (`linear_issue_id`); its title the phase h1, its description the phase
115
+ `**Goal:**` line, its state the phase heading emoji (⬜/🔄/✅).
116
+ - **Tasks are not synced.** Task checkboxes stay in the repo phase files only.
117
+
118
+ Unlinked local items (a spec with no `linear_identifier`, a phase with no
119
+ `linear_issue_id`) are created in Linear on the next `/spec-push`, which stamps
120
+ the new id back so they link from then on.
121
+
122
+ ## Which Project a spec issue belongs to
123
+
124
+ `linear.projectId` is the **default**, not a mandate. When a spec issue is first
125
+ created — by `/spec`, or by the first `/spec-push` if the spec was authored
126
+ offline — you're offered the team's projects, filterable by name, with an explicit
127
+ **None (team only)** option and this id pre-selected.
128
+
129
+ The choice is passed on the **create call only**. It is never written into the
130
+ spec, never recorded in the snapshot, and never sent on an update. So once the
131
+ issue exists, where it lives is Linear's business: move it between projects and
132
+ `/spec-status` will not call it drift and `/spec-push` will not move it back.
133
+
134
+ A spec that **adopted** an existing issue (see below) skips the picker entirely —
135
+ it was filed somewhere deliberately.
136
+
137
+ ## Starting a spec from an existing issue
138
+
139
+ With `intake` configured, a spec can begin life as a Linear issue someone else
140
+ filed:
141
+
142
+ - `/spec SKI-123` — adopt that issue.
143
+ - `/spec --from-issue [query]` — browse issues labelled `intake.label` (what your
144
+ web app or feedback form files under), optionally filtered by title.
145
+
146
+ The issue **becomes** the spec's issue: its identifier is stamped as
147
+ `linear_identifier`, phases become its sub-issues, and the first `/spec-push`
148
+ replaces its description with the spec. The reporter's comments, links and
149
+ subscribers stay on the one issue everyone is already watching; their original
150
+ words are carried into the spec's **Problem** section.
151
+
152
+ An issue already stamped on a spec can't be adopted twice — `skitterspec spec-sync
153
+ linked` is the list that's checked. An issue labelled with one of
154
+ `intake.bugLabels` routes to `/spec-bug` instead, which adopts it identically.
113
155
 
114
156
  ## One direction — nothing to reconcile
115
157
 
@@ -113,6 +113,9 @@ status.
113
113
 
114
114
  Every spec header carries:
115
115
 
116
+ - `> **Name:**` — the spec's folder name (`feat-`/`bug-`/`hotfix-<kebab-name>`).
117
+ It's the handle you pass to `/spec-go` and the other lifecycle skills, surfaced
118
+ in the header so it's copy-pasteable without digging for the folder name.
116
119
  - `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
117
120
  to `git config user.name`).
118
121
  - `> **Developer:**` — who implements it (`—` until `/spec-go` starts work, then
@@ -14,6 +14,66 @@ Lifecycle (the governing skills) — status in parentheses:
14
14
  `/spec-cancel` (Cancelled). See `.claude/rules/spec-planning.md`. (There is no
15
15
  separate grooming command — `/spec` grills to a Ready spec directly.)
16
16
 
17
+ ## Phase 0 — start from a Linear issue (only when asked)
18
+
19
+ **Only when `specs/.core/linear.config.json` exists** *and* the invocation names an
20
+ issue. Otherwise skip this phase entirely and grill from the user's own words.
21
+
22
+ Two ways in:
23
+
24
+ - **`<ISSUE-REF>`** (`SKI-123`, an issue URL, or a bare id anywhere in the
25
+ arguments) — adopt that issue.
26
+ - **`--from-issue [query]`** — browse the intake inbox: issues carrying
27
+ `intake.label` (what the web app files under). With a `query`, filter that list
28
+ case-insensitively by title. Show the shortlist and let the user pick one.
29
+
30
+ Then:
31
+
32
+ 1. **Exclude what's already adopted.** Run `skitterspec spec-sync linked --json`
33
+ and drop any issue whose identifier is already stamped on a spec. If the user
34
+ named such an issue directly, **stop** and point them at that spec — one issue
35
+ never becomes two specs.
36
+ 2. **Read the issue** with the discovered issue-read tool: title, description,
37
+ labels, reporter, URL. If Linear isn't connected, say so and offer to carry on
38
+ without it — a spec written from the user's own description is still a spec.
39
+ 3. **Route bugs away — in `/spec` only.** If any of the issue's labels matches
40
+ `intake.bugLabels` (case-insensitive), this is a bug report, not a feature
41
+ request. Say so, name the matching label, and tell the user to run
42
+ **`/spec-bug <ISSUE-REF>`** — then stop, without authoring a Feature spec.
43
+ With `intake.bugLabels` unset nothing is routed and every issue is treated as a
44
+ feature request.
45
+
46
+ **In `/spec-bug` this step is skipped** — you are already in the bug path.
47
+ Adopt the issue, then reproduce it as usual: the issue body is your repro
48
+ material, and the failing test comes before the spec exactly as normal.
49
+ 4. **Seed, don't skip, the grill.** The issue's title becomes the working spec
50
+ title and its description the starting material for **Problem** — quote the
51
+ reporter's words rather than paraphrasing them away. Note the reporter and the
52
+ issue URL for context. Everything after this runs exactly as normal: an issue
53
+ is a *request*, not a groomed spec, so grill it as hard as anything else.
54
+
55
+ ### Adopting the issue
56
+
57
+ The issue **becomes** the spec's issue — it is not copied and no second issue is
58
+ minted. This is identical in `/spec` and `/spec-bug`; only *when* it happens
59
+ differs (`/spec` writes the spec in Phase B, `/spec-bug` in its step 4). Once the
60
+ spec file exists:
61
+
62
+ - **Stamp `linear_identifier` and `linear_url`** in `00-overview.md` frontmatter
63
+ from the adopted issue. That is the whole link: every later skill
64
+ (`/spec-push`, `/spec-status`, `/spec-go`) keys off it being present.
65
+ - **Do not run the project picker** and never send `project`. The issue was filed
66
+ somewhere deliberately — where it lives is Linear's business, and adoption is
67
+ not a mint.
68
+ - **Do not write a base sidecar.** Leaving `sync.baseDir` empty for this spec is
69
+ what makes the first `/spec-push` push the spec over the reporter's original
70
+ description (an **update** to the existing issue, plus a sub-issue per phase).
71
+ Recording a snapshot here would declare the mirror already in sync and strand
72
+ the issue showing the raw report forever.
73
+ - **Say what will happen** in the finish-up message: the first `/spec-push` will
74
+ overwrite the issue's description with the spec, and the original report is
75
+ preserved in the spec's **Problem** section.
76
+
17
77
  ## Phase A — reach a clear shared understanding (grill first)
18
78
 
19
79
  Interview the user until requirement AND proposed solution are unambiguous. Do
@@ -73,6 +133,7 @@ the codebase, link rather than duplicate):
73
133
  # <Feature title>
74
134
 
75
135
  > **Type:** Feature
136
+ > **Name:** feat-<kebab-name> (the spec folder name — the handle you paste into `/spec-go`)
76
137
  > **Status:** Ready — not started
77
138
  > **Author:** <git user.name — `git config user.name`>
78
139
  > **Developer:** —
@@ -227,36 +288,71 @@ below (nothing to do here without one).
227
288
  **Only when `specs/.core/linear.config.json` exists** (Linear sync is opted in).
228
289
  If it's absent, skip this phase entirely — the spec stays local-only and `/spec`
229
290
  behaves exactly as above. When present, after writing the spec, link it to Linear
230
- so status and discussion live there while the repo stays the co-authoring surface:
291
+ so status and discussion live there while the repo stays the co-authoring surface.
292
+ A spec is a Linear **issue**; each phase is a **sub-issue**; tasks are not synced:
231
293
 
232
294
  - **Discover the Linear MCP tools at runtime** (don't hardcode names). If Linear
233
295
  isn't connected/authed, relay the fix and stop — leave the spec written and
234
296
  local; the user can link it later with `/spec-push`. Do nothing destructive.
235
- - **Create the Project** from the spec: name from the title, description from the
236
- `00-overview.md` plan. Attach the `initiativeId` from `linear.config.json` when
237
- one is set.
238
- - **Create a Milestone per phase** (the `mapping.phases` target milestones by
239
- default), named from each phase file, in execution order.
297
+ - **Pick the Project** run the picker in **Picking the Linear Project** below,
298
+ then **create the Issue** from the spec: `title` from the spec title,
299
+ `description` from the `00-overview.md` plan, `team` = `linear.teamId`, and
300
+ `project` = the picked id (omitted when the user chose None).
301
+ - **Create a sub-issue per phase** (the `mapping.phases` target — `subissue` by
302
+ default): a child issue with `parentId` = the spec issue, named from each phase
303
+ file, in execution order.
240
304
  - **Add the frontmatter block** to `00-overview.md` (above the `#` title) so the
241
305
  spec is linkable:
242
306
 
243
307
  ```yaml
244
308
  ---
245
- linear_project_id: "<uuid>"
246
309
  linear_identifier: "<TEAM-123>"
247
310
  linear_url: "https://linear.app/..."
248
- spec_status: "backlog"
249
311
  last_synced_at: "<ISO-8601 now>"
250
312
  ---
251
313
  ```
252
314
 
315
+ Stamp each phase file's `linear_issue_id` with its sub-issue id. (Status is not
316
+ stored in frontmatter — it comes from the spec's lifecycle folder.)
253
317
  - **Write the initial base sidecar** so the spec starts clean and non-diverged —
254
- run `skitterspec spec-sync normalize <spec>` to capture the local snapshot as the
318
+ run `skitterspec spec-sync record <spec>` to capture the local snapshot as the
255
319
  committed base (`sync.baseDir`). `/spec-status` should report in-sync right after.
320
+ (`record` is the writer; `normalize` only *prints* the projection.) Skip this
321
+ when the spec **adopted** an existing issue — see Phase 0 — because the issue's
322
+ description is still the reporter's, not the spec.
256
323
  - **Echo the branch name** from `branch.pattern` so the user knows what `/spec-go`
257
324
  will fork.
258
325
 
259
326
  Leave committing to the existing convention (the user commits the spec as usual)
260
327
  and **never auto-push git** — Linear's own automation reacts to real branch/PR
261
- events later. Report the Linear project URL and the base as part of Phase C's
328
+ events later. Report the Linear issue URL and the base as part of Phase C's
262
329
  finish-up message.
330
+
331
+ ### Picking the Linear Project
332
+
333
+ Run this **only when minting a spec issue** — creating it for the first time. On
334
+ an update the issue already has a project (or deliberately has none), and that
335
+ placement is **Linear's to own**: never send `project` on an update, and never
336
+ record the choice in the spec file or the snapshot. A PM re-homing a spec issue
337
+ must not show up as drift or be overwritten on the next push.
338
+
339
+ 1. **List the candidates.** Call the discovered project-list tool for
340
+ `linear.teamId`. Drop archived / completed projects — they can't take new work.
341
+ 2. **Offer them.** Show the names (most recently updated first is fine), plus an
342
+ explicit **None (team only)** option. Pre-select `linear.projectId` from
343
+ `linear.config.json` when it's set and still in the list; otherwise pre-select
344
+ **None**.
345
+ 3. **Narrow on request.** If the user types a fragment rather than choosing, filter
346
+ the list case-insensitively by name and re-offer. Don't re-fetch.
347
+ 4. **Never offer to create a project.** Projects are the PM's surface — if none
348
+ fits, that's **None (team only)**, and someone makes the project in Linear.
349
+ 5. **Pass it once.** Include `project: <chosen id>` on the issue-create call.
350
+ Chose None → omit the key entirely (do not send an empty string).
351
+
352
+ **Degrade, never block.** If Linear isn't connected, or the server exposes no
353
+ project-list tool, say so in one line — *"project picker unavailable; creating the
354
+ issue without a project"* — and carry on with `linear.projectId` if it's set, else
355
+ no project at all. A missing picker must never fail `/spec` or `/spec-push`.
356
+
357
+ If `linear.projectId` is set but that Project is archived or missing, relay
358
+ Linear's error and stop rather than silently minting an unparented issue.