Skip to content

Commit cff62ff

Browse files
address pr 148 review on hints v2 plan
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3e89a53 commit cff62ff

3 files changed

Lines changed: 58 additions & 39 deletions

File tree

plans/CURSOR-PROMPTS-HINTS-V2.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,23 @@ template strings (Appendix A).
4949
## Scope
5050
5151
1. **`mcp_v2.py`** — Add `resolved_identifier` and `hints` to `ResolveOutput`
52-
(`extra="forbid"` preserved). On every `success=True` path set
53-
`resolved_identifier` to the trimmed identifier. On `success=False` set
54-
`hints=[]` and `resolved_identifier=None`. After assembling success output, build
55-
hint payload (`status`, `resolved_identifier`, `candidates`, plumbed `hint_kind`,
56-
optional `path_prefix_seed` / `target_service_seed` from existing parsers) and set
57-
`hints=generate_hints("resolve", payload)`.
52+
(`extra="forbid"` preserved). **Refactor to a unified success assembler** so wildcard
53+
early-return (`*` / `?`) no longer bypasses echo/hint wiring — today it calls
54+
`_resolve_build_output([])` directly. Every `success=True` path must set
55+
`resolved_identifier=trimmed`, build payload with `hint_kind` plus parser-derived
56+
seeds (`path_prefix_seed`, `target_service_seed` when applicable), and set
57+
`hints=generate_hints("resolve", payload)` (wildcard → `hints: []` via suppression,
58+
UC2c). On `success=False`: `hints=[]`, `resolved_identifier=None`.
5859
2. **`mcp_hints.py`** — Extend `generate_hints` for `output_kind == "resolve"` with
5960
the four templates and rules in `propose/HINTS-V2-PROPOSE.md` Appendix A (120-char
6061
drop-on-overflow, wildcard suppression, seed suppression). Use `PRIORITY_META`.
6162
3. **`README.md`** — Document `resolve` `hints` + `resolved_identifier` under MCP v2
6263
response extras; link v2 propose.
6364
4. **`server.py`** — Minimal `resolve` tool description mention of advisory `hints`.
6465
5. **Tests** — Implement every `test_*` name listed under **Tests for PR-A** in
65-
`plans/PLAN-HINTS-V2.md` (verbatim names). Assert hint presence via substrings, not
66-
full-string equality.
66+
`plans/PLAN-HINTS-V2.md` (verbatim names). Reuse graph-discovery helpers in
67+
`tests/test_mcp_hints.py` for round-trip (fail loud, no unconditional skip). Assert
68+
hint presence via substrings, not full-string equality.
6769
6870
## Out of scope (do NOT touch)
6971
@@ -153,9 +155,11 @@ Read **Tests for PR-B** and propose §3.2 / Appendix A before coding.
153155
3. **`README.md`** — Document neighbors fuzzy-strategy hint under MCP v2 extras.
154156
4. **`server.py`** — Only if needed: brief mention that edge `attrs.strategy` indicates
155157
resolution quality.
156-
5. **Tests** — Implement all `test_*` names under **Tests for PR-B** in the plan
157-
(verbatim). Craft payloads for unit tests; round-trip via `neighbors_v2` + graph
158-
(discover fuzzy edge with Cypher — avoid unconditional skip).
158+
5. **Tests** — Implement all **new** `test_*` names under **Tests for PR-B** in the plan
159+
(verbatim). Do **not** add a duplicate UC11 test — existing
160+
`test_hints_neighbors_empty_with_edge_types_emits_kind_check` covers empty neighbors.
161+
Craft payloads for unit tests; round-trip via `neighbors_v2` + graph using helpers in
162+
`tests/test_mcp_hints.py` or Tier-2 `call_graph_smoke` (fail loud, no skip).
159163
160164
## Out of scope (do NOT touch)
161165
@@ -175,7 +179,7 @@ Read **Tests for PR-B** and propose §3.2 / Appendix A before coding.
175179
176180
```bash
177181
.venv/bin/ruff check java_ontology.py mcp_hints.py tests/test_mcp_hints.py
178-
.venv/bin/python -m pytest tests/test_mcp_hints.py -v -k "neighbors and fuzzy or neighbors_empty"
182+
.venv/bin/python -m pytest tests/test_mcp_hints.py -v -k "fuzzy_strategy or neighbors_empty"
179183
```
180184
181185
Before PR open:

plans/PLAN-HINTS-V2.md

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

136142
12. `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

141150
Optional 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`):
209218
4. `test_hints_neighbors_declares_no_strategy_attrs_empty` — UC9
210219
5. `test_hints_neighbors_multi_origin_fuzzy_emits_once` — UC10 (single hint string)
211220
6. `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

216226
Integration:
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.
273285
3. README documents v2 behavior; v1 hints on the original four tools are unchanged.
274286
4. 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

propose/HINTS-V2-PROPOSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# HINTS-V2 — extend hints to `resolve` and to edge-attribute-driven `neighbors` signals
22

3-
**Status**: draft
3+
**Status**: approved (plan: [`plans/PLAN-HINTS-V2.md`](../plans/PLAN-HINTS-V2.md); move to `propose/completed/` when PR-A and PR-B land)
44
**Author**: Dmitriy Teriaev + Perplexity Computer
55
**Date**: 2026-05-16
66

0 commit comments

Comments
 (0)