caveman 的 /caveman-stats:基于会话日志的真实 Token 用量统计与诚实的节省核算
本文围绕 caveman 仓库中的 skills/caveman-stats/SKILL.md 展开,讲清 /caveman-stats 这条斜杠命令背后的完整机制:它如何直接读取 Claude Code 的 JSONL 会话日志得到真实 token 用量、如何用基准数据估算"若不用 caveman 会多花多少 output token"、又如何在 Est. rule overhead 与 Est. 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.toml:description = "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 最新的.jsonl(src/hooks/caveman-stats.js); - 解析规则:
parseSession()逐行解析 JSONL,只统计type === "assistant"且带message.usage的条目,累加usage.output_tokens与usage.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: 2、Output tokens: 150、Cache-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,000、Est. 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 = 1250(src/hooks/caveman-stats.js),overhead = turns × 1250。 - 环境变量覆盖:
CAVEMAN_RULE_OVERHEAD_TOKENS可以覆盖每轮开销,ruleOverheadPerTurn()只接受正整数,garbage、0、-100、12.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()):
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丢弃其他窗口的切换行,避免跨窗口交织污染时间线。flag-mtime:没有切换日志、但 flag 文件在会话中途被写过——只有写入点之后的 token 可归因给当前模式,之前的记为 unknown 并明确排除("no-fake-savings"原则)。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):
- 生命周期历史:向
~/.claude/.caveman-history.jsonl追加一行快照(ts、session_id、mode、model、output_tokens、turns、est_saved_tokens、est_saved_usd)。同一会话多次调用会追加多行,--all聚合时按 session 取最新一条; - 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-activeflag(stats 命令不得顺带切模式)。
小结
/caveman-stats 体现的是 caveman 项目"诚实数字"的产品原则:真实数字(output/cache token、turns)直接来自会话 JSONL;估算数字(65% 压缩比、每轮 1250 规则开销)都标注来源并可被环境变量校准;净亏场景直接建议关闭而不是美化。理解这套核算口径后,你可以把它当作 A/B 的参照基线——而 docs/HONEST-NUMBERS.md 仍建议以供应商账单上的同任务 A/B 对比作为最终裁决依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00