Repository navigation
fix(turn): compaction is not a turn — one numbering everywhere - #69
Conversation
- red: TurnBudgetDetector numbered the post-compact segment as a new turn (expected 3 to be 2) — the bell's turn number diverged from the app's UserChunk numbering after the first compaction; turnSpendStats slices and session turn indexes drifted the same way - fix: isTurnNumberLine (real user lines only) in the canonical accounting core (turn-accounting.mjs + .d.mts declarations + hook re-export + eslint import allowlist); TurnBudgetDetector keeps the number across a compaction; turnSpendStats prints bucket numbers; buildLedger and the deep-dive preview already skip compact markers - audit: pnpm turn-spend:stats --audit <hhru session> → parity OK (47 turns) - mutation: manual — reverting the detector fix turns the parity test red - includes the cycle diagnostics built on this numbering (pnpm analyze:session <file> --turn N): cycle_motif / probe_no_progress findings and the «цикл или длинный ход» verdict Co-Authored-By: Claude Code <noreply@anthropic.com>
- pnpm session:sums <file.jsonl> prints raw-number JSON: session totals, per-turn sums (rounds count, inputSide/output/billedTotal, hookSpent via the hook's analyzeTurn — GUI/bell parity), per-round sums, explicit checksums (rounds=turns=session, ledger-vs-hook mismatches listed) and cycle attribution (tragedy: input-side of cycle-verdict turns) - splitSessionPath exported from analyzeSession — reused, not copied - audit: hhru session 3766d55e — checksums all true, hookParity 0 mismatches across 47 turns, jq sum 60 259 545 == session total; turn 46 15 495 691 (cycle-churning); tragedy 31.6M / 52.5% of the session Co-Authored-By: Claude Code <noreply@anthropic.com>
axisrow
left a comment
There was a problem hiding this comment.
Verdict: запрашиваю изменения (changes_requested)
Новая работа PR сильная: session:sums (machine-readable rollup с checksums), cycle-диагностика (cycle_motif / probe_no_progress + --turn deep-dive), rereadAll в панели (Re-read виден без переключения фаз), --audit с номерами слайсов, батч-паритет детектора против analyzeTurn. Мутационная дисциплина и тесты — на уровне.
Но ветка разошлась с main: #68 уже смержен как a80fd95 с ДРУГОЙ реализацией той же нумерации, и dry-run слияния (git merge-tree) даёт CONFLICT в 8 файлах: loopDetection.ts, turn-accounting.mjs/.d.mts, turn-budget-hook.d.mts, turnSpendStats.ts, FileWatcher.ts, RereadSection.tsx, loopDetection.test.ts. Разрешение нетривиальное — канон нумерации у двух веток расходится (находка 1), механический rebase молча откатит ось relay/sidechain.
Блокеры — см. inline
- isTurnNumberLine регрессирует против канона приложения: не исключает sidechain (isUserChunkLine не проверяет isSidechain) — колокол убегает вперёд от Turn-чипов при любом Task-субагенте (jsonl.ts turnCount явно гардит !isSidechain); и не считает teammate-релеи, которые приложение считает ходами (#55, main a80fd95: isTranscriptTurnLine && !isSidechain && !isCompactSummary) — в team-сессиях отстаёт. Компакт-ось PR чинит то, что main уже починил в a80fd95. Нужен rebase + один канон + parity-тест на обеих осях.
- sessionSums.ts: void main() без isDirectRun-гарда выполняется при импорте, а собственный тест модуля его импортирует.
- printTurnDeepDive: ledger.turns[turnNo - 1] — позиция в отфильтрованном filterLedgerByDate массиве (без реиндексации), а rounds/findings/preview матчатся по turnIndex; с --since/--until заголовок и таблица раундов описывают разные ходы.
Заметки (не блокируют)
- checksum roundsSumEqualsTurnsSum сравнивает сумму с самой собой (обе стороны — один и тот же reduce по roundsByTurn), всегда true; осмыслен только turnsSumEqualsSession.
- Топ-левел rereadAll в возврате processSessionContextWithPhases не читает никто: единственный коллер берёт phaseInfo.rereadAll (тот же массив). Лишняя поверхность API — вернуть одним способом.
- Детектор (loopDetection.ts:361) и sumTurnReread держат рукописные копии main-chain предиката рядом с isMainChainAssistantLine — «one definition, no drift» пока декларативен; после rebase можно свести в один экспорт (для ParsedMessage — с top-level model, для raw — message.model).
- Тест jsonl.test.ts с примесью relay+текст (#67/#64 пины) менялся в общем с main двойнике-коммите, в новой работе PR его нет — при rebase конфликта там быть не должно.
После rebase на main с сохранением канона нумерации main и добавлением relay/sidechain parity-теста — готов апрувнуть остальное: CLI-диагностика и панель в текущем виде хороши.
… bell's Post-#68-merge the detector (bell) numbers by transcript canon — teammate relays advance it — while buildLedger and the calibration CLIs number real user lines only. The slice-builder comment claimed detector parity; state the canon it actually implements. Co-Authored-By: Claude Code <noreply@anthropic.com>
e02af3c to
bd7c38a
Compare
…s on import Co-Authored-By: Claude Code <noreply@anthropic.com>
…ay position Co-Authored-By: Claude Code <noreply@anthropic.com>
…chips Co-Authored-By: Claude Code <noreply@anthropic.com>
axisrow
left a comment
There was a problem hiding this comment.
Verdict: approving (approval)
Rebase на main (a80fd95) выполнен корректно, все три находки прошлого ревью закрыты отдельными коммитами:
- Канон нумерации (было: регрессия relay/sidechain + конфликт с main).
src/main/utils/loopDetection.tsв диффе отсутствует — детектор остался ровно как в a80fd95 (транскрипт-канон: isTranscriptTurnLine && !isSidechain && !isCompactSummary). Ось sidechain теперь закреплена тестом (ffb91fb: sidechain-промпт субагента не двигает номер), ось relay — тестом main. isTurnNumberLine остался только для CLI-срезов и задокументирован как ledger-канон, не канон колокола (bd7c38a). Нумерация hookSpentByTurn совпадает с ledger по построению: isParsedUserChunkMessage ≡ isUserChunkLine — один предикат, включая sidechain-промпты. - isDirectRun-гард в sessionSums (d99fc7c) — main() больше не выполняется при импорте тестом.
- --turn по индексу ledger (19a1058) — turns.find(t => t.index === turnNo); индексы стабильны под filterLedgerByDate, поэтому и user-scan остаётся корректным при --since/--until.
Проверено: чекаут чистый, конфликтных маркеров нет (src/, scripts/), CI на ffb91fb зелёный. Панельная часть (rereadAll) корректно выпала при rebase — она уже на main через #68.
Заметки (не блокируют)
- checksum roundsSumEqualsTurnsSum сравнивает per-turn суммы с их же источником (один reduce по roundsByTurn) — всегда true; реальную проверку несёт только turnsSumEqualsSession и hookParity.
- В turn-accounting.d.mts комментарий к isTurnNumberLine говорит «app parity, CompactChunk» — точнее было бы «ledger canon»; у потребителей (bd7c38a) формулировка уже правильная, можно подтянуть декларацию позже.
Готово к мержу.
Проблема
После первой же компакции нумерация ходов расходилась между механизмами:
jsonl.tsturnCount, UserChunks)CompactChunk= структурная граница)TurnBudgetDetector(колокол)turnSpendStats(калибровка)isTurnBoundary→ тот же сдвигbuildLedger(analyze:session)Пользователь видит «ход 46», колокол рапортует «turn 47».
Red → Green
TurnBudgetDetector > a compaction marker consumes no turn number— падал сexpected 3 to be 2: компакт-маркер инкрементировал номер как реальный ход.isTurnNumberLine(только real user messages) вturn-accounting.mjs(+.d.mtsдекларации, реэкспорт хука, eslint allowlist). Детектор: компакт сбрасывает бакет, номер не растёт;turnSpendStatsпечатает номера бакетов.pnpm turn-spend:stats --audit <hhru-сессия с компактами>→parity OK (47 turns).В ветке также cycle-диагностика, построенная на этой нумерации:
pnpm analyze:session <file.jsonl> --turn N— находкиcycle_motif/probe_no_progressи вердикт «цикл или длинный ход».🤖 Generated with Claude Code