Skip to main content

graphyn_mcp/tools/
refresh_graph.rs

1use std::path::Path;
2
3use schemars::JsonSchema;
4use serde::Deserialize;
5
6use graphyn_adapter_ts::analyze_files;
7use graphyn_adapter_ts::language::is_supported_source_file;
8use graphyn_core::graph::GraphynGraph;
9use graphyn_core::ir::RepoIR;
10use graphyn_core::resolver::AliasResolver;
11use graphyn_core::scan::{parse_csv_patterns, walk_source_files_with_config, ScanConfig};
12use graphyn_store::RocksGraphStore;
13
14#[derive(Debug, Deserialize, JsonSchema)]
15pub struct RefreshGraphParams {
16    /// Optional relative root path under server repo root.
17    pub path: Option<String>,
18    /// Comma-separated include patterns.
19    pub include: Option<String>,
20    /// Comma-separated exclude patterns.
21    pub exclude: Option<String>,
22    /// Respect .gitignore rules. Default true.
23    pub respect_gitignore: Option<bool>,
24}
25
26#[derive(Debug, Clone)]
27pub struct RefreshResult {
28    pub symbols: usize,
29    pub relationships: usize,
30    pub alias_chains: usize,
31    pub files_indexed: usize,
32    pub parse_errors: usize,
33}
34
35pub fn execute(
36    repo_root: &Path,
37    params: RefreshGraphParams,
38) -> Result<(GraphynGraph, RefreshResult), String> {
39    let analysis_root = match params.path.as_deref() {
40        Some(rel) if !rel.trim().is_empty() => repo_root.join(rel),
41        _ => repo_root.to_path_buf(),
42    };
43
44    let root = std::fs::canonicalize(&analysis_root)
45        .map_err(|e| format!("cannot access '{}': {e}", analysis_root.display()))?;
46
47    let scan_config = ScanConfig {
48        include_patterns: parse_csv_patterns(params.include.as_deref()),
49        exclude_patterns: parse_csv_patterns(params.exclude.as_deref()),
50        respect_gitignore: params.respect_gitignore.unwrap_or(true),
51    };
52
53    let files = walk_source_files_with_config(&root, &scan_config, is_supported_source_file)
54        .map_err(|e| format!("scan failed: {e}"))?;
55
56    let repo_ir = analyze_files(&root, &files).map_err(|e| format!("analysis failed: {e}"))?;
57
58    let parse_errors: usize = repo_ir.files.iter().map(|f| f.parse_errors.len()).sum();
59    let graph = build_graph(&repo_ir);
60
61    let db = root.join(".graphyn").join("db");
62    if let Some(parent) = db.parent() {
63        std::fs::create_dir_all(parent).map_err(|e| format!("failed to create db dir: {e}"))?;
64    }
65    let store = RocksGraphStore::open(&db).map_err(|e| format!("failed to open store: {e}"))?;
66    store
67        .save_graph(&graph)
68        .map_err(|e| format!("failed to save graph: {e}"))?;
69
70    let result = RefreshResult {
71        symbols: graph.symbols.len(),
72        relationships: graph.graph.edge_count(),
73        alias_chains: graph.alias_chains.len(),
74        files_indexed: repo_ir.files.len(),
75        parse_errors,
76    };
77
78    Ok((graph, result))
79}
80
81fn build_graph(repo_ir: &RepoIR) -> GraphynGraph {
82    let mut graph = GraphynGraph::new();
83    let resolver = AliasResolver::default();
84
85    for file_ir in &repo_ir.files {
86        for symbol in &file_ir.symbols {
87            graph.add_symbol(symbol.clone());
88        }
89    }
90
91    for file_ir in &repo_ir.files {
92        for relationship in &file_ir.relationships {
93            graph.add_relationship(relationship);
94        }
95        resolver.ingest_relationships(&graph, &file_ir.relationships);
96    }
97
98    graph
99}