Skip to content

Commit f65eba8

Browse files
HumanBean17claude
andcommitted
fix: phantom route writes, duplicate members, and minor issues from review round 2
- Write phantom Route nodes in _write_clients_producers_and_calls using MERGE (pass5 creates phantom routes for cross-service calls that were never persisted to Kuzu, silently dropping HTTP_CALLS/ASYNC_CALLS edges) - Remove redundant _load_existing_members before full pass1_parse in global pass 5-6 step (was creating duplicate stub members alongside full entries) - Use conn.close() + del instead of bare del for Kuzu handle cleanup on fallback (avoids relying on CPython ref-counting for file lock release) - Add FileNotFoundError handling in FileHashTracker.detect_changes for files that vanish between listing and hashing - Remove redundant inline import json in cli.py _cmd_increment - Update stale _delete_file_scope docstring to match implementation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 856f18b commit f65eba8

2 files changed

Lines changed: 32 additions & 10 deletions

File tree

build_ast_graph.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,10 @@ def detect_changes(self, source_root: Path, ignore: LayeredIgnore) -> tuple[set[
462462
# Detect added and changed files.
463463
for rel_path in current_files:
464464
abs_path = source_root / rel_path
465-
file_hash = _hash_file(abs_path)
465+
try:
466+
file_hash = _hash_file(abs_path)
467+
except FileNotFoundError:
468+
continue
466469
stored_hash = self._hashes.get(rel_path)
467470
if stored_hash is None:
468471
added.add(rel_path)
@@ -612,9 +615,9 @@ def _find_dependents(conn: kuzu.Connection, changed_node_ids: set[str]) -> set[s
612615
def _delete_file_scope(conn: kuzu.Connection, filenames: set[str]) -> None:
613616
"""Delete all nodes and edges originating from the given files.
614617
615-
Skip phantom nodes (filename=""). For Symbol-to-Symbol and UNRESOLVED_AT
616-
edge tables only. Client/Producer/Route edges are handled separately in
617-
pass 5-6 global rebuild.
618+
Skip phantom nodes (filename=""). Deletes ALL edge types in Phase 1,
619+
then nodes in subsequent phases. Route/Client/Producer nodes use
620+
DETACH DELETE as a safety net for any edges missed in Phase 1.
618621
619622
Edges are deleted in batch across all filenames first to avoid Kuzu
620623
"has connected edges" errors when edges from one file point to nodes
@@ -3442,11 +3445,16 @@ def incremental_rebuild(
34423445
if version < 17:
34433446
if verbose:
34443447
_verbose_stderr_line(f"[increment] ontology version {version} < 17; falling back to full rebuild")
3448+
conn.close()
34453449
del conn, db
34463450
return _fallback_to_full(source_root, kuzu_path, verbose, t_start)
34473451
except Exception as e:
34483452
if verbose:
34493453
_verbose_stderr_line(f"[increment] failed to read ontology version: {e}; falling back to full rebuild")
3454+
try:
3455+
conn.close()
3456+
except Exception:
3457+
pass
34503458
del conn, db
34513459
return _fallback_to_full(source_root, kuzu_path, verbose, t_start)
34523460

@@ -3550,11 +3558,8 @@ def incremental_rebuild(
35503558
if verbose:
35513559
_verbose_stderr_line("[increment] running global passes 5-6")
35523560

3553-
# Load all members for pass 5
3561+
# Rebuild full tables for global pass 5-6 (pass1 populates members from scratch)
35543562
tables_for_global = GraphTables()
3555-
_load_existing_members(conn, tables_for_global)
3556-
3557-
# Rebuild asts for global scope (need for pass5/6)
35583563
global_asts = pass1_parse(source_root, tables_for_global, verbose=verbose)
35593564

35603565
pass5_imperative_edges(tables_for_global, global_asts, source_root=source_root, verbose=verbose)
@@ -3656,7 +3661,25 @@ def _fallback_to_full(source_root: Path, kuzu_path: Path, verbose: bool, t_start
36563661

36573662

36583663
def _write_clients_producers_and_calls(conn: kuzu.Connection, tables: GraphTables) -> None:
3659-
"""Write Client, Producer, and cross-service edges to Kuzu."""
3664+
"""Write Route, Client, Producer, and cross-service edges to Kuzu.
3665+
3666+
Used by the incremental rebuild's global pass 5-6 step. Writes phantom
3667+
Route nodes (created by pass5 for cross-service calls) that wouldn't
3668+
otherwise exist in Kuzu.
3669+
"""
3670+
# Write phantom routes that don't already exist (pass5 creates these for cross-service calls)
3671+
for row in tables.routes_rows:
3672+
# MERGE to avoid duplicates with routes written during scoped step
3673+
conn.execute(
3674+
"MERGE (r:Route {id: $id}) "
3675+
"SET r.kind = $kind, r.framework = $framework, r.method = $method, "
3676+
"r.path = $path, r.path_template = $path_template, r.path_regex = $path_regex, "
3677+
"r.topic = $topic, r.broker = $broker, r.feign_name = $feign_name, r.feign_url = $feign_url, "
3678+
"r.microservice = $microservice, r.module = $module, r.filename = $filename, "
3679+
"r.start_line = $start_line, r.end_line = $end_line, r.resolved = $resolved",
3680+
asdict(row),
3681+
)
3682+
36603683
# Build node_id lookup for members and types
36613684
member_by_id = {m.node_id: m for m in tables.members}
36623685

java_codebase_rag/cli.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ def work() -> int:
356356
# Parse stdout to check for full_fallback mode
357357
# The incremental_rebuild function returns a JSON payload with mode field
358358
try:
359-
import json
360359
result = json.loads(g.stdout.strip())
361360
if result.get("mode") == "full_fallback":
362361
print(

0 commit comments

Comments
 (0)