@skitterbyte/skitterspec-linear 7.0.2 → 8.0.1
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 +23 -25
- package/assets/core/SETUP.md +46 -51
- package/assets/core/linear.config.json.example +6 -7
- package/assets/core/linear.config.md +47 -77
- package/assets/rules/spec-planning.md +9 -7
- package/assets/skills/spec/SKILL.md +2 -2
- package/assets/skills/spec-go/SKILL.md +16 -18
- package/assets/skills/spec-push/SKILL.md +53 -47
- package/assets/skills/spec-status/SKILL.md +31 -26
- package/package.json +2 -2
- package/src/init.js +25 -0
- package/src/vendor/linear/cli-sanitise.js +0 -0
- package/src/vendor/linear/cli-sync.js +123 -204
- package/src/vendor/linear/config.js +18 -14
- package/src/vendor/sync-core/index.js +23 -19
- package/src/vendor/sync-core/src/base.js +8 -10
- package/src/vendor/sync-core/src/compare.js +83 -174
- package/src/vendor/sync-core/src/normalize.js +84 -80
- package/src/vendor/sync-core/src/push.js +39 -133
- package/src/vendor/sync-core/src/sanitise.js +4 -8
- package/src/vendor/sync-core/src/task-block.js +13 -1
- package/src/vendor/sync-core/src/write.js +15 -205
- package/assets/skills/spec-pull/SKILL.md +0 -49
- package/src/vendor/sync-core/src/apply.js +0 -66
- package/src/vendor/sync-core/src/pull.js +0 -115
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-push
|
|
3
|
-
description: Push a spec
|
|
3
|
+
description: Push a spec up to its linked Linear project (repo → Linear, one-way). The repo is the source of truth; Linear is a generated mirror. Runs `skitterspec spec-sync push` to get a create/update plan, applies it over MCP (project description/status, milestones, issues), stamps the returned ids back into the spec, then records the snapshot. Never reads Linear content back. Opt-in — needs specs/.core/linear.config.json. Use when the user says "/spec-push", "push to Linear", "update the Linear project from this spec".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /spec-push — send spec
|
|
6
|
+
# /spec-push — send a spec up to Linear (one-way)
|
|
7
7
|
|
|
8
|
-
Repo → Linear.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Linear
|
|
8
|
+
Repo → Linear. The repo is the **source of truth**; Linear is a **generated
|
|
9
|
+
mirror**. This skill computes what changed since the last push and applies it —
|
|
10
|
+
it never reads Linear content back or merges. A person editing the mirror in
|
|
11
|
+
Linear will see it overwritten on the next push.
|
|
12
12
|
|
|
13
13
|
**Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
|
|
14
14
|
tell the user how to enable Linear sync and stop.
|
|
@@ -17,58 +17,64 @@ tell the user how to enable Linear sync and stop.
|
|
|
17
17
|
|
|
18
18
|
Use the argument, else the spec in context; ask if unclear.
|
|
19
19
|
|
|
20
|
-
## 2.
|
|
20
|
+
## 2. Get the plan from the engine
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
22
|
+
```
|
|
23
|
+
skitterspec spec-sync push <spec> --json
|
|
24
|
+
```
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
The engine prints a JSON **plan** (no network, no remote read):
|
|
30
27
|
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"project": { "description": "…", "status": "in-progress", "priority": 2, "labels": ["…"] },
|
|
31
|
+
"milestones": { "create": [{ "ref": "01-outbox", "name": "…", "goal": "…" }], "update": [{ "id": "…", "name": "…", "goal": "…" }] },
|
|
32
|
+
"issues": { "create": [{ "ref": "<task text>", "title": "…", "description": "…", "done": false, "milestoneRef": "01-outbox" }], "update": [{ "id": "SKI-1", "title": "…", "description": "…", "done": true }] }
|
|
33
|
+
}
|
|
31
34
|
```
|
|
32
|
-
skitterspec spec-sync push <spec> --remote <tempfile> --out <mergedfile> [--force]
|
|
33
|
-
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
36
|
+
An empty plan (no project, no create/update) means the mirror is up to date —
|
|
37
|
+
say so and stop.
|
|
38
|
+
|
|
39
|
+
## 3. Discover the Linear MCP tools
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Discover project + milestone + issue **create/update** tools at runtime. If
|
|
42
|
+
Linear isn't connected or a needed tool is missing, relay the fix and stop,
|
|
43
|
+
**writing nothing**.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
**Validate the project states first.** Fetch the workspace's project-status
|
|
46
|
+
names and run `skitterspec spec-sync status <spec> --workspace-states <file>`; if
|
|
47
|
+
it errors (a configured `states` name isn't in the workspace), stop and fix the
|
|
48
|
+
config — Linear silently ignores an unknown project status.
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
engine can't write them itself — apply the plan over MCP:
|
|
50
|
+
## 4. Apply the plan (order matters)
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
1. **Milestones create** → create each in Linear; for each, stamp the returned id
|
|
53
|
+
into its phase file: the `ref` is the phase-file basename.
|
|
54
|
+
2. **Issues create** → create each (link to its milestone by `milestoneRef`,
|
|
55
|
+
resolving a `create` ref to the id just minted); stamp the returned identifier
|
|
56
|
+
back onto the matching task line (`ref` is the task's text).
|
|
57
|
+
3. **Milestones/issues update** → save by `id`.
|
|
58
|
+
4. **Project** → set description + status (map `status` — the local bucket — to
|
|
59
|
+
the Linear project-status name via `config.states`). Priority, labels, cycles
|
|
60
|
+
and comments are Linear-native triage — do **not** push them; they're the PM's.
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Map the local status bucket to Linear's project status through `config.states`
|
|
63
|
+
(e.g. `complete → Completed`).
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
## 5. Record the snapshot
|
|
66
|
+
|
|
67
|
+
After everything applied and the ids are stamped into the files:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
skitterspec spec-sync record <spec>
|
|
71
|
+
```
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- `create`: for each `{ text, done }`, create an issue under the project (attach it
|
|
68
|
-
to the milestone of the phase the task lives in when known), then **stamp the new
|
|
69
|
-
issue identifier inline** on that task line (`… (SKI-123)`), matching by text.
|
|
73
|
+
This writes the last-pushed snapshot from the now-stamped files, so the next
|
|
74
|
+
`/spec-push` produces an empty plan. Commit the stamped spec + snapshot into the
|
|
75
|
+
branch so the mirror-link rides in the PR.
|
|
70
76
|
|
|
71
|
-
##
|
|
77
|
+
## 6. Report
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
Summarise what was created/updated in Linear and confirm the snapshot was
|
|
80
|
+
recorded. There is no pull — Linear is a generated mirror.
|
|
@@ -1,46 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-status
|
|
3
|
-
description: Show a spec's sync status against
|
|
3
|
+
description: Show a spec's one-way sync status against Linear — a read-only drift report. Reports whether the spec changed since the last push (there's something to push) and, optionally, whether Linear's workflow-state differs from the spec's. 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 would push", or "show spec sync status".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /spec-status —
|
|
6
|
+
# /spec-status — one-way sync drift report
|
|
7
7
|
|
|
8
|
-
Read-only.
|
|
9
|
-
diverged since the last sync — the `git status` of the hybrid sync. Writes
|
|
10
|
-
nothing to either side.
|
|
8
|
+
Read-only. Reports two things and writes nothing:
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
1. **Pending push** — has the spec changed since the last push (are there
|
|
11
|
+
milestones/issues/description to create or update)?
|
|
12
|
+
2. **State drift** — does Linear's project workflow-state differ from the spec's
|
|
13
|
+
status? (The repo wins on the next push; this is just a heads-up, e.g. a card
|
|
14
|
+
moved in Linear.)
|
|
15
|
+
|
|
16
|
+
The repo is the source of truth; Linear is a generated mirror, so there is no
|
|
17
|
+
per-field "conflict" — only "what would the next push send" and "did the mirror
|
|
18
|
+
drift".
|
|
19
|
+
|
|
20
|
+
**Opt-in**: only runs when `specs/.core/linear.config.json` exists. If absent,
|
|
21
|
+
tell the user how to enable Linear sync and stop.
|
|
15
22
|
|
|
16
23
|
## 1. Identify the target spec
|
|
17
24
|
|
|
18
|
-
Use the
|
|
19
|
-
|
|
25
|
+
Use the argument, else the spec in context; ask if unclear.
|
|
26
|
+
|
|
27
|
+
## 2. Fetch the Linear project (optional, for drift)
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
If the spec has a `linear_project_id`, discover the Linear MCP read tool and write
|
|
30
|
+
the project JSON to a temp file — this lets the report compare workflow-state. If
|
|
31
|
+
Linear isn't connected, skip the drift line (still report pending-push).
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
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).
|
|
33
|
+
Optionally fetch the workspace project-status names to a file to validate the
|
|
34
|
+
configured `states` at the same time.
|
|
30
35
|
|
|
31
36
|
## 3. Run the engine
|
|
32
37
|
|
|
33
38
|
```
|
|
34
|
-
skitterspec spec-sync status <spec> --remote <
|
|
39
|
+
skitterspec spec-sync status <spec> [--remote <projectfile>] [--workspace-states <statesfile>]
|
|
35
40
|
```
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
- Reports `push: pending — N to create, M to update` or `up to date`.
|
|
43
|
+
- With `--remote`, adds a `drift:` line comparing Linear's workflow-state to the
|
|
44
|
+
spec's status.
|
|
45
|
+
- With `--workspace-states`, fails loudly if a configured state name isn't in the
|
|
46
|
+
workspace (Linear would silently no-op it).
|
|
40
47
|
|
|
41
48
|
## 4. Report
|
|
42
49
|
|
|
43
|
-
Relay the engine's
|
|
44
|
-
|
|
45
|
-
`conflict` — resolve locally or use `--force` (which backs up the losing side).
|
|
46
|
-
Never write anything from this skill.
|
|
50
|
+
Relay the engine's output verbatim. Suggest `/spec-push` if a push is pending.
|
|
51
|
+
Never write to either side.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec-linear",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Spec-driven development for Claude Code, with Linear
|
|
3
|
+
"version": "8.0.1",
|
|
4
|
+
"description": "Spec-driven development for Claude Code, with one-way Linear sync — a superset of @skitterbyte/skitterspec: the base filesystem workflow plus /spec-status · /spec-push and the spec-sync CLI. The repo is canonical; Linear is a generated mirror. Install this OR the base, not both.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
package/src/init.js
CHANGED
|
@@ -259,6 +259,30 @@ function removeRetiredFiles(dir) {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
// Prune a file the manifest records as managed but the current package no longer
|
|
263
|
+
// ships — a retired skill (e.g. `spec-pull` after the one-way switch), rule, or
|
|
264
|
+
// template. Without this, upgrading leaves a live, model-visible skill on disk
|
|
265
|
+
// whose instructions invoke a command that no longer exists. Delete it only when
|
|
266
|
+
// PRISTINE (still matches the hash we last wrote) so a user edit is never lost; a
|
|
267
|
+
// customized retired file is kept with a warning. An emptied skill folder is
|
|
268
|
+
// removed. Takes the pre-flush manifest (which still holds the retired entries).
|
|
269
|
+
function pruneRetiredManaged(dir, manifest) {
|
|
270
|
+
const managedRel = new Set(managedTargets(dir).map((t) => t.relPath))
|
|
271
|
+
for (const relPath of Object.keys(manifest.files || {})) {
|
|
272
|
+
if (managedRel.has(relPath)) continue // still shipped by this version
|
|
273
|
+
const abs = path.join(dir, relPath)
|
|
274
|
+
if (!fs.existsSync(abs)) continue // already gone
|
|
275
|
+
if (managedState(dir, relPath, manifest) === 'customized') {
|
|
276
|
+
report.warnings.push(`retired but kept (you edited it): ${relPath} — delete manually if unused`)
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
fs.unlinkSync(abs)
|
|
280
|
+
report.removed.push(rel(dir, abs))
|
|
281
|
+
const folder = path.dirname(abs)
|
|
282
|
+
if (fs.existsSync(folder) && !fs.readdirSync(folder).length) fs.rmdirSync(folder)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
262
286
|
// Scaffold the opt-in isolation templates into specs/.core/ (the example config
|
|
263
287
|
// + its field docs). Copied, not activated: the feature stays off until the
|
|
264
288
|
// consumer copies env.config.json.example → env.config.json.
|
|
@@ -409,6 +433,7 @@ function resync(dir, { force = false, claudeMd = true } = {}) {
|
|
|
409
433
|
for (const t of managedTargets(dir)) resyncManagedFile(dir, t, manifest, force)
|
|
410
434
|
installFolders(dir)
|
|
411
435
|
removeRetiredFiles(dir)
|
|
436
|
+
pruneRetiredManaged(dir, manifest)
|
|
412
437
|
if (claudeMd) installClaudeMd(dir, { mode: 'update' })
|
|
413
438
|
flushManifest(dir)
|
|
414
439
|
printReport(dir, 'resync')
|
|
Binary file
|