Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/ralph.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Ralph Loop

on:
workflow_dispatch:
push:
paths:
- "AGENTS.md"
- "scripts/ralph/**"

permissions:
contents: write

jobs:
ralph:
# 198982749 is the Copilot app actor id fallback when RALPH_BOT_ID is unset.
if: ${{ github.event_name == 'workflow_dispatch' || format('{0}', github.actor_id) == vars.RALPH_BOT_ID || github.actor_id == 198982749 }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.RALPH_PAT }}

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq

- name: Install OpenCode
run: npm install -g @github/opencode

- name: Validate state files
run: |
jq empty scripts/ralph/prd.json
jq empty scripts/ralph/constraints.json
jq empty scripts/ralph/failure.json

- name: Ensure guard is executable
run: chmod +x scripts/ralph/guard.sh

- name: Run OpenCode iteration
run: opencode run --config .opencode/opencode.json

- name: Run guard
run: bash scripts/ralph/guard.sh scripts/ralph/constraints.json

- name: Commit and push changes
env:
GIT_AUTHOR_NAME: ralph-bot
GIT_AUTHOR_EMAIL: ralph@example.com
GIT_COMMITTER_NAME: ralph-bot
GIT_COMMITTER_EMAIL: ralph@example.com
run: |
if [[ -z "$(git status --porcelain)" ]]; then
echo "No changes to commit."
exit 0
fi
git add .
git commit -m "chore: ralph iteration"
git push
16 changes: 16 additions & 0 deletions .opencode/opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"workdir": ".",
"agents": [
{
"id": "ralph",
"agentFile": "AGENTS.md",
"state": {
"prd": "scripts/ralph/prd.json",
"progress": "scripts/ralph/progress.txt",
"constraints": "scripts/ralph/constraints.json",
"failure": "scripts/ralph/failure.json"
},
"maxIterations": 1
}
]
}
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Ralph Agents

- PAUSED: false
- MAX_FAILURE_RETRIES: 3
- COMMIT_AUTHOR: ralph-bot <ralph@example.com>

## Loop checklist
1) Load AGENTS.md and scripts/ralph state files.
2) If paused, exit immediately.
3) Pick the first `"status": "todo"` story in prd.json and mark it doing before work.
4) Keep one story per run and make the smallest possible diff.
5) Run scripts/ralph/guard.sh before committing; never bypass it.
6) Update progress.txt with a short learning when a story is marked done.
7) If failures exceed MAX_FAILURE_RETRIES, set PAUSED: true and push state.

## Working files
- scripts/ralph/prd.json - story queue (todo/doing/done)
- scripts/ralph/progress.txt - chronological learnings
- scripts/ralph/constraints.json - guard limits
- scripts/ralph/failure.json - consecutive failure bookkeeping

## Footguns
- Do not touch build artifacts (dist, node_modules, .svelte-kit, build).
- Avoid changing more than 25 files or 400 total lines per iteration.
- Guard must pass before any commit or push.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ npx create-remote-app my-app

Then follow the setup guide in your new project's README.

## Ralph Loop Automation

This repo includes a minimal Ralph loop to run one story at a time.

1. Add a repo-scoped PAT secret named `RALPH_PAT` (contents + workflow).
2. Trigger the **Ralph Loop** workflow manually, or push changes to `AGENTS.md` or `scripts/ralph/**` (only bot pushes auto-run).
3. The loop reads `AGENTS.md` plus `scripts/ralph/{prd.json,progress.txt,constraints.json,failure.json}`, runs `scripts/ralph/guard.sh`, and commits if the guard passes.

State files live under `scripts/ralph/` and `.opencode/opencode.json` wires them into OpenCode.

## What This Repo Shows

This project demonstrates:
Expand Down Expand Up @@ -213,4 +223,4 @@ This is a pragmatic starting point for projects needing authenticated persistent
## Requirements

- Node.js + Bun
- Cloudflare account (for deployment)
- Cloudflare account (for deployment)
7 changes: 7 additions & 0 deletions scripts/ralph/constraints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"maxFilesChanged": 25,
"maxLinesChanged": 400,
"forbiddenPaths": ["node_modules", "dist", "build", ".svelte-kit", ".next"],
"requireProgressUpdate": true,
"maxFailureRetries": 3
}
6 changes: 6 additions & 0 deletions scripts/ralph/failure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"consecutiveFailures": 0,
"lastRunUrl": "",
"lastError": "",
"lastTimestamp": ""
}
76 changes: 76 additions & 0 deletions scripts/ralph/guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail

constraints_file="${1:-}"

if [[ -z "${constraints_file}" ]]; then
echo "Usage: $0 <constraints.json>" >&2
exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
echo "jq is required for guard checks" >&2
exit 1
fi

if [[ ! -f "${constraints_file}" ]]; then
echo "Constraints file not found: ${constraints_file}" >&2
exit 1
fi

if grep -Eq "^[[:space:]]*-[[:space:]]*PAUSED:[[:space:]]*true" AGENTS.md; then
echo "Guard blocked: AGENTS.md is paused" >&2
exit 1
fi

max_files=$(jq -r '.maxFilesChanged // 0' "${constraints_file}")
max_lines=$(jq -r '.maxLinesChanged // 0' "${constraints_file}")
require_progress=$(jq -r '.requireProgressUpdate // false' "${constraints_file}")
readarray -t forbidden_paths < <(jq -r '.forbiddenPaths[]?' "${constraints_file}")
readarray -t changed_list < <(git diff HEAD --name-only)

if [[ ${#changed_list[@]} -eq 0 ]]; then
echo "Guard note: no changes detected; nothing to validate."
exit 0
fi

file_count=${#changed_list[@]}
if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then
echo "Guard failed: ${file_count} files changed (max ${max_files})." >&2
exit 1
fi

line_total=$(git diff HEAD --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (NR==0) print 0; else print total}')
if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then
echo "Guard failed: ${line_total} total line changes (max ${max_lines})." >&2
exit 1
fi

if [[ ${#forbidden_paths[@]} -gt 0 ]]; then
for file in "${changed_list[@]}"; do
for path in "${forbidden_paths[@]}"; do
[[ -z "${path}" ]] && continue
case "${file}" in
"${path}"|"${path}"/*)
echo "Guard failed: forbidden path touched (${path})." >&2
exit 1
;;
esac
done
done
fi

if [[ "${require_progress}" == "true" ]]; then
prd_changed=0
progress_changed=0
for file in "${changed_list[@]}"; do
[[ "${file}" == "scripts/ralph/prd.json" ]] && prd_changed=$((prd_changed+1))
[[ "${file}" == "scripts/ralph/progress.txt" ]] && progress_changed=$((progress_changed+1))
done
if [[ "${prd_changed}" -gt 0 && "${progress_changed}" -eq 0 ]]; then
echo "Guard failed: prd.json changed without updating progress.txt." >&2
exit 1
fi
fi

echo "Guard passed: constraints satisfied."
32 changes: 32 additions & 0 deletions scripts/ralph/prd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"id": "S1",
"title": "Boot Ralph automation scaffold",
"status": "todo",
"acceptanceCriteria": [
"Required Ralph state files and OpenCode config exist in the repo",
"Guard script is available for later iterations"
],
"notes": ""
},
{
"id": "S2",
"title": "Add/verify diff guard in CI",
"status": "todo",
"acceptanceCriteria": [
"Guard runs in the Ralph workflow",
"Guard fails on forbidden paths or oversized diffs"
],
"notes": ""
},
{
"id": "S3",
"title": "Update README with usage instructions",
"status": "todo",
"acceptanceCriteria": [
"README documents the Ralph loop triggers",
"README lists required secrets and state files"
],
"notes": ""
}
]
3 changes: 3 additions & 0 deletions scripts/ralph/progress.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Ralph Progress
- Initialized scaffold and baseline state.
- Clarified story naming and guard behavior.