Skip to main content

lean_ctx/cli/
rules_cmd.rs

1use crate::core::contextops::{self, ContextOps};
2
3pub fn cmd_rules(args: &[String]) {
4    let action = args.first().map_or("help", String::as_str);
5
6    let Some(home) = dirs::home_dir() else {
7        eprintln!("Error: could not determine home directory");
8        std::process::exit(1);
9    };
10
11    let project_root = std::env::current_dir().unwrap_or_else(|_| home.clone());
12    let ops = ContextOps::new(&home, &project_root);
13
14    match action {
15        "sync" => cmd_sync(&ops, args),
16        "diff" => cmd_diff(&ops),
17        "lint" => cmd_lint(&ops),
18        "status" => cmd_status(&ops),
19        "init" => cmd_init(&ops),
20        "help" | "--help" | "-h" => print_help(),
21        _ => {
22            eprintln!("Unknown rules action: {action}");
23            print_help();
24            std::process::exit(1);
25        }
26    }
27}
28
29fn cmd_sync(ops: &ContextOps, args: &[String]) {
30    let agent = args.get(1).map(String::as_str);
31
32    let report = if let Some(agent_name) = agent {
33        println!("Syncing rules for {agent_name}...");
34        ops.sync_agent(agent_name)
35    } else {
36        println!("Syncing rules to all detected agents...");
37        ops.sync_all()
38    };
39
40    println!("{}", contextops::format_sync(&report));
41
42    if !report.errors.is_empty() {
43        std::process::exit(1);
44    }
45}
46
47fn cmd_diff(ops: &ContextOps) {
48    match ops.detect_drift() {
49        Ok(reports) => {
50            println!("{}", contextops::format_drift(&reports));
51
52            let drifted = reports
53                .iter()
54                .filter(|r| r.status == contextops::DriftStatus::Drifted)
55                .count();
56            if drifted > 0 {
57                println!("\n{drifted} target(s) drifted. Run `lean-ctx rules sync` to fix.");
58            }
59        }
60        Err(e) => {
61            eprintln!("Error: {e}");
62            println!("\nFalling back to status-based drift check...");
63            let statuses = ops.status();
64            let outdated: Vec<_> = statuses.iter().filter(|s| s.state == "outdated").collect();
65            if outdated.is_empty() {
66                println!("All detected targets appear up to date.");
67            } else {
68                for s in &outdated {
69                    println!("  [OUTDATED] {} ({})", s.name, s.path);
70                }
71            }
72        }
73    }
74}
75
76fn cmd_lint(ops: &ContextOps) {
77    match ops.lint() {
78        Ok(warnings) => {
79            println!("{}", contextops::format_lint(&warnings));
80            let errors = warnings
81                .iter()
82                .filter(|w| w.severity == contextops::LintSeverity::Error)
83                .count();
84            if errors > 0 {
85                std::process::exit(1);
86            }
87        }
88        Err(e) => {
89            eprintln!("Error: {e}");
90            eprintln!("Run `lean-ctx rules init` first to create .leanctx/rules.toml");
91            std::process::exit(1);
92        }
93    }
94}
95
96fn cmd_status(ops: &ContextOps) {
97    let statuses = ops.status();
98    println!("{}", contextops::format_status(&statuses));
99
100    let has_config = ops.has_config();
101    println!();
102    if has_config {
103        println!("Central config: ✓ (.leanctx/rules.toml)");
104    } else {
105        println!("Central config: ✗ (run `lean-ctx rules init` to create)");
106    }
107}
108
109fn cmd_init(ops: &ContextOps) {
110    if ops.has_config() {
111        eprintln!("Config already exists at .leanctx/rules.toml");
112        eprintln!("Delete it first if you want to reinitialize.");
113        std::process::exit(1);
114    }
115
116    match ops.init() {
117        Ok(_config) => {
118            println!("Created .leanctx/rules.toml from existing rules.");
119            println!();
120            println!("Next steps:");
121            println!("  1. Review .leanctx/rules.toml");
122            println!("  2. Run `lean-ctx rules lint` to check consistency");
123            println!("  3. Run `lean-ctx rules sync` to distribute");
124        }
125        Err(e) => {
126            eprintln!("Error: {e}");
127            std::process::exit(1);
128        }
129    }
130}
131
132fn print_help() {
133    eprintln!(
134        "lean-ctx rules — Cross-agent rules governance (ContextOps)\n\
135         \n\
136         USAGE:\n    \
137             lean-ctx rules <action> [args]\n\
138         \n\
139         ACTIONS:\n    \
140             sync [agent]      Sync central rules to all (or one) agent config(s)\n    \
141             diff              Show drift between central and distributed rules\n    \
142             lint              Check rules for consistency and completeness\n    \
143             status            Show sync status for all targets\n    \
144             init              Create .leanctx/rules.toml from existing rules\n    \
145             help              Show this help"
146    );
147}