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 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}