@skitterbyte/skitterspec-azure-boards 0.0.0-stage → 1.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 +1126 -0
- package/README.md +55 -2
- package/assets/claude-md-section.md +85 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/commands/spec-remote-review.md +37 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/SETUP.md +214 -0
- package/assets/core/azure-boards.config.json.example +35 -0
- package/assets/core/azure-boards.config.md +43 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +557 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/hooks/main-guard.cjs +162 -0
- package/assets/hooks/review-gate.cjs +146 -0
- package/assets/review/page.html +3384 -0
- package/assets/rules/commit-trailers.md +97 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +692 -0
- package/assets/rules/spec-reports.md +557 -0
- package/assets/skills/no-spec/SKILL.md +246 -0
- package/assets/skills/spec/SKILL.md +719 -0
- package/assets/skills/spec-azure-setup/SKILL.md +227 -0
- package/assets/skills/spec-bug/SKILL.md +647 -0
- package/assets/skills/spec-cancel/SKILL.md +262 -0
- package/assets/skills/spec-claim/SKILL.md +122 -0
- package/assets/skills/spec-complete/SKILL.md +449 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +669 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-list/SKILL.md +177 -0
- package/assets/skills/spec-next/SKILL.md +686 -0
- package/assets/skills/spec-push/SKILL.md +304 -0
- package/assets/skills/spec-review/SKILL.md +257 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +554 -0
- package/assets/skills/spec-status/SKILL.md +112 -0
- package/assets/skills/spec-sync/SKILL.md +221 -0
- package/assets/skills/spec-to-main/SKILL.md +139 -0
- package/bin/skitterspec-azure-boards.js +62 -0
- package/package.json +41 -4
- package/src/cli.js +5907 -0
- package/src/deprecate.js +138 -0
- package/src/env/bootstrap.js +188 -0
- package/src/env/building.js +143 -0
- package/src/env/classify.js +176 -0
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +753 -0
- package/src/env/dev.js +63 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +411 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +126 -0
- package/src/env/live.js +504 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +225 -0
- package/src/env/prune.js +119 -0
- package/src/env/registry.js +159 -0
- package/src/env/render.js +17 -0
- package/src/env/resolve.js +716 -0
- package/src/env/review.js +2577 -0
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +1044 -0
- package/src/env/supervise.js +157 -0
- package/src/env/teardown.js +248 -0
- package/src/env/trust.js +87 -0
- package/src/gating.js +155 -0
- package/src/init.js +1150 -0
- package/src/lines-diff.js +114 -0
- package/src/prompts.js +129 -0
- package/src/vendor/azure-boards/api.js +561 -0
- package/src/vendor/azure-boards/cli-sync.js +1428 -0
- package/src/vendor/azure-boards/commands.js +44 -0
- package/src/vendor/azure-boards/config.js +365 -0
- package/src/vendor/azure-boards/credentials.js +109 -0
- package/src/vendor/azure-boards/dialect.js +100 -0
- package/src/vendor/azure-boards/identity.js +40 -0
- package/src/vendor/azure-boards/mcp.js +133 -0
- package/src/vendor/provider-kit/index.js +41 -0
- package/src/vendor/provider-kit/src/adapter.js +23 -0
- package/src/vendor/provider-kit/src/apply.js +207 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +198 -0
- package/src/vendor/provider-kit/src/credentials.js +404 -0
- package/src/vendor/provider-kit/src/doctor.js +35 -0
- package/src/vendor/provider-kit/src/http.js +63 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/md-compare.js +39 -0
- package/src/vendor/provider-kit/src/md-tables.js +94 -0
- package/src/vendor/provider-kit/src/prompt.js +50 -0
- package/src/vendor/provider-kit/src/released.js +269 -0
- package/src/vendor/provider-kit/src/releases.js +380 -0
- package/src/vendor/provider-kit/src/specs.js +166 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +93 -0
- package/src/vendor/sync-core/src/base.js +81 -0
- package/src/vendor/sync-core/src/compare.js +338 -0
- package/src/vendor/sync-core/src/dialect.js +51 -0
- package/src/vendor/sync-core/src/fields.js +36 -0
- package/src/vendor/sync-core/src/legacy.js +100 -0
- package/src/vendor/sync-core/src/normalize.js +998 -0
- package/src/vendor/sync-core/src/push.js +85 -0
- package/src/vendor/sync-core/src/retarget.js +278 -0
- package/src/vendor/sync-core/src/task-block.js +308 -0
- package/src/vendor/sync-core/src/verify.js +67 -0
- package/src/vendor/sync-core/src/write.js +191 -0
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,1126 @@
|
|
|
1
|
+
# Migration guide
|
|
2
|
+
|
|
3
|
+
## `@skitterbyte/skitterspec` v23 → v24 (hotfix land runs the release itself)
|
|
4
|
+
|
|
5
|
+
### Breaking change 1 — `spec-env hotfix land` runs its steps, and holds before the carry
|
|
6
|
+
|
|
7
|
+
`hotfix land` used to print a plan: a `git tag` for the patch-bumped base tag,
|
|
8
|
+
the `--also` cherry-picks, and a `git cherry-pick <base>..<branch>` onto main.
|
|
9
|
+
The skill ran it by hand, and you pushed the tag yourself.
|
|
10
|
+
**It now runs the land itself**, stopping at the first step that fails:
|
|
11
|
+
|
|
12
|
+
1. **release** — `hotfix.releaseWith` when it is set (see below), otherwise the
|
|
13
|
+
built-in tagger. The release is recorded in `.spec-env/hotfix/<spec>.json`,
|
|
14
|
+
so a re-run reuses it rather than cutting a second one.
|
|
15
|
+
2. **push** — the branch, then the tag, to `origin`, **only with `--push`**.
|
|
16
|
+
Without it the run **holds** here: exit 0, both push commands printed, and
|
|
17
|
+
nothing carried. A re-run after you push by hand finds the tag on the remote
|
|
18
|
+
and carries on.
|
|
19
|
+
3. **carry** — the branch's commits onto main, **minus the version commit** and
|
|
20
|
+
anything main already has, so the spec's completion commits arrive and main's
|
|
21
|
+
version line does not move. It runs in place when main is checked out and
|
|
22
|
+
clean, through a throwaway worktree when it isn't checked out, and is
|
|
23
|
+
**refused** with the command to run later when main is checked out and dirty.
|
|
24
|
+
A conflict is aborted and names the commit.
|
|
25
|
+
|
|
26
|
+
`--plan` describes the steps and runs nothing; `--json` reports each step's
|
|
27
|
+
outcome. Refusals and failures now exit 1.
|
|
28
|
+
|
|
29
|
+
**What to change.** Run `skitterspec update` to take the new `/spec-complete`
|
|
30
|
+
and `/spec-hotfix`. `/spec-complete` asks before passing `--push`. Anything of
|
|
31
|
+
your own that ran `hotfix land`'s printed commands should run the verb itself,
|
|
32
|
+
with `--push` once you have decided to deploy.
|
|
33
|
+
|
|
34
|
+
### Breaking change 2 — a hotfix's worktree waits for `spec-env hotfix finish`
|
|
35
|
+
|
|
36
|
+
`/spec-complete` used to tear a hotfix down as soon as it had tagged. The branch
|
|
37
|
+
is now kept until the deploy is confirmed, because a failed deploy cuts the next
|
|
38
|
+
release from the same branch. Run `/spec-complete` again on the landed hotfix,
|
|
39
|
+
or `skitterspec spec-env hotfix finish <spec>` directly. It removes the worktree
|
|
40
|
+
and deletes the branch only when the tree is clean, a release tag is at HEAD,
|
|
41
|
+
and `origin/<branch>` is at HEAD. It names every check that fails, and has no
|
|
42
|
+
`--force`.
|
|
43
|
+
|
|
44
|
+
### New — `hotfix.releaseWith` and a real `hotfix.bump`
|
|
45
|
+
|
|
46
|
+
- **`hotfix.releaseWith`** is a command run in the hotfix worktree. It cuts the
|
|
47
|
+
release (a version commit and a tag) and prints one JSON line:
|
|
48
|
+
`{"tag","version","baseTag","branch","versionCommit","pushed"}`. skittership
|
|
49
|
+
2.1+ ships one:
|
|
50
|
+
|
|
51
|
+
```jsonc
|
|
52
|
+
"hotfix": { "releaseWith": "node scripts/release-hotfix.cjs --json" }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Call node directly, not `pnpm run`, so no run banner reaches stdout. Anything
|
|
56
|
+
but the JSON line is a refusal. Add `--allow-postversion` yourself if your
|
|
57
|
+
package.json has a `postversion` script. `init` and `update` suggest this when
|
|
58
|
+
the script is present, and never write it.
|
|
59
|
+
- **`hotfix.bump`** was parsed and ignored; it is now read. `"patch"` (the
|
|
60
|
+
default) is the old behaviour. `"hotfix-suffix"` cuts `X.Y.Z-hotfix.N`,
|
|
61
|
+
continues a base that is already a hotfix tag, and is not blocked by a later
|
|
62
|
+
`X.Y.(Z+1)`. Any other value is refused when `hotfix land` runs.
|
|
63
|
+
|
|
64
|
+
Extra targets (`hotfix.targets` / `--also`) stay tag-only: they never go through
|
|
65
|
+
`releaseWith` and are not pushed.
|
|
66
|
+
|
|
67
|
+
## `@skitterbyte/skitterspec` v22 → v23 (the engine runs the worktree bootstrap)
|
|
68
|
+
|
|
69
|
+
### Breaking change — `spec-env up` no longer prints the worktree steps
|
|
70
|
+
|
|
71
|
+
`spec-env up` (and `spec-env nospec`) used to end with a
|
|
72
|
+
`then, in the worktree, run:` block of shell commands: one per seeded file, the
|
|
73
|
+
env-file write, each `setup` command, and `docker compose … up -d`. They now end
|
|
74
|
+
with one line instead:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
then, run:
|
|
78
|
+
skitterspec spec-env bootstrap <name>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**`spec-env bootstrap [<spec>]` runs those steps itself**, with the worktree
|
|
82
|
+
as its cwd: it seeds the gitignored files, writes `docker.envFile`, runs `setup`
|
|
83
|
+
and brings the stack up, in that order. The behaviour is the same as before: it
|
|
84
|
+
is idempotent, prints `seeded …` / `exists — skipped` / `not in main — skipped`
|
|
85
|
+
per seed, refuses to write through a symlinked env file, and stops at the first
|
|
86
|
+
failing step, naming it. `--docs` seeds only. `--plan` describes the steps
|
|
87
|
+
without running them, and `--json` reports each step and its outcome.
|
|
88
|
+
|
|
89
|
+
**Why.** The printed commands had to be copied word for word, and agents did
|
|
90
|
+
not copy them faithfully. A project's secrets hook refused any Bash call naming
|
|
91
|
+
a `.env` file, so every rewritten seed command was blocked and bootstrap stopped
|
|
92
|
+
half-way. The new command line names no file.
|
|
93
|
+
|
|
94
|
+
**What to change.** The shipped skills already call `bootstrap`; run
|
|
95
|
+
`skitterspec update` to take them. Anything of your own that ran the printed
|
|
96
|
+
block (a script, or a skill you edited) should run
|
|
97
|
+
`skitterspec spec-env bootstrap <name>` after the `git worktree add` instead.
|
|
98
|
+
Nothing in `env.config.json` changes.
|
|
99
|
+
|
|
100
|
+
**If you added a secrets-hook exception for the printed seed commands**, you
|
|
101
|
+
can remove it now. Nothing shipped prints them any more.
|
|
102
|
+
|
|
103
|
+
## `@skitterbyte/skitterspec-azure-boards` v0 → v1 (first release)
|
|
104
|
+
|
|
105
|
+
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
106
|
+
of `@skitterbyte/skitterspec` — install exactly one of the base or one
|
|
107
|
+
superset. Coming from the base, run `npx @skitterbyte/skitterspec-azure-boards
|
|
108
|
+
update` and run `/spec-azure-setup`
|
|
109
|
+
(or `skitterspec-azure-boards spec-sync init-config --org-url … --project …`).
|
|
110
|
+
|
|
111
|
+
**Coming from `@skitterbyte/skitterspec-linear` or `-jira`** — a repo moving
|
|
112
|
+
trackers: swap the distribution, configure Azure as above, set
|
|
113
|
+
`refs.foreignPatterns` to the old team key (e.g. `"ERQ-\\d+"`), then push each
|
|
114
|
+
backlog and in-progress spec (`/spec-push`, or `spec-sync apply --all
|
|
115
|
+
in-progress` and `--all backlog`) to link it to a new work item. Finished specs
|
|
116
|
+
keep their old stamps (`linear_identifier`, …) as history; nothing rewrites
|
|
117
|
+
them. Old `Refs: ERQ-12` trailers stay in the log, and `spec-sync released`
|
|
118
|
+
counts them as `historical` rather than as missing a ref. New commits carry
|
|
119
|
+
`Refs: AB#n`. Delete the old `linear.config.json` / `jira.config.json` once the
|
|
120
|
+
open specs are linked.
|
|
121
|
+
|
|
122
|
+
## `@skitterbyte/skitterspec-github` v0 → v1 (first release)
|
|
123
|
+
|
|
124
|
+
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
125
|
+
of `@skitterbyte/skitterspec` — install exactly one of the base or one
|
|
126
|
+
superset. Coming from the base, run `npx @skitterbyte/skitterspec-github
|
|
127
|
+
update`, make a Projects v2 board for the repo, and run `/spec-github-setup`
|
|
128
|
+
(or `skitterspec-github spec-sync init-config --owner … --repo … --project N`).
|
|
129
|
+
|
|
130
|
+
**Coming from `@skitterbyte/skitterspec-linear` or `-jira`** — a repo moving
|
|
131
|
+
trackers: swap the distribution, configure GitHub as above, then push each
|
|
132
|
+
backlog and in-progress spec (`/spec-push`, or `spec-sync apply --all
|
|
133
|
+
in-progress` and `--all backlog`) to link it to a new issue. Finished specs keep
|
|
134
|
+
their old stamps (`linear_identifier`, …) as history; nothing rewrites them.
|
|
135
|
+
Old `Refs: SKS-12` trailers stay in the log, and `spec-sync released` counts
|
|
136
|
+
them as `historical` rather than as missing a ref. New commits carry
|
|
137
|
+
`Refs: #N`. Delete the old `linear.config.json` / `jira.config.json` once the
|
|
138
|
+
open specs are linked.
|
|
139
|
+
|
|
140
|
+
## `@skitterbyte/skitterspec-jira` v0 → v1 (first release)
|
|
141
|
+
|
|
142
|
+
Nothing to migrate: this is the distribution's first release. It is a superset
|
|
143
|
+
of `@skitterbyte/skitterspec` — install exactly one of the base, the Linear
|
|
144
|
+
superset, or this one. Coming from the base, run `npx
|
|
145
|
+
@skitterbyte/skitterspec-jira update` and then `skitterspec-jira spec-sync
|
|
146
|
+
init-config --site-url … --project-key …` (or the `/spec-jira-setup` skill) to
|
|
147
|
+
opt into Jira sync. Coming from `@skitterbyte/skitterspec-linear`: the two
|
|
148
|
+
supersets share one credentials store and one base workflow, but a repo syncs
|
|
149
|
+
to one tracker — swap distributions only if the repo is moving trackers.
|
|
150
|
+
|
|
151
|
+
## `@skitterbyte/skitterspec` v21 → v22 (a review port per repo, and every render serves)
|
|
152
|
+
|
|
153
|
+
### Breaking change 1 — `review.serveOnRemote` is now `review.serve`
|
|
154
|
+
|
|
155
|
+
**Every render now stands the review server up**, and hands back an `http://`
|
|
156
|
+
URL instead of a `file://` one. It used to serve only when it detected that you
|
|
157
|
+
were reading from somewhere else — and that detection reads three environment
|
|
158
|
+
variables, so a plain local terminal got a `file://` page. A `file://` page has
|
|
159
|
+
**no server to POST to**, so the verdict buttons on it had nowhere to go: the
|
|
160
|
+
press-a-button-and-the-work-continues loop was missing from exactly the sessions
|
|
161
|
+
that are easiest to use.
|
|
162
|
+
|
|
163
|
+
**Which interfaces it listens on is `review.allowNetwork`** — see breaking
|
|
164
|
+
change 3, below. The change here is narrower than it looks: `file://` →
|
|
165
|
+
`http://127.0.0.1`, a page that opens on the machine holding it and, unlike
|
|
166
|
+
`file://`, can answer. Serving more never means listening wider; that is one
|
|
167
|
+
setting, and it is not this one.
|
|
168
|
+
|
|
169
|
+
**What to change.** `review.serveOnRemote: false` is still read as the new
|
|
170
|
+
`review.serve: "never"`, so a config that turned serving off keeps working. The
|
|
171
|
+
key is renamed because the old name would now claim to govern remote renders
|
|
172
|
+
while governing every one of them:
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
// specs/.core/env.config.json — before
|
|
176
|
+
"review": { "serveOnRemote": false }
|
|
177
|
+
// after
|
|
178
|
+
"review": { "serve": "never" }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`serveOnRemote: true` needs no translation — `"always"` is the default and does
|
|
182
|
+
what it did. Note what `"never"` costs you: the page falls back to `file://`,
|
|
183
|
+
and its buttons copy a command for you to paste rather than sending anything.
|
|
184
|
+
|
|
185
|
+
**A render that could not serve now says why** — `review.serve is "never"`, or
|
|
186
|
+
the failure naming the port to free — on the `open:` line and in `--json` as
|
|
187
|
+
`notServed`. A `file://` link with nothing said about it used to be the ordinary
|
|
188
|
+
outcome; now it can only mean the ask did not land.
|
|
189
|
+
|
|
190
|
+
**A spec with no worktree is served too.** A spec you have only just written has
|
|
191
|
+
no branch of its own, and the served route used to refuse it — so its page
|
|
192
|
+
existed and 404'd. It is now served as its own documents, listed in the index,
|
|
193
|
+
and accepts a verdict.
|
|
194
|
+
|
|
195
|
+
### Breaking change 2 — a review port per repo
|
|
196
|
+
|
|
197
|
+
**`review.servePort` now defaults to `"auto"`, not `7777`.** A repo that does
|
|
198
|
+
not pin the key moves to a port derived from its own path —
|
|
199
|
+
`7700 + hash(realpath(repoRoot)) % 100` — the first time `spec-env review serve`
|
|
200
|
+
starts after the upgrade.
|
|
201
|
+
|
|
202
|
+
**The one-time break is a link, and only a link.** A review page open on a
|
|
203
|
+
phone at that moment points at `7777`, and `7777` is no longer this repo's
|
|
204
|
+
server. The verdict you press there goes nowhere. **Re-render the page** —
|
|
205
|
+
`/spec-diff`, or `/spec-next`'s own render — and the new link works. Nothing on
|
|
206
|
+
disk is lost, no pass is dropped, and this happens once.
|
|
207
|
+
|
|
208
|
+
**Why not simply keep 7777.** It was shared by every repo on the machine, so the
|
|
209
|
+
second repo to start was refused, whoever hit that passed `--port`, and the
|
|
210
|
+
links they had already handed out pointed at another repo's daemon — a `404`,
|
|
211
|
+
reported in a grey line in the page footer. A derived port is stable for a given
|
|
212
|
+
tree across a restart, a reboot and a `--stop`, which is what a link handed out
|
|
213
|
+
yesterday needs.
|
|
214
|
+
|
|
215
|
+
### To keep a fixed port
|
|
216
|
+
|
|
217
|
+
Pin it, and nothing derives:
|
|
218
|
+
|
|
219
|
+
```jsonc
|
|
220
|
+
// specs/.core/env.config.json
|
|
221
|
+
"review": { "servePort": 7777 }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
An explicit number always wins, exactly as it did before. Pin one when you want
|
|
225
|
+
a port you can memorise, or when two repos happen to derive the same one — a
|
|
226
|
+
hundred slots is a small chance of that, not no chance, and the server still
|
|
227
|
+
refuses a busy port rather than moving itself aside.
|
|
228
|
+
|
|
229
|
+
`spec-env review serve --status` now prints the port **and which of the three chose it** —
|
|
230
|
+
`--port`, `review.servePort`, or the derivation.
|
|
231
|
+
|
|
232
|
+
### Breaking change 3 — the reader no longer decides the bind: `review.allowNetwork` does
|
|
233
|
+
|
|
234
|
+
**Two new keys, and one of them changes a default.** The engine used to guess
|
|
235
|
+
where you were reading and pick a surface for you. It guessed wrong three
|
|
236
|
+
separate ways in one day — a `file://` page on a session detected `unknown`, a
|
|
237
|
+
LAN URL for a phone that had left the network, and an address that changed
|
|
238
|
+
underneath a reader when detection flipped mid-session. So it stopped guessing:
|
|
239
|
+
every render now lists every tier, labelled, in a fixed order.
|
|
240
|
+
|
|
241
|
+
```jsonc
|
|
242
|
+
// specs/.core/env.config.json — the new keys, with their defaults
|
|
243
|
+
"review": {
|
|
244
|
+
"allowNetwork": true, // the server binds every interface, so the page opens on your phone
|
|
245
|
+
"allowRemote": false // publishing is permitted at all — it permits, it never publishes
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**`allowNetwork` defaults to `true`, and that is the behaviour change to read before upgrading.**
|
|
250
|
+
Before, a local or unknown reader got a loopback bind and a
|
|
251
|
+
remote one got every interface; now the setting decides, and its default is the
|
|
252
|
+
wide bind — which is what a remote reader already got, applied to every render.
|
|
253
|
+
The URL still carries the **serve token**, 48 random bits minted per server,
|
|
254
|
+
which remains the only thing deciding who can POST a verdict. A machine you
|
|
255
|
+
would rather not expose sets `"allowNetwork": false`, and `network:` then reads
|
|
256
|
+
`off` with the command that turns it back on.
|
|
257
|
+
|
|
258
|
+
**`allowRemote` defaults to `false`** and permits publishing without performing
|
|
259
|
+
it — a published page is one skitterspec cannot delete, so it stays an explicit
|
|
260
|
+
ask either way.
|
|
261
|
+
|
|
262
|
+
**`review.reader` still exists** and now decides only how a page's location is
|
|
263
|
+
*worded*. It no longer chooses the bind and no longer chooses which tiers appear.
|
|
264
|
+
|
|
265
|
+
### `/spec-remote-review` — a new slash command
|
|
266
|
+
|
|
267
|
+
Toggles `review.allowRemote`, and takes `on`/`off` when you would rather say
|
|
268
|
+
which. It exists because a render's `remote:` line has to name something you can
|
|
269
|
+
act on, and `skitterspec spec-env review allow remote` is not what anyone
|
|
270
|
+
reconstructs from a page they are reading on a phone. `init` and `update`
|
|
271
|
+
install it with the other commands; like them it is user-only, so nothing but you
|
|
272
|
+
can run it.
|
|
273
|
+
|
|
274
|
+
**It writes `specs/.core/env.config.json` in the primary checkout** — a
|
|
275
|
+
committed file, so it changes for everyone who pulls and leaves that tree dirty.
|
|
276
|
+
The engine says so when it does.
|
|
277
|
+
|
|
278
|
+
## `@skitterbyte/skitterspec-linear` v18 → v19 (hotfix land runs the release itself)
|
|
279
|
+
|
|
280
|
+
The same change as `@skitterbyte/skitterspec` v23 → v24 above — this
|
|
281
|
+
distribution composes the same engine. Read that entry; nothing here is
|
|
282
|
+
Linear-specific.
|
|
283
|
+
|
|
284
|
+
## `@skitterbyte/skitterspec-linear` v17 → v18 (the engine runs the worktree bootstrap)
|
|
285
|
+
|
|
286
|
+
The same change as `@skitterbyte/skitterspec` v22 → v23 above — this
|
|
287
|
+
distribution composes the same engine. Read that entry; nothing here is
|
|
288
|
+
Linear-specific.
|
|
289
|
+
|
|
290
|
+
## `@skitterbyte/skitterspec-linear` v16 → v17 (a review port per repo, and every render serves)
|
|
291
|
+
|
|
292
|
+
The same two changes as `@skitterbyte/skitterspec` v21 → v22 above — this
|
|
293
|
+
distribution composes the same engine. Read that entry; nothing here is
|
|
294
|
+
Linear-specific.
|
|
295
|
+
|
|
296
|
+
**If you are coming from v15, read the v15 → v16 entry below as well.** 16.0.0
|
|
297
|
+
was bumped in the repo and never published, so npm went 15 → 17 and both
|
|
298
|
+
entries apply to you.
|
|
299
|
+
|
|
300
|
+
## `@skitterbyte/skitterspec-linear` v15 → v16 (assignment is on by default)
|
|
301
|
+
|
|
302
|
+
### Breaking change
|
|
303
|
+
|
|
304
|
+
**A spec's Linear issue is now assigned to whoever is building it.**
|
|
305
|
+
`sync.fieldOwnership.assignee` defaults to `"push"`; it was absent
|
|
306
|
+
before, and absent meant inert. From this version `/spec-start` stamps the
|
|
307
|
+
developer on the spec and the push assigns the issue, and `/spec-complete`
|
|
308
|
+
releases it — on every repo that has not said otherwise.
|
|
309
|
+
|
|
310
|
+
**To decline it**, one line in `specs/.core/linear.config.json`:
|
|
311
|
+
|
|
312
|
+
```jsonc
|
|
313
|
+
"sync": {
|
|
314
|
+
"fieldOwnership": {
|
|
315
|
+
"assignee": "none"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`none` is a new value in the ownership enum (`both|pull|push|none`) and means
|
|
321
|
+
*this repo does not own the field*: nothing is written, no hash is recorded in a
|
|
322
|
+
snapshot, and `spec-sync status` prints no assignee line — identical in every
|
|
323
|
+
respect to the old behaviour of never listing it. It has to be a value rather
|
|
324
|
+
than an omitted key, because the map merges **per key** onto the defaults: once
|
|
325
|
+
a field is owned by default, there is nothing an absent key can subtract.
|
|
326
|
+
|
|
327
|
+
**An explicit `"assignee": "push"` you already had is now redundant and harmless.**
|
|
328
|
+
Nothing rewrites it and nothing needs to.
|
|
329
|
+
|
|
330
|
+
### It cannot overwrite a PM's triage, and that is by construction
|
|
331
|
+
|
|
332
|
+
The concern this default has to answer is whether upgrading re-assigns or
|
|
333
|
+
un-assigns issues someone else owns. It cannot, for two reasons that predate it:
|
|
334
|
+
|
|
335
|
+
- **A spec that records no assignee sends none.** Only a spec the repo itself
|
|
336
|
+
stamped is ever pushed, so an issue a PM assigned in Linear is left alone.
|
|
337
|
+
- **A snapshot with no assignee key means "never pushed", not "was null".** Every
|
|
338
|
+
spec linked before this existed has exactly such a snapshot, and the diff emits
|
|
339
|
+
a *clear* only against an assignee it has a recorded hash for.
|
|
340
|
+
|
|
341
|
+
So the flip widens who gets **assigned** — specs you start from now on — and
|
|
342
|
+
cannot widen who gets **unassigned**. There is a regression test pinning this
|
|
343
|
+
(`sync-assignee-projection.test.js`, "THE UPGRADE CASE").
|
|
344
|
+
|
|
345
|
+
### `spec-sync init-config --assign` is gone; `--no-assign` replaces it
|
|
346
|
+
|
|
347
|
+
The flag's meaning inverted with the default, so it is removed rather than kept
|
|
348
|
+
as a silent no-op — a flag that still parses and does nothing would read as
|
|
349
|
+
"assignment configured" to anyone who wrote it before v16. `--assign` now fails
|
|
350
|
+
as an unknown flag, which is the louder and more useful answer. `--no-assign`
|
|
351
|
+
writes the `"none"` opt-out above.
|
|
352
|
+
|
|
353
|
+
### `spec-sync assign` now refuses a field the repo does not own
|
|
354
|
+
|
|
355
|
+
Previously it stamped the frontmatter in any repo and printed
|
|
356
|
+
`next: push it, so Linear agrees` — while `toFieldSet` dropped the field, so the
|
|
357
|
+
push never carried it. It now refuses, naming `sync.fieldOwnership.assignee` and
|
|
358
|
+
the value that enables it. Only `/spec-claim` enforced this before, so a script
|
|
359
|
+
or a direct CLI call got the untrue sentence.
|
|
360
|
+
|
|
361
|
+
## `@skitterbyte/skitterspec` v20 → v21 (the gate installs, and asking implies waiting)
|
|
362
|
+
|
|
363
|
+
If you upgraded to v20 and the commit gate never once fired, this is why. Two
|
|
364
|
+
independent bugs, either of which alone made it a no-op.
|
|
365
|
+
|
|
366
|
+
### Breaking change
|
|
367
|
+
|
|
368
|
+
**The hook ships as `.claude/hooks/review-gate.cjs`**, renamed from
|
|
369
|
+
`review-gate.js`. The script is CommonJS and it is copied *into your* project,
|
|
370
|
+
where your `package.json` decides how node parses a `.js` — so in any
|
|
371
|
+
`"type": "module"` project it died on its own first `require`, printing a stack
|
|
372
|
+
trace on **every Bash tool call**. `.cjs` settles the parse mode at the file,
|
|
373
|
+
which is the only place independent of the one file skitterspec does not
|
|
374
|
+
control. An ESM rewrite would have inverted the same problem onto CommonJS
|
|
375
|
+
projects, which are still the default for anything with no `"type"` set.
|
|
376
|
+
|
|
377
|
+
The upgrade migrates you: your existing `PreToolUse` entry has its path
|
|
378
|
+
**rewritten in place** — so a wrapper, a flag or a different interpreter you
|
|
379
|
+
added all survive — and the retired `review-gate.js` is deleted. If you edited
|
|
380
|
+
that file yourself it is **kept**, with a warning, and left for you to remove.
|
|
381
|
+
|
|
382
|
+
**Check whether you already had a `review-gate.cjs` of your own.** `.cjs` is the
|
|
383
|
+
obvious way to work around the v20 crash, so anyone who fixed it by hand most
|
|
384
|
+
likely picked this exact filename — and this upgrade is the moment skitterspec
|
|
385
|
+
starts managing that path. A file already sitting there is **kept**, the real
|
|
386
|
+
hook is therefore never installed, and `review-gate.js` is pruned out from under
|
|
387
|
+
it. A hand-written shim that only `require`d the old script then fails open, so
|
|
388
|
+
the gate is silently absent under an update that reported success.
|
|
389
|
+
|
|
390
|
+
Two lines settle it:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
head -3 .claude/hooks/review-gate.cjs # ours opens with a 'use strict' + a doc comment
|
|
394
|
+
npx @skitterbyte/skitterspec update --check # a line naming this path means yours was kept
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
If it is yours, take ours — `npx @skitterbyte/skitterspec update --force`, or
|
|
398
|
+
delete the file and re-run `update`. Later versions report this case in its own
|
|
399
|
+
words rather than as `your edit — kept`, but the manifest records the path after
|
|
400
|
+
the first upgrade, so that wording cannot reach anyone who has already run one.
|
|
401
|
+
|
|
402
|
+
### Bug fix
|
|
403
|
+
|
|
404
|
+
**`skitterspec update` now registers the hook.** v20's notes said `init` and
|
|
405
|
+
`update` both did; only `init` did. `update` copied the script, reported
|
|
406
|
+
`created: .claude/hooks/review-gate.js`, and wired nothing — so every project
|
|
407
|
+
that upgraded into v20 got a hook file and no hook, with nothing saying so.
|
|
408
|
+
|
|
409
|
+
### What to do
|
|
410
|
+
|
|
411
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
412
|
+
2. **Commit `.claude/settings.json` and `.claude/hooks/review-gate.cjs`**, and
|
|
413
|
+
the deletion of `.claude/hooks/review-gate.js`. A hook only a fraction of the
|
|
414
|
+
team has is a gate that holds for a fraction of the team.
|
|
415
|
+
3. **Check it is actually on** — the update reports
|
|
416
|
+
`updated: .claude/settings.json (review-gate hook)` the first time, and
|
|
417
|
+
`unchanged` afterwards. If it says neither, your settings file could not be
|
|
418
|
+
parsed; it was left untouched and the hook is not registered.
|
|
419
|
+
|
|
420
|
+
### Behaviour change — a bug fix and a hotfix now owe a verdict
|
|
421
|
+
|
|
422
|
+
**`/spec-bug` and `/spec-hotfix` now arm the review gate**, as `/spec-next`
|
|
423
|
+
already did. Both render the page at the end of red→green work, and both now
|
|
424
|
+
wait for your verdict instead of finishing — so in that spec's worktree a
|
|
425
|
+
`git commit` is **refused** until one of two things happens:
|
|
426
|
+
|
|
427
|
+
- you send a verdict from the page (or `/spec-reviewed` picks up one already
|
|
428
|
+
waiting), or
|
|
429
|
+
- you run `skitterspec spec-env review skip "<reason>"`, which records the
|
|
430
|
+
decision to move on.
|
|
431
|
+
|
|
432
|
+
The gate is still only armed by work that **finished**, it still fails open on
|
|
433
|
+
every cannot-tell, and `review.required: false` in `specs/.core/env.config.json`
|
|
434
|
+
still turns it off for a project.
|
|
435
|
+
|
|
436
|
+
**Why.** Those two skills used to render the page, ask *"want a written review
|
|
437
|
+
before you commit?"* and finish with nothing watching. A verdict pressed on that
|
|
438
|
+
page sat in the holding area until someone typed `/spec-reviewed` — which they
|
|
439
|
+
had no reason to do, because the run had said the page was *ready* rather than
|
|
440
|
+
that it was *waiting*. Two verdicts were stranded that way on one spec, and the
|
|
441
|
+
second existed only because the first appeared to do nothing.
|
|
442
|
+
|
|
443
|
+
### New, and not breaking — the `Continue` verdict
|
|
444
|
+
|
|
445
|
+
**A page rendered part-way through a run offers `Continue`** — *I have read it,
|
|
446
|
+
carry on* — instead of the committing buttons, via
|
|
447
|
+
`spec-env review <spec> --buttons midrun`. The default is unchanged, so a caller
|
|
448
|
+
that passes nothing renders exactly the page it rendered before. `Continue` can
|
|
449
|
+
never clear an armed gate: that still takes a committing verdict or a recorded
|
|
450
|
+
skip.
|
|
451
|
+
|
|
452
|
+
Nothing else changes: `review.required` still defaults to `true`, and the engine
|
|
453
|
+
and `/spec-next` held the gate throughout regardless of the hook.
|
|
454
|
+
|
|
455
|
+
## `@skitterbyte/skitterspec-linear` v14 → v15 (the gate installs, and asking implies waiting)
|
|
456
|
+
|
|
457
|
+
The same change as `@skitterbyte/skitterspec` v20 → v21 above — this
|
|
458
|
+
distribution composes the same lifecycle skills. Read that entry; nothing here
|
|
459
|
+
is Linear-specific.
|
|
460
|
+
|
|
461
|
+
## `@skitterbyte/skitterspec` v19 → v20 (a phase owes a verdict)
|
|
462
|
+
|
|
463
|
+
### Breaking change
|
|
464
|
+
|
|
465
|
+
**A phase that has ended now refuses to go further until you send a verdict.**
|
|
466
|
+
`/spec-next` **arms** a gate when it finishes a phase and renders its review
|
|
467
|
+
page. While it is armed, two things refuse:
|
|
468
|
+
|
|
469
|
+
- `/spec-next` will not build the next phase.
|
|
470
|
+
- `git commit` inside **that spec's own worktree** is blocked by a harness hook
|
|
471
|
+
— which is what covers a bare `git commit`, a chained command, and
|
|
472
|
+
skittership's `/commit` without skitterspec editing any of them.
|
|
473
|
+
|
|
474
|
+
Exactly two things clear it, and both are one command:
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
# press Commit or Commit & Continue on the review page — or:
|
|
478
|
+
skitterspec spec-env review skip "none: additive, nothing to revert"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The skip is deliberately not silent: the reason goes into the review outcome
|
|
482
|
+
log, on the same reasoning as the `Gating:` header — a reason is a decision a
|
|
483
|
+
reviewer can argue with, where silence is an oversight.
|
|
484
|
+
|
|
485
|
+
**It is on by default** wherever isolation is configured (`env.config.json`
|
|
486
|
+
present). To turn it off for a project, add to `specs/.core/env.config.json`:
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{ "review": { "required": false } }
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Three things keep it a push rather than a wall, and they are worth knowing
|
|
493
|
+
before you reach for that setting. It is armed **only by a phase ending**, so a
|
|
494
|
+
mid-phase `/spec-diff` owes nothing. The exit is always one command. And it
|
|
495
|
+
accuses only on a positive signal — no engine, an unreadable payload, a commit
|
|
496
|
+
on the base branch or in another spec's tree, a repo with no isolation: every
|
|
497
|
+
cannot-tell lets the commit through.
|
|
498
|
+
|
|
499
|
+
### Breaking change
|
|
500
|
+
|
|
501
|
+
**`.claude/settings.json` is now written by the installer.** `skitterspec init`
|
|
502
|
+
and `skitterspec update` copy the hook script and register it as a `PreToolUse`
|
|
503
|
+
hook in your project's **committed** settings file. (In v20 `update` copied
|
|
504
|
+
without registering, and the script was named `review-gate.js` — both fixed in
|
|
505
|
+
v21; read that entry above if you are landing on the current release.) That is a
|
|
506
|
+
tracked file in most repos, so expect it in `git status` after upgrading — and
|
|
507
|
+
commit it, because a hook only a fraction of the team has is a gate that holds
|
|
508
|
+
for a fraction of the team.
|
|
509
|
+
|
|
510
|
+
A settings file that cannot be parsed is **reported and left alone**, never
|
|
511
|
+
rewritten. The hook is an extra layer: the engine and `/spec-next` hold the gate
|
|
512
|
+
without it.
|
|
513
|
+
|
|
514
|
+
### The review page can now reach your session — deliberately
|
|
515
|
+
|
|
516
|
+
This inverts an invariant the docs used to state outright: *a device that
|
|
517
|
+
reaches your page cannot reach your conversation*. It no longer holds, and the
|
|
518
|
+
change is the point. `/spec-next` now ends a phase by rendering the page and
|
|
519
|
+
**waiting** on it, so the button you press is what carries the work on — there
|
|
520
|
+
is no command to remember.
|
|
521
|
+
|
|
522
|
+
What replaced the old guard is two mechanisms and one rule:
|
|
523
|
+
|
|
524
|
+
- **The serve token** — 48 random bits in the URL path, minted per server —
|
|
525
|
+
decides who can POST at all.
|
|
526
|
+
- **The wait window** — only a pass that arrives *while the session is waiting*
|
|
527
|
+
is claimed for you, and two arrivals refuse rather than pick one.
|
|
528
|
+
- Outside that window nothing is claimed unasked. `/spec-reviewed`, or
|
|
529
|
+
`/spec-reviewed 324199` to name one exactly, is still how a pass sent when
|
|
530
|
+
nobody was waiting gets picked up — and it is still user-only, so the model
|
|
531
|
+
cannot claim a pass on its own.
|
|
532
|
+
|
|
533
|
+
A `file://` page has no server to talk to, so it copies and you paste, exactly
|
|
534
|
+
as before.
|
|
535
|
+
|
|
536
|
+
### What to do
|
|
537
|
+
|
|
538
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
539
|
+
2. **Commit `.claude/settings.json` and the hook script.** Both are new in your
|
|
540
|
+
working tree after the update.
|
|
541
|
+
3. **Nothing else to configure.** `review.required` defaults to `true` and
|
|
542
|
+
`review.commitWith` defaults to `/commit`; neither needs adding unless you
|
|
543
|
+
are changing it.
|
|
544
|
+
|
|
545
|
+
## `@skitterbyte/skitterspec-linear` v13 → v14 (a phase owes a verdict)
|
|
546
|
+
|
|
547
|
+
The same change as `@skitterbyte/skitterspec` v19 → v20 above — this
|
|
548
|
+
distribution composes the same lifecycle skills. Read that entry; nothing here
|
|
549
|
+
is Linear-specific.
|
|
550
|
+
|
|
551
|
+
## `@skitterbyte/skitterspec` v18 → v19 (starting a spec offers phase 1)
|
|
552
|
+
|
|
553
|
+
### Breaking change
|
|
554
|
+
|
|
555
|
+
**skitterspec now requires Node 22.13 or newer.** `engines.node` was `>=18`, and
|
|
556
|
+
that floor was a claim nobody was testing: the test suite cannot run without an
|
|
557
|
+
install, and pnpm 11.11 — the package manager this repo pins — itself requires
|
|
558
|
+
22.13. A floor CI cannot exercise is a promise rather than a guarantee, so it was
|
|
559
|
+
raised to the version the toolchain actually needs.
|
|
560
|
+
|
|
561
|
+
On Node 18 or 20, `npm install` now warns — or fails, under `engine-strict`.
|
|
562
|
+
Upgrade Node, or stay on v18 of skitterspec. Nothing in your config changes
|
|
563
|
+
either way.
|
|
564
|
+
|
|
565
|
+
### Releases now carry provenance
|
|
566
|
+
|
|
567
|
+
Every release from v19 onwards is built and signed by GitHub Actions through npm
|
|
568
|
+
Trusted Publishing, so no publish token exists anywhere to be leaked. Each
|
|
569
|
+
published version carries a signed attestation you can verify back to the commit
|
|
570
|
+
it was built from:
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
npm view @skitterbyte/skitterspec@19.0.0 dist.attestations
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Nothing to do — it is a property of the package you receive.
|
|
577
|
+
|
|
578
|
+
### Breaking change
|
|
579
|
+
|
|
580
|
+
**`/spec-start` no longer pushes the spec's branch.** It provisions the worktree
|
|
581
|
+
and commits the spec's move to `in-progress/` exactly as before, and then stops.
|
|
582
|
+
Publishing is yours to do, whenever you want the work somewhere other than your
|
|
583
|
+
machine:
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
git -C <worktreePath> push -u origin <branch>
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Two things change for you, and neither is in your config:
|
|
590
|
+
|
|
591
|
+
- **Spec branches stop appearing on the remote.** Nothing is lost — the branch
|
|
592
|
+
and its commits are in the worktree — but a branch you have not pushed is on
|
|
593
|
+
one machine only, and that is now the default rather than something the
|
|
594
|
+
tooling quietly undid.
|
|
595
|
+
- **Cancelling a spec with unpublished work now refuses.** `/spec-cancel` has
|
|
596
|
+
always respected `guards.refuseTeardownIfUnpushed`, but the guard could never
|
|
597
|
+
fire while provisioning published every branch. It fires now, at the one moment
|
|
598
|
+
it was written for: the work really is about to be destroyed, and the worktree
|
|
599
|
+
is the only copy. `/spec-cancel` names both ways out — publish the branch and
|
|
600
|
+
re-run, or `spec-env down <name> --force` accepting the loss. Nothing was
|
|
601
|
+
removed from your config and nothing needs adding to it.
|
|
602
|
+
|
|
603
|
+
The justification for the old behaviour does not survive reading, which is why
|
|
604
|
+
it went rather than becoming a setting: it claimed to fire the tracker's branch
|
|
605
|
+
automation, and that needs `{identifier}` in `branch.pattern`, which the shipped
|
|
606
|
+
default does not carry. `/spec-bug` never pushed and `/spec-hotfix` forbids it,
|
|
607
|
+
so this also makes the three consistent.
|
|
608
|
+
|
|
609
|
+
**The `open.command` config key is gone.** It was the editor/terminal-agnostic
|
|
610
|
+
opener — `code {worktreePath}`, a `tmux` command, a `warp://` deeplink — that
|
|
611
|
+
`/spec-start` ran when it could not move your session into the worktree.
|
|
612
|
+
|
|
613
|
+
**Leaving it in `env.config.json` is harmless and silent** — which is the part
|
|
614
|
+
to watch. The config merge copies known keys only, so a leftover `open` block is
|
|
615
|
+
ignored rather than rejected: nothing errors, and your editor simply stops
|
|
616
|
+
opening. If you set it deliberately, that absence is the only signal you get.
|
|
617
|
+
|
|
618
|
+
**`/spec-start` no longer moves your session into the worktree either.** It
|
|
619
|
+
provisions the worktree, does the housekeeping there, prints the path, and stops.
|
|
620
|
+
This **supersedes the "opens a session in it" half of v17 → v18 below** — the
|
|
621
|
+
branch still never leaves its worktree, but nothing tries to relocate your shell
|
|
622
|
+
to reach it.
|
|
623
|
+
|
|
624
|
+
| v18 | v19 |
|
|
625
|
+
|-----|-----|
|
|
626
|
+
| Three paths through `/spec-start`: enter the session, or fall back two ways | **One path.** Provision, bootstrap, print the path. |
|
|
627
|
+
| Reaching the work meant getting a shell or a window into the worktree | **`/spec-diff`** renders the worktree's diff as a page you read anywhere |
|
|
628
|
+
| `open.command` opened an editor on the fallback path | Removed. Nothing opens anything. |
|
|
629
|
+
|
|
630
|
+
### What replaced it
|
|
631
|
+
|
|
632
|
+
**`/spec-diff`** — a new skill, and the reason the opener had nothing left to do.
|
|
633
|
+
A phase is built in its own worktree, so `git diff` in your terminal answers
|
|
634
|
+
about the base branch. `/spec-diff` collects that worktree's changes with
|
|
635
|
+
`git -C` and writes a self-contained HTML page: whole-file context that folds
|
|
636
|
+
away, a file tree, untracked files included. Open it locally, or publish it and
|
|
637
|
+
read it on a phone.
|
|
638
|
+
|
|
639
|
+
The page lands in `.spec-env/reviews/<spec>.html` (gitignored), and
|
|
640
|
+
**the diff never passes through the model** — so it costs no context tokens
|
|
641
|
+
however large it is. The optional *written* review is the part that costs, and it
|
|
642
|
+
is offered rather than assumed. `/spec-next` writes the page at the end of every
|
|
643
|
+
phase. Beneath it, `skitterspec spec-env review <spec> [--branch]` is the engine.
|
|
644
|
+
|
|
645
|
+
### `/spec-start` lands you in the worktree, and offers phase 1
|
|
646
|
+
|
|
647
|
+
Two changes to the same moment. In `worktree` mode `/spec-start` used to
|
|
648
|
+
provision the branch, print the path and leave your session where it was; opening
|
|
649
|
+
a session in the worktree was then yours to do. It now
|
|
650
|
+
**moves your session into the worktree** as part of bootstrapping it, and asks
|
|
651
|
+
whether to build phase 1:
|
|
652
|
+
|
|
653
|
+
```
|
|
654
|
+
worktree ready — this session is now in it:
|
|
655
|
+
../myrepo-wt/sort-inbox
|
|
656
|
+
|
|
657
|
+
build phase 1 now?
|
|
658
|
+
yes -> carries on into /spec-next
|
|
659
|
+
no -> you are already there; type /spec-next whenever you like
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**Your shell will not be where it was.** A session that was on `main` in the
|
|
663
|
+
primary checkout is standing in the spec's worktree afterwards, on the spec's
|
|
664
|
+
branch — so the next command you type runs there. That is the point of it, and it
|
|
665
|
+
is still a real change to plan for. The move is a plain `cd`: nothing prompts you
|
|
666
|
+
for approval, nothing opens a new terminal or window, and your primary checkout
|
|
667
|
+
is untouched and still on the base branch.
|
|
668
|
+
|
|
669
|
+
**`/spec-next` needs no argument now.** Say **yes** and `/spec-start` carries on
|
|
670
|
+
into a bare `/spec-next`; say **no** and typing `/spec-next` an hour later does
|
|
671
|
+
the same thing, because you are already standing in the right place. Neither is
|
|
672
|
+
assumed, because provisioning is cheap and reversible while a phase build is
|
|
673
|
+
neither.
|
|
674
|
+
|
|
675
|
+
**This is not a loosened refusal.** A bare `/spec-next` still refuses to build a
|
|
676
|
+
spec it is not standing in, exactly as before — what changed is where you are
|
|
677
|
+
standing, not how weakly the rule reads. `--worktree <path>` survives beside it as
|
|
678
|
+
the explicit way to build a spec you are *not* in, and a path you pass is still
|
|
679
|
+
not a path anything guessed.
|
|
680
|
+
|
|
681
|
+
**Leaving is a `cd` too.** `/spec-complete` and `/spec-cancel` delete the
|
|
682
|
+
worktree, which is now the directory you are standing in, so both tell you to `cd`
|
|
683
|
+
to the primary checkout first. `git worktree remove` **succeeds** on the tree you
|
|
684
|
+
occupy rather than refusing — the teardown looks fine and every command after it
|
|
685
|
+
dies with `Unable to read current working directory`.
|
|
686
|
+
|
|
687
|
+
**The `--worktree` build checks itself.** On that path, `/spec-next` first records
|
|
688
|
+
what your primary checkout looked like, and afterwards reports anything that
|
|
689
|
+
appeared in it — the signature of a relative path that missed the worktree. It
|
|
690
|
+
reports rather than accuses: it cannot know who wrote a file, so it names both
|
|
691
|
+
readings and deletes nothing.
|
|
692
|
+
`skitterspec spec-env resolve <spec> --record-primary` and
|
|
693
|
+
`--assert-primary-clean` are the engine underneath, usable on their own.
|
|
694
|
+
|
|
695
|
+
### What to do
|
|
696
|
+
|
|
697
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
698
|
+
2. **Delete the `open` block from `specs/.core/env.config.json`**, if you have
|
|
699
|
+
one. Optional — it is ignored either way — but leaving it implies a setting
|
|
700
|
+
that no longer does anything.
|
|
701
|
+
3. **Answer the question `/spec-start` now asks.** In `worktree` mode it offers
|
|
702
|
+
phase 1 before it finishes. Take the offer and it is built there and then;
|
|
703
|
+
decline and you are left standing in the provisioned worktree, free to type
|
|
704
|
+
`/spec-next` whenever you like. Both endings are fully supported — decline when
|
|
705
|
+
the phase is a big one and you would rather spend a fresh context on it.
|
|
706
|
+
4. **Expect your shell to move, whichever you answer.** Anything you had queued
|
|
707
|
+
for the primary checkout — a `git` command, a script, a relative path — now
|
|
708
|
+
runs in the worktree instead. `cd` back when you want the base branch, and note
|
|
709
|
+
that `/spec-complete` and `/spec-cancel` require exactly that before they tear
|
|
710
|
+
the worktree down.
|
|
711
|
+
5. **Use `/spec-diff` to read the work** rather than reaching for a terminal in
|
|
712
|
+
the worktree. It is gated on nothing — half a phase, a hand edit, or a
|
|
713
|
+
colleague's branch are all ordinary inputs.
|
|
714
|
+
|
|
715
|
+
`checkout` mode is unchanged.
|
|
716
|
+
|
|
717
|
+
## `@skitterbyte/skitterspec-linear` v12 → v13 (starting a spec offers phase 1)
|
|
718
|
+
|
|
719
|
+
The same change as `@skitterbyte/skitterspec` v18 → v19 above — this
|
|
720
|
+
distribution composes the same lifecycle skills. Read that entry first.
|
|
721
|
+
|
|
722
|
+
### Breaking change
|
|
723
|
+
|
|
724
|
+
**`spec-sync push` is now `spec-sync plan`.** The verb computes a create/update
|
|
725
|
+
plan and performs no network I/O; `spec-sync apply` is what writes to Linear.
|
|
726
|
+
Calling it `push` put three unrelated things behind one word — this verb, the
|
|
727
|
+
`/spec-push` skill, and `git push` — and it was the one that pushes nothing.
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json
|
|
731
|
+
skitterspec spec-sync apply <spec> --plan plan.json
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
The old name is **not** aliased. It is recognised and exits 1 naming its
|
|
735
|
+
replacement, so a script that calls it fails loudly with the fix in the message
|
|
736
|
+
rather than drifting on a name that will be removed later.
|
|
737
|
+
|
|
738
|
+
**Your `linear.config.json` needs no change.** The `"push"` values under
|
|
739
|
+
`sync.fieldOwnership` — `assignee: "push"`, `description: "push"`,
|
|
740
|
+
`workflowState: "push"` — are a different vocabulary: they name a direction of
|
|
741
|
+
ownership, not a subcommand. They are untouched and still mean what they meant.
|
|
742
|
+
Do not search-and-replace `push` in your config.
|
|
743
|
+
|
|
744
|
+
### What else is here
|
|
745
|
+
|
|
746
|
+
**One thing here is Linear-specific.** `/spec-start` now pushes to Linear itself,
|
|
747
|
+
right after it commits the spec's move to `in-progress/`. It used to push nothing
|
|
748
|
+
and leave the mirror to the refresh `/spec-next` runs — immediate in `checkout`
|
|
749
|
+
mode, but in `worktree` mode hours away or never. Until it came, the issue sat in
|
|
750
|
+
its old workflow state with nobody assigned while the repo read `in-progress`
|
|
751
|
+
with a developer on it. Expect one more Linear call per `/spec-start`, and expect
|
|
752
|
+
the issue to be current the moment the spec is in flight. Nothing else changes:
|
|
753
|
+
sync is still one-way, and an unlinked spec is still skipped rather than minted.
|
|
754
|
+
|
|
755
|
+
## `@skitterbyte/skitterspec` v17 → v18 (a spec is built in its own worktree)
|
|
756
|
+
|
|
757
|
+
### Breaking change
|
|
758
|
+
|
|
759
|
+
**`/spec-start` no longer moves the branch into your checkout.** In `worktree`
|
|
760
|
+
mode it provisions the spec's worktree, does the housekeeping there, opens a
|
|
761
|
+
session in it, and stops. The spec is built where it was provisioned — which is
|
|
762
|
+
what worktrees are for, and why `main` stays free.
|
|
763
|
+
|
|
764
|
+
This **supersedes the "one checkout holds one spec in flight" rule** described
|
|
765
|
+
under v16 → v17 below. That rule was a consequence of moving the branch into the
|
|
766
|
+
primary checkout; with the move gone, so is the restriction:
|
|
767
|
+
|
|
768
|
+
| v17 | v18 |
|
|
769
|
+
|-----|-----|
|
|
770
|
+
| `/spec-start` refused while another spec held your checkout | In `worktree` mode it only requires a **clean tree**. Several specs in flight is what the mode is for. |
|
|
771
|
+
| Starting a spec took two invocations, with a `/spec-live <name>` you typed in between | **One invocation.** No hand-off command, no re-run. |
|
|
772
|
+
| `/spec-live main` was one of the ways out of the gate | The ways out are `/spec-complete` and `/spec-cancel`. Parking to free a workbench is a one-workbench answer, and only `checkout` mode holds one spec now. |
|
|
773
|
+
|
|
774
|
+
**`/spec-live` is for testing only** — reusing your running dev server to reach a
|
|
775
|
+
spec at the canonical URL. It was never meant to be how work gets started, and no
|
|
776
|
+
lifecycle skill calls it.
|
|
777
|
+
|
|
778
|
+
`checkout` mode is unchanged: the branch is built in the primary checkout, the
|
|
779
|
+
gate still requires the workbench free, and `/spec-start` carries straight on
|
|
780
|
+
into phase 1 in the same session.
|
|
781
|
+
|
|
782
|
+
### What to do
|
|
783
|
+
|
|
784
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
785
|
+
2. **Expect a session, not a swap.** After `/spec-start` in `worktree` mode, run
|
|
786
|
+
`/spec-next` from the session it opens in the worktree. `/spec-next` builds
|
|
787
|
+
the spec it is *standing in* and refuses to build one from elsewhere.
|
|
788
|
+
3. **Nothing to configure.** `spec.companionPaths` in `env.config.json` is new
|
|
789
|
+
and optional — it names paths that belong to a spec alongside its own folder
|
|
790
|
+
(a tracker's per-spec snapshot), so `/spec-start` can commit them together.
|
|
791
|
+
Empty by default; `/spec-linear-setup` sets it for you.
|
|
792
|
+
|
|
793
|
+
## `@skitterbyte/skitterspec-linear` v11 → v12 (a spec is built in its own worktree)
|
|
794
|
+
|
|
795
|
+
The same change as `@skitterbyte/skitterspec` v17 → v18 above — this
|
|
796
|
+
distribution composes the same lifecycle skills. Read that entry; nothing here
|
|
797
|
+
is Linear-specific.
|
|
798
|
+
|
|
799
|
+
## `@skitterbyte/skitterspec` v16 → v17 (`/spec-go` splits in two)
|
|
800
|
+
|
|
801
|
+
### Breaking change
|
|
802
|
+
|
|
803
|
+
**`/spec-go` is removed.** It did two jobs — set an environment up, and build a
|
|
804
|
+
phase — and the seam between them is where the worktree hand-off hurt: you ran
|
|
805
|
+
the same command twice, once to provision and once, from another session, to
|
|
806
|
+
build. They are now two commands with one job each:
|
|
807
|
+
|
|
808
|
+
| Removed | Use instead |
|
|
809
|
+
|---------|-------------|
|
|
810
|
+
| `/spec-go <name>` (first run — start a spec) | **`/spec-start <name>`** — puts the spec in flight on this checkout, moves it to `in-progress`, then builds phase 1. |
|
|
811
|
+
| `/spec-go` (later runs — build the next phase) | **`/spec-next`** — builds the next phase of whichever spec is in flight. Re-run it per phase. |
|
|
812
|
+
| `/spec-go --here` | **Nothing — `/spec-start` *is* here.** It puts the branch in the checkout you are in, which is what the flag was reaching for. |
|
|
813
|
+
|
|
814
|
+
**One checkout holds one spec in flight.** `/spec-start` refuses while another
|
|
815
|
+
spec holds your checkout, naming it and the three ways to free the workbench
|
|
816
|
+
(`/spec-complete`, `/spec-cancel`, or `/spec-live main` to park it). It will not
|
|
817
|
+
stash, commit or switch on your behalf — moving unfinished work is a decision,
|
|
818
|
+
not a side effect.
|
|
819
|
+
|
|
820
|
+
**`/spec-next` refuses when nothing is in flight** rather than guessing a spec
|
|
821
|
+
from the conversation. It writes real code; a wrong guess produces commits on a
|
|
822
|
+
branch nobody asked for.
|
|
823
|
+
|
|
824
|
+
### What to do
|
|
825
|
+
|
|
826
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update` removes the retired
|
|
827
|
+
`/spec-go` skill and installs the two replacements. A `/spec-go` you edited
|
|
828
|
+
yourself is kept with a warning rather than deleted; remove it by hand.
|
|
829
|
+
2. **Retrain the muscle memory** — `/spec-start <name>` to begin, `/spec-next` to
|
|
830
|
+
carry on, unchanged `/spec-complete` to finish.
|
|
831
|
+
3. **Providers**: the `spec-go-start` seam is now **`spec-next-start`**. A
|
|
832
|
+
provider distribution must rename its fragment file to match, or the build
|
|
833
|
+
fails on an orphaned seam.
|
|
834
|
+
|
|
835
|
+
## `@skitterbyte/skitterspec-linear` v10 → v11 (`/spec-go` splits in two)
|
|
836
|
+
|
|
837
|
+
The same change as `@skitterbyte/skitterspec` v16 → v17 above — this
|
|
838
|
+
distribution composes the same lifecycle skills, so `/spec-go` is removed here
|
|
839
|
+
too and replaced by `/spec-start` + `/spec-next`. Read that entry; nothing here
|
|
840
|
+
is Linear-specific. (Backfilled: the base entry was written when the change
|
|
841
|
+
landed and this one was missed.)
|
|
842
|
+
|
|
843
|
+
## `@skitterbyte/skitterspec-linear` v9 → v10 (`push` validates your issue states)
|
|
844
|
+
|
|
845
|
+
**`spec-sync push` now refuses to run until the configured `states` names have been checked against your Linear workspace.** The check itself is not new — it
|
|
846
|
+
already existed on `spec-sync status --workspace-states` — but it was advisory,
|
|
847
|
+
and skipping it sent a state name Linear **silently ignores**: the description
|
|
848
|
+
lands, the issue never moves, and nothing errors. The base
|
|
849
|
+
`@skitterbyte/skitterspec` is unaffected.
|
|
850
|
+
|
|
851
|
+
### Breaking change
|
|
852
|
+
|
|
853
|
+
| | v9 | v10 |
|
|
854
|
+
|---|-----|-----|
|
|
855
|
+
| `spec-sync push <spec>` | runs | **exits 1** unless `--workspace-states <file>` or `--skip-state-check` is passed |
|
|
856
|
+
| A configured state absent from the workspace | pushed, silently no-op | **exits 1**, naming the workspace's real states |
|
|
857
|
+
|
|
858
|
+
`/spec-push` handles this for you — it fetches the workspace's issue
|
|
859
|
+
workflow-state names over MCP and passes them on. **Nothing changes if you drive sync through the skill.** Only a direct CLI caller needs updating.
|
|
860
|
+
|
|
861
|
+
### What to do
|
|
862
|
+
|
|
863
|
+
1. **Using `/spec-push`?** Nothing. Run `update` and carry on.
|
|
864
|
+
2. **Calling `spec-sync push` from CI or a script?** Supply the workspace's issue
|
|
865
|
+
workflow-state names as a JSON array and pass the file:
|
|
866
|
+
|
|
867
|
+
```sh
|
|
868
|
+
# names come from your Linear workspace, e.g. via the MCP server or the API
|
|
869
|
+
echo '["Backlog","Todo","In Progress","Done","Canceled"]' > states.json
|
|
870
|
+
skitterspec spec-sync push my-spec --workspace-states states.json --json
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Or opt out deliberately with `--skip-state-check`. Don't reach for it to get
|
|
874
|
+
past a *failing* check — that check is the only thing standing between you and
|
|
875
|
+
a push that moves nothing.
|
|
876
|
+
3. **If the check refuses,** it tells you what your workspace actually has and,
|
|
877
|
+
where the lifecycle bucket makes it unambiguous, which name to use:
|
|
878
|
+
|
|
879
|
+
```
|
|
880
|
+
spec-sync push: refusing — configured state name(s) not in the workspace
|
|
881
|
+
|
|
882
|
+
states.complete: "Done" is not an issue state in this workspace
|
|
883
|
+
use "Completed" instead
|
|
884
|
+
|
|
885
|
+
available: Backlog, Todo, In Progress, Completed, Canceled
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Fix `specs/.core/linear.config.json` → `states`. `/spec-push` will offer to
|
|
889
|
+
apply the fix for you.
|
|
890
|
+
|
|
891
|
+
### Also in v10 (not breaking)
|
|
892
|
+
|
|
893
|
+
- **A pre-9.0 mirror is detected before it can be orphaned.** A spec still
|
|
894
|
+
carrying `linear_project_id` / `linear_milestone_id` reads as unlinked to v9+,
|
|
895
|
+
so `push` would emit an all-creates plan and abandon the live mirror. The plan
|
|
896
|
+
now carries a `legacy` field naming how many objects that would strand, and
|
|
897
|
+
`/spec-push` stops. **If you skipped the v8 → v9 migration below, read it now** —
|
|
898
|
+
this is the guard that catches you, not a substitute for it.
|
|
899
|
+
- **`update` says what it skipped** — each `customized (kept)` file now reports
|
|
900
|
+
`+added −removed`, with `--diff` to see the upstream changes you declined.
|
|
901
|
+
- **This guide now ships inside the package** (it wasn't in the published tarball
|
|
902
|
+
before v10 — `files` listed only `bin`/`src`/`assets`).
|
|
903
|
+
|
|
904
|
+
## `@skitterbyte/skitterspec-linear` v8 → v9 (a spec is an Issue, phases are sub-issues)
|
|
905
|
+
|
|
906
|
+
**v9 remaps the Linear mirror.** A spec is now a Linear **issue** (not a Project),
|
|
907
|
+
each phase a **sub-issue** (not a Milestone), and **tasks are no longer synced**
|
|
908
|
+
(they stay in the repo phase files). This collapses a large spec from ~1 project +
|
|
909
|
+
N milestones + dozens of task-issues down to **one issue + one sub-issue per phase**. The base `@skitterbyte/skitterspec` is unaffected (still v15).
|
|
910
|
+
|
|
911
|
+
### Breaking changes
|
|
912
|
+
|
|
913
|
+
| Area | v8 | v9 |
|
|
914
|
+
|------|-----|-----|
|
|
915
|
+
| `linear.config.json` → `mapping` | `{specFolder:"project", phases:"milestone", tasks:"issue"}` | `{specFolder:"issue", phases:"subissue", tasks:"none"}` |
|
|
916
|
+
| `linear.config.json` → `linear` | `initiativeId` | `projectId` (the project picker's default) |
|
|
917
|
+
| `linear.config.json` → `states` | Linear **Project** statuses (e.g. `Completed`) | Linear **issue** workflow states (e.g. `Done`) |
|
|
918
|
+
| `linear.config.json` → `sync.fieldOwnership` | `{description, milestones, tasks, workflowState}` | `{description, subIssues, workflowState}` |
|
|
919
|
+
| Phase frontmatter | `linear_milestone_id` | `linear_issue_id` (the sub-issue id) |
|
|
920
|
+
| Overview frontmatter | `linear_project_id` + `linear_identifier` | `linear_identifier` (the spec issue) |
|
|
921
|
+
| Last-pushed snapshot | `{project, milestones, issues}` | `{issue, subIssues}` |
|
|
922
|
+
|
|
923
|
+
### What to do
|
|
924
|
+
|
|
925
|
+
1. **Upgrade and re-run `update`:** `npx @skitterbyte/skitterspec-linear update`.
|
|
926
|
+
It refreshes the skills, the `linear.config.md` / `SETUP.md` docs, and the
|
|
927
|
+
config example.
|
|
928
|
+
2. **Edit `specs/.core/linear.config.json`** to the new keys above (or delete it
|
|
929
|
+
and re-copy `linear.config.json.example`). Point `states` at your workspace's
|
|
930
|
+
**issue** states; set `linear.projectId` if most specs belong to one Project —
|
|
931
|
+
it pre-selects the picker's default rather than fixing every spec there.
|
|
932
|
+
3. **Optionally add `intake`** to start specs from issues someone else filed:
|
|
933
|
+
|
|
934
|
+
```jsonc
|
|
935
|
+
"intake": {
|
|
936
|
+
"label": "web-app", // the inbox `/spec --from-issue` browses
|
|
937
|
+
"bugLabels": ["bug"] // issues with these route to /spec-bug
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
Without it, `/spec SKI-123` still adopts an issue by id; only the browsable
|
|
942
|
+
inbox and the bug routing need the labels.
|
|
943
|
+
4. **Existing pushed specs:** the snapshot format changed, so the first
|
|
944
|
+
`/spec-push` after upgrading **re-creates** the mirror (a fresh issue +
|
|
945
|
+
sub-issues). Delete any stale `specs/.core/linear-base/*.base.json` and the old
|
|
946
|
+
`linear_project_id` / `linear_milestone_id` frontmatter first. If you were
|
|
947
|
+
pre-first-push, there's nothing to reconcile.
|
|
948
|
+
5. **Task-level issues** created under v8 are no longer managed by the sync —
|
|
949
|
+
close or repurpose them in Linear by hand.
|
|
950
|
+
|
|
951
|
+
## `@skitterbyte/skitterspec-linear` v7 → v8 (sync goes one-way; `/spec-pull` removed)
|
|
952
|
+
|
|
953
|
+
**v8 made sync one-way.** The repo became the sole source of truth and Linear a
|
|
954
|
+
**generated mirror**: content is pushed up, never read back or merged. The
|
|
955
|
+
three-way merge engine and everything that fed it were retired.
|
|
956
|
+
|
|
957
|
+
### Breaking changes
|
|
958
|
+
|
|
959
|
+
| Area | v7 | v8 |
|
|
960
|
+
|------|-----|-----|
|
|
961
|
+
| Skills | `/spec-status`, `/spec-push`, **`/spec-pull`** | `/spec-status`, `/spec-push` |
|
|
962
|
+
| `spec-sync` subcommands | `normalize`, `push`, `status`, **`pull`** | `normalize`, `push`, `status`, **`record`** |
|
|
963
|
+
| Sidecar | a three-way merge base | the **last-pushed snapshot** (`record` writes it) |
|
|
964
|
+
| `sync.fieldOwnership` | `pull` / `both` values were load-bearing | still parsed; nothing is pulled |
|
|
965
|
+
|
|
966
|
+
### What to do
|
|
967
|
+
|
|
968
|
+
1. **Drop `/spec-pull` from any workflow that calls it.** There is no
|
|
969
|
+
replacement: editing the mirror in Linear is no longer an input. A person
|
|
970
|
+
editing the issue will see it overwritten by the next push.
|
|
971
|
+
2. **Replace `spec-sync pull` in scripts with `spec-sync record`** — it writes the
|
|
972
|
+
snapshot from the current repo files after a push is applied.
|
|
973
|
+
3. **Delete stale merge-base sidecars** under `sync.baseDir`; the first push after
|
|
974
|
+
upgrading writes the new snapshot format.
|
|
975
|
+
|
|
976
|
+
## `@skitterbyte/skitterspec-linear` v1 → v7 (no breaking changes)
|
|
977
|
+
|
|
978
|
+
Every major in this range was a **routine version bump**, not a breaking
|
|
979
|
+
contract. The skills (`/spec-status`, `/spec-push`, `/spec-pull`) and the
|
|
980
|
+
`spec-sync` subcommands (`normalize`, `push`, `pull`, `status`) were identical at
|
|
981
|
+
v1 and at v7. Upgrading anywhere inside this range needs **no action** beyond
|
|
982
|
+
re-running `update`.
|
|
983
|
+
|
|
984
|
+
One thing did change quietly, at **v4**: `sync.fieldOwnership` lost its
|
|
985
|
+
`milestones`, `phaseBodies`, `acceptanceCriteria` and `taskBreakdown` entries when
|
|
986
|
+
that detail moved inside `description`. A config still listing them does not
|
|
987
|
+
error — unknown keys merge in and *join the compared set* — so remove them if
|
|
988
|
+
you have them, or they will be compared against fields that no longer exist.
|
|
989
|
+
|
|
990
|
+
## `@skitterbyte/skitterspec` v3 → v16 (no breaking changes)
|
|
991
|
+
|
|
992
|
+
**Nothing in this range requires action.** Thirteen majors sounds like thirteen
|
|
993
|
+
migrations; it was one habit. Every release in this period was cut as a major
|
|
994
|
+
bump regardless of size (see `RELEASING.md`), and the base package's contract
|
|
995
|
+
never broke: **no skill was ever removed and no CLI flag was ever removed** — the
|
|
996
|
+
surface only grew. The spec folder layout
|
|
997
|
+
(`.core`/`backlog`/`in-progress`/`complete`/`cancelled`) is unchanged throughout.
|
|
998
|
+
|
|
999
|
+
The `feat(sync)!` commits that appear in this window changed
|
|
1000
|
+
`@skitterbyte/skitterspec-linear`, which ships separately; the base was bumped
|
|
1001
|
+
alongside it in lockstep. If you are on the **superset**, read the provider
|
|
1002
|
+
entries above — those are the ones with work in them.
|
|
1003
|
+
|
|
1004
|
+
What each major actually added, so you can see what you gain by upgrading:
|
|
1005
|
+
|
|
1006
|
+
| Major | What landed |
|
|
1007
|
+
|-------|-------------|
|
|
1008
|
+
| v4 | `setup` commands bootstrap a fresh worktree's dependencies |
|
|
1009
|
+
| v5, v6 | version bumps only |
|
|
1010
|
+
| v7 | release docs refreshed; stale scripts dropped |
|
|
1011
|
+
| v8 | version bump only |
|
|
1012
|
+
| v9 | released alongside the provider's Linear body round-trip |
|
|
1013
|
+
| v10, v11 | version bumps only |
|
|
1014
|
+
| v12 | install manifest + `update --resync` / `--reset`; **`/spec-live`** overlay |
|
|
1015
|
+
| v13 | **`/spec-hotfix`**, **`/spec-to-main`**, `spec-env prune` for orphaned test DBs |
|
|
1016
|
+
| v14 | **Impact map** in the spec templates; live-aware `/spec-go`; the docs site |
|
|
1017
|
+
| v15 | released alongside the provider's one-way sync switch |
|
|
1018
|
+
| v16 | spec `Name:` handle; `/spec-complete` · `/spec-cancel` commit their own edits |
|
|
1019
|
+
|
|
1020
|
+
**Spec files written under an older version still read.** The template grew
|
|
1021
|
+
(the Impact map at v14, the `Name:` header at v16) but the lifecycle skills treat
|
|
1022
|
+
both as optional — `/spec-review` adds them if you want them.
|
|
1023
|
+
|
|
1024
|
+
## `@skitterbyte/skitterspec` v2 → v3 (slimmer surface + local traffic diversion)
|
|
1025
|
+
|
|
1026
|
+
**v3 shrinks the everyday command surface to five verbs — `spec → go → connect → commit → complete` — by folding provisioning, teardown, and grooming into the lifecycle skills, and adds `/spec-connect` for testing a worktree at your normal `localhost` URL.** (`@skitterbyte/skitterspec-linear` moves to v2.0.0 in lockstep.)
|
|
1027
|
+
|
|
1028
|
+
### Removed skills (breaking) → where they went
|
|
1029
|
+
|
|
1030
|
+
| Removed skill | Replaced by |
|
|
1031
|
+
|---------------|-------------|
|
|
1032
|
+
| `/spec-env` | **Automatic in `/spec-go`** — it provisions the worktree and (with your OK) starts the spec's dev servers. Escalate Docker later with the CLI: `skitterspec spec-env up <name>`. |
|
|
1033
|
+
| `/spec-env-down` | **Folded into `/spec-complete` and `/spec-cancel`** — they tear the environment down (dev servers, worktree, stack, slot) as part of finishing/abandoning a spec. |
|
|
1034
|
+
| `/spec-ready` | **Folded into `/spec`** — grilling now writes a `Ready` spec directly (or `Draft` if you deliberately leave open questions). Go straight to `/spec-go`. |
|
|
1035
|
+
|
|
1036
|
+
The **`skitterspec spec-env` CLI engine stays** (`up`, `down`, `dev`, `connect`,
|
|
1037
|
+
`integrate`, `status`, `resolve`) — only the three *skills* were removed. Anything
|
|
1038
|
+
that scripted those CLI verbs keeps working.
|
|
1039
|
+
|
|
1040
|
+
### New — `/spec-connect` and two config blocks
|
|
1041
|
+
|
|
1042
|
+
- **`/spec-connect <name>`** points your canonical `localhost` ports at a spec's
|
|
1043
|
+
running dev servers (so you can test a worktree's UI/API at the normal URL);
|
|
1044
|
+
`/spec-connect main` hands the ports back. It's a small bundled Node reverse
|
|
1045
|
+
proxy — no external install. Exclusive: one spec exposed at a time.
|
|
1046
|
+
- **`env.config.json` gains `dev` and `proxy` blocks.** `dev` lists the host dev
|
|
1047
|
+
servers `/spec-go` starts (`{ name, command, portVar, health?, frontPort? }`);
|
|
1048
|
+
`proxy` configures the front-door proxy (`{ enabled, host }`). Both default to
|
|
1049
|
+
off/empty, so existing projects are unaffected until you fill `dev` in.
|
|
1050
|
+
|
|
1051
|
+
### What to do
|
|
1052
|
+
|
|
1053
|
+
1. **Upgrade and re-run `init`** (or `update`): `npx @skitterbyte/skitterspec
|
|
1054
|
+
update`. It stops installing the three removed skills, installs `/spec-connect`,
|
|
1055
|
+
and refreshes the CLAUDE.md section + `spec-planning` rule. Your specs and
|
|
1056
|
+
`env.config.json` are untouched.
|
|
1057
|
+
2. **Remove muscle memory for the old commands** — use `/spec-go` to bring a spec
|
|
1058
|
+
up, `/spec-complete`/`/spec-cancel` to tear it down, and `/spec` (no separate
|
|
1059
|
+
`/spec-ready`) to reach a Ready spec.
|
|
1060
|
+
3. **To test UI/API worktrees:** add a `dev` block to `env.config.json` (see
|
|
1061
|
+
`specs/.core/env.config.md`), then `/spec-go` → `/spec-connect <name>`.
|
|
1062
|
+
|
|
1063
|
+
## `@skitterbyte/skitterspec` v1 → v2 (tracker-free base)
|
|
1064
|
+
|
|
1065
|
+
**v2 of the base package is tracker-free.** The Linear sync feature — the
|
|
1066
|
+
`/spec-status`, `/spec-push` skills, the `spec-sync` CLI, the
|
|
1067
|
+
Linear-aware steps of `/spec` and `/spec-go`, and the `linear.config.*`
|
|
1068
|
+
templates — moved out of `@skitterbyte/skitterspec` into a separate **superset**
|
|
1069
|
+
distribution, `@skitterbyte/skitterspec-linear`. You now install exactly one:
|
|
1070
|
+
|
|
1071
|
+
| If you… | Install |
|
|
1072
|
+
|---------|---------|
|
|
1073
|
+
| don't sync specs to a tracker | `@skitterbyte/skitterspec` (v2) |
|
|
1074
|
+
| use (or want) Linear sync | `@skitterbyte/skitterspec-linear` |
|
|
1075
|
+
|
|
1076
|
+
Everything else — the spec lifecycle and per-spec isolation — is unchanged and
|
|
1077
|
+
present in **both**.
|
|
1078
|
+
|
|
1079
|
+
### If you did NOT use Linear sync
|
|
1080
|
+
|
|
1081
|
+
Nothing to do. Upgrade to v2 and re-run `init` (or `update`) as usual. The base
|
|
1082
|
+
never installed the Linear skills for you, so there's nothing to remove.
|
|
1083
|
+
|
|
1084
|
+
### If you DID use Linear sync
|
|
1085
|
+
|
|
1086
|
+
Switching is one install plus a re-`init`:
|
|
1087
|
+
|
|
1088
|
+
1. **Install the superset** (in place of the base):
|
|
1089
|
+
|
|
1090
|
+
```sh
|
|
1091
|
+
npm rm @skitterbyte/skitterspec # if it was a dependency
|
|
1092
|
+
npx @skitterbyte/skitterspec-linear init
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
2. **Re-run `init`.** It re-installs the shared skills (now composed with the
|
|
1096
|
+
Linear steps) and the three sync skills, and re-scaffolds the config
|
|
1097
|
+
templates. Your existing files are preserved — `init` never overwrites without
|
|
1098
|
+
`--force`.
|
|
1099
|
+
|
|
1100
|
+
3. **Your config is unchanged.** The live config path is still
|
|
1101
|
+
`specs/.core/linear.config.json`, and the committed base sidecars under
|
|
1102
|
+
`specs/.core/linear-base/` are read as-is. No re-linking, no re-sync.
|
|
1103
|
+
|
|
1104
|
+
That's it — `/spec-status`, `/spec-push`, and `skitterspec-linear
|
|
1105
|
+
spec-sync …` work exactly as before.
|
|
1106
|
+
|
|
1107
|
+
### One config note — branch naming
|
|
1108
|
+
|
|
1109
|
+
Embedding the Linear identifier in a worktree branch name is now configured in the
|
|
1110
|
+
**isolation** config, not the Linear config. In `specs/.core/env.config.json` set:
|
|
1111
|
+
|
|
1112
|
+
```jsonc
|
|
1113
|
+
"branch": { "pattern": "{identifier}-{slug}", "identifierField": "linear_identifier" }
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
If you don't need the id in branch names, leave the default `{type}/{slug}` — the
|
|
1117
|
+
old implicit Linear-branch behaviour is off unless you opt in this way. (This is
|
|
1118
|
+
the only behavioural change beyond the package split.)
|
|
1119
|
+
|
|
1120
|
+
## Why the split
|
|
1121
|
+
|
|
1122
|
+
The base couldn't ship without a specific tracker's fingerprints baked into shared
|
|
1123
|
+
skills and a `src/sync/` engine. Extracting the provider makes the base a clean,
|
|
1124
|
+
tracker-free workflow and lets a new provider (e.g. Jira) ship as another superset
|
|
1125
|
+
over the same base — without re-patching the base. See
|
|
1126
|
+
`specs/complete/feat-extract-ticketing-provider/` for the full rationale.
|