wdi-method 0.5.13 → 0.6.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 (87) hide show
  1. package/README.md +436 -271
  2. package/bin/wdi-method.js +198 -5
  3. package/kit/.constitution/method/README.md +76 -75
  4. package/kit/.constitution/method/constitution.md +2 -1
  5. package/kit/.constitution/method/document/architecture-guide.md +10 -10
  6. package/kit/.constitution/method/document/bmad-guide.md +50 -16
  7. package/kit/.constitution/method/document/bmad-skill-register.md +74 -71
  8. package/kit/.constitution/method/document/brief-guide.md +42 -11
  9. package/kit/.constitution/method/document/corpus-guide.md +146 -11
  10. package/kit/.constitution/method/document/decision-guide.md +199 -134
  11. package/kit/.constitution/method/document/delivery-flow-guide.md +154 -83
  12. package/kit/.constitution/method/document/prd-guide.md +63 -20
  13. package/kit/.constitution/method/document/sdd-guide.md +5 -4
  14. package/kit/.constitution/method/document/srs-guide.md +150 -147
  15. package/kit/.constitution/method/document/templates/addendum.md +70 -71
  16. package/kit/.constitution/method/document/templates/architecture-spine.md +6 -4
  17. package/kit/.constitution/method/document/templates/brief.md +132 -110
  18. package/kit/.constitution/method/document/templates/c4.md +11 -11
  19. package/kit/.constitution/method/document/templates/contract.md +51 -52
  20. package/kit/.constitution/method/document/templates/cross-cutting.md +1 -2
  21. package/kit/.constitution/method/document/templates/design-system.md +0 -1
  22. package/kit/.constitution/method/document/templates/integration.md +68 -69
  23. package/kit/.constitution/method/document/templates/inventory.md +0 -1
  24. package/kit/.constitution/method/document/templates/lc.md +49 -47
  25. package/kit/.constitution/method/document/templates/lifecycle.md +51 -52
  26. package/kit/.constitution/method/document/templates/model.md +68 -55
  27. package/kit/.constitution/method/document/templates/oq.md +45 -45
  28. package/kit/.constitution/method/document/templates/prd.md +244 -226
  29. package/kit/.constitution/method/document/templates/questions.md +100 -76
  30. package/kit/.constitution/method/document/templates/rules.md +46 -47
  31. package/kit/.constitution/method/document/templates/sdd.md +26 -11
  32. package/kit/.constitution/method/document/templates/srs.md +46 -25
  33. package/kit/.constitution/method/document/templates/uc.md +1 -1
  34. package/kit/.constitution/method/document/templates/ux.md +76 -77
  35. package/kit/.constitution/method/document/ux-guide.md +115 -99
  36. package/kit/.constitution/method/language-guide.md +1 -1
  37. package/kit/.constitution/method/method-glossary.md +29 -16
  38. package/kit/.constitution/method/scripts/timeline.py +663 -665
  39. package/kit/.constitution/method/scripts/validate.py +1143 -362
  40. package/kit/.constitution/method/structure-guide.md +3 -3
  41. package/kit/.constitution/method/why/README.md +184 -169
  42. package/kit/.constitution/method/why/artifact-map.md +15 -13
  43. package/kit/.constitution/method/why/mode-risk-map.md +140 -0
  44. package/kit/.constitution/method/why/portability.md +1 -1
  45. package/kit/.constitution/method/why/rationale.md +12 -5
  46. package/kit/.constitution/project/README.md +2 -2
  47. package/kit/.constitution/project/codebase-brownfield-guide.md +18 -18
  48. package/kit/.constitution/project/codebase-conventions-guide.md +18 -18
  49. package/kit/.constitution/project/codebase-stack-guide.md +18 -18
  50. package/kit/assets/bmad-custom/bmad-architecture.toml +1 -1
  51. package/kit/assets/bmad-custom/bmad-correct-course.toml +5 -5
  52. package/kit/assets/bmad-custom/bmad-prd.toml +9 -3
  53. package/kit/assets/bmad-custom/bmad-product-brief.toml +6 -2
  54. package/kit/skills/wdi-blueprint/SKILL.md +71 -10
  55. package/kit/skills/wdi-build/SKILL.md +233 -122
  56. package/kit/skills/wdi-component/SKILL.md +8 -6
  57. package/kit/skills/wdi-decision/SKILL.md +187 -183
  58. package/kit/skills/wdi-help/SKILL.md +8 -7
  59. package/kit/skills/wdi-init/SKILL.md +20 -6
  60. package/kit/skills/wdi-problem/SKILL.md +31 -13
  61. package/kit/skills/wdi-product/SKILL.md +30 -17
  62. package/kit/skills/wdi-question/SKILL.md +170 -88
  63. package/kit/skills/wdi-reconcile/SKILL.md +33 -10
  64. package/kit/skills/wdi-report/SKILL.md +77 -25
  65. package/kit/skills/wdi-review/SKILL.md +201 -118
  66. package/kit/skills/wdi-systematic-debugging/SKILL.md +7 -7
  67. package/kit/skills/wdi-upgrade/SKILL.md +179 -0
  68. package/kit/skills/wdi-ux/SKILL.md +35 -11
  69. package/kit-overlay/AGENTS.md +2 -2
  70. package/kit-overlay/README.md +76 -75
  71. package/kit-overlay/constitution.md +2 -1
  72. package/kit-overlay/portability.md +1 -1
  73. package/package.json +43 -43
  74. package/scaffold/.control/product-glossary.md +1 -1
  75. package/scaffold/.control/registry/goals.yaml +25 -0
  76. package/scaffold/.control/registry/index.yaml +5 -5
  77. package/scaffold/.control/registry/specs.yaml +5 -0
  78. package/scaffold/.control/structure-codebase.md +19 -19
  79. package/scaffold/.control/structure-document.md +25 -25
  80. package/kit/.constitution/method/document/templates/rtr.md +0 -61
  81. package/kit/assets/bmad-custom/bmad-build-auto.toml +0 -10
  82. package/kit/assets/bmad-custom/bmad-build.toml +0 -52
  83. package/kit/assets/bmad-custom/bmad-code-review.toml +0 -13
  84. package/kit/assets/bmad-custom/bmad-retrospective.toml +0 -9
  85. package/kit/assets/bmad-custom/bmad-spec.toml +0 -16
  86. package/scaffold/.control/registry/requirements.yaml +0 -15
  87. package/scaffold/.control/registry/waves.yaml +0 -5
package/bin/wdi-method.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { spawnSync } from "node:child_process";
5
6
  import { fileURLToPath } from "node:url";
@@ -51,6 +52,7 @@ const WDI_SKILLS = [
51
52
  "wdi-review",
52
53
  "wdi-report",
53
54
  "wdi-systematic-debugging",
55
+ "wdi-upgrade",
54
56
  ];
55
57
 
56
58
  const PRD_SLUG_PLACEHOLDER = "FILL-initiative-slug";
@@ -62,6 +64,14 @@ const GENERIC_FOLDER_PATTERNS = new Set([
62
64
  ]);
63
65
 
64
66
  const BMAD_INSTALL = `npx bmad-method install`;
67
+ // The ticket engines G5 runs. BMad writes the documents; these cut the work. They are a Claude Code
68
+ // plugin installed per USER, not per repo, so the check reads the plugin registry — and the check
69
+ // warns instead of blocking, because G1–G4 run without them and a first install has no G5 yet.
70
+ const ENGINES_REPO = "https://github.com/mattpocock/skills";
71
+ const ENGINES_PLUGIN = "mattpocock-skills";
72
+ const ENGINES_INSTALL = `/plugin install ${ENGINES_PLUGIN}`;
73
+ const ENGINES_INSTALL_ANY = "npx skills@latest add mattpocock/skills";
74
+ const ENGINES_SETUP = "/setup-matt-pocock-skills";
65
75
  const REPO_URL = "https://github.com/wiradigitalid/wdi-method";
66
76
  const HELP_SKILL = "wdi-help";
67
77
  const INIT_SKILL = "wdi-init";
@@ -279,6 +289,22 @@ function requireTarget(dir) {
279
289
  return target;
280
290
  }
281
291
 
292
+ /** `to-spec` · `to-tickets` · `implement` — present as a user-level plugin, or copied into the repo. */
293
+ function enginesPresent(target) {
294
+ for (const dir of [".claude", ".agents", ".agent", ".cursor", ".codex"]) {
295
+ if (fs.existsSync(path.join(target, dir, "skills", "to-tickets", "SKILL.md"))) return true;
296
+ }
297
+ const cfg = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
298
+ const registry = path.join(cfg, "plugins", "installed_plugins.json");
299
+ if (!fs.existsSync(registry)) return false;
300
+ try {
301
+ const plugins = JSON.parse(fs.readFileSync(registry, "utf8")).plugins || {};
302
+ return Object.keys(plugins).some((k) => k.startsWith("mattpocock-skills@"));
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+
282
308
  function bmadMissingMessage() {
283
309
  return [
284
310
  "BMad Method is not installed in this repo. Install it first, then run this installer again.",
@@ -388,6 +414,60 @@ function splitProductConstitution(file) {
388
414
  return { cut, kept, relinked };
389
415
  }
390
416
 
417
+ // `waves.yaml` holds the PRODUCT's plan, not the package's. When the method retired `wave` for
418
+ // `spec` the registry had to follow, and a rename is the only part of that a tool can safely do:
419
+ // the file MOVES, its content is left exactly as written. Rewriting the rows — `W1` to `SPEC-1`,
420
+ // `epics`/`stories` to `tickets` — is the product's own migration, run by `wdi-build` where a human
421
+ // can see it, because a guess there silently rewrites months of real work.
422
+ //
423
+ // Two refusals matter more than the move. It never writes over an existing `specs.yaml`, and it
424
+ // never deletes a `waves.yaml` whose content has nowhere to go: a half-finished hand migration
425
+ // leaves BOTH files present, and which one is real is not something an installer can know.
426
+ function migrateRegistryNames(target) {
427
+ const reg = path.join(target, ".control", "registry");
428
+ const from = path.join(reg, "waves.yaml");
429
+ const to = path.join(reg, "specs.yaml");
430
+ if (!fs.existsSync(from)) return false;
431
+ if (fs.existsSync(to)) {
432
+ note("BOTH .control/registry/waves.yaml and specs.yaml exist — neither was touched");
433
+ note(" the plan is in one of them and I cannot tell which. Merge them yourself, then delete waves.yaml");
434
+ return false;
435
+ }
436
+ mv(from, to);
437
+ note("renamed .control/registry/waves.yaml → specs.yaml (content unchanged)");
438
+ note(" the rows still say `W<N>` and `epics`/`stories`. Re-cut them through the wdi-build skill");
439
+ return true;
440
+ }
441
+
442
+ // The requirement registry split into `goals.yaml` (the product's `BG`, written by `wdi-problem` at
443
+ // G1) plus one `requirements-<slug>.yaml` per PRD (`CAP`, `FR`, `NFR`, `UJ`, written by
444
+ // `wdi-product` at G2). One file, one writer, one gate. What a tool can do here is SEED `goals.yaml`;
445
+ // what it MUST NOT do is move the rows.
446
+ //
447
+ // Splitting the rows needs one fact the registry has never recorded: which PRD an `FR` belongs to.
448
+ // Before the split nothing wrote it down, and deriving it — FR → UC → ticket → spec → `prd:` — only
449
+ // works for FRs that already have tickets. A guess would file a promise under the wrong initiative,
450
+ // which is worse than leaving it where it is. So `requirements.yaml` is left ALONE and still read:
451
+ // `validate.py` unions every requirement file it finds, so a half-split corpus stays green while its
452
+ // owner cuts the rows through the skill that owns each one.
453
+ function seedRequirementSplit(target) {
454
+ const reg = path.join(target, ".control", "registry");
455
+ if (!fs.existsSync(reg)) return false;
456
+ const product = path.join(reg, "goals.yaml");
457
+ if (fs.existsSync(product)) return false;
458
+ const seed = path.join(SCAFFOLD, "registry", "goals.yaml");
459
+ if (!fs.existsSync(seed)) return false;
460
+ copyFile(seed, product);
461
+ note("seeded .control/registry/goals.yaml");
462
+ if (fs.existsSync(path.join(reg, "requirements.yaml"))) {
463
+ note(" requirements.yaml was left exactly as it is, and is still read — nothing broke");
464
+ note(" the wdi-upgrade skill moves `goals:` into goals.yaml and cuts `capabilities:`,");
465
+ note(" `functional:`, `nonfunctional:`, and `journeys:` into requirements-<slug>.yaml per PRD.");
466
+ note(" <slug> is the PRD's folder name under .what/_prd/");
467
+ }
468
+ return true;
469
+ }
470
+
391
471
  function migrateToTwoFolders(target) {
392
472
  const c = path.join(target, ".constitution");
393
473
  if (!fs.existsSync(c)) return false; // a first install has nothing to migrate
@@ -601,6 +681,34 @@ function syncTomls(target) {
601
681
  return { files: n, slugsKept };
602
682
  }
603
683
 
684
+ // The same argument pruneRetiredSkills makes, one folder over — with one difference that changes
685
+ // the rule. `wdi-` is this method's namespace, so "a wdi-* folder not in WDI_SKILLS" is safely ours.
686
+ // `_bmad/custom/` is NOT: a product may put its own override there, and `.user.toml` is the
687
+ // product's half of every override by convention. So removal here is by an EXPLICIT list of files
688
+ // this package once shipped and has now withdrawn — never by "absent from the kit".
689
+ //
690
+ // Why remove them at all: an override for a retired engine is worse than no override. It is still
691
+ // installed and still read, and bmad-retrospective.toml instructs an agent to archive an `RTR-`
692
+ // against a validator, V19, that no longer exists.
693
+ const RETIRED_TOMLS = [
694
+ "bmad-spec.toml", "bmad-build.toml", "bmad-build-auto.toml",
695
+ "bmad-code-review.toml", "bmad-retrospective.toml",
696
+ ];
697
+
698
+ function pruneRetiredTomls(target) {
699
+ const dir = path.join(target, "_bmad", "custom");
700
+ if (!fs.existsSync(dir)) return 0;
701
+ let removed = 0;
702
+ for (const name of RETIRED_TOMLS) {
703
+ const file = path.join(dir, name);
704
+ if (!fs.existsSync(file)) continue;
705
+ fs.rmSync(file);
706
+ note(`removed retired override ${name}`);
707
+ removed += 1;
708
+ }
709
+ return removed;
710
+ }
711
+
604
712
  function seedControlIfMissing(target) {
605
713
  const control = path.join(target, ".control");
606
714
  if (fs.existsSync(control)) {
@@ -688,6 +796,64 @@ function setLanguagePolicy(target, { docLanguage, docFilenameLanguage, chosen })
688
796
  `doc_filename_language = ${after.docFilenameLanguage}`);
689
797
  }
690
798
 
799
+ // After `update`, some of the corpus can still be in the OLD shape — content the installer MUST NOT
800
+ // move, because moving it takes a decision about meaning: which PRD an `FR` belongs to, whether a
801
+ // sentence was an assumption or a constraint. The `wdi-upgrade` skill does that half. This only
802
+ // DETECTS it, cheaply, so the summary can say how much is waiting and where.
803
+ function pendingUpgrades(target) {
804
+ const has = (...p) => fs.existsSync(path.join(target, ...p));
805
+ const read = (...p) => (has(...p) ? fs.readFileSync(path.join(target, ...p), "utf8") : "");
806
+ const anyIn = (dir, glob, re) => {
807
+ const d = path.join(target, dir);
808
+ if (!fs.existsSync(d)) return false;
809
+ return fs.readdirSync(d).some((n) => {
810
+ const f = path.join(d, n, glob);
811
+ return fs.existsSync(f) && re.test(fs.readFileSync(f, "utf8"));
812
+ });
813
+ };
814
+ const items = [];
815
+ if (has(".control", "registry", "requirements.yaml")) items.push("requirements.yaml → goals.yaml + requirements-<slug>.yaml");
816
+ if (/^\s*-\s*id:\s*W\d+|^\s*(epics|stories):/m.test(read(".control", "registry", "specs.yaml"))) items.push("specs.yaml rows still W<n>/epics/stories (wdi-build re-cuts)");
817
+ if (/^## (Executive Summary|Vision|Assumptions|Prerequisites)\s*$/m.test(read(".what", "_product-brief", "brief.md"))) items.push("brief.md in the 14-section shape");
818
+ // Sections by NAME: the numbers moved between kits (Non-Goals was §7 in one, §5 in the next).
819
+ if (anyIn(".what/_prd", "prd.md", /^## (\d+\.\s*)?(Document Purpose|Glossary|Non-Goals|Open Questions|Assumptions Index)\b|\*\*Proof of done:\*\*/m)) items.push("a prd.md in the 12-section shape, or with FR blocks");
820
+ const whatDir = path.join(target, ".what");
821
+ if (fs.existsSync(whatDir)) {
822
+ for (const pc of fs.readdirSync(whatDir)) {
823
+ if (pc.startsWith("_")) continue;
824
+ const srs = read(".what", pc, `SRS-${pc}.md`);
825
+ if (/^\|\s*UC-\d+\s*\|/m.test(srs)) { items.push("an SRS with a UC Catalogue table (now a pointer)"); break; }
826
+ }
827
+ }
828
+ const howDir = path.join(target, ".how");
829
+ if (fs.existsSync(howDir)) {
830
+ for (const pc of fs.readdirSync(howDir)) {
831
+ if (pc.startsWith("_")) continue;
832
+ if (/\|\s*Quoted rule\s*\||Quoted verbatim from/.test(read(".how", pc, `SDD-${pc}.md`))) { items.push("an SDD quoting AD-N text (now ids only)"); break; }
833
+ }
834
+ }
835
+ if (/\|\s*Container\s*\|\s*Product Components living in it\s*\|/.test(read(".how", "_platform", "c4-l2-containers.md"))) items.push("c4-l2 with a PC x container table (now a pointer)");
836
+ if (has(".control", "generated", "brief.md") || has(".control", "generated", "blueprint.md")) items.push("human pages still in .control/generated/ (render clears them)");
837
+ if (has(".what", "_product-brief", "brief.md") && !has(".what-rendered")) items.push("no .what-rendered/ yet (render creates it)");
838
+ // Skipped: what the validator never reads (kit copies, rendered output, dependencies) and what it
839
+ // treats as a record of the PAST — memlog, decisions, reports, _bmad-output. A stale path in a log
840
+ // is history, not a finding, and repointing it would falsify the record.
841
+ const SKIP = new Set([".git", "node_modules", "target", ".constitution", ".claude", ".agents", ".agent",
842
+ ".what-rendered", ".how-rendered", "dist", "build", "memlog", "decisions", "reports", "meetings", "_bmad-output", ".work"]);
843
+ const OLD_PAGE = /\.control\/generated\/(brief|blueprint|prd-[a-z0-9-]+)\.md/;
844
+ const citesOldPage = (dir, depth) => {
845
+ if (depth > 8) return false;
846
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
847
+ if (e.isDirectory()) { if (!SKIP.has(e.name) && citesOldPage(path.join(dir, e.name), depth + 1)) return true; continue; }
848
+ if (e.name === "answered.md") continue;
849
+ if (e.name.endsWith(".md") && OLD_PAGE.test(fs.readFileSync(path.join(dir, e.name), "utf8"))) return true;
850
+ }
851
+ return false;
852
+ };
853
+ if (citesOldPage(target, 0)) items.push("a document cites .control/generated/brief|blueprint|prd-*.md (pages moved to the rendered trees)");
854
+ return items;
855
+ }
856
+
691
857
  // Read BEFORE writeStamp overwrites it. Without this there is no version transition to print, and
692
858
  // an "updated" with no from-to tells the reader nothing they can use.
693
859
  function readStampVersion(target) {
@@ -785,9 +951,10 @@ function printSummary(target, agents, { first, was, written, skipped, skills, to
785
951
  summaryLine("written", `${written} constitution · ${skills.files} skill files · ${tomls.files} bmad overrides`
786
952
  + (opencodeCmds?.written ? ` · ${opencodeCmds.written} opencode commands` : ""));
787
953
  if (kept.length) summaryLine("kept", kept.join(" · "));
788
- if (skills.removed) {
789
- summaryLine("removed", `${skills.removed} retired wrapper${skills.removed === 1 ? "" : "s"}`);
790
- }
954
+ const gone = [];
955
+ if (skills.removed) gone.push(`${skills.removed} retired wrapper${skills.removed === 1 ? "" : "s"}`);
956
+ if (tomls.removed) gone.push(`${tomls.removed} retired override${tomls.removed === 1 ? "" : "s"}`);
957
+ if (gone.length) summaryLine("removed", gone.join(" · "));
791
958
  if (first && policy.docLanguage) {
792
959
  summaryLine("language", `${policy.docLanguage} · filenames ${policy.docFilenameLanguage}`);
793
960
  }
@@ -801,12 +968,27 @@ function printSummary(target, agents, { first, was, written, skipped, skills, to
801
968
  `run the ${INIT_SKILL} skill, intent ${DIM}readers${RESET}, ` +
802
969
  `to write it for this repo's stack`);
803
970
  }
804
- summaryLine("next", `invoke the ${HELP_SKILL} skill and ask what to do`);
971
+ summaryLine("engines", enginesPresent(target)
972
+ ? `to-spec · to-tickets · implement — found (${ENGINES_PLUGIN})`
973
+ : `to-spec · to-tickets · implement — NOT found. G5 (wdi-build) and the Fast Path need them; G1–G4 run without them`);
974
+ if (!enginesPresent(target)) {
975
+ summaryLine("", `${DIM}·${RESET} Claude Code: ${DIM}${ENGINES_INSTALL}${RESET} — other agents: ${DIM}${ENGINES_INSTALL_ANY}${RESET}`);
976
+ summaryLine("", `${DIM}·${RESET} then ${DIM}${ENGINES_SETUP}${RESET} once, to name the tracker · ${ENGINES_REPO}`);
977
+ }
978
+ const pending = first ? [] : pendingUpgrades(target);
979
+ if (pending.length) {
980
+ summaryLine("upgrade", `${pending.length} item${pending.length === 1 ? "" : "s"} still in the OLD shape — ` +
981
+ `run the ${DIM}wdi-upgrade${RESET} skill; it moves content, never invents it`);
982
+ for (const item of pending) summaryLine("", `${DIM}·${RESET} ${item}`);
983
+ }
984
+ summaryLine("next", pending.length
985
+ ? `run the ${DIM}wdi-upgrade${RESET} skill first, then ${HELP_SKILL}`
986
+ : `invoke the ${HELP_SKILL} skill and ask what to do`);
805
987
  summaryLine("", REPO_URL);
806
988
  console.log(`${DIM}${"─".repeat(62)}${RESET}`);
807
989
  }
808
990
 
809
- function printNextSteps({ first, productSet }) {
991
+ function printNextSteps({ first, productSet, upgradePending }) {
810
992
  console.log("");
811
993
  console.log(first ? "After install:" : "After update:");
812
994
  if (first) {
@@ -828,6 +1010,10 @@ function printNextSteps({ first, productSet }) {
828
1010
  console.log(" 1. The <!-- BEGIN:wdi-method --> block in AGENTS.md was replaced. Read the diff.");
829
1011
  console.log(" 2. constitution.md Articles 1-2-5, ## Code, and *.user.toml were not overwritten.");
830
1012
  console.log(" 3. If BMad has new skills, install those first, then run this update again.");
1013
+ if (upgradePending) {
1014
+ console.log(" 4. The summary listed an `upgrade` line: run the wdi-upgrade skill before any other skill.");
1015
+ console.log(" It moves content into the new shape and never invents any; one commit.");
1016
+ }
831
1017
  }
832
1018
  }
833
1019
 
@@ -838,6 +1024,8 @@ function apply(target, agents,
838
1024
  // MUST run before the kit is written: it moves the product's files out of the way of paths the kit
839
1025
  // is about to occupy. Running it after would leave two copies of most guides.
840
1026
  const migrated = migrateToTwoFolders(target);
1027
+ migrateRegistryNames(target);
1028
+ seedRequirementSplit(target);
841
1029
  // The split MUST also be reachable without a migration. 0.5.2 only ran it from inside
842
1030
  // migrateToTwoFolders, which returns early when the old layout is absent — so a repo that took
843
1031
  // 0.5.0 or 0.5.1, whose project/constitution.md was moved WHOLE and never split, could never be
@@ -873,6 +1061,7 @@ function apply(target, agents,
873
1061
  }
874
1062
  }
875
1063
  const tomls = syncTomls(target);
1064
+ tomls.removed = pruneRetiredTomls(target);
876
1065
  note(`bmad custom ${tomls.files} toml → _bmad/custom/`);
877
1066
  if (first) seedControlIfMissing(target);
878
1067
  seedEmptyLayers(target, { first });
@@ -884,6 +1073,7 @@ function apply(target, agents,
884
1073
  printNextSteps({
885
1074
  first,
886
1075
  productSet: Boolean(product) && !identityIsPlaceholder(product),
1076
+ upgradePending: !first && pendingUpgrades(target).length > 0,
887
1077
  });
888
1078
  }
889
1079
 
@@ -1098,6 +1288,9 @@ async function runWizard(pre) {
1098
1288
  ? `BMad Method: installed${readBmadVersion(target) ? ` (${readBmadVersion(target)})` : ""}`
1099
1289
  : "BMad Method: not installed",
1100
1290
  hasWdi ? "WDI Method: already present — the installer will offer an update" : "WDI Method: not present",
1291
+ enginesPresent(target)
1292
+ ? "Ticket engines (mattpocock-skills): installed"
1293
+ : `Ticket engines (mattpocock-skills): not found — needed at G5 only; ${ENGINES_INSTALL} (${ENGINES_REPO})`,
1101
1294
  nonempty ? "Folder is not empty (normal for a product repo already under way)" : "Folder is empty",
1102
1295
  ].join("\n");
1103
1296
  p.note(facts, "Detected");
@@ -1,75 +1,76 @@
1
- ---
2
- status: Reference
3
- ---
4
-
5
- # `.constitution/method/` — index
6
-
7
- `.constitution/` holds **exactly two folders**, and the folder is what says who owns a file:
8
-
9
- | Folder | Owner | `update` | `promote` |
10
- |---|---|---|---|
11
- | `method/` — you are in it | the method | **overwritten** in full | carries it into the package |
12
- | [`../project/`](../project/) | this product | **never touched** — seeded once when absent | never carries it, so your rules cannot be published |
13
-
14
- Load [`constitution.md`](constitution.md) and [`../project/constitution.md`](../project/constitution.md)
15
- before acting: Articles 3, 4, 6, 7 are here, Articles 1, 2, 5 are yours. Guides are loaded **lazily** —
16
- only when the task matches, and every guide states when on its own **Loaded when:** line.
17
-
18
- Every file here carries a `status:` — Article 4 owns the five values. Only `Accepted` binds;
19
- `Reference` explains and MUST NOT be cited to reject a change. A template carries no status of its
20
- own, because its frontmatter belongs to the artifact it produces.
21
-
22
- **A file this product adds MUST go in [`../project/`](../project/), not here** — anything in `method/`
23
- is replaced on the next update, without warning, because that is what `method/` means. This index is
24
- overwritten too.
25
-
26
- ## `why/` — the explanation, `status: Reference`
27
-
28
- Never a rule. When it disagrees with a guide, the guide wins and the disagreement is a defect.
29
-
30
- | File | Opened when |
31
- |---|---|
32
- | [`why/README.md`](why/README.md) | You want the whole shape in five minutes — five gates, two settings, fifteen skills, WDI ↔ BMad |
33
- | [`why/artifact-map.md`](why/artifact-map.md) | "Where does this file go", or "does this document exist at my `mode`" |
34
- | [`why/rationale.md`](why/rationale.md) | Before changing a rule, to know what you would break |
35
- | [`why/portability.md`](why/portability.md) | Which files are the method and which are the product; how promote and install move them |
36
-
37
- ## Cross-domain
38
-
39
- | File | Loaded when |
40
- |---|---|
41
- | [`repo-guide.md`](repo-guide.md) | Adding a file that is neither code nor corpus; the content boundary, `.work/`, cross-repo references |
42
- | [`language-guide.md`](language-guide.md) | Naming anything a code identifier, a code file, a document file |
43
- | [`method-glossary.md`](method-glossary.md) | Unsure what a method term means layer, wave, Product Component, ID code |
44
- | [`structure-guide.md`](structure-guide.md) | Writing or checking the two structure maps in `.control/` |
45
-
46
- ## `document/` — document rules
47
-
48
- | File | Governs |
49
- |---|---|
50
- | [`corpus-guide.md`](document/corpus-guide.md) | Where a file lives. Read before the other guides |
51
- | [`delivery-flow-guide.md`](document/delivery-flow-guide.md) | Five gates, `mode`, `risk_accepted`, the gate checklists, units of work, wave size, Fast Path, the story-closing checklist, change control |
52
- | [`bmad-guide.md`](document/bmad-guide.md) | How BMad skills are used here; classes A–D, the read-write map, memlog |
53
- | [`bmad-skill-register.md`](document/bmad-skill-register.md) | The installed BMad skills with the documents each reads and writes |
54
- | [`brief-guide.md`](document/brief-guide.md) · [`prd-guide.md`](document/prd-guide.md) · [`ux-guide.md`](document/ux-guide.md) | The G1 and G2 artifacts |
55
- | [`srs-guide.md`](document/srs-guide.md) · [`sdd-guide.md`](document/sdd-guide.md) | The G3 and G4 artifacts, per Product Component |
56
- | [`architecture-guide.md`](document/architecture-guide.md) | The architecture spine, `AD-N`, C4, and the three inventories |
57
- | [`decision-guide.md`](document/decision-guide.md) | `DEC-`: the one test for recording at all, shape, global numbering, the `draft accepted → applied` ladder, supersession |
58
- | [`templates/`](document/templates/) | Templates, one per kind of document; they MUST be copied, and MUST NOT be reproduced from memory |
59
-
60
- ## Code rules — in the room, not here
61
-
62
- All three are written by the **product**, so they live in [`../project/`](../project/) and no update
63
- touches them at any `status:`. While `Draft`, their contents MAY be read as guidance but MUST NOT be
64
- used to reject a change.
65
-
66
- [`stack-guide.md`](../project/codebase-stack-guide.md) · [`conventions-guide.md`](../project/codebase-conventions-guide.md) · [`brownfield-guide.md`](../project/codebase-brownfield-guide.md)
67
-
68
- ## `scripts/`
69
-
70
- `validate.py` — registry gates and the `.control/generated/` generator. `timeline.py` — the time
71
- dimension from git history. `inventory.py` — compares the three inventories against the code
72
- and reports the difference; it MUST NOT patch either side into agreement. It reads no code itself:
73
- the patterns live in `../project/inventory-readers.py`, because comparing is generic and reading a
74
- stack is not. That file ships as a skeleton and `wdi-init` intent `readers` writes it for the repo
75
- in front of it. All three run through `uv run`.
1
+ ---
2
+ status: Reference
3
+ ---
4
+
5
+ # `.constitution/method/` — index
6
+
7
+ `.constitution/` holds **exactly two folders**, and the folder is what says who owns a file:
8
+
9
+ | Folder | Owner | `update` | `promote` |
10
+ |---|---|---|---|
11
+ | `method/` — you are in it | the method | **overwritten** in full | carries it into the package |
12
+ | [`../project/`](../project/) | this product | **never touched** — seeded once when absent | never carries it, so your rules cannot be published |
13
+
14
+ Load [`constitution.md`](constitution.md) and [`../project/constitution.md`](../project/constitution.md)
15
+ before acting: Articles 3, 4, 6, 7 are here, Articles 1, 2, 5 are yours. Guides are loaded **lazily** —
16
+ only when the task matches, and every guide states when on its own **Loaded when:** line.
17
+
18
+ Every file here carries a `status:` — Article 4 owns the five values. Only `Accepted` binds;
19
+ `Reference` explains and MUST NOT be cited to reject a change. A template carries no status of its
20
+ own, because its frontmatter belongs to the artifact it produces.
21
+
22
+ **A file this product adds MUST go in [`../project/`](../project/), not here** — anything in `method/`
23
+ is replaced on the next update, without warning, because that is what `method/` means. This index is
24
+ overwritten too.
25
+
26
+ ## `why/` — the explanation, `status: Reference`
27
+
28
+ Never a rule. When it disagrees with a guide, the guide wins and the disagreement is a defect.
29
+
30
+ | File | Opened when |
31
+ |---|---|
32
+ | [`why/README.md`](why/README.md) | You want the whole shape in five minutes — five gates, two settings, sixteen skills, WDI ↔ BMad |
33
+ | [`why/artifact-map.md`](why/artifact-map.md) | "Where does this file go", or "does this document exist at my `mode`" |
34
+ | [`why/mode-risk-map.md`](why/mode-risk-map.md) | A `mode` and a `risk_accepted` are set and you want the two side by side — all twelve cells |
35
+ | [`why/rationale.md`](why/rationale.md) | Before changing a rule, to know what you would break |
36
+ | [`why/portability.md`](why/portability.md) | Which files are the method and which are the product; how promote and install move them |
37
+
38
+ ## Cross-domain
39
+
40
+ | File | Loaded when |
41
+ |---|---|
42
+ | [`repo-guide.md`](repo-guide.md) | Adding a file that is neither code nor corpus; the content boundary, `.work/`, cross-repo references |
43
+ | [`language-guide.md`](language-guide.md) | Naming anything a code identifier, a code file, a document file |
44
+ | [`method-glossary.md`](method-glossary.md) | Unsure what a method term means layer, wave, Product Component, ID code |
45
+ | [`structure-guide.md`](structure-guide.md) | Writing or checking the two structure maps in `.control/` |
46
+
47
+ ## `document/` — document rules
48
+
49
+ | File | Governs |
50
+ |---|---|
51
+ | [`corpus-guide.md`](document/corpus-guide.md) | Where a file lives. Read before the other guides |
52
+ | [`delivery-flow-guide.md`](document/delivery-flow-guide.md) | Five gates, `mode`, `risk_accepted`, the gate checklists, units of work, wave size, Fast Path, the story-closing checklist, change control |
53
+ | [`bmad-guide.md`](document/bmad-guide.md) | How BMad skills are used here; classes A–D, the read-write map, memlog |
54
+ | [`bmad-skill-register.md`](document/bmad-skill-register.md) | The installed BMad skills with the documents each reads and writes |
55
+ | [`brief-guide.md`](document/brief-guide.md) · [`prd-guide.md`](document/prd-guide.md) · [`ux-guide.md`](document/ux-guide.md) | The G1 and G2 artifacts |
56
+ | [`srs-guide.md`](document/srs-guide.md) · [`sdd-guide.md`](document/sdd-guide.md) | The G3 and G4 artifacts, per Product Component |
57
+ | [`architecture-guide.md`](document/architecture-guide.md) | The architecture spine, `AD-N`, C4, and the three inventories |
58
+ | [`decision-guide.md`](document/decision-guide.md) | `DEC-`: the one test for recording at all, shape, global numbering, the `draft accepted applied` ladder, supersession |
59
+ | [`templates/`](document/templates/) | Templates, one per kind of document; they MUST be copied, and MUST NOT be reproduced from memory |
60
+
61
+ ## Code rules — in the room, not here
62
+
63
+ All three are written by the **product**, so they live in [`../project/`](../project/) and no update
64
+ touches them at any `status:`. While `Draft`, their contents MAY be read as guidance but MUST NOT be
65
+ used to reject a change.
66
+
67
+ [`stack-guide.md`](../project/codebase-stack-guide.md) · [`conventions-guide.md`](../project/codebase-conventions-guide.md) · [`brownfield-guide.md`](../project/codebase-brownfield-guide.md)
68
+
69
+ ## `scripts/`
70
+
71
+ `validate.py` — registry gates and the `.control/generated/` generator. `timeline.py` the time
72
+ dimension from git history. `inventory.py` compares the three inventories against the code
73
+ and reports the difference; it MUST NOT patch either side into agreement. It reads no code itself:
74
+ the patterns live in `../project/inventory-readers.py`, because comparing is generic and reading a
75
+ stack is not. That file ships as a skeleton and `wdi-init` intent `readers` writes it for the repo
76
+ in front of it. All three run through `uv run`.
@@ -23,6 +23,7 @@ The repo layout is governed by `corpus-guide.md` and mapped by
23
23
  | `.control/` | Control — what currently holds and what has been decided |
24
24
  | `.what/` | What is promised |
25
25
  | `.how/` | How it is built |
26
+ | `.what-rendered/` · `.how-rendered/` | The two above, assembled for a human to read — one page per gate, at the mirror path. Regenerated by `validate.py`, never edited, never read by a skill |
26
27
  | `_bmad-output/` | Run workspace; committed, not curated |
27
28
  | `.work/` | Scratch; committed, emptied when a task closes |
28
29
  | *(application roots)* | Application code — named and mapped in `.control/structure-codebase.md` |
@@ -49,7 +50,7 @@ so a status there would land in the artifact and mean something else entirely.
49
50
  A missing header anywhere else is a **finding**, not an implicit anything.
50
51
 
51
52
  `Reference` exists so that the *explanation* of a rule can live beside the rule without competing with it —
52
- `method/why/` holds four such files. Where a `Reference` file and an `Accepted` one disagree, the `Accepted`
53
+ `method/why/` holds five such files. Where a `Reference` file and an `Accepted` one disagree, the `Accepted`
53
54
  one wins, and the disagreement MUST be reported as a defect rather than resolved by preferring whichever was
54
55
  opened first. A rule MUST NOT be born in a `Reference` file; when one is noticed there, it is stated as a
55
56
  finding and written in the guide that owns it.
@@ -48,14 +48,14 @@ read in the places where it is right.
48
48
  An `AD-N` with no **Prevents** is a preference. Preferences belong in
49
49
  `.constitution/project/codebase-conventions-guide.md`, where nothing has to justify itself.
50
50
 
51
- ## The spine stops being touched every story
51
+ ## The spine stops being touched every ticket
52
52
 
53
53
  This is the change that ends the tax that was being paid before the information existed.
54
54
 
55
55
  - **The spine holds invariants and nothing else.** It changes only when an `AD-N` is born or reversed, and
56
56
  both are decision events. For a mid-sized product: once at the start, around 6–10 `AD-N`, then almost
57
57
  never.
58
- - **A story MUST NOT touch the spine.** A story that contradicts an `AD-N` **stops** and opens a `DEC-` —
58
+ - **A ticket MUST NOT touch the spine.** A ticket that contradicts an `AD-N` **stops** and opens a `DEC-` —
59
59
  the one case where recording a decision is still mandatory.
60
60
  - **Editing an `AD-N` MUST NOT quietly reverse it.** A reversal is a decision, and it goes through
61
61
  `wdi-decision` first.
@@ -90,7 +90,7 @@ the C4 set describes and the spine forbids. A C4 file MUST NOT be used to justif
90
90
  belongs in an `AD-N`.
91
91
 
92
92
  The C4 set is **living**. It is amended when a container is added or changed — it MUST NOT be regenerated from
93
- scratch, which would drop the annotations three waves of amendment put there.
93
+ scratch, which would drop the annotations three specs of amendment put there.
94
94
 
95
95
  ## What counts as a container, and what does not
96
96
 
@@ -118,7 +118,7 @@ A container is inside the boundary whether or not we wrote it. `built:` records
118
118
  | A heading in `structure-codebase.md` | **Required** | **Never** — no code of ours lives there |
119
119
  | Listed in a PC's `containers:` | Yes | **Never** — see the matrix below |
120
120
 
121
- V25 checks all four. This is what makes the class settled rather than re-argued: a database and a web
121
+ `container-built` checks all four. This is what makes the class settled rather than re-argued: a database and a web
122
122
  server are containers, they carry NFRs, and they still produce no design artifact of ours.
123
123
 
124
124
  **What IS ours about a `built: false` container MUST have a home outside the C4 set** — its configuration
@@ -139,7 +139,7 @@ depends on it for lives in `cross-cutting.md` or an integration contract.
139
139
  A PC and a container cross, so neither list implies the other — and the crossing is what a builder needs
140
140
  first: *which container does this promise live in, and is it more than one?*
141
141
 
142
- - The **SSOT is each PC's `containers:`** in `components.yaml`; C4 L2 renders it, and V25 fails when the
142
+ - The **SSOT is each PC's `containers:`** in `components.yaml`; C4 L2 renders it, and `container-built` fails when the
143
143
  two disagree.
144
144
  - Complete at **G3** for every PC. It is blueprint content, so `mode` does not touch it.
145
145
  - A PC MUST list every `built: true` container it lives in. Listing only the main one is the error the
@@ -152,8 +152,8 @@ first: *which container does this promise live in, and is it more than one?*
152
152
  **L2 MUST be complete** — every container, plus the matrix. **L3 exists once per `built: true` container
153
153
  holding more than one PC**; a one-PC container needs none because the matrix already places it.
154
154
 
155
- **Not one of the three waits for a wave.** Which container a PC lives in cannot be discovered by a wave,
156
- because a wave picks its stories from that answer — a wave forced to invent it answers a G3 question with
155
+ **Not one of the three waits for a spec.** Which container a PC lives in cannot be discovered by a spec,
156
+ because a spec picks its tickets from that answer — a spec forced to invent it answers a G3 question with
157
157
  a fraction of G3's information.
158
158
 
159
159
  ## Cross-cutting
@@ -167,7 +167,7 @@ another. One fact, one home.
167
167
  **The platform MAY own things, and owning one costs a row here.** `_platform` is a legitimate owner in every
168
168
  position that asks which component owns something — a domain entity through `platform_owns`, an inventory row,
169
169
  an `LC`. Whatever it owns MUST be described under `## Platform-owned` in this file: what it is, its kind, why
170
- no component's promise explains it, who touches it, and the shape every toucher obeys. V21 checks that second
170
+ no component's promise explains it, who touches it, and the shape every toucher obeys. `entity-one-writer` checks that second
171
171
  half, because a platform that owns something without documenting it has taken ownership without taking
172
172
  responsibility.
173
173
 
@@ -181,9 +181,9 @@ at every mode.
181
181
 
182
182
  ## Binding order
183
183
 
184
- Spine first, then the SDD, then `bmad-spec`. An `SDD-<pc>.md` written before the spine will be rewritten,
184
+ Spine first, then the SDD, then the spec's contract. An `SDD-<pc>.md` written before the spine will be rewritten,
185
185
  because the constraints it was supposed to inherit did not exist yet. A `SPEC.md` written before the SDD has
186
- nothing to project. `bmad-spec` MUST keep `AD` ids stable so downstream artifacts can cite them.
186
+ nothing to project. `AD` ids MUST stay stable so downstream artifacts can cite them.
187
187
 
188
188
  ## Inheritance downward
189
189