@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 +63 -91
- package/bin/cli.mjs +77 -30
- package/bin/share.mjs +5 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/spec-investigate/SKILL.md +12 -0
- package/plugin/skills/spec-setup/SKILL.md +35 -4
- package/plugin/skills/spec-setup/references/config-template.yml +12 -1
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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:**
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
|
149
|
+
| What it asks | Detected from | Example answer |
|
|
162
150
|
|---|---|---|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
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
|
|
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 |
|
|
174
|
-
|
|
|
175
|
-
|
|
|
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
|
|
188
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
**
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
|
613
|
-
|
|
614
|
-
`
|
|
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
|
|
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
|
|
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.
|
|
717
|
-
|
|
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 |
|
|
772
|
-
| `spec-investigate` | 1 · understand |
|
|
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,
|
|
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.
|
|
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
|
|
6
|
-
// spec-driven-dev setup
|
|
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
|
-
|
|
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
|
|
63
|
-
• or
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|
|
868
|
-
console.log("No interactive terminal (for example a \"!\" command in Claude Code), so
|
|
869
|
-
const
|
|
870
|
-
if (
|
|
871
|
-
|
|
872
|
-
|
|
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 (
|
|
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,
|
|
17
|
-
const features = !ignored(top,
|
|
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.
|
|
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.
|
|
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.
|
|
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. **
|
|
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
|