给智能体的两个请求,一个接一个。第一个:"按文件 SPEC_LANDING.md 里的需求说明做一个页面"。几分钟后,文件夹里出现了 index.html,效果和设想的一模一样。第二个,同一天晚上:"做一个 2048 游戏"。这回智能体要么一口气问您五个问题,要么一声不吭地做出一个 5×5 的棋盘,还带一个谁也没要求过的撤销按钮。

区别不在智能体,而在于它拿到了什么输入。本文会讲清楚:一个小任务的需求说明由什么组成,写进去的内容从哪里来,只知道大致方向时该怎么办,以及为什么在智能体说"完成"之后,您要按照开始前写下的那几行去检查结果。

1. 想法还不是任务

"我想要 2048"。"做一个漂亮的页面"。"做个新游戏,有意思一点的"。这些都是想法,而且有一个共同的毛病:您脑子里是一整幅画面,写出来却只有几个字。智能体只看得到文字。

面对请求里的空白,智能体有两条路。一是提问。二是自己填上:选一个棋盘尺寸,想一条规则,加一个按钮。第二条路更安静,所以也更危险。在看到结果之前,您不会发现有人已经替您做了决定。请求里每一处没填的空白,都是一个迟早会有人做出的决定,而默认情况下,做决定的不是您。

"2048 怎么玩,谁都知道",这句话只存在于您的脑子里。智能体读不到这个"谁都知道"。棋盘多大?新方块从哪里来,什么时候出现?哪些方块会合并,合并成什么?什么时候算赢,什么时候算结束?对您来说这只是一个词,对它来说却是一片空白,直到拆成具体规则为止。在聊天里是这样的:

您:    做一个 2048 游戏
智能体:开始之前我先确认几点:
        1. 棋盘多大,4×4 吗?
        2. 怎样算赢?
        3. 需要撤销功能吗?

这是好的结果。同样的请求,也可能是另一种结果:

您:    做一个 2048 游戏
智能体:完成。游戏在 index.html 文件里:
        - 5×5 棋盘
        - 撤销上一步的按钮
        - 保存在 localStorage 里的排行榜

这不是智能体的错。这是您留下的三处空白,没问过您就被填上了。

智能体会提问,但不能指望它一定会问

Claude Code 会提出澄清问题,它提问是个好信号:说明智能体发现了漏洞。但下一次,同一个漏洞它可能会自己填上,事先无法预料会是哪种情况。所以空白要由您来填,在发出请求之前,而不是在看到结果之后。

  • 想法
  • 问题
  • 需求说明文件
  • 智能体
  • 按需求说明检查

这就是从想法走到一个值得信赖的结果的路径。本文从左到右沿着它讲下去。而最后一步会把您带回第三步:检查,发现漏洞,在需求说明里补一行,再交给智能体。

2. 内容从哪里来

页面或游戏的骨架,智能体几分钟就能搭好,这确实不是最难的部分。难的是知道页面上应该有什么,以及到底要玩的是什么。这些东西代码里没有。它们在人的脑子里,有时在您自己脑子里,也有一部分是智能体知道的。要靠提问把它们挖出来,来源有三个:别人、您自己、智能体。

别人,指的是提出需求的人和将来要使用的人。问他们问题最好一次问一批。一次列出五六条,而不是每天问一个。一次问一批看起来有点烦人,这我知道。但实际上,它能帮所有人省下一周的来回沟通。

如果提需求的就是您自己(自己的游戏、自己的页面),问题就是问自己,这时最大的诱惑是回答"这不是明摆着的吗"。明摆着的事,也要和不明显的事一样写下来。"棋盘 4×4"只需要两秒钟,对智能体来说,却是在一个岔路口替它选定了方向。

第三个来源是智能体,这里有一个最好提前知道的陷阱:您没法问自己不知道的事。一步什么都没移动,算不算走了一步?手机上需要滑动操作吗?要不要显示分数?在游戏真正摆到眼前之前,这些问题根本不会冒出来。而智能体见过上千个这样的游戏,知道大家通常在哪里栽跟头。所以有时候,与其说"做吧",不如先说"我们先讨论一下":

我想做一个在浏览器里玩的 2048 游戏。先什么都别写。
把你开始之前必须知道的问题都问我。一次全部列出来。
我回答完,我们再把需求说明整理到 SPEC_2048.md 文件里。

这种对话要先定两条规则,否则它永远结束不了。第一,下一节的四个支柱都填满了,讨论就结束,而讨论的成果是一个文件,不是聊天记录。第二,智能体的问题是选项,不是要求。它很乐意问"排行榜要不要做?",而"不要"是完全正常的回答。这样的"不要"同样是需求说明里的一行,只不过写在"不做"里。

无论如何,不管是您问的还是智能体问的,答案都要写进需求说明文件,而不是停留在聊天里的一句话。否则下一个请求又会从同一个漏洞开始。

来自别人、您自己和智能体的答案汇集到同一个需求说明文件里,而聊天里的一句话会丢失答案别人需求方、使用者您自己自己的游戏或页面智能体知道哪里容易出错需求说明文件聊天一句话就丢了
无论答案来自哪里,都要写进文件。聊天记录留不住它

左边三个来源,右边一个去处。向下的虚线箭头,是答案最常走的那条路:留在聊天里,过了十条消息就再也找不到了。图上有这支箭头,箭头尽头却没有文件。

您还可以让智能体多做一些:不只是提问,还可以根据您的回答自己起草一份需求说明,把您没说到的地方也补上。它自己补充的那些内容之后该怎么处理,见第 4 节。先来看需求说明由什么组成。

3. 需求说明的四个支柱

对一个小任务来说,需求说明就是六到八行。支撑它们的是四个支柱:目标、应该发生什么、第一个版本不做什么、怎么检查。下面是游戏的完整版本:

# 需求说明:2048

目标:     在浏览器里玩的 2048 游戏,直接打开文件就能玩,无需安装。
           HTML、CSS 和 JavaScript,不用构建工具

规则:     4×4 棋盘
           开局时和每走一步后出现一个新方块:2 或 4
           方向键让所有方块朝同一个方向移动,一直移到头
           两个相同的方块相撞时合并成一个,数值为两者之和
           胜利:棋盘上出现了 2048 方块
           失败:无路可走,棋盘填满,没有可以合并的方块

不做:     排行榜、动画、撤销

检查:     在浏览器里打开 index.html,看到棋盘和开局方块
           用方向键走三步,每一步之后都出现一个新方块
           把两个 2 合在一起,得到 4
           一直玩到结束,看到失败画面

接下来逐个讲解每个支柱,每个都用两个任务来演示:游戏,以及桌游俱乐部聚会的邀请页面。

目标

第一行。读完它,人脑子里浮现的应该是结果,而不是代码。"在浏览器里玩的 2048 游戏,直接打开文件就能玩,无需安装"。"俱乐部聚会的邀请页面:看的人能明白这是什么聚会,并通过按钮报名"。

检查一下目标:如果读的时候您看到的是代码,而不是用户将会看到的东西,那它就已经不是目标了。"写一个合并方块的函数"不是目标,只是工作的一部分。

这里还要用一行写明"用什么来做":HTML、CSS 和 JavaScript,不用构建工具。没有这一行,智能体会自己选,而它选的不一定是您知道怎么运行的东西。

而"做得漂亮点"不可能成为目标。不清楚是对谁漂亮,也不清楚在哪里能看出来。

应该发生什么

这是最长的一个支柱,游戏的这一部分上面已经写好了。规则只有一条:如果运行之后能看着这一行说出"是"或"否",这一行就是好的。"4×4 棋盘",是或否。"两个相同的方块合并成一个,数值为两者之和",是或否。"有意思一点",既不是"是"也不是"否",这样的行在需求说明里没有位置。

一行只写一件事。"方块出现并合并"是两项检查,所以要写成两行。

页面也一样,只不过写的不是规则,而是页面上有什么:"带标题和日期的页头","三个区块:活动内容、适合谁、在哪里","报名按钮跳转到表单地址"。每一项都能打开页面亲眼看到。

和想法放在一起,这样的清单看起来很枯燥。枯燥恰恰是好事。枯燥的东西才能检查。

第一个版本不做什么

这是唯一一个通常会被忘掉的支柱,也正因如此最有价值。空白由智能体自己来填,而它最喜欢的填法就是往里加东西。排行榜、方块动画、撤销按钮、深色主题。这些都有用,但这些您都没要求过。

要一项一项写出名字。不是写"不要多余的东西"(这不是边界,只是一个愿望),而是写"不做:排行榜、动画、撤销"。页面的话:"不做:报名表单本身、付款、照片墙"。一般写三项就够了,反正也没人会写十项。

这一条是为您写的,而不是为智能体。做出来的东西越少,要检查的就越少。出了问题,也越容易看出是哪里来的:在六行里找,比在六屏代码里找容易得多。没有边界的信号是:在您的请求里或者智能体的回答里,"顺便"这个词出现得越来越频繁。

没有它就达不到目标的东西,不能写进"不做"

一个以玩到结束为目标的游戏,写上"不做失败画面",这不是边界,而是漏洞。在往"不做"里写一行之前,先重读一遍目标:如果没有这样东西就达不到目标,这一行就放错了地方。

怎么检查

"应该发生什么"里的每一行,都对应智能体说"完成"之后的一项检查。打开游戏,4×4 棋盘,是。按下方向键,方块移动了,出现了新方块,是。把两个 2 合在一起,得到 4,是。检查清单不用另外写,它已经写好了。

另外,在需求说明里再加两三行"检查":具体要亲手做什么。"在浏览器里打开文件,走三步,一直玩到输"。这几行要在发出请求之前写,而不是之后。有了结果之后,人会不知不觉地把这几行改得去迁就已经做出来的东西。这是自然而然发生的,您也不会例外。

智能体说"完成"只是一句话,不是检查。检查是您的事,按您写下的那几行来。

一分钟看到胜利画面,而不是半小时

要玩到 2048 方块得花很久,胜利画面很难老老实实地检查到。可以让智能体在检查期间把 64 方块算作胜利:"为了测试,把胜利条件改成 64"。看过画面后,再让它改回 2048。这也是需求说明里的一行,只是临时的,之后要删掉。

现在来练习一下。拿"给桌游俱乐部聚会做一个漂亮的页面"这个请求,把它拆成四个支柱,每个写一两行。答案就在下面,对照一下:

# 需求说明:邀请页面

目标:       桌游俱乐部聚会的邀请页面:
             看的人能明白这是什么聚会、时间和地点,并通过按钮报名。
             一个 HTML 文件,样式写在文件里

页面内容:   带俱乐部名称和聚会日期的页头
             三个区块:活动内容、适合谁、在哪里
             "报名"按钮跳转到表单地址

不做:       报名表单本身、付款、照片墙

检查:       在浏览器里打开,看到页头和三个区块
             点击按钮,跳转到表单地址

注意,"漂亮"这个词根本没有进入需求说明。它没法检查。如果漂亮真的很重要,就像其他内容一样把它拆成具体的行:"页头放一张图片","不超过两种颜色","在手机屏幕上能完整显示"。这样就能打开页面看一看了。

两个例子里的区块名称不一样:游戏用的是"规则",页面用的是"页面内容"。这无关紧要,怎么顺手怎么起名。重要的是,每个例子里都有四个支柱:

支柱回答什么问题2048页面
目标做什么,给谁用在浏览器里玩的 2048 游戏,直接打开文件邀请页面:明白是什么聚会,并且报名
应该发生什么运行之后我会看到什么4×4 棋盘、新方块、合并、胜利、失败页头、三个区块、跳转到表单的按钮
不做什么智能体会自己加、而我们没要求的东西排行榜、动画、撤销表单、付款、照片墙
怎么检查"完成"之后要亲手做什么走三步、合并两个 2、玩到结束打开、查看、点击按钮

4. 这是谁的决定

需求说明里会出现一些没人确认过的决定,出现的方式有两种。第一种:您允许智能体"用合理的方式"填补漏洞,为的是现在就拿到草稿,而不是等到下周二才有答复。第二种:因为没人可问或者没时间问,您自己替需求方做了决定。

这两种情况都很正常。不正常的是忘了这只是一个假设。一周以后,谁也想不起来是谁定下按钮跳转到邮箱而不是表单的,而大家争论的恰恰会是这一点。

第一种情况下,给智能体的请求是这样的:

根据这些回答,起草一份邀请页面的需求说明。
没有答案的地方,你自己想一个合理的方案,并单独列一个清单,
写明哪些是你自己想出来的。写到 SPEC_INVITE.md 文件里。

先读这份"自己补充的内容"清单,再读需求说明本身。这些都是替您做出的决定:每一条要么交给了解情况的人去确认,要么带着标记留在需求说明里。

第二种情况,就在文件里直接做标记。在这条决定前面标上"假设:",或者干脆在行末加个问号:"按钮跳转到俱乐部邮箱 (?)"。等答复来了,一眼就能看出要改哪里,也不用再回忆这条是从哪儿来的。

标记是双向起作用的。拿到带问号的需求说明,智能体通常会先问这些地方:对它来说,这和对您一样是个信号。

5. 用沙盒代替提问

有时候,不管坐多久,四个支柱就是写不出来。"我想要个类似 2048 的东西,但不知道要不要分数,要不要滑动操作,甚至不知道是不是 4×4"。这不是需求说明,甚至都算不上通常意义上的想法。这是一个方向:往哪儿走清楚,结果是什么不清楚。这时提问帮不上忙:您和需求方都还没有答案,而智能体给出的选项,您也没有东西可以拿来比较。

这种情况下,最省事的办法是先看一看。做一个沙盒:最简单的版本,可以看一眼、上手试试,弄明白自己真正想要什么。有智能体帮忙,这只需要十分钟,为了这十分钟做出来的东西,扔掉也不可惜。

给沙盒也要写一份有四个支柱的需求说明,只是目标不同:

# 需求说明:2048 沙盒

目标:     看看 2048 在浏览器里玩起来怎么样,弄明白我想要什么。
           HTML、CSS 和 JavaScript,不用构建工具

规则:     4×4 棋盘
           方向键移动方块,相同的方块合并
           每走一步出现一个新方块

不做:     其余全部:胜利、失败、分数、滑动操作、美化

检查:     玩五分钟,记下缺什么、多了什么

看看变了什么。目标不是游戏,而是弄明白。"不做"是最长的一行,里面是所有您没把握的东西。检查的也不是"能不能用",而是"我现在知道了什么"。玩上五分钟,开始时没法问出口的问题就会自己冒出来:滑动操作不需要,在电脑上都是用方向键玩的;分数需要,没有分数太无聊;4×4 太小,想试试 5×5;失败要显示出来,不然不知道游戏已经结束了。

这就是沙盒的成果。不是代码,而是答案。用这些答案写出真正的需求说明,然后在一个干净的文件夹里从零开始构建,只需一句话:"按文件里的需求说明做这个游戏"。沙盒的任务完成了。

方向引向沙盒,玩五分钟得到答案,答案写进需求说明文件,智能体在干净的文件夹里从零构建正式版本答案,不是代码方向沙盒sandbox/试玩五分钟需求说明文件智能体干净的文件夹按需求说明检查
从沙盒进入需求说明的是答案。沙盒没有指向智能体的箭头:正式版本要从零开始构建

这还是第一节里的那条链,只是"问题"这一步换成了一个回路:没什么可问的时候,先看,再写。向下进入需求说明的只有一样东西:答案。沙盒没有指向智能体的箭头,这不是漏画了:正式版本要在干净的文件夹里从零构建。之后就按这条链走下去。

沙盒就是沙盒

这种诱惑可以理解:既然已经有东西能跑了,那就接着做完吧。"加上分数","加上失败画面","顺便加上滑动操作"。沙盒就这样一块一块地变成了产品,没有需求说明,一个小时后谁也说不清里面哪些是要求的,哪些是顺手粘上去的。把它放在单独的文件夹里,比如 sandbox/,正式版本则在干净的文件夹里开始。智能体的十分钟,比事后去理清什么东西从哪里来要便宜。

6. 需求说明放在文件里

为什么是文件,而不是聊天记录。聊天记录会丢:过了十条消息,关于方块合并的那条规则就找不到了。文件放在项目文件夹里,就在代码旁边,只要您指给智能体,它就会自己去读,修改之后用同一句话再交给它就行。文件名随意:SPEC_2048.md、SPEC_LANDING.md,甚至 task.txt 也行。这就是一个普通的文本文件,没有任何特殊格式。

交给智能体只需要一句话。对比一下:

之前:"做一个 2048 游戏,你懂的,有意思一点的"。

之后:"按文件 SPEC_2048.md 里的需求说明做这个游戏"。

第二个请求比第一个短,信息量却多了十倍,因为信息在文件里。不需要把需求说明的内容复制到聊天里,智能体会自己打开文件。

什么要改文件,什么可以直接在聊天里说。参考标准是这样的:规则或边界变了,就改文件,再交给智能体("2048 才算赢,而不是 1024",这要写进文件)。只是外观上的小改动,不影响规则,可以直接说("把按钮改成蓝色")。拿不准的时候,就写进文件:多一行的代价,比忘掉一条规则小得多。

关于篇幅。对一个小任务来说,就是六到八行,像上面 2048 的那一段一样。如果一个晚上就能做完的游戏,需求说明却写了两页,那多半是把本该写进"不做"的东西写进去了。

最后,在把文件交给智能体之前:把它当成别人写的东西读一遍。几乎总能找到一行没法检查的内容。我自己每次都能找到。

7. 智能体用需求说明做什么,您要做什么

把前面的内容汇总一下:在这套流程里,Claude Code 做什么,什么留在您这一边。一一对应。

智能体您
在您指定需求说明文件后读取它,并严格按照它来构建在发出请求之前写好需求说明,而不是看到结果后再写
提出问题,包括您自己想不到的问题,前提是您让它这么做。没有要求时,它可能自己把漏洞填上把答案写进文件,提前填好空白
十分钟做出一个沙盒决定从沙盒里取哪些东西写进需求说明,不把沙盒一路做成产品
按要求起草需求说明,并列出自己补充了哪些内容先读这份清单
说"完成"逐行对照需求说明和"检查"部分
很乐意"顺便"加点东西在"不做"里逐项写明名称
这个框架有一个完整版

对于更大的任务(涉及已有代码、多人协作、存在风险),这个框架有一个完整版:有固定的区块名称,还多了几个支柱。其中的四个支柱还是这四个。能为一个晚上的小游戏写出六行需求说明的人,看到它不会觉得陌生。

需求说明的意义不在于让智能体听懂。就算只给两个字,它也能做出点什么。意义在于让您自己清楚要的是什么,并且能够检查。