rae 0.1.2

Renderer-neutral widget, layout, state, and GLSL shader primitives for agentic Rust desktop tools.
Documentation
use crate::TaskStatus;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum AgentPhase {
    #[default]
    UnderstandGoal,
    InspectRepository,
    BuildContext,
    CreateEditPlan,
    ExecuteChanges,
    ValidateChanges,
    RepairFailures,
    Revalidate,
    CompleteTask,
}

impl AgentPhase {
    pub fn label(self) -> &'static str {
        match self {
            Self::UnderstandGoal => "Understand Goal",
            Self::InspectRepository => "Inspect Repository",
            Self::BuildContext => "Build Context",
            Self::CreateEditPlan => "Create Edit Plan",
            Self::ExecuteChanges => "Execute Changes",
            Self::ValidateChanges => "Validate Changes",
            Self::RepairFailures => "Repair Failures",
            Self::Revalidate => "Revalidate",
            Self::CompleteTask => "Complete Task",
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AgentTask {
    pub goal: String,
    pub phase: AgentPhase,
    pub decisions: Vec<String>,
    pub blockers: Vec<String>,
    pub next_steps: Vec<String>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EditPlan {
    pub objective: String,
    pub steps: Vec<String>,
    pub touched_paths: Vec<String>,
    pub validation_commands: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KanbanCard {
    pub id: u64,
    pub title: String,
    pub status: TaskStatus,
    pub context_refs: Vec<String>,
    pub agent_notes: Vec<String>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KanbanBoard {
    pub cards: Vec<KanbanCard>,
}

impl KanbanBoard {
    pub fn cards_for(&self, status: TaskStatus) -> Vec<&KanbanCard> {
        self.cards
            .iter()
            .filter(|card| card.status == status)
            .collect()
    }

    pub fn count_for(&self, status: TaskStatus) -> usize {
        self.cards
            .iter()
            .filter(|card| card.status == status)
            .count()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum RunStatus {
    Pending,
    Running,
    Passed,
    Failed,
    Blocked,
}

impl RunStatus {
    pub fn label(self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Running => "running",
            Self::Passed => "passed",
            Self::Failed => "failed",
            Self::Blocked => "blocked",
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RunRecord {
    pub profile: String,
    pub command: String,
    pub status: RunStatus,
    pub exit_code: Option<i32>,
    pub output_preview: String,
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ValidationProfile {
    pub name: String,
    pub command: String,
    pub detail: String,
    pub destructive: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompletionGate {
    Ready,
    NeedsValidation,
    Blocked,
}

pub fn default_validation_profiles() -> Vec<ValidationProfile> {
    [
        ("fmt", "cargo fmt --all --check", "formatting gate", false),
        (
            "build",
            "cargo check --workspace",
            "fast Rust compile gate",
            false,
        ),
        ("test", "cargo test --workspace", "workspace tests", false),
        (
            "scan",
            "aev --thinking none scan",
            "reference workspace scan",
            false,
        ),
        (
            "export",
            "aev export --format markdown",
            "session export smoke",
            false,
        ),
        (
            "full",
            "cargo fmt --all --check && cargo test --workspace",
            "release-quality local gate",
            false,
        ),
    ]
    .into_iter()
    .map(|(name, command, detail, destructive)| ValidationProfile {
        name: name.to_string(),
        command: command.to_string(),
        detail: detail.to_string(),
        destructive,
    })
    .collect()
}

pub fn completion_gate(had_writes: bool, latest_run: Option<&RunRecord>) -> CompletionGate {
    if !had_writes {
        return CompletionGate::Ready;
    }
    match latest_run.map(|run| run.status) {
        Some(RunStatus::Passed) => CompletionGate::Ready,
        Some(RunStatus::Failed | RunStatus::Blocked) => CompletionGate::Blocked,
        Some(RunStatus::Pending | RunStatus::Running) | None => CompletionGate::NeedsValidation,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn writes_require_validation_gate() {
        assert_eq!(completion_gate(true, None), CompletionGate::NeedsValidation);
        assert_eq!(completion_gate(false, None), CompletionGate::Ready);
    }

    #[test]
    fn board_counts_by_status() {
        let board = KanbanBoard {
            cards: vec![KanbanCard {
                id: 1,
                title: "ship shader deck".to_string(),
                status: TaskStatus::Doing,
                context_refs: Vec::new(),
                agent_notes: Vec::new(),
            }],
        };

        assert_eq!(board.count_for(TaskStatus::Doing), 1);
        assert_eq!(board.count_for(TaskStatus::Done), 0);
    }
}