Repository navigation
Epic: accounting correctness and fork hygiene (audit follow-ups) #87
Description
Follow-ups from an audit of how the fork and upstream protect calculation correctness. Finding: neither checks numbers against an external reference — tests prove the code agrees with itself, so a wrong price table passes all of them. This epic fixes the accounting layer (prices, cost, real-data validation) and the project hygiene around it (CI, dead scripts, upstream sync).
It is layer 1 of #78: the waste-detection evaluation there is only meaningful once the base numbers are right. In particular #72 blocks #82, and #74 overlaps #79.
Sub-issues
- test:chunks / test:semantic / test:noise / test:task-filtering point to missing files #76 — dead
test:*scripts - CI: restore build step and Windows test matrix dropped from upstream #75 — CI: build step, Windows matrix, critical coverage
- Upstream sync: 4 PRs merged upstream on 2026-09-26 not carried in fork #77 — upstream sync (4 PRs from 2026-09-26)
- CLI cost estimate uses outdated per-family prices (Opus overstated 3–3.75×) #72 — outdated per-family price table (priority/high)
- App never computes session cost — costUsd is hardcoded to 0 #73 — app never computes cost
- No validation of token/cost totals against real sessions or an external reference #74 — validation against real sessions / external reference
Найдено при проверке эпика #2 —
- Ledger drops content blocks: keep-last dedup treats split response lines as snapshots #88 — ledger теряет блоки ответа (keep-last дедупликация): ~⅓ вызовов инструментов и все thinking-блоки
- Active time differs between analyze:session and analyze:sessions on the same file #91 — активное время расходится между
analyze:sessionиanalyze:sessions - Plugin skills are stale: audit-session documents 6 of 12 finding types #92 — скиллы плагина описывают 6 из 12 типов findings
- Plugin install path: skills need a full checkout, README has no install line, docs drift #93 — путь установки плагина, README без строки установки, расхождения в CHANGELOG
Рекомендации по выполнению
Порядок
| Фаза | Ишью | Почему так |
|---|---|---|
| 0 | перепроверка #88, #91, #92, #93 | Выводы сделаны агентом, автор им не доверяет: сначала дважды подтвердить или закрыть как ложные. |
| 1 — быстрые, параллельно | #76, #75, #77 | Мелкие и независимые от расчётов. #75 первым, чтобы следующие PR проверялись сборкой и на Windows. |
| 2 | #72, #88 | Главные баги учёта: цены и потеря содержимого раундов. Можно начинать параллельно с фазой 1, но вливать после #75. |
| 3 | #73, #91 | #73 переиспользует модуль цен из #72; #91 — общая функция активного времени. |
| 4 | #74 вместе с #79 | #79 строит корпус и снапшоты, #74 добавляет внешний эталон на этом же корпусе. |
| 5 | #92, #93 | Скиллы и документация — после того как CLI считает правильно, иначе описывать придётся дважды. |
Общие правила: одно ишью — один PR; в PR, меняющих числа, прикладывать «было/стало» на своих реальных сессиях (после #79 — вывод corpus:diff); не менять пороги WASTE_THRESHOLDS и логику findings в тех же PR.
#76 — мёртвые скрипты (~15 мин)
test:chunks→vitest run test/main/services/analysis/ChunkBuilder.test.ts;test:noise— проверить, покрывает лиtest/main/services/parsing/MessageClassifier.test.ts; дляtest:semanticиtest:task-filteringсоответствующих тестов, похоже, нет — удалить.- Одновременно поправить
CLAUDE.mdи.claude/rules/testing.md: агенты выполняют эти команды буквально.
#75 — CI
- Взять за основу апстримный workflow (
git show upstream/main:.github/workflows/ci.yml): шагpnpm build+ матрицаubuntu-latest/windows-latestсfail-fast: false. Добавитьpnpm build:cli. Сохранить форковские--frozen-lockfileиclaude plugin validate(последний достаточно на Ubuntu). - Critical coverage: сначала измерить текущее покрытие файлов расчётов (
src/main/utils/jsonl.ts,src/shared/turnAccounting.ts,src/cli/analyzeSession.ts,src/renderer/utils/contextTracker.ts) и поставить пороги на этом уровне («храповик»), поднимать по мере tokensWasted mixes prices and ignores residency: express waste in $ and count re-reads #82/Non-overlapping waste attribution and session waste share; split observations from waste #84. Произвольный высокий порог сразу сделает CI красным. - Если Windows выявит падения — это реальные баги путей: чинить в этом же PR, тесты не отключать.
#77 — синхронизация с апстримом
- Пробный merge
upstream/mainв текущийmain: 13 файлов, +39/−5, 2 конфликта —src/renderer/components/chat/ChatHistory.tsxиsrc/renderer/components/chat/LastOutputDisplay.tsx. Остальное сливается автоматически. - Делать одним merge-коммитом (
git merge upstream/main), а не cherry-pick: так сдвигается merge-base, и следующая синхронизация покажет только новое. - Сделать до App never computes session cost — costUsd is hardcoded to 0 #73, если App never computes session cost — costUsd is hardcoded to 0 #73 будет трогать UI — меньше конфликтов в renderer.
- Обновить баннер и раздел Upstream Sync Policy в README.
#72 — таблица цен (главное)
- Сразу класть таблицу и
roundCostUsdвsrc/shared/(напр.src/shared/constants/pricing.ts), а не в CLI — иначе в App never computes session cost — costUsd is hardcoded to 0 #73 придётся переносить второй раз. - Ключ —
(family, major, minor)изparseModelString(src/shared/utils/modelParser.ts). Неизвестная версия → unpriced (costPartial), никогда не «цена семейства по умолчанию» — именно так и появился текущий баг. - Пять явных колонок: input, output, cache read, cache write 5m, cache write 1h. Cache read не выводить множителем: у Opus 5.5 и Fable 5.1 он не 0.1×. В комментарии — дата сверки и ссылка на прайс.
usage.cache_creation.ephemeral_5m/1h_input_tokensдобавить вUsageMetadata; если разбивки нет (старые логи) — весьcache_creation_input_tokensсчитать 5-минутным. В проверенной сессии 100 % записей — 1-часовые (см. комментарий в CLI cost estimate uses outdated per-family prices (Opus overstated 3–3.75×) #72).- Haiku 5.5 тарифицируется ступенчато (промпт > 100K дороже) — считать по входной стороне каждого запроса или явно задокументировать упрощение.
- Модели через роутеры (glm и т.п.) оставить отдельной веткой;
detectBillingSchemeуже различает схемы. - Ожидаемые значения в тестах выписывать вручную из прайса, не вычислять из таблицы.
#88 — потеря содержимого раундов
- Не ломать подсчёт токенов: usage по-прежнему из последней строки запроса; склеивать только содержимое.
- До фикса проверить на старых сессиях (корпус Regression corpus of real sessions (local, via .env) with numeric snapshots #79), не были ли строки там накапливающимися снимками — иначе склейка удвоит блоки.
#73 — стоимость в приложении
- Считать по дедуплицированным сообщениям (
deduplicateByRequestId) с моделью каждого сообщения: в одной сессии бывает несколько моделей. - Явно решить и задокументировать, входят ли subagents в стоимость сессии, и сделать так же, как в CLI — иначе parity-тест будет сравнивать разные вещи.
- Добавить флаг частичной стоимости в
SessionMetrics(src/main/types/domain.ts), а не возвращать 0 для неизвестных моделей. - Parity-тест CLI ↔
calculateMetricsпо образцуtest/shared/turnAccounting.parity.test.ts.
#74 — сверка с реальностью
- Делать на корпусе из Regression corpus of real sessions (local, via .env) with numeric snapshots #79 (локально, путь через
.env), не коммитить реальные сессии. - ccusage: токены должны совпадать точно; стоимость — с допуском, т.к. у ccusage свой источник цен. Расхождение стоимости по конкретной модели — сигнал ошибки цены у одной из сторон.
- Один раз сверить итог за день с Usage в Console — единственный эталон, который не зависит ни от чьей таблицы цен.
- Обезличенная мини-фикстура для CI: оставить только usage, model, requestId, timestamps, имена инструментов и размеры; тексты и пути вырезать.
Activity