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.
- package/README.md +15 -8
- package/bin/wdi-method.js +107 -7
- package/kit/.constitution/method/README.md +24 -17
- package/kit/.constitution/method/document/architecture-guide.md +209 -209
- package/kit/.constitution/method/document/corpus-guide.md +3 -3
- package/kit/.constitution/method/document/templates/cross-cutting.md +1 -1
- package/kit/.constitution/method/method-glossary.md +169 -169
- package/kit/.constitution/method/repo-guide.md +120 -110
- package/kit/.constitution/method/scripts/__pycache__/validate.cpython-314.pyc +0 -0
- package/kit/.constitution/method/scripts/validate.py +54 -5
- package/kit/.constitution/method/why/portability.md +6 -6
- package/kit/.constitution/project/README.md +68 -68
- package/kit/.constitution/project/constitution.md +9 -8
- package/kit/skills/wdi-blueprint/SKILL.md +203 -203
- package/kit-overlay/AGENTS.md +215 -212
- package/kit-overlay/repo-guide.md +8 -0
- package/package.json +39 -37
- package/scaffold/.control/questions/blocking.md +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
973
|
+
for path in _walk_corpus(c.root, (".md", ".yaml")):
|
|
925
974
|
rel = path.relative_to(c.root).as_posix()
|
|
926
|
-
if rel.startswith(("
|
|
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
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
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`,
|
|
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
|
|
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
|
|
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 |
|
|
31
|
-
| Scope, method ownership, repo checklist | `constitution.md` Articles 1, 2, 5
|
|
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
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
56
|
-
`.how/`, `.control/` product state,
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|