Skip to content

grund 0.15.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 22:19
· 1724 commits to main since this release

Added

  • §FS-terms.senses, §FS-terms.terms, §RULE-terms: the shared vocabulary is held by a machine instead of by a reviewer's search. §RULE-terms makes a missing ## terms: Terms chapter a grund check error over every FS declaration, and four fixed-syntax invariants gate beside it in tests/integration/test_functional_spec_terms_content.py — shared labels unique, every parenthesised word a label of the group its line cites, local labels unique per document, and a shared word redefined only in the (narrowed) form. What no machine can judge is reported rather than asserted: the same test program's --advisory flag prints, per spec, the shared words it uses without leaning on them and leans on without using, silent unless it is asked for and never an exit condition, because a gate over a lean line's completeness would ask authors to name words they do not use. The new senses chapter is where the four exclusions that make a token a use rather than a name are now written down. The architecture's twelve pages keep presence alone, held by their own test: a rule sentence cannot except the index page. This repository turns on its first rules = true kind, so its own AGENTS.md block moves from v12 to v13 and gains the ### Chapter rules section — no **Schema:** prefix, because the rule-enabled schema shipped in #335 and nothing about it moves here. That block is also taken out of grund fmt's reach with a grund:fmt off region around it (§FS-fmt.2.5.2): the chapter-rule bullet carries the first live citation this repository's managed block has ever held, and grund fmt --write wraps it into a cross-reference while grund check byte-compares it bare (§FS-init.2.3.5.10), so the two surfaces refuse each other until that is settled upstream. (PR #374)

  • §FS-config.concerns, §FS-terms.terms.9, §REQ-spec-section-names.reserved: every grund.toml key belongs to exactly one of three concerns, and which one is answered by what a finding from the key can be about — schema is one node on its own, rules are at least two, presentation is the bytes grund writes. grund_config_version, project_name and [workspace] are the envelope outside the three. The chapter also states once the rule its two existing instances were written one at a time: a rule's unit is its subject's node (§FS-config.3.9.1.1, §FS-config.3.9.2.4). The complete key→concern assignment is derived from the reader's parse sites and recorded with the decision that argues it, not written by hand. place, complement place, language, field and unit join the shared vocabulary with it; nothing a user runs behaves differently. The cut, the v2 format it is the axis of, and the releases between here and 1.0 are proposed in §DISC-core-concerns, and the two records that argue the halves of it ship with them: §DF-config-concerns carries the derived key→concern inventory, and §DA-config-concern-records the engine records the concerns become. (PR #359)

  • §FS-config.3.4.13, §FS-values.2.5: [[kinds]] value_chapter names the chapter whose named direct children are value roots in every declaration of that kind, so a record keeps a checked quantity under a stable name beside the declaration that explains it. Until now a named section held a value nothing compared: the citation resolved and grund check said success. The key needs [id] named_sections = true, is refused beside values = true, and a project that does not set it reads, reports, and exits byte-identically. (PR #278)

  • §DISC-grund-core-public-surface, §AR-core-module-layout.1.2: audit the public root surface of grund-core against the embedding surface §FS-distribution.3.1 documents — a recorded baseline, a counting convention, one classified row per root name, and an integration check holding the inventory equal to lib.rs's explicit pub use list. A proposal with a release plan under §REQ-backwards-compatibility.2; no symbol, visibility, #[doc(hidden)] attribute, command, output or exit code changes. (PR #281)

  • §FS-distribution.4.2: the release guard reads wording changes in <release>, so a release at or above a wording deadline is refused. The clause was in the spec's table and absent from the gate; the two wording constants this tree ships — the agents-init maintenance tail and the narrowed-alias scope suffix — now hold the release they name, and each has a version-gated deadline test of its own. (PR #282)

  • §FS-terms, §FS-check.2.3: settle the vocabulary the functional spec shares. One new declaration defines 42 shared words in seven cited groups, and each of the 24 specs the functional-spec index links gains a ## terms: Terms chapter that opens with a lean line naming the groups it draws on and then defines only the words it introduces or narrows. Until now the same word could mean two things two specs apart, so a section read on its own did not answer on its own. Two FS-check corrections ship with it: a suggestion is a finding carried on the suggestions channel rather than something other than a finding, which is what §FS-distribution.3.0.1 and §FS-rules.7 already type it as. No behavior changes — two reads that exit 1 today, grund FS-terms and grund FS-<spec>.terms, start returning content. (PR #286)

  • §FS-declarations, §FS-errors.5.5, §REQ-spec-section-names: a check is now named by the code that selects it. The ten checks grund check enforces about a declaration — duplicate, duplicate-section, orphan-section, section-heading-level, section-outside-declaration, unmarked-heading, declaration-near-miss, misplaced-declaration, broken-stub and oversized-lead — move out of FS-check's positional Errors detected and Warnings chapters into one concept spec, each at a section named for its diagnostic code, so grund FS-declarations.checks.duplicate reads the rule that --only duplicate selects. The flat code list of §FS-errors.5.5 becomes a table carrying each code's severity, its pending ramp, what enables it and the point that specifies it, which is what makes a promotion a one-cell edit rather than a section move: the two ramps this tree still owes, §RM-unmarked-heading-error and §RM-off-grammar-declaration-error, are rewritten to say so. §REQ-spec-section-names writes down what a section of this repository's own specification may be called. All ten old addresses stay as one-line pointers until 0.15.0, and scripts/move_spec_sections.py repointed the 330 sites that addressed them. No behavior changes: no message, severity, exit code, golden file or template byte moves. The nine remaining concept specs follow one pull request at a time. (PR #299)

  • §FS-terms, §AR-system.5: widen the shared vocabulary to the architecture. §FS-terms is the vocabulary of the functional spec and the architecture, and each of the eleven pages the architecture index links gains the same ## terms: Terms chapter the specs carry — placed after its placement chapter, which keeps the opening position — opening with a lean line that names the groups the page draws on and then defining only the words it introduces or narrows. One group is added, §FS-terms.terms.8, for the two words that are architecture's own and no functional-spec page uses in the shared sense: box and meter. It is appended, so no group is renumbered and every existing §FS-terms citation stands; the twenty-six functional-spec chapters and their lean lines are untouched, and tests/integration/test_architecture_terms.py holds the new obligation the way test_architecture_placement.py holds the chapter above it. A lean line names the settled word a page uses and says nothing about a retired synonym the page also uses, so the prose sweep stays issue #291. No behavior changes — eleven reads that exit 1 today, grund AR-<page>.terms, start returning content. (PR #307)

  • §FS-refs.1, §FS-refs.2, §FS-refs.3.3, §FS-refs.4, §FS-workspace.8.2, §FS-lsp.1.2.6: grund refs --descendants widens the section filter from the exact coordinate to that coordinate and every section beneath it — the set the LSP already returns on a section heading, now askable from the terminal, so pricing a section move, rename or delete is one query instead of a --toc read, one query per child and an addition done by hand. Beneath is a relation on dotted path components and never a string prefix, so 3.16 reaches 3.16.1 and never 3.160, and checks.duplicate never reaches the sibling checks.duplicate-section; a dangling descendant counts, because it is exactly the citation a move breaks; --summary folds the wider set with no grouping by section; and on a query that carries no section the flag is a silent no-op with byte-identical stdout, stderr and exit status rather than a usage error. RefsOpts gains descendants: bool, false by default — a source change for a Rust embedder who builds that record with a struct literal and no ..Default::default(). No existing invocation changes a byte, no exit code moves, and no JSON field is added, removed or reordered. (PR #333)

  • §FS-refs.3.4, §FS-refs.1, §FS-refs.2.1, §FS-output-shapes.5.1, §FS-lsp.1.2.5, §GOAL-token-economy.1: grund refs --total answers the citation set with its size instead of its members — one line, cited at 140 sites across 50 files, so "how much leans on this ID" costs one invocation and two numbers rather than two whole-tree scans, fifty rows and a grep -c. It is the third rung of the fold --summary is the second of, so it folds over whatever set the invocation lists: it composes with --section and the dotted inline form, widens with --descendants, and subsumes --summary silently rather than refusing it. The empty set reads not cited, for the reason the hover reads it; --format json emits exactly one object, {"sites":<n>,"files":<m>}, at every count including zero, with no project member in a workspace and no kind_title. The clause is the editor hover's own sentence, rendered by the same grund-core fold both transports now count with — usage_over_paths and usage_clause join the crate's public surface for it, and LspUsage becomes keep in the surface inventory — so the terminal and the editor cannot drift apart on a number (§FS-lsp.4). Opt-in: without the flag not a byte moves, and grund refs <ID> --summary | wc -l is unaffected. Closes issue #339. (PR #342)

  • §FS-rules.8, §FS-check.1.4, §FS-check.2.1.2, §FS-cli.3.3, §DF-rule-authority-is-a-field: grund check --only-rule scopes the report to what the --rule sentence authored, so trying a sentence out costs its own findings instead of the whole tree's — the affordance --rule was always sold as, which until now printed every declared rule's findings beside the one asked about, and which --only could not narrow because a trial sentence emits the same codes by construction. Selection becomes two axes that intersect, --ignore still winning over both; the flag is a boolean and requires --rule, refusing with error: --only-rule requires --rule and exit 2 rather than scoping to nothing and printing success. Two consequences are now written down so they cannot drift: a finding the trial sentence and a declared rule authored jointly is retained with its authority and bytes unchanged, and a sentence duplicating a [citations] direction yields an empty scoped report because the config finding wins and the rule authored nothing. A third is named where it can mislead (§FS-errors.5.2.3): no rule authors a run-level warning, so a scoped run over a tree that scanned nothing drops the empty-scan caution with everything else and prints success at exit 0 — what the code axis has always done with it, and what success has always meant (§FS-check.2.1.3). Bare --rule and every unselected run are byte-identical. Closes issue #357. (PR #363)

  • §FS-cover.3.2, §FS-refs.3.2, §FS-output-shapes.5.1, §FS-workspace.8.2.4, §FS-workspace.8.6: every citation record grund cover --format json and grund refs --format json emit carries enclosing_declaration and enclosing_section — the declaration and the accepted section the citing site sits in, the from half of the cites edge that no grund output named before. Both are string | null and always present, both sit after text, and kind_title stays the final field of the refs record; enclosing_declaration is bare in a workspace and rendered under the citing project's [id] grammar — unlike id, which renders under the target's — because the declaration it names sits in the citing file and project already says which project that is. enclosing_section carries the nearest accepted section path, numbered and named alike — "1" under ## 1. Inputs, "terms" under ## terms: Terms. A caller reads "FS-cover's terms section cites FS-terms" out of the same scan that checked it instead of re-parsing every grounded file to recover a unit the scan already held. Both commands now perform the citing-side classification pass they used to skip, measured at +3 to +7 ms on cover over this repository and below this machine's resolution on refs; list, show, fmt, completion, sizes and batch still skip it and change neither a byte nor a millisecond (§AR-scanner.2.4.2, whose "every Citation records" is corrected to say which runs do). Additive for a consumer reading named keys; a consumer diffing whole bytes sees the two new keys, and a Rust embedder who builds CoverCitation or RefHit with an exhaustive struct literal gains two fields to fill. refs --summary and refs --total are unchanged. Closes issue #353. (PR #364)

  • §FS-rules.checks.unreached-declaration, §FS-rules.2, §FS-rules.3.1, §FS-rules.5.2, §FS-rules.7.7, §FS-rules.7.3, §FS-rules.7.4, §FS-rules.7.6, §FS-rules.10, §FS-errors.5.5, §REQ-backwards-compatibility.2, §DF-chapter-rule-reaches-every-declaration, §RM-unreached-declaration-error: a chapter-scoped citation rule now reaches every declaration of its kind. The requirements chapter of each FS must cite at least one REQ. selected only the declarations that had the chapter, so deleting the chapter was the one edit the rule could not see: the rendered instruction in AGENTS.md claimed the whole kind and grund check was silent about the declaration that slipped out from under it. A new code unreached-declaration reports that declaration at its title line, naming the rule that stopped applying and the two actions that answer it — add the chapter, or narrow the rule's subject. It fires for the three positive citation families (outbound count, per-target coverage, inbound count) and not for prohibition, because a citation site inside the chapter body is deleted along with that body, so for must not cite the forbidden site genuinely no longer exists. A chapter-presence rule beside the citation rule suppresses nothing: one absent chapter under a paired pair prints both lines, each naming its own rule. The exact chapter spelling keeps its invalid-rule, and §FS-rules.2 now says why the two spellings stay different kinds of finding — a literal that does not resolve is a defect in the sentence, reported where the sentence is; a quantified subject that selects nothing for a declaration is a gap in coverage, reported where the declaration is. Who this breaks: a repository with a chapter-scoped must citation rule over a kind one of whose declarations lacks the chapter. The exit code does not move — the finding is a warning through release N, the deprecation path §REQ-backwards-compatibility.2 opens — but the bytes do, and a tree that printed success now prints the warning instead (§FS-check.2.1.3), so a pipeline that greps for success rather than reading the exit code sees a failure the exit code does not report. --ignore unreached-declaration is the escape for anyone who needs the window. The message names grund 0.16.0 in its own bytes; the promotion to an error is a follow-up issue blocked by this one, and a should rule reports the absence as an ordinary suggestion with no ramp clause and no change to any exit. A repository with no rules = true kind is byte-for-byte unchanged. Closes issue #362. (PR #367)

  • §FS-rules.3, §FS-rules.3.1, §FS-rules.3.2, §FS-rules.3.3, §FS-rules.3.4, §FS-rules.3.5: chapter rules accept at least N in all four count-bearing families, so a project can write a lower bound above one — a corroboration floor such as FS-demo.requirements must cite at least 2 REQ. — as a checked rule rather than as a ceiling, an exact count, or prose. Until now the grammar admitted a numeric count only after at most and exactly , so the one general sentence the lead of §FS-rules.3 already states was the one bound its productions did not honour. One meaning keeps one spelling: numeric at least 1 is refused the way numeric exactly 1 already is, and both rows join the normative refusal table. A widening, and only that — apart from the four new at least N productions, every sentence accepted today is accepted and every sentence refused today is refused, and no tree that checks green today starts failing. A tree that already carries the new spelling does move, from an invalid-rule refusal to whatever its rule now says, which is the change rather than a hazard. One message grows, by appending a cause after the verbatim count is not accepted; prefix, so no bytes are rewritten. Closes issue #352. (PR #358)

Changed

  • §FS-config.3: the chapter is retitled Schema → Keys, because schema is now the name of one of the three concerns and cannot also name the whole key set (§REQ-spec-section-names.names). The section is numbered, so no coordinate moves and no citation changes: §FS-config.3 resolves exactly as before. What does move is the derived Markdown anchor — a stored link to FS-config.md#3-schema becomes #3-keys — which is why this project asks for the citation to be stored rather than the anchor. (PR #359)

  • §FS-errors.5.1, §FS-errors.5.2, §FS-errors.5.2.3, §FS-output-shapes.1, §FS-distribution.3.0.1, §FS-rules.7.6, §FS-rules.6: every check finding record carries authority — the bytewise-sorted rule origins that authored it, null where no chapter rule did — so which rule said this is a field a script reads rather than a name buried in the message tail. A declared rule's ID, "--rule" for a trial sentence, or both where they reached the same meaning. The key is last, follows sites, and is present on every record of the shape §FS-distribution.3.0.1 fixes: always null on a failed ID query, on a run-level warning, and in the error member of a show --batch --format=json envelope, which carries the finding object unchanged (§FS-output-shapes.4.1) — so one key set holds across every binding rather than a conditional each consumer branches on. Who this breaks: a consumer that asserts a record's exact key set sees one more key; a consumer that reads the keys it wants is unaffected, no text output byte moves, and no exit code changes. A Rust embedder that builds a Finding by struct literal or matches one exhaustively sees a new public field. §FS-rules.7.6's "rule-derived JSON adds no source-list field" is rewritten rather than dropped: it stays true of sites, which is what it meant. Closes issue #357. (PR #363)

  • §FS-distribution.4.6, §FS-distribution.4.5, §AR-ci.7, §AR-ci.1.1, §AR-ci.1.2: the changelog gate asks for a bullet rather than for a number, and the release writes the number. Until now the pre-push hook asked gh for the current branch's pull request, found none before the first push, and passed — and pull-request CI then refused the same tree for not naming a number that did not exist when the contributor pushed, which is the first thing an outside contribution met. Both halves now ask one question of local git alone: ## Unreleased must hold at least one bullet the merge base does not, where two bullets are the same once whitespace collapses, a trailing PR #N or PR #346 is dropped and a link's destination is disregarded — so a rewrap, and a citation link grund fmt --write rewrote inside somebody else's bullet, are not changes. A number is optional: write (PR #346), or nothing, and scripts/prepare_changelog_release.py stamp blames each bullet to its pull request at release time and fills it in, warning about what it cannot resolve and never failing the release. Who this breaks: a push that wrote no changelog bullet is now refused where it used to be silent — SKIP=changelog-pr-entry git push is the documented escape for a branch that is not becoming a pull request, and the refusal names it. A base no merge base can be taken against degrades to "at least one bullet" and warns rather than refusing, saying which of the two it met — no ref resolves it, or it shares no history with the head, as a shallow clone's grafted base does. Both sides of that comparison are read from commits rather than from the working tree, so an uncommitted bullet does not satisfy a push that has not carried it yet, and a push that only deletes a ref is skipped. gh stops being a pre-push dependency, and --local-pr is gone. No command, flag, exit code or output byte of grund itself moves. Closes issue #345. (PR #346)

  • §FS-check.3.24, §FS-check.3.24.1, §FS-check.3.24.2: the declaration-local section finding names the releases its verdict moved between, and the command that clears it where grund fmt --write would actually write the site. Appended to the existing text, which stays a verbatim contiguous prefix; code, severity, sites and exit verdict unchanged. Closes issue #337. (PR #338)

  • Schema: §FS-init.2.3.7, §FS-check.3.5: the managed agent-instructions block bumps v10 → v12, and v11 → v13 where a rule kind is enabled, because the block's own prose carried bare ID-shaped illustrations that grund check reports under [reference] strict = false and its fixed canonical text may not change without the version moving. Rule-enabled stays base plus one (§FS-init.2.3.7.1). Migration: run grund init to refresh the block. (PR #335)

  • §REQ-backwards-compatibility.5, §REQ-no-missed-citation.2, §DF-unverifiable-rule-scope, §FS-check.3.5.4, §FS-rules.9: one verdict moves from 0 to 1: the ### Chapter rules managed section is compared even when a configured rule does not resolve. Until now the drift pass dropped that whole section from its byte comparison whenever rule resolution failed, and said nothing — so a managed block missing a bullet, or holding arbitrary text in place of one, passed grund check at the workspace root. A repository that worked around the grund init refusal above therefore gets a new agents-init error on its next check: every finding that replaces the old verdict names its location and the action to take, this one anchored at the block's own line and naming grund init as the refresh, which is now a command that works there. §REQ-backwards-compatibility.5 licenses the move: the old silence violated the prior prohibition in §REQ-no-missed-citation.2, and §DF-unverifiable-rule-scope.4 proves the conflict and why neither the deprecation path nor a mechanical migration fits. A project with no rules = true kind is byte-identical. (PR #334)

  • §FS-check.3.29, §FS-lsp.1.1.3, §FS-errors.5.2.3: a [workspace] block no enclosing block lists now fails grund check. The deprecation ramp every 0.14.x binary announced in its own output lands on the release it named: check exits 1 where it exited 0, and the finding moves from the report's warnings to its errors. With the verdict, the shape — it is a located error like every other in §FS-check.3, so it prints on stdout behind the <path>:<line>: error: prefix, and under --format json its object carries the block's grund.toml in path and its [workspace] line in line where both were null, with sites still null. The location leaves the message text, because the prefix now carries it. An editor marks the same line as an error instead of a warning, taking it from check's report rather than the run's warning channel. What does not move: the code is still unlisted-workspace-block, the anchor is still the block's [workspace] line, --ignore unlisted-workspace-block still clears the finding (and now the exit code with it), and the two config edits that clear it are the two that always did — list the directory in the enclosing [workspace] members, or keep it out of that project's [scan]. And list, refs, cover, fmt and the bare ID read do not move: same stream, same warning: shape, same exit 0, with one word of text different — becomes an error in grund 0.15.0 becomes became an error in grund 0.15.0. They have no error channel for a fact about the run, and §FS-cli.5 freezes their exit codes. Who this breaks: a repository with an unlisted block sees CI go red, clearable by one of the two one-line config edits it has been told about for two releases; a JSON consumer of check --format json sees the object change stream, severity and location, and one that grepped stderr for the warning under check stops seeing it — the one seam that bites silently, and the reason the schema move is argued under §REQ-backwards-compatibility.4 rather than carried by the ramp alone. Because the tree now reports 0.15.0 in the past tense, no version below it can be cut. Decided in §DF-unlisted-workspace-error-shape, which supersedes §DF-unlisted-workspace-block.2.4 for check and the editor and leaves it standing for the other five. Closes issue #249. (PR #289)

  • §AR-scanner.2.2.8: EmbeddedValueRoot records which of the two enrollment routes made the section a root — origin: ValueRootOrigin replaces the bare marker_column field, which a chapter root has none of. Embedders reading that field call marker_column() instead. (PR #278)

  • §FS-config.principle, §FS-config.requirements, §FS-config.2: state once how grund.toml's committed scopes relate — built-in default, project, kind — with the four settings admitted at both the project and the kind scope listed in one place, and eight marked requirements on the schema as a whole. §FS-config.2's "Layering is shallow" sentence was a wrong description of grund, which merges key by key, and is withdrawn; its [[kinds]]-omission compatibility note moves intact to §FS-config.3.4.4.4. No behavior changes: no key is renamed, none gains a scope it did not have, and grund_config_version stays 1. (PR #287)

  • §REQ-backwards-compatibility.5, §REQ-no-missed-citation.2, §DF-undeclared-blind-spots: two verdicts move from 0 to 1, closing two blind spots no section had declared — a marked qualified citation is reported at its own site whatever its tail grammar (§FS-workspace.5.2), and a scan root written below a linked parent directory is walked instead of refused (§FS-config.3.5.1); every finding that replaces the old verdict names its location and the action to take, the alias finding at the citation's own line and a newly read file's findings as ordinary located ones (PR #288)

  • §GOAL-configurable.2, §DF-verdict-vocabulary-freeze: state the freeze on severity, exit codes and report ordering once, and state it as a freeze on the verdict vocabulary. The goal scoped the trio to install-local choices while §FS-config.6, §GOAL-friendliness-first.2 and §FS-non-goals.9 froze the same list for every project, so a reader could not tell whether their own grund.toml was allowed to move a verdict — and one decision record in this tree had already answered wrongly. The goal now says three things in three paragraphs: the severity set, the report→exit mapping (§FS-cli.5) and the report's ordering are frozen for every project and every install alike; committed project policy chooses which specified rules are in force and, where a rule's own specification fixes a complete closed ladder, which channel that rule speaks through; and, as a separate axis, install-local state may change no verdict, with §FS-config.principle.install-local cited for the authoring procedure rather than restated. The new record carries the four-limb admissibility test for a proposed setting, the four shipped committed settings it admits — [citations.<KIND>] levels, [reference] shorthand, [reference] inline_note_layout_check, [id] section_heading_levels — with [reference] lead_size_warning as the control, and what the ruling costs. §FS-config.6, §GOAL-friendliness-first.2, §FS-non-goals.9 and §FS-cli.5 do not move; §DF-unmarked-markdown-headings keeps its refusal and re-grounds it on its own rule-specific reason. No behavior changes: no command, flag, configuration key, output byte or exit code moves, and all four shipped settings behave exactly as they did. Closes issue #294. (PR #302)

  • §FS-check.3.6, §AR-bindings.3, §GOAL-no-silent-breakage, §REQ-no-missed-citation.2: apply 44 of the 47 rulings the specification consolidation left for afterwards — the goals, the requirements and four decision records now say what the specification says, and two headings are renamed to what their own bodies define, which moves their anchors (#36-ungrounded-source-file-opt-in becomes #36-ungrounded-unit-opt-in, and AR-bindings' third section names the crate). Every link in the tree follows; an external bookmark of either anchor does not. Three rulings are deferred: two wait on the release in which bare grund stops running check ., and one on a re-ruling. (PR #288)

  • §FS-terms.terms, §FS-terms.terms.2, §FS-list.terms, §FS-list.1.5, §FS-list.3.4: a sweep commissioned on purpose is a permitted way to pay the retired-word debt, and the first word is paid. The vocabulary's lead keeps the lazy default it always had — a retired word is rewritten as prose is written or rewritten, so no change owes a pass of its own and none is held for one — and now says that a deliberate pass is the other way to pay, answering for the words it names and for nothing else; until now the same paragraph read as forbidding one. The citation row stops retiring cross-reference where it names the --cross-refs markup wrapper rather than the citation inside it (§FS-fmt.6), which is what the functional spec almost always means by the word. point size is the first word swept: FS-list's Terms chapter defines coordinate-size as one --size row per declaration or citable section (its lead and full-body measurement, or the site alone where there is nothing to measure), the two prose sites that said point-size rows say coordinate-size rows, and the size section's title becomes --size — per-coordinate lead and full-body measurements, which moves its anchor — every link in the tree follows (§FS-fmt.6.3 repairs them), an external bookmark of the old anchor does not. §FS-init.2.3.5.9 and §FS-init.2.3.7.1 call the v9 managed-block bump the coordinate-size sweep in prose; the block's own bytes do not move, and neither do the CLI help text, the --size and --top grammar, the closed unit set, lead_size_warning, the NDJSON lead_<unit> / full_<unit> field names, or any exit code. docs/user-facing/point-sizes.md is retitled Coordinate sizes and keeps its filename, so every link to it still resolves. The 21 remaining agreed words carry one issue each, and the twelve referred out are carried by one issue of their own, issue #330, which asks for the ruling on whether and how they are paid at all; all of them sit under issue #291. (PR #306)

  • §FS-terms.terms, §FS-terms.terms.1, §FS-terms.terms.6: the commissioned sweep issue #291 left pays twenty-seven words at once, one reviewed commit per word — walk root, configured scope, scan set, inline citation site, inline declaration, code-resident declaration, cross-project reference, section reference, doc comment (the two-word spelling), anchored at, value field, announcement, batch mode, declaring spec, walk, diagnostic, reference, namespace, tier, handle, point, stub, index, spec, marker, prefix and level leave functional-spec prose for the term the vocabulary blesses, each keeping the carve-outs its issue names — a frozen code, config key, output string, flag or link target is a name rather than a use, and stands. issue #330's ruling on the twelve dominated words is recorded with them: rule, fact, catalog and chapter stay, each narrowed to the sense no other term carries — rule is one rule the checker applies and direction and level its two parts, a declaration's body is the fact, bare catalog is the ID catalog while a qualified compound is a term of its own, and chapter remains the rule grammar's subject-unit word and the name of a second-level section an obligation requires by name — and the sweeps of point, prefix, index and marker follow that narrowing. No behavior changes: no command, flag, configuration key, finding code, severity, output byte or exit code moves, and the AGENTS.md managed block is byte-identical. Heading renames across twelve functional-spec files — FS-check and FS-config most of all — move their anchors — every link in the tree follows (§FS-fmt.6.3 repairs them), an external bookmark of an old anchor does not. Closes issue #310 through issue #330. (PR #343)

  • §FS-check.3.18.5, §FS-check.3.18.5.1, §FS-init.2.1: missing-index-entry now says whether the index mentions the ID at all. One sentence covered both cases until now — <ID> is not listed in <index> — became an error in grund 0.13.0 — so a reader who had just written the index row by hand was told, in the same bytes as before their edit, that it was not there, and went looking for a scanning fault. Where the index text mentions the ID and holds no entry for it, the finding now reads <ID> appears in <index> but not as an entry: an entry is a `§`-marked Markdown link to the declaration — became an error in grund 0.13.0, rendering the configured marker rather than a hard-coded §. The form that sentence names follows [reference] strict: off strict mode, where a bare token is a citation and a hand-written link around one is already a correct entry (§FS-check.3.17.5), the clause reads an entry is a Markdown link to the declaration whose text is the ID, with or without the `§` marker instead, because naming the marked form there would be a sentence the reader can see is not so about the index in front of them — this rule's own defect, one mode over. What counts as a mention is §FS-check.3.18.5.1: an ID-shaped token on its own boundaries, or the declaration's file name as a whole name, on a line that is neither inside a fenced block nor a declaration heading. Inline code is deliberately not excluded, which is what makes one sentence true both of a citation fmt declines to wrap and of a markerless mention the scan never recorded. Four of the eight end-to-end cases that reach this rule change text and four do not, and the split is what the change is judged on: an index that mentions the ID gets the new words, and an index that says nothing about it keeps the old ones — including check-index-missing-file, where there is no index text to look at, so §FS-check.3.18.7's (the index file does not exist) can never share a line with the new clause. The scaffold stops naming the wrong remedy: the three index README.md files grund init --docs writes promised “a full Markdown link that grund fmt --write writes and keeps current”, and fmt writes that link only where the row already carries a marked citation it may wrap. They now say to write the row with the marker, and that a row without one is prose grund check will report. grund fmt --cross-refs --write is still right to answer rewrote 0 lines on a markerless row (§FS-check.3.17.5, §DF-index-entry-form.2.3); what changes is that check no longer leaves fmt looking like the fix. The seam: the code is still missing-index-entry, and the severity, the anchor, the exit code and the set of repositories that pass are all unmoved — no verdict changes. A consumer matching the exact sentence sees four cases change text under one unchanged code; one matching on code, or on missing-index-entry in --format json, is unaffected. Closes issue #297. (PR #332)

  • §FS-terms.terms.2, §FS-check.1.1, §FS-check.1.1.3: the next word of the commissioned sweep is paid — ref gives way to citation. Two prose occurrences in one file, both in a lead and the last places in FS-check that said otherwise: bare tokens in default mode "do not trigger dangling citation errors", and an ID-shaped substring inside runtime data "does not raise a false dangling citation". The replacement is §FS-check.3.1's own heading title and stays two words: the published finding code is dangling, so a hyphenated form of the two-word phrase would mint a selector-shaped token the catalog does not publish (§FS-errors.5.5, §REQ-spec-section-names.code). No behavior changes: no finding code, message, severity, exit code, flag, output format or golden byte moves. No heading title carries the word, so no anchor moves and no link fragment is invalidated. The pass answers for ref and nothing else (§FS-terms): the word still stands in docs/architecture/, in the released changelogs under docs/changelog/, and in the frozen GOAL, REQ and decision-record texts — including §GOAL-no-dangling-refs's own ID, which FS-check's lead cites — because the vocabulary's lead exempts them, so a grep after the merge still finding it is not an incomplete sweep. Under issue #291. (PR #344)

  • §FS-values.5.2.1, §FS-values.5.2: a value-mismatch whose two components are of different kinds now names each side's kind. A JSON declaration holding "2022" against an authored `2022` reported two identical-looking sides as unequal and no word in the line named a type, so the search started where an invisible difference would live — a non-breaking space, a BOM, the JSON encoding — and the one fact that decided the finding, the component kind the comparison of §FS-values.4 turns on, was the only thing missing. The canonical text is now followed by — bound is a number, declared is a string; a number and a string are never equal, in the order the line already named its sides, and neither side gains quotes its own source does not carry. A same-kind mismatch is byte-identical: naming number twice tells a reader nothing the two values do not already show, so every existing golden, the canonical example and the JSON finding object stand unmodified, and no new NDJSON member is added — the improved sentence reaches --format json and the LSP through the one formatting site. The clause is appended after a complete, unchanged message, so it ships in one release under §FS-errors.3's appending rule rather than the deprecation window of §REQ-backwards-compatibility.2; the consumer it breaks is one matching a mixed-kind value-mismatch line whole, whose migration is code == "value-mismatch". No verdict, finding count, severity, ordering or exit status moves. Closes issue #356. (PR #360)

  • §FS-rules.2, §FS-rules.3.1, §FS-rules.10: say where an author actually reads it that a quantified chapter subject selects only the chapters that exist. The requirements chapter of each FS must cite at least one REQ. reports an FS whose requirements chapter cites no REQ and says nothing at all about an FS that has no such chapter, so the rule covers the whole kind only with Each FS must have at least one requirements chapter. beside it — now stated in the spec and in all three copies of the chapter-rules writing block, with the two chapter spellings documented side by side, because the exact one already fails as invalid-rule when the chapter is deleted while the quantified one is silent. No behaviour changes: no finding is added, removed or reworded, no exit status moves, and the examples/rules/ goldens are untouched. Tightening the check so an absent chapter is reported is deliberately left to its own issue. §AR-ci.9.4's measurement of the unsplittable skills/grund-init/SKILL.md is refreshed and its exception becomes a structural hard entry. (PR #361)

Deprecated

  • §REQ-backwards-compatibility.2, §FS-distribution.4.2: every ramp that promised a change in 0.15.0 and has not landed now names 0.16.0, so 0.15.0 can be cut without breaking a promise its own messages make. unmarked-heading and declaration-near-miss stay warnings, refs keeps exit 2 with its warning for invalid IDs and ambiguous number-only shorthands, the absorbed-scan warning keeps its exit code, and the agents-init, rule-alias, scope-only and managed-block wordings keep their compatibility forms, each through 0.15.x and each now saying 0.16.0 where it said 0.15.0; the version switches behind declaration-near-miss and refs move to 0.16 with them. Moving the far end lengthens each window and shortens none, as §DF-absorbed-scan-warning.2.3 already did once for 0.14.0. The unlisted-[workspace] error, which did land, still reports 0.15.0. (PR #381)
  • §FS-cli.1, §REQ-backwards-compatibility.2: bare grund names the release it stops running check . in. The warning it has always printed gains one sentence — This fallback is removed in grund 0.16.0. — so the deprecation has the named window the path requires, and the specification says the same in the same bytes. Nothing else about the run moves: same stderr prefix, same check . validation, same stdout, same exit code. What the number buys is a commitment rather than wording: §FS-distribution.4.2.2's release guard now refuses to publish 0.16.0 or anything above it until the fallback is gone, so the removal is a release blocker from here on. Removing it is not this change. Closes issue #293. (PR #300)

Removed

  • §DA-engine-renders-nothing, §FS-distribution.3.1: remove the deprecated grund_core::main_entry() process frontend and its engine renderer after 0.14.0 shipped the removal notice; process callers use the grund CLI package and embedders use the data-returning core APIs. (PR #276)

Fixed

  • §FS-check.1.3.6.1, §FS-check.4.1.4, §FS-check.3.1, §FS-check.1.3.7, §AR-scanner.1.6, §AR-resolver.3.3, §REQ-no-wrong-citation.2, §REQ-no-missed-citation.2, §REQ-backwards-compatibility.5, §DF-path-scope-resolves-project-wide: a path-scoped grund check narrows the report, not the resolution. grund check <path> read exactly <path> and resolved exactly what it read, so a citation whose declaration lives elsewhere in the same repository was reported unknown reference <ID> — the same message and the same dangling code as an ID declared nowhere — and a file holding twenty-three such citations was called an ungrounded source file on top of it. On this repository, clean under grund check ., the narrow runs produced 6,836 findings and every one was false. The declaring side was wrong in the mirror image: grund check docs/ warned declared but never cited about a declaration whose only citation sits in src/. A run given an explicit path now reads what its project's ordinary run reads — [scan] include plus every walked kind home, union the path — runs every rule over the whole of it, and drops the diagnostics anchored outside the path. The verdict moves in both directions. grund check <path> goes 1 → 0 where nothing is wrong, so a script that read its exit code as meaningful gets 0 on a clean tree and a script that greps its stdout gets fewer lines and success where there were findings; and it goes 0 → 1 on a duplicate declaration whose twin lies outside the path, which was invisible before and is the one direction a green path-scoped run can turn red. Every finding that replaces the old verdict names its location and the action to take — the duplicate declaration error names both declaration sites. Both directions are authorized by §REQ-backwards-compatibility.5, whose five conditions §DF-path-scope-resolves-project-wide meets, naming §REQ-no-wrong-citation.2 as the prohibition the old verdict violated. The cost: grund check <one file> goes from 4 ms to ~140 ms on this repository and ~212 ms on a 10,000-file tree — what grund check . costs, and what the documented grund check . | grep workaround already cost, so nobody loses a fast correct answer. No finding code is added or changed and no located finding's message moves, --only and --ignore gain no vocabulary, --format json keeps its shape and ordering, and --full with an explicit path is still a no-op — only its caution's stated reason moves, because "already bypasses [scan] include" became false. grund check with no path, --full at the root, the pre-commit hook as configured here, grund cover, the language server, and every query command are byte-for-byte unchanged. What the review of this change added to it. A finding that spans several sites is in scope at any of them (§FS-check.1.3.6.2), so the duplicate above is reported from either twin rather than only from the one the message happens to be anchored at, and §FS-values.5.2's value mismatch is reported from the declaring file as well as from the binding — the same direction, a second code. And a declaration source the wider walk could not read is no longer passed over in silence: it earns one CLI-level caution on stderr naming the file and the reason (§FS-check.1.3.6.3), on the io code that already meant a read failure, so a run whose false unknown reference comes from an unreadable declaration says so. That caution wears the io code, which no selector can hide, so a tree holding one unreadable file in its resolution scope prints the line in place of success for every grund check <path> in it. The exit code is untouched by both — §FS-check.2.4's 2 is still reserved for a file at or under the path — and the chapter rules are now asked whether the resolution scope was read in full, so a path-scoped run no longer asserts an absence fact over a read it knows failed. Closes issue #370. (PR #372)
  • §FS-errors.5.5, §FS-check.3.14, §FS-check.3.14.2, §DF-check-full-scope.2.2: the two codes grund check --only and --ignore have accepted since 0.14.0 — local-section-citation and out-of-scope-local-section-citation — now carry rows in the catalog that calls itself the exact public selector vocabulary, which goes from 47 rows to 49 and becomes the closed set it claims to be. Until now the only way to find either code was to read crates/. The tier of §FS-check.3.14 is written down as the five findings it has emitted since 0.14.0 rather than four, and what admits one out there becomes a test rather than a list: a verdict on a citing token, reached from that token, the declaration structure of the file holding it and the resolved graph, and from nothing the project assigned the file — so §FS-check.3.24's declaration-local form verdict stands on the same ground as §FS-declarations.checks.section-outside-declaration, and a sixth rule is held to the test rather than argued about. tests/integration/test_out_of_scope_code_family.py holds the match arms, the sentence and the catalog equal so the next arm cannot start the drift again. Nothing a user runs behaves differently: no code, message byte, severity, ordering or exit status moves, and crates/grund-core/src/checker/selection.rs is untouched. Closes issue #348. (PR #366)
  • §FS-lsp.responsiveness, §FS-lsp.responsiveness.1, §FS-lsp.responsiveness.2: the editor gets its diagnostics in about the time grund check takes. A workspace of one root and nine members carrying 1200 IDs and 1260 findings was checked in 73 ms and published its first publishDiagnostics 3921 ms later, with hover and definition queued behind it on the single-threaded event loop at 8007 ms; doubling the workspace moved the check to 100 ms and the publication to 15946 ms, which is the growth of a cost that multiplies its two inputs rather than adding them. Anchoring one finding scanned five snapshot collections and compared paths by resolving both sides of every comparison, and resolving a path reads the filesystem once for every component of it — 2 114 597 readlink calls over one session on a 300-ID fixture, every one of them EINVAL. So the cost was findings × snapshot records, paid again after every rebuild: on initialized, on every didOpen, and on every keystroke. A snapshot record's path is already resolved, so an answer now resolves the incoming path once and compares the resolved values, and the per-file lookups are indexed rather than scanned; the same shape in hover, definition, occurrence highlights, document links and references goes with it. Nothing the server publishes moves — §FS-lsp.4 parity is byte-for-byte and the tests/integration/lsp_cli_parity.rs sweep is untouched. Closes issue #354. (PR #373)
  • §FS-check.1.1.9, §FS-fmt.2.3, §REQ-no-wrong-citation.1, §REQ-backwards-compatibility.5, §DF-escape-position-is-not-a-citation: an ID-shaped token in a <§> escape position is no longer a citation, in either strict mode. Under [reference] strict = false the bare-token pass recognized it, so grund check raised unknown reference on an illustration and the only fix its hint named — write the escape — was already in the file; following the hint changed nothing and printed the hint again. The scaffold grund init writes teaches the escape by using it, so a repository in the compatibility mode the tool itself recommends had its first command produce a tree its second command rejected. grund fmt --marker --write had the mirror defect and spliced a marker inside the escape brackets, turning an inert illustration into a live dangling citation in both modes; a tree that ran it already carries <§>§ and <§>[§, which this change stops making and does not repair, so grep for those two spellings. The exclusion is total rather than an error withheld, so one verdict moves for an existing non-strict tree: a declaration whose only mention was an escape of its own real ID loses that inbound count, grund refs stops listing the site, and the declaration may earn declared but never cited or leave its file ungrounded — every finding that replaces the old verdict names its location and the action to take, at the declaration heading or at the file that owes a citation. declared but never cited and the lost grund refs row are warnings, so they stand in place of the success marker (§FS-check.2.1.3) without moving the exit code; where [reference] require_grounding is enabled the exit code does move, because an ungrounded unit is an error (§FS-check.3.6.3) — such a tree goes from 0 to 1. (PR #335)
  • §AR-ci.10, §AR-ci.10.1, §AR-ci.10.2: a test fixture's temporary root is unique by construction and held exclusively, so no reading of the wall clock can make two fixtures share one tree. tests/integration/functional_spec_coverage.rs named its root from one SystemTime::now() reading in nanoseconds, and libtest runs that binary's six fixtures as threads of one process, so the reading was the whole of the identity: two cases inside one tick of the platform's clock resolution named the same directory and the first to finish removed the tree the second was scanning. The macOS leg of the matrix failed on a file the suite had written itself and went green on the rerun. Nothing grund ships moves. (PR #305)
  • §FS-rules.4.1, §FS-rules.9.1, §FS-init.2.3.5.10, §FS-errors.3.7: grund init resolves a rule object kind pinned at a workspace member, and stops withholding the managed-block write over one it cannot resolve. init and the managed-block drift pass now build the namespace vocabulary grund check already builds, from the workspace the run's own config declares, so workshop/OP resolves wherever the run holds that workspace — the workspace root included, where grund check accepted the sentence and grund init refused it in the same directory. In a scope that holds no workspace, a member-scoped run, the rule is unverifiable here rather than invalid: it is still reported at its own heading with code invalid-rule and the run still exits nonzero, but the block is written and the bullet rendered exactly as authored, so the member's AGENTS.md is byte-for-byte what a run holding the whole workspace would write. This ends an upgrade deadlock: grund check <member> reported the managed block out of date and named grund init as the remedy, and grund init <member> refused over the rule and wrote nothing, with no way out from inside the member. The rule-site message takes a wording migration (§FS-errors.3.7) — the legacy unknown kind … in namespace … reason stays a contiguous prefix through 0.14.x and the true unknown project alias … clause is appended — so code == "invalid-rule" is what to match. A member-scoped run does not climb to its parent workspace to resolve the alias; that stays §FS-workspace.5.1's boundary and §DF-subproject-namespaces.3.6's deferred design. Closes issue #277. (PR #334)
  • §FS-distribution.1.1, §FS-distribution.1.1.1, §FS-distribution.1.1.2: the pre-release name guard reads crates.io ownership from the registry's own owner record instead of the repository field the package declares about itself. A repository move no longer deadlocks the release that would correct that field, and copying one of this project's repository URLs into an unrelated crate no longer passes as ownership. A crate that exists and cannot be proved this project's now stops the release with a diagnostic saying which of the four things went wrong — untrusted owners, unreadable owner data, a failing owner endpoint, or a failed request — rather than falling back to unauthoritative data. npm and PyPI keep the repository-metadata rule and its diagnostics unchanged. (PR #279)
  • §FS-values.8: grund fmt --cross-refs no longer rewrites the citation bytes of a form grund check refuses for aiming at value authority, which would have turned it into a Markdown link and carried the refusal away with it. A repository that hand-wrote the named binding form and ran --write before this release already has link-form lines that are not bindings; there is no migration for them, so look for them by hand. (PR #278)
  • §FS-values.9.1: the entry above reaches a repository that uses neither value key, because the refusal it protects exists under marker authority too. With a §FS-values.2.4 marker present, a delimited form aimed below one of the marked root's components — a path such as .1.0 — is now left alone: fmt --cross-refs --check stops reporting markdown link for it and can exit 0 where it exited 1, and --write rewrites one line fewer. No other surface moves. (PR #278)
  • §FS-cli.2.2, §FS-check.3.12, §FS-id.2.3, §AR-ci.5.2: grund --help fits the one screen its own specification sets at 24 lines and no row runs past 100 columns; a forbidden-citation error now ends with the repair §FS-check.3.12 specifies — — re-point the citation or downgrade it to a plain Markdown link — and a rule sentence's prohibition reuses those bytes (§FS-rules.7.5); grund id --explain offers the H1 hint for any single-file kind whose file holds the kind's single declaration rather than only one literally named GRUND; and docs/benchmarks.md stops promising that pull-request CI fails on Ir growth, which no limit is wired up to do. (PR #288)
  • §FS-distribution.4.2.2, §FS-distribution.4.2.3.1: the release guard reads the landed half of an attribution pair, so a release below the one the tree's own diagnostics name is refused where it was allowed. A verdict that moved prints both of its releases on one line — unchecked in grund <prior>, an error in <release> — and only the first half had a row in the guard's vocabulary, so the floor stayed at the earlier release and a tree could be cut below the release its own messages say the verdict moved in. Two shipped rules print that shape, the local-section-citation attribution (§FS-check.3.24.1) and the index rule, so the gap has been open since 0.12.0. The bare clause is a substring of both named-error tenses, so it is read only where it opens a clause — at the start of a line, or after a comma, semicolon or colon — and becomes, became and any other verb in front of it keep spelling their own direction, which is why a tense added later cannot silently read as landed. No message moves: not one emitted byte, severity, code or exit status changes, and this tree's own release window is unmoved at 0.15.0. Closes issue #340. (PR #341)
  • §AR-workspace.9.1.5, §AR-workspace.9.5, §AR-workspace.9.1, §AR-workspace.9.1.4: UPDATE_EXPECTED=1 refreshes expected.repo, and a refresh pass says what it covered. The refresh path wrote the exit, stdout and stderr goldens and returned before the final-tree surface was reached at all, so a stale expected.repo golden survived a green refresh and then failed the very next compare pass — and the pass returned the same verdict for every case whether it rewrote a golden or not, so its ok carried nothing about the surfaces it did not cover. A refresh now writes the tree golden too: the produced file's bytes and its mode, byte-for-byte and unfolded, because a tree file is compared as raw bytes and need not be text at all. It also prints the surfaces it covered, the count of cases it covered them over, and every golden file it wrote or removed — straight to the stderr handle, which libtest's capture does not hold, so a passing refresh is readable without --nocapture. Every surface became compare-then-write, so a refresh that changes nothing now leaves even the mtimes alone. Who this breaks: only a contributor of this repository, and only in refresh mode. The documented command is now destructive with no second opt-in — it removes a golden file the run no longer produces, and the directory that removal empties, inside the 74 tracked expected.repo trees — so the diff of a refresh is the thing to review. The blast radius is a working tree, git checkout -- tests/e2e examples reverts all of it, and the new accounting names every file it moved before you reach git diff. Closes issue #350. (PR #365)