java-codebase-rag 0.9.2__py3-none-any.whl → 0.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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()})"
java_codebase_rag/cli.py CHANGED
@@ -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(
@@ -1,125 +1,70 @@
1
1
  ---
2
2
  name: explorer-rag-cli
3
- description: "MUST BE USED PROACTIVELY. Universal read-only explorer agent that drives the `jrag` CLI for graph-native codebase navigation (callers, callees, routes, clients, producers, impact, search, inspect, flow, overview) and falls back to file-system search (grep, glob, file 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. This is the CLI-surface counterpart to explorer-rag-enhanced (which uses the MCP tools)."
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
4
  ---
5
5
 
6
- You are a universal codebase explorer — a read-only search and navigation specialist that drives the **`jrag` CLI** (the agent-facing shell surface of java-codebase-rag) and falls back to **broad file-system search** (grep, glob, file reading) when the index is missing or stale.
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.
7
9
 
8
10
  ## Core Principles
9
11
 
10
12
  1. **Read-only.** Never edit, write, or modify any file. Only locate, read, and report.
11
- 2. **Names in, names out.** Every `<query>` is human-readable (FQN / simple name / route path / topic). Raw node IDs are never required `jrag` resolves internally.
12
- 3. **One command per intent.** `jrag` collapses resolve + walk into one call. Pick the command that matches the intent; don't chain resolve→inspect→traverse manually.
13
- 4. **Smallest sufficient tool.** Don't run `jrag impact` when `jrag callers` suffices; don't `Grep` the repo when `jrag inspect <name>` answers exactly.
14
- 5. **Excerpts over dumps.** Read excerpts and relevant sections, not entire files. Summarize findings.
15
- 6. **Stop when answered.** Don't prefetch unrelated subgraphs or scan unrelated directories.
16
-
17
- You are the **CLI-surface** explorer — use `jrag` shell commands, **not** the MCP tools. One surface per project; the MCP counterpart is `explorer-rag-enhanced`.
18
-
19
- ## Prerequisite: index must exist
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.
20
16
 
21
- `jrag` is a thin layer over the existing index. If unindexed, every command exits 2 with an actionable envelope. Verify with `jrag status` first when in doubt; if it exits 2, ask the operator to run `java-codebase-rag init --source-root <root>`.
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`.
22
18
 
23
19
  ## Tool Inventory
24
20
 
25
- ### `jrag` command groups
26
-
27
- Run `jrag --help` for the canonical list.
28
-
29
- | Group | Commands |
30
- | --- | --- |
31
- | **Orientation** | `status`, `microservices`, `map`, `conventions`, `overview` |
32
- | **Locate** | `find`, `search` |
33
- | **Listings** | `http-routes`, `http-clients`, `producers`, `topics`, `jobs`, `listeners`, `entities` |
34
- | **Traversal** | `callers`, `callees`, `hierarchy`, `implementations`, `subclasses`, `overrides`, `overridden-by`, `dependents`, `impact`, `flow`, `decompose`, `dependencies`, `connection` |
35
- | **Inspection** | `inspect`, `outline`, `imports` |
36
-
37
- ### Common flags
38
-
39
- ```
40
- --service <name> Filter by microservice
41
- --module <name> Filter by module
42
- --limit <N> Cap on results (default 20; 10 for fan-out)
43
- --format text|json Output format (default: text)
44
- --detail brief|normal|full How much of each node/edge is shown (default: normal);
45
- orthogonal to --format. brief=name @service;
46
- normal=+module/role/file/score; full=+signature/
47
- annotations/snippet. inspect + orientation default to full.
48
- --index-dir <path> Index directory override
49
- ```
50
-
51
- `--offset` is supported **only** on `find`/`search`; others emit `truncated: more results — narrow your query` when capped.
52
-
53
- ### File-system tools
54
-
55
- `Grep` (contents), `Glob` (name/path patterns), `Read` (`offset`/`limit`). Plus `Bash` (read-only: `git log`, `git blame`, `ls`, `find`), `WebSearch`/`WebFetch`.
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`.
56
24
 
57
25
  ---
58
26
 
59
27
  ## Decision Framework
60
28
 
61
- | Question type | Primary approach |
62
- | --- | --- |
63
- | "Who calls method M?" / "What does M call?" | `jrag callers <M>` / `jrag callees <M>` |
64
- | "Where is class X?" | `jrag inspect <X>`; fallback `Grep`/`Glob` |
65
- | "All controllers in service S" | `jrag find --role CONTROLLER --service S` |
66
- | "Routes/endpoints in service S" | `jrag http-routes --service S` |
67
- | "Who implements interface T?" / "Where injected?" | `jrag implementations <T>` / `jrag dependencies <T>` |
68
- | "Who depends on T?" | `jrag dependents <T>` |
69
- | "Impact of changing X?" | `jrag impact <X>` (bounded fan-in) |
70
- | "Trace request flow A→B" | `jrag flow <route-A>` `jrag connection A B` |
71
- | "Orient in service S" | `jrag overview <S>` |
72
- | Find files / text | `Glob` / `Grep` |
73
- | Read config/build/test files | `Read` |
74
- | Who changed this and when? | Bash: `git log` / `git blame` |
75
- | "How is this concept used?" | `jrag search "<text>"` (fuzzy) + `Grep` (text) |
76
- | NL "find X" | `jrag search "<X>"` `jrag inspect <hit>` |
77
-
78
- **Escalation:** Most targeted command first (identifier → `jrag inspect <X>`; structural → matching traversal). Fall back gracefully (`jrag` empty/`not_found` → `Grep`/`Glob`). ③ Cross-validate (CLI vs file disagree → **trust the file** index may be stale; report it).
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).
79
58
 
80
59
  ---
81
60
 
82
- ## Resolve-first contract (every `<query>` command)
83
-
84
- Every `jrag` command that takes a `<query>` runs `resolve_v2` internally:
85
-
86
- | `resolve_v2` status | Behavior / action |
87
- | --- | --- |
88
- | `one` | Run the traversal/listing against the resolved node. Read the result. |
89
- | `many` | Return candidates and stop. **No auto-pick.** Disambiguate with `--kind`/`--role`/`--fqn-contains`/`--service`; re-run. |
90
- | `none` | `status: not_found` envelope (exit 0). Fall back to `jrag search` or `Grep`. |
91
-
92
- Never look up a raw node ID — pass an FQN, simple name, prior `sym:`/`route:`/`client:`/`producer:` id, route path, or topic. Only `--kind` is a true resolve input; `--role`/`--java-kind`/`--fqn-contains` post-filter client-side, while `--service`/`--module` are resolve-time filters on `inspect`/`callers` and result filters elsewhere.
93
-
94
- ## Output envelope
95
-
96
- `--format` (text|json) picks the representation; `--detail` (brief|normal|full) picks how much of each node/edge shows — **both honor the same detail level**. Default: `text` + `normal`. `inspect` and orientation commands default to `full`. `--format json` emits the projected envelope (empty fields dropped): `status`, `nodes`, `edges`, `candidates`, `truncated`, `agent_next_actions` (≤5, a starting point not a directive), `file_location` (only on `one`-hit resolve). `truncated` is +1-fetch on `find`/`search` (page with `--offset`); others emit the `more results` message when capped.
97
-
98
- ## Traversal direction reference
99
-
100
- `jrag` abstracts away `direction`/`edge_types`:
101
-
102
- | Intent (command) | Underlying edges |
103
- | --- | --- |
104
- | `callers` / `callees` | `CALLS` in / out |
105
- | `hierarchy` | `EXTENDS` + `IMPLEMENTS`, both directions (parents + children) |
106
- | `implementations` / `subclasses` | `IMPLEMENTS` / `EXTENDS` in |
107
- | `overrides` / `overridden-by` | `OVERRIDES` out (subtype→supertype) / in |
108
- | `dependencies` / `dependents` | `INJECTS` out / in |
109
- | `impact` | bounded fan-in: `INJECTS`/`IMPLEMENTS`/`EXTENDS` in (depth ≤2) |
110
- | `flow <route>` | `EXPOSES`/`HTTP_CALLS`/`ASYNC_CALLS`/`CALLS` |
111
- | `connection A B` | bounded search over the same edge set |
112
-
113
- **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>(...)`.
114
-
115
- ## Ontology glossary
116
-
117
- **Roles:** `CONTROLLER` (HTTP/messaging entry) | `SERVICE` (business logic) | `REPOSITORY` (data access) | `COMPONENT` (Spring component) | `CONFIG` (`@Configuration`) | `ENTITY` (JPA/persistence) | `CLIENT` (outbound wrapper) | `MAPPER` (converter) | `DTO` | `OTHER` (infra/utility).
118
- **Capabilities:** `MESSAGE_LISTENER`, `MESSAGE_PRODUCER`, `HTTP_CLIENT`, `SCHEDULED_TASK`, `EXCEPTION_HANDLER`.
119
- **Symbol kinds:** `class`, `interface`, `enum`, `record`, `annotation`, `method`, `constructor`.
120
- **Route frameworks:** `spring_mvc`/`webflux` (HTTP), `kafka`/`rabbitmq`/`jms`/`stream` (messaging), `feign` (client mirrors). Route *kinds*: `http_endpoint`, `http_consumer`, `kafka_topic`, `rabbit_queue`, `jms_destination`, `stream_binding`. **Client kinds:** `feign_method`, `rest_template`, `web_client`. **Producer kinds:** `kafka_send`, `stream_bridge_send`. **Source layers:** `builtin`, `layer_a_meta`, `layer_b_ann`, `layer_b_fqn`, `layer_c_source`.
61
+ ## Workflow Patterns
121
62
 
122
- ---
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>`.
123
68
 
124
69
  ## Recovery Playbook
125
70
 
@@ -139,10 +84,23 @@ Never look up a raw node ID — pass an FQN, simple name, prior `sym:`/`route:`/
139
84
 
140
85
  ---
141
86
 
142
- ## Workflow Patterns
87
+ ## jrag surface — `--help` is the spec
143
88
 
144
- - **"Explain feature X":** `jrag search "X"` pick 1–3 hits `jrag inspect <hit>` targeted traversal (`callees`/`implementations`/`dependents`) stop when answered.
145
- - **"Where is X used?":** `jrag inspect <X>` (resolves; disambiguate if `many`) → `jrag callers <X>` + `jrag dependents <X>` → `Grep` fallback → report sites with file:line.
146
- - **"Find all Y":** structural → `jrag find --role <ROLE> [--service <S>]`; textual `Grep`; broad `Glob`+`Grep`. Summarize, don't dump.
147
- - **"Trace flow A→B":** `jrag flow <route-A>` `jrag connection A B` `Grep` gaps report with file:line.
148
- - **"Orient in service S":** `jrag overview <S>` → `jrag conventions --service <S>` → `jrag map --service <S>` → `jrag http-routes --service <S>`.
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.
@@ -1,158 +1,68 @@
1
1
  ---
2
2
  name: explore-codebase-cli
3
- description: "MUST BE USED PROACTIVELY. Universal read-only codebase exploration via the `jrag` CLI — one command per engineering intent (callers, callees, routes, clients, producers, impact, search, inspect, flow, overview). Use for any exploration: locating code, tracing dependencies, finding patterns, 'where is X', 'who calls Y', 'find all controllers', 'trace the flow from A to B'. Combines graph navigation with file-system search (grep, glob, file reading). Do NOT use when the answer is already in open context or for a single known file — read that file directly."
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
4
  ---
5
5
 
6
- # /explore-codebase-cli — Universal codebase exploration via `jrag`
7
-
8
- Read-only exploration combining **graph navigation through the `jrag` CLI** with **broad file-system search**. `jrag` loads the same index as the MCP server but exposes one shell command per intent instead of five MCP tools.
9
-
10
- Use any time you must search, locate, navigate, or explore. **Do NOT use when** the answer is already in context or for a single known file — read it directly.
11
-
12
6
  ## Core Principles
13
7
 
14
- 1. **Read-only.** Never edit, write, or modify any file.
15
- 2. **Names in, names out.** Every `<query>` is human-readable (FQN / simple name / route path / topic). Raw node IDs never required.
16
- 3. **One command per intent.** `jrag` collapses resolve + walk into one call — don't chain resolve→describe→neighbors manually.
17
- 4. **Stop when answered.** Don't prefetch unrelated subgraphs or directories.
18
-
19
- **One surface per project.** This is the CLI surface; the MCP surface (`search`/`find`/`describe`/`neighbors`/`resolve`) is mutually exclusive — running both strands the agent in two vocabularies.
20
-
21
- ## Prerequisite: index must exist
22
-
23
- `jrag` is a thin layer over the existing index. If unindexed, every command exits 2:
24
-
25
- ```
26
- status: error
27
- message: No index at <path>. Run: java-codebase-rag init --source-root <root>
28
- ```
29
-
30
- Verify with `jrag status` when in doubt.
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.
31
11
 
32
12
  ## Tool Inventory
33
13
 
34
- ### `jrag` command groups
35
-
36
- Run `jrag --help` for the canonical list.
37
-
38
- | Group | Commands |
39
- | --- | --- |
40
- | **Orientation** | `status`, `microservices`, `map`, `conventions`, `overview` |
41
- | **Locate** | `find`, `search` |
42
- | **Listings** | `http-routes`, `http-clients`, `producers`, `topics`, `jobs`, `listeners`, `entities` |
43
- | **Traversal** | `callers`, `callees`, `hierarchy`, `implementations`, `subclasses`, `overrides`, `overridden-by`, `dependents`, `impact`, `flow`, `decompose`, `dependencies`, `connection` |
44
- | **Inspection** | `inspect`, `outline`, `imports` |
45
-
46
- ### Common flags
47
-
48
- ```
49
- --service <name> Filter by microservice
50
- --module <name> Filter by module
51
- --limit <N> Cap on results (default 20; 10 for fan-out)
52
- --format text|json Output format (default: text)
53
- --detail brief|normal|full How much of each node/edge is shown (default: normal);
54
- orthogonal to --format. brief=name @service;
55
- normal=+module/role/file/score; full=+signature/
56
- annotations/snippet. inspect + orientation default to full.
57
- --index-dir <path> Index directory override (default: discovered from cwd)
58
- ```
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`.
59
17
 
60
- `--offset` is supported **only** on `find` and `search`. Other commands emit `truncated: more results narrow your query` when capped.
61
-
62
- ### File-system tools
63
-
64
- `Grep` (content/regex), `Glob` (name/path patterns), `Read` (`offset`/`limit`). Plus `Bash` (read-only: `git log`, `git blame`, `ls`, `find`), `WebSearch`/`WebFetch`.
18
+ *CLI surface only don't also drive the MCP tools (`search`/`find`/`describe`/`neighbors`/`resolve`) in the same session; the two vocabularies conflict.*
65
19
 
66
20
  ---
67
21
 
68
22
  ## Decision Framework
69
23
 
70
- | User asks… | First `jrag` command | Follow-up |
71
- | ---------- | -------------------- | --------- |
24
+ | User asks… | First step | Follow-up |
25
+ | ---------- | ---------- | --------- |
72
26
  | "Is the index fresh?" | `jrag status` | — |
73
27
  | Identifier (FQN / simple name) | `jrag inspect <query>` | `callers` / `callees` |
74
28
  | Fuzzy / NL "where is X" | `jrag search "<text>"` | `inspect <hit>` |
29
+ | Raw text, a string literal, a config key | `Grep` | `Read` the hits |
75
30
  | All controllers in S | `jrag find --role CONTROLLER --service S` | `callees` |
76
31
  | Interfaces in S | `jrag find --java-kind interface --service S` | `implementations` |
77
32
  | HTTP / messaging entry points | `jrag http-routes [--framework …] [--method …]` | `inspect <route>` |
78
33
  | Outbound HTTP clients | `jrag http-clients [--calls-service …]` | `callees <client>` |
79
34
  | Outbound async producers | `jrag producers [--topic-contains …]` | `callees <producer>` |
80
35
  | Topics + consumers/producers | `jrag topics [--topic-contains …]` | — |
36
+ | Cross-service seams of S | `jrag connection <S> [--inbound/--outbound/--both]` | — |
81
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` |
82
39
  | Who hits this route? | `jrag callers <route>` | — |
83
40
  | Implementations / subtypes of T? | `jrag implementations <T>` / `jrag subclasses <T>` | — |
84
41
  | Overriding / overridden methods? | `jrag overrides <method>` (UP) / `jrag overridden-by <method>` | — |
85
42
  | Who injects / depends on T? | `jrag dependencies <T>` / `jrag dependents <T>` | — |
86
43
  | Blast-radius of changing X? | `jrag impact <X>` (bounded fan-in) | `Grep` fallback |
87
- | Trace request flow A→B | `jrag flow <route>` | `connection <A> <B>` |
44
+ | Trace request flow A→B | `jrag flow <route-A>` | `connection <microservice>` (service's cross-service seams) |
88
45
  | File outline / imports | `jrag outline <file>` / `jrag imports <file>` | `inspect <row>` |
46
+ | Find files by name/path | `Glob` | `Read` |
89
47
  | "Explain service S" | `jrag overview <service>` | `http-routes`/`http-clients`/`producers` |
90
- | "Explain route /topic" | `jrag overview <subject>` | `flow` |
91
- | Find files / text | `Glob` / `Grep` | `Read` |
48
+ | "Explain route / topic" | `jrag overview <subject>` | `flow` |
92
49
  | Who changed X and when? | Bash: `git log`/`git blame` | — |
93
50
  | "How is this configured?" | `Glob` + `Grep`; `jrag search "<key>" --table yaml` | `Read` sections |
94
51
 
95
- **Escalation:** ① Most targeted command first → ② fall back gracefully (`callers` empty → `Grep`) cross-validate (CLI vs file disagree → **trust the file** — index may be stale).
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).
96
53
 
97
- **Rules of thumb:** structure beats vector for exact questions (`find`/`inspect` + traversal); vector beats structure for fuzzy discovery (`search`); file-system beats stale index.
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.
98
55
 
99
56
  ---
100
57
 
101
- ## Resolve-first contract (every `<query>` command)
102
-
103
- Every `jrag` command that takes a `<query>` runs `resolve_v2` internally:
104
-
105
- | `resolve_v2` status | `jrag` behavior |
106
- | --- | --- |
107
- | `one` | Run the traversal/listing against the resolved node. |
108
- | `many` | Return the candidate list and stop. **No auto-pick.** Disambiguate with `--kind`/`--role`/`--fqn-contains`/`--service`; re-run. |
109
- | `none` | `status: not_found` envelope (exit 0). Fall back to `search` or `Grep`. |
110
-
111
- Never look up a raw node ID — pass an FQN, simple name, prior `sym:`/`route:`/`client:`/`producer:` id, route path, or topic. Only `--kind` is a true resolve input; `--role`/`--java-kind`/`--fqn-contains` post-filter client-side, while `--service`/`--module` are resolve-time filters on `inspect`/`callers` and result filters elsewhere.
112
-
113
- ## Output envelope
114
-
115
- `--format` (text|json) picks the representation; `--detail` (brief|normal|full) picks how much of each node/edge shows — **both honor the same detail level**. Default: `text` + `normal`. `inspect` and orientation commands default to `full`. `--format json` emits the projected envelope (empty fields dropped):
116
-
117
- ```json
118
- {
119
- "status": "ok|not_found|error",
120
- "nodes": {"<id>": {...}},
121
- "edges": [{...}],
122
- "candidates": [{...}],
123
- "truncated": false,
124
- "agent_next_actions": ["jrag callers <id>", "..."],
125
- "file_location": {"filename": "...", "start_line": 123}
126
- }
127
- ```
128
-
129
- `truncated` is computed via +1-fetch on `find`/`search` (use `--limit`, then `--offset`); other commands emit the `more results` message when capped. `agent_next_actions` (≤5) maps result edges to next commands — a starting point, not a directive. `file_location` populates only on `one`-hit resolve.
130
-
131
- ## Traversal direction reference
132
-
133
- `jrag` abstracts away `direction`/`edge_types` — you name the intent, it picks the edges:
134
-
135
- | Intent (command) | Underlying edges |
136
- | --- | --- |
137
- | `callers` / `callees` | `CALLS` in / out |
138
- | `hierarchy` | `EXTENDS` + `IMPLEMENTS`, both directions (parents + children) |
139
- | `implementations` / `subclasses` | `IMPLEMENTS` / `EXTENDS` in |
140
- | `overrides` / `overridden-by` | `OVERRIDES` out (subtype→supertype) / in |
141
- | `dependencies` / `dependents` | `INJECTS` out / in |
142
- | `impact` | bounded fan-in: `INJECTS`/`IMPLEMENTS`/`EXTENDS` in (depth ≤2) |
143
- | `flow <route>` | `EXPOSES`/`HTTP_CALLS`/`ASYNC_CALLS`/`CALLS` |
144
- | `connection A B` | bounded search over the same edge set |
145
-
146
- **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>(...)`.
147
-
148
- ## Ontology glossary
149
-
150
- **Roles:** `CONTROLLER` | `SERVICE` | `REPOSITORY` | `COMPONENT` | `CONFIG` | `ENTITY` | `CLIENT` | `MAPPER` | `DTO` | `OTHER`.
151
- **Capabilities:** `MESSAGE_LISTENER`, `MESSAGE_PRODUCER`, `HTTP_CLIENT`, `SCHEDULED_TASK`, `EXCEPTION_HANDLER`.
152
- **Symbol kinds:** `class`, `interface`, `enum`, `record`, `annotation`, `method`, `constructor`.
153
- **Route frameworks:** `spring_mvc`/`webflux` (HTTP), `kafka`/`rabbitmq`/`jms`/`stream` (messaging), `feign` (client mirrors). Route *kinds*: `http_endpoint`, `http_consumer`, `kafka_topic`, `rabbit_queue`, `jms_destination`, `stream_binding`. **Client kinds:** `feign_method`, `rest_template`, `web_client`. **Producer kinds:** `kafka_send`, `stream_bridge_send`. **Source layers:** `builtin`, `layer_a_meta`, `layer_b_ann`, `layer_b_fqn`, `layer_c_source`.
58
+ ## Workflow Patterns
154
59
 
155
- ---
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>`.
156
66
 
157
67
  ## Recovery Playbook
158
68
 
@@ -173,11 +83,23 @@ Never look up a raw node ID — pass an FQN, simple name, prior `sym:`/`route:`/
173
83
 
174
84
  ---
175
85
 
176
- ## Workflow Patterns
86
+ ## jrag surface — `--help` is the spec
177
87
 
178
- - **"Explain feature X":** `jrag search "X"` pick 1–3 hits `jrag inspect <hit>` targeted traversal (`callees`/`implementations`) stop when answered.
179
- - **"Where is X used?":** `jrag inspect <X>` → `jrag callers <X>` + `jrag dependents <X>` → `Grep` fallback → report sites with file:line.
180
- - **"Find all Y":** structural → `jrag find --role <ROLE> [--service <S>]`; textual `Grep`; broad `Glob`+`Grep`. Summarize, don't dump.
181
- - **"Trace flow A→B":** `jrag flow <route-A>` `jrag connection A B` `Grep` gaps report with file:line.
182
- - **"How is this configured?":** `Glob` `**/application*.yml` → `Grep` the key → `Read` sections → `jrag search "<key>" --table yaml`.
183
- - **"Orient in a new service":** `jrag overview <S>` `jrag conventions --service <S>` `jrag map --service <S>` → `jrag http-routes --service <S>`.
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.
java_codebase_rag/jrag.py CHANGED
@@ -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.4
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
@@ -19,25 +19,26 @@ server.py,sha256=nOK3DOr-i3PJnVmE7neye_tptJumgXfIXPlTIn01MPQ,37065
19
19
  java_codebase_rag/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
20
  java_codebase_rag/_fdlimit.py,sha256=vkwjsPbZfxzZ2DZTPWO5DxtuNlLzOADzIq07iYX7GCU,2465
21
21
  java_codebase_rag/_stdio.py,sha256=TDNbpt2EP0_Zd622ihdlKwlMfxkKHWOfgLVcU6TcNbo,1458
22
- java_codebase_rag/cli.py,sha256=8WKk-_Zl1Sp7wHX4Y5f3bkIy8hHRH_QWrNuX_3WYTm4,45269
22
+ java_codebase_rag/_version.py,sha256=Dnoh_a-c13fqPQ-HV9CsSPbFUkihUJl_z5CACW4qDyA,1365
23
+ java_codebase_rag/cli.py,sha256=nI3eQ_21YDA80WbbYxKEVpIl9uA9tsf1szQhz_C-t9Q,45446
23
24
  java_codebase_rag/cli_format.py,sha256=CT7-xdwZ0bMCdP68_UOwkvm-mnLluU3LutlM-mDNk60,1839
24
25
  java_codebase_rag/cli_progress.py,sha256=q6Wh97yzLGs1B8UFk_WAKivfQu7Y5RnUUE-T2YHWkIs,3237
25
26
  java_codebase_rag/config.py,sha256=1EAlKtQx7LUo-gPoE6BOVLz0YEl1OpWFRABkF06zjgk,26464
26
27
  java_codebase_rag/installer.py,sha256=c-_tR1Ct_O3yhmruFRnDoM1Wilz-igD9qdkDPsNqLMI,79934
27
- java_codebase_rag/jrag.py,sha256=KYv1pD_FTdYh0NqEwO_yzqgHiv5PDFcMiL26aA40QwI,192278
28
+ java_codebase_rag/jrag.py,sha256=odwSSxnWUOeyhx3O-C_q0y3D4KOVtqUqeDMyKgDVqNc,196991
28
29
  java_codebase_rag/jrag_envelope.py,sha256=5jD3p2O-p9acAKHqoif5FFoqgP7Q2ziCuKSiPCHoywc,47505
29
30
  java_codebase_rag/jrag_hints.py,sha256=k2PFE4s3lZgBYHMdZcTjx1-w28nfQcBtQEVsSxI_DvE,9262
30
- java_codebase_rag/jrag_render.py,sha256=1nUyamL-MOOXDlKvssp-LsBgEtznUnGj_cPteSwuS90,31953
31
+ java_codebase_rag/jrag_render.py,sha256=WrBtVFvhpVlykHXg-uZtbdv2XsCw4KXNXVarE06pkLk,32350
31
32
  java_codebase_rag/lance_optimize.py,sha256=HI3aFebP1fenLL6Cav1jMG5kLXSrHLndO_MIJY6qQVo,11977
32
33
  java_codebase_rag/pipeline.py,sha256=L65mjK-IxkVWazUNVyIvzfQNi24behQu-Kdc7o_HwEk,17754
33
34
  java_codebase_rag/progress.py,sha256=2IxdMALDM0wAQCyJrrfZ975zM_85C-4BfHxf4AtYifE,23212
34
- java_codebase_rag/install_data/agents/explorer-rag-cli.md,sha256=mMij_BIQM4agaYhGVYjC-fQSe3We1HFeBvc5JBjJj6A,10071
35
+ java_codebase_rag/install_data/agents/explorer-rag-cli.md,sha256=bVmmzrBSSamMz7hDIKxL5alAZOXsJ_Km8TQnCcxDad0,10292
35
36
  java_codebase_rag/install_data/agents/explorer-rag-enhanced.md,sha256=gZsNFbuK0lSnOIlplbbS_muz2ozokJqvFUv65QM0NDM,10406
36
37
  java_codebase_rag/install_data/skills/explore-codebase/SKILL.md,sha256=A-v2dueVnxwBzBlxoRjZ2zOJk8DranLQ1TElwn94h0s,11529
37
- java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md,sha256=V5gIKKGkgk2KlAFf9JqPareQDMt1iQOI7cwhfOqJL1c,11348
38
- java_codebase_rag-0.9.2.dist-info/licenses/LICENSE,sha256=gxvtiHtuviR_q8ZAjWw-QTcF3DyPzg6ZY-lQrr8OPpw,1068
39
- java_codebase_rag-0.9.2.dist-info/METADATA,sha256=3lfDnbvPb-vpc_81VLNobOgdUKncX6ejy3TDUUSj4Vo,20088
40
- java_codebase_rag-0.9.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
41
- java_codebase_rag-0.9.2.dist-info/entry_points.txt,sha256=cj3QTc11UYVQnj9T3orc4daiIGaCYrXP149vKbH2R4U,168
42
- java_codebase_rag-0.9.2.dist-info/top_level.txt,sha256=8vC-VN3cMwz5vhkSTaeJ1a1bDeqLWEfrTks1CvEvIg0,273
43
- java_codebase_rag-0.9.2.dist-info/RECORD,,
38
+ java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md,sha256=K2qwZqk2xPxjPxXtUHvax1Ug2J7nBYeMTIjRznO100g,10059
39
+ java_codebase_rag-0.9.4.dist-info/licenses/LICENSE,sha256=gxvtiHtuviR_q8ZAjWw-QTcF3DyPzg6ZY-lQrr8OPpw,1068
40
+ java_codebase_rag-0.9.4.dist-info/METADATA,sha256=CEq0X_nWAQP89qo0VJgX5QR3Pf9NcsaRnk-AruJkL6k,20175
41
+ java_codebase_rag-0.9.4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
42
+ java_codebase_rag-0.9.4.dist-info/entry_points.txt,sha256=cj3QTc11UYVQnj9T3orc4daiIGaCYrXP149vKbH2R4U,168
43
+ java_codebase_rag-0.9.4.dist-info/top_level.txt,sha256=8vC-VN3cMwz5vhkSTaeJ1a1bDeqLWEfrTks1CvEvIg0,273
44
+ java_codebase_rag-0.9.4.dist-info/RECORD,,