Files
h-school/.claude/skills/phase-work/SKILL.md
T
Leonid Pershin e299995d98
ci / server (push) Failing after 3m43s
ci / client (push) Failing after 14s
add skils
2026-08-20 11:33:39 +03:00

22 KiB
Raw Blame History

name, description
name description
phase-work Берёт ровно одну фазу из среза в 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. Читаешь docs/phases/README.md: находишь первый срез, где есть .
  2. Внутри среза берёшь первую , у которой закрыты зависимости — они перечислены в шапке фазы.
  3. Проверяешь, что её никто не взял: git branch --list "phase/<номер>-*" пусто.

Статус в README.md — первый источник правды, ветка — только арбитр гонки. 🔄 значит занято, значит сделано, значит свободно. Ветка может существовать и при — её просто не убрали после слияния; это не заявка, а мусор, см. «Кто убирает ветку».

Фаза 🔄 — чужая работа в процессе. Не трогай её, даже если кажется, что там застряли — кроме случая выше: номер передали и ветка уже есть, тебя посадили продолжить.

Как застолбить

Продолжение (номер дали и ветка phase/<N>-* уже есть) — этот блок пропусти: ветку не создавай, 🔄 не сбрасывай, садись в существующий worktree. «already exists» здесь не гонка.

Ветка — и есть заявка, потому что git не даст создать её дважды. Указывай main, не текущий HEAD: IDE пользователя часто стоит на bug/…, и ветка от HEAD утащит фазу на чужую историю.

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:
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/… пользователя.

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. Пока он держит ветку, слияние и удаление невозможны.

git worktree remove --force <путь-в-scratchpad>/wt-phase-25

2. Возьми замок слияния — той же уловкой, что и заявку на фазу:

git branch merge/lock

Упало «already exists» — сливает другой агент (фаза, баг, ревью или side: замок общий). Подожди и повтори; не лезь в дерево и не снимай чужой замок. Замок снимается git branch -D merge/lock на всех путях, включая неудачные: забытый замок остановит остальных.

3. Проверь основное дерево, прежде чем трогать. Оно должно уже быть на main и чистым:

git -C <корень> branch --show-current
git -C <корень> status --porcelain

Не main (часто bug/… в IDE пользователя) — не checkout, ничего не сливай, сними свой замок, отчёт. Непусто — там человек или сосед. То же: не сливать, снять замок, отчёт. Чужие незакоммиченные правки и чужая ветка в основном дереве дороже твоей. Stash→merge — не ты: это merge-агент /phase-orch, и только если пользователь сказал «сливай».

4. Слей явным коммитом слияния:

git -C <корень> merge --no-ff phase/25-golden-fixtures

--no-ff — чтобы фаза осталась видимой в истории одним куском, а не растворилась в линии.

5. Конфликты разрешай только те, что понимаешь. Почти всегда это строка статуса в docs/phases/README.md — там обе стороны правы, надо оставить обе. Конфликт в коде, смысл которого тебе неясен, — git merge --abort, снять замок, оставить ветку и написать в отчёте, с чем именно она не сходится.

6. После слияния. Конфликт только в строке статуса или в доке — тесты не гонять, ничего не сошлось в коде. Был конфликт в коде — тот же узкий набор проектов, что в шаге 5, не весь solution. Две зелёные ветки в сумме бывают красными; ловить это должен затронутый проект, а не ритуал на восемь сборок.

Красно — откати слияние и оставь ветку жить:

git -C <корень> reset --hard ORIG_HEAD

7. Зелено — удали ветку и сними замок:

git -C <корень> branch -d phase/25-golden-fixtures
git branch -D merge/lock

-d, а не -D: git откажется удалять неслитое, и это последняя защита от потери работы.

Не пушь. Удалёнка есть, но отправка — решение пользователя; делай только если попросили прямо.

Осиротевшее. Ветку когда-то забыли удалить — уберёт следующий запуск, но только при двух условиях сразу: фаза стоит в README.md и ветка слита в main.

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 убран, замок снят. Всё, что осталось от работы, — это коммиты и отчёт.