@@ -61,6 +61,17 @@ async def get_artifact_relationships(
6161 - The response contains relationship metadata and short summaries, not
6262 full source code. Use `fetch_artifacts` on returned identifiers when
6363 exact source content is needed.
64+ - Choose `profile` by artifact shape: `callsOnly` for function/method
65+ callers and callees; `inheritanceOnly` for hierarchy; `allRelevant`
66+ for calls plus inheritance only (it excludes references);
67+ `referencesOnly` for where-used checks on types, containers, fields,
68+ commands, events, interfaces, and other non-call usage.
69+ - Mediated or dynamic frameworks such as command buses, event buses,
70+ dependency injection, reflection, route binding, subscriptions,
71+ schedulers, or generated dispatch may not expose a direct call edge.
72+ When graph context is missing or insufficient, use targeted
73+ `grep_search` for construction, registration, dispatch, route,
74+ subscription, or scheduler text surfaced by source you've already read.
6475 - If any relationship group has `truncated=true`, increase
6576 `max_count_per_type` up to 1000 or narrow the investigation with a
6677 more specific `profile`.
@@ -69,9 +80,9 @@ async def get_artifact_relationships(
6980 identifier: Fully qualified artifact identifier from search or fetch results.
7081 profile: Relationship profile to expand. One of:
7182 - "callsOnly" (default): outgoing and incoming calls
72- - "inheritanceOnly": ancestors and descendants
73- - "allRelevant": calls + inheritance (4 groups)
74- - "referencesOnly": symbol references
83+ - "inheritanceOnly": ancestors, descendants, implementations, and derived types
84+ - "allRelevant": calls + inheritance only; references are excluded
85+ - "referencesOnly": where-used LSP references for non-call usage
7586 max_count_per_type: Maximum related artifacts per relationship type (1–1000, default 50).
7687
7788 Returns:
@@ -191,7 +202,15 @@ def _build_relationships_dict(data: dict) -> Dict[str, Any]:
191202
192203 if found :
193204 relationships = data .get ("relationships" ) or []
194- payload ["relationships" ] = [_build_group (group ) for group in relationships ]
205+ groups = [_build_group (group ) for group in relationships ]
206+ payload ["relationships" ] = groups
207+
208+ counts = _build_counts (data .get ("availableRelationshipCounts" ))
209+ if counts is not None :
210+ payload ["availableRelationshipCounts" ] = counts
211+ payload ["hint" ] = _build_relationship_hint (found , mcp_profile , groups , counts )
212+ else :
213+ payload ["hint" ] = _build_relationship_hint (found , mcp_profile , [], None )
195214
196215 return payload
197216
@@ -226,3 +245,113 @@ def _build_group(group: dict) -> Dict[str, Any]:
226245 "truncated" : bool (group .get ("truncated" )),
227246 "items" : items ,
228247 }
248+
249+
250+ def _build_counts (counts : Any ) -> Dict [str , int ] | None :
251+ """Preserve backend relationship counts that guide profile recovery."""
252+ if not isinstance (counts , dict ):
253+ return None
254+
255+ return {
256+ "outgoingCalls" : int (counts .get ("outgoingCalls" ) or counts .get ("OutgoingCalls" ) or 0 ),
257+ "incomingCalls" : int (counts .get ("incomingCalls" ) or counts .get ("IncomingCalls" ) or 0 ),
258+ "ancestors" : int (counts .get ("ancestors" ) or counts .get ("Ancestors" ) or 0 ),
259+ "descendants" : int (counts .get ("descendants" ) or counts .get ("Descendants" ) or 0 ),
260+ "references" : int (counts .get ("references" ) or counts .get ("References" ) or 0 ),
261+ }
262+
263+
264+ def _build_relationship_hint (
265+ found : bool ,
266+ profile : str ,
267+ groups : List [Dict [str , Any ]],
268+ counts : Dict [str , int ] | None ,
269+ ) -> str :
270+ """Give model-facing next-step guidance for graph traversal results."""
271+ if not found :
272+ return (
273+ "No relationship data was found for this identifier. Verify that the identifier came from "
274+ "a recent search/fetch result and points to a symbol-level artifact; otherwise re-run "
275+ "semantic_search or grep_search to get a fresh identifier."
276+ )
277+
278+ if any (group ["truncated" ] for group in groups ):
279+ return (
280+ "Some relationship groups are truncated. If the user asked for all usages or full graph "
281+ "scope, call get_artifact_relationships again with a higher max_count_per_type, then "
282+ "fetch promising related artifacts before making broad claims."
283+ )
284+
285+ if all (group ["totalCount" ] == 0 for group in groups ):
286+ return _build_empty_profile_hint (profile , counts )
287+
288+ return (
289+ "Fetch promising related artifacts before making claims about behavior, concrete applications, "
290+ "or how broadly this mechanism is used."
291+ )
292+
293+
294+ def _build_empty_profile_hint (profile : str , counts : Dict [str , int ] | None ) -> str :
295+ has_calls = (counts or {}).get ("outgoingCalls" , 0 ) > 0 or (counts or {}).get ("incomingCalls" , 0 ) > 0
296+ has_inheritance = (counts or {}).get ("ancestors" , 0 ) > 0 or (counts or {}).get ("descendants" , 0 ) > 0
297+ has_references = (counts or {}).get ("references" , 0 ) > 0
298+
299+ if profile == "referencesOnly" and has_calls and has_inheritance :
300+ return (
301+ "No references were found for this profile, but call and inheritance relationships exist. "
302+ "Use callsOnly for function/method callers or callees, or inheritanceOnly for base classes, "
303+ "interfaces, overrides, implementations, or derived types."
304+ )
305+ if profile == "referencesOnly" and has_calls :
306+ return (
307+ "No references were found for this profile, but call relationships exist. Use callsOnly "
308+ "for function/method callers or callees. Use referencesOnly for where-used checks on "
309+ "types, containers, fields, commands, events, interfaces, and other non-call usage."
310+ )
311+ if profile == "referencesOnly" and has_inheritance :
312+ return (
313+ "No references were found for this profile, but inheritance relationships exist. Use "
314+ "inheritanceOnly for base classes, interfaces, overrides, implementations, or derived types."
315+ )
316+ if profile == "callsOnly" and has_references and has_inheritance :
317+ return (
318+ "No call relationships were found for this profile, but references and inheritance "
319+ "relationships exist. Try referencesOnly for where-used checks or inheritanceOnly for hierarchy."
320+ )
321+ if profile == "callsOnly" and has_references :
322+ return (
323+ "No call relationships were found for this profile, but references exist. Use referencesOnly "
324+ "for where-used checks on types, containers, fields, commands, events, interfaces, or mediated dispatch symbols."
325+ )
326+ if profile == "callsOnly" and has_inheritance :
327+ return (
328+ "No call relationships were found for this profile, but inheritance relationships exist. "
329+ "Use inheritanceOnly for base classes, interfaces, overrides, implementations, or derived types."
330+ )
331+ if profile == "allRelevant" and has_references :
332+ return (
333+ "No calls or inheritance relationships were found for allRelevant. allRelevant excludes "
334+ "references by design; use referencesOnly for where-used checks."
335+ )
336+ if profile == "inheritanceOnly" and has_calls and has_references :
337+ return (
338+ "No inheritance relationships were found for this profile. Use callsOnly for function "
339+ "callers/callees, or referencesOnly for where-used checks on types, commands, events, fields, containers, or interfaces."
340+ )
341+ if profile == "inheritanceOnly" and has_calls :
342+ return (
343+ "No inheritance relationships were found for this profile, but call relationships exist. "
344+ "Use callsOnly for function/method callers or callees."
345+ )
346+ if profile == "inheritanceOnly" and has_references :
347+ return (
348+ "No inheritance relationships were found for this profile, but references exist. Use "
349+ "referencesOnly for where-used checks on types, containers, fields, commands, events, interfaces, or mediated dispatch symbols."
350+ )
351+
352+ return (
353+ "No relationships were found for this profile. Empty profile results do not mean the artifact "
354+ "has no graph data. Use callsOnly for function/method callers and callees, inheritanceOnly for "
355+ "hierarchy, allRelevant for calls plus inheritance, and referencesOnly for where-used checks on "
356+ "types, containers, fields, commands, events, interfaces, and other non-call usage."
357+ )
0 commit comments