实战笔记:为 Go 项目 issue2md 量身定制 constitution.md
一、核心定位
1. 什么是 constitution.md
constitution.md 是项目级最高原则文档,用来约束 AI Agent 的设计、编码、测试与规划行为。
它和普通规范文档的区别在于:
- 不是“建议”
- 而是“必须遵守的根规则”
2. 它在协作体系中的位置
可以这样理解层级关系:
constitution.md
> CLAUDE.md
> 单次会话指令
也就是说:
- 宪法是最高优先级
CLAUDE.md是项目协作手册- 聊天中的即时要求不能违背宪法
keywords: constitution, priority, governance
二、为什么 issue2md 需要自己的宪法
1. 因为通用模板不够“项目化”
通用 CLAUDE.md 可以解决“怎么协作”,但不能完全回答:
- 这个项目究竟崇尚什么设计哲学?
- 对抽象、依赖、测试、错误处理的底线是什么?
- 哪些行为是绝对不能做的?
2. issue2md 这类 Go 项目的典型特征
从命名和场景推断,这类项目通常具有这些特点:
- 偏工具型/CLI 型
- 更强调简单、可靠、直接
- 依赖链不宜过重
- 对输出稳定性要求高
- 对测试、可维护性要求高
因此,更适合一份:
- 简洁
- 可执行
- Go 风格强
- 约束明确
的 constitution.md
keywords: Go style, simplicity, tooling
三、原文核心思想总结
这份 constitution.md 的核心不是“写更多规则”,而是抓住 4 条真正影响代码质量的根原则:
- 简单性原则
- 测试先行铁律
- 明确性原则
- 单一职责原则
最后再加上:
- 治理条款
这是非常典型、非常成熟的工程约束结构。
四、适合直接落地的 constitution.md(整理版)
下面是按 Markdown 规范整理后的可复制版。
# issue2md 项目开发宪法
> Version: 1.0
> Ratified: 2025-10-20
本文件定义了本项目不可动摇的核心开发原则。所有 AI Agent 在进行技术规划和代码实现时,必须无条件遵循。
---
## 第一条:简单性原则 (Simplicity First)
**核心:** 遵循 Go 语言“少即是多”的哲学。绝不进行不必要的抽象,绝不引入非必需的依赖。
### 1.1 YAGNI
你不需要它(You Ain't Gonna Need It)。只实现 `spec.md` 中明确要求的功能。
### 1.2 标准库优先
除非有极其充分的理由,否则必须优先使用 Go 标准库。例如,Web 服务使用 `net/http`,而不是 Gin 或 Echo。
### 1.3 反过度工程
避免复杂设计模式。简单的函数和数据结构优于复杂的接口和继承体系。
---
## 第二条:测试先行铁律 (Test-First Imperative)
**核心:** 所有新功能或 Bug 修复,都必须从编写一个或多个失败的测试开始。
### 2.1 TDD 循环
严格遵循 `Red-Green-Refactor` 循环:
- 先编写失败测试
- 再让测试通过
- 最后进行重构
### 2.2 表格驱动测试
单元测试必须优先采用表格驱动测试(Table-Driven Tests)风格,以覆盖多种输入和边界情况。
### 2.3 拒绝过度 Mock
优先编写集成测试,使用真实依赖或 fake object(例如内存中的 GitHub API 模拟服务器),而不是过度依赖 Mock。
---
## 第三条:明确性原则 (Clarity and Explicitness)
**核心:** 代码的首要目的是让人类易于理解,其次才是让机器执行。
### 3.1 错误处理
**不可协商:**
- 所有错误都必须被显式处理
- 绝不允许使用 `_` 丢弃错误
- 错误传递时必须使用 `fmt.Errorf("...: %w", err)` 进行包装
### 3.2 无全局变量
绝不允许使用全局变量传递状态。所有依赖必须通过函数参数或结构体字段显式注入。
### 3.3 注释的意义
注释应该解释“为什么”,而不是“是什么”。所有公共 API 都必须有清晰的 GoDoc 注释。
---
## 第四条:单一职责原则 (Single Responsibility)
**核心:** 每个包、每个文件、每个函数都应该只做好一件事。
### 4.1 包的内聚
`internal` 目录下各个包应保持高内聚、低耦合。例如,`github` 包只负责与 GitHub API 交互,不能包含 Markdown 转换逻辑。
### 4.2 接口隔离
定义小而明确的接口,避免大而全的“上帝接口”。
---
## 治理 (Governance)
本宪法具有最高优先级,其效力高于任何 `CLAUDE.md` 或单次会话中的指令。任何计划(如 `plan.md`)在生成时,都必须首先进行“合宪性审查”。
五、逐条学霸式拆解
第一条:简单性原则 Simplicity First
核心含义
Go 社区非常强调:
- 直接
- 清晰
- 少抽象
- 少依赖
为什么它必须成为第一条
因为工具型 Go 项目最怕的就是:
- 明明一个函数能解决,非要分三层抽象
- 明明标准库够用,非要引入大型框架
- 明明需求很小,却提前设计一整套扩展体系
这一条在防什么
它主要在防:
- 过度设计
- 过早抽象
- 依赖膨胀
- 维护成本上升
记忆公式
简单性 = 只做需要的 + 用最直接的方式做
1.1 YAGNI
含义
不要实现未来“也许会用到”的功能。
工程意义
很多项目复杂,不是因为当前需求复杂,而是因为写了大量“预留”。
对 AI 尤其重要
AI 很喜欢“顺手补全”:
- 多设计几个接口
- 多做几层抽象
- 多预埋一些拓展点
这恰恰容易偏离真实需求。
keywords: YAGNI
1.2 标准库优先
含义
能用标准库就先别上第三方。
为什么是 Go 项目的强约束
Go 标准库非常强,尤其在这些方面:
net/httpcontextencoding/jsonerrorssynctesting
好处
- 少依赖
- 易维护
- 兼容性强
- 升级成本低
适用于 issue2md
如果 issue2md 是一个轻量工具,标准库优先几乎就是最优解。
keywords: stdlib, net/http
1.3 反过度工程
核心
不要为了“看起来高级”而复杂化。
典型错误
- 不必要的设计模式
- 过大的接口
- 过深的抽象层
- 为未来扩展提前拆太多模块
Go 社区倾向
Go 崇尚的是:
- plain code
- explicit code
- simple composition
keywords: over-engineering, abstraction
第二条:测试先行铁律 Test-First Imperative
这是整份宪法里最硬核的一条之一。
核心思想
不是“写完代码后补测试”,而是:
先用测试定义行为,再写实现
2.1 TDD 循环
三步
Red:先写失败测试Green:写最少代码让它通过Refactor:再整理代码
为什么重要
它会强迫开发者先想清楚:
- 输入是什么
- 输出是什么
- 边界在哪里
- 预期行为是什么
对 AI 的价值
AI 直接写实现时,容易:
- 漏边界
- 理解错需求
- 代码先行,规范滞后
而 TDD 会把行为先固定下来。
keywords: TDD, Red, Green, Refactor
2.2 表格驱动测试
为什么 Go 强推这种风格
因为 Go 的很多逻辑都非常适合用输入输出案例统一组织。
优势
- 容易扩展 case
- 可读性强
- 边界覆盖自然
- 结构统一
结论
这不是“偏好”,在 Go 项目里几乎可以视为测试默认风格。
keywords: table-driven
2.3 拒绝过度 Mock
这条非常有 Go 味
很多 Go 项目不鼓励到处造 mock。
为什么
过多 mock 会导致:
- 测试和真实行为脱节
- 重构成本升高
- 测试通过但系统不工作
更推荐什么
- 真实依赖
- fake object
- 内存实现
- 轻量集成测试
对 issue2md 的启发
如果要测试 GitHub API 交互,优先做一个内存/本地 fake server,而不是堆大量 mock expectation。
keywords: integration test, fake object, mock
第三条:明确性原则 Clarity and Explicitness
这条原则是 Go 风格的灵魂。
核心思想
代码首先要让人看懂。
Go 不追求炫技,追求:
- 显式
- 可读
- 可推断
- 易排错
3.1 错误处理
为什么是不可协商
Go 没有异常机制主导流程,错误处理就是程序可靠性的主通道。
三个硬要求
1)所有错误必须显式处理
不能假装看不见。
2)不能用 _ 丢弃错误
因为这会直接吞掉风险。
3)必须包装错误
例如:
if err != nil {
return fmt.Errorf("fetch issue failed: %w", err)
}
为什么包装错误重要
因为它保留了调用链上下文,排查时能知道:
- 哪一步失败
- 在什么业务语境失败
- 底层原始错误是什么
keywords: error, wrap, %w
3.2 无全局变量
为什么禁用
全局变量的问题:
- 状态不可控
- 难测试
- 易产生隐式依赖
- 并发下风险更大
Go 风格更倾向
通过:
- 构造函数
- 结构体字段
- 参数传递
来显式注入依赖。
本质
dependency 应该显式可见,而不是隐式漂浮
keywords: global state, dependency injection
3.3 注释解释“为什么”
为什么这条很重要
“这段代码做什么”,人通常看代码就能知道。
真正难的是:
- 为什么这么设计?
- 为什么不用另一种方案?
- 为什么这里要特殊处理?
所以注释应解释
- 设计意图
- 业务约束
- 边界原因
- 特殊处理理由
GoDoc 要求
公共 API 要有清晰注释,这是 Go 生态中非常标准的实践。
keywords: GoDoc, why
第四条:单一职责原则 Single Responsibility
核心
每一层只做自己该做的事。
为什么对 issue2md 尤其重要
这类项目很容易出现“图省事写在一起”:
- 拉 GitHub 数据
- 处理业务逻辑
- 生成 Markdown
- 输出文件
全部揉在一个包或一个函数里。
短期快,长期极难维护。
4.1 包的内聚
含义
每个包负责一个清晰边界。
例如:
github包:只管 GitHub API 交互markdown包:只管 Markdown 渲染app或service:组织流程
价值
- 易理解
- 易替换
- 易测试
- 易维护
keywords: cohesion, coupling
4.2 接口隔离
含义
接口要小而清晰,不要做成万能接口。
为什么
大接口会导致:
- 实现成本高
- mock 成本高
- 依赖不必要方法
- 结构臃肿
Go 的典型哲学
小接口 + 组合
而不是
超大抽象 + 继承体系
keywords: small interface
六、治理条款:这才是“宪法”成立的关键
为什么必须写治理
如果没有治理条款,前面规则只是“建议清单”。
而一旦写明:
宪法优先级高于
CLAUDE.md和临时对话
它就真正变成了项目级根规则。
“合宪性审查”是什么意思
任何计划、方案、代码改动,在开始之前,都应该问自己:
- 是否违反简单性?
- 是否从测试开始?
- 是否足够显式清晰?
- 是否职责清晰、边界明确?
如果不满足,就要先修正方案。
keywords: governance, compliance
七、如何在项目中使用这份宪法
1. 文件放置位置
可选:
- 项目根目录:
./constitution.md - Claude 配置目录:
./.claude/constitution.md
建议
如果你希望它更像项目正式规范,建议放根目录。
如果你更偏向 AI 协作配置,也可以放到 ./.claude/。
2. 在 CLAUDE.md 中导入
可以通过 @ 方式引入,例如:
# 项目协作说明
@./constitution.md
或者:
@./.claude/constitution.md
这样 AI 在处理任务时就能先读取宪法。
3. 使用策略
推荐组合方式:
constitution.md:写最高原则CLAUDE.md:写具体协作流程、工具命令、输出要求spec.md:写需求plan.md:写任务方案
最佳分工
constitution.md -> 规定不能违背什么
CLAUDE.md -> 规定平时怎么协作
spec.md -> 规定要做什么
plan.md -> 规定准备怎么做
八、学霸式总结
1. 这份 constitution.md 的精髓
它把 Go 项目最重要的工程哲学,压缩成了可执行的最高原则:
- 简单
- 测试优先
- 显式清晰
- 职责单一
2. 它的工程价值
它能有效防止 AI 生成以下问题代码:
- 过度抽象
- 依赖膨胀
- 不可测试
- 错误吞掉
- 全局状态污染
- 单文件大杂烩实现
3. 一句话记忆
constitution.md不是“开发建议”,而是 AI 在项目中必须遵守的最高法。
九、速记版
# 速记
## constitution.md 是什么
- 项目开发宪法
- 高于 CLAUDE.md
- 高于临时会话指令
## issue2md 的四大根原则
1. 简单性原则
2. 测试先行铁律
3. 明确性原则
4. 单一职责原则
## Go 风格关键点
- 标准库优先
- YAGNI
- TDD
- 表格驱动测试
- 少 Mock,多 fake / integration test
- 错误必须显式处理并 wrapping
- 禁止全局变量
- 注释写 why
- 小接口,低耦合,高内聚
## 本质
constitution.md = 项目最高开发规则