Keep your agents grounded — docs, values, and code as one knowledge graph, checked on every commit.
|
1 · Declare Write a spec in docs/functional-spec/
# FS-login: Login
## format: Username format
### format.1: Surrounding whitespace
Trim surrounding whitespace. |
2 · Cite Cite it from the code in src/login.py
def clean_username(username):
# §FS-login.format.1
return username.strip()
|
|
3 · Read Look it up before you change the code $ grund FS-login.format.1
### format.1: Surrounding whitespace
Trim surrounding whitespace. |
4 · Check Misspell format, and CI fails
$ grund check
… not found: FS-login.fromat.1; …
|
grund init lays a project out in layers, each citing the one that explains it, under
rules it writes commented out in grund.toml until you adopt them:
| Kind | Home | Cites | |
|---|---|---|---|
| Why | GRUND | docs/grund.md |
|
| Where | GOAL | docs/goals.md |
should cite GRUND or GOAL |
| What | FS | docs/functional-spec/ |
should cite GOAL or FS; never AR |
| REQ | docs/requirements/ |
should cite GRUND or GOAL; never AR | |
| How | AR | docs/architecture/ |
should cite FS or GOAL |
| Proof | e2e | tests/e2e/ |
must cite FS; avoid AR |
| integration | tests/integration/ |
should cite AR | |
| Code | code | any other file | should cite FS or AR |
| Decisions | DF | docs/decisions/functional/ |
should cite FS or GOAL |
| DA | docs/decisions/architectural/ |
should cite AR or FS | |
| Plan | RM | docs/roadmap.md |
Every rule grund check holds a project to, what each one means underneath, and the
finding it reports is listed in
Schema and links.
grund schema prints the ones a repository is held to on one screen, read from its
grund.toml alone; examples/schema/ shows its table and Mermaid output.
init also writes the structure into your agent's instructions, a managed block in
AGENTS.md that grund check keeps current. The number in its heading is the
block's revision, not a grund release
(the setup guide
says how the two differ). For example, grund's own
AGENTS.md tells an agent how to read a citation:
A
§<ID>is a pointer to a fact, not a file path. Resolve it withgrundand climb only as far as needed:
grund <ID>— the lead, cut at the first child section: heading-less for a whole declaration, whilegrund <ID>.<section>opens with that section's heading line. The cheap first read for a bare§<ID>citation.grund <ID> --toc— the lead plus the nested section map. Use to choose which subsection to fetch next.grund <ID> --full— the entire body. Escalate to this when narrower reads aren't enough.
Where each kind lives in this repository:
How it writes:
- Cite as you write. Place
§<ID>at the point a claim or behavior is made — on the doc-comment for a whole behavior, inline beside the clause it enforces.- …
- Inline citation style. Inline notes: ≤ 1 line preferred, hard cap 3 lines; ≤ 100 columns. A note is one comment block: a blank line splits it, an empty comment line does not. Doc-comments (
///,//!,/** */, a docstring, a Go, Ruby, shell, SQL or Lisp comment right above a definition) are documentation, not notes: they are never measured, so cite in-sentence there.- Always cite the most-specific point.
And the table's rules, as it reads them:
must/neveraregrund checkerrors;should/avoidare suggestions (grund check --suggestions). …
- Each FS declaration should cite GOAL or FS; never cite AR.
- Each REQ declaration should cite GRUND or GOAL; never cite AR.
- …
- Each file in tests/e2e/ must cite FS; avoid citing AR.
For an agent without the managed block, the loop fits in one rule for its system prompt:
When you see
§<ID>or§<ID>.<section>in any file you are reading, rungrund <ID>[.<section>]and treat the output as the authoritative definition. Do not paraphrase or guess — quote whatgrundreturned, or cite the ID and move on.
cargo install grundRun the following inside your repository if you are starting with new specs:
grund init --scaffold # config, agent instructions and starter files
grund checkAlready have specs? Hand the setup to your agent:
grund guide setup # the adoption guide, for your agentYour agent inspects the repository, recommends settings for the specs you have,
confirms each one with you, then writes grund.toml, runs grund init and validates.
The command prints the core of the guide; the agent fetches each further section
it needs with --section.
To set up by hand instead, see setup.
The runnable existing-structure adoption example
starts with grund init --template=none, asks for the first kind's offline help,
then declares the repository's own kinds.
Then:
- add
grund checkto pre-commit and CI; - look up any key in the
grund.tomlreference, and point your editor at its JSON Schema; grund.tomlhas two versions: version 2 (grund_config_version = 2) is whatgrund initwrites, holding only what differs from a default, andgrund config showprints every effective setting; a file without a version is version 1 and keeps loading unchanged. Version 1 reads the extensions[scan]lists, and version 2 names the languages it reads, maps suffixes to them, and lets only doc comments declare — see sources and its runnable example;- for prebuilt binaries and the Python and Node APIs, see installation.
A heading # <ID>: <title> in the kind's home declares an ID. The headings under it
are its sections, each at the depth its path names: numbered, or named once
[id] named_sections = true is set. This README's own requirement mixes both:
# REQ-readme: the README is the grounded shop window
…
## 1. What it must say
…
### 1.1 The loop
…
## evidence: Positioning and performance claims have evidenceThey are cited as §REQ-readme.1.1 and §REQ-readme.evidence.
A section heading may write only the component it adds: under ## format: Formatting,
### .1: Username declares FS-login.format.1, and every citation, refs line and
--toc shows that full path, as does the first line of a read of the section itself.
Moving the block under another parent changes its address, so after citing a relative
section run grund fmt --write with no path at the workspace root, or at the project
root outside a workspace, and commit the section lock it writes,
.grund/sections/FS-login.lock; check then says where a cited text went. See
section headings and
examples/relative-sections/. For declaration renames,
follow the maintenance workflow
and try the copy-based exercise in
examples/relative-sections-lock/. fmt's preview
names every kind of rewrite a line gets, its --write summary totals them, and a lock
file the rename deletes or creates says so; read both in
examples/fmt-reporting/.
A declaration can also live in code: a doc-comment that opens with the ID declares it,
and a link from the kind's index enrolls it with no stub file. Grund's own AR-checker is declared only on the
function it describes, in crates/grund-core/src/checker/report.rs:
/// AR-checker: how grund validates the scanner's findings
///
/// …
/// ## placement: Where the checker sits
/// …
pub(crate) fn check_on_disk(grund AR-checker prints that prose without the /// markers.
┌─────────────────── citation ───────────────────┐
┌───────────── ID ───────────────┐
[§] [alias /] KIND - [number -] slug [.section]
│ │ │ │ │ │
│ │ │ │ │ └─ section: .3, .3.1, .format
│ │ │ │ └───────── slug: [a-z0-9][a-z0-9-]*
│ │ │ └─────────────────── optional ordinal (e.g., 001)
│ │ └─────────────────────────── kind: FS, AR, … or your own
│ └──────────────────────────────────── alias: root, api, …
└────────────────────────────────────────── citation marker
[id] format picks one of three ID schemes, and a kind may override it:
| Scheme | Example | Trade-off |
|---|---|---|
{kind}-{number}-{slug} (default) |
FS-014-user-login |
the number stays stable; allocate it with grund id |
{kind}-{number} |
FS-014 |
the most stable, but says nothing until resolved |
{kind}-{slug} (grund's own) |
FS-user-login |
reads like English; renaming a slug rewrites its citations |
The structure guide has the rest of the syntax, and a
runnable example of each scheme. A link you write around a citation, in any form
Markdown has, stays yours: grund fmt leaves it as written and grund check still
checks the citation in it, as
examples/authored-citations/ shows. It also
explains why an index-only declaration warns while refs still lists its entry.
A monorepo of independently checked projects is a workspace: each member keeps its
own grund.toml and its own IDs, the root lists the members, and a citation that
crosses a project boundary names the target's alias. From the
workspace example, the root's grund.toml and a member's spec:
project_name = "root"
# …
[workspace]
members = ["packages/*"]
include_root = true# FS-session: API session
The API session model lives in this project. The platform-level coordinator
is §root/FS-platform.grund check at the root checks every member, each in its own namespace;
repository options has the details.
A member a checkout may lack, such as a private package CI does not get, goes
under optional_members; in a checkout without it, grund check does not judge
the managed blocks' member lists and grund init keeps them as they are, as the
optional-member example shows.
A GitHub issue, or a Jira or Linear ticket, is cited like any spec once a kind names the
executable that fetches it. grund fetch <ID> saves it as a declaration in the
repository; every other command reads that committed snapshot offline. From the external-tickets example:
[[kinds]]
kind = "GH"
file = "docs/tickets.md"
format = "{kind}-{number}"
resolve = "should"
fetch = "scripts/fetch-ticket"$ grund check
… no snapshot for GH-1234 in docs/tickets.md — run grund fetch GH-1234
$ grund fetch GH-1234
$ grund GH-1234
The issue requests committed snapshots that resolve offline.The external facts guide has the fetch contract and a GitHub integration to start from.
A number a spec declares can be checked wherever it is used. With values = true on a
kind, a declaration's numbered headings are its components, and a backticked literal
beside a citation must equal them. From the values example, a physics simulation's time step:
# CONST-time-step: Integration time step
## 1. 0.001
## 2. sEach solver step advances time by `1.0e-3 s` (§CONST-time-step).A use that still quotes the step from before it was tuned fails the check, naming the root the line cites and the literal it wrote:
$ grund check
… whole binding: value mismatch for CONST-time-step (component 1): bound `0.002 s`, declared `0.001 s` …The citation is right and the literal is stale, so the fix is 0.001 s, cited as before.
Until grund 0.22.0 the line opens with the older component-only message, and from then
it is the root's alone.
The values guide covers JSON and YAML declarations, values inside prose specs, and bindings in code comments.
A convention can be written as one sentence and checked. With rules = true on a kind,
each declaration's title is a rule in a small controlled English and its body says why.
From the rules example:
# RULE-requirements: The requirements chapter of each FS must cite at least one REQ.
The requirement chapter carries the evidence, because §GOAL-rules makes conventions executable.A rule can count chapters as well as citations: RULE-security, in the same example,
says FS-demo must have exactly one security chapter. A spec with no such chapter, which
cites its requirement from another chapter, fails both:
$ grund check
…
docs/fs/FS-demo.md:1: error: FS-demo has 0 security chapters; RULE-security requires exactly one; …
…
docs/fs/FS-demo.md:6: error: FS-demo.requirements must cite REQ (RULE-requirements); add such a citation within itA sentence that does not parse is refused rather than guessed at, offering the one that
would where it can tell, so a reader and grund check never give a rule two meanings.
Try one before you commit it:
$ grund check --rule "Each FS may not cite any AR." --only-rule
error: modality "may not" is not accepted; accepted form: Each FS must not cite any AR.The same example's RULE-overview-ceiling uses cite each AR at most once:
zero or one citation to a target passes; two physical sites to the same target fail.
Per-target at most 1 times is refused with an at most once rewrite.
grund init repeats every valid rule under ### Chapter rules in the agent
instructions, so an agent reads the sentence check enforces. The
chapter rules guide lists every accepted sentence, the
finding each one reports, and every refusal.
Turn a §<ID> in your terminal into something you click, landing at the exact line it cites:
grund integrations # what applies in this environment
grund integrations wezterm # review what it installs
grund integrations wezterm --write # install itSupported clients are codium, iterm2, kitty, tmux, vscode, and wezterm. --write is a one-time, idempotent user setup — the integration, the grund-open resolver, and a global instruction block for whichever agents you have installed. It changes no repository.
~/.local/bin must be on your PATH — that is where the resolver is installed, and it is not there by default on macOS, where a missing PATH entry makes every click silently do nothing.
Clickable citations is the full setup guide: the per-client reload each one needs, the manual step WezTerm and iTerm2 require, how to check it works, what to do when a click does nothing, and how to control the citations agents write in conversations.
Editor support via LSP
Install the optional language server separately when you want citation completion, editor diagnostics, hover previews, usage counts on declaration titles, definition jumps, document links, references, and live $$ → § formatting:
cargo install grund-lspThe server speaks LSP over stdio and has no daemon or socket. For IntelliJ
family IDEs, grund-lsp integrations lsp4ij --write <directory> generates the
LSP4IJ import template carried by the installed binary.
Type § or $$ and it completes the declared IDs; see
authoring and client bindings.
The setup guide has the complete import and
verification flow plus snippets for VSCode, Vim/Neovim, Emacs, Helix, Zed, and
Sublime Text. Put reusable client config in your editor's user (global)
settings, so the configuration is available across projects rather than only
in repos that ship an editor config. In VSCode / VSCodium, user settings do not
grant Workspace Trust; see the guide's trust prerequisite
for the affected clients.
Hover previews — the spec and the code that satisfies it, in one frame.
Error reporting — dangling citations are flagged inline with a "did you mean" hint.
Definitions & references — jump between a declaration and every §citation of it.
| Guide | For |
|---|---|
| Checking a repository | what grund check enforces, its report, --watch, --only, pre-commit |
grund.toml reference |
every key of both config versions, and the JSON Schema for your editor |
| Config explain | what a key or a check code means and what clears a finding, from the grund you run, in your config's version — runnable example |
| Migrating to version 2 | preview, repairs and preserving listed comments — runnable example |
| Structure and IDs | kinds, ID schemes, citation syntax, inline declarations, front matter — runnable example |
| Querying grund | selectors, batch reads, refs, cover — runnable example of refs on what nothing cites |
| Reviewing code | a citation's blast radius, diff recipes |
| Chapter rules | checked rules in controlled English — runnable example |
| Sources | which files a version-2 project reads, and which comments declare — runnable example |
| Fields | a version-2 kind's chapters as fields, their order, titles and values checked, and front matter as a field — runnable example, front matter |
| Values | one value, declared once, checked wherever it is quoted |
| External facts | cite tickets as committed offline snapshots |
| Workspaces | citations across sub-projects |
| Related work | where grund stands among academic work and open tools |
| All guides | each beside its runnable example |
grund --help is one screen; grund <command> --help is one page with flags, examples,
and exit codes.
grund follows its own scheme: why, goals,
roadmap, changelog. This README is under spec
too — §REQ-readme: excerpts come from this repository or its runnable examples,
whose citations are checked in their own repositories.