bmalph 2.2.0 → 2.2.1
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/bundled-versions.json +1 -2
- package/dist/cli.js +2 -2
- package/dist/commands/check-updates.js +5 -27
- package/dist/commands/doctor.js +16 -21
- package/dist/commands/init.js +1 -2
- package/dist/installer.d.ts +1 -1
- package/dist/installer.js +60 -32
- 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-index.js +2 -3
- package/dist/utils/config.d.ts +0 -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 +19 -4
- package/package.json +4 -4
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
|
}
|
|
@@ -70,7 +70,7 @@ program
|
|
|
70
70
|
.action(async (opts) => doctorCommand({ ...opts, projectDir: await resolveAndValidateProjectDir() }));
|
|
71
71
|
program
|
|
72
72
|
.command("check-updates")
|
|
73
|
-
.description("Check if bundled BMAD
|
|
73
|
+
.description("Check if bundled BMAD version is up to date with upstream")
|
|
74
74
|
.option("--json", "Output as JSON")
|
|
75
75
|
.action(checkUpdatesCommand);
|
|
76
76
|
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) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -248,22 +248,21 @@ async function checkUpstreamVersions(projectDir) {
|
|
|
248
248
|
return { label, passed: true, detail: "not tracked (pre-1.2.0 install)" };
|
|
249
249
|
}
|
|
250
250
|
const bundled = getBundledVersions();
|
|
251
|
-
const { bmadCommit
|
|
251
|
+
const { bmadCommit } = config.upstreamVersions;
|
|
252
252
|
const bmadMatch = bmadCommit === bundled.bmadCommit;
|
|
253
|
-
|
|
254
|
-
if (bmadMatch && ralphMatch) {
|
|
253
|
+
if (bmadMatch) {
|
|
255
254
|
return {
|
|
256
255
|
label,
|
|
257
256
|
passed: true,
|
|
258
|
-
detail: `BMAD:${bmadCommit.slice(0, 8)}
|
|
257
|
+
detail: `BMAD:${bmadCommit.slice(0, 8)}`,
|
|
259
258
|
};
|
|
260
259
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
260
|
+
return {
|
|
261
|
+
label,
|
|
262
|
+
passed: false,
|
|
263
|
+
detail: `outdated: BMAD:${bmadCommit.slice(0, 8)}→${bundled.bmadCommit.slice(0, 8)}`,
|
|
264
|
+
hint,
|
|
265
|
+
};
|
|
267
266
|
}
|
|
268
267
|
catch (err) {
|
|
269
268
|
return { label, passed: false, detail: `error: ${formatError(err)}`, hint };
|
|
@@ -397,20 +396,16 @@ async function checkUpstreamGitHubStatus(_projectDir) {
|
|
|
397
396
|
try {
|
|
398
397
|
const bundled = getBundledVersions();
|
|
399
398
|
const result = await checkUpstream(bundled);
|
|
400
|
-
// Check if
|
|
401
|
-
if (result.bmad === null
|
|
399
|
+
// Check if request failed
|
|
400
|
+
if (result.bmad === null) {
|
|
402
401
|
const reason = getSkipReason(result.errors);
|
|
403
402
|
return { label, passed: true, detail: `skipped: ${reason}` };
|
|
404
403
|
}
|
|
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(", ") };
|
|
404
|
+
return {
|
|
405
|
+
label,
|
|
406
|
+
passed: true,
|
|
407
|
+
detail: `BMAD: ${result.bmad.isUpToDate ? "up to date" : "behind"}`,
|
|
408
|
+
};
|
|
414
409
|
}
|
|
415
410
|
catch (err) {
|
|
416
411
|
return { label, passed: true, detail: `skipped: ${formatError(err)}` };
|
package/dist/commands/init.js
CHANGED
|
@@ -79,8 +79,7 @@ async function runInit(options) {
|
|
|
79
79
|
}
|
|
80
80
|
catch (err) {
|
|
81
81
|
throw new Error(`Partial installation: files were copied but configuration failed. ` +
|
|
82
|
-
`Run 'bmalph init' again to retry
|
|
83
|
-
`Cause: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
`Run 'bmalph init' again to retry.`, { cause: err });
|
|
84
83
|
}
|
|
85
84
|
console.log(chalk.green("\nbmalph initialized successfully!"));
|
|
86
85
|
console.log(`\n Project: ${chalk.bold(config.name)}`);
|
package/dist/installer.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare function getPackageVersion(): string;
|
|
2
2
|
export interface BundledVersions {
|
|
3
3
|
bmadCommit: string;
|
|
4
|
-
ralphCommit: string;
|
|
5
4
|
}
|
|
6
5
|
export declare function getBundledVersions(): BundledVersions;
|
|
7
6
|
export declare function getBundledBmadDir(): string;
|
|
@@ -18,6 +17,7 @@ export interface PreviewInstallResult {
|
|
|
18
17
|
export interface PreviewUpgradeResult {
|
|
19
18
|
wouldUpdate: string[];
|
|
20
19
|
wouldCreate: string[];
|
|
20
|
+
wouldPreserve: string[];
|
|
21
21
|
}
|
|
22
22
|
export declare function copyBundledAssets(projectDir: string): Promise<UpgradeResult>;
|
|
23
23
|
export declare function installProject(projectDir: string): Promise<void>;
|
package/dist/installer.js
CHANGED
|
@@ -22,21 +22,18 @@ export function getBundledVersions() {
|
|
|
22
22
|
const versionsPath = join(__dirname, "..", "bundled-versions.json");
|
|
23
23
|
try {
|
|
24
24
|
const versions = JSON.parse(readFileSync(versionsPath, "utf-8"));
|
|
25
|
-
if (!versions ||
|
|
26
|
-
|
|
27
|
-
typeof versions.ralphCommit !== "string") {
|
|
28
|
-
throw new Error("Invalid bundled-versions.json structure: missing bmadCommit or ralphCommit");
|
|
25
|
+
if (!versions || typeof versions.bmadCommit !== "string") {
|
|
26
|
+
throw new Error("Invalid bundled-versions.json structure: missing bmadCommit");
|
|
29
27
|
}
|
|
30
28
|
return {
|
|
31
29
|
bmadCommit: versions.bmadCommit,
|
|
32
|
-
ralphCommit: versions.ralphCommit,
|
|
33
30
|
};
|
|
34
31
|
}
|
|
35
32
|
catch (err) {
|
|
36
33
|
if (err instanceof Error && err.message.includes("Invalid bundled-versions.json")) {
|
|
37
34
|
throw err;
|
|
38
35
|
}
|
|
39
|
-
throw new Error(`Failed to read bundled-versions.json at ${versionsPath}:
|
|
36
|
+
throw new Error(`Failed to read bundled-versions.json at ${versionsPath}`, { cause: err });
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
export function getBundledBmadDir() {
|
|
@@ -48,6 +45,24 @@ export function getBundledRalphDir() {
|
|
|
48
45
|
export function getSlashCommandsDir() {
|
|
49
46
|
return join(__dirname, "..", "slash-commands");
|
|
50
47
|
}
|
|
48
|
+
const TEMPLATE_PLACEHOLDERS = {
|
|
49
|
+
"PROMPT.md": "[YOUR PROJECT NAME]",
|
|
50
|
+
"AGENT.md": "pip install -r requirements.txt",
|
|
51
|
+
};
|
|
52
|
+
async function isTemplateCustomized(filePath, templateName) {
|
|
53
|
+
const placeholder = TEMPLATE_PLACEHOLDERS[templateName];
|
|
54
|
+
if (!placeholder)
|
|
55
|
+
return false;
|
|
56
|
+
try {
|
|
57
|
+
const content = await readFile(filePath, "utf-8");
|
|
58
|
+
return !content.includes(placeholder);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (isEnoent(err))
|
|
62
|
+
return false;
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
51
66
|
export async function copyBundledAssets(projectDir) {
|
|
52
67
|
const bmadDir = getBundledBmadDir();
|
|
53
68
|
const ralphDir = getBundledRalphDir();
|
|
@@ -102,12 +117,19 @@ modules:
|
|
|
102
117
|
`);
|
|
103
118
|
// Copy Ralph templates → .ralph/
|
|
104
119
|
await mkdir(join(projectDir, ".ralph"), { recursive: true });
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
// Preserve customized PROMPT.md and @AGENT.md on upgrade
|
|
121
|
+
const promptCustomized = await isTemplateCustomized(join(projectDir, ".ralph/PROMPT.md"), "PROMPT.md");
|
|
122
|
+
const agentCustomized = await isTemplateCustomized(join(projectDir, ".ralph/@AGENT.md"), "AGENT.md");
|
|
123
|
+
if (!promptCustomized) {
|
|
124
|
+
await cp(join(ralphDir, "templates/PROMPT.md"), join(projectDir, ".ralph/PROMPT.md"), {
|
|
125
|
+
dereference: false,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!agentCustomized) {
|
|
129
|
+
await cp(join(ralphDir, "templates/AGENT.md"), join(projectDir, ".ralph/@AGENT.md"), {
|
|
130
|
+
dereference: false,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
111
133
|
await cp(join(ralphDir, "RALPH-REFERENCE.md"), join(projectDir, ".ralph/RALPH-REFERENCE.md"), {
|
|
112
134
|
dereference: false,
|
|
113
135
|
});
|
|
@@ -163,20 +185,19 @@ modules:
|
|
|
163
185
|
}
|
|
164
186
|
// Update .gitignore
|
|
165
187
|
await updateGitignore(projectDir);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
};
|
|
188
|
+
const updatedPaths = [
|
|
189
|
+
"_bmad/",
|
|
190
|
+
".ralph/ralph_loop.sh",
|
|
191
|
+
".ralph/ralph_import.sh",
|
|
192
|
+
".ralph/ralph_monitor.sh",
|
|
193
|
+
".ralph/lib/",
|
|
194
|
+
...(!promptCustomized ? [".ralph/PROMPT.md"] : []),
|
|
195
|
+
...(!agentCustomized ? [".ralph/@AGENT.md"] : []),
|
|
196
|
+
".ralph/RALPH-REFERENCE.md",
|
|
197
|
+
".claude/commands/",
|
|
198
|
+
".gitignore",
|
|
199
|
+
];
|
|
200
|
+
return { updatedPaths };
|
|
180
201
|
}
|
|
181
202
|
export async function installProject(projectDir) {
|
|
182
203
|
// Create user directories (not overwritten by upgrade)
|
|
@@ -410,21 +431,28 @@ export async function previewUpgrade(projectDir) {
|
|
|
410
431
|
{ path: ".ralph/ralph_import.sh", isDir: false },
|
|
411
432
|
{ path: ".ralph/ralph_monitor.sh", isDir: false },
|
|
412
433
|
{ path: ".ralph/lib/", isDir: true },
|
|
413
|
-
{ path: ".ralph/PROMPT.md", isDir: false },
|
|
414
|
-
{ path: ".ralph/@AGENT.md", isDir: false },
|
|
434
|
+
{ path: ".ralph/PROMPT.md", isDir: false, templateName: "PROMPT.md" },
|
|
435
|
+
{ path: ".ralph/@AGENT.md", isDir: false, templateName: "AGENT.md" },
|
|
415
436
|
{ path: ".ralph/RALPH-REFERENCE.md", isDir: false },
|
|
416
437
|
{ path: ".claude/commands/", isDir: true },
|
|
417
438
|
{ path: ".gitignore", isDir: false },
|
|
418
439
|
];
|
|
419
440
|
const wouldUpdate = [];
|
|
420
441
|
const wouldCreate = [];
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
442
|
+
const wouldPreserve = [];
|
|
443
|
+
for (const { path: p, templateName } of managedPaths) {
|
|
444
|
+
const fullPath = join(projectDir, p.replace(/\/$/, ""));
|
|
445
|
+
if (await exists(fullPath)) {
|
|
446
|
+
if (templateName && (await isTemplateCustomized(fullPath, templateName))) {
|
|
447
|
+
wouldPreserve.push(p);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
wouldUpdate.push(p);
|
|
451
|
+
}
|
|
424
452
|
}
|
|
425
453
|
else {
|
|
426
454
|
wouldCreate.push(p);
|
|
427
455
|
}
|
|
428
456
|
}
|
|
429
|
-
return { wouldUpdate, wouldCreate };
|
|
457
|
+
return { wouldUpdate, wouldCreate, wouldPreserve };
|
|
430
458
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Story, FixPlanItemWithTitle } from "./types.js";
|
|
2
|
-
export declare function generateFixPlan(stories: Story[]): string;
|
|
2
|
+
export declare function generateFixPlan(stories: Story[], storiesFileName?: string): string;
|
|
3
3
|
export declare function hasFixPlanProgress(content: string): boolean;
|
|
4
4
|
export declare function parseFixPlan(content: string): FixPlanItemWithTitle[];
|
|
5
5
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function generateFixPlan(stories) {
|
|
1
|
+
export function generateFixPlan(stories, storiesFileName) {
|
|
2
2
|
const lines = ["# Ralph Fix Plan", "", "## Stories to Implement", ""];
|
|
3
3
|
let currentEpic = "";
|
|
4
4
|
for (const story of stories) {
|
|
@@ -25,7 +25,8 @@ export function generateFixPlan(stories) {
|
|
|
25
25
|
}
|
|
26
26
|
// Add spec-link for easy reference to full story details
|
|
27
27
|
const anchor = story.id.replace(".", "-");
|
|
28
|
-
|
|
28
|
+
const fileName = storiesFileName ?? "stories.md";
|
|
29
|
+
lines.push(` > Spec: specs/planning-artifacts/${fileName}#story-${anchor}`);
|
|
29
30
|
}
|
|
30
31
|
lines.push("", "## Completed", "", "## Notes", "- Follow TDD methodology (red-green-refactor)", "- One story per Ralph loop iteration", "- Update this file after completing each story", "");
|
|
31
32
|
return lines.join("\n");
|
|
@@ -64,7 +64,7 @@ export async function runTransition(projectDir) {
|
|
|
64
64
|
}
|
|
65
65
|
// Generate new fix_plan from current stories, preserving completion status
|
|
66
66
|
info(`Generating fix plan for ${stories.length} stories...`);
|
|
67
|
-
const newFixPlan = generateFixPlan(stories);
|
|
67
|
+
const newFixPlan = generateFixPlan(stories, storiesFile);
|
|
68
68
|
const mergedFixPlan = mergeFixPlanProgress(newFixPlan, completedIds);
|
|
69
69
|
await atomicWriteFile(fixPlanPath, mergedFixPlan);
|
|
70
70
|
// Track whether progress was preserved for return value
|
|
@@ -10,14 +10,13 @@ export function detectSpecFileType(filename, _content) {
|
|
|
10
10
|
if (lower.includes("arch"))
|
|
11
11
|
return "architecture";
|
|
12
12
|
// Check stories/epic BEFORE brainstorm (Bug #5: brainstorm-stories.md should be "stories")
|
|
13
|
-
|
|
14
|
-
if (lower.includes("stori") || lower.includes("epic"))
|
|
13
|
+
if (/stor(y|ies)/i.test(lower) || lower.includes("epic"))
|
|
15
14
|
return "stories";
|
|
16
15
|
if (lower.includes("brainstorm"))
|
|
17
16
|
return "brainstorm";
|
|
18
17
|
if (lower.includes("ux"))
|
|
19
18
|
return "ux";
|
|
20
|
-
if (
|
|
19
|
+
if (/\btest/i.test(lower))
|
|
21
20
|
return "test-design";
|
|
22
21
|
if (lower.includes("readiness"))
|
|
23
22
|
return "readiness";
|
package/dist/utils/config.d.ts
CHANGED
package/dist/utils/errors.js
CHANGED
package/dist/utils/github.d.ts
CHANGED
package/dist/utils/github.js
CHANGED
|
@@ -4,11 +4,6 @@ const BMAD_REPO = {
|
|
|
4
4
|
repo: "BMAD-METHOD",
|
|
5
5
|
branch: "main",
|
|
6
6
|
};
|
|
7
|
-
const RALPH_REPO = {
|
|
8
|
-
owner: "snarktank",
|
|
9
|
-
repo: "ralph",
|
|
10
|
-
branch: "main",
|
|
11
|
-
};
|
|
12
7
|
const DEFAULT_TIMEOUT_MS = 10000;
|
|
13
8
|
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
14
9
|
const DEFAULT_MAX_CACHE_SIZE = 100;
|
|
@@ -182,27 +177,15 @@ export class GitHubClient {
|
|
|
182
177
|
async checkUpstream(bundled) {
|
|
183
178
|
const errors = [];
|
|
184
179
|
let bmadStatus = null;
|
|
185
|
-
|
|
186
|
-
// Fetch both in parallel
|
|
187
|
-
const [bmadResult, ralphResult] = await Promise.all([
|
|
188
|
-
this.fetchLatestCommit(BMAD_REPO),
|
|
189
|
-
this.fetchLatestCommit(RALPH_REPO),
|
|
190
|
-
]);
|
|
180
|
+
const bmadResult = await this.fetchLatestCommit(BMAD_REPO);
|
|
191
181
|
if (bmadResult.success) {
|
|
192
182
|
bmadStatus = buildUpstreamStatus(BMAD_REPO, bundled.bmadCommit, bmadResult.data.shortSha);
|
|
193
183
|
}
|
|
194
184
|
else {
|
|
195
185
|
errors.push({ ...bmadResult.error, repo: "bmad" });
|
|
196
186
|
}
|
|
197
|
-
if (ralphResult.success) {
|
|
198
|
-
ralphStatus = buildUpstreamStatus(RALPH_REPO, bundled.ralphCommit, ralphResult.data.shortSha);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
errors.push({ ...ralphResult.error, repo: "ralph" });
|
|
202
|
-
}
|
|
203
187
|
return {
|
|
204
188
|
bmad: bmadStatus,
|
|
205
|
-
ralph: ralphStatus,
|
|
206
189
|
errors,
|
|
207
190
|
};
|
|
208
191
|
}
|
package/dist/utils/json.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
-
import { isEnoent
|
|
2
|
+
import { isEnoent } from "./errors.js";
|
|
3
3
|
/**
|
|
4
4
|
* Reads and parses a JSON file with proper error discrimination:
|
|
5
5
|
* - File not found → returns null
|
|
@@ -21,6 +21,6 @@ export async function readJsonFile(path) {
|
|
|
21
21
|
return JSON.parse(content);
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
24
|
-
throw new Error(`Invalid JSON in ${path}:
|
|
24
|
+
throw new Error(`Invalid JSON in ${path}`, { cause: err });
|
|
25
25
|
}
|
|
26
26
|
}
|
package/dist/utils/state.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir } from "fs/promises";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { readJsonFile } from "./json.js";
|
|
4
|
-
import { validateState, validateRalphLoopStatus } from "./validate.js";
|
|
4
|
+
import { validateState, validateRalphLoopStatus, normalizeRalphStatus } from "./validate.js";
|
|
5
5
|
import { STATE_DIR, RALPH_STATUS_FILE } from "./constants.js";
|
|
6
6
|
import { atomicWriteFile } from "./file-system.js";
|
|
7
7
|
import { warn } from "./logger.js";
|
|
@@ -186,6 +186,12 @@ export async function readRalphStatus(projectDir) {
|
|
|
186
186
|
try {
|
|
187
187
|
return validateRalphLoopStatus(data);
|
|
188
188
|
}
|
|
189
|
+
catch {
|
|
190
|
+
// camelCase validation failed — try bash snake_case format
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
return normalizeRalphStatus(data);
|
|
194
|
+
}
|
|
189
195
|
catch (err) {
|
|
190
196
|
warn(`Ralph status file is corrupted, using defaults: ${formatError(err)}`);
|
|
191
197
|
return DEFAULT_RALPH_STATUS;
|
package/dist/utils/validate.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface RalphLoopStatus {
|
|
|
27
27
|
tasksTotal: number;
|
|
28
28
|
}
|
|
29
29
|
export declare function validateRalphLoopStatus(data: unknown): RalphLoopStatus;
|
|
30
|
+
export declare function normalizeRalphStatus(data: unknown): RalphLoopStatus;
|
|
30
31
|
/**
|
|
31
32
|
* Validates a project name for filesystem safety.
|
|
32
33
|
* Checks for:
|
package/dist/utils/validate.js
CHANGED
|
@@ -37,12 +37,8 @@ function validateUpstreamVersions(data) {
|
|
|
37
37
|
if (typeof data.bmadCommit !== "string") {
|
|
38
38
|
throw new Error("upstreamVersions.bmadCommit must be a string");
|
|
39
39
|
}
|
|
40
|
-
if (typeof data.ralphCommit !== "string") {
|
|
41
|
-
throw new Error("upstreamVersions.ralphCommit must be a string");
|
|
42
|
-
}
|
|
43
40
|
return {
|
|
44
41
|
bmadCommit: data.bmadCommit,
|
|
45
|
-
ralphCommit: data.ralphCommit,
|
|
46
42
|
};
|
|
47
43
|
}
|
|
48
44
|
export function validateConfig(data) {
|
|
@@ -158,6 +154,25 @@ export function validateRalphLoopStatus(data) {
|
|
|
158
154
|
tasksTotal: data.tasksTotal,
|
|
159
155
|
};
|
|
160
156
|
}
|
|
157
|
+
const BASH_STATUS_MAP = {
|
|
158
|
+
running: "running",
|
|
159
|
+
halted: "blocked",
|
|
160
|
+
stopped: "blocked",
|
|
161
|
+
completed: "completed",
|
|
162
|
+
success: "completed",
|
|
163
|
+
};
|
|
164
|
+
export function normalizeRalphStatus(data) {
|
|
165
|
+
assertObject(data, "normalizeRalphStatus");
|
|
166
|
+
const loopCount = typeof data.loop_count === "number" ? data.loop_count : 0;
|
|
167
|
+
const rawStatus = typeof data.status === "string" ? data.status : undefined;
|
|
168
|
+
const status = (rawStatus !== undefined ? BASH_STATUS_MAP[rawStatus] : undefined) ?? "running";
|
|
169
|
+
return {
|
|
170
|
+
loopCount,
|
|
171
|
+
status,
|
|
172
|
+
tasksCompleted: 0,
|
|
173
|
+
tasksTotal: 0,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
161
176
|
/**
|
|
162
177
|
* Validates a project name for filesystem safety.
|
|
163
178
|
* Checks for:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bmalph",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Unified AI Development Framework - BMAD phases with Ralph execution loop for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
"inquirer": "^13.2.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@eslint/js": "^
|
|
66
|
+
"@eslint/js": "^10.0.1",
|
|
67
67
|
"@types/node": "^20.0.0",
|
|
68
68
|
"@vitest/coverage-v8": "^4.0.18",
|
|
69
|
-
"eslint": "^
|
|
69
|
+
"eslint": "^10.0.1",
|
|
70
70
|
"prettier": "^3.8.1",
|
|
71
71
|
"typescript": "^5.9.3",
|
|
72
|
-
"typescript-eslint": "^8.
|
|
72
|
+
"typescript-eslint": "^8.56.0",
|
|
73
73
|
"vitest": "^4.0.18"
|
|
74
74
|
}
|
|
75
75
|
}
|