@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 +42 -0
- package/README.md +3 -2
- package/assets/commands/spec-remote-review.md +24 -9
- package/assets/core/env.config.md +37 -15
- package/assets/hooks/main-guard.cjs +20 -3
- package/assets/rules/spec-planning.md +11 -4
- package/assets/skills/no-spec/SKILL.md +28 -4
- package/assets/skills/spec/SKILL.md +12 -4
- package/assets/skills/spec-bug/SKILL.md +12 -3
- package/assets/skills/spec-complete/SKILL.md +29 -7
- package/assets/skills/spec-diff/SKILL.md +14 -10
- package/assets/skills/spec-hotfix/SKILL.md +12 -2
- package/assets/skills/spec-start/SKILL.md +19 -8
- package/assets/skills/spec-to-main/SKILL.md +17 -2
- package/package.json +1 -1
- package/src/cli.js +360 -108
- package/src/env/bootstrap.js +188 -0
- package/src/env/config.js +15 -0
- package/src/env/hotfix.js +8 -2
- package/src/env/integrate.js +28 -8
- package/src/env/mainguard.js +64 -5
- package/src/env/provision.js +86 -168
- package/src/env/review.js +51 -1
- package/src/env/serve.js +4 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
**
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
94
|
-
// Docker spec's worktree steps run in this order:
|
|
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
|
|
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
|
|
120
|
-
// worktree).
|
|
121
|
-
//
|
|
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
|
|
138
|
-
//
|
|
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
|
|
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
|
|
345
|
-
// publishes nothing. Default: FALSE,
|
|
346
|
-
// page is one skitterspec cannot
|
|
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
|
-
//
|
|
117
|
-
//
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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
|
|
63
|
-
|
|
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
|
|
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. **
|
|
171
|
-
|
|
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. **
|
|
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
|
-
|
|
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
|
|
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
|
|
424
|
-
`specs/.core/env.config.json` in the **primary checkout**, so it changes
|
|
425
|
-
behaviour for everyone who pulls and leaves that tree dirty —
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
|
433
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
86
|
-
`setup` commands `--docs` deliberately skipped
|
|
87
|
-
|
|
88
|
-
used for code — and report a re-attach rather than a fresh
|
|
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>" &&
|
|
168
|
+
cd "<worktreePath>" && skitterspec spec-env bootstrap <name>
|
|
168
169
|
```
|
|
169
170
|
|
|
170
|
-
|
|
171
|
-
|
|
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`
|