collet 0.1.1

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
//! EvolutionHistory — query facade over observations and workspace versions.
//!
//! Engines use this to look back at past cycles: score curves, collected
//! observations, workspace diffs, and file contents at earlier versions.

use anyhow::Result;

use super::observer::Observer;
use super::types::CycleRecord;
use super::versioning::VersionControl;

/// Unified query interface over observations and workspace versions.
///
/// The evolution loop populates this every cycle.  Engines read from it
/// in their `step()` implementation.
pub struct EvolutionHistory {
    observer: Observer,
    versioning: VersionControl,
    cycle_records: Vec<CycleRecord>,
}

impl EvolutionHistory {
    pub fn new(observer: Observer, versioning: VersionControl) -> Self {
        Self {
            observer,
            versioning,
            cycle_records: Vec::new(),
        }
    }

    // -- Cycle records ----------------------------------------------------

    /// Record a completed cycle (called by the loop).
    pub fn record_cycle(&mut self, record: CycleRecord) {
        self.cycle_records.push(record);
    }

    /// All cycle records so far.
    pub fn cycles(&self) -> &[CycleRecord] {
        &self.cycle_records
    }

    /// The latest cycle number (0 if none).
    pub fn latest_cycle(&self) -> u32 {
        self.cycle_records.last().map(|r| r.cycle).unwrap_or(0)
    }

    // -- Observation queries ----------------------------------------------

    /// Return recent observation records.
    pub fn get_observations(
        &self,
        last_n_cycles: usize,
        only_failures: bool,
    ) -> Result<Vec<serde_json::Value>> {
        let mut records = self.observer.get_recent_logs(last_n_cycles)?;
        if only_failures {
            records.retain(|r| !r.get("success").and_then(|v| v.as_bool()).unwrap_or(false));
        }
        Ok(records)
    }

    /// Aggregate stats across all observations.
    pub fn get_summary_stats(&self) -> Result<serde_json::Value> {
        self.observer.get_summary_stats()
    }

    // -- Score queries ----------------------------------------------------

    /// Return the score from each cycle, in order.
    pub fn get_score_curve(&self) -> Vec<f64> {
        self.cycle_records.iter().map(|r| r.score).collect()
    }

    // -- Workspace version queries ----------------------------------------

    /// Git diff between two version labels (e.g. `evo-2`, `evo-3`).
    pub fn get_workspace_diff(&self, from_label: &str, to_label: &str) -> Result<String> {
        self.versioning.get_diff(from_label, to_label)
    }

    /// Read a workspace file as it existed at `version_label`.
    pub fn read_file_at(&self, version_label: &str, path: &str) -> Result<String> {
        self.versioning.show_file_at(version_label, path)
    }

    /// List all `evo-*` tags, newest first.
    pub fn list_versions(&self) -> Result<Vec<String>> {
        self.versioning.list_tags()
    }

    /// Git log (oneline) for the workspace.
    pub fn get_version_log(&self, n: usize) -> Result<String> {
        self.versioning.get_log(n)
    }

    // -- Mutable access for the loop (observer) ---------------------------

    /// Get a mutable reference to the underlying observer for collecting.
    pub fn observer_mut(&mut self) -> &mut Observer {
        &mut self.observer
    }

    /// Get a reference to the underlying version control for committing.
    pub fn versioning(&self) -> &VersionControl {
        &self.versioning
    }
}