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, notgiven. System:andScenario:use a colon. Other keywords areKeyword <text>.System OutputsandSystem Outcomesare two-word headings. They introduce a derived value report, not authored steps.System Net OutputsandSystem Net Outcomesare 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)
- Optional YAML frontmatter between
---fences. - Optional Markdown prose (headings, paragraphs, lists).
- Optional
System:blocks, each grouping one or more Gurki scenarios. - One or more Gurki scenario blocks as plain lines (not required to be fenced).
- Optional
System Outputs/System Outcomesvalue 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:
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 changeFence 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 nextSystem:or the value report.System:is optional. Scenarios with no preceding System are ungrouped.And/Butinherit the nearest preceding primary step kind in the same scenario.And/Butbefore 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 Outcomesend the current scenario. Their item lines are a derived view: parsers skip them;gurki decoderegenerates 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 authoredButis the singleBut. Further negatives areAnd. Prefixes are presentation only: they are not part of step text in the IR. System Net Outputs/System Net Outcomesare also derived and also skipped by parsers, but they are only regenerated whengurki decodeis given an analysis sidecar via--analysis. Without one they are omitted, because free text alone cannot tell the toolchain that12 jobs createdand12 jobs cuttouch 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:
Given → When → Then → Output → Outcome → Activates
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):
| Field | Required | Notes |
|---|---|---|
type | yes (in *.spec.md) | Must be "spec" |
id | yes | Lowercase slug: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$ |
title | yes | Human title |
status | no | draft | review | published | archived |
updated | no | ISO date YYYY-MM-DD |
summary | no | One-line description |
jurisdiction | no | e.g. NZ |
sources | no | Array of strings or { title, url? } objects |
tags | no | Array 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 digestfrontmatter— known fields +extensionssegments— ordered prose / system / scenario / comment blocks with source locationssystems— title, line,scenarioIndexesintoscenariosscenarios— title, line, optionalsystemIndex, steps- Each step:
kind(resolved),connector(null|and|but),text,line, optionalcomment
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 presentSystem Outputs/System Outcomesregenerated 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 sourcemodel— optional model id / labelstatements[]— each statement:scenarioIndex/stepIndex— pointers into the documentoriginalText— 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.direction—increaseordecrease. Direction of the stock, not whether it is good news: a discharged obligation is adecrease.quantity— optional, non-negative. The sign comes fromdirection. Omit on Outcomes, which net by direction alone.unit,currency— optional. Every quantified entry on one account must agree, oranalysis checkerrors withledger_unit_conflict.label— optional display wording; defaults toaccount.
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:
---
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 changeAnd / But are written without indentation.