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