@skitterbyte/skitterspec 18.0.0 → 19.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 CHANGED
@@ -1,5 +1,209 @@
1
1
  # Migration guide
2
2
 
3
+ ## `@skitterbyte/skitterspec` v18 → v19 (starting a spec offers phase 1)
4
+
5
+ ### Breaking change
6
+
7
+ **skitterspec now requires Node 22.13 or newer.** `engines.node` was `>=18`, and
8
+ that floor was a claim nobody was testing: the test suite cannot run without an
9
+ install, and pnpm 11.11 — the package manager this repo pins — itself requires
10
+ 22.13. A floor CI cannot exercise is a promise rather than a guarantee, so it was
11
+ raised to the version the toolchain actually needs.
12
+
13
+ On Node 18 or 20, `npm install` now warns — or fails, under `engine-strict`.
14
+ Upgrade Node, or stay on v18 of skitterspec. Nothing in your config changes
15
+ either way.
16
+
17
+ ### Releases now carry provenance
18
+
19
+ Every release from v19 onwards is built and signed by GitHub Actions through npm
20
+ Trusted Publishing, so no publish token exists anywhere to be leaked. Each
21
+ published version carries a signed attestation you can verify back to the commit
22
+ it was built from:
23
+
24
+ ```
25
+ npm view @skitterbyte/skitterspec@19.0.0 dist.attestations
26
+ ```
27
+
28
+ Nothing to do — it is a property of the package you receive.
29
+
30
+ ### Breaking change
31
+
32
+ **`/spec-start` no longer pushes the spec's branch.** It provisions the worktree
33
+ and commits the spec's move to `in-progress/` exactly as before, and then stops.
34
+ Publishing is yours to do, whenever you want the work somewhere other than your
35
+ machine:
36
+
37
+ ```
38
+ git -C <worktreePath> push -u origin <branch>
39
+ ```
40
+
41
+ Two things change for you, and neither is in your config:
42
+
43
+ - **Spec branches stop appearing on the remote.** Nothing is lost — the branch
44
+ and its commits are in the worktree — but a branch you have not pushed is on
45
+ one machine only, and that is now the default rather than something the
46
+ tooling quietly undid.
47
+ - **Cancelling a spec with unpublished work now refuses.** `/spec-cancel` has
48
+ always respected `guards.refuseTeardownIfUnpushed`, but the guard could never
49
+ fire while provisioning published every branch. It fires now, at the one moment
50
+ it was written for: the work really is about to be destroyed, and the worktree
51
+ is the only copy. `/spec-cancel` names both ways out — publish the branch and
52
+ re-run, or `spec-env down <name> --force` accepting the loss. Nothing was
53
+ removed from your config and nothing needs adding to it.
54
+
55
+ The justification for the old behaviour does not survive reading, which is why
56
+ it went rather than becoming a setting: it claimed to fire the tracker's branch
57
+ automation, and that needs `{identifier}` in `branch.pattern`, which the shipped
58
+ default does not carry. `/spec-bug` never pushed and `/spec-hotfix` forbids it,
59
+ so this also makes the three consistent.
60
+
61
+ **The `open.command` config key is gone.** It was the editor/terminal-agnostic
62
+ opener — `code {worktreePath}`, a `tmux` command, a `warp://` deeplink — that
63
+ `/spec-start` ran when it could not move your session into the worktree.
64
+
65
+ **Leaving it in `env.config.json` is harmless and silent** — which is the part
66
+ to watch. The config merge copies known keys only, so a leftover `open` block is
67
+ ignored rather than rejected: nothing errors, and your editor simply stops
68
+ opening. If you set it deliberately, that absence is the only signal you get.
69
+
70
+ **`/spec-start` no longer moves your session into the worktree either.** It
71
+ provisions the worktree, does the housekeeping there, prints the path, and stops.
72
+ This **supersedes the "opens a session in it" half of v17 → v18 below** — the
73
+ branch still never leaves its worktree, but nothing tries to relocate your shell
74
+ to reach it.
75
+
76
+ | v18 | v19 |
77
+ |-----|-----|
78
+ | Three paths through `/spec-start`: enter the session, or fall back two ways | **One path.** Provision, bootstrap, print the path. |
79
+ | 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 |
80
+ | `open.command` opened an editor on the fallback path | Removed. Nothing opens anything. |
81
+
82
+ ### What replaced it
83
+
84
+ **`/spec-diff`** — a new skill, and the reason the opener had nothing left to do.
85
+ A phase is built in its own worktree, so `git diff` in your terminal answers
86
+ about the base branch. `/spec-diff` collects that worktree's changes with
87
+ `git -C` and writes a self-contained HTML page: whole-file context that folds
88
+ away, a file tree, untracked files included. Open it locally, or publish it and
89
+ read it on a phone.
90
+
91
+ The page lands in `.spec-env/reviews/<spec>.html` (gitignored), and
92
+ **the diff never passes through the model** — so it costs no context tokens
93
+ however large it is. The optional *written* review is the part that costs, and it
94
+ is offered rather than assumed. `/spec-next` writes the page at the end of every
95
+ phase. Beneath it, `skitterspec spec-env review <spec> [--branch]` is the engine.
96
+
97
+ ### `/spec-start` lands you in the worktree, and offers phase 1
98
+
99
+ Two changes to the same moment. In `worktree` mode `/spec-start` used to
100
+ provision the branch, print the path and leave your session where it was; opening
101
+ a session in the worktree was then yours to do. It now
102
+ **moves your session into the worktree** as part of bootstrapping it, and asks
103
+ whether to build phase 1:
104
+
105
+ ```
106
+ worktree ready — this session is now in it:
107
+ ../myrepo-wt/sort-inbox
108
+
109
+ build phase 1 now?
110
+ yes -> carries on into /spec-next
111
+ no -> you are already there; type /spec-next whenever you like
112
+ ```
113
+
114
+ **Your shell will not be where it was.** A session that was on `main` in the
115
+ primary checkout is standing in the spec's worktree afterwards, on the spec's
116
+ branch — so the next command you type runs there. That is the point of it, and it
117
+ is still a real change to plan for. The move is a plain `cd`: nothing prompts you
118
+ for approval, nothing opens a new terminal or window, and your primary checkout
119
+ is untouched and still on the base branch.
120
+
121
+ **`/spec-next` needs no argument now.** Say **yes** and `/spec-start` carries on
122
+ into a bare `/spec-next`; say **no** and typing `/spec-next` an hour later does
123
+ the same thing, because you are already standing in the right place. Neither is
124
+ assumed, because provisioning is cheap and reversible while a phase build is
125
+ neither.
126
+
127
+ **This is not a loosened refusal.** A bare `/spec-next` still refuses to build a
128
+ spec it is not standing in, exactly as before — what changed is where you are
129
+ standing, not how weakly the rule reads. `--worktree <path>` survives beside it as
130
+ the explicit way to build a spec you are *not* in, and a path you pass is still
131
+ not a path anything guessed.
132
+
133
+ **Leaving is a `cd` too.** `/spec-complete` and `/spec-cancel` delete the
134
+ worktree, which is now the directory you are standing in, so both tell you to `cd`
135
+ to the primary checkout first. `git worktree remove` **succeeds** on the tree you
136
+ occupy rather than refusing — the teardown looks fine and every command after it
137
+ dies with `Unable to read current working directory`.
138
+
139
+ **The `--worktree` build checks itself.** On that path, `/spec-next` first records
140
+ what your primary checkout looked like, and afterwards reports anything that
141
+ appeared in it — the signature of a relative path that missed the worktree. It
142
+ reports rather than accuses: it cannot know who wrote a file, so it names both
143
+ readings and deletes nothing.
144
+ `skitterspec spec-env resolve <spec> --record-primary` and
145
+ `--assert-primary-clean` are the engine underneath, usable on their own.
146
+
147
+ ### What to do
148
+
149
+ 1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
150
+ 2. **Delete the `open` block from `specs/.core/env.config.json`**, if you have
151
+ one. Optional — it is ignored either way — but leaving it implies a setting
152
+ that no longer does anything.
153
+ 3. **Answer the question `/spec-start` now asks.** In `worktree` mode it offers
154
+ phase 1 before it finishes. Take the offer and it is built there and then;
155
+ decline and you are left standing in the provisioned worktree, free to type
156
+ `/spec-next` whenever you like. Both endings are fully supported — decline when
157
+ the phase is a big one and you would rather spend a fresh context on it.
158
+ 4. **Expect your shell to move, whichever you answer.** Anything you had queued
159
+ for the primary checkout — a `git` command, a script, a relative path — now
160
+ runs in the worktree instead. `cd` back when you want the base branch, and note
161
+ that `/spec-complete` and `/spec-cancel` require exactly that before they tear
162
+ the worktree down.
163
+ 5. **Use `/spec-diff` to read the work** rather than reaching for a terminal in
164
+ the worktree. It is gated on nothing — half a phase, a hand edit, or a
165
+ colleague's branch are all ordinary inputs.
166
+
167
+ `checkout` mode is unchanged.
168
+
169
+ ## `@skitterbyte/skitterspec-linear` v12 → v13 (starting a spec offers phase 1)
170
+
171
+ The same change as `@skitterbyte/skitterspec` v18 → v19 above — this
172
+ distribution composes the same lifecycle skills. Read that entry first.
173
+
174
+ ### Breaking change
175
+
176
+ **`spec-sync push` is now `spec-sync plan`.** The verb computes a create/update
177
+ plan and performs no network I/O; `spec-sync apply` is what writes to Linear.
178
+ Calling it `push` put three unrelated things behind one word — this verb, the
179
+ `/spec-push` skill, and `git push` — and it was the one that pushes nothing.
180
+
181
+ ```
182
+ skitterspec spec-sync plan <spec> --workspace-states <file> --json > plan.json
183
+ skitterspec spec-sync apply <spec> --plan plan.json
184
+ ```
185
+
186
+ The old name is **not** aliased. It is recognised and exits 1 naming its
187
+ replacement, so a script that calls it fails loudly with the fix in the message
188
+ rather than drifting on a name that will be removed later.
189
+
190
+ **Your `linear.config.json` needs no change.** The `"push"` values under
191
+ `sync.fieldOwnership` — `assignee: "push"`, `description: "push"`,
192
+ `workflowState: "push"` — are a different vocabulary: they name a direction of
193
+ ownership, not a subcommand. They are untouched and still mean what they meant.
194
+ Do not search-and-replace `push` in your config.
195
+
196
+ ### What else is here
197
+
198
+ **One thing here is Linear-specific.** `/spec-start` now pushes to Linear itself,
199
+ right after it commits the spec's move to `in-progress/`. It used to push nothing
200
+ and leave the mirror to the refresh `/spec-next` runs — immediate in `checkout`
201
+ mode, but in `worktree` mode hours away or never. Until it came, the issue sat in
202
+ its old workflow state with nobody assigned while the repo read `in-progress`
203
+ with a developer on it. Expect one more Linear call per `/spec-start`, and expect
204
+ the issue to be current the moment the spec is in flight. Nothing else changes:
205
+ sync is still one-way, and an unlinked spec is still skipped rather than minted.
206
+
3
207
  ## `@skitterbyte/skitterspec` v17 → v18 (a spec is built in its own worktree)
4
208
 
5
209
  ### Breaking change
@@ -90,8 +294,7 @@ landed and this one was missed.)
90
294
 
91
295
  ## `@skitterbyte/skitterspec-linear` v9 → v10 (`push` validates your issue states)
92
296
 
93
- **`spec-sync push` now refuses to run until the configured `states` names have
94
- been checked against your Linear workspace.** The check itself is not new — it
297
+ **`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
95
298
  already existed on `spec-sync status --workspace-states` — but it was advisory,
96
299
  and skipping it sent a state name Linear **silently ignores**: the description
97
300
  lands, the issue never moves, and nothing errors. The base
@@ -105,8 +308,7 @@ lands, the issue never moves, and nothing errors. The base
105
308
  | A configured state absent from the workspace | pushed, silently no-op | **exits 1**, naming the workspace's real states |
106
309
 
107
310
  `/spec-push` handles this for you — it fetches the workspace's issue
108
- workflow-state names over MCP and passes them on. **Nothing changes if you drive
109
- sync through the skill.** Only a direct CLI caller needs updating.
311
+ 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.
110
312
 
111
313
  ### What to do
112
314
 
@@ -156,8 +358,7 @@ sync through the skill.** Only a direct CLI caller needs updating.
156
358
  **v9 remaps the Linear mirror.** A spec is now a Linear **issue** (not a Project),
157
359
  each phase a **sub-issue** (not a Milestone), and **tasks are no longer synced**
158
360
  (they stay in the repo phase files). This collapses a large spec from ~1 project +
159
- N milestones + dozens of task-issues down to **one issue + one sub-issue per
160
- phase**. The base `@skitterbyte/skitterspec` is unaffected (still v15).
361
+ N milestones + dozens of task-issues down to **one issue + one sub-issue per phase**. The base `@skitterbyte/skitterspec` is unaffected (still v15).
161
362
 
162
363
  ### Breaking changes
163
364
 
@@ -274,10 +475,7 @@ both as optional — `/spec-review` adds them if you want them.
274
475
 
275
476
  ## `@skitterbyte/skitterspec` v2 → v3 (slimmer surface + local traffic diversion)
276
477
 
277
- **v3 shrinks the everyday command surface to five verbs — `spec → go → connect →
278
- commit → complete` — by folding provisioning, teardown, and grooming into the
279
- lifecycle skills, and adds `/spec-connect` for testing a worktree at your normal
280
- `localhost` URL.** (`@skitterbyte/skitterspec-linear` moves to v2.0.0 in lockstep.)
478
+ **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.)
281
479
 
282
480
  ### Removed skills (breaking) → where they went
283
481
 
package/README.md CHANGED
@@ -6,13 +6,14 @@ Spec-driven development for [Claude Code](https://claude.com/claude-code) — a
6
6
  ```
7
7
  /spec → /spec-start → /spec-next → /commit → /spec-complete
8
8
  plan build it test it live save it finish + land
9
+ ↳ /spec-diff — read what the phase changed
9
10
  ```
10
11
 
11
12
  Ships the spec-lifecycle skills (`/spec`, `/spec-start`, `/spec-next`, `/spec-complete`,
12
- `/spec-cancel`, `/spec-bug`, `/spec-hotfix`, `/spec-review`, `/spec-init`) plus
13
- per-spec **isolation** — a git worktree per in-progress spec, Docker on demand,
14
- host dev servers on reserved ports, and `/spec-connect` to test a worktree at your
15
- normal `localhost` URL.
13
+ `/spec-cancel`, `/spec-bug`, `/spec-hotfix`, `/spec-review`, `/spec-diff`,
14
+ `/spec-init`) plus per-spec **isolation** — a git worktree per in-progress spec,
15
+ Docker on demand, host dev servers on reserved ports, and `/spec-connect` to test
16
+ a worktree at your normal `localhost` URL.
16
17
 
17
18
  ```sh
18
19
  npx @skitterbyte/skitterspec init
@@ -63,6 +64,54 @@ no external install), so you test at the exact URL you always use.
63
64
  `/spec-connect main` hands the ports back. Exclusive: one spec at a time. See
64
65
  `specs/.core/env.config.md` for the `dev`/`proxy` config.
65
66
 
67
+ ## Reading the diff — `/spec-diff`
68
+
69
+ A phase is built in its own worktree, so `git diff` in your terminal answers
70
+ about the base branch — and a 350-line diff read as terminal text is scrolling,
71
+ not review. **`/spec-diff`** collects the worktree's changes with `git -C` and
72
+ writes a self-contained HTML page: whole-file context that folds away, a file
73
+ tree, new files included. Open it locally, or publish it and read it on a phone.
74
+
75
+ The page lands in `.spec-env/reviews/<spec>.html`, which is gitignored — so
76
+ reviewing a branch leaves no change in the branch you are reviewing. **The diff never passes through the model**, so it costs no context tokens however large it
77
+ is; the optional *written* review (a short read plus `flag`/`confirm`/`good`
78
+ notes) is the part that costs, and it is offered rather than assumed. Publishing
79
+ is always opt-in, and one page per spec — later phases update the same link.
80
+
81
+ `/spec-next` writes the page at the end of every phase. Nothing about it depends
82
+ on where your shell is.
83
+
84
+ ## One ending, every skill — `.claude/rules/spec-reports.md`
85
+
86
+ Every skill finishes with the same block, and says nothing while it runs beyond
87
+ a question it cannot answer itself or a failure at the moment it happens:
88
+
89
+ ✅ **Phase 2 built** — `feat-orders`, 2 of 4
90
+
91
+ | | |
92
+ |---|---|
93
+ | **Branch** | `spec/feat-orders` · 3 commits, clean |
94
+ | **Built** | POST /orders handler, orders schema |
95
+ | **Tests** | 128 passed · npm test |
96
+ | **Review** | 7 files, +212 −18 · [open the page](file:///…) — want a written review before you commit? |
97
+ | **Follow-ups** | none |
98
+ | **Next** | `/spec-next` → phase 3 (Auth) |
99
+
100
+ Four verdicts, and the last two are different facts about your repo: `✅` done ·
101
+ `⚠️` done with caveats · `❌` failed part-way, so there is a mess to clear ·
102
+ `⏸` refused before acting, so nothing changed.
103
+ **A refusal emits the block too**, so "nothing happened" is a reported outcome
104
+ rather than an absent one.
105
+
106
+ Fields come from a fixed vocabulary in a fixed order, and a skill emits only the
107
+ ones it declares — `Next` is last because it is the only row you act on. (A `Tracker` row leads the
108
+ table once a ticketing provider is installed; the base has none, so it never
109
+ appears.)
110
+ `Follow-ups` is always there: a recorded `none` is a decision where a missing
111
+ line is an oversight. The block covers **that run only** — what else is in
112
+ flight is a different question, and answering it here leaves you unable to tell
113
+ what followed from the run you just watched.
114
+
66
115
  ## Production hotfixes — `/spec-hotfix`
67
116
 
68
117
  When prod is on a tagged release, a fix must be built on **that** version, not
@@ -2,14 +2,50 @@
2
2
 
3
3
  Spec-driven development runs through the lifecycle **skills** — use them so
4
4
  structure and lifecycle stay consistent. The everyday loop is
5
- **`spec → start → next → commit → complete`**, with `/spec-connect` when you want to test
6
- the spec in a browser.
5
+ **`spec → start → next → commit → complete`**, with **`/spec-diff`** to read what
6
+ a phase changed and `/spec-connect` when you want to test the spec in a browser.
7
7
 
8
8
  **Skills vs commands.** The lifecycle skills are read by Claude, which exercises
9
9
  judgment. `/spec-connect` and `/spec-live` are **slash commands** instead — each
10
10
  pre-executes one `spec-env` verb and relays it, so only you can run them; a
11
11
  skill that wants one will tell you to type it.
12
12
 
13
+ **Seeing the work.** A phase is built in its own worktree, so `git diff` in your
14
+ terminal answers about the base branch. **`/spec-diff`** renders that worktree's
15
+ diff as a self-contained HTML page you open locally — or publish, and read on a
16
+ phone. The diff never passes through the model, so the page costs no context
17
+ tokens however large it is; the optional written review is the part that costs,
18
+ and it is offered rather than assumed. `/spec-next` writes the page at the end of
19
+ every phase.
20
+
21
+ **Handing the review back.** The page takes marks: tick `✓ accept` per file as
22
+ you read, note anything against a line or a whole file, answer the questions a
23
+ written review asked — then **end it in a decision**. Three buttons, each
24
+ carrying its own verdict:
25
+ `✓ Commit` commits it, `✓ Commit & Continue` commits and builds the next phase,
26
+ `↺ Request changes` sends it straight back to be worked, `… Discuss first` asks
27
+ you what's up. A **served** page hands the pass to
28
+ the engine, which **holds** it and shows a six-digit code. Type `/spec-reviewed`
29
+ and the waiting pass is picked up and acted on; paste the code after it
30
+ (`/spec-reviewed 324199`) to name one exactly, which matters only when two are
31
+ waiting. **Nothing pushes** — a device that reaches your page cannot reach this
32
+ conversation, which is what keeps a stray approval out of your review, and is
33
+ why the code is an address rather than a password. A `file://` page has no server to talk to, so it
34
+ copies and you paste, as before. Approve is unavailable while a note is open —
35
+ you asked for something, so it cannot also be fine — and it hands off to your
36
+ own commit skill (`review.commitWith`, `/commit` by default) rather than a copy
37
+ living here. Fixes come back as resolutions, so the next render shows each note
38
+ struck through with what changed. An accept remembers the file's content, so it
39
+ lapses by itself when that file changes again. The marks are information —
40
+ nothing gates on them; the verdict is the one thing you choose, once.
41
+
42
+ **One ending.** Every spec skill finishes with the same block — a verdict
43
+ (`✅` · `⚠️` · `❌` · `⏸`), then a table of the fields that skill declares,
44
+ ending on the one thing to do next. A refusal emits it too, so "nothing
45
+ happened" is reported rather than absent, and `Follow-ups` is always there
46
+ because a recorded `none` is a decision. Skills stay quiet while they run. The
47
+ shape lives in **`.claude/rules/spec-reports.md`**.
48
+
13
49
  The skill table, the spec type/folder conventions and the per-spec isolation
14
50
  model all live in **`.claude/rules/spec-planning.md`**, the canonical reference
15
51
  every spec skill points at. Tailor its per-phase test commands to this stack.
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Expose one spec's dev servers on the canonical localhost ports (or `main` to hand them back)
3
- argument-hint: "[spec|main]"
2
+ description: Expose one spec's dev servers on the canonical localhost ports bare uses the spec you are on, `main` hands them back
3
+ argument-hint: "[<spec> | main]"
4
4
  allowed-tools: Bash({{exec}} skitterspec spec-env connect:*)
5
5
  disable-model-invocation: true
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Put one spec live on the already-running dev server — `<spec>` takes it, a bare `take` takes the spec you are on, `main` hands the instance back
3
- argument-hint: "[<spec> | main | take | release | abort | status]"
2
+ description: Put one spec live on the already-running dev server — bare takes the spec you are on, `<spec>` takes that one, `main` hands the instance back
3
+ argument-hint: "[<spec> | main | status | release | abort]"
4
4
  allowed-tools: Bash({{exec}} skitterspec spec-env live:*)
5
5
  disable-model-invocation: true
6
6
  ---
@@ -25,9 +25,6 @@
25
25
  "enabled": true,
26
26
  "host": "127.0.0.1"
27
27
  },
28
- "open": {
29
- "command": ""
30
- },
31
28
  "registry": ".spec-env/registry.json",
32
29
  "branch": {
33
30
  "pattern": "{type}/{slug}",
@@ -41,6 +38,12 @@
41
38
  "refuseTeardownIfDirty": true,
42
39
  "refuseTeardownIfUnpushed": true
43
40
  },
41
+ "review": {
42
+ "reader": "detect",
43
+ "servePort": 7777,
44
+ "serveOnRemote": true,
45
+ "commitWith": "/commit"
46
+ },
44
47
  "live": {
45
48
  "migrations": []
46
49
  }
@@ -1,11 +1,12 @@
1
1
  # `env.config.json` — per-spec isolation config
2
2
 
3
3
  Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
4
- stack + host dev servers + a front-door proxy + an optional opener per
5
- in-progress spec). Provisioning is folded into `/spec-start`, teardown into
6
- `/spec-complete` · `/spec-cancel`, and traffic diversion is `/spec-connect`; the
7
- `skitterspec spec-env <up|down|prune|dev|connect|integrate>` CLI is the engine
8
- beneath them.
4
+ stack + host dev servers + a front-door proxy per in-progress spec).
5
+ Provisioning is folded into `/spec-start`, teardown into `/spec-complete` ·
6
+ `/spec-cancel`, traffic diversion is `/spec-connect`, and reading a spec's diff
7
+ is `/spec-diff`; the `skitterspec spec-env
8
+ <up|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` CLI is
9
+ the engine beneath them.
9
10
 
10
11
  **Once this file is present, isolation is the default policy:** `/spec-start` gives
11
12
  **every** in-progress spec its own git worktree automatically. Docker is a
@@ -31,11 +32,12 @@ no live `env.config.json` was found.
31
32
  // Where a spec's branch gets built.
32
33
  //
33
34
  // "worktree" (default) — every spec gets its own git worktree. Several
34
- // specs run side by side and `main` stays free, at the cost of
35
- // one terminal session per spec (`/spec-start` sets it up for you).
35
+ // specs run side by side and `main` stays free. `/spec-start`
36
+ // moves your session into the spec's worktree with a `cd`, so
37
+ // the terminal you are already in follows the work.
36
38
  // "checkout" — the branch is built in the primary checkout instead. One
37
- // spec at a time, but no second session and no hand-off: the
38
- // terminal you are already in follows the work.
39
+ // spec at a time, and nowhere else to stand: the work comes to
40
+ // your terminal rather than your terminal going to it.
39
41
  //
40
42
  // Pick it for how you work, not for what this repo contains — a project with
41
43
  // no dev servers may still want several specs in flight. An unrecognised
@@ -124,24 +126,6 @@ no live `env.config.json` was found.
124
126
  "host": "127.0.0.1" // bind host for the canonical ports
125
127
  },
126
128
 
127
- // Optional, editor/terminal-agnostic opener — the FALLBACK for reaching a
128
- // worktree. `/spec-start` normally moves the session you typed into into the
129
- // worktree, and then there is nothing to open: it runs this only when it
130
- // could not switch in place (the session is already inside another worktree,
131
- // or the harness cannot move it). Run after provisioning and bootstrap, so
132
- // the session opens onto a tree that is ready to work in. The template is
133
- // expanded with {worktreePath}, {slug}, {branch}, {projectName},
134
- // {portOffset}.
135
- // Empty = nothing is opened (the path is just printed), which is how you turn
136
- // the auto-open off, and the right value unless you actually want a second
137
- // window on the fallback path. A non-interactive run skips it either way — an
138
- // opened window nobody is sitting at helps no one.
139
- // Examples: "code {worktreePath}", "tmux new-window -c {worktreePath}",
140
- // or a "warp://..." deeplink for Warp users.
141
- "open": {
142
- "command": ""
143
- },
144
-
145
129
  // Machine-local slot registry (spec → slot index). Resolved against the
146
130
  // primary checkout root, shared by all worktrees, gitignored.
147
131
  "registry": ".spec-env/registry.json",
@@ -193,9 +177,10 @@ no live `env.config.json` was found.
193
177
  "refuseTeardownIfUnpushed": true
194
178
  },
195
179
 
196
- // What teardown cleans up beyond this machine. `/spec-start` pushes the spec
197
- // branch when it provisions, so without this a completed spec leaves a merged
198
- // branch on the remote forever. `deleteRemoteBranch`:
180
+ // What teardown cleans up beyond this machine. Nothing publishes a spec
181
+ // branch for you, so a remote copy exists only because you pushed it by hand —
182
+ // and without this a completed spec leaves that merged branch on the remote
183
+ // forever. `deleteRemoteBranch`:
199
184
  // "prompt" (default) — plan `git push <remote> --delete <branch>` in its own
200
185
  // "confirm with the user first" section; /spec-complete and
201
186
  // /spec-cancel ask before running it.
@@ -232,6 +217,68 @@ no live `env.config.json` was found.
232
217
  "bump": "patch",
233
218
  "cherryPickMain": true,
234
219
  "targets": []
220
+ },
221
+
222
+ // Reading a spec's diff (`spec-env review`, `/spec-diff`).
223
+ //
224
+ // `reader` decides how the page's LOCATION IS WORDED, and — through
225
+ // `serveOnRemote` below — whether the engine stands its local server up so a
226
+ // remote reader gets a link that opens. It never decides to PUBLISH. Three
227
+ // values:
228
+ // "local" — you are at the machine holding the page; a file:// URL opens.
229
+ // "remote" — you are not; it does not, so the page is served instead.
230
+ // "detect" — work it out (the default).
231
+ // An explicit "local"/"remote" is BELIEVED WITHOUT SNIFFING: you know where
232
+ // you are reading, and no signal outranks being told. Detection is only the
233
+ // default, and it has three outcomes rather than two — local, remote, and
234
+ // unknown. Unknown behaves exactly as the tool did before any of this existed
235
+ // (the file:// URL, no warning), because a wrong "local" prints a dead link
236
+ // and a wrong "remote" warns at someone whose link works fine. An
237
+ // unrecognised value falls through to "detect", so a typo cannot become a
238
+ // confident answer. Default: detect.
239
+ //
240
+ // `servePort` is the default port for `spec-env review serve`, which renders
241
+ // every spec's diff per request on one local server. `--port` overrides it per
242
+ // run. The server binds 127.0.0.1 unless `--host 0.0.0.0` is passed, which
243
+ // mints an unguessable path token and prints the LAN URL including it —
244
+ // anyone holding that URL can read every spec's diff while it runs.
245
+ // Default: 7777.
246
+ //
247
+ // `serveOnRemote` is whether a "remote" reader may have that server started
248
+ // FOR them. On (the default) the engine brings it up, binds 0.0.0.0, and puts
249
+ // the served URL on the `open:` line — best-guess network address first, the
250
+ // rest listed under it, because the guess reads interface names and a VPN or
251
+ // an unusual adapter will fool it. Off, you get the file:// URL with its
252
+ // "will not open where you are reading" marker and the command to type.
253
+ // Either way NOTHING IS PUBLISHED on a detection: a server is one process
254
+ // ended by one flag, while a published page is one this tooling cannot
255
+ // remove, so that half stays an explicit ask. Teardown names a server that
256
+ // served the last spec, and `spec-env prune` reaps a pidfile whose process is
257
+ // gone. Default: true.
258
+ //
259
+ // `commitWith` names the skill a COMMITTING verdict hands off to. A review
260
+ // page ends in a verdict — commit, commit & continue, request changes,
261
+ // discuss — and the point of the first two is that the commit follows from
262
+ // the reading rather than costing a separate decision. Skitterspec never
263
+ // commits through a skill it vendored: `/commit` ships with skittership, a
264
+ // different package, and a copy living here would fork it. Two shapes:
265
+ // "/commit" — the default; hand off to skittership's commit skill.
266
+ // "<name>" — any other skill your project installs.
267
+ // With the named skill unavailable, the committing branch commits directly —
268
+ // stage, typecheck, test, conventional message — and SAYS it took that path,
269
+ // because a commit made under rules nobody configured must not read as one
270
+ // made under /commit.
271
+ //
272
+ // THERE IS NO OFF SWITCH. `"none"` existed and was removed: it produced a
273
+ // verdict that records itself and does nothing, which is the one thing a
274
+ // review page must not offer — a review is the guard in front of an action.
275
+ // Recording an approval for SOMEONE ELSE to act on is a separate mechanism,
276
+ // not a value of this key. Default: "/commit".
277
+ "review": {
278
+ "reader": "detect",
279
+ "servePort": 7777,
280
+ "serveOnRemote": true,
281
+ "commitWith": "/commit"
235
282
  }
236
283
  }
237
284
  ```