1use 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#[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}