java-codebase-rag 0.6.6__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. ast_java.py +8 -3
  2. build_ast_graph.py +72 -16
  3. graph_enrich.py +2 -1
  4. graph_types.py +133 -0
  5. java_codebase_rag/_fdlimit.py +10 -2
  6. java_codebase_rag/_stdio.py +32 -0
  7. java_codebase_rag/cli.py +135 -24
  8. java_codebase_rag/config.py +128 -9
  9. java_codebase_rag/install_data/agents/explorer-rag-cli.md +291 -0
  10. java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +8 -8
  11. java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +8 -8
  12. java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +251 -0
  13. java_codebase_rag/installer.py +438 -103
  14. java_codebase_rag/jrag.py +4300 -0
  15. java_codebase_rag/jrag_envelope.py +1085 -0
  16. java_codebase_rag/jrag_hints.py +204 -0
  17. java_codebase_rag/jrag_render.py +688 -0
  18. java_codebase_rag/pipeline.py +20 -0
  19. {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/METADATA +137 -94
  20. java_codebase_rag-0.8.0.dist-info/RECORD +43 -0
  21. {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/WHEEL +1 -1
  22. {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/entry_points.txt +1 -0
  23. {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/top_level.txt +2 -0
  24. java_index_flow_lancedb.py +34 -19
  25. java_ontology.py +12 -0
  26. ladybug_queries.py +233 -52
  27. mcp_hints.py +6 -6
  28. mcp_v2.py +205 -617
  29. resolve_service.py +649 -0
  30. search_lancedb.py +10 -1
  31. server.py +20 -12
  32. java_codebase_rag-0.6.6.dist-info/RECORD +0 -34
  33. {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,204 @@
1
+ """JRAG edge-label → CLI-command hint mapper (PR-JRAG-4).
2
+
3
+ This is the **net-new** module that powers ``envelope.agent_next_actions``. It
4
+ maps the graph's edge labels (CALLS, IMPLEMENTS, EXTENDS, INJECTS, OVERRIDES,
5
+ OVERRIDDEN_BY, HTTP_CALLS, ASYNC_CALLS — plus composed dot-keys like
6
+ ``DECLARES.CALLS`` and ``OVERRIDDEN_BY.DECLARES_CLIENT``) to the
7
+ ``jrag`` command an agent should run next for the resolved root.
8
+
9
+ Public surface: :func:`next_actions` — keyword-only, returns ``list[str]`` of
10
+ ``jrag <cmd> <fqn>`` hint strings (≤5, de-duped, zero-direction suppressed).
11
+ The function imports :data:`java_ontology.EDGE_SCHEMA` **lazily inside the body**
12
+ so :func:`java_codebase_rag.jrag.build_parser` stays pure (no backend imports at
13
+ module import time — the sentinel test pins this).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ __all__ = ["next_actions"]
20
+
21
+
22
+ # Edge label → {direction: jrag_command} map.
23
+ #
24
+ # Confirmed against java_ontology.EDGE_SCHEMA (java_ontology.py:179) and the
25
+ # traversal command surface (PR-JRAG-3a/3b). ``OVERRIDDEN_BY`` is a virtual
26
+ # label (the stored edge is ``OVERRIDES``; the describe-time rollup surfaces the
27
+ # inbound axis as ``OVERRIDDEN_BY`` for method Symbols — see NodeRecord.edge_summary
28
+ # docs at mcp_v2.py:469). HTTP_CALLS / ASYNC_CALLS only fire ``out`` because the
29
+ # ``callees`` command dispatches on Client/Producer roots to traverse those edges
30
+ # outbound; there is no inbound-only command for them (callers on a Route covers
31
+ # the inbound case via a different code path and a different root kind).
32
+ _LABEL_COMMANDS: dict[str, dict[str, str]] = {
33
+ "CALLS": {"in": "callers", "out": "callees"},
34
+ "IMPLEMENTS": {"in": "implementations", "out": "hierarchy"},
35
+ "EXTENDS": {"in": "subclasses", "out": "hierarchy"},
36
+ "INJECTS": {"in": "dependents", "out": "dependencies"},
37
+ "OVERRIDES": {"out": "overrides"},
38
+ "OVERRIDDEN_BY": {"in": "overridden-by"},
39
+ "HTTP_CALLS": {"out": "callees"},
40
+ "ASYNC_CALLS": {"out": "callees"},
41
+ # Phase 2: a Symbol declaring a Client/Producer has a useful `callees`
42
+ # surface — `callees` on a CLIENT-role Symbol aggregates the declared
43
+ # Client's HTTP_CALLS targets (and analogously for producers), so this
44
+ # is a valid, runnable follow-up. EXPOSES is intentionally unmapped (no
45
+ # clean traversal command for "routes this controller exposes"; `inspect`
46
+ # already shows them).
47
+ "DECLARES_CLIENT": {"out": "callees"},
48
+ "DECLARES_PRODUCER": {"out": "callees"},
49
+ }
50
+
51
+ # Per-root-kind command allowlist. Label-derived hints are filtered through
52
+ # this so a root never suggests a command whose kind guard would reject it.
53
+ # Route roots are special-cased in :func:`next_actions` (the label path would
54
+ # map HTTP_CALLS → `callees`, which is invalid on routes — `flow` is the
55
+ # correct escalation). Symbol (and unknown/None) → no filtering.
56
+ _KIND_ALLOWLIST: dict[str, frozenset[str]] = {
57
+ "client": frozenset({"callees", "inspect"}),
58
+ "producer": frozenset({"callees", "inspect"}),
59
+ }
60
+
61
+ # Cap on returned hints (brief: ≤5). Matches the Envelope.agent_next_actions
62
+ # contract and mcp_hints' own cap.
63
+ _MAX_HINTS = 5
64
+
65
+
66
+ def _candidate_labels(label: str) -> list[str]:
67
+ """Return the lookup candidates for a (possibly composed) edge label.
68
+
69
+ For a plain label (``"CALLS"``): ``["CALLS"]``.
70
+ For ``"OVERRIDDEN_BY.DECLARES_CLIENT"``: the prefix ``"OVERRIDDEN_BY"`` is
71
+ the semantic axis → looked up first.
72
+ For ``"DECLARES.CALLS"``: ``"DECLARES"`` is a rollup prefix with no direct
73
+ command, so the suffix ``"CALLS"`` is the actionable label.
74
+ The full label is always tried first (covers ``"DECLARES_CLIENT"`` if it
75
+ ever appears un-split).
76
+ """
77
+ if "." not in label:
78
+ return [label]
79
+ parts = label.split(".")
80
+ # Full label first (handles un-split composed forms), then prefix, then suffix.
81
+ return [label, parts[0], parts[-1]]
82
+
83
+
84
+ def _lookup_cmd(label: str) -> dict[str, str] | None:
85
+ """Look up a (possibly composed) label in the command map.
86
+
87
+ Tries the full label, the dot-prefix, and the dot-suffix. Returns the first
88
+ match or ``None``.
89
+ """
90
+ for cand in _candidate_labels(label):
91
+ cmds = _LABEL_COMMANDS.get(cand)
92
+ if cmds is not None:
93
+ return cmds
94
+ return None
95
+
96
+
97
+ def next_actions(
98
+ *,
99
+ root_fqn: str,
100
+ edge_summary: dict[str, Any] | None = None,
101
+ result_edges: list[dict[str, Any]],
102
+ graph: Any = None, # noqa: ARG001 — reserved for future use (brief contract)
103
+ current_command: str | None = None,
104
+ root_kind: str | None = None,
105
+ ) -> list[str]:
106
+ """Build ``agent_next_actions`` hints for a resolved root.
107
+
108
+ * When ``edge_summary`` is provided (``inspect`` path): iterate each
109
+ ``(label, counts)`` and emit ``jrag <cmd> <fqn>`` for direction ``d`` **only
110
+ when ``counts[d] > 0``** (zero-suppression). Composed dot-keys are covered
111
+ via :func:`_lookup_cmd`.
112
+ * When ``edge_summary`` is ``None`` (traversal path): fall back to the set of
113
+ ``edge_type`` labels present in ``result_edges``. Per-direction counts are
114
+ unavailable, so zero-suppression cannot apply — we emit both directions for
115
+ each recognized label. (The traversal command already filtered to one
116
+ direction; the hints surface the *other* edges the root has, encouraging
117
+ orthogonal exploration.)
118
+
119
+ ``root_kind`` filters hints through :data:`_KIND_ALLOWLIST` so a root never
120
+ suggests a command whose kind guard would reject it (e.g. a Client root no
121
+ longer suggests ``callers``). Route roots are special-cased: the label path
122
+ would map HTTP_CALLS → ``callees`` (invalid on routes — ``_cmd_callees``
123
+ rejects route roots), so routes instead get ``[flow, inspect]`` minus the
124
+ command just run. ``flow`` is the natural escalation from ``callers``
125
+ (full inbound+outbound chain).
126
+
127
+ De-dups and caps at ``_MAX_HINTS`` (5). ``graph`` is accepted for forward
128
+ compatibility but not read — all needed data comes from ``edge_summary`` or
129
+ ``result_edges``.
130
+ """
131
+ if not root_fqn:
132
+ return []
133
+
134
+ # Route root: special-case. The label path would map HTTP_CALLS → `callees`,
135
+ # but `_cmd_callees` rejects route roots (kind guard). `flow` is the natural
136
+ # escalation (full inbound+outbound chain). Invariant: a route root never
137
+ # gets `callees`, and gets `flow` when not the current command.
138
+ if root_kind == "route":
139
+ route_cmds = [c for c in ("flow", "inspect") if c != current_command]
140
+ return [f"jrag {cmd} {root_fqn}" for cmd in route_cmds][:_MAX_HINTS]
141
+
142
+ # Lazy import so build_parser() stays pure (PR-JRAG-4 sentinel test).
143
+ # EDGE_SCHEMA is the canonical label set; we use it to skip labels we don't
144
+ # recognize (avoids emitting hints for spurious / future edge types the
145
+ # command map doesn't cover).
146
+ from java_ontology import EDGE_SCHEMA
147
+
148
+ # Known virtual labels not in EDGE_SCHEMA (describe-time rollup constructs).
149
+ _VIRTUAL_LABELS = frozenset({"OVERRIDDEN_BY"})
150
+
151
+ def _is_known_label(label: str) -> bool:
152
+ base = label.split(".")[0]
153
+ return base in EDGE_SCHEMA or base in _VIRTUAL_LABELS
154
+
155
+ # Per-root-kind command allowlist (None → no filtering for symbol/unknown).
156
+ allowed = _KIND_ALLOWLIST.get(root_kind or "")
157
+
158
+ hints: list[str] = []
159
+ seen: set[str] = set()
160
+
161
+ def _add(cmd: str) -> None:
162
+ if allowed is not None and cmd not in allowed:
163
+ return
164
+ hint = f"jrag {cmd} {root_fqn}"
165
+ if hint not in seen:
166
+ seen.add(hint)
167
+ hints.append(hint)
168
+
169
+ if edge_summary is not None:
170
+ # inspect path: zero-suppress per direction using counts.
171
+ for label, counts in edge_summary.items():
172
+ if not _is_known_label(str(label)):
173
+ continue
174
+ cmds = _lookup_cmd(str(label))
175
+ if cmds is None:
176
+ continue
177
+ counts_dict = counts if isinstance(counts, dict) else {}
178
+ in_n = int(counts_dict.get("in", 0) or 0)
179
+ out_n = int(counts_dict.get("out", 0) or 0)
180
+ if in_n > 0 and "in" in cmds:
181
+ _add(cmds["in"])
182
+ if out_n > 0 and "out" in cmds:
183
+ _add(cmds["out"])
184
+ else:
185
+ # traversal path: infer from result_edges labels.
186
+ # No per-direction counts → emit both directions for recognized labels,
187
+ # then drop the self-hint (the command an agent just ran). The inverse
188
+ # direction (e.g. `callees` after `callers`) is the useful exploration
189
+ # signal and is kept; only the exact command just run is redundant.
190
+ # ``current_command`` is the jrag subcommand name (``args.command``).
191
+ labels_seen: set[str] = set()
192
+ for edge in result_edges or []:
193
+ et = str(edge.get("edge_type") or "").strip()
194
+ if et and _is_known_label(et):
195
+ labels_seen.add(et.split(".")[0])
196
+ for label in labels_seen:
197
+ cmds = _LABEL_COMMANDS.get(label)
198
+ if cmds is None:
199
+ continue
200
+ for d in ("in", "out"):
201
+ if d in cmds and cmds[d] != current_command:
202
+ _add(cmds[d])
203
+
204
+ return hints[:_MAX_HINTS]