docs(brain): brain dashboard design spec
Standalone HTML dashboard that visualises the observer episode log over the automation-graph topology — 4 views (map / task-replay / session feed / aggregate), graph as shared canvas, 3-phase build order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1475,3 +1475,11 @@ DWC
|
||||
инжектим
|
||||
фикстурный
|
||||
роута
|
||||
|
||||
# Brain dashboard design spec (2026-05-19)
|
||||
визуализирующий
|
||||
анимируются
|
||||
неподсвеченными
|
||||
полл
|
||||
инференс
|
||||
вендорено
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Дашборд мозга — дизайн
|
||||
|
||||
**Дата:** 2026-05-19
|
||||
**Статус:** согласован в brainstorming-сессии, готов к writing-plans
|
||||
**Источник:** brainstorming-сессия Claude + Дмитрий
|
||||
|
||||
## 1. Контекст и проблема
|
||||
|
||||
`docs/automation-graph.html` («карта мозга») — статический снимок: 124 узла, 130 рёбер, 11 размеченных конфликтов, плюс ручная теплокарта `NODE_META` за фиксированное окно. Карта показывает *топологию на момент*, но не показывает *работу*: как решались задачи, какие узлы задействованы, где возникли ошибки и ретраи, куда перенаправлялось, где сталкивались узлы.
|
||||
|
||||
Журнал исполнения уже существует — `docs/observer/episodes-YYYY-MM.jsonl`: наблюдатель (ADR-011) пишет одну запись на каждый ход (Stop-событие). В записи — выбранный узел, инструменты, ошибки, ретраи, перенаправления, hard-floor, классификация задачи, окружение. **Данные есть — нет визуализации.** Карта и журнал между собой не связаны.
|
||||
|
||||
## 2. Цель и не-цели
|
||||
|
||||
**Цель:** standalone-дашборд, визуализирующий журнал эпизодов поверх топологии карты — три способа смотреть на работу мозга плюс сама карта.
|
||||
|
||||
**В scope:**
|
||||
|
||||
- Чтение `episodes-*.jsonl` (схема v1 и v2).
|
||||
- Четыре view: Карта, Разбор задачи, Лента сессии, Агрегат.
|
||||
- Граф как общий холст для траекторий и тепла.
|
||||
- Три слоя «конфликтов» (см. §6).
|
||||
|
||||
**Не-цели (YAGNI):**
|
||||
|
||||
- Не меняем формат эпизодов и логику наблюдателя (исключение — отдельная задача, §13).
|
||||
- Не правим `/brain-retro` и контролёры.
|
||||
- Не пиксель-полировка — Forest накладывается на этапе реализации (frontend-design).
|
||||
- Нет истории до запуска наблюдателя — её физически нет.
|
||||
- Не Vue/Vuetify-приложение — это dev-инструмент, zero-build (см. §3).
|
||||
|
||||
## 3. Зафиксированные решения
|
||||
|
||||
**Тех-модель: standalone HTML + локальный статик-сервер.** Один HTML-файл без сборки; читает свежий JSONL через статик-сервер (`fetch` с `file://` браузер блокирует). Сервер — ~20 строк на `node:http`, запуск npm-командой, гасится по Ctrl-C. Ноль новых npm-зависимостей, ноль постоянных демонов. Отвергнуты: «запекаемый файл» (генератор вшивает данные — лента не живая) и «Vue/Vuetify-приложение» (сборка + стек портала в dev-инструменте; vis.js всё равно не Vue).
|
||||
|
||||
**Раскладка: граф баннером сверху.** Постоянная полоса графа сверху, рабочая зона view снизу; переключатель view меняет нижнюю зону. Граф всегда виден; широкие таблицы и ленты снизу.
|
||||
|
||||
## 4. Архитектура
|
||||
|
||||
### 4.1. Слой данных
|
||||
|
||||
Источник — `docs/observer/episodes-YYYY-MM.jsonl`, append-only, одна строка = один эпизод.
|
||||
|
||||
Сервер `tools/brain-dashboard-server.mjs`:
|
||||
|
||||
- статика из корня репо (HTML, JS, JSONL, `automation-graph.html`, vis.js);
|
||||
- эндпоинт `GET /api/episodes` → JSON-список имён файлов `docs/observer/episodes-*.jsonl` (дашборд не угадывает имена);
|
||||
- больше ничего; только localhost.
|
||||
|
||||
Парсер (JS, внутри дашборда):
|
||||
|
||||
- читает каждую строку JSONL → объект эпизода;
|
||||
- нормализует **v1** (строки без `schema_version` — нет `decision_provenance` / `environment` / `task_size` / `prompt_signal`, `outcome` уже проставлен) и **v2** (`schema_version: 2`);
|
||||
- битые строки и строки-маркеры `observer_error` — пропускаются, ведётся счётчик «N пропущено»;
|
||||
- результат — нормализованный массив эпизодов единой формы (отсутствующие v1-поля → `null`).
|
||||
|
||||
Производные данные (тепло, кластеры, агрегаты) считаются в браузере при загрузке. Ноль вшитых данных → всегда свежо.
|
||||
|
||||
### 4.2. Карта = общий холст
|
||||
|
||||
Сейчас топология зашита константами внутри `automation-graph.html` (один файл ≈2900 строк): `NODES` (стр. 229), `EDGES` (стр. 418), секции, `CONFLICT`-данные (стр. ≈406–614), `NODE_META` (стр. 1898), `NODE_SECTION` (стр. 2155).
|
||||
|
||||
**Рефактор-вынос:** константы топологии (`NODES`, `EDGES`, `SECTIONS`, `CONFLICT`-данные, `NODE_SECTION`) выносятся в `docs/automation-graph-data.js`. Старая карта `<script src>`-ит его — поведение и вид не меняются (подтвердить визуальным smoke-тестом). `NODE_META` (ручная теплокарта) **остаётся в старой карте** — дашборд её не использует, он считает тепло из эпизодов.
|
||||
|
||||
Дашборд импортирует `automation-graph-data.js` и строит **свой** экземпляр vis.js-графа в баннере. Этот граф управляемый — на нём анимируются траектории (Разбор) и красится тепло (Агрегат). Iframe старой карты отвергнут: чужой iframe нельзя анимировать снаружи.
|
||||
|
||||
Вкладка «Карта» = тот же граф дашборда в режиме «без оверлея». Файл `automation-graph.html` продолжает существовать как самостоятельная голая карта.
|
||||
|
||||
### 4.3. Атрибуция узлов (честная)
|
||||
|
||||
Граф — 124 узла (MCP-серверы, плагины, скилы, инструменты, секции). Эпизод даёт сигналы об узлах:
|
||||
|
||||
- `primary_rationale.node_chosen` — чаще всего `"direct"`, иногда id скила (`"superpowers:systematic-debugging"`);
|
||||
- события `skill_invoked` — id скилов;
|
||||
- `tool_summary.counts` — имена встроенных инструментов Claude (`Read`, `Edit`, `Bash`, `Grep`, …) и `Skill` / `ToolSearch`.
|
||||
|
||||
Маршрутизируемый словарь наблюдателя — `tools/observer-known-nodes.txt` (~22 имени: 13 superpowers-скилов + 7 проектных + 2 плагина/команды). Это **меньше** 124 узлов графа и пересекается с ними частично.
|
||||
|
||||
**Решение:** дашборд держит таблицу соответствия `сигнал эпизода → id узла графа`. Подсвечивает узлы, для которых соответствие есть. Узлы без соответствия (встроенные инструменты Claude, большинство MCP/плагинов, которые эпизоды пока не называют) **остаются неподсвеченными** — это ожидаемо и подписано в UI («атрибутировано N из M сигналов»). Полнее станет, когда роутер начнёт писать `node_chosen` детальнее — вне scope этой задачи.
|
||||
|
||||
## 5. Четыре view
|
||||
|
||||
Общий каркас (раскладка из §3): сверху переключатель view + граф-баннер; снизу — рабочая зона.
|
||||
|
||||
### 5.1. Карта
|
||||
|
||||
Граф без оверлея: топология + 11 размеченных дизайн-конфликтов (цвета RED / BLACK / GREEN из `CONFLICT_TYPES`). Фильтры по секциям/типам — переносятся из старой карты по возможности (или минимальный набор). Это «нулевое состояние» холста.
|
||||
|
||||
### 5.2. Разбор задачи (ретроспектива)
|
||||
|
||||
Нижняя зона: слева список эпизодов (фильтр: дата, `task_classification`, `outcome`, `path_type`, наличие ошибок); справа — детали выбранного.
|
||||
|
||||
Выбор эпизода → траектория:
|
||||
|
||||
- на графе подсвечиваются атрибутированные узлы (§4.3);
|
||||
- справа — упорядоченный список событий эпизода (`skill_invoked` / `error` / `retry` / `hook_fired` / `interrupt` / `time_burn`), плюс шапка: классификация, `path_type`, `decision_provenance` (если `user_directed_method` — «перенаправление: выбран X, автономно был бы Y»), `hard_floor`, окружение (`economy_level`, `model`, `post_compaction`, `session_turn`, `parallel_session`), `task_size`.
|
||||
|
||||
Честно: внутри хода есть упорядоченный список событий, но не каждый tool-вызов по порядку — `tool_summary` даёт только счётчики. «Траектория» = последовательность событий + сводка инструментов.
|
||||
|
||||
### 5.3. Лента сессии (живая)
|
||||
|
||||
Нижняя зона: одноколоночный поток эпизодов, сгруппированных по `task_id` / `task_ref`, новый ход сверху. Карточка хода: время, классификация, `path_type`, атрибутированный узел, ошибки/ретраи (бейджи), длительность (`ended_at − started_at`), флаг перенаправления.
|
||||
|
||||
Автоопрос: дашборд раз в N секунд (по умолчанию 5) перезапрашивает `/api/episodes` + текущий месячный JSONL, дописывает новые строки. Полл — только в этом view. Кнопка пауза/возобновить.
|
||||
|
||||
### 5.4. Агрегат (тренды)
|
||||
|
||||
Нижняя зона: плитки метрик по всем эпизодам:
|
||||
|
||||
- тепло узлов (авто — сколько раз каждый атрибутированный узел встречался); красит граф-баннер;
|
||||
- горячие точки ошибок/ретраев (узлы и классы задач с наибольшей долей `error` / `retry`);
|
||||
- доля перенаправлений (`decision_provenance.kind == "user_directed_method"`);
|
||||
- распределения `economy_level`, `path_type` (improvised/regulated), `task_classification`, `outcome`;
|
||||
- счётчик `observer_error` и пропущенных строк.
|
||||
|
||||
Опциональный reuse: для v2-эпизодов с `outcome: "unknown"` — переиспользовать детерминированный inference из `tools/brain-retro-analyzer.mjs`, если он оформлен импортируемым модулем; иначе показывать `unknown`. Решается в плане.
|
||||
|
||||
## 6. Конфликты — три слоя
|
||||
|
||||
Запрос — «где конфликты среди узлов». Эпизоды не пишут «узел A столкнулся с узлом B» — только `error` / `retry` / `hook_fired.errors`. Дашборд отдаёт три явно подписанных слоя:
|
||||
|
||||
1. **Дизайн-конфликты** — 11 размеченных `CONFLICT`-рёбер карты (факт, из топологии).
|
||||
2. **Трение** — эпизоды с `error`/`retry`, привязанные к атрибутированным в них узлам. Это инференс («во время хода с этим узлом была ошибка»), не доказанный конфликт. Подписано.
|
||||
3. **Корреляция** — эпизод с ошибкой, где атрибутированы два узла, между которыми есть `CONFLICT`-ребро → «конфликт мог реализоваться». Эвристика. Подписано.
|
||||
|
||||
Настоящего лога «узел×узел» нет. См. §13.
|
||||
|
||||
## 7. Раскладка
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ [Карта] [Разбор] [Лента] [Агрегат] │ переключатель view
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ГРАФ — баннер (vis.js) │ ~40% высоты, всегда виден
|
||||
│ │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ рабочая зона view (меняется) │ ~60% высоты
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Граф-баннер общий для всех view; рабочая зона своя у каждого. Forest-палитра (Teal `#0F6E56`, ivory `#F6F3EC`, теало-нуар `#012019`), Inter / JetBrains Mono — накладываются на этапе реализации как CSS-переменные.
|
||||
|
||||
## 8. Файлы и компоненты
|
||||
|
||||
| Файл | Назначение | Статус |
|
||||
|---|---|---|
|
||||
| `docs/observer/dashboard.html` | каркас дашборда (раскладка из §3/§7, vis.js-граф) | новый |
|
||||
| `docs/observer/dashboard.js` | парсер JSONL + агрегатор + 4 view + рендер графа | новый |
|
||||
| `docs/automation-graph-data.js` | вынесенная топология (`NODES` / `EDGES` / `SECTIONS` / `CONFLICT` / `NODE_SECTION`) | новый (вынос) |
|
||||
| `docs/automation-graph.html` | `<script src>` на data-файл; остальное без изменений | правка |
|
||||
| `tools/brain-dashboard-server.mjs` | статик-сервер + `/api/episodes` | новый |
|
||||
| `package.json` | скрипт `brain:dashboard` (запуск сервера + открытие браузера) | правка |
|
||||
| `tools/brain-dashboard-*.test.mjs` | тесты парсера / агрегатора / сервера | новый |
|
||||
|
||||
`dashboard.js` при росте можно разбить на модули (`parser.js`, `aggregate.js`, `graph.js`, `views/*.js`) — решается в плане по фактическому размеру.
|
||||
|
||||
## 9. Обработка ошибок и граничные случаи
|
||||
|
||||
- Битая JSONL-строка / `observer_error`-маркер → пропуск, инкремент счётчика, показ счётчика в UI.
|
||||
- Месячный файл отсутствует или пуст → не ошибка.
|
||||
- Эпизодов нет вообще → дружелюбное пустое состояние.
|
||||
- v1-эпизод (нет v2-полей) → недостающие поля `null`, UI показывает «—».
|
||||
- Сервер не запущен → дашборд физически не откроется (он отдаётся этим же сервером); пустой `/api/episodes` → пустое состояние.
|
||||
- `automation-graph-data.js` не загрузился → пустой граф + явное сообщение.
|
||||
|
||||
## 10. Тестирование
|
||||
|
||||
- **TDD** на чистую логику (`dashboard.js` — парсер, нормализация v1/v2, агрегатор, атрибуция узлов, инференс конфликтов): `tools/brain-dashboard-*.test.mjs` на `node:test`, failing-first → GREEN. Паттерн — как существующие `tools/*.test.mjs`.
|
||||
- Сервер `brain-dashboard-server.mjs` — smoke-тест (поднять, дёрнуть `/api/episodes`, проверить отдачу статики).
|
||||
- Вынос топологии — визуальный smoke старой карты (Playwright или ручной): карта выглядит и фильтруется как до выноса.
|
||||
- Рендер view и графа — ручной визуальный smoke в браузере.
|
||||
|
||||
## 11. Честные ограничения
|
||||
|
||||
1. **Гранулярность — один эпизод на ход.** Внутри хода есть упорядоченный список событий, но не каждый tool-вызов по порядку (только счётчики `tool_summary`).
|
||||
2. **«Живость» — после Stop, не в процессе.** Лента обновляется после завершения хода (+ задержка автоопроса), не пока ход идёт.
|
||||
3. **Атрибуция узлов частичная** — `node_chosen` чаще `direct`; словарь наблюдателя ~22 имени против 124 узлов графа. Бóльшая часть графа не подсвечивается. См. §4.3.
|
||||
4. **Конфликты узел×узел не логируются** — даётся инференс (§6), не факт.
|
||||
5. **История — только с запуска наблюдателя (~19.05.2026).** Раньше эпизодов нет.
|
||||
|
||||
## 12. Порядок сборки (3 фазы, один спек)
|
||||
|
||||
- **Фаза 1 — фундамент.** Статик-сервер + `/api/episodes`; вынос топологии в `automation-graph-data.js` + правка старой карты + визуальный smoke; каркас `dashboard.html`; парсер v1/v2 + атрибуция узлов (TDD); граф-баннер; view «Карта» + view «Разбор задачи»; npm-скрипт `brain:dashboard`.
|
||||
- **Фаза 2 — живость.** View «Лента сессии» + автоопрос/пауза.
|
||||
- **Фаза 3 — агрегат.** View «Агрегат» + тепло на граф-баннер + три слоя конфликтов (§6); опц. reuse `brain-retro-analyzer.mjs` для outcome-инференса.
|
||||
|
||||
## 13. Открытые вопросы / отложено
|
||||
|
||||
- **Настоящий лог конфликтов узел×узел** — потребует нового типа события в наблюдателе (`tools/observer-transcript-parser.mjs` + схема эпизода). Отдельная задача, не входит в этот спек.
|
||||
- **Двойной клик без сервера** — если когда-нибудь понадобится: добавить генератору запекание данных, файл выродится в тех-модель «запекаемый файл» без переписывания. Сейчас YAGNI.
|
||||
- **Forest-полировка** — этап реализации (frontend-design).
|
||||
- **vis.js** — откуда его берёт старая карта (CDN или вендорено) — уточнить в плане, дашборд переиспользует тот же способ.
|
||||
Reference in New Issue
Block a user