1use memnite_core::Conflict;
2use memnite_core::Relation;
3use memnite_core::Scope;
4
5#[derive(Clone, Debug)]
7pub struct AnchorSpec {
8 pub path: String,
9 pub symbol: Option<String>,
10 pub line_start: u32,
11 pub line_end: u32,
12}
13
14#[derive(Clone, Debug)]
16pub struct AddSpec {
17 pub title: String,
18 pub body: String,
19 pub mem_type: String,
20 pub scope: memnite_core::Scope,
21 pub project: String,
22 pub topic_key: Option<String>,
23 pub anchors: Vec<AnchorSpec>,
24 pub tags: Vec<String>,
25}
26
27#[derive(Clone, Debug)]
30pub struct RelationSpec {
31 pub relation: Relation,
32 pub confidence: f32,
33 pub reason: String,
34 pub judged_by: String,
35}
36
37#[derive(Clone, Debug)]
40pub struct EventCtx {
41 pub ts: String,
42 pub engine: String,
43 pub machine: String,
44}
45
46#[derive(Clone, Debug, Default, PartialEq, Eq)]
48pub struct CheckSummary {
49 pub stable: usize,
50 pub stale: usize,
51 pub unchanged: usize,
52}
53
54#[derive(Clone, Debug, Default)]
59pub struct UpdatePatch {
60 pub title: Option<String>,
61 pub body: Option<String>,
62 pub mem_type: Option<String>,
63 pub scope: Option<memnite_core::Scope>,
64 pub project: Option<String>,
65 pub topic_key: Option<String>,
66 pub anchors: Option<Vec<AnchorSpec>>,
67 pub tags: Option<Vec<String>>,
68}
69
70#[derive(Debug, Default)]
72pub struct SearchQuery {
73 pub text: String,
74 pub mem_type: Option<String>,
75 pub project: Option<String>,
76 pub scope: Option<Scope>,
77 pub match_any: bool,
79}
80
81impl From<&str> for SearchQuery {
82 fn from(text: &str) -> Self {
83 SearchQuery {
84 text: text.to_string(),
85 ..Default::default()
86 }
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
94pub struct DoctorReport {
95 pub log_count: usize,
96 pub proj_count: usize,
97 pub mismatches: Vec<String>,
98 pub cursor_ok: bool,
99 pub conflicts: Vec<Conflict>,
100}
101
102#[derive(Clone, Debug, Default, PartialEq, Eq)]
105pub struct ImportSummary {
106 pub imported: usize,
107 pub duplicates: usize,
108 pub corrupt: usize,
109}
110
111#[derive(Clone, Debug, Default, PartialEq)]
116pub struct ScanSummary {
117 pub pairs: usize,
118 pub asserted: usize,
119 pub not_conflict: usize,
120 pub errors: usize,
121 pub first_error: Option<String>,
122}
123
124use crate::error::CliError;
125
126pub fn parse_relation(s: &str) -> Result<Relation, CliError> {
128 Relation::from_label(s).ok_or_else(|| CliError::Invalid(format!("unknown relation {s:?}")))
129}
130
131pub fn parse_scope(s: &str) -> Result<memnite_core::Scope, CliError> {
133 match s {
134 "agent" => Ok(memnite_core::Scope::Agent),
135 "repo" => Ok(memnite_core::Scope::Repo),
136 "global" => Ok(memnite_core::Scope::Global),
137 other => Err(CliError::Invalid(format!(
138 "scope must be 'agent', 'repo', or 'global', got {other:?}"
139 ))),
140 }
141}
142
143pub fn parse_anchor(s: &str) -> Result<AnchorSpec, CliError> {
145 let parts: Vec<&str> = s.splitn(4, ':').collect();
146 if parts.len() < 3 {
147 return Err(CliError::Invalid(format!(
148 "anchor must be path:start:end[:symbol], got {s:?}"
149 )));
150 }
151 let line_start = parts[1]
152 .parse::<u32>()
153 .map_err(|_| CliError::Invalid(format!("anchor start not a number: {:?}", parts[1])))?;
154 let line_end = parts[2]
155 .parse::<u32>()
156 .map_err(|_| CliError::Invalid(format!("anchor end not a number: {:?}", parts[2])))?;
157 Ok(AnchorSpec {
158 path: parts[0].to_string(),
159 symbol: parts.get(3).map(|s| s.to_string()),
160 line_start,
161 line_end,
162 })
163}