Skip to content

Commit c31e0ca

Browse files
Kasper Jungeclaude
authored andcommitted
feat: remove ralph status, move validation into ralph run startup
Pre-flight checks (PATH validation, check script executability) now run at `ralph run` startup instead of requiring a separate `ralph status` command. This fails fast with clear errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 70187d0 commit c31e0ca

6 files changed

Lines changed: 99 additions & 279 deletions

File tree

docs/cli.md

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: ralph.toml configuration format, all CLI commands (init, run, status, new), and every option with defaults and examples.
2+
description: ralph.toml configuration format, all CLI commands (init, run, new), and every option with defaults and examples.
33
---
44

55
# Configuration & CLI Reference
@@ -109,22 +109,6 @@ The `[PROMPT]` argument is smart — it resolves in order:
109109

110110
Inline prompts still resolve context placeholders. When inline text is provided, `RALPH.md` doesn't need to exist.
111111

112-
### `ralph status`
113-
114-
Show current configuration, validate setup, and list all discovered primitives.
115-
116-
```bash
117-
ralph status
118-
```
119-
120-
This command checks:
121-
122-
- Whether the prompt file exists
123-
- Whether the agent command is on PATH
124-
- All discovered checks, contexts, and named ralphs (with enabled/disabled status)
125-
126-
If everything is configured correctly, it prints "Ready to run." If not, it tells you exactly what's wrong.
127-
128112
### `ralph new`
129113

130114
Create a new ralph with AI-guided setup. Installs a skill into your agent (Claude Code, Codex) and launches an interactive session where the agent guides you through creating a complete ralph — prompt, checks, and contexts — via conversation.

docs/getting-started.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,7 @@ You are an autonomous coding agent running in a loop...
174174

175175
Each context must be referenced by name — contexts not referenced are excluded from the prompt.
176176

177-
## Step 8: Verify and run
178-
179-
Check that everything is configured correctly:
180-
181-
```bash
182-
ralph status
183-
```
184-
185-
If it says "Ready to run", you're good.
177+
## Step 8: Run the loop
186178

187179
Start with a few iterations to verify things work as expected:
188180

docs/troubleshooting.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Fix common ralphify issues — setup errors, agent hangs, check fai
44

55
# Troubleshooting
66

7-
Common issues and how to fix them. If your problem isn't listed here, run `ralph status` first — it validates your setup and points out most configuration problems.
7+
Common issues and how to fix them. If your problem isn't listed here, run `ralph run -n 1` — it validates your setup and shows clear errors.
88

99
## Setup issues
1010

@@ -14,14 +14,13 @@ You haven't initialized the project yet. Run `ralph init` in your project direct
1414

1515
### "Command 'claude' not found on PATH"
1616

17-
The agent CLI isn't installed or isn't in your shell's PATH. Verify by running `claude --version` directly. If it's installed but not found, check your PATH. `ralph status` also checks this.
17+
The agent CLI isn't installed or isn't in your shell's PATH. Verify by running `claude --version` directly. If it's installed but not found, check your PATH.
1818

19-
### "Not ready" from `ralph status`
19+
### "Agent command not found on PATH"
2020

21-
`ralph status` found one or more problems. The output tells you exactly what's wrong:
21+
`ralph run` checks that the agent command exists on PATH before starting the loop. If you see this error:
2222

23-
- **Ralph file not found** → Create it with `ralph init`, or check that the `ralph` path in `ralph.toml` is correct
24-
- **Command not found** → Install the agent CLI or fix the `command` in `ralph.toml`
23+
- Install the agent CLI or fix the `command` in `ralph.toml`
2524

2625
### "RALPH.md already exists"
2726

@@ -99,8 +98,7 @@ Primitives are discovered once when `ralph run` starts. If you add, remove, or m
9998

10099
1. Press `Ctrl+C` to stop the loop
101100
2. Make your changes (add checks, edit instructions, etc.)
102-
3. Run `ralph status` to verify the changes are detected
103-
4. Run `ralph run` again
101+
3. Run `ralph run` again
104102

105103
This is different from `RALPH.md`, which **is** re-read every iteration. See [What's re-read vs. fixed at startup](primitives.md#whats-re-read-vs-fixed-at-startup) for details.
106104

@@ -168,7 +166,7 @@ chmod +x .ralphify/checks/my-check/run.sh
168166

169167
### Check has neither command nor script
170168

171-
If `ralph status` warns that a check has neither a `run.*` script nor a `command`, add one of:
169+
If a check has neither a `run.*` script nor a `command`, add one of:
172170

173171
- A `command` field in the CHECK.md frontmatter
174172
- An executable script named `run.sh`, `run.py`, etc. in the check directory
@@ -181,7 +179,7 @@ If a `{{ contexts.my-context }}` placeholder silently disappears (the prompt has
181179

182180
1. The directory name matches: `.ralphify/contexts/my-context/CONTEXT.md`
183181
2. The placeholder uses the exact directory name: `{{ contexts.my-context }}`
184-
3. The context is enabled (check with `ralph status`)
182+
3. The context is enabled (check the `enabled` field in its CONTEXT.md frontmatter)
185183
4. If the context has a command, verify the command produces output by running it manually
186184

187185
Same rules apply for `{{ instructions.name }}`.
@@ -241,7 +239,7 @@ git checkout -b feature-b && ralph run
241239

242240
## Getting more help
243241

244-
1. Run `ralph status` to validate your full setup
242+
1. Run `ralph run -n 1` to validate your setup — it checks your config and shows clear errors
245243
2. Use `ralph run -n 1 --log-dir ralph_logs` to capture a single iteration for debugging
246244
3. Check the [Configuration & CLI](cli.md) for all available options
247245
4. File an issue at [github.com/computerlovetech/ralphify](https://github.com/computerlovetech/ralphify/issues)

src/ralphify/cli.py

Lines changed: 6 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""CLI commands for ralphify — init, run, status, and scaffold new primitives.
1+
"""CLI commands for ralphify — init, run, and scaffold new primitives.
22
33
This is the main module. The ``run`` command delegates to the engine module
44
for the core autonomous loop. Terminal rendering of events is handled by
@@ -10,22 +10,17 @@
1010
import sys
1111
import tomllib
1212
import uuid
13-
from collections.abc import Callable
1413
from pathlib import Path
15-
from typing import TypeVar
1614

1715
import typer
1816
from rich.console import Console
1917

2018
from ralphify import __version__
2119
from ralphify._console_emitter import ConsoleEmitter
22-
from ralphify._discovery import Primitive
2320
from ralphify._frontmatter import CONFIG_FILENAME
24-
from ralphify.checks import discover_checks
25-
from ralphify.contexts import discover_contexts
2621
from ralphify._run_types import RunConfig, RunState
2722
from ralphify.engine import run_loop
28-
from ralphify.ralphs import discover_ralphs, resolve_ralph_source
23+
from ralphify.ralphs import resolve_ralph_source
2924
from ralphify.detector import detect_project
3025
from ralphify._templates import (
3126
ROOT_RALPH_TEMPLATE,
@@ -64,20 +59,6 @@
6459
]
6560

6661

67-
_P = TypeVar("_P", bound=Primitive)
68-
69-
70-
def _print_primitives_section(label: str, items: list[_P], detail_fn: Callable[[_P], str]) -> None:
71-
"""Print a status section for discovered primitives."""
72-
if items:
73-
rprint(f"\n[bold]{label}:[/bold] {len(items)} found")
74-
for item in items:
75-
icon = "[green]✓[/green]" if item.enabled else "[dim]○[/dim]"
76-
rprint(f" {icon} {item.name:<18} {detail_fn(item)}")
77-
else:
78-
rprint(f"\n[bold]{label}:[/bold] [dim]none[/dim]")
79-
80-
8162
def _print_banner() -> None:
8263
width = shutil.get_terminal_size().columns
8364
art_width = max(len(line) for line in BANNER_LINES)
@@ -174,54 +155,6 @@ def new(
174155
os.execvp(cmd[0], cmd)
175156

176157

177-
@app.command()
178-
def status() -> None:
179-
"""Show current configuration and validate setup."""
180-
config = _load_config()
181-
agent = config["agent"]
182-
command = agent["command"]
183-
args = agent.get("args", [])
184-
ralph_file = agent["ralph"]
185-
ralph_path = Path(ralph_file)
186-
187-
rprint("[bold]Configuration[/bold]")
188-
rprint(f" Command: [cyan]{command} {' '.join(args)}[/cyan]")
189-
rprint(f" Ralph: [cyan]{ralph_file}[/cyan]")
190-
191-
issues = []
192-
193-
if ralph_path.exists():
194-
size = len(ralph_path.read_text())
195-
rprint(f"\n[green]✓[/green] Ralph file exists ({size} chars)")
196-
else:
197-
issues.append("ralph")
198-
rprint(f"\n[red]✗[/red] Ralph file '{ralph_file}' not found")
199-
200-
if shutil.which(command):
201-
rprint(f"[green]✓[/green] Command '{command}' found on PATH")
202-
else:
203-
issues.append("command")
204-
rprint(f"[red]✗[/red] Command '{command}' not found on PATH")
205-
206-
checks = discover_checks()
207-
_print_primitives_section("Checks", checks,
208-
lambda c: str(c.script.name) if c.script else c.command or "?")
209-
210-
contexts = discover_contexts()
211-
_print_primitives_section("Contexts", contexts,
212-
lambda c: str(c.script.name) if c.script else c.command or "(static)")
213-
214-
ralphs = discover_ralphs()
215-
_print_primitives_section("Ralphs", ralphs,
216-
lambda p: p.description or "(no description)")
217-
218-
if issues:
219-
rprint("\n[red]Not ready.[/red] Fix the issues above before running.")
220-
raise typer.Exit(1)
221-
else:
222-
rprint("\n[green]Ready to run.[/green]")
223-
224-
225158
@app.command()
226159
def run(
227160
prompt: str | None = typer.Argument(None, help="Named ralph from .ralphify/ralphs/."),
@@ -256,6 +189,10 @@ def run(
256189
rprint(f"[red]Prompt file '{ralph_file_path}' not found.[/red]")
257190
raise typer.Exit(1)
258191

192+
if not shutil.which(command):
193+
rprint(f"[red]Agent command '{command}' not found on PATH.[/red]")
194+
raise typer.Exit(1)
195+
259196
if log_dir:
260197
rprint(f"[dim]Logging output to {log_dir}/[/dim]")
261198

src/ralphify/engine.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from __future__ import annotations
1515

16+
import os
1617
import time
1718
import traceback
1819
from datetime import datetime, timezone
@@ -52,26 +53,38 @@ def _resolve_ralph_dir(config: RunConfig) -> Path | None:
5253

5354

5455
def _discover_enabled_primitives(
55-
root: Path, ralph_dir: Path | None = None,
56+
root: Path,
57+
ralph_dir: Path | None = None,
58+
global_checks: list[str] | None = None,
59+
global_contexts: list[str] | None = None,
5660
) -> EnabledPrimitives:
5761
"""Discover all primitives and return only the enabled ones.
5862
63+
Global primitives are only included when explicitly requested via
64+
*global_checks* / *global_contexts* name lists. When ``None``, no
65+
globals are selected (the library model).
66+
5967
When *ralph_dir* is set, ralph-scoped primitives are merged with
60-
globals (local wins on name collisions). Enabled filtering happens
61-
**after** the merge so a disabled local primitive can suppress a
62-
global one with the same name.
63-
64-
This is the **single layer** responsible for enabled filtering.
65-
Downstream functions (``resolve_contexts``, ``run_all_contexts``,
66-
``run_all_checks``) trust that they receive only enabled primitives
67-
and do not re-filter.
68+
selected globals (local wins on name collisions). Enabled filtering
69+
happens **after** the merge so a disabled local primitive can
70+
suppress a global one with the same name.
6871
"""
6972
return EnabledPrimitives(
70-
checks=discover_enabled_checks(root, ralph_dir),
71-
contexts=discover_enabled_contexts(root, ralph_dir),
73+
checks=discover_enabled_checks(root, ralph_dir, global_names=global_checks),
74+
contexts=discover_enabled_contexts(root, ralph_dir, global_names=global_contexts),
7275
)
7376

7477

78+
def _validate_check_scripts(checks: list[Check]) -> None:
79+
"""Raise if any check uses a script that isn't executable."""
80+
for check in checks:
81+
if check.script and not os.access(check.script, os.X_OK):
82+
raise PermissionError(
83+
f"Check script not executable: '{check.script}'. "
84+
f"Run: chmod +x {check.script}"
85+
)
86+
87+
7588
class _BoundEmitter:
7689
"""Wraps an EventEmitter with a fixed run_id for concise emission.
7790
@@ -133,7 +146,11 @@ def _handle_loop_transitions(
133146
# immediately. When an explicit reload was requested (e.g. from the
134147
# UI), we still emit the PRIMITIVES_RELOADED event.
135148
explicit_reload = state.consume_reload_request()
136-
primitives = _discover_enabled_primitives(config.project_root, ralph_dir)
149+
primitives = _discover_enabled_primitives(
150+
config.project_root, ralph_dir,
151+
global_checks=config.global_checks,
152+
global_contexts=config.global_contexts,
153+
)
137154

138155
if explicit_reload:
139156
emit(EventType.PRIMITIVES_RELOADED, {
@@ -353,6 +370,7 @@ def run_loop(
353370
})
354371

355372
try:
373+
_validate_check_scripts(primitives.checks)
356374
while True:
357375
should_continue, primitives = _handle_loop_transitions(
358376
state, config, primitives, emit, ralph_dir,

0 commit comments

Comments
 (0)