wdi-method 0.6.18 → 0.6.24

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 (34) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/LICENSE +21 -21
  3. package/NOTICE +28 -0
  4. package/README.id.md +190 -0
  5. package/README.ja.md +188 -0
  6. package/README.md +190 -530
  7. package/README.zh.md +188 -0
  8. package/bin/wdi-method.js +2199 -2112
  9. package/kit/.constitution/method/README.md +1 -0
  10. package/kit/.constitution/method/branch-guide.md +87 -0
  11. package/kit/.constitution/method/ci-guide.md +169 -0
  12. package/kit/.constitution/method/constitution.md +1 -0
  13. package/kit/.constitution/method/scripts/lifecycle.py +416 -0
  14. package/kit/.constitution/method/scripts/validate.py +126 -9
  15. package/kit/skills/wdi-autopilot/SKILL.md +461 -384
  16. package/kit/skills/wdi-build/SKILL.md +404 -393
  17. package/kit/skills/wdi-daily-autopilot/SKILL.md +138 -0
  18. package/kit/skills/wdi-daily-what-to-build/SKILL.md +155 -0
  19. package/kit/skills/wdi-daily-what-to-test/SKILL.md +127 -0
  20. package/kit/skills/wdi-explain-to-me/SKILL.md +1 -1
  21. package/kit/skills/wdi-help/SKILL.md +21 -7
  22. package/kit/skills/wdi-init/SKILL.md +16 -0
  23. package/kit/skills/wdi-prune-or-archive/SKILL.md +76 -0
  24. package/kit/skills/wdi-review/SKILL.md +4 -1
  25. package/kit-overlay/AGENTS.md +37 -4
  26. package/kit-overlay/README.md +1 -0
  27. package/kit-overlay/constitution.md +1 -0
  28. package/lib/identity.mjs +246 -117
  29. package/package.json +8 -4
  30. package/scaffold/.control/custom-dispatch.yaml.example +65 -0
  31. package/scaffold/.control/registry/index.yaml +9 -0
  32. package/scaffold/.control/test-targets/desktop.md +15 -0
  33. package/scaffold/.control/test-targets/mobile.md +6 -0
  34. package/scaffold/.control/test-targets/web.md +6 -0
@@ -27,7 +27,7 @@ npx wdi-method update --yes
27
27
  ```
28
28
 
29
29
  BMad: https://github.com/bmad-code-org/BMAD-METHOD
30
- WDI Method: https://github.com/wiradigitalid/wdi-method
30
+ WDI Method: https://github.com/wiradeltaid/wdi-method
31
31
 
32
32
  A method file MUST NOT be invented here. If a rule is wrong, fix it in the WDI
33
33
  Method package, then update.
@@ -66,6 +66,28 @@ under `.constitution/`. They are agent instructions, and they travel to every re
66
66
  **A corpus written before these settings existed MUST NOT be migrated for them.** The readers accept more
67
67
  than one language, so existing documents keep working and only new writing follows the setting.
68
68
 
69
+ ## Branches
70
+
71
+ **Two branch settings live in `.control/registry/index.yaml` under `policy:`, both defaulting to `main`:**
72
+
73
+ | Setting | Governs | Default |
74
+ |---|---|---|
75
+ | `primary_branch` | Production / release trunk | `main` |
76
+ | `development_branch` | Active development branch for specs, worktrees, and PR targets | `main` |
77
+
78
+ - **Immunity:** Agents MUST NOT delete, overwrite, force-push, or rename `primary_branch` or `development_branch`.
79
+ - **Branch protection:** Agents MUST NOT commit application code directly to `primary_branch` or `development_branch`. Code changes MUST arrive via isolated task branches/worktrees and pull requests. In single-branch mode, direct commits to the shared branch are permitted strictly for Phase 1 & 2 planning documents in `.scratch/` and `.control/registry/specs.yaml`.
80
+ - **Target branch:** All worktrees and PRs MUST target `development_branch`. Agents MUST verify the branch exists locally or on remote (`refs/heads/<branch>` or `refs/remotes/origin/<branch>`) and MUST NOT guess or silently fall back to `main`.
81
+ - `.constitution/method/branch-guide.md` binds branch rules.
82
+
83
+ ## Delivery flow
84
+
85
+ Applies to all feature specification, ticket creation, and code delivery in this repo:
86
+
87
+ 1. Turning notes, a task, or test results into a spec and tickets MUST go through `wdi-build`'s engines (`to-spec` / `to-tickets`, `mattpocock/skills`) — agents MUST NOT invent a bespoke spec or ticket format.
88
+ 2. Standing exception to `wdi-build`'s isolated worktree precondition: the spec authoring step (opening the spec, writing `SPEC.md` and ticket files in `.scratch/`, with no application code changes) happens directly on the active development branch (`policy.development_branch`, default `main`). The worktree requirement binds ticket implementation onward and `wdi-autopilot`.
89
+ 3. During or after spec and ticket authoring, an agent MUST NOT auto-continue into implementing code, committing, pushing, launching `wdi-autopilot`, merging a PR, cleaning up a worktree, or deploying without explicit confirmation from the maintainer. When explicitly commanded to build a ticket (`wdi-build`), committing and pushing to the task branch are permitted, but auto-merging PRs or auto-deploying remain strictly prohibited.
90
+
69
91
  ## The thing in your hand → its folder
70
92
 
71
93
  Read this instead of reasoning about what `.what/` and `.how/` mean.
@@ -157,6 +179,11 @@ verifies the result, and lands the memlog.
157
179
  - `CONTEXT.md`, `CONTEXT-MAP.md`, and `docs/adr/` MUST NOT be created. `docs/agents/domain.md` is the
158
180
  engines' config and says so too: the vocabulary is `.control/product-glossary.md`, domain knowledge is
159
181
  `.what/`, design is `.how/`, and a decision is a `DEC-` — never an ADR. Article 3 owns the rule.
182
+ - `.control/registry/specs.yaml` MUST NOT be read in full (1000+ lines) into context to check project
183
+ status or open work — invoke skill `wdi-help` or read the generated status projection in
184
+ `.control/generated/` (`status.yaml`) instead.
185
+ - `.scratch/` MUST NOT be searched with broad or recursive wildcard patterns (`*` or `**`) to discover
186
+ specs — inspect only the candidate spec's folder using `spec_folder:` from `status.yaml` or `specs.yaml`.
160
187
 
161
188
  ## Routing — load a guide when the task matches
162
189
 
@@ -171,7 +198,7 @@ verifies the result, and lands the memlog.
171
198
  | Unsure about a domain term | `.control/product-glossary.md` |
172
199
  | Looking for a non-technical fact — a domain, an account, a legal entity, a locked date | `.control/project-non-technical-log.md` |
173
200
  | Naming anything — a code identifier, a file, a database column | `.constitution/method/language-guide.md` |
174
- | Asking "which gate now, what next" | `.constitution/method/document/delivery-flow-guide.md` · skill `wdi-help` |
201
+ | Asking "which gate now, what next", project status, or open/pending specs | skill `wdi-help` (MUST invoke `wdi-help` or read generated `status.yaml`) |
175
202
  | Having to decide something, and wanting the reading done first | skill `wdi-explain-to-me` — it briefs, and changes nothing |
176
203
  | Wanting every `FR` delivered without being asked in between | skill `wdi-autopilot` — preflight, one mandate, then the loop |
177
204
  | Setting or changing `mode` or `risk_accepted` | `.constitution/method/document/delivery-flow-guide.md` · skill `wdi-init` |
@@ -184,6 +211,8 @@ verifies the result, and lands the memlog.
184
211
  | Writing the spine, an `AD-N`, C4, or one of the three inventories | `.constitution/method/document/architecture-guide.md` |
185
212
  | Opening, accepting, or applying a `DEC-` | `.constitution/method/document/decision-guide.md` |
186
213
  | Writing or reading a structure map | `.constitution/method/structure-guide.md` |
214
+ | Writing or changing a CI workflow, or deciding whether a push may start a cloud run | `.constitution/method/ci-guide.md` |
215
+ | Branching rules, protected branches, or PR target policy | `.constitution/method/branch-guide.md` |
187
216
  | Looking for where code lives, or placing new code | `.control/structure-codebase.md` |
188
217
  | Looking for where a document lives | `.control/structure-document.md` |
189
218
  | Writing or reviewing code | `.constitution/project/codebase-stack-guide.md` · `.constitution/project/codebase-conventions-guide.md` · `.constitution/project/codebase-brownfield-guide.md` |
@@ -214,9 +243,13 @@ MUST anything in `.constitution/method/why/`; `status: Reference` forbids it. A
214
243
 
215
244
  - A skill MUST NOT be invoked automatically. Name the one that fits and wait for the owner's
216
245
  go-ahead — this holds even when the skill's own description says it must be used. Reading a
217
- skill as reference is fine. The one exception is `wdi-autopilot` under a `DEC-` of `type: mandate`
246
+ skill as reference is fine. The exceptions are: (1) `wdi-autopilot` under a `DEC-` of `type: mandate`
218
247
  at `status: accepted` that has not expired: the mandate **is** the go-ahead, for every skill it
219
- needs, until it lapses.
248
+ needs, until it lapses; (2) when the owner explicitly invokes an autonomous daily tier skill
249
+ (`/wdi-daily-*`), the owner's invocation authorizes the bounded orchestration steps specified in that skill;
250
+ (3) read-only status, open specs, and routing inquiries (`wdi-help`): the model MUST invoke
251
+ `wdi-help` (or read the generated status projection in `.control/generated/`) automatically to
252
+ inspect current gate progress, open work, or determine the next skill without modifying project state.
220
253
  - `.work/` is not production code. It MUST NOT be imported by the application, and MUST be
221
254
  excluded when searching for code.
222
255
  <!-- END:wdi-method -->
@@ -43,6 +43,7 @@ Never a rule. When it disagrees with a guide, the guide wins and the disagreemen
43
43
  | [`language-guide.md`](language-guide.md) | Naming anything — a code identifier, a code file, a document file |
44
44
  | [`method-glossary.md`](method-glossary.md) | Unsure what a method term means — layer, wave, Product Component, ID code |
45
45
  | [`structure-guide.md`](structure-guide.md) | Writing or checking the two structure maps in `.control/` |
46
+ | [`ci-guide.md`](ci-guide.md) | Writing or changing a CI workflow; when a push may start a cloud run, and what MUST NOT |
46
47
 
47
48
  ## `document/` — document rules
48
49
 
@@ -26,6 +26,7 @@ The repo layout is governed by `corpus-guide.md` and mapped by
26
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 |
27
27
  | `_bmad-output/` | Run workspace; MUST be in git, not curated |
28
28
  | `.scratch/` | One directory per effort: a spec's `SPEC.md` and its ticket files, and ad hoc work that has no `FR` yet. MUST be in git — the corpus cites into it by path |
29
+ | `.archive/` | Archived records — closed specs and historical provenance moved from `.scratch/`. MUST be in git |
29
30
  | `.work/` | Scratch; MUST be in git, emptied when a task closes |
30
31
  | *(application roots)* | Application code — named and mapped in `.control/structure-codebase.md` |
31
32
 
package/lib/identity.mjs CHANGED
@@ -1,117 +1,246 @@
1
- // index.yaml editing. Every regex here is anchored on `\n`, and a Windows checkout hands us `\r\n` —
2
- // so every function normalises before matching and restores the file's own ending before writing.
3
- //
4
- // Skipping that is not a cosmetic bug. It silently returns "" for a name that IS set, which makes
5
- // `identityIsPlaceholder` true, which makes `setProductIdentity` return early: the installer reports
6
- // nothing and writes nothing, on every CRLF repo. Found 2026-08-18 on the first real install into a
7
- // Windows product repo, where `policy.doc_language` printed empty while the file plainly said `id`.
8
-
9
- const PRODUCT_BLOCK = /(?:^|\n)product:\n(?: .*\n)*/;
10
- const POLICY_BLOCK = /(?:^|\n)policy:\n(?: .*\n)*/;
11
-
12
- // FREE TEXT, not an enum. The consumer is a model, and a model does not need a list: "English",
13
- // "Bahasa Indonesia", "id", "Indonesia" all read the same to it. Fencing this into two values would
14
- // force the owner to translate their intent into the installer's vocabulary first, and nothing is
15
- // bought with that.
16
- export const DEFAULT_DOC_LANGUAGE = "English";
17
-
18
- /** LF view of the text, plus how to put the original endings back. */
19
- function lf(text) {
20
- const crlf = text.includes("\r\n");
21
- return { body: crlf ? text.replaceAll("\r\n", "\n") : text, crlf };
22
- }
23
-
24
- function restore(body, crlf) {
25
- return crlf ? body.replaceAll("\n", "\r\n") : body;
26
- }
27
-
28
- export function yamlQuote(value) {
29
- return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
30
- }
31
-
32
- export function readProductIdentity(text) {
33
- const { body } = lf(text);
34
- const name = body.match(/product:\n(?: .*\n)*? name:\s*"([^"]*)"/);
35
- const client = body.match(/product:\n(?: .*\n)*? client:\s*"([^"]*)"/);
36
- return {
37
- name: name ? name[1] : "",
38
- client: client ? client[1] : "",
39
- };
40
- }
41
-
42
- export function writeProductIdentity(text, { name, client }) {
43
- const { body, crlf } = lf(text);
44
- const block =
45
- `product:\n name: ${yamlQuote(name)}\n client: ${yamlQuote(client ?? "")}\n`;
46
- if (/^product:/m.test(body)) {
47
- return restore(
48
- body.replace(PRODUCT_BLOCK, (m) => (m.startsWith("\n") ? `\n${block}` : block)), crlf);
49
- }
50
- return restore(`${block}\n${body.replace(/^/, "")}`, crlf);
51
- }
52
-
53
- export function identityIsPlaceholder(name) {
54
- return !name || name === "{product}";
55
- }
56
-
57
- // ---------------------------------------------------------------------- language policy
58
- //
59
- // Two settings, and only two. Everything else about language is NOT a choice: method terminology,
60
- // document code prefixes, machine-facing markers, and code identifiers are always English —
61
- // `language-guide.md` owns that, and a caller MUST NOT ask about them.
62
- //
63
- // doc_language the PROSE of working documents in .what/ .how/ .control/
64
- // doc_filename_language the SLUG part of a document filename
65
- //
66
- // A corpus written before these existed is NOT migrated for them: the readers in validate.py accept
67
- // more than one language, so an existing document keeps working and only new writing follows the value.
68
-
69
- function readPolicyValue(body, key) {
70
- const quoted = body.match(new RegExp(`policy:\\n(?: .*\\n)*? ${key}:[ \\t]*"([^"]*)"`));
71
- if (quoted) return quoted[1].trim();
72
- // Bare scalars are accepted because early repos wrote `doc_language: id` unquoted. Stops at a
73
- // comment so a trailing `# note` never becomes part of the value.
74
- const bare = body.match(new RegExp(`policy:\\n(?: .*\\n)*? ${key}:[ \\t]*([^\\n#]*)`));
75
- return bare ? bare[1].trim() : "";
76
- }
77
-
78
- export function readLanguagePolicy(text) {
79
- const { body } = lf(text);
80
- return {
81
- docLanguage: readPolicyValue(body, "doc_language"),
82
- docFilenameLanguage: readPolicyValue(body, "doc_filename_language"),
83
- };
84
- }
85
-
86
- export function writeLanguagePolicy(text, { docLanguage, docFilenameLanguage }) {
87
- const { body, crlf } = lf(text);
88
- // Always quoted: free text MAY carry a space or a colon, and a bare YAML scalar breaks on both.
89
- const block =
90
- `policy:\n doc_language: ${yamlQuote(docLanguage)}\n` +
91
- ` doc_filename_language: ${yamlQuote(docFilenameLanguage)}\n`;
92
- if (/^policy:/m.test(body)) {
93
- return restore(
94
- body.replace(POLICY_BLOCK, (m) => (m.startsWith("\n") ? `\n${block}` : block)), crlf);
95
- }
96
- // Sits right after `product:` when that block exists — the two answer the same kind of question,
97
- // so a reader who finds one finds the other.
98
- if (/^product:/m.test(body)) {
99
- return restore(body.replace(/^product:\n(?: .*\n)*/m, (m) => `${m}\n${block}`), crlf);
100
- }
101
- return restore(`${block}\n${body.replace(/^/, "")}`, crlf);
102
- }
103
-
104
- // A first install has nowhere to read a product name from, so the folder is the best guess available —
105
- // and a guess the owner can accept with Enter beats a field they must type. `worship-presenter-web`
106
- // becomes `Worship Presenter Web`; camelCase splits too. It is a SUGGESTION: G1 confirms the real name.
107
- export function humaniseFolderName(name) {
108
- return String(name || "")
109
- .replace(/[._-]+/g, " ")
110
- .replace(/([a-z\d])([A-Z])/g, "$1 $2")
111
- .replace(/\s+/g, " ")
112
- .trim()
113
- .split(" ")
114
- .filter(Boolean)
115
- .map((w) => (w === w.toUpperCase() && w.length <= 4 ? w : w[0].toUpperCase() + w.slice(1)))
116
- .join(" ");
117
- }
1
+ // index.yaml editing. Every regex here is anchored on `\n`, and a Windows checkout hands us `\r\n` —
2
+ // so every function normalises before matching and restores the file's own ending before writing.
3
+ //
4
+ // Skipping that is not a cosmetic bug. It silently returns "" for a name that IS set, which makes
5
+ // `identityIsPlaceholder` true, which makes `setProductIdentity` return early: the installer reports
6
+ // nothing and writes nothing, on every CRLF repo. Found 2026-08-18 on the first real install into a
7
+ // Windows product repo, where `policy.doc_language` printed empty while the file plainly said `id`.
8
+
9
+ // FREE TEXT, not an enum. The consumer is a model, and a model does not need a list: "English",
10
+ // "Bahasa Indonesia", "id", "Indonesia" all read the same to it. Fencing this into two values would
11
+ // force the owner to translate their intent into the installer's vocabulary first, and nothing is
12
+ // bought with that.
13
+ export const DEFAULT_DOC_LANGUAGE = "English";
14
+
15
+ /** LF view of the text, plus how to put the original endings back. */
16
+ function lf(text) {
17
+ const crlf = text.includes("\r\n");
18
+ return { body: crlf ? text.replaceAll("\r\n", "\n") : text, crlf };
19
+ }
20
+
21
+ function restore(body, crlf) {
22
+ return crlf ? body.replaceAll("\n", "\r\n") : body;
23
+ }
24
+
25
+ export function yamlQuote(value) {
26
+ return `"${String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
27
+ }
28
+
29
+ /**
30
+ * Generic block-level key upsert that preserves comments, indentations, and existing sibling keys.
31
+ */
32
+ export function upsertBlockKeys(body, blockName, pairs) {
33
+ const entries = Array.isArray(pairs) ? pairs : Object.entries(pairs);
34
+ const validEntries = entries.filter(([_, v]) => v !== undefined);
35
+ if (validEntries.length === 0) {
36
+ return body;
37
+ }
38
+
39
+ const lines = body.split("\n");
40
+ // Header must start at column 0 to prevent matching nested keys
41
+ const headerRegex = new RegExp(`^${blockName}:[ \\t]*(?:#.*)?$`);
42
+ const headerIndex = lines.findIndex((l) => headerRegex.test(l));
43
+
44
+ if (headerIndex === -1) {
45
+ const blockContent = validEntries.map(([k, v]) => ` ${k}: ${v}`).join("\n");
46
+ const newBlockLines = [`${blockName}:`, ...validEntries.map(([k, v]) => ` ${k}: ${v}`)];
47
+
48
+ // If adding policy and product exists, place policy right after product block
49
+ if (blockName === "policy") {
50
+ const productHeaderRegex = /^product:[ \t]*(?:#.*)?$/;
51
+ const productHeaderIndex = lines.findIndex((l) => productHeaderRegex.test(l));
52
+ if (productHeaderIndex !== -1) {
53
+ let nextIndex = lines.length;
54
+ for (let i = productHeaderIndex + 1; i < lines.length; i++) {
55
+ if (/^[a-zA-Z0-9_-]+:|^---/.test(lines[i])) {
56
+ nextIndex = i;
57
+ break;
58
+ }
59
+ }
60
+ let lastIndented = productHeaderIndex;
61
+ for (let i = nextIndex - 1; i > productHeaderIndex; i--) {
62
+ if (/^\s/.test(lines[i])) {
63
+ lastIndented = i;
64
+ break;
65
+ }
66
+ }
67
+ if (lines[lastIndented + 1] === "") {
68
+ const insertLines = nextIndex < lines.length ? [...newBlockLines, ""] : newBlockLines;
69
+ lines.splice(lastIndented + 2, 0, ...insertLines);
70
+ } else {
71
+ const insertLines = nextIndex < lines.length ? ["", ...newBlockLines, ""] : ["", ...newBlockLines];
72
+ lines.splice(lastIndented + 1, 0, ...insertLines);
73
+ }
74
+ return lines.join("\n");
75
+ }
76
+ }
77
+
78
+ const cleanBody = body.replace(/^\uFEFF/, "");
79
+ return cleanBody ? `${blockName}:\n${blockContent}\n\n${cleanBody}` : `${blockName}:\n${blockContent}\n`;
80
+ }
81
+
82
+ // End of block is reached at the next top-level mapping key or document separator
83
+ let nextBlockIndex = lines.length;
84
+ for (let i = headerIndex + 1; i < lines.length; i++) {
85
+ if (/^[a-zA-Z0-9_-]+:|^---/.test(lines[i])) {
86
+ nextBlockIndex = i;
87
+ break;
88
+ }
89
+ }
90
+
91
+ let lastIndentedIndex = headerIndex;
92
+ for (let i = nextBlockIndex - 1; i > headerIndex; i--) {
93
+ if (/^\s/.test(lines[i])) {
94
+ lastIndentedIndex = i;
95
+ break;
96
+ }
97
+ }
98
+
99
+ const toAppend = [];
100
+ for (const [key, value] of validEntries) {
101
+ // Only match direct children at 2 spaces indentation
102
+ const keyRegex = new RegExp(`^ ${key}:[ \\t]*`);
103
+ let foundIndex = -1;
104
+ for (let i = headerIndex + 1; i < nextBlockIndex; i++) {
105
+ if (keyRegex.test(lines[i])) {
106
+ foundIndex = i;
107
+ break;
108
+ }
109
+ }
110
+
111
+ if (foundIndex !== -1) {
112
+ // Preserve any trailing inline comment
113
+ const commentMatch = lines[foundIndex].match(/(?:[ \t]+#.*)$/);
114
+ const inlineComment = commentMatch ? commentMatch[0] : "";
115
+ lines[foundIndex] = ` ${key}: ${value}${inlineComment}`;
116
+ } else {
117
+ toAppend.push(` ${key}: ${value}`);
118
+ }
119
+ }
120
+
121
+ if (toAppend.length > 0) {
122
+ lines.splice(lastIndentedIndex + 1, 0, ...toAppend);
123
+ }
124
+
125
+ return lines.join("\n");
126
+ }
127
+
128
+ function readBlockValue(body, blockName, key) {
129
+ const lines = body.split("\n");
130
+ const headerRegex = new RegExp(`^${blockName}:[ \\t]*(?:#.*)?$`);
131
+ const headerIndex = lines.findIndex((l) => headerRegex.test(l));
132
+ if (headerIndex === -1) return "";
133
+
134
+ let nextBlockIndex = lines.length;
135
+ for (let i = headerIndex + 1; i < lines.length; i++) {
136
+ if (/^[a-zA-Z0-9_-]+:|^---/.test(lines[i])) {
137
+ nextBlockIndex = i;
138
+ break;
139
+ }
140
+ }
141
+
142
+ const doubleQuotedRegex = new RegExp(`^ ${key}:[ \\t]*"((?:[^"\\\\]|\\\\.)*)"`);
143
+ const singleQuotedRegex = new RegExp(`^ ${key}:[ \\t]*'((?:[^'\\\\]|\\\\.)*)'`);
144
+ const bareRegex = new RegExp(`^ ${key}:[ \\t]*([^\\n#]*)`);
145
+
146
+ for (let i = headerIndex + 1; i < nextBlockIndex; i++) {
147
+ const dQuoted = lines[i].match(doubleQuotedRegex);
148
+ if (dQuoted) {
149
+ return dQuoted[1].replaceAll('\\"', '"').replaceAll('\\\\', '\\').trim();
150
+ }
151
+ const sQuoted = lines[i].match(singleQuotedRegex);
152
+ if (sQuoted) {
153
+ return sQuoted[1].replaceAll("''", "'").trim();
154
+ }
155
+ const bare = lines[i].match(bareRegex);
156
+ if (bare) return bare[1].trim();
157
+ }
158
+ return "";
159
+ }
160
+
161
+ export function readProductIdentity(text) {
162
+ const { body } = lf(text);
163
+ return {
164
+ name: readBlockValue(body, "product", "name"),
165
+ client: readBlockValue(body, "product", "client"),
166
+ };
167
+ }
168
+
169
+ export function writeProductIdentity(text, { name, client }) {
170
+ const { body, crlf } = lf(text);
171
+ const pairs = [
172
+ ["name", yamlQuote(name)],
173
+ ["client", yamlQuote(client ?? "")],
174
+ ];
175
+ const nextBody = upsertBlockKeys(body, "product", pairs);
176
+ return restore(nextBody, crlf);
177
+ }
178
+
179
+ export function identityIsPlaceholder(name) {
180
+ return !name || name === "{product}";
181
+ }
182
+
183
+ // ---------------------------------------------------------------------- policy (language & branches)
184
+ //
185
+ // Language settings:
186
+ // doc_language the PROSE of working documents in .what/ .how/ .control/
187
+ // doc_filename_language the SLUG part of a document filename
188
+ //
189
+ // Branch settings:
190
+ // primary_branch trunk / production branch (defaults to "main")
191
+ // development_branch active development branch (defaults to "main")
192
+
193
+ function readPolicyValue(body, key) {
194
+ return readBlockValue(body, "policy", key);
195
+ }
196
+
197
+ export function readLanguagePolicy(text) {
198
+ const { body } = lf(text);
199
+ return {
200
+ docLanguage: readPolicyValue(body, "doc_language"),
201
+ docFilenameLanguage: readPolicyValue(body, "doc_filename_language"),
202
+ };
203
+ }
204
+
205
+ export function writeLanguagePolicy(text, { docLanguage, docFilenameLanguage }) {
206
+ const { body, crlf } = lf(text);
207
+ const pairs = [];
208
+ if (docLanguage !== undefined) pairs.push(["doc_language", yamlQuote(docLanguage)]);
209
+ if (docFilenameLanguage !== undefined) pairs.push(["doc_filename_language", yamlQuote(docFilenameLanguage)]);
210
+ const nextBody = upsertBlockKeys(body, "policy", pairs);
211
+ return restore(nextBody, crlf);
212
+ }
213
+
214
+ export function readBranchPolicy(text) {
215
+ const { body } = lf(text);
216
+ const primary = readPolicyValue(body, "primary_branch");
217
+ const development = readPolicyValue(body, "development_branch");
218
+ return {
219
+ primaryBranch: primary || "main",
220
+ developmentBranch: development || "main",
221
+ };
222
+ }
223
+
224
+ export function writeBranchPolicy(text, { primaryBranch, developmentBranch }) {
225
+ const { body, crlf } = lf(text);
226
+ const pairs = [];
227
+ if (primaryBranch !== undefined) pairs.push(["primary_branch", yamlQuote(primaryBranch)]);
228
+ if (developmentBranch !== undefined) pairs.push(["development_branch", yamlQuote(developmentBranch)]);
229
+ const nextBody = upsertBlockKeys(body, "policy", pairs);
230
+ return restore(nextBody, crlf);
231
+ }
232
+
233
+ // A first install has nowhere to read a product name from, so the folder is the best guess available —
234
+ // and a guess the owner can accept with Enter beats a field they must type. `acme-billing-portal`
235
+ // becomes `Acme Billing Portal`; camelCase splits too. It is a SUGGESTION: G1 confirms the real name.
236
+ export function humaniseFolderName(name) {
237
+ return String(name || "")
238
+ .replace(/[._-]+/g, " ")
239
+ .replace(/([a-z\d])([A-Z])/g, "$1 $2")
240
+ .replace(/\s+/g, " ")
241
+ .trim()
242
+ .split(" ")
243
+ .filter(Boolean)
244
+ .map((w) => (w === w.toUpperCase() && w.length <= 4 ? w : w[0].toUpperCase() + w.slice(1)))
245
+ .join(" ");
246
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdi-method",
3
- "version": "0.6.18",
3
+ "version": "0.6.24",
4
4
  "description": "WDI Method — software delivery method that wraps BMad",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,8 +13,12 @@
13
13
  "kit-overlay/",
14
14
  "scaffold/",
15
15
  "README.md",
16
+ "README.id.md",
17
+ "README.ja.md",
18
+ "README.zh.md",
16
19
  "CHANGELOG.md",
17
20
  "LICENSE",
21
+ "NOTICE",
18
22
  "!**/__pycache__",
19
23
  "!**/*.pyc"
20
24
  ],
@@ -35,10 +39,10 @@
35
39
  "license": "MIT",
36
40
  "repository": {
37
41
  "type": "git",
38
- "url": "git+https://github.com/wiradigitalid/wdi-method.git"
42
+ "url": "git+https://github.com/wiradeltaid/wdi-method.git"
39
43
  },
40
44
  "bugs": {
41
- "url": "https://github.com/wiradigitalid/wdi-method/issues"
45
+ "url": "https://github.com/wiradeltaid/wdi-method/issues"
42
46
  },
43
- "homepage": "https://github.com/wiradigitalid/wdi-method#readme"
47
+ "homepage": "https://github.com/wiradeltaid/wdi-method#readme"
44
48
  }
@@ -0,0 +1,65 @@
1
+ # .control/custom-dispatch.yaml.example
2
+ # Optional machine-local runner and dispatch configuration for autonomous daily tier skills.
3
+ # When seeded to `.control/custom-dispatch.yaml`, it remains local and gitignored.
4
+ schema: 1
5
+
6
+ runners: {}
7
+ # Uncomment and customize runners on this machine:
8
+ #
9
+ # Auto runners (dispatches in-session if model is reachable from active session;
10
+ # otherwise executes shell-out command):
11
+ # sonnet:
12
+ # type: auto
13
+ # model: sonnet
14
+ # command: 'claude --model claude-sonnet-5 --effort high --dangerously-skip-permissions -p'
15
+ #
16
+ # opus:
17
+ # type: auto
18
+ # model: opus
19
+ # command: 'claude --model claude-opus-5 --effort high --dangerously-skip-permissions -p'
20
+ #
21
+ # External CLI runners (always shell-out):
22
+ # NOTE: For roles.reviewer, runner commands MUST enforce read-only execution where supported
23
+ # (e.g. `--trust-tools=fs_read` for kiro-cli, `--mode plan` for cursor-agent).
24
+ #
25
+ # terra:
26
+ # type: shell-out
27
+ # command: 'kiro-cli chat --model gpt-5.6-terra --effort high --trust-tools=fs_read --no-interactive'
28
+ # timeout_s: 600 # Hard wall-clock timeout in seconds for backgrounded dispatch (default: 600s)
29
+ #
30
+ # composer:
31
+ # type: shell-out
32
+ # command: 'cursor-agent --mode plan --model "composer-2.5"'
33
+ # timeout_s: 600
34
+ #
35
+ # sol:
36
+ # type: shell-out
37
+ # command: 'cursor-agent --mode plan --model "gpt-5.6-sol-high"'
38
+ # timeout_s: 600
39
+
40
+ roles:
41
+ # Builder configuration:
42
+ # builder is fixed to 'coordinator' (mandatory). The coordinating session implements code
43
+ # directly in the active run worktree (tight TDD cycles, direct verification). Coding delegation
44
+ # (in-session subagent or external builder runner) is prohibited in the daily routine to eliminate
45
+ # handoff latency and delegation hallucinations.
46
+ # Guardrail: builder: coordinator MUST NOT be paired with reviewer: none unless all touched
47
+ # components have risk_accepted: low and the decision is recorded. A builder cannot be its own sole judge.
48
+ builder: coordinator
49
+
50
+ # Reviewer options:
51
+ # - default: Resolves peer review via caller's active session / global agent rules
52
+ # - <runner-id>: Dispatches peer review to the named runner
53
+ # - none: Disables peer review pass (coordinator self-review only)
54
+ # NOTE: Reviewer runners in daily triage (wdi-daily-what-to-build) operate strictly as advisory /
55
+ # read-only critics. The coordinator alone applies edits to the spec/tickets and writes registry traces.
56
+ reviewer: default
57
+
58
+ # Deep analyst options (for architecture spine, complex debugging, or hard decisions):
59
+ # - default: Uses reviewer runner or coordinator for deep analysis
60
+ # - <runner-id>: Dispatches deep analysis to a dedicated high-reasoning runner
61
+ # - none: Disables separate deep analyst dispatch (handled by reviewer/coordinator)
62
+ deep_analyst: default
63
+
64
+ review_policy:
65
+ peer_review: true # Set to false (or set reviewer: none) to bypass peer review pass
@@ -9,6 +9,15 @@ product:
9
9
  name: "{product}"
10
10
  client: ""
11
11
 
12
+ # POLICY SETTINGS. Language and branching rules for the repository.
13
+ # Branch settings default to `main` if omitted. Set `development_branch` to match
14
+ # `primary_branch` for single-branch workflows.
15
+ policy:
16
+ doc_language: "English"
17
+ doc_filename_language: "English"
18
+ # primary_branch: main
19
+ # development_branch: development # Set equal to primary_branch for single-branch workflow
20
+
12
21
  # GLOBAL DEPTH. The only document-depth knob, and it MUST NOT also govern review intensity —
13
22
  # that is `risk_accepted` per component in components.yaml.
14
23
  # catalog UC list + three inventories + C4; zero extra files per component. G4 is skipped
@@ -0,0 +1,15 @@
1
+ # Desktop Smoke Test Target
2
+
3
+ ## Target Configuration
4
+ # Optional build and process settings for this repository:
5
+ # build_command: <build command from codebase-stack-guide.md>
6
+ # profile: debug | release (default: release for visual/performance, debug for fast logic loops)
7
+ # artifact_path: <path to compiled binary artifact>
8
+ # process_name: <application process name>
9
+
10
+ ## Verification Checklist Template
11
+
12
+ - [ ] Application launches cleanly without crashes or uncaught runtime panics
13
+ - [ ] Primary window displays expected initial view and layout
14
+ - [ ] User input, navigation, and window controls respond as expected
15
+ - [ ] Application logs are free from unexpected warnings or errors
@@ -0,0 +1,6 @@
1
+ # Mobile Smoke Test Checklist Template
2
+
3
+ - [ ] Application installs and launches on target device or emulator
4
+ - [ ] Initial view displays cleanly without crashing
5
+ - [ ] Touch gestures and primary navigation respond as expected
6
+ - [ ] Device runtime log displays no unhandled exceptions
@@ -0,0 +1,6 @@
1
+ # Web Smoke Test Checklist Template
2
+
3
+ - [ ] Application server starts and responds to HTTP requests
4
+ - [ ] Initial route renders without 404 or 500 status codes
5
+ - [ ] Interactive UI controls respond to user interaction
6
+ - [ ] Browser console log shows no uncaught exceptions