Skip to content

Commit 51c57a4

Browse files
HumanBean17claude
andauthored
feat(hints): describe structural hints — IMPLEMENTS/INJECTS wiring + method road signs (#214)
* feat(hints): describe structural hints — IMPLEMENTS/INJECTS wiring + method road signs Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * review: simplify override-axis gate, extract _record_role, remove duplicate char-cap test Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c3593e6 commit 51c57a4

3 files changed

Lines changed: 419 additions & 2 deletions

File tree

mcp_hints.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ def _extract_other_ids(results: list[dict[str, Any]]) -> list[str]:
123123
TPL_DESCRIBE_ROUTE_DECLARING = "declaring method: neighbors(['{id}'],'in',['EXPOSES'])"
124124
TPL_DESCRIBE_CLIENT_DECLARING = "declaring method: neighbors(['{id}'],'in',['DECLARES_CLIENT'])"
125125
TPL_DESCRIBE_PRODUCER_DECLARING = "declaring method: neighbors(['{id}'],'in',['DECLARES_PRODUCER'])"
126+
TPL_DESCRIBE_TYPE_IMPLEMENTORS = "implementors: neighbors(['{id}'],'in',['IMPLEMENTS'])"
127+
TPL_DESCRIBE_TYPE_IMPLEMENTS = "implements: neighbors(['{id}'],'out',['IMPLEMENTS'])"
128+
TPL_DESCRIBE_TYPE_DEPENDENCIES = "dependencies: neighbors(['{id}'],'out',['INJECTS'])"
129+
TPL_DESCRIBE_TYPE_INJECTORS = "injectors: neighbors(['{id}'],'in',['INJECTS'])"
130+
TPL_DESCRIBE_METHOD_OUTBOUND_CALLS = "outbound calls: neighbors(['{id}'],'out',['CALLS'])"
131+
TPL_DESCRIBE_METHOD_SUPER_DECL = "super declaration: neighbors(['{id}'],'out',['OVERRIDES'])"
132+
TPL_DESCRIBE_METHOD_UNRESOLVED = "unresolved: neighbors(['{id}'],'out',['CALLS'],include_unresolved=True)"
126133

127134
TPL_FIND_EMPTY_RESOLVE = "no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup"
128135
TPL_FIND_PAGE_FULL = "result page full at {limit} — narrow filter or paginate"
@@ -254,6 +261,27 @@ def _out_count(edge_summary: dict[str, Any] | None, key: str) -> int:
254261
return int(cell.get("out", 0) or 0)
255262

256263

264+
def _in_count(edge_summary: dict[str, Any] | None, key: str) -> int:
265+
if not edge_summary or key not in edge_summary:
266+
return 0
267+
cell = edge_summary[key]
268+
if not isinstance(cell, dict):
269+
return 0
270+
return int(cell.get("in", 0) or 0)
271+
272+
273+
def _record_role(rec: dict[str, Any]) -> str:
274+
return str((rec.get("data") or {}).get("role") or rec.get("role") or "")
275+
276+
277+
def _type_rollup_would_emit(edge_summary: dict[str, Any] | None) -> bool:
278+
return (
279+
_out_count(edge_summary, "DECLARES.DECLARES_CLIENT") > 0
280+
or _out_count(edge_summary, "DECLARES.EXPOSES") > 0
281+
or _out_count(edge_summary, "DECLARES.DECLARES_PRODUCER") > 0
282+
)
283+
284+
257285
def _symbol_declaration_kind(record: dict[str, Any]) -> str | None:
258286
data = record.get("data")
259287
if isinstance(data, dict):
@@ -1037,13 +1065,25 @@ def generate_hints(
10371065
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_CLIENT"]},
10381066
True, PRIORITY_LEAF_FOLLOWUP,
10391067
))
1068+
if _out_count(edge_summary, "HTTP_CALLS") > 0:
1069+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_FIND_SUCCESS_HTTP_TARGETS.format(id=node_id)))
1070+
struct_pairs.append(_StructuredHint(
1071+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["HTTP_CALLS"]},
1072+
True, PRIORITY_LEAF_FOLLOWUP,
1073+
))
10401074
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10411075
if kind == "producer":
10421076
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_PRODUCER_DECLARING.format(id=node_id)))
10431077
struct_pairs.append(_StructuredHint(
10441078
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_PRODUCER"]},
10451079
True, PRIORITY_LEAF_FOLLOWUP,
10461080
))
1081+
if _out_count(edge_summary, "ASYNC_CALLS") > 0:
1082+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_FIND_SUCCESS_ASYNC_TARGETS.format(id=node_id)))
1083+
struct_pairs.append(_StructuredHint(
1084+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["ASYNC_CALLS"]},
1085+
True, PRIORITY_LEAF_FOLLOWUP,
1086+
))
10471087
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10481088

10491089
if kind != "symbol":
@@ -1078,6 +1118,33 @@ def generate_hints(
10781118
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES.DECLARES_PRODUCER"]},
10791119
True, PRIORITY_DECLARES_TYPE_ROLLUP,
10801120
))
1121+
1122+
if not _type_rollup_would_emit(edge_summary):
1123+
if decl_kind == "interface" and _in_count(edge_summary, "IMPLEMENTS") > 0:
1124+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_IMPLEMENTORS.format(id=node_id)))
1125+
struct_pairs.append(_StructuredHint(
1126+
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["IMPLEMENTS"]},
1127+
True, PRIORITY_LEAF_FOLLOWUP,
1128+
))
1129+
if decl_kind == "class" and _out_count(edge_summary, "IMPLEMENTS") > 0:
1130+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_IMPLEMENTS.format(id=node_id)))
1131+
struct_pairs.append(_StructuredHint(
1132+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["IMPLEMENTS"]},
1133+
True, PRIORITY_LEAF_FOLLOWUP,
1134+
))
1135+
if decl_kind == "class" and _record_role(rec) == "SERVICE" and _out_count(edge_summary, "INJECTS") > 0:
1136+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_DEPENDENCIES.format(id=node_id)))
1137+
struct_pairs.append(_StructuredHint(
1138+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["INJECTS"]},
1139+
True, PRIORITY_LEAF_FOLLOWUP,
1140+
))
1141+
if decl_kind in {"interface", "class"} and _in_count(edge_summary, "INJECTS") > 0:
1142+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_INJECTORS.format(id=node_id)))
1143+
struct_pairs.append(_StructuredHint(
1144+
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["INJECTS"]},
1145+
True, PRIORITY_LEAF_FOLLOWUP,
1146+
))
1147+
10811148
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10821149

10831150
if is_method:
@@ -1129,6 +1196,32 @@ def generate_hints(
11291196
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["EXPOSES"]},
11301197
True, PRIORITY_LEAF_FOLLOWUP,
11311198
))
1199+
calls_out = _out_count(edge_summary, "CALLS")
1200+
if 1 <= calls_out <= 9:
1201+
method_role = _record_role(rec)
1202+
if method_role != "OTHER" or calls_out >= 3:
1203+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_METHOD_OUTBOUND_CALLS.format(id=node_id)))
1204+
struct_pairs.append(_StructuredHint(
1205+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"]},
1206+
True, PRIORITY_LEAF_FOLLOWUP,
1207+
))
1208+
if _out_count(edge_summary, "OVERRIDES") > 0:
1209+
if _out_count(edge_summary, "OVERRIDDEN_BY") == 0:
1210+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_METHOD_SUPER_DECL.format(id=node_id)))
1211+
struct_pairs.append(_StructuredHint(
1212+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["OVERRIDES"]},
1213+
True, PRIORITY_LEAF_FOLLOWUP,
1214+
))
1215+
data = rec.get("data")
1216+
unresolved = 0
1217+
if isinstance(data, dict):
1218+
unresolved = int(data.get("unresolved_call_sites_total") or 0)
1219+
if unresolved > 0:
1220+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_METHOD_UNRESOLVED.format(id=node_id)))
1221+
struct_pairs.append(_StructuredHint(
1222+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"], "include_unresolved": True},
1223+
True, PRIORITY_LEAF_FOLLOWUP,
1224+
))
11321225
if _out_count(edge_summary, "CALLS") >= 10:
11331226
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_METHOD_MANY_CALLS))
11341227
struct_pairs.append(_StructuredHint(
File renamed without changes.

0 commit comments

Comments
 (0)