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.
@@ -1,4 +1,3 @@
1
1
  {
2
- "bmadCommit": "aa573bdb",
3
- "ralphCommit": "605e50f7"
2
+ "bmadCommit": "aa573bdb"
4
3
  }
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/Ralph versions are up to date with upstream")
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 = (result.bmad !== null && !result.bmad.isUpToDate) ||
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 (updatesCount === 0 && result.errors.length === 0) {
63
- console.log(chalk.green("All repositories are up to date."));
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 (updatesCount > 0) {
66
- const plural = updatesCount === 1 ? "repository has" : "repositories have";
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) {
@@ -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, ralphCommit } = config.upstreamVersions;
251
+ const { bmadCommit } = config.upstreamVersions;
252
252
  const bmadMatch = bmadCommit === bundled.bmadCommit;
253
- const ralphMatch = ralphCommit === bundled.ralphCommit;
254
- if (bmadMatch && ralphMatch) {
253
+ if (bmadMatch) {
255
254
  return {
256
255
  label,
257
256
  passed: true,
258
- detail: `BMAD:${bmadCommit.slice(0, 8)}, Ralph:${ralphCommit.slice(0, 8)}`,
257
+ detail: `BMAD:${bmadCommit.slice(0, 8)}`,
259
258
  };
260
259
  }
261
- const mismatches = [];
262
- if (!bmadMatch)
263
- mismatches.push(`BMAD:${bmadCommit.slice(0, 8)}→${bundled.bmadCommit.slice(0, 8)}`);
264
- if (!ralphMatch)
265
- mismatches.push(`Ralph:${ralphCommit.slice(0, 8)}→${bundled.ralphCommit.slice(0, 8)}`);
266
- return { label, passed: false, detail: `outdated: ${mismatches.join(", ")}`, hint };
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 all requests failed
401
- if (result.bmad === null && result.ralph === 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
- // Build status string
406
- const statuses = [];
407
- if (result.bmad) {
408
- statuses.push(`BMAD: ${result.bmad.isUpToDate ? "up to date" : "behind"}`);
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)}` };
@@ -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)}`);
@@ -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
- typeof versions.bmadCommit !== "string" ||
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}: ${formatError(err)}`);
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
- await cp(join(ralphDir, "templates/PROMPT.md"), join(projectDir, ".ralph/PROMPT.md"), {
106
- dereference: false,
107
- });
108
- await cp(join(ralphDir, "templates/AGENT.md"), join(projectDir, ".ralph/@AGENT.md"), {
109
- dereference: false,
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
- return {
167
- updatedPaths: [
168
- "_bmad/",
169
- ".ralph/ralph_loop.sh",
170
- ".ralph/ralph_import.sh",
171
- ".ralph/ralph_monitor.sh",
172
- ".ralph/lib/",
173
- ".ralph/PROMPT.md",
174
- ".ralph/@AGENT.md",
175
- ".ralph/RALPH-REFERENCE.md",
176
- ".claude/commands/",
177
- ".gitignore",
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
- for (const { path: p } of managedPaths) {
422
- if (await exists(join(projectDir, p.replace(/\/$/, "")))) {
423
- wouldUpdate.push(p);
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
- lines.push(` > Spec: specs/planning-artifacts/stories.md#story-${anchor}`);
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
- // Use "stori" to match "stories"/"story" but not "brainstorm" (which contains "stor")
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 (lower.includes("test"))
19
+ if (/\btest/i.test(lower))
21
20
  return "test-design";
22
21
  if (lower.includes("readiness"))
23
22
  return "readiness";
@@ -1,6 +1,5 @@
1
1
  export interface UpstreamVersions {
2
2
  bmadCommit: string;
3
- ralphCommit: string;
4
3
  }
5
4
  export interface BmalphConfig {
6
5
  name: string;
@@ -30,6 +30,9 @@ export function isEnoent(err) {
30
30
  }
31
31
  export function formatError(error) {
32
32
  if (error instanceof Error) {
33
+ if (error.cause) {
34
+ return `${error.message}: ${formatError(error.cause)}`;
35
+ }
33
36
  return error.message;
34
37
  }
35
38
  return String(error);
@@ -32,7 +32,6 @@ export interface UpstreamStatus {
32
32
  }
33
33
  export interface CheckUpstreamResult {
34
34
  bmad: UpstreamStatus | null;
35
- ralph: UpstreamStatus | null;
36
35
  errors: GitHubError[];
37
36
  }
38
37
  interface FetchOptions {
@@ -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
- let ralphStatus = null;
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
  }
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "fs/promises";
2
- import { isEnoent, formatError } from "./errors.js";
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}: ${formatError(err)}`);
24
+ throw new Error(`Invalid JSON in ${path}`, { cause: err });
25
25
  }
26
26
  }
@@ -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;
@@ -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:
@@ -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.0",
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": "^9.39.2",
66
+ "@eslint/js": "^10.0.1",
67
67
  "@types/node": "^20.0.0",
68
68
  "@vitest/coverage-v8": "^4.0.18",
69
- "eslint": "^9.39.2",
69
+ "eslint": "^10.0.1",
70
70
  "prettier": "^3.8.1",
71
71
  "typescript": "^5.9.3",
72
- "typescript-eslint": "^8.53.1",
72
+ "typescript-eslint": "^8.56.0",
73
73
  "vitest": "^4.0.18"
74
74
  }
75
75
  }