java-codebase-rag 0.6.2__py3-none-any.whl → 0.6.4__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.
path_filtering.py CHANGED
@@ -342,24 +342,22 @@ class LayeredIgnore:
342
342
  )
343
343
  return mega, GitIgnoreSpec.from_lines(mega), meta
344
344
 
345
- def is_ignored(self, path: Path) -> tuple[bool, IgnoreLayer | None]:
346
- """Return whether ``path`` is ignored and which layer last matched."""
345
+ def is_ignored(self, path: Path) -> bool:
346
+ """Return whether ``path`` is ignored by any configured layer.
347
+
348
+ Boolean-only fast path for the per-file index walk. It deliberately does
349
+ not compute *which* layer/source last matched: that attribution is
350
+ O(rules²) via :func:`_winning_row` (one ``GitIgnoreSpec`` rebuild per
351
+ rule prefix) and is only needed for ``diagnose-ignore``, so it lives in
352
+ :meth:`diagnose_dict` and is never paid on the hot path.
353
+ """
347
354
  rel = self._rel_project(path)
348
355
  if rel is None:
349
- return False, None
350
- mega, spec, meta = self._mega(rel)
356
+ return False
357
+ mega, spec, _ = self._mega(rel)
351
358
  if not mega:
352
- return False, None
353
- ignored = spec.match_file(rel)
354
- if not ignored:
355
- return False, None
356
- src, fp, ln, _pat = _winning_row(rel, mega, meta)
357
- return True, IgnoreLayer(
358
- root=self.project_root,
359
- spec=spec,
360
- source=src,
361
- ignore_file=fp,
362
- )
359
+ return False
360
+ return spec.match_file(rel)
363
361
 
364
362
  def diagnose(self, path: Path) -> str:
365
363
  """Human-readable, multi-line explanation of the ignore decision."""
@@ -466,7 +464,6 @@ def iter_java_source_files(
466
464
  if not fn.endswith(".java"):
467
465
  continue
468
466
  p = Path(dirpath) / fn
469
- ign, _ = ignore_ctx.is_ignored(p)
470
- if ign:
467
+ if ignore_ctx.is_ignored(p):
471
468
  continue
472
469
  yield p
pr_analysis.py CHANGED
@@ -152,7 +152,7 @@ def _resolve_graph_filename(
152
152
  *,
153
153
  ambiguity_notes: list[str] | None = None,
154
154
  ) -> str | None:
155
- """Map a diff path to `Symbol.filename` values stored in Kuzu."""
155
+ """Map a diff path to `Symbol.filename` values stored in LadybugDB."""
156
156
  variants = {_strip_ab_prefix(path)}
157
157
  for v in list(variants):
158
158
  if v.startswith("./"):
@@ -362,7 +362,7 @@ def _is_public_interface_method(graph: Any, sym: SymbolHit) -> bool:
362
362
 
363
363
 
364
364
  def _route_ids_for_symbol(graph: Any, symbol_id: str) -> list[str]:
365
- # Note: Kuzu rejects `ORDER BY r.id` together with `RETURN DISTINCT r.id` (binder loses `r`).
365
+ # Note: LadybugDB rejects `ORDER BY r.id` together with `RETURN DISTINCT r.id` (binder loses `r`).
366
366
  q = (
367
367
  "MATCH (s:Symbol)-[e:EXPOSES]->(r:Route) WHERE s.id = $sid "
368
368
  "RETURN r.id AS id ORDER BY id"
@@ -384,7 +384,6 @@ def compute_risk(graph: Any, changed: list[ChangedSymbol]) -> PrRiskReport:
384
384
  bump (up to +1.0) after normalization so they influence rank while
385
385
  preserving the public scalar contract.
386
386
  """
387
- notes: list[str] = []
388
387
  blast_by: dict[str, int] = {}
389
388
  blast_total = 0
390
389
  routes: list[str] = []
@@ -495,7 +494,7 @@ def compute_risk(graph: Any, changed: list[ChangedSymbol]) -> PrRiskReport:
495
494
  routes_touched=routes,
496
495
  risk_score=score,
497
496
  risk_band=band,
498
- notes=notes,
497
+ notes=[],
499
498
  )
500
499
 
501
500
 
search_lancedb.py CHANGED
@@ -677,8 +677,8 @@ def _graph_expand_merge(
677
677
  expand_depth: int,
678
678
  ladybug_path: str | None,
679
679
  ) -> list[dict]:
680
- """Expand vector top-k through the Kuzu graph and fuse (RRF) with the original list."""
681
- # Lazy import so the module works without kuzu installed when graph_expand=False.
680
+ """Expand vector top-k through the LadybugDB graph and fuse (RRF) with the original list."""
681
+ # Lazy import so the module works without ladybug installed when graph_expand=False.
682
682
  try:
683
683
  from ladybug_queries import LadybugGraph
684
684
  except Exception:
server.py CHANGED
@@ -13,9 +13,8 @@ import mcp_v2
13
13
  from index_common import SBERT_MODEL
14
14
  from java_codebase_rag.cli_progress import (
15
15
  accumulate_and_relay_subprocess_streams,
16
- emit_vectors_finish,
17
- emit_vectors_start,
18
16
  )
17
+ from java_codebase_rag.progress import ProgressEvent
19
18
  from java_codebase_rag._fdlimit import raise_fd_limit
20
19
  from java_codebase_rag.config import (
21
20
  cocoindex_subprocess_env_defaults,
@@ -113,7 +112,44 @@ class ScopeManager:
113
112
 
114
113
  def _detect_scope(self) -> str | None:
115
114
  from graph_enrich import detect_microservice_from_path
116
- return detect_microservice_from_path(Path.cwd(), self.source_root)
115
+
116
+ candidate = detect_microservice_from_path(Path.cwd(), self.source_root)
117
+ if candidate is None:
118
+ return None
119
+ # Only auto-scope to a microservice that actually has indexed code.
120
+ # detect_microservice_from_path can mislabel a non-microservice
121
+ # top-level child of source_root — most importantly the config/context
122
+ # directory the MCP server is launched from (no build marker, no
123
+ # source) — via its "first path segment under root" fallback. Scoping
124
+ # every query to such a name yields zero matches, so all tools return
125
+ # empty. A real microservice the operator is working in is, by
126
+ # definition, present in the index, so validating against the indexed
127
+ # set cannot suppress a legitimate scope. When the index is unreadable
128
+ # (empty known set) we keep the detected candidate rather than silently
129
+ # disabling auto-scope on a transient graph error.
130
+ known = self._indexed_microservices()
131
+ if known and candidate not in known:
132
+ return None
133
+ return candidate
134
+
135
+ def _indexed_microservices(self) -> set[str]:
136
+ """Microservice names that have indexed type symbols.
137
+
138
+ Graph-only source of truth: the graph is always built alongside Lance,
139
+ and a Lance-only index (no graph) is not a supported state. Any failure
140
+ (graph missing, open error, empty index) returns an empty set, which
141
+ ``_detect_scope`` treats as "cannot validate — keep detection".
142
+ """
143
+ try:
144
+ if not LadybugGraph.exists():
145
+ return set()
146
+ # LadybugGraph.get() opens the DB and runs meta(); it can raise
147
+ # (e.g. RuntimeError on ontology-version mismatch). Caught here ->
148
+ # empty set -> _detect_scope keeps the detected scope.
149
+ counts = LadybugGraph.get().microservice_counts()
150
+ return {name for name in counts if name}
151
+ except Exception:
152
+ return set()
117
153
 
118
154
  def _log_detection(self) -> None:
119
155
  if self.default_scope:
@@ -270,10 +306,20 @@ def list_code_index_tables_payload() -> IndexInfoOutput:
270
306
  )
271
307
 
272
308
 
273
- async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) -> RefreshIndexOutput:
309
+ async def run_refresh_pipeline(
310
+ *,
311
+ quiet: bool = False,
312
+ verbose: bool = True,
313
+ on_progress=None,
314
+ on_progress_console: object | None = None,
315
+ ) -> RefreshIndexOutput:
274
316
  root = _project_root()
275
317
  cocoindex_bin = Path(sys.executable).parent / "cocoindex"
276
318
  if not cocoindex_bin.is_file():
319
+ # 127 pre-spawn: emit a terminal failed vectors event so the renderer's
320
+ # task doesn't hang at running (matches the sync pipeline path).
321
+ if on_progress is not None:
322
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
277
323
  return RefreshIndexOutput(
278
324
  success=False,
279
325
  message=f"cocoindex not found next to Python: {cocoindex_bin}",
@@ -286,6 +332,8 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) ->
286
332
  if fallback.is_file():
287
333
  flow_path = fallback
288
334
  else:
335
+ if on_progress is not None:
336
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
289
337
  return RefreshIndexOutput(
290
338
  success=False,
291
339
  message=f"java_index_flow_lancedb.py not found under {root} nor {bundle_dir}",
@@ -308,13 +356,14 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) ->
308
356
  )
309
357
  out_b, err_b = await proc.communicate()
310
358
  except Exception as exc:
359
+ if on_progress is not None:
360
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
311
361
  return RefreshIndexOutput(
312
362
  success=False,
313
363
  message=f"spawn failed: {exc!s}",
314
364
  phases_run=[],
315
365
  )
316
366
  else:
317
- emit_vectors_start()
318
367
  t0 = time.perf_counter()
319
368
  code_c = -1
320
369
  try:
@@ -329,16 +378,30 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) ->
329
378
  stdout=asyncio.subprocess.PIPE,
330
379
  stderr=asyncio.subprocess.PIPE,
331
380
  )
332
- out_b, err_b = await accumulate_and_relay_subprocess_streams(proc, relay=True, verbose=verbose)
381
+ # The vectors task is fed by the child's per-file ticks + the
382
+ # approximate total line, parsed by the ProgressRelay inside the
383
+ # async drain and routed to on_progress.
384
+ out_b, err_b = await accumulate_and_relay_subprocess_streams(
385
+ proc, relay=True, verbose=verbose,
386
+ on_progress=on_progress, on_progress_console=on_progress_console,
387
+ )
333
388
  code_c = proc.returncode if proc.returncode is not None else -1
334
389
  except Exception as exc:
390
+ if on_progress is not None:
391
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
335
392
  return RefreshIndexOutput(
336
393
  success=False,
337
394
  message=f"spawn failed: {exc!s}",
338
395
  phases_run=[],
339
396
  )
340
397
  finally:
341
- emit_vectors_finish(elapsed_s=time.perf_counter() - t0, exit_code=code_c)
398
+ # The parent emits the terminal vectors event (the flow can't — no
399
+ # "all files done" hook). Drives clamp-on-completion + phase
400
+ # transition to Optimize.
401
+ if on_progress is not None:
402
+ elapsed = time.perf_counter() - t0
403
+ status = "done" if code_c == 0 else "failed"
404
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status=status, elapsed_s=elapsed))
342
405
  assert proc is not None
343
406
  out = out_b.decode(errors="replace")
344
407
  err = err_b.decode(errors="replace")
@@ -366,7 +429,7 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) ->
366
429
  idx_dir = Path(idx_raw)
367
430
  else:
368
431
  idx_dir = (root / ".java-codebase-rag").resolve()
369
- await optimize_lance_tables(idx_dir, quiet=quiet)
432
+ await optimize_lance_tables(idx_dir, quiet=quiet, on_progress=on_progress)
370
433
  except Exception as exc:
371
434
  optimize_error = f"lance optimize failed: {exc}"
372
435
  print(f"java-codebase-rag: {optimize_error}", file=sys.stderr)
@@ -394,7 +457,10 @@ async def run_refresh_pipeline(*, quiet: bool = False, verbose: bool = True) ->
394
457
  if quiet:
395
458
  gout_b, gerr_b = await gproc.communicate()
396
459
  else:
397
- gout_b, gerr_b = await accumulate_and_relay_subprocess_streams(gproc, relay=True, verbose=verbose)
460
+ gout_b, gerr_b = await accumulate_and_relay_subprocess_streams(
461
+ gproc, relay=True, verbose=verbose,
462
+ on_progress=on_progress, on_progress_console=on_progress_console,
463
+ )
398
464
  graph_code = gproc.returncode
399
465
  graph_out = gout_b.decode(errors="replace")
400
466
  graph_err = gerr_b.decode(errors="replace")
@@ -1,33 +0,0 @@
1
- ast_java.py,sha256=NQgZzstbsMq-PdowoD6r_ixJKxEEFzTP9xUzqDpiXeU,99661
2
- brownfield_events.py,sha256=yxXkKDgMb3VPtaiakGzncHM_EGnda8xIue6w90yYp8s,2055
3
- build_ast_graph.py,sha256=OKigswkUmWwUAKXXRNH4zplw2VonIdWUWzVjC-t5roo,152893
4
- chunk_heuristics.py,sha256=aQk2NOKxzUdqoUAJUO3G3LE0MN_bYZWNLQ0tkmj5uts,1813
5
- graph_enrich.py,sha256=POT4LwSkTsrjUmP67bsm2UezUam70cunuPDYDh-v1Bs,63332
6
- index_common.py,sha256=HT6FKHFJ084eFvd3fR1j8z8gf4eWoPHVW8GXLpw464I,285
7
- java_index_flow_lancedb.py,sha256=MH9iTNF6HDHDTt5Jn7TOVE5hQ4WUPNt7PlQoh1tuh9o,13212
8
- java_index_v1_common.py,sha256=nF1KrSqboF_RRvWerG9knRRFmWwsrG_CvhgnsoZ8KqA,1154
9
- java_ontology.py,sha256=71bCLDNvMy0SpZPzSR5apJ0qJXNd6y5ggkLdBEw_PFo,16682
10
- ladybug_queries.py,sha256=912j9VAYDjcU4ReVorWQ6R4DZl0tteKic-Pqu0jyBS0,90837
11
- mcp_hints.py,sha256=3swh05LSiWur3tm3-yssndBsLxIxFhy501kBtJI8jJ0,42509
12
- mcp_v2.py,sha256=o94GJI7j6dLJDIA3R_1ZiQhjzQfMAEW3etdeZYnHOUc,80637
13
- path_filtering.py,sha256=-oX16SYLWYwX9pcV1fu3vbVTIhY1GzFflT7J1E2tqPY,17122
14
- pr_analysis.py,sha256=3-5L8_G5XupdJsl9RN73Lq-ejPoK11B3m_VzAx2fGG8,18413
15
- search_lancedb.py,sha256=scG6HBUrsgIeSWFrGcLcGdhWv1qODOx4JOBMAlLDY_E,36793
16
- server.py,sha256=Js3XDpV7ThAtj352StH6QdhHutf1D5qUkbR-8k3jO8g,31303
17
- java_codebase_rag/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
18
- java_codebase_rag/_fdlimit.py,sha256=WroFdfSNbcriKok6q8znTf74dqlznxea_1Fd5bHl_3o,1930
19
- java_codebase_rag/cli.py,sha256=HkzCP8-G3WlCzoXcVCI2K3forDOMpTmUSoxgx3jbKk4,34774
20
- java_codebase_rag/cli_format.py,sha256=arU7P9W6Fvm7X_wzR1wJ8EfyxK1rDP_ESEhdA0ub4Mo,2579
21
- java_codebase_rag/cli_progress.py,sha256=9jCqEagYOXs32SYVA31_sOCrONvYy7cl1CrdBD2Pg44,3168
22
- java_codebase_rag/config.py,sha256=bfwYI4R8PU9YV_M4r8-03iaUZ_0TW-qN_NuhIsDXy2M,18769
23
- java_codebase_rag/installer.py,sha256=sE0l85K_o291PdpF1vpesefR9VgdvvVeARXrpTxa30A,46689
24
- java_codebase_rag/lance_optimize.py,sha256=MzACYlgwxmkJCK64qQLyIAdizSq5BARqaMYSZONlc1I,6069
25
- java_codebase_rag/pipeline.py,sha256=UcgluFAW9Ghnas8u40x45bVic0mQv6rjzcliDKsnYJI,11936
26
- java_codebase_rag/install_data/agents/explorer-rag-enhanced.md,sha256=APl9d-No12qZNZLjU7mwNRwxHIgnT3ZtQZiD4clWlyU,14413
27
- java_codebase_rag/install_data/skills/explore-codebase/SKILL.md,sha256=pIM-Xdwq_fXkhhBJCdb-fA2nes5c_mMPcdUXb7Adyxo,12040
28
- java_codebase_rag-0.6.2.dist-info/licenses/LICENSE,sha256=gxvtiHtuviR_q8ZAjWw-QTcF3DyPzg6ZY-lQrr8OPpw,1068
29
- java_codebase_rag-0.6.2.dist-info/METADATA,sha256=X92kaZ5TbEacz0sznWtUtpYEJvBdWIXX0s8MlqOeRyg,16934
30
- java_codebase_rag-0.6.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
31
- java_codebase_rag-0.6.2.dist-info/entry_points.txt,sha256=wsPZwot0Ui4JI3TIgW8LcbN8bNtKFbwQAlHAAJXfYgQ,117
32
- java_codebase_rag-0.6.2.dist-info/top_level.txt,sha256=syQgi8XPBwY2ws_NZ1uRCxTf_s41NpshwEHNdcdnk3A,245
33
- java_codebase_rag-0.6.2.dist-info/RECORD,,