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
package/README.md
CHANGED
|
@@ -228,19 +228,26 @@ It prints the version it replaced, what it wrote, what it kept, and what to do n
|
|
|
228
228
|
|
|
229
229
|
---
|
|
230
230
|
|
|
231
|
-
##
|
|
231
|
+
## Changing the method
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
**This repository is where a method change is authored** — a guide, a template, a skill wrapper, a
|
|
234
|
+
validator. It is proven here before publishing, against a fixture corpus the three registry scripts
|
|
235
|
+
actually run against:
|
|
235
236
|
|
|
236
237
|
```bash
|
|
237
|
-
|
|
238
|
-
npm test
|
|
239
|
-
git commit && git push
|
|
238
|
+
npm test # includes validate.py, timeline.py and inventory.py over tests/fixture/
|
|
240
239
|
```
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
The fixture is small but complete, and kept **green**, so a new finding is a regression rather than
|
|
242
|
+
noise. One test plants a defect in a copy and requires the matching validator to name it — a green
|
|
243
|
+
baseline is worthless if it is green because every check is broken.
|
|
244
|
+
|
|
245
|
+
A consuming repo then takes the change with `npx wdi-method update`, and that is where the judgement
|
|
246
|
+
half gets tested: whether a guide actually helps a person at G3 is only provable in use.
|
|
247
|
+
|
|
248
|
+
`promote` — pulling the method back out of a consumer — is a **rescue tool**, not the workflow. It
|
|
249
|
+
overwrites the whole kit from one copy, so it refuses to run without `--rescue`.
|
|
250
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) records why the direction was reversed and what it cost.
|
|
244
251
|
|
|
245
252
|
**Patch releases are routine; minor and major are the maintainer's call.** This package overwrites files
|
|
246
253
|
in repos that already hold months of work, and the version is the only signal a reader has for how
|
package/bin/wdi-method.js
CHANGED
|
@@ -89,7 +89,7 @@ function usage() {
|
|
|
89
89
|
install [dir] first install (TUI unless --yes)
|
|
90
90
|
update [dir] update (TUI unless --yes)
|
|
91
91
|
verify [dir]
|
|
92
|
-
promote <live-dir>
|
|
92
|
+
promote <live-dir> --rescue pull a method change back out of a consumer (not the normal flow)
|
|
93
93
|
|
|
94
94
|
--yes non-interactive
|
|
95
95
|
--agents a,b claude,cursor,codex,antigravity
|
|
@@ -109,6 +109,7 @@ function parseArgs(argv) {
|
|
|
109
109
|
dir: null,
|
|
110
110
|
agents: null,
|
|
111
111
|
skipBmad: false,
|
|
112
|
+
rescue: false,
|
|
112
113
|
yes: false,
|
|
113
114
|
product: null,
|
|
114
115
|
client: null,
|
|
@@ -136,6 +137,7 @@ function parseArgs(argv) {
|
|
|
136
137
|
while (rest.length) {
|
|
137
138
|
const t = rest.shift();
|
|
138
139
|
if (t === "--skip-bmad-check") args.skipBmad = true;
|
|
140
|
+
else if (t === "--rescue") args.rescue = true;
|
|
139
141
|
else if (t === "--yes" || t === "-y") args.yes = true;
|
|
140
142
|
else if (t === "--agents") {
|
|
141
143
|
const raw = rest.shift();
|
|
@@ -324,6 +326,77 @@ function mv(from, to) {
|
|
|
324
326
|
fs.renameSync(from, to);
|
|
325
327
|
}
|
|
326
328
|
|
|
329
|
+
/** Article numbers that belong to the method half. The product keeps 1, 2, and 5. */
|
|
330
|
+
const METHOD_ARTICLES = [3, 4, 6, 7];
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Cut the method's articles out of a product's constitution.md, and repoint its relative links.
|
|
334
|
+
*
|
|
335
|
+
* Returns {cut, kept, relinked}, or null when the file does not look like a constitution at all —
|
|
336
|
+
* in which case it is left ALONE rather than guessed at.
|
|
337
|
+
*
|
|
338
|
+
* 0.5.0 moved the file whole and printed "delete Articles 3, 4, 6, 7 yourself", on the grounds that
|
|
339
|
+
* no script can tell an edited copy from the original. That reasoning was wrong in the way that
|
|
340
|
+
* matters: the split does not need to know whether a section was edited, only which article numbers
|
|
341
|
+
* are the method's — and the file states them in its own headings. Leaving it whole left every
|
|
342
|
+
* migrated repo carrying those articles in TWO files, one of them frozen and drifting, plus relative
|
|
343
|
+
* links that no longer resolve one level down. It is all in git, so cutting is reversible; not
|
|
344
|
+
* cutting is what nobody notices.
|
|
345
|
+
*/
|
|
346
|
+
function splitProductConstitution(file) {
|
|
347
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
348
|
+
const crlf = raw.includes("\r\n");
|
|
349
|
+
const text = crlf ? raw.replaceAll("\r\n", "\n") : raw;
|
|
350
|
+
const marks = [...text.matchAll(/^## Article (\d+)\b.*$/gm)];
|
|
351
|
+
if (marks.length < 2) return null; // not the shape we know; do not touch it
|
|
352
|
+
|
|
353
|
+
const kept = [];
|
|
354
|
+
const cut = [];
|
|
355
|
+
let out = text.slice(0, marks[0].index);
|
|
356
|
+
for (let i = 0; i < marks.length; i += 1) {
|
|
357
|
+
const n = Number(marks[i][1]);
|
|
358
|
+
const end = i + 1 < marks.length ? marks[i + 1].index : text.length;
|
|
359
|
+
if (METHOD_ARTICLES.includes(n)) cut.push(n);
|
|
360
|
+
else {
|
|
361
|
+
kept.push(n);
|
|
362
|
+
out += text.slice(marks[i].index, end);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!cut.length) return { cut, kept, relinked: 0 };
|
|
366
|
+
|
|
367
|
+
// The file sits one level deeper than it did, and its former siblings moved into method/. A link
|
|
368
|
+
// left as `repo-guide.md` now resolves to .constitution/project/repo-guide.md, which does not exist.
|
|
369
|
+
let relinked = 0;
|
|
370
|
+
const bump = (re, to) => {
|
|
371
|
+
out = out.replace(re, (m, ...rest) => {
|
|
372
|
+
relinked += 1;
|
|
373
|
+
return typeof to === "function" ? to(m, ...rest) : to + m;
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
for (const name of ["repo-guide.md", "structure-guide.md", "language-guide.md",
|
|
377
|
+
"method-glossary.md"]) {
|
|
378
|
+
bump(new RegExp(`(?<![\\w./-])${name.replace(".", "\\.")}`, "g"), "../method/");
|
|
379
|
+
}
|
|
380
|
+
bump(/(?<![\w./-])document\//g, "../method/");
|
|
381
|
+
bump(/(?<![\w./-])codebase\/([a-z]+)-guide\.md/g, (_m, kind) => `codebase-${kind}-guide.md`);
|
|
382
|
+
out = out.replaceAll("../method/../method/", "../method/");
|
|
383
|
+
|
|
384
|
+
const banner = [
|
|
385
|
+
"",
|
|
386
|
+
`> **Articles ${cut.join(", ")} were removed from this file on migration to the two-folder layout.**`,
|
|
387
|
+
"> They are the method's and live in [`../method/constitution.md`](../method/constitution.md), which",
|
|
388
|
+
`> \`update\` replaces. Only Articles ${kept.join(", ")} are yours. The removed text is in git.`,
|
|
389
|
+
"",
|
|
390
|
+
].join("\n");
|
|
391
|
+
const firstArticle = out.search(/^## Article /m);
|
|
392
|
+
out = firstArticle === -1
|
|
393
|
+
? out + banner
|
|
394
|
+
: out.slice(0, firstArticle) + banner.trimStart() + "\n" + out.slice(firstArticle);
|
|
395
|
+
|
|
396
|
+
fs.writeFileSync(file, crlf ? out.replaceAll("\n", "\r\n") : out, "utf8");
|
|
397
|
+
return { cut, kept, relinked };
|
|
398
|
+
}
|
|
399
|
+
|
|
327
400
|
function migrateToTwoFolders(target) {
|
|
328
401
|
const c = path.join(target, ".constitution");
|
|
329
402
|
if (!fs.existsSync(c)) return false; // a first install has nothing to migrate
|
|
@@ -375,11 +448,18 @@ function migrateToTwoFolders(target) {
|
|
|
375
448
|
}
|
|
376
449
|
// 6. The product's constitution.md moves WHOLE into the room, so its Articles 1, 2, and 5 survive
|
|
377
450
|
// exactly as written. The generic half then arrives fresh at method/constitution.md.
|
|
378
|
-
let split =
|
|
451
|
+
let split = null;
|
|
379
452
|
if (fs.existsSync(at("constitution.md")) && !fs.existsSync(at("project", "constitution.md"))) {
|
|
380
453
|
mv(at("constitution.md"), at("project", "constitution.md"));
|
|
381
|
-
note(" moved constitution.md → project/constitution.md
|
|
382
|
-
split =
|
|
454
|
+
note(" moved constitution.md → project/constitution.md");
|
|
455
|
+
split = splitProductConstitution(at("project", "constitution.md"));
|
|
456
|
+
if (split && split.cut.length) {
|
|
457
|
+
note(` kept Articles ${split.kept.join(", ")}, removed ${split.cut.join(", ")} `
|
|
458
|
+
+ "(the method's — they arrive in method/constitution.md)");
|
|
459
|
+
if (split.relinked) note(` repointed ${split.relinked} relative links one level up`);
|
|
460
|
+
} else if (split === null) {
|
|
461
|
+
note(" it does not carry `## Article N` headings, so it was moved but NOT split — yours to check");
|
|
462
|
+
}
|
|
383
463
|
}
|
|
384
464
|
// Anything else loose at the root is a file this product ADDED. It is NOT moved: it may be routed
|
|
385
465
|
// from AGENTS.md by its current path, and guessing a destination would break that silently.
|
|
@@ -390,6 +470,8 @@ function migrateToTwoFolders(target) {
|
|
|
390
470
|
: [];
|
|
391
471
|
if (stray.length) {
|
|
392
472
|
note(` left at .constitution/ root, yours to place: ${stray.join(", ")}`);
|
|
473
|
+
note(" a file you added belongs in project/ — but moving it would break any pointer that");
|
|
474
|
+
note(" names its current path, so the choice is yours. repo-guide.md states the rule.");
|
|
393
475
|
}
|
|
394
476
|
return split;
|
|
395
477
|
}
|
|
@@ -745,10 +827,14 @@ function apply(target, agents,
|
|
|
745
827
|
const splitConstitution = migrateToTwoFolders(target);
|
|
746
828
|
const { written, skipped } = syncConstitution(target);
|
|
747
829
|
note(`constitution wrote ${written}, kept ${skipped}`);
|
|
830
|
+
// A migrated repo also carries derived output stamped against the OLD layout: .control/generated/*
|
|
831
|
+
// still names the pre-0.5.0 script path, and the two structure maps still draw the old tree. The
|
|
832
|
+
// installer MUST NOT write either — one is generated, the other is re-derived by a skill — so it
|
|
833
|
+
// says so instead of leaving them to be found by whoever trusts them next.
|
|
748
834
|
if (splitConstitution) {
|
|
749
|
-
note("
|
|
750
|
-
note("
|
|
751
|
-
note("
|
|
835
|
+
note(" derived output still describes the OLD layout, and neither is mine to write:");
|
|
836
|
+
note(" uv run .constitution/method/scripts/validate.py --generate → .control/generated/");
|
|
837
|
+
note(" then the wdi-init skill, intent `structure` → the two structure maps");
|
|
752
838
|
}
|
|
753
839
|
const skills = syncSkills(target, agents);
|
|
754
840
|
note(`skills ${skills.files} files`);
|
|
@@ -1100,6 +1186,20 @@ async function main() {
|
|
|
1100
1186
|
}
|
|
1101
1187
|
if (args.cmd === "promote") {
|
|
1102
1188
|
if (!args.dir) die("promote needs a path to the working copy");
|
|
1189
|
+
// `promote` used to BE the workflow: author a rule in a product repo, run it, carry it here.
|
|
1190
|
+
// It is now a rescue tool, and the flag is what makes that structural rather than a paragraph
|
|
1191
|
+
// nobody rereads. Running it by habit overwrites the whole kit with one consumer's copy —
|
|
1192
|
+
// silently reverting every change made here since that repo last updated.
|
|
1193
|
+
if (!args.rescue) {
|
|
1194
|
+
die([
|
|
1195
|
+
"promote overwrites the whole kit from a consumer's copy, and this package is now where a",
|
|
1196
|
+
" method change is authored — see CONTRIBUTING.md. If a change really was made in a",
|
|
1197
|
+
" product repo by mistake and needs rescuing, say so:",
|
|
1198
|
+
"",
|
|
1199
|
+
" npx wdi-method promote <dir> --rescue",
|
|
1200
|
+
].join("\n"));
|
|
1201
|
+
}
|
|
1202
|
+
note("--rescue: pulling the method back out of a consumer. Read the diff before committing.");
|
|
1103
1203
|
promote(args.dir);
|
|
1104
1204
|
return;
|
|
1105
1205
|
}
|
|
@@ -2,31 +2,37 @@
|
|
|
2
2
|
status: Reference
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# `.constitution/` — index
|
|
5
|
+
# `.constitution/method/` — index
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
11
17
|
|
|
12
18
|
Every file here carries a `status:` — Article 4 owns the five values. Only `Accepted` binds;
|
|
13
19
|
`Reference` explains and MUST NOT be cited to reject a change. A template carries no status of its
|
|
14
20
|
own, because its frontmatter belongs to the artifact it produces.
|
|
15
21
|
|
|
16
|
-
A file this product
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
19
25
|
|
|
20
|
-
## `
|
|
26
|
+
## `why/` — the explanation, `status: Reference`
|
|
21
27
|
|
|
22
28
|
Never a rule. When it disagrees with a guide, the guide wins and the disagreement is a defect.
|
|
23
29
|
|
|
24
30
|
| File | Opened when |
|
|
25
31
|
|---|---|
|
|
26
|
-
| [`
|
|
27
|
-
| [`
|
|
28
|
-
| [`
|
|
29
|
-
| [`
|
|
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 |
|
|
30
36
|
|
|
31
37
|
## Cross-domain
|
|
32
38
|
|
|
@@ -51,12 +57,13 @@ Never a rule. When it disagrees with a guide, the guide wins and the disagreemen
|
|
|
51
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 |
|
|
52
58
|
| [`templates/`](document/templates/) | Templates, one per kind of document; they MUST be copied, and MUST NOT be reproduced from memory |
|
|
53
59
|
|
|
54
|
-
##
|
|
60
|
+
## Code rules — in the room, not here
|
|
55
61
|
|
|
56
|
-
All three are written by the **
|
|
57
|
-
|
|
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.
|
|
58
65
|
|
|
59
|
-
[`stack-guide.md`](codebase
|
|
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)
|
|
60
67
|
|
|
61
68
|
## `scripts/`
|
|
62
69
|
|