sqry_cli/commands/graph/loader.rs
1//! Workspace graph loader for CLI graph commands.
2//!
3//! This module loads a unified `CodeGraph` either from a persisted snapshot or
4//! by invoking the core `build_unified_graph` entrypoint with the shared plugin
5//! registry.
6
7use crate::args::Cli;
8use crate::plugin_defaults::{self, PluginSelectionMode};
9use anyhow::{Context, Result, bail};
10use sqry_core::graph::CodeGraph;
11use sqry_core::graph::unified::build::{BuildConfig, build_unified_graph_with_progress};
12use sqry_core::graph::unified::persistence::{GraphStorage, load_from_path};
13use sqry_core::progress::{SharedReporter, no_op_reporter};
14use std::path::Path;
15
16/// Loader configuration derived from CLI flags.
17#[derive(Debug, Clone, Default)]
18pub struct GraphLoadConfig {
19 pub include_hidden: bool,
20 pub follow_symlinks: bool,
21 pub max_depth: Option<usize>,
22 /// Force building from source files, even if a snapshot exists.
23 /// Used by the index command to always rebuild.
24 pub force_build: bool,
25}
26
27/// Load a unified code graph using the new Arena+CSR storage architecture.
28///
29/// This is the preferred entry point for CLI graph operations. It loads a graph
30/// either from a persisted snapshot or by building from source files.
31///
32/// # Loading Strategy
33///
34/// 1. First tries to load from persisted snapshot (`.sqry/graph/snapshot.sqry`)
35/// 2. If no snapshot exists, builds from source files using language plugins
36///
37/// # Arguments
38/// * `root` - Root directory to scan for source files
39/// * `config` - Configuration for file walking (hidden files, symlinks, depth)
40///
41/// # Returns
42/// A `CodeGraph` populated with nodes and edges from all supported languages
43///
44/// # Errors
45/// Returns an error if the path is missing, the snapshot is invalid, or the graph build fails.
46///
47/// # Example
48/// ```ignore
49/// use std::path::Path;
50/// use sqry_cli::commands::graph::loader::{load_unified_graph, GraphLoadConfig};
51///
52/// let config = GraphLoadConfig::default();
53/// let graph = load_unified_graph(Path::new("."), &config)?;
54/// # Ok::<(), anyhow::Error>(())
55/// ```
56#[allow(dead_code)]
57pub fn load_unified_graph(root: &Path, config: &GraphLoadConfig) -> Result<CodeGraph> {
58 load_unified_graph_with_progress_and_plugins(
59 root,
60 config,
61 &sqry_plugin_registry::create_plugin_manager_all(),
62 no_op_reporter(),
63 )
64}
65
66/// CLI-aware graph loader that enforces manifest-backed plugin semantics.
67///
68/// # Errors
69///
70/// Returns an error if the workspace path is invalid, manifest-selected plugins
71/// cannot load the persisted snapshot, or the fallback source build fails.
72pub fn load_unified_graph_for_cli(
73 root: &Path,
74 config: &GraphLoadConfig,
75 cli: &Cli,
76) -> Result<CodeGraph> {
77 let resolved_plugins =
78 plugin_defaults::resolve_plugin_selection(cli, root, PluginSelectionMode::ReadOnly)?;
79 load_unified_graph_with_progress_and_plugins(
80 root,
81 config,
82 &resolved_plugins.plugin_manager,
83 no_op_reporter(),
84 )
85}
86
87/// Load a unified code graph with progress reporting.
88///
89/// Same as [`load_unified_graph`] but accepts a progress reporter for tracking
90/// build progress when loading from source files.
91///
92/// # Arguments
93/// * `root` - Root directory to scan for source files
94/// * `config` - Configuration for file walking (hidden files, symlinks, depth)
95/// * `progress` - Progress reporter for build status updates
96///
97/// # Returns
98/// A `CodeGraph` populated with nodes and edges from all supported languages
99///
100/// # Errors
101/// Returns an error if the path is missing, the snapshot is invalid, or the graph build fails.
102#[allow(dead_code)]
103pub fn load_unified_graph_with_progress(
104 root: &Path,
105 config: &GraphLoadConfig,
106 progress: SharedReporter,
107) -> Result<CodeGraph> {
108 load_unified_graph_with_progress_and_plugins(
109 root,
110 config,
111 &sqry_plugin_registry::create_plugin_manager_all(),
112 progress,
113 )
114}
115
116fn load_unified_graph_with_progress_and_plugins(
117 root: &Path,
118 config: &GraphLoadConfig,
119 plugins: &sqry_core::plugin::PluginManager,
120 progress: SharedReporter,
121) -> Result<CodeGraph> {
122 if !root.exists() {
123 bail!("Path {} does not exist", root.display());
124 }
125
126 // Try to load from persisted snapshot first (unless force_build is set)
127 if config.force_build {
128 log::info!("Force build enabled, skipping snapshot load");
129 } else {
130 let storage = GraphStorage::new(root);
131 if storage.exists() {
132 log::info!(
133 "Loading unified graph from snapshot: {}",
134 storage.snapshot_path().display()
135 );
136 match load_from_path(storage.snapshot_path(), Some(plugins)) {
137 Ok(mut graph) => {
138 log::info!("Loaded graph from snapshot");
139
140 // Restore confidence metadata from manifest if available
141 // The snapshot binary format doesn't include confidence,
142 // so we load it separately from the manifest JSON.
143 if let Ok(manifest) = storage.load_manifest()
144 && !manifest.confidence.is_empty()
145 {
146 log::debug!(
147 "Restoring confidence metadata for {} languages",
148 manifest.confidence.len()
149 );
150 graph.set_confidence(manifest.confidence);
151 }
152
153 return Ok(graph);
154 }
155 Err(e) => {
156 // Manifest present but snapshot missing/corrupt → corruption
157 // Do not silently rebuild; user should run `sqry index --force`
158 bail!(
159 "Index at {} is corrupted or incomplete ({}). Run `sqry index --force` to rebuild.",
160 root.display(),
161 e
162 );
163 }
164 }
165 }
166 }
167
168 // Build from source files
169 log::info!(
170 "Building unified graph from source files in {}",
171 root.display()
172 );
173
174 let build_config = BuildConfig {
175 include_hidden: config.include_hidden,
176 follow_links: config.follow_symlinks,
177 max_depth: config.max_depth,
178 num_threads: None,
179 ..BuildConfig::default()
180 };
181
182 let graph = build_unified_graph_with_progress(root, plugins, &build_config, progress)
183 .context("Failed to build unified graph")?;
184
185 log::info!("Built unified graph with {} nodes", graph.node_count());
186 Ok(graph)
187}