首页
/ caveman-compress 压缩机制解析:以 Taskflow 项目笔记为样本,看懂 caveman 记忆文件压缩的完整管线与安全边界

caveman-compress 压缩机制解析:以 Taskflow 项目笔记为样本,看懂 caveman 记忆文件压缩的完整管线与安全边界

2026-09-06 14:28:23作者:蔡丛锟

在 caveman 项目中,Claude 每次会话启动都会加载 CLAUDE.md 等记忆文件,大文件会反复消耗输入 token。caveman-compress 技能将这类自然语言记忆文件压缩为"穴居人风格"(caveman-speak),而 tests/caveman-compress/project-notes.original.md 正是仓库内置的官方基准样本之一:一份虚构项目"Taskflow"的完整项目笔记(架构决策、性能调查、安全评审、组件库选型、技术债清单),与它的压缩结果 tests/caveman-compress/project-notes.md 共同构成了一组"原始版 vs 压缩版"对照 fixture。读完本文,你将掌握 caveman-compress 的压缩规则与保留边界、端到端管线(检测 → 掩码 → 压缩 → 校验 → 重试 → 原子写入)、备份与回滚机制,并能在逐段对照真实 fixture 的情况下,判断一份自己的笔记文件压缩后哪些内容会被保住、哪些会被改写。

1. 样本文件是什么:一份典型的"会重复计费"的项目记忆

project-notes.original.md 是一份结构完整的 Markdown 记忆文件,包含 5 个带日期的章节:

  • Architecture Decision: Background Job Processing (March 2026):团队选择 BullMQ 而非自研或 AWS SQS,理由是已有 Redis、无新基础设施依赖;BullMQ 自带指数退避重试、优先级队列、限流、调度;初始作业类型为邮件通知、文件上传(缩略图、病毒扫描)、第三方同步(Slack、GitHub)、过期会话清理;并新增了管理员路由 /admin/jobs 的 BullMQ dashboard。
  • Performance Investigation: Dashboard Slowness (March 2026):任务数超过 500 时仪表盘不可用;主瓶颈是 N+1 查询(500 条任务 = 501 次数据库查询),次因是前端未虚拟化一次性渲染 500+ 任务卡片;给出 5 条候选方案(JOIN 加载 assignee、tasks(project_id, status, updated_at) 复合索引、游标分页每页 50 条、TanStack Virtual 虚拟滚动、Redis 30 秒 TTL 缓存),决定先做前 3 条治本方案。
  • Meeting Notes: Security Review (February 2026):外部审计发现 SQL 注入(字符串拼接查询,已改为 Knex.js 参数化查询并加 ESLint 规则)、JWT 有效期过长(access token 降至 15 分钟、refresh token 7 天存 HttpOnly cookie、access token 只存内存)、缺少 CSP(已加入 Next.js 中间件、先 report-only 模式运行 2 周)、公共 API 缺限流(express-rate-limit + Redis store 跨实例共享状态)。
  • Design Decision: Component Library (January 2026):对比 shadcn/ui + Radix、MUI、Chakra UI 三个选项,最终选 shadcn/ui,理由是代码完全自主、Radix 提供可访问性、Tailwind 契合现有策略、包体积小。
  • Technical Debt Inventory (January 2026):认证系统重构待测试覆盖补齐;测试套件统一为 Testing Library + MSW 并迁移 Enzyme;迁移脚本规范为"schema-only",数据转换独立成脚本;Webpack → Vite 迁移后清理残留配置。

从结构上看,这份文件恰好是 caveman-compress 最有代表性的输入类型:大量冗长叙述 + 若干不可触碰的技术锚点(库名 BullMQ/Knex.js/TanStack Virtual、路由 /admin/jobs、索引定义 `tasks(project_id, status, updated_at)`、数值 500/501/30 秒/15 分钟/7 天/2 周/50 条、人名 Sam/Alex/Maya、日期章节标题)。这些锚点在压缩后必须逐字保留,叙述部分则可以大刀阔斧地缩短——这正是该 fixture 入选基准集的原因。

2. 逐段对照:压缩到底改了什么、保住了什么

先看"背景作业处理"一节的原始段落:

After extensive discussion, the team decided to adopt BullMQ for background job processing instead of building a custom solution or using AWS SQS. The primary reasons for this decision were:

The team is already familiar with Redis, which is a requirement for BullMQ, and we're already running Redis for caching and session storage. Adding BullMQ doesn't introduce any new infrastructure dependencies. ...

对应压缩版(来自 project-notes.md):

Team pick BullMQ for background jobs. No custom build, no AWS SQS. Why:

Already run Redis for cache+sessions. BullMQ need Redis. No new infra. SQS break local dev, hurt contributor setup.

以及"性能调查"一节:

The primary bottleneck is the database query that loads the task list. The current implementation fetches all tasks for a project in a single query, then for each task, makes a separate query to load the assignee's profile. This classic N+1 problem means that loading 500 tasks results in 501 database queries.

Main bottleneck: N+1 query. Load all tasks, then per-task query for assignee profile. 500 tasks = 501 queries. Slow.

再比如技术债清单里"认证系统"段落,原文是完整的复合长句("was originally implemented in a rush ... leading to the reconnection race condition we're currently experiencing"),压缩后变成电报体:"Auth system: rushed at launch, messy. Token refresh split across 3 files, inconsistent error handling. WebSocket auth separate from HTTP auth — causing reconnect race condition now. Needs refactor, but team wait for better test coverage first."

把整份 fixture 的原始/压缩两版并排读完,可以归纳出 caveman 压缩的四个稳定特征,它们全部有源码级的规则依据(见 SKILL.md 的 Compression Rules 与 compress.pybuild_compress_prompt):

内容 原始版 压缩版 处理规则
冠词/填充词 "After extensive discussion", "It is important to" 全部删除 Remove: a/an/the、just、really、basically、actually 等
从句与被动语态 "which is a requirement for BullMQ" "BullMQ need Redis" 改为短语/断句,允许残句(fragments OK)
冗余连接词 "The primary reasons for this decision were:" "Why:" Remove: however、furthermore、additionally 等连接性废话
章节标题 ## Architecture Decision: Background Job Processing (March 2026) 逐字不变 Preserve Structure:所有 heading 原文保留
内联代码 `tasks(project_id, status, updated_at)` 逐字不变 反引号内容 EXACT 保留
技术术语与库名 BullMQ、Knex.js、TanStack Virtual、MSW 逐字不变 技术词/库名/API 名不压缩
专有名词 Sam、Alex、Maya、Taskflow 逐字不变 项目名、人名、公司名保留
数值与日期 500 tasks、501 queries、15 分钟、30 秒 TTL、March 2026 逐字不变 日期、版本号、数值保留
有序列表 1.–5. 方案编号 1.–5. 编号保留,条目文字压缩 保留编号结构,压缩单元格/条目文本
加粗标记 **shadcn/ui with Radix primitives** 保留 Markdown 结构保留,正文压缩

值得强调的是:这份 fixture 里没有一个围栏代码块(fenced code block),但内联代码、路径与数值锚点无处不在——比如安全评审中的 localStorage/admin/jobsexpress-rate-limit。压缩版把这些全部原样搬走,而叙述部分从完整的"审计叙事"塌缩成"电报体结论"。整份文件从 1145 个计 token 降到 535 个,节省 53.3%(数据见 skills/caveman-compress/README.md 的 Benchmarks 表),是五个官方 fixture 中压缩率第二高的——第一是纯偏好的 claude-md-preferences.md(59.6%),最低是结构更杂的 mixed-with-code.md(36.9%)。这也印证了规律:叙述越密、代码锚点占比越低的文件,压缩收益越大。

3. 端到端管线:从 /caveman-compress 到落盘的安全链

fixture 不是手工写的,它是管线跑出来的产物。下面按 skills/caveman-compress/scripts/ 里的真实实现,走一遍这份 project-notes 被压缩时经历的每一步。

3.1 入口与预检(cli.py)

技能入口是 /caveman-compress <filepath>,底层运行 python3 -m scripts <absolute_filepath>(见 SKILL.md 的 Process 一节)。cli.py 先做存在性/文件检查、用 detect_file_type 打印检测结论、should_compress 判定是否继续;.md 命中可压缩扩展名,直接放行。

3.2 类型检测与敏感文件拒收(detect.py / compress.py)

detect.py 维护 COMPRESSIBLE_EXTENSIONS = {".md", ".txt", ".markdown", ".rst", ".typ", ".typst", ".tex"}SKIP_EXTENSIONS.py.js.json.yaml.sql 等),对无扩展名文件还会做 shebang / JSON / YAML / 代码行占比(>0.4 判为代码)内容启发;should_compress 额外硬跳过一切 *.original.md 备份文件,保证"压缩产物不会成为下一轮压缩输入"。

进入 compress.pycompress_file 后还有三道硬闸门(均先于加锁执行,避免被拒输入留下锁文件残留):

  1. 500KB 上限MAX_FILE_SIZE = 500_000,超限直接报错;
  2. 敏感文件名拒收is_sensitive_path 用正则与路径组件黑名单(.ssh.awscredentialssecret*.pem 等)拒绝任何"压缩会把原始字节送到第三方 API"的文件——这是数据边界保护;
  3. 跨会话独占锁file_lock 用 POSIX fcntl.flock / Windows msvcrt.locking 在共享状态目录上的哈希锁文件上加 OS 级独占锁,等待上限 LOCK_WAIT_SECONDS = 900(15 分钟),进程崩溃会自动释放,无需陈旧锁簿记。

3.3 代码掩码:让模型"看不见"代码

_compress_file_locked 在读入源文件后,先 split_frontmatter 把 YAML frontmatter 整体摘出(frontmatter 从不交给模型,输出时原样拼回),再调用 mask_code_blocks:把围栏代码块(``` / ~~~)与 4 空格缩进代码块逐块替换为形如 @@CAVEMAN_PRESERVED_CODE_0_<sha256前16位>@@ 的不透明标记,再把这个"代码已打码"的正文交给 Claude。模型返回后经 restore_code_blocks 还原;若任一标记被模型删除、复制或缺改(restored.count(marker) != 1),直接抛 ValueError 拒绝写入——代码保真是 fail-closed 的。本 fixture 无围栏代码块,但内联代码、路径等锚点则靠压缩提示词中的 STRICT RULES("Do NOT modify anything inside inline backticks / Preserve ALL URLs exactly / Preserve ALL headings exactly")约束。

模型调用优先走 Anthropic SDK(设置 ANTHROPIC_API_KEY 时,默认模型可用 CAVEMAN_MODEL 覆盖,max_tokens=8192),否则回退到 claude --print CLI;每次调用有超时上限(LOCK_WAIT_SECONDS // (MAX_RETRIES + 1) = 300 秒)。

3.4 校验、定点修复与回滚(validate.py)

写入新文件后进入"校验 + 重试"循环(MAX_RETRIES = 2)。validate.py 对比备份原件与压缩产物:

  • 标题HEADING_REGEX 提取所有 #~###### 标题,级别与文本必须逐一匹配——这就是 fixture 里 5 个章节标题(连同日期)一字不差的原因;
  • 代码块:逐行解析围栏块(支持不同长度围栏与嵌套),并额外识别缩进代码块(注释里明确解释:漏掉缩进代码块曾让 kubectl delete pod --all -n prod 被悄悄改写而校验"干净通过"——"对一个破坏性命令给出干净通过是此工具最坏的失败");
  • URL、内联代码、文件路径URL_REGEX、反引号配对、PATH_REGEX/DEFINITE_PATH_REGEX(路径误报宽容、明确路径硬失败)。

校验失败时不做重新压缩,而是把错误清单 + 原文(仅参考)+ 压缩稿喂给 build_fix_prompt,要求模型"只修列出的错误,其余逐字不动";修复产物还要过"首行锚点"检查(原文以 ---# 开头时,修复稿首行必须一致,防模型夹带前言文本)。若 2 次重试后仍失败:把原始字节原子写回目标文件、删除备份,报告错误——原始文件最终保持原状。

3.5 备份、原子写入与"备份先验后写"

两个工程细节决定这份 fixture 生成过程零数据丢失风险:

  • 备份放在树外backup_dir_for 把原件字节级(write_bytes_atomic 写原始 raw bytes,非重渲染)保存到 $XDG_DATA_HOME/caveman-compress/backups/<父目录名>/(Windows 为 %LOCALAPPDATA%\caveman-compress\backups\<父目录名>\),而非源文件旁边——目的是让技能自动加载器不会把 .original.md 当活文件二次摄入。本仓库 tests/caveman-compress/ 下的 *.original.md 正是按此命名约定留存的五组基准原件。
  • 原子写 + 备份回读验证write_text_atomic 先编码、写同目录临时文件、fsync、os.replace 换入,并保持原文件权限位;写目标前会先 backup_path.read_bytes() != original_raw 逐字节核对备份,不一致就删掉坏备份并中止,绝不让用户拿到"损坏备份 + 已压缩主文件"的组合。行尾符也被保留(CRLF 文档保持 CRLF)。

4. 官方基准中的位置与适用边界

skills/caveman-compress/README.md 给出的五个 fixture 官方基准:

文件 原始 tokens 压缩后 tokens 节省
claude-md-preferences.md 706 285 59.6%
project-notes.md 1145 535 53.3%
claude-md-project.md 1122 636 43.3%
todo-list.md 627 388 38.1%
mixed-with-code.md 888 560 36.9%
平均 898 481 46%

README 同时给出诚实边界声明:结构校验(标题、代码块、URL、文件路径逐字保留)在全部 fixture 上通过,但"结果并不证明语义等价于其他文件或其他模型"。对 project-notes 这类多章节长文档,收益主要来自叙述句的塌缩(冠词、从句、连接词、重复表述),而章节骨架、编号方案列表(1–5 条方案及其取舍顺序)、内联技术锚点全部存活——这正是该技能"输入 token 减半、结构零漂移"的核心承诺。

适用前提与限制(以当前仓库实现为准):

  • 仅压缩自然语言文件(.md/.txt/.rst/.typ/.typst/.tex 及无扩展名文本文件),代码/配置文件一律跳过;
  • 单次输入上限 500KB,敏感命名文件硬性拒收;
  • 依赖 Claude(SDK 需 ANTHROPIC_API_KEY,或本机已安装 claude CLI),Python 3.10+;
  • 只有"首次压缩"和"校验失败后的定点修复"消耗 token,检测、掩码、校验、写入全部是本地 Python。

如果你想亲手复现本文的对照,可直接查看 tests/caveman-compress/project-notes.original.mdtests/caveman-compress/project-notes.md 的完整逐行 diff,再对照 skills/caveman-compress/scripts/compress.pyskills/caveman-compress/scripts/validate.pyskills/caveman-compress/scripts/detect.py 三个文件,即可完整还原从"叙述型项目笔记"到"53.3% token 节省的 caveman 版"的全过程。

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