bmalph 2.2.1 → 2.3.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.
Files changed (41) hide show
  1. package/README.md +111 -30
  2. package/dist/cli.js +1 -0
  3. package/dist/commands/doctor.d.ts +14 -2
  4. package/dist/commands/doctor.js +83 -35
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +74 -7
  7. package/dist/commands/upgrade.js +8 -5
  8. package/dist/installer.d.ts +15 -4
  9. package/dist/installer.js +190 -101
  10. package/dist/platform/aider.d.ts +2 -0
  11. package/dist/platform/aider.js +71 -0
  12. package/dist/platform/claude-code.d.ts +2 -0
  13. package/dist/platform/claude-code.js +88 -0
  14. package/dist/platform/codex.d.ts +2 -0
  15. package/dist/platform/codex.js +67 -0
  16. package/dist/platform/copilot.d.ts +2 -0
  17. package/dist/platform/copilot.js +71 -0
  18. package/dist/platform/cursor.d.ts +2 -0
  19. package/dist/platform/cursor.js +71 -0
  20. package/dist/platform/detect.d.ts +7 -0
  21. package/dist/platform/detect.js +23 -0
  22. package/dist/platform/index.d.ts +4 -0
  23. package/dist/platform/index.js +3 -0
  24. package/dist/platform/registry.d.ts +4 -0
  25. package/dist/platform/registry.js +27 -0
  26. package/dist/platform/resolve.d.ts +8 -0
  27. package/dist/platform/resolve.js +24 -0
  28. package/dist/platform/types.d.ts +41 -0
  29. package/dist/platform/types.js +7 -0
  30. package/dist/platform/windsurf.d.ts +2 -0
  31. package/dist/platform/windsurf.js +71 -0
  32. package/dist/transition/artifacts.js +1 -1
  33. package/dist/transition/specs-changelog.js +4 -1
  34. package/dist/utils/config.d.ts +2 -0
  35. package/dist/utils/validate.js +16 -0
  36. package/package.json +1 -1
  37. package/ralph/drivers/claude-code.sh +118 -0
  38. package/ralph/drivers/codex.sh +81 -0
  39. package/ralph/ralph_import.sh +11 -0
  40. package/ralph/ralph_loop.sh +37 -64
  41. package/ralph/templates/ralphrc.template +7 -0
@@ -1,3 +1,4 @@
1
+ import type { Platform } from "./platform/types.js";
1
2
  export declare function getPackageVersion(): string;
2
3
  export interface BundledVersions {
3
4
  bmadCommit: string;
@@ -19,10 +20,20 @@ export interface PreviewUpgradeResult {
19
20
  wouldCreate: string[];
20
21
  wouldPreserve: string[];
21
22
  }
22
- export declare function copyBundledAssets(projectDir: string): Promise<UpgradeResult>;
23
- export declare function installProject(projectDir: string): Promise<void>;
23
+ export declare function copyBundledAssets(projectDir: string, platform?: Platform): Promise<UpgradeResult>;
24
+ export declare function installProject(projectDir: string, platform?: Platform): Promise<void>;
24
25
  export declare function generateManifests(projectDir: string): Promise<void>;
26
+ /**
27
+ * Merge the BMAD instructions snippet into the platform's instructions file.
28
+ * Creates the file if it doesn't exist, replaces an existing BMAD section on upgrade.
29
+ */
30
+ export declare function mergeInstructionsFile(projectDir: string, platform?: Platform): Promise<void>;
31
+ /**
32
+ * @deprecated Use `mergeInstructionsFile(projectDir)` instead.
33
+ * Kept for backward compatibility during migration.
34
+ */
25
35
  export declare function mergeClaudeMd(projectDir: string): Promise<void>;
26
36
  export declare function isInitialized(projectDir: string): Promise<boolean>;
27
- export declare function previewInstall(projectDir: string): Promise<PreviewInstallResult>;
28
- export declare function previewUpgrade(projectDir: string): Promise<PreviewUpgradeResult>;
37
+ export declare function hasExistingBmadDir(projectDir: string): Promise<boolean>;
38
+ export declare function previewInstall(projectDir: string, platform?: Platform): Promise<PreviewInstallResult>;
39
+ export declare function previewUpgrade(projectDir: string, platform?: Platform): Promise<PreviewUpgradeResult>;
package/dist/installer.js CHANGED
@@ -63,7 +63,86 @@ async function isTemplateCustomized(filePath, templateName) {
63
63
  throw err;
64
64
  }
65
65
  }
66
- export async function copyBundledAssets(projectDir) {
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: write to temp dir, then swap with old
159
+ // Atomic copy: rename-aside pattern to prevent data loss
81
160
  const bmadDest = join(projectDir, "_bmad");
82
- const bmadTemp = join(projectDir, "_bmad.new");
83
- await rm(bmadTemp, { recursive: true, force: true });
84
- await cp(bmadDir, bmadTemp, { recursive: true, dereference: false });
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 rm(bmadDest, { recursive: true, force: true });
87
- await rename(bmadTemp, bmadDest);
179
+ await rename(bmadNew, bmadDest);
88
180
  }
89
181
  catch (err) {
90
- // If rename fails, attempt to restore from temp
182
+ // Restore original on failure
183
+ debug(`Rename failed, restoring original: ${formatError(err)}`);
91
184
  try {
92
- await rename(bmadTemp, bmadDest);
185
+ await rename(bmadOld, bmadDest);
93
186
  }
94
- catch {
95
- // Best effort restore failed
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: claude-code
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
- await cp(join(ralphDir, "templates/ralphrc.template"), ralphrcDest, { dereference: false });
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
- // Install all slash commands → .claude/commands/
166
- // Clean stale bmalph-owned commands before copying (preserve user-created commands)
167
- const commandsDir = join(projectDir, ".claude/commands");
168
- await mkdir(commandsDir, { recursive: true });
169
- const slashFiles = await readdir(slashCommandsDir);
170
- const bundledCommandNames = new Set(slashFiles.filter((f) => f.endsWith(".md")));
171
- try {
172
- const existingCommands = await readdir(commandsDir);
173
- for (const file of existingCommands) {
174
- if (file.endsWith(".md") && bundledCommandNames.has(file)) {
175
- await rm(join(commandsDir, file), { force: true });
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
- catch (err) {
180
- if (!isEnoent(err))
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
- ".claude/commands/",
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
- export async function mergeClaudeMd(projectDir) {
294
- const claudeMdPath = join(projectDir, "CLAUDE.md");
295
- const snippet = `
296
- ## BMAD-METHOD Integration
297
-
298
- Use \`/bmalph\` to navigate phases. Use \`/bmad-help\` to discover all commands. Use \`/bmalph-status\` for a quick overview.
299
-
300
- ### Phases
301
-
302
- | Phase | Focus | Key Commands |
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(claudeMdPath, "utf-8");
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("## BMAD-METHOD Integration")) {
413
+ if (existing.includes(marker)) {
345
414
  // Replace stale section with current content, preserving content after it
346
- const sectionStart = existing.indexOf("## BMAD-METHOD Integration");
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 BMAD section start
350
- const nextHeadingMatch = afterSection.match(/\n## (?!BMAD-METHOD Integration)/);
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(claudeMdPath, before.trimEnd() + "\n" + snippet + after);
422
+ await atomicWriteFile(instructionsPath, before.trimEnd() + "\n" + snippet + after);
353
423
  return;
354
424
  }
355
- await atomicWriteFile(claudeMdPath, existing + snippet);
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 previewInstall(projectDir) {
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/" || dir === ".claude/commands/") {
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
- // CLAUDE.md integration check
492
+ // Instructions file integration check
408
493
  try {
409
- const claudeMd = await readFile(join(projectDir, "CLAUDE.md"), "utf-8");
410
- if (claudeMd.includes("## BMAD-METHOD Integration")) {
411
- wouldSkip.push("CLAUDE.md (already integrated)");
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("CLAUDE.md");
499
+ wouldModify.push(p.instructionsFile);
415
500
  }
416
501
  }
417
502
  catch (err) {
418
503
  if (isEnoent(err)) {
419
- wouldCreate.push("CLAUDE.md");
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: p, templateName } of managedPaths) {
444
- const fullPath = join(projectDir, p.replace(/\/$/, ""));
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(p);
536
+ wouldPreserve.push(pathStr);
448
537
  }
449
538
  else {
450
- wouldUpdate.push(p);
539
+ wouldUpdate.push(pathStr);
451
540
  }
452
541
  }
453
542
  else {
454
- wouldCreate.push(p);
543
+ wouldCreate.push(pathStr);
455
544
  }
456
545
  }
457
546
  return { wouldUpdate, wouldCreate, wouldPreserve };
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const aiderPlatform: Platform;
@@ -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,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const claudeCodePlatform: Platform;
@@ -0,0 +1,88 @@
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
+ | \`/bmalph-reset\` | Reset state (soft or hard reset with confirmation) |
40
+
41
+ ### Available Agents
42
+
43
+ | Command | Agent | Role |
44
+ |---------|-------|------|
45
+ | \`/analyst\` | Analyst | Research, briefs, discovery |
46
+ | \`/architect\` | Architect | Technical design, architecture |
47
+ | \`/pm\` | Product Manager | PRDs, epics, stories |
48
+ | \`/sm\` | Scrum Master | Sprint planning, status, coordination |
49
+ | \`/dev\` | Developer | Implementation, coding |
50
+ | \`/ux-designer\` | UX Designer | User experience, wireframes |
51
+ | \`/qa\` | QA Engineer | Test automation, quality assurance |
52
+ `,
53
+ getDoctorChecks: () => [
54
+ {
55
+ id: "slash-command",
56
+ label: ".claude/commands/bmalph.md present",
57
+ check: async (projectDir) => {
58
+ if (await exists(join(projectDir, ".claude/commands/bmalph.md"))) {
59
+ return { passed: true };
60
+ }
61
+ return { passed: false, detail: "not found", hint: "Run: bmalph init" };
62
+ },
63
+ },
64
+ {
65
+ id: "instructions-file",
66
+ label: "CLAUDE.md contains BMAD snippet",
67
+ check: async (projectDir) => {
68
+ try {
69
+ const content = await readFile(join(projectDir, "CLAUDE.md"), "utf-8");
70
+ if (content.includes("BMAD-METHOD Integration")) {
71
+ return { passed: true };
72
+ }
73
+ return {
74
+ passed: false,
75
+ detail: "missing BMAD-METHOD Integration section",
76
+ hint: "Run: bmalph init",
77
+ };
78
+ }
79
+ catch (err) {
80
+ if (isEnoent(err)) {
81
+ return { passed: false, detail: "CLAUDE.md not found", hint: "Run: bmalph init" };
82
+ }
83
+ return { passed: false, detail: formatError(err), hint: "Check file permissions" };
84
+ }
85
+ },
86
+ },
87
+ ],
88
+ };
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const codexPlatform: Platform;