Skip to content

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/d62fd26 on the v2.0.0-dirty.2 line — 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/, verifygen_index.py --check tasks. До 2.0.0 все три были echo, потому что запускать было нечего.
  • CLAUDE.md приведён в соответствие: версия каркаса, состояние задач без доски, тонкие адаптеры, реальные команды сборки, workspace/00-INDEX.md в порядке чтения.
  • INNO-4: UNDECLAREDTS_DRAFT с событием журнала. Доска несла 🟡 TS; эмодзи однозначно отображается в TS_DRAFT по словарю статусов самого проекта.
  • README.md: доска снята, на её месте маршрут; попутно исправлена мёртвая ссылка .tfw/init.md.tfw/workflows/init.md (существовала до обновления).

Что осталось открытым

TFW-90__knowledge_platform_presentations — в разделе «Нераспознанные входы» индекса. Строка доски была простым текстом без ссылки, поэтому строгий разбор не связал её с каталогом; status.md не записан. Доска говорила ✅ DONE. Это ровно та ситуация, где инструмент обязан сообщить и не описывать: закрыть её должен человек — либо записав status.md с терминальным исходом, либо оставив как есть. Оставлено видимым намеренно.

Рекомендации другим пользователям TFW — по порядку исполнения

  1. Прогоните Шаг 3a раньше, чем поверите любому списку слияний. Скорее всего у вас ноль ручных слияний вместо объявленных трёх. Одна команда экономит час чтения диффов.
  2. Сразу после Шага 3a прогрепайте слой адаптеров на ретированный словарь. Для 2.0.0 это grep -rl 'Task Board' .claude .agent .agents AGENTS.md CLAUDE.md. Процедура этого не велит, а именно там переживают обновление противоречащие инструкции.
  3. Если ваши адаптеры — копии тел воркфлоу, переведите их в тонкие в это же обновление. Не в следующее. Вы держите в руках доказательство, что копии протухают молча.
  4. Читайте манифест целиком, а не итоговую строку. Он поимённо называет каждую строку доски. Единственная непоправимая ошибка процедуры — снять доску до манифеста.
  5. Заведите агентский профиль с посессионным хэндлом до первого события журнала. Не claude, не claude-code, не codex — их валидатор отвергает. Что-нибудь вроде claude-20260828a.
  6. Перечитайте build.* руками. Обновление их сохраняет, а сохранено — не значит верно: после релиза, переместившего инструмент, ваша команда молча указывает в пустоту.
  7. Запишите, откуда вы обновились. Если тег локальный и не запушен, tfw.upstream до него не дотянется, и следующее обновление тихо не найдёт ничего нового.
  8. UNDECLARED — не место для стоянки. Разрешайте его владельцем и событием, а не правкой поля. Правка поля стирает единственную улику о том, что источник нёс.

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