@@ -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) ]
83171mod 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}
0 commit comments