@skitterbyte/skitterspec 17.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 +260 -10
- package/README.md +53 -4
- package/assets/claude-md-section.md +48 -2
- package/assets/commands/spec-connect.md +2 -2
- package/assets/commands/spec-live.md +2 -2
- package/assets/core/env.config.json.example +9 -3
- package/assets/core/env.config.md +102 -30
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/review/page.html +1501 -0
- package/assets/rules/spec-planning.md +224 -15
- package/assets/rules/spec-reports.md +269 -0
- package/assets/skills/spec/SKILL.md +63 -12
- package/assets/skills/spec-bug/SKILL.md +134 -26
- package/assets/skills/spec-cancel/SKILL.md +85 -6
- package/assets/skills/spec-complete/SKILL.md +109 -20
- package/assets/skills/spec-diff/SKILL.md +564 -0
- package/assets/skills/spec-hotfix/SKILL.md +143 -21
- package/assets/skills/spec-init/SKILL.md +49 -9
- package/assets/skills/spec-next/SKILL.md +289 -7
- package/assets/skills/spec-review/SKILL.md +45 -9
- package/assets/skills/spec-reviewed/SKILL.md +241 -0
- package/assets/skills/spec-start/SKILL.md +323 -66
- package/assets/skills/spec-to-main/SKILL.md +42 -20
- package/package.json +11 -7
- package/src/cli.js +1710 -80
- package/src/env/building.js +143 -0
- package/src/env/classify.js +91 -0
- package/src/env/config.js +57 -9
- package/src/env/provision.js +192 -19
- package/src/env/proxy.js +34 -1
- package/src/env/render.js +3 -12
- package/src/env/resolve.js +296 -9
- package/src/env/review.js +1329 -0
- package/src/env/serve.js +549 -0
- package/src/env/teardown.js +13 -6
- package/src/gating.js +155 -0
- package/src/init.js +124 -2
- package/src/prompts.js +10 -1
- package/LICENSE +0 -21
|
@@ -5,6 +5,11 @@ description: Fix a production bug on the exact released version — fork a workt
|
|
|
5
5
|
|
|
6
6
|
# /spec-hotfix — fix a released version, tag it, cherry-pick back to main
|
|
7
7
|
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
8
13
|
This is the **hotfix** counterpart to `/spec-bug`. Same test-first discipline
|
|
9
14
|
(reproduce as a **failing test (RED)**, then drive to **GREEN**), but the base is
|
|
10
15
|
a **release tag**, not `main`: prod is running a tagged version, so the fix must
|
|
@@ -61,11 +66,13 @@ that header — must exist **before** `spec-env up`:
|
|
|
61
66
|
`## Symptom` you established. It starts in `in-progress` — work begins now.
|
|
62
67
|
- Run `skitterspec spec-env up hotfix-<name>`. It prints a `git worktree add …
|
|
63
68
|
-b hotfix/<slug> <tag>` command (the branch forks from **the tag**, not
|
|
64
|
-
`main`), the worktree path,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
`main`), the worktree path, and any `in the worktree, run:` bootstrap steps.
|
|
70
|
+
- Run the printed `git worktree add`, then **move the stub across yourself.**
|
|
71
|
+
This is where a hotfix differs from `/spec-bug`, which no longer needs the move:
|
|
72
|
+
that skill's worktree forks from `main`, so committing the stub puts it there,
|
|
73
|
+
while **this worktree is checked out at the tag** — a commit on `main` is not in
|
|
74
|
+
it and never will be. The move is not redundant here; keep it.
|
|
75
|
+
**Create the destination bucket first:**
|
|
69
76
|
|
|
70
77
|
```
|
|
71
78
|
mkdir -p <worktreePath>/specs/in-progress
|
|
@@ -74,14 +81,13 @@ that header — must exist **before** `spec-env up`:
|
|
|
74
81
|
|
|
75
82
|
The `mkdir -p` is not belt-and-braces. Git does not store empty directories, so
|
|
76
83
|
`specs/in-progress/` is **absent** from the worktree whenever nothing was in
|
|
77
|
-
progress at that point in history — and here that point is an
|
|
78
|
-
tag**, where it is absent more often than not. `mv` into a
|
|
79
|
-
renames your spec folder **to** `specs/in-progress`,
|
|
80
|
-
end up one level too high, `00-overview.md` sits
|
|
81
|
-
every later step still appears to work until
|
|
82
|
-
Confirm the result before carrying on — you
|
|
83
|
-
`<worktreePath>/specs/in-progress/hotfix-<name>/00-overview.md`.
|
|
84
|
-
- **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
|
|
84
|
+
progress at that point in history — and here that point is an
|
|
85
|
+
**old release tag**, where it is absent more often than not. `mv` into a
|
|
86
|
+
missing destination renames your spec folder **to** `specs/in-progress`,
|
|
87
|
+
silently: the spec's files end up one level too high, `00-overview.md` sits
|
|
88
|
+
where the bucket should be, and every later step still appears to work until
|
|
89
|
+
something cannot find the spec. Confirm the result before carrying on — you
|
|
90
|
+
want `<worktreePath>/specs/in-progress/hotfix-<name>/00-overview.md`. - **Bootstrap the worktree.** A fresh worktree has no installed dependencies and
|
|
85
91
|
none of the repo's gitignored files (`.env`, local overrides). Run the printed
|
|
86
92
|
`in the worktree, run:` steps (file seeding, then `setup`) in order, before
|
|
87
93
|
anything else.
|
|
@@ -123,6 +129,8 @@ is usually a single-pass fix, so the `## Fix` block can live directly in
|
|
|
123
129
|
> **Base version:** <tag prod is running, e.g. v33.16.4>
|
|
124
130
|
> **Raised:** <YYYY-MM-DD (today)>
|
|
125
131
|
> **Area:** <files/modules>
|
|
132
|
+
> **Gating:** <pre-filled "none: hotfix — restoring released behaviour"; only
|
|
133
|
+
> when release gating is configured, and overridable — see below>
|
|
126
134
|
|
|
127
135
|
## Symptom
|
|
128
136
|
|
|
@@ -189,6 +197,23 @@ narrative and decisions).
|
|
|
189
197
|
|
|
190
198
|
|
|
191
199
|
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
### Release gating (only when configured)
|
|
203
|
+
|
|
204
|
+
**Only when `specs/.core/gating.config.json` exists.** A hotfix is the one spec
|
|
205
|
+
type that does **not** ask the question cold: it writes
|
|
206
|
+
`none: hotfix — restoring released behaviour` and asks only for confirmation.
|
|
207
|
+
|
|
208
|
+
The default differs on purpose. A hotfix restores behaviour a release already
|
|
209
|
+
had, under time pressure, and the fix is captured by a deploy tag rather than
|
|
210
|
+
riding the next release — so a flag has nothing to gate and nothing to roll back
|
|
211
|
+
to. Making someone answer a design question mid-incident buys nothing.
|
|
212
|
+
|
|
213
|
+
It is a **default, not a rule**: say what you are writing and let the user
|
|
214
|
+
override it. If they name a flag, record that instead. Skip entirely when the
|
|
215
|
+
config is absent.
|
|
216
|
+
|
|
192
217
|
## 6. Drive to GREEN
|
|
193
218
|
|
|
194
219
|
- Implement the **minimal, root-cause** fix on the branch. Match surrounding code;
|
|
@@ -205,18 +230,115 @@ is a no-op.
|
|
|
205
230
|
|
|
206
231
|
|
|
207
232
|
|
|
233
|
+
## 6b. Render the page — then offer the review, never write it
|
|
234
|
+
|
|
235
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
236
|
+
present). Without it there is no worktree to read and this step does not exist —
|
|
237
|
+
skip it in silence rather than explaining an absence.
|
|
238
|
+
|
|
239
|
+
The fix is green and nothing is committed yet. That is the moment the page is
|
|
240
|
+
about, so render it now — **after** the tests pass and **before** the commit:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
skitterspec spec-env review <spec>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**This is free.** The engine reads git and splices the patches into a template;
|
|
247
|
+
the diff never passes through you, so a 266KB patch costs nothing.
|
|
248
|
+
|
|
249
|
+
**Then offer `/spec-diff`. Do not run it.** The written review is the part that
|
|
250
|
+
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
251
|
+
— and that spend is the operator's call, not a default.
|
|
252
|
+
|
|
253
|
+
**The offer is the `Review` row of step 6's block** — the counts, the page link
|
|
254
|
+
and a question, in one row:
|
|
255
|
+
|
|
256
|
+
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
|
|
257
|
+
|
|
258
|
+
**It ends in a question, addressed to someone.** It was once a fenced block of
|
|
259
|
+
engine output, and it fired on every phase and was never once taken: two quoted
|
|
260
|
+
lines under the test counts, addressed to nobody, with the report then closing
|
|
261
|
+
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
262
|
+
so they did. A row in a labelled table is findable; a question in it is
|
|
263
|
+
answerable. Both halves are load-bearing.
|
|
264
|
+
|
|
265
|
+
**Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
|
|
266
|
+
the page and the question stay in the same row: two adjacent rows about one page
|
|
267
|
+
make the reader resolve a distinction before acting on either. A later edit that
|
|
268
|
+
moves it out of the block, or separates the link from the question, undoes this
|
|
269
|
+
and should be read as a regression rather than tidying.
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
Relay the **`open:`** line rather than the bare path: a path is not clickable in
|
|
273
|
+
any terminal, and a page nobody can open is a page nobody reads.
|
|
274
|
+
|
|
275
|
+
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
276
|
+
something behind that this tooling cannot remove, so it is always an ask. A
|
|
277
|
+
`file://` link is no use on a phone, and saying so **is** the ask —
|
|
278
|
+
`/spec-diff` §6 owns how.
|
|
279
|
+
**Follow the `reader:` line the engine printed — do not sniff for it.** It
|
|
280
|
+
answers where the person reading this is sitting, and the offer changes with it:
|
|
281
|
+
|
|
282
|
+
- **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
|
|
283
|
+
unknown is the ordinary state of a local machine, and a warning there is an
|
|
284
|
+
accusation against a healthy session.
|
|
285
|
+
- **`local`** — the `file://` URL.
|
|
286
|
+
- **`remote`** — the engine has already stood its local server up and put a URL
|
|
287
|
+
the reader can open on `open:`. So there is **nothing special to say**: relay
|
|
288
|
+
that line like any other. Any `also:` lines under it are the other addresses
|
|
289
|
+
this machine has, offered because the best-guess one can be wrong — pass them
|
|
290
|
+
on rather than editing them out.
|
|
291
|
+
|
|
292
|
+
**Never read an environment variable to decide this** — not `SSH_CONNECTION`,
|
|
293
|
+
not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
|
|
294
|
+
and in `--json`, and a second implementation here could not be tested and would
|
|
295
|
+
drift.
|
|
296
|
+
|
|
297
|
+
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
298
|
+
authorises a local server — one process, ended by one flag, leaving nothing
|
|
299
|
+
behind — and authorises nothing else. Publishing leaves a page this tooling
|
|
300
|
+
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
301
|
+
serve (a busy port, a machine with no network address) it falls back to the
|
|
302
|
+
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
303
|
+
|
|
304
|
+
- **Never fatal.** A failed render — no worktree, a git error — is one line, and
|
|
305
|
+
the fix is still done. The page is a convenience; the repo is the record.
|
|
306
|
+
|
|
307
|
+
**The range is measured from the base tag, not from `main`.** A hotfix forks its
|
|
308
|
+
worktree from its `> **Base version:**` tag, and the engine reads that header, so
|
|
309
|
+
the header line says `since <tag>` — do not relay it as `since main`, and do not
|
|
310
|
+
reach for a branch view expecting the base branch.
|
|
311
|
+
|
|
208
312
|
## 7. Report
|
|
209
313
|
|
|
210
|
-
|
|
211
|
-
|
|
314
|
+
Do **not** `git push` or `git tag`-and-push unless the user asks — deploying to
|
|
315
|
+
prod is theirs to trigger. The spec stays in `in-progress`.
|
|
212
316
|
|
|
213
317
|
- **`/spec-live` is refused for a hotfix** — its branch is built on an old tag, so
|
|
214
318
|
hot-reloading it onto the running dev server could break the shared instance.
|
|
215
319
|
To test it, the user runs `/spec-connect` (its own isolated stack).
|
|
216
|
-
-
|
|
217
|
-
hotfix branch **locally** (you push it to deploy), and cherry-picks the
|
|
218
|
-
`main`. Add `--also <tag>` at completion to also patch other release
|
|
219
|
-
(test/demo on their own versions).
|
|
320
|
+
- `Next` points at **`/spec-complete`** to land it: it patch-bumps the base tag,
|
|
321
|
+
tags the hotfix branch **locally** (you push it to deploy), and cherry-picks the
|
|
322
|
+
fix onto `main`. Add `--also <tag>` at completion to also patch other release
|
|
323
|
+
lines (test/demo on their own versions).
|
|
220
324
|
|
|
221
|
-
|
|
222
|
-
|
|
325
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
326
|
+
the shape; this section carries only what is specific here.
|
|
327
|
+
|
|
328
|
+
**Verdicts**
|
|
329
|
+
|
|
330
|
+
- `✅` — red→green on the base tag, fix in, suite passing.
|
|
331
|
+
- `⚠️` — green, with something worth knowing.
|
|
332
|
+
- `❌` — still red, or a later test broke. Quote the failure.
|
|
333
|
+
- `⏸` — no reproduction on that tag, or the tag could not be established.
|
|
334
|
+
|
|
335
|
+
**Fields:** `Tracker` · `Branch` · `Spec` · `Cause` · `Built` · `Tests` ·
|
|
336
|
+
`Review` · `Follow-ups` · `Next`
|
|
337
|
+
|
|
338
|
+
**The base tag goes in the verdict clause** — `✅ /spec-hotfix · hotfix-foo ·
|
|
339
|
+
green on v2.3.1`. Which released version this was fixed against is the first
|
|
340
|
+
thing anyone needs, and it is not a field: the clause is where the run says
|
|
341
|
+
where it got to.
|
|
342
|
+
|
|
343
|
+
Step 6b's offer is the `Review` row, not a paragraph after the block — the
|
|
344
|
+
counts, the link and the question in one row. Nothing follows the block.
|
|
@@ -5,6 +5,11 @@ description: Bootstrap the spec-driven-development workflow in the current proje
|
|
|
5
5
|
|
|
6
6
|
# /spec-init — set up the spec-driven-development workflow
|
|
7
7
|
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
8
13
|
Bootstrap (or repair) everything this project needs to run the spec lifecycle.
|
|
9
14
|
**Idempotent:** detect what already exists, create only what's missing, and never
|
|
10
15
|
clobber customised content. Finish with a summary of created vs already-present.
|
|
@@ -22,10 +27,11 @@ clobber customised content. Finish with a summary of created vs already-present.
|
|
|
22
27
|
> **Leave alone**. Non-interactively it only adds what's missing; `--resync` /
|
|
23
28
|
> `--reset` (reset needs `--yes`) drive the stronger actions.
|
|
24
29
|
|
|
25
|
-
The system is **
|
|
30
|
+
The system is **twelve skills**: `spec` (feature), `spec-bug` (bug),
|
|
26
31
|
`spec-hotfix` (a bug fixed on a released tag), `spec-review`, `spec-start`,
|
|
27
|
-
`spec-next`, `spec-
|
|
28
|
-
`spec-
|
|
32
|
+
`spec-next`, `spec-diff` (read a spec's diff as a page),
|
|
33
|
+
`spec-reviewed` (pick up a review you approved on that page), `spec-to-main`,
|
|
34
|
+
`spec-complete`, `spec-cancel`, and this `spec-init`. The lifecycle is `backlog → in-progress → complete / cancelled`,
|
|
29
35
|
with `.core` holding always-apply project rules.
|
|
30
36
|
|
|
31
37
|
## 0. Workspace mode (only when adopting isolation)
|
|
@@ -37,6 +43,19 @@ checkout you are already in; one spec at a time, no hand-off). It is a question
|
|
|
37
43
|
about how the operator works, not about what the repo contains, so ask rather
|
|
38
44
|
than infer it from whether dev servers or Docker are configured.
|
|
39
45
|
|
|
46
|
+
## 0b. Release gating (optional, and separate)
|
|
47
|
+
|
|
48
|
+
Ask whether specs should record a **release-gating** decision — does this change
|
|
49
|
+
ship behind a feature flag, or land live? Adopting it copies
|
|
50
|
+
`specs/.core/gating.config.json.example` → `gating.config.json` and sets
|
|
51
|
+
`guidance` to wherever this project documents its flags.
|
|
52
|
+
|
|
53
|
+
It is **orthogonal to isolation**: a project can adopt either, both, or neither.
|
|
54
|
+
Skitterspec never learns how the flags work — it asks the question, cites that
|
|
55
|
+
path, and records the answer on each spec. Leave it off and nothing appears:
|
|
56
|
+
no question, no header, no check. Off is a perfectly good answer for a project
|
|
57
|
+
that does not use flags.
|
|
58
|
+
|
|
40
59
|
## 1. Folders
|
|
41
60
|
|
|
42
61
|
Create any that are missing; drop a `.gitkeep` into ones that would otherwise be
|
|
@@ -80,8 +99,8 @@ Ensure it exists. If missing, create it documenting:
|
|
|
80
99
|
- the **Author** / **Developer** header fields;
|
|
81
100
|
- the **State log** audit table (folder/status transitions), kept separate from
|
|
82
101
|
the **Changelog** (decisions);
|
|
83
|
-
- the project's concrete typecheck/test commands, and the rule that
|
|
84
|
-
phase ends with creating + running tests**.
|
|
102
|
+
- the project's concrete typecheck/test commands, and the rule that
|
|
103
|
+
**every phase ends with creating + running tests**.
|
|
85
104
|
|
|
86
105
|
Read a sibling spec skill (e.g. `spec`, `spec-next`) for the canonical shapes
|
|
87
106
|
rather than inventing them. If the rule already exists, leave it unless stale.
|
|
@@ -96,7 +115,28 @@ the section exists, refresh only stale folder/skill names — don't rewrite it.
|
|
|
96
115
|
|
|
97
116
|
## 6. Report
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
Do **not** `git commit` unless the user asks.
|
|
119
|
+
|
|
120
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
121
|
+
the shape; this section carries only what is specific here.
|
|
122
|
+
|
|
123
|
+
**Verdicts**
|
|
124
|
+
|
|
125
|
+
- `✅` — every area is in place.
|
|
126
|
+
- `⚠️` — bootstrapped, with something worth knowing: a skill that could not be
|
|
127
|
+
installed, a CLAUDE.md that was left alone, a `.core` that is still ignored.
|
|
128
|
+
- `❌` — it wrote some areas and failed on another. Name which, so the re-run is
|
|
129
|
+
informed; this skill is idempotent and re-running it is the fix.
|
|
130
|
+
- `⏸` — not a git repo, or nothing it could safely write into.
|
|
131
|
+
|
|
132
|
+
**Fields:** `Built` · `Follow-ups` · `Next`
|
|
133
|
+
|
|
134
|
+
**`Built` is per area, one line each** — folders, `.gitignore` lines,
|
|
135
|
+
tooling-ignore negations, skills (present/missing), rule files, CLAUDE.md
|
|
136
|
+
section — as created / updated / already-present, plus the `git check-ignore`
|
|
137
|
+
result for `.core`. This is the one skill whose `Built` is a list rather than a
|
|
138
|
+
clause, because "what is now true of this project" is the entire answer it
|
|
139
|
+
exists to give.
|
|
140
|
+
|
|
141
|
+
**There is no spec to name**, so the verdict line drops that segment:
|
|
142
|
+
`✅ /spec-init · every area in place`.
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-next
|
|
3
|
-
description: Build the next unfinished phase of the spec
|
|
3
|
+
description: Build the next unfinished phase of the spec in flight for this session — pre-flight, implement with tests, record progress and refresh the tracker. Refuses when no spec is in flight rather than guessing one, and builds wherever that spec resolves rather than wherever the session happens to stand. Use when the user says "/spec-next", "build the next phase", "continue the spec", or "carry on with this spec".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /spec-next — build the next phase of the spec in flight
|
|
7
7
|
|
|
8
|
+
> Stay silent while this runs — speak only to ask something you cannot answer
|
|
9
|
+
> yourself, or to report a failure at the moment it happens. Read
|
|
10
|
+
> `.claude/rules/spec-reports.md` before reporting; it defines the block this
|
|
11
|
+
> skill ends with.
|
|
12
|
+
|
|
8
13
|
It assumes the workbench is already set up: a spec is **in flight** on this
|
|
9
14
|
checkout, and this skill implements its next unfinished phase. Putting a spec in
|
|
10
15
|
flight — provisioning, moving it to `in-progress`, getting its branch here — is
|
|
@@ -13,7 +18,36 @@ flight — provisioning, moving it to `in-progress`, getting its branch here —
|
|
|
13
18
|
|
|
14
19
|
## 1. Identify the spec in flight
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
**`--worktree <path>` answers before anything else.** When the invocation names a
|
|
22
|
+
worktree, that is the spec to build and that is where it is built — cwd is not
|
|
23
|
+
consulted. It is how `/spec-start` carries on into phase 1 without moving your
|
|
24
|
+
session, and you can type it yourself.
|
|
25
|
+
|
|
26
|
+
**This is not a loosening of the refusal below.** That refusal exists against
|
|
27
|
+
*guessing* which spec to build, and a path someone typed is not a guess. A bare
|
|
28
|
+
`/spec-next` still refuses exactly as it does today.
|
|
29
|
+
|
|
30
|
+
**Validate the path before writing a line into it.** Run the resolver *from* the
|
|
31
|
+
path, so the answer comes from where you are about to write:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
cd "<path>" && skitterspec spec-env resolve
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Check the `worktree:` line it prints is that same path. If it is not — or the
|
|
38
|
+
command reports that isolation is not enabled — refuse and stop, naming what you
|
|
39
|
+
were given. A path that is not a provisioned worktree must never become a place
|
|
40
|
+
to write code.
|
|
41
|
+
**Read the output, not the exit status** — it exits 0 even when it
|
|
42
|
+
cannot resolve anything.
|
|
43
|
+
|
|
44
|
+
**Not `--dir <path>`.** That flag sets the **repo root**, not the worktree to
|
|
45
|
+
resolve from, so it answers a different question: on a repo with two or more
|
|
46
|
+
worktrees it refuses with *"no spec given, and N specs have worktrees"* and
|
|
47
|
+
validates nothing at all. The `cd` form is what makes the path itself the
|
|
48
|
+
evidence.
|
|
49
|
+
|
|
50
|
+
Otherwise resolve **in this order**, and stop at the first that answers:
|
|
17
51
|
|
|
18
52
|
1. **The live spec of this checkout** — run
|
|
19
53
|
`skitterspec spec-env live status` and read its `live:` line. `live: yes`
|
|
@@ -24,15 +58,60 @@ Resolve **in this order**, and stop at the first that answers:
|
|
|
24
58
|
opened in one is its own workbench.
|
|
25
59
|
3. **The current branch, in `checkout` mode** — no worktrees exist, so the
|
|
26
60
|
branch the checkout is on names the spec.
|
|
61
|
+
4. **The only spec provisioned in this repo** — ask the engine, with no spec
|
|
62
|
+
named, from wherever you happen to be standing:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
skitterspec spec-env resolve
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Take its `spec:` line **only when it names exactly one spec**.
|
|
69
|
+
|
|
70
|
+
This is the **durable** rung, and that is the whole reason it exists. Rungs 1
|
|
71
|
+
to 3 all read *session* state, and session state does not survive a `/clear`,
|
|
72
|
+
a new terminal tab, or coming back tomorrow — while the provisioned worktree
|
|
73
|
+
they are each a proxy for is **on disk** and survives all three. `/spec-start`
|
|
74
|
+
does leave the session standing in the worktree and that `cd` is real; what it
|
|
75
|
+
is not is durable. Without this rung, a repo holding exactly one answer sends
|
|
76
|
+
the operator away to re-supply something it already knew.
|
|
77
|
+
|
|
78
|
+
It is also the resolution every other bare command in this workflow already
|
|
79
|
+
uses — the worktree you are standing in, else the sole provisioned spec — so
|
|
80
|
+
this rung is what stops `/spec-next` being a silent exception to a rule
|
|
81
|
+
`.claude/rules/spec-planning.md` states has none left.
|
|
82
|
+
|
|
83
|
+
**Say which spec you resolved and how**, before writing a line of it:
|
|
84
|
+
*"not standing in a worktree — `<spec>` is the only spec provisioned"*. Rungs
|
|
85
|
+
1 to 3 are self-evident to whoever typed the command; this one is not, and
|
|
86
|
+
the operator cannot see from where they sit what you picked.
|
|
87
|
+
|
|
88
|
+
**Several worktrees stay a refusal.** The engine names them and resolves
|
|
89
|
+
nothing — exactly the ambiguity the refusal below exists for. Relay its list
|
|
90
|
+
unchanged and stop; never pick from it.
|
|
91
|
+
|
|
92
|
+
WHAT WOULD FOOL THIS: a worktree left behind by a declined teardown is still
|
|
93
|
+
a worktree, so a finished spec can go on counting as provisioned. That widens
|
|
94
|
+
the candidate set, so the failure it produces is an extra candidate — an
|
|
95
|
+
ambiguity the engine refuses on — and never a wrong spec built. It cannot
|
|
96
|
+
manufacture an *absence*, which is why the absence below is still worth
|
|
97
|
+
refusing on.
|
|
27
98
|
|
|
28
99
|
**If none answers, refuse and stop:**
|
|
29
|
-
`no spec in flight — run /spec-start <name> to
|
|
100
|
+
`no spec in flight — run /spec-start <name> to start one`.
|
|
101
|
+
|
|
102
|
+
That now answers a real absence — no worktree anywhere, the engine included —
|
|
103
|
+
rather than a session that merely lost track of where it was standing.
|
|
30
104
|
|
|
31
105
|
**Never fall back to the spec "in context".** A spec discussed in conversation
|
|
32
106
|
is not a spec in flight, and this skill writes real code: building the wrong
|
|
33
107
|
spec's phase produces commits on a branch nobody asked for. The refusal is
|
|
34
108
|
cheap; the mistake is not.
|
|
35
109
|
|
|
110
|
+
**Rung 4 is not that fallback wearing a hat.** A provisioned worktree is a
|
|
111
|
+
record that someone ran `/spec-start`: it is on disk, the engine reads it, and it
|
|
112
|
+
either names one spec or refuses. A spec named in conversation is a guess about
|
|
113
|
+
intent with nothing underneath it, and no number of them ever resolves to one.
|
|
114
|
+
|
|
36
115
|
A **name argument** is accepted, but it must *match* the spec in flight — it
|
|
37
116
|
narrows a re-run, it does not select a different spec. A mismatch refuses,
|
|
38
117
|
naming both.
|
|
@@ -63,6 +142,33 @@ once it is over. Without a provider this is a no-op and nothing below changes.
|
|
|
63
142
|
|
|
64
143
|
|
|
65
144
|
|
|
145
|
+
**Before building, compare the worktree against where you are standing.** Take
|
|
146
|
+
the `worktree:` line from `skitterspec spec-env resolve <spec>` and compare it
|
|
147
|
+
with this session's cwd, resolving both paths first so a symlinked or
|
|
148
|
+
trailing-slash spelling of one tree does not read as two.
|
|
149
|
+
|
|
150
|
+
Same tree — the ordinary case, since `/spec-start` leaves the session standing
|
|
151
|
+
in it — and the rest of this step is inert.
|
|
152
|
+
**Different trees, and the discipline below applies however the spec was resolved.**
|
|
153
|
+
`--worktree <path>` is one way to get here and §1's rung 4 is another: a bare
|
|
154
|
+
`/spec-next` typed from the primary checkout resolves the sole provisioned spec
|
|
155
|
+
and builds it somewhere this session is not. What makes the discipline necessary
|
|
156
|
+
is the two trees, so that is what it is conditioned on — not the shape of the
|
|
157
|
+
invocation, which cannot see rung 4 at all.
|
|
158
|
+
|
|
159
|
+
Record the baseline before you write anything:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
skitterspec spec-env resolve <spec> --record-primary
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Then build as below, with one discipline on top.
|
|
166
|
+
**The session is not standing in the worktree**, so every write takes an
|
|
167
|
+
absolute path under it and every command
|
|
168
|
+
is prefixed `cd "<worktreePath>" &&` — typecheck and tests included. A single
|
|
169
|
+
relative path lands the work in the primary checkout, on the base branch, and
|
|
170
|
+
nothing about it looks wrong at the time. Step 4b is what catches it.
|
|
171
|
+
|
|
66
172
|
Then build it, following the project rules in `.claude/rules/*.md` and `CLAUDE.md`:
|
|
67
173
|
|
|
68
174
|
- Work task by task through the phase file. Make focused edits that match
|
|
@@ -91,8 +197,184 @@ a mirror lag a whole spec behind. Without a provider this is a no-op.
|
|
|
91
197
|
|
|
92
198
|
|
|
93
199
|
|
|
94
|
-
##
|
|
200
|
+
## 4b. Prove nothing leaked into the primary checkout
|
|
201
|
+
|
|
202
|
+
**Only when the resolved worktree is not this session's cwd** — the same
|
|
203
|
+
comparison step 3 made, and it holds however the spec was resolved. Standing in
|
|
204
|
+
the worktree there is no second tree to have written into, so this step does not
|
|
205
|
+
apply and there is nothing to check.
|
|
206
|
+
|
|
207
|
+
WHAT WOULD FOOL THIS CHECK: it watches the **primary checkout** and nothing
|
|
208
|
+
else, so a build run from inside *another* spec's worktree would leak there
|
|
209
|
+
unseen. That is left unhandled deliberately rather than overlooked — reaching it
|
|
210
|
+
takes an explicit `--worktree` typed from a second worktree — and the cost of the
|
|
211
|
+
gap is a missed leak, never a false accusation.
|
|
212
|
+
|
|
213
|
+
The phase is built and its progress recorded — all of it written into a tree this
|
|
214
|
+
session is not standing in. Before reporting any of it as done:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
skitterspec spec-env resolve <spec> --assert-primary-clean
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- **Exit 0, "primary checkout clean"** — carry on.
|
|
221
|
+
- **Non-zero** — stop and relay the engine's message unchanged. It names the
|
|
222
|
+
paths and both readings: this build wrote them and they belong in the worktree,
|
|
223
|
+
or something else did and the baseline wants re-recording.
|
|
224
|
+
**Do not guess which, and do not delete anything.**
|
|
225
|
+
A path that appeared is not proof of who put it there.
|
|
226
|
+
- **"cannot tell"** — no baseline, or one from another spec. It exits 0 and
|
|
227
|
+
claims nothing; say so in one line and carry on. An absence is not evidence.
|
|
228
|
+
|
|
229
|
+
## 5. Render the page — then offer the review, never write it
|
|
230
|
+
|
|
231
|
+
**Only when the project has per-spec isolation** (`specs/.core/env.config.json`
|
|
232
|
+
present). Without it there is no worktree to read and this step does not exist.
|
|
233
|
+
|
|
234
|
+
The phase is built, its tests are green, and nothing is committed yet. That is
|
|
235
|
+
the moment the page is about, so render it now — **after** the tests pass and
|
|
236
|
+
**before** the commit:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
skitterspec spec-env review <spec>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**This is free.** It is the engine reading git and splicing text into a template;
|
|
243
|
+
the diff never passes through you, so a 266KB patch costs nothing. Report the
|
|
244
|
+
path it prints and move on.
|
|
245
|
+
|
|
246
|
+
**Then offer `/spec-diff`. Do not run it.** The written review is the part that
|
|
247
|
+
costs — roughly **700 output tokens**, because writing it means reading the diff
|
|
248
|
+
— and that spend is the operator's call, not a default.
|
|
249
|
+
|
|
250
|
+
**The offer is the `Review` row of step 6's block** — the counts, the page link
|
|
251
|
+
and a question, in one row:
|
|
252
|
+
|
|
253
|
+
| **Review** | <N> files, +<a> −<d> · [open the page](<the `open:` URL>) — want a written review before you commit? |
|
|
254
|
+
|
|
255
|
+
**It ends in a question, addressed to someone.** It was once a fenced block of
|
|
256
|
+
engine output, and it fired on every phase and was never once taken: two quoted
|
|
257
|
+
lines under the test counts, addressed to nobody, with the report then closing
|
|
258
|
+
on *"commit this first"* — the last instruction the reader got was to move on,
|
|
259
|
+
so they did. A row in a labelled table is findable; a question in it is
|
|
260
|
+
answerable. Both halves are load-bearing.
|
|
261
|
+
|
|
262
|
+
**Never bury it and never split it.** It sits above `Follow-ups` and `Next`, and
|
|
263
|
+
the page and the question stay in the same row: two adjacent rows about one page
|
|
264
|
+
make the reader resolve a distinction before acting on either. A later edit that
|
|
265
|
+
moves it out of the block, or separates the link from the question, undoes this
|
|
266
|
+
and should be read as a regression rather than tidying.
|
|
267
|
+
|
|
268
|
+
**Non-blocking, deliberately.** Do not end your turn waiting on the answer.
|
|
269
|
+
Phases get chained — `/commit && /spec-next` typed as one line — and a question
|
|
270
|
+
that stops the run taxes every phase to fix a problem the row already fixes.
|
|
271
|
+
|
|
272
|
+
Relay the **`open:`** line the engine prints, not the bare path: a path is not
|
|
273
|
+
clickable in any terminal, and a page nobody can open is a page nobody reads.
|
|
274
|
+
|
|
275
|
+
- **Never write the review unasked**, and **never publish**. Publishing leaves
|
|
276
|
+
something behind that this tooling cannot remove, so it is always something
|
|
277
|
+
someone asks for. A `file://` link is no use on a phone, and saying so **is**
|
|
278
|
+
the ask — publishing is the answer to it, and `/spec-diff` §6 owns how.
|
|
279
|
+
**Follow the `reader:` line the engine printed — do not sniff for it.** It
|
|
280
|
+
answers where the person reading this is sitting, and the offer changes with it:
|
|
281
|
+
|
|
282
|
+
- **absent** (`unknown`) — the `file://` URL, exactly as always. **Do not warn:**
|
|
283
|
+
unknown is the ordinary state of a local machine, and a warning there is an
|
|
284
|
+
accusation against a healthy session.
|
|
285
|
+
- **`local`** — the `file://` URL.
|
|
286
|
+
- **`remote`** — the engine has already stood its local server up and put a URL
|
|
287
|
+
the reader can open on `open:`. So there is **nothing special to say**: relay
|
|
288
|
+
that line like any other. Any `also:` lines under it are the other addresses
|
|
289
|
+
this machine has, offered because the best-guess one can be wrong — pass them
|
|
290
|
+
on rather than editing them out.
|
|
291
|
+
|
|
292
|
+
**Never read an environment variable to decide this** — not `SSH_CONNECTION`,
|
|
293
|
+
not `CLAUDE_CODE_*`, not a tty check. The engine did it, reports it on that line
|
|
294
|
+
and in `--json`, and a second implementation here could not be tested and would
|
|
295
|
+
drift.
|
|
296
|
+
|
|
297
|
+
**Serving is the engine's to do; publishing is never.** A `remote` reader
|
|
298
|
+
authorises a local server — one process, ended by one flag, leaving nothing
|
|
299
|
+
behind — and authorises nothing else. Publishing leaves a page this tooling
|
|
300
|
+
cannot remove, so it stays an ask in every case, always. If the engine could not
|
|
301
|
+
serve (a busy port, a machine with no network address) it falls back to the
|
|
302
|
+
`file://` URL with its marker, and that is when publishing is worth naming.
|
|
303
|
+
|
|
304
|
+
- **Never fatal.** A failed render — no worktree, a git error — is one line and
|
|
305
|
+
the phase is still done. The page is a convenience; the repo is the record.
|
|
306
|
+
- If the project has no isolation config, skip the whole step in silence rather
|
|
307
|
+
than explaining an absence.
|
|
308
|
+
|
|
309
|
+
## 6. Report
|
|
310
|
+
|
|
311
|
+
Do **not** `git commit` unless the user asks — finish, verify, and wait.
|
|
312
|
+
|
|
313
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
314
|
+
the shape; this section carries only what is specific here.
|
|
315
|
+
|
|
316
|
+
**Verdicts**
|
|
317
|
+
|
|
318
|
+
- `✅` — the phase is built and its tests are green.
|
|
319
|
+
- `⚠️` — built and green, with something worth knowing (a mirror that did not
|
|
320
|
+
refresh, a deviation from the plan).
|
|
321
|
+
- `❌` — the phase's tests are red, or it stopped part-way. Quote the failure.
|
|
322
|
+
- `⏸` — no spec in flight, or the name given does not match the one that is.
|
|
323
|
+
|
|
324
|
+
**Fields:** `Tracker` · `Branch` · `Built` · `Tests` · `Snags` · `Review` ·
|
|
325
|
+
`Follow-ups` · `Next`
|
|
326
|
+
|
|
327
|
+
## 6a. End in a picker
|
|
328
|
+
|
|
329
|
+
The block says what happened; this is what to do about it. Offer the same four
|
|
330
|
+
endings the review page carries, so a review finishes the same way wherever the
|
|
331
|
+
reader is standing — the page, a pasted code, or here.
|
|
332
|
+
|
|
333
|
+
| Option | Does |
|
|
334
|
+
|--------|------|
|
|
335
|
+
| `Reviewed` | Claims the waiting pass and routes on its verdict |
|
|
336
|
+
| `Commit` | Runs the project's commit skill, and stops |
|
|
337
|
+
| `Commit & Continue` | Commits, then `/spec-next` — and **stops there** |
|
|
338
|
+
| `Discuss` | Asks what is up; changes nothing |
|
|
339
|
+
|
|
340
|
+
**`Reviewed` only when a pass is actually waiting.** The render's `pending:`
|
|
341
|
+
block already says. Offering a pickup with nothing to pick up is the empty
|
|
342
|
+
gesture this exists against — the other three stand on their own.
|
|
343
|
+
|
|
344
|
+
**Do not restate the routing.** `/spec-diff` §2, §2a and §4 own it, including
|
|
345
|
+
the commit hand-off through `review.commitWith` and what `commit-continue` does
|
|
346
|
+
after. Two copies of a routing rule is how the two come to disagree.
|
|
347
|
+
|
|
348
|
+
**It does not break a chained run.** `/commit && /spec-next` is typed as one
|
|
349
|
+
line and the picker appears at the **end**, by which point the chain has already
|
|
350
|
+
finished. The cost that was feared here — a question stopping a run mid-way — is
|
|
351
|
+
not a cost this placement has.
|
|
352
|
+
|
|
353
|
+
**Nothing may claim a pass without a pick.** `/spec-reviewed` is user-only by
|
|
354
|
+
*harness enforcement*, because prose alone once failed to stop an agent claiming
|
|
355
|
+
a pass nobody asked it to. A pick keeps the **property** that makes that safe —
|
|
356
|
+
a person in the conversation chose, and a device that reaches the page cannot —
|
|
357
|
+
while routing around the **mechanism**, since the claim runs downstream of the
|
|
358
|
+
pick. That trade is deliberate and it has exactly one condition: a run that
|
|
359
|
+
shows no picker claims nothing, and a picker nobody answered claims nothing.
|
|
360
|
+
|
|
361
|
+
WHAT WOULD FOOL THIS: a picker shown reflexively at the end of every run trains
|
|
362
|
+
the reader to dismiss it, and a dismissed picker is indistinguishable from a
|
|
363
|
+
considered decline. So offer it where there is a real choice, and let the `Next`
|
|
364
|
+
row carry the rest.
|
|
365
|
+
|
|
366
|
+
`Next` names the commit and then the phase, as
|
|
367
|
+
`/commit, then /spec-next → phase 3 (Auth)`, so the block says what to do and
|
|
368
|
+
which phase is next without a line of prose for either.
|
|
369
|
+
|
|
370
|
+
**The commit is not optional politeness.** Step 6 above deliberately leaves the
|
|
371
|
+
phase uncommitted, and §2 of this very skill refuses to build the next phase on
|
|
372
|
+
top of an uncommitted one — so a `Next` that names only `/spec-next` sends the
|
|
373
|
+
reader straight into that refusal. The two halves are four hundred lines apart,
|
|
374
|
+
which is exactly how they drifted.
|
|
95
375
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
376
|
+
**Step 5's offer is the `Review` row.** It is not a paragraph after the block,
|
|
377
|
+
because nothing is after the block: the counts, the page link and the question
|
|
378
|
+
go in one row, above `Follow-ups` and `Next`. Step 5 renders before the commit
|
|
379
|
+
and this step is where its offer lands, so the two must not disagree about where
|
|
380
|
+
it goes.
|