@benjaminthomas/spec-driven-dev 1.12.0 → 1.13.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/README.md CHANGED
@@ -63,26 +63,21 @@ start a new session and continue where you left off.
63
63
  npx @benjaminthomas/spec-driven-dev@latest
64
64
  ```
65
65
 
66
- This one command downloads the tool, installs the skills, and sets up the project. In a terminal
67
- window it asks its setup questions there. Run it with `!` inside Claude Code (or any other place
68
- without a terminal to answer in) and it installs the skills for the AI tools it finds, then tells
69
- you to say **"run spec-setup"**, which asks the same questions in the chat. It never writes the
70
- settings file from guesses.
71
-
72
- > **Why not `npm install -g`?** npm doesn't let a package ask questions while it installs, and an
73
- > install doesn't know which project it's for. That's why setup is a separate step you run inside
74
- > the project. If you'll use the `doctor`/`status` commands often, you can also install it
75
- > globally: `npm install -g @benjaminthomas/spec-driven-dev`, then run `spec-driven-dev` in place of
76
- > the `npx …` command.
77
-
78
- It:
79
- 1. **Finds your AI tools** (Claude Code, Cursor, Codex CLI, Antigravity) and installs the skills
80
- into each one you choose, for this project or for all projects.
81
- 2. **Sets up your project** by detecting its layout, issue tracker, branch conventions, and
82
- commands, then asking you to confirm. The answers go into `.spec-driven.yml`
83
- ([details below](#project-setup-your-teams-rules)).
84
-
85
- **3. In your AI tool, run each stage in order.** You can type the slash command or just say the
66
+ It installs the skills into the AI tools you pick (Claude Code, Cursor, Codex CLI, Antigravity),
67
+ for this project or all your projects. Each step has its own colour so it's easy to follow. It also
68
+ works with `!` inside Claude Code: it then installs for the tools it finds.
69
+
70
+ > **Why not `npm install -g`?** You can: `npm install -g @benjaminthomas/spec-driven-dev`, then run
71
+ > `spec-driven-dev` in place of the `npx …` command. Handy if you use `doctor`/`status` often.
72
+
73
+ **2. Say "run spec-setup" in your AI tool.** Setup is a conversation, so the agent can read the code
74
+ and you can change any answer as you go. It asks how the project is organised (one repo, a monorepo,
75
+ or several repos that change together), your tracker, branches and commit style, where specs live
76
+ and whether your team shares them, then looks through the code and saves the project memory
77
+ ([details below](#project-setup-your-teams-rules)). If Claude Code or Codex is installed, the
78
+ installer offers to open it with "run spec-setup" already typed in.
79
+
80
+ **3. Then run each stage in order.** You can type the slash command or just say the
86
81
  words in quotes.
87
82
 
88
83
  ```
@@ -145,36 +140,24 @@ others `fix/STP-123-short-name`. Some have one repo, others have six. None of th
145
140
  skills. Each project describes its own rules in **`.spec-driven.yml`**, and every skill reads that
146
141
  file before doing anything.
147
142
 
148
- **How the file gets created:** `spec-driven-dev` (or `spec-driven-dev setup` to run only this
149
- step) detects the answers from your repos and asks you to confirm them. In your AI tool you can also
150
- say **"run spec-setup"**: that does a deeper version that also reads `CLAUDE.md`/`AGENTS.md`, CI
151
- config, and recent PRs, and fills in what the installer can't (such as each repo's role, or commands
152
- for stacks other than Node). If a skill finds no config, it runs this setup first.
153
-
154
- **What `spec-driven-dev` asks, in order.** Each question is a menu: move with **↑/↓**, tick with
155
- **Space**, and confirm with **Enter**. What it detected is pre-selected and marked *(detected)*, so you
156
- can usually just press Enter. Repo paths are added one at a time: paste a path (quotes, `~`, and
157
- Windows paths like `C:\work\api` all work). A path that doesn't exist or isn't a git repo is flagged
158
- straight away, so you can fix it before moving on. Press Enter on an empty line when you're done.
159
- **Ctrl+C** cancels without changing anything.
143
+ **How the file gets created:** in your AI tool, say **"run spec-setup"**. It detects what it can
144
+ (layout, tracker, branches, commit style per repo, commands, written rules in `CLAUDE.md`/`AGENTS.md`,
145
+ CI config, recent PR targets), shows you a draft marked *detected* or *guess*, and asks only about
146
+ the guesses, in one round. You can correct anything in plain words ("actually it's Jira, key STP")
147
+ before it writes the file. If any skill finds no config, it runs this setup first.
160
148
 
161
- | Question | Detected from | Example answer |
149
+ | What it asks | Detected from | Example answer |
162
150
  |---|---|---|
163
- | Which AI tools get the skills | tool folders in your home directory and commands on your `PATH` | Claude Code, Cursor |
164
- | This project only, or all projects | (you choose) | `project` |
165
- | Project layout | git repos and workspace files (`pnpm-workspace.yaml`, `turbo.json`, `nx.json`, `go.work`, …) | `single`, `monorepo`, or `multi-repo` |
166
- | *multi-repo:* which repos nearby belong | git repos up to two folders down, grouped by GitHub owner | `frontend` |
167
- | *multi-repo:* repos in other places | (you type paths: absolute, `~/…`, or relative) | `~/Work/backend, /srv/database` |
168
- | *multi-repo:* where the config lives | the repos' shared parent folder, or `~/.spec-driven/<name>/` | `~/Projects` |
169
- | *monorepo:* package folders | the workspaces your `package.json`/`pnpm-workspace.yaml` declares, or `apps/*`, `packages/*` | `apps/web, packages/ui` |
151
+ | How the project is organised | git repos and workspace files (`pnpm-workspace.yaml`, `turbo.json`, `nx.json`, `go.work`, …). A Shopify app's `extensions/` count as one app | `single`, `monorepo`, or `multi-repo` |
152
+ | *multi-repo:* which repos you work on | git repos nearby, grouped by GitHub owner, plus the team's `team-repos.yml` | `soreto-shopify, reverb-backend` |
153
+ | *multi-repo:* where specs live | (you choose) | a dedicated specs repo, a code repo, or this machine only |
170
154
  | Issue tracker | ticket keys in branch names and commits | Jira, key `STP` |
171
- | Base branch / PR target | default branch, `staging`/`develop` branches | `staging` |
155
+ | Base branch / PR target | default branch, `staging`/`develop`, recent PR targets | `staging` |
172
156
  | Branch name pattern | your recent branch names | `{ticket}` → `STP-123` |
173
- | Commit style | your recent commit messages | `conventional` (`feat: …`) or `ticket` (`STP-123: …`) |
174
- | Lint / typecheck / test / build / dev commands | `package.json` scripts and lockfile (per repo or package) | `pnpm run lint` |
175
- | Where specs go | (you choose) | `specs/` |
157
+ | Commit style | recent commit messages, judged per repo | `ticket` (`STP-123: …`), with one repo on `conventional` |
158
+ | Commands | `package.json` scripts, lockfile, Makefile, CI | `npm run lint` |
159
+ | Share specs with the team | your `.gitignore` today | everything / project memory only / local |
176
160
  | After you approve a spec | (you choose) | `stop` (wait at each stage) or `run-through` |
177
- | Model and effort per task tier | written with the defaults above | edit `models:` / `effort:` later |
178
161
 
179
162
  **Where the file lives, by layout:**
180
163
 
@@ -184,34 +167,23 @@ straight away, so you can fix it before moving on. Press Enter on an empty line
184
167
  | **monorepo** | one repo, many apps/packages | the repo root (commit it) | tasks name their package, and checks run per package |
185
168
  | **multi-repo** | several repos that change together, in any folders | their shared parent folder, or `~/.spec-driven/<name>/` (personal to your machine) | investigation searches every listed repo, tasks stay inside one repo each, and `spec-ship` opens one PR per repo, each linking the others |
186
169
 
187
- **Repos in different places.** A multi-repo workspace doesn't need its repos in one folder. For
188
- example, from inside `/Projects/frontend`:
170
+ **Repos in different places.** A multi-repo workspace doesn't need its repos in one folder
171
+ (`/Projects/frontend`, `~/Work/backend`, `/srv/database`). The config goes in their shared parent
172
+ folder, or `~/.spec-driven/<name>/` when that would be your home folder, and is recorded in
173
+ `~/.spec-driven/workspaces.json` so every repo finds it.
189
174
 
190
- ```
191
- ✔ How is this project organised? › Multiple repos
192
- ✔ Which of these repos (found under /Projects) belong to this workspace? › frontend
193
- ? Add a repo from another folder — paste its path, or press Enter to finish (1 selected) › ~/Work/nope
194
- › Folder not found: /Users/you/Work/nope
195
- ? Add a repo from another folder — paste its path, or press Enter to finish (1 selected) › ~/Work/backend
196
- ✓ added /Users/you/Work/backend
197
- ? Add a repo from another folder — paste its path, or press Enter to finish (2 selected) › /srv/database
198
- ✓ added /srv/database
199
- ? Add a repo from another folder — paste its path, or press Enter to finish (3 selected) ›
200
- ✔ Folder for this workspace's settings file (.spec-driven.yml) › ~/.spec-driven/frontend-workspace
201
- ```
175
+ **Teams where people work on different repos.** One developer has the Shopify app and the backend,
176
+ another the dashboard and the backend. Each person's workspace lists only the repos they have, and
177
+ specs go in a place everyone has: a small dedicated specs repo is best. Its `team-repos.yml` lists
178
+ every team repo with its git URL, so setup offers them, `doctor` shows which aren't cloned yet (with
179
+ the clone command), and an investigation stops and says so when a ticket needs a repo that isn't on
180
+ this machine.
202
181
 
203
- - **Where the config goes:** the repos' shared parent folder, or `~/.spec-driven/<name>/` when that
204
- parent would be your home folder or `/`.
205
- - **How every repo finds it:** setup records the workspace in `~/.spec-driven/workspaces.json`, so
206
- `/Work/backend` finds its config even though the config isn't above it. The skills, `doctor`,
207
- and `status` all look there.
208
- - **Paths are checked:** ones that don't exist or aren't git repos are skipped with a message, and
209
- setup stops if a workspace would end up with no repos.
210
-
211
- Every question also has a flag, for scripts and CI:
212
- `--layout=multi-repo --repos=~/Projects/frontend,~/Work/backend --workspace=~/Projects`,
213
- `--layout=monorepo --packages=apps/web,packages/ui`, `--after-approval=run-through`,
214
- `--project=new|existing`, `--share=all|memory|none`, `--specs-dir=<path>`, `--tools=cursor,codex`, `--global`, `--yes` (accept everything detected), and `--version`.
182
+ **For scripts and CI** there's `spec-driven-dev setup` with flags (it asks nothing):
183
+ `--yes`, `--layout=multi-repo --repos=~/Projects/frontend,~/Work/backend --workspace=~/Projects`,
184
+ `--layout=monorepo --packages=apps/web,packages/ui`, `--specs-dir=<path>`, `--share=all|memory|none`,
185
+ `--after-approval=run-through`, `--project=new|existing`. Installer flags: `--tools=cursor,codex`,
186
+ `--global`, `--no-setup`, `--version`.
215
187
 
216
188
  To change the rules later, edit the file or run setup again, then run `spec-driven-dev doctor`
217
189
  to confirm the file still matches your repos. Setup never overwrites the file without
@@ -601,19 +573,19 @@ run one after another, stopping at the first failure or review blocker. Two step
601
573
  spec, and pushing or opening the PR. You don't need slash commands either. Saying "investigate
602
574
  STP-123" or "ship it" picks the right skill.
603
575
 
604
- **Setup didn't ask me anything.**
605
- Update to 1.5.1 or later (`npx @benjaminthomas/spec-driven-dev@latest --version`). Earlier versions
606
- crashed on Node 14 or 16, which nvm often switches to inside older projects, and wrote a config
607
- silently when there was no terminal. Now it runs on Node 14+. With no terminal to answer in (a `!`
608
- command in Claude Code, an editor task, piped input), it installs the skills and hands the questions
609
- to your AI tool: say "run spec-setup" there. `spec-driven-dev setup` on its own still needs a
610
- terminal, or flags such as `--layout=multi-repo --repos=…`.
576
+ **The installer didn't ask me about my project.**
577
+ That's expected from 1.13.0: the installer only installs the skills. Project setup happens in your
578
+ AI tool: say "run spec-setup". It runs on Node 14+, and works from `!` inside Claude Code too.
579
+
580
+ **npm prints "Unknown project config" warnings.**
581
+ They come from your project's `.npmrc`, not from this package: settings such as `auto-install-peers`
582
+ or `shamefully-hoist` are for pnpm, and npm warns about them on every `npm`/`npx` command in that
583
+ folder. If the project uses npm (it has a `package-lock.json`), delete those lines. If it uses pnpm,
584
+ run `pnpm dlx @benjaminthomas/spec-driven-dev` instead.
611
585
 
612
- **Setup didn't ask me about repos.**
613
- It only asks about repos for a multi-repo or monorepo layout. If it detected `single`, type
614
- `multi-repo` at the layout question, then add your other repos by path. If it asked nothing at all,
615
- there was no terminal to type answers into (it prints a note saying so), so run it directly in a
616
- terminal. Check which version ran with `npx @benjaminthomas/spec-driven-dev@latest --version`.
586
+ **Setup thought my repo was a monorepo (or a single repo).**
587
+ Tell it in the chat: "this is one repo that works with reverb-backend". A Shopify app's
588
+ `extensions/` folder is treated as part of one app from 1.13.0.
617
589
 
618
590
  **Can I skip stages?**
619
591
  Yes. Every skill works on its own. `spec-verify` even works without a spec: it asks what to check.
@@ -663,7 +635,7 @@ Then, in a project:
663
635
 
664
636
  ```bash
665
637
  spec-driven-dev # detect tools, install skills, set up the project
666
- spec-driven-dev setup # only (re)run project setup
638
+ spec-driven-dev setup --yes # only (re)run project setup
667
639
  spec-driven-dev --global # install skills for all projects, not just this one
668
640
  spec-driven-dev --tools=cursor,codex # pick tools without being asked
669
641
  spec-driven-dev --skills=spec-create,spec-ship # install only some skills
@@ -679,7 +651,7 @@ spec-driven-dev --help # show all of the above
679
651
  ```
680
652
 
681
653
  **Without installing:** replace `spec-driven-dev` with `npx @benjaminthomas/spec-driven-dev`, for
682
- example `npx @benjaminthomas/spec-driven-dev setup`. That downloads and runs the latest version once,
654
+ example `npx @benjaminthomas/spec-driven-dev doctor`. That downloads and runs the latest version once,
683
655
  which is handy in CI or on a machine you won't use again.
684
656
 
685
657
  `doctor` checks that every branch, folder, and `npm run` script named in the config exists, and
@@ -713,8 +685,8 @@ install `ticket-to-pr@spec-driven-dev`; the old name no longer gets updates.
713
685
 
714
686
  To see unfinished specs and their next step at any time, run `spec-driven-dev status`
715
687
  (for example, `specs/stp-123: 3/5 tasks done → next: spec-implement`). If
716
- you use the plugin, `spec-driven-dev` skips Claude Code so you don't get every skill twice. Run
717
- `spec-driven-dev setup` for the project config.
688
+ you use the plugin, `spec-driven-dev` skips Claude Code so you don't get every skill twice. Then say
689
+ "run spec-setup" in Claude Code for the project config.
718
690
 
719
691
  ### By hand
720
692
 
@@ -768,8 +740,8 @@ skips that step rather than failing.
768
740
 
769
741
  | Skill | Stage | SKILL.md lines | Words | Supporting files |
770
742
  |---|---|---:|---:|---:|
771
- | `spec-setup` | 0 · configure | 179 | 1,747 | 3 |
772
- | `spec-investigate` | 1 · understand | 373 | 3,211 | 1 |
743
+ | `spec-setup` | 0 · configure | 210 | 2,201 | 3 |
744
+ | `spec-investigate` | 1 · understand | 385 | 3,325 | 1 |
773
745
  | `spec-create` | 2 · plan | 198 | 1,782 | 6 |
774
746
  | `spec-implement` | 3 · build | 327 | 2,753 | 3 |
775
747
  | `spec-verify` | 4 · prove | 163 | 1,557 | 1 |
@@ -777,7 +749,7 @@ skips that step rather than failing.
777
749
  | `spec-checkpoint` | 6 · commit | 140 | 1,144 | 0 |
778
750
  | `spec-ship` | 7 · PR | 230 | 1,388 | 0 |
779
751
  | `generative-seo` | standalone | 255 | 2,209 | 7 |
780
- | **Total** | | **2,002** | **17,085** | **22** |
752
+ | **Total** | | **2,045** | **17,653** | **22** |
781
753
 
782
754
  - **8** skills: **1** setup + **6** workflow stages + **1** standalone
783
755
  - **4** tools supported natively (Claude Code, Codex CLI, Cursor, Antigravity), plus an MCP fallback
@@ -830,7 +802,7 @@ plugin/evals/ `claude plugin eval` cases
830
802
  1. Increase `version` in both `package.json` and `plugin/.claude-plugin/plugin.json` (the check fails if
831
803
  they differ).
832
804
  2. Add a `CHANGELOG.md` entry, then commit and push.
833
- 3. Tag the release and push the tag: `git tag v1.12.0 && git push origin v1.12.0`.
805
+ 3. Tag the release and push the tag: `git tag v1.13.0 && git push origin v1.13.0`.
834
806
  4. Watch the **publish** run in the repo's Actions tab. When it's green, the new version is on
835
807
  npm. Claude Code plugin users get it from the marketplace once the commit is on `main`.
836
808
 
package/bin/cli.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
  // spec-driven-dev installer (npm i -g @benjaminthomas/spec-driven-dev): detects your AI coding tools, copies the skills into each one, and
3
3
  // runs project discovery (layout, tracker, branches, commands) to write .spec-driven.yml.
4
4
  //
5
- // spec-driven-dev install + project setup (interactive)
6
- // spec-driven-dev setup project setup only
5
+ // spec-driven-dev install the skills, then hand over to your AI tool ("run spec-setup")
6
+ // spec-driven-dev setup scripted setup for CI: needs --yes or answer flags (people: say "run spec-setup")
7
7
  // spec-driven-dev doctor check .spec-driven.yml against the real repos (exit 1 on problems)
8
8
  // spec-driven-dev status list specs with unfinished work and each one's "Resume here" next step
9
9
  // spec-driven-dev context print specs/CONTEXT.md; --areas=billing,api prints only those area files
@@ -39,9 +39,24 @@ const args = process.argv.slice(2);
39
39
  const flag = (n) => args.includes(`--${n}`);
40
40
  const opt = (n) => args.find((a) => a.startsWith(`--${n}=`))?.split("=")[1];
41
41
  const command = args.find((a) => !a.startsWith("-")) || "install";
42
- const YES = flag("yes") || flag("y") || !process.stdin.isTTY || ["doctor", "status", "context", "risk", "risk-eval"].includes(command);
42
+ const YES = flag("yes") || flag("y") || !process.stdin.isTTY || ["doctor", "status", "context", "risk", "risk-eval", "setup"].includes(command);
43
43
  const DIR = path.resolve(opt("dir") || process.cwd());
44
44
 
45
+ // Colour only for a person at a terminal; pipes, CI and NO_COLOR get plain text (and the tests stay plain).
46
+ const COLOR = (process.stdout.isTTY || !!process.env.FORCE_COLOR) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
47
+ const paint = (code) => (t) => (COLOR ? `\x1b[${code}m${t}\x1b[0m` : t);
48
+ const ink = { green: paint("32"), yellow: paint("33"), red: paint("31"), cyan: paint("36"), magenta: paint("35"), blue: paint("34"), bold: paint("1"), dim: paint("2") };
49
+ // Each stage gets its own colour so the steps are easy to tell apart while scrolling.
50
+ const step = (n, title, color) => console.log(`\n${color(ink.bold(`━━ ${n} · ${title} `))}${color("━".repeat(Math.max(4, 44 - title.length)))}`);
51
+ if (COLOR) {
52
+ const plain = console.log.bind(console);
53
+ console.log = (...a) => {
54
+ const t = a.join(" ");
55
+ plain(t.replace(/^(\s*)(✓)/gm, (_, s, g) => s + ink.green(g)).replace(/^(\s*)(✗.*)$/gm, (_, s, g) => s + ink.red(g))
56
+ .replace(/^(\s*)(•)/gm, (_, s, g) => s + ink.yellow(g)).replace(/^(Next:.*)$/gm, (_, g) => ink.cyan(ink.bold(g))));
57
+ };
58
+ }
59
+
45
60
  if (flag("version") || flag("v")) {
46
61
  console.log(JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version);
47
62
  process.exit(0);
@@ -53,18 +68,17 @@ const NO_TTY = !process.stdin.isTTY && !args.some((a) => ANSWER_FLAGS.some((f) =
53
68
  // Installing the skills needs no answers, so the default command still does that without a terminal
54
69
  // (e.g. `!` in Claude Code) and hands the questions to the spec-setup skill, which asks them in the chat.
55
70
  const HANDOFF = command === "install" && NO_TTY;
56
- if (["setup", "settings"].includes(command) && NO_TTY) {
71
+ // Setup is a conversation in the AI tool now; the setup command stays for scripts and CI (flags only).
72
+ const SETUP_IN_AGENT = command === "setup" && !args.some((a) => ANSWER_FLAGS.some((f) => a === `--${f}` || a.startsWith(`--${f}=`)));
73
+ if (command === "settings" && NO_TTY) {
57
74
  console.log(`✗ No interactive terminal, so ${command === "settings" ? "the settings menu can't open" : "setup can't ask its questions"}. Nothing was changed.
58
75
 
59
76
  This happens when it runs through another program: a "!" command in Claude Code, an editor task,
60
77
  or piped input. Either:
61
78
  • run it yourself in a terminal window (Terminal, iTerm, or your editor's terminal panel):
62
- npx @benjaminthomas/spec-driven-dev@latest ${command}${command === "setup" ? `
63
- • or, in your AI tool (Claude Code, Cursor, Codex), say "run spec-setup": it asks the same
64
- questions in the chat` : ""}
65
- • or give the answers as flags, for example:
66
- npx @benjaminthomas/spec-driven-dev@latest ${command === "settings" ? "settings --risk=off" : "setup --layout=multi-repo --repos=~/Projects/frontend,~/Projects/backend"}
67
- (add --yes to accept everything it detects)`);
79
+ npx @benjaminthomas/spec-driven-dev@latest settings
80
+ • or give the answer as a flag, for example:
81
+ npx @benjaminthomas/spec-driven-dev@latest settings --risk=off`);
68
82
  process.exit(1);
69
83
  }
70
84
 
@@ -113,6 +127,7 @@ function copyDir(from, to) {
113
127
 
114
128
  // ---------- install ----------
115
129
  async function install() {
130
+ step(1, "Install the skills", ink.blue);
116
131
  const detected = Object.keys(TOOLS).filter((t) => fs.existsSync(path.join(HOME, TOOLS[t].home)) || onPath(TOOLS[t].bin));
117
132
  let tools = opt("tools")?.split(",");
118
133
  if (!tools) tools = await askMulti("Which AI coding tools should get the skills?",
@@ -145,7 +160,26 @@ async function install() {
145
160
  console.log(`✓ ${skills.length} skills → ${dest}`);
146
161
  }
147
162
  if (!dests.length) console.log(HANDOFF ? "• No AI coding tools found (Claude Code, Cursor, Codex or Antigravity). Install one, or pass --tools=claude,cursor." : "• No tools selected — nothing installed.");
148
- return dests.length;
163
+ return dests.length ? tools : [];
164
+ }
165
+
166
+ // The setup questions (layout, repos, tracker, branches, sharing) and the audit need a model, so they
167
+ // happen in the AI tool. Claude Code and Codex can be opened with the request already typed in.
168
+ const LAUNCHERS = [["claude", "Claude Code"], ["codex", "Codex"]];
169
+ async function handOver(tools, onlySetup) {
170
+ const name = path.basename(gitTop(DIR));
171
+ step(onlySetup ? 1 : 2, `Set up ${name} in your AI tool`, ink.magenta);
172
+ console.log(`
173
+ ${onlySetup ? "Setup happens in your AI tool now, so it can read the code and you can change any answer as you go." : "Skills installed."}
174
+ Next: set up ${name}. In your AI tool, say "run spec-setup". It asks how the project is organised
175
+ (single repo, monorepo, or several repos), your tracker, branches and whether to share specs,
176
+ then looks through the code. Start a new session if your tool doesn't recognise it yet.${onlySetup ? `
177
+ For scripts and CI: spec-driven-dev setup --yes [--layout=… --repos=… --share=…]` : ""}`);
178
+ const launcher = LAUNCHERS.find(([bin]) => (onlySetup || tools.includes(bin)) && onPath(bin));
179
+ if (!launcher || !process.stdin.isTTY || YES || onlySetup) return;
180
+ if (!(await askConfirm(`Open ${launcher[1]} here now and start it? (takes a few minutes, uses your ${launcher[1]} plan)`, true))) return;
181
+ const { spawnSync } = await import("node:child_process");
182
+ spawnSync(launcher[0], ["run spec-setup"], { cwd: DIR, stdio: "inherit" });
149
183
  }
150
184
 
151
185
  // ---------- discovery ----------
@@ -186,7 +220,9 @@ function detectLayout(dir) {
186
220
  const base = g.replace(/\/\*\*?$/, "");
187
221
  return base === g ? [g.replace(/\/$/, "")] : subdirs(path.join(top, base)).map((n) => `${base}/${n}`);
188
222
  }))].filter(hasManifest);
189
- const mono = MARKERS.some((m) => fs.existsSync(path.join(top, m))) || pkg?.workspaces || cargo || packages.length > 1;
223
+ // A Shopify app keeps its app extensions in npm workspaces under extensions/: one app, not a monorepo.
224
+ const shopifyApp = fs.readdirSync(top).some((f) => /^shopify\.app.*\.toml$/.test(f)) && declared.length > 0 && declared.every((g) => g.startsWith("extensions/"));
225
+ const mono = !shopifyApp && (MARKERS.some((m) => fs.existsSync(path.join(top, m))) || pkg?.workspaces || cargo || packages.length > 1);
190
226
  return { layout: mono ? "monorepo" : "single", root: top, repos: mono ? packages : [] };
191
227
  }
192
228
  const repos = findRepos(dir);
@@ -237,11 +273,12 @@ function infer(facts) {
237
273
  const y = (v) => JSON.stringify(v ?? ""); // JSON strings are valid YAML scalars
238
274
  function toYaml(c) {
239
275
  const cmds = (o, pad) => Object.entries(o).map(([k, v]) => `${pad}${k}: ${y(v)}`).join("\n");
240
- let out = `# .spec-driven.yml — written by spec-driven-dev setup. Edit freely, or re-run setup.\n# Every spec-* skill reads this first. Field reference: skills/spec-setup/references/config-template.yml\nversion: 1\nlayout: ${c.layout}\n`;
276
+ let out = `# .spec-driven.yml — written by spec-driven-dev. Edit freely, or say \"run spec-setup\" in your AI tool to review it.\n# Every spec-* skill reads this first. Field reference: skills/spec-setup/references/config-template.yml\nversion: 1\nlayout: ${c.layout}\n`;
241
277
  if (c.repos.length) {
242
278
  out += "repos:\n" + c.repos.map((r) => {
243
279
  let e = ` - path: ${y(r.path)}\n role: ${y(r.role)}\n`;
244
- if (r.base && r.base !== c.git.base_branch) e += ` git:\n base_branch: ${y(r.base)}\n pr_target: ${y(r.base)}\n`;
280
+ const g = [r.base && r.base !== c.git.base_branch ? ` base_branch: ${y(r.base)}\n pr_target: ${y(r.base)}\n` : "", r.commitStyle ? ` commit_style: ${y(r.commitStyle)}\n` : ""].join("");
281
+ if (g) e += ` git:\n${g}`;
245
282
  if (r.commands && Object.values(r.commands).some(Boolean)) e += ` commands:\n${cmds(r.commands, " ")}\n`;
246
283
  return e;
247
284
  }).join("");
@@ -481,7 +518,9 @@ async function setup() {
481
518
  repos: repos.map((r) => {
482
519
  const f = layout === "multi-repo" ? gitFacts(path.resolve(root, r)) : null;
483
520
  // a repo without an integration branch must say so, or it inherits the top-level base (e.g. staging) it doesn't have
484
- return { path: r, role: "", base: f ? f.integration || "default" : "", commands: commandsFor(path.resolve(root, r)) };
521
+ // each repo keeps its own commit style when it differs from the workspace's (one repo uses STP-123:, another feat:)
522
+ const own = f && f.subjects.length >= 5 ? infer([f]).commitStyle : "";
523
+ return { path: r, role: "", base: f ? f.integration || "default" : "", commitStyle: own && own !== commitStyle ? own : "", commands: commandsFor(path.resolve(root, r)) };
485
524
  }),
486
525
  };
487
526
  fs.writeFileSync(file, toYaml(config));
@@ -555,8 +594,9 @@ function findConfig(dir) {
555
594
  }
556
595
 
557
596
  function doctor() {
597
+ if (COLOR) step("✓", "Check the settings", ink.cyan);
558
598
  const file = findConfig(DIR);
559
- if (!file) throw new Error(`No .spec-driven.yml in ${DIR} or above. Run: spec-driven-dev setup (or npx @benjaminthomas/spec-driven-dev setup)`);
599
+ if (!file) throw new Error(`No .spec-driven.yml in ${DIR} or above. In your AI tool, say "run spec-setup".`);
560
600
  const root = path.dirname(file);
561
601
  let c;
562
602
  try { c = parseYaml(fs.readFileSync(file, "utf8")); } catch (e) { throw new Error(`${file} could not be read: ${e.message}`); }
@@ -604,6 +644,17 @@ function doctor() {
604
644
  // Multi-repo context names paths from the workspace folder (soreto-shopify/app/) or from inside a repo (app/).
605
645
  const repoRoots = c.layout === "multi-repo" ? [root, ...(c.repos || []).map((r) => path.resolve(root, expandHome(r.path)))] : [root];
606
646
  checkContext(specs, c.specs_dir || "specs", repoRoots, problems, notes);
647
+ // The team's repo list (in the specs location) vs the repos on this machine.
648
+ const team = parseYaml(fs.existsSync(path.join(specs, "team-repos.yml")) ? fs.readFileSync(path.join(specs, "team-repos.yml"), "utf8") : "").repos;
649
+ if (Array.isArray(team)) {
650
+ const mine = new Set((c.repos || []).map((r) => path.basename(String(r.path || "").replace(/\/+$/, ""))));
651
+ for (const t of team) {
652
+ if (!t.name || mine.has(t.name)) continue;
653
+ const here = path.join(root, t.name);
654
+ notes.push(isRepo(here) ? `${t.name} is cloned at ${here} but isn't in this workspace — say "add ${t.name} to the workspace" if you work on it`
655
+ : `team repo ${t.name} isn't on this machine (fine if you don't work on it)${t.url ? `: git clone ${t.url} ${here}` : ""}`);
656
+ }
657
+ }
607
658
  if (c.context?.share) {
608
659
  if (!SHARE_MODES.includes(c.context.share)) problems.push(`context.share "${c.context.share}" must be all, memory or none`);
609
660
  const g = specsInGit(root, c.specs_dir || "specs");
@@ -687,6 +738,7 @@ async function chooseShare(root, specsDir) {
687
738
  const g = specsInGit(root, specsDir);
688
739
  if (opt("share") && !SHARE_MODES.includes(opt("share"))) throw new Error("--share must be all, memory or none");
689
740
  if (!g) { if (opt("share") && opt("share") !== "none") console.log(` • ${specsDir} isn't inside a git repo, so it can't be shared. Choose a folder inside a repo (--specs-dir) to share it.`); return null; }
741
+ if (!g.rel) { console.log(` • ${specsDir} is a repo of its own, so everything in it is shared.`); return "all"; }
690
742
  const now = shareState(g.top, g.rel);
691
743
  if (now !== "all") console.log(` • Your .gitignore currently makes ${g.rel}: ${shareTitle[now].toLowerCase()}.`);
692
744
  return opt("share") || askSelect("Share specs and project memory with your team?", [
@@ -699,6 +751,7 @@ async function chooseShare(root, specsDir) {
699
751
  async function applyShare(root, specsDir, mode, { quietIfSame } = {}) {
700
752
  const g = specsInGit(root, specsDir);
701
753
  if (!g) { console.log(` • ${specsDir} isn't inside a git repo, so there's nothing to share or ignore.`); return; }
754
+ if (!g.rel) mode = "all"; // a dedicated specs repo is shared by definition
702
755
  if (quietIfSame && shareState(g.top, g.rel) === mode && mode !== "all") return;
703
756
  const gi = path.join(g.top, ".gitignore");
704
757
  const before = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : "";
@@ -804,7 +857,7 @@ const SETTINGS = [
804
857
 
805
858
  async function settings() {
806
859
  const file = findConfig(DIR);
807
- if (!file) throw new Error(`No .spec-driven.yml in ${DIR} or above. Run: spec-driven-dev setup`);
860
+ if (!file) throw new Error(`No .spec-driven.yml in ${DIR} or above. In your AI tool, say "run spec-setup" first.`);
808
861
  let text = fs.readFileSync(file, "utf8");
809
862
  const label = (s, v) => s.options.find((o) => o.value === v)?.title || v;
810
863
  const save = async (s, v) => {
@@ -864,20 +917,14 @@ try {
864
917
  else if (command === "risk") risk();
865
918
  else if (command === "settings") await settings();
866
919
  else if (command === "risk-eval") await riskEvalCmd();
867
- else if (command === "install" && HANDOFF) {
868
- console.log("No interactive terminal (for example a \"!\" command in Claude Code), so this installs the skills\nwith the detected defaults and leaves the setup questions to your AI tool.\n");
869
- const n = await install();
870
- if (n) console.log(`
871
- Next: in your AI tool, say "run spec-setup". It asks the setup questions in the chat
872
- (single repo, monorepo or multi-repo, tracker, branches) and writes .spec-driven.yml.
873
- If your AI tool doesn't recognise it yet, start a new session so it loads the new skills.
874
- To answer in a terminal instead, open Terminal or iTerm and run: npx @benjaminthomas/spec-driven-dev setup`);
875
- } else if (command === "install") {
876
- await install();
877
- if (!flag("no-setup") && (await askConfirm("Set up this project now?", true))) await setup();
878
- } else if (command === "setup") await setup();
920
+ else if (command === "install") {
921
+ if (HANDOFF) console.log("No interactive terminal (for example a \"!\" command in Claude Code), so the skills go to the AI tools it finds, for this project.\n");
922
+ const tools = await install();
923
+ if (tools.length && !flag("no-setup")) await handOver(tools);
924
+ } else if (SETUP_IN_AGENT) handOver([], true);
925
+ else if (command === "setup") await setup();
879
926
  else throw new Error(`Unknown command "${command}" — use install (default), setup, settings, doctor, status, context, risk, or risk-eval`);
880
- if (["install", "setup"].includes(command) && !HANDOFF) console.log('\nNext: in your AI tool, say "investigate <ticket>" or "create a spec".');
927
+ if (command === "setup" && !SETUP_IN_AGENT) console.log('\nNext: in your AI tool, say "investigate <ticket>" or "create a spec".');
881
928
  } catch (e) {
882
929
  console.error(`✗ ${e.message}`);
883
930
  process.exitCode = 1;
package/bin/share.mjs CHANGED
@@ -12,13 +12,16 @@ const git = (cwd, args) => {
12
12
  const ignored = (top, rel) => !!git(top, ["check-ignore", "--no-index", rel]);
13
13
 
14
14
  // What git does today: is the project memory ignored, and is a feature's working folder ignored?
15
+ const under = (specs, p) => (specs ? `${specs}/${p}` : p); // specs can be a whole repo (a dedicated specs repo)
16
+
15
17
  export function shareState(top, specs) {
16
- const memory = !ignored(top, `${specs}/CONTEXT.md`) && !ignored(top, `${specs}/context/notes.md`);
17
- const features = !ignored(top, `${specs}/some-feature/README.md`);
18
+ const memory = !ignored(top, under(specs, "CONTEXT.md")) && !ignored(top, under(specs, "context/notes.md"));
19
+ const features = !ignored(top, under(specs, "some-feature/README.md"));
18
20
  return features && memory ? "all" : memory ? "memory" : features ? "custom" : "none";
19
21
  }
20
22
 
21
23
  export function blockFor(specs, mode) {
24
+ if (!specs) return [BEGIN, "/context/risk.md", END]; // a specs repo exists to be shared: only the generated file is ignored
22
25
  const lines = mode === "none" ? [`${specs}/`]
23
26
  : mode === "memory" ? [`${specs}/*`, `!${specs}/CONTEXT.md`, `!${specs}/context/`, `${specs}/context/risk.md`]
24
27
  : [`${specs}/context/risk.md`];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benjaminthomas/spec-driven-dev",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Spec-driven AI development workflow (investigate → spec → implement → verify → review → ship) as Agent Skills for Claude Code, Cursor, Codex CLI and Antigravity.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticket-to-pr",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Parallel-batch spec-driven feature workflow (spec-setup, spec-investigate, spec-create, spec-implement, spec-verify, spec-review, spec-checkpoint, spec-ship) plus a portable SEO/GEO operations skill (generative-seo) for any codebase.",
5
5
  "author": {
6
6
  "name": "Benjamin Thomas",
@@ -65,6 +65,18 @@ Cheap sources first, code second:
65
65
 
66
66
  No CONTEXT.md? Investigate as usual, and suggest running `spec-setup` afterwards to create one.
67
67
 
68
+ ## Repos this machine doesn't have (multi-repo)
69
+
70
+ The workspace config lists only the repos cloned here. If the ticket or the evidence points at a
71
+ repo that isn't listed (its name in the ticket, an API or package it owns, a `team-repos.yml` role
72
+ that matches), **stop before concluding** and say so plainly:
73
+
74
+ > This ticket also touches **reverb-react** (the merchant dashboard), which isn't on this machine.
75
+ > Clone it: `git clone <url from team-repos.yml> <workspace>/reverb-react`, then say "add
76
+ > reverb-react to the workspace" and investigate again. Or I can carry on and mark that part UNKNOWN.
77
+
78
+ Never present a cross-repo change as complete when part of it lives in a repo you couldn't read.
79
+
68
80
  ## Risk signal (GitRisk)
69
81
 
70
82
  Use it only when `.spec-driven.yml` has `risk.enabled: on`, or has no `risk:` block, **and**
@@ -17,7 +17,9 @@ license: MIT
17
17
  # spec-setup
18
18
 
19
19
  Learn this project's rules and context once, save them, and let every other `spec-*` skill reuse
20
- them. Team rules live in a config file the team can review and change — never in the skills
20
+ them. This is **the** setup: the npm installer only copies the skills and then sends people here, so
21
+ treat it as their first conversation with the workflow. Explain each question in a line, and let
22
+ them change any earlier answer at any point ("actually it's Jira") before you write anything. Team rules live in a config file the team can review and change — never in the skills
21
23
  themselves. What the project *knows* (areas, conventions, decisions) lives in
22
24
  `{specs_dir}/CONTEXT.md`, kept small so loading it is cheap.
23
25
 
@@ -44,6 +46,11 @@ Read, don't guess. Collect a draft of every field in `references/config-template
44
46
  `package.json`, `turbo.json`, `nx.json`, `lerna.json`, `go.work`, `[workspace]` in
45
47
  `Cargo.toml`, several `apps/*`/`packages/*`/`services/*` with their own manifests) →
46
48
  `monorepo`; list the packages.
49
+ - **Not a monorepo:** a Shopify app (`shopify.app*.toml` at the root) whose workspaces only cover
50
+ `extensions/`. Those are parts of one app deployed together, so it's `single` (or one repo of a
51
+ multi-repo workspace). The same goes for any repo whose "packages" are plug-ins of one app.
52
+ - **Ask when one repo works with others.** Even inside a single repo, ask once: "Does this repo
53
+ change together with other repos (for example a backend)?" A yes means `multi-repo`.
47
54
  - Not inside a git repo, but sibling folders are git repos → candidate `multi-repo`. Group them
48
55
  by remote owner (`git remote get-url origin`) and show the groups — the user picks which repos
49
56
  belong together. Never assume every folder in the parent belongs. Always also ask whether
@@ -68,7 +75,9 @@ Read, don't guess. Collect a draft of every field in `references/config-template
68
75
  `gh repo view --json defaultBranchRef`); integration branches (`staging`, `develop`,
69
76
  `pre-prod`); which branch recent PRs targeted (`gh pr list --state merged --limit 20 --json
70
77
  baseRefName`); the pattern of the last ~20 remote branch names; the commit style from
71
- `git log -30 --format=%s`.
78
+ `git log -30 --format=%s`. Judge commit style **per repo**: when one repo writes `STP-123: …` and
79
+ the others write `feat: …`, keep the majority at the top level and give that repo its own
80
+ `git.commit_style`.
72
81
  5. **Commands, per repo/package.** `package.json` scripts plus the lockfile's package manager,
73
82
  `Makefile`, `pyproject.toml`/`tox.ini`, `Cargo.toml`, `go.mod`, `composer.json`, CI workflow
74
83
  steps (CI is often the most truthful source). Dev URL from the dev script, `.env.example`, or
@@ -82,7 +91,22 @@ Read, don't guess. Collect a draft of every field in `references/config-template
82
91
  pushing. Store it as `pipeline.after_approval`.
83
92
  8. **New or existing project.** Detect it (commit and file counts) and confirm in one line. Store
84
93
  it as `context.project`. This decides what Step 4 does.
85
- 9. **Sharing with the team.** Find out what git does with the specs folder today:
94
+ 9. **Where specs live (multi-repo).** One specs folder serves the whole workspace: each ticket gets
95
+ one folder there even when it changes several repos; code, branches and PRs stay in each repo.
96
+ Offer, in this order, with the reason for each:
97
+ - **A dedicated specs repo (recommended for teams where developers work on different subsets of
98
+ repos):** everyone clones it, whatever code repos they have. Use an existing one (path or git
99
+ URL; offer the `git clone` command) or create one: only after a yes, `mkdir` it next to the
100
+ repos and `git init`. Never create a remote repo; say how to push it (`gh repo create`).
101
+ Then set `specs_dir: <specs-repo>` (its root holds `CONTEXT.md`, `context/` and feature folders).
102
+ - **Inside one code repo** (`<repo>/specs`): fine when every developer has that repo.
103
+ - **The workspace folder:** on this machine only, since it isn't a git repo.
104
+ 10. **The team's repo list.** Keep `{specs_dir}/team-repos.yml` (see `references/config-template.yml`)
105
+ listing every repo the team works across with its git URL and a one-line role. Each developer's
106
+ own `.spec-driven.yml` lists only the repos they have. When the file exists, offer its repos
107
+ instead of making people remember them, and for any not cloned here show
108
+ `git clone <url> <workspace>/<name>`. When someone adds a repo, add it to the file too.
109
+ 11. **Sharing with the team.** Find out what git does with the specs folder today:
86
110
  `git check-ignore -v {specs_dir}/CONTEXT.md` and `git check-ignore -v {specs_dir}/x/README.md`.
87
111
  Then ask once, saying what's true now (e.g. "your .gitignore ignores `specs/`, so nothing is
88
112
  shared today"):
@@ -93,7 +117,8 @@ Read, don't guess. Collect a draft of every field in `references/config-template
93
117
 
94
118
  **Multi-repo:** if the config folder isn't a git repo, specs kept there can't be shared. Say so
95
119
  and offer `specs_dir: <repo>/specs` inside the repo the team treats as home.
96
- Store the answer as `context.share`.
120
+ Store the answer as `context.share`. A dedicated specs repo is shared by nature: set
121
+ `context.share: all`.
97
122
 
98
123
  ## Step 2 — Confirm in one round
99
124
 
@@ -145,6 +170,11 @@ Skip if `{specs_dir}/CONTEXT.md` already exists (offer a refresh instead, see `r
145
170
  6. Re-run `npx -y @benjaminthomas/spec-driven-dev doctor` if available. It checks the size limits
146
171
  and flags context lines that point to files which no longer exist.
147
172
 
173
+ ## After setup
174
+
175
+ Finish with **one** next step, not a list: "Setup's done. Say *investigate STP-123* to start your
176
+ first ticket." If anything was left out (a tracker URL, a repo not cloned), name it in one line.
177
+
148
178
  ## Quick switches (plain words, no YAML)
149
179
 
150
180
  When the user asks to switch a feature on or off, change only that one line of `.spec-driven.yml`,
@@ -156,6 +186,7 @@ keep every other line and comment, and confirm in one sentence. Don't re-run dis
156
186
  | "turn GitRisk on", "use the risk scores" | `risk.enabled: on`, then run `spec-driven-dev risk` if `{specs_dir}/context/risk.md` doesn't exist yet |
157
187
  | "run straight through after I approve", "fewer stops" | `pipeline.after_approval: run-through` |
158
188
  | "stop after each stage" | `pipeline.after_approval: stop` |
189
+ | "add reverb-react to the workspace", "I also work on X" | add it under `repos:` (clone first if needed, from `team-repos.yml`), and to `team-repos.yml` if it's new to the team |
159
190
  | "share the specs with the team", "keep specs local", "share only the project memory" | `context.share: all / none / memory`: show the `.gitignore` change, then run `spec-driven-dev settings --share=…` on a yes |
160
191
 
161
192
  Reply with what changed and what it means, for example: "GitRisk is now off. Investigations, plans
@@ -101,4 +101,15 @@ context:
101
101
  # CONTEXT.md + context/ | none = ignore the specs folder. risk.md is always ignored.
102
102
  # Change with: spec-driven-dev settings --share=…
103
103
 
104
- specs_dir: specs # where specs/<feature>/ folders go (relative to this file)
104
+ specs_dir: specs # where specs/<feature>/ folders go (relative to this file).
105
+ # Multi-repo teams: a dedicated specs repo (e.g. soreto-specs) everyone clones.
106
+
107
+ # {specs_dir}/team-repos.yml — every repo the team works across, shared with the specs. Each developer's
108
+ # .spec-driven.yml lists only the repos they have; setup offers the rest with their clone commands.
109
+ # repos:
110
+ # - name: reverb-backend
111
+ # url: git@github.com:acme/reverb-backend.git
112
+ # role: REST API and workers
113
+ # - name: reverb-react
114
+ # url: git@github.com:acme/reverb-react.git
115
+ # role: merchant dashboard