@skitterbyte/skitterspec-linear 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.
- package/README.md +56 -0
- package/assets/claude-md-section.md +39 -0
- package/assets/core/env.config.json.example +28 -0
- package/assets/core/env.config.md +99 -0
- package/assets/core/linear.config.json.example +39 -0
- package/assets/core/linear.config.md +121 -0
- package/assets/rules/spec-planning.md +152 -0
- package/assets/skills/spec/SKILL.md +232 -0
- package/assets/skills/spec-bug/SKILL.md +110 -0
- package/assets/skills/spec-cancel/SKILL.md +61 -0
- package/assets/skills/spec-complete/SKILL.md +87 -0
- package/assets/skills/spec-env/SKILL.md +63 -0
- package/assets/skills/spec-env-down/SKILL.md +64 -0
- package/assets/skills/spec-go/SKILL.md +134 -0
- package/assets/skills/spec-init/SKILL.md +84 -0
- package/assets/skills/spec-pull/SKILL.md +46 -0
- package/assets/skills/spec-push/SKILL.md +53 -0
- package/assets/skills/spec-ready/SKILL.md +50 -0
- package/assets/skills/spec-review/SKILL.md +69 -0
- package/assets/skills/spec-status/SKILL.md +46 -0
- package/bin/skitterspec-linear.js +26 -0
- package/package.json +38 -0
- package/src/cli.js +495 -0
- package/src/deprecate.js +138 -0
- package/src/env/config.js +165 -0
- package/src/env/integrate.js +46 -0
- package/src/env/provision.js +76 -0
- package/src/env/registry.js +95 -0
- package/src/env/render.js +26 -0
- package/src/env/resolve.js +202 -0
- package/src/env/teardown.js +109 -0
- package/src/env/trust.js +87 -0
- package/src/init.js +311 -0
- package/src/prompts.js +56 -0
- package/src/vendor/linear/cli-sync.js +256 -0
- package/src/vendor/linear/config.js +198 -0
- package/src/vendor/linear/mcp.js +112 -0
- package/src/vendor/sync-core/index.js +35 -0
- package/src/vendor/sync-core/src/apply.js +66 -0
- package/src/vendor/sync-core/src/base.js +83 -0
- package/src/vendor/sync-core/src/compare.js +99 -0
- package/src/vendor/sync-core/src/normalize.js +249 -0
- package/src/vendor/sync-core/src/pull.js +84 -0
- package/src/vendor/sync-core/src/push.js +106 -0
- package/src/vendor/sync-core/src/write.js +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @skitterbyte/skitterspec-linear
|
|
2
|
+
|
|
3
|
+
Spec-driven development for [Claude Code](https://claude.com/claude-code), **with
|
|
4
|
+
Linear hybrid-sync**. A strict **superset** of
|
|
5
|
+
[`@skitterbyte/skitterspec`](https://www.npmjs.com/package/@skitterbyte/skitterspec):
|
|
6
|
+
everything in the base filesystem workflow, plus git-like sync between a spec and
|
|
7
|
+
its linked Linear project.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx @skitterbyte/skitterspec-linear init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Install **this OR the base**, never both — this package contains the entire base.
|
|
14
|
+
|
|
15
|
+
## What the superset adds
|
|
16
|
+
|
|
17
|
+
On top of the base skills (`/spec`, `/spec-go`, isolation, …):
|
|
18
|
+
|
|
19
|
+
- **`/spec-status`** — read-only, per-field divergence (local-only / remote-only /
|
|
20
|
+
conflict / in-sync). Changes nothing.
|
|
21
|
+
- **`/spec-pull [--force]`** — Linear → repo. Applies remote-only fields; refuses
|
|
22
|
+
to clobber a conflicting local edit unless `--force`.
|
|
23
|
+
- **`/spec-push [--force]`** — repo → Linear. Ownership-respecting,
|
|
24
|
+
concurrency-checked; refuses if Linear moved since base unless `--force`.
|
|
25
|
+
- **`spec-sync` CLI** (`skitterspec-linear spec-sync …`) — the deterministic
|
|
26
|
+
engine behind the skills, for CI / local runs.
|
|
27
|
+
|
|
28
|
+
The shared `/spec` and `/spec-go` skills come composed with the Linear steps
|
|
29
|
+
filled in: `/spec` links a new spec to a Linear Project (a Milestone per phase),
|
|
30
|
+
and `/spec-go` pulls first so you build against the current shared state.
|
|
31
|
+
|
|
32
|
+
## Opt-in
|
|
33
|
+
|
|
34
|
+
Linear sync is inert until `specs/.core/linear.config.json` exists — copy the
|
|
35
|
+
scaffolded `linear.config.json.example` and fill in your team / initiative IDs
|
|
36
|
+
(every field is documented in `specs/.core/linear.config.md`). Without it, this
|
|
37
|
+
behaves exactly like the base.
|
|
38
|
+
|
|
39
|
+
**Mapping** (config-driven): spec folder → Linear **Project**; each phase → a
|
|
40
|
+
**Milestone**; tasks → **Issues**; an optional **Initiative** groups specs.
|
|
41
|
+
**Field ownership** (`both` / `pull` / `push`) collapses conflicts — only a `both`
|
|
42
|
+
field that moved on both sides is a real conflict, and `--force` backs up the
|
|
43
|
+
losing side before winning. **Base sidecars** (`specs/.core/linear-base/`) are
|
|
44
|
+
committed; **backups** (`specs/.core/linear-backups/`) are gitignored.
|
|
45
|
+
|
|
46
|
+
Branch naming that embeds the Linear id lives in the isolation config
|
|
47
|
+
(`env.config.json` → `branch.pattern` with `{identifier}`, `branch.identifierField:
|
|
48
|
+
"linear_identifier"`), not in `linear.config.json`.
|
|
49
|
+
|
|
50
|
+
## Migrating from `@skitterbyte/skitterspec` v1
|
|
51
|
+
|
|
52
|
+
If you used Linear sync on the old base, switch here and re-run `init` — see
|
|
53
|
+
[MIGRATION.md](../../MIGRATION.md). Your `specs/.core/linear.config.json` path is
|
|
54
|
+
unchanged.
|
|
55
|
+
|
|
56
|
+
MIT © Reuben Greaves
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## Spec workflow
|
|
2
|
+
|
|
3
|
+
Spec-driven development runs through eight skills — use them so structure and
|
|
4
|
+
lifecycle stay consistent (see `.claude/rules/spec-planning.md`):
|
|
5
|
+
|
|
6
|
+
| Skill | Action | Status | Folder |
|
|
7
|
+
|-------|--------|--------|--------|
|
|
8
|
+
| `/spec` | (Feature) Grill to a clear shared understanding, then write a concise spec | `Draft` | `specs/backlog/` |
|
|
9
|
+
| `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
|
|
10
|
+
| `/spec-ready` | Confirm the spec is groomed (no open questions, phases + tests defined) | `Ready` | `specs/backlog/` |
|
|
11
|
+
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
12
|
+
| `/spec-go` | Implement the next phase (with tests) | `In Progress` | `specs/in-progress/` |
|
|
13
|
+
| `/spec-complete` | Verify all phases done + tests green | `Complete` | `specs/complete/` |
|
|
14
|
+
| `/spec-cancel` | Record progress, stamp a reason on the header | `Cancelled` | `specs/cancelled/` |
|
|
15
|
+
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
16
|
+
|
|
17
|
+
Every spec has a **type** (`> **Type:** Feature\|Bug`) and a filename prefix
|
|
18
|
+
(`feat-<name>` / `bug-<name>`) — never `[BUG]` brackets (glob hazard). Specs use
|
|
19
|
+
markdown checkboxes (`- [ ]`) for task tracking and are the single source of
|
|
20
|
+
truth for progress. Every spec is a folder: `00-overview.md` is the dashboard
|
|
21
|
+
(problem, decisions, solution, **phase index**, logs) and **each phase is its own
|
|
22
|
+
file** (`01-<slug>.md`, `02-…`) holding that phase's tasks — never a bare file,
|
|
23
|
+
never phases lumped into the overview. **Every phase ends with creating and
|
|
24
|
+
running tests**; decisions go in the spec's Changelog, state transitions in its
|
|
25
|
+
State log.
|
|
26
|
+
|
|
27
|
+
> Tailor the per-phase test commands and project conventions referenced by the
|
|
28
|
+
> spec skills to this project's stack (see `.claude/rules/spec-planning.md`).
|
|
29
|
+
|
|
30
|
+
**Per-spec isolation (adopt once, then default):** with isolation adopted
|
|
31
|
+
(`skitterspec init --isolation`, or `specs/.core/env.config.json` present),
|
|
32
|
+
**`/spec-go`** gives every in-progress spec its own git worktree automatically —
|
|
33
|
+
parallel specs, no stashing, `main` left free. Docker is a **per-spec
|
|
34
|
+
escalation**: `/spec` sets `> **Stack:** worktree` (default) or `worktree +
|
|
35
|
+
docker` when the spec touches the DB / stateful services, and only the latter
|
|
36
|
+
gets a namespaced stack (isolated volumes + reserved port block). **`/spec-env`**
|
|
37
|
+
· **`/spec-env-down`** are the manual engine (escalate Docker later, re-attach,
|
|
38
|
+
tear down — guarding dirty/unpushed work, backing up volumes first). Independent
|
|
39
|
+
of lifecycle status; inactive when `env.config.json` is absent.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"worktree": {
|
|
3
|
+
"root": "../{repo}-wt",
|
|
4
|
+
"folderPattern": "{slug}"
|
|
5
|
+
},
|
|
6
|
+
"docker": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"composeFile": "docker-compose.yml",
|
|
9
|
+
"projectNamePattern": "{repoSlug}_{slug}",
|
|
10
|
+
"portBase": 3000,
|
|
11
|
+
"portsPerSpec": 10,
|
|
12
|
+
"envFile": ".env",
|
|
13
|
+
"backupCommand": ""
|
|
14
|
+
},
|
|
15
|
+
"open": {
|
|
16
|
+
"command": ""
|
|
17
|
+
},
|
|
18
|
+
"registry": ".spec-env/registry.json",
|
|
19
|
+
"branch": {
|
|
20
|
+
"pattern": "{type}/{slug}",
|
|
21
|
+
"identifierField": ""
|
|
22
|
+
},
|
|
23
|
+
"baseBranch": "",
|
|
24
|
+
"guards": {
|
|
25
|
+
"refuseTeardownIfDirty": true,
|
|
26
|
+
"refuseTeardownIfUnpushed": true
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# `env.config.json` — per-spec isolation config
|
|
2
|
+
|
|
3
|
+
Opt-in config for per-spec isolation (git worktree + optional namespaced Docker
|
|
4
|
+
stack + an optional opener per in-progress spec), driven by `/spec-go` and the
|
|
5
|
+
`/spec-env` · `/spec-env-down` skills.
|
|
6
|
+
|
|
7
|
+
**Once this file is present, isolation is the default policy:** `/spec-go` gives
|
|
8
|
+
**every** in-progress spec its own git worktree automatically. Docker is a **per-
|
|
9
|
+
spec escalation** — a spec brings up a stack only when its `> **Stack:**` header
|
|
10
|
+
is `worktree + docker` (set at `/spec` when it touches the DB / stateful
|
|
11
|
+
services). A `worktree`-only spec takes no registry slot, no port block, and no
|
|
12
|
+
`.env`.
|
|
13
|
+
|
|
14
|
+
**Adopt it** with `skitterspec init --isolation` (or copy
|
|
15
|
+
`env.config.json.example` → `env.config.json` here) and edit the values. While
|
|
16
|
+
`env.config.json` is absent the feature is simply unused — every skill behaves
|
|
17
|
+
exactly as it does today.
|
|
18
|
+
|
|
19
|
+
The loader (`src/env/config.js` → `loadEnvConfig`) merges your file over the
|
|
20
|
+
frozen defaults below and returns `{ config, present }`; `present:false` means
|
|
21
|
+
no live `env.config.json` was found.
|
|
22
|
+
|
|
23
|
+
## Fields
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{
|
|
27
|
+
// Where sibling worktrees are created and how their dirs are named.
|
|
28
|
+
"worktree": {
|
|
29
|
+
"root": "../{repo}-wt", // dir that holds all spec worktrees; sibling of
|
|
30
|
+
// the primary checkout, never nested inside it.
|
|
31
|
+
"folderPattern": "{slug}" // per-spec worktree dir name.
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Per-spec Docker stack. COMPOSE_PROJECT_NAME namespaces containers,
|
|
35
|
+
// networks, and named volumes; PORT_OFFSET shifts the spec's port block.
|
|
36
|
+
"docker": {
|
|
37
|
+
// Master switch: "is Docker escalation available on this project?" — NOT
|
|
38
|
+
// "always run Docker". true = specs MAY escalate (a spec still needs
|
|
39
|
+
// `Stack: worktree + docker` to actually get a stack); the default stack is
|
|
40
|
+
// worktree-only. false = every spec is worktree-only and the escalation is
|
|
41
|
+
// hidden. (Was "always provision Docker" in the pre-Stack engine.)
|
|
42
|
+
"enabled": true,
|
|
43
|
+
"composeFile": "docker-compose.yml",
|
|
44
|
+
"projectNamePattern": "{repoSlug}_{slug}", // → COMPOSE_PROJECT_NAME
|
|
45
|
+
"portBase": 3000, // first port of slot 0's block
|
|
46
|
+
"portsPerSpec": 10, // block width; slot n → portBase + n*portsPerSpec
|
|
47
|
+
"envFile": ".env", // written into the worktree
|
|
48
|
+
"backupCommand": "" // optional pre-teardown backup (e.g. pg_dump);
|
|
49
|
+
// empty = no backup, volumes dropped directly.
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Optional, editor/terminal-agnostic opener run after `spec-env up`. The
|
|
53
|
+
// template is expanded with {worktreePath}, {slug}, {branch}, {projectName},
|
|
54
|
+
// {portOffset}. Empty = nothing is opened (the path is just printed).
|
|
55
|
+
// Examples: "code {worktreePath}", "tmux new-window -c {worktreePath}",
|
|
56
|
+
// or a "warp://..." deeplink for Warp users.
|
|
57
|
+
"open": {
|
|
58
|
+
"command": ""
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Machine-local slot registry (spec → slot index). Resolved against the
|
|
62
|
+
// primary checkout root, shared by all worktrees, gitignored.
|
|
63
|
+
"registry": ".spec-env/registry.json",
|
|
64
|
+
|
|
65
|
+
// Git branch naming, provider-neutral. `pattern` expands {type} and {slug}
|
|
66
|
+
// (e.g. "feat/add-widget"). When a ticketing provider is linked and you want
|
|
67
|
+
// tracker ids in branch names, use {identifier} in the pattern and point
|
|
68
|
+
// `identifierField` at the 00-overview.md frontmatter field the provider
|
|
69
|
+
// writes the id into — pushing that branch can then fire the tracker's
|
|
70
|
+
// automation. Empty `identifierField` (or a spec missing that field) makes a
|
|
71
|
+
// pattern with {identifier} fall back to {type}/{slug}.
|
|
72
|
+
"branch": {
|
|
73
|
+
"pattern": "{type}/{slug}",
|
|
74
|
+
"identifierField": ""
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Integration base branch — the branch specs fork from and land back onto
|
|
78
|
+
// (used by the teardown "merged?" guard and, later, the integrate step).
|
|
79
|
+
// Empty = auto-detect: origin/HEAD → main → master. Set it when your default
|
|
80
|
+
// branch isn't discoverable (e.g. no remote) or differs (trunk, develop).
|
|
81
|
+
"baseBranch": "",
|
|
82
|
+
|
|
83
|
+
// Teardown safety. --force overrides both. refuseTeardownIfUnpushed only
|
|
84
|
+
// blocks when the commits are ALSO unmerged into the base branch — a branch
|
|
85
|
+
// already landed on base tears down (and its branch is deleted) without
|
|
86
|
+
// --force, even with no remote.
|
|
87
|
+
"guards": {
|
|
88
|
+
"refuseTeardownIfDirty": true,
|
|
89
|
+
"refuseTeardownIfUnpushed": true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Token expansion
|
|
95
|
+
|
|
96
|
+
- `{repo}` — primary checkout dir basename (e.g. `skitterspec`).
|
|
97
|
+
- `{repoSlug}` — `{repo}` lower-cased, non-alphanumerics collapsed to `-`
|
|
98
|
+
(safe for a `COMPOSE_PROJECT_NAME`).
|
|
99
|
+
- `{slug}` — the spec slug (folder name minus its `feat-`/`bug-` prefix).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"linear": {
|
|
3
|
+
"teamKey": "",
|
|
4
|
+
"teamId": "",
|
|
5
|
+
"initiativeId": ""
|
|
6
|
+
},
|
|
7
|
+
"mapping": {
|
|
8
|
+
"specFolder": "project",
|
|
9
|
+
"phases": "milestone",
|
|
10
|
+
"tasks": "issue"
|
|
11
|
+
},
|
|
12
|
+
"states": {
|
|
13
|
+
"backlog": "Backlog",
|
|
14
|
+
"in-progress": "In Progress",
|
|
15
|
+
"complete": "Done",
|
|
16
|
+
"cancelled": "Cancelled"
|
|
17
|
+
},
|
|
18
|
+
"snapshot": {
|
|
19
|
+
"overviewFile": "00-overview.md"
|
|
20
|
+
},
|
|
21
|
+
"branch": {
|
|
22
|
+
"pattern": "{type}/{slug}"
|
|
23
|
+
},
|
|
24
|
+
"sync": {
|
|
25
|
+
"baseDir": "specs/.core/linear-base",
|
|
26
|
+
"backupDir": "specs/.core/linear-backups",
|
|
27
|
+
"fieldOwnership": {
|
|
28
|
+
"description": "both",
|
|
29
|
+
"milestones": "both",
|
|
30
|
+
"phaseBodies": "both",
|
|
31
|
+
"acceptanceCriteria": "both",
|
|
32
|
+
"taskBreakdown": "both",
|
|
33
|
+
"workflowState": "pull",
|
|
34
|
+
"priority": "pull",
|
|
35
|
+
"labels": "pull"
|
|
36
|
+
},
|
|
37
|
+
"localOnlySections": ["State log", "Changelog", "Open questions"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# `linear.config.json` — Linear hybrid-sync config
|
|
2
|
+
|
|
3
|
+
Opt-in config for the git-like Linear sync (`/spec-status`, `/spec-pull`,
|
|
4
|
+
`/spec-push`, and the Linear-aware paths of `/spec` and `/spec-go`). Linear owns
|
|
5
|
+
**status and discussion**; the repo stays the **co-authoring surface for spec
|
|
6
|
+
content**. Sync is bidirectional but git-like: explicit commands, a committed
|
|
7
|
+
**base sidecar** for three-way merge, and no blind overwrites.
|
|
8
|
+
|
|
9
|
+
**Every Linear step is gated on this file.** While `specs/.core/linear.config.json`
|
|
10
|
+
is absent the feature is simply unused — `/spec`, `/spec-go`, and the CLI's
|
|
11
|
+
`spec-sync` subcommands behave exactly as they do today (local-only). Adopt it by
|
|
12
|
+
copying `linear.config.json.example` → `linear.config.json` here and filling in
|
|
13
|
+
your team / initiative IDs.
|
|
14
|
+
|
|
15
|
+
The loader (`src/sync/config.js` → `loadLinearConfig`) merges your file over the
|
|
16
|
+
frozen defaults below and returns `{ config, present }`; `present:false` means no
|
|
17
|
+
live `linear.config.json` was found (the opt-in gate — it never throws on
|
|
18
|
+
absence). A `sync.fieldOwnership` value outside `both|pull|push` is a hard error.
|
|
19
|
+
|
|
20
|
+
## Fields
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
{
|
|
24
|
+
// Which Linear team/initiative specs sync into. IDs are read by the Phase 2
|
|
25
|
+
// MCP adapter; leave blank until you connect the `linear` MCP server.
|
|
26
|
+
"linear": {
|
|
27
|
+
"teamKey": "", // human-facing key, e.g. "ENG" (optional)
|
|
28
|
+
"teamId": "", // Linear team UUID (create target)
|
|
29
|
+
"initiativeId": "" // optional Initiative that groups these specs
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// How a spec's parts map onto Linear objects. Defaults mirror Decision 7:
|
|
33
|
+
// spec folder → Project, phases → Milestones, tasks → Issues. `phases` may be
|
|
34
|
+
// switched to "issue" if your workspace doesn't expose project milestones.
|
|
35
|
+
"mapping": {
|
|
36
|
+
"specFolder": "project",
|
|
37
|
+
"phases": "milestone", // "milestone" | "issue"
|
|
38
|
+
"tasks": "issue"
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Map the spec's lifecycle bucket → the Linear workflow-state name. Used when
|
|
42
|
+
// translating workflowState across the boundary (Linear owns status → `pull`).
|
|
43
|
+
"states": {
|
|
44
|
+
"backlog": "Backlog",
|
|
45
|
+
"in-progress": "In Progress",
|
|
46
|
+
"complete": "Done",
|
|
47
|
+
"cancelled": "Cancelled"
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// The spec's entry-point file the local snapshot + frontmatter live in.
|
|
51
|
+
"snapshot": {
|
|
52
|
+
"overviewFile": "00-overview.md"
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Git branch name derived for a linked spec. Tokens: {type}, {slug},
|
|
56
|
+
// {identifier} (the Linear issue/project identifier, e.g. ENG-123). Shared
|
|
57
|
+
// with the isolation engine's branch derivation (src/env/resolve.js).
|
|
58
|
+
"branch": {
|
|
59
|
+
"pattern": "{type}/{slug}"
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// The three-way merge engine's on-disk state.
|
|
63
|
+
"sync": {
|
|
64
|
+
// Committed base sidecar dir: the last-synced snapshot per spec, as
|
|
65
|
+
// {baseDir}/{identifier}.base.json. Committed so each worktree carries its
|
|
66
|
+
// own base and the divergence check stays accurate.
|
|
67
|
+
"baseDir": "specs/.core/linear-base",
|
|
68
|
+
|
|
69
|
+
// Backup-before-force lands the about-to-be-clobbered side here (the
|
|
70
|
+
// reflog). --force never destroys without first writing a copy.
|
|
71
|
+
"backupDir": "specs/.core/linear-backups",
|
|
72
|
+
|
|
73
|
+
// Per-field sync direction — collapses which fields can ever conflict:
|
|
74
|
+
// "both" — co-authored: push + pull, may conflict (both moved off base).
|
|
75
|
+
// "pull" — Linear→local only (e.g. status/priority); a local edit never
|
|
76
|
+
// pushes and a conflict resolves to remote-wins.
|
|
77
|
+
// "push" — local→Linear only; a remote edit never pulls and a conflict
|
|
78
|
+
// resolves to local-wins.
|
|
79
|
+
// Any field key you add here joins the compared field set; a value outside
|
|
80
|
+
// both|pull|push is rejected at load time.
|
|
81
|
+
"fieldOwnership": {
|
|
82
|
+
"description": "both",
|
|
83
|
+
"milestones": "both",
|
|
84
|
+
"phaseBodies": "both",
|
|
85
|
+
"acceptanceCriteria": "both",
|
|
86
|
+
"taskBreakdown": "both",
|
|
87
|
+
"workflowState": "pull",
|
|
88
|
+
"priority": "pull",
|
|
89
|
+
"labels": "pull"
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Markdown sections of 00-overview.md that are local-only scaffolding and
|
|
93
|
+
// are stripped from the pushed `description` (never sent to Linear).
|
|
94
|
+
"localOnlySections": ["State log", "Changelog", "Open questions"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Field ownership & conflicts
|
|
100
|
+
|
|
101
|
+
The spec is a set of structured fields, most written by only one side. Marking a
|
|
102
|
+
field's owner collapses which fields can genuinely conflict:
|
|
103
|
+
|
|
104
|
+
- A `pull` field (Linear owns it) never reports as **pushable** — a stray local
|
|
105
|
+
edit is informational and gets reverted on the next pull.
|
|
106
|
+
- A `push` field (the repo owns it) never reports as **pullable**.
|
|
107
|
+
- Only a `both` field where **both** sides moved off the committed base is a real
|
|
108
|
+
`conflict` — `/spec-push` / `/spec-pull` refuse it unless `--force` (which
|
|
109
|
+
backs up the losing side into `sync.backupDir` first).
|
|
110
|
+
|
|
111
|
+
After any successful pull/push/force the engine **rewrites the base** so the next
|
|
112
|
+
three-way compare starts clean.
|
|
113
|
+
|
|
114
|
+
## What to commit
|
|
115
|
+
|
|
116
|
+
- **`sync.baseDir`** (default `specs/.core/linear-base/`) — **commit it.** The base
|
|
117
|
+
sidecar is the last-synced snapshot the three-way merge compares against; each
|
|
118
|
+
worktree carries its own base, so it must travel with the branch.
|
|
119
|
+
- **`sync.backupDir`** (default `specs/.core/linear-backups/`) — **gitignore it.**
|
|
120
|
+
These are `--force` recovery copies (a local reflog), per-machine and not shared.
|
|
121
|
+
Add `specs/.core/linear-backups/` to your `.gitignore`.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Spec Planning
|
|
2
|
+
|
|
3
|
+
Spec-driven development is driven by eight skills — use them rather than
|
|
4
|
+
hand-rolling specs so the structure and lifecycle stay consistent. Each sets a
|
|
5
|
+
status on the spec header (`> **Status:** …`):
|
|
6
|
+
|
|
7
|
+
| Skill | Purpose | Status | Folder |
|
|
8
|
+
|-------|---------|--------|--------|
|
|
9
|
+
| `/spec` | (Feature) Grill to a clear shared understanding, then write a new spec | `Draft` | `specs/backlog/` |
|
|
10
|
+
| `/spec-bug` | (Bug) Reproduce with a failing test, capture spec, drive red→green | `In Progress` | `specs/in-progress/` |
|
|
11
|
+
| `/spec-ready` | Confirm it's groomed (no open questions, phases + tests defined) | `Ready` | `specs/backlog/` |
|
|
12
|
+
| `/spec-review` | Re-validate a spec against the codebase; refresh stale parts | `—` | (unchanged) |
|
|
13
|
+
| `/spec-go` | Implement the next phase (with tests) | `In Progress` | `specs/in-progress/` |
|
|
14
|
+
| `/spec-complete` | Verify all phases done + tests green | `Complete` | `specs/complete/` |
|
|
15
|
+
| `/spec-cancel` | Record progress, stamp a reason on the header | `Cancelled` | `specs/cancelled/` |
|
|
16
|
+
| `/spec-init` | Bootstrap/repair this workflow in a project (idempotent) | — | — |
|
|
17
|
+
|
|
18
|
+
Status flow: `Draft → Ready → In Progress → Complete` (or `Cancelled` from any
|
|
19
|
+
state). `/spec-ready` is a grooming gate only — it does not move the folder.
|
|
20
|
+
`/spec-bug` is test-first and starts straight in `In Progress` (work begins
|
|
21
|
+
immediately), so it skips Draft/Ready.
|
|
22
|
+
|
|
23
|
+
**Per-spec isolation (opt-in to adopt, then the default policy).** When a project
|
|
24
|
+
adopts isolation (`skitterspec init --isolation`, or `specs/.core/env.config.json`
|
|
25
|
+
present), `/spec-go` gives **every** in-progress spec its own git worktree
|
|
26
|
+
automatically — several specs run side by side without stashing or clashing, and
|
|
27
|
+
`main` stays free. Docker is a **per-spec escalation**: `/spec` records
|
|
28
|
+
`> **Stack:** worktree` (default) or `worktree + docker` when the spec touches the
|
|
29
|
+
DB / stateful services, and `/spec-go` brings up a namespaced stack only for the
|
|
30
|
+
latter. All housekeeping (the backlog→in-progress move, header edits, the code)
|
|
31
|
+
happens on the spec's branch in the worktree; `main` changes only when it merges.
|
|
32
|
+
`/spec-env` · `/spec-env-down` remain the manual engine (escalate Docker later,
|
|
33
|
+
re-attach, tear down). Isolation is **orthogonal to lifecycle status** and
|
|
34
|
+
inactive when `env.config.json` is absent — every skill then behaves as it does
|
|
35
|
+
today.
|
|
36
|
+
|
|
37
|
+
**Ticketing-provider sync (opt-in, a separate package).** The base is
|
|
38
|
+
tracker-free: it knows nothing about any specific ticketing system. A
|
|
39
|
+
ticketing provider is installed as its own distribution that plugs into two named
|
|
40
|
+
**seams** in the shared skills (`/spec` Phase E, `/spec-go` step 3b) and fulfils a
|
|
41
|
+
skill-name + CLI contract — it ships `/spec-status` (read-only per-field
|
|
42
|
+
divergence), `/spec-pull` (tracker→repo), and `/spec-push` (repo→tracker), backed
|
|
43
|
+
by a `spec-sync` CLI, three-way merged against a committed base sidecar. When a
|
|
44
|
+
provider is present, `/spec` also links the spec to the tracker and `/spec-go`
|
|
45
|
+
pulls first. With no provider installed the seams are empty and every skill
|
|
46
|
+
behaves as a plain filesystem workflow. See the provider package's own docs for
|
|
47
|
+
its config and field reference.
|
|
48
|
+
|
|
49
|
+
## Project conventions (fill this in)
|
|
50
|
+
|
|
51
|
+
The spec skills tell you to run "your project's typecheck and test commands" and
|
|
52
|
+
to "honour project conventions". Make those concrete here so specs stay
|
|
53
|
+
consistent with the codebase:
|
|
54
|
+
|
|
55
|
+
- **Typecheck command:** `<e.g. npm run typecheck>`
|
|
56
|
+
- **Test command:** `<e.g. npm test>` (single file/dir: `<e.g. npx vitest run path>`)
|
|
57
|
+
- **Lint/format:** `<e.g. npm run lint>`
|
|
58
|
+
- **Other rules specs must honour:** link the relevant `.claude/rules/*.md`
|
|
59
|
+
(architecture, code style, testing, database, etc.) rather than restating them.
|
|
60
|
+
|
|
61
|
+
## Spec types — Feature vs Bug
|
|
62
|
+
|
|
63
|
+
Every spec is one of two types, recorded **both** in the header and the filename:
|
|
64
|
+
|
|
65
|
+
- **Header field:** `> **Type:** Feature` or `> **Type:** Bug` (authoritative,
|
|
66
|
+
greppable: `grep -rl 'Type:.*Bug' specs/`).
|
|
67
|
+
- **Filename prefix:** `feat-<name>` for features, `bug-<name>` for bugs
|
|
68
|
+
(visible in listings; glob-safe — never use `[BUG]`/`[FEATURE]` brackets).
|
|
69
|
+
|
|
70
|
+
Both types share the same lifecycle folders below — type is orthogonal to status.
|
|
71
|
+
|
|
72
|
+
## Header fields & State log (audit trail)
|
|
73
|
+
|
|
74
|
+
Every spec header carries:
|
|
75
|
+
|
|
76
|
+
- `> **Author:**` — who created the spec (set at `/spec` / `/spec-bug`, defaults
|
|
77
|
+
to `git config user.name`).
|
|
78
|
+
- `> **Developer:**` — who implements it (`—` until `/spec-go` starts work, then
|
|
79
|
+
set to `git config user.name`; `/spec-bug` sets it immediately).
|
|
80
|
+
|
|
81
|
+
Every spec also has a **State log** table — the audit trail of folder/status
|
|
82
|
+
transitions. Each lifecycle skill appends exactly one row when it changes state:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
## State log
|
|
86
|
+
|
|
87
|
+
| Date | Status | Folder | By |
|
|
88
|
+
|------|--------|--------|----|
|
|
89
|
+
| 2026-01-01 | Draft | backlog | Jane Dev |
|
|
90
|
+
| 2026-01-02 | In Progress | in-progress | Jane Dev |
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Keep the **State log** (state transitions) separate from the **Changelog**
|
|
94
|
+
(decisions and course-corrections) — state moves go in the table, not the
|
|
95
|
+
changelog.
|
|
96
|
+
|
|
97
|
+
When asked for a plan, implementation strategy, or feature breakdown:
|
|
98
|
+
|
|
99
|
+
1. Create or update a spec under `specs/` — never plan only in chat.
|
|
100
|
+
2. Reach a clear shared understanding of the requirement AND the proposed
|
|
101
|
+
solution before writing (the `/spec` skill grills for this).
|
|
102
|
+
3. Use markdown checkboxes `- [ ]` for tasks, `- [x]` when done.
|
|
103
|
+
4. Organise work into phased sections with short goal descriptions.
|
|
104
|
+
5. Tasks must be granular enough to complete in one coding session.
|
|
105
|
+
6. Every phase ends with creating and running tests — a phase is not done until
|
|
106
|
+
its tests are green (run the project's typecheck + test commands above).
|
|
107
|
+
7. Keep specs **as concise as possible**.
|
|
108
|
+
8. Record decisions and course-corrections in the spec's **Changelog** section.
|
|
109
|
+
|
|
110
|
+
## Lifecycle folders
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
specs/backlog/ Draft + Ready specs (/spec, /spec-ready)
|
|
114
|
+
specs/in-progress/ under active implementation (/spec-go, /spec-bug)
|
|
115
|
+
specs/complete/ finished (/spec-complete)
|
|
116
|
+
specs/cancelled/ abandoned, with a reason on the header (/spec-cancel)
|
|
117
|
+
specs/.core/ project rules — ALWAYS APPLY, never moved
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Every spec is a **folder** `specs/<bucket>/<name>/` — never a bare file, even for
|
|
121
|
+
simple changes. Inside it:
|
|
122
|
+
|
|
123
|
+
- `00-overview.md` is the entry point / dashboard: header, Problem, Decisions,
|
|
124
|
+
Solution overview, the **phase index** (a table linking to each phase file with
|
|
125
|
+
its status), Open questions, State log, Changelog. **No per-phase task lists
|
|
126
|
+
live here.**
|
|
127
|
+
- **One file per phase** — `01-<phase-slug>.md`, `02-<phase-slug>.md`, … in
|
|
128
|
+
execution order. Each holds that phase's goal, its task checkboxes (tests
|
|
129
|
+
included), and any phase-specific notes. Even a single-phase spec gets `01-….md`
|
|
130
|
+
— so each phase is easy to open and work on its own.
|
|
131
|
+
|
|
132
|
+
Keep the index and the phase files in sync (`⬜`/`🔄`/`✅`). Legacy specs may be a
|
|
133
|
+
bare `<name>.md`, or a `00-overview.md` with inline phases — the skills read
|
|
134
|
+
those, but new specs always use the folder + phase-file form.
|
|
135
|
+
|
|
136
|
+
## Finding specs
|
|
137
|
+
|
|
138
|
+
The **folder buckets are the source of truth** — a spec's bucket is its status.
|
|
139
|
+
To see the backlog, list `specs/backlog/`; for the latest completed specs, use
|
|
140
|
+
`git log`/mtime on `specs/complete/` or each spec's dated **State log**. Live
|
|
141
|
+
status also lives in the tracker when a ticketing provider is linked. (There are
|
|
142
|
+
no `00-index.md` summary files — the folder tree, headers, and State logs are
|
|
143
|
+
queried directly.)
|
|
144
|
+
|
|
145
|
+
## Rules
|
|
146
|
+
|
|
147
|
+
- If a spec already exists, update it — don't rewrite from scratch.
|
|
148
|
+
- Preserve completed `[x]` tasks.
|
|
149
|
+
- Add new tasks to the appropriate phase.
|
|
150
|
+
- Never delete historical notes.
|
|
151
|
+
- The spec file is the single source of truth for implementation progress.
|
|
152
|
+
- Move specs between buckets with `git mv` to preserve history.
|