首页
/ caveman 的 /caveman-stats:基于会话日志的真实 Token 用量统计与诚实的节省核算

caveman 的 /caveman-stats:基于会话日志的真实 Token 用量统计与诚实的节省核算

2026-09-06 16:30:41作者:庞队千Virginia

本文围绕 caveman 仓库中的 skills/caveman-stats/SKILL.md 展开,讲清 /caveman-stats 这条斜杠命令背后的完整机制:它如何直接读取 Claude Code 的 JSONL 会话日志得到真实 token 用量、如何用基准数据估算"若不用 caveman 会多花多少 output token"、又如何在 Est. rule overheadEst. net 两行中如实呈现规则注入的输入成本与净收益(甚至直接告诉你"这个工作负载下建议关掉 caveman")。读完本文,你能完整理解该技能的 hook 契约、数据流、命令行参数、环境变量覆盖项,以及源码中按模式归因(per-mode attribution)的三层回退策略。

技能定位与 hook 契约

caveman-stats 的核心承诺是:真实会话 token 收据,不经过模型估算。它由 src/hooks/caveman-stats.js 实现,在 Claude Code 中通过 UserPromptSubmit 阶段的 caveman-mode-tracker hook 被触发:当用户输入 /caveman-stats 时,src/hooks/caveman-mode-tracker.js 用正则 /^\/caveman(?::caveman)?-stats(?:\s+(.*))?$/ 匹配提示词,识别后不再走模式解析逻辑,而是直接把该 prompt 转发给 stats 脚本并注入统计结果。

SKILL.md 原文描述的契约是:模型在这个技能触发时"不需要做任何事"——hook 会把格式化好的统计结果作为拦截决策的理由返回,用户立刻看到数字。当前实现的具体形式可以从源码中确认:mode-tracker 以同步子进程方式执行 caveman-stats.js(2.5 秒看门狗超时,超时或脚本缺失时降级为提示"could not run stats script"),然后把输出包进 hookSpecificOutput.additionalContext,并附带一条指令要求模型"把这段统计块原样打印在代码块里,不要说别的"(见 src/hooks/caveman-mode-tracker.js)。也就是说,模型只是"传声筒",所有数字都来自磁盘上的日志解析,而非模型自己心算——这一点由 tests/test_caveman_stats.js 中"mode tracker delivers /caveman-stats via additionalContext"等用例直接验证。

斜杠命令本身的注册见 commands/caveman-stats.tomldescription = "Real session token usage + lifetime savings + USD. Tweetable line via --share."prompt = "/caveman-stats {{args}}"{{args}} 允许把尾部参数透传给脚本。

数据从哪来:会话 JSONL 日志

stats 脚本的所有数字都来自 Claude Code 的会话 transcript(JSONL 文件):

  • 会话目录process.env.CLAUDE_CONFIG_DIR 或默认的 ~/.claude,会话文件位于 <claudeDir>/projects/ 下;
  • 定位会话:hook 集成时 Claude Code 会提供 transcript_path,mode-tracker 通过 --session-file <path> 显式传入,保证读的是当前活跃会话而不是"最近被修改的某个 JSONL";不带该参数直接运行时,脚本会递归扫描 projects/ 目录取 mtime 最新的 .jsonlsrc/hooks/caveman-stats.js);
  • 解析规则parseSession() 逐行解析 JSONL,只统计 type === "assistant" 且带 message.usage 的条目,累加 usage.output_tokensusage.cache_read_input_tokens,每计一次 usage 记一个 turn,并取第一条记录的 message.model 作为计费模型标识(src/hooks/caveman-stats.js)。

测试用例 tests/test_caveman_stats.js 验证了最基本的行为:给脚本一个包含两条 assistant 消息(output 100+50、cache_read 200+50)的合成会话文件,输出必须包含 Turns: 2Output tokens: 150Cache-read tokens: 250

此外,输出里还有 Cache-read tokens 一行:它本身不算"被节省"的部分,只是如实展示缓存命中的输入规模,为读者理解整个会话的 token 结构提供参考。

节省量的估算:只用有基准数据的模式

估算逻辑集中在 src/hooks/caveman-stats.js 的常量表与 deriveSavings()

  • 压缩比表COMPRESSION = { 'full': 0.65 }。源码注释说明 65% 来自 benchmarks/results/*.json 中 10 个任务的 per-task 均值(sonnet-4-20250514);lite/ultra/wenyan 等模式尚无基准数据,输出会明确写 No savings estimate for 'mode' — only 'full' has benchmark data,而不是给一个拍脑袋的数字。
  • 换算公式estNormal = round(outputTokens / (1 - ratio))estSaved = estNormal - outputTokens。即"若正常风格应输出 X token,而实际只输出了 Y,则节省了 X − Y"。测试中 350 output tokens、full 模式的断言是 Est. without caveman: 1,000Est. tokens saved: 650 (~65% of output)tests/test_caveman_stats.js)。
  • 美元换算:脚本内置一张按 model id 前缀匹配的 output 定价表 MODEL_OUTPUT_PRICE_PER_M(如 claude-sonnet-4 → $15/M、claude-opus-4 → $25/M、claude-3-5-haiku → $4/M 等,最具体的前缀必须排在最前,取第一个匹配)。模型不在表内(如未来的新模型)时美元行被省略,token 估算仍会输出——测试 tests/test_caveman_stats.js 专门验证了"unknown model 时不出现 Est. saved (USD)"。
  • 输出比例措辞:源码注释强调,从 output token 能诚实计算的比例只有"output reduction",绝不能标成"usage/budget 占比",因为 input 与 cache token 在 agentic 会话中占大头且不受 caveman 影响(详见 docs/HONEST-NUMBERS.md)。

Est. rule overhead 与 Est. net:把隐藏成本摆到台面上

这是 SKILL.md 强调的重点,也是该技能区别于"只看毛节省"的关键。只要上方节省量估算"无歧义"(单一有基准的模式、已知 turn 数),输出就会多出两行:

Est. rule overhead:    58,750 (input, ~1,250/turn over 47 turns)
Est. net:              -51,394 (caveman cost more than it saved for this workload — consider turning it off)

(上例取自 skills/caveman-stats/README.md 的示例输出。)

  • 规则开销:caveman 每轮会往上下文注入 SKILL.md 规则(约 5 KB)加上 mode tracker 的逐轮强化提示,docs/HONEST-NUMBERS.md 承认这是每轮约 1–1.5k 输入 token 的固定成本。脚本将其量化为 DEFAULT_RULE_OVERHEAD_TOKENS_PER_TURN = 1250src/hooks/caveman-stats.js),overhead = turns × 1250
  • 环境变量覆盖CAVEMAN_RULE_OVERHEAD_TOKENS 可以覆盖每轮开销,ruleOverheadPerTurn() 只接受正整数,garbage0-10012.5 等非法值一律回落到默认 1250——tests/test_caveman_stats.js 覆盖了这些边界(如设为 500 后断言 Est. rule overhead: 500 (input, ~500/turn over 1 turn))。
  • 净额与直白结论net = estSavedTokens − overheadTokens。净值为正时输出 +N (net saving after rule overhead);为负时不回避,直接写"caveman cost more than it saved for this workload — consider turning it off"。这正是 SKILL.md 所说的"不把净亏区间藏在毛节省数字背后"的实现。注意节省量是 output token、开销是 input token,两者属于不同计费桶,但源码注释指出:把它们相减是唯一的诚实全预算口径(src/hooks/caveman-stats.js)。
  • 何时不出 net 行:混合模式或部分 token 无法归因的会话,源码有意不输出 net 行,宁可缺省也不猜(src/hooks/caveman-stats.js)。

按模式归因:绝不用"当前 flag"冒充整个会话

如果会话中途切换过 caveman 级别,把整场 token 全记在统计时刻的 flag 名下会高估或归零节省量。源码用三层策略解决(attributeByMode()):

  1. log(最精确):mode tracker 与 SessionStart hook 在每次真实模式切换时往 ~/.claude/.caveman-mode-log.jsonl 追加 {ts, mode, prev} 行(src/hooks/caveman-config.js 定义文件名)。stats 把这些时间戳与会话 JSONL 中每条 assistant 消息的 timestamp 做 join,每段输出 token 记在生成当时生效的模式名下。readModeLog() 还会按 --session-id 丢弃其他窗口的切换行,避免跨窗口交织污染时间线。
  2. flag-mtime:没有切换日志、但 flag 文件在会话中途被写过——只有写入点之后的 token 可归因给当前模式,之前的记为 unknown 并明确排除("no-fake-savings"原则)。
  3. whole-session(兜底):既无日志也无中途变更证据,则当前模式覆盖全会话(模式从未变时这是正确答案,也是 #601 之前的旧行为)。

输出格式也随之变化:非 uniform 会话会打印逐模式分解(如 full: N tokens (est. X saved)caveman off: N tokens (no benchmark estimate)unattributed: N tokens (mode unknown — excluded from estimate)),页脚注明"只有模式已知的区段才套用基准估算"。

命令行参数与运行方式

脚本既可被 hook 调用,也能手动直接运行:

node src/hooks/caveman-stats.js                 # 自动找最近会话
node src/hooks/caveman-stats.js --session-file <path.jsonl>   # 指定会话文件
node src/hooks/caveman-stats.js --all           # 全生命周期汇总
node src/hooks/caveman-stats.js --since 7d      # 最近 7 天(支持 Nh / Nd)
node src/hooks/caveman-stats.js --share         # 单行可转发摘要
  • --session-file:hook 集成必传,防止读错会话;
  • --session-id:hook 转发,用于过滤模式日志中属于其他窗口的行;缺省时回退到 transcript 文件名(Claude Code 按 session id 命名 transcript,所以这不是猜测而是既有约定);
  • --all / --since:走生命周期聚合路径,短路的"无需活跃会话"——aggregateHistory()~/.claude/.caveman-history.jsonl每个 session 只取最新一条快照再求和(src/hooks/caveman-stats.js);--since 只接受 Nh/Nd 格式,非法值报错退出(--since takes Nh or Nd (e.g. 7d, 24h))。生命周期视图同样只对"记录了 turns 字段"的行计算 net——旧格式行缺 turns,混入会歪曲开销,因此只计入毛总量;
  • --share:输出单行摘要,如 🪨 Saved 650 output tokens (~$0.0098) across 1 turns this session — caveman.sh(测试断言见 tests/test_caveman_stats.js);无基准比例的模式退化为 🪨 1 turns, 200 output tokens this session — caveman.sh

在 Claude Code 内则直接输入 /caveman-stats(支持 /caveman:caveman-stats 命名空间写法及 --share/--all/--since 尾部参数,mode-tracker 会逐一透传)。

附带写入:历史快照与 statusline 徽章

每次运行时,若 turns > 0,脚本还会产生两个副作用(src/hooks/caveman-stats.js):

  1. 生命周期历史:向 ~/.claude/.caveman-history.jsonl 追加一行快照(tssession_idmodemodeloutput_tokensturnsest_saved_tokensest_saved_usd)。同一会话多次调用会追加多行,--all 聚合时按 session 取最新一条;
  2. statusline 后缀:聚合后的终身毛节省量经 humanizeTokens()(1.2M / 12.4k 风格)渲染成 ⛏ 12.4k 写入 .caveman-statusline-suffix,供状态栏直接 cat 显示。SKILL.md 特别说明:该徽章故意保持毛节省口径——它是"一眼可读的摘要"而非完整核算,要看净收益就运行 /caveman-stats

另外,脚本会扫描 ~/.claude 与当前目录下的 *.original.md 备份对(caveman-compress 压缩记忆文件留下的原件),若压缩版更小则按约 4 字符/token 估算每次会话启动的输入节省,并在输出中单独列出 Memory compressed: N files, ~M tokens saved per session start (approx)

健壮性设计:安装不完整时给可操作的信息

skills/caveman-stats/SKILL.md 提到该技能"由 hooks/caveman-stats.js 提供、被 hooks/caveman-mode-tracker.js 读取",而 src/hooks/caveman-stats.js 开头一大段防御代码正是为此契约服务:

  • 强制兄弟模块 caveman-config.js 缺失时,不抛裸的 MODULE_NOT_FOUND 堆栈,而是打印一行可操作提示("the install is incomplete. Run /plugin update caveman, or rerun install.sh")并以非零码退出——stats 没有"降级输出"可言,它打印的每个数字都依赖 config 模块管理的 flag/history,宁缺毋滥;
  • 针对 opencode 的目录布局(插件目录是 "type": "module",兄弟文件被改名为 .cjs)做了条件重试,且只在错误确为找不到 ./caveman-config 时才重试,避免把兄弟模块内部的 MODULE_NOT_FOUND 误报成"配置文件缺失";
  • 加载成功但导出形状不对(插件缓存漂移场景)也会被 shape check 拦截,报"install is inconsistent";
  • 依赖 readFlag/appendFlag/readHistory/safeWriteFlag/VALID_MODES 等导出,per-session 新导出(resolveActiveMode 等)则逐个回退到旧行为,保证旧版 config 模块下仍能出正确的机器级数字而不是直接报错。

验证依据

以上机制均有测试覆盖,tests/test_caveman_stats.js(约 900 行)包括:

  • token 求和、full 模式 65% 换算、非 full 模式不出估算;
  • USD 行随模型定价表出现/省略、priceForModel 前缀匹配跨点发布版本(claude-opus-4-20250101 → $75/M 而 claude-opus-4-7 → $25/M);
  • --all 每 session 取最新快照、--since 时间窗过滤;
  • 历史快照追加内容逐字段断言;
  • net 行为:正值、负值、CAVEMAN_RULE_OVERHEAD_TOKENS 覆盖及非法值回落;
  • mode-tracker 触发时不改变 .caveman-active flag(stats 命令不得顺带切模式)。

小结

/caveman-stats 体现的是 caveman 项目"诚实数字"的产品原则:真实数字(output/cache token、turns)直接来自会话 JSONL;估算数字(65% 压缩比、每轮 1250 规则开销)都标注来源并可被环境变量校准;净亏场景直接建议关闭而不是美化。理解这套核算口径后,你可以把它当作 A/B 的参照基线——而 docs/HONEST-NUMBERS.md 仍建议以供应商账单上的同任务 A/B 对比作为最终裁决依据。

登录后查看全文
热门项目推荐
相关项目推荐