Skip to content

Commit 2b3321e

Browse files
committed
fix: improve installer validation, transition matching, and state atomicity
- Add source directory validation in copyBundledAssets - Replace loose epic file matching with proper regex pattern - Add atomic writes (write-to-temp-then-rename) for state files - Add --verbose flag with debug logging utility - Add version marker injection to ralph_loop.sh
1 parent 448912a commit 2b3321e

6 files changed

Lines changed: 60 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bmalph",
3-
"version": "0.8.0",
3+
"version": "0.8.1",
44
"description": "Unified AI Development Framework - BMAD phases with Ralph execution loop for Claude Code",
55
"type": "module",
66
"bin": {

src/cli.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ import { resetCommand } from "./commands/reset.js";
55
import { upgradeCommand } from "./commands/upgrade.js";
66
import { statusCommand } from "./commands/status.js";
77
import { doctorCommand } from "./commands/doctor.js";
8+
import { setVerbose } from "./utils/logger.js";
89

910
const program = new Command();
1011

1112
program
1213
.name("bmalph")
1314
.description("BMAD-METHOD + Ralph integration — structured planning to autonomous implementation")
14-
.version("0.8.0");
15+
.version("0.8.1")
16+
.option("--verbose", "Enable debug logging")
17+
.hook("preAction", () => {
18+
if (program.opts().verbose) {
19+
setVerbose(true);
20+
}
21+
});
1522

1623
program
1724
.command("init")

src/installer.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ export async function copyBundledAssets(projectDir: string): Promise<UpgradeResu
3333
const ralphDir = getRalphDir();
3434
const slashCommandsDir = getSlashCommandsDir();
3535

36+
// Validate source directories exist
37+
try {
38+
await access(bmadDir);
39+
} catch {
40+
throw new Error(`BMAD source directory not found at ${bmadDir}. Package may be corrupted.`);
41+
}
42+
try {
43+
await access(ralphDir);
44+
} catch {
45+
throw new Error(`Ralph source directory not found at ${ralphDir}. Package may be corrupted.`);
46+
}
47+
try {
48+
await access(slashCommandsDir);
49+
} catch {
50+
throw new Error(`Slash commands directory not found at ${slashCommandsDir}. Package may be corrupted.`);
51+
}
52+
3653
// Copy BMAD files → _bmad/
3754
await cp(bmadDir, join(projectDir, "_bmad"), { recursive: true });
3855

src/transition.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFile, writeFile, readdir, cp, mkdir, access } from "fs/promises";
22
import { join } from "path";
3+
import { debug } from "./utils/logger.js";
34

45
export interface Story {
56
epic: string;
@@ -17,13 +18,17 @@ export async function findArtifactsDir(projectDir: string): Promise<string | nul
1718
];
1819

1920
for (const candidate of candidates) {
21+
const fullPath = join(projectDir, candidate);
22+
debug(`Checking artifacts dir: ${fullPath}`);
2023
try {
21-
await access(join(projectDir, candidate));
22-
return join(projectDir, candidate);
24+
await access(fullPath);
25+
debug(`Found artifacts at: ${fullPath}`);
26+
return fullPath;
2327
} catch {
2428
continue;
2529
}
2630
}
31+
debug(`No artifacts found. Checked: ${candidates.join(", ")}`);
2732
return null;
2833
}
2934

@@ -240,15 +245,18 @@ export async function runTransition(projectDir: string): Promise<{ storiesCount:
240245

241246
// Find and parse stories file
242247
const files = await readdir(artifactsDir);
248+
const STORIES_PATTERN = /^(epics[-_]?(and[-_]?)?)?stor(y|ies)([-_]\d+)?\.md$/i;
243249
const storiesFile = files.find(
244-
(f) => f.includes("epic") || f.includes("stories") || f.includes("story"),
250+
(f) => STORIES_PATTERN.test(f) || /epic/i.test(f),
245251
);
246252

247253
if (!storiesFile) {
254+
debug(`Files in artifacts dir: ${files.join(", ")}`);
248255
throw new Error(
249-
"No epics/stories file found in artifacts. Run 'CE' (Create Epics and Stories) first.",
256+
`No epics/stories file found in ${artifactsDir}. Available files: ${files.join(", ")}. Run 'CE' (Create Epics and Stories) first.`,
250257
);
251258
}
259+
debug(`Using stories file: ${storiesFile}`);
252260

253261
const storiesContent = await readFile(join(artifactsDir, storiesFile), "utf-8");
254262
const stories = parseStories(storiesContent);

src/utils/logger.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import chalk from "chalk";
2+
3+
let verbose = false;
4+
5+
export function setVerbose(value: boolean): void {
6+
verbose = value;
7+
}
8+
9+
export function isVerbose(): boolean {
10+
return verbose;
11+
}
12+
13+
export function debug(message: string): void {
14+
if (verbose) {
15+
console.log(chalk.dim(`[debug] ${message}`));
16+
}
17+
}

src/utils/state.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { writeFile, mkdir } from "fs/promises";
1+
import { writeFile, mkdir, rename } from "fs/promises";
22
import { join } from "path";
33
import { readJsonFile } from "./json.js";
44
import { validateState } from "./validate.js";
@@ -34,10 +34,10 @@ export async function readState(projectDir: string): Promise<BmalphState | null>
3434

3535
export async function writeState(projectDir: string, state: BmalphState): Promise<void> {
3636
await mkdir(join(projectDir, STATE_DIR), { recursive: true });
37-
await writeFile(
38-
join(projectDir, STATE_DIR, "current-phase.json"),
39-
JSON.stringify(state, null, 2) + "\n",
40-
);
37+
const target = join(projectDir, STATE_DIR, "current-phase.json");
38+
const tmp = target + ".tmp";
39+
await writeFile(tmp, JSON.stringify(state, null, 2) + "\n");
40+
await rename(tmp, target);
4141
}
4242

4343
export function getPhaseLabel(phase: number): string {

0 commit comments

Comments
 (0)