@skitterbyte/skitterspec-linear 3.4.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,6 +12,45 @@ npx @skitterbyte/skitterspec-linear init
12
12
 
13
13
  Install **this OR the base**, never both — this package contains the entire base.
14
14
 
15
+ ## Quick start
16
+
17
+ Get sync working in four steps (the scaffolded `specs/.core/SETUP.md` is the
18
+ fuller guide):
19
+
20
+ 1. **Install** into your repo:
21
+
22
+ ```sh
23
+ npx @skitterbyte/skitterspec-linear init
24
+ ```
25
+
26
+ 2. **Connect the Linear MCP server** (once per machine) — the skills talk to
27
+ Linear over MCP, so this is the prerequisite people miss:
28
+
29
+ ```sh
30
+ claude mcp add --transport http linear https://mcp.linear.app/mcp
31
+ ```
32
+
33
+ Then, in Claude Code, run `/mcp` → select **linear** → **Authenticate** (a
34
+ browser opens; pick your workspace). A freshly added server only shows up after
35
+ Claude Code restarts — relaunch with `claude --continue` if you don't see it.
36
+ Verify with `claude mcp list` (want `linear … ✓`).
37
+
38
+ 3. **Configure** — copy the scaffolded example and fill in your team id (ask
39
+ Claude "list my Linear teams" once connected to get it):
40
+
41
+ ```jsonc
42
+ // specs/.core/linear.config.json
43
+ { "linear": { "teamId": "<your-team-uuid>" } }
44
+ ```
45
+
46
+ That file is the opt-in gate — until it exists, everything below is inert and
47
+ the package behaves exactly like the base.
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).
53
+
15
54
  ## What the superset adds
16
55
 
17
56
  On top of the base skills (`/spec`, `/spec-go`, isolation, …):
@@ -39,14 +78,15 @@ behaves exactly like the base.
39
78
  setup guide — connecting the `linear` MCP server, finding your team id, linking a
40
79
  spec, and a smoke test. Per-field docs live in `specs/.core/linear.config.md`.
41
80
 
42
- **What syncs today:** the whole spec body travels as the Linear Project
81
+ **What syncs:** by default the whole spec body travels as the Linear Project
43
82
  **`description`** (co-authored, push + pull); **status / priority / labels** are
44
83
  Linear-owned (pull only). **Field ownership** (`both` / `pull` / `push`) collapses
45
84
  conflicts — only a `both` field that moved on both sides is a real conflict, and
46
- `--force` backs up the losing side before winning. A per-**Milestone** /
47
- per-**Issue** round-trip (phases and tasks as first-class Linear objects) is a
48
- planned extensionsee `linear.config.md`. **Base sidecars**
49
- (`specs/.core/linear-base/`) are committed; **backups**
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**
50
90
  (`specs/.core/linear-backups/`) are gitignored.
51
91
 
52
92
  Branch naming that embeds the Linear id lives in the isolation config
@@ -124,11 +124,17 @@ in-repo → `/spec-push` (send content up).
124
124
  | `priority` | Linear | pull only |
125
125
  | `labels` | Linear | pull only |
126
126
 
127
- The **entire spec body travels as the project `description`** — phases and
128
- acceptance criteria included. A separate per-milestone / per-issue round-trip
129
- (editing an individual phase in Linear and pulling just that back) is **not yet
130
- implemented**; opt in later by adding `milestones`/`phaseBodies`/`taskBreakdown`
131
- to `sync.fieldOwnership` once that write path exists.
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`.
132
138
 
133
139
  Sections listed in `sync.localOnlySections` (default: **State log**, **Changelog**,
134
140
  **Open questions**) are stripped from the pushed description — they never leave
@@ -34,5 +34,8 @@
34
34
  "guards": {
35
35
  "refuseTeardownIfDirty": true,
36
36
  "refuseTeardownIfUnpushed": true
37
+ },
38
+ "live": {
39
+ "migrations": []
37
40
  }
38
41
  }
@@ -146,6 +146,15 @@ no live `env.config.json` was found.
146
146
  "guards": {
147
147
  "refuseTeardownIfDirty": true,
148
148
  "refuseTeardownIfUnpushed": true
149
+ },
150
+
151
+ // Live overlay (`spec-env live` / `/spec-live`): test a spec on the already-
152
+ // running dev server by checking its branch out in the primary checkout.
153
+ // `migrations` is a list of globs (`**`, `*`, `?`) marking migration files; a
154
+ // branch that changes any of them is treated as STATEFUL and `live take`
155
+ // refuses it (code-only v1 — use `/spec-connect` for those). Default: none.
156
+ "live": {
157
+ "migrations": []
149
158
  }
150
159
  }
151
160
  ```
@@ -30,6 +30,7 @@
30
30
  "priority": "pull",
31
31
  "labels": "pull"
32
32
  },
33
- "localOnlySections": ["State log", "Changelog", "Open questions"]
33
+ "localOnlySections": ["State log", "Changelog", "Open questions"],
34
+ "keyedFields": {}
34
35
  }
35
36
  }
@@ -92,11 +92,48 @@ absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error
92
92
 
93
93
  // Markdown sections of 00-overview.md that are local-only scaffolding and
94
94
  // are stripped from the pushed `description` (never sent to Linear).
95
- "localOnlySections": ["State log", "Changelog", "Open questions"]
95
+ "localOnlySections": ["State log", "Changelog", "Open questions"],
96
+
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.
100
+ "keyedFields": {}
96
101
  }
97
102
  }
98
103
  ```
99
104
 
105
+ ## Body round-trip (milestones & tasks) — opt-in
106
+
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:
109
+
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
121
+ 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.
132
+
133
+ Unlinked local items (a new phase with no `linear_milestone_id`, a task with no
134
+ inline id) are created in Linear on the next `/spec-push`, which stamps the new id
135
+ back so they link from then on.
136
+
100
137
  ## Field ownership & conflicts
101
138
 
102
139
  The spec is a set of structured fields, most written by only one side. Marking a
@@ -39,6 +39,20 @@ changes only when it merges. Teardown is folded into `/spec-complete` ·
39
39
  lifecycle status** and inactive when `env.config.json` is absent — every skill
40
40
  then behaves as it does today.
41
41
 
42
+ **Live overlay (`/spec-live`) — the light way to test a spec.** `/spec-connect`
43
+ runs a spec's *own* dev stack and proxies the canonical ports to it (one stack per
44
+ spec). **Live overlay** instead reuses the one dev server you already have running:
45
+ `/spec-live <spec>` rebases the branch onto base, frees it from its worktree, and
46
+ checks it out **in the primary checkout**, so your running server hot-reloads the
47
+ feature at the normal URL — no second stack, no proxy. The branch checked out in
48
+ the primary checkout **is** the lock: exactly one spec is live at a time, and
49
+ `/spec-live main` hands the instance back (fixes you make while live commit
50
+ straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
51
+ thumb: **live overlay is the light default for code-only specs**; it *refuses*
52
+ stateful ones (`Stack: worktree + docker`, or a branch touching migrations) — keep
53
+ `/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
54
+ Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
55
+
42
56
  **Ticketing-provider sync (opt-in, a separate package).** The base is
43
57
  tracker-free: it knows nothing about any specific ticketing system. A
44
58
  ticketing provider is installed as its own distribution that plugs into two named
@@ -62,6 +62,12 @@ applies, offer to land the finished branch on the base branch so the work reache
62
62
  1. **Require a clean worktree.** The completion edits (status flip, the
63
63
  `git mv` to `complete/`) must be committed first — integrate refuses a dirty
64
64
  tree. If it's dirty, offer `/commit` and **stop**; don't auto-commit.
65
+ **If the spec is live** (you took the running instance with `/spec-live`):
66
+ `integrate` is live-aware — it ends the live session first (releases the branch
67
+ back to base, re-isolates it into its worktree, clears the receipt), then prints
68
+ the normal landing plan. Commit any live fixes to the branch first; it refuses
69
+ if the primary checkout is dirty, or if a *different* spec holds it (release that
70
+ one with `/spec-live main`). Teardown (step 7) is unchanged.
65
71
  2. **Plan + execute.** Run `skitterspec spec-env integrate <name>` and run the
66
72
  printed commands **in order**:
67
73
  - `git -C <worktree> rebase <base>` — replay the branch onto base.
@@ -15,6 +15,12 @@ This skill is **opt-in**: it needs `specs/.core/env.config.json` with a `dev`
15
15
  block (host dev servers + their `frontPort`s). If isolation or `dev` is absent,
16
16
  say so and stop.
17
17
 
18
+ **Lighter alternative for a code-only spec:** `/spec-live` reuses the dev server
19
+ you already have running (it branch-switches the primary checkout) instead of
20
+ starting a second stack — no proxy, one process. Prefer it for code-only specs;
21
+ use `/spec-connect` when a spec has its own Docker stack, or to run several stacks
22
+ in parallel.
23
+
18
24
  ## 1. Identify the target
19
25
 
20
26
  - Use the spec named as an argument. The literal `main` means **disconnect**
@@ -98,7 +98,9 @@ runnable — its UI/API on the spec's reserved port block, isolated from `main`.
98
98
  - **Diverting your browser is a separate step.** To test the spec at your normal
99
99
  `localhost` URL, run **`/spec-connect <name>`** (exclusive — it exposes this
100
100
  spec on the canonical ports; `/spec-connect main` hands them back). `/spec-go`
101
- never seizes the canonical ports on its own.
101
+ never seizes the canonical ports on its own. For a **code-only** spec, the
102
+ lighter **`/spec-live <name>`** reuses your already-running dev server (a
103
+ branch-switch, no second stack) — `/spec-live main` hands it back.
102
104
 
103
105
  ## 3. Pre-flight — commit prior work
104
106
 
@@ -13,6 +13,14 @@ clobber customised content. Finish with a summary of created vs already-present.
13
13
  > all of the below mechanically. This skill is the manual/repair path and is
14
14
  > useful when the package isn't available or you only need to fix part of the
15
15
  > setup.
16
+ >
17
+ > **Re-running on an already-set-up repo is safe.** `init` detects the existing
18
+ > setup (via the committed `specs/.core/.skitterspec-manifest.json` of installed
19
+ > file hashes) and, interactively, offers three paths — **Resync** (update managed
20
+ > skills/rules to the latest but keep files you've edited), **Start again** (reset
21
+ > the managed scaffolding fresh — never your specs or config, with a confirm), or
22
+ > **Leave alone**. Non-interactively it only adds what's missing; `--resync` /
23
+ > `--reset` (reset needs `--yes`) drive the stronger actions.
16
24
 
17
25
  The system is **eight skills**: `spec` (feature), `spec-bug` (bug), `spec-ready`,
18
26
  `spec-review`, `spec-go`, `spec-complete`, `spec-cancel`, and this `spec-init`. The lifecycle is
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: spec-live
3
+ description: Test a spec on your already-running dev server by checking its branch out in the primary checkout — no second stack, no proxy. `spec-live <spec>` takes the running instance for that spec; `spec-live main` releases it. Runs `skitterspec spec-env live`. Opt-in — needs specs/.core/env.config.json. Code-only specs; stateful (Docker/migration) specs use /spec-connect. Use when the user says "/spec-live", "go live with <spec>", "take the instance for <spec>", or "test <spec> on the running server".
4
+ ---
5
+
6
+ # /spec-live — put one spec live on the running instance
7
+
8
+ Instead of running a second dev stack for a spec (that's `/spec-connect`), **reuse
9
+ the one instance you already have**: rebase the spec's branch onto base, hand it
10
+ from its worktree to the **primary checkout**, and let your running dev server
11
+ hot-reload it. You test at your normal URL, with one process. The branch that's
12
+ checked out in the primary checkout **is** the lock — exactly one spec is live at
13
+ a time, and `/spec-live main` hands the instance back.
14
+
15
+ This skill is **opt-in**: it needs `specs/.core/env.config.json`. If isolation is
16
+ absent, say so and stop.
17
+
18
+ **Code-only.** Live overlay refuses a **stateful** spec — one whose `> **Stack:**`
19
+ is `worktree + docker`, or whose branch changes migrations (per
20
+ `env.config.json` → `live.migrations`). Those keep their isolated stack; use
21
+ `/spec-connect` for them. The engine enforces this and prints why.
22
+
23
+ ## 1. Identify the target
24
+
25
+ - Use the spec named as an argument. The literal `main` means **release** (hand
26
+ the instance back to base). Else use the spec **currently in context**; if
27
+ unclear, ask.
28
+
29
+ ## 2. Make sure a dev server is running
30
+
31
+ `live take` **verifies** a dev server is up on your canonical ports and switches
32
+ the branch under it — it does **not** start one. If nothing is listening it
33
+ refuses; start your dev server first (however you normally run it, or
34
+ `skitterspec spec-env dev up <spec>`). (Projects with no `dev` servers configured
35
+ have nothing to hot-reload — the switch still happens, with a warning.)
36
+
37
+ ## 3. Take (or release)
38
+
39
+ ```
40
+ skitterspec spec-env live take <spec> # rebase → detach worktree → checkout in primary
41
+ skitterspec spec-env live release # hand the instance back to base, re-isolate the branch
42
+ skitterspec spec-env live abort # crash recovery (see below)
43
+ skitterspec spec-env live status # who's live (branch in the primary checkout + receipt)
44
+ ```
45
+
46
+ **Take** rebases the branch onto base, frees it from its worktree
47
+ (`switch --detach`), checks it out in the primary checkout, then writes a receipt
48
+ (`.spec-env/live.json`). Relay its output. **If it reports the rebase hit
49
+ conflicts**, it left everything untouched — rebase the branch in its worktree,
50
+ resolve, then retry. **If it says a spec already holds the instance**, release it
51
+ first. If it warns dependencies changed, restart your dev server after the switch.
52
+
53
+ **Release** (`/spec-live main`) is the graceful exit of an unfinished session:
54
+ `skitterspec spec-env live release` reads the live spec from the receipt, checks
55
+ base back out in the primary checkout, re-attaches the branch to its worktree, and
56
+ clears the receipt. Commit any fixes to the branch first — it refuses on a dirty
57
+ tree rather than discard them. (To *finish* a live spec instead of releasing it,
58
+ use `/spec-complete`, which is live-aware.)
59
+
60
+ **Abort** is crash recovery, for when a session died mid-take and left the primary
61
+ checkout on a feature branch: `skitterspec spec-env live abort` restores base from
62
+ the receipt and re-isolates. It refuses if the primary checkout has uncommitted
63
+ changes (it won't discard them) — commit or stash first.
64
+
65
+ ## 4. Report
66
+
67
+ Echo which spec is now live on the primary checkout (and any warning), that it was
68
+ released / recovered, or — for `status` — which branch the primary checkout is on
69
+ and whether the instance is free. Fixes you make while live commit straight onto
70
+ the spec's branch.
@@ -23,7 +23,10 @@ Use the argument, else the spec in context; ask if unclear.
23
23
  spec isn't linked — stop and point at `/spec`.
24
24
  - Discover the Linear MCP project-read tool at runtime. If Linear isn't
25
25
  connected, relay the fix and stop — **do nothing destructive**.
26
- - Call it and write the project JSON to a temp file.
26
+ - Call it (include milestones) and write the project JSON to a temp file. When
27
+ tasks are keyed, also list the project's issues and add them as an `issues`
28
+ array on that JSON (each `{ identifier, title, state }`) so the engine can
29
+ reconcile task lines.
27
30
 
28
31
  ## 3. Run the engine
29
32
 
@@ -47,6 +47,27 @@ tool with that field's local value (e.g. `description` → the project descripti
47
47
  The engine has already vetted the change and moved the base — so if a Linear
48
48
  write fails, re-run `/spec-pull` to reconcile rather than retrying blindly.
49
49
 
50
+ **Milestones (`milestonesPush` in the result).** When milestones are keyed, the
51
+ engine can't write them itself — apply the plan over MCP:
52
+
53
+ - `update`: for each `{ id, name, goal }`, call the milestone-save tool with that
54
+ `id` (name → milestone name, goal → its description).
55
+ - `create`: for each `{ name, goal }`, call the milestone-save tool with no id to
56
+ create it under the project, then **stamp the returned milestone id** into the
57
+ matching phase file's frontmatter (`linear_milestone_id`) so it links on the
58
+ next sync. Match the phase file by its title.
59
+
60
+ Progress is Linear-derived — never push it.
61
+
62
+ **Issues (`issuesPush` in the result).** When tasks are keyed:
63
+
64
+ - `update`: for each `{ id, text, done }`, call the issue-save tool with that `id`
65
+ (text → title; `done` → a completed state, else a non-completed state — leave an
66
+ already-non-completed issue's exact state untouched).
67
+ - `create`: for each `{ text, done }`, create an issue under the project (attach it
68
+ to the milestone of the phase the task lives in when known), then **stamp the new
69
+ issue identifier inline** on that task line (`… (SKI-123)`), matching by text.
70
+
50
71
  ## 5. Report
51
72
 
52
73
  Relay the git-like summary (written / skipped / backup / base) plus which Linear
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec-linear",
3
- "version": "3.4.0",
3
+ "version": "5.0.0",
4
4
  "description": "Spec-driven development for Claude Code, with Linear hybrid-sync — a superset of @skitterbyte/skitterspec: the base filesystem workflow plus git-like /spec-status · /spec-pull · /spec-push and the spec-sync CLI. Install this OR the base, not both.",
5
5
  "keywords": [
6
6
  "claude",