Поддержание актуальности
Resync сравнивает старый граф вызовов с новым и перегенерирует только то, что действительно изменилось. Тронули три файла — платите за три файла.
handbook resync --case <case-dir> --work <workdir>Документация гниёт, потому что её обновление стоит столько же, сколько написание. Resync делает обновление пропорциональным изменению.
Контракт кейса
Кейс — это каталог, который вы собираете сами. Он отвечает на два вопроса: как код выглядит сейчас и каким должно было быть изменение.
cases/upload-retry/
edited/ the changed source tree REQUIRED
plan.md what the change was optional — SHARPENS the scope
change.diff unified diff vs the previous tree optional — WIDENS the scopemkdir -p cases/upload-retry
cp -R $REPO cases/upload-retry/edited
cp plan.md cases/upload-retry/
git -C $REPO diff HEAD~1 > cases/upload-retry/change.diff
handbook resync --case cases/upload-retry --work work/apiДекларации и диффы могут только расширять набор
Дифф графа — это нижняя граница: если байты файла изменились, он будет обновлён независимо от того, упоминал ли его план. План, занизивший собственный радиус поражения, не может привести к устаревшей странице.
Пустой change.diff означает «делать нечего», и запуск чисто пропускается, а не
трактуется как «изменилось всё».
Что он делает на самом деле
- Повторно анализирует изменённое дерево — свежий граф phase 1.
- Сравнивает старое с новым → изменённые / добавленные / удалённые файлы.
- Перегенерирует карточки для изменённых и добавленных файлов.
- Назначает добавленные файлы, убирает удалённые, согласует корзины.
- Перестраивает организацию затронутых этапов — детерминированно, без LLM.
- Заново повествует затронутые этапы и обзор системы. Благодаря кэшу по хешу содержимого незатронутый этап вообще не повествуется заново.
- Обновляет регистры.
- Обновляет уже отрендеренные результаты в
<work>/handbook(--no-render, чтобы пропустить).
{
"skipped": false,
"changedFiles": ["src/upload.py"],
"addedFiles": [],
"deletedFiles": [],
"affectedStages": ["stage-3"],
"cardsRegenerated": 1,
"narrated": true,
"rendered": ["work/api/handbook/overview.md", "work/api/handbook/stage-3.md"]
}Как дифф ловит изменения
| Сигнал | Обнаруживает |
|---|---|
| Хеш содержимого | Правку тела на месте, не трогающую номера строк и сигнатуры, — случай, который структурный дифф пропускает целиком |
| Набор функций | Добавленные, удалённые или переименованные функции |
| Сигнатуры и диапазоны строк | Перекроенные функции |
| Рёбра вызовов | Новые или исчезнувшие связи, в том числе входящие в незатронутые файлы и исходящие из них |
| Набор файлов | Добавленные и удалённые файлы |
Пофайловые хеши были проставлены на этапе phase 1 ровно для этой цели. Граф, созданный до их появления, откатывается к структуре — деградация, но никогда не ошибка.
Работа без эндпоинта
handbook resync --case cases/x --work work/api --no-llmСтруктурные факты обновляются — граф вызовов, перечень функций, назначение, порядок, — а
к назначению каждой затронутой карточки добавляется (stale: code changed since narration).
Это честная деградация. Альтернатива — оставить прозу нетронутой и непомеченной — это handbook, который тихо лжёт.
Возврат исправлений
handbook resync --case cases/x --work work/api \
--corrections skills/api/corrections.jsonlФайлы, названные в corrections.jsonl, попадают в набор обновления, даже если их байты
не менялись, потому что утверждение, которому противоречит исходник, — достаточный
повод заново описать файл. Использованный файл после этого архивируется с меткой времени,
поэтому одно и то же исправление не может примениться дважды.
Некорректные строки сообщаются в report.corrections.problems и никогда не фатальны —
одна плохая строка, записанная одним агентом, не должна блокировать обновление.
Детализация и язык остаются как были
--detail и --narrate-lang по умолчанию не заданы, а незаданность означает
«соответствовать тому, каким этот handbook уже является». Resync никогда молча не
понизит глубокий handbook до краткого и не переключит китайский handbook на английский.
Передавайте их явно только тогда, когда вы действительно хотите сменить глубину или язык, — и ожидайте смешанный handbook, пока каждая карточка не будет перегенерирована.
Когда вместо этого перегенерировать
Resync прокатывает вперёд производный слой. Перегенерируйте, когда должна измениться структура:
| Ситуация | Что делать |
|---|---|
| Изменилась пара файлов | resync |
| Рефакторинг переместил код между модулями | resync — дифф графа с этим справляется |
| Вы добавили целую новую подсистему | resync, затем проверьте, годится ли ещё скелет |
| Скелет больше не описывает систему | generate --phase 2b,2c,3 --synth-mode doctor |
| Вы сменили язык повествования или глубину | generate --phase 2a / --phase 3 --refresh |
| Половина репозитория переписана | generate с нуля — дешевле, чем огромный resync |
Автоматизация
on:
push:
branches: [main]
jobs:
resync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 2 }
- run: |
mkdir -p case
cp -R . case/edited
git diff HEAD~1 > case/change.diff
- run: handbook resync --case case --work work/api
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- run: handbook validate --skill skills/api --source .edited/ можно и вовсе пропустить, когда вы запускаете resync программно: опция
editedRoot указывает на живое дерево — именно так Studio выполняет его на месте, не
копируя репозиторий.
Безопасность
- Та же блокировка каталога, что и у
generate, поэтому resync никогда не переплетётся с параллельной генерацией на тех же артефактах. - Промежуточная область phase 1 всегда вычищается —
<case>/.resync-phase1не переживает вызов ни при успехе, ни при сбое. - Карточки удалённых файлов удаляются, поэтому удалённый файл не может задержаться в handbook.
- Отменяемо —
AbortSignalпроверяется между шагами и протягивается в каждый LLM-проход.
Применение и откат
Механический исполнитель с четырьмя правилами безопасности, резервная копия, способная доказать, что она восстанавливает, и парсер, отвергающий всё неоднозначное.
Studio — веб-интерфейс
Весь тулчейн во вкладке браузера, с живыми логами и откатом в один клик. Только localhost — намеренно.