Skip to content

feat(handoff): окно 31 день + указатель всегда с живой целью (v1.2.13) - #36

Merged
jojoprison merged 8 commits into
mainfrom
worktree-floating-frolicking-goose
Jul 26, 2026
Merged

feat(handoff): окно 31 день + указатель всегда с живой целью (v1.2.13)#36
jojoprison merged 8 commits into
mainfrom
worktree-floating-frolicking-goose

Conversation

@jojoprison

@jojoprison jojoprison commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Доводка реформы handoff по итогам аудита + ответ на требование j «31 день должен влезать обязательно».

Ротация стала календарной

handoff.keepDays (31) — правило; handoff.maxKB (56) — страховка под ним; maxLines удалён. Замер живого волта: 7.5 сессий/день, 234 за 31 день — прежний потолок 180 строк молча отдавал ~24 дня при конфиге, где написано keepDays: 31. Размер выведен из замера, не из вкуса: 234 × ~195 B ≈ 46 KiB, потолок 56 KiB даёт ~20% запаса и файл по-прежнему читается за один раз. Пять ручек конфига → три.

Когда месяц не влезает — файл говорит об этом строкой > _overflow:. Молча отдавать меньше обещанного — ровно тот дефект, ради которого затевалась реформа.

Эвикция безопасна только потому, что закрыты две дыры

  • relink-orphan-pointers.py — указатель без цели ((без session-заметки)) был косметикой, пока индекс хранил всё; под окном это потеря данных: эвикция убирает последнюю входящую ссылку на блок. Теперь такие указатели ведут в точную часть архива (на живом волте — 10 блоков из 186, 170 открытых пунктов). Миграция их больше не создаёт.
  • backfill-tails-from-archive.py — хвосты, жившие только в холодных частях, переносятся в свою же session-заметку. Узкий пересмотр «не переписываем заметки пользователя»: переносится только реально отсутствующее — 31 пункт из 136, потому что 105 уже были в заметке другими словами. Сравнение нечёткое намеренно (буквальное даёт 0% там, где правда 77%), и оно же делает повторный прогон no-op.

Дистрибуция

Скрипты миграции переехали в plugins/mnemo/scripts/, health Step 7.6 предлагает их в порядке зависимостей. У установленного пользователя раньше не было пути миграции вовсе: handoff в блочном формате, а новый session пишет указатели.

Плюс фиксы аудита (были в этом PR до релиза)

Мёртвый откат (бэкап выбирался по mtime), затирание частей при повторном split, молчаливая потеря указателя на легаси-файле, дубли при --keep-blocks, отсутствие переклипа существующих строк, украденный хвост в шаблоне отчёта health.

Тесты: 16 наборов зелёные (+31 новый), lint 7/7, verify-release.py — v1.2.13.

🤖 Generated with Claude Code

jojoprison and others added 8 commits July 25, 2026 19:06
…риаж handoff (v1.2.11)

Continuity получила автоматического ЧИТАТЕЛЯ, которого у неё не было.

Замер на живом vault 2026-07-25: handoff 805 KiB, 185 блоков, 863 открытых
пункта. Тело втягивалось ради continuity в 6% сессий — потому что прочитать
его нечем: Read отказывается на 256 KB / 25000 токенов, а Bash режет вывод до
превью. Инструкция «при старте прочитать handoff» была неисполнима, и
docs/session.md обещал путь чтения, которого в коде нет.

Добавлено:
- hot-scan.py — собирает незакрытые хвосты из pending-секций свежих
  session-заметок; внутри pending-секции голый «- буллет» тоже хвост (класс
  v1.1.10: живое ≠ всегда чекбокс, на живом vault таких 90); все лимиты в
  БАЙТАХ (len(str) занижает кириллицу на 34%)
- инжект дайджеста в mnemo-context.sh рядом с нуджем: project-scoped (метка
  проекта = basename git common dir, чтобы воркти отдавал имя проекта, а не
  своё), байтовый cap, деградация до нуджа при любом сбое, обязательная
  строка про протухшую премису
- handoff-resolver.py + health Step 7.6 — офлайн report-only триаж: потолок
  выигрыша, классы блоков по payoff, пункты с внешним якорем. Инвариант
  keep-hot ИМПОРТИРУЕТСЯ из vault-write.py, а не копируется. Свои якоря
  пункта отделены от унаследованных из заголовка блока: смешение раздувает
  «резолвимость» с 27.6% до 60.6%

Исправлено:
- docs/session.md обещал чтение handoff при старте — такого пути не было
- «token bomb read every session» как обоснование потолка: реальная цена
  раздутого handoff — нечитаемость, а не токены
- session/SKILL.md теперь честно называет предел гварда: один открытый
  «- [ ]» пришпиливает блок, поэтому архиватор законно бывает no-op при
  превышении потолка в 20 раз

Тесты: test-hot-scan.py (20), test-context-hook.py (11, CLI obsidian
замокан), test-handoff-resolver.py (15). Мутант-килл пройден на двух
инвариантах: подмена байтов символами и игнор плоских буллетов роняют сюиту.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…строку-указатель (v1.2.12)

Замер, который переопределил реформу: свежие открытые пункты handoff
присутствуют в своих session-заметках лишь в 9% случаев (старые — 34%).
То есть handoff стал ЕДИНСТВЕННЫМ домом forward-state — отсюда и 805 KiB,
и невозможность ни прочитать его, ни сжать. Схлопывать блоки в строки
нельзя: потерялись бы 257 живых хвостов.

Настоящий фикс — не формат блока, а ДОМ хвоста.

- session Step 5 + depth-contract: незакрытая нить пишется в свою
  session-заметку, handoff получает указатель. Строка-указатель ограничена
  ЧИСЛОМ СЕССИЙ, скопированный хвост — ничем.
- vault-write: action handoff-index-upsert, идемпотентный по ссылке на
  session-заметку (чекпоинт обновляет свою строку, а не плодит близнеца).
  Попутно чинит уже сломанный контракт: Step 5 требовал «точную старую
  секцию из read», но большой handoff читается превью, где целой секции нет.
  Лимиты в БАЙТАХ; при обрезке жертвуем меткой проекта, но не ссылкой.
  Блочный путь (archive-handoff) не тронут, его регресс-сюита цела.
- migrate-handoff-to-index.py — одноразовая миграция, dry-run по умолчанию.
  Блоки переносятся ДОСЛОВНО И ЦЕЛИКОМ (не извлечением чекбоксов: 90
  плоских буллетов и ~200 строк прозы держат живое без «- [ ]»),
  session-заметки не трогаются, ничего не удаляется, .bak до записи,
  проверка дословного присутствия после. Живой замер: 826916 B → 35910 B.
- restore-handoff-from-bak.py — откат написан и отрепетирован ДО миграции
  (vault без git). На копии живого файла: migrate → restore возвращает
  байт-в-байт, md5 совпал.
- split-handoff-archive.py — архив по месяцам + компактный хаб. Холодный
  файл не безобиден: 717 KiB не открывается (лимит чтения 256 KB), при этом
  попадает почти в любой поиск. Крупные месяцы дробятся: июнь = 425 KB.

43 новых теста. Мутант-килл: счёт символов вместо байтов и снятый hard cap
роняют сюиту.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Поймано живым повторным запуском на уже мигрированном handoff: скрипт
не выходил рано при нуле блоков, а честно проходил весь путь и дописывал
в хаб архива маркер «сюда перенесены 0 блоков (0 B)» плюс пару пустых
строк в конец handoff. Данные не страдали (0 блоков — нечего переносить,
бэкапы писались впустую), но повторный прогон обязан быть неотличим от
несделанного.

Теперь при отсутствии датированных блоков скрипт печатает «handoff уже в
индекс-формате — мигрировать нечего» и выходит, не создавая ни бэкапов,
ни правок. Два теста пинят это: повторный прогон не меняет ни один из
двух файлов и не плодит лишний .bak.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Личные permission-разрешения в публичном плагине выдали бы каждому, кто
клонирует репо, заранее одобренный путь к скриптам, которые необратимо
переписывают vault. Файл нужен локально, в репозитории ему не место.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…вый откат

Аудит пятью независимыми срезами по свежесмерженной реформе. Данные целы
(186/186 и 444/444 блоков дословно на месте), но защитные механизмы —
нет.

Критичное:
- ОТКАТ БЫЛ NO-OP. Повторный прогон миграции пишет .bak уже мигрированного
  файла, а restore выбирал по mtime → «отмена» восстанавливала индекс
  поверх индекса. Живой vault был именно в этом состоянии. Теперь бэкап
  обязан выглядеть до-миграционным, иначе требуется явный --stamp.
- Повторный split затирал разложенные части (бэкап делался только хабу).
- handoff-index-upsert на легаси-файле больше cap выбрасывал собственную
  новую строку и рапортовал успех — сессия выглядела записанной и не была.
- migrate --keep-blocks дублировал указатели при повторе.
- Существующие строки не переклипывались, поэтому клейм про границы был
  ложен на живом файле (754 B при потолке 200).

Доки: блок 📮 Handoff в health украл хвост content-lint (тот самый класс
из code-discipline); Step 7.6 помечен legacy-only; досвипаны сиблинги
контракта; в design-decisions добавлен отсутствовавший раздел про handoff.

Живой vault приведён в порядок: битая ссылка И№26, затёкший [[Т-Банк]],
дубль указателя, нормализация до 180 строк / 36 459 B.

+13 тестов. Все 14 файлов зелёные, lint и release-контракт тоже.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…естный счётчик дайджеста

Остаток находок аудита:
- clip_to_bytes резал сводку посреди слова без маркера — «BTS: BRIDGE-»
  читалось как целая запись. Теперь ставится «…», а огрызок короче 12
  символов выбрасывается целиком: фрагмент выглядит информацией, не неся её.
- Части архива получали date: «2026-07 ч1-01» — не дата, из-за суффикса
  части. Property-вью по таким файлам молча не отрабатывают.
- У частей не было секции Связи — нарушение граф-правила проекта, на неё
  опираются connect и health.
- Дайджест показывал 43 хвоста из 234, не говоря об этом. Теперь пишет
  «показано N из M» прямо в тексте.

11 живых частей архива приведены в порядок тем же контрактом.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… взят из main (1.2.11 слит в 1.2.12), код — из ветки
…с живой целью (v1.2.13)

Ротация индекса стала календарной: keepDays=31 — правило, maxKB=56 — страховка
под ним, maxLines удалён. При замеренном темпе (7.5 сессий/день, 234 за 31 день)
прежний потолок 180 строк молча отдавал ~24 дня при конфиге, где написано 31.
Пять ручек конфига схлопнуты в три.

Эвикция безопасна только потому, что закрыты две дыры: relink-orphan-pointers
чинит указатели без цели (10 блоков / 170 пунктов на живом волте),
backfill-tails-from-archive переносит хвосты, которые жили только в архиве, в их
же session-заметку — точечно, 31 пункт из 136 (остальные 105 уже были в заметке
другими словами).

Скрипты миграции переехали в plugins/mnemo/scripts — у установленного
пользователя раньше не было пути миграции вовсе.
@jojoprison jojoprison changed the title fix(handoff): пять дефектов реформы, найденных аудитом — включая мёртвый откат feat(handoff): окно 31 день + указатель всегда с живой целью (v1.2.13) Jul 26, 2026
@jojoprison
jojoprison merged commit c19f720 into main Jul 26, 2026
2 checks passed
@jojoprison
jojoprison deleted the worktree-floating-frolicking-goose branch July 26, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant