Skip to content

Spec files in your repository

The folder layout, Markdown and graph.json formats SpecsGraph commits to your repository, and how to use spec files in code review and CI checks.

Folder layout

Every approved proposal updates one folder in the project's repository, specsgraph/ unless you chose another path. This is the layout for the Northwind Commerce Shop project, trimmed to the Orders context:

specsgraph/Text
specsgraph/
  README.md                 index of the model, generated
  glossary.md               every term with its definition
  actors.md                 people, systems and agents
  graph.json                machine-readable graph for tools and CI
  contexts/
    orders/
      context.md            purpose, subdomain, services, relationships
      requirements/
        ORD-12.md           one file per requirement, scenarios included
        ORD-13.md
  decisions/
    0007-reserve-stock-before-payment.md
  workstreams/
    WS-3.md
PathHoldsWritten for
README.mdAn index of contexts, requirements and decisions with links to each file.People browsing on GitHub
glossary.mdEvery term with its one definition and the context it belongs to.People and agents
actors.mdThe people, external systems and agents that interact with the system.People and agents
graph.jsonThe whole model as data: contexts, relationships, requirements and their links.Scripts and CI
contexts/<context>/The context file and one file per requirement.People reviewing code
decisions/One file per decision, numbered in order.People
workstreams/One file per workstream, as of the last publish that touched it.People

A context's folder is named after its slug, which is set when the context is created. Renaming the context does not change the slug, so file paths stay the same. A requirement that moves to another context keeps its ID, and its file moves to that context's folder.

File formats

Context and requirement files are Markdown with YAML front matter. The front matter holds the fields a tool needs; the body is written for people and reads well in GitHub's file view, which shows front matter as a table. Decision and workstream files are plain Markdown, as shown in the decision log.

A requirement file

ORD-12 lives at specsgraph/contexts/orders/requirements/ORD-12.md. The scenarios sit in a fenced Gherkin block, so BDD tools and editors highlight them, and links are relative so they work on GitHub and in any clone.

specsgraph/contexts/orders/requirements/ORD-12.mdMarkdown
---
id: ORD-12
title: Reserve stock for every line item at checkout
context: orders
service: checkout
state: active
terms: [reservation, customer, line-item]
actors: [customer]
related: [BIL-04]
decisions: ["0007"]
---

# ORD-12 Reserve stock for every line item at checkout

When a Customer starts checkout, the system creates a reservation for the
full quantity of every line item before payment is requested.

## Scenarios

```gherkin
@ORD-12
Feature: Reserve stock for every line item at checkout

  Background:
    Given the Catalog has 5 units of "Trail mug" in stock
    And the Catalog has 1 unit of "Canvas tote" in stock

  Scenario: Every line item gets a reservation
    Given a Customer has a cart with 2 "Trail mug" and 1 "Canvas tote"
    When the Customer starts checkout
    Then a reservation holds 2 "Trail mug" for the order
    And a reservation holds 1 "Canvas tote" for the order
    And the Catalog shows 3 units of "Trail mug" available

  Scenario: Checkout stops when a line item cannot be reserved
    Given a Customer has a cart with 2 "Canvas tote"
    When the Customer starts checkout
    Then no reservation is created for the order
    And the Customer is told that only 1 "Canvas tote" is left
    And payment is not requested
```

## Links

- Term: [Reservation](../../../glossary.md#reservation)
- Actor: [Customer](../../../actors.md#customer)
- Requirement: [BIL-04](../../billing/requirements/BIL-04.md) Capture payment only once stock is reserved
- Decision: [0007 Reserve stock before payment](../../../decisions/0007-reserve-stock-before-payment.md)

SpecsGraph writes a requirement file as the requirement will be once its proposal is published, so state is active or, once the requirement is removed from the spec, retired. The Proposed state never appears in a file. A retired requirement keeps its file, so an ID quoted in an old commit still leads somewhere.

graph.json

graph.json holds the same model as data. SpecsGraph writes it with a stable order, so a change to one requirement shows up as a small diff. An excerpt:

specsgraph/graph.json (excerpt)JSON
{
  "schemaVersion": 1,
  "project": { "key": "shop", "name": "Shop" },
  "contexts": [
    { "key": "ORD", "slug": "orders", "name": "Orders", "subdomain": "core", "owner": "Checkout team" },
    { "key": "BIL", "slug": "billing", "name": "Billing", "subdomain": "supporting", "owner": "Billing team" }
  ],
  "relationships": [
    { "upstream": "CAT", "downstream": "ORD", "patterns": ["customer-supplier"] },
    { "upstream": "ORD", "downstream": "BIL", "patterns": ["partnership"] }
  ],
  "requirements": [
    {
      "id": "ORD-12",
      "title": "Reserve stock for every line item at checkout",
      "context": "ORD",
      "state": "active",
      "file": "contexts/orders/requirements/ORD-12.md",
      "scenarios": 2,
      "related": ["BIL-04"],
      "decisions": ["0007"]
    }
  ]
}

Front matter reference

Scripts can rely on these keys. Values that point at other files use the same slugs and IDs as the folder layout.

Requirement files

KeyHolds
idThe requirement ID, such as ORD-12.
titleThe requirement title.
contextSlug of the context the requirement belongs to.
serviceSlug of its service. Left out when the requirement has none.
stateactive or retired.
termsGlossary terms it links to, by their anchor in glossary.md.
actorsActors it links to, by their anchor in actors.md.
relatedIDs of the requirements it is linked to.
decisionsNumbers of the decisions it links to.

Context files

Each context folder holds a context.md with the context's fields and its relationships. The body carries the purpose.

specsgraph/contexts/orders/context.mdMarkdown
---
key: ORD
slug: orders
name: Orders
subdomain: core
owner: Checkout team
services: [checkout, order-history]
upstream:
  - { context: CAT, patterns: [customer-supplier] }
  - { context: IDN, patterns: [open-host-service] }
downstream:
  - { context: BIL, patterns: [partnership] }
  - { context: FUL, patterns: [published-language] }
  - { context: ANL, patterns: [published-language] }
---

# Orders

Turns a cart into a confirmed order and holds stock while the Customer pays.
Does not ship anything.

graph.json reference

KeyTypeHolds
schemaVersionnumberThe format version, currently 1. Check it in scripts, and read the release notes when it changes.
projectobjectThe project's key and name.
contextsarrayEach context's key, slug, name, subdomain and owner.
relationshipsarrayEach relationship's upstream and downstream context keys and its patterns.
requirementsarrayEach requirement's id, title, context key, state, file, number of scenarios, related IDs and decisions.

Fields with a fixed set of values:

FieldAllowed values
subdomaincore, supporting or generic
stateactive or retired
patternspartnership, shared-kernel, customer-supplier, conformist, anticorruption-layer, open-host-service, published-language, separate-ways

Glossary terms, actors, decisions and workstreams are published as Markdown only, in the files listed above. Scenario text lives in the requirement files; Run scenarios with a BDD tool shows how to extract it.

Treat the folder as generated

SpecsGraph writes these files from the graph on every publish. The graph is where the spec is edited; the folder is its output. If someone edits specsgraph/contexts/orders/requirements/ORD-12.md by hand, the graph does not learn about it, and the next publish that touches ORD-12 stops instead of overwriting the edit, as described in Conflicts and rebasing.

SpecsGraph notices commits to the spec folder that it did not make and shows a warning on the project with the files involved. It does not import them. To keep the change, make it in SpecsGraph as a proposal, so it gets a review, a place in history and links to the rest of the model.

Tip

Moving the folder

Change the spec folder in project settings rather than moving files in Git. The next publish writes to the new path; remove the old folder in an ordinary pull request. Workspaces and projects covers project settings.

Use the files in code review

  • Put the rule next to the change. A pull request that changes checkout can link specsgraph/contexts/orders/requirements/ORD-12.md. The reviewer reads the scenarios and checks the code against them without leaving GitHub.
  • Review spec changes as diffs. A pull request from spec/checkout-reserve shows exactly which scenario lines changed. Branches and pull requests describes those pull requests.
  • Cite IDs. Commit messages and pull requests that name ORD-12 make git log --grep ORD-12 a list of every code change made for that rule.
  • Run the scenarios. Gherkin tags such as @ORD-12 let a BDD runner execute the scenarios of one requirement; see Run scenarios with a BDD tool.

Use the files in CI

Because graph.json is plain data, a few lines of shell can hold pull requests to the spec. This GitHub Actions workflow fails a pull request whose title and description cite no requirement ID, or cite one that does not exist. Spec pull requests are skipped, and a no-spec label opts out a change such as a dependency bump.

.github/workflows/requirement-ids.ymlYAML
name: Requirement IDs

on:
  pull_request:
    types: [opened, edited, synchronize, labeled, unlabeled]

jobs:
  cite-requirements:
    if: ${{ !startsWith(github.head_ref, 'spec/') && !contains(github.event.pull_request.labels.*.name, 'no-spec') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Check cited requirement IDs
        env:
          PR_TEXT: ${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}
        run: |
          keys=$(jq -r '.contexts[].key' specsgraph/graph.json | paste -sd '|' -)
          ids=$(printf '%s' "$PR_TEXT" | grep -oE "\b($keys)-[0-9]+\b" | sort -u)
          if [ -z "$ids" ]; then
            echo "Cite at least one requirement ID, for example ORD-12."
            exit 1
          fi
          for id in $ids; do
            jq -e --arg id "$id" '.requirements[] | select(.id == $id)' specsgraph/graph.json > /dev/null \
              || { echo "Unknown requirement ID: $id"; exit 1; }
          done

The script builds its pattern from the context keys in graph.json, so ORD-12 counts as a citation and an unrelated token such as UTF-8 does not. The pull request text reaches the script through an environment variable instead of being pasted into it, which keeps a crafted title from running as shell code.

A second guard keeps hand edits out of the spec folder. It fails any pull request that touches specsgraph/ from a branch outside spec/:

.github/workflows/spec-folder.ymlYAML
name: Spec folder

on:
  pull_request:
    paths:
      - "specsgraph/**"

jobs:
  generated-only:
    if: ${{ !startsWith(github.head_ref, 'spec/') }}
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Files in specsgraph/ are generated by SpecsGraph. Propose the change there instead."
          exit 1

Run scenarios with a BDD tool

Scenarios are published inside the requirement files, in a fenced Gherkin block that already carries the Feature line and the ID tag. A few lines of shell turn them into .feature files that Cucumber and similar runners read, skipping retired requirements:

Extract and run scenariosShell
mkdir -p features
for f in specsgraph/contexts/*/requirements/*.md; do
  grep -q '^state: retired' "$f" && continue
  awk '/^```gherkin$/ { on = 1; next } /^```$/ { on = 0 } on' "$f" \
    > "features/$(basename "$f" .md).feature"
done

# Run the scenarios of one requirement, next to your step definitions
npx cucumber-js features --tags "@ORD-12"

Note

GitHub's documentation is the authority on workflow syntax and action versions, and your BDD runner's documentation on its flags. Adjust the examples to your own conventions.

Next steps