1pub mod briefing;
9pub mod entities;
10pub mod events;
11pub mod import;
12pub mod memory;
13pub mod memory_type;
14pub mod narrative;
15pub mod onboarding;
16pub mod pages;
17pub mod requests;
18pub mod responses;
19pub mod sources;
20pub mod system_info;
21pub mod working_memory;
22
23pub use briefing::{BriefingResponse, ContradictionItem};
25pub use entities::{
26 Entity, EntityDetail, EntitySearchResult, EntitySuggestion, Observation, RecentRelation,
27 Relation, RelationWithEntity,
28};
29pub use memory::{
30 ActivityBadge, ActivityKind, AgentActivityRow, AgentConnection, DomainInfo,
31 EnrichmentStatusResponse, EnrichmentStepStatus, HomeStats, IndexedFileInfo, MemoryItem,
32 MemoryStats, MemoryVersionItem, PageChange, PageChangeKind, Profile, RecentActivityItem,
33 RejectionRecord, RetrievalEvent, SearchResult, SessionSnapshot, SnapshotCapture,
34 SnapshotCaptureWithContent, Space, TopMemory, TypeBreakdown,
35};
36pub use memory_type::{MEMORY_TYPE_CAPTURE_DESCRIPTION, MEMORY_TYPE_FILTER_DESCRIPTION};
37pub use narrative::NarrativeResponse;
38pub use pages::Page;
39pub use responses::{ExportStats, MemoryDetail, PendingRevision};
40pub use sources::{MemoryType, RawDocument, SourceType, StabilityTier, SyncStatus};
41
42use serde::{Deserialize, Serialize};
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ChangelogEntry {
47 pub version: i64,
48 pub at: i64,
50 pub delta: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub source_agent: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub incoming_source_id: Option<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PageSource {
64 pub page_id: String,
65 pub memory_source_id: String,
66 pub linked_at: i64,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub link_reason: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct PageSourceWithMemory {
75 pub source: PageSource,
76 pub memory: Option<crate::memory::MemoryItem>,
77}
78
79pub fn version() -> &'static str {
81 env!("CARGO_PKG_VERSION")
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn version_is_set() {
90 assert!(!version().is_empty());
91 }
92
93 #[test]
94 fn memory_type_roundtrip() {
95 for variant in [
96 MemoryType::Identity,
97 MemoryType::Preference,
98 MemoryType::Decision,
99 MemoryType::Lesson,
100 MemoryType::Gotcha,
101 MemoryType::Fact,
102 ] {
103 let s = variant.to_string();
104 let parsed: MemoryType = s.parse().unwrap();
105 assert_eq!(parsed, variant);
106 }
107 }
108
109 #[test]
110 fn search_result_serializes() {
111 let sr = SearchResult {
112 id: "1".into(),
113 content: "test".into(),
114 source: "memory".into(),
115 source_id: "mem_abc".into(),
116 title: "Test".into(),
117 url: None,
118 chunk_index: 0,
119 last_modified: 1000,
120 score: 0.9,
121 chunk_type: None,
122 language: None,
123 semantic_unit: None,
124 memory_type: Some("fact".into()),
125 domain: None,
126 source_agent: None,
127 confidence: Some(0.8),
128 confirmed: Some(true),
129 stability: None,
130 supersedes: None,
131 summary: None,
132 entity_id: None,
133 entity_name: None,
134 quality: None,
135 is_archived: false,
136 is_recap: false,
137 structured_fields: None,
138 retrieval_cue: None,
139 source_text: None,
140 raw_score: 0.0,
141 };
142 let json = serde_json::to_string(&sr).unwrap();
143 assert!(json.contains("mem_abc"));
144 assert!(!json.contains("entity_id"));
146 }
147
148 #[test]
149 fn raw_document_default() {
150 let doc = RawDocument::default();
151 assert_eq!(doc.enrichment_status, "raw");
152 assert_eq!(doc.supersede_mode, "hide");
153 assert!(!doc.pending_revision);
154 assert!(!doc.is_recap);
155 }
156
157 #[test]
158 fn stability_tier_mapping() {
159 use sources::stability_tier;
160 assert_eq!(stability_tier(Some("identity")), StabilityTier::Protected);
161 assert_eq!(stability_tier(Some("preference")), StabilityTier::Protected);
162 assert_eq!(stability_tier(Some("fact")), StabilityTier::Standard);
163 assert_eq!(stability_tier(Some("decision")), StabilityTier::Standard);
164 assert_eq!(stability_tier(Some("lesson")), StabilityTier::Standard);
165 assert_eq!(stability_tier(Some("gotcha")), StabilityTier::Standard);
166 assert_eq!(stability_tier(Some("goal")), StabilityTier::Protected);
169 assert_eq!(stability_tier(None), StabilityTier::Ephemeral);
170 }
171}
172
173#[cfg(test)]
174mod retrieval_event_tests {
175 use super::*;
176
177 #[test]
178 fn retrieval_event_roundtrips() {
179 let e = RetrievalEvent {
180 timestamp_ms: 1_700_000_000_000,
181 agent_name: "claude-code".into(),
182 query: Some("origin positioning".into()),
183 page_titles: vec!["Origin positioning".into(), "Daemon architecture".into()],
184 page_ids: vec![],
185 memory_snippets: vec![],
186 };
187 let s = serde_json::to_string(&e).unwrap();
188 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
189 assert_eq!(back.agent_name, "claude-code");
190 assert_eq!(back.page_titles.len(), 2);
191 assert_eq!(back.query.as_deref(), Some("origin positioning"));
192 }
193
194 #[test]
195 fn retrieval_event_omits_none_query() {
196 let e = RetrievalEvent {
197 timestamp_ms: 1_700_000_000_000,
198 agent_name: "claude-code".into(),
199 query: None,
200 page_titles: vec![],
201 page_ids: vec![],
202 memory_snippets: vec![],
203 };
204 let s = serde_json::to_string(&e).unwrap();
205 assert!(
206 !s.contains("\"query\""),
207 "expected None query to be skipped on the wire, got: {s}",
208 );
209 let back: RetrievalEvent = serde_json::from_str(&s).unwrap();
210 assert_eq!(back.query, None);
211 assert!(back.page_titles.is_empty());
212 }
213
214 #[test]
215 fn page_change_roundtrips() {
216 let c = PageChange {
217 page_id: "page_abc".into(),
218 title: "Wiki-style prose pages".into(),
219 change_kind: PageChangeKind::Revised,
220 changed_at_ms: 1_700_000_000_000,
221 };
222 let s = serde_json::to_string(&c).unwrap();
223 assert!(
224 s.contains("\"change_kind\":\"revised\""),
225 "expected snake_case change_kind on the wire, got: {s}",
226 );
227 let back: PageChange = serde_json::from_str(&s).unwrap();
228 assert_eq!(back.change_kind, PageChangeKind::Revised);
229 }
230}