java-codebase-rag 0.10.0__tar.gz → 0.10.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 (74) hide show
  1. {java_codebase_rag-0.10.0/src/java_codebase_rag.egg-info → java_codebase_rag-0.10.2}/PKG-INFO +1 -1
  2. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/pyproject.toml +1 -1
  3. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/mcp/mcp_v2.py +44 -9
  4. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/search/search_lexical.py +20 -8
  5. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/search/search_scoring.py +26 -0
  6. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2/src/java_codebase_rag.egg-info}/PKG-INFO +1 -1
  7. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/LICENSE +0 -0
  8. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/README.md +0 -0
  9. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/setup.cfg +0 -0
  10. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/__init__.py +0 -0
  11. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/_fdlimit.py +0 -0
  12. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/_stdio.py +0 -0
  13. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/_version.py +0 -0
  14. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/absence/__init__.py +0 -0
  15. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/absence/absence_diagnosis.py +0 -0
  16. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/absence/absence_types.py +0 -0
  17. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/absence/absence_vocab.py +0 -0
  18. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/analysis/__init__.py +0 -0
  19. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/analysis/pr_analysis.py +0 -0
  20. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/analysis/resolve_service.py +0 -0
  21. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/ast/__init__.py +0 -0
  22. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/ast/ast_java.py +0 -0
  23. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/ast/brownfield_events.py +0 -0
  24. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/ast/chunk_heuristics.py +0 -0
  25. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/cli.py +0 -0
  26. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/cli_format.py +0 -0
  27. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/cli_progress.py +0 -0
  28. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/config.py +0 -0
  29. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/__init__.py +0 -0
  30. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/build_ast_graph.py +0 -0
  31. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/graph_enrich.py +0 -0
  32. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/graph_types.py +0 -0
  33. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/java_ontology.py +0 -0
  34. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/ladybug_queries.py +0 -0
  35. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/graph/path_filtering.py +0 -0
  36. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/index/__init__.py +0 -0
  37. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/index/java_index_flow_lancedb.py +0 -0
  38. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/index/java_index_v1_common.py +0 -0
  39. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/install_data/__init__.py +0 -0
  40. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/install_data/agents/explorer-rag-cli.md +0 -0
  41. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +0 -0
  42. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +0 -0
  43. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +0 -0
  44. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/installer.py +0 -0
  45. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/jrag.py +0 -0
  46. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/jrag_envelope.py +0 -0
  47. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/jrag_hints.py +0 -0
  48. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/jrag_render.py +0 -0
  49. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/lance_optimize.py +0 -0
  50. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/mcp/__init__.py +0 -0
  51. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/mcp/mcp_hints.py +0 -0
  52. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/mcp/server.py +0 -0
  53. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/pipeline.py +0 -0
  54. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/progress.py +0 -0
  55. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/read_payloads.py +0 -0
  56. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/search/__init__.py +0 -0
  57. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/search/index_common.py +0 -0
  58. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/search/search_lancedb.py +0 -0
  59. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/__init__.py +0 -0
  60. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/client.py +0 -0
  61. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/daemon.py +0 -0
  62. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/lock.py +0 -0
  63. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/paths.py +0 -0
  64. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/protocol.py +0 -0
  65. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/server.py +0 -0
  66. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/warm.py +0 -0
  67. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag/watch/watcher.py +0 -0
  68. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag.egg-info/SOURCES.txt +0 -0
  69. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag.egg-info/dependency_links.txt +0 -0
  70. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag.egg-info/entry_points.txt +0 -0
  71. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag.egg-info/requires.txt +0 -0
  72. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/src/java_codebase_rag.egg-info/top_level.txt +0 -0
  73. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/tests/test_config_watch.py +0 -0
  74. {java_codebase_rag-0.10.0 → java_codebase_rag-0.10.2}/tests/test_docs_watch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-codebase-rag
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: MCP server for semantic + structural search over Java codebases
5
5
  Author: HumanBean17
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "java-codebase-rag"
7
- version = "0.10.0"
7
+ version = "0.10.2"
8
8
  description = "MCP server for semantic + structural search over Java codebases"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -50,15 +50,49 @@ from java_codebase_rag.graph.ladybug_queries import LadybugGraph, OVERRIDE_AXIS_
50
50
  from java_codebase_rag.mcp.mcp_hints import MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION
51
51
 
52
52
  # The vector stack (lancedb/torch, reached via search_lancedb) is optional — it is absent on
53
- # graph-only installs (macOS Intel). Import eagerly when available so ``run_search``/``TABLES``
54
- # exist as module attributes (tests monkeypatch ``mcp_v2.run_search``; callers use ``TABLES``);
55
- # fall back to sentinels on ImportError so importing this module never fails and ``search_v2``
56
- # can return a clean "vector search unavailable" envelope instead of crashing.
57
- try:
58
- from java_codebase_rag.search.search_lancedb import TABLES, run_search
59
- except ImportError: # graph-only install: no torch/lancedb
60
- TABLES = {}
61
- run_search = None
53
+ # graph-only installs (macOS Intel). It is imported LAZILY on the first ``search_v2`` call via
54
+ # ``_ensure_vector_backend``, NOT at module load. Reason: ``search_lancedb`` imports
55
+ # ``sentence_transformers``/``torch`` at its own top level (~2.8s), and the warm ``jrag watch``
56
+ # client reconstructs ``SearchOutput`` from this module (see ``watch/client._reconstruct``)
57
+ # without ever calling the vector backend — an eager import here would force every warm CLI
58
+ # read to pay that ~2.8s, masking the daemon's win.
59
+ #
60
+ # ``run_search`` uses a distinct ``_NOT_LOADED`` sentinel (not ``None``) for the unloaded state,
61
+ # so ``None`` unambiguously means "lexical/disabled". This lets tests force the lexical path by
62
+ # monkeypatching ``run_search=None`` (``_ensure_vector_backend`` treats that as authoritative and
63
+ # will not overwrite it), while a non-None monkeypatch drives the vector path. ``TABLES`` is
64
+ # ``{}`` until first successful load (only read in the vector path, where it is guaranteed set).
65
+ _NOT_LOADED = object()
66
+ run_search: Any = _NOT_LOADED
67
+ TABLES: dict = {}
68
+ _vector_backend_lock = threading.Lock()
69
+
70
+
71
+ def _ensure_vector_backend() -> None:
72
+ """Populate ``run_search``/``TABLES`` from ``search_lancedb`` on first use.
73
+
74
+ No-op once ``run_search`` has left the ``_NOT_LOADED`` state — real callable (vector path),
75
+ ``None`` (graph-only install or a test-forced lexical mode), or a test monkeypatch.
76
+ Thread-safe (double-checked locking): the MCP server dispatches ``search_v2`` via
77
+ ``asyncio.to_thread``, so two concurrent first-batch searches could otherwise race the
78
+ sentinel mutation. On ImportError (graph-only) ``run_search`` becomes ``None`` and
79
+ ``search_v2`` takes the lexical fallback path. A non-ImportError propagates (caught by
80
+ ``search_v2``'s outer handler) and leaves ``run_search`` as ``_NOT_LOADED`` so the next
81
+ call retries rather than poisoning the process.
82
+ """
83
+ global run_search, TABLES
84
+ if run_search is not _NOT_LOADED:
85
+ return
86
+ with _vector_backend_lock:
87
+ if run_search is not _NOT_LOADED:
88
+ return
89
+ try:
90
+ from java_codebase_rag.search.search_lancedb import TABLES as _TABLES, run_search as _run_search
91
+ except ImportError: # graph-only install: no torch/lancedb — lexical fallback
92
+ run_search = None
93
+ else:
94
+ run_search = _run_search
95
+ TABLES = _TABLES
62
96
  __all__ = [
63
97
  "search_v2",
64
98
  "find_v2",
@@ -913,6 +947,7 @@ def search_v2(
913
947
  if nf and (err := _nodefilter_applicability_error("symbol", nf)):
914
948
  _log_fail_loud("applicability")
915
949
  return SearchOutput(success=False, message=err, advisories=[], limit=None, offset=None)
950
+ _ensure_vector_backend()
916
951
  advisories: list[str] = []
917
952
  lexical_mode = run_search is None
918
953
  if lexical_mode:
@@ -32,6 +32,7 @@ from java_codebase_rag.search.search_scoring import (
32
32
  _dedup_by_fqn,
33
33
  _query_tokens,
34
34
  _split_identifier,
35
+ build_fts_query,
35
36
  )
36
37
 
37
38
  if TYPE_CHECKING:
@@ -272,8 +273,10 @@ def run_lexical_search(
272
273
  BM25-first (fork A): when the LadybugDB ``sym_fts`` index exists, candidates are
273
274
  fetched DB-side via Okapi BM25 over ``Symbol.search_text`` (killing the bounded
274
275
  Python scan that silently missed matches past the cap on large repos) and then
275
- re-ranked here by the name/type/fqn/role heuristic. Falls back to that heuristic
276
- scan when the FTS index or extension is unavailable (older graph, offline first run).
276
+ re-ranked here by the name/type/fqn/role heuristic. The query is pre-split with the
277
+ same tokenizer as ``search_text`` so pasted camelCase identifiers match. Falls back
278
+ to the heuristic scan when the FTS index or extension is unavailable (older graph,
279
+ offline first run), or when the query is degenerate / the BM25 result is empty.
277
280
 
278
281
  Raises ``RuntimeError`` (message contains "lexical search unavailable") if no
279
282
  symbol graph exists — the caller maps that to a clean failure envelope. Returns
@@ -292,14 +295,23 @@ def run_lexical_search(
292
295
  g = graph or LadybugGraph.get()
293
296
 
294
297
  # --- candidate fetch: BM25 (FTS) preferred, heuristic scan fallback ---
298
+ # FTS indexes Symbol.search_text (camelCase-split tokens); pre-split the query the
299
+ # same way (build_fts_query) so a pasted identifier like "DistributionChunkService"
300
+ # matches — LadybugDB FTS's own tokenizer does not split camelCase. An empty split
301
+ # (degenerate / stopword-only query), an unavailable FTS index, OR an empty BM25
302
+ # result all fall back to the heuristic scan, which yields role-ranked output for
303
+ # degenerate queries and covers selective filters where BM25's top-K thins to nil.
295
304
  bm25_scores: dict[str, float] = {}
296
305
  use_fts = False
297
- fts = _try_fts_candidates(g, query, filter, path_contains)
298
- if fts is not None:
299
- rows = fts["rows"]
300
- bm25_scores = fts["scores"]
301
- use_fts = True
302
- else:
306
+ rows: list[dict] | None = None
307
+ q_fts = build_fts_query(query)
308
+ if q_fts:
309
+ fts = _try_fts_candidates(g, q_fts, filter, path_contains)
310
+ if fts is not None and fts["rows"]:
311
+ rows = fts["rows"]
312
+ bm25_scores = fts["scores"]
313
+ use_fts = True
314
+ if rows is None:
303
315
  where, params = _lexical_where(filter, path_contains=path_contains)
304
316
  # Always exclude structural Symbol nodes. Files and packages are :Symbol-labeled
305
317
  # (kind='file'/'package') but aren't searchable code declarations — without this
@@ -136,6 +136,32 @@ def _split_identifier(name: str) -> list[str]:
136
136
  return [p for p in parts if p]
137
137
 
138
138
 
139
+ # Alphanumeric-word extractor for FTS query building (mirrors the index side, which
140
+ # runs the same regex over name/fqn/signature/annotations/capabilities/package fields).
141
+ _FTS_WORD_RE = re.compile(r"[A-Za-z][A-Za-z0-9]*")
142
+
143
+
144
+ def build_fts_query(text: str) -> str:
145
+ """Tokenize a search query into the ``Symbol.search_text`` token space (fork A).
146
+
147
+ ``search_text`` is indexed from ``_split_identifier`` tokens (camelCase / snake_case
148
+ split, lowercased). LadybugDB FTS's own tokenizer does NOT split camelCase, so a raw
149
+ pasted identifier like ``DistributionChunkService`` would match nothing — this
150
+ extracts alphanumeric words from the query and splits each via ``_split_identifier``
151
+ so the query lands in the index's token space. Tokens shorter than 2 chars are
152
+ dropped (mirrors the index side); duplicates collapse. Returns ``""`` for a query
153
+ with no usable tokens — the caller then falls back to the heuristic / role listing.
154
+ """
155
+ out: list[str] = []
156
+ seen: set[str] = set()
157
+ for word in _FTS_WORD_RE.findall(text or ""):
158
+ for tok in _split_identifier(word):
159
+ if len(tok) >= 2 and tok not in seen:
160
+ seen.add(tok)
161
+ out.append(tok)
162
+ return " ".join(out)
163
+
164
+
139
165
  def _symbol_bonus(r: dict, query_toks: set[str]) -> float:
140
166
  """Symbol-name overlap + action-verb bump for java chunks.
141
167
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-codebase-rag
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: MCP server for semantic + structural search over Java codebases
5
5
  Author: HumanBean17
6
6
  License-Expression: MIT