ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从规范到发布:用SDD驾驭AI协作开发npm工具包的实践

从规范到发布:用SDD驾驭AI协作开发npm工具包的实践 老实说我一开始对让 AI 写代码这件事已经有点审美疲劳了。不是不好用是太容易翻车需求说一半AI 自由发挥一半改了三轮才发现最初的方向就偏了。直到我试着用 SDDSpec-Driven Development规范驱动开发配合 AI 做一个小工具——一个用于文案排版的 npm 包整个协作方式才突然顺了起来。写规范、拆任务、生成代码、跑测试、发布包每一步都有明确的验收标准AI 不再猜需求我也不用反复打补丁。这篇文章就把这套流程、踩过的坑、以及最终如何把包发到 npm 的完整过程记录下来希望能给同样在摸索 AI 协作开发的人一点参考。1. 把 AI 协作从聊天写代码升级成按规格施工1.1 对话式编程为什么经常失控我以前用 AI 写代码的路径基本是打开对话窗口描述一个功能等它吐出一大段实现然后复制到项目里跑。一开始确实惊艳但规模稍微上来就露馅了。比如我想做一个文本排版函数需求是把中英文之间加上空格AI 第一版只用一行正则替换。跑起来才发现问题URL 里的AI被打上了空格代码块内部的缩进被清掉全角半角标点混在一起时输出完全不可控。问题不在 AI而在需求本身。自然语言是有歧义的中英文之间到底指什么是指中文字符和 ASCII 字母之间还是单词与单词之间加空格要加在哪个位置遇到链接、代码块、引号嵌套时怎么处理如果这些边界条件没有定义AI 就只能根据概率给你猜一个答案。而这个猜的过程恰恰是不可控的根源。1.2 SDD 的六步闭环后来我在整理自己项目经验时把以前做需求分析的习惯提炼成了一套流程恰好在社区里也看到不少人把它叫作 SDD。核心不是新概念就是把开发顺序从先写代码再补测试变成先写规范再写实现。我个人实践中固化下来的六步是定义目标用一段话描述要解决的问题不给任何实现方案。编写规范把目标拆成可验证的行为规则每个规则都附输入、输出示例和反例。拆解任务按依赖关系把规范拆成小任务每个任务有明确的验收标准。AI 实现把单个任务交给 AI带上规范和测试用例让它只做这一件事。人工审查跑测试、读代码、对照规范逐条确认而不是直接信任输出。收敛迭代把 review 中发现的新边界情况变成新的规范条目回到第 2 步。这个流程放在 AI 协作里尤其顺手因为 AI 最擅长的是给定明确规则后生成实现而不是自己理解复杂模糊的业务场景。把业务判断拿回人手里把模式识别交给 AI分工就清晰了。1.3 为什么拿排版工具来练手选排版工具当实验品是因为它的输入输出足够明确非常适合验证 SDD 流程。我给你随便丢一段中文广告文案里面有中英文混排、全角半角标点混用、多余空格排版规则是要把它们处理成统一风格。这件事的规则看起来主观但只要把规则逐条定义清楚就完全可以自动化。它不像生成一个电商系统那样宏大模糊也不像写一个排序算法那样没有业务味道恰好处于 AI 能搞定和人类要拍板的中间地带。于是就有了 typo-clean 这个包一个纯 TypeScript 实现的文本排版函数库附带一个简单的 CLI专门处理中文文案里的空格、标点、引号这类常见排版问题。2. 排版包的规范拆解哪些规则可自动化哪些必须放弃2.1 需求到规范的第一步先定边界写规范的第一步不是写规则而是定义这工具不做什么。我给 typo-clean 定的边界很明确它只处理纯文本和 Markdown 文本不碰 PDF、不排版图片、不做段落重排。这个边界看着像废话但非常关键。因为一旦边界不清晰AI 在实现时就会自作主张往里面塞功能比如给你输出一个 HTML 解析器或者把换行全改成段落标签。边界定完后才开始定义核心规则。我给整个工具定了 8 条初始规则全部围绕中文文案排版规则编号规则名称示例说明R1中西文之间加空格AI赋能 → AI 赋能在 CJK 字符和 ASCII 字母/数字之间插入空格R2统一标点形式你好,world → 你好world中文语境使用全角逗号、句号R3引号整理AI → AI中文语境使用弯引号但代码块内不处理R4压缩重复标点太好了!!! → 太好了连续重复标点只保留一个R5清理行尾空白你好 \n → 你好\n去除每行末尾多余空格R6数字与单位不拆5G网络 → 5G 网络5G内部不加空格但后面紧跟中文时加空格R7保护代码块\nconst a1\n 原样保留代码块内部不应用任何规则R8保护 URL访问https://example.com → 访问 https://example.comURL 作为整体内部不加空格这里最反直觉的一条是 R8。常规正则替换很容易把 URL 内部的连字符、点号、斜杠都当成边界处理结果把链接搞得支离破碎。所以规范里必须明确URL 是不可分割的整体处理时先提取保护处理完再放回去。2.2 每条规则都得有正例和反例规范文档里只有规则描述是不够的AI 和人都会对自然语言产生不同理解。我给每条规则都配了至少一个正例和一个反例。正例说明什么情况要处理反例说明什么情况不要处理。R1 的反例就很有意思英文单词内部不能加空格Transformer 不能被处理成 Trans former再有就是 Markdown 标题语法后不能加##AI 应该变成 ## AIATX 标题标记和标题文本之间需要空格但这种空格是 Markdown 结构性空白不是排版规则该管的。我干脆在规范里写死R1 只处理 CJK 字符和 ASCII 字母/数字相邻的边界不处理字符串内部、不处理标记符号。这样一个一个抠出来规范文档写到第 35 条时已经非常长了但每条规则长度只有一两行示例。事实证明这部分投入非常值AI 后续生成的代码几乎不需要返工。2.3 从规范直接生成测试用例规范里的正例和反例直接就是测试用例。我把 spec.md 放在仓库根目录用 Markdown 表格维护规则然后用一个脚本把这些示例对解析成 Vitest 测试。也就是说改规范表格里的任何一行示例测试代码就会自动变化。这样做的好处是规范永远和测试同步AI 拿到的验收标准就是测试本身不存在文档说一套测试测另一套的情况。3. 六步 SDD 实操记录从规范文件到第一版提交3.1 目标定义和任务拆解项目启动时我只写了一段话放在 README 顶部目标是提供一个 npm 库能让开发者输入一段中文/中英混排的文案输出经过统一排版规则的文本。用户主要通过函数调用或简单 CLI 使用不需要图形界面。排版规则以 spec.md 为准。这段话后来基本没有改过它给整个项目定住了风向。AGENTS.md 里有一段更长的描述但核心就是这几句AI 每次读上下文时先看到这个不会跑偏。任务拆解则是按照依赖顺序核心格式化引擎输入字符串输出处理后的字符串内部按规则顺序执行。保护机制先把 URL、代码块、行内代码提取出来处理完再还原。规则实现每 2-3 条规则一组一个任务一个分支。CLI 入口用 Node.js 解析命令行参数读取文件或标准输入。打包发布生成类型声明配置 package.json 的入口文件验证发布结果。这个顺序很重要。一开始执行保护机制后面的规则就会在安全环境里工作极大降低正则误伤概率。3.2 AI 实现阶段一条任务一个合并请求到 AI 实现阶段我不再让它一口气生成整个项目。每一个子任务拆出来时都已经带着对应的测试文件。比如 R1 的实现任务我先提交了一个只有测试的文件测试里写着AI赋能 → AI 赋能然后把 spec.md 片段一起提供给 AI让它只去实现 typoCleanR1 这个函数。AI 第一次给出的实现是function typoCleanR1(text: string): string { return text.replace(/([\u4e00-\u9fff])([A-Za-z0-9])/g, $1 $2) .replace(/([A-Za-z0-9])([\u4e00-\u9fff])/g, $1 $2); }这个版本看起来没问题但只过了最基本的测试。真正的问题出现在组合场景当字符串里同时包含 URL 和中文时比如访问https://example.com/AI助手它会先经过 R1把AI助手处理成AI 助手但 URL 里的com/AI也会被处理成com/ AI并不会因为com/后是A前面是m/是 ASCII 和斜杠相邻R1 不会触发。可问题出在别的地方如果 URL 里包含了形如zh-cn的段R1 的正则并不会触碰连字符但后面 R8 保护逻辑还没生效时R1 就已经在里面改了不该改的东西。这个问题的根源是规则之间的顺序耦合。我最后决定在架构层把所有保护逻辑提前先提取 URL 和代码块保留位置占位符再执行 R1 到 R6最后还原。这个改动既不是 AI 主动提出来的也不是我拍脑袋拍出来的而是在 review 阶段对照规范时发现 R1 和 R8 存在冲突后做的设计决策。3.3 人工审查时我具体看什么很多人让 AI 写完代码就直接合并最多跑一遍测试。但我的经验是测试通过只说明规则被满足不代表实现方式是对的。我会重点看三个地方是否修改了规范之外的行为比如 AI 可能顺手把换行符统一成 LF这在 Windows 环境下属于越权必须禁止。正则是否有潜在的灾难性回溯排版工具可能跑在长文本上一个复杂度很高的正则就能拖垮页面。规则之间有没有隐式依赖比如先替换标点还是先加空格顺序不同结果可能完全不同。这些审查点我在第一次使用 AI 时踩过后来全部写进了审查清单每次提交代码时按清单过一遍效率很高。4. 最容易翻车的三处实现细节链接保护、成对符号和边界空格4.1 预处理领先于一切规则typo-clean 的最终架构和大多数人想象的一堆正则顺序执行不同。我采用了两阶段处理export function cleanTypo(text: string): string { const tokens: Token[] []; const content protectTokens(text, tokens); const cleaned applyRules(content); return restoreTokens(cleaned, tokens); }protectTokens 负责把所有代码块、行内代码、URL、以及不需要处理的占位符提取出来替换成不可见字符加索引的形式。applyRules 在安全文本上操作restoreTokens 最后把原始内容塞回去。这个设计的直接收益是URL 内部的字符永远不会被误处理。URL 里出现的下划线、斜杠、点号在保护阶段就离开了规则作用域。这个方案其实不复杂代码量也很小但是结构化地解决了 AI 原生实现里最容易出现的问题。后来我测试了 100 个真实 URL 和 20 个代码块零误伤。4.2 成对符号的状态处理另一个翻车点是引号。中文排版规范里弯引号要成对出现左引号和右引号不能搞混。简单的正则替换会把所有直引号变成同一个方向的弯引号输出像这是一个 测试 文本里的引号方向完全错乱。AI 在这个问题上的第一版实现是text text.replace(//g, \u201C); // 全部变成左引号这版跑测试直接红了因为规范里写了右引号对应的用例。实现正确逻辑需要跟踪状态遇到一个引号时判断它是左还是右取决于上一个引号的状态、上下文是否为中文引用的开头。我最后用了一个双向状态标志来解决代码大致是let open false; let result ; for (const ch of text) { if (ch ) { result open ? \u201D : \u201C; open !open; } else { result ch; } }这段代码是 AI 生成的但我在审查时发现一个缺陷如果文本是从中间截断的或者引号数量本身是奇数会出现方向错位。于是我在规范里加了一条当引号数量不配对时保留原始字符不强行转换。这属于典型的规范越细AI 输出越稳的例子。4.3 边界空格不是越细越好加空格的规则里数字与单位不拆是个很微妙的边界。规范定义数字紧随英文字母时数字和字母之间不加空格但在数字单位英文字母整体和中文相邻时整体作为单词加空格。5G网络 → 5G 网络但5G内部不加空格。AI 在这里给出的正则方案是text.replace(/([0-9])([A-Za-z])/g, $1 $2)直接把5G拆成了5 G这是我一开始最担心的误伤。后来在规范里明确数字与字母之间是否存在空格依据字典表判断并为常见的单位G、GB、KB、cm、mm、Hz 等建立了一张白名单。这个表不长但解决了 AI 无法通过上下文准确判断的行业术语问题。这里收获很大排版规范有些部分需要精确到字符级别但也有些部分需要放到领域知识层面不是 AI 从文本模式中就能学出来的。5. 发布 npm 包的过程比写代码更折腾PowerShell、镜像源和 files 字段5.1 Windows 环境下的 npm.ps1 执行策略问题写完代码只是万里长征第一步发布 npm 包时我才发现本机环境一堆问题。最常见的那个报错就是npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本这是 PowerShell 的执行策略限制了 .ps1 脚本运行而 npm 的 Windows 安装包默认是通过 npm.ps1 调用的。解决办法不是关掉整个执行策略而是只针对当前用户放开Set-ExecutionPolicy -Scope CurrentUser RemoteSigned这里要注意别顺手写成Set-ExecutionPolicy Unrestricted那会把机器置于不可控状态。如果你在公司电脑上最好先和本地安全策略确认一下避免影响其他脚本的权限审计。5.2 镜像源过期导致证书报错发布前我想先跑一轮 npm install 验证依赖结果屏幕上出现了一堆npm ERR! code CERT_HAS_EXPIRED。仔细看错误信息里面访问的是https://registry.npm.taobao.org/...。这其实是一个历史遗留问题早前我为了提速把 registry 切到了淘宝镜像但老地址的证书已经过期。新版镜像源已经迁移到了https://registry.npmmirror.com。排查方式很简单先看当前 registrynpm config get registry然后统一改成新版镜像源或官方源npm config set registry https://registry.npmmirror.com如果你用的是公司内部 Nexus 仓库同理需要确认证书链是否完整。这里给出的经验和发不了包的关系很多人要等到发布时才能体会到。5.3 发布前先用 npm pack 检查包内容真正执行npm publish之前我建议先跑一遍npm pack --dry-run这个命令会列出最终打到 npm 包里的所有文件。我在这一步发现了两个问题一是src/目录也被打包进去了而我并不想让消费者看到 TypeScript 源码二是spec.md没有被包含但我想让使用者能查看规则说明。解决方案是在 package.json 里显式配置 files 字段files: [ dist, spec.md, README.md ]files 字段是白名单机制只要写清楚就不会把任何多余文件带进去。同时确认main指向 dist/index.jstypes指向 dist/index.d.ts。这一步不做好最容易出现的情况就是包发上去了别人import进来却是 undefined。5.4 发布后必须做的冒烟测试发布成功不代表万事大吉。我会在另一个干净目录里重新安装一遍npm i -g typo-clean echo AI赋能 | typo-clean输出正确后再用 Node 跑一次函数调用node -e const { cleanTypo } require(typo-clean); console.log(cleanTypo(AI赋能))两次都通过才说明这个包在真实环境里可用。这个流程看起来繁琐但能筛掉一大半类型声明挺好跑起来报错的尴尬情况。实测下来发布一个纯 TypeScript 写的工具包最容易出问题的往往不是业务逻辑而是exports字段没配好导致 ESM 和 CJS 环境下的加载行为不一致。6. 尝到甜头之后我现在怎么安排 AI 与人工的分工6.1 AI 负责模式识别人负责边界定义这个项目做完后我最大的感触是AI 的能力边界不在代码生成而在业务边界的判断。AI 可以写出非常漂亮的实现但它不知道数字单位白名单该包含哪些词不知道 URL 在中文排版里应该整体保留更不知道弯引号不配对时就保留原样这种面向异常的策略。这些边界定义必须由人来完成而且越早写进规范后面返工越少。SDD 的价值就在于它逼着你在编码之前把这些边界想清楚。没有这套流程时我可能写完正则才开始想边界结果被各种误伤追着跑。有了规范之后AI 的实现质量明显提升review 的时间反而大幅缩短。6.2 我还在坚持的几条习惯项目结束后我把这套流程沉淀进了自己的项目模板现在已经成了固定习惯每个项目的根目录都有一份 spec.md所有业务规则的唯一权威来源。任何 AI 产生的代码变更必须对应一个测试用例不能只有代码没有测试。规则的分裂或修改先改 spec再改代码顺序不能反。每次 AI 生成了意料之外的好方案我会回填进规范作为参考实现知识库越攒越多。这个习惯给我带来的直接变化是AI 生成代码的采纳率从原来的不到一半提高到了八成以上。剩下的两成不是代码质量问题而是业务逻辑本来就没定义清楚。说到底AI 并不是替你写代码而是替你执行你已经写好的规范。规范写得越清晰AI 协作的体验就越接近靠谱同事而不是猜谜选手。做 typo-clean 这个排版包只是个小项目但验证下来的这套协作范式我觉得值得每个人都试一次。
返回列表