Skip to content

Status: advisory. It states what happened and recommends; it decides nothing, amends no frozen section and changes no task state. Relates to: TFW-60 Phase AB (the .4 correction of the migration path) and the still-open half of AC-13 — a first migration by an operator who did not write the code, on the corrected tag. Payload taken at: tag v2.0.0-dirty.4 = 51677ff0 on D:/projects/research/steps-framework. Tag verified against HEAD before VERSION was trusted, re-verified after the copy; source .tfw/ clean at both checks. installed_from recorded. Committed: 018a194 in the receiving project — 113 files, +8713/−2339. Board accounting: tasks/MIGRATION-2.0.0.md; snapshot: tasks/BOARD-SNAPSHOT.md; the update's own record with the pin and the per-file checklist: tasks/UPDATE-2.0.0-dirty.4.md.


Разбор полётов: обновление TFW 1.0.0 → 2.0.0-dirty.4

Проект: kaznpu-ai-lab — нормативная база и организационное строительство лаборатории ИИ. Не код: документы, положения, штатные расписания. Дата: 2026-08-30. Источник: локальный чекаут D:/projects/research/steps-framework, тег v2.0.0-dirty.4 = HEAD. Исполнитель: сессия Claude Code от имени saubakirov, автономно — владелец вернулся после завершения. Стартовое состояние: HEAD проекта на 0.8.7, рабочее дерево — на незакоммиченном обновлении до 1.0.0; каждый файл .tfw/ побайтно равен тегу v1.0.0.

Одной строкой

Процедура .4 исполнилась чисто и честно от пина до --check project — и при этом главная задача проекта осталась без состояния, а владелец без объяснения. migrate_board.py прочитал строку AILAB-2 — «✅ DONE (A/V/B/C) · 🔄 Phase D …» — по первому токену, объявил задачу терминальной и ничего не написал; манифест сошёлся до нуля, потому что отсутствие файла не ошибка учёта. Пять status.md (задача + четыре фазы) написаны руками. А единственный человек в проекте, вернувшись, узнал, что его не спросили ни кто он, ни где хранить задачи, и никто не сказал ему, что стало лучше.

Всё остальное сработало так, как обещали четыре предыдущих отчёта — и это тоже результат.

Что ожидал владелец

Цитата полностью, по просьбе владельца; орфография выправлена, слова его:

«Я ожидал, что ты меня заонбордишь нормально, запустишь процесс, спросишь кто, где я хочу хранить задачи. Объяснишь, что поменялось в проекте после апдейта, причём я хочу, чтобы это преподносилось положительно: что появились новые возможности, исправлена проблема, стало лучше и круче и т.д. — потому что я знаю, люди не любят изменения.»

— Санжар Аубакиров, владелец проекта, 2026-08-30

Три ожидания — вопросы, объяснение, позитивная подача — и ни одно не описано в update.md. Шаг 3 говорит «choose tfw.task_containers deliberately» и «create team/{handle}.md», но в отличие от plan.md, handoff.md и review.md у update.md нет ни одного 🛑 гейта. В автономном режиме «deliberately» означает «агент решил сам». Он решил [tasks], и владелец через час поменял на [workspace, tasks].

Что произошло, в числах

Измерение Значение
Файлов .tfw/ в v1.0.0 → в v2.0.0-dirty.4 63 → 69 (изменено 62, ретировано 4, добавлено 10)
Строк CHANGELOG между версиями (1.1.0 … .4) 573
Локальных файлов .tfw/, отличных от v1.0.0 2 из 63 — project_config.yaml 🟡, knowledge_state.yaml
Ручных слияний 1 — конфиг
Адаптерных копий .claude/commands/, отставших от собственных workflow до обновления 4 из 11 (handoff, init, knowledge, update)
Адаптерных копий ре-синхронизировано 22 (.claude/commands ×11, .agents/skills ×11) + блок AGENTS.md
Строк доски → каталогов 4 → 4; все три гарантии манифеста HELD; unresolved 0; malformed 0
status.md записано инструментом / руками 2 / 5
Задач, классифицированных терминальными 2 — одна верно (AILAB-1), одна неверно (AILAB-2, multi-phase, Phase D в работе)
Фазовых каталогов без status.md после --apply — и что сказал --check tasks 4 — «4 tasks validate against the closed schema»
Хиты ретированной лексики вне allowlist 0 (те же 6 инструкций ретирования + скрипты и тесты, что у отчётов 3 и 4)
Тесты payload до / после миграции 177 passed, 3 deselected / 179 passed, 1 skipped
Смена контейнера постфактум ([tasks][workspace, tasks]) 1 строка конфига + gen_index.py; индекс переехал; старые пути резолвятся; 3 файла README-маршрутов переписаны руками

Что сработало отлично

Пин источника прошёл с первого раза — впервые за четыре отчёта. Тег существовал, указывал на HEAD, .tfw/ источника был чист (грязными были только tasks/, что процедура прямо называет неважным). Повторная проверка после копирования — та же. Команды из Step 0 исполнились буквально, потому что источник стоял на релизе; замечание четвёртого отчёта (§6) о живом дереве — верно, здесь просто не сработало условие.

Классификация против установленного бейзлайна обнулила слияния. installed_from не было (конфиг 1.0.0), fallback — тег v1.0.0 источника — дал 61 из 63 файлов SAME_AS_1.0.0. Всё, что отличалось от цели, — дрейф происхождения; перезапись без чтения диффов. Третий отчёт получил здесь 10 ложных срабатываний из-за чужой линии установки; у проекта, установленного из этого репо, метод работает ровно как задуман.

Манифест честен, и его честность читается. Строка AILAB-2 → snapshot + task directory + index (unresolved or closed) — правда: в индексе она оказалась бы под «Closed». Дефект §1 не в том, что манифест соврал, а в том, что он не подсветил редкий случай. Важная разница с третьим отчётом, где инструмент печатал «Unaccounted: 0» над двойником.

Каждый --check говорит, чего он не проверил. --check project до миграции ответил «consistent» — и тут же перечислил: index freshness, task state detail, adapter copies, Git state. Это позволило доверять зелёному, а не гадать, что за ним. Пользовательская ценность этой строки выше, чем кажется: она превращает «инструмент сказал ОК» в «инструмент сказал ОК про вот это».

ASCII в runtime-сообщениях окупился на первом же Windows. Консоль cp1252: 177 тестов и оба скрипта отработали без единого UnicodeEncodeError. Мой собственный однострочник на Python, печатавший кириллицу, упал именно на этом. Правило 2.0.0-dirty.2 проверено на операторе.

Байт-копии адаптеров поймали то, что не поймал бы никто. Четыре команды .claude/commands/ отставали от собственных workflow 1.0.0 — проект жил на инструкциях, противоречащих своему же payload, и не знал об этом. Step 6 закрыл это одним циклом cp.

Миграция аддитивна, и это снимает страх. Ни один существующий файл не открыт на запись; откат — удалить новые файлы. Оператор, знающий это, действует быстрее и не делает бэкапов доски.

Смена контейнера постфактум — дёшево. Владелец захотел workspace/ после того, как всё было сделано. Одна строка конфига, gen_index.py переписал индекс в первый контейнер, --check project подтвердил creates in 'workspace', resolves across ['workspace', 'tasks']. Модель «один список, создаём в первом, ищем во всех» на практике ведёт себя ровно так, как написана.

Дефекты — по тяжести для пользователя

1. migrate_board.py читает первый статус-токен и молча пропускает живую multi-phase задачу

Строка доски AILAB-2:

| [AILAB-2](tasks/AILAB-2__regulatory_and_org_form/) | … | ✅ DONE (A/V/B/C) · 🔄 Phase D (досборка) — … R8 🟢 RF … R9 ⬜ | 2026-08-20 |

Владелец писал её как человек: закрытые фазы, потом живая. Парсер взял ✅ DONE, признал задачу терминальной и по правилу «state only for non-terminal tasks» не написал ничего. Никакой ошибки, никакого предупреждения; в манифесте — index (unresolved or closed), что формально верно. Индекс показал бы главную задачу проекта под «Closed — 2».

Это зеркальное отражение дефекта третьего отчёта: там инструмент написал состояние туда, куда не должен был; здесь не написал туда, куда должен. Исправление .4 («whole-or-refuse» для идентификаторов) на ячейку статуса не распространилось: статус разбирается «first-match», а не «whole-or-refuse».

Второй слой того же дефекта: миграция не пишет status.md фазам и migrations/2.0.0.md не говорит, что это делает человек. Конвенции §5: «A task with phase directories carries one status.md inside each phase directory». После --apply четыре каталога phase-* стояли без состояния, а --check tasks отвечал «4 tasks validate» — он валидирует то, что есть, и не знает, что чего-то нет.

Что сделано: пять файлов написаны руками из строки доски и HL фаз — задача PHASES, V/B/C DONE с outcome, D RF; в каждом — комментарий-провенанс, почему инструмент промолчал. Фиксированное предложение для фазового status.md («…this phase's live state. The task-level status.md never summarizes it») найдено не в шаблоне, а в TFW-60/phase-a/status.md самого фреймворка — шаблон templates/status.md несёт только задачный вариант.

Рекомендация. (a) Ячейка статуса разбирается whole-or-refuse: больше одного токена жизненного цикла → UNDECLARED с lifecycle_verbatim, отдельный подраздел манифеста «Rows carrying more than one lifecycle token» — никогда не терминальная по первому. (b) Для каталога с phase-*-подкаталогами манифест перечисляет фазы и прямо пишет: «phase state is not written by migration; author {phase}/status.md by hand». (c) --check tasks сообщает фазовые каталоги без status.md — хотя бы строкой «N phase directories carry no state file». (d) templates/status.md получает второй фиксированный абзац — для фазы.

2. У update.md нет ни одного 🛑 гейта, и нет шага «что изменилось для вас»

Это и есть жалоба владельца. Каждый lifecycle-workflow TFW останавливается и спрашивает; update.md — единственный, который принимает три решения за владельца и не сообщает результата на его языке:

Решение Кто должен Кто принял
tfw.task_containers владелец агент ([tasks]; владелец потом изменил)
team/{handle}.md — кто это владелец агент (угадал верно по git-автору и профилю апстрима — то есть вывел из имени аккаунта, что конвенции §4 прямо запрещают)
build.verify — заменить заглушку на реальную команду владелец агент

И после: 573 строки CHANGELOG прочитаны агентом, владельцу пересказаны в терминах процедуры («дрейф происхождения», «allowlist») — не в терминах пользы. Человек, который не любит изменений, получил список изменений.

Рекомендация. (a) Step 3 → явный 🛑 WAIT с тремя вопросами: кто вы (handle, имя); где создавать новые задачи; подтвердите build.*. В AG-режиме — те же вопросы одним сообщением до первой долговременной записи; ответы — в чеклист. (b) Новый Step 8a «Briefing»: агент пишет владельцу на content_language три блока — что теперь можно, что перестало ломаться, что вам больше не нужно делать — из CHANGELOG-разделов Added / Fixed / Removed соответственно. Шаблон в templates/, потому что «преподносить позитивно» — это не тон, это структура: возможности → исправления → снятые обязанности. (c) Про handle: шаг должен спросить, а не позволить агенту вывести его из git config user.name. Я нарушил §4 и угадал; следующий угадает неверно.

Образец такого брифинга для этого проекта — в приложении к отчёту.

3. Обновлением управляет старый update.md — тот, который надо заменить

/tfw-update загружает .tfw/workflows/update.md проекта, версии 1.0.0: «clone tfw.upstream», без пина, без installed_from, без маршрута в migrations/, с командой rm -rf .tfw/.upstream && git clone. Владелец передал локальный путь — старый workflow его не предусматривает. Агент заметил расхождение, прочитал update.md цели и пошёл по нему. Агент, который этого не сделает, выполнит мажорное обновление по минорной процедуре: перезапишет payload, не тронет доску, не создаст team/, и --check project скажет ему правду слишком поздно.

Это бутстрап-парадокс любого self-updating процесса, и у него есть стандартное решение.

Рекомендация. Первая строка каждого update.md, начиная с ближайшего патча: «Step −1. Прочитай {source}/.tfw/workflows/update.md цели и дальше следуй ему, не этому файлу.» Одно предложение, ретроактивно неисполнимое для уже установленных 1.0.0 — поэтому его же стоит вынести в CHANGELOG-раздел «Updating from 1.x» первым пунктом. Плюс: Step 0 должен принимать локальный путь как первоклассный источник (в .4 принимает — в 1.0.0 нет).

4. Копирование payload перезаписывает project_config.yaml

Step 5 говорит «apply the checklist», Step 3 — «project_config.yaml is part project and part framework». Между ними нет механики. cp -r .upstream/.tfw/. .tfw/ — самое естественное действие — накрыло конфиг проекта конфигом репозитория фреймворка (name: my-project, task_prefix: TFW, installed_from: "self"). Агент сделал бэкап заранее и восстановил из него. Агент без бэкапа восстанавливал бы AILAB, ru и scope budgets по памяти.

knowledge_state.yaml — та же история: в payload лежит состояние фреймворка (его last_consolidation_seq), и оно затирает состояние проекта.

Рекомендация. Step 5 называет список исключений при копировании явно: project_config.yaml, knowledge_state.yaml — «never copied; merged by hand / never touched». Лучше — payload не содержит knowledge_state.yaml вовсе (это ⚫ по собственной классификации шага 3), а project_config.yaml источника при архивации переименовывается в project_config.upstream.yaml. Ещё лучше — .tfw/scripts/apply_payload.py, который делает копирование с исключениями и печатает, что пропустил.

5. «Commit whatever board changes are in flight» — а агент коммитить не вправе

migrations/2.0.0.md: доска читается из HEAD, изменения в работе — закоммитить. Строка AILAB-2 в рабочем дереве была на пять дней новее HEAD-а (R4–R8), владелец отсутствовал, а коммит без разрешения — вне полномочий агента. Выход — --working-tree, «deliberate and logged». Он есть, он записался в лог, и это правильно спроектированный предохранитель. Но процедура не говорит, когда его выбирать, и оператор без опыта прочтёт «commit first» как блокер.

Рекомендация. В гид одно предложение: «Если доска в рабочем дереве новее HEAD и коммит не в ваших полномочиях — --working-tree; это ровно тот случай, для которого флаг существует. Запишите выбор в чеклист».

6. Мелочи, которые стоили по несколько минут каждая

  • Где лежит манифест. Гид: --manifest MIGRATION.md (корень). Третий отчёт положил в корень, этот — в tasks/ рядом со снимком. Двух получателей — два места. Назвать одно.
  • Маршрут в README не шаблонизирован. «Add a permanent route» — и каждый проект пишет свой абзац на своём языке. При смене контейнера постфактум его пришлось переписывать руками в трёх местах. Шаблон templates/readme_route.md с {container} решает оба раза.
  • Смена первого контейнера оставляет старый индекс. gen_index.py написал workspace/00-INDEX.md, tasks/00-INDEX.md остался на диске как устаревшая копия. Удалён руками. Проверено ли, что --check index его заметил бы, — нет: удалил до проверки. Пусть генератор печатает «stale index at {old}: delete it» при виде 00-INDEX.md в непервом контейнере.
  • Роль участника некуда записать. Схема профиля закрыта четырьмя ключами; владелец хочет видеть «руководитель лаборатории, автор методики». Записано в team/README.md (парсер его пропускает) — конвенция, которой нет в шаблоне. Либо опциональный role, либо строка в шаблоне: «роль и контекст — в team/README.md».
  • since без семантики. Дата чего — прихода в проект, начала работы по TFW, создания профиля? Записан день первого коммита проекта. Одно слово в таблице ключей шаблона.
  • created с секундами из дневной колонки. AILAB-3 получил created: 20260625-151231 — доска несла только дату. Источник, видимо, Git-история каталога; это лучше, чем 000000, но комментарий-провенанс в файле про это молчит, а шаблон обещает «declared zero time». Написать в комментарии, откуда взята секунда.
  • --check project до миграции — зелёный. Доска в README ещё стояла, status.md не было — «consistent with the release it declares». Честно (перечислил, чего не смотрел), но человек, прочитавший только первую строку, решит, что миграция не нужна. Заголовок ## Task Board в README чекер найти умеет — одной строки «board still present at README.md: migration pending» хватит.

Ошибки оператора — как сигнал об UX

  1. cd .tfw в одной команде оболочки пережил до следующих — три проверки отработали в неверном каталоге и «не нашли файлов». Ошибка агента, не фреймворка; но она стала возможна потому, что гид миграции сам предлагает cd .tfw && find …. Все команды в гиде — от корня.
  2. cp -r без исключений (§4). Спасён бэкапом, сделанным «на всякий случай», — не по инструкции.
  3. Handle выведен из имени Git-аккаунта (§2) — прямое нарушение §4 конвенций, совершённое потому, что альтернативой был останов без владельца. Результат верный; метод — нет.
  4. Решения за владельца (§2). Самая дорогая ошибка сессии — не по данным, по доверию.

Что изменено в проекте сверх процедуры

  • 5 × status.md руками (AILAB-2 + фазы V/B/C/D) — §1.
  • tasks/UPDATE-2.0.0-dirty.4.md — запись обновления: пин, бейзлайн, чеклист по файлам, верификация, решения владельца постфактум. Такого артефакта процедура не называет; он оказался единственным местом, куда легли source_head и путь источника, как требует Step 0.
  • tasks/README.md и team/README.md на русском — объяснение двух контейнеров и роль участника.
  • build.verify: заглушка echopython .tfw/scripts/gen_index.py --check tasks.
  • Маршрут в README на русском; футер версии v0.8.7v2.0.0-dirty.4 (стоял устаревшим с незакоммиченного обновления 1.0.0).

Открыто — владельцу фреймворка

  1. §1 — whole-or-refuse для ячейки статуса и явная инструкция про фазы. Это второй подряд первичный дефект миграции в парсере доски; оба — «нашёл правдоподобное вместо доказанного целого», один в идентификаторе, другой в статусе.
  2. §2 — 🛑 гейт и Briefing в update.md. Единственная категория дефектов, которую видит владелец, а не оператор.
  3. §3 — Step −1 в update.md.

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

  1. Откройте update.md из источника, не из своего .tfw/. Ваш — тот, который устарел.
  2. Прочитайте манифест построчно и найдите каждую multi-phase задачу. Если она начинается с ✅ DONE (…), инструмент её закрыл. Пишите status.md задаче и каждой фазе сами; фазовый абзац берите из TFW-60/phase-a/status.md, не из шаблона.
  3. Бэкап project_config.yaml и knowledge_state.yaml до копирования payload. Копируйте, восстанавливайте, сливайте конфиг руками.
  4. Три вопроса владельцу до первой записи: кто вы, где новые задачи, каков build.*. Если владельца нет — задайте их первым сообщением после и будьте готовы переделать маршруты.
  5. Закончите брифингом на языке владельца: что можно / что починено / что больше не нужно. Это не любезность — это то, что определяет, примут ли изменение.
  6. Доска новее HEAD, коммитить не вправе — --working-tree, и запишите это в чеклист.

Приложение — брифинг для владельца kaznpu-ai-lab

Образец к §2: как обновление должно было представиться человеку. Три блока, из Added / Fixed / Removed CHANGELOG, на языке проекта.

Что теперь можно - Вести две задачи параллельно, не сталкиваясь в README: у каждой задачи — свой status.md, у каждой фазы AILAB-2 — свой. Индекс workspace/00-INDEX.md собирается одной командой. - Закрыть неудачную задачу честно — статусом ❌ REJECTED, не удаляя папку и не притворяясь DONE. - Записывать, почему задача сменила состояние, — journal/, один файл на событие, от вашего имени (on_behalf_of: saubakirov), с указанием, какой инструмент это сделал. - Утверждённый HL — контракт: §1/3/4/5/6/7 замораживаются, изменения только через журнал поправок с вашим вердиктом. Ревьюер теперь проверяет не «сделано ли по TS», а «то ли это, что мы хотели» — против HL и North Star проекта. - Создавать задачи без счётчика и без коллизий: AILAB_20260830-143000_ABBR, аббревиатуру утверждаете вы при планировании.

Что перестало ломаться - Команды /tfw-* больше не расходятся с workflow: 4 из 11 отставали — теперь байт-в-байт и проверяются тестом. - Следующее обновление будет знать, откуда вы установились (installed_from), и не сообщит «всё в порядке», найдя в GitHub старый payload. - build.verify — реальная проверка состояния задач, не echo. - Ревью без «режимов»: один чеклист из 10 строк, три из них — про достаточность доказательств, самую частую причину провала ревью (16 % по 637 строкам).

Что вам больше не нужно делать - Править Task Board в README при каждом шаге задачи — доски нет; её последний вид сохранён в tasks/BOARD-SNAPSHOT.md. - Выбирать «режим ревью» и подтверждать его — шаг удалён. - Хранить initial_seq и следить за нумерацией — идентификатор берётся из часов. - Что-либо переименовывать: AILAB-1 … AILAB-4 остаются где были и как назывались, навсегда.


Пятый полевой отчёт TFW-60. Первая первичная миграция на v2.0.0-dirty.4 — тег, вырезанный, чтобы проверить исправленный путь миграции; путь исправлен для идентификаторов и не исправлен для статусов. Каждое число получено измерением в репозитории получателя; цитата владельца приведена по его просьбе.