
前阵子我干了件以前至少得花一整天的活把一套中文排版规则做成一个可发布的 npm 包从设计、编码、测试到发布整个闭环只用了小半天。工具还是 Node、npm、GitHub 这套老伙计真正变化的是开发方式——我没再像传统流程那样先建目录、再逐行手写每个函数而是先写了一篇规格文档把“排版到底要处理什么、哪些字符动、哪些字符坚决不动”讲清楚然后让 AI agent 按规格把代码实现出来我只负责评审结果和兜底测试。这个方法就是标题里的 SDDSpec-Driven Development规范驱动开发。这篇文章不打算扯太玄的概念。我会拿这个排版 npm 包当完整案例把 SDD 和 AI 协作开发的思路一步步拆开为什么选 SDD 而不是继续 vibe coding规范文档怎么写才能让 AI 产出靠谱代码npm 发布过程中那些报了错没人告诉你原因的坑以及这套方法哪些场景能复制、哪些场景别硬套。适合正在用 AI 写代码但总觉得不可控的人也想给团队引入“AI 协作开发”做参考的读者。1. 为什么是 SDD从 vibe coding 到“先写规格再写代码”1.1 vibe coding 的问题不是代码质量而是不可预期过去一年 AI 编程工具进化得飞快很多人已经习惯了 vibe coding 那套玩法把需求往对话框里一扔AI 噼里啪啦吐出一堆代码能跑就觉得不错。说实话我自己也这么干过一段时间。但在做稍微正式点的项目时这种方式的脆弱感会越来越明显——AI 每次给的实现思路不一样这次用正则下次用循环这次处理了边界情况下次可能就漏了。你问它为什么这么写它讲得头头是道但你要是没提前定义清楚“什么是对的”你就很难判断它写的是不是对的。更隐蔽的问题在于vibe coding 会把设计决策散落在对话记录里。写这段代码的时候是这么定的写那段代码的时候又是那么定的等代码量上来整个项目的行为就开始漂移。团队协作的时候尤其痛苦因为不同成员的对话上下文不共享AI 给每个人生成的东西可能互相打架。这不是 AI 的问题而是缺乏约束的问题。人写代码需要需求文档、接口约定、测试用例来兜底AI 写代码同样需要一套明确的“行为契约”而且因为 AI 理解自然语言的能力很强这套契约可以直接写成接近自然语言的规格文档。1.2 SDD 的核心逻辑规格是产品代码是副产品SDD 的思路其实不新鲜甚至有点像传统软件工程里“先写需求文档再开发”的回归但它有一个关键差异规格文档不再是给架构师评审用的形式化产物而是直接喂给 AI 的“施工图纸”。Thoughtworks 的 Birgitta Böckeler 团队在提出这个方向时核心主张就是把开发流程从“写代码”转向“写规格”让 AI 依据规格去实现人负责把规格写清楚、把验收标准定明白。我自己的体会是SDD 最有价值的一点在于它把人的注意力拉回到了“定义问题”上。以前写代码经常写着写着才发现需求没想清楚SDD 逼着你先把“输入是什么、输出是什么、边界是什么、异常怎么处理”写出来这个思考过程本身就帮你把项目里 80% 的模糊地带消灭掉了。代码反而成了规格的副产品——规格足够清晰时AI 实现的代码基本八九不离十。1.3 SDD 的三级分类框架从系统到功能再到任务SDD 实践里有一个很实用的分类框架把规格分成三个层级方便控制颗粒度System Spec系统级规格描述整个系统或模块的宏观行为包括用户是谁、解决什么问题、全局约束有哪些。放在排版包这个例子里就是“这是一个 Node.js 环境下处理中文文本排版的工具库输入字符串输出规范化后的字符串不抛异常不修改用户传入的对象”。Feature Spec功能级规格描述某一个具体功能的行为包括接受什么、拒绝什么、输出什么。比如“中英文混排时自动插入空格”就是一个功能级规格里面要写清楚哪些字符算中文、哪些算英文、已有空格时怎么办。Task Spec任务级规格落到具体实现单元的规格精确到函数、模块的输入输出和边界。类似“实现 normalizeSpacing(text) 函数返回值类型为 string空字符串返回空字符串”。这个三级框架最大的价值是让规格有层次感。你不用一开始就把所有细节写完可以先写系统级规格确定大方向再拆功能级规格最后落到任务级规格让 AI 去执行。我做完这个排版包后回头看最顺畅的路径其实是“先写 System Spec 定边界再拆 Feature Spec 定行为最后让 AI 根据 Task Spec 写代码”。1.4 为什么拿“排版 npm 包”当案例选排版包做 SDD 的试验田不是随便选的它有几个特别适合验证 SDD 的特质。首先是边界清晰。排版就是“字符串进、字符串出”输入输出都很明确没有复杂的 IO、没有外部依赖天然适合作为 AI 生成代码的标的。其次是规则可枚举。中文排版有很多约定俗成的规范比如中英文之间加空格、全角半角统一、标点压缩等这些规则能一条条列出每一条都能写出输入输出来验证正好能检验“规格质量越高AI 产出越准”这个假设。第三是测试友好。规则明确的项目最容易写测试用例AI 写完代码后我可以通过一组边界用例快速判定它到底有没有真正理解规格。这个选型思路其实可以迁移如果你也想试 SDD别拿一个大型业务系统开刀找一个边界明确、规则可枚举、有自动化验证手段的小模块跑通一个完整闭环比上来就挑战复杂项目要靠谱得多。2. 从需求到规格先把“排版到底要做什么”讲清楚2.1 给这个 npm 包定义一个明确的“系统级规格”动手写代码之前我先把整个包的系统级规格写了出来。Mardown 文档没有复杂模板就是回答几个问题这个包是什么、服务谁、在什么环境下跑、必须满足哪些硬性约束。我写的系统级规格大致是这样的项目名称cn-text-format示例名 一句话描述用于中文文本的轻量排版规范化工具。 运行环境Node.js 16支持 CommonJS 与 ESM 两种引入方式。 输入输出所有导出的函数接收 string 类型参数返回 string 类型结果输入非法类型时抛出 TypeError。 核心原则不改变文本语义不修改用户传入对象不做分词或语义判断规则可配置默认启用推荐规则。 非目标不处理 Markdown 语法、不识别 URL、不做繁简转换。这里最容易被忽略的是“非目标”这一条。以前写需求时我总爱写“要支持什么”很少写“不做什么”。但 AI 生成代码时你不限制的范围它会自由发挥——它会试图帮你翻译繁体、或者把 Markdown 符号也改掉。写上“不做什么”相当于给 AI 划了一条护栏线。2.2 功能级规格把排版规则拆成一条条可验收的行为系统级规格定了边界之后我开始拆功能级规格。这是整个 SDD 流程里最关键的一步因为规则拆得越细AI 产出的代码就越不需要你“事后纠偏”。针对中文排版我列了下面这些规则每一条都附上输入输出示例规则编号规则描述输入示例期望输出F-01中英文混排时在中英文间插入一个空格我用JavaScript写代码我用 JavaScript 写代码F-02中文与数字之间保留/插入空格数字含百分号、日期共20个字符共 20 个字符F-03连续两个及以上英文单词间的空白只保留一个hello worldhello worldF-04中文标点统一为全角。你好,世界.你好世界。F-05英文语境下的标点保持半角不强制转换call foo(1, 2).call foo(1, 2).F-06删除连续空行最多保留一个line1\n\n\n\nline2line1\n\nline2F-07中文行内省略号规范化为“……”六个点非三个等等...等等……F-08英文单词、URL、数字串内部不做任何修改visit https://a.com/x_yvisit https://a.com/x_y每条规则都尽量给出边界示例因为 AI 对示例的敏感度远高于对描述性文字的敏感度。比如 F-04 里如果不给“你好,世界.”这个例子AI 可能会把所有逗号都改成中文逗号包括英文句子里的逗号那就越界了。2.3 编写规格文档的实操心得示例表格最值钱和 AI 协作几次之后我慢慢摸到一个规律描述性文字让 AI 理解方向输入输出示例让 AI 对齐细节。尤其在规则类项目里一张“输入→输出”的对照表比你写五段解释都管用。所以我在写规格文档时每条规则除了文字说明外都配了至少三组测试向量一组正常情况、一组边界情况、一组异常情况。比如 F-01 中英文空格规则正常我用了Node做工具→我用了 Node 做工具边界我 用 了 Node→ 已有空格时不叠加保持一个空格边界Node.js真好用→ 点号不触发空格Node.js视为整体输出Node.js 真好用异常null、undefined、非字符串类型 → 抛 TypeError这些测试向量后来直接被我用在了测试代码里几乎没有改动。这也是 SDD 的一个隐藏收益规格文档里的示例表就是测试用例的草稿写完规格就等于写完了一半的测试计划。2.4 规格评审在让 AI 动手前先当一回“苛刻的架构师”规格写完之后我习惯晾上十几分钟然后自己以评审者的角色读一遍专门挑毛病。这不是形式主义因为规格里的任何一个模糊点都会在 AI 实现阶段放大成返工成本。我当时评审时发现的问题包括F-04 和 F-05 之间其实存在冲突风险——如果一句文本里既包含中文又包含英文标点到底以哪条为准解决方案是增加一条优先级规则按“段落内主语言”判断或者更简单一点标点前是中文则转全角标点前是英文则保留半角。这个规则也来自真实排版场景比如全中文文章里的逗号应该全是全角但混排代码片段时不能乱动英文逗号。另外还有一个容易踩的坑F-08 要求 URL 内部不做修改但如果 URL 后面紧跟中文逗号怎么办这时候应该把中文逗号规范成全角但 URL 本身保持不变。所以规则里我加了一条“保护优先”原则——先识别并保护 URL、代码块、邮箱等特殊片段再执行普通排版规则。这种规则间的优先级设计是规格文档里特别容易遗漏但又特别重要的部分。3. AI agent 按规格实现一套实用的六步迭代工作流3.1 六步实践不用等“完美规格”先跑通再校准规格写好后引入 AI agent 做实现。我在这个项目里实际用的流程可以概括成六步也是我在多次实践后觉得对个人开发者最顺手的一套节奏定义系统与功能规格先定边界和验收标准明确哪些行为是必须有的哪些是明确不做的。拆分任务级规格把功能拆成函数/模块级别写明每个函数职责和输入输出约定。编写并评审规格拿示例表格自我质检确保没有明显的规则冲突和边界漏洞。让 AI agent 依据任务规格生成代码把规格文档路径和任务要求交给 agent要求它严格按规格实现。跑测试验证把规格里的示例向量转成测试用例自动验证 AI 的产出。修复与复盘失败的用例回传给 AI 做修正分析失败原因是规格模糊还是实现偏差然后更新规格文档。这套流程和官方讲的 SDD 精神是对齐的但它更贴近单人/小团队的实操场景。关键不是这六步的名称而是循环逻辑规格驱动实现测试反馈修正修正沉淀回规格。每循环一轮规格质量和代码质量都会同步提升。3.2 提示词设计把规格文档当成“施工图纸”交给 AI agent用 AI agent 的时候很多人习惯把需求直接放对话里然后期望 AI 自己“理解一切”。在 SDD 工作流里正确的做法是把规格文档作为第一优先级输入提示词里只说明目标和约束不跟 AI 讨论实现细节。我当时给 AI agent 的提示词大概是这样的请阅读项目根目录下的 SPEC.md严格按其中的规则实现 cn-text-format 的全部导出函数。 要求不新增规格中不存在的规则不擅自扩大功能范围。所有工具函数需放在 src/ 下每个文件附带 JSDoc 注释。实现完成后根据 SPEC.md 的示例表生成对应的测试文件 tests/format.test.js。不要修改 package.json 与 README.md 之外的文件。这里有个细节值得展开为什么不直接让 AI“写一个格式化函数”而是强制它读 SPEC.md因为当你把规格文档作为输入后AI 会以规格里定义的边界为准而不是靠训练数据里的常识去“猜”排版规则。我用过一次不带规格的对话AI 自己脑补了繁体转简体、去 HTML 标签、Markdown 渲染等一堆无关功能全部背离了系统级规格里的“非目标”约束。带上 SPEC.md 之后这种情况几乎消失了。3.3 任务拆分与并行实现Feature spec 拆成 Task spec排版包功能不算多但也可以拆成几个相对独立的任务级规格方便 AI agent 分批执行Task-01实现空白字符处理函数F-01、F-02、F-03、F-06Task-02实现标点规范化函数F-04、F-05、F-07Task-03实现特殊片段保护机制F-08URL/邮箱/代码块识别与占位替换Task-04实现主入口串联所有规则并保证规则按优先级执行每个任务级规格我都写清楚了函数签名、输入输出类型、引用到的功能规则编号以及和相邻任务的关系。比如 Task-03 的保护机制它的“保护”行为必须在其他任务之前执行否则 URL 里的点号会被 F-04 误伤。这个执行顺序不是在代码里靠运气实现的而是在任务级规格里直接写明。当时 AI agent 生成主入口代码时第一版没有把“保护”作为前置步骤而是先做标点规范化再做保护结果https://a.b.com被改成了https//a.b。com。这个 bug 靠测试用例抓出来后我把失败案例补充进了 SPEC.md 的 F-08 示例表二次生成的代码就正确了。这就是“测试反馈修正修正沉淀回规格”的闭环价值。3.4 人机协作边界哪些必须人来做哪些放心交给 AI用 SDD 和 AI agent 协作一段时间后我对“什么交给人、什么交给 AI”有了比较清晰的判断。更适合交给 AI 的部分按规则写代码、生成单元测试用例、补充 JSDoc、处理重复性重构。这些工作本质上是把规格翻译成代码AI 干得又快又稳。尤其是“根据示例生成测试代码”AI 效率极高我几乎不用改动。必须人来负责的部分规格里的价值判断。比如“中英文之间加空格”这条规则AI 不会质疑它是否合理但如果你的产品是诗歌排版这条规则可能反而会破坏原有的排版节奏。另外不同规则之间的优先级权衡、非目标的划定、对外 API 的设计这些都需要人来做决策AI 只能执行。还有一点印象很深AI 非常容易“过度拟合”示例。我在 F-08 里写了“URL 不做修改”AI 实现时真的只保护了带http://的字符串却漏掉了www.example.com这种不带协议头的域名。后来我把这条规则改成“凡匹配 URL 模式含协议头和非协议头的文本片段一律跳过排版”并在规格里补了www.example.com/test的测试用例AI 的修正才覆盖到位。4. npm 包发布全程从本地测试到 registry 上线的坑与解法4.1 本地验证先跑测试再跑 lint双保险AI agent 把代码生成完毕后我没有马上发布而是先把本地验证走了一遍。整个验证流程分三层第一层是单元测试。我把 SPEC.md 里的示例表转成实际测试用例跑一遍看有没有失败。测试框架选的是 Node 内置的node:test零依赖省得为了一个试点项目引入 Jest。测试文件里除了规格里的输入输出还额外补了一批随机文本用例比如中英文标点混合的长段落防止 AI 只做了“背答案式”实现。第二层是多版本 Node 兼容性。包声明支持 Node 16 以上但我在 Node 16、18、20 三个版本下各跑了一次测试确认没有用到哪个版本独有的 API。这一步容易被忽略尤其当你本机跑的是最新版 Node 时AI 生成代码可能会不自觉地用到比较新的语言特性导致降级安装的用户直接报错。第三层是 lint。顺手配了 ESLint规则用比较宽松的 recommended 集主要抓未使用变量和明显错误。AI 生成的代码一般不会有大问题但偶尔会有未引用的常量这类小瑕疵lint 能快速扫出来。这三层跑完我才会进入 npm pack 阶段做本地安装模拟确认包的入口文件和导出方式没问题。4.2 npm publish 的正确打开方式版本号、文件白名单、实际发布发布 npm 包本身不复杂但也有几个容易踩坑的细节。先看 package.json 的几个关键字段{ name: cn-text-format, version: 0.1.0, description: 轻量中文排版规范化工具, main: src/index.js, exports: { .: { require: ./src/index.js, import: ./src/index.mjs } }, files: [src, README.md, LICENSE], scripts: { test: node --test tests/, prepublishOnly: npm test } }我用了exports字段同时支持 CommonJS 和 ESM 两种引入方式这是现代 npm 包比较推荐的写法。files字段声明发布时只打包src目录和两个文档文件避免把.git、tests、node_modules等无关内容推上去控制包体积。prepublishOnly脚本会在执行npm publish前自动跑测试这个设计很实用。它保证了推上 registry 的代码必然是经过测试的版本这个习惯最好从第一个包就养成。执行发布前我建议先跑一次npm pack --dry-run它会模拟打包过程并列出最终会进入 tarball 的文件列表。我见过不少新手把本地无关文件传到 npm 上源头就是没做这一步检查。确认文件列表干净后再执行npm publish --access public作用域包需要显式指定 access看到 cn-text-format0.1.0这行输出就算正式上线了。4.3 发布过程中遇到的典型问题速查表真正发布的时候我在环境配置和依赖安装环节踩了几个经典坑这里整理成速查表按症状、原因、解法排列问题现象根本原因解决办法PowerShell 下执行npm报 “无法加载文件 ...npm.ps1因为在此系统上禁止运行脚本”Windows 默认执行策略禁止运行 PowerShell 脚本以管理员身份运行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned或改用 cmd 执行 npm执行npm install报cert_has_expired本地配置的 registry 地址 HTTPS 证书过期常见于旧的淘宝镜像域名检查.npmrc中 registry 配置更新为有效镜像源或官方源https://registry.npmjs.org/安装依赖时提示node-domexception1.0.0已弃用传递依赖里的老包标记为 deprecated不影响正常使用忽略即可若想消除警告升级相关依赖版本提示npm WARN using --force --recommended protections disabled用了npm install --forcenpm 关闭默认保护机制避免使用--force如需强制操作明确知晓后果后再执行报错ERESOLVE unable to resolve dependency tree依赖版本冲突npm 无法解析依赖树用npm install --legacy-peer-deps临时解决或手动对齐依赖版本内网环境下需要用私有仓库公司/团队要求包发布到内部 Nexus 等仓库在项目级.npmrc指向内网 registry发布时指定npm publish --registryhttp://内网地址这些坑里我花时间最多的是第一个 PowerShell 执行策略的问题。这其实是 Windows 环境下 Node 开发者的老朋友了npm 被安装后会在全局生成npm.ps1这个 PowerShell 脚本而 Windows 默认对脚本执行有限制导致你一敲npm就报错。解决方式我在表格里写了但这里多说一句RemoteSigned只对本地脚本放行远程下载的脚本仍然需要签名安全姿态是合理的可以放心设置。4.4 细节优化包体积、README 与语义化版本发布完之后还有几个细节能提升包的质量。第一是包体积。我发布后特意去 npm 页面看了眼文件大小因为 RN 生态里有个习惯是“一个工具函数也尽可能精简”发布包时看到里面只有src目录下的几 KB 代码心里才踏实。如果发现包体积异常可以检查files字段是否漏配以及是否把node_modules或示例目录打了进去。第二是 README。npm 包的 README 就是门面我把使用示例、规则列表、配置项说明都写了进去并在发布后通过npm view cn-text-format确认描述和版本号正确。第三是版本号策略。0.x 阶段不要太频繁发 breaking change至少保持 Minor 版本内向后兼容。后面如果要改规则默认值记得升 Minor 而不是 Patch因为默认行为变化对用户来说属于功能变更不是 bug 修复。5. 这套 SDD 流程的可复制性哪些场景适合哪些别硬套5.1 适合 SDD AI agent 的项目特征做完这个排版包我对“什么样的项目适合 SDD”有了比较具体的判断。不是所有项目都应该用 SDD适合的项目通常有三个特征第一规则可以枚举。像排版规则、数据校验规则、文本解析规则、代码转换规则这类项目的行为边界天然清晰每一条规则都能用输入输出表达写规格的成本很低验证成本也很低。第二有自动化验证手段。SDD 的闭环依赖测试去判断 AI 的产出是否符合预期。如果没有测试AI 生成的代码对不对基本靠人眼 review效率优势就被削弱了。反过来如果项目一开始就有完善的单测基础设施SDD 会如鱼得水。第三需求变更频率中等偏低。SDD 在需求相对明确的场景下效率最高。如果需求每天都在变规格文档会不断返工AI 也要跟着反复改反而比直接手写更慢。5.2 可以迁移到团队协作的实践规格即文档文档即测试这套流程对我个人最有价值的副产品是规格文档本身。以前团队协作时代码写完了需求文档可能早就过时了新人接手全靠啃代码。SDD 模式下规格文档是活的——AI 每轮修复、每轮新增规则我都会把变更回写到 SPEC.md。文档不会和代码脱节因为它是“驱动代码生成的依据”不是“事后补写的记录”。这个特性放到团队里很有意义。假设一个小组用 SDD 开发新成员接手某个模块先读 SPEC.md 就能知道这个模块设计的前因后果不需要靠聊天记录考古。Code Review 时也能对照规格逐条核对比漫无目的地看 diff 高效很多。另外规格文档里的示例表可以直接复用为测试用例。我们团队后续在另一个项目里沿用这套方法时我甚至让 AI agent 根据 SPEC.md 自动生成测试文件人工只做抽查和补充边界用例。实测下来测试覆盖率基本能到 70% 以上剩下的人工补开发速度提升很明显。5.3 想复刻这套工作流的建议起点如果有人看完这篇文章也想自己试一次我的建议是别贪大。选一个你自己日常工作中经常做的小工具——比如日志格式化、日期转换、JSON 深度合并、CSV 解析这类单模块功能按照我这篇文章的流程走一遍写系统级规格 → 拆功能规则 → 列输入输出示例表 → 交给 AI agent 实现 → 用示例表转测试 → 修复并回填规格 → 发布/提交。我自己的体会是第一次跑通这套流程的最大障碍往往不是工具而是“写规格”这个习惯的建立。写代码的人习惯了“动手”忽然让你先写一堆文字来描述“什么是对的”会觉得很别扭。但只要你坚持完成一次完整闭环看到 AI 依据一份清晰的规格产出近乎零返工的代码时你就会被这套方法说服。最后再分享一个我在这个项目里最受用的小技巧写规格时给每条规则配三组输入输出示例分别是正常情况、边界情况和异常情况。这个习惯帮我省下的返工时间比任何配置项调优都多。AI 读一万字描述不如看三行“输入长这样、输出长这样”来得可靠。这也是我从这次 SDD 实践中收获的最实在的经验。