关于 write-product-spec
为 Warp 编写重要功能的用户视角 `PRODUCT.md` 规格。它将可观察行为整理为可测试的不变量,并将实现细节交由配套的 `TECH.md` 说明。
主要功能
- 01编写用户视角的产品规格
- 02列出可测试的行为不变量
- 03梳理边缘情况与状态
- 04按工单或功能命名规格路径
- 05衔接 `write-tech-spec`
适用场景
- 01在实现前明确预期行为
- 02记录 UI、API 或 CLI 功能要求
- 03为配套技术规格准备产品规格
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ warpdotdev/ common-skills --skill write-product-spec安装后在 Claude Code 中输入/write-product-spec确认已加载。
详细介绍
write-product-spec
为 Warp 中的重要功能编写 PRODUCT.md 规格说明。
概述
产品规格说明应清晰明确地描述预期行为,确保 Agent 能正确实现功能并避免引入回归。从用户视角纯粹描述该功能——用户能看到、执行和体验什么,以及必须满足哪些不变量。不要包含实现细节(内部类型、状态布局、模块边界、数据流、算法)。
“用户”并不限于 Warp 应用的最终用户,而是指任何使用所设计界面的人:
- 对于 UI / UX 功能:使用 Warp 的用户。
- 对于数据模型:读取和写入该模型的代码。
- 对于 API、协议或库:该 API 的调用方——其他服务、客户端代码、插件或 Agent。
- 对于 CLI 工具或面向开发者的界面:调用该工具的开发者。
规格说明应从这些使用者的视角描述行为:界面的形式、他们可以执行的操作、他们会收到什么反馈、可以依赖的不变量,以及必须处理的边缘情况——但不规定界面底层的实现方式。
实现细节、验证和测试规划应写在配套的 TECH.md 中,由 write-tech-spec 技能生成。编写产品规格说明通常是两步流程的第一步:PRODUCT.md 获得认可后,调用 write-tech-spec 为同一功能生成 TECH.md(或告知用户这是预期的下一步)。产品规格说明的编写方式应确保可以直接据此编写技术规格说明。
将规格说明写入 specs/<id>/PRODUCT.md,其中 <id> 可以是:
- Linear 工单编号(例如
specs/APP-1234/PRODUCT.md) - 添加
gh-前缀的 GitHub issue 编号(例如specs/gh-4567/PRODUCT.md) - 简短的 kebab-case 功能名称(例如
specs/vertical-tabs-hover-sidecar/PRODUCT.md)
specs/ 的直接子项只能是以 id 命名的目录——不要创建以工程师姓名命名的子目录。
工单 / issue 引用为可选项。如果用户有 Linear 工单或 GitHub issue,请使用其 id。如果没有,请询问用户要用作目录名的功能名称。只有在用户明确要求时,才创建新的 Linear 工单或 GitHub issue;此时分别使用 Linear MCP 工具或 gh CLI(如果团队、标签或仓库不明确,则使用 ask_user_question)。
编写前
只收集所需的上下文:目录 id(Linear 工单、GitHub issue 或功能名称)、功能摘要、目标用户、关键行为、边缘情况,以及如何验证该功能。缺少上下文时,使用 ask_user_question 询问,不要猜测。
Figma 设计稿
如果功能包含任何 UI 或交互设计,请在起草 Behavior 部分前询问用户是否有 Figma 设计稿,并在用户提供链接时将其写入规格说明。设计稿通常是视觉状态、间距和边缘布局最可靠的依据——不询问可能导致 Behavior 部分猜测设计师已经确定的意图。
- 如果用户提供链接,请在简短的
## Figma部分下(或 Behavior 开头附近的行内位置)注明Figma: <link>。 - 如果用户确认没有设计稿,请注明
Figma: none provided,明确说明其不存在,避免产生歧义。 - 如果功能纯属后端功能(数据模型、API,或没有可视界面的 CLI),则跳过此问题并省略该部分。
不要悄悄省略设计背景;对于通常需要设计稿的功能,明确注明“无”比完全不提更好。
结构
必需章节:
- 摘要 — 用 1–3 句话描述此功能及其预期结果。
- 行为 — 规范的核心内容。用编号列出穷尽、可测试的不变量,以英文描述此功能的工作方式。请参阅下文“行为章节”——规范的重点就在这里,其他内容应保持精简,以避免重复。
可选章节 — 仅在能为核心内容提供额外信息时添加。若章节为空,则完全省略标题;不要用“无”作为占位内容。
- 问题 — 仅当摘要没有明确说明动机时添加。
- 目标 / 非目标 — 仅当范围含糊或存在争议时添加。
- Figma — 如有链接则附上链接;如果设计很重要但没有原型,则明确注明
Figma: none provided。非视觉功能完全省略此项。请参阅上文“Figma 原型”。 - 待解决问题 — 优先在相关行为旁内联添加
**待解决问题:** …。仅当有多个值得集中列出、尚未解决的问题时,才添加专门章节。
不要添加验证、成功标准或测试章节。验证和测试计划应写在配套的 TECH.md 中(由 write-tech-spec 生成)。将行为写成可单独测试的编号不变量——技术规范可以直接引用这些不变量。
行为章节
行为就是规范。其他内容只是背景说明。
行为章节的目标是完整描述此功能的工作方式,细致到可以据此直接编写技术规范,而无需作者猜测或重新推导产品意图。读者看完行为章节后,如果对功能在某些情况下的表现仍有疑问,说明该章节还不完整。
至少应描述:
- 默认行为和顺畅路径下的用户流程。
- 用户可见的所有状态及状态之间的转换。
- 用户可以提供的所有输入,以及功能对此的响应方式。
- 空状态、错误状态、加载/待处理状态和取消操作。
- 合理的实现者可能不会主动询问的边缘情况——权限被拒绝、离线、超时、状态变化之间的竞争、多个并发实例、数据过时或缺失、交互中途失去焦点、与相邻功能的交互。
- 相关的键盘、无障碍和焦点预期。
- 必须始终成立的不变量,以及不得回退的行为。
行为章节的长度应与功能相匹配。简单功能可能只需要几个不变量;复杂功能可能需要很多不变量,并按流程或状态划分子章节。规范的其余部分应保持精简,以便行为章节能根据功能所需尽可能详尽,同时避免文档整体臃肿。宁可多列一个边缘情况,也不要少列一个。
长度建议
行为章节的长度应与功能所需相匹配——不要为了达到行数目标而删减边缘情况。以下建议适用于行为章节以外的内容(摘要和可选章节):让背景说明保持精简,使规范的总长度反映功能的实际复杂度,而非结构上的冗余。
- 简单修复或范围有限的 UI 调整:无需编写规范。
- 小型功能(单个模块、少量边缘情况):背景说明加行为章节通常共约 30–60 行。
- 中型功能(跨模块、多种状态):通常共约 80–150 行。
- 大型或行为复杂的功能:可以更长,且大部分篇幅应放在行为章节中。
如果你发现自己在摘要、问题、目标和行为中重复描述同一内容,应精简背景说明,而不是删减行为内容。
编写指南
- 优先描述具体、可观察的行为,避免使用理想化措辞。
- 尽可能将 Behavior 写成不变量列表,而非散文。
- 记录绝不能退化的不变量,以及容易遗漏的边界情况。
- 除非对 UX 不可或缺,否则避免写入实现细节。
- 每个章节都应有存在的价值——如果某个章节会重复其他章节的内容,或只包含套话,就省略它。
保持规范与时俱进
已批准的规范可以与实现一起在同一个 PR 中发布。随着实现不断演进,如果面向用户的行为或 UX 细节发生变化,请在同一个 PR 中更新 PRODUCT.md。仓库中检入的规范应描述实际发布的功能。
对于大型功能,实现者可以酌情保留 DECISIONS.md 文件,总结设计和实现过程中做出的具体决策。如果这能帮助后续的 Agent,就提供该文件;否则略过。
相关技能
implement-specswrite-tech-specspec-driven-implementation
Behavior 章节示例
以下是一个假想功能的 Behavior 章节示例:在 Warp 块列表中渲染 GitHub 风格的 Markdown 表格。它展示了预期的形式——以编号列出可测试、面向用户的不变量,涵盖默认行为、边界情况、格式错误的输入、流式处理、选择和复制、搜索、共享、主题,以及不同界面的一致性,并包含一个内嵌的开放问题。
## Behavior
1. When a terminal output block contains a GitHub-flavored Markdown table (a header row, a separator row of one or more `---` segments, and one or more body rows, all delimited by `|`), that table renders as a visually formatted table in the block — not as raw pipe-delimited text.
2. The table renders with:
- A visually distinct header row.
- Aligned columns based on the separator row: `|:---|` left-align, `|:---:|` center, `|---:|` right-align. `|---|` with no colons falls back to the default alignment (left for text, right for numeric-looking values).
- Visible row separators (or equivalent spacing) consistent with the active theme.
3. Inline markdown inside a cell renders inline: bold, italic, inline code, strikethrough, and links all render the same way they do in the surrounding block output. Line breaks inside a cell (`<br>` or escaped `\n`) render as in-cell line breaks.
4. Column widths are chosen to fit the table's natural content when it fits inside the block. If a single cell's content is very long, that cell wraps its text within its column rather than forcing the column to an unreasonable width.
- **Open question:** when a wrapped cell would produce an unreasonably tall row, do we clip with an "expand" affordance, or let the row grow unbounded?
5. Horizontal scrolling: when the table's total width exceeds the block width — many columns, or wide columns that can't reasonably be narrowed — the table becomes horizontally scrollable within the block. Scrolling horizontally reveals off-screen columns without clipping or truncating them. Vertical scrolling of the block continues to work independently of table scroll.
6. When the block is resized (terminal resize, pane split, sidebar open/close), the table reflows to the new width without losing row or column order.
7. Empty cells render as visibly empty (same row height as surrounding cells, no placeholder text). A row with all empty cells still renders as a row.
8. A table with only a header and separator (zero body rows) renders as a header-only table, not as raw text.
9. A single-column table renders as a single-column table (not collapsed to a bullet list or similar).
10. Malformed tables fall back gracefully:
- Missing separator row → rendered as preformatted text, not as a table.
- Ragged rows (some rows have fewer or more cells than the header) → missing cells render empty; extra cells are shown, with the header row extended visually if possible. The block should never silently drop data.
- Unclosed table (last row truncated mid-stream) → rendered as a partial table; see (11).
11. Streaming output: while a command is still producing rows, the table renders incrementally. New rows append as they arrive. The header row locks in as soon as the separator line is received; rows before the separator render as plain text until the table is recognized.
12. Selection and copy:
- Selecting across cells with the mouse or keyboard selects their visible text content.
- Copying the selection produces tab-separated plain text by default (one row per line, cells separated by tabs). An affordance (context menu, shortcut) lets the user copy the original markdown source instead.
- Copying the entire block preserves the original markdown source verbatim.
13. Search within a block (find-in-block) matches against cell text content. Matches highlight in place in the rendered cell; navigating matches scrolls the table into view, including horizontally if the match is in an off-screen column.
14. Sharing or exporting a block (Warp Drive, share link, save as file) preserves the original markdown source, not the rendered form.
15. Theming: table borders, header backgrounds, alternating row shading (if any), and link/code styles all come from the active Warp theme. No hard-coded colors.
16. Markdown tables render consistently wherever block-list markdown already renders — command output, agent responses, and any other block type that supports inline markdown. The same input produces the same table in each surface.
17. Non-table pipe content is not misrendered as a table. Text that contains `|` characters but no valid header-separator line remains plain text, even if it visually resembles a table.