test(ai-chat): current-page coverage + getCurrentPage helper (verified live) #112

Closed
Ghost wants to merge 0 commits from feat/ai-chat-current-page-robustness into develop

Фича: надёжная передача «текущей страницы» в AI-агента

Отчёт по скиллу архитектора.

1. Задача

Из docs/backlog/ai-chat-current-page-fragile.md: агент не понимал «эта/текущая страница». Две независимые причины хрупкости: (№1) клиент по исходнику отдавал openPage = null из-за useParams() в pathless layout-роуте; (№2) контекст жил только в system-промпте и мог теряться на прокси-пути (CLIProxyAPI).

2. Результат анализа (verify-before-build)

Обе механики уже реализованы и смержены в develop:

  • Хрупкость №1 (клиент): ada1dce7AiChatWindow перешёл с useParams() на useMatch("/s/:spaceSlug/p/:pageSlug") (ai-chat-window.tsx:159-166), теперь openPage{id,title} резолвится корректно независимо от позиции в дереве роутов.
  • Хрупкость №2 (устойчивость к прокси): добавлен инструмент getCurrentPage (ai-chat-tools.service.ts:218-233), читающий открытую страницу из серверного контекста запроса — идентичность страницы больше не зависит от сохранности system-промпта прокси. Плюс контекст по-прежнему инжектится в system-промпт (ai-chat.prompt.ts:112-118).

Не покрыто было только тестами — это и есть содержание PR.

3. Решение

  • Вынес чистый хелпер resolveCurrentPageResult(openedPage) в новый current-page.util.ts (побайтно повторяет прежний inline-код инструмента getCurrentPage), чтобы его можно было юнит-тестировать без динамически-импортируемого ESM Docmost-клиента; инструмент теперь делегирует ему.
  • current-page.util.spec.ts — 7 кейсов (null/undefined/нет id/пустой id/полный/без title/пустой title).
  • ai-chat.prompt.spec.ts — +8 кейсов на context-строку openedPage (title+pageId присутствуют; fallback «Untitled» для пустого/whitespace title; строки нет при отсутствии/пустом id; порядок — внутри safety-сэндвича, перед закрывающей копией SAFETY_FRAMEWORK).
  • Удалена завершённая планировочная доку.

4. Найденные баги

Ревью-субагент: дрейфа поведения у извлечённого хелпера нет (символ-в-символ), тесты содержательны — APPROVE. Найдено одно постороннее: в рабочем дереве «протёк» несвязанный build-артефакт packages/mcp/build/lib/docmost-schema.js (нода htmlEmbed) — исключён из коммита (git restore), в PR не вошёл.

5. Тестирование — статистика

Юнит-тесты: 2 сьюта / 20 тестов зелёные (jest current-page.util.spec.ts ai-chat.prompt.spec.ts); tsc --noEmit exit 0.

Циклы ревью: 1 (APPROVE с первого прохода; 1 посторонний артефакт убран — не дефект кода).

Живая браузерная проверка: 1 цикл, автономный субагент, headless Chromium, реальный стенд + реальный провайдер (OpenRouter claude-sonnet-latest). Багов: 0.

  • Part A (детерминированно, перехват сети): открыта страница «Quarterly Report Template» → POST /api/ai-chat/stream содержит openPage:{id:"019ee2dc-…", title:"Quarterly Report Template"}; на не-страничном роуте (/home) → openPage = null. ✓ Доказывает фикс хрупкости №1 вживую.
  • Part B (end-to-end, LLM, 1 ход): на вопрос «какую страницу я сейчас смотрю?» агент вызвал инструмент getCurrentPage и ответил «Quarterly Report Template (ID: 019ee2dc-…)». ✓ Опровергает исходный симптом «не вижу текущую страницу».

Скрины: /tmp/f2-page-open.png, f2-chat-sent.png, f2-offpage-sent.png, f2-agent-reply.png.

🤖 Generated with Claude Code

## Фича: надёжная передача «текущей страницы» в AI-агента Отчёт по скиллу архитектора. ### 1. Задача Из `docs/backlog/ai-chat-current-page-fragile.md`: агент не понимал «эта/текущая страница». Две независимые причины хрупкости: (№1) клиент по исходнику отдавал `openPage = null` из-за `useParams()` в pathless layout-роуте; (№2) контекст жил только в system-промпте и мог теряться на прокси-пути (CLIProxyAPI). ### 2. Результат анализа (verify-before-build) **Обе механики уже реализованы и смержены в develop:** - Хрупкость №1 (клиент): `ada1dce7` — `AiChatWindow` перешёл с `useParams()` на `useMatch("/s/:spaceSlug/p/:pageSlug")` (`ai-chat-window.tsx:159-166`), теперь `openPage{id,title}` резолвится корректно независимо от позиции в дереве роутов. - Хрупкость №2 (устойчивость к прокси): добавлен инструмент `getCurrentPage` (`ai-chat-tools.service.ts:218-233`), читающий открытую страницу из серверного контекста запроса — идентичность страницы больше не зависит от сохранности system-промпта прокси. Плюс контекст по-прежнему инжектится в system-промпт (`ai-chat.prompt.ts:112-118`). Не покрыто было только тестами — это и есть содержание PR. ### 3. Решение - Вынес чистый хелпер `resolveCurrentPageResult(openedPage)` в новый `current-page.util.ts` (побайтно повторяет прежний inline-код инструмента `getCurrentPage`), чтобы его можно было юнит-тестировать без динамически-импортируемого ESM Docmost-клиента; инструмент теперь делегирует ему. - `current-page.util.spec.ts` — 7 кейсов (null/undefined/нет id/пустой id/полный/без title/пустой title). - `ai-chat.prompt.spec.ts` — +8 кейсов на context-строку `openedPage` (title+pageId присутствуют; fallback «Untitled» для пустого/whitespace title; строки нет при отсутствии/пустом id; порядок — внутри safety-сэндвича, перед закрывающей копией SAFETY_FRAMEWORK). - Удалена завершённая планировочная доку. ### 4. Найденные баги **Ревью-субагент:** дрейфа поведения у извлечённого хелпера нет (символ-в-символ), тесты содержательны — **APPROVE**. Найдено одно постороннее: в рабочем дереве «протёк» несвязанный build-артефакт `packages/mcp/build/lib/docmost-schema.js` (нода htmlEmbed) — **исключён из коммита** (`git restore`), в PR не вошёл. ### 5. Тестирование — статистика **Юнит-тесты:** 2 сьюта / **20 тестов** зелёные (`jest current-page.util.spec.ts ai-chat.prompt.spec.ts`); `tsc --noEmit` exit 0. **Циклы ревью:** 1 (APPROVE с первого прохода; 1 посторонний артефакт убран — не дефект кода). **Живая браузерная проверка:** 1 цикл, автономный субагент, headless Chromium, реальный стенд + реальный провайдер (OpenRouter claude-sonnet-latest). Багов: **0**. - **Part A (детерминированно, перехват сети):** открыта страница «Quarterly Report Template» → POST `/api/ai-chat/stream` содержит `openPage:{id:"019ee2dc-…", title:"Quarterly Report Template"}`; на не-страничном роуте (`/home`) → `openPage = null`. ✓ Доказывает фикс хрупкости №1 вживую. - **Part B (end-to-end, LLM, 1 ход):** на вопрос «какую страницу я сейчас смотрю?» агент **вызвал инструмент getCurrentPage** и ответил «Quarterly Report Template (ID: 019ee2dc-…)». ✓ Опровергает исходный симптом «не вижу текущую страницу». Скрины: `/tmp/f2-page-open.png`, `f2-chat-sent.png`, `f2-offpage-sent.png`, `f2-agent-reply.png`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-06-21 06:22:24 +03:00
The 'current page' feature (client useMatch openPage + server getCurrentPage
tool + system-prompt injection) was already implemented & merged; this backfills
its missing test coverage and removes the completed backlog doc.

- extract pure resolveCurrentPageResult(openedPage) into current-page.util.ts
  (byte-identical to the prior inline getCurrentPage tool body) so it is
  unit-testable without the dynamically-imported ESM Docmost client; the tool
  now delegates to it.
- current-page.util.spec.ts: 7 cases (null/undefined/no-id/empty-id/full/no-title).
- ai-chat.prompt.spec.ts: +8 cases for the openedPage context line (title+pageId
  present, Untitled fallback for blank/whitespace title, no line when absent/blank
  id, and sandwich ordering before the trailing safety block).

Verified live in-browser: client sends openPage{id,title} on a page and null
off-page; the agent invokes getCurrentPage and answers with the real title+id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ghost closed this pull request 2026-06-21 14:48:38 +03:00

Pull request closed

Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#112