@benjaminthomas/spec-driven-dev 1.4.0 → 1.5.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 +147 -36
- package/bin/cli.mjs +161 -26
- package/package.json +1 -1
- package/skills/spec-checkpoint/SKILL.md +3 -1
- package/skills/spec-create/SKILL.md +10 -2
- package/skills/spec-implement/SKILL.md +39 -2
- package/skills/spec-investigate/SKILL.md +3 -1
- package/skills/spec-setup/SKILL.md +16 -1
- package/skills/spec-setup/references/config-template.yml +6 -0
- package/skills/spec-ship/SKILL.md +3 -1
- package/skills/spec-verify/SKILL.md +3 -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, even when those repos live in different folders. To set it up, run
|
|
16
|
+
`npx @benjaminthomas/spec-driven-dev@latest` 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,30 @@ start a new session and continue where you left off.
|
|
|
48
54
|
|
|
49
55
|
## Quick start (5 minutes)
|
|
50
56
|
|
|
51
|
-
**1.
|
|
57
|
+
**1. Run the installer in your project** (for several repos, run it inside any one of them):
|
|
52
58
|
|
|
53
59
|
```bash
|
|
54
|
-
npx @benjaminthomas/spec-driven-dev
|
|
60
|
+
npx @benjaminthomas/spec-driven-dev@latest
|
|
55
61
|
```
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
This one command downloads the tool, installs the skills, and sets up the project. It asks its
|
|
64
|
+
questions in your terminal, so run it in a real terminal, not through another tool. If there's no
|
|
65
|
+
terminal to answer in, it says so and uses the detected values.
|
|
66
|
+
|
|
67
|
+
> **Why not `npm install -g`?** npm doesn't let a package ask questions while it installs, and an
|
|
68
|
+
> install doesn't know which project it's for. That's why setup is a separate step you run inside
|
|
69
|
+
> the project. If you'll use the `doctor`/`status` commands often, you can also install it
|
|
70
|
+
> globally: `npm install -g @benjaminthomas/spec-driven-dev`, then run `spec-driven-dev` in place of
|
|
71
|
+
> the `npx …` command.
|
|
72
|
+
|
|
73
|
+
It:
|
|
58
74
|
1. **Finds your AI tools** (Claude Code, Cursor, Codex CLI, Antigravity) and installs the skills
|
|
59
75
|
into each one you choose, for this project or for all projects.
|
|
60
76
|
2. **Sets up your project** by detecting its layout, issue tracker, branch conventions, and
|
|
61
77
|
commands, then asking you to confirm. The answers go into `.spec-driven.yml`
|
|
62
78
|
([details below](#project-setup-your-teams-rules)).
|
|
63
79
|
|
|
64
|
-
**
|
|
80
|
+
**3. In your AI tool, run each stage in order.** You can type the slash command or just say the
|
|
65
81
|
words in quotes.
|
|
66
82
|
|
|
67
83
|
```
|
|
@@ -77,6 +93,11 @@ words in quotes.
|
|
|
77
93
|
/spec-ship "ship it"
|
|
78
94
|
```
|
|
79
95
|
|
|
96
|
+
**Fewer stops:** after reviewing the spec, say **"approved, run it through"**. The workflow then
|
|
97
|
+
implements, verifies, and commits in one go. It stops at the first failure, and it always asks
|
|
98
|
+
before pushing or opening a PR. To make this the default after approval, choose `run-through` during
|
|
99
|
+
setup (`pipeline.after_approval` in `.spec-driven.yml`).
|
|
100
|
+
|
|
80
101
|
That's the whole workflow. Every stage can be used on its own as well: use `/spec-create` straight
|
|
81
102
|
from a planning chat, or `/spec-checkpoint` on any work in progress.
|
|
82
103
|
|
|
@@ -89,24 +110,31 @@ others `fix/STP-123-short-name`. Some have one repo, others have six. None of th
|
|
|
89
110
|
skills. Each project describes its own rules in **`.spec-driven.yml`**, and every skill reads that
|
|
90
111
|
file before doing anything.
|
|
91
112
|
|
|
92
|
-
**How the file gets created:** `
|
|
113
|
+
**How the file gets created:** `spec-driven-dev` (or `spec-driven-dev setup` to run only this
|
|
93
114
|
step) detects the answers from your repos and asks you to confirm them. In your AI tool you can also
|
|
94
115
|
say **"run spec-setup"**: that does a deeper version that also reads `CLAUDE.md`/`AGENTS.md`, CI
|
|
95
116
|
config, and recent PRs, and fills in what the installer can't (such as each repo's role, or commands
|
|
96
117
|
for stacks other than Node). If a skill finds no config, it runs this setup first.
|
|
97
118
|
|
|
98
|
-
**What it
|
|
119
|
+
**What `spec-driven-dev` asks, in order** (every answer comes pre-filled with what it detected, so you
|
|
120
|
+
can usually just press Enter):
|
|
99
121
|
|
|
100
122
|
| Question | Detected from | Example answer |
|
|
101
123
|
|---|---|---|
|
|
124
|
+
| Which AI tools get the skills | tool folders in your home directory and commands on your `PATH` | Claude Code, Cursor |
|
|
125
|
+
| This project only, or all projects | (you choose) | `project` |
|
|
102
126
|
| Project layout | git repos and workspace files (`pnpm-workspace.yaml`, `turbo.json`, `nx.json`, `go.work`, …) | `single`, `monorepo`, or `multi-repo` |
|
|
103
|
-
|
|
|
127
|
+
| *multi-repo:* which repos nearby belong | git repos up to two folders down, grouped by GitHub owner | `frontend` |
|
|
128
|
+
| *multi-repo:* repos in other places | (you type paths: absolute, `~/…`, or relative) | `~/Work/backend, /srv/database` |
|
|
129
|
+
| *multi-repo:* where the config lives | the repos' shared parent folder, or `~/.spec-driven/<name>/` | `~/Projects` |
|
|
130
|
+
| *monorepo:* package folders | the workspaces your `package.json`/`pnpm-workspace.yaml` declares, or `apps/*`, `packages/*` | `apps/web, packages/ui` |
|
|
104
131
|
| Issue tracker | ticket keys in branch names and commits | Jira, key `STP` |
|
|
105
132
|
| Base branch / PR target | default branch, `staging`/`develop` branches | `staging` |
|
|
106
133
|
| Branch name pattern | your recent branch names | `{ticket}` → `STP-123` |
|
|
107
134
|
| Commit style | your recent commit messages | `conventional` (`feat: …`) or `ticket` (`STP-123: …`) |
|
|
108
135
|
| Lint / typecheck / test / build / dev commands | `package.json` scripts and lockfile (per repo or package) | `pnpm run lint` |
|
|
109
136
|
| Where specs go | (you choose) | `specs/` |
|
|
137
|
+
| After you approve a spec | (you choose) | `stop` (wait at each stage) or `run-through` |
|
|
110
138
|
|
|
111
139
|
**Where the file lives, by layout:**
|
|
112
140
|
|
|
@@ -114,9 +142,34 @@ for stacks other than Node). If a skill finds no config, it runs this setup firs
|
|
|
114
142
|
|---|---|---|---|
|
|
115
143
|
| **single** | one repo, one app | the repo root (commit it, so the team shares it) | as described below |
|
|
116
144
|
| **monorepo** | one repo, many apps/packages | the repo root (commit it) | tasks name their package, and checks run per package |
|
|
117
|
-
| **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 |
|
|
145
|
+
| **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 |
|
|
146
|
+
|
|
147
|
+
**Repos in different places.** A multi-repo workspace doesn't need its repos in one folder. For
|
|
148
|
+
example, from inside `/Projects/frontend`:
|
|
118
149
|
|
|
119
|
-
|
|
150
|
+
```
|
|
151
|
+
Layout — single, monorepo, or multi-repo (detected: single) [single]: multi-repo
|
|
152
|
+
Found 1 git repos under /Projects. Which ones belong to this workspace?
|
|
153
|
+
1. frontend
|
|
154
|
+
Repos [1]:
|
|
155
|
+
Other repo paths, comma-separated (empty for none): ~/Work/backend, /srv/database
|
|
156
|
+
Where should this workspace's .spec-driven.yml live [~/.spec-driven/frontend-workspace]:
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- **Where the config goes:** the repos' shared parent folder, or `~/.spec-driven/<name>/` when that
|
|
160
|
+
parent would be your home folder or `/`.
|
|
161
|
+
- **How every repo finds it:** setup records the workspace in `~/.spec-driven/workspaces.json`, so
|
|
162
|
+
`/Work/backend` finds its config even though the config isn't above it. The skills, `doctor`,
|
|
163
|
+
and `status` all look there.
|
|
164
|
+
- **Paths are checked:** ones that don't exist or aren't git repos are skipped with a message, and
|
|
165
|
+
setup stops if a workspace would end up with no repos.
|
|
166
|
+
|
|
167
|
+
Every question also has a flag, for scripts and CI:
|
|
168
|
+
`--layout=multi-repo --repos=~/Projects/frontend,~/Work/backend --workspace=~/Projects`,
|
|
169
|
+
`--layout=monorepo --packages=apps/web,packages/ui`, `--after-approval=run-through`,
|
|
170
|
+
`--tools=cursor,codex`, `--global`, `--yes` (accept everything detected), and `--version`.
|
|
171
|
+
|
|
172
|
+
To change the rules later, edit the file or run setup again, then run `spec-driven-dev doctor`
|
|
120
173
|
to confirm the file still matches your repos. Setup never overwrites the file without
|
|
121
174
|
asking. See [`config-template.yml`](skills/spec-setup/references/config-template.yml) for every field.
|
|
122
175
|
|
|
@@ -316,6 +369,18 @@ config sits outside any repo, so it stays on your machine.
|
|
|
316
369
|
**Does it work with my stack?**
|
|
317
370
|
Yes. No skill assumes a language or framework. Each one finds the repo's own commands.
|
|
318
371
|
|
|
372
|
+
**Do I have to start every stage myself?**
|
|
373
|
+
No. After you approve a spec, say "approved, run it through": implement, verify, and checkpoint then
|
|
374
|
+
run one after another, stopping at the first failure. Two steps always wait for you: approving the
|
|
375
|
+
spec, and pushing or opening the PR. You don't need slash commands either. Saying "investigate
|
|
376
|
+
STP-123" or "ship it" picks the right skill.
|
|
377
|
+
|
|
378
|
+
**Setup didn't ask me about repos.**
|
|
379
|
+
It only asks about repos for a multi-repo or monorepo layout. If it detected `single`, type
|
|
380
|
+
`multi-repo` at the layout question, then add your other repos by path. If it asked nothing at all,
|
|
381
|
+
there was no terminal to type answers into (it prints a note saying so), so run it directly in a
|
|
382
|
+
terminal. Check which version ran with `npx @benjaminthomas/spec-driven-dev@latest --version`.
|
|
383
|
+
|
|
319
384
|
**Can I skip stages?**
|
|
320
385
|
Yes. Every skill works on its own. `spec-verify` even works without a spec: it asks what to check.
|
|
321
386
|
|
|
@@ -326,6 +391,24 @@ Yes. Every skill works on its own. `spec-verify` even works without a spec: it a
|
|
|
326
391
|
Yes. Progress is saved in the spec's `README.md` checkboxes and the commit for each batch. Run
|
|
327
392
|
`spec-implement` again to continue.
|
|
328
393
|
|
|
394
|
+
**How do I update?**
|
|
395
|
+
Run `npm install -g @benjaminthomas/spec-driven-dev@latest`, then `spec-driven-dev` in your project.
|
|
396
|
+
Claude Code plugin users run
|
|
397
|
+
`/plugin marketplace update spec-driven-dev` and then update the plugin. Your config is never
|
|
398
|
+
touched by an update.
|
|
399
|
+
|
|
400
|
+
**Can I trust the npm package?**
|
|
401
|
+
Releases are published by this repo's GitHub Actions workflow, not from anyone's laptop, through
|
|
402
|
+
npm [trusted publishing](https://docs.npmjs.com/trusted-publishers). Each version's npm page shows
|
|
403
|
+
a provenance badge linking it to the exact commit and workflow run that built it. The package has
|
|
404
|
+
no dependencies, and it contains only `bin/cli.mjs` and the `skills/` folder.
|
|
405
|
+
|
|
406
|
+
**Something isn't working. Where do I start?**
|
|
407
|
+
Run `spec-driven-dev doctor` in your project. It checks the config against your
|
|
408
|
+
repos and names anything that doesn't match. If that passes, open an
|
|
409
|
+
[issue](https://github.com/benjaminthomas/spec-driven-dev/issues) with the doctor output and the
|
|
410
|
+
skill you were running.
|
|
411
|
+
|
|
329
412
|
**`spec-verify` says it skipped some UI checks.**
|
|
330
413
|
Your tool needs browser automation (an MCP browser server, a built-in browser tool, or an
|
|
331
414
|
extension) for checks that involve the UI. Checks that only touch the API or database work
|
|
@@ -338,23 +421,41 @@ everywhere.
|
|
|
338
421
|
### Any tool: npm (recommended)
|
|
339
422
|
|
|
340
423
|
```bash
|
|
341
|
-
|
|
342
|
-
npx @benjaminthomas/spec-driven-dev setup # only (re)run project setup
|
|
343
|
-
npx @benjaminthomas/spec-driven-dev --global # install for all projects, not just this one
|
|
344
|
-
npx @benjaminthomas/spec-driven-dev --tools=cursor,codex # pick tools without being asked
|
|
345
|
-
npx @benjaminthomas/spec-driven-dev --skills=spec-create,spec-ship # install only some skills
|
|
346
|
-
npx @benjaminthomas/spec-driven-dev --yes # accept every detected default (CI-friendly)
|
|
347
|
-
npx @benjaminthomas/spec-driven-dev doctor # check .spec-driven.yml against the real repos
|
|
348
|
-
npx @benjaminthomas/spec-driven-dev status # list specs with unfinished work
|
|
424
|
+
npm install -g @benjaminthomas/spec-driven-dev # once per machine; adds the `spec-driven-dev` command
|
|
349
425
|
```
|
|
350
426
|
|
|
427
|
+
Then, in a project:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
spec-driven-dev # detect tools, install skills, set up the project
|
|
431
|
+
spec-driven-dev setup # only (re)run project setup
|
|
432
|
+
spec-driven-dev --global # install skills for all projects, not just this one
|
|
433
|
+
spec-driven-dev --tools=cursor,codex # pick tools without being asked
|
|
434
|
+
spec-driven-dev --skills=spec-create,spec-ship # install only some skills
|
|
435
|
+
spec-driven-dev --yes # accept every detected default (CI-friendly)
|
|
436
|
+
spec-driven-dev doctor # check .spec-driven.yml against the real repos
|
|
437
|
+
spec-driven-dev status # list specs with unfinished work
|
|
438
|
+
spec-driven-dev --help # show all of the above
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Without installing:** replace `spec-driven-dev` with `npx @benjaminthomas/spec-driven-dev`, for
|
|
442
|
+
example `npx @benjaminthomas/spec-driven-dev setup`. That downloads and runs the latest version once,
|
|
443
|
+
which is handy in CI or on a machine you won't use again.
|
|
444
|
+
|
|
351
445
|
`doctor` checks that every branch, folder, and `npm run` script named in the config exists, and
|
|
352
446
|
that each value is one the skills understand. It exits 1 if anything is wrong, so you can run it in
|
|
353
447
|
CI. `status` prints nothing when all specs are finished.
|
|
354
448
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
449
|
+
**Updating:** run `npm install -g @benjaminthomas/spec-driven-dev@latest` to get the newest CLI,
|
|
450
|
+
then `spec-driven-dev` in each project to refresh its skills. This replaces the installed skill
|
|
451
|
+
folders and leaves your `.spec-driven.yml` alone. If you use `npx`, add `@latest`, because `npx`
|
|
452
|
+
can otherwise reuse an older copy it cached. What changed in each version is in
|
|
453
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
454
|
+
|
|
455
|
+
`npx github:benjaminthomas/spec-driven-dev` installs straight from GitHub instead, for example to
|
|
456
|
+
try an unreleased commit on `main`.
|
|
457
|
+
|
|
458
|
+
**Requirements:** Node.js 18 or newer and git. The installer has no dependencies.
|
|
358
459
|
|
|
359
460
|
### Claude Code (plugin, with automatic updates)
|
|
360
461
|
|
|
@@ -369,8 +470,8 @@ The plugin also adds a **session-start reminder**. When you open Claude Code in
|
|
|
369
470
|
unfinished specs, Claude is told which ones and what the next step is (for example,
|
|
370
471
|
`specs/stp-123: 3/5 tasks done → next: spec-implement`). It prints nothing when everything is
|
|
371
472
|
finished. If
|
|
372
|
-
you use the plugin, `
|
|
373
|
-
`
|
|
473
|
+
you use the plugin, `spec-driven-dev` skips Claude Code so you don't get every skill twice. Run
|
|
474
|
+
`spec-driven-dev setup` for the project config.
|
|
374
475
|
|
|
375
476
|
### By hand
|
|
376
477
|
|
|
@@ -423,20 +524,20 @@ skips that step rather than failing.
|
|
|
423
524
|
|
|
424
525
|
| Skill | Stage | SKILL.md lines | Words | Supporting files |
|
|
425
526
|
|---|---|---:|---:|---:|
|
|
426
|
-
| `spec-setup` | 0 · configure |
|
|
427
|
-
| `spec-investigate` | 1 · understand |
|
|
428
|
-
| `spec-create` | 2 · plan |
|
|
429
|
-
| `spec-implement` | 3 · build |
|
|
430
|
-
| `spec-verify` | 4 · prove |
|
|
431
|
-
| `spec-checkpoint` | 5 · commit |
|
|
432
|
-
| `spec-ship` | 6 · PR |
|
|
527
|
+
| `spec-setup` | 0 · configure | 114 | 1,022 | 1 |
|
|
528
|
+
| `spec-investigate` | 1 · understand | 325 | 2,741 | 1 |
|
|
529
|
+
| `spec-create` | 2 · plan | 157 | 1,415 | 4 |
|
|
530
|
+
| `spec-implement` | 3 · build | 273 | 2,109 | 3 |
|
|
531
|
+
| `spec-verify` | 4 · prove | 155 | 1,492 | 1 |
|
|
532
|
+
| `spec-checkpoint` | 5 · commit | 108 | 795 | 0 |
|
|
533
|
+
| `spec-ship` | 6 · PR | 220 | 1,268 | 0 |
|
|
433
534
|
| `generative-seo` | standalone | 255 | 2,209 | 7 |
|
|
434
|
-
| **Total** | | **1,
|
|
535
|
+
| **Total** | | **1,607** | **13,051** | **17** |
|
|
435
536
|
|
|
436
537
|
- **8** skills: **1** setup + **6** workflow stages + **1** standalone
|
|
437
538
|
- **4** tools supported natively (Claude Code, Codex CLI, Cursor, Antigravity), plus an MCP fallback
|
|
438
539
|
- **3** layouts: single repo, monorepo, multi-repo
|
|
439
|
-
- **1**
|
|
540
|
+
- **1** npm package, `@benjaminthomas/spec-driven-dev`, plus the Claude Code plugin and an MCP fallback
|
|
440
541
|
- **0** stack assumptions: lint, typecheck, test, build, and dev commands come from your repo
|
|
441
542
|
|
|
442
543
|
_The table is generated by `python3 scripts/check.py --fix`; CI fails if it goes out of date._
|
|
@@ -450,19 +551,22 @@ skills/<name>/SKILL.md the skill itself (frontmatter: name, description,
|
|
|
450
551
|
skills/<name>/references/ templates and prompts the skill loads when needed
|
|
451
552
|
.claude-plugin/ Claude Code plugin + marketplace manifests
|
|
452
553
|
.agents/skills/ symlinks so Codex/Antigravity pick the skills up inside this repo
|
|
453
|
-
bin/cli.mjs the
|
|
554
|
+
bin/cli.mjs the `spec-driven-dev` CLI: installer, setup, doctor, status (no dependencies)
|
|
454
555
|
package.json npm package (ships bin/ and skills/)
|
|
455
556
|
mcp-server/ MCP fallback server
|
|
456
557
|
hooks/hooks.json Claude Code session-start hook (runs `cli.mjs status`)
|
|
457
558
|
CHANGELOG.md what changed in each version
|
|
458
559
|
scripts/check.py consistency checks + metrics generator
|
|
459
560
|
scripts/test-cli.mjs installer self-test (fake HOME, throwaway repos)
|
|
561
|
+
.github/workflows/check.yml CI: consistency checks + installer self-test on every push and PR
|
|
562
|
+
.github/workflows/publish.yml publishes to npm when a vX.Y.Z tag is pushed
|
|
460
563
|
evals/ `claude plugin eval` cases
|
|
461
564
|
```
|
|
462
565
|
|
|
463
566
|
- **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
567
|
`scripts/check.py`, and this README. `python3 scripts/check.py` lists anything you missed.
|
|
465
|
-
- **Before pushing:** run `python3 scripts/check.py --fix
|
|
568
|
+
- **Before pushing:** run `python3 scripts/check.py --fix`, then `npm test` (which runs the checks
|
|
569
|
+
and the installer self-test). CI (`.github/workflows/check.yml`) runs
|
|
466
570
|
the same checks, the installer self-test, and a syntax check on the MCP server.
|
|
467
571
|
- **Evals:** `claude plugin eval . --runs 1` checks that skills trigger on their phrases and stay
|
|
468
572
|
within their own stage. These runs use your Claude credentials, so they aren't part of CI.
|
|
@@ -476,8 +580,15 @@ evals/ `claude plugin eval` cases
|
|
|
476
580
|
- **Releasing:**
|
|
477
581
|
1. Increase `version` in both `package.json` and `.claude-plugin/plugin.json` (the check fails if
|
|
478
582
|
they differ).
|
|
479
|
-
2. Add a `CHANGELOG.md` entry.
|
|
480
|
-
3.
|
|
583
|
+
2. Add a `CHANGELOG.md` entry, then commit and push.
|
|
584
|
+
3. Tag the release and push the tag: `git tag v1.4.1 && git push origin v1.4.1`.
|
|
585
|
+
4. Watch the **publish** run in the repo's Actions tab. When it's green, the new version is on
|
|
586
|
+
npm. Claude Code plugin users get it from the marketplace once the commit is on `main`.
|
|
587
|
+
|
|
588
|
+
`.github/workflows/publish.yml` then runs the tests and publishes to npm through
|
|
589
|
+
[trusted publishing](https://docs.npmjs.com/trusted-publishers). No npm token or passkey is
|
|
590
|
+
needed, and the package page shows a provenance badge linking the release to the exact commit.
|
|
591
|
+
The workflow refuses to publish if the tag doesn't match `package.json`.
|
|
481
592
|
|
|
482
593
|
## License
|
|
483
594
|
|
package/bin/cli.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
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=~/Projects/frontend,/srv/backend --workspace=<config dir>
|
|
11
|
+
// --packages=apps/web,packages/ui --after-approval=stop|run-through --version
|
|
10
12
|
import fs from "node:fs";
|
|
11
13
|
import os from "node:os";
|
|
12
14
|
import path from "node:path";
|
|
@@ -34,8 +36,15 @@ const command = args.find((a) => !a.startsWith("-")) || "install";
|
|
|
34
36
|
const YES = flag("yes") || flag("y") || !process.stdin.isTTY || ["doctor", "status"].includes(command);
|
|
35
37
|
const DIR = path.resolve(opt("dir") || process.cwd());
|
|
36
38
|
|
|
39
|
+
if (flag("version") || flag("v")) {
|
|
40
|
+
console.log(JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version);
|
|
41
|
+
process.exit(0);
|
|
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.");
|
|
45
|
+
|
|
37
46
|
if (flag("help") || flag("h")) {
|
|
38
|
-
console.log(fs.readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1,
|
|
47
|
+
console.log(fs.readFileSync(fileURLToPath(import.meta.url), "utf8").split("\n").slice(1, 11).join("\n").replace(/^\/\/ ?/gm, ""));
|
|
39
48
|
process.exit(0);
|
|
40
49
|
}
|
|
41
50
|
|
|
@@ -102,6 +111,21 @@ const isRepo = (d) => run("git", ["rev-parse", "--is-inside-work-tree"], d) ===
|
|
|
102
111
|
const readJson = (f) => { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } };
|
|
103
112
|
const subdirs = (d) => { try { return fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name); } catch { return []; } };
|
|
104
113
|
|
|
114
|
+
// Git repos up to two folders down (e.g. ~/work/client/api). A git worktree has a .git *file*;
|
|
115
|
+
// it belongs to its main repo, so it is skipped. Repos aren't searched inside.
|
|
116
|
+
function findRepos(dir, depth = 2, prefix = "") {
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const n of subdirs(dir)) {
|
|
119
|
+
if (n === "node_modules") continue;
|
|
120
|
+
const full = path.join(dir, n);
|
|
121
|
+
let isGitDir = false;
|
|
122
|
+
try { isGitDir = fs.statSync(path.join(full, ".git")).isDirectory(); } catch {}
|
|
123
|
+
if (isGitDir) out.push(prefix + n);
|
|
124
|
+
else if (depth > 1) out.push(...findRepos(full, depth - 1, `${prefix}${n}/`));
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
105
129
|
function detectLayout(dir) {
|
|
106
130
|
if (isRepo(dir)) {
|
|
107
131
|
const top = run("git", ["rev-parse", "--show-toplevel"], dir);
|
|
@@ -120,8 +144,7 @@ function detectLayout(dir) {
|
|
|
120
144
|
const mono = MARKERS.some((m) => fs.existsSync(path.join(top, m))) || pkg?.workspaces || cargo || packages.length > 1;
|
|
121
145
|
return { layout: mono ? "monorepo" : "single", root: top, repos: mono ? packages : [] };
|
|
122
146
|
}
|
|
123
|
-
|
|
124
|
-
const repos = subdirs(dir).filter((n) => { try { return fs.statSync(path.join(dir, n, ".git")).isDirectory(); } catch { return false; } });
|
|
147
|
+
const repos = findRepos(dir);
|
|
125
148
|
return repos.length ? { layout: "multi-repo", root: dir, repos } : { layout: "single", root: dir, repos: [] };
|
|
126
149
|
}
|
|
127
150
|
|
|
@@ -181,29 +204,126 @@ function toYaml(c) {
|
|
|
181
204
|
out += `tracker:\n type: ${c.tracker.type}\n key: ${y(c.tracker.key)}\n url: ${y(c.tracker.url)}\n`;
|
|
182
205
|
out += `git:\n${cmds(c.git, " ")}\n`;
|
|
183
206
|
out += `commands:\n${cmds(c.commands, " ")}\n dev_url: ${y(c.devUrl)}\n`;
|
|
207
|
+
out += `pipeline:\n after_approval: ${c.afterApproval || "stop"} # stop | run-through (implement → verify → checkpoint; ship always asks)\n`;
|
|
184
208
|
return out + `specs_dir: ${y(c.specsDir)}\n`;
|
|
185
209
|
}
|
|
186
210
|
|
|
211
|
+
const LAYOUTS = ["single", "monorepo", "multi-repo"];
|
|
212
|
+
const splitList = (v) => (v || "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
213
|
+
// Paths are stored relative to the config's folder when inside it, absolute otherwise.
|
|
214
|
+
const real = (p) => { try { return fs.realpathSync(p); } catch { return p; } }; // /var vs /private/var etc.
|
|
215
|
+
const relOrAbs = (root, p) => { const r = path.relative(real(root), real(p)); return r.startsWith("..") || path.isAbsolute(r) ? real(p) : r || "."; };
|
|
216
|
+
|
|
217
|
+
async function chooseLayout(detected) {
|
|
218
|
+
const forced = opt("layout");
|
|
219
|
+
if (forced && !LAYOUTS.includes(forced)) throw new Error(`--layout must be one of ${LAYOUTS.join(", ")}`);
|
|
220
|
+
if (forced) return forced;
|
|
221
|
+
for (;;) {
|
|
222
|
+
const a = await ask(`Layout — single, monorepo, or multi-repo (detected: ${detected})`, detected);
|
|
223
|
+
if (LAYOUTS.includes(a)) return a;
|
|
224
|
+
console.log(` Please type one of: ${LAYOUTS.join(", ")}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const expandHome = (p) => p.replace(/^~(?=$|\/)/, HOME);
|
|
229
|
+
|
|
230
|
+
function commonParent(paths) {
|
|
231
|
+
if (!paths.length) return "";
|
|
232
|
+
let parts = paths[0].split(path.sep);
|
|
233
|
+
for (const p of paths.slice(1)) { const q = p.split(path.sep); let i = 0; while (i < parts.length && parts[i] === q[i]) i++; parts = parts.slice(0, i); }
|
|
234
|
+
return parts.join(path.sep) || path.sep;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// multi-repo: the repos can live anywhere (/Projects/frontend, /Work/backend, …). Pick the ones found
|
|
238
|
+
// nearby, add any others by path, then decide where the config lives.
|
|
239
|
+
async function chooseRepos(d) {
|
|
240
|
+
const insideRepo = d.layout !== "multi-repo";
|
|
241
|
+
const scanRoot = insideRepo ? path.dirname(d.root) : d.root;
|
|
242
|
+
let picked = [];
|
|
243
|
+
const fromFlag = opt("repos");
|
|
244
|
+
if (fromFlag) {
|
|
245
|
+
picked = splitList(fromFlag).map((p) => path.resolve(DIR, expandHome(p)));
|
|
246
|
+
} else {
|
|
247
|
+
const found = findRepos(scanRoot);
|
|
248
|
+
if (found.length) {
|
|
249
|
+
const owners = {};
|
|
250
|
+
for (const r of found) (owners[gitFacts(path.join(scanRoot, r))?.owner || "?"] ||= []).push(r);
|
|
251
|
+
const biggest = Object.values(owners).sort((a, b) => b.length - a.length)[0] || [];
|
|
252
|
+
const here = insideRepo ? [path.relative(scanRoot, d.root)] : [];
|
|
253
|
+
console.log(`Found ${found.length} git repos under ${scanRoot}. Which ones belong to this workspace?`);
|
|
254
|
+
picked = (await pick("Repos", found.map((r) => ({ value: r, label: `${r} (${Object.keys(owners).find((o) => owners[o].includes(r))})` })), [...new Set([...here, ...(insideRepo ? [] : biggest)])]))
|
|
255
|
+
.map((r) => path.join(scanRoot, r));
|
|
256
|
+
} else if (insideRepo) picked = [d.root];
|
|
257
|
+
console.log("Repos in other places can be part of the same workspace (e.g. ~/Work/backend, /srv/database).");
|
|
258
|
+
const extra = await ask("Other repo paths, comma-separated (empty for none)", "");
|
|
259
|
+
picked.push(...splitList(extra).map((p) => path.resolve(DIR, expandHome(p))));
|
|
260
|
+
}
|
|
261
|
+
const abs = [];
|
|
262
|
+
for (const p of [...new Set(picked)]) {
|
|
263
|
+
if (!fs.existsSync(p)) console.log(` ✗ ${p}: folder not found — skipped`);
|
|
264
|
+
else if (!isRepo(p)) console.log(` ✗ ${p}: not a git repo — skipped`);
|
|
265
|
+
else abs.push(run("git", ["rev-parse", "--show-toplevel"], p) || p);
|
|
266
|
+
}
|
|
267
|
+
if (!abs.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).");
|
|
268
|
+
|
|
269
|
+
// Config location: the repos' shared parent folder, unless that's too broad (home or /), in which
|
|
270
|
+
// case a named folder under ~/.spec-driven/. Repos outside it find the config via the registry.
|
|
271
|
+
const parent = commonParent([...new Set(abs.map(real))].map((p) => path.dirname(p)));
|
|
272
|
+
const tooBroad = !parent || parent === HOME || parent === path.parse(parent).root || HOME.startsWith(parent + path.sep);
|
|
273
|
+
// Ran in a workspace folder and every repo is under it → keep the config right there.
|
|
274
|
+
const allUnderScan = !insideRepo && abs.every((p) => real(p).startsWith(real(scanRoot) + path.sep));
|
|
275
|
+
const defRoot = opt("workspace") ? path.resolve(DIR, expandHome(opt("workspace")))
|
|
276
|
+
: allUnderScan ? scanRoot
|
|
277
|
+
: tooBroad ? path.join(HOME, ".spec-driven", path.basename(abs[0]) + "-workspace") : parent;
|
|
278
|
+
const root = path.resolve(DIR, expandHome(await ask("Where should this workspace's .spec-driven.yml live", defRoot)));
|
|
279
|
+
fs.mkdirSync(root, { recursive: true });
|
|
280
|
+
const repos = [...new Set(abs)].map((p) => relOrAbs(root, p));
|
|
281
|
+
console.log(` Workspace repos: ${repos.join(", ")}`);
|
|
282
|
+
return { root, repos };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ~/.spec-driven/workspaces.json maps each multi-repo config to its repos, so a repo outside the
|
|
286
|
+
// config's folder (e.g. /Work/backend for a config in /Projects) can still find it.
|
|
287
|
+
const REGISTRY = () => path.join(HOME, ".spec-driven", "workspaces.json");
|
|
288
|
+
function readRegistry() { return readJson(REGISTRY()) || {}; }
|
|
289
|
+
function register(file, repoPaths) {
|
|
290
|
+
const reg = readRegistry();
|
|
291
|
+
reg[file] = repoPaths;
|
|
292
|
+
fs.mkdirSync(path.dirname(REGISTRY()), { recursive: true });
|
|
293
|
+
fs.writeFileSync(REGISTRY(), JSON.stringify(reg, null, 2) + "\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// monorepo: confirm or edit the package folders (relative to the repo root).
|
|
297
|
+
async function choosePackages(d) {
|
|
298
|
+
const root = d.root;
|
|
299
|
+
const fromFlag = opt("packages");
|
|
300
|
+
const answer = fromFlag || (await ask("Package folders, comma-separated, relative to the repo root (empty if none yet)", d.repos.join(",")));
|
|
301
|
+
const pkgs = [];
|
|
302
|
+
for (const p of splitList(answer)) {
|
|
303
|
+
if (fs.existsSync(path.resolve(root, p))) pkgs.push(relOrAbs(root, path.resolve(root, p)));
|
|
304
|
+
else console.log(` ✗ ${p}: folder not found in ${root} — skipped`);
|
|
305
|
+
}
|
|
306
|
+
return { root, repos: pkgs };
|
|
307
|
+
}
|
|
308
|
+
|
|
187
309
|
async function setup() {
|
|
310
|
+
if (opt("after-approval") && !["stop", "run-through"].includes(opt("after-approval"))) throw new Error("--after-approval must be stop or run-through");
|
|
188
311
|
console.log("\nProject setup — detecting how this project works…");
|
|
189
312
|
const d = detectLayout(DIR);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
const layout = await ask(`Layout — single, monorepo, or multi-repo (detected: ${d.layout})`, d.layout);
|
|
199
|
-
const root = d.root;
|
|
313
|
+
console.log(d.layout === "multi-repo" ? `Detected: ${d.repos.length} git repos under ${d.root} (multi-repo).`
|
|
314
|
+
: d.layout === "monorepo" ? `Detected: monorepo at ${d.root} with ${d.repos.length} package(s)${d.repos.length ? `: ${d.repos.join(", ")}` : ""}.`
|
|
315
|
+
: isRepo(d.root) ? `Detected: single repo at ${d.root}. If it works together with other repos, choose multi-repo.`
|
|
316
|
+
: `Detected: no git repo in ${d.root}. Choose multi-repo to list your repos, wherever they are.`);
|
|
317
|
+
const layout = await chooseLayout(d.layout);
|
|
318
|
+
const { root, repos } = layout === "multi-repo" ? await chooseRepos(d) : layout === "monorepo" ? await choosePackages(d) : { root: d.layout === "multi-repo" ? DIR : d.root, repos: [] };
|
|
319
|
+
if (layout === "single" && !isRepo(root)) console.log(` • ${root} isn't a git repo — run setup inside the repo, or choose multi-repo.`);
|
|
200
320
|
const file = path.join(root, ".spec-driven.yml");
|
|
201
321
|
if (fs.existsSync(file) && (await ask(`${file} exists — overwrite? (y/n)`, "n")) !== "y") {
|
|
202
322
|
console.log("• Kept the existing config. Run the spec-setup skill in your AI tool to review it.");
|
|
203
323
|
return;
|
|
204
324
|
}
|
|
205
325
|
|
|
206
|
-
const repoDirs = layout === "multi-repo" ? repos.map((r) => path.
|
|
326
|
+
const repoDirs = layout === "multi-repo" ? repos.map((r) => path.resolve(root, r)) : [root];
|
|
207
327
|
const facts = repoDirs.map(gitFacts).filter(Boolean);
|
|
208
328
|
const g = infer(facts);
|
|
209
329
|
if (facts.length) console.log(`Detected: tracker ${g.trackerType}${g.key ? ` (${g.key}-…)` : ""}, base ${g.base}, branches "${g.branchName}", commits ${g.commitStyle}`);
|
|
@@ -216,18 +336,27 @@ async function setup() {
|
|
|
216
336
|
const branchName = await ask("Branch name pattern ({ticket} {slug} {type})", g.branchName);
|
|
217
337
|
const commitStyle = await ask("Commit style (conventional/ticket/freeform)", g.commitStyle);
|
|
218
338
|
const specsDir = await ask("Where specs/<feature>/ folders go", "specs");
|
|
339
|
+
console.log("After you approve a spec, the workflow can stop and wait for you at each stage, or run");
|
|
340
|
+
console.log("implement → verify → checkpoint in one go (it stops at any failure, and never pushes without asking).");
|
|
341
|
+
let afterApproval = opt("after-approval");
|
|
342
|
+
if (!afterApproval) for (;;) {
|
|
343
|
+
afterApproval = await ask("After approval: stop or run-through", "stop");
|
|
344
|
+
if (["stop", "run-through"].includes(afterApproval)) break;
|
|
345
|
+
console.log(" Please type stop or run-through");
|
|
346
|
+
}
|
|
219
347
|
|
|
220
348
|
const config = {
|
|
221
|
-
layout, tracker: { type: trackerType, key, url }, specsDir, devUrl: "",
|
|
349
|
+
layout, tracker: { type: trackerType, key, url }, specsDir, devUrl: "", afterApproval,
|
|
222
350
|
git: { remote: "origin", base_branch: base, pr_target: pr, branch_name: branchName, commit_style: commitStyle },
|
|
223
351
|
commands: layout === "single" ? commandsFor(repoDirs[0]) : commandsFor(root),
|
|
224
|
-
repos:
|
|
225
|
-
const f = layout === "multi-repo" ? gitFacts(path.
|
|
352
|
+
repos: repos.map((r) => {
|
|
353
|
+
const f = layout === "multi-repo" ? gitFacts(path.resolve(root, r)) : null;
|
|
226
354
|
// 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.
|
|
355
|
+
return { path: r, role: "", base: f ? f.integration || "default" : "", commands: commandsFor(path.resolve(root, r)) };
|
|
228
356
|
}),
|
|
229
357
|
};
|
|
230
358
|
fs.writeFileSync(file, toYaml(config));
|
|
359
|
+
if (layout === "multi-repo") register(real(file), repos.map((r) => real(path.resolve(root, r))));
|
|
231
360
|
console.log(`✓ Wrote ${file}`);
|
|
232
361
|
console.log(isRepo(root) ? " Commit it so your team shares the same rules." : " This folder isn't a git repo, so the config is personal to this machine.");
|
|
233
362
|
console.log(' Fill in repo roles, dev_url, and non-Node commands by asking your AI tool to "run spec-setup".');
|
|
@@ -273,13 +402,18 @@ function findConfig(dir) {
|
|
|
273
402
|
for (let d = dir; ; d = path.dirname(d)) {
|
|
274
403
|
const f = path.join(d, ".spec-driven.yml");
|
|
275
404
|
if (fs.existsSync(f)) return f;
|
|
276
|
-
if (d === path.dirname(d) || d === HOME)
|
|
405
|
+
if (d === path.dirname(d) || d === HOME) break;
|
|
277
406
|
}
|
|
407
|
+
// Not above us: a multi-repo workspace elsewhere may list this repo.
|
|
408
|
+
const here = real(dir);
|
|
409
|
+
for (const [file, repos] of Object.entries(readRegistry()))
|
|
410
|
+
if (fs.existsSync(file) && repos.some((r) => here === r || here.startsWith(r + path.sep))) return file;
|
|
411
|
+
return null;
|
|
278
412
|
}
|
|
279
413
|
|
|
280
414
|
function doctor() {
|
|
281
415
|
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`);
|
|
416
|
+
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
417
|
const root = path.dirname(file);
|
|
284
418
|
let c;
|
|
285
419
|
try { c = parseYaml(fs.readFileSync(file, "utf8")); } catch (e) { throw new Error(`${file} could not be read: ${e.message}`); }
|
|
@@ -287,11 +421,12 @@ function doctor() {
|
|
|
287
421
|
if (!["single", "monorepo", "multi-repo"].includes(c.layout)) problems.push(`layout "${c.layout}" must be single, monorepo, or multi-repo`);
|
|
288
422
|
if (c.tracker && !["jira", "github", "linear", "none"].includes(c.tracker.type)) problems.push(`tracker.type "${c.tracker.type}" must be jira, github, linear, or none`);
|
|
289
423
|
if (c.git?.commit_style && !["conventional", "ticket", "freeform"].includes(c.git.commit_style)) problems.push(`git.commit_style "${c.git.commit_style}" must be conventional, ticket, or freeform`);
|
|
424
|
+
if (c.pipeline?.after_approval && !["stop", "run-through"].includes(c.pipeline.after_approval)) problems.push(`pipeline.after_approval "${c.pipeline.after_approval}" must be stop or run-through`);
|
|
290
425
|
if (c.git?.branch_name && !/\{(ticket|slug)\}/.test(c.git.branch_name)) problems.push(`git.branch_name "${c.git.branch_name}" needs {ticket} or {slug}`);
|
|
291
426
|
const repos = c.layout === "single" ? [{ path: "." }] : Array.isArray(c.repos) ? c.repos : [];
|
|
292
427
|
if (c.layout !== "single" && !repos.length) problems.push(`layout is ${c.layout} but repos: is empty`);
|
|
293
428
|
for (const r of repos) {
|
|
294
|
-
const dir = path.
|
|
429
|
+
const dir = path.resolve(root, r.path || ".");
|
|
295
430
|
const label = r.path === "." ? "repo" : r.path;
|
|
296
431
|
if (!fs.existsSync(dir)) { problems.push(`${label}: folder not found`); continue; }
|
|
297
432
|
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.
|
|
3
|
+
"version": "1.5.0",
|
|
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" },
|
|
@@ -17,7 +17,9 @@ Create a checkpoint commit that captures all current changes — but only after
|
|
|
17
17
|
## Project config (read first)
|
|
18
18
|
|
|
19
19
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
20
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
20
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
21
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
22
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
21
23
|
you would detect or assume. This skill uses `commands` (lint/typecheck/test/build), `git.commit_style`, and `layout`/`repos`.
|
|
22
24
|
|
|
23
25
|
**Multi-repo:** run this in every listed repo that has changes, making one commit per repo with the same subject. **Monorepo:** run the checks for the packages that changed, or the root commands if that's how the repo works.
|
|
@@ -20,7 +20,9 @@ The key insight: implementation plans that live in a single file are either too
|
|
|
20
20
|
## Project config (read first)
|
|
21
21
|
|
|
22
22
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
23
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
23
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
24
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
25
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
24
26
|
you would detect or assume. This skill uses `layout`/`repos` and `specs_dir`.
|
|
25
27
|
|
|
26
28
|
**Monorepo / multi-repo:** every task names its package or repo, and its file paths are relative to that. Keep each task inside one repo. Cross-repo work becomes separate tasks, with the contract (API shape, event, schema) written into both.
|
|
@@ -136,9 +138,15 @@ Batch breakdown:
|
|
|
136
138
|
Next steps:
|
|
137
139
|
1. Review action-required.md for tasks you need to complete manually
|
|
138
140
|
2. Review the requirements and task files
|
|
139
|
-
3.
|
|
141
|
+
3. When you're happy with it, either:
|
|
142
|
+
- say "implement" to build it and stop for review, or
|
|
143
|
+
- say "approved, run it through" to build, verify and commit in one go (stops at any failure,
|
|
144
|
+
never pushes without asking)
|
|
140
145
|
```
|
|
141
146
|
|
|
147
|
+
If `.spec-driven.yml` has `pipeline.after_approval: run-through`, say that this project runs through
|
|
148
|
+
by default after approval. Never start implementing from this skill.
|
|
149
|
+
|
|
142
150
|
## Critical Rules
|
|
143
151
|
|
|
144
152
|
- Every task file must be fully self-contained — this is the entire point of the spec structure. A coder agent reading only that file must know exactly what to do.
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Orchestrate parallel implementation of a feature specification by dispatching coder agents
|
|
5
5
|
batch-by-batch with code review gates between batches. Use this skill when the user says
|
|
6
6
|
"implement this feature", "start implementing", "run the spec", "execute the plan",
|
|
7
|
-
"continue implementing", or wants to begin coding a previously planned feature from a
|
|
7
|
+
"continue implementing", "approved, run it through", or wants to begin coding a previously planned feature from a
|
|
8
8
|
specs/{feature}/ folder. Also use when the user says "/spec-implement" (or their host's
|
|
9
9
|
equivalent invocation syntax) or drags a spec folder into the conversation and asks to
|
|
10
10
|
implement it. This skill does NOT write code itself — it orchestrates coder subagents that
|
|
@@ -26,7 +26,9 @@ The orchestrator never writes code itself. Its job is to:
|
|
|
26
26
|
## Project config (read first)
|
|
27
27
|
|
|
28
28
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
29
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
29
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
30
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
31
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
30
32
|
you would detect or assume. This skill uses `layout`/`repos`, `commands` (lint/typecheck/test per repo or package), and `git.commit_style`.
|
|
31
33
|
|
|
32
34
|
**Multi-repo:** create worktrees in each repo a task touches, run each repo's own commands in review, and make the batch commit in every repo that changed, using the same subject line.
|
|
@@ -220,6 +222,41 @@ Next steps:
|
|
|
220
222
|
- Push and create a PR when ready
|
|
221
223
|
```
|
|
222
224
|
|
|
225
|
+
## Run-through mode (after approval)
|
|
226
|
+
|
|
227
|
+
Run-through applies when the user asks for it ("approved, run it through", "build, verify and
|
|
228
|
+
commit it") or when `.spec-driven.yml` has `pipeline.after_approval: run-through` and the user has
|
|
229
|
+
approved the spec. In that case, don't stop after Step 9. Continue:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
Run-through progress:
|
|
233
|
+
- [ ] Implement all batches (Steps 1–9) — every review gate passed
|
|
234
|
+
- [ ] Verify — follow the spec-verify skill; every acceptance criterion PASS
|
|
235
|
+
- [ ] Checkpoint — follow the spec-checkpoint skill; every check passes, commit made
|
|
236
|
+
- [ ] Stop and hand off to spec-ship
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
1. **Gate on each stage.** Move on only if the previous stage fully passed. On any failure (a review
|
|
240
|
+
gate exhausted its 3 cycles, a criterion FAILed or was SKIPPED, a check failed), stop the
|
|
241
|
+
run-through and report where it stopped, why, and the command to resume (e.g. "fix X, then say
|
|
242
|
+
'continue the run-through'").
|
|
243
|
+
2. **Verify, then checkpoint.** Follow those skills' own instructions in full, including the
|
|
244
|
+
verification report. Don't shortcut them because you're running them in sequence.
|
|
245
|
+
3. **Never push.** The run-through ends at a local commit. Finish with the summary below and ask
|
|
246
|
+
whether to run `spec-ship`. `spec-ship` still shows exactly what will be pushed and waits for a
|
|
247
|
+
yes.
|
|
248
|
+
4. **No approval, no run-through.** If the spec hasn't been reviewed and approved in this
|
|
249
|
+
conversation, or the user only said "implement", do the normal Steps 1–9 and stop.
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
Run-through complete for "{feature}":
|
|
253
|
+
- Implement: {N} batches, all review gates PASS
|
|
254
|
+
- Verify: {P}/{P} criteria PASS (specs/{feature}/verification-report.md)
|
|
255
|
+
- Checkpoint: {commit hash} — {subject}
|
|
256
|
+
|
|
257
|
+
Ready to push and open a PR. Run spec-ship? (it will show exactly what gets pushed first)
|
|
258
|
+
```
|
|
259
|
+
|
|
223
260
|
## Error Handling
|
|
224
261
|
|
|
225
262
|
- **Coder agent failure**: mark the task as failed, report to user, continue with remaining tasks in the batch. The review step will catch integration issues.
|
|
@@ -33,7 +33,9 @@ ready-to-use work branch; a human decides when to run `spec-create` next.
|
|
|
33
33
|
## Project config (read first)
|
|
34
34
|
|
|
35
35
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
36
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
36
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
37
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
38
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
37
39
|
you would detect or assume. This skill uses `layout`/`repos` (where to look and what each repo does), `tracker`, `git.remote`, `git.base_branch`, `git.branch_name`, and `specs_dir`.
|
|
38
40
|
|
|
39
41
|
**Monorepo:** name the package(s) involved. **Multi-repo:** search every listed repo whose role fits the ticket, and create the work branch, with the same name, only in the repos the investigation shows need changes.
|
|
@@ -41,7 +41,18 @@ 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 repos of one workspace can live in unrelated places (`/Projects/frontend`,
|
|
48
|
+
`~/Work/backend`, `/srv/database`). Put the config in their shared parent folder, or in
|
|
49
|
+
`~/.spec-driven/<name>/` when that parent would be the home folder or `/`. Then record it in
|
|
50
|
+
`~/.spec-driven/workspaces.json` (`{"<config path>": ["<repo path>", …]}`) so every repo can
|
|
51
|
+
find it.
|
|
52
|
+
- The user can override the detected layout. If they choose `multi-repo` from inside one repo,
|
|
53
|
+
the workspace folder defaults to that repo's parent, with the current repo pre-selected. If
|
|
54
|
+
they choose `monorepo`, ask for the package folders. Never write a `multi-repo` config with
|
|
55
|
+
no repos.
|
|
45
56
|
- Otherwise → `single`.
|
|
46
57
|
2. **Existing written rules.** `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`, PR templates,
|
|
47
58
|
`.github/workflows`. Anything stated there is a strong default — quote it back to the user.
|
|
@@ -58,6 +69,10 @@ Read, don't guess. Collect a draft of every field in `references/config-template
|
|
|
58
69
|
steps (CI is often the most truthful source). Dev URL from the dev script, `.env.example`, or
|
|
59
70
|
framework defaults.
|
|
60
71
|
|
|
72
|
+
6. **After approval.** Ask whether an approved spec should stop after each stage (`stop`), or
|
|
73
|
+
run implement → verify → checkpoint in one go (`run-through`), stopping at any failure and never
|
|
74
|
+
pushing. Store it as `pipeline.after_approval`.
|
|
75
|
+
|
|
61
76
|
## Step 2 — Confirm in one round
|
|
62
77
|
|
|
63
78
|
Show the draft as a short table grouped by layout, tracker, git, commands, and specs location.
|
|
@@ -42,4 +42,10 @@ commands:
|
|
|
42
42
|
dev: "" # how spec-verify starts the app
|
|
43
43
|
dev_url: "" # where it answers, e.g. http://localhost:3000
|
|
44
44
|
|
|
45
|
+
pipeline:
|
|
46
|
+
# stop — every stage waits for you (default)
|
|
47
|
+
# run-through — after you approve a spec: implement → verify → checkpoint in one go,
|
|
48
|
+
# stopping at any failure. Pushing / opening the PR (spec-ship) always asks first.
|
|
49
|
+
after_approval: stop
|
|
50
|
+
|
|
45
51
|
specs_dir: specs # where specs/<feature>/ folders go (relative to this file)
|
|
@@ -19,7 +19,9 @@ This skill complements `spec-checkpoint`. Use `spec-checkpoint` to commit locall
|
|
|
19
19
|
## Project config (read first)
|
|
20
20
|
|
|
21
21
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
22
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
22
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
23
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
24
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
23
25
|
you would detect or assume. This skill uses `git.remote`, `git.pr_target`, `git.branch_name`, `tracker` (to link the ticket in the PR), and `layout`/`repos`.
|
|
24
26
|
|
|
25
27
|
**Multi-repo:** push and open one PR per repo that has unpushed commits, and list the sibling PRs in each PR's body so reviewers see the whole change.
|
|
@@ -26,7 +26,9 @@ by the real code path. It produces a report the user can trust before shipping.
|
|
|
26
26
|
## Project config (read first)
|
|
27
27
|
|
|
28
28
|
Load `.spec-driven.yml`: walk up from the current directory to the home folder, and the first one
|
|
29
|
-
found wins (a `multi-repo` config only applies inside the repos it lists).
|
|
29
|
+
found wins (a `multi-repo` config only applies inside the repos it lists). If none is found, check
|
|
30
|
+
`~/.spec-driven/workspaces.json`, which maps each multi-repo config to its repo paths. Use the config
|
|
31
|
+
whose repos contain the current directory, because a workspace's repos can live in unrelated folders. Its values beat anything
|
|
30
32
|
you would detect or assume. This skill uses `commands.dev`, `commands.dev_url`, and `layout`/`repos`.
|
|
31
33
|
|
|
32
34
|
**Monorepo / multi-repo:** start every app or service the criteria need (e.g. the API and the frontend), each with its own `dev` command.
|