bmalph 2.2.1 → 2.4.0
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 +162 -48
- package/dist/cli.js +14 -0
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.js +105 -41
- package/dist/commands/implement.d.ts +6 -0
- package/dist/commands/implement.js +82 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +74 -7
- package/dist/commands/reset.d.ts +7 -0
- package/dist/commands/reset.js +81 -0
- package/dist/commands/status.js +86 -10
- package/dist/commands/upgrade.js +8 -5
- package/dist/installer.d.ts +15 -4
- package/dist/installer.js +190 -101
- package/dist/platform/aider.d.ts +2 -0
- package/dist/platform/aider.js +71 -0
- package/dist/platform/claude-code.d.ts +2 -0
- package/dist/platform/claude-code.js +87 -0
- package/dist/platform/codex.d.ts +2 -0
- package/dist/platform/codex.js +67 -0
- package/dist/platform/copilot.d.ts +2 -0
- package/dist/platform/copilot.js +71 -0
- package/dist/platform/cursor.d.ts +2 -0
- package/dist/platform/cursor.js +71 -0
- package/dist/platform/detect.d.ts +7 -0
- package/dist/platform/detect.js +23 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/registry.d.ts +4 -0
- package/dist/platform/registry.js +27 -0
- package/dist/platform/resolve.d.ts +8 -0
- package/dist/platform/resolve.js +24 -0
- package/dist/platform/types.d.ts +41 -0
- package/dist/platform/types.js +7 -0
- package/dist/platform/windsurf.d.ts +2 -0
- package/dist/platform/windsurf.js +71 -0
- package/dist/reset.d.ts +18 -0
- package/dist/reset.js +181 -0
- package/dist/transition/artifact-scan.d.ts +27 -0
- package/dist/transition/artifact-scan.js +91 -0
- package/dist/transition/artifacts.d.ts +1 -0
- package/dist/transition/artifacts.js +2 -1
- package/dist/transition/context.js +34 -0
- package/dist/transition/fix-plan.d.ts +8 -2
- package/dist/transition/fix-plan.js +33 -7
- package/dist/transition/orchestration.d.ts +2 -2
- package/dist/transition/orchestration.js +120 -41
- package/dist/transition/preflight.d.ts +6 -0
- package/dist/transition/preflight.js +154 -0
- package/dist/transition/specs-changelog.js +4 -1
- package/dist/transition/specs-index.d.ts +1 -1
- package/dist/transition/specs-index.js +24 -1
- package/dist/transition/types.d.ts +23 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/dryrun.d.ts +1 -1
- package/dist/utils/dryrun.js +22 -0
- package/dist/utils/validate.js +18 -2
- package/package.json +1 -1
- package/ralph/drivers/claude-code.sh +118 -0
- package/ralph/drivers/codex.sh +81 -0
- package/ralph/ralph_import.sh +11 -0
- package/ralph/ralph_loop.sh +52 -64
- package/ralph/templates/ralphrc.template +7 -0
- package/slash-commands/bmalph-doctor.md +16 -0
- package/slash-commands/bmalph-implement.md +18 -141
- package/slash-commands/bmalph-status.md +15 -0
- package/slash-commands/bmalph-upgrade.md +15 -0
package/dist/installer.js
CHANGED
|
@@ -63,7 +63,86 @@ async function isTemplateCustomized(filePath, templateName) {
|
|
|
63
63
|
throw err;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Lazily loads the default (claude-code) platform to avoid circular imports
|
|
68
|
+
* and keep backward compatibility for callers that don't pass a platform.
|
|
69
|
+
*/
|
|
70
|
+
async function getDefaultPlatform() {
|
|
71
|
+
const { claudeCodePlatform } = await import("./platform/claude-code.js");
|
|
72
|
+
return claudeCodePlatform;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Deliver slash commands based on the platform's command delivery strategy.
|
|
76
|
+
*
|
|
77
|
+
* - "directory": Copy command files to a directory (e.g., .claude/commands/)
|
|
78
|
+
* - "inline": Merge command content as sections in the instructions file
|
|
79
|
+
* - "none": Skip command delivery entirely
|
|
80
|
+
*/
|
|
81
|
+
async function deliverCommands(projectDir, platform, slashCommandsDir) {
|
|
82
|
+
const delivery = platform.commandDelivery;
|
|
83
|
+
if (delivery.kind === "none") {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const slashFiles = await readdir(slashCommandsDir);
|
|
87
|
+
const bundledCommandNames = new Set(slashFiles.filter((f) => f.endsWith(".md")));
|
|
88
|
+
if (delivery.kind === "directory") {
|
|
89
|
+
const commandsDir = join(projectDir, delivery.dir);
|
|
90
|
+
await mkdir(commandsDir, { recursive: true });
|
|
91
|
+
// Clean stale bmalph-owned commands before copying (preserve user-created commands)
|
|
92
|
+
try {
|
|
93
|
+
const existingCommands = await readdir(commandsDir);
|
|
94
|
+
for (const file of existingCommands) {
|
|
95
|
+
if (file.endsWith(".md") && bundledCommandNames.has(file)) {
|
|
96
|
+
await rm(join(commandsDir, file), { force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (!isEnoent(err))
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
for (const file of bundledCommandNames) {
|
|
105
|
+
await cp(join(slashCommandsDir, file), join(commandsDir, file), { dereference: false });
|
|
106
|
+
}
|
|
107
|
+
return [`${delivery.dir}/`];
|
|
108
|
+
}
|
|
109
|
+
if (delivery.kind === "inline") {
|
|
110
|
+
// Merge command content as sections in the instructions file
|
|
111
|
+
const instructionsPath = join(projectDir, platform.instructionsFile);
|
|
112
|
+
let existing = "";
|
|
113
|
+
try {
|
|
114
|
+
existing = await readFile(instructionsPath, "utf-8");
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (!isEnoent(err))
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
const commandSections = [];
|
|
121
|
+
for (const file of [...bundledCommandNames].sort()) {
|
|
122
|
+
const commandName = file.replace(/\.md$/, "");
|
|
123
|
+
const content = await readFile(join(slashCommandsDir, file), "utf-8");
|
|
124
|
+
commandSections.push(`### Command: ${commandName}\n\n${content.trim()}`);
|
|
125
|
+
}
|
|
126
|
+
const inlineMarker = "## BMAD Commands";
|
|
127
|
+
const commandsBlock = `\n${inlineMarker}\n\n${commandSections.join("\n\n---\n\n")}\n`;
|
|
128
|
+
if (existing.includes(inlineMarker)) {
|
|
129
|
+
// Replace existing commands section
|
|
130
|
+
const sectionStart = existing.indexOf(inlineMarker);
|
|
131
|
+
const before = existing.slice(0, sectionStart);
|
|
132
|
+
const afterSection = existing.slice(sectionStart);
|
|
133
|
+
const nextHeadingMatch = afterSection.match(/\n## (?!BMAD Commands)/);
|
|
134
|
+
const after = nextHeadingMatch ? afterSection.slice(nextHeadingMatch.index) : "";
|
|
135
|
+
await atomicWriteFile(instructionsPath, before.trimEnd() + commandsBlock + after);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await atomicWriteFile(instructionsPath, existing + commandsBlock);
|
|
139
|
+
}
|
|
140
|
+
return [platform.instructionsFile];
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
export async function copyBundledAssets(projectDir, platform) {
|
|
145
|
+
const p = platform ?? (await getDefaultPlatform());
|
|
67
146
|
const bmadDir = getBundledBmadDir();
|
|
68
147
|
const ralphDir = getBundledRalphDir();
|
|
69
148
|
const slashCommandsDir = getSlashCommandsDir();
|
|
@@ -77,32 +156,50 @@ export async function copyBundledAssets(projectDir) {
|
|
|
77
156
|
if (!(await exists(slashCommandsDir))) {
|
|
78
157
|
throw new Error(`Slash commands directory not found at ${slashCommandsDir}. Package may be corrupted.`);
|
|
79
158
|
}
|
|
80
|
-
// Atomic copy:
|
|
159
|
+
// Atomic copy: rename-aside pattern to prevent data loss
|
|
81
160
|
const bmadDest = join(projectDir, "_bmad");
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
161
|
+
const bmadOld = join(projectDir, "_bmad.old");
|
|
162
|
+
const bmadNew = join(projectDir, "_bmad.new");
|
|
163
|
+
// Clean leftover from previous failed attempt
|
|
164
|
+
await rm(bmadOld, { recursive: true, force: true });
|
|
165
|
+
// Move original aside (tolerate ENOENT on first install)
|
|
166
|
+
try {
|
|
167
|
+
await rename(bmadDest, bmadOld);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
if (!isEnoent(err))
|
|
171
|
+
throw err;
|
|
172
|
+
debug("No existing _bmad to preserve (first install)");
|
|
173
|
+
}
|
|
174
|
+
// Stage new content
|
|
175
|
+
await rm(bmadNew, { recursive: true, force: true });
|
|
176
|
+
await cp(bmadDir, bmadNew, { recursive: true, dereference: false });
|
|
177
|
+
// Swap in
|
|
85
178
|
try {
|
|
86
|
-
await
|
|
87
|
-
await rename(bmadTemp, bmadDest);
|
|
179
|
+
await rename(bmadNew, bmadDest);
|
|
88
180
|
}
|
|
89
181
|
catch (err) {
|
|
90
|
-
//
|
|
182
|
+
// Restore original on failure
|
|
183
|
+
debug(`Rename failed, restoring original: ${formatError(err)}`);
|
|
91
184
|
try {
|
|
92
|
-
await rename(
|
|
185
|
+
await rename(bmadOld, bmadDest);
|
|
93
186
|
}
|
|
94
|
-
catch {
|
|
95
|
-
|
|
187
|
+
catch (restoreErr) {
|
|
188
|
+
if (!isEnoent(restoreErr)) {
|
|
189
|
+
debug(`Could not restore _bmad.old: ${formatError(restoreErr)}`);
|
|
190
|
+
}
|
|
96
191
|
}
|
|
97
192
|
throw err;
|
|
98
193
|
}
|
|
194
|
+
// Clean up backup
|
|
195
|
+
await rm(bmadOld, { recursive: true, force: true });
|
|
99
196
|
// Generate combined manifest from module-help.csv files
|
|
100
197
|
await generateManifests(projectDir);
|
|
101
|
-
// Generate _bmad/config.yaml
|
|
198
|
+
// Generate _bmad/config.yaml with platform-specific value
|
|
102
199
|
const projectName = await deriveProjectName(projectDir);
|
|
103
200
|
const escapedName = projectName.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
104
201
|
await atomicWriteFile(join(projectDir, "_bmad/config.yaml"), `# BMAD Configuration - Generated by bmalph
|
|
105
|
-
platform:
|
|
202
|
+
platform: ${p.id}
|
|
106
203
|
project_name: "${escapedName}"
|
|
107
204
|
output_folder: _bmad-output
|
|
108
205
|
user_name: BMad
|
|
@@ -136,7 +233,11 @@ modules:
|
|
|
136
233
|
// Copy .ralphrc from template (skip if user has customized it)
|
|
137
234
|
const ralphrcDest = join(projectDir, ".ralph/.ralphrc");
|
|
138
235
|
if (!(await exists(ralphrcDest))) {
|
|
139
|
-
|
|
236
|
+
// Read template and inject platform driver
|
|
237
|
+
let ralphrcContent = await readFile(join(ralphDir, "templates/ralphrc.template"), "utf-8");
|
|
238
|
+
// Replace default PLATFORM_DRIVER value with the actual platform id
|
|
239
|
+
ralphrcContent = ralphrcContent.replace(/PLATFORM_DRIVER="\$\{PLATFORM_DRIVER:-[^"]*\}"/, `PLATFORM_DRIVER="\${PLATFORM_DRIVER:-${p.id}}"`);
|
|
240
|
+
await atomicWriteFile(ralphrcDest, ralphrcContent);
|
|
140
241
|
}
|
|
141
242
|
// Copy Ralph loop and lib → .ralph/
|
|
142
243
|
// Add version marker to ralph_loop.sh
|
|
@@ -162,27 +263,27 @@ modules:
|
|
|
162
263
|
dereference: false,
|
|
163
264
|
});
|
|
164
265
|
await chmod(join(projectDir, ".ralph/ralph_monitor.sh"), 0o755);
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
266
|
+
// Copy Ralph drivers → .ralph/drivers/
|
|
267
|
+
const driversDir = join(ralphDir, "drivers");
|
|
268
|
+
if (await exists(driversDir)) {
|
|
269
|
+
const destDriversDir = join(projectDir, ".ralph/drivers");
|
|
270
|
+
await rm(destDriversDir, { recursive: true, force: true });
|
|
271
|
+
await cp(driversDir, destDriversDir, { recursive: true, dereference: false });
|
|
272
|
+
// Make driver scripts executable
|
|
273
|
+
try {
|
|
274
|
+
const driverFiles = await readdir(destDriversDir);
|
|
275
|
+
for (const file of driverFiles) {
|
|
276
|
+
if (file.endsWith(".sh")) {
|
|
277
|
+
await chmod(join(destDriversDir, file), 0o755);
|
|
278
|
+
}
|
|
176
279
|
}
|
|
177
280
|
}
|
|
281
|
+
catch {
|
|
282
|
+
// Non-fatal if chmod fails
|
|
283
|
+
}
|
|
178
284
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
throw err;
|
|
182
|
-
}
|
|
183
|
-
for (const file of bundledCommandNames) {
|
|
184
|
-
await cp(join(slashCommandsDir, file), join(commandsDir, file), { dereference: false });
|
|
185
|
-
}
|
|
285
|
+
// Deliver slash commands based on platform strategy
|
|
286
|
+
const commandPaths = await deliverCommands(projectDir, p, slashCommandsDir);
|
|
186
287
|
// Update .gitignore
|
|
187
288
|
await updateGitignore(projectDir);
|
|
188
289
|
const updatedPaths = [
|
|
@@ -194,18 +295,18 @@ modules:
|
|
|
194
295
|
...(!promptCustomized ? [".ralph/PROMPT.md"] : []),
|
|
195
296
|
...(!agentCustomized ? [".ralph/@AGENT.md"] : []),
|
|
196
297
|
".ralph/RALPH-REFERENCE.md",
|
|
197
|
-
|
|
298
|
+
...commandPaths,
|
|
198
299
|
".gitignore",
|
|
199
300
|
];
|
|
200
301
|
return { updatedPaths };
|
|
201
302
|
}
|
|
202
|
-
export async function installProject(projectDir) {
|
|
303
|
+
export async function installProject(projectDir, platform) {
|
|
203
304
|
// Create user directories (not overwritten by upgrade)
|
|
204
305
|
await mkdir(join(projectDir, STATE_DIR), { recursive: true });
|
|
205
306
|
await mkdir(join(projectDir, ".ralph/specs"), { recursive: true });
|
|
206
307
|
await mkdir(join(projectDir, ".ralph/logs"), { recursive: true });
|
|
207
308
|
await mkdir(join(projectDir, ".ralph/docs/generated"), { recursive: true });
|
|
208
|
-
await copyBundledAssets(projectDir);
|
|
309
|
+
await copyBundledAssets(projectDir, platform);
|
|
209
310
|
}
|
|
210
311
|
async function deriveProjectName(projectDir) {
|
|
211
312
|
try {
|
|
@@ -290,74 +391,54 @@ async function updateGitignore(projectDir) {
|
|
|
290
391
|
: newEntries.join("\n") + "\n";
|
|
291
392
|
await atomicWriteFile(gitignorePath, existing + suffix);
|
|
292
393
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
| 1. Analysis | Understand the problem | \`/create-brief\`, \`/brainstorm-project\`, \`/market-research\` |
|
|
305
|
-
| 2. Planning | Define the solution | \`/create-prd\`, \`/create-ux\` |
|
|
306
|
-
| 3. Solutioning | Design the architecture | \`/create-architecture\`, \`/create-epics-stories\`, \`/implementation-readiness\` |
|
|
307
|
-
| 4. Implementation | Build it | \`/sprint-planning\`, \`/create-story\`, then \`/bmalph-implement\` for Ralph |
|
|
308
|
-
|
|
309
|
-
### Workflow
|
|
310
|
-
|
|
311
|
-
1. Work through Phases 1-3 using BMAD agents and workflows (interactive, command-driven)
|
|
312
|
-
2. Run \`/bmalph-implement\` to transition planning artifacts into Ralph format, then start Ralph
|
|
313
|
-
|
|
314
|
-
### Management Commands
|
|
315
|
-
|
|
316
|
-
| Command | Description |
|
|
317
|
-
|---------|-------------|
|
|
318
|
-
| \`/bmalph-status\` | Show current phase, Ralph progress, version info |
|
|
319
|
-
| \`/bmalph-implement\` | Transition planning artifacts → prepare Ralph loop |
|
|
320
|
-
| \`/bmalph-upgrade\` | Update bundled assets to match current bmalph version |
|
|
321
|
-
| \`/bmalph-doctor\` | Check project health and report issues |
|
|
322
|
-
| \`/bmalph-reset\` | Reset state (soft or hard reset with confirmation) |
|
|
323
|
-
|
|
324
|
-
### Available Agents
|
|
325
|
-
|
|
326
|
-
| Command | Agent | Role |
|
|
327
|
-
|---------|-------|------|
|
|
328
|
-
| \`/analyst\` | Analyst | Research, briefs, discovery |
|
|
329
|
-
| \`/architect\` | Architect | Technical design, architecture |
|
|
330
|
-
| \`/pm\` | Product Manager | PRDs, epics, stories |
|
|
331
|
-
| \`/sm\` | Scrum Master | Sprint planning, status, coordination |
|
|
332
|
-
| \`/dev\` | Developer | Implementation, coding |
|
|
333
|
-
| \`/ux-designer\` | UX Designer | User experience, wireframes |
|
|
334
|
-
| \`/qa\` | QA Engineer | Test automation, quality assurance |
|
|
335
|
-
`;
|
|
394
|
+
/**
|
|
395
|
+
* Merge the BMAD instructions snippet into the platform's instructions file.
|
|
396
|
+
* Creates the file if it doesn't exist, replaces an existing BMAD section on upgrade.
|
|
397
|
+
*/
|
|
398
|
+
export async function mergeInstructionsFile(projectDir, platform) {
|
|
399
|
+
const p = platform ?? (await getDefaultPlatform());
|
|
400
|
+
const instructionsPath = join(projectDir, p.instructionsFile);
|
|
401
|
+
const snippet = p.generateInstructionsSnippet();
|
|
402
|
+
const marker = p.instructionsSectionMarker;
|
|
403
|
+
// Ensure parent directory exists for nested paths (e.g. .cursor/rules/)
|
|
404
|
+
await mkdir(dirname(instructionsPath), { recursive: true });
|
|
336
405
|
let existing = "";
|
|
337
406
|
try {
|
|
338
|
-
existing = await readFile(
|
|
407
|
+
existing = await readFile(instructionsPath, "utf-8");
|
|
339
408
|
}
|
|
340
409
|
catch (err) {
|
|
341
410
|
if (!isEnoent(err))
|
|
342
411
|
throw err;
|
|
343
412
|
}
|
|
344
|
-
if (existing.includes(
|
|
413
|
+
if (existing.includes(marker)) {
|
|
345
414
|
// Replace stale section with current content, preserving content after it
|
|
346
|
-
const sectionStart = existing.indexOf(
|
|
415
|
+
const sectionStart = existing.indexOf(marker);
|
|
347
416
|
const before = existing.slice(0, sectionStart);
|
|
348
417
|
const afterSection = existing.slice(sectionStart);
|
|
349
|
-
// Find the next level-2 heading after the
|
|
350
|
-
const
|
|
418
|
+
// Find the next level-2 heading after the section start
|
|
419
|
+
const markerEscaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
420
|
+
const nextHeadingMatch = afterSection.match(new RegExp(`\\n## (?!${markerEscaped.slice(3)})`));
|
|
351
421
|
const after = nextHeadingMatch ? afterSection.slice(nextHeadingMatch.index) : "";
|
|
352
|
-
await atomicWriteFile(
|
|
422
|
+
await atomicWriteFile(instructionsPath, before.trimEnd() + "\n" + snippet + after);
|
|
353
423
|
return;
|
|
354
424
|
}
|
|
355
|
-
await atomicWriteFile(
|
|
425
|
+
await atomicWriteFile(instructionsPath, existing + snippet);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* @deprecated Use `mergeInstructionsFile(projectDir)` instead.
|
|
429
|
+
* Kept for backward compatibility during migration.
|
|
430
|
+
*/
|
|
431
|
+
export async function mergeClaudeMd(projectDir) {
|
|
432
|
+
return mergeInstructionsFile(projectDir);
|
|
356
433
|
}
|
|
357
434
|
export async function isInitialized(projectDir) {
|
|
358
435
|
return exists(join(projectDir, CONFIG_FILE));
|
|
359
436
|
}
|
|
360
|
-
export async function
|
|
437
|
+
export async function hasExistingBmadDir(projectDir) {
|
|
438
|
+
return exists(join(projectDir, "_bmad"));
|
|
439
|
+
}
|
|
440
|
+
export async function previewInstall(projectDir, platform) {
|
|
441
|
+
const p = platform ?? (await getDefaultPlatform());
|
|
361
442
|
const wouldCreate = [];
|
|
362
443
|
const wouldModify = [];
|
|
363
444
|
const wouldSkip = [];
|
|
@@ -368,11 +449,15 @@ export async function previewInstall(projectDir) {
|
|
|
368
449
|
".ralph/logs/",
|
|
369
450
|
".ralph/docs/generated/",
|
|
370
451
|
"_bmad/",
|
|
371
|
-
".claude/commands/",
|
|
372
452
|
];
|
|
453
|
+
// Add command directory only for directory-based delivery
|
|
454
|
+
if (p.commandDelivery.kind === "directory") {
|
|
455
|
+
dirsToCreate.push(`${p.commandDelivery.dir}/`);
|
|
456
|
+
}
|
|
373
457
|
for (const dir of dirsToCreate) {
|
|
374
458
|
if (await exists(join(projectDir, dir))) {
|
|
375
|
-
if (dir === "_bmad/" ||
|
|
459
|
+
if (dir === "_bmad/" ||
|
|
460
|
+
(p.commandDelivery.kind === "directory" && dir === `${p.commandDelivery.dir}/`)) {
|
|
376
461
|
wouldModify.push(dir);
|
|
377
462
|
}
|
|
378
463
|
}
|
|
@@ -404,19 +489,19 @@ export async function previewInstall(projectDir) {
|
|
|
404
489
|
else {
|
|
405
490
|
wouldCreate.push(".gitignore");
|
|
406
491
|
}
|
|
407
|
-
//
|
|
492
|
+
// Instructions file integration check
|
|
408
493
|
try {
|
|
409
|
-
const
|
|
410
|
-
if (
|
|
411
|
-
wouldSkip.push(
|
|
494
|
+
const content = await readFile(join(projectDir, p.instructionsFile), "utf-8");
|
|
495
|
+
if (content.includes(p.instructionsSectionMarker)) {
|
|
496
|
+
wouldSkip.push(`${p.instructionsFile} (already integrated)`);
|
|
412
497
|
}
|
|
413
498
|
else {
|
|
414
|
-
wouldModify.push(
|
|
499
|
+
wouldModify.push(p.instructionsFile);
|
|
415
500
|
}
|
|
416
501
|
}
|
|
417
502
|
catch (err) {
|
|
418
503
|
if (isEnoent(err)) {
|
|
419
|
-
wouldCreate.push(
|
|
504
|
+
wouldCreate.push(p.instructionsFile);
|
|
420
505
|
}
|
|
421
506
|
else {
|
|
422
507
|
throw err;
|
|
@@ -424,7 +509,8 @@ export async function previewInstall(projectDir) {
|
|
|
424
509
|
}
|
|
425
510
|
return { wouldCreate, wouldModify, wouldSkip };
|
|
426
511
|
}
|
|
427
|
-
export async function previewUpgrade(projectDir) {
|
|
512
|
+
export async function previewUpgrade(projectDir, platform) {
|
|
513
|
+
const p = platform ?? (await getDefaultPlatform());
|
|
428
514
|
const managedPaths = [
|
|
429
515
|
{ path: "_bmad/", isDir: true },
|
|
430
516
|
{ path: ".ralph/ralph_loop.sh", isDir: false },
|
|
@@ -434,24 +520,27 @@ export async function previewUpgrade(projectDir) {
|
|
|
434
520
|
{ path: ".ralph/PROMPT.md", isDir: false, templateName: "PROMPT.md" },
|
|
435
521
|
{ path: ".ralph/@AGENT.md", isDir: false, templateName: "AGENT.md" },
|
|
436
522
|
{ path: ".ralph/RALPH-REFERENCE.md", isDir: false },
|
|
437
|
-
{ path: ".claude/commands/", isDir: true },
|
|
438
523
|
{ path: ".gitignore", isDir: false },
|
|
439
524
|
];
|
|
525
|
+
// Add command directory only for directory-based delivery
|
|
526
|
+
if (p.commandDelivery.kind === "directory") {
|
|
527
|
+
managedPaths.push({ path: `${p.commandDelivery.dir}/`, isDir: true });
|
|
528
|
+
}
|
|
440
529
|
const wouldUpdate = [];
|
|
441
530
|
const wouldCreate = [];
|
|
442
531
|
const wouldPreserve = [];
|
|
443
|
-
for (const { path:
|
|
444
|
-
const fullPath = join(projectDir,
|
|
532
|
+
for (const { path: pathStr, templateName } of managedPaths) {
|
|
533
|
+
const fullPath = join(projectDir, pathStr.replace(/\/$/, ""));
|
|
445
534
|
if (await exists(fullPath)) {
|
|
446
535
|
if (templateName && (await isTemplateCustomized(fullPath, templateName))) {
|
|
447
|
-
wouldPreserve.push(
|
|
536
|
+
wouldPreserve.push(pathStr);
|
|
448
537
|
}
|
|
449
538
|
else {
|
|
450
|
-
wouldUpdate.push(
|
|
539
|
+
wouldUpdate.push(pathStr);
|
|
451
540
|
}
|
|
452
541
|
}
|
|
453
542
|
else {
|
|
454
|
-
wouldCreate.push(
|
|
543
|
+
wouldCreate.push(pathStr);
|
|
455
544
|
}
|
|
456
545
|
}
|
|
457
546
|
return { wouldUpdate, wouldCreate, wouldPreserve };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const aiderPlatform = {
|
|
5
|
+
id: "aider",
|
|
6
|
+
displayName: "Aider",
|
|
7
|
+
tier: "instructions-only",
|
|
8
|
+
instructionsFile: "CONVENTIONS.md",
|
|
9
|
+
commandDelivery: { kind: "none" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
|
|
24
|
+
### Workflow
|
|
25
|
+
|
|
26
|
+
Work through Phases 1-3 using BMAD agents and workflows interactively.
|
|
27
|
+
|
|
28
|
+
> **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: "CONVENTIONS.md contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, "CONVENTIONS.md"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
detail: "CONVENTIONS.md not found",
|
|
63
|
+
hint: "Run: bmalph init",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { exists } from "../utils/file-system.js";
|
|
4
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
5
|
+
export const claudeCodePlatform = {
|
|
6
|
+
id: "claude-code",
|
|
7
|
+
displayName: "Claude Code",
|
|
8
|
+
tier: "full",
|
|
9
|
+
instructionsFile: "CLAUDE.md",
|
|
10
|
+
commandDelivery: { kind: "directory", dir: ".claude/commands" },
|
|
11
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
12
|
+
generateInstructionsSnippet: () => `
|
|
13
|
+
## BMAD-METHOD Integration
|
|
14
|
+
|
|
15
|
+
Use \`/bmalph\` to navigate phases. Use \`/bmad-help\` to discover all commands. Use \`/bmalph-status\` for a quick overview.
|
|
16
|
+
|
|
17
|
+
### Phases
|
|
18
|
+
|
|
19
|
+
| Phase | Focus | Key Commands |
|
|
20
|
+
|-------|-------|-------------|
|
|
21
|
+
| 1. Analysis | Understand the problem | \`/create-brief\`, \`/brainstorm-project\`, \`/market-research\` |
|
|
22
|
+
| 2. Planning | Define the solution | \`/create-prd\`, \`/create-ux\` |
|
|
23
|
+
| 3. Solutioning | Design the architecture | \`/create-architecture\`, \`/create-epics-stories\`, \`/implementation-readiness\` |
|
|
24
|
+
| 4. Implementation | Build it | \`/sprint-planning\`, \`/create-story\`, then \`/bmalph-implement\` for Ralph |
|
|
25
|
+
|
|
26
|
+
### Workflow
|
|
27
|
+
|
|
28
|
+
1. Work through Phases 1-3 using BMAD agents and workflows (interactive, command-driven)
|
|
29
|
+
2. Run \`/bmalph-implement\` to transition planning artifacts into Ralph format, then start Ralph
|
|
30
|
+
|
|
31
|
+
### Management Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| \`/bmalph-status\` | Show current phase, Ralph progress, version info |
|
|
36
|
+
| \`/bmalph-implement\` | Transition planning artifacts → prepare Ralph loop |
|
|
37
|
+
| \`/bmalph-upgrade\` | Update bundled assets to match current bmalph version |
|
|
38
|
+
| \`/bmalph-doctor\` | Check project health and report issues |
|
|
39
|
+
|
|
40
|
+
### Available Agents
|
|
41
|
+
|
|
42
|
+
| Command | Agent | Role |
|
|
43
|
+
|---------|-------|------|
|
|
44
|
+
| \`/analyst\` | Analyst | Research, briefs, discovery |
|
|
45
|
+
| \`/architect\` | Architect | Technical design, architecture |
|
|
46
|
+
| \`/pm\` | Product Manager | PRDs, epics, stories |
|
|
47
|
+
| \`/sm\` | Scrum Master | Sprint planning, status, coordination |
|
|
48
|
+
| \`/dev\` | Developer | Implementation, coding |
|
|
49
|
+
| \`/ux-designer\` | UX Designer | User experience, wireframes |
|
|
50
|
+
| \`/qa\` | QA Engineer | Test automation, quality assurance |
|
|
51
|
+
`,
|
|
52
|
+
getDoctorChecks: () => [
|
|
53
|
+
{
|
|
54
|
+
id: "slash-command",
|
|
55
|
+
label: ".claude/commands/bmalph.md present",
|
|
56
|
+
check: async (projectDir) => {
|
|
57
|
+
if (await exists(join(projectDir, ".claude/commands/bmalph.md"))) {
|
|
58
|
+
return { passed: true };
|
|
59
|
+
}
|
|
60
|
+
return { passed: false, detail: "not found", hint: "Run: bmalph init" };
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "instructions-file",
|
|
65
|
+
label: "CLAUDE.md contains BMAD snippet",
|
|
66
|
+
check: async (projectDir) => {
|
|
67
|
+
try {
|
|
68
|
+
const content = await readFile(join(projectDir, "CLAUDE.md"), "utf-8");
|
|
69
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
70
|
+
return { passed: true };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
passed: false,
|
|
74
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
75
|
+
hint: "Run: bmalph init",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (isEnoent(err)) {
|
|
80
|
+
return { passed: false, detail: "CLAUDE.md not found", hint: "Run: bmalph init" };
|
|
81
|
+
}
|
|
82
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const codexPlatform = {
|
|
5
|
+
id: "codex",
|
|
6
|
+
displayName: "OpenAI Codex",
|
|
7
|
+
tier: "full",
|
|
8
|
+
instructionsFile: "AGENTS.md",
|
|
9
|
+
commandDelivery: { kind: "inline" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Run the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
| 4. Implementation | Build it | Developer agent, then Ralph autonomous loop |
|
|
24
|
+
|
|
25
|
+
### Workflow
|
|
26
|
+
|
|
27
|
+
1. Work through Phases 1-3 using BMAD agents and workflows
|
|
28
|
+
2. Use the bmalph-implement transition to prepare Ralph format, then start Ralph
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: "AGENTS.md contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, "AGENTS.md"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return { passed: false, detail: "AGENTS.md not found", hint: "Run: bmalph init" };
|
|
61
|
+
}
|
|
62
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|