ralph-cli-sandboxed 0.6.3 → 0.6.5
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 +9 -5
- package/dist/commands/docker.js +20 -11
- package/dist/commands/init.js +6 -0
- package/dist/commands/once.js +4 -0
- package/dist/commands/run.js +77 -17
- package/dist/config/cli-providers.json +18 -0
- package/dist/tui/components/SectionNav.js +6 -0
- package/dist/utils/config.d.ts +1 -0
- package/docs/BRANCHING.md +22 -29
- package/docs/CHAT-CLIENTS.md +13 -17
- package/docs/CHAT-RESPONDERS.md +3 -3
- package/docs/DEVELOPMENT.md +45 -7
- package/docs/DOCKER.md +5 -4
- package/docs/FAQ.md +125 -0
- package/docs/HOW-TO-WRITE-PRDs.md +5 -8
- package/docs/MACOS-DEVELOPMENT.md +3 -3
- package/docs/PRD-GENERATOR.md +9 -12
- package/docs/RALPH-SETUP-TEMPLATE.md +56 -24
- package/docs/SECURITY.md +4 -2
- package/docs/SKILLS.md +3 -3
- package/docs/chat-architecture.md +37 -30
- package/docs/run-state-machine.md +13 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,10 @@ ralph docker run
|
|
|
42
42
|
| `ralph status` | Show PRD completion status |
|
|
43
43
|
| `ralph toggle <n>` | Toggle passes status for entry n |
|
|
44
44
|
| `ralph clean` | Remove all passing entries from PRD |
|
|
45
|
+
| `ralph reset` | Reset all PRD entries to passes: false |
|
|
45
46
|
| `ralph fix-prd [opts]` | Validate and recover corrupted PRD file |
|
|
46
47
|
| `ralph prompt [opts]` | Display resolved prompt |
|
|
48
|
+
| `ralph progress <sub>` | Manage progress file (summarize) |
|
|
47
49
|
| `ralph branch <sub>` | Manage PRD branches (list, merge, pr, delete) |
|
|
48
50
|
| `ralph docker <sub>` | Manage Docker sandbox environment |
|
|
49
51
|
| `ralph daemon <sub>` | Manage host daemon for sandbox notifications |
|
|
@@ -233,10 +235,10 @@ Ralph supports 18 programming languages with pre-configured build/test commands:
|
|
|
233
235
|
| Java | `mvn compile` | `mvn test` |
|
|
234
236
|
| Kotlin | `gradle build` | `gradle test` |
|
|
235
237
|
| C#/.NET | `dotnet build` | `dotnet test` |
|
|
236
|
-
| Ruby | `bundle exec rubocop` | `bundle exec rspec` |
|
|
237
|
-
| PHP | `composer validate` | `vendor/bin/phpunit` |
|
|
238
|
+
| Ruby | `bundle exec rubocop --fail-level error` | `bundle exec rspec` |
|
|
239
|
+
| PHP | `composer validate && php -l` | `vendor/bin/phpunit` |
|
|
238
240
|
| Swift | `swift build` | `swift test` |
|
|
239
|
-
| Elixir | `mix compile` | `mix test` |
|
|
241
|
+
| Elixir | `mix compile --warnings-as-errors` | `mix test` |
|
|
240
242
|
| Scala | `sbt compile` | `sbt test` |
|
|
241
243
|
| Zig | `zig build` | `zig build test` |
|
|
242
244
|
| Haskell | `stack build` | `stack test` |
|
|
@@ -255,6 +257,7 @@ Ralph supports multiple AI CLI tools. Select your provider during `ralph init`:
|
|
|
255
257
|
| [OpenCode](https://github.com/anomalyco/opencode) | Working | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` | No autonomous/yolo mode yet. Requires [PR #9073](https://github.com/anomalyco/opencode/pull/9073) |
|
|
256
258
|
| [Aider](https://github.com/paul-gauthier/aider) | Working | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` | |
|
|
257
259
|
| [Goose](https://github.com/block/goose) | Working | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` | Block's AI coding agent |
|
|
260
|
+
| [Ollama](https://ollama.com/) | Working | (none) | Local LLM server |
|
|
258
261
|
| [Codex CLI](https://github.com/openai/codex) | Testers wanted | `OPENAI_API_KEY` | Sponsors welcome |
|
|
259
262
|
| [AMP](https://ampcode.com/) | Testers wanted | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | Sponsors welcome |
|
|
260
263
|
| Custom | - | User-defined | Configure your own CLI |
|
|
@@ -353,6 +356,7 @@ Not all CLI providers support stream-json output. Here's the compatibility matri
|
|
|
353
356
|
| Goose | ✅ Yes | `--output-format stream-json` |
|
|
354
357
|
| Aider | ❌ No | - |
|
|
355
358
|
| AMP | ❌ No | - |
|
|
359
|
+
| Ollama | ❌ No | - |
|
|
356
360
|
| Custom | ❌ No* | *Add `streamJsonArgs` to your custom config |
|
|
357
361
|
|
|
358
362
|
Each provider uses different command-line arguments and output formats. Ralph automatically selects the correct parser based on your configured provider.
|
|
@@ -426,7 +430,7 @@ The PRD (`prd.json`) is an array of requirements:
|
|
|
426
430
|
]
|
|
427
431
|
```
|
|
428
432
|
|
|
429
|
-
Categories: `
|
|
433
|
+
Categories: `ui`, `feature`, `bugfix`, `setup`, `development`, `testing`, `docs`
|
|
430
434
|
|
|
431
435
|
### Branching
|
|
432
436
|
|
|
@@ -499,7 +503,7 @@ ralph fix-prd backup.json # Restore from a specific backup file
|
|
|
499
503
|
|
|
500
504
|
### Dynamic Iteration Limits
|
|
501
505
|
|
|
502
|
-
To prevent runaway loops, `ralph run`
|
|
506
|
+
To prevent runaway loops, `ralph run` stops after 3 consecutive iterations without progress (no tasks completed and no new tasks added). It also stops after 3 consecutive failures with the same exit code.
|
|
503
507
|
|
|
504
508
|
## Docker Sandbox
|
|
505
509
|
|
package/dist/commands/docker.js
CHANGED
|
@@ -17,6 +17,7 @@ function computeConfigHash(config) {
|
|
|
17
17
|
cliProvider: config.cliProvider,
|
|
18
18
|
docker: config.docker,
|
|
19
19
|
claude: config.claude,
|
|
20
|
+
cliModel: config.cli?.model,
|
|
20
21
|
};
|
|
21
22
|
const content = JSON.stringify(relevantConfig, null, 2);
|
|
22
23
|
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
@@ -71,9 +72,15 @@ function getCliProviderSnippet(cliProvider) {
|
|
|
71
72
|
}
|
|
72
73
|
return provider.docker.install;
|
|
73
74
|
}
|
|
74
|
-
function generateDockerfile(language, javaVersion, cliProvider, dockerConfig) {
|
|
75
|
+
function generateDockerfile(language, javaVersion, cliProvider, dockerConfig, cliModel) {
|
|
75
76
|
const languageSnippet = getLanguageSnippet(language, javaVersion);
|
|
76
77
|
const cliSnippet = getCliProviderSnippet(cliProvider);
|
|
78
|
+
// Ollama model pull: when provider is ollama and a model is configured,
|
|
79
|
+
// start the server briefly and pull the model during the Docker build
|
|
80
|
+
let ollamaModelPull = "";
|
|
81
|
+
if (cliProvider === "ollama" && cliModel) {
|
|
82
|
+
ollamaModelPull = `\n# Pull Ollama model during build\nRUN ollama serve & sleep 2 && ollama pull ${cliModel}\n`;
|
|
83
|
+
}
|
|
77
84
|
// Build custom packages section
|
|
78
85
|
let customPackages = "";
|
|
79
86
|
if (dockerConfig?.packages && dockerConfig.packages.length > 0) {
|
|
@@ -206,14 +213,14 @@ if [ -z "$RALPH_BANNER_SHOWN" ]; then
|
|
|
206
213
|
echo "\\033[38;2;253;216;53m██╔══██╗██╔══██║██║ ██╔═══╝ ██╔══██║ ██║ ██║ ██║\\033[0m"
|
|
207
214
|
echo "\\033[38;2;251;192;45m██║ ██║██║ ██║███████╗██║ ██║ ██║ ╚██████╗███████╗██║\\033[0m"
|
|
208
215
|
echo "\\033[38;2;249;168;37m╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝\\033[0m"
|
|
209
|
-
RALPH_VERSION=$(ralph --version 2>/dev/null |
|
|
210
|
-
echo "\\033[38;5;
|
|
216
|
+
RALPH_VERSION=$(ralph --version 2>/dev/null | awk '{print $NF}' || echo "unknown")
|
|
217
|
+
echo "\\033[38;5;248m$RALPH_VERSION\\033[0m"
|
|
211
218
|
echo ""
|
|
212
219
|
fi
|
|
213
220
|
RALPH_BANNER
|
|
214
221
|
|
|
215
222
|
${cliSnippet}
|
|
216
|
-
|
|
223
|
+
${ollamaModelPull}
|
|
217
224
|
# Install ralph-cli-sandboxed from npm registry
|
|
218
225
|
RUN npm install -g ralph-cli-sandboxed
|
|
219
226
|
RUN ralph logo
|
|
@@ -654,7 +661,7 @@ function generateClaudeSettings() {
|
|
|
654
661
|
};
|
|
655
662
|
return JSON.stringify(settings, null, 2) + "\n";
|
|
656
663
|
}
|
|
657
|
-
async function generateFiles(ralphDir, language, imageName, force = false, javaVersion, cliProvider, dockerConfig, claudeConfig) {
|
|
664
|
+
async function generateFiles(ralphDir, language, imageName, force = false, javaVersion, cliProvider, dockerConfig, claudeConfig, cliModel) {
|
|
658
665
|
const dockerDir = join(ralphDir, DOCKER_DIR);
|
|
659
666
|
// Create docker directory
|
|
660
667
|
if (!existsSync(dockerDir)) {
|
|
@@ -665,7 +672,7 @@ async function generateFiles(ralphDir, language, imageName, force = false, javaV
|
|
|
665
672
|
const files = [
|
|
666
673
|
{
|
|
667
674
|
name: "Dockerfile",
|
|
668
|
-
content: generateDockerfile(language, javaVersion, cliProvider, dockerConfig),
|
|
675
|
+
content: generateDockerfile(language, javaVersion, cliProvider, dockerConfig, cliModel),
|
|
669
676
|
},
|
|
670
677
|
{ name: "init-firewall.sh", content: generateFirewallScript(customDomains) },
|
|
671
678
|
{ name: "docker-compose.yml", content: generateDockerCompose(imageName, dockerConfig) },
|
|
@@ -797,6 +804,7 @@ async function generateFiles(ralphDir, language, imageName, force = false, javaV
|
|
|
797
804
|
cliProvider,
|
|
798
805
|
docker: dockerConfig,
|
|
799
806
|
claude: claudeConfig,
|
|
807
|
+
cli: cliModel ? { command: "", model: cliModel } : undefined,
|
|
800
808
|
};
|
|
801
809
|
const hash = computeConfigHash(configForHash);
|
|
802
810
|
saveConfigHash(dockerDir, hash);
|
|
@@ -816,7 +824,7 @@ async function buildImage(ralphDir) {
|
|
|
816
824
|
await generateFiles(ralphDir, config.language, config.imageName ||
|
|
817
825
|
`ralph-${basename(process.cwd())
|
|
818
826
|
.toLowerCase()
|
|
819
|
-
.replace(/[^a-z0-9-]/g, "-")}`, true, config.javaVersion, config.cliProvider, config.docker, config.claude);
|
|
827
|
+
.replace(/[^a-z0-9-]/g, "-")}`, true, config.javaVersion, config.cliProvider, config.docker, config.claude, config.cli?.model);
|
|
820
828
|
console.log("");
|
|
821
829
|
}
|
|
822
830
|
}
|
|
@@ -957,11 +965,12 @@ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvi
|
|
|
957
965
|
cliProvider,
|
|
958
966
|
docker: dockerConfig,
|
|
959
967
|
claude: claudeConfig,
|
|
968
|
+
cli: fullConfig?.cli?.model ? { command: "", model: fullConfig.cli.model } : undefined,
|
|
960
969
|
};
|
|
961
970
|
if (hasConfigChanged(ralphDir, configForHash)) {
|
|
962
971
|
const regenerate = await promptConfirm("Config has changed since last docker init. Regenerate Docker files?");
|
|
963
972
|
if (regenerate) {
|
|
964
|
-
await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider, dockerConfig, claudeConfig);
|
|
973
|
+
await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider, dockerConfig, claudeConfig, fullConfig?.cli?.model);
|
|
965
974
|
console.log("");
|
|
966
975
|
}
|
|
967
976
|
}
|
|
@@ -970,7 +979,7 @@ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvi
|
|
|
970
979
|
if (!dockerfileExists || !hasImage) {
|
|
971
980
|
if (!dockerfileExists) {
|
|
972
981
|
console.log("Docker folder not found. Initializing docker setup...\n");
|
|
973
|
-
await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider, dockerConfig, claudeConfig);
|
|
982
|
+
await generateFiles(ralphDir, language, imageName, true, javaVersion, cliProvider, dockerConfig, claudeConfig, fullConfig?.cli?.model);
|
|
974
983
|
console.log("");
|
|
975
984
|
}
|
|
976
985
|
if (!hasImage) {
|
|
@@ -1249,7 +1258,7 @@ export async function dockerInit(silent = false) {
|
|
|
1249
1258
|
console.log(`CLI provider: ${config.cliProvider}`);
|
|
1250
1259
|
}
|
|
1251
1260
|
console.log(`Image name: ${imageName}\n`);
|
|
1252
|
-
await generateFiles(ralphDir, config.language, imageName, true, config.javaVersion, config.cliProvider, config.docker, config.claude);
|
|
1261
|
+
await generateFiles(ralphDir, config.language, imageName, true, config.javaVersion, config.cliProvider, config.docker, config.claude, config.cli?.model);
|
|
1253
1262
|
if (!silent) {
|
|
1254
1263
|
console.log(`
|
|
1255
1264
|
Docker files generated in .ralph/docker/
|
|
@@ -1367,7 +1376,7 @@ INSTALLING PACKAGES (works with Docker & Podman):
|
|
|
1367
1376
|
console.log(`CLI provider: ${config.cliProvider}`);
|
|
1368
1377
|
}
|
|
1369
1378
|
console.log(`Image name: ${imageName}\n`);
|
|
1370
|
-
await generateFiles(ralphDir, config.language, imageName, force, config.javaVersion, config.cliProvider, config.docker, config.claude);
|
|
1379
|
+
await generateFiles(ralphDir, config.language, imageName, force, config.javaVersion, config.cliProvider, config.docker, config.claude, config.cli?.model);
|
|
1371
1380
|
console.log(`
|
|
1372
1381
|
Docker files generated in .ralph/docker/
|
|
1373
1382
|
|
package/dist/commands/init.js
CHANGED
|
@@ -98,6 +98,11 @@ export async function init(args) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
console.log(`\nSelected CLI provider: ${CLI_PROVIDERS[selectedCliProviderKey].name}`);
|
|
101
|
+
// Optional: specify default model
|
|
102
|
+
const modelInput = await promptInput("Enter default model name (optional, press Enter to skip): ");
|
|
103
|
+
if (modelInput.trim()) {
|
|
104
|
+
cliConfig.model = modelInput.trim();
|
|
105
|
+
}
|
|
101
106
|
// Step 2: Select language (second)
|
|
102
107
|
const languageKeys = Object.keys(LANGUAGES);
|
|
103
108
|
const languageNames = languageKeys.map((k) => `${LANGUAGES[k].name} - ${LANGUAGES[k].description}`);
|
|
@@ -369,6 +374,7 @@ config.json
|
|
|
369
374
|
# Runtime state files
|
|
370
375
|
messages.json
|
|
371
376
|
chat-state.json
|
|
377
|
+
prd-tasks.json
|
|
372
378
|
|
|
373
379
|
# Service logs
|
|
374
380
|
daemon.log
|
package/dist/commands/once.js
CHANGED
|
@@ -36,6 +36,10 @@ export async function once(args) {
|
|
|
36
36
|
});
|
|
37
37
|
const paths = getPaths();
|
|
38
38
|
const cliConfig = getCliConfig(config);
|
|
39
|
+
// Use config model as fallback when --model flag is not provided
|
|
40
|
+
if (!model && cliConfig.model) {
|
|
41
|
+
model = cliConfig.model;
|
|
42
|
+
}
|
|
39
43
|
// Check if stream-json output is enabled
|
|
40
44
|
const streamJsonConfig = config.docker?.asciinema?.streamJson;
|
|
41
45
|
const streamJsonEnabled = streamJsonConfig?.enabled ?? false;
|
package/dist/commands/run.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync, unlinkSync, appendFileSync, mk
|
|
|
3
3
|
import { extname, join } from "path";
|
|
4
4
|
import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer, saveBranchState, loadBranchState, clearBranchState, getProjectName, } from "../utils/config.js";
|
|
5
5
|
import { resolvePromptVariables, getCliProviders, GEMINI_MD } from "../templates/prompts.js";
|
|
6
|
-
import { validatePrd, smartMerge, readPrdFile,
|
|
6
|
+
import { validatePrd, smartMerge, readPrdFile, writePrdAuto, expandPrdFileReferences, } from "../utils/prd-validator.js";
|
|
7
7
|
import { getStreamJsonParser } from "../utils/stream-json.js";
|
|
8
8
|
import { sendNotificationWithDaemonEvents } from "../utils/notification.js";
|
|
9
9
|
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
@@ -118,11 +118,11 @@ function setupWorktreeRalphDir(worktreePath, branchItems, workspacePaths) {
|
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* Creates a filtered PRD file containing only incomplete items (passes: false).
|
|
121
|
-
* Optionally filters by category if specified.
|
|
121
|
+
* Optionally filters by category and/or branch if specified.
|
|
122
122
|
* Expands @{filepath} references to include file contents.
|
|
123
123
|
* Returns the path to the temp file, or null if all items pass.
|
|
124
124
|
*/
|
|
125
|
-
function createFilteredPrd(prdPath, baseDir, category) {
|
|
125
|
+
function createFilteredPrd(prdPath, baseDir, category, branchFilterActive, branchFilter) {
|
|
126
126
|
// Use readPrdFile to handle both JSON and YAML formats
|
|
127
127
|
const parsed = readPrdFile(prdPath);
|
|
128
128
|
if (!parsed) {
|
|
@@ -145,6 +145,10 @@ function createFilteredPrd(prdPath, baseDir, category) {
|
|
|
145
145
|
if (category) {
|
|
146
146
|
filteredItems = filteredItems.filter((item) => item.category === category);
|
|
147
147
|
}
|
|
148
|
+
// Apply branch filter if specified
|
|
149
|
+
if (branchFilterActive) {
|
|
150
|
+
filteredItems = applyBranchFilter(filteredItems, branchFilter);
|
|
151
|
+
}
|
|
148
152
|
// Expand @{filepath} references in description and steps
|
|
149
153
|
const expandedItems = expandPrdFileReferences(filteredItems, baseDir);
|
|
150
154
|
// Write to .ralph/prd-tasks.json so LLMs see a sensible path
|
|
@@ -406,11 +410,24 @@ function formatElapsedTime(startTime, endTime) {
|
|
|
406
410
|
parts.push(`${seconds}s`);
|
|
407
411
|
return parts.join(" ");
|
|
408
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Filters PRD items by branch.
|
|
415
|
+
* - branchFilter = "name": only items whose branch field matches "name"
|
|
416
|
+
* - branchFilter = "": only items that have any branch field set (non-empty)
|
|
417
|
+
*/
|
|
418
|
+
function applyBranchFilter(items, branchFilter) {
|
|
419
|
+
if (branchFilter === "") {
|
|
420
|
+
// --branch without value: include only items that have a branch set
|
|
421
|
+
return items.filter((item) => item.branch && item.branch.length > 0);
|
|
422
|
+
}
|
|
423
|
+
// --branch <name>: include only items whose branch matches
|
|
424
|
+
return items.filter((item) => item.branch === branchFilter);
|
|
425
|
+
}
|
|
409
426
|
/**
|
|
410
427
|
* Counts total and incomplete items in the PRD.
|
|
411
|
-
* Optionally filters by category if specified.
|
|
428
|
+
* Optionally filters by category and/or branch if specified.
|
|
412
429
|
*/
|
|
413
|
-
function countPrdItems(prdPath, category) {
|
|
430
|
+
function countPrdItems(prdPath, category, branchFilterActive, branchFilter) {
|
|
414
431
|
// Use readPrdFile to handle both JSON and YAML formats
|
|
415
432
|
const parsed = readPrdFile(prdPath);
|
|
416
433
|
if (!parsed) {
|
|
@@ -430,7 +447,10 @@ function countPrdItems(prdPath, category) {
|
|
|
430
447
|
const items = parsed.content;
|
|
431
448
|
let filteredItems = items;
|
|
432
449
|
if (category) {
|
|
433
|
-
filteredItems =
|
|
450
|
+
filteredItems = filteredItems.filter((item) => item.category === category);
|
|
451
|
+
}
|
|
452
|
+
if (branchFilterActive) {
|
|
453
|
+
filteredItems = applyBranchFilter(filteredItems, branchFilter);
|
|
434
454
|
}
|
|
435
455
|
const complete = filteredItems.filter((item) => item.passes === true).length;
|
|
436
456
|
const incomplete = filteredItems.filter((item) => item.passes === false).length;
|
|
@@ -485,7 +505,7 @@ function validateAndRecoverPrd(prdPath, validPrd) {
|
|
|
485
505
|
if (!parsed) {
|
|
486
506
|
console.log("\nNote: PRD corrupted (invalid JSON) - restored from memory.");
|
|
487
507
|
const mergedPrd = [...validPrd, ...newItems];
|
|
488
|
-
|
|
508
|
+
writePrdAuto(prdPath, mergedPrd);
|
|
489
509
|
if (newItems.length > 0) {
|
|
490
510
|
console.log(`Preserved ${newItems.length} newly-added item(s).`);
|
|
491
511
|
}
|
|
@@ -503,7 +523,7 @@ function validateAndRecoverPrd(prdPath, validPrd) {
|
|
|
503
523
|
// Add any newly-added items
|
|
504
524
|
const mergedPrd = [...mergeResult.merged, ...newItems];
|
|
505
525
|
// Write the valid structure back (with new items)
|
|
506
|
-
|
|
526
|
+
writePrdAuto(prdPath, mergedPrd);
|
|
507
527
|
if (mergeResult.itemsUpdated > 0) {
|
|
508
528
|
console.log(`Recovered: merged ${mergeResult.itemsUpdated} passes flag(s) into valid PRD structure.`);
|
|
509
529
|
}
|
|
@@ -537,6 +557,8 @@ function loadValidPrd(prdPath) {
|
|
|
537
557
|
export async function run(args) {
|
|
538
558
|
// Parse flags
|
|
539
559
|
let category;
|
|
560
|
+
let branchFilter; // undefined = no filter, "" = any branch, "name" = specific branch
|
|
561
|
+
let branchFilterActive = false; // true when --branch flag is present
|
|
540
562
|
let model;
|
|
541
563
|
let loopMode = false;
|
|
542
564
|
let allModeExplicit = false;
|
|
@@ -554,6 +576,18 @@ export async function run(args) {
|
|
|
554
576
|
process.exit(1);
|
|
555
577
|
}
|
|
556
578
|
}
|
|
579
|
+
else if (args[i] === "--branch" || args[i] === "-b") {
|
|
580
|
+
branchFilterActive = true;
|
|
581
|
+
// Check if next arg is a branch name (not another flag)
|
|
582
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
583
|
+
branchFilter = args[i + 1];
|
|
584
|
+
i++; // Skip the branch value
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
// --branch without a value: filter to items that have any branch set
|
|
588
|
+
branchFilter = "";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
557
591
|
else if (args[i] === "--model" || args[i] === "-m") {
|
|
558
592
|
if (i + 1 < args.length) {
|
|
559
593
|
model = args[i + 1];
|
|
@@ -610,6 +644,10 @@ export async function run(args) {
|
|
|
610
644
|
});
|
|
611
645
|
const paths = getPaths();
|
|
612
646
|
const cliConfig = getCliConfig(config);
|
|
647
|
+
// Use config model as fallback when --model flag is not provided
|
|
648
|
+
if (!model && cliConfig.model) {
|
|
649
|
+
model = cliConfig.model;
|
|
650
|
+
}
|
|
613
651
|
// Check if stream-json output is enabled
|
|
614
652
|
const streamJsonConfig = config.docker?.asciinema?.streamJson;
|
|
615
653
|
// Get provider-specific streamJsonArgs, falling back to Claude's defaults
|
|
@@ -634,7 +672,7 @@ export async function run(args) {
|
|
|
634
672
|
// Container is required, so always run with skip-permissions
|
|
635
673
|
const sandboxed = true;
|
|
636
674
|
if (allMode) {
|
|
637
|
-
const counts = countPrdItems(paths.prd, category);
|
|
675
|
+
const counts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
638
676
|
console.log("Starting ralph in --all mode (runs until all tasks complete)...");
|
|
639
677
|
console.log(`PRD Status: ${counts.complete}/${counts.total} complete, ${counts.incomplete} remaining`);
|
|
640
678
|
}
|
|
@@ -647,6 +685,25 @@ export async function run(args) {
|
|
|
647
685
|
if (category) {
|
|
648
686
|
console.log(`Filtering PRD items by category: ${category}`);
|
|
649
687
|
}
|
|
688
|
+
if (branchFilterActive) {
|
|
689
|
+
if (branchFilter === "") {
|
|
690
|
+
console.log("Filtering PRD items to only branched items");
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
console.log(`Filtering PRD items by branch: ${branchFilter}`);
|
|
694
|
+
}
|
|
695
|
+
// Check if any items match the branch filter; if not, exit early
|
|
696
|
+
const branchCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
697
|
+
if (branchCounts.total === 0) {
|
|
698
|
+
if (branchFilter === "") {
|
|
699
|
+
console.error("\nNo PRD items have a branch field set.");
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
console.error(`\nNo PRD items match branch "${branchFilter}".`);
|
|
703
|
+
}
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
650
707
|
if (streamJson?.enabled) {
|
|
651
708
|
console.log("Stream JSON output enabled - displaying formatted Claude output");
|
|
652
709
|
if (streamJson.saveRawJson) {
|
|
@@ -664,7 +721,7 @@ export async function run(args) {
|
|
|
664
721
|
let iterationCount = 0;
|
|
665
722
|
// Progress tracking for --all mode
|
|
666
723
|
// Progress = tasks completed OR new tasks added (allows ralph to expand the PRD)
|
|
667
|
-
const initialCounts = countPrdItems(paths.prd, category);
|
|
724
|
+
const initialCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
668
725
|
let lastCompletedCount = initialCounts.complete;
|
|
669
726
|
let lastTotalCount = initialCounts.total;
|
|
670
727
|
let iterationsWithoutProgress = 0;
|
|
@@ -792,7 +849,7 @@ export async function run(args) {
|
|
|
792
849
|
try {
|
|
793
850
|
while (true) {
|
|
794
851
|
iterationCount++;
|
|
795
|
-
const currentCounts = countPrdItems(paths.prd, category);
|
|
852
|
+
const currentCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
796
853
|
// Check if we should stop (not in loop mode)
|
|
797
854
|
if (!loopMode && !allMode) {
|
|
798
855
|
if (iterationCount > requestedIterations) {
|
|
@@ -819,6 +876,9 @@ export async function run(args) {
|
|
|
819
876
|
if (category) {
|
|
820
877
|
itemsForIteration = itemsForIteration.filter((item) => item.category === category);
|
|
821
878
|
}
|
|
879
|
+
if (branchFilterActive) {
|
|
880
|
+
itemsForIteration = applyBranchFilter(itemsForIteration, branchFilter);
|
|
881
|
+
}
|
|
822
882
|
const branchGroups = groupItemsByBranch(itemsForIteration);
|
|
823
883
|
// Check if there are any incomplete items
|
|
824
884
|
if (branchGroups.size === 0) {
|
|
@@ -834,7 +894,7 @@ export async function run(args) {
|
|
|
834
894
|
console.log("=".repeat(50));
|
|
835
895
|
while (true) {
|
|
836
896
|
await sleep(POLL_INTERVAL_MS);
|
|
837
|
-
const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category);
|
|
897
|
+
const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
|
|
838
898
|
if (newItems) {
|
|
839
899
|
console.log("\nNew incomplete item(s) detected! Resuming...");
|
|
840
900
|
break;
|
|
@@ -846,7 +906,7 @@ export async function run(args) {
|
|
|
846
906
|
else {
|
|
847
907
|
console.log("\n" + "=".repeat(50));
|
|
848
908
|
if (allMode) {
|
|
849
|
-
const counts = countPrdItems(paths.prd, category);
|
|
909
|
+
const counts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
850
910
|
if (category) {
|
|
851
911
|
console.log(`PRD COMPLETE - All "${category}" tasks finished!`);
|
|
852
912
|
}
|
|
@@ -976,7 +1036,7 @@ export async function run(args) {
|
|
|
976
1036
|
console.log(`\n\x1b[36m--- No-branch items (${noBranchItems.length} item(s)) ---\x1b[0m`);
|
|
977
1037
|
}
|
|
978
1038
|
// Create filtered PRD for no-branch items only (or all items if no branches exist)
|
|
979
|
-
const { tempPath } = createFilteredPrd(paths.prd, paths.dir, category);
|
|
1039
|
+
const { tempPath } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
|
|
980
1040
|
filteredPrdPath = tempPath;
|
|
981
1041
|
// If there are branch groups, rewrite prd-tasks.json to only include no-branch items
|
|
982
1042
|
if (hasBranches) {
|
|
@@ -995,7 +1055,7 @@ export async function run(args) {
|
|
|
995
1055
|
validateAndRecoverPrd(paths.prd, validPrd);
|
|
996
1056
|
// Track progress for --all mode: stop if no progress after N iterations
|
|
997
1057
|
if (allMode) {
|
|
998
|
-
const progressCounts = countPrdItems(paths.prd, category);
|
|
1058
|
+
const progressCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
999
1059
|
const tasksCompleted = progressCounts.complete > lastCompletedCount;
|
|
1000
1060
|
const tasksAdded = progressCounts.total > lastTotalCount;
|
|
1001
1061
|
if (tasksCompleted || tasksAdded) {
|
|
@@ -1056,7 +1116,7 @@ export async function run(args) {
|
|
|
1056
1116
|
// so its COMPLETE signal means "this group is done", not "all PRD items are done".
|
|
1057
1117
|
// We must verify the full PRD before treating this as a global completion.
|
|
1058
1118
|
if (iterOutput.includes("<promise>COMPLETE</promise>")) {
|
|
1059
|
-
const fullCounts = countPrdItems(paths.prd, category);
|
|
1119
|
+
const fullCounts = countPrdItems(paths.prd, category, branchFilterActive, branchFilter);
|
|
1060
1120
|
if (fullCounts.incomplete > 0) {
|
|
1061
1121
|
// There are still incomplete items in other groups — continue the loop
|
|
1062
1122
|
if (debug) {
|
|
@@ -1070,7 +1130,7 @@ export async function run(args) {
|
|
|
1070
1130
|
console.log("=".repeat(50));
|
|
1071
1131
|
while (true) {
|
|
1072
1132
|
await sleep(POLL_INTERVAL_MS);
|
|
1073
|
-
const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category);
|
|
1133
|
+
const { hasIncomplete: newItems } = createFilteredPrd(paths.prd, paths.dir, category, branchFilterActive, branchFilter);
|
|
1074
1134
|
if (newItems) {
|
|
1075
1135
|
console.log("\nNew incomplete item(s) detected! Resuming...");
|
|
1076
1136
|
break;
|
|
@@ -135,6 +135,24 @@
|
|
|
135
135
|
},
|
|
136
136
|
"modelArgs": ["--model"]
|
|
137
137
|
},
|
|
138
|
+
"ollama": {
|
|
139
|
+
"name": "Ollama",
|
|
140
|
+
"description": "Local LLM server with CLI interface",
|
|
141
|
+
"command": "ollama",
|
|
142
|
+
"defaultArgs": [],
|
|
143
|
+
"yoloArgs": [],
|
|
144
|
+
"promptArgs": ["run"],
|
|
145
|
+
"docker": {
|
|
146
|
+
"install": "# Install zstd (required for Ollama model layers)\nRUN apt-get update && apt-get install -y zstd && rm -rf /var/lib/apt/lists/*\n\n# Install Ollama\nRUN curl -fsSL https://ollama.com/install.sh | sh",
|
|
147
|
+
"note": "After building, Ollama needs a model pulled. Set cli.model in config.json and rebuild, or pull manually inside the container with: ollama pull <model>"
|
|
148
|
+
},
|
|
149
|
+
"envVars": [],
|
|
150
|
+
"credentialMount": null,
|
|
151
|
+
"modelConfig": {
|
|
152
|
+
"note": "Set cli.model in config.json (e.g., \"qwen3:1.7b\") to auto-pull during docker build. Or pull manually: ollama pull <model>"
|
|
153
|
+
},
|
|
154
|
+
"modelArgs": []
|
|
155
|
+
},
|
|
138
156
|
"custom": {
|
|
139
157
|
"name": "Custom CLI",
|
|
140
158
|
"description": "Configure your own AI CLI tool",
|
|
@@ -12,6 +12,12 @@ export const CONFIG_SECTIONS = [
|
|
|
12
12
|
icon: "⚙",
|
|
13
13
|
fields: ["language", "checkCommand", "testCommand", "imageName", "technologies", "javaVersion"],
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
id: "cli",
|
|
17
|
+
label: "CLI",
|
|
18
|
+
icon: "⌨",
|
|
19
|
+
fields: ["cliProvider", "cli.command", "cli.args", "cli.model", "cli.yoloArgs", "cli.promptArgs", "cli.modelArgs", "cli.fileArgs"],
|
|
20
|
+
},
|
|
15
21
|
{
|
|
16
22
|
id: "docker",
|
|
17
23
|
label: "Docker",
|
package/dist/utils/config.d.ts
CHANGED
package/docs/BRANCHING.md
CHANGED
|
@@ -177,18 +177,18 @@ The branch state is cleaned up from config after the group completes.
|
|
|
177
177
|
|
|
178
178
|
### `ralph branch list`
|
|
179
179
|
|
|
180
|
-
Shows all branches referenced in
|
|
180
|
+
Shows all branches referenced in the PRD with status:
|
|
181
181
|
|
|
182
182
|
```
|
|
183
183
|
Branches:
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* = currently active
|
|
185
|
+
○ feat/auth 2/3 [worktree] ◀ active
|
|
186
|
+
○ feat/ui 0/1
|
|
187
|
+
○ (no branch) 1/2
|
|
190
188
|
```
|
|
191
189
|
|
|
190
|
+
Each branch shows a pass/total count, a `[worktree]` indicator if the worktree directory exists on disk, and `◀ active` if it's the branch currently being worked on.
|
|
191
|
+
|
|
192
192
|
### `ralph branch merge <name>`
|
|
193
193
|
|
|
194
194
|
Merges a completed branch back into the base branch:
|
|
@@ -199,33 +199,25 @@ ralph branch merge feat/auth
|
|
|
199
199
|
|
|
200
200
|
1. Asks for confirmation
|
|
201
201
|
2. Merges `feat/auth` into the base branch (in `/workspace`)
|
|
202
|
-
3. Removes the worktree with `git worktree remove`
|
|
203
|
-
4. Cleans up the worktree directory from the host
|
|
202
|
+
3. Removes the worktree with `git worktree remove` (if it exists)
|
|
204
203
|
|
|
205
204
|
If there are merge conflicts, ralph aborts the merge, lists the conflicting files, and suggests resolving manually or creating a PRD item for the AI to resolve.
|
|
206
205
|
|
|
207
206
|
### `ralph branch pr <name>`
|
|
208
207
|
|
|
209
|
-
Creates a
|
|
208
|
+
Creates a GitHub pull request for the branch using the `gh` CLI:
|
|
210
209
|
|
|
211
210
|
```bash
|
|
212
211
|
ralph branch pr feat/auth
|
|
213
212
|
```
|
|
214
213
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
branch: feat/auth
|
|
221
|
-
steps:
|
|
222
|
-
- Push feat/auth to the remote repository
|
|
223
|
-
- Create a pull request from feat/auth into main using `gh pr create`
|
|
224
|
-
- Include a summary of changes in the PR description
|
|
225
|
-
passes: false
|
|
226
|
-
```
|
|
214
|
+
1. Verifies `gh` is installed and authenticated
|
|
215
|
+
2. Pushes the branch to the remote if it has no upstream tracking
|
|
216
|
+
3. Generates a PR body with PRD item checklist and commit log
|
|
217
|
+
4. Asks for confirmation
|
|
218
|
+
5. Creates the PR via `gh pr create`
|
|
227
219
|
|
|
228
|
-
|
|
220
|
+
Requires the [GitHub CLI](https://cli.github.com/) (`gh`) to be installed and authenticated.
|
|
229
221
|
|
|
230
222
|
### `ralph branch delete <name>`
|
|
231
223
|
|
|
@@ -245,17 +237,18 @@ ralph branch delete feat/old-feature
|
|
|
245
237
|
When `ralph branch merge` detects conflicts:
|
|
246
238
|
|
|
247
239
|
```
|
|
248
|
-
Merge conflict detected
|
|
240
|
+
Merge conflict detected!
|
|
249
241
|
|
|
250
242
|
Conflicting files:
|
|
251
|
-
|
|
252
|
-
|
|
243
|
+
src/components/App.tsx
|
|
244
|
+
src/utils/config.ts
|
|
253
245
|
|
|
254
|
-
|
|
246
|
+
Merge aborted.
|
|
255
247
|
|
|
256
|
-
|
|
257
|
-
1. Resolve manually
|
|
258
|
-
2.
|
|
248
|
+
To resolve:
|
|
249
|
+
1. Resolve conflicts manually and merge again
|
|
250
|
+
2. Or add a PRD item to resolve the conflicts:
|
|
251
|
+
ralph prd add # describe the conflict resolution needed
|
|
259
252
|
```
|
|
260
253
|
|
|
261
254
|
Conflicts are a normal part of branching. They occur when both the branch and the base branch modify the same lines. Ralph never force-merges — it always aborts cleanly and lets the user decide.
|
package/docs/CHAT-CLIENTS.md
CHANGED
|
@@ -79,6 +79,7 @@ The bot will respond to commands in the allowed chats.
|
|
|
79
79
|
| `/stop` | Stop running ralph process |
|
|
80
80
|
| `/action build` | Execute a daemon action |
|
|
81
81
|
| `/claude Fix the CSS` | Run Claude Code with a prompt |
|
|
82
|
+
| `/branch list` | List branches and their status |
|
|
82
83
|
| `/help` | Show available commands |
|
|
83
84
|
|
|
84
85
|
---
|
|
@@ -132,20 +133,14 @@ Socket Mode allows the bot to receive events without a public URL.
|
|
|
132
133
|
- `message.im` - Direct messages
|
|
133
134
|
4. Click **Save Changes**
|
|
134
135
|
|
|
135
|
-
### Step 6: Create Slash
|
|
136
|
+
### Step 6: Create Slash Command (Optional)
|
|
136
137
|
|
|
137
138
|
1. Go to **Slash Commands**
|
|
138
|
-
2. Click **Create New Command
|
|
139
|
+
2. Click **Create New Command**:
|
|
139
140
|
|
|
140
141
|
| Command | Request URL | Description |
|
|
141
142
|
|---------|-------------|-------------|
|
|
142
|
-
| `/
|
|
143
|
-
| `/status` | (leave empty for Socket Mode) | Show PRD progress |
|
|
144
|
-
| `/add` | (leave empty for Socket Mode) | Add new task |
|
|
145
|
-
| `/exec` | (leave empty for Socket Mode) | Execute shell command |
|
|
146
|
-
| `/stop` | (leave empty for Socket Mode) | Stop ralph process |
|
|
147
|
-
| `/action` | (leave empty for Socket Mode) | Execute daemon action |
|
|
148
|
-
| `/claude` | (leave empty for Socket Mode) | Run Claude Code |
|
|
143
|
+
| `/ralph` | (leave empty for Socket Mode) | Ralph unified command (use subcommands like `/ralph run`, `/ralph status`) |
|
|
149
144
|
|
|
150
145
|
### Step 7: Get Channel IDs
|
|
151
146
|
|
|
@@ -186,17 +181,18 @@ ralph chat start
|
|
|
186
181
|
|
|
187
182
|
### Slack Commands
|
|
188
183
|
|
|
189
|
-
Use
|
|
184
|
+
Use the `/ralph` slash command with subcommands, or message the bot directly:
|
|
190
185
|
|
|
191
186
|
| Command | Description |
|
|
192
187
|
|---------|-------------|
|
|
193
|
-
| `/run` or `/run feature` | Start automation |
|
|
194
|
-
| `/status` | Show PRD progress |
|
|
195
|
-
| `/add Fix the bug` | Add a task |
|
|
196
|
-
| `/exec npm test` | Execute command |
|
|
197
|
-
| `/stop` | Stop ralph |
|
|
198
|
-
| `/action build` | Execute action |
|
|
199
|
-
| `/
|
|
188
|
+
| `/ralph run` or `/ralph run feature` | Start automation |
|
|
189
|
+
| `/ralph status` | Show PRD progress |
|
|
190
|
+
| `/ralph add Fix the bug` | Add a task |
|
|
191
|
+
| `/ralph exec npm test` | Execute command |
|
|
192
|
+
| `/ralph stop` | Stop ralph |
|
|
193
|
+
| `/ralph action build` | Execute action |
|
|
194
|
+
| `/ralph branch list` | Manage branches |
|
|
195
|
+
| `/ralph Fix CSS` | Run Claude Code (any unrecognized subcommand) |
|
|
200
196
|
|
|
201
197
|
---
|
|
202
198
|
|