290 lines
22 KiB
Markdown
290 lines
22 KiB
Markdown
---
|
||
name: phase-work
|
||
description: Берёт ровно одну фазу из среза в docs/phases/ и доводит её до конца — в своём git worktree и на своей ветке. Используй, когда просят «сделай фазу 25», «возьми фазу», «реализуй фазу», «work on the phase». Не для волны из нескольких агентов — /phase-orch, не для ревью — /phase-review, не для бага — /bug-work, не для задачи вне среза — /side-work, не для нового среза — /slice-work.
|
||
---
|
||
|
||
# Работа над фазой
|
||
|
||
Один запуск — **одна фаза**. Скилл берёт её из `docs/phases/`, делает целиком, проверяет по её же
|
||
критериям приёмки и отдаёт готовую ветку. Ничего сверх взятой фазы он не трогает: именно это
|
||
позволяет запустить несколько агентов на один срез и не получить кашу.
|
||
|
||
Критерии приёмки уже написаны. У каждой фазы есть «Задачи», «Тесты, без которых фаза не закрыта» и
|
||
«Критерий готовности», у проекта — инварианты в `AGENTS.md`, у среза — дизайн-док. Выдумывать
|
||
нечего, надо сделать и доказать.
|
||
|
||
## Какую фазу брать
|
||
|
||
**Номер передали в аргументе** — берёшь его, вопросов нет. Так пользователь и `/phase-orch`
|
||
раздают фазы: `/phase-work 25`. Ветка `phase/<N>-*` уже есть — это продолжение (зависшего
|
||
агента сменили), не гонка: работай в существующем worktree, не создавай вторую ветку и не
|
||
сбрасывай 🔄 на ⬜.
|
||
|
||
**Номера нет** — выбираешь сам. Волну «запусти троих» так не раздавай — это `/phase-orch`.
|
||
|
||
1. `rg "⬜" docs/phases/*/README.md` — не читай индексы всех срезов. Оглавление
|
||
`docs/phases/README.md` — каталог папок, без таблиц фаз.
|
||
2. В первом срезе с ⬜ берёшь первую ⬜, у которой закрыты зависимости — они в шапке фазы.
|
||
3. Проверяешь, что её никто не взял: `git branch --list "phase/<номер>-*"` пусто.
|
||
|
||
Номер дали — файл `docs/phases/*/<N>-*.md`. Читай его, `README.md` этой папки и дизайн
|
||
среза по ссылке в индексе. Чужие срезы и `reviewed.md` не открывать.
|
||
|
||
**Статус в индексе среза (`docs/phases/<slice>/README.md`) — первый источник правды, ветка —
|
||
только арбитр гонки.** 🔄 значит занято, ✅ значит сделано, ⬜ значит свободно. Ветка может
|
||
существовать и при ✅ — её просто не убрали после слияния; это не заявка, а мусор, см.
|
||
«Кто убирает ветку».
|
||
|
||
Фаза 🔄 — чужая работа в процессе. Не трогай её, даже если кажется, что там застряли —
|
||
кроме случая выше: номер передали и ветка уже есть, тебя посадили продолжить.
|
||
|
||
## Как застолбить
|
||
|
||
Продолжение (номер дали и ветка `phase/<N>-*` уже есть) — этот блок
|
||
**пропусти**: ветку не создавай, 🔄 не сбрасывай, садись в существующий
|
||
worktree. «already exists» здесь не гонка.
|
||
|
||
Ветка — и есть заявка, потому что git не даст создать её дважды. Указывай **`main`**, не текущий
|
||
HEAD: IDE пользователя часто стоит на `bug/…`, и ветка от HEAD утащит фазу на чужую историю.
|
||
|
||
```bash
|
||
git branch phase/25-golden-fixtures main
|
||
```
|
||
|
||
Команда упала с «already exists» — значит фазу взял другой агент между твоей проверкой и попыткой.
|
||
Не спорь, вернись к выбору и возьми следующую.
|
||
|
||
Ветка создалась — фаза твоя. Сразу пометь её 🔄 в `docs/phases/<slice>/README.md` **на `main`**, отдельным
|
||
коммитом из одной этой строки. Соседи читают занятость с `main`, а пустая свежая ветка ещё ничем
|
||
не отличается от слитой — по ней одной понять, что фаза в работе, нельзя.
|
||
|
||
Как доставить этот коммит, зависит от основного дерева (`git -C <корень> branch --show-current`):
|
||
|
||
- дерево **уже на `main`** — правь README там и коммить. Это единственная правка вне своего
|
||
worktree фазы.
|
||
- дерево на `bug/…` или другой ветке пользователя — **не** `checkout`, **не** коммить в это
|
||
дерево. Одноразовый worktree `main`:
|
||
|
||
```bash
|
||
git worktree add <scratch>/wt-claim-main main
|
||
# README ⬜→🔄, commit, затем:
|
||
git worktree remove <scratch>/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/<slice>/README.md` пишется в ветку, которую сейчас
|
||
держит пользователь. `git add` / `commit` без `-C` worktree — то же самое.
|
||
|
||
Занятость чужих фаз читай так: `git show main:docs/phases/<slice>/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/<slice>/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` правят все ветки сразу.
|
||
Индекс — только свой `docs/phases/<slice>/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 убран, замок снят. Всё, что осталось от
|
||
работы, — это коммиты и отчёт.
|