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