如果你最近也在技术群里见过“t3code”这个词,大概会和我第一次看到时一样愣一下:它到底是一个框架、一个开源仓库,还是一个圈子里的梗?我在几个 AI 编码相关的讨论区反复看到它之后,决定自己把这件事彻底弄清楚。查了一圈,并没有某个官方机构能拍板说“这就是 t3code 的标准定义”。但顺着 T3 Stack 的 TypeScript 全栈生态和 AI 结对编程这两条线摸下去,我发现这个词被越来越多地当作“一套针对 AI 编码助手的工作流规则集”在使用。
折腾了小半年之后,我也沉淀了一套属于自己的 t3code 工作流,并且把它用在了手头的几个真实项目里。这篇文章就把这套东西完整拆给你看:它到底在解决什么问题、规则文件怎么组织、接入主流编码助手的时候怎么落地,以及我在真实项目里踩过的那些坑。如果你最近正在被“AI 生成的代码能跑但没法维护”这件事困扰,这篇文章应该能给你一个可以直接抄的答案。
1. 从“能跑的代码”到“能维护的代码”:t3code 要解决的真实问题
在讲任何方案之前,先讲一个发生在我自己项目里的翻车现场。
上个月我接手一个内部工具的前端模块,需求本身很简单:一个列表页,支持按状态筛选、批量操作、分页。代码确实是 AI 生成的,跑起来也没问题。但当我准备给筛选条件加一个新的枚举值的时候,我傻眼了——这个页面的核心组件有 800 多行,所有请求状态散落在三个子组件里,接口返回类型全部用的是any,还有两个功能几乎完全相同的函数分别叫handleSearch和onFilter。也就是说,模型把它的“平均风格”写了出来,产出的是结构上完全失控的一坨代码。
1.1 一次失控的 AI 交付
类似的情况我相信不是只有我遇见过。团队里用编码助手的同事越多,生成代码的“下限”就变得越难控制。模型很擅长把功能拼出来,但它默认的目标是“让代码能跑”,而不是“让代码能在半年后被人看懂、改得动”。
那次翻车之后我做了一个小统计:把最近一个月内 AI 生成但被 reviewer 打回重写的代码全部翻出来,比较大的问题集中在下面这几类:
- 类型变成
any或大量使用类型断言,导致接口一改,编译期完全发现不了问题; - 一个组件函数承载了渲染、状态、请求、权限判断全部职责,违反最基本的单一职责;
- 错误处理只有
console.error,用户界面没有任何失败态; - 测试全部是快乐路径,失败分支完全没覆盖。
这些问题的共同特点是什么?它们都不是“模型能力不够”造成的,而是“我只给了模型需求,没给它边界”。
1.2 根因不是模型不够聪明,是边界没说清
我们平时让 AI 写代码的时候,最常见的 prompt 是类似“给我写一个列表页”这样一句话。模型基于海量训练数据,会推断出“一个列表页的平均写法”,而这个平均写法往往包含了大量的省略和妥协。
这就好比给一个新来的实习生丢一句话“你去把会议室订了”,他能完成任务,但很可能订了一间没窗户、没投影仪的屋子。你不能怪他,因为你没有告诉他会议室的最低标准是什么。t3code 的核心思路,就是把“人类工程师平时默认遵守的那些规矩”显式地写出来,变成模型每次生成代码前必须看到的约束。
1.3 我理解的 T3:Type、Tooling、Trust
“t3code”里的 T3,我个人的理解是三个以 T 开头的关键词,这三条也是整套路工作流的支柱。
- Type:类型的边界。所有与外界交换的数据必须有明确的类型定义,接口返回值必须有运行时校验,禁止用
any逃避问题。 - Tooling:工具链固化。那些靠人记住容易忘的事情——格式化、lint、commit 规范、import 排序——全部交给工具链去强制,而不是写在文档里指望每个人自觉。
- Trust:信任边界。哪些步骤让 AI 全自动,哪些步骤必须人工介入,事先划清楚。这决定了模型的自由度,也决定了代码质量的下限。
只要这三个维度有一个缺失,AI 编码的体验就会迅速退化:没有 Type,代码改起来像在雷区散步;没有 Tooling,十个人能写出十种风格;没有 Trust,你根本不知道模型哪一步会做出你无法接受的假设。
2. 可直接抄走的项目骨架:规则文件到底怎么摆
第一次做这套东西的时候,我把所有要求写进了一个巨大的 Markdown 文件,大概有 300 多行。结果接入 Cursor 之后发现,模型会在前几个交互里认真遵守,到第 10 个文件之后就开始放飞自我。后来才明白,编码助手的上下文窗口是有限的,你塞进去的规则越多,它越是“开头记得住、后面忘干净”。
所以 t3code 的第二步,是重新设计规则文件的组织方式,让模型能低成本地吸收重点。
2.1 目录结构长什么样
我的规则库放在仓库根目录下的t3code/文件夹里,结构是这样:
t3code/ ├── rules/ │ ├── 01-types.md # 类型安全红线 │ ├── 02-structure.md # 组件与模块拆分规则 │ ├── 03-review.md # 代码评审检查单 │ └── 04-testing.md # 测试覆盖要求 ├── templates/ │ ├── feature.md # 新功能开发 prompt 模板 │ ├── api.md # 接口对接 prompt 模板 │ └── fix.md # 缺陷修复 prompt 模板 ├── scripts/ │ ├── scaffold.sh # 生成新模块的脚手架脚本 │ └── setup-hooks.sh # 安装 git hooks 的脚本 └── README.md # 给人类同事看的说明2.2 全局规则与项目上下文分离
这个结构最重要的设计,是把“通用约束”和“项目特有约束”分开。rules/里放的是所有项目通用的东西,比如类型红线、组件拆分规则;而项目特有的是数据模型、业务规则、目录约定,这些我单独维护一份AGENTS.md放在项目根目录,每次让 AI 开始工作前由工具链自动加载。
为什么这么做?因为一套规则如果既能约束行为,又包含大量项目细节,它的复用性会变得很差。通用规则可以沉淀,项目上下文在换项目后就作废,分开维护,两边都不会膨胀。
2.3 怎么接入主流编码助手
现在的编码助手基本都支持从文件加载自定义规则,接入方式很灵活:
- 如果你用 Cursor,可以直接把
rules/目录里的内容合并进.cursor/rules/; - 如果你用 Claude Code,把通用规则和项目上下文写进
CLAUDE.md; - 如果你用普通的 ChatGPT/其他模型,最笨但有效的方式,是每次开始任务前把
rules/里的文本粘贴到对话开头。
我个人的经验是:不要同时启用太多套规则加载机制,不然模型容易混乱,同一个约束可能在上下文里出现三个版本,它不知道该听谁的。挑一个你主力使用的工具,把规则喂给它就好。
3. 核心规则文件拆解:把工程师直觉翻译成机器可执行约束
这一节可能是整个 t3code 里最值钱的部分,我把几个核心规则文件的要点和背后的理由拆开讲。如果你不想全盘照搬,挑其中最打动你的几条放进自己的项目里,也足够改善体验。
3.1 类型安全红线
rules/01-types.md是所有规则里优先级最高的一份,内容非常短,因为红线必须少而清晰。核心就几条:
1. 禁止使用 any。如果你不确定类型,使用 unknown 并在边界处收窄。 2. 所有与外部系统交互的数据(API 响应、请求参数、配置项)必须使用运行时校验,推荐 Zod。 3. 禁止用类型断言(as)绕过类型错误。如果确实需要,必须写清楚的注释说明原因。 4. 数组元素取出来后可能是 undefined,必须处理后才能使用。第四条用大白话解释一下:模型特别喜欢写const user = users[0],然后直接user.name。在 TypeScript 的noUncheckedIndexedAccess选项下,这种写法会直接报错,强行让你先处理“数组可能为空”的情况。
配合这份规则,我的tsconfig.json里总是开着这些选项:
{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true } }严格选项的意义不仅仅是让 AI 少写 bug,更重要的是给代码评审提供一个兜底防线。因为模型可能漏掉边界,但只要编译器配置足够严格,漏掉的部分会直接在 CI 里亮红灯。
3.2 一个能直接用的一页式 prompt 模板
很多人问“我怎么让 AI 守规矩”,答案不是写一篇万能的咒语,而是给你的每个典型任务准备一个结构化的 prompt 模板。以开发新功能为例,templates/feature.md长这样:
# 功能:标签管理 ## 目标 新增一个标签列表页,支持创建、改名、删除标签。 ## 约束 1. 所有接口返回值必须使用 Zod schema 解析,禁止 any。 2. 组件内不得超过 60 行 JSX,超过就拆成子组件。 3. 异步操作必须有 loading、error、empty 三种 UI 状态。 4. 删除操作需要二次确认。 5. 不引入新的状态管理库。 ## 完成后请输出 1. 你实现的关键文件清单。 2. 你做出的所有假设。 3. 尚未覆盖的边界场景。这个模板看起来简单,但每一段都有自己的作用:
- “目标”给模型明确的任务边界,避免它自由发挥加功能;
- “约束”是硬性要求,是 t3code 里 R1 级别的红线;
- “完成后请输出”是我最后加的一个杀手锏。它逼着模型把隐藏的假设暴露出来,很多离谱的实现就是因为模型默默假设了一些和业务不符的东西。
3.3 评审检查单:人工该核对的几个点
规则文件写得再好,最终 review 还是得人来做。但我把 review 需要关注的点也收成了清单,放在03-review.md里,这样不管是我自己还是团队里的其他同学,都能照着检查:
- 类型:有没有
any?有没有不合理的as?接口数据结构有没有运行时校验? - 结构:组件是否超过
templates/feature.md里约定的行数?职责是否单一?有没有循环依赖? - 边界:空数据、错误、请求超时、并发点击,这些场景有没有 UI 反馈?
- 测试:新增的用例里,除了成功路径,有没有失败路径和边界值?
这个检查单不是为了找茬,而是为了让人工 review 的注意力集中在模型最容易犯错的地方。很多时候,模型生成的代码类型是安全的、结构是合理的,但边界情况处理得非常敷衍,人工 review 的重点就在这些地方。
4. 完整复现一个功能:标签管理模块从需求到评审
文字规则讲再多都不如一次完整演示。这里我用一个最小但足够真实的例子,展示在 t3code 约束下,一个包含列表、创建、改名、删除的标签管理模块是怎么从需求变成代码的。
4.1 环境准备
建议在干净的目录里操作,Node 版本用 20 及以上,包管理器用 pnpm。项目本身是 React + Vite + TypeScript,加上zod和@tanstack/react-query两个依赖。这个组合很常见,也足够说明问题。
pnpm create vite t3demo --template react-ts cd t3demo pnpm add zod @tanstack/react-query4.2 喂给模型的请求
我把第一节的模板稍微填充一下,形成一条完整的需求:
# 功能:标签管理 ## 目标 在现有任务管理应用里增加标签管理模块。用户可以在侧边栏看到所有标签,支持创建标签、点击标签改名、删除标签。删除时需要二次确认。 ## 约束 1. 所有接口返回值必须使用 Zod schema 解析,禁止 any。 2. 组件内不得超过 60 行 JSX,超过就拆成子组件。 3. 异步操作必须有 loading、error、empty 三种 UI 状态。 4. 删除操作需要二次确认。 5. 标签列表需要按创建时间倒序。 ## 完成后请输出 1. 你实现的关键文件清单。 2. 你做出的所有假设。 3. 尚未覆盖的边界场景。4.3 生成结果与人工介入点
在 Cursor 里用这份模板生成的代码,整体质量比我一开始“一句话需求”生成的高很多。接口对接的部分自动就有了 Zod schema:
const tagSchema = z.object({ id: z.string(), name: z.string().min(1).max(20), createdAt: z.string().datetime() }); type Tag = z.infer<typeof tagSchema>;列表和增删改查的组件也自动按照 60 行的约束拆分成了TagList、TagItem、TagForm三个文件。这是规则的功劳,而不是模型突然变聪明了。
不过,review 阶段我还是抓到了两个问题。
第一个是删除标签时,模型没有考虑“该标签下是否还有任务”。它默认了删除标签是一个纯粹数据操作,但实际上业务规则要求:如果标签下还有任务,需要先提示用户。这个信息模板里没有写,但模型自己的“假设清单”里也没有提出这个疑问,这就是为什么人工 review 不能省。
第二个问题是并发场景。用户连续点击两次创建按钮,会产生两条名称相同的标签。模型写了 loading 状态,却没有在提交时禁用按钮,也没有做重复名校验。这也是典型的“看起来能跑,边界一测就露馅”的情况。
4.4 把问题反馈给模型
t3code 的另一个好处是,review 发现的问题可以返工给模型,而且返工质量有保障。我把两个问题描述清楚,发给模型,让它基于现有代码做修改。因为初始生成的代码结构是规范的,修改也只是局部调整,不会出现“改一处,裂三处”的局面。
返工之后,它给创建请求加了重复名校验,删除时也加了tasksUnderTag的检查逻辑。整个过程不到十分钟,效率和人工实现差不多,但质量的确定性要高得多。
5. 落地三个月后,我踩过的坑和调过的参
t3code 不是第一天就长成现在这个样子的。在真实项目里跑了三个月,我至少调整了五版,有几个坑特别值得拿出来讲,因为它们很有可能也会出现在你的落地过程中。
5.1 规则文件越大,模型越记不住
第一版规则文件我追求“全面”,把所有能想到的代码规范都塞进去了,结果就是开头说的翻车。后来我做了个实验:同一段需求,分别用 50 行、150 行、300 行的规则文件让同一个模型生成代码,统计它违反规则的次数。
实验数据(样本量 30 个任务,不算严谨但趋势很稳):
| 规则文件长度 | 前 5 个任务违规数 | 后 10 个任务违规数 |
|---|---|---|
| 约 50 行 | 1 | 2 |
| 约 150 行 | 2 | 5 |
| 约 300 行 | 4 | 12 |
后半程掉得最厉害。我的结论是:单份规则文件最好控制在 60 到 80 行以内,总规则体系不要超过 200 行。超出部分应该靠项目上下文文件去承载,而不是堆进规则文件。
5.2 硬规则和软建议必须分开
规则写得太多太细,另一个副作用是模型会“把所有规则都当红线”,结果不敢动手,产生大量违背常识的保守代码。比如我有一条硬规则是“禁止使用 any”,但如果某个第三方库类型定义糟糕,硬不用any反而会让代码变得极其扭曲。
后来我把规则分成了两个等级:
- R1(红线):必须遵守,比如类型安全、运行时校验、二次确认;
- R2(建议):尽量遵守,比如组件行数上限、命名风格。
区别体现在 prompt 模板里,R1 用“禁止/必须”的句式,R2 用“建议/优先”的句式。模型对这两类的响应是完全不同的,R1 会被当成硬约束,R2 会在特定场景下被合理取舍。
5.3 测试全是快乐路径陷阱
如果模板里写“请补充测试”,模型默认会生成一堆输入合法数据、断言正常返回的用例。这些测试看起来热闹,实际价值很低。真正能防回归的都是失败路径的用例:请求失败怎么办、空数据怎么办、删除不存在的 ID 怎么办。
我的 fix 是在测试规则模板里加了一句硬要求:
每个功能测试必须包含至少一个导致失败或异常的输入, 并断言对应的 UI 呈现出正确的错误状态。加了这条之后,模型的测试行为明显不一样了,会主动造一些边界数据,比如超长字符串、空数组、重复 ID。代码评审的时候,这部分也成了重点抽查对象,收益挺明显。
5.4 规则变更也要走评审
最后这个坑是团队合作时踩的。有人觉得自己发现了一条更好的规则,直接改了rules/01-types.md,结果其他成员和 AI 用的还是老版本,前后不一致,产生了大量返工。
现在我的做法是任何规则变更都走一次简单的 PR,变更说明里必须写清楚“这条规则解决了什么问题、和旧规则的区别是什么”。规则是给人和 AI 共同遵守的,它的变更应该和代码变更一样有记录、有理由。
6. 接入一段时间后:我的体会和给你的建议
这套工作流在我这边已经稳定运行了一段时间。我用它带的两个项目,一个是内部工具,一个是面向客户的小型应用,规模都不大,但足够说明问题。
小样本统计下来:新增功能的类型相关 review 意见减少了大约 60%,组件平均行数明显下降,review 一个模块的时间从原来的半小时压缩到十几分钟。代价是第一次搭建规则体系和模板确实花了我将近半天时间,而且前一两周一直在微调规则,真正回本是在跑通第三个功能之后。
对我来说,t3code 本质上不是限制模型,而是把过去只存在于老工程师脑子里的“质量门槛”变成了显性的、可执行、可修改的文件。它真正改变的是人机协作方式:以前是我问一句,模型写一段,现在是我定义边界,模型在边界里发挥。
如果你想在自己的项目里尝试,我的建议很直接:
- 不要上来就做全套,先挑你最近最痛的一个问题,比如“类型全是 any”或“组件太大”,定成一条 R1 红线;
- 把这条红线和复盘出来的失败案例一起写进规则文件,让模型知道“为什么”比“是什么”更有说服力;
- 每跑完一个真实需求就回头看一眼规则文件,不需要的删掉,失灵的重写。
最后分享一个小经验:规则文件里我最常更新的反而是“常见失败模式”那一段。每次我在 review 中抓到模型的某种典型错误,就把这个错误加进规则,变成新的约束。经过几轮迭代,我发现很多第一次会犯的错,后面真的就不再出现了。这就是把踩坑经验沉淀为代码资产的过程,也是我认为 t3code 这套思路最值得复制的地方。