Skip to content

Commit 98f4028

Browse files
rookopenclawclaude
andcommitted
feat(cli): add ralph clean --events
Removes event run history under .ralph/ (events.jsonl, events-*.jsonl, and the current-events marker) without touching specs, loops registry, or other .ralph state. Mirrors the --diagnostics UX: nothing-to-clean message, dry-run listing, and a success summary of removed files. Closes #350 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ee9fa67 commit 98f4028

3 files changed

Lines changed: 157 additions & 2 deletions

File tree

crates/ralph-cli/src/lib.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,94 @@ pub fn clean_diagnostics(workspace_root: &Path, use_colors: bool, dry_run: bool)
7979
Ok(())
8080
}
8181

82+
/// Collect event run-history artifacts under `.ralph/`: `events.jsonl`,
83+
/// `events-*.jsonl`, and the `current-events` marker.
84+
fn event_artifacts(workspace_root: &Path) -> Vec<std::path::PathBuf> {
85+
let ralph_dir = workspace_root.join(".ralph");
86+
let mut found = Vec::new();
87+
88+
let marker = ralph_dir.join("current-events");
89+
if marker.exists() {
90+
found.push(marker);
91+
}
92+
93+
if let Ok(entries) = fs::read_dir(&ralph_dir) {
94+
for entry in entries.flatten() {
95+
let name = entry.file_name();
96+
let name = name.to_string_lossy();
97+
if name.ends_with(".jsonl") && (name == "events.jsonl" || name.starts_with("events-")) {
98+
found.push(entry.path());
99+
}
100+
}
101+
}
102+
103+
found.sort();
104+
found
105+
}
106+
107+
/// Clean event run history (`events*.jsonl` + `current-events` marker) from `.ralph/`
108+
pub fn clean_events(workspace_root: &Path, use_colors: bool, dry_run: bool) -> Result<()> {
109+
let targets = event_artifacts(workspace_root);
110+
111+
if targets.is_empty() {
112+
if use_colors {
113+
println!(
114+
"{}Nothing to clean:{} No event files found in '{}'",
115+
colors::DIM,
116+
colors::RESET,
117+
workspace_root.join(".ralph").display()
118+
);
119+
} else {
120+
println!(
121+
"Nothing to clean: No event files found in '{}'",
122+
workspace_root.join(".ralph").display()
123+
);
124+
}
125+
return Ok(());
126+
}
127+
128+
if dry_run {
129+
if use_colors {
130+
println!(
131+
"{}Dry run mode:{} Would delete event files:",
132+
colors::CYAN,
133+
colors::RESET
134+
);
135+
} else {
136+
println!("Dry run mode: Would delete event files:");
137+
}
138+
for path in &targets {
139+
println!(" {}", path.display());
140+
}
141+
return Ok(());
142+
}
143+
144+
for path in &targets {
145+
fs::remove_file(path).with_context(|| {
146+
format!(
147+
"Failed to delete '{}'. Check permissions and try again.",
148+
path.display()
149+
)
150+
})?;
151+
}
152+
153+
if use_colors {
154+
println!(
155+
"{}✓{} Cleaned: Deleted {} event file(s)",
156+
colors::GREEN,
157+
colors::RESET,
158+
targets.len()
159+
);
160+
} else {
161+
println!("Cleaned: Deleted {} event file(s)", targets.len());
162+
}
163+
for path in &targets {
164+
println!(" {}", path.display());
165+
}
166+
167+
Ok(())
168+
}
169+
82170
#[cfg(test)]
83171
mod tests {
84172
use super::*;
@@ -112,4 +200,56 @@ mod tests {
112200
clean_diagnostics(temp_dir.path(), false, false).expect("clean diagnostics");
113201
assert!(!diagnostics_dir.exists());
114202
}
203+
204+
/// Creates a `.ralph/` with event artifacts plus non-event files that must survive.
205+
fn events_fixture() -> tempfile::TempDir {
206+
let temp_dir = tempfile::tempdir().expect("temp dir");
207+
let ralph_dir = temp_dir.path().join(".ralph");
208+
std::fs::create_dir_all(ralph_dir.join("specs")).expect("create .ralph");
209+
for name in [
210+
"events.jsonl",
211+
"events-20260101-000000.jsonl",
212+
"current-events",
213+
"loops.json",
214+
"merge-queue.jsonl",
215+
"events-notes.md",
216+
] {
217+
std::fs::write(ralph_dir.join(name), "x").expect("write fixture");
218+
}
219+
std::fs::write(ralph_dir.join("specs/plan.md"), "x").expect("write spec");
220+
temp_dir
221+
}
222+
223+
#[test]
224+
fn clean_events_no_files_is_ok() {
225+
let temp_dir = tempfile::tempdir().expect("temp dir");
226+
assert!(clean_events(temp_dir.path(), false, false).is_ok());
227+
}
228+
229+
#[test]
230+
fn clean_events_dry_run_keeps_files() {
231+
let temp_dir = events_fixture();
232+
clean_events(temp_dir.path(), false, true).expect("dry run");
233+
assert!(temp_dir.path().join(".ralph/events.jsonl").exists());
234+
assert!(temp_dir.path().join(".ralph/current-events").exists());
235+
}
236+
237+
#[test]
238+
fn clean_events_deletes_only_event_artifacts() {
239+
let temp_dir = events_fixture();
240+
clean_events(temp_dir.path(), false, false).expect("clean events");
241+
242+
let ralph_dir = temp_dir.path().join(".ralph");
243+
for gone in [
244+
"events.jsonl",
245+
"events-20260101-000000.jsonl",
246+
"current-events",
247+
] {
248+
assert!(!ralph_dir.join(gone).exists(), "{gone} should be deleted");
249+
}
250+
for kept in ["loops.json", "merge-queue.jsonl", "events-notes.md"] {
251+
assert!(ralph_dir.join(kept).exists(), "{kept} should survive");
252+
}
253+
assert!(ralph_dir.join("specs/plan.md").exists());
254+
}
115255
}

crates/ralph-cli/src/main.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,11 @@ struct CleanArgs {
918918
/// Clean diagnostic logs instead of `.ralph/` directory
919919
#[arg(long)]
920920
diagnostics: bool,
921+
922+
/// Clean event run history (`.ralph/events*.jsonl` + `current-events` marker)
923+
/// instead of the agent directory
924+
#[arg(long, conflicts_with = "diagnostics")]
925+
events: bool,
921926
}
922927

923928
/// Arguments for the emit subcommand.
@@ -2413,6 +2418,12 @@ fn clean_command(
24132418
return ralph_cli::clean_diagnostics(&workspace_root, use_colors, args.dry_run);
24142419
}
24152420

2421+
// If --events flag is set, clean event run history only
2422+
if args.events {
2423+
let workspace_root = std::env::current_dir().context("Failed to get current directory")?;
2424+
return ralph_cli::clean_events(&workspace_root, use_colors, args.dry_run);
2425+
}
2426+
24162427
// Load config with overrides applied
24172428
let config = load_config_with_overrides(config_sources)?;
24182429

docs/guide/cli-reference.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ ralph emit <TOPIC> [PAYLOAD] [OPTIONS]
262262

263263
### ralph clean
264264

265-
Clean `.ralph/agent` scratchpad and memory state.
265+
By default, deletes the whole `.ralph/agent` directory — scratchpad, `memories.md`, and
266+
`tasks.jsonl` included. Use `--diagnostics` or `--events` to target run artifacts instead.
266267

267268
```bash
268269
ralph clean [OPTIONS]
@@ -272,9 +273,12 @@ ralph clean [OPTIONS]
272273

273274
| Option | Description |
274275
|--------|-------------|
275-
| `--diagnostics` | Clean diagnostics directory |
276+
| `--diagnostics` | Clean `.ralph/diagnostics` instead of the agent directory |
277+
| `--events` | Clean event run history: `.ralph/events.jsonl`, `.ralph/events-*.jsonl`, and the `.ralph/current-events` marker |
276278
| `--dry-run` | Preview deletions |
277279

280+
`--diagnostics` and `--events` are mutually exclusive; run the command twice to clear both.
281+
278282
### ralph loops
279283

280284
Manage parallel loops and worktree loop lifecycle.

0 commit comments

Comments
 (0)