write-a-spec

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Write a spec — scope a feature and commit an implementation spec

编写 spec——界定功能范围并提交 implementation spec

This skill is pack guidance. The platform
/open-knowledge
skill (read/write/preview/linking/grounding rules) still governs every markdown operation — this layers spec-writing craft on top.
A spec is the contract between "we decided to build this" and "here is how it gets built." It is derived from an ACCEPTED proposal, maps the system that exists TODAY before proposing a change, and records its one-way-door choices in a Decision Log so the reasoning survives the author leaving. A spec that skips the current-system mapping, or resolves its open questions silently, is a liability — it reads authoritative while being unfounded.
specs/
uses a folder-per-spec shape:
specs/NNN-name/
holding
spec.md
, and (when the work is actually going to be built)
plan.md
+
tasks.md
. The pack ships all three templates.
status
on
spec.md
flows
draft → ... → shipped
.
这是 pack 指导。平台
/open-knowledge
skill(read/write/preview/linking/grounding 规则)仍管辖所有 markdown 操作——本 skill 在其基础上叠加 spec 编写技巧。
spec 是“我们决定构建它”与“它将如何被构建”之间的契约。它派生自一个已 ACCEPTED 的 proposal,先映射今天已经存在的系统,再通过 Decision Log 记录其 one-way-door 选择,使推理过程不会随着作者离开而丢失。跳过 current-system mapping,或悄悄解决 open questions 的 spec 是一项隐患——它读起来很权威,却毫无依据。
specs/
采用每个 spec 一个文件夹的结构:
specs/NNN-name/
包含
spec.md
,并且(当工作确实将被构建时)包含
plan.md
+
tasks.md
。pack 提供了全部三个模板。
spec.md
上的
status
按
draft → ... → shipped
流转。

Autonomy mode

自主模式

ModeBehaviorHow entered
Supervised (default)STOP at the Step 2 scoping gate for user confirmation of Goals / Non-goals / change shape. Route open decisions interactively.Default when a user drives the session.
HeadlessAuto-confirm the scoping proposal after presenting it; auto-select routing decisions. All other gates (parent-proposal check, current-system mapping, Decision Log, validation, grounding) still enforced.Explicit "don't wait for me", "just proceed", "run headless" — or non-interactive container environments.
In headless mode, propose the scope AND proceed immediately; record in the Decision Log that scope was auto-confirmed without user sign-off.

模式行为进入方式
Supervised(默认)在 Step 2 范围界定 gate 处停止,等待用户确认 Goals / Non-goals / change shape。以交互方式处理 open decisions。用户主导会话时默认。
Headless展示范围界定方案后自动确认;自动选择路由决策。所有其他 gate(parent-proposal check、current-system mapping、Decision Log、validation、grounding)仍须执行。明确说出 "don't wait for me"、"just proceed"、"run headless",或处于非交互式容器环境。
在 Headless 模式下,展示范围界定方案后立即继续;并在 Decision Log 中记录范围是在未获得用户签字确认的情况下自动确认的。

Mandatory execution order

强制执行顺序

Hard gates — do NOT skip ahead. If you find yourself about to write
## Design
before you have read the actual code the change touches, STOP — you skipped Step 1.
  1. Find the parent proposal. No accepted proposal behind a spec is a smell.
  2. Map the CURRENT system. Read real code + prior specs + guides. Write what exists today, with pointers.
  3. Scoping STOP gate. Propose Goals / Non-goals / change shape. In Supervised mode, WAIT.
  4. Create the spec folder +
    spec.md
    from the
    spec
    template; allocate
    NNN
    by listing, not guessing.
  5. Fill Goals / Non-goals — non-goals temporal, each with a reason.
  6. Design — options considered, one chosen, why; one-way doors flow to the Decision Log.
  7. Decision Log — append the section; each entry survives the author.
  8. Open Questions — a live backlog, each with its closing evidence and decider.
  9. Migration + Test plan — real states, real tiers, explicit gaps.
  10. plan.md
    +
    tasks.md
    — only when the spec is going to be built.
  11. Link + validate — backlinks, dead-links clean, frontmatter complete.
  12. Recap — and what would move it out of
    draft
    .

Hard gates——不要跳过。 如果你正准备编写
## Design
,却尚未阅读变更实际涉及的代码,请停止——你跳过了 Step 1。
  1. 查找父级 proposal。 没有已接受的 proposal 支撑的 spec 是一个危险信号。
  2. 映射 CURRENT 系统。 阅读真实代码 + 既有 specs + guides。写下今天存在的内容,并附上指针。
  3. 范围界定 STOP gate。 提出 Goals / Non-goals / change shape。在 Supervised 模式下,等待。
  4. 创建 spec 文件夹 +
    spec.md
    :从
    spec
    模板生成;通过列出目录来分配
    NNN
    ,不要猜测。
  5. 填写 Goals / Non-goals——non-goals 具有时效性,每项都要说明原因。
  6. Design——列出考虑过的选项、选定方案及其原因;one-way doors 写入 Decision Log。
  7. Decision Log——追加该章节;每项记录都应在作者离开后仍然保留。
  8. Open Questions——持续维护的 backlog,每项都包含结案证据和决策者。
  9. Migration + Test plan——真实状态、真实测试层级、明确差距。
  10. plan.md
    +
    tasks.md
    ——仅当 spec 确实将被构建时创建。
  11. 链接 + 验证——backlinks、清理 dead-links、补全 frontmatter。
  12. 总结——说明还需要什么才能脱离
    draft
    状态。

Step 0: Find the parent proposal

Step 0:查找父级 proposal

A spec implements a decision that was already made. Before scoping anything, find the accepted proposal it derives from.
  1. exec("ls -A proposals/")
    — surface the proposal set with frontmatter enrichment.
  2. exec("ls -A decisions/")
    — an accepted proposal often has a matching frozen record here.
  3. search({ query: "<feature name or problem statement>" })
    — semantic match across both folders when the name differs from the filename.
  4. For the 1-2 strongest candidates,
    exec("cat proposals/0004-x.md")
    — read the accepted design and its unresolved questions in full; those unresolved questions become your Open Questions seed.
Route on what you find:
  • Accepted proposal exists → note its path; it becomes
    parent_proposal:
    in the spec frontmatter. Proceed to Step 1.
  • Proposal exists but is not accepted (
    draft
    ,
    fcp
    ,
    rejected
    ) → STOP. A spec for an un-accepted proposal builds on sand. Surface this: "The proposal
    <path>
    is at
    status: <x>
    , not accepted. A spec should follow an accepted proposal. Want me to help get it accepted first, or proceed anyway?"
  • No proposal at all → say so plainly: "There's no accepted proposal behind this feature. Specs derive from proposals — the proposal is where we argue whether to build; the spec is how. I can frame a proposal first (that's the
    /frame-a-proposal
    skill), or, if you acknowledge the risk, proceed straight to a spec and record that we skipped the proposal." If the user opts to proceed, record that choice in the Decision Log (Step 6) with its rationale. Do not silently skip it.
HARD GATE: do not create
spec.md
until the parent-proposal question is resolved one way or the other and, if skipped, acknowledged by the user.

spec 实现的是一项已经做出的决定。在界定任何范围之前,先找到它所派生的已接受 proposal。
  1. exec("ls -A proposals/")
    ——列出 proposal 集合,并补充 frontmatter 信息。
  2. exec("ls -A decisions/")
    ——已接受的 proposal 通常在
    decisions/
    中有对应的冻结记录。
  3. search({ query: "<feature name or problem statement>" })
    ——当名称与文件名不同时,在这两个文件夹中进行语义匹配。
  4. 对于最匹配的 1—2 个候选项,执行
    exec("cat proposals/0004-x.md")
    ——完整阅读已接受的设计及其未解决问题;这些未解决问题将成为 Open Questions 的初始内容。
根据发现进行路由:
  • 存在已接受的 proposal→记录其路径;它将成为 spec frontmatter 中的
    parent_proposal:
    。继续执行 Step 1。
  • 存在 proposal,但尚未被接受(
    draft
    、
    fcp
    、
    rejected
    )→停止。为尚未被接受的 proposal 编写 spec 无异于建立在流沙之上。请明确告知用户:“proposal
    <path>
    的
    status
    为
    <x>
    ,尚未被接受。spec 应基于已接受的 proposal。你希望我先协助完成接受流程,还是无论如何继续?”
  • 完全没有 proposal→直接说明:“此功能背后没有已接受的 proposal。spec 派生自 proposal——proposal 用于讨论是否构建,而 spec 用于说明如何构建。我可以先帮你构建 proposal(使用
    /frame-a-proposal
    skill),或者,如果你承认其中的风险,可以直接继续编写 spec,并记录我们跳过了 proposal。”如果用户选择继续,请在 Decision Log(Step 6)中记录该选择及其理由。不要悄悄跳过。
**HARD GATE:**在父级 proposal 的问题以某种方式得到解决之前,不要创建
spec.md
;如果选择跳过,则必须获得用户的明确确认。

Step 1: Map the CURRENT system before designing the change

Step 1:在设计变更之前映射 CURRENT 系统

The most common spec failure is designing against an imagined system. You cannot write a sound
## Design
or a real
## Migration
without knowing what the running system does today.
Read the code that exists — with native host tools. Source code (
.ts
,
.py
, config, schema) lives OUTSIDE the knowledge base, so read it with the host's native file tools (Read / Grep / Glob), not OpenKnowledge verbs. Trace the actual flow end to end: the entry points, the modules the change will touch, the data shapes, the call chain across boundaries.
Read the knowledge base with OpenKnowledge verbs. Prior specs and guides ARE in-scope markdown:
  • exec("ls -A specs/")
    — see what's been specced before; a sibling spec may already map the same subsystem.
  • exec("cat specs/003-x/spec.md")
    — read the closest prior spec's Design + Migration for the shape and the pointers.
  • exec("ls -A guides/")
    then
    exec("cat guides/<runbook>.md")
    — runbooks describe operational reality the code alone won't tell you.
  • search({ query: "<subsystem name>" })
    — surface adjacent decisions and postmortems that constrain the design.
Write what exists today into the spec's Design section as a "Current system" subsection first, with file/module pointers (
src/foo/bar.ts
, the
handleX
function, the
widgets
table). Concrete pointers are what make the spec verifiable and let a reviewer check your map against the code.
HARD GATE: do not draft the proposed
## Design
until the current-system map is written with real pointers. If you cannot point at where a behavior lives today, you do not yet understand it well enough to change it.

最常见的 spec 失败,是基于一个想象出来的系统进行设计。如果不了解运行中的系统今天实际如何工作,就无法写出可靠的
## Design
或真实的
## Migration
。
使用宿主原生工具阅读现有代码。 源代码(
.ts
、
.py
、config、schema)位于 knowledge base 之外,因此应使用宿主的原生文件工具(Read / Grep / Glob)读取,而不是 OpenKnowledge verbs。端到端追踪实际流程:入口点、变更将触及的 modules、数据 shapes,以及跨边界的 call chain。
使用 OpenKnowledge verbs 阅读 knowledge base。 既有 specs 和 guides 属于范围内的 markdown:
  • exec("ls -A specs/")
    ——查看此前编写过哪些 specs;同类 spec 可能已经映射过相同的 subsystem。
  • exec("cat specs/003-x/spec.md")
    ——阅读最接近的既有 spec 的 Design + Migration,了解其结构和指针。
  • exec("ls -A guides/")
    ,然后
    exec("cat guides/<runbook>.md")
    ——runbooks 描述了仅靠代码无法了解的 operational reality。
  • search({ query: "<subsystem name>" })
    ——找出对设计构成约束的相邻 decisions 和 postmortems。
在 spec 的 Design 章节中,首先以“Current system”子章节写下今天存在的内容,并附上 file/module 指针(
src/foo/bar.ts
、
handleX
函数、
widgets
表)。具体的指针让 spec 可以被验证,也让 reviewer 能够对照代码检查你的映射。
**HARD GATE:**在写出带有真实指针的 current-system map 之前,不要起草建议的
## Design
。如果你无法指出某项行为今天具体位于何处,就说明你尚未充分理解它,无法安全地修改它。

Step 2: Scoping STOP gate

Step 2:范围界定 STOP gate

Before writing the spec body, propose the scope and get confirmation. Getting Goals / Non-goals wrong is expensive to unwind after the spec is drafted.
Present this to the user:
undefined
在编写 spec 正文之前,先提出范围并获得确认。Goals / Non-goals 一旦有误,spec 起草完成后再更改的成本会非常高。
向用户展示以下内容:
undefined

Proposed scope

Proposed scope

Parent proposal: [path — or "none; proceeding without one per your acknowledgement"]
Goals (what this spec delivers):
  1. [Goal — an observable capability or change]
  2. ...
Non-goals (explicitly out of THIS spec, and why):
  1. [Thing] — deferred because [reason]
  2. ...
Shape of the change: [2-4 sentences: the approach at a high level, and which modules from the Step 1 map it touches]
Open questions I already see: [1-3 — the ones that will gate the design]

**Supervised mode:** STOP and WAIT for confirmation. Do not create the spec folder until the user confirms or adjusts.

**Headless mode:** present the scope AND proceed; note the auto-confirmation in the Decision Log.

Scoping discipline:
- If the feature is vague, narrow it here, not in the Design. "Which surfaces? For whom? What's explicitly deferred?"
- Name the beneficiary of each goal — a goal with no one who benefits is scope creep.
- Do not invent requirements the proposal never established (see Non-goals for this skill). If a goal isn't traceable to the parent proposal, flag it and ask.

---
Parent proposal: [path — or "none; proceeding without one per your acknowledgement"]
Goals (what this spec delivers):
  1. [Goal — an observable capability or change]
  2. ...
Non-goals (explicitly out of THIS spec, and why):
  1. [Thing] — deferred because [reason]
  2. ...
Shape of the change: [2-4 sentences: the approach at a high level, and which modules from the Step 1 map it touches]
Open questions I already see: [1-3 — the ones that will gate the design]

**Supervised 模式:**停止并等待确认。在用户确认或调整之前,不要创建 spec 文件夹。

**Headless 模式:**展示范围并继续执行;在 Decision Log 中注明此次自动确认。

范围界定纪律:

- 如果功能描述含糊,应在此处收窄,而不是留到 Design 中。“涉及哪些界面?面向谁?明确推迟了什么?”
- 指明每个 goal 的受益者——没有受益者的 goal 就是 scope creep。
- 不要发明 proposal 从未确立的 requirements(参见本 skill 的 Non-goals)。如果某个 goal 无法追溯到父级 proposal,请指出并询问。

---

Step 3: Create the spec folder +
spec.md

Step 3:创建 spec 文件夹 +
spec.md

Allocate the next
NNN
by listing, not guessing:
  1. exec("ls -A specs/")
    — read the existing
    NNN-name/
    folders. Take the highest
    NNN
    , add one, zero-pad to the existing width (
    003
    →
    004
    ).
  2. Choose a short kebab-case name from the feature:
    specs/004-payment-retries/
    .
  3. Create the spec:
write({ document: { path: "specs/004-payment-retries/spec.md", template: "spec" } })
The
spec
template ships frontmatter and these H2 sections in order:
## Goals
,
## Non-goals
,
## Design
,
## Migration
,
## Test plan
. Fill the frontmatter now:
yaml
type: spec
description: "<one line: what this spec delivers>"
status: draft
owner: <user>
target_release:
created: <today YYYY-MM-DD>
parent_proposal: ./proposals/0004-payment-retries.md   # or note the skip
tags: [spec]
Set
parent_proposal:
to the relative markdown path from Step 0. Leave
target_release:
blank until the plan exists.

通过列出目录而不是猜测来分配下一个
NNN
:
  1. exec("ls -A specs/")
    ——读取现有的
    NNN-name/
    文件夹。取最大的
    NNN
    加一,并按照现有宽度补零(
    003
    →
    004
    )。
  2. 根据功能选择一个简短的 kebab-case 名称:
    specs/004-payment-retries/
    。
  3. 创建 spec:
write({ document: { path: "specs/004-payment-retries/spec.md", template: "spec" } })
spec
模板提供了 frontmatter,并按顺序包含以下 H2 章节:
## Goals
、
## Non-goals
、
## Design
、
## Migration
、
## Test plan
。现在填写 frontmatter:
yaml
type: spec
description: "<one line: what this spec delivers>"
status: draft
owner: <user>
target_release:
created: <today YYYY-MM-DD>
parent_proposal: ./proposals/0004-payment-retries.md   # or note the skip
tags: [spec]
将
parent_proposal:
设置为 Step 0 中得到的相对 markdown 路径。在 plan 存在之前,保持
target_release:
为空。

Step 4: Fill Goals and Non-goals

Step 4:填写 Goals 和 Non-goals

Edit the
## Goals
and
## Non-goals
sections via
edit({ ... })
.
Goals — each an observable outcome, not an implementation detail. "Failed payments retry with backoff and surface a final status" is a goal; "add a
retryCount
column" is a design choice. Tie each goal to the beneficiary and, where possible, to the parent proposal's motivation.
Non-goals are TEMPORAL, not eternal. Frame each as "not in THIS spec" with the reason it's deferred — never as "we will never do this." "Multi-currency retries — deferred; the retry engine is currency-agnostic and multi-currency is its own proposal" tells a future reader the door is open and why it's closed for now. "We don't support multi-currency" reads as a permanent product stance the spec has no authority to make. Every non-goal carries its why.

通过
edit({ ... })
编辑
## Goals
和
## Non-goals
章节。
Goals——每一项都应是可观察的结果,而不是实现细节。“失败支付会按退避策略重试,并展示最终状态”是 goal;“添加
retryCount
列”则是设计选择。将每个 goal 与受益者关联,并在可能的情况下与父级 proposal 的动机关联。
Non-goals 具有时效性,而非永久有效。 将每一项表述为“不在 THIS spec 中”,并说明推迟原因——绝不能表述为“我们永远不做这件事”。“多币种重试——推迟;重试引擎与币种无关,多币种需要单独的 proposal”会让未来读者知道这扇门仍然开放,以及它现在为何关闭。“我们不支持多币种”听起来像 spec 无权做出的永久产品立场。每个 non-goal 都必须包含推迟原因。

Step 5: Design

Step 5:Design

Edit
## Design
. It has two layers, in order:
  1. Current system (from Step 1) — what exists today, with file/module pointers. Keep this; it is what makes the proposed change legible.
  2. Proposed change — the options you considered, the one you chose, and why.
For each meaningful design fork, write: the options on the table, the one chosen, and the reasoning. Name the trade-off you accepted. A design that presents only the chosen option hides the fact that a choice was made — and hides it from the reviewer whose job is to pressure-test it.
Every one-way-door choice goes to the Decision Log (Step 6). A one-way door is a decision that is expensive or impossible to reverse once code ships on it: a wire format, a schema migration that drops data, a public API shape, a security boundary. Reversible choices (an internal helper name, a log level) do not need a log entry — keep the log to decisions that will cost real money to undo.

编辑
## Design
。它包含两层,顺序如下:
  1. Current system(来自 Step 1)——今天存在的内容,并附上 file/module 指针。保留这一部分;它让建议的变更易于理解。
  2. Proposed change——考虑过的选项、最终选择的方案及其原因。
对于每个重要的 design fork,写明:桌上有哪些选项、选择了哪一个,以及理由。指出你接受的 trade-off。只展示所选方案的设计会掩盖“确实做过选择”这一事实,也会让负责施压测试的 reviewer 看不到它。
每一个 one-way-door 选择都进入 Decision Log(Step 6)。 one-way door 是指一旦代码发布,撤回成本极高或几乎不可能撤回的决定,例如 wire format、会丢数据的 schema migration、public API shape、security boundary。可逆选择(内部 helper 名称、日志级别等)不需要记录——Decision Log 只保留那些撤销后会付出真实成本的决策。

Step 6: Decision Log

Step 6:Decision Log

The
spec
template does NOT ship a Decision Log — you append it. Add an
## Decision Log
section to
spec.md
after
## Test plan
. This is the single most valuable artifact in the spec: it is what a new owner reads to understand why the system is shaped the way it is, without having to re-derive it or ask someone who has left.
Each entry is a short block:
markdown
undefined
spec
模板不包含 Decision Log——你需要追加它。在
spec.md
的
## Test plan
之后添加一个
## Decision Log
章节。这是 spec 中最有价值的产物:新 owner 可以通过它了解系统为何被设计成当前形态,而不必重新推导,也不必询问已经离开的作者。
每条记录都是一个简短的区块:
markdown
undefined

DL-1: <the question, as a question>

DL-1: <the question, as a question>

  • Options: A [...], B [...], C [...]
  • Choice: B
  • Rationale: [why B beat A and C — the trade-off accepted, the evidence]
  • Date: YYYY-MM-DD
  • Reverses if: [what new fact or condition would force revisiting this]

Discipline:
- One entry per one-way-door choice from Step 5, plus any process decision (e.g. "proceeded without a parent proposal per the user's acknowledgement on <date>").
- The `Reverses if` line is not optional — a decision with no stated reversal condition can never be re-examined honestly.
- This log is scoped to THIS spec. It is not the project's decision record. Recording an architecture decision in `decisions/` is the sibling `/record-a-decision` skill's job; do not write ADRs here.

---
  • Options: A [...], B [...], C [...]
  • Choice: B
  • Rationale: [why B beat A and C — the trade-off accepted, the evidence]
  • Date: YYYY-MM-DD
  • Reverses if: [what new fact or condition would force revisiting this]

纪律:

- Step 5 中的每个 one-way-door 选择对应一条记录;任何流程性决定也需记录,例如“根据用户在 `<date>` 的确认,在没有父级 proposal 的情况下继续”。
- `Reverses if` 一行不可省略——没有明确反转条件的决定永远无法被诚实地重新审视。
- 此日志仅限于 THIS spec。它不是项目的 decision record。在 `decisions/` 中记录架构决策是同类 `/record-a-decision` skill 的职责;不要在这里编写 ADR。

---

Step 7: Open Questions

Step 7:Open Questions

A spec ships with its open questions MARKED, not silently resolved. Guessing at an answer and writing it as settled is how a spec misleads everyone downstream.
Append an
## Open Questions
section (the template does not ship one). Keep it as a live backlog:
markdown
undefined
spec 应带着明确标记的 open questions 发布,而不是悄悄解决它们。猜测答案并将其写成已确定事项,是 spec 误导所有下游人员的方式。
追加一个
## Open Questions
章节(模板不包含该章节)。将其作为持续维护的 backlog:
markdown
undefined

OQ-1: <the unresolved question>

OQ-1: <the unresolved question>

  • Blocks: [which goal / design decision this gates, or "nothing — nice to know"]
  • Closed by: [the evidence that would resolve it — a benchmark, a load test, a product call, a spike]
  • Decider: [who has authority to close it — a role, not a name that will rot]
  • Status: open

Discipline:
- If a question blocks a Decision Log entry, say so in both places — the log entry stays provisional until the question closes.
- Prefer "we don't know yet, here's how we'd find out" over a confident guess. The boundary of what you know is information; a fabricated answer is a hazard.
- As questions close during the spec's life, flip `Status: open` to `resolved → DL-N` and move the resolution into the Decision Log — don't just delete the question.

---
  • Blocks: [which goal / design decision this gates, or "nothing — nice to know"]
  • Closed by: [the evidence that would resolve it — a benchmark, a load test, a product call, a spike]
  • Decider: [who has authority to close it — a role, not a name that will rot]
  • Status: open

纪律:

- 如果某个问题阻塞 Decision Log 中的一项记录,应在这两处都注明——在问题关闭之前,该记录保持暂定状态。
- 优先采用“我们还不知道,但可以这样查明”,而不是自信地猜测。知道与不知道之间的边界本身就是信息;编造答案则会造成危险。
- 当问题在 spec 生命周期内关闭时,将 `Status: open` 改为 `resolved → DL-N`,并把解决方案移入 Decision Log——不要只是删除问题。

---

Step 8: Migration and Test plan

Step 8:Migration 和 Test plan

Both real, not decorative. A migration section that says "migrate the data" and a test plan that says "add tests" are worse than nothing — they signal coverage that doesn't exist.
## Migration
— name the STATES a live system passes through, in order, and what is true at each. A running system does not teleport from old to new; it lives in intermediate states where both must work:
  • The states, e.g.: (1) new code deployed, feature flag off, old path live. (2) backfill runs, both schemas readable. (3) flag on for a cohort, dual-write. (4) flag on globally. (5) old path + old column removed.
  • What is reversible at each state and where the point of no return is (link that point to its Decision Log entry).
  • Data at rest: what gets backfilled, what gets dropped, whether the drop is recoverable.
  • If the change needs no migration (net-new, no live data, no deployed callers), say so explicitly and why — don't leave the section empty.
## Test plan
— name WHAT is verified and at WHICH tier, and explicitly what is NOT verifiable and why:
  • Per goal: the tier that proves it — unit (a pure function's edges), integration (a component against its real collaborators), end-to-end (the user-visible flow), contract (a boundary both sides must honor).
  • The failure modes worth a test, not just the happy path — the retry that exhausts, the migration that half-completes, the concurrent write.
  • What cannot be verified before ship, and why — a production-only load characteristic, a third-party behavior you can't stage, a race that only manifests under real traffic. Naming the gap is what lets the reviewer decide whether it's acceptable; hiding it is how the gap becomes an incident.

两者都必须真实,而不能仅作装饰。只写“迁移数据”的 migration 章节和只写“添加测试”的 test plan 反而更糟——它们暗示着实际上并不存在的覆盖率。
## Migration
——按顺序说明运行中的系统会经历哪些 STATES,以及每个状态中有哪些事实成立。运行中的系统不会从旧状态瞬间跳到新状态;它会经历新旧两种方式都必须可用的中间状态:
  • 各状态,例如:(1) 新代码已部署,feature flag 关闭,旧路径仍在运行。(2) backfill 执行中,两种 schema 均可读取。(3) 为一组 cohort 开启 flag,进行 dual-write。(4) 全局开启 flag。(5) 删除旧路径和旧列。
  • 每个状态下哪些操作可以回退,以及不可逆点位于何处(将该点关联到对应的 Decision Log 条目)。
  • 静态数据:哪些内容会被 backfill,哪些会被 drop,以及 drop 是否可恢复。
  • 如果变更不需要 migration(全新功能、无在线数据、无已部署调用方),请明确说明原因——不要让该章节留空。
## Test plan
——说明验证什么、在哪个层级验证,并明确指出什么无法验证以及原因:
  • 针对每个 goal:用于证明它的测试层级——unit(纯函数的边界情况)、integration(组件与真实协作者协作)、end-to-end(用户可见流程)、contract(双方都必须遵守的边界)。
  • 值得测试的 failure modes,而不只是 happy path——重试耗尽、迁移只完成一半、并发写入。
  • 发布前无法验证的内容及其原因——仅在生产环境出现的负载特征、无法搭建的第三方行为、只有在真实流量下才会显现的 race。明确指出这些差距,可以让 reviewer 判断是否可以接受;隐藏差距只会让差距最终变成事故。

Step 9:
plan.md
and
tasks.md

Step 9:
plan.md
和
tasks.md

Only when the spec is actually going to be built. A spec at
status: draft
that may not be implemented does not need these yet — creating them early produces stale scaffolding. Create them when the spec is greenlit for implementation.
write({ document: { path: "specs/004-payment-retries/plan.md",  template: "spec-plan"  } })
write({ document: { path: "specs/004-payment-retries/tasks.md", template: "spec-tasks" } })
Set
parent_spec: ./spec.md
in both frontmatter blocks.
plan.md
(
## Approach
,
## Phases
,
## Risks + unknowns
,
## Dependencies
,
## Rollout
) — the how-to-build sequenced into phases.
Risks + unknowns
pulls from the Open Questions that block implementation;
Rollout
mirrors the Migration states.
tasks.md
(
## Tasks
checklist,
## Done when
,
## Out of scope
) — the work broken down. Two rules:
  • Ordered by dependency. A task that depends on another comes after it. The reader should be able to work top to bottom.
  • Each task independently verifiable. "Add retry backoff to the payment worker — verified by the exhaustion integration test" is a task; "improve reliability" is not. If you can't state how a task is checked, it's not decomposed enough.
## Done when
restates the spec's Goals as the completion bar.
## Out of scope
mirrors the Non-goals.

仅当 spec 确实将被构建时创建。 仍处于
status: draft
且可能不会实施的 spec 暂时不需要这些文件;过早创建只会产生过时的脚手架。当 spec 获准实施时再创建。
write({ document: { path: "specs/004-payment-retries/plan.md",  template: "spec-plan"  } })
write({ document: { path: "specs/004-payment-retries/tasks.md", template: "spec-tasks" } })
在两个 frontmatter 区块中都设置
parent_spec: ./spec.md
。
plan.md
(
## Approach
、
## Phases
、
## Risks + unknowns
、
## Dependencies
、
## Rollout
)——将实施方式按阶段排序。
Risks + unknowns
提取自阻塞实施的 Open Questions;
Rollout
与 Migration 状态保持一致。
tasks.md
(
## Tasks
checklist、
## Done when
、
## Out of scope
)——将工作拆分。两条规则:
  • 按依赖关系排序。 依赖另一项 task 的任务必须排在其后。读者应能从头到尾依次执行。
  • 每项 task 均可独立验证。 “为 payment worker 添加带 backoff 的重试——通过耗尽场景的 integration test 验证”是一项 task;“提高可靠性”则不是。如果无法说明如何检查一项 task,就说明拆分得还不够细致。
## Done when
将 spec 的 Goals 重新表述为完成标准。
## Out of scope
与 Non-goals 保持一致。

Step 10: Link and validate

Step 10:链接与验证

Specs that don't link back are islands. Wire the spec into the graph and prove it's clean:
  • Backlink the parent proposal inline in the spec body:
    derived from [Payment retries proposal](./proposals/0004-payment-retries.md)
    . Plain markdown relative link — never backticked, never an HTML anchor.
  • Link every decision the spec depends on — if the design rests on a frozen record in
    decisions/
    , link it where it's relied upon.
  • Update the parent proposal (and any hub) to link forward to this spec, so the spec is discoverable via backlinks.
    links({ kind: "backlinks", ... })
    to confirm the reverse edge registered.
  • audit({ path: "specs/004-payment-retries/spec.md" })
    returns clean (every lint violation + broken internal link) — fix every finding.
  • Frontmatter complete:
    type
    ,
    description
    ,
    status
    ,
    owner
    ,
    parent_proposal
    ,
    created
    ,
    tags
    all present.

没有链接回链的 spec 是孤岛。将 spec 接入知识图谱,并证明它是干净的:
  • 在 spec 正文中回链父级 proposal:
    derived from [Payment retries proposal](./proposals/0004-payment-retries.md)
    。使用纯 markdown 相对链接——不要放在反引号中,也不要使用 HTML anchor。
  • 链接 spec 所依赖的每一项 decision——如果设计依赖
    decisions/
    中的冻结记录,应在依赖位置链接它。
  • 更新父级 proposal(以及任何 hub),使其能够向前链接到本 spec,从而可以通过 backlinks 找到 spec。使用
    links({ kind: "backlinks", ... })
    确认反向边已注册。
  • audit({ path: "specs/004-payment-retries/spec.md" })
    返回 clean(不存在任何 lint violation + broken internal link)——修复每一项发现。
  • Frontmatter 完整:
    type
    、
    description
    、
    status
    、
    owner
    、
    parent_proposal
    、
    created
    、
    tags
    全部存在。

Step 11: Recap

Step 11:总结

Close the loop with the user:
undefined
与用户完成闭环:
undefined

Recap

Recap

  • Spec at specs/004-payment-retries/spec.md, status: draft
  • Derived from [proposal] (or: proceeding without one, logged as DL-N)
  • N goals, M non-goals (all temporal, with reasons)
  • Decision Log: K one-way-door choices recorded
  • Open Questions: J open — [name the ones that block the design]
  • Migration: [one line] · Test plan: [what's covered / what can't be]
To move out of
draft
:
[the specific things — usually: close the design-blocking open questions, get the owner's sign-off, and create plan.md + tasks.md once greenlit for build]

State plainly what is still open. A spec's honesty about its own gaps is what makes it trustworthy.

---
  • Spec at specs/004-payment-retries/spec.md, status: draft
  • Derived from [proposal] (or: proceeding without one, logged as DL-N)
  • N goals, M non-goals (all temporal, with reasons)
  • Decision Log: K one-way-door choices recorded
  • Open Questions: J open — [name the ones that block the design]
  • Migration: [one line] · Test plan: [what's covered / what can't be]
To move out of
draft
:
[the specific things — usually: close the design-blocking open questions, get the owner's sign-off, and create plan.md + tasks.md once greenlit for build]

明确说明仍然开放的事项。spec 对自身缺口的坦诚,是它值得信赖的基础。

---

Non-goals

Non-goals

  • Don't invent requirements the proposal didn't establish. The spec is how, not a second bite at whether. A goal with no root in the parent proposal is scope creep — flag it, don't smuggle it in.
  • Don't record decisions in
    decisions/
    .
    That's the sibling
    /record-a-decision
    skill. The spec's Decision Log is scoped to THIS spec's one-way doors; it is not the project's ADR record.
  • Don't skip the current-system mapping. Designing against an imagined system is the top failure mode. No
    ## Design
    before Step 1's map with real pointers.
  • Don't mark
    shipped
    before the work ships.
    status
    reflects reality. A spec is
    draft
    until built, and only
    shipped
    once the code is live — never aspirationally.
  • Don't write the postmortem. If the shipped feature breaks, the incident write-up is the
    /write-a-postmortem
    skill's job, not an addendum here.
  • Don't silently resolve Open Questions. A marked open question is information; a fabricated answer is a hazard. Ship the spec with its unknowns visible.
  • 不要发明 proposal 未确立的 requirements。 spec 负责说明如何实现,而不是再次讨论是否实现。没有父级 proposal 依据的 goal 就是 scope creep——应指出问题,而不是偷偷将其纳入。
  • 不要在
    decisions/
    中记录 decisions。
    那是同类
    /record-a-decision
    skill 的职责。spec 的 Decision Log 仅限于 THIS spec 的 one-way doors,不是项目的 ADR record。
  • 不要跳过 current-system mapping。 基于想象出来的系统进行设计是首要失败模式。在 Step 1 写出带有真实指针的 map 之前,不要编写
    ## Design
    。
  • 不要在工作发布前将状态标记为
    shipped
    。
    status
    必须反映真实情况。spec 在代码上线前始终是
    draft
    ,只有代码实际发布后才是
    shipped
    ——绝不能带有 aspirational 状态。
  • 不要编写 postmortem。 如果已发布的功能发生故障,事故复盘属于
    /write-a-postmortem
    skill,而不是本 spec 的附录。
  • 不要悄悄解决 Open Questions。 明确标记的 open question 本身就是信息;编造答案会造成危险。发布 spec 时必须让其中未知的事项保持可见。