--- name: phase-work description: Берёт ровно одну фазу из среза в docs/phases/ и доводит её до конца — в своём git worktree и на своей ветке. Используй, когда просят «сделай фазу 25», «возьми фазу», «реализуй фазу», «work on the phase». Не для волны из нескольких агентов — /phase-orch, не для ревью — /phase-review, не для бага — /bug-work, не для задачи вне среза — /side-work. --- # Работа над фазой Один запуск — **одна фаза**. Скилл берёт её из `docs/phases/`, делает целиком, проверяет по её же критериям приёмки и отдаёт готовую ветку. Ничего сверх взятой фазы он не трогает: именно это позволяет запустить несколько агентов на один срез и не получить кашу. Критерии приёмки уже написаны. У каждой фазы есть «Задачи», «Тесты, без которых фаза не закрыта» и «Критерий готовности», у проекта — инварианты в `AGENTS.md`, у среза — дизайн-док. Выдумывать нечего, надо сделать и доказать. ## Какую фазу брать **Номер передали в аргументе** — берёшь его, вопросов нет. Так пользователь и `/phase-orch` раздают фазы: `/phase-work 25`. Ветка `phase/-*` уже есть — это продолжение (зависшего агента сменили), не гонка: работай в существующем worktree, не создавай вторую ветку и не сбрасывай 🔄 на ⬜. **Номера нет** — выбираешь сам. Волну «запусти троих» так не раздавай — это `/phase-orch`. 1. Читаешь `docs/phases/README.md`: находишь первый срез, где есть ⬜. 2. Внутри среза берёшь первую ⬜, у которой закрыты зависимости — они перечислены в шапке фазы. 3. Проверяешь, что её никто не взял: `git branch --list "phase/<номер>-*"` пусто. **Статус в `README.md` — первый источник правды, ветка — только арбитр гонки.** 🔄 значит занято, ✅ значит сделано, ⬜ значит свободно. Ветка может существовать и при ✅ — её просто не убрали после слияния; это не заявка, а мусор, см. «Кто убирает ветку». Фаза 🔄 — чужая работа в процессе. Не трогай её, даже если кажется, что там застряли — кроме случая выше: номер передали и ветка уже есть, тебя посадили продолжить. ## Как застолбить Продолжение (номер дали и ветка `phase/-*` уже есть) — этот блок **пропусти**: ветку не создавай, 🔄 не сбрасывай, садись в существующий worktree. «already exists» здесь не гонка. Ветка — и есть заявка, потому что git не даст создать её дважды. Указывай **`main`**, не текущий HEAD: IDE пользователя часто стоит на `bug/…`, и ветка от HEAD утащит фазу на чужую историю. ```bash git branch phase/25-golden-fixtures main ``` Команда упала с «already exists» — значит фазу взял другой агент между твоей проверкой и попыткой. Не спорь, вернись к выбору и возьми следующую. Ветка создалась — фаза твоя. Сразу пометь её 🔄 в `docs/phases/README.md` **на `main`**, отдельным коммитом из одной этой строки. Соседи читают занятость с `main`, а пустая свежая ветка ещё ничем не отличается от слитой — по ней одной понять, что фаза в работе, нельзя. Как доставить этот коммит, зависит от основного дерева (`git -C <корень> branch --show-current`): - дерево **уже на `main`** — правь README там и коммить. Это единственная правка вне своего worktree фазы. - дерево на `bug/…` или другой ветке пользователя — **не** `checkout`, **не** коммить в это дерево. Одноразовый worktree `main`: ```bash git worktree add /wt-claim-main main # README ⬜→🔄, commit, затем: git worktree remove /wt-claim-main ``` Пометить 🔄 надо **до** первой строчки кода, а не после. Коммит «Mark phase N…», который уже уехал в `bug/…`, не revertь и не переписывай историю пользователя — доведи 🔄 на `main` через `wt-claim-main` (cherry-pick, если коммит тот же). ## Где работать В своём worktree, а не в общем дереве. Иначе два агента правят одни файлы и роняют друг другу сборку — или, хуже, пишут код фазы в `bug/…` пользователя. ```bash git worktree add <путь-в-scratchpad>/wt-phase-25 phase/25-golden-fixtures ``` Путь — **вне репозитория**; каталог scratchpad этой сессии подходит. Worktree внутри репозитория засоряет `git status` и попадёт в глаза следующему ревью. Cursor workspace по умолчанию — корень IDE, не этот worktree. После `worktree add` каждый `Read` / `Write` / `StrReplace` / `Shell` идёт **абсолютным путём** (или `working_directory`) внутрь `…/wt-phase-25`. Относительный `docs/phases/README.md` пишется в ветку, которую сейчас держит пользователь. `git add` / `commit` без `-C` worktree — то же самое. Занятость чужих фаз читай так: `git show main:docs/phases/README.md` и `git branch --list "phase/*"` — не рабочую копию на `bug/…`. Дальше вся работа там. В конце worktree удаляется (`git worktree remove --force`), ветка остаётся. Что нужно знать про worktree: - Первая сборка полная, минуту-две. Это нормально, а не сломанный кэш. - `bin` и `obj` у каждого worktree свои, поэтому параллельные `dotnet test` не мешают друг другу. - Тесты хоста поднимают сервер со **своей** папкой сейвов внутри worktree — соседний агент их не видит. Порты Aspire раздаёт сам, конфликтов нет. - `node_modules` не копируется. Для клиентской фазы либо `npm ci`, либо скопируй `node_modules` из основного дерева — это быстрее и достаточно. ## Порядок работы **1. Прочитай три документа, прежде чем писать код.** Саму фазу; дизайн-док среза, ссылка на него в шапке среза в `README.md`; инварианты и «Things that will bite you» в `AGENTS.md`. Дизайн объясняет *почему* так, а не иначе — без него легко сделать формально по задачам и мимо смысла. **2. Начни с тестов из списка «Тесты, без которых фаза не закрыта».** Они уже сформулированы — превращай каждый пункт в тест. Проект для теста выбирает не удобство, а «Testing policy» в `AGENTS.md`: генерация людей — в `HSchool.People.Tests`, планировщик — в `HSchool.Schedule.Tests`, эндпоинты — в `HSchool.AppHost.Tests`. Тест генератора в тестах хоста найдёт следующее ревью. **3. Делай задачи по порядку и отмечай `- [x]` по мере готовности.** Галочка ставится, когда есть код **и** подтверждение, а не когда «в целом написано». **4. Правь документы тем же коммитом, что и код.** Это правило проекта, а не вежливость: - меняется раскладка сообщения — `ProtocolCodec.cs`, `protocol.ts` и `docs/protocol.md` вместе, плюс бамп версии; - меняется HTTP — `docs/protocol.md` тем же коммитом; - решение разошлось с дизайн-доком — правится документ, и в отчёте это названо явно. **5. Проверь то, что заявляешь.** Гоняй только проект(ы) из Testing policy в `AGENTS.md`, которые эта фаза трогает; пока пишешь — `--filter` на новый класс. Клиентский Vitest — только если трогал `src/HSchool.Client`. `HSchool.AppHost.Tests` — только если трогал HTTP, сокет или хост. Весь solution и «на всякий случай оба стека» не гонять. Не «должно работать», а «прогнал вот этот проект, вот результат». ## Что считать сделанным Фаза закрыта, когда **каждый** пункт «Тестов, без которых фаза не закрыта» существует отдельным тестом и проходит, и когда выполнен «Критерий готовности». Не раньше. ✅ в `README.md` ставится последним коммитом ветки, и в этом коммите — только строка статуса. Отдельным потому, что при слиянии нескольких веток строки статуса конфликтуют, а мелкий коммит разруливается за секунду. Не смог закрыть пункт — оставь ⬜ и напиши в отчёте, что именно не вышло и почему. Полработы с честной пометкой лучше, чем ✅ с дырой: следующее ревью её всё равно найдёт, только дороже. ## Слить в main и убрать ветку Фаза не считается сданной, пока лежит на ветке. Доводишь до `main` сам, тем же запуском. Сливать можно **только в основном дереве**: `main` уже занят им, и во второй worktree его не взять — git прямо откажет. А раз дерево одно на всех агентов, слияния надо выстроить в очередь. **1. Убери свой worktree.** Пока он держит ветку, слияние и удаление невозможны. ```bash git worktree remove --force <путь-в-scratchpad>/wt-phase-25 ``` **2. Возьми замок слияния** — той же уловкой, что и заявку на фазу: ```bash git branch merge/lock ``` Упало «already exists» — сливает другой агент (фаза, баг, ревью или side: замок **общий**). Подожди и повтори; не лезь в дерево и **не снимай** чужой замок. Замок снимается `git branch -D merge/lock` **на всех путях**, включая неудачные: забытый замок остановит остальных. **3. Проверь основное дерево, прежде чем трогать.** Оно должно **уже** быть на `main` и чистым: ```bash git -C <корень> branch --show-current git -C <корень> status --porcelain ``` Не `main` (часто `bug/…` в IDE пользователя) — **не** `checkout`, ничего не сливай, сними свой замок, отчёт. Непусто — там человек или сосед. То же: не сливать, снять замок, отчёт. Чужие незакоммиченные правки и чужая ветка в основном дереве дороже твоей. Stash→merge — не ты: это merge-агент `/phase-orch`, и только если пользователь сказал «сливай». **4. Слей явным коммитом слияния:** ```bash git -C <корень> merge --no-ff phase/25-golden-fixtures ``` `--no-ff` — чтобы фаза осталась видимой в истории одним куском, а не растворилась в линии. **5. Конфликты разрешай только те, что понимаешь.** Почти всегда это строка статуса в `docs/phases/README.md` — там обе стороны правы, надо оставить обе. Конфликт в коде, смысл которого тебе неясен, — `git merge --abort`, снять замок, оставить ветку и написать в отчёте, с чем именно она не сходится. **6. После слияния.** Конфликт только в строке статуса или в доке — тесты не гонять, ничего не сошлось в коде. Был конфликт в коде — тот же узкий набор проектов, что в шаге 5, не весь solution. Две зелёные ветки в сумме бывают красными; ловить это должен затронутый проект, а не ритуал на восемь сборок. Красно — откати слияние и оставь ветку жить: ```bash git -C <корень> reset --hard ORIG_HEAD ``` **7. Зелено — удали ветку и сними замок:** ```bash git -C <корень> branch -d phase/25-golden-fixtures git branch -D merge/lock ``` `-d`, а не `-D`: git откажется удалять неслитое, и это последняя защита от потери работы. **Не пушь.** Удалёнка есть, но отправка — решение пользователя; делай только если попросили прямо. **Осиротевшее.** Ветку когда-то забыли удалить — уберёт следующий запуск, но **только при двух условиях сразу**: фаза стоит ✅ в `README.md` **и** ветка слита в `main`. ```bash git branch --merged main --list "phase/*" --format="%(refname:short)" ``` Оба условия обязательны, и вот почему: только что созданная ветка ещё не имеет коммитов, а значит равна `main` и в этот список попадает. Удалять по одной «слитости» — снести чужую только что взятую фазу. Статус ✅ и есть то, что отличает доделанное от начатого. Каталоги умерших агентов подчищаются `git worktree prune` — записи о несуществующих worktree и так мусор. ## Границы - **Только своя фаза.** Заметил проблему в соседней — строкой в отчёт, не в диф. Реализация, которая походя переписала полпроекта, невозможна к просмотру глазами. - **Сливаешь только свою ветку** и только по порядку из «Слить в main и убрать ветку». Чужие ветки не сливаешь, даже если они выглядят готовыми. - **Не переставляй чужие статусы** в `README.md` и не трогай чужие 🔄. Своя фаза на 🔄, которую тебе отдали продолжить (номер + ветка), — не чужая: работай там, статус не возвращай в ⬜. - **Не меняй молча** версию протокола, форму сейва и публичное поведение API — даже когда это очевидно правильно. Если фаза этого требует, так и написано в её задачах. - **Общие файлы трогай минимально.** `AGENTS.md`, `docs/protocol.md`, `README.md` правят все ветки сразу — чем точечнее правка, тем дешевле слияние. ## Окружение: что укусит - **`MSB3021` / `MSB3027`, «блокирует этот файл»** — у пользователя запущено приложение, оно держит DLL. **Процесс не убивать.** В своём worktree сборка обычно проходит, там свои `bin`; если всё же упёрлось — проверь то, что можно без сборки, и скажи об этом в отчёте прямо. - **Концы строк.** В репозитории есть и CRLF, и LF. `sed -i` переворачивает файл целиком: правка в три строки превращается в диф на тысячу. Правь скриптом, который читает и пишет с `newline` пустой строкой, а потом смотри `git diff --stat` — внезапные сотни строк там, где ты правил три, это оно. - **Heredoc и экранирование.** Файлы с табуляциями, юникод-экранами и кавычками приезжают через heredoc покалеченными. Пиши такие файлы инструментом записи файла, а не `cat` в шелле. - **Тесты хоста делят один сервер и одну папку сейвов**, поэтому каждый начинает с очистки списка. Не пиши тест, который полагается на пустую базу или на конкретный id школы. - **Состав школы зависит от её id**, пока не сделана фаза 26. Тест, требующий у первого ученика двух родителей или конкретную фамилию, зелёный у тебя и красный в CI. Проверяй свойство, а не совпадение. - **Не гоняй `dotnet test` на solution** «на всякий случай»: это восемь проектов и подъём хоста. Если всё же гонял и упало целиком с нативной ошибкой в `Simulation.Tests` — один повтор того же проекта; повторяется — в отчёт. - **Dev-сервер не запускать.** Нужно посмотреть на UI — пользуйся уже запущенным приложением пользователя. ## Измеряй, а не рассуждай Когда вопрос звучит как «а хорошо ли ложатся часы» или «а сколько выходит неполных семей» — не рассуждай о коде. Напиши временный тест, который печатает реальные числа, посмотри и удали его. Час размышлений о поведении алгоритма стоит дороже и ошибается чаще, чем один прогон с цифрами. Числа из такого замера — лучшее, что можно положить в отчёт. ## Отчёт Коротко, в конце: - какая фаза взята и на какой ветке лежит; - что сделано — по пунктам задач, без пересказа кода; - какие тесты добавлены, списком по одной строке; - чем подтверждено: какой проект прогнал и с каким результатом, числа замеров, если были; не «весь solution зелёный»; - что осталось незакрытым и почему; - что замечено за границами фазы; - слита ли ветка в `main` и удалена ли — а если нет, то что помешало. Итог запуска — фаза в `main`, ветка удалена, worktree убран, замок снят. Всё, что осталось от работы, — это коммиты и отчёт.