我给 Claude Code 写了一套"宪法"——为什么 AI 编程需要约束系统
AI 不缺能力,缺的是记忆。它能写出完美的代码,却不记得三分钟前自己做了什么决定。
从一个 Bug 说起
有一天我在做评测平台,Tools 列显示 Bash×undefined。
排查后发现:一周前我把 trace collector 的数据格式从 {name, count} 升级为 {toolUseId, name, input, output},详情页已经适配了新格式,但列表页还在按旧格式解析——访问不存在的 count 字段,自然 undefined。
这不是 AI 写错了代码。Claude 改详情页的时候改得很好。问题是它不知道列表页也在消费同一份数据。它改了一个地方,忘了另一个地方。
这种 bug 有个名字:孤立变更。改了 A 忘了 B,因为没有机制提醒"B 也依赖这份数据"。
同一个项目里,我还遇到了:
- 相对路径多退一级,文件静默写入错误目录,数据库显示"已转换"但页面看不到
- CSS 类名漂移——两个页面做同样的事,一个用
.cards,另一个发明了.stats-row,后者没有样式定义 - 保存笔记时表单偷偷把 trace 标记为
bad,因为 API 硬编码了用户没有操作的字段
七个 bug,七种姿势,但本质相同:系统不记得自己各部分之间的关系。
AI 的阿尔茨海默症
人类工程师也会犯这些错误,但人类有一个优势——持续记忆。你在一个项目上工作三个月,脑子里自然形成了模块之间的依赖图谱。你改格式的时候会想起"哦,列表页那边也用了这个数据"。
AI 没有这个能力。每次对话是一个独立的上下文窗口。它能看到你让它看的文件,但看不到你没提到的文件。它不知道三天前的那个决定,不知道上周的架构约定,甚至不知道十分钟前另一个 Agent 在同一个项目里做了什么。
这就是 AI 编程的根本矛盾:
AI 的代码生成能力在指数增长,但它的上下文理解能力受限于窗口大小。
给 AI 更大的上下文窗口能缓解问题,但治不了根。一个 200 万 token 的窗口装不下整个项目的历史决策。真正的解法是外部化记忆——把系统的知识写成文档,让 AI 每次启动时能快速加载。
核心哲学:地图即地形
CC-Kit 的设计哲学可以用一句话概括:
The map IS the terrain.
代码是机器看的,文档是 AI 看的。两者描述同一个系统,必须保持同构。
这不是什么新鲜理念——人类工程师几十年来一直在说"文档要和代码同步"。区别在于,过去这是一个美好的愿望,现在这是一个功能需求。因为:
- 人类工程师的文档读者是其他人类,读不读都能凑合
- AI Agent 的文档读者是 AI 自己,读不到就真的不知道
当你的同事是 AI 的时候,文档不再是"最好有",而是AI 的工作记忆。
这意味着什么
想象你雇了一个非常聪明但完全没有长期记忆的实习生。每天早上他来上班,你得把项目背景、架构决策、编码规范从头讲一遍。如果你讲漏了什么,他就会按自己的理解乱来。
你会怎么做?你会写一份详细的入职文档。
CC-Kit 就是这份入职文档的规范化——确保这份文档始终是最新的,因为一份过时的入职文档比没有更危险。
三层分形:从宪法到注释
CC-Kit 把文档组织成三层结构:
L1 — 项目宪法(/CLAUDE.md)
└─ L2 — 模块地图(/module/CLAUDE.md)
└─ L3 — 文件契约(文件头部注释)
L1 是全局地图。 打开一个项目的 CLAUDE.md,30 秒内你能知道:这个项目是什么、用什么技术栈、目录结构长什么样、有哪些模块。AI 读到 L1,就知道去哪里找代码,哪些模块会互相影响。
L2 是局部导航。 进入一个模块目录,它的 CLAUDE.md 告诉你:这个目录有哪些文件、每个文件做什么、对外暴露什么接口。AI 读到 L2,就不需要把整个目录的文件全读一遍。
L3 是文件契约。 每个文件头部三行注释:我依赖谁(INPUT)、我提供什么(OUTPUT)、我在系统中的位置(POS)。AI 读到 L3,在改一个文件时就能知道"改了这里,谁会受影响"。
三层之间的关系是分形的——L1 是 L2 的折叠摘要,L2 是 L3 的折叠摘要。就像 Google Maps 的缩放层级:卫星视图看全貌,街景视图看细节,但它们描述的是同一片地形。
为什么不是更多层或更少层?
一层不够——一个 CLAUDE.md 装不下所有信息,要么太长 AI 读不完,要么太短没有用。
五层太多——维护成本超过收益,而且分形的美在于自相似的简洁。
三层刚好:全局、模块、文件。每一层的粒度自然对应 AI 工作时的思维层级:先看全貌定方向,再看模块定范围,最后看文件定实现。
禁止孤立变更:铁律与自动化
有了文档结构,下一个问题是:怎么保证它不过时?
答案是把同步变成自动化约束,而不是靠自觉。
CC-Kit 在 Claude Code 的 hooks 系统上挂了一个 doc-lint 脚本:每次 AI 编辑或创建文件时,自动检查:
- 这个文件所在的模块是否在 CLAUDE.md 中有记录?
- 同目录的 README 是不是超过 30 天没更新了?
- STACK.md 的技术栈声明是不是过时了?
它不会阻止你提交代码,但会在 AI 的上下文中注入一条提醒:"你改了代码,文档可能需要同步。"
这就像 ESLint 不会阻止你写 var,但会画一条红线提醒你。约束系统的目标不是限制行为,而是让正确的行为比错误的行为更容易。
一个反面教材:CC-Kit 自己的漂移
今天我们在整理 CC-Kit 仓库时,发现了一个讽刺的事实:
CC-Kit 的 session-lesson.sh 脚本在 ~/.claude/ 运行环境中已经修复了竞态条件(两个后台 worker 共享临时目录导致文件被另一个进程删除),但仓库里还是旧版。memory-decay.sh 运行版已经把花哨的 sigmoid 衰减算法简化为直接计算闲置天数,但仓库里还保留着那个"学术范儿"的旧实现。
一个以"禁止孤立变更"为核心法则的项目,自己违反了这条法则。
这恰恰证明了:约束系统存在的意义,不是因为遵守它很容易,而是因为违反它太自然。 如果连制定规则的人都会忘记同步,普通项目更需要自动化检查。
记忆衰减:让 AI 忘掉该忘的
CC-Kit 里有一套记忆生命周期管理:
- 自动提取: 每次对话结束时,后台 worker 分析对话内容,提取有价值的教训写入记忆文件
- 访问追踪: 每次 AI 读取记忆文件时,更新
last_accessed和access_count - 衰减归档: 超过阈值未被访问的记忆文件被标记为 stale,最终归档
这听起来很"系统工程",但它解决的是一个真实问题:AI 的记忆文件会无限增长。
如果不做衰减,三个月后你的记忆目录里会有 200 个文件,其中 180 个已经过时。AI 每次加载都要花 token 读这些废弃信息,而且过时的记忆比没有记忆更糟糕——它会让 AI 基于过时的事实做出错误的决定。
最初的设计用了 sigmoid 函数和指数衰减来计算"热度分数",看起来很科学,但实际上最终决策只依赖一个简单条件:idle_days > threshold。那些数学公式完全是装饰。后来简化为直接算闲置天数,效果一样,代码少了一半。
教训:不要用数学的优雅掩盖逻辑的简单。 如果你的决策只需要一个阈值判断,就别引入 sigmoid。
真正的价值:不是工具,是思维方式
写到这里,你可能发现了——CC-Kit 作为一个可安装的工具包,价值有限。它太个人化了,分形文档协议的严格程度不适合所有人。
但它背后的思维方式是通用的:
1. 把 AI 当失忆的天才来管理
它什么都会,但什么都不记得。你的工作不是教它写代码,而是给它提供足够的上下文来做正确的决定。这意味着:
- 项目的关键决策要写成文档,不能只存在于 git commit message 里
- 模块之间的依赖关系要显式声明,不能只靠 AI 自己去推断
- 编码规范要机器可读,不能只写在 wiki 上
2. 约束比自由更有效率
给 AI 无限自由,它会每次用不同的方式解决同一个问题。给它明确的约束(用什么风格、遵循什么协议、改完代码检查什么),它反而更高效,因为减少了决策空间。
这就像建筑规范——不是因为建筑师不会设计,而是因为规范让所有人的设计能互相兼容。
3. 自动化是唯一可靠的执行力
所有靠"记得做"的流程,最终都会被遗忘。doc-lint hook 的存在不是因为我不想手动检查文档,而是因为我知道自己一定会忘记。今天我们发现仓库和运行环境脚本漂移了五天,就是证据。
如果一件事很重要,把它变成自动化检查。如果它不重要到值得自动化,那它可能真的不重要。
实操建议:你不需要 CC-Kit,但你需要这三样东西
如果你也在用 Claude Code 或类似的 AI 编程工具,以下是我认为投入产出比最高的三件事:
一、项目根目录放一个 CLAUDE.md
不需要复杂的三层结构,一个文件就够。写清楚:
- 这个项目是什么(2-3 句话)
- 技术栈和主要依赖
- 目录结构和每个目录的职责
- 重要的架构决策和为什么这么决定
这个文件就是你和 AI 之间的共享记忆。每次开新对话,AI 都会先读它,然后它就不再是一个什么都不知道的实习生。
二、用 rules/ 目录管理编码规范
把你的偏好写成机器可读的规则文件:
~/.claude/rules/
├── coding-style.md # 不可变数据、小函数、文件不超过 800 行
├── git-workflow.md # commit 格式、PR 流程
└── security.md # 安全检查清单
这些不是 AI 的"建议",而是它的行为约束。写一次,每次对话自动加载。
三、给重要流程加一个 hook
哪怕只加一个:代码文件被修改时,提醒检查对应文档是否需要更新。
{
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "bash ~/.claude/scripts/doc-lint.sh"
}]
}]
}
这比完美的文档体系更重要,因为它解决的是执行力问题,而不是设计问题。
结语
CC-Kit 不是一个产品,它是一个人在和 AI 协作过程中踩了足够多的坑之后,总结出来的一套自我保护机制。
它的核心洞察很简单:
AI 时代的工程能力,不是写代码的能力,而是管理上下文的能力。
代码 AI 会写,而且越写越好。但"该写什么代码"、"这段代码和哪些模块有关"、"上次为什么做这个决定"——这些上下文,是人类工程师需要维护的。
你维护得越好,AI 就越像一个靠谱的同事。你维护得越差,AI 就越像一个每天重新入职的实习生。
文档不再是写给人看的注释,而是写给 AI 的工作记忆。
架构即认知,文档即记忆。系统是否可靠,取决于它是否记得自己是谁。
本文基于 CC-Kit 项目的实践经验,所有踩坑案例均来自真实项目。