@@ -55,7 +55,7 @@ Depends on: **none** (v1 hints on `search` / `find` / `describe` / `neighbors` a
5555| ` resolved_identifier ` | Set on every ` success=True ` response to post-validation trimmed identifier; ` None ` on ` success=False ` . |
5656| Resolve hints fire on | ` status: none ` and ` status: many ` only; ` status: one ` → ` hints: [] ` . |
5757| ` hint_kind ` default | ` None ` → symbol branch (` search(query=…) ` template). |
58- | Wildcards in identifier | ` * ` / ` ? ` in ` resolved_identifier ` suppress resolve hints (UC2c); do not emit ` search(query='*') ` . |
58+ | Wildcards in identifier | ` * ` / ` ? ` in ` resolved_identifier ` suppress resolve hints (UC2c); do not emit ` search(query='*') ` . Unified success assembler sets echo + runs ` generate_hints ` on wildcard paths too. |
5959| ` FUZZY_STRATEGY_SET ` location | ` java_ontology.py ` ; ` mcp_hints.py ` imports it. Locked members per propose §3.2. |
6060| ` layer_b_ann ` vs ` layer_b_fqn ` | ` _ann ` is primary (not in fuzzy set); ` _fqn ` is fuzzy. |
6161| Truncated candidate cap | Hint says ` {n} candidates ` with ` n = len(candidates) ` ; no ` truncated ` flag on output (§5 carve-out). |
@@ -75,18 +75,24 @@ Depends on: **none** (v1 hints on `search` / `find` / `describe` / `neighbors` a
7575 ` None ` when ` success=False ` .
7676 - ` hints: list[str] = Field(default_factory=list, description=MCP_HINTS_FIELD_DESCRIPTION) ` .
7777- Preserve ` extra="forbid" ` .
78- - Refactor success-path assembly so every ` success=True ` output sets
79- ` resolved_identifier ` to the trimmed request identifier (including ` status: none `
80- from wildcards and no-match).
81- - After building the success output, compute hint payload and assign ` hints ` :
78+ - ** Refactor: unified success assembler** — Today ` resolve_v2 ` wildcard identifiers
79+ (` * ` / ` ? ` ) early-return via ` _resolve_build_output([]) ` and never set echo fields.
80+ Replace scattered success returns with one helper (e.g.
81+ ` _resolve_finalize_success(trimmed, hint_kind, matches, graph) -> ResolveOutput ` ) that:
82+ 1 . Builds ` status ` / ` node ` / ` candidates ` / ` message ` (same semantics as today).
83+ 2 . Always sets ` resolved_identifier=trimmed ` on ` success=True ` (wildcard, empty-match,
84+ one, many — all paths).
85+ 3 . Builds the hint payload and sets ` hints=generate_hints("resolve", payload) ` .
86+ Wildcard ` status: none ` must ** not** bypass the assembler; hints stay ` [] ` via
87+ ` generate_hints ` suppression (UC2c), not a separate shortcut.
88+ - ** Hint payload on every success path** (including wildcard and empty-match):
8289 - Echo: ` status ` , ` resolved_identifier ` , ` candidates ` .
83- - Plumbed (not on model): ` hint_kind ` , optional ` path_prefix_seed ` ,
84- ` target_service_seed ` .
85- - Seeds: reuse ` _resolve_parse_route_method_path ` (path from method+path identifier)
86- and ` _resolve_parse_microservice_route ` / client parsers consistent with
87- ` _resolve_client_candidates ` (microservice token or ` target path ` split) — match
88- propose §3.1.2 semantics; parser logic stays here, not in ` mcp_hints.py ` .
89- - Call ` generate_hints("resolve", payload) ` only when ` success=True ` .
90+ - Plumbed (not on model): ` hint_kind ` (request param — today only used for search
91+ routing), optional ` path_prefix_seed ` , ` target_service_seed ` .
92+ - Seeds: compute on every success return using existing parsers
93+ (` _resolve_parse_route_method_path ` , ` _resolve_parse_microservice_route ` , and client
94+ ` target ` / ` target path ` split consistent with ` _resolve_client_candidates ` ) — match
95+ propose §3.1.2; parser logic stays here, not in ` mcp_hints.py ` .
9096- ` success=False ` paths (validation error, exceptions): ` hints=[] ` ,
9197 ` resolved_identifier=None ` (Decision §7.15 — validation rejection gets no v2 hint).
9298
@@ -135,8 +141,11 @@ Integration:
135141
13614212 . ` test_hints_resolve_v2_round_trip ` — ` resolve_v2 ` against ` kuzu_graph ` : assert
137143 ` hints ` present for a known ` status: none ` symbol identifier and empty for a known
138- ` status: one ` ; assert ` resolved_identifier ` echoed on success; assert validation
139- failure yields ` hints == [] ` and ` resolved_identifier is None ` .
144+ ` status: one ` ; assert ` resolved_identifier ` echoed on success (including wildcard
145+ ` status: none ` with ` hints == [] ` ); assert validation failure yields ` hints == [] `
146+ and ` resolved_identifier is None ` . Reuse existing discovery helpers in
147+ ` tests/test_mcp_hints.py ` (` _route_id ` , ` _client_id ` , symbol/route Cypher patterns)
148+ — fail loud with ` pytest.fail ` if fixture data missing; no unconditional ` skip ` .
140149
141150Optional hygiene (same PR if quick):
142151
@@ -161,7 +170,7 @@ Optional hygiene (same PR if quick):
161170| --- | --- | --- | --- |
162171| 1 | Add model fields | ` mcp_v2.py ` | Pydantic schema validates |
163172| 2 | Implement resolve branch + templates | ` mcp_hints.py ` | Unit tests 1–11 pass |
164- | 3 | Wire ` resolve_v2 ` payload + ` resolved_identifier ` | ` mcp_v2.py ` | Round -trip test passes |
173+ | 3 | Add unified success assembler; thread ` trimmed ` , ` hint_kind ` , seeds into payload on every ` success=True ` path (wildcard included) | ` mcp_v2.py ` | Wildcard + lookup paths echo ` resolved_identifier ` ; round -trip passes |
165174| 4 | Docs / server description | ` README.md ` , ` server.py ` | Copy matches behavior |
166175| 5 | Add tests | ` tests/test_mcp_hints.py ` | All PR-A tests green |
167176
@@ -209,17 +218,19 @@ Pure `generate_hints("neighbors", …)` (craft `results` with `attrs.strategy`):
2092184 . ` test_hints_neighbors_declares_no_strategy_attrs_empty ` — UC9
2102195 . ` test_hints_neighbors_multi_origin_fuzzy_emits_once ` — UC10 (single hint string)
2112206 . ` test_hints_neighbors_layer_a_meta_no_fuzzy_hint ` — UC17
212- 7 . ` test_hints_neighbors_empty_with_edge_types_still_emits_kind_check ` — UC11 regression
213- (may already exist as ` test_hints_neighbors_empty_with_edge_types_emits_kind_check ` —
214- extend or duplicate only if PR-B changes branch ordering)
221+
222+ ** UC11 regression (existing v1 test — do not add a duplicate):** after PR-B, re-run
223+ ` test_hints_neighbors_empty_with_edge_types_emits_kind_check ` unchanged; only touch it if
224+ branch ordering breaks the empty-result path.
215225
216226Integration:
217227
218- 8 . ` test_hints_neighbors_fuzzy_strategy_neighbors_v2_round_trip ` — call ` neighbors_v2 `
219- on a graph edge known to carry a fuzzy ` strategy ` (discover via Cypher helper over
220- ` kuzu_graph ` or Tier-2 ` call_graph_smoke ` session); assert fuzzy hint substring in
221- ` out.hints ` . Prefer ** no** unconditional ` pytest.skip ` — use a helper that fails with
222- a clear message if the chosen fixture lacks fuzzy edges.
228+ 7 . ` test_hints_neighbors_fuzzy_strategy_neighbors_v2_round_trip ` — call ` neighbors_v2 `
229+ on a graph edge known to carry a fuzzy ` strategy ` . Reuse or extend helpers in
230+ ` tests/test_mcp_hints.py ` (` _method_declares_client ` , ` _class_symbol_id ` , Cypher
231+ discovery for ` e.strategy IN FUZZY_STRATEGY_SET ` ) or Tier-2 ` call_graph_smoke ` session
232+ for ` phantom ` / ` implicit_super ` on ` CALLS ` . Fail loud if fixture lacks a fuzzy edge;
233+ no unconditional ` pytest.skip ` .
223234
224235## Definition of done (PR-B)
225236
@@ -235,8 +246,8 @@ Integration:
235246| # | Step | File(s) | Done when |
236247| --- | --- | --- | --- |
237248| 1 | Add ontology set | ` java_ontology.py ` | Importable constant |
238- | 2 | Template + ` _any_fuzzy_strategy ` + neighbors branch | ` mcp_hints.py ` | Unit tests 1–7 pass |
239- | 3 | Round-trip neighbors test | ` tests/test_mcp_hints.py ` | Test 8 passes |
249+ | 2 | Template + ` _any_fuzzy_strategy ` + neighbors branch | ` mcp_hints.py ` | Unit tests 1–6 pass |
250+ | 3 | Round-trip neighbors test | ` tests/test_mcp_hints.py ` | Test 7 passes |
240251| 4 | README / optional server | ` README.md ` , ` server.py ` | Docs match |
241252
242253---
@@ -246,7 +257,8 @@ Integration:
246257| # | Risk | Severity | Mitigation |
247258| --- | --- | --- | --- |
248259| 1 | Resolve hint duplicates generic ` message ` prose | Low | Intentional dual channel; hint embeds verbatim ` resolved_identifier ` (Decision §7.22). Tests use substrings, not full equality. |
249- | 2 | Missing payload plumbing → silent ` hints: [] ` | Medium | ` test_hints_resolve_payload_missing_identifier_suppressed ` + round-trip asserts ` resolved_identifier ` on success. |
260+ | 2 | Missing payload plumbing → silent ` hints: [] ` | Medium | Unified success assembler; ` test_hints_resolve_payload_missing_identifier_suppressed ` + round-trip asserts ` resolved_identifier ` on success (wildcard included). |
261+ | 2b | Wildcard early-return bypasses assembler | Medium | PR-A refactor: no direct ` _resolve_build_output([]) ` return without ` resolved_identifier ` + ` generate_hints ` . |
250262| 3 | Fuzzy hint noise in brownfield-heavy repos | Low | Meta-tier drops first under cap; single terse template. |
251263| 4 | ` FUZZY_STRATEGY_SET ` drifts from pipeline literals | Medium | Issue #147 follow-up; document locked set in ontology; code review checks new strategies. |
252264| 5 | PR-B merged before PR-A | Low | Enforce landing order; PR-B does not touch ` ResolveOutput ` . |
@@ -272,11 +284,14 @@ Integration:
272284 ` attrs.strategy ∈ FUZZY_STRATEGY_SET ` ; ontology set is the single source of truth.
2732853 . README documents v2 behavior; v1 hints on the original four tools are unchanged.
2742864 . Default test suite green without heavy env vars.
287+ 5 . ` propose/HINTS-V2-PROPOSE.md ` moved to ` propose/completed/ ` (whole effort landed).
275288
276289# Tracking
277290
278291- ` PR-A ` : _ pending_
279292- ` PR-B ` : _ pending_
293+ - ` #147 ` (strategy classification CI invariant): _ pending_ chore — out of PR-A/B scope;
294+ file after PR-B or in parallel.
280295
281296## Cursor handoff
282297
0 commit comments