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 +184 -17
- package/README.zh-CN.md +265 -0
- package/dist/tools/merge.js +63 -14
- package/dist/tools/start.js +1 -1
- package/dist/tools/update.js +10 -0
- package/dist/utils/merge-helpers.d.ts +2 -1
- package/dist/utils/merge-helpers.js +69 -10
- package/dist/utils/prd-parser.js +1 -2
- package/dist/utils/worktree.d.ts +2 -2
- package/dist/utils/worktree.js +27 -8
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
# Ralph MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ralph-mcp)
|
|
4
|
+
[](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
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Auto
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
215
|
+
Ralph parses markdown PRD files. Example:
|
|
97
216
|
|
|
98
217
|
```markdown
|
|
99
|
-
|
|
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:
|
|
229
|
+
### US-1: User Registration
|
|
230
|
+
|
|
231
|
+
Users can create new accounts.
|
|
104
232
|
|
|
105
233
|
**Acceptance Criteria:**
|
|
106
|
-
- [ ]
|
|
107
|
-
- [ ]
|
|
234
|
+
- [ ] Email validation
|
|
235
|
+
- [ ] Password strength check
|
|
236
|
+
- [ ] Confirmation email sent
|
|
237
|
+
|
|
238
|
+
### US-2: User Login
|
|
108
239
|
|
|
109
|
-
|
|
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`
|
|
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
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Ralph MCP
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ralph-mcp)
|
|
4
|
+
[](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
|
package/dist/tools/merge.js
CHANGED
|
@@ -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:
|
|
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,
|
|
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
|
-
//
|
|
259
|
-
|
|
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
|
-
//
|
|
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
|
|
272
|
-
const { stdout: hash } = await execPromise("git rev-parse HEAD", {
|
|
273
|
-
|
|
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
|
|
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 "))
|
package/dist/tools/start.js
CHANGED
|
@@ -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(
|
|
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"])
|
package/dist/tools/update.js
CHANGED
|
@@ -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
|
-
//
|
|
12
|
-
|
|
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(
|
|
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
|
-
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
*/
|
package/dist/utils/prd-parser.js
CHANGED
|
@@ -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;
|
package/dist/utils/worktree.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/utils/worktree.js
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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.
|
|
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.
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
44
48
|
"gray-matter": "^4.0.3",
|
|
45
49
|
"node-notifier": "^10.0.1",
|
|
46
|
-
"zod": "^3.
|
|
50
|
+
"zod": "^3.25.76"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
|
-
"@types/node": "^22.
|
|
53
|
+
"@types/node": "^22.19.7",
|
|
50
54
|
"@types/node-notifier": "^8.0.5",
|
|
51
|
-
"tsx": "^4.
|
|
52
|
-
"typescript": "^5.
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
53
57
|
}
|
|
54
58
|
}
|