- Revised instructions for merging branches into `main`, emphasizing the importance of removing worktrees before merging. - Added detailed steps for acquiring a merge lock and checking the main branch status prior to merging. - Clarified the conditions under which orphaned branches and worktrees are cleaned up. - Enhanced the final report requirements to include the status of the branch and worktree after completion.
243 lines
18 KiB
Markdown
243 lines
18 KiB
Markdown
---
|
||
name: phase-work
|
||
description: Берёт ровно одну фазу из среза в docs/phases/ и доводит её до конца — в своём git worktree и на своей ветке, чтобы над одним срезом можно было запустить несколько агентов параллельно. Используй, когда просят «сделай фазу 25», «возьми фазу», «реализуй фазу», «начни срез», «раздели срез между агентами», «work on the phase». Не для ревью уже написанного — там /phase-review, и не для правки одного файла по просьбе.
|
||
---
|
||
|
||
# Работа над фазой
|
||
|
||
Один запуск — **одна фаза**. Скилл берёт её из `docs/phases/`, делает целиком, проверяет по её же
|
||
критериям приёмки и отдаёт готовую ветку. Ничего сверх взятой фазы он не трогает: именно это
|
||
позволяет запустить несколько агентов на один срез и не получить кашу.
|
||
|
||
Критерии приёмки уже написаны. У каждой фазы есть «Задачи», «Тесты, без которых фаза не закрыта» и
|
||
«Критерий готовности», у проекта — инварианты в `AGENTS.md`, у среза — дизайн-док. Выдумывать
|
||
нечего, надо сделать и доказать.
|
||
|
||
## Какую фазу брать
|
||
|
||
**Номер передали в аргументе** — берёшь его, вопросов нет. Так пользователь раздаёт фазы агентам:
|
||
`/phase-work 25`, `/phase-work 26` в соседних окнах.
|
||
|
||
**Номера нет** — выбираешь сам:
|
||
|
||
1. Читаешь `docs/phases/README.md`: находишь первый срез, где есть ⬜.
|
||
2. Внутри среза берёшь первую ⬜, у которой закрыты зависимости — они перечислены в шапке фазы.
|
||
3. Проверяешь, что её никто не взял: `git branch --list "phase/<номер>-*"` пусто.
|
||
|
||
**Статус в `README.md` — первый источник правды, ветка — только арбитр гонки.** 🔄 значит занято,
|
||
✅ значит сделано, ⬜ значит свободно. Ветка может существовать и при ✅ — её просто не убрали после
|
||
слияния; это не заявка, а мусор, см. «Кто убирает ветку».
|
||
|
||
Фаза 🔄 — чужая работа в процессе. Не трогай её, даже если кажется, что там застряли.
|
||
|
||
## Как застолбить
|
||
|
||
Ветка — и есть заявка, потому что git не даст создать её дважды:
|
||
|
||
```bash
|
||
git branch phase/25-golden-fixtures
|
||
```
|
||
|
||
Команда упала с «already exists» — значит фазу взял другой агент между твоей проверкой и попыткой.
|
||
Не спорь, вернись к выбору и возьми следующую.
|
||
|
||
Ветка создалась — фаза твоя. Сразу пометь её 🔄 в `docs/phases/README.md` **в основном дереве**,
|
||
отдельным коммитом, состоящим только из этой строки. Это единственная правка, которую ты делаешь
|
||
вне своего worktree, и она нужна по двум причинам: соседние агенты видят занятое, а пустая свежая
|
||
ветка ещё ничем не отличается от слитой — по ней одной понять, что фаза в работе, нельзя.
|
||
|
||
Пометить 🔄 надо **до** первой строчки кода, а не после.
|
||
|
||
## Где работать
|
||
|
||
В своём worktree, а не в общем дереве. Иначе два агента правят одни файлы и роняют друг другу
|
||
сборку.
|
||
|
||
```bash
|
||
git worktree add <путь-в-scratchpad>/wt-phase-25 phase/25-golden-fixtures
|
||
```
|
||
|
||
Путь — **вне репозитория**; каталог scratchpad этой сессии подходит. Worktree внутри репозитория
|
||
засоряет `git status` и попадёт в глаза следующему ревью.
|
||
|
||
Дальше вся работа там. В конце 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. Проверь то, что заявляешь.** Прогоняешь тесты своей фазы и полный прогон. Не «должно
|
||
работать», а «прогнал, вот результат».
|
||
|
||
## Что считать сделанным
|
||
|
||
Фаза закрыта, когда **каждый** пункт «Тестов, без которых фаза не закрыта» существует отдельным
|
||
тестом и проходит, и когда выполнен «Критерий готовности». Не раньше.
|
||
|
||
✅ в `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» — сливает другой агент. Подожди и повтори; не лезь в дерево, пока замок
|
||
чужой. Замок снимается `git branch -D merge/lock` **на всех путях**, включая неудачные: забытый
|
||
замок остановит остальных.
|
||
|
||
**3. Проверь основное дерево, прежде чем трогать.** Оно должно быть на `main` и чистым:
|
||
|
||
```bash
|
||
git -C <корень> status --porcelain
|
||
```
|
||
|
||
Непусто — там работает человек или соседний агент. **Ничего не сливай**, сними замок и скажи об
|
||
этом в отчёте. Чужие незакоммиченные правки дороже твоей ветки.
|
||
|
||
**4. Слей явным коммитом слияния:**
|
||
|
||
```bash
|
||
git -C <корень> merge --no-ff phase/25-golden-fixtures
|
||
```
|
||
|
||
`--no-ff` — чтобы фаза осталась видимой в истории одним куском, а не растворилась в линии.
|
||
|
||
**5. Конфликты разрешай только те, что понимаешь.** Почти всегда это строка статуса в
|
||
`docs/phases/README.md` — там обе стороны правы, надо оставить обе. Конфликт в коде, смысл
|
||
которого тебе неясен, — `git merge --abort`, снять замок, оставить ветку и написать в отчёте, с
|
||
чем именно она не сходится.
|
||
|
||
**6. Перепрогони тесты на `main` после слияния.** Две зелёные ветки в сумме бывают красными:
|
||
каждая правила своё, а вместе получилось не то. Это единственный способ поймать такое.
|
||
|
||
Красно — откати слияние и оставь ветку жить:
|
||
|
||
```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` изредка падает целиком** с нативной ошибкой в `Simulation.Tests`. Один раз
|
||
— перепрогони; повторяется — это находка для отчёта.
|
||
- **Dev-сервер не запускать.** Нужно посмотреть на UI — пользуйся уже запущенным приложением
|
||
пользователя.
|
||
|
||
## Измеряй, а не рассуждай
|
||
|
||
Когда вопрос звучит как «а хорошо ли ложатся часы» или «а сколько выходит неполных семей» — не
|
||
рассуждай о коде. Напиши временный тест, который печатает реальные числа, посмотри и удали его.
|
||
Час размышлений о поведении алгоритма стоит дороже и ошибается чаще, чем один прогон с цифрами.
|
||
Числа из такого замера — лучшее, что можно положить в отчёт.
|
||
|
||
## Отчёт
|
||
|
||
Коротко, в конце:
|
||
|
||
- какая фаза взята и на какой ветке лежит;
|
||
- что сделано — по пунктам задач, без пересказа кода;
|
||
- какие тесты добавлены, списком по одной строке;
|
||
- чем подтверждено: что прогнал и с каким результатом, числа замеров, если были;
|
||
- что осталось незакрытым и почему;
|
||
- что замечено за границами фазы;
|
||
- слита ли ветка в `main` и удалена ли — а если нет, то что помешало.
|
||
|
||
Итог запуска — фаза в `main`, ветка удалена, worktree убран, замок снят. Всё, что осталось от
|
||
работы, — это коммиты и отчёт.
|