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);
}
}