Document Phase 0 scaffold and architecture
This commit is contained in:
423
AGENTS.md
Normal file
423
AGENTS.md
Normal file
@@ -0,0 +1,423 @@
|
||||
# Max Index — контракт для агента
|
||||
|
||||
## Что мы создаём
|
||||
|
||||
Max Index — небольшое кроссплатформенное desktop-приложение для дейликов и других разговоров.
|
||||
|
||||
Приложение:
|
||||
|
||||
1. Слушает выбранный источник звука.
|
||||
2. Локально распознаёт русскую речь.
|
||||
3. Находит заранее настроенные фразы вроде «это сделать легко».
|
||||
4. Увеличивает индекс от 0 до 100.
|
||||
5. Немедленно меняет лицо персонажа и показывает короткую реакцию.
|
||||
|
||||
Продукт должен ощущаться как смешной живой индикатор встречи, а не как тяжёлая система транскрибации.
|
||||
|
||||
## Какой результат хотим получить
|
||||
|
||||
Первая полноценная версия должна:
|
||||
|
||||
- работать на Windows, macOS и Linux;
|
||||
- запускаться как обычное desktop-приложение и уметь жить в tray;
|
||||
- по умолчанию слушать микрофон;
|
||||
- показывать, что прослушивание включено или остановлено;
|
||||
- позволять выбрать аудиоустройство;
|
||||
- распознавать речь локально, без облачного API;
|
||||
- поддерживать редактируемые правила: варианты фразы, величина увеличения и cooldown;
|
||||
- увеличивать индекс строго один раз на одно произнесение;
|
||||
- ограничивать индекс диапазоном `0..100`;
|
||||
- показывать последнюю сработавшую фразу и величину изменения;
|
||||
- позволять поставить прослушивание на паузу и сбросить индекс;
|
||||
- не сохранять сырой звук;
|
||||
- не сохранять полный транскрипт по умолчанию;
|
||||
- не блокировать интерфейс во время распознавания;
|
||||
- понятно объяснять ошибки разрешений, модели и аудиоустройства.
|
||||
|
||||
## Границы первой версии
|
||||
|
||||
В первую версию входят:
|
||||
|
||||
- режим микрофона;
|
||||
- локальное потоковое распознавание;
|
||||
- список правил фраз;
|
||||
- индекс и история срабатываний текущей сессии;
|
||||
- несколько состояний лица;
|
||||
- Start, Pause и Reset;
|
||||
- выбор микрофона;
|
||||
- сборка установщиков для основных desktop-платформ.
|
||||
|
||||
В первую версию не входят без отдельного запроса:
|
||||
|
||||
- аккаунты и серверная часть;
|
||||
- синхронизация между устройствами;
|
||||
- мобильные приложения;
|
||||
- запись встреч;
|
||||
- полная стенограмма;
|
||||
- определение, кто именно произнёс фразу;
|
||||
- обучение собственной speech-to-text модели;
|
||||
- сложная аналитика и рейтинги сотрудников;
|
||||
- обязательный захват системного звука.
|
||||
|
||||
Захват звука компьютера добавить отдельным вертикальным срезом после стабильного режима микрофона. Он нужен для встреч в наушниках, но имеет разные ограничения на Windows, macOS и Linux.
|
||||
|
||||
## Основной стек
|
||||
|
||||
- Electron.
|
||||
- TypeScript в strict-режиме.
|
||||
- React для интерфейса.
|
||||
- Vite для разработки и сборки renderer-кода.
|
||||
- Electron Forge для установщиков.
|
||||
- Web Audio API и `AudioWorklet` для потокового PCM.
|
||||
- `utilityProcess` для локального распознавания.
|
||||
- `sherpa-onnx` с русской streaming-моделью T-one как первый recognizer.
|
||||
- Адаптер recognizer, позволяющий позже проверить `whisper.cpp` без переделки приложения.
|
||||
- Vitest для unit-тестов.
|
||||
- Playwright или эквивалентный Electron smoke-test только там, где он реально окупается.
|
||||
|
||||
Если в репозитории уже принят другой package manager, formatter или test runner, следовать репозиторию. В новом репозитории использовать `pnpm`.
|
||||
|
||||
## Архитектура
|
||||
|
||||
Разделять приложение на процессы и чистую предметную логику:
|
||||
|
||||
```text
|
||||
Renderer
|
||||
├─ React UI
|
||||
├─ выбор источника
|
||||
└─ Web Audio / AudioWorklet
|
||||
│ PCM-блоки
|
||||
▼
|
||||
Utility process
|
||||
├─ ресемплинг и буферизация
|
||||
├─ speech-to-text adapter
|
||||
└─ partial/final transcript events
|
||||
│ текст
|
||||
▼
|
||||
Domain
|
||||
├─ нормализация текста
|
||||
├─ поиск правил
|
||||
├─ защита от дублей
|
||||
└─ индекс 0..100
|
||||
│ события
|
||||
▼
|
||||
Renderer
|
||||
├─ лицо
|
||||
├─ индекс
|
||||
└─ история срабатываний
|
||||
```
|
||||
|
||||
### Ответственность процессов
|
||||
|
||||
`main`:
|
||||
|
||||
- жизненный цикл Electron;
|
||||
- окна и tray;
|
||||
- безопасные IPC-каналы;
|
||||
- разрешения операционной системы;
|
||||
- путь к модели и настройки;
|
||||
- запуск и восстановление utility-процесса.
|
||||
|
||||
`preload`:
|
||||
|
||||
- только узкий типизированный API через `contextBridge`;
|
||||
- никаких произвольных вызовов Node.js из renderer;
|
||||
- никаких универсальных `send(channel, payload)` наружу.
|
||||
|
||||
`renderer`:
|
||||
|
||||
- интерфейс;
|
||||
- получение MediaStream;
|
||||
- AudioWorklet;
|
||||
- отображение состояния;
|
||||
- отсутствие тяжёлого inference-кода.
|
||||
|
||||
`utility process`:
|
||||
|
||||
- загрузка нативного recognizer;
|
||||
- обработка PCM;
|
||||
- потоковое распознавание;
|
||||
- контроль очереди и backpressure;
|
||||
- возврат текста и диагностических состояний.
|
||||
|
||||
`domain`:
|
||||
|
||||
- чистые TypeScript-функции без Electron и React;
|
||||
- правила фраз;
|
||||
- deduplication и cooldown;
|
||||
- вычисление индекса;
|
||||
- выбор визуального состояния.
|
||||
|
||||
## Предлагаемая структура
|
||||
|
||||
```text
|
||||
src/
|
||||
main/
|
||||
preload/
|
||||
renderer/
|
||||
components/
|
||||
features/
|
||||
audio/
|
||||
capture/
|
||||
recognition/
|
||||
domain/
|
||||
triggers/
|
||||
index/
|
||||
face/
|
||||
shared/
|
||||
resources/
|
||||
models/
|
||||
faces/
|
||||
tests/
|
||||
fixtures/
|
||||
```
|
||||
|
||||
Не создавать все каталоги заранее. Добавлять структуру по мере появления реального кода.
|
||||
|
||||
## Контракт аудио
|
||||
|
||||
- Получать микрофон через `navigator.mediaDevices.getUserMedia`.
|
||||
- Получать сырой звук через `AudioWorklet`, а не строить основной realtime-путь на `MediaRecorder`.
|
||||
- Передавать mono PCM блоками примерно по 100–300 мс.
|
||||
- Не полагаться на то, что ОС или Chromium действительно выдали запрошенный sample rate.
|
||||
- Выполнять ресемплинг в одном явно определённом месте.
|
||||
- Не отправлять один IPC-вызов на каждый 128-sample worklet frame.
|
||||
- Не использовать синхронный IPC.
|
||||
- Останавливать MediaStream tracks при Pause, смене источника и завершении приложения.
|
||||
- Использовать идентификатор audio session и игнорировать запоздалые результаты старой сессии.
|
||||
- Ограничивать очередь PCM; при перегрузке сообщать состояние, а не бесконечно накапливать память.
|
||||
- Не писать сырой звук на диск без отдельного явного debug-режима.
|
||||
|
||||
## Контракт распознавания
|
||||
|
||||
Определить небольшой интерфейс recognizer:
|
||||
|
||||
```ts
|
||||
interface SpeechRecognizer {
|
||||
start(config: RecognitionConfig): Promise<void>;
|
||||
acceptPcm(chunk: Float32Array): void;
|
||||
stop(): Promise<void>;
|
||||
onEvent(handler: (event: RecognitionEvent) => void): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
Не протаскивать API конкретной библиотеки в UI или domain-логику.
|
||||
|
||||
События должны различать:
|
||||
|
||||
- partial transcript;
|
||||
- final transcript;
|
||||
- ready/listening state;
|
||||
- recoverable error;
|
||||
- fatal model error.
|
||||
|
||||
Каждое transcript-событие должно содержать:
|
||||
|
||||
- `sessionId` — запуск аудиосессии;
|
||||
- `utteranceId` — одно отдельное произнесение;
|
||||
- `sequence` — номер ревизии текста внутри произнесения;
|
||||
- `kind` — `partial` или `final`;
|
||||
- `text`.
|
||||
|
||||
Все partial и final одного произнесения обязаны иметь одинаковый `utteranceId`. Если recognizer не предоставляет такой идентификатор, adapter должен синтезировать его на основе endpointing, не перекладывая эту задачу на UI.
|
||||
|
||||
Распознавание должно работать локально. Любой облачный вариант добавлять только как явную необязательную функцию после согласования.
|
||||
|
||||
## Контракт правил и индекса
|
||||
|
||||
Минимальная модель правила:
|
||||
|
||||
```ts
|
||||
interface TriggerRule {
|
||||
id: string;
|
||||
title: string;
|
||||
patterns: string[];
|
||||
delta: number;
|
||||
cooldownMs: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Перед сопоставлением:
|
||||
|
||||
- привести текст к нижнему регистру;
|
||||
- заменить `ё` на `е`;
|
||||
- нормализовать пробелы;
|
||||
- убрать незначащую пунктуацию;
|
||||
- учитывать границы слов.
|
||||
|
||||
Сначала использовать точные варианты фраз. Нечёткое сравнение по умолчанию выключить: оно легко создаёт ложные срабатывания.
|
||||
|
||||
Streaming ASR может вернуть одну фразу в нескольких partial-результатах. Одно произнесение обязано создавать только одно `TriggerMatched` событие. Идентифицировать совпадение по `sessionId + utteranceId + ruleId + позиции фразы`. Cooldown проверять после дедупликации: это дополнительная защита, но не замена корректной идентичности события.
|
||||
|
||||
Если occurrence был подавлен cooldown, всё равно пометить его обработанным, чтобы поздний final того же utterance не сработал повторно.
|
||||
|
||||
Индекс изменять только через чистую функцию:
|
||||
|
||||
```ts
|
||||
next = Math.max(0, Math.min(100, current + delta));
|
||||
```
|
||||
|
||||
React-компоненты не должны самостоятельно искать фразы или изменять индекс.
|
||||
|
||||
## Визуальный контракт
|
||||
|
||||
Главный элемент окна — лицо персонажа и индекс. Остальные элементы не должны с ними конкурировать.
|
||||
|
||||
Базовые уровни:
|
||||
|
||||
| Индекс | Состояние |
|
||||
| ---: | --- |
|
||||
| 0–19 | спокойный |
|
||||
| 20–39 | настороженный |
|
||||
| 40–59 | раздражённый |
|
||||
| 60–79 | на грани |
|
||||
| 80–99 | критический |
|
||||
| 100 | MAXIMUM |
|
||||
|
||||
На срабатывание показывать короткую transient-реакцию, затем возвращаться к состоянию текущего уровня.
|
||||
|
||||
Использовать оригинального персонажа и оригинальные ассеты. Не копировать спрайты Doom. Допустимо повторить принцип HUD-лица: несколько базовых эмоций, idle-взгляды и отдельный кадр реакции.
|
||||
|
||||
Предпочитать PNG/WebP sprite sheet и простую state machine. Не добавлять тяжёлый animation framework без необходимости. Уважать системную настройку reduced motion.
|
||||
|
||||
## Интерфейс первой версии
|
||||
|
||||
Основное окно должно содержать:
|
||||
|
||||
- лицо;
|
||||
- крупное значение индекса;
|
||||
- состояние `Listening`, `Paused`, `No permission`, `Model error`;
|
||||
- Start/Pause;
|
||||
- Reset;
|
||||
- последнюю найденную фразу;
|
||||
- компактный переход к настройкам.
|
||||
|
||||
Настройки должны содержать:
|
||||
|
||||
- аудиоустройство;
|
||||
- список trigger rules;
|
||||
- изменение `delta` и cooldown;
|
||||
- тест микрофона;
|
||||
- диагностический transcript preview, явно помеченный как временный.
|
||||
|
||||
Не показывать технические термины вроде PCM, ONNX или endpointing обычному пользователю. Ошибки писать человеческим языком и давать одно понятное действие.
|
||||
|
||||
## Приватность
|
||||
|
||||
- Всегда явно показывать активное прослушивание.
|
||||
- Не запускать микрофон скрытно.
|
||||
- Не сохранять звук.
|
||||
- Не вести полную историю распознанной речи по умолчанию.
|
||||
- В обычных логах не писать полный transcript.
|
||||
- Хранить только настройки и короткие события сработавших правил.
|
||||
- Предусмотреть понятную очистку истории текущей сессии.
|
||||
- Перед использованием на рабочей встрече напомнить пользователю учитывать правила компании и согласие участников.
|
||||
|
||||
## Кроссплатформенные требования
|
||||
|
||||
- Не собирать пути строковой конкатенацией; использовать `path` и Electron resource paths.
|
||||
- Не считать, что бинарник, модель или библиотека одинаковы для всех OS/arch.
|
||||
- Проверять Windows x64, macOS arm64/x64 и Linux x64 как отдельные release targets.
|
||||
- На macOS предусмотреть описания разрешений микрофона и системного аудио в `Info.plist`.
|
||||
- На Windows корректно объяснять отключённый глобальный доступ desktop-приложений к микрофону.
|
||||
- На Linux не обещать одинаковую работу loopback без проверки PipeWire/PulseAudio окружения.
|
||||
- После первого получения модели приложение должно уметь работать offline.
|
||||
- Проверять целостность загружаемой модели и не начинать распознавание с неполным файлом.
|
||||
|
||||
## Правила написания кода
|
||||
|
||||
- Писать названия типов, файлов и переменных на английском; пользовательский интерфейс первой версии — на русском.
|
||||
- Использовать строгие типы; не вводить `any` ради быстрого исправления.
|
||||
- Держать domain-логику независимой от Electron, React и конкретного recognizer.
|
||||
- Не создавать абстракции до появления второго реального варианта, кроме явно нужного `SpeechRecognizer` и audio source boundary.
|
||||
- Не смешивать сбор аудио, распознавание, matching и UI в одном сервисе.
|
||||
- Не использовать глобальные mutable singleton-состояния для сессии.
|
||||
- Делать операции Start, Pause, Stop и Reset идемпотентными.
|
||||
- Обрабатывать отмену и завершение процессов.
|
||||
- Не оставлять фоновые listeners после закрытия окна или смены сессии.
|
||||
- Комментариями объяснять причину нетривиального решения, а не пересказывать код.
|
||||
- Не проводить несвязанный рефакторинг вместе с продуктовой задачей.
|
||||
|
||||
## Обязательные проверки
|
||||
|
||||
Для trigger/index-логики покрыть тестами как минимум:
|
||||
|
||||
- точное совпадение;
|
||||
- разные варианты одной фразы;
|
||||
- `ё` и `е`;
|
||||
- повтор одного partial transcript;
|
||||
- partial, который затем стал final;
|
||||
- два настоящих произнесения с паузой;
|
||||
- пересекающиеся правила;
|
||||
- слово как часть другого слова;
|
||||
- cooldown;
|
||||
- достижение и превышение 100;
|
||||
- Reset.
|
||||
|
||||
Для аудио проверить:
|
||||
|
||||
- Start → Listening;
|
||||
- Pause действительно останавливает tracks;
|
||||
- смену устройства;
|
||||
- исчезновение устройства;
|
||||
- падение recognizer;
|
||||
- перезапуск utility process;
|
||||
- игнорирование событий старой сессии;
|
||||
- ограничение очереди;
|
||||
- отсутствие сохранённых аудиофайлов.
|
||||
|
||||
Для UI проверить:
|
||||
|
||||
- все шесть уровней лица;
|
||||
- transient-реакцию;
|
||||
- состояния разрешений и ошибок;
|
||||
- работу при выключенной анимации;
|
||||
- отсутствие зависания при медленном recognizer.
|
||||
|
||||
## Рекомендуемый порядок реализации
|
||||
|
||||
1. Создать Electron scaffold и чистые domain-модули с unit-тестами.
|
||||
2. Собрать первый сквозной demo-срез на mock recognizer: Start → тестовое utterance → индекс → реакция лица.
|
||||
3. Подключить микрофон, AudioWorklet, session lifecycle и mock inference в utility process.
|
||||
4. Заменить mock на локальный `sherpa-onnx` adapter и проверить качество на реальных фразах.
|
||||
5. Добавить редактирование rules, выбор устройства, tray и нормальные error states.
|
||||
6. Собрать установщики и пройти release-проверки на каждой заявленной платформе.
|
||||
7. Только затем добавлять system loopback отдельными platform providers.
|
||||
|
||||
## Как агент должен работать
|
||||
|
||||
1. Сначала прочитать этот файл и существующий код.
|
||||
2. Кратко сформулировать, какой пользовательский результат будет получен.
|
||||
3. Назвать основные файлы, которых коснётся изменение.
|
||||
4. Выбрать самый маленький законченный вертикальный срез.
|
||||
5. Сохранить существующие решения, если нет конкретной причины менять их.
|
||||
6. Реализовать изменение.
|
||||
7. Запустить релевантные проверки.
|
||||
8. Не заявлять о проверке платформы, на которой тесты не запускались.
|
||||
9. В конце дать короткую человеческую сводку.
|
||||
|
||||
Формат итоговой сводки:
|
||||
|
||||
```text
|
||||
Готово
|
||||
- Что теперь умеет пользователь.
|
||||
|
||||
Изменено
|
||||
- Путь к файлу — зачем он изменён.
|
||||
|
||||
Проверено
|
||||
- Какие команды или сценарии прошли.
|
||||
|
||||
Осталось
|
||||
- Только реальные ограничения или следующий логичный шаг.
|
||||
```
|
||||
|
||||
Не писать полотно о каждой строке. Объяснять важные решения обычным языком, учитывая, что пользователь — опытный разработчик, которому нужна короткая и хорошо структурированная сводка.
|
||||
|
||||
## Навыки проекта
|
||||
|
||||
- `$build-max-index` — общая разработка и выбор вертикального среза.
|
||||
- `$implement-max-index-audio` — микрофон, системный звук, AudioWorklet, recognizer и разрешения.
|
||||
- `$implement-max-index-experience` — правила фраз, индекс, HUD и реакции персонажа.
|
||||
- `$verify-max-index` — тестирование, диагностика и проверка готовности релиза.
|
||||
Reference in New Issue
Block a user