@skitterbyte/skitterspec-linear 6.0.0 → 7.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 +5 -3
- package/assets/skills/spec/SKILL.md +27 -0
- package/assets/skills/spec-bug/SKILL.md +20 -0
- package/assets/skills/spec-complete/SKILL.md +10 -0
- package/assets/skills/spec-go/SKILL.md +18 -3
- package/assets/skills/spec-hotfix/SKILL.md +20 -0
- package/assets/skills/spec-review/SKILL.md +10 -3
- package/package.json +1 -1
- package/src/cli.js +62 -2
|
@@ -159,9 +159,11 @@ Every spec is a **folder** `specs/<bucket>/<name>/` — never a bare file, even
|
|
|
159
159
|
simple changes. Inside it:
|
|
160
160
|
|
|
161
161
|
- `00-overview.md` is the entry point / dashboard: header, Problem, Decisions,
|
|
162
|
-
Solution overview, the **
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
Solution overview, the **Impact map** (a `Surface | Change | Detail` table
|
|
163
|
+
naming the concrete surfaces the spec touches — endpoints, schemas, DB tables,
|
|
164
|
+
domain objects, routes, business rules — as the scannable blast radius), the
|
|
165
|
+
**phase index** (a table linking to each phase file with its status), Open
|
|
166
|
+
questions, State log, Changelog. **No per-phase task lists live here.**
|
|
165
167
|
- **One file per phase** — `01-<phase-slug>.md`, `02-<phase-slug>.md`, … in
|
|
166
168
|
execution order. Each holds that phase's goal, its task checkboxes (tests
|
|
167
169
|
included), and any phase-specific notes. Even a single-phase spec gets `01-….md`
|
|
@@ -96,6 +96,26 @@ the spec — be specific.>
|
|
|
96
96
|
<Short prose or bullets describing the chosen shape end-to-end. Optional small
|
|
97
97
|
schema/grammar/output snippets where they remove ambiguity.>
|
|
98
98
|
|
|
99
|
+
## Impact
|
|
100
|
+
|
|
101
|
+
<The concrete surfaces this spec touches — the scannable blast radius, so a
|
|
102
|
+
reader can eyeball where the spec got something wrong without reading prose.
|
|
103
|
+
`Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
|
|
104
|
+
values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
|
|
105
|
+
Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
|
|
106
|
+
this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
|
|
107
|
+
terse — names/signatures, not sentences. List **only** surfaces that actually
|
|
108
|
+
change; the heading is always present, but if nothing external changes write the
|
|
109
|
+
single line below instead of an empty table.>
|
|
110
|
+
|
|
111
|
+
| Surface | Change | Detail |
|
|
112
|
+
|---------|--------|--------|
|
|
113
|
+
| <e.g. Endpoint> | add | <e.g. POST /orders> |
|
|
114
|
+
| <e.g. DB> | update | <e.g. orders (+status col)> |
|
|
115
|
+
|
|
116
|
+
<_No external surface changes — internal refactor only._ — use this line in
|
|
117
|
+
place of the table when the spec touches no external surface.>
|
|
118
|
+
|
|
99
119
|
## Phases
|
|
100
120
|
|
|
101
121
|
Each phase lives in its own file in this folder. Status: ⬜ not started ·
|
|
@@ -163,6 +183,13 @@ Rules for the spec body:
|
|
|
163
183
|
phase index.
|
|
164
184
|
- **Honour project conventions** when writing tasks — reference the relevant
|
|
165
185
|
`.claude/rules/*.md` rather than re-explaining them.
|
|
186
|
+
- **The `## Impact` table is derived from Phase A items 3 (Affected areas) & 5
|
|
187
|
+
(Data/API impact)** — a structured place to record what those already surface,
|
|
188
|
+
not new grilling. It is the scannable substitute for spelling impact out in
|
|
189
|
+
prose: name the surfaces (endpoints, schemas, DB tables, domain objects,
|
|
190
|
+
routes, business rules) instead of describing them, keep `Detail` terse, and
|
|
191
|
+
let it — not paragraphs — carry the blast radius. It complements the `Area:`
|
|
192
|
+
header (files) by naming behavioural surfaces.
|
|
166
193
|
- **Changelog** is mandatory and lives in the spec. Every later decision or
|
|
167
194
|
course-correction gets a dated one-line entry. Convert relative dates to
|
|
168
195
|
absolute.
|
|
@@ -120,6 +120,26 @@ because work is already underway. Keep it lean:
|
|
|
120
120
|
commands — confirm no regressions.
|
|
121
121
|
- [ ] <any follow-up hardening, or "None">
|
|
122
122
|
|
|
123
|
+
## Impact
|
|
124
|
+
|
|
125
|
+
<The concrete surfaces this spec touches — the scannable blast radius, so a
|
|
126
|
+
reader can eyeball where the spec got something wrong without reading prose.
|
|
127
|
+
`Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
|
|
128
|
+
values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
|
|
129
|
+
Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
|
|
130
|
+
this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
|
|
131
|
+
terse — names/signatures, not sentences. List **only** surfaces that actually
|
|
132
|
+
change; the heading is always present, but if nothing external changes write the
|
|
133
|
+
single line below instead of an empty table. A bug fix often changes no external
|
|
134
|
+
surface — that's fine, use the one-liner.>
|
|
135
|
+
|
|
136
|
+
| Surface | Change | Detail |
|
|
137
|
+
|---------|--------|--------|
|
|
138
|
+
| <e.g. Endpoint> | update | <e.g. GET /orders (fix null total)> |
|
|
139
|
+
|
|
140
|
+
<_No external surface changes — internal refactor only._ — use this line in
|
|
141
|
+
place of the table when the spec touches no external surface.>
|
|
142
|
+
|
|
123
143
|
## State log
|
|
124
144
|
|
|
125
145
|
| Date | Status | Folder | By |
|
|
@@ -101,6 +101,16 @@ is finished — e.g. to run a later phase in CI or a shared test env? Use
|
|
|
101
101
|
the normal landing plan. Commit any live fixes to the branch first; it refuses
|
|
102
102
|
if the primary checkout is dirty, or if a *different* spec holds it (release that
|
|
103
103
|
one with `/spec-live main`). Teardown (step 7) is unchanged.
|
|
104
|
+
**Work-loss abort.** Before it ends the live session, `integrate` checks the
|
|
105
|
+
work is actually landable and **aborts loudly** rather than finalize a spec
|
|
106
|
+
having landed nothing. Two cases, both leaving the live session intact:
|
|
107
|
+
- *stranded commits* — commits sit on the worktree's **detached HEAD** (e.g. a
|
|
108
|
+
pre-fix `/spec-go` committed there instead of on the branch). It prints the
|
|
109
|
+
count, the sha, and a `git -C <worktree> branch <tmp> <sha>` recovery hint —
|
|
110
|
+
recover those commits onto the branch, then re-run.
|
|
111
|
+
- *no worktree* — the spec is live but its worktree is gone. Re-isolate it with
|
|
112
|
+
`skitterspec spec-env up <name>`, then re-run.
|
|
113
|
+
Relay the diagnostic to the user and **stop** — do not proceed to teardown.
|
|
104
114
|
2. **Plan + execute.** Run `skitterspec spec-env integrate <name>` and run the
|
|
105
115
|
printed commands **in order**:
|
|
106
116
|
- `git -C <worktree> rebase <base>` — replay the branch onto base.
|
|
@@ -22,9 +22,24 @@ Diverting your browser to the spec is a separate explicit step — `/spec-connec
|
|
|
22
22
|
|
|
23
23
|
## 2. Move it into development
|
|
24
24
|
|
|
25
|
-
**If per-spec isolation is enabled
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
**Live check first (isolation only).** If per-spec isolation is enabled, before
|
|
26
|
+
provisioning run `skitterspec spec-env live status <name>` and read its `live:`
|
|
27
|
+
line. If it says **`live: yes`**, this spec is already checked out in the
|
|
28
|
+
**primary checkout** (you took it live with `/spec-live`) — **do not provision,
|
|
29
|
+
do not run `spec-env up`, and do not "work in the worktree"**. Its branch lives
|
|
30
|
+
in the primary checkout and its worktree is on a **detached HEAD**, so a commit
|
|
31
|
+
made in the worktree would strand on that detached HEAD and never reach the
|
|
32
|
+
branch. Instead skip the provisioning bullets and step 2b, leave the spec where
|
|
33
|
+
it is, and go straight to **step 4**, implementing the phase **in the primary
|
|
34
|
+
checkout on the branch** — edits and commits there advance the branch, and
|
|
35
|
+
`/spec-complete` lands them. (`spec-env up` refuses while live and says the same.
|
|
36
|
+
To return to an isolated worktree instead, run `/spec-live main` first, then
|
|
37
|
+
re-run `/spec-go`.)
|
|
38
|
+
|
|
39
|
+
**If per-spec isolation is enabled** (`specs/.core/env.config.json` exists), the
|
|
40
|
+
spec **isn't already live** (the check above), and it doesn't already have a
|
|
41
|
+
worktree, provision it **first**, so all the housekeeping below lands on the
|
|
42
|
+
spec's branch and never on `main`:
|
|
28
43
|
|
|
29
44
|
**Opt-out:** if the user passes `--no-worktree` (or explicitly asks to work in
|
|
30
45
|
place), skip the provisioning bullets below and build on the current branch — the
|
|
@@ -115,6 +115,26 @@ is usually a single-pass fix, so the `## Fix` block can live directly in
|
|
|
115
115
|
commands — confirm no regressions.
|
|
116
116
|
- [ ] <any follow-up hardening, or "None">
|
|
117
117
|
|
|
118
|
+
## Impact
|
|
119
|
+
|
|
120
|
+
<The concrete surfaces this spec touches — the scannable blast radius, so a
|
|
121
|
+
reader can eyeball where the spec got something wrong without reading prose.
|
|
122
|
+
`Change` is `add` · `update` · `remove`. `Surface` is guided-but-open: use
|
|
123
|
+
values like Endpoint, Route/UI, Schema/model, DB table/migration, Domain object,
|
|
124
|
+
Service, CLI command, Config key, Skill/rule, Business rule — or whatever fits
|
|
125
|
+
this project (skitterspec itself is a CLI with no HTTP surface). Keep `Detail`
|
|
126
|
+
terse — names/signatures, not sentences. List **only** surfaces that actually
|
|
127
|
+
change; the heading is always present, but if nothing external changes write the
|
|
128
|
+
single line below instead of an empty table. A hotfix should be minimal — often
|
|
129
|
+
no external surface changes; that's fine, use the one-liner.>
|
|
130
|
+
|
|
131
|
+
| Surface | Change | Detail |
|
|
132
|
+
|---------|--------|--------|
|
|
133
|
+
| <e.g. Endpoint> | update | <e.g. GET /orders (fix null total)> |
|
|
134
|
+
|
|
135
|
+
<_No external surface changes — internal refactor only._ — use this line in
|
|
136
|
+
place of the table when the spec touches no external surface.>
|
|
137
|
+
|
|
118
138
|
## Landing
|
|
119
139
|
|
|
120
140
|
- [ ] Deploy tag (patch bump of the base version) created at `/spec-complete`
|
|
@@ -26,6 +26,13 @@ trust the spec's own wording — verify:
|
|
|
26
26
|
- **Referenced things still exist.** Grep/read for each `file:line`, module,
|
|
27
27
|
function, route, model, type, or symbol the spec names. Flag anything renamed,
|
|
28
28
|
moved, or deleted.
|
|
29
|
+
- **Impact map matches reality.** Walk every row of the `## Impact` table and
|
|
30
|
+
verify against the code: does the named surface (endpoint, schema/model, DB
|
|
31
|
+
table/migration, domain object, route, business rule) exist, and does its
|
|
32
|
+
stated `Change` (add/update/remove) still hold? A stale Impact map is the
|
|
33
|
+
highest-signal drift — each row is a named, checkable surface, so a wrong row
|
|
34
|
+
is exactly the "obvious mistake" the map exists to surface. Flag/refresh stale
|
|
35
|
+
rows (and add rows for surfaces the spec now touches but omits).
|
|
29
36
|
- **Tasks already done.** For each `- [ ]`, check whether the code already
|
|
30
37
|
implements it (it may have landed via other work). Tick `- [x]` what's done.
|
|
31
38
|
- **Decisions still valid.** Re-check each `## Decisions` entry against the
|
|
@@ -47,9 +54,9 @@ reading the code, do that instead of asking.
|
|
|
47
54
|
|
|
48
55
|
## 4. Update the spec
|
|
49
56
|
|
|
50
|
-
- Rewrite stale **Decisions** / **Solution overview**
|
|
51
|
-
stale **tasks** in the phase files so they
|
|
52
|
-
resolved questions. Add/remove tasks within a phase file; add a new phase by
|
|
57
|
+
- Rewrite stale **Decisions** / **Solution overview** and refresh the **Impact
|
|
58
|
+
map** in `00-overview.md`, and stale **tasks** in the phase files, so they
|
|
59
|
+
match the current code and the resolved questions. Add/remove tasks within a phase file; add a new phase by
|
|
53
60
|
creating a `0N-<slug>.md` file **and** a matching overview index row, or drop a
|
|
54
61
|
dead phase by removing both. Keep the index and files in sync; **preserve
|
|
55
62
|
completed `[x]` history**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec-linear",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Spec-driven development for Claude Code, with Linear hybrid-sync — a superset of @skitterbyte/skitterspec: the base filesystem workflow plus git-like /spec-status · /spec-pull · /spec-push and the spec-sync CLI. Install this OR the base, not both.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/src/cli.js
CHANGED
|
@@ -185,6 +185,19 @@ function specEnvUp(dir, config, specArg) {
|
|
|
185
185
|
}
|
|
186
186
|
const spec = resolveSpec(specArg, dir, config)
|
|
187
187
|
|
|
188
|
+
// Live-safe: if this spec is already live on the primary checkout (its branch was
|
|
189
|
+
// branch-switched in by `live take`), a `git worktree add` would fail — the branch
|
|
190
|
+
// is checked out there. Point the operator at the primary checkout rather than
|
|
191
|
+
// emit a plan that can't run (see spec feat-live-spec-flow).
|
|
192
|
+
const primaryUp = assertPrimaryOnMain(config, gitReader(dir))
|
|
193
|
+
if (!primaryUp.onBase && primaryUp.branch === spec.branch) {
|
|
194
|
+
process.stdout.write(
|
|
195
|
+
`spec-env up: ${spec.folder} is live in the primary checkout — work there directly ` +
|
|
196
|
+
'(its branch is checked out), or run `/spec-live main` first to re-isolate its worktree.\n',
|
|
197
|
+
)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
// Trust the shared worktree root so edits into the freshly-provisioned worktree
|
|
189
202
|
// don't prompt. One absolute entry (the root) covers every spec; self-heals on
|
|
190
203
|
// every provision for teammates who only cloned and ran /spec-go.
|
|
@@ -567,6 +580,33 @@ function specEnvIntegrate(dir, config, specArg) {
|
|
|
567
580
|
)
|
|
568
581
|
return
|
|
569
582
|
}
|
|
583
|
+
// Work-loss guard — runs BEFORE the destructive `checkout base` that ends the
|
|
584
|
+
// live session. Ending the session must leave landable work behind; if it
|
|
585
|
+
// wouldn't, abort loudly instead of silently finalizing the spec with nothing
|
|
586
|
+
// landed (see spec feat-live-spec-flow).
|
|
587
|
+
if (!fs.existsSync(spec.worktreePath)) {
|
|
588
|
+
process.stdout.write(
|
|
589
|
+
`spec-env integrate: blocked — ${spec.folder} is live but has no worktree to land ` +
|
|
590
|
+
`from. Re-isolate it with \`skitterspec spec-env up ${spec.folder}\`, then re-run.\n`,
|
|
591
|
+
)
|
|
592
|
+
return
|
|
593
|
+
}
|
|
594
|
+
const liveWtGit = gitReader(spec.worktreePath)
|
|
595
|
+
if (liveWtGit(['symbolic-ref', '--short', 'HEAD']) === null) {
|
|
596
|
+
// Detached worktree HEAD: any commits ahead of the branch ref (e.g. made by a
|
|
597
|
+
// non-live-aware /spec-go) would be abandoned by the re-isolate `switch` below.
|
|
598
|
+
const stranded = liveWtGit(['rev-list', '--count', `${spec.branch}..HEAD`])
|
|
599
|
+
const head = liveWtGit(['rev-parse', '--short', 'HEAD'])
|
|
600
|
+
if (stranded !== null && Number(stranded) > 0) {
|
|
601
|
+
process.stdout.write(
|
|
602
|
+
`spec-env integrate: blocked — ${stranded} commit(s) are stranded on the detached ` +
|
|
603
|
+
`HEAD of ${spec.worktreePath} (at ${head}), ahead of ${spec.branch}; re-isolating ` +
|
|
604
|
+
`would abandon them. Recover with \`git -C ${spec.worktreePath} branch <tmp> ${head}\`, ` +
|
|
605
|
+
`reconcile onto ${spec.branch}, then re-run.\n`,
|
|
606
|
+
)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
}
|
|
570
610
|
const co = runGit(dir, ['checkout', base])
|
|
571
611
|
if (!co.ok) {
|
|
572
612
|
process.stdout.write(`spec-env integrate: could not check out ${base} — ${co.err}\n`)
|
|
@@ -912,7 +952,7 @@ async function specEnvLive(dir, config, positional) {
|
|
|
912
952
|
const action = positional[0] || 'status'
|
|
913
953
|
switch (action) {
|
|
914
954
|
case 'status':
|
|
915
|
-
specEnvLiveStatus(dir, config)
|
|
955
|
+
specEnvLiveStatus(dir, config, positional[1])
|
|
916
956
|
break
|
|
917
957
|
case 'take':
|
|
918
958
|
await specEnvLiveTake(dir, config, positional[1])
|
|
@@ -1146,8 +1186,28 @@ async function specEnvLiveAbort(dir, config) {
|
|
|
1146
1186
|
)
|
|
1147
1187
|
}
|
|
1148
1188
|
|
|
1149
|
-
function specEnvLiveStatus(dir, config) {
|
|
1189
|
+
function specEnvLiveStatus(dir, config, specArg) {
|
|
1150
1190
|
const { onBase, branch, baseBranch } = assertPrimaryOnMain(config, gitReader(dir))
|
|
1191
|
+
|
|
1192
|
+
// Per-spec query (`live status <spec>`): a clear yes/no verdict the /spec-go
|
|
1193
|
+
// skill branches on to decide whether to skip worktree provisioning and work in
|
|
1194
|
+
// the primary checkout. The stable `live: yes|no` line is the machine seam.
|
|
1195
|
+
if (specArg) {
|
|
1196
|
+
const spec = resolveSpecWithWorktree(dir, config, specArg)
|
|
1197
|
+
const live = !onBase && branch === spec.branch
|
|
1198
|
+
process.stdout.write(
|
|
1199
|
+
`spec-env live status: ${spec.folder}\n` +
|
|
1200
|
+
` spec: ${spec.folder} (branch ${spec.branch})\n` +
|
|
1201
|
+
` primary: ${branch || '(detached)'}\n` +
|
|
1202
|
+
` live: ${
|
|
1203
|
+
live
|
|
1204
|
+
? `yes — ${spec.folder} holds the primary checkout; work there`
|
|
1205
|
+
: `no — primary is on ${branch || '(detached)'}`
|
|
1206
|
+
}\n`,
|
|
1207
|
+
)
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1151
1211
|
const receipt = readReceipt(dir, config)
|
|
1152
1212
|
const state = onBase
|
|
1153
1213
|
? 'on base — free'
|