@skitterbyte/skitterspec-jira 0.0.0-stage → 1.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 +1089 -0
- package/README.md +42 -2
- package/assets/claude-md-section.md +85 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/commands/spec-remote-review.md +37 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/SETUP.md +192 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +554 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/jira.config.json.example +18 -0
- package/assets/core/jira.config.md +61 -0
- package/assets/hooks/main-guard.cjs +162 -0
- package/assets/hooks/review-gate.cjs +146 -0
- package/assets/review/page.html +3384 -0
- package/assets/rules/commit-trailers.md +96 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +692 -0
- package/assets/rules/spec-reports.md +557 -0
- package/assets/skills/no-spec/SKILL.md +246 -0
- package/assets/skills/spec/SKILL.md +714 -0
- package/assets/skills/spec-bug/SKILL.md +583 -0
- package/assets/skills/spec-cancel/SKILL.md +261 -0
- package/assets/skills/spec-complete/SKILL.md +448 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +605 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-jira-setup/SKILL.md +220 -0
- package/assets/skills/spec-list/SKILL.md +173 -0
- package/assets/skills/spec-next/SKILL.md +676 -0
- package/assets/skills/spec-push/SKILL.md +306 -0
- package/assets/skills/spec-review/SKILL.md +256 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +496 -0
- package/assets/skills/spec-status/SKILL.md +112 -0
- package/assets/skills/spec-sync/SKILL.md +218 -0
- package/assets/skills/spec-to-main/SKILL.md +139 -0
- package/bin/skitterspec-jira.js +61 -0
- package/package.json +40 -4
- package/src/cli.js +5901 -0
- package/src/deprecate.js +138 -0
- package/src/env/bootstrap.js +188 -0
- package/src/env/building.js +143 -0
- package/src/env/classify.js +176 -0
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +753 -0
- package/src/env/dev.js +63 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +385 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +126 -0
- package/src/env/live.js +504 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +192 -0
- package/src/env/prune.js +119 -0
- package/src/env/registry.js +159 -0
- package/src/env/render.js +17 -0
- package/src/env/resolve.js +716 -0
- package/src/env/review.js +2577 -0
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +1044 -0
- package/src/env/supervise.js +157 -0
- package/src/env/teardown.js +248 -0
- package/src/env/trust.js +87 -0
- package/src/gating.js +155 -0
- package/src/init.js +1150 -0
- package/src/lines-diff.js +114 -0
- package/src/prompts.js +129 -0
- package/src/vendor/jira/adf.js +414 -0
- package/src/vendor/jira/api.js +295 -0
- package/src/vendor/jira/cli-sync.js +1256 -0
- package/src/vendor/jira/commands.js +44 -0
- package/src/vendor/jira/config.js +296 -0
- package/src/vendor/jira/credentials.js +108 -0
- package/src/vendor/jira/dialect.js +45 -0
- package/src/vendor/jira/mcp.js +129 -0
- package/src/vendor/provider-kit/index.js +41 -0
- package/src/vendor/provider-kit/src/adapter.js +23 -0
- package/src/vendor/provider-kit/src/apply.js +197 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/config.js +198 -0
- package/src/vendor/provider-kit/src/credentials.js +404 -0
- package/src/vendor/provider-kit/src/doctor.js +35 -0
- package/src/vendor/provider-kit/src/http.js +44 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/md-compare.js +39 -0
- package/src/vendor/provider-kit/src/md-tables.js +94 -0
- package/src/vendor/provider-kit/src/prompt.js +50 -0
- package/src/vendor/provider-kit/src/released.js +226 -0
- package/src/vendor/provider-kit/src/specs.js +166 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +92 -0
- package/src/vendor/sync-core/src/base.js +81 -0
- package/src/vendor/sync-core/src/compare.js +306 -0
- package/src/vendor/sync-core/src/dialect.js +51 -0
- package/src/vendor/sync-core/src/fields.js +36 -0
- package/src/vendor/sync-core/src/legacy.js +100 -0
- package/src/vendor/sync-core/src/normalize.js +989 -0
- package/src/vendor/sync-core/src/push.js +85 -0
- package/src/vendor/sync-core/src/retarget.js +278 -0
- package/src/vendor/sync-core/src/task-block.js +308 -0
- package/src/vendor/sync-core/src/verify.js +67 -0
- package/src/vendor/sync-core/src/write.js +191 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
# Spec Planning
|
|
2
|
+
|
|
3
|
+
Spec-driven development is driven by ten lifecycle skills (plus the
|
|
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
|
+
and lifecycle stay consistent. Each sets a status on the spec header
|
|
6
|
+
(`> **Status:** …`):
|
|
7
|
+
|
|
8
|
+
| Skill | Purpose | Status | Folder |
|
|
9
|
+
|-------|---------|--------|--------|
|
|
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
|
+
| `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
|
|
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) |
|
|
14
|
+
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
15
|
+
| `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
|
|
16
|
+
| `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
|
|
17
|
+
| `/spec-reviewed` | Pick up a review approved on the page — offered by its code, claimed on your word | (unchanged) | (unchanged) |
|
|
18
|
+
| `/spec-to-main` | Land the branch on the base (rebase + ff) **without** finishing — for running the work in CI / a shared env mid-spec; repeatable | `In Progress` (unchanged) | (unchanged) |
|
|
19
|
+
| `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
|
|
20
|
+
| `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
|
|
21
|
+
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
22
|
+
|
|
23
|
+
**Skills vs commands.** The table above lists **skills** — Claude reads them and
|
|
24
|
+
exercises judgment. `/spec-connect`, `/spec-live` and `/spec-remote-review` are
|
|
25
|
+
instead **slash commands**
|
|
26
|
+
(`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
|
|
27
|
+
relays its output, so there is no judgment to apply and no model turn spent
|
|
28
|
+
finding one. They are marked `disable-model-invocation`, meaning
|
|
29
|
+
**only you can run them** — a skill that wants one will tell you to type it
|
|
30
|
+
rather than invoking it.
|
|
31
|
+
|
|
32
|
+
**`/spec-remote-review`** is the newest and the smallest: it toggles whether
|
|
33
|
+
**this spec** may publish, and takes `on`/`off` when you would rather say which.
|
|
34
|
+
The permission is per worktree, kept in the gitignored review store, so the base
|
|
35
|
+
branch is never dirtied and no other spec gains it; `review.allowRemote` in the
|
|
36
|
+
committed config is the project default beneath it. Turned on beside a page
|
|
37
|
+
that already exists, it is the ask to publish that page and re-show the stack.
|
|
38
|
+
It exists because a render's `remote:` line has to name something a person can
|
|
39
|
+
act on, and `skitterspec spec-env review allow remote` is not what anyone
|
|
40
|
+
reconstructs from a page they are reading on a phone. Making the line
|
|
41
|
+
*clickable* was considered and rejected — the only clickable thing markdown has
|
|
42
|
+
is a URL, and a URL that acts when it is **fetched** is one a link previewer or
|
|
43
|
+
a prefetcher fires with nobody involved. It **permits** publishing; it publishes
|
|
44
|
+
nothing.
|
|
45
|
+
|
|
46
|
+
`/spec-reviewed` is a **skill** and **user-only**, and there the marking is not
|
|
47
|
+
convenience — it is the enforcement of `/spec-diff` step 0's rule that a waiting
|
|
48
|
+
review pass is never claimed unasked. Because the model cannot invoke this
|
|
49
|
+
skill, a pass named this way is only ever picked up because a person typed the
|
|
50
|
+
command, and typing it **is** the human signal. Prose alone did not hold that
|
|
51
|
+
line once already.
|
|
52
|
+
|
|
53
|
+
It is **no longer the only way in**, and the difference is worth stating
|
|
54
|
+
precisely. A phase that ends now *waits* on its page, and a pass arriving inside
|
|
55
|
+
that wait is claimed by the engine (`--claim-since`) without anyone typing
|
|
56
|
+
anything — scoped to the window, refusing when two arrive. `/spec-reviewed` is
|
|
57
|
+
what answers everything outside it: a pass sent when nobody was waiting, two
|
|
58
|
+
passes to choose between, and every harness with no file-watch to wait with.
|
|
59
|
+
|
|
60
|
+
**`/no-spec` is the one row that writes no spec**, and it is in the table
|
|
61
|
+
because it is the same lane: a branch, a worktree, a review page, a verdict, a
|
|
62
|
+
land. What it leaves out is the document, because mechanical work — a version
|
|
63
|
+
bump, a lockfile refresh, a rename — genuinely has none, and a one-phase spec
|
|
64
|
+
written for it is a document nobody reads. What it does **not** leave out is the
|
|
65
|
+
branch. Doing that work in the primary checkout leaves the base branch dirty for
|
|
66
|
+
every in-flight spec to replay over, and renders no page, so it is the one kind
|
|
67
|
+
of change that reaches the base branch unreviewed. It is model-invocable, which
|
|
68
|
+
matters: it is where Claude is sent when it would otherwise write on the base
|
|
69
|
+
branch. Beneath it, `skitterspec spec-env nospec <name>` records the branch —
|
|
70
|
+
and that record is what makes a name with nothing under `specs/**` resolvable by
|
|
71
|
+
`review`, `integrate`, `down` and a bare `resolve`.
|
|
72
|
+
|
|
73
|
+
**`/allow-main` is the fourth command.** It is user-only for a different reason
|
|
74
|
+
from the other three: `/spec-connect`, `/spec-live` and `/spec-remote-review`
|
|
75
|
+
are marked that way because there is no judgment to apply. This one is marked that
|
|
76
|
+
way because **Claude must not be able to lift a guard aimed at Claude.**
|
|
77
|
+
|
|
78
|
+
The guard is `.claude/hooks/main-guard.cjs`, a `PreToolUse` hook on the write
|
|
79
|
+
tools, and it refuses an `Edit` or a `Write` in the **primary checkout** while
|
|
80
|
+
it is on the **base branch**. `main` is where work lands, not where it happens:
|
|
81
|
+
work done there leaves the base branch dirty for every in-flight spec to replay
|
|
82
|
+
over, and renders no review page at all, so it is the one kind of change that
|
|
83
|
+
arrives unreviewed. It refuses the FIRST write rather than the commit, because
|
|
84
|
+
nothing has been written yet and the fix is one command — where a commit-time
|
|
85
|
+
gate fires once the work already exists in a checkout that cannot `switch -c`
|
|
86
|
+
out from under the other worktrees.
|
|
87
|
+
|
|
88
|
+
It fires only on a **positive signal** — isolation configured, the guard not
|
|
89
|
+
switched off, this directory IS the primary checkout, HEAD IS the base branch —
|
|
90
|
+
and every cannot-tell allows the write in silence: no repo, no config, a
|
|
91
|
+
worktree, an unresolvable base, a missing or crashed engine
|
|
92
|
+
(`.claude/rules/negative-checks.md`). `skitterspec spec-env main
|
|
93
|
+
<check|allow|status>` is the engine, and `guards.mainIsLandingZone` in
|
|
94
|
+
`env.config.json` turns it off for a project. It is
|
|
95
|
+
**on by default wherever isolation is configured**, matching the review gate.
|
|
96
|
+
|
|
97
|
+
There is **no allowlist**, and it is empty by construction rather than by
|
|
98
|
+
omission: `/spec` authors into its own worktree and `/no-spec` catches ad-hoc
|
|
99
|
+
work, so no legitimate writer to the base branch is left. `/spec-init` looks
|
|
100
|
+
like the exception and is not one — it bootstraps a repo with no
|
|
101
|
+
`env.config.json` yet, so the guard never fires there.
|
|
102
|
+
|
|
103
|
+
**Two exits, and they are not equal.** `/no-spec <name>` and `/spec-start
|
|
104
|
+
<name>` move the work, and Claude takes those on its own; `/allow-main` lifts
|
|
105
|
+
the guard, and only a person can type it. That asymmetry is the same line
|
|
106
|
+
`/spec-reviewed` draws, and `spec-planning.md` already records that prose alone
|
|
107
|
+
did not hold it once.
|
|
108
|
+
|
|
109
|
+
**`/spec-skip` is the fifth command**, and it is user-only for `/allow-main`'s
|
|
110
|
+
reason rather than the mild one: it lifts the review gate, which is a guard
|
|
111
|
+
aimed at Claude. It pre-executes `skitterspec spec-env review skip "<reason>"`
|
|
112
|
+
and relays it — the same exit that always existed, at an address a person will
|
|
113
|
+
actually type. The long form stays exactly as valid and is what the engine
|
|
114
|
+
prints; what changed is that a refusal now names something reachable, because a
|
|
115
|
+
gate whose exit nobody can find gets switched off wholesale instead of answered.
|
|
116
|
+
|
|
117
|
+
**It is not a `--force`, and one was asked for.** A reasonless lift reopens
|
|
118
|
+
precisely what the reason requirement closed: `none: additive, nothing to
|
|
119
|
+
revert` is a decision a reviewer can argue with, and silence is an oversight.
|
|
120
|
+
So a bare `/spec-skip` refuses and asks for a reason, and the two things that
|
|
121
|
+
discharge a phase's obligation are still a committing verdict and a recorded
|
|
122
|
+
skip — never a flag. Putting the flag on `/commit` was the other suggestion and
|
|
123
|
+
is worse: `/commit` belongs to skittership, which is why the gate is enforced
|
|
124
|
+
by a hook rather than by editing that skill.
|
|
125
|
+
|
|
126
|
+
**A refusal may OFFER its own exit, and that is a different thing again.**
|
|
127
|
+
Where the engine declares one, a skill relays the refusal and **then** offers it
|
|
128
|
+
— once per obligation, with the choice recorded, and answered by the operator
|
|
129
|
+
rather than by Claude. `.claude/rules/offered-unblocks.md` is the whole
|
|
130
|
+
contract. The three properties named there are what let a Claude-raised bypass
|
|
131
|
+
survive the rule above, where a reasonless `--force` did not: the operator
|
|
132
|
+
answers, the choice is on the record, and it is offered once. Remove any one and
|
|
133
|
+
the objection returns.
|
|
134
|
+
|
|
135
|
+
`/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
|
|
136
|
+
real judgment (green tests before a land; an MCP fetch and a team-key check; ten
|
|
137
|
+
subcommands) — but they are marked user-only too, since nobody reaches them
|
|
138
|
+
except by typing them. Everything else in the table above stays model-invocable,
|
|
139
|
+
which is what lets `/spec-next` hand off to `/spec-push` as work progresses.
|
|
140
|
+
|
|
141
|
+
Status flow: `Ready → In Progress → Complete` (or `Cancelled` from any state).
|
|
142
|
+
`/spec` grills to a **Ready** spec directly — there is no separate grooming
|
|
143
|
+
command; it writes `Draft` only when open questions are deliberately left.
|
|
144
|
+
`/spec-bug` and `/spec-hotfix` are test-first and start straight in `In Progress`
|
|
145
|
+
(work begins immediately), so they skip Draft/Ready. `/spec-to-main` does **not**
|
|
146
|
+
move the spec through the flow at all — it lands the branch on the base branch
|
|
147
|
+
mid-spec (so the work can run in CI / a shared test env) while the spec stays
|
|
148
|
+
`In Progress` in `specs/in-progress/`; it's the intermediate, repeatable half of
|
|
149
|
+
`/spec-complete`'s landing, without the finalise-and-tear-down.
|
|
150
|
+
|
|
151
|
+
**Two workspace modes.** `specs/.core/env.config.json` → `mode` decides where a
|
|
152
|
+
spec's branch is built. **`worktree`** (the default) gives each spec its own
|
|
153
|
+
checkout — several specs at once and `main` left free. `/spec-start` builds the
|
|
154
|
+
branch there, **moves your session into it** with a plain `cd`, and then asks:
|
|
155
|
+
build phase 1 now? Say yes and it carries on into a bare `/spec-next`; say no and
|
|
156
|
+
you are already standing in the worktree, so `/spec-next` typed later does the
|
|
157
|
+
same thing. The primary checkout stays on the base branch throughout — the
|
|
158
|
+
session followed the spec, nothing was checked out anywhere.
|
|
159
|
+
**`checkout`** builds the branch in the primary checkout instead: one spec at a
|
|
160
|
+
time, nowhere else to stand, and the build always carries straight on.
|
|
161
|
+
The difference is how many specs can be in flight at once.
|
|
162
|
+
Pick it for how you work rather than for what the project contains — a repo with
|
|
163
|
+
no dev servers may still want several specs in flight. In `checkout` mode
|
|
164
|
+
`/spec-connect` and `/spec-live` do not apply and say so: both exist to reach
|
|
165
|
+
work that lives elsewhere, which is the gap that mode removes.
|
|
166
|
+
|
|
167
|
+
**Per-spec isolation (opt-in to adopt, then the default policy).** When a project
|
|
168
|
+
adopts isolation (`skitterspec init --isolation`, or `specs/.core/env.config.json`
|
|
169
|
+
present), `/spec-start` gives **every** in-progress spec its own git worktree
|
|
170
|
+
automatically — several specs run side by side without stashing or clashing, and
|
|
171
|
+
`main` stays free. Docker is a **per-spec escalation**: `/spec` records
|
|
172
|
+
`> **Stack:** worktree` (default) or `worktree + docker` when the spec touches the
|
|
173
|
+
DB / stateful services, and `/spec-start` brings up a namespaced stack only for the
|
|
174
|
+
latter. `/spec-start` also starts the project's host **dev servers** (`env.config`
|
|
175
|
+
→ `dev`) on the spec's ports; **`/spec-connect <name>`** then exposes that spec on
|
|
176
|
+
your canonical `localhost` ports so you can test it at the normal URL
|
|
177
|
+
(`/spec-connect main` hands them back). All housekeeping (the backlog→in-progress
|
|
178
|
+
move, header edits, the code) happens on the spec's branch in the worktree; `main`
|
|
179
|
+
changes only when it merges. Teardown is folded into `/spec-complete` ·
|
|
180
|
+
`/spec-cancel`. Beneath the skills, `skitterspec spec-env
|
|
181
|
+
<up|bootstrap|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` is the CLI
|
|
182
|
+
engine. `up` plans the fork;
|
|
183
|
+
**`bootstrap` then runs the worktree's own steps itself** — seeding the gitignored files, the env file, `setup`, the stack — so
|
|
184
|
+
the command a skill runs names no file, and a secrets hook that refuses any
|
|
185
|
+
`.env` path has nothing to refuse. **Omit the spec name anywhere and it uses the worktree you are standing in**,
|
|
186
|
+
else the sole provisioned spec — with no exceptions left: a bare
|
|
187
|
+
`/spec-connect` connects, and a bare `/spec-live` takes, where both once meant
|
|
188
|
+
`main`. Teardown drops
|
|
189
|
+
the finished spec's own test-DB volume; `spec-env prune` additionally reaps
|
|
190
|
+
**orphaned** volumes left by declined/aborted teardowns, so `/spec-complete` and
|
|
191
|
+
`/spec-cancel` also sweep orphans (confirm-first). A **hotfix** is the one
|
|
192
|
+
exception to "fork from `main`": `/spec-hotfix` forks the worktree from a release
|
|
193
|
+
tag and `/spec-complete` lands it via `spec-env hotfix land` (release, push on
|
|
194
|
+
consent, carry to `main` without the version commit), not a fast-forward; its
|
|
195
|
+
worktree stays until the deploy is out and a second `/spec-complete` runs
|
|
196
|
+
`spec-env hotfix finish`. Isolation is **orthogonal to lifecycle status** and inactive
|
|
197
|
+
when `env.config.json` is absent — every skill then behaves as it does today.
|
|
198
|
+
|
|
199
|
+
**Live overlay (`/spec-live`, a command) — the light way to test a spec.** `/spec-connect`
|
|
200
|
+
runs a spec's *own* dev stack and proxies the canonical ports to it (one stack per
|
|
201
|
+
spec). **Live overlay** instead reuses the one dev server you already have running:
|
|
202
|
+
`/spec-live <spec>` — or a bare `/spec-live`, which takes the spec you are
|
|
203
|
+
standing on — rebases the branch onto base, frees it from its worktree, and
|
|
204
|
+
checks it out **in the primary checkout**, so your running server hot-reloads the
|
|
205
|
+
feature at the normal URL — no second stack, no proxy. Bare only acts when there
|
|
206
|
+
is exactly one answer *and* the workbench is free; anything else prints the
|
|
207
|
+
status report rather than guessing. The branch checked out in
|
|
208
|
+
the primary checkout **is** the lock: exactly one spec is live at a time, and
|
|
209
|
+
`/spec-live main` hands the instance back (fixes you make while live commit
|
|
210
|
+
straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
|
|
211
|
+
thumb: **live overlay is the light default for *testing* a code-only spec** — it
|
|
212
|
+
is not how work gets started, and no lifecycle skill invokes it; it *refuses*
|
|
213
|
+
stateful ones (`Stack: worktree + docker`, or a branch touching migrations) — keep
|
|
214
|
+
`/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
|
|
215
|
+
Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
|
|
216
|
+
|
|
217
|
+
**Authoring a spec is work, so it happens off the base branch too (`/spec`).**
|
|
218
|
+
`main` is where work **lands**, not where it happens, and a spec document is no
|
|
219
|
+
exception. With isolation configured, `/spec` grills first — which puts nothing
|
|
220
|
+
on disk — then provisions the new spec's own worktree with
|
|
221
|
+
`spec-env up <name> --docs`, moves the session into it, and writes the spec
|
|
222
|
+
there. `--docs` skips the `setup` commands and Docker, so a tree to write
|
|
223
|
+
markdown in costs one `git worktree add`; `/spec-start` re-runs `up` without the
|
|
224
|
+
flag and the install happens then, at the first moment the tree holds code.
|
|
225
|
+
|
|
226
|
+
**The committing verdict is what puts it on the base branch.** Phase C2's page
|
|
227
|
+
offers `Commit & Start` and `Commit`, and **both** commit on the branch and then
|
|
228
|
+
`spec-env integrate` — rebase, fast-forward — so the base gains the spec as one
|
|
229
|
+
commit. That is not optional tidiness: `ls specs/backlog/` is how specs are
|
|
230
|
+
found, and a folder bucket is only the truth on the branch you are standing on,
|
|
231
|
+
so a spec left unlanded is invisible to everyone. They differ only in the
|
|
232
|
+
worktree: `Commit & Start` keeps it and carries into `/spec-start`, plain
|
|
233
|
+
`commit` tears it down, because a spec parked in the backlog should hold no
|
|
234
|
+
checkout.
|
|
235
|
+
|
|
236
|
+
**What this bought is the reason it changed.** The page waits for a verdict
|
|
237
|
+
without a timeout — deliberately, because a reader walking away from a diff is
|
|
238
|
+
the normal case — and while the spec was authored in the primary checkout that
|
|
239
|
+
wait held the base branch dirty for as long as it lasted, so a release could not
|
|
240
|
+
be cut through it. Now an unanswered spec sits on its own branch in its own
|
|
241
|
+
worktree, in nothing's way. Phase C2 still **arms nothing**: the gate asserts
|
|
242
|
+
that a phase which ended owes an answer, and a backlog spec owes no phase — and
|
|
243
|
+
walking away from it now costs nobody anything rather than leaving a mess
|
|
244
|
+
someone else has to work around.
|
|
245
|
+
|
|
246
|
+
With isolation **absent** none of this applies: there is no worktree to make and
|
|
247
|
+
no land to do, so `/spec` writes the folder where the session is standing,
|
|
248
|
+
exactly as it always did.
|
|
249
|
+
|
|
250
|
+
**Staging one spec and not another (`spec-env stage`).** A `git add specs/`
|
|
251
|
+
stages a *directory*, sweeping whatever else happens to be sitting in it into
|
|
252
|
+
this spec's commit, under this spec's ticket trailer.
|
|
253
|
+
`skitterspec spec-env stage [<spec>] [--json]` is how a skill asks instead of
|
|
254
|
+
guessing: it splits the uncommitted tree into the paths that are this spec's —
|
|
255
|
+
its folder in **any** bucket, so a tree mid-`git mv` is handled, plus each
|
|
256
|
+
`spec.companionPaths` entry the project declares — and the paths that are not.
|
|
257
|
+
It reads **the tree you are standing in**, which is the worktree inside one and
|
|
258
|
+
the primary checkout outside, and prints which. The `owned` half is the spec's
|
|
259
|
+
**documents**, never its code: a phase's own implementation is `foreign`, because
|
|
260
|
+
the commits this bounds are the lifecycle ones. Nothing here refuses, writes, or
|
|
261
|
+
exits non-zero — `foreign` is a list of paths to leave alone, not an accusation.
|
|
262
|
+
|
|
263
|
+
**And it stays load-bearing now that `/spec` authors in its own worktree**, even
|
|
264
|
+
though that tree holds one spec. It was once justified by several specs sharing
|
|
265
|
+
one `specs/` folder, and that justification is gone; what replaces it is smaller
|
|
266
|
+
and just as real. A project's `spec.companionPaths`, a hand-edited
|
|
267
|
+
`specs/.core/`, and a tracker snapshot all land in the same tree as the spec, and
|
|
268
|
+
only the spec's own documents belong under its authoring verdict. Where isolation
|
|
269
|
+
is absent the original reason applies unchanged.
|
|
270
|
+
|
|
271
|
+
**Naming the paths is necessary and not sufficient**, which is why every spec
|
|
272
|
+
commit is also pathspec-limited: `git add -- <paths>` **and**
|
|
273
|
+
`git commit -m "…" -- <paths>`. A checkout has one `.git/index` and every session
|
|
274
|
+
standing in it shares that index, so a bare `git commit` takes whatever another
|
|
275
|
+
session has already staged no matter how carefully this one staged its own. The
|
|
276
|
+
`add` is still needed — `git commit` cannot take a path git has never seen, which
|
|
277
|
+
is every brand-new spec folder — but it is the `--` on the **commit** that bounds
|
|
278
|
+
what lands. The two failures are different and the fix needs both halves.
|
|
279
|
+
|
|
280
|
+
**Reading a spec's diff (`/spec-diff`) — seeing the work, not running it.**
|
|
281
|
+
`/spec-connect` and `/spec-live` both exist to reach a *running app*.
|
|
282
|
+
**`/spec-diff`** answers the other question: what did this actually change? A
|
|
283
|
+
phase is built in its own worktree, so `git diff` in your terminal answers about
|
|
284
|
+
the base branch — and a 350-line diff read as terminal text is scrolling, not
|
|
285
|
+
review. The engine collects the worktree's changes with `git -C` and writes a
|
|
286
|
+
self-contained HTML page (`.spec-env/reviews/<spec>.html`, gitignored) with
|
|
287
|
+
whole-file context that folds away, a file tree, and new files included. You open
|
|
288
|
+
it locally, or publish it and read it on a phone.
|
|
289
|
+
**The diff never passes through the model**, so the page is free to produce
|
|
290
|
+
however large it is; the *written*
|
|
291
|
+
review — a short read plus `flag`/`confirm`/`good` notes — is the part that costs
|
|
292
|
+
tokens, and it is offered rather than assumed. It is a **skill**, model-invocable,
|
|
293
|
+
and it is gated on nothing: half a phase, a hand edit and a colleague's branch are
|
|
294
|
+
all ordinary inputs. `/spec-next` renders the page at the end of a phase and
|
|
295
|
+
offers the review; `skitterspec spec-env review <spec> [--branch]` is the engine.
|
|
296
|
+
|
|
297
|
+
**The page is not read-only — it takes a review pass back.** Tick `✓ accept` per
|
|
298
|
+
file as you read, write notes against a line or a whole file, answer the
|
|
299
|
+
questions a written review asked, then **end it in a decision** and hand it
|
|
300
|
+
back. Three buttons, each carrying its own verdict:
|
|
301
|
+
**`✓ Approve`** (commit it), **`↺ Request changes`** (work them now),
|
|
302
|
+
**`… Discuss first`** (report and talk). `/spec-diff` stores it (`--notes`),
|
|
303
|
+
plays back what it read, and routes on the verdict — `changes` **is** the
|
|
304
|
+
go-ahead, so it works the commented files immediately, while `discuss`, a
|
|
305
|
+
refused approval and a pass carrying no verdict at all report and wait, which
|
|
306
|
+
is what a bare paste has always done. Accepted files stay unopened either way.
|
|
307
|
+
|
|
308
|
+
**Two ways back, and both are ordinary.** A **served** page POSTs the pass to
|
|
309
|
+
its own URL; the engine holds it in a gitignored `.pending.json` beside the
|
|
310
|
+
notes sidecar and answers with a **six-digit code**. Read the code out and
|
|
311
|
+
`/spec-diff` claims it (`--claim`), merging exactly the pass that code names.
|
|
312
|
+
A `file://` page has no server to talk to — the File System Access API is
|
|
313
|
+
Chrome-only and absent on iOS Safari — so it copies to the clipboard and you
|
|
314
|
+
paste, exactly as before. The clipboard path is **not legacy**: it is the whole
|
|
315
|
+
story for a local reader.
|
|
316
|
+
|
|
317
|
+
The code is **not a secret** — it is printed on the page, and it cannot be a
|
|
318
|
+
gate against Claude either, because the store is a file Claude can read.
|
|
319
|
+
|
|
320
|
+
It was once true that a device reaching your page
|
|
321
|
+
**could not reach your conversation**, and that fact was the whole guard: a pass sat in the holding
|
|
322
|
+
area until you typed `/spec-reviewed`. It is no longer true, deliberately —
|
|
323
|
+
a phase that ends **waits** for its verdict, so the button you press on the page
|
|
324
|
+
is what carries the work on. What replaced the guard is two mechanisms and one
|
|
325
|
+
rule. The **serve token** — 48 bits of randomness in the URL path, minted per
|
|
326
|
+
server — decides who can POST at all. The **wait window** decides which pass may
|
|
327
|
+
be claimed without you naming it: the engine's `--claim-since` takes the one
|
|
328
|
+
pass that arrived *while this session was waiting*, acts on nothing when none
|
|
329
|
+
did, and refuses to choose when two did. And outside that window the rule stands
|
|
330
|
+
unchanged and absolute: **Claude never claims a pass it was not asked to**
|
|
331
|
+
(`/spec-diff` step 0, because nothing enforces it).
|
|
332
|
+
|
|
333
|
+
What you give up is real and worth naming: the page can now act. What you get is
|
|
334
|
+
the loop closing without anyone remembering a command — which is the failure the
|
|
335
|
+
old design traded it for.
|
|
336
|
+
|
|
337
|
+
What the code *does* is let you **tell two passes apart** — it is an address,
|
|
338
|
+
never a password. One waiting pass is claimed and acted on the moment you type
|
|
339
|
+
`/spec-reviewed`, with nothing read back: the round-trip verified *which* pass
|
|
340
|
+
in a design where the page pushed and the agent went looking, and that design is
|
|
341
|
+
gone. Two waiting is a refusal to guess — the one case where a stranger's pass
|
|
342
|
+
sits beside yours, and the one case where six digits are worth anyone's time.
|
|
343
|
+
Pasting the code (`/spec-reviewed 324199`) skips even that. A pass you disown is `--drop <code>`; left there it is reported on every
|
|
344
|
+
render until you stop reading the line.
|
|
345
|
+
|
|
346
|
+
A code that matches nothing refuses, **names nothing**, and never falls back to
|
|
347
|
+
"the only one" — that fallback is precisely what would let an unread pass
|
|
348
|
+
through. Claiming **consumes**: a code works once. A second pass from the same
|
|
349
|
+
page render supersedes the first, so the code on screen is always the pass on
|
|
350
|
+
screen; passes from different renders stand alongside each other.
|
|
351
|
+
|
|
352
|
+
**And a claimed pass never enters the model's context.** The engine holds it,
|
|
353
|
+
merges it, and reports the counts — six digits is what reaches Claude, where a
|
|
354
|
+
pasted blob costs context in proportion to how much you wrote. That is the same
|
|
355
|
+
rule the diff already follows, applied to the one place it used to break. What it did comes back as a **resolution** (`--resolve`),
|
|
356
|
+
so the next render shows each note struck through with a one-line account and you
|
|
357
|
+
verify the fix instead of trusting it. An accept is keyed to the file's
|
|
358
|
+
**content hash**, so it survives the commit that ends the phase and lapses by
|
|
359
|
+
itself when that file changes again — announced as `accepted earlier — changed since`, never
|
|
360
|
+
silently. All of it lives beside the page in gitignored `.spec-env/`, and
|
|
361
|
+
**the marks are information, never a gate**: nothing counts them and nothing
|
|
362
|
+
refuses on them.
|
|
363
|
+
|
|
364
|
+
**A second opinion, from something that did not write the code (`review.reviewers`).**
|
|
365
|
+
The page can also carry findings from code reviewers
|
|
366
|
+
that run from a CLI — CodeRabbit's has a free tier, PR-Agent and Kodus are
|
|
367
|
+
self-hosted — and they arrive as **checks**, the same shape a written review
|
|
368
|
+
uses. `/spec-next` runs them once per phase before the render, so the page is
|
|
369
|
+
populated when it is opened; `/spec-diff --reviewers` is the opt-in mid-phase
|
|
370
|
+
run, and a bare `/spec-diff` never spends one. Results are cached against a hash
|
|
371
|
+
of the diff, so re-reading unchanged work costs nothing.
|
|
372
|
+
|
|
373
|
+
**They inform; they never gate.** A check is not a comment, so a page carrying
|
|
374
|
+
twenty findings still offers `Commit` — and a reader who replies to one creates
|
|
375
|
+
a comment, which does gate, because then a person has asked for something. There
|
|
376
|
+
is deliberately no severity threshold that blocks: that is the counting gate
|
|
377
|
+
this whole design exists against, wearing a vendor's name.
|
|
378
|
+
|
|
379
|
+
**And a reviewer that could not run says so**, which is the one place
|
|
380
|
+
`.claude/rules/negative-checks.md` inverts. Silence is normally the safe branch;
|
|
381
|
+
here a rate-limited or unauthenticated reviewer would render identically to one
|
|
382
|
+
that read the diff and approved of it, on the page a commit decision is made
|
|
383
|
+
from. So each gets a line — `12 findings` · `clean` · `did not run — <why>` —
|
|
384
|
+
and none of it ever refuses a render, a verdict or a commit.
|
|
385
|
+
|
|
386
|
+
The list is **empty by default** and `init` never writes one: configuring a
|
|
387
|
+
hosted reviewer sends the worktree's diff to a third party, which is a real
|
|
388
|
+
change for a tool that otherwise touches nothing outside `.spec-env/`. Every
|
|
389
|
+
finding on the page is badged with the reviewer that produced it. `env.config.md`
|
|
390
|
+
carries the field reference.
|
|
391
|
+
|
|
392
|
+
**The verdict is chosen, never derived.** A verdict is one person's conclusion,
|
|
393
|
+
sent once per pass; a counting gate is a refusal computed from how many boxes
|
|
394
|
+
are ticked, and it stays forbidden. The one refusal here is not a count of
|
|
395
|
+
ticks: **committing** is unavailable while any **comment** is unresolved,
|
|
396
|
+
because you asked for something and it therefore cannot also be fine. Files you
|
|
397
|
+
never ticked block nothing — an unticked file is something you said nothing
|
|
398
|
+
about, and requiring every one of them would be the tally this design exists to
|
|
399
|
+
avoid.
|
|
400
|
+
|
|
401
|
+
**The gate — a phase that ended owes an answer.** That is the second refusal,
|
|
402
|
+
and it is a different kind: not a count, and not about the marks at all.
|
|
403
|
+
`/spec-next` **arms** it when a phase ends and renders its page, and exactly two
|
|
404
|
+
things clear it — a **committing verdict**, or
|
|
405
|
+
`skitterspec spec-env review skip "<reason>"`. Until one of them happens,
|
|
406
|
+
`/spec-next` refuses to build the next phase and (where the hook is installed)
|
|
407
|
+
`git commit` refuses in that worktree. `skitterspec spec-env review gate
|
|
408
|
+
[--check] [--json]` is what both ask.
|
|
409
|
+
|
|
410
|
+
**The hook is the half that cannot be talked past.** `skitterspec init` and
|
|
411
|
+
`skitterspec update` install `.claude/hooks/review-gate.cjs` and register it in
|
|
412
|
+
the project's committed `.claude/settings.json`, so a `git commit` is refused by
|
|
413
|
+
the harness rather than by prose — which is what covers a bare `git commit`, a chained command, and
|
|
414
|
+
skittership's own `/commit` without skitterspec ever editing it. It decides
|
|
415
|
+
nothing itself: it hands the command line to
|
|
416
|
+
`spec-env review gate --check --for-command` and turns one exit status into an
|
|
417
|
+
answer. It refuses only where the commit is running
|
|
418
|
+
**inside that spec's own worktree** — a commit on the base branch, or in
|
|
419
|
+
another spec's tree, is not this obligation's business — and it fails **open** on everything else: no engine, an
|
|
420
|
+
unreadable payload, a crash, a timeout, a repo with no isolation.
|
|
421
|
+
|
|
422
|
+
Three things keep it a push rather than a wall. It is armed
|
|
423
|
+
**only by a phase ending**, so reading your own half-finished work mid-phase
|
|
424
|
+
owes nothing.
|
|
425
|
+
It has an **exit that is always one command**, and one of them is
|
|
426
|
+
*"I am moving on"* — with a reason, because `none: additive, nothing to revert` is a decision
|
|
427
|
+
a reviewer can argue with while silence is an oversight. And it accuses
|
|
428
|
+
**only on a positive signal**: an unreadable sidecar, a project that set
|
|
429
|
+
`review.required: false`, a spec the engine could not resolve — every
|
|
430
|
+
cannot-tell exits 0 and says nothing (`.claude/rules/negative-checks.md`).
|
|
431
|
+
|
|
432
|
+
It is on by default wherever isolation is configured. That is the point: the
|
|
433
|
+
push toward reading the diff is the normal path, and stepping off it is the
|
|
434
|
+
thing you have to do deliberately.
|
|
435
|
+
|
|
436
|
+
**The verdict names the action**, and that is why it is `commit` rather than
|
|
437
|
+
`approve`: a review is the guard in front of an action, and an approval that
|
|
438
|
+
only recorded itself was the one control on the page that did not describe what
|
|
439
|
+
it does. Both committing verdicts hand off to the project's own commit skill
|
|
440
|
+
(`review.commitWith` in `env.config.json`, `/commit` by default) — skitterspec
|
|
441
|
+
never vendors one, because `/commit` belongs to skittership.
|
|
442
|
+
**There is no off switch**: `"none"` existed and was removed, since it produced
|
|
443
|
+
exactly the record-and-do-nothing verdict this design is against.
|
|
444
|
+
`Commit & Continue` then runs `/spec-next` and **stops there** — it never
|
|
445
|
+
completes, lands or tears anything down.
|
|
446
|
+
|
|
447
|
+
A verdict is **consumed**, not stored: a commit verdict is spent by the commit
|
|
448
|
+
and a changes one by the work, so neither can go stale and later act on
|
|
449
|
+
something nobody read. What survives is a one-line outcome log, shown on the
|
|
450
|
+
next render as history.
|
|
451
|
+
|
|
452
|
+
**One ending, every skill (`.claude/rules/spec-reports.md`).** Every skill in
|
|
453
|
+
the table above finishes with the same block — a verdict, then a table of only
|
|
454
|
+
the fields that skill declares — and says nothing while it runs beyond a
|
|
455
|
+
question it cannot answer itself or a failure at the moment it happens. The
|
|
456
|
+
shape is defined once in that rule; each skill's `## Report` section names only
|
|
457
|
+
its verdicts and its fields.
|
|
458
|
+
|
|
459
|
+
✅ **Phase 2 built** — `feat-orders`, 2 of 4
|
|
460
|
+
|
|
461
|
+
| | |
|
|
462
|
+
|---|---|
|
|
463
|
+
| **Tracker** | [ABC-88](https://example.invalid/ABC-88) · `feat-orders` · phase 2 moved |
|
|
464
|
+
| **Branch** | `spec/feat-orders` · 3 commits, clean |
|
|
465
|
+
| **Built** | POST /orders handler, orders schema |
|
|
466
|
+
| **Tests** | 128 passed · dotnet test |
|
|
467
|
+
| **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 |
|
|
468
|
+
| **Follow-ups** | none |
|
|
469
|
+
| **Next** | `/spec-next` → phase 3 (Auth) |
|
|
470
|
+
|
|
471
|
+
Four verdicts, and the last two are different facts about the repo: `✅` done ·
|
|
472
|
+
`⚠️` done with caveats · `❌` failed part-way, so there is a mess to clear ·
|
|
473
|
+
`⏸` refused before acting, so nothing changed.
|
|
474
|
+
**A refusal emits the block too** — "nothing happened" is a reported outcome
|
|
475
|
+
rather than an absent one.
|
|
476
|
+
`Follow-ups` is always present, because a recorded `none` is a decision where a
|
|
477
|
+
missing line is an oversight. And the block covers **that run only**: what else
|
|
478
|
+
is in flight is another question, and volunteering it here leaves the reader
|
|
479
|
+
unable to tell what followed from the run they just watched.
|
|
480
|
+
|
|
481
|
+
**Ticketing-provider sync (opt-in, a separate package).** The base is
|
|
482
|
+
tracker-free: it knows nothing about any specific ticketing system. A
|
|
483
|
+
ticketing provider is installed as its own distribution that plugs into named
|
|
484
|
+
**seams** in the shared skills and fulfils a skill-name + CLI contract.
|
|
485
|
+
|
|
486
|
+
Part of that contract is the **binary name**: the shipped `/spec-connect` and
|
|
487
|
+
`/spec-live` commands invoke `skitterspec`, so a superset that replaces the base
|
|
488
|
+
must expose that name too, alongside whatever it calls itself — one entry point
|
|
489
|
+
under two names. A distribution shipping only its own name leaves those commands
|
|
490
|
+
failing with `command not found`, which reads as a broken install rather than a
|
|
491
|
+
missing alias.
|
|
492
|
+
Sync is **one-way**: the repo is the source of truth and the tracker is a
|
|
493
|
+
**generated mirror**. It ships `/spec-push` (repo→tracker; computes a create/update plan
|
|
494
|
+
against a committed last-pushed snapshot and applies it) and `/spec-status`
|
|
495
|
+
(read-only drift report — what would push, and whether the tracker's
|
|
496
|
+
workflow-state drifted), backed by a `spec-sync` CLI. There is no content pull —
|
|
497
|
+
the tracker is never read back or merged.
|
|
498
|
+
|
|
499
|
+
**One-way means the repo wins, and it now wins *loudly*.** Those are different
|
|
500
|
+
claims and only the first was ever in doubt. A ticket's description is generated
|
|
501
|
+
from the spec, so a person editing it there is writing something the next push
|
|
502
|
+
replaces — which is correct, and which used to happen in silence. The engine now
|
|
503
|
+
compares a hash of what the tracker holds against a hash of what the repo last
|
|
504
|
+
sent, and says *that* it changed and where to read it.
|
|
505
|
+
It **reports and never refuses**: only a person can tell a typo fix from a
|
|
506
|
+
paragraph worth keeping.
|
|
507
|
+
|
|
508
|
+
**This is still not a pull.** Nothing read is merged, written to a spec, or fed
|
|
509
|
+
into the projection, and the text never enters the conversation — the comparison
|
|
510
|
+
is hashes, and the output is one line for a human. It is the same thing
|
|
511
|
+
`compareStored` has always done for a different question.
|
|
512
|
+
|
|
513
|
+
**And adoption no longer costs the reporter their words.** When a provider
|
|
514
|
+
adopts an existing ticket — one a PM or colleague filed — the linking push
|
|
515
|
+
replaces its description with the spec. Before that happens the original is
|
|
516
|
+
posted back onto the ticket as a **comment**: a surface one-way sync never
|
|
517
|
+
touches, so no later push can clobber it. Where a provider supports it, this is
|
|
518
|
+
automatic and has an opt-out. See the provider's own docs.
|
|
519
|
+
|
|
520
|
+
A provider may also ship a **read-only listing** of what the tracker holds. That
|
|
521
|
+
is not a pull: nothing is merged back and no spec file is written. It exists
|
|
522
|
+
because the folder buckets are only the truth *on the branch you are standing
|
|
523
|
+
on* — `/spec-start` moves a spec to
|
|
524
|
+
`specs/in-progress/` on that spec's own branch, so on the base branch an
|
|
525
|
+
in-flight spec still reads `backlog` and a teammate's unlanded spec is not on
|
|
526
|
+
disk at all. The tracker knows both, and each row carries the spec's folder name
|
|
527
|
+
so `/spec-start <name>` is a copy-paste away. See the provider's own docs for
|
|
528
|
+
what it calls the command.
|
|
529
|
+
|
|
530
|
+
A provider may also mirror **who is building a spec**, as one more field the repo
|
|
531
|
+
owns. Where it does, `/spec-start` records the developer and the tracker's ticket
|
|
532
|
+
is assigned to them, the assignment is released when the spec completes, and
|
|
533
|
+
`/spec-claim` moves ownership mid-flight — take it, hand it back, or hand it to a
|
|
534
|
+
teammate. Like everything else here it is opt-in and one-way: the spec file is
|
|
535
|
+
the record, and the ticket is the mirror. See the provider's own docs for how to
|
|
536
|
+
turn it on.
|
|
537
|
+
|
|
538
|
+
**Every skill that moves a spec through the lifecycle carries a seam**, so the
|
|
539
|
+
mirror keeps up without anyone remembering to push: `/spec`, `/spec-bug` and
|
|
540
|
+
`/spec-hotfix` link the spec they create; `/spec-next` refreshes it as work starts;
|
|
541
|
+
`/spec-complete`, `/spec-cancel` and `/spec-review` refresh it after they change
|
|
542
|
+
it. `/spec-to-main` and `/spec-live` carry none — they change no status.
|
|
543
|
+
With no provider installed the seams are empty and every skill behaves as a plain
|
|
544
|
+
filesystem workflow. See the provider package's own docs for its config and field
|
|
545
|
+
reference.
|
|
546
|
+
|
|
547
|
+
**Release gating (opt-in, config-gated).** With
|
|
548
|
+
`specs/.core/gating.config.json` present, every spec `/spec`, `/spec-bug` and
|
|
549
|
+
`/spec-hotfix` write carries a `> **Gating:**` header recording one decision:
|
|
550
|
+
does this ship behind a feature flag, or land live? The value is a **flag name**,
|
|
551
|
+
or **`none: <one-line reason>`** — and the reason half is the load-bearing part,
|
|
552
|
+
because `none: additive, nothing to revert` is a decision a reviewer can argue
|
|
553
|
+
with while a bare `none` is a shrug and a missing line is an oversight.
|
|
554
|
+
|
|
555
|
+
Skitterspec bakes in **the offer, never the mechanism**: it asks, cites the
|
|
556
|
+
project's own doc (`guidance` in that config), and records the answer. It never
|
|
557
|
+
reads your flag code. `skitterspec gating check` reports specs with no decision
|
|
558
|
+
and **always exits 0** — it reads only `backlog/` and `in-progress/`, so specs
|
|
559
|
+
finished before you adopted gating are out of range by construction. With the
|
|
560
|
+
config absent nothing appears at all: no question, no header, no check.
|
|
561
|
+
|
|
562
|
+
## Project conventions (fill this in)
|
|
563
|
+
|
|
564
|
+
The spec skills tell you to run "your project's typecheck and test commands" and
|
|
565
|
+
to "honour project conventions". Make those concrete here so specs stay
|
|
566
|
+
consistent with the codebase:
|
|
567
|
+
|
|
568
|
+
Skitterspec never runs these itself and knows nothing about what this project
|
|
569
|
+
is written in — it shells out to `git`, and to whatever you write below. The
|
|
570
|
+
examples span several stacks on purpose: pick the shape that fits, or write one
|
|
571
|
+
none of them describes.
|
|
572
|
+
|
|
573
|
+
- **Typecheck command:** `<e.g. npm run typecheck · dotnet build · mypy . · go vet ./...>`
|
|
574
|
+
- **Test command:** `<e.g. npm test · dotnet test · pytest · go test ./...>`
|
|
575
|
+
(single file/dir: `<e.g. npx vitest run path · dotnet test --filter · pytest path>`)
|
|
576
|
+
- **Lint/format:** `<e.g. npm run lint · dotnet format · ruff check · golangci-lint run>`
|
|
577
|
+
- **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
|
|
578
|
+
(architecture, code style, testing, database, etc.) rather than restating them.
|
|
579
|
+
|
|
580
|
+
## Spec types — Feature, Bug, Hotfix
|
|
581
|
+
|
|
582
|
+
Every spec is one of three types, recorded **both** in the header and the filename:
|
|
583
|
+
|
|
584
|
+
- **Header field:** `> **Type:** Feature`, `> **Type:** Bug`, or
|
|
585
|
+
`> **Type:** Hotfix` (authoritative, greppable:
|
|
586
|
+
`grep -rl 'Type:.*Hotfix' specs/`).
|
|
587
|
+
- **Filename prefix:** `feat-<name>` for features, `bug-<name>` for bugs,
|
|
588
|
+
`hotfix-<name>` for hotfixes (visible in listings; glob-safe — never use
|
|
589
|
+
`[BUG]`/`[FEATURE]` brackets).
|
|
590
|
+
|
|
591
|
+
A **Hotfix** is a Bug fixed against a **released tag** rather than `main`: it
|
|
592
|
+
carries an extra `> **Base version:** <tag>` header, forks its worktree from that
|
|
593
|
+
tag, and lands by tagging a new patch + cherry-picking onto `main` (never a
|
|
594
|
+
fast-forward merge). `/spec-live` refuses a hotfix — test it with `/spec-connect`.
|
|
595
|
+
|
|
596
|
+
All three types share the same lifecycle folders below — type is orthogonal to
|
|
597
|
+
status.
|
|
598
|
+
|
|
599
|
+
## Header fields & State log (audit trail)
|
|
600
|
+
|
|
601
|
+
Every spec header carries:
|
|
602
|
+
|
|
603
|
+
- `> **Name:**` — the spec's folder name (`feat-`/`bug-`/`hotfix-<kebab-name>`).
|
|
604
|
+
It's the handle you pass to `/spec-start` and the other lifecycle skills, surfaced
|
|
605
|
+
in the header so it's copy-pasteable without digging for the folder name.
|
|
606
|
+
- `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
|
|
607
|
+
to `git config user.name`).
|
|
608
|
+
- `> **Developer:**` — who implements it (`—` until `/spec-start` starts work, then
|
|
609
|
+
set to `git config user.name`; `/spec-bug` sets it immediately).
|
|
610
|
+
- `> **Gating:**` — the release-gating decision,
|
|
611
|
+
**only when `specs/.core/gating.config.json` exists**: a flag name, or
|
|
612
|
+
`none: <one-line reason>`. Absent entirely in a project that has not adopted
|
|
613
|
+
gating.
|
|
614
|
+
|
|
615
|
+
Every spec also has a **State log** table — the audit trail of folder/status
|
|
616
|
+
transitions. Each lifecycle skill appends exactly one row when it changes state:
|
|
617
|
+
|
|
618
|
+
```
|
|
619
|
+
## State log
|
|
620
|
+
|
|
621
|
+
| Date | Status | Folder | By |
|
|
622
|
+
|------|--------|--------|----|
|
|
623
|
+
| 2026-01-01 | Draft | backlog | Jane Dev |
|
|
624
|
+
| 2026-01-02 | In Progress | in-progress | Jane Dev |
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Keep the **State log** (state transitions) separate from the **Changelog**
|
|
628
|
+
(decisions and course-corrections) — state moves go in the table, not the
|
|
629
|
+
changelog.
|
|
630
|
+
|
|
631
|
+
When asked for a plan, implementation strategy, or feature breakdown:
|
|
632
|
+
|
|
633
|
+
1. Create or update a spec under `specs/` — never plan only in chat.
|
|
634
|
+
2. Reach a clear shared understanding of the requirement AND the proposed
|
|
635
|
+
solution before writing (the `/spec` skill grills for this).
|
|
636
|
+
3. Use markdown checkboxes `- [ ]` for tasks, `- [x]` when done.
|
|
637
|
+
4. Organise work into phased sections with short goal descriptions.
|
|
638
|
+
5. Tasks must be granular enough to complete in one coding session.
|
|
639
|
+
6. Every phase ends with creating and running tests — a phase is not done until
|
|
640
|
+
its tests are green (run the project's typecheck + test commands above).
|
|
641
|
+
7. Keep specs **as concise as possible**.
|
|
642
|
+
8. Record decisions and course-corrections in the spec's **Changelog** section.
|
|
643
|
+
|
|
644
|
+
## Lifecycle folders
|
|
645
|
+
|
|
646
|
+
```
|
|
647
|
+
specs/backlog/ Ready (or Draft) specs (/spec)
|
|
648
|
+
specs/in-progress/ under active implementation (/spec-start, /spec-bug)
|
|
649
|
+
specs/complete/ finished (/spec-complete)
|
|
650
|
+
specs/cancelled/ abandoned, with a reason on the header (/spec-cancel)
|
|
651
|
+
specs/.core/ project rules — ALWAYS APPLY, never moved
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Every spec is a **folder** `specs/<bucket>/<name>/` — never a bare file, even for
|
|
655
|
+
simple changes. Inside it:
|
|
656
|
+
|
|
657
|
+
- `00-overview.md` is the entry point / dashboard: header, Problem, Decisions,
|
|
658
|
+
Solution overview, the **Impact map** (a `Surface | Change | Detail` table
|
|
659
|
+
naming the concrete surfaces the spec touches — endpoints, schemas, DB tables,
|
|
660
|
+
domain objects, routes, business rules — as the scannable blast radius), the
|
|
661
|
+
**phase index** (a table linking to each phase file with its status), Open
|
|
662
|
+
questions, State log, Changelog. **No per-phase task lists live here.**
|
|
663
|
+
- **One file per phase** — `01-<phase-slug>.md`, `02-<phase-slug>.md`, … in
|
|
664
|
+
execution order. Each holds that phase's goal, its task checkboxes (tests
|
|
665
|
+
included), and any phase-specific notes. Even a single-phase spec gets `01-….md`
|
|
666
|
+
— so each phase is easy to open and work on its own.
|
|
667
|
+
|
|
668
|
+
Keep the index and the phase files in sync (`⬜`/`🔄`/`✅`). Legacy specs may be a
|
|
669
|
+
bare `<name>.md`, or a `00-overview.md` with inline phases — the skills read
|
|
670
|
+
those, but new specs always use the folder + phase-file form.
|
|
671
|
+
|
|
672
|
+
## Finding specs
|
|
673
|
+
|
|
674
|
+
The **folder buckets are the source of truth** — a spec's bucket is its status.
|
|
675
|
+
To see the backlog, list `specs/backlog/`; for the latest completed specs, use
|
|
676
|
+
`git log`/mtime on `specs/complete/` or each spec's dated **State log**. Live
|
|
677
|
+
status also lives in the tracker when a ticketing provider is linked. (There are
|
|
678
|
+
no `00-index.md` summary files — the folder tree, headers, and State logs are
|
|
679
|
+
queried directly.)
|
|
680
|
+
|
|
681
|
+
## Rules
|
|
682
|
+
|
|
683
|
+
- If a spec already exists, update it — don't rewrite from scratch.
|
|
684
|
+
- Preserve completed `[x]` tasks.
|
|
685
|
+
- Add new tasks to the appropriate phase.
|
|
686
|
+
- Never delete historical notes.
|
|
687
|
+
- The spec file is the single source of truth for implementation progress.
|
|
688
|
+
- Move specs between buckets with `git mv` to preserve history.
|
|
689
|
+
- Never let inline emphasis or a link cross a hard line break — keep a whole
|
|
690
|
+
`**bold**`, `*italic*`, or `[text](url)` on one line (let it overflow the wrap
|
|
691
|
+
column rather than splitting it). Many round-tripping editors mangle a
|
|
692
|
+
`**`/`*`/link span that straddles a newline, so clean source avoids the churn.
|