ainl_contracts/
feature.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7use crate::assertion::AssertionId;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct FeatureId(pub String);
13
14impl FeatureId {
15 pub fn as_str(&self) -> &str {
16 &self.0
17 }
18}
19
20impl From<String> for FeatureId {
21 fn from(s: String) -> Self {
22 Self(s)
23 }
24}
25
26impl From<&str> for FeatureId {
27 fn from(s: &str) -> Self {
28 Self(s.to_string())
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
34#[serde(rename_all = "snake_case")]
35pub enum FeatureStatus {
36 #[default]
37 Pending,
38 InProgress,
39 Completed,
40 Cancelled,
41 RolledBack,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct FeatureSnapshot {
47 pub repo_toplevel: PathBuf,
48 pub stash_sha: String,
49 pub head_sha: String,
50 pub taken_at: DateTime<Utc>,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde(tag = "kind", rename_all = "snake_case")]
56pub enum VerificationStep {
57 ShellCommand {
58 cmd: String,
59 #[serde(default)]
60 expected_exit_code: i32,
61 },
62 BrowserFlow {
63 url: String,
64 success_criteria: String,
65 },
66 AgentSupervise {
67 agent_id: String,
68 task: String,
69 success_criteria: String,
70 },
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct Feature {
76 pub feature_id: FeatureId,
77 pub description: String,
78 pub status: FeatureStatus,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub milestone: Option<String>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub skill_name: Option<String>,
83 #[serde(default)]
84 pub touches_files: Vec<String>,
85 #[serde(default)]
86 pub preconditions: Vec<FeatureId>,
87 #[serde(default)]
88 pub expected_behavior: Vec<String>,
89 #[serde(default)]
90 pub verification_steps: Vec<VerificationStep>,
91 #[serde(default)]
92 pub fulfills: Vec<AssertionId>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub snapshot: Option<FeatureSnapshot>,
95}