ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用规格驱动开发(SDD)与AI编程打造可发布的npm排版包

用规格驱动开发(SDD)与AI编程打造可发布的npm排版包 最近这段时间聊 AI 编程的人基本绕不开 Vibe Coding——把需求往对话框里一扔AI 噼里啪啦生成一堆代码看着挺热闹真到要发布一个能用的 npm 包时问题全来了代码不可控、上下文越聊越飘、改一处毁三处。我这次刻意换了一个思路用 SDDSpecification-Driven Development规格驱动开发来做一个排版 npm 包把 AI 协作开发的流程完全反过来先花时间把规格写清楚再让 Claude Code 和 Codex 按图施工。两天半做下来最终产物是一个能把 Markdown 转成中文排版 Word 文档的命令行工具并且在 npm 上顺利发布。这篇文章会把整套流程、踩过的坑、以及和 AI 协作的细节全部拆开讲清楚适合正在尝试用 AI 做真实项目的朋友参考。1. 为什么要用 SDD从“AI 随手写”到“按图施工”1.1 先搞清楚 SDD 到底在解决什么问题SDD 全称是 Specification-Driven Development国内一般翻译成“规格驱动开发”或“规范驱动开发”。这个名字听起来有点学术其实核心思想特别朴素在让 AI 写任何代码之前先把“要做什么、做到什么程度、输入什么、输出什么”用一份明确的规格写下来然后让 AI 严格按照这份规格去实现。我刚开始接触这个概念时觉得这不就是传统软件开发里的需求文档吗但实际操作下来发现SDD 和传统需求文档有三个明显区别第一SDD 的规格是给人和 AI 双方看的不是在 PRD 里写一堆业务价值而是写清楚函数签名、数据结构、错误码第二SDD 的规格颗粒度往往更细细化到某个文件、某个函数的行为第三SDD 的规格要和测试强绑定每条规格基本都能对应一条验收标准AI 写完代码跑一遍测试就知道合不合格。Thoughtworks 有位工程师提过一个 SDD 的三级分类框架大致把规格分成三个层次第一层是描述项目整体目标和边界的宏观规格比如“这个包要解决中文 Markdown 转化为 Word 排版的问题”第二层是设计级规格描述模块划分、核心接口比如“parser.ts 负责解析render.ts 负责渲染”第三层是任务级规格细化到具体函数的行为。按我的理解这个分类的意义在于让开发者按需取用项目越小越可以直奔第三层项目越大越需要从第一层逐步往下拆。我这次做排版包本质上就是一个三层规格都写得比较完整的小项目所以后续和 AI 协作时几乎没遇到过“AI 天马行空乱加功能”的问题因为规格已经把它的活动范围框死了。1.2 为什么排版包适合拿来做第一个 SDD 项目如果你也想尝试 SDD我建议别一上来就做个几十个页面的中后台系统从排版类工具开始是最合适的。原因很简单这类项目的需求边界极其清晰输入是 Markdown 文档输出是格式化之后的 Word 或 HTML中间要处理的无非是标题层级、段落样式、字体字号、表格、代码块这些规则。规则一旦能列清楚规格就非常容易写AI 也不容易跑偏。另外排版包的失败成本很低。就算 AI 生成的代码有 bug最坏的结果也就是产出一个排版丑一点的 Word 文档重新跑一次命令就行不会弄坏任何线上服务。这对我这种第一次全职兼职混用 AI 开发的人来说很关键因为可以放心大胆地试错不需要一上来就面对“生产环境崩了怎么办”的压力。而且排版工具的验证成本也很低。我说白了你不需要写一堆复杂的单元测试去校验业务逻辑直接用一份真实的 Markdown 文档跑一下打开生成的 Word 一眼就能看出排版对不对。这种“肉眼可验收”的特性正好完美贴合 SDD 强调的“可验证性”规格里写一句“标题 2 级要用黑体三号加粗”AI 照着实现了你打开文档看得见摸得着比抽象接口更容易判断对错。1.3 我给自己定的 SDD 六步实践流程网上关于 SDD 六步实践的文章不少但看十遍不如做一遍。我这次实际操作时把流程固定成下面六步后续所有环节都围绕这六步展开定义边界明确输入输出、使用场景、不做哪些事情编写规格把样式规则、目录结构、接口签名全部写进 SPEC.md拆解任务把规格拆成一个个可以被 AI 独立完成的小任务实现与验证让 AI 按任务清单逐个实现每完成一个立刻跑测试集成与验收把所有模块拼起来用一批真实 Markdown 文档做整体验证复盘与沉淀把踩到的坑、AI 容易犯的错误、可复用的 Prompt 模板整理进仓库。这六步听起来简单真正难的是第二步和第三步。规格写得太大AI 一次处理不了规格写得太小又会产生大量不必要的对话轮次。我的经验是一个任务的规格描述控制在 200 到 500 字以内并且必须包含“输入是什么、输出是什么、成功标准是什么”这三要素AI 的执行质量会稳定很多。2. 规格先行给 AI 一份不产生歧义的“施工图”2.1 需求拆解与产品边界先交代一下这个包到底做什么。项目最初的痛点很实际我经常需要把 Markdown 笔记整理成排版规整的 Word 文档交付给同事每次手动调整字体字号、标题编号、首行缩进特别痛苦。所以这个包的核心需求就一句话输入一个 Markdown 文件或目录输出一份符合中文公文/文档排版习惯的 .docx 文件。为了不让需求无限膨胀我明确划了几条边界不支持复杂的页眉页脚定制不处理插入页码不做 PDF 直出不搞可视化编辑器。只做一条最扎实的主链路解析 Markdown → 规范化中英文标点和空行 → 把 AST 渲染为 Word 段落 → 输出 docx同时附带一个 HTML 预览模式方便快速检查。这个包的 CLI 用法也被我提前定义死了这一步很关键因为 CLI 接口就是 AI 实现时必须遵守的协议之一typograph-md build docs/ -o output.docx typograph-md build docs/ --format html参数就三个--input输入文件或目录、--output输出路径、--formatdocx 或 html默认 docx。除此之外不做任何奇奇怪怪的短参数避免 AI 自由发挥设计出我没见过的用法。2.2 一份能直接交付给 AI 的规格长什么样这是整篇文章里我觉得最值得参考的部分。很多人在 AI 编程时只给一句话“帮我写个解析 Markdown 的工具”然后抱怨 AI 写得不对。我这次把规格详细到“AI 看到之后没有理由反问”的程度。以下是 SPEC.md 里核心片段的结构大家可以参考这种写法输入协议只接受 UTF-8 编码的 Markdown 文件文件头部允许带 YAML front matter但 front matter 内容不得出现在任何输出内容中支持#到######六级标题。输出协议.docx文件必须使用docx库生成正文中文字体默认为宋体标题中文字体默认为黑体正文大小为 12pt小四行距固定值 28 磅首行缩进 2 字符标题自动编号一级标题为“一、二、三”二级标题为“一二”三级标题为“1. 2. 3.”四级标题为“12”。模块划分src/parser.ts负责解析 Markdown 并输出 ASTsrc/typographer.ts负责标点规范化和空行清理src/render.ts负责把 AST 渲染成 docx 的 Document 结构src/cli.ts负责解析命令行参数并调用主流程src/index.ts负责串联整个流程并定义错误码。样式矩阵用 Markdown 表格定义 Markdown 元素与 Word 样式的映射关系包括标题、正文、引用块、代码块、表格、无序列表、有序列表、图片路径处理。验收标准每一条需求都要对应一个可执行的验证方式比如“解析带 front matter 的文件时控制台输出 INFO: front matter ignored生成的文档正文从一级标题开始”。规格里最重要的不是写得多而是写得“可验证”。我特意加了一条如果 AI 对规格里的任何一句话有歧义必须先提问禁止自行假设。实测下来这条规则帮我避免了很多次“AI 以为你想要的”和“你实际想要的”之间的偏差。2.3 为什么规格要刻意强调“可验证”我从这次实践里悟出一个道理AI 写代码和人类写代码最大的不同在于人会对模糊的需求本能地提问而大多数 AI 会默默脑补一个最合理但未必是你想要的答案。比如我在第一版规格里只写了“标题自动编号”AI 就自己按 Word 原生多级列表的方式实现了结果编号格式跟我的要求完全不一样。后来我在每条规格后面追加了一个“验证”字段用一句话描述怎么判断这条规格是否达标。比如“标题自动编号解析一个包含三级标题的 Markdown生成 docx 后文档中标题文本前必须出现‘一、’、‘一’、‘1.’这样的前缀”。这个字段看着不起眼却让 AI 的实现目标和我的验收目标完全对齐了。另外可验证的规格还有一个隐藏好处它可以被转成自动化测试。我让 AI 根据“验证”字段直接生成对应的 Vitest 测试用例测试断言就是规格里的验收标准。这样一来规格、代码、测试三者的关系就闭环了规格描述行为测试验证行为代码实现行为。只要测试通过我就有信心 AI 没有自作主张乱写。3. 实操阶段和 AI Agent 结对开发排版包3.1 环境准备与工具选型开发环境我提前做了准备避免做到一半被环境问题打断思路。核心环境是 Node.js 18 LTS 和 npm 9用 nvm 管理版本避免系统全局 Node 版本冲突。项目本身用 TypeScript 编写打包用 tsup测试用 Vitest这些选型都是 AI 生态里比较成熟稳定的方案不太容易出幺蛾子。AI 协作工具我这边用了两套Claude Code 和 OpenAI Codex。实际体验下来两个工具各有长处Claude Code 在长上下文理解和按规格执行方面更稳Codex 在代码生成速度和测试补全方面更快。我自己的使用节奏是规格讨论、任务拆解用 Claude Code密集生成代码和补测试用 Codex。切换工具其实也不需要额外配置因为大家都在同一个 Git 仓库里工作只要 README 和 SPEC.md 写得足够清楚换一个 AI 来接手上一个 AI 的活也不会出现太大的理解偏差。依赖方面项目最终只用了 5 个核心依赖docx生成 Word 文档、gray-matter解析 YAML front matter、commander解析 CLI 参数、mdast-util-from-markdown把 Markdown 转成 AST、vitest测试。整个包体积控制在 200KB 以内发布之后用户安装也快。选依赖的原则就一条能不做的不做能少用的少用。我发现 AI 有时会顺手引入一堆看起来很酷但根本用不到的工具库所以我在规格里写明“禁止引入规格之外的运行时依赖”AI 就不会乱装了。3.2 把规格“喂”给 AI 的第一轮对话第一次对话特别重要因为它决定了 AI 对整个项目的认知框架。我不会一上来就说“帮我写一个 Markdown 转 Word 的工具”而是先给 AI 一段非常结构化的开场白把项目背景、文件位置、工作方式一次讲清楚。我实际用的 Prompt 大概是这样的项目目标提供一个 CLI 工具把 Markdown 转成符合中文公文排版的 .docx。 规格文件仓库根目录 SPEC.md请先完整读取并理解再开始写代码。 当前任务实现 src/parser.ts只做解析不要碰渲染。 输入markdown 字符串输出mdast 风格的 AST 对象。 成功标准npm run test:parser 全部通过。 额外要求如果 SPEC.md 里有任何不明确的地方先停下来提问不要自行假设。这段 Prompt 的关键在于“只做解析不要碰渲染”这句话。SDD 执行过程中最容易翻车的地方就是 AI 把多个模块混在一起写明明让你写 parser它连 render 的代码也给你生成了。把任务颗粒度切小一次只让 AI 完成一个模块配合测试做边界约束产出质量会明显提升。第一轮对话我让 AI 生成 parser.ts 之后立刻跑测试。第一次跑挂了两个用例原因是我在测试里要求 AST 的节点类型包含position信息但 AI 生成的解析器没有保留行列位置。我把失败信息原样贴回给 AI它只花一分钟就修好了。这个过程看起来不起眼但正是 SDD 最舒服的地方对话上下文始终聚焦在一个小任务上AI 不会因为上下文过长而忘记前面的规格。3.3 核心模块的实现细节与代码示例parser.ts的实现相对标准用mdast-util-from-markdown把 Markdown 解析成 AST再额外处理一下 front matter 的剥离。真正的难点在render.ts因为把 AST 渲染成符合中文排版规范的 docx比想象中繁琐得多。举个例子docx 库默认的 Heading 样式是英文排版习惯直接拿来用会被 Word 的默认主题覆盖。我在render.ts里用一段函数把 AST 的标题节点转成自定义 Paragraph关键代码如下import { Paragraph, TextRun } from docx; function headingToParagraph(node: any, numbering: string[], theme: Theme) { const text ${numbering[node.depth]} ${node.text}.trim(); const run new TextRun({ text, font: theme.headingFont, // 例如 黑体 size: theme.headingSizes[node.depth], // 一级 32 半磅二级 28 半磅 bold: true, }); return new Paragraph({ children: [run], spacing: { before: 240, after: 120 }, heading: Heading${Math.min(node.depth, 6)}, }); }注意这里的字体大小用的单位是“半磅”也就是 Word 里的字号。三号字是 32 半磅四号字是 28 半磅小四是 24 半磅。这个单位搞错的话生成的文档字号会离谱地大或离谱地小。AI 一开始就写错了把三号字直接写成了size: 32结果生成的字号相当于 48pt后来我在验收时发现反手就把“字号必须使用 half-point 单位”追加进了规格。typographer.ts的处理逻辑虽然简单但极其出效果。它做三件事把英文逗号、句号替换成中文逗号、句号但代码块内部不替换把连续空行压缩成一个把标题文本首尾空格清掉。正是这个模块让输出的 Word 看起来“像人排过的版”而不是 Markdown 渲染器直出的粗糙样式。3.4 AI 生成代码的验收与纠偏整个开发过程中我对 AI 生成的代码保持了“边信任边怀疑”的态度。每个模块完成后我不是直接拿过来就用而是先过一遍代码再跑测试再用一个小样例看输出。这里列几个我实际遇到并纠正过的典型问题AI 把 YAML front matter 渲染进了正文。明明规格里写了“front matter 内容不得出现在任何输出内容中”它还是在某个分支里把未解析的原始文本塞进去了。解决办法是在验收清单里加一条专门针对 front matter 的检查。中文字体名称写错写成了 “SimSun” 而不是 “宋体”。docx 库在 Windows 上两种写法都能识别但在 macOS 上可能失效所以最终在规格里明确要求字体的中文名称和英文名称同时设置。单元测试写得太敷衍几个用例都堆在一个it里断言非常弱。后来我在开发公约里加了一句“每个测试只能验证一个行为且必须包含正反两个用例”AI 生成的测试质量立刻提升了一个档次。这些纠偏过程看似琐碎却非常值得记录下来。我发现每次给 AI 纠偏之后把修正内容回写进 SPEC.mdAI 在后续任务中的相同问题出现概率就会大幅降低。这也算是一种“规格即知识库”的用法吧。4. 发布到 npm从报错地狱到成功 publish4.1 发布前的 package.json 与文件准备代码写完、本地测试全部通过后下一步是发布到 npm。这个环节我原本以为半小时能搞定实际折腾了快一个小时问题基本都出在环境上而不是代码上。先手动配置 package.json 的发布信息。我用npm init -y生成初始文件然后手动修改了这些字段name用了一个在 npm 上不冲突的名字version按语义化版本从 0.1.0 开始main指向dist/index.jstypes指向dist/index.d.tsbin配置成{ typograph-md: dist/cli.js }files字段只保留dist、README.md、LICENSE。files字段特别重要它决定了包里最终包含哪些文件避免把src目录、测试文件、.git目录一起发上去。为了让 CLI 在 Windows、macOS、Linux 上都能直接运行dist/cli.js文件头部必须加上一行 shebang#!/usr/bin/env node。这个细节容易漏漏了之后 npx 会在 Linux 上报Permission deniedWindows 上会直接报不能识别文件类型。我这次就没漏因为我提前在规格里写了AI 生成 cli.ts 时它自己加上去的。发布前我还用npm pack生成了一个本地 tgz 包解压检查里面的文件列表确认没有多余的测试目录和临时文件再执行npm publish。4.2 我踩过的几个 npm 报错与解决办法下面这个表格里的问题每个都是真实踩过的坑整理出来给你们参考报错现场原因解决办法npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本PowerShell 执行策略限制了 .ps1 脚本运行以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned或者直接改用 CMD 运行 npm 命令npm ERR! code CERT_HAS_EXPIRED请求https://registry.npm.taobao.org/...失败旧淘宝 npm 镜像源证书过期把镜像源换成https://registry.npmjs.org/或官方认可的https://registry.npmmirror.com执行npm config set registry https://registry.npmjs.org/npm warn deprecated node-domexception1.0.0: use your platforms native dome某个间接依赖使用了已废弃的包先npm update升级依赖如果仍然无法消除就在 README 的已知问题里记录不影响功能npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称Node.js 安装后 PATH 环境变量没有生效重新打开终端或手动把 Node.js 安装目录加入系统 PATH最好安装 18 LTS 及以上版本npm ERR! 404 Not Found发布时提示包名被占用package.json 里的 name 已被别人使用去 npm 官网搜一下包名换一个不冲突的名字或者使用scope/name命名空间这些报错里最坑的是第一个 PowerShell 执行策略问题。它不是 npm 本身的问题而是 Windows 默认禁止执行未经签名的 PowerShell 脚本npm.ps1自然被拦下来了。很多新手在这一步就卡住了以为 npm 没装好。我的建议是如果只是临时跑命令可以直接在 cmd 里操作如果长期开发就把执行策略改成RemoteSigned这样本机脚本可以运行从网络下载的脚本仍然受限安全性有保障。4.3 发布后的第一次安装与冒烟验证npm publish成功后我在另一个目录新建了一个空项目执行npm install typograph-md然后尝试npx typograph-md build example.md -o output.docx。这一步是发布后的冒烟测试能验证发布的包是否完整、CLI 是否正常工作、依赖是否都打包进去了。第一次安装时发现一个问题docx库的版本被安装在dependencies里没问题但因为我用的 tsup 打包时把所有代码打成了一个文件生成的文件里居然还包含了require(docx)导致用户在安装我包时必须也装 docx。解决方式是检查 tsup 配置把docx、gray-matter、commander等依赖标记为 external不打进 bundle然后正常声明在dependencies里用户在安装时 npm 会自动处理。改完重新发布一个 patch 版本再安装测试一切正常。冒烟测试通过后我在仓库里补了一篇简短的 CHANGELOG记录了从开发到发布的完整过程和发布版本对应的特性后续维护起来会轻松很多。5. 贴地排雷让这套流程更顺的实战心得5.1 常见问题速查表除了 npm 报错开发排版包本身也会遇到一些跟文档处理相关的奇怪问题。我整理了一个速查表帮大家快速定位现象排查方向建议生成的 Word 里中文全变成英文标点typographer.ts没有处理全角半角转换在规格中明确要求做标点规范化且代码块内部不替换文档里的本地图片不显示图片路径是相对路径但渲染时没有基于源文件目录解析用path.resolve(path.dirname(markdownFilePath), relativePath)转换成绝对路径用 npx 运行命令时提示找不到 binpackage.json 的bin字段配置或 shebang 缺失确认bin指向的文件存在且在文件头部有#!/usr/bin/env node测试用例全部混在一个it里报错时看不出是谁的问题AI 图省事把多个断言写在一起在开发公约里写明“每个测试只验证一个行为”AI 生成代码时经常引入多余依赖规格里没有限制依赖范围在 SPEC.md 里写明“禁止引入规格之外的运行时依赖”改了代码但生成 docx 样式没变化可能编译产物没更新确认 npm run build 已执行CLI 运行的是 dist 而不是 src这些坑都有共性归根结底是规格不够细或者 AI 在执行时越过了规格边界。我的经验是把速查表里的每一条都反馈到 SPEC.md 里形成“出问题 → 补规格 → 再验证”的循环。几次循环之后AI 的产出质量会指数级上升。5.2 和 AI 协作时最容易忽视的三个细节第一是上下文漂移。AI 对话窗口再长也有上限连续聊到几百行后它会慢慢淡忘最初的规格开始凭感觉生成代码。我的解决办法是每隔一段时间就让 AI 重新读一遍 SPEC.md并且在每个新任务开始前把规格相关片段直接贴进 Prompt别指望它自己记住。第二是过度设计。AI 特别喜欢给一个 20 行需求的模块抽象出三层接口和五个类型定义。如果你不管它代码库会被它吹得越来越膨胀。我在规格里明确写了“模块文件数量不超过 6 个每个函数超过 60 行必须拆分”这才把 AI 的“架构瘾”按住。第三是测试与规格的关系。我一开始把测试当作 AI 输出质量的筛选器后来发现测试本身也会被 AI“水”过去。后来我把策略调整成“测试是规格的第一消费者”让 AI 严格按照规格里的验收标准去写测试测试不过就是规格或实现有问题先回去改规格或代码而不是硬改测试去迎合一个错的行为。这句话说起来平淡但真正执行下来项目质量会稳很多。5.3 这个项目后续还能往哪些方向扩展排版包做完之后我又想了一圈后续的扩展方向。最直接的是做一个模板系统用户通过一个 YAML 文件自定义字体、字号、行距和标题编号规则这样不同公司、不同场景的排版规范都能覆盖。其次是跟工作流打通比如接一个 GitHub Action用户 push 一份 Markdown 到指定分支自动触发构建并生成 Word 附件上传到 Release 或者发到群机器人。这些功能单拆出来都不复杂用 SDD 的方式推进很合适。另一个我比较看好的方向是跟“论文排版”结合。热知识很多期刊和学校对论文的格式有严格要求标题字型、正文行距、参考文献格式琐碎到让人崩溃。如果把 SDD 写好的排版规格和 AI 解析能力结合起来做一个论文排版工具价值会比通用排版工具高得多。我目前只做了 Markdown 转 Word 这一条链路后续如果要支持多级列表、图注表注、参考文献引用规格还需要继续完善。这次把 SDD 落到实际项目上我的体会是它不是一个银弹但确实能把 AI 协作开发从“碰运气”变成“可控执行”。以前我让 AI 帮我写代码总觉得像是在放飞一只不拴绳的狗跑得快但不知道会跑去哪现在有了规格这条绳子它跑得再欢也始终在院子里。整个排版包本身不大但 SDD 的这套工作方式我已经准备迁移到后面更大的项目里了。如果你也准备用 AI 做一个 npm 包建议先从这种边界清晰、肉眼可验收的小工具开始把规格写明白再谈模型选多强。
返回列表