ralph-cli-sandboxed 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,24 +20,21 @@ ralph init
20
20
  ## Quick Start
21
21
 
22
22
  ```bash
23
- # 1. Initialize ralph in your project
23
+ # 1. Initialize ralph in your project (creates config AND Docker files)
24
24
  ralph init
25
25
 
26
- # 2. Add requirements to your PRD
26
+ # 2. Edit .ralph/prd.json with your requirements, or use:
27
27
  ralph add
28
28
 
29
- # 3. Run a single iteration
30
- ralph once
31
-
32
- # 4. Or run until all tasks complete (default)
33
- ralph run
29
+ # 3. Run in Docker sandbox (auto-builds image on first run)
30
+ ralph docker run
34
31
  ```
35
32
 
36
33
  ## Commands
37
34
 
38
35
  | Command | Description |
39
36
  |---------|-------------|
40
- | `ralph init` | Initialize ralph in current project |
37
+ | `ralph init` | Initialize ralph in current project (config + Docker files) |
41
38
  | `ralph once` | Run a single automation iteration |
42
39
  | `ralph run [n]` | Run automation iterations (default: all tasks) |
43
40
  | `ralph add` | Add a new PRD entry (interactive) |
@@ -45,21 +42,45 @@ ralph run
45
42
  | `ralph status` | Show PRD completion status |
46
43
  | `ralph toggle <n>` | Toggle passes status for entry n |
47
44
  | `ralph clean` | Remove all passing entries from PRD |
45
+ | `ralph fix-prd [opts]` | Validate and recover corrupted PRD file |
46
+ | `ralph prompt [opts]` | Display resolved prompt |
48
47
  | `ralph docker <sub>` | Manage Docker sandbox environment |
49
48
  | `ralph help` | Show help message |
50
49
 
51
50
  > **Note:** `ralph prd <subcommand>` still works for compatibility (e.g., `ralph prd add`).
52
51
 
52
+ ### Run Options
53
+
54
+ ```bash
55
+ ralph run --model claude-sonnet-4-20250514 # Use specific model
56
+ ralph run -m claude-sonnet-4-20250514 # Short form
57
+ ```
58
+
59
+ The `--model` flag is passed to the underlying CLI provider. Support depends on your CLI configuration (see [CLI Configuration](#cli-configuration)).
60
+
61
+ ### fix-prd Options
62
+
63
+ ```bash
64
+ ralph fix-prd # Validate and auto-fix corrupted PRD
65
+ ralph fix-prd --verify # Check only, don't fix
66
+ ralph fix-prd <backup> # Restore from specific backup file
67
+ ```
68
+
53
69
  ## Configuration
54
70
 
55
71
  After running `ralph init`, you'll have:
56
72
 
57
73
  ```
58
74
  .ralph/
59
- ├── config.json # Project configuration
60
- ├── prompt.md # Shared prompt template
61
- ├── prd.json # Product requirements document
62
- └── progress.txt # Progress tracking file
75
+ ├── config.json # Project configuration
76
+ ├── prompt.md # Shared prompt template
77
+ ├── prd.json # Product requirements document
78
+ ├── progress.txt # Progress tracking file
79
+ ├── HOW-TO-WRITE-PRDs.md # PRD writing guide
80
+ └── docker/ # Docker sandbox files
81
+ ├── Dockerfile
82
+ ├── docker-compose.yml
83
+ └── ...
63
84
  ```
64
85
 
65
86
  ### Supported Languages
@@ -80,9 +101,9 @@ Ralph supports multiple AI CLI tools. Select your provider during `ralph init`:
80
101
  | [Claude Code](https://github.com/anthropics/claude-code) | Working | `ANTHROPIC_API_KEY` | Default provider. Also supports ~/.claude OAuth credentials |
81
102
  | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Working | `GEMINI_API_KEY`, `GOOGLE_API_KEY` | |
82
103
  | [OpenCode](https://github.com/anomalyco/opencode) | Working | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` | Requires [PR #9073](https://github.com/anomalyco/opencode/pull/9073) |
83
- | [Aider](https://github.com/paul-gauthier/aider) | Untested | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` | |
84
- | [Codex CLI](https://github.com/openai/codex) | Untested | `OPENAI_API_KEY` | |
85
- | [AMP](https://ampcode.com/) | Untested | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | |
104
+ | [Aider](https://github.com/paul-gauthier/aider) | Working | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` | |
105
+ | [Codex CLI](https://github.com/openai/codex) | Testers wanted | `OPENAI_API_KEY` | Sponsors welcome |
106
+ | [AMP](https://ampcode.com/) | Testers wanted | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | Sponsors welcome |
86
107
  | Custom | - | User-defined | Configure your own CLI |
87
108
 
88
109
  ### CLI Configuration
@@ -93,15 +114,19 @@ Ralph can be configured to use different AI CLI tools. By default, it uses Claud
93
114
  {
94
115
  "cli": {
95
116
  "command": "claude",
96
- "args": ["--permission-mode", "acceptEdits"]
117
+ "args": ["--permission-mode", "acceptEdits"],
118
+ "modelArgs": ["--model"],
119
+ "promptArgs": ["-p"]
97
120
  }
98
121
  }
99
122
  ```
100
123
 
101
124
  - `command`: The CLI executable name (must be in PATH)
102
125
  - `args`: Default arguments passed to the CLI
126
+ - `modelArgs`: Arguments for passing model (e.g., `["--model"]`). Required for `--model` flag support.
127
+ - `promptArgs`: Arguments for passing prompt (e.g., `["-p"]` or `[]` for positional)
103
128
 
104
- The `-p` flag with PRD/prompt content and `--dangerously-skip-permissions` (in containers) are added automatically at runtime.
129
+ The prompt content and `--dangerously-skip-permissions` (in containers) are added automatically at runtime.
105
130
 
106
131
  ## PRD Format
107
132
 
@@ -124,21 +149,42 @@ The PRD (`prd.json`) is an array of requirements:
124
149
 
125
150
  Categories: `ui`, `feature`, `bugfix`, `setup`, `development`, `testing`, `docs`
126
151
 
152
+ ### Advanced: File References
153
+
154
+ PRD steps can include file contents using the `@{filepath}` syntax:
155
+
156
+ ```json
157
+ {
158
+ "steps": ["Based on content: @{backup.prd.2024-01-15.json}"]
159
+ }
160
+ ```
161
+
162
+ File paths are resolved relative to the project root. Absolute paths are also supported.
163
+
164
+ ## PRD Protection
165
+
166
+ Ralph includes automatic PRD protection to handle cases where the LLM corrupts the PRD structure:
167
+
168
+ - **Automatic backup**: Before each run, the PRD is backed up
169
+ - **Validation**: After each iteration, the PRD structure is validated
170
+ - **Smart recovery**: If corrupted, ralph attempts to extract `passes: true` flags from the corrupted PRD and merge them into the backup
171
+ - **Manual recovery**: Use `ralph fix-prd` to validate, auto-fix, or restore from a specific backup
172
+
173
+ ### Dynamic Iteration Limits
174
+
175
+ To prevent runaway loops, `ralph run` limits iterations to `incomplete_tasks + 3`. This limit adjusts dynamically if new tasks are added during execution.
176
+
127
177
  ## Docker Sandbox
128
178
 
129
179
  Run ralph in an isolated Docker container:
130
180
 
131
181
  ```bash
132
- # Generate Docker files
133
- ralph docker init
134
-
135
- # Build the image
136
- ralph docker build
137
-
138
- # Run container
182
+ # Run container (auto-builds image on first run)
139
183
  ralph docker run
140
184
  ```
141
185
 
186
+ > **Note:** `ralph init` auto-creates Docker files in `.ralph/docker/`. Use `ralph docker init` to regenerate them if needed.
187
+
142
188
  Features:
143
189
  - Based on [Claude Code devcontainer](https://github.com/anthropics/claude-code/tree/main/.devcontainer)
144
190
  - Network sandboxing (firewall allows only GitHub, npm, Anthropic API)
@@ -1 +1,6 @@
1
+ /**
2
+ * Initialize Docker files. Can be called directly from other commands.
3
+ * @param silent - If true, suppress the "Next steps" message
4
+ */
5
+ export declare function dockerInit(silent?: boolean): Promise<void>;
1
6
  export declare function docker(args: string[]): Promise<void>;
@@ -345,6 +345,7 @@ function getCliProviderConfig(cliProvider) {
345
345
  command: "claude",
346
346
  yoloArgs: ["--dangerously-skip-permissions"],
347
347
  envVars: ["ANTHROPIC_API_KEY"],
348
+ modelConfig: { envVar: "CLAUDE_MODEL", note: "Or use --model flag" },
348
349
  };
349
350
  }
350
351
  return {
@@ -352,6 +353,7 @@ function getCliProviderConfig(cliProvider) {
352
353
  command: provider.command,
353
354
  yoloArgs: provider.yoloArgs || [],
354
355
  envVars: provider.envVars || [],
356
+ modelConfig: provider.modelConfig,
355
357
  };
356
358
  }
357
359
  async function runContainer(ralphDir, imageName, language, javaVersion, cliProvider) {
@@ -397,6 +399,18 @@ async function runContainer(ralphDir, imageName, language, javaVersion, cliProvi
397
399
  }
398
400
  }
399
401
  console.log("");
402
+ // Display model configuration info if available
403
+ if (cliConfig.modelConfig) {
404
+ console.log("Model configuration (optional):");
405
+ if (cliConfig.modelConfig.envVar) {
406
+ const note = cliConfig.modelConfig.note ? ` - ${cliConfig.modelConfig.note}` : "";
407
+ console.log(` ${cliConfig.modelConfig.envVar}${note}`);
408
+ }
409
+ else if (cliConfig.modelConfig.note) {
410
+ console.log(` ${cliConfig.modelConfig.note}`);
411
+ }
412
+ console.log("");
413
+ }
400
414
  console.log("Set them in docker-compose.yml or export before running.");
401
415
  console.log("");
402
416
  return new Promise((resolve, reject) => {
@@ -582,6 +596,36 @@ async function cleanImage(imageName, ralphDir) {
582
596
  console.log("\nDocker image and associated resources cleaned.");
583
597
  console.log("Run 'ralph docker build' to rebuild the image.");
584
598
  }
599
+ /**
600
+ * Initialize Docker files. Can be called directly from other commands.
601
+ * @param silent - If true, suppress the "Next steps" message
602
+ */
603
+ export async function dockerInit(silent = false) {
604
+ const config = loadConfig();
605
+ const ralphDir = getRalphDir();
606
+ const imageName = config.imageName ?? `ralph-${basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
607
+ console.log(`\nGenerating Docker files for: ${config.language}`);
608
+ if ((config.language === "java" || config.language === "kotlin") && config.javaVersion) {
609
+ console.log(`Java version: ${config.javaVersion}`);
610
+ }
611
+ if (config.cliProvider && config.cliProvider !== "claude") {
612
+ console.log(`CLI provider: ${config.cliProvider}`);
613
+ }
614
+ console.log(`Image name: ${imageName}\n`);
615
+ await generateFiles(ralphDir, config.language, imageName, true, config.javaVersion, config.cliProvider);
616
+ if (!silent) {
617
+ console.log(`
618
+ Docker files generated in .ralph/docker/
619
+
620
+ Next steps:
621
+ 1. Build the image: ralph docker build
622
+ 2. Run container: ralph docker run
623
+
624
+ Or use docker compose directly:
625
+ cd .ralph/docker && docker compose run --rm ralph
626
+ `);
627
+ }
628
+ }
585
629
  export async function docker(args) {
586
630
  const subcommand = args[0];
587
631
  const subArgs = args.slice(1);
@@ -0,0 +1 @@
1
+ export declare function fixPrd(args?: string[]): Promise<void>;
@@ -0,0 +1,201 @@
1
+ import { existsSync, readFileSync, copyFileSync } from "fs";
2
+ import { join, isAbsolute } from "path";
3
+ import { getPaths, getRalphDir } from "../utils/config.js";
4
+ import { validatePrd, attemptRecovery, createBackup, findLatestBackup, createTemplatePrd, readPrdFile, writePrd, } from "../utils/prd-validator.js";
5
+ /**
6
+ * Resolves a backup path - can be absolute, relative, or just a filename.
7
+ */
8
+ function resolveBackupPath(backupArg) {
9
+ if (isAbsolute(backupArg)) {
10
+ return backupArg;
11
+ }
12
+ // Check if it's in the .ralph directory
13
+ const inRalphDir = join(getRalphDir(), backupArg);
14
+ if (existsSync(inRalphDir)) {
15
+ return inRalphDir;
16
+ }
17
+ // Otherwise treat as relative to cwd
18
+ return join(process.cwd(), backupArg);
19
+ }
20
+ /**
21
+ * Restores PRD from a specific backup file.
22
+ */
23
+ function restoreFromBackup(prdPath, backupPath) {
24
+ if (!existsSync(backupPath)) {
25
+ console.error(`Error: Backup file not found: ${backupPath}`);
26
+ return false;
27
+ }
28
+ try {
29
+ const backupContent = readFileSync(backupPath, "utf-8");
30
+ const backupParsed = JSON.parse(backupContent);
31
+ const validation = validatePrd(backupParsed);
32
+ if (!validation.valid) {
33
+ console.error("Error: Backup file contains invalid PRD structure:");
34
+ validation.errors.slice(0, 3).forEach(err => {
35
+ console.error(` - ${err}`);
36
+ });
37
+ return false;
38
+ }
39
+ // Create backup of current file before overwriting
40
+ if (existsSync(prdPath)) {
41
+ const currentBackup = createBackup(prdPath);
42
+ console.log(`Created backup of current PRD: ${currentBackup}`);
43
+ }
44
+ writePrd(prdPath, validation.data);
45
+ console.log(`\x1b[32m✓ PRD restored from: ${backupPath}\x1b[0m`);
46
+ console.log(` Restored ${validation.data.length} entries.`);
47
+ return true;
48
+ }
49
+ catch (err) {
50
+ console.error(`Error: Failed to read backup file: ${err}`);
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Handles the case where the PRD file contains invalid JSON.
56
+ * Attempts to restore from backup or reset to template.
57
+ */
58
+ function handleBrokenPrd(prdPath) {
59
+ // Create backup of the broken file (preserving raw content)
60
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
61
+ const backupPath = prdPath.replace("prd.json", `backup.prd.${timestamp}.json`);
62
+ copyFileSync(prdPath, backupPath);
63
+ console.log(`Created backup of broken file: ${backupPath}\n`);
64
+ // Try to restore from a previous valid backup
65
+ const latestBackup = findLatestBackup(prdPath);
66
+ if (latestBackup && latestBackup !== backupPath) {
67
+ console.log(`Found previous backup: ${latestBackup}`);
68
+ try {
69
+ const backupContent = readFileSync(latestBackup, "utf-8");
70
+ const backupParsed = JSON.parse(backupContent);
71
+ const backupValidation = validatePrd(backupParsed);
72
+ if (backupValidation.valid) {
73
+ writePrd(prdPath, backupValidation.data);
74
+ console.log("\x1b[32m✓ PRD restored from backup!\x1b[0m");
75
+ console.log(` Restored ${backupValidation.data.length} entries.`);
76
+ console.log("\x1b[33m Note: Recent changes may have been lost.\x1b[0m");
77
+ return;
78
+ }
79
+ else {
80
+ console.log(" Backup is also invalid, cannot restore.\n");
81
+ }
82
+ }
83
+ catch {
84
+ console.log(" Failed to read backup file.\n");
85
+ }
86
+ }
87
+ else {
88
+ console.log(" No valid backup found to restore from.\n");
89
+ }
90
+ // Reset to template as last resort - with instructions to recover from backup
91
+ console.log("Resetting PRD to recovery template...");
92
+ writePrd(prdPath, createTemplatePrd(backupPath));
93
+ console.log("\x1b[33m✓ PRD reset with recovery task.\x1b[0m");
94
+ console.log(" Next 'ralph run' will instruct the LLM to recover entries from backup.");
95
+ console.log(` Backup location: ${backupPath}`);
96
+ }
97
+ export async function fixPrd(args = []) {
98
+ // Parse arguments
99
+ let verifyOnly = false;
100
+ let backupFile;
101
+ for (let i = 0; i < args.length; i++) {
102
+ if (args[i] === "--verify" || args[i] === "-v") {
103
+ verifyOnly = true;
104
+ }
105
+ else if (!args[i].startsWith("-")) {
106
+ backupFile = args[i];
107
+ }
108
+ }
109
+ const paths = getPaths();
110
+ // If a backup file is specified, restore from it
111
+ if (backupFile) {
112
+ const resolvedPath = resolveBackupPath(backupFile);
113
+ const success = restoreFromBackup(paths.prd, resolvedPath);
114
+ process.exit(success ? 0 : 1);
115
+ }
116
+ if (!existsSync(paths.prd)) {
117
+ console.error("Error: .ralph/prd.json not found. Run 'ralph init' first.");
118
+ process.exit(1);
119
+ }
120
+ console.log("Checking PRD structure...\n");
121
+ // Step 1: Try to read and parse the file
122
+ const parsed = readPrdFile(paths.prd);
123
+ if (!parsed) {
124
+ // JSON parsing failed - file is completely broken
125
+ console.log("\x1b[31m✗ PRD file contains invalid JSON.\x1b[0m\n");
126
+ if (verifyOnly) {
127
+ process.exit(1);
128
+ }
129
+ handleBrokenPrd(paths.prd);
130
+ return;
131
+ }
132
+ // Step 2: Validate the structure
133
+ const validation = validatePrd(parsed.content);
134
+ if (validation.valid) {
135
+ console.log("\x1b[32m✓ PRD is valid.\x1b[0m");
136
+ console.log(` ${validation.data.length} entries found.`);
137
+ return;
138
+ }
139
+ // PRD is invalid
140
+ console.log("\x1b[31m✗ PRD structure is invalid:\x1b[0m");
141
+ validation.errors.slice(0, 5).forEach(err => {
142
+ console.log(` - ${err}`);
143
+ });
144
+ if (validation.errors.length > 5) {
145
+ console.log(` - ... and ${validation.errors.length - 5} more errors`);
146
+ }
147
+ console.log();
148
+ if (verifyOnly) {
149
+ process.exit(1);
150
+ }
151
+ // Step 3: Create backup before any modifications
152
+ const backupPath = createBackup(paths.prd);
153
+ console.log(`Created backup: ${backupPath}\n`);
154
+ // Step 4: Attempt recovery strategies
155
+ console.log("Attempting recovery...\n");
156
+ // Strategy 1: Try to recover from malformed structure
157
+ const recovered = attemptRecovery(parsed.content);
158
+ if (recovered) {
159
+ // Validate the recovered data
160
+ const recoveredValidation = validatePrd(recovered);
161
+ if (recoveredValidation.valid) {
162
+ writePrd(paths.prd, recovered);
163
+ console.log("\x1b[32m✓ PRD recovered successfully!\x1b[0m");
164
+ console.log(` Recovered ${recovered.length} entries by unwrapping/remapping fields.`);
165
+ return;
166
+ }
167
+ }
168
+ console.log(" Direct recovery failed.\n");
169
+ // Strategy 2: Restore from backup
170
+ const latestBackup = findLatestBackup(paths.prd);
171
+ if (latestBackup && latestBackup !== backupPath) {
172
+ console.log(`Found previous backup: ${latestBackup}`);
173
+ try {
174
+ const backupContent = readFileSync(latestBackup, "utf-8");
175
+ const backupParsed = JSON.parse(backupContent);
176
+ const backupValidation = validatePrd(backupParsed);
177
+ if (backupValidation.valid) {
178
+ writePrd(paths.prd, backupValidation.data);
179
+ console.log("\x1b[32m✓ PRD restored from backup!\x1b[0m");
180
+ console.log(` Restored ${backupValidation.data.length} entries.`);
181
+ console.log("\x1b[33m Note: Recent changes may have been lost.\x1b[0m");
182
+ return;
183
+ }
184
+ else {
185
+ console.log(" Backup is also invalid, cannot restore.\n");
186
+ }
187
+ }
188
+ catch {
189
+ console.log(" Failed to read backup file.\n");
190
+ }
191
+ }
192
+ else {
193
+ console.log(" No valid backup found to restore from.\n");
194
+ }
195
+ // Strategy 3: Reset to recovery template - LLM will fix it on next run
196
+ console.log("Resetting PRD to recovery template...");
197
+ writePrd(paths.prd, createTemplatePrd(backupPath));
198
+ console.log("\x1b[33m✓ PRD reset with recovery task.\x1b[0m");
199
+ console.log(" Next 'ralph run' will instruct the LLM to recover entries from backup.");
200
+ console.log(` Backup location: ${backupPath}`);
201
+ }
@@ -13,6 +13,7 @@ COMMANDS:
13
13
  status Show PRD completion status
14
14
  toggle <n> Toggle passes status for entry n
15
15
  clean Remove all passing entries from the PRD
16
+ fix-prd [opts] Validate and recover corrupted PRD file
16
17
  prompt [opts] Display resolved prompt (for testing in Claude Code)
17
18
  docker <sub> Manage Docker sandbox environment
18
19
  help Show this help message
@@ -42,6 +43,10 @@ TOGGLE OPTIONS:
42
43
  <n> [n2] [n3]... Toggle one or more entries by number
43
44
  --all, -a Toggle all PRD entries
44
45
 
46
+ FIX-PRD OPTIONS:
47
+ <backup-file> Restore PRD from a specific backup file
48
+ --verify, -v Only verify format, don't attempt to fix
49
+
45
50
  DOCKER SUBCOMMANDS:
46
51
  docker init Generate Dockerfile and scripts
47
52
  docker build Build image (always fetches latest Claude Code)
@@ -66,6 +71,9 @@ EXAMPLES:
66
71
  ralph toggle 1 2 3 # Toggle multiple entries
67
72
  ralph toggle --all # Toggle all entries
68
73
  ralph clean # Remove passing entries
74
+ ralph fix-prd # Validate/recover corrupted PRD file
75
+ ralph fix-prd --verify # Check PRD format without fixing
76
+ ralph fix-prd backup.prd.2024-01-15.json # Restore from specific backup
69
77
  ralph prompt # Display resolved prompt
70
78
  ralph docker init # Generate Dockerfile for sandboxed env
71
79
  ralph docker build # Build Docker image
@@ -3,6 +3,7 @@ import { join, basename, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { getLanguages, generatePromptTemplate, DEFAULT_PRD, DEFAULT_PROGRESS, getCliProviders } from "../templates/prompts.js";
5
5
  import { promptSelectWithArrows, promptConfirm, promptInput, promptMultiSelectWithArrows } from "../utils/prompt.js";
6
+ import { dockerInit } from "./docker.js";
6
7
  // Get package root directory (works for both dev and installed package)
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = dirname(__filename);
@@ -168,11 +169,13 @@ export async function init(_args) {
168
169
  else {
169
170
  console.log(`Skipped ${RALPH_DIR}/${PRD_GUIDE_FILE} (already exists)`);
170
171
  }
172
+ // Generate Docker files automatically
173
+ await dockerInit(true);
174
+ console.log("Created .ralph/docker/ files");
171
175
  console.log("\nRalph initialized successfully!");
172
176
  console.log("\nNext steps:");
173
- console.log(" 1. Read .ralph/HOW-TO-WRITE-PRDs.md for guidance on writing PRDs");
174
- console.log(" 2. Edit .ralph/prd.json to add your project requirements");
175
- console.log(" 3. Run 'ralph docker init' to generate Docker configuration");
176
- console.log(" 4. Run 'ralph docker build' to build the container");
177
- console.log(" 5. Run 'ralph docker run' to start ralph in the container");
177
+ console.log(" 1. Edit .ralph/prd.json to add your project requirements");
178
+ console.log(" 2. Run 'ralph docker run' to start (auto-builds image on first run)");
179
+ console.log("\nSee .ralph/HOW-TO-WRITE-PRDs.md for guidance on writing PRDs");
180
+ console.log("To regenerate Docker files: ralph docker init");
178
181
  }
@@ -2,7 +2,24 @@ import { spawn } from "child_process";
2
2
  import { checkFilesExist, loadConfig, loadPrompt, getPaths, getCliConfig, requireContainer } from "../utils/config.js";
3
3
  import { resolvePromptVariables } from "../templates/prompts.js";
4
4
  export async function once(args) {
5
- const debug = args.includes("--debug") || args.includes("-d");
5
+ // Parse flags
6
+ let debug = false;
7
+ let model;
8
+ for (let i = 0; i < args.length; i++) {
9
+ if (args[i] === "--debug" || args[i] === "-d") {
10
+ debug = true;
11
+ }
12
+ else if (args[i] === "--model" || args[i] === "-m") {
13
+ if (i + 1 < args.length) {
14
+ model = args[i + 1];
15
+ i++; // Skip the model value
16
+ }
17
+ else {
18
+ console.error("Error: --model requires a value");
19
+ process.exit(1);
20
+ }
21
+ }
22
+ }
6
23
  requireContainer("once");
7
24
  checkFilesExist();
8
25
  const config = loadConfig();
@@ -16,7 +33,7 @@ export async function once(args) {
16
33
  const paths = getPaths();
17
34
  const cliConfig = getCliConfig(config);
18
35
  console.log("Starting single ralph iteration...\n");
19
- // Build CLI arguments: config args + yolo args + prompt args
36
+ // Build CLI arguments: config args + yolo args + model args + prompt args
20
37
  // Use yoloArgs from config if available, otherwise default to Claude's --dangerously-skip-permissions
21
38
  const yoloArgs = cliConfig.yoloArgs ?? ["--dangerously-skip-permissions"];
22
39
  const promptArgs = cliConfig.promptArgs ?? ["-p"];
@@ -24,9 +41,12 @@ export async function once(args) {
24
41
  const cliArgs = [
25
42
  ...(cliConfig.args ?? []),
26
43
  ...yoloArgs,
27
- ...promptArgs,
28
- promptValue,
29
44
  ];
45
+ // Add model args if model is specified
46
+ if (model && cliConfig.modelArgs) {
47
+ cliArgs.push(...cliConfig.modelArgs, model);
48
+ }
49
+ cliArgs.push(...promptArgs, promptValue);
30
50
  if (debug) {
31
51
  console.log(`[debug] ${cliConfig.command} ${cliArgs.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}\n`);
32
52
  }