Skip to content

fix(turn): compaction is not a turn — one numbering everywhere - #69

Merged
axisrow merged 9 commits into
mainfrom
fix/turn-numbering-parity
Oct 10, 2026
Merged

axisrow merged 9 commits into
mainfrom
fix/turn-numbering-parity

Conversation

@axisrow

@axisrow axisrow commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Проблема

После первой же компакции нумерация ходов расходилась между механизмами:

Место До фикса
Приложение (jsonl.ts turnCount, UserChunks) компакт — не ход (канон, CompactChunk = структурная граница)
TurnBudgetDetector (колокол) компакт съедал номер → после компакции номера сдвинуты на +1
turnSpendStats (калибровка) слайсы нумеровались по isTurnBoundary → тот же сдвиг
buildLedger (analyze:session) компакт открывал ход (исправлено ранее в этой ветке)

Пользователь видит «ход 46», колокол рапортует «turn 47».

Red → Green

  • red: TurnBudgetDetector > a compaction marker consumes no turn number — падал с expected 3 to be 2: компакт-маркер инкрементировал номер как реальный ход.
  • fix: каноничный предикат isTurnNumberLine (только real user messages) в turn-accounting.mjs (+ .d.mts декларации, реэкспорт хука, eslint allowlist). Детектор: компакт сбрасывает бакет, номер не растёт; turnSpendStats печатает номера бакетов.
  • green: сюита 965/965.
  • mutation (ручной): откат детекторного фикса → parity-тест снова красный.
  • аудит: 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

axisrow and others added 5 commits October 10, 2026 23:44
- 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 axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. isTurnNumberLine регрессирует против канона приложения: не исключает sidechain (isUserChunkLine не проверяет isSidechain) — колокол убегает вперёд от Turn-чипов при любом Task-субагенте (jsonl.ts turnCount явно гардит !isSidechain); и не считает teammate-релеи, которые приложение считает ходами (#55, main a80fd95: isTranscriptTurnLine && !isSidechain && !isCompactSummary) — в team-сессиях отстаёт. Компакт-ось PR чинит то, что main уже починил в a80fd95. Нужен rebase + один канон + parity-тест на обеих осях.
  2. sessionSums.ts: void main() без isDirectRun-гарда выполняется при импорте, а собственный тест модуля его импортирует.
  3. 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-диагностика и панель в текущем виде хороши.

Comment thread src/main/utils/loopDetection.ts Outdated
Comment thread src/cli/sessionSums.ts Outdated
Comment thread src/cli/analyzeSession.ts Outdated
… 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>
@axisrow
axisrow force-pushed the fix/turn-numbering-parity branch from e02af3c to bd7c38a Compare October 10, 2026 15:53
axisrow and others added 3 commits October 10, 2026 23:58
…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 axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approving (approval)

Rebase на main (a80fd95) выполнен корректно, все три находки прошлого ревью закрыты отдельными коммитами:

  1. Канон нумерации (было: регрессия 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-промпты.
  2. isDirectRun-гард в sessionSums (d99fc7c) — main() больше не выполняется при импорте тестом.
  3. --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) формулировка уже правильная, можно подтянуть декларацию позже.

Готово к мержу.

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