@skitterbyte/skitterspec 22.4.0 → 23.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,41 @@
1
1
  # Migration guide
2
2
 
3
+ ## `@skitterbyte/skitterspec` v22 → v23 (the engine runs the worktree bootstrap)
4
+
5
+ ### Breaking change — `spec-env up` no longer prints the worktree steps
6
+
7
+ `spec-env up` (and `spec-env nospec`) used to end with a
8
+ `then, in the worktree, run:` block of shell commands: one per seeded file, the
9
+ env-file write, each `setup` command, and `docker compose … up -d`. They now end
10
+ with one line instead:
11
+
12
+ ```
13
+ then, run:
14
+ skitterspec spec-env bootstrap <name>
15
+ ```
16
+
17
+ **`spec-env bootstrap [<spec>]` runs those steps itself**, with the worktree
18
+ as its cwd: it seeds the gitignored files, writes `docker.envFile`, runs `setup`
19
+ and brings the stack up, in that order. The behaviour is the same as before: it
20
+ is idempotent, prints `seeded …` / `exists — skipped` / `not in main — skipped`
21
+ per seed, refuses to write through a symlinked env file, and stops at the first
22
+ failing step, naming it. `--docs` seeds only. `--plan` describes the steps
23
+ without running them, and `--json` reports each step and its outcome.
24
+
25
+ **Why.** The printed commands had to be copied word for word, and agents did
26
+ not copy them faithfully. A project's secrets hook refused any Bash call naming
27
+ a `.env` file, so every rewritten seed command was blocked and bootstrap stopped
28
+ half-way. The new command line names no file.
29
+
30
+ **What to change.** The shipped skills already call `bootstrap`; run
31
+ `skitterspec update` to take them. Anything of your own that ran the printed
32
+ block (a script, or a skill you edited) should run
33
+ `skitterspec spec-env bootstrap <name>` after the `git worktree add` instead.
34
+ Nothing in `env.config.json` changes.
35
+
36
+ **If you added a secrets-hook exception for the printed seed commands**, you
37
+ can remove it now. Nothing shipped prints them any more.
38
+
3
39
  ## `@skitterbyte/skitterspec` v21 → v22 (a review port per repo, and every render serves)
4
40
 
5
41
  ### Breaking change 1 — `review.serveOnRemote` is now `review.serve`
@@ -127,6 +163,12 @@ can run it.
127
163
  committed file, so it changes for everyone who pulls and leaves that tree dirty.
128
164
  The engine says so when it does.
129
165
 
166
+ ## `@skitterbyte/skitterspec-linear` v17 → v18 (the engine runs the worktree bootstrap)
167
+
168
+ The same change as `@skitterbyte/skitterspec` v22 → v23 above — this
169
+ distribution composes the same engine. Read that entry; nothing here is
170
+ Linear-specific.
171
+
130
172
  ## `@skitterbyte/skitterspec-linear` v16 → v17 (a review port per repo, and every render serves)
131
173
 
132
174
  The same two changes as `@skitterbyte/skitterspec` v21 → v22 above — this
package/README.md CHANGED
@@ -164,7 +164,8 @@ live: off — /spec-live to put it live
164
164
  `local` and `network` are two doors into one room — the same server, the same
165
165
  waiting verdict — so the wait covers both. `remote` is a published page, which
166
166
  is a separate store: a verdict there needs `/spec-reviewed`, and it is off until
167
- you type `/spec-remote-review`. The `live:` line is the other way to judge a
167
+ you type `/spec-remote-review`, which permits **that spec** to publish, publishes
168
+ the waiting page and re-shows the links. The `live:` line is the other way to judge a
168
169
  change: whether it is also **running**, and the page has a button to put it
169
170
  there.
170
171
 
@@ -182,7 +183,7 @@ All optional, in `specs/.core/env.config.json`:
182
183
  |-----|---------|------|
183
184
  | `review.serve` | `"always"` | whether a render stands the local server up. `"never"` gives you a `file://` page, whose buttons copy a command instead of sending |
184
185
  | `review.allowNetwork` | `true` | whether that server binds every interface, so the page opens on your phone |
185
- | `review.allowRemote` | `false` | whether publishing is permitted at all. `/spec-remote-review` toggles it; it permits publishing, it does not publish |
186
+ | `review.allowRemote` | `false` | whether publishing is permitted for every spec — the project default. `/spec-remote-review` permits one spec at a time in a gitignored sidecar and never edits this key (`review allow remote --project` does) |
186
187
  | `review.required` | `true` | whether a finished phase owes a verdict |
187
188
  | `review.commitWith` | `"/commit"` | the skill a committing verdict hands off to |
188
189
  | `review.reader` | `"detect"` | where you are reading — `local`, `remote`, or detect it |
@@ -1,22 +1,37 @@
1
1
  ---
2
- description: Permit or forbid publishing a review for a reader off your network — bare toggles it, `on`/`off` set it explicitly
2
+ description: Permit or forbid publishing this spec's review for a reader off your network — bare toggles it, `on`/`off` set it explicitly
3
3
  argument-hint: "[on | off]"
4
4
  allowed-tools: Bash({{exec}} skitterspec spec-env review allow remote:*)
5
5
  disable-model-invocation: true
6
6
  ---
7
7
  !`{{exec}} skitterspec spec-env review allow remote --set "$ARGUMENTS"`
8
8
 
9
- Relay the engine output above verbatim. Add nothing and run nothing else.
9
+ Relay the engine output above verbatim. Add nothing to it.
10
+
11
+ **Then one thing, and only when the engine asked for it.** Where the output
12
+ carries a `review:` and a `publish:` line, typing this command **is** the ask
13
+ to publish that page: the banner named this command beside a review the reader
14
+ could not reach, so the link is what they are waiting for. Publish it exactly
15
+ as `/spec-diff` §6 describes (run the `publish:` command, publish the copy it
16
+ names with a store, write the URL file), then re-emit the review banner with
17
+ all three tiers, `remote` now carrying the URL. A wait already running keeps
18
+ running; do not restart it or the server. If the harness refuses the publish,
19
+ say so in one line and keep the banner as it was, because local and network
20
+ still work.
21
+
22
+ With no `publish:` line, run nothing else. Nothing has a page to publish, or
23
+ the tier was turned off.
10
24
 
11
25
  **Bare toggles.** That is the common case: you are looking at a render whose
12
26
  `remote:` line says which way it currently is, and what you want is the other
13
27
  one. `on` and `off` are there for when you are not looking at it.
14
28
 
15
- **It permits publishing; it publishes nothing.** A published page is one
16
- skitterspec cannot delete, so publishing stays an explicit ask
17
- (`/spec-diff` §6) — and a verdict pressed on one needs `/spec-reviewed`, because
18
- nothing pushes from an artifact's store into a conversation.
29
+ **It is per spec.** The engine resolves the spec from the worktree you are
30
+ standing in and records the permission in the gitignored review store, so
31
+ the base branch is untouched and no other spec may publish because of it.
32
+ `skitterspec spec-env review allow remote --project` is the committed,
33
+ every-spec form, for a team that wants it.
19
34
 
20
- **It writes a committed file.** `specs/.core/env.config.json` in the primary
21
- checkout, whichever tree you ran this from — so it changes for everyone who
22
- pulls and leaves that tree dirty. The engine says so; do not soften it.
35
+ **A published page is one skitterspec cannot delete**, and a verdict pressed
36
+ on one needs `/spec-reviewed`, because nothing pushes from an artifact's store
37
+ into a conversation.
@@ -5,7 +5,7 @@ stack + host dev servers + a front-door proxy per in-progress spec).
5
5
  Provisioning is folded into `/spec-start`, teardown into `/spec-complete` ·
6
6
  `/spec-cancel`, traffic diversion is `/spec-connect`, and reading a spec's diff
7
7
  is `/spec-diff`; the `skitterspec spec-env
8
- <up|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` CLI is
8
+ <up|bootstrap|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` CLI is
9
9
  the engine beneath them.
10
10
 
11
11
  **Once this file is present, isolation is the default policy:** `/spec-start` gives
@@ -90,8 +90,9 @@ through to a documented conservative default; see the field notes below.
90
90
  "projectNamePattern": "{repoSlug}_{slug}", // → COMPOSE_PROJECT_NAME
91
91
  "portBase": 3000, // first port of slot 0's block
92
92
  "portsPerSpec": 10, // block width; slot n → portBase + n*portsPerSpec
93
- // Written into the worktree, and passed to compose as `--env-file`. A
94
- // Docker spec's worktree steps run in this order: seedFiles, this env file
93
+ // Written into the worktree by `spec-env bootstrap`, and passed to compose
94
+ // as `--env-file`. A Docker spec's worktree steps run in this order:
95
+ // seedFiles, this env file
95
96
  // (COMPOSE_PROJECT_NAME and PORT_OFFSET appended only where missing, so a
96
97
  // value `setup` or you put there is kept), `setup`, then
97
98
  // `docker compose … up -d` — all from inside the worktree, so the stack
@@ -106,7 +107,7 @@ through to a documented conservative default; see the field notes below.
106
107
  },
107
108
 
108
109
  // Gitignored files seeded from the primary checkout into a fresh worktree by
109
- // `spec-env up`, right after `git worktree add` and BEFORE `setup` runs — so a
110
+ // `spec-env bootstrap`, after `git worktree add` and BEFORE `setup` runs — so a
110
111
  // fresh linked worktree (which starts with none of the repo's gitignored files)
111
112
  // has the .env / local secret overrides / local config that setup steps and
112
113
  // git hooks depend on. Without this any setup step that reads .env — a schema
@@ -116,9 +117,9 @@ through to a documented conservative default; see the field notes below.
116
117
  // stays in sync; "copy" makes an independent copy.
117
118
  // files repo-relative paths to seed. A source absent in main is a printed
118
119
  // no-op (not an error); a target that already exists is left untouched
119
- // (idempotent — safe when `spec-env up` re-attaches an existing
120
- // worktree). The main checkout is resolved robustly at run time via
121
- // `git rev-parse --git-common-dir` — no hardcoded repo name or path.
120
+ // (idempotent — safe to re-run `spec-env bootstrap` over an existing
121
+ // worktree). Seeding is done by the engine itself, never printed as
122
+ // shell, so no command line ever names one of these files.
122
123
  // Shorthand: `"seedFiles": [".env", …]` == `{ "mode": "symlink", "files": […] }`.
123
124
  // `.env` is an ILLUSTRATION, not an expectation — skitterspec never reads any
124
125
  // of these files, it only links or copies them. Name whatever your stack keeps
@@ -134,16 +135,33 @@ through to a documented conservative default; see the field notes below.
134
135
  "files": [".env"]
135
136
  },
136
137
 
137
- // Bootstrap commands `spec-env up <spec>` runs IN the worktree, right after
138
- // `git worktree add` (before Docker/dev), on every provision including
138
+ // Bootstrap commands `spec-env bootstrap <spec>` runs IN the worktree, after
139
+ // the seeds and the env file and before the stack, on every provision including
139
140
  // re-attach — so a fresh worktree's dependencies exist and git hooks,
140
141
  // typechecks, builds and tests work immediately instead of failing on a
141
- // missing node_modules. An array, run in order; [] = none. Each string is a
142
- // shell command; {slug}/{branch}/{worktreePath}/{projectName}/{portOffset}
142
+ // missing node_modules. An array, run in order, stopping at the first that
143
+ // exits non-zero; [] = none. Each string is a shell command; {slug}/{branch}/{worktreePath}/{projectName}/{portOffset}
143
144
  // expand (the cwd is already the worktree, so {worktreePath} is usually
144
145
  // redundant). Example: ["pnpm install --frozen-lockfile"].
145
146
  "setup": [],
146
147
 
148
+ // Commands to run in the PRIMARY checkout after a land — the counterpart of
149
+ // `setup`, for whatever landed code needs before the base branch is usable
150
+ // locally (e.g. ["pnpm db:data-migration"] for imperative data migrations
151
+ // your deploy runs and nothing runs on your dev DB). An array, run in order;
152
+ // [] (or absent) = none, and every plan prints exactly what it always did.
153
+ // `spec-env integrate` prints them after the fast-forward (worktree and
154
+ // checkout mode), and also after "already landed" — a land on another
155
+ // machine leaves them pending here. `spec-env hotfix land` prints them
156
+ // after the cherry-pick onto the base branch, and not at all when
157
+ // `hotfix.cherryPickMain` is false, since the base gained nothing.
158
+ // The engine only plans: /spec-complete, /spec-to-main and /no-spec run
159
+ // them (a /spec documents-only land does not). They stop at the first
160
+ // failure, report it as a caveat, and never undo the land or block
161
+ // teardown. Literal strings — tokens are NOT expanded, because they run on
162
+ // the base branch where a spec's {slug} or {portOffset} mean nothing.
163
+ "afterLand": [],
164
+
147
165
  // Host dev servers `spec-env dev up <spec>` starts on the spec's port block
148
166
  // (for apps that run via `pnpm dev` on the host, not inside the Docker stack).
149
167
  // An array so UI + API (or more) are supervised independently; [] = none.
@@ -341,10 +359,14 @@ through to a documented conservative default; see the field notes below.
341
359
  // page opens on your phone, or loopback only. THIS IS WHAT
342
360
  // CHOOSES THE BIND; `reader` no longer does. Default: true,
343
361
  // which matches what the engine already did.
344
- // `allowRemote` — whether PUBLISHING is permitted at all. It permits it; it
345
- // publishes nothing. Default: FALSE, because a published
346
- // page is one skitterspec cannot delete, so it must never
347
- // happen unasked.
362
+ // `allowRemote` — whether PUBLISHING is permitted for EVERY spec. It
363
+ // permits it; it publishes nothing. Default: FALSE,
364
+ // because a published page is one skitterspec cannot
365
+ // delete, so it must never happen unasked. This is the
366
+ // PROJECT DEFAULT: `/spec-remote-review` permits one spec
367
+ // at a time, in a gitignored sidecar beside its page, and
368
+ // never edits this file — `review allow remote --project`
369
+ // is what does.
348
370
  //
349
371
  // `local` and `network` are two doors into ONE ROOM — the page POSTs to
350
372
  // `location.pathname`, so both reach the same server and the same waiting
@@ -107,19 +107,36 @@ function findEngine(projectDir) {
107
107
  // skills carry the rule for everything this matcher cannot see.
108
108
  const WRITE_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit', 'MultiEdit'])
109
109
 
110
+ // The path a write tool names: `file_path` for Edit, Write and MultiEdit,
111
+ // `notebook_path` for NotebookEdit. Anything else is no path at all.
112
+ function writeTarget(input) {
113
+ if (!input || typeof input !== 'object') return null
114
+ for (const key of ['file_path', 'notebook_path']) {
115
+ if (typeof input[key] === 'string' && input[key]) return input[key]
116
+ }
117
+ return null
118
+ }
119
+
110
120
  function main() {
111
121
  const payload = readPayload()
112
122
  if (!payload) allow()
113
123
 
114
124
  if (!WRITE_TOOLS.has(payload.tool_name)) allow()
115
125
 
116
- // The write's own directory decides the answer — a worktree is its own
117
- // checkout, and only the primary one is this guard's business.
126
+ // A worktree is its own checkout, and only the primary one is this guard's
127
+ // business. `cwd` is where the session stands; the target below is where the
128
+ // write lands, and the engine prefers it.
118
129
  const cwd = typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : process.cwd()
119
130
  const session = typeof payload.session_id === 'string' ? payload.session_id : ''
120
131
 
121
132
  const argv = ['spec-env', 'main', 'check', '--dir', cwd]
122
133
  if (session) argv.push('--session', session)
134
+ // The FILE decides where the write lands, not the session's directory. Judged
135
+ // on `cwd` alone, a session on the base branch was refused its own scratchpad
136
+ // under /tmp, and a session in a worktree could write into the primary
137
+ // checkout unseen. No path in the payload → the engine judges `cwd`, as before.
138
+ const target = writeTarget(payload.tool_input)
139
+ if (target) argv.push('--path', target)
123
140
 
124
141
  const result = spawnSync(findEngine(cwd), argv, { cwd, encoding: 'utf8', timeout: 8000 })
125
142
 
@@ -142,4 +159,4 @@ if (require.main === module) {
142
159
  }
143
160
  }
144
161
 
145
- module.exports = { findEngine, WRITE_TOOLS }
162
+ module.exports = { findEngine, WRITE_TOOLS, writeTarget }
@@ -29,8 +29,12 @@ finding one. They are marked `disable-model-invocation`, meaning
29
29
  **only you can run them** — a skill that wants one will tell you to type it
30
30
  rather than invoking it.
31
31
 
32
- **`/spec-remote-review`** is the newest and the smallest: it toggles
33
- `review.allowRemote`, and takes `on`/`off` when you would rather say which.
32
+ **`/spec-remote-review`** is the newest and the smallest: it toggles whether
33
+ **this spec** may publish, and takes `on`/`off` when you would rather say which.
34
+ The permission is per worktree, kept in the gitignored review store, so the base
35
+ branch is never dirtied and no other spec gains it; `review.allowRemote` in the
36
+ committed config is the project default beneath it. Turned on beside a page
37
+ that already exists, it is the ask to publish that page and re-show the stack.
34
38
  It exists because a render's `remote:` line has to name something a person can
35
39
  act on, and `skitterspec spec-env review allow remote` is not what anyone
36
40
  reconstructs from a page they are reading on a phone. Making the line
@@ -174,8 +178,11 @@ your canonical `localhost` ports so you can test it at the normal URL
174
178
  move, header edits, the code) happens on the spec's branch in the worktree; `main`
175
179
  changes only when it merges. Teardown is folded into `/spec-complete` ·
176
180
  `/spec-cancel`. Beneath the skills, `skitterspec spec-env
177
- <up|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
178
- engine. **Omit the spec name anywhere and it uses the worktree you are standing in**,
181
+ <up|bootstrap|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
182
+ engine. `up` plans the fork;
183
+ **`bootstrap` then runs the worktree's own steps itself** — seeding the gitignored files, the env file, `setup`, the stack — so
184
+ the command a skill runs names no file, and a secrets hook that refuses any
185
+ `.env` path has nothing to refuse. **Omit the spec name anywhere and it uses the worktree you are standing in**,
179
186
  else the sole provisioned spec — with no exceptions left: a bare
180
187
  `/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
181
188
  `main`. Teardown drops
@@ -46,9 +46,23 @@ skitterspec spec-env nospec <name>
46
46
  ```
47
47
 
48
48
  It records the name as a **specless branch** and prints the plan. Run the
49
- commands it prints, then move this session into the worktree with a plain `cd`
50
- — the Bash working directory persists between calls, so from here on the work
51
- happens on its own branch.
49
+ `git worktree add` it prints, then move this session into the worktree with a
50
+ plain `cd` and run the `then, run:` line where there is one:
51
+
52
+ ```
53
+ cd "<worktreePath>" && skitterspec spec-env bootstrap <name>
54
+ ```
55
+
56
+ That runs the project's `setup` in the worktree and stops at the first failing
57
+ command, naming it. The Bash working directory persists between calls, so from
58
+ here on the work happens on its own branch.
59
+
60
+ **The worktree steps are the engine's to run, never yours.** Seeding the
61
+ gitignored files, the env file, `setup` and the stack all happen inside
62
+ `skitterspec spec-env bootstrap <name>` — one call that names no file. Do not
63
+ reconstruct them as shell: they used to be printed that way, an agent's copy was
64
+ never faithful, and a project's secrets hook refused every rewrite that named a
65
+ `.env` path. `bootstrap --plan` says what it would do, for reading only.
52
66
 
53
67
  **Confirm the move landed** rather than reading silence as success
54
68
  (`.claude/rules/negative-checks.md` rule 1): `skitterspec spec-env resolve` with
@@ -156,6 +170,14 @@ three, in that order — never the bare `page:` path.
156
170
  **forgets the specless record**, which is what stops a torn-down name going on
157
171
  answering to every verb with a path that is no longer there.
158
172
 
173
+ **Between the two, run the after-land commands.** Where the `integrate` plan
174
+ ends with a `then, in the primary checkout (<path>), run:` section — the
175
+ project's `afterLand` — run those commands there, in order, once the
176
+ fast-forward has succeeded, and on an already-landed no-op too. A lockfile
177
+ refresh is exactly the land that needs them.
178
+ **Stop at the first non-zero exit**: the run becomes `⚠️`, naming the command that failed and the ones that
179
+ did not run, and it **never undoes the land and never stops the `down`**.
180
+
159
181
  **If the rebase conflicts, stop.** `git rebase --abort`, leave the commit on
160
182
  the branch, tear nothing down, and report `❌` with the conflict — there is a
161
183
  standing worktree, which is what separates `❌` from `⏸`.
@@ -181,7 +203,9 @@ the shape; this section carries only what is specific here.
181
203
  **Verdicts**
182
204
 
183
205
  - `✅` — the work is done, the tests are green, and the verdict was acted on.
184
- - `⚠️` — done and green, with something worth knowing.
206
+ - `⚠️` — done and green, with something worth knowing — an after-land command
207
+ that failed, for one. `Notes` carries what the after-land commands ran, or
208
+ where they stopped; nothing about them when none are configured.
185
209
  - `❌` — the tests are red, or the land failed part-way. Quote the failure and
186
210
  say what is standing.
187
211
  - `⏸` — refused before acting: no name given, the name is already a spec, the
@@ -87,9 +87,11 @@ a worktree costs nothing to obtain and everything is still ahead of it:
87
87
  skitterspec spec-env up <name> --docs
88
88
  ```
89
89
 
90
- Run the commands it prints, then move this session into the worktree with a
91
- plain `cd` — the Bash working directory persists between calls, so from here on
92
- the spec is written on its own branch. Confirm the move landed rather than
90
+ Run the commands it prints under `to provision, run:`, and the
91
+ `skitterspec spec-env bootstrap <name> --docs` under `then, run:` where there is
92
+ one (it seeds the gitignored files, nothing more). Then move this session into
93
+ the worktree with a plain `cd` — the Bash working directory persists between
94
+ calls, so from here on the spec is written on its own branch. Confirm the move landed rather than
93
95
  reading silence as success (`.claude/rules/negative-checks.md` rule 1):
94
96
  `skitterspec spec-env resolve` with no argument must name this spec. If it does
95
97
  not, say so and write the spec where you are standing instead; a spec in the
@@ -363,6 +365,12 @@ It plans a rebase onto the base branch in the worktree, then a
363
365
  refuses on a dirty tree, which is correct here: the commit ran first, so anything
364
366
  still uncommitted at this point is not this spec's and the land must not proceed.
365
367
 
368
+ **Do not run its after-land section.** Where the plan ends with
369
+ `then, in the primary checkout (<path>), run:`, those are the project's
370
+ `afterLand` commands — a data migration, an install — and they exist for code
371
+ that landed. This land is documents only, so it changes nothing they would
372
+ catch up on; `/spec-complete`, `/spec-to-main` and `/no-spec` run them.
373
+
366
374
  **If the rebase conflicts, stop and say so.** It is unlikely on a
367
375
  documents-only branch and not impossible — two specs authored at once can both
368
376
  touch `specs/.core/`. Run `git rebase --abort`, leave the commit sitting on the
@@ -372,7 +380,7 @@ get the land through.
372
380
 
373
381
  **The worktree lifetime follows the verdict, and the asymmetry is deliberate.**
374
382
  `commit-start` keeps it because you are carrying straight on, and `/spec-start`
375
- re-runs `up` over it without `--docs` — which is where the setup commands
383
+ re-runs `up` and `bootstrap` over it without `--docs`, which is where the setup commands
376
384
  Phase B skipped finally run. Plain `commit` tears it down with
377
385
  `skitterspec spec-env down <name>`, because a spec parked in the backlog should
378
386
  hold no worktree and no `node_modules`; `/spec-start` provisions from scratch
@@ -59,8 +59,9 @@ skitterspec spec-env up bug-<name> --docs
59
59
  **The spec does not exist yet, and that is the lane.** `up` opens its authoring
60
60
  branch on exactly three signals — a spec it could not find, `--docs`, and a name
61
61
  shaped `bug-<something>` — records the branch so the name resolves from here on,
62
- and prints a `git worktree add … -b bug/<name>` forked from `main`. Run what it
63
- prints, then move this session into the worktree with a plain `cd`; the Bash
62
+ and prints a `git worktree add … -b bug/<name>` forked from `main`. Run it, and
63
+ the `skitterspec spec-env bootstrap bug-<name> --docs` under `then, run:` where
64
+ there is one, then move this session into the worktree with a plain `cd`; the Bash
64
65
  working directory persists between calls, so from here on the test, the fix and
65
66
  the spec are all written on the bug's own branch. Confirm the move landed rather
66
67
  than reading silence as success (`.claude/rules/negative-checks.md` rule 1):
@@ -80,7 +81,15 @@ tree *runnable* — because provisioning somewhere to write markdown should cost
80
81
  steps from here, in this same tree, and
81
82
  **a tree that cannot run the suite cannot go red**.
82
83
  The flagless re-run re-attaches the worktree that now exists
83
- (no second `-b` fork) and prints the bootstrap steps the flag deferred.
84
+ (no second `-b` fork) and prints `skitterspec spec-env bootstrap bug-<name>` —
85
+ run it, and it runs the steps the flag deferred.
86
+
87
+ **The worktree steps are the engine's to run, never yours.** Seeding the
88
+ gitignored files, the env file, `setup` and the stack all happen inside
89
+ `skitterspec spec-env bootstrap <name>` — one call that names no file. Do not
90
+ reconstruct them as shell: they used to be printed that way, an agent's copy was
91
+ never faithful, and a project's secrets hook refused every rewrite that named a
92
+ `.env` path. `bootstrap --plan` says what it would do, for reading only.
84
93
 
85
94
  **Why the stub is no longer seeded on the base branch.** This step used to open
86
95
  by writing `specs/in-progress/bug-<name>/00-overview.md` in the primary checkout
@@ -113,16 +113,21 @@ the shape; this section carries only what is specific here.
113
113
 
114
114
  - `✅` — every phase done, tests green, landed, torn down.
115
115
  - `⚠️` — finished, with something worth knowing: teardown declined, a tracker
116
- refresh that failed, a hotfix tag still waiting to be pushed.
116
+ refresh that failed, a hotfix tag still waiting to be pushed, an after-land
117
+ command that failed.
117
118
  - `❌` — it acted and stopped part-way — a conflicted rebase, a failed
118
119
  cherry-pick, a red base after landing. Say what state the repo is in and quote
119
120
  the failure; there is a mess to clear and the reader needs to know where.
120
121
  - `⏸` — phases are unfinished, tests are red, or the tree is dirty. Nothing
121
122
  moved.
122
123
 
123
- **Fields:** `Tracker` · `Spec` · `Tests` · `Landed` · `Worktree` ·
124
+ **Fields:** `Tracker` · `Spec` · `Tests` · `Notes` · `Landed` · `Worktree` ·
124
125
  `Follow-ups` · `Next`
125
126
 
127
+ `Notes` carries the after-land commands step 6 ran, and where — or, on a
128
+ failure, the command that failed and the ones that did not run. A project with
129
+ no `afterLand` says nothing about it.
130
+
126
131
  **Report this spec and no other.** A completion is the moment it is most
127
132
  tempting to survey what is left — what else is in flight, what is in the
128
133
  backlog, which worktrees are still standing — and it is the moment it helps
@@ -167,8 +172,12 @@ Use the hotfix landing instead of the integrate steps below:
167
172
  cherry-pick --abort` there, relay the conflict, and **stop** — don't offer
168
173
  teardown.
169
174
  On a **no-op** ("nothing to land"), say so and continue.
170
- 3. **Re-test on `main`** after the cherry-pick — it must be **green**.
171
- 4. **Report** the deploy tag(s) and the `main` cherry-pick. It **never pushes** —
175
+ 3. **Run the after-land commands** when the plan ends with a
176
+ `then, in the primary checkout (<path>), run:` section — exactly as step 3
177
+ of 6-feature/bug below says. `hotfix land` prints it only when it
178
+ cherry-picks onto `main`, so a prod-only land or a no-op has none.
179
+ 4. **Re-test on `main`** after the cherry-pick — it must be **green**.
180
+ 5. **Report** the deploy tag(s) and the `main` cherry-pick. It **never pushes** —
172
181
  remind the user to `git push origin <deploy-tag>` to deploy. Then teardown
173
182
  (step 7) applies: a tagged hotfix branch tears down with **no `--force`**.
174
183
 
@@ -207,9 +216,20 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
207
216
  `git -C <worktree> rebase --abort`, relay the conflict, and **stop** — leave it
208
217
  to the user; do not tear anything down.
209
218
  On a **no-op** ("already landed"), just say so and continue.
210
- 3. **Re-test on base.** Run the project's test command from the primary checkout;
219
+ 3. **Run the after-land commands.** Where the plan ends with a
220
+ `then, in the primary checkout (<path>), run:` section — the project's
221
+ `afterLand`, such as a data migration — run those commands in that
222
+ directory, in order, after the fast-forward succeeds. **Also on the no-op**:
223
+ the engine prints them there deliberately, because a land made on another
224
+ machine leaves them pending here. **Stop at the first non-zero exit** — a
225
+ later command may depend on it — and carry on with the rest of the skill:
226
+ a failure makes the run `⚠️`, naming the command that failed and the ones
227
+ that did not run, and it
228
+ **never undoes the land and never blocks teardown** (step 7). The land is correct; catching up is the operator's.
229
+ No section, nothing to run, and nothing to say about it.
230
+ 4. **Re-test on base.** Run the project's test command from the primary checkout;
211
231
  it must be **green** before you call the landing done.
212
- 4. **Report** the landing (base branch, fast-forward result). It **never pushes** —
232
+ 5. **Report** the landing (base branch, fast-forward result). It **never pushes** —
213
233
  mention the user can `git push` the base branch themselves.
214
234
 
215
235
  ## 7. Tear down the environment (opt-in, only if configured)
@@ -221,7 +241,9 @@ teardown is folded in here).
221
241
 
222
242
  **The precondition is that the work actually landed.** Only tear down when step 6
223
243
  completed: it landed (or reported "already landed") **and** the base suite came
224
- back green. A rebase conflict, a work-loss abort or a red suite means step 6 told
244
+ back green. A failed after-land command is **not** a reason to stop here — the
245
+ land stands, and the command is the operator's to re-run from the primary
246
+ checkout, which teardown does not touch. A rebase conflict, a work-loss abort or a red suite means step 6 told
225
247
  you to stop — tear nothing down, because the worktree is where the user picks the
226
248
  problem up. That precondition is what makes a confirmation redundant: by the time
227
249
  you get here the branch is an ancestor of base and the engine's guards have
@@ -363,7 +363,7 @@ still has a decision in front of them and has not made it.
363
363
  |--------|----|------|
364
364
  | `live-on` | commit first (below), then `skitterspec spec-env live take <spec>` | re-render `--branch`, wait again |
365
365
  | `allow-network` | `skitterspec spec-env review allow network` | re-render, wait again |
366
- | `allow-remote` | `skitterspec spec-env review allow remote --set on` | re-render, wait again |
366
+ | `allow-remote` | `skitterspec spec-env review allow remote <spec> --set on` | publish (§6), re-render, wait again |
367
367
 
368
368
  **THERE IS NO `live-off`, and its absence is deliberate.** Putting *this* change
369
369
  live is about the diff on screen; handing the whole instance back to `main` is a
@@ -420,18 +420,22 @@ text, and do not infer an offer that is not there — the boundary that keeps
420
420
  another spec's workbench out of this lives in that field rather than in prose
421
421
  here.
422
422
 
423
- **`allow` writes a committed file, and that is worth a sentence.** It edits
424
- `specs/.core/env.config.json` in the **primary checkout**, so it changes
425
- behaviour for everyone who pulls and leaves that tree dirty — unlike the live
426
- actions, which move a branch and write a gitignored receipt. The engine prints
427
- the absolute path and whose tree it dirtied; relay that rather than letting a
428
- shared setting change land silently. And it is **enable-only**: there is no
423
+ **`allow network` writes a committed file, and that is worth a sentence.** It
424
+ edits `specs/.core/env.config.json` in the **primary checkout**, so it changes
425
+ behaviour for everyone who pulls and leaves that tree dirty — one server binds
426
+ for every spec, so the bind cannot be narrower. The engine prints the absolute
427
+ path; relay that rather than letting a shared setting change land silently.
428
+ **`allow remote` is per spec** and writes only a gitignored sidecar beside the
429
+ page, so the base branch stays clean and no other spec may publish. And it is **enable-only**: there is no
429
430
  action that turns a tier off, because turning `network` off from a page reached
430
431
  over the network kills the page doing the turning.
431
432
 
432
- **`allow remote` permits publishing. It does not publish.** Publishing stays an
433
- explicit ask in every case (§6), because the page it leaves behind is one this
434
- tooling cannot remove.
433
+ **`allow remote` permits publishing. It does not publish by itself.**
434
+ Publishing stays an explicit ask (§6), because the page it leaves behind is one
435
+ this tooling cannot remove. A reader who presses `Allow remote` on the page, or
436
+ types `/spec-remote-review` beside a waiting review, **has** asked: the engine
437
+ names the page on its `review:`/`publish:` lines, and that page is published
438
+ and the stack re-shown. Nothing it does not name is published.
435
439
 
436
440
  ## 3. Gate it on nothing
437
441
 
@@ -79,7 +79,9 @@ through to `git worktree add`, which refuses far more loudly than a planner
79
79
  guessing from an absence.
80
80
 
81
81
  Run what it prints — the `git worktree add … -b hotfix/<slug> <tag>` is checked
82
- out **at the tag**, not at `main` — then move this session into the worktree
82
+ out **at the tag**, not at `main`, and the
83
+ `skitterspec spec-env bootstrap hotfix-<name> --docs` under `then, run:` where
84
+ there is one — then move this session into the worktree
83
85
  with a plain `cd`; the Bash working directory persists between calls, so from
84
86
  here on the test, the fix and the spec are all written on the hotfix's own
85
87
  branch. Confirm the move landed rather than reading silence as success
@@ -100,7 +102,15 @@ to: §4 runs the project's suite in this same tree, and
100
102
  **a tree that cannot run the suite cannot go red**.
101
103
  The flagless re-run re-attaches the worktree that now exists (no second `-b`
102
104
  fork, and no fork point — the branch is already on the tag's line) and prints
103
- the bootstrap steps the flag deferred.
105
+ `skitterspec spec-env bootstrap hotfix-<name>` — run it, and it runs the steps
106
+ the flag deferred.
107
+
108
+ **The worktree steps are the engine's to run, never yours.** Seeding the
109
+ gitignored files, the env file, `setup` and the stack all happen inside
110
+ `skitterspec spec-env bootstrap <name>` — one call that names no file. Do not
111
+ reconstruct them as shell: they used to be printed that way, an agent's copy was
112
+ never faithful, and a project's secrets hook refused every rewrite that named a
113
+ `.env` path. `bootstrap --plan` says what it would do, for reading only.
104
114
 
105
115
  **Why nothing is seeded on the base branch, and nothing is moved.** This step
106
116
  used to write `specs/in-progress/hotfix-<name>/00-overview.md` in the primary
@@ -82,10 +82,11 @@ nothing to commit" as a sign something was skipped: it means the landing worked.
82
82
 
83
83
  **And the worktree may already exist**, because `Commit & Start` keeps it. That
84
84
  is a re-attach, not a clash: `spec-env up` plans the attach form rather than a
85
- second `-b` fork, and its `then, in the worktree, run:` step is where the
86
- `setup` commands `--docs` deliberately skipped finally run. So run the plan as
87
- printed — the install belongs to this moment, the first at which the tree is
88
- used for code — and report a re-attach rather than a fresh provision.
85
+ second `-b` fork, and its `then, run:` line — `spec-env bootstrap <name>`,
86
+ without `--docs` — is where the `setup` commands `--docs` deliberately skipped
87
+ finally run. So run it — the install belongs to this moment, the first at which
88
+ the tree is used for code — and report a re-attach rather than a fresh
89
+ provision.
89
90
 
90
91
  ## 2. Identify the spec
91
92
 
@@ -164,12 +165,22 @@ render says so. On **`--plan`** this step does not run at all.
164
165
  spec on its own:
165
166
 
166
167
  ```
167
- cd "<worktreePath>" && <the planner's "then, in the worktree, run:" steps>
168
+ cd "<worktreePath>" && skitterspec spec-env bootstrap <name>
168
169
  ```
169
170
 
170
- Run the seeding steps before `setup`: a fresh worktree has no dependencies and
171
- none of the repo's gitignored files, so hooks, typechecks and tests fail until
172
- both have happened.
171
+ That is the planner's `then, run:` line, with the `cd` in front. It seeds
172
+ the gitignored files before `setup` itself — a fresh worktree has neither, so
173
+ hooks, typechecks and tests fail until both have happened — and stops at the
174
+ first step that fails, naming it. Relay its step lines; a non-zero exit is a
175
+ failure to report at the moment it happens, not a reason to run the steps by
176
+ hand.
177
+
178
+ **The worktree steps are the engine's to run, never yours.** Seeding the
179
+ gitignored files, the env file, `setup` and the stack all happen inside
180
+ `skitterspec spec-env bootstrap <name>` — one call that names no file. Do not
181
+ reconstruct them as shell: they used to be printed that way, an agent's copy was
182
+ never faithful, and a project's secrets hook refused every rewrite that named a
183
+ `.env` path. `bootstrap --plan` says what it would do, for reading only.
173
184
 
174
185
  **Then confirm the move landed — never assume it.** Ask for a positive signal
175
186
  rather than reading silence as success (`.claude/rules/negative-checks.md`