ralph-mcp 1.0.0 → 1.0.2

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
@@ -1,15 +1,47 @@
1
1
  # Ralph MCP
2
2
 
3
- MCP server for autonomous PRD execution with Claude Code. Git worktree isolation, progress tracking, auto-merge.
3
+ [![npm version](https://badge.fury.io/js/ralph-mcp.svg)](https://www.npmjs.com/package/ralph-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ **Parallel Ralph loop**: PRD → `ralph_start` → merged. Run multiple PRDs simultaneously in isolated worktrees with auto quality gates and merge.
4
7
 
5
8
  Based on [Geoffrey Huntley's Ralph pattern](https://ghuntley.com/ralph/).
6
9
 
10
+ [中文文档](./README.zh-CN.md)
11
+
12
+ ## The Ralph Loop (2 Steps)
13
+
14
+ ```
15
+ Step 1: Generate PRD
16
+ User: "Create a PRD for user authentication"
17
+ Claude: [Generates tasks/prd-auth.md]
18
+
19
+ Step 2: Execute
20
+ User: "Start" or "Execute this PRD"
21
+ Claude: ralph_start → Task Agent handles everything automatically
22
+ ```
23
+
24
+ **That's it.** Ralph MCP automatically handles: branch creation, worktree isolation, code implementation, quality checks, commits, merge, and doc sync.
25
+
26
+ ## Why Ralph MCP?
27
+
28
+ | Without Ralph | With Ralph |
29
+ |---------------|------------|
30
+ | One feature at a time | Multiple features in parallel |
31
+ | Manual PRD writing | Claude generates PRD for you |
32
+ | Manual git branch management | Automatic worktree isolation |
33
+ | Lost progress on restart | Persistent state (JSON) |
34
+ | Manual merge coordination | Auto merge with conflict resolution |
35
+ | No visibility into progress | Real-time status tracking |
36
+
7
37
  ## Features
8
38
 
9
- - **PRD Parsing** - Extracts User Stories from markdown PRD files
10
- - **Git Worktree Isolation** - Each PRD runs in isolated worktree
11
- - **Progress Tracking** - Real-time status via `ralph_status()`
12
- - **Auto Merge** - One-click merge with conflict resolution
39
+ - **2-Step Workflow** - Just create PRD and run `ralph_start`, everything else is automatic
40
+ - **Parallel Execution** - Run 5+ PRDs simultaneously with Claude Code Task tool
41
+ - **Git Worktree Isolation** - Each PRD runs in its own worktree, zero conflicts
42
+ - **Auto Quality Gates** - Type check, lint, build before every commit
43
+ - **Auto Merge** - Merges to main when all User Stories pass
44
+ - **Doc Sync** - Automatically updates TODO.md with completed items
13
45
  - **Notifications** - Windows Toast when PRD completes
14
46
 
15
47
  ## Installation
@@ -59,6 +91,36 @@ Or if installed from source:
59
91
 
60
92
  Restart Claude Code to load.
61
93
 
94
+ ## Claude Code Skill Setup (Recommended)
95
+
96
+ For the best experience, create a skill file that teaches Claude how to use Ralph.
97
+
98
+ See **[SKILL-EXAMPLE.md](./SKILL-EXAMPLE.md)** for a complete, copy-paste ready skill configuration.
99
+
100
+ ### Quick Setup
101
+
102
+ ```bash
103
+ # 1. Create skill directory
104
+ mkdir -p .claude/skills/ralph
105
+
106
+ # 2. Copy the example (adjust path as needed)
107
+ cp /path/to/ralph-mcp/SKILL-EXAMPLE.md .claude/skills/ralph/SKILL.md
108
+
109
+ # 3. Customize quality check commands for your project
110
+ ```
111
+
112
+ ### Add to CLAUDE.md (optional)
113
+
114
+ ```markdown
115
+ ## Skills
116
+
117
+ | Skill | Trigger |
118
+ |-------|---------|
119
+ | `ralph` | PRD execution (generate PRD → start → auto merge) |
120
+ ```
121
+
122
+ This enables Claude to automatically use Ralph when you mention PRD execution.
123
+
62
124
  ## Tools
63
125
 
64
126
  | Tool | Description |
@@ -74,40 +136,113 @@ Restart Claude Code to load.
74
136
 
75
137
  ## Usage
76
138
 
139
+ ### Typical Session
140
+
141
+ ```
142
+ User: Help me finish the Speaking module from TODO.md
143
+
144
+ Claude: Let me check TODO.md... Found 3 incomplete items:
145
+ - Speaking dialogue practice
146
+ - Speaking scoring optimization
147
+ - Speaking question bank
148
+
149
+ I'll create 3 PRDs...
150
+ [Generates prd-speaking-dialogue.md]
151
+ [Generates prd-speaking-scoring.md]
152
+ [Generates prd-speaking-qb.md]
153
+
154
+ User: Start
155
+
156
+ Claude:
157
+ [ralph_start × 3]
158
+ [Task Agent × 3 running in parallel]
159
+
160
+ 3 PRDs started. They'll auto-merge when complete.
161
+ Use ralph_status to check progress.
162
+
163
+ --- Some time later ---
164
+
165
+ User: Progress?
166
+
167
+ Claude: [ralph_status]
168
+ ✅ prd-speaking-dialogue - Merged
169
+ ✅ prd-speaking-scoring - Merged
170
+ 🔄 prd-speaking-qb - US-003/005 in progress
171
+
172
+ User: 👍
173
+ ```
174
+
175
+ ### Manual Workflow
176
+
77
177
  ```javascript
78
- // Start PRD execution
178
+ // 1. Start PRD execution
79
179
  ralph_start({ prdPath: "tasks/prd-feature.md" })
80
180
 
81
- // Check all status
181
+ // 2. Check status anytime
182
+ ralph_status()
183
+
184
+ // 3. Manual merge if needed (usually automatic)
185
+ ralph_merge({ branch: "ralph/prd-feature" })
186
+ ```
187
+
188
+ ### API Reference
189
+
190
+ ```javascript
191
+ // Start PRD execution (returns agent prompt)
192
+ ralph_start({ prdPath: "tasks/prd-feature.md" })
193
+
194
+ // View all PRD status
82
195
  ralph_status()
83
196
 
84
197
  // Get single PRD details
85
198
  ralph_get({ branch: "ralph/prd-feature" })
86
199
 
87
- // Update US status (agent calls this after completing a story)
200
+ // Update User Story status (called by agent)
88
201
  ralph_update({ branch: "ralph/prd-feature", storyId: "US-1", passes: true, notes: "..." })
89
202
 
90
- // Merge completed PRD
203
+ // Stop execution
204
+ ralph_stop({ branch: "ralph/prd-feature" })
205
+
206
+ // Merge to main
91
207
  ralph_merge({ branch: "ralph/prd-feature" })
208
+
209
+ // Record Task agent ID (for tracking)
210
+ ralph_set_agent_id({ branch: "ralph/prd-feature", agentTaskId: "abc123" })
92
211
  ```
93
212
 
94
213
  ## PRD Format
95
214
 
96
- Ralph parses markdown PRD files with User Stories:
215
+ Ralph parses markdown PRD files. Example:
97
216
 
98
217
  ```markdown
99
- # Feature Name
218
+ ---
219
+ title: User Authentication
220
+ priority: high
221
+ ---
222
+
223
+ # User Authentication
224
+
225
+ Implement user login and registration.
100
226
 
101
227
  ## User Stories
102
228
 
103
- ### US-1: Story Title
229
+ ### US-1: User Registration
230
+
231
+ Users can create new accounts.
104
232
 
105
233
  **Acceptance Criteria:**
106
- - [ ] Criterion 1
107
- - [ ] Criterion 2
234
+ - [ ] Email validation
235
+ - [ ] Password strength check
236
+ - [ ] Confirmation email sent
237
+
238
+ ### US-2: User Login
108
239
 
109
- ### US-2: Another Story
110
- ...
240
+ Users can log into their accounts.
241
+
242
+ **Acceptance Criteria:**
243
+ - [ ] Email/password authentication
244
+ - [ ] Remember me option
245
+ - [ ] Forgot password flow
111
246
  ```
112
247
 
113
248
  ## Conflict Resolution
@@ -123,9 +258,41 @@ Ralph parses markdown PRD files with User Stories:
123
258
 
124
259
  ## Data Storage
125
260
 
126
- - State: `~/.ralph/state.json` (JSON file)
261
+ - State: `~/.ralph/state.json`
127
262
  - Logs: `~/.ralph/logs/`
128
263
 
264
+ Override data directory with `RALPH_DATA_DIR` environment variable.
265
+
266
+ ## Advanced Options
267
+
268
+ ### ralph_start options
269
+
270
+ | Option | Default | Description |
271
+ |--------|---------|-------------|
272
+ | `prdPath` | required | Path to PRD markdown file |
273
+ | `projectRoot` | cwd | Project root directory |
274
+ | `worktree` | `true` | Create isolated git worktree |
275
+ | `autoStart` | `true` | Return agent prompt for immediate execution |
276
+ | `autoMerge` | `false` | Auto-merge when all stories pass |
277
+ | `notifyOnComplete` | `true` | Show Windows notification on completion |
278
+ | `onConflict` | `"agent"` | Conflict resolution: `auto_theirs`, `auto_ours`, `notify`, `agent` |
279
+
280
+ ### Example with options
281
+
282
+ ```javascript
283
+ ralph_start({
284
+ prdPath: "tasks/prd-feature.md",
285
+ autoMerge: true, // Auto-merge when done
286
+ notifyOnComplete: true, // Windows Toast notification
287
+ onConflict: "auto_theirs" // Prefer main on conflicts
288
+ })
289
+ ```
290
+
291
+ ## Credits
292
+
293
+ - [Geoffrey Huntley](https://ghuntley.com/) - Original Ralph pattern
294
+ - [Anthropic](https://anthropic.com/) - Claude Code & MCP protocol
295
+
129
296
  ## License
130
297
 
131
298
  MIT
@@ -0,0 +1,265 @@
1
+ # Ralph MCP
2
+
3
+ [![npm version](https://badge.fury.io/js/ralph-mcp.svg)](https://www.npmjs.com/package/ralph-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ 自主并行执行 PRD 的 Claude Code MCP 服务器。自动解析 PRD、创建隔离 worktree、追踪进度、合并完成的功能。
7
+
8
+ 基于 [Geoffrey Huntley 的 Ralph 模式](https://ghuntley.com/ralph/)。
9
+
10
+ [English](./README.md)
11
+
12
+ ## 为什么选择 Ralph MCP?
13
+
14
+ | 没有 Ralph | 有 Ralph |
15
+ |------------|----------|
16
+ | 一次只能做一个功能 | 多个功能并行执行 |
17
+ | 手动管理 git 分支 | 自动 worktree 隔离 |
18
+ | 重启后进度丢失 | 状态持久化(JSON) |
19
+ | 手动协调合并 | 串行合并队列 |
20
+ | 看不到执行进度 | 实时状态追踪 |
21
+
22
+ ## 特性
23
+
24
+ - **并行执行** - 配合 Claude Code Task 工具同时执行多个 PRD
25
+ - **Git Worktree 隔离** - 每个 PRD 在独立 worktree 中运行,零冲突
26
+ - **智能合并队列** - 串行合并避免并行合并冲突
27
+ - **进度追踪** - 通过 `ralph_status()` 实时查看状态
28
+ - **状态持久化** - 重启 Claude Code 不丢失状态(JSON 存储)
29
+ - **自动合并** - 一键合并,支持多种冲突解决策略
30
+ - **完成通知** - PRD 完成时弹出 Windows Toast 通知
31
+
32
+ ## 安装
33
+
34
+ ### 从 npm 安装
35
+
36
+ ```bash
37
+ npm install -g ralph-mcp
38
+ ```
39
+
40
+ ### 从源码安装
41
+
42
+ ```bash
43
+ git clone https://github.com/G0d2i11a/ralph-mcp.git
44
+ cd ralph-mcp
45
+ npm install
46
+ npm run build
47
+ ```
48
+
49
+ ## 配置
50
+
51
+ 添加到 `~/.claude/mcp.json`:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "ralph": {
57
+ "command": "npx",
58
+ "args": ["ralph-mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ 或者从源码安装时:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "ralph": {
70
+ "command": "node",
71
+ "args": ["/path/to/ralph-mcp/dist/index.js"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ 重启 Claude Code 生效。
78
+
79
+ ## 工具列表
80
+
81
+ | 工具 | 说明 |
82
+ |------|------|
83
+ | `ralph_start` | 启动 PRD 执行(解析 PRD,创建 worktree,返回 agent prompt) |
84
+ | `ralph_status` | 查看所有 PRD 执行状态 |
85
+ | `ralph_get` | 获取单个 PRD 详情 |
86
+ | `ralph_update` | 更新 User Story 状态(agent 调用) |
87
+ | `ralph_stop` | 停止执行 |
88
+ | `ralph_merge` | 合并到 main + 清理 worktree |
89
+ | `ralph_merge_queue` | 管理串行合并队列 |
90
+ | `ralph_set_agent_id` | 记录 Task agent ID |
91
+
92
+ ## 使用方法
93
+
94
+ ### 基本流程
95
+
96
+ ```javascript
97
+ // 1. 启动 PRD 执行
98
+ ralph_start({ prdPath: "tasks/prd-feature.md" })
99
+
100
+ // 2. 随时查看状态
101
+ ralph_status()
102
+
103
+ // 3. 完成后合并
104
+ ralph_merge({ branch: "ralph/prd-feature" })
105
+ ```
106
+
107
+ ### 配合 Claude Code Task 工具并行执行
108
+
109
+ Ralph MCP 设计为配合 Claude Code 的 Task 工具实现并行 PRD 执行:
110
+
111
+ ```
112
+ 1. 分析 PRD,识别可以并行执行的独立任务
113
+ 2. 通过 ralph_start() 启动多个 PRD
114
+ 3. 为每个 PRD 启动后台 Task agent
115
+ 4. 继续聊天 - 规划下一个功能、审查代码等
116
+ 5. PRD 完成时收到 Windows Toast 通知
117
+ 6. 通过 ralph_merge() 将完成的 PRD 合并到 main
118
+ ```
119
+
120
+ **示例会话:**
121
+
122
+ ```
123
+ 用户: 并行执行这 3 个 PRD
124
+
125
+ Claude: 让我分析一下这些 PRD...
126
+ - prd-auth.md(独立)
127
+ - prd-dashboard.md(独立)
128
+ - prd-api.md(独立)
129
+
130
+ 3 个都可以并行执行。正在启动...
131
+
132
+ [为每个 PRD 调用 ralph_start()]
133
+ [启动 3 个后台 Task agent]
134
+
135
+ PRD 正在后台运行。你可以继续其他工作。
136
+ 完成后我会通知你。
137
+
138
+ 用户: 好的,等待的时候我们来规划下一个功能...
139
+
140
+ [稍后 - Windows Toast 通知弹出]
141
+
142
+ Claude: 3 个 PRD 全部完成!
143
+ - ralph/prd-auth: 4/4 US ✓
144
+ - ralph/prd-dashboard: 3/3 US ✓
145
+ - ralph/prd-api: 5/5 US ✓
146
+
147
+ 准备合并吗?
148
+
149
+ 用户: 是的,全部合并
150
+
151
+ Claude: [为每个分支调用 ralph_merge()]
152
+ 所有 PRD 已成功合并到 main。
153
+ ```
154
+
155
+ ### API 参考
156
+
157
+ ```javascript
158
+ // 启动 PRD 执行(返回 agent prompt)
159
+ ralph_start({ prdPath: "tasks/prd-feature.md" })
160
+
161
+ // 查看所有 PRD 状态
162
+ ralph_status()
163
+
164
+ // 获取单个 PRD 详情
165
+ ralph_get({ branch: "ralph/prd-feature" })
166
+
167
+ // 更新 User Story 状态(agent 调用)
168
+ ralph_update({ branch: "ralph/prd-feature", storyId: "US-1", passes: true, notes: "..." })
169
+
170
+ // 停止执行
171
+ ralph_stop({ branch: "ralph/prd-feature" })
172
+
173
+ // 合并到 main
174
+ ralph_merge({ branch: "ralph/prd-feature" })
175
+
176
+ // 记录 Task agent ID(用于追踪)
177
+ ralph_set_agent_id({ branch: "ralph/prd-feature", agentTaskId: "abc123" })
178
+ ```
179
+
180
+ ## PRD 格式
181
+
182
+ Ralph 解析 markdown 格式的 PRD 文件。示例:
183
+
184
+ ```markdown
185
+ ---
186
+ title: 用户认证
187
+ priority: high
188
+ ---
189
+
190
+ # 用户认证
191
+
192
+ 实现用户登录和注册功能。
193
+
194
+ ## User Stories
195
+
196
+ ### US-1: 用户注册
197
+
198
+ 用户可以创建新账户。
199
+
200
+ **Acceptance Criteria:**
201
+ - [ ] 邮箱验证
202
+ - [ ] 密码强度检查
203
+ - [ ] 发送确认邮件
204
+
205
+ ### US-2: 用户登录
206
+
207
+ 用户可以登录账户。
208
+
209
+ **Acceptance Criteria:**
210
+ - [ ] 邮箱/密码认证
211
+ - [ ] 记住我选项
212
+ - [ ] 忘记密码流程
213
+ ```
214
+
215
+ ## 冲突解决
216
+
217
+ `ralph_merge` 支持以下策略:
218
+
219
+ | 策略 | 行为 |
220
+ |------|------|
221
+ | `auto_theirs` | `git merge -X theirs`,优先 main |
222
+ | `auto_ours` | `git merge -X ours`,优先 branch |
223
+ | `notify` | 暂停,通知用户手动处理 |
224
+ | `agent` | 启动 merge subagent 解决冲突(默认) |
225
+
226
+ ## 数据存储
227
+
228
+ - 状态文件:`~/.ralph/state.json`
229
+ - 日志目录:`~/.ralph/logs/`
230
+
231
+ 可通过 `RALPH_DATA_DIR` 环境变量覆盖数据目录。
232
+
233
+ ## 高级选项
234
+
235
+ ### ralph_start 参数
236
+
237
+ | 参数 | 默认值 | 说明 |
238
+ |------|--------|------|
239
+ | `prdPath` | 必填 | PRD markdown 文件路径 |
240
+ | `projectRoot` | 当前目录 | 项目根目录 |
241
+ | `worktree` | `true` | 创建隔离的 git worktree |
242
+ | `autoStart` | `true` | 返回 agent prompt 以便立即执行 |
243
+ | `autoMerge` | `false` | 所有 story 通过后自动合并 |
244
+ | `notifyOnComplete` | `true` | 完成时显示 Windows 通知 |
245
+ | `onConflict` | `"agent"` | 冲突解决策略:`auto_theirs`, `auto_ours`, `notify`, `agent` |
246
+
247
+ ### 带参数示例
248
+
249
+ ```javascript
250
+ ralph_start({
251
+ prdPath: "tasks/prd-feature.md",
252
+ autoMerge: true, // 完成后自动合并
253
+ notifyOnComplete: true, // Windows Toast 通知
254
+ onConflict: "auto_theirs" // 冲突时优先 main
255
+ })
256
+ ```
257
+
258
+ ## 致谢
259
+
260
+ - [Geoffrey Huntley](https://ghuntley.com/) - 原始 Ralph 模式
261
+ - [Anthropic](https://anthropic.com/) - Claude Code 和 MCP 协议
262
+
263
+ ## 许可证
264
+
265
+ MIT
@@ -92,7 +92,7 @@ export async function merge(input) {
92
92
  const commitMessage = generateCommitMessage(exec.branch, exec.description, completedStories);
93
93
  // Step 4: Attempt merge to main
94
94
  console.log(">>> Merging to main...");
95
- const mergeResult = await mergeBranchWithMessage(exec.projectRoot, exec.branch, commitMessage, onConflict);
95
+ const mergeResult = await mergeBranchWithMessage(exec.projectRoot, exec.worktreePath || undefined, exec.branch, commitMessage, onConflict);
96
96
  if (mergeResult.success) {
97
97
  // Step 5: Update docs
98
98
  const docsUpdated = [];
@@ -138,7 +138,9 @@ export async function merge(input) {
138
138
  : { typeCheck: true, build: true },
139
139
  docsUpdated: docsUpdated.length > 0 ? docsUpdated : undefined,
140
140
  mergedStories: completedStories.map((s) => s.id),
141
- message: `Successfully merged ${input.branch} to main`,
141
+ message: mergeResult.alreadyMerged
142
+ ? `Branch ${input.branch} was already merged to main`
143
+ : `Successfully merged ${input.branch} to main`,
142
144
  };
143
145
  }
144
146
  // Handle conflicts
@@ -250,13 +252,56 @@ export async function merge(input) {
250
252
  }
251
253
  /**
252
254
  * Merge branch with custom commit message
255
+ * Always merges in projectRoot (main repo) since it's already on main branch.
256
+ * Worktree is only used for development, not for merging.
253
257
  */
254
- async function mergeBranchWithMessage(projectRoot, branch, commitMessage, onConflict) {
258
+ async function mergeBranchWithMessage(projectRoot, _worktreePath, // Unused, kept for API compatibility
259
+ branch, commitMessage, onConflict) {
255
260
  const { exec: execAsync } = await import("child_process");
256
261
  const { promisify } = await import("util");
257
262
  const execPromise = promisify(execAsync);
258
- // Checkout main and pull
259
- await execPromise("git checkout main && git pull", { cwd: projectRoot });
263
+ // Always merge in projectRoot (main repo is already on main branch)
264
+ const cwd = projectRoot;
265
+ // Check if origin remote exists
266
+ let hasOrigin = false;
267
+ try {
268
+ const { stdout } = await execPromise("git remote", { cwd });
269
+ hasOrigin = stdout.includes("origin");
270
+ }
271
+ catch {
272
+ hasOrigin = false;
273
+ }
274
+ // Fetch latest main and pull
275
+ if (hasOrigin) {
276
+ try {
277
+ await execPromise("git fetch origin main", { cwd });
278
+ await execPromise("git pull origin main", { cwd });
279
+ }
280
+ catch {
281
+ // Ignore fetch/pull errors
282
+ }
283
+ }
284
+ // Check if branch is already merged into main
285
+ try {
286
+ const mergeBase = hasOrigin ? "origin/main" : "main";
287
+ const { stdout: mergedBranches } = await execPromise(`git branch --merged ${mergeBase}`, { cwd });
288
+ const isMerged = mergedBranches
289
+ .split("\n")
290
+ .map((b) => b.trim().replace(/^\* /, ""))
291
+ .includes(branch);
292
+ if (isMerged) {
293
+ const { stdout: hash } = await execPromise(`git rev-parse ${mergeBase}`, { cwd });
294
+ return {
295
+ success: true,
296
+ commitHash: hash.trim(),
297
+ hasConflicts: false,
298
+ alreadyMerged: true,
299
+ };
300
+ }
301
+ }
302
+ catch {
303
+ // Continue with merge attempt if check fails
304
+ }
260
305
  // Build merge strategy
261
306
  let mergeStrategy = "";
262
307
  if (onConflict === "auto_theirs") {
@@ -266,23 +311,27 @@ async function mergeBranchWithMessage(projectRoot, branch, commitMessage, onConf
266
311
  mergeStrategy = "-X ours";
267
312
  }
268
313
  try {
269
- // Use heredoc-style commit message to handle special characters
314
+ // Perform merge (main repo is already on main branch, no checkout needed)
270
315
  const escapedMessage = commitMessage.replace(/'/g, "'\\''");
271
- await execPromise(`git merge --no-ff ${mergeStrategy} "${branch}" -m '${escapedMessage}'`, { cwd: projectRoot });
272
- const { stdout: hash } = await execPromise("git rev-parse HEAD", {
273
- cwd: projectRoot,
274
- });
316
+ const { stdout: mergeOutput } = await execPromise(`git merge --no-ff ${mergeStrategy} "${branch}" -m '${escapedMessage}'`, { cwd });
317
+ const { stdout: hash } = await execPromise("git rev-parse HEAD", { cwd });
318
+ const commitHash = hash.trim();
319
+ // Check if "Already up to date" (no new commit created)
320
+ const alreadyUpToDate = mergeOutput.includes("Already up to date");
321
+ // Push to origin if available
322
+ if (hasOrigin) {
323
+ await execPromise("git push origin main", { cwd });
324
+ }
275
325
  return {
276
326
  success: true,
277
- commitHash: hash.trim(),
327
+ commitHash,
278
328
  hasConflicts: false,
329
+ alreadyMerged: alreadyUpToDate,
279
330
  };
280
331
  }
281
332
  catch {
282
333
  // Check for conflicts
283
- const { stdout: status } = await execPromise("git status --porcelain", {
284
- cwd: projectRoot,
285
- });
334
+ const { stdout: status } = await execPromise("git status --porcelain", { cwd });
286
335
  const conflictFiles = status
287
336
  .split("\n")
288
337
  .filter((line) => line.startsWith("UU ") || line.startsWith("AA "))
@@ -10,7 +10,7 @@ export const startInputSchema = z.object({
10
10
  projectRoot: z.string().optional().describe("Project root directory (defaults to cwd)"),
11
11
  worktree: z.boolean().default(true).describe("Create a worktree for isolation"),
12
12
  autoStart: z.boolean().default(true).describe("Generate agent prompt for auto-start"),
13
- autoMerge: z.boolean().default(false).describe("Auto add to merge queue when all stories pass"),
13
+ autoMerge: z.boolean().default(true).describe("Auto add to merge queue when all stories pass"),
14
14
  notifyOnComplete: z.boolean().default(true).describe("Show Windows notification when all stories complete"),
15
15
  onConflict: z
16
16
  .enum(["auto_theirs", "auto_ours", "notify", "agent"])
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import notifier from "node-notifier";
3
3
  import { findExecutionByBranch, findMergeQueueItemByExecutionId, findUserStoryById, insertMergeQueueItem, listMergeQueue, listUserStoriesByExecutionId, updateExecution, updateUserStory, } from "../store/state.js";
4
+ import { mergeQueueAction } from "./merge.js";
4
5
  export const updateInputSchema = z.object({
5
6
  branch: z.string().describe("Branch name (e.g., ralph/task1-agent)"),
6
7
  storyId: z.string().describe("Story ID (e.g., US-001)"),
@@ -49,6 +50,15 @@ export async function update(input) {
49
50
  createdAt: new Date(),
50
51
  });
51
52
  addedToMergeQueue = true;
53
+ // Auto-process merge queue (fire and forget)
54
+ setImmediate(async () => {
55
+ try {
56
+ await mergeQueueAction({ action: "process" });
57
+ }
58
+ catch (e) {
59
+ console.error("Auto-merge failed:", e);
60
+ }
61
+ });
52
62
  }
53
63
  }
54
64
  // Send Windows toast notification when all complete (if enabled)
@@ -35,7 +35,8 @@ export declare function generateCommitMessage(branch: string, description: strin
35
35
  */
36
36
  export declare function getBranchCommits(projectRoot: string, branch: string): Promise<string[]>;
37
37
  /**
38
- * Update TODO.md to mark PRD as completed
38
+ * Update TODO.md to mark PRD-related items as completed
39
+ * Uses fuzzy matching based on keywords from the PRD description
39
40
  */
40
41
  export declare function updateTodoDoc(projectRoot: string, branch: string, description: string): boolean;
41
42
  /**
@@ -8,11 +8,23 @@ const execAsync = promisify(exec);
8
8
  */
9
9
  export async function syncMainToBranch(worktreePath, branch) {
10
10
  try {
11
- // Fetch latest main
12
- await execAsync("git fetch origin main", { cwd: worktreePath });
11
+ // Check if origin remote exists
12
+ let hasOrigin = false;
13
+ try {
14
+ const { stdout } = await execAsync("git remote", { cwd: worktreePath });
15
+ hasOrigin = stdout.includes("origin");
16
+ }
17
+ catch {
18
+ hasOrigin = false;
19
+ }
20
+ // Fetch latest main only if origin exists
21
+ if (hasOrigin) {
22
+ await execAsync("git fetch origin main", { cwd: worktreePath });
23
+ }
13
24
  // Try to merge main into feature branch
25
+ const mergeTarget = hasOrigin ? "origin/main" : "main";
14
26
  try {
15
- await execAsync("git merge origin/main --no-edit", { cwd: worktreePath });
27
+ await execAsync(`git merge ${mergeTarget} --no-edit`, { cwd: worktreePath });
16
28
  return {
17
29
  success: true,
18
30
  hasConflicts: false,
@@ -118,7 +130,8 @@ export async function getBranchCommits(projectRoot, branch) {
118
130
  }
119
131
  }
120
132
  /**
121
- * Update TODO.md to mark PRD as completed
133
+ * Update TODO.md to mark PRD-related items as completed
134
+ * Uses fuzzy matching based on keywords from the PRD description
122
135
  */
123
136
  export function updateTodoDoc(projectRoot, branch, description) {
124
137
  const todoPath = join(projectRoot, "docs", "TODO.md");
@@ -127,12 +140,28 @@ export function updateTodoDoc(projectRoot, branch, description) {
127
140
  }
128
141
  try {
129
142
  let content = readFileSync(todoPath, "utf-8");
130
- // Find and update the PRD entry (mark as completed)
131
- // Pattern: - [ ] PRD description -> - [x] PRD description
132
- const prdPattern = new RegExp(`- \\[ \\] (.*${description.slice(0, 30)}.*|.*${branch}.*)`);
133
- if (prdPattern.test(content)) {
134
- content = content.replace(prdPattern, "- [x] $1");
135
- writeFileSync(todoPath, content, "utf-8");
143
+ let updated = false;
144
+ // Extract keywords from description and branch for matching
145
+ const keywords = extractKeywords(description, branch);
146
+ // Find unchecked items that match keywords
147
+ const lines = content.split("\n");
148
+ const updatedLines = lines.map((line) => {
149
+ // Only process unchecked items
150
+ if (!line.match(/^(\s*)- \[ \]/)) {
151
+ return line;
152
+ }
153
+ // Check if line matches any keywords
154
+ const lineLower = line.toLowerCase();
155
+ const matchCount = keywords.filter((kw) => lineLower.includes(kw)).length;
156
+ // Require at least 2 keyword matches for confidence
157
+ if (matchCount >= 2) {
158
+ updated = true;
159
+ return line.replace("- [ ]", "- [x]");
160
+ }
161
+ return line;
162
+ });
163
+ if (updated) {
164
+ writeFileSync(todoPath, updatedLines.join("\n"), "utf-8");
136
165
  return true;
137
166
  }
138
167
  return false;
@@ -141,6 +170,36 @@ export function updateTodoDoc(projectRoot, branch, description) {
141
170
  return false;
142
171
  }
143
172
  }
173
+ /**
174
+ * Extract keywords from PRD description and branch name
175
+ */
176
+ function extractKeywords(description, branch) {
177
+ const keywords = [];
178
+ // Common words to ignore
179
+ const stopWords = new Set([
180
+ "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
181
+ "of", "with", "by", "from", "as", "is", "was", "are", "were", "been",
182
+ "be", "have", "has", "had", "do", "does", "did", "will", "would",
183
+ "could", "should", "may", "might", "must", "shall", "can", "need",
184
+ "prd", "ralph", "user", "want", "that", "this", "which", "who",
185
+ ]);
186
+ // Extract from description
187
+ const descWords = description
188
+ .toLowerCase()
189
+ .replace(/[^a-z0-9\s]/g, " ")
190
+ .split(/\s+/)
191
+ .filter((w) => w.length > 2 && !stopWords.has(w));
192
+ keywords.push(...descWords);
193
+ // Extract from branch name (e.g., ralph/prd-speaking-dialogue-coach)
194
+ const branchWords = branch
195
+ .toLowerCase()
196
+ .replace(/[^a-z0-9]/g, " ")
197
+ .split(/\s+/)
198
+ .filter((w) => w.length > 2 && !stopWords.has(w));
199
+ keywords.push(...branchWords);
200
+ // Dedupe and return
201
+ return [...new Set(keywords)];
202
+ }
144
203
  /**
145
204
  * Update PROJECT-STATUS.md with merge info
146
205
  */
@@ -35,8 +35,7 @@ function parsePrdMarkdown(content) {
35
35
  const titleMatch = body.match(/^#\s+(.+)$/m);
36
36
  const title = frontmatter.title || titleMatch?.[1] || "Untitled PRD";
37
37
  // Extract branch name from frontmatter or generate from title
38
- const branchName = frontmatter.branch ||
39
- `ralph/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
38
+ const branchName = frontmatter.branch || generateBranchName(title);
40
39
  // Extract description
41
40
  const descMatch = body.match(/##\s*(?:Description|描述|Overview|概述)\s*\n([\s\S]*?)(?=\n##|\n$)/i);
42
41
  const description = descMatch?.[1]?.trim() || title;
@@ -8,9 +8,9 @@ export interface WorktreeInfo {
8
8
  */
9
9
  export declare function createWorktree(projectRoot: string, branch: string): Promise<string>;
10
10
  /**
11
- * Remove a git worktree
11
+ * Remove a git worktree and optionally delete the branch
12
12
  */
13
- export declare function removeWorktree(projectRoot: string, worktreePath: string): Promise<void>;
13
+ export declare function removeWorktree(projectRoot: string, worktreePath: string, deleteBranch?: boolean): Promise<void>;
14
14
  /**
15
15
  * List all worktrees
16
16
  */
@@ -28,16 +28,35 @@ export async function createWorktree(projectRoot, branch) {
28
28
  return worktreePath;
29
29
  }
30
30
  /**
31
- * Remove a git worktree
31
+ * Remove a git worktree and optionally delete the branch
32
32
  */
33
- export async function removeWorktree(projectRoot, worktreePath) {
34
- if (!existsSync(worktreePath)) {
35
- console.log(`Worktree does not exist at ${worktreePath}`);
36
- return;
33
+ export async function removeWorktree(projectRoot, worktreePath, deleteBranch = true) {
34
+ // Get branch name before removing worktree
35
+ let branchToDelete = null;
36
+ if (deleteBranch) {
37
+ const worktrees = listWorktrees(projectRoot);
38
+ const worktree = worktrees.find((w) => w.path === worktreePath);
39
+ if (worktree?.branch) {
40
+ branchToDelete = worktree.branch;
41
+ }
42
+ }
43
+ if (existsSync(worktreePath)) {
44
+ await execAsync(`git worktree remove "${worktreePath}" --force`, {
45
+ cwd: projectRoot,
46
+ });
47
+ }
48
+ // Delete the branch after worktree is removed
49
+ if (branchToDelete) {
50
+ try {
51
+ await execAsync(`git branch -D "${branchToDelete}"`, {
52
+ cwd: projectRoot,
53
+ });
54
+ console.log(`Deleted branch: ${branchToDelete}`);
55
+ }
56
+ catch (e) {
57
+ console.error(`Failed to delete branch ${branchToDelete}:`, e);
58
+ }
37
59
  }
38
- await execAsync(`git worktree remove "${worktreePath}" --force`, {
39
- cwd: projectRoot,
40
- });
41
60
  }
42
61
  /**
43
62
  * List all worktrees
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for autonomous PRD execution with Claude Code. Git worktree isolation, progress tracking, auto-merge.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,11 @@
20
20
  "prd",
21
21
  "ai-agent",
22
22
  "autonomous",
23
- "git-worktree"
23
+ "git-worktree",
24
+ "project-management",
25
+ "task-management",
26
+ "user-story",
27
+ "merge-queue"
24
28
  ],
25
29
  "author": "G0d2i11a",
26
30
  "license": "MIT",
@@ -40,15 +44,15 @@
40
44
  "node": ">=18"
41
45
  },
42
46
  "dependencies": {
43
- "@modelcontextprotocol/sdk": "^1.25.2",
47
+ "@modelcontextprotocol/sdk": "^1.25.3",
44
48
  "gray-matter": "^4.0.3",
45
49
  "node-notifier": "^10.0.1",
46
- "zod": "^3.24.1"
50
+ "zod": "^3.25.76"
47
51
  },
48
52
  "devDependencies": {
49
- "@types/node": "^22.10.5",
53
+ "@types/node": "^22.19.7",
50
54
  "@types/node-notifier": "^8.0.5",
51
- "tsx": "^4.19.2",
52
- "typescript": "^5.7.2"
55
+ "tsx": "^4.21.0",
56
+ "typescript": "^5.9.3"
53
57
  }
54
58
  }