Skip to content

Commit 97b3497

Browse files
tiagoefreitasclaude
andcommitted
feat(ralph): add user-level storage option
Add support for storing Ralph scripts in user home directory (~/.config/flow-next/ralph/) instead of project-local scripts/ralph/. Benefits: - Scripts shared across multiple projects - User config provides defaults, project config overrides - Auto-updates via /flow-next:sync - Project keeps only config.env (overrides) and runs/ Config loading: 1. User config (~/.config/flow-next/ralph/config.env) - defaults 2. Project config (scripts/ralph/config.env) - overrides Changes: - ralph.sh: detect user-level dir, load user+project config - ralph-init SKILL: support --user flag for user-level install - Add flow-next-sync skill for updating from plugin - Add VERSION file for tracking plugin version Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ddfd796 commit 97b3497

5 files changed

Lines changed: 269 additions & 24 deletions

File tree

plugins/flow-next/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.4.0
Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
---
22
name: flow-next-ralph-init
3-
description: Scaffold repo-local Ralph autonomous harness under scripts/ralph/. Use when user runs /flow-next:ralph-init.
3+
description: Scaffold Ralph autonomous harness. Supports project-local (scripts/ralph/) or user-level (~/.config/flow-next/ralph/) modes. Use when user runs /flow-next:ralph-init.
44
---
55

66
# Ralph init
77

8-
Scaffold repo-local Ralph harness. Opt-in only.
8+
Scaffold Ralph autonomous harness. Opt-in only.
9+
10+
## Installation Modes
11+
12+
### Project-local (default)
13+
- Everything in `scripts/ralph/` in the current repo
14+
- Scripts, config, and runs all in one place
15+
- Good for: single project, team sharing via git
16+
17+
### User-level (`--user` flag)
18+
- Scripts in `~/.config/flow-next/ralph/` (shared across projects)
19+
- Project gets `scripts/ralph/config.env` and symlinks
20+
- Runs stay in project `scripts/ralph/runs/`
21+
- Good for: multiple projects, personal workflow, auto-updates via `/flow-next:sync`
922

1023
## Rules
1124

12-
- Only create `scripts/ralph/` in the current repo.
13-
- If `scripts/ralph/` already exists, stop and ask the user to remove it first.
14-
- Copy templates from `templates/` into `scripts/ralph/`.
15-
- Copy `flowctl` and `flowctl.py` from `${CLAUDE_PLUGIN_ROOT}/scripts/` into `scripts/ralph/`.
16-
- Set executable bit on `scripts/ralph/ralph.sh`, `scripts/ralph/ralph_once.sh`, and `scripts/ralph/flowctl`.
25+
### Project-local mode
26+
- Create `scripts/ralph/` in the current repo
27+
- If exists, stop and ask user to remove it first
28+
- Copy all templates from `templates/` into `scripts/ralph/`
29+
- Copy `flowctl` and `flowctl.py` from `${CLAUDE_PLUGIN_ROOT}/scripts/`
30+
- Set executable bits
31+
32+
### User-level mode (`--user`)
33+
- Create `~/.config/flow-next/ralph/` if not exists
34+
- If exists, ask user if they want to update (runs /flow-next:sync) or skip
35+
- Copy scripts to user dir: `ralph.sh`, `ralph_once.sh`, `flowctl`, `flowctl.py`, `watch-filter.py`, `prompt_*.md`
36+
- Write VERSION file to track plugin version
37+
- In project, create `scripts/ralph/` with:
38+
- `config.env` (project-specific config)
39+
- Symlinks: `ralph.sh -> ~/.config/flow-next/ralph/ralph.sh` etc.
40+
- `runs/` directory for run logs
1741

1842
## Workflow
1943

20-
1. Resolve repo root: `git rev-parse --show-toplevel`
21-
2. Check `scripts/ralph/` does not exist.
44+
1. Parse arguments: check for `--user` flag
45+
2. Resolve repo root: `git rev-parse --show-toplevel`
2246
3. Detect available review backends:
2347
```bash
2448
HAVE_RP=$(which rp-cli >/dev/null 2>&1 && echo 1 || echo 0)
@@ -37,12 +61,35 @@ Scaffold repo-local Ralph harness. Opt-in only.
3761
- If only rp-cli available: use `rp`
3862
- If only codex available: use `codex`
3963
- If neither available: use `none`
40-
5. Write `scripts/ralph/config.env` with:
41-
- `PLAN_REVIEW=<chosen>` and `WORK_REVIEW=<chosen>`
42-
- replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` placeholders in the template
43-
6. Copy templates and flowctl files.
44-
7. Print next steps (run from terminal, NOT inside Claude Code):
45-
- Edit `scripts/ralph/config.env` to customize settings
46-
- `./scripts/ralph/ralph_once.sh` (one iteration, observe)
47-
- `./scripts/ralph/ralph.sh` (full loop, AFK)
48-
- Uninstall: `rm -rf scripts/ralph/`
64+
65+
### If project-local mode (no --user):
66+
5. Check `scripts/ralph/` does not exist
67+
6. Copy templates to `scripts/ralph/`
68+
7. Copy flowctl files
69+
8. Replace `{{PLAN_REVIEW}}` and `{{WORK_REVIEW}}` in config.env
70+
9. Set executable bits
71+
72+
### If user-level mode (--user):
73+
5. Check/create `~/.config/flow-next/ralph/`
74+
6. Copy scripts to user dir (skip config.env)
75+
7. Write `~/.config/flow-next/ralph/VERSION` with plugin version
76+
8. In project `scripts/ralph/`:
77+
- Copy config.env template, replace placeholders
78+
- Create symlinks to user scripts
79+
- Create `runs/` directory
80+
9. Set executable bits on user scripts
81+
82+
## Print next steps
83+
84+
### Project-local:
85+
- Edit `scripts/ralph/config.env` to customize settings
86+
- `./scripts/ralph/ralph_once.sh` (one iteration, observe)
87+
- `./scripts/ralph/ralph.sh` (full loop, AFK)
88+
- Uninstall: `rm -rf scripts/ralph/`
89+
90+
### User-level:
91+
- Edit `scripts/ralph/config.env` for project-specific settings
92+
- `./scripts/ralph/ralph.sh` (runs from user-level scripts)
93+
- Update scripts: `/flow-next:sync` (backs up changes, updates from plugin)
94+
- Uninstall project: `rm -rf scripts/ralph/`
95+
- Uninstall user-level: `rm -rf ~/.config/flow-next/ralph/`

plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,92 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5-
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
6-
CONFIG="$SCRIPT_DIR/config.env"
4+
# ─────────────────────────────────────────────────────────────────────────────
5+
# Path resolution: supports user-level or project-local installation
6+
# ─────────────────────────────────────────────────────────────────────────────
7+
# User-level mode:
8+
# - Scripts in ~/.config/flow-next/ralph/
9+
# - User config in ~/.config/flow-next/ralph/config.env (defaults)
10+
# - Project config in scripts/ralph/config.env (overrides)
11+
# - Runs in scripts/ralph/runs/ (always project-local)
12+
#
13+
# Project-local mode: everything in scripts/ralph/ (original behavior)
14+
#
15+
# Detection order:
16+
# 1. RALPH_USER_DIR env var (explicit user-level path)
17+
# 2. ~/.config/flow-next/ralph/ if exists
18+
# 3. Fall back to project-local (script's directory)
19+
# ─────────────────────────────────────────────────────────────────────────────
20+
21+
_resolve_script_dir() {
22+
local self_dir
23+
self_dir="$(cd "$(dirname "$0")" && pwd)"
24+
25+
# Check if we're a symlink to user-level scripts
26+
if [[ -L "$0" ]]; then
27+
local target
28+
target="$(readlink -f "$0")"
29+
self_dir="$(dirname "$target")"
30+
fi
31+
32+
echo "$self_dir"
33+
}
34+
35+
_resolve_user_dir() {
36+
# Explicit env var takes precedence
37+
if [[ -n "${RALPH_USER_DIR:-}" ]]; then
38+
echo "$RALPH_USER_DIR"
39+
return
40+
fi
41+
# Default user-level location
42+
local default_user_dir="${HOME}/.config/flow-next/ralph"
43+
if [[ -d "$default_user_dir" ]]; then
44+
echo "$default_user_dir"
45+
return
46+
fi
47+
echo ""
48+
}
49+
50+
_resolve_project_dir() {
51+
# Project scripts/ralph/ directory (for config and runs)
52+
local root="$1"
53+
echo "$root/scripts/ralph"
54+
}
55+
56+
# Determine actual script source (may be user-level)
57+
SCRIPT_DIR="$(_resolve_script_dir)"
58+
USER_DIR="$(_resolve_user_dir)"
59+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
60+
PROJECT_DIR="$(_resolve_project_dir "$ROOT_DIR")"
61+
62+
# Scripts come from user-level if available, else project-local
63+
if [[ -n "$USER_DIR" && -f "$USER_DIR/ralph.sh" ]]; then
64+
SCRIPT_DIR="$USER_DIR"
65+
fi
66+
67+
# Config: user-level defaults + project overrides
68+
# User config provides defaults, project config overrides
69+
USER_CONFIG=""
70+
PROJECT_CONFIG=""
71+
if [[ -n "$USER_DIR" && -f "$USER_DIR/config.env" ]]; then
72+
USER_CONFIG="$USER_DIR/config.env"
73+
fi
74+
if [[ -f "$PROJECT_DIR/config.env" ]]; then
75+
PROJECT_CONFIG="$PROJECT_DIR/config.env"
76+
fi
77+
# For compatibility, CONFIG points to the primary config (user or project)
78+
if [[ -n "$USER_CONFIG" ]]; then
79+
CONFIG="$USER_CONFIG"
80+
elif [[ -n "$PROJECT_CONFIG" ]]; then
81+
CONFIG="$PROJECT_CONFIG"
82+
else
83+
CONFIG="$SCRIPT_DIR/config.env"
84+
fi
785
FLOWCTL="$SCRIPT_DIR/flowctl"
886

87+
# Ensure runs directory exists in project
88+
mkdir -p "$PROJECT_DIR/runs" 2>/dev/null || true
89+
990
fail() { echo "ralph: $*" >&2; exit 1; }
1091
log() {
1192
# Machine-readable logs: only show when UI disabled
@@ -264,12 +345,15 @@ ui_waiting() {
264345
ui " ${C_DIM}⏳ Claude working...${C_RESET}"
265346
}
266347

267-
[[ -f "$CONFIG" ]] || fail "missing config.env"
348+
# Require at least one config file
349+
[[ -n "$USER_CONFIG" || -n "$PROJECT_CONFIG" ]] || fail "missing config.env (user or project)"
268350
[[ -x "$FLOWCTL" ]] || fail "missing flowctl"
269351

352+
# Load config: user-level first (defaults), then project (overrides)
270353
# shellcheck disable=SC1090
271354
set -a
272-
source "$CONFIG"
355+
[[ -n "$USER_CONFIG" && -f "$USER_CONFIG" ]] && source "$USER_CONFIG"
356+
[[ -n "$PROJECT_CONFIG" && -f "$PROJECT_CONFIG" ]] && source "$PROJECT_CONFIG"
273357
set +a
274358

275359
MAX_ITERATIONS="${MAX_ITERATIONS:-25}"
@@ -421,7 +505,7 @@ PY
421505
}
422506

423507
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$(hostname -s 2>/dev/null || hostname)-$(sanitize_id "$(get_actor)")-$$-$(rand4)"
424-
RUN_DIR="$SCRIPT_DIR/runs/$RUN_ID"
508+
RUN_DIR="$PROJECT_DIR/runs/$RUN_ID"
425509
mkdir -p "$RUN_DIR"
426510
ATTEMPTS_FILE="$RUN_DIR/attempts.json"
427511
ensure_attempts_file "$ATTEMPTS_FILE"

plugins/flow-next/skills/flow-next-ralph-init/templates/ralph_once.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
# Use this to observe behavior before going fully autonomous
44

55
set -euo pipefail
6+
7+
# Resolve script directory (follows symlinks for user-level mode)
68
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9+
if [[ -L "$0" ]]; then
10+
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
11+
fi
712

813
export MAX_ITERATIONS=1
914
exec "$SCRIPT_DIR/ralph.sh" "$@"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
name: flow-next-sync
3+
description: Sync user-level Ralph scripts from plugin. Backs up local changes before updating. Use when user runs /flow-next:sync.
4+
---
5+
6+
# Flow-Next Sync
7+
8+
Update user-level Ralph scripts from plugin templates. Creates diff backups of local changes.
9+
10+
## Overview
11+
12+
When using user-level Ralph installation (`~/.config/flow-next/ralph/`), this command:
13+
1. Compares current scripts with plugin templates
14+
2. Backs up any local modifications
15+
3. Updates scripts to latest plugin version
16+
4. Reports what changed
17+
18+
## Rules
19+
20+
- Only operates on user-level installation (`~/.config/flow-next/ralph/`)
21+
- If user-level dir doesn't exist, suggest running `/flow-next:ralph-init --user`
22+
- Always create backup before overwriting modified files
23+
- Backup location: `~/.config/flow-next/ralph/backups/<timestamp>/`
24+
- Update VERSION file after sync
25+
26+
## Workflow
27+
28+
1. Check `~/.config/flow-next/ralph/` exists
29+
- If not: print "No user-level Ralph found. Run `/flow-next:ralph-init --user` first."
30+
31+
2. Read current VERSION from `~/.config/flow-next/ralph/VERSION`
32+
- If missing, assume version "0.0.0"
33+
34+
3. Read plugin VERSION from `${CLAUDE_PLUGIN_ROOT}/VERSION`
35+
36+
4. Compare versions:
37+
- If same: print "Already up to date (version X.Y.Z)"
38+
- If different: proceed with sync
39+
40+
5. For each script file, check if modified:
41+
```bash
42+
# Files to sync:
43+
ralph.sh
44+
ralph_once.sh
45+
watch-filter.py
46+
prompt_plan.md
47+
prompt_work.md
48+
flowctl
49+
flowctl.py
50+
```
51+
52+
6. Create backup directory if any files modified:
53+
```bash
54+
BACKUP_DIR="$HOME/.config/flow-next/ralph/backups/$(date +%Y%m%dT%H%M%S)"
55+
mkdir -p "$BACKUP_DIR"
56+
```
57+
58+
7. For each modified file:
59+
- Create diff: `diff -u <plugin_file> <user_file> > $BACKUP_DIR/<filename>.diff`
60+
- Copy user file to backup: `cp <user_file> $BACKUP_DIR/<filename>`
61+
- Print: "Backed up: <filename> (modified)"
62+
63+
8. Copy all template files from plugin:
64+
- From: `${CLAUDE_PLUGIN_ROOT}/skills/flow-next-ralph-init/templates/`
65+
- To: `~/.config/flow-next/ralph/`
66+
- Skip: `config.env`, `runs/`, `.gitignore`
67+
68+
9. Copy flowctl files from plugin:
69+
- From: `${CLAUDE_PLUGIN_ROOT}/scripts/flowctl`, `flowctl.py`
70+
- To: `~/.config/flow-next/ralph/`
71+
72+
10. Update VERSION file:
73+
```bash
74+
cp "${CLAUDE_PLUGIN_ROOT}/VERSION" "$HOME/.config/flow-next/ralph/VERSION"
75+
```
76+
77+
11. Set executable bits:
78+
```bash
79+
chmod +x ~/.config/flow-next/ralph/ralph.sh
80+
chmod +x ~/.config/flow-next/ralph/ralph_once.sh
81+
chmod +x ~/.config/flow-next/ralph/flowctl
82+
chmod +x ~/.config/flow-next/ralph/watch-filter.py
83+
```
84+
85+
12. Print summary:
86+
```
87+
Synced to version X.Y.Z
88+
Updated: <list of files>
89+
Backups: ~/.config/flow-next/ralph/backups/<timestamp>/
90+
```
91+
92+
## Detecting Modifications
93+
94+
Compare file checksums (md5sum or sha256sum):
95+
```bash
96+
PLUGIN_HASH=$(md5sum "$PLUGIN_FILE" | cut -d' ' -f1)
97+
USER_HASH=$(md5sum "$USER_FILE" | cut -d' ' -f1)
98+
if [[ "$PLUGIN_HASH" != "$USER_HASH" ]]; then
99+
# File was modified
100+
fi
101+
```
102+
103+
## Force Mode
104+
105+
If user says "force" or passes `--force`:
106+
- Skip version check
107+
- Always sync all files
108+
- Still create backups of modified files

0 commit comments

Comments
 (0)