bmalph 2.9.0 → 2.11.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 CHANGED
@@ -279,12 +279,12 @@ BMAD (add Epic 2) → bmalph implement → Ralph sees changes + picks up Epic 2
279
279
 
280
280
  ### run options
281
281
 
282
- | Flag | Description |
283
- | ---------------------- | --------------------------------------------------------------------------- |
284
- | `--driver <platform>` | Override platform driver (claude-code, codex, opencode, copilot, cursor) |
285
- | `--review/--no-review` | Enable/disable periodic code review (Claude Code only, prompted by default) |
286
- | `--interval <ms>` | Dashboard refresh interval in milliseconds (default: 2000) |
287
- | `--no-dashboard` | Run Ralph without the dashboard overlay |
282
+ | Flag | Description |
283
+ | --------------------- | ---------------------------------------------------------------------------------------- |
284
+ | `--driver <platform>` | Override platform driver (claude-code, codex, opencode, copilot, cursor) |
285
+ | `--review [mode]` | Quality review: `enhanced` (every 5 loops) or `ultimate` (every story). Claude Code only |
286
+ | `--interval <ms>` | Dashboard refresh interval in milliseconds (default: 2000) |
287
+ | `--no-dashboard` | Run Ralph without the dashboard overlay |
288
288
 
289
289
  ### watch options
290
290
 
@@ -416,7 +416,7 @@ Safety mechanisms:
416
416
 
417
417
  - **Circuit breaker** — prevents infinite loops on failing stories
418
418
  - **Response analyzer** — detects stuck or repeating outputs
419
- - **Code review** — optional periodic review every 5 loops (`--review`, Claude Code only). A read-only session analyzes git diffs and feeds structured findings into the next implementation loop
419
+ - **Code review** — optional quality review (`--review [mode]`, Claude Code only). Enhanced: periodic review every 5 loops. Ultimate: review after every completed story. A read-only session analyzes git diffs and feeds structured findings into the next implementation loop
420
420
  - **Completion** — loop exits when all `@fix_plan.md` items are checked off
421
421
 
422
422
  Cursor-specific runtime checks:
package/dist/cli.js CHANGED
@@ -105,7 +105,8 @@ program
105
105
  .option("--driver <platform>", "Override platform driver (claude-code, codex, opencode, copilot, cursor)")
106
106
  .option("--interval <ms>", "Dashboard refresh interval in milliseconds (default: 2000)")
107
107
  .option("--no-dashboard", "Run Ralph without the dashboard overlay")
108
- .option("--review", "Enable periodic code review loop (~10-14% more tokens)")
108
+ .option("--review [mode]", "Quality review: enhanced (~10-14% tokens) or ultimate (~20-30%)")
109
+ .option("--no-review", "Disable code review")
109
110
  .action(async (opts) => runCommand({ ...opts, projectDir: await resolveAndValidateProjectDir() }));
110
111
  void program.parseAsync();
111
112
  //# sourceMappingURL=cli.js.map
@@ -68,6 +68,34 @@ export async function checkJq(projectDir) {
68
68
  : "Install jq: sudo apt-get install jq",
69
69
  };
70
70
  }
71
+ export async function checkGitRepo(projectDir) {
72
+ const label = "git repository with commits";
73
+ const gitDir = await runBashCommand("git rev-parse --git-dir", { cwd: projectDir });
74
+ if (gitDir.exitCode !== 0) {
75
+ return {
76
+ label,
77
+ passed: false,
78
+ detail: "not a git repository",
79
+ hint: "Run: git init && git add -A && git commit -m 'initial commit'",
80
+ };
81
+ }
82
+ const head = await runBashCommand("git rev-parse HEAD", { cwd: projectDir });
83
+ if (head.exitCode !== 0) {
84
+ return {
85
+ label,
86
+ passed: false,
87
+ detail: "no commits found",
88
+ hint: "Run: git add -A && git commit -m 'initial commit'",
89
+ };
90
+ }
91
+ const branch = await runBashCommand("git branch --show-current", { cwd: projectDir });
92
+ const branchName = branch.stdout.trim();
93
+ return {
94
+ label,
95
+ passed: true,
96
+ detail: branchName ? `branch: ${branchName}` : undefined,
97
+ };
98
+ }
71
99
  export async function checkBmadDir(projectDir) {
72
100
  return checkDir(join(projectDir, "_bmad"), "_bmad/ directory present", "Run: bmalph init");
73
101
  }
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { withErrorHandling } from "../utils/errors.js";
3
3
  import { resolveProjectPlatform } from "../platform/resolve.js";
4
- import { checkNodeVersion, checkBash, checkJq, checkConfig, checkBmadDir, checkRalphLoop, checkRalphLib, } from "./doctor-checks.js";
4
+ import { checkNodeVersion, checkBash, checkGitRepo, checkJq, checkConfig, checkBmadDir, checkRalphLoop, checkRalphLib, } from "./doctor-checks.js";
5
5
  import { checkGitignore, checkVersionMarker, checkUpstreamVersions, } from "./doctor-health-checks.js";
6
6
  import { checkCircuitBreaker, checkRalphSession, checkApiCalls, checkUpstreamGitHubStatus, } from "./doctor-runtime-checks.js";
7
7
  export async function doctorCommand(options) {
@@ -62,6 +62,7 @@ export async function runDoctor(options, checksOverride) {
62
62
  const CORE_CHECKS = [
63
63
  { id: "node-version", run: checkNodeVersion },
64
64
  { id: "bash-available", run: checkBash },
65
+ { id: "git-repo", run: checkGitRepo },
65
66
  { id: "jq-available", run: checkJq },
66
67
  { id: "config-valid", run: checkConfig },
67
68
  { id: "bmad-dir", run: checkBmadDir },
@@ -3,7 +3,7 @@ import { readConfig } from "../utils/config.js";
3
3
  import { withErrorHandling } from "../utils/errors.js";
4
4
  import { isPlatformId, getPlatform, getFullTierPlatformNames } from "../platform/registry.js";
5
5
  import { validateCursorRuntime } from "../platform/cursor-runtime-checks.js";
6
- import { validateBashAvailable, validateRalphLoop, spawnRalphLoop } from "../run/ralph-process.js";
6
+ import { validateBashAvailable, validateRalphLoop, validateGitRepo, spawnRalphLoop, } from "../run/ralph-process.js";
7
7
  import { startRunDashboard } from "../run/run-dashboard.js";
8
8
  import { parseInterval } from "../utils/validate.js";
9
9
  import { getDashboardTerminalSupport } from "../watch/frame-writer.js";
@@ -24,10 +24,13 @@ async function executeRun(options) {
24
24
  if (platform.experimental) {
25
25
  console.log(chalk.yellow(`Warning: ${platform.displayName} support is experimental`));
26
26
  }
27
- const reviewEnabled = await resolveReviewMode(options.review, platform);
28
- if (reviewEnabled) {
27
+ const reviewMode = await resolveReviewMode(options.review, platform);
28
+ if (reviewMode === "enhanced") {
29
29
  console.log(chalk.cyan("Enhanced mode: code review every 5 implementation loops"));
30
30
  }
31
+ else if (reviewMode === "ultimate") {
32
+ console.log(chalk.cyan("Ultimate mode: code review after every completed story"));
33
+ }
31
34
  const interval = parseInterval(options.interval);
32
35
  let useDashboard = dashboard;
33
36
  if (useDashboard) {
@@ -37,16 +40,20 @@ async function executeRun(options) {
37
40
  useDashboard = false;
38
41
  }
39
42
  }
40
- await Promise.all([validateBashAvailable(), validateRalphLoop(projectDir)]);
43
+ await Promise.all([
44
+ validateBashAvailable(),
45
+ validateRalphLoop(projectDir),
46
+ validateGitRepo(projectDir),
47
+ ]);
41
48
  if (platform.id === "cursor") {
42
49
  await validateCursorRuntime(projectDir);
43
50
  }
44
51
  const ralph = spawnRalphLoop(projectDir, platform.id, {
45
52
  inheritStdio: !useDashboard,
46
- ...(reviewEnabled && { reviewEnabled }),
53
+ reviewMode,
47
54
  });
48
55
  if (useDashboard) {
49
- await startRunDashboard({ projectDir, interval, ralph, reviewEnabled });
56
+ await startRunDashboard({ projectDir, interval, ralph, reviewMode });
50
57
  if (ralph.state === "stopped") {
51
58
  applyRalphExitCode(ralph.exitCode);
52
59
  }
@@ -70,34 +77,44 @@ function resolvePlatform(driverOverride, configPlatform) {
70
77
  }
71
78
  return getPlatform(id);
72
79
  }
80
+ const VALID_REVIEW_MODES = new Set(["enhanced", "ultimate"]);
73
81
  async function resolveReviewMode(reviewFlag, platform) {
74
- if (reviewFlag === true) {
82
+ if (reviewFlag === false) {
83
+ return "off";
84
+ }
85
+ if (reviewFlag === true || typeof reviewFlag === "string") {
75
86
  if (platform.id !== "claude-code") {
76
87
  throw new Error("--review requires Claude Code (other drivers lack read-only enforcement)");
77
88
  }
78
- return true;
79
- }
80
- if (reviewFlag === false) {
81
- return false;
89
+ if (reviewFlag === true) {
90
+ return "enhanced";
91
+ }
92
+ if (!VALID_REVIEW_MODES.has(reviewFlag)) {
93
+ throw new Error(`Unknown review mode: ${reviewFlag}. Valid modes: enhanced, ultimate`);
94
+ }
95
+ return reviewFlag;
82
96
  }
83
97
  if (platform.id !== "claude-code") {
84
- return false;
98
+ return "off";
85
99
  }
86
100
  if (!process.stdin.isTTY) {
87
- return false;
101
+ return "off";
88
102
  }
89
103
  const { default: select } = await import("@inquirer/select");
90
- const mode = await select({
104
+ return select({
91
105
  message: "Quality mode:",
92
106
  choices: [
93
- { name: "Standard — current behavior (no extra cost)", value: "standard" },
107
+ { name: "Standard — no code review (no extra cost)", value: "off" },
94
108
  {
95
109
  name: "Enhanced — periodic code review every 5 loops (~10-14% more tokens)",
96
110
  value: "enhanced",
97
111
  },
112
+ {
113
+ name: "Ultimate — review after every completed story (~20-30% more tokens)",
114
+ value: "ultimate",
115
+ },
98
116
  ],
99
- default: "standard",
117
+ default: "off",
100
118
  });
101
- return mode === "enhanced";
102
119
  }
103
120
  //# sourceMappingURL=run.js.map
@@ -55,6 +55,25 @@ const REVIEW_TEMPLATE_BLOCK = `# ===============================================
55
55
  # PERIODIC CODE REVIEW
56
56
  # =============================================================================
57
57
 
58
+ # Review mode: off, enhanced, or ultimate (set via 'bmalph run --review [mode]')
59
+ # - off: no code review (default)
60
+ # - enhanced: periodic review every REVIEW_INTERVAL loops (~10-14% more tokens)
61
+ # - ultimate: review after every completed story (~20-30% more tokens)
62
+ # The review agent analyzes git diffs and outputs findings for the next implementation loop.
63
+ # Currently supported on Claude Code only.
64
+ REVIEW_MODE="\${REVIEW_MODE:-off}"
65
+
66
+ # (Legacy) Enables review — prefer REVIEW_MODE instead
67
+ REVIEW_ENABLED="\${REVIEW_ENABLED:-false}"
68
+
69
+ # Number of implementation loops between review sessions (enhanced mode only)
70
+ REVIEW_INTERVAL="\${REVIEW_INTERVAL:-5}"
71
+
72
+ `;
73
+ const PREVIOUS_REVIEW_TEMPLATE_BLOCK = `# =============================================================================
74
+ # PERIODIC CODE REVIEW
75
+ # =============================================================================
76
+
58
77
  # Enable periodic code review loops (set via 'bmalph run --review' or manually)
59
78
  # When enabled, Ralph runs a read-only review session every REVIEW_INTERVAL loops.
60
79
  # The review agent analyzes git diffs and outputs findings for the next implementation loop.
@@ -229,11 +248,21 @@ async function isRalphrcCustomized(filePath, platformId) {
229
248
  if (matchesManagedPermissionVariants(content, templateWithoutReview)) {
230
249
  return false;
231
250
  }
251
+ // Check variants with previous review block (pre-ultimate installs)
252
+ const templateWithPreviousReview = currentTemplate.replace(REVIEW_TEMPLATE_BLOCK, PREVIOUS_REVIEW_TEMPLATE_BLOCK);
253
+ if (matchesManagedPermissionVariants(content, templateWithPreviousReview)) {
254
+ return false;
255
+ }
232
256
  // Check variants without both quality gates and review blocks
233
257
  const templateWithoutQGAndReview = templateWithoutQG.replace(REVIEW_TEMPLATE_BLOCK, "");
234
258
  if (matchesManagedPermissionVariants(content, templateWithoutQGAndReview)) {
235
259
  return false;
236
260
  }
261
+ // Check variants without quality gates but with previous review block
262
+ const templateWithoutQGButPreviousReview = templateWithoutQG.replace(REVIEW_TEMPLATE_BLOCK, PREVIOUS_REVIEW_TEMPLATE_BLOCK);
263
+ if (matchesManagedPermissionVariants(content, templateWithoutQGButPreviousReview)) {
264
+ return false;
265
+ }
237
266
  const legacyTemplate = normalizeManagedRalphrcContent(renderLegacyRalphrcTemplate(platformId));
238
267
  return content !== legacyTemplate;
239
268
  }
@@ -123,11 +123,28 @@ export async function validateRalphLoop(projectDir) {
123
123
  throw new Error(`${RALPH_LOOP_PATH} not found. Run: bmalph init`);
124
124
  }
125
125
  }
126
+ export async function validateGitRepo(projectDir) {
127
+ const gitDir = await runBashCommand("git rev-parse --git-dir", { cwd: projectDir });
128
+ if (gitDir.exitCode !== 0) {
129
+ throw new Error("No git repository found. Ralph requires git for progress detection.\n" +
130
+ "Run: git init && git add -A && git commit -m 'initial commit'");
131
+ }
132
+ const head = await runBashCommand("git rev-parse HEAD", { cwd: projectDir });
133
+ if (head.exitCode !== 0) {
134
+ throw new Error("Git repository has no commits. Ralph requires at least one commit for progress detection.\n" +
135
+ "Run: git add -A && git commit -m 'initial commit'");
136
+ }
137
+ }
126
138
  export function spawnRalphLoop(projectDir, platformId, options) {
127
139
  const env = { ...process.env, PLATFORM_DRIVER: platformId };
128
- if (options.reviewEnabled) {
129
- env.REVIEW_ENABLED = "true";
130
- env.REVIEW_INTERVAL = "5";
140
+ if (options.reviewMode) {
141
+ env.REVIEW_MODE = options.reviewMode;
142
+ if (options.reviewMode !== "off") {
143
+ env.REVIEW_ENABLED = "true";
144
+ if (options.reviewMode === "enhanced") {
145
+ env.REVIEW_INTERVAL = "5";
146
+ }
147
+ }
131
148
  }
132
149
  const child = spawn(cachedBashCommand ?? "bash", [BASH_RALPH_LOOP_PATH], {
133
150
  cwd: projectDir,
@@ -1,15 +1,16 @@
1
+ import { formatExitReason } from "../utils/format-status.js";
1
2
  import { createRefreshCallback } from "../watch/dashboard.js";
2
3
  import { createTerminalFrameWriter } from "../watch/frame-writer.js";
3
4
  import { FileWatcher } from "../watch/file-watcher.js";
4
5
  import { renderFooterLine } from "../watch/renderer.js";
5
- export function renderStatusBar(ralph, reviewEnabled) {
6
+ export function renderStatusBar(ralph, reviewMode) {
6
7
  const pid = ralph.child.pid ?? "?";
7
- const badge = reviewEnabled ? " [review]" : "";
8
+ const badge = reviewMode === "ultimate" ? " [ultimate]" : reviewMode === "enhanced" ? " [review]" : "";
8
9
  switch (ralph.state) {
9
10
  case "running":
10
11
  return `Ralph: running (PID ${pid})${badge} | q: stop/detach`;
11
12
  case "stopped":
12
- return `Ralph: stopped (exit ${ralph.exitCode ?? "?"}) | q: quit`;
13
+ return `Ralph: stopped ${formatExitReason(ralph.exitCode)} | q: quit`;
13
14
  case "detached":
14
15
  return `Ralph: detached (PID ${pid})`;
15
16
  }
@@ -18,12 +19,12 @@ export function renderQuitPrompt() {
18
19
  return "Stop (s) | Detach (d) | Cancel (c)";
19
20
  }
20
21
  export async function startRunDashboard(options) {
21
- const { projectDir, interval, ralph, reviewEnabled } = options;
22
+ const { projectDir, interval, ralph, reviewMode } = options;
22
23
  const frameWriter = createTerminalFrameWriter();
23
24
  let showingPrompt = false;
24
25
  let stopped = false;
25
26
  const footerRenderer = (lastUpdated, cols) => {
26
- const leftText = showingPrompt ? renderQuitPrompt() : renderStatusBar(ralph, reviewEnabled);
27
+ const leftText = showingPrompt ? renderQuitPrompt() : renderStatusBar(ralph, reviewMode);
27
28
  return renderFooterLine(leftText, `Updated: ${lastUpdated.toISOString().slice(11, 19)}`, cols);
28
29
  };
29
30
  const refresh = createRefreshCallback(projectDir, (frame) => {
@@ -22,13 +22,11 @@ export async function generateContextOutputs(projectDir, inputs) {
22
22
  info("Generating PROJECT_CONTEXT.md...");
23
23
  const projectContextPath = join(projectDir, ".ralph/PROJECT_CONTEXT.md");
24
24
  const projectContextExisted = await exists(projectContextPath);
25
- let projectContext = null;
26
25
  let truncationWarnings = [];
27
26
  if (inputs.artifactContents.size > 0) {
28
27
  const { context, truncated } = extractProjectContext(inputs.artifactContents);
29
- projectContext = context;
30
28
  truncationWarnings = detectTruncation(truncated);
31
- const contextMd = generateProjectContextMd(projectContext, projectName);
29
+ const contextMd = generateProjectContextMd(context, projectName);
32
30
  await atomicWriteFile(projectContextPath, contextMd);
33
31
  generatedFiles.push({
34
32
  path: ".ralph/PROJECT_CONTEXT.md",
@@ -46,7 +44,7 @@ export async function generateContextOutputs(projectDir, inputs) {
46
44
  prompt = existingPrompt.replace(/\[YOUR PROJECT NAME\]/g, projectName);
47
45
  }
48
46
  else {
49
- prompt = generatePrompt(projectName, projectContext ?? undefined);
47
+ prompt = generatePrompt(projectName);
50
48
  }
51
49
  }
52
50
  catch (err) {
@@ -56,7 +54,7 @@ export async function generateContextOutputs(projectDir, inputs) {
56
54
  else {
57
55
  warn(`Could not read existing PROMPT.md: ${formatError(err)}`);
58
56
  }
59
- prompt = generatePrompt(projectName, projectContext ?? undefined);
57
+ prompt = generatePrompt(projectName);
60
58
  }
61
59
  await atomicWriteFile(join(projectDir, ".ralph/PROMPT.md"), prompt);
62
60
  generatedFiles.push({ path: ".ralph/PROMPT.md", action: promptExisted ? "updated" : "created" });
@@ -186,39 +186,22 @@ export function generateProjectContextMd(context, projectName) {
186
186
  }
187
187
  return lines.join("\n");
188
188
  }
189
- export function generatePrompt(projectName, context) {
190
- // Build context sections if provided
191
- const contextSections = context
192
- ? [
193
- context.projectGoals && `### Project Goals\n${context.projectGoals}`,
194
- context.successMetrics && `### Success Metrics\n${context.successMetrics}`,
195
- context.architectureConstraints &&
196
- `### Architecture Constraints\n${context.architectureConstraints}`,
197
- context.scopeBoundaries && `### Scope\n${context.scopeBoundaries}`,
198
- context.technicalRisks && `### Technical Risks\n${context.technicalRisks}`,
199
- context.targetUsers && `### Target Users\n${context.targetUsers}`,
200
- context.nonFunctionalRequirements &&
201
- `### Non-Functional Requirements\n${context.nonFunctionalRequirements}`,
202
- context.designGuidelines && `### Design Guidelines\n${context.designGuidelines}`,
203
- context.researchInsights && `### Research Insights\n${context.researchInsights}`,
204
- ]
205
- .filter(Boolean)
206
- .join("\n\n")
207
- : "";
208
- const projectContextBlock = contextSections
209
- ? `
210
-
211
- ## Project Specifications (CRITICAL - READ THIS)
212
-
213
- ${contextSections}
214
- `
215
- : "";
189
+ export function generatePrompt(projectName) {
216
190
  return `# Ralph Development Instructions
217
191
 
218
192
  ## Context
219
193
  You are an autonomous AI development agent working on the ${projectName} project.
220
194
  You follow BMAD-METHOD's developer (Amelia) persona and TDD methodology.
221
- ${projectContextBlock}
195
+
196
+ ## Current Objectives
197
+ 1. Read .ralph/@fix_plan.md and identify the next incomplete story
198
+ 2. Check existing codebase for related code — especially which existing files need changes to integrate your work
199
+ 3. Implement the story using TDD (red-green-refactor)
200
+ 4. Run tests after implementation
201
+ 5. Toggle the completed story checkbox in @fix_plan.md from \`- [ ]\` to \`- [x]\`
202
+ 6. Commit with descriptive conventional commit message
203
+ 7. Read specs ONLY if the story's inline acceptance criteria are insufficient
204
+
222
205
  ## Development Methodology (BMAD Dev Agent)
223
206
 
224
207
  For each story in @fix_plan.md:
@@ -226,30 +209,27 @@ For each story in @fix_plan.md:
226
209
  2. Write failing tests first (RED)
227
210
  3. Implement minimum code to pass tests (GREEN)
228
211
  4. Refactor while keeping tests green (REFACTOR)
229
- 5. Toggle the completed story checkbox in @fix_plan.md from \`- [ ]\` to \`- [x]\`
212
+ 5. Toggle the completed story checkbox
230
213
  6. Commit with descriptive conventional commit message
231
214
 
232
- ## Specs Reading Strategy
233
- 1. Read .ralph/SPECS_INDEX.md first for a prioritized overview of all spec files
234
- 2. Follow the reading order in SPECS_INDEX.md:
235
- - **Critical**: Always read fully (PRD, architecture, stories)
236
- - **High**: Read for implementation details (test design, readiness)
237
- - **Medium**: Reference as needed (UX specs, sprint plans)
238
- - **Low**: Optional background (brainstorming sessions)
239
- 3. For files marked [LARGE], scan headers first and read relevant sections
215
+ ## Specs Reference (Read On Demand)
216
+ - .ralph/SPECS_INDEX.md lists all spec files with paths and priorities
217
+ - .ralph/PROJECT_CONTEXT.md summarizes project goals, constraints, and scope
218
+ - Read specific specs only when the current story requires clarification
219
+ - For files marked [LARGE] in SPECS_INDEX.md, scan headers first
240
220
 
241
- ## Current Objectives
242
- 1. Read .ralph/PROJECT_CONTEXT.md for project goals, constraints, and scope
243
- 2. Read .ralph/SPECS_INDEX.md for prioritized spec file overview
244
- 3. Study .ralph/specs/ following the reading order in SPECS_INDEX.md
245
- 4. Use the exact spec paths listed in SPECS_INDEX.md instead of assuming a fixed subdirectory layout
246
- 5. Prioritize planning specs first (PRD, architecture, epics/stories, test design, UX)
247
- 6. Review implementation artifacts next (sprint plans, detailed stories) when they exist
248
- 7. Check docs/ for project knowledge and research documents (if present)
249
- 8. Review .ralph/@fix_plan.md for current priorities
250
- 9. Implement the highest priority story using TDD
251
- 10. Run tests after each implementation
252
- 11. Update the completed story checkbox in @fix_plan.md before committing
221
+ ## Key Principles
222
+ - Write code within the first few minutes of each loop
223
+ - ONE story per loop - focus completely on it
224
+ - TDD: tests first, always
225
+ - Search the codebase before assuming something isn't implemented
226
+ - Creating new files is often only half the task — wire them into the existing application
227
+ - Commit working changes with descriptive messages
228
+
229
+ ## Session Continuity
230
+ - If you have context from a previous loop, do NOT re-read spec files
231
+ - Resume implementation where you left off
232
+ - Only consult specs when you encounter ambiguity in the current story
253
233
 
254
234
  ## Progress Tracking (CRITICAL)
255
235
  - Ralph tracks progress by counting story checkboxes in @fix_plan.md
@@ -259,12 +239,12 @@ For each story in @fix_plan.md:
259
239
  - Set \`TASKS_COMPLETED_THIS_LOOP\` to the exact number of story checkboxes toggled this loop
260
240
  - Only valid values: 0 or 1
261
241
 
262
- ## Key Principles
263
- - ONE story per loop - focus completely on it
264
- - TDD: tests first, always
265
- - Search the codebase before assuming something isn't implemented
266
- - Write comprehensive tests with clear documentation
267
- - Commit working changes with descriptive messages
242
+ ## Execution Guidelines
243
+ - Before making changes: search codebase using subagents
244
+ - After implementation: run essential tests for the modified code
245
+ - If tests fail: fix them as part of your current work
246
+ - Keep .ralph/@AGENT.md updated with build/run instructions
247
+ - No placeholder implementations - build it properly
268
248
 
269
249
  ## Testing Guidelines
270
250
  - Write tests BEFORE implementation (TDD)
@@ -272,6 +252,25 @@ For each story in @fix_plan.md:
272
252
  - Run the full test suite after implementation
273
253
  - Fix any regressions immediately
274
254
 
255
+ ## Autonomous Mode (CRITICAL)
256
+ - do not ask the user questions during loop execution
257
+ - do not use AskUserQuestion, EnterPlanMode, or ExitPlanMode during loop execution
258
+ - make the safest reasonable assumption and continue
259
+ - prefer small, reversible changes when requirements are ambiguous
260
+ - surface blockers in the Ralph status block instead of starting a conversation
261
+
262
+ ## Self-Review Checklist (Before Reporting Status)
263
+
264
+ Before writing your RALPH_STATUS block, review your own work:
265
+
266
+ 1. Re-read the diff of files you modified this loop — check for obvious bugs, typos, missing error handling
267
+ 2. Verify you did not introduce regressions in existing functionality
268
+ 3. Confirm your changes match the spec in .ralph/specs/ for the story you worked on
269
+ 4. Check that new functions have proper error handling and edge case coverage
270
+ 5. Ensure you did not leave TODO/FIXME/HACK comments without justification
271
+
272
+ If you find issues, fix them before reporting status.
273
+
275
274
  ## Status Reporting (CRITICAL)
276
275
 
277
276
  At the end of your response, ALWAYS include this status block:
@@ -3,7 +3,7 @@ import { join, relative } from "node:path";
3
3
  import { debug, info, warn } from "../utils/logger.js";
4
4
  import { isEnoent, formatError } from "../utils/errors.js";
5
5
  import { atomicWriteFile, exists } from "../utils/file-system.js";
6
- import { generateFixPlan, parseFixPlan, mergeFixPlanProgress, detectOrphanedCompletedStories, detectRenumberedStories, buildCompletedTitleMap, normalizeTitle, } from "./fix-plan.js";
6
+ import { generateFixPlan, parseFixPlan, mergeFixPlanProgress, collapseCompletedStories, detectOrphanedCompletedStories, detectRenumberedStories, buildCompletedTitleMap, normalizeTitle, } from "./fix-plan.js";
7
7
  import { parseSprintStatus } from "./sprint-status.js";
8
8
  export async function syncFixPlan(projectDir, inputs) {
9
9
  let completedIds = new Set();
@@ -73,7 +73,8 @@ export async function syncFixPlan(projectDir, inputs) {
73
73
  info(`Generating fix plan for ${inputs.stories.length} stories...`);
74
74
  const newFixPlan = generateFixPlan(inputs.stories, undefined, inputs.planningSpecsSubpath);
75
75
  const mergedFixPlan = mergeFixPlanProgress(newFixPlan, completedIds, useTitleBasedMerge ? newTitleMap : undefined, useTitleBasedMerge ? completedTitles : undefined);
76
- await atomicWriteFile(fixPlanPath, mergedFixPlan);
76
+ const compactedFixPlan = collapseCompletedStories(mergedFixPlan);
77
+ await atomicWriteFile(fixPlanPath, compactedFixPlan);
77
78
  return {
78
79
  warnings: [...completionWarnings, ...orphanWarnings, ...renumberWarnings],
79
80
  fixPlanPreserved,
@@ -108,6 +108,26 @@ export function buildCompletedTitleMap(items) {
108
108
  }
109
109
  return map;
110
110
  }
111
+ const COMPLETED_STORY_LINE = /^\s*-\s*\[[xX]\]\s*Story\s+[\d.]+:/;
112
+ const INDENTED_DETAIL_LINE = /^\s+>/;
113
+ export function collapseCompletedStories(fixPlan) {
114
+ const lines = fixPlan.split("\n");
115
+ const result = [];
116
+ let skippingDetails = false;
117
+ for (const line of lines) {
118
+ if (COMPLETED_STORY_LINE.test(line)) {
119
+ skippingDetails = true;
120
+ result.push(line);
121
+ continue;
122
+ }
123
+ if (skippingDetails && INDENTED_DETAIL_LINE.test(line)) {
124
+ continue;
125
+ }
126
+ skippingDetails = false;
127
+ result.push(line);
128
+ }
129
+ return result.join("\n");
130
+ }
111
131
  export function mergeFixPlanProgress(newFixPlan, completedIds, titleMap, completedTitles) {
112
132
  // Replace [ ] with [x] for completed story IDs or title matches
113
133
  return newFixPlan.replace(createOpenFixPlanStoryLinePattern(), (match, prefix, suffix, id) => {
@@ -1,5 +1,5 @@
1
1
  function headingPattern(expression) {
2
- return new RegExp(`^##\\s+${expression}(?:\\s*:)?\\s*$`, "im");
2
+ return new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${expression}(?:\\s*:)?\\s*$`, "im");
3
3
  }
4
4
  export const PROJECT_GOALS_SECTION_PATTERNS = [
5
5
  headingPattern("Executive Summary"),
@@ -147,8 +147,8 @@ export function formatSpecsIndexMd(index) {
147
147
  "",
148
148
  ];
149
149
  const priorityConfig = [
150
- { key: "critical", heading: "Critical (Always Read First)" },
151
- { key: "high", heading: "High Priority (Read for Implementation)" },
150
+ { key: "critical", heading: "Critical (Read When Needed for Current Story)" },
151
+ { key: "high", heading: "High Priority (Reference as Needed)" },
152
152
  { key: "medium", heading: "Medium Priority (Reference as Needed)" },
153
153
  { key: "low", heading: "Low Priority (Optional)" },
154
154
  ];
@@ -1,4 +1,17 @@
1
1
  import chalk from "chalk";
2
+ const EXIT_CODE_LABELS = new Map([
3
+ [0, "completed"],
4
+ [1, "error"],
5
+ [124, "timed out"],
6
+ [130, "interrupted (SIGINT)"],
7
+ [137, "killed (OOM or SIGKILL)"],
8
+ [143, "terminated (SIGTERM)"],
9
+ ]);
10
+ export function formatExitReason(code) {
11
+ if (code === null)
12
+ return "unknown";
13
+ return EXIT_CODE_LABELS.get(code) ?? `error (exit ${code})`;
14
+ }
2
15
  /**
3
16
  * Shared status formatting with chalk colors.
4
17
  *
@@ -296,7 +296,8 @@ export function renderAnalysisPanel(analysis, cols) {
296
296
  const lines = [
297
297
  [
298
298
  `Files: ${String(analysis.filesModified)}`,
299
- `Confidence: ${String(analysis.confidenceScore)}%`,
299
+ `Parse: ${String(analysis.formatConfidence)}%`,
300
+ `Completion: ${String(analysis.confidenceScore)}%`,
300
301
  ].join(" "),
301
302
  [`Test-only: ${yesNo(analysis.isTestOnly)}`, `Stuck: ${yesNo(analysis.isStuck)}`].join(" "),
302
303
  [
@@ -95,6 +95,7 @@ export async function readAnalysisInfo(projectDir) {
95
95
  return null;
96
96
  const a = analysis;
97
97
  const filesModified = typeof a.files_modified === "number" ? a.files_modified : 0;
98
+ const formatConfidence = typeof a.format_confidence === "number" ? a.format_confidence : 0;
98
99
  const confidenceScore = typeof a.confidence_score === "number" ? a.confidence_score : 0;
99
100
  const isTestOnly = typeof a.is_test_only === "boolean" ? a.is_test_only : false;
100
101
  const isStuck = typeof a.is_stuck === "boolean" ? a.is_stuck : false;
@@ -108,6 +109,7 @@ export async function readAnalysisInfo(projectDir) {
108
109
  const permissionDenialCount = typeof a.permission_denial_count === "number" ? a.permission_denial_count : 0;
109
110
  return {
110
111
  filesModified,
112
+ formatConfidence,
111
113
  confidenceScore,
112
114
  isTestOnly,
113
115
  isStuck,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmalph",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Unified AI Development Framework - BMAD phases with Ralph execution loop",
5
5
  "type": "module",
6
6
  "bin": {