@skitterbyte/skitterspec-linear 7.0.2 → 8.0.1

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
@@ -1,10 +1,10 @@
1
1
  # @skitterbyte/skitterspec-linear
2
2
 
3
3
  Spec-driven development for [Claude Code](https://claude.com/claude-code), **with
4
- Linear hybrid-sync**. A strict **superset** of
4
+ one-way Linear sync**. A strict **superset** of
5
5
  [`@skitterbyte/skitterspec`](https://www.npmjs.com/package/@skitterbyte/skitterspec):
6
- everything in the base filesystem workflow, plus git-like sync between a spec and
7
- its linked Linear project.
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.
8
8
 
9
9
  ```sh
10
10
  npx @skitterbyte/skitterspec-linear init
@@ -46,27 +46,26 @@ 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 sync** — `/spec` now creates a linked Linear Project (a Milestone
50
- per phase) and stamps the id; then `/spec-status`, `/spec-pull`, `/spec-push`
51
- keep the spec and its project in step. Optionally turn on the per-Milestone /
52
- per-Issue **body round-trip** (see below).
49
+ 4. **Link and push** — `/spec` creates a linked Linear Project (a Milestone per
50
+ phase) and stamps the id; then `/spec-push` publishes the spec up and
51
+ `/spec-status` reports what would push. Sync is **one-way**: the repo is the
52
+ source of truth and Linear is a generated mirror.
53
53
 
54
54
  ## What the superset adds
55
55
 
56
56
  On top of the base skills (`/spec`, `/spec-go`, isolation, …):
57
57
 
58
- - **`/spec-status`** — read-only, per-field divergence (local-only / remote-only /
59
- conflict / in-sync). Changes nothing.
60
- - **`/spec-pull [--force]`** — Linearrepo. Applies remote-only fields; refuses
61
- to clobber a conflicting local edit unless `--force`.
62
- - **`/spec-push [--force]`** repo Linear. Ownership-respecting,
63
- concurrency-checked; refuses if Linear moved since base unless `--force`.
58
+ - **`/spec-status`** — read-only drift report: what the next push would create /
59
+ update, plus any workflow-state drift. Changes nothing.
60
+ - **`/spec-push`** — repoLinear, 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.
64
63
  - **`spec-sync` CLI** (`skitterspec-linear spec-sync …`) — the deterministic
65
64
  engine behind the skills, for CI / local runs.
66
65
 
67
66
  The shared `/spec` and `/spec-go` skills come composed with the Linear steps
68
- filled in: `/spec` links a new spec to a Linear Project (a Milestone per phase),
69
- and `/spec-go` pulls first so you build against the current shared state.
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.
70
69
 
71
70
  ## Opt-in
72
71
 
@@ -78,16 +77,15 @@ behaves exactly like the base.
78
77
  setup guide — connecting the `linear` MCP server, finding your team id, linking a
79
78
  spec, and a smoke test. Per-field docs live in `specs/.core/linear.config.md`.
80
79
 
81
- **What syncs:** by default the whole spec body travels as the Linear Project
82
- **`description`** (co-authored, push + pull); **status / priority / labels** are
83
- Linear-owned (pull only). **Field ownership** (`both` / `pull` / `push`) collapses
84
- conflicts only a `both` field that moved on both sides is a real conflict, and
85
- `--force` backs up the losing side before winning. **Opt into a per-Milestone /
86
- per-Issue body round-trip** phases Linear Milestones and tasks Issues,
87
- compared per item by adding `milestones`/`tasks` to `sync.keyedFields` (see the
88
- "Body round-trip" section of `linear.config.md`); deletions there are report-only.
89
- **Base sidecars** (`specs/.core/linear-base/`) are committed; **backups**
90
- (`specs/.core/linear-backups/`) are gitignored.
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.
91
89
 
92
90
  Branch naming that embeds the Linear id lives in the isolation config
93
91
  (`env.config.json` → `branch.pattern` with `{identifier}`, `branch.identifierField:
@@ -1,9 +1,11 @@
1
- # Setting up Linear hybrid-sync
1
+ # Setting up Linear sync (one-way)
2
2
 
3
- A start-to-finish guide to getting `/spec-status`, `/spec-pull`, and `/spec-push`
4
- working against a real Linear workspace. Covers the **Linear side** (connecting
5
- the MCP server, finding your team) that the config reference
6
- (`linear.config.md`) assumes you already have.
3
+ A start-to-finish guide to getting `/spec-status` and `/spec-push` working against
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.
7
9
 
8
10
  > The whole feature is **opt-in**: until `specs/.core/linear.config.json` exists,
9
11
  > everything below is inert and the package behaves exactly like the base
@@ -47,7 +49,7 @@ claude mcp list
47
49
  ```
48
50
 
49
51
  > Read-only trial: use `https://mcp.linear.app/mcp/readonly` to exercise
50
- > `/spec-status` and `/spec-pull` without granting write access. `/spec-push`
52
+ > `/spec-status` (drift report) without granting write access. `/spec-push`
51
53
  > needs the full (writable) endpoint.
52
54
 
53
55
  ## 3. Find your team id
@@ -108,33 +110,30 @@ Two ways to get there:
108
110
 
109
111
  | Command | Direction | What it does |
110
112
  |---------|-----------|--------------|
111
- | `/spec-status` | — | Read-only. Per-field divergence: `local-only` / `remote-only` / `conflict` / `in sync`. |
112
- | `/spec-pull` | Linearrepo | Applies remote-owned fields (status/priority/labels). Refuses a real conflict unless `--force`. |
113
- | `/spec-push` | repo → Linear | Sends the co-authored `description` up. Refuses if a co-authored field moved on Linear since your last sync (pull first) unless `--force`. |
114
-
115
- Typical loop: `/spec-status` `/spec-pull` (take Linear's status) edit the spec
116
- in-repo → `/spec-push` (send content up).
117
-
118
- ### What actually syncs
119
-
120
- | Field | Owner | Direction |
121
- |-------|-------|-----------|
122
- | `description` (the whole spec body: problem, solution, **phases**, acceptance criteria) | co-authored | push **and** pull |
123
- | `workflowState` `spec_status` | Linear | pull only |
124
- | `priority` | Linear | pull only |
125
- | `labels` | Linear | pull only |
126
-
127
- By default the **entire spec body travels as the project `description`** phases
128
- and acceptance criteria included.
129
-
130
- **Optional: body round-trip.** Opt in — add `milestones`/`tasks` to
131
- `sync.keyedFields` (and `sync.fieldOwnership`) and phases sync as **Milestones**
132
- and tasks as **Issues**, compared per item: edit an individual phase or task in
133
- Linear and pull just that back into the right phase file / task line, or push
134
- local changes up. The link ids live in the phase file frontmatter
135
- (`linear_milestone_id`) and inline on task lines (`- [ ] do it (SKI-123)`).
136
- Deletions are report-only (surfaced by `/spec-status`, never auto-applied). Full
137
- details in the "Body round-trip" section of `linear.config.md`.
113
+ | `/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` | repoLinear | 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. |
115
+
116
+ Typical loop: edit the spec in-repo → `/spec-status` (what's pending) →
117
+ `/spec-push` (send it up). There is no pull Linear is a generated mirror.
118
+
119
+ ### What gets pushed
120
+
121
+ | Field | What |
122
+ |-------|------|
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` |
127
+
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.
132
+
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.
138
137
 
139
138
  Sections listed in `sync.localOnlySections` (default: **State log**, **Changelog**,
140
139
  **Open questions**) are stripped from the pushed description — they never leave
@@ -142,24 +141,20 @@ the repo.
142
141
 
143
142
  ## 7. What to commit
144
143
 
145
- - **Commit** `specs/.core/linear-base/` — the three-way merge's base sidecars
146
- (last-synced snapshot per spec). Each worktree carries its own, so it must
147
- travel with the branch.
148
- - **Gitignore** `specs/.core/linear-backups/` — `--force` recovery copies, local
149
- and per-machine. Add `specs/.core/linear-backups/` to `.gitignore`.
144
+ - **Commit** `specs/.core/linear-base/` — the last-pushed snapshots (content
145
+ hashes per spec, so `/spec-push` knows what changed without reading Linear
146
+ back). Each worktree carries its own, so it must travel with the branch.
150
147
 
151
148
  ## 8. Smoke test (verify your setup)
152
149
 
153
- With a linked spec, confirm the round-trip end-to-end:
150
+ With a linked spec, confirm push end-to-end:
154
151
 
155
- 1. `/spec-status` → note the current divergence.
156
- 2. `/spec-pull` → Linear's status/priority/labels land in the spec's frontmatter
157
- (`spec_status`, `priority`, `labels`).
158
- 3. `/spec-status` again → **in sync**. (This also proves description idempotency:
159
- Linear rewrites markdown bullets on save, and the sync canonicalizes both
160
- sides so that never shows as a spurious change.)
161
- 4. Edit the spec body locally, `/spec-push` → the change lands on the Linear
162
- project's description; `/spec-status` returns to **in sync**.
152
+ 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.
155
+ 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;
157
+ `/spec-status` returns to **up to date**.
163
158
 
164
159
  ## Troubleshooting
165
160
 
@@ -167,9 +162,9 @@ With a linked spec, confirm the round-trip end-to-end:
167
162
  this session. Re-run step 2; remember a fresh add needs a Claude Code restart.
168
163
  - **"missing required tools: projectUpdate"** — you're on the read-only endpoint
169
164
  (or a restricted API key). Use `https://mcp.linear.app/mcp` for push.
170
- - **A field won't stop showing as diverged** — that field genuinely differs on
171
- the two sides. `pull`-owned fields (status/priority/labels) resolve to Linear;
172
- `/spec-pull` reconciles them. For a co-authored `conflict`, resolve locally or
173
- `--force` (which backs up the losing side under `sync.backupDir` first).
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`).
174
169
  - **Reconnecting doesn't switch workspace** — Linear ties the OAuth session to one
175
170
  workspace. Remove and re-add the server to authenticate against another.
@@ -12,8 +12,8 @@
12
12
  "states": {
13
13
  "backlog": "Backlog",
14
14
  "in-progress": "In Progress",
15
- "complete": "Done",
16
- "cancelled": "Cancelled"
15
+ "complete": "Completed",
16
+ "cancelled": "Canceled"
17
17
  },
18
18
  "snapshot": {
19
19
  "overviewFile": "00-overview.md"
@@ -23,12 +23,11 @@
23
23
  },
24
24
  "sync": {
25
25
  "baseDir": "specs/.core/linear-base",
26
- "backupDir": "specs/.core/linear-backups",
27
26
  "fieldOwnership": {
28
- "description": "both",
29
- "workflowState": "pull",
30
- "priority": "pull",
31
- "labels": "pull"
27
+ "description": "push",
28
+ "milestones": "push",
29
+ "tasks": "push",
30
+ "workflowState": "push"
32
31
  },
33
32
  "localOnlySections": ["State log", "Changelog", "Open questions"],
34
33
  "keyedFields": {}
@@ -1,10 +1,12 @@
1
- # `linear.config.json` — Linear hybrid-sync config
1
+ # `linear.config.json` — Linear one-way sync config
2
2
 
3
- Opt-in config for the git-like Linear sync (`/spec-status`, `/spec-pull`,
4
- `/spec-push`, and the Linear-aware paths of `/spec` and `/spec-go`). Linear owns
5
- **status and discussion**; the repo stays the **co-authoring surface for spec
6
- content**. Sync is bidirectional but git-like: explicit commands, a committed
7
- **base sidecar** for three-way merge, and no blind overwrites.
3
+ Opt-in config for the Linear sync (`/spec-status`, `/spec-push`, and the
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
6
+ pushed up and never read back or merged — `/spec-push` diffs the spec against a
7
+ committed **last-pushed snapshot** and applies only what changed; `/spec-status`
8
+ is a read-only drift report. The `sync.fieldOwnership` map now just selects the
9
+ projection field set (every field is repo-owned and pushed).
8
10
 
9
11
  **Every Linear step is gated on this file.** While `specs/.core/linear.config.json`
10
12
  is absent the feature is simply unused — `/spec`, `/spec-go`, and the CLI's
@@ -59,101 +61,69 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
59
61
  "pattern": "{type}/{slug}"
60
62
  },
61
63
 
62
- // The three-way merge engine's on-disk state.
64
+ // One-way push engine state.
63
65
  "sync": {
64
- // Committed base sidecar dir: the last-synced snapshot per spec, as
65
- // {baseDir}/{identifier}.base.json. Committed so each worktree carries its
66
- // own base and the divergence check stays accurate.
66
+ // Committed last-pushed snapshot dir: content hashes of the last push per
67
+ // spec, as {baseDir}/{identifier}.base.json. Committed so each worktree
68
+ // carries its own, and push knows what changed without reading Linear back.
67
69
  "baseDir": "specs/.core/linear-base",
68
70
 
69
- // Backup-before-force lands the about-to-be-clobbered side here (the
70
- // reflog). --force never destroys without first writing a copy.
71
- "backupDir": "specs/.core/linear-backups",
72
-
73
- // Per-field sync direction collapses which fields can ever conflict:
74
- // "both" co-authored: push + pull, may conflict (both moved off base).
75
- // "pull" — Linear→local only (e.g. status/priority); a local edit never
76
- // pushes and a conflict resolves to remote-wins.
77
- // "push" — local→Linear only; a remote edit never pulls and a conflict
78
- // resolves to local-wins.
79
- // Any field key you add here joins the compared field set; a value outside
80
- // both|pull|push is rejected at load time. The default set is scoped to the
81
- // fields that round-trip through the live skill today: the whole spec body
82
- // travels as `description`; status/priority/labels are Linear-owned (pull).
83
- // A phase/milestone or per-issue round-trip is a future extension — opt in by
84
- // adding `milestones`/`phaseBodies`/`taskBreakdown` here once that write path
85
- // exists (see the deferred body write-back note in the package README).
71
+ // 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 triagedeliberately NOT here, so a PM's triage is never
76
+ // touched. Any key you add joins the pushed projection.
86
77
  "fieldOwnership": {
87
- "description": "both",
88
- "workflowState": "pull",
89
- "priority": "pull",
90
- "labels": "pull"
78
+ "description": "push",
79
+ "milestones": "push",
80
+ "tasks": "push",
81
+ "workflowState": "push"
91
82
  },
92
83
 
93
84
  // Markdown sections of 00-overview.md that are local-only scaffolding and
94
85
  // are stripped from the pushed `description` (never sent to Linear).
95
86
  "localOnlySections": ["State log", "Changelog", "Open questions"],
96
87
 
97
- // OPT-IN body round-trip. Map a keyed collection field its item id key to
98
- // sync it per item (each phase ↔ a Milestone, each task an Issue) instead of
99
- // as one description blob. Empty by default. See "Body round-trip" below.
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 `{}`.
100
91
  "keyedFields": {}
101
92
  }
102
93
  }
103
94
  ```
104
95
 
105
- ## Body round-trip (milestones & tasks) opt-in
96
+ ## Phases Milestones, tasks Issues
106
97
 
107
- By default the whole spec body travels as the project **`description`**. Opt a
108
- workspace into a finer-grained, bidirectional sync by adding the keyed fields:
98
+ Push maps the spec's structure to Linear's, keyed by id so it updates rather than
99
+ recreates:
109
100
 
110
- ```jsonc
111
- "sync": {
112
- "fieldOwnership": { "milestones": "both", "tasks": "both" },
113
- "keyedFields": { "milestones": "id", "tasks": "id" }
114
- }
115
- ```
116
-
117
- With this on:
118
-
119
- - **Phases ↔ Milestones.** Each phase file maps to a Linear Milestone. The link id
120
- lives in the phase file's frontmatter (`linear_milestone_id`); its title ← the
101
+ - **Phases → Milestones.** 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
121
103
  phase h1, its description ← the phase `**Goal:**` line. The `Phases` index is
122
- then stripped from the pushed `description` (no duplication).
123
- - **Tasks Issues.** Each `- [ ]` task line maps to a Linear Issue. The link id
124
- is carried **inline** on the line — `- [ ] do the thing (SKI-123)`. Text ↔ the
125
- issue title; `[x]`/`[ ]` a completed / non-completed issue state.
126
- - **Per-item merge.** Items are compared by id, so editing milestone A locally and
127
- milestone B in Linear both apply; only the *same* item moving on both sides is a
128
- conflict.
129
- - **Deletions are report-only.** A phase/milestone or task/issue removed on either
130
- side is surfaced by `/spec-status` (and the pull/push summaries) for you to
131
- resolve by hand — it is never auto-deleted.
104
+ stripped from the pushed `description` (no duplication).
105
+ - **Tasks Issues.** 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.
132
109
 
133
110
  Unlinked local items (a new phase with no `linear_milestone_id`, a task with no
134
111
  inline id) are created in Linear on the next `/spec-push`, which stamps the new id
135
112
  back so they link from then on.
136
113
 
137
- ## Field ownership & conflicts
138
-
139
- The spec is a set of structured fields, most written by only one side. Marking a
140
- field's owner collapses which fields can genuinely conflict:
141
-
142
- - A `pull` field (Linear owns it) never reports as **pushable** — a stray local
143
- edit is informational and gets reverted on the next pull.
144
- - A `push` field (the repo owns it) never reports as **pullable**.
145
- - Only a `both` field where **both** sides moved off the committed base is a real
146
- `conflict` — `/spec-push` / `/spec-pull` refuse it unless `--force` (which
147
- backs up the losing side into `sync.backupDir` first).
114
+ ## One direction nothing to reconcile
148
115
 
149
- After any successful pull/push/force the engine **rewrites the base** so the next
150
- three-way compare starts clean.
116
+ The repo owns the spec and pushes it; there is no pull. `/spec-push` diffs the
117
+ current spec against the committed **last-pushed snapshot** and sends only what
118
+ changed — creates for unlinked items, updates for changed ones. A workflow-state a
119
+ teammate moves in Linear is surfaced by `/spec-status` as **drift** (advisory) and
120
+ overwritten on the next push. Priority, labels, cycles and comments are
121
+ Linear-native triage and are never touched. No base merge, no conflicts, no
122
+ `--force`.
151
123
 
152
124
  ## What to commit
153
125
 
154
- - **`sync.baseDir`** (default `specs/.core/linear-base/`) — **commit it.** The base
155
- sidecar is the last-synced snapshot the three-way merge compares against; each
156
- worktree carries its own base, so it must travel with the branch.
157
- - **`sync.backupDir`** (default `specs/.core/linear-backups/`) — **gitignore it.**
158
- These are `--force` recovery copies (a local reflog), per-machine and not shared.
159
- Add `specs/.core/linear-backups/` to your `.gitignore`.
126
+ - **`sync.baseDir`** (default `specs/.core/linear-base/`) — **commit it.** The
127
+ last-pushed snapshot is content hashes of the last push, so `/spec-push` knows
128
+ what changed without reading Linear back; each worktree carries its own, so it
129
+ must travel with the branch.
@@ -68,13 +68,15 @@ Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engin
68
68
  tracker-free: it knows nothing about any specific ticketing system. A
69
69
  ticketing provider is installed as its own distribution that plugs into two named
70
70
  **seams** in the shared skills (`/spec` Phase E, `/spec-go` step 3b) and fulfils a
71
- skill-name + CLI contract it ships `/spec-status` (read-only per-field
72
- divergence), `/spec-pull` (tracker→repo), and `/spec-push` (repo→tracker), backed
73
- by a `spec-sync` CLI, three-way merged against a committed base sidecar. When a
74
- provider is present, `/spec` also links the spec to the tracker and `/spec-go`
75
- pulls first. With no provider installed the seams are empty and every skill
76
- behaves as a plain filesystem workflow. See the provider package's own docs for
77
- its config and field reference.
71
+ skill-name + CLI contract. Sync is **one-way**: the repo is the source of truth
72
+ and the tracker is a **generated mirror**. It ships `/spec-push` (repo→tracker;
73
+ computes a create/update plan against a committed last-pushed snapshot and applies
74
+ it) and `/spec-status` (read-only drift report what would push, and whether the
75
+ tracker's workflow-state drifted), backed by a `spec-sync` CLI. There is no
76
+ content pull the tracker is never read back or merged. When a provider is
77
+ present, `/spec` also links the spec to the tracker. With no provider installed
78
+ the seams are empty and every skill behaves as a plain filesystem workflow. See
79
+ the provider package's own docs for its config and field reference.
78
80
 
79
81
  ## Project conventions (fill this in)
80
82
 
@@ -217,11 +217,11 @@ finish as above.
217
217
  ## Phase E — link to a ticketing provider (only if one is installed)
218
218
 
219
219
  **Only when a ticketing provider is installed and configured** (it ships the
220
- `/spec-push` · `/spec-pull` · `/spec-status` skills and a provider config under
220
+ `/spec-push` · `/spec-status` skills and a provider config under
221
221
  `specs/.core/`). If none is present, skip this phase entirely — the spec stays
222
222
  local-only and `/spec` behaves exactly as above. When a provider is present, link
223
223
  the spec to the tracker after writing it, so status and discussion live there
224
- while the repo stays the co-authoring surface — follow the provider's link steps
224
+ while the repo stays the source of truth — follow the provider's link steps
225
225
  below (nothing to do here without one).
226
226
 
227
227
  **Only when `specs/.core/linear.config.json` exists** (Linear sync is opted in).
@@ -129,27 +129,25 @@ Before writing any code for this phase, get the workspace clean:
129
129
  next phase on top of an uncommitted one. (Skip if this is the first phase —
130
130
  there's nothing prior to commit.)
131
131
 
132
- ## 3b. Pull from the tracker first (only if a provider is installed)
132
+ ## 3b. Sync with the tracker (only if a provider is installed)
133
133
 
134
- **Only when a ticketing provider with a `/spec-pull` skill is installed** and the
135
- spec is linked to the tracker. Otherwise skip this step — no provider means zero
136
- change to the flow below. Follow the provider's pull steps below (nothing to do
137
- here without one).
134
+ **Only when a ticketing provider is installed** and the spec is linked to the
135
+ tracker. Otherwise skip this step — no provider means zero change to the flow
136
+ below. Follow the provider's steps below (nothing to do here without one).
138
137
 
139
138
  **Only when `specs/.core/linear.config.json` exists** and the spec carries a
140
- `linear_project_id` in its `00-overview.md` frontmatter. Otherwise skip this
141
- step — no config means zero change to the flow below.
142
-
143
- - **Run `/spec-pull` first.** Bring down anything Linear changed since the last
144
- sync (status, priority, discussion-driven fields) so you build against the
145
- current shared state, not a stale snapshot. On a conflict it refuses relay
146
- that and let the user resolve before continuing; do not `--force` for them.
147
- - **Commit the refreshed snapshot** into the feature branch (a small
148
- `chore(spec): pull latest from Linear`-style commit) so the frozen spec rides
149
- in the PR alongside the code it describes.
150
- - Linear's GitHub branch/PR automation may now drive status transitions off the
151
- branch and PR you pushed in step 2 — expect state to move on the Linear side;
152
- keep any manual status edits minimal to avoid fighting it.
139
+ `linear_project_id`. Otherwise skip this step no config means zero change.
140
+
141
+ - **No pull.** Linear is a generated mirror in one-way sync, so there is nothing
142
+ to bring down before building the repo is already the source of truth. (A
143
+ workflow-state a teammate moved in Linear is advisory only; `/spec-status`
144
+ surfaces it. It is overwritten on the next push.)
145
+ - **Optional: refresh the mirror.** If you want Linear to reflect the now
146
+ in-progress spec, run `/spec-push` to send it up. This is optional at
147
+ `/spec-go` time the mirror can equally be refreshed later.
148
+ - Linear's GitHub branch/PR automation may drive status transitions off the
149
+ branch/PR you pushed in step 2; that's expected and the repo still wins on the
150
+ next `/spec-push`.
153
151
 
154
152
  ## 4. Implement the phase
155
153