ralph-mcp 1.0.1 → 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 +94 -51
- package/README.zh-CN.md +16 -6
- package/dist/tools/merge.js +63 -14
- 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/package.json +11 -7
package/README.md
CHANGED
|
@@ -3,21 +3,46 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/ralph-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
**Parallel Ralph loop**: PRD → `ralph_start` → merged. Run multiple PRDs simultaneously in isolated worktrees with auto quality gates and merge.
|
|
7
7
|
|
|
8
8
|
Based on [Geoffrey Huntley's Ralph pattern](https://ghuntley.com/ralph/).
|
|
9
9
|
|
|
10
10
|
[中文文档](./README.zh-CN.md)
|
|
11
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
|
+
|
|
12
37
|
## Features
|
|
13
38
|
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **Auto Merge** -
|
|
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
|
|
19
45
|
- **Notifications** - Windows Toast when PRD completes
|
|
20
|
-
- **State Persistence** - Survives Claude Code restarts (JSON storage)
|
|
21
46
|
|
|
22
47
|
## Installation
|
|
23
48
|
|
|
@@ -66,6 +91,36 @@ Or if installed from source:
|
|
|
66
91
|
|
|
67
92
|
Restart Claude Code to load.
|
|
68
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
|
+
|
|
69
124
|
## Tools
|
|
70
125
|
|
|
71
126
|
| Tool | Description |
|
|
@@ -81,65 +136,53 @@ Restart Claude Code to load.
|
|
|
81
136
|
|
|
82
137
|
## Usage
|
|
83
138
|
|
|
84
|
-
###
|
|
85
|
-
|
|
86
|
-
```javascript
|
|
87
|
-
// 1. Start PRD execution
|
|
88
|
-
ralph_start({ prdPath: "tasks/prd-feature.md" })
|
|
139
|
+
### Typical Session
|
|
89
140
|
|
|
90
|
-
// 2. Check status anytime
|
|
91
|
-
ralph_status()
|
|
92
|
-
|
|
93
|
-
// 3. Merge when complete
|
|
94
|
-
ralph_merge({ branch: "ralph/prd-feature" })
|
|
95
141
|
```
|
|
142
|
+
User: Help me finish the Speaking module from TODO.md
|
|
96
143
|
|
|
97
|
-
|
|
144
|
+
Claude: Let me check TODO.md... Found 3 incomplete items:
|
|
145
|
+
- Speaking dialogue practice
|
|
146
|
+
- Speaking scoring optimization
|
|
147
|
+
- Speaking question bank
|
|
98
148
|
|
|
99
|
-
|
|
149
|
+
I'll create 3 PRDs...
|
|
150
|
+
[Generates prd-speaking-dialogue.md]
|
|
151
|
+
[Generates prd-speaking-scoring.md]
|
|
152
|
+
[Generates prd-speaking-qb.md]
|
|
100
153
|
|
|
101
|
-
|
|
102
|
-
1. Analyze PRDs to identify independent tasks that can run in parallel
|
|
103
|
-
2. Start multiple PRDs via ralph_start()
|
|
104
|
-
3. Launch background Task agents for each PRD
|
|
105
|
-
4. Continue chatting - plan next features, review code, etc.
|
|
106
|
-
5. Get Windows Toast notification when PRDs complete
|
|
107
|
-
6. Merge completed PRDs to main via ralph_merge()
|
|
108
|
-
```
|
|
154
|
+
User: Start
|
|
109
155
|
|
|
110
|
-
|
|
156
|
+
Claude:
|
|
157
|
+
[ralph_start × 3]
|
|
158
|
+
[Task Agent × 3 running in parallel]
|
|
111
159
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Claude: Let me analyze the PRDs...
|
|
116
|
-
- prd-auth.md (independent)
|
|
117
|
-
- prd-dashboard.md (independent)
|
|
118
|
-
- prd-api.md (independent)
|
|
119
|
-
|
|
120
|
-
All 3 can run in parallel. Starting...
|
|
160
|
+
3 PRDs started. They'll auto-merge when complete.
|
|
161
|
+
Use ralph_status to check progress.
|
|
121
162
|
|
|
122
|
-
|
|
123
|
-
[Launches 3 background Task agents]
|
|
163
|
+
--- Some time later ---
|
|
124
164
|
|
|
125
|
-
|
|
126
|
-
I'll notify you when they complete.
|
|
165
|
+
User: Progress?
|
|
127
166
|
|
|
128
|
-
|
|
167
|
+
Claude: [ralph_status]
|
|
168
|
+
✅ prd-speaking-dialogue - Merged
|
|
169
|
+
✅ prd-speaking-scoring - Merged
|
|
170
|
+
🔄 prd-speaking-qb - US-003/005 in progress
|
|
129
171
|
|
|
130
|
-
|
|
172
|
+
User: 👍
|
|
173
|
+
```
|
|
131
174
|
|
|
132
|
-
|
|
133
|
-
- ralph/prd-auth: 4/4 US ✓
|
|
134
|
-
- ralph/prd-dashboard: 3/3 US ✓
|
|
135
|
-
- ralph/prd-api: 5/5 US ✓
|
|
175
|
+
### Manual Workflow
|
|
136
176
|
|
|
137
|
-
|
|
177
|
+
```javascript
|
|
178
|
+
// 1. Start PRD execution
|
|
179
|
+
ralph_start({ prdPath: "tasks/prd-feature.md" })
|
|
138
180
|
|
|
139
|
-
|
|
181
|
+
// 2. Check status anytime
|
|
182
|
+
ralph_status()
|
|
140
183
|
|
|
141
|
-
|
|
142
|
-
|
|
184
|
+
// 3. Manual merge if needed (usually automatic)
|
|
185
|
+
ralph_merge({ branch: "ralph/prd-feature" })
|
|
143
186
|
```
|
|
144
187
|
|
|
145
188
|
### API Reference
|
package/README.zh-CN.md
CHANGED
|
@@ -3,21 +3,31 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/ralph-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
自主并行执行 PRD 的 Claude Code MCP 服务器。自动解析 PRD、创建隔离 worktree、追踪进度、合并完成的功能。
|
|
7
7
|
|
|
8
8
|
基于 [Geoffrey Huntley 的 Ralph 模式](https://ghuntley.com/ralph/)。
|
|
9
9
|
|
|
10
10
|
[English](./README.md)
|
|
11
11
|
|
|
12
|
+
## 为什么选择 Ralph MCP?
|
|
13
|
+
|
|
14
|
+
| 没有 Ralph | 有 Ralph |
|
|
15
|
+
|------------|----------|
|
|
16
|
+
| 一次只能做一个功能 | 多个功能并行执行 |
|
|
17
|
+
| 手动管理 git 分支 | 自动 worktree 隔离 |
|
|
18
|
+
| 重启后进度丢失 | 状态持久化(JSON) |
|
|
19
|
+
| 手动协调合并 | 串行合并队列 |
|
|
20
|
+
| 看不到执行进度 | 实时状态追踪 |
|
|
21
|
+
|
|
12
22
|
## 特性
|
|
13
23
|
|
|
14
|
-
-
|
|
15
|
-
- **Git Worktree 隔离** - 每个 PRD
|
|
16
|
-
-
|
|
24
|
+
- **并行执行** - 配合 Claude Code Task 工具同时执行多个 PRD
|
|
25
|
+
- **Git Worktree 隔离** - 每个 PRD 在独立 worktree 中运行,零冲突
|
|
26
|
+
- **智能合并队列** - 串行合并避免并行合并冲突
|
|
17
27
|
- **进度追踪** - 通过 `ralph_status()` 实时查看状态
|
|
18
|
-
- **自动合并** - 一键合并,支持冲突解决策略
|
|
19
|
-
- **完成通知** - PRD 完成时弹出 Windows Toast 通知
|
|
20
28
|
- **状态持久化** - 重启 Claude Code 不丢失状态(JSON 存储)
|
|
29
|
+
- **自动合并** - 一键合并,支持多种冲突解决策略
|
|
30
|
+
- **完成通知** - PRD 完成时弹出 Windows Toast 通知
|
|
21
31
|
|
|
22
32
|
## 安装
|
|
23
33
|
|
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/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/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
|
}
|