From ralph-harness
Use to set up the Ralph loop harness in the CURRENT project — scaffolds ralph.conf, PROMPT.md, a thin Containerfile (FROM the ralph base image) and Makefile, and a tasks.md starter, inferring values from the repo. Triggers: "set up the ralph loop", "ralph init", "add the harness to this project".
How this skill is triggered — by the user, by Claude, or both
Slash command
/ralph-harness:ralph-initThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Your job is to generate the per-project config a project needs to run the Ralph
Your job is to generate the per-project config a project needs to run the Ralph
loop, by filling this plugin's bundled templates with values inferred from the
target repo. The loop machinery lives in the ralph-base container image, so
you scaffold config only — never copy ralph.sh/until_reset.py into the repo.
The templates ship with this plugin. Read them from the plugin directory — prefer
$CLAUDE_PLUGIN_ROOT/templates/ if that variable is set, otherwise find the
plugin install path and read templates/ under it:
templates/ralph.conf.exampletemplates/PROMPT.md.templatetemplates/Containerfile.templatetemplates/Makefile.templatetemplates/gate.sh.templatetemplates/pre-commit.templatetemplates/ci.yml.templatetemplates/STATUS.md.seedtemplates/questions.md.seedtemplates/specs-README.md.templateThe fully-resolved example/ directory in the same plugin is your golden
reference for what good output looks like.
The plugin also bundles base/ (the base-image Containerfile + the runner
scripts) and the root Makefile, so the ralph-base:v1 image can be built from
$CLAUDE_PLUGIN_ROOT without a separate source clone — see §4.
Run in the target project's repo root (the repo you want the loop to build —
NOT this plugin). Confirm with the user if the cwd is ambiguous. If a ralph.conf
or PROMPT.md already exists, stop and ask before overwriting.
Gather these, reading the repo; state each as "inferred X" or ask if unsure:
pyproject.toml [project].name, package.json name,
or the repo directory name.docs/specs/, specs/, openspec/; default docs/specs.tests/, test/; default tests.docs/decisions/; default docs/decisions.openspec/changes/<feature>/). Little/no prior source →
greenfield; anything else, or unclear → treat as brownfield. Do NOT run the test
suite to measure coverage. This assessment drives the coverage-mode recommendation..github/workflows/*.yml) for the lint/type/test
sequence; otherwise infer from the toolchain. The gate MUST include a
test-COVERAGE check with a threshold, not just a test run. This MUST match what CI
runs, in CI order. Confirm the gate, the coverage threshold, AND the coverage
MODE with the user — a wrong/too-aggressive value, or the wrong mode, blocks
every commit. Present each as a recommended option with a repo-specific reason (per
the Guardrails convention); these are high-stakes, so the rationale states the risk
and you require confirmation rather than silently applying it. For the threshold,
recommend a starting value that fits the repo (e.g. higher for a scoped pure-logic
gate than for a global floor). The coverage-MODE choice you present MUST always include patch/scoped
coverage (gating only the lines/paths a turn changed) as a first-class option — not
only "global floor vs none". Order the recommended option by the assessment:
ruff format . && ruff check . && mypy . && pytest --cov=<pkg> --cov-fail-under=80.pytest --cov=<new_pkg>, vitest
--coverage.include='<feature-dir>/**' --coverage.thresholds.lines=<N>, or
go test -cover ./<feature-pkg>/...; true diff-coverage (diff-cover vs the base
branch) is the stricter alternative. Warn that a GLOBAL floor on an
under-covered existing codebase fails the very first commit. Never silently pick a
global floor the codebase does not already meet; leave the final mode to the user.pytest-cov), as a RUN/install block for the Containerfile
(pin versions where you can read them from lockfiles/config). Add the same
coverage tool to the CI workflow's toolchain step.<project>-loop (kebab-case).podman (this harness targets podman on Linux).Resolve the templates and write, in the target repo root. Never overwrite a file or directory that already exists — leave it intact and record it as skipped for the report (see §4).
ralph.conf — from ralph.conf.example, with the inferred values
(RALPH_CONTAINER=RALPH_REVIEW_GATE, RALPH_AUTO_MERGE,
RALPH_REVIEW_MAX_ROUNDS, RALPH_BASE_BRANCH, RALPH_REVIEWER). The loop is
GitHub-dependent by default: keep RALPH_REVIEW_GATE=1 and ensure GitHub is
ready (§3d). Set it to 0 only if the user explicitly asks for an offline loop
with no review. (RALPH_AUTO_MERGE stays 0 — merging is still the user's call.)PROMPT.md — from PROMPT.md.template, filling {{PROJECT_NAME}},
{{SPECS_DIR}}, {{TESTS_DIR}}, {{DECISIONS_DIR}}. The gate command is NOT a
PROMPT placeholder — it lives only in scripts/gate.sh (below); the prompt just
references that script. Keep the whole portable contract intact (one task/turn,
spec→test→implement, run ./scripts/gate.sh before commit, stop conditions, the
no-Co-Authored-By rule, and the review-findings.md-comes-first clause).Containerfile — from Containerfile.template: FROM ralph-base:v1 plus
the project's toolchain block.Makefile — from Makefile.template, with IMAGE/RUNTIME filled. It carries
the hooks target that installs the gate hook (below).tasks.md — only if absent: a starter with a couple of - [ ] 1.1 ...
example tasks and a note to replace them.scripts/gate.sh — from gate.sh.template, with {{GATE_COMMAND}} filled
by the inferred gate (in CI order). chmod +x it. This is the ONE home of the
gate command.hooks/pre-commit — from pre-commit.template, verbatim (it only execs
scripts/gate.sh). chmod +x it. The Makefile hooks target points git at
this via core.hooksPath..github/workflows/ci.yml — from ci.yml.template, filling
{{TOOLCHAIN_INSTALL}} with the GitHub-runner setup of the gate's tools
(best-effort; flag it for the user to confirm — see Guardrails).core.hooksPath hooks, check
the target repo for a pre-existing core.hooksPath (git config --get core.hooksPath) or a populated .git/hooks (non-sample hooks). If either
exists, do NOT silently override — warn the user that core.hooksPath hooks
will supersede their existing hooks and ask them to consolidate into hooks/.SPECS_DIR,
TESTS_DIR, DECISIONS_DIR), each with a .gitkeep so the empty dir is tracked.STATUS.md — from STATUS.md.seed (a cold-start breadcrumb that is NOT a
stop reason).docs/questions.md — from questions.md.seed.specs-README.md.template (filling
{{PROJECT_NAME}}), written as <SPECS_DIR>/README.md, ONLY if absent. This is
a GUIDE, not a starter spec: do NOT scaffold a placeholder spec the loop could
mistake for real requirements (a convincing-but-fake spec makes the loop build
the wrong thing). If the user can describe the first subsystem in a sentence or
two, offer to write that as the first REAL spec (<SPECS_DIR>/<system>.md);
otherwise leave the dir with just the guide and let the loop's normal
spec→test→implement workflow take over.The loop's outer-loop review gate is ON by default: after a turn commits, the
runner (never the agent) pushes the branch, opens/uses a PR, requests an
independent review (GitHub Copilot by default), and treats zero findings + green
CI as the only PASS — writing any findings to review-findings.md for the agent
to resolve next turn. The container agent stays GitHub-blind, so this works with
any coding agent.
Because it is on by default, this loop is GitHub-dependent: loop mode refuses
to start without a git remote, an authenticated gh, and a non-base feature
branch. So ensure those during init — do not leave the user to discover the
refusal at first run:
git remote — if none, help the user add one (git remote add origin <url>); a GitHub remote is required.gh auth status — if unauthenticated, tell the user to run gh auth login (an interactive step they run on the host).gh auth token returns a value — the runner runs gh inside the
container, so the loop forwards a host-derived GH_TOKEN (the generated
Makefile does export GH_TOKEN ?= $(shell gh auth token) + -e GH_TOKEN).
Host gh auth status alone does NOT authenticate the in-container runner; a
derivable token does. Confirm the generated Makefile forwards GH_TOKEN.RALPH_REVIEW_GATE=0 in ralph.conf.Then ensure the runtime state is gitignored — append .ralph/ and
/review-findings.md to the repo's .gitignore (create it if missing).
review-findings.md is machine-written by the review gate, like .ralph/, so it
must not be committed.
scripts/gate.sh / hooks/pre-commit / .github/workflows/ci.yml (§3b), and
the readiness items: specs/tests/decisions dirs, STATUS.md, docs/questions.md,
the specs-dir guide and any first spec captured (§3c). If you hit a hooks-path
conflict, surface the warning here.make build/loop sets core.hooksPath, the
gate applies to EVERY commit in the repo — host or container — because
.git/config is shared. Tell the user that host-side committers need the gate's
toolchain installed locally (or should commit via make shell); the gate is
designed to run where the toolchain lives (the container).git remote), an authenticated gh (gh auth status), and a non-base feature branch. For any that are blocked, give the
user the exact fix (git remote add origin <url>, gh auth login, check out a
feature branch). If they instead want an offline loop with no review, the
opt-out is RALPH_REVIEW_GATE=0 in ralph.conf.ralph-base:v1 from the plugin's bundled base/ —
no clone needed: make -C "$CLAUDE_PLUGIN_ROOT" build-base. The plugin ships
base/ and the root Makefile, so this builds the same image the source repo
does (registry-free, host UID/GID-matched). Contributors working from a
keep-on-ralphing checkout can run make build-base there instead.make build (also installs the gate hook), then make login
(one-time), then make loop.ralph-base:v1 for them now from the bundled base —
make -C "$CLAUDE_PLUGIN_ROOT" build-base — and to run make build here. If
$CLAUDE_PLUGIN_ROOT is unset, or make/the container runtime is missing, say
which precondition is unmet and fall back to printing the explicit
podman build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t ralph-base:v1 -f "$CLAUDE_PLUGIN_ROOT/base/Containerfile" "$CLAUDE_PLUGIN_ROOT/base"
(or a source-clone build) — never skip the build silently, or the user hits a
missing-image error at make loop. Do not run make loop unattended unless asked./plugin update that touched the runner (base/), the base image is
stale: tell the user to run /ralph-build-base to rebuild ralph-base:v1
from the freshly-installed bundled base/.ralph.sh, until_reset.py) into the target
repo — it comes from the image. scripts/gate.sh is the one exception that is
NOT machinery: it is project-OWNED config (this project's own gate command),
analogous to ralph.conf. Writing it is correct; vendoring the runner is not.ralph-base:v1 from the plugin's bundled base/ ($CLAUDE_PLUGIN_ROOT)
is read-only build context, NOT vendoring: it reads the bundled sources to
produce the image and copies nothing into the target repo. Do not copy base/,
the base Containerfile, or the runner scripts into the consumer — the single
source stays the plugin's base/. Resolve $CLAUDE_PLUGIN_ROOT at build time;
never write a version-pinned plugin cache path into a generated file (the
per-version cache dir is pruned after an update).make login.{{TOOLCHAIN_INSTALL}} is best-effort — CI runner setup can't
be fully inferred. Always flag it for the user to confirm against the Containerfile.RALPH_REVIEW_GATE=0 if the user explicitly asks for an
offline loop. RALPH_AUTO_MERGE stays OFF unless the user opts in — auto-merging
is a separate, explicit choice.Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub swinney/keep-on-ralphing --plugin ralph-harness