Update AGENTS.md to reflect live status of country, wardrobes, and weather; clarify off-queue tasks in off-queue.md; introduce phase 10 (craft) in README.md with design details and phase breakdown.
ci / server (push) Failing after 3m39s
ci / client (push) Failing after 15s

This commit is contained in:
Leonid Pershin
2026-08-20 12:08:27 +03:00
parent d8a2c2903c
commit 4f3d3fc068
8 changed files with 324 additions and 6 deletions
+2 -1
View File
@@ -20,11 +20,12 @@ way; this file is *how to work in them*.
| connection handling, workers, saves | `src/HSchool.Server` | | connection handling, workers, saves | `src/HSchool.Server` |
| what runs locally | `src/HSchool.AppHost/AppHost.cs` | | what runs locally | `src/HSchool.AppHost/AppHost.cs` |
| screens, dialogs, formatting, UI language | `src/HSchool.Client/src` | | 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 2937; country and wardrobes are live, weather is not | | clothing, inventory, climate, dress codes | [`docs/design/inventory.md`](docs/design/inventory.md) — slice 7, phases 2937; country, wardrobes and weather are live |
| login, who owns a school, watching others | [`docs/design/session.md`](docs/design/session.md) — slice 8, phases 3839 | | login, who owns a school, watching others | [`docs/design/session.md`](docs/design/session.md) — slice 8, phases 3839 |
| clock speed buttons and high-speed stride | `src/HSchool.Simulation/ClockSpeed.cs` **and** [`docs/design/session.md`](docs/design/session.md) — phase 40 | | 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 4147 | | talks, opinions, fights, romance pack | [`docs/design/social.md`](docs/design/social.md) — slice 9, phases 4147 |
| lesson consequences (teacher present, skill, warmth, textbook, weather commute) | [`docs/design/off-queue.md`](docs/design/off-queue.md) — phases 48, 5053 | | lesson consequences (teacher present, skill, warmth, textbook, weather commute) | [`docs/design/off-queue.md`](docs/design/off-queue.md) — phases 48, 5053 |
| layer tests, client tsc-in-test, worker/card splits, dump nodes | [`docs/design/craft.md`](docs/design/craft.md) — slice 10, phases 5659 |
| 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 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 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` | | a new named slice of the game | `.claude/skills/slice-work` — discuss, write design and phases, then `/phase-orch` |
+131
View File
@@ -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 занята |
+3 -2
View File
@@ -1,7 +1,8 @@
# Вне очереди # Вне очереди
Задачи, которых нет в нумерованных срезах. Сюда не дописывают срез 9 и не заводят срез 10: Задачи, которых нет в нумерованных срезах. Срез 10 — ремесло
склейка уже живых систем и мелкий DX. После любой фазы школа целая. ([`craft.md`](craft.md)), не сюда. Сюда — склейка уже живых систем и мелкий DX. После любой
фазы школа целая.
Люди: [`people.md`](people.md). Штат: [`staffing.md`](staffing.md). Расписание: Люди: [`people.md`](people.md). Штат: [`staffing.md`](staffing.md). Расписание:
[`schedule.md`](schedule.md). Присутствие: [`ai.md`](ai.md). Одежда и климат: [`schedule.md`](schedule.md). Присутствие: [`ai.md`](ai.md). Одежда и климат:
+42
View File
@@ -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`. Не бампить протокол, не трогать сейв.
+37
View File
@@ -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.
+41
View File
@@ -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` «заодно».
+34
View File
@@ -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` мимо работника.
Не бампить протокол. Не трогать сейв.
+34 -3
View File
@@ -225,13 +225,44 @@
46 стоит на 37 и 42; 47 — на 42 и 23. `example` не трогать. 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). Дизайн: [`../design/off-queue.md`](../design/off-queue.md).
Склейка уже живых систем и мелкий DX, не новый кусок игры. Номера не спорят со срезом 10. Склейка уже живых систем и мелкий DX, не новый кусок игры. Номера не спорят с именованными
После каждой игровой фазы школа целая. 50–52 стоят на 48; 53 — на 18 и 32, можно параллельно срезами. После каждой игровой фазы школа целая. 50–52 стоят на 48; 53 — на 18 и 32, можно
с 48. 49 и 54 от них не зависят. Баг в уже сделанном — [`../bugs/README.md`](../bugs/README.md). параллельно с 48. 49 и 54 от них не зависят. Баг в уже сделанном —
[`../bugs/README.md`](../bugs/README.md).
| Фаза | Статус | Зачем | | Фаза | Статус | Зачем |
| --- | --- | --- | | --- | --- | --- |