sqry_cli/commands/
repair.rs1use crate::args::Cli;
11use crate::commands::graph::loader::{GraphLoadConfig, load_unified_graph};
12use crate::index_discovery::find_nearest_index;
13use anyhow::{Context, Result};
14use std::path::Path;
15
16#[allow(clippy::fn_params_excessive_bools)]
31pub fn run_repair(
32 cli: &Cli,
33 path: &str,
34 fix_orphans: bool,
35 fix_dangling: bool,
36 recompute_checksum: bool,
37 fix_all: bool,
38 dry_run: bool,
39) -> Result<()> {
40 let root_path = Path::new(path);
41
42 let index_location = find_nearest_index(root_path);
44 let Some(ref loc) = index_location else {
45 anyhow::bail!(
46 "No index found at {}. Run 'sqry index' first.",
47 root_path.display()
48 );
49 };
50
51 let config = GraphLoadConfig::default();
53 let graph = load_unified_graph(&loc.index_root, &config)
54 .context("Failed to load graph. Run 'sqry index' to build the graph.")?;
55
56 let Some(actions) =
57 determine_repair_actions(fix_all, fix_orphans, fix_dangling, recompute_checksum)
58 else {
59 return Ok(());
60 };
61
62 if dry_run {
63 println!("DRY RUN MODE - Detecting issues only\n");
64 }
65
66 let mut stats = RepairStats::default();
67 let mut issues_found = false;
68
69 if actions.orphans {
71 println!("Checking for orphaned symbols...");
72 let orphan_count = detect_orphaned_nodes(&graph, &loc.index_root);
73 stats.orphans_detected = orphan_count;
74 if orphan_count > 0 {
75 println!(" Found {orphan_count} orphaned files (missing from disk)");
76 issues_found = true;
77 } else {
78 println!(" No orphaned files found");
79 }
80 }
81
82 if actions.dangling {
83 println!("Checking for dangling references...");
84 println!(" Note: Dangling reference detection requires full graph analysis");
85 }
86
87 if actions.checksum {
88 println!("Checksum verification...");
89 println!(" Graph integrity verified during load");
90 stats.checksum_verified = true;
91 }
92
93 if issues_found {
95 println!("\n⚠ Issues detected. To fix, rebuild the index:");
96 println!(" sqry index --force {}", root_path.display());
97 } else {
98 println!("\n✓ No issues detected - index is healthy");
99 }
100
101 if cli.json {
102 print_repair_json(issues_found, dry_run, &stats)?;
103 }
104
105 Ok(())
106}
107
108struct RepairActions {
109 orphans: bool,
110 dangling: bool,
111 checksum: bool,
112}
113
114#[allow(clippy::fn_params_excessive_bools)]
115fn determine_repair_actions(
116 fix_all: bool,
117 fix_orphans: bool,
118 fix_dangling: bool,
119 recompute_checksum: bool,
120) -> Option<RepairActions> {
121 let actions = RepairActions {
122 orphans: fix_all || fix_orphans,
123 dangling: fix_all || fix_dangling,
124 checksum: fix_all || recompute_checksum,
125 };
126
127 if !actions.orphans && !actions.dangling && !actions.checksum {
128 report_missing_repair_options();
129 return None;
130 }
131
132 Some(actions)
133}
134
135fn report_missing_repair_options() {
136 eprintln!("No repair options specified. Use --fix-all or specify individual checks:");
137 eprintln!(" --fix-orphans Check for symbols with missing files");
138 eprintln!(" --fix-dangling Check for dangling references");
139 eprintln!(" --recompute-checksum Verify index integrity");
140 eprintln!(" --fix-all Run all checks");
141}
142
143fn detect_orphaned_nodes(
145 graph: &sqry_core::graph::unified::concurrent::CodeGraph,
146 root_path: &Path,
147) -> usize {
148 let files = graph.files();
149 let mut orphan_count = 0;
150
151 for (node_id, entry) in graph.nodes().iter() {
153 let _ = node_id; if let Some(file_path) = files.resolve(entry.file) {
156 let full_path = root_path.join(file_path.as_ref());
157 if !full_path.exists() {
158 orphan_count += 1;
159 }
160 }
161 }
162
163 orphan_count
164}
165
166fn print_repair_json(issues_found: bool, dry_run: bool, stats: &RepairStats) -> Result<()> {
167 let json_output = serde_json::json!({
168 "issues_found": issues_found,
169 "dry_run": dry_run,
170 "stats": {
171 "orphans_detected": stats.orphans_detected,
172 "dangling_refs_detected": stats.dangling_refs_detected,
173 "checksum_verified": stats.checksum_verified,
174 },
175 "recommendation": if issues_found { "Run 'sqry index --force' to rebuild" } else { "No action needed" }
176 });
177 println!("{}", serde_json::to_string_pretty(&json_output)?);
178 Ok(())
179}
180
181#[derive(Default)]
183struct RepairStats {
184 orphans_detected: usize,
185 dangling_refs_detected: usize,
186 checksum_verified: bool,
187}