SpecDD Language Specification

This document defines the .sdd file format used by SpecDD specifications.

It describes syntax and format only. It does not define the SpecDD workflow, agent behavior, inheritance algorithm, editor behavior, or project adoption guidance.

The terms MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are used in their usual normative sense.

Formal Language Specification

This part defines the normative .sdd language: valid files, line structure, sections, body syntax, inline syntax, and semantic text extraction.

File Type

A SpecDD file:

Whitespace And Indentation

A blank line is an empty line or a line containing only whitespace.

Indentation is leading whitespace before the first non-whitespace character.

Indentation rules:

Continuation lines are continuation text for the preceding body entry in the same section. A continuation line without a preceding body entry in the current section is invalid.

Formatters SHOULD emit body entry indentation as 2 spaces and continuation indentation as 4 spaces unless preserving author formatting.

Comments

A comment line is any line whose first non-whitespace character is #.

# root comment
  # section comment
      # deeply indented comment

Comments:

Inline trailing comments are not recognized. Text after other syntax is ordinary line content.

Must:
  Validate input. # This is body text, not a comment.

Line Model

A .sdd file is a sequence of lines. Each line is one of:

Line classification tracks the current section established by the most recent recognized section header.

For each nonblank line, classification uses this precedence:

  1. Comment line
  2. Known section header
  3. Continuation line
  4. Task line
  5. Scenario step line
  6. Key-value line
  7. Text line

Comments therefore take precedence over section syntax, task markers, scenario steps, key-value syntax, paths, symbols, and ordinary text.

Task-line classification applies only inside the Tasks section.

A body entry in the Tasks section that begins with [ at the first non-whitespace position is classified as a task-like line before it may be classified as text. If the bracketed marker is not one of the supported task markers, it is an invalid task state.

Sections

A section begins with a known section header.

A section header has this shape:

KnownSectionLabel ":" [ 1*SPACE inline-value ]

Rules:

Valid:

Spec: Invoice Service
Scenario: invalid invoice amount
Purpose:

Invalid:

Spec : Invoice Service
Spec:Invoice Service
Purpose: inline text is not allowed here

Canonical section labels are:

Spec
Platform
Purpose
Structure
Owns
Can modify
Can read
References
Must
Must not
Forbids
Depends on
Exposes
Accepts
Returns
Raises
Handles
Tasks
Done when
Scenario
Example

Inline Values

Only these sections may have inline values:

Inline value rules:

Examples:

Spec: Invoice Service
Platform: TypeScript/Node
Scenario: invalid invoice amount
Example: unsupported currency
Example:

Bodyless Sections

The following sections are bodyless:

Bodyless sections:

Valid:

Spec: Invoice Service
Platform: TypeScript/Node

Invalid:

Platform:
  TypeScript

Repeatability

Non-repeatable sections:

Repeatable sections:

Rules:

Section Order

The first section in a complete .sdd file MUST be Spec.

Only blank lines and comments may appear before the first section. Nonblank non-comment text outside a section is invalid.

The recommended section order is:

Spec
Platform
Purpose
Structure
Owns
Can modify
Can read
References
Must
Must not
Forbids
Depends on
Exposes
Accepts
Returns
Raises
Handles
Tasks
Scenario
Example
Done when

Tools SHOULD preserve source order unless explicitly formatting a file.

Body Lines

A body entry line is a non-comment, non-section line under the current section with exactly 2 spaces of indentation.

A continuation line is a non-comment, non-section line under the current section with 4 or more spaces of indentation, in multiples of 2.

Continuation indentation is formatting only. When a body entry and its continuation lines are extracted as semantic text, parsers MUST normalize them into a single text value:

Example:

Must:
  Validate invoice input before provider calls
    and before persistence.

Extracted Must text:

Validate invoice input before provider calls and before persistence.

Accepted body entry kinds depend on the current section.

Bodyless sections:

Task-only sections:

Default body-capable sections:

Default body-capable sections accept:

Except where a section explicitly narrows its body rules, body-capable sections are mixed-entry sections. A mixed-entry section may contain prose text, explicit paths, globs, symbols, references, scenario step lines, and key-value lines in the same section.

Blank lines and comments are accepted anywhere.

Inline Code Spans And Symbol References

Inline code spans are inline text spans delimited by single backticks.

Only balanced single-line backtick pairs form inline code spans. Inline code spans do not cross LF, CRLF, or CR line endings.

Must:
  Use `FetchClient` and `./src/main.kt`.

Inline code spans are code text. They do not change the section structure, body line kind, or validity of the surrounding line.

Symbol references use @.

A symbol reference:

If the captured symbol text ends with ., and the next source character after the captured symbol text is whitespace, end of line, or closing punctuation, the final . is sentence punctuation and is excluded from the resolved symbol text.

Opening punctuation before @ includes:

(
[
{
<
"
'

Examples:

Depends on:
  @InvoiceRepository
  @Billing.Provider

Must:
  Call @InvoiceService.createInvoice before returning.
  See (@InvoiceService.createInvoice).
  Use `@dataclass` as a symbol reference inside code text.
  Treat `InvoiceService.createInvoice` as code text, not a symbol reference.
  Use \@decorator as literal text.

In the first Must line above, the symbol reference is @InvoiceService.createInvoice; the final sentence period is not part of the reference.

Because symbol references use an explicit @ prefix, plain text such as InvoiceService.createInvoice is not a symbol reference.

\@ escapes a literal @ and MUST NOT be recognized as a symbol reference.

Tools MUST NOT recognize @ inside a larger non-whitespace token as a symbol reference unless the immediately preceding character is allowed opening punctuation. This prevents ordinary text such as email addresses from being treated as symbol references while still allowing forms like (@Symbol). Embedded @ text is ignored rather than reported as invalid syntax.

Inline code spans, escaped @ sequences, and symbol references may appear inside text body entries, task text, scenario step text, key-value values, and continuation lines.

Fenced code blocks are not supported as special .sdd syntax. Fence markers are ordinary body text when they appear in a body-capable section.

Path Syntax

Path-like values may appear in any body-capable section.

Explicit path prefixes:

Prefix meaning:

Unsupported path prefix:

Unprefixed prose, filenames, dependency names, class names, service names, symbols, and ordinary text are allowed, but they are not explicit path references.

Example:

Structure:
  ./src/main: implementation sources
  Generated files are not committed.
Owns:
  /README.md
  ./fixtures/*.sdd
Depends on:
  FetchClient

In the example above, FetchClient is dependency text, not a path reference.

Glob Syntax

Glob path candidates are path candidates containing one or more glob metacharacters.

Glob metacharacters:

*
?
[
]
{
}

Supported glob constructs:

*       zero or more characters within one path segment
?       exactly one character within one path segment
[abc]   character class
{a,b}   alternatives
**      zero or more characters across directory boundaries
**/     zero or more directories

Rules:

Key-Value Lines

A key-value line has the form:

key ": " value

Recognition rules:

Examples:

Structure:
  ./src: implementation sources
  glob path: generated/*.json
  Output/Delta Path!: generated/delta.json
  key:

In the last example, key: is text rather than a key-value line because there is no space after :.

Invalid key-value forms are classified as text and may then be rejected by the current section’s body rules.

Mixed-Entry Sections

Most body-capable sections are mixed-entry sections. They may contain any combination of ordinary prose, explicit paths, globs, symbols, references, scenario step lines, and key-value lines.

Common mixed-entry body forms:

text
path-or-glob
path-or-glob: text
symbol-or-reference
symbol-or-reference: text

Section names give the entries their semantic role. They do not make the body path-only, reference-only, or symbol-only unless that section explicitly says so.

Identity Sections

Spec

Spec names the subject being specified.

Canonical form:

Spec: Invoice Service

Spec is required for a complete .sdd file. It MUST be the first section, MUST have a nonempty inline value, and MUST NOT have body lines.

Platform

Platform describes the implementation language, runtime, platform, framework, or environment when useful.

Canonical form:

Platform: TypeScript/Node

Platform is optional. When present, it MUST have a nonempty inline value and MUST NOT have body lines.

Purpose

Purpose states why the specified unit exists.

Canonical form:

Purpose:
  Coordinate invoice creation.

Purpose is a body-capable block section and MUST NOT have an inline value.

Scope Sections

Structure

Structure describes files and directories in the current or descendant scope.

Common body entry forms:

path-or-glob
path-or-glob: description
text

Example:

Structure:
  ./src: Source code
  ./tests: Test suite
  ./docs
  Generated files are not committed.

Owns

Owns lists files, directories, symbols, concepts, or responsibilities owned by the spec. It is a mixed-entry section.

Example:

Owns:
  invoice.ts
  invoice.test.ts
  InvoiceService
  Billing behavior for invoice creation.

Can modify

Can modify lists files or paths that may be changed when working under the spec. It is a mixed-entry section.

Example:

Can modify:
  ./invoice.ts
  ./invoice.test.ts
  ./fixtures/*
  Generated fixtures for invoice service tests.

Can read

Can read lists files, paths, specs, or prose context that may be read for context. It is a mixed-entry section.

Example:

Can read:
  ../models/*
  ../ports/*
  Review repository contracts before editing.

References

References lists explicit horizontal references to other specs, contracts, or context. It is a mixed-entry section.

Example:

References:
  ../models/invoice.sdd
  ../ports/billing-provider.sdd
  @InvoiceRepository

Reference paths SHOULD use explicit path prefixes. See “Path Syntax” and “Glob Syntax”.

Requirement Sections

Requirement sections are mixed-entry sections. Their entries are normally prose requirements, but explicit paths, symbols, references, scenario step lines, and key-value lines are valid unless a project rule narrows them.

Must

Must lists positive requirements.

Example:

Must:
  Validate input before provider calls.
  Persist invoice after provider success.

Must not

Must not lists forbidden behavior, non-goals, and boundaries.

Example:

Must not:
  Call Stripe directly.
  Send emails.

Forbids

Forbids lists forbidden dependencies, paths, modules, libraries, or architectural access. It is a mixed-entry section.

Example:

Forbids:
  stripe
  ../../api/*
  ../../ui/*
  Direct provider SDK access from domain models.

Contract Sections

Contract sections describe interfaces, dependencies, results, errors, and handled cases. Each contract section is a mixed-entry section and accepts normal body entry kinds.

Values in these sections are free-form text. Tools MAY add project-specific interpretation.

Depends on

Depends on lists dependencies, collaborators, contracts, symbols, paths, or required context.

Depends on:
  InvoiceRepository
  BillingProviderPort
  @BillingLogger

Exposes

Exposes lists public entry points, exported symbols, APIs, contracts, or observable capabilities.

Exposes:
  InvoiceService.createInvoice(input)
  @InvoiceCreationResult

Accepts

Accepts lists accepted inputs, input types, request shapes, parameters, or preconditions.

Accepts:
  CreateInvoiceInput

Returns

Returns lists return values, output types, response shapes, or result states.

Returns:
  InvoiceCreationResult

Raises

Raises lists errors, exceptions, rejected states, or failure conditions.

Raises:
  InvalidInvoiceInputError
  BillingProviderError

Handles

Handles lists cases, events, states, branches, or conditions handled by the spec.

Handles:
  provider timeout
  unsupported currency

Tasks

Tasks is a local implementation checklist.

The Tasks section accepts task lines only. Non-task body entry lines in Tasks are invalid.

A task line starts with a supported task marker after exactly 2 spaces of indentation.

Task lines are valid only inside the Tasks section. In other sections, text that looks like a task marker is ordinary text unless another rule rejects it.

Supported task markers:

[ ] open
[x] done
[X] done
[-] skipped
[!] blocked
[?] needs decision

Examples:

Tasks:
  [ ] Write the parser.
  [x] #12 Add tests.
  [X] #13 Update docs.
  [?] #7 Decide fixture policy.

Unsupported bracketed task states inside Tasks are invalid task states.

Task id rules:

Task text is free-form text after the marker and optional task id.

Continuation lines may follow task lines. When a continuation follows a task line, it is part of that task’s text and uses the normal continuation text normalization rules.

Scenarios

Scenario defines a behavioral example in a Gherkin-like form.

Canonical header form:

Scenario: scenario name

Scenario MUST have a nonempty inline title.

Scenario step lines start after exactly 2 spaces of indentation with one of:

Given
When
Then
And
But

The keyword MUST be followed by end of line or whitespace.

Example:

Scenario: invalid invoice amount
  Given invoice amount is zero
  When createInvoice is called
  Then validation fails
  And provider is not called

Words that merely start with a scenario keyword are not scenario steps:

Scenario: plain text line
  Andromeda is plain text.

The language does not enforce that scenario bodies contain Given, When, or Then, and it does not enforce step ordering.

Examples

Example provides concrete examples, payloads, usage snippets, or expected transformations.

Both forms are valid:

Example:
  input currency: EUR
  input amount minor units: 1250
  result invoice status: created
Example: unsupported currency
  input currency: BTC
  result error: unsupported currency

Example body entries are normal mixed-entry body entries.

Multiple Example sections MAY appear in one file.

Done When

Done when lists completion criteria. It is a mixed-entry section and MUST NOT have an inline value.

Example:

Done when:
  All scenarios have tests.
  No forbidden dependencies are imported.
  Public contract is preserved.

Basename Matching

Same-directory basename matching is part of SpecDD resolution, but its syntax is only a file naming convention.

When a source file and a spec file share the same basename in the same directory, the .sdd file is the local spec for that source file.

Examples:

invoice.ts       -> invoice.sdd
main.test.ts     -> main.test.sdd
Dockerfile       -> Dockerfile.sdd
bootstrap.md     -> bootstrap.sdd

This rule does not create additional syntax inside the .sdd file.

Minimal Complete File

A minimal complete .sdd file contains a Spec section.

Spec: Math Service

A useful minimal spec usually also contains Purpose:

Spec: Math Service

Purpose:
  Add and subtract two numbers.

Other sections are optional and should be included only when they add useful local information.

Implementation Guidelines

This part is for parsers, validators, IDEs, editors, highlighters, and related tools. It is implementation guidance unless it explicitly restates a normative rule from the formal language specification.

Informal Grammar For Implementers

This grammar is informative rather than a complete parser contract.

When tokenizing text, implementations should recognize inline-code-span, escaped-at, and symbol-reference before falling back to character. Implementations that extract references MAY tokenize code-text recursively for paths and symbol references.

document             = { blank-line | comment } { section } ;

newline              = "\n" | "\r\n" | "\r" ;
blank-line           = { whitespace } newline ;
whitespace           = " " | "\t" ;
comment              = { whitespace } "#" [ text ] newline ;

section              = section-header { blank-line | comment | body-entry | continuation } ;
section-header       = section-name ":" [ inline-tail ] newline ;
inline-tail          = inline-separator text ;
inline-separator     = " " { " " } ;

section-name         = "Spec"
                     | "Platform"
                     | "Purpose"
                     | "Structure"
                     | "Owns"
                     | "Can modify"
                     | "Can read"
                     | "References"
                     | "Must"
                     | "Must not"
                     | "Forbids"
                     | "Depends on"
                     | "Exposes"
                     | "Accepts"
                     | "Returns"
                     | "Raises"
                     | "Handles"
                     | "Tasks"
                     | "Done when"
                     | "Scenario"
                     | "Example" ;

body-entry           = body-indent body-content newline ;
body-indent          = "  " ;

continuation         = continuation-indent text newline ;
continuation-indent  = body-indent body-indent { body-indent } ;

body-content         = tasks-body-content
                     | default-body-content ;
tasks-body-content   = task-content ;
default-body-content = scenario-step-content
                     | key-value-content
                     | text ;

; tasks-body-content is valid only in the Tasks section.
; default-body-content is valid only outside the Tasks section.

text                 = { inline-code-span | escaped-at | symbol-reference | character } ;
task-text            = text ;
value                = text ;

inline-code-span     = "`" code-text "`" ;
escaped-at           = backslash "@" ;
backslash            = U+005C ;
symbol-reference     = symbol-boundary "@" symbol-start { symbol-char } ;
symbol-boundary      = line-start | whitespace | opening-punctuation ;
opening-punctuation  = "(" | "[" | "{" | "<" | "\"" | "'" ;
closing-punctuation  = ")" | "]" | "}" | ">" | "\"" | "'" ;
symbol-start         = ascii-letter | "_" ;
symbol-char          = ascii-letter
                     | digit
                     | "_"
                     | "."
                     | ":"
                     | "#"
                     | backslash
                     | "/"
                     | "?"
                     | "!" ;

; If captured symbol text ends with "." and the next source character after the
; captured symbol text is whitespace, line-end, or closing-punctuation, the
; final "." is sentence punctuation and is not part of the resolved symbol text.

task-content         = task-marker [ " " task-id ] " " task-text ;
task-marker          = "[ ]" | "[x]" | "[X]" | "[-]" | "[!]" | "[?]" ;
task-id              = "#" digit { digit } ;

scenario-step-content = scenario-keyword [ whitespace text ] ;
scenario-keyword     = "Given" | "When" | "Then" | "And" | "But" ;

key-value-content    = key ": " value ;

Content Root Selection

A content root is the highest relevant directory that a tool treats as the boundary of one SpecDD-aware project.

The content root is not .sdd syntax. It is implementation context used for features that need a project boundary, including:

In implementation guidance, “project root” means the selected content root for the .sdd file being processed.

Content root selection SHOULD prefer explicit configuration when available. If no explicit configuration exists, tools SHOULD choose the highest project or workspace root that reasonably contains the relevant SpecDD files.

Common content root choices:

Module roots, package roots, source roots such as src/main/java, test roots, generated-output directories, and individual feature directories SHOULD NOT be chosen as content roots unless they are explicitly configured as independent SpecDD projects.

Multiple independent content roots MAY exist in one editor workspace. When candidate roots are nested, tools SHOULD prefer explicit configuration; without explicit configuration, they SHOULD choose the outer root when SpecDD references are expected to cross the inner boundary, and the inner root only when it is opened or configured as an independent project.

Tools SHOULD make the selected content root visible or inspectable and SHOULD allow projects to override it.

Reference Extraction And Resolution Guidance

Tools that extract references SHOULD treat only explicit syntax as references.

Path-bearing sections:

Path-bearing section guidance:

Inline paths in text are recognized only when they use ./, ../, or /. URLs are not file paths.

Reference extraction inside inline code spans:

Path resolution guidance:

Validation Guidance

Strict validators SHOULD report:

Strict validators MAY report:

Formatting Guidance

Formatters SHOULD preserve all semantic content and comments, including inline values, body entries, continuation text, task text and ids, scenario steps, key-value text, paths, globs, symbol references, and inline code spans. They MAY normalize blank lines, section spacing, and indentation.

Language Changelog

1.0 - 2026-05-19