ralph-cli-sandboxed 0.5.1 → 0.6.1
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 +15 -0
- package/dist/commands/branch.d.ts +4 -0
- package/dist/commands/branch.js +408 -0
- package/dist/commands/chat.js +320 -2
- package/dist/commands/docker.js +47 -33
- package/dist/commands/help.js +16 -0
- package/dist/commands/init.js +1 -0
- package/dist/commands/prd.js +14 -3
- package/dist/commands/progress.d.ts +1 -0
- package/dist/commands/progress.js +98 -0
- package/dist/commands/run.js +304 -67
- package/dist/index.js +4 -0
- package/dist/providers/slack.js +1 -1
- package/dist/tui/components/SectionNav.js +1 -0
- package/dist/utils/chat-client.js +1 -0
- package/dist/utils/config.d.ts +28 -0
- package/dist/utils/config.js +75 -1
- package/dist/utils/prd-validator.d.ts +5 -0
- package/dist/utils/prd-validator.js +138 -4
- package/docs/BRANCHING.md +281 -0
- package/docs/PRD-GENERATOR.md +133 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ ralph docker run
|
|
|
44
44
|
| `ralph clean` | Remove all passing entries from PRD |
|
|
45
45
|
| `ralph fix-prd [opts]` | Validate and recover corrupted PRD file |
|
|
46
46
|
| `ralph prompt [opts]` | Display resolved prompt |
|
|
47
|
+
| `ralph branch <sub>` | Manage PRD branches (list, merge, pr, delete) |
|
|
47
48
|
| `ralph docker <sub>` | Manage Docker sandbox environment |
|
|
48
49
|
| `ralph daemon <sub>` | Manage host daemon for sandbox notifications |
|
|
49
50
|
| `ralph notify [msg]` | Send notification (from sandbox to host) |
|
|
@@ -427,6 +428,20 @@ The PRD (`prd.json`) is an array of requirements:
|
|
|
427
428
|
|
|
428
429
|
Categories: `setup`, `feature`, `bugfix`, `refactor`, `docs`, `test`, `release`, `config`, `ui`
|
|
429
430
|
|
|
431
|
+
### Branching
|
|
432
|
+
|
|
433
|
+
PRD items can be tagged with a `branch` field to group work onto separate git branches. Ralph uses git worktrees to isolate branch work from the main checkout, so the host's working directory stays untouched.
|
|
434
|
+
|
|
435
|
+
```yaml
|
|
436
|
+
- category: feature
|
|
437
|
+
description: Add login page
|
|
438
|
+
branch: feat/auth
|
|
439
|
+
steps: [...]
|
|
440
|
+
passes: false
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
See [docs/BRANCHING.md](docs/BRANCHING.md) for the full architecture, configuration, and branch management commands.
|
|
444
|
+
|
|
430
445
|
### Advanced: File References
|
|
431
446
|
|
|
432
447
|
PRD steps can include file contents using the `@{filepath}` syntax:
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { extname, join } from "path";
|
|
4
|
+
import { getRalphDir, getPrdFiles, loadBranchState, getProjectName } from "../utils/config.js";
|
|
5
|
+
import { readPrdFile, writePrdAuto } from "../utils/prd-validator.js";
|
|
6
|
+
import { promptConfirm } from "../utils/prompt.js";
|
|
7
|
+
import YAML from "yaml";
|
|
8
|
+
/**
|
|
9
|
+
* Converts a branch name to a worktree directory name, prefixed with the project name.
|
|
10
|
+
* e.g., "feat/login" -> "myproject_feat-login"
|
|
11
|
+
* The project prefix avoids conflicts when multiple projects share the same worktrees directory.
|
|
12
|
+
*/
|
|
13
|
+
function branchToWorktreeName(branch) {
|
|
14
|
+
const projectName = getProjectName();
|
|
15
|
+
return `${projectName}_${branch.replace(/\//g, "-")}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Gets the worktrees base path from config or defaults to /worktrees.
|
|
19
|
+
*/
|
|
20
|
+
function getWorktreesBase() {
|
|
21
|
+
return "/worktrees";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Loads PRD entries from the primary PRD file.
|
|
25
|
+
*/
|
|
26
|
+
function loadPrdEntries() {
|
|
27
|
+
const prdFiles = getPrdFiles();
|
|
28
|
+
if (!prdFiles.primary) {
|
|
29
|
+
console.error("\x1b[31mError: No PRD file found. Run 'ralph init' first.\x1b[0m");
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const parsed = readPrdFile(prdFiles.primary);
|
|
33
|
+
if (!parsed || !Array.isArray(parsed.content)) {
|
|
34
|
+
console.error("\x1b[31mError: PRD file is corrupted. Run 'ralph fix-prd' to repair.\x1b[0m");
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return { entries: parsed.content, prdPath: prdFiles.primary };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the base branch (the branch that /workspace is on).
|
|
41
|
+
*/
|
|
42
|
+
function getBaseBranch() {
|
|
43
|
+
try {
|
|
44
|
+
return execSync("git -C /workspace rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return "main";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a git branch exists.
|
|
52
|
+
*/
|
|
53
|
+
function branchExists(branch) {
|
|
54
|
+
try {
|
|
55
|
+
execSync(`git rev-parse --verify "${branch}"`, { stdio: "pipe" });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* List all branches referenced in the PRD and their status.
|
|
64
|
+
* Shows item counts, pass/fail status, worktree existence, and active branch indicator.
|
|
65
|
+
*/
|
|
66
|
+
function branchList() {
|
|
67
|
+
const result = loadPrdEntries();
|
|
68
|
+
if (!result)
|
|
69
|
+
return;
|
|
70
|
+
const { entries } = result;
|
|
71
|
+
const worktreesBase = getWorktreesBase();
|
|
72
|
+
const activeBranch = loadBranchState();
|
|
73
|
+
// Group items by branch
|
|
74
|
+
const branchGroups = new Map();
|
|
75
|
+
const noBranchItems = [];
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.branch) {
|
|
78
|
+
const group = branchGroups.get(entry.branch) || [];
|
|
79
|
+
group.push(entry);
|
|
80
|
+
branchGroups.set(entry.branch, group);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
noBranchItems.push(entry);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (branchGroups.size === 0 && noBranchItems.length === 0) {
|
|
87
|
+
console.log("No PRD items found.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log("\x1b[1mBranches:\x1b[0m\n");
|
|
91
|
+
// Sort branches alphabetically
|
|
92
|
+
const sortedBranches = [...branchGroups.keys()].sort();
|
|
93
|
+
for (const branchName of sortedBranches) {
|
|
94
|
+
const items = branchGroups.get(branchName);
|
|
95
|
+
const passing = items.filter((e) => e.passes).length;
|
|
96
|
+
const total = items.length;
|
|
97
|
+
const allPassing = passing === total;
|
|
98
|
+
// Check if worktree exists on disk
|
|
99
|
+
const dirName = branchToWorktreeName(branchName);
|
|
100
|
+
const worktreePath = join(worktreesBase, dirName);
|
|
101
|
+
const hasWorktree = existsSync(worktreePath);
|
|
102
|
+
// Check if this is the active branch
|
|
103
|
+
const isActive = activeBranch?.currentBranch === branchName;
|
|
104
|
+
// Build the line
|
|
105
|
+
const statusIcon = allPassing ? "\x1b[32m✅\x1b[0m" : "\x1b[33m○\x1b[0m";
|
|
106
|
+
const activeIndicator = isActive ? " \x1b[36m◀ active\x1b[0m" : "";
|
|
107
|
+
const worktreeStatus = hasWorktree ? " \x1b[32m[worktree]\x1b[0m" : "";
|
|
108
|
+
const countStr = `${passing}/${total}`;
|
|
109
|
+
console.log(` ${statusIcon} \x1b[1m${branchName}\x1b[0m ${countStr}${worktreeStatus}${activeIndicator}`);
|
|
110
|
+
}
|
|
111
|
+
// Show no-branch group
|
|
112
|
+
if (noBranchItems.length > 0) {
|
|
113
|
+
const passing = noBranchItems.filter((e) => e.passes).length;
|
|
114
|
+
const total = noBranchItems.length;
|
|
115
|
+
const allPassing = passing === total;
|
|
116
|
+
const statusIcon = allPassing ? "\x1b[32m✅\x1b[0m" : "\x1b[33m○\x1b[0m";
|
|
117
|
+
const countStr = `${passing}/${total}`;
|
|
118
|
+
console.log(` ${statusIcon} \x1b[2m(no branch)\x1b[0m ${countStr}`);
|
|
119
|
+
}
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Merge a completed branch worktree back into the base branch.
|
|
124
|
+
* Handles merge conflicts by aborting and showing conflicting files.
|
|
125
|
+
*/
|
|
126
|
+
async function branchMerge(args) {
|
|
127
|
+
const branchName = args[0];
|
|
128
|
+
if (!branchName) {
|
|
129
|
+
console.error("Usage: ralph branch merge <branch-name>");
|
|
130
|
+
console.error("\nExample: ralph branch merge feat/login");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
// Verify the branch exists
|
|
134
|
+
if (!branchExists(branchName)) {
|
|
135
|
+
console.error(`\x1b[31mError: Branch "${branchName}" does not exist.\x1b[0m`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const baseBranch = getBaseBranch();
|
|
139
|
+
const worktreesBase = getWorktreesBase();
|
|
140
|
+
const dirName = branchToWorktreeName(branchName);
|
|
141
|
+
const worktreePath = join(worktreesBase, dirName);
|
|
142
|
+
console.log(`Branch: ${branchName}`);
|
|
143
|
+
console.log(`Base branch: ${baseBranch}`);
|
|
144
|
+
if (existsSync(worktreePath)) {
|
|
145
|
+
console.log(`Worktree: ${worktreePath}`);
|
|
146
|
+
}
|
|
147
|
+
console.log();
|
|
148
|
+
// Ask for confirmation
|
|
149
|
+
const confirmed = await promptConfirm(`Merge "${branchName}" into "${baseBranch}"?`, true);
|
|
150
|
+
if (!confirmed) {
|
|
151
|
+
console.log("Merge cancelled.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Perform the merge from /workspace (which is on the base branch)
|
|
155
|
+
try {
|
|
156
|
+
console.log(`\nMerging "${branchName}" into "${baseBranch}"...`);
|
|
157
|
+
execSync(`git -C /workspace merge "${branchName}" --no-edit`, { stdio: "pipe" });
|
|
158
|
+
console.log(`\x1b[32mSuccessfully merged "${branchName}" into "${baseBranch}".\x1b[0m`);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
// Check if this is a merge conflict
|
|
162
|
+
let conflictingFiles = [];
|
|
163
|
+
try {
|
|
164
|
+
const status = execSync("git -C /workspace status --porcelain", { encoding: "utf-8" });
|
|
165
|
+
conflictingFiles = status
|
|
166
|
+
.split("\n")
|
|
167
|
+
.filter((line) => line.startsWith("UU") || line.startsWith("AA") || line.startsWith("DD") || line.startsWith("AU") || line.startsWith("UA") || line.startsWith("DU") || line.startsWith("UD"))
|
|
168
|
+
.map((line) => line.substring(3).trim());
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Ignore status errors
|
|
172
|
+
}
|
|
173
|
+
if (conflictingFiles.length > 0) {
|
|
174
|
+
// Merge conflict detected - abort and report
|
|
175
|
+
console.error(`\n\x1b[31mMerge conflict detected!\x1b[0m`);
|
|
176
|
+
console.error(`\nConflicting files:`);
|
|
177
|
+
for (const file of conflictingFiles) {
|
|
178
|
+
console.error(` \x1b[33m${file}\x1b[0m`);
|
|
179
|
+
}
|
|
180
|
+
// Abort the merge
|
|
181
|
+
try {
|
|
182
|
+
execSync("git -C /workspace merge --abort", { stdio: "pipe" });
|
|
183
|
+
console.error(`\n\x1b[36mMerge aborted.\x1b[0m`);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
console.error("\n\x1b[33mWarning: Could not abort merge. You may need to run 'git merge --abort' manually.\x1b[0m");
|
|
187
|
+
}
|
|
188
|
+
console.error(`\nTo resolve:`);
|
|
189
|
+
console.error(` 1. Resolve conflicts manually and merge again`);
|
|
190
|
+
console.error(` 2. Or add a PRD item to resolve the conflicts:`);
|
|
191
|
+
console.error(` ralph prd add # describe the conflict resolution needed`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Some other merge error
|
|
196
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
197
|
+
console.error(`\x1b[31mMerge failed: ${message}\x1b[0m`);
|
|
198
|
+
// Try to abort in case merge is in progress
|
|
199
|
+
try {
|
|
200
|
+
execSync("git -C /workspace merge --abort", { stdio: "pipe" });
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Ignore if nothing to abort
|
|
204
|
+
}
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Clean up worktree if it exists
|
|
209
|
+
if (existsSync(worktreePath)) {
|
|
210
|
+
console.log(`\nCleaning up worktree at ${worktreePath}...`);
|
|
211
|
+
try {
|
|
212
|
+
execSync(`git -C /workspace worktree remove "${worktreePath}"`, { stdio: "pipe" });
|
|
213
|
+
console.log(`\x1b[32mWorktree removed.\x1b[0m`);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
217
|
+
console.warn(`\x1b[33mWarning: Could not remove worktree: ${message}\x1b[0m`);
|
|
218
|
+
console.warn("You can remove it manually with: git worktree remove " + worktreePath);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Clean up the branch itself (optional - merged branches can be deleted)
|
|
222
|
+
console.log(`\n\x1b[32mDone!\x1b[0m Branch "${branchName}" has been merged into "${baseBranch}".`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Gets the PRD file path, preferring the primary if it exists.
|
|
226
|
+
*/
|
|
227
|
+
function getPrdPath() {
|
|
228
|
+
const prdFiles = getPrdFiles();
|
|
229
|
+
if (prdFiles.primary) {
|
|
230
|
+
return prdFiles.primary;
|
|
231
|
+
}
|
|
232
|
+
return join(getRalphDir(), "prd.json");
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Parses a PRD file (YAML or JSON) and returns the entries.
|
|
236
|
+
*/
|
|
237
|
+
function parsePrdFile(path) {
|
|
238
|
+
const content = readFileSync(path, "utf-8");
|
|
239
|
+
const ext = extname(path).toLowerCase();
|
|
240
|
+
try {
|
|
241
|
+
let result;
|
|
242
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
243
|
+
result = YAML.parse(content);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
result = JSON.parse(content);
|
|
247
|
+
}
|
|
248
|
+
return result ?? [];
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
console.error(`Error parsing ${path}. Run 'ralph fix-prd' to attempt automatic repair.`);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Saves PRD entries to the PRD file (YAML or JSON based on extension).
|
|
257
|
+
*/
|
|
258
|
+
function savePrd(entries) {
|
|
259
|
+
const path = getPrdPath();
|
|
260
|
+
const ext = extname(path).toLowerCase();
|
|
261
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
262
|
+
writeFileSync(path, YAML.stringify(entries));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
writeFileSync(path, JSON.stringify(entries, null, 2) + "\n");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Create a PRD item to open a pull request for a branch.
|
|
270
|
+
*/
|
|
271
|
+
function branchPr(args) {
|
|
272
|
+
const branchName = args[0];
|
|
273
|
+
if (!branchName) {
|
|
274
|
+
console.error("Usage: ralph branch pr <branch-name>");
|
|
275
|
+
console.error("\nExample: ralph branch pr feat/login");
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
// Verify the branch exists
|
|
279
|
+
if (!branchExists(branchName)) {
|
|
280
|
+
console.error(`\x1b[31mError: Branch "${branchName}" does not exist.\x1b[0m`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
const baseBranch = getBaseBranch();
|
|
284
|
+
const entry = {
|
|
285
|
+
category: "feature",
|
|
286
|
+
description: `Create a pull request from \`${branchName}\` into \`${baseBranch}\``,
|
|
287
|
+
steps: [
|
|
288
|
+
`Ensure all changes on \`${branchName}\` are committed`,
|
|
289
|
+
`Push \`${branchName}\` to the remote if not already pushed`,
|
|
290
|
+
`Create a pull request from \`${branchName}\` into \`${baseBranch}\` using the appropriate tool (e.g. gh pr create)`,
|
|
291
|
+
"Include a descriptive title and summary of the changes in the PR",
|
|
292
|
+
],
|
|
293
|
+
passes: false,
|
|
294
|
+
branch: branchName,
|
|
295
|
+
};
|
|
296
|
+
const prdPath = getPrdPath();
|
|
297
|
+
const prd = parsePrdFile(prdPath);
|
|
298
|
+
prd.push(entry);
|
|
299
|
+
savePrd(prd);
|
|
300
|
+
console.log(`Added PRD entry #${prd.length}: Create PR for ${branchName} → ${baseBranch}`);
|
|
301
|
+
console.log(`Branch field set to: ${branchName}`);
|
|
302
|
+
console.log("Run 'ralph run' or 'ralph once' to execute.");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Delete a branch: remove worktree, delete git branch, and untag PRD items.
|
|
306
|
+
* Asks for confirmation before proceeding.
|
|
307
|
+
*/
|
|
308
|
+
async function branchDelete(args) {
|
|
309
|
+
const branchName = args[0];
|
|
310
|
+
if (!branchName) {
|
|
311
|
+
console.error("Usage: ralph branch delete <branch-name>");
|
|
312
|
+
console.error("\nExample: ralph branch delete feat/old-branch");
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
// Verify the branch exists
|
|
316
|
+
if (!branchExists(branchName)) {
|
|
317
|
+
console.error(`\x1b[31mError: Branch "${branchName}" does not exist.\x1b[0m`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
const worktreesBase = getWorktreesBase();
|
|
321
|
+
const dirName = branchToWorktreeName(branchName);
|
|
322
|
+
const worktreePath = join(worktreesBase, dirName);
|
|
323
|
+
const hasWorktree = existsSync(worktreePath);
|
|
324
|
+
// Load PRD to check for tagged items
|
|
325
|
+
const result = loadPrdEntries();
|
|
326
|
+
const taggedCount = result
|
|
327
|
+
? result.entries.filter((e) => e.branch === branchName).length
|
|
328
|
+
: 0;
|
|
329
|
+
console.log(`Branch: ${branchName}`);
|
|
330
|
+
if (hasWorktree) {
|
|
331
|
+
console.log(`Worktree: ${worktreePath}`);
|
|
332
|
+
}
|
|
333
|
+
if (taggedCount > 0) {
|
|
334
|
+
console.log(`PRD items tagged: ${taggedCount}`);
|
|
335
|
+
}
|
|
336
|
+
console.log();
|
|
337
|
+
// Ask for confirmation
|
|
338
|
+
const confirmed = await promptConfirm(`Delete branch "${branchName}"${hasWorktree ? " and its worktree" : ""}?`, false);
|
|
339
|
+
if (!confirmed) {
|
|
340
|
+
console.log("Delete cancelled.");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Step 1: Remove worktree if it exists
|
|
344
|
+
if (hasWorktree) {
|
|
345
|
+
console.log(`\nRemoving worktree at ${worktreePath}...`);
|
|
346
|
+
try {
|
|
347
|
+
execSync(`git -C /workspace worktree remove "${worktreePath}" --force`, { stdio: "pipe" });
|
|
348
|
+
console.log(`\x1b[32mWorktree removed.\x1b[0m`);
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
352
|
+
console.warn(`\x1b[33mWarning: Could not remove worktree: ${message}\x1b[0m`);
|
|
353
|
+
console.warn("You can remove it manually with: git worktree remove " + worktreePath);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Step 2: Delete the git branch
|
|
357
|
+
console.log(`Deleting branch "${branchName}"...`);
|
|
358
|
+
try {
|
|
359
|
+
execSync(`git -C /workspace branch -D "${branchName}"`, { stdio: "pipe" });
|
|
360
|
+
console.log(`\x1b[32mBranch deleted.\x1b[0m`);
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
364
|
+
console.error(`\x1b[31mError deleting branch: ${message}\x1b[0m`);
|
|
365
|
+
}
|
|
366
|
+
// Step 3: Remove branch tag from PRD items
|
|
367
|
+
if (result && taggedCount > 0) {
|
|
368
|
+
console.log(`Removing branch tag from ${taggedCount} PRD item(s)...`);
|
|
369
|
+
const updatedEntries = result.entries.map((entry) => {
|
|
370
|
+
if (entry.branch === branchName) {
|
|
371
|
+
const { branch: _, ...rest } = entry;
|
|
372
|
+
return rest;
|
|
373
|
+
}
|
|
374
|
+
return entry;
|
|
375
|
+
});
|
|
376
|
+
writePrdAuto(result.prdPath, updatedEntries);
|
|
377
|
+
console.log(`\x1b[32mPRD items updated.\x1b[0m`);
|
|
378
|
+
}
|
|
379
|
+
console.log(`\n\x1b[32mDone!\x1b[0m Branch "${branchName}" has been deleted.`);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Main branch command dispatcher.
|
|
383
|
+
*/
|
|
384
|
+
export async function branch(args) {
|
|
385
|
+
const subcommand = args[0];
|
|
386
|
+
switch (subcommand) {
|
|
387
|
+
case "list":
|
|
388
|
+
branchList();
|
|
389
|
+
break;
|
|
390
|
+
case "merge":
|
|
391
|
+
await branchMerge(args.slice(1));
|
|
392
|
+
break;
|
|
393
|
+
case "delete":
|
|
394
|
+
await branchDelete(args.slice(1));
|
|
395
|
+
break;
|
|
396
|
+
case "pr":
|
|
397
|
+
branchPr(args.slice(1));
|
|
398
|
+
break;
|
|
399
|
+
default:
|
|
400
|
+
console.error("Usage: ralph branch <subcommand>");
|
|
401
|
+
console.error("\nSubcommands:");
|
|
402
|
+
console.error(" list List all branches and their status");
|
|
403
|
+
console.error(" merge <name> Merge a branch worktree into the base branch");
|
|
404
|
+
console.error(" delete <name> Delete a branch and its worktree");
|
|
405
|
+
console.error(" pr <name> Create a PRD item to open a PR for a branch");
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
}
|