Skip to content

Gherkin Patterns Guide

Practical patterns for writing Gherkin specs that work with delivery-process generators.

Tag Reference: Run npx generate-tag-taxonomy -o TAG_TAXONOMY.md -f for the complete tag list. See TAXONOMY.md for concepts.


Roadmap specs define planned work with Problem/Solution descriptions and a Background deliverables table.

@libar-docs-pattern:ProcessGuardLinter
@libar-docs-status:roadmap
@libar-docs-phase:99
Feature: Process Guard Linter
**Problem:**
During planning and implementation sessions, accidental modifications occur:
- Specs outside the intended scope get modified in bulk
- Completed/approved work gets inadvertently changed
**Solution:**
Implement a Decider-based linter that:
1. Derives process state from existing file annotations
2. Validates proposed changes against derived state
3. Enforces file protection levels per PDR-005
Background: Deliverables
Given the following deliverables:
| Deliverable | Status | Location |
| State derivation | Pending | src/lint/process-guard/derive.ts |
| Git diff change detection | Pending | src/lint/process-guard/detect.ts |
| CLI integration | Pending | src/cli/lint-process.ts |

Key elements:

  • @libar-docs-pattern:Name - Unique identifier (required)
  • @libar-docs-status:roadmap - FSM state
  • **Problem:** / **Solution:** - Extracted by generators
  • Background deliverables table - Tracks implementation progress

Use Rule: to group related scenarios under a business constraint.

Rule: Status transitions must follow PDR-005 FSM
@happy-path
Scenario Outline: Valid transitions pass validation
Given a file with status "<from>"
When the status changes to "<to>"
Then validation passes
Examples:
| from | to |
| roadmap | active |
| roadmap | deferred |
| active | completed |
| deferred | roadmap |
@edge-case
Scenario Outline: Invalid transitions fail validation
Given a file with status "<from>"
When the status changes to "<to>"
Then validation fails with "invalid-status-transition"
Examples:
| from | to |
| roadmap | completed |
| deferred | active |
| completed | roadmap |

Rules provide semantic grouping - generators extract them for business rules documentation.

When the same pattern applies with different inputs, use Scenario Outline with an Examples table.

Scenario Outline: Protection levels by status
Given a file with status "<status>"
When checking protection level
Then protection is "<protection>"
And unlock required is "<unlock>"
Examples:
| status | protection | unlock |
| roadmap | none | no |
| active | scope | no |
| completed | hard | yes |
| deferred | none | no |

Test features focus on behavior verification with section dividers for organization.

@behavior @scanner-core
@libar-docs-pattern:ScannerCore
Feature: Scanner Core Integration
The scanPatterns function orchestrates file discovery and AST parsing.
**Problem:**
- Need to scan codebases for documentation directives efficiently
- Files without @libar-docs opt-in should be skipped
**Solution:**
- Two-phase filtering: quick regex check, then file opt-in validation
- Result monad pattern captures errors without failing entire scan
Background:
Given a scanner integration context with temp directory
# ==========================================================================
# Basic Scanning
# ==========================================================================
@happy-path
Scenario: Scan files and extract directives
Given a file "src/auth.ts" with content:
"""
/** @libar-docs */
/** @libar-docs-core */
export function authenticate() {}
"""
When scanning with pattern "src/**/*.ts"
Then the scan should succeed with 1 file
# ==========================================================================
# Error Handling
# ==========================================================================
@error-handling
Scenario: Collect errors for files that fail to parse
Given a file "src/valid.ts" with valid content
And a file "src/invalid.ts" with syntax errors
When scanning with pattern "src/**/*.ts"
Then the scan should succeed with 1 file
And the scan should have 1 error

Section comments (# ===) improve readability in large feature files.


Use for data that applies to all scenarios - deliverables, definitions, etc.

Background: Deliverables
Given the following deliverables:
| Deliverable | Status | Location | Tests |
| Category types | Done | src/types.ts | Yes |
| Validation logic | Pending | src/validate.ts | Yes |

Use for scenario-specific test inputs.

Scenario: Session file defines modification scope
Given a session file with in-scope specs:
| spec | intent |
| mvp-workflow-implementation | modify |
| short-form-tag-migration | review |
When deriving process state
Then "mvp-workflow-implementation" is modifiable

Use """typescript for code blocks. Essential when content contains pipes or special characters.

Scenario: Extract directive from TypeScript
Given a file with content:
"""typescript
/** @libar-docs */
/**
* @libar-docs-core
* Authentication utilities
*/
export function authenticate() {}
"""
When scanning the file
Then directive should have tag "@libar-docs-core"

These tags are recognized by the extractor and appear in generated documentation:

TagPurpose
@acceptance-criteriaRequired for DoD validation of completed patterns
@happy-pathPrimary success scenario
@validationInput validation, constraint checks
@business-ruleBusiness invariant verification
@business-failureExpected business failure scenario
@compensationCompensating action scenario
@idempotencyIdempotency verification
@expirationExpiration/timeout behavior
@workflow-stateWorkflow state transition scenario

These tags are not extracted by generators but are used by convention for organizing feature files:

TagPurpose
@edge-caseBoundary conditions, unusual inputs
@error-handlingError recovery, graceful degradation
@integrationCross-component behavior
@pocProof of concept, experimental

Combine scenario-level tags with feature-level tags for filtering:

@behavior @scanner-core
@libar-docs-pattern:ScannerCore
Feature: Scanner Core Integration

Feature files serve dual purposes: executable specs and documentation source. Content in the Feature description section appears in generated docs.

Prefer code stubs over DocStrings for complex examples. Feature files should reference code, not duplicate it.

ApproachWhen to Use
DocStrings ("""typescript)Brief examples (5-10 lines), current/target state comparison
Code stub referenceComplex APIs, interfaces, full implementations

Instead of large DocStrings:

Rule: Reservations use atomic claim
See `@libar-dev/platform-core/src/reservations/reserve.ts` for API.

Code stubs are annotated TypeScript files with throw new Error("not yet implemented").

For features that define business constraints, use Rule: blocks with structured descriptions:

Rule: Reservations prevent race conditions
**Invariant:** Only one reservation can exist for a given key at a time.
**Rationale:** Check-then-create patterns have TOCTOU vulnerabilities.
**Verified by:** Concurrent reservations, Expired reservation cleanup
@acceptance-criteria @happy-path
Scenario: Concurrent reservations
...
ElementPurposeExtracted By
**Invariant:**Business constraint (what must be true)Business Rules generator
**Rationale:**Business justification (why it exists)Business Rules generator
**Verified by:**Comma-separated scenario namesMultiple codecs (Business Rules, Reference)

Note: Rule blocks are optional. Use them when the feature defines business invariants that benefit from structured documentation.

Choose headers that fit your pattern:

StructureHeadersBest For
Problem/Solution**Problem:**, **Solution:**Pain point → fix
Value-First**Business Value:**, **How It Works:**TDD-style, Gherkin spirit
Context/Approach**Context:**, **Approach:**Technical patterns

The Problem/Solution pattern is the dominant style in this codebase.

Content TypeSyntaxAppears in Docs
Plain textRegular paragraphsYes
Bold/emphasis**bold**, *italic*Yes
TablesMarkdown pipe tablesYes
Lists- item or 1. itemYes
DocStrings"""typescript"""Yes (code block)
Comments# commentNo (ignored)

Prefer DocStrings over code fences for portability:

# Preferred - DocStrings with language hint
Given the following code:
"""typescript
const x = 1;
"""
# Avoid - markdown fences in descriptions may not render consistently

Tag values cannot contain spaces. Use hyphens:

InvalidValid
@unlock-reason:Fix for issue@unlock-reason:Fix-for-issue
@libar-docs-pattern:My Pattern@libar-docs-pattern:MyPattern

For values with spaces, use the quoted-value format where supported:

@libar-docs-usecase "When handling command failures"

lint-steps is a static analyzer that catches vitest-cucumber compatibility issues before tests run. It uses regex-based state machines (not the @cucumber/gherkin parser) to detect patterns that cause cryptic runtime failures. Run it after writing or modifying any .feature or .steps.ts file:

Terminal window
pnpm lint:steps

12 rules across 3 categories (8 error, 4 warning). For the full validation tool suite, see VALIDATION.md.

These rules scan .feature files without needing a Gherkin parser:

Rule IDSeverityWhat It Catches
hash-in-descriptionerror# at line start inside """ block in description — terminates parsing
keyword-in-descriptionerrorDescription line starting with Given/When/Then/And/But — breaks parser
duplicate-and-steperrorMultiple And steps with identical text in same scenario
dollar-in-step-textwarning$ in step text (outside quotes) causes matching issues
hash-in-step-textwarningMid-line # in step text (outside quotes) silently truncates the step

hash-in-description — the most surprising trap:

# BAD — # inside """ block in description terminates parsing
Rule: My Rule
"""bash
# This breaks the parser — Gherkin sees a comment, not code
generate-docs --output docs
"""
# GOOD — move code to a step DocString (safe context)
Scenario: Example usage
Given the following script:
"""bash
# Safe inside a real DocString
generate-docs --output docs
"""

keyword-in-description:

# BAD — starts with "Given", parser interprets as a step
Rule: Authentication
Given a valid session, the system should...
# GOOD — rephrase to avoid reserved keywords at line start
Rule: Authentication
A valid session enables the system to...

These rules scan .steps.ts files:

Rule IDSeverityWhat It Catches
regex-step-patternerrorRegex pattern in step registration — use string patterns
unsupported-phrase-typeerror{phrase} in step string — use {string} instead
repeated-step-patternerrorSame pattern registered twice — second silently overwrites

regex-step-pattern:

// BAD — regex pattern throws StepAbleStepExpressionError
Given(/a user with id (\d+)/, (_ctx, id) => { ... });
// GOOD — string pattern with Cucumber expression
Given('a user with id {int}', (_ctx, id: number) => { ... });

These rules pair .feature and .steps.ts files and cross-check them:

Rule IDSeverityWhat It Catches
scenario-outline-function-paramserrorFunction params in ScenarioOutline callback (should use variables)
missing-and-destructuringerrorFeature has And steps but step file does not destructure And
missing-rule-wrappererrorFeature has Rule: blocks but step file does not destructure Rule
outline-quoted-valueswarningQuoted values in Outline steps instead of <placeholder> syntax

The Two-Pattern Problemscenario-outline-function-params + outline-quoted-values form a pair:

# Feature file — BAD (outline-quoted-values)
Scenario Outline: Validate quantity
When I set quantity to "<quantity>"
# Should be: When I set quantity to <quantity>
Examples:
| quantity |
| 5 |
// Step file — BAD (scenario-outline-function-params)
ScenarioOutline('Validate quantity', ({ When }) => {
When('I set quantity to {string}', (_ctx, qty: string) => {
// qty is undefined at runtime — {string} does NOT work in ScenarioOutline
});
});
// GOOD — use variables object
ScenarioOutline('Validate quantity', ({ When }, variables: { quantity: string }) => {
When('I set quantity to <quantity>', () => {
const qty = variables.quantity;
});
});

missing-and-destructuring:

// BAD — And not destructured, causes StepAbleUnknowStepError
describeFeature(feature, ({ Given, When, Then }) => { ... });
// GOOD — And is available for feature And steps
describeFeature(feature, ({ Given, When, Then, And }) => { ... });
FlagShortDescriptionDefault
--strictTreat warnings as errorsfalse
--format <type>Output: pretty or jsonpretty
--base-dir <dir>-bBase directory for pathscwd

Scan scope (hardcoded defaults):

Feature files: tests/features/**/*.feature
delivery-process/specs/**/*.feature
delivery-process/decisions/**/*.feature
Step files: tests/steps/**/*.steps.ts

Exit codes:

CodeMeaning
0No errors (warnings allowed unless --strict)
1Errors found (or warnings with --strict)

ElementUse ForExample Location
Background DataTableDeliverables, shared reference datadelivery-process/specs/process-guard-linter.feature
Rule:Group scenarios by business constrainttests/features/validation/*.feature
Scenario OutlineSame pattern with variationstests/features/validation/fsm-validator.feature
DocString """Code examples, content with pipestests/features/behavior/scanner-*.feature
Section comments #Organize large feature filesMost test features
lint-stepsCatch vitest-cucumber traps staticallypnpm lint:steps

DocumentPurpose
ANNOTATION-GUIDE.mdAnnotation mechanics and tag reference
TAXONOMY.mdTag taxonomy concepts and API
CONFIGURATION.mdPreset and tag prefix configuration
VALIDATION.mdFull validation tool suite and CI setup