Skip to content

Commit 633efba

Browse files
authored
feat(native): port full-build structure computation to Rust (#937)
* feat(native): port full-build structure computation to Rust The native Rust orchestrator only handled the small-incremental fast path for structure computation. Full builds and large incremental builds fell back to JS, requiring a DB round-trip through reconstructFileSymbolsFromDb() to reload all nodes. Add build_full_structure() to the Rust structure module that handles: - Cleanup of previous structure data (scoped for incremental) - Directory node insertion from discovered dirs + file paths - Contains edges (dir→file, parent→child) - File metrics (line count, symbol count, imports, exports, fan-in/out) - Directory metrics (file count, symbol count, fan-in/out, cohesion) Set structure_handled=true unconditionally so the JS fallback path is never triggered for structure computation. Closes #935 * fix(structure): load all file paths from DB in incremental mode (#937) The incremental path of build_full_structure deleted all contains edges from affected directories but only re-inserted edges for files in the partial file_symbols set. Unchanged files in those directories permanently lost their dir->file containment edges until the next full rebuild. Directory metrics (file_count, symbol_count, cohesion) were also undercounted for the same reason. Fix: load existing file nodes from DB for affected directories before re-inserting contains edges and computing directory metrics, ensuring unchanged files retain their structural relationships. Impact: 3 functions changed, 2 affected * fix(build): remove dead structure_scope field (#937) Since structure_handled is now unconditionally true, the JS structure fallback never runs and structure_scope is never read. Remove the field from BuildPipelineResult and the corresponding JS type/usage. Impact: 2 functions changed, 5 affected * fix(structure): restore dir->dir containment edges for unchanged sibling subdirs (#937) During large-incremental builds, cleanup_previous_data deletes all contains edges from affected ancestor directories. The dir->dir edge loop only iterated over all_dirs (ancestors of changed files), so unchanged sibling subdirectories lost their parent->child edges permanently until the next full rebuild. Add load_child_dirs_in_affected() to query existing directory nodes from the DB whose parent is in the affected set, and re-insert their containment edges after the main dir->dir loop completes. Impact: 2 functions changed, 2 affected
1 parent 9cd5276 commit 633efba

3 files changed

Lines changed: 701 additions & 21 deletions

File tree

crates/codegraph-core/src/build_pipeline.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,9 @@ pub struct BuildPipelineResult {
7272
pub changed_count: usize,
7373
pub removed_count: usize,
7474
pub is_full_build: bool,
75-
/// Full set of changed files including reverse-dep files. Used by the JS
76-
/// structure fallback path so it can update metrics for files whose edges
77-
/// changed even though their content didn't. `None` for full builds.
78-
#[serde(skip_serializing_if = "Option::is_none")]
79-
pub structure_scope: Option<Vec<String>>,
8075
/// Whether the Rust pipeline handled the structure phase (directory nodes,
81-
/// contains edges, file metrics). True when the small-incremental fast path
82-
/// ran (≤5 changed files, >20 existing files). When false, the JS caller
83-
/// must run its own structure phase as a post-processing step.
76+
/// contains edges, file and directory metrics). Always true — the Rust
77+
/// pipeline handles both the small-incremental fast path and full builds.
8478
pub structure_handled: bool,
8579
/// Whether the Rust pipeline wrote AST/complexity/CFG/dataflow to the DB.
8680
/// When true, the JS caller can skip `runPostNativeAnalysis` entirely.
@@ -181,7 +175,6 @@ pub fn run_pipeline(
181175
changed_count: 0,
182176
removed_count: 0,
183177
is_full_build: false,
184-
structure_scope: Some(vec![]),
185178
structure_handled: true,
186179
analysis_complete: true,
187180
});
@@ -373,11 +366,22 @@ pub fn run_pipeline(
373366
&line_count_map,
374367
&file_symbols,
375368
);
369+
} else {
370+
// Full structure: directory nodes, contains edges, file + directory metrics.
371+
let changed_for_structure: Option<Vec<String>> = if change_result.is_full_build {
372+
None
373+
} else {
374+
Some(changed_files.clone())
375+
};
376+
structure::build_full_structure(
377+
conn,
378+
&file_symbols,
379+
&collect_result.directories,
380+
root_dir,
381+
&line_count_map,
382+
changed_for_structure.as_deref(),
383+
);
376384
}
377-
// For full/larger builds, the JS fallback handles full structure via
378-
// `features/structure.ts`. The Rust orchestrator handles the fast path
379-
// for small incremental builds. Full structure computation will be
380-
// ported in a follow-up.
381385
timing.structure_ms = t0.elapsed().as_secs_f64() * 1000.0;
382386

383387
let t0 = Instant::now();
@@ -489,8 +493,7 @@ pub fn run_pipeline(
489493
changed_count: parse_changes.len(),
490494
removed_count: change_result.removed.len(),
491495
is_full_build: change_result.is_full_build,
492-
structure_scope: changed_file_list.clone(),
493-
structure_handled: use_fast_path,
496+
structure_handled: true,
494497
analysis_complete: do_analysis && analysis_ok,
495498
})
496499
}

0 commit comments

Comments
 (0)