Chapter 12: Writing Specs—Requirements and Scenarios
Learning Objectives
Write testable contracts. Each Scenario should be translatable into a pytest function.
Spec Hierarchy
flowchart TB
Cap["Capability
(specs/<name>/spec.md)"] --> R1["Requirement 1
(SHALL statement)"]
Cap --> R2["Requirement 2"]
Cap --> Rn["Requirement N"]
R1 --> S1a["Scenario 1.1
(WHEN/THEN)"]
R1 --> S1b["Scenario 1.2"]
R2 --> S2a["Scenario 2.1"]
R2 --> S2b["Scenario 2.2"]
R2 --> S2c["Scenario 2.3"]
style Cap fill:#fce4ec
style R1 fill:#fff9c4
style R2 fill:#fff9c4
style Rn fill:#fff9c4→ A Capability contains multiple Requirements, and a Requirement contains at least one Scenario.
How to Write Requirements
### Requirement: Command Completion Detection
The tool SHALL identify command completion time and exit code via marker output, without relying on prompt string matching.
Keywords:
| Strength | Keyword | When to Use |
|---|---|---|
| Mandatory | SHALL / MUST | The system must do this |
| Prohibited | SHALL NOT / MUST NOT | The system must not do this |
| Recommended | SHOULD (use sparingly) | Strongly advised but exceptions allowed |
| Optional | MAY (use sparingly) | Can be done or not |
→ For the MVP phase, use SHALL/MUST for all Requirements. SHOULD and MAY create "loopholes for future debate," so use them sparingly.
How to Write Scenarios
#### Scenario: Command Completion Detection
- **WHEN** the tool sends a command to tmux
- **THEN** the tool identifies command completion time and exit code via marker output, without relying on prompt string matching
WHEN/THEN statements = can be translated into pytest test cases:
def test_terminal__command_completion_detection():
"""Scenario: Command Completion Detection"""
# WHEN the tool sends a command to tmux
terminal.send("echo hi")
# THEN ... identifies via marker ...
assert terminal.last_exit_code == 0
Full Mapping Chain:
flowchart LR
Spec["spec.md
### Requirement"] --> S1["#### Scenario A
WHEN ... THEN ..."]
Spec --> S2["#### Scenario B"]
Spec --> S3["#### Scenario C"]
S1 -.translated by tester.-> T1["test_xxx__a()"]
S2 -.translated by tester.-> T2["test_xxx__b()"]
S3 -.translated by tester.-> T3["test_xxx__c()"]
T1 --> Pytest["pytest execution"]
T2 --> Pytest
T3 --> Pytest
Pytest --> Report["test-reports/N.md
**Status:** PASS|FAIL"]
style Spec fill:#fce4ec
style Pytest fill:#c8e6c9
style Report fill:#fff9c4→ One Scenario, one test function—the name should ideally include the Requirement identifier + Scenario name.
⚠️ Formatting Pitfall: 4 Hashes Are Essential
OpenSpec's schema strictly requires:
✅ #### Scenario: ... 4 hashes
❌ ### Scenario: ... 3 hashes will be silently ignored!
❌ - **Scenario:** ... bullet point is not allowed
This is OpenSpec's #1 most common pitfall. After writing, run openspec status --change <name> to check if the spec is recognized.
Delta Operations (4 Types)
The spec file in change is not a complete spec; it's a change:
## Chapter 12: ADDED Requirements
(New)
### Requirement: <new name>
...
## Chapter 12: MODIFIED Requirements
(Modified—must copy the complete original text + modify)
### Requirement: <existing name>
...all content (including original Scenarios)...
## Chapter 12: REMOVED Requirements
### Requirement: <name>
**Reason**: ...
**Migration**: ...
## Chapter 12: RENAMED Requirements
- FROM: Old Name
- TO: New Name
Critical Pitfall for MODIFIED
❌ Only write the diff: "Change X to Y"
✅ Copy all original content + modify = complete new version
When OpenSpec archives, it "replaces the original Requirement with the MODIFIED section," so omitting the original content will lead to information loss.
Example: Partial Spec for doc2video
## Chapter 12: ADDED Requirements
### Requirement: Markdown Protocol Parsing
The tool SHALL parse markdown documents into an ordered list of steps, with the following rules: each H2 heading defines a step; consecutive paragraphs are merged into narration text; ```bash code blocks are sequentially treated as terminal commands; :::manual containers mark the step as a pure narration step.
#### Scenario: Terminal Step Parsing
- **WHEN** the document contains `## Step One: Install Node` followed by a narration paragraph and a ```bash code block
- **THEN** the tool generates a step of type `terminal`, with the paragraph text as narration and the commands within the code block as the command list
#### Scenario: Manual Step Parsing
- **WHEN** a `:::manual` container appears under an H2 heading in the document
- **THEN** the tool generates a step of type `manual`, with the text inside the container as narration and no commands
#### Scenario: Multiple Code Blocks in the Same Step
- **WHEN** multiple ```bash code blocks appear under the same H2 heading
- **THEN** the tool executes all commands within the code blocks sequentially according to document order, and considers the step passed only if all commands succeed
→ One Requirement, three Scenarios, each Scenario is a test case.
Scenario Count Reference
| Requirement Complexity | Number of Scenarios |
|---|---|
| Simple ("System SHALL return 200") | 1~2 |
| Medium | 2~4 |
| Complex (multiple branches, multiple states) | 4~7 |
→ If there are more than 7 Scenarios, consider splitting the Requirement.
Anti-patterns
❌ Scenario describes "internal state" → THEN must be an observable behavior
❌ One Scenario tests multiple behaviors → Split them
❌ Scenario contains implementation details → "Call function X" is not allowed; instead, state "User sees Y"
❌ Scenario has no WHEN → Missing trigger condition
❌ MODIFIED without original content → Information loss during archiving
What You Can Do Now
- Distinguish between Capability / Requirement / Scenario
- Write correctly formatted Scenarios using 4 hashes (
####) - Mentally map each Scenario to a test function
The next chapter translates the specification into an executable "task list."