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