@skitterbyte/skitterspec-linear 10.8.0 → 12.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 +88 -0
- package/README.md +3 -3
- package/assets/claude-md-section.md +20 -48
- package/assets/core/SETUP.md +1 -1
- package/assets/core/env.config.json.example +7 -1
- package/assets/core/env.config.md +56 -11
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/linear.config.md +12 -11
- package/assets/rules/spec-planning.md +53 -13
- package/assets/skills/spec/SKILL.md +46 -18
- package/assets/skills/spec-bug/SKILL.md +32 -27
- package/assets/skills/spec-cancel/SKILL.md +26 -0
- package/assets/skills/spec-complete/SKILL.md +70 -17
- package/assets/skills/spec-hotfix/SKILL.md +40 -19
- package/assets/skills/spec-init/SKILL.md +31 -8
- package/assets/skills/spec-linear-setup/SKILL.md +32 -7
- package/assets/skills/spec-next/SKILL.md +141 -0
- package/assets/skills/spec-push/SKILL.md +15 -16
- package/assets/skills/spec-review/SKILL.md +24 -11
- package/assets/skills/spec-start/SKILL.md +226 -0
- package/assets/skills/spec-status/SKILL.md +2 -2
- package/assets/skills/spec-sync/SKILL.md +8 -8
- package/assets/skills/spec-to-main/SKILL.md +21 -19
- package/package.json +1 -1
- package/src/cli.js +405 -15
- package/src/env/classify.js +91 -0
- package/src/env/config.js +39 -1
- package/src/env/integrate.js +61 -1
- package/src/env/live.js +27 -3
- package/src/env/provision.js +196 -5
- package/src/env/resolve.js +1 -0
- package/src/env/teardown.js +41 -3
- package/src/gating.js +155 -0
- package/src/init.js +45 -9
- package/src/prompts.js +41 -4
- package/src/vendor/linear/cli-sync.js +22 -2
- package/src/vendor/linear/config.js +1 -1
- package/src/vendor/sync-core/src/compare.js +25 -2
- package/assets/skills/spec-go/SKILL.md +0 -233
package/MIGRATION.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Migration guide
|
|
2
2
|
|
|
3
|
+
## `@skitterbyte/skitterspec` v17 → v18 (a spec is built in its own worktree)
|
|
4
|
+
|
|
5
|
+
### Breaking change
|
|
6
|
+
|
|
7
|
+
**`/spec-start` no longer moves the branch into your checkout.** In `worktree`
|
|
8
|
+
mode it provisions the spec's worktree, does the housekeeping there, opens a
|
|
9
|
+
session in it, and stops. The spec is built where it was provisioned — which is
|
|
10
|
+
what worktrees are for, and why `main` stays free.
|
|
11
|
+
|
|
12
|
+
This **supersedes the "one checkout holds one spec in flight" rule** described
|
|
13
|
+
under v16 → v17 below. That rule was a consequence of moving the branch into the
|
|
14
|
+
primary checkout; with the move gone, so is the restriction:
|
|
15
|
+
|
|
16
|
+
| v17 | v18 |
|
|
17
|
+
|-----|-----|
|
|
18
|
+
| `/spec-start` refused while another spec held your checkout | In `worktree` mode it only requires a **clean tree**. Several specs in flight is what the mode is for. |
|
|
19
|
+
| Starting a spec took two invocations, with a `/spec-live <name>` you typed in between | **One invocation.** No hand-off command, no re-run. |
|
|
20
|
+
| `/spec-live main` was one of the ways out of the gate | The ways out are `/spec-complete` and `/spec-cancel`. Parking to free a workbench is a one-workbench answer, and only `checkout` mode holds one spec now. |
|
|
21
|
+
|
|
22
|
+
**`/spec-live` is for testing only** — reusing your running dev server to reach a
|
|
23
|
+
spec at the canonical URL. It was never meant to be how work gets started, and no
|
|
24
|
+
lifecycle skill calls it.
|
|
25
|
+
|
|
26
|
+
`checkout` mode is unchanged: the branch is built in the primary checkout, the
|
|
27
|
+
gate still requires the workbench free, and `/spec-start` carries straight on
|
|
28
|
+
into phase 1 in the same session.
|
|
29
|
+
|
|
30
|
+
### What to do
|
|
31
|
+
|
|
32
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update`.
|
|
33
|
+
2. **Expect a session, not a swap.** After `/spec-start` in `worktree` mode, run
|
|
34
|
+
`/spec-next` from the session it opens in the worktree. `/spec-next` builds
|
|
35
|
+
the spec it is *standing in* and refuses to build one from elsewhere.
|
|
36
|
+
3. **Nothing to configure.** `spec.companionPaths` in `env.config.json` is new
|
|
37
|
+
and optional — it names paths that belong to a spec alongside its own folder
|
|
38
|
+
(a tracker's per-spec snapshot), so `/spec-start` can commit them together.
|
|
39
|
+
Empty by default; `/spec-linear-setup` sets it for you.
|
|
40
|
+
|
|
41
|
+
## `@skitterbyte/skitterspec-linear` v11 → v12 (a spec is built in its own worktree)
|
|
42
|
+
|
|
43
|
+
The same change as `@skitterbyte/skitterspec` v17 → v18 above — this
|
|
44
|
+
distribution composes the same lifecycle skills. Read that entry; nothing here
|
|
45
|
+
is Linear-specific.
|
|
46
|
+
|
|
47
|
+
## `@skitterbyte/skitterspec` v16 → v17 (`/spec-go` splits in two)
|
|
48
|
+
|
|
49
|
+
### Breaking change
|
|
50
|
+
|
|
51
|
+
**`/spec-go` is removed.** It did two jobs — set an environment up, and build a
|
|
52
|
+
phase — and the seam between them is where the worktree hand-off hurt: you ran
|
|
53
|
+
the same command twice, once to provision and once, from another session, to
|
|
54
|
+
build. They are now two commands with one job each:
|
|
55
|
+
|
|
56
|
+
| Removed | Use instead |
|
|
57
|
+
|---------|-------------|
|
|
58
|
+
| `/spec-go <name>` (first run — start a spec) | **`/spec-start <name>`** — puts the spec in flight on this checkout, moves it to `in-progress`, then builds phase 1. |
|
|
59
|
+
| `/spec-go` (later runs — build the next phase) | **`/spec-next`** — builds the next phase of whichever spec is in flight. Re-run it per phase. |
|
|
60
|
+
| `/spec-go --here` | **Nothing — `/spec-start` *is* here.** It puts the branch in the checkout you are in, which is what the flag was reaching for. |
|
|
61
|
+
|
|
62
|
+
**One checkout holds one spec in flight.** `/spec-start` refuses while another
|
|
63
|
+
spec holds your checkout, naming it and the three ways to free the workbench
|
|
64
|
+
(`/spec-complete`, `/spec-cancel`, or `/spec-live main` to park it). It will not
|
|
65
|
+
stash, commit or switch on your behalf — moving unfinished work is a decision,
|
|
66
|
+
not a side effect.
|
|
67
|
+
|
|
68
|
+
**`/spec-next` refuses when nothing is in flight** rather than guessing a spec
|
|
69
|
+
from the conversation. It writes real code; a wrong guess produces commits on a
|
|
70
|
+
branch nobody asked for.
|
|
71
|
+
|
|
72
|
+
### What to do
|
|
73
|
+
|
|
74
|
+
1. **Upgrade** — `npx @skitterbyte/skitterspec update` removes the retired
|
|
75
|
+
`/spec-go` skill and installs the two replacements. A `/spec-go` you edited
|
|
76
|
+
yourself is kept with a warning rather than deleted; remove it by hand.
|
|
77
|
+
2. **Retrain the muscle memory** — `/spec-start <name>` to begin, `/spec-next` to
|
|
78
|
+
carry on, unchanged `/spec-complete` to finish.
|
|
79
|
+
3. **Providers**: the `spec-go-start` seam is now **`spec-next-start`**. A
|
|
80
|
+
provider distribution must rename its fragment file to match, or the build
|
|
81
|
+
fails on an orphaned seam.
|
|
82
|
+
|
|
83
|
+
## `@skitterbyte/skitterspec-linear` v10 → v11 (`/spec-go` splits in two)
|
|
84
|
+
|
|
85
|
+
The same change as `@skitterbyte/skitterspec` v16 → v17 above — this
|
|
86
|
+
distribution composes the same lifecycle skills, so `/spec-go` is removed here
|
|
87
|
+
too and replaced by `/spec-start` + `/spec-next`. Read that entry; nothing here
|
|
88
|
+
is Linear-specific. (Backfilled: the base entry was written when the change
|
|
89
|
+
landed and this one was missed.)
|
|
90
|
+
|
|
3
91
|
## `@skitterbyte/skitterspec-linear` v9 → v10 (`push` validates your issue states)
|
|
4
92
|
|
|
5
93
|
**`spec-sync push` now refuses to run until the configured `states` names have
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ them. Your `specs/` content and live `.core` config are never touched.
|
|
|
72
72
|
|
|
73
73
|
## What the superset adds
|
|
74
74
|
|
|
75
|
-
On top of the base skills (`/spec`, `/spec-
|
|
75
|
+
On top of the base skills (`/spec`, `/spec-start`, `/spec-next`, isolation, …):
|
|
76
76
|
|
|
77
77
|
- **`/spec-status`** — read-only drift report: what the next push would create /
|
|
78
78
|
update, any workflow-state drift, and any phase whose status signals disagree
|
|
@@ -108,7 +108,7 @@ On top of the base skills (`/spec`, `/spec-go`, isolation, …):
|
|
|
108
108
|
Linear steps filled in, so the mirror keeps up without anyone remembering to
|
|
109
109
|
push: `/spec`, `/spec-bug` and `/spec-hotfix` link the spec they create (asking
|
|
110
110
|
which **Project** it belongs to, and minting a sub-issue per phase);
|
|
111
|
-
`/spec-
|
|
111
|
+
`/spec-next` refreshes it as work starts; `/spec-complete`, `/spec-cancel` and
|
|
112
112
|
`/spec-review` refresh it after they change it. All three creating skills can
|
|
113
113
|
also start **from** an existing issue — `/spec SKI-123`,
|
|
114
114
|
`/spec-hotfix v33.16.4 SKI-123`, or `--from-issue` to browse the ones your web
|
|
@@ -155,7 +155,7 @@ whole repo *or* one per lifecycle bucket:
|
|
|
155
155
|
so agents can be assigned one each.
|
|
156
156
|
- `"deferred"` — unlinked phases wait until the work starts. A spec sitting in
|
|
157
157
|
`specs/backlog/` mirrors as **the issue alone**, keeping its phase list in the
|
|
158
|
-
description; the sub-issues arrive with the push that follows `/spec-
|
|
158
|
+
description; the sub-issues arrive with the push that follows `/spec-start`. Worth
|
|
159
159
|
it when adopting sync on a backlog of dozens of specs, where the default
|
|
160
160
|
front-loads hundreds of calls for work nobody has started.
|
|
161
161
|
- `"inline"` — phases become **sections of the spec issue's own description**,
|
|
@@ -1,53 +1,25 @@
|
|
|
1
1
|
## Spec workflow
|
|
2
2
|
|
|
3
|
-
Spec-driven development runs through the lifecycle **skills**
|
|
4
|
-
structure and lifecycle stay consistent
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Spec-driven development runs through the lifecycle **skills** — use them so
|
|
4
|
+
structure and lifecycle stay consistent. The everyday loop is
|
|
5
|
+
**`spec → start → next → commit → complete`**, with `/spec-connect` when you want to test
|
|
6
|
+
the spec in a browser.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
13
|
-
| `/spec-go` | Provision the env, bring dev servers up, implement the next phase | `In Progress` | `specs/in-progress/` |
|
|
14
|
-
| `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
|
|
15
|
-
| `/spec-cancel` | Record progress, stamp a reason; tear down | `Cancelled` | `specs/cancelled/` |
|
|
16
|
-
| `/spec-hotfix` | (Hotfix) Fork a worktree from a release tag, red→green, land by tag | `In Progress` | `specs/in-progress/` |
|
|
17
|
-
| `/spec-to-main` | Land the branch on the base mid-spec, without finishing | (unchanged) | (unchanged) |
|
|
18
|
-
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
8
|
+
**Skills vs commands.** The lifecycle skills are read by Claude, which exercises
|
|
9
|
+
judgment. `/spec-connect` and `/spec-live` are **slash commands** instead — each
|
|
10
|
+
pre-executes one `spec-env` verb and relays it, so only you can run them; a
|
|
11
|
+
skill that wants one will tell you to type it.
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
that wants one will tell you to type it.
|
|
13
|
+
The skill table, the spec type/folder conventions and the per-spec isolation
|
|
14
|
+
model all live in **`.claude/rules/spec-planning.md`**, the canonical reference
|
|
15
|
+
every spec skill points at. Tailor its per-phase test commands to this stack.
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
> Tailor the per-phase test commands and project conventions referenced by the
|
|
36
|
-
> spec skills to this project's stack (see `.claude/rules/spec-planning.md`).
|
|
37
|
-
|
|
38
|
-
**Per-spec isolation (adopt once, then default):** with isolation adopted
|
|
39
|
-
(`skitterspec init --isolation`, or `specs/.core/env.config.json` present),
|
|
40
|
-
**`/spec-go`** gives every in-progress spec its own git worktree automatically —
|
|
41
|
-
parallel specs, no stashing, `main` left free — and brings up the project's host
|
|
42
|
-
**dev servers** (`env.config` → `dev`) on the spec's ports. The **`/spec-connect <name>`** command then exposes that spec on your canonical
|
|
43
|
-
`localhost` ports so you can test it at the normal URL (`/spec-connect main`
|
|
44
|
-
hands them back); for a code-only spec, **`/spec-live <name>`** reuses the dev
|
|
45
|
-
server you already have running instead. Docker is a
|
|
46
|
-
**per-spec escalation**: `/spec` sets `> **Stack:** worktree` (default) or
|
|
47
|
-
`worktree + docker` when the spec touches the DB / stateful services, and only
|
|
48
|
-
the latter gets a namespaced stack. Teardown is folded into **`/spec-complete`** ·
|
|
49
|
-
**`/spec-cancel`**; beneath the skills, `skitterspec spec-env` is the CLI engine — `up`, `down`,
|
|
50
|
-
`prune`, `dev`, `connect`, `integrate`, `hotfix`, `live`, `status` and `resolve`.
|
|
51
|
-
Most are planners the skills run; omit the spec name and it uses the worktree you
|
|
52
|
-
are standing in. Independent of lifecycle
|
|
53
|
-
status; inactive when `env.config.json` is absent.
|
|
17
|
+
**Release gating** *(only when `specs/.core/gating.config.json` exists)* — each
|
|
18
|
+
spec records whether it ships behind a feature flag, as
|
|
19
|
+
`> **Gating:** <flag name>` or `> **Gating:** none: <one-line reason>`. `/spec`,
|
|
20
|
+
`/spec-bug` and `/spec-hotfix` ask; `/spec-review`, `/spec-start` and
|
|
21
|
+
`/spec-complete` report a spec that has no answer, and never block over it.
|
|
22
|
+
`skitterspec gating check` lists them and always exits 0. The point is that the
|
|
23
|
+
question is **on the record**: a missing line is an oversight, a reason is a
|
|
24
|
+
decision. Skitterspec never reads your flag system — it asks and cites the doc
|
|
25
|
+
you point it at. Without that config, none of this appears.
|
package/assets/core/SETUP.md
CHANGED
|
@@ -168,7 +168,7 @@ field:
|
|
|
168
168
|
|
|
169
169
|
Everything else (state names, field ownership) has sensible defaults — see
|
|
170
170
|
`linear.config.md` to customise. The moment this file exists, the Linear steps in
|
|
171
|
-
`/spec` and `/spec-
|
|
171
|
+
`/spec` and `/spec-start` and the three sync skills switch on.
|
|
172
172
|
|
|
173
173
|
> Editing by hand skips the state-name check described above. If you go this
|
|
174
174
|
> route and your workspace renamed any state, `/spec-push` catches it at your
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"mode": "worktree",
|
|
2
3
|
"worktree": {
|
|
3
4
|
"root": "../{repo}-wt",
|
|
4
5
|
"folderPattern": "{slug}"
|
|
@@ -14,7 +15,9 @@
|
|
|
14
15
|
},
|
|
15
16
|
"seedFiles": {
|
|
16
17
|
"mode": "symlink",
|
|
17
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
".env"
|
|
20
|
+
]
|
|
18
21
|
},
|
|
19
22
|
"setup": [],
|
|
20
23
|
"dev": [],
|
|
@@ -30,6 +33,9 @@
|
|
|
30
33
|
"pattern": "{type}/{slug}",
|
|
31
34
|
"identifierField": ""
|
|
32
35
|
},
|
|
36
|
+
"spec": {
|
|
37
|
+
"companionPaths": []
|
|
38
|
+
},
|
|
33
39
|
"baseBranch": "",
|
|
34
40
|
"guards": {
|
|
35
41
|
"refuseTeardownIfDirty": true,
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
|
|
4
4
|
stack + host dev servers + a front-door proxy + an optional opener per
|
|
5
|
-
in-progress spec). Provisioning is folded into `/spec-
|
|
5
|
+
in-progress spec). Provisioning is folded into `/spec-start`, teardown into
|
|
6
6
|
`/spec-complete` · `/spec-cancel`, and traffic diversion is `/spec-connect`; the
|
|
7
7
|
`skitterspec spec-env <up|down|prune|dev|connect|integrate>` CLI is the engine
|
|
8
8
|
beneath them.
|
|
9
9
|
|
|
10
|
-
**Once this file is present, isolation is the default policy:** `/spec-
|
|
11
|
-
**every** in-progress spec its own git worktree automatically. Docker is a
|
|
12
|
-
spec escalation** — a spec brings up a stack only when its
|
|
13
|
-
is `worktree + docker` (set at `/spec` when it touches
|
|
14
|
-
services). A `worktree`-only spec takes no registry slot, no
|
|
15
|
-
`.env`.
|
|
10
|
+
**Once this file is present, isolation is the default policy:** `/spec-start` gives
|
|
11
|
+
**every** in-progress spec its own git worktree automatically. Docker is a
|
|
12
|
+
**per- spec escalation** — a spec brings up a stack only when its
|
|
13
|
+
`> **Stack:**` header is `worktree + docker` (set at `/spec` when it touches
|
|
14
|
+
the DB / stateful services). A `worktree`-only spec takes no registry slot, no
|
|
15
|
+
port block, and no `.env`.
|
|
16
16
|
|
|
17
17
|
**Adopt it** with `skitterspec init --isolation` (or copy
|
|
18
18
|
`env.config.json.example` → `env.config.json` here) and edit the values. While
|
|
@@ -28,6 +28,22 @@ no live `env.config.json` was found.
|
|
|
28
28
|
```jsonc
|
|
29
29
|
{
|
|
30
30
|
// Where sibling worktrees are created and how their dirs are named.
|
|
31
|
+
// Where a spec's branch gets built.
|
|
32
|
+
//
|
|
33
|
+
// "worktree" (default) — every spec gets its own git worktree. Several
|
|
34
|
+
// specs run side by side and `main` stays free, at the cost of
|
|
35
|
+
// one terminal session per spec (`/spec-start` sets it up for you).
|
|
36
|
+
// "checkout" — the branch is built in the primary checkout instead. One
|
|
37
|
+
// spec at a time, but no second session and no hand-off: the
|
|
38
|
+
// terminal you are already in follows the work.
|
|
39
|
+
//
|
|
40
|
+
// Pick it for how you work, not for what this repo contains — a project with
|
|
41
|
+
// no dev servers may still want several specs in flight. An unrecognised
|
|
42
|
+
// value falls back to "worktree" rather than erroring.
|
|
43
|
+
//
|
|
44
|
+
// Not to be confused with `seedFiles.mode`, which is "symlink" | "copy".
|
|
45
|
+
"mode": "worktree",
|
|
46
|
+
|
|
31
47
|
"worktree": {
|
|
32
48
|
"root": "../{repo}-wt", // dir that holds all spec worktrees; sibling of
|
|
33
49
|
// the primary checkout, never nested inside it.
|
|
@@ -108,9 +124,18 @@ no live `env.config.json` was found.
|
|
|
108
124
|
"host": "127.0.0.1" // bind host for the canonical ports
|
|
109
125
|
},
|
|
110
126
|
|
|
111
|
-
// Optional, editor/terminal-agnostic opener
|
|
112
|
-
//
|
|
113
|
-
//
|
|
127
|
+
// Optional, editor/terminal-agnostic opener — the FALLBACK for reaching a
|
|
128
|
+
// worktree. `/spec-start` normally moves the session you typed into into the
|
|
129
|
+
// worktree, and then there is nothing to open: it runs this only when it
|
|
130
|
+
// could not switch in place (the session is already inside another worktree,
|
|
131
|
+
// or the harness cannot move it). Run after provisioning and bootstrap, so
|
|
132
|
+
// the session opens onto a tree that is ready to work in. The template is
|
|
133
|
+
// expanded with {worktreePath}, {slug}, {branch}, {projectName},
|
|
134
|
+
// {portOffset}.
|
|
135
|
+
// Empty = nothing is opened (the path is just printed), which is how you turn
|
|
136
|
+
// the auto-open off, and the right value unless you actually want a second
|
|
137
|
+
// window on the fallback path. A non-interactive run skips it either way — an
|
|
138
|
+
// opened window nobody is sitting at helps no one.
|
|
114
139
|
// Examples: "code {worktreePath}", "tmux new-window -c {worktreePath}",
|
|
115
140
|
// or a "warp://..." deeplink for Warp users.
|
|
116
141
|
"open": {
|
|
@@ -133,6 +158,26 @@ no live `env.config.json` was found.
|
|
|
133
158
|
"identifierField": ""
|
|
134
159
|
},
|
|
135
160
|
|
|
161
|
+
// Paths that belong to a spec ALONGSIDE its own `specs/<bucket>/<name>/`
|
|
162
|
+
// folder. `/spec-start` uses this to tell "the spec you just wrote, not yet
|
|
163
|
+
// committed" apart from someone else's uncommitted work: if every dirty path
|
|
164
|
+
// belongs to the spec being started it is committed for you, and if a single
|
|
165
|
+
// path does not, the start is refused as before.
|
|
166
|
+
//
|
|
167
|
+
// Provider-neutral by design — the base engine must not know that any
|
|
168
|
+
// particular tracker exists — so you declare the shape here. `{slug}` and
|
|
169
|
+
// `{identifier}` expand exactly as in `branch.pattern` above, `{identifier}`
|
|
170
|
+
// via `branch.identifierField`.
|
|
171
|
+
//
|
|
172
|
+
// A pattern using {identifier} matches NOTHING when no identifier resolves
|
|
173
|
+
// (no `identifierField` set, or a spec never pushed to a tracker). That is
|
|
174
|
+
// deliberate: the file it names then belongs to some other spec, and the safe
|
|
175
|
+
// failure is a refusal you clear with /commit, not a stranger's file swept
|
|
176
|
+
// into your commit. Default: none — a spec owns only its own folder.
|
|
177
|
+
"spec": {
|
|
178
|
+
"companionPaths": []
|
|
179
|
+
},
|
|
180
|
+
|
|
136
181
|
// Integration base branch — the branch specs fork from and land back onto
|
|
137
182
|
// (used by the teardown "merged?" guard and, later, the integrate step).
|
|
138
183
|
// Empty = auto-detect: origin/HEAD → main → master. Set it when your default
|
|
@@ -148,7 +193,7 @@ no live `env.config.json` was found.
|
|
|
148
193
|
"refuseTeardownIfUnpushed": true
|
|
149
194
|
},
|
|
150
195
|
|
|
151
|
-
// What teardown cleans up beyond this machine. `/spec-
|
|
196
|
+
// What teardown cleans up beyond this machine. `/spec-start` pushes the spec
|
|
152
197
|
// branch when it provisions, so without this a completed spec leaves a merged
|
|
153
198
|
// branch on the remote forever. `deleteRemoteBranch`:
|
|
154
199
|
// "prompt" (default) — plan `git push <remote> --delete <branch>` in its own
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# `gating.config.json` — release-gating config
|
|
2
|
+
|
|
3
|
+
Opt-in config for **release gating**: whether each spec records a decision about
|
|
4
|
+
shipping behind a feature flag.
|
|
5
|
+
|
|
6
|
+
The problem it solves is not "we forgot to use a flag" — it is that
|
|
7
|
+
**nobody can tell whether the question was asked**. A spec can go from `/spec`
|
|
8
|
+
through
|
|
9
|
+
implementation to `/spec-complete` with no flag and no mention of one, and that
|
|
10
|
+
is indistinguishable from "we considered it and decided against". Prose guidance
|
|
11
|
+
in a project rule has already been shown not to close that gap; a required header
|
|
12
|
+
does, because its absence is visible.
|
|
13
|
+
|
|
14
|
+
**Skitterspec bakes in the offer, never the mechanism.** How your project does
|
|
15
|
+
flags — a resolver, an admin toggle, env precedence, a vendor SDK — is none of
|
|
16
|
+
its business, and it never reads your flag code. It asks the question, cites your
|
|
17
|
+
own documentation, and records the answer.
|
|
18
|
+
|
|
19
|
+
**Adopt it** by copying `gating.config.json.example` → `gating.config.json` here
|
|
20
|
+
(or `skitterspec init --gating`). While this file is absent the feature is
|
|
21
|
+
entirely unused: no grill question, no header, nothing to check — which is read
|
|
22
|
+
as "this project does not use feature flags".
|
|
23
|
+
|
|
24
|
+
## Fields
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
{
|
|
28
|
+
// Where THIS project documents how its flags work — a repo-relative path,
|
|
29
|
+
// cited when the question is asked so the answer is an informed one.
|
|
30
|
+
// Skitterspec never reads the file; it only names it. Empty = say nothing.
|
|
31
|
+
"guidance": ".claude/rules/feature-flags.md",
|
|
32
|
+
|
|
33
|
+
// The value written when the user declines a flag, so a project can
|
|
34
|
+
// standardise its wording. Must keep the `none: <reason>` shape — the reason
|
|
35
|
+
// half is the whole point (see below). Empty falls back to `none: <reason>`.
|
|
36
|
+
"default": "none: <reason>"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The header it drives
|
|
41
|
+
|
|
42
|
+
With this file present, every spec `/spec`, `/spec-bug` and `/spec-hotfix` write
|
|
43
|
+
carries a `Gating:` field beside `Stack:` in `00-overview.md`:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
> **Gating:** search-ranking-v2
|
|
47
|
+
> **Gating:** none: additive, nothing to revert
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Two valid shapes, and one that is not:
|
|
51
|
+
|
|
52
|
+
| Value | Meaning |
|
|
53
|
+
|-------|---------|
|
|
54
|
+
| a flag name | ships behind that flag |
|
|
55
|
+
| `none: <reason>` | deliberately not flagged, and why |
|
|
56
|
+
| *missing, empty, or a bare `none`* | **not a decision** — nobody answered |
|
|
57
|
+
|
|
58
|
+
The reason half is load-bearing. `none: additive, nothing to revert` is a
|
|
59
|
+
decision a reviewer can disagree with; a bare `none` is a shrug, and a missing
|
|
60
|
+
line is an oversight. Distinguishing those three is the entire feature.
|
|
61
|
+
|
|
62
|
+
## What it never does
|
|
63
|
+
|
|
64
|
+
- **It never blocks.** `skitterspec gating check` reports and exits 0;
|
|
65
|
+
`/spec-start` and `/spec-complete` mention a missing header and carry on.
|
|
66
|
+
Nothing here can stop a spec being started, completed or landed.
|
|
67
|
+
- **It never accuses an old spec.** Only `specs/backlog/` and
|
|
68
|
+
`specs/in-progress/` are read. Specs finished or abandoned before you adopted
|
|
69
|
+
gating are out of range by construction, not by a filter someone has to
|
|
70
|
+
remember.
|
|
71
|
+
- **It never learns your flag system.** `guidance` is a path it prints, nothing
|
|
72
|
+
more.
|
|
73
|
+
|
|
74
|
+
## Checking
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
skitterspec gating check # the spec in flight here
|
|
78
|
+
skitterspec gating check <spec> # one named spec
|
|
79
|
+
skitterspec gating check --all # every active spec
|
|
80
|
+
skitterspec gating check --json # for tooling
|
|
81
|
+
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# `linear.config.json` — Linear one-way sync config
|
|
2
2
|
|
|
3
3
|
Opt-in config for the Linear sync (`/spec-status`, `/spec-push`, and the
|
|
4
|
-
Linear-aware paths of `/spec` and `/spec-
|
|
4
|
+
Linear-aware paths of `/spec` and `/spec-start`). Sync is **one-way**: the repo is
|
|
5
5
|
the source of truth and the Linear **issue** is a **generated mirror**. A spec is
|
|
6
6
|
a Linear issue and each phase a sub-issue; a phase's tasks ride along inside
|
|
7
7
|
that sub-issue's description as a read-only checklist. Content is
|
|
@@ -11,7 +11,7 @@ is a read-only drift report. The `sync.fieldOwnership` map now just selects the
|
|
|
11
11
|
projection field set (every field is repo-owned and pushed).
|
|
12
12
|
|
|
13
13
|
**Every Linear step is gated on this file.** While `specs/.core/linear.config.json`
|
|
14
|
-
is absent the feature is simply unused — `/spec`, `/spec-
|
|
14
|
+
is absent the feature is simply unused — `/spec`, `/spec-start`, and the CLI's
|
|
15
15
|
`spec-sync` subcommands behave exactly as they do today (local-only). Adopt it by
|
|
16
16
|
copying `linear.config.json.example` → `linear.config.json` here and filling in
|
|
17
17
|
your team ID (and an optional grouping project).
|
|
@@ -365,10 +365,11 @@ default for anyone who never sets one.
|
|
|
365
365
|
`save_issue` calls to mirror, N being its phase count.
|
|
366
366
|
- `"deferred"` — only once the work starts. A spec sitting in `specs/backlog/`
|
|
367
367
|
mirrors as **the issue alone**; its sub-issues are created by the push that
|
|
368
|
-
follows `/spec-
|
|
369
|
-
- `"inline"` — never. Each phase becomes a
|
|
370
|
-
description**, with its full task list, and
|
|
371
|
-
table of contents. One issue per spec,
|
|
368
|
+
follows `/spec-start`.
|
|
369
|
+
- `"inline"` — never. Each phase becomes a
|
|
370
|
+
**section of the spec issue's own description**, with its full task list, and
|
|
371
|
+
the `## Phases` index stays as the table of contents. One issue per spec,
|
|
372
|
+
however many phases it has.
|
|
372
373
|
|
|
373
374
|
### One mode, or one per bucket
|
|
374
375
|
|
|
@@ -405,11 +406,11 @@ that made the choice worth it.
|
|
|
405
406
|
|
|
406
407
|
### Switching modes is non-destructive
|
|
407
408
|
|
|
408
|
-
**A phase already carrying a `linear_issue_id` keeps its sub-issue in every
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
**A phase already carrying a `linear_issue_id` keeps its sub-issue in every mode**,
|
|
410
|
+
and is never *also* inlined. One-way sync has no delete op, so withholding a
|
|
411
|
+
live sub-issue would not remove it from Linear — it would freeze it there,
|
|
412
|
+
never updated again. So changing `mapping.phases` only ever changes what has
|
|
413
|
+
yet to be minted, and a spec part-way through keeps a coherent mirror.
|
|
413
414
|
|
|
414
415
|
**Adopting on an established repo:** set `complete: "inline"` (and `"deferred"`
|
|
415
416
|
or `"inline"` for `backlog`) **before** the first backfill push. Finished specs
|
|
@@ -11,7 +11,8 @@ and lifecycle stay consistent. Each sets a status on the spec header
|
|
|
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
13
|
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
14
|
-
| `/spec-
|
|
14
|
+
| `/spec-start` | Put a spec in flight — provision its branch, then build phase 1 | `In Progress` | `specs/in-progress/` |
|
|
15
|
+
| `/spec-next` | Build the next phase of the spec this session is in (re-run per phase) | `In Progress` (unchanged) | (unchanged) |
|
|
15
16
|
| `/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) |
|
|
16
17
|
| `/spec-complete` | Verify all phases done + tests green; land + tear down | `Complete` | `specs/complete/` |
|
|
17
18
|
| `/spec-cancel` | Record progress, stamp a reason on the header; tear down | `Cancelled` | `specs/cancelled/` |
|
|
@@ -21,15 +22,15 @@ and lifecycle stay consistent. Each sets a status on the spec header
|
|
|
21
22
|
exercises judgment. `/spec-connect` and `/spec-live` are instead **slash commands**
|
|
22
23
|
(`.claude/commands/`): each pre-executes one `skitterspec spec-env` verb and
|
|
23
24
|
relays its output, so there is no judgment to apply and no model turn spent
|
|
24
|
-
finding one. They are marked `disable-model-invocation`, meaning
|
|
25
|
-
run them** — a skill that wants one will tell you to type it
|
|
26
|
-
it.
|
|
25
|
+
finding one. They are marked `disable-model-invocation`, meaning
|
|
26
|
+
**only you can run them** — a skill that wants one will tell you to type it
|
|
27
|
+
rather than invoking it.
|
|
27
28
|
|
|
28
29
|
`/spec-to-main`, `/spec-status` and `/spec-sync` stay **skills** — each carries
|
|
29
30
|
real judgment (green tests before a land; an MCP fetch and a team-key check; ten
|
|
30
31
|
subcommands) — but they are marked user-only too, since nobody reaches them
|
|
31
32
|
except by typing them. Everything else in the table above stays model-invocable,
|
|
32
|
-
which is what lets `/spec-
|
|
33
|
+
which is what lets `/spec-next` hand off to `/spec-push` as work progresses.
|
|
33
34
|
|
|
34
35
|
Status flow: `Ready → In Progress → Complete` (or `Cancelled` from any state).
|
|
35
36
|
`/spec` grills to a **Ready** spec directly — there is no separate grooming
|
|
@@ -41,14 +42,26 @@ mid-spec (so the work can run in CI / a shared test env) while the spec stays
|
|
|
41
42
|
`In Progress` in `specs/in-progress/`; it's the intermediate, repeatable half of
|
|
42
43
|
`/spec-complete`'s landing, without the finalise-and-tear-down.
|
|
43
44
|
|
|
45
|
+
**Two workspace modes.** `specs/.core/env.config.json` → `mode` decides where a
|
|
46
|
+
spec's branch is built. **`worktree`** (the default) gives each spec its own
|
|
47
|
+
checkout — several specs at once and `main` left free — and `/spec-start` moves
|
|
48
|
+
the session you typed into *into* that checkout, so the work carries on in the
|
|
49
|
+
same terminal. **`checkout`** builds the branch in the primary checkout instead:
|
|
50
|
+
one spec at a time, and nothing to move into. Neither mode costs you a second
|
|
51
|
+
terminal; the difference is how many specs can be in flight at once.
|
|
52
|
+
Pick it for how you work rather than for what the project contains — a repo with
|
|
53
|
+
no dev servers may still want several specs in flight. In `checkout` mode
|
|
54
|
+
`/spec-connect` and `/spec-live` do not apply and say so: both exist to reach
|
|
55
|
+
work that lives elsewhere, which is the gap that mode removes.
|
|
56
|
+
|
|
44
57
|
**Per-spec isolation (opt-in to adopt, then the default policy).** When a project
|
|
45
58
|
adopts isolation (`skitterspec init --isolation`, or `specs/.core/env.config.json`
|
|
46
|
-
present), `/spec-
|
|
59
|
+
present), `/spec-start` gives **every** in-progress spec its own git worktree
|
|
47
60
|
automatically — several specs run side by side without stashing or clashing, and
|
|
48
61
|
`main` stays free. Docker is a **per-spec escalation**: `/spec` records
|
|
49
62
|
`> **Stack:** worktree` (default) or `worktree + docker` when the spec touches the
|
|
50
|
-
DB / stateful services, and `/spec-
|
|
51
|
-
latter. `/spec-
|
|
63
|
+
DB / stateful services, and `/spec-start` brings up a namespaced stack only for the
|
|
64
|
+
latter. `/spec-start` also starts the project's host **dev servers** (`env.config`
|
|
52
65
|
→ `dev`) on the spec's ports; **`/spec-connect <name>`** then exposes that spec on
|
|
53
66
|
your canonical `localhost` ports so you can test it at the normal URL
|
|
54
67
|
(`/spec-connect main` hands them back). All housekeeping (the backlog→in-progress
|
|
@@ -74,7 +87,8 @@ feature at the normal URL — no second stack, no proxy. The branch checked out
|
|
|
74
87
|
the primary checkout **is** the lock: exactly one spec is live at a time, and
|
|
75
88
|
`/spec-live main` hands the instance back (fixes you make while live commit
|
|
76
89
|
straight onto the branch; `/spec-complete` is live-aware and lands them). Rule of
|
|
77
|
-
thumb: **live overlay is the light default for code-only
|
|
90
|
+
thumb: **live overlay is the light default for *testing* a code-only spec** — it
|
|
91
|
+
is not how work gets started, and no lifecycle skill invokes it; it *refuses*
|
|
78
92
|
stateful ones (`Stack: worktree + docker`, or a branch touching migrations) — keep
|
|
79
93
|
`/spec-connect` + a Docker stack for those, and for genuinely parallel testing.
|
|
80
94
|
Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engine.
|
|
@@ -83,6 +97,13 @@ Beneath it, `skitterspec spec-env live <take|release|abort|status>` is the engin
|
|
|
83
97
|
tracker-free: it knows nothing about any specific ticketing system. A
|
|
84
98
|
ticketing provider is installed as its own distribution that plugs into named
|
|
85
99
|
**seams** in the shared skills and fulfils a skill-name + CLI contract.
|
|
100
|
+
|
|
101
|
+
Part of that contract is the **binary name**: the shipped `/spec-connect` and
|
|
102
|
+
`/spec-live` commands invoke `skitterspec`, so a superset that replaces the base
|
|
103
|
+
must expose that name too, alongside whatever it calls itself — one entry point
|
|
104
|
+
under two names. A distribution shipping only its own name leaves those commands
|
|
105
|
+
failing with `command not found`, which reads as a broken install rather than a
|
|
106
|
+
missing alias.
|
|
86
107
|
Sync is **one-way**: the repo is the source of truth and the tracker is a
|
|
87
108
|
**generated mirror**. It ships `/spec-push` (repo→tracker; computes a create/update plan
|
|
88
109
|
against a committed last-pushed snapshot and applies it) and `/spec-status`
|
|
@@ -92,13 +113,28 @@ the tracker is never read back or merged.
|
|
|
92
113
|
|
|
93
114
|
**Every skill that moves a spec through the lifecycle carries a seam**, so the
|
|
94
115
|
mirror keeps up without anyone remembering to push: `/spec`, `/spec-bug` and
|
|
95
|
-
`/spec-hotfix` link the spec they create; `/spec-
|
|
116
|
+
`/spec-hotfix` link the spec they create; `/spec-next` refreshes it as work starts;
|
|
96
117
|
`/spec-complete`, `/spec-cancel` and `/spec-review` refresh it after they change
|
|
97
118
|
it. `/spec-to-main` and `/spec-live` carry none — they change no status.
|
|
98
119
|
With no provider installed the seams are empty and every skill behaves as a plain
|
|
99
120
|
filesystem workflow. See the provider package's own docs for its config and field
|
|
100
121
|
reference.
|
|
101
122
|
|
|
123
|
+
**Release gating (opt-in, config-gated).** With
|
|
124
|
+
`specs/.core/gating.config.json` present, every spec `/spec`, `/spec-bug` and
|
|
125
|
+
`/spec-hotfix` write carries a `> **Gating:**` header recording one decision:
|
|
126
|
+
does this ship behind a feature flag, or land live? The value is a **flag name**,
|
|
127
|
+
or **`none: <one-line reason>`** — and the reason half is the load-bearing part,
|
|
128
|
+
because `none: additive, nothing to revert` is a decision a reviewer can argue
|
|
129
|
+
with while a bare `none` is a shrug and a missing line is an oversight.
|
|
130
|
+
|
|
131
|
+
Skitterspec bakes in **the offer, never the mechanism**: it asks, cites the
|
|
132
|
+
project's own doc (`guidance` in that config), and records the answer. It never
|
|
133
|
+
reads your flag code. `skitterspec gating check` reports specs with no decision
|
|
134
|
+
and **always exits 0** — it reads only `backlog/` and `in-progress/`, so specs
|
|
135
|
+
finished before you adopted gating are out of range by construction. With the
|
|
136
|
+
config absent nothing appears at all: no question, no header, no check.
|
|
137
|
+
|
|
102
138
|
## Project conventions (fill this in)
|
|
103
139
|
|
|
104
140
|
The spec skills tell you to run "your project's typecheck and test commands" and
|
|
@@ -135,12 +171,16 @@ status.
|
|
|
135
171
|
Every spec header carries:
|
|
136
172
|
|
|
137
173
|
- `> **Name:**` — the spec's folder name (`feat-`/`bug-`/`hotfix-<kebab-name>`).
|
|
138
|
-
It's the handle you pass to `/spec-
|
|
174
|
+
It's the handle you pass to `/spec-start` and the other lifecycle skills, surfaced
|
|
139
175
|
in the header so it's copy-pasteable without digging for the folder name.
|
|
140
176
|
- `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
|
|
141
177
|
to `git config user.name`).
|
|
142
|
-
- `> **Developer:**` — who implements it (`—` until `/spec-
|
|
178
|
+
- `> **Developer:**` — who implements it (`—` until `/spec-start` starts work, then
|
|
143
179
|
set to `git config user.name`; `/spec-bug` sets it immediately).
|
|
180
|
+
- `> **Gating:**` — the release-gating decision,
|
|
181
|
+
**only when `specs/.core/gating.config.json` exists**: a flag name, or
|
|
182
|
+
`none: <one-line reason>`. Absent entirely in a project that has not adopted
|
|
183
|
+
gating.
|
|
144
184
|
|
|
145
185
|
Every spec also has a **State log** table — the audit trail of folder/status
|
|
146
186
|
transitions. Each lifecycle skill appends exactly one row when it changes state:
|
|
@@ -175,7 +215,7 @@ When asked for a plan, implementation strategy, or feature breakdown:
|
|
|
175
215
|
|
|
176
216
|
```
|
|
177
217
|
specs/backlog/ Ready (or Draft) specs (/spec)
|
|
178
|
-
specs/in-progress/ under active implementation (/spec-
|
|
218
|
+
specs/in-progress/ under active implementation (/spec-start, /spec-bug)
|
|
179
219
|
specs/complete/ finished (/spec-complete)
|
|
180
220
|
specs/cancelled/ abandoned, with a reason on the header (/spec-cancel)
|
|
181
221
|
specs/.core/ project rules — ALWAYS APPLY, never moved
|