@skitterbyte/skitterspec-linear 10.5.2 → 10.7.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 (34) hide show
  1. package/assets/claude-md-section.md +19 -9
  2. package/assets/commands/spec-connect.md +13 -0
  3. package/assets/commands/spec-live.md +14 -0
  4. package/assets/core/ci-stages.md +110 -0
  5. package/assets/core/env.config.md +18 -0
  6. package/assets/core/linear.config.json.example +3 -0
  7. package/assets/core/linear.config.md +46 -0
  8. package/assets/rules/commit-trailers.md +84 -0
  9. package/assets/rules/spec-planning.md +17 -3
  10. package/assets/skills/spec/SKILL.md +20 -0
  11. package/assets/skills/spec-bug/SKILL.md +40 -2
  12. package/assets/skills/spec-cancel/SKILL.md +9 -0
  13. package/assets/skills/spec-complete/SKILL.md +12 -1
  14. package/assets/skills/spec-go/SKILL.md +60 -28
  15. package/assets/skills/spec-hotfix/SKILL.md +41 -3
  16. package/assets/skills/spec-linear-setup/SKILL.md +38 -2
  17. package/assets/skills/spec-status/SKILL.md +1 -0
  18. package/assets/skills/spec-sync/SKILL.md +27 -2
  19. package/assets/skills/spec-to-main/SKILL.md +2 -1
  20. package/bin/skitterspec-linear.js +10 -0
  21. package/package.json +1 -1
  22. package/src/cli.js +176 -39
  23. package/src/env/config.js +19 -0
  24. package/src/env/teardown.js +62 -4
  25. package/src/init.js +120 -2
  26. package/src/vendor/linear/cli-sync.js +560 -12
  27. package/src/vendor/linear/config.js +91 -1
  28. package/src/vendor/linear/doctor.js +67 -1
  29. package/src/vendor/linear/released.js +164 -0
  30. package/src/vendor/sync-core/index.js +4 -1
  31. package/src/vendor/sync-core/src/compare.js +59 -3
  32. package/src/vendor/sync-core/src/normalize.js +65 -2
  33. package/assets/skills/spec-connect/SKILL.md +0 -59
  34. package/assets/skills/spec-live/SKILL.md +0 -73
@@ -7,7 +7,8 @@ description: Promote a spec into active development and build the next phase —
7
7
 
8
8
  The "up" button: it promotes the spec, provisions its worktree, brings its host
9
9
  dev servers up on the spec's reserved ports (with your OK), then builds the phase.
10
- Diverting your browser to the spec is a separate explicit step `/spec-connect`.
10
+ Diverting your browser to the spec is a separate explicit step the **user**
11
+ types — `/spec-connect` (a slash command, not a skill you can invoke).
11
12
 
12
13
  ## 1. Identify the target spec
13
14
 
@@ -33,8 +34,8 @@ branch. Instead skip the provisioning bullets and step 2b, leave the spec where
33
34
  it is, and go straight to **step 4**, implementing the phase **in the primary
34
35
  checkout on the branch** — edits and commits there advance the branch, and
35
36
  `/spec-complete` lands them. (`spec-env up` refuses while live and says the same.
36
- To return to an isolated worktree instead, run `/spec-live main` first, then
37
- re-run `/spec-go`.)
37
+ To return to an isolated worktree instead, ask the user to type `/spec-live main`
38
+ first, then re-run `/spec-go`.)
38
39
 
39
40
  **If per-spec isolation is enabled** (`specs/.core/env.config.json` exists), the
40
41
  spec **isn't already live** (the check above), and it doesn't already have a
@@ -121,11 +122,12 @@ runnable — its UI/API on the spec's reserved port block, isolated from `main`.
121
122
  dev process detached on its port, logs to `.spec-env/logs/`, and waits on each
122
123
  `health` check. With no `dev` configured it's a clean no-op; skip this step.
123
124
  - **Diverting your browser is a separate step.** To test the spec at your normal
124
- `localhost` URL, run **`/spec-connect <name>`** (exclusive — it exposes this
125
- spec on the canonical ports; `/spec-connect main` hands them back). `/spec-go`
126
- never seizes the canonical ports on its own. For a **code-only** spec, the
127
- lighter **`/spec-live <name>`** reuses your already-running dev server (a
128
- branch-switch, no second stack) — `/spec-live main` hands it back.
125
+ `localhost` URL, the **user** types **`/spec-connect <name>`** (exclusive — it
126
+ exposes this spec on the canonical ports; `/spec-connect main` hands them back).
127
+ `/spec-go` never seizes the canonical ports on its own. For a **code-only** spec,
128
+ the lighter **`/spec-live <name>`** reuses the already-running dev server (a
129
+ branch-switch, no second stack) — `/spec-live main` hands it back. Both are
130
+ user-only slash commands: tell the user to run one, never try to invoke it.
129
131
 
130
132
  ## 3. Pre-flight — commit prior work
131
133
 
@@ -139,11 +141,17 @@ Before writing any code for this phase, get the workspace clean:
139
141
  next phase on top of an uncommitted one. (Skip if this is the first phase —
140
142
  there's nothing prior to commit.)
141
143
 
142
- ## 3b. Sync with the tracker (only if a provider is installed)
144
+ ## 4. Implement the phase
145
+
146
+ Identify the **first unfinished phase** from the `00-overview.md` phase index,
147
+ then open its phase file (`0N-<slug>.md`) — that file holds the tasks. Mark it
148
+ started: set the phase-file heading to `🔄` and its `> **Status:**` to
149
+ `In progress`, and flip the matching row in the overview phase index to `🔄`.
143
150
 
144
- **Only when a ticketing provider is installed** and the spec is linked to the
145
- tracker. Otherwise skip this step no provider means zero change to the flow
146
- below. Follow the provider's steps below (nothing to do here without one).
151
+ **Then sync with the tracker (only if a provider is installed).** The phase has
152
+ just changed state, so refresh the mirror before the build starts that is what
153
+ makes the phase show as in progress *while* it is being built rather than only
154
+ once it is over. Without a provider this is a no-op and nothing below changes.
147
155
 
148
156
  **Only when `specs/.core/linear.config.json` exists** and the spec carries a
149
157
  `linear_identifier`. Otherwise skip this step — no config means zero change.
@@ -152,26 +160,27 @@ below. Follow the provider's steps below (nothing to do here without one).
152
160
  to bring down before building — the repo is already the source of truth. (A
153
161
  workflow-state a teammate moved in Linear is advisory only; `/spec-status`
154
162
  surfaces it. It is overwritten on the next push.)
155
- - **Refresh the mirror.** Run `/spec-push` to send the now in-progress spec up.
156
- Whether that is optional depends on `mapping.phases`:
157
- - `"subissue"` (the default) **optional**. The phase sub-issues already
158
- exist; this push only moves their states. Refresh now or later.
159
- - `"deferred"` **do it now, without asking.** Under deferral a spec sitting
160
- in the backlog is mirrored as the issue alone, and this push is what mints
161
- its phase sub-issues. Skip it and a started spec stays mirrored as a
162
- phase-less issue until someone happens to run `/spec-push` by hand. Run it
163
- straight after the step-2 commit, so the sub-issues land with the same
164
- branch push that fires the tracker's automation.
163
+ - **Refresh the mirror now, without asking.** Run `/spec-push`. The spec has just
164
+ moved to `in-progress` and its phase to `🔄` — both real state changes, and the
165
+ tracker is a generated mirror of them. This holds under **both**
166
+ `mapping.phases` modes, for different reasons:
167
+ - `"subissue"` (the default) the phase sub-issues already exist, and this
168
+ push is what moves the current one into its in-progress state. Skip it and
169
+ every sub-issue sits in Backlog until the spec completes.
170
+ - `"deferred"` the sub-issues do not exist yet, and this push is what mints
171
+ them. Skip it and a started spec stays mirrored as a phase-less issue.
172
+ - **Never mint the spec issue.** An unlinked spec is skipped, not created —
173
+ `/spec-push` is how someone opts in.
174
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
175
+ and **carry on with the build**. The repo is correct regardless; the mirror is
176
+ disposable and the next push repairs it.
177
+ - **Expect a dirty `specs/.core/` afterwards.** The push writes a base snapshot
178
+ and stamps any new ids, and `/spec-go` does not commit. The next `/commit`
179
+ sweeps it up with the phase's own work.
165
180
  - Linear's GitHub branch/PR automation may drive status transitions off the
166
181
  branch/PR you pushed in step 2; that's expected and the repo still wins on the
167
182
  next `/spec-push`.
168
183
 
169
- ## 4. Implement the phase
170
-
171
- Identify the **first unfinished phase** from the `00-overview.md` phase index,
172
- then open its phase file (`0N-<slug>.md`) — that file holds the tasks. Mark it
173
- started: set the phase-file heading to `🔄` and its `> **Status:**` to
174
- `In progress`, and flip the matching row in the overview phase index to `🔄`.
175
184
  Then build it, following the project rules in `.claude/rules/*.md` and `CLAUDE.md`:
176
185
 
177
186
  - Work task by task through the phase file. Make focused edits that match
@@ -194,6 +203,29 @@ Then build it, following the project rules in `.claude/rules/*.md` and `CLAUDE.m
194
203
  - If new work surfaced, add it as tasks to the appropriate phase file (or add a
195
204
  new phase file + index row) rather than doing it silently.
196
205
 
206
+ **Then refresh the mirror (only if a provider is installed).** The phase is done
207
+ in the repo now; leaving the tracker to catch up at `/spec-complete` is what makes
208
+ a mirror lag a whole spec behind. Without a provider this is a no-op.
209
+
210
+ **Only when `specs/.core/linear.config.json` exists** and the spec carries a
211
+ `linear_identifier`. Either missing → **skip**, in one line
212
+ (`not linked to Linear — /spec-push to mirror it`), and carry on.
213
+
214
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
215
+ become the truth about this phase's progress, and progress is what the mirror
216
+ exists to show. Deferring it to `/spec-complete` is what makes every phase
217
+ sub-issue jump from Backlog straight to Done, with nothing visible in between.
218
+
219
+ - **Never mint.** An unlinked spec is skipped, not created.
220
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
221
+ and **finish the operation anyway**. The phase is done in the repo regardless;
222
+ the mirror is disposable and the next push repairs it.
223
+ - **Expect a dirty `specs/.core/` afterwards.** The push writes a base snapshot
224
+ and stamps any new ids, and these skills do not commit. The next `/commit`
225
+ sweeps it up with the phase's own work.
226
+ - **Say what happened** in the skill's report: mirror updated, skipped as
227
+ unlinked, or failed with the reason.
228
+
197
229
  ## 6. Report
198
230
 
199
231
  Summarise what was implemented, the test result (quote failures if any), and
@@ -155,8 +155,22 @@ that header — must exist **before** `spec-env up`:
155
155
  bootstrap steps.
156
156
  - Run the printed `git worktree add`. **The worktree is checked out at the tag,
157
157
  so your uncommitted stub doesn't travel with it** — move it across so `main`
158
- stays pristine:
159
- `mv specs/in-progress/hotfix-<name> <worktreePath>/specs/in-progress/`.
158
+ stays pristine. **Create the destination bucket first:**
159
+
160
+ ```
161
+ mkdir -p <worktreePath>/specs/in-progress
162
+ mv specs/in-progress/hotfix-<name> <worktreePath>/specs/in-progress/
163
+ ```
164
+
165
+ The `mkdir -p` is not belt-and-braces. Git does not store empty directories, so
166
+ `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`.
160
174
  - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
161
175
  none of the repo's gitignored files (`.env`, local overrides). Run the printed
162
176
  `in the worktree, run:` steps (file seeding, then setup) in order, before
@@ -318,6 +332,30 @@ message.
318
332
  - Commit the fix to the `hotfix/<slug>` branch (this commit is what gets tagged
319
333
  and cherry-picked). Tick the Fix tasks; add a Changelog line.
320
334
 
335
+ **Then refresh the mirror (only if a provider is installed).** The Fix tasks are
336
+ ticked, so the repo is now the truth about this fix — and this skill can take a
337
+ bug all the way to green without `/spec-go` ever running. Without a provider this
338
+ is a no-op.
339
+
340
+ **Only when `specs/.core/linear.config.json` exists** and the spec carries a
341
+ `linear_identifier`. Either missing → **skip**, in one line
342
+ (`not linked to Linear — /spec-push to mirror it`), and carry on.
343
+
344
+ **Refresh the mirror now, without asking.** Run `/spec-push`. The repo has just
345
+ become the truth about this phase's progress, and progress is what the mirror
346
+ exists to show. Deferring it to `/spec-complete` is what makes every phase
347
+ sub-issue jump from Backlog straight to Done, with nothing visible in between.
348
+
349
+ - **Never mint.** An unlinked spec is skipped, not created.
350
+ - **Never fatal.** If the push fails — offline, no key, a Linear error — say so
351
+ and **finish the operation anyway**. The phase is done in the repo regardless;
352
+ the mirror is disposable and the next push repairs it.
353
+ - **Expect a dirty `specs/.core/` afterwards.** The push writes a base snapshot
354
+ and stamps any new ids, and these skills do not commit. The next `/commit`
355
+ sweeps it up with the phase's own work.
356
+ - **Say what happened** in the skill's report: mirror updated, skipped as
357
+ unlinked, or failed with the reason.
358
+
321
359
  ## 7. Report
322
360
 
323
361
  Summarise: the base tag, root cause, the failing→passing test, the fix, and the
@@ -325,7 +363,7 @@ full test result. The spec stays in `in-progress`.
325
363
 
326
364
  - **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
327
365
  hot-reloading it onto the running dev server could break the shared instance.
328
- To test it, use `/spec-connect` (its own isolated stack).
366
+ To test it, the user runs `/spec-connect` (its own isolated stack).
329
367
  - Suggest **`/spec-complete`** to land it: it patch-bumps the base tag, tags the
330
368
  hotfix branch **locally** (you push it to deploy), and cherry-picks the fix onto
331
369
  `main`. Add `--also <tag>` at completion to also patch other release lines
@@ -142,7 +142,38 @@ never apply one they didn't agree to), and re-run with the `--state` flags. If i
142
142
  made no suggestion for a bucket, **ask** which state means "finished" here rather
143
143
  than guessing.
144
144
 
145
- ## 7. Write it
145
+ ## 7. Does this repo deploy through stages? (optional)
146
+
147
+ A spec's lifecycle ends at `complete`. Where a ticket goes **after** that —
148
+ deployed to test, approved for demo, live in production — is a fact about an
149
+ environment, and nothing in the repo can derive it. If the team runs a deploy
150
+ pipeline, it can declare that ladder here and CI moves tickets along it with
151
+ `spec-sync stage`.
152
+
153
+ **Ask, do not assume.** Most projects have no ladder, and an invented one is
154
+ worse than none: `spec-sync stage` refuses cleanly when none is declared, whereas
155
+ a wrong ladder is a pipeline quietly moving tickets to the wrong column. If the
156
+ user does not raise deployment, offer it once in a line and take "no" for an
157
+ answer.
158
+
159
+ If they do want one, ask for the stages **in deployment order**, and map each to
160
+ a state from the `list_issue_statuses` names you already have:
161
+
162
+ ```
163
+ --stage test="On Test" --stage demo="Ready for Demo" --stage prod="Done"
164
+ ```
165
+
166
+ - The **key** (`test`) is what a pipeline names; the **state** is the Linear
167
+ column. Pipelines reference the key, so renaming the column later is one edit
168
+ here.
169
+ - Order is recorded, not enforced — a rollback and a hotfix straight to prod are
170
+ both legitimate and are never refused.
171
+ - **Ask what closes an issue.** If the last rung is not a completed-type state,
172
+ tickets that finish the ladder never reach Done. That is fine when Linear
173
+ automation closes them and a problem otherwise — `spec-sync doctor` warns about
174
+ it either way, so say which it is.
175
+
176
+ ## 8. Write it
146
177
 
147
178
  ```
148
179
  skitterspec spec-sync init-config \
@@ -157,12 +188,17 @@ shows this repo's choices and keeps inheriting everything else. `--force` is
157
188
  required to replace an existing config — never pass it without the user having
158
189
  asked for a rewrite in step 1.
159
190
 
191
+ Every `--stage` state is checked against the workspace exactly like a bucket
192
+ state, and a bad one is refused the same way — as `release.stages[test]` rather
193
+ than `states.complete`. There is no suggestion for a rung: the vocabulary is the
194
+ project's own, so ask rather than guess.
195
+
160
196
  **Relay the engine's report as printed.** It names the team, the project (or
161
197
  "team only"), the intake labels, and how many state names were checked against
162
198
  the workspace — that report is the evidence the setup is right, so don't
163
199
  paraphrase it into "done".
164
200
 
165
- ## 8. Report and hand off
201
+ ## 9. Report and hand off
166
202
 
167
203
  **Finish by checking, not by describing.** First write down what the MCP server
168
204
  says, from the reads you already made in step 2 — no extra round trip unless a
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: spec-status
3
3
  description: Show a spec's one-way sync status against Linear — a read-only drift report. Reports whether the spec changed since the last push (there's something to push) and, optionally, whether Linear's issue workflow-state differs from the spec's. Fetches the Linear issue over MCP and runs `skitterspec spec-sync status`. Changes nothing. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-status", "is this spec in sync with Linear", "what would push", or "show spec sync status".
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # /spec-status — one-way sync drift report
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: spec-sync
3
3
  description: Run the repo-wide spec-sync operations against Linear — what's linked, the workspace's states and projects, read-back verification, manual stamping, and bulk `apply --all <bucket>`. Run it bare for the repo-wide overview. Wraps the `spec-sync` CLI, which is a local devDependency and never on PATH, so this skill always states the full invocation. Defers to /spec-push and /spec-status for per-spec work. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-sync", "run spec-sync", "what's linked to Linear", "which states does the workspace have", or "mirror the whole backlog".
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # /spec-sync — the repo-wide Linear sync operations
@@ -37,6 +38,8 @@ In a project that installs the Linear superset the binary is
37
38
  | "link this spec to KEY-1 by hand" | `stamp <spec> --issue KEY-1` |
38
39
  | "mirror the whole backlog / every complete spec" | `apply --all <bucket>` — **confirm first** |
39
40
  | "is the team key stale?", "did Linear get renamed?" | `retarget` |
41
+ | "what ticket am I on?", writing a commit | `ref` |
42
+ | "what shipped in this release?" | `released` |
40
43
  | push one spec, or "what would push?" | **defer** — see below |
41
44
 
42
45
  **With no argument, run `linked`.** It is the repo-wide overview, it is
@@ -131,7 +134,29 @@ its object exists, so an interrupted run continues rather than duplicating.
131
134
 
132
135
  `--all` refuses over MCP by design — bulk goes through the API path.
133
136
 
134
- ## 7. `retarget` after a Linear team is renamed
137
+ ## 7. `ref` and `released` tickets in commits
138
+
139
+ ```
140
+ pnpm exec skitterspec-linear spec-sync ref [--json]
141
+ pnpm exec skitterspec-linear spec-sync released [<range>] [--json]
142
+ ```
143
+
144
+ `ref` prints the ticket for the branch you are on, so a commit can carry
145
+ `Refs: <KEY-N>` — see `.claude/rules/commit-trailers.md`. **Off a spec branch, or
146
+ on a spec that is not linked, it prints nothing and exits non-zero.** That is the
147
+ correct answer, not an error to work around: do not invent a ref, and never write
148
+ `Refs: none`.
149
+
150
+ `released` reports the tickets in a commit range — by default since the most
151
+ recent tag, and it always prints the range it chose so a wrong default is
152
+ visible. It is **read-only**: it does not move anything in Linear. A release can
153
+ be cut and never deployed, so transitioning tickets stays a deliberate act.
154
+
155
+ Relay the **unreferenced count** even when it is zero. A chore commit
156
+ legitimately carries no ticket and a *missed* trailer looks identical, so
157
+ omitting the number reads as "everything is accounted for" when it may not be.
158
+
159
+ ## 8. `retarget` — after a Linear team is renamed
135
160
 
136
161
  ```
137
162
  pnpm exec skitterspec-linear spec-sync retarget [--yes]
@@ -160,7 +185,7 @@ rewrites those fields. Read-only until `--yes`.
160
185
  Over MCP the team key is unreadable (`get_team` does not return it), so it says
161
186
  so and asks you to confirm the key rather than guessing.
162
187
 
163
- ## 8. Report
188
+ ## 9. Report
164
189
 
165
190
  Relay the engine's output. Name the subcommand you ran, in full, so the user can
166
191
  re-run it themselves. For anything that wrote, say what changed in Linear and
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: spec-to-main
3
3
  description: Land an in-progress spec's branch onto main WITHOUT finishing it — rebase + fast-forward so the work reaches main (to run it in CI / a shared test env), while the worktree stays and the spec stays In Progress. Repeatable — land again as you commit more. Targets a spec by name (arg) or the spec in context. Use when the user says "/spec-to-main", "land this on main but keep going", "I need this on main to run tests before finishing", or "merge to main without completing the spec".
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # /spec-to-main — land the branch on main, keep the spec open
@@ -51,7 +52,7 @@ uncommitted changes, offer `/commit` and **stop**; don't auto-commit.
51
52
  back to base, re-isolates it into its worktree, clears the receipt), then prints
52
53
  the landing plan. Commit any live fixes to the branch first; it refuses if the
53
54
  primary checkout is dirty, or if a *different* spec holds it (release that one with
54
- `/spec-live main`).
55
+ `/spec-live main`, which the user types).
55
56
 
56
57
  ## 3. Tests must be green before landing
57
58
 
@@ -41,6 +41,16 @@ const {
41
41
  async function main(argv) {
42
42
  const [cmd, ...rest] = argv
43
43
 
44
+ // The check above asks whether src/ exists, which is inert in a workspace source
45
+ // package — src/ is committed there. That is exactly where the other half of the
46
+ // problem lives: a source package HAS a runnable bin and src, but its assets/ is
47
+ // PRE-composition (seam markers still literal). Installing from it writes those
48
+ // markers into the user's skills. So ask a second, positive question before any
49
+ // install command runs.
50
+ if (cmd === 'init' || cmd === 'update') {
51
+ require('../src/init.js').assertComposedAssets()
52
+ }
53
+
44
54
  // Base help + what this distribution adds. Matched on the COMMAND SLOT only,
45
55
  // never the whole argv: `spec-sanitise --help` must reach that command's own
46
56
  // help, not be swallowed by the top-level one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec-linear",
3
- "version": "10.5.2",
3
+ "version": "10.7.0",
4
4
  "description": "Spec-driven development for Claude Code, with one-way Linear sync — a superset of @skitterbyte/skitterspec: the base filesystem workflow plus /spec-status · /spec-push and the spec-sync CLI. The repo is canonical; Linear is a generated mirror. Install this OR the base, not both.",
5
5
  "keywords": [
6
6
  "claude",