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
.4correction 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: tagv2.0.0-dirty.4=51677ff0onD:/projects/research/steps-framework. Tag verified againstHEADbeforeVERSIONwas trusted, re-verified after the copy; source.tfw/clean at both checks.installed_fromrecorded. Committed:018a194in 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.mdby 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¶
cd .tfwв одной команде оболочки пережил до следующих — три проверки отработали в неверном каталоге и «не нашли файлов». Ошибка агента, не фреймворка; но она стала возможна потому, что гид миграции сам предлагаетcd .tfw && find …. Все команды в гиде — от корня.cp -rбез исключений (§4). Спасён бэкапом, сделанным «на всякий случай», — не по инструкции.- Handle выведен из имени Git-аккаунта (§2) — прямое нарушение §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: заглушкаecho→python .tfw/scripts/gen_index.py --check tasks.- Маршрут в README на русском; футер версии
v0.8.7→v2.0.0-dirty.4(стоял устаревшим с незакоммиченного обновления 1.0.0).
Открыто — владельцу фреймворка¶
- §1 — whole-or-refuse для ячейки статуса и явная инструкция про фазы. Это второй подряд первичный дефект миграции в парсере доски; оба — «нашёл правдоподобное вместо доказанного целого», один в идентификаторе, другой в статусе.
- §2 — 🛑 гейт и Briefing в
update.md. Единственная категория дефектов, которую видит владелец, а не оператор. - §3 — Step −1 в
update.md.
Рекомендации другим пользователям TFW — по порядку исполнения¶
- Откройте
update.mdиз источника, не из своего.tfw/. Ваш — тот, который устарел. - Прочитайте манифест построчно и найдите каждую multi-phase задачу. Если она начинается с
✅ DONE (…), инструмент её закрыл. Пишитеstatus.mdзадаче и каждой фазе сами; фазовый абзац берите изTFW-60/phase-a/status.md, не из шаблона. - Бэкап
project_config.yamlиknowledge_state.yamlдо копирования payload. Копируйте, восстанавливайте, сливайте конфиг руками. - Три вопроса владельцу до первой записи: кто вы, где новые задачи, каков
build.*. Если владельца нет — задайте их первым сообщением после и будьте готовы переделать маршруты. - Закончите брифингом на языке владельца: что можно / что починено / что больше не нужно. Это не любезность — это то, что определяет, примут ли изменение.
- Доска новее
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 — тег, вырезанный,
чтобы проверить исправленный путь миграции; путь исправлен для идентификаторов и не исправлен
для статусов. Каждое число получено измерением в репозитории получателя; цитата владельца
приведена по его просьбе.