← 返回文章列表← Back to posts

我给 Claude Code 写了一套“宪法”——为什么 AI 编程需要约束系统Why AI Programming Needs a Constraint System — Building a Constitution for Claude Code

从真实踩坑案例出发,探讨 AI 编程的核心矛盾——能力强但无记忆,以及如何通过分形文档、自动化约束和记忆衰减来管理 AI 的上下文。Starting from real bug stories, exploring AI programming's core contradiction — powerful but memoryless — and how to manage AI context through fractal documentation, automated constraints, and memory decay.

·11 分钟阅读min read

我给 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 项目的实践经验,所有踩坑案例均来自真实项目。