首页
/ ECC 的 Cursor Go 编码风格规则:让 AI Agent 写出地道 Go 代码的约束设计

ECC 的 Cursor Go 编码风格规则:让 AI Agent 写出地道 Go 代码的约束设计

2026-09-06 17:55:54作者:晏闻田Solitary

在 ECC(agent harness 性能优化系统)的 Cursor 规则体系中,golang-coding-style.md 是一份面向 Go 语言的编码风格约束文件。它通过 Cursor 规则的 frontmatter 元数据声明匹配范围(**/*.go**/go.mod**/go.sum),在 Agent 编辑 Go 代码时自动生效,强制 gofmt/goimports 格式化、约束接口设计尺度,并要求所有错误都带上下文包装。读完本文,你能掌握 ECC 规则文件的三层结构(frontmatter 匹配机制、风格条款、技能引用链),并理解这套规则如何与 Hook 自动格式化和 golang-patterns 技能联动,从而在 AI 辅助开发中保持 Go 代码的地道性与一致性。

规则文件的位置与触发机制

ECC 将 Cursor 的 Agent 行为约束组织在 .cursor/rules/ 目录下,按「通用规则 + 语言规则包」两层结构组织。Go 相关的规则包共 5 个文件:

golang-coding-style.md 的完整 frontmatter 如下:

---
description: "Go coding style extending common rules"
globs: ["**/*.go", "**/go.mod", "**/go.sum"]
alwaysApply: false
---

三个字段各自承担明确职责:

字段 取值 含义
description Go coding style extending common rules 向 Agent 说明该规则是"对通用编码规则的 Go 扩展",暗示存在一份更底层的通用规则需要一并遵守
globs **/*.go, **/go.mod, **/go.sum 只有当会话涉及这些 glob 匹配的文件时,规则才被注入上下文;Go 模块文件(go.mod/go.sum)也纳入范围,意味着依赖变更同样受此风格约束
alwaysApply false 该规则不做全局常驻,按需触发,避免无关语言场景下的 token 浪费

对比同目录下的 common-coding-style.md,其 alwaysApply: true——通用编码风格规则始终生效,而语言专属规则由 globs 精准激活。这正是 ECC 规则系统的核心设计:语言特定规则在与通用规则冲突时覆盖通用规则,且只在触达对应文件时才占用上下文预算

这一打包逻辑在跨 Harness 同步脚本 scripts/sync-ecc-to-codex.sh 中得到了印证:该脚本把 5 个 golang-*.md 规则文件组合成 ecc-rules-pack-golang.md 提示包,供 Codex 等其它 Harness 使用,并明确写有 "Language-specific guidance overrides common rules when they conflict"(语言特定指导在冲突时覆盖通用规则)。

三条核心条款逐一解析

格式化:gofmt 与 goimports 是强制项

原文条款只有一句话,但态度明确:

gofmt and goimports are mandatory -- no style debates

ECC 把格式化问题从"团队讨论项"降级为"无讨论空间的强制项"。这里的工程考量是:在 AI Agent 参与编码的场景中,如果风格允许争议,Agent 会在不同轮次产出不同风格的代码,而 gofmt/goimports 提供了确定性——任何 Agent 产出的 Go 代码都必须通过同一格式化标准。配套的 golang-patterns 技能 中的 Go 工具链章节给出了对应的实操命令:

# Formatting
gofmt -w .
goimports -w .

# Static analysis
go vet ./...
staticcheck ./...
golangci-lint run

并且推荐了完整的 .golangci.yml 配置(启用 errcheckgosimplegovetineffassignstaticcheckunusedgofmtgoimports 等 linter),使"no style debates"落地为 CI 可校验的具体配置。

设计原则:接受接口,返回结构体;接口保持 1-3 个方法

原文给出两条设计准则:

  1. Accept interfaces, return structs(接受接口,返回具体结构体)
  2. Keep interfaces small (1-3 methods)(接口保持小而精,1 到 3 个方法)

这两条与 golang-patterns 技能 中的"Interface Design"章节互为表里。技能文件给出了更完整的正反例:

// 好:接受接口参数,返回具体类型
func ProcessData(r io.Reader) (*Result, error) {
    data, err := io.ReadAll(r)
    if err != nil {
        return nil, err
    }
    return &Result{Data: data}, nil
}

以及"在消费方而非实现方定义接口"的做法:

// 在 consumer 包定义接口,provider 包不需要知道它的存在
package service

// UserStore defines what this service needs
type UserStore interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

Cursor 规则目录下的 golang-patterns.md 则用更短的篇幅复述了同一条原则("Define interfaces where they are used, not where they are implemented"),并补充了构造函数式依赖注入的示例:

func NewUserService(repo UserRepository, logger Logger) *UserService {
    return &UserService{repo: repo, logger: logger}
}

从这种"规则文件短、技能文件长"的分层结构看,ECC 的做法是:golang-coding-style.md 只保留不可协商的红线(Agent 每次编辑 Go 文件都看到),而把丰富的模式库放在按需激活的 golang-patterns 技能里,由 Agent 在"写代码、Review 代码、重构、设计包结构"等场景下完整加载。

错误处理:永远用 %w 包装上下文

原文给出的核心条款:

if err != nil {
    return fmt.Errorf("failed to create user: %w", err)
}

关键点有两个:

  • 必须 wrap:错误向上传递前,用 fmt.Errorf 加上"当前层做了什么"的上下文;
  • 必须用 %w 而非 %v%w 保留底层错误,使得调用链上的 errors.Is / errors.As 仍能穿透包装识别哨兵错误与自定义错误类型。

golang-patterns 技能 对此做了纵深展开,给出了配置加载的多层包装示例:

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }
    // ...
    return &cfg, nil
}

并配套了错误判定的标准手法:哨兵错误(ErrNotFoundErrUnauthorized)用 errors.Is 判定,领域自定义错误(如带 Field/Message 字段的 ValidationError)用 errors.As 提取。同时明确"Never Ignore Errors"——空标识符丢弃错误是反模式,确需忽略时要用 _ = writer.Close() 这类显式写法表明这是尽力而为的清理操作。

这三条错误处理条款与通用规则 common-coding-style.md 中 "ALWAYS handle errors comprehensively... Never silently swallow errors" 形成呼应:通用规则定原则,Go 规则给出语言级的落地语法(%w 包装)。

它扩展的通用编码规则

golang-coding-style.md 的开头注明 "This file extends the common coding style rule",所指的 .cursor/rules/common-coding-style.md 是一份 alwaysApply: true 的通用风格基线,包含五组约束:

  1. 不可变性(CRITICAL):永远创建新对象而非原地修改。理由是避免隐藏副作用、便于调试、支持并发安全。在 Go 语境下,这条主要约束 Agent 不要随意修改共享 slice/map,而是返回新的副本。
  2. 文件组织:"MANY SMALL FILES > FEW LARGE FILES"——单文件典型 200-400 行、最多 800 行,按 feature/domain 而非类型组织。
  3. 错误处理:每一层显式处理,UI 层给友好信息,服务端记录详细上下文。
  4. 输入校验:在系统边界校验所有外部输入,快速失败(fail fast)。
  5. 代码质量检查清单:函数小于 50 行、文件小于 800 行、嵌套不超过 4 层、无硬编码值、无原地修改。

也就是说,当 Cursor Agent 在一次会话中编辑一个 .go 文件时,实际生效的约束 = 通用基线(常驻)+ golang-coding-style(glob 触发)+ 其它按需加载的 Go 规则/技能。这种叠加而非替换的结构,保证了风格约束既不遗漏基线、又保留语言特异性。

规则的运行时落地:Hook 自动格式化

风格规则如果只是"写在纸上的约束",Agent 输出后仍可能出现格式漂移。ECC 用 Cursor Hooks 把"mandatory"真正变成自动化动作。

.cursor/hooks.json 注册了 afterFileEdit 事件钩子:

"afterFileEdit": [
  {
    "command": "node .cursor/hooks/after-file-edit.js",
    "event": "afterFileEdit",
    "description": "Auto-format, TypeScript check, console.log warning, and frontend design-quality reminder"
  }
]

对应的 .cursor/hooks/after-file-edit.js 在每次文件编辑后把文件路径经 adapter.js 转换后,转发给 post-edit-accumulator.js(累计编辑路径、在 stop 时机批量做 format + typecheck)与 post-edit-console-warn.js 等下游 Hook。从源码结构看,Go 文件编辑后会被纳入这个"攒批格式化"流程——即 golang-coding-style.md 中 "gofmt is mandatory" 的执行者不是 Agent 的自觉,而是编辑后 Hook 管道。

此外,golang-hooks.md 补充了 Claude Code 侧的静态检查配置建议:在 ~/.claude/settings.json 中为 .go 文件配置 PostToolUse Hook,编辑后自动执行 gofmt/goimportsgo vet(静态分析)、staticcheck(扩展静态检查)。两条 Hook 通道(Cursor 的 afterFileEdit、Claude Code 的 PostToolUse)共同保证了同一套风格规则在不同 Harness 下都有执行器。

深层知识源:golang-patterns 技能

golang-coding-style.md 的 Reference 一节指向 golang-patterns 技能,即 skills/golang-patterns/SKILL.md。该技能是规则文件之外的完整 Go 知识库,其触发时机明确为"写新 Go 代码、Review Go 代码、重构 Go 代码、设计 Go 包/模块",内容远厚于规则文件本身,涵盖:

核心原则

  • 简单胜于聪明(Good/Bad 对比:直接返回 vs IIFE 式过度精巧)
  • 零值可用(Countersync.Mutex 零值即可用;map 字段零值为 nil 会 panic,属于反例)
  • 接受接口、返回结构体

并发模式:Worker Pool(sync.WaitGroup 收拢)、context.WithTimeout 超时控制、signal.Notify 优雅停机、errgroup 协调多 goroutine、避免 goroutine 泄漏的 select 写法(case ch <- data: case <-ctx.Done():)。

包组织:标准项目布局(cmd/internal/handler|service|repository|configpkg/api/testdata/)、包命名(短、小写、无下划线、不冗余加 Service 后缀)、避免包级全局状态(用依赖注入的 NewServer(db *sql.DB) 替代 var db *sql.DB + init())。

结构体设计:Functional Options 模式(type Option func(*Server) + NewServer(addr, opts ...Option),与 golang-patterns.md 规则文件中的示例一致)以及嵌入(embedding)实现组合。

内存与性能:已知容量时 make([]Result, 0, len(items)) 预分配、sync.Pool 复用高频分配的 bytes.Buffer、循环内字符串拼接改用 strings.Builder 或直接 strings.Join

Go 惯用法速查表(节选自技能文件):

惯用法 说明
Accept interfaces, return structs 函数接受接口参数,返回具体类型
Errors are values 错误是一等值,不是异常
Don't communicate by sharing memory 用 channel 协调 goroutine
Make the zero value useful 类型无需显式初始化即可用
A little copying is better than a little dependency 避免不必要的外部依赖
Clear is better than clever 可读性优先于精巧
Return early 先处理错误,让主路径保持不缩进

反模式清单:长函数裸返回(naked return)、用 panic 做控制流、把 context.Context 塞进 struct 而非作为首参、值/指针接收者混用。

在自身项目中的配置方式

如果你的项目希望复用这套规则,可以直接参考仓库内的组织方式(仓库只读,以下为配置方式说明):

  1. 放置规则文件:在 Cursor 项目中创建 .cursor/rules/ 目录,放入 golang-coding-style.md(内容即上述 30 行规则文件)。关键是保留 frontmatter 的 globs 字段,否则规则会失去按文件类型触发的能力。
  2. 配套通用基线:同时提供一份 alwaysApply: true 的通用编码风格文件(可参考 common-coding-style.md 的五组约束),让语言规则"扩展"而非"替代"它。
  3. 接上 Hook 执行器:在 .cursor/hooks.json 中注册 afterFileEdit 钩子做自动格式化(参考 .cursor/hooks.json.cursor/hooks/after-file-edit.js 的事件结构);若使用 Claude Code,则按 golang-hooks.md~/.claude/settings.json 配置 gofmt/goimports、go vet、staticcheck 的 PostToolUse 钩子。
  4. 可选的知识层:将 skills/golang-patterns/SKILL.md 作为技能文件纳入项目,供 Agent 在编写/Review/重构 Go 代码时加载,补足规则文件未覆盖的并发、包组织与性能细节。
  5. 跨 Harness 分发(可选):若同时使用 Codex,可用类似 scripts/sync-ecc-to-codex.sh 的脚本,把 golang-coding-style.md 等 5 个 Go 规则文件打包成 ecc-rules-pack-golang.md 提示文件,保持"通用规则打底、语言规则冲突时覆盖"的语义。

小结

golang-coding-style.md 只有 30 行,却体现了 ECC 约束 AI 编码的完整方法论:用 glob 精准触发控制上下文成本,用三条不可协商的红线(强制格式化、接口设计尺度、错误必须带 %w 包装)压缩 Agent 的风格自由度,用 Hook 把"强制"变成自动化动作,再用 golang-patterns 技能承载可展开的深层惯用法。对 Go 项目的 AI 辅助开发而言,这套"短规则 + 长技能 + 自动执行器"的三层结构,是保证 Agent 产出的 Go 代码与 gofmtgo vetstaticcheck 工具链结论一致的务实方案。

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