codemem-engine 0.19.0

Domain logic engine for Codemem: indexing, hooks, watching, scoring, recall, consolidation
Documentation
//! Trigger-based auto-insights generated during PostToolUse.

/// An auto-insight generated by trigger-based analysis during PostToolUse.
#[derive(Debug, Clone)]
pub struct AutoInsight {
    /// The insight content to store as a memory.
    pub content: String,
    /// Tags to attach to the insight memory.
    pub tags: Vec<String>,
    /// Importance score for the insight.
    pub importance: f64,
    /// Unique tag used for deduplication within a session.
    pub dedup_tag: String,
}

/// Check trigger conditions against session activity and return any auto-insights.
///
/// Three triggers are evaluated:
/// 1. **Directory focus**: 3+ files read from the same directory suggests deep exploration.
/// 2. **Edit after read**: Editing a file that was previously read indicates an informed change.
/// 3. **Repeated search**: Same search pattern used 2+ times suggests a recurring need.
///
/// Each trigger checks `has_auto_insight()` to avoid duplicate insights within the same session.
pub fn check_triggers(
    storage: &dyn codemem_core::StorageBackend,
    session_id: &str,
    tool_name: &str,
    file_path: Option<&str>,
    pattern: Option<&str>,
) -> Vec<AutoInsight> {
    let mut insights = Vec::new();

    // Trigger 1: 3+ files read from the same directory
    if tool_name == "Read" {
        if let Some(fp) = file_path {
            let directory = std::path::Path::new(fp)
                .parent()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_default();
            if !directory.is_empty() {
                let dedup_tag = format!("dir_focus:{}", directory);
                let already_exists = storage
                    .has_auto_insight(session_id, &dedup_tag)
                    .unwrap_or(true);
                if !already_exists {
                    let count = storage
                        .count_directory_reads(session_id, &directory)
                        .unwrap_or(0);
                    if count >= 3 {
                        insights.push(AutoInsight {
                            content: format!(
                                "Deep exploration of directory '{}': {} files read in this session. \
                                 This area may be a focus of the current task.",
                                directory, count
                            ),
                            tags: vec![
                                "auto-insight".to_string(),
                                "directory-focus".to_string(),
                                format!("dir:{}", directory),
                            ],
                            importance: 0.6,
                            dedup_tag,
                        });
                    }
                }
            }
        }
    }

    // Trigger 2: Edit after read — an informed change
    if matches!(tool_name, "Edit" | "Write") {
        if let Some(fp) = file_path {
            let dedup_tag = format!("edit_after_read:{}", fp);
            let already_exists = storage
                .has_auto_insight(session_id, &dedup_tag)
                .unwrap_or(true);
            if !already_exists {
                let was_read = storage
                    .was_file_read_in_session(session_id, fp)
                    .unwrap_or(false);
                if was_read {
                    insights.push(AutoInsight {
                        content: format!(
                            "File '{}' was read and then modified in this session, \
                             indicating an informed change based on code review.",
                            fp
                        ),
                        tags: vec![
                            "auto-insight".to_string(),
                            "edit-after-read".to_string(),
                            format!(
                                "file:{}",
                                std::path::Path::new(fp)
                                    .file_name()
                                    .and_then(|f| f.to_str())
                                    .unwrap_or("unknown")
                            ),
                        ],
                        importance: 0.5,
                        dedup_tag,
                    });
                }
            }
        }
    }

    // Trigger 3: "Understanding module" — 3+ files read from the same directory
    // (reuses the directory focus data but generates a module-level insight)
    if tool_name == "Read" {
        if let Some(fp) = file_path {
            let directory = std::path::Path::new(fp)
                .parent()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_default();
            if !directory.is_empty() {
                let module_name = std::path::Path::new(&directory)
                    .file_name()
                    .and_then(|f| f.to_str())
                    .unwrap_or("unknown");
                let dedup_tag = format!("exploring_module:{}", directory);
                let already_exists = storage
                    .has_auto_insight(session_id, &dedup_tag)
                    .unwrap_or(true);
                if !already_exists {
                    let count = storage
                        .count_directory_reads(session_id, &directory)
                        .unwrap_or(0);
                    if count >= 3 {
                        insights.push(AutoInsight {
                            content: format!(
                                "Exploring '{}' module: {} files read. Building understanding of this area.",
                                module_name, count
                            ),
                            tags: vec![
                                "auto-insight".to_string(),
                                "exploring-module".to_string(),
                                format!("module:{}", module_name),
                            ],
                            importance: 0.55,
                            dedup_tag,
                        });
                    }
                }
            }
        }
    }

    // Trigger 4: "Debugging" — Bash with error output followed by file reads
    if tool_name == "Bash" {
        let has_error = storage
            .count_search_pattern_in_session(session_id, "error")
            .unwrap_or(0)
            > 0;
        if has_error {
            let area = file_path
                .and_then(|fp| {
                    std::path::Path::new(fp)
                        .parent()
                        .and_then(|p| p.file_name())
                        .and_then(|f| f.to_str())
                })
                .unwrap_or("project");
            let dedup_tag = format!("debugging:{}", area);
            let already_exists = storage
                .has_auto_insight(session_id, &dedup_tag)
                .unwrap_or(true);
            if !already_exists {
                insights.push(AutoInsight {
                    content: format!(
                        "Debugging in '{}': error output detected in bash commands during this session.",
                        area
                    ),
                    tags: vec![
                        "auto-insight".to_string(),
                        "debugging".to_string(),
                        format!("area:{}", area),
                    ],
                    importance: 0.6,
                    dedup_tag,
                });
            }
        }
    }

    // Trigger 5: Same search pattern used 2+ times
    if matches!(tool_name, "Grep" | "Glob") {
        if let Some(pat) = pattern {
            let dedup_tag = format!("repeated_search:{}", pat);
            let already_exists = storage
                .has_auto_insight(session_id, &dedup_tag)
                .unwrap_or(true);
            if !already_exists {
                let count = storage
                    .count_search_pattern_in_session(session_id, pat)
                    .unwrap_or(0);
                if count >= 2 {
                    insights.push(AutoInsight {
                        content: format!(
                            "Search pattern '{}' used {} times in this session. \
                             Consider storing a permanent memory for this recurring lookup.",
                            pat, count
                        ),
                        tags: vec![
                            "auto-insight".to_string(),
                            "repeated-search".to_string(),
                            format!("pattern:{}", pat),
                        ],
                        importance: 0.5,
                        dedup_tag,
                    });
                }
            }
        }
    }

    insights
}