diff --git a/src/codegraphcontext/cli/cli_helpers.py b/src/codegraphcontext/cli/cli_helpers.py index 0054223d..3c39cc54 100644 --- a/src/codegraphcontext/cli/cli_helpers.py +++ b/src/codegraphcontext/cli/cli_helpers.py @@ -235,7 +235,7 @@ async def _run_index_with_progress(graph_builder: GraphBuilder, path_obj: Path, progress.update(task_id, total=job.total_files, completed=job.processed_files) # Update the current filename in the UI - current_file = job.current_file or "" + current_file = job.current_file or job.status_message or "" if len(current_file) > 40: current_file = "..." + current_file[-37:] progress.update(task_id, filename=current_file) diff --git a/src/codegraphcontext/core/database_kuzu.py b/src/codegraphcontext/core/database_kuzu.py index 7f251f1d..5fd4cdbf 100644 --- a/src/codegraphcontext/core/database_kuzu.py +++ b/src/codegraphcontext/core/database_kuzu.py @@ -298,16 +298,26 @@ def _run_schema_migrations(self): raise RuntimeError("Kuzu Schema Migration Failed") from e def close_driver(self): - """Closes the connection pool.""" + """Closes the connection pool and releases database resources.""" if self._db is not None: - info_logger("Closing KùzuDB connection pool") - # Clear the pool - while not self._pool.empty(): - try: - self._pool.get_nowait() - except: - break - self._db = None + with self._lock: + if self._db is None: + return + info_logger("Closing KùzuDB connection pool") + # Clear and close all connections in the pool + while self._pool is not None and not self._pool.empty(): + try: + conn = self._pool.get_nowait() + try: + conn.close() + except Exception: + pass + except: + break + self._pool = None + # Drop database reference to trigger KuzuDB cleanup + self._db = None + import gc; gc.collect() def is_connected(self) -> bool: """Checks if the database connection is currently active.""" diff --git a/src/codegraphcontext/core/jobs.py b/src/codegraphcontext/core/jobs.py index 67f52767..f41f5c13 100644 --- a/src/codegraphcontext/core/jobs.py +++ b/src/codegraphcontext/core/jobs.py @@ -33,6 +33,7 @@ class JobInfo: total_files: int = 0 processed_files: int = 0 current_file: Optional[str] = None + status_message: Optional[str] = None estimated_duration: Optional[float] = None actual_duration: Optional[float] = None errors: List[str] = None diff --git a/src/codegraphcontext/tools/code_finder.py b/src/codegraphcontext/tools/code_finder.py index 790529aa..cf17c7c3 100644 --- a/src/codegraphcontext/tools/code_finder.py +++ b/src/codegraphcontext/tools/code_finder.py @@ -780,7 +780,13 @@ def find_dead_code(self, exclude_decorated_with: Optional[List[str]] = None, rep with self.driver.session() as session: repo_filter = "AND func.path STARTS WITH $repo_path" if repo_path else "" - decorator_filter = "AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)" if exclude_decorated_with else "" + decorator_filter = "" + if exclude_decorated_with: + any_conditions = " AND ".join( + f"NOT ANY(d IN func.decorators WHERE d CONTAINS '{p.replace(chr(39), chr(39)+chr(39))}')" + for p in exclude_decorated_with + ) + decorator_filter = f"AND {any_conditions}" func_ignore = cypher_path_not_under_ignore_dirs("func.path") caller_ignore = cypher_path_not_under_ignore_dirs("caller.path") @@ -816,9 +822,7 @@ def find_dead_code(self, exclude_decorated_with: Optional[List[str]] = None, rep params = {} if repo_path: params["repo_path"] = repo_path - if exclude_decorated_with: - params["exclude_decorated_with"] = exclude_decorated_with - + result = session.run(query, **params) return { diff --git a/src/codegraphcontext/tools/indexing/pipeline.py b/src/codegraphcontext/tools/indexing/pipeline.py index a0121fd0..7156f809 100644 --- a/src/codegraphcontext/tools/indexing/pipeline.py +++ b/src/codegraphcontext/tools/indexing/pipeline.py @@ -105,6 +105,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: ) t0 = time.time() + if job_id: + job_manager.update_job(job_id, status_message="Resolving inheritance links...") info_logger(f"[INHERITS] Resolving inheritance links across {len(all_file_data)} files...") inheritance_batch, csharp_files = build_inheritance_and_csharp_files(all_file_data, imports_map) writer.write_inheritance_links(inheritance_batch, csharp_files, imports_map) @@ -117,6 +119,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: None, diagnostics=call_resolution_diagnostics, ) + if job_id: + job_manager.update_job(job_id, status_message="Writing function CALLS edges...") writer.write_function_call_groups(*resolved_calls) t2 = time.time() info_logger(f"Function calls created in {t2 - t1:.1f}s. Total post-processing: {t2 - t0:.1f}s") @@ -125,10 +129,14 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: # C++ method definitions live in .cpp while the Class node lives in .h. # The per-file write cannot create these edges reliably due to ordering; # this single repo-scoped pass runs after every node is in the graph. + if job_id: + job_manager.update_job(job_id, status_message="Linking C++ class-function edges...") info_logger("[CPP] Linking C++ out-of-line method definitions to their classes...") writer.write_cpp_class_function_links(resolved_repo_path_str) # ── Spring injection edges (#887) ───────────────────────────────────────── + if job_id: + job_manager.update_job(job_id, status_message="Processing Spring injection edges...") spring_inject_batch = [] for fd in all_file_data: injections = fd.get("spring_injections") @@ -155,6 +163,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: # ── Maven / Gradle build graph (#888) ──────────────────────────────────── if not is_dependency and path.is_dir(): + if job_id: + job_manager.update_job(job_id, status_message="Processing Maven build graph...") try: from ...tools.languages.maven import parse_repo_maven maven_data = parse_repo_maven(path.resolve()) @@ -163,6 +173,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: except Exception as _me: info_logger(f"[MAVEN] Build graph failed (skipping): {_me}") + if job_id: + job_manager.update_job(job_id, status_message="Processing Gradle build graph...") try: from ...tools.languages.gradle import parse_repo_gradle gradle_data = parse_repo_gradle(path.resolve()) @@ -172,6 +184,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: info_logger(f"[GRADLE] Build graph failed (skipping): {_ge}") # ── ORM / datasource code linkage (#843) ───────────────────────────────── + if job_id: + job_manager.update_job(job_id, status_message="Processing ORM mappings...") orm_batch = [] for fd in all_file_data: orm_mappings = fd.get("orm_mappings") @@ -189,6 +203,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: # ── MyBatis XML mapper READS / WRITES edges ─────────────────────────────── if not is_dependency and path.is_dir(): + if job_id: + job_manager.update_job(job_id, status_message="Processing MyBatis XML mappers...") try: from ...tools.languages.mybatis import find_and_parse_mybatis_mappers mybatis_batch = find_and_parse_mybatis_mappers(path.resolve()) @@ -200,6 +216,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: # ── Phase 4: embedding generation (optional, config-gated) ──────────────── from ...cli.config_manager import get_config_value as _gcv if (_gcv("ENABLE_VECTOR_RESOLVE") or "false").lower() == "true": + if job_id: + job_manager.update_job(job_id, status_message="Generating embeddings...") try: from .embeddings import EmbeddingPipeline repo_path_str = str(path.resolve()) @@ -211,6 +229,8 @@ async def process_file(file: Path) -> Optional[Dict[str, Any]]: # ── Phase 5: inheritance-aware re-resolution (optional, config-gated) ───── if (_gcv("ENABLE_INHERIT_RESOLVE") or "false").lower() == "true": + if job_id: + job_manager.update_job(job_id, status_message="Running inheritance re-resolution...") try: from .resolution.post_resolution import run_inheritance_reresolve vector_resolver = None