1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Namespace {
10 #[default]
12 Decisions,
13 Patterns,
15 Learnings,
17 Context,
19 #[serde(alias = "techdebt", alias = "tech_debt")]
21 #[serde(rename = "tech-debt")]
22 TechDebt,
23 Blockers,
25 Progress,
27 Apis,
29 Config,
31 Security,
33 Performance,
35 Testing,
37 Help,
39 Prompts,
41}
42
43impl Namespace {
44 #[must_use]
46 pub const fn all() -> &'static [Self] {
47 &[
48 Self::Decisions,
49 Self::Patterns,
50 Self::Learnings,
51 Self::Context,
52 Self::TechDebt,
53 Self::Blockers,
54 Self::Progress,
55 Self::Apis,
56 Self::Config,
57 Self::Security,
58 Self::Performance,
59 Self::Testing,
60 Self::Help,
61 Self::Prompts,
62 ]
63 }
64
65 #[must_use]
67 pub const fn user_namespaces() -> &'static [Self] {
68 &[
69 Self::Decisions,
70 Self::Patterns,
71 Self::Learnings,
72 Self::Context,
73 Self::TechDebt,
74 Self::Blockers,
75 Self::Progress,
76 Self::Apis,
77 Self::Config,
78 Self::Security,
79 Self::Performance,
80 Self::Testing,
81 Self::Prompts,
82 ]
83 }
84
85 #[must_use]
87 pub const fn as_str(&self) -> &'static str {
88 match self {
89 Self::Decisions => "decisions",
90 Self::Patterns => "patterns",
91 Self::Learnings => "learnings",
92 Self::Context => "context",
93 Self::TechDebt => "tech-debt",
94 Self::Blockers => "blockers",
95 Self::Progress => "progress",
96 Self::Apis => "apis",
97 Self::Config => "config",
98 Self::Security => "security",
99 Self::Performance => "performance",
100 Self::Testing => "testing",
101 Self::Help => "help",
102 Self::Prompts => "prompts",
103 }
104 }
105
106 #[must_use]
108 pub const fn is_system(&self) -> bool {
109 matches!(self, Self::Help)
110 }
111
112 #[must_use]
114 pub fn parse(s: &str) -> Option<Self> {
115 match s.to_lowercase().as_str() {
116 "decisions" => Some(Self::Decisions),
117 "patterns" => Some(Self::Patterns),
118 "learnings" => Some(Self::Learnings),
119 "context" => Some(Self::Context),
120 "tech-debt" | "techdebt" | "tech_debt" => Some(Self::TechDebt),
121 "blockers" => Some(Self::Blockers),
122 "progress" => Some(Self::Progress),
123 "apis" => Some(Self::Apis),
124 "config" => Some(Self::Config),
125 "security" => Some(Self::Security),
126 "performance" => Some(Self::Performance),
127 "testing" => Some(Self::Testing),
128 "help" => Some(Self::Help),
129 "prompts" => Some(Self::Prompts),
130 _ => None,
131 }
132 }
133}
134
135impl fmt::Display for Namespace {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(f, "{}", self.as_str())
138 }
139}
140
141impl std::str::FromStr for Namespace {
142 type Err = String;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 Self::parse(s).ok_or_else(|| format!("unknown namespace: {s}"))
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
151pub struct Domain {
152 pub organization: Option<String>,
154 pub project: Option<String>,
156 pub repository: Option<String>,
158}
159
160impl Domain {
161 #[must_use]
163 pub const fn new() -> Self {
164 Self {
165 organization: None,
166 project: None,
167 repository: None,
168 }
169 }
170
171 #[must_use]
180 pub fn default_for_context() -> Self {
181 use crate::storage::index::is_in_git_repo;
182
183 if is_in_git_repo() {
184 Self::new()
186 } else {
187 Self::for_user()
189 }
190 }
191
192 #[must_use]
197 pub fn for_user() -> Self {
198 Self {
199 organization: None,
200 project: Some("user".to_string()),
201 repository: None,
202 }
203 }
204
205 #[must_use]
210 pub fn for_org() -> Self {
211 Self {
212 organization: Some("org".to_string()),
213 project: None,
214 repository: None,
215 }
216 }
217
218 #[must_use]
220 pub fn for_repository(org: impl Into<String>, repo: impl Into<String>) -> Self {
221 Self {
222 organization: Some(org.into()),
223 project: None,
224 repository: Some(repo.into()),
225 }
226 }
227
228 #[must_use]
230 pub const fn is_project_scoped(&self) -> bool {
231 self.organization.is_none() && self.project.is_none() && self.repository.is_none()
232 }
233
234 #[must_use]
236 pub fn is_user(&self) -> bool {
237 self.project.as_deref() == Some("user") && self.organization.is_none()
238 }
239
240 #[must_use]
246 pub fn to_scope_string(&self) -> String {
247 match (&self.organization, &self.repository) {
248 (Some(org), Some(repo)) => format!("{org}/{repo}"),
249 (Some(org), None) => format!("org/{org}"),
250 (None, Some(repo)) => repo.clone(),
251 (None, None) => "project".to_string(),
252 }
253 }
254}
255
256impl fmt::Display for Domain {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match (&self.organization, &self.project, &self.repository) {
259 (Some(org), Some(proj), Some(repo)) => write!(f, "{org}/{proj}/{repo}"),
260 (Some(org), None, Some(repo)) => write!(f, "{org}/{repo}"),
261 (Some(org), Some(proj), None) => write!(f, "{org}/{proj}"),
262 (Some(org), None, None) => write!(f, "{org}"),
263 (None, Some(proj), _) if proj == "user" => write!(f, "user"),
265 (None, Some(proj), _) => write!(f, "{proj}"),
266 (None, None, Some(repo)) => write!(f, "{repo}"),
267 (None, None, None) => write!(f, "project"),
269 }
270 }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
275pub enum MemoryStatus {
276 #[default]
278 Active,
279 Archived,
281 Superseded,
283 Pending,
285 Deleted,
287 Tombstoned,
289 Consolidated,
295}
296
297impl MemoryStatus {
298 #[must_use]
300 pub const fn as_str(&self) -> &'static str {
301 match self {
302 Self::Active => "active",
303 Self::Archived => "archived",
304 Self::Superseded => "superseded",
305 Self::Pending => "pending",
306 Self::Deleted => "deleted",
307 Self::Tombstoned => "tombstoned",
308 Self::Consolidated => "consolidated",
309 }
310 }
311}
312
313impl fmt::Display for MemoryStatus {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "{}", self.as_str())
316 }
317}