Status: advisory. It states what happened and recommends; it decides nothing, amends no frozen section and changes no task state. Closes: TFW-60 Phase AA, AC-13 half two — acceptance evidence. The executor reported that half UNMET and handed it back; this is the artifact the TS named as its closing form. Payload taken at:
312dca9/d62fd26on thev2.0.0-dirty.2line — one commit before the tag's own record was written into the CHANGELOG. The delivered.tfw/was verified against the tag: 0 files missing, 0 stray framework files, the single difference being that one CHANGELOG commit.
Разбор полётов: обновление TFW 1.3.0 → 2.0.0-dirty.2¶
Проект:
innoforce-ai-first. Дата: 2026-08-28. Источник: локальное рабочее деревоD:/projects/research/steps-framework, тегv2.0.0-dirty.2. Исполнитель: сессия Claude Code от имениsaubakirov. Учёт доски —MIGRATION.md, дословный снимок — [tasks/BOARD-SNAPSHOT.md](../BOARD-SNAPSHOT.md).
Одной строкой¶
2.0.0-dirty.2 закрыл ту проблему, ради которой выпускался: порядок действий больше не надо восстанавливать. Стоимость обновления не исчезла — она переехала. Теперь она в слое адаптеров и в идентичности участников, и там её никто не измеряет.
Первый внешний потребитель 2.0.0-dirty отчитался: «копирование файлов заняло минуты,
остаток сессии ушёл на восстановление того, что делать и в каком порядке». У меня порядок
не занял ничего: update.md Шаг 3 отправил в migrations/2.0.0.md, там процедура из семи
шагов с явным «что будет, если перескочить». Ноль догадок. Зато два тупика ждали там, где
руководство не смотрит.
Что произошло, в числах¶
| Измерение | Значение |
|---|---|
| Файлов в нагрузке v1.3.0 | 60 |
| Из них реально кастомизировано в проекте | 2 (knowledge_state.yaml ⚫, project_config.yaml 🟡) |
| Объявленных ручных слияний по старому списку | 2 (conventions.md, glossary.md) |
| Фактических ручных слияний | 1 — только конфиг |
| Новых файлов нагрузки | 9 (scripts/ ×4, migrations/ ×1, шаблоны ×4) |
| Ретировано | 1 (templates/topic_file.md) |
| Строк доски → задач | 17 строк, 15 каталогов → 16 сущностей, 0 неучтённых |
Записано status.md |
11 (только нетерминальные) |
| Каталогов, не распознанных грамматикой | 0 |
UNDECLARED после миграции |
1 (INNO-4), разрешён владельцем |
| Файлов команд, переписанных с копий на тонкие адаптеры | 12 |
| Тесты нагрузки | 158 passed, 1 skipped |
Проверки --check index / tasks / project |
все зелёные |
Что сработало отлично¶
Шаг 3a — измерение вместо списка. Старый update.md давал список файлов, которые
могут отличаться. Новый велит продиффить каждый локальный файл .tfw/ против чистого
предыдущего тега. У conventions.md апстрим изменил 212 строк — и слияния не потребовалось
вообще, потому что локальная копия была байт-в-байт релизной. Список предсказывал два
слияния, измерение показало одно. Это не оптимизация — это разница между «прочитать 212
строк диффа» и «не открывать файл».
Манифест до записи. migrate_board.py --manifest поимённо перечислил все 17 строк доски
и куда каждая денется, не тронув ни байта. Минута чтения сняла все сомнения. Формулировка
«Unaccounted: 0» выведена подсчётом, а не заявлена — это ровно то, чего не хватало
предыдущему проходу, о чём манифест сам и пишет.
Отказ вместо молчаливого приёма. Лучшее в релизе. Я записал событие журнала с
actor: claude-code — очевидное имя. --check tasks его отверг: «семейство провайдера, а
не писатель — две сессии одного инструмента суть два актора». Инструмент не позволил
испортить запись. Это работающий гейт, а не декоративная валидация.
UNDECLARED как проектное решение. Доска несла 🟡 TS — статус вне словаря. Миграция
не угадала, написала UNDECLARED и сохранила исходник в lifecycle_verbatim. Разрешение
владельцем — два поля и одно событие transition с from: UNDECLARED. Граница между «у
инструмента нет оснований для догадки» и «у человека есть» проведена по живому и держится.
Порядок как жёсткое ограничение. «Мигрируй → сгенерируй → сними доску», и у каждого шага написано, что́ сломается при перескоке. Пункт «сделать шаг 5 до шага 2 — единственная непоправимая ошибка в процедуре» стоит всех остальных абзацев вместе взятых.
Пять мест, где было непонятно или больно¶
1. У правила об акторе нет соглашения об именовании¶
--check tasks справедливо отвергает claude-code. Но нигде в нагрузке не сказано, как
выглядит допустимый хэндл агента: ни примера, ни правила чеканки, ни ответа на вопрос, что
делает team/ при требовании «каждая сессия — новое имя». Список PROVIDER_FAMILIES живёт
в коде gen_index.py; шаблон профиля показывает только handle: handle.
Я пришёл к claude-20260828a перебором: написал очевидное → отказ → прочитал исходник
валидатора → изобрёл суффикс сессии. Это ровно тот класс дефекта, который релиз объявил
своей темой: инструкция, называющая то, чего у читателя нет.
Рекомендация. Положить в
templates/team/profile.mdвторой, агентский пример с посессионным хэндлом и одной строкой правила чеканки. Назвать вconventions.md§4, чтоteam/растёт по сессиям и это нормально, — либо назвать противоположное.
2. ~/.tfw/bindings.yaml упомянут в семи воркфлоу и не описан нигде¶
plan.md, handoff.md, review.md, resume.md, release.md, research/base.md,
init.md — все велят при нескольких профилях «прочитать привязку на этой машине». Формата
этого файла нет ни в conventions.md, ни в шаблонах, ни в глоссарии: глоссарий сообщает
только, что привязка живёт вне дерева проекта. Агенту, которому велено её прочитать, нечего
разбирать; агенту, который захотел бы её создать, нечего писать.
Хуже другое: update.md Шаг 3b велит создать team/ вместе с первым профилем — и
останавливается. Профиль там оказывается один, привязка не нужна. Но как только в
проекте появляется агент (а он появляется на первом же событии журнала), профилей
становится два, и каждая следующая сессия навсегда уходит в ветку «привязки нет → задай
один вопрос».
Рекомендация. Отгрузить
templates/bindings.yamlсо схемой и велетьupdate.mdШагу 3b /init.mdзаписать её сразу после второго профиля. Иначе описанный механизм разрешения не исполняется никогда — а записанный урок сам себя не исполняет.
3. conventions.md ссылается на шаблон, который этот же релиз ретировал¶
.tfw/conventions.md в разделе об именовании: «topic_file.md (не TOPIC_FILE.md)».
Файла templates/topic_file.md в нагрузке 2.0.0-dirty.2 нет — он переехал в
templates/knowledge/topic.md. Релиз при этом заявляет: «тест падает, если любой путь в
любом источнике адаптера или установленной копии не разрешается». Значит, тест покрывает
адаптеры и не покрывает прозу самой нагрузки.
Рекомендация. Расширить проверку разрешимости путей на каждый
.mdв.tfw/, а не только на источники адаптеров. Дефект одноклассовый с уже пойманным/tfw-research → research.md; его не поймали, потому что смотрели в одну сторону.
4. tfw.upstream не дотягивается до релиза, который только что установлен¶
Тег v2.0.0-dirty.2 — локальный и не запушен, а tfw.upstream в конфиге — адрес GitHub, где
его нет. Следующий /tfw-update склонирует GitHub, найдёт старую нагрузку и сообщит, что
всё в порядке. Шаг 0 теперь принимает локальное дерево как источник — но нигде не
записывается, каким источником обновление воспользовалось фактически. Я оставил
каноничный URL и приписал комментарий руками; это соглашение одного проекта, а не механизм.
Рекомендация. Шагу 7 писать рядом с
tfw.versionразрешённый источник и тег:installed_from: <источник>@<тег>. Один ключ закрывает вопрос «откуда это на самом деле приехало» для всех последующих обновлений.
5. Главное: слой адаптеров — настоящая цена мажорного обновления, и update.md её не считает¶
Шаг 3a измеряет .tfw/ безупречно — и молчит обо всём, что вне .tfw/. В этом проекте
двенадцать файлов .claude/commands/tfw-*.md были полными копиями тел воркфлоу v1.3.0.
После копирования нагрузки они остались на месте и продолжили инструктировать агентов
обновлять Доску задач, которой больше нет, работать по старой грамматике идентификаторов и
создавать задачи в контейнере, о котором ничего не знают.
Ни одна проверка релиза этого не заметила. Я нашёл это единственным grep 'Task Board' по
дереву адаптеров: восемнадцать файлов в трёх адаптерах несли ретированный словарь. Причём
.tfw/adapters/claude-code/README.md сам же и формулирует принцип, который проект нарушал:
«Commands never duplicate workflow content — they reference it».
Это не частный случай. Любой проект, который держит адаптеры копиями (а копии — прямая и соблазнительная реализация «синхронизации адаптеров»), после мажорного обновления получает две противоречащие инструкции и ни одного сигнала об этом.
Рекомендация, самая важная из пяти. Дать Шагу 6 своё измерение по образцу Шага 3a: продиффить установленные копии адаптеров против источников адаптеров предыдущего тега и прогнать по всей поверхности адаптеров тот самый реестр ретированных формулировок, который релиз уже проверяет на файлах нагрузки. Проверка занимает секунды и ловит единственный класс дефекта, переживший это обновление.
Что изменено в проекте сверх процедуры¶
Названо отдельно, потому что это решения, а не механика:
task_containers: [workspace, tasks]— решение владельца. Новые задачи вworkspace/{ГГГГ}/, корпус до 2.0.0 остаётся вtasks/, все старые пути разрешаются.- 12 команд
.claude/commands/tfw-*.mdпереписаны с копий на тонкие адаптеры. Копии только что доказали, что протухают молча; апстрим прямо запрещает дублирование.tfw-taskсохранил свой контракт (два шага и жёсткий стоп) — это композиция проекта, а не канон. build.*перестали быть заглушками. Нагрузка привезла инструменты, поэтомуlint/test— pytest по.tfw/scripts/,verify—gen_index.py --check tasks. До 2.0.0 все три былиecho, потому что запускать было нечего.CLAUDE.mdприведён в соответствие: версия каркаса, состояние задач без доски, тонкие адаптеры, реальные команды сборки,workspace/00-INDEX.mdв порядке чтения.- INNO-4:
UNDECLARED→TS_DRAFTс событием журнала. Доска несла🟡 TS; эмодзи однозначно отображается вTS_DRAFTпо словарю статусов самого проекта. README.md: доска снята, на её месте маршрут; попутно исправлена мёртвая ссылка.tfw/init.md→.tfw/workflows/init.md(существовала до обновления).
Что осталось открытым¶
TFW-90__knowledge_platform_presentations — в разделе «Нераспознанные входы» индекса.
Строка доски была простым текстом без ссылки, поэтому строгий разбор не связал её с
каталогом; status.md не записан. Доска говорила ✅ DONE. Это ровно та ситуация, где
инструмент обязан сообщить и не описывать: закрыть её должен человек — либо записав
status.md с терминальным исходом, либо оставив как есть. Оставлено видимым намеренно.
Рекомендации другим пользователям TFW — по порядку исполнения¶
- Прогоните Шаг 3a раньше, чем поверите любому списку слияний. Скорее всего у вас ноль ручных слияний вместо объявленных трёх. Одна команда экономит час чтения диффов.
- Сразу после Шага 3a прогрепайте слой адаптеров на ретированный словарь. Для 2.0.0
это
grep -rl 'Task Board' .claude .agent .agents AGENTS.md CLAUDE.md. Процедура этого не велит, а именно там переживают обновление противоречащие инструкции. - Если ваши адаптеры — копии тел воркфлоу, переведите их в тонкие в это же обновление. Не в следующее. Вы держите в руках доказательство, что копии протухают молча.
- Читайте манифест целиком, а не итоговую строку. Он поимённо называет каждую строку доски. Единственная непоправимая ошибка процедуры — снять доску до манифеста.
- Заведите агентский профиль с посессионным хэндлом до первого события журнала.
Не
claude, неclaude-code, неcodex— их валидатор отвергает. Что-нибудь вродеclaude-20260828a. - Перечитайте
build.*руками. Обновление их сохраняет, а сохранено — не значит верно: после релиза, переместившего инструмент, ваша команда молча указывает в пустоту. - Запишите, откуда вы обновились. Если тег локальный и не запушен,
tfw.upstreamдо него не дотянется, и следующее обновление тихо не найдёт ничего нового. UNDECLARED— не место для стоянки. Разрешайте его владельцем и событием, а не правкой поля. Правка поля стирает единственную улику о том, что источник нёс.
Разбор полётов обновления. Написан по следам одного реального обновления одного реального проекта; каждое число выше получено измерением, а не оценкой.