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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { runTransition } from "../transition/orchestration.js";
|
|
5
|
+
import { withErrorHandling } from "../utils/errors.js";
|
|
6
|
+
import { resolveProjectPlatform } from "../platform/resolve.js";
|
|
7
|
+
export async function implementCommand(options) {
|
|
8
|
+
await withErrorHandling(() => runImplement(options));
|
|
9
|
+
}
|
|
10
|
+
async function runImplement(options) {
|
|
11
|
+
const { projectDir, force } = options;
|
|
12
|
+
// Re-run protection: warn if implement was already run
|
|
13
|
+
try {
|
|
14
|
+
await access(join(projectDir, ".ralph/@fix_plan.md"));
|
|
15
|
+
if (!force) {
|
|
16
|
+
console.log(chalk.yellow("Warning: bmalph implement has already been run."));
|
|
17
|
+
console.log("Re-running will overwrite PROMPT.md, PROJECT_CONTEXT.md, @AGENT.md, and SPECS_INDEX.md.");
|
|
18
|
+
console.log("Fix plan progress will be preserved.\n");
|
|
19
|
+
console.log(`Use ${chalk.bold("--force")} to proceed anyway.`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// fix_plan doesn't exist — first run, proceed
|
|
26
|
+
}
|
|
27
|
+
const platform = await resolveProjectPlatform(projectDir);
|
|
28
|
+
const result = await runTransition(projectDir, { force });
|
|
29
|
+
// Print preflight issues with severity icons
|
|
30
|
+
if (result.preflightIssues && result.preflightIssues.length > 0) {
|
|
31
|
+
console.log(chalk.bold("\nPre-flight checks\n"));
|
|
32
|
+
for (const issue of result.preflightIssues) {
|
|
33
|
+
console.log(` ${severityIcon(issue)} ${issue.message}`);
|
|
34
|
+
if (issue.suggestion) {
|
|
35
|
+
console.log(chalk.dim(` ${issue.suggestion}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
console.log("");
|
|
39
|
+
}
|
|
40
|
+
// Print warnings
|
|
41
|
+
if (result.warnings.length > 0) {
|
|
42
|
+
for (const warning of result.warnings) {
|
|
43
|
+
console.log(chalk.yellow(` ! ${warning}`));
|
|
44
|
+
}
|
|
45
|
+
console.log("");
|
|
46
|
+
}
|
|
47
|
+
// Generated files summary
|
|
48
|
+
if (result.generatedFiles.length > 0) {
|
|
49
|
+
console.log(chalk.bold("\nGenerated files\n"));
|
|
50
|
+
for (const file of result.generatedFiles) {
|
|
51
|
+
const icon = file.action === "created" ? chalk.green("+") : chalk.cyan("~");
|
|
52
|
+
console.log(` ${icon} ${file.path}`);
|
|
53
|
+
}
|
|
54
|
+
console.log("");
|
|
55
|
+
}
|
|
56
|
+
// Summary
|
|
57
|
+
const preserved = result.fixPlanPreserved ? chalk.dim(" (progress preserved)") : "";
|
|
58
|
+
console.log(chalk.green(`Transition complete: ${result.storiesCount} stories`) + preserved);
|
|
59
|
+
if (result.warnings.length > 0) {
|
|
60
|
+
console.log(chalk.yellow(` ${result.warnings.length} warning(s)`));
|
|
61
|
+
}
|
|
62
|
+
// Driver instructions
|
|
63
|
+
console.log("");
|
|
64
|
+
if (platform.tier === "full") {
|
|
65
|
+
console.log(`Start the Ralph loop:\n`);
|
|
66
|
+
console.log(` bash .ralph/drivers/${platform.id}.sh`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`Ralph requires a full-tier platform (claude-code or codex). ` +
|
|
70
|
+
`Current platform: ${platform.displayName}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function severityIcon(issue) {
|
|
74
|
+
switch (issue.severity) {
|
|
75
|
+
case "error":
|
|
76
|
+
return chalk.red("\u2717");
|
|
77
|
+
case "warning":
|
|
78
|
+
return chalk.yellow("!");
|
|
79
|
+
case "info":
|
|
80
|
+
return chalk.dim("i");
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,13 +1,60 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import { writeConfig } from "../utils/config.js";
|
|
4
|
-
import { installProject,
|
|
4
|
+
import { installProject, mergeInstructionsFile, isInitialized, hasExistingBmadDir, previewInstall, getBundledVersions, } from "../installer.js";
|
|
5
5
|
import { formatDryRunSummary } from "../utils/dryrun.js";
|
|
6
6
|
import { validateProjectName } from "../utils/validate.js";
|
|
7
7
|
import { withErrorHandling } from "../utils/errors.js";
|
|
8
|
+
import { isPlatformId, getPlatform } from "../platform/registry.js";
|
|
9
|
+
import { detectPlatform } from "../platform/detect.js";
|
|
8
10
|
export async function initCommand(options) {
|
|
9
11
|
await withErrorHandling(() => runInit(options));
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve which platform to use:
|
|
15
|
+
* 1. Explicit --platform flag
|
|
16
|
+
* 2. Auto-detect from filesystem markers
|
|
17
|
+
* 3. Interactive prompt (if TTY)
|
|
18
|
+
* 4. Default to claude-code (non-interactive)
|
|
19
|
+
*/
|
|
20
|
+
async function resolvePlatform(projectDir, explicit) {
|
|
21
|
+
// 1. Explicit flag
|
|
22
|
+
if (explicit) {
|
|
23
|
+
if (!isPlatformId(explicit)) {
|
|
24
|
+
throw new Error(`Unknown platform: "${explicit}". ` +
|
|
25
|
+
`Valid platforms: claude-code, codex, cursor, windsurf, copilot, aider`);
|
|
26
|
+
}
|
|
27
|
+
return getPlatform(explicit);
|
|
28
|
+
}
|
|
29
|
+
// 2. Auto-detect
|
|
30
|
+
const detection = await detectPlatform(projectDir);
|
|
31
|
+
if (detection.detected) {
|
|
32
|
+
return getPlatform(detection.detected);
|
|
33
|
+
}
|
|
34
|
+
// 3. Interactive prompt if multiple candidates or none detected
|
|
35
|
+
if (process.stdin.isTTY) {
|
|
36
|
+
const choices = [
|
|
37
|
+
{ name: "Claude Code", value: "claude-code" },
|
|
38
|
+
{ name: "OpenAI Codex", value: "codex" },
|
|
39
|
+
{ name: "Cursor", value: "cursor" },
|
|
40
|
+
{ name: "Windsurf", value: "windsurf" },
|
|
41
|
+
{ name: "GitHub Copilot", value: "copilot" },
|
|
42
|
+
{ name: "Aider", value: "aider" },
|
|
43
|
+
];
|
|
44
|
+
const { platformId } = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: "list",
|
|
47
|
+
name: "platformId",
|
|
48
|
+
message: "Which platform are you using?",
|
|
49
|
+
choices,
|
|
50
|
+
default: detection.candidates[0] ?? "claude-code",
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
return getPlatform(platformId);
|
|
54
|
+
}
|
|
55
|
+
// 4. Non-interactive default
|
|
56
|
+
return getPlatform("claude-code");
|
|
57
|
+
}
|
|
11
58
|
async function runInit(options) {
|
|
12
59
|
const projectDir = options.projectDir;
|
|
13
60
|
if (await isInitialized(projectDir)) {
|
|
@@ -15,9 +62,16 @@ async function runInit(options) {
|
|
|
15
62
|
console.log("Use 'bmalph upgrade' to update bundled assets to the latest version.");
|
|
16
63
|
return;
|
|
17
64
|
}
|
|
65
|
+
if (await hasExistingBmadDir(projectDir)) {
|
|
66
|
+
console.log(chalk.cyan("Existing BMAD installation detected."));
|
|
67
|
+
console.log("Framework files in _bmad/ will be replaced with the managed version.");
|
|
68
|
+
console.log("Planning artifacts in _bmad-output/ will not be modified.\n");
|
|
69
|
+
}
|
|
70
|
+
// Resolve platform
|
|
71
|
+
const platform = await resolvePlatform(projectDir, options.platform);
|
|
18
72
|
// Handle dry-run mode
|
|
19
73
|
if (options.dryRun) {
|
|
20
|
-
const preview = await previewInstall(projectDir);
|
|
74
|
+
const preview = await previewInstall(projectDir, platform);
|
|
21
75
|
const actions = [
|
|
22
76
|
...preview.wouldCreate.map((p) => ({ type: "create", path: p })),
|
|
23
77
|
...preview.wouldModify.map((p) => ({ type: "modify", path: p })),
|
|
@@ -64,18 +118,19 @@ async function runInit(options) {
|
|
|
64
118
|
throw new Error("Project name cannot be empty");
|
|
65
119
|
}
|
|
66
120
|
const validatedName = validateProjectName(name);
|
|
67
|
-
console.log(chalk.blue(
|
|
68
|
-
await installProject(projectDir);
|
|
121
|
+
console.log(chalk.blue(`\nInstalling BMAD + Ralph for ${platform.displayName}...`));
|
|
122
|
+
await installProject(projectDir, platform);
|
|
69
123
|
const bundledVersions = getBundledVersions();
|
|
70
124
|
const config = {
|
|
71
125
|
name: validatedName,
|
|
72
126
|
description: description ?? "",
|
|
73
127
|
createdAt: new Date().toISOString(),
|
|
128
|
+
platform: platform.id,
|
|
74
129
|
upstreamVersions: bundledVersions,
|
|
75
130
|
};
|
|
76
131
|
try {
|
|
77
132
|
await writeConfig(projectDir, config);
|
|
78
|
-
await
|
|
133
|
+
await mergeInstructionsFile(projectDir, platform);
|
|
79
134
|
}
|
|
80
135
|
catch (err) {
|
|
81
136
|
throw new Error(`Partial installation: files were copied but configuration failed. ` +
|
|
@@ -83,11 +138,23 @@ async function runInit(options) {
|
|
|
83
138
|
}
|
|
84
139
|
console.log(chalk.green("\nbmalph initialized successfully!"));
|
|
85
140
|
console.log(`\n Project: ${chalk.bold(config.name)}`);
|
|
141
|
+
console.log(` Platform: ${chalk.bold(platform.displayName)}`);
|
|
86
142
|
console.log(`\nInstalled:`);
|
|
87
143
|
console.log(` _bmad/ BMAD agents and workflows`);
|
|
88
144
|
console.log(` .ralph/ Ralph loop and templates`);
|
|
89
|
-
|
|
145
|
+
if (platform.commandDelivery.kind === "directory") {
|
|
146
|
+
console.log(` ${platform.commandDelivery.dir}/ Slash commands`);
|
|
147
|
+
}
|
|
90
148
|
console.log(` bmalph/ State management`);
|
|
149
|
+
// Platform-specific next step guidance
|
|
91
150
|
console.log(`\nNext step:`);
|
|
92
|
-
|
|
151
|
+
if (platform.id === "claude-code") {
|
|
152
|
+
console.log(` Use ${chalk.cyan("/bmalph")} in Claude Code to see your current phase and commands.`);
|
|
153
|
+
}
|
|
154
|
+
else if (platform.id === "codex") {
|
|
155
|
+
console.log(` Ask Codex to ${chalk.cyan("run the BMAD master agent")} to navigate phases.`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(` Ask your AI assistant to ${chalk.cyan("use the BMAD agents")} defined in ${chalk.cyan(platform.instructionsFile)}.`);
|
|
159
|
+
}
|
|
93
160
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import { isInitialized } from "../installer.js";
|
|
4
|
+
import { buildResetPlan, executeResetPlan, planToDryRunActions } from "../reset.js";
|
|
5
|
+
import { formatDryRunSummary } from "../utils/dryrun.js";
|
|
6
|
+
import { withErrorHandling } from "../utils/errors.js";
|
|
7
|
+
import { resolveProjectPlatform } from "../platform/resolve.js";
|
|
8
|
+
export async function resetCommand(options) {
|
|
9
|
+
await withErrorHandling(() => runReset(options));
|
|
10
|
+
}
|
|
11
|
+
async function runReset(options) {
|
|
12
|
+
const projectDir = options.projectDir;
|
|
13
|
+
if (!(await isInitialized(projectDir))) {
|
|
14
|
+
console.log(chalk.red("bmalph is not initialized. Nothing to reset."));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const platform = await resolveProjectPlatform(projectDir);
|
|
18
|
+
const plan = await buildResetPlan(projectDir, platform);
|
|
19
|
+
// Preview
|
|
20
|
+
const actions = planToDryRunActions(plan);
|
|
21
|
+
if (actions.length === 0) {
|
|
22
|
+
console.log(chalk.dim("Nothing to reset."));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Dry-run mode
|
|
26
|
+
if (options.dryRun) {
|
|
27
|
+
console.log(formatDryRunSummary(actions));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Show preview before confirmation
|
|
31
|
+
for (const action of actions) {
|
|
32
|
+
if (action.type === "delete") {
|
|
33
|
+
console.log(` ${chalk.red("delete")} ${action.path}`);
|
|
34
|
+
}
|
|
35
|
+
else if (action.type === "modify") {
|
|
36
|
+
console.log(` ${chalk.yellow("modify")} ${action.path}`);
|
|
37
|
+
}
|
|
38
|
+
else if (action.type === "warn") {
|
|
39
|
+
console.log(` ${chalk.yellow("warn")} ${action.path}${action.reason ? ` (${action.reason})` : ""}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Confirm unless --force or non-interactive
|
|
43
|
+
if (!options.force) {
|
|
44
|
+
if (!process.stdin.isTTY) {
|
|
45
|
+
throw new Error("Non-interactive mode requires --force flag for reset");
|
|
46
|
+
}
|
|
47
|
+
const { confirm } = await inquirer.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: "confirm",
|
|
50
|
+
name: "confirm",
|
|
51
|
+
message: "This will remove all bmalph files from the project. Continue?",
|
|
52
|
+
default: false,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
if (!confirm) {
|
|
56
|
+
console.log("Aborted.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Execute
|
|
61
|
+
console.log("\nResetting...");
|
|
62
|
+
await executeResetPlan(projectDir, plan);
|
|
63
|
+
// Summary
|
|
64
|
+
console.log(chalk.green("\nReset complete."));
|
|
65
|
+
if (plan.directories.length > 0) {
|
|
66
|
+
console.log(chalk.dim("\nRemoved:"));
|
|
67
|
+
for (const dir of plan.directories) {
|
|
68
|
+
console.log(` ${dir}/`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (plan.commandFiles.length > 0) {
|
|
72
|
+
console.log(chalk.dim("\nRemoved commands:"));
|
|
73
|
+
for (const file of plan.commandFiles) {
|
|
74
|
+
console.log(` ${file}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Show warnings
|
|
78
|
+
for (const warning of plan.warnings) {
|
|
79
|
+
console.log(chalk.yellow(`\nNote: ${warning.path} ${warning.message}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/commands/status.js
CHANGED
|
@@ -2,6 +2,8 @@ import chalk from "chalk";
|
|
|
2
2
|
import { readConfig } from "../utils/config.js";
|
|
3
3
|
import { readState, readRalphStatus, getPhaseLabel, getPhaseInfo } from "../utils/state.js";
|
|
4
4
|
import { withErrorHandling } from "../utils/errors.js";
|
|
5
|
+
import { resolveProjectPlatform } from "../platform/resolve.js";
|
|
6
|
+
import { scanProjectArtifacts } from "../transition/artifact-scan.js";
|
|
5
7
|
export async function statusCommand(options) {
|
|
6
8
|
await withErrorHandling(() => runStatus(options));
|
|
7
9
|
}
|
|
@@ -15,17 +17,32 @@ export async function runStatus(options) {
|
|
|
15
17
|
}
|
|
16
18
|
// Read current state
|
|
17
19
|
const state = await readState(projectDir);
|
|
18
|
-
const
|
|
20
|
+
const storedPhase = state?.currentPhase ?? 1;
|
|
19
21
|
const status = state?.status ?? "planning";
|
|
20
|
-
const phaseName = getPhaseLabel(phase);
|
|
21
|
-
const phaseInfo = getPhaseInfo(phase);
|
|
22
22
|
// Read Ralph status if in implementation phase
|
|
23
23
|
let ralphStatus = null;
|
|
24
|
-
if (
|
|
24
|
+
if (storedPhase === 4) {
|
|
25
25
|
ralphStatus = await readRalphStatus(projectDir);
|
|
26
26
|
}
|
|
27
|
-
//
|
|
28
|
-
|
|
27
|
+
// Scan artifacts for phases 1-3 to detect actual progress
|
|
28
|
+
let artifactScan = null;
|
|
29
|
+
let phase = storedPhase;
|
|
30
|
+
let phaseDetected = false;
|
|
31
|
+
if (phase < 4) {
|
|
32
|
+
artifactScan = await scanProjectArtifacts(projectDir);
|
|
33
|
+
if (artifactScan && artifactScan.detectedPhase > phase) {
|
|
34
|
+
phase = artifactScan.detectedPhase;
|
|
35
|
+
phaseDetected = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const phaseName = getPhaseLabel(phase);
|
|
39
|
+
const phaseInfo = getPhaseInfo(phase);
|
|
40
|
+
// Resolve platform for next action hints
|
|
41
|
+
const platform = await resolveProjectPlatform(projectDir);
|
|
42
|
+
// Determine next action — use artifact-based suggestion when available
|
|
43
|
+
const nextAction = artifactScan && phaseDetected
|
|
44
|
+
? artifactScan.nextAction
|
|
45
|
+
: getNextAction(phase, status, ralphStatus, platform);
|
|
29
46
|
if (options.json) {
|
|
30
47
|
const output = {
|
|
31
48
|
phase,
|
|
@@ -40,6 +57,14 @@ export async function runStatus(options) {
|
|
|
40
57
|
tasksTotal: ralphStatus.tasksTotal,
|
|
41
58
|
};
|
|
42
59
|
}
|
|
60
|
+
if (artifactScan) {
|
|
61
|
+
output.artifacts = {
|
|
62
|
+
directory: artifactScan.directory,
|
|
63
|
+
found: artifactScan.found,
|
|
64
|
+
detectedPhase: artifactScan.detectedPhase,
|
|
65
|
+
missing: artifactScan.missing,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
43
68
|
if (nextAction) {
|
|
44
69
|
output.nextAction = nextAction;
|
|
45
70
|
}
|
|
@@ -48,9 +73,18 @@ export async function runStatus(options) {
|
|
|
48
73
|
}
|
|
49
74
|
// Human-readable output
|
|
50
75
|
console.log(chalk.bold("bmalph status\n"));
|
|
51
|
-
|
|
76
|
+
const phaseLabel = phaseDetected
|
|
77
|
+
? `${phase} - ${phaseName} (detected from artifacts)`
|
|
78
|
+
: `${phase} - ${phaseName}`;
|
|
79
|
+
console.log(` ${chalk.cyan("Phase:")} ${phaseLabel}`);
|
|
52
80
|
console.log(` ${chalk.cyan("Agent:")} ${phaseInfo.agent}`);
|
|
53
81
|
console.log(` ${chalk.cyan("Status:")} ${formatStatus(status)}`);
|
|
82
|
+
// Show artifact checklist for phases 1-3
|
|
83
|
+
if (artifactScan) {
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(chalk.bold(` Artifacts (${artifactScan.directory})`));
|
|
86
|
+
printArtifactChecklist(artifactScan);
|
|
87
|
+
}
|
|
54
88
|
if (phase === 4 && ralphStatus) {
|
|
55
89
|
console.log("");
|
|
56
90
|
console.log(chalk.bold(" Ralph Loop"));
|
|
@@ -68,6 +102,45 @@ export async function runStatus(options) {
|
|
|
68
102
|
console.log(` ${chalk.cyan("Next:")} ${nextAction}`);
|
|
69
103
|
}
|
|
70
104
|
}
|
|
105
|
+
const ARTIFACT_DEFINITIONS = [
|
|
106
|
+
{ phase: 1, name: "Product Brief", required: false },
|
|
107
|
+
{ phase: 1, name: "Market Research", required: false },
|
|
108
|
+
{ phase: 1, name: "Domain Research", required: false },
|
|
109
|
+
{ phase: 1, name: "Technical Research", required: false },
|
|
110
|
+
{ phase: 2, name: "PRD", required: true },
|
|
111
|
+
{ phase: 2, name: "UX Design", required: false },
|
|
112
|
+
{ phase: 3, name: "Architecture", required: true },
|
|
113
|
+
{ phase: 3, name: "Epics & Stories", required: true },
|
|
114
|
+
{ phase: 3, name: "Readiness Report", required: true },
|
|
115
|
+
];
|
|
116
|
+
const PHASE_LABELS = {
|
|
117
|
+
1: "Phase 1 - Analysis",
|
|
118
|
+
2: "Phase 2 - Planning",
|
|
119
|
+
3: "Phase 3 - Solutioning",
|
|
120
|
+
};
|
|
121
|
+
function printArtifactChecklist(scan) {
|
|
122
|
+
const foundByName = new Map();
|
|
123
|
+
for (const artifacts of [scan.phases[1], scan.phases[2], scan.phases[3]]) {
|
|
124
|
+
for (const artifact of artifacts) {
|
|
125
|
+
foundByName.set(artifact.name, artifact);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let currentPhase = 0;
|
|
129
|
+
for (const def of ARTIFACT_DEFINITIONS) {
|
|
130
|
+
if (def.phase !== currentPhase) {
|
|
131
|
+
currentPhase = def.phase;
|
|
132
|
+
console.log(` ${PHASE_LABELS[currentPhase]}`);
|
|
133
|
+
}
|
|
134
|
+
const found = foundByName.get(def.name);
|
|
135
|
+
if (found) {
|
|
136
|
+
console.log(` ${chalk.green("*")} ${def.name} (${found.filename})`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const suffix = def.required ? " (required)" : "";
|
|
140
|
+
console.log(` ${chalk.dim("-")} ${def.name}${suffix}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
71
144
|
function formatStatus(status) {
|
|
72
145
|
switch (status) {
|
|
73
146
|
case "planning":
|
|
@@ -94,7 +167,7 @@ function formatRalphStatus(status) {
|
|
|
94
167
|
return status;
|
|
95
168
|
}
|
|
96
169
|
}
|
|
97
|
-
function getNextAction(phase, status, ralphStatus) {
|
|
170
|
+
function getNextAction(phase, status, ralphStatus, platform) {
|
|
98
171
|
if (status === "completed") {
|
|
99
172
|
return null;
|
|
100
173
|
}
|
|
@@ -104,10 +177,13 @@ function getNextAction(phase, status, ralphStatus) {
|
|
|
104
177
|
case 2:
|
|
105
178
|
return "Run /pm to create PRD";
|
|
106
179
|
case 3:
|
|
107
|
-
return "Run
|
|
180
|
+
return "Run: bmalph implement";
|
|
108
181
|
case 4:
|
|
109
182
|
if (!ralphStatus || ralphStatus.status === "not_started") {
|
|
110
|
-
|
|
183
|
+
if (platform.tier === "full") {
|
|
184
|
+
return `Start Ralph loop with: bash .ralph/drivers/${platform.id}.sh`;
|
|
185
|
+
}
|
|
186
|
+
return "Ralph requires a full-tier platform (Claude Code or Codex)";
|
|
111
187
|
}
|
|
112
188
|
if (ralphStatus.status === "blocked") {
|
|
113
189
|
return "Review Ralph logs: bmalph doctor";
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
|
-
import { isInitialized, copyBundledAssets,
|
|
3
|
+
import { isInitialized, copyBundledAssets, mergeInstructionsFile, previewUpgrade, getBundledVersions, } from "../installer.js";
|
|
4
4
|
import { readConfig, writeConfig } from "../utils/config.js";
|
|
5
5
|
import { formatDryRunSummary } from "../utils/dryrun.js";
|
|
6
6
|
import { withErrorHandling } from "../utils/errors.js";
|
|
7
|
+
import { resolveProjectPlatform } from "../platform/resolve.js";
|
|
7
8
|
export async function upgradeCommand(options) {
|
|
8
9
|
await withErrorHandling(() => runUpgrade(options));
|
|
9
10
|
}
|
|
@@ -13,9 +14,11 @@ async function runUpgrade(options) {
|
|
|
13
14
|
console.log(chalk.red("bmalph is not initialized. Run 'bmalph init' first."));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
17
|
+
// Read platform from existing config
|
|
18
|
+
const platform = await resolveProjectPlatform(projectDir);
|
|
16
19
|
// Handle dry-run mode
|
|
17
20
|
if (options.dryRun) {
|
|
18
|
-
const preview = await previewUpgrade(projectDir);
|
|
21
|
+
const preview = await previewUpgrade(projectDir, platform);
|
|
19
22
|
const actions = [
|
|
20
23
|
...preview.wouldUpdate.map((p) => ({ type: "modify", path: p })),
|
|
21
24
|
...preview.wouldCreate.map((p) => ({ type: "create", path: p })),
|
|
@@ -41,9 +44,9 @@ async function runUpgrade(options) {
|
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
console.log(chalk.blue(
|
|
45
|
-
const result = await copyBundledAssets(projectDir);
|
|
46
|
-
await
|
|
47
|
+
console.log(chalk.blue(`Upgrading bundled assets for ${platform.displayName}...`));
|
|
48
|
+
const result = await copyBundledAssets(projectDir, platform);
|
|
49
|
+
await mergeInstructionsFile(projectDir, platform);
|
|
47
50
|
// Update upstreamVersions in config to match bundled versions
|
|
48
51
|
const config = await readConfig(projectDir);
|
|
49
52
|
if (config) {
|
package/dist/installer.d.ts
CHANGED
|
@@ -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
|
|
28
|
-
export declare function
|
|
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>;
|