@skitterbyte/skitterspec 22.0.0 → 22.1.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/README.md +14 -0
- package/assets/claude-md-section.md +13 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/env.config.json.example +2 -1
- package/assets/core/env.config.md +83 -2
- package/assets/hooks/main-guard.cjs +145 -0
- package/assets/hooks/review-gate.cjs +8 -3
- package/assets/review/page.html +132 -2
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +160 -11
- package/assets/rules/spec-reports.md +50 -9
- package/assets/skills/no-spec/SKILL.md +219 -0
- package/assets/skills/spec/SKILL.md +135 -36
- package/assets/skills/spec-bug/SKILL.md +5 -0
- package/assets/skills/spec-cancel/SKILL.md +29 -0
- package/assets/skills/spec-complete/SKILL.md +25 -0
- package/assets/skills/spec-diff/SKILL.md +33 -1
- package/assets/skills/spec-init/SKILL.md +4 -2
- package/assets/skills/spec-next/SKILL.md +47 -2
- package/assets/skills/spec-reviewed/SKILL.md +3 -3
- package/assets/skills/spec-start/SKILL.md +14 -0
- package/package.json +1 -1
- package/src/cli.js +702 -59
- package/src/env/config.js +121 -4
- package/src/env/hooks.js +102 -33
- package/src/env/live.js +30 -1
- package/src/env/mainguard.js +240 -0
- package/src/env/provision.js +106 -10
- package/src/env/registry.js +70 -6
- package/src/env/resolve.js +92 -3
- package/src/env/review.js +324 -20
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +189 -44
- package/src/env/teardown.js +12 -1
- package/src/init.js +125 -15
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Offered Unblocks
|
|
2
|
+
|
|
3
|
+
A refusal says what is wrong. Some refusals can also say **what would fix it**,
|
|
4
|
+
and where the engine declares that, a skill may offer it rather than leaving the
|
|
5
|
+
operator at a dead end.
|
|
6
|
+
|
|
7
|
+
This file is the whole contract. A skill's own text says only *that* it acts on
|
|
8
|
+
an offer, never how.
|
|
9
|
+
|
|
10
|
+
## The engine declares; the skill relays
|
|
11
|
+
|
|
12
|
+
An offer is a field on a refusal, never a judgement a caller makes from the
|
|
13
|
+
reason text:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"blocked": true,
|
|
18
|
+
"reason": "feat-x's worktree has uncommitted changes — commit or stash them…",
|
|
19
|
+
"offer": { "kind": "satisfy", "label": "Commit first, then go live", "command": "/commit" }
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Absent stays absent.** Most refusals carry no `offer` at all, and that is the
|
|
24
|
+
correct, common answer. Where there is none, relay the refusal and stop.
|
|
25
|
+
|
|
26
|
+
**Never infer one.** If the engine did not declare an offer, there is not one —
|
|
27
|
+
however obviously the reason seems to name a fix. The boundary that matters most
|
|
28
|
+
lives in that decision, and it must not be re-litigated per skill:
|
|
29
|
+
**work that is not the operator's is never offered up.** A live workbench another
|
|
30
|
+
spec holds is that spec's operator's to free, so it declares nothing, and a
|
|
31
|
+
helpful picker is exactly how that would creep back in.
|
|
32
|
+
|
|
33
|
+
## Two kinds
|
|
34
|
+
|
|
35
|
+
| `kind` | Means | Recorded |
|
|
36
|
+
|--------|-------|----------|
|
|
37
|
+
| `satisfy` | do what the guard asked for | no — nothing was weakened |
|
|
38
|
+
| `bypass` | step past a guard still unsatisfied | **yes** |
|
|
39
|
+
|
|
40
|
+
## Relay first, then offer
|
|
41
|
+
|
|
42
|
+
In that order, and never instead. A picker that replaced the refusal would hide
|
|
43
|
+
what is wrong behind a choice about what to do next — and the refusal text is
|
|
44
|
+
where every exit is named, including the ones the picker does not offer.
|
|
45
|
+
|
|
46
|
+
Then, only where `offer` is present, put **two** options to the operator: the
|
|
47
|
+
offer's `label`, and Cancel.
|
|
48
|
+
|
|
49
|
+
- **`satisfy`** — run the command, then retry the original operation.
|
|
50
|
+
- **`bypass`** — run the command, then carry on.
|
|
51
|
+
- **Cancel** — stop. Record nothing: declining to bypass is not a decision
|
|
52
|
+
anyone needs to audit.
|
|
53
|
+
|
|
54
|
+
**The label is the engine's, shown verbatim.** Never reword a bypass into
|
|
55
|
+
something easier-sounding, never pre-select it, and never add a third option. A
|
|
56
|
+
bypass that arrives pre-selected is a bypass by default wearing a question mark.
|
|
57
|
+
|
|
58
|
+
## A bypass is offered once per obligation
|
|
59
|
+
|
|
60
|
+
The engine enforces this; a skill does not count anything. Spend the offer by
|
|
61
|
+
recording it **when you raise the picker**:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
skitterspec spec-env review gate <spec> --offered
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
After that the gate declares no offer for the same arming, so the next refused
|
|
68
|
+
commit is a plain refusal with its exits named in the text. A new phase ending
|
|
69
|
+
arms the gate again and is asked in its own right.
|
|
70
|
+
|
|
71
|
+
**Spent by being made, not by being taken.** An operator who declines still saw
|
|
72
|
+
it, and re-offering on every attempt is how a deliberate step becomes the
|
|
73
|
+
default.
|
|
74
|
+
|
|
75
|
+
**Do not spend it by reading.** `review gate --json` is asked on every
|
|
76
|
+
`/spec-next` run; only raising the picker spends the offer.
|
|
77
|
+
|
|
78
|
+
## What this is not
|
|
79
|
+
|
|
80
|
+
**Not a `--force`.** One was asked for and rejected: a reasonless lift is
|
|
81
|
+
indistinguishable from nobody having looked, where
|
|
82
|
+
`none: additive, nothing to revert` is a decision a reviewer can argue with. The
|
|
83
|
+
gate's exits are unchanged — a committing verdict, or a recorded skip — and a
|
|
84
|
+
bypass is the second of those at an address a person will reach.
|
|
85
|
+
|
|
86
|
+
**Not a replacement for the refusal.** A picker exists only in a harness. The
|
|
87
|
+
engine and the `PreToolUse` hook keep refusing regardless, so this is a layer on
|
|
88
|
+
top of a guard and never the guard itself.
|
|
89
|
+
|
|
90
|
+
**Not Claude lifting a guard aimed at Claude.** `spec-planning.md` draws that
|
|
91
|
+
line for `/allow-main` and `/spec-skip`, and a bypass offer survives it because
|
|
92
|
+
three things hold at once, where a `--force` had none of them:
|
|
93
|
+
|
|
94
|
+
1. **The operator answers.** Claude may raise the question and cannot answer it.
|
|
95
|
+
2. **The choice is recorded** — a fixed reason, distinct from anything typed,
|
|
96
|
+
which shows on the next render as history.
|
|
97
|
+
3. **It is offered once**, held by the sidecar rather than by prose, so it
|
|
98
|
+
survives a `/clear`, a compaction and a fresh session.
|
|
99
|
+
|
|
100
|
+
Remove any one of those and the objection returns.
|
|
@@ -7,9 +7,10 @@ and lifecycle stay consistent. Each sets a status on the spec header
|
|
|
7
7
|
|
|
8
8
|
| Skill | Purpose | Status | Folder |
|
|
9
9
|
|-------|---------|--------|--------|
|
|
10
|
-
| `/spec` | (Feature) Grill to a clear shared understanding,
|
|
10
|
+
| `/spec` | (Feature) Grill to a clear shared understanding, write a groomed spec in its own worktree, then land it on the verdict | `Ready` (or `Draft`) | `specs/backlog/` (on the base branch, by a fast-forward) |
|
|
11
11
|
| `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
|
|
12
12
|
| `/spec-hotfix` | (Hotfix) Fork a worktree from a release tag, red→green, land by tag + cherry-pick | `In Progress` | `specs/in-progress/` |
|
|
13
|
+
| `/no-spec` | Work that genuinely has no spec — its own branch, worktree, page and land. Writes no spec and moves nothing through the lifecycle | `—` | (none) |
|
|
13
14
|
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
14
15
|
| `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
|
|
15
16
|
| `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
|
|
@@ -52,6 +53,81 @@ anything — scoped to the window, refusing when two arrive. `/spec-reviewed` is
|
|
|
52
53
|
what answers everything outside it: a pass sent when nobody was waiting, two
|
|
53
54
|
passes to choose between, and every harness with no file-watch to wait with.
|
|
54
55
|
|
|
56
|
+
**`/no-spec` is the one row that writes no spec**, and it is in the table
|
|
57
|
+
because it is the same lane: a branch, a worktree, a review page, a verdict, a
|
|
58
|
+
land. What it leaves out is the document, because mechanical work — a version
|
|
59
|
+
bump, a lockfile refresh, a rename — genuinely has none, and a one-phase spec
|
|
60
|
+
written for it is a document nobody reads. What it does **not** leave out is the
|
|
61
|
+
branch. Doing that work in the primary checkout leaves the base branch dirty for
|
|
62
|
+
every in-flight spec to replay over, and renders no page, so it is the one kind
|
|
63
|
+
of change that reaches the base branch unreviewed. It is model-invocable, which
|
|
64
|
+
matters: it is where Claude is sent when it would otherwise write on the base
|
|
65
|
+
branch. Beneath it, `skitterspec spec-env nospec <name>` records the branch —
|
|
66
|
+
and that record is what makes a name with nothing under `specs/**` resolvable by
|
|
67
|
+
`review`, `integrate`, `down` and a bare `resolve`.
|
|
68
|
+
|
|
69
|
+
**`/allow-main` is the fourth command.** It is user-only for a different reason
|
|
70
|
+
from the other three: `/spec-connect`, `/spec-live` and `/spec-remote-review`
|
|
71
|
+
are marked that way because there is no judgment to apply. This one is marked that
|
|
72
|
+
way because **Claude must not be able to lift a guard aimed at Claude.**
|
|
73
|
+
|
|
74
|
+
The guard is `.claude/hooks/main-guard.cjs`, a `PreToolUse` hook on the write
|
|
75
|
+
tools, and it refuses an `Edit` or a `Write` in the **primary checkout** while
|
|
76
|
+
it is on the **base branch**. `main` is where work lands, not where it happens:
|
|
77
|
+
work done there leaves the base branch dirty for every in-flight spec to replay
|
|
78
|
+
over, and renders no review page at all, so it is the one kind of change that
|
|
79
|
+
arrives unreviewed. It refuses the FIRST write rather than the commit, because
|
|
80
|
+
nothing has been written yet and the fix is one command — where a commit-time
|
|
81
|
+
gate fires once the work already exists in a checkout that cannot `switch -c`
|
|
82
|
+
out from under the other worktrees.
|
|
83
|
+
|
|
84
|
+
It fires only on a **positive signal** — isolation configured, the guard not
|
|
85
|
+
switched off, this directory IS the primary checkout, HEAD IS the base branch —
|
|
86
|
+
and every cannot-tell allows the write in silence: no repo, no config, a
|
|
87
|
+
worktree, an unresolvable base, a missing or crashed engine
|
|
88
|
+
(`.claude/rules/negative-checks.md`). `skitterspec spec-env main
|
|
89
|
+
<check|allow|status>` is the engine, and `guards.mainIsLandingZone` in
|
|
90
|
+
`env.config.json` turns it off for a project. It is
|
|
91
|
+
**on by default wherever isolation is configured**, matching the review gate.
|
|
92
|
+
|
|
93
|
+
There is **no allowlist**, and it is empty by construction rather than by
|
|
94
|
+
omission: `/spec` authors into its own worktree and `/no-spec` catches ad-hoc
|
|
95
|
+
work, so no legitimate writer to the base branch is left. `/spec-init` looks
|
|
96
|
+
like the exception and is not one — it bootstraps a repo with no
|
|
97
|
+
`env.config.json` yet, so the guard never fires there.
|
|
98
|
+
|
|
99
|
+
**Two exits, and they are not equal.** `/no-spec <name>` and `/spec-start
|
|
100
|
+
<name>` move the work, and Claude takes those on its own; `/allow-main` lifts
|
|
101
|
+
the guard, and only a person can type it. That asymmetry is the same line
|
|
102
|
+
`/spec-reviewed` draws, and `spec-planning.md` already records that prose alone
|
|
103
|
+
did not hold it once.
|
|
104
|
+
|
|
105
|
+
**`/spec-skip` is the fifth command**, and it is user-only for `/allow-main`'s
|
|
106
|
+
reason rather than the mild one: it lifts the review gate, which is a guard
|
|
107
|
+
aimed at Claude. It pre-executes `skitterspec spec-env review skip "<reason>"`
|
|
108
|
+
and relays it — the same exit that always existed, at an address a person will
|
|
109
|
+
actually type. The long form stays exactly as valid and is what the engine
|
|
110
|
+
prints; what changed is that a refusal now names something reachable, because a
|
|
111
|
+
gate whose exit nobody can find gets switched off wholesale instead of answered.
|
|
112
|
+
|
|
113
|
+
**It is not a `--force`, and one was asked for.** A reasonless lift reopens
|
|
114
|
+
precisely what the reason requirement closed: `none: additive, nothing to
|
|
115
|
+
revert` is a decision a reviewer can argue with, and silence is an oversight.
|
|
116
|
+
So a bare `/spec-skip` refuses and asks for a reason, and the two things that
|
|
117
|
+
discharge a phase's obligation are still a committing verdict and a recorded
|
|
118
|
+
skip — never a flag. Putting the flag on `/commit` was the other suggestion and
|
|
119
|
+
is worse: `/commit` belongs to skittership, which is why the gate is enforced
|
|
120
|
+
by a hook rather than by editing that skill.
|
|
121
|
+
|
|
122
|
+
**A refusal may OFFER its own exit, and that is a different thing again.**
|
|
123
|
+
Where the engine declares one, a skill relays the refusal and **then** offers it
|
|
124
|
+
— once per obligation, with the choice recorded, and answered by the operator
|
|
125
|
+
rather than by Claude. `.claude/rules/offered-unblocks.md` is the whole
|
|
126
|
+
contract. The three properties named there are what let a Claude-raised bypass
|
|
127
|
+
survive the rule above, where a reasonless `--force` did not: the operator
|
|
128
|
+
answers, the choice is on the record, and it is offered once. Remove any one and
|
|
129
|
+
the objection returns.
|
|
130
|
+
|
|
55
131
|
`/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
|
|
56
132
|
real judgment (green tests before a land; an MCP fetch and a team-key check; ten
|
|
57
133
|
subcommands) — but they are marked user-only too, since nobody reaches them
|
|
@@ -98,7 +174,7 @@ your canonical `localhost` ports so you can test it at the normal URL
|
|
|
98
174
|
move, header edits, the code) happens on the spec's branch in the worktree; `main`
|
|
99
175
|
changes only when it merges. Teardown is folded into `/spec-complete` ·
|
|
100
176
|
`/spec-cancel`. Beneath the skills, `skitterspec spec-env
|
|
101
|
-
<up|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
|
|
177
|
+
<up|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
|
|
102
178
|
engine. **Omit the spec name anywhere and it uses the worktree you are standing in**,
|
|
103
179
|
else the sole provisioned spec — with no exceptions left: a bare
|
|
104
180
|
`/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
|
|
@@ -129,11 +205,42 @@ stateful ones (`Stack: worktree + docker`, or a branch touching migrations) —
|
|
|
129
205
|
`/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
|
|
130
206
|
Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
|
|
131
207
|
|
|
132
|
-
**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
208
|
+
**Authoring a spec is work, so it happens off the base branch too (`/spec`).**
|
|
209
|
+
`main` is where work **lands**, not where it happens, and a spec document is no
|
|
210
|
+
exception. With isolation configured, `/spec` grills first — which puts nothing
|
|
211
|
+
on disk — then provisions the new spec's own worktree with
|
|
212
|
+
`spec-env up <name> --docs`, moves the session into it, and writes the spec
|
|
213
|
+
there. `--docs` skips the `setup` commands and Docker, so a tree to write
|
|
214
|
+
markdown in costs one `git worktree add`; `/spec-start` re-runs `up` without the
|
|
215
|
+
flag and the install happens then, at the first moment the tree holds code.
|
|
216
|
+
|
|
217
|
+
**The committing verdict is what puts it on the base branch.** Phase C2's page
|
|
218
|
+
offers `Commit & Start` and `Commit`, and **both** commit on the branch and then
|
|
219
|
+
`spec-env integrate` — rebase, fast-forward — so the base gains the spec as one
|
|
220
|
+
commit. That is not optional tidiness: `ls specs/backlog/` is how specs are
|
|
221
|
+
found, and a folder bucket is only the truth on the branch you are standing on,
|
|
222
|
+
so a spec left unlanded is invisible to everyone. They differ only in the
|
|
223
|
+
worktree: `Commit & Start` keeps it and carries into `/spec-start`, plain
|
|
224
|
+
`commit` tears it down, because a spec parked in the backlog should hold no
|
|
225
|
+
checkout.
|
|
226
|
+
|
|
227
|
+
**What this bought is the reason it changed.** The page waits for a verdict
|
|
228
|
+
without a timeout — deliberately, because a reader walking away from a diff is
|
|
229
|
+
the normal case — and while the spec was authored in the primary checkout that
|
|
230
|
+
wait held the base branch dirty for as long as it lasted, so a release could not
|
|
231
|
+
be cut through it. Now an unanswered spec sits on its own branch in its own
|
|
232
|
+
worktree, in nothing's way. Phase C2 still **arms nothing**: the gate asserts
|
|
233
|
+
that a phase which ended owes an answer, and a backlog spec owes no phase — and
|
|
234
|
+
walking away from it now costs nobody anything rather than leaving a mess
|
|
235
|
+
someone else has to work around.
|
|
236
|
+
|
|
237
|
+
With isolation **absent** none of this applies: there is no worktree to make and
|
|
238
|
+
no land to do, so `/spec` writes the folder where the session is standing,
|
|
239
|
+
exactly as it always did.
|
|
240
|
+
|
|
241
|
+
**Staging one spec and not another (`spec-env stage`).** A `git add specs/`
|
|
242
|
+
stages a *directory*, sweeping whatever else happens to be sitting in it into
|
|
243
|
+
this spec's commit, under this spec's ticket trailer.
|
|
137
244
|
`skitterspec spec-env stage [<spec>] [--json]` is how a skill asks instead of
|
|
138
245
|
guessing: it splits the uncommitted tree into the paths that are this spec's —
|
|
139
246
|
its folder in **any** bucket, so a tree mid-`git mv` is handled, plus each
|
|
@@ -144,6 +251,14 @@ the primary checkout outside, and prints which. The `owned` half is the spec's
|
|
|
144
251
|
the commits this bounds are the lifecycle ones. Nothing here refuses, writes, or
|
|
145
252
|
exits non-zero — `foreign` is a list of paths to leave alone, not an accusation.
|
|
146
253
|
|
|
254
|
+
**And it stays load-bearing now that `/spec` authors in its own worktree**, even
|
|
255
|
+
though that tree holds one spec. It was once justified by several specs sharing
|
|
256
|
+
one `specs/` folder, and that justification is gone; what replaces it is smaller
|
|
257
|
+
and just as real. A project's `spec.companionPaths`, a hand-edited
|
|
258
|
+
`specs/.core/`, and a tracker snapshot all land in the same tree as the spec, and
|
|
259
|
+
only the spec's own documents belong under its authoring verdict. Where isolation
|
|
260
|
+
is absent the original reason applies unchanged.
|
|
261
|
+
|
|
147
262
|
**Naming the paths is necessary and not sufficient**, which is why every spec
|
|
148
263
|
commit is also pathspec-limited: `git add -- <paths>` **and**
|
|
149
264
|
`git commit -m "…" -- <paths>`. A checkout has one `.git/index` and every session
|
|
@@ -237,6 +352,34 @@ silently. All of it lives beside the page in gitignored `.spec-env/`, and
|
|
|
237
352
|
**the marks are information, never a gate**: nothing counts them and nothing
|
|
238
353
|
refuses on them.
|
|
239
354
|
|
|
355
|
+
**A second opinion, from something that did not write the code (`review.reviewers`).**
|
|
356
|
+
The page can also carry findings from code reviewers
|
|
357
|
+
that run from a CLI — CodeRabbit's has a free tier, PR-Agent and Kodus are
|
|
358
|
+
self-hosted — and they arrive as **checks**, the same shape a written review
|
|
359
|
+
uses. `/spec-next` runs them once per phase before the render, so the page is
|
|
360
|
+
populated when it is opened; `/spec-diff --reviewers` is the opt-in mid-phase
|
|
361
|
+
run, and a bare `/spec-diff` never spends one. Results are cached against a hash
|
|
362
|
+
of the diff, so re-reading unchanged work costs nothing.
|
|
363
|
+
|
|
364
|
+
**They inform; they never gate.** A check is not a comment, so a page carrying
|
|
365
|
+
twenty findings still offers `Commit` — and a reader who replies to one creates
|
|
366
|
+
a comment, which does gate, because then a person has asked for something. There
|
|
367
|
+
is deliberately no severity threshold that blocks: that is the counting gate
|
|
368
|
+
this whole design exists against, wearing a vendor's name.
|
|
369
|
+
|
|
370
|
+
**And a reviewer that could not run says so**, which is the one place
|
|
371
|
+
`.claude/rules/negative-checks.md` inverts. Silence is normally the safe branch;
|
|
372
|
+
here a rate-limited or unauthenticated reviewer would render identically to one
|
|
373
|
+
that read the diff and approved of it, on the page a commit decision is made
|
|
374
|
+
from. So each gets a line — `12 findings` · `clean` · `did not run — <why>` —
|
|
375
|
+
and none of it ever refuses a render, a verdict or a commit.
|
|
376
|
+
|
|
377
|
+
The list is **empty by default** and `init` never writes one: configuring a
|
|
378
|
+
hosted reviewer sends the worktree's diff to a third party, which is a real
|
|
379
|
+
change for a tool that otherwise touches nothing outside `.spec-env/`. Every
|
|
380
|
+
finding on the page is badged with the reviewer that produced it. `env.config.md`
|
|
381
|
+
carries the field reference.
|
|
382
|
+
|
|
240
383
|
**The verdict is chosen, never derived.** A verdict is one person's conclusion,
|
|
241
384
|
sent once per pass; a counting gate is a refusal computed from how many boxes
|
|
242
385
|
are ticked, and it stays forbidden. The one refusal here is not a count of
|
|
@@ -311,7 +454,7 @@ its verdicts and its fields.
|
|
|
311
454
|
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-orders` · phase 2 moved |
|
|
312
455
|
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
313
456
|
| **Built** | POST /orders handler, orders schema |
|
|
314
|
-
| **Tests** | 128 passed ·
|
|
457
|
+
| **Tests** | 128 passed · dotnet test |
|
|
315
458
|
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) · **remote** off |
|
|
316
459
|
| **Follow-ups** | none |
|
|
317
460
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
@@ -413,9 +556,15 @@ The spec skills tell you to run "your project's typecheck and test commands" and
|
|
|
413
556
|
to "honour project conventions". Make those concrete here so specs stay
|
|
414
557
|
consistent with the codebase:
|
|
415
558
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
559
|
+
Skitterspec never runs these itself and knows nothing about what this project
|
|
560
|
+
is written in — it shells out to `git`, and to whatever you write below. The
|
|
561
|
+
examples span several stacks on purpose: pick the shape that fits, or write one
|
|
562
|
+
none of them describes.
|
|
563
|
+
|
|
564
|
+
- **Typecheck command:** `<e.g. npm run typecheck · dotnet build · mypy . · go vet ./...>`
|
|
565
|
+
- **Test command:** `<e.g. npm test · dotnet test · pytest · go test ./...>`
|
|
566
|
+
(single file/dir: `<e.g. npx vitest run path · dotnet test --filter · pytest path>`)
|
|
567
|
+
- **Lint/format:** `<e.g. npm run lint · dotnet format · ruff check · golangci-lint run>`
|
|
419
568
|
- **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
|
|
420
569
|
(architecture, code style, testing, database, etc.) rather than restating them.
|
|
421
570
|
|
|
@@ -35,7 +35,7 @@ A verdict sentence, a blank line, then a two-column table:
|
|
|
35
35
|
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-foo` · phase 2 moved |
|
|
36
36
|
| **Branch** | `spec/feat-foo` · 3 commits, clean |
|
|
37
37
|
| **Built** | POST /orders handler, orders schema |
|
|
38
|
-
| **Tests** | 128 passed ·
|
|
38
|
+
| **Tests** | 128 passed · dotnet test |
|
|
39
39
|
| **Review** | 7 files, +212 −18 · **local** [http://127.0.0.1:7760/…](http://127.0.0.1:7760/…) · **network** [http://192.168.0.136:7760/…](http://192.168.0.136:7760/…) · **remote** off |
|
|
40
40
|
| **Follow-ups** | none |
|
|
41
41
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
@@ -201,6 +201,42 @@ escape hatch is what let a whole class of runs go on asking questions nobody was
|
|
|
201
201
|
listening for. Where a watch is unavailable the wait is the turn ending, which
|
|
202
202
|
every harness can do.
|
|
203
203
|
|
|
204
|
+
**A watch that dies is re-armed, silently, on the same window.** A backgrounded
|
|
205
|
+
wait does not always survive a long idle: three sessions lost theirs over one
|
|
206
|
+
lunch break, and two in another were killed at 43 and 30 minutes. So a wait that
|
|
207
|
+
ends **without a verdict** and **was not stopped by the operator** is started
|
|
208
|
+
again on the **same** `--since`. Not a fresh one — a pass that arrived during
|
|
209
|
+
the gap is still inside the original window, and re-arming on a new timestamp
|
|
210
|
+
would strand exactly that pass.
|
|
211
|
+
|
|
212
|
+
**Say nothing when you do it.** Nothing happened and the window is unchanged; a
|
|
213
|
+
line on every death turns a working loop into a stream of non-events.
|
|
214
|
+
|
|
215
|
+
**An operator who stopped it is not a death.** Re-arming over a deliberate stop
|
|
216
|
+
makes the wait unstoppable, which is worse than a wait that stops. Where the two
|
|
217
|
+
cannot be told apart, treat it as deliberate and leave it
|
|
218
|
+
(`.claude/rules/negative-checks.md` rule 4): the cost of being wrong is one
|
|
219
|
+
`/spec-reviewed`, against a loop nobody can break.
|
|
220
|
+
|
|
221
|
+
**It is bounded by the age of the window, not by a count of retries.** Keep
|
|
222
|
+
re-arming while the window is younger than **12 hours**, then stop. A count means
|
|
223
|
+
something different depending on whether deaths come five minutes or forty-five
|
|
224
|
+
apart, and the observed interval already varies that much. Twelve hours
|
|
225
|
+
deliberately does not cover an overnight gap — the pass is never lost, and
|
|
226
|
+
`/spec-reviewed` is the designed recovery — because the alternative is waking
|
|
227
|
+
the session every half hour all night for a review nobody is returning to
|
|
228
|
+
before morning.
|
|
229
|
+
|
|
230
|
+
**Past the bound the banner degrades**, to the sentence this file already
|
|
231
|
+
defines for a transport that cannot push into the conversation:
|
|
232
|
+
|
|
233
|
+
*press a verdict, then type `/spec-reviewed` — I cannot see it until you do.*
|
|
234
|
+
|
|
235
|
+
That is a new **state** for an existing sentence, not a new sentence. The
|
|
236
|
+
holding line is only ever printed while a wait is actually running, which is the
|
|
237
|
+
rule this section has always carried: *only promise a wait the transport can
|
|
238
|
+
deliver.*
|
|
239
|
+
|
|
204
240
|
**The `Continue` ending, and why the banner's exits differ mid-run.** A page
|
|
205
241
|
rendered part-way through a run offers `Continue` — *I have read it, carry on* —
|
|
206
242
|
in place of the committing verdicts, because "commit" is the wrong verb for
|
|
@@ -422,10 +458,10 @@ fenced block **above** the table, where a fence means what a fence should mean:
|
|
|
422
458
|
|
|
423
459
|
| | |
|
|
424
460
|
|---|---|
|
|
425
|
-
| **Why** | 3 assertions fail in `
|
|
461
|
+
| **Why** | 3 assertions fail in `test_orders.py`; nothing committed |
|
|
426
462
|
| **Branch** | `spec/feat-foo` · 2 commits, dirty |
|
|
427
463
|
| **Built** | POST /orders handler, orders schema |
|
|
428
|
-
| **Tests** | 125 passed, 3 failed ·
|
|
464
|
+
| **Tests** | 125 passed, 3 failed · pytest |
|
|
429
465
|
| **Follow-ups** | none |
|
|
430
466
|
| **Next** | fix the failures, then `/spec-next` to finish phase 2 |
|
|
431
467
|
|
|
@@ -452,11 +488,16 @@ finish, so it outlives the session:
|
|
|
452
488
|
canonical port; not in scope here.
|
|
453
489
|
```
|
|
454
490
|
|
|
455
|
-
**
|
|
456
|
-
worktree, and a spec authored there physically
|
|
457
|
-
from the base branch, and cancelled along with its host
|
|
458
|
-
|
|
459
|
-
|
|
491
|
+
**The offer needs no caveat about where.** These skills run inside the spec's own
|
|
492
|
+
worktree, and a follow-up spec authored *there* would physically live on that
|
|
493
|
+
branch — invisible from the base branch, and cancelled along with its host. That
|
|
494
|
+
used to make the offer name the primary checkout as the place to write it.
|
|
495
|
+
It no longer does: `/spec` provisions the **new** spec's own worktree before it
|
|
496
|
+
writes anything and moves the session there, so the follow-up lands on its own
|
|
497
|
+
branch and its own ticket without anyone naming a location. Take the offer and
|
|
498
|
+
let `/spec` handle it. Where isolation is absent there is no worktree to move to,
|
|
499
|
+
and the old caveat applies — `spec-planning.md` and `commit-trailers.md` carry
|
|
500
|
+
the reasoning.
|
|
460
501
|
|
|
461
502
|
## Worked examples
|
|
462
503
|
|
|
@@ -468,7 +509,7 @@ cancelled. So the offer names the primary checkout as the place to write it.
|
|
|
468
509
|
|---|---|
|
|
469
510
|
| **Branch** | `spec/feat-foo` · 4 commits, clean |
|
|
470
511
|
| **Built** | Auth middleware, session table |
|
|
471
|
-
| **Tests** | 140 passed ·
|
|
512
|
+
| **Tests** | 140 passed · go test ./... |
|
|
472
513
|
| **Follow-ups** | Session expiry is read from two places; phase 4 leans on one |
|
|
473
514
|
| **Next** | `/spec-next` → phase 4 (Docs) |
|
|
474
515
|
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: no-spec
|
|
3
|
+
description: Do a piece of work that genuinely has no spec — a version bump, a lockfile refresh, a rename — on its own branch in its own worktree, reviewed on a page and landed on the verdict. Never on the base branch. Use when the user says "/no-spec", or asks for mechanical work that does not warrant a spec and should not sit on main.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /no-spec — work with no spec, off the base branch
|
|
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
|
+
|
|
13
|
+
Some work has no spec and should not have one. Bumping four projects to a new
|
|
14
|
+
version, refreshing a lockfile, a rename across a package: writing a one-phase
|
|
15
|
+
document for it produces a spec nobody reads, and `spec --quick` would have been
|
|
16
|
+
that under a friendlier name.
|
|
17
|
+
|
|
18
|
+
**What it is not is an excuse to work on the base branch.** Two things follow
|
|
19
|
+
from doing it there, and both cost someone else something: the tree is left
|
|
20
|
+
dirty, so every in-flight spec has to replay over it and a release cannot be
|
|
21
|
+
cut; and no page is ever rendered, so the work gets no review at all. This skill
|
|
22
|
+
is the same lane every spec uses — branch, worktree, page, verdict, land —
|
|
23
|
+
with the document left out because there is nothing to put in it.
|
|
24
|
+
|
|
25
|
+
**It writes no spec, moves nothing through the lifecycle, and mints no ticket.**
|
|
26
|
+
There is no bucket, no status, no phase index and no `Refs:` trailer. If you
|
|
27
|
+
find yourself wanting any of those, the work wanted `/spec`.
|
|
28
|
+
|
|
29
|
+
## 1. Name it
|
|
30
|
+
|
|
31
|
+
Take a kebab-case name from the invocation. It becomes the branch
|
|
32
|
+
(`chore/<name>`) and the worktree folder, so it should describe the work:
|
|
33
|
+
`bump-deps`, `rename-widget-props`, `lockfile-refresh`.
|
|
34
|
+
|
|
35
|
+
**Ask if none was given.** Do not invent one — a name nobody chose is a branch
|
|
36
|
+
nobody recognises a week later, and this is the one thing the skill cannot infer
|
|
37
|
+
from the work.
|
|
38
|
+
|
|
39
|
+
**Refuse a name that is already a spec**, which the engine does for you: two
|
|
40
|
+
things answering to one name is how a teardown removes the wrong tree.
|
|
41
|
+
|
|
42
|
+
## 2. Provision
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
skitterspec spec-env nospec <name>
|
|
46
|
+
```
|
|
47
|
+
|
|
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.
|
|
52
|
+
|
|
53
|
+
**Confirm the move landed** rather than reading silence as success
|
|
54
|
+
(`.claude/rules/negative-checks.md` rule 1): `skitterspec spec-env resolve` with
|
|
55
|
+
no argument must name this branch, and report `no spec` as its bucket. If it
|
|
56
|
+
names something else, stop — say so and do nothing further, because the failure
|
|
57
|
+
this guards against is a session that writes a branch's worth of work into the
|
|
58
|
+
primary checkout while everything looks normal at the time.
|
|
59
|
+
|
|
60
|
+
**The record is not bookkeeping.** It is the only thing that makes a name with
|
|
61
|
+
nothing under `specs/**` resolvable, so without it `review`, `integrate`, `down`
|
|
62
|
+
and a bare `resolve` would all refuse this branch as a spec that does not exist.
|
|
63
|
+
|
|
64
|
+
**Where isolation is not configured** (`specs/.core/env.config.json` absent)
|
|
65
|
+
there is no worktree to make. Say so in one line and stop: this skill exists to
|
|
66
|
+
move work off the base branch, and without isolation there is nowhere to move it
|
|
67
|
+
to. Do not silently do the work in place — that is the outcome it exists to
|
|
68
|
+
prevent.
|
|
69
|
+
|
|
70
|
+
## 3. Do the work
|
|
71
|
+
|
|
72
|
+
Follow the project's rules in `.claude/rules/*.md` and `CLAUDE.md`, exactly as a
|
|
73
|
+
phase would. Tests are part of the work and not after it: extend or add whatever
|
|
74
|
+
covers the change, then run the project's typecheck and test commands. Do not
|
|
75
|
+
proceed to the page until they are green — and if they cannot be made green, say
|
|
76
|
+
so where it happens and report `❌`.
|
|
77
|
+
|
|
78
|
+
**Mechanical does not mean untested.** A version bump that breaks a build is the
|
|
79
|
+
commonest thing in this lane, which is why the gate is the same one a phase has.
|
|
80
|
+
|
|
81
|
+
## 4. Render the page and arm the gate
|
|
82
|
+
|
|
83
|
+
The work is done, the tests are green, and nothing is committed yet:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
skitterspec spec-env review <name> --buttons nospec --run-reviewers
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**This is free.** The engine reads git and splices text into a template; the
|
|
90
|
+
diff never passes through you however large it is.
|
|
91
|
+
|
|
92
|
+
`--buttons nospec` offers `Commit & Land` and `Commit`. It is its own set
|
|
93
|
+
because neither of the usual pairs fits: there is no next phase to continue and
|
|
94
|
+
nothing to put in flight.
|
|
95
|
+
|
|
96
|
+
**Then arm the gate:**
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
skitterspec spec-env review arm <name>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**This skill arms, and `/spec` does not.** That asymmetry is the rule in
|
|
103
|
+
`.claude/rules/spec-reports.md` rather than a choice made here: the gate asserts
|
|
104
|
+
that *finished work* owes an answer, and this is finished work. A written spec
|
|
105
|
+
owes no phase, so authoring arms nothing.
|
|
106
|
+
|
|
107
|
+
**Then offer `/spec-diff`. Do not run it.** The written review costs real tokens
|
|
108
|
+
because writing it means reading the diff, and that spend is the user's call.
|
|
109
|
+
|
|
110
|
+
## 5. Wait for the verdict
|
|
111
|
+
|
|
112
|
+
**The wait is a command. Do not write one.**
|
|
113
|
+
|
|
114
|
+
1. **Note the moment you start waiting**, as an ISO timestamp. That instant is
|
|
115
|
+
the whole scope of what you may claim.
|
|
116
|
+
2. **Run the engine's wait in the background, and end your turn:**
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
skitterspec spec-env review wait <name> --since <the timestamp>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Pass no timeout. A reader who walks away from a diff is the normal case, and
|
|
123
|
+
a bounded watch once lost a verdict to a lunch break.
|
|
124
|
+
3. **On waking, let the engine pick:**
|
|
125
|
+
|
|
126
|
+
**A watch that dies is re-armed on the same window**, silently and bounded by
|
|
127
|
+
the age of that window — `.claude/rules/spec-reports.md` carries the contract,
|
|
128
|
+
including when to stop and what the banner says instead. Do not restate it
|
|
129
|
+
here.
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
skitterspec spec-env review <name> --claim-since <the timestamp> --json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
It claims the one pass that arrived inside the window, acts on nothing when
|
|
136
|
+
none did, and refuses to choose when two did.
|
|
137
|
+
|
|
138
|
+
Relay the engine's **stack** — the `local:`, `network:` and `remote:` lines, all
|
|
139
|
+
three, in that order — never the bare `page:` path.
|
|
140
|
+
`.claude/rules/spec-reports.md` carries the shape and why every tier is named.
|
|
141
|
+
|
|
142
|
+
### Route on the verdict
|
|
143
|
+
|
|
144
|
+
- **`commit-land`** — hand off to `review.commitWith` (`/commit` by default),
|
|
145
|
+
then land it, then tear the worktree down:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
skitterspec spec-env integrate <name>
|
|
149
|
+
skitterspec spec-env down <name>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`integrate` plans a rebase onto the base branch and a `merge --ff-only`; run
|
|
153
|
+
the commands it prints. `down` removes the worktree, deletes the branch and
|
|
154
|
+
**forgets the specless record**, which is what stops a torn-down name going on
|
|
155
|
+
answering to every verb with a path that is no longer there.
|
|
156
|
+
|
|
157
|
+
**If the rebase conflicts, stop.** `git rebase --abort`, leave the commit on
|
|
158
|
+
the branch, tear nothing down, and report `❌` with the conflict — there is a
|
|
159
|
+
standing worktree, which is what separates `❌` from `⏸`.
|
|
160
|
+
|
|
161
|
+
- **`commit`** — the same commit, and **stop there**. The branch stands with the
|
|
162
|
+
work on it, which is the right ending for work that turned out to be bigger
|
|
163
|
+
than one sitting. Say in `Next` that `/spec-to-main` lands it later.
|
|
164
|
+
- **`changes`** — work the notes, record a resolution for each one so the next
|
|
165
|
+
render shows it struck through with what changed, then
|
|
166
|
+
**re-render and wait again**. The reader is still holding a decision.
|
|
167
|
+
- **`discuss`** — report and talk. Claim nothing, change nothing, tear nothing
|
|
168
|
+
down.
|
|
169
|
+
|
|
170
|
+
**Never tear the worktree down on anything but `commit-land`.** It is the only
|
|
171
|
+
verdict that says the work is finished, and the cost of being wrong is somebody
|
|
172
|
+
else's branch removed from disk.
|
|
173
|
+
|
|
174
|
+
## Report
|
|
175
|
+
|
|
176
|
+
End with the block defined in `.claude/rules/spec-reports.md`. That file carries
|
|
177
|
+
the shape; this section carries only what is specific here.
|
|
178
|
+
|
|
179
|
+
**Verdicts**
|
|
180
|
+
|
|
181
|
+
- `✅` — the work is done, the tests are green, and the verdict was acted on.
|
|
182
|
+
- `⚠️` — done and green, with something worth knowing.
|
|
183
|
+
- `❌` — the tests are red, or the land failed part-way. Quote the failure and
|
|
184
|
+
say what is standing.
|
|
185
|
+
- `⏸` — refused before acting: no name given, the name is already a spec, the
|
|
186
|
+
`cd` did not take, or the project has no isolation. Nothing changed.
|
|
187
|
+
|
|
188
|
+
**Fields:** `Branch` · `Built` · `Tests` · `Notes` · `Landed` · `Worktree` ·
|
|
189
|
+
`Review` · `Follow-ups` · `Next`
|
|
190
|
+
|
|
191
|
+
**No `Tracker` and no `Spec`, ever.** There is no ticket and no document, and a
|
|
192
|
+
row saying so would report an absence with nothing behind it — which
|
|
193
|
+
`spec-reports.md` forbids for exactly this reason.
|
|
194
|
+
|
|
195
|
+
`Landed` and `Worktree` appear only once a verdict has been acted on; a run
|
|
196
|
+
waiting on the page has neither, and **omits the `Review` row** in favour of the
|
|
197
|
+
banner, which carries the counts, the stack and the two exits:
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## ⏸ Review ready — <N> files, +<a> −<d>
|
|
202
|
+
|
|
203
|
+
- **local** — <the `local:` URL>
|
|
204
|
+
- **network** — <the `network:` URL, or off with the command that turns it on>
|
|
205
|
+
- **remote** — <the `remote:` URL, or off with the command that turns it on>
|
|
206
|
+
|
|
207
|
+
I'm holding here until you send a verdict — the wait covers local and network.
|
|
208
|
+
|
|
209
|
+
`Commit & Land` finishes it · `Commit` keeps the branch
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
There `Next` names the page rather than a command, because the button is what
|
|
214
|
+
carries the work on.
|
|
215
|
+
|
|
216
|
+
**`Follow-ups` is where a spec gets offered.** This lane is for work with no
|
|
217
|
+
spec, so the thing it most often surfaces is work that *does* want one. Offer to
|
|
218
|
+
`/spec` it on the spot — and `/spec` provisions its own worktree, so there is
|
|
219
|
+
no caveat about where to write it.
|