wdi-method 0.5.0 → 0.5.2

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.
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
 
22
22
  import argparse
23
23
  import datetime as dt
24
+ import os
24
25
  import re
25
26
  import subprocess
26
27
  import sys
@@ -482,7 +483,16 @@ FRONTMATTER_KEYS = ("reviewed:", "date:", "sha:", "lenses:", "updated:")
482
483
 
483
484
 
484
485
  def _reviewed_ok(r: Result, rel: str, block: object, need: set[str]) -> None:
485
- if not isinstance(block, dict) or not block.get("sha") or not block.get("date"):
486
+ # str() before the truth test: an unquoted sha of all digits — `0000000`, and roughly one
487
+ # short sha in twenty-seven is all digits — is read by YAML as the INTEGER 0, which is falsy.
488
+ # The old test then reported "carries no reviewed trace" about a file that plainly carries one,
489
+ # which is the worst kind of finding: correct-looking, and wrong.
490
+ if not isinstance(block, dict):
491
+ r.fail("V13", rel, "carries no `reviewed` trace with a date and sha")
492
+ return
493
+ # NOT `block.get("sha") or ""` — for the integer 0 that yields "" and reintroduces the very
494
+ # bug this guards. `.get(key, "")` returns the 0, and str(0) is "0", which is truthy.
495
+ if not str(block.get("sha", "")).strip() or not str(block.get("date", "")).strip():
486
496
  r.fail("V13", rel, "carries no `reviewed` trace with a date and sha")
487
497
  return
488
498
  lenses = {str(x) for x in (block.get("lenses") or [])}
@@ -713,7 +723,12 @@ def v19(c: Corpus, r: Result) -> None:
713
723
 
714
724
  PLATFORM = "_platform"
715
725
  CROSS_CUTTING = ".how/_platform/cross-cutting.md"
716
- PLATFORM_DATA_HEADING = "Milik platform"
726
+ # The section heading V21 looks for. A heading a SCRIPT matches is a machine-facing key, and
727
+ # `language-guide.md` says a key is always English — so the template writes the English one and
728
+ # this is what a new corpus carries. The Indonesian form is kept as a READER-side alias, exactly
729
+ # like `yes|ya`: a corpus written before this MUST NOT be migrated for a regex.
730
+ PLATFORM_DATA_HEADINGS = ("Platform-owned", "Milik platform")
731
+ PLATFORM_DATA_HEADING = PLATFORM_DATA_HEADINGS[0]
717
732
 
718
733
 
719
734
  def v21(c: Corpus, r: Result) -> None:
@@ -797,7 +812,7 @@ def _platform_documented(c: Corpus, r: Result, entities: list[str]) -> None:
797
812
  return
798
813
  path = c.root / CROSS_CUTTING
799
814
  text = path.read_text(encoding="utf-8", errors="replace") if path.exists() else ""
800
- if PLATFORM_DATA_HEADING.lower() not in text.lower():
815
+ if not any(h.lower() in text.lower() for h in PLATFORM_DATA_HEADINGS):
801
816
  r.skip("V21", f"`{CROSS_CUTTING}` has no `{PLATFORM_DATA_HEADING}` section yet — "
802
817
  f"{len(entities)} entities with platform_owns are not documented yet: "
803
818
  + ", ".join(sorted(entities)))
@@ -906,6 +921,40 @@ CITE_RE = re.compile(
906
921
  r"/[A-Za-z0-9_./-]+\.(?:md|yaml|yml|py|go|tsx|ts|js|mjs|sql|html|css|json))`")
907
922
 
908
923
 
924
+ # Directories that MUST be pruned DURING traversal, not filtered afterwards.
925
+ #
926
+ # The old form was `c.root.rglob("*.md")` plus a `rel.startswith(...)` filter, and it had two faults
927
+ # that only showed up on a real machine:
928
+ #
929
+ # The filter ran too late. rglob had already walked in, so a dangling symlink inside
930
+ # node_modules — an npm workspace link left behind by an abandoned git worktree — raised
931
+ # FileNotFoundError and took the whole run down. A validator that CRASHES on somebody's build
932
+ # output reports nothing about the corpus at all.
933
+ #
934
+ # `node_modules/` matched only at the ROOT. `web/node_modules/` sailed straight through, which is
935
+ # where a monorepo actually keeps it.
936
+ PRUNE_DIRS = frozenset({
937
+ ".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build",
938
+ ".pytest_cache", ".mypy_cache", ".ruff_cache", ".next", ".turbo", ".idea", ".vscode",
939
+ "worktrees", # .claude/worktrees/ — another checkout's tree is not this corpus
940
+ })
941
+
942
+
943
+ def _walk_corpus(root: Path, suffixes: tuple[str, ...]) -> list[Path]:
944
+ """Every file under `root` with one of `suffixes`, sorted, pruning PRUNE_DIRS as it goes.
945
+
946
+ Sorted because determinism is this script's contract: two runs over the same tree MUST report the
947
+ same thing in the same order.
948
+ """
949
+ out: list[Path] = []
950
+ for dirpath, dirnames, filenames in os.walk(root, onerror=lambda _e: None):
951
+ dirnames[:] = sorted(d for d in dirnames if d not in PRUNE_DIRS)
952
+ for name in filenames:
953
+ if name.endswith(suffixes):
954
+ out.append(Path(dirpath) / name)
955
+ return sorted(out)
956
+
957
+
909
958
  def v24(c: Corpus, r: Result) -> None:
910
959
  """A path citation inside a document that STATES what currently holds MUST resolve.
911
960
 
@@ -921,9 +970,9 @@ def v24(c: Corpus, r: Result) -> None:
921
970
  there names a file nobody is allowed to fix.
922
971
  """
923
972
  scanned = 0
924
- for path in sorted(c.root.rglob("*.md")) + sorted(c.root.rglob("*.yaml")):
973
+ for path in _walk_corpus(c.root, (".md", ".yaml")):
925
974
  rel = path.relative_to(c.root).as_posix()
926
- if rel.startswith((".git/", "node_modules/", "_bmad-output/", ".claude/skills/bmad-")):
975
+ if rel.startswith(("_bmad-output/", ".claude/skills/bmad-")):
927
976
  continue
928
977
  if rel.startswith(PAST_RECORD) or rel.startswith(FROZEN) or rel.startswith(DERIVED):
929
978
  continue
@@ -22,14 +22,14 @@ them only an *example* does — not a rule.
22
22
 
23
23
  | File | What is this product's | What to do when carrying it |
24
24
  |---|---|---|
25
- | `constitution.md` | Articles 1, 2, and 5 | **Rewrite 2 and 5.** Article 1 cites `index.yaml` `product.name`. Articles 3, 4, 6, 7 travel unchanged. `wdi-method promote` already replaces this file with the kit template |
26
- | `document/architecture-guide.md` | Seed examples of stack and tree shape | Re-point the examples. Every rule around them travels |
27
- | `document/corpus-guide.md` | Worked examples of `_platform` ownership | Re-point the examples. **Keep both kinds**: they teach the trap better than the rule alone |
25
+ | `../constitution.md` | Articles 1, 2, and 5 | **Rewrite 2 and 5.** Article 1 cites `index.yaml` `product.name`. Articles 3, 4, 6, 7 travel unchanged. `wdi-method promote` already replaces this file with the kit template |
26
+ | `../document/architecture-guide.md` | Seed examples of stack and tree shape | Re-point the examples. Every rule around them travels |
27
+ | `../document/corpus-guide.md` | Worked examples of `_platform` ownership | Re-point the examples. **Keep both kinds**: they teach the trap better than the rule alone |
28
28
  | `templates/design-system.md` | The pointer to wherever this project keeps its tokens | Re-point at that project's token file |
29
29
  | `templates/oq.md` | One example of a bad question title | Cosmetic |
30
30
 
31
31
  Everything else — the five gates, the two fields, the fifteen skills, the templates, `validate.py`,
32
- `inventory.py`, `method-glossary.md`, and the three files beside this one — carries without edit.
32
+ `inventory.py`, `../method-glossary.md`, and the three files beside this one — carries without edit.
33
33
 
34
34
  ## What does NOT travel
35
35
 
@@ -37,7 +37,7 @@ Everything else — the five gates, the two fields, the fifteen skills, the temp
37
37
  |---|---|
38
38
  | `.control/` | This product's state. A new project scaffolds its own through `wdi-init` intent `setup`, or receives empty stubs on first `install` |
39
39
  | `.what/` · `.how/` | This product's promises and build |
40
- | `.constitution/codebase/*-guide.md` | Written by the **project**, not the method. They ship as empty `Draft` stubs |
40
+ | `.constitution/project/codebase-*-guide.md` | Written by the **project**, not the method. They ship as empty `Draft` stubs |
41
41
  | `_bmad-output/` | Run workspace |
42
42
  | The `bmad-*` skills themselves | BMad's, installed by BMad. Only `_bmad/custom/*.toml` is ours |
43
43
 
@@ -75,7 +75,7 @@ the steps that a runbook used to carry are now `wdi-init` intent `setup`. The or
75
75
 
76
76
  1. `npx bmad-method install` in the product repo.
77
77
  2. `npx wdi-method install` (optionally `--agents …`).
78
- 3. Set `product.name` in `.control/registry/index.yaml`. Rewrite `constitution.md` Articles 2 and 5.
78
+ 3. Set `product.name` in `.control/registry/index.yaml`. Rewrite `../constitution.md` Articles 2 and 5.
79
79
  4. Merge the method routing into `AGENTS.md` if that file already existed.
80
80
  5. Run `wdi-init` intent `setup`.
81
81
  6. Sort what already existed. A file that is already the artifact one slot asks for goes into that
@@ -1,68 +1,68 @@
1
- ---
2
- status: Accepted
3
- scope: project-room
4
- ---
5
-
6
- # `.constitution/project/` — this product's custom rules
7
-
8
- **This folder belongs to the product, not to the method.** It is seeded once at install, and after
9
- that `wdi-method update` **never** writes over a file in it. `wdi-method promote` **skips it entirely**,
10
- so nothing you write here can reach the public package.
11
-
12
- This README is the one exception: it is authored in the package and `promote` never carries it home.
13
- You MAY edit it, but the edit will not survive the next install elsewhere — so **your rules MUST be
14
- other files.**
15
-
16
- ## What goes here
17
-
18
- Normative rules that hold **only in this product**, and are not code conventions:
19
-
20
- - a review policy a client requires
21
- - a process rule that came out of a contract
22
- - a naming or language policy that differs from the method default
23
- - a prohibition or obligation specific to this domain
24
-
25
- ## What does not
26
-
27
- | The thing | Its home |
28
- |---|---|
29
- | Product or client name | `.control/registry/index.yaml` → `product:` |
30
- | Code conventions, stack, brownfield patterns | `.constitution/codebase/*-guide.md`already protected once `Accepted` |
31
- | Scope, method ownership, repo checklist | `constitution.md` Articles 1, 2, 5 already protected |
32
- | Agent instructions for this product | `AGENTS.md`, **outside** the marked `wdi-method` block |
33
- | BMad overrides for this product | `_bmad/custom/*.user.toml` |
34
- | State, promises, design | `.control/` · `.what/` · `.how/` |
35
-
36
- **A generic rule MUST NOT be moved here.** If it holds in any project, it belongs to the package — fix
37
- it there, then `promote`. Using this room to bypass the package is how a method stops being generic
38
- with nobody deciding it, and **an empty room is a valid state**: filling it so that it gets used is the
39
- very failure this rule prevents.
40
-
41
- ## The shape of a file here
42
-
43
- Frontmatter is required, and `V27` checks it:
44
-
45
- ```yaml
46
- ---
47
- scope: project # REQUIRED, and exactly this value
48
- purpose: "" # REQUIRED, one line: what this rule protects
49
- overrides: null # optional: the kit file it narrows or contradicts
50
- decision: null # REQUIRED when `overrides:` is set — the DEC- that decided it
51
- ---
52
- ```
53
-
54
- - A file here MAY **narrow** or **add to** a generic rule with no `overrides:` at all.
55
- - To **contradict** a generic rule it MUST name that rule in `overrides:` and carry `decision:`.
56
- Without both, this room becomes the place where generic rules are broken with no trace — and that is
57
- what stops a method being trustworthy in the next repo.
58
- - An `overrides:` pointing at a file that does not exist is a finding, not a typo: it means the rule
59
- being contradicted is gone, and the contradiction may no longer have a reason.
60
-
61
- ## Why whole files, and not marked blocks
62
-
63
- `AGENTS.md` uses a marked block because it is **one** file. `.constitution/` has fifty-odd, and marked
64
- blocks inside them would make `update` perform surgery in every file — one broken marker and either
65
- the product's rule is erased, or the generic rule freezes forever.
66
-
67
- Whole files in their own room avoid both, and they keep a product's rules **readable in one place**
68
- instead of scattered inside fifty files that belong to somebody else.
1
+ ---
2
+ status: Accepted
3
+ scope: project-room
4
+ ---
5
+
6
+ # `.constitution/project/` — this product's custom rules
7
+
8
+ **This folder belongs to the product, not to the method.** It is seeded once at install, and after
9
+ that `wdi-method update` **never** writes over a file in it. `wdi-method promote` **skips it entirely**,
10
+ so nothing you write here can reach the public package.
11
+
12
+ This README is the one exception: it is authored in the package and `promote` never carries it home.
13
+ You MAY edit it, but the edit will not survive the next install elsewhere — so **your rules MUST be
14
+ other files.**
15
+
16
+ ## What goes here
17
+
18
+ Normative rules that hold **only in this product**, and are not code conventions:
19
+
20
+ - a review policy a client requires
21
+ - a process rule that came out of a contract
22
+ - a naming or language policy that differs from the method default
23
+ - a prohibition or obligation specific to this domain
24
+
25
+ ## What does not
26
+
27
+ | The thing | Its home |
28
+ |---|---|
29
+ | Product or client name | `.control/registry/index.yaml` → `product:` |
30
+ | Code conventions, stack, brownfield patterns | `codebase-*-guide.md`, here in this room — protected at **any** `status:` |
31
+ | Scope, method ownership, repo checklist | `constitution.md` Articles 1, 2, 5, here in this room |
32
+ | Agent instructions for this product | `AGENTS.md`, **outside** the marked `wdi-method` block |
33
+ | BMad overrides for this product | `_bmad/custom/*.user.toml` |
34
+ | State, promises, design | `.control/` · `.what/` · `.how/` |
35
+
36
+ **A generic rule MUST NOT be moved here.** If it holds in any project, it belongs to the package — fix
37
+ it there, then `promote`. Using this room to bypass the package is how a method stops being generic
38
+ with nobody deciding it, and **an empty room is a valid state**: filling it so that it gets used is the
39
+ very failure this rule prevents.
40
+
41
+ ## The shape of a file here
42
+
43
+ Frontmatter is required, and `V27` checks it:
44
+
45
+ ```yaml
46
+ ---
47
+ scope: project # REQUIRED, and exactly this value
48
+ purpose: "" # REQUIRED, one line: what this rule protects
49
+ overrides: null # optional: the kit file it narrows or contradicts
50
+ decision: null # REQUIRED when `overrides:` is set — the DEC- that decided it
51
+ ---
52
+ ```
53
+
54
+ - A file here MAY **narrow** or **add to** a generic rule with no `overrides:` at all.
55
+ - To **contradict** a generic rule it MUST name that rule in `overrides:` and carry `decision:`.
56
+ Without both, this room becomes the place where generic rules are broken with no trace — and that is
57
+ what stops a method being trustworthy in the next repo.
58
+ - An `overrides:` pointing at a file that does not exist is a finding, not a typo: it means the rule
59
+ being contradicted is gone, and the contradiction may no longer have a reason.
60
+
61
+ ## Why whole files, and not marked blocks
62
+
63
+ `AGENTS.md` uses a marked block because it is **one** file. `.constitution/` has fifty-odd, and marked
64
+ blocks inside them would make `update` perform surgery in every file — one broken marker and either
65
+ the product's rule is erased, or the generic rule freezes forever.
66
+
67
+ Whole files in their own room avoid both, and they keep a product's rules **readable in one place**
68
+ instead of scattered inside fifty files that belong to somebody else.
@@ -44,18 +44,19 @@ What is particular to this repo, and therefore lives here:
44
44
 
45
45
  This is the **consumer** article. Use it in every product repo.
46
46
 
47
- `.constitution/` guides and templates (except this file's Articles 1, 2, and 5,
48
- `codebase/*-guide.md`, and any extra file this repo added), the `wdi-*` skills,
49
- and `_bmad/custom/*.toml` arrive from the public WDI Method package via
50
- `npx wdi-method install` / `update`.
47
+ Everything in `.constitution/method/`, the `wdi-*` skills, and
48
+ `_bmad/custom/*.toml` arrive from the public WDI Method package via
49
+ `npx wdi-method install` / `update`. Everything in `.constitution/project/`
50
+ this file, `codebase-*-guide.md`, and any rule this repo adds — is **ours**: it is
51
+ seeded once and never written again.
51
52
 
52
53
  - A method file MUST NOT be invented or patched here to improve the method. If a
53
54
  rule is wrong, it is fixed in the WDI Method package, then brought here with
54
55
  `update`.
55
- - `wdi-method update` MUST overwrite method files and MUST NOT touch `.what/`,
56
- `.how/`, `.control/` product state, this file's Articles 1–2 and 5,
57
- `codebase/*-guide.md` once `Accepted`, extra constitution files this repo
58
- added, or `_bmad/custom/*.user.toml`.
56
+ - `wdi-method update` MUST overwrite everything in `.constitution/method/` and
57
+ MUST NOT touch `.what/`, `.how/`, `.control/` product state, anything in
58
+ `.constitution/project/` at **any** `status:`, `Draft` included, which is when
59
+ a codebase guide is actually written — or `_bmad/custom/*.user.toml`.
59
60
  - A rule particular to this repo MUST be written out in full in this file or a
60
61
  sibling, and MUST NOT be replaced by a pointer into another repository.
61
62