Skip to content

grund 0.9.0

Choose a tag to compare

@github-actions github-actions released this 13 Aug 09:47
· 2677 commits to main since this release

Added

  • §AR-ci.9: a fissile pre-commit hook gives §AR-core-module-layout.3's "below 500 lines of code" its first enforcement. The rule has been stated since the core/CLI split but never checked, and files had reached eight times it — crates/grund-core/src/tests.rs was 4,111 code lines in a single mod tests. Budgets live in .agents/fissile.toml, per file type, counting code lines only so doc-comment citations are never taxed; soft overflows warn, hard overflows fail the commit. The tests/ Python suite that gates this repository's own scripts is measured too, on the Rust test budget but with its own guidance, since advice to add an include!() line is meant for a crate. Adoption is a ratchet rather than a freeze: every file already over budget is pinned at today's size, with a reason and the trigger that retires it, in the registry matching its severity — docs/file-size-agent-exceptions.toml for soft, docs/file-size-human-exceptions.toml for hard. Each entry declares kind, which fixes what its reason must establish: structural states the architectural constraint that makes splitting the file illegal or harmful and never expires (until = "indefinite", enforced), while deferred names the boundary that is missing and what must exist before the split can happen. Three files are structural — skills/grund-init/SKILL.md, crates/grund-core/src/model.rs, crates/grund-cli/src/cli_help.rs — and 20 carry debt across 32 registry entries, a distinction fissile audit now reports as two separate counts. Keeping them apart is the point — a file that is merely large should not be able to borrow the language of one that must stay whole. So the tree starts clean at both tiers, nothing is blocked now, and one line past a recorded ceiling brings the finding straight back. A file over the hard budget is over the soft one too, and a deferred hard entry leaves that soft finding standing on purpose, so such a file needs an entry in each registry; rather than copy the argument across, the human registry carries it and the soft twin records the same ceiling, points at the registry that reasons, and retires when it does — one file, one argument, with no stale half for a reader to trust. A structural hard entry needs no twin, since it silences both tiers itself. Entries carry no EX-NNN id: what identifies one is the registry it lives in and the path it accepts, the same pair fissile leads its diagnostics with, and a slug on top of that is a second name that can go stale. fissile validates the sizes but not what a reason may claim, so tests/test_file_size_exceptions.py enforces the rest: structural entries never expire, a deferred until must name a condition rather than a bigger number ("revisit if it passes 500" is rejected as a budget wearing the word "until"), twins agree with their originals and appear only where a deferred entry leaves a soft finding standing, the registry schema version is pinned so an unmigrated upgrade fails by name, and every recorded path still exists, so a rename cannot strand an entry pointing at nothing. Per §AR-ci.1 the hook is in .pre-commit-config.yaml and CI installs fissile 0.5.0 — whose registry schema is fissile_exceptions_version = 2 — so pre-commit run --all-files enforces the same budgets remotely. PR #59, PR #60

  • §AR-ci.8: CI gains a commit-messages job that rejects AI-tool attribution boilerplate — an assistant co-author trailer, a "generated with" marker, a session-link trailer — in the commit messages a push or pull request contributes. The check already existed as a pre-commit hook bound to two stages, but §AR-ci.1's parity promise is delivered by pre-commit run --all-files, which reproduces only the stages whose input is a file list; the commit-msg stage takes a message file and was therefore absent from CI, leaving a trailer that touches no tracked file with no remote gate at all. Eleven such commits had reached main. The job scans base..head on a pull request and before..sha on a push, narrowing to the tip commit when the base is unresolvable after a force-push, and runs without a Rust toolchain so it reports in seconds. scripts/check_no_claude_attribution.py replaces the shell script and serves all three surfaces — staged files, one message file, a revision range — from a single pattern set, kept narrow so ordinary prose about Claude stays legal. §AR-ci.1 now records the --all-files limitation so the next stage-bound hook does not repeat the gap. PR #58

Changed

  • Schema: §FS-config.1 / §DF-config-file-location: config discovery now probes two names at every level of the upward walk — the bare root-visible grund.toml first, then .agents/grund.toml — and grund init generates the bare one (§FS-init.2.4). The .agents/ wrapper earned its keep at a repository root, where it keeps agent tooling off a root already carrying Cargo.toml and package.json; it stopped earning it at workspace members, where it meant one member/.agents/ directory per member holding exactly one file one level down, and where the layout was not merely conventional — a member root was partly identified by carrying .agents/grund.toml, so the nesting was load-bearing in the workspace-member rendering of §FS-init.2.3.4.15 as well as in discovery. One uniform rule replaces the special-casing: root and members each pick the form that suits them, a workspace may mix the two, and a bare member config shadows an ancestor's .agents/ one exactly as a nested .agents/grund.toml always did. Nothing existing breaks — no key changes meaning, no file becomes invalid, and there is no grund_config_version bump (§FS-config.5); a config written for the old rule is read identically by the new one, and init never overwrites a config it finds under either name, so re-running it after this change cannot produce a redundant pair. What does change is the default a new project meets, and the reason is discoverability across several workspaces rather than tidiness within one (§DF-config-file-location.2.4). .agents/ is a dot-directory — hidden by ls, by editor file trees, by shell globs — so under that layout the question "is this a grund workspace?" had no answer a reader could see: you had to already know to look inside a hidden directory, or run a command per candidate. With one repository that cost is paid once and forgotten, which is why the original choice looked right; with a workspace root and its members, or a dozen sibling checkouts of which some are grounded and some are not, every one of them looks un-grounded and the property a reader most needs at a glance is the one the layout hides. A root grund.toml answers it from the listing, the way Cargo.toml answers "is this a Rust crate" — and a tool that supports both forms while generating the one that needs a directory created for it was recommending the other by omission. Everything that asked "does this directory have .agents/grund.toml" now asks whether it has either name, and where the answer feeds a diagnostic, the diagnostic names the file that was actually read. Moving an existing config to the root is a git mv with no other edit, and never required. This repository moved its own. Schema: two managed blocks bump, each for a taught-workflow change rather than wording. The grund integrations agent-guidance block goes to v4 — its self-scoping texts gated on "repositories with a .agents/grund.toml", which a bare-config repository does not match on a literal reading (§FS-integrations.4.3); run grund integrations --write to refresh. The AGENTS.md init block goes to v6 → v7 — its namespace rule tells an agent to give a new subproject its own config, and that instruction now names the bare form init generates (§FS-init.2.3); grund check reports a v6 block as outdated and grund init re-renders it. Without the bump an agent reading a stale block would keep creating configs in the form the tool no longer generates, with nothing saying so (§GOAL-no-silent-breakage). The clickable-citation integrations are part of the same sweep: the terminal resolver and the VS Code extension each found the config root by probing .agents/grund.toml alone, so every repository on the generated bare form had unclickable citations; both now climb on either name (§FS-integrations.3.1, §FS-integrations.3.2), and grund integrations --write refreshes them by content comparison without a further block bump. Resolves issue #48. PR #61

  • §FS-check.4.3 / §FS-config.1.1: when one directory carries both names, the bare grund.toml wins and grund check warns about the .agents/ file it ignored — warning: .agents/grund.toml is ignored — grund.toml takes precedence; delete one, a CLI-level line on stderr that leaves the exit code alone. The form the tool generates is the form that governs, so a project never has to hold one rule for the file grund writes and a contradicting one for the file grund reads; it is also what a user reaching for a root grund.toml means, since a repository acquires the pair only when someone deliberately puts one beside an existing .agents/ config. The warning is what makes that order safe to state — the losing file is never silently ignored, so a config quietly replaced is reported at the first check. It is a warning and not an error because the pair is the ordinary transient state of a migration in either direction, and making the intermediate step of a supported move a hard failure is the opposite of what a migration aid should do. grund config validate and grund config show report it too (§FS-config.4.1, §FS-config.4.2) — those are the surfaces a user reaches when the answer to "why is my config not taking effect" is that grund is reading the other file. Six e2e fixtures turned out to be carrying exactly this pair — dead root copies left behind on 2026-05-11 when the fixtures' configs moved into .agents/, one of them stale enough to disagree with the file that was actually read. The new warning surfaced four of them on its first run (the other two are cases that exit before any warning is emitted) and all six are deleted here. They are also the strongest evidence against this tie-break, and the reason it is recorded rather than assumed (§DF-config-file-location.4): every one of them predates dual discovery, when a bare grund.toml was neither generated nor read, so they are artifacts of a form that was never live rather than files a user wrote on purpose. PR #61

  • §FS-config.4.1: grund_core::validate_config now returns Result<Config> instead of Result<()>, so a caller can print the non-fatal findings the new config_warnings reports without loading the config twice. Embedders matching on Ok(()) need Ok(_). It is effective_config under the name of the question grund config validate asks and delegates to it rather than repeating the body. print_config_warnings is exported alongside, so the published CLI and the deprecated grund_core command adapter (§AR-bindings.2) cannot drift on the warning: prefix or the stream it goes to. PR #61

  • §AR-core-module-layout.1: split the three largest files into category siblings, a pure refactor with no change to CLI output, diagnostics, scan behavior, or template bytes (§AR-core-module-layout.2). crates/grund-core/src/tests.rs (4,111 lines, one mod tests) becomes thirteen tests_* modules plus a shared tests_support; crates/grund-cli/src/cli.rs (1,735 lines) becomes one file per command, each now carrying the §FS- citation for the command it implements; and the deprecated config command adapter moves out of crates/grund-core/src/config.rs into config_cmd.rs, taking that file from 936 to 732 lines. Because both crates are assembled by include!, the moves needed no module, visibility, or import changes. The 159 tests are unchanged and still pass. Two of the new modules import tests_support for cases that are all #[cfg(unix)] — the resolver is a POSIX shell script — so those imports are gated the same way; unguarded they are dead code on Windows, which -D warnings rejects. PR #59