Skip to content

Gurki language specification

This is the machine-oriented contract for Gurki v0.1. The human introduction lives on gurki.nz. Keywords are closed in keywords.json.

Design rules

  • Gurki source (Markdown / *.spec.md) is canonical.
  • JSON IR is a generated interchange surface.
  • AI analysis lives in a separate sidecar; it never replaces source text.
  • Indentation is cosmetic. Parsers ignore leading whitespace on Gurki lines.
  • Keywords are Title Case and case-sensitive: Given, not given.
  • System: and Scenario: use a colon. Other keywords are Keyword <text>.
  • System Outputs and System Outcomes are two-word headings. They introduce a derived value report, not authored steps.
  • System Net Outputs and System Net Outcomes are the netted rollup of that report. They are derived from an analysis sidecar, never authored.
  • Time in authored steps is elapsed duration (after 22 days, on the second day). Calendar dates belong in prose notes, and only when the document is also a named historical incident. Gurki is a simulation language first.

Envelopes

Spec file (*.spec.md)

  1. Optional YAML frontmatter between --- fences.
  2. Optional Markdown prose (headings, paragraphs, lists).
  3. Optional System: blocks, each grouping one or more Gurki scenarios.
  4. One or more Gurki scenario blocks as plain lines (not required to be fenced).
  5. Optional System Outputs / System Outcomes value report (derived; regenerated on decode).

Prose and Gurki may interleave. A Gurki line is any non-blank line whose first non-whitespace token is a Gurki keyword.

Book / prose Markdown

Ordinary Markdown with one or more fenced blocks:

gherkin
System: Example

Scenario: Example
Given a precondition
When an event occurs
Then something happens
Output a quantity
Outcome a lasting change

System Outputs
a quantity

System Outcomes
a lasting change

Fence language is gherkin (case-insensitive). gurki is accepted as an alias. Nested fences are not supported.

Line grammar

document        = envelope
system_block    = system_line scenario_block+ value_report?
system_line     = "System:" WS+ title
scenario_block  = scenario_line step_or_comment+
scenario_line   = "Scenario:" WS+ title
step_or_comment = primary_step | continuation | comment | blank
primary_step    = PRIMARY WS+ text
continuation    = ("And" | "But") WS+ text
comment         = "#" text?
PRIMARY         = "Given" | "When" | "Then" | "Output" | "Outcome" | "Activates"
value_report    = system_outputs? system_outcomes? net_report?
system_outputs  = "System Outputs" report_item+
system_outcomes = "System Outcomes" report_item+
report_item     = ("And" | "But")? text
net_report      = net_outputs? net_outcomes?
net_outputs     = "System Net Outputs" net_item+
net_outcomes    = "System Net Outcomes" net_item+
net_item        = label ":" WS+ net_value (", churn " quantity)?
net_value       = ("net " quantity) | "net increase" | "net decrease" | "unchanged"

Rules:

  • System: groups the scenarios that follow until the next System: or the value report.
  • System: is optional. Scenarios with no preceding System are ungrouped.
  • And / But inherit the nearest preceding primary step kind in the same scenario.
  • And / But before any primary step is an error.
  • Empty step text is a warning.
  • Unknown first tokens that look like keywords (Title Case word) are errors when they appear where a Gurki line is expected.
  • # starts a comment to end of line. Comments are preserved in the IR.
  • Blank lines are ignored between Gurki lines; they do not end a scenario.
  • A new Scenario: starts a new scenario.
  • A new System: starts a new system and ends the previous scenario.
  • System Outputs / System Outcomes end the current scenario. Their item lines are a derived view: parsers skip them; gurki decode regenerates them as one Gherkin chain, not a dump of every scenario's first step.
  • The first positive item is an unprefixed statement (the primary benefit). Remaining positives are prefixed And. The first authored But is the single But. Further negatives are And. Prefixes are presentation only: they are not part of step text in the IR.
  • System Net Outputs / System Net Outcomes are also derived and also skipped by parsers, but they are only regenerated when gurki decode is given an analysis sidecar via --analysis. Without one they are omitted, because free text alone cannot tell the toolchain that 12 jobs created and 12 jobs cut touch one account. Decoding over a file that has them without passing a sidecar warns before dropping them.
  • Multiline step text is not supported in v0.1.

Intended lifecycle order

Any subset of step kinds is valid. The intended order inside a scenario is:

GivenWhenThenOutputOutcomeActivates

Deviation is a warning, not an error. Continuations do not change order checks; order is evaluated on resolved primary kinds in appearance order.

Frontmatter

Known fields (Zod-validated when present):

FieldRequiredNotes
typeyes (in *.spec.md)Must be "spec"
idyesLowercase slug: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
titleyesHuman title
statusnodraft | review | published | archived
updatednoISO date YYYY-MM-DD
summarynoOne-line description
jurisdictionnoe.g. NZ
sourcesnoArray of strings or { title, url? } objects
tagsnoArray of strings

Unknown frontmatter keys are preserved as extensions. Missing required fields on *.spec.md are errors. Book envelopes without frontmatter are fine.

Document IR (GurkiDocument)

Versioned JSON object (schemaVersion: "0.1"):

  • source — path, envelope (spec | book), optional content digest
  • frontmatter — known fields + extensions
  • segments — ordered prose / system / scenario / comment blocks with source locations
  • systems — title, line, scenarioIndexes into scenarios
  • scenarios — title, line, optional systemIndex, steps
  • Each step: kind (resolved), connector (null | and | but), text, line, optional comment

Encode validates with Zod. Decode emits canonical Markdown (not byte-identical):

  • Frontmatter keys in stable order when present
  • No leading indentation on Gurki lines
  • One blank line between frontmatter and body
  • Scenarios separated by a blank line
  • System: emitted before its scenarios when present
  • System Outputs / System Outcomes regenerated from scenario steps
  • Comments preserved on their own lines

Analysis sidecar (GurkiAnalysis)

Optional AI output. Never written by the deterministic toolchain.

  • schemaVersion: "0.1"
  • sourceDigest — portable digest of document content (excludes absolute paths and line numbers)
  • sourcePath — optional path to the Gurki source
  • model — optional model id / label
  • statements[] — each statement:
    • scenarioIndex / stepIndex — pointers into the document
    • originalText — must match source step text
    • optional subject, relation, object
    • optional measures[] (value, unit, currency?)
    • optional ledger — one signed movement, feeding the net rollup
    • optional categories[], confidence (0–1), notes

ledger

The netting substrate. Categorising a step with a ledger entry is what lets the toolchain add 12 jobs created to 12 jobs cut and get zero.

  • account — normalised key the movement lands on. Same key means same stock.
  • directionincrease or decrease. Direction of the stock, not whether it is good news: a discharged obligation is a decrease.
  • quantity — optional, non-negative. The sign comes from direction. Omit on Outcomes, which net by direction alone.
  • unit, currency — optional. Every quantified entry on one account must agree, or analysis check errors with ledger_unit_conflict.
  • label — optional display wording; defaults to account.

Net is the signed sum, churn the sum of absolute movements. Churn is only rendered when it exceeds the absolute net, so an account that cancelled out reads as jobs: net 0, churn 24 rather than as silence.

gurki analysis check rejects sidecars whose digest does not match, whose originalText / indexes do not resolve, or whose ledger accounts mix units.

See examples/nz-school-lunch/analysis.example.json for the basic shape and examples/nz-ets/analysis.example.json for a worked ledger.

Diagnostics

Lint/parse diagnostics use stable codes with remediation hints (see src/diagnostics.ts).

  • Errors block encode/decode.
  • Warnings are non-blocking (lifecycle order, empty text, unresolved Activates, etc.).

Canonical formatting

gurki decode and the formatter produce:

text
---
type: spec
id: example
title: Example
---

System: Example

Scenario: Example title
Given a precondition
And another precondition
When an event occurs
Then an immediate effect
Output a quantity or transfer
Outcome a lasting change
Activates another scenario title

System Outputs
a quantity or transfer

System Outcomes
a lasting change

And / But are written without indentation.

MIT License