Skip to content

Instantly share code, notes, and snippets.

@vojtaholik
Last active February 26, 2026 08:00
Show Gist options
  • Select an option

  • Save vojtaholik/d844f9dd69e5a052d0835041a37cbd62 to your computer and use it in GitHub Desktop.

Select an option

Save vojtaholik/d844f9dd69e5a052d0835041a37cbd62 to your computer and use it in GitHub Desktop.

What it does

ralph-review.sh [max_iter] [base_branch]
ralph-review.sh              # 3 iterations, against main
ralph-review.sh 5            # 5 iterations, against main
ralph-review.sh 3 develop    # 3 iterations, against develop

Each iteration runs two Claude instances serially:

Agent Focus Action
Validator correctness, safety, no regressions fixes issues + commits
Minifier bloat, reuse, simplification simplifies + commits

Loop exits when both respond ALL_CLEAN, or after max iterations.

Why not a skill

  • A skill runs inside a Claude session — the loop would eat context on every pass
  • Ralph runs Claude externally — each invocation gets a fresh context window with the current (post-fix) diff

Usage

Make it executable (first time only):

chmod +x ~/.claude/scripts/ralph-review.sh

Then run it from your project root after you're done implementing:

~/.claude/scripts/ralph-review.sh

Or alias it in your shell config if you want something shorter like ralph-review.

#!/bin/bash
#
# attendu-design-system-check — Ralph-style loop checking that new/changed
# code uses @attendu/design-system components instead of raw HTML elements.
#
# Usage:
# attendu-design-system-check
# attendu-design-system-check 5 # custom max iterations
# attendu-design-system-check 3 develop # custom base branch
MAX_ITER="${1:-3}"
BASE_BRANCH="${2:-main}"
ITER=0
SIGNAL="ALL_CLEAN"
TMPDIR_RALPH=$(mktemp -d)
trap 'echo ""; echo "=== Interrupted. ==="; rm -rf "$TMPDIR_RALPH"; exit 130' INT TERM
run_agent() {
local label="$1"
local outfile="$2"
local prompt="$3"
echo "[$label] Running..."
echo ""
claude -p --verbose --output-format stream-json --permission-mode acceptEdits "$prompt" 2>/dev/null \
| jq --unbuffered -rj 'select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text // empty' \
| tee "$outfile"
echo ""
}
echo "=== Design System Usage Check ==="
echo " Base: $BASE_BRANCH | Max iterations: $MAX_ITER"
echo ""
while [ "$ITER" -lt "$MAX_ITER" ]; do
ITER=$((ITER + 1))
echo "--- Iteration $ITER/$MAX_ITER ---"
DOUT="$TMPDIR_RALPH/ds.txt"
# ── Agent: Design System Enforcer ───────────────────────────────
run_agent "DS Enforcer" "$DOUT" "You are enforcing usage of the @attendu/design-system component library.
This project has a design system with these auto-imported components (no import needed):
BUTTONS & CONTROLS: Button, ButtonGroup, Popover, Tooltip
FORM & INPUT: TextInput, TextArea, NumberInput, PhoneInput, DatePicker, TimeInput, TimeRange, Select, Cascader, PlaceInput, Checkbox, CheckboxGroup, RadioGroup, ColorPicker, Switch, ToggleButtons, InputBlock
LAYOUT: Form, Dialog, DialogHeader, Modal, Navigation, NavigationItem, Tabs, Stepper, ActionList, ActionListItem, DynamicList, DynamicListItem
DATA DISPLAY: Avatar, Badge, Icon, StatTile, ProgressPie, EmptyState, Callout
FILE & MEDIA: ImageUpload, AvatarChange, Thumbnail
Inspect all changed .vue files on this branch vs $BASE_BRANCH:
git diff $BASE_BRANCH -- '*.vue'
git diff --cached -- '*.vue'
In the CHANGED/NEW code (not pre-existing code), find raw HTML elements that should use design system components instead:
<button> → <Button>
<input> → <TextInput>, <NumberInput>, <PhoneInput>, etc. (match by type)
<textarea> → <TextArea>
<select> → <Select>
<dialog> → <Dialog>
<img> used as avatar → <Avatar>
<a> styled as button → <Button>
Raw checkbox/radio inputs → <Checkbox>, <RadioGroup>
Raw toggle/switch → <Switch>
Custom empty states → <EmptyState>
Custom tooltips → <Tooltip>
Custom popovers → <Popover>
IMPORTANT: Only flag elements in code that was ADDED or CHANGED on this branch. Do not touch pre-existing code. Look at the actual source files to understand context — a raw <button> inside a design system component itself is fine, but a raw <button> in a page or app component should use <Button>.
NEVER run git commit or git add. You may edit files to replace raw elements with design system components, but never commit.
For each replacement, ensure you preserve all existing attributes, event handlers, classes, and slots. Check the component file to understand its props before replacing.
If all changed code properly uses design system components: respond with ONLY the exact text ALL_CLEAN and nothing else."
echo ""
if grep -q "$SIGNAL" "$DOUT"; then
echo "[DS Enforcer] All clean — design system components used properly."
else
echo "[DS Enforcer] Found and fixed raw HTML elements."
fi
echo ""
# ── Check convergence ──────────────────────────────────────────
if grep -q "$SIGNAL" "$DOUT"; then
echo "=== Design system usage verified. Done! ==="
rm -rf "$TMPDIR_RALPH"
exit 0
fi
done
echo "=== Max iterations ($MAX_ITER) reached. ==="
rm -rf "$TMPDIR_RALPH"
exit 1
#!/bin/bash
#
# ralph-review.sh — Ralph-style review loop for branch changes
#
# Runs two agents serially (Validator + Minifier) against all changes
# (committed + uncommitted). Agents can fix code but never commit.
# Loops until both report ALL_CLEAN or max iterations reached.
#
# Usage:
# ralph-review.sh # default: 3 iterations, compare against main
# ralph-review.sh 5 # custom max iterations
# ralph-review.sh 3 develop # custom iterations + base branch
MAX_ITER="${1:-3}"
BASE_BRANCH="${2:-main}"
ITER=0
SIGNAL="ALL_CLEAN"
TMPDIR_RALPH=$(mktemp -d)
trap 'echo ""; echo "=== Interrupted. ==="; rm -rf "$TMPDIR_RALPH"; exit 130' INT TERM
# Stream claude output live while also saving the final result
run_agent() {
local label="$1"
local outfile="$2"
local prompt="$3"
echo "[$label] Running..."
echo ""
# stream-json + verbose for real-time output, jq extracts text as it arrives
claude -p --verbose --output-format stream-json --permission-mode acceptEdits "$prompt" 2>/dev/null \
| jq --unbuffered -rj 'select(.type == "assistant") | .message.content[]? | select(.type == "text") | .text // empty' \
| tee "$outfile"
echo ""
}
echo "=== Ralph Review Loop ==="
echo " Base: $BASE_BRANCH | Max iterations: $MAX_ITER"
echo ""
while [ "$ITER" -lt "$MAX_ITER" ]; do
ITER=$((ITER + 1))
echo "--- Iteration $ITER/$MAX_ITER ---"
VOUT="$TMPDIR_RALPH/validator.txt"
MOUT="$TMPDIR_RALPH/minifier.txt"
# ── Agent 1: Validator ──────────────────────────────────────────
run_agent "Validator" "$VOUT" "Review ALL changes on the current branch vs $BASE_BRANCH — both committed and uncommitted.
Run these commands to see the full picture:
git diff $BASE_BRANCH
git diff --cached
git status
NEVER run git commit or git add. You may edit files to fix issues, but never commit.
Check everything:
1. Is everything implemented correctly and safely?
2. Any bugs, edge cases, or security issues?
3. Any unwanted disruption to existing functionality?
4. Any dead code or leftover debug artifacts?
5. Does the new code follow patterns and conventions used in the rest of the codebase? (naming, file structure, error handling, etc.)
6. Is the code quality consistent with surrounding code? (no style clashes, no mixing paradigms)
7. Are there any inconsistencies in how similar things are handled across the changed files?
If you find issues: fix them in the files and describe what you changed.
If everything is clean: respond with ONLY the exact text ALL_CLEAN and nothing else."
echo ""
if grep -q "$SIGNAL" "$VOUT"; then
echo "[Validator] Clean."
else
echo "[Validator] Found and fixed issues."
fi
echo ""
# ── Agent 2: Minifier ──────────────────────────────────────────
run_agent "Minifier" "$MOUT" "Review ALL changes on the current branch vs $BASE_BRANCH — both committed and uncommitted.
Run these commands to see the full picture:
git diff $BASE_BRANCH
git diff --cached
git status
NEVER run git commit or git add. You may edit files to simplify code, but never commit.
Check the changes for bloat:
1. Is there unnecessary or redundant code?
2. Can existing utils, helpers, or packages be reused instead?
3. Can anything be shorter or simpler without losing clarity?
4. Are there patterns used elsewhere in the codebase that should be reused here?
If you find simplifications: apply them and describe what you changed.
If the code is already tight: respond with ONLY the exact text ALL_CLEAN and nothing else."
echo ""
if grep -q "$SIGNAL" "$MOUT"; then
echo "[Minifier] Clean."
else
echo "[Minifier] Applied simplifications."
fi
echo ""
# ── Check convergence ──────────────────────────────────────────
if grep -q "$SIGNAL" "$VOUT" && grep -q "$SIGNAL" "$MOUT"; then
echo "=== Both agents report ALL_CLEAN. Done! ==="
rm -rf "$TMPDIR_RALPH"
exit 0
fi
done
echo "=== Max iterations ($MAX_ITER) reached. ==="
rm -rf "$TMPDIR_RALPH"
exit 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment