diff --git a/docs/archive/eval_system_design_spec.md b/docs/archive/eval_system_design_spec.md new file mode 100644 index 0000000..335510d --- /dev/null +++ b/docs/archive/eval_system_design_spec.md @@ -0,0 +1,289 @@ +> **Archived / obsolete.** This document was an early design spec. The eval system is now implemented; see [`docs/eval_system.md`](../eval_system.md) for the current user guide and behavior. + +# Eval System — Design Spec + +LLM-as-judge evaluation of Navi sessions. Tracks quality dynamics over time without dedicated test scenarios — analysis runs against real, unmodified sessions. + +Status: **spec / not implemented**. + +## Goals + +1. See how Navi's quality changes over time across multiple axes. +2. Detect regressions after prompt/model/architecture changes. +3. Surface concrete sessions for inspection (best, worst, biggest deltas). +4. No special test fixtures — evaluation runs against real usage. + +## Non-goals + +- Absolute "correctness" of scores. We care about **dynamics**, not whether a 75 is "really a 75". +- Real-time scoring during sessions. Eval is offline. +- Verification of factual claims (judge can't run code; this is a known limit). + +## Architecture + +Three parts, deliberately decoupled: + +1. **In-app feedback signal** — like/dislike per assistant response in the main webclient, stored alongside messages. +2. **Eval runner (CLI)** — standalone, runs offline against PostgreSQL, evaluates accumulated sessions, writes scores to eval tables. Does not require the FastAPI server to be running. +3. **Eval UI (debug page)** — read-only SPA for browsing sessions / scores / charts, plus a button to trigger an eval run on the server. Lives under `debug/eval/`, served as static and pulls data through a small REST namespace `/api/eval/...`. + +### Directory layout + +Everything for the eval system lives under `debug/eval/`. The directory contains both the standalone Python backend (CLI + REST router) and the frontend SPA. `debug/eval/` is a Python package (`__init__.py`) so the CLI can be invoked as `python -m debug.eval` from the project root. + +``` +debug/eval/ + __init__.py + cli.py # entry: python -m debug.eval ... + judge.py # judge orchestration (3 experts, averaging) + schema.py # Pydantic models for scores / requests + db.py # asyncpg queries for feedback + evaluations + api.py # FastAPI APIRouter, mounted from navi/main.py + # GET /api/eval/sessions + # GET /api/eval/sessions/{id} + # GET /api/eval/stats + # POST /api/eval/run (background task) + # POST /api/eval/feedback (like/dislike) + index.html # frontend SPA (matches debug/index.html style) + app.js + style.css + prompts/ + expert_strict_critic.txt + expert_pragmatist.txt + expert_tech_lead.txt + rubric_v1.yaml # axes + anchors (frozen per version) + schema.sql # postgres migration (eval_v1) + README.md # ops doc — running CLI, applying migration +``` + +`navi/main.py` adds two lines: include the eval router and serve `debug/eval/index.html` at `/debug/eval/`. Everything else stays out of `navi/`. + +The webclient (`webclient/`) gets a small addition: like/dislike thumbs on each assistant message that POST to `/api/eval/feedback`. That's the only touchpoint outside `debug/eval/`. + +### What the judge sees + +Maximum signal — the judge gets the full session, no filtering, no compression-summary substitution. + +- **Full transcript** in original order: user / assistant / tool calls + tool results / thinking blocks / sub-agent transcripts (recursively, with depth markers) / planning phases (Phase 1 analysis, Phase 2 review, Phase 3 plan) — exactly as they appeared. +- **Per-message feedback ratings** inlined next to each assistant message ("[user reaction: 👍]" / "[user reaction: 👎]" / nothing). +- **Aggregated counts** at the top: total likes, total dislikes. +- **Profile metadata**: which profile ran, model used, planning flags state at the time. +- **Session timing**: start, end, duration, iteration count, total tokens. + +We do **not** substitute compressed summaries for the original messages — that would hide the actual work and only let the judge grade the final outcome. The point is to grade the **process**. + +If a session is too long for the judge's context, the runner logs a warning and skips it (or chunks by user-turn group with explicit gaps — TBD; v1 just skips). + +## Signal sources + +When evaluating a session, the judge LLM has access to: + +- Full session transcript (user / assistant / tool calls / thinking). +- Per-message likes/dislikes from the user. +- The user's own follow-up text in chat ("не работает", "переделай", "спасибо") — judge extracts implicit signal. + +Aggregated like/dislike counts are computed before judge runs. If `likes > dislikes` → tilt toward "successful". If `dislikes > likes` → tilt toward "unsuccessful". If both 0 → judge infers from transcript only. + +## Axes + +Fixed set, scored 0-100 (no hard upper limit — see "Open scale" below): + +| Axis | Meaning | +|---|---| +| `task_complexity` | Difficulty of what was asked, judged from the user's request alone | +| `goal_completion` | Did the user end up with what they wanted | +| `tool_usage_quality` | Right tools chosen, no thrashing, no unnecessary calls | +| `efficiency` | Iterations vs result; loops, dead-ends, redundancy | +| `communication` | Clarity of replies, no hallucinations, no excessive verbosity | +| `subagent_orchestration` | Quality of sub-agent delegation (null if no sub-agents used) | +| `self_extension` | Quality of write_tool / reload_tools usage (null if not used) | + +The judge sees the planning structure as part of the transcript, but the rubric does **not** ask for separate scores per planning phase. The judge instructions deliberately stay at "did the agent reason / execute / communicate well" — the architectural details of how planning runs are not evaluated, since those are the very things we're trying to measure progress on. Coupling the rubric to current planning shape would lock the eval to today's mechanics. + +Scoring scale anchors (designed once, frozen as `rubric_v1`): + +- **10** — trivial, near-zero effort. +- **30** — straightforward, one tool, one step. +- **50** — moderate, 2-4 steps, planning helpful. +- **75** — complex, multi-tool with planning, easy to fail. +- **100** — at the limit of what Navi can do today (full project tasks, multiple sub-agents, self-extension). + +Anchors include **2-3 real session examples** at each level (user picks them once from accumulated history). + +### Open scale + +Scale is **not capped at 100**. If the judge encounters a task harder than any 100-anchor, it scores 120, 150, etc. Those become future anchors when we expand the rubric. + +## Experts (multi-judge averaging) + +Each session is evaluated by **3 different expert prompts**, then averaged. Different prompts produce different blind spots; averaging reduces variance and bias. + +| Expert | Prompt slant | +|---|---| +| `strict_critic` | Looks for flaws, scores conservatively, penalizes weakly any slip-up | +| `pragmatist` | "Did the user end up with what they wanted, regardless of the path?" | +| `tech_lead` | Architecture / tool choice / efficiency, focused on technical decisions | + +All three see the same transcript and the same rubric. Final per-axis score = mean across experts. Spread between experts is also stored — large spread = noisy/contested session. + +## Storage (PostgreSQL) + +Append-only. Multiple evals per session are normal (re-evaluation when judge upgrades, rubric changes, or you just want a fresh take). + +```sql +-- Per-message user feedback (drives the like/dislike signal) +CREATE TABLE message_feedback ( + message_id UUID PRIMARY KEY REFERENCES messages(id), + session_id UUID NOT NULL, + rating SMALLINT NOT NULL, -- +1 / -1 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX ON message_feedback(session_id); + +-- One row per (session, expert, eval_run) +CREATE TABLE evaluations ( + id UUID PRIMARY KEY, + session_id UUID NOT NULL, + eval_run_id UUID NOT NULL, -- groups the 3 experts of one run + eval_date TIMESTAMPTZ NOT NULL, + judge_model TEXT NOT NULL, -- e.g. "gemma4:31b-cloud" + judge_version TEXT NOT NULL, -- snapshotted version string + rubric_version TEXT NOT NULL, -- "v1", "v2", ... + expert_id TEXT NOT NULL, -- "strict_critic" | "pragmatist" | "tech_lead" + scores JSONB NOT NULL, -- {task_complexity: 65, goal_completion: 80, ...} + comment TEXT NOT NULL -- free-form "what stood out" +); +CREATE INDEX ON evaluations(session_id); +CREATE INDEX ON evaluations(eval_date); +CREATE INDEX ON evaluations(judge_version, rubric_version); + +-- View: averaged scores per session per eval_run +CREATE VIEW evaluation_summary AS +SELECT + session_id, + eval_run_id, + eval_date, + judge_version, + rubric_version, + jsonb_object_agg( + axis, + avg_score + ) AS avg_scores +FROM ( + SELECT + session_id, eval_run_id, eval_date, judge_version, rubric_version, + key AS axis, + AVG((value)::numeric) AS avg_score + FROM evaluations, jsonb_each_text(scores) + GROUP BY session_id, eval_run_id, eval_date, judge_version, rubric_version, key +) t +GROUP BY session_id, eval_run_id, eval_date, judge_version, rubric_version; +``` + +## Judge model policy + +- Judge model is **pinned** in eval config. Don't change casually. +- When you do upgrade the judge, **re-evaluate the entire archive** with the new judge. Old scores stay (different `judge_version` row), new scores are the new baseline. +- Comparisons across `judge_version` boundaries are not meaningful — visualizations should respect this. + +## Rubric versioning + +Same policy. Rubric changes (new anchors, reworded prompts) bump `rubric_version`. Old rows preserved, new ones are the live series. + +## CLI + +Standalone, no server dependency. + +```bash +# Evaluate all unevaluated sessions (with current pinned judge + rubric) +python -m navi.eval run + +# Re-evaluate everything (after judge or rubric change) +python -m navi.eval run --re-evaluate-all + +# Evaluate a single session +python -m navi.eval run --session + +# Limit to recent +python -m navi.eval run --since 2026-04-01 + +# Show eval for one session +python -m navi.eval show + +# Aggregate stats +python -m navi.eval stats --days 30 +python -m navi.eval stats --days 30 --by-complexity-bucket +``` + +`stats` exports CSV by default; visualization is a separate concern (see below). + +## UI (`debug/eval/index.html`) + +Single-page debug SPA in the same style as the existing `debug/index.html` (dark mono theme, no framework). Tabbed layout: + +### Tab 1 — Sessions +Paginated table of all sessions, newest first. Columns: started_at, profile, turns count, likes / dislikes, last avg score (or "—"), eval status (`evaluated rubric_v1` / `pending` / `stale judge_v1 → v2`). Row click → Tab 2 with that session preselected. + +Filters at top: profile, date range, "show only unevaluated", "show only stale". + +### Tab 2 — Session detail +Two-pane layout. Left: transcript (collapsed by default; user / assistant / tool-call / sub-agent indented). Right: eval results. + +- All eval runs for this session listed (most recent first), each expandable. +- Inside an eval run: 3 expert blocks side-by-side with their per-axis scores, the spread, and free-form comment. +- Avg row at top of run with `(judge_version, rubric_version, eval_date)`. +- Action button: "Re-evaluate this session". + +### Tab 3 — Stats +Charts (server-rendered SVG or simple canvas, no chart library): + +1. Average score per axis over time — weekly rolling mean. +2. Score by complexity bucket (`0-25`, `26-50`, `51-75`, `76+`) — per-bucket trend, catches selection bias when overall score moves. +3. Likes / dislikes ratio per week — orthogonal sanity check. +4. Top-K worst sessions in the last 7 days — clickable, jumps to Tab 2. + +Filter bar: judge_version + rubric_version (mixing across versions disabled by default). + +### Tab 4 — Run +Trigger an eval run. +- Form: scope (`all unevaluated` / `since date` / `single session id` / `re-evaluate all`), max sessions, dry-run checkbox. +- Submit → POST `/api/eval/run`, server kicks off the CLI as a background task and returns a `run_id`. +- Live log panel below subscribes to a small WS or SSE stream and prints progress: "session N/M, expert K/3, scores …". +- Run history table at the bottom: past runs with timestamp, count of sessions, judge_version, status. + +CSV export available on Tab 3 for offline plotting. + +## Implementation phases + +1. **Phase 1 — Feedback signal** + - `message_feedback` postgres table + migration. + - Webclient UI: thumbs up/down on each assistant message, REST POST to `/api/eval/feedback`. + - Endpoint `POST /api/eval/feedback {message_id, rating}` — upsert. +2. **Phase 2 — Eval backend skeleton** + - `navi/eval/` package with CLI entry point (`python -m navi.eval`). + - `evaluations` table + migration. + - Judge prompt templates per expert (`prompts/expert_*.txt`). + - Rubric anchors as YAML (`prompts/rubric_v1.yaml`) — anchor examples filled in by user before going live. +3. **Phase 3 — Run + store** + - `run` command: pick unevaluated sessions, render full transcript, fan out to 3 experts, validate JSON output against pydantic schema, persist all expert rows under one `eval_run_id`. +4. **Phase 4 — Read endpoints** + - `/api/eval/sessions`, `/sessions/{id}`, `/stats` — read-only, used by debug UI. + - `/api/eval/run` (POST) — kicks off CLI in a background task, returns `run_id`. SSE/WS stream for live progress. +5. **Phase 5 — Debug UI** + - `debug/eval/index.html` + `app.js` + `style.css` in the existing debug-SPA style. + - All four tabs (Sessions / Detail / Stats / Run) wired to the endpoints above. + +CSV export from `python -m navi.eval stats --csv` is also available as a pure-CLI path for offline plotting. + +## Costs / constraints + +- 3 experts × full session transcript per run. For 50-turn sessions with 50k+ token contexts that's 3 large LLM calls per session. Plan to run overnight on a small batch, not in real time. +- Judge calls go through the same backend stack (`FallbackOllamaBackend`) — so multi-server fallback applies. +- Eval runner should respect a `--max-tokens-per-session` guard so a runaway transcript doesn't burn the queue. + +## Known limits / open questions + +- No verification of factual or code correctness — judge sees only the transcript. For "did this code actually work?" we'd need separate runtime checks; out of scope here. +- Judge bias toward verbose / confident answers is not fully mitigated by 3 experts — partial only. +- Calibration set (manual scoring of N sessions to validate judge against user) is **deliberately skipped** — we only need dynamics, not absolute correctness. Re-open if the trends turn out to be uninterpretable. +- Rubric anchors must be set with care; once the archive is large, changing the rubric forces re-eval of everything. diff --git a/docs/archive/plan_navi_code_done.md b/docs/archive/plan_navi_code_done.md new file mode 100644 index 0000000..682760b --- /dev/null +++ b/docs/archive/plan_navi_code_done.md @@ -0,0 +1,251 @@ +> **Archived / done.** This plan was implemented. For current usage see [`docs/navi_code.md`](../navi_code.md) and [`docs/navi_code_cli.md`](../navi_code_cli.md). + +# План: Navi Code — локальный терминальный клиент для Navi + +## Цель + +Создать систему "Navi Code": локально запускаемый вариант Navi, управляемый через терминал. Без авторизации (`NAVI_AUTH_ENABLED=false`), с выделенным профилем, ориентированным на работу с кодом, терминалом и файловой системой. + +## Что НЕ входит в этот этап + +- Docker-упаковка (отложено). +- Рендеринг изображений, content_publish, share_file UI (отложено). +- Авторизация (используем готовый `NAVI_AUTH_ENABLED=false`). + +## Что входит в этот этап + +1. Создание профиля `navi_code` на базе `developer`. +2. Механизм дефолтного профиля. +3. Подготовка bundled `.env` / конфигурации для локального терминального режима. +4. Подготовка персоны / системного промпта для Navi Code. +5. CLI-терминал-клиент для взаимодействия с Нави. +6. Документация по запуску и использованию. + +--- + +## 1. Профиль `navi_code` + +### 1.1. База + +- Скопировать `navi/profiles/developer/` → `navi/profiles/navi_code/`. +- `id`: `navi_code`. +- `name`: `"Navi Code"`. + +### 1.2. Тюнинг инструментов + +Включить: + +- `terminal` — основной инструмент. +- `filesystem` — чтение/запись файлов. +- `code_exec` — выполнение кода. +- `spawn_agent` — для сложных подзадач. +- `list_tools`, `tool_manual`, `write_tool`, `reload_tools` — саморасширение. +- `scratchpad`, `todo`, `reflect` — для планирования. + +Исключить (для упрощения терминального опыта): + +- `share_file`. +- `content_publish`. +- `image_view`. +- `http_request` — оставить по необходимости, но по умолчанию убрать. +- `web_search`, `ssh_exec` — оставить как опцию, но не включать по умолчанию. + +### 1.3. Тюнинг параметров + +- `max_iterations`: сохранить высокое значение (например, 100), но не безгранично. +- `temperature`: 0.3–0.4. +- `model`: локальная модель по умолчанию, например `gemma4:26b-a4b-it-q4_K_M`. +- Планирование: включить phase 1 и 3, отключить phase 2 (3 advisor) для снижения latency. +- `iteration_budget_enabled`, `goal_anchoring_enabled`, `anti_stall_enabled`: оставить включёнными. +- `step_validation_enabled`: отключить. +- `adaptive_replan_enabled`: оставить выключенным. + +### 1.4. Системный промпт + +- Скопировать `developer/system_prompt.txt`. +- Адаптировать под терминальный контекст: Нави работает локально, у неё есть терминал, файловая система и возможность выполнять код. +- Добавить инструкции по безопасности: перед разрушительными операциями (`rm`, перезапись) спрашивать подтверждение. + +--- + +## 2. Механизм дефолтного профиля + +### 2.1. Варианты + +Вариант A — env-переменная (предпочтительный): + +- Добавить в `navi/config.py`: `navi_default_profile_id: str = ""`. +- Читать `NAVI_DEFAULT_PROFILE_ID` из `.env`. +- Если задана и профиль существует, использовать её как fallback при создании сессии без `profile_id`. +- REST `POST /sessions` разрешить отсутствие `profile_id`, взяв дефолт. + +Вариант B — клиент-side: + +- Терминал-клиент сам знает профиль `navi_code` и всегда шлёт его. +- Проще, но менее универсально. + +### 2.2. Решение + +Реализовать **вариант A**: серверная env-переменная + поддержка отсутствующего `profile_id` в `POST /sessions`. Это позволит любому клиенту (CLI, веб, скрипт) работать с дефолтным профилем. + +--- + +## 3. Конфигурация для локального режима + +### 3.1. Новые/изменённые env-переменные + +- `NAVI_AUTH_ENABLED=false` (уже есть). +- `NAVI_DEFAULT_PROFILE_ID=navi_code` (новая). +- `NAVI_PERSONA_FILE=persona_navi_code.txt` (новая персона). +- `FS_ALLOWED_PATHS=*`. +- `TERMINAL_ALLOWED_COMMANDS=*`. +- `OLLAMA_HOST=http://localhost:11434`. +- `DATABASE_URL=postgresql://navi:navipass@localhost:5432/navidb` (или локальная настройка пользователя). + +### 3.2. Файлы, которые нужно подготовить + +- `.env.navi_code.example` — пример `.env` для Navi Code. +- `persona_navi_code.txt` — глобальная персона для Navi Code. + +### 3.3. Что не меняем + +- Структура конфигурации (`navi/config.py`) — добавляем только новые поля. +- Поведение при `NAVI_AUTH_ENABLED=true` — не ломаем. + +--- + +## 4. Персона Navi Code + +### 4.1. Основные черты + +- Локальная ассистентка-разработчик. +- Имеет доступ к терминалу, файловой системе и выполнению кода. +- Умеет планировать, разбивать задачи на todo, работать с spawn_agent. +- Перед опасными операциями спрашивает подтверждение. +- Говорит с пользователем на его языке (русский/английский). + +### 4.2. Правила работы с инструментами + +- Использует `terminal` для shell-команд. +- Использует `filesystem` для чтения/записи. +- Использует `code_exec` для быстрой проверки небольших фрагментов. +- Использует `scratchpad` для длительных мыслей, `todo` для планирования. +- Перед `write_tool` всегда вызывает `tool_manual("write_tool")`. + +--- + +## 5. CLI-терминал-клиент + +### 5.1. Расположение + +- `navi_code_cli/` в корне проекта (отдельный Python-пакет). +- Или `clients/terminal/`. + +### 5.2. Минимальный функционал MVP + +- Подключение к запущенному Navi backend по WebSocket. +- Поддержка интерактивного режима (`navi-code` без аргументов → чат). +- Поддержка one-shot режима (`navi-code "задача"` → выполнить и выйти). +- Сохранение `session_id` между запусками (`~/.navi_code/state.json`). +- Поддержка команд: + - `/new` — новая сессия, + - `/sessions` — список сессий, + - `/switch ` — переключиться, + - `/profile` — показать текущий профиль, + - `/quit` — выход. + +### 5.3. Рендеринг событий + +- `stream_delta` — печатать текст. +- `thinking_delta` / `thinking_end` — показывать в сворачиваемом блоке или с флагом `--show-thinking`. +- `tool_started` / `tool_call` — показывать имя инструмента и результат. +- `stream_end` — завершение ответа. +- `error` — красным цветом. + +### 5.4. Зависимости + +- `click` или `typer`. +- `websockets`. +- `rich` — для цветного вывода, markdown, таблиц. +- `pydantic` — для моделей. + +### 5.5. Взаимодействие с сервером + +- `GET /agents/profiles` — проверить профиль. +- `POST /sessions` — создать сессию (с дефолтным профилем). +- `WS /ws/sessions/` — основной чат. +- `POST /sessions//stop` — остановить генерацию. + +--- + +## 6. Документация + +### 6.1. Новые документы + +- `docs/navi_code.md` — полное руководство по Navi Code. +- `docs/navi_code_cli.md` — документация по CLI. +- `docs/profiles.md` — обновить: добавить профиль `navi_code`, описать `NAVI_DEFAULT_PROFILE_ID`. +- `docs/config.md` — обновить: новые env-переменные. + +### 6.2. README + +- Добавить раздел "Navi Code" в основной README. + +--- + +## 7. Порядок реализации + +### Этап 1 — Профиль и конфигурация + +1. Создать `navi/profiles/navi_code/` на базе `developer/`. +2. Добавить `navi_default_profile_id` в `navi/config.py`. +3. Обновить `POST /sessions` для использования дефолтного профиля. +4. Создать `persona_navi_code.txt`. +5. Создать `.env.navi_code.example`. +6. Обновить документацию (`docs/profiles.md`, `docs/config.md`). + +### Этап 2 — CLI клиент + +1. Создать структуру `navi_code_cli/`. +2. Реализовать WebSocket-клиент. +3. Реализовать интерактивный режим. +4. Реализовать one-shot режим. +5. Реализовать сохранение состояния сессии. +6. Добавить README и документацию. + +### Этап 3 — Тестирование и полировка + +1. Проверить создание сессии с дефолтным профилем. +2. Проверить работу терминала через CLI. +3. Проверить персистентность сессий. +4. Проверить no-auth режим. +5. Добавить юнит-тесты на новые механизмы. + +--- + +## 8. Риски и вопросы + +### Риски + +- **Безопасность:** `TERMINAL_ALLOWED_COMMANDS=*` и admin-роль дают полный доступ к системе. Нужно ясно документировать, что Navi Code предназначена только для локального использования. +- **Зависимость от Ollama:** пользователь должен сам запускать Ollama. Нужно документировать. +- **Postgres:** нужен локальный Postgres с pgvector. Возможно, стоит позже предоставить docker-compose для БД. +- **Приватная зависимость `gnexus-auth-client-py`:** при сборке/установке может потребоваться доступ к Git. Для локальной разработки текущий venv уже настроен. + +### Открытые вопросы + +- Как именно CLI должен обрабатывать persistent терминалы? Сразу в интерактивном режиме или через отдельную команду? +- Нужна ли команда `/cd` для смены рабочей директории в CLI? +- Стоит ли добавить provider контекста (`cwd_provider`) или передавать cwd через параметры CLI? + +--- + +## 9. Критерии завершения + +- [ ] Профиль `navi_code` создан и загружается. +- [ ] `NAVI_DEFAULT_PROFILE_ID` работает, `POST /sessions` без `profile_id` создаёт сессию с дефолтным профилем. +- [ ] Персона Navi Code подключена через `NAVI_PERSONA_FILE`. +- [ ] CLI клиент умеет подключаться и вести диалог. +- [ ] CLI клиент сохраняет `session_id` между запусками. +- [ ] Документация обновлена. +- [ ] Все тесты проходят. diff --git a/docs/archive/plan_navi_code_tui_done.md b/docs/archive/plan_navi_code_tui_done.md new file mode 100644 index 0000000..5cbe9ce --- /dev/null +++ b/docs/archive/plan_navi_code_tui_done.md @@ -0,0 +1,136 @@ +> **Archived / done.** This plan was implemented. For current usage see [`docs/navi_code_cli.md`](../navi_code_cli.md). + +# План: Navi Code TUI (OpenCode-style) + +Цель: превратить `navi-code` из простого click-CLI в полноэкранный терминальный UI, вдохновлённый OpenCode, сохранив click-CLI как `navi-code --raw`. + +--- + +## Принципы + +- **Микро-архитектура**: каждый компонент отвечает за одну задачу, общаётся через события/шину. +- **Расширяемость**: новые slash-команды, виджеты, renderers, themes добавляются без переделки ядра. +- **Совместимость**: TUI и click-CLI используют общий `ws_client.py`, `api.py`, `config.py`, `state.py`. +- **Постепенность**: каждая фаза — отдельный коммит, после которого продукт работает. + +--- + +## Общая архитектура + +``` +┌─────────────────────────────────────────────────────────────┐ +│ NaviCodeApp (Textual) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ +│ │ ChatPanel │ │ StatusPanel │ │ SessionsPanel │ │ +│ │ (messages) │ │ (profile, │ │ (optional/right)│ │ +│ │ │ │ model, │ │ │ │ +│ │ │ │ connection) │ │ │ │ +│ └──────────────┘ └──────────────┘ └──────────────────┘ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ InputBox (prompt frame, slash commands, @/! parsing) │ │ +│ └────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ EventBus / Dispatcher │ +│ - WebSocket events → ChatPanel/StatusPanel │ +│ - User input → CommandParser → execute command/send message │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Shared services │ +│ - NaviWebSocketClient │ +│ - api (REST wrappers) │ +│ - StateManager (~/.navi_code/state.json) │ +│ - Settings │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Фазы + +### Phase 3 — TUI skeleton + +- Добавить `textual>=0.70` в `pyproject.toml`. +- Создать `clients/terminal/tui_app.py` с базовым `App`: + - `ChatPanel` — `ScrollableContainer` + RichLog для сообщений. + - `StatusPanel` — Static/Label с профилем, сессией, моделью, статусом. + - `InputBox` — кастомный виджет ввода с рамкой в стиле OpenCode. +- `navi-code` по умолчанию запускает TUI; `navi-code --raw` — старый click-CLI. +- Интегрировать `NaviWebSocketClient` с Textual event loop через `asyncio.create_task` + `call_from_thread`/`post_message`. +- Базовый рендеринг событий: `stream_delta`, `thinking_delta`/`thinking_end`, `tool_started`/`tool_call`, `error`, `stream_end`. +- Добавить `TuiRenderer`, который превращает WebSocket-события в Rich renderables. +- Обновить тесты: хотя бы один smoke-test, что TUI App монтируется и запускается без ошибок. + +### Phase 4 — OpenCode UX + +- **Slash commands**: `/help`, `/new`, `/sessions`, `/switch`, `/profile`, `/thinking`, `/compact`, `/quit`, `/models`. +- **Command palette**: `Ctrl+P`, поиск по командам и настройкам. +- **`@` file references**: fuzzy autocomplete файлов в CWD при вводе `@`. +- **`!` shell pre-command**: если сообщение начинается с `!`, выполнить shell и подставить вывод. +- **Permission prompt**: inline prompt для destructive tool calls (`rm`, overwrite, format), кнопки Allow once / Allow always / Reject. +- **Markdown/code highlighting**: `rich.markdown` + `rich.syntax` в ChatPanel. +- **Diff/artifact renderers**: расширяемый `ContentRenderer` registry — code, diff, plain, image mention. + +### Phase 5 — Polish & config + +- **Mouse support** включить в Textual. +- **Themes**: `/themes` + `~/.navi_code/tui.json` с `theme`, `keybinds`, `diff_style`, `mouse`, `scroll_speed`. +- **SessionsPanel**: боковая панель со списком сессий, переключение по клику/стрелкам. +- **Export**: `/export` сохраняет текущий чат в markdown и открывает `$EDITOR`. +- **Advanced status panel**: tokens used, remaining iterations, backend, connection health. +- **Undo/Redo**: если получится интегрировать с git — отдельно. +- **Тесты**: unit + TUI integration tests через Textual Pilot. + +--- + +## Расширяемые точки + +1. **Command registry** (`clients/terminal/commands/registry.py`) + - Каждая slash-команда = класс с `name`, `aliases`, `description`, `keybind`, `async execute(ctx)`. + - Регистрация через декоратор `@register_command`. + +2. **Content renderers** (`clients/terminal/renderers/`) + - `BaseRenderer` → `CodeRenderer`, `DiffRenderer`, `MarkdownRenderer`, `ToolCallRenderer`, `ErrorRenderer`. + - `RendererRegistry` выбирает по `type`/`mime`. + +3. **Themes** (`clients/terminal/themes/`) + - `Theme` dataclass: цвета рамок, фона, акцента, статуса, ошибок, thinking. + - `ThemeRegistry` с built-in темами и загрузкой из `tui.json`. + +4. **Event bus** (`clients/terminal/events.py`) + - Textual-native `post_message`, но с типизированными событиями `WsEvent`, `CommandEvent`, `PermissionEvent`. + +5. **Permission engine** (`clients/terminal/permissions.py`) + - Правила по имени инструмента + action/pattern. + - `PermissionStore` хранит `allow_always` в `~/.navi_code/permissions.json`. + +--- + +## Интеграция с существующим CLI + +- `cli.py` остаётся, получает флаг `--raw`. +- `tui_app.py` импортирует `Settings`, `StateManager`, `api`, `NaviWebSocketClient`. +- `render.py` остаётся для `--raw`; TUI использует новые renderers поверх Rich. + +--- + +## Тестирование + +- `tests/clients/test_tui_app.py` — монтирование App, проверка layout. +- `tests/clients/test_tui_commands.py` — unit tests командного парсера и registry. +- `tests/clients/test_tui_renderers.py` — рендеринг разных типов контента. +- `tests/clients/test_tui_permissions.py` — permission prompt и `allow_always`. +- Smoke test: `navi-code --help` и `navi-code --version` работают в обоих режимах. + +--- + +## Критерий завершения + +- `navi-code` запускается в полноэкранном TUI. +- Click-CLI доступен через `navi-code --raw`. +- Все новые файлы покрыты тестами, ruff чистый, pytest зелёный. +- Документация `docs/navi_code_cli.md` обновлена с TUI-режимом.