java-codebase-rag 0.9.2__tar.gz → 0.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {java_codebase_rag-0.9.2/java_codebase_rag.egg-info → java_codebase_rag-0.9.3}/PKG-INFO +2 -1
  2. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/README.md +1 -0
  3. java_codebase_rag-0.9.3/java_codebase_rag/_version.py +35 -0
  4. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/cli.py +6 -0
  5. java_codebase_rag-0.9.3/java_codebase_rag/install_data/agents/explorer-rag-cli.md +106 -0
  6. java_codebase_rag-0.9.3/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +105 -0
  7. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/jrag.py +115 -28
  8. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/jrag_render.py +11 -3
  9. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3/java_codebase_rag.egg-info}/PKG-INFO +2 -1
  10. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag.egg-info/SOURCES.txt +4 -1
  11. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/pyproject.toml +1 -1
  12. java_codebase_rag-0.9.3/tests/test_jrag_enum_choices.py +76 -0
  13. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_render.py +46 -0
  14. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_traversal_direct.py +53 -0
  15. java_codebase_rag-0.9.3/tests/test_version_flag.py +39 -0
  16. java_codebase_rag-0.9.2/java_codebase_rag/install_data/agents/explorer-rag-cli.md +0 -148
  17. java_codebase_rag-0.9.2/java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +0 -183
  18. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/LICENSE +0 -0
  19. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/ast_java.py +0 -0
  20. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/brownfield_events.py +0 -0
  21. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/build_ast_graph.py +0 -0
  22. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/chunk_heuristics.py +0 -0
  23. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/graph_enrich.py +0 -0
  24. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/graph_types.py +0 -0
  25. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/index_common.py +0 -0
  26. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/__init__.py +0 -0
  27. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/_fdlimit.py +0 -0
  28. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/_stdio.py +0 -0
  29. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/cli_format.py +0 -0
  30. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/cli_progress.py +0 -0
  31. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/config.py +0 -0
  32. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +0 -0
  33. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +0 -0
  34. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/installer.py +0 -0
  35. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/jrag_envelope.py +0 -0
  36. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/jrag_hints.py +0 -0
  37. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/lance_optimize.py +0 -0
  38. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/pipeline.py +0 -0
  39. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag/progress.py +0 -0
  40. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag.egg-info/dependency_links.txt +0 -0
  41. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag.egg-info/entry_points.txt +0 -0
  42. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag.egg-info/requires.txt +0 -0
  43. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_codebase_rag.egg-info/top_level.txt +0 -0
  44. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_index_flow_lancedb.py +0 -0
  45. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_index_v1_common.py +0 -0
  46. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/java_ontology.py +0 -0
  47. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/ladybug_queries.py +0 -0
  48. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/mcp_hints.py +0 -0
  49. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/mcp_v2.py +0 -0
  50. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/path_filtering.py +0 -0
  51. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/pr_analysis.py +0 -0
  52. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/resolve_service.py +0 -0
  53. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/search_lancedb.py +0 -0
  54. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/server.py +0 -0
  55. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/setup.cfg +0 -0
  56. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_agent_skills_static.py +0 -0
  57. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_assign_endpoint_client_extraction.py +0 -0
  58. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_ast_graph_build.py +0 -0
  59. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_ast_java_calls.py +0 -0
  60. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_ast_java_capabilities.py +0 -0
  61. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_ast_java_thread_safety.py +0 -0
  62. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_bank_chat_brownfield_integration.py +0 -0
  63. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_brownfield_clients.py +0 -0
  64. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_brownfield_events.py +0 -0
  65. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_brownfield_overrides.py +0 -0
  66. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_brownfield_routes.py +0 -0
  67. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_call_edge_matching.py +0 -0
  68. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_call_edges_e2e.py +0 -0
  69. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_call_graph_receiver_resolution.py +0 -0
  70. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_call_graph_smoke_roundtrip.py +0 -0
  71. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_call_invariant.py +0 -0
  72. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_cli_progress_stdout_invariant.py +0 -0
  73. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_cli_quiet_parity.py +0 -0
  74. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_client_hint_recovery.py +0 -0
  75. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_client_node_extraction.py +0 -0
  76. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_client_role_rename.py +0 -0
  77. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_config.py +0 -0
  78. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_cross_service_resolution_flag.py +0 -0
  79. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_edge_navigation_doc.py +0 -0
  80. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_fd_limit.py +0 -0
  81. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_feign_not_exposer.py +0 -0
  82. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_graph_enrich.py +0 -0
  83. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_graph_only_boot.py +0 -0
  84. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_incremental_graph.py +0 -0
  85. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_install_data_sync.py +0 -0
  86. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_installer.py +0 -0
  87. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_installer_integration.py +0 -0
  88. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_installer_surface.py +0 -0
  89. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_java_codebase_rag_cli.py +0 -0
  90. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_auto_scope.py +0 -0
  91. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_envelope.py +0 -0
  92. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_listing.py +0 -0
  93. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_locate.py +0 -0
  94. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_orientation.py +0 -0
  95. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_status.py +0 -0
  96. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_token_budget.py +0 -0
  97. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_jrag_traversal_compose.py +0 -0
  98. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_ladybug_queries.py +0 -0
  99. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_lance_optimize.py +0 -0
  100. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_lancedb_e2e.py +0 -0
  101. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_mcp_hints.py +0 -0
  102. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_mcp_server_project_root.py +0 -0
  103. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_mcp_tools.py +0 -0
  104. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_mcp_v2.py +0 -0
  105. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_mcp_v2_compose.py +0 -0
  106. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_meta_chain_core.py +0 -0
  107. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_microservice_scope.py +0 -0
  108. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_outgoing_call_extraction.py +0 -0
  109. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_packaging_metadata.py +0 -0
  110. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_path_filtering.py +0 -0
  111. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_pipeline.py +0 -0
  112. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_pr_analysis.py +0 -0
  113. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_progress.py +0 -0
  114. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_resolve_routes_messaging_layer_c.py +0 -0
  115. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_resolve_service.py +0 -0
  116. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_route_extraction.py +0 -0
  117. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_schema_consistency.py +0 -0
  118. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_search_lancedb.py +0 -0
  119. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_search_lancedb_capability.py +0 -0
  120. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_string_value_atoms.py +0 -0
  121. {java_codebase_rag-0.9.2 → java_codebase_rag-0.9.3}/tests/test_vectors_progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-codebase-rag
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: MCP server for semantic + structural search over Java codebases
5
5
  Author: HumanBean17
6
6
  License-Expression: MIT
@@ -168,6 +168,7 @@ jrag entities # JPA entities
168
168
 
169
169
  # Traversals (all resolve-first)
170
170
  jrag callers ChatService#assign(Request) # who calls me?
171
+ jrag callers ChatIngressController # controller: also lists its EXPOSES routes
171
172
  jrag callees ChatService#assign(Request) # what do I call?
172
173
  jrag hierarchy AbstractBase # type tree (parents + children)
173
174
  jrag implementations PaymentProcessor # classes implementing an interface
@@ -123,6 +123,7 @@ jrag entities # JPA entities
123
123
 
124
124
  # Traversals (all resolve-first)
125
125
  jrag callers ChatService#assign(Request) # who calls me?
126
+ jrag callers ChatIngressController # controller: also lists its EXPOSES routes
126
127
  jrag callees ChatService#assign(Request) # what do I call?
127
128
  jrag hierarchy AbstractBase # type tree (parents + children)
128
129
  jrag implementations PaymentProcessor # classes implementing an interface
@@ -0,0 +1,35 @@
1
+ """Version string for the CLI ``--version`` flag.
2
+
3
+ The single source of truth is the installed distribution metadata
4
+ (``java-codebase-rag`` in pyproject.toml), read via :mod:`importlib.metadata`
5
+ so a pyproject bump propagates with no second hardcoded copy.
6
+ :func:`version_string` appends the CPython version for the
7
+ ``<prog> <version> (python <x.y.z>)`` format chosen for the ``--version`` flag.
8
+
9
+ Stdlib-only on purpose: this is imported at module load by both CLIs, and
10
+ ``jrag`` keeps ``build_parser()`` free of torch / sentence_transformers / mcp_v2.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import platform
15
+ from importlib.metadata import PackageNotFoundError
16
+ from importlib.metadata import version as _dist_version
17
+
18
+ _PACKAGE = "java-codebase-rag"
19
+
20
+
21
+ def package_version() -> str:
22
+ """Installed distribution version, or ``"unknown"`` if metadata is absent.
23
+
24
+ Absent only when run from a raw checkout without ``pip install -e``; the
25
+ test suite (``conftest.py``) enforces editable install, so this is defensive.
26
+ """
27
+ try:
28
+ return _dist_version(_PACKAGE)
29
+ except PackageNotFoundError: # pragma: no cover - defensive
30
+ return "unknown"
31
+
32
+
33
+ def version_string(prog: str) -> str:
34
+ """Formatted ``--version`` output: ``<prog> <version> (python <x.y.z>)``."""
35
+ return f"{prog} {package_version()} (python {platform.python_version()})"
@@ -25,6 +25,7 @@ from java_codebase_rag.config import (
25
25
  write_config_source_pointer,
26
26
  )
27
27
  from java_codebase_rag._fdlimit import raise_fd_limit
28
+ from java_codebase_rag._version import version_string
28
29
  from java_codebase_rag.pipeline import (
29
30
  clip,
30
31
  is_cocoindex_preflight_blocker,
@@ -919,6 +920,11 @@ def build_parser() -> argparse.ArgumentParser:
919
920
  formatter_class=argparse.RawDescriptionHelpFormatter,
920
921
  exit_on_error=False,
921
922
  )
923
+ parser.add_argument(
924
+ "--version",
925
+ action="version",
926
+ version=version_string(parser.prog),
927
+ )
922
928
  subparsers = parser.add_subparsers(dest="subcommand")
923
929
 
924
930
  init = subparsers.add_parser(
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: explorer-rag-cli
3
+ description: "MUST BE USED PROACTIVELY. Universal read-only explorer agent. Combines graph navigation via the `jrag` CLI (call chains, routes, service boundaries, clients, producers, impact, FQN resolution) with broad file-system search (grep, glob, excerpt reading). Use for any exploration task: locating code, tracing dependencies, finding patterns, answering 'where is X' or 'who calls Y'. Read-only — never edits files. CLI-surface counterpart to explorer-rag-enhanced (which uses the MCP tools)."
4
+ ---
5
+
6
+ You are a universal codebase explorer — a read-only search and navigation specialist that combines **graph navigation via the `jrag` CLI** (the agent-facing surface of java-codebase-rag: one command per engineering intent) with **broad file-system search** (`Grep`/`Glob`/`Read`) as a first-class peer. Reach for `jrag` on structural questions and `Grep`/`Glob`/`Read` on raw text, config, or a stale index — whichever is lighter.
7
+
8
+ **Self-contained.** Do not invoke the `/explore-codebase-cli` skill and do not spawn another explorer subagent — the methodology below is baked in. Apply it directly.
9
+
10
+ ## Core Principles
11
+
12
+ 1. **Read-only.** Never edit, write, or modify any file. Only locate, read, and report.
13
+ 2. **Smallest sufficient tool — both ways.** Pick the lightest tool that answers the question. Don't run `jrag impact` when `jrag callers` suffices; don't fire `jrag inspect` when a single `Grep` lands on the line; don't `Grep` the whole repo when `jrag find` lists the nodes structurally. Graph beats grep for structural questions; grep beats graph for raw text, config, and a stale index. Neither is the default — match the tool to the question.
14
+ 3. **Excerpts over dumps.** Read excerpts and relevant sections, not entire files. Summarize findings.
15
+ 4. **Stop when answered.** Don't prefetch unrelated subgraphs or scan unrelated directories.
16
+
17
+ You drive **`jrag` shell commands**, not the MCP tools (`search`/`find`/`describe`/`neighbors`/`resolve`). One surface per project; the MCP counterpart is `explorer-rag-enhanced`.
18
+
19
+ ## Tool Inventory
20
+
21
+ - **Graph (`jrag` CLI):** one command per intent (`callers`, `callees`, `hierarchy`, `implementations`, `dependents`, `impact`, `flow`, `http-routes`, `http-clients`, `producers`, `topics`, `find`, `search`, `inspect`, `overview`, …). Use for whole-codebase structural queries — callers/callees, route handlers, HTTP/async seams, clients/producers, service boundaries, impact analysis, FQN resolution, implementations, DI chains. Pass it names; it resolves internally (no raw IDs). Requires an index (see **jrag surface**).
22
+ - **File-system:** `Grep` (contents), `Glob` (name/path patterns), `Read` (files — `offset`/`limit`; excerpts over dumps). Use for text searches, file discovery, and anything outside the graph index (config, build, test, CI, docs) — and whenever they're lighter than a `jrag` call.
23
+ - **Other:** `Bash` (read-only: `git log`, `git blame`, `ls`, `find`), `WebSearch`, `WebFetch`.
24
+
25
+ ---
26
+
27
+ ## Decision Framework
28
+
29
+ | User asks… | First step | Follow-up |
30
+ | ---------- | ---------- | --------- |
31
+ | "Is the index fresh?" | `jrag status` | — |
32
+ | Identifier (FQN / simple name) | `jrag inspect <query>` | `callers` / `callees` |
33
+ | Fuzzy / NL "where is X" | `jrag search "<text>"` | `inspect <hit>` |
34
+ | Raw text, a string literal, a config key | `Grep` | `Read` the hits |
35
+ | All controllers in S | `jrag find --role CONTROLLER --service S` | `callees` |
36
+ | Interfaces in S | `jrag find --java-kind interface --service S` | `implementations` |
37
+ | HTTP / messaging entry points | `jrag http-routes [--framework …] [--method …]` | `inspect <route>` |
38
+ | Outbound HTTP clients | `jrag http-clients [--calls-service …]` | `callees <client>` |
39
+ | Outbound async producers | `jrag producers [--topic-contains …]` | `callees <producer>` |
40
+ | Topics + consumers/producers | `jrag topics [--topic-contains …]` | — |
41
+ | Cross-service seams of S | `jrag connection <S> [--inbound/--outbound/--both]` | — |
42
+ | Who calls / what does M call? | `jrag callers <M>` / `jrag callees <M>` | `inspect` |
43
+ | What routes does a controller expose? | `jrag callers <controller>` (folds in its `EXPOSES` routes) | `inspect` |
44
+ | Who hits this route? | `jrag callers <route>` | — |
45
+ | Implementations / subtypes of T? | `jrag implementations <T>` / `jrag subclasses <T>` | — |
46
+ | Overriding / overridden methods? | `jrag overrides <method>` (UP) / `jrag overridden-by <method>` | — |
47
+ | Who injects / depends on T? | `jrag dependencies <T>` / `jrag dependents <T>` | — |
48
+ | Blast-radius of changing X? | `jrag impact <X>` (bounded fan-in) | `Grep` fallback |
49
+ | Trace request flow A→B | `jrag flow <route-A>` | `connection <microservice>` (service's cross-service seams) |
50
+ | File outline / imports | `jrag outline <file>` / `jrag imports <file>` | `inspect <row>` |
51
+ | Find files by name/path | `Glob` | `Read` |
52
+ | "Explain service S" | `jrag overview <service>` | `http-routes`/`http-clients`/`producers` |
53
+ | "Explain route / topic" | `jrag overview <subject>` | `flow` |
54
+ | Who changed X and when? | Bash: `git log`/`git blame` | — |
55
+ | "How is this configured?" | `Glob` + `Grep`; `jrag search "<key>" --table yaml` | `Read` sections |
56
+
57
+ **Escalation:** ① Most targeted tool first (identifier → `jrag inspect`; structural → matching `jrag` traversal; raw text / config / history → `Grep`/`Glob`/`Bash`). ② Fall back gracefully (`jrag` empty / `not_found` / exit 2 → `Grep`/`Glob`). ③ Cross-validate (`jrag` vs file disagree → **trust the file** — the index may be stale; report it).
58
+
59
+ ---
60
+
61
+ ## Workflow Patterns
62
+
63
+ - **"Explain feature X":** `jrag search "X"` → pick 1–3 hits → `jrag inspect <hit>` → targeted traversal (`callees`/`implementations`/`dependents`) → stop when answered.
64
+ - **"Where is X used?":** `jrag inspect <X>` (resolves; disambiguate if `many`) → `jrag callers <X>` + `jrag dependents <X>` → `Grep` the symbol name as fallback → report sites with file:line.
65
+ - **"Find all Y":** structural → `jrag find --role <ROLE> [--service <S>]`; textual → `Grep`; broad → `Glob`+`Grep`. Summarize, don't dump.
66
+ - **"Trace flow A→B":** `jrag flow <route-A>` → `jrag connection <microservice>` (cross-service seams) → `Grep` the gaps → report with file:line.
67
+ - **"Orient in service S":** `jrag overview <S>` → `jrag conventions --service <S>` → `jrag map --service <S>` → `jrag http-routes --service <S>`.
68
+
69
+ ## Recovery Playbook
70
+
71
+ **After two failed attempts on the same intent, stop and report what was tried and what failed.**
72
+
73
+ | Symptom | Fix |
74
+ | ------- | --- |
75
+ | `jrag status` exits 2 | Run `java-codebase-rag init --source-root <root>`; retry |
76
+ | `status: not_found` | `jrag search "<query>"`; or `find --fqn-contains`; fallback `Grep` |
77
+ | `many` candidates | Add `--kind`/`--role`/`--fqn-contains`/`--service`; re-run |
78
+ | `find` too broad | Add `--service`, `--fqn-contains`, `--path-contains`, `--topic-contains` |
79
+ | Empty `search` | Try `--table all`; `find --fqn-contains`; `Grep` |
80
+ | `truncated: true` | Narrow, or page with `--offset` (`find`/`search` only) |
81
+ | Empty across commands | Index missing/stale → `Grep`/`Glob`/`Read`; ask operator to rebuild |
82
+ | CLI vs file disagree | Trust the file; report stale index |
83
+ | `--offset` rejected | Only `find`/`search` accept it; others narrow via filters |
84
+
85
+ ---
86
+
87
+ ## jrag surface — `--help` is the spec
88
+
89
+ `jrag` is self-documenting and the canonical, always-fresh source for commands, flags, and valid enum values — so it isn't duplicated here. Don't memorize the surface:
90
+
91
+ - `jrag --help` — every command, grouped by intent, with one-line descriptions.
92
+ - `jrag <command> --help` — that command's flags and accepted values. Enum filters (`--role` / `--exclude-role` / `--java-kind` / `--framework` / `--capability`) print their set in `--help` and reject mistyped values with the valid choices.
93
+
94
+ The Decision Framework above tells you *which* command; reach for `--help` only when you need exact flags or enum values.
95
+
96
+ **Prerequisite.** `jrag` needs an index — unindexed, every command exits 2 (`jrag status` checks; the file-system tools work without one).
97
+
98
+ **Resolve-first contract.** Every `<query>` command resolves the identifier first, then maps `one` / `many` / `none` onto one envelope: `one` → run; `many` → return candidates and stop, **no silent guess across distinct types** (a class sharing its simple name with its own constructor still resolves to the type — narrow with `--kind` / `--role` / `--fqn-contains` / `--service`); `none` → `status: not_found` (exit 0), fall back to `search` or `Grep`. Pass names (FQN / simple name / route path / topic) or prior `sym:`/`route:`/`client:`/`producer:` ids — never raw node ids. `--kind` is a true resolve input; `--role` / `--java-kind` / `--fqn-contains` post-filter client-side.
99
+
100
+ **Output.** Default is compact text; `--format json` emits `{status, nodes, edges, candidates, truncated, agent_next_actions, file_location}` (empty fields dropped; `file_location` is a `filename:line` string; `agent_next_actions` suggests ≤5 next commands). `truncated` pages via `--limit` / `--offset` (`find` / `search` only).
101
+
102
+ **Edge semantics `--help` doesn't spell out.** `callers` / `callees` = `CALLS` in/out (on a controller/entry-point type, `callers` also lists the routes its methods `EXPOSE`). `impact` = bounded fan-in over `INJECTS` / `IMPLEMENTS` / `EXTENDS` (default depth 2; raise with `--depth`). `flow <route>` follows `EXPOSES` → `HTTP_CALLS` / `ASYNC_CALLS` → `CALLS`. `connection <microservice>` = inbound/outbound cross-service seams (its positional is a literal service name, not a query). Per-command edge mappings and the rest of the flag surface live in each command's `--help`.
103
+
104
+ **Node id prefixes (from prior results):** `sym:` (Symbol), `route:`/`r:` (Route), `client:`/`c:` (Client), `producer:`/`p:` (Producer). **Symbol FQN:** `<package>.<Type>[.<NestedType>]#<methodName>(<SimpleType1>,…)` — generics erased, no spaces after commas, no-arg `()`, constructor `#<init>(...)`.
105
+
106
+ **Ontology.** Role / symbol-kind / framework / capability values are enumerated in `--help`; client/producer kinds and source layers validate at runtime and surface the accepted set on a typo.
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: explore-codebase-cli
3
+ description: "MUST BE USED PROACTIVELY. Universal codebase exploration (CLI surface). Use for any exploration task: locating code, tracing dependencies, finding patterns, 'where is X', 'who calls Y', 'find all controllers', 'trace the flow from A to B'. Do NOT use when the answer is already in open context or for a single known file — read that file directly."
4
+ ---
5
+
6
+ ## Core Principles
7
+
8
+ 1. **Smallest sufficient tool — both ways.** Pick the lightest tool that answers the question. Don't run `jrag impact` when `jrag callers` suffices; don't fire `jrag inspect` when a single `Grep` lands on the line; don't `Grep` the whole repo when `jrag find --role CONTROLLER --service S` lists them structurally. Graph beats grep for structural questions; grep beats graph for raw text, config, and a stale index. Neither is the default — match the tool to the question.
9
+ 2. **Excerpts over dumps.** Read excerpts and relevant sections, not entire files. Summarize findings.
10
+ 3. **Stop when answered.** Don't prefetch unrelated subgraphs or scan unrelated directories.
11
+
12
+ ## Tool Inventory
13
+
14
+ - **Graph (`jrag` CLI):** one command per intent — `callers`, `callees`, `hierarchy`, `implementations`, `dependents`, `impact`, `flow`, `http-routes`, `http-clients`, `producers`, `topics`, `find`, `search`, `inspect`, `overview`, … Drives the same index as the MCP server. Fast path for structural questions: call chains, route handlers, HTTP/async seams, clients/producers, service boundaries, impact, FQN resolution, implementations, DI chains. Pass it names (FQN / simple name / route path / topic) — it resolves internally; raw node IDs are never required. Requires an index; if unindexed every command exits 2 (see **jrag surface**).
15
+ - **File-system:** `Grep` (content/regex), `Glob` (name/path patterns), `Read` (`offset`/`limit`). First-class for text searches, file discovery, and anything outside the graph index (config, build, test, CI, docs) — and the right answer whenever they're lighter than a `jrag` call.
16
+ - **Other:** `Bash` (read-only: `git log`, `git blame`, `ls`, `find`), `WebSearch`/`WebFetch`.
17
+
18
+ *CLI surface only — don't also drive the MCP tools (`search`/`find`/`describe`/`neighbors`/`resolve`) in the same session; the two vocabularies conflict.*
19
+
20
+ ---
21
+
22
+ ## Decision Framework
23
+
24
+ | User asks… | First step | Follow-up |
25
+ | ---------- | ---------- | --------- |
26
+ | "Is the index fresh?" | `jrag status` | — |
27
+ | Identifier (FQN / simple name) | `jrag inspect <query>` | `callers` / `callees` |
28
+ | Fuzzy / NL "where is X" | `jrag search "<text>"` | `inspect <hit>` |
29
+ | Raw text, a string literal, a config key | `Grep` | `Read` the hits |
30
+ | All controllers in S | `jrag find --role CONTROLLER --service S` | `callees` |
31
+ | Interfaces in S | `jrag find --java-kind interface --service S` | `implementations` |
32
+ | HTTP / messaging entry points | `jrag http-routes [--framework …] [--method …]` | `inspect <route>` |
33
+ | Outbound HTTP clients | `jrag http-clients [--calls-service …]` | `callees <client>` |
34
+ | Outbound async producers | `jrag producers [--topic-contains …]` | `callees <producer>` |
35
+ | Topics + consumers/producers | `jrag topics [--topic-contains …]` | — |
36
+ | Cross-service seams of S | `jrag connection <S> [--inbound/--outbound/--both]` | — |
37
+ | Who calls / what does M call? | `jrag callers <M>` / `jrag callees <M>` | `inspect` |
38
+ | What routes does a controller expose? | `jrag callers <controller>` (folds in its `EXPOSES` routes) | `inspect` |
39
+ | Who hits this route? | `jrag callers <route>` | — |
40
+ | Implementations / subtypes of T? | `jrag implementations <T>` / `jrag subclasses <T>` | — |
41
+ | Overriding / overridden methods? | `jrag overrides <method>` (UP) / `jrag overridden-by <method>` | — |
42
+ | Who injects / depends on T? | `jrag dependencies <T>` / `jrag dependents <T>` | — |
43
+ | Blast-radius of changing X? | `jrag impact <X>` (bounded fan-in) | `Grep` fallback |
44
+ | Trace request flow A→B | `jrag flow <route-A>` | `connection <microservice>` (service's cross-service seams) |
45
+ | File outline / imports | `jrag outline <file>` / `jrag imports <file>` | `inspect <row>` |
46
+ | Find files by name/path | `Glob` | `Read` |
47
+ | "Explain service S" | `jrag overview <service>` | `http-routes`/`http-clients`/`producers` |
48
+ | "Explain route / topic" | `jrag overview <subject>` | `flow` |
49
+ | Who changed X and when? | Bash: `git log`/`git blame` | — |
50
+ | "How is this configured?" | `Glob` + `Grep`; `jrag search "<key>" --table yaml` | `Read` sections |
51
+
52
+ **Escalation:** ① Most targeted tool first (identifier → `jrag inspect`; structural → matching `jrag` traversal; raw text / config / history → `Grep`/`Glob`/`Bash`). ② Fall back gracefully (`jrag` empty / `not_found` / exit 2 → `Grep`/`Glob`). ③ Cross-validate (`jrag` vs file disagree → **trust the file** — the index may be stale; report it).
53
+
54
+ **Rules of thumb:** structure beats vector for exact questions (`jrag find`/`inspect` + traversal); vector beats structure for fuzzy discovery (`jrag search`); raw text / config / history beats both (`Grep`/`Glob`/`Bash`); file-system beats a stale index.
55
+
56
+ ---
57
+
58
+ ## Workflow Patterns
59
+
60
+ - **"Explain feature X":** `jrag search "X"` → pick 1–3 hits → `jrag inspect <hit>` → targeted traversal (`callees`/`implementations`) → stop when answered.
61
+ - **"Where is X used?":** `jrag inspect <X>` → `jrag callers <X>` + `jrag dependents <X>` → `Grep` the symbol name as fallback → report sites with file:line.
62
+ - **"Find all Y":** structural → `jrag find --role <ROLE> [--service <S>]`; textual → `Grep`; broad → `Glob`+`Grep`. Summarize, don't dump.
63
+ - **"Trace flow A→B":** `jrag flow <route-A>` → `jrag connection <microservice>` (cross-service seams) → `Grep` the gaps → report with file:line.
64
+ - **"How is this configured?":** `Glob` `**/application*.yml` → `Grep` the key → `Read` sections → `jrag search "<key>" --table yaml`.
65
+ - **"Orient in a new service":** `jrag overview <S>` → `jrag conventions --service <S>` → `jrag map --service <S>` → `jrag http-routes --service <S>`.
66
+
67
+ ## Recovery Playbook
68
+
69
+ **After two failed attempts on the same intent, stop and report command, args, and result snippet.**
70
+
71
+ | Symptom | Fix |
72
+ | ------- | --- |
73
+ | `status: error` "No index at …" | Run `java-codebase-rag init --source-root <root>`; retry |
74
+ | `status: not_found` | `jrag search "<query>"`; or `find --fqn-contains …`; fallback `Grep` |
75
+ | `many` candidates | Add `--kind`/`--role`/`--fqn-contains`/`--service`; re-run |
76
+ | `find` too broad | Add `--service`, `--fqn-contains`, `--path-contains`, `--topic-contains` |
77
+ | Empty `search` | Try `--table all`; `find --fqn-contains`; `Grep` |
78
+ | `truncated: true` | Narrow, or page with `--offset` (`find`/`search` only) |
79
+ | Empty across commands | Index missing/stale → `Grep`/`Glob`/`Read`; ask operator to rebuild (`java-codebase-rag reprocess`) |
80
+ | CLI vs file disagree | **Trust the file**; report stale index |
81
+ | `--offset` rejected | Only `find`/`search` accept it; others narrow via filters |
82
+ | Wrong node picked | Resolve ambiguous — pass `--kind` |
83
+
84
+ ---
85
+
86
+ ## jrag surface — `--help` is the spec
87
+
88
+ `jrag` is self-documenting and the canonical, always-fresh source for commands, flags, and valid enum values — so this skill doesn't duplicate them. Don't memorize the surface:
89
+
90
+ - `jrag --help` — every command, grouped by intent, with one-line descriptions.
91
+ - `jrag <command> --help` — that command's flags and accepted values. Enum filters (`--role` / `--exclude-role` / `--java-kind` / `--framework` / `--capability`) print their set in `--help` and reject mistyped values with the valid choices.
92
+
93
+ The Decision Framework above tells you *which* command; reach for `--help` only when you need exact flags or enum values.
94
+
95
+ **Prerequisite.** `jrag` needs an index — unindexed, every command exits 2 (`jrag status` checks; the file-system tools work without one).
96
+
97
+ **Resolve-first contract.** Every `<query>` command resolves the identifier first, then maps `one` / `many` / `none` onto one envelope: `one` → run; `many` → return candidates and stop, **no silent guess across distinct types** (a class sharing its simple name with its own constructor still resolves to the type — narrow with `--kind` / `--role` / `--fqn-contains` / `--service`); `none` → `status: not_found` (exit 0), fall back to `search` or `Grep`. Pass names (FQN / simple name / route path / topic) or prior `sym:`/`route:`/`client:`/`producer:` ids — never raw node ids. `--kind` is a true resolve input; `--role` / `--java-kind` / `--fqn-contains` post-filter client-side.
98
+
99
+ **Output.** Default is compact text; `--format json` emits `{status, nodes, edges, candidates, truncated, agent_next_actions, file_location}` (empty fields dropped; `file_location` is a `filename:line` string; `agent_next_actions` suggests ≤5 next commands). `truncated` pages via `--limit` / `--offset` (`find` / `search` only).
100
+
101
+ **Edge semantics `--help` doesn't spell out.** `callers` / `callees` = `CALLS` in/out (on a controller/entry-point type, `callers` also lists the routes its methods `EXPOSE`). `impact` = bounded fan-in over `INJECTS` / `IMPLEMENTS` / `EXTENDS` (default depth 2; raise with `--depth`). `flow <route>` follows `EXPOSES` → `HTTP_CALLS` / `ASYNC_CALLS` → `CALLS`. `connection <microservice>` = inbound/outbound cross-service seams (its positional is a literal service name, not a query). Per-command edge mappings and the rest of the flag surface live in each command's `--help`.
102
+
103
+ **Node id prefixes (from prior results):** `sym:` (Symbol), `route:`/`r:` (Route), `client:`/`c:` (Client), `producer:`/`p:` (Producer). **Symbol FQN:** `<package>.<Type>[.<NestedType>]#<methodName>(<SimpleType1>,…)` — generics erased, no spaces after commas, no-arg `()`, constructor `#<init>(...)`.
104
+
105
+ **Ontology.** Role / symbol-kind / framework / capability values are enumerated in `--help`; client/producer kinds and source layers validate at runtime and surface the accepted set on a typo.
@@ -30,6 +30,7 @@ from pathlib import Path
30
30
 
31
31
  from java_codebase_rag._fdlimit import raise_fd_limit
32
32
  from java_codebase_rag._stdio import force_utf8_stdio
33
+ from java_codebase_rag._version import version_string
33
34
 
34
35
  __all__ = ["build_parser", "main", "_console_script_main"]
35
36
 
@@ -302,6 +303,44 @@ def _preparse_render_flags(raw: list[str]) -> tuple[str | None, str | None, list
302
303
  return None, None, list(raw)
303
304
 
304
305
 
306
+ # Closed enum taxonomies for the --role / --exclude-role / --java-kind /
307
+ # --framework / --capability filters. Sourced from the canonical literals
308
+ # (mcp_v2.Role, mcp_v2.DeclarationSymbolKind, mcp_v2.Framework) and
309
+ # java_ontology.VALID_CAPABILITIES, and cross-checked by test_jrag_enum_choices.
310
+ # Hardcoded here (not imported) so `jrag --help` stays fast — build_parser
311
+ # imports no backend modules, and importing mcp_v2 costs ~0.7s.
312
+ _ROLE_CHOICES = (
313
+ "CONTROLLER", "SERVICE", "REPOSITORY", "COMPONENT", "CONFIG",
314
+ "ENTITY", "CLIENT", "MAPPER", "DTO", "OTHER",
315
+ )
316
+ _JAVA_KIND_CHOICES = (
317
+ "class", "interface", "enum", "record", "annotation", "method", "constructor",
318
+ )
319
+ _FRAMEWORK_CHOICES = (
320
+ "spring_mvc", "webflux", "kafka", "rabbitmq", "jms", "stream", "feign",
321
+ )
322
+ _CAPABILITY_CHOICES = (
323
+ "MESSAGE_LISTENER", "MESSAGE_PRODUCER", "HTTP_CLIENT",
324
+ "SCHEDULED_TASK", "EXCEPTION_HANDLER",
325
+ )
326
+
327
+
328
+ def _upper_snake(value: str) -> str:
329
+ """Normalize a role/capability value to its stored UPPER_SNAKE form so
330
+ argparse ``choices=`` accepts flexible casing (``controller`` /
331
+ ``scheduled-task`` -> ``CONTROLLER`` / ``SCHEDULED_TASK``). Mirrors the
332
+ role/capability branch of jrag_envelope.normalize_enum."""
333
+ return value.strip().upper().replace("-", "_").replace(" ", "_")
334
+
335
+
336
+ def _lower_snake(value: str) -> str:
337
+ """Normalize a java-kind/framework value to its stored lowercase form so
338
+ argparse ``choices=`` accepts flexible casing (``Spring-MVC`` ->
339
+ ``spring_mvc``). Mirrors the framework/java_kind branch of
340
+ jrag_envelope.normalize_enum."""
341
+ return value.strip().lower().replace("-", "_").replace(" ", "_")
342
+
343
+
305
344
  def build_parser() -> argparse.ArgumentParser:
306
345
  """Argparse builder. Imports no backend modules.
307
346
 
@@ -334,11 +373,16 @@ def build_parser() -> argparse.ArgumentParser:
334
373
  formatter_class=argparse.RawDescriptionHelpFormatter,
335
374
  exit_on_error=False,
336
375
  )
376
+ parser.add_argument(
377
+ "--version",
378
+ action="version",
379
+ version=version_string(parser.prog),
380
+ )
337
381
  subparsers = parser.add_subparsers(dest="command", parser_class=_EnvelopeArgumentParser)
338
382
 
339
383
  # Common flags applied per command via parents=[_common_parser()]. NOT
340
- # global so commands can override defaults (e.g. fan-out commands use
341
- # limit=10). The helper builds a FRESH parser each call so every subparser
384
+ # global so commands can override defaults (e.g. inspect/orientation
385
+ # default --detail to full). The helper builds a FRESH parser each call so every subparser
342
386
  # owns its own --detail Action object — argparse `parents` shares Action
343
387
  # objects by reference, and `set_defaults(detail=...)` mutates the shared
344
388
  # action's default (CPython walks `self._actions`), so a single shared
@@ -360,7 +404,7 @@ def build_parser() -> argparse.ArgumentParser:
360
404
  ),
361
405
  )
362
406
  common.add_argument(
363
- "--limit", type=int, default=20, help="Cap on results (default 20; 10 for fan-out)."
407
+ "--limit", type=int, default=20, help="Cap on results (default 20)."
364
408
  )
365
409
  common.add_argument(
366
410
  "--index-dir",
@@ -455,12 +499,12 @@ def build_parser() -> argparse.ArgumentParser:
455
499
  default=None,
456
500
  help="Node kind (omit for auto-inference from domain flags).",
457
501
  )
458
- find.add_argument("--role", type=str, default=None, help="Filter by role.")
459
- find.add_argument("--exclude-role", type=str, default=None, help="Exclude by role.")
460
- find.add_argument("--java-kind", type=str, default=None, help="Filter by Java symbol kind.")
502
+ find.add_argument("--role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, help="Filter by role.")
503
+ find.add_argument("--exclude-role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, help="Exclude by role.")
504
+ find.add_argument("--java-kind", type=_lower_snake, choices=_JAVA_KIND_CHOICES, default=None, help="Filter by Java symbol kind.")
461
505
  find.add_argument("--annotation", type=str, default=None, help="Filter by annotation.")
462
- find.add_argument("--capability", type=str, default=None, help="Filter by capability.")
463
- find.add_argument("--framework", type=str, default=None, help="Filter by framework.")
506
+ find.add_argument("--capability", type=_upper_snake, choices=_CAPABILITY_CHOICES, default=None, help="Filter by capability.")
507
+ find.add_argument("--framework", type=_lower_snake, choices=_FRAMEWORK_CHOICES, default=None, help="Filter by framework.")
464
508
  find.add_argument("--source-layer", type=str, default=None, help="Filter by source layer.")
465
509
  find.add_argument("--fqn-contains", type=str, default=None, help="Filter by FQN substring.")
466
510
  find.add_argument("--http-method", type=str, default=None, help="Filter by HTTP method (route).")
@@ -496,8 +540,8 @@ def build_parser() -> argparse.ArgumentParser:
496
540
  default=None,
497
541
  help="Hint for resolve (omitted for broad search).",
498
542
  )
499
- inspect.add_argument("--java-kind", type=str, default=None, help="Post-filter by Java symbol kind.")
500
- inspect.add_argument("--role", type=str, default=None, help="Post-filter by role.")
543
+ inspect.add_argument("--java-kind", type=_lower_snake, choices=_JAVA_KIND_CHOICES, default=None, help="Post-filter by Java symbol kind.")
544
+ inspect.add_argument("--role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, help="Post-filter by role.")
501
545
  inspect.add_argument("--fqn-contains", type=str, default=None, help="Post-filter by FQN substring.")
502
546
  inspect.set_defaults(handler=_cmd_inspect, detail="full")
503
547
 
@@ -512,7 +556,7 @@ def build_parser() -> argparse.ArgumentParser:
512
556
  "kafka topics live under `topics`."
513
557
  ),
514
558
  )
515
- http_routes.add_argument("--framework", type=str, default=None, help="Filter by framework.")
559
+ http_routes.add_argument("--framework", type=_lower_snake, choices=_FRAMEWORK_CHOICES, default=None, help="Filter by framework.")
516
560
  http_routes.add_argument("--path-contains", type=str, default=None, help="Filter by path substring.")
517
561
  http_routes.add_argument("--method", type=str, default=None, help="Filter by HTTP method.")
518
562
  http_routes.set_defaults(handler=_cmd_routes, detail="full", auto_scope=True)
@@ -611,8 +655,8 @@ def build_parser() -> argparse.ArgumentParser:
611
655
  default=None,
612
656
  help="Hint for resolve (omit for broad search).",
613
657
  )
614
- resolve_parent.add_argument("--java-kind", type=str, default=None, help="Post-filter by Java symbol kind.")
615
- resolve_parent.add_argument("--role", type=str, default=None, help="Post-filter by role.")
658
+ resolve_parent.add_argument("--java-kind", type=_lower_snake, choices=_JAVA_KIND_CHOICES, default=None, help="Post-filter by Java symbol kind.")
659
+ resolve_parent.add_argument("--role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, help="Post-filter by role.")
616
660
  resolve_parent.add_argument("--fqn-contains", type=str, default=None, help="Post-filter by FQN substring.")
617
661
 
618
662
  callers = subparsers.add_parser(
@@ -694,7 +738,7 @@ def build_parser() -> argparse.ArgumentParser:
694
738
  ),
695
739
  )
696
740
  implementations.add_argument("query", help="Interface FQN or name.")
697
- implementations.add_argument("--capability", type=str, default=None, help="Filter implementors by capability.")
741
+ implementations.add_argument("--capability", type=_upper_snake, choices=_CAPABILITY_CHOICES, default=None, help="Filter implementors by capability.")
698
742
  implementations.set_defaults(handler=_cmd_implementations, auto_scope=True)
699
743
 
700
744
  subclasses = subparsers.add_parser(
@@ -777,16 +821,21 @@ def build_parser() -> argparse.ArgumentParser:
777
821
  decompose.add_argument("--depth", type=int, default=2, help="Neighbour hop count per stage (clamped 1..3, default 2).")
778
822
  decompose.add_argument(
779
823
  "--follow-calls",
780
- action="store_true",
824
+ action=argparse.BooleanOptionalAction,
825
+ default=True,
781
826
  dest="follow_calls",
782
- help="Follow DECLARES+CALLS type-to-type hops to top up each stage.",
827
+ help=(
828
+ "Top up each stage with DECLARES+CALLS type-to-type hops when the "
829
+ "structural INJECTS/EXTENDS/IMPLEMENTS pass under-fills it (default: "
830
+ "on). --no-follow-calls restricts the waterfall to structural edges."
831
+ ),
783
832
  )
784
833
  decompose.add_argument(
785
- "--max-stage",
834
+ "--per-stage-limit",
786
835
  type=int,
787
836
  default=20,
788
- dest="max_stage",
789
- help="Cap on symbols per stage (stage_limit, default 20).",
837
+ dest="per_stage_limit",
838
+ help="Cap on symbols per stage (stage_limit, default 20). Not a stage-count knob.",
790
839
  )
791
840
  decompose.add_argument(
792
841
  "--min-confidence",
@@ -1094,12 +1143,12 @@ def build_parser() -> argparse.ArgumentParser:
1094
1143
  ),
1095
1144
  )
1096
1145
  # NodeFilter flags (same set as `find` filter mode, minus the query-only ones).
1097
- search.add_argument("--role", type=str, default=None, help="Filter by role.")
1098
- search.add_argument("--exclude-role", type=str, default=None, dest="exclude_role", help="Exclude by role.")
1099
- search.add_argument("--java-kind", type=str, default=None, dest="java_kind", help="Filter by Java symbol kind.")
1146
+ search.add_argument("--role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, help="Filter by role.")
1147
+ search.add_argument("--exclude-role", type=_upper_snake, choices=_ROLE_CHOICES, default=None, dest="exclude_role", help="Exclude by role.")
1148
+ search.add_argument("--java-kind", type=_lower_snake, choices=_JAVA_KIND_CHOICES, default=None, dest="java_kind", help="Filter by Java symbol kind.")
1100
1149
  search.add_argument("--annotation", type=str, default=None, help="Filter by annotation.")
1101
- search.add_argument("--capability", type=str, default=None, help="Filter by capability.")
1102
- search.add_argument("--framework", type=str, default=None, help="Filter by framework.")
1150
+ search.add_argument("--capability", type=_upper_snake, choices=_CAPABILITY_CHOICES, default=None, help="Filter by capability.")
1151
+ search.add_argument("--framework", type=_lower_snake, choices=_FRAMEWORK_CHOICES, default=None, help="Filter by framework.")
1103
1152
  search.add_argument("--fqn-contains", type=str, default=None, dest="fqn_contains", help="Filter by FQN substring.")
1104
1153
  search.add_argument(
1105
1154
  "--offset",
@@ -2404,6 +2453,44 @@ def _cmd_callers(args: argparse.Namespace) -> int:
2404
2453
  edges.append(
2405
2454
  {"other_id": ce.src.id, "edge_type": "CALLS", "confidence": ce.confidence}
2406
2455
  )
2456
+ # Entry-point awareness. A controller / messaging-listener type is invoked
2457
+ # via the routes its methods EXPOSE (Controller -[:DECLARES]-> method
2458
+ # -[:EXPOSES]-> Route), NOT via in-repo CALLS edges — so find_callers is
2459
+ # typically empty for HTTP handlers. Without this fold, `callers
2460
+ # <Controller>` returns a bug-looking empty list when the controller is the
2461
+ # very thing the agent is investigating. The routes ARE its inbound callers,
2462
+ # so surface them as additional EXPOSES rows alongside any CALLS-in edges.
2463
+ # Gated on having DECLARES.EXPOSES out-edges (covers any entry-point holder,
2464
+ # not just role=CONTROLLER). Routes are additive and usually few, so they do
2465
+ # not count against the CALLS --limit (cf. the callees client/producer path,
2466
+ # which likewise emits its own targets without sharing the CALLS budget).
2467
+ expose_rows = graph._rows( # noqa: SLF001 - one-shot aggregation, cf. _cmd_callees client path
2468
+ "MATCH (t:Symbol {id: $tid})-[:DECLARES]->(m:Symbol)-[e:EXPOSES]->(r:Route) "
2469
+ "RETURN r.id AS rid, r.method AS rmethod, r.path AS rpath, "
2470
+ "r.path_template AS rpt, r.microservice AS rms, "
2471
+ "m.fqn AS via_fqn, e.confidence AS conf",
2472
+ {"tid": root_id},
2473
+ )
2474
+ for row in expose_rows:
2475
+ rid = str(row.get("rid") or "")
2476
+ if not rid or rid in nodes:
2477
+ continue
2478
+ rmethod = str(row.get("rmethod") or "")
2479
+ rpath = str(row.get("rpt") or row.get("rpath") or "")
2480
+ nodes[rid] = {
2481
+ "id": rid,
2482
+ "kind": "route",
2483
+ "fqn": f"{rmethod} {rpath}".strip(),
2484
+ "method": rmethod,
2485
+ "path": rpath,
2486
+ "microservice": str(row.get("rms") or ""),
2487
+ }
2488
+ edge_row: dict = {"other_id": rid, "edge_type": "EXPOSES"}
2489
+ via_fqn = str(row.get("via_fqn") or "")
2490
+ if via_fqn:
2491
+ # Declaring method that exposes the route; rendered at --detail full.
2492
+ edge_row["from_fqn"] = via_fqn
2493
+ edges.append(edge_row)
2407
2494
  nodes[root_id] = root_dict
2408
2495
  return _emit_traversal(
2409
2496
  args, root_id=root_id, nodes=nodes, edges=edges,
@@ -2956,8 +3043,8 @@ def _cmd_decompose(args: argparse.Namespace) -> int:
2956
3043
  stages = graph.trace_flow(
2957
3044
  seed_fqns=[seed_fqn],
2958
3045
  depth=depth,
2959
- follow_calls=getattr(args, "follow_calls", False),
2960
- stage_limit=getattr(args, "max_stage", 20),
3046
+ follow_calls=getattr(args, "follow_calls", True),
3047
+ stage_limit=getattr(args, "per_stage_limit", 20),
2961
3048
  min_call_confidence=getattr(args, "min_confidence", 0.0),
2962
3049
  exclude_external=not getattr(args, "include_external", False),
2963
3050
  microservice=args.service,
@@ -2983,12 +3070,12 @@ def _cmd_decompose(args: argparse.Namespace) -> int:
2983
3070
  edge_row["from_fqn"] = via.from_fqn
2984
3071
  edges.append(edge_row)
2985
3072
  # --limit is inherited from common but does not cap decompose (trace_flow
2986
- # is stage-limited via --max-stage, not a total edge count). Warn when the
3073
+ # is stage-limited via --per-stage-limit, not a total edge count). Warn when the
2987
3074
  # user explicitly set --limit away from the default so they get a signal
2988
3075
  # rather than a silent multi-stage dump (Fix 4).
2989
3076
  if args.limit is not None and args.limit != 20:
2990
3077
  warnings.append(
2991
- "--limit does not apply to decompose; use --max-stage to cap per-stage breadth"
3078
+ "--limit does not apply to decompose; use --per-stage-limit to cap per-stage breadth"
2992
3079
  )
2993
3080
  return _emit_traversal(
2994
3081
  args, root_id=root_id, nodes=nodes, edges=edges,
@@ -472,11 +472,19 @@ def _render_traversal(envelope: Envelope, *, noun: str, detail: str = "normal")
472
472
  by_stage[s].append(e)
473
473
  for s in stage_order:
474
474
  stage_edges = by_stage[s]
475
- roles = {str(e.get("role") or "").upper() for e in stage_edges if e.get("role")}
475
+ # Preserve first-seen order so a mixed stage reads naturally
476
+ # (e.g. `stage 1 (service, component):`) instead of dropping the
477
+ # role label entirely — the role allow-list is the whole point of a
478
+ # role-waterfall, so hiding it on the busiest stages is a loss.
479
+ seen: list[str] = []
480
+ for e in stage_edges:
481
+ r = str(e.get("role") or "").strip().lower()
482
+ if r and r not in seen:
483
+ seen.append(r)
476
484
  if s == 0:
477
485
  header = "stage 0 (seed):"
478
- elif len(roles) == 1:
479
- header = f"stage {s} ({next(iter(roles)).lower()}):"
486
+ elif seen:
487
+ header = f"stage {s} ({', '.join(seen)}):"
480
488
  else:
481
489
  header = f"stage {s}:"
482
490
  lines.append(header)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-codebase-rag
3
- Version: 0.9.2
3
+ Version: 0.9.3
4
4
  Summary: MCP server for semantic + structural search over Java codebases
5
5
  Author: HumanBean17
6
6
  License-Expression: MIT
@@ -168,6 +168,7 @@ jrag entities # JPA entities
168
168
 
169
169
  # Traversals (all resolve-first)
170
170
  jrag callers ChatService#assign(Request) # who calls me?
171
+ jrag callers ChatIngressController # controller: also lists its EXPOSES routes
171
172
  jrag callees ChatService#assign(Request) # what do I call?
172
173
  jrag hierarchy AbstractBase # type tree (parents + children)
173
174
  jrag implementations PaymentProcessor # classes implementing an interface
@@ -22,6 +22,7 @@ server.py
22
22
  java_codebase_rag/__init__.py
23
23
  java_codebase_rag/_fdlimit.py
24
24
  java_codebase_rag/_stdio.py
25
+ java_codebase_rag/_version.py
25
26
  java_codebase_rag/cli.py
26
27
  java_codebase_rag/cli_format.py
27
28
  java_codebase_rag/cli_progress.py
@@ -79,6 +80,7 @@ tests/test_installer_integration.py
79
80
  tests/test_installer_surface.py
80
81
  tests/test_java_codebase_rag_cli.py
81
82
  tests/test_jrag_auto_scope.py
83
+ tests/test_jrag_enum_choices.py
82
84
  tests/test_jrag_envelope.py
83
85
  tests/test_jrag_listing.py
84
86
  tests/test_jrag_locate.py
@@ -111,4 +113,5 @@ tests/test_schema_consistency.py
111
113
  tests/test_search_lancedb.py
112
114
  tests/test_search_lancedb_capability.py
113
115
  tests/test_string_value_atoms.py
114
- tests/test_vectors_progress.py
116
+ tests/test_vectors_progress.py
117
+ tests/test_version_flag.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "java-codebase-rag"
7
- version = "0.9.2"
7
+ version = "0.9.3"
8
8
  description = "MCP server for semantic + structural search over Java codebases"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"