remem-ai 0.5.207

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};

pub const ARTIFACT_VERSION: u32 = 1;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CompiledRulesArtifact {
    pub version: u32,
    pub compiled_at_epoch: i64,
    pub rules: Vec<CompiledRule>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CompiledRule {
    pub rule_id: String,
    pub source_memory_id: i64,
    pub reinforcement_count: i64,
    pub action: RuleAction,
    pub override_state: RuleOverrideState,
    pub predicate: RulePredicate,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RuleAction {
    Warn,
    Block,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuleOverrideState {
    pub disabled: bool,
    pub action_override: Option<RuleAction>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum RulePredicate {
    CommandRegex { pattern: String, message: String },
    CommitTrailerForbidden { trailer: String, message: String },
}

impl CompiledRulesArtifact {
    pub fn new(compiled_at_epoch: i64, rules: Vec<CompiledRule>) -> Self {
        Self {
            version: ARTIFACT_VERSION,
            compiled_at_epoch,
            rules,
        }
    }

    pub fn validate(&self) -> Result<()> {
        if self.version != ARTIFACT_VERSION {
            bail!(
                "unsupported compiled rule artifact version {}; expected {}",
                self.version,
                ARTIFACT_VERSION
            );
        }
        if self.compiled_at_epoch < 0 {
            bail!("compiled rule artifact has negative compiled_at_epoch");
        }
        for rule in &self.rules {
            rule.validate()?;
        }
        Ok(())
    }
}

impl CompiledRule {
    pub fn effective_action(&self) -> RuleAction {
        self.override_state.action_override.unwrap_or(self.action)
    }

    fn validate(&self) -> Result<()> {
        if self.rule_id.trim().is_empty() {
            bail!("compiled rule has empty rule_id");
        }
        if self.source_memory_id <= 0 {
            bail!(
                "compiled rule {} has invalid source_memory_id",
                self.rule_id
            );
        }
        if self.reinforcement_count < 1 {
            bail!(
                "compiled rule {} has invalid reinforcement_count {}",
                self.rule_id,
                self.reinforcement_count
            );
        }
        self.predicate.validate(&self.rule_id)?;
        Ok(())
    }
}

impl RulePredicate {
    pub fn message(&self) -> &str {
        match self {
            RulePredicate::CommandRegex { message, .. }
            | RulePredicate::CommitTrailerForbidden { message, .. } => message,
        }
    }

    fn validate(&self, rule_id: &str) -> Result<()> {
        match self {
            RulePredicate::CommandRegex { pattern, message } => {
                if pattern.trim().is_empty() {
                    bail!("compiled rule {rule_id} has empty command_regex pattern");
                }
                if let Err(error) = regex::Regex::new(pattern) {
                    bail!("compiled rule {rule_id} has invalid command_regex pattern: {error}");
                }
                if message.trim().is_empty() {
                    bail!("compiled rule {rule_id} has empty command_regex message");
                }
            }
            RulePredicate::CommitTrailerForbidden { trailer, message } => {
                if trailer.trim().is_empty() {
                    bail!("compiled rule {rule_id} has empty forbidden trailer");
                }
                if message.trim().is_empty() {
                    bail!("compiled rule {rule_id} has empty forbidden trailer message");
                }
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::rules::test_support::package_manager_rule;

    #[test]
    fn artifact_schema_round_trips_versioned_rules() -> Result<()> {
        let artifact =
            CompiledRulesArtifact::new(1234, vec![package_manager_rule(RuleAction::Warn)]);

        let text = serde_json::to_string_pretty(&artifact)?;
        let parsed: CompiledRulesArtifact = serde_json::from_str(&text)?;

        assert_eq!(parsed.version, ARTIFACT_VERSION);
        assert_eq!(parsed.compiled_at_epoch, 1234);
        assert_eq!(parsed.rules[0].rule_id, "pref-123-1");
        assert_eq!(parsed.rules[0].source_memory_id, 123);
        assert_eq!(parsed.rules[0].reinforcement_count, 3);
        assert_eq!(parsed.rules[0].action, RuleAction::Warn);
        assert_eq!(
            parsed.rules[0].predicate,
            RulePredicate::CommandRegex {
                pattern: r"(^|\s)npm\s+(install|i|add)\b".to_string(),
                message: "Command violates a compiled package-manager preference".to_string()
            }
        );
        parsed.validate()?;
        Ok(())
    }

    #[test]
    fn artifact_rejects_unsupported_predicate_kind() {
        let text = r#"{
          "version": 1,
          "compiled_at_epoch": 1234,
          "rules": [{
            "rule_id": "pref-123-1",
            "source_memory_id": 123,
            "reinforcement_count": 3,
            "action": "warn",
            "override_state": {"disabled": false, "action_override": null},
            "predicate": {"kind": "javascript", "source": "return true"}
          }]
        }"#;

        let err = serde_json::from_str::<CompiledRulesArtifact>(text)
            .expect_err("unsupported predicate kind should fail closed at parse time");
        assert!(
            err.to_string().contains("unknown variant"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn artifact_validation_rejects_invalid_command_regex() {
        let mut rule = package_manager_rule(RuleAction::Warn);
        rule.predicate = RulePredicate::CommandRegex {
            pattern: "(".to_string(),
            message: "invalid regex fixture".to_string(),
        };
        let artifact = CompiledRulesArtifact::new(1234, vec![rule]);

        let error = artifact
            .validate()
            .expect_err("invalid command regex must fail artifact validation");

        assert!(error.to_string().contains("invalid command_regex pattern"));
    }
}