mempal 0.6.0

Project memory for coding agents. Single binary, hybrid search, knowledge graph.
Documentation
use std::fmt::{Display, Formatter};

use serde::{Deserialize, Serialize};
use thiserror::Error;

use super::parse::parse_document;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AaakMeta {
    pub wing: String,
    pub room: String,
    pub date: String,
    pub source: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AaakHeader {
    pub version: u8,
    pub wing: String,
    pub room: String,
    pub date: String,
    pub source: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Zettel {
    pub id: usize,
    pub entities: Vec<String>,
    pub topics: Vec<String>,
    pub quote: String,
    pub weight: u8,
    pub emotions: Vec<String>,
    pub flags: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tunnel {
    pub left: usize,
    pub right: usize,
    pub label: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArcLine {
    pub emotions: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AaakLine {
    Zettel(Zettel),
    Tunnel(Tunnel),
    Arc(ArcLine),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AaakDocument {
    pub header: AaakHeader,
    pub body: Vec<AaakLine>,
    pub zettels: Vec<Zettel>,
}

impl AaakDocument {
    pub fn parse(input: &str) -> Result<Self, ParseError> {
        parse_document(input)
    }

    pub fn zettel_lines(&self) -> Vec<Zettel> {
        if !self.body.is_empty() {
            return self
                .body
                .iter()
                .filter_map(|line| match line {
                    AaakLine::Zettel(zettel) => Some(zettel.clone()),
                    AaakLine::Tunnel(_) | AaakLine::Arc(_) => None,
                })
                .collect();
        }

        self.zettels.clone()
    }
}

impl Display for AaakDocument {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "V{}|{}|{}|{}|{}",
            self.header.version,
            self.header.wing,
            self.header.room,
            self.header.date,
            self.header.source
        )?;

        for (index, line) in self.body_lines().iter().enumerate() {
            if index > 0 {
                writeln!(f)?;
            }
            match line {
                AaakLine::Zettel(zettel) => write!(
                    f,
                    "{}:{}|{}|\"{}\"|{}|{}|{}",
                    zettel.id,
                    zettel.entities.join("+"),
                    zettel.topics.join("_"),
                    zettel.quote.replace('"', "'"),
                    "".repeat(usize::from(zettel.weight.max(1))),
                    zettel.emotions.join("+"),
                    zettel.flags.join("+"),
                )?,
                AaakLine::Tunnel(tunnel) => {
                    write!(f, "T:{}<->{}|{}", tunnel.left, tunnel.right, tunnel.label)?
                }
                AaakLine::Arc(arc) => write!(f, "ARC:{}", arc.emotions.join("->"))?,
            }
        }

        Ok(())
    }
}

impl AaakDocument {
    fn body_lines(&self) -> Vec<AaakLine> {
        if !self.body.is_empty() {
            return self.body.clone();
        }

        self.zettels.iter().cloned().map(AaakLine::Zettel).collect()
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EncodeReport {
    pub topics_truncated: usize,
    pub key_sentence_truncated: bool,
    pub coverage: f32,
    pub lost_assertions: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EncodeOutput {
    pub document: AaakDocument,
    pub report: EncodeReport,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoundtripReport {
    pub preserved: Vec<String>,
    pub lost: Vec<String>,
    pub coverage: f32,
}

#[derive(Debug, Error, PartialEq, Eq)]
pub enum ParseError {
    #[error("missing AAAK header")]
    MissingHeader,
    #[error("invalid AAAK header")]
    InvalidHeader,
    #[error("invalid version marker")]
    InvalidVersion,
    #[error("invalid zettel line: {0}")]
    InvalidZettel(String),
}