java-codebase-rag 0.9.7__py3-none-any.whl → 0.10.0__py3-none-any.whl

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 (34) hide show
  1. java_codebase_rag/analysis/pr_analysis.py +33 -3
  2. java_codebase_rag/ast/ast_java.py +2 -1
  3. java_codebase_rag/cli.py +23 -8
  4. java_codebase_rag/config.py +68 -1
  5. java_codebase_rag/graph/build_ast_graph.py +123 -4
  6. java_codebase_rag/graph/graph_types.py +109 -22
  7. java_codebase_rag/graph/ladybug_queries.py +45 -2
  8. java_codebase_rag/index/java_index_flow_lancedb.py +10 -16
  9. java_codebase_rag/install_data/agents/explorer-rag-cli.md +3 -1
  10. java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +3 -1
  11. java_codebase_rag/jrag.py +627 -661
  12. java_codebase_rag/jrag_render.py +160 -3
  13. java_codebase_rag/lance_optimize.py +11 -12
  14. java_codebase_rag/mcp/mcp_v2.py +2 -1
  15. java_codebase_rag/pipeline.py +47 -1
  16. java_codebase_rag/read_payloads.py +781 -0
  17. java_codebase_rag/search/search_lancedb.py +138 -6
  18. java_codebase_rag/search/search_lexical.py +128 -30
  19. java_codebase_rag/search/search_scoring.py +82 -0
  20. java_codebase_rag/watch/__init__.py +0 -0
  21. java_codebase_rag/watch/client.py +230 -0
  22. java_codebase_rag/watch/daemon.py +368 -0
  23. java_codebase_rag/watch/lock.py +201 -0
  24. java_codebase_rag/watch/paths.py +76 -0
  25. java_codebase_rag/watch/protocol.py +122 -0
  26. java_codebase_rag/watch/server.py +273 -0
  27. java_codebase_rag/watch/warm.py +105 -0
  28. java_codebase_rag/watch/watcher.py +352 -0
  29. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/METADATA +30 -31
  30. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/RECORD +34 -24
  31. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/WHEEL +0 -0
  32. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/entry_points.txt +0 -0
  33. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/licenses/LICENSE +0 -0
  34. {java_codebase_rag-0.9.7.dist-info → java_codebase_rag-0.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,105 @@
1
+ """Warm-resources holder for the ``jrag watch`` daemon.
2
+
3
+ ``WarmResources`` keeps two expensive objects resident for the daemon's lifetime:
4
+
5
+ * the ``SentenceTransformer`` embedding model (loaded once, reused for every
6
+ query), and
7
+ * a read-only ``LadybugGraph`` over ``cfg.ladybug_path``.
8
+
9
+ It also owns the graph copy-on-write snapshot lifecycle (design §4.7): while a
10
+ graph-reindex subprocess writes the ORIGINAL ``code_graph.lbug``, the daemon serves
11
+ graph reads from a file COPY (sidecar) so readers and the single-writer subprocess
12
+ never collide. The ``ladybug`` engine has no transaction API and is single-writer,
13
+ which is why graph builds run as subprocesses and reads are served from a copy.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import shutil
19
+ import threading
20
+ from pathlib import Path
21
+
22
+ from java_codebase_rag.config import ResolvedOperatorConfig
23
+ from java_codebase_rag.graph.ladybug_queries import LadybugGraph
24
+ from java_codebase_rag.mcp.mcp_v2 import _get_sentence_transformer
25
+
26
+ log = logging.getLogger(__name__)
27
+
28
+
29
+ class WarmResources:
30
+ """Holder for the warm embedding model + the read-only graph reader.
31
+
32
+ The model is cached automatically by ``mcp_v2``'s module global, so ``model()``
33
+ returning the same instance across calls is the warmth win. ``graph()`` returns
34
+ the snapshot reader when a snapshot is active, else the original reader. Both are
35
+ safe to call from server threads (the underlying singletons are lock-guarded).
36
+
37
+ The snapshot flip (``begin_graph_snapshot`` / ``commit_graph_snapshot``) is
38
+ serialized against ``graph()`` reads by ``self._lock`` so a reader can never
39
+ observe a torn state (e.g. read ``_snapshot_path=None`` after ``begin`` has
40
+ already reset the original singleton but before it set the sidecar, then
41
+ re-cache the original a subprocess is concurrently overwriting). Lock ordering:
42
+ ``WarmResources._lock`` is always the OUTER lock; ``LadybugGraph._lock`` (taken
43
+ inside ``get``/``reset_for_path``) is INNER — never reversed. The lock is held
44
+ only across the in-process flip + read, NEVER across the long reindex
45
+ subprocess (the watcher calls ``begin`` before it and ``commit`` after).
46
+ """
47
+
48
+ def __init__(self, cfg: ResolvedOperatorConfig) -> None:
49
+ self.cfg = cfg
50
+ self._snapshot_path: Path | None = None
51
+ # Serializes the snapshot flip against graph() reads (see class docstring).
52
+ self._lock = threading.Lock()
53
+
54
+ def model(self):
55
+ """Return the warm ``SentenceTransformer`` (cached by the module global)."""
56
+ return _get_sentence_transformer(self.cfg.embedding_model, self.cfg.embedding_device)
57
+
58
+ def graph(self) -> LadybugGraph:
59
+ """Return the current graph reader: the sidecar if a snapshot is active, else the original.
60
+
61
+ The read of ``_snapshot_path`` and the matching ``LadybugGraph.get(...)`` are
62
+ atomic w.r.t. the snapshot flip (held under ``self._lock``) so a reader always
63
+ pairs the right path with the right cached singleton.
64
+ """
65
+ with self._lock:
66
+ if self._snapshot_path is not None:
67
+ return LadybugGraph.get(str(self._snapshot_path))
68
+ return LadybugGraph.get(str(self.cfg.ladybug_path))
69
+
70
+ def begin_graph_snapshot(self) -> None:
71
+ """Copy the graph to a sidecar and serve subsequent ``graph()`` reads from it.
72
+
73
+ Drops the cached original reader (so the subprocess is free to overwrite the
74
+ original file) and switches ``graph()`` to a fresh reader on the sidecar copy.
75
+ The whole flip runs under ``self._lock`` so no ``graph()`` read can observe an
76
+ intermediate state (original reset but sidecar not yet set).
77
+ """
78
+ with self._lock:
79
+ sidecar = self.cfg.ladybug_path.with_suffix(".lbug.snapshot")
80
+ shutil.copy2(self.cfg.ladybug_path, sidecar)
81
+ LadybugGraph.reset_for_path(str(self.cfg.ladybug_path))
82
+ self._snapshot_path = sidecar
83
+
84
+ def commit_graph_snapshot(self) -> None:
85
+ """Drop the sidecar reader, remove the sidecar, and reopen the updated original.
86
+
87
+ After this call ``graph()`` reads the original again (which the subprocess has
88
+ just rewritten), and the sidecar file is gone. Idempotent no-op when no
89
+ snapshot is active. The whole flip runs under ``self._lock`` so no ``graph()``
90
+ read can observe the sidecar mid-teardown.
91
+ """
92
+ with self._lock:
93
+ if self._snapshot_path is None:
94
+ return
95
+ sidecar = self._snapshot_path
96
+ LadybugGraph.reset_for_path(str(sidecar))
97
+ try:
98
+ sidecar.unlink(missing_ok=True)
99
+ except OSError:
100
+ log.warning("Failed to remove graph snapshot sidecar %s", sidecar, exc_info=True)
101
+ finally:
102
+ # Clear state and reopen the original even if unlink failed, so a
103
+ # possibly-deleted sidecar isn't kept serving reads.
104
+ self._snapshot_path = None
105
+ LadybugGraph.reset_for_path(str(self.cfg.ladybug_path))
@@ -0,0 +1,352 @@
1
+ """File watcher + debounced per-type reindex dispatcher for ``jrag watch``.
2
+
3
+ ``SourceWatcher`` observes the project source tree and, after a debounce window,
4
+ re-runs the minimal reindex for the changed file types:
5
+
6
+ * a ``.java`` change -> vectors THEN graph. The graph reindex runs as a
7
+ subprocess under a copy-on-write snapshot (design §4.7) so concurrent graph
8
+ reads keep being served from a sidecar copy while the single-writer
9
+ subprocess overwrites the original. ``ladybug`` has no transactions and is
10
+ single-writer, which is why graph builds are subprocesses and reads are
11
+ served from a copy.
12
+ * a matching ``.sql`` / ``.yml`` / ``.yaml`` resource change -> vectors only
13
+ (the graph does not index SQL/YAML, so no snapshot is needed).
14
+
15
+ Change bursts are coalesced: the leading event arms a ``debounce_ms`` timer that
16
+ keeps getting re-armed while events keep arriving, and a single ``reindex`` fires
17
+ once the window goes quiet. ``reindex`` runs on a dedicated background thread so
18
+ the watchdog observer thread is never blocked.
19
+
20
+ The cocoindex flow indexes three sets (``**/*.java``,
21
+ ``**/src/main/resources/db/migration/*.sql``,
22
+ ``**/src/main/resources/application*.yml``/``.yaml``). There is NO shared
23
+ iterator (``iter_java_source_files`` yields ``.java`` only), so the watcher
24
+ defines this UNION and classifies each event path into a reindex kind.
25
+
26
+ All status is reported via ``on_event(kind, detail)`` callbacks (kinds:
27
+ ``indexing_started`` / ``vectors`` / ``graph`` / ``indexing_done`` / ``error``);
28
+ the watcher never prints directly.
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import logging
33
+ import threading
34
+ import time
35
+ from pathlib import Path
36
+ from typing import TYPE_CHECKING, Any, Callable
37
+
38
+ from watchdog.events import FileSystemEventHandler
39
+ from watchdog.observers import Observer
40
+ from watchdog.observers.polling import PollingObserver
41
+
42
+ from java_codebase_rag.graph.path_filtering import LayeredIgnore
43
+ from java_codebase_rag.pipeline import run_cocoindex_update, run_incremental_graph
44
+
45
+ if TYPE_CHECKING:
46
+ from java_codebase_rag.config import ResolvedOperatorConfig
47
+ from java_codebase_rag.watch.warm import WarmResources
48
+
49
+ log = logging.getLogger(__name__)
50
+
51
+ # Suffixes the watcher treats as java sources. The non-java resource types are
52
+ # matched by the glob helpers below (the cocoindex set has no shared iterator).
53
+ INDEXED_SUFFIXES: tuple[str, ...] = (".java",)
54
+ _YAML_SUFFIXES: tuple[str, ...] = (".yml", ".yaml")
55
+
56
+ # Project-relative anchored prefixes for the two non-java resource globs:
57
+ # **/src/main/resources/db/migration/*.sql
58
+ # **/src/main/resources/application*.yml / .yaml
59
+ _SQL_MIGRATION_PREFIX = "src/main/resources/db/migration/"
60
+ _RESOURCES_PREFIX = "src/main/resources/"
61
+
62
+
63
+ def _is_migration_sql(rel_posix: str) -> bool:
64
+ """True iff ``rel_posix`` is a ``.sql`` directly under a ``.../db/migration/`` dir."""
65
+ if not rel_posix.endswith(".sql"):
66
+ return False
67
+ idx = rel_posix.rfind(_SQL_MIGRATION_PREFIX)
68
+ if idx == -1:
69
+ return False
70
+ # The file must sit directly under migration/ (no further path segment).
71
+ return "/" not in rel_posix[idx + len(_SQL_MIGRATION_PREFIX):]
72
+
73
+
74
+ def _is_application_yaml(rel_posix: str) -> bool:
75
+ """True iff ``rel_posix`` is ``application*.yml``/``.yaml`` directly under
76
+ a ``.../src/main/resources/`` dir."""
77
+ name = rel_posix.rsplit("/", 1)[-1]
78
+ if not name.startswith("application"):
79
+ return False
80
+ if not name.endswith((".yml", ".yaml")):
81
+ return False
82
+ idx = rel_posix.rfind(_RESOURCES_PREFIX)
83
+ if idx == -1:
84
+ return False
85
+ return "/" not in rel_posix[idx + len(_RESOURCES_PREFIX):]
86
+
87
+
88
+ class _ChangeHandler(FileSystemEventHandler):
89
+ """Bridge watchdog events to the watcher's classify+schedule path.
90
+
91
+ Directory events are ignored. Each file event's path (and, for moves, the
92
+ destination) is classified; if it matches an indexed type and survives
93
+ ``LayeredIgnore`` its kind is scheduled.
94
+ """
95
+
96
+ def __init__(self, watcher: "SourceWatcher") -> None:
97
+ super().__init__()
98
+ self._watcher = watcher
99
+
100
+ def on_any_event(self, event) -> None: # type: ignore[override]
101
+ if event.is_directory:
102
+ return
103
+ self._watcher._handle_path(event.src_path)
104
+ dest = getattr(event, "dest_path", None)
105
+ if dest:
106
+ self._watcher._handle_path(dest)
107
+
108
+
109
+ class SourceWatcher:
110
+ """Watch ``cfg.source_root`` and dispatch debounced per-type reindexes.
111
+
112
+ Thread model: watchdog's observer thread calls the handler (which only does a
113
+ quick classify + schedule -- never blocks). A single dedicated debounce
114
+ worker thread runs ``reindex`` so the observer is never held up by a build.
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ cfg: "ResolvedOperatorConfig",
120
+ warm: "WarmResources",
121
+ *,
122
+ debounce_ms: int,
123
+ backend: str,
124
+ poll_interval_ms: int,
125
+ on_event: Callable[[str, dict[str, Any]] | None] = None,
126
+ ) -> None:
127
+ self.cfg = cfg
128
+ self.warm = warm
129
+ self._debounce_s = max(int(debounce_ms), 1) / 1000.0
130
+ self._backend = backend
131
+ # watchdog's PollingObserver takes a float timeout in SECONDS (it flows
132
+ # straight into ``threading.Event.wait``); a timedelta crashes the emitter.
133
+ self._poll_interval_s = max(int(poll_interval_ms), 1) / 1000.0
134
+ self._on_event = on_event
135
+
136
+ self._ignore = LayeredIgnore(cfg.source_root)
137
+ self._source_root_resolved = Path(cfg.source_root).resolve()
138
+
139
+ self._observer = self._make_observer()
140
+ self._handler = _ChangeHandler(self)
141
+
142
+ # Debounce state -- touched by the observer thread and the debounce worker.
143
+ self._lock = threading.Lock()
144
+ self._pending: set[str] = set()
145
+ self._activity = threading.Event()
146
+ self._stop = threading.Event()
147
+ self._debounce_thread = threading.Thread(
148
+ target=self._debounce_loop, name="jrag-watch-debounce", daemon=True
149
+ )
150
+
151
+ # In-memory reindex state, refreshed after each successful reindex.
152
+ self.last_reindex: dict[str, Any] | None = None
153
+
154
+ # -- observer backend ----------------------------------------------------
155
+
156
+ def _make_observer(self):
157
+ if self._backend == "polling":
158
+ return PollingObserver(timeout=self._poll_interval_s)
159
+ # "watchdog" and "auto" both prefer the native observer; "auto" falls
160
+ # back to polling in :meth:`start` if the native backend cannot start.
161
+ return Observer()
162
+
163
+ # -- lifecycle -----------------------------------------------------------
164
+
165
+ def start(self) -> None:
166
+ """Schedule the observer on ``cfg.source_root`` (recursive) and start the
167
+ debounce loop. Under ``backend="auto"`` a native observer that fails to
168
+ start (e.g. on some network filesystems) falls back to polling."""
169
+ self._observer.schedule(self._handler, str(self.cfg.source_root), recursive=True)
170
+ try:
171
+ self._observer.start()
172
+ except Exception as exc:
173
+ if self._backend != "auto":
174
+ raise
175
+ self._emit(
176
+ "error",
177
+ {"phase": "observer", "fallback": "polling", "error": repr(exc)},
178
+ )
179
+ self._observer = PollingObserver(timeout=self._poll_interval_s)
180
+ self._observer.schedule(self._handler, str(self.cfg.source_root), recursive=True)
181
+ self._observer.start()
182
+ self._debounce_thread.start()
183
+
184
+ def stop(self) -> None:
185
+ """Stop the observer and the debounce loop; join both."""
186
+ self._stop.set()
187
+ self._activity.set() # unblock the debounce worker's wait
188
+ try:
189
+ if self._observer.is_alive():
190
+ self._observer.stop()
191
+ self._observer.join(timeout=5.0)
192
+ except Exception:
193
+ log.warning("watchdog observer stop failed", exc_info=True)
194
+ if (
195
+ self._debounce_thread.is_alive()
196
+ and threading.current_thread() is not self._debounce_thread
197
+ ):
198
+ self._debounce_thread.join(timeout=10.0)
199
+
200
+ # -- event classification (observer thread) ------------------------------
201
+ #
202
+ # The watcher fires on exactly the cocoindex-indexed set (``.java`` plus the
203
+ # SQL/YAML resource patterns below) filtered through ``LayeredIgnore``.
204
+ # ``target/generated-sources/**/*.java`` therefore correctly fires: generated
205
+ # sources are first-class, cocoindex does NOT exclude ``target/``, so the
206
+ # watcher must fire to keep them fresh. ``LayeredIgnore.is_ignored`` does NOT
207
+ # prune build-output dirs (``target/``/``build``/``out``) -- that pruning lives
208
+ # in ``iter_java_source_files``'s ``os.walk`` (``_is_build_output_dir``), used
209
+ # by the graph builder, not by cocoindex. (Compiled ``.class`` output under
210
+ # ``target/`` doesn't match the ``.java`` suffix anyway.)
211
+
212
+ def _handle_path(self, src_path: object) -> None:
213
+ """Classify one observed path and schedule its kind if indexed & not ignored."""
214
+ kinds = self._classify(Path(str(src_path)))
215
+ if kinds:
216
+ self._schedule(kinds)
217
+
218
+ def _classify(self, path: Path) -> set[str]:
219
+ """Return the set of reindex kinds for ``path`` (empty if ignored/unknown).
220
+
221
+ ``LayeredIgnore`` wins: an ignored path yields no kind even when its
222
+ suffix is ``.java``. Outside-source-root paths also yield nothing.
223
+ """
224
+ try:
225
+ if self._ignore.is_ignored(path):
226
+ return set()
227
+ except Exception:
228
+ return set()
229
+ try:
230
+ rel = path.resolve().relative_to(self._source_root_resolved).as_posix()
231
+ except ValueError:
232
+ return set()
233
+ suffix = path.suffix.lower()
234
+ if suffix == ".java":
235
+ return {"java"}
236
+ if suffix == ".sql":
237
+ return {"sql"} if _is_migration_sql(rel) else set()
238
+ if suffix in _YAML_SUFFIXES:
239
+ return {"yaml"} if _is_application_yaml(rel) else set()
240
+ return set()
241
+
242
+ def _schedule(self, kinds: set[str]) -> None:
243
+ """Union ``kinds`` into the debounce collector and (re)arm the debounce window."""
244
+ if not kinds:
245
+ return
246
+ with self._lock:
247
+ self._pending |= kinds
248
+ self._activity.set()
249
+
250
+ # -- debounce worker thread ----------------------------------------------
251
+
252
+ def _debounce_loop(self) -> None:
253
+ """Fire ``reindex`` once per quiet period, coalescing bursts.
254
+
255
+ Leading edge: the first event arms a ``debounce_ms`` window. Each further
256
+ event during the window re-arms it. When the window expires with no new
257
+ events, the accumulated kinds are flushed to ``reindex`` in ONE call.
258
+ """
259
+ while not self._stop.is_set():
260
+ # Wait for the leading edge of a burst.
261
+ self._activity.wait()
262
+ if self._stop.is_set():
263
+ return
264
+ # Re-arm while events keep arriving (debounce).
265
+ while not self._stop.is_set():
266
+ self._activity.clear()
267
+ if self._activity.wait(timeout=self._debounce_s):
268
+ continue # new activity -> reset the window
269
+ break # quiet period elapsed -> fire
270
+ if self._stop.is_set():
271
+ return
272
+ with self._lock:
273
+ kinds = self._pending
274
+ self._pending = set()
275
+ if kinds:
276
+ try:
277
+ self.reindex(kinds)
278
+ except Exception: # noqa: BLE001 -- reindex emits its own error; never kill the worker
279
+ log.warning("reindex raised unexpectedly", exc_info=True)
280
+
281
+ # -- reindex (runs on the debounce worker thread) ------------------------
282
+
283
+ def reindex(self, kinds: set[str]) -> None:
284
+ """Run one debounced reindex: vectors always; graph only when java changed.
285
+
286
+ COW lifecycle (design §4.7): the graph subprocess writes the ORIGINAL
287
+ graph while reads are served from a sidecar copy. ``begin_graph_snapshot``
288
+ is called BEFORE the subprocess and ``commit_graph_snapshot`` ALWAYS
289
+ (success OR failure) so a snapshot reader is never left dangling -- on
290
+ graph failure the existing ``.graph_increment_in_progress`` crash marker
291
+ drives the next full rebuild. Lance needs no snapshot (commits are atomic
292
+ per version; fresh per-query reads are fine).
293
+ """
294
+ if not kinds:
295
+ return
296
+ kind_list = sorted(kinds)
297
+ self._emit("indexing_started", {"kinds": kind_list})
298
+ try:
299
+ # Vectors run for every indexed type (java/sql/yaml all flow through cocoindex).
300
+ self._emit("vectors", {"kinds": kind_list})
301
+ vres = run_cocoindex_update(
302
+ self.cfg.subprocess_env(),
303
+ full_reprocess=False,
304
+ quiet=True,
305
+ verbose=False,
306
+ )
307
+
308
+ graph_rc = 0
309
+ if "java" in kinds:
310
+ # Graph indexes java only; reindex under a COW snapshot so graph
311
+ # reads continue (from the sidecar) during the subprocess write.
312
+ self._emit("graph", {"kinds": kind_list})
313
+ try:
314
+ # begin/commit paired in this try/finally. begin_graph_snapshot
315
+ # sets ``_snapshot_path`` last, so if it raises commit no-ops;
316
+ # moving begin inside the try keeps the pairing locally evident.
317
+ self.warm.begin_graph_snapshot()
318
+ gres = run_incremental_graph(
319
+ source_root=self.cfg.source_root,
320
+ ladybug_path=self.cfg.ladybug_path,
321
+ verbose=False,
322
+ quiet=True,
323
+ env=self.cfg.subprocess_env(),
324
+ )
325
+ graph_rc = gres.returncode
326
+ finally:
327
+ # ALWAYS drop the snapshot reader -- even on failure -- so it
328
+ # is never left dangling. No-ops when no snapshot is active.
329
+ self.warm.commit_graph_snapshot()
330
+
331
+ if vres.returncode != 0:
332
+ self._emit("error", {"phase": "vectors", "returncode": vres.returncode})
333
+ return
334
+ if graph_rc != 0:
335
+ self._emit("error", {"phase": "graph", "returncode": graph_rc})
336
+ return
337
+
338
+ self.last_reindex = {"time": time.time(), "kinds": kind_list}
339
+ self._emit("indexing_done", {"kinds": kind_list})
340
+ except Exception as exc: # noqa: BLE001 -- the daemon must survive any reindex error
341
+ self._emit("error", {"phase": "reindex", "error": repr(exc)})
342
+
343
+ # -- status --------------------------------------------------------------
344
+
345
+ def _emit(self, kind: str, detail: dict[str, Any]) -> None:
346
+ """Forward a status callback to the UI; never let it crash the watcher."""
347
+ if self._on_event is None:
348
+ return
349
+ try:
350
+ self._on_event(kind, detail)
351
+ except Exception: # noqa: BLE001 -- a UI callback must not crash the watcher
352
+ log.warning("on_event callback raised", exc_info=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-codebase-rag
3
- Version: 0.9.7
3
+ Version: 0.10.0
4
4
  Summary: MCP server for semantic + structural search over Java codebases
5
5
  Author: HumanBean17
6
6
  License-Expression: MIT
@@ -21,21 +21,22 @@ Classifier: Operating System :: Microsoft :: Windows
21
21
  Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: cocoindex[lancedb]<2,>=1.0.7; sys_platform != "darwin" or platform_machine != "x86_64"
24
+ Requires-Dist: cocoindex[lancedb]<2,>=1.0.15; sys_platform != "darwin" or platform_machine != "x86_64"
25
25
  Requires-Dist: ladybug<0.18,>=0.17.1
26
- Requires-Dist: lancedb<0.31,>=0.25.3; sys_platform != "darwin" or platform_machine != "x86_64"
26
+ Requires-Dist: lancedb<0.36,>=0.34; sys_platform != "darwin" or platform_machine != "x86_64"
27
27
  Requires-Dist: mcp<2,>=1.27.0
28
28
  Requires-Dist: numpy<2.5,>=1.26.4
29
29
  Requires-Dist: pathspec<2,>=1.0.4
30
- Requires-Dist: pyarrow<24,>=23.0.1
30
+ Requires-Dist: pyarrow<26,>=23.0.1
31
31
  Requires-Dist: pydantic<3,>=2.0
32
32
  Requires-Dist: PyYAML<7,>=6.0.3
33
33
  Requires-Dist: questionary<3,>=2.0
34
- Requires-Dist: rich<15,>=14
34
+ Requires-Dist: rich<16,>=14
35
35
  Requires-Dist: sentence-transformers<6,>=5.4.0; sys_platform != "darwin" or platform_machine != "x86_64"
36
36
  Requires-Dist: tree-sitter<0.26,>=0.25.2
37
37
  Requires-Dist: tree-sitter-java<0.24,>=0.23.5
38
38
  Requires-Dist: unidiff<1,>=0.7.3
39
+ Requires-Dist: watchdog<7,>=6
39
40
  Provides-Extra: dev
40
41
  Requires-Dist: pytest>=7; extra == "dev"
41
42
  Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
@@ -45,18 +46,16 @@ Dynamic: license-file
45
46
 
46
47
  # java-codebase-rag
47
48
 
48
- A graph-native code intelligence layer for Java microservice estates — usable as an **MCP server** or a **CLI** (`jrag`), two surfaces over the same graph.
49
+ A graph-native code intelligence layer for Java microservice estates, surfaced through the **`jrag` CLI** — one command per engineering intent. A **legacy MCP server** (`search` / `find` / `describe` / `neighbors` / `resolve`) is also available for existing setups. Both are thin surfaces over the same **AST Graph**: a deterministic property graph extracted from Java source with tree-sitter, stored **locally** in **LadybugDB** (graph) alongside a **LanceDB** vector index (chunks). There is no server to host and no cloud round-trip — the index lives on your disk and your source never leaves the machine. Both surfaces collapse onto three primitive operations: **locate**, **inspect**, **walk**.
49
50
 
50
- The system extracts a deterministic property graph from Java source (tree-sitter), stores it in **LadybugDB** (graph) alongside a **LanceDB** vector index (chunks), and exposes two agent surfaces, picked at install time (`java-codebase-rag install --surface mcp|cli`): the **MCP** surface ships five tools — `search`, `find`, `describe`, `neighbors`, `resolve` — over stdio; the **CLI** surface ships `jrag`, one command per engineering intent. Both collapse onto three primitive operations: **locate**, **inspect**, **walk**.
51
-
52
- > **What this MCP is:** a **GPS for code navigation**, not a reasoning engine.
51
+ > **What this is: a GPS for code navigation**, not a reasoning engine.
53
52
  > Agents use a simple loop:
54
53
  >
55
- > 1. **Locate** entry nodes (`search` / `find`, or identifier-shaped **`resolve`**)
56
- > 2. **Inspect** what a node is (`describe`)
57
- > 3. **Walk** one hop at a time (`neighbors`) until enough evidence is gathered
54
+ > 1. **Locate** entry nodes (`jrag find`, `jrag search`, or identifier-shaped lookup)
55
+ > 2. **Inspect** what a node is (`jrag inspect`)
56
+ > 3. **Walk** one hop at a time (`jrag callers` / `callees` / `hierarchy` / …) until enough evidence is gathered
58
57
  >
59
- > The MCP exposes structure and adjacency; the agent owns multi-hop reasoning and stop conditions.
58
+ > The tool exposes structure and adjacency; the agent owns multi-hop reasoning and stop conditions.
60
59
 
61
60
  For the design rationale, the GPS metaphor, and the full ontology, see [`docs/paper/paper.pdf`](./docs/paper/paper.pdf) (architecture report).
62
61
 
@@ -66,7 +65,7 @@ For the design rationale, the GPS metaphor, and the full ontology, see [`docs/pa
66
65
 
67
66
  Generic code-search tools (grep, ctags, vector-only RAG) hit a ceiling on real Java microservice estates: they find files but lose the structure that makes a Spring/JAX-RS system navigable. This project is built around five choices that target that gap.
68
67
 
69
- - **Hybrid RAG + GraphRAG, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (LadybugDB property graph) are composed in one surface. `search` finds candidate nodes by meaning; `neighbors` walks the exact edge you care about (`CALLS`, `IMPLEMENTS`, `INJECTS`, `EXPOSES`, …). The agent picks the right primitive per step instead of being forced into pure-vector or pure-symbol search.
68
+ - **Hybrid RAG + AST Graph, not either-or.** Semantic recall (LanceDB chunk vectors) and structural navigation (the LadybugDB AST Graph) are composed in one surface. `search` finds candidate nodes by meaning; `neighbors` walks the exact edge you care about (`CALLS`, `IMPLEMENTS`, `INJECTS`, `EXPOSES`, …). The agent picks the right primitive per step instead of being forced into pure-vector or pure-symbol search.
70
69
 
71
70
  - **A Java-tuned role model.** Symbols are labelled with stereotypes inferred from Spring and JAX-RS conventions — `CONTROLLER`, `SERVICE`, `REPOSITORY`, `COMPONENT`, `CONFIG`, `ENTITY`, `CLIENT`, `MAPPER`, `DTO`. Agents can ask "list controllers" or "who injects this repository" directly, instead of grep-ing for `@RestController` and hoping for the best. Roles drive both filtering (`find` with a `NodeFilter`) and ranking.
72
71
 
@@ -86,7 +85,7 @@ The rest of this README is the install, the tool/command orientation, and the re
86
85
  pip install java-codebase-rag
87
86
  ```
88
87
 
89
- Python **3.11+** required, on **Linux, macOS, and Windows**. On Linux, Windows, and **Apple Silicon** Macs every native dependency (LanceDB, LadybugDB/kuzu, CocoIndex) ships a wheel and you get the full semantic + graph search. **Intel Macs (x86_64) install graph-only**: PyTorch ≥2.3 and LanceDB ≥0.26 dropped macOS Intel wheels, so the vector stack is auto-excluded via PEP 508 markers — `pip install java-codebase-rag` works out of the box, the graph layer (`find` / `describe` / `neighbors` / `resolve`) is fully usable, and the `search` tool falls back to **lexical (keyword) search** over the symbol graph (same tool contract, keyword-ranked instead of semantic; an advisory notes the mode). Semantic/vector search needs Apple Silicon, Linux, or Windows. After install, `java-codebase-rag --help` should print the CLI groups.
88
+ Python **3.11+** required, on **Linux, macOS, and Windows**. On Linux, Windows, and **Apple Silicon** Macs every native dependency (LanceDB, LadybugDB, CocoIndex) ships a wheel and you get the full semantic + graph search. **Intel Macs (x86_64) install graph-only**: PyTorch ≥2.3 and LanceDB ≥0.26 dropped macOS Intel wheels, so the vector stack is auto-excluded via PEP 508 markers — `pip install java-codebase-rag` works out of the box, the graph layer (`find` / `describe` / `neighbors` / `resolve`) is fully usable, and the `search` tool falls back to **lexical search** over the symbol graph — BM25-ranked over a LadybugDB full-text index (same tool contract, keyword-ranked instead of semantic; an advisory notes the mode). Semantic/vector search needs Apple Silicon, Linux, or Windows. After install, `java-codebase-rag --help` should print the CLI groups.
90
89
  The package includes the CocoIndex lifecycle dependency used by `init`, `increment`, `reprocess`, and `erase` on platforms that have it (it is absent on Intel Mac).
91
90
 
92
91
  ### Interactive setup (recommended)
@@ -122,19 +121,7 @@ If you prefer manual configuration, see [`docs/JAVA-CODEBASE-RAG-CLI.md`](./docs
122
121
 
123
122
  ## Tools & commands at a glance
124
123
 
125
- Pick a surface at install time — `java-codebase-rag install --surface mcp|cli` (default `cli`, recommended). Both surfaces walk the same LanceDB vectors + LadybugDB graph. Switch an existing install later with `java-codebase-rag update --surface mcp|cli`.
126
-
127
- **MCP surface — five tools over stdio**
128
-
129
- | Tool | Purpose | Required args |
130
- |---|---|---|
131
- | `search` | Locate nodes by NL / code text. | `query` |
132
- | `find` | Locate nodes by structured filter. | `kind`, `filter` |
133
- | `describe` | Full record + edge counts for one node. | `id` |
134
- | `resolve` | Identifier-shaped lookup (FQN-collision-safe). Returns `one` / `many` / `none`. | `identifier` |
135
- | `neighbors` | Graph walk, one hop. | `ids`, `direction`, `edge_types` |
136
-
137
- Full schemas, `NodeFilter` / `EdgeFilter` semantics, and the hints contract live in [`docs/AGENT-GUIDE.md`](./docs/AGENT-GUIDE.md). Edge types and traversal directions are listed in [`docs/EDGE-NAVIGATION.md`](./docs/EDGE-NAVIGATION.md).
124
+ `jrag` is the default and recommended surface (`java-codebase-rag install --surface cli`). The **MCP server** (`--surface mcp`) is kept as a **legacy** option for existing setups. Both surfaces walk the same LanceDB vectors + LadybugDB **AST Graph**. Switch an existing install later with `java-codebase-rag update --surface mcp|cli`.
138
125
 
139
126
  **CLI surface — `jrag`, one command per engineering intent**
140
127
 
@@ -143,7 +130,7 @@ Full schemas, `NodeFilter` / `EdgeFilter` semantics, and the hints contract live
143
130
  jrag status # index health (ontology version, freshness, counts)
144
131
  jrag microservices # microservices with resolved type counts
145
132
  jrag map # counts per kind per service/module
146
- jrag map --module # group by module instead
133
+ jrag map --by module # group by module instead (--module filters)
147
134
  jrag conventions # dominant roles + framework tallies
148
135
  jrag overview chat-core # bundle for a microservice
149
136
  jrag overview /chat/assign # route flow (inbound callers + outbound CALLS)
@@ -191,9 +178,21 @@ jrag search "audit" --offset 5 # paginated
191
178
 
192
179
  Every `<query>` command takes human-readable identifiers (FQN / simple name / route path / topic) — never raw node IDs. Output contract, flags, and the resolve-first rule are in [`jrag` — agent CLI](#jrag--agent-cli) below.
193
180
 
181
+ **MCP surface — five tools over stdio (legacy)**
182
+
183
+ | Tool | Purpose | Required args |
184
+ |---|---|---|
185
+ | `search` | Locate nodes by NL / code text. | `query` |
186
+ | `find` | Locate nodes by structured filter. | `kind`, `filter` |
187
+ | `describe` | Full record + edge counts for one node. | `id` |
188
+ | `resolve` | Identifier-shaped lookup (FQN-collision-safe). Returns `one` / `many` / `none`. | `identifier` |
189
+ | `neighbors` | Graph walk, one hop. | `ids`, `direction`, `edge_types` |
190
+
191
+ Full schemas, `NodeFilter` / `EdgeFilter` semantics, and the hints contract live in [`docs/AGENT-GUIDE.md`](./docs/AGENT-GUIDE.md). Edge types and traversal directions are listed in [`docs/EDGE-NAVIGATION.md`](./docs/EDGE-NAVIGATION.md).
192
+
194
193
  ### Three-layer architecture
195
194
 
196
- Layer 1 (storage) → Layer 2 (5 MCP tools **or** the `jrag` CLI) → Layer 3 (skill). The MCP-surface skill **[`/explore-codebase`](./skills/explore-codebase/SKILL.md)** documents the 5-tool MCP; the CLI-surface skill **[`/explore-codebase-cli`](./skills/explore-codebase-cli/SKILL.md)** documents the `jrag` CLI (PR-JRAG-5). See the [architecture diagram in `skills/README.md`](./skills/README.md#three-layer-architecture).
195
+ Layer 1 (storage) → Layer 2 (the `jrag` CLI, **or** the legacy 5-tool MCP) → Layer 3 (skill). The CLI-surface skill **[`/explore-codebase-cli`](./skills/explore-codebase-cli/SKILL.md)** documents the `jrag` CLI; the MCP-surface skill **[`/explore-codebase`](./skills/explore-codebase/SKILL.md)** documents the legacy 5-tool MCP (PR-JRAG-5). See the [architecture diagram in `skills/README.md`](./skills/README.md#three-layer-architecture).
197
196
 
198
197
  ---
199
198
 
@@ -303,7 +302,7 @@ full design and per-PR breakdown.
303
302
  | [`docs/CONFIGURATION.md`](./docs/CONFIGURATION.md) | Environment variables, project YAML, graph ontology, brownfield overrides, ignore patterns. |
304
303
  | [`docs/JAVA-CODEBASE-RAG-CLI.md`](./docs/JAVA-CODEBASE-RAG-CLI.md) | CLI operator playbook: workflows, exit codes, env alignment. |
305
304
  | [`docs/EDGE-NAVIGATION.md`](./docs/EDGE-NAVIGATION.md) | MCP-traversable edges, directions, dot-key composition. |
306
- | [`skills/`](./skills/) | `/explore-codebase` (MCP surface) + `/explore-codebase-cli` (CLI surface) skills — operating manuals for hosts with skill discovery (alternative to copy-pasting AGENT-GUIDE). See [`skills/README.md`](./skills/README.md). |
305
+ | [`skills/`](./skills/) | `/explore-codebase-cli` (CLI surface) + `/explore-codebase` (legacy MCP surface) skills — operating manuals for hosts with skill discovery (alternative to copy-pasting AGENT-GUIDE). See [`skills/README.md`](./skills/README.md). |
307
306
  | [`docs/MANUAL-VERIFICATION-CHECKLIST.md`](./docs/MANUAL-VERIFICATION-CHECKLIST.md) | 7-phase agent-driven verification after indexing your project. |
308
307
  | [`docs/CODEBASE_REQUIREMENTS.md`](./docs/CODEBASE_REQUIREMENTS.md) | Assumptions about your Java repo + per-file edit map for non-conforming codebases. |
309
308
  | [`docs/PRODUCT-VISION.md`](./docs/PRODUCT-VISION.md) | Long-term product direction. |