java-codebase-rag 0.9.4__py3-none-any.whl → 0.9.6__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 (46) hide show
  1. java_codebase_rag/absence/__init__.py +0 -0
  2. java_codebase_rag/absence/absence_diagnosis.py +700 -0
  3. java_codebase_rag/absence/absence_types.py +124 -0
  4. java_codebase_rag/absence/absence_vocab.py +455 -0
  5. java_codebase_rag/analysis/__init__.py +0 -0
  6. pr_analysis.py → java_codebase_rag/analysis/pr_analysis.py +1 -1
  7. resolve_service.py → java_codebase_rag/analysis/resolve_service.py +73 -6
  8. java_codebase_rag/ast/__init__.py +0 -0
  9. ast_java.py → java_codebase_rag/ast/ast_java.py +5 -5
  10. java_codebase_rag/cli.py +13 -18
  11. java_codebase_rag/config.py +116 -0
  12. java_codebase_rag/graph/__init__.py +0 -0
  13. build_ast_graph.py → java_codebase_rag/graph/build_ast_graph.py +89 -11
  14. graph_enrich.py → java_codebase_rag/graph/graph_enrich.py +248 -3
  15. graph_types.py → java_codebase_rag/graph/graph_types.py +6 -2
  16. java_ontology.py → java_codebase_rag/graph/java_ontology.py +1 -1
  17. ladybug_queries.py → java_codebase_rag/graph/ladybug_queries.py +6 -6
  18. java_codebase_rag/index/__init__.py +0 -0
  19. java_index_flow_lancedb.py → java_codebase_rag/index/java_index_flow_lancedb.py +30 -10
  20. java_codebase_rag/install_data/__init__.py +0 -0
  21. java_codebase_rag/jrag.py +71 -16
  22. java_codebase_rag/jrag_envelope.py +13 -4
  23. java_codebase_rag/jrag_hints.py +1 -1
  24. java_codebase_rag/jrag_render.py +67 -3
  25. java_codebase_rag/mcp/__init__.py +0 -0
  26. mcp_hints.py → java_codebase_rag/mcp/mcp_hints.py +1 -1
  27. mcp_v2.py → java_codebase_rag/mcp/mcp_v2.py +280 -81
  28. server.py → java_codebase_rag/mcp/server.py +138 -54
  29. java_codebase_rag/pipeline.py +26 -7
  30. java_codebase_rag/search/__init__.py +0 -0
  31. search_lancedb.py → java_codebase_rag/search/search_lancedb.py +53 -314
  32. java_codebase_rag/search/search_lexical.py +329 -0
  33. java_codebase_rag/search/search_scoring.py +338 -0
  34. {java_codebase_rag-0.9.4.dist-info → java_codebase_rag-0.9.6.dist-info}/METADATA +2 -2
  35. java_codebase_rag-0.9.6.dist-info/RECORD +57 -0
  36. {java_codebase_rag-0.9.4.dist-info → java_codebase_rag-0.9.6.dist-info}/entry_points.txt +1 -1
  37. java_codebase_rag-0.9.6.dist-info/top_level.txt +1 -0
  38. java_codebase_rag-0.9.4.dist-info/RECORD +0 -44
  39. java_codebase_rag-0.9.4.dist-info/top_level.txt +0 -19
  40. /brownfield_events.py → /java_codebase_rag/ast/brownfield_events.py +0 -0
  41. /chunk_heuristics.py → /java_codebase_rag/ast/chunk_heuristics.py +0 -0
  42. /path_filtering.py → /java_codebase_rag/graph/path_filtering.py +0 -0
  43. /java_index_v1_common.py → /java_codebase_rag/index/java_index_v1_common.py +0 -0
  44. /index_common.py → /java_codebase_rag/search/index_common.py +0 -0
  45. {java_codebase_rag-0.9.4.dist-info → java_codebase_rag-0.9.6.dist-info}/WHEEL +0 -0
  46. {java_codebase_rag-0.9.4.dist-info → java_codebase_rag-0.9.6.dist-info}/licenses/LICENSE +0 -0
@@ -9,11 +9,17 @@ import time
9
9
  from pathlib import Path
10
10
  from typing import Literal
11
11
 
12
- import mcp_v2
13
- from index_common import SBERT_MODEL
12
+ from java_codebase_rag.mcp import mcp_v2
13
+ from java_codebase_rag.analysis import resolve_service
14
+ from java_codebase_rag.search.index_common import SBERT_MODEL
14
15
  from java_codebase_rag.cli_progress import (
15
16
  accumulate_and_relay_subprocess_streams,
16
17
  )
18
+ from java_codebase_rag.pipeline import (
19
+ VECTORS_SKIPPED_GRAPH_ONLY,
20
+ cocoindex_bin as resolve_cocoindex_bin,
21
+ vector_stack_installed,
22
+ )
17
23
  from java_codebase_rag.progress import ProgressEvent
18
24
  from java_codebase_rag._fdlimit import raise_fd_limit
19
25
  from java_codebase_rag.config import (
@@ -23,13 +29,20 @@ from java_codebase_rag.config import (
23
29
  resolved_sbert_model_for_process_env,
24
30
  resolve_operator_config,
25
31
  )
26
- from ladybug_queries import LadybugGraph, resolve_ladybug_path
32
+ from java_codebase_rag.graph.ladybug_queries import LadybugGraph, resolve_ladybug_path
27
33
  from mcp.server.fastmcp import FastMCP
28
34
  from pydantic import BaseModel, Field
29
35
  # NOTE: search_lancedb.TABLES is imported lazily in list_code_index_tables_payload() — it
30
36
  # pulls lancedb/torch and is unavailable on graph-only installs (macOS Intel).
31
37
 
32
38
  _COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance"
39
+
40
+ # Package-internal locations of the cocoindex flow and the graph builder, both
41
+ # executed by file path (see java_codebase_rag.pipeline). Derived from this
42
+ # file's location so they resolve under editable and wheel installs alike.
43
+ _PKG_DIR = Path(__file__).resolve().parent.parent
44
+ _FLOW_FILE = _PKG_DIR / "index" / "java_index_flow_lancedb.py"
45
+ _BUILDER_FILE = _PKG_DIR / "graph" / "build_ast_graph.py"
33
46
  _INSTRUCTIONS = (
34
47
  "Java codebase graph navigator over an indexed Java codebase. "
35
48
  "Tools: search (NL/code locate), find (structured NodeFilter), describe (one node + edge_summary: stored edge-label counts and optional composed keys for type Symbols and override-axis virtual keys for method Symbols), "
@@ -112,7 +125,7 @@ class ScopeManager:
112
125
  self._log_detection()
113
126
 
114
127
  def _detect_scope(self) -> str | None:
115
- from graph_enrich import detect_microservice_from_path
128
+ from java_codebase_rag.graph.graph_enrich import detect_microservice_from_path
116
129
 
117
130
  candidate = detect_microservice_from_path(Path.cwd(), self.source_root)
118
131
  if candidate is None:
@@ -298,7 +311,7 @@ def _graph_meta_output() -> GraphMetaOutput:
298
311
 
299
312
  def list_code_index_tables_payload() -> IndexInfoOutput:
300
313
  try:
301
- from search_lancedb import TABLES
314
+ from java_codebase_rag.search.search_lancedb import TABLES
302
315
 
303
316
  tables = dict(TABLES)
304
317
  except ImportError:
@@ -314,6 +327,61 @@ def list_code_index_tables_payload() -> IndexInfoOutput:
314
327
  )
315
328
 
316
329
 
330
+ async def _run_graph_phase(
331
+ root: Path,
332
+ *,
333
+ quiet: bool,
334
+ verbose: bool,
335
+ on_progress: object | None,
336
+ on_progress_console: object | None,
337
+ ) -> tuple[int | None, str, str, bool]:
338
+ """Run ``build_ast_graph.py`` and return ``(code, stdout, stderr, started)``.
339
+
340
+ Shared by the vectors→graph refresh path and the graph-only path (macOS Intel,
341
+ where the vector stack is gated off). ``started`` is True only when the graph
342
+ subprocess was actually created, so callers set ``phases_run`` accurately — the
343
+ CLI maps an empty ``phases_run`` to a preflight exit code 2 (nothing spawned).
344
+ A missing builder or a spawn failure returns ``started=False`` with the graph
345
+ code carrying the reason (``None`` for missing builder, ``-1`` for spawn error).
346
+ """
347
+ builder = _BUILDER_FILE
348
+ if not builder.is_file():
349
+ return None, "", "", False
350
+ try:
351
+ graph_args = [
352
+ sys.executable,
353
+ str(builder),
354
+ "--source-root",
355
+ str(root),
356
+ "--ladybug-path",
357
+ resolve_ladybug_path(),
358
+ ]
359
+ if not quiet:
360
+ graph_args.append("--verbose")
361
+ gproc = await asyncio.create_subprocess_exec(
362
+ *graph_args,
363
+ cwd=str(root),
364
+ env=_cocoindex_subprocess_env(root),
365
+ stdout=asyncio.subprocess.PIPE,
366
+ stderr=asyncio.subprocess.PIPE,
367
+ )
368
+ if quiet:
369
+ gout_b, gerr_b = await gproc.communicate()
370
+ else:
371
+ gout_b, gerr_b = await accumulate_and_relay_subprocess_streams(
372
+ gproc, relay=True, verbose=verbose,
373
+ on_progress=on_progress, on_progress_console=on_progress_console,
374
+ )
375
+ return (
376
+ gproc.returncode,
377
+ gout_b.decode(errors="replace"),
378
+ gerr_b.decode(errors="replace"),
379
+ True,
380
+ )
381
+ except Exception as exc:
382
+ return -1, "", f"graph builder spawn failed: {exc}", False
383
+
384
+
317
385
  async def run_refresh_pipeline(
318
386
  *,
319
387
  quiet: bool = False,
@@ -322,7 +390,51 @@ async def run_refresh_pipeline(
322
390
  on_progress_console: object | None = None,
323
391
  ) -> RefreshIndexOutput:
324
392
  root = _project_root()
325
- cocoindex_bin = Path(sys.executable).parent / "cocoindex"
393
+ if not vector_stack_installed():
394
+ # Graph-only install (macOS Intel): the vector stack (cocoindex/lancedb/
395
+ # sentence-transformers) is gated off by PEP 508 markers and uninstallable,
396
+ # so the cocoindex binary is absent. Skip the vectors phase and build the
397
+ # graph only — mirroring init/increment, which treat cocoindex-absent as a
398
+ # skip, not a failure (the graph layer is the supported surface there). No
399
+ # vectors progress event is emitted, so the renderer's vectors task stays
400
+ # invisible (its "never spawned" invariant) instead of hanging at running.
401
+ print(VECTORS_SKIPPED_GRAPH_ONLY, file=sys.stderr, flush=True)
402
+ if not quiet:
403
+ print(file=sys.stderr, flush=True)
404
+ graph_code, graph_out, graph_err, started = await _run_graph_phase(
405
+ root, quiet=quiet, verbose=verbose,
406
+ on_progress=on_progress, on_progress_console=on_progress_console,
407
+ )
408
+ ok = graph_code == 0
409
+ if not ok:
410
+ message = (
411
+ f"graph builder exit {graph_code}"
412
+ if graph_code is not None
413
+ else (graph_err.strip() or "graph builder unavailable")
414
+ )
415
+ else:
416
+ message = "reprocess completed (graph-only; vectors skipped — vector stack not installed)"
417
+ return RefreshIndexOutput(
418
+ success=ok,
419
+ exit_code=None,
420
+ stdout="",
421
+ stderr="",
422
+ message=message,
423
+ graph_exit_code=graph_code,
424
+ graph_stdout=graph_out[-4000:] if len(graph_out) > 4000 else graph_out,
425
+ graph_stderr=graph_err[-4000:] if len(graph_err) > 4000 else graph_err,
426
+ phases_run=["graph"] if started else [],
427
+ optimize_error=None,
428
+ )
429
+ # Resolve cocoindex the same way the sync path does (pipeline.cocoindex_bin):
430
+ # next to the interpreter first, then via PATH (``shutil.which``). A console
431
+ # script legitimately lives away from the venv python — e.g. ``pip install
432
+ # --user`` places it in ``~/.local/bin``. Honoring PATH keeps ``reprocess``
433
+ # (no flags) consistent with init/increment/``reprocess --vectors-only``,
434
+ # which all resolve through cocoindex_bin(); previously this path checked
435
+ # only next-to-python and failed with "cocoindex not found next to Python"
436
+ # even though cocoindex was reachable on PATH.
437
+ cocoindex_bin = resolve_cocoindex_bin()
326
438
  if not cocoindex_bin.is_file():
327
439
  # 127 pre-spawn: emit a terminal failed vectors event so the renderer's
328
440
  # task doesn't hang at running (matches the sync pipeline path).
@@ -330,23 +442,21 @@ async def run_refresh_pipeline(
330
442
  on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
331
443
  return RefreshIndexOutput(
332
444
  success=False,
333
- message=f"cocoindex not found next to Python: {cocoindex_bin}",
445
+ message=(
446
+ f"cocoindex not found next to Python ({cocoindex_bin}) or on PATH; "
447
+ "install cocoindex[lancedb] into the same venv or add its bin/ to PATH."
448
+ ),
334
449
  phases_run=[],
335
450
  )
336
- flow_path = root / "java_index_flow_lancedb.py"
337
- bundle_dir = Path(__file__).resolve().parent
451
+ flow_path = _FLOW_FILE
338
452
  if not flow_path.is_file():
339
- fallback = bundle_dir / "java_index_flow_lancedb.py"
340
- if fallback.is_file():
341
- flow_path = fallback
342
- else:
343
- if on_progress is not None:
344
- on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
345
- return RefreshIndexOutput(
346
- success=False,
347
- message=f"java_index_flow_lancedb.py not found under {root} nor {bundle_dir}",
348
- phases_run=[],
349
- )
453
+ if on_progress is not None:
454
+ on_progress(ProgressEvent(kind="vectors", phase=None, pass_=None, done=None, total=None, status="failed", elapsed_s=None))
455
+ return RefreshIndexOutput(
456
+ success=False,
457
+ message=f"java_index_flow_lancedb.py not found at {flow_path}",
458
+ phases_run=[],
459
+ )
350
460
  proc: asyncio.subprocess.Process | None = None
351
461
  out_b, err_b = b"", b""
352
462
  # DROP the Lance target tables so the update takes the fast INSERT path
@@ -466,40 +576,12 @@ async def run_refresh_pipeline(
466
576
  except Exception as exc:
467
577
  optimize_error = f"lance optimize failed: {exc}"
468
578
  print(f"java-codebase-rag: {optimize_error}", file=sys.stderr)
469
- builder = Path(__file__).resolve().parent / "build_ast_graph.py"
470
- if builder.is_file():
471
- try:
472
- graph_args = [
473
- sys.executable,
474
- str(builder),
475
- "--source-root",
476
- str(root),
477
- "--ladybug-path",
478
- resolve_ladybug_path(),
479
- ]
480
- if not quiet:
481
- graph_args.append("--verbose")
482
- gproc = await asyncio.create_subprocess_exec(
483
- *graph_args,
484
- cwd=str(root),
485
- env=_cocoindex_subprocess_env(root),
486
- stdout=asyncio.subprocess.PIPE,
487
- stderr=asyncio.subprocess.PIPE,
488
- )
489
- phases_run = ["vectors", "graph"]
490
- if quiet:
491
- gout_b, gerr_b = await gproc.communicate()
492
- else:
493
- gout_b, gerr_b = await accumulate_and_relay_subprocess_streams(
494
- gproc, relay=True, verbose=verbose,
495
- on_progress=on_progress, on_progress_console=on_progress_console,
496
- )
497
- graph_code = gproc.returncode
498
- graph_out = gout_b.decode(errors="replace")
499
- graph_err = gerr_b.decode(errors="replace")
500
- except Exception as exc:
501
- graph_code = -1
502
- graph_err = f"graph builder spawn failed: {exc}"
579
+ graph_code, graph_out, graph_err, graph_started = await _run_graph_phase(
580
+ root, quiet=quiet, verbose=verbose,
581
+ on_progress=on_progress, on_progress_console=on_progress_console,
582
+ )
583
+ if graph_started:
584
+ phases_run = ["vectors", "graph"]
503
585
  message: str | None = None
504
586
  if not ok:
505
587
  message = f"cocoindex exit {proc.returncode}"
@@ -788,6 +870,8 @@ def main() -> None:
788
870
  cfg = resolve_operator_config(source_root=_source_root_for_operator_config())
789
871
  cfg.apply_to_os_environ()
790
872
  mcp_v2.set_hints_enabled(cfg.hints_enabled)
873
+ mcp_v2.set_absence_config(cfg)
874
+ resolve_service.set_absence_config(cfg)
791
875
 
792
876
  # Initialize scope manager for automatic microservice detection
793
877
  global _scope_manager
@@ -18,9 +18,28 @@ from java_codebase_rag.progress import ProgressEvent, ProgressRelay, make_relay
18
18
 
19
19
  COCOINDEX_TARGET = "java_index_flow_lancedb.py:JavaCodeIndexLance"
20
20
 
21
-
22
- def bundle_dir() -> Path:
23
- return Path(__file__).resolve().parent.parent
21
+ # Operator-facing line printed when an indexing command skips the vectors phase
22
+ # because the vector stack is absent (graph-only install, e.g. macOS Intel where
23
+ # cocoindex[lancedb]/lancedb/sentence-transformers are gated off by PEP 508 markers).
24
+ # Single source of truth — imported by cli.py (init/increment) and server.py
25
+ # (reprocess) so the wording can't drift between the two paths.
26
+ VECTORS_SKIPPED_GRAPH_ONLY = (
27
+ "java-codebase-rag: vectors skipped — vector stack not installed on this platform "
28
+ "(graph-only mode). The graph is built/refreshed; semantic search is unavailable."
29
+ )
30
+
31
+
32
+ # Package-internal locations of the cocoindex flow definition and the graph
33
+ # builder. Both are executed *by file path* — cocoindex loads the flow via a
34
+ # ``file:Class`` target (COCOINDEX_TARGET below) and the builder runs as
35
+ # ``python <path>`` — so we must resolve their paths without importing them
36
+ # (this module deliberately stays free of heavy ML imports at import time).
37
+ # Derived from this file's location so they resolve under both editable and
38
+ # wheel installs.
39
+ _PKG_DIR = Path(__file__).resolve().parent
40
+ _FLOW_DIR = _PKG_DIR / "index"
41
+ _FLOW_FILE = _FLOW_DIR / "java_index_flow_lancedb.py"
42
+ _BUILDER_FILE = _PKG_DIR / "graph" / "build_ast_graph.py"
24
43
 
25
44
 
26
45
  def cocoindex_bin() -> Path:
@@ -256,7 +275,7 @@ def _run_cocoindex_update_impl(
256
275
  stdout="",
257
276
  stderr=f"cocoindex not found: {exe}",
258
277
  )
259
- bd = bundle_dir()
278
+ bd = _FLOW_DIR
260
279
  flow = bd / "java_index_flow_lancedb.py"
261
280
  if not flow.is_file():
262
281
  if on_progress is not None:
@@ -333,7 +352,7 @@ def run_cocoindex_drop(env: dict[str, str], *, quiet: bool) -> subprocess.Comple
333
352
  stdout="",
334
353
  stderr=f"cocoindex not found: {exe}",
335
354
  )
336
- bd = bundle_dir()
355
+ bd = _FLOW_DIR
337
356
  cmd = [str(exe), "drop", COCOINDEX_TARGET, "-f"]
338
357
  if quiet:
339
358
  cmd.append("-q")
@@ -356,7 +375,7 @@ def run_build_ast_graph(
356
375
  on_progress: Callable[[ProgressEvent], None] | None = None,
357
376
  on_progress_console: object | None = None,
358
377
  ) -> subprocess.CompletedProcess[str]:
359
- builder = bundle_dir() / "build_ast_graph.py"
378
+ builder = _BUILDER_FILE
360
379
  if not builder.is_file():
361
380
  return subprocess.CompletedProcess(
362
381
  args=[],
@@ -414,7 +433,7 @@ def run_incremental_graph(
414
433
  on_progress_console: object | None = None,
415
434
  ) -> subprocess.CompletedProcess[str]:
416
435
  """Run incremental graph rebuild by passing --incremental flag to build_ast_graph.py."""
417
- builder = bundle_dir() / "build_ast_graph.py"
436
+ builder = _BUILDER_FILE
418
437
  if not builder.is_file():
419
438
  return subprocess.CompletedProcess(
420
439
  args=[],
File without changes