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.
Files changed (100) hide show
  1. package/README.md +58 -16
  2. package/bin/wdi-method.js +152 -60
  3. package/kit/.constitution/method/README.md +66 -169
  4. package/kit/.constitution/{constitution.md → method/constitution.md} +10 -56
  5. package/kit/.constitution/{document → method/document}/architecture-guide.md +1 -1
  6. package/kit/.constitution/{document → method/document}/corpus-guide.md +2 -2
  7. package/kit/.constitution/{document → method/document}/delivery-flow-guide.md +338 -338
  8. package/kit/.constitution/{document → method/document}/sdd-guide.md +1 -1
  9. package/kit/.constitution/{document → method/document}/templates/cross-cutting.md +5 -5
  10. package/kit/.constitution/{document → method/document}/templates/design-system.md +67 -67
  11. package/kit/.constitution/{document → method/document}/templates/inventory.md +69 -69
  12. package/kit/.constitution/{document → method/document}/templates/model.md +2 -2
  13. package/kit/.constitution/{document → method/document}/templates/questions.md +10 -9
  14. package/kit/.constitution/{document → method/document}/templates/sdd.md +1 -1
  15. package/kit/.constitution/{document → method/document}/templates/srs.md +2 -2
  16. package/kit/.constitution/{document → method/document}/templates/structure-codebase.md +129 -129
  17. package/kit/.constitution/{document → method/document}/templates/structure-document.md +90 -90
  18. package/kit/.constitution/{language-guide.md → method/language-guide.md} +2 -2
  19. package/kit/.constitution/{repo-guide.md → method/repo-guide.md} +16 -3
  20. package/kit/.constitution/{scripts → method/scripts}/inventory.py +102 -100
  21. package/kit/.constitution/{scripts → method/scripts}/timeline.py +665 -665
  22. package/kit/.constitution/{scripts → method/scripts}/validate.py +1599 -1579
  23. package/kit/.constitution/{structure-guide.md → method/structure-guide.md} +3 -3
  24. package/kit/.constitution/method/why/README.md +169 -0
  25. package/kit/.constitution/method/{artifact-map.md → why/artifact-map.md} +5 -5
  26. package/kit/.constitution/method/{rationale.md → why/rationale.md} +5 -5
  27. package/kit/.constitution/project/constitution.md +63 -0
  28. package/kit/assets/bmad-custom/bmad-advanced-elicitation.toml +9 -9
  29. package/kit/assets/bmad-custom/bmad-architecture.toml +33 -31
  30. package/kit/assets/bmad-custom/bmad-build-auto.toml +5 -5
  31. package/kit/assets/bmad-custom/bmad-build.toml +52 -52
  32. package/kit/assets/bmad-custom/bmad-code-review.toml +13 -12
  33. package/kit/assets/bmad-custom/bmad-correct-course.toml +20 -19
  34. package/kit/assets/bmad-custom/bmad-deep-recon.toml +12 -11
  35. package/kit/assets/bmad-custom/bmad-prd.toml +42 -42
  36. package/kit/assets/bmad-custom/bmad-product-brief.toml +18 -18
  37. package/kit/assets/bmad-custom/bmad-retrospective.toml +4 -4
  38. package/kit/assets/bmad-custom/bmad-spec.toml +16 -15
  39. package/kit/assets/bmad-custom/bmad-ux.toml +19 -19
  40. package/kit/assets/bmad-custom/config.toml +3 -3
  41. package/kit/skills/wdi-blueprint/SKILL.md +3 -3
  42. package/kit/skills/wdi-build/SKILL.md +244 -244
  43. package/kit/skills/wdi-component/SKILL.md +1 -1
  44. package/kit/skills/wdi-help/SKILL.md +98 -98
  45. package/kit/skills/wdi-init/SKILL.md +1 -1
  46. package/kit/skills/wdi-problem/SKILL.md +90 -90
  47. package/kit/skills/wdi-product/SKILL.md +127 -127
  48. package/kit/skills/wdi-reconcile/SKILL.md +2 -2
  49. package/kit/skills/wdi-report/SKILL.md +205 -205
  50. package/kit/skills/wdi-systematic-debugging/SKILL.md +193 -193
  51. package/kit/skills/wdi-ux/SKILL.md +145 -145
  52. package/kit-overlay/AGENTS.md +20 -20
  53. package/kit-overlay/README.md +24 -17
  54. package/kit-overlay/constitution.md +10 -56
  55. package/kit-overlay/portability.md +6 -6
  56. package/kit-overlay/repo-guide.md +112 -97
  57. package/package.json +37 -37
  58. package/scaffold/.control/product-glossary.md +21 -21
  59. package/scaffold/.control/project-non-technical-log.md +23 -23
  60. package/scaffold/.control/questions/answered.md +11 -11
  61. package/scaffold/.control/questions/assumptions.md +15 -15
  62. package/scaffold/.control/questions/blocking.md +21 -21
  63. package/scaffold/.control/questions/external.md +11 -11
  64. package/scaffold/.control/registry/components.yaml +21 -21
  65. package/scaffold/.control/registry/defects.yaml +3 -3
  66. package/scaffold/.control/registry/index.yaml +46 -46
  67. package/scaffold/.control/registry/requirements.yaml +15 -15
  68. package/scaffold/.control/registry/risks.yaml +5 -5
  69. package/scaffold/.control/registry/usecases.yaml +6 -6
  70. package/kit/.constitution/README.md +0 -66
  71. /package/kit/.constitution/{document → method/document}/bmad-guide.md +0 -0
  72. /package/kit/.constitution/{document → method/document}/bmad-skill-register.md +0 -0
  73. /package/kit/.constitution/{document → method/document}/brief-guide.md +0 -0
  74. /package/kit/.constitution/{document → method/document}/decision-guide.md +0 -0
  75. /package/kit/.constitution/{document → method/document}/prd-guide.md +0 -0
  76. /package/kit/.constitution/{document → method/document}/srs-guide.md +0 -0
  77. /package/kit/.constitution/{document → method/document}/templates/addendum.md +0 -0
  78. /package/kit/.constitution/{document → method/document}/templates/architecture-spine.md +0 -0
  79. /package/kit/.constitution/{document → method/document}/templates/brief.md +0 -0
  80. /package/kit/.constitution/{document → method/document}/templates/c4.md +0 -0
  81. /package/kit/.constitution/{document → method/document}/templates/contract.md +0 -0
  82. /package/kit/.constitution/{document → method/document}/templates/decision.md +0 -0
  83. /package/kit/.constitution/{document → method/document}/templates/flow.md +0 -0
  84. /package/kit/.constitution/{document → method/document}/templates/integration.md +0 -0
  85. /package/kit/.constitution/{document → method/document}/templates/lc.md +0 -0
  86. /package/kit/.constitution/{document → method/document}/templates/lifecycle.md +0 -0
  87. /package/kit/.constitution/{document → method/document}/templates/meeting.md +0 -0
  88. /package/kit/.constitution/{document → method/document}/templates/oq.md +0 -0
  89. /package/kit/.constitution/{document → method/document}/templates/prd.md +0 -0
  90. /package/kit/.constitution/{document → method/document}/templates/rtr.md +0 -0
  91. /package/kit/.constitution/{document → method/document}/templates/rules.md +0 -0
  92. /package/kit/.constitution/{document → method/document}/templates/scn.md +0 -0
  93. /package/kit/.constitution/{document → method/document}/templates/uc.md +0 -0
  94. /package/kit/.constitution/{document → method/document}/templates/ux.md +0 -0
  95. /package/kit/.constitution/{document → method/document}/ux-guide.md +0 -0
  96. /package/kit/.constitution/{method-glossary.md → method/method-glossary.md} +0 -0
  97. /package/kit/.constitution/method/{portability.md → why/portability.md} +0 -0
  98. /package/kit/.constitution/{codebase/brownfield-guide.md → project/codebase-brownfield-guide.md} +0 -0
  99. /package/kit/.constitution/{codebase/conventions-guide.md → project/codebase-conventions-guide.md} +0 -0
  100. /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
- A software delivery method for agent-driven work. It **wraps [BMad](https://github.com/bmad-code-org/BMAD-METHOD); it does not replace it.**
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* and *how to build it* well. What it leaves thin is the middle: the
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** and generic. It MUST NOT contain a client name, a product name, or a link
10
- to a private repository. Product identity lives in the consuming repo.
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/` belongs to the method and is **overwritten** on every update. Four rooms are yours:
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
- | Room | Yours because |
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
- | `constitution.md` Articles 1, 2, 5 | Scope, repo checklist, method ownership |
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/` guides, templates and scripts · the fifteen wrappers · `_bmad/custom/*.toml` · the marked block in `AGENTS.md` |
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 | Everything in the table above, plus your initiative slug and your language choice. A setting somebody already chose is not the installer's to change behind their back |
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
- MIT. Requires Node 20+ and [uv](https://docs.astral.sh/uv/) for the Python scripts.
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
+ [![Version](https://img.shields.io/npm/v/wdi-method?color=blue&label=version)](https://www.npmjs.com/package/wdi-method)
264
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 PRODUCT_CONSTITUTION = "constitution.md";
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
- // Kamar custom milik produk. Tiga sifatnya, dan ketiganya harus dipegang bersama:
312
- // install/update menyemai isinya HANYA bila belum adasesudah itu ia tak pernah ditulis lagi
313
- // promote MELEWATINYA seluruhnya, jadi aturan khusus produk tidak mungkin terbit ke repo publik
314
- // agent memuatnya seperti guide lain, jadi ia MENGIKAT
315
- // Konsekuensi yang disengaja: README kamar ini diarang di paket dan tidak pernah pulang lewat promote.
301
+ // The product's custom room. Three properties, and all three MUST hold together:
302
+ // install/update seeds its content ONLY when absentnever 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
- if (rel === PRODUCT_CONSTITUTION && fs.existsSync(dest)) {
328
- skipped += 1;
329
- note(`keep ${rel} (product articles)`);
330
- continue;
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} (product custom room)`);
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 = "toko-tanpa-akun"` with `ISI-slug-inisiatif`, and nothing said so. A value
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 = "ISI-slug-inisiatif";
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
- // Bahasa dokumen milik PRODUK, jadi update MUST NOT menimpanya. Ia ditulis hanya ketika belum ada —
507
- // sama seperti kamar custom, dan dengan alasan yang sama: setelan yang pernah dipilih seseorang bukan
508
- // milik installer untuk diubah di belakangnya.
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` berarti seseorang benar-benar menjawabdi TUI, atau lewat flag eksplisit. Maka
515
- // jawabannya berlaku. Tanpa itu nilai yang masuk hanyalah default, dan default MUST NOT menimpa
516
- // pilihan yang sudah pernah diambil seseorang.
588
+ // `chosen` means somebody actually answeredin 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
- // Dibaca SEBELUM writeStamp menimpanya. Tanpa ini tidak ada transisi versi yang bisa dicetak, dan
534
- // "updated" tanpa dari-ke tidak memberi tahu apa pun yang bisa dipakai.
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
- // README kamar custom dikarang di paket dan MUST bertahan melewati rmSync di bawah. Dibaca di
746
- // sini, bukan sesudahnya versi pertama patch ini membacanya sesudah kit dihapus, sehingga
747
- // nilainya selalu null dan README-nya hilang tiap promote. Tes project-room yang menemukannya.
748
- const roomKit = path.join(KIT, ".constitution", PROJECT_ROOM, "README.md");
749
- const roomKept = fs.existsSync(roomKit) ? fs.readFileSync(roomKit, "utf8") : null;
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
- if (roomKept !== null) {
758
- fs.mkdirSync(path.dirname(roomKit), { recursive: true });
759
- fs.writeFileSync(roomKit, roomKept, "utf8");
760
- note(`${PROJECT_ROOM}README.md restored from the package — promote never carries it home`);
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: terpasang${readBmadVersion(target) ? ` (${readBmadVersion(target)})` : ""}`
851
- : "BMad Method: belum terpasang",
852
- hasWdi ? "WDI Method: sudah ada — installer akan menawarkan update" : "WDI Method: belum ada",
853
- nonempty ? "Folder tidak kosong (repo produk yang sudah jalan itu biasa)" : "Folder masih kosong",
942
+ ? `BMad Method: installed${readBmadVersion(target) ? ` (${readBmadVersion(target)})` : ""}`
943
+ : "BMad Method: not installed",
944
+ hasWdi ? "WDI Method: already presentthe 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, "Deteksi");
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
- // Dua pertanyaan, dan hanya dua. Istilah metodologi, kode di depan nama berkas, penanda
913
- // machine-facing, dan identifier kode selalu English — MUST NOT ditanyakan.
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
- // Teks bebas, bukan daftar. Tulis apa saja yang dimengerti sebuah model — "English",
916
- // "Bahasa Indonesia", "id". Yang ditolak hanya kosong.
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("bukan TTY. Pakai `install --yes` / `update --yes`, atau jalankan di terminal.");
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);