use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::assertion::AssertionId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct FeatureId(pub String);
impl FeatureId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for FeatureId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for FeatureId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum FeatureStatus {
#[default]
Pending,
InProgress,
Completed,
Cancelled,
RolledBack,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeatureSnapshot {
pub repo_toplevel: PathBuf,
pub stash_sha: String,
pub head_sha: String,
pub taken_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum VerificationStep {
ShellCommand {
cmd: String,
#[serde(default)]
expected_exit_code: i32,
},
BrowserFlow {
url: String,
success_criteria: String,
},
AgentSupervise {
agent_id: String,
task: String,
success_criteria: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Feature {
pub feature_id: FeatureId,
pub description: String,
pub status: FeatureStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub milestone: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skill_name: Option<String>,
#[serde(default)]
pub touches_files: Vec<String>,
#[serde(default)]
pub preconditions: Vec<FeatureId>,
#[serde(default)]
pub expected_behavior: Vec<String>,
#[serde(default)]
pub verification_steps: Vec<VerificationStep>,
#[serde(default)]
pub fulfills: Vec<AssertionId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snapshot: Option<FeatureSnapshot>,
}