use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EntryType {
Decision,
Commit,
Constraint,
Lesson,
Plan,
Feature,
Stub,
Deferred,
BuilderNote,
TechDebt,
SessionSummary,
}
impl rusqlite::ToSql for EntryType {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
let s = serde_json::to_string(self)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
let s = s.trim_matches('"').to_owned();
Ok(rusqlite::types::ToSqlOutput::from(s))
}
}
impl rusqlite::types::FromSql for EntryType {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
let s = value.as_str()?;
serde_json::from_str(&format!("\"{s}\""))
.map_err(|e| rusqlite::types::FromSqlError::Other(Box::new(e)))
}
}
impl EntryType {
#[must_use]
pub const fn allowed_roles(self) -> &'static [&'static str] {
match self {
Self::Decision | Self::Constraint | Self::Lesson | Self::Plan | Self::Feature => {
&["architect"]
}
Self::Commit | Self::Stub | Self::BuilderNote => &["builder"],
Self::Deferred | Self::TechDebt | Self::SessionSummary => &["architect", "builder"],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanData {
pub scope: String,
pub tier: String,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitData {
pub hash: String,
pub files: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintData {
pub source: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LessonData {
pub root_cause: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureData {
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StubData {
pub phase_number: u32,
pub contract: String,
pub module: String,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeferredData {
pub reason: String,
pub target_phase: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuilderNoteData {
pub note_type: String,
pub step_ref: String,
pub plan_ref: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechDebtData {
pub severity: String,
pub origin_phase: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionSummaryData {
pub session_date: String,
pub entries_created: Option<u32>,
pub entries_updated: Option<u32>,
pub entries_deleted: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ReflectFocus {
Stale,
Dead,
Hot,
Orphaned,
Contradictions,
CoverageGaps,
Lonely,
#[default]
All,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum MemoryState {
#[default]
Empty,
Nascent,
Active,
Mature,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReflectCriteria {
#[serde(default)]
pub focus: ReflectFocus,
pub stale_days: Option<u32>,
pub min_access_count: Option<u32>,
pub limit: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectFinding {
pub category: String,
pub entry_id: String,
pub entry_type: String,
pub title: String,
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReflectSummary {
pub total: usize,
pub stale: usize,
pub dead: usize,
pub hot: usize,
pub orphaned: usize,
pub contradictions: usize,
pub coverage_gaps: usize,
pub lonely: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReflectReport {
pub state: MemoryState,
pub findings: Vec<ReflectFinding>,
pub summary: ReflectSummary,
pub guidance: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimilarEntry {
pub id: String,
pub title: String,
pub entry_type: String,
pub score: f64,
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn reflect_focus_serde_new_variants() {
assert_eq!(
serde_json::to_string(&ReflectFocus::CoverageGaps).unwrap(),
"\"coverage_gaps\""
);
assert_eq!(serde_json::to_string(&ReflectFocus::Lonely).unwrap(), "\"lonely\"");
}
#[test]
fn reflect_summary_includes_new_fields() {
let s = ReflectSummary::default();
assert_eq!(s.coverage_gaps, 0);
assert_eq!(s.lonely, 0);
}
}