chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

View file

@ -0,0 +1,89 @@
# ADR-0001. Триаж prestart-litany для профиля M
- Дата: 2026-08-05
- Статус: принят
- Ссылки на литанию — слагами/названиями пунктов, не номерами (`gotcha-renumbered-parent`).
## Контекст
Проект «Сайт обучения Го»: профиль **M** (продукт без своего прода) — код, тесты,
релизы, но нет своего сервера/БД (до опциональных этапов 89). Полностью клиентский:
статическая сборка на шаред-хостинг, движок и ИИ — в браузере. Один разработчик,
сессии ведут разные ИИ-модели без общей памяти.
## Триаж
| Пункт литании | Вердикт | Причина / условие |
| ---------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.2 Минимальное ядро (все 9 пунктов) | IN | Действует всегда, для любого профиля |
| 0.3-D/S/A профильные добавки | OUT | Не наш профиль |
| 0.3-M профильные добавки | IN | Чистое ядро и детерминизм (II), версионирование локального состояния (I.14), адаптер платформы, CI-матрица ОС (II.11), профиль публичного релиза (V.9), роли (IV) |
| 0.3-XL литания целиком | DEFERRED | Активация: старт этапа 8 (PHP+SQLite, свой прод) — до-триаж раздела V обязателен по спеке |
| I.1 Модульность / Module Map | IN | packages/* = модули; Module Map в AGENTS.md |
| I.2 Сервисный слой | IN (адаптация) | Без backend: контроллер→сервис читается как UI→чистые функции ядра; логика только в packages/core и packages/ai |
| I.3 Статусы = конечный автомат | IN | Состояние партии/урока — именованные переходы, прямое присваивание запрещено |
| I.4 Аудит-след | OUT | Нет транзакций, денег, сервера. Журнал ходов партии существует как доменная сущность (история с ветвлением), не как аудит |
| I.5 Soft delete | OUT | Нет БД и документов |
| I.6 Числа/единицы/стандартные форматы | IN | Координаты доски, коми (0.5-шаг), размер доски — объявлены письменно в INTERFACES.md; SGF FF[4] — стандартный формат обмена, берём его |
| I.7 Миграции схемы append-only | IN (адаптация) | Схема localStorage: version + миграция при загрузке; БД-миграции — DEFERRED до этапа 8 |
| I.8 Внешние вызовы | OUT | Нет внешних сервисов на этапах 17. Загрузка весов KataGo (этап 9) — статический файл, не API; адаптер площадки не нужен (свой сайт, не маркетплейс) |
| I.9 Парные операции симметричны | IN | Сделать ход ↔ отмена хода; поставить разметку ↔ снять; тесты на пары |
| I.10 Без хардкода / доменные параметры | IN | Коми, бюджеты времени ИИ, вероятности уровней — константы/конфиг одним местом (packages/core/src/config или данные уроков) |
| I.11 Contract First | IN | INTERFACES.md — контракт движка, протокол postMessage воркера, форматы уроков, схема localStorage. Сначала запись, потом код; удалил сущность — докажи grep'ом |
| I.12 Docker-first | OUT | Статика на шаред-хостинге, контейнеров нет |
| I.13 Fail-safe | IN | Битый сейв → старт с дефолтов с пометкой; нет SharedArrayBuffer → честный однопоточный фолбэк; фича-флаги (этапы 89) по умолчанию выключены |
| I.14 Персистентное состояние вне БД | IN | localStorage: version + checksum + атомарность (насколько позволяет API) + миграция + roundtrip-тест — прямо в спеке |
| I.15 Права на уровне выборки | OUT | Нет сервера и пользователей |
| I.16 Retention / классы данных для AI | IN (частично) | Сборов данных нет по спеке; класс «данные пользователя не покидают браузер» объявлен письменно здесь: прогресс и партии — только localStorage, во внешние сервисы (включая облачные AI) не отправляются |
| I.17 Один назначенный писатель | IN | Состояние партии пишет только UI-поток; воркер ИИ — чистый вычислитель, обмен через postMessage |
| I.18 Не создавай второй канал доставки | IN | Вся статика (включая веса сети на этапе 9) отдаётся тем же хостингом, что и код |
| I.19 Файлы | IN (адаптация) | SGF-файлы — только через File API в браузере, без сервера |
| I.20 Автономный/демо-режим | IN | Детерминированные реплеи и тестовые партии — обычные входные данные движка, не ветки if |
| II.1 Гейты с первого коммита | IN | prettier + eslint + tsc -b --noEmit + vitest = `npm run gates`; hard-блокирующие. Поведение при провале: стоп, показать гейт и stderr, не чинить молча |
| II.2 Правило→хук | OUT | Проект ведётся разными инструментами/моделями, PreToolUse-хуки — фича одного инструмента; машинная проверка — через npm-гейты и CI (`gotcha-two-agent-configs`) |
| II.3 Проверяй каждый слой сразу | IN | Прогон gates после каждого слоя каркаса и каждого модуля |
| II.4 Тулчейн проверяй фактически | IN | Выполнено при развёртывании: версии сверены через npm view 2026-08-05 и запинены |
| II.5 Типы обязательны, функции ≤40 строк | IN | strict без any; превышение 40 строк — с явным обоснованием в комментарии |
| II.6 Тесты зеркалят структуру, багфикс приносит тест | IN | AAA; тест = полная жизненная цепочка; для ядра — инварианты алгоритма в docstring + property-based тесты (fast-check, добавляется на этапе 1) |
| II.7 Тесты wiring — отдельный класс | IN | Orphan-check по каждой публичной функции ядра + интеграционный тест полной партии — прямо в спеке этапа 1 (`gotcha-green-modules-dead-system`) |
| II.8 Тест без боевой среды — спроектирован | IN | Ядро не импортирует DOM и воркер-API (две оси изоляции); Date.now()/Math.random() запрещены в ядре, часы и seeded RNG инжектируются; headless-прогон партий ботом с проверкой метрик |
| II.9 Фоновые задачи/очередь | OUT | Нет очереди; Web Worker — не таска в смысле пункта, протокол фиксируется в INTERFACES.md |
| II.10 Фронтенд | IN (адаптация) | strict без any; без react-query/zustand/RHF — UI минимальный, состояние своё; строки UI — в один ресурсный файл с первого дня; error boundary на маршрут; бюджеты числом (страница урока ≤ 300 КБ) |
| II.11 CI — не только гейты | IN | GitHub Actions: gates + матрица ubuntu+windows + гейт «чистый клон собирается» + npm audit. Настраивается на этапе 1 вместе с первым кодом |
| II.12 Лицензионный гейт | IN | Только пермиссивные лицензии зависимостей; THIRD_PARTY_LICENSES.md; цумэ-го — свободная лицензия с атрибуцией (CREDITS.md); лицензия весов KataGo проверяется на этапе 9 |
| III.1 Карта навигации | IN | Module Map в AGENTS.md + docs/navigation.md (появится с кодом); крупные файлы — порог + команда генерации |
| III.2 Один индекс и один гейт документации | IN (адаптация) | Доки — markdown в docs/, индекс — docs/README.md; гейт ссылок — легковесный скрипт проверки относительных ссылок (в gates), без mkdocs |
| III.3 ADR | IN | Этот файл — первый. Существующие ADR не редактируются |
| III.4 Ритуал закрытия сессии | IN | Триггер «закрываем сессию»; если сводить нечего — сказать явно |
| III.5 Статусы в планах по факту кода | IN | Реестр «Критические долги» в начале roadmap; закрытие долга — двухфазно |
| III.6 Версии | IN | SemVer; SoT версии — корневой package.json; версия наблюдаема в подвале сайта; бамп только по явной команде владельца; push тега — отдельное «да» |
| IV.1 Модель ветвления | IN | Trunk-based: один разработчик, ревью — сам сверка с гейтами; фиксируется здесь |
| IV.2 Делегирование субагентам | IN | Правку 13 строк не делегировать; субагентам — правило контекст-экономии и запрет на commit/push |
| IV.3 Опасные команды письменно | IN | Список в AGENTS.md; одно «да» = одно действие; escape-вектора (cd && git push, sh -c, eval, xargs) не использовать |
| IV.4 Не переспрашивай и не угадывай | IN | Дублируется в мастер-промпте |
| IV.5 TodoWrite, планы в docs/plans/ | IN | Двухфазность: фикс отдельным коммитом, доки отдельным |
| IV.6 Бюджет точки входа агента | IN | Точка входа — AGENTS.md (инструменто-нейтральная), только то, что нужно каждую сессию; чужие конфиг-каталоги не заводить |
| IV.7 Нарушаемое правило — дефект системы | IN | Заморозки и осознанные исключения — письменно в самом правиле |
| IV.8 Гигиена сессии | IN | Ссылки на docs/ в ответах; конфиги сторонних сервисов — по актуальной доке (`gotcha-llm-stale-configs`) |
| IV.9 Протокол отладки | IN | «Снаружи внутрь»: UI → воркер → движок; зафиксирован в мастер-промпте |
| V.1V.8 Прод/эксплуатация/бэкапы | DEFERRED | Активация: этап 8 (свой backend на шареде). До него прода нет — деплой = FTP-заливка статики |
| V.9 Профиль публичного релиза | IN | Сайт публичный: LICENSE, README-витрина со скриншотами (с этапа 7), CREDITS.md, внутренние доки на русском — записано здесь |
| V.10 Межпроектные контракты | OUT | Потребителей значений проекта вне его нет |
| VI Грабли: [Docker] группа | OUT | Контейнеров нет |
| VI Грабли: [инфра/прод] группа | DEFERRED | Активация с этапом 8. Исключения, уже в спеке этапа 7: `gotcha-https-redirect-proxy` (IN — редирект средствами панели), `gotcha-robots-secret` (IN — актуально при открытии индексации) |
| VI Грабли: данные и логика, версии, Windows, процесс | IN | Все применимые — постоянный чеклист; особо: `gotcha-gitattributes` (закрыт первым коммитом), `gotcha-non-ascii-toolchain` (файлы для тулчейна — ASCII), `gotcha-green-modules-dead-system` и `gotcha-orphan-config` (wiring-тесты этапа 1), `gotcha-docs-drift` (ритуал закрытия), `gotcha-phantom-tails` (сверка плана с кодом в начале сессии), `gotcha-modal-drag-close` (этап 5, UI) |
## Компромиссы — что осознанно потеряли
1. **Нет машинных хуков (II.2 OUT)** — дисциплина опирается на npm-гейты и CI, а не на
блокирующие хуки инструмента. Цена: правило можно нарушить в спешке и узнать об этом
только на прогоне gates. Принято, потому что проект ведётся разными моделями и
инструментами (`gotcha-two-agent-configs`, `gotcha-hook-shell-blind`).
2. **Нет Docker-воспроизводимости среды сборки** — воспроизводимость обеспечивается
запиненными версиями + engines.node + CI-гейтом «чистый клон собирается».
3. **Гейт документации — самодельный скрипт ссылок, не mkdocs --strict** — проще, но
ловит только битые относительные ссылки, не полноту nav.
4. **Trunk-based без внешнего ревью** — роль ревью выполняют гейты + ритуал закрытия +
сверка владельца; ошибка может дойти до main, откат — реверт-коммитом.
5. **Этапы 89 остаются недо-триаженными** — раздел V литании активируется отдельным
до-триажем при старте этапа 8; до тех пор правила прода не действуют.

View file

@ -0,0 +1,57 @@
# ADR-0002: алгоритм поиска групп и дамэ — flood fill
Дата: 2026-08-05 | Статус: принято (этап 1)
## Контекст
Движку правил (packages/core) на каждый ход нужно находить группы камней и их
дамэ: при проверке захватов, самоубийства, при подсчёте и в эвристике мёртвых
групп. План этапа 1 предписывает выбрать между flood fill и union-find по
микро-бенчмарку и зафиксировать выбор в ADR.
## Альтернативы
1. **Flood fill** — обход группы стеком от стартового камня, дамэ собираются в
множество. Простая реализация, локальный обход (только затронутые группы).
2. **Union-find** — массив parent на всю доску, объединение соседей одного
цвета, дамэ агрегируются по корням. Теоретически эффективен при
инкрементальных обновлениях, но наша позиция неизменяема: структуру пришлось
бы перестраивать на каждый ход, что обнуляет его главное преимущество.
## Бенчмарк
`packages/core/bench/group-bench.mts` (запуск: `npx tsx ...`): оба алгоритма
находят все группы и дамэ на случайных позициях 19×19 (плотности 0.35/0.55/0.75,
40 позиций × 300 итераций на плотность, детерминированный RNG). Среда:
Node.js 20, x86-64. Контрольные суммы результатов совпали.
| Плотность | flood fill | union-find | u/f |
| --------- | ---------- | ---------- | ---- |
| 0.35 | 230.4 мс | 373.1 мс | 1.62 |
| 0.55 | 288.5 мс | 505.6 мс | 1.75 |
| 0.75 | 328.3 мс | 596.8 мс | 1.82 |
| Итого | 847.1 мс | 1475.5 мс | 1.74 |
Union-find проигрывает на всех плотностях (в 1.61.8 раза): на маленькой доске
(361 клетка) стоимость полного прохода union-find и аллокаций Map/Set по корням
не окупается, а flood fill обходится дешёвыми операциями над массивом и одним
множеством дамэ на группу.
## Решение
Оставляем **flood fill** (реализован в `groupAt`, packages/core/src/board.ts).
## Причины
- Быстрее на целевом профиле (доски 919, обходы только затронутых групп).
- Существенно проще код и меньше аллокаций; неизменяемость BoardState всё равно
не даёт переиспользовать структуру union-find между ходами.
- Производительности достаточно с запасом: полный пересчёт всех групп 19×19 —
десятки микросекунд, а движок обходит лишь соседние с ходом группы.
## Компромисс
Если этапы 34 (ИИ) покажут профилировкой, что пересчёт групп — узкое место
(например, массовые симуляции MCTS), решение пересматривается: кандидат —
инкрементальный union-find с копированием при записи или кэш групп в узлах
симуляции. Точка пересмотра — бенчмарк на реальной нагрузке ИИ, не раньше.

View file

@ -0,0 +1,35 @@
# ADR-0003: buildPosition в core + разметка цели CR вместо MA (этап 6)
Дата: 2026-08-06. Статус: принято.
## Контекст
Этап 6 (уроки и цумэ-го) требует стартовать задачу с произвольной позиции.
План-фаза 6 фиксировала «packages/* не трогаем», но аудит выявил два
пробела контракта:
1. parseSgf (этап 1) не поддерживает setup-свойства AB/AW — произвольную
позицию из SGF не собрать, а конструировать BoardState вручную вне core
опасно (positionHashes для суперко считает внутренний hashPosition).
2. NodeMarkup не содержит стрелок MA (только LB/TR/SQ/CR) — разметка цели
«connect» по контракту была невыразима.
## Решение
1. В packages/core добавлена ОДНА аддитивная функция
`buildPosition(size, black, white, toPlay)` (board.ts, экспорт через
index.ts): валидация (вне доски / дубли / группа без дамэ → Error),
корректный positionHashes. Поведение существующих функций не меняется,
зона core в остальном заморожена.
2. Разметка цели connect в задачах — CR (круги) вместо MA; контракт
INTERFACES.md обновлён. SGF задачи хранит позицию данными JSON
(setupBlack/setupWhite), а SGF-поле несёт только дерево вариантов
(B/W-ходы от стартовой позиции, TR/CR-разметку цели на корневом узле).
## Последствия
- Позиция задачи: JSON-поля setupBlack/setupWhite + buildPosition; дерево
вариантов парсится parseSgf и накатывается applyMove на построенную
позицию.
- При расширении parseSgf поддержкой AB/AW в будущем формат можно
упростить до чистого SGF (миграция контента, отдельной задачей).

View file

@ -0,0 +1,43 @@
# ADR-0004: личный кабинет email+пароль (этап 8) и до-триаж раздела V
Дата: 2026-08-06. Статус: принято (решение владельца).
## Контекст
Владелец заказал «маленький личный кабинет» с хранением прогресса —
активация этапа 8 (ОПЦИОНАЛЬНЫЙ по спеке: «только по отдельной команде»).
Спека этапа 8 предписывала анонимный токен; владелец выбрал
**email + пароль** (ask_user 2026-08-06) — осознанное отступление от
«без сбора данных»: email — персональные данные, храним минимум
(email + хэш пароля), никакой аналитики/трекинга не добавляется.
## Решение
1. Регистрация email+пароль, password_hash/password_verify, сессии
PHP native (HttpOnly, SameSite=Lax). Контракт API — INTERFACES.md.
2. **Без верификации почты и восстановления пароля**: на shared-хостинге
нет гарантии SMTP; потерянный пароль = новая учётка. Это предупреждение
показывается в UI при регистрации (зафиксировано в контракте).
3. Прогресс — opaque-блоб ProgressV2 на сервере, last-write-wins по
updatedAt; localStorage остаётся SoT для неавторизованных.
## До-триаж раздела V литании (требование мастер-промпта для этапа 8)
| Пункт | Вердикт | Комментарий |
| --------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| V.1 (агент не трогает прод) | IN | Прод-деплой — только команды владельцу блоком; агент не выполняет |
| V.2 (деплой-чеклист) | IN | docs/deploy.md: ВСЕ артефакты (dist → web root, server/ → /api, config.php, БД вне web root), smoke = health.php, откат = перезаливка предыдущего dist |
| V.3 (один скрипт деплоя) | OUT | Нет SSH/контейнеров, деплой по FTP; чек-лист V.2 покрывает |
| V.4 (секрет-гигиена) | IN | config.php с путём БД — вне репо (.gitignore), в репо config.example.php с плейсхолдерами [SECRET]; секретов в коде нет |
| V.5 (канонические пути) | IN | Пути на хостинге фиксируются в docs/deploy.md письменно при первом деплое (владельцем) |
| V.6 (наблюдаемость) | IN (минимум) | health.php {"status":"ok"}; внешний наблюдатель/алерты — OUT (нет сервера в нашем управлении) |
| V.7 (бэкап) | IN (минимум) | Бэкап = копия SQLite-файла средствами панели хостинга; процедура restore в deploy.md; автоматика — OUT |
| V.8 (приёмка/ворота) | IN | Приёмка этапа 8 = smoke.sh зелёный локально + ручной прогон на хостинге владельцем |
## Последствия
- В превью (статический хостинг) API недоступен — UI кабинета обязан
деградировать тихо («синхронизация недоступна»), без алертов.
- CI-гейты не запускают PHP (в среде его нет у GitHub runner по
умолчанию — есть: shivammathur/setup-php при желании; в v1 smoke.sh
локальный/на хостинге, зафиксировано в PROJECT_STATE).