@benjaminthomas/spec-driven-dev 1.4.0 → 1.4.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 +103 -29
- package/bin/cli.mjs +80 -22
- package/package.json +1 -1
- package/skills/spec-setup/SKILL.md +7 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# spec-driven-dev
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@benjaminthomas/spec-driven-dev)
|
|
4
|
+
[](https://github.com/benjaminthomas/spec-driven-dev/actions/workflows/check.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
**A step-by-step workflow that gets AI coding agents from "here's a ticket" to "here's a reviewed PR"
|
|
4
8
|
without guessing.** It first investigates the problem, then writes a plan you approve, then builds it
|
|
5
9
|
in parallel with tests and review, then checks the real running app, and finally commits and opens
|
|
@@ -8,7 +12,8 @@ the PR.
|
|
|
8
12
|
It's a set of eight [Agent Skills](https://agentskills.io), plain Markdown instruction files that
|
|
9
13
|
AI coding tools load on demand. It works with Claude Code, Codex CLI, Cursor, and Google Antigravity,
|
|
10
14
|
in any language or framework, and in a single repo, a monorepo, or several repos that change
|
|
11
|
-
together.
|
|
15
|
+
together. Install it from npm with `npm install -g @benjaminthomas/spec-driven-dev`, then run
|
|
16
|
+
`spec-driven-dev` in your project.
|
|
12
17
|
|
|
13
18
|
---
|
|
14
19
|
|
|
@@ -26,6 +31,7 @@ together. One command installs it: `npx @benjaminthomas/spec-driven-dev`.
|
|
|
26
31
|
- [Compatibility](#compatibility)
|
|
27
32
|
- [By the numbers](#by-the-numbers)
|
|
28
33
|
- [Working on this repo](#working-on-this-repo)
|
|
34
|
+
- [Changelog](CHANGELOG.md)
|
|
29
35
|
|
|
30
36
|
---
|
|
31
37
|
|
|
@@ -48,20 +54,26 @@ start a new session and continue where you left off.
|
|
|
48
54
|
|
|
49
55
|
## Quick start (5 minutes)
|
|
50
56
|
|
|
51
|
-
**1. Install
|
|
57
|
+
**1. Install the CLI from npm** (once per machine):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install -g @benjaminthomas/spec-driven-dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**2. Run it in your project folder** (or, for several repos, in their shared parent folder):
|
|
52
64
|
|
|
53
65
|
```bash
|
|
54
|
-
|
|
66
|
+
spec-driven-dev
|
|
55
67
|
```
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
It:
|
|
58
70
|
1. **Finds your AI tools** (Claude Code, Cursor, Codex CLI, Antigravity) and installs the skills
|
|
59
71
|
into each one you choose, for this project or for all projects.
|
|
60
72
|
2. **Sets up your project** by detecting its layout, issue tracker, branch conventions, and
|
|
61
73
|
commands, then asking you to confirm. The answers go into `.spec-driven.yml`
|
|
62
74
|
([details below](#project-setup-your-teams-rules)).
|
|
63
75
|
|
|
64
|
-
**
|
|
76
|
+
**3. In your AI tool, run each stage in order.** You can type the slash command or just say the
|
|
65
77
|
words in quotes.
|
|
66
78
|
|
|
67
79
|
```
|
|
@@ -89,18 +101,24 @@ others `fix/STP-123-short-name`. Some have one repo, others have six. None of th
|
|
|
89
101
|
skills. Each project describes its own rules in **`.spec-driven.yml`**, and every skill reads that
|
|
90
102
|
file before doing anything.
|
|
91
103
|
|
|
92
|
-
**How the file gets created:** `
|
|
104
|
+
**How the file gets created:** `spec-driven-dev` (or `spec-driven-dev setup` to run only this
|
|
93
105
|
step) detects the answers from your repos and asks you to confirm them. In your AI tool you can also
|
|
94
106
|
say **"run spec-setup"**: that does a deeper version that also reads `CLAUDE.md`/`AGENTS.md`, CI
|
|
95
107
|
config, and recent PRs, and fills in what the installer can't (such as each repo's role, or commands
|
|
96
108
|
for stacks other than Node). If a skill finds no config, it runs this setup first.
|
|
97
109
|
|
|
98
|
-
**What it
|
|
110
|
+
**What `spec-driven-dev` asks, in order** (every answer comes pre-filled with what it detected, so you
|
|
111
|
+
can usually just press Enter):
|
|
99
112
|
|
|
100
113
|
| Question | Detected from | Example answer |
|
|
101
114
|
|---|---|---|
|
|
115
|
+
| Which AI tools get the skills | tool folders in your home directory and commands on your `PATH` | Claude Code, Cursor |
|
|
116
|
+
| This project only, or all projects | (you choose) | `project` |
|
|
102
117
|
| Project layout | git repos and workspace files (`pnpm-workspace.yaml`, `turbo.json`, `nx.json`, `go.work`, …) | `single`, `monorepo`, or `multi-repo` |
|
|
103
|
-
|
|
|
118
|
+
| *multi-repo:* workspace folder (where the config goes) | the folder you ran it in, or the parent of the repo you're in | `~/work` |
|
|
119
|
+
| *multi-repo:* which repos belong together | git repos in the workspace folder, grouped by GitHub owner | `api`, `web` |
|
|
120
|
+
| *multi-repo:* repos from other folders | (you type paths) | `~/clients/billing, ../shared-lib` |
|
|
121
|
+
| *monorepo:* package folders | the workspaces your `package.json`/`pnpm-workspace.yaml` declares, or `apps/*`, `packages/*` | `apps/web, packages/ui` |
|
|
104
122
|
| Issue tracker | ticket keys in branch names and commits | Jira, key `STP` |
|
|
105
123
|
| Base branch / PR target | default branch, `staging`/`develop` branches | `staging` |
|
|
106
124
|
| Branch name pattern | your recent branch names | `{ticket}` → `STP-123` |
|
|
@@ -116,7 +134,17 @@ for stacks other than Node). If a skill finds no config, it runs this setup firs
|
|
|
116
134
|
| **monorepo** | one repo, many apps/packages | the repo root (commit it) | tasks name their package, and checks run per package |
|
|
117
135
|
| **multi-repo** | several repos that change together | their shared parent folder (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 |
|
|
118
136
|
|
|
119
|
-
|
|
137
|
+
Repos outside the workspace folder are saved with their full path. Paths that don't exist or
|
|
138
|
+
aren't git repos are skipped with a message, and setup stops if a multi-repo workspace ends up with
|
|
139
|
+
no repos. You can choose a different layout from the one detected: for example, choose
|
|
140
|
+
`multi-repo` from inside one repo and it uses the parent folder as the workspace, with your current
|
|
141
|
+
repo pre-selected.
|
|
142
|
+
|
|
143
|
+
Every question also has a flag, for scripts and CI:
|
|
144
|
+
`--layout=multi-repo --repos=api,web,~/clients/billing`, `--layout=monorepo --packages=apps/web,packages/ui`,
|
|
145
|
+
`--tools=cursor,codex`, `--global`, `--yes` (accept everything detected).
|
|
146
|
+
|
|
147
|
+
To change the rules later, edit the file or run setup again, then run `spec-driven-dev doctor`
|
|
120
148
|
to confirm the file still matches your repos. Setup never overwrites the file without
|
|
121
149
|
asking. See [`config-template.yml`](skills/spec-setup/references/config-template.yml) for every field.
|
|
122
150
|
|
|
@@ -326,6 +354,24 @@ Yes. Every skill works on its own. `spec-verify` even works without a spec: it a
|
|
|
326
354
|
Yes. Progress is saved in the spec's `README.md` checkboxes and the commit for each batch. Run
|
|
327
355
|
`spec-implement` again to continue.
|
|
328
356
|
|
|
357
|
+
**How do I update?**
|
|
358
|
+
Run `npm install -g @benjaminthomas/spec-driven-dev@latest`, then `spec-driven-dev` in your project.
|
|
359
|
+
Claude Code plugin users run
|
|
360
|
+
`/plugin marketplace update spec-driven-dev` and then update the plugin. Your config is never
|
|
361
|
+
touched by an update.
|
|
362
|
+
|
|
363
|
+
**Can I trust the npm package?**
|
|
364
|
+
Releases are published by this repo's GitHub Actions workflow, not from anyone's laptop, through
|
|
365
|
+
npm [trusted publishing](https://docs.npmjs.com/trusted-publishers). Each version's npm page shows
|
|
366
|
+
a provenance badge linking it to the exact commit and workflow run that built it. The package has
|
|
367
|
+
no dependencies, and it contains only `bin/cli.mjs` and the `skills/` folder.
|
|
368
|
+
|
|
369
|
+
**Something isn't working. Where do I start?**
|
|
370
|
+
Run `spec-driven-dev doctor` in your project. It checks the config against your
|
|
371
|
+
repos and names anything that doesn't match. If that passes, open an
|
|
372
|
+
[issue](https://github.com/benjaminthomas/spec-driven-dev/issues) with the doctor output and the
|
|
373
|
+
skill you were running.
|
|
374
|
+
|
|
329
375
|
**`spec-verify` says it skipped some UI checks.**
|
|
330
376
|
Your tool needs browser automation (an MCP browser server, a built-in browser tool, or an
|
|
331
377
|
extension) for checks that involve the UI. Checks that only touch the API or database work
|
|
@@ -338,23 +384,41 @@ everywhere.
|
|
|
338
384
|
### Any tool: npm (recommended)
|
|
339
385
|
|
|
340
386
|
```bash
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
387
|
+
npm install -g @benjaminthomas/spec-driven-dev # once per machine; adds the `spec-driven-dev` command
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Then, in a project:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
spec-driven-dev # detect tools, install skills, set up the project
|
|
394
|
+
spec-driven-dev setup # only (re)run project setup
|
|
395
|
+
spec-driven-dev --global # install skills for all projects, not just this one
|
|
396
|
+
spec-driven-dev --tools=cursor,codex # pick tools without being asked
|
|
397
|
+
spec-driven-dev --skills=spec-create,spec-ship # install only some skills
|
|
398
|
+
spec-driven-dev --yes # accept every detected default (CI-friendly)
|
|
399
|
+
spec-driven-dev doctor # check .spec-driven.yml against the real repos
|
|
400
|
+
spec-driven-dev status # list specs with unfinished work
|
|
401
|
+
spec-driven-dev --help # show all of the above
|
|
349
402
|
```
|
|
350
403
|
|
|
404
|
+
**Without installing:** replace `spec-driven-dev` with `npx @benjaminthomas/spec-driven-dev`, for
|
|
405
|
+
example `npx @benjaminthomas/spec-driven-dev setup`. That downloads and runs the latest version once,
|
|
406
|
+
which is handy in CI or on a machine you won't use again.
|
|
407
|
+
|
|
351
408
|
`doctor` checks that every branch, folder, and `npm run` script named in the config exists, and
|
|
352
409
|
that each value is one the skills understand. It exits 1 if anything is wrong, so you can run it in
|
|
353
410
|
CI. `status` prints nothing when all specs are finished.
|
|
354
411
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
412
|
+
**Updating:** run `npm install -g @benjaminthomas/spec-driven-dev@latest` to get the newest CLI,
|
|
413
|
+
then `spec-driven-dev` in each project to refresh its skills. This replaces the installed skill
|
|
414
|
+
folders and leaves your `.spec-driven.yml` alone. If you use `npx`, add `@latest`, because `npx`
|
|
415
|
+
can otherwise reuse an older copy it cached. What changed in each version is in
|
|
416
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
417
|
+
|
|
418
|
+
`npx github:benjaminthomas/spec-driven-dev` installs straight from GitHub instead, for example to
|
|
419
|
+
try an unreleased commit on `main`.
|
|
420
|
+
|
|
421
|
+
**Requirements:** Node.js 18 or newer and git. The installer has no dependencies.
|
|
358
422
|
|
|
359
423
|
### Claude Code (plugin, with automatic updates)
|
|
360
424
|
|
|
@@ -369,8 +433,8 @@ The plugin also adds a **session-start reminder**. When you open Claude Code in
|
|
|
369
433
|
unfinished specs, Claude is told which ones and what the next step is (for example,
|
|
370
434
|
`specs/stp-123: 3/5 tasks done → next: spec-implement`). It prints nothing when everything is
|
|
371
435
|
finished. If
|
|
372
|
-
you use the plugin, `
|
|
373
|
-
`
|
|
436
|
+
you use the plugin, `spec-driven-dev` skips Claude Code so you don't get every skill twice. Run
|
|
437
|
+
`spec-driven-dev setup` for the project config.
|
|
374
438
|
|
|
375
439
|
### By hand
|
|
376
440
|
|
|
@@ -423,7 +487,7 @@ skips that step rather than failing.
|
|
|
423
487
|
|
|
424
488
|
| Skill | Stage | SKILL.md lines | Words | Supporting files |
|
|
425
489
|
|---|---|---:|---:|---:|
|
|
426
|
-
| `spec-setup` | 0 · configure |
|
|
490
|
+
| `spec-setup` | 0 · configure | 105 | 935 | 1 |
|
|
427
491
|
| `spec-investigate` | 1 · understand | 323 | 2,708 | 1 |
|
|
428
492
|
| `spec-create` | 2 · plan | 149 | 1,328 | 4 |
|
|
429
493
|
| `spec-implement` | 3 · build | 236 | 1,790 | 3 |
|
|
@@ -431,12 +495,12 @@ skips that step rather than failing.
|
|
|
431
495
|
| `spec-checkpoint` | 5 · commit | 106 | 762 | 0 |
|
|
432
496
|
| `spec-ship` | 6 · PR | 218 | 1,235 | 0 |
|
|
433
497
|
| `generative-seo` | standalone | 255 | 2,209 | 7 |
|
|
434
|
-
| **Total** | | **1,
|
|
498
|
+
| **Total** | | **1,545** | **12,426** | **17** |
|
|
435
499
|
|
|
436
500
|
- **8** skills: **1** setup + **6** workflow stages + **1** standalone
|
|
437
501
|
- **4** tools supported natively (Claude Code, Codex CLI, Cursor, Antigravity), plus an MCP fallback
|
|
438
502
|
- **3** layouts: single repo, monorepo, multi-repo
|
|
439
|
-
- **1**
|
|
503
|
+
- **1** npm package, `@benjaminthomas/spec-driven-dev`, plus the Claude Code plugin and an MCP fallback
|
|
440
504
|
- **0** stack assumptions: lint, typecheck, test, build, and dev commands come from your repo
|
|
441
505
|
|
|
442
506
|
_The table is generated by `python3 scripts/check.py --fix`; CI fails if it goes out of date._
|
|
@@ -450,19 +514,22 @@ skills/<name>/SKILL.md the skill itself (frontmatter: name, description,
|
|
|
450
514
|
skills/<name>/references/ templates and prompts the skill loads when needed
|
|
451
515
|
.claude-plugin/ Claude Code plugin + marketplace manifests
|
|
452
516
|
.agents/skills/ symlinks so Codex/Antigravity pick the skills up inside this repo
|
|
453
|
-
bin/cli.mjs the
|
|
517
|
+
bin/cli.mjs the `spec-driven-dev` CLI: installer, setup, doctor, status (no dependencies)
|
|
454
518
|
package.json npm package (ships bin/ and skills/)
|
|
455
519
|
mcp-server/ MCP fallback server
|
|
456
520
|
hooks/hooks.json Claude Code session-start hook (runs `cli.mjs status`)
|
|
457
521
|
CHANGELOG.md what changed in each version
|
|
458
522
|
scripts/check.py consistency checks + metrics generator
|
|
459
523
|
scripts/test-cli.mjs installer self-test (fake HOME, throwaway repos)
|
|
524
|
+
.github/workflows/check.yml CI: consistency checks + installer self-test on every push and PR
|
|
525
|
+
.github/workflows/publish.yml publishes to npm when a vX.Y.Z tag is pushed
|
|
460
526
|
evals/ `claude plugin eval` cases
|
|
461
527
|
```
|
|
462
528
|
|
|
463
529
|
- **Adding a skill:** create `skills/<name>/SKILL.md`, then add the name to the two manifests, a `.agents/skills/<name>` symlink, the `STAGES` map in
|
|
464
530
|
`scripts/check.py`, and this README. `python3 scripts/check.py` lists anything you missed.
|
|
465
|
-
- **Before pushing:** run `python3 scripts/check.py --fix
|
|
531
|
+
- **Before pushing:** run `python3 scripts/check.py --fix`, then `npm test` (which runs the checks
|
|
532
|
+
and the installer self-test). CI (`.github/workflows/check.yml`) runs
|
|
466
533
|
the same checks, the installer self-test, and a syntax check on the MCP server.
|
|
467
534
|
- **Evals:** `claude plugin eval . --runs 1` checks that skills trigger on their phrases and stay
|
|
468
535
|
within their own stage. These runs use your Claude credentials, so they aren't part of CI.
|
|
@@ -476,8 +543,15 @@ evals/ `claude plugin eval` cases
|
|
|
476
543
|
- **Releasing:**
|
|
477
544
|
1. Increase `version` in both `package.json` and `.claude-plugin/plugin.json` (the check fails if
|
|
478
545
|
they differ).
|
|
479
|
-
2. Add a `CHANGELOG.md` entry.
|
|
480
|
-
3.
|
|
546
|
+
2. Add a `CHANGELOG.md` entry, then commit and push.
|
|
547
|
+
3. Tag the release and push the tag: `git tag v1.4.1 && git push origin v1.4.1`.
|
|
548
|
+
4. Watch the **publish** run in the repo's Actions tab. When it's green, the new version is on
|
|
549
|
+
npm. Claude Code plugin users get it from the marketplace once the commit is on `main`.
|
|
550
|
+
|
|
551
|
+
`.github/workflows/publish.yml` then runs the tests and publishes to npm through
|
|
552
|
+
[trusted publishing](https://docs.npmjs.com/trusted-publishers). No npm token or passkey is
|
|
553
|
+
needed, and the package page shows a provenance badge linking the release to the exact commit.
|
|
554
|
+
The workflow refuses to publish if the tag doesn't match `package.json`.
|
|
481
555
|
|
|
482
556
|
## License
|
|
483
557
|
|
package/bin/cli.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// spec-driven-dev installer: detects your AI coding tools, copies the skills into each one, and
|
|
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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// spec-driven-dev install + project setup (interactive)
|
|
6
|
+
// spec-driven-dev setup project setup only
|
|
7
|
+
// spec-driven-dev doctor check .spec-driven.yml against the real repos (exit 1 on problems)
|
|
8
|
+
// spec-driven-dev status list specs with unfinished tasks (used by the session-start hook)
|
|
9
9
|
// flags: --yes --global --tools=claude,cursor --skills=spec-create,... --no-setup --dir=<path>
|
|
10
|
+
// --layout=single|monorepo|multi-repo --repos=api,../web,/abs/path --packages=apps/web,packages/ui
|
|
10
11
|
import fs from "node:fs";
|
|
11
12
|
import os from "node:os";
|
|
12
13
|
import path from "node:path";
|
|
@@ -35,7 +36,7 @@ const YES = flag("yes") || flag("y") || !process.stdin.isTTY || ["doctor", "stat
|
|
|
35
36
|
const DIR = path.resolve(opt("dir") || process.cwd());
|
|
36
37
|
|
|
37
38
|
if (flag("help") || flag("h")) {
|
|
38
|
-
console.log(fs.readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1,
|
|
39
|
+
console.log(fs.readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1, 10).join("\n").replace(/^\/\/ ?/gm, ""));
|
|
39
40
|
process.exit(0);
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -184,26 +185,83 @@ function toYaml(c) {
|
|
|
184
185
|
return out + `specs_dir: ${y(c.specsDir)}\n`;
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
const LAYOUTS = ["single", "monorepo", "multi-repo"];
|
|
189
|
+
const splitList = (v) => (v || "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
190
|
+
// Paths are stored relative to the config's folder when inside it, absolute otherwise.
|
|
191
|
+
const relOrAbs = (root, p) => { const r = path.relative(root, p); return r.startsWith("..") || path.isAbsolute(r) ? p : r || "."; };
|
|
192
|
+
|
|
193
|
+
async function chooseLayout(detected) {
|
|
194
|
+
const forced = opt("layout");
|
|
195
|
+
if (forced && !LAYOUTS.includes(forced)) throw new Error(`--layout must be one of ${LAYOUTS.join(", ")}`);
|
|
196
|
+
if (forced) return forced;
|
|
197
|
+
for (;;) {
|
|
198
|
+
const a = await ask(`Layout — single, monorepo, or multi-repo (detected: ${detected})`, detected);
|
|
199
|
+
if (LAYOUTS.includes(a)) return a;
|
|
200
|
+
console.log(` Please type one of: ${LAYOUTS.join(", ")}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// multi-repo: pick from repos found in the workspace folder, then add any others by path.
|
|
205
|
+
async function chooseRepos(d) {
|
|
206
|
+
// Inside one repo but multi-repo chosen → the workspace is the folder that holds this repo.
|
|
207
|
+
const insideRepo = d.layout !== "multi-repo";
|
|
208
|
+
const defRoot = insideRepo ? path.dirname(d.root) : d.root;
|
|
209
|
+
const root = path.resolve(DIR, await ask("Workspace folder (where .spec-driven.yml goes)", defRoot));
|
|
210
|
+
const found = detectLayout(root).layout === "multi-repo" ? detectLayout(root).repos : [];
|
|
211
|
+
let picked = [];
|
|
212
|
+
const fromFlag = opt("repos");
|
|
213
|
+
if (fromFlag) {
|
|
214
|
+
picked = splitList(fromFlag).map((p) => path.resolve(root, p));
|
|
215
|
+
} else {
|
|
216
|
+
if (found.length) {
|
|
217
|
+
const owners = {};
|
|
218
|
+
for (const r of found) (owners[gitFacts(path.join(root, r))?.owner || "?"] ||= []).push(r);
|
|
219
|
+
const biggest = Object.values(owners).sort((a, b) => b.length - a.length)[0] || [];
|
|
220
|
+
const here = insideRepo ? [path.relative(root, d.root)] : [];
|
|
221
|
+
console.log(`Found ${found.length} git repos in ${root}. Which ones change together as one workspace?`);
|
|
222
|
+
picked = (await pick("Repos", found.map((r) => ({ value: r, label: `${r} (${Object.keys(owners).find((o) => owners[o].includes(r))})` })), [...new Set([...here, ...biggest])]))
|
|
223
|
+
.map((r) => path.join(root, r));
|
|
224
|
+
} else if (insideRepo) picked = [d.root];
|
|
225
|
+
const extra = await ask("Add repos from other folders? Paths, comma-separated (absolute, ~/…, or relative to the workspace folder); empty for none", "");
|
|
226
|
+
picked.push(...splitList(extra).map((p) => path.resolve(root, p.replace(/^~(?=$|\/)/, HOME))));
|
|
227
|
+
}
|
|
228
|
+
const repos = [];
|
|
229
|
+
for (const p of [...new Set(picked)]) {
|
|
230
|
+
if (!fs.existsSync(p)) console.log(` ✗ ${p}: folder not found — skipped`);
|
|
231
|
+
else if (!isRepo(p)) console.log(` ✗ ${p}: not a git repo — skipped`);
|
|
232
|
+
else repos.push(relOrAbs(root, p));
|
|
233
|
+
}
|
|
234
|
+
if (!repos.length) throw new Error("A multi-repo workspace needs at least one git repo. Re-run setup and pick or add repos (or use --repos=a,b).");
|
|
235
|
+
console.log(` Workspace repos: ${repos.join(", ")}`);
|
|
236
|
+
return { root, repos };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// monorepo: confirm or edit the package folders (relative to the repo root).
|
|
240
|
+
async function choosePackages(d) {
|
|
241
|
+
const root = d.root;
|
|
242
|
+
const fromFlag = opt("packages");
|
|
243
|
+
const answer = fromFlag || (await ask("Package folders, comma-separated, relative to the repo root (empty if none yet)", d.repos.join(",")));
|
|
244
|
+
const pkgs = [];
|
|
245
|
+
for (const p of splitList(answer)) {
|
|
246
|
+
if (fs.existsSync(path.resolve(root, p))) pkgs.push(relOrAbs(root, path.resolve(root, p)));
|
|
247
|
+
else console.log(` ✗ ${p}: folder not found in ${root} — skipped`);
|
|
248
|
+
}
|
|
249
|
+
return { root, repos: pkgs };
|
|
250
|
+
}
|
|
251
|
+
|
|
187
252
|
async function setup() {
|
|
188
253
|
console.log("\nProject setup — detecting how this project works…");
|
|
189
254
|
const d = detectLayout(DIR);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
for (const r of repos) (owners[gitFacts(path.join(d.root, r))?.owner || "?"] ||= []).push(r);
|
|
194
|
-
const biggest = Object.values(owners).sort((a, b) => b.length - a.length)[0] || [];
|
|
195
|
-
console.log(`Found ${repos.length} git repos in ${d.root}. Which ones change together as one workspace?`);
|
|
196
|
-
repos = await pick("Repos", repos.map((r) => ({ value: r, label: `${r} (${Object.keys(owners).find((o) => owners[o].includes(r))})` })), biggest);
|
|
197
|
-
}
|
|
198
|
-
const layout = await ask(`Layout — single, monorepo, or multi-repo (detected: ${d.layout})`, d.layout);
|
|
199
|
-
const root = d.root;
|
|
255
|
+
const layout = await chooseLayout(d.layout);
|
|
256
|
+
const { root, repos } = layout === "multi-repo" ? await chooseRepos(d) : layout === "monorepo" ? await choosePackages(d) : { root: d.layout === "multi-repo" ? DIR : d.root, repos: [] };
|
|
257
|
+
if (layout === "single" && !isRepo(root)) console.log(` • ${root} isn't a git repo — run setup inside the repo, or choose multi-repo.`);
|
|
200
258
|
const file = path.join(root, ".spec-driven.yml");
|
|
201
259
|
if (fs.existsSync(file) && (await ask(`${file} exists — overwrite? (y/n)`, "n")) !== "y") {
|
|
202
260
|
console.log("• Kept the existing config. Run the spec-setup skill in your AI tool to review it.");
|
|
203
261
|
return;
|
|
204
262
|
}
|
|
205
263
|
|
|
206
|
-
const repoDirs = layout === "multi-repo" ? repos.map((r) => path.
|
|
264
|
+
const repoDirs = layout === "multi-repo" ? repos.map((r) => path.resolve(root, r)) : [root];
|
|
207
265
|
const facts = repoDirs.map(gitFacts).filter(Boolean);
|
|
208
266
|
const g = infer(facts);
|
|
209
267
|
if (facts.length) console.log(`Detected: tracker ${g.trackerType}${g.key ? ` (${g.key}-…)` : ""}, base ${g.base}, branches "${g.branchName}", commits ${g.commitStyle}`);
|
|
@@ -221,10 +279,10 @@ async function setup() {
|
|
|
221
279
|
layout, tracker: { type: trackerType, key, url }, specsDir, devUrl: "",
|
|
222
280
|
git: { remote: "origin", base_branch: base, pr_target: pr, branch_name: branchName, commit_style: commitStyle },
|
|
223
281
|
commands: layout === "single" ? commandsFor(repoDirs[0]) : commandsFor(root),
|
|
224
|
-
repos:
|
|
225
|
-
const f = layout === "multi-repo" ? gitFacts(path.
|
|
282
|
+
repos: repos.map((r) => {
|
|
283
|
+
const f = layout === "multi-repo" ? gitFacts(path.resolve(root, r)) : null;
|
|
226
284
|
// a repo without an integration branch must say so, or it inherits the top-level base (e.g. staging) it doesn't have
|
|
227
|
-
return { path: r, role: "", base: f ? f.integration || "default" : "", commands: commandsFor(path.
|
|
285
|
+
return { path: r, role: "", base: f ? f.integration || "default" : "", commands: commandsFor(path.resolve(root, r)) };
|
|
228
286
|
}),
|
|
229
287
|
};
|
|
230
288
|
fs.writeFileSync(file, toYaml(config));
|
|
@@ -279,7 +337,7 @@ function findConfig(dir) {
|
|
|
279
337
|
|
|
280
338
|
function doctor() {
|
|
281
339
|
const file = findConfig(DIR);
|
|
282
|
-
if (!file) throw new Error(`No .spec-driven.yml in ${DIR} or above. Run: npx @benjaminthomas/spec-driven-dev setup`);
|
|
340
|
+
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)`);
|
|
283
341
|
const root = path.dirname(file);
|
|
284
342
|
let c;
|
|
285
343
|
try { c = parseYaml(fs.readFileSync(file, "utf8")); } catch (e) { throw new Error(`${file} could not be read: ${e.message}`); }
|
|
@@ -291,7 +349,7 @@ function doctor() {
|
|
|
291
349
|
const repos = c.layout === "single" ? [{ path: "." }] : Array.isArray(c.repos) ? c.repos : [];
|
|
292
350
|
if (c.layout !== "single" && !repos.length) problems.push(`layout is ${c.layout} but repos: is empty`);
|
|
293
351
|
for (const r of repos) {
|
|
294
|
-
const dir = path.
|
|
352
|
+
const dir = path.resolve(root, r.path || ".");
|
|
295
353
|
const label = r.path === "." ? "repo" : r.path;
|
|
296
354
|
if (!fs.existsSync(dir)) { problems.push(`${label}: folder not found`); continue; }
|
|
297
355
|
const gitDir = c.layout === "monorepo" ? root : dir;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benjaminthomas/spec-driven-dev",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.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
6
|
"bin": { "spec-driven-dev": "bin/cli.mjs" },
|
|
@@ -41,7 +41,13 @@ Read, don't guess. Collect a draft of every field in `references/config-template
|
|
|
41
41
|
`monorepo`; list the packages.
|
|
42
42
|
- Not inside a git repo, but sibling folders are git repos → candidate `multi-repo`. Group them
|
|
43
43
|
by remote owner (`git remote get-url origin`) and show the groups — the user picks which repos
|
|
44
|
-
belong together. Never assume every folder in the parent belongs.
|
|
44
|
+
belong together. Never assume every folder in the parent belongs. Always also ask whether
|
|
45
|
+
repos in **other folders** belong (e.g. `~/clients/billing`). Check each path is a git repo,
|
|
46
|
+
and store paths inside the workspace as relative and paths outside it as absolute.
|
|
47
|
+
- The user can override the detected layout. If they choose `multi-repo` from inside one repo,
|
|
48
|
+
the workspace folder defaults to that repo's parent, with the current repo pre-selected. If
|
|
49
|
+
they choose `monorepo`, ask for the package folders. Never write a `multi-repo` config with
|
|
50
|
+
no repos.
|
|
45
51
|
- Otherwise → `single`.
|
|
46
52
|
2. **Existing written rules.** `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`, PR templates,
|
|
47
53
|
`.github/workflows`. Anything stated there is a strong default — quote it back to the user.
|