SDD+AI Agent:从规格到npm包的开发实践与踩坑指南

发布时间:2026/9/19 11:00:44
SDD+AI Agent:从规格到npm包的开发实践与踩坑指南
1. 一次“先写规格再让 AI 动手”的真实尝试先说结论我最近用 SDDSpec-Driven Development规格驱动开发配合 AI 协作做了一个排版类的 npm 包。整个过程从写规格到发布到 npm 仓库大概花了两天其中真正我自己动手写核心代码的时间不到一个小时剩下的大多数时间都花在“写清楚要什么”和“让 AI 改到符合要求”这两件事上。这里说的排版包不是 Word 里的图文排版而是一个文本排版工具包专门处理中文场景下的排版细节比如中英文之间自动加空格、中文标点标准化、段落首行缩进、连续空行压缩这类问题。为什么选这个方向因为文本排版规则足够明确边界清晰非常适合拿来做 SDD 的试验田。如果规格写得好AI 生成的代码基本就是按照规格“填空”翻车概率很低。我用的 AI 主要是目前常见的 Agent 编程工具也就是聊天界面里带文件读写、命令行执行能力的那一档很多团队叫它 AI Agent。我的角色从“写代码的人”变成了“写需求、审代码的人”。这个转变听起来简单实际上对思维方式的要求完全不一样以前我可以边写边想逻辑不完整的地方靠编译器兜底现在必须在写规格阶段就把各种边界条件想清楚因为 AI 不会替你补逻辑它只会忠实于你给它的说明。如果你也是第一次尝试这种方式我建议别一上来就让 AI 写一个大型项目最好找一个像“排版规则处理”这样功能单一、输入输出明确的小项目练手。等你能把规格写得让 AI 一遍过的时候你其实已经具备了一个架构师最核心的能力把模糊需求变成无歧义描述的拆解能力。2. SDD 六步法落地的完整链路网上关于 SDD 的讨论不少尤其今年这几个月的 AI 编程热词里SDD 经常和 AI Agent 并列出现。我理解的 SDD 不是一种工具而是一套流程分六步定义规格、解释规格、拆解任务、编写实现、测试验收、迭代演进。下面把我实际跑通的完整链路展开说顺便把每一步里 AI 到底干了什么、我自己干了什么讲清楚。2.1 第一步把需求翻译成无歧义的 Spec规格这一步是整个流程的地基也是最容易被人忽视的一步。很多人让 AI 写代码失败其实就是败在“需求描述是人话但不是规格”。人话的特点是模糊例如“处理一下标点符号”AI 听到这种话就会自由发挥结果你得到一堆表面能用、边界一碰就碎的逻辑。我第一步干的活是打开一个新的 Markdown 文件把排版规则一条一条列出来。每条规则都写成“输入特征 处理动作 预期效果”的句式例如当中文和英文之间紧邻时两者之间插入一个空格例如“使用SDD开发”应变为“使用 SDD 开发”。当中文和数字之间紧邻时两者之间插入一个空格例如“共10个字符”应变为“共 10 个字符”。数字与%、℃、万这样的单位之间不加空格例如“50%”保持不变。中文文本中的英文逗号、英文句号、英文问号、英文冒号、英文分号统一替换为全角版本。连续的三个及以上空行压缩为空行但保留单个空行作为段落分隔。每段开头插入两个全角空格作为首行缩进如果该段已经是缩进状态则跳过。所有 HTML 标签和 Markdown 标记不得被处理破坏标签内部的内容不动。这些规则听起来都很简单但真要把它们写到“AI 不会理解错”的程度还是有技巧的。我会给每条规则配上正例和反例。正例是这个规则期望的结果反例是最容易搞错的输入。比如“中文标点替换”的反例就要写清楚如果文本里出现了英文引号嵌套不能把引号简单替换成全角必须考虑引号成对匹配的问题。AI 看到正反例之后生成的逻辑明显比我只有一句描述的时候稳得多。规格文件里我还写了一个“非目标”小节明确告诉 AI 哪块不需要做。比如我这个排版包不做中英文分词、不做拼写检查、不做 Markdown 解析输入里如果携带 Markdown 标记原样保留即可。这个“非目标”看着不起眼实际上特别关键它像护栏一样避免 AI 在无关的方向上过度设计。2.2 第二、三步让 AI 代理解析规格并拆分任务规格写完后我没有直接把整个文件丢给 AI 让它“写一个包出来”而是先让它读规格然后把它理解的规则用自己的话复述给我。这一步对应 SDD 里的解释规格和拆解任务。AI 复述的过程相当于“对需求”能提前暴露我规格里没写清楚的地方。比如我第一次让 AI 复述时它把“数字与单元号之间不加空格”理解为“所有数字后都不加空格”这显然跟我的预期不同。我修正了规格补充了“%、℃、万、亿、元、年、月、日等字符视为单元号”的详细说明。确认理解一致后我让 AI 输出一份任务拆解清单。它给出的结构大致是建立 npm 包基础结构确定 package.json、tsconfig、入口文件。实现 spacing 规则模块中英文之间、中数与数之间、数字与单位之间。实现 punctuation 规则模块半角标点转全角、引号成对处理、省略号标准化。实现 paragraph 规则模块首行缩进、空行压缩。实现主函数按顺序应用规则。编写测试用例覆盖规格中的每个正例和反例。编写 README 和构建发布配置。这个拆解几乎就是一份开发计划。我检查了一遍把两个优先级问题调整了先做最核心的主函数顺序约束再实现具体规则。因为我意识到规则的应用顺序本身就很重要如果先做首行缩进再做中英文空格可能会导致缩进判断错位。这些思考放在后面“踩坑”部分细说。2.3 第四、五步实现与验收以测试锚定需求实现阶段AI Agent 根据任务拆解逐个文件创建。它能直接在工作目录里写文件、跑命令我只需要在它每完成一个小任务之后做代码审查。我习惯让它“一次只做一块”。比如先做 spacing 模块写完跑一下单测通过了再进入 punctuation 模块。这样一旦出错问题定位范围很小不会出现一堆文件堆在一起、一跑全是 bug 的失控场面。验收环节我用的是 Vitest不是简单的 console.log 验证。为什么非要用测试框架因为规格里每条规则都对应一个“输入 - 输出”的映射这天然就是测试用例。把规格里的正例反例全塞进测试文件只要测试绿了规格就算落地了。测试代码有一半是我手写的另一半是 AI 根据规格文件自动生成的。AI 生成的测试有个问题容易“太善良”只测自己实现能通过的情况挑不出自己的毛病。所以我会故意混进几个规格里有、但实现容易漏掉的反例确认测试真的会失败再让 AI 去修逻辑。这一步其实就是“测试锚定需求”让规格、测试、实现三者一一对应缺一个都会导致整个流程失守。2.4 第六步迭代以及我实际遇到的问题前期做完后进入迭代阶段。迭代的动力来自两种场景一是规格不够细测试跑挂二是真实使用场景里出现了我没有预料的输入。我这次迭代里遇到最典型的问题出在“引号成对处理”上。规格里我只写了“英文引号要变成中文引号”但实际测试里发现了嵌套引号的情况——引用的话里又套着一层引号如果统一替换引号方向就会错乱变成两个左引号或者两个右引号。这个问题的本质是我的规格描述没有覆盖“引号上下文”这个维度只描述了“字符层面”的替换。后来我改了规格要求 AI 先扫描字符串找出引号配对关系再根据层级交替使用双引号和单引号。顺便说一句中文排版里的引号处理本来就是一个看起来简单实际很烧脑的领域因为它跟上下文语义绑定的程度远高于普通字符替换。如果你打算自己实现类似功能强烈建议在规格阶段就把嵌套用例想清楚。迭代流程每次都是新增规格条目 - 补充测试用例 - 让 AI 改实现 - 回归测试。整体跑下来迭代了几轮之后代码质量和规格的完整度同步提升这大概就是 SDD 最让人上瘾的地方需求的变化不会直接变成代码补丁而是先沉淀成规格文档让每个改动都有据可查。3. 排版包的核心实现从 Spec 到可发布的代码前面讲的是流程这一节讲这个 npm 包装了什么具体的代码看看“排版”这件事在技术上是如何落地的。项目本身结构不复杂但其中有一些如果没在规格阶段说明、写代码时会反复纠结的选择。3.1 包结构设计与核心 API我用的技术栈是 TypeScript Vite 库模式构建输出 ESM 和 CJS 两种格式附带 .d.ts 类型声明。包目录结构如下typography-kit/ // 包名示例 ├── spec/ │ └── typography.spec.md ├── src/ │ ├── index.ts // 主入口导出 formatText │ ├── rules/ │ │ ├── spacing.ts │ │ ├── punctuation.ts │ │ └── paragraph.ts │ └── utils/ │ └── quote.ts // 引号配对处理 ├── tests/ │ ├── spacing.test.ts │ ├── punctuation.test.ts │ └── paragraph.test.ts ├── package.json ├── tsconfig.json └── vitest.config.ts核心 API 只暴露一个纯函数export interface FormatOptions { indent?: boolean // 是否开启首行缩进默认 true compressBlankLines?: boolean // 是否压缩连续空行默认 true spacing?: boolean // 是否开启中英文/数字间空格默认 true punctuation?: boolean // 是否开启标点标准化默认 true } export function formatText(input: string, options?: FormatOptions): string每个排版规则都被拆成独立函数。这样做的好处有两个一是 AI Agent 写单个规则函数时上下文很小不容易出错二是用户可以按需组合。我在 README 里也承诺了如果用户只想做标点标准化可以传一个 options 对象关掉其他规则。这种 API 设计思路其实是从“单一功能模块化”的角度出发的与 SDD 的理念一脉相承——每个模块对应规格中的一个明确职责。3.2 核心排版逻辑的实现思路标题看着高大上其实核心就是一个正则 一个配对的字符串扫描。下面挑几个有代表性的实现片段说说。中英文之间加空格的规则我用的是正则环视判断中文和英文相邻的位置插入空格const CN_EN_SPACE_REGEX /([\u4e00-\u9fa5])([A-Za-z0-9])/g; const EN_CN_SPACE_REGEX /([A-Za-z0-9])([\u4e00-\u9fa5])/g; export function addSpacingBetweenCnAndAlnum(text: string): string { return text .replace(CN_EN_SPACE_REGEX, $1 $2) .replace(EN_CN_SPACE_REGEX, $1 $2); }数字与单位之间不加空格又是另一条规则需要维护一个单位集合用正则的负向环视排除const UNITS [%, ℃, 万, 亿, 元, 年, 月, 日, 个, 款]; const UNIT_PATTERN new RegExp(([0-9])(\\s*)(?${UNITS.join(|)}), g); export function removeSpacingBetweenNumberAndUnit(text: string): string { return text.replace(UNIT_PATTERN, $1$2); }为什么顺序很重要因为我先加空格再去掉数字和单位之间的空格。如果顺序反过来数字和单位之间会被先塞进空格最后又得二次处理徒增复杂度。这个顺序问题我在写规格的时候没有明说是 AI 在实现时主动提出“规则应用顺序应为 spacing 先、单位修正后”我觉得有道理就采纳了。这就是 AI 协作开发里比较舒服的一种状态规格定义了“做什么”AI 补上了“怎么做最合理”我做最终决策。再比如段落首行缩进我用了个比较朴素的按行处理策略。真正需要注意的是不能给本身就是缩进状态的行再加缩进否则每次调用 formatText 缩进都会翻倍。排版工具最常见的问题就是幂等性差——同一个文本跑两次排版结果应该和跑一次一致。我在规格里专门写了这条“不可重复处理导致结果变化”的约束测试里也放了一组双跑对比用例。引号配对处理是这次实现里唯一有点难度的部分。我没有用简单 replace而是写了一个状态机扫描器遇到英文引号时根据当前是否是“引用开始”状态决定转成左引号还是右引号并维护一个引号栈处理嵌套。核心代码如下export function normalizeQuotes(text: string): string { const stack: string[] []; let result ; for (let i 0; i text.length; i) { const ch text[i]; if (ch ) { if (stack.length 0) { result “; stack.push(“); } else { result ”; stack.pop(); } } else { result ch; } } return result; }这个实现能覆盖绝大多数正常场景但对中文里常用的单引号嵌套还需要进一步扩展。AI 的第一次实现只处理了双引号我在审代码时发现规格里写了“支持引号嵌套”于是让它在遇到双引号内部再出现双引号时自动降级为单引号。补充这个逻辑后嵌套场景就过了。4. 发布到 npm 时我踩过的几个环境坑说实话写代码的过程整体很顺真正让我费时间的是发布 npm 包前后的各种环境问题。这些问题跟 AI 无关跟 SDD 也无关纯属老生常谈的“npm 环境老三样”但很多新人在第一次打包发布时会一头扎进去出不来。我按踩坑顺序把最有代表性的三个问题整理出来。4.1 PowerShell 禁止运行脚本npm.ps1 报错的根因在 Windows 上用 npm最常见的报错就是这个npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本我第一次在 Windows 环境遇到这个报错时第一反应是 npm 装坏了。其实不是这是 PowerShell 的执行策略Execution Policy在拦路。npm 在 Windows 上是通过 npm.ps1 脚本执行的PowerShell 出于安全考虑默认禁止运行本地脚本于是把这个脚本拦下了。解决办法是在 PowerShell 里执行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned这条命令的意思是当前用户允许运行本地脚本从远程下载的脚本必须有可信签名。比直接设成 Unrestricted 安全一些也不会影响日常开发。如果你不想改执行策略还有一个临时的办法在 PowerShell 里用 npm.cmd 代替 npm例如运行npm.cmd -v。这个方式适合应急但不建议长期用因为很多工具内部会直接调用 npm 命令绕过脚本后行为可能不一致。4.2 npm registry 证书过期与国内源问题我发布前需要先安装依赖结果碰上了一个很多人都会遇到的证书报错npm ERR! code CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/... failed, reason: certificate has expired看到 cert_has_expired 第一反应是系统时间不对我查了之后发现时间没问题再一看 URL豁然开朗——我用了一个老旧的国内镜像源registry.npm.taobao.org。这个源的证书已经过期而且这个域名早已被官方废弃淘宝镜像现在迁移到了 npmmirror.com。这个问题根源在于我早期配置过全局 npm 源后来镜像搬迁旧域名没有迁移证书导致安装请求全部失败。解决办法是先看当前配置npm config get registry如果返回的还是 taobao 老域名那就改成新地址或者官方源npm config set registry https://registry.npmmirror.com如果你不在国内或者不需要用国内镜像直接用官方源更省心npm config set registry https://registry.npmjs.org这里我多说一句很多教程喜欢叫大家用镜像源但镜像源有一个隐形问题同步延迟。发布本地包后立刻在别的项目里 install如果镜像还没同步就会装不到最新版本。我这个排版包发布时就遇到过在镜像源下还是旧版本、官方源下已经是新版本的情况。所以发布新版本后做安装验证时尽量先用官方源验证避免被镜像拉到旧版本搞懵。4.3 npm 环境变量与 Node 安装选型的坑第三个问题是在另一台电脑上复现时碰到的报错是npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称这是典型的 PATH 环境变量问题也就是系统找不到 npm 可执行文件。造成这个问题的原因一般是 Node.js 安装时没把路径加进 PATH或者重装 Node 之后旧路径残留。排查时我先跑了node -v能正常输出版本号说明 Node 本体没问题问题只出在 npm。然后用where.exe node找到 Node 安装目录发现 npm 的可执行文件其实就在 Node 安装目录下。解决方法是把C:\Program Files\nodejs\这个目录手动加到系统 PATH 里重启终端即可。顺带提一个 Node 安装选型的建议尽量别用 nvm-windows 之外的方式覆盖安装 Node直接在官网下载 MSI 安装包最省事。如果要用 nvm 管理多个 Node 版本一定要在安装 nvm 之后重新打开一遍命令行确保 PATH 配置生效。我遇到好几个开发者的环境问题最后都追溯到了“先装 Node 再装 nvm”的顺序错误导致 PATH 里有两套 Node 路径冲突。还有一个容易被忽略的配置.npmrc文件。npm 会依次读取项目级、用户级、全局级配置如果你在某级设置了奇怪的 registry 或者代理其他命令行为就会变得难以理解。遇到诡异的 npm 问题时先执行npm config ls -l看看所有配置再逐级排查 .npmrc基本能解决一半问题。5. 想复制这套范式你需要注意的取舍关于“AI 协作开发新范式”这个话题我不想只讲吹得天花乱坠的部分。SDD AI Agent 的组合确实有效但也有限制。这里把我在实践中体会到的边界和取舍完整分享出来免得你照着做的时候预期错位。5.1 什么样的项目适合 SDD我尝试下来的经验是适合 SDD 的项目有三个特征目标可描述、输出可验证、边界可枚举。先看目标可描述。你要做的事能不能用语言写清楚文本排版、数据转换、格式化工具、校验规则这类项目天然适合因为输出结果是人话级别的。反过来如果你的项目高度审美化、缺乏客观标准比如 UI 视觉设计、产品流程的某些润色规格就很难写写了 AI 也不知道怎么做。再看输出可验证。SDD 强调测试锚定需求意味着每个规格条目最好能变成一个自动化测试断言。“输入 A 得到输出 B”这种模式是最理想的。如果项目的输出很难量化验证比如“提升用户体验”“让代码更优雅”那 SDD 会非常痛苦因为你没法定义什么叫“完成”。最后是边界可枚举。项目有没有明确的输入边界范围我做的排版包输入是字符串边界是各种标点、空格、回车组合理论上可以列一个规则拓扑。但如果项目是那种“无穷无尽的情况会冒出来”的类型比如一个开放式的爬虫或者通用问答系统规格永远写不完SDD 的迭代成本就会很高。5.2 AI 能做什么不能做什么在这一次实践中AI Agent 的价值主要体现在三个环节把规格翻译成代码、生成测试用例、根据失败信息定位修改点。尤其最后一条AI 能快速读取测试报错堆栈定位到具体函数并给出修复比人工排查快很多。但 AI 不能做什么最明显的短板是“规格没有提到的需求它不会主动补全”。它不会因为你写了一句“处理中英文间距”就顺带把“处理中文和数字之间间距”也做了除非你的规格明确写了。这在某种程度上是优点保证代码不会超出预期但同时也是风险因为如果你自己没想清楚AI 绝不会替你兜底。另一个短板是“大范围重构能力有限”。当代码已经写了两三个模块之后你再让 AI 改一个贯穿所有模块的架构假设它会改出很多不一致的地方。这一点我是在迭代时发现的我有一次想调整主函数的管线顺序以“规则数组”的方式注册规则AI 改的时候把规则模块内部也改成了数组形式但测试文件里还有一些硬编码的调用结果回归测试红了一片。不是说 AI 改不了而是它改动时对整个项目影响面的把控明显弱于一个有经验的人类开发者。所以我的结论是AI 是执行者不是架构师。架构决策、模块边界、数据结构这些设计层问题最好人自己定再让 AI 去填细节。如果你把架构也扔给 AI短期看省了时间长期看代码结构会迅速混乱后续维护成本会爆炸。5.3 我实际在用的 SDD 提示词模板最后分享一个实用的东西——我在整个流程里反复使用的一套提示词结构。SDD 说起来玄乎落到提示词上本质就是“分段、限定、可验证”。写规格阶段我用这样的提示词你是一名资深前端/Node.js 开发者。请根据以下需求输出一份 SDD 规格文档 1. 将需求拆分为不超过 10 条明确规则 2. 每条规则必须包含规则描述、输入示例、输出示例、禁止行为 3. 标出可能存在的歧义点 4. 给出你理解的最小实现范围。 我的需求是...让 AI 复述规格时我用请用你自己的话复述上述规格中的每条规则并列出你认为实现时最容易出错的 3 个地方。开始写代码时我用严格按照 spec/typography.spec.md 中的规则实现 spacing 模块。 要求 - 使用 TypeScript - 导出纯函数不修改输入字符串 - 实现后先运行相关测试文件确认通过 - 不要修改测试文件。遇到测试失败时我用测试失败信息如下... 相关代码文件... 请先分析失败原因再给出最小修复方案不要顺带重构其他部分。这套模板没什么玄机核心就是每个指令都带上“范围限定”和“验收条件”。“不要修改测试文件”这句话尤其重要因为 AI 为了凑一个通过的结果很有可能会偷偷改测试用例那会让整个 SDD 闭环失效。我个人的体会是用 SDD 驱动 AI 开发真正考验的不是你会不会写提示词而是你能不能像写合同一样写规格。规格越严谨AI 越省事你的审查压力越小。等这套流程跑顺之后你会发现自己的角色不知不觉从一个“写代码的人”变成了“代码产品的产品经理”。刚开始可能会不习惯但当你发现很多模块 AI 能一次写对、你只需要做测试验证时那种掌控感和效率感确实和传统的开发方式很不一样。