Skip to content

Commit c36b60f

Browse files
authored
Merge pull request #131 from HumanBean17/feat/filter-frame-vocabulary
feat: filter frame vocabulary renames (PR-FRAME-1)
2 parents f02dff0 + 08b01ef commit c36b60f

3 files changed

Lines changed: 47 additions & 5 deletions

File tree

docs/AGENT-GUIDE.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ One object shape everywhere. **For `find`, `filter` is required** — use at lea
150150
| `microservice`, `module`, `source_layer` | All kinds (`source_layer` mainly **client**: `builtin` / brownfield) |
151151
| `role`, `exclude_roles`, `annotation`, `capability`, `fqn_prefix`, `symbol_kind`, `symbol_kinds` | **symbol** (ignored for route/client) |
152152
| `http_method`, `path_prefix`, `framework` | **route** |
153-
| `client_kind`, `target_service`, `target_path_prefix`, `client_method` | **client** |
153+
| `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
154+
155+
The same `http_method` key filters HTTP verbs on **routes** (server-side declared method) and on **clients** (caller-side method on the outbound call). It is not applicable to **symbol** rows.
156+
157+
**`source_layer` vs `role`:** On **Client** nodes, `source_layer` records which brownfield or built-in layer produced the client declaration (`builtin`, `layer_a_meta`, `layer_b_ann`, `layer_c_source`, `layer_b_fqn`, …). On **Symbol** nodes, `role` is the inferred architectural stereotype (`CONTROLLER`, `SERVICE`, `REPO`, …). They answer different questions; names stay distinct.
158+
159+
**`target_service` vs `microservice`:** `microservice` is the service **where the node lives** (home service / owning module). `target_service` (clients only) is the **remote service being called**. A client in `operator-api` may list `partner-api` as `target_service`.
154160

155161
Exact allowed values for roles, capabilities, client kinds, etc. live in `java_ontology.py`.
156162

mcp_v2.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class NodeFilter(BaseModel):
7575
client_kind: str | None = None
7676
target_service: str | None = None
7777
target_path_prefix: str | None = None
78-
client_method: str | None = None
7978

8079

8180
_NODEFILTER_FIELD_ORDER: tuple[str, ...] = tuple(NodeFilter.model_fields.keys())
@@ -106,7 +105,7 @@ class NodeFilter(BaseModel):
106105
"client_kind",
107106
"target_service",
108107
"target_path_prefix",
109-
"client_method",
108+
"http_method",
110109
),
111110
}
112111

@@ -475,7 +474,7 @@ def _node_matches_filter(kind: Literal["symbol", "route", "client"], row: dict[s
475474
path = str(row.get("path") or "")
476475
if not path.startswith(f.target_path_prefix):
477476
return False
478-
if f.client_method and str(row.get("method") or "") != f.client_method:
477+
if f.http_method and str(row.get("method") or "") != f.http_method:
479478
return False
480479
return True
481480

@@ -581,7 +580,7 @@ def find_v2(
581580
client_kind=nf.client_kind,
582581
target_service=nf.target_service,
583582
path_prefix=nf.target_path_prefix,
584-
method=nf.client_method,
583+
method=nf.http_method,
585584
limit=max(500, limit + offset),
586585
)
587586
rows = [r for r in rows if _node_matches_filter("client", r, nf)]

tests/test_mcp_v2.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,43 @@ def test_nodefilter_applicability_table_covers_all_fields() -> None:
238238
assert declared == covered
239239

240240

241+
def test_http_method_field_applies_to_route_kind(kuzu_graph) -> None:
242+
routes = kuzu_graph.list_routes(limit=2000)
243+
post_ids = {str(r["id"]) for r in routes if str(r.get("method") or "").upper() == "POST"}
244+
if not post_ids:
245+
pytest.skip("fixture has no POST routes")
246+
out = find_v2("route", {"http_method": "POST"}, graph=kuzu_graph, limit=500)
247+
assert out.success is True
248+
assert out.results
249+
assert {r.id for r in out.results} <= post_ids
250+
assert all(r.fqn == "POST" or r.fqn.startswith("POST ") for r in out.results)
251+
252+
253+
def test_http_method_field_applies_to_client_kind(kuzu_graph) -> None:
254+
clients = kuzu_graph.list_clients(limit=2000)
255+
post_ids = {str(r["id"]) for r in clients if str(r.get("method") or "").upper() == "POST"}
256+
if not post_ids:
257+
pytest.skip("fixture has no POST clients")
258+
out = find_v2("client", {"http_method": "POST"}, graph=kuzu_graph, limit=500)
259+
assert out.success is True
260+
assert out.results
261+
assert {r.id for r in out.results} <= post_ids
262+
263+
264+
def test_http_method_field_inapplicable_to_symbol(kuzu_graph) -> None:
265+
out = find_v2("symbol", {"http_method": "POST"}, graph=kuzu_graph)
266+
assert out.success is False
267+
assert out.message is not None
268+
assert "http_method" in out.message
269+
assert "kind='symbol'" in out.message
270+
271+
272+
def test_nodefilter_rejects_old_client_method_field() -> None:
273+
with pytest.raises(ValidationError) as excinfo:
274+
NodeFilter.model_validate({"client_method": "POST"})
275+
assert any("client_method" in str(e.get("loc", ())) for e in excinfo.value.errors())
276+
277+
241278
async def test_find_missing_filter_rejected(mcp_server) -> None:
242279
with pytest.raises(ToolError, match="Field required"):
243280
await mcp_server.call_tool("find", {"kind": "symbol"})

0 commit comments

Comments
 (0)