Skip to main content

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}