Skip to main content

lean_ctx/tools/
ctx_rules.rs

1use crate::core::contextops::{self, ContextOps};
2
3pub fn handle(action: &str, agent: Option<&str>) -> String {
4    let Some(home) = dirs::home_dir() else {
5        return "Error: could not determine home directory".to_string();
6    };
7
8    let project_root = std::env::current_dir().unwrap_or_else(|_| home.clone());
9    let ops = ContextOps::new(&home, &project_root);
10
11    match action {
12        "sync" => {
13            let report = if let Some(agent_name) = agent {
14                ops.sync_agent(agent_name)
15            } else {
16                ops.sync_all()
17            };
18            contextops::format_sync(&report)
19        }
20        "diff" => match ops.detect_drift() {
21            Ok(reports) => {
22                let mut output = contextops::format_drift(&reports);
23                let drifted = reports
24                    .iter()
25                    .filter(|r| r.status == contextops::DriftStatus::Drifted)
26                    .count();
27                if drifted > 0 {
28                    output.push_str(&format!(
29                        "\n\n{drifted} target(s) drifted. Run ctx_rules(action=\"sync\") to fix."
30                    ));
31                }
32                output
33            }
34            Err(e) => {
35                let statuses = ops.status();
36                let mut output = format!("Note: {e}\n\nFallback status check:\n");
37                output.push_str(&contextops::format_status(&statuses));
38                output
39            }
40        },
41        "lint" => match ops.lint() {
42            Ok(warnings) => contextops::format_lint(&warnings),
43            Err(e) => {
44                format!("Error: {e}\nRun ctx_rules(action=\"init\") to create .leanctx/rules.toml")
45            }
46        },
47        "status" => {
48            let statuses = ops.status();
49            let mut output = contextops::format_status(&statuses);
50            output.push('\n');
51            if ops.has_config() {
52                output.push_str("\nCentral config: present (.leanctx/rules.toml)");
53            } else {
54                output.push_str(
55                    "\nCentral config: missing (run ctx_rules(action=\"init\") to create)",
56                );
57            }
58            output
59        }
60        "init" => {
61            if ops.has_config() {
62                return "Config already exists at .leanctx/rules.toml. Delete it first to reinitialize.".to_string();
63            }
64            match ops.init() {
65                Ok(_) => "Created .leanctx/rules.toml from existing rules.\nRun ctx_rules(action=\"lint\") to check, then ctx_rules(action=\"sync\") to distribute.".to_string(),
66                Err(e) => format!("Error: {e}"),
67            }
68        }
69        _ => "Unknown action. Use: sync, diff, lint, status, init".to_string(),
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn handle_status() {
79        let result = handle("status", None);
80        assert!(result.contains("Agent Rules Status"));
81    }
82
83    #[test]
84    fn handle_unknown_action() {
85        let result = handle("unknown_xyz", None);
86        assert!(result.contains("Unknown action"));
87    }
88
89    #[test]
90    fn handle_lint_without_config() {
91        let result = handle("lint", None);
92        assert!(
93            result.contains("Error") || result.contains("Lint"),
94            "Should show error or lint results: {result}"
95        );
96    }
97
98    #[test]
99    fn handle_diff_without_config() {
100        let result = handle("diff", None);
101        assert!(!result.is_empty());
102    }
103}