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 +70 -24
- package/dist/commands/docker.d.ts +5 -0
- package/dist/commands/docker.js +44 -0
- package/dist/commands/fix-prd.d.ts +1 -0
- package/dist/commands/fix-prd.js +201 -0
- package/dist/commands/help.js +8 -0
- package/dist/commands/init.js +8 -5
- package/dist/commands/once.js +24 -4
- package/dist/commands/run.js +105 -21
- package/dist/config/cli-providers.json +34 -6
- package/dist/index.js +2 -0
- package/dist/templates/prompts.d.ts +5 -0
- package/dist/templates/prompts.js +3 -3
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +17 -15
- package/dist/utils/prd-validator.d.ts +80 -0
- package/dist/utils/prd-validator.js +417 -0
- package/package.json +1 -1
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.
|
|
26
|
+
# 2. Edit .ralph/prd.json with your requirements, or use:
|
|
27
27
|
ralph add
|
|
28
28
|
|
|
29
|
-
# 3. Run
|
|
30
|
-
ralph
|
|
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
|
|
60
|
-
├── prompt.md
|
|
61
|
-
├── prd.json
|
|
62
|
-
|
|
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) |
|
|
84
|
-
| [Codex CLI](https://github.com/openai/codex) |
|
|
85
|
-
| [AMP](https://ampcode.com/) |
|
|
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
|
|
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
|
-
#
|
|
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>;
|
package/dist/commands/docker.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/commands/help.js
CHANGED
|
@@ -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
|
package/dist/commands/init.js
CHANGED
|
@@ -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.
|
|
174
|
-
console.log(" 2.
|
|
175
|
-
console.log("
|
|
176
|
-
console.log("
|
|
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
|
}
|
package/dist/commands/once.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|