lean_ctx/tools/
ctx_rules.rs1use 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}