@benjaminthomas/spec-driven-dev 1.5.0 → 1.5.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 CHANGED
@@ -375,6 +375,13 @@ run one after another, stopping at the first failure. Two steps always wait for
375
375
  spec, and pushing or opening the PR. You don't need slash commands either. Saying "investigate
376
376
  STP-123" or "ship it" picks the right skill.
377
377
 
378
+ **Setup didn't ask me anything.**
379
+ Update to 1.5.1 or later (`npx @benjaminthomas/spec-driven-dev@latest --version`). Earlier versions
380
+ crashed on Node 14 or 16, which nvm often switches to inside older projects, and wrote a config
381
+ silently when there was no terminal. Now it runs on Node 14+. With no terminal to answer in (a `!`
382
+ command in Claude Code, an editor task, piped input), it stops, changes nothing, and tells you to run
383
+ it in a terminal window or pass flags such as `--layout=multi-repo --repos=…`.
384
+
378
385
  **Setup didn't ask me about repos.**
379
386
  It only asks about repos for a multi-repo or monorepo layout. If it detected `single`, type
380
387
  `multi-repo` at the layout question, then add your other repos by path. If it asked nothing at all,
@@ -455,7 +462,8 @@ can otherwise reuse an older copy it cached. What changed in each version is in
455
462
  `npx github:benjaminthomas/spec-driven-dev` installs straight from GitHub instead, for example to
456
463
  try an unreleased commit on `main`.
457
464
 
458
- **Requirements:** Node.js 18 or newer and git. The installer has no dependencies.
465
+ **Requirements:** Node.js 14 or newer and git. The installer has no dependencies, and CI tests it on
466
+ Node 14, 16, 18, 20, 22, and 24.
459
467
 
460
468
  ### Claude Code (plugin, with automatic updates)
461
469
 
package/bin/cli.mjs CHANGED
@@ -13,7 +13,7 @@ import fs from "node:fs";
13
13
  import os from "node:os";
14
14
  import path from "node:path";
15
15
  import { execFileSync } from "node:child_process";
16
- import { createInterface } from "node:readline/promises";
16
+ import { createInterface } from "node:readline"; // callback API: readline/promises needs Node 17+
17
17
  import { fileURLToPath } from "node:url";
18
18
 
19
19
  const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
@@ -40,8 +40,21 @@ if (flag("version") || flag("v")) {
40
40
  console.log(JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version);
41
41
  process.exit(0);
42
42
  }
43
- if (["install", "setup"].includes(command) && !process.stdin.isTTY && !flag("yes") && !flag("y"))
44
- console.log("• No interactive terminal detected, so no questions will be asked: using the detected values.\n Run it directly in a terminal to answer them, or pass flags (--layout, --repos, --tools …). Add --yes to hide this note.");
43
+ // No terminal to answer in (e.g. `!` in Claude Code, an IDE task, a pipe) and nothing passed explicitly:
44
+ // stop rather than write a config from guesses.
45
+ const ANSWER_FLAGS = ["yes", "y", "layout", "repos", "packages", "tools", "workspace", "after-approval", "skills", "global", "no-setup"];
46
+ if (["install", "setup"].includes(command) && !process.stdin.isTTY && !args.some((a) => ANSWER_FLAGS.some((f) => a === `--${f}` || a.startsWith(`--${f}=`)))) {
47
+ console.log(`✗ No interactive terminal, so setup can't ask its questions. Nothing was changed.
48
+
49
+ This happens when it runs through another program: a "!" command in Claude Code, an editor task,
50
+ or piped input. Either:
51
+ • run it yourself in a terminal window (Terminal, iTerm, or your editor's terminal panel):
52
+ npx @benjaminthomas/spec-driven-dev@latest ${command === "setup" ? "setup" : ""}
53
+ • or give the answers as flags, for example:
54
+ npx @benjaminthomas/spec-driven-dev@latest setup --layout=multi-repo --repos=~/Projects/frontend,~/Projects/backend
55
+ (add --yes to accept everything it detects)`);
56
+ process.exit(1);
57
+ }
45
58
 
46
59
  if (flag("help") || flag("h")) {
47
60
  console.log(fs.readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1, 11).join("\n").replace(/^\/\/ ?/gm, ""));
@@ -51,7 +64,7 @@ if (flag("help") || flag("h")) {
51
64
  const rl = YES ? null : createInterface({ input: process.stdin, output: process.stdout });
52
65
  async function ask(q, def = "") {
53
66
  if (YES) return def;
54
- const a = (await rl.question(`${q}${def ? ` [${def}]` : ""}: `)).trim();
67
+ const a = (await new Promise((resolve) => rl.question(`${q}${def ? ` [${def}]` : ""}: `, resolve))).trim();
55
68
  return a || def;
56
69
  }
57
70
  async function pick(q, items, defaults) {
@@ -68,6 +81,17 @@ const run = (cmd, argv, cwd) => {
68
81
  };
69
82
  const onPath = (bin) => !!run(process.platform === "win32" ? "where" : "which", [bin]);
70
83
 
84
+ // fs.cpSync needs Node 16.7+; this keeps Node 14 working. Follows symlinks, skips .DS_Store.
85
+ function copyDir(from, to) {
86
+ fs.mkdirSync(to, { recursive: true });
87
+ for (const e of fs.readdirSync(from)) {
88
+ if (e === ".DS_Store") continue;
89
+ const src = path.join(from, e), dst = path.join(to, e);
90
+ if (fs.statSync(src).isDirectory()) copyDir(src, dst);
91
+ else fs.copyFileSync(src, dst);
92
+ }
93
+ }
94
+
71
95
  // ---------- install ----------
72
96
  async function install() {
73
97
  const detected = Object.keys(TOOLS).filter((t) => fs.existsSync(path.join(HOME, TOOLS[t].home)) || onPath(TOOLS[t].bin));
@@ -96,7 +120,7 @@ async function install() {
96
120
  for (const s of skills) {
97
121
  const to = path.join(dest, s);
98
122
  fs.rmSync(to, { recursive: true, force: true });
99
- fs.cpSync(path.join(SKILLS_DIR, s), to, { recursive: true, dereference: true, filter: (f) => !f.endsWith(".DS_Store") });
123
+ copyDir(path.join(SKILLS_DIR, s), to);
100
124
  }
101
125
  console.log(`✓ ${skills.length} skills → ${dest}`);
102
126
  }
@@ -247,7 +271,7 @@ async function chooseRepos(d) {
247
271
  const found = findRepos(scanRoot);
248
272
  if (found.length) {
249
273
  const owners = {};
250
- for (const r of found) (owners[gitFacts(path.join(scanRoot, r))?.owner || "?"] ||= []).push(r);
274
+ for (const r of found) { const o = gitFacts(path.join(scanRoot, r))?.owner || "?"; (owners[o] = owners[o] || []).push(r); }
251
275
  const biggest = Object.values(owners).sort((a, b) => b.length - a.length)[0] || [];
252
276
  const here = insideRepo ? [path.relative(scanRoot, d.root)] : [];
253
277
  console.log(`Found ${found.length} git repos under ${scanRoot}. Which ones belong to this workspace?`);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Launcher: plain CommonJS so it runs on any Node and can explain a too-old version,
3
+ // instead of the ESM entry failing with a syntax error.
4
+ const major = Number(process.versions.node.split(".")[0]);
5
+ if (major < 14) {
6
+ console.error(`✗ spec-driven-dev needs Node.js 14 or newer; this is ${process.version}.`);
7
+ console.error(" If you use nvm, run it from a folder without an old .nvmrc, or: nvm exec 20 npx @benjaminthomas/spec-driven-dev@latest");
8
+ process.exit(1);
9
+ }
10
+ import("./cli.mjs").catch((e) => { console.error(e); process.exit(1); });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@benjaminthomas/spec-driven-dev",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Spec-driven AI development workflow (investigate → spec → implement → verify → ship) as Agent Skills for Claude Code, Cursor, Codex CLI and Antigravity.",
5
5
  "type": "module",
6
- "bin": { "spec-driven-dev": "bin/cli.mjs" },
6
+ "bin": { "spec-driven-dev": "bin/spec-driven-dev.cjs" },
7
7
  "files": ["bin", "skills"],
8
8
  "scripts": { "test": "python3 scripts/check.py && node scripts/test-cli.mjs" },
9
- "engines": { "node": ">=18" },
9
+ "engines": { "node": ">=14" },
10
10
  "keywords": ["agent-skills", "claude-code", "cursor", "codex", "antigravity", "spec-driven-development", "ai-agents"],
11
11
  "repository": { "type": "git", "url": "git+https://github.com/benjaminthomas/spec-driven-dev.git" },
12
12
  "license": "MIT",