@skitterbyte/skitterspec-linear 10.2.0 → 10.3.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/bin/skitterspec-linear.js +27 -13
- package/package.json +1 -1
- package/src/cli.js +25 -2
- package/src/vendor/linear/cli-sync.js +13 -8
- package/src/vendor/linear/commands.js +54 -0
|
@@ -4,8 +4,13 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* The Linear-provider distribution's bin — a superset of the base CLI.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The provider's own commands are routed from ONE table (`src/commands.js`),
|
|
8
|
+
* which also generates their `--help` section; every other command (`init`,
|
|
9
|
+
* `update`, `spec-env`, …) delegates to the base CLI unchanged.
|
|
10
|
+
*
|
|
11
|
+
* `--help` is the exception that has to be handled here rather than delegated:
|
|
12
|
+
* the base prints its own HELP const, which cannot know what a provider adds, so
|
|
13
|
+
* delegating made this distribution report that `spec-sync` did not exist.
|
|
9
14
|
*/
|
|
10
15
|
|
|
11
16
|
// This package's bin/, src/ and assets/ are COMPOSED by scripts/build-dist.js and
|
|
@@ -27,24 +32,33 @@ if (!existsSync(join(__dirname, '..', 'src'))) {
|
|
|
27
32
|
process.exit(1)
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
const { run } = require('../src/cli.js')
|
|
31
|
-
const {
|
|
32
|
-
|
|
35
|
+
const { run, HELP } = require('../src/cli.js')
|
|
36
|
+
const {
|
|
37
|
+
PROVIDER_COMMANDS,
|
|
38
|
+
providerHelpSection,
|
|
39
|
+
} = require('../src/vendor/linear/commands.js')
|
|
33
40
|
|
|
34
41
|
async function main(argv) {
|
|
35
42
|
const [cmd, ...rest] = argv
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
process.
|
|
43
|
+
|
|
44
|
+
// Base help + what this distribution adds. Matched on the COMMAND SLOT only,
|
|
45
|
+
// never the whole argv: `spec-sanitise --help` must reach that command's own
|
|
46
|
+
// help, not be swallowed by the top-level one.
|
|
47
|
+
if (!cmd || cmd === '--help' || cmd === '-h') {
|
|
48
|
+
process.stdout.write(`${HELP}\n${providerHelpSection()}`)
|
|
42
49
|
return
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
|
|
52
|
+
const provider = PROVIDER_COMMANDS[cmd]
|
|
53
|
+
if (provider) {
|
|
54
|
+
// Propagate the exit code. Dropping it made `spec-sync status
|
|
55
|
+
// --workspace-states` (a bad state name) and `stamp` (a refused write) both
|
|
56
|
+
// look successful to any caller checking $?, which is exactly what the
|
|
57
|
+
// /spec-push skill does before it applies a plan.
|
|
58
|
+
process.exitCode = await provider.run(rest)
|
|
46
59
|
return
|
|
47
60
|
}
|
|
61
|
+
|
|
48
62
|
await run(argv)
|
|
49
63
|
}
|
|
50
64
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skitterbyte/skitterspec-linear",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.3.0",
|
|
4
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",
|
package/src/cli.js
CHANGED
|
@@ -48,6 +48,29 @@ const { renderRoutes, portsInUse, waitListening } = require('./env/proxy.js')
|
|
|
48
48
|
|
|
49
49
|
const pkg = require('../package.json')
|
|
50
50
|
|
|
51
|
+
// Commands this (tracker-free) base does NOT ship, and the distribution that
|
|
52
|
+
// does. Without this the base says only "unknown command: spec-sync", which a
|
|
53
|
+
// user correctly reads as "no such feature" — nothing anywhere named the
|
|
54
|
+
// distribution that has it, so they were stranded. Naming Linear here is a
|
|
55
|
+
// diagnostic string, not provider machinery: `init.js` already knows
|
|
56
|
+
// `linear.config.json` and `linear-base/` by name in order to protect them.
|
|
57
|
+
const PROVIDER_COMMANDS = {
|
|
58
|
+
'spec-sync': '@skitterbyte/skitterspec-linear',
|
|
59
|
+
'spec-sanitise': '@skitterbyte/skitterspec-linear',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function unknownCommand(cmd) {
|
|
63
|
+
const dist = PROVIDER_COMMANDS[cmd]
|
|
64
|
+
if (dist) {
|
|
65
|
+
return (
|
|
66
|
+
`unknown command: ${cmd} — this is the base distribution, which does not ` +
|
|
67
|
+
`ship it.\n ${cmd} comes from ${dist} (a superset of this package): ` +
|
|
68
|
+
`install that instead.`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
return `unknown command: ${cmd} (try --help)`
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
const HELP = `skitterspec — spec-driven-development for Claude Code
|
|
52
75
|
|
|
53
76
|
Usage:
|
|
@@ -1384,8 +1407,8 @@ async function run(argv) {
|
|
|
1384
1407
|
await cleanupReleaseTooling(dir, opts)
|
|
1385
1408
|
break
|
|
1386
1409
|
default:
|
|
1387
|
-
throw new Error(
|
|
1410
|
+
throw new Error(unknownCommand(cmd))
|
|
1388
1411
|
}
|
|
1389
1412
|
}
|
|
1390
1413
|
|
|
1391
|
-
module.exports = { run, parse }
|
|
1414
|
+
module.exports = { run, parse, HELP, unknownCommand }
|
|
@@ -158,8 +158,15 @@ function warnToErr(snapshotDir, config, err) {
|
|
|
158
158
|
if (lines.length) err.write(lines.join('\n') + '\n')
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// Resolve a spec argument to its folder, or null with a message on stdout.
|
|
162
|
+
// EVERY failure path here must print and EVERY caller must return a non-zero
|
|
163
|
+
// code: /spec-push checks $? before it applies a plan, so a resolve failure that
|
|
164
|
+
// exits 0 reads as "nothing to do" rather than "I could not find the spec".
|
|
161
165
|
function resolveOrExit(specArg, dir, out) {
|
|
162
|
-
if (!specArg)
|
|
166
|
+
if (!specArg) {
|
|
167
|
+
out.write('spec-sync: no spec given\n')
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
163
170
|
const snapshotDir = resolveSnapshotDir(specArg, dir)
|
|
164
171
|
if (!snapshotDir) {
|
|
165
172
|
out.write(`spec-sync: spec not found: ${specArg}\n`)
|
|
@@ -171,7 +178,7 @@ function resolveOrExit(specArg, dir, out) {
|
|
|
171
178
|
// `spec-sync normalize <spec>` — print the local projection as JSON.
|
|
172
179
|
function specSyncNormalize(dir, config, specArg, out, err) {
|
|
173
180
|
const snapshotDir = resolveOrExit(specArg, dir, out)
|
|
174
|
-
if (!snapshotDir) return
|
|
181
|
+
if (!snapshotDir) return 1
|
|
175
182
|
// stdout is the projection and nothing else — callers pipe it into jq.
|
|
176
183
|
warnToErr(snapshotDir, config, err)
|
|
177
184
|
out.write(JSON.stringify(projectionOf(snapshotDir, config), null, 2) + '\n')
|
|
@@ -404,7 +411,7 @@ function specSyncStamp(dir, config, specArg, flags, out) {
|
|
|
404
411
|
// files. The skill calls this AFTER applying the plan and stamping new ids.
|
|
405
412
|
function specSyncRecord(dir, config, specArg, out) {
|
|
406
413
|
const snapshotDir = resolveOrExit(specArg, dir, out)
|
|
407
|
-
if (!snapshotDir) return
|
|
414
|
+
if (!snapshotDir) return 1
|
|
408
415
|
const identifier = specIdentifier(snapshotDir, config)
|
|
409
416
|
const file = recordPush({ dir, snapshotDir, identifier, config })
|
|
410
417
|
out.write(`spec-sync record: snapshot written → ${path.relative(dir, file)}\n`)
|
|
@@ -417,7 +424,7 @@ function specSyncRecord(dir, config, specArg, out) {
|
|
|
417
424
|
// configured state names and fails loudly on a typo Linear would silently no-op.
|
|
418
425
|
function specSyncStatus(dir, config, specArg, flags, out) {
|
|
419
426
|
const snapshotDir = resolveOrExit(specArg, dir, out)
|
|
420
|
-
if (!snapshotDir) return
|
|
427
|
+
if (!snapshotDir) return 1
|
|
421
428
|
const identifier = specIdentifier(snapshotDir, config)
|
|
422
429
|
const lines = [`spec-sync status: ${identifier}`, ...warningLines(snapshotDir, config)]
|
|
423
430
|
|
|
@@ -1227,15 +1234,13 @@ async function specSync(rest, io = {}) {
|
|
|
1227
1234
|
|
|
1228
1235
|
switch (sub) {
|
|
1229
1236
|
case 'normalize':
|
|
1230
|
-
specSyncNormalize(dir, config, positional[0], out, err)
|
|
1231
|
-
return 0
|
|
1237
|
+
return specSyncNormalize(dir, config, positional[0], out, err) || 0
|
|
1232
1238
|
case 'push':
|
|
1233
1239
|
return specSyncPush(dir, config, positional[0], flags, out, err) || 0
|
|
1234
1240
|
case 'stamp':
|
|
1235
1241
|
return specSyncStamp(dir, config, positional[0], flags, out)
|
|
1236
1242
|
case 'record':
|
|
1237
|
-
specSyncRecord(dir, config, positional[0], out)
|
|
1238
|
-
return 0
|
|
1243
|
+
return specSyncRecord(dir, config, positional[0], out) || 0
|
|
1239
1244
|
case 'status':
|
|
1240
1245
|
return specSyncStatus(dir, config, positional[0], flags, out) || 0
|
|
1241
1246
|
case 'projects':
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The commands this provider distribution adds on top of the base CLI.
|
|
5
|
+
*
|
|
6
|
+
* ONE table drives both routing and `--help`. That pairing is the point: the bug
|
|
7
|
+
* this fixes was `spec-sync` being routed by the bin while the base's `HELP`
|
|
8
|
+
* const knew nothing about it, so the one distribution that ships the command
|
|
9
|
+
* told users it did not exist. Anything added here is routed and documented in
|
|
10
|
+
* the same edit — the two cannot drift, and a test asserts it.
|
|
11
|
+
*
|
|
12
|
+
* `run(rest)` returns an exit code (the bin propagates it); `summary` is the
|
|
13
|
+
* one-line description `--help` prints.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { specSync } = require('./cli-sync.js')
|
|
17
|
+
const { specSanitise } = require('./cli-sanitise.js')
|
|
18
|
+
|
|
19
|
+
const DIST = '@skitterbyte/skitterspec-linear'
|
|
20
|
+
|
|
21
|
+
const PROVIDER_COMMANDS = {
|
|
22
|
+
'spec-sync': {
|
|
23
|
+
run: specSync,
|
|
24
|
+
usage: 'skitterspec spec-sync <cmd>',
|
|
25
|
+
summary:
|
|
26
|
+
'One-way sync to Linear (repo -> tracker; opt-in, needs\n' +
|
|
27
|
+
'specs/.core/linear.config.json). Run it with no args to\n' +
|
|
28
|
+
'list its subcommands.',
|
|
29
|
+
},
|
|
30
|
+
'spec-sanitise': {
|
|
31
|
+
run: specSanitise,
|
|
32
|
+
usage: 'skitterspec spec-sanitise',
|
|
33
|
+
summary:
|
|
34
|
+
'Rewrite spec markdown so no emphasis or link straddles a\n' +
|
|
35
|
+
'line break. Dry-run; --write to apply.',
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The `--help` section for these commands, in the base HELP's column layout:
|
|
40
|
+
// two-space indent, description starting at column 30, continuations aligned.
|
|
41
|
+
const COL = 30
|
|
42
|
+
|
|
43
|
+
function providerHelpSection() {
|
|
44
|
+
const lines = [`Provider commands (${DIST}):`]
|
|
45
|
+
for (const name of Object.keys(PROVIDER_COMMANDS)) {
|
|
46
|
+
const { usage, summary } = PROVIDER_COMMANDS[name]
|
|
47
|
+
const [first, ...rest] = summary.split('\n')
|
|
48
|
+
lines.push(` ${usage.padEnd(COL - 2)}${first}`)
|
|
49
|
+
for (const line of rest) lines.push(`${' '.repeat(COL)}${line}`)
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n') + '\n'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { PROVIDER_COMMANDS, providerHelpSection, DIST }
|