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.
- ast_java.py +8 -3
- build_ast_graph.py +72 -16
- graph_enrich.py +2 -1
- graph_types.py +133 -0
- java_codebase_rag/_fdlimit.py +10 -2
- java_codebase_rag/_stdio.py +32 -0
- java_codebase_rag/cli.py +135 -24
- java_codebase_rag/config.py +128 -9
- java_codebase_rag/install_data/agents/explorer-rag-cli.md +291 -0
- java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +8 -8
- java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +8 -8
- java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +251 -0
- java_codebase_rag/installer.py +438 -103
- java_codebase_rag/jrag.py +4300 -0
- java_codebase_rag/jrag_envelope.py +1085 -0
- java_codebase_rag/jrag_hints.py +204 -0
- java_codebase_rag/jrag_render.py +688 -0
- java_codebase_rag/pipeline.py +20 -0
- {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/METADATA +137 -94
- java_codebase_rag-0.8.0.dist-info/RECORD +43 -0
- {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/WHEEL +1 -1
- {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/entry_points.txt +1 -0
- {java_codebase_rag-0.6.6.dist-info → java_codebase_rag-0.8.0.dist-info}/top_level.txt +2 -0
- java_index_flow_lancedb.py +34 -19
- java_ontology.py +12 -0
- ladybug_queries.py +233 -52
- mcp_hints.py +6 -6
- mcp_v2.py +205 -617
- resolve_service.py +649 -0
- search_lancedb.py +10 -1
- server.py +20 -12
- java_codebase_rag-0.6.6.dist-info/RECORD +0 -34
- {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]
|