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,40 @@
# План фазы 1: этап 1 — движок правил (packages/core)
Обновлено: 2026-08-05 | Статус: завершена (этап 1 принят, гейты зелёные)
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Профиль проекта M, триаж литании — ADR-0001.
- Подсчёт — китайские правила (Tromp-Taylor), не японские: однозначно алгоритмизируется.
- Группы/дамэ: flood fill или union-find — выбрать по бенчмарку в этапе 1, выбор — в ADR.
- Ко: простое ко + позиционное суперко (оба, суперко нужен для китайских правил).
- Определение мёртвых групп — отдельная функция + ручная корректировка игроком,
не авто-анализ.
## Объём этапа 1
1. Доски 9×9, 13×13, 19×19; позиция — типизированный массив.
2. Группы и дамэ, снятие камней, запрет самоубийства (кроме самоубийства с захватом),
простое ко и суперко.
3. Подсчёт Tromp-Taylor; коми 6.5 (19×19) / 5.5 (9×9); сэки обрабатывается корректно.
4. SGF FF[4]: парсер и генератор (ходы, вариации, комментарии, разметка LB/TR/SQ/CR).
5. История с откатом и ветвлением.
6. Контракты — сначала в docs/INTERFACES.md, потом код.
7. Бенчмарк flood fill vs union-find → ADR-0002.
## Тесты (гейт приёмки этапа)
- Захват камня и группы; запрет самоубийства; самоубийство с захватом (легально);
простое ко; суперко; сэки при подсчёте; ничья.
- Wiring: orphan-check «кто вызывает» по каждой публичной функции ядра
(`gotcha-green-modules-dead-system`).
- Интеграционный тест полной партии (по заранее записанной SGF).
- Property-based тесты инвариантов (fast-check): инварианты — в docstring функций.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. К этапу 2 не переходим без этого.

View file

@ -0,0 +1,59 @@
# План этапа 10 — дизайн-перенос (Kimi WebSites → каркас Astro)
Дата: 2026-08-06. Ветка интеграции: main.
## Контекст
Пользователь получил от Kimi WebSites переносной пакет дизайна
(«васи + гобан», светлая тема, индиго + терракота). Эталон распакован и
сохранён в `docs/design/kimi-export/`. Задача — натянуть дизайн на
существующий каркас, не трогая логику, контент, API и тесты.
Контракт: `docs/INTERFACES.md`, раздел «Дизайн-перенос (этап 10)» —
токены (с 3 правками контраста), каркас Layout, тема доски, изображения,
паттерны страниц, критерии приёмки.
## Стадии
### Стадия 0 (оркестратор, сделано)
- Распаковка пакета, сжатие изображений в `apps/web/public/assets/`
(hero 53КБ / ink 118КБ / stones 24КБ).
- Эталон в `docs/design/kimi-export/`, контракт в INTERFACES.md.
### Стадия 1 — параллельные кодеры (3 ветки)
- **design-a (фундамент)**: Layout.astro (глобальные стили = адаптация
kimi-export/style.css + правила [hidden]/go-task, шапка/подвал/проп
section/мобильное меню/шрифты), packages/board render.ts DEFAULT_THEME →
светлая, index.astro (hero+features+ink-band+home-strip). Файлы:
`apps/web/src/layouts/Layout.astro`, `packages/board/src/render.ts`,
`apps/web/src/pages/index.astro`, при необходимости
`apps/web/src/ui/strings.ts` (только новые строки главной).
- **design-b (контентные страницы)**: learn.astro, learn/[id].astro,
tsumego.astro, progress.astro — разметка по паттернам контракта,
логика/id/data-атрибуты не меняются.
- **design-c (страницы приложения)**: play.astro, account.astro,
register.astro, password-reset.astro — то же правило.
Пересечений по файлам нет (Layout только у A). Общий API — проп
`section` Layout, описан в контракте.
### Стадия 2 — интеграция (оркестратор)
- Слияние веток в main, разрешение конфликтов.
- Гейты, точечные правки (wiring/тема-тесты).
- Проверка веса страниц dist и контраста.
- Сборка dist → `/mnt/agents/output/app`, НОВАЯ версия превью
(website_version_manager) — старая рабочая версия остаётся для сравнения.
### Стадия 3 — ритуал закрытия
- PROJECT_STATE.md (0.3.0), отчёт session-10, docs(sync) коммит, zip.
## Риски
- Слияние: Layout меняет только A — конфликтов не ожидается; strings.ts
могут трогать A и B — B не трогает strings (правило в брифе).
- Контраст исправлен на уровне токенов — регрессий быть не должно.
- Вес главной: hero 53КБ + CSS/JS — в бюджете.

View file

@ -0,0 +1,60 @@
# План этапа 11 — аудит контента, страница «Источники», дисклеймер
Дата: 2026-08-07. Инициатор: владелец. Ветка интеграции: main.
## Вводные
Владелец спросил, насколько можно доверять обучающему контенту. Ответ:
движок и проверка задач — машинно проверены; проза уроков и база цумэ-го
написаны ИИ без рецензии сильного игрока. Решение владельца:
1. Провести аудит контента по авторитетным источникам (обязательно).
2. Сделать на сайте отдельную страницу «Источники» — только голые
проверяемые факты. Чего не нашли — пишем «не найдено». Выдумывать
источники и факты КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО (требование владельца).
3. Добавить дисклеймер об открытом редактировании курса.
## Стадии
### Стадия 0 (оркестратор)
- Контракт в INTERFACES.md (этап 11): правила аудита, формат страницы
источников, текст и размещение дисклеймера.
### Стадия 1 — аудит (3 параллельных research-агента)
- audit-1: уроки 0103 (дамэ/захват, атари/лестница/сеть, самоубийство/ко).
- audit-2: уроки 0407 (глаза, подсчёт/коми, связь/разрезание, фусэки).
- audit-3: уроки 0810 (дзёсэки — конкретные последовательности!, форма,
ёсэ) + специфические числовые утверждения всего курса.
Каждый агент: читает markdown уроков из репо, извлекает проверяемые
утверждения, ищет подтверждения в авторитетных источниках (Sensei's
Library, Wikipedia, BGA/AGA, gobase и т.п.), КАЖДЫЙ источник обязан
реально открыть инструментом и процитировать подтверждающую строку.
Вердикты: ПОДТВЕРЖДЕНО (URL + цитата) | НЕ НАЙДЕНО | РАСХОЖДЕНИЕ (что
именно расходится). Отчёт — markdown-файл docs/audit/NN.md, коммит в
ветку audit/rN.
### Стадия 2 — интеграция (оркестратор + coder)
- Сверка отчётов, выборка подтверждённых источников (только реально
открытые URL — оркестратор перепроверяет выборочно сам).
- Страница /istochniki.html: по темам — факт + ссылка; раздел «что не
удалось подтвердить» честно перечислен.
- Дисклеймер: на странице курса и в футере (ссылка на «Источники»).
- Если аудит нашёл РЕАЛЬНЫЕ ошибки контента — отдельный список владельцу;
правки контента только с подтверждающим источником.
### Стадия 3 — приёмка и ритуал
- Гейты, вес, контраст; скриншот страницы источников; новая версия
превью; PROJECT_STATE, отчёт, zip.
## Риски
- Главный риск — галлюцинация источников агентами. Митигация: правило
«URL только из реально открытых страниц, с цитатой» + выборочная
перепроверка оркестратором через web_open_url.
- Sensei's Library и gobase могут быть медленными/недоступными — тогда
честный «не найдено» по части утверждений.

View file

@ -0,0 +1,70 @@
# План фазы 2: этап 2 — доска Canvas (packages/board)
Обновлено: 2026-08-05 | Статус: код и тесты готовы, gates зелёный (74 теста:
43 core + 31 board). Осталось: визуальная проверка — на этапе 5.
## Пометки о ходе (2026-08-05)
- Объём выполнен целиком: geometry.ts, render.ts (тёмная тема по умолчанию),
input.ts, dom.ts, index.ts; корневой tsconfig.json — reference на
packages/board; зависимость @go-learn/core 0.1.0 в package.json пакета.
- Все тесты из раздела «Тесты» ниже написаны и зелёные (31 тест board).
- Отклонение: в packages/board/tsconfig.json нет references на ../core —
`tsc -b --noEmit` (гейт typecheck) с project references падает с чистого
состояния (TS6310, CLI --noEmit распространяется на referenced-проект,
TS 5.9.3). Импорт резолвится через workspace-симлинк; подробности —
комментарий в packages/board/tsconfig.json.
- Уточнение семантики territory (в первой редакции контракта стороны не
различались): структура TerritoryMap { black, white } с множествами ключей
"x,y" по цветам — зафиксировано в контракте (правка main loop при приёмке).
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Доска — Canvas 2D, не DOM/SVG (плавность на 361 пересечении) — мастер-промпт.
- render(ctx, position, options) — чистая функция состояния, весь кадр из аргументов.
- Геометрия, редьюсер ввода и рендер — headless-тестируемые; DOM — тонкий адаптер
(setupCanvas, attachPointerInput), юнит-тестами не покрывается — см. матрицу
«чего НЕ покрывает ни один тест» ниже.
- Координаты — буквы без «I» (стандарт Го), числа снизу вверх.
- Дефолтная тема — тёмная (мастер-промпт: минималистичный дизайн, тёмная тема).
- Контракт — docs/INTERFACES.md, раздел «Контракт отрисовки доски» (зафиксирован
2026-08-05 до кода).
## Объём этапа 2
1. packages/board: geometry.ts (computeGeometry, pointToPixel, pixelToPoint,
hoshiPoints), render.ts (render + BoardTheme + дефолтная тёмная тема),
input.ts (reduceInput), dom.ts (setupCanvas, attachPointerInput), index.ts.
2. Отзывчивый размер и devicePixelRatio (setupCanvas).
3. Хоси, координаты и номера ходов (переключаемые), фантомный камень,
подсветка последнего хода, тач-ввод с подтверждением (первый тап — позиция,
второй тап или confirm — ход), слои разметки (LB/TR/SQ/CR, заливка
территории, приглушение мёртвых).
4. Корневой tsconfig: references += packages/board; packages/board зависит от
@go-learn/core (project reference).
## Тесты (гейт приёмки этапа)
- Геометрия: хоси для 9/13/19; roundtrip pixelToPoint∘pointToPixel; отклонение
точки дальше половины клетки; padding при showCoordinates on/off.
- Редьюсер ввода: тап → pending; второй тап по той же точке → commit; тап по
другой точке → перенос; confirm без pending → commit null; cancel → сброс;
тап мимо доски → без изменений.
- Рендер: мок-ctx с записью вызовов — число камней = числу занятых клеток
BoardState; фантом рисуется с phantomOpacity; метка последнего хода только
при lastMove; LB/TR/SQ/CR из markup; заливка только для territory-ключей;
мёртвые приглушены. Не проверяем каждый вызов — только инварианты.
- Wiring: orphan-check по публичным функциям packages/board (вызывающие —
тесты допустимы, дом-адаптер помечен как входная точка).
- Матрица «чего НЕ покрывает ни один тест»: реальный вид кадра, pointer-события
браузера, поведение на мобильном Safari — ручная проверка в этапе 5/7
(смоук-сценарий, слой GUI — litany II.8).
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. Визуальная проверка — на этапе 5
(когда появится страница); до неё — demo-страница не нужна.

View file

@ -0,0 +1,53 @@
# План фазы 3: этап 3 — ИИ ур. 13, эвристика (packages/ai)
Обновлено: 2026-08-05 | Статус: выполнен (gates зелёные; perf chooseMove 19×19 ≈ 8 мс)
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Отдельный пакет packages/ai — core остаётся чистыми правилами (рекомендация
отчёта сессии 2, принято).
- ИИ — синхронный чистый вычислитель; воркер появится в этапе 4 (MCTS),
протокол postMessage фиксируется там же.
- Случайность — только seeded Rng из core, передаётся параметром (детерминизм
= воспроизводимые партии и тесты).
- Легальность — только через applyMove из core; дублирование правил запрещено.
- Приоритеты и вероятности уровней — по контракту в docs/INTERFACES.md
(зафиксирован 2026-08-05 до кода).
- Простая эвристика глаза: все ортогональные соседи — свои камни, без анализа
диагоналей; осознанно грубо, ручной игры «подложить себе» бот не имитирует.
## Объём этапа 3
1. packages/ai (@go-learn/ai 0.1.0, зависимость @go-learn/core):
chooseMove + LEVEL_ATARI_PROBABILITY + типы по контракту.
2. Внутренние эвристики: поиск групп в атари (groupAt/liberties из core),
ходы-спасения (>1 дамэ после хода или контрзахват), ходы-захвата,
фильтр своего глаза, кандидаты nearby (Chebyshev ≤ 2), взвешенный выбор.
3. Корневой tsconfig: references += packages/ai (без project reference на core —
TS6310, см. PROJECT_STATE; резолв через workspace-симлинк).
## Тесты (гейт приёмки этапа)
- Детерминизм: одинаковый seed → идентичная последовательность ходов в полной
партии бот-бот (два прогона, сравнение SGF или списка ходов).
- Приоритеты: сконструированные позиции — при rng «всегда 0» (ролл < p)
бот спасает своё атари / забирает чужое; при rng «всегда 0.99» (ролл ≥ p)
приоритет пропускается.
- Фильтр глаза: бот не заполняет свой глаз при наличии других ходов.
- Легальность: все ходы бота в длинных партиях легальны (property: случайные
партии бот-бот до двух пасов, seed запинен; ни одного ok:false).
- Завершение: партия бот-бот 9×9 завершается (over) за разумное число ходов;
после over вызов chooseMove кидает Error.
- Perf-smoke: chooseMove на позиции 19×19 средней игры < 200 мс в CI-среде
(критерий приёмки «ур.3 < 50 мс на среднем ноутбуке» замер фактический,
число в отчёте сессии; порог в тесте с запасом, машинно-зависимый).
- Wiring: orphan-check по экспортам packages/ai.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. Полная партия 9×9 бот-бот
завершается корректно (предпосылка критерия приёмки v1 №2 для ур. 5 в этапе 4).

View file

@ -0,0 +1,81 @@
# План фазы 4: этап 4 — ИИ ур. 46, MCTS + Web Worker (packages/ai)
Обновлено: 2026-08-05 | Статус: реализовано (гейты зелёные, 118 тестов)
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- MCTS-движок — чистый модуль в packages/ai (без воркер-API); воркер — тонкая
обёртка worker.ts с абстракцией порта. Однопоточно (без SharedArrayBuffer —
COOP/COEP на шареде не гарантированы).
- Плейаут — «лёгкая» политика, НЕ chooseMove этапа 3: тот сканирует всю доску
через applyMove на каждый ход (~мс на ход) — в плейауте непозволительно.
Лёгкая: дешёвая реакция на атари у последнего хода + случайные точки с
фильтром своего глаза и лимитом попыток. Компромисс силы ради бюджета —
зафиксирован здесь.
- Бюджеты: {4: 300, 5: 800, 6: 1500} мс; ур.5 — дефолт между уровнями (спека
задаёт только крайние) — записано в контракте.
- Сериализация позиции в протоколе — списком ходов от пустой доски (воркер
пересобирает и валидирует через applyMove), не снапшотом grid.
- cancel → воркер отвечает result с лучшим найденным, не молчит и не error.
- maxSimulations — override для детерминированных тестов.
- Контракт — docs/INTERFACES.md (зафиксирован 2026-08-05 до кода).
## Объём этапа 4
1. packages/ai/src/mcts.ts — дерево UCT (c=1.4), ленивая экспансия, лёгкий
плейаут, оценка scorePosition с komi, бюджет clock/shouldStop/maxSimulations,
topMoves (до 3) для подсказки этапа 5.
2. packages/ai/src/worker-protocol.ts — типы WorkerRequest/WorkerResponse +
пересборка BoardState из списка ходов (валидация, error при нелегальном).
3. packages/ai/src/worker.ts — тонкая обёртка порта (onmessage → choose →
result/error; cancel → result с лучшим найденным).
4. index.ts — экспорты движка и протокола.
## Тесты (гейт приёмки этапа)
- Детерминизм: maxSimulations=50 + фиксированный seed → идентичный move и
topMoves в двух прогонах на одной позиции.
- Легальность: ход MCTS легален на случайных позициях (запиненные seed'ы,
9×9 и 13×13); pass возвращается, только если легальных постановок нет или
он лучший по визитам (в любом случае applyMove не падает).
- Бюджет: фейк-clock (счётчик now()) — остановка по deadline; shouldStop() —
остановка после N симуляций, ответ всегда с легальным ходом.
- Качество (мягкий): MCTS ур.4 с maxSimulations=200 забирает группу в атари
на сконструированной позиции (где эвристика ур.1 с rng 0.99 пропускает) —
не эталон силы, а smoke «дерево реально ищет».
- Протокол: фейк-порт — choose → result с тем же requestId; нелегальный список
ходов → error; cancel активного → result, не error; cancel чужого id →
без эффекта.
- Полная партия 9×9 бот(MCTS ур.4, maxSimulations=100) против бота ур.3 —
завершается до over; все ходы легальны (предпосылка критерия v1 №2).
- Perf-smoke: 1500 мс бюджета → ≤ ~2.2 с фактически (overhead кроме симуляций
мал); число симуляций за 300 мс на 9×9 — в отчёт.
- Wiring: orphan-check обновить под новые экспорты.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный. Критерий v1 №3 («ур.6 < 2 с на
среднем ноутбуке») — по бюджету 1500 мс + overhead; факт — в отчёт сессии.
## Пометки по факту реализации (2026-08-05)
- Все пункты объёма и тестов выполнены; гейты зелёные (118 тестов, lint без
предупреждений). Perf-факты: ~147 симуляций за 300 мс на 9×9; ур.6 на
13×13 — фактически ~1500 мс (бюджет 1500 мс + overhead < 5 мс).
- Экспансия untried-детей — в детерминированном порядке legalMoves, первыми
идут ходы со снятием камней (захваты разворачиваются в дерево раньше).
Контракт порядок экспансии не оговаривает — эвристика внутри рамок UCT;
без неё сигнал качество-smoke (захват группы за 200 симуляций) не
выделяется на фоне шума плейаутов.
- createWorkerHandler(port, deps?) — необязательный второй параметр
WorkerDeps с инжектируемыми часами (в контракте «функция вида…»): нужен
для детерминированных тестов протокола (фейк-часы, реентерабельный cancel
из колбэка now()).
- Качество-smoke: позиция «кольцо в атари + сплошные стены» — единственная
из проверенных конструкций, где захват даёт устойчивый перекос winRate в
случайных плейаутах (≈0.95 против ≈0.57). Seed запинен: при 200 симуляциях
захват выбирается в ~75% seed'ов (при 800 — 12/12).

View file

@ -0,0 +1,82 @@
# План фазы 5: этап 5 — игровой интерфейс (apps/web, Astro)
Обновлено: 2026-08-05 | Статус: завершён (гейты зелёные, astro build собирает dist)
## Итоговые пометки (факт против плана)
- Astro запинен **5.18.2**, а не 7.1.6: astro 6/7 жёстко требуют Node
> =22.12.0 (bin/astro.mjs, проверено по tarball), в окружении Node 20.
> 5.18.2 — последняя линия с engines `^20.3.0`. Конфиг и объём — по плану
> (output: 'static', без интеграций).
- Зона расширена на 3 корневых конфига игноров: eslint.config.js,
.prettierignore, .gitignore — по одной записи `**/.astro/` (генерируемый
astro каталог ломал lint/format).
- AiClient дополнен `readonly fallback` (предусмотрено текстом контракта);
loadSettings/saveSettings — необязательным параметром StorageLike.
- Воркер собран Vite (chunk ai-worker-*.js в dist). Смоук dist в браузере:
доска рендерится, ход игрока + ответ бота работают.
## Критические долги (реестр — в начале, III.5)
- Нет.
## Ключевые решения (не пере-решать)
- Astro 7.1.6 (версия сверена npm view 2026-08-05), output: 'static', без
UI-фреймворков — vanilla TS + DOM (спека: «без тяжёлых UI-фреймворков»).
- Строки UI — один ресурсный файл src/ui/strings.ts с первого дня (спека).
- Отмена хода бота — terminate + пересоздание воркера (решение по известной
проблеме cancel в реальном Worker, отчёт сессии 4).
- Режим подсказки: topMoves последнего MCTS-ответа как разметка + оценка;
дельта = изменение winRate лучшего хода между запросами (упрощение —
оценка с точки зрения стороны, чей ход анализировался; зафиксировано).
- Схема localStorage и AiClient — контракты в docs/INTERFACES.md
(зафиксированы 2026-08-05 до кода).
- Страницы: / — заглушка-визитка со ссылкой на партию; /play — игра.
Курс — этап 6, полировка (a11y, .htaccess) — этап 7.
## Объём этапа 5
1. apps/web (Astro): package.json @go-learn/web 0.1.0, astro.config.mjs
(static), tsconfig (extends базового, lib DOM), workspaces += "apps/*".
2. Страница /play: доска (packages/board render + reduceInput + setupCanvas +
attachPointerInput), панель: выбор доски 9/13/19, уровня 16, цвета
(чёрные/белые/случайно), коми (auto/число), кнопки «Пас», «Сдаться»,
«Отменить ход», «Новая партия»; счётчики пленников; статус-строка
(чей ход, причина отказа из MoveError, итог партии).
3. Подсчёт по окончании (два паса): scorePosition + отметка мёртвых
(suggestDeadGroups + ручной toggle по клику) → итог.
4. SGF: «Скачать SGF» (serializeSgf, blob-скачивание), «Загрузка SGF» —
режим просмотра (parseSgf, навигация назад/вперёд без игры).
5. Подсказки ур. 4+ (переключатель, hintsEnabled): топ-3 хода маркерами с
оценкой; дельта winRate.
6. 19×19 при ур. 46 — честное предупреждение (спека этапа 4).
7. Тёмная минималистичная тема (согласована с палитрой packages/board),
адаптивность desktop+мобильные.
8. lib/storage.ts (по контракту), lib/ai-client.ts (по контракту),
lib/game-store.ts (чистый, без DOM: GameTree + действия), ui/strings.ts.
9. Воркер: entry ai-worker.ts (строка подключения — комментарий в
packages/ai/src/worker.ts), сборка воркера средствами Astro/Vite.
## Тесты (гейт приёмки этапа)
- storage: roundtrip save→load; битый JSON/checksum/неизвестная version →
дефолты + recovered; миграция-путь задокументирован.
- game-store (чистый): новая партия, ход игрока через pending→confirm,
отказ с reason при нелегальном, пас×2 → фаза подсчёта, toggle мёртвой
группы, итог, resign → result, отмена хода, viewer: загрузка SGF,
навигация prev/next.
- ai-client: фейк-worker (инжекция) — маршрутизация ур. 13 в синхронный
движок, ур. 46 в воркер; cancel → AiCancelledError; фолбэк при отказе
воркера.
- Матрица «чего НЕ покрывает ни один тест»: реальный кадр канвы в браузере,
pointer на тачах, реальный Worker, astro build — смоук этапа 7 +
ручная проверка владельцем.
- vitest.config: include += apps/*/src.
## Критерий выхода
Все тесты зелёные + `npm run gates` зелёный + `astro build` собирает dist.
Критерий v1 №4 («чистый клон собирается») проверяется сборкой в этой фазе;
№2 (партия против ур.5) и №5 (мобильный Safari) — ручная приёмка владельцем
после фазы 57.

View file

@ -0,0 +1,62 @@
# План этапа 6 — курс и цумэ-го
Дата: 2026-08-06. Статус: завершён (413/413, dist 280 КБ). Предшественник: этап 5 (игровой UI)
завершён, main = 4aee5b7 + hotfix play.html.
## Ключевые решения (не пере-решать)
1. Контент — оригинальный, авторский (не скачиваем чужие сборники цумэ-го):
это снимает лицензионный вопрос; атрибуция — CREDITS.md. Спека допускает
«свободная лицензия с атрибуцией ЛИБО формат импорта»; собственный
контент строго безопаснее (ADR-0001, I.16).
2. Интерактив в Markdown — raw HTML-элемент `<go-task data-task-id="…">`,
гидратация клиентским скриптом страницы. Никаких remark/rehype-плагинов
и новых зависимостей. Теория читаема без JS (запасной текст внутри
элемента).
3. Критерий успеха задачи — goal-предикат, вычисляемый движком
(apps/web/src/lib/goals.ts поверх @go-learn/core), НЕ сравнение с
координатами решения. Дерево вариантов в SGF — только автоответы
соперника и подсказки.
4. Формальный глаз — упрощение v1 (пустая область 13 пункта, окружённая
одним цветом); ложный глаз не отличаем, уроки про ложный глаз строятся
на capture-target. Уточнение — через ADR, не в этой фазе.
5. Прогресс — ключ `go-learn:progress` (ProgressV1) в том же конверте
version+checksum, API — расширение существующего storage.ts. Серия дней —
чистая функция touchStreak.
6. Зона работы — apps/web + CREDITS.md + docs; packages/board и
packages/ai не трогаем (goal-предикаты живут в apps/web). Исключение по
ADR-0003: в core добавлена одна аддитивная функция buildPosition
(parseSgf не умеет AB/AW), разметка connect — CR вместо MA.
7. Параллельные ветки: stage-6-core (механика: goals, storage, виджет,
страницы, строки) и stage-6-content (уроки, цумэ-го, CREDITS,
контентные тесты). Мерж: core → content.
## Объём
- Механика: goals.ts (4 предиката + формальные глаза), прогресс в storage.ts,
виджет задачи (канва board, автоответы по дереву, сброс/подсказка),
страницы /learn, /learn/[id], /tsumego, /progress, навигация, strings.ts.
- Контент: 10 уроков (по разделу каждый, программа из мастер-промпта),
≥30 цумэ-го (difficulty 15, ≥3 на градацию), задачи для уроков
(≥1 интерактив там, где уместно), CREDITS.md.
- Тесты: goals (все предикаты + ложный/формальный глаз), storage roundtrip
прогресса, touchStreak (вчера/сегодня/разрыв), валидация контента (все
TaskV1 парсятся, SGF легальны движком, goal-разметка присутствует, id
уникальны, у уроков валидный frontmatter и существующие task-id),
wiring-страниц.
## Приёмка этапа
- npm run gates зелёные (0 warnings), astro build PASS.
- ≥10 уроков, ≥30 цумэ-го, фильтры работают, прогресс и серия дней
сохраняются в localStorage (roundtrip).
- Страница урока читаема без JS (проверка отключением гидратации).
- Вес страниц остаётся скромным (жёсткий бюджет ≤300 КБ — гейт этапа 7, но
не раздуваем заранее).
## Риски
- suggestDeadGroups грубовата (PROJECT_STATE) — two-eyes предикат пишем
собственный, на неё не опираемся.
- Контент-объём большой: качество SGF важнее количества сверх минимума;
валидационный тест — обязательный страховочный гейт.

View file

@ -0,0 +1,50 @@
# План этапа 7 — полировка
Дата: 2026-08-06. Статус: завершён (431/431, dist 296 КБ). Предшественник: этап 6 завершён,
main = 2b249a5 (413/413, dist 280 КБ).
## Ключевые решения (не пере-решать)
1. .htaccess — apps/web/public/.htaccess (Astro копирует public/ в dist
как есть); содержимое зафиксировано в INTERFACES.md; HTTPS-редирект
сознательно ОТСУТСТВУЕТ (только панель хостинга,
gotcha-https-redirect-proxy).
2. Клавиатурный ввод — НЕ новый код в packages/board: чистый редьюсер
курсора в apps/web/src/lib/keyboard.ts, подтверждение хода идёт через
существующий reduceInput (tap по курсору → фантом → повтор → commit).
Семантика подтверждения едина с тач-вводом (I.6, одна механика).
3. ARIA-live — role="status" polite на существующих статусных строках,
без live-region-спама: меняется только текстовое содержимое.
4. Бюджет 300 КБ — по сырым байтам dist (с запасом к gzip); тест считает
html + локальные ассеты, на которые страница ссылается.
5. Отметка «урок пройден» — ручная кнопка + markLessonDone в storage.ts;
авто- done для уроков с задачами (этап 6) сохраняется, кнопка —
дополнение, не замена.
6. Зона — только apps/web; packages и корневые конфиги не трогаем.
## Объём
- public/.htaccess + wiring-проверка директив в dist.
- keyboard.ts (reduceCursor, кламп, тесты) + подписки в play-page.ts и
task-widget.ts (курсор, стрелки/Enter/Space/Escape, aria-labels).
- prefers-reduced-motion блок, :focus-visible, контроль контраста
(--muted при недоборе), role="status" на статусах.
- Кнопка «Отметить пройденным» на странице урока + markLessonDone + тест.
- Wiring-тест бюджета веса уроков ≤300 КБ.
- Строки — strings.ts.
## Приёмка этапа (и критерии v1, что можно проверить автоматически)
- npm run gates зелёные, 0 warnings; astro build PASS.
- dist/.htaccess с требуемыми директивами; без RewriteCond %{HTTPS}.
- Каждая страница урока ≤ 300 КБ по тесту.
- Критерии v1: №1 (тесты) и №4 (чистый клон собирается) — автоматически;
№2 (партия vs ур.5), №3 (тайминги бота), №5 (мобильный Safari) —
ручные, владельцу.
## Риски
- Клавиатурный курсор не должен ломать pointer-ввод: общий InputState,
подписки независимы.
- suggestDeadGroups грубовата (PROJECT_STATE) — в этап 7 НЕ входит
(отдельная задача через ADR при жалобах на подсчёт).

View file

@ -0,0 +1,47 @@
# План этапа 8.1 — регистрация отдельной страницей, восстановление пароля, капча
Дата: 2026-08-06. Статус: завершён (467/467 + smoke 58 OK). Предшественник: этап 8 завершён,
main = 7da814c (461/461 + smoke OK). Решения владельца: своя автономная
капча; OAuth — позже (дизайн docs/design/oauth-vk-yandex.md).
## Ключевые решения (не пере-решать)
1. Регистрация — ОТДЕЛЬНАЯ страница /register.html (не модалка): проще,
доступнее (a11y), диплинкабельна. /account.html становится страницей
входа со ссылками «Нет аккаунта? Зарегистрируйтесь» и «Забыли
пароль?».
2. Капча — автономная SVG (БЕЗ GD): glyph-повроты + шумовые кривые;
код в сессии (sha256, 10 мин), одноразовая; только на регистрации
(login прикрыт rate limit). SVG выбран вместо GD: GD не везде есть
на хостингах, а локальный рантайм из deb — тем более.
3. Восстановление — токен 1 час, sha256 в БД, request всегда 200 (нет
enumeration), сбой mail() → 503; тестовый режим GOLEARN_TEST=1:
письмо в файл, капча-код в заголовке X-Captcha-Debug.
4. Параллельные ветки: stage-8-1-server (эндпоинты + миграция
password_resets + smoke) и stage-8-1-web (страницы + строки);
зоны не пересекаются. Мерж: server → web.
## Объём
- server/: api/captcha.php, api/password-reset/request.php,
api/password-reset/confirm.php; register.php += captcha; миграция
password_resets; config.example.php += base_url, mail_from;
smoke.sh += сценарии капчи и восстановления.
- apps/web: pages/register.html, pages/password-reset.html (два
режима по query), правки account.astro (ссылки), sync-client.ts
+= requestPasswordReset/confirmPasswordReset + captcha в register,
strings.ts.
- docs/deploy.md += шаги: проверка mail() (тестовое письмо), base_url
в config.php.
## Приёмка
- npm run gates зелёные; smoke.sh (с новыми сценариями) зелёный
локально; astro build PASS (18 страниц).
## Риски
- Доставляемость mail() с shared-хостинга (SPF/DKIM) — ручная проверка
владельцем на деплое; риск в PROJECT_STATE.
- SVG-капча слабее GD/сервисной к OCR-ботам — для учебного сайта
приемлемо; при злоупотреблениях — Yandex SmartCaptcha через ADR.

View file

@ -0,0 +1,50 @@
# План этапа 8 — личный кабинет и синхронизация прогресса
Дата: 2026-08-06. Статус: завершён (461/461 + smoke.sh OK). Предшественник: этапы 17 завершены,
main = a99aeb9 (431/431). Активация — команда владельца; модель учётки и
до-триаж раздела V — ADR-0004.
## Ключевые решения (не пере-решать)
1. Модель — email+пароль (ADR-0004); БЕЗ верификации почты и
восстановления пароля (нет SMTP на shared-хостинге; предупреждение в
UI при регистрации).
2. PHP 8 без фреймворка, PDO SQLite (WAL, busy_timeout, БД вне web root),
prepared statements везде, лимит тела 256 КБ, rate limit по IP,
SameSite=Lax + заголовок X-GoLearn-Client как CSRF-мера. Синтаксис
PHP ≥ 8.0 — совместимость с хостингами.
3. Прогресс на сервере — opaque ProgressV2 (ProgressV1 + updatedAt),
last-write-wins по updatedAt; localStorage — SoT для гостей;
миграция V1→V2 в storage.ts по контракту конверта.
4. Превью статическое: кабинет деградирует тихо при недоступном API
(status 'unavailable', пометка в UI, без алертов).
5. Локальная проверка бэкенда: PHP 8.2 CLI (распакованные deb-пакеты в
/tmp/debs, обёртка /tmp/php-run) + php -S + curl smoke.sh. В среде
нет системного PHP — гейты npm PHP не запускают.
6. Параллельные ветки: stage-8-server (server/) и stage-8-web (apps/web);
общих файлов нет. Мерж: server → web.
## Объём
- server/: api/{register,login,logout,me,progress,health}.php,
lib/{db,http,auth,ratelimit}.php, config.example.php, .htaccess
(заголовки, запрет листинга), tests/smoke.sh.
- apps/web: storage.ts (ProgressV2 + миграция), sync-client.ts
(mergeDecision чистая + fetch), pages/account.astro, nav + strings.
- docs/deploy.md — деплой-чеклист V.2 (артефакты, порядок, smoke,
откат, пути на хостинге, бэкап/restore SQLite).
- PROJECT_STATE: этап 8 разморожен → в работе → завершён.
## Приёмка этапа
- npm run gates зелёные (0 warnings), astro build PASS.
- server/tests/smoke.sh зелёный локально (PHP 8.2): полный прогон
register/login/put/get/401/403/429/health.
- Ручная приёмка владельцем на хостинге — по docs/deploy.md.
## Риски
- PHP на хостинге владельца может отличаться (версия, pdo_sqlite) —
чек-лист deploy.md начинается с проверки `php -v` и модулей.
- Сессии PHP на shared-хостинге: gc и путь сессий — зафиксировать
session_save_path в config при проблемах (заметка в deploy.md).