Skip to content
This repository was archived by the owner on Mar 21, 2026. It is now read-only.

Commit 364de6f

Browse files
Merge pull request #1 from PromptExecution/ralph/pytest-harness-pr
ralph/pytest harness pr
2 parents 800c221 + 2cf03dd commit 364de6f

12 files changed

Lines changed: 744 additions & 115 deletions

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,17 @@ progress.txt
1111

1212
#Claude
1313
.claude/
14+
15+
# Python caches
16+
__pycache__/
17+
*.pyc
18+
.pytest_cache/
19+
.mypy_cache/
20+
.ruff_cache/
21+
22+
# Editor/backup files
23+
*~
24+
*.swp
25+
26+
# UV/temporary artifacts
27+
=3.0.0b1

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ cd flowchart && npm run dev
1313
# Build the flowchart
1414
cd flowchart && npm run build
1515

16-
# Run Ralph with Amp (default)
16+
# Run Ralph with Amp
1717
./ralph.sh [max_iterations]
1818

1919
# Run Ralph with Claude Code
20-
./ralph.sh --tool claude [max_iterations]
20+
./ralph.sh --agent claude [max_iterations]
2121
```
2222

2323
## Key Files
2424

25-
- `ralph.sh` - The bash loop that spawns fresh AI instances (supports `--tool amp` or `--tool claude`)
25+
- `ralph.sh` - The bash loop that spawns fresh AI instances (supports `--agent amp`, `--agent claude`, or `--agent codex`)
2626
- `prompt.md` - Instructions given to each AMP instance
2727
- `CLAUDE.md` - Instructions given to each Claude Code instance
2828
- `prd.json.example` - Example PRD format
@@ -45,3 +45,4 @@ npm run dev
4545
- Memory persists via git history, `progress.txt`, and `prd.json`
4646
- Stories should be small enough to complete in one context window
4747
- Always update AGENTS.md with discovered patterns for future iterations
48+
- `mypy` and `ruff` are not yet available via `uv run` until Python tooling is configured (see US-007)

README-MCP.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Ralph MCP Integration 🚀
2+
3+
Ralph now supports **FastMCP 3.0** with both server and client capabilities!
4+
5+
## Features
6+
7+
### MCP Server Mode
8+
Ralph exposes its autonomous agent capabilities as an MCP server with:
9+
10+
**Tools:**
11+
- `run_ralph_iteration(agent, max_iterations, prd_path)` - Run Ralph for N iterations
12+
- `get_ralph_status()` - Get current execution status from progress.txt
13+
- `get_prd_status(prd_path)` - Get PRD completion metrics
14+
15+
**Resources:**
16+
- `ralph://prd` - Current PRD JSON content
17+
- `ralph://progress` - Current progress.txt log
18+
19+
### Usage
20+
21+
**As CLI (default):**
22+
```bash
23+
./ralph.sh --agent codex 3
24+
# or
25+
uv run --script ralphython.py --agent codex 3
26+
```
27+
28+
**As MCP Server (HTTP):**
29+
```bash
30+
uv run --script ralphython.py --mcp --transport http --port 8000
31+
```
32+
33+
**As MCP Server (stdio):**
34+
```bash
35+
uv run --script ralphython.py --mcp --transport stdio
36+
```
37+
38+
### Client Example
39+
```python
40+
import asyncio
41+
from fastmcp import Client
42+
43+
async def check_ralph():
44+
async with Client("http://localhost:8000/mcp") as client:
45+
# Get PRD status
46+
status = await client.call_tool("get_prd_status", {})
47+
print(f"Completed: {status['completed_stories']}/{status['total_stories']}")
48+
49+
# Run Ralph for 1 iteration
50+
result = await client.call_tool("run_ralph_iteration", {
51+
"agent": "codex",
52+
"max_iterations": 1
53+
})
54+
print(f"Exit code: {result['exit_code']}")
55+
56+
asyncio.run(check_ralph())
57+
```
58+
59+
### MCP Client Config
60+
Add to your MCP client settings (e.g., Claude Desktop):
61+
62+
```json
63+
{
64+
"mcpServers": {
65+
"ralph": {
66+
"command": "uv",
67+
"args": ["run", "--script", "/path/to/ralphython.py", "--mcp"]
68+
}
69+
}
70+
}
71+
```
72+
73+
## Implementation Details
74+
75+
- FastMCP 3.0.0b1 (beta)
76+
- PEP 723 inline script dependencies
77+
- Supports both CLI and MCP modes via `--mcp` flag
78+
- Model: gpt-5.2-codex (configurable via `CODEX_MODEL` env var)
79+

README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Based on [Geoffrey Huntley's Ralph pattern](https://ghuntley.com/ralph/).
1111
## Prerequisites
1212

1313
- One of the following AI coding tools installed and authenticated:
14-
- [Amp CLI](https://ampcode.com) (default)
14+
- [Amp CLI](https://ampcode.com)
1515
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`npm install -g @anthropic-ai/claude-code`)
1616
- `jq` installed (`brew install jq` on macOS)
1717
- A git repository for your project
@@ -110,14 +110,14 @@ This creates `prd.json` with user stories structured for autonomous execution.
110110
### 3. Run Ralph
111111

112112
```bash
113-
# Using Amp (default)
113+
# Using Amp
114114
./scripts/ralph/ralph.sh [max_iterations]
115115

116116
# Using Claude Code
117-
./scripts/ralph/ralph.sh --tool claude [max_iterations]
117+
./scripts/ralph/ralph.sh --agent claude [max_iterations]
118118
```
119119

120-
Default is 10 iterations. Use `--tool amp` or `--tool claude` to select your AI coding tool.
120+
Default is 10 iterations. Use `--agent amp`, `--agent claude`, or `--agent codex` to select your AI coding tool.
121121

122122
Ralph will:
123123
1. Create a feature branch (from PRD `branchName`)
@@ -129,11 +129,29 @@ Ralph will:
129129
7. Append learnings to `progress.txt`
130130
8. Repeat until all stories pass or max iterations reached
131131

132+
## Testing
133+
134+
Install dev dependencies:
135+
136+
```bash
137+
uv pip install -r requirements-dev.txt
138+
```
139+
140+
Run tests:
141+
142+
```bash
143+
uv run pytest
144+
```
145+
146+
The pytest harness exercises the `ralphython` CLI end-to-end (argument parsing,
147+
deprecated `--tool` handling, and PRD ingestion) without invoking Amp, Claude,
148+
or Codex so you can validate behavior locally before handing off to agents.
149+
132150
## Key Files
133151

134152
| File | Purpose |
135153
|------|---------|
136-
| `ralph.sh` | The bash loop that spawns fresh AI instances (supports `--tool amp` or `--tool claude`) |
154+
| `ralph.sh` | The bash loop that spawns fresh AI instances (supports `--agent amp`, `--agent claude`, or `--agent codex`) |
137155
| `prompt.md` | Prompt template for Amp |
138156
| `CLAUDE.md` | Prompt template for Claude Code |
139157
| `prd.json` | User stories with `passes` status (the task list) |

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
testpaths = tests

ralph.sh

Lines changed: 13 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,19 @@
11
#!/bin/bash
22
# Ralph Wiggum - Long-running AI agent loop
3-
# Usage: ./ralph.sh [--tool amp|claude] [max_iterations]
3+
# Usage: ./ralph.sh --agent amp|claude|codex [max_iterations]
44

55
set -e
66

7-
# Parse arguments
8-
TOOL="amp" # Default to amp for backwards compatibility
9-
MAX_ITERATIONS=10
10-
11-
while [[ $# -gt 0 ]]; do
12-
case $1 in
13-
--tool)
14-
TOOL="$2"
15-
shift 2
16-
;;
17-
--tool=*)
18-
TOOL="${1#*=}"
19-
shift
20-
;;
21-
*)
22-
# Assume it's max_iterations if it's a number
23-
if [[ "$1" =~ ^[0-9]+$ ]]; then
24-
MAX_ITERATIONS="$1"
25-
fi
26-
shift
27-
;;
28-
esac
29-
done
30-
31-
# Validate tool choice
32-
if [[ "$TOOL" != "amp" && "$TOOL" != "claude" ]]; then
33-
echo "Error: Invalid tool '$TOOL'. Must be 'amp' or 'claude'."
34-
exit 1
35-
fi
36-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
37-
PRD_FILE="$SCRIPT_DIR/prd.json"
38-
PROGRESS_FILE="$SCRIPT_DIR/progress.txt"
39-
ARCHIVE_DIR="$SCRIPT_DIR/archive"
40-
LAST_BRANCH_FILE="$SCRIPT_DIR/.last-branch"
41-
42-
# Archive previous run if branch changed
43-
if [ -f "$PRD_FILE" ] && [ -f "$LAST_BRANCH_FILE" ]; then
44-
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
45-
LAST_BRANCH=$(cat "$LAST_BRANCH_FILE" 2>/dev/null || echo "")
46-
47-
if [ -n "$CURRENT_BRANCH" ] && [ -n "$LAST_BRANCH" ] && [ "$CURRENT_BRANCH" != "$LAST_BRANCH" ]; then
48-
# Archive the previous run
49-
DATE=$(date +%Y-%m-%d)
50-
# Strip "ralph/" prefix from branch name for folder
51-
FOLDER_NAME=$(echo "$LAST_BRANCH" | sed 's|^ralph/||')
52-
ARCHIVE_FOLDER="$ARCHIVE_DIR/$DATE-$FOLDER_NAME"
53-
54-
echo "Archiving previous run: $LAST_BRANCH"
55-
mkdir -p "$ARCHIVE_FOLDER"
56-
[ -f "$PRD_FILE" ] && cp "$PRD_FILE" "$ARCHIVE_FOLDER/"
57-
[ -f "$PROGRESS_FILE" ] && cp "$PROGRESS_FILE" "$ARCHIVE_FOLDER/"
58-
echo " Archived to: $ARCHIVE_FOLDER"
59-
60-
# Reset progress file for new run
61-
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
62-
echo "Started: $(date)" >> "$PROGRESS_FILE"
63-
echo "---" >> "$PROGRESS_FILE"
64-
fi
65-
fi
66-
67-
# Track current branch
68-
if [ -f "$PRD_FILE" ]; then
69-
CURRENT_BRANCH=$(jq -r '.branchName // empty' "$PRD_FILE" 2>/dev/null || echo "")
70-
if [ -n "$CURRENT_BRANCH" ]; then
71-
echo "$CURRENT_BRANCH" > "$LAST_BRANCH_FILE"
72-
fi
73-
fi
74-
75-
# Initialize progress file if it doesn't exist
76-
if [ ! -f "$PROGRESS_FILE" ]; then
77-
echo "# Ralph Progress Log" > "$PROGRESS_FILE"
78-
echo "Started: $(date)" >> "$PROGRESS_FILE"
79-
echo "---" >> "$PROGRESS_FILE"
80-
fi
81-
82-
echo "Starting Ralph - Tool: $TOOL - Max iterations: $MAX_ITERATIONS"
83-
84-
for i in $(seq 1 $MAX_ITERATIONS); do
85-
echo ""
86-
echo "==============================================================="
87-
echo " Ralph Iteration $i of $MAX_ITERATIONS ($TOOL)"
88-
echo "==============================================================="
89-
90-
# Run the selected tool with the ralph prompt
91-
if [[ "$TOOL" == "amp" ]]; then
92-
OUTPUT=$(cat "$SCRIPT_DIR/prompt.md" | amp --dangerously-allow-all 2>&1 | tee /dev/stderr) || true
93-
else
94-
# Claude Code: use --dangerously-skip-permissions for autonomous operation, --print for output
95-
OUTPUT=$(claude --dangerously-skip-permissions --print < "$SCRIPT_DIR/CLAUDE.md" 2>&1 | tee /dev/stderr) || true
96-
fi
97-
98-
# Check for completion signal
99-
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
100-
echo ""
101-
echo "Ralph completed all tasks!"
102-
echo "Completed at iteration $i of $MAX_ITERATIONS"
103-
exit 0
104-
fi
105-
106-
echo "Iteration $i complete. Continuing..."
107-
sleep 2
7+
SOURCE="${BASH_SOURCE[0]}"
8+
while [ -h "$SOURCE" ]; do
9+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
10+
TARGET="$(readlink "$SOURCE")"
11+
if [[ "$TARGET" = /* ]]; then
12+
SOURCE="$TARGET"
13+
else
14+
SOURCE="$DIR/$TARGET"
15+
fi
10816
done
109-
110-
echo ""
111-
echo "Ralph reached max iterations ($MAX_ITERATIONS) without completing all tasks."
112-
echo "Check $PROGRESS_FILE for status."
113-
exit 1
17+
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
18+
cd "$SCRIPT_DIR"
19+
exec uv run --script ralphython.py "$@"

ralph_cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env -S uv run --script
2+
# This file implements the CLI entry point for the Ralph Wiggum agent.
3+
# The shebang above uses `uv run --script` so the script can be executed
4+
# directly without a pre-created virtual environment in environments
5+
# where `uv` is installed. When run as a regular Python module
6+
# (e.g. `python ralph_cli.py`), this shebang is ignored.

0 commit comments

Comments
 (0)