ralph-tool 1.0.0
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/Dockerfile +40 -0
- package/README.md +148 -0
- package/bin/ralph.js +357 -0
- package/docker-compose.yaml +40 -0
- package/helpers/constants.sh +22 -0
- package/helpers/cost_helpers.sh +73 -0
- package/helpers/display_helpers.sh +312 -0
- package/helpers/extract_subagent_map.jq +32 -0
- package/helpers/iteration_helpers.sh +69 -0
- package/helpers/output_formatting.jq +294 -0
- package/package.json +48 -0
- package/scripts/check_feature_completion.sh +116 -0
- package/scripts/cleanup.sh +179 -0
- package/scripts/clear.sh +38 -0
- package/scripts/git_feature_complete.sh +133 -0
- package/scripts/loop.sh +206 -0
- package/scripts/plan-loop.sh +233 -0
- package/scripts/postinstall.js +155 -0
- package/scripts/review.sh +90 -0
- package/scripts/sandbox.sh +15 -0
- package/scripts/start.sh +25 -0
- package/scripts/status.sh +108 -0
- package/templates/AGENT.md +27 -0
- package/templates/feature-spec-template.md +115 -0
- package/templates/plan-cleanup-prompt.md +61 -0
- package/templates/plan-prompt.md +114 -0
- package/templates/pr-review-prompt.md +14 -0
- package/templates/prd.json +22 -0
- package/templates/prompt.md +124 -0
- package/templates/roles/ralph-helper.md +73 -0
- package/templates/roles/ralph-plan-feature.md +74 -0
- package/templates/roles/ralph-plan-vision.md +46 -0
- package/templates/testing-harness.md +42 -0
- package/templates/vision.md +43 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
FROM node:20-bookworm
|
|
2
|
+
|
|
3
|
+
# System deps
|
|
4
|
+
USER root
|
|
5
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
6
|
+
git bash ca-certificates curl ripgrep openssh-client \
|
|
7
|
+
jq bsdutils bc gosu \
|
|
8
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
# Install GitHub CLI
|
|
11
|
+
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
12
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
|
13
|
+
&& apt-get update && apt-get install -y gh \
|
|
14
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
15
|
+
|
|
16
|
+
# Install Claude Code globally
|
|
17
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
18
|
+
|
|
19
|
+
# Install ralph-tool globally from source
|
|
20
|
+
COPY . /tmp/ralph-tool
|
|
21
|
+
RUN npm install -g /tmp/ralph-tool && rm -rf /tmp/ralph-tool \
|
|
22
|
+
&& ln -sf $(which ralph) /usr/local/bin/ralph
|
|
23
|
+
|
|
24
|
+
# Prepare writable config dirs for the non-root user
|
|
25
|
+
RUN mkdir -p /home/node/.config /home/node/.cache /home/node/.ssh \
|
|
26
|
+
&& chown -R node:node /home/node/.config /home/node/.cache /home/node/.ssh
|
|
27
|
+
|
|
28
|
+
# Add GitHub's SSH host key to known_hosts
|
|
29
|
+
RUN ssh-keyscan github.com >> /home/node/.ssh/known_hosts \
|
|
30
|
+
&& chown node:node /home/node/.ssh/known_hosts
|
|
31
|
+
|
|
32
|
+
# Add entrypoint script
|
|
33
|
+
COPY entrypoint.sh /entrypoint.sh
|
|
34
|
+
RUN chmod +x /entrypoint.sh
|
|
35
|
+
|
|
36
|
+
WORKDIR /workspace
|
|
37
|
+
ENV TERM=xterm-256color
|
|
38
|
+
|
|
39
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
40
|
+
CMD ["bash"]
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Ralph
|
|
2
|
+
|
|
3
|
+
AI-powered task automation for software development. Ralph orchestrates [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to implement features task-by-task — one task per iteration, fresh context every time, automatic PRs when done.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g ralph-tool
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> Ralph auto-installs required dependencies (`jq`, `bc`, `claude`) during setup. Docker and `gh` are optional.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd my-project
|
|
17
|
+
ralph init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This creates a `.ralph/` directory with all config, templates, and task files. Then choose your workflow:
|
|
21
|
+
|
|
22
|
+
### Workflow A: Plan from Vision
|
|
23
|
+
|
|
24
|
+
Best for greenfield projects or large feature sets. Define what you want, let Ralph plan the tasks.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Write your project vision (or use the Claude Code role to help)
|
|
28
|
+
# Edit .ralph/config/vision.md
|
|
29
|
+
|
|
30
|
+
# 2. Ralph plans features + creates task specs from your vision
|
|
31
|
+
ralph plan --max 20
|
|
32
|
+
|
|
33
|
+
# 3. Ralph implements everything
|
|
34
|
+
ralph start --max 50
|
|
35
|
+
|
|
36
|
+
# 4. Review & merge PRs as they come
|
|
37
|
+
ralph review
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Workflow B: Define Tasks Directly
|
|
41
|
+
|
|
42
|
+
Best for adding specific features to an existing project.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Edit .ralph/tasks/prd.json with your features and tasks
|
|
46
|
+
# Optionally create spec files in .ralph/tasks/1_new_tasks/
|
|
47
|
+
|
|
48
|
+
# 2. Configure build/test commands
|
|
49
|
+
# Edit .ralph/tasks/testing-harness.md
|
|
50
|
+
|
|
51
|
+
# 3. Run it
|
|
52
|
+
ralph start --max 10
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
| Command | Description |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `ralph init` | Initialize Ralph in current directory |
|
|
60
|
+
| `ralph start [options]` | Run loop in Docker sandbox (recommended) |
|
|
61
|
+
| `ralph loop [options]` | Run loop directly on your machine |
|
|
62
|
+
| `ralph plan [options]` | Plan features from `vision.md` into tasks |
|
|
63
|
+
| `ralph sandbox` | Open interactive Docker shell |
|
|
64
|
+
| `ralph status` | Show current feature and task progress |
|
|
65
|
+
| `ralph review` | Review latest PR and merge to main |
|
|
66
|
+
| `ralph cleanup [feature]` | Archive completed features |
|
|
67
|
+
| `ralph clear` | Clear iteration logs |
|
|
68
|
+
|
|
69
|
+
### Options
|
|
70
|
+
|
|
71
|
+
| Flag | Default | Description |
|
|
72
|
+
|------|---------|-------------|
|
|
73
|
+
| `--max N` | 6 (start/loop), 10 (plan) | Maximum iterations |
|
|
74
|
+
| `--model MODEL` | sonnet | Model to use: `sonnet`, `opus`, `haiku` |
|
|
75
|
+
| `--local` | off | Commit locally only, skip branch/push/PR |
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
**Loop mode** (`start` / `loop`):
|
|
80
|
+
|
|
81
|
+
1. Ralph picks the next incomplete task from `prd.json`
|
|
82
|
+
2. Reads the task spec (if one exists) and delegates code exploration to subagents
|
|
83
|
+
3. Implements the code changes, then runs your test harness
|
|
84
|
+
4. On pass: marks task complete, updates progress. On fail: fixes and retests
|
|
85
|
+
5. When all tasks in a feature are done → creates a git branch and PR automatically
|
|
86
|
+
6. Repeat. Each iteration gets fresh context — state lives in files, not memory
|
|
87
|
+
|
|
88
|
+
**Plan mode** (`plan`):
|
|
89
|
+
|
|
90
|
+
1. Reads your `vision.md` and existing `prd.json`
|
|
91
|
+
2. Identifies one unplanned area, explores the codebase
|
|
92
|
+
3. Creates a detailed spec file and adds the feature + tasks to `prd.json`
|
|
93
|
+
4. Every 5 iterations: runs a cleanup pass to consolidate and reorder
|
|
94
|
+
5. Repeat until the vision is fully covered
|
|
95
|
+
|
|
96
|
+
## Claude Code Roles
|
|
97
|
+
|
|
98
|
+
Ralph ships with roles you can use directly in Claude Code (`@` mention the file):
|
|
99
|
+
|
|
100
|
+
| Role | File | Purpose |
|
|
101
|
+
|------|------|---------|
|
|
102
|
+
| Vision Creator | `.ralph/roles/ralph-plan-vision.md` | Helps you write `vision.md` interactively |
|
|
103
|
+
| Feature Planner | `.ralph/roles/ralph-plan-feature.md` | Plans a single feature with you |
|
|
104
|
+
| Helper | `.ralph/roles/ralph-helper.md` | Explains Ralph concepts and usage |
|
|
105
|
+
|
|
106
|
+
## Project Structure
|
|
107
|
+
|
|
108
|
+
After `ralph init`:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
.ralph/
|
|
112
|
+
├── config/
|
|
113
|
+
│ ├── prompt.md # Loop orchestrator prompt
|
|
114
|
+
│ ├── plan-prompt.md # Planning orchestrator prompt
|
|
115
|
+
│ ├── plan-cleanup-prompt.md # Planning cleanup prompt
|
|
116
|
+
│ ├── AGENT.md # Persistent learnings (accumulates over time)
|
|
117
|
+
│ ├── vision.md # Your project vision (for plan mode)
|
|
118
|
+
│ └── pr-review-prompt.md # PR review template
|
|
119
|
+
├── tasks/
|
|
120
|
+
│ ├── prd.json # Features and tasks definition
|
|
121
|
+
│ ├── progress.txt # Current iteration progress
|
|
122
|
+
│ ├── testing-harness.md # Build/test commands
|
|
123
|
+
│ ├── feature-spec-template.md
|
|
124
|
+
│ ├── 1_new_tasks/ # Active task spec files
|
|
125
|
+
│ └── 2_done_tasks/ # Archived completed features
|
|
126
|
+
├── roles/ # Claude Code role files
|
|
127
|
+
└── logs/ # Iteration logs and cost tracking
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Key Concepts
|
|
131
|
+
|
|
132
|
+
- **One task per iteration** — Ralph completes exactly one task, then exits. The loop script starts a new iteration with fresh context.
|
|
133
|
+
- **File-based state** — All state lives in `prd.json`, `progress.txt`, and `AGENT.md`. No memory between iterations.
|
|
134
|
+
- **AGENT.md** — Persistent knowledge base that accumulates learnings across all iterations and features. Never cleared automatically.
|
|
135
|
+
- **Spec files** — Detailed implementation specs in `1_new_tasks/`. Created by `ralph plan` or manually.
|
|
136
|
+
- **Testing harness** — Your build/test commands in `testing-harness.md`. Ralph runs these after every implementation.
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- **Node.js** 18+
|
|
141
|
+
- **Claude Code CLI** — `npm install -g @anthropic-ai/claude-code`
|
|
142
|
+
- **Docker** — for `ralph start` / `ralph sandbox` (optional if using `ralph loop`)
|
|
143
|
+
- **GitHub CLI** (`gh`) — for PR creation and `ralph review` (optional with `--local`)
|
|
144
|
+
- **jq**, **bc** — auto-installed during `npm install`
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/bin/ralph.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const { spawn, spawnSync } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
// Colors matching culinary cloud style
|
|
10
|
+
const cmd = chalk.bold.magenta;
|
|
11
|
+
const title = chalk.bold.cyan;
|
|
12
|
+
const dim = chalk.dim;
|
|
13
|
+
|
|
14
|
+
// Resolve paths
|
|
15
|
+
const RALPH_HOME = path.resolve(__dirname, '..');
|
|
16
|
+
const PROJECT_DIR = process.cwd();
|
|
17
|
+
const PROJECT_RALPH_DIR = path.join(PROJECT_DIR, '.ralph');
|
|
18
|
+
|
|
19
|
+
// Set environment variables for child scripts
|
|
20
|
+
process.env.RALPH_HOME = RALPH_HOME;
|
|
21
|
+
process.env.PROJECT_DIR = PROJECT_DIR;
|
|
22
|
+
process.env.PROJECT_RALPH_DIR = PROJECT_RALPH_DIR;
|
|
23
|
+
|
|
24
|
+
// Helper: Check if .ralph exists
|
|
25
|
+
function requireInit() {
|
|
26
|
+
if (!fs.existsSync(PROJECT_RALPH_DIR)) {
|
|
27
|
+
console.error(chalk.red('Error: .ralph/ not found in current directory'));
|
|
28
|
+
console.log('');
|
|
29
|
+
console.log("Run 'ralph init' first to initialize Ralph in this project.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Helper: Run a bash script (async — for non-interactive commands)
|
|
35
|
+
function runScript(scriptName, args = []) {
|
|
36
|
+
const scriptPath = path.join(RALPH_HOME, 'scripts', scriptName);
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(scriptPath)) {
|
|
39
|
+
console.error(chalk.red(`Error: Script not found: ${scriptPath}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const child = spawn('bash', [scriptPath, ...args], {
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
env: { ...process.env, RALPH_HOME, PROJECT_DIR, PROJECT_RALPH_DIR }
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
child.on('close', (code) => {
|
|
49
|
+
process.exit(code || 0);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Helper: Run a bash script synchronously (for interactive/TTY commands like sandbox)
|
|
54
|
+
function runScriptSync(scriptName, args = []) {
|
|
55
|
+
const scriptPath = path.join(RALPH_HOME, 'scripts', scriptName);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(scriptPath)) {
|
|
58
|
+
console.error(chalk.red(`Error: Script not found: ${scriptPath}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = spawnSync('bash', [scriptPath, ...args], {
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
env: { ...process.env, RALPH_HOME, PROJECT_DIR, PROJECT_RALPH_DIR }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
process.exit(result.status || 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper: Copy directory recursively
|
|
71
|
+
function copyDir(src, dest) {
|
|
72
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
73
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
74
|
+
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const srcPath = path.join(src, entry.name);
|
|
77
|
+
const destPath = path.join(dest, entry.name);
|
|
78
|
+
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
copyDir(srcPath, destPath);
|
|
81
|
+
} else {
|
|
82
|
+
fs.copyFileSync(srcPath, destPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Init command
|
|
88
|
+
function doInit() {
|
|
89
|
+
if (fs.existsSync(PROJECT_RALPH_DIR)) {
|
|
90
|
+
console.log(chalk.yellow('Warning: .ralph/ already exists in this directory'));
|
|
91
|
+
const readline = require('readline');
|
|
92
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
93
|
+
|
|
94
|
+
rl.question('Overwrite? (y/N) ', (answer) => {
|
|
95
|
+
rl.close();
|
|
96
|
+
if (answer.toLowerCase() !== 'y') {
|
|
97
|
+
console.log('Aborted.');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
performInit();
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
performInit();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function performInit() {
|
|
108
|
+
console.log(chalk.green(`Initializing Ralph in ${PROJECT_DIR}`));
|
|
109
|
+
console.log('');
|
|
110
|
+
|
|
111
|
+
// Create directory structure
|
|
112
|
+
console.log('[1/3] Creating directory structure...');
|
|
113
|
+
fs.mkdirSync(path.join(PROJECT_RALPH_DIR, 'config'), { recursive: true });
|
|
114
|
+
fs.mkdirSync(path.join(PROJECT_RALPH_DIR, 'tasks', '1_new_tasks'), { recursive: true });
|
|
115
|
+
fs.mkdirSync(path.join(PROJECT_RALPH_DIR, 'tasks', '2_done_tasks'), { recursive: true });
|
|
116
|
+
fs.mkdirSync(path.join(PROJECT_RALPH_DIR, 'logs', 'progress'), { recursive: true });
|
|
117
|
+
fs.mkdirSync(path.join(PROJECT_RALPH_DIR, 'roles'), { recursive: true });
|
|
118
|
+
console.log(chalk.green(' ✓') + ' Directories created');
|
|
119
|
+
|
|
120
|
+
// Copy templates
|
|
121
|
+
console.log('[2/3] Copying templates...');
|
|
122
|
+
const templatesDir = path.join(RALPH_HOME, 'templates');
|
|
123
|
+
|
|
124
|
+
// Config files
|
|
125
|
+
fs.copyFileSync(
|
|
126
|
+
path.join(templatesDir, 'prompt.md'),
|
|
127
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'prompt.md')
|
|
128
|
+
);
|
|
129
|
+
fs.copyFileSync(
|
|
130
|
+
path.join(templatesDir, 'AGENT.md'),
|
|
131
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'AGENT.md')
|
|
132
|
+
);
|
|
133
|
+
fs.copyFileSync(
|
|
134
|
+
path.join(templatesDir, 'pr-review-prompt.md'),
|
|
135
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'pr-review-prompt.md')
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Task files
|
|
139
|
+
fs.copyFileSync(
|
|
140
|
+
path.join(templatesDir, 'testing-harness.md'),
|
|
141
|
+
path.join(PROJECT_RALPH_DIR, 'tasks', 'testing-harness.md')
|
|
142
|
+
);
|
|
143
|
+
fs.copyFileSync(
|
|
144
|
+
path.join(templatesDir, 'feature-spec-template.md'),
|
|
145
|
+
path.join(PROJECT_RALPH_DIR, 'tasks', 'feature-spec-template.md')
|
|
146
|
+
);
|
|
147
|
+
fs.copyFileSync(
|
|
148
|
+
path.join(templatesDir, 'prd.json'),
|
|
149
|
+
path.join(PROJECT_RALPH_DIR, 'tasks', 'prd.json')
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Plan mode files
|
|
153
|
+
fs.copyFileSync(
|
|
154
|
+
path.join(templatesDir, 'vision.md'),
|
|
155
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'vision.md')
|
|
156
|
+
);
|
|
157
|
+
fs.copyFileSync(
|
|
158
|
+
path.join(templatesDir, 'plan-prompt.md'),
|
|
159
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'plan-prompt.md')
|
|
160
|
+
);
|
|
161
|
+
fs.copyFileSync(
|
|
162
|
+
path.join(templatesDir, 'plan-cleanup-prompt.md'),
|
|
163
|
+
path.join(PROJECT_RALPH_DIR, 'config', 'plan-cleanup-prompt.md')
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Role files
|
|
167
|
+
copyDir(
|
|
168
|
+
path.join(templatesDir, 'roles'),
|
|
169
|
+
path.join(PROJECT_RALPH_DIR, 'roles')
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Create empty progress file
|
|
173
|
+
fs.writeFileSync(path.join(PROJECT_RALPH_DIR, 'tasks', 'progress.txt'), '');
|
|
174
|
+
|
|
175
|
+
console.log(chalk.green(' ✓') + ' Templates copied');
|
|
176
|
+
|
|
177
|
+
// Done
|
|
178
|
+
console.log('[3/3] Done!');
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(chalk.green('Ralph initialized successfully!'));
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log('Next steps:');
|
|
183
|
+
console.log(' 1. Plan a feature: @.ralph/roles/ralph-plan-feature.md in Claude Code');
|
|
184
|
+
console.log(' 2. Edit .ralph/tasks/testing-harness.md for your build/test commands');
|
|
185
|
+
console.log(' 3. Run \'ralph start\' to begin');
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Custom help display
|
|
190
|
+
function showHelp() {
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(title('Ralph - AI Task Automation CLI'));
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log(`Usage: ralph ${dim('{command} [options]')}`);
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log('Commands:');
|
|
197
|
+
console.log(` ${cmd('init')}`);
|
|
198
|
+
console.log(' Initialize Ralph in current directory (creates .ralph/)');
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(` ${cmd('start')} ${dim('[--max N] [--model MODEL] [--local]')}`);
|
|
201
|
+
console.log(' Open Docker sandbox and start loop inside container');
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(` ${cmd('sandbox')}`);
|
|
204
|
+
console.log(' Open interactive Docker shell');
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(` ${cmd('loop')} ${dim('[--max N] [--model MODEL] [--local]')}`);
|
|
207
|
+
console.log(' Start Ralph loop directly (default: 6 iterations, sonnet-4-5)');
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(` ${cmd('plan')} ${dim('[--max N] [--model MODEL]')}`);
|
|
210
|
+
console.log(' Plan features from vision.md (default: 10 iterations, cleanup every 5)');
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(` ${cmd('status')}`);
|
|
213
|
+
console.log(' Check current PRD task status');
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(` ${cmd('clear')}`);
|
|
216
|
+
console.log(' Clear all log files (keeps progress)');
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(` ${cmd('review')}`);
|
|
219
|
+
console.log(' Review latest PR, merge to main');
|
|
220
|
+
console.log('');
|
|
221
|
+
console.log(` ${cmd('cleanup')} ${dim('[feature]')}`);
|
|
222
|
+
console.log(' Archive completed features');
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log(` ${cmd('help')}`);
|
|
225
|
+
console.log(' Show this help message');
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log('Options:');
|
|
228
|
+
console.log(` ${cmd('--max N')} Maximum iterations (default: 6)`);
|
|
229
|
+
console.log(` ${cmd('--model MODEL')} AI model: sonnet, opus, haiku (default: sonnet)`);
|
|
230
|
+
console.log(` ${cmd('--local')} Commit locally, skip branch/push/PR creation`);
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log('Examples:');
|
|
233
|
+
console.log(` ralph ${cmd('init')} # Set up Ralph in your project`);
|
|
234
|
+
console.log(` ralph ${cmd('start')} --max 5 --model haiku # Run in Docker with haiku`);
|
|
235
|
+
console.log(` ralph ${cmd('loop')} # Run loop directly`);
|
|
236
|
+
console.log(` ralph ${cmd('loop')} --max 10 --model opus # 10 iterations with opus`);
|
|
237
|
+
console.log(` ralph ${cmd('plan')} # Plan features from vision`);
|
|
238
|
+
console.log(` ralph ${cmd('plan')} --max 20 --model opus # Deep planning with opus`);
|
|
239
|
+
console.log(` ralph ${cmd('status')} # Check task progress`);
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Setup CLI — disable commander's built-in help entirely
|
|
244
|
+
program
|
|
245
|
+
.name('ralph')
|
|
246
|
+
.version('1.0.0')
|
|
247
|
+
.helpOption(false)
|
|
248
|
+
.addHelpCommand(false)
|
|
249
|
+
.configureOutput({
|
|
250
|
+
outputError: () => {}, // suppress commander error output
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
program
|
|
254
|
+
.command('init')
|
|
255
|
+
.description('Initialize Ralph in current directory')
|
|
256
|
+
.action(doInit);
|
|
257
|
+
|
|
258
|
+
program
|
|
259
|
+
.command('start')
|
|
260
|
+
.option('--max <n>', 'Maximum iterations', '6')
|
|
261
|
+
.option('--model <model>', 'AI model: sonnet, opus, haiku')
|
|
262
|
+
.option('--local', 'Local mode: commit but skip branch/push/PR creation')
|
|
263
|
+
.action((options) => {
|
|
264
|
+
requireInit();
|
|
265
|
+
if (options.local) process.env.RALPH_LOCAL = '1';
|
|
266
|
+
console.log(chalk.dim('Starting Docker container...'));
|
|
267
|
+
runScriptSync('start.sh', [options.max, options.model || '']);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
program
|
|
271
|
+
.command('sandbox')
|
|
272
|
+
.action(() => {
|
|
273
|
+
requireInit();
|
|
274
|
+
console.log(chalk.green('Opening Docker sandbox...'));
|
|
275
|
+
runScriptSync('sandbox.sh');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
program
|
|
279
|
+
.command('loop')
|
|
280
|
+
.option('--max <n>', 'Maximum iterations', '6')
|
|
281
|
+
.option('--model <model>', 'AI model: sonnet, opus, haiku')
|
|
282
|
+
.option('--local', 'Local mode: commit but skip branch/push/PR creation')
|
|
283
|
+
.action((options) => {
|
|
284
|
+
requireInit();
|
|
285
|
+
if (options.local) process.env.RALPH_LOCAL = '1';
|
|
286
|
+
console.log(chalk.dim('Starting Ralph loop...'));
|
|
287
|
+
const promptFile = path.join(PROJECT_RALPH_DIR, 'config', 'prompt.md');
|
|
288
|
+
runScript('loop.sh', [promptFile, options.max, options.model || '']);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
program
|
|
292
|
+
.command('plan')
|
|
293
|
+
.option('--max <n>', 'Maximum iterations', '10')
|
|
294
|
+
.option('--model <model>', 'AI model: sonnet, opus, haiku')
|
|
295
|
+
.action((options) => {
|
|
296
|
+
requireInit();
|
|
297
|
+
const visionFile = path.join(PROJECT_RALPH_DIR, 'config', 'vision.md');
|
|
298
|
+
if (!fs.existsSync(visionFile)) {
|
|
299
|
+
console.error(chalk.red('Error: Vision file not found at .ralph/config/vision.md'));
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log('Create your vision first:');
|
|
302
|
+
console.log(' Use @.ralph/roles/ralph-plan-vision.md in Claude Code');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
console.log(chalk.dim('Starting Ralph planning loop...'));
|
|
306
|
+
runScript('plan-loop.sh', [options.max, options.model || '']);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
program
|
|
310
|
+
.command('status')
|
|
311
|
+
.action(() => {
|
|
312
|
+
requireInit();
|
|
313
|
+
runScript('status.sh');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
program
|
|
317
|
+
.command('clear')
|
|
318
|
+
.action(() => {
|
|
319
|
+
requireInit();
|
|
320
|
+
runScript('clear.sh');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
program
|
|
324
|
+
.command('review')
|
|
325
|
+
.action(() => {
|
|
326
|
+
requireInit();
|
|
327
|
+
runScript('review.sh');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
program
|
|
331
|
+
.command('cleanup [feature]')
|
|
332
|
+
.action((feature) => {
|
|
333
|
+
requireInit();
|
|
334
|
+
runScript('cleanup.sh', feature ? [feature] : []);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
program
|
|
338
|
+
.command('help')
|
|
339
|
+
.action(() => showHelp());
|
|
340
|
+
|
|
341
|
+
// Intercept before commander parses: handle no args, help, and --help
|
|
342
|
+
const args = process.argv.slice(2);
|
|
343
|
+
const firstArg = args[0];
|
|
344
|
+
|
|
345
|
+
if (!firstArg || firstArg === 'help' || firstArg === '--help' || firstArg === '-h') {
|
|
346
|
+
showHelp();
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Unknown commands → show help
|
|
351
|
+
program.on('command:*', () => {
|
|
352
|
+
console.error(chalk.red(`Error: Unknown command '${args[0]}'`));
|
|
353
|
+
showHelp();
|
|
354
|
+
process.exit(1);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
program.parse();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
services:
|
|
2
|
+
ai:
|
|
3
|
+
build:
|
|
4
|
+
context: .
|
|
5
|
+
dockerfile: Dockerfile
|
|
6
|
+
working_dir: /workspace
|
|
7
|
+
environment:
|
|
8
|
+
- TERM=xterm-256color
|
|
9
|
+
- CLAUDE_CONFIG_DIR=/home/node/.config/claude
|
|
10
|
+
- SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock
|
|
11
|
+
- RALPH_HOME=/ralph-tool
|
|
12
|
+
- GIT_USER_NAME=${GIT_USER_NAME:-}
|
|
13
|
+
- GIT_USER_EMAIL=${GIT_USER_EMAIL:-}
|
|
14
|
+
volumes:
|
|
15
|
+
# Project directory mounted to /workspace (set by ralph CLI)
|
|
16
|
+
# This is handled dynamically by the start.sh script
|
|
17
|
+
|
|
18
|
+
# Ralph tool installation
|
|
19
|
+
- .:/ralph-tool:ro
|
|
20
|
+
|
|
21
|
+
# Persistent volumes for configs
|
|
22
|
+
- claude_config:/home/node/.config/claude
|
|
23
|
+
- claude_workspace:/workspace/.claude
|
|
24
|
+
- gh_config:/home/node/.config/gh
|
|
25
|
+
|
|
26
|
+
# SSH agent for git operations
|
|
27
|
+
- type: bind
|
|
28
|
+
source: /run/host-services/ssh-auth.sock
|
|
29
|
+
target: /run/host-services/ssh-auth.sock
|
|
30
|
+
|
|
31
|
+
# Git config (read-only)
|
|
32
|
+
- ~/.gitconfig:/home/node/.gitconfig:ro
|
|
33
|
+
|
|
34
|
+
stdin_open: true
|
|
35
|
+
tty: true
|
|
36
|
+
|
|
37
|
+
volumes:
|
|
38
|
+
claude_config:
|
|
39
|
+
claude_workspace:
|
|
40
|
+
gh_config:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Constants for Ralph Loop
|
|
3
|
+
|
|
4
|
+
# Configuration constants
|
|
5
|
+
MAX_CHARS="${MAX_CHARS:-4000}"
|
|
6
|
+
MAX_LINES_FOR_TOOL_RESULT=4
|
|
7
|
+
DEFAULT_MODEL="claude-sonnet-4.5"
|
|
8
|
+
|
|
9
|
+
# Fun names for Ralph
|
|
10
|
+
RALPH_NAMES=(
|
|
11
|
+
"YOLO RALPH COOKING"
|
|
12
|
+
"RALPH IN THE MATRIX"
|
|
13
|
+
"RALPH MODE: ACTIVATED"
|
|
14
|
+
"RALPH.EXE RUNNING"
|
|
15
|
+
"EMPLOYEE OF THE MONTH RALPH"
|
|
16
|
+
"RALPH IS SPEEEEEEEEEED"
|
|
17
|
+
"BUSINESS MONEY GETTING BURNED BY RAAAALLLPPPHHHHH"
|
|
18
|
+
"CRACKHEAD RALPH TO THE RESCUE"
|
|
19
|
+
"BUSINESSSSSSS ISS RAAAAALLLLPPPPHHHH"
|
|
20
|
+
"RALPH: PROCESSING..."
|
|
21
|
+
"RALPH COOKING SOME TOOOKKKEEENNNNN FOR BUSINESSSSSSS"
|
|
22
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cost calculation helpers for Ralph loop
|
|
3
|
+
|
|
4
|
+
# Model pricing lookup (bash 3.2 compatible — no associative arrays)
|
|
5
|
+
# Returns: input_base cache_write_5m cache_write_1h cache_hit output (per million tokens)
|
|
6
|
+
get_model_pricing() {
|
|
7
|
+
case "$1" in
|
|
8
|
+
claude-opus-4-5) echo "5.0 6.25 10.0 0.50 25.0" ;;
|
|
9
|
+
claude-opus-4-1) echo "15.0 18.75 30.0 1.50 75.0" ;;
|
|
10
|
+
claude-opus-4) echo "15.0 18.75 30.0 1.50 75.0" ;;
|
|
11
|
+
claude-sonnet-4-5) echo "3.0 3.75 6.0 0.30 15.0" ;;
|
|
12
|
+
claude-sonnet-3-5) echo "3.0 3.75 6.0 0.30 15.0" ;;
|
|
13
|
+
claude-haiku-3) echo "0.25 0.30 1.25 0.03 1.25" ;;
|
|
14
|
+
*) echo "3.0 3.75 6.0 0.30 15.0" ;; # default to sonnet pricing
|
|
15
|
+
esac
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Function to normalize model name for pricing lookup
|
|
19
|
+
normalize_model_name() {
|
|
20
|
+
local model="$1"
|
|
21
|
+
echo "$model" | tr '[:upper:]' '[:lower:]' | sed 's/\./-/g' | sed 's/-[0-9]\{8\}$//' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Function to calculate cost based on model and usage
|
|
25
|
+
calculate_cost() {
|
|
26
|
+
local model="$1"
|
|
27
|
+
local input_tokens="$2"
|
|
28
|
+
local output_tokens="$3"
|
|
29
|
+
local cache_read_tokens="$4"
|
|
30
|
+
local cache_write_tokens="$5"
|
|
31
|
+
|
|
32
|
+
local normalized_model
|
|
33
|
+
normalized_model=$(normalize_model_name "$model")
|
|
34
|
+
local pricing
|
|
35
|
+
pricing=$(get_model_pricing "$normalized_model")
|
|
36
|
+
|
|
37
|
+
local input_price cache_write_price cache_hit_price output_price
|
|
38
|
+
input_price=$(echo "$pricing" | awk '{print $1}')
|
|
39
|
+
cache_write_price=$(echo "$pricing" | awk '{print $2}')
|
|
40
|
+
cache_hit_price=$(echo "$pricing" | awk '{print $4}')
|
|
41
|
+
output_price=$(echo "$pricing" | awk '{print $5}')
|
|
42
|
+
|
|
43
|
+
local input_cost output_cost cache_read_cost cache_write_cost total_cost
|
|
44
|
+
input_cost=$(echo "scale=6; ($input_tokens * $input_price) / 1000000" | bc)
|
|
45
|
+
output_cost=$(echo "scale=6; ($output_tokens * $output_price) / 1000000" | bc)
|
|
46
|
+
cache_read_cost=$(echo "scale=6; ($cache_read_tokens * $cache_hit_price) / 1000000" | bc)
|
|
47
|
+
cache_write_cost=$(echo "scale=6; ($cache_write_tokens * $cache_write_price) / 1000000" | bc)
|
|
48
|
+
|
|
49
|
+
total_cost=$(echo "scale=6; $input_cost + $output_cost + $cache_read_cost + $cache_write_cost" | bc)
|
|
50
|
+
|
|
51
|
+
echo "$total_cost"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Function to format token count (add commas)
|
|
55
|
+
format_tokens() {
|
|
56
|
+
local tokens="$1"
|
|
57
|
+
if [[ -z "$tokens" ]] || [[ "$tokens" == "null" ]] || [[ "$tokens" == "0" ]]; then
|
|
58
|
+
echo "0"
|
|
59
|
+
else
|
|
60
|
+
echo "$tokens" | awk '{printf "%'"'"'d\n", $1}'
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Function to format cost
|
|
65
|
+
format_cost() {
|
|
66
|
+
local cost="$1"
|
|
67
|
+
if [[ -z "$cost" ]] || [[ "$cost" == "null" ]] || [[ "$cost" == "0" ]]; then
|
|
68
|
+
echo "\$0.00"
|
|
69
|
+
else
|
|
70
|
+
local formatted=$(printf "%.4f" "$cost" | sed 's/0*$//' | sed 's/\.$//')
|
|
71
|
+
printf "\$%s\n" "$formatted"
|
|
72
|
+
fi
|
|
73
|
+
}
|