Codex 漏项目特定约定:6 个原因 + 「规则 + canonical example」模板

Codex 用 `getUserById`,但你的项目用 `findUserById`——每条约定都要在 AGENTS.md 里配一个 canonical example 文件。

Codex 生成的代码能跑,但”感觉”不像你的项目。命名用 get* 但你们用 find*;错误处理 throw string 但你们 throw 类型化的 AppError;测试用 expect(x).toBe(y) 但你们的风格是 assert.strictEqual(actual, expected)。Review 起来很别扭——不是错了,而是形状错了。

约定从几个文件 sample 里看不出来,必须显式化——以「规则 + 具体例子」的形式让 Codex 能读到。AGENTS.md 是合同,指向 canonical example 文件就是落实。

常见原因

按命中率从高到低:

1. 约定根本没写下来

团队上季度在群里说定 find* 表示可能返回 null、get* 表示必须存在或 throw。Codex 看不到群——也没人把这条写进文件。它就按训练里先看到的那个挑。

如何判断:问团队”这条约定写在哪儿”——没人指得出来文件,Codex 也找不到。

2. 约定写了但 Codex 没读

STYLE.md 存在,但 Codex 的 read 范围只扫 src/。除了 AGENTS.md/CLAUDE.md/README.md 之外的名字经常被跳过。

如何判断:把 STYLE.md 改名 AGENTS.md(或合并进去),Codex 现在遵守了——就是文件名问题。

3. Codex 套了”网红”模式不是你的

团队用 kebab-case 的 testid(data-testid="user-card-name"),Codex 生成 camelCase——Storybook 文档里 camelCase 更常见。约定输给 meme。

如何判断:新代码更像某个流行博客的模式而不像你现有代码——lint / test / 命名这类最常见。

4. AGENTS.md 写了规则但没给例子

“用团队的错误模式”——什么模式?Codex 需要一个具体例子来抄——一行规则不够。

如何判断:AGENTS.md 某节有标题但下面没代码块也没文件指针。光规则没例子的章节经常被忽略。

5. 项目里约定本身就不一致

一半代码用 findUserById,一半用 getUserById(迁移没做完)。Codex 抽到哪个就用哪个,没明确 canonical 就是随机输出。

如何判断grep -c "findUserById" src/ && grep -c "getUserById" src/ 都非 0——Codex 没办法挑对,因为根本没”胜方”。

6. 约定靠 linter 强制但 Codex 没跑 lint

约定确实被编码了——.eslintrcnaming-convention: camelCase——但 Codex 写完代码没跑 lint。问题到 CI 才暴露。

如何判断:Codex patch 后跑 pnpm lint path/to/new-file.ts——有报错就是它绕过了 lint 编码的约定。

最短修复路径

按收益从高到低,前 3 步覆盖最常见情况。

Step 1:每条约定 = 规则 + canonical example

别只写规则,配一个 Codex 能读的真实文件:

## 命名约定

- `find*` 表示可空查找(返回 `T | null`)。
  例子:`src/services/user/findUserById.ts`
- `get*` 表示必有查找(缺则 throw)。
  例子:`src/services/user/getUserById.ts`

## 错误处理

所有 throw 必须 extend `src/lib/errors.ts``AppError`
例子:`src/api/billing/handlers.ts:42`

## 测试风格

用 vitest + `assert`**不**`expect`)。
canonical:`src/services/user/user.test.ts`

Codex 实际抄的是 example 文件,规则只是导航。

Step 2:task prompt 里指向 example

加 `getOrgById` 查找。
完全照 `src/services/user/getUserById.ts` 的样子:
- 函数形状一样
- 错误 throw 风格一样
- 测试布局一样(见 `getUserById.test.ts`)

文件 + 测试一起生成。

指向一个真实文件比任何文字描述都有效。

Step 3:形状错了拒收 + 重 prompt 时点 canonical

Codex 形状错了:

你的输出用了 `expect(x).toBe(y)`——那是 jest 风格。
本 repo 用 `assert.strictEqual(actual, expected)`。
canonical 见 `src/services/user/user.test.ts`。

按 canonical 风格重生成测试。

每次拒收都是给当前 session 训练,1-2 次纠正后剩下的就稳了。

Step 4:可视化标记列成必需产出

data-testid、ARIA、自定义 decorator 这种,列成必需输出:

新组件必须包含:
- 所有可交互元素上 `data-testid="<kebab-name>"`
- icon-only 按钮加 `aria-label`
- `<Component>.stories.tsx` Storybook story 文件

缺任何一项都拒收。

Step 5:把 lint 加进 verifier

Lint 编码了很多 Codex 绕过的约定,把它写进”完成”:

写完后跑:
  pnpm eslint <new-files> --max-warnings 0

任意 warn/error 都修,修完再说完成。

ESLint config 严格点(naming-conventionimport/order、自定义规则),很多约定就自动强制了。

Step 6:Monorepo 每包一份 AGENTS.md

不同包可以有不同约定,不要强用一份根规则。每包加:

apps/web/AGENTS.md        → Next.js App Router 约定
packages/ui/AGENTS.md     → 组件库约定
packages/db/AGENTS.md     → Prisma + repository 模式约定

Codex 按”从被编辑文件向上找最近一份”取规则。

预防建议

  • AGENTS.md 每条约定都配一个具体 example 文件——光规则的章节会被忽略
  • 项目内不一致先解决(钦定一个胜方),再指望 Codex 跟约定
  • 能用 ESLint/Biome/Prettier 编码的尽量编码——linter 强制 AGENTS.md 描述
  • Monorepo 每个 package 一份 AGENTS.md,最近的那份生效
  • 形状错了上线,当成文档缺口而不是 Codex 失败——补规则 + example
  • 每季度让 Codex 按项目风格写一段,看输出有没有漂移——漂了就刷新 doc

相关阅读

标签: #Codex #Coding Agent #排查 #排查 #约定