@skitterbyte/skitterspec 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +97 -2
  2. package/assets/claude-md-section.md +11 -0
  3. package/assets/core/env.config.json.example +24 -0
  4. package/assets/core/env.config.md +83 -0
  5. package/assets/core/linear.config.json.example +39 -0
  6. package/assets/core/linear.config.md +121 -0
  7. package/assets/rules/spec-planning.md +29 -11
  8. package/assets/skills/spec/SKILL.md +61 -13
  9. package/assets/skills/spec-bug/SKILL.md +0 -4
  10. package/assets/skills/spec-cancel/SKILL.md +8 -3
  11. package/assets/skills/spec-complete/SKILL.md +10 -10
  12. package/assets/skills/spec-env/SKILL.md +57 -0
  13. package/assets/skills/spec-env-down/SKILL.md +56 -0
  14. package/assets/skills/spec-go/SKILL.md +39 -3
  15. package/assets/skills/spec-init/SKILL.md +0 -8
  16. package/assets/skills/spec-pull/SKILL.md +46 -0
  17. package/assets/skills/spec-push/SKILL.md +53 -0
  18. package/assets/skills/spec-ready/SKILL.md +0 -2
  19. package/assets/skills/spec-review/SKILL.md +2 -2
  20. package/assets/skills/spec-status/SKILL.md +46 -0
  21. package/bin/skitterspec.js +0 -0
  22. package/package.json +6 -6
  23. package/src/cli.js +497 -2
  24. package/src/env/config.js +152 -0
  25. package/src/env/provision.js +76 -0
  26. package/src/env/registry.js +95 -0
  27. package/src/env/render.js +26 -0
  28. package/src/env/resolve.js +184 -0
  29. package/src/env/teardown.js +94 -0
  30. package/src/init.js +82 -27
  31. package/src/prompts.js +23 -12
  32. package/src/sync/apply.js +66 -0
  33. package/src/sync/base.js +83 -0
  34. package/src/sync/compare.js +99 -0
  35. package/src/sync/config.js +198 -0
  36. package/src/sync/mcp.js +112 -0
  37. package/src/sync/normalize.js +249 -0
  38. package/src/sync/pull.js +84 -0
  39. package/src/sync/push.js +106 -0
  40. package/src/sync/write.js +86 -0
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: spec-env
3
+ description: Provision an isolated environment for a spec — a git worktree on its own branch + a namespaced Docker stack (isolated containers/networks/volumes + a reserved port block), plus an optional editor/terminal opener. Runs `skitterspec spec-env up` and executes the printed git/docker/open commands. Opt-in — needs specs/.core/env.config.json. Use when the user says "/spec-env", "spin up an environment for <spec>", "give this spec its own worktree/stack", or "isolate <spec>".
4
+ ---
5
+
6
+ # /spec-env — provision an isolated environment for a spec
7
+
8
+ Give an in-progress spec its own **git worktree** (a sibling directory on its own
9
+ branch, no stashing) + a **namespaced Docker stack** (`COMPOSE_PROJECT_NAME`
10
+ isolates containers/networks/volumes; `PORT_OFFSET` reserves a port block), so N
11
+ specs run side by side and `main` stays clean. An optional `open.command` then
12
+ opens the worktree however you like.
13
+
14
+ This skill is **opt-in**: it only works when `specs/.core/env.config.json` exists
15
+ (copy `env.config.json.example` to adopt it). If it's absent, tell the user how
16
+ to enable it and stop.
17
+
18
+ ## 1. Identify the target spec
19
+
20
+ - Use the spec named as an argument, else the spec **currently in context**. If
21
+ neither is clear, ask which spec.
22
+
23
+ ## 2. Plan the environment
24
+
25
+ Run the engine — it allocates the slot (idempotent), persists the registry, and
26
+ **prints** the plan (worktree path, branch, project name, port block, the exact
27
+ commands, the `.env` contents, and the opener):
28
+
29
+ ```
30
+ skitterspec spec-env up <spec>
31
+ ```
32
+
33
+ If it reports the feature isn't enabled, relay that and stop — do not hand-roll a
34
+ worktree/stack.
35
+
36
+ ## 3. Execute the printed side effects
37
+
38
+ Run the printed commands **in order**, exactly as printed:
39
+
40
+ 1. **`git worktree add …`** — creates the sibling worktree on its branch. It is a
41
+ **sibling** of this checkout, **never nested** inside it. If the worktree
42
+ already exists, the engine prints the *attach* form (no `-b`) — do not clobber
43
+ an existing worktree/branch.
44
+ 2. **Write the `.env`** — write the printed `.env` contents into the new
45
+ worktree's env file (default `.env`). Do this *after* the worktree exists.
46
+ 3. **`docker compose … up -d`** — only printed when Docker is enabled. Brings the
47
+ namespaced stack up in the spec's reserved port block.
48
+ 4. **Opener** — if an `open.command` line was printed, run it (e.g. opens the
49
+ worktree in your editor/terminal). Skipped silently when unset.
50
+
51
+ ## 4. Report
52
+
53
+ Echo the summary: worktree path, branch, project name, the allocated slot + port
54
+ block, and whether the stack was brought up. **Idempotent** — re-running attaches
55
+ to the existing slot/worktree and never reallocates.
56
+
57
+ Tear down later with `/spec-env-down <spec>`.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: spec-env-down
3
+ description: Tear down a spec's isolated environment — stop and remove its namespaced Docker stack (optionally backing up + dropping volumes), remove its git worktree, and free its slot. Guards refuse teardown on a dirty or unpushed worktree unless --force. Runs `skitterspec spec-env down` and executes the printed commands. Opt-in — needs specs/.core/env.config.json. Use when the user says "/spec-env-down", "tear down <spec>'s environment", "clean up the worktree/stack for <spec>", or "reclaim <spec>'s slot".
4
+ ---
5
+
6
+ # /spec-env-down — tear down a spec's isolated environment
7
+
8
+ Reverse `/spec-env`: stop + remove the spec's Docker stack, remove its git
9
+ worktree, and free its slot so the ports/slot are reclaimed. **Volumes are the
10
+ only destructive part** — dropped by default (to reclaim disk) unless
11
+ `--keep-volumes`, and always backed up first when `docker.backupCommand` is set.
12
+
13
+ Opt-in: only works when `specs/.core/env.config.json` exists. If absent, say so
14
+ and stop.
15
+
16
+ ## 1. Identify the target spec
17
+
18
+ - Use the spec named as an argument, else the spec **currently in context**. If
19
+ neither is clear, ask which spec.
20
+
21
+ ## 2. Plan the teardown
22
+
23
+ Run the engine — it checks the guards, frees the slot, and **prints** the plan:
24
+
25
+ ```
26
+ skitterspec spec-env down <spec> [--keep-volumes] [--force]
27
+ ```
28
+
29
+ - **`--keep-volumes`** — keep the stack's data (plain `down`, no backup, no drop).
30
+ - **`--force`** — override the guards below.
31
+
32
+ ## 3. Handle a guard block
33
+
34
+ If the CLI reports **blocked** (the worktree has uncommitted changes or unpushed
35
+ commits), **relay the reason and stop** — do not destroy unreviewed work. Offer
36
+ the user `--force` (and suggest committing/pushing first). Only re-run with
37
+ `--force` when the user explicitly asks.
38
+
39
+ ## 4. Execute the printed side effects
40
+
41
+ When not blocked, run the printed commands **in order**, exactly as printed:
42
+
43
+ 1. **Backup** (only when a `docker.backupCommand` is configured and volumes are
44
+ being dropped) — writes a dump under `.spec-env/backups/` before anything is
45
+ destroyed.
46
+ 2. **`docker compose … down`** — with `--volumes` unless `--keep-volumes`.
47
+ 3. **`git worktree remove …`** — removes the sibling worktree.
48
+
49
+ The slot is already freed by the CLI.
50
+
51
+ ## 5. Report
52
+
53
+ Confirm what happened: worktree removed, containers down, volumes
54
+ **dropped|kept**, slot freed, and the backup path (if any). If the spec wasn't
55
+ provisioned / was already torn down, the CLI reports a clean **no-op** — relay
56
+ that; it's not an error.
@@ -18,6 +18,23 @@ description: Promote a spec into active development and implement its first phas
18
18
 
19
19
  ## 2. Move it into development
20
20
 
21
+ **If per-spec isolation is enabled** (`specs/.core/env.config.json` exists) and
22
+ the spec doesn't already have a worktree, provision it **first**, so all the
23
+ housekeeping below lands on the spec's branch and never on `main`:
24
+
25
+ - Run `skitterspec spec-env up <name>` (the `/spec-env` engine). It adds a git
26
+ worktree on a branch forked from `main`, and — only when the spec's
27
+ `> **Stack:**` header is `worktree + docker` — also brings up its Docker stack.
28
+ Print the worktree path and the opener command it emits.
29
+ - **Do the rest in the worktree**, on the branch: open it (the printed opener, or
30
+ a fresh Claude session rooted there) or, staying in this session, act on the
31
+ worktree path with absolute paths / `git -C <worktreePath>`. The spec move,
32
+ header edits **and** the phase's code all happen on the branch — so the spec's
33
+ evolution travels with the code it describes and lands in one PR. `main` changes
34
+ only when that branch merges (at `/spec-complete`).
35
+
36
+ Then move the spec (in the worktree when isolated, in place otherwise):
37
+
21
38
  - If it isn't already under `specs/in-progress/`, move the whole spec folder
22
39
  there. **Use `git mv`** to keep history:
23
40
  `git mv "specs/backlog/<name>" "specs/in-progress/<name>"`.
@@ -27,14 +44,16 @@ description: Promote a spec into active development and implement its first phas
27
44
  - Set the **Developer** header field if it's still `—`: use `git config user.name`.
28
45
  - Append a **State log** row:
29
46
  `| <YYYY-MM-DD> | In Progress | in-progress | <git user.name> |`.
30
- - **Remove the spec's row from `specs/backlog/00-index.md`** it has left the backlog
31
- (there is no index for `in-progress`).
47
+ - **When isolated:** commit the move and **push the branch** now that records
48
+ the in-progress state for everyone and fires Linear's automation (when linked).
32
49
 
33
50
  A spec ideally reaches here as `Ready` (via `/spec-ready`), but `/spec-go` works
34
51
  on a `Draft` too — just sanity-check it's well-formed before building.
35
52
 
36
53
  If the spec is already in `in-progress`, skip the move and implement the **next
37
- unfinished phase** instead of Phase 1.
54
+ unfinished phase** instead of Phase 1. (When isolated, subsequent `/spec-go` runs
55
+ happen from inside the worktree — where the spec already sits in `in-progress` on
56
+ the branch — and a re-run of `spec-env up` just re-attaches it.)
38
57
 
39
58
  ## 3. Pre-flight — commit prior work, then compact
40
59
 
@@ -52,6 +71,23 @@ Before writing any code for this phase, get the workspace and context clean:
52
71
  the spec file on disk is the source of truth, so nothing is lost. Pause for the
53
72
  `/compact`, then implement the phase.
54
73
 
74
+ ## 3b. Sync from Linear first (opt-in)
75
+
76
+ **Only when `specs/.core/linear.config.json` exists** and the spec carries a
77
+ `linear_project_id` in its `00-overview.md` frontmatter. Otherwise skip this
78
+ step — no config means zero change to the flow below.
79
+
80
+ - **Run `/spec-pull` first.** Bring down anything Linear changed since the last
81
+ sync (status, priority, discussion-driven fields) so you build against the
82
+ current shared state, not a stale snapshot. On a conflict it refuses — relay
83
+ that and let the user resolve before continuing; do not `--force` for them.
84
+ - **Commit the refreshed snapshot** into the feature branch (a small
85
+ `chore(spec): pull latest from Linear`-style commit) so the frozen spec rides
86
+ in the PR alongside the code it describes.
87
+ - Linear's GitHub branch/PR automation may now drive status transitions off the
88
+ branch and PR you pushed in step 2 — expect state to move on the Linear side;
89
+ keep any manual status edits minimal to avoid fighting it.
90
+
55
91
  ## 4. Implement the phase
56
92
 
57
93
  Identify the **first unfinished phase** from the `00-overview.md` phase index,
@@ -27,14 +27,6 @@ empty so git keeps them:
27
27
  - `specs/.core/` — project rules (always apply; never moved)
28
28
  - `specs/backlog/` `specs/in-progress/` `specs/complete/` `specs/cancelled/`
29
29
 
30
- Also ensure the two index files exist (create with just their header comment +
31
- table header if missing — do not regenerate if present):
32
-
33
- - `specs/backlog/00-index.md` — live view of backlog (`Added | Spec | Type | Status`);
34
- `/spec` adds rows, `/spec-go` and `/spec-cancel` remove them.
35
- - `specs/complete/00-index.md` — append-only completion log (`Completed | Spec | Type`),
36
- newest first; `/spec-complete` prepends a row.
37
-
38
30
  ## 2. Version control — keep specs tracked
39
31
 
40
32
  The whole lifecycle lives in git — **everything under `specs/` is tracked**, so
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: spec-pull
3
+ description: Pull a spec's linked Linear project into the local spec (Linear → repo), three-way aware. Applies remote-only fields; refuses to clobber local edits on a conflict unless --force (which backs up the local side first). Fetches Linear over MCP and runs `skitterspec spec-sync pull`. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-pull", "pull from Linear", "sync Linear changes down", or "update this spec from Linear".
4
+ ---
5
+
6
+ # /spec-pull — bring Linear changes into the spec
7
+
8
+ Linear → repo. Applies fields Linear changed since the last sync (status,
9
+ priority, labels, and co-authored fields), rewrites the committed base, and
10
+ stamps `last_synced_at`. It **refuses** to overwrite a local edit that conflicts
11
+ with a Linear edit unless you pass `--force`.
12
+
13
+ **Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
14
+ tell the user how to enable Linear sync and stop.
15
+
16
+ ## 1. Identify the target spec
17
+
18
+ Use the argument, else the spec in context; ask if unclear.
19
+
20
+ ## 2. Fetch the Linear project
21
+
22
+ - Read `linear_project_id` from `00-overview.md` frontmatter; if missing, the
23
+ spec isn't linked — stop and point at `/spec`.
24
+ - Discover the Linear MCP project-read tool at runtime. If Linear isn't
25
+ connected, relay the fix and stop — **do nothing destructive**.
26
+ - Call it and write the project JSON to a temp file.
27
+
28
+ ## 3. Run the engine
29
+
30
+ ```
31
+ skitterspec spec-sync pull <spec> --remote <tempfile> [--force]
32
+ ```
33
+
34
+ - **No conflict** — the engine applies remote-only fields to the local snapshot,
35
+ rewrites the base, and stamps the sync. Body fields with no local home yet are
36
+ reported as `deferred` (apply them by hand from Linear if needed).
37
+ - **Conflict** (a co-authored field changed on both sides) — the engine
38
+ **refuses** and lists the fields. Relay that; do not force on the user's behalf.
39
+ - **`--force`** — only when the user explicitly asks. Remote wins after the engine
40
+ backs up the local side under `sync.backupDir` (the reflog). Relay the backup
41
+ path.
42
+
43
+ ## 4. Report
44
+
45
+ Relay the git-like summary (applied / deferred / conflicts / backup / base). If
46
+ fields were applied, remind the user to review and commit the refreshed snapshot.
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: spec-push
3
+ description: Push a spec's local content up to its linked Linear project (repo → Linear), three-way aware and ownership-respecting. Never pushes pull-owned fields or local-only sections; aborts if Linear moved since the last sync unless --force (which backs up the remote side first). Runs `skitterspec spec-sync push` then applies the blessed writes over MCP. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-push", "push to Linear", "sync my spec up to Linear", or "update the Linear project from this spec".
4
+ ---
5
+
6
+ # /spec-push — send spec content up to Linear
7
+
8
+ Repo → Linear. Sends the fields the repo owns/co-authors (description, phases,
9
+ tasks per config) up to the linked project. It **never** writes `pull`-owned
10
+ fields (status/priority/labels) or `localOnlySections`, and it **aborts** if
11
+ Linear moved since the last sync (pull first) unless you `--force`.
12
+
13
+ **Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
14
+ tell the user how to enable Linear sync and stop.
15
+
16
+ ## 1. Identify the target spec
17
+
18
+ Use the argument, else the spec in context; ask if unclear.
19
+
20
+ ## 2. Fetch the Linear project
21
+
22
+ - Read `linear_project_id` from `00-overview.md` frontmatter; if missing, stop
23
+ (link via `/spec` first).
24
+ - Discover the Linear MCP tools at runtime (project read **and** update). If
25
+ Linear isn't connected — or the update tool is missing — relay the fix and stop,
26
+ **writing nothing**.
27
+ - Call the read tool and write the project JSON to a temp file.
28
+
29
+ ## 3. Run the engine (the guard)
30
+
31
+ ```
32
+ skitterspec spec-sync push <spec> --remote <tempfile> --out <mergedfile> [--force]
33
+ ```
34
+
35
+ - **Refused** (`remote-moved` / `concurrent-write` / conflict) — relay the message
36
+ and **stop**. Do not write to Linear. Suggest `/spec-pull` first.
37
+ - **OK** — the engine has confirmed it's safe, rewritten the base, and stamped
38
+ `last_synced_at`. Its summary lists the `written` fields (and any `skipped`
39
+ because they're not pushable).
40
+ - **`--force`** — only when the user explicitly asks. Local wins after the engine
41
+ backs up the remote side under `sync.backupDir`. Relay the backup path.
42
+
43
+ ## 4. Apply the blessed writes to Linear
44
+
45
+ Only when step 3 returned OK: for each `written` field, call the Linear update
46
+ tool with that field's local value (e.g. `description` → the project description).
47
+ The engine has already vetted the change and moved the base — so if a Linear
48
+ write fails, re-run `/spec-pull` to reconcile rather than retrying blindly.
49
+
50
+ ## 5. Report
51
+
52
+ Relay the git-like summary (written / skipped / backup / base) plus which Linear
53
+ fields you updated.
@@ -42,8 +42,6 @@ Offer to fix small gaps inline if the user wants; otherwise leave it `Draft`.
42
42
  `> **Status:** Ready (<YYYY-MM-DD>)`.
43
43
  - Append a **State log** row: `| <YYYY-MM-DD> | Ready | backlog | <git user.name> |`
44
44
  (no folder change — Ready stays in `backlog`).
45
- - Update the spec's row in `specs/backlog/00-index.md` — set its Status column to
46
- `Ready` (the row stays; the spec is still in backlog).
47
45
  - Optionally add a **Changelog** note if grooming changed anything substantive.
48
46
 
49
47
  ## 4. Report
@@ -59,8 +59,8 @@ reading the code, do that instead of asking.
59
59
  Reviewed vs codebase: retargeted Phase 2 onto X, dropped Y (removed), ticked Z
60
60
  (already done)`).
61
61
  - **Status:** if drift is minor, leave status as-is. If a `Ready` spec needs real
62
- re-grooming, knock it back to `Draft` (set `> **Status:** Draft`, append a
63
- **State log** row, and reset its row in `specs/backlog/00-index.md` to `Draft`).
62
+ re-grooming, knock it back to `Draft` (set `> **Status:** Draft` and append a
63
+ **State log** row).
64
64
 
65
65
  ## 5. Report
66
66
 
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: spec-status
3
+ description: Show a spec's sync status against its linked Linear project — a read-only, git-status-style per-field divergence (local-only / remote-only / conflict / in-sync). Fetches the Linear project over MCP and runs `skitterspec spec-sync status`. Changes nothing. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-status", "is this spec in sync with Linear", "what's diverged from Linear", or "show spec sync status".
4
+ ---
5
+
6
+ # /spec-status — show a spec's divergence from Linear
7
+
8
+ Read-only. Prints, per field, whether the spec and its linked Linear project have
9
+ diverged since the last sync — the `git status` of the hybrid sync. Writes
10
+ nothing to either side.
11
+
12
+ This skill is **opt-in**: it only runs when `specs/.core/linear.config.json`
13
+ exists. If it's absent, tell the user to copy `linear.config.json.example` →
14
+ `linear.config.json` to enable Linear sync, and stop.
15
+
16
+ ## 1. Identify the target spec
17
+
18
+ Use the spec named as an argument, else the spec **currently in context**. If
19
+ neither is clear, ask which spec.
20
+
21
+ ## 2. Fetch the Linear project (read-only)
22
+
23
+ - Read the spec's `linear_project_id` from `00-overview.md` frontmatter. If it's
24
+ missing, the spec isn't linked yet — say so and stop (link it via `/spec`).
25
+ - Discover the connected Linear MCP tools at runtime (the project-read tool). If
26
+ Linear isn't connected, relay "connect the `linear` MCP server" and stop — do
27
+ nothing else.
28
+ - Call the project-read tool for that id and write the returned JSON to a temp
29
+ file (e.g. under the OS temp dir).
30
+
31
+ ## 3. Run the engine
32
+
33
+ ```
34
+ skitterspec spec-sync status <spec> --remote <tempfile>
35
+ ```
36
+
37
+ The engine does the three-way compare (local vs Linear vs the committed base) and
38
+ prints each diverged field with its classification and sync direction. Without
39
+ `--remote` it falls back to a local-vs-base comparison (still read-only).
40
+
41
+ ## 4. Report
42
+
43
+ Relay the engine's summary verbatim, then offer the natural next step:
44
+ `/spec-pull` for remote-only changes, `/spec-push` for local-only, and — for a
45
+ `conflict` — resolve locally or use `--force` (which backs up the losing side).
46
+ Never write anything from this skill.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skitterbyte/skitterspec",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Spec-driven-development workflow for Claude Code — installs the spec lifecycle skills, rule, and specs/ folders into any project.",
5
5
  "keywords": [
6
6
  "claude",
@@ -27,6 +27,10 @@
27
27
  "dependencies": {
28
28
  "prompts": "^2.4.2"
29
29
  },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/skitterbyte/skitterspec.git"
33
+ },
30
34
  "scripts": {
31
35
  "test": "node --test",
32
36
  "changelog": "node scripts/generate-changelog.js",
@@ -34,9 +38,5 @@
34
38
  "releases": "node scripts/generate-releases.js",
35
39
  "releases:retro": "node scripts/generate-releases.js --retro",
36
40
  "version": "node scripts/generate-changelog.js && node scripts/generate-releases.js && git add CHANGELOG.md RELEASES.md"
37
- },
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/skitterbyte/skitterspec.git"
41
41
  }
42
- }
42
+ }