Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions agents/explorer-rag-enhanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,15 @@ Simple types in parentheses; generics erased. No spaces after commas. No-arg: `(

### Shared NodeFilter

For `find`, `filter` is required — `{}` means no predicates. **Strict frame:** unknown keys or inapplicable populated fields → `success=false`.
For `find`, `filter` is required — `{}` means no predicates. **Strict frame:** unknown keys or inapplicable populated fields → `success=false`; invalid enum values (e.g. wrong case) are rejected earlier at the schema layer with the valid set listed.

| Keys | Applies to |
| ---- | ---------- |
| `microservice`, `module` | All kinds |
| `role`, `exclude_roles`, `annotation`, `capability`, `fqn_prefix`, `symbol_kind`, `symbol_kinds` | **symbol** |
| `http_method`, `path_prefix`, `framework` | **route** |
| `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
| `producer_kind`, `topic_prefix` | **producer** |
| `source_layer`, `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
| `source_layer`, `producer_kind`, `topic_prefix` | **producer** |

No wildcards in prefix fields — use `search(query=…)` for fuzzy text.

Expand Down
29 changes: 23 additions & 6 deletions ast_java.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from __future__ import annotations

import posixpath
import sys
from dataclasses import dataclass, field
from functools import lru_cache
from typing import Iterable
Expand Down Expand Up @@ -1642,9 +1643,17 @@ def _parse_codebase_http_client_annotation(
pairs, _ = _annotation_kv_nodes(ann, src)
client_kind = ""
if "clientKind" in pairs:
val, _kind = _annotation_value(pairs["clientKind"], src)
if val and _kind == "enum":
client_kind = str(val)
val, vkind = _annotation_value(pairs["clientKind"], src)
if val and vkind == "enum":
kind_val = str(val)
from java_ontology import VALID_CLIENT_KINDS # deferred: java_ontology imports ast_java
if kind_val in VALID_CLIENT_KINDS:
client_kind = kind_val
else:
print(
f"[lancedb-mcp] CodebaseHttpClient: invalid clientKind {kind_val!r} — ignored",
file=sys.stderr,
)
target_service = ""
if "targetService" in pairs:
atoms = _string_value_atoms(pairs["targetService"], src, ctx)
Expand Down Expand Up @@ -1714,9 +1723,17 @@ def _parse_codebase_producer_annotation(
client_kind = "kafka_send"
kind_node = pairs.get("producerKind") or pairs.get("clientKind")
if kind_node is not None:
val, _kind = _annotation_value(kind_node, src)
if val and _kind == "enum":
client_kind = str(val)
val, vkind = _annotation_value(kind_node, src)
if val and vkind == "enum":
kind_val = str(val)
from java_ontology import VALID_PRODUCER_KINDS # deferred: java_ontology imports ast_java
if kind_val in VALID_PRODUCER_KINDS:
client_kind = kind_val
else:
print(
f"[lancedb-mcp] CodebaseProducer: invalid producerKind {kind_val!r} — ignored",
file=sys.stderr,
)
topic = ""
if "topic" in pairs:
atoms = _string_value_atoms(pairs["topic"], src, ctx)
Expand Down
10 changes: 6 additions & 4 deletions docs/AGENT-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ For **`find`**, `filter` is required — `{}` means no predicates (all nodes of
| `microservice`, `module` | All kinds |
| `role`, `exclude_roles`, `annotation`, `capability`, `fqn_prefix`, `symbol_kind`, `symbol_kinds` | **symbol** |
| `http_method`, `path_prefix`, `framework` | **route** |
| `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
| `producer_kind`, `topic_prefix` | **producer** |
| `source_layer`, `client_kind`, `target_service`, `target_path_prefix`, `http_method` | **client** |
| `source_layer`, `producer_kind`, `topic_prefix` | **producer** |

`http_method` filters HTTP verbs on **routes** (declared method) and on **clients** (outbound call method). Not applicable to **symbol** rows.

**Strict frame:** one populated field → one stored attribute for that kind. Unknown keys or inapplicable populated fields → `success=false` with a teaching `message`. No wildcards in `fqn_prefix`, `path_prefix`, or `target_path_prefix` (`*` / `?` rejected) — use `search(query=…)` for ranked text instead. `search.query` is opaque text, not a DSL.
**Strict frame:** one populated field → one stored attribute for that kind. Unknown keys or inapplicable populated fields → `success=false` with a teaching `message`. Invalid enum values (e.g. wrong case) are rejected earlier at the schema layer with the valid set listed. No wildcards in `fqn_prefix`, `path_prefix`, or `target_path_prefix` (`*` / `?` rejected) — use `search(query=…)` for ranked text instead. `search.query` is opaque text, not a DSL.

### Identifier resolution (`resolve`)

Expand Down Expand Up @@ -245,12 +245,14 @@ Returns **edges** with `attrs` (`confidence`, `strategy`, `match`, … on cross-

**Symbol kinds (`symbol_kind` / `symbol_kinds`):** `class`, `interface`, `enum`, `record`, `annotation`, `method`, `constructor`.

**Route `framework` (examples on stored routes):** `spring_mvc`, `webflux`, `kafka`, `rabbitmq`, `jms`, `stream`, `codebase_async_route`, …
**Route `framework` (closed set on stored routes):** `spring_mvc`, `webflux`, `kafka`, `rabbitmq`, `jms`, `stream`, `feign`.

**Client kinds:** `feign_method`, `rest_template`, `web_client`.

**Producer kinds:** `kafka_send`, `stream_bridge_send`.

**Source layers (client/producer):** `builtin`, `layer_a_meta`, `layer_b_ann`, `layer_b_fqn`, `layer_c_source`.

**HTTP call `attrs.match` / async `attrs.match`:** `cross_service`, `intra_service`, `ambiguous`, `phantom`, `unresolved`.

### Recovery playbook
Expand Down
5 changes: 4 additions & 1 deletion java_ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
_TYPE_ANN_TO_CAPABILITY,
)

# Roles: Spring stereotype values plus DTO from `infer_role_for_type`.
# Roles assignable by indexing: Spring stereotype values plus DTO. ``OTHER`` is the
# built-in inference fallback (ast_java.infer_role when nothing matches) and is
# deliberately excluded here — it is a read-side value (the mcp_v2 ``Role`` enum
# includes it) but not a role a user may set via @CodebaseRole / role_overrides.
VALID_ROLES: frozenset[str] = frozenset((*ROLE_ANNOTATIONS.values(), "DTO"))

VALID_CAPABILITIES: frozenset[str] = frozenset(
Expand Down
45 changes: 35 additions & 10 deletions mcp_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ def _hints_or_skip(tool: str, payload: dict) -> tuple[list, list]:

DeclarationSymbolKind = Literal["class", "interface", "enum", "record", "annotation", "method", "constructor"]

# Closed value taxonomies surfaced to MCP consumers as enums. Sources of truth:
# Role — VALID_ROLES in java_ontology.py + the "OTHER" inference fallback (ast_java.infer_role)
# Framework — hardcoded literals across ast_java.py / build_ast_graph.py
# SourceLayer — exhaustive classifier build_ast_graph._client_source_layer / _producer_source_layer
# ClientKind — VALID_CLIENT_KINDS in java_ontology.py (every producer validated at index time)
# ProducerKind — VALID_PRODUCER_KINDS in java_ontology.py (every producer validated at index time)
# Keep these in sync with the indexing-side taxonomies if they change.
Role = Literal[
"CONTROLLER", "SERVICE", "REPOSITORY", "COMPONENT", "CONFIG",
"ENTITY", "CLIENT", "MAPPER", "DTO", "OTHER",
]
Framework = Literal["spring_mvc", "webflux", "kafka", "rabbitmq", "jms", "stream", "feign", ""]
SourceLayer = Literal["builtin", "layer_a_meta", "layer_b_ann", "layer_b_fqn", "layer_c_source"]
ClientKind = Literal["feign_method", "rest_template", "web_client"]
ProducerKind = Literal["kafka_send", "stream_bridge_send"]

# Stored graph edge labels for one-hop neighbors. Composed DECLARES.* and OVERRIDDEN_BY.*
# dot-keys are separate ComposedEdgeType literals (2-hop traversal). Stored OVERRIDES is an EdgeType.
EdgeType = Literal[
Expand Down Expand Up @@ -133,21 +149,30 @@ class NodeFilter(BaseModel):

microservice: str | None = None
module: str | None = None
source_layer: str | None = None
role: str | None = None
exclude_roles: list[str] | None = None
source_layer: SourceLayer | None = None
role: Role | None = None
exclude_roles: list[Role] | None = None
annotation: str | None = None
capability: str | None = None
fqn_prefix: str | None = None
symbol_kind: DeclarationSymbolKind | None = None
symbol_kinds: list[DeclarationSymbolKind] | None = None
http_method: str | None = None
http_method: str | None = Field(
default=None,
description="HTTP verb (commonly GET/POST/PUT/DELETE/PATCH; user route annotations may yield others).",
)
path_prefix: str | None = None
framework: str | None = None
client_kind: str | None = None
framework: Framework | None = None
client_kind: ClientKind | None = Field(
default=None,
description="Outbound HTTP client kind: feign_method, rest_template, or web_client.",
)
target_service: str | None = None
target_path_prefix: str | None = None
producer_kind: str | None = None
producer_kind: ProducerKind | None = Field(
default=None,
description="Outbound async producer kind: kafka_send or stream_bridge_send.",
)
topic_prefix: str | None = None


Expand All @@ -157,9 +182,9 @@ class EdgeFilter(BaseModel):
min_confidence: float | None = None
exclude_strategies: list[str] | None = None
include_strategies: list[str] | None = None
callee_declaring_role: str | None = None
callee_declaring_roles: list[str] | None = None
exclude_callee_declaring_roles: list[str] | None = None
callee_declaring_role: Role | None = None
callee_declaring_roles: list[Role] | None = None
exclude_callee_declaring_roles: list[Role] | None = None

@model_validator(mode="after")
def _strategy_axes_mutually_exclusive(self) -> EdgeFilter:
Expand Down
Loading
Loading