codespine 0.6.0__tar.gz → 0.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {codespine-0.6.0 → codespine-0.6.2}/PKG-INFO +1 -1
  2. {codespine-0.6.0 → codespine-0.6.2}/codespine/__init__.py +1 -1
  3. {codespine-0.6.0 → codespine-0.6.2}/codespine/cli.py +37 -10
  4. {codespine-0.6.0 → codespine-0.6.2}/codespine/db/store.py +46 -9
  5. {codespine-0.6.0 → codespine-0.6.2}/codespine/mcp/server.py +10 -2
  6. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/PKG-INFO +1 -1
  7. {codespine-0.6.0 → codespine-0.6.2}/pyproject.toml +1 -1
  8. {codespine-0.6.0 → codespine-0.6.2}/LICENSE +0 -0
  9. {codespine-0.6.0 → codespine-0.6.2}/README.md +0 -0
  10. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/__init__.py +0 -0
  11. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/community.py +0 -0
  12. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/context.py +0 -0
  13. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/coupling.py +0 -0
  14. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/crossmodule.py +0 -0
  15. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/deadcode.py +0 -0
  16. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/flow.py +0 -0
  17. {codespine-0.6.0 → codespine-0.6.2}/codespine/analysis/impact.py +0 -0
  18. {codespine-0.6.0 → codespine-0.6.2}/codespine/config.py +0 -0
  19. {codespine-0.6.0 → codespine-0.6.2}/codespine/db/__init__.py +0 -0
  20. {codespine-0.6.0 → codespine-0.6.2}/codespine/db/schema.py +0 -0
  21. {codespine-0.6.0 → codespine-0.6.2}/codespine/diff/__init__.py +0 -0
  22. {codespine-0.6.0 → codespine-0.6.2}/codespine/diff/branch_diff.py +0 -0
  23. {codespine-0.6.0 → codespine-0.6.2}/codespine/indexer/__init__.py +0 -0
  24. {codespine-0.6.0 → codespine-0.6.2}/codespine/indexer/call_resolver.py +0 -0
  25. {codespine-0.6.0 → codespine-0.6.2}/codespine/indexer/engine.py +0 -0
  26. {codespine-0.6.0 → codespine-0.6.2}/codespine/indexer/java_parser.py +0 -0
  27. {codespine-0.6.0 → codespine-0.6.2}/codespine/indexer/symbol_builder.py +0 -0
  28. {codespine-0.6.0 → codespine-0.6.2}/codespine/mcp/__init__.py +0 -0
  29. {codespine-0.6.0 → codespine-0.6.2}/codespine/noise/__init__.py +0 -0
  30. {codespine-0.6.0 → codespine-0.6.2}/codespine/noise/blocklist.py +0 -0
  31. {codespine-0.6.0 → codespine-0.6.2}/codespine/overlay/__init__.py +0 -0
  32. {codespine-0.6.0 → codespine-0.6.2}/codespine/overlay/git_state.py +0 -0
  33. {codespine-0.6.0 → codespine-0.6.2}/codespine/overlay/merge.py +0 -0
  34. {codespine-0.6.0 → codespine-0.6.2}/codespine/overlay/store.py +0 -0
  35. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/__init__.py +0 -0
  36. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/bm25.py +0 -0
  37. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/fuzzy.py +0 -0
  38. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/hybrid.py +0 -0
  39. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/rrf.py +0 -0
  40. {codespine-0.6.0 → codespine-0.6.2}/codespine/search/vector.py +0 -0
  41. {codespine-0.6.0 → codespine-0.6.2}/codespine/watch/__init__.py +0 -0
  42. {codespine-0.6.0 → codespine-0.6.2}/codespine/watch/watcher.py +0 -0
  43. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/SOURCES.txt +0 -0
  44. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/dependency_links.txt +0 -0
  45. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/entry_points.txt +0 -0
  46. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/requires.txt +0 -0
  47. {codespine-0.6.0 → codespine-0.6.2}/codespine.egg-info/top_level.txt +0 -0
  48. {codespine-0.6.0 → codespine-0.6.2}/gindex.py +0 -0
  49. {codespine-0.6.0 → codespine-0.6.2}/setup.cfg +0 -0
  50. {codespine-0.6.0 → codespine-0.6.2}/tests/test_branch_diff_normalize.py +0 -0
  51. {codespine-0.6.0 → codespine-0.6.2}/tests/test_call_resolver.py +0 -0
  52. {codespine-0.6.0 → codespine-0.6.2}/tests/test_community_detection.py +0 -0
  53. {codespine-0.6.0 → codespine-0.6.2}/tests/test_deadcode.py +0 -0
  54. {codespine-0.6.0 → codespine-0.6.2}/tests/test_index_and_hybrid.py +0 -0
  55. {codespine-0.6.0 → codespine-0.6.2}/tests/test_java_parser.py +0 -0
  56. {codespine-0.6.0 → codespine-0.6.2}/tests/test_multimodule_index.py +0 -0
  57. {codespine-0.6.0 → codespine-0.6.2}/tests/test_overlay.py +0 -0
  58. {codespine-0.6.0 → codespine-0.6.2}/tests/test_search_ranking.py +0 -0
  59. {codespine-0.6.0 → codespine-0.6.2}/tests/test_store_recovery.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespine
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Local Java code intelligence indexer backed by a graph database
5
5
  Author: CodeSpine contributors
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
1
  """CodeSpine package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.6.0"
4
+ __version__ = "0.6.2"
@@ -751,17 +751,25 @@ def clear_project_cmd(project_id: str, allow_running: bool) -> None:
751
751
  if not allow_running and _is_running():
752
752
  click.secho("Stop MCP first ('codespine stop') to modify index.", fg="yellow")
753
753
  return
754
- store = GraphStore(read_only=False)
755
- recs = store.query_records(
756
- "MATCH (p:Project) WHERE p.id = $pid RETURN p.id as id, p.path as path",
757
- {"pid": project_id},
758
- )
754
+ try:
755
+ store = GraphStore(read_only=False)
756
+ recs = store.query_records(
757
+ "MATCH (p:Project) WHERE p.id = $pid RETURN p.id as id, p.path as path",
758
+ {"pid": project_id},
759
+ )
760
+ except Exception as exc:
761
+ click.secho(f"DB is corrupted ({exc}). Use 'codespine force-reset' to wipe all data.", fg="red")
762
+ return
759
763
  if not recs:
760
764
  click.secho(f"Project '{project_id}' not found in index.", fg="yellow")
761
765
  return
762
766
  project_path = recs[0].get("path", "")
763
- store.clear_analysis_artifacts()
764
- store.clear_project(project_id)
767
+ try:
768
+ store.clear_analysis_artifacts()
769
+ store.clear_project(project_id)
770
+ except Exception as exc:
771
+ click.secho(f"DB write failed ({exc}). Use 'codespine force-reset' to recover.", fg="red")
772
+ return
765
773
  store.overlay_store.clear_project(project_id)
766
774
  meta_path = JavaIndexer._meta_cache_path(project_id)
767
775
  if os.path.exists(meta_path):
@@ -769,6 +777,8 @@ def clear_project_cmd(project_id: str, allow_running: bool) -> None:
769
777
  os.remove(meta_path)
770
778
  except OSError:
771
779
  pass
780
+ # Update the read replica so read-only callers (stats, MCP) see the change.
781
+ GraphStore.snapshot_to_read_replica()
772
782
  click.secho(f"Cleared project '{project_id}' (was at {project_path}).", fg="green")
773
783
 
774
784
 
@@ -784,9 +794,23 @@ def clear_index_cmd(allow_running: bool) -> None:
784
794
  if not allow_running and _is_running():
785
795
  click.secho("Stop MCP first ('codespine stop') to modify index.", fg="yellow")
786
796
  return
787
- store = GraphStore(read_only=False)
788
- projects = store.query_records("MATCH (p:Project) RETURN p.id as id")
789
- store.rebuild_empty_db()
797
+ try:
798
+ store = GraphStore(read_only=False)
799
+ projects = store.query_records("MATCH (p:Project) RETURN p.id as id")
800
+ except Exception:
801
+ # DB is corrupted — can't even open it. Force-delete everything.
802
+ click.secho("DB is corrupted. Running force-reset instead...", fg="yellow")
803
+ removed = GraphStore.force_delete_all_data()
804
+ click.secho(f"Force-reset complete. {len(removed)} path(s) removed. Index is now empty.", fg="green")
805
+ return
806
+ try:
807
+ store.rebuild_empty_db()
808
+ except Exception as exc:
809
+ # rebuild_empty_db failed even with fallbacks — force-delete.
810
+ click.secho(f"rebuild failed ({exc}). Running force-reset...", fg="yellow")
811
+ GraphStore.force_delete_all_data()
812
+ click.secho("Force-reset complete. Index is now empty.", fg="green")
813
+ return
790
814
  store.overlay_store.clear_all()
791
815
  for p in projects:
792
816
  meta_path = JavaIndexer._meta_cache_path(p["id"])
@@ -795,6 +819,9 @@ def clear_index_cmd(allow_running: bool) -> None:
795
819
  os.remove(meta_path)
796
820
  except OSError:
797
821
  pass
822
+ # Publish an empty read replica so that read-only callers (stats, MCP)
823
+ # immediately see the cleared state and the MCP daemon hot-reloads.
824
+ GraphStore.snapshot_to_read_replica()
798
825
  click.secho(f"Cleared {len(projects)} project(s). Index is now empty.", fg="green")
799
826
 
800
827
 
@@ -550,21 +550,58 @@ class GraphStore:
550
550
  def rebuild_empty_db(self) -> None:
551
551
  self._recycle_conn()
552
552
  path = SETTINGS.db_path
553
+ # Remove the DB directory AND any stale WAL / lock files
553
554
  self._remove_db_path(path)
555
+ for suffix in (".wal", ".lock"):
556
+ sidecar = path + suffix
557
+ if os.path.exists(sidecar):
558
+ try:
559
+ os.remove(sidecar)
560
+ except OSError:
561
+ pass
562
+
563
+ # Also remove the read replica so that read-only callers (stats, MCP)
564
+ # don't continue to see stale data from before the wipe.
565
+ for stale in [
566
+ SETTINGS.db_snapshot_path,
567
+ SETTINGS.db_snapshot_path + ".tmp",
568
+ SETTINGS.db_snapshot_path + ".updated",
569
+ ]:
570
+ self._remove_db_path(stale)
571
+
554
572
  # Kuzu may retain stale internal state from a previous failed open of
555
- # this path (e.g. after Ctrl+C mid-write). If re-opening the just-
556
- # deleted path raises, fall back to a clean /tmp location so the
557
- # command succeeds rather than leaving the user stuck.
573
+ # this path (e.g. after Ctrl+C mid-write). The in-process C++ state
574
+ # is poisoned and will raise "unordered_map::at: key not found" on any
575
+ # new kuzu.Database() call even for a freshly deleted path.
576
+ #
577
+ # Strategy: try primary → try /tmp fallback → force-delete everything
578
+ # and re-import kuzu to get a clean C++ state.
558
579
  try:
559
580
  self.db = self._open_db(path)
560
- except Exception as exc:
581
+ except Exception as exc1:
582
+ LOGGER.warning("rebuild_empty_db: primary path failed (%s)", exc1)
561
583
  fallback = os.path.join("/tmp", ".codespine_db")
562
- LOGGER.warning(
563
- "Could not open fresh DB at %s after rebuild (%s); falling back to %s",
564
- path, exc, fallback,
565
- )
566
584
  self._remove_db_path(fallback)
567
- self.db = self._open_db(fallback)
585
+ for suffix in (".wal", ".lock"):
586
+ sidecar = fallback + suffix
587
+ if os.path.exists(sidecar):
588
+ try:
589
+ os.remove(sidecar)
590
+ except OSError:
591
+ pass
592
+ try:
593
+ self.db = self._open_db(fallback)
594
+ except Exception as exc2:
595
+ # Nuclear option: force-delete all files and reimport kuzu
596
+ # so the C++ runtime starts from a completely clean state.
597
+ LOGGER.warning("rebuild_empty_db: fallback also failed (%s); force-resetting", exc2)
598
+ self.force_delete_all_data()
599
+ import importlib
600
+ importlib.reload(kuzu)
601
+ try:
602
+ self.db = kuzu.Database(path, buffer_pool_size=_WRITE_BUFFER_POOL_SIZE)
603
+ except TypeError:
604
+ self.db = kuzu.Database(path)
568
605
  self._tls = threading.local()
569
606
  ensure_schema(self._conn())
570
607
 
@@ -1227,9 +1227,13 @@ def build_mcp_server(store, repo_path_provider):
1227
1227
  timeout=60,
1228
1228
  )
1229
1229
  if proc.returncode != 0:
1230
+ error_text = (proc.stderr.strip() or proc.stdout.strip())[:300]
1230
1231
  return {
1231
1232
  "available": False,
1232
- "note": f"Reset failed: {proc.stderr.strip() or proc.stdout.strip()}",
1233
+ "note": (
1234
+ f"Reset failed: {error_text}. "
1235
+ "If this is a buffer pool or DB corruption error, use force_reset_index() instead."
1236
+ ),
1233
1237
  }
1234
1238
  return {
1235
1239
  "available": True,
@@ -1268,9 +1272,13 @@ def build_mcp_server(store, repo_path_provider):
1268
1272
  timeout=120,
1269
1273
  )
1270
1274
  if proc.returncode != 0:
1275
+ error_text = (proc.stderr.strip() or proc.stdout.strip())[:300]
1271
1276
  return {
1272
1277
  "available": False,
1273
- "note": f"Reset failed: {proc.stderr.strip() or proc.stdout.strip()}",
1278
+ "note": (
1279
+ f"Reset failed: {error_text}. "
1280
+ "If this is a buffer pool or DB corruption error, use force_reset_index() instead."
1281
+ ),
1274
1282
  }
1275
1283
 
1276
1284
  cleared = [{"project_id": p["id"], "path": p["path"]} for p in projects]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespine
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Local Java code intelligence indexer backed by a graph database
5
5
  Author: CodeSpine contributors
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codespine"
7
- version = "0.6.0"
7
+ version = "0.6.2"
8
8
  description = "Local Java code intelligence indexer backed by a graph database"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes