安装此技能
npx skills@latest add mattpocock/skills --skill=prototype 然后在你的编码 agent 里输入 /prototype 。
本页目录
它做什么
prototype 写的是用完即弃、用来回答一个问题的代码:这个状态模型(state model)感觉对不对,或者这个界面看起来该是什么样。问题先出现,并决定你构建什么。一个回答了错误问题的原型就是浪费,不管它看起来多好。
「用完即弃」是对代码怎么写的约束,不是销毁它的承诺。没有测试,除了让它跑起来所必需之外没有错误处理,没有抽象,没有持久化,因为那些都无助于你学到你正想学的那一件事。有两样东西会留下来。答案进入真实代码,而原型进入一条从 main 分出的分支,作为答案的证据。
什么时候用它
输入 /prototype,或者当一个任务匹配时由 agent 自动去拿它。
当你撞上一个无法靠谈话解决的问题时,就立刻用它:一个你无法在脑中记住所有边界情况的状态机,一个不并排看到三个版本就想不出来的界面。质询(grilling)会话在这些问题上会越拖越长。agent 换个说法,你来猜,范围越来越大。停下质询,做出那个用完即弃的版本,看一眼,然后用一句话回答。如果相反,是某个已经构建出来的东西行为异常,而你想知道为什么,那就用 diagnosing-bugs;原型探索的是该构建什么,而不是已构建的东西为什么坏了。
你也会在并非主动选择的情况下来到这里。wayfinder 会在地图上创建 prototype 决策工单(decision ticket),而你用这个技能来处理其中一张。
两条分支
问题会挑选分支,而不同分支产出不同的产物:
- 「这段逻辑 / 这个状态模型感觉对不对?」:一个可分享的单个 HTML 文件。一个自包含的页面,没有构建也没有服务器,双击就能打开。它带有一个带标签的状态面板,每次点击后都会重新渲染;还有自由操作按钮,让你以任意顺序试探这个模型;以及分标签页的引导式演练(guided walkthrough)(每个标签页一个场景,每个场景下面都列出要按顺序按下的按钮)。一切都用领域语言(domain language)标注,所以你可以把它交给设计师、PM 或领域专家,让他们自己试这个模型。页面背后的逻辑是一个小的纯模块(一个 reducer、一个状态机、一组函数),里面没有 DOM 代码,所以你可以把验证过的版本直接搬进真实代码。
- 「这看起来该是什么样?」:同一条路由上几个截然不同的 UI 变体,可以从一个悬浮底栏和一个
?variant=URL 参数切换。变体必须在结构上不同,而不是在颜色上不同;三个只有小改动的卡片网格什么也回答不了。它们尽可能渲染在真实页面里,使用真实数据和真实密度,因为在一个空页面上评判的变体看起来总是没问题。
两者都把状态放在内存里,开始时无需任何配置,并在每一步之后向你显示完整状态。当你发现自己开始把其中一个加固(加测试、接上真实数据库、为一个以后可能想要的场景做泛化)的那一刻,你就已经不在做原型了。
原型是一手来源
一个完成的原型留下两样东西,它们去往不同的地方。
你把答案(裁决结果,加上它所解决的那个问题)记录到一个永久的地方:一条提交信息、一份 ADR、那个实现 issue。main 以真实代码的形式保留答案。
原型是答案来源的可运行证据,而你不会删掉它。它也不属于 main,因为没有人维护它,它会很快过时。所以你把它提交到一条从 main 分出的、用完即弃的 prototype/<name> 分支上,绝不合并它,并在实现 issue 上留下一个指向那条分支的上下文指针(context pointer)。main 保持干净,而接下来接手这项工作的人可以找到并重新运行这个原型。
常见问题
等等,原型不是应该被删掉吗?
不再是了。以前是这样:构建它,留下答案,把代码扔掉。对那种做法最强烈的反对与速度无关。它反对的是下一场会话(session)里谁来接手这项工作,以及他们得从什么开始? 一份对原型的散文式总结,会丢掉当初让它有说服力的那个东西。所以这个技能现在把原型当作一手来源(primary source)。它落在一条从 main 分出的 prototype/<name> 分支上,实现 issue 指向它。变的是代码住在哪里,不是纪律;它仍然永远不会合并进 main。
它过去会构建一个终端应用。那个去哪了? 逻辑分支现在改为产出一个可分享的单个 HTML 文件。终端应用只能由克隆了仓库、装好运行时的人来操作,这就排除了原型需要听取意见的那些人:设计师、PM、以及知道这个状态模型本该意味着什么的领域专家。一个自包含的文件,双击就能打开,通过邮件发出去之后仍然能用,任何人都可以操作它。底下的纯逻辑模块没有变,仍然是你要搬进真实代码的那部分。
我本该在做实现时,一个 agent 却让我去 /prototype。
这是一个已知的命名问题。一个不了解这条流程的 agent,会在工单存在之后,把 prototype 这个通用又讨喜的词读成「显而易见的下一步」。所以即使设计已经在谈话里定下来了,agent 也会点名推荐它。如果你已经知道该构建什么,下一步就是按工单运行 /implement。只有当某个具体的设计问题仍未解决、而且谈话解决不了它时,才去用原型。
我该不该在构建任何生产功能之前先给整个应用做原型(比如为了向潜在客户演示)? 那是另一种产物,只是恰好和这个技能同名。这里的原型范围限定在一个问题上,而「整个应用是什么样?」并不是一个问题。全应用原型没有天然的停止点,于是它漂移成了生产应用:清理环节永远不会发生,而在原型规则下写出的代码(没有测试、没有错误处理)最终会摆到用户面前。如果你需要一场销售演示,就故意把它当作演示来构建,并明确说明它没有一处是生产级的。如果你需要定下一个设计问题,就把它削减到那个问题。
我怎么在它自己的一场会话里运行它? 原型住在自己的目录里,并且会生成大量你不想留在提出问题的那个对话串里的上下文(context),所以把它放到别处运行,只把答案带回来。用 handoff 把工作搬进那场会话,再把答案带回来。
这不就是烧 token 最快的方式吗? 有可能,如果你给那些本来靠谈话就能回答的问题做原型,或者让一个原型蔓延到整个功能上。真正重要的比较不是 token 对零;而是 token 对构建了错误的状态模型、并在它有了生产调用方之后才发现。把问题保持得窄,把运行保持得短,花费就会保持成比例。
它生效的标志
- 你能用一句话说出这个原型存在是为了回答什么问题,而且这句话写在演示的顶部,不只是在你的脑子里。
- 不读代码的人也能操作逻辑演示。他们打开文件,在演练标签页里按按钮,用自己的话描述他们看到的东西。
- 有人说「等等,这不应该可能」或者「咦,我以为是 X」。那是想法里的 bug,而原型正是为此存在的。
- UI 变体在布局和信息层级上不同,而不只是在颜色和文案上不同,而你得到的反馈是「B 的页头加上 C 的侧栏」。
- 它在一坐的工夫里就得到了回答。如果一天之后你还在构建它,那个问题就太大了;把它拆开。
- 结束时,main 包含决定,而不包含原型的任何部分,实现 issue 指向仍然保存着它的那条分支。
它在体系中的位置
prototype 是一个随时可用的独立技能(Standalones):你用它定下一个设计问题,然后回到你的工作里。还有一个技能也会运行它。
运行它最多的技能是 wayfinder。一张 wayfinder 地图由决策工单组成,而 prototype 是工单可以有的四种类型之一:用于阻塞性问题为「这看起来该是什么样」或「它该怎么表现」的时候,这类问题再多讨论也解决不了。当讨论含糊时,wayfinder 做出某个具体的东西供人反应,而这个技能负责构建那个东西。答案解决一张原型工单,地图把原型作为一个资产链接过去。
其他邻居则在那条链的上游和下游。grill-me 和 grill-with-docs 回答可以质询的问题;质询不了的那些转到这里,一句话的答案再回到访谈里。在下游,一个经过验证的状态模型或 UI 方向成为 to-spec 的已定输入,它可以内联原型产出的那段富含决定的代码片段,而不是用散文去描述它。其他任何情况,ask-matt 会在整套技能上为你路由。