@skitterbyte/skitterspec-linear 10.6.0 → 10.8.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/claude-md-section.md +19 -9
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/core/ci-stages.md +110 -0
- package/assets/core/env.config.md +18 -0
- package/assets/core/linear.config.json.example +4 -0
- package/assets/core/linear.config.md +88 -0
- package/assets/rules/commit-trailers.md +37 -5
- package/assets/rules/spec-planning.md +19 -4
- package/assets/skills/spec/SKILL.md +20 -0
- package/assets/skills/spec-bug/SKILL.md +48 -2
- package/assets/skills/spec-cancel/SKILL.md +9 -0
- package/assets/skills/spec-complete/SKILL.md +12 -1
- package/assets/skills/spec-go/SKILL.md +10 -8
- package/assets/skills/spec-hotfix/SKILL.md +49 -3
- package/assets/skills/spec-linear-setup/SKILL.md +38 -2
- package/assets/skills/spec-status/SKILL.md +1 -0
- package/assets/skills/spec-sync/SKILL.md +9 -0
- package/assets/skills/spec-to-main/SKILL.md +2 -1
- package/bin/skitterspec-linear.js +10 -0
- package/package.json +1 -1
- package/src/cli.js +211 -44
- package/src/env/config.js +19 -0
- package/src/env/teardown.js +62 -4
- package/src/init.js +120 -2
- package/src/vendor/linear/cli-sync.js +454 -34
- package/src/vendor/linear/config.js +158 -1
- package/src/vendor/linear/doctor.js +67 -1
- package/src/vendor/linear/released.js +149 -5
- package/src/vendor/sync-core/index.js +4 -1
- package/src/vendor/sync-core/src/compare.js +59 -3
- package/src/vendor/sync-core/src/normalize.js +65 -2
- package/assets/skills/spec-connect/SKILL.md +0 -59
- package/assets/skills/spec-live/SKILL.md +0 -73
|
@@ -818,10 +818,31 @@ function phasesWithheld(snapshotDir, config) {
|
|
|
818
818
|
|
|
819
819
|
// --- remote projection ------------------------------------------------------
|
|
820
820
|
|
|
821
|
+
// The lifecycle bucket a deployment ladder descends FROM. A spec is handed over
|
|
822
|
+
// to the deploy pipeline once it is finished, so every rung sits downstream of
|
|
823
|
+
// `complete` — a ticket on "On Test" is a completed spec that has been deployed,
|
|
824
|
+
// not a spec that moved somewhere else.
|
|
825
|
+
const LADDER_ORIGIN_BUCKET = 'complete'
|
|
826
|
+
|
|
827
|
+
// The declared ladder rung with this state name, or null.
|
|
828
|
+
function stageForState(state, config) {
|
|
829
|
+
if (state == null) return null
|
|
830
|
+
const want = String(state).toLowerCase().trim()
|
|
831
|
+
const stages = (config && config.release && config.release.stages) || []
|
|
832
|
+
if (!Array.isArray(stages)) return null
|
|
833
|
+
return stages.find((s) => s && typeof s.state === 'string' && s.state.toLowerCase().trim() === want) || null
|
|
834
|
+
}
|
|
835
|
+
|
|
821
836
|
// Map a remote workflow-state name back to the local lifecycle bucket (the
|
|
822
837
|
// vocabulary `spec_status` uses) via config.states, so local and remote
|
|
823
838
|
// workflowState hash equal when semantically equal. Falls back to a lowercased
|
|
824
839
|
// raw value when the state isn't one of the configured names.
|
|
840
|
+
//
|
|
841
|
+
// A DECLARED DEPLOYMENT STAGE reads as `complete`, deliberately. Without this
|
|
842
|
+
// the fallback lowercases it — "On Test" becomes "on test", which equals no
|
|
843
|
+
// bucket — and every deployed spec reports as drifted forever, for the whole
|
|
844
|
+
// time it sits in the pipeline. The project told us these states are downstream
|
|
845
|
+
// of a finished spec, so they are not a disagreement about where the spec is.
|
|
825
846
|
function bucketForState(state, config) {
|
|
826
847
|
if (state == null) return null
|
|
827
848
|
const states = (config && config.states) || {}
|
|
@@ -829,6 +850,7 @@ function bucketForState(state, config) {
|
|
|
829
850
|
for (const [bucket, name] of Object.entries(states)) {
|
|
830
851
|
if (typeof name === 'string' && name.toLowerCase().trim() === want) return bucket
|
|
831
852
|
}
|
|
853
|
+
if (stageForState(state, config)) return LADDER_ORIGIN_BUCKET
|
|
832
854
|
return want
|
|
833
855
|
}
|
|
834
856
|
|
|
@@ -860,6 +882,13 @@ function remoteWorkflowState(issue, config) {
|
|
|
860
882
|
return name != null ? bucketForState(name, config) : null
|
|
861
883
|
}
|
|
862
884
|
|
|
885
|
+
// The declared ladder rung a remote issue is currently sitting on, or null.
|
|
886
|
+
// Pairs with `remoteWorkflowState`: that says which bucket the issue maps to,
|
|
887
|
+
// this says whether it got there by being deployed. Read-only.
|
|
888
|
+
function remoteStage(issue, config) {
|
|
889
|
+
return stageForState(remoteStateName(issue || {}), config)
|
|
890
|
+
}
|
|
891
|
+
|
|
863
892
|
// A Linear issue title is plain text, so markdown emphasis is noise there — and
|
|
864
893
|
// worse, an emphasis run cut mid-title (or a bold LABEL like `**1. Foo**`) can
|
|
865
894
|
// leave a dangling `**`. Strip `*` emphasis markers and unwrap `[text](url)` to
|
|
@@ -932,11 +961,26 @@ function titleFromText(text, max = 100) {
|
|
|
932
961
|
return stripTitleMarkup(title)
|
|
933
962
|
}
|
|
934
963
|
|
|
964
|
+
// Every state NAME the config points at: the lifecycle bucket map, plus the
|
|
965
|
+
// project's deployment ladder when it declares one. Both reach Linear the same
|
|
966
|
+
// way and fail the same way, so both are checked by the same lookup.
|
|
967
|
+
function configuredStateNames(config) {
|
|
968
|
+
const names = Object.values((config && config.states) || {}).filter((v) => typeof v === 'string')
|
|
969
|
+
const stages = (config && config.release && config.release.stages) || []
|
|
970
|
+
for (const stage of Array.isArray(stages) ? stages : []) {
|
|
971
|
+
if (stage && typeof stage.state === 'string') names.push(stage.state)
|
|
972
|
+
}
|
|
973
|
+
return names
|
|
974
|
+
}
|
|
975
|
+
|
|
935
976
|
// Which configured state NAMES are absent from the live workspace. The skill
|
|
936
977
|
// fetches the workspace's project-status names over MCP and passes them here;
|
|
937
978
|
// a non-empty result means a typo/rename that Linear would silently no-op.
|
|
979
|
+
//
|
|
980
|
+
// A project with no `release.stages` contributes nothing here, so the ladder
|
|
981
|
+
// cannot make this accuse a config that was fine before it existed.
|
|
938
982
|
function validateStates(config, workspaceStates) {
|
|
939
|
-
const configured =
|
|
983
|
+
const configured = configuredStateNames(config)
|
|
940
984
|
const have = new Set((workspaceStates || []).map((s) => String(s).toLowerCase().trim()))
|
|
941
985
|
return configured.filter((name) => !have.has(name.toLowerCase().trim()))
|
|
942
986
|
}
|
|
@@ -971,13 +1015,29 @@ function stateSuggestions(config, workspaceStates) {
|
|
|
971
1015
|
if (have.has(configured.toLowerCase().trim())) continue
|
|
972
1016
|
const words = BUCKET_WORDS[bucket] || []
|
|
973
1017
|
const suggestion = names.find((n) => words.includes(n.toLowerCase().trim())) || null
|
|
974
|
-
out.push({ bucket, configured, suggestion })
|
|
1018
|
+
out.push({ bucket, configured, suggestion, label: `states.${bucket}` })
|
|
1019
|
+
}
|
|
1020
|
+
// Deployment-ladder rungs, reported the same way. No suggestion is offered:
|
|
1021
|
+
// BUCKET_WORDS describes lifecycle vocabulary, and a stage name is the
|
|
1022
|
+
// project's own — guessing "On Test" meant "In Progress" would be worse than
|
|
1023
|
+
// saying nothing.
|
|
1024
|
+
const stages = (config && config.release && config.release.stages) || []
|
|
1025
|
+
for (const stage of Array.isArray(stages) ? stages : []) {
|
|
1026
|
+
if (!stage || typeof stage.state !== 'string') continue
|
|
1027
|
+
if (have.has(stage.state.toLowerCase().trim())) continue
|
|
1028
|
+
out.push({
|
|
1029
|
+
bucket: null,
|
|
1030
|
+
configured: stage.state,
|
|
1031
|
+
suggestion: null,
|
|
1032
|
+
label: `release.stages[${stage.key}]`,
|
|
1033
|
+
})
|
|
975
1034
|
}
|
|
976
1035
|
return out
|
|
977
1036
|
}
|
|
978
1037
|
|
|
979
1038
|
module.exports = {
|
|
980
1039
|
stateSuggestions,
|
|
1040
|
+
configuredStateNames,
|
|
981
1041
|
normalizeLocal,
|
|
982
1042
|
phaseProjection,
|
|
983
1043
|
phaseModeFor,
|
|
@@ -994,5 +1054,8 @@ module.exports = {
|
|
|
994
1054
|
canonicalizeMarkdown,
|
|
995
1055
|
joinEmphasisAcrossBreaks,
|
|
996
1056
|
bucketForState,
|
|
1057
|
+
stageForState,
|
|
1058
|
+
remoteStage,
|
|
997
1059
|
remoteWorkflowState,
|
|
1060
|
+
LADDER_ORIGIN_BUCKET,
|
|
998
1061
|
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: spec-connect
|
|
3
|
-
description: Point your local canonical origin (localhost:3000/:8080) at a spec's running dev servers so you can test a worktree's UI/API changes at the normal URL — or `spec-connect main` to hand the ports back to your main checkout. Runs `skitterspec spec-env connect` (a small bundled reverse proxy). Opt-in — needs specs/.core/env.config.json with a `dev` block. Use when the user says "/spec-connect", "test <spec> locally", "point local at <spec>", or "connect to <spec>".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /spec-connect — expose one spec on the canonical ports
|
|
7
|
-
|
|
8
|
-
Make `http://localhost:<frontPort>` serve a **spec's** warm dev servers instead of
|
|
9
|
-
your main checkout's, so you can test a worktree's UI/API at the exact URL you
|
|
10
|
-
always use — no bookmark, base-URL, or OAuth-callback changes. **Exclusive:** one
|
|
11
|
-
spec is exposed at a time. `spec-connect main` stops the proxy and hands the ports
|
|
12
|
-
back to your primary checkout.
|
|
13
|
-
|
|
14
|
-
This skill is **opt-in**: it needs `specs/.core/env.config.json` with a `dev`
|
|
15
|
-
block (host dev servers + their `frontPort`s). If isolation or `dev` is absent,
|
|
16
|
-
say so and stop.
|
|
17
|
-
|
|
18
|
-
**Lighter alternative for a code-only spec:** `/spec-live` reuses the dev server
|
|
19
|
-
you already have running (it branch-switches the primary checkout) instead of
|
|
20
|
-
starting a second stack — no proxy, one process. Prefer it for code-only specs;
|
|
21
|
-
use `/spec-connect` when a spec has its own Docker stack, or to run several stacks
|
|
22
|
-
in parallel.
|
|
23
|
-
|
|
24
|
-
## 1. Identify the target
|
|
25
|
-
|
|
26
|
-
- Use the spec named as an argument. The literal `main` means **disconnect**
|
|
27
|
-
(hand the ports back to the primary checkout). Else use the spec **currently in
|
|
28
|
-
context**; if unclear, ask.
|
|
29
|
-
|
|
30
|
-
## 2. Make sure the spec's dev servers are running
|
|
31
|
-
|
|
32
|
-
`connect` proxies to a spec's dev servers on its reserved port block — it does
|
|
33
|
-
**not** start them. If they aren't up yet, start them first:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
skitterspec spec-env dev up <spec>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
(This is automatic under `/spec-go`; run it by hand only when connecting a spec
|
|
40
|
-
whose servers you stopped.)
|
|
41
|
-
|
|
42
|
-
## 3. Connect (or disconnect)
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
skitterspec spec-env connect <spec> # expose <spec> on the canonical ports
|
|
46
|
-
skitterspec spec-env connect main # stop the proxy — main owns the ports
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
The engine (re)starts a small bundled Node reverse proxy and **prints** the
|
|
50
|
-
canonical URL → spec-port mapping. **If it reports a canonical port is in use**,
|
|
51
|
-
your **main dev server still holds it** — stop main on that port, then re-run
|
|
52
|
-
(the proxy can't share a port main is bound to). Relay the printed message.
|
|
53
|
-
|
|
54
|
-
## 4. Report
|
|
55
|
-
|
|
56
|
-
Echo which spec is now on the canonical ports (and the URLs), or that the proxy
|
|
57
|
-
was stopped and main owns them again. Switching to a different spec is just
|
|
58
|
-
`spec-connect <other>` — the dev servers stay warm, so it's a near-instant
|
|
59
|
-
re-point.
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: spec-live
|
|
3
|
-
description: Test a spec on your already-running dev server by checking its branch out in the primary checkout — no second stack, no proxy. `spec-live <spec>` takes the running instance for that spec; `spec-live main` releases it. Runs `skitterspec spec-env live`. Opt-in — needs specs/.core/env.config.json. Code-only specs; stateful (Docker/migration) specs use /spec-connect. Use when the user says "/spec-live", "go live with <spec>", "take the instance for <spec>", or "test <spec> on the running server".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /spec-live — put one spec live on the running instance
|
|
7
|
-
|
|
8
|
-
Instead of running a second dev stack for a spec (that's `/spec-connect`), **reuse
|
|
9
|
-
the one instance you already have**: rebase the spec's branch onto base, hand it
|
|
10
|
-
from its worktree to the **primary checkout**, and let your running dev server
|
|
11
|
-
hot-reload it. You test at your normal URL, with one process. The branch that's
|
|
12
|
-
checked out in the primary checkout **is** the lock — exactly one spec is live at
|
|
13
|
-
a time, and `/spec-live main` hands the instance back.
|
|
14
|
-
|
|
15
|
-
This skill is **opt-in**: it needs `specs/.core/env.config.json`. If isolation is
|
|
16
|
-
absent, say so and stop.
|
|
17
|
-
|
|
18
|
-
**Code-only.** Live overlay refuses a **stateful** spec — one whose `> **Stack:**`
|
|
19
|
-
is `worktree + docker`, or whose branch changes migrations (per
|
|
20
|
-
`env.config.json` → `live.migrations`). Those keep their isolated stack; use
|
|
21
|
-
`/spec-connect` for them. It also **always refuses a `Type: Hotfix` spec** — its
|
|
22
|
-
branch is built on an old release tag, so hot-reloading it onto the running dev
|
|
23
|
-
server could break the shared instance; test a hotfix with `/spec-connect`. The
|
|
24
|
-
engine enforces all of this and prints why.
|
|
25
|
-
|
|
26
|
-
## 1. Identify the target
|
|
27
|
-
|
|
28
|
-
- Use the spec named as an argument. The literal `main` means **release** (hand
|
|
29
|
-
the instance back to base). Else use the spec **currently in context**; if
|
|
30
|
-
unclear, ask.
|
|
31
|
-
|
|
32
|
-
## 2. Make sure a dev server is running
|
|
33
|
-
|
|
34
|
-
`live take` **verifies** a dev server is up on your canonical ports and switches
|
|
35
|
-
the branch under it — it does **not** start one. If nothing is listening it
|
|
36
|
-
refuses; start your dev server first (however you normally run it, or
|
|
37
|
-
`skitterspec spec-env dev up <spec>`). (Projects with no `dev` servers configured
|
|
38
|
-
have nothing to hot-reload — the switch still happens, with a warning.)
|
|
39
|
-
|
|
40
|
-
## 3. Take (or release)
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
skitterspec spec-env live take <spec> # rebase → detach worktree → checkout in primary
|
|
44
|
-
skitterspec spec-env live release # hand the instance back to base, re-isolate the branch
|
|
45
|
-
skitterspec spec-env live abort # crash recovery (see below)
|
|
46
|
-
skitterspec spec-env live status # who's live (branch in the primary checkout + receipt)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Take** rebases the branch onto base, frees it from its worktree
|
|
50
|
-
(`switch --detach`), checks it out in the primary checkout, then writes a receipt
|
|
51
|
-
(`.spec-env/live.json`). Relay its output. **If it reports the rebase hit
|
|
52
|
-
conflicts**, it left everything untouched — rebase the branch in its worktree,
|
|
53
|
-
resolve, then retry. **If it says a spec already holds the instance**, release it
|
|
54
|
-
first. If it warns dependencies changed, restart your dev server after the switch.
|
|
55
|
-
|
|
56
|
-
**Release** (`/spec-live main`) is the graceful exit of an unfinished session:
|
|
57
|
-
`skitterspec spec-env live release` reads the live spec from the receipt, checks
|
|
58
|
-
base back out in the primary checkout, re-attaches the branch to its worktree, and
|
|
59
|
-
clears the receipt. Commit any fixes to the branch first — it refuses on a dirty
|
|
60
|
-
tree rather than discard them. (To *finish* a live spec instead of releasing it,
|
|
61
|
-
use `/spec-complete`, which is live-aware.)
|
|
62
|
-
|
|
63
|
-
**Abort** is crash recovery, for when a session died mid-take and left the primary
|
|
64
|
-
checkout on a feature branch: `skitterspec spec-env live abort` restores base from
|
|
65
|
-
the receipt and re-isolates. It refuses if the primary checkout has uncommitted
|
|
66
|
-
changes (it won't discard them) — commit or stash first.
|
|
67
|
-
|
|
68
|
-
## 4. Report
|
|
69
|
-
|
|
70
|
-
Echo which spec is now live on the primary checkout (and any warning), that it was
|
|
71
|
-
released / recovered, or — for `status` — which branch the primary checkout is on
|
|
72
|
-
and whether the instance is free. Fixes you make while live commit straight onto
|
|
73
|
-
the spec's branch.
|