ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SDD规范驱动AI开发:打造中英文排版npm包

2026/9/5 9:55:12 拓冰建站 浏览量
SDD规范驱动AI开发:打造中英文排版npm包 1. 为什么我选择用 SDD 来做这个 npm 包先说一下背景。我最近一直在琢磨中英文混排的排版问题想做一个轻量的排版工具包解决两个痛点一是网页里中英文之间没有空格导致的阅读拥挤二是标点符号在换行时被折断的显示问题。这类需求其实不算新市面上也有类似方案但我想做一个更聚焦、更可配置、零依赖的版本顺便走一遍从需求到发布 npm 的完整流程。本来这件事我自己手写也就两三天但我最近正好在研究 AI 协作开发的新玩法尤其是从 vibe coding那种让 AI 自由发挥、自己只看结果的粗放模式往更结构化、更可控的方向过渡。这个过渡的关键方法论就是 SDD——Spec-Driven Development规范驱动开发。Thoughtworks 的 Birgitta Böckeler 提出过 SDD 的三级分类框架她把人机协作分成三种模式AI 作为研究工具、AI 作为结对编程伙伴、AI 作为实现者。我这次明显是想让 AI 承担第三种角色但又不想让它放飞自我所以我决定把 SDD 作为整个开发流程的主线。SDD 的核心思想其实很简单先写清楚做什么、为什么做、怎么验证再让 AI比如 Claude Code、Codex 这类 agent去写代码。规范的优先级高于结果这就解决了 vibe coding 最大的问题——AI 写出来的东西看着能跑但你不清楚它为什么这么做也没法验证它做对了没有。说得直白一点vibe coding 是“AI 写代码人看结果”SDD 是“人写规范AI 写实现测试做裁判”。这次的项目正好适合拿来实践 SDD排版规则边界清晰、需求可枚举、测试容易写、结果可量化。单测一跑空格插没插对、标点有没有被保护一目了然。我不需要跟 AI 反复描述“这里感觉有点挤”只需要把规范写明白什么字符之间要加空格、什么情况不加、标点怎么处理AI 照做就行。然后就是工程选型。包名我定为text-flow功能定位是文本排版处理自动在中英文之间插入空格、压缩连续空格、处理标点换行保护。技术栈非常克制TypeScript 写源码、Vitest 做单元测试、tsup 打包、npm 发布。零运行时依赖输出 ESM 和 CJS 双格式。这个选型不是拍脑袋排版工具的核心价值在于规则的可信度而不在于依赖了多少框架。依赖越少维护成本越低出问题的概率也越小。2. SDD 的三级分类框架以及我选的是哪一级既然主题是 SDD先把方法论层面的东西理清楚。2.1 Birgitta Böckeler 的三级分类到底在说什么Birgitta Böckeler 是 Thoughtworks 的杰出工程师她对 AI 辅助软件开发提出了一个非常实用的分类框架把 AI 在开发中的角色分成三个层级第一级AI 作为研究工具。用来搜索 API 用法、查文档、做技术选型调研。这个层级下 AI 不写代码价值在于信息检索和初步判断。第二级AI 作为结对编程伙伴。人和 AI 一起设计、一起写、一起审查。AI 能提出建议人来做决策双方是互动关系。第三级AI 作为实现者。人把需求拆成清晰的规范交给 AIAI 直接产出代码人来验证结果。我这次做排版 npm 包明显落在了第三级。但我个人感受是即便在第三级人也不能当甩手掌柜。你把规范写得越精确AI 产出的质量就越稳定。这跟带新人很像你把上下文和验收标准讲清楚了新人写出来的东西才靠谱你只说“把排版做一下”那结果基本靠猜。2.2 SDD 与 vibe coding 的本质区别规范先行测试兜底现在很多人推崇 vibe coding 的效率让 AI 一口气生成整个项目。但我试过几次之后发现一个问题AI 生成的东西往往结构漂亮、注释齐全可是在边界条件上一塌糊涂。比如处理标点换行保护时AI 可能会把英文缩写里的句点当成句子结束来处理这种问题不写测试是发现不了的。SDD 正好补上这个短板。规范先行意味着你把期望的行为用自然语言写清楚测试兜底意味着你把期望的结果用代码固定下来。AI 可以自由选择实现路径但行为必须符合规范结果必须通过测试。这样一来AI 的创造力和人的控制力就兼得了。这次做text-flow包我给自己定了一个 SDD 工作流写规范Spec→ 让 AI 实现Implement→ 跑测试Verify→ 有问题改规范或改代码Iterate。这个流程跟传统的 TDD 有点相似但最大的区别是TDD 是先写测试再写实现SDD 是先写行为规范再写实现测试只是验证手段。规范描述的是“应该怎样”测试验证的是“实际怎样”。2.3 OpenSpec让规范不再是文档孤岛说句实话SDD 的理念很早就有人提但一直缺一个落地的载体。如果你用 Word 写一份几十页的规范文档扔给 AI它根本没法有效消费。这个问题的解法我找到的是 OpenSpec一个开源的项目规范管理规范meta-spec。OpenSpec 的玩法是把规范拆成项目里的结构化文件放置于openspec/目录下每个变更请求change request就是一个子目录里面包含需求背景Context、变更说明Requirements、验收标准Acceptance Criteria等几个 Markdown 文件。这样 AI 可以直接读取这些文件作为上下文人也可以像审查代码一样审查规范本身。我用 OpenSpec 做text-flow这个包时亲身体会到它的一个好处规范和代码在同一个仓库里不会漂移。需求变了先改规范文件再让 AI 改代码代码和文档永远对得上。3. 从规范到实现这次排版包的核心需求拆解3.1 排版问题定位中文与英文、数字之间的留白我做text-flow这个 npm 包最原始的痛点来自阅读体验。中文是方块字英文是字母串两者之间如果没有空格视觉上会粘在一起。举个例子“使用SDD开发npm包”这句话里“用”和“SDD”、“发”和“npm”之间都缺一个空隙读起来很挤。规范第一步就是定义哪些字符组合之间需要自动插入半角空格我总结了四个规则中文CJK 字符与英文ASCII 字母之间需要插入空格中文与数字之间需要插入空格中文与http://、https://之类 URL 之间URL 整体视为一个英文词连续多个空格全角或半角一律压缩为单个半角空格。这个规则很朴素但实现的时候要注意 Unicode 范围。不能只考虑中文字符\u4e00-\u9fa5还要考虑扩展区、注音符号、日文假名等 CJK 字符。我这次先把范围限定在常用 CJK 统一表意文字区域内之后再通过配置扩展。3.2 标点换行保护防止中文标点出现在行首第二个痛点是中文标点的换行问题。在 HTML 或者某些富文本场景下标点符号可能会被单独挤到下一行的开头。比如逗号、句号、感叹号出现在行首这在中文排版规范里是不能接受的。解决思路有两个层面。一个是在文本处理层面做“标点悬挂”把行首的标点合并到上一行末尾这通常需要配合排版引擎另一个是在字符串层面把标点前的空格去掉减少换行机会。对于 npm 包来说我能做的是处理文本内容本身把标点前的空格压缩掉避免标点和前面的文字被拆开。还要提一下弯引号和直引号的处理。中文排版习惯用弯引号“”‘’但很多技术文档输入的是直引号。作为排版工具我要提供一个选项把成对的直引号转成弯引号并且保证转换后的引号不出现在行首。3.3 可配置性不是所有场景都需要自动加空格不同项目的排版偏好差异很大。技术文档喜欢在中英文之间加空格但新闻类中文稿件可能就不加。所以text-flow不能写死规则需要提供配置选项。我在规范里设计了三个开关spacing.enabled是否启用中英文空格插入默认truespacing.cjk指定哪些 Unicode 区块算作 CJK 字符默认是常用中文punctuation.hanging是否启用标点行首保护默认true。配置层级要支持全局和单次调用两级覆盖。这样既有合理的默认值又能灵活扩展。规范文档里我把这些选项的定义、类型、默认值都写清楚了后续实现和测试都有据可依。4. 实操过程OpenSpec 初始化到 AI 生成代码的完整记录这一节是重点我尽可能把实际操作中的细节还原出来方便想复刻这套工作流的朋友直接抄作业。4.1 初始化 OpenSpec 工作区首先我在项目根目录创建了openspec/目录结构mkdir -p openspec/changes/add-text-flow然后在该目录下创建了四个文件context.md、requirements.md、acceptance-criteria.md和task.md。context.md描述项目背景和动机核心就一句话当前中文互联网内容排版质量参差不齐大多通过人工加空格解决效率低且不一致需要一个自动化排版工具。requirements.md是规范的主体我把上一节梳理的规则全部写进去每条规则都尽量做到无歧义。比如“在中文字符和英文字母之间插入一个半角空格U0020”就比“在中文和英文之间加空格”要精确得多。acceptance-criteria.md是验收标准每个标准都必须可测试。例如输入使用SDD开发输出必须是使用 SDD 开发输入他说Hello, world输出中必须移除world和之间的所有空格。这些验收标准最终就是我写单测的依据。task.md是给实现者的操作清单。我把任务拆成三步第一步实现纯函数applySpacing第二步实现format主函数第三步整理类型声明并导出。4.2 AI Agent 的角色选择与环境准备环境和工具链我用的是 Node.js 18 和 npm 9。AI 代理这边我试了两种方案一是 Claude Code 命令行工具全局安装命令是npm i -g anthropic-ai/claude-codelatest二是 OpenAI 的 Codex CLI安装命令是npm install -g openai/codex。两个都能胜任这个任务我最后用的是 Claude Code因为它对长上下文的理解更稳而且对 OpenSpec 这类结构化的 markdown 规范文件解析得比较准。在正式开工之前还有一个经常被忽略的准备工作让 AI 先读一遍规范文件。我跟 AI 说的是“先读openspec/changes/add-text-flow/下的所有文件然后开始实现”而不是直接说“帮我写代码”。这一步很关键AI 接受到规范的先后顺序会影响它对任务的优先级判断。4.3 让 AI 实现核心逻辑从加载规范到跑通测试AI 读完规范后我让它先搭建工程骨架初始化package.json、安装 TypeScript、配置 tsconfig、装 Vitest 和 tsup。这个环节 AI 做得很流畅没有人为干预。接着是核心实现。我给它的指令很简单“根据 requirements.md 中的规则实现src/index.ts要求导出format和applySpacing两个函数类型声明使用 TypeScript 接口。”AI 生成的第一版核心逻辑大概是这样的结构我做了一些简化// src/index.ts const CJK_REGEX /[\u4e00-\u9fa5]/; const LATIN_REGEX /[A-Za-z0-9]/; export function applySpacing(input: string): string { return input.replace( /([\u4e00-\u9fa5])([A-Za-z0-9])|([A-Za-z0-9])([\u4e00-\u9fa5])/g, (_match, cjk1, latin1, latin2, cjk2) { return ${cjk1 || latin2} ${latin1 || cjk2}; } ); } export function format(input: string, options?: FormatOptions): string { const spacingEnabled options?.spacing?.enabled ?? true; let result input; if (spacingEnabled) { result applySpacing(result); } result result.replace(/[ \t]/g, ); result result.replace(/ ([。、])/g, $1); return result.trim(); }第一版能跑通简单的测试但在边界条件上存在问题。比如它只处理了常用汉字和 ASCII 字母数字的匹配没有覆盖全角标点前插入空格后又被误伤的情况。我跑了一遍验收标准发现三个用例没过输入他说Hello,world输出变成了他说Hello, world但验收标准期望的是他说Hello, world这个倒是通过了输入你好world 输出里world和之间还有一个空格但我的验收标准要求不保留这个空格输入已经测试通过✅表情符号前后的间距没有被规范化。4.4 用测试驱动 AI 修正把验收标准翻译成单测我没有直接给 AI 讲怎么改代码而是把acceptance-criteria.md里的每一条都翻译成了 Vitest 的单元测试写进src/index.test.ts然后运行npx vitest run让它看失败信息。这一步操作的意义在于把“规范期望”翻译成“代码可验证的事实”AI 看到失败断言之后会更有针对性地调整实现而不是自己在脑子里猜。实测下来AI 看到断言失败后的修复效率远高于空泛地让它“优化一下”。AI 第二版修复了标点空格问题正则改为在压缩空格之后再处理标点行首的情况并且把表情符号这类非 CJK 字符也纳入宽度判断条件。第二版跑通了所有我预设的 12 条测试用例。这里分享一个心得让 AI 修 bug 的时候不要一次性丢给它十个失败用例而是两三个一组地给。AI 的上下文窗口虽然越来越大但分组给能让它聚焦在局部问题上修复质量更高。5. 遇到的坑npm 安装执行权限与过期证书AI 生成代码只占了这次开发的一部分工作量真正麻烦的事情在工程化和发布环节。这块我相信不少朋友都遇到过因为相关报表非常普遍。5.1 Windows 下 npm 脚本被禁止执行的问题我在 Windows 上运行npx vitest run时遇到了经典的报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本这个问题的原因是 PowerShell 的执行策略Execution Policy默认是Restricted不允许运行本地 PowerShell 脚本。npm 命令本身是一个.ps1文件所以在 PowerShell 中调用时会被拦截。解决办法有两个以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned然后重新打开终端或者在当前用户级别执行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned。我建议用第二种影响范围小不用动系统全局策略。执行完之后再运行 npm 命令就不会报权限错误了。必须提醒一下RemoteSigned策略的意思是“本地脚本可以运行远程下载的脚本必须有签名”。这个策略相对安全比直接Unrestricted要好得多。5.2 npm 镜像源证书过期安装依赖失败另一个高频问题是 npm 源证书过期。我遇到的是npm ERR! code CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/vuex-along/download/vuex-along-1.2.11.tgz failed, reason: certificate has expired这是因为某些历史镜像域名如 registry.npm.taobao.org的 TLS 证书已经过期或者镜像本身已经停止维护但你的 npm 配置里还残留着旧的镜像地址。解决办法是把 registry 换回官方源或者切换到当前可用的新镜像。我的做法是直接重置到官方源npm config set registry https://registry.npmjs.org/如果确实需要加速可以换成新的镜像域名并注意证书是否有效。但作为个人项目官方源已经完全够用。还有一个隐藏的坑即使 registry 配置看起来正常npm 缓存里可能还存着旧证书的数据。极端情况下要清缓存npm cache clean --force这个命令慎用它会删除所有本地缓存下次安装会重新下载所有包耗时较长。5.3 打包与发布从 tsup 到 npm publish 的心路历程编译用 tsup 很省心。它在tsup.config.ts里配置入口和输出格式import { defineConfig } from tsup; export default defineConfig({ entry: [src/index.ts], format: [esm, cjs], dts: true, clean: true, sourcemap: true, });这里值得说一下format为什么要输出双格式。现在前端项目大多走 ESM但不少旧的 Node.js 项目还在用 CJS。双格式输出能让使用者无论用什么模块系统都能直接import或require这也是一个成熟 npm 包的基本素质。dts: true会自动生成.d.ts类型声明文件这也是必须的。TypeScript 项目如果没有类型声明使用体验会非常糟糕。打包完成后发布到 npm。发布之前有几个检查项package.json里的name是否在 npm 上已经存在files字段是否只包含打包产物dist和 README避免把源码和临时文件一起发布main、module、types字段是否指向正确的文件路径version号是否递增。我第一次发布时踩了一个小坑package.json 里没有加files字段结果把src目录和openspec目录都发布上去了包体瞬间大了十倍。后来补上{ files: [dist] }之后发布的包体积只有几 KB清爽不少。npm 发布命令很简单npm publish如果包名已经被占用npm 会报403错误这时候要么换名字要么加 scoped 前缀比如yourname/text-flow。6. 常见问题与排查速查表这次开发过程中我总结了一些高频问题按出现频率排个序做成一个速查表供大家对照排查。问题现象可能原因解决办法npm.ps1无法加载禁止运行脚本PowerShell 执行策略限制Set-ExecutionPolicy -Scope CurrentUser RemoteSignedCERT_HAS_EXPIRED安装依赖失败镜像源证书过期npm config set registry https://registry.npmjs.org/npm不是内部或外部命令Node.js 未安装或 PATH 未配置重装 Node.js检查环境变量PATH是否包含 Node.js 安装目录npm publish报 403包名已被占用或未登录npm login登录或更换包名打包后类型声明缺失tsup 配置没有开dts在tsup.config.ts中设置dts: true发布到 npm 后包体积过大未配置files字段在 package.json 中限制files: [dist]npm install卡住不动网络问题或源不稳定换镜像源或使用代理合法网络环境下调整网络配置有一条经验我非常想分享排查 npm 问题时第一步永远是npm config list和npm config get registry先看配置再看报错。很多人遇到安装失败就在网上搜报错文案但忽略了本地配置已经被人修改过的事实。7. 把这条路走通之后我对 AI 协作开发的真实感受整个text-flow包从规范到发布累计大概花了我一个周末的时间。如果用传统方式我自己写完所有代码加测试可能也要一到两天。所以单从时间上看SDD AI 的协作模式并没有显著缩短工期它真正的价值在于第一代码质量有了明确定义。验收标准先于代码存在AI 写的每一行都在为通过标准服务而不是为了“看起来正确”。第二文档和代码同步生成。OpenSpec 结构本身就是一个活的文档系统发布之后我甚至不用额外写技术文档直接让想用这个包的人去读openspec下的规范文件即可。第三人的精力从“怎么写”转移到了“怎么定义”。这对我的工作方式的改变是深远的以前我拿到需求就开始敲代码现在我更习惯先想清楚边界条件和验收标准再让 AI 去编码。当然这套模式也有明显的适用边界。SDD 适合需求清晰、可验证、有明确边界规则的任务比如工具函数、API 封装、算法实现。但如果你要做的是探索性很强的创新产品需求本身还在摇摆之中那 SDD 反而会拖慢节奏这种情况下 vibe coding 的快速试错价值更大。说白了工具没有高下之分匹配场景才是关键。SDD 和 vibe coding 不是替代关系它们站在光谱的两端你要根据任务的性质决定自己在光谱上的位置。这次用 SDD 做排版包是一个典型的“规则清晰、验证容易”的任务所以它天然适合规范驱动。最后分享一个小技巧AI 生成的代码一定要加上你的关键业务规则注释别指望它能自动理解所有上下文。我在format函数里加了几行注释说明了中英文空格插入的规则和标点保护策略以后再有别的 AI 阅读这份代码也会比较容易理解当初的设计意图。text-flow这个包发布之后我自己已经在两个小项目里用上了跑起来很稳定。后续我计划给它加上命令行工具CLI支持方便直接在终端里批量处理文本文件。到时候回头发一篇续集讲讲 AI 怎么帮我把一个库改造成 CLI 工具的。