java-codebase-rag 0.12.0__py3-none-any.whl → 0.12.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- java_codebase_rag-0.12.2.dist-info/METADATA +35 -0
- java_codebase_rag-0.12.2.dist-info/RECORD +4 -0
- {java_codebase_rag-0.12.0.dist-info → java_codebase_rag-0.12.2.dist-info}/WHEEL +1 -1
- java_codebase_rag/_deprecation.py +0 -103
- java_codebase_rag/_fdlimit.py +0 -56
- java_codebase_rag/_stdio.py +0 -32
- java_codebase_rag/_version.py +0 -35
- java_codebase_rag/absence/__init__.py +0 -0
- java_codebase_rag/absence/absence_diagnosis.py +0 -700
- java_codebase_rag/absence/absence_types.py +0 -124
- java_codebase_rag/absence/absence_vocab.py +0 -460
- java_codebase_rag/analysis/__init__.py +0 -0
- java_codebase_rag/analysis/pr_analysis.py +0 -563
- java_codebase_rag/analysis/resolve_service.py +0 -740
- java_codebase_rag/ast/__init__.py +0 -0
- java_codebase_rag/ast/ast_java.py +0 -2847
- java_codebase_rag/ast/ast_kotlin.py +0 -1794
- java_codebase_rag/ast/brownfield_events.py +0 -58
- java_codebase_rag/ast/chunk_heuristics.py +0 -83
- java_codebase_rag/ast/language.py +0 -117
- java_codebase_rag/cli.py +0 -1215
- java_codebase_rag/cli_dispatch.py +0 -251
- java_codebase_rag/cli_format.py +0 -85
- java_codebase_rag/cli_progress.py +0 -94
- java_codebase_rag/config.py +0 -833
- java_codebase_rag/eval/__init__.py +0 -1
- java_codebase_rag/eval/ground_truth.py +0 -100
- java_codebase_rag/eval/metrics.py +0 -107
- java_codebase_rag/eval/runner.py +0 -556
- java_codebase_rag/graph/__init__.py +0 -0
- java_codebase_rag/graph/build_ast_graph.py +0 -4593
- java_codebase_rag/graph/graph_enrich.py +0 -1940
- java_codebase_rag/graph/graph_types.py +0 -224
- java_codebase_rag/graph/java_ontology.py +0 -465
- java_codebase_rag/graph/ladybug_queries.py +0 -2213
- java_codebase_rag/graph/path_filtering.py +0 -509
- java_codebase_rag/index/__init__.py +0 -0
- java_codebase_rag/index/java_index_flow_lancedb.py +0 -879
- java_codebase_rag/index/java_index_v1_common.py +0 -33
- java_codebase_rag/install_data/__init__.py +0 -0
- java_codebase_rag/install_data/agents/explorer-rag-cli.md +0 -110
- java_codebase_rag/install_data/agents/explorer-rag-enhanced.md +0 -152
- java_codebase_rag/install_data/skills/explore-codebase/SKILL.md +0 -165
- java_codebase_rag/install_data/skills/explore-codebase-cli/SKILL.md +0 -107
- java_codebase_rag/installer.py +0 -2188
- java_codebase_rag/jrag.py +0 -4545
- java_codebase_rag/jrag_envelope.py +0 -1107
- java_codebase_rag/jrag_hints.py +0 -204
- java_codebase_rag/jrag_render.py +0 -926
- java_codebase_rag/lance_optimize.py +0 -264
- java_codebase_rag/mcp/__init__.py +0 -0
- java_codebase_rag/mcp/mcp_hints.py +0 -932
- java_codebase_rag/mcp/mcp_v2.py +0 -1916
- java_codebase_rag/mcp/server.py +0 -886
- java_codebase_rag/pipeline.py +0 -531
- java_codebase_rag/progress.py +0 -570
- java_codebase_rag/read_payloads.py +0 -781
- java_codebase_rag/search/__init__.py +0 -0
- java_codebase_rag/search/index_common.py +0 -10
- java_codebase_rag/search/search_lancedb.py +0 -1296
- java_codebase_rag/search/search_lexical.py +0 -449
- java_codebase_rag/search/search_scoring.py +0 -537
- java_codebase_rag/watch/__init__.py +0 -0
- java_codebase_rag/watch/client.py +0 -230
- java_codebase_rag/watch/daemon.py +0 -396
- java_codebase_rag/watch/lock.py +0 -201
- java_codebase_rag/watch/paths.py +0 -76
- java_codebase_rag/watch/protocol.py +0 -122
- java_codebase_rag/watch/server.py +0 -273
- java_codebase_rag/watch/warm.py +0 -105
- java_codebase_rag/watch/watcher.py +0 -394
- java_codebase_rag-0.12.0.dist-info/METADATA +0 -340
- java_codebase_rag-0.12.0.dist-info/RECORD +0 -75
- java_codebase_rag-0.12.0.dist-info/entry_points.txt +0 -5
- java_codebase_rag-0.12.0.dist-info/licenses/LICENSE +0 -21
- java_codebase_rag-0.12.0.dist-info/top_level.txt +0 -1
- /java_codebase_rag/__init__.py → /java_codebase_rag-0.12.2.dist-info/top_level.txt +0 -0
|
@@ -1,932 +0,0 @@
|
|
|
1
|
-
"""Pure MCP v2 road-sign hint generation (no graph I/O, no search, no LLM).
|
|
2
|
-
|
|
3
|
-
Locked v1 catalog: ``propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md`` Appendix A
|
|
4
|
-
(issue #161 producer/override-route amendments in that appendix).
|
|
5
|
-
v2 resolve + neighbors fuzzy-strategy catalog: ``propose/completed/HINTS-V2-PROPOSE.md`` Appendix A.
|
|
6
|
-
v3 empty-neighbors structural catalog: ``propose/completed/HINTS-V3-PROPOSE.md`` §3.1–3.3.
|
|
7
|
-
v4 success-path catalog: ``propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md``.
|
|
8
|
-
Priority cap: same propose §7.12 / ``plans/completed/PLAN-HINTS.md`` principles.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import json
|
|
14
|
-
from typing import Any, Literal, NamedTuple
|
|
15
|
-
|
|
16
|
-
from java_codebase_rag.graph.java_ontology import EDGE_SCHEMA, FUZZY_STRATEGY_SET
|
|
17
|
-
|
|
18
|
-
# Normative schema description (propose §3.1) — imported by ``mcp_v2`` for Field(description=...).
|
|
19
|
-
MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION = (
|
|
20
|
-
"Machine-parseable next-action objects. Each element has "
|
|
21
|
-
"label (short semantic name, e.g. 'routes via members', 'implementors'), "
|
|
22
|
-
"tool (MCP tool name), args (ready-to-use parameters), and actionable "
|
|
23
|
-
"(True = direct call with complete args; False = advisory/partial — agent "
|
|
24
|
-
"fills missing values or uses as guidance). reason explains why the hint was emitted. "
|
|
25
|
-
"Advisories (separate field) carry pure informational text with no tool call suggestion."
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# --- Internal structured hint representation (no mcp_v2 import) ---
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class _StructuredHint(NamedTuple):
|
|
32
|
-
tool: str
|
|
33
|
-
args: dict[str, Any]
|
|
34
|
-
actionable: bool
|
|
35
|
-
priority: int
|
|
36
|
-
label: str = ""
|
|
37
|
-
reason: str = ""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def finalize_structured_hints(scored: list[_StructuredHint]) -> list[_StructuredHint]:
|
|
41
|
-
"""Dedupe by ``(tool, json.dumps(args, sort_keys=True))``, keep highest priority, cap to 5."""
|
|
42
|
-
best: dict[tuple[str, str], tuple[int, int]] = {}
|
|
43
|
-
hints: dict[tuple[str, str], _StructuredHint] = {}
|
|
44
|
-
for idx, h in enumerate(scored):
|
|
45
|
-
key = (h.tool, json.dumps(h.args, sort_keys=True))
|
|
46
|
-
prev = best.get(key)
|
|
47
|
-
if prev is None or h.priority > prev[0]:
|
|
48
|
-
best[key] = (h.priority, idx)
|
|
49
|
-
hints[key] = h
|
|
50
|
-
elif h.priority == prev[0]:
|
|
51
|
-
best[key] = (h.priority, min(prev[1], idx))
|
|
52
|
-
ordered = sorted(best.items(), key=lambda kv: (-kv[1][0], kv[1][1]))
|
|
53
|
-
return [hints[k] for k, _ in ordered[:5]]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def finalize_advisories(scored: list[tuple[int, str]]) -> list[str]:
|
|
57
|
-
"""Dedupe identical strings keeping the highest priority; cap to 5 (drop lowest).
|
|
58
|
-
|
|
59
|
-
Within the same priority tier, keep advisories in emission order (first scored wins the cap).
|
|
60
|
-
"""
|
|
61
|
-
best: dict[str, tuple[int, int]] = {}
|
|
62
|
-
for idx, (pri, text) in enumerate(scored):
|
|
63
|
-
if not text:
|
|
64
|
-
continue
|
|
65
|
-
prev = best.get(text)
|
|
66
|
-
if prev is None or pri > prev[0]:
|
|
67
|
-
best[text] = (pri, idx)
|
|
68
|
-
elif pri == prev[0]:
|
|
69
|
-
best[text] = (pri, min(prev[1], idx))
|
|
70
|
-
ordered = sorted(best.items(), key=lambda kv: (-kv[1][0], kv[1][1]))
|
|
71
|
-
return [text for text, _pri in ordered[:5]]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
_MEMBER_ONLY_DOT_KEY: dict[str, str] = {
|
|
75
|
-
"DECLARES_CLIENT": "DECLARES.DECLARES_CLIENT",
|
|
76
|
-
"DECLARES_PRODUCER": "DECLARES.DECLARES_PRODUCER",
|
|
77
|
-
"EXPOSES": "DECLARES.EXPOSES",
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
_CALLS_HIGH_FANOUT_THRESHOLD = 10
|
|
81
|
-
_EDGE_DECLARES_CLIENT = frozenset({"DECLARES_CLIENT", "DECLARES.DECLARES_CLIENT"})
|
|
82
|
-
_EDGE_DECLARES_PRODUCER = frozenset({"DECLARES_PRODUCER", "DECLARES.DECLARES_PRODUCER"})
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _extract_other_ids(results: list[dict[str, Any]]) -> list[str]:
|
|
90
|
-
ids: list[str] = []
|
|
91
|
-
for r in results:
|
|
92
|
-
other = r.get("other")
|
|
93
|
-
if isinstance(other, dict):
|
|
94
|
-
oid = other.get("id")
|
|
95
|
-
if isinstance(oid, str) and oid:
|
|
96
|
-
ids.append(oid)
|
|
97
|
-
return ids
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# --- Labels for structured hints (semantic name of each hint) ---
|
|
101
|
-
|
|
102
|
-
LABEL_CLIENTS_VIA_MEMBERS = "clients via members"
|
|
103
|
-
LABEL_ROUTES_VIA_MEMBERS = "routes via members"
|
|
104
|
-
LABEL_PRODUCERS_VIA_MEMBERS = "producers via members"
|
|
105
|
-
LABEL_OVERRIDERS = "overriders"
|
|
106
|
-
LABEL_CLIENTS_IN_OVERRIDERS = "clients in overriders"
|
|
107
|
-
LABEL_PRODUCERS_IN_OVERRIDERS = "producers in overriders"
|
|
108
|
-
LABEL_ROUTES_IN_OVERRIDERS = "routes in overriders"
|
|
109
|
-
LABEL_OUTBOUND_CLIENT = "outbound client"
|
|
110
|
-
LABEL_OUTBOUND_PRODUCER = "outbound producer"
|
|
111
|
-
LABEL_INBOUND_ROUTE = "inbound route"
|
|
112
|
-
LABEL_HIGH_FANOUT = "high fanout"
|
|
113
|
-
LABEL_DECLARING_METHOD = "declaring method"
|
|
114
|
-
LABEL_IMPLEMENTORS = "implementors"
|
|
115
|
-
LABEL_IMPLEMENTS = "implements"
|
|
116
|
-
LABEL_DEPENDENCIES = "dependencies"
|
|
117
|
-
LABEL_INJECTORS = "injectors"
|
|
118
|
-
LABEL_OUTBOUND_CALLS = "outbound calls"
|
|
119
|
-
LABEL_SUPER_DECLARATION = "super declaration"
|
|
120
|
-
LABEL_UNRESOLVED = "unresolved"
|
|
121
|
-
LABEL_TRY_RESOLVE = "try resolve"
|
|
122
|
-
LABEL_PAGE_FULL = "page full"
|
|
123
|
-
LABEL_HANDLER = "handler"
|
|
124
|
-
LABEL_HTTP_TARGETS = "HTTP targets"
|
|
125
|
-
LABEL_ASYNC_TARGETS = "async targets"
|
|
126
|
-
LABEL_WEAK_RESULTS = "weak results"
|
|
127
|
-
LABEL_TRY_SEARCH = "try search"
|
|
128
|
-
LABEL_TRY_FIND_ROUTE = "try find route"
|
|
129
|
-
LABEL_TRY_FIND_CLIENT = "try find client"
|
|
130
|
-
LABEL_TIGHTEN_IDENTIFIER = "tighten identifier"
|
|
131
|
-
LABEL_FUZZY_STRATEGY = "fuzzy strategy"
|
|
132
|
-
LABEL_CALLERS = "callers"
|
|
133
|
-
LABEL_WRONG_SUBJECT_KIND = "wrong subject kind"
|
|
134
|
-
LABEL_WRONG_DIRECTION = "wrong direction"
|
|
135
|
-
LABEL_TYPE_LEVEL_REQUERY = "type-level requery"
|
|
136
|
-
|
|
137
|
-
# §7.12 priority: DECLARES.* type rollups > OVERRIDDEN_BY.* > leaf follow-ups > meta.
|
|
138
|
-
PRIORITY_DECLARES_TYPE_ROLLUP = 4
|
|
139
|
-
PRIORITY_OVERRIDDEN_AXIS = 3
|
|
140
|
-
PRIORITY_LEAF_FOLLOWUP = 2
|
|
141
|
-
PRIORITY_META = 1
|
|
142
|
-
|
|
143
|
-
_TYPE_SYMBOL_KINDS = frozenset({"class", "interface", "enum", "record", "annotation"})
|
|
144
|
-
_METHOD_SYMBOL_KINDS = frozenset({"method", "constructor"})
|
|
145
|
-
|
|
146
|
-
_COMPOSED_DOT_KEY_PREFIXES = ("DECLARES.", "OVERRIDDEN_BY.")
|
|
147
|
-
# Row 4 (brownfield absence): only when the subject is a resolver endpoint node, not a
|
|
148
|
-
# structurally valid Symbol query that happens to be empty (DECLARES_CLIENT, EXPOSES, …).
|
|
149
|
-
_BROWNFIELD_ABSENCE_SUBJECT_LABELS = frozenset({"Client", "Producer", "Route"})
|
|
150
|
-
_REQUIRED_TRAVERSAL_ROLE_KEYS = frozenset({"type_subject", "member_subject", "alien_subject"})
|
|
151
|
-
|
|
152
|
-
_IDENTIFIER_FILTER_FIELDS: dict[str, tuple[str, ...]] = {
|
|
153
|
-
"symbol": ("fqn_contains",),
|
|
154
|
-
"route": ("path_contains",),
|
|
155
|
-
"client": ("target_service", "target_path_contains"),
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _out_count(edge_summary: dict[str, Any] | None, key: str) -> int:
|
|
160
|
-
if not edge_summary or key not in edge_summary:
|
|
161
|
-
return 0
|
|
162
|
-
cell = edge_summary[key]
|
|
163
|
-
if not isinstance(cell, dict):
|
|
164
|
-
return 0
|
|
165
|
-
return int(cell.get("out", 0) or 0)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _in_count(edge_summary: dict[str, Any] | None, key: str) -> int:
|
|
169
|
-
if not edge_summary or key not in edge_summary:
|
|
170
|
-
return 0
|
|
171
|
-
cell = edge_summary[key]
|
|
172
|
-
if not isinstance(cell, dict):
|
|
173
|
-
return 0
|
|
174
|
-
return int(cell.get("in", 0) or 0)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _record_role(rec: dict[str, Any]) -> str:
|
|
178
|
-
return str((rec.get("data") or {}).get("role") or rec.get("role") or "")
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _type_rollup_would_emit(edge_summary: dict[str, Any] | None) -> bool:
|
|
182
|
-
return (
|
|
183
|
-
_out_count(edge_summary, "DECLARES.DECLARES_CLIENT") > 0
|
|
184
|
-
or _out_count(edge_summary, "DECLARES.EXPOSES") > 0
|
|
185
|
-
or _out_count(edge_summary, "DECLARES.DECLARES_PRODUCER") > 0
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def _symbol_declaration_kind(record: dict[str, Any]) -> str | None:
|
|
190
|
-
data = record.get("data")
|
|
191
|
-
if isinstance(data, dict):
|
|
192
|
-
k = data.get("kind")
|
|
193
|
-
if k is not None:
|
|
194
|
-
return str(k).strip() or None
|
|
195
|
-
return None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _subject_node_label(subject_record: dict[str, Any]) -> str:
|
|
199
|
-
if "producer_kind" in subject_record:
|
|
200
|
-
return "Producer"
|
|
201
|
-
if "client_kind" in subject_record:
|
|
202
|
-
return "Client"
|
|
203
|
-
if "framework" in subject_record:
|
|
204
|
-
return "Route"
|
|
205
|
-
return "Symbol"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _traversal_role_for_wrong_kind(subject_label: str, subject_record: dict[str, Any]) -> str:
|
|
209
|
-
if subject_label == "Symbol":
|
|
210
|
-
sk = str(subject_record.get("kind") or "")
|
|
211
|
-
if sk in _METHOD_SYMBOL_KINDS:
|
|
212
|
-
return "member_subject"
|
|
213
|
-
if sk in _TYPE_SYMBOL_KINDS:
|
|
214
|
-
return "alien_subject"
|
|
215
|
-
return "alien_subject"
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def typical_traversal_for(
|
|
219
|
-
edge: str,
|
|
220
|
-
role_key: str,
|
|
221
|
-
*,
|
|
222
|
-
subject_id: str,
|
|
223
|
-
direction: str,
|
|
224
|
-
) -> str:
|
|
225
|
-
template = EDGE_SCHEMA[edge].typical_traversals[role_key]
|
|
226
|
-
return template.format(id=subject_id, direction=direction, edge=edge)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _neighbors_success_subject_is_type(subject_record: dict[str, Any]) -> bool:
|
|
234
|
-
return (
|
|
235
|
-
_subject_node_label(subject_record) == "Symbol"
|
|
236
|
-
and str(subject_record.get("kind") or "") in _TYPE_SYMBOL_KINDS
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _neighbors_results_homogeneous(
|
|
241
|
-
results: list[dict[str, Any]],
|
|
242
|
-
*,
|
|
243
|
-
endpoint_kind: str | None = None,
|
|
244
|
-
symbol_kinds: frozenset[str] | None = None,
|
|
245
|
-
) -> bool:
|
|
246
|
-
if not results:
|
|
247
|
-
return False
|
|
248
|
-
for row in results:
|
|
249
|
-
other = row.get("other")
|
|
250
|
-
if not isinstance(other, dict):
|
|
251
|
-
return False
|
|
252
|
-
ok = str(other.get("kind") or "")
|
|
253
|
-
if endpoint_kind is not None and ok != endpoint_kind:
|
|
254
|
-
return False
|
|
255
|
-
if symbol_kinds is not None:
|
|
256
|
-
if ok != "symbol":
|
|
257
|
-
return False
|
|
258
|
-
if str(other.get("symbol_kind") or "") not in symbol_kinds:
|
|
259
|
-
return False
|
|
260
|
-
return True
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _any_fuzzy_strategy(edges: list[dict[str, Any]]) -> bool:
|
|
272
|
-
for e in edges:
|
|
273
|
-
attrs = e.get("attrs") if isinstance(e.get("attrs"), dict) else {}
|
|
274
|
-
s = attrs.get("strategy") if isinstance(attrs, dict) else None
|
|
275
|
-
if isinstance(s, str) and s in FUZZY_STRATEGY_SET:
|
|
276
|
-
return True
|
|
277
|
-
return False
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def _find_has_identifier_shaped_filter(kind: str, flt: dict[str, Any]) -> bool:
|
|
281
|
-
for name in _IDENTIFIER_FILTER_FIELDS.get(kind, ()):
|
|
282
|
-
val = flt.get(name)
|
|
283
|
-
if val is None:
|
|
284
|
-
continue
|
|
285
|
-
if isinstance(val, str) and val.strip():
|
|
286
|
-
return True
|
|
287
|
-
if not isinstance(val, str):
|
|
288
|
-
return True
|
|
289
|
-
return False
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def _neighbors_empty_structured_hints(
|
|
295
|
-
*,
|
|
296
|
-
subject_record: dict[str, Any],
|
|
297
|
-
requested_edge_types: list[str],
|
|
298
|
-
requested_direction: Literal["in", "out"],
|
|
299
|
-
) -> list[_StructuredHint]:
|
|
300
|
-
"""Structured counterparts to neighbors empty results (Rows 1–3)."""
|
|
301
|
-
out: list[_StructuredHint] = []
|
|
302
|
-
subject_label = _subject_node_label(subject_record)
|
|
303
|
-
subject_id = str(subject_record.get("id") or "")
|
|
304
|
-
|
|
305
|
-
for edge in requested_edge_types:
|
|
306
|
-
spec = EDGE_SCHEMA.get(edge)
|
|
307
|
-
if spec is None:
|
|
308
|
-
continue
|
|
309
|
-
|
|
310
|
-
# Row 1: wrong subject kind
|
|
311
|
-
if subject_label != spec.src and subject_label != spec.dst:
|
|
312
|
-
role = _traversal_role_for_wrong_kind(subject_label, subject_record)
|
|
313
|
-
if role != "alien_subject":
|
|
314
|
-
template = spec.typical_traversals.get(role, "")
|
|
315
|
-
# Parse template like "neighbors(['{id}'],'out',['DECLARES_CLIENT'])"
|
|
316
|
-
# Intentionally removed regex fallback — the schema-controlled format
|
|
317
|
-
# is stable; silence on format mismatch is acceptable for this low-risk
|
|
318
|
-
# requery hint (main users are search→neighbors divergences, not edge types).
|
|
319
|
-
parts = template.split("'")
|
|
320
|
-
if len(parts) >= 6:
|
|
321
|
-
direction = parts[3]
|
|
322
|
-
edge_type = parts[5].replace("']", "").replace("['", "")
|
|
323
|
-
out.append(_StructuredHint(
|
|
324
|
-
"neighbors",
|
|
325
|
-
{"ids": [subject_id], "direction": direction, "edge_types": [edge_type]},
|
|
326
|
-
False,
|
|
327
|
-
PRIORITY_META,
|
|
328
|
-
LABEL_WRONG_SUBJECT_KIND,
|
|
329
|
-
f"'{edge}' connects {spec.src} → {spec.dst}; this is a {subject_label}.",
|
|
330
|
-
))
|
|
331
|
-
continue
|
|
332
|
-
|
|
333
|
-
# Row 2: wrong direction
|
|
334
|
-
wrong_direction = spec.src != spec.dst and (
|
|
335
|
-
(requested_direction == "out" and subject_label == spec.dst)
|
|
336
|
-
or (requested_direction == "in" and subject_label == spec.src)
|
|
337
|
-
)
|
|
338
|
-
if wrong_direction:
|
|
339
|
-
correct_dir = "in" if requested_direction == "out" else "out"
|
|
340
|
-
out.append(_StructuredHint(
|
|
341
|
-
"neighbors",
|
|
342
|
-
{"ids": [subject_id], "direction": correct_dir, "edge_types": [edge]},
|
|
343
|
-
False,
|
|
344
|
-
PRIORITY_META,
|
|
345
|
-
LABEL_WRONG_DIRECTION,
|
|
346
|
-
f"'{edge}' is {spec.src} → {spec.dst}; you requested direction='{requested_direction}'.",
|
|
347
|
-
))
|
|
348
|
-
continue
|
|
349
|
-
|
|
350
|
-
# Row 3: type-level requery
|
|
351
|
-
if (
|
|
352
|
-
subject_label == "Symbol"
|
|
353
|
-
and str(subject_record.get("kind") or "") in _TYPE_SYMBOL_KINDS
|
|
354
|
-
and spec.member_only
|
|
355
|
-
):
|
|
356
|
-
dot_key = _MEMBER_ONLY_DOT_KEY.get(edge)
|
|
357
|
-
if dot_key:
|
|
358
|
-
out.append(_StructuredHint(
|
|
359
|
-
"neighbors",
|
|
360
|
-
{"ids": [subject_id], "direction": requested_direction, "edge_types": [dot_key]},
|
|
361
|
-
False,
|
|
362
|
-
PRIORITY_META,
|
|
363
|
-
LABEL_TYPE_LEVEL_REQUERY,
|
|
364
|
-
f"'{edge}' lives on methods, not on {subject_label}.",
|
|
365
|
-
))
|
|
366
|
-
return out
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
def _neighbors_success_structured_hints(payload: dict[str, Any]) -> list[_StructuredHint]:
|
|
370
|
-
"""Structured counterparts to ``neighbors_success_hints`` (N1a–N7)."""
|
|
371
|
-
if not payload.get("success"):
|
|
372
|
-
return []
|
|
373
|
-
results = list(payload.get("results") or [])
|
|
374
|
-
if not results or int(payload.get("offset") or 0) != 0:
|
|
375
|
-
return []
|
|
376
|
-
req_types = payload.get("requested_edge_types")
|
|
377
|
-
if not isinstance(req_types, list) or len(req_types) != 1:
|
|
378
|
-
return []
|
|
379
|
-
edge = str(req_types[0]).strip()
|
|
380
|
-
if not edge:
|
|
381
|
-
return []
|
|
382
|
-
direction = payload.get("requested_direction")
|
|
383
|
-
if direction not in ("in", "out"):
|
|
384
|
-
return []
|
|
385
|
-
|
|
386
|
-
out: list[_StructuredHint] = []
|
|
387
|
-
origin_id = str(payload.get("origin_id") or "")
|
|
388
|
-
if not origin_id:
|
|
389
|
-
origin_id = str(results[0].get("origin_id") or "")
|
|
390
|
-
subject_record = payload.get("subject_record")
|
|
391
|
-
is_type_subject = (
|
|
392
|
-
isinstance(subject_record, dict) and _neighbors_success_subject_is_type(subject_record)
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
# N1a/N1b: DECLARES out from type → dot-key clients/routes
|
|
396
|
-
if (
|
|
397
|
-
edge == "DECLARES"
|
|
398
|
-
and direction == "out"
|
|
399
|
-
and is_type_subject
|
|
400
|
-
and _neighbors_results_homogeneous(results, symbol_kinds=_METHOD_SYMBOL_KINDS)
|
|
401
|
-
):
|
|
402
|
-
if origin_id:
|
|
403
|
-
out.append(_StructuredHint(
|
|
404
|
-
"neighbors",
|
|
405
|
-
{"ids": [origin_id], "direction": "out", "edge_types": ["DECLARES.DECLARES_CLIENT"]},
|
|
406
|
-
True,
|
|
407
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
408
|
-
LABEL_CLIENTS_VIA_MEMBERS,
|
|
409
|
-
"type has members with DECLARES_CLIENT edges",
|
|
410
|
-
))
|
|
411
|
-
out.append(_StructuredHint(
|
|
412
|
-
"neighbors",
|
|
413
|
-
{"ids": [origin_id], "direction": "out", "edge_types": ["DECLARES.EXPOSES"]},
|
|
414
|
-
True,
|
|
415
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
416
|
-
LABEL_ROUTES_VIA_MEMBERS,
|
|
417
|
-
"type has members with EXPOSES edges",
|
|
418
|
-
))
|
|
419
|
-
|
|
420
|
-
# N2: DECLARES_CLIENT / DECLARES.DECLARES_CLIENT out → HTTP_CALLS
|
|
421
|
-
if edge in _EDGE_DECLARES_CLIENT and direction == "out":
|
|
422
|
-
if _neighbors_results_homogeneous(results, endpoint_kind="client"):
|
|
423
|
-
other_ids = _extract_other_ids(results)
|
|
424
|
-
out.append(_StructuredHint(
|
|
425
|
-
"neighbors",
|
|
426
|
-
{"ids": other_ids, "direction": "out", "edge_types": ["HTTP_CALLS"]},
|
|
427
|
-
bool(other_ids),
|
|
428
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
429
|
-
LABEL_HTTP_TARGETS,
|
|
430
|
-
"clients have outbound HTTP_CALLS edges",
|
|
431
|
-
))
|
|
432
|
-
|
|
433
|
-
# N3: DECLARES_PRODUCER / DECLARES.DECLARES_PRODUCER out → ASYNC_CALLS
|
|
434
|
-
if edge in _EDGE_DECLARES_PRODUCER and direction == "out":
|
|
435
|
-
if _neighbors_results_homogeneous(results, endpoint_kind="producer"):
|
|
436
|
-
other_ids = _extract_other_ids(results)
|
|
437
|
-
out.append(_StructuredHint(
|
|
438
|
-
"neighbors",
|
|
439
|
-
{"ids": other_ids, "direction": "out", "edge_types": ["ASYNC_CALLS"]},
|
|
440
|
-
bool(other_ids),
|
|
441
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
442
|
-
LABEL_ASYNC_TARGETS,
|
|
443
|
-
"producers have outbound ASYNC_CALLS edges",
|
|
444
|
-
))
|
|
445
|
-
|
|
446
|
-
# N4: EXPOSES in → CALLS (callers)
|
|
447
|
-
if (
|
|
448
|
-
edge == "EXPOSES"
|
|
449
|
-
and direction == "in"
|
|
450
|
-
and _neighbors_results_homogeneous(results, symbol_kinds=_METHOD_SYMBOL_KINDS)
|
|
451
|
-
):
|
|
452
|
-
other_ids = _extract_other_ids(results)
|
|
453
|
-
out.append(_StructuredHint(
|
|
454
|
-
"neighbors",
|
|
455
|
-
{"ids": other_ids, "direction": "in", "edge_types": ["CALLS"]},
|
|
456
|
-
bool(other_ids),
|
|
457
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
458
|
-
LABEL_CALLERS,
|
|
459
|
-
"handler methods may have inbound CALLS edges",
|
|
460
|
-
))
|
|
461
|
-
|
|
462
|
-
# N5: HTTP_CALLS in → DECLARES_CLIENT
|
|
463
|
-
if edge == "HTTP_CALLS" and direction == "in":
|
|
464
|
-
if _neighbors_results_homogeneous(results, endpoint_kind="client"):
|
|
465
|
-
other_ids = _extract_other_ids(results)
|
|
466
|
-
out.append(_StructuredHint(
|
|
467
|
-
"neighbors",
|
|
468
|
-
{"ids": other_ids, "direction": "in", "edge_types": ["DECLARES_CLIENT"]},
|
|
469
|
-
bool(other_ids),
|
|
470
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
471
|
-
LABEL_DECLARING_METHOD,
|
|
472
|
-
"clients are declared on methods",
|
|
473
|
-
))
|
|
474
|
-
|
|
475
|
-
# N6: ASYNC_CALLS in → DECLARES_PRODUCER
|
|
476
|
-
if edge == "ASYNC_CALLS" and direction == "in":
|
|
477
|
-
if _neighbors_results_homogeneous(results, endpoint_kind="producer"):
|
|
478
|
-
other_ids = _extract_other_ids(results)
|
|
479
|
-
out.append(_StructuredHint(
|
|
480
|
-
"neighbors",
|
|
481
|
-
{"ids": other_ids, "direction": "in", "edge_types": ["DECLARES_PRODUCER"]},
|
|
482
|
-
bool(other_ids),
|
|
483
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
484
|
-
LABEL_DECLARING_METHOD,
|
|
485
|
-
"producers are declared on methods",
|
|
486
|
-
))
|
|
487
|
-
|
|
488
|
-
# N7: DECLARES.EXPOSES out → EXPOSES (handler)
|
|
489
|
-
if edge == "DECLARES.EXPOSES" and direction == "out":
|
|
490
|
-
if _neighbors_results_homogeneous(results, endpoint_kind="route"):
|
|
491
|
-
other_ids = _extract_other_ids(results)
|
|
492
|
-
out.append(_StructuredHint(
|
|
493
|
-
"neighbors",
|
|
494
|
-
{"ids": other_ids, "direction": "in", "edge_types": ["EXPOSES"]},
|
|
495
|
-
bool(other_ids),
|
|
496
|
-
PRIORITY_LEAF_FOLLOWUP,
|
|
497
|
-
LABEL_HANDLER,
|
|
498
|
-
"routes expose handler methods",
|
|
499
|
-
))
|
|
500
|
-
|
|
501
|
-
return out
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def generate_hints(
|
|
505
|
-
output_kind: Literal["search", "find", "describe", "neighbors", "resolve"],
|
|
506
|
-
payload: dict[str, Any],
|
|
507
|
-
) -> tuple[list[_StructuredHint], list[str]]:
|
|
508
|
-
"""Return structured hints and advisories for a success-only MCP v2 payload dict.
|
|
509
|
-
|
|
510
|
-
Returns (structured_hints, advisories) where structured_hints are machine-parseable
|
|
511
|
-
next-action objects and advisories are pure informational strings.
|
|
512
|
-
"""
|
|
513
|
-
struct_pairs: list[_StructuredHint] = []
|
|
514
|
-
advisories: list[tuple[int, str]] = []
|
|
515
|
-
|
|
516
|
-
if output_kind == "resolve":
|
|
517
|
-
if payload.get("success") is False:
|
|
518
|
-
return ([], [])
|
|
519
|
-
status = str(payload.get("status") or "")
|
|
520
|
-
if status == "one":
|
|
521
|
-
return ([], [])
|
|
522
|
-
if status == "many":
|
|
523
|
-
n = len(payload.get("candidates") or [])
|
|
524
|
-
if n > 1:
|
|
525
|
-
advisories.append((PRIORITY_META, f"{n} candidates — tighten identifier or pick a candidate by id"))
|
|
526
|
-
struct_pairs.append(_StructuredHint(
|
|
527
|
-
"resolve",
|
|
528
|
-
{"identifier": str(payload.get("resolved_identifier") or ""), "hint_kind": str(payload.get("hint_kind") or "")},
|
|
529
|
-
False, PRIORITY_META,
|
|
530
|
-
LABEL_TIGHTEN_IDENTIFIER,
|
|
531
|
-
"multiple matches found for identifier",
|
|
532
|
-
))
|
|
533
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
534
|
-
if status == "none":
|
|
535
|
-
identifier = payload.get("resolved_identifier")
|
|
536
|
-
hint_kind = payload.get("hint_kind")
|
|
537
|
-
if not isinstance(identifier, str) or not identifier.strip():
|
|
538
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
539
|
-
if any(w in identifier for w in ("*", "?")):
|
|
540
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
541
|
-
if hint_kind == "route":
|
|
542
|
-
seed = payload.get("path_prefix_seed")
|
|
543
|
-
if isinstance(seed, str) and seed.strip():
|
|
544
|
-
advisories.append((PRIORITY_META, f"no match — try find(kind='route', filter={{path_contains: '{seed}'}})"))
|
|
545
|
-
struct_pairs.append(_StructuredHint(
|
|
546
|
-
"find", {"kind": "route", "filter": {"path_contains": seed}}, True, PRIORITY_META,
|
|
547
|
-
LABEL_TRY_FIND_ROUTE,
|
|
548
|
-
"no route found for path seed",
|
|
549
|
-
))
|
|
550
|
-
elif hint_kind == "client":
|
|
551
|
-
seed = payload.get("target_service_seed")
|
|
552
|
-
if isinstance(seed, str) and seed.strip():
|
|
553
|
-
advisories.append((PRIORITY_META, f"no match — try find(kind='client', filter={{target_service: '{seed}'}})"))
|
|
554
|
-
struct_pairs.append(_StructuredHint(
|
|
555
|
-
"find", {"kind": "client", "filter": {"target_service": seed}}, True, PRIORITY_META,
|
|
556
|
-
LABEL_TRY_FIND_CLIENT,
|
|
557
|
-
"no client found for target service seed",
|
|
558
|
-
))
|
|
559
|
-
else:
|
|
560
|
-
advisories.append((PRIORITY_META, f"no match — try search(query='{identifier}') for ranked fuzzy lookup"))
|
|
561
|
-
struct_pairs.append(_StructuredHint(
|
|
562
|
-
"search", {"query": identifier}, True, PRIORITY_META,
|
|
563
|
-
LABEL_TRY_SEARCH,
|
|
564
|
-
"no exact match found for identifier",
|
|
565
|
-
))
|
|
566
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
567
|
-
return ([], [])
|
|
568
|
-
|
|
569
|
-
if not payload.get("success"):
|
|
570
|
-
return ([], [])
|
|
571
|
-
|
|
572
|
-
if output_kind == "search":
|
|
573
|
-
results: list[dict[str, Any]] = list(payload.get("results") or [])
|
|
574
|
-
lim = payload.get("limit")
|
|
575
|
-
if lim is not None and len(results) == int(lim) and results:
|
|
576
|
-
scores = [float(r.get("score", 0.0) or 0.0) for r in results]
|
|
577
|
-
mx = max(scores)
|
|
578
|
-
mn = min(scores)
|
|
579
|
-
if mx > 0.0 and (mx - mn) < 0.1 * mx:
|
|
580
|
-
advisories.append((PRIORITY_META, "results look weak — narrow the query or try find(role=…)"))
|
|
581
|
-
struct_pairs.append(_StructuredHint(
|
|
582
|
-
"find", {"kind": "symbol", "filter": {"role": "SERVICE"}}, False, PRIORITY_META,
|
|
583
|
-
LABEL_WEAK_RESULTS,
|
|
584
|
-
"search results have low score variance",
|
|
585
|
-
))
|
|
586
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
587
|
-
|
|
588
|
-
if output_kind == "find":
|
|
589
|
-
kind = str(payload.get("kind") or "")
|
|
590
|
-
results = list(payload.get("results") or [])
|
|
591
|
-
flt = payload.get("filter") if isinstance(payload.get("filter"), dict) else {}
|
|
592
|
-
lim = payload.get("limit")
|
|
593
|
-
if not results and _find_has_identifier_shaped_filter(kind, flt):
|
|
594
|
-
advisories.append((PRIORITY_META, f"no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup"))
|
|
595
|
-
identifier = ""
|
|
596
|
-
for fname in _IDENTIFIER_FILTER_FIELDS.get(kind, ()):
|
|
597
|
-
val = flt.get(fname)
|
|
598
|
-
if isinstance(val, str) and val.strip():
|
|
599
|
-
identifier = val.strip()
|
|
600
|
-
break
|
|
601
|
-
struct_pairs.append(_StructuredHint(
|
|
602
|
-
"resolve", {"identifier": identifier, "hint_kind": kind}, True, PRIORITY_META,
|
|
603
|
-
LABEL_TRY_RESOLVE,
|
|
604
|
-
f"no {kind} found with filter",
|
|
605
|
-
))
|
|
606
|
-
if lim is not None and len(results) >= int(lim) and payload.get("has_more_results") is True:
|
|
607
|
-
advisories.append((PRIORITY_META, f"result page full at {lim} — narrow filter or paginate"))
|
|
608
|
-
struct_pairs.append(_StructuredHint(
|
|
609
|
-
"find", {"kind": kind, "filter": flt, "limit": int(lim)}, False, PRIORITY_META,
|
|
610
|
-
LABEL_PAGE_FULL,
|
|
611
|
-
f"result page full at {lim}",
|
|
612
|
-
))
|
|
613
|
-
if results and (lim is None or len(results) < int(lim)):
|
|
614
|
-
node_id = str(results[0].get("id") or "")
|
|
615
|
-
if node_id:
|
|
616
|
-
if kind == "route":
|
|
617
|
-
struct_pairs.append(_StructuredHint(
|
|
618
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["EXPOSES"]},
|
|
619
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
620
|
-
LABEL_HANDLER,
|
|
621
|
-
"route exposes handler method",
|
|
622
|
-
))
|
|
623
|
-
elif kind == "client":
|
|
624
|
-
struct_pairs.append(_StructuredHint(
|
|
625
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["HTTP_CALLS"]},
|
|
626
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
627
|
-
LABEL_HTTP_TARGETS,
|
|
628
|
-
"client has outbound HTTP_CALLS edges",
|
|
629
|
-
))
|
|
630
|
-
elif kind == "producer":
|
|
631
|
-
struct_pairs.append(_StructuredHint(
|
|
632
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["ASYNC_CALLS"]},
|
|
633
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
634
|
-
LABEL_ASYNC_TARGETS,
|
|
635
|
-
"producer has outbound ASYNC_CALLS edges",
|
|
636
|
-
))
|
|
637
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
638
|
-
|
|
639
|
-
if output_kind == "neighbors":
|
|
640
|
-
results = list(payload.get("results") or [])
|
|
641
|
-
req_types = payload.get("requested_edge_types")
|
|
642
|
-
if not isinstance(req_types, list):
|
|
643
|
-
req_types = []
|
|
644
|
-
edge_labels = [str(x).strip() for x in req_types if str(x).strip()]
|
|
645
|
-
offset = int(payload.get("offset") or 0)
|
|
646
|
-
subject_record = payload.get("subject_record")
|
|
647
|
-
requested_direction = payload.get("requested_direction")
|
|
648
|
-
struct_empty: list[_StructuredHint] = []
|
|
649
|
-
struct_success: list[_StructuredHint] = []
|
|
650
|
-
struct_meta: list[_StructuredHint] = []
|
|
651
|
-
if not results and edge_labels and offset == 0:
|
|
652
|
-
if (
|
|
653
|
-
isinstance(subject_record, dict)
|
|
654
|
-
and subject_record
|
|
655
|
-
and requested_direction in ("in", "out")
|
|
656
|
-
):
|
|
657
|
-
struct_empty.extend(
|
|
658
|
-
_neighbors_empty_structured_hints(
|
|
659
|
-
subject_record=subject_record,
|
|
660
|
-
requested_edge_types=edge_labels,
|
|
661
|
-
requested_direction=requested_direction,
|
|
662
|
-
)
|
|
663
|
-
)
|
|
664
|
-
# Brownfield absence advisory (Row 4)
|
|
665
|
-
subject_label = _subject_node_label(subject_record)
|
|
666
|
-
if subject_label in _BROWNFIELD_ABSENCE_SUBJECT_LABELS:
|
|
667
|
-
for edge in edge_labels:
|
|
668
|
-
spec = EDGE_SCHEMA.get(edge)
|
|
669
|
-
if spec is not None and spec.brownfield_resolver_sourced:
|
|
670
|
-
advisories.append((PRIORITY_META, f"edges on '{edge}' are emitted by the brownfield resolver — absence here may mean unresolved (no matching annotation/target), not absent from the codebase"))
|
|
671
|
-
break
|
|
672
|
-
elif results and offset == 0:
|
|
673
|
-
struct_success.extend(_neighbors_success_structured_hints(payload))
|
|
674
|
-
# CALLS-specific meta hints
|
|
675
|
-
if isinstance(req_types, list) and req_types == ["CALLS"]:
|
|
676
|
-
if not payload.get("edge_filter_provided") and not payload.get("include_unresolved"):
|
|
677
|
-
calls_n = int(payload.get("calls_row_count") or 0) or len(results)
|
|
678
|
-
if calls_n >= _CALLS_HIGH_FANOUT_THRESHOLD:
|
|
679
|
-
origin_id = str(payload.get("origin_id") or "")
|
|
680
|
-
if origin_id:
|
|
681
|
-
struct_meta.append(_StructuredHint(
|
|
682
|
-
"neighbors",
|
|
683
|
-
{"ids": [origin_id], "direction": "out", "edge_types": ["CALLS"], "edge_filter": {}},
|
|
684
|
-
False, PRIORITY_LEAF_FOLLOWUP,
|
|
685
|
-
LABEL_HIGH_FANOUT,
|
|
686
|
-
f"{calls_n} CALLS — noisy axes are callee_declaring_role and per-call-site multiplicity",
|
|
687
|
-
))
|
|
688
|
-
# Unresolved sites advisory
|
|
689
|
-
unresolved = int(payload.get("unresolved_count") or 0)
|
|
690
|
-
if unresolved > 0:
|
|
691
|
-
page_n = len(results)
|
|
692
|
-
# Advisory shortened to stay under 200 char cap (test_advisories_char_cap)
|
|
693
|
-
advisories.append((PRIORITY_LEAF_FOLLOWUP, f"{page_n} CALLS shown; {unresolved} unresolved — see describe().unresolved_call_sites or neighbors(include_unresolved=True)"))
|
|
694
|
-
# Fuzzy strategy advisory
|
|
695
|
-
if results and _any_fuzzy_strategy(results):
|
|
696
|
-
advisories.append((PRIORITY_META, "some edges resolved via brownfield/fallback strategy — check attrs.strategy on each row"))
|
|
697
|
-
# Role-filter OTHER fallback advisory
|
|
698
|
-
edge_flt = payload.get("edge_filter") if isinstance(payload.get("edge_filter"), dict) else {}
|
|
699
|
-
role_exact = edge_flt.get("callee_declaring_role")
|
|
700
|
-
if (
|
|
701
|
-
role_exact in ("SERVICE", "REPOSITORY")
|
|
702
|
-
and not results
|
|
703
|
-
and int(payload.get("unfiltered_calls_count") or 0) >= 5
|
|
704
|
-
):
|
|
705
|
-
advisories.append((PRIORITY_META, "0 CALLS matched callee_declaring_role filter but method has many callees — targets may be OTHER (interface/JDK); try edge_filter={{exclude_callee_declaring_roles: ['ENTITY','DTO']}} instead of role exact match"))
|
|
706
|
-
# NodeFilter.role collision advisory
|
|
707
|
-
node_flt = payload.get("node_filter") if isinstance(payload.get("node_filter"), dict) else {}
|
|
708
|
-
node_role = node_flt.get("role")
|
|
709
|
-
if node_role and results:
|
|
710
|
-
method_rows = [
|
|
711
|
-
r
|
|
712
|
-
for r in results
|
|
713
|
-
if str(((r.get("other") or {}) if isinstance(r.get("other"), dict) else {}).get("symbol_kind") or "")
|
|
714
|
-
== "method"
|
|
715
|
-
]
|
|
716
|
-
if method_rows:
|
|
717
|
-
other_roles = [
|
|
718
|
-
str(
|
|
719
|
-
((r.get("other") or {}) if isinstance(r.get("other"), dict) else {}).get("role")
|
|
720
|
-
or ""
|
|
721
|
-
)
|
|
722
|
-
for r in method_rows
|
|
723
|
-
]
|
|
724
|
-
if other_roles and sum(1 for role in other_roles if role == "OTHER") >= max(
|
|
725
|
-
1, (len(other_roles) * 3) // 4
|
|
726
|
-
):
|
|
727
|
-
advisories.append((PRIORITY_META, "NodeFilter.role filters the neighbor method's role (usually OTHER), not the callee's declaring type — use edge_filter={{callee_declaring_role: 'SERVICE'}} (or REPOSITORY) for CALLS stereotype projection"))
|
|
728
|
-
return (finalize_structured_hints(struct_empty + struct_success + struct_meta), finalize_advisories(advisories))
|
|
729
|
-
|
|
730
|
-
if output_kind == "describe":
|
|
731
|
-
rec = payload.get("record")
|
|
732
|
-
if not isinstance(rec, dict):
|
|
733
|
-
return ([], [])
|
|
734
|
-
node_id = str(rec.get("id") or "")
|
|
735
|
-
if not node_id:
|
|
736
|
-
return ([], [])
|
|
737
|
-
kind = str(rec.get("kind") or "")
|
|
738
|
-
es = rec.get("edge_summary")
|
|
739
|
-
edge_summary = es if isinstance(es, dict) else None
|
|
740
|
-
|
|
741
|
-
if kind == "route":
|
|
742
|
-
struct_pairs.append(_StructuredHint(
|
|
743
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["EXPOSES"]},
|
|
744
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
745
|
-
LABEL_DECLARING_METHOD,
|
|
746
|
-
"route exposes handler method",
|
|
747
|
-
))
|
|
748
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
749
|
-
if kind == "client":
|
|
750
|
-
struct_pairs.append(_StructuredHint(
|
|
751
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_CLIENT"]},
|
|
752
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
753
|
-
LABEL_DECLARING_METHOD,
|
|
754
|
-
"client is declared on a method",
|
|
755
|
-
))
|
|
756
|
-
if _out_count(edge_summary, "HTTP_CALLS") > 0:
|
|
757
|
-
struct_pairs.append(_StructuredHint(
|
|
758
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["HTTP_CALLS"]},
|
|
759
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
760
|
-
LABEL_HTTP_TARGETS,
|
|
761
|
-
"client has outbound HTTP_CALLS edges",
|
|
762
|
-
))
|
|
763
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
764
|
-
if kind == "producer":
|
|
765
|
-
struct_pairs.append(_StructuredHint(
|
|
766
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_PRODUCER"]},
|
|
767
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
768
|
-
LABEL_DECLARING_METHOD,
|
|
769
|
-
"producer is declared on a method",
|
|
770
|
-
))
|
|
771
|
-
if _out_count(edge_summary, "ASYNC_CALLS") > 0:
|
|
772
|
-
struct_pairs.append(_StructuredHint(
|
|
773
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["ASYNC_CALLS"]},
|
|
774
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
775
|
-
LABEL_ASYNC_TARGETS,
|
|
776
|
-
"producer has outbound ASYNC_CALLS edges",
|
|
777
|
-
))
|
|
778
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
779
|
-
|
|
780
|
-
if kind != "symbol":
|
|
781
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
782
|
-
|
|
783
|
-
decl_kind = _symbol_declaration_kind(rec)
|
|
784
|
-
is_type = decl_kind in _TYPE_SYMBOL_KINDS
|
|
785
|
-
is_method = decl_kind in _METHOD_SYMBOL_KINDS
|
|
786
|
-
|
|
787
|
-
if is_type:
|
|
788
|
-
if _out_count(edge_summary, "DECLARES.DECLARES_CLIENT") > 0:
|
|
789
|
-
struct_pairs.append(_StructuredHint(
|
|
790
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES.DECLARES_CLIENT"]},
|
|
791
|
-
True, PRIORITY_DECLARES_TYPE_ROLLUP,
|
|
792
|
-
LABEL_CLIENTS_VIA_MEMBERS,
|
|
793
|
-
"type has members with DECLARES_CLIENT edges",
|
|
794
|
-
))
|
|
795
|
-
if _out_count(edge_summary, "DECLARES.EXPOSES") > 0:
|
|
796
|
-
struct_pairs.append(_StructuredHint(
|
|
797
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES.EXPOSES"]},
|
|
798
|
-
True, PRIORITY_DECLARES_TYPE_ROLLUP,
|
|
799
|
-
LABEL_ROUTES_VIA_MEMBERS,
|
|
800
|
-
"type has members with EXPOSES edges",
|
|
801
|
-
))
|
|
802
|
-
if _out_count(edge_summary, "DECLARES.DECLARES_PRODUCER") > 0:
|
|
803
|
-
struct_pairs.append(_StructuredHint(
|
|
804
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES.DECLARES_PRODUCER"]},
|
|
805
|
-
True, PRIORITY_DECLARES_TYPE_ROLLUP,
|
|
806
|
-
LABEL_PRODUCERS_VIA_MEMBERS,
|
|
807
|
-
"type has members with DECLARES_PRODUCER edges",
|
|
808
|
-
))
|
|
809
|
-
|
|
810
|
-
if not _type_rollup_would_emit(edge_summary):
|
|
811
|
-
if decl_kind == "interface" and _in_count(edge_summary, "IMPLEMENTS") > 0:
|
|
812
|
-
struct_pairs.append(_StructuredHint(
|
|
813
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["IMPLEMENTS"]},
|
|
814
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
815
|
-
LABEL_IMPLEMENTORS,
|
|
816
|
-
"interface is implemented by other types",
|
|
817
|
-
))
|
|
818
|
-
if decl_kind == "class" and _out_count(edge_summary, "IMPLEMENTS") > 0:
|
|
819
|
-
struct_pairs.append(_StructuredHint(
|
|
820
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["IMPLEMENTS"]},
|
|
821
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
822
|
-
LABEL_IMPLEMENTS,
|
|
823
|
-
"class implements other types",
|
|
824
|
-
))
|
|
825
|
-
if decl_kind == "class" and _record_role(rec) == "SERVICE" and _out_count(edge_summary, "INJECTS") > 0:
|
|
826
|
-
struct_pairs.append(_StructuredHint(
|
|
827
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["INJECTS"]},
|
|
828
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
829
|
-
LABEL_DEPENDENCIES,
|
|
830
|
-
"SERVICE injects other types",
|
|
831
|
-
))
|
|
832
|
-
if decl_kind in {"interface", "class"} and _in_count(edge_summary, "INJECTS") > 0:
|
|
833
|
-
struct_pairs.append(_StructuredHint(
|
|
834
|
-
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["INJECTS"]},
|
|
835
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
836
|
-
LABEL_INJECTORS,
|
|
837
|
-
"type is injected by other types",
|
|
838
|
-
))
|
|
839
|
-
|
|
840
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
841
|
-
|
|
842
|
-
if is_method:
|
|
843
|
-
if _out_count(edge_summary, "OVERRIDDEN_BY") > 0:
|
|
844
|
-
struct_pairs.append(_StructuredHint(
|
|
845
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDDEN_BY"]},
|
|
846
|
-
True, PRIORITY_OVERRIDDEN_AXIS,
|
|
847
|
-
LABEL_OVERRIDERS,
|
|
848
|
-
"method is overridden by other methods",
|
|
849
|
-
))
|
|
850
|
-
if _out_count(edge_summary, "OVERRIDDEN_BY.DECLARES_CLIENT") > 0:
|
|
851
|
-
struct_pairs.append(_StructuredHint(
|
|
852
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDDEN_BY.DECLARES_CLIENT"]},
|
|
853
|
-
True, PRIORITY_OVERRIDDEN_AXIS,
|
|
854
|
-
LABEL_CLIENTS_IN_OVERRIDERS,
|
|
855
|
-
"overriding methods have DECLARES_CLIENT edges",
|
|
856
|
-
))
|
|
857
|
-
if _out_count(edge_summary, "OVERRIDDEN_BY.DECLARES_PRODUCER") > 0:
|
|
858
|
-
struct_pairs.append(_StructuredHint(
|
|
859
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDDEN_BY.DECLARES_PRODUCER"]},
|
|
860
|
-
True, PRIORITY_OVERRIDDEN_AXIS,
|
|
861
|
-
LABEL_PRODUCERS_IN_OVERRIDERS,
|
|
862
|
-
"overriding methods have DECLARES_PRODUCER edges",
|
|
863
|
-
))
|
|
864
|
-
if _out_count(edge_summary, "OVERRIDDEN_BY.EXPOSES") > 0:
|
|
865
|
-
struct_pairs.append(_StructuredHint(
|
|
866
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDDEN_BY.EXPOSES"]},
|
|
867
|
-
True, PRIORITY_OVERRIDDEN_AXIS,
|
|
868
|
-
LABEL_ROUTES_IN_OVERRIDERS,
|
|
869
|
-
"overriding methods have EXPOSES edges",
|
|
870
|
-
))
|
|
871
|
-
if _out_count(edge_summary, "DECLARES_CLIENT") > 0:
|
|
872
|
-
struct_pairs.append(_StructuredHint(
|
|
873
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES_CLIENT"]},
|
|
874
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
875
|
-
LABEL_OUTBOUND_CLIENT,
|
|
876
|
-
"method declares outbound HTTP client",
|
|
877
|
-
))
|
|
878
|
-
if _out_count(edge_summary, "DECLARES_PRODUCER") > 0:
|
|
879
|
-
struct_pairs.append(_StructuredHint(
|
|
880
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES_PRODUCER"]},
|
|
881
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
882
|
-
LABEL_OUTBOUND_PRODUCER,
|
|
883
|
-
"method declares outbound async producer",
|
|
884
|
-
))
|
|
885
|
-
if _out_count(edge_summary, "EXPOSES") > 0:
|
|
886
|
-
struct_pairs.append(_StructuredHint(
|
|
887
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["EXPOSES"]},
|
|
888
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
889
|
-
LABEL_INBOUND_ROUTE,
|
|
890
|
-
"method is exposed as a route handler",
|
|
891
|
-
))
|
|
892
|
-
calls_out = _out_count(edge_summary, "CALLS")
|
|
893
|
-
if 1 <= calls_out <= 9:
|
|
894
|
-
method_role = _record_role(rec)
|
|
895
|
-
if method_role != "OTHER" or calls_out >= 3:
|
|
896
|
-
struct_pairs.append(_StructuredHint(
|
|
897
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"]},
|
|
898
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
899
|
-
LABEL_OUTBOUND_CALLS,
|
|
900
|
-
"method has outbound CALLS edges",
|
|
901
|
-
))
|
|
902
|
-
if _out_count(edge_summary, "OVERRIDES") > 0:
|
|
903
|
-
if _out_count(edge_summary, "OVERRIDDEN_BY") == 0:
|
|
904
|
-
struct_pairs.append(_StructuredHint(
|
|
905
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDES"]},
|
|
906
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
907
|
-
LABEL_SUPER_DECLARATION,
|
|
908
|
-
"method overrides a super declaration",
|
|
909
|
-
))
|
|
910
|
-
data = rec.get("data")
|
|
911
|
-
unresolved = 0
|
|
912
|
-
if isinstance(data, dict):
|
|
913
|
-
unresolved = int(data.get("unresolved_call_sites_total") or 0)
|
|
914
|
-
if unresolved > 0:
|
|
915
|
-
struct_pairs.append(_StructuredHint(
|
|
916
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"], "include_unresolved": True},
|
|
917
|
-
True, PRIORITY_LEAF_FOLLOWUP,
|
|
918
|
-
LABEL_UNRESOLVED,
|
|
919
|
-
f"method has {unresolved} unresolved call sites",
|
|
920
|
-
))
|
|
921
|
-
if _out_count(edge_summary, "CALLS") >= 10:
|
|
922
|
-
struct_pairs.append(_StructuredHint(
|
|
923
|
-
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"]},
|
|
924
|
-
False, PRIORITY_LEAF_FOLLOWUP,
|
|
925
|
-
LABEL_HIGH_FANOUT,
|
|
926
|
-
f"method has {_out_count(edge_summary, 'CALLS')} CALLS — consider filtering",
|
|
927
|
-
))
|
|
928
|
-
# Advisory for many CALLS
|
|
929
|
-
advisories.append((PRIORITY_LEAF_FOLLOWUP, "many CALLS — consider filtering by target microservice"))
|
|
930
|
-
return (finalize_structured_hints(struct_pairs), finalize_advisories(advisories))
|
|
931
|
-
|
|
932
|
-
return ([], [])
|