java-codebase-rag 0.5.0__py3-none-any.whl → 0.5.2__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.
- build_ast_graph.py +3 -6
- java_codebase_rag/cli.py +31 -0
- java_codebase_rag/installer.py +418 -9
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/METADATA +31 -2
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/RECORD +9 -9
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/WHEEL +0 -0
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/entry_points.txt +0 -0
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {java_codebase_rag-0.5.0.dist-info → java_codebase_rag-0.5.2.dist-info}/top_level.txt +0 -0
build_ast_graph.py
CHANGED
|
@@ -3422,12 +3422,10 @@ def incremental_rebuild(
|
|
|
3422
3422
|
pass6_match_edges(tables, verbose=verbose)
|
|
3423
3423
|
write_kuzu(kuzu_path, tables, source_root=source_root, verbose=verbose)
|
|
3424
3424
|
|
|
3425
|
-
n_files = _init_hash_tracker(source_root, kuzu_path)
|
|
3426
|
-
|
|
3427
3425
|
return IncrementalResult(
|
|
3428
3426
|
mode="full_fallback",
|
|
3429
3427
|
files_changed=0,
|
|
3430
|
-
files_added=
|
|
3428
|
+
files_added=0,
|
|
3431
3429
|
files_removed=0,
|
|
3432
3430
|
dependents_reprocessed=0,
|
|
3433
3431
|
elapsed_sec=time.time() - t_start,
|
|
@@ -3648,12 +3646,10 @@ def _fallback_to_full(source_root: Path, kuzu_path: Path, verbose: bool, t_start
|
|
|
3648
3646
|
pass6_match_edges(tables, verbose=verbose)
|
|
3649
3647
|
write_kuzu(kuzu_path, tables, source_root=source_root, verbose=verbose)
|
|
3650
3648
|
|
|
3651
|
-
n_files = _init_hash_tracker(source_root, kuzu_path)
|
|
3652
|
-
|
|
3653
3649
|
return IncrementalResult(
|
|
3654
3650
|
mode="full_fallback",
|
|
3655
3651
|
files_changed=0,
|
|
3656
|
-
files_added=
|
|
3652
|
+
files_added=0,
|
|
3657
3653
|
files_removed=0,
|
|
3658
3654
|
dependents_reprocessed=0,
|
|
3659
3655
|
elapsed_sec=time.time() - t_start,
|
|
@@ -3785,6 +3781,7 @@ def write_kuzu(
|
|
|
3785
3781
|
_verbose_stderr_line(f"[graph] writing · routes/exposes written in {time.time() - t2:.2f}s")
|
|
3786
3782
|
_write_meta(conn, tables, source_root)
|
|
3787
3783
|
conn.close()
|
|
3784
|
+
_init_hash_tracker(source_root, db_path)
|
|
3788
3785
|
|
|
3789
3786
|
|
|
3790
3787
|
# ---------- CLI ----------
|
java_codebase_rag/cli.py
CHANGED
|
@@ -496,6 +496,15 @@ def _cmd_install(args: argparse.Namespace) -> int:
|
|
|
496
496
|
)
|
|
497
497
|
|
|
498
498
|
|
|
499
|
+
def _cmd_update(args: argparse.Namespace) -> int:
|
|
500
|
+
from java_codebase_rag.installer import run_update
|
|
501
|
+
|
|
502
|
+
return run_update(
|
|
503
|
+
force=bool(args.force),
|
|
504
|
+
dry_run=bool(args.dry_run),
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
499
508
|
def _cmd_erase(args: argparse.Namespace) -> int:
|
|
500
509
|
cfg = _resolved_from_ns(args)
|
|
501
510
|
_startup_hints(cfg)
|
|
@@ -760,6 +769,28 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
760
769
|
_add_verbosity_flags(install)
|
|
761
770
|
install.set_defaults(handler=_cmd_install)
|
|
762
771
|
|
|
772
|
+
update = subparsers.add_parser(
|
|
773
|
+
"update",
|
|
774
|
+
help="Refresh shipped artifacts (skill, agent, MCP entry) after pip upgrade.",
|
|
775
|
+
description=(
|
|
776
|
+
"Post-upgrade refresh: overwrites skill and agent files with the latest "
|
|
777
|
+
"shipped versions and updates the MCP command path. Use --dry-run to "
|
|
778
|
+
"preview changes without writing. Requires a prior `install` run."
|
|
779
|
+
),
|
|
780
|
+
)
|
|
781
|
+
update.add_argument(
|
|
782
|
+
"--force",
|
|
783
|
+
action="store_true",
|
|
784
|
+
help="Overwrite all artifacts even if content matches.",
|
|
785
|
+
)
|
|
786
|
+
update.add_argument(
|
|
787
|
+
"--dry-run",
|
|
788
|
+
action="store_true",
|
|
789
|
+
help="Print changes without writing files.",
|
|
790
|
+
)
|
|
791
|
+
_add_verbosity_flags(update)
|
|
792
|
+
update.set_defaults(handler=_cmd_update)
|
|
793
|
+
|
|
763
794
|
increment = subparsers.add_parser(
|
|
764
795
|
"increment",
|
|
765
796
|
help="Pick up changes since the last index update.",
|
java_codebase_rag/installer.py
CHANGED
|
@@ -22,6 +22,14 @@ import yaml
|
|
|
22
22
|
|
|
23
23
|
Scope = Literal["project", "user"]
|
|
24
24
|
|
|
25
|
+
# MCP server name constant
|
|
26
|
+
_MCP_SERVER_NAME = "java-codebase-rag"
|
|
27
|
+
|
|
28
|
+
# Exit code constants
|
|
29
|
+
EXIT_SUCCESS = 0
|
|
30
|
+
EXIT_PARTIAL = 1
|
|
31
|
+
EXIT_FATAL = 2
|
|
32
|
+
|
|
25
33
|
|
|
26
34
|
class ArtifactResult(NamedTuple):
|
|
27
35
|
"""Result of deploying a single artifact."""
|
|
@@ -111,16 +119,27 @@ def prompt(
|
|
|
111
119
|
|
|
112
120
|
# Lazy import questionary only when needed (TTY)
|
|
113
121
|
import questionary
|
|
122
|
+
from prompt_toolkit.styles import Style
|
|
123
|
+
|
|
124
|
+
# Strip default ANSI colors — rely on ●/○ indicators only, no fg/bg highlights
|
|
125
|
+
# noinherit prevents prompt_toolkit from merging in questionary's default fg colors
|
|
126
|
+
no_color_style = Style(
|
|
127
|
+
[
|
|
128
|
+
("highlighted", "noinherit"),
|
|
129
|
+
("selected", "noinherit"),
|
|
130
|
+
("pointer", "noinherit bold"),
|
|
131
|
+
]
|
|
132
|
+
)
|
|
114
133
|
|
|
115
134
|
try:
|
|
116
135
|
if prompt_type == "checkbox":
|
|
117
|
-
return questionary.checkbox(message, choices=choices).ask()
|
|
136
|
+
return questionary.checkbox(message, choices=choices, style=no_color_style).ask()
|
|
118
137
|
elif prompt_type == "select":
|
|
119
|
-
return questionary.select(message, choices=choices).ask()
|
|
138
|
+
return questionary.select(message, choices=choices, style=no_color_style).ask()
|
|
120
139
|
elif prompt_type == "text":
|
|
121
|
-
return questionary.text(message, default=default).ask()
|
|
140
|
+
return questionary.text(message, default=default, style=no_color_style).ask()
|
|
122
141
|
elif prompt_type == "confirm":
|
|
123
|
-
return questionary.confirm(message).ask()
|
|
142
|
+
return questionary.confirm(message, style=no_color_style).ask()
|
|
124
143
|
else:
|
|
125
144
|
raise ValueError(f"Unknown prompt_type: {prompt_type}")
|
|
126
145
|
except KeyboardInterrupt:
|
|
@@ -279,10 +298,15 @@ def select_hosts(*, non_interactive: bool, cli_agents: list[str] | None) -> list
|
|
|
279
298
|
print(f"Valid agents: {', '.join(HOSTS.keys())}")
|
|
280
299
|
raise SystemExit(2)
|
|
281
300
|
|
|
282
|
-
# Interactive: show checkbox with
|
|
301
|
+
# Interactive: show checkbox with claude-code pre-selected (most common)
|
|
302
|
+
# Changed from all pre-selected to avoid confusion
|
|
283
303
|
host_names = list(HOSTS.keys())
|
|
284
|
-
choices = [
|
|
304
|
+
choices = [
|
|
305
|
+
{"name": name, "value": name, "checked": (name == "claude-code")}
|
|
306
|
+
for name in host_names
|
|
307
|
+
]
|
|
285
308
|
|
|
309
|
+
print("Note: You can select multiple agent hosts with Space. Navigate with arrow keys.")
|
|
286
310
|
selected = prompt("checkbox", "Select agent hosts to configure:", choices=choices)
|
|
287
311
|
|
|
288
312
|
if not selected:
|
|
@@ -296,6 +320,8 @@ def select_hosts(*, non_interactive: bool, cli_agents: list[str] | None) -> list
|
|
|
296
320
|
else:
|
|
297
321
|
raise SystemExit(2)
|
|
298
322
|
|
|
323
|
+
# Show confirmation of what will be deployed
|
|
324
|
+
print(f"Will deploy to: {', '.join(selected)}")
|
|
299
325
|
return [HOSTS[name] for name in selected]
|
|
300
326
|
|
|
301
327
|
|
|
@@ -319,6 +345,8 @@ def select_scope(*, non_interactive: bool, cli_scope: str | None) -> Scope:
|
|
|
319
345
|
return "project"
|
|
320
346
|
|
|
321
347
|
# Interactive: prompt for scope
|
|
348
|
+
print("Note: 'project' scope stores configs in the project directory.")
|
|
349
|
+
print(" 'user' scope stores configs in your home directory.")
|
|
322
350
|
selected = prompt(
|
|
323
351
|
"select",
|
|
324
352
|
"Select installation scope:",
|
|
@@ -328,6 +356,7 @@ def select_scope(*, non_interactive: bool, cli_scope: str | None) -> Scope:
|
|
|
328
356
|
if not selected:
|
|
329
357
|
return "project"
|
|
330
358
|
|
|
359
|
+
print(f"Selected scope: {selected}")
|
|
331
360
|
return selected # type: ignore
|
|
332
361
|
|
|
333
362
|
|
|
@@ -422,14 +451,14 @@ def merge_mcp_config(config_path: Path, host: HostConfig, *, mcp_command: str) -
|
|
|
422
451
|
|
|
423
452
|
# Prepare new entry
|
|
424
453
|
new_entry = {"command": mcp_command, "type": "stdio"}
|
|
425
|
-
existing_entry = config["mcpServers"].get(
|
|
454
|
+
existing_entry = config["mcpServers"].get(_MCP_SERVER_NAME)
|
|
426
455
|
|
|
427
456
|
# Check if entry already exists with same config
|
|
428
457
|
if existing_entry == new_entry:
|
|
429
458
|
return False
|
|
430
459
|
|
|
431
460
|
# Merge/update entry
|
|
432
|
-
config["mcpServers"][
|
|
461
|
+
config["mcpServers"][_MCP_SERVER_NAME] = new_entry
|
|
433
462
|
|
|
434
463
|
# Write atomically (write to tmp, then rename)
|
|
435
464
|
tmp_name = None
|
|
@@ -738,7 +767,8 @@ def run_init_if_needed(
|
|
|
738
767
|
)
|
|
739
768
|
from java_codebase_rag.pipeline import run_build_ast_graph, run_cocoindex_update
|
|
740
769
|
|
|
741
|
-
|
|
770
|
+
has_existing, _ = index_dir_has_existing_artifacts(index_dir)
|
|
771
|
+
if has_existing:
|
|
742
772
|
print("Index already exists. Run `java-codebase-rag reprocess` to rebuild.")
|
|
743
773
|
return False
|
|
744
774
|
|
|
@@ -762,6 +792,8 @@ def run_init_if_needed(
|
|
|
762
792
|
g = run_build_ast_graph(
|
|
763
793
|
source_root=cfg.source_root,
|
|
764
794
|
kuzu_path=cfg.kuzu_path,
|
|
795
|
+
verbose=not quiet,
|
|
796
|
+
quiet=quiet,
|
|
765
797
|
env=env,
|
|
766
798
|
)
|
|
767
799
|
if g.returncode != 0:
|
|
@@ -823,6 +855,383 @@ def handle_rerun(cwd: Path, *, non_interactive: bool) -> dict | None:
|
|
|
823
855
|
return existing_config
|
|
824
856
|
|
|
825
857
|
|
|
858
|
+
def detect_configured_hosts(cwd: Path) -> list[tuple[HostConfig, str]]:
|
|
859
|
+
"""Scan project + user config files for java-codebase-rag MCP entries.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
cwd: Current working directory (for project-scope configs)
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
List of (host_config, scope) tuples where scope is "project" or "user"
|
|
866
|
+
"""
|
|
867
|
+
detected = []
|
|
868
|
+
|
|
869
|
+
# Check all hosts in both project and user scopes
|
|
870
|
+
for host_name, host_config in HOSTS.items():
|
|
871
|
+
# Check project scope
|
|
872
|
+
project_mcp_path = host_config.mcp_config_path("project", cwd)
|
|
873
|
+
if _has_java_codebase_rag_entry(project_mcp_path):
|
|
874
|
+
detected.append((host_config, "project"))
|
|
875
|
+
|
|
876
|
+
# Check user scope
|
|
877
|
+
user_mcp_path = host_config.mcp_config_path("user", cwd)
|
|
878
|
+
if _has_java_codebase_rag_entry(user_mcp_path):
|
|
879
|
+
detected.append((host_config, "user"))
|
|
880
|
+
|
|
881
|
+
return detected
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def _has_java_codebase_rag_entry(config_path: Path) -> bool:
|
|
885
|
+
"""Check if MCP config file has a java-codebase-rag entry.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
config_path: Path to MCP config file
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
True if file exists and contains java-codebase-rag in mcpServers
|
|
892
|
+
"""
|
|
893
|
+
if not config_path.is_file():
|
|
894
|
+
return False
|
|
895
|
+
|
|
896
|
+
try:
|
|
897
|
+
with open(config_path, "r") as f:
|
|
898
|
+
config = json.load(f)
|
|
899
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
900
|
+
return False
|
|
901
|
+
|
|
902
|
+
mcp_servers = config.get("mcpServers", {})
|
|
903
|
+
return _MCP_SERVER_NAME in mcp_servers
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def refresh_artifacts(
|
|
907
|
+
host: HostConfig,
|
|
908
|
+
scope: str,
|
|
909
|
+
cwd: Path,
|
|
910
|
+
*,
|
|
911
|
+
force: bool,
|
|
912
|
+
dry_run: bool,
|
|
913
|
+
) -> list[ArtifactResult]:
|
|
914
|
+
"""Overwrite skill and agent files from package data. Skip MCP if entry is correct.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
host: HostConfig for the agent host
|
|
918
|
+
scope: Installation scope ("project" or "user")
|
|
919
|
+
cwd: Current working directory
|
|
920
|
+
force: If True, overwrite all files even if matching
|
|
921
|
+
dry_run: If True, print changes without writing
|
|
922
|
+
|
|
923
|
+
Returns:
|
|
924
|
+
List of ArtifactResult objects for each artifact
|
|
925
|
+
"""
|
|
926
|
+
results = []
|
|
927
|
+
|
|
928
|
+
# Refresh skill file
|
|
929
|
+
skills_dir = host.skills_dir(scope, cwd)
|
|
930
|
+
skill_dest = skills_dir / "explore-codebase" / "SKILL.md"
|
|
931
|
+
skill_result = _refresh_file(
|
|
932
|
+
skill_dest,
|
|
933
|
+
"skills/explore-codebase/SKILL.md",
|
|
934
|
+
artifact_type="skill",
|
|
935
|
+
force=force,
|
|
936
|
+
dry_run=dry_run,
|
|
937
|
+
)
|
|
938
|
+
results.append(skill_result)
|
|
939
|
+
|
|
940
|
+
# Refresh agent file
|
|
941
|
+
agents_dir = host.agents_dir(scope, cwd)
|
|
942
|
+
agent_dest = agents_dir / "explorer-rag-enhanced.md"
|
|
943
|
+
agent_result = _refresh_file(
|
|
944
|
+
agent_dest,
|
|
945
|
+
"agents/explorer-rag-enhanced.md",
|
|
946
|
+
artifact_type="agent",
|
|
947
|
+
force=force,
|
|
948
|
+
dry_run=dry_run,
|
|
949
|
+
)
|
|
950
|
+
results.append(agent_result)
|
|
951
|
+
|
|
952
|
+
# Refresh MCP config (update command path if needed)
|
|
953
|
+
mcp_config_path = host.mcp_config_path(scope, cwd)
|
|
954
|
+
mcp_result = _refresh_mcp_config(mcp_config_path, host, force=force, dry_run=dry_run)
|
|
955
|
+
results.append(mcp_result)
|
|
956
|
+
|
|
957
|
+
return results
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def _refresh_file(
|
|
961
|
+
dest_path: Path,
|
|
962
|
+
package_relative_path: str,
|
|
963
|
+
*,
|
|
964
|
+
artifact_type: str,
|
|
965
|
+
force: bool,
|
|
966
|
+
dry_run: bool,
|
|
967
|
+
) -> ArtifactResult:
|
|
968
|
+
"""Refresh a single file from package data.
|
|
969
|
+
|
|
970
|
+
Args:
|
|
971
|
+
dest_path: Destination file path
|
|
972
|
+
package_relative_path: Path relative to install_data
|
|
973
|
+
artifact_type: Type of artifact (for error messages)
|
|
974
|
+
force: If True, overwrite even if matching
|
|
975
|
+
dry_run: If True, print without writing
|
|
976
|
+
|
|
977
|
+
Returns:
|
|
978
|
+
ArtifactResult with success status
|
|
979
|
+
"""
|
|
980
|
+
try:
|
|
981
|
+
# Read package data
|
|
982
|
+
package_content = _read_package_artifact(package_relative_path)
|
|
983
|
+
|
|
984
|
+
# Check if file exists
|
|
985
|
+
if dest_path.is_file():
|
|
986
|
+
existing_content = dest_path.read_text(encoding="utf-8")
|
|
987
|
+
|
|
988
|
+
# Skip if content matches and not forcing
|
|
989
|
+
if package_content == existing_content and not force:
|
|
990
|
+
return ArtifactResult(path=dest_path, success=True, error=None)
|
|
991
|
+
|
|
992
|
+
# Content differs or force mode
|
|
993
|
+
if dry_run:
|
|
994
|
+
print(f"Would update {artifact_type} file at {dest_path}")
|
|
995
|
+
return ArtifactResult(path=dest_path, success=True, error=None)
|
|
996
|
+
|
|
997
|
+
elif dry_run:
|
|
998
|
+
print(f"Would create {artifact_type} file at {dest_path}")
|
|
999
|
+
return ArtifactResult(path=dest_path, success=True, error=None)
|
|
1000
|
+
|
|
1001
|
+
# Ensure parent directory exists
|
|
1002
|
+
if not dry_run:
|
|
1003
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1004
|
+
|
|
1005
|
+
# Check writability
|
|
1006
|
+
if not _is_writable(dest_path.parent):
|
|
1007
|
+
return ArtifactResult(
|
|
1008
|
+
path=dest_path,
|
|
1009
|
+
success=False,
|
|
1010
|
+
error=f"Directory not writable: {dest_path.parent}",
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
# Write file (skip in dry_run mode)
|
|
1014
|
+
if not dry_run:
|
|
1015
|
+
dest_path.write_text(package_content, encoding="utf-8")
|
|
1016
|
+
print(f"Updated {artifact_type} file at {dest_path}")
|
|
1017
|
+
|
|
1018
|
+
return ArtifactResult(path=dest_path, success=True, error=None)
|
|
1019
|
+
|
|
1020
|
+
except Exception as e:
|
|
1021
|
+
return ArtifactResult(path=dest_path, success=False, error=str(e))
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def _refresh_mcp_config(
|
|
1025
|
+
config_path: Path,
|
|
1026
|
+
host: HostConfig,
|
|
1027
|
+
*,
|
|
1028
|
+
force: bool,
|
|
1029
|
+
dry_run: bool,
|
|
1030
|
+
) -> ArtifactResult:
|
|
1031
|
+
"""Refresh MCP config entry (update command path if needed).
|
|
1032
|
+
|
|
1033
|
+
Args:
|
|
1034
|
+
config_path: Path to MCP config file
|
|
1035
|
+
host: HostConfig for the agent host
|
|
1036
|
+
force: If True, update even if matching
|
|
1037
|
+
dry_run: If True, print without writing
|
|
1038
|
+
|
|
1039
|
+
Returns:
|
|
1040
|
+
ArtifactResult with success status
|
|
1041
|
+
"""
|
|
1042
|
+
try:
|
|
1043
|
+
# Resolve current MCP command path
|
|
1044
|
+
# Catch SystemExit because resolve_mcp_command raises it when binary not found
|
|
1045
|
+
try:
|
|
1046
|
+
mcp_command = resolve_mcp_command(non_interactive=True)
|
|
1047
|
+
except SystemExit:
|
|
1048
|
+
return ArtifactResult(
|
|
1049
|
+
path=config_path,
|
|
1050
|
+
success=False,
|
|
1051
|
+
error="java-codebase-rag-mcp not found on PATH",
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
# Prepare new entry
|
|
1055
|
+
new_entry = {"command": mcp_command, "type": "stdio"}
|
|
1056
|
+
|
|
1057
|
+
# Read existing config
|
|
1058
|
+
if config_path.is_file():
|
|
1059
|
+
try:
|
|
1060
|
+
with open(config_path, "r") as f:
|
|
1061
|
+
config = json.load(f)
|
|
1062
|
+
except json.JSONDecodeError as e:
|
|
1063
|
+
return ArtifactResult(
|
|
1064
|
+
path=config_path,
|
|
1065
|
+
success=False,
|
|
1066
|
+
error=f"Failed to parse {config_path}: {e}",
|
|
1067
|
+
)
|
|
1068
|
+
else:
|
|
1069
|
+
config = {}
|
|
1070
|
+
|
|
1071
|
+
# Ensure mcpServers key exists
|
|
1072
|
+
if "mcpServers" not in config:
|
|
1073
|
+
config["mcpServers"] = {}
|
|
1074
|
+
|
|
1075
|
+
existing_entry = config["mcpServers"].get(_MCP_SERVER_NAME)
|
|
1076
|
+
|
|
1077
|
+
# Check if entry already matches (skip unless force)
|
|
1078
|
+
if existing_entry == new_entry and not force:
|
|
1079
|
+
return ArtifactResult(path=config_path, success=True, error=None)
|
|
1080
|
+
|
|
1081
|
+
# Entry differs or force mode
|
|
1082
|
+
if dry_run:
|
|
1083
|
+
print(f"Would update MCP config at {config_path}")
|
|
1084
|
+
return ArtifactResult(path=config_path, success=True, error=None)
|
|
1085
|
+
|
|
1086
|
+
# Merge/update entry
|
|
1087
|
+
config["mcpServers"][_MCP_SERVER_NAME] = new_entry
|
|
1088
|
+
|
|
1089
|
+
# Ensure parent directory exists
|
|
1090
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1091
|
+
|
|
1092
|
+
# Check writability
|
|
1093
|
+
if not _is_writable(config_path.parent):
|
|
1094
|
+
return ArtifactResult(
|
|
1095
|
+
path=config_path,
|
|
1096
|
+
success=False,
|
|
1097
|
+
error=f"Directory not writable: {config_path.parent}",
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
# Write atomically
|
|
1101
|
+
tmp_name = None
|
|
1102
|
+
try:
|
|
1103
|
+
with tempfile.NamedTemporaryFile(
|
|
1104
|
+
mode="w",
|
|
1105
|
+
dir=config_path.parent,
|
|
1106
|
+
prefix=f".{config_path.name}.",
|
|
1107
|
+
delete=False,
|
|
1108
|
+
) as tmp:
|
|
1109
|
+
json.dump(config, tmp, indent=2)
|
|
1110
|
+
tmp.flush()
|
|
1111
|
+
os.fsync(tmp.fileno())
|
|
1112
|
+
tmp_name = tmp.name
|
|
1113
|
+
|
|
1114
|
+
# Atomic rename
|
|
1115
|
+
os.rename(tmp_name, config_path)
|
|
1116
|
+
print(f"Updated MCP config at {config_path}")
|
|
1117
|
+
return ArtifactResult(path=config_path, success=True, error=None)
|
|
1118
|
+
|
|
1119
|
+
except (IOError, OSError) as e:
|
|
1120
|
+
if tmp_name:
|
|
1121
|
+
try:
|
|
1122
|
+
os.unlink(tmp_name)
|
|
1123
|
+
except OSError:
|
|
1124
|
+
pass
|
|
1125
|
+
raise RuntimeError(f"Failed to write {config_path}: {e}") from e
|
|
1126
|
+
|
|
1127
|
+
except SystemExit as e:
|
|
1128
|
+
# Catch SystemExit from resolve_mcp_command and other exits
|
|
1129
|
+
return ArtifactResult(path=config_path, success=False, error=f"Command failed: {e.code}")
|
|
1130
|
+
except Exception as e:
|
|
1131
|
+
return ArtifactResult(path=config_path, success=False, error=str(e))
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
def run_update(
|
|
1135
|
+
*,
|
|
1136
|
+
force: bool,
|
|
1137
|
+
dry_run: bool,
|
|
1138
|
+
cwd: Path | None = None,
|
|
1139
|
+
) -> int:
|
|
1140
|
+
"""Run the update pipeline. Returns exit code.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
force: If True, overwrite all artifacts even if matching
|
|
1144
|
+
dry_run: If True, print changes without writing
|
|
1145
|
+
cwd: Current working directory (defaults to Path.cwd())
|
|
1146
|
+
|
|
1147
|
+
Returns:
|
|
1148
|
+
Exit code (0=success, 1=partial, 2=fatal)
|
|
1149
|
+
"""
|
|
1150
|
+
if cwd is None:
|
|
1151
|
+
cwd = Path.cwd()
|
|
1152
|
+
cwd = cwd.resolve()
|
|
1153
|
+
|
|
1154
|
+
# Detect configured hosts
|
|
1155
|
+
configured_hosts = detect_configured_hosts(cwd)
|
|
1156
|
+
|
|
1157
|
+
if not configured_hosts:
|
|
1158
|
+
print("No configured agent hosts found.")
|
|
1159
|
+
print("Run `java-codebase-rag install` first.")
|
|
1160
|
+
return EXIT_FATAL
|
|
1161
|
+
|
|
1162
|
+
print(f"Found {len(configured_hosts)} configured host(s).")
|
|
1163
|
+
|
|
1164
|
+
# Refresh artifacts for each host
|
|
1165
|
+
all_results = []
|
|
1166
|
+
for host_config, scope in configured_hosts:
|
|
1167
|
+
print(f"\nRefreshing {host_config.name} ({scope} scope)...")
|
|
1168
|
+
results = refresh_artifacts(host_config, scope, cwd, force=force, dry_run=dry_run)
|
|
1169
|
+
all_results.extend(results)
|
|
1170
|
+
|
|
1171
|
+
# Check for partial failures
|
|
1172
|
+
partial_failures = [r for r in all_results if not r.success]
|
|
1173
|
+
has_artifact_failures = len(partial_failures) > 0
|
|
1174
|
+
if partial_failures:
|
|
1175
|
+
print("\nWarning: Some artifacts failed to update:")
|
|
1176
|
+
for r in partial_failures:
|
|
1177
|
+
print(f" {r.path}: {r.error}")
|
|
1178
|
+
|
|
1179
|
+
# Check if index exists
|
|
1180
|
+
from java_codebase_rag.config import (
|
|
1181
|
+
discover_project_root,
|
|
1182
|
+
index_dir_has_existing_artifacts,
|
|
1183
|
+
resolve_operator_config,
|
|
1184
|
+
)
|
|
1185
|
+
from java_codebase_rag.pipeline import run_cocoindex_update
|
|
1186
|
+
|
|
1187
|
+
project_root = discover_project_root(cwd)
|
|
1188
|
+
if project_root is None:
|
|
1189
|
+
print("\nNo project configuration found (.java-codebase-rag.yml).")
|
|
1190
|
+
print("Skipping index update.")
|
|
1191
|
+
return EXIT_PARTIAL if has_artifact_failures else EXIT_SUCCESS
|
|
1192
|
+
|
|
1193
|
+
# Resolve configuration
|
|
1194
|
+
try:
|
|
1195
|
+
cfg = resolve_operator_config(source_root=project_root, cli_index_dir=None)
|
|
1196
|
+
index_dir = cfg.index_dir
|
|
1197
|
+
except Exception as e:
|
|
1198
|
+
print(f"\nWarning: Failed to resolve configuration: {e}")
|
|
1199
|
+
print("Skipping index update.")
|
|
1200
|
+
return EXIT_PARTIAL if has_artifact_failures else EXIT_SUCCESS
|
|
1201
|
+
|
|
1202
|
+
# Check if index has existing artifacts
|
|
1203
|
+
index_exists, _ = index_dir_has_existing_artifacts(index_dir)
|
|
1204
|
+
|
|
1205
|
+
if not index_exists:
|
|
1206
|
+
print("\nNo index found.")
|
|
1207
|
+
print("Run `java-codebase-rag install` to create one.")
|
|
1208
|
+
return EXIT_PARTIAL if has_artifact_failures else EXIT_SUCCESS
|
|
1209
|
+
|
|
1210
|
+
# Run increment (LanceDB catch-up)
|
|
1211
|
+
if not dry_run:
|
|
1212
|
+
print("\nUpdating index (incremental LanceDB update)...")
|
|
1213
|
+
cfg.apply_to_os_environ()
|
|
1214
|
+
env = cfg.subprocess_env()
|
|
1215
|
+
|
|
1216
|
+
coco = run_cocoindex_update(env, full_reprocess=False, quiet=True)
|
|
1217
|
+
if coco.returncode != 0:
|
|
1218
|
+
print(f"Error: Index update failed with code {coco.returncode}")
|
|
1219
|
+
return 1
|
|
1220
|
+
|
|
1221
|
+
# Print graph staleness warning
|
|
1222
|
+
from java_codebase_rag.cli import _INCREMENT_WARNING_LINES
|
|
1223
|
+
print("\n" + "\n".join(_INCREMENT_WARNING_LINES))
|
|
1224
|
+
else:
|
|
1225
|
+
print("\nWould run incremental index update.")
|
|
1226
|
+
|
|
1227
|
+
# Print summary
|
|
1228
|
+
print("\nUpdate complete.")
|
|
1229
|
+
successful = [r for r in all_results if r.success]
|
|
1230
|
+
print(f"Updated {len(successful)} artifact(s).")
|
|
1231
|
+
|
|
1232
|
+
return 1 if has_artifact_failures else 0
|
|
1233
|
+
|
|
1234
|
+
|
|
826
1235
|
def run_install(
|
|
827
1236
|
*,
|
|
828
1237
|
non_interactive: bool,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: java-codebase-rag
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: MCP server for semantic + structural search over Java codebases
|
|
5
5
|
Author: HumanBean17
|
|
6
6
|
License-Expression: MIT
|
|
@@ -84,6 +84,31 @@ pip install java-codebase-rag
|
|
|
84
84
|
Python **3.11+** required. After install, `java-codebase-rag --help` should print the CLI groups.
|
|
85
85
|
The package includes the CocoIndex lifecycle dependency used by `init`, `increment`, `reprocess`, and `erase`.
|
|
86
86
|
|
|
87
|
+
### Interactive setup (recommended)
|
|
88
|
+
|
|
89
|
+
Run `java-codebase-rag install` from your Java project root to launch an interactive setup wizard that:
|
|
90
|
+
|
|
91
|
+
1. Detects Java source directories (Maven/Gradle modules)
|
|
92
|
+
2. Configures the embedding model (auto-downloads ~90MB or uses a local path)
|
|
93
|
+
3. Selects agent hosts (Claude Code, Qwen Code, GigaCode)
|
|
94
|
+
4. Deploys MCP registration, skill, and agent artifacts
|
|
95
|
+
5. Generates `.java-codebase-rag.yml` configuration
|
|
96
|
+
6. Runs `init` to build the index
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Interactive mode
|
|
100
|
+
java-codebase-rag install
|
|
101
|
+
|
|
102
|
+
# Non-interactive mode (for CI/automation)
|
|
103
|
+
java-codebase-rag install --non-interactive --agent claude-code
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
After `pip install --upgrade java-codebase-rag`, run `java-codebase-rag update` to refresh shipped artifacts.
|
|
107
|
+
|
|
108
|
+
### Manual registration
|
|
109
|
+
|
|
110
|
+
If you prefer manual configuration, see [`docs/JAVA-CODEBASE-RAG-CLI.md`](./docs/JAVA-CODEBASE-RAG-CLI.md) for the full CLI reference.
|
|
111
|
+
|
|
87
112
|
> **Stability disclaimer.** This package does **not** promise backward compatibility. MCP tool contracts, env vars, Lance/Kuzu schemas, config files, and Python APIs may change without a deprecation period. Track `main` and rebuild indexes when ontology or embedding settings change.
|
|
88
113
|
|
|
89
114
|
---
|
|
@@ -124,7 +149,9 @@ If vector hits come back and graph expansion adds neighbor symbols, the install
|
|
|
124
149
|
|
|
125
150
|
## Wire into an MCP host
|
|
126
151
|
|
|
127
|
-
|
|
152
|
+
> **Quick setup:** Run `java-codebase-rag install` from your Java project root. The interactive wizard handles MCP registration, skill deployment, and configuration for Claude Code, Qwen Code, and GigaCode in one step.
|
|
153
|
+
|
|
154
|
+
### Claude Code (manual)
|
|
128
155
|
|
|
129
156
|
With the package installed, the console script `java-codebase-rag-mcp` is on your `PATH`. Register it project-scoped:
|
|
130
157
|
|
|
@@ -207,6 +234,8 @@ Run `java-codebase-rag --help` to list grouped subcommands. Operator playbook wi
|
|
|
207
234
|
|
|
208
235
|
| Group | Subcommand | What it does |
|
|
209
236
|
|---|---|---|
|
|
237
|
+
| Setup | `install` | Interactive setup wizard: config, MCP registration, skill/agent deployment, indexing. |
|
|
238
|
+
| Setup | `update` | Refresh shipped artifacts (skill, agent, MCP entry) after pip upgrade. |
|
|
210
239
|
| Lifecycle | `init` | First-time index. Refuses if artifacts already exist. |
|
|
211
240
|
| Lifecycle | `increment` | CocoIndex catch-up + incremental Kuzu update. `--vectors-only` for Lance only. |
|
|
212
241
|
| Lifecycle | `reprocess` | Full Lance + Kuzu rebuild. `--vectors-only` / `--graph-only` for a single phase. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
ast_java.py,sha256=OKoH7oX6L7AEEd6UY-spK8BPtWYY1T_4esrTC5VtoK8,98881
|
|
2
2
|
brownfield_events.py,sha256=yxXkKDgMb3VPtaiakGzncHM_EGnda8xIue6w90yYp8s,2055
|
|
3
|
-
build_ast_graph.py,sha256=
|
|
3
|
+
build_ast_graph.py,sha256=1uqgFK2ebBdEc2QcAYK5vU4afOb95jU3zht5FracCkI,148683
|
|
4
4
|
chunk_heuristics.py,sha256=aQk2NOKxzUdqoUAJUO3G3LE0MN_bYZWNLQ0tkmj5uts,1813
|
|
5
5
|
graph_enrich.py,sha256=m3cksCHLqLHhA0Y-TLodbm09YfSJZjlTDN0Z51DiP2c,63317
|
|
6
6
|
index_common.py,sha256=HT6FKHFJ084eFvd3fR1j8z8gf4eWoPHVW8GXLpw464I,285
|
|
@@ -15,17 +15,17 @@ pr_analysis.py,sha256=Zaq90xYgMgrReV3vCGcFhOkK61gIRMAAIgs7ev-rJG4,18410
|
|
|
15
15
|
search_lancedb.py,sha256=-XgtpbJ_3zDLiZ_vGKXjaLpl7RlvgyzUb7oAGoWkXO0,36754
|
|
16
16
|
server.py,sha256=c4Bo0FXPoKP2AwIVP_wiv0XENkmKchLHf0QrQPUUgq4,28645
|
|
17
17
|
java_codebase_rag/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
|
-
java_codebase_rag/cli.py,sha256=
|
|
18
|
+
java_codebase_rag/cli.py,sha256=WW-DsskSGr-d0JXBLkj4IdAa2OsAcLz5e54_DWvD9Sk,33872
|
|
19
19
|
java_codebase_rag/cli_format.py,sha256=arU7P9W6Fvm7X_wzR1wJ8EfyxK1rDP_ESEhdA0ub4Mo,2579
|
|
20
20
|
java_codebase_rag/cli_progress.py,sha256=9jCqEagYOXs32SYVA31_sOCrONvYy7cl1CrdBD2Pg44,3168
|
|
21
21
|
java_codebase_rag/config.py,sha256=1BkRQsdY2ohZ8IWmbTG3WHgotVVUIrRTN537A1QAoCQ,15352
|
|
22
|
-
java_codebase_rag/installer.py,sha256=
|
|
22
|
+
java_codebase_rag/installer.py,sha256=flj330ZPSBrO2iw_yuNFBILHOTVbarMufYwqjZ8JzN0,42778
|
|
23
23
|
java_codebase_rag/pipeline.py,sha256=nMXwX9r7HG9yPstrm7y_vfOMUZuDmw5_1lJTAfR-jwI,9488
|
|
24
24
|
java_codebase_rag/install_data/agents/explorer-rag-enhanced.md,sha256=APl9d-No12qZNZLjU7mwNRwxHIgnT3ZtQZiD4clWlyU,14413
|
|
25
25
|
java_codebase_rag/install_data/skills/explore-codebase/SKILL.md,sha256=pIM-Xdwq_fXkhhBJCdb-fA2nes5c_mMPcdUXb7Adyxo,12040
|
|
26
|
-
java_codebase_rag-0.5.
|
|
27
|
-
java_codebase_rag-0.5.
|
|
28
|
-
java_codebase_rag-0.5.
|
|
29
|
-
java_codebase_rag-0.5.
|
|
30
|
-
java_codebase_rag-0.5.
|
|
31
|
-
java_codebase_rag-0.5.
|
|
26
|
+
java_codebase_rag-0.5.2.dist-info/licenses/LICENSE,sha256=gxvtiHtuviR_q8ZAjWw-QTcF3DyPzg6ZY-lQrr8OPpw,1068
|
|
27
|
+
java_codebase_rag-0.5.2.dist-info/METADATA,sha256=LrlQYmOpTRNtx7q4-sr2yO8dCc5RHy4FgDY5GzvsoR8,16807
|
|
28
|
+
java_codebase_rag-0.5.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
29
|
+
java_codebase_rag-0.5.2.dist-info/entry_points.txt,sha256=mVVQJa0n73OWfhHXYCDoPRrWin_LJhH2Rn0CkJ2iax4,101
|
|
30
|
+
java_codebase_rag-0.5.2.dist-info/top_level.txt,sha256=5aIYoMkvJvvfXvf4iHn2OeSIM7PZXP-0j94eNESnwMw,242
|
|
31
|
+
java_codebase_rag-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|