@skitterbyte/skitterspec-linear 10.6.0 → 10.7.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 +3 -0
- package/assets/core/linear.config.md +46 -0
- package/assets/rules/commit-trailers.md +37 -5
- package/assets/rules/spec-planning.md +17 -3
- package/assets/skills/spec/SKILL.md +20 -0
- package/assets/skills/spec-bug/SKILL.md +16 -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 +17 -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 +1 -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 +176 -39
- 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 +402 -33
- package/src/vendor/linear/config.js +91 -1
- package/src/vendor/linear/doctor.js +67 -1
- package/src/vendor/linear/released.js +85 -1
- 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
package/src/init.js
CHANGED
|
@@ -23,6 +23,21 @@ function listSkills() {
|
|
|
23
23
|
.sort()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Slash commands shipped as `assets/commands/*.md`, installed to
|
|
27
|
+
// `.claude/commands/`. Discovered from the bundled tree exactly like skills, so
|
|
28
|
+
// each distribution installs precisely what it ships.
|
|
29
|
+
function listCommands() {
|
|
30
|
+
const dir = path.join(ASSETS, 'commands')
|
|
31
|
+
try {
|
|
32
|
+
return fs
|
|
33
|
+
.readdirSync(dir)
|
|
34
|
+
.filter((f) => f.endsWith('.md'))
|
|
35
|
+
.sort()
|
|
36
|
+
} catch {
|
|
37
|
+
return [] // a distribution may ship no commands
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
function listRules() {
|
|
27
42
|
return fs
|
|
28
43
|
.readdirSync(path.join(ASSETS, 'rules'))
|
|
@@ -42,6 +57,8 @@ function listCoreTemplates() {
|
|
|
42
57
|
|
|
43
58
|
const SKILLS = listSkills()
|
|
44
59
|
|
|
60
|
+
const COMMANDS = listCommands()
|
|
61
|
+
|
|
45
62
|
const RULES = listRules()
|
|
46
63
|
|
|
47
64
|
const SPEC_FOLDERS = ['.core', 'backlog', 'in-progress', 'complete', 'cancelled']
|
|
@@ -50,6 +67,89 @@ const SPEC_FOLDERS = ['.core', 'backlog', 'in-progress', 'complete', 'cancelled'
|
|
|
50
67
|
// env.config isolation templates; a provider superset also ships its own).
|
|
51
68
|
const CORE_FILES = listCoreTemplates()
|
|
52
69
|
|
|
70
|
+
// The CLI is a local devDependency and never on PATH, so a command file that
|
|
71
|
+
// pre-executes it must carry a literal, working invocation. Detect the runner
|
|
72
|
+
// from the lockfile and bake it in at write time.
|
|
73
|
+
//
|
|
74
|
+
// The lockfile is a POSITIVE signal — a file that must be present for the answer
|
|
75
|
+
// to be yes — rather than an absence. When none is found we do not guess a
|
|
76
|
+
// package manager we have no evidence for; `npx` is the fallback because it is
|
|
77
|
+
// the one runner that works across all three installs.
|
|
78
|
+
const PACKAGE_MANAGERS = [
|
|
79
|
+
['pnpm-lock.yaml', 'pnpm exec'],
|
|
80
|
+
['yarn.lock', 'yarn'],
|
|
81
|
+
['package-lock.json', 'npx'],
|
|
82
|
+
['bun.lockb', 'bunx'],
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
function detectPackageManager(dir) {
|
|
86
|
+
for (const [lockfile, exec] of PACKAGE_MANAGERS) {
|
|
87
|
+
if (fs.existsSync(path.join(dir, lockfile))) return exec
|
|
88
|
+
}
|
|
89
|
+
return 'npx'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fill a command file's `{{exec}}` placeholders. Kept a pure function of
|
|
93
|
+
// (content, dir) so `managedTargets` can compare against exactly what
|
|
94
|
+
// `installCommands` would write — otherwise every install would hash as
|
|
95
|
+
// customized on the next run.
|
|
96
|
+
function renderCommand(content, dir) {
|
|
97
|
+
return content.split('{{exec}}').join(detectPackageManager(dir))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- composed-assets guard -------------------------------------------------
|
|
101
|
+
//
|
|
102
|
+
// A source package's `assets/` is PRE-composition: `<!-- seam:NAME -->` is still
|
|
103
|
+
// literal text that scripts/build-dist.js replaces — with a provider's fragment
|
|
104
|
+
// for the provider distribution, with nothing for the base. Only a built
|
|
105
|
+
// distribution's assets are installable.
|
|
106
|
+
//
|
|
107
|
+
// This is a POSITIVE signal, not an absence: a seam marker must be **present**
|
|
108
|
+
// for the guard to fire, so it cannot misfire on a tree it simply failed to read.
|
|
109
|
+
// And it refuses rather than repairing — installing the wrong thing is the
|
|
110
|
+
// expensive mistake here. In a dev-linked checkout the installed skills are
|
|
111
|
+
// symlinks to the composed distribution, so a `--force` install would write
|
|
112
|
+
// these markers straight through the link and into the built assets.
|
|
113
|
+
const SEAM_RE = /<!--\s*seam:[A-Za-z0-9_-]+\s*-->/
|
|
114
|
+
|
|
115
|
+
function assetFiles(dir) {
|
|
116
|
+
const out = []
|
|
117
|
+
const walk = (d) => {
|
|
118
|
+
let entries
|
|
119
|
+
try {
|
|
120
|
+
entries = fs.readdirSync(d, { withFileTypes: true })
|
|
121
|
+
} catch {
|
|
122
|
+
return // a distribution need not ship every asset kind
|
|
123
|
+
}
|
|
124
|
+
for (const e of entries) {
|
|
125
|
+
const p = path.join(d, e.name)
|
|
126
|
+
if (e.isDirectory()) walk(p)
|
|
127
|
+
else if (e.name.endsWith('.md')) out.push(p)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
walk(dir)
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Throws when this package's assets are uncomposed. Called from the **bins** —
|
|
135
|
+
// the outermost boundary, and the only way a real install happens — never from
|
|
136
|
+
// init()/resync()/run(), which the unit tests legitimately drive against this
|
|
137
|
+
// source tree. Guarding any deeper would make the library untestable while
|
|
138
|
+
// protecting nothing extra.
|
|
139
|
+
function assertComposedAssets() {
|
|
140
|
+
const offenders = assetFiles(ASSETS)
|
|
141
|
+
.filter((f) => SEAM_RE.test(fs.readFileSync(f, 'utf8')))
|
|
142
|
+
.map((f) => path.relative(ASSETS, f))
|
|
143
|
+
if (!offenders.length) return
|
|
144
|
+
throw new Error(
|
|
145
|
+
`refusing to install: this package's assets are uncomposed (${offenders.length} ` +
|
|
146
|
+
`file(s) still carry a <!-- seam:… --> marker, e.g. ${offenders[0]}).\n` +
|
|
147
|
+
' These are a workspace source package\'s assets, not a distribution\'s. ' +
|
|
148
|
+
'Run "npm run build" in the skitterspec repo, then run the command again ' +
|
|
149
|
+
'from the built distribution.',
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
53
153
|
const SPEC_MARKER_START = '<!-- skitterspec:start -->'
|
|
54
154
|
const SPEC_MARKER_END = '<!-- skitterspec:end -->'
|
|
55
155
|
|
|
@@ -99,13 +199,15 @@ function sha1(content) {
|
|
|
99
199
|
// the bundled content that ships in this distribution's assets.
|
|
100
200
|
function managedTargets(dir) {
|
|
101
201
|
const out = []
|
|
102
|
-
const add = (assetRel, targetAbs) =>
|
|
202
|
+
const add = (assetRel, targetAbs, render = (c) => c) =>
|
|
103
203
|
out.push({
|
|
104
204
|
relPath: rel(dir, targetAbs),
|
|
105
205
|
abs: targetAbs,
|
|
106
|
-
bundled: fs.readFileSync(path.join(ASSETS, assetRel), 'utf8'),
|
|
206
|
+
bundled: render(fs.readFileSync(path.join(ASSETS, assetRel), 'utf8'), dir),
|
|
107
207
|
})
|
|
108
208
|
for (const name of SKILLS) add(path.join('skills', name, 'SKILL.md'), path.join(dir, '.claude', 'skills', name, 'SKILL.md'))
|
|
209
|
+
for (const name of COMMANDS)
|
|
210
|
+
add(path.join('commands', name), path.join(dir, '.claude', 'commands', name), renderCommand)
|
|
109
211
|
for (const name of RULES) add(path.join('rules', name), path.join(dir, '.claude', 'rules', name))
|
|
110
212
|
for (const asset of CORE_FILES) add(asset, path.join(dir, 'specs', '.core', path.basename(asset)))
|
|
111
213
|
return out
|
|
@@ -230,6 +332,16 @@ function installSkills(dir, opts) {
|
|
|
230
332
|
}
|
|
231
333
|
}
|
|
232
334
|
|
|
335
|
+
function installCommands(dir, opts) {
|
|
336
|
+
for (const name of COMMANDS) {
|
|
337
|
+
const content = renderCommand(
|
|
338
|
+
fs.readFileSync(path.join(ASSETS, 'commands', name), 'utf8'),
|
|
339
|
+
dir,
|
|
340
|
+
)
|
|
341
|
+
writeFile(dir, path.join(dir, '.claude', 'commands', name), content, opts)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
233
345
|
function installRule(dir, opts) {
|
|
234
346
|
for (const name of RULES) {
|
|
235
347
|
copyAsset(
|
|
@@ -519,6 +631,7 @@ function reset(dir, { claudeMd = true } = {}) {
|
|
|
519
631
|
}
|
|
520
632
|
if (claudeMd) stripClaudeMdSection(dir)
|
|
521
633
|
installSkills(dir, { force: true })
|
|
634
|
+
installCommands(dir, { force: true })
|
|
522
635
|
installRule(dir, { force: true })
|
|
523
636
|
installFolders(dir)
|
|
524
637
|
removeRetiredFiles(dir)
|
|
@@ -597,6 +710,7 @@ async function init({ dir, force, claudeMd, mode, isolation }) {
|
|
|
597
710
|
resetReport()
|
|
598
711
|
|
|
599
712
|
installSkills(dir, { force })
|
|
713
|
+
installCommands(dir, { force })
|
|
600
714
|
installRule(dir, { force })
|
|
601
715
|
installFolders(dir)
|
|
602
716
|
removeRetiredFiles(dir)
|
|
@@ -615,6 +729,7 @@ async function init({ dir, force, claudeMd, mode, isolation }) {
|
|
|
615
729
|
module.exports = {
|
|
616
730
|
init,
|
|
617
731
|
SKILLS,
|
|
732
|
+
COMMANDS,
|
|
618
733
|
RULES,
|
|
619
734
|
SPEC_FOLDERS,
|
|
620
735
|
MANIFEST_FILE,
|
|
@@ -627,4 +742,7 @@ module.exports = {
|
|
|
627
742
|
resync,
|
|
628
743
|
reset,
|
|
629
744
|
assertSafeToDelete,
|
|
745
|
+
assertComposedAssets,
|
|
746
|
+
detectPackageManager,
|
|
747
|
+
renderCommand,
|
|
630
748
|
}
|