graphyn_mcp/tools/
refresh_graph.rs1use 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 pub path: Option<String>,
18 pub include: Option<String>,
20 pub exclude: Option<String>,
22 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}