diff --git a/plans/CURSOR-PROMPTS-HINTS-V4.md b/plans/CURSOR-PROMPTS-HINTS-V4.md new file mode 100644 index 0000000..92b28b6 --- /dev/null +++ b/plans/CURSOR-PROMPTS-HINTS-V4.md @@ -0,0 +1,193 @@ +# Cursor task prompts — HINTS-V4 + +Status: **active**. Plan: +[`plans/PLAN-HINTS-V4.md`](./PLAN-HINTS-V4.md). Propose: +[`propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md`](../propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md). + +**Depends on:** NEIGHBORS-DOT-KEY ([#171](https://github.com/HumanBean17/java-codebase-rag/pull/171)) on `master`. + +**Propose lock:** Locked in planning PR [#174](https://github.com/HumanBean17/java-codebase-rag/pull/174); must remain locked before PR-A **merge**. + +**Universal rules:** + +- Use `.venv/bin/python` and `.venv/bin/ruff` only. +- No stdout from MCP handlers. +- Do not expand scope beyond the plan. +- Do not push git unless the user asked. + +--- + +## PR-HINTS-V4-A — `neighbors` success-path catalog (N1a–N7) + +**Branch:** `feat/hints-v4-neighbors-success` off `master`. +**Base:** `master` (with #171 merged). +**Plan section:** [`plans/PLAN-HINTS-V4.md`](./PLAN-HINTS-V4.md) § PR-A. +**PR title:** `feat(hints): v4 success-path neighbors road signs (N1a–N7)` + +**Attach (`@-files`):** + +- `@plans/PLAN-HINTS-V4.md` +- `@propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md` +- `@propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md` (priority cap §7.12) +- `@propose/completed/HINTS-V2-PROPOSE.md` (no per-row neighbors hints) +- `@propose/completed/HINTS-V3-PROPOSE.md` (empty neighbors; dot-key prohibition) +- `@propose/completed/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.md` (dot-key context) +- `@mcp_hints.py` +- `@mcp_v2.py` (`neighbors_v2` payload — read only unless field missing) +- `@tests/test_mcp_hints.py` + +**Prompt:** + +```` +You are implementing PR-HINTS-V4-A from `plans/PLAN-HINTS-V4.md`. + +Confirm `propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md` is **Status: locked** before merge. + +## Scope + +1. **`mcp_hints.py`** + - Add v4 neighbors success templates N2–N7 (verbatim strings in plan). + - N1a/N1b: reuse `TPL_DESCRIBE_TYPE_CLIENTS_VIA_MEMBERS` and `TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERS` — do not fork wording. + - Implement `neighbors_success_hints(payload)` per propose trigger contract (single edge type, offset 0, homogeneous `other`, char cap 120). + - **N1a/N1b type gate:** flat Kuzu `subject_record` from `neighbors_v2` — `_subject_node_label == "Symbol"` and top-level `subject_record["kind"] in _TYPE_SYMBOL_KINDS` (same as v3 `neighbors_empty_hints`; **not** nested `data.kind` / `NodeRecord.model_dump()`). + - **Homogeneity:** method + constructor on one page = OK (both ∈ `_METHOD_SYMBOL_KINDS`); method + type Symbol = silent. + - Wire `generate_hints("neighbors", …)`: call success helper on non-empty `results`; keep empty + fuzzy paths unchanged. + - **Critical:** `_filter_neighbors_dotkey_hints` applies to **empty structural pairs only** — success-path N1a/N1b must retain `DECLARES.*` dot-keys. + - Module docstring: reference HINTS-V4 propose. + +2. **`tests/test_mcp_hints.py`** + - Implement every test name under **Tests for PR-A** in `plans/PLAN-HINTS-V4.md` (including N6 and **required** `test_hints_neighbors_v2_declares_success_emits_dot_key_clients`). + - Synthetic `subject_record`: flat `{"id": "sym:…", "kind": "class"}` — **not** `NodeRecord.model_dump()`. + - Narrow `test_hints_hv20_no_dotkey_edge_labels_in_rendered_neighbors_hints` to **empty** payloads only. + - Add `test_hints_neighbors_success_may_emit_declares_dot_keys` (synthetic flat subject). + - Add `test_hints_all_v4_templates_under_120_chars` (parametrize templates + realistic ids). + +## Out of scope (do NOT touch) + +- `mcp_v2.py` (unless payload field genuinely missing — unexpected). +- `build_ast_graph.py`, `java_ontology.py`, `ONTOLOGY_VERSION`, `EDGE_SCHEMA`. +- `find` / `search` success hints (PR-B). +- `describe` / `resolve` catalog rows. +- `MCP_HINTS_FIELD_DESCRIPTION`, `README.md`, `server.py`. +- `propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md` (PR-B appendix). +- Per-row hints; ontology bump. + +## Deliverables + +1. N1a–N7 success-path hints on matching non-empty `neighbors` payloads. +2. Dot-key filter split: empty structural never has dot-keys; success N1a/N1b may. +3. All PR-A named tests pass. + +## Tests to run + +```bash +.venv/bin/ruff check mcp_hints.py tests/test_mcp_hints.py +.venv/bin/python -m pytest tests/test_mcp_hints.py -v -k "hints_neighbors or hints_all_v4 or hv20 or v2_declares_success" +``` + +Before PR open: + +```bash +.venv/bin/ruff check . +.venv/bin/python -m pytest tests -v +``` + +## Sentinel checks (`git diff master..HEAD` — zero matches) + +- `ONTOLOGY_VERSION` +- `build_ast_graph.py` (unless accidental touch — revert) +- `TPL_FIND_SUCCESS` / `find_success_hints` (PR-B) +- `TPL_SEARCH_SUCCESS` (PR-B unless mistakenly added in A) + +## Definition of done + +- [ ] PR-A checklist in `plans/PLAN-HINTS-V4.md` satisfied. +- [ ] Propose still **locked**; `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` passes. +- [ ] PR body: scope, plan + propose links, note lossy N1a/N1b, **no re-index**. +```` + +--- + +## PR-HINTS-V4-B — `find` success-path (+ optional S1, appendix) + +**Branch:** `feat/hints-v4-find-success` off `master` (or rebase on PR-A merge). +**Base:** `master` after PR-A merged (or include PR-A commits if stacked). +**Plan section:** [`plans/PLAN-HINTS-V4.md`](./PLAN-HINTS-V4.md) § PR-B. +**PR title:** `feat(hints): v4 success-path find road signs (F1–F3)` + +**Attach (`@-files`):** + +- `@plans/PLAN-HINTS-V4.md` +- `@propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md` +- `@mcp_hints.py` +- `@tests/test_mcp_hints.py` +- `@propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md` (appendix only) + +**Prompt:** + +```` +You are implementing PR-HINTS-V4-B from `plans/PLAN-HINTS-V4.md`. + +PR-A (neighbors N1a–N7) is on `master`. + +## Scope + +1. **`mcp_hints.py`** + - Add F1–F3 templates and `find_success_hints(payload)`. + - Wire `generate_hints("find", …)` — success hints at `PRIORITY_LEAF_FOLLOWUP`; page-full stays `PRIORITY_META`. + - Page-full gate: do not emit F-rows when `len(results) >= limit` and `has_more_results is True`. + - `{id}` = `results[0]["id"]` when multiple matches (document in PR — not per-row). + - **Optional:** S1 `search` single-hit → `describe(id='{symbol_id}')` only if plan reviewer approved; else skip S1 and its test. + +2. **`propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md`** + - Add short v4 amendment paragraph (#163): second partial dot-key reversal for type-origin neighbors success path only. + +3. **`tests/test_mcp_hints.py`** + - `test_hints_find_route_success_emits_handler` + - `test_hints_find_client_success_emits_http_calls` + - `test_hints_find_producer_success_emits_async_calls` + - `test_hints_search_single_hit_emits_describe` — only if S1 implemented + +## Out of scope (do NOT touch) + +- Graph builder, ontology, `mcp_v2.py` (find payload already has `kind`, `results`, `limit`, `has_more_results`). +- Neighbors success catalog changes except regressions. +- `MCP_HINTS_FIELD_DESCRIPTION` change. +- `README.md` / `server.py` unless explicitly requested by reviewer. + +## Deliverables + +1. F1–F3 on non-page-full find success payloads. +2. Appendix paragraph in HINTS-ROAD-SIGNS completed propose. +3. Optional S1 + test if shipped. + +## Tests to run + +```bash +.venv/bin/ruff check mcp_hints.py tests/test_mcp_hints.py propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md +.venv/bin/python -m pytest tests/test_mcp_hints.py -v -k "hints_find_route_success or hints_find_client or hints_find_producer or hints_search_single" +``` + +Before PR open: + +```bash +.venv/bin/ruff check . +.venv/bin/python -m pytest tests -v +``` + +## Sentinel checks (`git diff master..HEAD` — zero matches) + +- `ONTOLOGY_VERSION` +- `build_ast_graph.py` +- Changes to `neighbors_success_hints` trigger table (PR-A owned) + +## Landing hygiene (after PR-B merges) + +- Move `propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md` → `propose/completed/` +- Move `plans/PLAN-HINTS-V4.md` and this file → `plans/completed/` + +## Definition of done + +- [ ] PR-B checklist in plan satisfied. +- [ ] PR body: F1 uses first result id when multiple matches; no re-index. +```` diff --git a/plans/PLAN-HINTS-V4.md b/plans/PLAN-HINTS-V4.md new file mode 100644 index 0000000..84f4dc2 --- /dev/null +++ b/plans/PLAN-HINTS-V4.md @@ -0,0 +1,235 @@ +# Plan: HINTS-V4 (success-path road signs) + +Status: **active (planning)**. This plan implements +[`propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md`](../propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md) +(issue [#163](https://github.com/HumanBean17/java-codebase-rag/issues/163)). + +Depends on: **NEIGHBORS-DOT-KEY-TRAVERSAL** landed ([#171](https://github.com/HumanBean17/java-codebase-rag/pull/171); +[`propose/completed/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.md`](../propose/completed/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.md)). +`neighbors_v2` already echoes `subject_record`, `origin_id`, `requested_edge_types`, `requested_direction`, and `offset` — no `mcp_v2.py` payload work required. + +## Goal + +- **PR-A:** Add v4 **non-empty** `neighbors` success-path catalog (N1a–N7): output-level follow-ups keyed on homogeneous endpoint kinds, single requested edge type, `offset == 0`, and (for N1a/N1b) type Symbol `subject_record`. +- **PR-B:** Add v4 **`find`** success-path catalog (F1–F3); optional **S1** `search` single-hit row; short **HINTS-ROAD-SIGNS** appendix traceability paragraph for the second partial dot-key emission reversal. +- Agents chaining `neighbors(class, DECLARES)` → dot-key clients/routes → `HTTP_CALLS` / handler see road signs **without** calling `describe` first. + +## Principles (do not relitigate in review) + +- **Output-level only** — extends v2: no per-row hints on `Edge` / `NodeRef` / `SearchHit`; no confidence / `attrs.match` gates on success rows. +- **No graph I/O** — hints are pure functions of MCP payload dicts (`.model_dump()` shapes already built in `mcp_v2.py`). +- **Single edge type** — `len(requested_edge_types) == 1`; multi-edge requests get no v4 success hints (agent-composed queries). +- **Homogeneous endpoints** — mixed `other.kind` (or mixed method **and** type Symbols on one page) → silence for that template row. **Method + constructor** Symbols together are homogeneous (both ∈ `_METHOD_SYMBOL_KINDS`). +- **Multi-origin `neighbors`** — success hints use echoed `origin_id` / `subject_record` for **`origins[0]` only** (v3 empty-hint parity); no v4-specific multi-origin payload field. +- **`subject_record` is a flat Kuzu row** — `neighbors_v2` passes `_load_node_record` output (top-level `kind: "class" | …`), **not** `NodeRecord.model_dump()`. N1a/N1b type gate must match v3: `_subject_node_label == "Symbol"` and top-level `subject_record["kind"] in _TYPE_SYMBOL_KINDS`. +- **Pagination** — success hints require `offset == 0` (mirror v3 empty suppression). +- **Priority** — all v4 rows use `PRIORITY_LEAF_FOLLOWUP` (2); beat v2 fuzzy and v3 empty (`PRIORITY_META` 1) in cap contests; N1a + N1b may co-fire (both priority 2). +- **Dot-key partial reversal (neighbors success only)** — N1a/N1b reuse `TPL_DESCRIBE_TYPE_CLIENTS_VIA_MEMBERS` / `TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERS` verbatim (`{id}` = `origin_id`). N2/N3 accept flat **or** composed `DECLARES.*` triggers. **v3 empty structural hints** still never emit dot-keys — apply `_filter_neighbors_dotkey_hints` to **empty-branch pairs only**, not success-path pairs. +- **N1a/N1b always co-fire** when homogeneous method/constructor targets hold — lossy vs `edge_summary` counts; do not add combined `or` line (exceeds 120 chars). +- **N1a/N1b vs describe** — intentional duplicate road signs across tools. +- **`MCP_HINTS_FIELD_DESCRIPTION` unchanged** — already documents describe dot-keys + empty-neighbors dot-key prohibition; success-path neighbors dot-keys are an implementation amendment only (see PR-B appendix). +- **No ontology bump, no re-index** — query-time hint logic only. +- **No `mcp_v2.py` changes** unless a test round-trip exposes a missing payload field (not expected post-#171). + +## PR breakdown — overview + +| PR | Scope | Ontology bump | Areas of concern | Test buckets | Independent of | +| --- | --- | --- | --- | --- | --- | +| PR-A | `mcp_hints.py` neighbors success + `tests/test_mcp_hints.py` N* + dot-key filter split | **No** | `_filter_neighbors_dotkey_hints` scope; N1a/N1b flat `subject_record` gate (must match v3, not describe `data.kind`); required `neighbors_v2` round-trip; cap co-fire with fuzzy | `test_hints_neighbors_*` (propose table) | #171 | +| PR-B | `mcp_hints.py` find (+ optional search S1); appendix note; F* (+ optional S1) tests | **No** | F1 uses `results[0].id` only when `len(results) > 1`; page-full meta vs F-row priority; HV20 must stay empty-only for dot-keys | `test_hints_find_*`, optional `test_hints_search_*` | PR-A optional | + +**Landing order:** **PR-A → PR-B** (PR-B may rebase on PR-A; can collapse to one PR if reviewer prefers — keep sections separable for review). + +## Resolved design decisions + +| Topic | Decision | +| --- | --- | +| Implementation surface | `mcp_hints.py` only (+ tests; PR-B adds optional appendix in `propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md`) | +| N1a/N1b templates | Import/reference describe constants — do not fork strings | +| N2/N3 edge triggers | `DECLARES_CLIENT` **or** `DECLARES.DECLARES_CLIENT`; `DECLARES_PRODUCER` **or** `DECLARES.DECLARES_PRODUCER` | +| N7 trigger | `DECLARES.EXPOSES`, `direction=='out'`, all `other.kind=='route'` | +| Rejected N1 combined `or` line | Exceeds 120 chars; N1a + N1b only | +| `find` multi-match | F-rows emit once with `results[0]["id"]` — not per-row | +| Search S1 | **Defer to PR-B optional** — default skip unless traces warrant | +| N6 test | `test_hints_neighbors_async_calls_in_producers_emits_declares_producer` (locked in propose) | +| `subject_record` shape | Flat Kuzu row from `_load_node_record` — reuse v3 type-subject gate (`subject_record.get("kind") in _TYPE_SYMBOL_KINDS`), not nested `data.kind` | +| N1a round-trip | **Required** `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` on `kuzu_graph` | +| Propose lock | Locked in planning PR [#174](https://github.com/HumanBean17/java-codebase-rag/pull/174); must remain locked before PR-A **merge** | +| `EDGE_SCHEMA.type_subject` / #172 | Out of scope — v3 empty hints may still show legacy two-hop strings | +| `IMPLEMENTS` / `EXTENDS` / generic `CALLS` success | Deferred (v1 §5); N4 is the only `CALLS` success row | + +--- + +# PR-A — `neighbors` success-path catalog (N1a–N7) + +## File-by-file changes + +### 1. `mcp_hints.py` + +- Module docstring: add v4 amendment pointer to `propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md`. +- Add verbatim success templates (flat literals only where not aliasing describe): + - **N1a/N1b:** emit using existing `TPL_DESCRIBE_TYPE_CLIENTS_VIA_MEMBERS` / `TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERS` with `{id}` = `payload["origin_id"]` (fallback: first result `origin_id` if needed — prefer echoed `origin_id`). + - **N2:** `TPL_NEIGHBORS_SUCCESS_HTTP_TARGETS` = `HTTP targets: neighbors(client_ids,'out',['HTTP_CALLS'])` + - **N3:** `TPL_NEIGHBORS_SUCCESS_ASYNC_TARGETS` = `async targets: neighbors(producer_ids,'out',['ASYNC_CALLS'])` + - **N4:** `TPL_NEIGHBORS_SUCCESS_CALLERS` = `callers: neighbors(handler_ids,'in',['CALLS'])` + - **N5:** `TPL_NEIGHBORS_SUCCESS_DECLARING_CLIENT` = `declaring method: neighbors(client_ids,'in',['DECLARES_CLIENT'])` + - **N6:** `TPL_NEIGHBORS_SUCCESS_DECLARING_PRODUCER` = `declaring method: neighbors(producer_ids,'in',['DECLARES_PRODUCER'])` + - **N7:** `TPL_NEIGHBORS_SUCCESS_HANDLER` = `handler: neighbors(route_ids,'in',['EXPOSES'])` +- Add helpers (names illustrative; match repo style): + - `_neighbors_success_subject_is_type(subject_record) -> bool` — same as v3 type-level detection: `_subject_node_label(subject_record) == "Symbol"` and `str(subject_record.get("kind") or "") in _TYPE_SYMBOL_KINDS` (flat Kuzu row; do **not** require nested `data.kind`). + - `_neighbors_results_homogeneous(results, *, endpoint_kind: str | None, symbol_kinds: frozenset[str] | None) -> bool` — every `results[i]["other"]` matches predicate; for method rows use `other["symbol_kind"] in _METHOD_SYMBOL_KINDS` when present. + - `neighbors_success_hints(payload: dict[str, Any]) -> list[tuple[int, str]]` — evaluate N1a–N7 in fixed order; each rendered string `len <= 120` or drop; all pairs at `PRIORITY_LEAF_FOLLOWUP`. +- **`generate_hints` `neighbors` branch:** + 1. Collect `empty_pairs` from `neighbors_empty_hints` when `not results and edge_labels and offset == 0` (unchanged). + 2. When `results` and `offset == 0`: `success_pairs = neighbors_success_hints(payload)`. + 3. When `results` and fuzzy strategy present: append `TPL_NEIGHBORS_FUZZY_STRATEGY` (unchanged). + 4. `return finalize_hint_list(_filter_neighbors_dotkey_hints(empty_pairs) + success_pairs + meta_pairs)` — **do not** filter success_pairs. +- Trigger contract (all must hold per row): `success`, non-empty `results`, `offset == 0`, exactly one `requested_edge_types` entry, homogeneous `other`, direction match, char cap. + +| ID | `requested_edge_types[0]` | `requested_direction` | Homogeneous `other` | +| --- | --- | --- | --- | +| N1a | `DECLARES` | `out` | method/constructor Symbols + type subject | +| N1b | `DECLARES` | `out` | same payload as N1a | +| N2 | `DECLARES_CLIENT` or `DECLARES.DECLARES_CLIENT` | `out` | `kind == "client"` | +| N3 | `DECLARES_PRODUCER` or `DECLARES.DECLARES_PRODUCER` | `out` | `kind == "producer"` | +| N4 | `EXPOSES` | `in` | method/constructor Symbols | +| N5 | `HTTP_CALLS` | `in` | `kind == "client"` | +| N6 | `ASYNC_CALLS` | `in` | `kind == "producer"` | +| N7 | `DECLARES.EXPOSES` | `out` | `kind == "route"` | + +### 2. `tests/test_mcp_hints.py` + +- Extend `_neighbors_hint_payload` with `origin_id` and `offset` defaults (`offset=0`). +- Add `_type_subject_record(node_id, decl_kind="class")` helper returning a **flat Kuzu-shaped** dict (`{"id": node_id, "kind": decl_kind}` — same shape as v3 HV tests and production `subject_record`). +- Add synthetic `results[]` builders with `other: {kind, id, symbol_kind?}` matching `Edge.model_dump()`. +- Implement every named test from the propose § Tests table (PR-A subset). +- **Required:** `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` — `neighbors_v2` on session `kuzu_graph` (type Symbol → `DECLARES` out → non-empty methods); asserts N1a in output hints (guards flat vs NodeRecord test footgun). +- **Update `test_hints_hv20_no_dotkey_edge_labels_in_rendered_neighbors_hints`** — docstring/assertion scope: **empty structural hints only**; add `test_hints_neighbors_success_may_emit_declares_dot_keys` for N1a/N1b on synthetic flat `subject_record`. + +## Tests for PR-A + +Implement **verbatim** names from the propose: + +1. `test_hints_neighbors_declares_methods_emits_dot_key_clients` +2. `test_hints_neighbors_declares_methods_emits_dot_key_routes` +3. `test_hints_neighbors_declares_client_homogeneous_emits_http_calls` +4. `test_hints_neighbors_declares_dot_key_client_homogeneous_emits_http_calls` +5. `test_hints_neighbors_declares_producer_homogeneous_emits_async_calls` +6. `test_hints_neighbors_declares_dot_key_producer_homogeneous_emits_async_calls` +7. `test_hints_neighbors_declares_dot_key_exposes_homogeneous_emits_handler` +8. `test_hints_neighbors_exposes_in_methods_emits_calls` +9. `test_hints_neighbors_http_calls_in_clients_emits_declares_client` +10. `test_hints_neighbors_async_calls_in_producers_emits_declares_producer` +11. `test_hints_neighbors_mixed_endpoint_kinds_silent` +12. `test_hints_neighbors_offset_suppresses_success_hints` +13. `test_hints_neighbors_success_beats_fuzzy_in_cap` +14. `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` — **required** `neighbors_v2` round-trip +15. `test_hints_all_v4_templates_under_120_chars` — parametrize new templates + N1a/N1b via describe constants + N7; realistic id substitution +16. `test_hints_neighbors_success_may_emit_declares_dot_keys` — HV20 complement + +**Regression:** all `test_hints_hv*`, `test_hints_neighbors_fuzzy_*`, v1 describe/find tests unchanged except HV20 scope clarification. + +## Definition of done (PR-A) + +- [ ] N1a–N7 wired; empty vs success dot-key filter split correct. +- [ ] All named PR-A tests pass. +- [ ] `.venv/bin/ruff check .` and `.venv/bin/python -m pytest tests -v` green. +- [ ] No `ONTOLOGY_VERSION` / graph / `mcp_v2.py` changes. +- [ ] Propose remains **locked** (done in #174). +- [ ] `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` passes. + +## Implementation step list + +| # | Step | File(s) | Done when | +| --- | --- | --- | --- | +| 1 | Success templates + `neighbors_success_hints` | `mcp_hints.py` | N2–N7 unit triggers pass | +| 2 | N1a/N1b via describe template aliases + type subject gate | `mcp_hints.py` | Tests 1–2 pass | +| 3 | Wire `generate_hints` + dot-key filter split | `mcp_hints.py` | Mixed/offset/fuzzy cap tests pass | +| 4 | Synthetic payload tests + HV20 split | `tests/test_mcp_hints.py` | Full PR-A table green | +| 5 | `neighbors_v2` round-trip + char-cap | `tests/test_mcp_hints.py` | Round-trip + `test_hints_all_v4_templates_under_120_chars` pass | + +--- + +# PR-B — `find` success-path (+ optional search S1, appendix) + +## File-by-file changes + +### 1. `mcp_hints.py` + +- Add find success templates: + - **F1:** `TPL_FIND_SUCCESS_HANDLER` = `handler: neighbors(['{id}'],'in',['EXPOSES'])` + - **F2:** `TPL_FIND_SUCCESS_HTTP_TARGETS` = `HTTP targets: neighbors(['{id}'],'out',['HTTP_CALLS'])` + - **F3:** `TPL_FIND_SUCCESS_ASYNC_TARGETS` = `async targets: neighbors(['{id}'],'out',['ASYNC_CALLS'])` +- Add `find_success_hints(payload) -> list[tuple[int, str]]`: + - Gates: `success`, `len(results) > 0`, not page-full (`not (limit set and len(results) >= limit and has_more_results)`), `kind` match, `{id}` = `results[0]["id"]`, char cap. +- Extend `generate_hints` `find` branch: merge `find_success_hints` pairs **before** `finalize_hint_list` (page-full meta at priority 1; F-rows at priority 2 win cap). +- **Optional S1:** `TPL_SEARCH_SUCCESS_DESCRIBE` = `inspect: describe(id='{symbol_id}')`; fire only when `len(results)==1` and top hit has `symbol_id`; priority 2; skip if char cap exceeded. + +### 2. `propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md` + +- Short appendix paragraph: **v4 amendment (#163)** — second partial dot-key emission reversal: success-path `neighbors` on type Symbol origins may recommend `DECLARES.DECLARES_CLIENT` / `DECLARES.DECLARES_PRODUCER` / `DECLARES.EXPOSES`; v3 empty structural hints unchanged; `OVERRIDDEN_BY.*` remains describe-only. + +### 3. `tests/test_mcp_hints.py` + +1. `test_hints_find_route_success_emits_handler` +2. `test_hints_find_client_success_emits_http_calls` +3. `test_hints_find_producer_success_emits_async_calls` +4. `test_hints_search_single_hit_emits_describe` — **only if S1 ships** + +## Definition of done (PR-B) + +- [ ] F1–F3 wired; page-full + empty-resolve behavior unchanged. +- [ ] Named find tests pass; optional S1 test if implemented. +- [ ] Appendix paragraph added to `HINTS-ROAD-SIGNS-PROPOSE.md`. +- [ ] Full test suite green; no ontology/README requirement unless reviewer asks for README mention. + +## Implementation step list + +| # | Step | File(s) | Done when | +| --- | --- | --- | --- | +| 1 | Find templates + `find_success_hints` | `mcp_hints.py` | F1–F3 tests pass | +| 2 | Optional S1 + search branch | `mcp_hints.py` | S1 test if shipped | +| 3 | Appendix traceability | `propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md` | Paragraph merged | +| 4 | Find (+ search) tests | `tests/test_mcp_hints.py` | PR-B tests green | + +--- + +# Cross-PR risks and mitigations + +| # | Risk | Severity | Mitigation | +| --- | --- | --- | --- | +| 1 | Success-path dot-keys filtered by `_filter_neighbors_dotkey_hints` | High | Filter empty branch only; `test_hints_neighbors_success_may_emit_declares_dot_keys` | +| 2 | Wrong `subject_record` shape in tests vs production | **High** | Flat Kuzu fixtures (`{"id", "kind": "class"}`); **required** `neighbors_v2` round-trip test | +| 3 | N1a/N1b noise when type has no clients/routes | Low | Accepted lossy design (Open Q1); document in PR body | +| 4 | N4 empty `CALLS` follow-up loops | Low | Ship advisory hint; remove only if traces show harm (Open Q2) | +| 5 | F1 misleading when `len(results) > 1` | Medium | Document `results[0]` only in PR description (Open Q4) | +| 6 | Cap drops fuzzy but not success | Low | `test_hints_neighbors_success_beats_fuzzy_in_cap` | +| 7 | HV20 false failure after PR-A | Medium | Narrow HV20 to empty payloads only | + +# Out of scope + +- Per-row hints; `resolve(status='one')` hints; `describe` catalog changes. +- `IMPLEMENTS` / `EXTENDS` generic success chains; `EDGE_SCHEMA` / graph builder / `type_subject` (#172). +- `structured next_actions`; `hints_version` field. +- Conditioning success hints on `attrs.match` / confidence. +- Member-batch `DECLARES` → `member_ids` → flat edges as primary teaching path. +- `OVERRIDDEN_BY.*` dot-keys in any hint emission. +- Ontology bump and re-index. +- Changing `MCP_HINTS_FIELD_DESCRIPTION` (frozen per propose). + +# Whole-plan done definition + +1. Non-empty `neighbors` at `offset==0` with single edge type emits N* follow-ups per homogeneous endpoint rules; N1a/N1b use describe dot-key strings on type origins. +2. v3 empty structural hints still never contain dot-keys; success-path dot-keys not filtered. +3. `find` success emits F1–F3 when not page-full; optional S1 for single-hit `search`. +4. All propose-named tests pass; char-cap sweep includes v4 templates. +5. `propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md` moved to `propose/completed/`; this plan + prompts moved to `plans/completed/` after PR-B lands. + +# Tracking + +- `PR-A`: _pending_ +- `PR-B`: _pending_ + +## Cursor handoff + +[`plans/CURSOR-PROMPTS-HINTS-V4.md`](./CURSOR-PROMPTS-HINTS-V4.md) diff --git a/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md b/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md index 50f85e9..cba3d86 100644 --- a/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md +++ b/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md @@ -2,7 +2,7 @@ ## Status -Proposal — not yet implemented. Tracks [issue #163](https://github.com/HumanBean17/java-codebase-rag/issues/163). +**Status**: locked — not yet implemented. Tracks [issue #163](https://github.com/HumanBean17/java-codebase-rag/issues/163). Implementation plan: [`plans/PLAN-HINTS-V4.md`](../plans/PLAN-HINTS-V4.md) (planning PR [#174](https://github.com/HumanBean17/java-codebase-rag/pull/174)); Cursor prompts: [`plans/CURSOR-PROMPTS-HINTS-V4.md`](../plans/CURSOR-PROMPTS-HINTS-V4.md). **Depends on (landed):** [NEIGHBORS-DOT-KEY-TRAVERSAL](./completed/NEIGHBORS-DOT-KEY-TRAVERSAL-PROPOSE.md) ([#171](https://github.com/HumanBean17/java-codebase-rag/pull/171)) — `neighbors` accepts `DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, `DECLARES.EXPOSES` on type Symbol origins; describe rollup templates already prescribe those dot-keys. @@ -17,6 +17,7 @@ This section records deltas applied to the original #163 draft after dot-key tra | Success after composed traversal | Not covered | **N2/N3** triggers extended to composed `requested_edge_types`; **N7** for `DECLARES.EXPOSES` → routes | | HINTS-ROAD-SIGNS §2.7–2.9 | Emissions use atomic `EdgeType` only | **Second partial reversal** (after describe rollups in #171): v4 success-path emissions may recommend the three `DECLARES.*` dot-keys on type origins only; v3 **empty** structural hints still never use dot-keys (`_filter_neighbors_dotkey_hints` unchanged) | | `EDGE_SCHEMA.type_subject` | Assumed aligned | **Out of v4 scope** — v3 empty hints may still show the legacy two-hop `DECLARES` → `member_ids` string until [#172](https://github.com/HumanBean17/java-codebase-rag/issues/172) | +| N1a/N1b `subject_record` gate | `NodeRecord.model_dump()` with nested `data.kind` | **Flat Kuzu row** from `_load_node_record` (same as v3 empty hints): `_subject_node_label == "Symbol"` and top-level `subject_record["kind"]` in `_TYPE_SYMBOL_KINDS` — **not** nested `data.kind` (describe-only shape) | ## Problem Statement @@ -65,7 +66,7 @@ v2 (`propose/completed/HINTS-V2-PROPOSE.md` §1) ruled out **per-row** neighbors | `requested_direction` | `"in" \| "out"` | echoed direction | | `offset` | `int` | echoed page offset; success hints require `0` | | `origin_id` | `str` | first origin id; `{id}` for N1a/N1b | -| `subject_record` | `dict` | `NodeRecord.model_dump()` for origin; **required for N1a/N1b** | +| `subject_record` | `dict` | Raw Kuzu row from `_load_node_record` for first origin (flat `kind: "class" \| "method" \| …` on Symbol rows — **not** `NodeRecord.model_dump()`); **required for N1a/N1b** | **`find`** — `find_v2` builds: @@ -84,9 +85,10 @@ Fire a success-path hint iff **all** of: 1. `payload["success"] is True` and `len(payload["results"]) > 0` and `payload.get("offset", 0) == 0` (paginated tail pages are ambiguous — mirror v3 empty-hint suppression). 2. `len(payload["requested_edge_types"]) == 1` (**locked** — multi-edge requests are agent-composed; no success hints). -3. Every row’s `results[i]["other"]["kind"]` (and `symbol_kind` when `kind == "symbol"`) matches the template predicate — **mixed endpoint kinds → no hint**. +3. Every row’s `results[i]["other"]["kind"]` (and `symbol_kind` when `kind == "symbol"`) matches the template predicate — **mixed endpoint kinds → no hint**. For N1a/N1b/N4, **method + constructor Symbols on one page are homogeneous** (both ∈ `_METHOD_SYMBOL_KINDS`); mixed method **and** type Symbols → silence. 4. Rendered string `len <= 120` after substitution; otherwise drop that row. -5. **N1a/N1b only:** `payload["subject_record"]` is present, `subject_record["kind"] == "symbol"`, and `subject_record["data"]["kind"]` (declaration kind) is one of `class`, `interface`, `enum`, `record`, `annotation`. +5. **N1a/N1b only:** `payload["subject_record"]` is present and matches v3 empty-hint type-subject detection: `_subject_node_label(subject_record) == "Symbol"` **and** `str(subject_record.get("kind") or "") in _TYPE_SYMBOL_KINDS` (top-level declaration kind on the flat Kuzu row — same gate as `neighbors_empty_hints` type-level requery, **not** nested `data.kind`). +6. **Multi-origin `neighbors`:** hints use echoed `origin_id` / `subject_record` for **`origins[0]` only** (v3 parity). No new payload field; v4 does not add multi-origin-specific rows. Priority: **`PRIORITY_LEAF_FOLLOWUP` (2)**. Fuzzy-strategy (`PRIORITY_META` 1) and v3 empty structural (`PRIORITY_META` 1) lose cap contests to success-path hints. @@ -206,11 +208,14 @@ Named scenarios (implementer contract): | `test_hints_neighbors_declares_dot_key_exposes_homogeneous_emits_handler` | N7 | | `test_hints_neighbors_exposes_in_methods_emits_calls` | N4 | | `test_hints_neighbors_http_calls_in_clients_emits_declares_client` | N5 | +| `test_hints_neighbors_async_calls_in_producers_emits_declares_producer` | N6 | | `test_hints_neighbors_mixed_endpoint_kinds_silent` | client + route in one page → no success-path hints | | `test_hints_neighbors_offset_suppresses_success_hints` | `offset > 0`, non-empty `results` → no N* hints (mirror v3 offset test) | | `test_hints_neighbors_success_beats_fuzzy_in_cap` | constructed payload with both signals → leaf hint retained | | `test_hints_find_route_success_emits_handler` | F1 with concrete `{id}` substitution | | `test_hints_find_client_success_emits_http_calls` | F2 | +| `test_hints_find_producer_success_emits_async_calls` | F3 | +| `test_hints_neighbors_v2_declares_success_emits_dot_key_clients` | **Required** N1a `neighbors_v2` round-trip on `kuzu_graph` (flat `subject_record` — catches NodeRecord-shaped unit-test footgun) | | `test_hints_all_v4_templates_under_120_chars` | rendered with realistic ids (include N1a/N1b via describe template constants + N7) | | Char-cap + dedupe + cap-5 | reuse existing `finalize_hint_list` tests pattern | @@ -245,6 +250,6 @@ Suggested **2 PRs** (can collapse to 1 if reviewer prefers): | **PR-A** | N1a–N7 + neighbors tests + char-cap sweep for new templates | #171 landed | | **PR-B** | F1–F3 + find tests + appendix traceability note; optional S1 | PR-A optional | -After land: move this file to `propose/completed/`, add `plans/PLAN-HINTS-V4.md` + `CURSOR-PROMPTS-HINTS-V4.md` if the team wants the per-PR sentinel contract (not required for a 1–2 PR effort). +After land: move this file to `propose/completed/`; move `plans/PLAN-HINTS-V4.md` + `plans/CURSOR-PROMPTS-HINTS-V4.md` to `plans/completed/` (landed in planning PR [#174](https://github.com/HumanBean17/java-codebase-rag/pull/174)). **Related issues:** #161 (describe producer symmetry — landed); #163 (this propose); #171 (neighbors `DECLARES.*` dot-keys — landed). No ontology coordination with SCHEMA-V2.