@skitterbyte/skitterspec-linear 14.0.0 → 17.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/MIGRATION.md +304 -4
- package/README.md +34 -1
- package/assets/claude-md-section.md +29 -18
- package/assets/commands/spec-remote-review.md +22 -0
- package/assets/core/SETUP.md +10 -6
- package/assets/core/env.config.json.example +4 -2
- package/assets/core/env.config.md +103 -25
- package/assets/core/linear.config.json.example +2 -1
- package/assets/core/linear.config.md +49 -22
- package/assets/review/page.html +1101 -108
- package/assets/rules/spec-planning.md +39 -7
- package/assets/rules/spec-reports.md +210 -31
- package/assets/skills/spec/SKILL.md +161 -4
- package/assets/skills/spec-bug/SKILL.md +102 -49
- package/assets/skills/spec-cancel/SKILL.md +2 -2
- package/assets/skills/spec-claim/SKILL.md +12 -4
- package/assets/skills/spec-complete/SKILL.md +2 -2
- package/assets/skills/spec-diff/SKILL.md +183 -39
- package/assets/skills/spec-hotfix/SKILL.md +96 -49
- package/assets/skills/spec-init/SKILL.md +18 -6
- package/assets/skills/spec-linear-setup/SKILL.md +19 -11
- package/assets/skills/spec-next/SKILL.md +147 -62
- package/assets/skills/spec-push/SKILL.md +45 -0
- package/assets/skills/spec-review/SKILL.md +89 -2
- package/assets/skills/spec-reviewed/SKILL.md +31 -5
- package/assets/skills/spec-start/SKILL.md +26 -3
- package/assets/skills/spec-status/SKILL.md +20 -6
- package/assets/skills/spec-sync/SKILL.md +1 -0
- package/package.json +2 -2
- package/src/cli.js +940 -116
- package/src/env/classify.js +87 -2
- package/src/env/config.js +214 -17
- package/src/env/hooks.js +49 -9
- package/src/env/live.js +94 -0
- package/src/env/resolve.js +36 -2
- package/src/env/review.js +581 -21
- package/src/env/serve.js +298 -19
- package/src/env/supervise.js +8 -1
- package/src/init.js +88 -13
- package/src/vendor/linear/api.js +111 -2
- package/src/vendor/linear/cli-sync.js +661 -11
- package/src/vendor/linear/config.js +41 -13
- package/src/vendor/linear/doctor.js +6 -5
- package/src/vendor/sync-core/index.js +11 -3
- package/src/vendor/sync-core/src/compare.js +65 -0
- package/src/vendor/sync-core/src/normalize.js +26 -0
- package/src/vendor/sync-core/src/retarget.js +1 -1
- package/src/vendor/sync-core/src/task-block.js +2 -2
- /package/assets/hooks/{review-gate.js → review-gate.cjs} +0 -0
|
@@ -120,8 +120,8 @@ Linear API key is set (see `apply.transport` in `linear.config.md`).
|
|
|
120
120
|
Do not roll anything back, and do not stop to ask.
|
|
121
121
|
- **Say what happened** in the skill's report: mirror updated, skipped as
|
|
122
122
|
unlinked, or failed with the reason.
|
|
123
|
-
- **There is no unassign step, and that is not an omission.**
|
|
124
|
-
`
|
|
123
|
+
- **There is no unassign step, and that is not an omission.** Where the repo owns
|
|
124
|
+
`assignee`, the projection derives it from the spec's lifecycle
|
|
125
125
|
bucket — so moving the folder to `complete`/`cancelled` is *itself* what
|
|
126
126
|
releases the issue, and the push above carries it like any other field. The
|
|
127
127
|
`linear_assignee_id` stamp and the `> **Developer:**` header both stay put:
|
|
@@ -146,6 +146,67 @@ names it. This is why the commit no longer stages `specs/` wholesale: the one
|
|
|
146
146
|
file outside the spec's folder that had to be swept up is addressable, so the
|
|
147
147
|
breadth bought nothing and cost another session's work.
|
|
148
148
|
|
|
149
|
+
## 4b. Hand the refresh back on a page, then wait
|
|
150
|
+
|
|
151
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
152
|
+
present) **and this review actually changed something.** Either missing → skip
|
|
153
|
+
the whole step in silence.
|
|
154
|
+
|
|
155
|
+
A refreshed spec is a rewrite of a document someone is about to work from, so it
|
|
156
|
+
gets read before it is committed — the same ending `/spec` now has:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
skitterspec spec-env review <spec> --docs --buttons refresh
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`--docs` reads the spec's own documents from the tree you are standing in, so it
|
|
163
|
+
wants no worktree and works for a spec in any bucket. These documents are
|
|
164
|
+
**tracked**, so the diff against `HEAD` is a patch rather than a set of new
|
|
165
|
+
files — which is exactly what the reader wants here: what drifted, and what you
|
|
166
|
+
rewrote.
|
|
167
|
+
|
|
168
|
+
**Render nothing when nothing changed.** `/spec-review` frequently concludes a
|
|
169
|
+
spec is still accurate, and that is a success rather than a gap. A page showing
|
|
170
|
+
an empty diff under a commit button asks for a verdict on nothing, and the
|
|
171
|
+
engine refuses it for the same reason — `nothing to review`. Take that refusal
|
|
172
|
+
as the answer and report the `⏸`/`✅` you already had.
|
|
173
|
+
|
|
174
|
+
**Arm nothing.** No `review arm` here: the gate asserts that a phase which ended
|
|
175
|
+
owes an answer, and a re-validated spec has ended no phase. Walking away leaves
|
|
176
|
+
an uncommitted spec, which is the state this skill has always finished in.
|
|
177
|
+
|
|
178
|
+
### The three endings
|
|
179
|
+
|
|
180
|
+
`--buttons refresh` offers `Commit`, `Request changes` and `Discuss`.
|
|
181
|
+
|
|
182
|
+
**No start verdict, deliberately.** The spec this skill refreshed may already be
|
|
183
|
+
in progress — that is half of what it is used for — so `Commit & Start` would
|
|
184
|
+
offer to provision a worktree for a spec that already has one.
|
|
185
|
+
|
|
186
|
+
- **`commit`** — hand off to `review.commitWith` (`/commit` by default) with the
|
|
187
|
+
pathspec the render reported on `docs.paths`, then finish. The spec keeps
|
|
188
|
+
whatever status step 4 left it in.
|
|
189
|
+
- **`changes`** — work the notes into the spec, record a resolution for each so
|
|
190
|
+
the next render strikes it through with what changed, re-render, and wait
|
|
191
|
+
again.
|
|
192
|
+
- **`discuss`** — report and talk. Claim nothing, change nothing.
|
|
193
|
+
|
|
194
|
+
### Then wait for it
|
|
195
|
+
|
|
196
|
+
**The wait is a command. Do not write one.** Note the moment you start waiting
|
|
197
|
+
as an ISO timestamp, run the engine's wait in the background, and end your turn:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
skitterspec spec-env review wait <spec> --since <the timestamp>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Pass no timeout. On waking, let the engine pick the pass —
|
|
204
|
+
`skitterspec spec-env review <spec> --docs --claim-since <the timestamp> --json`
|
|
205
|
+
— and route on the verdict above.
|
|
206
|
+
|
|
207
|
+
**Where the page is `file://`** the pass is copied and pasted, so the wait is the
|
|
208
|
+
turn ending and the banner says so. Do not start a watch that cannot fire.
|
|
209
|
+
|
|
149
210
|
## 5. Report
|
|
150
211
|
|
|
151
212
|
Do **not** `git commit` unless the user asks.
|
|
@@ -166,6 +227,32 @@ the shape; this section carries only what is specific here.
|
|
|
166
227
|
API that moved, a task the code already does. `Spec` carries the status the
|
|
167
228
|
review leaves it in, which is the answer to "can I start this?".
|
|
168
229
|
|
|
230
|
+
**Where step 4b rendered a page the run is waiting**, so the block omits the
|
|
231
|
+
`Review` row and ends on the banner from `.claude/rules/spec-reports.md` — the
|
|
232
|
+
tier stack copied from the engine's own lines, never one link chosen for the
|
|
233
|
+
reader:
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## ⏸ Review ready — <N> files, +<a> −<d>
|
|
238
|
+
|
|
239
|
+
- **local** — <the `local:` URL>
|
|
240
|
+
- **network** — <the `network:` URL, or off with the command that turns it on>
|
|
241
|
+
- **remote** — <the `remote:` URL, or off with the command that turns it on>
|
|
242
|
+
|
|
243
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
244
|
+
|
|
245
|
+
`Commit` commits the refresh · `Request changes` works them now
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
`Next` then names the page rather than a command, because the button is what
|
|
250
|
+
carries the work on.
|
|
251
|
+
|
|
252
|
+
Where nothing was rendered — no isolation, or no drift to show — `Next` is
|
|
253
|
+
`/commit, then /spec-start <name>`, since this skill does not commit and
|
|
254
|
+
`/spec-start` refuses a tree holding anything that is not the spec's.
|
|
255
|
+
|
|
169
256
|
**Drift found and not fixed is a `Follow-up`, not a silence.** A spec this skill
|
|
170
257
|
declared reviewed is one nobody will re-read; something it noticed and left
|
|
171
258
|
undone has to leave the session in writing or it did not happen.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-reviewed
|
|
3
|
-
description: Pick up the review you approved on the page — run it bare to pick up the single waiting pass, paste the six-digit code off the page ("/spec-reviewed 608223") to name one exactly,
|
|
3
|
+
description: Pick up the review you approved on the page — run it bare to pick up the single waiting pass, paste the six-digit code off the page ("/spec-reviewed 608223") to name one exactly, or pass the verdict itself ("/spec-reviewed commit") when the page could not send and copied you a command instead. Use when the user says "/spec-reviewed", "I approved it", "I've reviewed it", "pick up my review", "I pressed approve", or otherwise says they have finished reviewing a rendered diff.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -70,16 +70,42 @@ Several provisioned and none resolved is a refusal — relay its list and stop,
|
|
|
70
70
|
never pick from it. See `.claude/rules/spec-planning.md`; do not restate the
|
|
71
71
|
rule here.
|
|
72
72
|
|
|
73
|
-
**
|
|
74
|
-
matches `^\d{6}$`; a **
|
|
75
|
-
**
|
|
76
|
-
|
|
73
|
+
**Four argument shapes, and they cannot collide.** A **six-digit code**
|
|
74
|
+
matches `^\d{6}$`; a **verdict** is one of a closed list of six words;
|
|
75
|
+
a **tracker id** carries a letter and a hyphen;
|
|
76
|
+
a **spec name** is none of them and always carries a lifecycle prefix
|
|
77
|
+
(`feat-`, `bug-`, `hotfix-`), which no verdict does. So the parse needs no flag,
|
|
78
|
+
and nothing has to be guessed at from context.
|
|
77
79
|
|
|
78
80
|
A **six-digit code** is a pass the operator read off their own page. It says
|
|
79
81
|
**which pass**, not which spec — so resolve the spec exactly as a bare
|
|
80
82
|
invocation does, above, and claim the code *there*. Then go to step 4 — a named
|
|
81
83
|
pass has nothing to disambiguate.
|
|
82
84
|
|
|
85
|
+
A **verdict word** — `commit`, `commit-continue`, `commit-start`, `continue`,
|
|
86
|
+
`changes`, `discuss` — is the conclusion itself, arriving without a pass behind
|
|
87
|
+
it. It is
|
|
88
|
+
what a **`file://` page** hands over: that page has no server to POST to and no
|
|
89
|
+
store to write to, so it copies a command instead of sending anything, and this
|
|
90
|
+
is the command. Resolve the spec as a bare invocation does, then send the word
|
|
91
|
+
through the engine:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
skitterspec spec-env review <spec> --verdict <word>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
It joins the same merge a claimed pass goes through, so the routing in step 4
|
|
98
|
+
is unchanged: a commit over open notes is refused exactly as it would be, the
|
|
99
|
+
outcome log records it, and the gate a phase armed is cleared. Then go to step
|
|
100
|
+
4 — there is nothing to disambiguate.
|
|
101
|
+
|
|
102
|
+
**A word carries a verdict and nothing else**, and that is a property of the
|
|
103
|
+
transport, not a shortcut. Accepts and comments do not fit on a command line,
|
|
104
|
+
so the page only ever offers the words while the reader has marked nothing —
|
|
105
|
+
the moment they tick an accept or write a note it goes back to handing over the
|
|
106
|
+
blob. If someone tells you they marked things up *and* gives you a word, ask
|
|
107
|
+
for the pass: the word would land a verdict with their notes silently dropped.
|
|
108
|
+
|
|
83
109
|
A **name** targets that spec instead. A **tracker id** does too, but only
|
|
84
110
|
through a **provider seam**: the base knows nothing about tracker ids, so
|
|
85
111
|
resolve one by asking whichever provider is installed for its listing —
|
|
@@ -233,10 +233,14 @@ branch is already here.
|
|
|
233
233
|
- Set the **Status** header: `> **Status:** In Progress — Phase 1 (started <YYYY-MM-DD>)`.
|
|
234
234
|
- Set **Developer** if it is still `—` (`git config user.name`).
|
|
235
235
|
|
|
236
|
-
**Only when all three hold**: `specs/.core/linear.config.json` exists,
|
|
237
|
-
`
|
|
236
|
+
**Only when all three hold**: `specs/.core/linear.config.json` exists, the repo
|
|
237
|
+
**owns** `assignee` in its `sync.fieldOwnership`, and the spec carries a
|
|
238
238
|
`linear_identifier`. Any one missing → skip this step silently and carry on; a
|
|
239
|
-
project that
|
|
239
|
+
project that declined must see no trace of assignment.
|
|
240
|
+
|
|
241
|
+
Ownership is the **value**, not the key: `assignee` is owned by default, and
|
|
242
|
+
`"none"` is how a project declines it. Testing whether the key is *present* would
|
|
243
|
+
read as opted-in everywhere, since the default puts it in every config.
|
|
240
244
|
|
|
241
245
|
**Never blocks, never fails the skill.** Everything below is best-effort: the
|
|
242
246
|
branch is provisioned and the spec is moving either way, and an unassigned issue
|
|
@@ -371,6 +375,25 @@ here beyond knowing it is no longer dead code.
|
|
|
371
375
|
A spec ideally arrives `Ready` from `/spec`; a `Draft` works too — sanity-check
|
|
372
376
|
it is well-formed first.
|
|
373
377
|
|
|
378
|
+
- **Report anything left waiting, and claim none of it.** Ask the engine once:
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
skitterspec spec-env review waiting
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
A pass listed here arrived when nothing was watching — a wait that never ran,
|
|
385
|
+
a session cleared, a terminal closed overnight — and no watcher can recover
|
|
386
|
+
those, however good. Relay what it prints: the spec, the code, the verdict and
|
|
387
|
+
the age, with `/spec-reviewed <code>` to pick one up and `--drop <code>` to
|
|
388
|
+
disown it.
|
|
389
|
+
|
|
390
|
+
**It is information, not a gate.** It never refuses, nothing here is
|
|
391
|
+
conditional on it, and **you never claim one** — `/spec-diff` §0 stands
|
|
392
|
+
unchanged, and a pass sitting there when you arrived was not sent to you.
|
|
393
|
+
Silent when nothing is waiting, which is the usual case: say nothing rather
|
|
394
|
+
than reporting that there was nothing to report.
|
|
395
|
+
|
|
396
|
+
|
|
374
397
|
## 4b. Note a missing gating decision (only if configured)
|
|
375
398
|
|
|
376
399
|
**Only when `specs/.core/gating.config.json` exists.** Run
|
|
@@ -33,9 +33,20 @@ Use the argument, else the spec in context; ask if unclear.
|
|
|
33
33
|
## 2. Fetch the Linear issue (optional, for drift)
|
|
34
34
|
|
|
35
35
|
If the spec has a `linear_identifier`, discover the Linear MCP read tool
|
|
36
|
-
(`get_issue`) and write the issue JSON to a temp file
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
(`get_issue`) and write the issue JSON to a temp file. If Linear isn't connected,
|
|
37
|
+
skip the drift lines (still report pending-push).
|
|
38
|
+
|
|
39
|
+
**Write the whole issue, including its `description`.** The engine reports two
|
|
40
|
+
independent kinds of drift off that one file: the workflow-state someone moved,
|
|
41
|
+
and the **description someone edited**. Write only the state fields and the
|
|
42
|
+
second check has nothing to look at — and it says nothing rather than guessing,
|
|
43
|
+
so the loss is silent. Keep `url` too: it is what the description line points
|
|
44
|
+
the reader at.
|
|
45
|
+
|
|
46
|
+
**The description itself never enters the conversation.** The engine hashes it
|
|
47
|
+
and compares hashes; what it prints is *that* the text changed, plus where to
|
|
48
|
+
read it. Do not open the temp file, and do not paste the description into your
|
|
49
|
+
report.
|
|
39
50
|
|
|
40
51
|
Optionally fetch the workspace issue-state names to a file to validate the
|
|
41
52
|
configured `states` at the same time.
|
|
@@ -81,9 +92,12 @@ the shape; this section carries only what is specific here.
|
|
|
81
92
|
**Verdicts**
|
|
82
93
|
|
|
83
94
|
- `✅` — in sync; nothing would push.
|
|
84
|
-
- `⚠️` — drift: N objects would push,
|
|
85
|
-
|
|
86
|
-
reason someone
|
|
95
|
+
- `⚠️` — drift: N objects would push, Linear's workflow state was moved by hand,
|
|
96
|
+
or the issue's **description** was edited on Linear since the last push. None
|
|
97
|
+
is an error — the repo wins on the next push — but all are the reason someone
|
|
98
|
+
ran this. **Say which**, since the three call for different things: a pending
|
|
99
|
+
push is routine, a moved state is usually someone else's automation, and an
|
|
100
|
+
edited description means a person wrote something that the next push replaces.
|
|
87
101
|
- `⏸` — no config, or the spec is not linked. Nothing to compare.
|
|
88
102
|
|
|
89
103
|
**Fields:** `Tracker` · `Follow-ups` · `Next`
|
|
@@ -42,6 +42,7 @@ In a project that installs the Linear superset the binary is
|
|
|
42
42
|
| "what specs are there?", "what's in the backlog?", "what is Jane on?" | `list` — or hand to `/spec-list` |
|
|
43
43
|
| "did the mirror survive the push?" | `verify <spec> --stored <file>` |
|
|
44
44
|
| "link this spec to KEY-1 by hand" | `stamp <spec> --issue KEY-1` |
|
|
45
|
+
| "this spec lost its link", "a push made a duplicate issue" | `reattach <spec>` — or `--to KEY-1` to name one |
|
|
45
46
|
| "mirror the whole backlog / every complete spec" | `apply --all <bucket>` — **confirm first** |
|
|
46
47
|
| "is the team key stale?", "did Linear get renamed?" | `retarget` |
|
|
47
48
|
| "what ticket am I on?", writing a commit | `ref` |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec-linear",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Spec-driven development for Claude Code, with one-way Linear sync
|
|
3
|
+
"version": "17.0.0",
|
|
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",
|
|
7
7
|
"claude-code",
|