wdi-method 0.6.30 → 0.6.32
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/CHANGELOG.md +129 -0
- package/NOTICE +7 -2
- package/README.md +16 -11
- package/bin/wdi-method.js +396 -87
- package/kit/.constitution/method/document/architecture-guide.md +217 -209
- package/kit/.constitution/method/document/bmad-skill-register.md +107 -104
- package/kit/.constitution/method/document/corpus-guide.md +522 -517
- package/kit/.constitution/method/document/decision-guide.md +236 -216
- package/kit/.constitution/method/document/delivery-flow-guide.md +20 -0
- package/kit/.constitution/method/document/prd-guide.md +245 -245
- package/kit/.constitution/method/document/templates/design-system.md +96 -66
- package/kit/.constitution/method/document/templates/experience.md +62 -0
- package/kit/.constitution/method/document/templates/structure-codebase.md +131 -129
- package/kit/.constitution/method/document/templates/ux.md +78 -76
- package/kit/.constitution/method/document/ux-guide.md +161 -115
- package/kit/.constitution/method/method-glossary.md +3 -0
- package/kit/.constitution/method/scripts/validate.py +3375 -3200
- package/kit/.constitution/method/structure-guide.md +204 -202
- package/kit/.constitution/method/why/README.md +1 -1
- package/kit/.constitution/method/why/artifact-map.md +158 -157
- package/kit/.constitution/method/why/portability.md +19 -2
- package/kit/skills/wdi-autopilot/SKILL.md +32 -19
- package/kit/skills/wdi-blueprint/SKILL.md +271 -264
- package/kit/skills/wdi-build/SKILL.md +28 -19
- package/kit/skills/wdi-component/SKILL.md +179 -174
- package/kit/skills/wdi-daily-autopilot/SKILL.md +24 -13
- package/kit/skills/wdi-daily-what-to-build/SKILL.md +9 -6
- package/kit/skills/wdi-daily-what-to-test/SKILL.md +2 -0
- package/kit/skills/wdi-decision/SKILL.md +206 -203
- package/kit/skills/wdi-explain-to-me/SKILL.md +2 -0
- package/kit/skills/wdi-help/SKILL.md +130 -125
- package/kit/skills/wdi-init/SKILL.md +10 -5
- package/kit/skills/wdi-problem/SKILL.md +114 -108
- package/kit/skills/wdi-product/SKILL.md +167 -162
- package/kit/skills/wdi-prune-or-archive/SKILL.md +2 -0
- package/kit/skills/wdi-reconcile/SKILL.md +170 -169
- package/kit/skills/wdi-upgrade/SKILL.md +234 -215
- package/kit/skills/wdi-ux/SKILL.md +187 -169
- package/kit-overlay/AGENTS.md +15 -2
- package/kit-overlay/portability.md +19 -2
- package/lib/platforms.mjs +420 -248
- package/package.json +1 -1
- package/scaffold/.control/registry/index.yaml +2 -1
package/lib/platforms.mjs
CHANGED
|
@@ -1,248 +1,420 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent / IDE targets for install and update
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
* `
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{ id: "
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{ id: "
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{ id: "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Agent / IDE targets for install and update — one record per host, and the ONE home of what the
|
|
3
|
+
* method knows about each.
|
|
4
|
+
*
|
|
5
|
+
* IDs and `skillDir` align with BMad Method `platform-codes.yaml`, so a repo's
|
|
6
|
+
* `_bmad/_config/manifest.yaml` `ides:` list maps directly. Everything else is read from the host's
|
|
7
|
+
* own documentation and decides behaviour: where the engines must be visible (`reads`), which
|
|
8
|
+
* `npx skills --agent` id puts them there (`skillsCli`), which project rule files carry the method
|
|
9
|
+
* block (`rules`), how a person types a skill (`invoke`), whether the host can hold a skill to
|
|
10
|
+
* manual-only (`manualOnly`), and whether it has a scheduler of its own (`loop`).
|
|
11
|
+
*
|
|
12
|
+
* The installer writes the selected records into `.control/wdi-method.yaml`, and `validate.py` and
|
|
13
|
+
* the skills read them from there — no second list anywhere.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
/** @typedef {"opencode-commands"} PlatformHook */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} Loop
|
|
22
|
+
* @property {"command" | "scheduler"} kind `command`: typed in-session; `scheduler`: the host's own
|
|
23
|
+
* scheduler, started the way `how` says
|
|
24
|
+
* @property {string} how `{interval}` and `{prompt}` are filled in by the skill
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} Platform
|
|
29
|
+
* @property {string} id
|
|
30
|
+
* @property {string} name
|
|
31
|
+
* @property {boolean} preferred
|
|
32
|
+
* @property {string} skillDir where the `wdi-*` skills are written (BMad's target_dir)
|
|
33
|
+
* @property {string[]} reads every project skill directory the host reads, `skillDir` first
|
|
34
|
+
* @property {string} skillsCli `npx skills add … --agent <id>` that lands inside `reads`
|
|
35
|
+
* @property {string[]} rules project rule files beyond `AGENTS.md` that carry the method block
|
|
36
|
+
* @property {string} invoke how a person types a skill; `{skill}` is the name, `natural` = ask by name
|
|
37
|
+
* @property {"frontmatter" | "claude" | "opencode" | null} manualOnly
|
|
38
|
+
* `frontmatter`: the host honours `disable-model-invocation: true`; `claude`: that plus
|
|
39
|
+
* `.claude/settings.json` deny rules; `opencode`: `opencode.json` `permission.skill: ask`;
|
|
40
|
+
* null: no mechanism — the body guard and the rule file are all there is
|
|
41
|
+
* @property {Loop | null} loop
|
|
42
|
+
* @property {PlatformHook[]} [hooks]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** Legacy WDI flags. `antigravity` is NOT one: it is BMad's id for the IDE (`.agent/skills`), and
|
|
46
|
+
* aliasing it to the CLI made the IDE option in the picker install for the wrong host. */
|
|
47
|
+
/** @type {Record<string, string>} */
|
|
48
|
+
export const LEGACY_ALIASES = {
|
|
49
|
+
claude: "claude-code",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Hosts the method no longer supports, and why. `update` names them; it never deletes their folders. */
|
|
53
|
+
export const RETIRED_PLATFORMS = {
|
|
54
|
+
iflow: "iFlow CLI shut down on 2026-04-17, and its skills are marketplace `skill.toml` packages, not SKILL.md",
|
|
55
|
+
firebender: "Firebender's service ends on 2026-10-31",
|
|
56
|
+
roo: "Roo Code's repository was archived on 2026-05-15",
|
|
57
|
+
hermes: "Hermes Agent reads skills from ~/.hermes/skills only — a repo's own skills are not loaded",
|
|
58
|
+
neovate: "Neovate Code documents no project skill directory",
|
|
59
|
+
mux: "Mux runs other agents and reads no skills of its own — select the agent it runs instead",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const cmd = (how) => ({ kind: "command", how });
|
|
63
|
+
const sched = (how) => ({ kind: "scheduler", how });
|
|
64
|
+
|
|
65
|
+
/** @type {Platform[]} */
|
|
66
|
+
export const PLATFORMS = [
|
|
67
|
+
{ id: "adal", name: "AdaL", preferred: false, skillDir: ".adal/skills",
|
|
68
|
+
reads: [".adal/skills"], skillsCli: "adal", rules: [], invoke: "natural", manualOnly: null,
|
|
69
|
+
loop: sched("AdaL Scheduled Prompts (cron) re-running `{prompt}` every {interval}") },
|
|
70
|
+
{ id: "amp", name: "Sourcegraph Amp", preferred: false, skillDir: ".agents/skills",
|
|
71
|
+
reads: [".agents/skills", ".claude/skills"], skillsCli: "amp", rules: [], invoke: "natural",
|
|
72
|
+
manualOnly: null, loop: null },
|
|
73
|
+
{ id: "antigravity", name: "Google Antigravity", preferred: false, skillDir: ".agent/skills",
|
|
74
|
+
reads: [".agent/skills", ".agents/skills"], skillsCli: "antigravity", rules: [".agents/AGENTS.md"],
|
|
75
|
+
invoke: "natural", manualOnly: null, loop: null },
|
|
76
|
+
{ id: "antigravity-cli", name: "Antigravity CLI (AGY)", preferred: false, skillDir: ".agents/skills",
|
|
77
|
+
reads: [".agents/skills"], skillsCli: "antigravity-cli", rules: [".agents/AGENTS.md"],
|
|
78
|
+
invoke: "natural", manualOnly: null, loop: null },
|
|
79
|
+
{ id: "auggie", name: "Auggie", preferred: false, skillDir: ".agents/skills",
|
|
80
|
+
reads: [".agents/skills", ".augment/skills", ".claude/skills"], skillsCli: "augment", rules: [],
|
|
81
|
+
invoke: "/{skill}", manualOnly: null, loop: null },
|
|
82
|
+
{ id: "bob", name: "IBM Bob", preferred: false, skillDir: ".bob/skills",
|
|
83
|
+
reads: [".bob/skills"], skillsCli: "bob", rules: [], invoke: "/{skill}", manualOnly: null, loop: null },
|
|
84
|
+
{ id: "claude-code", name: "Claude Code", preferred: true, skillDir: ".claude/skills",
|
|
85
|
+
reads: [".claude/skills"], skillsCli: "claude-code", rules: ["CLAUDE.md"], invoke: "/{skill}",
|
|
86
|
+
manualOnly: "claude", loop: cmd("/loop {interval} {prompt}") },
|
|
87
|
+
{ id: "cline", name: "Cline", preferred: false, skillDir: ".cline/skills",
|
|
88
|
+
reads: [".cline/skills", ".clinerules/skills", ".claude/skills"], skillsCli: "claude-code", rules: [],
|
|
89
|
+
invoke: "/{skill}", manualOnly: null,
|
|
90
|
+
loop: sched("`cline schedule` — a Cline schedule running `{prompt}` every {interval}") },
|
|
91
|
+
{ id: "codex", name: "Codex", preferred: true, skillDir: ".agents/skills",
|
|
92
|
+
reads: [".agents/skills"], skillsCli: "codex", rules: [], invoke: "${skill}", manualOnly: null,
|
|
93
|
+
loop: null },
|
|
94
|
+
{ id: "codewhale", name: "CodeWhale", preferred: false, skillDir: ".codewhale/skills",
|
|
95
|
+
reads: [".codewhale/skills", ".agents/skills", ".claude/skills", ".opencode/skills", ".cursor/skills"],
|
|
96
|
+
skillsCli: "universal", rules: [], invoke: "/skill {skill}", manualOnly: "frontmatter", loop: null },
|
|
97
|
+
{ id: "codebuddy", name: "CodeBuddy", preferred: false, skillDir: ".codebuddy/skills",
|
|
98
|
+
reads: [".codebuddy/skills"], skillsCli: "codebuddy", rules: ["CODEBUDDY.md"], invoke: "/{skill}",
|
|
99
|
+
manualOnly: "frontmatter", loop: sched("the CronCreate tool — a session task running `{prompt}` every {interval}") },
|
|
100
|
+
{ id: "command-code", name: "Command Code", preferred: false, skillDir: ".agents/skills",
|
|
101
|
+
reads: [".agents/skills", ".commandcode/skills", ".claude/skills"], skillsCli: "command-code", rules: [],
|
|
102
|
+
invoke: "/{skill}", manualOnly: null, loop: cmd("/loop {interval} {prompt}") },
|
|
103
|
+
{ id: "cortex", name: "Snowflake Cortex Code", preferred: false, skillDir: ".cortex/skills",
|
|
104
|
+
reads: [".cortex/skills", ".claude/skills"], skillsCli: "cortex", rules: ["CLAUDE.md"],
|
|
105
|
+
invoke: "${skill}", manualOnly: null, loop: null },
|
|
106
|
+
{ id: "crush", name: "Crush", preferred: false, skillDir: ".agents/skills",
|
|
107
|
+
reads: [".agents/skills", ".crush/skills", ".claude/skills", ".cursor/skills"], skillsCli: "crush",
|
|
108
|
+
rules: ["CRUSH.md"], invoke: "natural", manualOnly: null, loop: null },
|
|
109
|
+
{ id: "cursor", name: "Cursor", preferred: true, skillDir: ".agents/skills",
|
|
110
|
+
reads: [".agents/skills", ".cursor/skills"], skillsCli: "cursor", rules: [".cursorrules"],
|
|
111
|
+
invoke: "/{skill}", manualOnly: null, loop: null },
|
|
112
|
+
{ id: "droid", name: "Factory Droid", preferred: false, skillDir: ".factory/skills",
|
|
113
|
+
reads: [".factory/skills", ".agents/skills", ".agent/skills"], skillsCli: "droid", rules: [],
|
|
114
|
+
invoke: "/{skill}", manualOnly: "frontmatter", loop: null },
|
|
115
|
+
{ id: "gemini", name: "Gemini CLI", preferred: false, skillDir: ".agents/skills",
|
|
116
|
+
reads: [".agents/skills", ".gemini/skills"], skillsCli: "gemini-cli", rules: ["GEMINI.md"],
|
|
117
|
+
invoke: "natural", manualOnly: null, loop: null },
|
|
118
|
+
{ id: "github-copilot", name: "GitHub Copilot", preferred: true, skillDir: ".agents/skills",
|
|
119
|
+
reads: [".agents/skills", ".github/skills", ".claude/skills"], skillsCli: "github-copilot", rules: [],
|
|
120
|
+
invoke: "/{skill}", manualOnly: null,
|
|
121
|
+
loop: sched("Copilot CLI scheduled prompts — `{prompt}` every {interval}") },
|
|
122
|
+
{ id: "goose", name: "Block Goose", preferred: false, skillDir: ".agents/skills",
|
|
123
|
+
reads: [".agents/skills", ".goose/skills", ".claude/skills"], skillsCli: "goose", rules: [".goosehints"],
|
|
124
|
+
invoke: "natural", manualOnly: null,
|
|
125
|
+
loop: sched("a Goose schedule (`goose schedule`) running `{prompt}` every {interval}") },
|
|
126
|
+
{ id: "junie", name: "Junie", preferred: false, skillDir: ".junie/skills",
|
|
127
|
+
reads: [".junie/skills", ".agents/skills"], skillsCli: "junie", rules: [".junie/guidelines.md"],
|
|
128
|
+
invoke: "/{skill}", manualOnly: null, loop: null },
|
|
129
|
+
{ id: "kilo", name: "Kilo Code", preferred: false, skillDir: ".agents/skills",
|
|
130
|
+
reads: [".agents/skills", ".kilo/skills", ".claude/skills"], skillsCli: "kilo", rules: [],
|
|
131
|
+
invoke: "/{skill}", manualOnly: null, loop: null },
|
|
132
|
+
{ id: "kimi-code", name: "Kimi Code", preferred: false, skillDir: ".agents/skills",
|
|
133
|
+
reads: [".agents/skills", ".kimi-code/skills"], skillsCli: "kimi-code-cli", rules: [],
|
|
134
|
+
invoke: "/skill:{skill}", manualOnly: "frontmatter", loop: null },
|
|
135
|
+
{ id: "kiro", name: "Kiro", preferred: false, skillDir: ".kiro/skills",
|
|
136
|
+
reads: [".kiro/skills"], skillsCli: "kiro-cli", rules: [], invoke: "/{skill}", manualOnly: null,
|
|
137
|
+
loop: null },
|
|
138
|
+
{ id: "kode", name: "Kode", preferred: false, skillDir: ".kode/skills",
|
|
139
|
+
reads: [".kode/skills", ".claude/skills"], skillsCli: "kode", rules: [], invoke: "/{skill}",
|
|
140
|
+
manualOnly: null, loop: null },
|
|
141
|
+
{ id: "mistral-vibe", name: "Mistral Vibe", preferred: false, skillDir: ".vibe/skills",
|
|
142
|
+
reads: [".vibe/skills", ".agents/skills"], skillsCli: "mistral-vibe", rules: [], invoke: "/{skill}",
|
|
143
|
+
manualOnly: null, loop: null },
|
|
144
|
+
{ id: "ona", name: "Ona", preferred: false, skillDir: ".ona/skills",
|
|
145
|
+
reads: [".ona/skills", ".agents/skills", ".claude/skills"], skillsCli: "ona", rules: [],
|
|
146
|
+
invoke: "natural", manualOnly: null, loop: null },
|
|
147
|
+
{ id: "openclaw", name: "OpenClaw", preferred: false, skillDir: ".agents/skills",
|
|
148
|
+
reads: [".agents/skills", "skills"], skillsCli: "openclaw", rules: [], invoke: "/{skill}",
|
|
149
|
+
manualOnly: "frontmatter", loop: null },
|
|
150
|
+
{ id: "opencode", name: "OpenCode", preferred: false, skillDir: ".agents/skills",
|
|
151
|
+
reads: [".agents/skills", ".opencode/skills", ".claude/skills"], skillsCli: "opencode", rules: [],
|
|
152
|
+
invoke: "/{skill}", manualOnly: "opencode", loop: null, hooks: ["opencode-commands"] },
|
|
153
|
+
{ id: "openhands", name: "OpenHands", preferred: false, skillDir: ".agents/skills",
|
|
154
|
+
reads: [".agents/skills", ".openhands/skills"], skillsCli: "openhands", rules: [], invoke: "natural",
|
|
155
|
+
manualOnly: null, loop: null },
|
|
156
|
+
{ id: "pi", name: "Pi", preferred: false, skillDir: ".agents/skills",
|
|
157
|
+
reads: [".agents/skills", ".pi/skills"], skillsCli: "pi", rules: [], invoke: "/skill:{skill}",
|
|
158
|
+
manualOnly: "frontmatter", loop: null },
|
|
159
|
+
{ id: "pochi", name: "Pochi", preferred: false, skillDir: ".agents/skills",
|
|
160
|
+
reads: [".agents/skills", ".pochi/skills"], skillsCli: "pochi", rules: [], invoke: "/{skill}",
|
|
161
|
+
manualOnly: "frontmatter", loop: null },
|
|
162
|
+
{ id: "qoder", name: "Qoder", preferred: false, skillDir: ".qoder/skills",
|
|
163
|
+
reads: [".qoder/skills"], skillsCli: "qoder", rules: [], invoke: "/{skill}", manualOnly: null,
|
|
164
|
+
loop: cmd("/loop {interval} {prompt}") },
|
|
165
|
+
{ id: "qwen", name: "QwenCoder", preferred: false, skillDir: ".qwen/skills",
|
|
166
|
+
reads: [".qwen/skills"], skillsCli: "qwen-code", rules: ["QWEN.md"], invoke: "/{skill}",
|
|
167
|
+
manualOnly: "frontmatter", loop: null },
|
|
168
|
+
{ id: "replit", name: "Replit Agent", preferred: false, skillDir: ".agents/skills",
|
|
169
|
+
reads: [".agents/skills"], skillsCli: "replit", rules: ["replit.md"], invoke: "natural",
|
|
170
|
+
manualOnly: null, loop: null },
|
|
171
|
+
{ id: "rovo-dev", name: "Rovo Dev", preferred: false, skillDir: ".agents/skills",
|
|
172
|
+
reads: [".agents/skills", ".rovodev/skills"], skillsCli: "rovodev", rules: [], invoke: "natural",
|
|
173
|
+
manualOnly: null, loop: null },
|
|
174
|
+
{ id: "trae", name: "Trae", preferred: false, skillDir: ".trae/skills",
|
|
175
|
+
reads: [".trae/skills"], skillsCli: "trae", rules: [".trae/rules/wdi-method.md"], invoke: "natural",
|
|
176
|
+
manualOnly: null, loop: null },
|
|
177
|
+
{ id: "warp", name: "Warp", preferred: false, skillDir: ".agents/skills",
|
|
178
|
+
reads: [".agents/skills", ".warp/skills", ".claude/skills"], skillsCli: "warp", rules: [],
|
|
179
|
+
invoke: "/{skill}", manualOnly: null,
|
|
180
|
+
loop: sched("a Warp Scheduled Agent running `{prompt}` every {interval}") },
|
|
181
|
+
{ id: "windsurf", name: "Windsurf (Devin Desktop)", preferred: false, skillDir: ".agents/skills",
|
|
182
|
+
reads: [".agents/skills", ".windsurf/skills", ".devin/skills"], skillsCli: "windsurf", rules: [],
|
|
183
|
+
invoke: "@{skill}", manualOnly: null, loop: null },
|
|
184
|
+
{ id: "zencoder", name: "Zencoder", preferred: false, skillDir: ".zencoder/skills",
|
|
185
|
+
reads: [".zencoder/skills", ".agents/skills", ".claude/skills"], skillsCli: "zencoder",
|
|
186
|
+
rules: [".zencoder/rules/wdi-method.md"], invoke: "/{skill}", manualOnly: "frontmatter", loop: null },
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
const BY_ID = new Map(PLATFORMS.map((p) => [p.id, p]));
|
|
190
|
+
|
|
191
|
+
export const ALL_PLATFORM_IDS = PLATFORMS.map((p) => p.id);
|
|
192
|
+
|
|
193
|
+
export const PREFERRED_PLATFORM_IDS = PLATFORMS.filter((p) => p.preferred).map((p) => p.id);
|
|
194
|
+
|
|
195
|
+
/** @param {string} raw */
|
|
196
|
+
export function normalizePlatformId(raw) {
|
|
197
|
+
const id = raw.trim();
|
|
198
|
+
return LEGACY_ALIASES[id] || id;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @param {string} id */
|
|
202
|
+
export function isRetiredPlatform(id) {
|
|
203
|
+
return Object.hasOwn(RETIRED_PLATFORMS, normalizePlatformId(id));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @param {string[]} rawIds */
|
|
207
|
+
export function normalizePlatformIds(rawIds) {
|
|
208
|
+
const out = [];
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (const raw of rawIds) {
|
|
211
|
+
const id = normalizePlatformId(raw);
|
|
212
|
+
if (!BY_ID.has(id)) continue;
|
|
213
|
+
if (seen.has(id)) continue;
|
|
214
|
+
seen.add(id);
|
|
215
|
+
out.push(id);
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** @param {string} id */
|
|
221
|
+
export function getPlatform(id) {
|
|
222
|
+
return BY_ID.get(normalizePlatformId(id));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** @param {string} id */
|
|
226
|
+
export function isKnownPlatform(id) {
|
|
227
|
+
return BY_ID.has(normalizePlatformId(id));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Preferred first, then declaration order — mirrors BMad install ordering. */
|
|
231
|
+
export function sortedPlatforms() {
|
|
232
|
+
const preferred = PLATFORMS.filter((p) => p.preferred);
|
|
233
|
+
const other = PLATFORMS.filter((p) => !p.preferred);
|
|
234
|
+
return [...preferred, ...other];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Every rule file a set of hosts reads, `AGENTS.md` first. */
|
|
238
|
+
export function ruleFiles(platformIds) {
|
|
239
|
+
const out = ["AGENTS.md"];
|
|
240
|
+
for (const id of normalizePlatformIds(platformIds)) {
|
|
241
|
+
for (const file of getPlatform(id).rules) if (!out.includes(file)) out.push(file);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** @param {Platform} platform */
|
|
247
|
+
function hookNote(platform) {
|
|
248
|
+
const bits = [platform.skillDir, ["AGENTS.md", ...platform.rules].join(" + ")];
|
|
249
|
+
if (platform.hooks?.includes("opencode-commands")) bits.push(".opencode/commands");
|
|
250
|
+
if (platform.loop) bits.push("loop");
|
|
251
|
+
return bits.join(" · ");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** @param {string[]} selectedIds */
|
|
255
|
+
export function platformSelectOptions(selectedIds = []) {
|
|
256
|
+
const configured = new Set(normalizePlatformIds(selectedIds));
|
|
257
|
+
const sorted = sortedPlatforms();
|
|
258
|
+
const head = sorted.filter((p) => configured.has(p.id));
|
|
259
|
+
const tail = sorted.filter((p) => !configured.has(p.id));
|
|
260
|
+
return [...head, ...tail].map((p) => {
|
|
261
|
+
const tags = [];
|
|
262
|
+
if (p.preferred) tags.push("⭐");
|
|
263
|
+
if (configured.has(p.id)) tags.push("✅");
|
|
264
|
+
const prefix = tags.length ? `${tags.join(" ")} ` : "";
|
|
265
|
+
return {
|
|
266
|
+
value: p.id,
|
|
267
|
+
label: `${prefix}${p.name} → ${hookNote(p)}`,
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Minimal YAML list reader for a top-level `<key>:` block — no dependency on a YAML parser.
|
|
274
|
+
* @param {import("node:fs")} fs
|
|
275
|
+
* @param {string} filePath
|
|
276
|
+
* @param {string} [key]
|
|
277
|
+
*/
|
|
278
|
+
export function readYamlIdesList(fs, filePath, key = "ides") {
|
|
279
|
+
if (!fs.existsSync(filePath)) return [];
|
|
280
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
281
|
+
const out = [];
|
|
282
|
+
let inList = false;
|
|
283
|
+
const head = new RegExp(`^${key}:\\s*$`);
|
|
284
|
+
for (const line of text.split(/\r?\n/)) {
|
|
285
|
+
if (head.test(line)) {
|
|
286
|
+
inList = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (inList) {
|
|
290
|
+
const item = line.match(/^\s+-\s+(?:id:\s*)?([A-Za-z0-9_.-]+)\s*$/);
|
|
291
|
+
if (item) {
|
|
292
|
+
out.push(item[1]);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (line.trim() && !/^\s/.test(line)) inList = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @param {string} target
|
|
303
|
+
* @param {import("node:fs")} fs
|
|
304
|
+
*/
|
|
305
|
+
export function readBmadManifestIdes(target, fs) {
|
|
306
|
+
const file = path.join(target, "_bmad", "_config", "manifest.yaml");
|
|
307
|
+
return readYamlIdesList(fs, file);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** The hosts the last install or update was run for — the stamp's own record. */
|
|
311
|
+
export function readStampPlatforms(target, fs) {
|
|
312
|
+
return readYamlIdesList(fs, path.join(target, ".control", "wdi-method.yaml"), "platforms");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Ids a repo still names that the method no longer supports — from the stamp and BMad's manifest. */
|
|
316
|
+
export function retiredPlatformsIn(target, fs) {
|
|
317
|
+
const raw = [...readStampPlatforms(target, fs), ...readBmadManifestIdes(target, fs)];
|
|
318
|
+
return [...new Set(raw.map(normalizePlatformId).filter((id) => isRetiredPlatform(id)))];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The hosts a repo DECLARES — the stamp, then BMad's manifest — with no guessing from files. Empty
|
|
323
|
+
* means nobody has said, and a check that needs hosts falls back to "anywhere in the repo".
|
|
324
|
+
* @param {string} target
|
|
325
|
+
* @param {import("node:fs")} fs
|
|
326
|
+
*/
|
|
327
|
+
export function declaredPlatforms(target, fs) {
|
|
328
|
+
const fromStamp = normalizePlatformIds(readStampPlatforms(target, fs));
|
|
329
|
+
if (fromStamp.length) return fromStamp;
|
|
330
|
+
return normalizePlatformIds(readBmadManifestIdes(target, fs));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Which hosts this repo is for. The stamp first — it is what the owner picked last time — then
|
|
335
|
+
* BMad's manifest, then the files on disk.
|
|
336
|
+
* @param {string} target
|
|
337
|
+
* @param {import("node:fs")} fs
|
|
338
|
+
*/
|
|
339
|
+
export function detectPlatforms(target, fs) {
|
|
340
|
+
const declared = declaredPlatforms(target, fs);
|
|
341
|
+
if (declared.length) return declared;
|
|
342
|
+
|
|
343
|
+
const found = [];
|
|
344
|
+
const has = (dir) => fs.existsSync(path.join(target, dir, "wdi-init", "SKILL.md"))
|
|
345
|
+
|| fs.existsSync(path.join(target, dir, "bmad-help", "SKILL.md"));
|
|
346
|
+
// A skill directory only one host writes names that host. `.agents/skills` is shared by many,
|
|
347
|
+
// so it alone stays ambiguous and is read as Cursor, the long-standing default.
|
|
348
|
+
for (const p of PLATFORMS) {
|
|
349
|
+
if (p.skillDir === ".agents/skills") continue;
|
|
350
|
+
if (PLATFORMS.filter((q) => q.skillDir === p.skillDir).length > 1) continue;
|
|
351
|
+
if (has(p.skillDir)) found.push(p.id);
|
|
352
|
+
}
|
|
353
|
+
if (fs.existsSync(path.join(target, ".cursorrules")) || has(".agents/skills")) found.push("cursor");
|
|
354
|
+
if (fs.existsSync(path.join(target, "AGENTS.md"))) found.push("codex");
|
|
355
|
+
if (fs.existsSync(path.join(target, ".agents", "AGENTS.md"))) found.push("antigravity-cli");
|
|
356
|
+
const unique = normalizePlatformIds(found);
|
|
357
|
+
return unique.length ? unique : PREFERRED_PLATFORM_IDS.slice();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @param {string} target
|
|
362
|
+
* @param {string[]} platformIds
|
|
363
|
+
*/
|
|
364
|
+
export function skillDestinations(target, platformIds) {
|
|
365
|
+
const dests = new Set();
|
|
366
|
+
for (const id of normalizePlatformIds(platformIds)) {
|
|
367
|
+
const platform = getPlatform(id);
|
|
368
|
+
if (platform?.skillDir) dests.add(path.join(target, platform.skillDir));
|
|
369
|
+
}
|
|
370
|
+
return [...dests];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** @param {string[]} platformIds */
|
|
374
|
+
export function platformUsesHook(platformIds, hook) {
|
|
375
|
+
return normalizePlatformIds(platformIds).some((id) => getPlatform(id)?.hooks?.includes(hook));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** @param {string[]} platformIds */
|
|
379
|
+
export function platformsWithManualOnly(platformIds, kind) {
|
|
380
|
+
return normalizePlatformIds(platformIds).filter((id) => getPlatform(id).manualOnly === kind);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** The `hosts:` block the installer writes into `.control/wdi-method.yaml`. */
|
|
384
|
+
export function hostsYaml(platformIds) {
|
|
385
|
+
const q = (s) => JSON.stringify(s);
|
|
386
|
+
const lines = ["hosts:"];
|
|
387
|
+
for (const id of normalizePlatformIds(platformIds)) {
|
|
388
|
+
const p = getPlatform(id);
|
|
389
|
+
lines.push(` - id: ${p.id}`);
|
|
390
|
+
lines.push(` name: ${q(p.name)}`);
|
|
391
|
+
lines.push(` reads: [${p.reads.map(q).join(", ")}]`);
|
|
392
|
+
lines.push(` rules: [${["AGENTS.md", ...p.rules].map(q).join(", ")}]`);
|
|
393
|
+
lines.push(` invoke: ${q(p.invoke)}`);
|
|
394
|
+
lines.push(` manual_only: ${p.manualOnly ?? "none"}`);
|
|
395
|
+
lines.push(p.loop
|
|
396
|
+
? ` loop: {kind: ${p.loop.kind}, how: ${q(p.loop.how)}}`
|
|
397
|
+
: " loop: none");
|
|
398
|
+
}
|
|
399
|
+
return lines.join("\n");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function formatPlatformList() {
|
|
403
|
+
const idWidth = Math.max(...PLATFORMS.map((p) => p.id.length), 2);
|
|
404
|
+
const nameWidth = Math.max(...PLATFORMS.map((p) => p.name.length), 4);
|
|
405
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
406
|
+
const lines = [
|
|
407
|
+
"Supported platform IDs (pass via --agents <id>[,<id>...]):",
|
|
408
|
+
"",
|
|
409
|
+
` ${pad("ID", idWidth)} ${pad("Name", nameWidth)} Skill directory · rule files · loop`,
|
|
410
|
+
` ${pad("-".repeat(idWidth), idWidth)} ${pad("-".repeat(nameWidth), nameWidth)} ${"-".repeat(36)}`,
|
|
411
|
+
];
|
|
412
|
+
for (const p of sortedPlatforms()) {
|
|
413
|
+
const star = p.preferred ? "⭐" : " ";
|
|
414
|
+
lines.push(`${star} ${pad(p.id, idWidth)} ${pad(p.name, nameWidth)} ${hookNote(p)}`);
|
|
415
|
+
}
|
|
416
|
+
lines.push("", "⭐ = recommended (same as BMad Method)",
|
|
417
|
+
"loop = the host has its own scheduler, so wdi-daily-autopilot repeats; elsewhere it runs once",
|
|
418
|
+
"", "Example: npx wdi-method install --yes --agents claude-code,kiro");
|
|
419
|
+
return lines.join("\n");
|
|
420
|
+
}
|