@skitterbyte/skitterspec 21.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/MIGRATION.md +218 -0
- package/README.md +127 -6
- package/assets/claude-md-section.md +42 -18
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-remote-review.md +22 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/env.config.json.example +6 -3
- package/assets/core/env.config.md +183 -25
- package/assets/hooks/main-guard.cjs +145 -0
- package/assets/hooks/review-gate.cjs +8 -3
- package/assets/review/page.html +1174 -101
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +195 -14
- package/assets/rules/spec-reports.md +181 -29
- package/assets/skills/no-spec/SKILL.md +219 -0
- package/assets/skills/spec/SKILL.md +248 -21
- package/assets/skills/spec-bug/SKILL.md +34 -24
- 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 +164 -37
- package/assets/skills/spec-hotfix/SKILL.md +22 -19
- package/assets/skills/spec-init/SKILL.md +4 -2
- package/assets/skills/spec-next/SKILL.md +166 -58
- package/assets/skills/spec-review/SKILL.md +87 -0
- package/assets/skills/spec-reviewed/SKILL.md +31 -5
- package/assets/skills/spec-start/SKILL.md +33 -0
- package/package.json +1 -1
- package/src/cli.js +1612 -172
- package/src/env/classify.js +87 -2
- package/src/env/config.js +333 -19
- package/src/env/hooks.js +102 -33
- package/src/env/live.js +124 -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 +128 -5
- package/src/env/review.js +854 -29
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +474 -50
- package/src/env/supervise.js +8 -1
- package/src/env/teardown.js +12 -1
- package/src/init.js +185 -24
|
@@ -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.
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# Spec Planning
|
|
2
2
|
|
|
3
3
|
Spec-driven development is driven by ten lifecycle skills (plus the
|
|
4
|
-
`/spec-connect` and `/spec-
|
|
4
|
+
`/spec-connect`, `/spec-live` and `/spec-remote-review` **commands** when isolation is on) — use them rather than hand-rolling specs so the structure
|
|
5
5
|
and lifecycle stay consistent. Each sets a status on the spec header
|
|
6
6
|
(`> **Status:** …`):
|
|
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) |
|
|
@@ -20,13 +21,24 @@ and lifecycle stay consistent. Each sets a status on the spec header
|
|
|
20
21
|
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
21
22
|
|
|
22
23
|
**Skills vs commands.** The table above lists **skills** — Claude reads them and
|
|
23
|
-
exercises judgment. `/spec-connect` and `/spec-
|
|
24
|
+
exercises judgment. `/spec-connect`, `/spec-live` and `/spec-remote-review` are
|
|
25
|
+
instead **slash commands**
|
|
24
26
|
(`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
|
|
25
27
|
relays its output, so there is no judgment to apply and no model turn spent
|
|
26
28
|
finding one. They are marked `disable-model-invocation`, meaning
|
|
27
29
|
**only you can run them** — a skill that wants one will tell you to type it
|
|
28
30
|
rather than invoking it.
|
|
29
31
|
|
|
32
|
+
**`/spec-remote-review`** is the newest and the smallest: it toggles
|
|
33
|
+
`review.allowRemote`, and takes `on`/`off` when you would rather say which.
|
|
34
|
+
It exists because a render's `remote:` line has to name something a person can
|
|
35
|
+
act on, and `skitterspec spec-env review allow remote` is not what anyone
|
|
36
|
+
reconstructs from a page they are reading on a phone. Making the line
|
|
37
|
+
*clickable* was considered and rejected — the only clickable thing markdown has
|
|
38
|
+
is a URL, and a URL that acts when it is **fetched** is one a link previewer or
|
|
39
|
+
a prefetcher fires with nobody involved. It **permits** publishing; it publishes
|
|
40
|
+
nothing.
|
|
41
|
+
|
|
30
42
|
`/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
|
|
31
43
|
convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
|
|
32
44
|
review pass is never claimed unasked. Because the model cannot invoke this
|
|
@@ -41,6 +53,81 @@ anything — scoped to the window, refusing when two arrive. `/spec-reviewed` is
|
|
|
41
53
|
what answers everything outside it: a pass sent when nobody was waiting, two
|
|
42
54
|
passes to choose between, and every harness with no file-watch to wait with.
|
|
43
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
|
+
|
|
44
131
|
`/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
|
|
45
132
|
real judgment (green tests before a land; an MCP fetch and a team-key check; ten
|
|
46
133
|
subcommands) — but they are marked user-only too, since nobody reaches them
|
|
@@ -87,7 +174,7 @@ your canonical `localhost` ports so you can test it at the normal URL
|
|
|
87
174
|
move, header edits, the code) happens on the spec's branch in the worktree; `main`
|
|
88
175
|
changes only when it merges. Teardown is folded into `/spec-complete` ·
|
|
89
176
|
`/spec-cancel`. Beneath the skills, `skitterspec spec-env
|
|
90
|
-
<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
|
|
91
178
|
engine. **Omit the spec name anywhere and it uses the worktree you are standing in**,
|
|
92
179
|
else the sole provisioned spec — with no exceptions left: a bare
|
|
93
180
|
`/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
|
|
@@ -118,11 +205,42 @@ stateful ones (`Stack: worktree + docker`, or a branch touching migrations) —
|
|
|
118
205
|
`/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
|
|
119
206
|
Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
|
|
120
207
|
|
|
121
|
-
**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
126
244
|
`skitterspec spec-env stage [<spec>] [--json]` is how a skill asks instead of
|
|
127
245
|
guessing: it splits the uncommitted tree into the paths that are this spec's —
|
|
128
246
|
its folder in **any** bucket, so a tree mid-`git mv` is handled, plus each
|
|
@@ -133,6 +251,14 @@ the primary checkout outside, and prints which. The `owned` half is the spec's
|
|
|
133
251
|
the commits this bounds are the lifecycle ones. Nothing here refuses, writes, or
|
|
134
252
|
exits non-zero — `foreign` is a list of paths to leave alone, not an accusation.
|
|
135
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
|
+
|
|
136
262
|
**Naming the paths is necessary and not sufficient**, which is why every spec
|
|
137
263
|
commit is also pathspec-limited: `git add -- <paths>` **and**
|
|
138
264
|
`git commit -m "…" -- <paths>`. A checkout has one `.git/index` and every session
|
|
@@ -226,6 +352,34 @@ silently. All of it lives beside the page in gitignored `.spec-env/`, and
|
|
|
226
352
|
**the marks are information, never a gate**: nothing counts them and nothing
|
|
227
353
|
refuses on them.
|
|
228
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
|
+
|
|
229
383
|
**The verdict is chosen, never derived.** A verdict is one person's conclusion,
|
|
230
384
|
sent once per pass; a counting gate is a refusal computed from how many boxes
|
|
231
385
|
are ticked, and it stays forbidden. The one refusal here is not a count of
|
|
@@ -300,8 +454,8 @@ its verdicts and its fields.
|
|
|
300
454
|
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-orders` · phase 2 moved |
|
|
301
455
|
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
302
456
|
| **Built** | POST /orders handler, orders schema |
|
|
303
|
-
| **Tests** | 128 passed ·
|
|
304
|
-
| **Review** | 7 files, +212 −18 · [
|
|
457
|
+
| **Tests** | 128 passed · dotnet test |
|
|
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 |
|
|
305
459
|
| **Follow-ups** | none |
|
|
306
460
|
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
307
461
|
|
|
@@ -333,6 +487,27 @@ against a committed last-pushed snapshot and applies it) and `/spec-status`
|
|
|
333
487
|
workflow-state drifted), backed by a `spec-sync` CLI. There is no content pull —
|
|
334
488
|
the tracker is never read back or merged.
|
|
335
489
|
|
|
490
|
+
**One-way means the repo wins, and it now wins *loudly*.** Those are different
|
|
491
|
+
claims and only the first was ever in doubt. A ticket's description is generated
|
|
492
|
+
from the spec, so a person editing it there is writing something the next push
|
|
493
|
+
replaces — which is correct, and which used to happen in silence. The engine now
|
|
494
|
+
compares a hash of what the tracker holds against a hash of what the repo last
|
|
495
|
+
sent, and says *that* it changed and where to read it.
|
|
496
|
+
It **reports and never refuses**: only a person can tell a typo fix from a
|
|
497
|
+
paragraph worth keeping.
|
|
498
|
+
|
|
499
|
+
**This is still not a pull.** Nothing read is merged, written to a spec, or fed
|
|
500
|
+
into the projection, and the text never enters the conversation — the comparison
|
|
501
|
+
is hashes, and the output is one line for a human. It is the same thing
|
|
502
|
+
`compareStored` has always done for a different question.
|
|
503
|
+
|
|
504
|
+
**And adoption no longer costs the reporter their words.** When a provider
|
|
505
|
+
adopts an existing ticket — one a PM or colleague filed — the linking push
|
|
506
|
+
replaces its description with the spec. Before that happens the original is
|
|
507
|
+
posted back onto the ticket as a **comment**: a surface one-way sync never
|
|
508
|
+
touches, so no later push can clobber it. Where a provider supports it, this is
|
|
509
|
+
automatic and has an opt-out. See the provider's own docs.
|
|
510
|
+
|
|
336
511
|
A provider may also ship a **read-only listing** of what the tracker holds. That
|
|
337
512
|
is not a pull: nothing is merged back and no spec file is written. It exists
|
|
338
513
|
because the folder buckets are only the truth *on the branch you are standing
|
|
@@ -381,9 +556,15 @@ The spec skills tell you to run "your project's typecheck and test commands" and
|
|
|
381
556
|
to "honour project conventions". Make those concrete here so specs stay
|
|
382
557
|
consistent with the codebase:
|
|
383
558
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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>`
|
|
387
568
|
- **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
|
|
388
569
|
(architecture, code style, testing, database, etc.) rather than restating them.
|
|
389
570
|
|