首页
/ caveman /caveman-stats:从会话 JSONL 日志读取真实 Token 用量与诚实的净节省核算

caveman /caveman-stats:从会话 JSONL 日志读取真实 Token 用量与诚实的净节省核算

2026-09-06 12:42:42作者:秋泉律Samson

本文围绕 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_tokensusage.cache_read_input_tokens,每命中一条计一个 turn,并记录首条出现的 message.modelcaveman-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,000Est. tokens saved: 650 (~65% of output)tests/test_caveman_stats.js)。

4. 美元折算。 脚本内置一张按模型 id 前缀匹配的 Anthropic 公开 output 定价表 MODEL_OUTPUT_PRICE_PER_Mcaveman-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. netsaved - 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 只有是正整数才生效;garbage0-10012.5 全部回落到默认 1,250——这一点由 tests/test_caveman_stats.js 的专门用例逐值验证(合法值 500 生效,非法值回退)。deriveNet() 的注释还点明了一个易被忽略的会计问题:节省是 OUTPUT token,开销是 INPUT token,两者分属不同计费桶,但相加才是诚实的全预算增量。

按模式归因:绝不伪造节省

如果一个会话中途切换过模式(比如先 offfull),把整段 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-statsCAVEMAN_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.jsmain()):

参数 作用
--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 生命周期视图加时间窗,如 7d24h;格式非法时退出码 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;最终裁定仍建议用同一任务开启/关闭各跑一遍、以服务商计费总量为准。

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