diff --git a/crates/maestro-daemon/src/session.rs b/crates/maestro-daemon/src/session.rs index 7ae544c..ac54fb1 100644 --- a/crates/maestro-daemon/src/session.rs +++ b/crates/maestro-daemon/src/session.rs @@ -169,6 +169,16 @@ impl SessionManager { .unwrap_or_default() } + /// Every deduped recent dir, newest-first, without the picker's result + /// cap (#54): project enrichment folds the whole history. + pub fn all_recent_dirs(&self) -> Vec { + self.inner + .spawn_history + .as_ref() + .map(|h| h.all_recent_dirs()) + .unwrap_or_default() + } + /// Subscribe to session deltas. Each subscriber gets its own lag domain — /// a slow client makes only itself resync. pub fn events(&self) -> broadcast::Receiver { diff --git a/crates/maestro-daemon/src/spawn_history.rs b/crates/maestro-daemon/src/spawn_history.rs index 668bca4..b0aec59 100644 --- a/crates/maestro-daemon/src/spawn_history.rs +++ b/crates/maestro-daemon/src/spawn_history.rs @@ -143,11 +143,23 @@ impl SpawnHistory { /// Recent working dirs, newest use first, deduped by `cwd`, within the /// recency cap, capped at `max_results`. Empty when disabled or unreadable. pub fn recent_dirs(&self) -> Vec { - self.recent_dirs_as_of(Utc::now()) + self.recent_dirs_capped(Utc::now(), self.max_results) + } + + /// Every deduped recent dir, newest-first, without the picker's result + /// cap (#54): project enrichment folds the whole history, not its first + /// page, so a project spawned in long ago still reads as fresh. + pub fn all_recent_dirs(&self) -> Vec { + self.recent_dirs_capped(Utc::now(), usize::MAX) } /// [`recent_dirs`](Self::recent_dirs) with an explicit clock for testing. + #[cfg(test)] fn recent_dirs_as_of(&self, now: DateTime) -> Vec { + self.recent_dirs_capped(now, self.max_results) + } + + fn recent_dirs_capped(&self, now: DateTime, max_results: usize) -> Vec { if !self.is_enabled() { return Vec::new(); } @@ -168,7 +180,7 @@ impl SpawnHistory { last_command: rec.command, last_used: rec.at, }); - if out.len() >= self.max_results { + if out.len() >= max_results { break; } } @@ -362,4 +374,28 @@ mod tests { assert_eq!(recents[0].cwd, PathBuf::from("/d4")); assert_eq!(recents[1].cwd, PathBuf::from("/d3")); } + + /// Project enrichment (#54) folds the whole log, not just the picker's + /// first page. + #[test] + fn all_recent_dirs_skips_the_result_cap() { + let tmp = tempfile::tempdir().unwrap(); + let mut h = SpawnHistory::new(tmp.path().join("h.jsonl"), 30); + h.max_results = 2; + // Recent enough to survive the 30-day cap against the real clock. + let base = Utc::now() - Duration::hours(1); + for i in 0..5 { + h.append(&rec( + &format!("/d{i}"), + "claude", + base + Duration::minutes(i), + )) + .unwrap(); + } + assert_eq!(h.recent_dirs().len(), 2); + let all = h.all_recent_dirs(); + assert_eq!(all.len(), 5); + assert_eq!(all[0].cwd, PathBuf::from("/d4")); + assert_eq!(all[4].cwd, PathBuf::from("/d0")); + } }