Concepts
This page explains the parts of the model and how they fit together. To try them on your machine, start with the quickstart. Element ids in the examples on this page are invented.
Element
An element is the unit of the model: one requirement, one screen, one data entity, one metric. It is one Markdown file. The file has two parts:
- Machine fields between two
---lines: id, type, parent, status, relationships and acceptance criteria. - A few sentences of text for a human.
Everything lives in the project’s git repository, next to the code. There is no database to run and no export step: the files are the model, and a pull request on a requirement looks like a pull request on code.
The folder tree is the tree of the system
An element that belongs to another one is stored in a folder named after its parent, and names the parent in its parent field. A module MOD-001 and its component COM-001 look like this on disk:
aspects/
└── modules/
├── MOD-001.md
└── MOD-001/
└── COM-001.md
Reading the folders top to bottom gives the decomposition of the system: aspect, then module, then component, then whatever the methodology allows below. Relationships that are not parent and child (a screen that navigates to another, a module that depends on another) are stored in the relationships field of the element.
Statuses
| Status | Meaning |
|---|---|
draft |
Written, not yet reviewed. This is where a new element starts. |
reviewed |
Read and accepted as ready to implement. |
confirmed |
Implemented, and its acceptance criteria passed. |
blocked |
Could not be implemented as written; needs rework. |
deprecated |
No longer relevant. |
In the open core you set the status yourself, or an agent does through MCP. In spec-editor Pro the delivery team moves elements along: it takes reviewed ones to implementation and confirms them after the checks pass.
Acceptance criteria live inside the requirement
The requirement carries its own definition of done. There is no separate test-case element: the criteria are a list in the front matter of the element. A criterion is one of two kinds.
- A deterministic criterion is a command with an expected result. Anyone can run the command and compare.
- A judged criterion is a question for a judge (a person or a model) together with the evidence that must be looked at.
acceptance:
- id: AC-1
kind: deterministic
command: pytest tests/test_export.py -q
expected: exits with code 0
- id: AC-2
kind: judge
judge_prompt: >-
Open the exported CSV for March. Does it list every March order
exactly once?
expected: every order of the month is present, none twice
needs_vision: false
- id: AC-3
kind: judge
judge_prompt: >-
Open the export screen. Is the Download button visible without
scrolling at 1280 by 800 pixels?
expected: the button is visible in the first screen
needs_vision: true
| Field | Meaning |
|---|---|
id |
Id of the criterion inside the element: AC-1, AC-2. |
kind |
deterministic or judge. |
command |
Deterministic criteria: the command to run. |
judge_prompt |
Judged criteria: what the judge must decide. |
expected |
The expected result. The validator treats an empty value as an error. |
needs_vision |
The visual-check mark. When true, the judge must look at a screenshot of the result, not only read text. |
The methodology sets limits on the form: the bundled methodology warns about a requirement with more than six criteria, and about a requirement with none. Short requirements with exact criteria are easier to check than long ones.
Sources
Everything that arrives is a source: a document, a bug report, an incident, an idea. A source is an element of the aspect sources. Elements derived from it name it in their derived_from field, so you can always trace a requirement back to the words that caused it. A closed source is deprecated and leaves the active model.
In the open core, sources can come from Jira, GitHub Issues, Slack, Discord and Telegram channels, and an OpenSLO file can be imported as metrics. The commands live under spec-editor source: add, list, import, sync and listen. Turning a source into elements is analysis work done by a model; in spec-editor Pro independent analysts do it and the result is checked against the source.
Code links with @implements
A marker in the code says which requirement the code implements. It is a comment, a docstring or a decorator that contains the id:
# @implements MOD-001
def export_orders(month):
...
@implements("MOD-001")
def export_orders(month):
...
The marker binds to the definition that follows it, or to the one that contains it. A marker may also name an operation of the element, as in MOD-001.export_orders. The marker grammar is not tied to one language: the text @implements <id> in any comment counts.
spec-editor validate --scope links checks the links between model and code. It scans the src/ folder of the project for markers and compares the result with the snapshot saved by the previous run in .spec-editor/implements_snapshot.json. It reports two kinds of drift:
missing_function: a function that carried a marker last time is no longer in the code.deleted_element: a marker names an element that is no longer in the model.
Markers are read from Python, TypeScript, JavaScript, Rust, Astro, HTML, CSS, Markdown and YAML files. To add a file type, list it under code: file_types: in local.yaml.
By default drift is listed and the exit code stays 0. With --strict any drift makes the command fail, which is what a CI job wants. The first run has no snapshot to compare with and only records one.
Methodology as data
The shape of a model is not in the program; it is in a YAML file, methodology.yaml, in your project. It declares the aspects, the element types of each aspect with their attributes, the relationship types between aspects, the roles, and the rules of the form of a requirement. Change the file and the validator, the interface and the export change with it.
The bundled waterfall methodology has eight aspects:
| Aspect | What it holds | Element types |
|---|---|---|
sources |
Original requirements from documents, chats, bugs and incidents. | source |
modules |
Decomposition of the system. | module, component |
user_scenarios |
What users do. | high_level_scenario, detailed_scenario, step |
user_interface |
What users see. | section, screen, widget, control |
data_entities |
The data the system keeps. | entity, field |
non_functional |
Qualities such as speed and security. | requirement |
implementation |
The trace to code, builds and hosts. | code_artifact, api_endpoint, build_artifact, host |
metrics |
How the running system is measured. | gauge, event, dashboard, alert, kpi |
The bundled methodology is translated into five languages. spec-editor methodology list prints the catalog of bundled methodologies, and spec-editor methodology generate --domain "..." generates one for a domain; the result is validated before anything is written.
The model describes the system as it is
An element says what the system does now. It does not carry a changelog, a “was” and “will be”, or a record of who changed what. That history is in git, and in the journal the tools keep. When the system changes, the requirement is edited in place, the way code is.
spec-editor diff shows what a change did to the elements: --element MOD-001 gives the history of one element, --run <id> the elements touched by one run, --since <time> a time window.
Two axes of the interface
The web interface shows the element selected in the tree on two axes.
- Time: as designed (what the model says) or what happened (what the running system did).
- Medium: text, image or dynamic (a diagram you can drill into).
Design with text is the element card with its acceptance criteria. Design with image is the diagram: flowchart, ER, sequence, class, state and other types, with a trace map of the relationships. Run-time views show the run log, the run-time trace map and the event graph. The interface is in English and has a light and a dark theme.
Run-time operation events
Code that carries an @implements marker can write operation events: what ran, when, and inside which element. The trace level says how much is written: error, warn, info, debug or trace. The level is set per element and per host with set-trace:
spec-editor set-trace MOD-001 HOST-local debug --ttl 600 -p .
The command takes the element, the host and the level. The debug and trace levels require --ttl, a number of seconds after which the level falls back. The run-time trace map then shows which requirements actually ran on that host.