remem-ai 0.6.78

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::collections::HashSet;

use crate::workstream::WorkStream;

use super::types::SessionSummaryBrief;

const MAX_QUERY_CHARS: usize = 512;
const MAX_BRANCH_SEGMENTS: usize = 4;
const MAX_COMMIT_SIGNALS: usize = 3;
const MAX_WORKSTREAM_SIGNALS: usize = 3;
const MAX_SUMMARY_SIGNALS: usize = 2;

pub(super) fn build_implicit_context_query(
    project: &str,
    current_branch: Option<&str>,
    commit_messages: &[String],
    summaries: &[SessionSummaryBrief],
    workstreams: &[WorkStream],
) -> Option<String> {
    let mut builder = QueryBuilder::default();
    builder.push_signal(project.rsplit('/').next().unwrap_or(project));
    if let Some(branch) = current_branch {
        builder.push_signal(branch);
        for segment in branch
            .split(|ch: char| !ch.is_ascii_alphanumeric())
            .filter(|segment| segment.len() >= 3)
            .take(MAX_BRANCH_SEGMENTS)
        {
            builder.push_signal(segment);
        }
    }
    for message in commit_messages.iter().take(MAX_COMMIT_SIGNALS) {
        builder.push_signal(message);
    }
    for workstream in workstreams.iter().take(MAX_WORKSTREAM_SIGNALS) {
        builder.push_signal(&workstream.title);
        builder.push_signal(workstream.next_action.as_deref().unwrap_or_default());
        builder.push_signal(workstream.blockers.as_deref().unwrap_or_default());
    }
    for summary in summaries.iter().take(MAX_SUMMARY_SIGNALS) {
        builder.push_signal(&summary.request);
        builder.push_signal(summary.completed.as_deref().unwrap_or_default());
    }
    builder.build()
}

#[derive(Default)]
struct QueryBuilder {
    parts: Vec<String>,
    seen: HashSet<String>,
    char_count: usize,
}

impl QueryBuilder {
    fn push_signal(&mut self, value: &str) {
        let normalized = value.split_whitespace().collect::<Vec<_>>().join(" ");
        let trimmed = normalized.trim();
        if trimmed.len() < 2 {
            return;
        }
        let key = trimmed.to_ascii_lowercase();
        if !self.seen.insert(key) {
            return;
        }
        let remaining = MAX_QUERY_CHARS.saturating_sub(self.char_count);
        if remaining == 0 {
            return;
        }
        let part = truncate_chars(trimmed, remaining);
        if part.is_empty() {
            return;
        }
        self.char_count += part.chars().count() + 1;
        self.parts.push(part);
    }

    fn build(self) -> Option<String> {
        (!self.parts.is_empty()).then(|| self.parts.join(" "))
    }
}

fn truncate_chars(value: &str, limit: usize) -> String {
    value.chars().take(limit).collect()
}

#[cfg(test)]
mod tests {
    use crate::workstream::{WorkStream, WorkStreamStatus};

    use super::*;

    #[test]
    fn implicit_query_uses_project_branch_workstream_and_summary_signals() {
        let summaries = vec![SessionSummaryBrief {
            id: 1,
            request: "Investigate SQLCipher context recall".to_string(),
            completed: Some("Verified retrieval injection path".to_string()),
            created_at_epoch: 1,
        }];
        let workstreams = vec![WorkStream {
            id: 1,
            project: "/tmp/remem".to_string(),
            title: "Context retrieval".to_string(),
            description: None,
            status: WorkStreamStatus::Active,
            progress: None,
            next_action: Some("Wire hybrid search into SessionStart".to_string()),
            blockers: None,
            created_at_epoch: 1,
            updated_at_epoch: 1,
            completed_at_epoch: None,
        }];

        let query = build_implicit_context_query(
            "/tmp/remem",
            Some("fix/context-retrieval"),
            &["Preserve SQLCipher retrieval evidence".to_string()],
            &summaries,
            &workstreams,
        )
        .expect("query");

        assert!(query.contains("remem"));
        assert!(query.contains("context"));
        assert!(query.contains("retrieval"));
        assert!(query.contains("SessionStart"));
        assert!(query.contains("SQLCipher"));
        assert!(query.contains("Preserve"));
    }
}