OpenSpec in one chapter
A ledger says what to do next. It does not say what the software is supposed to do, and an
agent working from a ledger item alone is guessing at that. OpenSpec
(MIT, by Fission AI; this book follows its 1.x layout, current release 1.14.1) is a small
convention for writing it down where the code is: Markdown files in an openspec/ directory,
with just enough structure that a tool — or a SPARQL query — can find the parts.
This chapter is the convention, on a tree this book commits; the next two lift that tree into a graph and keep its work in a ledger. Nothing here needs OpenSpec’s own CLI, which drafts these files with an AI assistant and validates and archives them; what follows is only the format it reads and writes.
Two halves
crates/openspec-ledger/openspec/
├── specs/ what the system does NOW, one folder per capability
│ ├── board/spec.md
│ └── moves/spec.md
└── changes/ what someone PROPOSES to change, one folder per change
└── add-undo/
├── proposal.md why, and what changes
├── specs/moves/spec.md the change as a delta against specs/moves/spec.md
└── tasks.md the checklist that implements it
Specs are the source of truth: they describe how the system behaves today, grouped by capability. Changes are proposals, kept apart until they are done, so several can be in flight without editing the truth underneath each other. The game is tic-tac-toe, as in The ikigai Book, because everybody already knows its rules and the spec can be short.
A spec: requirements and scenarios
# moves Specification
## Purpose
How players take turns, and which moves the game refuses.
## Requirements
### Requirement: Players alternate
The system SHALL give the turn to the other player after every accepted move.
#### Scenario: X then O
- **WHEN** X takes a square
- **THEN** it is O's turn
### Requirement: A taken square is refused
The system MUST refuse a move to a square that is already taken, and MUST NOT change the
turn when it does.
#### Scenario: Moving onto X
- **WHEN** O moves to a square X holds
- **THEN** the move is refused
- **AND** it is still O's turn
A ### Requirement: is one behavior, stated with an RFC 2119 keyword — SHALL or MUST
for an absolute, SHOULD for a recommendation, MAY for an option. Each has at least one
#### Scenario:, a concrete case written as WHEN / THEN (and AND) steps that a test
could check. That is all the structure there is: headings with fixed prefixes, and bullets under
them. A spec says what is observable, never how it is built; class names and libraries belong in
a change’s design, not here.
A change: a proposal, a delta, and tasks
The proposal says why, and which capabilities the change touches:
# Proposal
## Why
A misclick ends a game of tic-tac-toe. Players want to take back the last move, and only the
last one, so that undo cannot be used to replay a lost game from the start.
## What Changes
- A player can undo the most recent move, which empties its square and gives the turn back.
- Undo after the game is over is refused.
## Capabilities
### New Capabilities
### Modified Capabilities
- `moves`: adds undo, and says what alternation means once a move can be taken back.
## Impact
The move history becomes state the game keeps; the board view gains an Undo control.
The delta spec says what will be different, by section — ADDED, MODIFIED and REMOVED Requirements — rather than restating the whole spec, so a reviewer reads only the change:
# Spec Delta
## ADDED Requirements
### Requirement: The last move can be undone
The system SHALL let a player undo the most recent move of a game that is not over: its square
becomes empty and the turn returns to the player who made it.
#### Scenario: Undoing X's move
- **WHEN** X takes the center and then undoes it
- **THEN** the center is empty
- **AND** it is X's turn again
#### Scenario: Undo after a win
- **WHEN** the game is over and a player asks to undo
- **THEN** the undo is refused
## MODIFIED Requirements
### Requirement: Players alternate
The system SHALL give the turn to the other player after every accepted move, and back to the
player whose move was undone after an undo.
#### Scenario: X then O
- **WHEN** X takes a square
- **THEN** it is O's turn
A MODIFIED requirement is written out in full, as it will read afterwards, and is matched to
the current one by its name. And the tasks are an ordinary Markdown checklist, numbered by
group:
# Tasks
## 1. Undo
- [x] 1.1 Keep the game's moves in order, as the history undo reads
- [ ] 1.2 Empty the last move's square and return the turn (The last move can be undone)
- [ ] 1.3 Refuse undo once the game is over (The last move can be undone)
## 2. Turns
- [ ] 2.1 Give the turn back after an undo (Players alternate)
⚠ One convention here is this book’s, not OpenSpec’s. A task that ends in a requirement’s
name in parentheses — (The last move can be undone) — says which requirement it implements.
OpenSpec’s tasks are free text and name nothing; this book needs the link to file each task as a
ledger item about its requirement, so it adds the parenthesis, and a task without one (1.1
here) is about its change alone. A project adopting this would agree the same convention, or put
the link somewhere else and change one construct.
Archiving
When a change is done, OpenSpec archives it: the delta is merged into the current specs (the
added requirement appended to specs/moves/spec.md, the modified one replaced), and the change’s
folder moves to changes/archive/<date>-<id>/, intact, as the record of why the specs say
what they say. So at any moment, changes/ is exactly the work in flight, and changes/archive/
the work done. The ledger in two chapters’ time keys off that move.
Where this goes next
Everything above is text, and a person reads it well. What a person cannot do is ask the tree a question across files: which requirements does this change touch, which of its tasks are still open, which requirement has the most work against it, which items in the ledger are about a requirement that no longer exists. The next chapter turns the tree into a graph that can be asked, without writing a parser for any of it.