wdi-method 0.4.5 → 0.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 +58 -16
- package/bin/wdi-method.js +152 -60
- package/kit/.constitution/method/README.md +66 -169
- package/kit/.constitution/{constitution.md → method/constitution.md} +10 -56
- package/kit/.constitution/{document → method/document}/architecture-guide.md +1 -1
- package/kit/.constitution/{document → method/document}/corpus-guide.md +2 -2
- package/kit/.constitution/{document → method/document}/delivery-flow-guide.md +338 -338
- package/kit/.constitution/{document → method/document}/sdd-guide.md +1 -1
- package/kit/.constitution/{document → method/document}/templates/cross-cutting.md +5 -5
- package/kit/.constitution/{document → method/document}/templates/design-system.md +67 -67
- package/kit/.constitution/{document → method/document}/templates/inventory.md +69 -69
- package/kit/.constitution/{document → method/document}/templates/model.md +2 -2
- package/kit/.constitution/{document → method/document}/templates/questions.md +10 -9
- package/kit/.constitution/{document → method/document}/templates/sdd.md +1 -1
- package/kit/.constitution/{document → method/document}/templates/srs.md +2 -2
- package/kit/.constitution/{document → method/document}/templates/structure-codebase.md +129 -129
- package/kit/.constitution/{document → method/document}/templates/structure-document.md +90 -90
- package/kit/.constitution/{language-guide.md → method/language-guide.md} +2 -2
- package/kit/.constitution/{repo-guide.md → method/repo-guide.md} +16 -3
- package/kit/.constitution/{scripts → method/scripts}/inventory.py +102 -100
- package/kit/.constitution/{scripts → method/scripts}/timeline.py +665 -665
- package/kit/.constitution/{scripts → method/scripts}/validate.py +1599 -1579
- package/kit/.constitution/{structure-guide.md → method/structure-guide.md} +3 -3
- package/kit/.constitution/method/why/README.md +169 -0
- package/kit/.constitution/method/{artifact-map.md → why/artifact-map.md} +5 -5
- package/kit/.constitution/method/{rationale.md → why/rationale.md} +5 -5
- package/kit/.constitution/project/constitution.md +63 -0
- package/kit/assets/bmad-custom/bmad-advanced-elicitation.toml +9 -9
- package/kit/assets/bmad-custom/bmad-architecture.toml +33 -31
- package/kit/assets/bmad-custom/bmad-build-auto.toml +5 -5
- package/kit/assets/bmad-custom/bmad-build.toml +52 -52
- package/kit/assets/bmad-custom/bmad-code-review.toml +13 -12
- package/kit/assets/bmad-custom/bmad-correct-course.toml +20 -19
- package/kit/assets/bmad-custom/bmad-deep-recon.toml +12 -11
- package/kit/assets/bmad-custom/bmad-prd.toml +42 -42
- package/kit/assets/bmad-custom/bmad-product-brief.toml +18 -18
- package/kit/assets/bmad-custom/bmad-retrospective.toml +4 -4
- package/kit/assets/bmad-custom/bmad-spec.toml +16 -15
- package/kit/assets/bmad-custom/bmad-ux.toml +19 -19
- package/kit/assets/bmad-custom/config.toml +3 -3
- package/kit/skills/wdi-blueprint/SKILL.md +3 -3
- package/kit/skills/wdi-build/SKILL.md +244 -244
- package/kit/skills/wdi-component/SKILL.md +1 -1
- package/kit/skills/wdi-help/SKILL.md +98 -98
- package/kit/skills/wdi-init/SKILL.md +1 -1
- package/kit/skills/wdi-problem/SKILL.md +90 -90
- package/kit/skills/wdi-product/SKILL.md +127 -127
- package/kit/skills/wdi-reconcile/SKILL.md +2 -2
- package/kit/skills/wdi-report/SKILL.md +205 -205
- package/kit/skills/wdi-systematic-debugging/SKILL.md +193 -193
- package/kit/skills/wdi-ux/SKILL.md +145 -145
- package/kit-overlay/AGENTS.md +20 -20
- package/kit-overlay/README.md +24 -17
- package/kit-overlay/constitution.md +10 -56
- package/kit-overlay/portability.md +6 -6
- package/kit-overlay/repo-guide.md +112 -97
- package/package.json +37 -37
- package/scaffold/.control/product-glossary.md +21 -21
- package/scaffold/.control/project-non-technical-log.md +23 -23
- package/scaffold/.control/questions/answered.md +11 -11
- package/scaffold/.control/questions/assumptions.md +15 -15
- package/scaffold/.control/questions/blocking.md +21 -21
- package/scaffold/.control/questions/external.md +11 -11
- package/scaffold/.control/registry/components.yaml +21 -21
- package/scaffold/.control/registry/defects.yaml +3 -3
- package/scaffold/.control/registry/index.yaml +46 -46
- package/scaffold/.control/registry/requirements.yaml +15 -15
- package/scaffold/.control/registry/risks.yaml +5 -5
- package/scaffold/.control/registry/usecases.yaml +6 -6
- package/kit/.constitution/README.md +0 -66
- /package/kit/.constitution/{document → method/document}/bmad-guide.md +0 -0
- /package/kit/.constitution/{document → method/document}/bmad-skill-register.md +0 -0
- /package/kit/.constitution/{document → method/document}/brief-guide.md +0 -0
- /package/kit/.constitution/{document → method/document}/decision-guide.md +0 -0
- /package/kit/.constitution/{document → method/document}/prd-guide.md +0 -0
- /package/kit/.constitution/{document → method/document}/srs-guide.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/addendum.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/architecture-spine.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/brief.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/c4.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/contract.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/decision.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/flow.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/integration.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/lc.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/lifecycle.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/meeting.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/oq.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/prd.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/rtr.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/rules.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/scn.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/uc.md +0 -0
- /package/kit/.constitution/{document → method/document}/templates/ux.md +0 -0
- /package/kit/.constitution/{document → method/document}/ux-guide.md +0 -0
- /package/kit/.constitution/{method-glossary.md → method/method-glossary.md} +0 -0
- /package/kit/.constitution/method/{portability.md → why/portability.md} +0 -0
- /package/kit/.constitution/{codebase/brownfield-guide.md → project/codebase-brownfield-guide.md} +0 -0
- /package/kit/.constitution/{codebase/conventions-guide.md → project/codebase-conventions-guide.md} +0 -0
- /package/kit/.constitution/{codebase/stack-guide.md → project/codebase-stack-guide.md} +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# WDI Method
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The review layer BMad leaves thin — documents a human reads to check a decision before code gets written, sized to what the change actually deserves.**
|
|
4
4
|
|
|
5
|
-
BMad decides *what to build
|
|
6
|
-
documents a **human** reads to check that the decision is right before anybody writes code. WDI Method
|
|
7
|
-
adds that middle, and a way to choose how much of it you want.
|
|
5
|
+
[BMad](https://github.com/bmad-code-org/BMAD-METHOD) decides *what* to build and *how* to build it well. WDI Method wraps it — it does not replace it — and adds the part between those two decisions and the code: inventories, a use case catalogue, a component design record, and a way to choose how much of that a given change actually needs.
|
|
8
6
|
|
|
9
|
-
This repository is **public
|
|
10
|
-
to a private repository
|
|
7
|
+
> This repository is **public and generic**. It MUST NOT carry a client name, a product name, or a
|
|
8
|
+
> link to a private repository — product identity lives entirely in the repo that installs it.
|
|
11
9
|
|
|
12
10
|
---
|
|
13
11
|
|
|
@@ -50,6 +48,25 @@ answers with the gate you are at, not with a menu.
|
|
|
50
48
|
|
|
51
49
|
---
|
|
52
50
|
|
|
51
|
+
## Why WDI Method?
|
|
52
|
+
|
|
53
|
+
- **Depth separate from scrutiny.** `mode` sets how much gets written; `risk_accepted` sets how hard it
|
|
54
|
+
gets reviewed. Neither is derived from the other, so a component MAY be thin on purpose and reviewed the
|
|
55
|
+
hardest.
|
|
56
|
+
- **Ground truth over plan.** Once code exists, the tables, endpoints, and screens are **derived from it**
|
|
57
|
+
— the gap between plan and reality is a finding to resolve, not an argument to have.
|
|
58
|
+
- **Containers that match what actually ships.** C4's containers follow deployability, not folders, and a
|
|
59
|
+
component view is drawn for every container that carries more than one Product Component.
|
|
60
|
+
- **A gate that can be skipped honestly.** `mode: catalog` skips the component gate entirely — a fast
|
|
61
|
+
default is fast because the work is genuinely gone, not nominally trimmed.
|
|
62
|
+
- **Decisions that don't rot.** A `DEC-` is recorded only when the reason would not survive reading the
|
|
63
|
+
code, and it freezes the moment it is applied — a change of mind writes a new one rather than editing
|
|
64
|
+
the old.
|
|
65
|
+
- **Wraps BMad, never forks it.** Every `wdi-*` skill is a wrapper around a BMad skill. Upgrading BMad
|
|
66
|
+
does not strand you, and no BMad skill is meant to be invoked directly.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
53
70
|
## The gap this fills
|
|
54
71
|
|
|
55
72
|
A gate is only as good as the artifact it reads. Between *"the architecture is decided"* and *"the code
|
|
@@ -148,15 +165,27 @@ reads guards nothing.
|
|
|
148
165
|
|
|
149
166
|
## What is generic, and where your own rules live
|
|
150
167
|
|
|
151
|
-
`.constitution/`
|
|
168
|
+
`.constitution/` holds **exactly two folders**, and the folder is the whole answer to who owns a file:
|
|
169
|
+
|
|
170
|
+
| Folder | Owner | `update` | `promote` |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| `.constitution/method/` | the method | **overwritten** in full | carries it into the package |
|
|
173
|
+
| **`.constitution/project/`** | you | **never touched** — seeded once when absent | never carries it, so your rules cannot be published |
|
|
174
|
+
|
|
175
|
+
Everything in the room is yours: `project/constitution.md` (Articles 1, 2, 5 — scope, repo checklist,
|
|
176
|
+
method ownership), `project/codebase-*-guide.md` (stack, conventions, brownfield, protected at **any**
|
|
177
|
+
`status:` — `Draft` is when they actually get written), and any rule file you add.
|
|
178
|
+
|
|
179
|
+
**The seam is a folder, never a marked region inside a generic file.** Prose has no merge algebra: you
|
|
180
|
+
cannot "merge" your paragraph with the method's, so only a path can say unambiguously whose a file is.
|
|
181
|
+
`AGENTS.md` is the one exception, and only because it is a single file with nowhere else to go.
|
|
152
182
|
|
|
153
|
-
|
|
183
|
+
Two more things are yours, outside `.constitution/`:
|
|
184
|
+
|
|
185
|
+
| Yours | Because |
|
|
154
186
|
|---|---|
|
|
155
187
|
| `.control/registry/index.yaml` → `product:` | The product and client name live in exactly one place |
|
|
156
|
-
| `
|
|
157
|
-
| `.constitution/codebase/*-guide.md` | Your stack and conventions, protected once `Accepted` |
|
|
158
|
-
| **`.constitution/project/`** | Any rule that binds **only this product** |
|
|
159
|
-
| `_bmad/custom/*.user.toml` | Your BMad overrides |
|
|
188
|
+
| `_bmad/custom/*.user.toml` | Your BMad overrides — TOML, so these genuinely merge: a string replaces, a list appends, a table merges per key |
|
|
160
189
|
|
|
161
190
|
`.control/` `.what/` `.how/` are never touched by an update at all — they are your state, your promises,
|
|
162
191
|
and your design.
|
|
@@ -181,7 +210,8 @@ Write whatever names the language — `English`, `Bahasa Indonesia`, `id`. What
|
|
|
181
210
|
and a model does not need a lookup table.
|
|
182
211
|
|
|
183
212
|
Always English, and never asked: method terminology, document code prefixes (`UC-`, `DEC-`), machine
|
|
184
|
-
markers (`[NEEDS CONFIRMATION]`, `[MISSING]`), and code identifiers.
|
|
213
|
+
markers (`[NEEDS CONFIRMATION]`, `[MISSING]`), and code identifiers. `.constitution/` itself is always
|
|
214
|
+
English, whatever the settings say — it travels to every repo through this package.
|
|
185
215
|
|
|
186
216
|
---
|
|
187
217
|
|
|
@@ -189,9 +219,9 @@ markers (`[NEEDS CONFIRMATION]`, `[MISSING]`), and code identifiers.
|
|
|
189
219
|
|
|
190
220
|
| | |
|
|
191
221
|
|---|---|
|
|
192
|
-
| Overwrites | `.constitution/`
|
|
222
|
+
| Overwrites | everything in `.constitution/method/` · the fifteen wrappers · `_bmad/custom/*.toml` · the marked block in `AGENTS.md` |
|
|
193
223
|
| Removes | Wrappers the method has retired — a `wdi-*` folder with a `SKILL.md` that is no longer one of the fifteen. Each removal is printed |
|
|
194
|
-
| Keeps |
|
|
224
|
+
| Keeps | All of `.constitution/project/`, plus your initiative slug and your language choice. A setting somebody already chose is not the installer's to change behind their back |
|
|
195
225
|
| Never resurrects | A folder you retired. On update, absence is treated as a decision |
|
|
196
226
|
|
|
197
227
|
It prints the version it replaced, what it wrote, what it kept, and what to do next.
|
|
@@ -219,4 +249,16 @@ carefully to read the diff. [`CONTRIBUTING.md`](CONTRIBUTING.md) has the detail,
|
|
|
219
249
|
|
|
220
250
|
---
|
|
221
251
|
|
|
222
|
-
|
|
252
|
+
## Support and Contributing
|
|
253
|
+
|
|
254
|
+
Open an [issue](https://github.com/wiradigitalid/wdi-method/issues) for a bug or a proposal. Read
|
|
255
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) before sending a pull request — it explains where a change belongs,
|
|
256
|
+
how versioning works here, and what to check before publishing.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT — see [LICENSE](LICENSE). Requires Node 20+ and [uv](https://docs.astral.sh/uv/) for the Python
|
|
261
|
+
scripts.
|
|
262
|
+
|
|
263
|
+
[](https://www.npmjs.com/package/wdi-method)
|
|
264
|
+
[](LICENSE)
|
package/bin/wdi-method.js
CHANGED
|
@@ -42,8 +42,7 @@ const WDI_SKILLS = [
|
|
|
42
42
|
"wdi-systematic-debugging",
|
|
43
43
|
];
|
|
44
44
|
|
|
45
|
-
const
|
|
46
|
-
const PRD_SLUG_PLACEHOLDER = "ISI-slug-inisiatif";
|
|
45
|
+
const PRD_SLUG_PLACEHOLDER = "FILL-initiative-slug";
|
|
47
46
|
const GENERIC_FOLDER_PATTERNS = new Set([
|
|
48
47
|
"_product-brief",
|
|
49
48
|
"ux",
|
|
@@ -205,15 +204,6 @@ function posixRel(from, to) {
|
|
|
205
204
|
return path.relative(from, to).split(path.sep).join("/");
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
function acceptedCodebase(file) {
|
|
209
|
-
try {
|
|
210
|
-
const head = fs.readFileSync(file, "utf8").slice(0, 400);
|
|
211
|
-
return /status:\s*Accepted/i.test(head);
|
|
212
|
-
} catch {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
207
|
function bmadPresent(target) {
|
|
218
208
|
const markers = [
|
|
219
209
|
path.join(target, ".claude", "skills", "bmad-help", "SKILL.md"),
|
|
@@ -308,13 +298,102 @@ function bmadMissingMessage() {
|
|
|
308
298
|
].join("\n");
|
|
309
299
|
}
|
|
310
300
|
|
|
311
|
-
//
|
|
312
|
-
// install/update
|
|
313
|
-
// promote
|
|
314
|
-
// agent
|
|
315
|
-
//
|
|
301
|
+
// The product's custom room. Three properties, and all three MUST hold together:
|
|
302
|
+
// install/update seeds its content ONLY when absent — never written again after that
|
|
303
|
+
// promote SKIPS it entirely, so a product's own rules can never reach the public repo
|
|
304
|
+
// agent loads it like any other guide, so it BINDS
|
|
305
|
+
// The deliberate consequence: this room's README is authored in the package and never comes home
|
|
306
|
+
// through promote.
|
|
316
307
|
const PROJECT_ROOM = "project/";
|
|
317
308
|
|
|
309
|
+
// 0.5.0 moved `.constitution/` to exactly two folders: `method/` is the method's and is overwritten,
|
|
310
|
+
// `project/` is the product's and is never touched. Before it, generic and product-owned files sat
|
|
311
|
+
// side by side at the root, `codebase/` was a third product-owned room nobody had written down, and
|
|
312
|
+
// `constitution.md` was ONE file holding both — which is why `update` had to keep the whole thing and
|
|
313
|
+
// the product never received a fixed generic Article.
|
|
314
|
+
//
|
|
315
|
+
// Without this migration an installed repo would end up carrying BOTH layouts: the kit writes the new
|
|
316
|
+
// paths while the old files stay behind, and an agent reading `AGENTS.md` routing would find two
|
|
317
|
+
// copies of most guides and no way to tell which binds.
|
|
318
|
+
const OLD_ROOT_GUIDES = ["README", "language-guide", "method-glossary", "repo-guide", "structure-guide"];
|
|
319
|
+
const OLD_WHY = ["README", "artifact-map", "portability", "rationale"];
|
|
320
|
+
const OLD_CODEBASE = ["stack", "conventions", "brownfield"];
|
|
321
|
+
|
|
322
|
+
function mv(from, to) {
|
|
323
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
324
|
+
fs.renameSync(from, to);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function migrateToTwoFolders(target) {
|
|
328
|
+
const c = path.join(target, ".constitution");
|
|
329
|
+
if (!fs.existsSync(c)) return false; // a first install has nothing to migrate
|
|
330
|
+
const at = (...p) => path.join(c, ...p);
|
|
331
|
+
// The old layout is identified by `document/` at the ROOT — in the new layout that folder only ever
|
|
332
|
+
// exists under `method/`. Checking a loose guide instead would misfire on a repo that added one.
|
|
333
|
+
if (!fs.existsSync(at("document")) && !fs.existsSync(at("codebase"))
|
|
334
|
+
&& !fs.existsSync(at("constitution.md")) && !fs.existsSync(at("scripts"))) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
note("pre-0.5.0 .constitution/ found — migrating to method/ + project/");
|
|
338
|
+
|
|
339
|
+
// 1. The four Reference files go one level deeper. This MUST run before the kit is written, or the
|
|
340
|
+
// kit's own why/ files land while the old copies still sit at method/ root.
|
|
341
|
+
for (const name of OLD_WHY) {
|
|
342
|
+
const from = at("method", `${name}.md`);
|
|
343
|
+
if (fs.existsSync(from)) {
|
|
344
|
+
mv(from, at("method", "why", `${name}.md`));
|
|
345
|
+
note(` moved method/${name}.md → method/why/${name}.md`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// 2. and 3. whole folders
|
|
349
|
+
for (const dir of ["document", "scripts"]) {
|
|
350
|
+
if (fs.existsSync(at(dir)) && !fs.existsSync(at("method", dir))) {
|
|
351
|
+
mv(at(dir), at("method", dir));
|
|
352
|
+
note(` moved ${dir}/ → method/${dir}/`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// 4. the loose generic guides
|
|
356
|
+
for (const name of OLD_ROOT_GUIDES) {
|
|
357
|
+
const from = at(`${name}.md`);
|
|
358
|
+
if (fs.existsSync(from)) {
|
|
359
|
+
mv(from, at("method", `${name}.md`));
|
|
360
|
+
note(` moved ${name}.md → method/${name}.md`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// 5. codebase/ was a product-owned room all along — it becomes flat files in the room that says so
|
|
364
|
+
for (const name of OLD_CODEBASE) {
|
|
365
|
+
const from = at("codebase", `${name}-guide.md`);
|
|
366
|
+
if (fs.existsSync(from)) {
|
|
367
|
+
mv(from, at("project", `codebase-${name}-guide.md`));
|
|
368
|
+
note(` moved codebase/${name}-guide.md → project/codebase-${name}-guide.md`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (fs.existsSync(at("codebase"))) {
|
|
372
|
+
const left = fs.readdirSync(at("codebase"));
|
|
373
|
+
if (!left.length) fs.rmdirSync(at("codebase"));
|
|
374
|
+
else note(` codebase/ still holds ${left.join(", ")} — left in place, move them yourself`);
|
|
375
|
+
}
|
|
376
|
+
// 6. The product's constitution.md moves WHOLE into the room, so its Articles 1, 2, and 5 survive
|
|
377
|
+
// exactly as written. The generic half then arrives fresh at method/constitution.md.
|
|
378
|
+
let split = false;
|
|
379
|
+
if (fs.existsSync(at("constitution.md")) && !fs.existsSync(at("project", "constitution.md"))) {
|
|
380
|
+
mv(at("constitution.md"), at("project", "constitution.md"));
|
|
381
|
+
note(" moved constitution.md → project/constitution.md (your Articles 1, 2, 5)");
|
|
382
|
+
split = true;
|
|
383
|
+
}
|
|
384
|
+
// Anything else loose at the root is a file this product ADDED. It is NOT moved: it may be routed
|
|
385
|
+
// from AGENTS.md by its current path, and guessing a destination would break that silently.
|
|
386
|
+
const stray = fs.existsSync(c)
|
|
387
|
+
? fs.readdirSync(c, { withFileTypes: true })
|
|
388
|
+
.filter((e) => e.isFile() && e.name.endsWith(".md"))
|
|
389
|
+
.map((e) => e.name)
|
|
390
|
+
: [];
|
|
391
|
+
if (stray.length) {
|
|
392
|
+
note(` left at .constitution/ root, yours to place: ${stray.join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
return split;
|
|
395
|
+
}
|
|
396
|
+
|
|
318
397
|
function syncConstitution(target) {
|
|
319
398
|
const kitConst = path.join(KIT, ".constitution");
|
|
320
399
|
const destConst = path.join(target, ".constitution");
|
|
@@ -324,19 +403,14 @@ function syncConstitution(target) {
|
|
|
324
403
|
for (const file of walkFiles(kitConst)) {
|
|
325
404
|
const rel = posixRel(kitConst, file);
|
|
326
405
|
const dest = path.join(destConst, rel);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (rel.startsWith("codebase/") && fs.existsSync(dest) && acceptedCodebase(dest)) {
|
|
333
|
-
skipped += 1;
|
|
334
|
-
note(`keep ${rel} (Accepted codebase guide)`);
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
406
|
+
// ONE rule for everything the product owns, because 0.5.0 put all of it in one folder. Before
|
|
407
|
+
// that this loop had three branches — the mixed constitution.md kept whole, `codebase/` gated on
|
|
408
|
+
// `status: Accepted` (which is what silently destroyed a half-written guide), and the room — and
|
|
409
|
+
// the three disagreed about when a file was the product's. Seeded when absent, never written
|
|
410
|
+
// again: the same rule as the language policy.
|
|
337
411
|
if (rel.startsWith(PROJECT_ROOM) && fs.existsSync(dest)) {
|
|
338
412
|
skipped += 1;
|
|
339
|
-
note(`keep ${rel} (
|
|
413
|
+
note(`keep ${rel} (yours — the project room)`);
|
|
340
414
|
continue;
|
|
341
415
|
}
|
|
342
416
|
copyFile(file, dest);
|
|
@@ -394,10 +468,10 @@ function pruneRetiredSkills(dests) {
|
|
|
394
468
|
|
|
395
469
|
// `promote` scrubs a product's initiative slug out of bmad-prd.toml before publishing, which is right.
|
|
396
470
|
// Writing the scrubbed PLACEHOLDER back into a product repo is not: the first real install replaced a
|
|
397
|
-
// live `run_folder_pattern = "
|
|
471
|
+
// live `run_folder_pattern = "some-real-slug"` with `FILL-initiative-slug`, and nothing said so. A value
|
|
398
472
|
// the product already chose is not the installer's to overwrite — same rule as the custom room and the
|
|
399
473
|
// language policy.
|
|
400
|
-
const PLACEHOLDER_SLUG = "
|
|
474
|
+
const PLACEHOLDER_SLUG = "FILL-initiative-slug";
|
|
401
475
|
const RUN_FOLDER_LINE = /^(\s*run_folder_pattern\s*=\s*)(".*?"|'.*?')/m;
|
|
402
476
|
|
|
403
477
|
// The slug appears MORE THAN ONCE — bmad-prd.toml carries it in `run_folder_pattern` and again inside a
|
|
@@ -503,17 +577,17 @@ function setProductIdentity(target, { name, client }) {
|
|
|
503
577
|
note(`product.name = ${name}`);
|
|
504
578
|
}
|
|
505
579
|
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
580
|
+
// The document language belongs to the PRODUCT, so update MUST NOT overwrite it. It is written only
|
|
581
|
+
// when absent — same as the custom room, and for the same reason: a setting somebody already chose
|
|
582
|
+
// is not the installer's to change behind their back.
|
|
509
583
|
function setLanguagePolicy(target, { docLanguage, docFilenameLanguage, chosen }) {
|
|
510
584
|
const file = path.join(target, ".control", "registry", "index.yaml");
|
|
511
585
|
if (!fs.existsSync(file)) return;
|
|
512
586
|
const text = fs.readFileSync(file, "utf8");
|
|
513
587
|
const existing = readLanguagePolicy(text);
|
|
514
|
-
// `chosen`
|
|
515
|
-
//
|
|
516
|
-
//
|
|
588
|
+
// `chosen` means somebody actually answered — in the TUI, or through an explicit flag. Only then
|
|
589
|
+
// does the answer take effect. Without it the incoming value is just a default, and a default
|
|
590
|
+
// MUST NOT overwrite a choice somebody already made.
|
|
517
591
|
if (!chosen && existing.docLanguage && existing.docFilenameLanguage) {
|
|
518
592
|
note(`kept policy.doc_language = ${existing.docLanguage}, ` +
|
|
519
593
|
`doc_filename_language = ${existing.docFilenameLanguage}`);
|
|
@@ -530,8 +604,8 @@ function setLanguagePolicy(target, { docLanguage, docFilenameLanguage, chosen })
|
|
|
530
604
|
`doc_filename_language = ${after.docFilenameLanguage}`);
|
|
531
605
|
}
|
|
532
606
|
|
|
533
|
-
//
|
|
534
|
-
// "updated"
|
|
607
|
+
// Read BEFORE writeStamp overwrites it. Without this there is no version transition to print, and
|
|
608
|
+
// an "updated" with no from-to tells the reader nothing they can use.
|
|
535
609
|
function readStampVersion(target) {
|
|
536
610
|
const file = path.join(target, ".control", "wdi-method.yaml");
|
|
537
611
|
if (!fs.existsSync(file)) return "";
|
|
@@ -666,8 +740,16 @@ function apply(target, agents,
|
|
|
666
740
|
{ first, product, client, docLanguage, docFilenameLanguage, languageChosen }) {
|
|
667
741
|
requireKit();
|
|
668
742
|
const was = readStampVersion(target);
|
|
743
|
+
// MUST run before the kit is written: it moves the product's files out of the way of paths the kit
|
|
744
|
+
// is about to occupy. Running it after would leave two copies of most guides.
|
|
745
|
+
const splitConstitution = migrateToTwoFolders(target);
|
|
669
746
|
const { written, skipped } = syncConstitution(target);
|
|
670
747
|
note(`constitution wrote ${written}, kept ${skipped}`);
|
|
748
|
+
if (splitConstitution) {
|
|
749
|
+
note(" your constitution.md still carries Articles 3, 4, 6, 7 — now also in method/constitution.md.");
|
|
750
|
+
note(" Delete them from project/constitution.md; only 1, 2, 5 are yours. Not automated: your");
|
|
751
|
+
note(" copy may have been edited, and no script can tell an edit from the original.");
|
|
752
|
+
}
|
|
671
753
|
const skills = syncSkills(target, agents);
|
|
672
754
|
note(`skills ${skills.files} files`);
|
|
673
755
|
const tomls = syncTomls(target);
|
|
@@ -742,22 +824,32 @@ function promote(live) {
|
|
|
742
824
|
if (!fs.existsSync(path.join(live, ".constitution"))) {
|
|
743
825
|
die(`${live} has no .constitution/ — is this a method-carrying repo?`);
|
|
744
826
|
}
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
const
|
|
827
|
+
// EVERY file in the room is authored in the package and MUST survive the rmSync below — the room's
|
|
828
|
+
// README, the generic Articles 1-2-5, and the three empty codebase templates. Read here, not
|
|
829
|
+
// after: the first version of this preserved only README.md and read it AFTER the kit was deleted,
|
|
830
|
+
// so it was always null and the file vanished on every promote. Two tests cover it now.
|
|
831
|
+
const roomKit = path.join(KIT, ".constitution", PROJECT_ROOM);
|
|
832
|
+
const roomKept = fs.existsSync(roomKit)
|
|
833
|
+
? Object.fromEntries(walkFiles(roomKit).map((f) => [posixRel(roomKit, f), fs.readFileSync(f, "utf8")]))
|
|
834
|
+
: {};
|
|
750
835
|
|
|
751
836
|
fs.rmSync(KIT, { recursive: true, force: true });
|
|
752
837
|
fs.mkdirSync(KIT, { recursive: true });
|
|
753
838
|
|
|
839
|
+
// ONE skip, because 0.5.0 put everything the product owns in one folder. It covers the codebase
|
|
840
|
+
// guides too, which used to need a rule of their own: promoting a filled-in stack guide would leak
|
|
841
|
+
// one product's conventions — possibly written in its own `doc_language` — into a public package.
|
|
754
842
|
const nConst = copyTree(path.join(live, ".constitution"), path.join(KIT, ".constitution"),
|
|
755
843
|
(rel) => rel.startsWith(PROJECT_ROOM));
|
|
756
844
|
note(`constitution ${nConst} files (${PROJECT_ROOM} skipped — it is the product's)`);
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
fs.
|
|
760
|
-
|
|
845
|
+
for (const [rel, text] of Object.entries(roomKept)) {
|
|
846
|
+
const dest = path.join(roomKit, rel);
|
|
847
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
848
|
+
fs.writeFileSync(dest, text, "utf8");
|
|
849
|
+
}
|
|
850
|
+
if (Object.keys(roomKept).length) {
|
|
851
|
+
note(`${PROJECT_ROOM} restored from the package (${Object.keys(roomKept).length} files) — `
|
|
852
|
+
+ "promote never carries the room home");
|
|
761
853
|
}
|
|
762
854
|
|
|
763
855
|
let copiedSkills = 0;
|
|
@@ -785,10 +877,10 @@ function promote(live) {
|
|
|
785
877
|
note(`bmad custom ${tomls} toml`);
|
|
786
878
|
|
|
787
879
|
const replacements = {
|
|
788
|
-
"constitution.md": path.join(KIT, ".constitution", "constitution.md"),
|
|
789
|
-
"portability.md": path.join(KIT, ".constitution", "method", "portability.md"),
|
|
790
|
-
"repo-guide.md": path.join(KIT, ".constitution", "repo-guide.md"),
|
|
791
|
-
"README.md": path.join(KIT, ".constitution", "README.md"),
|
|
880
|
+
"constitution.md": path.join(KIT, ".constitution", "method", "constitution.md"),
|
|
881
|
+
"portability.md": path.join(KIT, ".constitution", "method", "why", "portability.md"),
|
|
882
|
+
"repo-guide.md": path.join(KIT, ".constitution", "method", "repo-guide.md"),
|
|
883
|
+
"README.md": path.join(KIT, ".constitution", "method", "README.md"),
|
|
792
884
|
};
|
|
793
885
|
for (const [name, dest] of Object.entries(replacements)) {
|
|
794
886
|
const src = path.join(OVERLAY, name);
|
|
@@ -847,12 +939,12 @@ async function runWizard(pre) {
|
|
|
847
939
|
|
|
848
940
|
const facts = [
|
|
849
941
|
hasBmad
|
|
850
|
-
? `BMad Method:
|
|
851
|
-
: "BMad Method:
|
|
852
|
-
hasWdi ? "WDI Method:
|
|
853
|
-
nonempty ? "Folder
|
|
942
|
+
? `BMad Method: installed${readBmadVersion(target) ? ` (${readBmadVersion(target)})` : ""}`
|
|
943
|
+
: "BMad Method: not installed",
|
|
944
|
+
hasWdi ? "WDI Method: already present — the installer will offer an update" : "WDI Method: not present",
|
|
945
|
+
nonempty ? "Folder is not empty (normal for a product repo already under way)" : "Folder is empty",
|
|
854
946
|
].join("\n");
|
|
855
|
-
p.note(facts, "
|
|
947
|
+
p.note(facts, "Detected");
|
|
856
948
|
|
|
857
949
|
if (!hasBmad && !pre.skipBmad) {
|
|
858
950
|
p.note(bmadMissingMessage(), "BMad first");
|
|
@@ -909,11 +1001,11 @@ async function runWizard(pre) {
|
|
|
909
1001
|
}),
|
|
910
1002
|
).trim();
|
|
911
1003
|
|
|
912
|
-
//
|
|
913
|
-
//
|
|
1004
|
+
// Two questions, and only two. Method terminology, document code prefixes, machine-facing
|
|
1005
|
+
// markers, and code identifiers are always English — MUST NOT be asked about.
|
|
914
1006
|
const policy = readIndexPolicy(target);
|
|
915
|
-
//
|
|
916
|
-
// "
|
|
1007
|
+
// Free text, not a list. Write whatever a model understands — "English", "Bahasa Indonesia",
|
|
1008
|
+
// "id". The only value refused is empty.
|
|
917
1009
|
const askLanguage = async (message, current) =>
|
|
918
1010
|
(cancelIf(
|
|
919
1011
|
await p.text({
|
|
@@ -1017,7 +1109,7 @@ async function main() {
|
|
|
1017
1109
|
return;
|
|
1018
1110
|
}
|
|
1019
1111
|
if (args.cmd === "wizard" && !args.yes) {
|
|
1020
|
-
die("
|
|
1112
|
+
die("not a TTY. Use `install --yes` / `update --yes`, or run this in a terminal.");
|
|
1021
1113
|
}
|
|
1022
1114
|
if (args.cmd === "wizard") args.cmd = wdiPresent(requireTarget(args.dir)) ? "update" : "install";
|
|
1023
1115
|
runNonInteractive(args);
|