lean-ctx 3.5.16

Context Runtime for AI Agents with CCP. 63 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing + diaries, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24 AI tools. Reduces LLM token consumption by up to 99%.
Documentation
use super::super::{install_project_rules, resolve_binary_path, HookMode, CLI_REDIRECT_RULES};

pub(super) const HERMES_RULES_TEMPLATE: &str = "\
# lean-ctx — Context Engineering Layer

PREFER lean-ctx MCP tools over native equivalents for token savings:

| PREFER | OVER | Why |
|--------|------|-----|
| `ctx_read(path, mode)` | `Read` / `cat` | Cached, 10 read modes, re-reads ~13 tokens |
| `ctx_shell(command)` | `Shell` / `bash` | Pattern compression for git/npm/cargo output |
| `ctx_search(pattern, path)` | `Grep` / `rg` | Compact search results |
| `ctx_tree(path, depth)` | `ls` / `find` | Compact directory maps |

- Native Edit/StrReplace stay unchanged. If Edit requires Read and Read is unavailable, use `ctx_edit(path, old_string, new_string)`.
- Write, Delete, Glob — use normally.

ctx_read modes: full|map|signatures|diff|task|reference|aggressive|entropy|lines:N-M. Auto-selects optimal mode.
Re-reads cost ~13 tokens (cached).

Available tools: ctx_overview, ctx_preload, ctx_dedup, ctx_compress, ctx_session, ctx_knowledge, ctx_semantic_search.
Multi-agent: ctx_agent(action=handoff|sync). Diary: ctx_agent(action=diary, category=discovery|decision|blocker|progress|insight).
";

pub(crate) fn install_hermes_hook_with_mode(global: bool, mode: HookMode) {
    let Some(home) = crate::core::home::resolve_home_dir() else {
        tracing::error!("Cannot resolve home directory");
        return;
    };

    let binary = resolve_binary_path();
    let config_path = home.join(".hermes/config.yaml");
    let target = crate::core::editor_registry::EditorTarget {
        name: "Hermes Agent",
        agent_key: "hermes".to_string(),
        config_path: config_path.clone(),
        detect_path: home.join(".hermes"),
        config_type: crate::core::editor_registry::ConfigType::HermesYaml,
    };

    match mode {
        HookMode::CliRedirect => {
            let _ = crate::core::editor_registry::remove_lean_ctx_server(
                &target,
                crate::core::editor_registry::WriteOptions {
                    overwrite_invalid: true,
                },
            );
        }
        HookMode::Mcp | HookMode::Hybrid => {
            match crate::core::editor_registry::write_config_with_options(
                &target,
                &binary,
                crate::core::editor_registry::WriteOptions {
                    overwrite_invalid: true,
                },
            ) {
                Ok(res) => match res.action {
                    crate::core::editor_registry::WriteAction::Created => {
                        eprintln!(
                            "  \x1b[32m✓\x1b[0m Hermes Agent MCP configured at ~/.hermes/config.yaml"
                        );
                    }
                    crate::core::editor_registry::WriteAction::Updated => {
                        eprintln!(
                            "  \x1b[32m✓\x1b[0m Hermes Agent MCP updated at ~/.hermes/config.yaml"
                        );
                    }
                    crate::core::editor_registry::WriteAction::Already => {
                        eprintln!("  Hermes Agent MCP already configured at ~/.hermes/config.yaml");
                    }
                },
                Err(e) => {
                    tracing::error!("Failed to configure Hermes Agent MCP: {e}");
                }
            }
        }
    }

    let scope = crate::core::config::Config::load().rules_scope_effective();

    match scope {
        crate::core::config::RulesScope::Global => {
            install_hermes_rules(&home, mode);
        }
        crate::core::config::RulesScope::Project => {
            if !global {
                install_project_hermes_rules(mode);
                install_project_rules();
            }
        }
        crate::core::config::RulesScope::Both => {
            if global {
                install_hermes_rules(&home, mode);
            } else {
                install_hermes_rules(&home, mode);
                install_project_hermes_rules(mode);
                install_project_rules();
            }
        }
    }
}

fn install_hermes_rules(home: &std::path::Path, mode: HookMode) {
    let rules_path = home.join(".hermes/HERMES.md");
    let content = match mode {
        HookMode::CliRedirect => CLI_REDIRECT_RULES,
        HookMode::Hybrid | HookMode::Mcp => HERMES_RULES_TEMPLATE,
    };

    if rules_path.exists() {
        let existing = std::fs::read_to_string(&rules_path).unwrap_or_default();
        if existing.contains("lean-ctx") {
            eprintln!("  Hermes rules already present in ~/.hermes/HERMES.md");
            return;
        }
        let mut updated = existing;
        if !updated.ends_with('\n') {
            updated.push('\n');
        }
        updated.push('\n');
        updated.push_str(content);
        let _ = std::fs::write(&rules_path, updated);
        eprintln!("  \x1b[32m✓\x1b[0m Appended lean-ctx rules to ~/.hermes/HERMES.md");
    } else {
        if let Some(parent) = rules_path.parent() {
            let _ = std::fs::create_dir_all(parent);
        }
        let _ = std::fs::write(&rules_path, content);
        eprintln!("  \x1b[32m✓\x1b[0m Created ~/.hermes/HERMES.md with lean-ctx rules");
    }
}

fn install_project_hermes_rules(mode: HookMode) {
    let Ok(cwd) = std::env::current_dir() else {
        return;
    };
    let rules_path = cwd.join(".hermes.md");
    if rules_path.exists() {
        let existing = std::fs::read_to_string(&rules_path).unwrap_or_default();
        if existing.contains("lean-ctx") {
            eprintln!("  .hermes.md already contains lean-ctx rules");
            return;
        }
        let mut updated = existing;
        if !updated.ends_with('\n') {
            updated.push('\n');
        }
        updated.push('\n');
        updated.push_str(match mode {
            HookMode::CliRedirect => CLI_REDIRECT_RULES,
            HookMode::Hybrid | HookMode::Mcp => HERMES_RULES_TEMPLATE,
        });
        let _ = std::fs::write(&rules_path, updated);
        eprintln!("  \x1b[32m✓\x1b[0m Appended lean-ctx rules to .hermes.md");
    } else {
        let _ = std::fs::write(
            &rules_path,
            match mode {
                HookMode::CliRedirect => CLI_REDIRECT_RULES,
                HookMode::Hybrid | HookMode::Mcp => HERMES_RULES_TEMPLATE,
            },
        );
        eprintln!("  \x1b[32m✓\x1b[0m Created .hermes.md with lean-ctx rules");
    }
}