Copy files using bash (MUST use cp, NOT Write tool):
If UPDATE_MODE=1 (updating):
cp scripts/ralph/config.env /tmp/ralph-config-backup.env
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/ralph.sh" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/ralph_once.sh" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/prompt_plan.md" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/prompt_work.md" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/prompt_completion.md" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/watch-filter.py" scripts/ralph/
cp "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/ralphctl.py" scripts/ralph/
cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.cmd" "$PLUGIN_ROOT/scripts/flowctl.py" "$PLUGIN_ROOT/scripts/flowctl_bootstrap.py" "$PLUGIN_ROOT/scripts/flowctl-help.txt" "$PLUGIN_ROOT/scripts/lib/pick-python.sh" scripts/ralph/
mkdir -p scripts/ralph/hooks
cp "$PLUGIN_ROOT/scripts/hooks/ralph-guard.py" "$PLUGIN_ROOT/scripts/hooks/ralph-guard" scripts/ralph/hooks/
chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl scripts/ralph/ralphctl.py scripts/ralph/hooks/ralph-guard.py scripts/ralph/hooks/ralph-guard
cp /tmp/ralph-config-backup.env scripts/ralph/config.env
If UPDATE_MODE=0 (fresh install):
mkdir -p scripts/ralph/runs scripts/ralph/hooks
cp -R "$PLUGIN_ROOT/skills/flow-next-ralph-init/templates/." scripts/ralph/
cp "$PLUGIN_ROOT/scripts/flowctl" "$PLUGIN_ROOT/scripts/flowctl.cmd" "$PLUGIN_ROOT/scripts/flowctl.py" "$PLUGIN_ROOT/scripts/flowctl_bootstrap.py" "$PLUGIN_ROOT/scripts/flowctl-help.txt" "$PLUGIN_ROOT/scripts/lib/pick-python.sh" scripts/ralph/
cp "$PLUGIN_ROOT/scripts/hooks/ralph-guard.py" "$PLUGIN_ROOT/scripts/hooks/ralph-guard" scripts/ralph/hooks/
chmod +x scripts/ralph/ralph.sh scripts/ralph/ralph_once.sh scripts/ralph/flowctl scripts/ralph/ralphctl.py scripts/ralph/hooks/ralph-guard.py scripts/ralph/hooks/ralph-guard
Note: cp -R templates/. copies all files including dotfiles (.gitignore).
Register project hooks (agent-driven; required for the guard to fire).
Detect host (same signals as /flow-next:setup Step 0 when available; otherwise probe the settings paths below). Then Read the target file, merge the flow-next Ralph guard entries, Edit/Write the result. Never replace the whole hooks object with only our entries. Idempotent: if an entry's command already contains scripts/ralph/hooks/ralph-guard, leave that matcher group alone (or refresh the command string to the canonical form below if it drifted).
Fingerprint for "this is a flow-next Ralph guard entry": the hook command string contains scripts/ralph/hooks/ralph-guard (wrapper and/or .py fallback).
Canonical guard command (same on every host that can run bash wrappers):
if [ -f scripts/ralph/hooks/ralph-guard ]; then bash scripts/ralph/hooks/ralph-guard; elif [ -f scripts/ralph/hooks/ralph-guard.py ]; then scripts/ralph/hooks/ralph-guard.py; fi
Timeout: 5 seconds. Type: command.
Claude Code → merge into .claude/settings.json
Target: project file .claude/settings.json (create {"hooks":{}} skeleton if missing; preserve every non-hooks key).
Merge these four event groups under hooks (Claude schema). Matchers use regex OR so Droid interop and Claude share one entry shape:
| Event | Matcher | Notes |
|---|
PreToolUse | Bash|Execute | shell (Claude Bash, Droid Execute) |
PreToolUse | Edit|Write | file tools (Claude host names) |
PostToolUse | Bash|Execute | shell |
PostToolUse | Edit|Write | file tools (receipt-path gate parity) |
Stop | (no matcher) | stop gate |
SubagentStop | (no matcher) | subagent stop gate |
Each event's array entry is one matcher group with a single hook object {type, command, timeout} using the canonical command above.
Consent gate: Claude Code's project-hooks trust prompt is the human consent surface. Do not invent a second consent ceremony. After merge, tell the user they may need to accept/trust project hooks in the host UI for them to load this session.
Factory Droid → merge into .factory/hooks.json
Target (verified against Factory hooks-reference): project file .factory/hooks.json. Prefer that path. Fallback only if the project already stores hooks under the hooks key of .factory/settings.json and has no .factory/hooks.json — merge there instead; never invent a third path.
Host-appropriate matchers for Droid (Factory's shell tool is Execute; file tools include Create / ApplyPatch). The guard body accepts the full dual-platform sets (Bash/Execute, Edit/Write/Create/ApplyPatch).
| Event | Matcher | Notes |
|---|
PreToolUse | Bash|Execute | shell |
PreToolUse | Edit|Write|Create|ApplyPatch | Droid file tools |
PostToolUse | Bash|Execute | shell |
PostToolUse | Edit|Write|Create|ApplyPatch | file tools (receipt-path gate) |
Stop | (no matcher) | stop gate |
SubagentStop | (no matcher) | subagent stop gate |
Prefer project-relative command as above (Ralph harness is repo-local). If the host requires absolute paths, rewrite with "$FACTORY_PROJECT_DIR"/scripts/ralph/hooks/... but keep the same fingerprint substring scripts/ralph/hooks/ralph-guard.
Codex → write/merge project .codex/hooks.json
Codex has no Claude-schema plugin hooks auto-load from the marketplace plugin. Project scope is .codex/hooks.json.
Codex subset (no SubagentStop; no Edit/Write matchers — Codex only intercepts shell):
| Event | Matcher |
|---|
PreToolUse | Bash|Execute |
PostToolUse | Bash|Execute |
Stop | (no matcher) |
Top-level JSON must be only {"hooks":{...}} — no sibling description key (Codex rejects unknown fields and disables all hooks).
If .codex/config.toml exists, ensure exactly one hooks = true under [features] (drop deprecated codex_hooks). Same normalization intent as setup's historical Codex hooks step; do it with a careful edit, not a second copy of setup's python block unless you need it.
Cursor / Grok
- Cursor: Ralph hooks are unsupported (Cursor hook schema is
afterFileEdit / beforeShellExecution). Scaffold scripts/ralph/ only; print that the guard will not fire on Cursor; do not invent a Cursor-format hook file.
- Grok Build: reads Claude-compat plugin/project surfaces; use the Claude Code path (
.claude/settings.json).
Re-run / update
On UPDATE_MODE=1 still re-merge hooks so a project that had scaffold but lost settings entries is repaired. Skip only when every required event already has a fingerprinted entry with the canonical command.
Print next steps (run from terminal, NOT inside the agent session):
If UPDATE_MODE=1:
Ralph updated! Your config.env was preserved.
Hooks: project settings were re-merged (idempotent). Accept the host's
project-hooks trust prompt if it appears.
Run from terminal:
- ./scripts/ralph/ralph_once.sh (one iteration, observe)
- ./scripts/ralph/ralph.sh (full loop, AFK)
- ./scripts/ralph/ralphctl.py status|pause|resume|stop (run control; not flowctl)
If UPDATE_MODE=0:
Ralph initialized!
Next steps (run from terminal, NOT inside the agent session):
- Accept project-hooks trust if the host prompts (required once)
- Edit scripts/ralph/config.env to customize settings
- ./scripts/ralph/ralph_once.sh (one iteration, observe)
- ./scripts/ralph/ralph.sh (full loop, AFK)
- ./scripts/ralph/ralphctl.py status|pause|resume|stop (run control; not flowctl)
Maintenance:
- Re-run /flow-next:ralph-init after plugin updates to refresh scripts + re-merge hooks
- Uninstall: /flow-next:uninstall removes hook entries; then manually rm -rf scripts/ralph/ if desired