Chapter 12 | Writing Specs — Requirements and Scenarios

9 MIN READ|UPDATED: 2026-06-16
DIRECT SUMMARY // KEY TAKEAWAY

Write testable contracts. Each Scenario should map to a pytest function.

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