SDD实战指南:用规范驱动AI协作开发并发布npm排版包
发布时间:2026/9/9 0:53:09 锦皓数字建站

前段时间我给自己安排了一个小项目把日常写作中经常用到的一套中文排版规则封装成一个可以发布到 npm 的 Node 工具库。整个开发过程没有采用传统的“我写代码、AI 帮忙补全”的模式而是换成了另一种协作方式——SDD也就是 Specification-Driven Development规范驱动开发。先把这个工具该有的行为写成规格再让 AI 在规格框架内完成实现、测试和文档我负责审查、验收和发布。最终做出来的包不大核心功能就是自动处理中英文混排空格、标点归一化以及保护 URL 和代码片段不被改写。先说一句总结性的体会这条路跑通之后我第一次在一段 AI 协作开发里体验到“写出来的代码可预期”是什么感觉。以前让 AI 直接生成功能经常拿到一段看起来没问题、一进边界测试就翻车的代码而这次是先画好跑道再让它跑每一步产出都能对照规格说话心里踏实很多。这篇文章我会从为什么选 SDD、规格怎么写、AI 怎么协作、npm 包怎么发布这几个环节完整拆一遍中间穿插一些实际踩坑记录希望对正在用 AI 写代码、尤其是打算发公共包的朋友有帮助。1. 为什么是 SDD先给 AI 画好跑道再让它跑1.1 “对话式生成”的失控感来自哪里我最早和 AI 协作写代码的方式很简单就是“聊天式编程”把一个功能需求丢给它它给我一段代码我看一眼能用就粘进项目。这种模式做小脚本、做一次性工具非常爽但一旦功能开始有边界问题就来了。印象最深的一次我让 AI 写一个“中英文之间自动加空格”的函数第一次输出很漂亮然后我追加需求希望它能跳过 URL 和代码片段结果它把整个实现重写了一遍原来已经通过的几个用例直接挂了。它的逻辑没有错但它不知道哪些行为属于“既定事实”。这就是对话式生成失控感的来源。AI 是一个极度“顺从”的执行者你说什么它都会尝试接住但如果你不给它一份稳定、可回溯的约束它就会在每次对话上下文里重新理解这个世界。今天你说“加上空格”它理解成一个规则明天你说“注意保留 URL”它可能把所有文本都当成可改动的内容。问题不在 AI在于人和 AI 之间缺少一份共同遵守的契约。1.2 SDD 不是让你写 PRD而是写行为契约我第一次听到 SDD 这个词是在一个小范围技术讨论里后来慢慢接触了一些团队分享才意识到它并不是什么新造的银弹。SDD 和传统的 TDD、BDD 有相似气质本质都是“先定义可验证的行为再写实现”只不过 SDD 在 AI 协作场景里的位置更特殊它把“给 AI 的提示词”变成了一种工程制品。我理解的 SDD 可以粗略分成三个成熟层级。第一层是最原始的 Vibe Coding纯靠对话让 AI 输出代码代码是否可用全靠人的临场判断。第二层就是 SDD在动手之前把功能拆成一条条规格每条规格都带上输入输出样例和边界条件AI 实现的依据不是聊天记录而是这份规格。第三层更进一步把规格、工具链、人工审查、持续集成串成一套完整的开发闭环AI 只是其中一个环节。对我这次做排版包来说真正用到的是第二层但设计思路已经带了第三层的影子。我不需要写几百页需求文档只需要把一个 npm 包涉及的模块边界、参数行为、错误处理写清楚。关键判断标准只有一条如果一份规格没办法让我对着它写出测试用例那它还不够具体AI 也不可能根据它做出稳定的实现。1.3 为什么拿一个 npm 排版包来试水 SDD选“排版 npm 包”作为 SDD 实验载体其实是一个精准的选择。npm 包天然有三个适合 SDD 的特性接口边界清晰、验收结果可自动化、交付物有明确发布动作。接口边界清晰意味着我可以把功能压缩成几个函数签名和类型定义不需要处理大型系统里的模块间复杂依赖。验收结果可自动化意味着每条规格都能直接转成一组测试断言——输入中文字符串断言输出字符串没有任何模糊空间。而“发布到 npm”这个动作又逼着我走完打包、版本号、registry、权限校验这一整套工程流程让这次实验不止停留在“写代码跑通”的层面。另外我也是一个内容创作者平时需要处理大量中英文混排的文本。中文排版里最基础的一条规范是中英文和数字之间要加空格比如“我用Prompt做了一款工具”应该写成“我用 Prompt 做了一款工具”。这个需求看似简单真要写成稳定的工具库边界情况不少适合拿来验证 SDD 流程是不是真的能减少返工。2. 规格先行把“排版工具”拆成可验收的边界2.1 需求从一句话变成三个核心能力最初我的需求只有一句话“做一个自动排版文本的 npm 包。”如果我真把这句话丢给 AI它大概率会给我一个庞大到失控的实现甚至擅自加入全角转半角、自动分段、Markdown 解析等等功能。这就是 SDD 要解决的第一个问题把模糊愿望翻译成有限、明确、可验收的能力项。我把需求拆成了三个核心能力。第一个是中英文间自动加空格识别中文和 ASCII 字母、数字之间的边界插入一个空格第二个是中文标点的规范化和压缩把连续重复的标点按规则收敛比如把多个感叹号压缩成不超过两个第三个是特殊内容保护URL、邮箱、代码片段、变量名这些内容不能被空格规则或标点规则破坏。这三个能力不是拍脑袋定的而是来自我日常写作的真实场景。写技术文档时经常出现“Node.js环境下用npm安装依赖”这类混排需要中英文空格而文本里又经常带命令行示例和网址这些部分如果被“自动化排版”动了等于帮倒忙。所以能力边界里的“不做”反而成了这个包最有价值的部分。2.2 一份能直接交给 AI 的规格长什么样规格文档不需要花哨但必须包含四个要素行为名称、输入输出样例、核心规则、边界条件。以“中英文间自动加空格”为例我实际给 AI 的规格是这样写的# 规格cjk-latin-space 行为说明在中文字符与 ASCII 字母/数字之间插入一个普通空格。 输入样例 - 输入: 我用Prompt做了一款工具 - 输出: 我用 Prompt 做了一款工具 - 输入: Node.js环境下跑npm命令 - 输出: Node.js 环境下跑 npm 命令 核心规则 1. 只处理中文字符与 ASCII 字母、数字之间的交界处 2. 单个字符串内部连续出现的中文字符之间不处理 3. 如果对应位置已经有空格不能重复插入 4. 不影响 ASCII 片段内部的格式例如 Node.js 不能变成 Node. js。就这样一份简短规格已经能覆盖 AI 实现时的大部分歧义。第二条防止它做全文扫描式处理第四条防止它把“Node.js”这个完整 token 拆掉。这些规则不是我想当然写的都是早期对话式编程时 AI 真实犯过的错误。我把这种规格整理成 Markdown 文件放进项目仓库的spec/目录里然后明确告诉 AI所有代码和测试都必须依据这份文件当需求冲突时以规格为准。这样每次对话都有一份“宪法”兜底AI 不会因为上下文轮次增多而“遗忘”最初的约定。2.3 “明确不做的事”比功能清单更能防空想给 AI 写规格时最容易被忽略的是“不做的事”清单。AI 有一个很强的倾向默认你想把一个工具做得更完善。你让它处理中文空格它可能会顺手把英文标点都改成中文标点你让它压缩标点它可能会把所有引号重新配对。这些“额外优化”在不经意间就会破坏你的原始文本。所以我在规格末尾专门留了一节“明确不做的事”不做全角半角转换因为那是另一个需求不做中英文标点映射因为会影响代码和 URL不做整段重排和断行因为没有上下文信息不解析 Markdown 或 HTML只把它当作纯文本片段处理。写完这一节AI 的“自由发挥空间”被进一步收窄。这一点在 AI 协作开发里尤其重要。传统开发里一个工程师如果发现需求不明确会来问你AI 不会问它只会基于训练数据里的“常见做法”替你补全。如果我们不在规格里堵上这条路就等于默认它可以用统计概率替你决定产品行为。规格里写清界限不是限制 AI而是防止它对需求做过度的默认假设。3. AI 协作实现四轮推进和容易被忽略的优先级3.1 四轮协作流程每轮只交一个明确结果规格写完后我并没有让 AI 一次生成整个项目而是把实现过程拆成了四轮。这个拆分来自一个很朴实的观察一旦 AI 生成的代码量超过某个阈值出错的概率会指数上升而人 review 的负担也会变得不可控。拆成小轮次每一轮都能快速验证、快速纠偏。第一轮只做骨架定义typofix(input, options)的接口签名、参数类型、返回类型以及主流程里三个核心函数的占位。这一轮不追求功能正确只确认结构边界。第二轮实现核心规则重点是让规格里的四个输入输出样例跑通。第三轮让 AI 自己根据规格生成测试用例并把样例表里的所有情况覆盖全。第四轮是“规格自检”让 AI 逐条对照规格列出一个自查表标注每一行代码覆盖了哪条规则。四轮之间我保持同一个对话上下文每轮开始都重新粘贴规格文件的摘要提醒 AI 以规格为准。实测下来效果比一轮梭哈好太多——代码结构不会到后期才暴露问题测试用例也能及时兜住实现过程中的回归AI 的每次输出范围都小到我可以快速 review。3.2 输入输出样例表是最有效的接口契约在 AI 协作里文字描述再详细也容易产生歧义但输入输出样例几乎没有歧义。我在规格里附了一张样例表AI 实现时的第一优先级就是让这张表里的所有条目满足预期。输入期望输出说明我用Prompt做了一款工具我用 Prompt 做了一款工具基础中英混排Node.js环境下跑npm命令Node.js 环境下跑 npm 命令Node.js 是整体不被拆开打开https://example.com看看吧打开 https://example.com 看看吧URL 前后补空格但 URL 内部不变这个工具非常好支持Python这个工具非常好支持 Python感叹号压缩为两个中文与英文空格AI 写代码AI 写代码已有空格不重复插入这张表如果用手写实现大概只要几条正则就能完成但它真正的价值在于它让 AI 的每个实现选择都有了可验证的标尺。AI 在第三轮生成测试时自己也主动把这张表转成了test/目录下的断言。这就是规格的“可测试性”红利——好的规格不仅约束开发还直接变成了测试用例。我在 review 测试时又加了一条这张表里没有的边界当输入为空字符串或者只包含空格时函数必须原样返回不报错。AI 看到这个新增样例后在入口处补了一个空值保护。这个场景不算复杂但如果没有测试兜底很容易在后来的版本迭代里被某次重构删掉。3.3 规则优先级先保护再压缩最后补空格这个排版包最容易翻车的地方不在单条规则而在规则之间的执行顺序。比如一段文本“官网https://example.com欢迎你”如果先做中英文空格URL 前会被插入空格变成“官网 https://example.com 欢迎你”这似乎没问题但如果先做标点压缩或空格可能会把 URL 内部的下划线、斜杠当成可拆分边界直接破坏链接。我给规则定的顺序是先保护特殊内容再压缩标点最后补中英空格。保护阶段先把 URL、邮箱、代码块等片段提取出来替换成不可见的占位符压缩标点阶段只处理占位符之外的普通文本补空格阶段同样不触碰占位符。所有处理结束后再把占位符还原成原始片段。这个顺序逻辑上很像“先冻结再加工”先把不能动的内容冻结起来剩下的普通文本随便折腾最后解冻。AI 第一版实现其实把它们写成了三个独立正则的连续调用没有占位符机制。我看代码后问了一句话“如果 URL 内部包含中文字符或者文本里有多个 URL还能保证正确吗”AI 自己分析后也承认会出错。于是它改成了先 scan 特殊内容、保存映射、最后 replace 回填的结构。这类结构性问题单看功能样例不一定能发现但结合规则优先级思考就能暴露。3.4 测试结果与人审盯什么整个实现加测试大概在一个下午内完成最后跑测试规格里的 12 个基础样例和 6 个边界样例全部通过。不过我作为人并没有完全放手重点盯了三处 AI 容易自我感觉良好的地方。第一看它有没有“把规格之外的事也做了”。AI 曾经主动给函数加了一个自动转小写的选项规格里没有我删掉了。第二看类型定义是否严格控制了 options 的可选字段。这个包面向外部用户API 设计一旦定下来就不好随便改所以要在一开始把 types 收窄。第三看它的文档和 README 是否和真实行为一致。AI 生成的 README 有两处写得太乐观把“全角标点转换”也写成了功能但实现里根本没做。人审盯的不是代码风格而是“规格一致性”。AI 写代码的速度远快于我但它在“这个包到底承诺了什么”这件事上并不可靠。每一条 README 里的能力描述都应该能在测试里找到对应断言。如果我盯完发现描述功能没有测试覆盖那基本可以确定 AI 又在自我发挥。4. 发布到 npm完整流程与热门报错速查4.1 发布前自检清单为别人做的准备自己写的工具能在本地跑通和能发布到 npm 供别人使用中间隔着不少准备。SDD 的好处在这里又体现了一次规格让包的行为边界清楚以后README、关键词、类型声明这些“给别人看的东西”也有了明确蓝本。我发布前逐项过了四件事。第一package.json的files字段只保留dist和必要的README.md、LICENSE避免把spec/、测试代码和本地文件全部打进包里。第二确认main指向编译后的dist/index.jstypes指向dist/index.d.tsexports字段把 CommonJS 和 ES Module 的入口都声明清楚。第三检查版本号是否符合 SemVer第一次发布用0.1.0比较合适。第四补上repository、keywords、license信息否则包的“完成感”会差很多。这里我习惯先跑一遍npm pack生成一个本地 tarball解压进去看看到底包含哪些文件。这一步几乎不花时间但能发现很多低级问题比如dist没构建就有脏文件混进发布包或者配置文件把本机路径写到了包里。发现问题时还没有造成影响改起来也从容。{ name: typofix, version: 0.1.0, type: module, main: ./dist/index.js, types: ./dist/index.d.ts, exports: { .: { types: ./dist/index.d.ts, import: ./dist/index.js } }, files: [dist], license: MIT }我核心的 npm 包逻辑都在src/index.ts里构建命令用的是 TypeScript 官方编译器。代码实现部分其实已经由 AI 按规格完成到了这一步我的精力都放在“别人装了这个包能不能立刻用”这件事上。4.2 从 login 到 publish 的标准步骤npm 包的发布动作本身不复杂核心就三步登录、构建、发布。但如果此前没配置过 registry经常会在第一步就翻车。第一步是npm login它会弹出交互式输入填 npm 用户名、密码和邮箱。如果本地设置过第三方镜像源登录请求可能会被转发到镜像地址轻则登录失败重则把凭据发到非官方服务。所以发布前第一件事是执行npm config get registry确认结果是官方 registry而不是某个第三方镜像。第二步是构建。因为我的包是 TypeScript 写的需要先执行npm run build生成dist目录。构建命令在package.json里定义为tsc没有做多余的文件拷贝。这一步容易忽略的坑是改了源码后忘了重新构建就发布结果发布的是上一次编译的旧版本。养成的习惯是发布前手动清空dist再重新构建确保不是增量残留。第三步是npm publish。如果你用的是作用域包名比如yourname/typofix默认情况下 npm 会把作用域包当私有包处理必须显式加--access public参数。我在这一步曾被 403 错误卡过一次原因就是作用域包没有声明 public。npm config get registry npm run build npm publish --access public发布成功之后npm 会返回 typofix0.1.0这样的信息。到这一步还没有万事大吉一定要换一个空目录从 registry 重新安装一次验证包的完整可用性。4.3 最容易遇到的四个环境问题排查发布路径上会遇到的问题其实绝大多数和环境配置有关不是代码本身。我把几个高频问题整理成了一张速查表这里额外标注一下排查思路。报错现象根因处理方式npm不是内部或外部命令Node.js 安装目录没有加入 PATH用where node找到安装路径补到系统 PATH重开终端npm : 无法加载文件 ...\npm.ps1因为在此系统上禁止运行脚本PowerShell 默认不允许执行.ps1脚本以当前用户维度放开执行策略Set-ExecutionPolicy -Scope CurrentUser RemoteSignednpm ERR! code cert_has_expirednpm registry 指向了证书过期的第三方镜像执行npm config get registry把 registry 改为官方源npm ERR! 403 Forbidden包名被占用、作用域包未公开或 registry 是只读镜像检查包名是否被抢注、作用域包加--access public、确认 registry 不是镜像PowerShell 那个错误最典型几乎每个在 Windows 上装过 Node 的新手都会遇到。它的本质不是 npm 坏了而是 PowerShell 出于安全策略默认不允许运行未签名的.ps1脚本。Set-ExecutionPolicy -Scope CurrentUser RemoteSigned表示“对当前用户放开本地创建的脚本允许运行从网络下载的脚本需要有签名”。这是相对保守的放开方式比直接用管理员权限全开合理。证书过期那个坑则是典型的“镜像源年久失修”。当 npm 包安装报cert_has_expired先别怀疑代码先跑npm config get registry。如果看到本地配置的 registry 是某个第三方镜像直接改成官方源是最快的处理方式。镜像源的初衷是加速下载但维护不及时就会引发证书问题实际体验反而更差。4.4 发布后的纯净环境验证发布完成后我习惯性地在本地建一个全新的临时目录模拟用户从零安装的场景。这一步能发现很多“我这边明明能跑别人却说不行”的隐蔽问题比如files漏掉了某个依赖文件、类型声明没有正确打包、包入口指向了不存在的路径。mkdir verify-typofix cd verify-typofix npm init -y npm install typofix node -e import(typofix).then(m console.log(m.typofix(我用AI写了一个npm包链接是https://example.com)))如果这段命令能从官方 registry 正常安装并且输出符合规格里的排版结果那这个包的发布流程才算真正跑通。我在验证时还加了一步用npm view typofix检查发布版本、main 入口和 types 入口是否都正确展示。这一步能从 npm 服务端的视角确认包元数据没有写错避免用户安装后本地拿到一个“空壳包”。个人体验来说发布到 npm 最需要耐心的是第一次。第一次会把所有环境问题集中暴露出来等第二次、第三次发布时整个流程会变得机械而稳定。我也顺手写了个简单的发布脚本把测试、构建、npm publish 串联起来避免手动操作漏步骤。这个项目做完我对 SDD 和 AI 协作开发的感受发生了一个很明显的变化以前写规格总觉得是额外负担实际算下来规格加测试样例占用的时间大约是整个开发的三成但它们帮我省掉的返工时间远不止三成。尤其是在 AI 出现在协作链条里以后代码生成已经不再是最稀缺的能力“让代码始终围绕目标”反而成了主要挑战。如果你也想试试这个流程我的建议是别一上来就拿大系统练手找一个像这样边界清晰的小工具花十来分钟写一份“行为清单 输入输出样例 不做的事”然后让 AI 严格按这份规格去做。你会发现 AI 的输出质量和可控程度都会明显提高一个档次而且这个套路换到任何需要对外交付的模块上都适用。后面我打算给这个排版包加上 CLI 调用能力让它不只作为函数库提供服务而是能直接参与文件批处理。有了 SDD 这套流程打底后续扩展每个能力时我心里都能多一分底气。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。