@@ -123,6 +123,13 @@ def _extract_other_ids(results: list[dict[str, Any]]) -> list[str]:
123123TPL_DESCRIBE_ROUTE_DECLARING = "declaring method: neighbors(['{id}'],'in',['EXPOSES'])"
124124TPL_DESCRIBE_CLIENT_DECLARING = "declaring method: neighbors(['{id}'],'in',['DECLARES_CLIENT'])"
125125TPL_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
127134TPL_FIND_EMPTY_RESOLVE = "no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup"
128135TPL_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+
257281def _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 (
0 commit comments