grund is one pipeline. A single tree walk reads every file once and produces
Findings; the resolver completes the structural model; chapter-rule parsing
and fact production meet only as ParsedRule and RuleFacts in the rules
engine; checker rules turn that model into a Report; queries and writers
answer from the same model; and thin frontends render or transport what the
engine returns. Everything that decides lives in the engine crate grund-core,
so the CLI, the LSP server and the planned bindings are the same verdicts behind
different surfaces (§GOAL-multi-language,
§FS-distribution,
§FS-rules.11).
Speed is set by the walk, which is why the walk happens once
(§GOAL-fast-feedback).
This page is the whole system, described once; each page in the index below
describes one part and names its box here in a placement chapter (section 5).
Twelve components in one crate, stacked in the order they may read each other, and the frontends above them:
┌───────────┐ ┌───────────┐ ┌──────────────────────┐
frontends │ grund-cli │ │ grund-lsp │ │ grund-node, grund-py │ planned
└─────┬─────┘ └─────┬─────┘ └──────────┬───────────┘
└──────────────┼───────────────────┘
▼
┌────────────────────────────────────────────────────┐
grund-core │ 2.9 api │
├─────────────────────────┬──────────────────────────┤
│ 2.7 queries │ 2.8 writers │
│ show, refs, list, │ fmt, id, init, │
│ cover, completions,│ fetch, integrations │
│ editor answers │ │
├─────────────────────────┴──────────────────────────┤
│ 2.6 checker │
├────────────────────────────────────────────────────┤
│ 2.12 rules │
├────────────────────────────────────────────────────┤
│ 2.10 resolver │
├────────────────────────────────────────────────────┤
│ 2.5 scanner │
├─────────────────────────┬──────────────────────────┤
│ 2.4 workspace │ 2.11 templates │
├─────────────────────────┴──────────────────────────┤
│ 2.3 config │
├────────────────────────────────────────────────────┤
│ 2.1 grammar │
├────────────────────────────────────────────────────┤
│ 2.2 model │
└────────────────────────────────────────────────────┘
a component reads only what is below it (section 4)
The same components as the data moves through them:
grund.toml ──► config ──► workspace ──┐
▼
tree ──► scanner ──► Findings ─────────────────► resolver ────────────────┐
│ │
rule titles + vocabulary ──┤ │
▼ ▼
ParsedRule + RuleFacts ─► rules ─► Diagnostic│
▼
loaded Findings ─────────────────► checker ─► Report ─┐
│ │
├──► queries ──► data ────────────────────────┤
└──► writers ──► edits ───────────────────────┤
▼
api
│
┌──────────────┬────────────┴───────────┐
cli lsp node, py (planned)
Data flows along the arrows and so does knowledge: a component knows only what the arrows into it carry (section 4).
One subsection per box. Each says what the box consumes, what it produces, what it must not know, and where its design is written. A component whose subsection is its whole architecture has no page of its own; it gets one when it has invariants beyond its placement. Each subsection ends with the component's Rust module in crates/grund-core/src/, one directory per box, whose mod.rs re-exports all that crosses its boundary (§AR-core-module-layout.1, section 4).
Consumes text. Produces the lexical facts every other component shares: the ID grammar and its near-miss detection, comment-line and comment-block recognition, fenced-block boundaries, the number-only shorthand, inline-note layout, the never-rewrite predicates (§FS-fmt.2.3), and the formatter's own syntax — the --cross-refs link wrapper a shown body is flattened back from (§FS-fmt.6.2, §DF-show-cross-ref-flattening) and the two scopes a repository takes out of a rewrite's reach (§FS-fmt.2.5, §DF-fmt-suppression). Knows no file and no rule, and takes no Config: what configuration decides reaches it as the compiled Grammar and the one settings record built beside it — the marker, [reference] strict, the comment prefixes, the inline-note keys — so every reader here reads a decision and makes none (§FS-config.3.2, §FS-config.3.1, §FS-config.3.5). Module: crates/grund-core/src/grammar/.
Consumes nothing. Produces the data every component passes along: Findings, Declaration, Citation, Report, and the value records (§FS-values.2). Knows nothing else; it is types and tiny helpers. Module: crates/grund-core/src/model/.
Consumes grund.toml and the defaults. Produces one validated Config per project (§FS-config). Knows nothing of the tree it describes. Module: crates/grund-core/src/config/.
Consumes configs. Produces the multi-project scope — member expansion, claims, scope narrowing, boundary roots (§FS-workspace). Knows no rule, no rendering and no scan: loading the projects it names is the resolver's, one box up (section 2.10). Its own invariants are §AR-workspace. Module: crates/grund-core/src/workspace/.
Consumes the scope and the grammar. Produces Findings: every declaration, section, citation, value binding and grounding unit in the tree, from one walk (§FS-check.1) — and the one probe over the tree that is no part of that walk, which agent entrypoint files a repository has, because init and check both ask it and must not disagree (§FS-init.2.1, §FS-check.3.5). Knows no rule and no frontend, and never asks whether it is in a workspace. Design: §AR-scanner. Module: crates/grund-core/src/scanner/.
Consumes the resolver's loaded Findings and diagnostics from the rules
component. Produces the Report: errors, warnings and suggestions, each check
one pass over its owned input (§FS-check,
§FS-rules.11).
It orchestrates rule parsing, fact production and evaluation without owning any
of their grammar or relational meaning. Reads no file except in the two checks
that must, and knows no frontend. Design:
§AR-checker. Module:
crates/grund-core/src/checker/.
Consume Findings. Produce data for one question each: a declaration body (§FS-show), the citers of an ID (§FS-refs), the catalog (§FS-list), per-file coverage (§FS-cover), shell completions (§FS-completions), and the editor's snapshot, hover and on-type answers (§FS-lsp). Know no rendering; the text and JSON shapes belong to the frontends. Module: crates/grund-core/src/queries/.
Consume Findings and the tree. Produce edits: citation normalization and cross-reference links (§FS-fmt), a proposed ID (§FS-id), the init scaffold and the managed agent-entrypoint block — which files a run writes, the splice that puts the block in one, and the walk-up the block's workspace section needs, the block text being the templates' (section 2.11) (§FS-init) — an external fact snapshot (§FS-fetch), and the clickable-citation client artifacts (§FS-integrations). The only components that write to the tree, and each writes only what its spec names (§REQ-no-data-loss). Module: crates/grund-core/src/writers/. The grund CLI calls these data-returning operations and owns their argv, rendered bytes and exit codes; for example, integrations is implemented by crates/grund-cli/src/cli_integrations*.rs over the pub block of writers/mod.rs (§FS-integrations.1, §AR-bindings.3).
Consumes everything above. Produces the embedding surface: data-returning functions and the public types (§AR-bindings.2, §FS-distribution.3). Writes to no stream, exits no process and knows no frontend. Module: crates/grund-core/src/api/, one file per surface beside the private adapters that fill it. There is no process-frontend exception (section 2.9.1).
No engine component parses argv, renders to a stream, or decides a process exit status. The former grund_core::main_entry() process frontend and its compat/ module left after the deprecation path of §REQ-backwards-compatibility.2 completed; lib.rs exports no replacement renderer (§FS-distribution.3.1.1, §DA-engine-renders-nothing). Run-level cautions, including the four [workspace] warnings that were last to move, travel as Diagnostic data for each frontend to render (§FS-check.4.7, §FS-check.3.29, §FS-check.4.10, §FS-workspace.6.1). tests/integration/test_engine_boundary.py holds the absence of the renderer and process-entry export; the direction ledger in section 4 covers only the twelve engine components.
Consumes the project map from workspace and every project's Findings from the scanner. Produces the loaded project set a run operates on — each project scanned, its off-grammar citations and its cross-namespace number-only shorthands reconciled across the whole set — and four answers that are functions of what a run loaded: which project a citation resolves against, a declaration's body sliced by the spans the scan recorded, the link target its ID resolves to, and which declaration a shorthand token names in whichever project's catalog answers for it (§AR-resolver.4) (§FS-workspace.8, §FS-show.2, §FS-fmt.6.2, §FS-fmt.2.4). Knows no rule and no rendering; it runs scans, which is what puts it above the scanner while the config half of the workspace stays below it. Design: §AR-resolver. Module: crates/grund-core/src/resolver/.
Consumes config and the managed-block markers the grammar recognizes. Produces the text a managed block should say as a function of that config: the AGENTS.md block of §FS-init.2.3 with its config-derived sections, the generated grund.toml, and the embedded scaffold payload (§FS-init.2.1, §FS-init.2.4, §FS-init.5). Rendering is deterministic, so a fresh render is the hash: init writes it and check re-renders its ### Citation directions and ### Clickable citations sections and byte-compares them for drift (§FS-check.3.5, §AR-checker.2.7) — two commands asking one component for the same answer from opposite directions, which is why it is a box rather than a corner of the writers. Knows no file, no rule and no rendering of a report: it writes nothing and reads no tree, and the one block section that needs a walk arrives already rendered from the run that walked for it (§FS-init.2.3.4.15). Module: crates/grund-core/src/templates/.
Consumes authored rule titles with config vocabulary and the resolver's complete
structural model. Produces located rule Diagnostics for the checker through
exactly two internal data boundaries: ParsedRule from the sentence front end
and RuleFacts from the Markdown adapter. The logic engine evaluates only
those values and owns semantic deduplication; the scanner remains rule-blind
(§FS-rules.11).
Knows no frontend, renderer or filesystem beyond repository-relative fact
anchors. Design: §AR-rules.
Module: crates/grund-core/src/rules/ (created with the implementation).
Two today, two planned, and none has engine logic (§AR-bindings). grund-cli parses arguments, renders text and JSON, and maps exit codes (§AR-bindings.3, §FS-cli). grund-lsp speaks LSP over stdio and translates every request into an api call (§AR-lsp). grund-node and grund-py will marshal the same functions (§AR-bindings.5, §AR-bindings.6). Each depends on grund-core and on nothing of the others, so the CLI carries no JSON-RPC and the server no terminal renderer (§DA-lsp-optional).
One rule: no component reads one above it. The stack in section 1 is the
rule drawn: the frontends sit above api; api above the queries and the writers,
which are siblings and read nothing of each other; those above the checker; the
checker above rules; rules above resolver; resolver above scanner; scanner above
workspace and templates, which are siblings and read nothing of each other;
both above config; config above grammar; and grammar above model, which reads
nothing but std. Two things hold it. The compiler holds a component's privacy —
nothing outside a module directory can name what its mod.rs does not re-export
(§AR-core-module-layout.1.1) —
and tests/integration/test_dependency_direction.py holds the order across
those directories, with every read that still runs the other way listed one by
one and marked at its import, so the list can only shrink. The rules component's
internal direction and stronger parser/engine prohibitions are held by
tests/integration/test_rules_architecture.py (§AR-rules.6).
Three consequences are held by tests of their own:
- The engine writes no stream and exits no process; the frontends render (
tests/integration/test_engine_boundary.py). - The engine names no frontend's protocol: no LSP types in
grund-core, no CLI ingrund-lsp(tests/integration/test_frontend_isolation.py). - A frontend re-implements nothing: every regex, walk and rule is in the engine (§AR-bindings.2).
- Placement. Every page in the index below opens with a
## placement:chapter — a named section, sogrund <ID>.placementis the question — that opens with a diagram in the notation of section 1 — what feeds the component on the left, its box in the middle, what it feeds on the right — and then says, in four facts, its box in section 2 or 3, what it takes and from whom, what it gives and to whom, and what it must not know. Anything wider than that belongs on this page.tests/integration/test_architecture_placement.pyholds it: every page but this one has the chapter, the chapter opens with a fenced diagram, and it cites this page. - Files. How the engine's files are named, owned and sized is §AR-core-module-layout; two tests hold its module directories and the direction across them (§AR-core-module-layout.1.4), and
fissileholds the size. - Assurance is not a component. §AR-ci, §AR-benchmarks and §AR-goal-measurement measure the system rather than sit in it, and are listed apart below; their placement chapters say what each measures.
One file per page; each H1 declares an AR-<slug> ID and the body is its contract, and §AR-<slug>.<section> from anywhere in the tree resolves into it. A page may live inline in the doc-comment of the file it describes: its canonical link in this index enrolls it directly, with no stub file (§FS-check.3.18), and grund <ID> resolves the source declaration and strips its comment markers (§AR-scanner.4 lists the doc-comment forms). §AR-checker is the worked example: its only declaration is the doc-comment of fn check in crates/grund-core/src/checker/report.rs.
The system:
| ID | Subject |
|---|---|
| §AR-system | one engine, twelve components, three frontends — this page |
The components and frontends:
| ID | Subject |
|---|---|
| §AR-scanner | how grund discovers declarations and citations |
| §AR-checker | how grund validates the scanner's findings — declared and enrolled directly from crates/grund-core/src/checker/report.rs |
| §AR-workspace | how the config-time workspace layer composes with the config loader and the scanner |
| §AR-resolver | how a run loads every project and resolves a citation to one of them |
| §AR-rules | how sentence parsing and relational evaluation meet only through ParsedRule and RuleFacts |
| §AR-bindings | the engine's contract with its frontends, and the shape of the planned ones |
| §AR-lsp | how the LSP server is built |
| §AR-core-module-layout | how the engine's files are named, owned and sized |
What measures the system:
| ID | Subject |
|---|---|
| §AR-ci | CI mirrors the local pre-commit gate |
| §AR-benchmarks | instruction-counting benchmarks for the hot CLI commands |
| §AR-goal-measurement | goal and requirement meters live outside goals |
The index is navigational: cite a page's ID, or AR-system for the whole, never this file by path.