Skip to main content

aicx_parser/
types.rs

1//! Shared canonical types used by the intent engine.
2//!
3//! Vibecrafted with AI Agents by VetCoders (c)2026 VetCoders
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::hash::{Hash, Hasher};
8
9pub use crate::timeline::{FrameKind, Kind, RepoIdentity, SemanticSegment, SourceTier};
10
11// ── Intent Engine schema ─────────────────────────────────────────────
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum EntryType {
16    Intent,
17    Why,
18    Argue,
19    Decision,
20    Assumption,
21    Outcome,
22    Result,
23    Question,
24    Insight,
25}
26
27impl EntryType {
28    pub fn as_str(self) -> &'static str {
29        match self {
30            Self::Intent => "intent",
31            Self::Why => "why",
32            Self::Argue => "argue",
33            Self::Decision => "decision",
34            Self::Assumption => "assumption",
35            Self::Outcome => "outcome",
36            Self::Result => "result",
37            Self::Question => "question",
38            Self::Insight => "insight",
39        }
40    }
41
42    pub fn parse(s: &str) -> Option<Self> {
43        match s.to_ascii_lowercase().as_str() {
44            "intent" => Some(Self::Intent),
45            "why" => Some(Self::Why),
46            "argue" | "argument" | "debate" => Some(Self::Argue),
47            "decision" => Some(Self::Decision),
48            "assumption" | "hypothesis" => Some(Self::Assumption),
49            "outcome" => Some(Self::Outcome),
50            "result" => Some(Self::Result),
51            "question" => Some(Self::Question),
52            "insight" => Some(Self::Insight),
53            _ => None,
54        }
55    }
56}
57
58impl fmt::Display for EntryType {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.write_str(self.as_str())
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "lowercase")]
66pub enum EntryState {
67    Proposed,
68    Active,
69    Superseded,
70    Done,
71    Contradicted,
72}
73
74impl EntryState {
75    pub fn as_str(self) -> &'static str {
76        match self {
77            Self::Proposed => "proposed",
78            Self::Active => "active",
79            Self::Superseded => "superseded",
80            Self::Done => "done",
81            Self::Contradicted => "contradicted",
82        }
83    }
84}
85
86impl fmt::Display for EntryState {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.write_str(self.as_str())
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub enum LinkType {
95    DerivedFrom,
96    Supersedes,
97    Verifies,
98    Contradicts,
99    Supports,
100    ResultsIn,
101    Answers,
102    LinksTo,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct Link {
107    pub relation: LinkType,
108    pub target: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub confidence: Option<f32>,
111}
112
113impl Eq for Link {}
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct IntentEntry {
117    pub id: String,
118    pub entry_type: EntryType,
119    #[serde(rename = "status", alias = "state")]
120    pub state: EntryState,
121    pub title: String,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub body: Option<String>,
124    #[serde(default, skip_serializing_if = "Vec::is_empty")]
125    pub evidence: Vec<String>,
126    #[serde(default, skip_serializing_if = "Vec::is_empty")]
127    pub links: Vec<Link>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub superseded_by: Option<String>,
130    pub confidence: f32,
131    #[serde(default, skip_serializing_if = "Vec::is_empty")]
132    pub tags: Vec<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub project: Option<String>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub agent: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub session_id: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub timestamp: Option<String>,
141    pub date: String,
142    pub source_chunk: String,
143}
144
145impl Eq for IntentEntry {}
146
147impl IntentEntry {
148    pub fn stable_id(source_chunk: &str, byte_offset: usize, entry_type: EntryType) -> String {
149        let mut hasher = siphasher::sip::SipHasher13::new();
150        source_chunk.hash(&mut hasher);
151        byte_offset.hash(&mut hasher);
152        entry_type.as_str().hash(&mut hasher);
153        format!("{:016x}", hasher.finish())
154    }
155}