Chapter 12 | Writing Specs — Requirements and Scenarios

9 MIN READ | UPDATED: 2026-05-15

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."