From a080e39c81f0030f835156e793abaedf0e45c312 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Sun, 17 May 2026 11:59:02 +0300 Subject: [PATCH 1/2] =?UTF-8?q?add=20v4=20find=20success-path=20hints=20(F?= =?UTF-8?q?1=E2=80=93F3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emit handler/HTTP/async neighbors follow-ups on non-page-full find results; document v4 dot-key reversal in HINTS-ROAD-SIGNS appendix. S1 search single-hit describe deferred per plan. Co-authored-by: Cursor --- mcp_hints.py | 47 +++++++++++-- propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md | 2 + tests/test_mcp_hints.py | 66 +++++++++++++++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/mcp_hints.py b/mcp_hints.py index e89fdc9..64fecf9 100644 --- a/mcp_hints.py +++ b/mcp_hints.py @@ -58,6 +58,11 @@ TPL_FIND_EMPTY_RESOLVE = "no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup" TPL_FIND_PAGE_FULL = "result page full at {limit} — narrow filter or paginate" +TPL_FIND_SUCCESS_HANDLER = "handler: neighbors(['{id}'],'in',['EXPOSES'])" +TPL_FIND_SUCCESS_HTTP_TARGETS = "HTTP targets: neighbors(['{id}'],'out',['HTTP_CALLS'])" +TPL_FIND_SUCCESS_ASYNC_TARGETS = "async targets: neighbors(['{id}'],'out',['ASYNC_CALLS'])" + +_FIND_SUCCESS_MAX_CHARS = 120 TPL_NEIGHBORS_WRONG_SUBJECT_KIND = ( "0 results — '{edge}' connects {src_kind} → {dst_kind}; " @@ -394,6 +399,41 @@ def neighbors_success_hints(payload: dict[str, Any]) -> list[tuple[int, str]]: return pairs +def _find_is_page_full(payload: dict[str, Any], results: list[dict[str, Any]]) -> bool: + lim = payload.get("limit") + return ( + lim is not None + and len(results) >= int(lim) + and payload.get("has_more_results") is True + ) + + +def _append_find_success_hint(pairs: list[tuple[int, str]], text: str) -> None: + if text and len(text) <= _FIND_SUCCESS_MAX_CHARS: + pairs.append((PRIORITY_LEAF_FOLLOWUP, text)) + + +def find_success_hints(payload: dict[str, Any]) -> list[tuple[int, str]]: + """v4 non-empty find follow-ups (F1–F3); no graph I/O.""" + if not payload.get("success"): + return [] + results = list(payload.get("results") or []) + if not results or _find_is_page_full(payload, results): + return [] + node_id = str(results[0].get("id") or "") + if not node_id: + return [] + kind = str(payload.get("kind") or "") + pairs: list[tuple[int, str]] = [] + if kind == "route": + _append_find_success_hint(pairs, TPL_FIND_SUCCESS_HANDLER.format(id=node_id)) + elif kind == "client": + _append_find_success_hint(pairs, TPL_FIND_SUCCESS_HTTP_TARGETS.format(id=node_id)) + elif kind == "producer": + _append_find_success_hint(pairs, TPL_FIND_SUCCESS_ASYNC_TARGETS.format(id=node_id)) + return pairs + + def _any_fuzzy_strategy(edges: list[dict[str, Any]]) -> bool: for e in edges: attrs = e.get("attrs") if isinstance(e.get("attrs"), dict) else {} @@ -503,12 +543,9 @@ def generate_hints( lim = payload.get("limit") if not results and _find_has_identifier_shaped_filter(kind, flt): pairs.append((PRIORITY_META, TPL_FIND_EMPTY_RESOLVE.format(kind=kind))) - if ( - lim is not None - and len(results) >= int(lim) - and payload.get("has_more_results") is True - ): + if _find_is_page_full(payload, results) and lim is not None: pairs.append((PRIORITY_META, TPL_FIND_PAGE_FULL.format(limit=int(lim)))) + pairs.extend(find_success_hints(payload)) return finalize_hint_list(pairs) if output_kind == "neighbors": diff --git a/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md b/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md index 2495178..eab9962 100644 --- a/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md +++ b/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md @@ -280,6 +280,8 @@ File placement (`mcp_hints.py`), function decomposition, integration points in ` **Amendment (2026-05-16, issue #161 / PR #164)** — five describe templates for the producer axis and override-route rollup, symmetric with the client/route rows above: `DECLARES.DECLARES_PRODUCER`, `DECLARES_PRODUCER`, `OVERRIDDEN_BY.DECLARES_PRODUCER`, `OVERRIDDEN_BY.EXPOSES`, and `kind == producer` declaring-method hint. No ontology or re-index change. +**Amendment (v4 success-path, issue #163)** — second partial dot-key emission reversal: non-empty `neighbors` success hints on type Symbol origins may recommend `DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, and `DECLARES.EXPOSES` (matching describe rollups). v3 empty structural `neighbors` hints still never use dot-keys (`_filter_neighbors_dotkey_hints` applies to the empty branch only). `OVERRIDDEN_BY.*` dot-keys remain describe-only. No ontology or re-index change. + **What stayed unchanged from the first draft** - §1 frame statement; §2 principles 1–8; §3.1 field shape; §3.2 generation contract; §5 "deliberately does NOT do" table; §8 risks table. diff --git a/tests/test_mcp_hints.py b/tests/test_mcp_hints.py index 1660770..cf97194 100644 --- a/tests/test_mcp_hints.py +++ b/tests/test_mcp_hints.py @@ -1026,6 +1026,9 @@ def test_hints_neighbors_v2_declares_success_emits_dot_key_clients(kuzu_graph) - mcp_hints.TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERS, {"id": "sym:com.example.bank.chat.Controller"}, ), + (mcp_hints.TPL_FIND_SUCCESS_HANDLER, {"id": "route:svc:GET:/api/v1/chat"}), + (mcp_hints.TPL_FIND_SUCCESS_HTTP_TARGETS, {"id": "client:svc:feign:target:GET:/p"}), + (mcp_hints.TPL_FIND_SUCCESS_ASYNC_TARGETS, {"id": "producer:svc:kafka:topic:t"}), ], ) def test_hints_all_v4_templates_under_120_chars(template: str, substitutions: dict[str, str]) -> None: @@ -1159,6 +1162,69 @@ def test_hints_cap_same_priority_keeps_emission_order() -> None: assert "e-meta" not in got +def _find_success_payload( + kind: str, + node_id: str, + *, + limit: int | None = None, + has_more_results: bool = False, +) -> dict[str, Any]: + payload: dict[str, Any] = { + "success": True, + "kind": kind, + "results": [{"id": node_id, "kind": kind}], + "filter": {}, + "offset": 0, + } + if limit is not None: + payload["limit"] = limit + payload["has_more_results"] = has_more_results + return payload + + +def test_hints_find_route_success_emits_handler() -> None: + rid = "route:svc:GET:/api/v1/chat" + payload = _find_success_payload("route", rid) + want = mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=rid) + assert want in generate_hints("find", payload) + + +def test_hints_find_client_success_emits_http_calls() -> None: + cid = "client:svc:feign:target:GET:/p" + payload = _find_success_payload("client", cid) + want = mcp_hints.TPL_FIND_SUCCESS_HTTP_TARGETS.format(id=cid) + assert want in generate_hints("find", payload) + + +def test_hints_find_producer_success_emits_async_calls() -> None: + pid = "producer:svc:kafka:topic:t" + payload = _find_success_payload("producer", pid) + want = mcp_hints.TPL_FIND_SUCCESS_ASYNC_TARGETS.format(id=pid) + assert want in generate_hints("find", payload) + + +def test_hints_find_success_suppressed_when_page_full() -> None: + rid = "route:svc:GET:/api/v1/chat" + payload = _find_success_payload("route", rid, limit=1, has_more_results=True) + hints = generate_hints("find", payload) + assert mcp_hints.TPL_FIND_PAGE_FULL.format(limit=1) in hints + assert mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=rid) not in hints + + +def test_hints_find_success_uses_first_result_id_when_multiple() -> None: + first = "route:svc:GET:/first" + second = "route:svc:GET:/second" + payload = _find_success_payload("route", first) + payload["results"] = [ + {"id": first, "kind": "route"}, + {"id": second, "kind": "route"}, + ] + want = mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=first) + hints = generate_hints("find", payload) + assert want in hints + assert mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=second) not in hints + + def test_hints_find_page_full_requires_has_more_results_flag() -> None: full_page = { "success": True, From 3b8dbc0ea5608c709b83d3a7bc090bf477a4b070 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Sun, 17 May 2026 12:14:30 +0300 Subject: [PATCH 2/2] address PR-176 review: find round-trip, docs, landing hygiene Add find_v2 kuzu_graph round-trip and negative gates; update HINTS-ROAD-SIGNS catalog/UCs for v4 find success; move v4 propose and plan to completed/. Co-authored-by: Cursor --- mcp_hints.py | 2 +- .../CURSOR-PROMPTS-HINTS-V4.md | 0 plans/{ => completed}/PLAN-HINTS-V4.md | 18 ++++++------ propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md | 13 +++++++-- .../HINTS-V4-SUCCESS-PATH-PROPOSE.md | 2 +- tests/test_mcp_hints.py | 29 +++++++++++++++++++ 6 files changed, 50 insertions(+), 14 deletions(-) rename plans/{ => completed}/CURSOR-PROMPTS-HINTS-V4.md (100%) rename plans/{ => completed}/PLAN-HINTS-V4.md (95%) rename propose/{ => completed}/HINTS-V4-SUCCESS-PATH-PROPOSE.md (97%) diff --git a/mcp_hints.py b/mcp_hints.py index 64fecf9..27d3958 100644 --- a/mcp_hints.py +++ b/mcp_hints.py @@ -4,7 +4,7 @@ (issue #161 producer/override-route amendments in that appendix). v2 resolve + neighbors fuzzy-strategy catalog: ``propose/completed/HINTS-V2-PROPOSE.md`` Appendix A. v3 empty-neighbors structural catalog: ``propose/completed/HINTS-V3-PROPOSE.md`` §3.1–3.3. -v4 non-empty neighbors success-path catalog: ``propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md``. +v4 success-path catalog: ``propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md``. Priority cap: same propose §7.12 / ``plans/completed/PLAN-HINTS.md`` principles. """ diff --git a/plans/CURSOR-PROMPTS-HINTS-V4.md b/plans/completed/CURSOR-PROMPTS-HINTS-V4.md similarity index 100% rename from plans/CURSOR-PROMPTS-HINTS-V4.md rename to plans/completed/CURSOR-PROMPTS-HINTS-V4.md diff --git a/plans/PLAN-HINTS-V4.md b/plans/completed/PLAN-HINTS-V4.md similarity index 95% rename from plans/PLAN-HINTS-V4.md rename to plans/completed/PLAN-HINTS-V4.md index d0cd989..6a27acf 100644 --- a/plans/PLAN-HINTS-V4.md +++ b/plans/completed/PLAN-HINTS-V4.md @@ -1,7 +1,7 @@ # Plan: HINTS-V4 (success-path road signs) -Status: **active (PR-A in progress)**. This plan implements -[`propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md`](../propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md) +Status: **completed** (PR-A [#175](https://github.com/HumanBean17/java-codebase-rag/pull/175), PR-B [#176](https://github.com/HumanBean17/java-codebase-rag/pull/176)). This plan implements +[`propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md`](../propose/completed/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); @@ -178,10 +178,10 @@ Implement **verbatim** names from the propose: ## 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. +- [x] F1–F3 wired; page-full + empty-resolve behavior unchanged. +- [x] Named find tests pass; optional S1 test if implemented (S1 deferred). +- [x] Appendix paragraph added to `HINTS-ROAD-SIGNS-PROPOSE.md`. +- [x] Full test suite green; no ontology/README requirement unless reviewer asks for README mention. ## Implementation step list @@ -227,9 +227,9 @@ Implement **verbatim** names from the propose: # Tracking -- `PR-A`: _landed (PR open)_ -- `PR-B`: _pending_ +- `PR-A`: _landed ([#175](https://github.com/HumanBean17/java-codebase-rag/pull/175))_ +- `PR-B`: _landed ([#176](https://github.com/HumanBean17/java-codebase-rag/pull/176))_ ## Cursor handoff -[`plans/CURSOR-PROMPTS-HINTS-V4.md`](./CURSOR-PROMPTS-HINTS-V4.md) +[`plans/completed/CURSOR-PROMPTS-HINTS-V4.md`](./CURSOR-PROMPTS-HINTS-V4.md) diff --git a/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md b/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md index eab9962..a7502f4 100644 --- a/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md +++ b/propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md @@ -108,6 +108,9 @@ The full catalog of hints emitted at v1. Each row is one template; multiple may | `describe` (client node) | always | `declaring method: neighbors([{id}],'in',['DECLARES_CLIENT'])` | | `find` | `len(results) == 0` and filter has an identifier-shaped value (e.g. `fqn_prefix`, `target_service`) | `no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup` | | `find` | `len(results) >= limit` (page-full) | `result page full at {limit} — narrow filter or paginate` | +| `find` (route) | `len(results) > 0`, not page-full | `handler: neighbors(['{id}'],'in',['EXPOSES'])` — `{id}` = `results[0].id` (#163 v4) | +| `find` (client) | same, `kind == client` | `HTTP targets: neighbors(['{id}'],'out',['HTTP_CALLS'])` (#163 v4) | +| `find` (producer) | same, `kind == producer` | `async targets: neighbors(['{id}'],'out',['ASYNC_CALLS'])` (#163 v4) | | `neighbors` | `len(results) == 0` and `len(edge_types) > 0` | `0 results — check if the requested edge_types apply to this kind` | | `neighbors` | rows include `edge_type='DECLARES'` to method targets, **and** any of those methods has known `DECLARES_CLIENT` out in summary | (deferred — needs second-hop awareness; not in v1) | | `search` | `len(results) == limit` **and** `(max_score - min_score) < 0.1 * max_score` (structural low-confidence signal — no absolute threshold). Requires `SearchOutput.limit` echo per §3.1 / §7.18. | `results look weak — narrow the query or try find(role=…)` | @@ -138,8 +141,9 @@ This catalog is the v1 lock. Adding a new template requires a propose-doc amendm | UC9 | Agent does `neighbors([class_id], 'out', ['DECLARES_CLIENT'])` and gets 0 (correctly, because DECLARES_CLIENT lives on methods) | neighbors | "0 results — check if the requested edge_types apply to this kind" | | UC10 | Agent does `search`, gets a full page of hits all clustered within 10% of the top score (no dominant match) | search | "results look weak — narrow the query or try find(role=…)" | | UC11 | Agent describes a leaf method (no rollups, no override axis) | describe | (no hints — clean output, agent has all it needs) | -| UC12 | Agent does `find` with successful match list | find | (no hints — page not full, results present) | -| UC13 | Agent does `neighbors` with results matching all requested edge_types | neighbors | (no hints — happy path) | +| UC12 | Agent does `find` on route, client, or producer with successful match list, not page-full | find | v4 success follow-up (F1–F3): handler / HTTP targets / async targets using `results[0].id` | +| UC12b | Agent does `find(kind=symbol)` with successful match list | find | (no v4 find hints — symbol kind out of F1–F3 scope) | +| UC13 | Agent does `neighbors` with homogeneous results at `offset==0`, single edge type | neighbors | v4 success follow-ups (N1a–N7) when triggers match; else fuzzy-strategy meta only | | UC14 | Agent describes a class with `DECLARES.DECLARES_CLIENT` and `DECLARES.EXPOSES` both non-zero, plus `OVERRIDDEN_BY` (it's an interface) | describe | up to 3 hints — clients via members, routes via members, overriders if applicable | 15 realistic cases (UC6a + UC6b counted separately). The §2.5 cap is exercised by a dedicated test scenario in §6 (a hand-crafted record with > 5 firing conditions), not by a hypothetical UC — the UC re-walk is the design-validation move, the cap is a guardrail with its own test. @@ -266,6 +270,9 @@ kind == producer, always → "declaring method: neighbors(['{id}'],'in', # FindOutput results==[] and filter has identifier-shaped value → "no matches — try resolve(identifier, hint_kind='{kind}') for canonical lookup" len(results) >= limit → "result page full at {limit} — narrow filter or paginate" +kind==route, len(results)>0, not page-full → "handler: neighbors(['{id}'],'in',['EXPOSES'])" # v4 #163 +kind==client, len(results)>0, not page-full → "HTTP targets: neighbors(['{id}'],'out',['HTTP_CALLS'])" # v4 #163 +kind==producer, len(results)>0, not page-full → "async targets: neighbors(['{id}'],'out',['ASYNC_CALLS'])" # v4 #163 # NeighborsOutput results==[] and edge_types non-empty → "0 results — check if the requested edge_types apply to this kind" @@ -280,7 +287,7 @@ File placement (`mcp_hints.py`), function decomposition, integration points in ` **Amendment (2026-05-16, issue #161 / PR #164)** — five describe templates for the producer axis and override-route rollup, symmetric with the client/route rows above: `DECLARES.DECLARES_PRODUCER`, `DECLARES_PRODUCER`, `OVERRIDDEN_BY.DECLARES_PRODUCER`, `OVERRIDDEN_BY.EXPOSES`, and `kind == producer` declaring-method hint. No ontology or re-index change. -**Amendment (v4 success-path, issue #163)** — second partial dot-key emission reversal: non-empty `neighbors` success hints on type Symbol origins may recommend `DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, and `DECLARES.EXPOSES` (matching describe rollups). v3 empty structural `neighbors` hints still never use dot-keys (`_filter_neighbors_dotkey_hints` applies to the empty branch only). `OVERRIDDEN_BY.*` dot-keys remain describe-only. No ontology or re-index change. +**Amendment (v4 success-path, issue #163)** — (1) **Find:** non-page-full `find` on route/client/producer emits F1–F3 (`handler` / `HTTP targets` / `async targets`); `{id}` = `results[0].id` when multiple matches. (2) **Neighbors — second partial dot-key emission reversal:** non-empty success hints on type Symbol origins may recommend `DECLARES.DECLARES_CLIENT`, `DECLARES.DECLARES_PRODUCER`, and `DECLARES.EXPOSES` (matching describe rollups). v3 empty structural `neighbors` hints still never use dot-keys (`_filter_neighbors_dotkey_hints` applies to the empty branch only). `OVERRIDDEN_BY.*` dot-keys remain describe-only. No ontology or re-index change. **What stayed unchanged from the first draft** diff --git a/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md b/propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md similarity index 97% rename from propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md rename to propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md index cba3d86..f170382 100644 --- a/propose/HINTS-V4-SUCCESS-PATH-PROPOSE.md +++ b/propose/completed/HINTS-V4-SUCCESS-PATH-PROPOSE.md @@ -2,7 +2,7 @@ ## Status -**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). +**Status**: completed (landed PRs [#175](https://github.com/HumanBean17/java-codebase-rag/pull/175) neighbors, [#176](https://github.com/HumanBean17/java-codebase-rag/pull/176) find). Tracks [issue #163](https://github.com/HumanBean17/java-codebase-rag/issues/163). Plan: [`plans/completed/PLAN-HINTS-V4.md`](../plans/completed/PLAN-HINTS-V4.md); Cursor prompts: [`plans/completed/CURSOR-PROMPTS-HINTS-V4.md`](../plans/completed/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. diff --git a/tests/test_mcp_hints.py b/tests/test_mcp_hints.py index cf97194..e6ac001 100644 --- a/tests/test_mcp_hints.py +++ b/tests/test_mcp_hints.py @@ -1225,6 +1225,35 @@ def test_hints_find_success_uses_first_result_id_when_multiple() -> None: assert mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=second) not in hints +def test_hints_find_symbol_success_emits_no_v4_followup() -> None: + sym_id = "sym:com.example.T" + payload = _find_success_payload("symbol", sym_id) + hints = generate_hints("find", payload) + v4_markers = ( + mcp_hints.TPL_FIND_SUCCESS_HANDLER, + mcp_hints.TPL_FIND_SUCCESS_HTTP_TARGETS, + mcp_hints.TPL_FIND_SUCCESS_ASYNC_TARGETS, + ) + assert not any(m.format(id=sym_id) in hints for m in v4_markers) + + +def test_hints_find_success_silent_when_first_result_missing_id() -> None: + payload = _find_success_payload("route", "route:unused") + payload["results"] = [{"kind": "route"}] + hints = generate_hints("find", payload) + assert not any("handler:" in h and "EXPOSES" in h for h in hints) + + +def test_hints_find_v2_route_success_emits_handler(kuzu_graph) -> None: + out = find_v2("route", {"path_prefix": "/api"}, graph=kuzu_graph, limit=500, offset=0) + assert out.success is True + assert out.results + rid = out.results[0].id + want = mcp_hints.TPL_FIND_SUCCESS_HANDLER.format(id=rid) + assert want in out.hints + assert mcp_hints.TPL_FIND_PAGE_FULL.format(limit=500) not in out.hints + + def test_hints_find_page_full_requires_has_more_results_flag() -> None: full_page = { "success": True,