diff --git a/AGENTS.md b/AGENTS.md index ccbab65..9b030ab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,11 +20,12 @@ way; this file is *how to work in them*. | connection handling, workers, saves | `src/HSchool.Server` | | what runs locally | `src/HSchool.AppHost/AppHost.cs` | | screens, dialogs, formatting, UI language | `src/HSchool.Client/src` | -| clothing, inventory, climate, dress codes | [`docs/design/inventory.md`](docs/design/inventory.md) — slice 7, phases 29–37; country and wardrobes are live, weather is not | +| clothing, inventory, climate, dress codes | [`docs/design/inventory.md`](docs/design/inventory.md) — slice 7, phases 29–37; country, wardrobes and weather are live | | login, who owns a school, watching others | [`docs/design/session.md`](docs/design/session.md) — slice 8, phases 38–39 | | clock speed buttons and high-speed stride | `src/HSchool.Simulation/ClockSpeed.cs` **and** [`docs/design/session.md`](docs/design/session.md) — phase 40 | | talks, opinions, fights, romance pack | [`docs/design/social.md`](docs/design/social.md) — slice 9, phases 41–47 | | lesson consequences (teacher present, skill, warmth, textbook, weather commute) | [`docs/design/off-queue.md`](docs/design/off-queue.md) — phases 48, 50–53 | +| layer tests, client tsc-in-test, worker/card splits, dump nodes | [`docs/design/craft.md`](docs/design/craft.md) — slice 10, phases 56–59 | | a bug in already-shipped behaviour | `.claude/skills/bug-work` — document in `docs/bugs/`, discuss only if the fix has a fork, then patch | | a task that is not in any slice | `.claude/skills/side-work` — discuss, document in «Вне очереди», then implement | | a new named slice of the game | `.claude/skills/slice-work` — discuss, write design and phases, then `/phase-orch` | diff --git a/docs/design/craft.md b/docs/design/craft.md new file mode 100644 index 0000000..8e19ebc --- /dev/null +++ b/docs/design/craft.md @@ -0,0 +1,131 @@ +# Ремесло: слои, страховка тестов, горячие точки + +Договорённость на срез после одежды, сессии и склейки урока, не текущий код. +Фундамент: [`foundation.md`](foundation.md). Проекты: [`projects.md`](projects.md). +Вне очереди: [`off-queue.md`](off-queue.md). Общение (не ждём 44/46): [`social.md`](social.md). + +Игра уже живая: люди ходят, одеваются, учатся, болтают. Рядом лежат четыре копии одного +запрета на ссылки, `npm test` зелёный при красном `tsc`, работник школы на тысячу строк и +дамп, который не проверяет явку. Срез это **подметает**. Новой кнопки нет. + +Срез отвечает на вопросы «сломал ли я слой», «прошёл ли клиентский tsc» и «можно ли разрезать +работника, не трогая поток». Оценок, протокола +1 и охоты на всё SOLID нет. + +## Что увидит игрок + +Ничего обязательного на экране. Школа после каждой фазы та же, что вчера. Игрок может заметить +только то, что агент и CI ловят дубликат ключа и чужой `using` до мержа, а не после. + +## На какие вопросы отвечает + +- Слои из [`projects.md`](projects.md) и инвариант 4 — тест в одном месте, не четыре копипасты. +- `npm test` клиента = typecheck + vitest; дубликат в `strings.ts` красный без `npm run build`. +- `SchoolWorker` и сборка карточки режутся по швам, поведение то же. + +## Чего нет + +- Новых экранов, полей сейва, байтов сокета. +- Фаз 44 и 46, портретов/SwarmUI как фичи, `GET /api/catalog.lunchBreaks`. +- Багов сессии (второе имя при живой куке, ключи DataProtection) — [`../bugs/README.md`](../bugs/README.md). +- Инверсии Game → Api DTO и «пройти SOLID по всему дереву». + +## Что меняется в уже принятом + +| Было | Стало | Почему | +| --- | --- | --- | +| Запрет Arch/ASP.NET/`DateTime.Now` скопирован в People, Ai, Schedule, Simulation | Один проект `HSchool.Architecture.Tests`; Content и Protocol в том же списке | Четыре теста расходятся; Content и Protocol молчат | +| `npm test` = только vitest | `npm test` = `tsc -b` и vitest | Журнал трижды ловил дубликат ключа, зелёный в vitest | +| `SchoolWorker` и `PersonCardReader` — по одному файлу на всё | `partial` / файлы по шву; один тип, один поток, один `Read` | SRP без нового инварианта | +| Дамп «люди с узлом **или** нуждами» | После найма и хода часов — кто-то с `NodeId` | Нужды есть у всех; явка не проверялась | + +Протокол, HTTP меню и формат сейва не меняются. Версию сокета не бампить. + +## Этап A — страховка + +Слои и клиентский tsc. После этапа школа целая, даже если работник ещё тысяча строк. + +### Слои одной пачкой + +Новый тестовый проект ссылается на библиотеки, не на AppHost. Пакета NetArchTest нет: как сейчас, +`GetReferencedAssemblies` и чтение `*.cs`. Копии `Assembly_DoesNotReference*` и `Sources_DoNotUseWallClock` +из People/Ai/Schedule/Simulation уезжают сюда. + +Список (как [`projects.md`](projects.md)): + +| Сборка | Не ссылается | Не содержит в исходниках | +| --- | --- | --- | +| Protocol | Arch, ASP.NET, Sockets, игровые проекты | `DateTime.Now` / `UtcNow` | +| Content | Arch, ASP.NET, Sockets, Protocol, Simulation | то же | +| People, Schedule, Ai | Arch, ASP.NET, Sockets | то же | +| Simulation | ASP.NET, Sockets | то же; Arch — да | + +`HSchool.Server` в этот список не входит: он и есть хост. + +### tsc в `npm test` + +Скрипт `test` гоняет `typecheck` (`tsc -b`), потом vitest. Петля `"hschool-client": "file:"` +в `package.json` уходит, если без неё `npm test` и `npm run build` зелёные. + +## Этап B — горячие точки + +Именованный рефактор, не аудит всего дерева. После этапа поведение то же: карточка, пауза, +снимок, дамп. + +1. **`SchoolWorker`** (~1200 строк) — `partial`: почта, тик, запись. Один тип, тот же + `LongRunning`-поток, без замка вокруг `School`/`World`. +2. **`PersonCardReader`** (~600) — сборка по вкладкам в том же классе или соседних + `internal` helper; публичный вход по-прежнему `Read(School, personId, locale)`. +3. **`personCard.ts`** (~850) — модули по вкладкам; `personCardHost.ts` по-прежнему + ходит в HTTP. + +Game-слой по-прежнему может зависеть от DTO в `Api` — инверсия не этот срез. + +## Этап C — дамп не врёт + +`Dump_ReturnsPeopleNodesAndTimetable` после найма **запускает** часы и ждёт человека с +`NodeId`, не `Needs.Count > 0`. На паузе не ждать людей (кадры присутствия всё равно идут). + +## Где живёт код + +Новой игровой библиотеки нет. + +| Что | Куда | +| --- | --- | +| Запреты слоёв и wall-clock | `tests/HSchool.Architecture.Tests` | +| Скрипт `npm test` | `src/HSchool.Client/package.json` | +| Работник | `src/HSchool.Server/Game/SchoolWorker*.cs` | +| Карточка HTTP | `PersonCardReader` + клиент `ui/personCard*.ts` | +| Дамп | уже `DevEndpoints` / `SchoolDumpReader`; меняется тест | + +## Советы, которые стоит принять сразу + +- Не тащить NetArchTest ради четырёх `Assert.DoesNotContain`. +- Не резать `SchoolWorker` на несколько типов с разными потоками. +- Не инвертировать Game → Api «заодно»: это другой срез или вне очереди. +- Не бампить протокол и не расширять catalog, чтобы «заодно» отдать `lunchBreaks`. +- Фаза 55 занята веткой `phase/55-view-state` — номера среза с 56. + +## Заведомо не сейчас + +- Срез 9: ссора/драка, политика речи и дом. +- Баги сессии и DataProtection. +- `GET /api/catalog` + `lunchBreaks`. +- Портреты как контент; SwarmUI не выкидывать. +- README «школа ещё пустая» как отдельная кампания — одна строка в фазе 56, не рерайт. +- Новый именованный кусок игры (оценки, приказы, магазины). + +## Зафиксировано этим разговором + +| Тема | Решение | +| --- | --- | +| Куда | Срез 10, не «Вне очереди» | +| SOLID | Arch-тесты слоёв + три именованные точки, не всё дерево | +| Список | Журнал ревью + ложь AGENTS; без инвентаризации-фазы | +| Баги сессии | Не брать, `/bug-work` | +| Протокол | Не бампить | +| HTTP | Не расширять catalog | +| Сейв | Не трогать | +| Срез 9 | Не ждать 44/46, не писать в них | +| Стоп | После A школа целая; после B — тоже | +| Ваниль | Без пака | +| Номера | 56–59; 55 занята | diff --git a/docs/design/off-queue.md b/docs/design/off-queue.md index 405de3c..e8e5317 100644 --- a/docs/design/off-queue.md +++ b/docs/design/off-queue.md @@ -1,7 +1,8 @@ # Вне очереди -Задачи, которых нет в нумерованных срезах. Сюда не дописывают срез 9 и не заводят срез 10: -склейка уже живых систем и мелкий DX. После любой фазы школа целая. +Задачи, которых нет в нумерованных срезах. Срез 10 — ремесло +([`craft.md`](craft.md)), не сюда. Сюда — склейка уже живых систем и мелкий DX. После любой +фазы школа целая. Люди: [`people.md`](people.md). Штат: [`staffing.md`](staffing.md). Расписание: [`schedule.md`](schedule.md). Присутствие: [`ai.md`](ai.md). Одежда и климат: diff --git a/docs/phases/56-architecture-tests.md b/docs/phases/56-architecture-tests.md new file mode 100644 index 0000000..8944ce9 --- /dev/null +++ b/docs/phases/56-architecture-tests.md @@ -0,0 +1,42 @@ +# Фаза 56. Слои одной пачкой + +## Зависимости + +Нет. Запреты уже размазаны по четырём тестовым проектам. + +## Зачем + +Инвариант 4 и таблица в [`projects.md`](../design/projects.md) должны ломаться одним красным +тестом, а не «забыли дописать пятую копию». Content и Protocol сейчас молчат. + +## Задачи + +- [ ] Проект `tests/HSchool.Architecture.Tests` в solution, `IsTestProject`, без AppHost и без + нового NuGet. Ссылки на Protocol, Content, People, Schedule, Ai, Simulation +- [ ] Тесты ссылок: каждая библиотека из таблицы в [`craft.md`](../design/craft.md) — этап A +- [ ] Тесты исходников: в `src/HSchool.{Protocol,Content,People,Schedule,Ai,Simulation}` нет + `DateTime.Now` / `DateTime.UtcNow` +- [ ] Удалить копипасты `Assembly_DoesNotReference*` и `Sources_DoNotUseWallClock` из People, + Ai, Schedule, Simulation (остальные факты тех файлов не трогать) +- [ ] Testing policy в `AGENTS.md`: слои — `HSchool.Architecture.Tests`, не хост +- [ ] README: школа не «ещё пустая»; потолок create — слоты игрока, не «шесть» + +## Тесты, без которых фаза не закрыта + +- [ ] Protocol не ссылается на Arch, ASP.NET, Sockets, Content, Simulation +- [ ] Content не ссылается на Arch, ASP.NET, Sockets, Protocol, Simulation +- [ ] People, Schedule, Ai не ссылаются на Arch, ASP.NET, Sockets +- [ ] Simulation не ссылается на ASP.NET и Sockets (Arch — можно) +- [ ] Исходники шести библиотек выше не содержат `DateTime.Now` / `DateTime.UtcNow` +- [ ] В People/Ai/Schedule/Simulation.Tests больше нет этих двух фактов + +## Критерий готовности + +- `dotnet test tests/HSchool.Architecture.Tests` проходит +- Добавить `using System.Net.Http` в `HSchool.Content` — слой красный +- Старые проекты тестов без этих фактов по-прежнему собираются + +## Стоп + +Не подключать NetArchTest. Не гонять AppHost. Не резать `SchoolWorker`. Не ссылаться на +`HSchool.Server`. Не бампить протокол, не трогать сейв. diff --git a/docs/phases/57-client-typecheck.md b/docs/phases/57-client-typecheck.md new file mode 100644 index 0000000..da9e6dc --- /dev/null +++ b/docs/phases/57-client-typecheck.md @@ -0,0 +1,37 @@ +# Фаза 57. tsc в npm test + +## Зависимости + +Нет. Скрипт `typecheck` уже есть; `npm test` его не вызывает. + +## Зачем + +Журнал срезов 1 и 7: дубликат ключа в `strings.ts` / поле в клиенте зелёный в vitest и красный +на `npm run build`. Агент гоняет `npm test` и мержит дыру. + +## Задачи + +- [ ] `package.json` → `"test":` сначала `tsc -b` (`typecheck`), потом `vitest run`. Без + `&&` скрывать код выхода typecheck +- [ ] Убрать петлю `"hschool-client": "file:"` из `dependencies`; `npm test` и `npm run build` + без неё +- [ ] `AGENTS.md` Commands и Testing policy: клиентский `npm test` включает typecheck +- [ ] `test:watch` по-прежнему только vitest — typecheck на каждый файл в watch не нужен + +## Тесты, без которых фаза не закрыта + +- [ ] `npm --prefix src/HSchool.Client test` гоняет tsc и vitest и проходит на текущем дереве +- [ ] Дубликат ключа в одном объекте `strings.ts` (ru или en) делает `npm test` красным без + `npm run build` +- [ ] В `package.json` нет зависимости `hschool-client` +- [ ] `npm --prefix src/HSchool.Client run build` проходит + +## Критерий готовности + +- Обычный агентский `npm test` ловит TS2300/TS1117 +- Watch-режим не стал вдвое медленнее из-за полного `tsc -b` на каждый файл + +## Стоп + +Не тащить eslint ради одного дубликата. Не включать клиентский TS в stamp AppHost (фаза 49). +Не бампить протокол. Не трогать сейв и HTTP. diff --git a/docs/phases/58-worker-card-split.md b/docs/phases/58-worker-card-split.md new file mode 100644 index 0000000..e71de72 --- /dev/null +++ b/docs/phases/58-worker-card-split.md @@ -0,0 +1,41 @@ +# Фаза 58. SchoolWorker и карточка + +## Зависимости + +Нет. Поведение уже покрыто хостом и клиентскими тестами карточки. Этап A (56, 57) не обязан +быть зелёным, но резать удобнее после tsc в `npm test`. + +## Зачем + +Три файла держат слишком много швов: почта работника, тик, запись; сборка карточки; вкладки +DOM. Разрезать по существующим границам, не меняя поток и JSON. + +## Задачи + +- [ ] `SchoolWorker` — `partial` (почта / тик / запись или эквивалент по швам). Один тип, + тот же `LongRunning`-поток, тот же mailbox. Без `lock` вокруг `School`/`World` +- [ ] `PersonCardReader`: публичный вход по-прежнему `Read(School, personId, locale)`; + сборка вкладок — соседние `internal` helper или `partial`, не второй публичный API +- [ ] `personCard.ts`: вкладки в отдельных модулях; `personCardHost.ts` по-прежнему fetch +- [ ] Инверсия Game → типы из `HSchool.Server.Api` **не** входит: `using` можно оставить + +## Тесты, без которых фаза не закрыта + +- [ ] Карточка: нужды, одежда/ноша, связи — как сейчас (`PeopleApiTests`, + `InventoryApiTests`, `Card_ConnectionsCarryOnlyThisPersonsOpinions`) +- [ ] `personCard.test.ts` (вкладки, портрет-кнопки, связи) проходит +- [ ] Пауза одной школы не останавливает другую (существующий хостовый факт) +- [ ] Дамп школы по-прежнему 200 на известный id (`Dump_ReturnsPeopleNodesAndTimetable` — + текущий оракул, ужесточение — фаза 59) +- [ ] `SchoolWorker` остаётся одним `internal sealed` типом (рефлексия или исходник: нет + второго worker-класса с mailbox) + +## Критерий готовности + +- Открыть школу, карточку, паузу — как вчера +- Нет нового публичного фасада карточки и нет второго потока на школу + +## Стоп + +Не менять инвариант 3. Не инвертировать DTO. Не трогать протокол, сейв, SwarmUI. Не резать +`GameLoopService` «заодно». diff --git a/docs/phases/59-dump-nodes.md b/docs/phases/59-dump-nodes.md new file mode 100644 index 0000000..43e6ebe --- /dev/null +++ b/docs/phases/59-dump-nodes.md @@ -0,0 +1,34 @@ +# Фаза 59. Дамп проверяет узлы + +## Зависимости + +Нет. Ручка дампа и тест уже есть (фаза 27). Журнал среза 6: оракул не проверяет явку. + +## Зачем + +`Needs.Count > 0` истинно у всех. Тест зелёный на пустом кампусе. После найма дамп должен +видеть человека в узле, как ревью и хотело замерить. + +## Задачи + +- [ ] `Dump_ReturnsPeopleNodesAndTimetable`: нанять, **включить** часы, дождаться человека с + `NodeId`, затем дамп. Не ждать людей на паузе +- [ ] Нужды по-прежнему не пустые у всех в выборке, но явка — отдельный assert +- [ ] Урок математики в дампе — как сейчас + +## Тесты, без которых фаза не закрыта + +- [ ] После найма математика и хода часов дамп содержит человека с непустым `NodeId` +- [ ] Все люди в дампе с id, именем и непустым `Needs` +- [ ] В `dump.Lessons` есть Mathematics +- [ ] Неизвестный id — по-прежнему 404 `unknown-school` + +## Критерий готовности + +- Сломать публикацию `NodeId` в дампе — этот тест красный, а не только глаз ревью +- На паузе тест не висит на пустой явке + +## Стоп + +Не расширять JSON дампа. Не включать ручку по умолчанию. Не ходить в `World` мимо работника. +Не бампить протокол. Не трогать сейв. diff --git a/docs/phases/README.md b/docs/phases/README.md index 09a1c77..c1db561 100644 --- a/docs/phases/README.md +++ b/docs/phases/README.md @@ -225,13 +225,44 @@ 46 стоит на 37 и 42; 47 — на 42 и 23. `example` не трогать. +## Срез 10. Ремесло + +Дизайн: [`../design/craft.md`](../design/craft.md). + +Срез без новой кнопки. Слои ловятся одним тестовым проектом, клиентский `npm test` гоняет tsc, +работник и карточка режутся по швам, дамп проверяет явку. После этапа A школа целая, даже если +файлы ещё толстые. Не ждёт фазы 44 и 46. + +Порядок свободный: 56, 57 и 59 ни от чего не зависят, 58 удобнее после 57. Начинать разумно с +56 или 57. + +**Этап A — страховка.** Слои и tsc. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [56. Слои одной пачкой](56-architecture-tests.md) | ⬜ | Один проект вместо четырёх копипаст | +| [57. tsc в npm test](57-client-typecheck.md) | ⬜ | Дубликат ключа красный без `npm run build` | + +**Этап B — горячие точки.** Поведение то же. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [58. SchoolWorker и карточка](58-worker-card-split.md) | ⬜ | partial / модули по швам, один поток | + +**Этап C — дамп.** + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [59. Дамп проверяет узлы](59-dump-nodes.md) | ⬜ | После найма в дампе есть `NodeId` | + ## Вне очереди Дизайн: [`../design/off-queue.md`](../design/off-queue.md). -Склейка уже живых систем и мелкий DX, не новый кусок игры. Номера не спорят со срезом 10. -После каждой игровой фазы школа целая. 50–52 стоят на 48; 53 — на 18 и 32, можно параллельно -с 48. 49 и 54 от них не зависят. Баг в уже сделанном — [`../bugs/README.md`](../bugs/README.md). +Склейка уже живых систем и мелкий DX, не новый кусок игры. Номера не спорят с именованными +срезами. После каждой игровой фазы школа целая. 50–52 стоят на 48; 53 — на 18 и 32, можно +параллельно с 48. 49 и 54 от них не зависят. Баг в уже сделанном — +[`../bugs/README.md`](../bugs/README.md). | Фаза | Статус | Зачем | | --- | --- | --- |