caveman /caveman-stats:从会话 JSONL 日志读取真实 Token 用量与诚实的净节省核算
本文围绕 caveman 仓库的 caveman-stats 技能展开:它直接读取 Claude Code 会话日志(JSONL),报告本会话真实的 output/cache token 用量,并对照基准给出节省估算——所有数字由脚本从磁盘日志计算,模型本身不做任何估算。文末还给出规则开销(rule overhead)与净节省(net)的核算逻辑、按模式归因的“不伪造节省”机制,以及从源码与测试可验证的完整参数说明,读完你可以自行运行、核对并理解每一行输出的来源。
真实回执,而不是 AI 估算
caveman-stats 的核心承诺只有一句话:Real session token receipts. No AI estimation.(skills/caveman-stats/README.md)
它的工作方式是直接读取当前 Claude Code 会话日志文件,报告实际 input/output token 用量,以及与“不开启 caveman”的基线相比的估算节省。数字来自磁盘上的 JSONL 会话日志——模型既不计算也不估算它们。输出由 caveman-mode-tracker 钩子注入:该钩子拦截 /caveman-stats 斜杠命令,运行统计脚本,并把格式化结果作为被拦截决策的 reason(decision: "block",见 SKILL.md)返回给用户,用户在终端里立刻看到数字。
从源码结构看,这条投递链在 caveman-mode-tracker.js 中实现:钩子用正则匹配 /caveman-stats 及其参数,然后以 execFileSync 同步执行 caveman-stats.js,并在可用时透传 --session-file <transcript_path>——这样读到的永远是当前活动会话,而不是碰巧最近被修改过的某个 JSONL 文件;子进程带 2500ms 看门狗超时,失败时退化为一条可手动重试的提示:
caveman-stats: could not run stats script.
Try manually: node hooks/caveman-stats.js
调用方式与输出形态
在 Claude Code 会话内直接输入:
/caveman-stats
示例输出(README 原文,数字为示意值):
Session: 47 turns
Input: 12,304 tokens
Output: 3,891 tokens (caveman)
Baseline: 11,247 tokens (estimated without caveman)
Saved: 7,356 tokens (~65%)
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)
注意最后两行的语义:Est. rule overhead 估算规则每轮注入的 INPUT token 成本(默认 1,250 tokens/turn),Est. net 是节省减去该开销。在短而简练的回复场景中 net 完全可能为负——caveman 的 OUTPUT 节省抵不过它的 INPUT 成本——输出会直接说出来,而不是藏在一个毛节省数字后面。README 明确说明以上数字仅为示意,为什么短会话倾向于净负,见 docs/HONEST-NUMBERS.md。
数字是怎么算出来的:源码级拆解
统计脚本 src/hooks/caveman-stats.js 的取数路径如下:
1. 解析会话日志。 parseSession() 逐行 JSON.parse JSONL,只统计 type: "assistant" 且带 message.usage 的条目,累加 usage.output_tokens 与 usage.cache_read_input_tokens,每命中一条计一个 turn,并记录首条出现的 message.model(caveman-stats.js)。
2. 压缩率表。 节省估算依赖一张硬编码的压缩率表,目前只有 full 模式有实测数据:
const COMPRESSION = { 'full': 0.65 };
其来源注释写明:取自 benchmarks/results/ 下基准结果(10 个任务平均节省 65%,模型 sonnet-4-20250514);lite/ultra/wenyan 等模式在补齐基准前不显示任何估算。测试 tests/test_caveman_stats.js 中有一条用例直接验证:flag 为 ultra 时输出 No savings estimate for 'ultra' mode。
3. 基线与节省公式。 对统一处于某单一基准模式的会话,脚本反推“若无 caveman 会用多少 output token”:
estNormal = round(outputTokens / (1 - ratio))
estSaved = estNormal - outputTokens // ratio=0.65 时约为 output 的 65%
同一条测试给出了可手算复现的例子:full 模式下 350 个 output tokens → Est. without caveman: 1,000,Est. tokens saved: 650 (~65% of output)(tests/test_caveman_stats.js)。
4. 美元折算。 脚本内置一张按模型 id 前缀匹配的 Anthropic 公开 output 定价表 MODEL_OUTPUT_PRICE_PER_M(caveman-stats.js),要求“更具体的前缀必须排在前面,取第一个命中”;命不中就不输出 USD 行。
诚实核算:Est. rule overhead 与 Est. net
这是 README 强调的核心增量:只要上方节省数字无歧义(单一有基准的模式、已知轮数——不在混合模式或未归因区间上猜),输出就追加两行:
Est. rule overhead:每轮 INPUT token 开销 × 轮数。默认 1,250/turn,对应 SKILL.md(约 5 KB 规则)注入上下文 + 模式跟踪器每轮强化文本的成本——正是 docs/HONEST-NUMBERS.md 承认的“每轮约 1–1.5k input tokens”区间的中位值;Est. net:saved - overhead。为负时文案直接是caveman cost more than it saved for this workload — consider turning it off。
实现上,默认值与环境变量覆盖都在 caveman-stats.js:
const DEFAULT_RULE_OVERHEAD_TOKENS_PER_TURN = 1250;
function ruleOverheadPerTurn() {
const raw = process.env.CAVEMAN_RULE_OVERHEAD_TOKENS;
if (raw === undefined) return DEFAULT_RULE_OVERHEAD_TOKENS_PER_TURN;
const n = Number(raw);
return Number.isInteger(n) && n > 0 ? n : DEFAULT_RULE_OVERHEAD_TOKENS_PER_TURN;
}
即 CAVEMAN_RULE_OVERHEAD_TOKENS 只有是正整数才生效;garbage、0、-100、12.5 全部回落到默认 1,250——这一点由 tests/test_caveman_stats.js 的专门用例逐值验证(合法值 500 生效,非法值回退)。deriveNet() 的注释还点明了一个易被忽略的会计问题:节省是 OUTPUT token,开销是 INPUT token,两者分属不同计费桶,但相加才是诚实的全预算增量。
按模式归因:绝不伪造节省
如果一个会话中途切换过模式(比如先 off 后 full),把整段 token 全记到“查统计时 flag 恰好指向的模式”名下,会虚增或清零估算。因此 attributeByMode() 按精确度从高到低采用三种归因依据(caveman-stats.js):
| 依据 | 条件 | 行为 |
|---|---|---|
log |
模式切换日志 .caveman-mode-log.jsonl 覆盖该消息 |
每条消息按当时生效模式记账;日志首行之前的区间记为首行 prev |
flag-mtime |
无日志,但 flag 文件在会话中途被写过 | 只有 flag 写入时刻之后的 token 归属当前模式;之前的模式未知,直接排除,绝不猜测 |
whole-session |
无日志也无中途变更证据 | 当前模式覆盖全会话(模式从未变化时正确,等价于旧行为) |
切换日志按 session_id 过滤(多窗口并发时避免把 B 窗口的切换拼接到 A 的时间线上),非白名单模式值整行拒绝。输出端,非统一会话会打印逐模式分解,未归因 token 显示为 unattributed ... (mode unknown — excluded from estimate);只有统一会话才出 Est. net 行——混合模式或含未知区间时宁可不出净数,也不猜。
生命周期视图、状态栏徽章与内存压缩行
每次 /caveman-stats 运行(turns > 0 时)还会做两件旁路工作:
1. 追加生命周期历史。 向 ~/.claude/.caveman-history.jsonl(目录可用 CLAUDE_CONFIG_DIR 覆盖)追加一条含 ts / session_id / mode / model / output_tokens / turns / est_saved_tokens / est_saved_usd 的快照;同会话多次运行会追加多行,聚合时 aggregateHistory() 只取每个 session_id 的最新一条。命令行直接支持两种生命周期视图:
node src/hooks/caveman-stats.js --all # 全部历史
node src/hooks/caveman-stats.js --since 7d # 近 7 天;也支持 Nh,如 24h
2. 写状态栏徽章。 通过 safeWriteFlag() 写入 .caveman-statusline-suffix,内容形如 ⛏ 12.4k,供 shell 状态栏(caveman-statusline.sh)直接 cat 显示而无需解析 JSONL。README 特意说明:徽章故意保持毛节省口径——它是可一瞥的摘要而非完整核算,要看净口径请跑 /caveman-stats;CAVEMAN_STATUSLINE_SAVINGS=0 可关闭徽章。
此外,formatStats() 会扫描 ~/.claude 与当前目录中由 caveman-compress 留下的 *.original.md/*.md 文件对,若压缩版确实更小,则按约 4 字符/token 折算出一行 Memory compressed: N files, ~X tokens saved per session start (approx) 的被动节省提示。
手动运行与参数一览
脚本既可被钩子调用,也可脱离 Claude Code 直接执行(caveman-stats.js 的 main()):
| 参数 | 作用 |
|---|---|
--session-file <path> |
指定要解析的会话 JSONL;缺省时在 <CLAUDE_CONFIG_DIR>/projects 下递归找 mtime 最新的 .jsonl |
--session-id <id> |
钩子从 UserPromptSubmit 载荷转发;缺省时回退到 transcript 文件名(Claude Code 以 session id 命名 transcript,这不是猜测) |
--share |
输出一行可分享的摘要(含节省 token 数与可选 USD) |
--all |
生命周期累计视图,短路径处理,不依赖活动会话 |
--since Nh|Nd |
生命周期视图加时间窗,如 7d、24h;格式非法时退出码 2 |
两个实用环境变量:CLAUDE_CONFIG_DIR 重定向日志/历史目录(测试正是用它指向临时目录);CAVEMAN_RULE_OVERHEAD_TOKENS 覆盖每轮规则开销(见上文,仅正整数生效)。找不到任何会话文件时脚本以非零退出并打印 no Claude Code session found。
测试如何钉住这些行为
tests/test_caveman_stats.js 对以上机制做了可复现的钉扎,值得当作“验收清单”:
- 直接传
--session-file时,两个 assistant 条目(100 + 50 output,200 + 50 cache-read)汇总为Turns: 2 / Output tokens: 150 / Cache-read tokens: 250; full模式 350 tokens → 基线 1,000、节省 650(~65%),ultra模式不估算;- 钩子投递:向 mode-tracker 喂入
{ prompt: '/caveman-stats', transcript_path },断言 stdout 是hookSpecificOutput.additionalContext且包含格式化统计,同时确认统计请求不会篡改当前模式 flag; CAVEMAN_RULE_OVERHEAD_TOKENS的合法/非法取值行为(上文已述)。
小结:何时信它,何时不信它
caveman-stats 的价值在于把“省了多少 token”从模型嘴里的估算变成磁盘日志上可复核的回执:真实用量来自 JSONL 逐条求和,节省来自已提交的基准压缩率,净口径则显式扣掉规则注入的 input 成本,并且在归因不明时选择“不显示”而非“猜一个”。结合 docs/HONEST-NUMBERS.md 的结论——规则每轮约 1–1.5k input token,短问答类负载常常净负——Est. net 为负时应按提示对相应工作负载关闭 caveman;最终裁定仍建议用同一任务开启/关闭各跑一遍、以服务商计费总量为准。
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