让 Codex 一口气把一个完整前端生成出来,第一眼效果确实唬人:页面有了、按钮能点了、列表也出来了。但等你真正开始接手,问题会一个接一个冒出来——样式和逻辑混在一个巨型文件里、状态管理随手 new 一个全局变量、测试一个都没写、构建脚本还是脚手架自带的默认值。你越是让它接着改,它越是把代码搅成一锅粥。
我自己踩过这个坑之后,现在的做法完全反过来了:不让 Codex 当“全栈独狼”,而是让它当一个“有分工的团队”。具体手段就是给它配 5 组 Skills,把页面、逻辑、测试、构建这些职责彻底拆开,每一步都在明确边界内工作。这篇文章就把这套打法的完整思路、Skills 写法和实操流程全部拆给你看。
1. 先回答一个问题:为什么不能让 Codex 一口气写完
1.1 一次性生成的最大问题不是代码,是认知断点
很多人以为“让 AI 一口气写完”省事,其实只是把成本延后了。代码生成的那一刻很爽,但项目不是靠“生成出来”就能运行的,它要靠人理解和维护。Codex 在一次超长上下文里连续生成几十个文件之后,前面的设计意图基本就丢了——它只记得自己刚刚写了什么,但记不得“为什么这么写”。
举个例子,我让 Codex 一次性生成过一个带筛选、排序、分页的表格页面。它确实把所有功能都写出来了,但用的是三个useState加一堆内联函数,数据请求直接写在组件里,没有任何 hook 抽象。等我想加一个导出功能时,发现业务逻辑完全没法复用,只能把整块代码抽出来重构。这还只是一个小页面,如果是整个前端项目,后果可想而知。
另一个更隐蔽的问题是一致性。一次性生成多个文件时,Codex 经常会“自创”一些共享类型、工具函数或接口命名,但这个定义散落在不同文件里,前后版本可能还不一致。你用 A 文件里的User类型,它却在 B 文件里定义了一个UserInfo,字段还不一样。这种问题靠肉眼很难全部发现,跑起来才报错,回头排查成本极高。
1.2 拆分的正确姿势:不是按文件,而是按“工作边界”
所以关键不是“少让 Codex 写代码”,而是“每次只让 Codex 干一类活”。人写代码也是这么分工的:先设计页面结构,再写业务逻辑,再补测试,最后配构建。AI 也一样,只不过我们需要用 Skills 给它划出清晰的边界。
我的划分方式是 5 组 Skills,每组的职责边界如下:
| Skill 名称 | 职责范围 | 不负责什么 |
|---|---|---|
| ui-architect | 页面结构、组件拆分、样式、响应式 | 不写数据请求、不写状态管理 |
| logic-owner | 业务逻辑、hooks、状态流转、数据接口适配 | 不写 DOM 和样式 |
| test-suite | 单测、组件测试、端到端测试用例 | 不改业务实现 |
| build-pipeline | 构建配置、类型检查、Lint、产物优化 | 不写业务代码 |
| gatekeeper | 代码评审、兼容性检查、变更记录 | 不直接改大逻辑 |
这样拆完,Codex 每一次的工作目标都非常收敛:生成页面时它不用想数据从哪来(只用约定好的接口),写逻辑时不用纠结长什么样(只关心状态和数据),写测试时不会顺手去改实现。这个模式,本质上就是把一个“全栈工程师 AI”拆成了“一个前端小组”。
2. 开工之前:把全局规则和上下文喂给 Codex
2.1 AGENTS.md 先定“法律”,再谈具体开发
Skills 解决的是“这一类活怎么干”,但 Codex 还需要知道“这个项目本身有什么规矩”。这一步靠项目根的AGENTS.md(有些工具叫CODEX.md,原理一样)。这个文件要做的不是写作文,而是把所有 Codex 需要“默认遵守”的规则钉死。越具体越好,写清楚也不会伤感情。
我最基本的AGENTS.md长这样:
# 项目指令 ## 技术栈 - 前端:React 18 + TypeScript 5 + Vite 5 - 样式:CSS Modules,禁止使用 Tailwind - 状态:Zustand,禁止使用 Redux - 测试:Vitest + React Testing Library - 包管理:pnpm ## 目录约定 - 页面组件放 src/pages/ - 业务组件放 src/components/ - hooks 放 src/hooks/ - API 相关放 src/api/ - 页面内一次性组件就近存放 ## 开发命令 - 安装依赖:pnpm install - 启动开发:pnpm dev - 类型检查:pnpm typecheck - 运行测试:pnpm test - 构建:pnpm build ## 编码红线 - 不要新增没用的依赖 - 不要写内联样式(特殊情况除外) - 组件默认使用函数组件,禁止 class 组件 - 类型定义必须使用 interface,不要用 type 定义对象这段规则看着简单,但价值巨大。它保证了不管哪个 Skill 在执行任务,Codex 都默认按统一标准产出代码。如果你不做这一步,就会出现 ui-architect 写出来的组件用了 Tailwind,logic-owner 写的状态管理却想引入 Redux,最后整个项目风格割裂。
2.2 Skills 目录结构与命名约定
Skills 本质上是一组带说明文档的指令文件。我沿用的是社区里比较通用的目录约定:在项目根目录建一个.codex/skills/目录,每个 skill 一个子目录,里面必须有一个SKILL.md做入口。目录结构大致如下:
项目根目录/ ├── AGENTS.md ├── .codex/ │ └── skills/ │ ├── ui-architect/ │ │ ├── SKILL.md │ │ └── references/ │ │ └── component-patterns.md │ ├── logic-owner/ │ │ └── SKILL.md │ ├── test-suite/ │ │ └── SKILL.md │ ├── build-pipeline/ │ │ └── SKILL.md │ └── gatekeeper/ │ └── SKILL.mdSKILL.md的开头建议写清楚几个字段:name(技能名)、description(什么时候用)、when_to_use(什么时候不该用)、steps(执行步骤)。description 一定要写得像给搜索引擎看的摘要,因为 Codex 读取 Skills 的时候很依赖这段描述来决定是否启用它。我之前吃过亏,描述写得模糊,结果我明明要生成页面,它却去调了 logic-owner,产出一堆 hooks 文件,页面结构完全没动。
2.3 把脚本和依赖也统一“喂”给它
还有一个容易忽略的点:Codex 默认情况下并不知道你的项目有哪些命令和依赖。如果你不在 AGENTS.md 里写清楚依赖管理用pnpm、测试工具是 Vitest,它很可能默认用 npm 或 Jest。一旦生成的文件里带package.json或配置文件,后面就非常被动。
所以我在 AGENTS.md 里除了写命令,还会把关键依赖的版本都列出来,甚至注明“新增依赖必须经我手动确认”。这样 Codex 在测试或构建时就会去用现有工具链,而不是自己再造一套轮子。这个细节看起来很小,但实际能挡住很多坑。
3. 五组 Skills 的职责拆解和写法
3.1 第一组:ui-architect,只管页面长什么样
ui-architect是前端的“门面担当”,负责页面结构、组件拆分、样式、响应式布局。这组 skill 的核心要求是:不要碰数据请求,不要碰状态管理。UI 组件需要数据时,用 props 或约定好的 hooks 接口占位即可。
一个精简版SKILL.md模板:
--- name: ui-architect description: 用于生成页面结构、React 组件、样式与响应式布局。当需要从零搭建 UI、拆分组件、调整视觉表现时使用。 when_to_use: 创建新页面、重构组件结构、修复样式问题 when_not_to_use: 修改业务数据流、编写 API 请求、调整状态管理逻辑 --- # UI 架构与实现 ## 执行步骤 1. 先阅读 AGENTS.md,确认技术栈和目录规范。 2. 根据需求拆分组件层级,一个组件文件只做一个核心功能。 3. 使用 CSS Modules 编写样式,禁止 Tailwind 和内联样式。 4. 所有数据展示通过 props 传入,不在组件内直接请求接口。 5. 输出文件清单和组件关系说明。 ## 产出约束 - 每个组件必须声明 props 的 interface。 - 列表渲染必须有 key,且 key 不使用数组 index。 - 通用组件放 components 目录,页面一次性组件就近创建。实际使用的时候,我会在 prompt 里加一句“请列出组件拆分的理由”,这样我能看出它是不是真的理解了页面结构,而不是机械地切块。ui-architect 最重要的产出不是代码,而是组件结构——结构对了,后面的逻辑和样式调整都好办。
3.2 第二组:logic-owner,把数据和业务状态管起来
logic-owner负责的是业务逻辑、自定义 hooks、状态管理、数据请求和接口适配。它和 ui-architect 是天然搭档:ui 组件只负责“长得好看”,logic-owner 负责“背后怎么运转”。
我给它写的 SKILL.md 核心约束包括:所有数据请求走src/api/下的统一封装,不直接在页面组件里fetch;状态管理用 Zustand,且 store 要按业务领域拆分;业务逻辑尽量抽象成 hooks,让组件保持薄。同时,每个 hook 都要返回清晰的类型定义,方便 ui-architect 那边用起来不迷糊。
这组 skill 还有一个任务:把接口数据“翻译”成页面需要的结构。比如后端返回的字段是user_name,页面里想用userName,这个映射逻辑就应该放在 logic-owner 层,而不是让页面组件去处理。做到这一点,后面换后端字段、加缓存、做权限控制,都只需要动 hooks 或 store,不用波及 UI。
3.3 第三组:test-suite,把测试补成“安全网”
测试这组 skill 的定位是“监督者”。它负责读现有代码,然后生成单元测试、组件测试、交互测试。我要求它遵循几个原则:测试文件与被测文件同目录,命名统一为xxx.test.ts(x);测试里不 mock 自己写的 hooks,而是真实调用(除非涉及外部 API);每个测试只验证一个行为;测试用例的描述必须是“用户语言”,而不是“实现语言”。
为什么强调这一点?因为 Codex 写测试最容易犯的毛病是“为了覆盖而覆盖”——给每个函数写一堆断言,但测的全是内部实现,实际上一点保护作用都没有。比如它测一个addTodo函数,断言的是“调用了 setTodos”,而不是“点击添加按钮后列表出现新条目”。这种测试对重构毫无帮助,改一下实现就全红了。我在 SKILL.md 里专门写了一条:优先测行为,不测实现细节。
3.4 第四组:build-pipeline,让构建和工程化跑起来
build-pipeline负责工程化相关的脏活累活:构建配置、TypeScript 类型检查、ESLint 规则、环境变量管理、产物优化。这组 skill 不需要频繁调用,但一旦调用就要确保整个项目“可交付”。
它的 SKILL.md 我会写得偏“检查清单化”,比如:执行pnpm typecheck确认无类型错误;执行pnpm build确认产出成功;检查dist/产物大小是否异常;确认构建产物中无console.log残留;检查是否能部署到静态服务器预览。此外还要求它把 CI 里的命令写成一行一个,方便我复制到流水线里。
这里有个实操技巧:build-pipeline 的 output 写成“结论 + 日志摘要”模式,让它告诉我“构建通过”“类型检查通过”“产物体积为 230KB,比上次增加 12KB”这种结论,我只关心结果和异常。它给出原始日志太长了,反而不好排查。
3.5 第五组:gatekeeper,质量的最后一道闸门
gatekeeper是五组里最特殊的一个,它不做“创作”,只做“评审”。它需要读一遍改动过的代码,按一套规则挑毛病:有没有不合理的any、有没有重复代码、有没有未使用的变量、props 传递是否过大、组件是否过度复杂、有没有引入不必要的依赖。
我用它来替代“人工 code review 的前置过滤”。每次 Codex 干完活,我先让 gatekeeper 看一遍,把明显的问题打回重做,再自己人工 review。这比直接人肉检查高效太多。它的 SKILL.md 核心是让它输出“问题清单 + 建议修改方案 + 影响范围”,而不是直接改代码。直接让 AI 又评审又修改,很容易引入新的问题,因为它的“判断脑”和“生成脑”切换容易出错。
4. 完整实操:把一个“用户管理页”拆给 Codex
4.1 初始化项目与全局配置
我假设你要做一个“用户管理页”,新仓库,技术栈 React + TS + Vite + Zustand + Vitest。第一步先把项目初始化和 AGENTS.md 写好,然后再让 Codex 介入。
pnpm create vite codex-split-demo --template react-ts cd codex-split-demo pnpm install pnpm add zustand pnpm add -D vitest @testing-library/react @testing-library/jest-dom然后按第 2 节的内容写好 AGENTS.md。这里的关键是让你的项目从一开始就有“规矩”,而不是先让 Codex 自由发挥,后面再驯服它。
4.2 用 ui-architect 生成页面骨架
配置好之后,第一条指令我给 ui-architect:
请使用 ui-architect skill,实现“用户管理页”。 需求:左侧为筛选区(按姓名、状态筛选),右侧为用户表格,顶部有新增按钮。 要求:先输出组件拆分清单,再生成代码。Codex 读取 ui-architect 之后,会先给出一个类似这样的拆分方案:
UserManagementPage ├── UserFilterForm(姓名输入框、状态下拉) ├── UserTable(表格展示、分页、排序) └── CreateUserButton(触发新增弹窗)随后生成对应组件。这个阶段生成出来的表格里没有真实数据,只用 props 接收users和loading。UI 层不关心数据从哪来,这正是我想要的。
4.3 用 logic-owner 把数据和状态接上
页面骨架生成之后,让 logic-owner 干活:
请使用 logic-owner skill,为“用户管理页”补齐数据流。 要求:封装 useUserList hook,处理筛选、分页、排序;用 Zustand 管理用户列表状态;API 请求放在 src/api/user.ts。它生成的核心 hook 大概长这样(示意):
export function useUserList() { const { list, loading, fetchUsers } = useUserStore(); const [keyword, setKeyword] = useState(''); const [status, setStatus] = useState<UserStatus | ''>(''); const [page, setPage] = useState(1); useEffect(() => { fetchUsers({ keyword, status, page }); }, [keyword, status, page]); return { list, loading, keyword, setKeyword, status, setStatus, page, setPage }; }有了这个 hook,ui-architect 之前写的占位 props 就能改成真实的数据流。此时再让 ui-architect “小改”一下页面,把 props 替换成 hook 返回值,就能把两端接起来了。注意,这里我给的是两次独立调用,而不是让一次对话连续完成所有事,这样 Codex 在每一阶段都保持清晰上下文。
4.4 用 test-suite 补测试
页面和数据流都通了,接下来让 test-suite 写测试:
请使用 test-suite skill,为 UserManagementPage 和相关 hooks 编写测试。 要求:覆盖筛选交互、分页、空状态、加载状态;用 Testing Library 模拟用户操作。Codex 会生成类似这样的测试:
describe('UserManagementPage', () => { it('输入姓名关键字后,表格只展示匹配用户', async () => { render(<UserManagementPage />); fireEvent.change(screen.getByPlaceholderText('请输入姓名'), { target: { value: '张三' }, }); expect(await screen.findByText('张三')).toBeInTheDocument(); expect(screen.queryByText('李四')).not.toBeInTheDocument(); }); });如果测试跑不过,我一般不会让 test-suite 去改业务代码,而是把失败信息反馈给 logic-owner 或者 ui-architect,让他们改完再回来跑测试。这两组的边界必须分清楚,不然 test-suite 一会儿改测试、一会儿改实现,最后你都不知道代码为什么变绿。
4.5 用 build-pipeline 做交付前验证
最后一步,让 build-pipeline 收尾:
请使用 build-pipeline skill,执行完整的交付前检查:typecheck、lint、build、测试,并输出结论和产物信息。Codex 会执行命令并汇报结果。如果中间有失败,就根据报错内容打回给对应 skill 修复。等这一轮全绿,代码才能算“完成”。现实里很多 AI 生成的代码就是死在构建这一步——组件导出来是undefined、类型对不上、CSS Modules 的interface没导出,这些都要靠构建验证兜住。
4.6 用 gatekeeper 做最后评审
构建通过不代表代码质量 OK。我最后会让 gatekeeper 看一下整体 diff:
请使用 gatekeeper skill,评审本次“用户管理页”的全部改动。 重点关注:类型安全、重复代码、组件复杂度、props 传递合理性、是否有不必要的依赖。 输出格式:问题清单(按严重程度排序)+ 每条的修改建议。这一轮经常能抓出一些“能跑但不优雅”的问题,比如列表 key 用了 index、hooks 里塞了多个不相干功能、any偷偷出现。没过 gatekeeper 的代码,我会让对应 skill 修完再走一遍构建验证。
5. 常见问题与排查技巧实录
5.1 Codex 调用时本地通道报错、provider 校验不过
这是我自己用 Codex 时遇到过比较烦的问题:调用到一半,客户端直接报一段codex endpoint /responses相关的错误,后面跟着provider字样,任务中断。第一次遇到还以为是网络不稳定,重启了好几回,后来才发现是本地服务通道状态的锅。
排查思路一般分三步:第一,检查本地服务端口是否正常启动,有没有被杀掉;第二,重启客户端让配置重新加载;第三,检查 provider 端的 API Key 或 baseUrl 是否写错、是否过期。这里要特别提醒,如果你配置了第三方模型服务,模型切换后旧会话的连接信息可能失效,最稳妥的办法是新起一个会话再继续。
5.2 Skills 不生效,指令总是被忽略
很多人配置了 Skills 但发现 Codex 根本不调用,十有八九是 description 写得太泛。比如你写“用于前端开发”,那它根本不知道该在什么时候启用;相反,如果你写“当需要生成页面组件、拆分 UI 结构时使用,不要用于数据逻辑”,命中率会高很多。
另外确认一下文件路径是不是项目根目录下被 Codex 默认扫描到的地方。有时候你放在src/下面或者放错层级,工具根本读不到。还有一个常见问题是 SKILL.md 的 frontmatter 格式不标准,字段名写错了,解析器没法识别。我一直用name / description / when_to_use / when_not_to_use这套结构,暂时没出过问题。
5.3 上下文溢出和“重复劳动”
Codex 在超长对话里会变笨,最常见表现是同一个需求反复改、越改越乱。我的应对措施是把任务拆得更小,一次会话只处理一个 skill 范围内的一个明确目标。比如“实现用户管理页”这种指令还是太大,可以继续拆成“先实现表格组件”“再实现筛选表单”。
另外,建议每完成一个阶段就做一次 commit。这样即使 Codex 后面改崩了,也能快速回退到上一个稳定版本,而不是在它的错误思路上反复纠缠。我的经验是:Context 省着用,Codex 的状态才稳。该开新会话就开新会话,不用觉得浪费。
5.4 测试组和逻辑组“打架”
测试失败的原因经常不是测试写错,而是生产代码行为变了。比如 logic-owner 把fetchUsers从“同步返回”改成了“异步加载”,导致 test-suite 里原有的断言超时。这时候不要手动改测试掩盖问题,正确的流程是:看改动是不是预期行为,如果是,就让 test-suite 按新行为更新测试;如果不是,就让 logic-owner 修复实现。
这套流程走顺之后,测试组和逻辑组会形成一种“你写实现、我补断言”的良性循环,前提是别让两边跨边界改代码。
5.5 构建环境差异
本地构建过了,CI 却挂了,这种问题在 AI 生成代码的项目里特别常见。原因多半是 CI 环境的 Node 版本、包管理器版本和本机不一致,或者某个依赖是 AI 自动加的、你没手动确认,结果锁文件和 package.json 对不上。build-pipeline的检查清单里建议加一条:检查 Node 版本声明、检查packageManager字段、确认 pnpm-lock.yaml 已提交。这些小细节能省掉大量“本地能跑、线上必挂”的尴尬时刻。
6. 最后分享一点我自己的实践体会
这套五组 Skills 的流程跑下来,我最明显的感觉是:Codex 从“一个容易上头的大聪明”变成了“一个可以按节奏协作的同事”。它不再一口气给你整一堆看似完整、实则脆弱的代码,而是每一步都给出边界清晰、可校验的产物。我踩过几次坑之后已经习惯了这种节奏:UI 出来后先看结构,逻辑接完先试用,测试写完先跑一遍,构建全绿才合代码——每一步都有验证节点,每一步翻车都在可控范围内。
如果你现在的项目还没用过 Skills,我建议不要一上来就配五组,太重的体系反而让你不想维护。先把test-suite或build-pipeline单独拎出来用,感受一下“边界如何约束 AI 发挥”,再逐渐把页面、逻辑、评审各组补齐。等你真的习惯了这种“拆开来干活”的方式,你会发现 Codex 写前端的上限比自己瞎指挥高不少,而你作为开发者,也终于能把精力放在真正需要判断力的事情上,而不是追着它的烂摊子到处救火。