news 2026/9/8 19:55:28

SDD+AI Agent实战:从规格到npm包的高效开发全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SDD+AI Agent实战:从规格到npm包的高效开发全流程

这阵子我试了一套很有意思的开发方式,一个需求用传统方式做大概要两天,这次一个下午加一个晚上就搞定,而且质量比我预期的高不少。核心就是把之前靠感觉、靠白板、靠嘴上说的需求整理过程,变成一份机器和人都能读懂的规格说明,再用 AI Agent 按规格把代码填出来,整个交互过程就是 SDD(Spec-Driven Development,规格驱动开发)。

我不是说 AI 能替你写代码就完事了,真正值钱的是把这个过程变成可审计、可回滚、可复现的。刚好最近我用这套思路做了一个排版用的 npm 包,从规格、编码、测试到发布,全过程夹着不少坑,今天一次性讲清楚。

1. 从 vibe coding 到 SDD:为什么我觉得“直接让 AI 写”不够用

先说背景。之前有一段时间我特别沉迷 vibe coding,需求描述几句话,往编辑器里一贴,Agent 吭哧吭哧生成几百行代码,看着挺爽。但用了几次就发现问题:代码能跑,但漏洞特别多,而且很多漏洞不是语法层面的,是需求理解层面的。我让它做一个“排版工具”,它给我做成了“纯文本处理工具”,完全没考虑终端 ANSI 颜色序列、中英文混排的宽度差异、表格列宽自适应这些真实场景里的硬需求。

后来我研究了不少 AI 工程化实践,看到一个概念让我挺有共鸣——国外有工程师提过 SDD 的三级分类框架。大致是这么分的:

  • 第一级:纯 vibe coding,需求在脑子里,AI 靠提示词自由发挥;
  • 第二级:有规格,至少把输入、输出、边界条件写清楚,AI 按规格实现;
  • 第三级:被约束的生成(harnessed),不仅写规格,还有验证方法、测试套件、性能约束,AI 的生成结果会被自动校验。

我第一次看到这个分类的时候,心里就说:这不就是我一直想做的事吗?我之前大部分时间都处在第一级,偶尔运气好到第二级。真正稳定、可复用的 AI 协作开发,必须是第三级。

所以这次我做这个 npm 包,一开始就给自己定了规矩:先写规格,再谈代码。而且规格不是我拍脑袋写,是需要把“什么样算排版正确”这个模糊概念变成一个可验证的规格集合。

1.1 为什么 SDD 特别适合工具类 / 库类项目

可能有人会问,SDD 适合所有项目吗?我的经验是:特别适合那种行为边界清晰、输入输出可以定义的库和工具类项目。比如我这个排版 npm 包,本质上做的是数据转换工作:输入字符串和配置参数,输出排版后的字符串。这类项目非常适合规格驱动。

为什么?因为它的行为是可枚举的。你可以非常精确地说,表格单元格内容超过列宽时怎么办,中文按两个字符宽度计算,颜色符号不能被计算成可见宽度。这些都能写成测试用例,而测试用例就是规格的活体版本。

反过来,那种探索性很强、交互流程特别长的产品功能,比如做一个复杂的 React 页面,SDD 也能用,但规格会写得比较累,视觉交互部分很难完全用文本定义。所以我的建议是:第一回试用 SDD,建议从库、CLI、工具函数这类项目开始,见效最快,打击感最爽。

1.2 SDD 不是“AI 帮我写代码”,而是“我先定义正确”

我见过太多人把 SDD 理解成“写提示词”。其实差的不是提示词怎么组织,而是你有没有能力在没有 AI 的情况下,把需求说成一条条精确的规格。

举个具体例子。做排版包,我第一个要定义的是“命令行表格”长什么样。这不只是画个框的问题,你得定义:边框用哪些字符、列和列之间空几格、内容超长是截断还是换行、数字列右对齐还是左对齐。每个问题都是一条规格,每条规格最终对应一个或多个测试用例。

写规格是这个项目里最花时间、也最值钱的部分。AI Agent 真正发挥效率是在规格写清楚之后,它只需要做一件事:把这些规格翻译成高质量的 TypeScript 代码,并且不偏离规格的语义。

2. 项目立项与规格设计:这个 npm 包到底要做什么

我这次做的东西,对外叫排版工具,实际上是一个同时支持终端输出格式化和文本对齐的库,外部形态是 npm 包,同时附带一个简单的 CLI。名字不说了,避免广告嫌疑,功能上主要解决这几类问题:

  • 把二维数组输出成对齐良好的终端表格;
  • 对一段文本做左对齐、右对齐、居中和两端对齐;
  • 自动换行,要求中文按两个西文字符宽度计算;
  • 支持 ANSI 颜色序列,计算宽度时能正确剥掉颜色码;
  • 提供 CLI 命令,支持从 stdin 读入文本,处理后输出到 stdout。

功能列出来后,下一步不是写代码,而是写规格。我整理了一个精简版规格片段,展示一下大概的粒度。注意,这些都是可以用测试验证的,不是“系统应具备良好的用户体验”这种废话。

2.1 规格片段:渲染表格的行为定义

输入是一个二维数组,每一行的列数可以不一样,第一行默认作为表头,也可以配置是否忽略表头。规格要点如下:

  • 表格列数为所有行中的最大列数;
  • 每列宽度为该列所有单元格的“显示宽度”最大值加 2(左右各一个空格);
  • 列宽有上限和下限配置,超宽截断并添加省略号;
  • 表头行默认加粗,底部输出分隔线;
  • 支持配置对齐方式:全局对齐、按列对齐、表头单独对齐。

每一列宽度的计算,不是简单地取字符串 length,而是按显示宽度计算。显示宽度的含义是:一个英文字母占 1,一个中文汉字占 2,ANSI 序列占 0。这个定义直接对应后面的核心算法,所以在规格阶段必须明确。

2.2 规格片段:文本对齐和自动换行

文本排版这块,规格定义更细。比如两端对齐,指在不超过指定宽度的前提下,通过调整单词间空格数,让每一行左右两端对齐,最后一行保持左对齐。这里有个问题:英文靠空格分词,中文呢?

中文没有空格,两端对齐如果按空格分词,一行可能全是中文词,排出来的效果和纯左对齐没差别。所以我定义了一条规则:中文场景下,按单个字符级粒度调整间距,但必须在标点处禁止行首,这个属于排版的基本规则。

自动换行的规格是这样的:

  • 优先在空格处断行;
  • 空格断不了就按字符断行;
  • 中文标点不能出现在行首;
  • 断行后的文本行宽度不得超过设定宽度。

这些规则看起来很基础,但一旦写成规格,Agent 生成的代码就不会跑偏。我实际测试时发现,只要把这条标点禁则写清楚,AI 生成的换行算法基本就是正确的。

2.3 设计非目标(Non-goals):规格里最容易忽略的部分

规格里还必须有“不做什么”的清单,这个太重要了。我加了几条非目标:不支持 Rich Text 格式解析、不支持表格单元格合并、不支持把 Markdown 解析成表格、不处理图片和链接。

为什么要专门写这个?因为 AI 生成代码时有个习惯,喜欢顺手扩展功能。我遇到过一次,我让它实现表格渲染,它给输出结果套了一个 Markdown 形式,还自作主张支持了 Markdown 表格语法解析。功能很炫,但不是我要的,反而增加了包的体积和复杂度。写上非目标后,AI 这类发挥基本就绝迹了。

规格写完,我顺手整理了一份 SDD 六步实践指南,核心流程是:需求拆解、规格编写、规格评审、Agent 生成实现、自动化验证、人工 review 与迭代。后面几个章节,我会按这个流程把这次实践拆开讲,每一步都对应实际操作。

3. AI Agent 协作实操:从规格到核心算法的高效落地

规格定稿后,进入真刀实枪的阶段。我用的开发工具是支持 Agent 模式的编辑器,背后挂的是最新的大模型接口。就实现方式来说,关键不是选哪个模型,而是你怎么把规格投喂给 Agent,并要求它严格遵循。我一般把规格拆成多个小任务,分步执行,而不是一次性让 Agent 生成整个项目。

我的做法是,先让它搭项目骨架,包括 package.json、tsconfig、目录结构、构建工具。这些不需要规格,属于工程模板,Agent 做得很快。然后是核心算法库的实现,我会把规格逐条拆开喂进去,每拆一条要求它补对应的测试。

3.1 ANSI 宽度计算:最容易翻车的地方

排版第一个核心算法是显示宽度计算。终端里的字符串,肉眼看到的宽度和 length 属性是两回事,原因就在 ANSI 转义序列,比如\x1b[32m是绿色开始,\x1b[0m是重置。这些字符本身不显示,但会被 JavaScript 的 length 计算在内。如果直接拿 length 去对齐,彩色文字永远对不齐。

我给的规格是:先剥离 ANSI 序列,再对剩余文本计算显示宽度。剥离用正则/\x1b\[[0-9;]*m/g,这个是最常见的 ANSI SGR 序列,覆盖颜色和样式。计算显示宽度时,需要区分全角和半角。TypeScript 实现大致是这样:

export function displayWidth(input: string, ansiEnabled = true): number { const text = ansiEnabled ? input.replace(/\x1b\[[0-9;]*m/g, '') : input; let width = 0; for (const ch of text) { const code = ch.codePointAt(0)!; if (code >= 0x1100 && ( code <= 0x115f || code === 0x2329 || code === 0x232a || (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) || (code >= 0xac00 && code <= 0xd7a3) || (code >= 0xf900 && code <= 0xfaff) || (code >= 0xfe10 && code <= 0xfe19) || (code >= 0xfe30 && code <= 0xfe6f) || (code >= 0xff00 && code <= 0xff60) || (code >= 0xffe0 && code <= 0xffe6) || (code >= 0x1f300 && code <= 0x1f64f) || (code >= 0x1f900 && code <= 0x1f9ff) )) { width += 2; } else { width += 1; } } return width; }

这段代码看着长,本质就一句话:常用全角字符区间返回 2,其他返回 1。这个区间列表是参考 Unicode East Asian Width 标准整理的,不是随便写的。实测下来对中文、日文假名、朝鲜文、Emoji 的处理都符合预期。Emoji 本身是可变宽度的,这里按 2 处理,在大多数终端里是合理近似。

规格到这里还不够,我还定义了padEndpadStartcenter这几个辅助函数的行为。它们的规格是:补齐宽度时按显示宽度计算,如果原文本显示宽度大于目标宽度,直接返回原文本,不做截断。截断单独由truncate函数负责,而不是混在 padding 逻辑里,这个职责划分能让 AI 生成的代码结构干净很多。

3.2 自动换行算法:空格优先、字符兜底、中文标点禁则

自动换行是第二个核心算法。我的换行策略分三个层级:

  • 在空格处断行,空格不保留在行尾;
  • 如果一个单词本身超过最大宽度,只能强制按字符断行(此时英文单词被拆开);
  • 中文文本按字符断行,但行首禁止出现特定标点,比如逗号、句号、感叹号、问号、顿号、分号、冒号、右括号、右引号。

第三点是中文排版的灵魂。没有这条规则,AI 生成的换行算法处理中文时会在行首留下一个逗号,这种排版错误特别显眼。规格里把这个禁则表列出来,代码实现就是断行后检查一下首字符,如果命中禁则表,就把这个字符推回到上一行。

const FORBIDDEN_LINE_START = new Set([ ',', '。', '、', ';', ':', '?', '!', '”', '』', '》', ')', '】', '…', '—', ]); export function hardWrap(text: string, maxWidth: number): string[] { const lines: string[] = []; let current = ''; for (const ch of text) { const next = current + ch; if (displayWidth(next) > maxWidth && current !== '') { if (FORBIDDEN_LINE_START.has(ch) && lines.length > 0) { // 把当前行塞回去,让禁止行首的标点出现在上一行末尾 if (current.endsWith(' ')) { current = current.slice(0, -1); } lines.push(current + ch); current = ''; } else { lines.push(current); current = ch; } } else { current = next; } } if (current !== '') lines.push(current); return lines; }

这段代码是给 Agent 看规格后它生成的初版,我再 review 时调整了几处细节。比如处理中文标点禁则时,原始版本直接把current + ch一起 push 了,没有去掉 current 末尾的空格,结果上一行末尾多了一个空格,视觉上有个小缺口。这就是为什么 SDD 的第四步「人工 review」不能省。规格能约束大体行为,但这些细小的排版洁癖还是需要人眼把关。

3.3 两端对齐的实现:按空格扩展还是按字符扩展

两端对齐在某些环境下争议很大,因为有很多种实现策略。我用的是最经典的策略:对已经切好的行,计算该行显示宽度和目标宽度之间的差值,把差值均摊到空格上,空格多的多摊,空格少的少摊。如果行内没有空格,比如一行全是中文,那就切到字符级,在字符之间插入全角空格补齐宽度。

这里有一个隐藏问题:一行里正好有一个空格和一个超长单词时,均摊逻辑会出问题。所以我规格里明确写了:单空格行不做均摊,直接保持左对齐,否则单个空格会被拉成一大段空白,视觉上非常怪异。这又是一条只有真实排版需求才会想到的场景,AI 自己大概率不会主动处理。

Agent 生成的两端对齐初版在英文文本上表现不错,但中文文本会插入半角空格来对齐,效果很丑。我后续补了一条规格:中文场景下优先用全角空格补齐,因为半角空格在混排时宽度不对。定了这条之后,测试用例也同步补上,Agent 再迭代版本时就稳定了。

4. 测试、调试与 npm 发布流程

SDD 的第五步是自动化验证,第六步是发布交付。我对自动化验证的理解是:测试用例就是规格的实体化,测试通过,规格才算实现。所以每一条规格都要有测试用例覆盖,这个项目我一共写了 60 多个测试,覆盖宽高计算、换行、对齐、表格渲染、命令行输出等。

4.1 测试策略:单元测试为主、快照测试辅助

测试框架选了 Vitest,原因就是快,TypeScript 支持好,和 Vite 生态天然配合。主要分成两类:

  • 纯函数单元测试:直接测 displayWidth、wrap、align、renderTable 这类函数,给输入断言输出;
  • 快照测试:对比较复杂的表格渲染输出做快照,后续改动一眼能看出影响。

表格渲染的测试很有意思。它生成的是一大段字符串,包含制表符、空格、ANSI 码,人工逐个字符断言太痛苦。快照测试在这里非常合适:第一次运行生成快照,我人工确认快照内容正确,之后每次改动只需要对比快照差异。有一次 Agent 重构了 padding 逻辑,快照测试立刻显示出所有表格右边框空了一格,问题定位几乎零成本。

4.2 调试 Chrome 页面和 CLI 输出

这个项目有一个比较特殊的调试需求:生成的表格不仅在终端显示,还要嵌入到 Web 环境里,供浏览器页面调用。所以测试覆盖了纯 Node 环境,也要覆盖浏览器环境。

我在这里用了一个调试工具链,通过 Chrome DevTools 调试协议辅助定位问题。具体做法是用官方提供的 chrome-devtools-mcp npm 包,在本地启动一个 stdio server,把它作为一个 MCP 工具接入到 Agent 对话里。这样 Agent 在开发过程中可以直接连接一个 Chrome 实例,实时执行 JavaScript、查看页面布局、检查样式。

这个能力对排版调试太关键了。比如我怀疑表格在 web 端显示时每个单元格宽度比终端里多出一个像素,这个用纯 Node 测试根本发现不了,但 Agent 通过调试协议打开页面,直接读 getBoundingClientRect 的结果,几秒钟就找到了原因:CSS 的 white-space 属性没设置,导致连续空格被折叠。这种跨环境的问题,日志打一百遍都不如实际连上浏览器看一眼。

4.3 发布 npm 包:从版本号到真实下载量

发布 npm 包的流程本身不复杂,但有几个点值得讲。首先包名要在 npm 上搜一下,重名非常常见。我这次也踩了,起好的名字一查已经被占用了,最后加了 scope 前缀,变成@scope/package-name的形式。

版本号我严格按照语义化版本管理:初始开发用 0.1.0,所有公开 API 定稿后升 1.0.0,之后每次破坏性变更升 minor,修复 bug 升 patch。AI 自动生成代码时常会改动公开 API,所以每次 Agent 迭代完我都会跑一遍npm run typecheck,再检查导出的类型定义是否有变化,避免发布出去别人一升级就崩。

构建和发布脚本我是这样组织的:

{ "scripts": { "build": "tsup", "test": "vitest run", "typecheck": "tsc --noEmit", "prepublishOnly": "npm run typecheck && npm run test && npm run build" }, "files": ["dist"] }

prepublishOnly这个钩子是保障,发布之前强制跑类型检查、测试和构建,任何一环挂了都不能 publish。这样就不会出现把 TypeScript 源码直接发出去、或者测试失败还强行发布的情况。files字段限定只发布 dist 目录,package.json、README 是 npm 自动带的,其他乱七八糟的文件不会进包。

关于发布本身,我再说一个经验:把包推上 npm 不是终点,而是维护责任的开始。我发布后第二天就收到一个 issue,用户反馈表格的右边框在某些终端里显示错位。排查后发现是终端的 Unicode 渲染宽度和我的计算值不一致,具体是某些符号在特定终端里被当成双倍宽度。最后我加了一个配置项,允许用户手动指定宽度表,问题解决。这种用户反馈是纯靠测试用例覆盖不了的,SDD 的规格也会跟着用户场景不断进化。

5. 常见问题与排查技巧实录

最后这部分是我踩过的一堆坑的合集,不一定都和 AI 有关,但每一个都在 SDD 协作过程里冒出来过,整理成一个速查表方便大家直接用。

现象根因排查与解决
表格列宽忽大忽小列宽计算用了字符串 length,没算显示宽度全局替换为 displayWidth 函数,并补测试
彩色文本对不齐ANSI 码被计入显示宽度剥离 ANSI 序列后再计算宽度,注意正则覆盖 SGR 全部模式
换行后行首出现中文逗号没加标点禁则逻辑在 hardWrap 中维护 FORBIDDEN_LINE_START 集合并回退上一行
两端对齐后单词之间空格过大对单个空格的行强行均摊单空格行直接返回,不做均摊
web 端表格显示错位white-space 默认折叠连续空格设置 white-space: pre,或在容器上使用等宽字体
AI 生成代码多用了一层深拷贝规格里没写性能要求规格补充“对输出为只读视图,不改变输入引用”,回头测试一下输入对象未 mutate
发布后用户终端渲染不一致终端对 Unicode 宽度判断不同暴露宽度表配置项,允许用户自定义
Agent 顺手实现了 Markdown 解析规格里缺非目标声明写规格时必须包含 Non-Goals 清单

在这里多说两句 AI 协作的体会。经常有人问我,AI 生成代码质量不稳定怎么办。我发现大部分不稳定都不是模型问题,而是规格不清晰。你把规格写到“这个函数接收两个参数,返回一个数组,数组元素类型为接口 X”这个粒度,AI 基本不会发挥。真正会发挥的地方,一定是你没写清楚的地方。

与其抱怨 AI 太自由,不如反思是不是规格太宽松。SDD 的价值就是把不确定性前移,在编码之前解决。用一句话总结我的感受:以前是想到哪写到哪,现在是先定标准再施工,AI 只是施工队,规格才是图纸。

这个排版包目前已经发布到 npm,star 不算多,但真实使用中反馈还行。我下一步打算在规格里把性能预算也写进去,比如渲染一万行表格的时间上限,让测试去兜底。这又是 SDD 往下走的一个方向:把非功能需求也规格化。对我来说,这比单纯堆功能更让我安心。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:55:14

具身智能“最后一公里”:为什么聪明大脑干不了精细活

我先说一个最近让我特别有感触的现象&#xff1a;朋友圈里做机器人的同行&#xff0c;几乎人手一份《人形机器人与具身智能标准体系&#xff08;2026版&#xff09;》的PDF在转&#xff0c;但真正聊起来&#xff0c;大家吐槽最多的不是标准缺不缺&#xff0c;而是——为什么自家…

作者头像 李华
网站建设 2026/9/8 19:52:08

基于STM32与OneNet的货车重量检测系统保姆级设计笔记

简介&#xff1a;面向嵌入式与物联网课程设计、毕业设计及实际项目开发的货车重量检测系统完整源码包&#xff0c;围绕STM32单片机、HX711称重传感器与ESP8266 WiFi模块构建&#xff0c;通过采集称重数据并上传至OneNet云平台&#xff0c;实现货车载重的实时监测与远程管理&…

作者头像 李华