Skip to content

Commit d62b48c

Browse files
rename feign_client role to client and add http_client capability (#33)
Switch Feign role inference and flow/ranking role literals to CLIENT, add HTTP_CLIENT capability detection for @FeignClient, and update docs/tool enums to reflect ontology version 9 vocabulary. Add focused rename regression coverage for graph role/capability behavior and brownfield warn-and-drop/acceptance paths. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4e2e96f commit d62b48c

8 files changed

Lines changed: 208 additions & 32 deletions

CODEBASE_REQUIREMENTS.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ Roles are assigned **first hit wins** from the type's annotations
143143
| `@Component` | `COMPONENT` |
144144
| `@Configuration` | `CONFIG` |
145145
| `@Entity`, `@MappedSuperclass`, `@Embeddable` | `ENTITY` |
146-
| `@FeignClient` | `FEIGN_CLIENT` |
146+
| `@FeignClient` | `CLIENT` (+ capability `HTTP_CLIENT`) |
147147
| `@Mapper` | `MAPPER` |
148148

149149
**Recommendations:**
@@ -159,8 +159,10 @@ Roles are assigned **first hit wins** from the type's annotations
159159
`@CodebaseRole` (see `README.md`).
160160
- **Annotate Feign clients with `@FeignClient`.** This is a
161161
**class-level** annotation; manually-coded HTTP clients (raw
162-
`RestTemplate`/`WebClient` wrappers) won't get the `FEIGN_CLIENT`
163-
boost. Consider switching to Feign or extending the role table.
162+
`RestTemplate`/`WebClient` wrappers) won't auto-promote to
163+
`CLIENT`/`HTTP_CLIENT`; use brownfield annotations
164+
(`@CodebaseRole(CodebaseRoleKind.CLIENT)` +
165+
`@CodebaseCapability(CodebaseCapabilityKind.HTTP_CLIENT)`) when needed.
164166
- **JAX-RS resources** (`@Path`, `@GET`, ...) are not recognised as
165167
`CONTROLLER` out of the box. Add them to the role table
166168
(Section B.1) or use a brownfield / `@CodebaseRole` override if you
@@ -343,8 +345,8 @@ ROLE_ANNOTATIONS: dict[str, str] = {
343345
"Entity": "ENTITY",
344346
"MappedSuperclass": "ENTITY",
345347
"Embeddable": "ENTITY",
346-
"FeignClient": "FEIGN_CLIENT",
347-
"RegisterRestClient": "FEIGN_CLIENT", # MicroProfile RestClient
348+
"FeignClient": "CLIENT",
349+
"RegisterRestClient": "CLIENT", # MicroProfile RestClient
348350
"Mapper": "MAPPER",
349351
}
350352
```

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ Resolution order for `microservice`:
103103
> 5. **`ontology_version` 8** adds `GraphMeta.cross_service_resolution` (from
104104
> `cross_service_resolution` in `.lancedb-mcp.yml`) — rebuild the Kuzu graph
105105
> (`build_ast_graph.py` or `refresh_code_index`) after upgrading.
106+
> 6. **`ontology_version` 9** renames role `FEIGN_CLIENT` to `CLIENT` and adds
107+
> capability `HTTP_CLIENT` for `@FeignClient` interfaces — rebuild to refresh
108+
> stored role/capability literals.
106109
>
107110
> Any index built before these changes must be rebuilt via
108111
> `cocoindex update ... --full-reprocess -f` or `refresh_code_index`. Until
@@ -134,7 +137,7 @@ The DB is dropped and rebuilt from scratch on each run (Phase 1 is a full rebuil
134137
| Tool | Purpose |
135138
|------|---------|
136139
| `codebase_search` | Vector / hybrid / graph-expanded search. Supports `role`, `module`, `microservice`, `package_prefix` filters, `graph_expand=true` + `expand_depth=1..3` for Kuzu-BFS fusion (RRF), and `context_neighbors=1..2` to attach adjacent chunks as `context_before`/`context_after`. Java hits return `score_components` (`distance`, `hybrid_rrf`, `role_weight`, `symbol_bonus`, `import_penalty`) so callers can see why a row ranked where it did. |
137-
| `trace_flow` | Behavioural trace from a natural-language query. Seeds via vector search, then walks CONTROLLER -> SERVICE/COMPONENT -> FEIGN_CLIENT/REPOSITORY/MAPPER in the Kuzu graph and returns staged chains. Defaults to `follow_calls=true` (merges DECLARES+CALLS paths with INJECTS/EXTENDS/IMPLEMENTS — structural edges fill `stage_limit` first per hop, CALLS tops up the remainder); set `follow_calls=false` for type-only wiring. Defaults to `exclude_external=true` on that CALLS hop (same external FQN prefixes as `find_callees` / `expand_methods`: discovered types reached via CALLS, not the caller-only filter used by `find_callers`). |
140+
| `trace_flow` | Behavioural trace from a natural-language query. Seeds via vector search, then walks CONTROLLER -> SERVICE/COMPONENT -> CLIENT/REPOSITORY/MAPPER in the Kuzu graph and returns staged chains. Defaults to `follow_calls=true` (merges DECLARES+CALLS paths with INJECTS/EXTENDS/IMPLEMENTS — structural edges fill `stage_limit` first per hop, CALLS tops up the remainder); set `follow_calls=false` for type-only wiring. Defaults to `exclude_external=true` on that CALLS hop (same external FQN prefixes as `find_callees` / `expand_methods`: discovered types reached via CALLS, not the caller-only filter used by `find_callers`). |
138141
| `list_code_index_tables` | Lance tables + Kuzu graph metadata. |
139142
| `refresh_code_index` | Rebuild LanceDB + Kuzu graph. |
140143
| `find_implementors` | Classes implementing an interface. |
@@ -208,7 +211,7 @@ Java hits are reweighted after vector / hybrid scoring by their `role`:
208211
|------|--------|
209212
| `CONTROLLER` | +0.10 |
210213
| `SERVICE` | +0.08 |
211-
| `FEIGN_CLIENT` | +0.06 |
214+
| `CLIENT` | +0.06 |
212215
| `COMPONENT` | +0.03 |
213216
| `REPOSITORY` | +0.02 |
214217
| `MAPPER` / `OTHER` | 0 |
@@ -232,6 +235,7 @@ A type can carry zero or many capabilities. Capabilities never
232235
|---|---|
233236
| `MESSAGE_LISTENER` | `@KafkaListener`, `@RabbitListener`, `@JmsListener`, `@SqsListener`, `@EventListener`, `@StreamListener` on any method |
234237
| `MESSAGE_PRODUCER` | type injects `KafkaTemplate`, `RabbitTemplate`, `JmsTemplate`, `StreamBridge`, or `ApplicationEventPublisher` |
238+
| `HTTP_CLIENT` | type has `@FeignClient` |
235239
| `SCHEDULED_TASK` | `@Scheduled` on any method, or class implements `org.quartz.Job` |
236240
| `EXCEPTION_HANDLER`| `@ControllerAdvice`, `@RestControllerAdvice`, or any method with `@ExceptionHandler` |
237241

@@ -269,6 +273,11 @@ role_overrides:
269273
270274
Unknown role or capability strings are ignored with a warning on load.
271275
276+
`@FeignClient` interfaces now auto-attach `role=CLIENT` and
277+
`capability=HTTP_CLIENT`. For `RestTemplate`/`WebClient` wrappers, opt in
278+
explicitly with `@CodebaseRole(CodebaseRoleKind.CLIENT)` and
279+
`@CodebaseCapability(CodebaseCapabilityKind.HTTP_CLIENT)`.
280+
272281
**Route overrides (`route_overrides`)** — same `.lancedb-mcp.yml` file; maps
273282
custom annotation names or qualified names (or suffixes such as `com.acme.Foo`
274283
when usage sites show only `Foo`) and per-type FQNs to `Route` fields for
@@ -330,11 +339,11 @@ package com.example.rag; // any package
330339
import java.lang.annotation.*;
331340
332341
public enum CodebaseRoleKind {
333-
CONTROLLER, SERVICE, REPOSITORY, COMPONENT, CONFIG, ENTITY, FEIGN_CLIENT, MAPPER, DTO
342+
CONTROLLER, SERVICE, REPOSITORY, COMPONENT, CONFIG, ENTITY, CLIENT, MAPPER, DTO
334343
}
335344
336345
public enum CodebaseCapabilityKind {
337-
MESSAGE_LISTENER, MESSAGE_PRODUCER, SCHEDULED_TASK, EXCEPTION_HANDLER
346+
MESSAGE_LISTENER, MESSAGE_PRODUCER, HTTP_CLIENT, SCHEDULED_TASK, EXCEPTION_HANDLER
338347
}
339348
340349
@Target(ElementType.TYPE)

ast_java.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@
7070
"EqualsAndHashCode", "ToString",
7171
})
7272

73-
# Phase 5: HTTP_CALLS + ASYNC_CALLS (B2b); Phase 6: cross-service resolution mode on GraphMeta.
73+
# Phase 5: HTTP_CALLS + ASYNC_CALLS (B2b); Phase 6: cross-service resolution mode on GraphMeta;
74+
# Phase 7: FEIGN_CLIENT role -> CLIENT + HTTP_CLIENT capability vocabulary cleanup.
7475
# Bumps whenever extraction / enrichment semantics change.
75-
ONTOLOGY_VERSION = 8
76+
ONTOLOGY_VERSION = 9
7677

7778
ROLE_ANNOTATIONS: dict[str, str] = {
7879
# Spring Web
@@ -88,7 +89,7 @@
8889
"MappedSuperclass": "ENTITY",
8990
"Embeddable": "ENTITY",
9091
# Remoting / messaging
91-
"FeignClient": "FEIGN_CLIENT",
92+
"FeignClient": "CLIENT",
9293
# Mappers
9394
"Mapper": "MAPPER",
9495
}
@@ -112,6 +113,7 @@
112113
_TYPE_ANN_TO_CAPABILITY: dict[str, str] = {
113114
"ControllerAdvice": "EXCEPTION_HANDLER",
114115
"RestControllerAdvice": "EXCEPTION_HANDLER",
116+
"FeignClient": "HTTP_CLIENT",
115117
}
116118

117119
_INJECTED_TYPES_TO_CAPABILITY: dict[str, str] = {

kuzu_queries.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ def impact_analysis(self, fqn_or_name: str, *, depth: int = 2,
994994
_FLOW_STAGES: tuple[tuple[str, ...], ...] = (
995995
("CONTROLLER",),
996996
("SERVICE", "COMPONENT"),
997-
("FEIGN_CLIENT", "REPOSITORY", "MAPPER"),
997+
("CLIENT", "REPOSITORY", "MAPPER"),
998998
)
999999

10001000
# Stage-0 accepts any entrypoint-like role. COMPONENT is included because
@@ -1003,7 +1003,7 @@ def impact_analysis(self, fqn_or_name: str, *, depth: int = 2,
10031003
# orchestrator seeds when the caller already narrowed the vector search
10041004
# to services.
10051005
_ENTRYPOINT_ROLES: tuple[str, ...] = (
1006-
"CONTROLLER", "COMPONENT", "SERVICE", "FEIGN_CLIENT",
1006+
"CONTROLLER", "COMPONENT", "SERVICE", "CLIENT",
10071007
)
10081008

10091009
def trace_flow(self, seed_fqns: list[str], *,
@@ -1013,7 +1013,7 @@ def trace_flow(self, seed_fqns: list[str], *,
10131013
follow_calls: bool = True,
10141014
min_call_confidence: float = 0.0,
10151015
exclude_external: bool = True) -> list[list[StageSymbol]]:
1016-
"""Walk stages `CONTROLLER -> SERVICE/COMPONENT -> FEIGN_CLIENT/REPOSITORY/MAPPER`.
1016+
"""Walk stages `CONTROLLER -> SERVICE/COMPONENT -> CLIENT/REPOSITORY/MAPPER`.
10171017
10181018
Returns a list of stages; each stage is a list of SymbolHit. The first
10191019
stage is the seed set (entrypoints matched by FQN, filtered to

search_lancedb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def coerce_position_field(val: object) -> dict[str, object]:
185185
_ROLE_SCORE_WEIGHTS: dict[str, float] = {
186186
"CONTROLLER": 0.10,
187187
"SERVICE": 0.08,
188-
"FEIGN_CLIENT": 0.06,
188+
"CLIENT": 0.06,
189189
"COMPONENT": 0.03,
190190
"REPOSITORY": 0.02,
191191
"MAPPER": 0.00,

server.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@
4242
"impact_analysis / neighbors / list_by_role / list_by_annotation / list_by_capability for "
4343
"exact structural traversal. "
4444
"Use list_by_capability for behavioural questions about message-driven "
45-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER), scheduled (SCHEDULED_TASK), or "
45+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER), HTTP integration (HTTP_CLIENT), scheduled (SCHEDULED_TASK), or "
4646
"exception-handling (EXCEPTION_HANDLER) code. "
47-
"Use trace_flow for CONTROLLER -> SERVICE -> REPOSITORY/FEIGN end-to-end chains; "
47+
"Use trace_flow for CONTROLLER -> SERVICE -> REPOSITORY/CLIENT end-to-end chains; "
4848
"its seeds are auto-filtered to entrypoint-like roles (CONTROLLER / COMPONENT / "
49-
"SERVICE / FEIGN_CLIENT), so it's the right tool for 'how / what happens when' queries. "
49+
"SERVICE / CLIENT), so it's the right tool for 'how / what happens when' queries. "
5050
"trace_flow defaults to follow_calls=true (DECLARES+CALLS plus cross-service HTTP_CALLS/ASYNC_CALLS "
5151
"paths merged with INJECTS/EXTENDS/"
5252
"IMPLEMENTS) and exclude_external=true on that CALLS hop (drops discovered types under "
@@ -686,13 +686,13 @@ async def codebase_search(
686686
auto_hybrid: bool = Field(default=False),
687687
role: str | None = Field(
688688
default=None,
689-
description="Java only: CONTROLLER|SERVICE|REPOSITORY|COMPONENT|CONFIG|ENTITY|FEIGN_CLIENT|MAPPER|DTO",
689+
description="Java only: CONTROLLER|SERVICE|REPOSITORY|COMPONENT|CONFIG|ENTITY|CLIENT|MAPPER|DTO",
690690
),
691691
capability: str | None = Field(
692692
default=None,
693693
description=(
694694
"Java only: AND-filter to chunks whose enclosing type carries "
695-
"this capability (MESSAGE_LISTENER|MESSAGE_PRODUCER|"
695+
"this capability (MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|"
696696
"SCHEDULED_TASK|EXCEPTION_HANDLER). Use `list_by_capability` "
697697
"for graph-only queries."
698698
),
@@ -856,7 +856,7 @@ async def find_implementors(
856856
capability: str | None = Field(
857857
default=None,
858858
description="Optional: only return symbols also carrying this capability "
859-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER).",
859+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER).",
860860
),
861861
) -> SymbolListOutput:
862862
ok, graph, msg = _require_graph()
@@ -880,7 +880,7 @@ async def find_subclasses(
880880
capability: str | None = Field(
881881
default=None,
882882
description="Optional: only return symbols also carrying this capability "
883-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER).",
883+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER).",
884884
),
885885
) -> SymbolListOutput:
886886
ok, graph, msg = _require_graph()
@@ -1138,14 +1138,14 @@ async def trace_request_flow(
11381138
description="All graph symbols with a given role (CONTROLLER|SERVICE|REPOSITORY|...).",
11391139
)
11401140
async def list_by_role(
1141-
role: str = Field(description="CONTROLLER|SERVICE|REPOSITORY|COMPONENT|CONFIG|ENTITY|FEIGN_CLIENT|MAPPER|OTHER"),
1141+
role: str = Field(description="CONTROLLER|SERVICE|REPOSITORY|COMPONENT|CONFIG|ENTITY|CLIENT|MAPPER|OTHER"),
11421142
module: str | None = Field(default=None, description="Maven/Gradle module name."),
11431143
microservice: str | None = Field(default=None, description="Microservice name."),
11441144
limit: int = Field(default=100, ge=1, le=500),
11451145
capability: str | None = Field(
11461146
default=None,
11471147
description="Optional: AND-filter to symbols also carrying this capability "
1148-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER).",
1148+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER).",
11491149
),
11501150
) -> SymbolListOutput:
11511151
ok, graph, msg = _require_graph()
@@ -1169,7 +1169,7 @@ async def list_by_annotation(
11691169
capability: str | None = Field(
11701170
default=None,
11711171
description="Optional: AND-filter to symbols also carrying this capability "
1172-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER).",
1172+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER).",
11731173
),
11741174
) -> SymbolListOutput:
11751175
ok, graph, msg = _require_graph()
@@ -1185,15 +1185,15 @@ async def list_by_annotation(
11851185
name="list_by_capability",
11861186
description=(
11871187
"All graph symbols carrying a given capability "
1188-
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER). "
1188+
"(MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER). "
11891189
"Capabilities are derived from method/type annotations and injected "
11901190
"types; a class can carry several. Pair with `list_by_role` for "
11911191
"primary-purpose questions."
11921192
),
11931193
)
11941194
async def list_by_capability(
11951195
capability: str = Field(
1196-
description="MESSAGE_LISTENER|MESSAGE_PRODUCER|SCHEDULED_TASK|EXCEPTION_HANDLER",
1196+
description="MESSAGE_LISTENER|MESSAGE_PRODUCER|HTTP_CLIENT|SCHEDULED_TASK|EXCEPTION_HANDLER",
11971197
),
11981198
module: str | None = Field(default=None, description="Maven/Gradle module name."),
11991199
microservice: str | None = Field(default=None, description="Microservice name."),
@@ -1335,11 +1335,11 @@ async def graph_meta() -> GraphMetaOutput:
13351335
description=(
13361336
"End-to-end behavioural trace for a natural-language query. "
13371337
"Picks seed entrypoints via vector search (restricted to CONTROLLER / "
1338-
"COMPONENT / SERVICE / FEIGN_CLIENT roles, plus types carrying "
1338+
"COMPONENT / SERVICE / CLIENT roles, plus types carrying "
13391339
"MESSAGE_LISTENER or SCHEDULED_TASK capabilities, with a fallback pass "
13401340
"when nothing matches), then walks the Kuzu graph in role-ordered stages "
13411341
"(CONTROLLER/COMPONENT/SERVICE -> SERVICE/COMPONENT -> "
1342-
"FEIGN_CLIENT/REPOSITORY/MAPPER) and returns the likely chain.\n"
1342+
"CLIENT/REPOSITORY/MAPPER) and returns the likely chain.\n"
13431343
"Each stage symbol carries `via: [{edge_type, from_fqn, hop}]` so "
13441344
"callers can see *why* it was pulled in (INJECTS / EXTENDS / "
13451345
"IMPLEMENTS / CALLS). Stage 0 is seeds and has `via=[]`.\n"
@@ -1415,7 +1415,7 @@ async def trace_flow(
14151415
if exclude_roles:
14161416
baseline_excludes.update(r.upper() for r in exclude_roles if r)
14171417

1418-
entry_roles = ["CONTROLLER", "COMPONENT", "SERVICE", "FEIGN_CLIENT"]
1418+
entry_roles = ["CONTROLLER", "COMPONENT", "SERVICE", "CLIENT"]
14191419
entry_capabilities = ["MESSAGE_LISTENER", "SCHEDULED_TASK"]
14201420

14211421
def _seed(role_allowlist: list[str] | None,

0 commit comments

Comments
 (0)