codespine 0.9.1__tar.gz → 0.9.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 (62) hide show
  1. {codespine-0.9.1 → codespine-0.9.2}/PKG-INFO +1 -1
  2. {codespine-0.9.1 → codespine-0.9.2}/codespine/__init__.py +1 -1
  3. {codespine-0.9.1 → codespine-0.9.2}/codespine/db/store.py +111 -61
  4. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/PKG-INFO +1 -1
  5. {codespine-0.9.1 → codespine-0.9.2}/pyproject.toml +1 -1
  6. {codespine-0.9.1 → codespine-0.9.2}/LICENSE +0 -0
  7. {codespine-0.9.1 → codespine-0.9.2}/README.md +0 -0
  8. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/__init__.py +0 -0
  9. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/community.py +0 -0
  10. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/context.py +0 -0
  11. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/coupling.py +0 -0
  12. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/crossmodule.py +0 -0
  13. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/deadcode.py +0 -0
  14. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/flow.py +0 -0
  15. {codespine-0.9.1 → codespine-0.9.2}/codespine/analysis/impact.py +0 -0
  16. {codespine-0.9.1 → codespine-0.9.2}/codespine/cli.py +0 -0
  17. {codespine-0.9.1 → codespine-0.9.2}/codespine/config.py +0 -0
  18. {codespine-0.9.1 → codespine-0.9.2}/codespine/db/__init__.py +0 -0
  19. {codespine-0.9.1 → codespine-0.9.2}/codespine/db/schema.py +0 -0
  20. {codespine-0.9.1 → codespine-0.9.2}/codespine/diff/__init__.py +0 -0
  21. {codespine-0.9.1 → codespine-0.9.2}/codespine/diff/branch_diff.py +0 -0
  22. {codespine-0.9.1 → codespine-0.9.2}/codespine/guide.py +0 -0
  23. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/__init__.py +0 -0
  24. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/call_resolver.py +0 -0
  25. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/di_resolver.py +0 -0
  26. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/engine.py +0 -0
  27. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/java_parser.py +0 -0
  28. {codespine-0.9.1 → codespine-0.9.2}/codespine/indexer/symbol_builder.py +0 -0
  29. {codespine-0.9.1 → codespine-0.9.2}/codespine/mcp/__init__.py +0 -0
  30. {codespine-0.9.1 → codespine-0.9.2}/codespine/mcp/server.py +0 -0
  31. {codespine-0.9.1 → codespine-0.9.2}/codespine/noise/__init__.py +0 -0
  32. {codespine-0.9.1 → codespine-0.9.2}/codespine/noise/blocklist.py +0 -0
  33. {codespine-0.9.1 → codespine-0.9.2}/codespine/overlay/__init__.py +0 -0
  34. {codespine-0.9.1 → codespine-0.9.2}/codespine/overlay/git_state.py +0 -0
  35. {codespine-0.9.1 → codespine-0.9.2}/codespine/overlay/merge.py +0 -0
  36. {codespine-0.9.1 → codespine-0.9.2}/codespine/overlay/store.py +0 -0
  37. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/__init__.py +0 -0
  38. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/bm25.py +0 -0
  39. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/fuzzy.py +0 -0
  40. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/hybrid.py +0 -0
  41. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/rrf.py +0 -0
  42. {codespine-0.9.1 → codespine-0.9.2}/codespine/search/vector.py +0 -0
  43. {codespine-0.9.1 → codespine-0.9.2}/codespine/watch/__init__.py +0 -0
  44. {codespine-0.9.1 → codespine-0.9.2}/codespine/watch/git_hook.py +0 -0
  45. {codespine-0.9.1 → codespine-0.9.2}/codespine/watch/watcher.py +0 -0
  46. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/SOURCES.txt +0 -0
  47. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/dependency_links.txt +0 -0
  48. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/entry_points.txt +0 -0
  49. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/requires.txt +0 -0
  50. {codespine-0.9.1 → codespine-0.9.2}/codespine.egg-info/top_level.txt +0 -0
  51. {codespine-0.9.1 → codespine-0.9.2}/gindex.py +0 -0
  52. {codespine-0.9.1 → codespine-0.9.2}/setup.cfg +0 -0
  53. {codespine-0.9.1 → codespine-0.9.2}/tests/test_branch_diff_normalize.py +0 -0
  54. {codespine-0.9.1 → codespine-0.9.2}/tests/test_call_resolver.py +0 -0
  55. {codespine-0.9.1 → codespine-0.9.2}/tests/test_community_detection.py +0 -0
  56. {codespine-0.9.1 → codespine-0.9.2}/tests/test_deadcode.py +0 -0
  57. {codespine-0.9.1 → codespine-0.9.2}/tests/test_index_and_hybrid.py +0 -0
  58. {codespine-0.9.1 → codespine-0.9.2}/tests/test_java_parser.py +0 -0
  59. {codespine-0.9.1 → codespine-0.9.2}/tests/test_multimodule_index.py +0 -0
  60. {codespine-0.9.1 → codespine-0.9.2}/tests/test_overlay.py +0 -0
  61. {codespine-0.9.1 → codespine-0.9.2}/tests/test_search_ranking.py +0 -0
  62. {codespine-0.9.1 → codespine-0.9.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.9.1
3
+ Version: 0.9.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.9.1"
4
+ __version__ = "0.9.2"
@@ -297,14 +297,20 @@ class GraphStore:
297
297
  )
298
298
 
299
299
  def upsert_files_batch(self, records: list[dict[str, Any]]) -> None:
300
- for record in records:
301
- self.upsert_file(
302
- file_id=record["id"],
303
- path=record["path"],
304
- project_id=record["project_id"],
305
- is_test=bool(record["is_test"]),
306
- digest=record["hash"],
307
- )
300
+ if not records:
301
+ return
302
+ self.execute(
303
+ """
304
+ UNWIND $rows AS row
305
+ MERGE (f:File {id: row.id})
306
+ SET f.path = row.path,
307
+ f.project_id = row.project_id,
308
+ f.is_test = row.is_test,
309
+ f.hash = row.hash
310
+ """,
311
+ {"rows": [{"id": r["id"], "path": r["path"], "project_id": r["project_id"],
312
+ "is_test": bool(r["is_test"]), "hash": r["hash"]} for r in records]},
313
+ )
308
314
 
309
315
  def upsert_class(self, class_id: str, fqcn: str, name: str, package: str, file_id: str) -> None:
310
316
  self.execute(
@@ -322,14 +328,20 @@ class GraphStore:
322
328
  )
323
329
 
324
330
  def upsert_classes_batch(self, records: list[dict[str, Any]]) -> None:
325
- for record in records:
326
- self.upsert_class(
327
- class_id=record["id"],
328
- fqcn=record["fqcn"],
329
- name=record["name"],
330
- package=record["package"],
331
- file_id=record["file_id"],
332
- )
331
+ if not records:
332
+ return
333
+ self.execute(
334
+ """
335
+ UNWIND $rows AS row
336
+ MERGE (c:Class {id: row.id})
337
+ SET c.fqcn = row.fqcn,
338
+ c.name = row.name,
339
+ c.package = row.package,
340
+ c.file_id = row.file_id
341
+ """,
342
+ {"rows": [{"id": r["id"], "fqcn": r["fqcn"], "name": r["name"],
343
+ "package": r["package"], "file_id": r["file_id"]} for r in records]},
344
+ )
333
345
 
334
346
  def upsert_method(
335
347
  self,
@@ -370,17 +382,28 @@ class GraphStore:
370
382
  )
371
383
 
372
384
  def upsert_methods_batch(self, records: list[dict[str, Any]]) -> None:
373
- for record in records:
374
- self.upsert_method(
375
- method_id=record["id"],
376
- class_id=record["class_id"],
377
- name=record["name"],
378
- signature=record["signature"],
379
- return_type=record["return_type"],
380
- modifiers=record["modifiers"],
381
- is_constructor=bool(record["is_constructor"]),
382
- is_test=bool(record["is_test"]),
383
- )
385
+ if not records:
386
+ return
387
+ # Single UNWIND: upsert node + HAS_METHOD relationship in one round-trip.
388
+ self.execute(
389
+ """
390
+ UNWIND $rows AS row
391
+ MATCH (c:Class {id: row.class_id})
392
+ MERGE (m:Method {id: row.id})
393
+ SET m.class_id = row.class_id,
394
+ m.name = row.name,
395
+ m.signature = row.signature,
396
+ m.return_type = row.return_type,
397
+ m.modifiers = row.modifiers,
398
+ m.is_constructor = row.is_constructor,
399
+ m.is_test = row.is_test
400
+ MERGE (c)-[:HAS_METHOD]->(m)
401
+ """,
402
+ {"rows": [{"id": r["id"], "class_id": r["class_id"], "name": r["name"],
403
+ "signature": r["signature"], "return_type": r["return_type"],
404
+ "modifiers": r["modifiers"], "is_constructor": bool(r["is_constructor"]),
405
+ "is_test": bool(r["is_test"])} for r in records]},
406
+ )
384
407
 
385
408
  def upsert_symbol(
386
409
  self,
@@ -421,17 +444,28 @@ class GraphStore:
421
444
  )
422
445
 
423
446
  def upsert_symbols_batch(self, records: list[dict[str, Any]]) -> None:
424
- for record in records:
425
- self.upsert_symbol(
426
- symbol_id=record["id"],
427
- kind=record["kind"],
428
- name=record["name"],
429
- fqname=record["fqname"],
430
- file_id=record["file_id"],
431
- line=int(record["line"]),
432
- col=int(record["col"]),
433
- embedding=record.get("embedding"),
434
- )
447
+ if not records:
448
+ return
449
+ # Single UNWIND: upsert node + DECLARES relationship in one round-trip.
450
+ self.execute(
451
+ """
452
+ UNWIND $rows AS row
453
+ MATCH (f:File {id: row.file_id})
454
+ MERGE (s:Symbol {id: row.id})
455
+ SET s.kind = row.kind,
456
+ s.name = row.name,
457
+ s.fqname = row.fqname,
458
+ s.file_id = row.file_id,
459
+ s.line = row.line,
460
+ s.col = row.col,
461
+ s.embedding = row.embedding
462
+ MERGE (f)-[:DECLARES]->(s)
463
+ """,
464
+ {"rows": [{"id": r["id"], "kind": r["kind"], "name": r["name"],
465
+ "fqname": r["fqname"], "file_id": r["file_id"],
466
+ "line": int(r["line"]), "col": int(r["col"]),
467
+ "embedding": r.get("embedding")} for r in records]},
468
+ )
435
469
 
436
470
  def add_call(self, source_id: str, target_id: str, confidence: float, reason: str) -> None:
437
471
  self.execute(
@@ -448,13 +482,18 @@ class GraphStore:
448
482
  )
449
483
 
450
484
  def add_calls_batch(self, records: list[dict[str, Any]]) -> None:
451
- for record in records:
452
- self.add_call(
453
- source_id=record["source_id"],
454
- target_id=record["target_id"],
455
- confidence=float(record["confidence"]),
456
- reason=record["reason"],
457
- )
485
+ if not records:
486
+ return
487
+ self.execute(
488
+ """
489
+ UNWIND $rows AS row
490
+ MATCH (src:Method {id: row.source_id}), (dst:Method {id: row.target_id})
491
+ MERGE (src)-[:CALLS {confidence: row.confidence, reason: row.reason}]->(dst)
492
+ """,
493
+ {"rows": [{"source_id": r["source_id"], "target_id": r["target_id"],
494
+ "confidence": float(r["confidence"]), "reason": r["reason"]}
495
+ for r in records]},
496
+ )
458
497
 
459
498
  def add_reference(self, rel: str, src_label: str, src_id: str, dst_label: str, dst_id: str, confidence: float) -> None:
460
499
  if rel not in {"REFERENCES_TYPE", "IMPLEMENTS", "OVERRIDES"}:
@@ -466,14 +505,25 @@ class GraphStore:
466
505
  self.execute(query, {"src_id": src_id, "dst_id": dst_id, "confidence": confidence})
467
506
 
468
507
  def add_references_batch(self, records: list[dict[str, Any]]) -> None:
469
- for record in records:
470
- self.add_reference(
471
- rel=record["rel"],
472
- src_label=record["src_label"],
473
- src_id=record["src_id"],
474
- dst_label=record["dst_label"],
475
- dst_id=record["dst_id"],
476
- confidence=float(record["confidence"]),
508
+ if not records:
509
+ return
510
+ # Group by (rel, src_label, dst_label) so each group can use a single UNWIND.
511
+ from collections import defaultdict
512
+ groups: dict[tuple, list[dict]] = defaultdict(list)
513
+ for rec in records:
514
+ rel = rec.get("rel")
515
+ if rel not in {"REFERENCES_TYPE", "IMPLEMENTS", "OVERRIDES"}:
516
+ continue
517
+ groups[(rel, rec["src_label"], rec["dst_label"])].append(
518
+ {"src_id": rec["src_id"], "dst_id": rec["dst_id"],
519
+ "confidence": float(rec["confidence"])}
520
+ )
521
+ for (rel, src_label, dst_label), batch in groups.items():
522
+ self.execute(
523
+ f"UNWIND $rows AS row "
524
+ f"MATCH (s:{src_label} {{id: row.src_id}}), (d:{dst_label} {{id: row.dst_id}}) "
525
+ f"MERGE (s)-[:{rel} {{confidence: row.confidence}}]->(d)",
526
+ {"rows": batch},
477
527
  )
478
528
 
479
529
  def add_injection(
@@ -603,17 +653,17 @@ class GraphStore:
603
653
  self.upsert_symbols_batch(batch)
604
654
  self._recycle_conn()
605
655
 
606
- # 6. Write call edges in sub-batches of 500
656
+ # 6. Write call edges in sub-batches of 500 (normalise key names to match add_calls_batch)
607
657
  for i in range(0, len(calls), self._FILE_CALL_SUB_BATCH):
608
658
  batch = calls[i: i + self._FILE_CALL_SUB_BATCH]
659
+ normalised = [
660
+ {"source_id": rec["src"], "target_id": rec["dst"],
661
+ "confidence": float(rec.get("confidence", 0.5)),
662
+ "reason": rec.get("reason", "unknown")}
663
+ for rec in batch
664
+ ]
609
665
  with self.transaction():
610
- for rec in batch:
611
- self.add_call(
612
- source_id=rec["src"],
613
- target_id=rec["dst"],
614
- confidence=float(rec.get("confidence", 0.5)),
615
- reason=rec.get("reason", "unknown"),
616
- )
666
+ self.add_calls_batch(normalised)
617
667
  self._recycle_conn()
618
668
 
619
669
  # 7. Write type relations (IMPLEMENTS, OVERRIDES, REFERENCES_TYPE)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codespine
3
- Version: 0.9.1
3
+ Version: 0.9.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.9.1"
7
+ version = "0.9.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
File without changes
File without changes