You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(neighbors): navigate DECLARES.* composed edge types in one call (#171)
* feat(neighbors): navigate DECLARES.* composed edge types in one call
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>
* fix review: dedupe EDGE-NAVIGATION composed traversals
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>
* chore: land neighbors dot-key propose/plan; track #172
Move NEIGHBORS-DOT-KEY-TRAVERSAL propose and plan to completed/ with
landed status (PR #171). Follow-up #172 tracks unifying composed dot-key
constants across kuzu_queries, mcp_v2, and java_ontology.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
| `describe` | Full record + edge counts for one node. For **type** symbols, `edge_summary` may include composed dot-keys (`DECLARES.DECLARES_CLIENT`, `DECLARES.EXPOSES`); for **method** symbols it may include override-axis virtual keys (`OVERRIDDEN_BY`, …) and an `OVERRIDES` row that **merges** stored `[:OVERRIDES]` in/out with the dispatch-up rollup (per direction `max`). See [`docs/AGENT-GUIDE.md`](./docs/AGENT-GUIDE.md) (`describe`). | `id: str` | `{"id":"sym:com.bank.chat.core.api.ChatController#joinOperator(JoinOperatorRequest)"}` |
246
246
| `resolve` | Identifier-shaped node lookup (symbol / route / client). Returns `status` `one`, `many`, or `none`; prefer over `describe(fqn=…)` when an FQN may collide. See [`docs/AGENT-GUIDE.md`](./docs/AGENT-GUIDE.md) (`resolve`). | `identifier: str`, `hint_kind: "symbol"|"route"|"client" \| null` | `{"identifier":"com.bank.chat.core.api.ChatController","hint_kind":"symbol"}` |
Copy file name to clipboardExpand all lines: docs/AGENT-GUIDE.md
+7-6Lines changed: 7 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -225,16 +225,16 @@ Exact allowed values for roles, capabilities, client kinds, etc. live in `java_o
225
225
226
226
#### `describe`
227
227
228
-
- **Purpose:** Full node payload + `edge_summary`: `in` / `out` counts **per stored graph edge label** (what exists as edges in Kuzu). For **type** Symbols only (`class`, `interface`, `enum`, `record`, `annotation`), the same map may also include **describe-time composed** dot-keys — summaries of member edges, not stored labels — see the next bullets (`DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, `DECLARES.EXPOSES`); those keys are **not** valid in `neighbors(edge_types=…)`. For **method** Symbols, the map may include **override-axis virtual keys** (`OVERRIDDEN_BY`, `OVERRIDDEN_BY.DECLARES_CLIENT`, `OVERRIDDEN_BY.DECLARES_PRODUCER`, `OVERRIDDEN_BY.EXPOSES`) plus an **`OVERRIDES` row** that merges stored `[:OVERRIDES]` incident counts with the describe-time dispatch-up rollup (per direction `max`, so inbound stored overrides are preserved); see **Override-axis keys (method Symbols)** below — those virtual keys are **not** `neighbors` arguments. The **stored** relationship label **`OVERRIDES`** **is** a valid `EdgeType` for `neighbors` (same spelling as the map key; the map row is the merged view).
228
+
- **Purpose:** Full node payload + `edge_summary`: `in` / `out` counts **per stored graph edge label** (what exists as edges in Kuzu). For **type** Symbols only (`class`, `interface`, `enum`, `record`, `annotation`), the same map may also include **describe-time composed** dot-keys — summaries of member edges, not stored labels — see the next bullets (`DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, `DECLARES.EXPOSES`); those three keys **are** valid in `neighbors(edge_types=…)` for type Symbol origins (`direction="out"` only). For **method** Symbols, the map may include **override-axis virtual keys** (`OVERRIDDEN_BY`, `OVERRIDDEN_BY.DECLARES_CLIENT`, `OVERRIDDEN_BY.DECLARES_PRODUCER`, `OVERRIDDEN_BY.EXPOSES`) plus an **`OVERRIDES` row** that merges stored `[:OVERRIDES]` incident counts with the describe-time dispatch-up rollup (per direction `max`, so inbound stored overrides are preserved); see **Override-axis keys (method Symbols)** below — those virtual keys are **not** `neighbors` arguments. The **stored** relationship label **`OVERRIDES`** **is** a valid `EdgeType` for `neighbors` (same spelling as the map key; the map row is the merged view).
229
229
-**Args:**`id` (symbol, route, or client id) or **`fqn`** (exact symbol FQN when you do not have the graph id). When both are set, `id` wins. For identifier-shaped inputs and FQN collision handling, see **Identifier resolution** above.
230
230
231
231
**Composed `edge_summary` keys (type Symbols).** Keys use dot notation: `<parent_relation>.<projected_relation>`. Three are emitted today:
232
232
233
-
-`DECLARES.DECLARES_CLIENT` — the type's methods declare brownfield HTTP clients (count is the number of `Client` rows reached through `DECLARES → DECLARES_CLIENT`). To enumerate them: `neighbors(ids=<class_id>, direction="out", edge_types=["DECLARES"])` → for each method id, `neighbors(ids=<method_id>, direction="out", edge_types=["DECLARES_CLIENT"])`.
234
-
-`DECLARES.DECLARES_PRODUCER` — the type's methods declare async producers. Same walk shape with `DECLARES_PRODUCER`.
235
-
-`DECLARES.EXPOSES` — the type's methods expose routes. Same walk shape with `EXPOSES`.
233
+
-`DECLARES.DECLARES_CLIENT` — the type's methods declare brownfield HTTP clients (count is the number of `Client` rows reached through `DECLARES → DECLARES_CLIENT`). Primary recipe: `neighbors(ids=<class_id>, direction="out", edge_types=["DECLARES.DECLARES_CLIENT"])` → terminal `Client` nodes with `via_id` in `attrs`. Alternative (two hops): `neighbors(..., ["DECLARES"])` then per-method `DECLARES_CLIENT`.
234
+
-`DECLARES.DECLARES_PRODUCER` — producers via members. Use `edge_types=["DECLARES.DECLARES_PRODUCER"]` (or the two-hop `DECLARES` → `DECLARES_PRODUCER` walk).
235
+
-`DECLARES.EXPOSES` — routes via members. Use `edge_types=["DECLARES.EXPOSES"]` (or the two-hop `DECLARES` → `EXPOSES` walk).
236
236
237
-
Composed keys are **read-only**: they cannot be passed to `neighbors(edge_types=…)` (the dot is not a valid `EdgeType` literal — the call fails with a Pydantic `ValidationError`). Use them as a hop affordance only.
237
+
These three `DECLARES.*` dot-keys are navigable on **type** Symbol origins (`out` only). `OVERRIDDEN_BY*` virtual keys remain describe-only.
238
238
239
239
Note on counting semantics: composed counts measure **edge rows**, not distinct member methods. One method that declares multiple `Client` rows (e.g. a `rest_template` method with several call sites) contributes its full edge count to `DECLARES.DECLARES_CLIENT`. The "does this class have any clients?" predicate is answered by `count > 0`; the count itself is an affordance for how rich the downstream walk will be.
240
240
@@ -248,7 +248,7 @@ Walk recipe (manual, if you need types in the middle): `neighbors(ids=<method_id
248
248
249
249
Static methods suppress the entire override-axis rollup. Constructors do not receive these keys.
250
250
251
-
Virtual keys (`OVERRIDDEN_BY`, …) and composed dot-keys are **not** valid `EdgeType` literals — `neighbors(edge_types=["OVERRIDDEN_BY"])` fails at the Pydantic boundary. Use them as hop affordances only. **`OVERRIDES`** in `edge_types=[...]` selects the **stored** override relationship (same spelling as the `OVERRIDES` map key, whose `in`/`out` merge Kuzu edges with the dispatch-up rollup).
251
+
Virtual keys (`OVERRIDDEN_BY`, …) are **not** valid `neighbors` arguments — `neighbors(edge_types=["OVERRIDDEN_BY"])` fails at the Pydantic boundary. **`OVERRIDES`** in `edge_types=[...]` selects the **stored** override relationship (same spelling as the `OVERRIDES` map key, whose `in`/`out` merge Kuzu edges with the dispatch-up rollup).
252
252
253
253
#### `resolve`
254
254
@@ -261,6 +261,7 @@ Virtual keys (`OVERRIDDEN_BY`, …) and composed dot-keys are **not** valid `Edg
261
261
-**Purpose:** One hop over explicit edge types; returns **edges** with attributes (`confidence`, `strategy`, `match`, …) and the **`other`** node.
262
262
-**Args:**`ids` (string or array — batch allowed), **`direction`** (`in`|`out`), **`edge_types`** (non-empty list), `limit`, `offset`, optional `filter` on the other node.
263
263
-**Batching:** Multiple origins are expanded; pagination slices the **combined** edge list — use larger `limit` when batching many ids.
264
+
-**Mixed flat + composed `edge_types`:** flat edges are appended before composed edges, then `limit`/`offset` apply. A small `limit` with e.g. `["DECLARES", "DECLARES.DECLARES_CLIENT"]` may return only member Symbols and no Clients — use the dot-key alone when enumerating terminals.
264
265
-**Confidence:** Cross-service edges (`HTTP_CALLS`, `ASYNC_CALLS`) carry confidence, strategy, and match metadata on `edge.attrs` (`attrs.confidence`, `attrs.strategy`, `attrs.match`). Low confidence means the resolver had to guess at the route binding — treat it as a **resolver gap signal**, not a hallucination. Report low-confidence edges with their confidence value, not as facts. Intra-service edges (`CALLS`, `INJECTS`, `IMPLEMENTS`, `EXTENDS`, `DECLARES`, `DECLARES_CLIENT`, `EXPOSES`, `OVERRIDES`) faithfully represent the static graph; the resolved set is still a **lower bound** under reflection / dynamic dispatch (see *What this MCP is NOT*).
Copy file name to clipboardExpand all lines: docs/EDGE-NAVIGATION.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -160,7 +160,7 @@
160
160
161
161
**Typical traversals**:
162
162
163
-
-`type_subject`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['EXPOSES'])
163
+
-`type_subject`: neighbors(['{id}'],'out',['DECLARES.EXPOSES']) — or neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['EXPOSES'])
-`alien_subject`: EXPOSES connects method Symbol → Route; use a method Symbol id
166
166
@@ -180,7 +180,7 @@
180
180
181
181
**Typical traversals**:
182
182
183
-
-`type_subject`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['DECLARES_CLIENT'])
183
+
-`type_subject`: neighbors(['{id}'],'out',['DECLARES.DECLARES_CLIENT']) — or neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['DECLARES_CLIENT'])
-`alien_subject`: DECLARES_CLIENT connects method Symbol → Client
186
186
@@ -200,7 +200,7 @@
200
200
201
201
**Typical traversals**:
202
202
203
-
-`type_subject`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['DECLARES_PRODUCER'])
203
+
-`type_subject`: neighbors(['{id}'],'out',['DECLARES.DECLARES_PRODUCER']) — or neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['DECLARES_PRODUCER'])
0 commit comments