@skitterbyte/skitterspec 15.0.5 → 16.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/assets/rules/spec-planning.md +3 -0
- package/assets/skills/spec/SKILL.md +5 -0
- package/assets/skills/spec-bug/SKILL.md +3 -0
- package/assets/skills/spec-cancel/SKILL.md +18 -3
- package/assets/skills/spec-complete/SKILL.md +26 -8
- package/assets/skills/spec-go/SKILL.md +15 -5
- package/assets/skills/spec-hotfix/SKILL.md +1 -0
- package/package.json +1 -1
- package/src/cli.js +18 -6
- package/src/env/provision.js +39 -3
- package/src/env/resolve.js +1 -0
|
@@ -113,6 +113,9 @@ status.
|
|
|
113
113
|
|
|
114
114
|
Every spec header carries:
|
|
115
115
|
|
|
116
|
+
- `> **Name:**` — the spec's folder name (`feat-`/`bug-`/`hotfix-<kebab-name>`).
|
|
117
|
+
It's the handle you pass to `/spec-go` and the other lifecycle skills, surfaced
|
|
118
|
+
in the header so it's copy-pasteable without digging for the folder name.
|
|
116
119
|
- `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
|
|
117
120
|
to `git config user.name`).
|
|
118
121
|
- `> **Developer:**` — who implements it (`—` until `/spec-go` starts work, then
|
|
@@ -14,6 +14,8 @@ Lifecycle (the governing skills) — status in parentheses:
|
|
|
14
14
|
`/spec-cancel` (Cancelled). See `.claude/rules/spec-planning.md`. (There is no
|
|
15
15
|
separate grooming command — `/spec` grills to a Ready spec directly.)
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
|
|
17
19
|
## Phase A — reach a clear shared understanding (grill first)
|
|
18
20
|
|
|
19
21
|
Interview the user until requirement AND proposed solution are unambiguous. Do
|
|
@@ -73,6 +75,7 @@ the codebase, link rather than duplicate):
|
|
|
73
75
|
# <Feature title>
|
|
74
76
|
|
|
75
77
|
> **Type:** Feature
|
|
78
|
+
> **Name:** feat-<kebab-name> (the spec folder name — the handle you paste into `/spec-go`)
|
|
76
79
|
> **Status:** Ready — not started
|
|
77
80
|
> **Author:** <git user.name — `git config user.name`>
|
|
78
81
|
> **Developer:** —
|
|
@@ -225,3 +228,5 @@ while the repo stays the source of truth — follow the provider's link steps
|
|
|
225
228
|
below (nothing to do here without one).
|
|
226
229
|
|
|
227
230
|
|
|
231
|
+
|
|
232
|
+
|
|
@@ -13,6 +13,8 @@ Spec type convention (see `.claude/rules/spec-planning.md`):
|
|
|
13
13
|
- Bug specs are named `bug-<kebab-name>`; feature specs `feat-<kebab-name>`.
|
|
14
14
|
- Every spec header carries `> **Type:** Bug` (or `Feature`).
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
|
|
16
18
|
## 1. Reproduce & isolate (light investigation)
|
|
17
19
|
|
|
18
20
|
Bugs are concrete — confirm, don't over-grill. Establish:
|
|
@@ -95,6 +97,7 @@ because work is already underway. Keep it lean:
|
|
|
95
97
|
# Bug: <short title>
|
|
96
98
|
|
|
97
99
|
> **Type:** Bug
|
|
100
|
+
> **Name:** bug-<kebab-name> (the spec folder name — the handle you paste into `/spec-go`)
|
|
98
101
|
> **Status:** In Progress — fixing (red test added)
|
|
99
102
|
> **Author:** <git user.name — who reported/captured it>
|
|
100
103
|
> **Developer:** <git user.name — you, since you're fixing it now>
|
|
@@ -25,6 +25,10 @@ it verbatim/condensed for the header.
|
|
|
25
25
|
tick anything that was actually completed before cancelling so the record is
|
|
26
26
|
honest about what landed.
|
|
27
27
|
- Note any partial/abandoned work so it isn't mistaken for unstarted.
|
|
28
|
+
- **Check for pre-existing uncommitted changes — before you touch anything.**
|
|
29
|
+
Run `git status`. Anything already uncommitted is the *user's* work and must not
|
|
30
|
+
be swept into the cancellation commit: offer `/commit` and **stop**. Everything
|
|
31
|
+
this skill writes in steps 4–5 is its own, and step 5 commits that.
|
|
28
32
|
|
|
29
33
|
## 4. Stamp the spec
|
|
30
34
|
|
|
@@ -41,16 +45,27 @@ Append a **State log** row:
|
|
|
41
45
|
Add a **Changelog** entry:
|
|
42
46
|
`- <YYYY-MM-DD> — Cancelled: <reason>.`
|
|
43
47
|
|
|
44
|
-
## 5. Move to cancelled
|
|
48
|
+
## 5. Move to cancelled — and commit it
|
|
45
49
|
|
|
46
50
|
`mkdir -p specs/cancelled` then **`git mv`** the file or folder:
|
|
47
51
|
`git mv "specs/<bucket>/<name>" "specs/cancelled/<name>"` (preserve history;
|
|
48
52
|
move the whole folder).
|
|
49
53
|
|
|
54
|
+
Then **commit the cancellation edits** — this skill wrote them, so it commits
|
|
55
|
+
them:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
git add specs/ && git commit -m "chore(spec): cancel <name>"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**This matters more here than anywhere else.** Teardown (step 7) refuses a dirty
|
|
62
|
+
worktree and offers `--force` as the way through — and forcing would destroy the
|
|
63
|
+
cancellation record this skill just wrote. Committing first means teardown never
|
|
64
|
+
needs `--force`. Do not `git push`.
|
|
65
|
+
|
|
50
66
|
## 6. Report
|
|
51
67
|
|
|
52
|
-
Confirm the cancellation, the reason recorded,
|
|
53
|
-
`git commit` unless the user asks.
|
|
68
|
+
Confirm the cancellation, the reason recorded, the new location, and the commit.
|
|
54
69
|
|
|
55
70
|
## 7. Tear down the environment (opt-in, only if configured)
|
|
56
71
|
|
|
@@ -27,6 +27,12 @@ Before marking complete, confirm the work is actually finished:
|
|
|
27
27
|
- If genuinely incomplete work remains, **stop and tell the user** rather than
|
|
28
28
|
forcing completion. Offer to finish it (`/spec-go`) or to complete with the
|
|
29
29
|
remaining items explicitly listed as deferred.
|
|
30
|
+
- **Check for pre-existing uncommitted changes — before you touch anything.**
|
|
31
|
+
Run `git status`. Anything already uncommitted is the *user's* work (a
|
|
32
|
+
half-finished phase, a stray fix) and must not be swept into the completion
|
|
33
|
+
commit: offer `/commit` and **stop**. Everything this skill writes in steps 3–4
|
|
34
|
+
is its own, and step 4 commits that.
|
|
35
|
+
|
|
30
36
|
|
|
31
37
|
## 3. Update the spec
|
|
32
38
|
|
|
@@ -40,17 +46,28 @@ Before marking complete, confirm the work is actually finished:
|
|
|
40
46
|
`- <YYYY-MM-DD> — Completed; all phases done, tests green.`
|
|
41
47
|
(Note any consciously-deferred items here too.)
|
|
42
48
|
|
|
43
|
-
## 4. Move to complete
|
|
49
|
+
## 4. Move to complete — and commit it
|
|
44
50
|
|
|
45
51
|
`mkdir -p specs/complete` then **`git mv`** the file or folder:
|
|
46
52
|
`git mv "specs/in-progress/<name>" "specs/complete/<name>"` (preserve history;
|
|
47
53
|
move the whole folder). The `specs/complete/` folder is the record of finished
|
|
48
54
|
specs — `git log`/the per-spec State log give the completion order.
|
|
49
55
|
|
|
56
|
+
Then **commit the completion edits** — steps 3–4 are this skill's own output, so
|
|
57
|
+
it finishes its own work rather than handing you a dirty tree:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
git add specs/ && git commit -m "chore(spec): complete <name>"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Step 2 established the tree was otherwise clean, so this commits exactly the
|
|
64
|
+
status flip and the move — nothing of yours rides along. **This is what lets step
|
|
65
|
+
6 land:** `integrate` refuses a dirty worktree, so without committing here the
|
|
66
|
+
skill would block on the very edits it just made. Do not `git push`.
|
|
67
|
+
|
|
50
68
|
## 5. Report
|
|
51
69
|
|
|
52
|
-
Confirm the move, the final test result, and list anything deferred.
|
|
53
|
-
`git commit` unless the user asks.
|
|
70
|
+
Confirm the move, the commit, the final test result, and list anything deferred.
|
|
54
71
|
|
|
55
72
|
## 6. Land the branch (opt-in, only if isolated)
|
|
56
73
|
|
|
@@ -65,8 +82,9 @@ destination in one flow. **How it lands depends on the spec type:**
|
|
|
65
82
|
A hotfix is built on an old release **tag**, so it can't fast-forward onto `main`.
|
|
66
83
|
Use the hotfix landing instead of the integrate steps below:
|
|
67
84
|
|
|
68
|
-
1. **Require a clean worktree
|
|
69
|
-
|
|
85
|
+
1. **Require a clean worktree.** Step 4 already committed the completion edits, so
|
|
86
|
+
this should pass. If the tree is *still* dirty, that's unrelated work — offer
|
|
87
|
+
`/commit` and **stop**.
|
|
70
88
|
2. **Plan + execute.** Run `skitterspec spec-env hotfix land <name>` — add
|
|
71
89
|
`--also <tag>` for each extra release line to patch (test/demo on their own
|
|
72
90
|
versions). Run the printed commands **in order**. It:
|
|
@@ -92,9 +110,9 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
|
|
|
92
110
|
**`/spec-to-main`**: same rebase + fast-forward, but it leaves the spec
|
|
93
111
|
`In Progress` and the worktree standing, and it's repeatable.)
|
|
94
112
|
|
|
95
|
-
1. **Require a clean worktree.**
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
1. **Require a clean worktree.** Step 4 already committed the completion edits, so
|
|
114
|
+
integrate's dirty-tree guard should pass. If the tree is *still* dirty, that's
|
|
115
|
+
unrelated work — offer `/commit` and **stop**; don't sweep it in.
|
|
98
116
|
**If the spec is live** (you took the running instance with `/spec-live`):
|
|
99
117
|
`integrate` is live-aware — it ends the live session first (releases the branch
|
|
100
118
|
back to base, re-isolates it into its worktree, clears the receipt), then prints
|
|
@@ -47,16 +47,26 @@ same "in place otherwise" path used when isolation is off. Warn that the work
|
|
|
47
47
|
will land wherever you currently are (usually `main`); reserve it for a trivial
|
|
48
48
|
change or an explicit request.
|
|
49
49
|
|
|
50
|
-
- Run `skitterspec spec-env up <name>` (the `spec-env` CLI engine). It
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
- Run `skitterspec spec-env up <name>` (the `spec-env` CLI engine). It is a
|
|
51
|
+
**planner: it prints commands and creates nothing itself.** Under
|
|
52
|
+
`to provision, run:` it emits the `git worktree add` on a branch forked from
|
|
53
|
+
`main` and — only when the spec's `> **Stack:**` header is
|
|
54
|
+
`worktree + docker` — the Docker bring-up.
|
|
55
|
+
**Run those commands and confirm they succeeded** before anything below: every
|
|
56
|
+
later step assumes the worktree exists, and the header line says
|
|
57
|
+
`(plan — nothing created yet)` precisely because at that point it doesn't.
|
|
53
58
|
Print the worktree path and the opener command it emits.
|
|
54
59
|
- **Bootstrap the worktree's dependencies.** A fresh worktree has an empty
|
|
55
60
|
working tree — no installed dependencies, and none of the repo's gitignored
|
|
56
61
|
files (`.env`, local secret/config overrides) — so git hooks, typechecks,
|
|
57
62
|
builds and tests fail until they're in place. `spec-env up` prints the
|
|
58
|
-
project's configured **`in the worktree, run:`** commands — run them in
|
|
59
|
-
|
|
63
|
+
project's configured **`then, in the worktree, run:`** commands — run them in
|
|
64
|
+
order, before doing anything else. Each one begins by `cd`-ing into the
|
|
65
|
+
worktree, so it works from any cwd and cannot quietly act on the main
|
|
66
|
+
checkout; if the worktree is missing it prints
|
|
67
|
+
**`no worktree at … — run the provisioning commands first`** and exits
|
|
68
|
+
non-zero. Seeing that means the `git worktree add` above didn't run or didn't
|
|
69
|
+
work — fix that before going on. Those commands are: first any
|
|
60
70
|
**file seeding** (from `env.config.json` → `seedFiles`), which symlinks or
|
|
61
71
|
copies the configured gitignored files from the main checkout into the fresh
|
|
62
72
|
worktree so setup can rely on them; then the **`setup`** commands (e.g. an
|
|
@@ -89,6 +89,7 @@ is usually a single-pass fix, so the `## Fix` block can live directly in
|
|
|
89
89
|
# Hotfix: <short title>
|
|
90
90
|
|
|
91
91
|
> **Type:** Hotfix
|
|
92
|
+
> **Name:** hotfix-<kebab-name> (the spec folder name — the handle you paste into `/spec-go`)
|
|
92
93
|
> **Status:** In Progress — fixing (red test added)
|
|
93
94
|
> **Author:** <git user.name — who reported/captured it>
|
|
94
95
|
> **Developer:** <git user.name — you, since you're fixing it now>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.0",
|
|
4
4
|
"description": "Spec-driven development for Claude Code — a tracker-free filesystem workflow: lifecycle skills and per-spec isolation. For Linear sync, install @skitterbyte/skitterspec-linear instead.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/src/cli.js
CHANGED
|
@@ -60,7 +60,8 @@ Usage:
|
|
|
60
60
|
.core config alone.
|
|
61
61
|
skitterspec spec-env <cmd> Per-spec isolation engine (opt-in; needs
|
|
62
62
|
specs/.core/env.config.json). Subcommands:
|
|
63
|
-
up <spec> plan a worktree +
|
|
63
|
+
up <spec> print the plan to provision a worktree +
|
|
64
|
+
Docker stack (prints commands; creates nothing)
|
|
64
65
|
down <spec> tear down (guards; --keep-volumes, --force)
|
|
65
66
|
prune reap orphaned test-DB volumes (--older-than <days>)
|
|
66
67
|
dev up <spec> start host dev servers on the spec's ports
|
|
@@ -176,8 +177,10 @@ function specEnvStatus(dir, config) {
|
|
|
176
177
|
})
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
//
|
|
180
|
-
// /spec-env skill executes (git worktree add, docker compose up, .env,
|
|
180
|
+
// Plan a provision: allocate the slot, persist the registry, and print the plan
|
|
181
|
+
// the /spec-env skill executes (git worktree add, docker compose up, .env,
|
|
182
|
+
// opener). This creates no worktree and starts no stack — the caller runs the
|
|
183
|
+
// printed commands. Keep the output's verb honest about that.
|
|
181
184
|
function specEnvUp(dir, config, specArg) {
|
|
182
185
|
if (!specArg) {
|
|
183
186
|
process.stdout.write('Usage: skitterspec spec-env up <spec>\n')
|
|
@@ -224,7 +227,16 @@ function specEnvUp(dir, config, specArg) {
|
|
|
224
227
|
const plan = planUp(spec, { slot, attached }, config)
|
|
225
228
|
|
|
226
229
|
const out = []
|
|
227
|
-
|
|
230
|
+
// `up` is a planner: it prints commands for the caller to run and creates no
|
|
231
|
+
// worktree or stack itself (the registry slot and the trust entry, both reported
|
|
232
|
+
// separately below, are its only writes). Say so in the verb — a past-tense
|
|
233
|
+
// "(provisioned)" reads as a completed state change, and a caller that believes
|
|
234
|
+
// it skips the commands and works on `main`, which is what isolation exists to
|
|
235
|
+
// prevent.
|
|
236
|
+
out.push(
|
|
237
|
+
`spec-env up: ${spec.folder} ` +
|
|
238
|
+
(attached ? '(plan — worktree exists; will attach)' : '(plan — nothing created yet)'),
|
|
239
|
+
)
|
|
228
240
|
out.push('')
|
|
229
241
|
out.push(` worktree: ${plan.worktreePath}`)
|
|
230
242
|
out.push(` branch: ${plan.branch}`)
|
|
@@ -247,7 +259,7 @@ function specEnvUp(dir, config, specArg) {
|
|
|
247
259
|
)
|
|
248
260
|
}
|
|
249
261
|
out.push('')
|
|
250
|
-
out.push(' run
|
|
262
|
+
out.push(' to provision, run:')
|
|
251
263
|
for (const cmd of plan.commands) out.push(` ${cmd}`)
|
|
252
264
|
if (plan.openCommand) out.push(` ${plan.openCommand}`)
|
|
253
265
|
// Seed files first (setup may depend on them), then the setup commands —
|
|
@@ -255,7 +267,7 @@ function specEnvUp(dir, config, specArg) {
|
|
|
255
267
|
const worktreeSteps = [...plan.seedCommands, ...plan.setupCommands]
|
|
256
268
|
if (worktreeSteps.length) {
|
|
257
269
|
out.push('')
|
|
258
|
-
out.push(' in the worktree, run:')
|
|
270
|
+
out.push(' then, in the worktree, run:')
|
|
259
271
|
for (const cmd of worktreeSteps) out.push(` ${cmd}`)
|
|
260
272
|
}
|
|
261
273
|
if (plan.envContents) {
|
package/src/env/provision.js
CHANGED
|
@@ -15,6 +15,37 @@ const { portOffset } = require('./registry.js')
|
|
|
15
15
|
const { renderEnvFile, expandOpenCommand } = require('./render.js')
|
|
16
16
|
const { expandTokens } = require('./resolve.js')
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Build the POSIX-sh prefix that puts a command in the spec's worktree, or stops.
|
|
20
|
+
*
|
|
21
|
+
* Every command under the "then, in the worktree, run:" heading carries this.
|
|
22
|
+
* Without it those commands stay silent when run from the wrong place: in the
|
|
23
|
+
* primary checkout `$m` resolves to the checkout itself, so a seed's source and
|
|
24
|
+
* target are the same path and it prints `exists — skipped` — indistinguishable
|
|
25
|
+
* from a correctly-provisioned re-run — while a setup command like an install
|
|
26
|
+
* runs against the main checkout and "succeeds" too. Three layers then agree that
|
|
27
|
+
* nothing is wrong, and the caller carries on committing to `main`.
|
|
28
|
+
*
|
|
29
|
+
* A `cd` is deliberately chosen over comparing `git rev-parse --show-toplevel`
|
|
30
|
+
* against `worktreePath`: the planned path is a lexical `path.resolve` while git
|
|
31
|
+
* reports the symlink-resolved one (`/tmp` vs `/private/tmp` on macOS), so a
|
|
32
|
+
* string compare would refuse a perfectly good worktree. `cd` sidesteps that, and
|
|
33
|
+
* does better than refusing — it *positions* the command, so a caller who is in
|
|
34
|
+
* the wrong directory still gets correct behaviour. When the worktree was never
|
|
35
|
+
* created — the reported case — the `cd` fails and takes the whole command with
|
|
36
|
+
* it, non-zero and loud.
|
|
37
|
+
*
|
|
38
|
+
* Per-command, not once at the top of the block: a single leading `cd` only
|
|
39
|
+
* protects the sequence if the caller chains it with `&&`, and the reported
|
|
40
|
+
* failure is precisely a caller that ran the steps as separate statements.
|
|
41
|
+
*/
|
|
42
|
+
function worktreeCd(worktreePath) {
|
|
43
|
+
return (
|
|
44
|
+
`cd "${worktreePath}" 2>/dev/null || ` +
|
|
45
|
+
`{ echo "no worktree at ${worktreePath} — run the provisioning commands first"; exit 1; }`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
/**
|
|
19
50
|
* Build one idempotent POSIX-sh command that seeds a gitignored file from the
|
|
20
51
|
* main checkout into the current worktree (the cwd when the skill runs it).
|
|
@@ -84,13 +115,18 @@ function planUp(spec, alloc, config) {
|
|
|
84
115
|
// config ⇒ no commands ⇒ current behaviour.
|
|
85
116
|
const seed = config.seedFiles || { mode: 'symlink', files: [] }
|
|
86
117
|
const seedMode = seed.mode === 'copy' ? 'copy' : 'symlink'
|
|
87
|
-
const
|
|
118
|
+
const guard = worktreeCd(spec.worktreePath)
|
|
119
|
+
const seedCommands = (seed.files || []).map(
|
|
120
|
+
(file) => `${guard}; ${seedCommandFor(file, seedMode)}`,
|
|
121
|
+
)
|
|
88
122
|
|
|
89
123
|
// Bootstrap commands run *in the worktree* after `git worktree add` (before
|
|
90
124
|
// Docker/dev), on every provision including re-attach — deps must exist for
|
|
91
125
|
// the worktree to be usable. Kept separate from `commands` (run from the
|
|
92
126
|
// primary checkout root); the CLI prints them under an "in the worktree" head.
|
|
93
|
-
const setupCommands = (config.setup || []).map(
|
|
127
|
+
const setupCommands = (config.setup || []).map(
|
|
128
|
+
(cmd) => `${guard}; ${expandTokens(cmd, tokens)}`,
|
|
129
|
+
)
|
|
94
130
|
|
|
95
131
|
const commands = []
|
|
96
132
|
// Fresh branch → -b; attach an existing branch/slot → plain form (never clobber).
|
|
@@ -122,4 +158,4 @@ function planUp(spec, alloc, config) {
|
|
|
122
158
|
}
|
|
123
159
|
}
|
|
124
160
|
|
|
125
|
-
module.exports = { planUp, seedCommandFor }
|
|
161
|
+
module.exports = { planUp, seedCommandFor, worktreeCd }
|