bmalph 2.2.0 → 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.
- package/README.md +111 -30
- package/bundled-versions.json +1 -2
- package/dist/cli.js +3 -2
- package/dist/commands/check-updates.js +5 -27
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.js +99 -56
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +75 -9
- package/dist/commands/upgrade.js +8 -5
- package/dist/installer.d.ts +16 -5
- package/dist/installer.js +245 -128
- 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 +88 -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/transition/artifacts.js +1 -1
- package/dist/transition/fix-plan.d.ts +1 -1
- package/dist/transition/fix-plan.js +3 -2
- package/dist/transition/orchestration.js +1 -1
- package/dist/transition/specs-changelog.js +4 -1
- package/dist/transition/specs-index.js +2 -3
- package/dist/utils/config.d.ts +2 -1
- package/dist/utils/errors.js +3 -0
- package/dist/utils/github.d.ts +0 -1
- package/dist/utils/github.js +1 -18
- package/dist/utils/json.js +2 -2
- package/dist/utils/state.js +7 -1
- package/dist/utils/validate.d.ts +1 -0
- package/dist/utils/validate.js +35 -4
- package/package.json +4 -4
- 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 +37 -64
- package/ralph/templates/ralphrc.template +7 -0
package/README.md
CHANGED
|
@@ -27,11 +27,30 @@ bmalph provides:
|
|
|
27
27
|
- `bmalph doctor` — Check installation health
|
|
28
28
|
- `/bmalph-implement` — Transition from BMAD to Ralph
|
|
29
29
|
|
|
30
|
+
## Supported Platforms
|
|
31
|
+
|
|
32
|
+
bmalph works with multiple AI coding assistants. Each platform gets BMAD planning (Phases 1-3). The Ralph autonomous loop (Phase 4) requires a CLI-based platform.
|
|
33
|
+
|
|
34
|
+
| Platform | ID | Tier | Instructions File | Commands |
|
|
35
|
+
| -------------- | ------------- | ----------------- | --------------------------------- | ----------------------------- |
|
|
36
|
+
| Claude Code | `claude-code` | full | `CLAUDE.md` | `.claude/commands/` directory |
|
|
37
|
+
| OpenAI Codex | `codex` | full | `AGENTS.md` | Inline in instructions file |
|
|
38
|
+
| Cursor | `cursor` | instructions-only | `.cursor/rules/bmad.mdc` | None |
|
|
39
|
+
| Windsurf | `windsurf` | instructions-only | `.windsurf/rules/bmad.md` | None |
|
|
40
|
+
| GitHub Copilot | `copilot` | instructions-only | `.github/copilot-instructions.md` | None |
|
|
41
|
+
| Aider | `aider` | instructions-only | `CONVENTIONS.md` | None |
|
|
42
|
+
|
|
43
|
+
**Tiers:**
|
|
44
|
+
|
|
45
|
+
- **full** — Phases 1-4. BMAD planning + Ralph autonomous implementation loop.
|
|
46
|
+
- **instructions-only** — Phases 1-3. BMAD planning only. Ralph is not available.
|
|
47
|
+
|
|
30
48
|
## Prerequisites
|
|
31
49
|
|
|
32
50
|
- Node.js 20+
|
|
33
51
|
- Bash (WSL or Git Bash on Windows)
|
|
34
|
-
-
|
|
52
|
+
- A supported AI coding platform (see table above)
|
|
53
|
+
- For Ralph loop (Phase 4): Claude Code (`claude`) or Codex CLI (`codex`) in PATH
|
|
35
54
|
|
|
36
55
|
## Installation
|
|
37
56
|
|
|
@@ -44,9 +63,9 @@ npm install -g bmalph
|
|
|
44
63
|
```bash
|
|
45
64
|
cd my-project
|
|
46
65
|
bmalph init --name my-project
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
#
|
|
66
|
+
|
|
67
|
+
# To target a specific platform, add --platform (e.g. codex, cursor, windsurf)
|
|
68
|
+
# Without --platform, bmalph auto-detects or prompts interactively
|
|
50
69
|
```
|
|
51
70
|
|
|
52
71
|
## Workflow
|
|
@@ -58,17 +77,27 @@ cd my-project
|
|
|
58
77
|
bmalph init
|
|
59
78
|
```
|
|
60
79
|
|
|
80
|
+
**Platform resolution:** `--platform` flag > auto-detect from project markers > interactive prompt > default `claude-code`
|
|
81
|
+
|
|
61
82
|
This installs:
|
|
62
83
|
|
|
63
84
|
- `_bmad/` — BMAD agents and workflows
|
|
64
|
-
- `.ralph/` — Ralph loop, libs, templates
|
|
65
|
-
- `bmalph/` — State management (config.json)
|
|
66
|
-
- Updates
|
|
67
|
-
- Installs slash commands
|
|
85
|
+
- `.ralph/` — Ralph loop, libs, templates (drivers for claude-code and codex only)
|
|
86
|
+
- `bmalph/` — State management (config.json, stores selected platform)
|
|
87
|
+
- Updates the platform's instructions file with BMAD workflow instructions (e.g. `CLAUDE.md`, `AGENTS.md`, `.cursor/rules/bmad.mdc`)
|
|
88
|
+
- Installs slash commands for supported platforms (Claude Code: `.claude/commands/` directory; Codex: inline in `AGENTS.md`; other platforms: commands not installed)
|
|
89
|
+
|
|
90
|
+
### Migrating from standalone BMAD
|
|
91
|
+
|
|
92
|
+
If you already have BMAD installed (a `_bmad/` directory), `bmalph init` works as a migration path:
|
|
93
|
+
|
|
94
|
+
- `_bmad/` (framework files) will be replaced with the bmalph-managed version
|
|
95
|
+
- `_bmad-output/` (your planning artifacts: PRDs, architecture, stories) is not touched
|
|
96
|
+
- If you've customized framework files inside `_bmad/`, commit first so you can review changes with `git diff`
|
|
68
97
|
|
|
69
98
|
### Step 2: Plan with BMAD (Phases 1-3)
|
|
70
99
|
|
|
71
|
-
Work interactively in
|
|
100
|
+
Work interactively with BMAD agents in your AI coding assistant. On Claude Code, use the `/bmalph` slash command to see your current phase and available commands. On other platforms, ask the agent about BMAD phases or run `bmalph status` in terminal.
|
|
72
101
|
|
|
73
102
|
| Phase | Agent | Commands |
|
|
74
103
|
| ------------- | ---------------- | ------------------ |
|
|
@@ -124,7 +153,9 @@ Available in any phase for supporting tasks:
|
|
|
124
153
|
|
|
125
154
|
### Step 3: Implement with Ralph (Phase 4)
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
> **Note:** Ralph is only available on **full** tier platforms (Claude Code, OpenAI Codex). Instructions-only platforms (Cursor, Windsurf, Copilot, Aider) support Phases 1-3 only.
|
|
157
|
+
|
|
158
|
+
Use the `/bmalph-implement` slash command in Claude Code, or run the transition from the BMAD agents in Codex.
|
|
128
159
|
|
|
129
160
|
This transitions your BMAD artifacts into Ralph's format:
|
|
130
161
|
|
|
@@ -181,10 +212,11 @@ BMAD (add Epic 2) → /bmalph-implement → Ralph sees changes + picks up Epic 2
|
|
|
181
212
|
|
|
182
213
|
### init options
|
|
183
214
|
|
|
184
|
-
| Flag | Description
|
|
185
|
-
| -------------------------- |
|
|
186
|
-
| `-n, --name <name>` | Project name
|
|
187
|
-
| `-d, --description <desc>` | Project description
|
|
215
|
+
| Flag | Description | Default |
|
|
216
|
+
| -------------------------- | ---------------------------------------------------------------------------------- | -------------- |
|
|
217
|
+
| `-n, --name <name>` | Project name | directory name |
|
|
218
|
+
| `-d, --description <desc>` | Project description | (prompted) |
|
|
219
|
+
| `--platform <id>` | Target platform (`claude-code`, `codex`, `cursor`, `windsurf`, `copilot`, `aider`) | auto-detect |
|
|
188
220
|
|
|
189
221
|
### upgrade options
|
|
190
222
|
|
|
@@ -195,7 +227,13 @@ BMAD (add Epic 2) → /bmalph-implement → Ralph sees changes + picks up Epic 2
|
|
|
195
227
|
|
|
196
228
|
## Slash Commands
|
|
197
229
|
|
|
198
|
-
bmalph installs 47 BMAD slash commands.
|
|
230
|
+
bmalph installs 47 BMAD slash commands. Command delivery varies by platform:
|
|
231
|
+
|
|
232
|
+
- **Claude Code** — installed as files in `.claude/commands/` (invoke with `/command-name`)
|
|
233
|
+
- **OpenAI Codex** — inlined in `AGENTS.md` (reference agents by name)
|
|
234
|
+
- **Cursor, Windsurf, Copilot, Aider** — not delivered as slash commands; use agents by name in chat
|
|
235
|
+
|
|
236
|
+
Key commands (Claude Code syntax):
|
|
199
237
|
|
|
200
238
|
| Command | Description |
|
|
201
239
|
| ----------------------- | ----------------------------------- |
|
|
@@ -239,12 +277,15 @@ project/
|
|
|
239
277
|
│ ├── planning-artifacts/ # PRD, architecture, stories
|
|
240
278
|
│ ├── implementation-artifacts/ # Sprint plans (optional)
|
|
241
279
|
│ └── brainstorming/ # Brainstorm sessions (optional)
|
|
242
|
-
├── .ralph/ # Ralph autonomous loop
|
|
280
|
+
├── .ralph/ # Ralph autonomous loop (drivers for claude-code and codex only)
|
|
243
281
|
│ ├── ralph_loop.sh # Main loop script
|
|
244
282
|
│ ├── ralph_import.sh # Import requirements into Ralph
|
|
245
283
|
│ ├── ralph_monitor.sh # Monitor loop progress
|
|
246
284
|
│ ├── .ralphrc # Ralph configuration
|
|
247
285
|
│ ├── RALPH-REFERENCE.md # Ralph usage reference
|
|
286
|
+
│ ├── drivers/ # Platform driver scripts
|
|
287
|
+
│ │ ├── claude-code.sh # Claude Code driver (uses `claude`)
|
|
288
|
+
│ │ └── codex.sh # OpenAI Codex driver (uses `codex exec`)
|
|
248
289
|
│ ├── lib/ # Circuit breaker, response analyzer
|
|
249
290
|
│ ├── specs/ # Copied from _bmad-output during transition
|
|
250
291
|
│ ├── logs/ # Loop execution logs
|
|
@@ -254,16 +295,23 @@ project/
|
|
|
254
295
|
│ ├── @AGENT.md # Agent build instructions
|
|
255
296
|
│ └── @fix_plan.md # Generated task list (after /bmalph-implement)
|
|
256
297
|
├── bmalph/ # State management
|
|
257
|
-
│ ├── config.json # Project config (name, description)
|
|
298
|
+
│ ├── config.json # Project config (name, description, platform)
|
|
258
299
|
│ └── state/ # Phase tracking data
|
|
259
|
-
├── .claude/
|
|
260
|
-
│ └── commands/ # Slash commands
|
|
261
|
-
└──
|
|
300
|
+
├── .claude/ # Claude Code specific
|
|
301
|
+
│ └── commands/ # Slash commands (claude-code only)
|
|
302
|
+
└── <instructions file> # Varies by platform (see Supported Platforms)
|
|
262
303
|
```
|
|
263
304
|
|
|
305
|
+
The instructions file and command directory depend on the configured platform. See the [Supported Platforms](#supported-platforms) table for details.
|
|
306
|
+
|
|
264
307
|
## How Ralph Works
|
|
265
308
|
|
|
266
|
-
Ralph is a bash loop that spawns fresh
|
|
309
|
+
Ralph is a bash loop that spawns fresh AI coding sessions using a **platform driver** matching the configured platform:
|
|
310
|
+
|
|
311
|
+
- **Claude Code driver** — invokes `claude` with `--allowedTools` and session resume
|
|
312
|
+
- **Codex driver** — invokes `codex exec` with `--sandbox workspace-write`
|
|
313
|
+
|
|
314
|
+
Each iteration:
|
|
267
315
|
|
|
268
316
|
1. Pick the next unchecked story from `@fix_plan.md`
|
|
269
317
|
2. Implement with TDD (tests first, then code)
|
|
@@ -313,12 +361,14 @@ ls -la .ralph/
|
|
|
313
361
|
|
|
314
362
|
### Common Issues
|
|
315
363
|
|
|
316
|
-
| Scenario
|
|
317
|
-
|
|
|
318
|
-
| Commands fail before init
|
|
319
|
-
| Transition finds no stories
|
|
320
|
-
| Ralph stops mid-loop
|
|
321
|
-
| Doctor reports version drift
|
|
364
|
+
| Scenario | Solution |
|
|
365
|
+
| ----------------------------- | -------------------------------------------------------------- |
|
|
366
|
+
| Commands fail before init | Run `bmalph init` first |
|
|
367
|
+
| Transition finds no stories | Create stories in Phase 3 with `/create-epics-stories` |
|
|
368
|
+
| Ralph stops mid-loop | Circuit breaker detected stagnation. Check `.ralph/logs/` |
|
|
369
|
+
| Doctor reports version drift | Run `bmalph upgrade` to update bundled assets |
|
|
370
|
+
| Wrong platform detected | Re-run `bmalph init --platform <id>` with the correct platform |
|
|
371
|
+
| Ralph unavailable on platform | Ralph requires a full tier platform (claude-code or codex) |
|
|
322
372
|
|
|
323
373
|
### Reset Installation
|
|
324
374
|
|
|
@@ -326,8 +376,16 @@ If something goes wrong, you can manually reset:
|
|
|
326
376
|
|
|
327
377
|
```bash
|
|
328
378
|
# Remove bmalph directories (preserves your project code)
|
|
329
|
-
rm -rf _bmad .ralph bmalph
|
|
330
|
-
|
|
379
|
+
rm -rf _bmad .ralph bmalph
|
|
380
|
+
|
|
381
|
+
# Also remove platform-specific files:
|
|
382
|
+
# Claude Code: rm -rf .claude/commands/ and remove bmalph section from CLAUDE.md
|
|
383
|
+
# Codex: remove bmalph sections from AGENTS.md
|
|
384
|
+
# Cursor: rm .cursor/rules/bmad.mdc
|
|
385
|
+
# Windsurf: rm .windsurf/rules/bmad.md
|
|
386
|
+
# Copilot: remove bmalph sections from .github/copilot-instructions.md
|
|
387
|
+
# Aider: remove bmalph sections from CONVENTIONS.md
|
|
388
|
+
# See the Supported Platforms table for your platform's files.
|
|
331
389
|
|
|
332
390
|
# Reinitialize
|
|
333
391
|
bmalph init
|
|
@@ -338,12 +396,17 @@ bmalph init
|
|
|
338
396
|
### Initialize a new project
|
|
339
397
|
|
|
340
398
|
```bash
|
|
341
|
-
# Interactive mode (prompts for name/description)
|
|
399
|
+
# Interactive mode (prompts for name/description, auto-detects platform)
|
|
342
400
|
bmalph init
|
|
343
401
|
|
|
344
402
|
# Non-interactive mode
|
|
345
403
|
bmalph init --name my-app --description "My awesome app"
|
|
346
404
|
|
|
405
|
+
# Specify platform explicitly
|
|
406
|
+
bmalph init --name my-app --platform codex
|
|
407
|
+
bmalph init --name my-app --platform cursor
|
|
408
|
+
bmalph init --name my-app --platform windsurf
|
|
409
|
+
|
|
347
410
|
# Preview what would be created
|
|
348
411
|
bmalph init --dry-run
|
|
349
412
|
```
|
|
@@ -370,6 +433,8 @@ bmalph upgrade --dry-run
|
|
|
370
433
|
|
|
371
434
|
### After init: Next steps
|
|
372
435
|
|
|
436
|
+
**Claude Code:**
|
|
437
|
+
|
|
373
438
|
```bash
|
|
374
439
|
# 1. Open Claude Code in your project
|
|
375
440
|
claude
|
|
@@ -389,6 +454,22 @@ claude
|
|
|
389
454
|
bash .ralph/ralph_loop.sh
|
|
390
455
|
```
|
|
391
456
|
|
|
457
|
+
**Other platforms:**
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# 1. Open your project in your AI coding assistant
|
|
461
|
+
|
|
462
|
+
# 2. Ask the agent about BMAD phases to start planning
|
|
463
|
+
# Or check status from terminal: bmalph status
|
|
464
|
+
|
|
465
|
+
# 3. Reference BMAD agents by name (analyst, pm, architect)
|
|
466
|
+
# Follow phases: Analysis → Planning → Solutioning
|
|
467
|
+
|
|
468
|
+
# 4. For full tier platforms (Codex), transition via BMAD agents
|
|
469
|
+
# then start Ralph:
|
|
470
|
+
bash .ralph/ralph_loop.sh
|
|
471
|
+
```
|
|
472
|
+
|
|
392
473
|
## Contributing
|
|
393
474
|
|
|
394
475
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, test workflow, and commit guidelines.
|
package/bundled-versions.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -44,7 +44,7 @@ async function resolveAndValidateProjectDir() {
|
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
if (isEnoent(err)) {
|
|
47
|
-
throw new Error(`Project directory not found: ${dir}
|
|
47
|
+
throw new Error(`Project directory not found: ${dir}`, { cause: err });
|
|
48
48
|
}
|
|
49
49
|
throw err;
|
|
50
50
|
}
|
|
@@ -55,6 +55,7 @@ program
|
|
|
55
55
|
.description("Initialize bmalph in the current project")
|
|
56
56
|
.option("-n, --name <name>", "Project name")
|
|
57
57
|
.option("-d, --description <desc>", "Project description")
|
|
58
|
+
.option("--platform <id>", "Target platform (claude-code, codex, cursor, windsurf, copilot, aider)")
|
|
58
59
|
.option("--dry-run", "Preview changes without writing files")
|
|
59
60
|
.action(async (opts) => initCommand({ ...opts, projectDir: await resolveAndValidateProjectDir() }));
|
|
60
61
|
program
|
|
@@ -70,7 +71,7 @@ program
|
|
|
70
71
|
.action(async (opts) => doctorCommand({ ...opts, projectDir: await resolveAndValidateProjectDir() }));
|
|
71
72
|
program
|
|
72
73
|
.command("check-updates")
|
|
73
|
-
.description("Check if bundled BMAD
|
|
74
|
+
.description("Check if bundled BMAD version is up to date with upstream")
|
|
74
75
|
.option("--json", "Output as JSON")
|
|
75
76
|
.action(checkUpdatesCommand);
|
|
76
77
|
program
|
|
@@ -12,11 +12,9 @@ async function runCheckUpdates(options) {
|
|
|
12
12
|
}
|
|
13
13
|
const result = await checkUpstream(bundled);
|
|
14
14
|
if (options.json) {
|
|
15
|
-
const hasUpdates =
|
|
16
|
-
(result.ralph !== null && !result.ralph.isUpToDate);
|
|
15
|
+
const hasUpdates = result.bmad !== null && !result.bmad.isUpToDate;
|
|
17
16
|
const output = {
|
|
18
17
|
bmad: result.bmad,
|
|
19
|
-
ralph: result.ralph,
|
|
20
18
|
errors: result.errors,
|
|
21
19
|
hasUpdates,
|
|
22
20
|
};
|
|
@@ -24,14 +22,11 @@ async function runCheckUpdates(options) {
|
|
|
24
22
|
return;
|
|
25
23
|
}
|
|
26
24
|
// Human-readable output
|
|
27
|
-
let updatesCount = 0;
|
|
28
|
-
// BMAD status
|
|
29
25
|
if (result.bmad) {
|
|
30
26
|
if (result.bmad.isUpToDate) {
|
|
31
27
|
console.log(chalk.green(` ✓ BMAD-METHOD: up to date (${result.bmad.bundledSha})`));
|
|
32
28
|
}
|
|
33
29
|
else {
|
|
34
|
-
updatesCount++;
|
|
35
30
|
console.log(chalk.yellow(` ! BMAD-METHOD: updates available (${result.bmad.bundledSha} → ${result.bmad.latestSha})`));
|
|
36
31
|
console.log(chalk.dim(` → ${result.bmad.compareUrl}`));
|
|
37
32
|
}
|
|
@@ -41,30 +36,13 @@ async function runCheckUpdates(options) {
|
|
|
41
36
|
const reason = bmadError ? getErrorReason(bmadError) : "unknown error";
|
|
42
37
|
console.log(chalk.yellow(` ? BMAD-METHOD: Could not check (${reason})`));
|
|
43
38
|
}
|
|
44
|
-
// Ralph status
|
|
45
|
-
if (result.ralph) {
|
|
46
|
-
if (result.ralph.isUpToDate) {
|
|
47
|
-
console.log(chalk.green(` ✓ Ralph: up to date (${result.ralph.bundledSha})`));
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
updatesCount++;
|
|
51
|
-
console.log(chalk.yellow(` ! Ralph: updates available (${result.ralph.bundledSha} → ${result.ralph.latestSha})`));
|
|
52
|
-
console.log(chalk.dim(` → ${result.ralph.compareUrl}`));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
const ralphError = result.errors.find((e) => e.repo === "ralph");
|
|
57
|
-
const reason = ralphError ? getErrorReason(ralphError) : "unknown error";
|
|
58
|
-
console.log(chalk.yellow(` ? Ralph: Could not check (${reason})`));
|
|
59
|
-
}
|
|
60
39
|
// Summary
|
|
61
40
|
console.log();
|
|
62
|
-
if (
|
|
63
|
-
console.log(chalk.green("
|
|
41
|
+
if (result.bmad !== null && result.bmad.isUpToDate && result.errors.length === 0) {
|
|
42
|
+
console.log(chalk.green("Up to date."));
|
|
64
43
|
}
|
|
65
|
-
else if (
|
|
66
|
-
|
|
67
|
-
console.log(chalk.yellow(`${updatesCount} ${plural} updates available.`));
|
|
44
|
+
else if (result.bmad !== null && !result.bmad.isUpToDate) {
|
|
45
|
+
console.log(chalk.yellow("Updates available."));
|
|
68
46
|
}
|
|
69
47
|
}
|
|
70
48
|
function getErrorReason(error) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Platform } from "../platform/types.js";
|
|
1
2
|
/**
|
|
2
3
|
* Result of a single doctor check.
|
|
3
4
|
*/
|
|
@@ -32,8 +33,19 @@ interface DoctorResult {
|
|
|
32
33
|
}
|
|
33
34
|
export declare function runDoctor(options: DoctorOptions): Promise<DoctorResult>;
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
36
|
+
* Build the full check registry for a given platform.
|
|
37
|
+
* Core checks + platform doctor checks + trailing checks.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildCheckRegistry(platform: Platform): CheckDefinition[];
|
|
40
|
+
/**
|
|
41
|
+
* Static registry for backward compatibility with existing tests.
|
|
42
|
+
* Uses claude-code platform checks (slash-command + claude-md).
|
|
43
|
+
*
|
|
44
|
+
* Note: The check ids here ("slash-command", "claude-md") intentionally differ
|
|
45
|
+
* from the live buildCheckRegistry path which uses platform-provided ids
|
|
46
|
+
* ("instructions-file"). This is for test backward compatibility only.
|
|
47
|
+
*
|
|
48
|
+
* @deprecated Use `buildCheckRegistry(platform)` for platform-aware checks.
|
|
37
49
|
*/
|
|
38
50
|
export declare const CHECK_REGISTRY: CheckDefinition[];
|
|
39
51
|
export {};
|
package/dist/commands/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ import { checkUpstream, getSkipReason } from "../utils/github.js";
|
|
|
9
9
|
import { isEnoent, formatError, withErrorHandling } from "../utils/errors.js";
|
|
10
10
|
import { validateCircuitBreakerState, validateRalphSession, validateRalphApiStatus, } from "../utils/validate.js";
|
|
11
11
|
import { SESSION_AGE_WARNING_MS, API_USAGE_WARNING_PERCENT, CONFIG_FILE, RALPH_STATUS_FILE, } from "../utils/constants.js";
|
|
12
|
+
import { resolveProjectPlatform } from "../platform/resolve.js";
|
|
12
13
|
export async function doctorCommand(options) {
|
|
13
14
|
await withErrorHandling(async () => {
|
|
14
15
|
const { failed } = await runDoctor(options);
|
|
@@ -19,8 +20,12 @@ export async function doctorCommand(options) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function runDoctor(options) {
|
|
21
22
|
const projectDir = options.projectDir;
|
|
22
|
-
//
|
|
23
|
-
const
|
|
23
|
+
// Resolve platform for this project
|
|
24
|
+
const platform = await resolveProjectPlatform(projectDir);
|
|
25
|
+
// Build the check list: core checks + platform-specific checks
|
|
26
|
+
const checks = buildCheckRegistry(platform);
|
|
27
|
+
// Run all checks
|
|
28
|
+
const results = await Promise.all(checks.map((check) => check.run(projectDir)));
|
|
24
29
|
// Output
|
|
25
30
|
const passed = results.filter((r) => r.passed).length;
|
|
26
31
|
const failed = results.filter((r) => !r.passed).length;
|
|
@@ -71,7 +76,7 @@ async function checkBashAvailable() {
|
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
// =============================================================================
|
|
74
|
-
//
|
|
79
|
+
// Core check functions - platform-independent
|
|
75
80
|
// =============================================================================
|
|
76
81
|
async function checkNodeVersion(_projectDir) {
|
|
77
82
|
const major = parseInt(process.versions.node.split(".")[0]);
|
|
@@ -104,9 +109,6 @@ async function checkRalphLoop(projectDir) {
|
|
|
104
109
|
async function checkRalphLib(projectDir) {
|
|
105
110
|
return checkDir(join(projectDir, ".ralph/lib"), ".ralph/lib/ directory present", "Run: bmalph upgrade");
|
|
106
111
|
}
|
|
107
|
-
async function checkSlashCommand(projectDir) {
|
|
108
|
-
return checkFileExists(join(projectDir, ".claude/commands/bmalph.md"), ".claude/commands/bmalph.md present", "Run: bmalph init");
|
|
109
|
-
}
|
|
110
112
|
async function checkConfig(projectDir) {
|
|
111
113
|
const label = "bmalph/config.json exists and valid";
|
|
112
114
|
const hint = "Run: bmalph init";
|
|
@@ -135,12 +137,6 @@ async function checkDir(dirPath, label, hint) {
|
|
|
135
137
|
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
|
-
async function checkFileExists(filePath, label, hint) {
|
|
139
|
-
if (await exists(filePath)) {
|
|
140
|
-
return { label, passed: true };
|
|
141
|
-
}
|
|
142
|
-
return { label, passed: false, detail: "not found", hint };
|
|
143
|
-
}
|
|
144
140
|
async function checkFileHasContent(filePath, label, hint) {
|
|
145
141
|
try {
|
|
146
142
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -153,23 +149,6 @@ async function checkFileHasContent(filePath, label, hint) {
|
|
|
153
149
|
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
154
150
|
}
|
|
155
151
|
}
|
|
156
|
-
async function checkClaudeMd(projectDir) {
|
|
157
|
-
const label = "CLAUDE.md contains BMAD snippet";
|
|
158
|
-
const hint = "Run: bmalph init";
|
|
159
|
-
try {
|
|
160
|
-
const content = await readFile(join(projectDir, "CLAUDE.md"), "utf-8");
|
|
161
|
-
if (content.includes("BMAD-METHOD Integration")) {
|
|
162
|
-
return { label, passed: true };
|
|
163
|
-
}
|
|
164
|
-
return { label, passed: false, detail: "missing BMAD-METHOD Integration section", hint };
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
if (isEnoent(err)) {
|
|
168
|
-
return { label, passed: false, detail: "CLAUDE.md not found", hint };
|
|
169
|
-
}
|
|
170
|
-
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
152
|
async function checkGitignore(projectDir) {
|
|
174
153
|
const label = ".gitignore has required entries";
|
|
175
154
|
const required = [".ralph/logs/", "_bmad-output/"];
|
|
@@ -248,22 +227,21 @@ async function checkUpstreamVersions(projectDir) {
|
|
|
248
227
|
return { label, passed: true, detail: "not tracked (pre-1.2.0 install)" };
|
|
249
228
|
}
|
|
250
229
|
const bundled = getBundledVersions();
|
|
251
|
-
const { bmadCommit
|
|
230
|
+
const { bmadCommit } = config.upstreamVersions;
|
|
252
231
|
const bmadMatch = bmadCommit === bundled.bmadCommit;
|
|
253
|
-
|
|
254
|
-
if (bmadMatch && ralphMatch) {
|
|
232
|
+
if (bmadMatch) {
|
|
255
233
|
return {
|
|
256
234
|
label,
|
|
257
235
|
passed: true,
|
|
258
|
-
detail: `BMAD:${bmadCommit.slice(0, 8)}
|
|
236
|
+
detail: `BMAD:${bmadCommit.slice(0, 8)}`,
|
|
259
237
|
};
|
|
260
238
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
239
|
+
return {
|
|
240
|
+
label,
|
|
241
|
+
passed: false,
|
|
242
|
+
detail: `outdated: BMAD:${bmadCommit.slice(0, 8)}→${bundled.bmadCommit.slice(0, 8)}`,
|
|
243
|
+
hint,
|
|
244
|
+
};
|
|
267
245
|
}
|
|
268
246
|
catch (err) {
|
|
269
247
|
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
@@ -397,41 +375,39 @@ async function checkUpstreamGitHubStatus(_projectDir) {
|
|
|
397
375
|
try {
|
|
398
376
|
const bundled = getBundledVersions();
|
|
399
377
|
const result = await checkUpstream(bundled);
|
|
400
|
-
// Check if
|
|
401
|
-
if (result.bmad === null
|
|
378
|
+
// Check if request failed
|
|
379
|
+
if (result.bmad === null) {
|
|
402
380
|
const reason = getSkipReason(result.errors);
|
|
403
381
|
return { label, passed: true, detail: `skipped: ${reason}` };
|
|
404
382
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
if (result.ralph) {
|
|
411
|
-
statuses.push(`Ralph: ${result.ralph.isUpToDate ? "up to date" : "behind"}`);
|
|
412
|
-
}
|
|
413
|
-
return { label, passed: true, detail: statuses.join(", ") };
|
|
383
|
+
return {
|
|
384
|
+
label,
|
|
385
|
+
passed: true,
|
|
386
|
+
detail: `BMAD: ${result.bmad.isUpToDate ? "up to date" : "behind"}`,
|
|
387
|
+
};
|
|
414
388
|
}
|
|
415
389
|
catch (err) {
|
|
416
390
|
return { label, passed: true, detail: `skipped: ${formatError(err)}` };
|
|
417
391
|
}
|
|
418
392
|
}
|
|
419
393
|
// =============================================================================
|
|
420
|
-
// Check Registry -
|
|
394
|
+
// Check Registry - core checks + platform-specific checks
|
|
421
395
|
// =============================================================================
|
|
422
396
|
/**
|
|
423
|
-
*
|
|
424
|
-
* Each check has a unique ID and a run function that takes a project directory.
|
|
397
|
+
* Core checks that apply to every platform.
|
|
425
398
|
*/
|
|
426
|
-
|
|
399
|
+
const CORE_CHECKS = [
|
|
427
400
|
{ id: "node-version", run: checkNodeVersion },
|
|
428
401
|
{ id: "bash-available", run: checkBash },
|
|
429
402
|
{ id: "config-valid", run: checkConfig },
|
|
430
403
|
{ id: "bmad-dir", run: checkBmadDir },
|
|
431
404
|
{ id: "ralph-loop", run: checkRalphLoop },
|
|
432
405
|
{ id: "ralph-lib", run: checkRalphLib },
|
|
433
|
-
|
|
434
|
-
|
|
406
|
+
];
|
|
407
|
+
/**
|
|
408
|
+
* Checks that run after platform-specific checks.
|
|
409
|
+
*/
|
|
410
|
+
const TRAILING_CHECKS = [
|
|
435
411
|
{ id: "gitignore", run: checkGitignore },
|
|
436
412
|
{ id: "version-marker", run: checkVersionMarker },
|
|
437
413
|
{ id: "upstream-versions", run: checkUpstreamVersions },
|
|
@@ -440,3 +416,70 @@ export const CHECK_REGISTRY = [
|
|
|
440
416
|
{ id: "api-calls", run: checkApiCalls },
|
|
441
417
|
{ id: "upstream-github", run: checkUpstreamGitHubStatus },
|
|
442
418
|
];
|
|
419
|
+
/**
|
|
420
|
+
* Build the full check registry for a given platform.
|
|
421
|
+
* Core checks + platform doctor checks + trailing checks.
|
|
422
|
+
*/
|
|
423
|
+
export function buildCheckRegistry(platform) {
|
|
424
|
+
const platformChecks = platform.getDoctorChecks().map((pc) => ({
|
|
425
|
+
id: pc.id,
|
|
426
|
+
run: async (projectDir) => {
|
|
427
|
+
const result = await pc.check(projectDir);
|
|
428
|
+
return {
|
|
429
|
+
label: pc.label,
|
|
430
|
+
passed: result.passed,
|
|
431
|
+
detail: result.detail,
|
|
432
|
+
hint: result.hint,
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
}));
|
|
436
|
+
return [...CORE_CHECKS, ...platformChecks, ...TRAILING_CHECKS];
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Static registry for backward compatibility with existing tests.
|
|
440
|
+
* Uses claude-code platform checks (slash-command + claude-md).
|
|
441
|
+
*
|
|
442
|
+
* Note: The check ids here ("slash-command", "claude-md") intentionally differ
|
|
443
|
+
* from the live buildCheckRegistry path which uses platform-provided ids
|
|
444
|
+
* ("instructions-file"). This is for test backward compatibility only.
|
|
445
|
+
*
|
|
446
|
+
* @deprecated Use `buildCheckRegistry(platform)` for platform-aware checks.
|
|
447
|
+
*/
|
|
448
|
+
export const CHECK_REGISTRY = (() => {
|
|
449
|
+
// Inline claude-code platform checks to avoid async import at module level
|
|
450
|
+
const slashCommandCheck = {
|
|
451
|
+
id: "slash-command",
|
|
452
|
+
run: async (projectDir) => {
|
|
453
|
+
if (await exists(join(projectDir, ".claude/commands/bmalph.md"))) {
|
|
454
|
+
return { label: ".claude/commands/bmalph.md present", passed: true };
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
label: ".claude/commands/bmalph.md present",
|
|
458
|
+
passed: false,
|
|
459
|
+
detail: "not found",
|
|
460
|
+
hint: "Run: bmalph init",
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
const claudeMdCheck = {
|
|
465
|
+
id: "claude-md",
|
|
466
|
+
run: async (projectDir) => {
|
|
467
|
+
const label = "CLAUDE.md contains BMAD snippet";
|
|
468
|
+
const hint = "Run: bmalph init";
|
|
469
|
+
try {
|
|
470
|
+
const content = await readFile(join(projectDir, "CLAUDE.md"), "utf-8");
|
|
471
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
472
|
+
return { label, passed: true };
|
|
473
|
+
}
|
|
474
|
+
return { label, passed: false, detail: "missing BMAD-METHOD Integration section", hint };
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
if (isEnoent(err)) {
|
|
478
|
+
return { label, passed: false, detail: "CLAUDE.md not found", hint };
|
|
479
|
+
}
|
|
480
|
+
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
return [...CORE_CHECKS, slashCommandCheck, claudeMdCheck, ...TRAILING_CHECKS];
|
|
485
|
+
})();
|