ralph-prd 1.1.0 → 3.0.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
@@ -2,44 +2,27 @@
2
2
 
3
3
  AI-powered phased implementation runner for [Claude Code](https://claude.ai/claude-code). Go from PRD to shipped code — automatically.
4
4
 
5
- Ralph takes a markdown plan file, breaks it into phases, and executes each one through the Claude CLI with built-in verification, repair loops, and auto-commits.
5
+ Ralph takes a markdown plan file, breaks it into phases and tasks, and executes each one through the Claude CLI with built-in verification, repair loops, and auto-commits.
6
6
 
7
7
  ## Install
8
8
 
9
- One command installs Ralph and all skills into your project's `.claude/` directory:
10
-
11
9
  ```bash
12
- # Using npx (recommended)
13
- npx ralph-prd
14
-
15
- # Or using curl
16
- curl -fsSL https://raw.githubusercontent.com/tahaJemmali/ralph-prd/main/install.sh | bash
17
-
18
- # Or clone and run locally
19
- git clone https://github.com/tahaJemmali/ralph-prd.git
20
- cd your-project && ../ralph-prd/install.sh
10
+ npx ralph-prd init
21
11
  ```
22
12
 
23
- This installs:
24
- - `.claude/ralph/` — the phased runner
25
- - `.claude/skills/` — 7 Claude Code skills fetched from [`tahaJemmali/skills`](https://github.com/tahaJemmali/skills) via `npx skills add`
26
- - On first install, adds `.claude/ralph/` and `.claude/skills/` to `.gitignore` (skipped if `.claude/` already exists, so shared setups are respected)
13
+ This installs skills to `.claude/skills/` and scaffolds a config file. Ralph itself runs directly from the npm package — no files are copied to your project.
27
14
 
28
- ### Updating
15
+ Skills are fetched from [`tahaJemmali/skills`](https://github.com/tahaJemmali/skills). Installation retries automatically (3 attempts) and falls back to cached skills on network failure.
29
16
 
30
- To update Ralph and re-fetch skills, just re-run the install command:
17
+ ### Updating
31
18
 
32
19
  ```bash
33
- npx ralph-prd
20
+ npx ralph-prd init
34
21
  ```
35
22
 
36
- Ralph also checks for updates automatically on every run. If a newer version is available, you'll see a notice in the console output.
23
+ Re-running init re-fetches skills. Your `ralph.config.yaml` is preserved.
37
24
 
38
- To update only the skills without reinstalling:
39
-
40
- ```bash
41
- node .claude/ralph/ralph-claude.mjs --update-skills
42
- ```
25
+ Ralph also checks for updates automatically on every run.
43
26
 
44
27
  ## Requirements
45
28
 
@@ -68,15 +51,21 @@ Run `/prd-to-plan`. Claude reads the PRD, identifies durable architectural decis
68
51
  ### 4. Execute with Ralph
69
52
 
70
53
  ```bash
71
- node .claude/ralph/ralph-claude.mjs docs/<feature>/plan.md
54
+ npx ralph-prd run docs/<feature>/plan.md
72
55
  ```
73
56
 
74
57
  Ralph runs each phase through Claude:
75
- 1. **Implementation** — Claude builds the phase
76
- 2. **Verification** — checks acceptance criteria pass
77
- 3. **Repair loop** — if verification fails, auto-repairs (up to N attempts)
78
- 4. **Commit** — stages and commits changes
79
- 5. **Checkpoint** — saves progress for crash recovery
58
+ 1. **Task-level implementation** — each phase is broken into individual tasks (user stories) and implemented one at a time for focused, atomic progress
59
+ 2. **Per-task commit** — every task gets its own commit with enriched messages (decisions, blockers, notes for next task)
60
+ 3. **Verification** — checks acceptance criteria pass after all tasks in the phase complete
61
+ 4. **Repair loop** — if verification fails, auto-repairs (up to N attempts)
62
+ 5. **Ship-check** — post-commit quality gate
63
+ 6. **Checkpoint** — saves progress per-task for crash recovery
64
+
65
+ Each implementation session receives:
66
+ - The **PRD** for business context (resolved from the plan's `> Source PRD:` line)
67
+ - The **last 5 commits** per repo so tasks build on each other's work
68
+ - The **full plan** for cross-phase awareness
80
69
 
81
70
  ### 5. Ship check
82
71
 
@@ -85,38 +74,67 @@ Run `/ship-check` to validate the final result against your project's documented
85
74
  ## CLI Reference
86
75
 
87
76
  ```bash
88
- node .claude/ralph/ralph-claude.mjs <plan-file.md> [OPTIONS]
89
-
90
- Options:
91
- --dry-run Preview all phases without executing
92
- --reset Delete state and restart from Phase 1
93
- --only-phase N Force re-run phase N (1-based)
94
- --i-did-this Skip Claude self-commit; run separate commit step
95
- --send-it Push branch + open PR when all phases complete
96
- --wait-for-it Pause before each commit for review
97
- --skip-ship-check Skip the post-commit ship-check step entirely
98
- --ship-check-retries=N Retry ship-check up to N times per phase before giving up (default 1)
99
- --skip-on-ship-check-fail Log and continue when all ship-check retries fail instead of hard-stopping
100
- --skip-on-verify-fail Skip verification and continue instead of hard-stopping when all repair attempts fail
101
- --update-skills Re-fetch skills from tahaJemmali/skills and exit
102
- --version, -v Print installed version and exit
77
+ npx ralph-prd init Install skills and scaffold config
78
+ npx ralph-prd run <plan.md> [OPTIONS] Execute a plan
79
+ npx ralph-prd --version Print version
80
+
81
+ Run options:
82
+ --dry-run Preview all phases without executing
83
+ --reset Delete state and restart from Phase 1
84
+ --only-phase N Force re-run phase N (1-based)
85
+ --i-did-this Skip Claude self-commit; run separate commit step
86
+ --send-it Push branch + open PR when all phases complete
87
+ --wait-for-it Pause before each commit for review
88
+ --skip-ship-check Skip the post-commit ship-check step entirely
89
+ --ship-check-retries=N Retry ship-check up to N times (default 1)
90
+ --skip-on-ship-check-fail Log and continue when ship-check fails
91
+ --skip-on-verify-fail Skip verification when all repair attempts fail
92
+ --log-level=LEVEL none | necessary | dump (default: necessary)
93
+ --update-skills Re-fetch skills and exit
103
94
  ```
104
95
 
105
96
  ### Examples
106
97
 
107
98
  ```bash
108
99
  # Resume from last incomplete phase
109
- node .claude/ralph/ralph-claude.mjs docs/auth-rework/plan.md
100
+ npx ralph-prd run docs/auth-rework/plan.md
110
101
 
111
102
  # Preview the plan
112
- node .claude/ralph/ralph-claude.mjs docs/auth-rework/plan.md --dry-run
103
+ npx ralph-prd run docs/auth-rework/plan.md --dry-run
113
104
 
114
105
  # Re-run only Phase 3
115
- node .claude/ralph/ralph-claude.mjs docs/auth-rework/plan.md --only-phase 3
106
+ npx ralph-prd run docs/auth-rework/plan.md --only-phase 3
116
107
 
117
108
  # Ship it: run all phases, push, open PR
118
- node .claude/ralph/ralph-claude.mjs docs/auth-rework/plan.md --send-it
109
+ npx ralph-prd run docs/auth-rework/plan.md --send-it
110
+
111
+ # Legacy invocation (still works)
112
+ npx ralph-prd docs/auth-rework/plan.md
113
+ ```
114
+
115
+ ## Commit Messages
116
+
117
+ Ralph produces enriched commit messages with context that carries forward between tasks:
118
+
119
119
  ```
120
+ ralph: add user authentication endpoint
121
+
122
+ - Added POST /auth/login route with JWT response
123
+ - Created JWT token generation utility
124
+
125
+ Decisions:
126
+ - Chose bcrypt over argon2 for password hashing (broader ecosystem support)
127
+ - Used 15-minute JWT expiry with refresh token pattern per PRD requirement
128
+
129
+ Blockers:
130
+ - Redis session store not available yet (Phase 3) — using in-memory Map
131
+
132
+ Next:
133
+ - Token refresh endpoint needs the auth middleware created here
134
+ - Rate limiting should be added before login goes to production
135
+ ```
136
+
137
+ The **Decisions**, **Blockers**, and **Next** sections are optional — included only when relevant.
120
138
 
121
139
  ## Included Skills
122
140
 
@@ -132,9 +150,9 @@ node .claude/ralph/ralph-claude.mjs docs/auth-rework/plan.md --send-it
132
150
 
133
151
  Skills are fetched from [`tahaJemmali/skills`](https://github.com/tahaJemmali/skills) during install. You can also install or update them independently with `npx skills add tahaJemmali/skills`.
134
152
 
135
- ## Multi-Repo Support
153
+ ## Configuration
136
154
 
137
- Create `ralph.config.yaml` in `.claude/ralph/` for monorepo setups:
155
+ Create `.claude/ralph.config.yaml` for multi-repo setups or to customize flags:
138
156
 
139
157
  ```yaml
140
158
  repos:
@@ -158,6 +176,8 @@ hooks:
158
176
  afterCommit: npm test
159
177
  ```
160
178
 
179
+ Config lookup order: `.claude/ralph.config.yaml` (canonical) → `.claude/ralph/ralph.config.yaml` (legacy). If no config exists, Ralph uses the current directory as the single repo.
180
+
161
181
  ## Plan File Format
162
182
 
163
183
  Plans must follow the template produced by `/prd-to-plan`:
@@ -180,7 +200,9 @@ Plans must follow the template produced by `/prd-to-plan`:
180
200
 
181
201
  ### What to build
182
202
 
183
- Description of the vertical slice.
203
+ 1. First task — implement the login endpoint
204
+ 2. Second task — add JWT token generation
205
+ 3. Third task — wire up password hashing
184
206
 
185
207
  ### Acceptance criteria
186
208
 
@@ -190,15 +212,31 @@ Description of the vertical slice.
190
212
 
191
213
  Ralph validates the plan structure before execution and checks off criteria as phases complete.
192
214
 
215
+ The "What to build" section is parsed into individual tasks. Numbered items (`1.`, `2.`) or bullet items (`-`, `*`) each become a separate implementation session with its own commit. If the section is a single paragraph, the entire phase runs as one task.
216
+
193
217
  ## How It Works
194
218
 
195
219
  - **Zero dependencies** — pure Node.js, no npm packages
196
- - **Crash recovery** — checkpoints after each step; resume where you left off
220
+ - **Runs from package** — no files copied to your project (only skills + config)
221
+ - **Task-level execution** — phases are broken into focused tasks, each with its own implementation + commit cycle
222
+ - **PRD context** — every session gets the source PRD and recent git history
223
+ - **Enriched commits** — decisions, blockers, and notes carry knowledge between tasks
224
+ - **Crash recovery** — checkpoints after each task; resume where you left off
225
+ - **Atomic state writes** — write-then-rename prevents corruption on crash
197
226
  - **Live streaming** — see Claude's tool calls, thinking, and output in real-time
198
227
  - **Full logging** — JSONL logs of every Claude CLI event per phase
199
- - **macOS notifications** — get notified when phases complete or fail
228
+ - **Cross-platform notifications** — macOS, Linux (notify-send), and terminal bell fallback
229
+ - **Resilient installs** — retries skill installation, falls back to cached skills on network failure
200
230
  - **Safety** — optional `blocked-commands.txt` and `blocked-paths.txt` restrict what Claude can do
201
231
 
232
+ ## Public API
233
+
234
+ ralph-prd exports a stable API for use by orchestrators like [ralph-prd-afk](https://github.com/tahaJemmali/ralph-prd-afk):
235
+
236
+ ```javascript
237
+ import { send, preflight, getCumulativeCost } from 'ralph-prd/transport';
238
+ ```
239
+
202
240
  ## Acknowledgments
203
241
 
204
242
  Three of the skills in this repo — [`/grill-me`](https://skills.sh/mattpocock/skills/grill-me), [`/write-a-prd`](https://skills.sh/mattpocock/skills/write-a-prd), and [`/prd-to-plan`](https://skills.sh/mattpocock/skills/prd-to-plan) — are based on [Matt Pocock](https://github.com/mattpocock)'s work. Matt generously gave his blessing to include them here. If you find these useful, go check out his cohort and give him a star.
package/bin/cli.mjs ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/cli.mjs — ralph-prd CLI router
4
+ *
5
+ * Usage:
6
+ * npx ralph-prd init Install skills and scaffold config
7
+ * npx ralph-prd run <plan.md> Execute a plan
8
+ * npx ralph-prd --version Print version
9
+ *
10
+ * The `init` subcommand installs skills to .claude/skills/ and scaffolds
11
+ * ralph.config.yaml. It does NOT copy the runner — ralph-prd executes
12
+ * directly from the npm package.
13
+ *
14
+ * For backward compatibility, running `npx ralph-prd` without a subcommand
15
+ * still triggers init (legacy behavior).
16
+ */
17
+
18
+ import { existsSync, readFileSync } from 'fs';
19
+ import { resolve, dirname } from 'path';
20
+ import { fileURLToPath } from 'url';
21
+
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const PKG_ROOT = resolve(__dirname, '..');
24
+
25
+ // ─── Version ─────────────────────────────────────────────────────────────────
26
+
27
+ const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, 'package.json'), 'utf8'));
28
+ const VERSION = pkg.version;
29
+
30
+ const args = process.argv.slice(2);
31
+
32
+ if (args.includes('--version') || args.includes('-v')) {
33
+ console.log(`ralph-prd v${VERSION}`);
34
+ process.exit(0);
35
+ }
36
+
37
+ // ─── Subcommand routing ──────────────────────────────────────────────────────
38
+
39
+ const subcommand = args.find(a => !a.startsWith('--'));
40
+
41
+ if (subcommand === 'run') {
42
+ // npx ralph-prd run <plan.md> [flags]
43
+ const runArgs = args.filter(a => a !== 'run');
44
+ const planArg = runArgs.find(a => !a.startsWith('--'));
45
+
46
+ if (!planArg) {
47
+ console.error('Usage: npx ralph-prd run <plan-file.md> [OPTIONS]');
48
+ process.exit(1);
49
+ }
50
+
51
+ // Import and run ralph-claude.mjs directly from the package
52
+ // Override process.argv so ralph-claude.mjs sees the correct args
53
+ process.argv = [process.argv[0], resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'), ...runArgs];
54
+
55
+ await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
56
+
57
+ } else if (subcommand === 'init' || !subcommand) {
58
+ // npx ralph-prd init OR npx ralph-prd (legacy: bare invocation = init)
59
+ const { install } = await import('./install.mjs');
60
+ await install();
61
+
62
+ } else if (subcommand === '--update-skills') {
63
+ // npx ralph-prd --update-skills
64
+ process.argv = [process.argv[0], 'ralph-claude.mjs', '--update-skills'];
65
+ await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
66
+
67
+ } else {
68
+ // Assume it's a plan file path (legacy: npx ralph-prd docs/feature/plan.md)
69
+ // Treat bare arguments as plan files for backward compat
70
+ process.argv = [process.argv[0], resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'), ...args];
71
+ await import(resolve(PKG_ROOT, 'ralph', 'ralph-claude.mjs'));
72
+ }
package/bin/install.mjs CHANGED
@@ -48,6 +48,13 @@ mkdirSync(claudeDir, { recursive: true });
48
48
  // Copy ralph runner
49
49
  const ralphSrc = resolve(PKG_ROOT, 'ralph');
50
50
  const ralphDst = resolve(claudeDir, 'ralph');
51
+ // Preserve user config across updates
52
+ const configDst = resolve(ralphDst, 'ralph.config.yaml');
53
+ let savedConfig = null;
54
+ if (existsSync(configDst)) {
55
+ savedConfig = readFileSync(configDst, 'utf8');
56
+ }
57
+
51
58
  if (existsSync(ralphDst)) {
52
59
  info('Updating existing .claude/ralph/');
53
60
  rmSync(ralphDst, { recursive: true });
@@ -62,16 +69,19 @@ const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, 'package.json'), 'utf8'));
62
69
  writeFileSync(resolve(ralphDst, '.ralph-version'), pkg.version + '\n', 'utf8');
63
70
  ok(`Installed ralph runner v${pkg.version} -> .claude/ralph/`);
64
71
 
65
- // Create default ralph.config.yaml if it doesn't exist yet
66
- const configDst = resolve(ralphDst, 'ralph.config.yaml');
67
- const configSrc = resolve(ralphDst, 'ralph.config.sample.yaml');
68
- if (!existsSync(configDst) && existsSync(configSrc)) {
69
- copyFileSync(configSrc, configDst);
70
- ok('Created default ralph.config.yaml -> .claude/ralph/ralph.config.yaml');
72
+ // Restore saved config or create default from sample
73
+ if (savedConfig !== null) {
74
+ writeFileSync(configDst, savedConfig, 'utf8');
75
+ ok('Restored existing ralph.config.yaml');
76
+ } else {
77
+ const configSrc = resolve(ralphDst, 'ralph.config.sample.yaml');
78
+ if (existsSync(configSrc)) {
79
+ copyFileSync(configSrc, configDst);
80
+ ok('Created default ralph.config.yaml -> .claude/ralph/ralph.config.yaml');
81
+ }
71
82
  }
72
83
 
73
84
  // Install skills via skills.sh
74
- info('Installing skills from tahaJemmali/skills…');
75
85
  const REQUIRED_SKILLS = [
76
86
  'grill-me',
77
87
  'write-a-prd',
@@ -81,15 +91,39 @@ const REQUIRED_SKILLS = [
81
91
  'review-changes',
82
92
  'repo-doc-maintainer',
83
93
  ];
84
- const skillsResult = spawnSync(
85
- 'npx',
86
- ['skills', 'add', 'tahaJemmali/skills', ...REQUIRED_SKILLS.flatMap(s => ['--skill', s]), '-a', 'claude-code', '-y'],
87
- { cwd: projectRoot, stdio: 'inherit', encoding: 'utf8' },
88
- );
89
- if (skillsResult.status !== 0) {
90
- fail('Skills installation failed — aborting. ralph-prd requires all skills to be installed.\n Retry by running: npx ralph-prd');
94
+
95
+ info('Installing skills from tahaJemmali/skills…');
96
+ let skillsOk = false;
97
+ for (let attempt = 1; attempt <= 3; attempt++) {
98
+ const skillsResult = spawnSync(
99
+ 'npx',
100
+ ['skills', 'add', 'tahaJemmali/skills', ...REQUIRED_SKILLS.flatMap(s => ['--skill', s]), '-a', 'claude-code', '-y'],
101
+ { cwd: projectRoot, stdio: 'inherit', encoding: 'utf8' },
102
+ );
103
+ if (skillsResult.status === 0) {
104
+ skillsOk = true;
105
+ break;
106
+ }
107
+ if (attempt < 3) {
108
+ info(`Skills install attempt ${attempt} failed, retrying in 2s…`);
109
+ spawnSync('sleep', ['2']);
110
+ }
111
+ }
112
+
113
+ if (skillsOk) {
114
+ ok('Installed skills -> .claude/skills/');
115
+ } else {
116
+ // Check if skills already exist from a previous install
117
+ const skillsDir = resolve(projectRoot, '.claude', 'skills');
118
+ const allPresent = REQUIRED_SKILLS.every(s =>
119
+ existsSync(resolve(skillsDir, s, 'SKILL.md'))
120
+ );
121
+ if (allPresent) {
122
+ info('Skills install failed but existing skills found — continuing with cached skills.');
123
+ } else {
124
+ fail('Skills installation failed after 3 attempts — aborting.\n Retry: npx ralph-prd');
125
+ }
91
126
  }
92
- ok('Installed skills -> .claude/skills/');
93
127
 
94
128
  const gitignorePath = resolve(projectRoot, '.gitignore');
95
129
  let gitignoreContent = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "ralph-prd",
3
- "version": "1.1.0",
3
+ "version": "3.0.0",
4
+ "type": "module",
4
5
  "description": "AI-powered phased implementation runner for Claude Code — from PRD to shipped code",
5
6
  "bin": {
6
- "ralph-prd": "./bin/install.mjs"
7
+ "ralph-prd": "./bin/cli.mjs"
8
+ },
9
+ "exports": {
10
+ ".": "./ralph/index.mjs",
11
+ "./transport": "./ralph/lib/transport.mjs"
7
12
  },
8
13
  "keywords": [
9
14
  "claude",
@@ -0,0 +1,10 @@
1
+ /**
2
+ * ralph-prd public API
3
+ *
4
+ * Stable exports for use by ralph-prd-afk and other consumers.
5
+ * Import via: import { send, preflight } from 'ralph-prd/transport'
6
+ * Or: import { send, preflight } from 'ralph-prd'
7
+ */
8
+
9
+ export { send, preflight, getCumulativeCost, TransportError } from './lib/transport.mjs';
10
+ export { resolveRepos } from './lib/config.mjs';
@@ -166,14 +166,36 @@ function parseCommitPlan(text) {
166
166
  const commitLine = lines.find(l => l.startsWith('COMMIT:'));
167
167
  const commitSubject = commitLine ? commitLine.slice('COMMIT:'.length).trim() : '';
168
168
 
169
- // DESCRIPTION bullets body paragraphs
170
- const bodyLines = [];
171
- let inDesc = false;
169
+ // Parse sectioned body: DESCRIPTION, DECISIONS, BLOCKERS, NEXT
170
+ const sections = { DESCRIPTION: [], DECISIONS: [], BLOCKERS: [], NEXT: [] };
171
+ let currentSection = null;
172
172
  for (const line of lines) {
173
- if (line === 'DESCRIPTION:') { inDesc = true; continue; }
174
- if (inDesc) bodyLines.push(line);
173
+ if (line === 'DESCRIPTION:') { currentSection = 'DESCRIPTION'; continue; }
174
+ if (line === 'DECISIONS:') { currentSection = 'DECISIONS'; continue; }
175
+ if (line === 'BLOCKERS:') { currentSection = 'BLOCKERS'; continue; }
176
+ if (line === 'NEXT:') { currentSection = 'NEXT'; continue; }
177
+ // Stop collecting on known non-body headers
178
+ if (line === 'FILES:' || line.startsWith('COMMIT:') || line.startsWith('REPO:')) {
179
+ currentSection = null; continue;
180
+ }
181
+ if (currentSection) sections[currentSection].push(line);
182
+ }
183
+
184
+ // Build commit body: description bullets, then optional enrichment sections
185
+ const bodyParts = [];
186
+ if (sections.DESCRIPTION.length > 0) {
187
+ bodyParts.push(sections.DESCRIPTION.join('\n'));
188
+ }
189
+ if (sections.DECISIONS.length > 0) {
190
+ bodyParts.push('Decisions:\n' + sections.DECISIONS.join('\n'));
191
+ }
192
+ if (sections.BLOCKERS.length > 0) {
193
+ bodyParts.push('Blockers:\n' + sections.BLOCKERS.join('\n'));
194
+ }
195
+ if (sections.NEXT.length > 0) {
196
+ bodyParts.push('Next:\n' + sections.NEXT.join('\n'));
175
197
  }
176
- const commitBody = bodyLines.join('\n').trim();
198
+ const commitBody = bodyParts.join('\n\n').trim();
177
199
 
178
200
  plans.push({ name, files, commitSubject, commitBody, skip: false });
179
201
  }
@@ -325,7 +347,7 @@ export async function runCommitStep({
325
347
  const changedRepos = await scanChangedRepos(repos);
326
348
 
327
349
  if (changedRepos.length === 0) {
328
- return { nextStepIndex: si, anyCommitted: false };
350
+ return { nextTaskNum: si, anyCommitted: false };
329
351
  }
330
352
 
331
353
  // Build and run the commit session
@@ -158,16 +158,28 @@ function parseConfigYaml(content) {
158
158
  * Throws an Error with a human-readable message on any validation failure so
159
159
  * the caller can print it and exit before any phase begins.
160
160
  *
161
- * @param {string} runnerDir - Absolute path of the directory containing ralph-claude.mjs
161
+ * @param {string} runnerDir - Absolute path of the directory containing ralph-claude.mjs.
162
+ * Also searches process.cwd()/.claude/ for config (new canonical location).
162
163
  * @returns {{ repos: Repo[], flags: RalphFlags, hooks: RalphHooks }}
163
164
  */
164
165
  export function resolveRepos(runnerDir) {
165
- const configPath = join(runnerDir, CONFIG_FILENAME);
166
+ // Config lookup chain: new canonical path → legacy path → no config
167
+ const cwd = process.cwd();
168
+ const canonicalPath = join(cwd, '.claude', CONFIG_FILENAME);
169
+ const legacyPath = join(runnerDir, CONFIG_FILENAME);
170
+ const configPath = existsSync(canonicalPath) ? canonicalPath
171
+ : existsSync(legacyPath) ? legacyPath
172
+ : null;
173
+
174
+ if (configPath === legacyPath && existsSync(legacyPath)) {
175
+ console.log(`[ralph] Config loaded from legacy path: ${legacyPath}`);
176
+ console.log('[ralph] Move to .claude/ralph.config.yaml for the new canonical location.');
177
+ }
178
+
166
179
  const defaultFlags = { iDidThis: false, sendIt: false, waitForIt: false, maxRepairs: 3, onlyPhase: null, skipShipCheck: false, shipCheckRetries: 1, skipOnShipCheckFail: true, skipOnVerifyFail: false };
167
180
  const defaultHooks = { afterCommit: null };
168
181
 
169
- if (!existsSync(configPath)) {
170
- const cwd = process.cwd();
182
+ if (!configPath) {
171
183
  if (!existsSync(cwd)) {
172
184
  throw new Error(`Default repo (cwd) does not exist: ${cwd}`);
173
185
  }
@@ -182,7 +194,16 @@ export function resolveRepos(runnerDir) {
182
194
  const configDir = dirname(configPath);
183
195
 
184
196
  if (parsed.repos.length === 0) {
185
- throw new Error(`${configPath}: no repos defined`);
197
+ // No repos defined — fall back to cwd (same as no-config-file behavior),
198
+ // but still respect flags and hooks from the config.
199
+ const cwd = process.cwd();
200
+ if (!existsSync(cwd)) {
201
+ throw new Error(`Default repo (cwd) does not exist: ${cwd}`);
202
+ }
203
+ if (!isGitRepo(cwd)) {
204
+ throw new Error(`Default repo (cwd) is not a git repository: ${cwd}`);
205
+ }
206
+ return { repos: [{ name: basename(cwd), path: cwd }], flags: parsed.flags, hooks: parsed.hooks };
186
207
  }
187
208
 
188
209
  const errors = [];
@@ -59,7 +59,7 @@ export class PhaseExecutorError extends Error {
59
59
  * @param {string} opts.safetyHeader - May be empty string
60
60
  * @returns {string}
61
61
  */
62
- export function buildImplementationPrompt({ planContent, phase, repos, safetyHeader, selfCommit = true }) {
62
+ export function buildImplementationPrompt({ planContent, phase, repos, safetyHeader, selfCommit = true, prdContent = '', recentCommits = '' }) {
63
63
  const primaryRepos = repos.filter(r => !r.writableOnly);
64
64
  const writableDirs = repos.filter(r => r.writableOnly);
65
65
 
@@ -71,6 +71,16 @@ export function buildImplementationPrompt({ planContent, phase, repos, safetyHea
71
71
  writableDirs.map(r => ` - ${r.path}`).join('\n')
72
72
  : '';
73
73
 
74
+ // Build recent commits section (shows what prior phases/tasks already built)
75
+ const recentCommitsSection = recentCommits
76
+ ? `\n## Recent commits (for context — do NOT redo this work)\n\n${recentCommits}\n\n`
77
+ : '';
78
+
79
+ // Build PRD section (business context for the implementation)
80
+ const prdSection = prdContent
81
+ ? `## Source PRD (business context — understand the WHY behind the plan)\n\n${prdContent.trim()}\n\n---\n\n`
82
+ : '';
83
+
74
84
  const closingKey = selfCommit ? 'implementation_closing_commit' : 'implementation_closing_no_commit';
75
85
 
76
86
  return (
@@ -78,6 +88,8 @@ export function buildImplementationPrompt({ planContent, phase, repos, safetyHea
78
88
  render('implementation', {
79
89
  repoLines,
80
90
  writableLines,
91
+ recentCommits: recentCommitsSection,
92
+ prdSection,
81
93
  planContent: planContent.trim(),
82
94
  phaseTitle: phase.title,
83
95
  phaseBody: phase.body.trim(),
@@ -118,8 +130,10 @@ export async function runImplementation({
118
130
  send,
119
131
  isDryRun,
120
132
  selfCommit = true,
133
+ prdContent = '',
134
+ recentCommits = '',
121
135
  }) {
122
- const prompt = buildImplementationPrompt({ planContent, phase, repos, safetyHeader, selfCommit });
136
+ const prompt = buildImplementationPrompt({ planContent, phase, repos, safetyHeader, selfCommit, prdContent, recentCommits });
123
137
  const step = logWriter.openStep(phaseNum, taskNum, 'implementation', phase.title);
124
138
 
125
139
  step.writeHeader();