context-loss-audit
v0.1.1
Published
Audit what your output compressor silently deleted. Tells you how much of the reduction was noise and how much was signal.
Maintainers
Readme
context-loss-audit
English | 中文
审计你的输出压缩器到底删掉了什么。
它把压缩器宣传的那个数字——"削减 90%"——拆成两笔:
- 纯噪音删了多少:空行、ANSI 转义、重复行、进度条。这是真省下的。
- 信号删了多少:测试失败、警告、BREAKING CHANGE、枚举值、迁移文件路径。这是代价。
一个数字说明不了任何事。两个数字才能让你决定要不要用这个压缩器,以及在哪些命令上不能用。
为什么需要它
2026 年主流 agent 工作流里,压缩器已经默认装上了:把 Bash 输出过滤一遍再喂给模型,省 token。它们确实省了。
问题在于,省下的部分里混着不该省的东西。有用户在生产里踩过这些:
git status被截断,agent 只看到 15 个改动文件,把剩下的 5 个漏在提交之外——里面可能有迁移脚本、配置变更、安全补丁grep有每文件 25 条的上限,找废弃 API 的 agent 改完它看得见的那 25 处,宣布任务完成,剩下的漏洞实例还在线上git log把 commit body 砍到 3 行,BREAKING CHANGE:在第 4 行,被丢掉- JSON 里的枚举值被砍到 5 项,agent 照着生成校验逻辑,把合法用户挡在门外
这些场景里没有任何报错。压缩器的截断标记 ... +5 more 就在那儿,但标记不会阻止任何人继续干活——尤其是 agent:它读到、记一笔、继续。
有句话把这件事说得最清楚:
压缩器不只是滤掉噪音,它还把信号截断了。
—— RTK issue #1313
它怎么工作
三种信息汇成一份报告:
1. 痕迹识别(lib/markers.js)
从压缩后的文本里找出压缩器留下的截断标记,认出它的形态和数量。这里最难的不是匹配省略号,而是不匹配省略号——源码里的 ...args、diff 里的上下文行、test_alpha ... ok,都不该被当成截断。误报一次,报告就没人看了。
2. 损失判定(lib/loss.js)
把原文和压缩后逐行比对,判断每一条被删的行是噪音还是信号。判定原则是保守的:只有能被证明冗余的行才算噪音。空行、ANSI、重复行可以证明;其他一律算信号。这个不对称是故意的——把噪音误报成信号的代价是显得啰嗦,把信号误报成噪音的代价是告诉人家数据安全、而其实不安全。
3. 压缩器档案(lib/profiles.js)
已记录的压缩器规则表:它在哪砍、砍多少、砍的时候说不说。数字全部来自公开的 issue 和发版记录,每条规则都标了出处。
第三条里最有价值的是"说不说":
| 告知方式 | 含义 |
|---|---|
| 有标记 + 有数量 | ... +15 more —— 读者知道丢了多少 |
| 有标记 + 无数量 | ... (truncated) —— 读者知道丢了东西,不知道多少 |
| 静默 | 什么都不输出 —— 读者完全不知道 |
静默截断在压缩后的文本里没有任何痕迹,光看输出永远发现不了,只能拿原文对比。这也是为什么有 run 模式。
安装与使用
零依赖,Node 20+。
node bin/ctxaudit.js --help模式一:audit(手上已有两份文本)
ctxaudit audit --raw full.log --compressed rtk.log --command "git status"输出:
上下文压缩审计报告 (压缩器: RTK (Rust Token Killer))
命令: git status
================================================================
原文 566 字节 / 25 行
压缩后 382 字节 / 18 行
总削减 184 字节 (32.5%)
但这三笔要分开看:
① 纯噪音被删 0 行 <- 这才是真正省下的
② 信号被删 7 行 <- 这是代价
其中关键级 3 行
③ 砍了但说不清 0 处 <- 标记存在但没说砍了多少
----------------------------------------------------------------
被删掉的信号(按严重度排序)
----------------------------------------------------------------
[CRITICAL] 高风险文件(迁移/安全/配置/凭证)
原文第 17 行: modified: config/production.yaml
[CRITICAL] 高风险文件(迁移/安全/配置/凭证)
原文第 18 行: modified: migrations/0042_add_index.sql
[CRITICAL] 高风险文件(迁移/安全/配置/凭证)
原文第 19 行: modified: src/security/patch.js
...
----------------------------------------------------------------
结论: 危险:关键信号丢失
----------------------------------------------------------------
· 3 行关键信号在压缩后消失,报告读者很可能据此做出错误判断。
· 该压缩器有 1 条规则被记录为"静默截断":rtk.git-log.count。
这类损失无法从压缩后文本中发现,必须拿原文对比。模式二:run(当场跑对比)
ctxaudit run -- git status
ctxaudit run --compressor "npx rtk" -- go test ./...同一条命令跑两遍——原样一遍、经压缩器一遍——然后比对。这是唯一能抓到静默截断的方式。
查某压缩器记录在案的行为
ctxaudit --profile-info rtk列出它的每一条截断规则、上限、告知方式、严重度和出处。
通用选项
| 选项 | 作用 |
|---|---|
| --json | 输出 JSON,含全部明细不截断 |
| --strict | 只要检出信号损失就返回非零 |
| --quiet | 只输出一行结论 |
| --profile <名字> | 指定压缩器档案 |
| --command <名字> | 指定命令族,提升标记归因准确度 |
退出码
| 码 | 含义 | |---|---| | 0 | 干净,或仅有非关键损失 | | 1 | 危险:关键信号丢失,或存在无法计量的截断 | | 2 | 用法错误,或压缩器无法调用 |
注意 "有损"不会让构建变红,只有关键级和无法计量才会。一个动不动就报红的工具会被人静音,而静音了的审计器保护不了任何人。
已知局限
这一节比上一节重要。
1. 只看文本,不看后果。
它判断的是"信息有没有从文本里消失",不是"agent 会不会因此做错事"。一条被删的 --- PASS: 完全无害;一条被删的 modified: migrations/0042.sql 可能很贵。它按类别给严重度,但这个判断是规则式的,不懂你的业务。
2. 就地改写会误报。 如果压缩器把一行改写而不是删掉(重排表格、合并列),多重集比对会算作"删了一行、加了一行"。被删的那条按自身内容判级,可能报成信号——尽管信息其实还在改写后的行里。这个误差只会高报,不会漏报,但确实会让报告显得比实际严重。
3. 静默截断只能靠 run 模式抓。
audit 模式能力的天花板就是"压缩后文本里留下了什么"。压缩器不说的地方,它也无从得知——只能靠 --profile-info 告诉你哪里有坑,让你自己拿原文去比。
4. 档案是外部信息,会过时。
profiles.js 里的上限抄自公开 issue 和发版记录。压缩器改了行为而档案没更新,它就会按旧规则归因。每一条规则都标了来源,方便你核对。目前只覆盖 RTK,没有做自动探测。
5. 它不阻止任何事。 这是审计工具,不是拦截器。它告诉你损失,怎么处置是你的事。
和压缩器的关系
不是替代品,不是竞品。它在压缩器下面一层:压缩器管省 token,它管省下来的那部分里有没有夹带不该省的。
RTK 是目前记录最完整的对象,因为它的截断行为有公开的、带真实失败场景的 bug 报告可查(issue #1313、#2001)。这不是针对它——恰恰相反,它是因为把问题摊开讨论才被记录得最清楚。多数压缩器连这份材料都没有。
需要说明的是,RTK 已经在自己往"少截断"的方向走:v0.34.0 里修了 diff 截断、把 read 的默认改成不过滤、给 git 的截断标记加上了准确数量。但全局的无截断开关至今没有——[safety] no_truncation = true 有人 2026 年 4 月就实现好了,12 个测试全过,到本文写作时仍未进主线。
开发
node test/audit.js273 项断言,9 个章节,全部真实断言,无 skip。
测试最看重的两节:
- 第 3 节(误报防线):源码、diff、散文里的省略号不能被当成截断。这是最容易做错的地方。
- 第 9 节(命令行端到端):起真实子进程跑 CLI,不用 mock。这一节存在的原因是手工发现了一个真 bug——
run模式配一个不存在的压缩器时,它输出了一份完整的、自信的报告,而那个对比根本没有发生过。
目录
bin/ctxaudit.js 可执行入口
lib/profiles.js 压缩器档案(截断规则、上限、告知方式、出处)
lib/markers.js 截断标记识别 + 误报防线
lib/loss.js 噪音 / 信号判定
lib/report.js 报告组装与渲染
lib/cli.js 命令行
test/audit.js 测试数据来源
profiles.js 里所有数字都来自公开记录:
- RTK issue #1313 — rtk-ai/rtk#1313 七类真实失败场景、各条截断上限、截断标记的实际形态、静默截断的确认、以及"标记存在但不阻止行为"这个核心判断。
- RTK issue #2001 — rtk-ai/rtk#2001 41 条命令的实测压缩率、维护者对 bypass 阈值缺失的确认。
- RTK v0.34.0 发版说明 — diff/read 截断行为变更的出处。
如果你要审计的压缩器不在档案里,--profile passthrough 仍能跑"行分类"和"损失判定"两部分,只是没有规则归因。
License
MIT
