Skip to content

Commit e1eab42

Browse files
HumanBean17claude
andcommitted
feat(hints): describe structural hints — IMPLEMENTS/INJECTS wiring + method road signs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c3593e6 commit e1eab42

3 files changed

Lines changed: 434 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,23 @@ 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 _type_rollup_would_emit(edge_summary: dict[str, Any] | None) -> bool:
274+
return (
275+
_out_count(edge_summary, "DECLARES.DECLARES_CLIENT") > 0
276+
or _out_count(edge_summary, "DECLARES.EXPOSES") > 0
277+
or _out_count(edge_summary, "DECLARES.DECLARES_PRODUCER") > 0
278+
)
279+
280+
257281
def _symbol_declaration_kind(record: dict[str, Any]) -> str | None:
258282
data = record.get("data")
259283
if isinstance(data, dict):
@@ -1037,13 +1061,25 @@ def generate_hints(
10371061
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_CLIENT"]},
10381062
True, PRIORITY_LEAF_FOLLOWUP,
10391063
))
1064+
if _out_count(edge_summary, "HTTP_CALLS") > 0:
1065+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_FIND_SUCCESS_HTTP_TARGETS.format(id=node_id)))
1066+
struct_pairs.append(_StructuredHint(
1067+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["HTTP_CALLS"]},
1068+
True, PRIORITY_LEAF_FOLLOWUP,
1069+
))
10401070
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10411071
if kind == "producer":
10421072
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_PRODUCER_DECLARING.format(id=node_id)))
10431073
struct_pairs.append(_StructuredHint(
10441074
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["DECLARES_PRODUCER"]},
10451075
True, PRIORITY_LEAF_FOLLOWUP,
10461076
))
1077+
if _out_count(edge_summary, "ASYNC_CALLS") > 0:
1078+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_FIND_SUCCESS_ASYNC_TARGETS.format(id=node_id)))
1079+
struct_pairs.append(_StructuredHint(
1080+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["ASYNC_CALLS"]},
1081+
True, PRIORITY_LEAF_FOLLOWUP,
1082+
))
10471083
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10481084

10491085
if kind != "symbol":
@@ -1078,6 +1114,33 @@ def generate_hints(
10781114
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["DECLARES.DECLARES_PRODUCER"]},
10791115
True, PRIORITY_DECLARES_TYPE_ROLLUP,
10801116
))
1117+
1118+
if not _type_rollup_would_emit(edge_summary):
1119+
if decl_kind == "interface" and _in_count(edge_summary, "IMPLEMENTS") > 0:
1120+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_IMPLEMENTORS.format(id=node_id)))
1121+
struct_pairs.append(_StructuredHint(
1122+
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["IMPLEMENTS"]},
1123+
True, PRIORITY_LEAF_FOLLOWUP,
1124+
))
1125+
if decl_kind == "class" and _out_count(edge_summary, "IMPLEMENTS") > 0:
1126+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_IMPLEMENTS.format(id=node_id)))
1127+
struct_pairs.append(_StructuredHint(
1128+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["IMPLEMENTS"]},
1129+
True, PRIORITY_LEAF_FOLLOWUP,
1130+
))
1131+
if decl_kind == "class" and str(rec.get("data", {}).get("role") or rec.get("role") or "") == "SERVICE" and _out_count(edge_summary, "INJECTS") > 0:
1132+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_DEPENDENCIES.format(id=node_id)))
1133+
struct_pairs.append(_StructuredHint(
1134+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["INJECTS"]},
1135+
True, PRIORITY_LEAF_FOLLOWUP,
1136+
))
1137+
if decl_kind in {"interface", "class"} and _in_count(edge_summary, "INJECTS") > 0:
1138+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_TYPE_INJECTORS.format(id=node_id)))
1139+
struct_pairs.append(_StructuredHint(
1140+
"neighbors", {"ids": [node_id], "direction": "in", "edge_types": ["INJECTS"]},
1141+
True, PRIORITY_LEAF_FOLLOWUP,
1142+
))
1143+
10811144
return (finalize_hint_list(pairs), finalize_structured_hints(struct_pairs))
10821145

10831146
if is_method:
@@ -1129,6 +1192,36 @@ def generate_hints(
11291192
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["EXPOSES"]},
11301193
True, PRIORITY_LEAF_FOLLOWUP,
11311194
))
1195+
calls_out = _out_count(edge_summary, "CALLS")
1196+
if 1 <= calls_out <= 9:
1197+
method_role = str((rec.get("data") or {}).get("role") or rec.get("role") or "")
1198+
if method_role != "OTHER" or calls_out >= 3:
1199+
pairs.append((PRIORITY_LEAF_FOLLOWUP, TPL_DESCRIBE_METHOD_OUTBOUND_CALLS.format(id=node_id)))
1200+
struct_pairs.append(_StructuredHint(
1201+
"neighbors", {"ids": [node_id], "direction": "out", "edge_types": ["CALLS"]},
1202+
True, PRIORITY_LEAF_FOLLOWUP,
1203+
))
1204+
if _out_count(edge_summary, "OVERRIDES") > 0:
1205+
override_axis_emits = any(
1206+
_out_count(edge_summary, k) > 0
1207+
for k in ["OVERRIDDEN_BY"] + [k for k in (edge_summary or {}) if k == "OVERRIDDEN_BY" or k.startswith("OVERRIDDEN_BY.")]
1208+
)
1209+
if not override_axis_emits:
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)