@skitterbyte/skitterspec-jira 0.0.0-stage → 2.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 +1266 -0
- package/README.md +44 -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 +606 -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 +88 -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 +702 -0
- package/assets/rules/spec-reports.md +572 -0
- package/assets/skills/no-spec/SKILL.md +249 -0
- package/assets/skills/spec/SKILL.md +714 -0
- package/assets/skills/spec-bug/SKILL.md +585 -0
- package/assets/skills/spec-cancel/SKILL.md +261 -0
- package/assets/skills/spec-complete/SKILL.md +451 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +607 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-jira-setup/SKILL.md +221 -0
- package/assets/skills/spec-list/SKILL.md +173 -0
- package/assets/skills/spec-next/SKILL.md +678 -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 +114 -0
- package/assets/skills/spec-sync/SKILL.md +220 -0
- package/assets/skills/spec-to-main/SKILL.md +141 -0
- package/bin/skitterspec-jira.js +68 -0
- package/package.json +40 -4
- package/src/cli.js +5967 -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 +850 -0
- package/src/env/dev.js +63 -0
- package/src/env/glob.js +34 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +413 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +167 -0
- package/src/env/live.js +479 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +225 -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 +2622 -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 +1252 -0
- package/src/vendor/jira/commands.js +44 -0
- package/src/vendor/jira/config.js +286 -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 +207 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +251 -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 +63 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/list-assignee.js +71 -0
- package/src/vendor/provider-kit/src/list-rows.js +35 -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 +219 -0
- package/src/vendor/provider-kit/src/specs.js +248 -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 +338 -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 +971 -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,606 @@
|
|
|
1
|
+
# `env.config.json` — per-spec isolation config
|
|
2
|
+
|
|
3
|
+
Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
|
|
4
|
+
stack + host dev servers + a front-door proxy per in-progress spec).
|
|
5
|
+
Provisioning is folded into `/spec-start`, teardown into `/spec-complete` ·
|
|
6
|
+
`/spec-cancel`, traffic diversion is `/spec-connect`, and reading a spec's diff
|
|
7
|
+
is `/spec-diff`; the `skitterspec spec-env
|
|
8
|
+
<up|bootstrap|nospec|main|down|prune|dev|connect|integrate|hotfix|live|review|stage|status|resolve>` CLI is
|
|
9
|
+
the engine beneath them.
|
|
10
|
+
|
|
11
|
+
**Once this file is present, isolation is the default policy:** `/spec-start` gives
|
|
12
|
+
**every** in-progress spec its own git worktree automatically. Docker is a
|
|
13
|
+
**per- spec escalation** — a spec brings up a stack only when its
|
|
14
|
+
`> **Stack:**` header is `worktree + docker` (set at `/spec` when it touches
|
|
15
|
+
the DB / stateful services). A `worktree`-only spec takes no registry slot, no
|
|
16
|
+
port block, and no `.env`.
|
|
17
|
+
|
|
18
|
+
**Adopt it** with `skitterspec init --isolation` (or copy
|
|
19
|
+
`env.config.json.example` → `env.config.json` here) and edit the values. While
|
|
20
|
+
`env.config.json` is absent the feature is simply unused — every skill behaves
|
|
21
|
+
exactly as it does today.
|
|
22
|
+
|
|
23
|
+
The loader (`src/env/config.js` → `loadEnvConfig`) merges your file over the
|
|
24
|
+
frozen defaults below and returns `{ config, present, unknown }`;
|
|
25
|
+
`present:false` means no live `env.config.json` was found.
|
|
26
|
+
|
|
27
|
+
**A key not listed below is ignored — and says so.** Every `spec-env` command
|
|
28
|
+
prints one advisory line per unrecognised key, top-level or nested:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
spec-env: env.config.json — unknown key "docker.portbase" is ignored.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
It is advisory in the strongest sense: the key is dropped exactly as it always
|
|
35
|
+
was, nothing refuses, and the exit status is unchanged. It exists because the
|
|
36
|
+
two things an unknown key can be — a deliberate forward-compat entry and a typo
|
|
37
|
+
— are indistinguishable from here, and only one of them is a mistake you would
|
|
38
|
+
want to hear about. A mis-typed `review.required` leaves the commit gate on and
|
|
39
|
+
a mis-typed `teardown.deleteRemoteBranch` reverts to `prompt`; before this, the
|
|
40
|
+
only signal either gave was that nothing happened.
|
|
41
|
+
|
|
42
|
+
A **known** key whose value is rejected — `mode: "Checkout"`,
|
|
43
|
+
`teardown.deleteRemoteBranch: "yes"` — is not reported here. Each of those falls
|
|
44
|
+
through to a documented conservative default; see the field notes below.
|
|
45
|
+
|
|
46
|
+
## Fields
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{
|
|
50
|
+
// Where sibling worktrees are created and how their dirs are named.
|
|
51
|
+
// Where a spec's branch gets built.
|
|
52
|
+
//
|
|
53
|
+
// "worktree" (default) — every spec gets its own git worktree. Several
|
|
54
|
+
// specs run side by side and `main` stays free. `/spec-start`
|
|
55
|
+
// moves your session into the spec's worktree with a `cd`, so
|
|
56
|
+
// the terminal you are already in follows the work.
|
|
57
|
+
// "checkout" — the branch is built in the primary checkout instead. One
|
|
58
|
+
// spec at a time, and nowhere else to stand: the work comes to
|
|
59
|
+
// your terminal rather than your terminal going to it.
|
|
60
|
+
//
|
|
61
|
+
// Pick it for how you work, not for what this repo contains — a project with
|
|
62
|
+
// no dev servers may still want several specs in flight. An unrecognised
|
|
63
|
+
// value falls back to "worktree" rather than erroring.
|
|
64
|
+
//
|
|
65
|
+
// Not to be confused with `seedFiles.mode`, which is "symlink" | "copy".
|
|
66
|
+
"mode": "worktree",
|
|
67
|
+
|
|
68
|
+
"worktree": {
|
|
69
|
+
"root": "../{repo}-wt", // dir that holds all spec worktrees; sibling of
|
|
70
|
+
// the primary checkout, never nested inside it.
|
|
71
|
+
"folderPattern": "{slug}" // per-spec worktree dir name.
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Per-spec Docker stack. COMPOSE_PROJECT_NAME namespaces containers,
|
|
75
|
+
// networks, and named volumes; PORT_OFFSET shifts the spec's port block.
|
|
76
|
+
"docker": {
|
|
77
|
+
// Master switch: "is Docker escalation available on this project?" — NOT
|
|
78
|
+
// "always run Docker". true = specs MAY escalate (a spec still needs
|
|
79
|
+
// `Stack: worktree + docker` to actually get a stack); the default stack is
|
|
80
|
+
// worktree-only. false = every spec is worktree-only and the escalation is
|
|
81
|
+
// hidden. (Was "always provision Docker" in the pre-Stack engine.)
|
|
82
|
+
"enabled": true,
|
|
83
|
+
// Passed as `-f` to `docker compose` on up and down, but ONLY when the file
|
|
84
|
+
// is actually there — a project whose file has a name docker finds by
|
|
85
|
+
// itself keeps working untouched. It is also the signal that lets a spec
|
|
86
|
+
// with no `Stack:` header inherit `enabled` above: the switch is a
|
|
87
|
+
// project-wide default, the file is evidence there is a stack to bring up.
|
|
88
|
+
// An explicit header is checked first and never consults it.
|
|
89
|
+
"composeFile": "docker-compose.yml",
|
|
90
|
+
"projectNamePattern": "{repoSlug}_{slug}", // → COMPOSE_PROJECT_NAME
|
|
91
|
+
"portBase": 3000, // first port of slot 0's block
|
|
92
|
+
"portsPerSpec": 10, // block width; slot n → portBase + n*portsPerSpec
|
|
93
|
+
// Written into the worktree by `spec-env bootstrap`, and passed to compose
|
|
94
|
+
// as `--env-file`. A Docker spec's worktree steps run in this order:
|
|
95
|
+
// seedFiles, this env file
|
|
96
|
+
// (COMPOSE_PROJECT_NAME and PORT_OFFSET appended only where missing, so a
|
|
97
|
+
// value `setup` or you put there is kept), `setup`, then
|
|
98
|
+
// `docker compose … up -d` — all from inside the worktree, so the stack
|
|
99
|
+
// reads this spec's values and not the primary checkout's.
|
|
100
|
+
// It must be a file of its OWN: if it is also in `seedFiles` as a symlink,
|
|
101
|
+
// the write is refused rather than send this spec's port block into the
|
|
102
|
+
// primary checkout's file. Name a different file here (".worktree.env")
|
|
103
|
+
// and have your compose file read the values from it.
|
|
104
|
+
"envFile": ".env",
|
|
105
|
+
"backupCommand": "" // optional pre-teardown backup (e.g. pg_dump);
|
|
106
|
+
// empty = no backup, volumes dropped directly.
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Gitignored files seeded from the primary checkout into a fresh worktree by
|
|
110
|
+
// `spec-env bootstrap`, after `git worktree add` and BEFORE `setup` runs — so a
|
|
111
|
+
// fresh linked worktree (which starts with none of the repo's gitignored files)
|
|
112
|
+
// has the .env / local secret overrides / local config that setup steps and
|
|
113
|
+
// git hooks depend on. Without this any setup step that reads .env — a schema
|
|
114
|
+
// or client generator, a codegen pass — hard-fails in the new worktree
|
|
115
|
+
// because the file it expects isn't there.
|
|
116
|
+
// mode "symlink" (default) points the worktree file at the main file, so it
|
|
117
|
+
// stays in sync; "copy" makes an independent copy.
|
|
118
|
+
// files repo-relative paths to seed. A source absent in main is a printed
|
|
119
|
+
// no-op (not an error); a target that already exists is left untouched
|
|
120
|
+
// (idempotent — safe to re-run `spec-env bootstrap` over an existing
|
|
121
|
+
// worktree). Seeding is done by the engine itself, never printed as
|
|
122
|
+
// shell, so no command line ever names one of these files.
|
|
123
|
+
// Shorthand: `"seedFiles": [".env", …]` == `{ "mode": "symlink", "files": […] }`.
|
|
124
|
+
// `.env` is an ILLUSTRATION, not an expectation — skitterspec never reads any
|
|
125
|
+
// of these files, it only links or copies them. Name whatever your stack keeps
|
|
126
|
+
// out of git: `appsettings.Development.json` or a user-secrets file, `.envrc`,
|
|
127
|
+
// `local.settings.json`, `config/local.yml`.
|
|
128
|
+
// Seeded files are gitignored, so they never make the worktree "dirty" and
|
|
129
|
+
// never block teardown; they vanish with the worktree at `spec-env down`.
|
|
130
|
+
// On a Docker project, do not seed `docker.envFile` as a symlink — the env
|
|
131
|
+
// file step refuses to write through one (see `envFile` above).
|
|
132
|
+
// [] (or absent) = seed nothing (current behaviour).
|
|
133
|
+
"seedFiles": {
|
|
134
|
+
"mode": "symlink",
|
|
135
|
+
"files": [".env"]
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Bootstrap commands `spec-env bootstrap <spec>` runs IN the worktree, after
|
|
139
|
+
// the seeds and the env file and before the stack, on every provision including
|
|
140
|
+
// re-attach — so a fresh worktree's dependencies exist and git hooks,
|
|
141
|
+
// typechecks, builds and tests work immediately instead of failing on a
|
|
142
|
+
// missing node_modules. An array, run in order, stopping at the first that
|
|
143
|
+
// exits non-zero; [] = none. Each string is a shell command; {slug}/{branch}/{worktreePath}/{projectName}/{portOffset}
|
|
144
|
+
// expand (the cwd is already the worktree, so {worktreePath} is usually
|
|
145
|
+
// redundant). Example: ["pnpm install --frozen-lockfile"].
|
|
146
|
+
// An entry may also be { "run": "<cmd>", "when": ["<glob>", ...] }. `when` is
|
|
147
|
+
// IGNORED here — a fresh worktree always runs every setup step — and exists
|
|
148
|
+
// so `afterLand` can re-run that step after a land that changes its inputs
|
|
149
|
+
// (`{ "setup": true }`, below).
|
|
150
|
+
"setup": [],
|
|
151
|
+
|
|
152
|
+
// Commands to run in the PRIMARY checkout after a land — the counterpart of
|
|
153
|
+
// `setup`, for whatever landed code needs before the base branch is usable
|
|
154
|
+
// locally (e.g. ["pnpm db:data-migration"] for imperative data migrations
|
|
155
|
+
// your deploy runs and nothing runs on your dev DB). An array, run in order;
|
|
156
|
+
// [] (or absent) = none, and every plan prints exactly what it always did.
|
|
157
|
+
// `spec-env integrate` prints them after the fast-forward (worktree and
|
|
158
|
+
// checkout mode), and also after "already landed" — a land on another
|
|
159
|
+
// machine leaves them pending here. `spec-env hotfix land` prints them
|
|
160
|
+
// after a carry that moved the base branch, and not at all when it moved
|
|
161
|
+
// nothing (held, already carried, or `hotfix.cherryPickMain` false).
|
|
162
|
+
// The engine only plans: /spec-complete, /spec-to-main and /no-spec run
|
|
163
|
+
// them (a /spec documents-only land does not). They stop at the first
|
|
164
|
+
// failure, report it as a caveat, and never undo the land or block
|
|
165
|
+
// teardown. Literal strings — tokens are NOT expanded, because they run on
|
|
166
|
+
// the base branch where a spec's {slug} or {portOffset} mean nothing.
|
|
167
|
+
//
|
|
168
|
+
// CONDITIONAL ENTRIES. A plain string always runs. An entry may instead be
|
|
169
|
+
// { "run": "<cmd>", "when": ["<glob>", ...] } (a single string `when` is
|
|
170
|
+
// fine), which runs only when the land changes a file one of its globs
|
|
171
|
+
// matches — so a generate or an install costs nothing on a land that does
|
|
172
|
+
// not need it, and is not forgotten on one that does:
|
|
173
|
+
//
|
|
174
|
+
// "afterLand": [
|
|
175
|
+
// { "run": "pnpm install --frozen-lockfile", "when": ["pnpm-lock.yaml", "**/package.json"] },
|
|
176
|
+
// { "run": "pnpm run db:generate", "when": ["prisma/schema.prisma"] },
|
|
177
|
+
// "pnpm db:data-migration"
|
|
178
|
+
// ]
|
|
179
|
+
//
|
|
180
|
+
// Globs are repo-relative: `*` stays within a directory, `**` crosses them
|
|
181
|
+
// (`**/package.json` matches the root one too), `?` is one character. An
|
|
182
|
+
// entry whose `when` holds no usable glob is DROPPED, never widened to
|
|
183
|
+
// always-run.
|
|
184
|
+
// The changed files are what the land brings into the base branch:
|
|
185
|
+
// `base...branch` for `integrate` (read before the rebase, so it is the
|
|
186
|
+
// branch's own changes), and the carried fix commits — minus the release's
|
|
187
|
+
// version commit — for `hotfix land`.
|
|
188
|
+
// When that cannot be known, the conditional entries RUN. That is the
|
|
189
|
+
// "already landed" plan (the land happened elsewhere, so its range is not
|
|
190
|
+
// here) and any diff git would not answer. A redundant generate costs
|
|
191
|
+
// seconds; a skipped one leaves the base red with nothing pointing at why.
|
|
192
|
+
// A land that changed no files is known, not unknown, and skips them.
|
|
193
|
+
// The plan says why for every entry: a matched one ends in
|
|
194
|
+
// `# <path> changed`, an unknown one in `# range unknown` with a line saying
|
|
195
|
+
// so, and the rest are listed under `skipped (nothing in the land matched):`
|
|
196
|
+
// with their globs. The reason is a shell comment, so a line run as printed
|
|
197
|
+
// stays one command. Skipped lines are information — run only the list
|
|
198
|
+
// under `then, … run`.
|
|
199
|
+
//
|
|
200
|
+
// REUSING A SETUP STEP. `{ "setup": true }` expands, in place, to every
|
|
201
|
+
// `setup` entry that declares `when`, so the trigger lives in one list:
|
|
202
|
+
//
|
|
203
|
+
// "setup": [
|
|
204
|
+
// { "run": "pnpm install --frozen-lockfile", "when": ["pnpm-lock.yaml", "**/package.json"] },
|
|
205
|
+
// { "run": "pnpm run db:generate", "when": ["prisma/schema.prisma"] }
|
|
206
|
+
// ],
|
|
207
|
+
// "afterLand": [{ "setup": true }, "pnpm db:data-migration"]
|
|
208
|
+
//
|
|
209
|
+
// Plain setup strings carry no trigger and are never pulled in. The command
|
|
210
|
+
// is copied literally — afterLand expands no tokens — so a setup step that
|
|
211
|
+
// relies on {slug}-style tokens is a poor one to reuse.
|
|
212
|
+
"afterLand": [],
|
|
213
|
+
|
|
214
|
+
// Host dev servers `spec-env dev up <spec>` starts on the spec's port block
|
|
215
|
+
// (for apps that run via `pnpm dev` on the host, not inside the Docker stack).
|
|
216
|
+
// An array so UI + API (or more) are supervised independently; [] = none.
|
|
217
|
+
// Each entry:
|
|
218
|
+
// name label used in logs/pid files (.spec-env/{logs,pids}/<spec>-<name>).
|
|
219
|
+
// command the launch command, run detached in the worktree. {portVar} and
|
|
220
|
+
// {port} expand to the process's resolved port.
|
|
221
|
+
// portVar env var the command reads for its port; injected into its env.
|
|
222
|
+
// Process i in the block gets portBase + slot*portsPerSpec + i.
|
|
223
|
+
// health optional URL polled until it answers before reporting ready;
|
|
224
|
+
// {portVar}/{port} expand here too. Omit for no health gate.
|
|
225
|
+
// frontPort optional canonical origin this process fronts (e.g. 3000 for the
|
|
226
|
+
// UI, 8080 for the API) — used by the proxy to route to it.
|
|
227
|
+
"dev": [],
|
|
228
|
+
|
|
229
|
+
// Front-door proxy for `spec-env connect <spec>` — a small bundled Node
|
|
230
|
+
// reverse proxy (no external install) that exposes ONE connected spec's
|
|
231
|
+
// frontPort dev servers on the canonical ports. Exclusive: one target at a
|
|
232
|
+
// time. `connect main` stops it so the primary checkout owns the ports again.
|
|
233
|
+
"proxy": {
|
|
234
|
+
"enabled": true, // false = the connect command is unavailable
|
|
235
|
+
"host": "127.0.0.1" // bind host for the canonical ports
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Machine-local slot registry (spec → slot index). Resolved against the
|
|
239
|
+
// primary checkout root, shared by all worktrees, gitignored.
|
|
240
|
+
"registry": ".spec-env/registry.json",
|
|
241
|
+
|
|
242
|
+
// Git branch naming, provider-neutral. `pattern` expands {type} and {slug}
|
|
243
|
+
// (e.g. "feat/add-widget"). When a ticketing provider is linked and you want
|
|
244
|
+
// tracker ids in branch names, use {identifier} in the pattern and point
|
|
245
|
+
// `identifierField` at the 00-overview.md frontmatter field the provider
|
|
246
|
+
// writes the id into — pushing that branch can then fire the tracker's
|
|
247
|
+
// automation. Empty `identifierField` (or a spec missing that field) makes a
|
|
248
|
+
// pattern with {identifier} fall back to {type}/{slug}.
|
|
249
|
+
"branch": {
|
|
250
|
+
"pattern": "{type}/{slug}",
|
|
251
|
+
"identifierField": ""
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
// Paths that belong to a spec ALONGSIDE its own `specs/<bucket>/<name>/`
|
|
255
|
+
// folder. `/spec-start` uses this to tell "the spec you just wrote, not yet
|
|
256
|
+
// committed" apart from someone else's uncommitted work: if every dirty path
|
|
257
|
+
// belongs to the spec being started it is committed for you, and if a single
|
|
258
|
+
// path does not, the start is refused as before.
|
|
259
|
+
//
|
|
260
|
+
// Provider-neutral by design — the base engine must not know that any
|
|
261
|
+
// particular tracker exists — so you declare the shape here. `{slug}` and
|
|
262
|
+
// `{identifier}` expand exactly as in `branch.pattern` above, `{identifier}`
|
|
263
|
+
// via `branch.identifierField`.
|
|
264
|
+
//
|
|
265
|
+
// A pattern using {identifier} matches NOTHING when no identifier resolves
|
|
266
|
+
// (no `identifierField` set, or a spec never pushed to a tracker). That is
|
|
267
|
+
// deliberate: the file it names then belongs to some other spec, and the safe
|
|
268
|
+
// failure is a refusal you clear with /commit, not a stranger's file swept
|
|
269
|
+
// into your commit. Default: none — a spec owns only its own folder.
|
|
270
|
+
//
|
|
271
|
+
// THIS DISTRIBUTION SEEDS BOTH KEYS. `skitterspec init --isolation` writes an
|
|
272
|
+
// `env.config.json` already carrying
|
|
273
|
+
// `"companionPaths": ["specs/.core/jira-base/{identifier}.base.json"]` and
|
|
274
|
+
// `"identifierField": "jira_identifier"`, because the Jira snapshot is
|
|
275
|
+
// written by this package, and only this package knows where. What follows is
|
|
276
|
+
// the base engine's default — what you get if you clear them.
|
|
277
|
+
//
|
|
278
|
+
// Both or neither: `{identifier}` resolves through `branch.identifierField`,
|
|
279
|
+
// so `companionPaths` on its own expands to nothing and owns nothing.
|
|
280
|
+
"spec": {
|
|
281
|
+
"companionPaths": []
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// Integration base branch — the branch specs fork from and land back onto
|
|
285
|
+
// (used by the teardown "merged?" guard and, later, the integrate step).
|
|
286
|
+
// Empty = auto-detect: origin/HEAD → main → master. Set it when your default
|
|
287
|
+
// branch isn't discoverable (e.g. no remote) or differs (trunk, develop).
|
|
288
|
+
"baseBranch": "",
|
|
289
|
+
|
|
290
|
+
// Teardown safety. --force overrides both. refuseTeardownIfUnpushed only
|
|
291
|
+
// blocks when the commits are ALSO unmerged into the base branch — a branch
|
|
292
|
+
// already landed on base tears down (and its branch is deleted) without
|
|
293
|
+
// --force, even with no remote.
|
|
294
|
+
"guards": {
|
|
295
|
+
"refuseTeardownIfDirty": true,
|
|
296
|
+
"refuseTeardownIfUnpushed": true
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// What teardown cleans up beyond this machine. Nothing publishes a spec
|
|
300
|
+
// branch for you, so a remote copy exists only because you pushed it by hand —
|
|
301
|
+
// and without this a completed spec leaves that merged branch on the remote
|
|
302
|
+
// forever. `deleteRemoteBranch`:
|
|
303
|
+
// "prompt" (default) — plan `git push <remote> --delete <branch>` in its own
|
|
304
|
+
// "confirm with the user first" section; /spec-complete and
|
|
305
|
+
// /spec-cancel ask before running it.
|
|
306
|
+
// "never" — omit it; clean the remote up yourself.
|
|
307
|
+
// "always" — fold it into `run these:` and never ask.
|
|
308
|
+
// Only ever planned for a branch that has LANDED (merged into base, or captured
|
|
309
|
+
// by a hotfix's deploy tag) and whose remote-tracking ref this clone can
|
|
310
|
+
// actually see. Until a branch lands, the remote copy is its only backup, so
|
|
311
|
+
// --force does NOT enable this. A branch pushed from another machine has no
|
|
312
|
+
// local ref here and is simply missed — teardown under-cleans rather than
|
|
313
|
+
// deleting something it cannot see. An unrecognised value means "prompt".
|
|
314
|
+
"teardown": {
|
|
315
|
+
"deleteRemoteBranch": "prompt"
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// Live overlay (`spec-env live` / `/spec-live`): test a spec on the already-
|
|
319
|
+
// running dev server by checking its branch out in the primary checkout.
|
|
320
|
+
// `migrations` is a list of globs (`**`, `*`, `?`) marking migration files; a
|
|
321
|
+
// branch that changes any of them is treated as STATEFUL and `live take`
|
|
322
|
+
// refuses it (code-only v1 — use `/spec-connect` for those). Default: none.
|
|
323
|
+
"live": {
|
|
324
|
+
"migrations": []
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// Hotfix landing (`spec-env hotfix land` / `/spec-complete` on a Type: Hotfix
|
|
328
|
+
// spec). A hotfix forks from a release tag and lands by release + push + carry,
|
|
329
|
+
// not fast-forward; `hotfix land` runs those steps itself, in that order.
|
|
330
|
+
//
|
|
331
|
+
// `releaseWith` is a command run IN THE HOTFIX WORKTREE that cuts the release —
|
|
332
|
+
// its version commit and its tag — and prints ONE JSON line on stdout:
|
|
333
|
+
// {"tag","version","baseTag","branch","versionCommit","pushed"}. skittership
|
|
334
|
+
// 2.1+ ships one: "node scripts/release-hotfix.cjs --json" (call node directly
|
|
335
|
+
// rather than through `pnpm run`, so no run banner reaches stdout; anything but
|
|
336
|
+
// the JSON line is a refusal). skitterspec never passes it `--push`, since it
|
|
337
|
+
// owns the push. skittership refuses a branch outside its `hotfix.branchPrefix`
|
|
338
|
+
// (default `hotfix/`) — the default `branch.pattern` already gives
|
|
339
|
+
// `hotfix/<slug>` — and refuses a package.json with a `postversion` script
|
|
340
|
+
// unless the command says `--allow-postversion`; add that yourself if you have
|
|
341
|
+
// one. Empty (the default) = the built-in tagger.
|
|
342
|
+
//
|
|
343
|
+
// `bump` is the built-in tagger's strategy: "patch" (v33.16.4 -> v33.16.5) or
|
|
344
|
+
// "hotfix-suffix" (v33.16.4 -> v33.16.4-hotfix.1, then -hotfix.2, ...). Under
|
|
345
|
+
// either, a base that is already a hotfix tag continues its line
|
|
346
|
+
// (v36.2.3-hotfix.4 -> v36.2.3-hotfix.5), so "patch" never takes a number the
|
|
347
|
+
// mainline owns. When the patch tag already exists, the refusal names the
|
|
348
|
+
// free hotfix tag that "hotfix-suffix" would cut. Any other value is refused
|
|
349
|
+
// when `hotfix land` runs. Tags order X.Y.Z-rc.N < X.Y.Z < X.Y.Z-hotfix.N <
|
|
350
|
+
// X.Y.(Z+1), the same as skittership.
|
|
351
|
+
//
|
|
352
|
+
// `cherryPickMain` carries the fix onto the base branch for the next release
|
|
353
|
+
// (default true) — its commits minus the version commit and minus anything the
|
|
354
|
+
// base already has. `targets` is an optional default list of extra base tags to
|
|
355
|
+
// also patch (test/demo lines); `--also <tag>` adds more at run time. Targets
|
|
356
|
+
// are TAG-ONLY: they get the built-in tagger, never `releaseWith`, and are not
|
|
357
|
+
// pushed. The prod release is pushed (branch, then tag, to origin) only on
|
|
358
|
+
// `hotfix land --push`; without it the land holds before the carry. The
|
|
359
|
+
// worktree stays until `spec-env hotfix finish`. Default: built-in patch tag,
|
|
360
|
+
// carry to main, no targets.
|
|
361
|
+
"hotfix": {
|
|
362
|
+
"releaseWith": "",
|
|
363
|
+
"bump": "patch",
|
|
364
|
+
"cherryPickMain": true,
|
|
365
|
+
"targets": []
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// Reading a spec's diff (`spec-env review`, `/spec-diff`).
|
|
369
|
+
//
|
|
370
|
+
// `reader` decides how the page's LOCATION IS WORDED. It no longer decides
|
|
371
|
+
// the bind — `allowNetwork` does, see below — and it no longer decides which
|
|
372
|
+
// tiers are offered, because every tier is now listed whatever it says.
|
|
373
|
+
// WHAT THE SERVER
|
|
374
|
+
// BINDS TO — and nothing else. It never decides whether to serve (`serve`
|
|
375
|
+
// does) and never decides to PUBLISH. Three values:
|
|
376
|
+
// "local" — you are at the machine holding the page; it binds 127.0.0.1.
|
|
377
|
+
// "remote" — you are not; it binds every interface so the page opens.
|
|
378
|
+
// "detect" — work it out (the default); cannot-tell binds 127.0.0.1.
|
|
379
|
+
// An explicit "local"/"remote" is BELIEVED WITHOUT SNIFFING: you know where
|
|
380
|
+
// you are reading, and no signal outranks being told. Detection is only the
|
|
381
|
+
// default, and it has three outcomes rather than two — local, remote, and
|
|
382
|
+
// unknown. Unknown behaves exactly as the tool did before any of this existed
|
|
383
|
+
// (the file:// URL, no warning), because a wrong "local" prints a dead link
|
|
384
|
+
// and a wrong "remote" warns at someone whose link works fine. An
|
|
385
|
+
// unrecognised value falls through to "detect", so a typo cannot become a
|
|
386
|
+
// confident answer. Default: detect.
|
|
387
|
+
//
|
|
388
|
+
// `servePort` is the port for `spec-env review serve`, which renders every
|
|
389
|
+
// spec's diff per request on one local server. Two forms:
|
|
390
|
+
//
|
|
391
|
+
// "auto" (the default) — derive it from this repo's path, as
|
|
392
|
+
// 7700 + hash(realpath(repoRoot)) % 100. Two repos on one machine stop
|
|
393
|
+
// competing for one shared port without anyone configuring anything, and
|
|
394
|
+
// — the part that matters — THE SAME REPO GETS THE SAME PORT EVERY TIME.
|
|
395
|
+
// The derivation reads nothing on disk, so the port survives a restart, a
|
|
396
|
+
// reboot and a `--stop`, which is what lets a link handed out yesterday
|
|
397
|
+
// still resolve. A symlinked spelling of the tree resolves first, so one
|
|
398
|
+
// repo never lands on two ports.
|
|
399
|
+
// <a number> — pin it. An explicit number always wins, and pinning is what
|
|
400
|
+
// you do when you want a port you can memorise, or when two repos derive
|
|
401
|
+
// the same one.
|
|
402
|
+
//
|
|
403
|
+
// A derived port CAN still collide — a hundred slots is a small chance, not
|
|
404
|
+
// no chance — and the server refuses rather than moving itself aside. The fix
|
|
405
|
+
// it names is `servePort`, because that is what the next link is built from;
|
|
406
|
+
// `--port` moves one run and leaves every link already handed out pointing at
|
|
407
|
+
// the busy port. `spec-env review serve --status` prints the port and which
|
|
408
|
+
// of the three chose it.
|
|
409
|
+
//
|
|
410
|
+
// An unrecognised value falls through to "auto", like every other typed key
|
|
411
|
+
// here. Default: "auto".
|
|
412
|
+
//
|
|
413
|
+
// The server binds 127.0.0.1 unless `--host 0.0.0.0` is passed, which mints
|
|
414
|
+
// an unguessable path token and prints the LAN URL including it — anyone
|
|
415
|
+
// holding that URL can read every spec's diff while it runs.
|
|
416
|
+
//
|
|
417
|
+
// `serve` is whether a render stands the local server up at all:
|
|
418
|
+
// "always" — every render does (the default), so `local:` and `network:`
|
|
419
|
+
// are both http URLs the page can POST a verdict back to. The
|
|
420
|
+
// LAN address comes first with the rest listed under it, because
|
|
421
|
+
// the guess reads interface names and a VPN or an unusual
|
|
422
|
+
// adapter will fool it.
|
|
423
|
+
// "never" — `local:` is a file:// URL instead. Note what that costs: a
|
|
424
|
+
// file:// page has no server to POST to, so its verdict buttons
|
|
425
|
+
// copy a command for you to paste rather than sending anything.
|
|
426
|
+
// It replaced `serveOnRemote`, which gated serving on the reader — and so
|
|
427
|
+
// handed a local machine a page it could read and not answer. A legacy
|
|
428
|
+
// `serveOnRemote: false` is still read as `serve: "never"`.
|
|
429
|
+
// Either way NOTHING IS PUBLISHED on a detection: a server is one process
|
|
430
|
+
// ended by one flag, while a published page is one this tooling cannot
|
|
431
|
+
// remove, so that half stays an explicit ask. Teardown names a server that
|
|
432
|
+
// served the last spec, and `spec-env prune` reaps a pidfile whose process is
|
|
433
|
+
// gone. Default: true.
|
|
434
|
+
//
|
|
435
|
+
// `allowNetwork` and `allowRemote` decide WHICH TIERS a render offers, and
|
|
436
|
+
// they replaced the engine guessing where the reader was sitting. It guessed
|
|
437
|
+
// for a while and got it wrong three separate ways in one day: a file:// page
|
|
438
|
+
// on a session detected `unknown`, a LAN URL for a phone that had left the
|
|
439
|
+
// network, and an address that changed underneath a reader mid-session. So
|
|
440
|
+
// every tier is listed, labelled, and either a URL or the one command that
|
|
441
|
+
// turns it on — and you pick the one that reaches you.
|
|
442
|
+
//
|
|
443
|
+
// `allowNetwork` — whether the review server binds EVERY INTERFACE, so the
|
|
444
|
+
// page opens on your phone, or loopback only. THIS IS WHAT
|
|
445
|
+
// CHOOSES THE BIND; `reader` no longer does. Default: true,
|
|
446
|
+
// which matches what the engine already did.
|
|
447
|
+
// `allowRemote` — whether PUBLISHING is permitted for EVERY spec. It
|
|
448
|
+
// permits it; it publishes nothing. Default: FALSE,
|
|
449
|
+
// because a published page is one skitterspec cannot
|
|
450
|
+
// delete, so it must never happen unasked. This is the
|
|
451
|
+
// PROJECT DEFAULT: `/spec-remote-review` permits one spec
|
|
452
|
+
// at a time, in a gitignored sidecar beside its page, and
|
|
453
|
+
// never edits this file — `review allow remote --project`
|
|
454
|
+
// is what does.
|
|
455
|
+
//
|
|
456
|
+
// `local` and `network` are two doors into ONE ROOM — the page POSTs to
|
|
457
|
+
// `location.pathname`, so both reach the same server and the same waiting
|
|
458
|
+
// verdict, and one wait covers both. `remote` is a second store: a verdict
|
|
459
|
+
// pressed on a published page needs `/spec-reviewed`, because nothing pushes
|
|
460
|
+
// from an artifact's store into a conversation.
|
|
461
|
+
//
|
|
462
|
+
// Both are toggled by `spec-env review allow <tier> --set [on|off]`, where an
|
|
463
|
+
// empty value toggles — which is what `/spec-remote-review` runs. It writes
|
|
464
|
+
// THIS FILE in the primary checkout, so it changes for everyone who pulls and
|
|
465
|
+
// leaves that tree dirty; the engine says so when it does.
|
|
466
|
+
//
|
|
467
|
+
// `commitWith` names the skill a COMMITTING verdict hands off to. A review
|
|
468
|
+
// page ends in a verdict — commit, commit & continue, request changes,
|
|
469
|
+
// discuss — and the point of the first two is that the commit follows from
|
|
470
|
+
// the reading rather than costing a separate decision. Skitterspec never
|
|
471
|
+
// commits through a skill it vendored: `/commit` ships with skittership, a
|
|
472
|
+
// different package, and a copy living here would fork it. Two shapes:
|
|
473
|
+
// "/commit" — the default; hand off to skittership's commit skill.
|
|
474
|
+
// "<name>" — any other skill your project installs.
|
|
475
|
+
// With the named skill unavailable, the committing branch commits directly —
|
|
476
|
+
// stage, typecheck, test, conventional message — and SAYS it took that path,
|
|
477
|
+
// because a commit made under rules nobody configured must not read as one
|
|
478
|
+
// made under /commit.
|
|
479
|
+
//
|
|
480
|
+
// THERE IS NO OFF SWITCH. `"none"` existed and was removed: it produced a
|
|
481
|
+
// verdict that records itself and does nothing, which is the one thing a
|
|
482
|
+
// review page must not offer — a review is the guard in front of an action.
|
|
483
|
+
// Recording an approval for SOMEONE ELSE to act on is a separate mechanism,
|
|
484
|
+
// not a value of this key. Default: "/commit".
|
|
485
|
+
//
|
|
486
|
+
// `required` decides whether a phase that has ended owes a verdict before its
|
|
487
|
+
// work is committed or the next phase is built. The gate is armed when a
|
|
488
|
+
// phase's page is rendered and cleared by exactly two things: a COMMITTING
|
|
489
|
+
// verdict, or `spec-env review skip "<reason>"` — allowed, and on the record,
|
|
490
|
+
// which is the whole difference between skipping and drifting.
|
|
491
|
+
// true — the default. A review is the normal exit from a phase.
|
|
492
|
+
// false — nothing is ever owed; `review gate` answers "cannot tell" and
|
|
493
|
+
// the commit hook (if installed) defers to it, so this one key
|
|
494
|
+
// turns the whole thing off.
|
|
495
|
+
// Only a literal `false` opts out: a typo leaves a check that REFUSES in
|
|
496
|
+
// place rather than quietly disabling it. Default: true.
|
|
497
|
+
//
|
|
498
|
+
// `reviewers` lists EXTERNAL CODE REVIEWERS whose findings render on the page
|
|
499
|
+
// as CHECKS, beside (or instead of) a written review. Empty by default, and
|
|
500
|
+
// `init` never writes one.
|
|
501
|
+
//
|
|
502
|
+
// READ THIS BEFORE ADDING ONE. A reviewer is a command run in the spec's
|
|
503
|
+
// worktree, and every hosted one sends that worktree's diff to a third
|
|
504
|
+
// party. Nothing else in skitterspec leaves your machine — the page is a
|
|
505
|
+
// local file, the server binds your own network, and the diff never reaches
|
|
506
|
+
// a model. Configuring a reviewer changes that, deliberately and only
|
|
507
|
+
// because you asked. Every finding on the page is badged with the reviewer
|
|
508
|
+
// that produced it, so it stays visible where the code went.
|
|
509
|
+
//
|
|
510
|
+
// Two entry shapes, told apart by which key is present:
|
|
511
|
+
// { "use": "<adapter>" }
|
|
512
|
+
// A bundled adapter. The engine owns its command line and its parser,
|
|
513
|
+
// so this is the whole configuration. One ships today: "coderabbit",
|
|
514
|
+
// which drives `cr review --agent` (a free tier, rate-limited per
|
|
515
|
+
// hour; `cr auth login` first). A `use` this build does not have is
|
|
516
|
+
// reported on stderr and does not run.
|
|
517
|
+
// Its adapter also does the one thing a shell script cannot: it tells
|
|
518
|
+
// ITS OWN refusals — not authenticated, rate limited, no network —
|
|
519
|
+
// apart from a review that ran and found nothing.
|
|
520
|
+
// { "name": "<label>", "command": "<shell>", "format": "rdjsonl" }
|
|
521
|
+
// Bring your own. `name` is what the page badges findings with, and
|
|
522
|
+
// `command` is a shell line run with the worktree as its cwd.
|
|
523
|
+
// Both take an optional `timeout` in seconds (default 180).
|
|
524
|
+
//
|
|
525
|
+
// `format` is the output contract, and today there is one: `rdjsonl` — one
|
|
526
|
+
// JSON object per line, reviewdog's diagnostic shape:
|
|
527
|
+
// {"path":"src/a.js","range":{"start":{"line":12}},"severity":"ERROR","message":"…"}
|
|
528
|
+
// A flat `"line": 12` is read too. Most linters already emit this, so a
|
|
529
|
+
// project gets non-AI checks on the same page for free, and an adapter for
|
|
530
|
+
// anything else is a twenty-line script that prints rdjsonl.
|
|
531
|
+
//
|
|
532
|
+
// `command` may carry `${scope}` (`working` | `branch`), `${base}`,
|
|
533
|
+
// `${spec}` and `${worktree}`. `scope` matches the render's own two modes —
|
|
534
|
+
// a phase-end render is `working` (uncommitted + untracked in the worktree)
|
|
535
|
+
// and `--branch` is the whole spec against its base — so what the page shows
|
|
536
|
+
// is what was reviewed. An unknown `${…}` is left verbatim rather than
|
|
537
|
+
// blanked, so a typo shows up in the command that failed instead of silently
|
|
538
|
+
// becoming an empty argument.
|
|
539
|
+
//
|
|
540
|
+
// `severity` maps to the page's levels: `ERROR` → `flag`, everything else →
|
|
541
|
+
// `confirm`. Never `good` — that means "I read this and it is right", which
|
|
542
|
+
// is a thing a person says.
|
|
543
|
+
//
|
|
544
|
+
// FINDINGS NEVER GATE A COMMIT. They are checks, and `judgeVerdict` refuses a
|
|
545
|
+
// committing verdict only while a COMMENT is unresolved. A comment is
|
|
546
|
+
// something a person asked for; twelve machine findings are not. Reply to one
|
|
547
|
+
// on the page and that reply IS a comment — which does gate, because now
|
|
548
|
+
// someone asked. There is deliberately no severity threshold that blocks:
|
|
549
|
+
// that is the counting gate this whole design exists against, and it makes a
|
|
550
|
+
// reviewer's bad day into a wall.
|
|
551
|
+
//
|
|
552
|
+
// A REVIEWER THAT COULD NOT RUN SAYS SO, rather than staying silent — the one
|
|
553
|
+
// place `.claude/rules/negative-checks.md` inverts. Silence is normally the
|
|
554
|
+
// safe branch; here a rate-limited or unauthenticated reviewer would render
|
|
555
|
+
// identically to one that read the diff and found nothing, on the page a
|
|
556
|
+
// commit decision is made from. So every configured reviewer gets a line:
|
|
557
|
+
// `12 findings` · `clean` · `cached` · `did not run — <why>`. Nothing exits
|
|
558
|
+
// non-zero and nothing refuses — a reviewer that could not run never blocks a
|
|
559
|
+
// render, a verdict or a commit.
|
|
560
|
+
//
|
|
561
|
+
// They run in SEQUENCE, at the end of a phase and on demand (`/spec-diff
|
|
562
|
+
// --reviewers`), and the results are cached against a hash of the diff — so
|
|
563
|
+
// re-rendering unchanged work reuses them rather than spending another review
|
|
564
|
+
// against an hourly limit. An entry that is neither shape is dropped and
|
|
565
|
+
// REPORTED on stderr; it cannot be caught by the unknown-key check, because
|
|
566
|
+
// this default is an array. Default: [].
|
|
567
|
+
"review": {
|
|
568
|
+
"reader": "detect",
|
|
569
|
+
"servePort": "auto",
|
|
570
|
+
"serve": "always",
|
|
571
|
+
"commitWith": "/commit",
|
|
572
|
+
"required": true,
|
|
573
|
+
"reviewers": []
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Token expansion
|
|
579
|
+
|
|
580
|
+
- `{repo}` — primary checkout dir basename (e.g. `skitterspec`).
|
|
581
|
+
- `{repoSlug}` — `{repo}` lower-cased, non-alphanumerics collapsed to `-`
|
|
582
|
+
(safe for a `COMPOSE_PROJECT_NAME`).
|
|
583
|
+
- `{slug}` — the spec slug (folder name minus its `feat-`/`bug-` prefix).
|
|
584
|
+
|
|
585
|
+
## Pruning orphaned test-DB volumes
|
|
586
|
+
|
|
587
|
+
`spec-env down` drops the finished spec's own Docker volume, but volumes leak
|
|
588
|
+
when that path is skipped — a declined/guard-aborted teardown, a manual
|
|
589
|
+
`git worktree remove`, or `--keep-volumes`. Over many worktrees these orphaned
|
|
590
|
+
DB volumes pile up and eat disk.
|
|
591
|
+
|
|
592
|
+
`skitterspec spec-env prune` reconciles the live volumes in the
|
|
593
|
+
`{repoSlug}_*` namespace against the specs that still have a **worktree** and
|
|
594
|
+
lists the orphans (volumes owned by no live spec) plus the `docker volume rm`
|
|
595
|
+
commands to remove them. It plans only — you run the printed commands — and it
|
|
596
|
+
frees any stale registry slot for a reaped spec.
|
|
597
|
+
|
|
598
|
+
- **Liveness = an existing worktree, not the registry** (the registry is what
|
|
599
|
+
goes stale). A spec with a live worktree — in any bucket, including one checked
|
|
600
|
+
out only on its branch — is always protected.
|
|
601
|
+
- **No backup.** Unlike `spec-env down`, prune does **not** run
|
|
602
|
+
`backupCommand`: an orphan has no running DB to dump. Add `--older-than <days>`
|
|
603
|
+
to only reap volumes older than a cutoff (volumes of unknown age are kept).
|
|
604
|
+
- `/spec-complete` and `/spec-cancel` run prune (confirm-first) as their last
|
|
605
|
+
teardown step, so orphans get swept as specs finish. You can also run it by
|
|
606
|
+
hand at any time to clear an existing backlog.
|