Skip to main content

impactsense_parser/
project.rs

1//! High-level project parsing API for library and MCP consumers.
2
3use std::path::Path;
4
5use thiserror::Error;
6
7use crate::extract::{parse_files_to_ir, scan_and_build_ir, scan_and_build_ir_async, ExtractOptions};
8use crate::pipeline::ScanOptions;
9use crate::store::{GraphStore, InMemoryGraph, RefreshReport};
10
11#[derive(Debug, Error)]
12pub enum ProjectError {
13    #[error("scan/extract failed: {0}")]
14    Extract(#[from] crate::extract::ExtractError),
15}
16
17/// Parse an entire workspace and return a queryable in-memory graph.
18///
19/// When `scan.graph.compressor.enabled` is true, function/module/class snippets are
20/// compressed via the RedCompressor HTTP API and stored on [`crate::ir::ProjectIr`] nodes.
21pub fn parse_project(root: &Path, scan: &ScanOptions) -> Result<InMemoryGraph, ProjectError> {
22    let extract_opts = ExtractOptions::from(&scan.graph);
23    let ir = scan_and_build_ir(root, &extract_opts, scan)?;
24    Ok(InMemoryGraph::from_ir(ir))
25}
26
27/// Async parse for Tokio runtimes (MCP). Same as [`parse_project`] including optional compression.
28pub async fn parse_project_async(
29    root: &Path,
30    scan: &ScanOptions,
31) -> Result<InMemoryGraph, ProjectError> {
32    let extract_opts = ExtractOptions::from(&scan.graph);
33    let ir = scan_and_build_ir_async(root, &extract_opts, scan).await?;
34    Ok(InMemoryGraph::from_ir(ir))
35}
36
37/// Incrementally refresh changed files in an existing graph.
38pub fn refresh_files(
39    graph: &mut InMemoryGraph,
40    root: &Path,
41    cleanup_targets: &[String],
42    parse_targets: &[String],
43    scan: &ScanOptions,
44) -> Result<RefreshReport, ProjectError> {
45    let extract_opts = ExtractOptions::from(&scan.graph);
46
47    for path in cleanup_targets {
48        graph.remove_file(path);
49    }
50
51    let nodes_before = graph.node_count();
52    let edges_before = graph.edge_count();
53
54    if !parse_targets.is_empty() {
55        let delta = parse_files_to_ir(root, parse_targets, &extract_opts, scan)?;
56        graph.merge_ir(delta);
57    }
58
59    Ok(RefreshReport {
60        cleanup_targets: cleanup_targets.len(),
61        parse_targets: parse_targets.len(),
62        nodes_merged: graph.node_count().saturating_sub(nodes_before),
63        edges_merged: graph.edge_count().saturating_sub(edges_before),
64    })
65}