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 +7 -7
- package/dist/cli.js +2 -1
- package/dist/commands/doctor-checks.js +28 -0
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/run.js +34 -17
- package/dist/installer/template-files.js +29 -0
- package/dist/run/ralph-process.js +20 -3
- package/dist/run/run-dashboard.js +6 -5
- package/dist/transition/context-output.js +3 -5
- package/dist/transition/context.js +54 -55
- package/dist/transition/fix-plan-sync.js +3 -2
- package/dist/transition/fix-plan.js +20 -0
- package/dist/transition/section-patterns.js +1 -1
- package/dist/transition/specs-index.js +2 -2
- package/dist/utils/format-status.js +13 -0
- package/dist/watch/renderer.js +2 -1
- package/dist/watch/state-reader.js +2 -0
- package/package.json +1 -1
- package/ralph/drivers/DRIVER_INTERFACE.md +422 -0
- package/ralph/drivers/codex.sh +2 -2
- package/ralph/lib/response_analyzer.sh +114 -102
- package/ralph/ralph_loop.sh +318 -31
- package/ralph/templates/PROMPT.md +15 -8
- package/ralph/templates/ralphrc.template +8 -3
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
|
|
283
|
-
|
|
|
284
|
-
| `--driver <platform>`
|
|
285
|
-
| `--review
|
|
286
|
-
| `--interval <ms>`
|
|
287
|
-
| `--no-dashboard`
|
|
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
|
|
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", "
|
|
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
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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 },
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
|
28
|
-
if (
|
|
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([
|
|
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
|
-
|
|
53
|
+
reviewMode,
|
|
47
54
|
});
|
|
48
55
|
if (useDashboard) {
|
|
49
|
-
await startRunDashboard({ projectDir, interval, ralph,
|
|
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 ===
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
98
|
+
return "off";
|
|
85
99
|
}
|
|
86
100
|
if (!process.stdin.isTTY) {
|
|
87
|
-
return
|
|
101
|
+
return "off";
|
|
88
102
|
}
|
|
89
103
|
const { default: select } = await import("@inquirer/select");
|
|
90
|
-
|
|
104
|
+
return select({
|
|
91
105
|
message: "Quality mode:",
|
|
92
106
|
choices: [
|
|
93
|
-
{ name: "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: "
|
|
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.
|
|
129
|
-
env.
|
|
130
|
-
|
|
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,
|
|
6
|
+
export function renderStatusBar(ralph, reviewMode) {
|
|
6
7
|
const pid = ralph.child.pid ?? "?";
|
|
7
|
-
const badge =
|
|
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
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
212
|
+
5. Toggle the completed story checkbox
|
|
230
213
|
6. Commit with descriptive conventional commit message
|
|
231
214
|
|
|
232
|
-
## Specs
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
##
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
##
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
151
|
-
{ key: "high", heading: "High Priority (
|
|
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
|
*
|
package/dist/watch/renderer.js
CHANGED
|
@@ -296,7 +296,8 @@ export function renderAnalysisPanel(analysis, cols) {
|
|
|
296
296
|
const lines = [
|
|
297
297
|
[
|
|
298
298
|
`Files: ${String(analysis.filesModified)}`,
|
|
299
|
-
`
|
|
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,
|