healthlog уже переехал на канон 3, а переименование секции я внёс
правкой записи версии 3 задним числом — то есть переписал текст, по
которому он ехал. Посылка «ни один проект на каноне 3 не стоит» была
ложной, решение ШШШ отменено.
Запись версии — черновик ровно до первого переехавшего проекта. После
этого она история, и любое изменение канона заводит новую версию, даже
если меняется одно слово. Проверять дёшево: grep '"canon"' по живым
проектам. Дорого обратное — проект, повышенный по тексту, которого
больше не существует, невоспроизводим.
Запись версии 3 восстановлена дословно (Разработка | Tooling),
переименование уехало в версию 4. jellybit, стоящий на каноне 2,
прочтёт обе записи подряд и заведёт Разработка, чтобы через шаг
переименовать; в шаг версии 3 добавлена оговорка «едешь сразу на 4 —
заводи Готово последней и не переставляй дважды».
«Готово» переехало вниз, и порядок секций стал каноническим.
Достигнутое копится: через год этой секции больше, чем всех остальных
вместе, и стоя первой она отодвигает за экран то, ради чего роадмап
открывают чаще всего. Порядок проверяет roadmap_lint, переставляет
check --fix — вместе с содержимым секций, потому что двигать десяток
строк руками это работа, на которой ошибаются. Чужую секцию
перестановка не трогает вовсе: её место в порядке неизвестно.
Индексы позиций считаются из самого кортежа: ACHIEVED был 0 и стал 3,
хардкод пережил бы перестановку молча и сломал бы close.
Обкатка нашла два дефекта оформления, оба порождённые самой
перестановкой. Отбивка нужна и перед заголовком — сдвиг блоков ставит
два заголовка вплотную. Удаление строки индекса оставляет две пустые
подряд, и пустоты копятся. Проверка оформления теперь сверяется с самим
нормализатором, а не своим набором условий: два описания одного правила
разъедутся, и check начнёт молчать о том, что --fix правит.
CANON_VERSION = 4 в docs.py, примеры .pm.json в canon.md и skeletons.md.
DECISIONS тема 26 (ЭЭЭ, ЮЮЮ, ЯЯЯ, следствия 98–100).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
«Разработка» называла слишком много: роадмап весь про разработку, и
секция с таким именем не отличалась от остальных ничем. Стало
Сопровождение | Operations.
Смысл расширен вместе с именем: было «инструмент и процесс», стало «чем
держат проект: инструмент, процесс, эксплуатация». Расширение не
косметическое — английское Operations при узком смысле обещало бы
эксплуатацию, а внутри лежал бы линтер. Метрики, логи, инфраструктура и
выкладка в эту секцию просятся и так.
Заодно синхронизирован словарь трёх мест канона, которые про одну тему.
Сопровождение — всё, чем держат проект; эксплуатация — его часть, работа
системы на проде. ROADMAP.md, секция Сопровождение — план работ;
architecture.md, раздел «Эксплуатация» — как устроено сейчас;
эксплуатационный проход ревью — оптика проверки. Сливать их в одно слово
было бы ошибкой: они отвечают на разные вопросы. Синхронизирован
словарь, а не границы; дом — canon.md. Слово «поддержка» запрещено
вовсе: в нём слышится помощь пользователю.
Граница с возможностями проходит по тому, кто наблюдает: «приложение
сообщает о своём состоянии» — возможность, «дежурный видит состояние на
одном экране» — сопровождение.
Версия канона не менялась, и это законно: ни один проект на каноне 3 не
стоит, оба держат канон 2. Запись версии 3 правится как черновик, а не
как история — версия отделяет одно состояние проектов от другого, а не
одну редакцию текста от другой.
DECISIONS тема 25 (ЧЧЧ, ШШШ, ЩЩЩ, следствия 96–97).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Языковые правила лежали внутри скилла tasks: англицизмы, неизвестные
термины, «сложность формулировки — не признак сложности работы». Три
пункта из практики, без общей опоры и без ответа на «а что ещё сюда
относится».
Дом у языка теперь один — canon/references/language.md. Не в tasks, хотя
пришли правила оттуда: они относятся к документам канона, решениям ADR,
запискам разведки и сообщениям коммитов в той же мере, что к задачам, а
каталог задач и сам часть docs/. Раскладка отвечает, где текст лежит, —
этот файл отвечает, каким он должен быть словами.
Основа — информационный стиль Ильяхова, взятый не целиком. Взято:
полезное действие, глагол вместо отглагольного существительного,
активный залог, факт вместо оценки, стоп-слова, одна мысль — одно
предложение, параллельность, работающий заголовок.
Отброшенное названо вслух, и это отдельный раздел. Инфостиль написан для
текстов, где читателя надо удержать, а проектный текст читают потому,
что надо. Парцелляция ломает причинную связь, а в решении ценность
именно в ней. Запрет вводных целиком режет «если» и «в отличие от» —
условия, то есть сведения. Скобки в технической записи несут уточнение:
имя команды, единицы, слаг. Без этого раздела правило читается как «пиши
короче», и первый же агент начинает резать «поэтому» и «иначе».
«Снять корону с себя и надеть на клиента» переведено на здешнего
читателя: клиент — ты сам через квартал и тот, кто возьмёт задачу.
Таблицы англицизмов и жаргона взяты из скилла prepare-jira-text и
дополнены; в устав агента они уехали помеченной копией. Устав обязан
быть самодостаточным — он не разрешает пути плагина и не ходит по
ссылкам, — а два дома у одного правила здесь уже трижды расходились.
scripts/copies.py считает теперь 4 копии при 4 домах.
У агента вычитки правил стало двенадцать, разделены на форму записи
(только для задач) и язык (для любого проектного текста). Находки
докладываются в этом порядке: форма меняет решение «брать или не брать»,
язык — только цену чтения.
DECISIONS тема 21 (ЛЛЛ–ООО, следствия 86–88), changelog канона v3 —
пункт 6 и шаг переезда «прочитать и ничего не переписывать задним
числом».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Основной инструмент владельца отвечал на половину своего вопроса. Оценка идёт
по поведению: что приложение уже может и чего ещё не может, — а close
--implemented удалял у достигнутой цели и файл, и строку, так что роадмап по
построению показывал только «что осталось». Свидетельство лежало в самом
роадмапе healthlog: секция «Что уже пройдено» на двадцать строк прозы, руками,
с припиской «Эти звенья целями не заведены: закрытая цель записи не оставляет».
Теперь строка с датой переезжает в секцию достигнутого, файл удаляется
по-прежнему. Вторым домом поведения это не делает: нормативное поведение живёт
в openspec/specs, роадмап отвечает, когда и в каком порядке оно появилось.
Ссылки на файл в строке нет — файла больше нет, форма как в REJECTED.md.
Цель стала возможностью приложения, задача — шагом к ней:
- заголовок цели отвечает на «что приложение будет уметь»; свойство поведения
(«сообщает о своём состоянии», «исход не зависит от порядка») — тоже
возможность и переформулировки не требует;
- «Завершение» — списком, а не абзацем: задача ссылается на его строку, и это
новая защита от «отрефакторить X» вместо прежнего «наблюдаемо снаружи».
Заодно видно обратное: строка, к которой не относится ни одна задача, —
незакрытая часть возможности;
- работа над инструментом и процессом на этот вопрос не отвечает и живёт в
отдельной секции.
Цель обязательна не у всякой задачи. Прежнее «иначе она не попадёт ни в один
спринт» было угрозой, а не аргументом, и заставляло операционную работу
выдумывать себе направление. Граница по роду: feature без цели не бывает, fix,
chore и research живут без неё и входят в набор помимо цели спринта.
Тип [epic] упразднён: зонтиком стала цель, а слишком крупный шаг дробится под
ней. Ноль употреблений на 97 записей двух живых проектов.
Секции роадмапа — умеет / строим / направления / станок, четыре вместо двух;
имена приняты как временные и запаркованы (TODO 7). Имя секции достигнутого
знает скрипт — docs/.pm.json, ключ tasks.achieved_section. reopen цели снимает
строку достигнутого, круг проверен вживую.
Всё дописано в версию 3 канона: она ещё нигде не выкачена. DECISIONS 19,
YYY–ГГГ и следствия 78–81.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
«Идентичность, слияние, разбор» пришли из одного проекта, и в общем виде
формулировка не читалась: вопрос «как применить это к моему проекту» не имел
ответа в тексте. Теперь класс задан тремя условиями, не зависящими ни от
домена, ни от языка: вариантов несколько и оба защитимы; спека между ними не
выбирает; неверный выбор не падает, а даёт правдоподобный результат и молча
меняет смысл данных.
Отрицательный тест сильнее трёх положительных: то, что красит гейт, роняет
запрос или ломает тест, в класс не входит — это ловят проходы дешевле. Отсюда
же и причина, по которой класс достался самому дорогому проходу: независимая
реализация выберет другой вариант, и дифф между вариантами и есть находка; там,
где вариант один, она совпадёт с существующей.
Три слова остались как три места, где такие правила водятся — граница, где
данные входят или встречаются: состав ключа и нормализация перед сравнением;
победитель конфликта и тай-брейк при равенстве; границы токенов и неоднозначный
вход. Проект перечисляет свои места в docs/review.md, и перечень производен от
теста, а не заменяет его.
Две оговорки, без которых правило вырождается:
- триггер — новое или изменённое по существу правило, а не код рядом с ним;
иначе проект, чей домен и состоит из таких правил, всегда в deep;
- проект, где такого класса нет вовсе, deep не запускает никогда, и это
законное состояние, а не недонастройка.
review-reimpl получил тот же тест и право сказать первой строкой, что позвали
не на его класс, — строкой в границы покрытия, а не отказом работать.
DECISIONS 18, XXX и следствия 76–77.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Полный набор гонялся чаще, чем оправдано, и размер задач тут вторая причина,
не первая. Первая — триггеры: миграция схемы, публичный контракт и инвариант
поднимали ступень, не добавляя ни одного прохода. Миграцию гоняет gate шагом
миграций и разбирает ops, контракт сверяет specs направлением code→spec,
инвариант даёт основание для critical любому проходу — все трое уже в
standard. На проекте с базой и эндпоинтами верхняя ступень оказывалась не
исключением, а умолчанием: правило объявляло исключением то, что происходит
всегда.
Теперь ступень поднимает то, что даёт работу новому проходу. wide означает
ровно одно — изменение вводит новое понятие или структурную единицу; добавить
поле в существующий ответ это не концепт. standard стал рабочим умолчанием.
Проект, где изменение контракта и правда архитектурное, поднимает его сам в
docs/review.md — уточнением, а не возвратом прежнего умолчания.
Чекпоинт design получил то же условие: specs идёт всегда, rubric и
architecture — только при новом понятии. Он стоит на каждой задаче, поэтому
при мелкой нарезке три прохода умножаются на число задач.
Со стороны задач — шов нарезки: тест декомпозиции отвечает, допустим ли
разрез, шов отвечает, где его провести. Резать по границе, за которой падает
ступень; не резать, когда обе половины остаются в одной — костяк из четырёх
проходов платится за каждую задачу, и такой разрез делает ревью дороже.
Порога в числе границ нет по тому же принципу, что в теме 16: размер не
триггер. Дешёвое место заметить разнородную задачу — показ набора спринта,
там «Затрагивает» уже написан, а предложение ещё не заведено.
Правило выведено из состава проходов, а не из статистики прогонов — замер
остаётся за обкаткой. DECISIONS 18, RRR–WWW и следствия 72–75; JJJ темы 17
помечен как пересмотренный. Шаг про «Триггеры профиля» дописан в ещё не
выкаченную версию 3 канона, а не отдельной версией.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три изменения одной версией, потому что все три про одно — можно ли
оценить задачу, не открывая код.
PLAN.md → ROADMAP.md. Слово «план» значило в репозитории три разных вещи:
оглавление целей, план реализации внутри задачи и PLAN.json разовой
адаптации. Переименовано целиком — ключ конфига tasks.plan → tasks.roadmap,
--index roadmap, --roadmap-sections, --roadmap. Старый ключ в docs/.pm.json
не игнорируется молча: скрипт останавливается кодом 3 и называет
переименование, иначе проект искал бы опечатку там, где на самом деле
версия канона.
Род работы — тег kind:feature|fix|chore|research, вторая ось поверх типа
записи. В один префикс их не свести: идея бывает про функцию, эпик функцией
и является. Дом — тег, потому что теги здесь единственный механизм
разметки, а list --kind работает даром; цена принята — в строку индекса род
не попадает. Словарь закрыт, иначе он разъедется на bug/bugfix/fix/defect.
Отдельно легализован chore: у него «что станет наблюдаемо иначе» отвечается
разработчику, а раньше такие задачи либо не заводились, либо придумывали
себе пользовательскую пользу — и это второе хуже, оно проходит проверку.
Раздел «Затрагивает» — границы, которых изменение касается: эндпоинт,
таблица и миграция, формат на диске, публичный тип пакета. Без него задача
оценивается по объёму текста, а не по объёму поверхности. Механизируется
только наличие непустого раздела: полноту перечня машина не видит.
Род и границы требуются к взятию в спринт, а не к заведению — тот же приём,
что уже работает для критериев приёмки, и по той же причине. check о
пропаже только напоминает: иначе два живых проекта покраснели бы на 98
задачах, заведённых до этого решения.
Плюс правила языка задач: англицизм, у которого есть русское слово,
заменяется; термин не из паспорта, архитектуры или конвенций вводится
строкой или не употребляется; задача, которую не удаётся сказать просто,
чаще всего не одна задача.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прыжок standard → deep стоил самого дорогого прохода конвейера, а платить
приходилось за одну архитектурную находку: изменений, которые трогают
публичный контракт, но не вводят нового правила слияния, — большинство.
Ступень wide это standard плюс architecture (вход шире диффа, отсюда имя),
семь проходов против восьми.
Заодно вычистилась давняя неровность: триггер reimpl стоял внутри deep, и
профиль означал то семь проходов, то восемь — реестр состава, который
«сверяется взглядом до коммита», проверять было нечем. Теперь условие
«новое правило идентичности, слияния или разбора» выбирает профиль, reimpl
в deep безусловен и есть единственное отличие от wide. Барьер стоимости
остался только в deep: в wide за ним стоял бы один дешёвый проход с
потолком в 3 находки, а барьер сериализует то, что могло идти разом.
Цвет charter'а теперь кодирует модель, а не роль: sonnet → green,
opus → yellow, fable → red. Роль видна из имени, стоимость прогона —
ниоткуда, а список агентов читается взглядом.
scripts/frontmatter.py ловит три класса ошибок, невидимых при чтении:
- двоеточие с пробелом в незакавыченном описании — для YAML это вложенное
отображение, а не текст. Так было написано три описания из четырнадцати,
и читались они правильно;
- name, разошедшееся с именем каталога скилла или файла charter'а;
- цвет, не отвечающий модели: он ставится один раз при заведении charter'а,
а модель потом двигает калибровка.
Обе ветки проверены, коды выхода — общий словарь. Триггеры профиля в
canon.md и skeletons.md подтянуты под wide.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Поля Дата и Источник приведены к общей форме меты (поле на строку, имя
жирным) — той же, что у задачи и у записи журнала дефектов.
Существеннее второе. Правило «передумали — новая запись, старая получает
статус» жило в каноне с первой версии, но места под статус шаблон не отводил:
каждая запись изобретала своё — то абзацем, то заголовком, — а колонка
«Статус» таблицы adr/README.md обязана его показывать и брала оттуда, где он
у каждого свой. Теперь статус — такое же поле шапки, у активной записи его
нет.
Правка ушла в дом (canon.md, раздел adr/) и в оба скелета: template.md и
«Соглашения» в adr/README.md. Скелеты уезжают в репозиторий проекта, поэтому
версия канона поднята до 2 с записью в changelog: что добавилось, что
переехало и три шага проекту. Копии под маркерами не тронуты — copies.py
дословность подтверждает.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Разделение плагинов оставлено, цена названа: пять симметричных контрактов в двух
домах, два уже разошлись — форма журнала дефектов потеряла в копии поле
«Причина», список читателей docs/research/ потерял specs. Оба раза копия
выглядела актуальной и прошла мимо трёх ревью.
scripts/copies.py требует побайтового совпадения текста между маркерами.
Комментарии, а не манифест копий: маркер уезжает в репозиторий проекта вместе со
скелетом и там полезен — говорит, что у текста есть дом.
Идентификатор строгий и повторяется в закрывающем маркере. Иначе документация о
самом механизме объявляет дом и роняет проверку: это случилось на первом же
прогоне, README объявил дом примером.
Ограда блока кода в сверку не входит: в доме текст обрамлён своей оградой, в
скелете лежит внутри чужой, объемлющей.
Помечены два контракта. Второй пришлось сперва сделать дословным: копия говорила
«обязателен статус», дом — «обязателен статус „заменено на“».
Проверка не ловит копию, которую забыли пометить, — это сказано вслух, иначе
зелёный прогон читался бы как «копий больше нет». И не заменяет запись в журнал
версий канона: она видит, что копия отстала, но не что проект унёс старую версию.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Два независимых сабагента на av-dev-pm и av-dev-pipeline. Две находки нашли оба.
Главная — моя же перестановка закрытия за коммит сломала reopen и батч. close
печатал «дорога назад из git», а reopen искал коммит удаления, которого в новом
порядке ещё нет: шаг 11 последний, учёт остаётся незакоммиченным. Проверено
прогоном — отказ кодом 2 на свежезакрытой задаче. Тем же грязным деревом
ломались rebase и worktree remove в батче: каждая закрывшая задачу ветка уехала
бы в провалившиеся.
Починено с обеих сторон: reopen берёт текст из HEAD, если коммита удаления нет,
а шаг 11 коммитит учёт вторым коммитом.
Вторая — канонический пример docs/.pm.json убивал tasks.py. Четыре документа
показывали ключ tasks.sections, которого скрипт не знает: неизвестный ключ это
код 3 на любой команде. Проект, заведённый по канону дословно, остался бы без
работы с задачами, а docs.py при этом печатал «канон соблюдён». Секции живут в
заголовках индекса и второго дома не получают.
Остальные восемнадцать: init писал конфиг в упразднённый .tasks.json;
looks_like_tasks не видел переименованный индекс; урожай спринта терял автотег
после sprint close; ответ на вопрос по инструкции оставлял задачу незабираемой;
adopt требовал недостижимого зелёного; путь отчёта триажа не переживал archive;
review-specs не имел режима для стыка после слияния; три остатка «шаг 9а» несли
предкоммитную позицию закрытия; sprint.md отрицал сам себя в пункте «Сделана».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- sprint.md прямо запрещал шаг, который пайплайн теперь делает: раздел «кто и
когда закрывает» переписан под снятую границу приёмки
- закрытие задачи стало шагом 11, после коммита: раньше упавший коммит оставил
бы задачу закрытой без следа работы
- контракт close --implemented больше не обещает состоявшуюся приёмку
- слот «куда копируются критерии приёмки» убран из session: на него отвечает
пайплайн, а канон его не заводил
- research/ в таблице ролей вернул adversary; журнал версий поднимает CLAUDE.md
целиком, а не тремя пунктами из восьми
- TODO и REMAINING перестали занижать: коммитов одиннадцать, переписок три
- task-pipeline и task-batch запрещали шаг, который сами же добавили: раздел
границ и финальный доклад переписаны под снятую границу приёмки
- «триаж сводит строки в одну» противоречило «не сливает» — деградация снова
поразрядная во всех трёх местах
- канон не требовал в CLAUDE.md имени основной ветки, testdata и запретов, а
скелета CLAUDE.md не было вовсе — заведён
- обратимость жила в двух домах, читатели ходили в пустой; единственный дом
теперь CLAUDE.md
- заведены слоты «Единые точки проекта» и «Триггеры профиля», куда charter'ы
слали, а канон их не создавал
- блок «Вопросы к проходам» стал частью задания прохода: за ним ходили двое
из девяти
- чек-лист синка и правило «замер + настройка» сведены к одному дому;
plugin.json больше не про бриф
- av-dev-tasks → av-dev-pm; канон определён единственным reference-файлом,
который читают все три новых скилла
- canon: check/adopt/upgrade плюс docs.py — раскладка, битые ссылки, версия,
маркеры долга, сверки миграций и capability с документацией
- tasks и session: путь docs/tasks жёсткий, конфиг переехал в docs/.pm.json,
слот «Команда учёта задач» убран в пользу вызова скилла, раздел «Стимулы»
переписан под совпавших приёмщика и исполнителя