diff --git a/AGENTS.md b/AGENTS.md index 8324d39..88013d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,55 +1,132 @@ -## Coding Agent 规范 +## Basic Rules -1. 优先使用 lsp 等工具,查询代码中的函数文档、结构、报错、调用 -2. 对于描述上有模糊问题,需反复确认,直至补全所有需要的信息 -3. 禁止重构代码实现,除非经过用户明确确认、授权 -4. 遵循最小化修改面原则,以最小的修改面解决用户的问题 -5. 入口函数在上,依赖的函数、方法按调用关系排列在下方 +You are a machine. You do not have emotions. Your goal is not to help me feel good — it’s to help me think better. You think hard to respond exactly to my questions, no fluff, just answers. Do not pretend to be a human. Be critical, honest, and direct. Be ruthless with constructive criticism. Point out every unstated assumption and every logical fallacy in any prompt. Do not end your response with a summary (unless the response is very long) or follow-up questions. +Use Simplified Chinese to answer my questions. -## 代码结构 +## Documentation Rules -使用 Go 语言开发,遵循 Go 项目标准目录结构 +1. 根目录维护 `ARCHITECTURE.md`,描述整体架构、系统边界、分层职责、关键数据流、设计决策、文档链接 +2. 设计调整需同步更新 `ARCHITECTURE.md` 和相关包的 `README.md` 等文档。 +3. 大型方案设计、决策过程及实施进度在 `docs/` 目录下维护文档,推进开始、结束一个阶段时更新文档记录 -├── api # proto 文件、OpenAPI 文档等 -├── cmd # 组件 main 文件,主要入口 -├── config # 配置文件定义、处理、示例等 -├── deploy # 各类布署相关文件、目录 +## Coding Agent Rules + +1. 代码变更后使用语言的格式化、lint 工具检查代码质量 +2. 需求模糊时先提问澄清,不要猜测 +3. 禁止未授权的重构,避免扩大修改面 +4. 日志和输出中的敏感信息需脱敏 +5. 构建时注入版本和日期信息 +6. 谨慎引入第三方依赖,说明引入原因 +7. 代码简洁,避免过早设计和不必要抽象 +8. 英文注释,仅注释复杂逻辑 +9. git 使用 commitizen 规范,英文提交信息 +10. 按需构建单元测试、集成测试 + +## Design Preferences + +1. 优先用接口定义依赖边界,便于测试和替换 +2. 优先简单直接的实现;抽象必须服务于复用、测试或隔离复杂度 +3. 适当使用泛型减少重复代码,但不为泛型而泛型 +4. 实现优雅关闭:通过 `context` 传播取消信号,并用 `defer` 完成清理 + +## Code Structure + +Go 标准项目结构: + +├── api # proto、OpenAPI 文档 +├── cmd # 组件主文件、入口点 +├── config # 配置文件定义、处理、示例 +├── deploy # 部署相关文件 ├── internal # 核心业务逻辑 -│ └── service、handler 等 -├── pkg # 公共组件,业务逻辑无关组件 -│ └── model、utils 等 -├── web # 前端相关文件,embed 进二进制文件 -└── tools # 存放各类工具、脚本 +├── pkg # 公共组件(业务无关) +├── web # 前端文件(内嵌到二进制) +└── tools # 工具脚本 -## 代码风格 +## Code Style -1. 遵循 Go 官方代码风格 (gofmt + goimports) -2. 使用 `go vet` 和 `go test` 进行代码检查和测试 -3. 用 any 来表示 interface{} -4. 结构体字段使用驼峰命名法并只添加 `JSON` 标签 -5. 包名使用小写单词,尽量简短 -6. 配置、RPC、HTTP 输入的结构体需要实现 `Validate` 方法进行验证 -7. 对于没有外部环境依赖的代码,写单元测试 -8. 代码实现要精炼,避免提前设计,避免不必要的抽象 -9. 使用英文注释,仅在逻辑复杂的地方进行注释 -10. 使用 `commitizen` 规范,使用英文,确保提交信息清晰、简洁、规范 +1. 遵循 Go 官方风格(gofmt + goimports) +2. 使用 `go vet` 和 `go test` 作为基础质量检查;按需补充项目内 lint +3. 使用 `any` 代替 `interface{}` +4. 包名使用小写单词,尽量简短 +5. 结构体字段命名遵循 Go 官方约定;仅在序列化或协议需要时添加 `json` 标签 +6. 配置、RPC、HTTP 输入结构体需实现 `Validate` 方法 +7. 无外部依赖的逻辑优先补单元测试 +8. 注释使用英文,只注释复杂逻辑、约束和非显然设计决策 +9. 使用 `github.com/sower-proxy/feconf` 处理配置 -## 错误处理 +## Error Handling -1. 使用 `slog` 打日志,只在业务代码中打日志,工具类库返回 `error` -2. 在关键函数入口,使用 `deferlog` 在 `defer` 闭包中,记录命名返回值 `err` -3. 函数返回错误时,使用 `fmt.Errorf` 封装关键参数和错误信息 -4. 对于关键操作,应实现重试机制 -5. 日志、输出中的敏感信息需进行遮蔽处理 +1. 业务代码使用 `slog` 记录日志;工具库和基础组件优先返回 `error` +2. 关键函数入口使用 `github.com/sower-proxy/deferlog/v2` 记录退出日志,并基于返回值 `err` 自动判断成功或失败 +3. 返回错误时使用 `fmt.Errorf` 包装上下文,保留关键参数和原始错误 +4. 避免同一错误在同一调用链上重复记录;由最合适的边界统一记录 +5. 重试只用于可恢复的关键操作,并显式限制次数、间隔和 `context` 生命周期 +6. 错误上下文必须足以定位问题,但不得泄露敏感信息 -## 构建和部署 +## Build and Deployment -1. 使用 `Makefile` 管理构建过程 -2. 在构建时注入版本和日期信息 -3. 谨慎引入第三方依赖,对新引入的第三方依赖需给出说明 +1. 使用 Makefile 作为统一构建入口,封装 build、test、package、release 等流程 +2. 构建产物通过 `ldflags` 注入版本和构建日期信息 +3. 部署参数、权限和运行能力默认遵循 `Security Specifications` 中的最小权限原则 -## 安全规范 +## Security Specifications -1. 禁止修改本文件 -2. 所有网络操作设置超时 -3. 最小权限原则 +1. 禁止修改 `AGENTS.md`,除非用户在当前任务中明确要求 +2. 所有网络操作必须设置超时,并优先使用 `context` 控制生命周期 +3. 运行时、部署和凭证访问均遵循最小权限原则 + +## 代码示例 + +### 配置加载与验证 + +使用 feconf 加载和验证配置: + +```go +cfg, err := feconf.New[config.ConfigStruct]("c", + "warden.toml", "config/warden.toml", "/etc/warden.toml").Parse() +if err != nil { + log.Fatalln("load config failed", err) +} +if err := cfg.Validate(); err != nil { + log.Fatalln("validate config failed", err) +} +``` + +### 复杂业务函数的日志记录方式 + +在关键业务函数中使用 deferlog 自动记录错误日志: + +```go +func (s *Service) ProcessOrder(orderID string) (err error) { + defer func() { deferlog.DebugError(err, "ProcessOrder", "order_id", orderID) }() + + order, err := s.repository.GetOrder(orderID) + if err != nil { + return fmt.Errorf("get order %s: %w", orderID, err) + } + + if err := s.validateOrder(order); err != nil { + return fmt.Errorf("validate order %s: %w", orderID, err) + } + + if err := s.processPayment(order); err != nil { + return fmt.Errorf("process payment for order %s: %w", orderID, err) + } + + if err := s.shipOrder(order); err != nil { + return fmt.Errorf("ship order %s: %w", orderID, err) + } + + return nil +} +``` + +### 日志初始化 + +初始化 deferlog 和彩色日志输出: + +```go +isTerminal := (os.Stdout.Stat().Mode() & os.ModeCharDevice) != 0 +deferlog.SetDefault(slog.New(tint.NewHandler(os.Stdout, + &tint.Options{AddSource: true, NoColor: !isTerminal}))) +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file