AI工程化开发工作流实战:从TypeScript约束到CI自动化测试的完整搭建指南
最近在研究 Matt Pocock 的一些教学思路时我突然意识到一个很关键的问题大家缺的并不是更多 AI 工具而是一套能把 AI 真正塞进开发流程里的工程化方法。Matt Pocock 在 TypeScript 领域强调类型安全、可复用模式和渐进式改造这套思路放在 AI 工程化开发工作流上一样成立。我花了两个周末从零开始搭了一套完整工作流把 AI 编码、规则约束、类型检查、自动化测试全部串起来。这篇文章就记录一下整个搭建过程、核心环节的决策理由以及踩过的一些坑希望能给正在摸索 AI 工程化的朋友一些参考。开头先交代清楚这是一篇面向开发者的实战记录涉及 AI 辅助编码、Agent 工作流、TypeScript 工程实践、CI 集成等内容。无论你是在个人项目里尝试还是想在公司内部推动 AI 落地这篇文章里都会找到可以直接抄作业的部分。文章不会停留在AI 很厉害这种层面我会把每一步的实际操作、参数选择、为什么这样做全部讲透。1. 先把方向理清楚什么是 AI 工程化开发工作流1.1 为什么不是会用 AI 写代码就够了很多人觉得 AI 编程就是打开对话窗口把需求丢进去复制代码出来。这种用法在写脚本、做 Demo 的时候没问题但一旦进入真实项目你会发现各种问题AI 生成的代码风格和项目不一致、类型定义过于宽泛、隐含的业务逻辑错误、改一处引发另外三处报错。这不是 AI 能力不够而是缺少工程化的约束环节。工程化的本质不是让 AI 更聪明而是让 AI 的输出变得可预期、可控、可维护。Matt Pocock 在 TypeScript 教学中最常强调的一点是类型系统是开发者的约束工具它能在一开始就把很多错误挡在门外。这个思路迁移到 AI 工作流中就是用规则文件、任务模板、静态检查、自动化测试这些硬约束把 AI 从自由发挥的聊天模式拉回到生产级的开发节奏里。1.2 工程化 AI 工作流的三条核心原则我搭建这套工作流时给自己定了三条原则后面所有技术选型都围绕它们展开。原则一AI 生成只是起点不是终点。所有 AI 输出的代码必须经过类型检查、Lint、测试三重关卡合格才能进入主分支。原则二上下文比模型更重要。同一个模型给足项目背景、代码风格、约束条件后输出质量会有质的提升。工程化工作流要解决的就是如何系统性地提供这些上下文。原则三流程必须可复用。一次成功的生成不值得兴奋能沉淀成模板、规则、脚本让下次项目继续受益才是工程化。这三条原则帮我做出了一个关键决策工作流的核心不只在于选哪个 AI 助手或模型更在于如何构建一套连接需求输入—AI生成—自动校验—人工确认的管道。这个管道就是整篇文章要搭建的东西。1.3 这套工作流适合谁、解决什么问题如果你符合下面几种情况中的任何一种那么这篇文章会有实际价值团队准备引入 AI 编程助手但担心代码质量失控需要一个质量兜底的方案。个人项目里已经使用 AI 辅助开发但总感觉 AI 参与度和产出质量不稳定想体系化地提升。想在公司内部搭建一套包含 AI 生成、审查、发布的完整流水线但不知道从哪里下手。对 Agent、工作流平台、提示词工程有兴趣想看看这些概念如何落地到一个具体的开发场景。简单说这篇文章提供的是一套在全人工编码和全自动 AI 生成之间的中间路线。它不会让你丢掉对代码的控制权也能让 AI 承担起大部分重复性编码工作。2. 工具与底座选型搭建 AI 开发环境的几个关键决策2.1 模型选择通用大模型与代码专用模型的取舍开始之前先想清楚一个问题你的工作流需要哪些 AI 能力是纯代码补全还是需要理解业务需求后生成完整模块不同的需求对应不同的模型选择。我个人的建议是分两层配置。第一层是 IDE 内的实时补全模型这类轻量级模型主打低延迟在你写代码时给出下一段建议我用的是基于代码专门训练过的模型第二层是对话/Agent 型模型负责理解完整需求、生成多文件改动、解释复杂逻辑这种任务需要更强大的推理能力我选择的是当前主流的多模态大模型。两者分工明确既保证了 $ \text{速度} $也兼顾了 $ \text{深度} $。这里有一个容易踩坑的点很多人只用一个聊天窗口就试图完成所有事。实测下来补全场景用重型模型会明显感觉到卡顿而复杂的代码重构任务用轻量补全模型又总是答非所问。分层配置虽然初期要多花一点时间但长期收益非常明显。2.2 Agent 与工作流平台什么时候需要什么时候不必热词里频繁出现 Dify、n8n、Coze 这些工作流平台不少朋友也在纠结到底要不要引入。我的判断标准很简单看你的任务是否涉及多个 AI 步骤或外部系统协调。如果只是AI 生成代码 人工检查完全不需要工作流平台直接配置好 IDE 和命令行工具就够了。如果任务涉及需求拆解 → 代码生成 → 自动测试 → 生成变更说明 → 创建 PR每个环节有独立逻辑且需要条件判断、循环、状态管理那引入一个轻量级 Agent 框架或工作流平台是值得的。如果你的场景还包括外部工具调用比如查询数据库、调用内部 API、更新工单系统那 Agent 化的需求就非常明显直接在 Cursor、Claude Code 或类似的 Agent 式工具中编写专用脚本和工具比用可视化拖拽平台要灵活得多。我在这次实践中选择了一个折中方案不单独引入可视化工作流平台而是用 Agent 式编程工具配合自建的 Shell 脚本和 CI 流水线把 AI 生成、代码检查、测试执行串联起来。原因是开发场景的流程相对固定用代码表达流程比图形化拖拽更精确也更容易放进 Git 仓库做版本管理。2.3 规则文件与上下文管理AI 工作流的灵魂这是整篇文章最想强调的部分。AI 生成代码质量不稳定的最大原因往往不是模型不够强而是项目上下文没有有效传递给模型。Matt Pocock 常强调类型即文档而在 AI 工程化实践中规则即上下文。我做的第一件事是在项目根目录创建了一个AGENTS.md文件里面写了项目简介和技术栈语言、框架、包管理器。目录结构说明告诉 AI 不同模块的代码应该放在哪里。代码风格约定命名习惯、组件写法、TS 严格模式要求。常用命令安装依赖、运行测试、构建、Lint。禁止事项不要修改哪些文件、不要引入哪些依赖。实测效果非常直接把AGENTS.md加进去之后AI 生成的代码风格一致性明显提升基本不再出现把 Vue 组件写进 React 项目、在 Node 端使用浏览器 API 这类低级错误。很多工具都支持项目级规则文件除了AGENTS.md还包括CLAUDE.md、.cursorrules等建议根据工具选择并保持文件不冲突。3. 从零到一五步搭建设计可落地的 AI 工程化工作流3.1 第一步先建目录结构与规则文件搭工作流之前我先整理了一个标准的项目骨架。借用 Matt 的教学风格我会先把项目的类型定义清楚——也就是目录结构、入口文件、模块边界、规则文件全部固定下来。这一步花的时间不长但能让后续所有 AI 生成的代码都有所归属。我使用的目录结构大致如下my-ai-project/ ├── src/ │ ├── modules/ # 业务模块按功能拆分 │ ├── shared/ # 共享的类型、工具函数 │ └── index.ts # 入口 ├── tests/ │ ├── unit/ # 单元测试 │ └── integration/ # 集成测试 ├── .ai/ │ ├── rules.md # AI 专用规则与提示词片段 │ ├── templates/ │ │ ├── feature.md # 新功能开发的任务模板 │ │ ├── fix.md # Bug 修复任务模板 │ │ └── refactor.md # 重构任务模板 ├── AGENTS.md # 项目上下文说明 ├── package.json ├── tsconfig.json └── ....ai/templates目录是我比较得意的设计。每个任务模板是一份结构化的提示词里面规定了 AI 需要输出的内容、必须遵循的约束、需要补充的测试用例。比如feature.md的核心结构是需求背景与验收标准涉及的模块与函数签名类型定义要求必须是严格模式不允许any需要生成的测试用例清单代码完成后的自检清单这样一来每次让 AI 干活的时候不是从空白对话开始而是把一个已经填好的模板丢给它。模板就是给 AI 的类型定义它会约束 AI 的输出范围和质量标准。3.2 第二步定义任务模板与提示词体系下面是feature.md模板的核心内容我稍微脱敏后贴出来供参考。这一段看起来很短但每个字段都有实际用途。角色你是一名资深全栈工程师熟悉 TypeScript 和 Node.js 生态。 任务背景[在这里粘贴需求描述尽量包含业务上下文] ## 功能要求 1. 输入描述函数/接口的输入参数 2. 输出描述期望的返回值 3. 边界条件列出需要处理的异常情况 ## 工程约束 - 所有类型必须显式定义禁止使用 any - 函数必须编写 JSDoc 注释 - 不允许引入新的运行时依赖 - 代码风格遵循项目 ESLint 规则 ## 交付物 - 完整代码 - 对应的单元测试 - 一段变更说明说明改动文件与影响范围 ## 自检清单代码完成后逐一确认 - [ ] 是否通过 TypeScript 类型检查 - [ ] 是否通过 ESLint - [ ] 是否补充了关键用例 - [ ] 是否更新了相关文档这套模板运行下来的效果让我很意外。AI 的代码一次性通过类型检查和测试的比例从原来的不到一半提升到了七八成省下了大量来回纠错的时间。3.3 第三步用类型安全与静态检查守住质量底线Matt Pocock 最出名的理念就是类型到位Bug 无处可藏。我在工作流里把这一条贯彻得相当彻底。首先是 TypeScript 的tsconfig.json严格配置以下是我在项目里使用的关键项{ compilerOptions: { strict: true, noImplicitAny: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true, exactOptionalPropertyTypes: true } }strict模式打开后AI 生成的代码只要有类型问题立刻就会被编译器挡住根本走不到代码审查环节。noUnusedLocals和noUnusedParameters看起来是小配置但对 AI 输出非常有用因为它会强制 AI 只生成真正被用到的变量和参数减少冗余代码。其次是 ESLint 配置。我直接沿用了项目原有的规则集没有为 AI 单独放水。AI 生成的代码如果 Lint 不过同样会被直接打回。这里有个小技巧在任务模板中把通过 ESLint列为交付条件AI 会在生成时就主动遵循大多数规则而不是等到报错再去修复。3.4 第四步自动化测试与 CI 接入一个工作流如果没有自动化测试闭环就不能叫工程化。我的做法是每次 AI 生成一个功能模块模板里会强制它一并生成对应的单元测试。测试框架用的是 Vitest原因是它和 TypeScript 的搭配最自然配置也最轻。下面是命令行的调用方式把这些环节串成一条流水线# 本地执行链路 npx tsc --noEmit npx eslint src/ tests/ npx vitest run每一次 AI 生成完代码后我会按顺序跑这三个命令。只有全部通过代码才允许进入暂存区和提交。更进一步我在 CI 中加入了一条流水线使用 GitHub Actions 示例来做演示name: ai-workflow-check on: [pull_request] jobs: quality-check: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: npm ci - run: npx tsc --noEmit - run: npx eslint src/ tests/ - run: npx vitest run这条 CI 流水线把 AI 参与开发的代码和人工开发的代码放在同一个质量门下评审杜绝了AI 生成的代码要求放低一点的心态。从实际效果看这条门禁至少挽救了两个差点合入主分支的类型问题。3.5 第五步复盘沉淀积累可复用的 AI 资产工程化最大的价值就是沉淀。我在每个迭代周期结束后会做一个固定动作把这次 AI 生成过程中出现的问题和有效的提示词修改都记录下来。记录方式有两种。一种是直接在.ai/rules.md中追加新的规则比如不要在类型定义中生成函数实现、生成 API 调用代码时必须包含错误处理。另一种是维护一份失败案例集把 AI 生成出错的完整对话或代码片段存在.ai/failures/目录下每周复盘一次。这些沉淀出来的规则和案例比任何外部教程都更贴合自己的项目。随着时间推移AI 生成代码的质量会越来越稳定因为每一次踩坑都会变成规则文件里的一条新约束。这就是 Matt 那套用类型系统让错误变少的思路在 AI 时代的延伸。4. 实操过程与核心环节实现一个功能模块的 AI 工程化之旅4.1 实际需求与初始上下文准备光说理论不够直观我用一个实际功能来演示整套工作流的运行方式。假设要在某个 Express 项目中新增一个用户注册接口需要包含参数校验、用户创建、密码加密、异常处理。按照流程我先在.ai/templates/feature.md中填写需求内容然后把下面这段上下文粘贴到 AI 对话中项目是一个 Express TypeScript 的服务端应用。 数据库通过 Prisma 访问用户模型已经存在字段包括 id、email、passwordHash、createdAt。 密码加密统一使用 bcryptjs不要使用 bcrypt。 所有接口返回结构统一为 { code: number, data: unknown, message: string }。 入参校验使用 zod在路由层完成校验后再进入 service 层。这样一段上下文能把 AI 直接带到项目的真实环境里它给出的代码不会再是泛泛的教程代码而是符合当前项目约定的实现。4.2 AI 生成代码与人工审查的配合方式AI 给出的代码大致如下这是当时它返回的重要内容我做了精简import { z } from zod; import bcrypt from bcryptjs; import { prisma } from ../lib/prisma; export const registerSchema z.object({ email: z.string().email(), password: z.string().min(8).max(64), }); type RegisterInput z.infertypeof registerSchema; export async function registerUser({ email, password }: RegisterInput) { const existing await prisma.user.findUnique({ where: { email } }); if (existing) { throw new Error(EMAIL_ALREADY_EXISTS); } const passwordHash await bcrypt.hash(password, 10); return prisma.user.create({ data: { email, passwordHash }, }); }这份代码完成度相当高类型推导正确校验和错误处理都到位。但重点在于它只是起点。我必须把它放进工作流链路里跑一遍看类型检查、Lint、测试是否全部通过。这一步我称之为AI 生成不等于完成AI 生成等于待检。4.3 类型检查、Lint 和测试跑起来之后发生了什么执行三个命令后确实还是发现了问题。类型检查和 Lint 没问题但有一个测试用例没写到位。template 里要求补充用户已存在时抛错的用例AI 生成的是一个简单的重复调用断言对错误类型没有做验证。我手动补了一个更严谨的用例it(should throw when email already exists, async () { vi.mocked(prisma.user.findUnique).mockResolvedValue({ id: 1, email: ab.com, passwordHash: hash, createdAt: new Date(), }); await expect(registerUser({ email: ab.com, password: 12345678 })) .rejects.toThrow(EMAIL_ALREADY_EXISTS); });这个例子很好地说明了工程化工作流的价值AI 能解决 90% 的常规编码但边界条件的测试意识仍然需要人来补位。工作流保证了这个补位动作成为固定的、不可跳过的环节而不是靠心情。4.4 CLI 工作流脚本与全自动衔接为了让整个流程更顺手我还写了一个简单的 Node 脚本run-workflow.mjs把生成任务 → 执行检查 → 汇总结果做成一个完整的 CLI 工具。我简化后的逻辑大致是import { execSync } from node:child_process; const steps [ { name: TypeScript, cmd: npx tsc --noEmit }, { name: ESLint, cmd: npx eslint src/ tests/ }, { name: Unit Tests, cmd: npx vitest run }, ]; let failed false; for (const step of steps) { try { execSync(step.cmd, { stdio: inherit }); console.log([PASS] ${step.name}); } catch { console.error([FAIL] ${step.name}); failed true; break; } } if (failed) { console.error(Workflow blocked. Fix issues before committing.); process.exit(1); } else { console.log(All checks passed. Ready to commit.); }这个脚本让我可以在终端里一键跑完整个质量链路不需要手动敲多次命令同时也方便挂到 Git 钩子里。整个实操环节跑通以后AI 参与开发的体验发生了质的变化它更像一个配合你执行任务的智能协作者而不是一个光说不练的聊天窗口。5. 常见问题与排查技巧实录5.1 AI 生成的类型定义太宽泛到处都是 any这是遇到最频繁的问题。AI 在拿不准类型时会倾向于使用any来绕过类型检查表面上代码能跑实际上把类型防线给拆了。我的处理方法是双管齐下。一方面是配置约束noImplicitAny: true已经能挡住显式any再结合 ESLint 的typescript-eslint/no-explicit-any规则把any从源头掐断。另一方面是在任务模板里写明禁止使用 anyAI 在生成时就会更积极地推导类型或主动询问类型信息。5.2 生成代码出现幻觉依赖AI 偶尔会生成一个并不存在的包或函数或者引用一个并不在package.json里的依赖。这种问题单靠人眼看很难发现很可能直到 CI 执行时才暴露。建议在 CI 中加入一个依赖一致性检测步骤。简单做法是安装依赖后执行npm ls --depth0如果有未声明依赖会直接报错也能帮助发现问题。另外任务模板中那句不允许引入新的运行时依赖在执行层面就变得特别有效。5.3 上下文窗口被无用信息塞满响应质量下降对话型 AI 的上下文窗口是有限的。当你把一堆堆代码文档粘贴进对话AI 会越来越分心回复质量和相关性都下降。这是工程化实践中常被忽视的问题。我的做法不让 AI 直接读整个项目而是建立一个索引文件里面只有每个模块的关键信息比如文件路径、导出的核心类型、主要函数签名。每次对话只提供索引中相关页面的信息而不是贴一整个源码文件。这样上下文利用率高生成准确性也高。5.4 AI 生成的测试用例质量偏弱覆盖不到边界条件具体表现是测试用例能跑通但断言过于宽松或者是只测了快乐路径一遇到异常分支就没有用例了。应对方案是在任务模板的自检清单中增加一条至少包含 2 个错误分支测试。这一条很有效因为 AI 在生成过程中会自行检查输出是否满足清单要求。另外我也会在审查测试时多加一层把关通过阅读测试来反推 AI 对业务的理解是否准确。5.5 辅助排查速查表我用一个表格把当前常见的场景整理出来方便工作中快速对照场景典型表现排查方向推荐做法类型推断失败大量any、报错堆叠上下文是否缺少类型定义在上下文中追加类型定义片段逻辑死角等值判断写反、数组越界测试用例是否覆盖边界强制补充边界分支测试重复代码相似逻辑散落在多个函数中是否给 AI 说明了现有工具函数在规则文件中列出可复用函数异步处理错误Promise 未处理、竞态条件上下文是否说明异步偏好模板中设置异步处理规范依赖引入异常使用不存在或过期的包是否明确禁止新增依赖CI 加入依赖一致性检测6. 个人经验与扩展思路6.1 这套工作流还能往哪些方向扩展搭建完这套 AI 工程化开发工作流之后我试着把同样的思路迁移到其他场景发现也是成立的。比如内容生产方向很多人提到的工作流生成书单、AI 漫剧工作流本质上和代码生成是一回事定义清楚输入输出格式用模板约束 AI 的行为再加上一层人工审核就成了一个可复用的生产流水线。我最近还在尝试把类似的方法用在简历筛选上把 JD岗位描述和简历丢给 AI让它按照评分规则打分然后输出结构化报告。规则文件和模板同样起到了关键作用AI 的评估稳定性明显高于直接问这个人合适吗。另一个方向是专利相关场景里的 AI 辅助检索和文档生成本质上也是把非结构化输入技术方案描述转化为结构化输出检索式、文档草稿严格约束上下文和输出格式配合审核闭环就是标准的工作流思路。这里的合规要求更高但工程化约束的方法论是通用的。6.2 我踩过坑之后的几句真心话说到底工具和模型会不断更替今天新出的 Agent 框架明天可能就迭代掉。但用约束构建流程、用流程沉淀经验、用经验反哺产出这套工程化思路在很长一段时间内都是适用的。我自己最大的体会是不要急着追新工具先把流程搭起来。哪怕你现在使用的只是一个很普通的 AI 编程助手只要你坚持做规则文件、任务模板、自动检查这三件事产出质量很快就会超过那些频繁换工具但完全不做工程化的人。最后分享一个小细节现在每次让 AI 干活之前我会设置一道三秒思考题——我提供的信息是否足够模板是否完整输出如何校验这三个问题想清楚了AI 生成的效果基本都不会差。这套工作流以后我还会继续迭代但骨架已经稳定接下来就是在各个场景里填肉了。