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