feat(neighbors): navigate DECLARES.* composed edge types in one call#171
Conversation
Enable 2-hop DECLARES.DECLARES_CLIENT, DECLARES.DECLARES_PRODUCER, and DECLARES.EXPOSES traversal for type Symbol origins (out only), with via_id in attrs and count parity against describe edge_summary. Update describe hint templates and docs; keep OVERRIDDEN_BY* rejected and empty neighbors hints dot-key-free. Co-authored-by: Cursor <cursoragent@cursor.com>
HumanBean17
left a comment
There was a problem hiding this comment.
Review summary
Solid PR-1 implementation: shared _MEMBER_EDGE_COMPOSED_REL_MAP for rollup + traversal, count-parity test, scoped hints reversal, and clear MCP contract (edge_type echoes dot-key, via_id in attrs, OVERRIDDEN_BY* still rejected at validation).
Recommend fixing one doc-generator bug before merge (details below). Happy path tests and manual evidence look good.
Blocking: EDGE-NAVIGATION.md duplicates the two-hop recipe
_COMPOSED_MEMBER_TYPE_TRAVERSAL in java_ontology.py already includes both the dot-key call and the two-hop alternative:
_COMPOSED_MEMBER_TYPE_TRAVERSAL = (
"neighbors(['{id}'],'out',['DECLARES.{edge}']) — or "
"neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['{edge}'])"
)But scripts/generate_edge_navigation.py appends the ontology type_subject again:
traversal = f"{composed} — or {traversal}"Generated type_subject lines therefore read like:
dot-key — or two-hop — or two-hop
Plan §6 asked for dot-key alongside two-hop, not two-hop twice.
Suggested fix: for EXPOSES / DECLARES_CLIENT / DECLARES_PRODUCER, set traversal = composed only (drop the — or {traversal} suffix), then regenerate docs/EDGE-NAVIGATION.md.
Non-blocking (optional follow-ups)
-
Mixed
edge_types+ pagination — when requesting e.g.["DECLARES", "DECLARES.DECLARES_CLIENT"], all flat edges are appended before composed edges, thenlimitis applied; a small limit can return only member Symbols and zero Clients/Routes. Fine for v1 if documented or callers use the dot-key alone for terminals. -
Drift risk — composed keys live in
kuzu_queries._MEMBER_EDGE_COMPOSED_REL_MAP,mcp_v2.ComposedEdgeType, andjava_ontology._COMPOSED_MEMBER_TYPE_TRAVERSAL; a future fourth key needs three edits. Could deriveComposedEdgeTypefrom the map later. -
Duplicate tests —
test_neighbors_rejects_composed_edge_summary_keyandtest_neighbors_dot_key_method_origin_rejectedcover the same method-origin rejection; one could be dropped per plan §9. -
Merge housekeeping — move
propose/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.mdtopropose/completed/when landed.
Otherwise LGTM on scope: no re-index, describe/neighbors parity, HV20 preserved for empty neighbors hints.
Use _COMPOSED_MEMBER_TYPE_TRAVERSAL alone in the doc generator (it already includes the two-hop alternative). Document mixed flat+composed pagination ordering; drop duplicate method-origin test covered in compose. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
neighborsaccepts threeDECLARES.*composed dot-keys (DECLARES.DECLARES_CLIENT,DECLARES.DECLARES_PRODUCER,DECLARES.EXPOSES) for type Symbol origins withdirection="out"only; results echo the dot-key asedge_typeand includevia_idinattrs._MEMBER_EDGE_COMPOSED_REL_MAPdrives bothmember_edge_rollup_for(describe) andmember_edge_traversal_for(neighbors) for count parity.TPL_DESCRIBE_TYPE_*_VIA_MEMBERS) now prescribe single-call dot-keyneighbors(...)— partial reversal of HINTS-ROAD-SIGNS decision plan: Tier 1B (B2b + B6) plan + per-PR Cursor prompts #11 for that family only; emptyneighborsstructural hints still filter dot-keys via_filter_neighbors_dotkey_hints.OVERRIDDEN_BY/OVERRIDDEN_BY.*remain rejected at validation; wrong origin or inbound direction returnssuccess=Falsewith clear messages.Implements propose/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.md / #162. Plan:
plans/PLAN-NEIGHBORS-DOT-KEY-TRAVERSAL.md§ PR-1.No ontology version bump or re-index (query-time / MCP only).
Manual evidence
Test plan
.venv/bin/ruff check .PATH="$(pwd)/.venv/bin:$PATH" .venv/bin/python -m pytest tests -q(604 passed)pytest … -k "dot_key or declares_dot or overridden_by or composed_edge_summary or hints_describe_type or hv20"Made with Cursor