Repository navigation
grund 0.9.0
Added
-
§AR-ci.9: a
fissilepre-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.rswas 4,111 code lines in a singlemod 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. Thetests/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 aninclude!()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.tomlfor soft,docs/file-size-human-exceptions.tomlfor hard. Each entry declareskind, which fixes what its reason must establish:structuralstates the architectural constraint that makes splitting the file illegal or harmful and never expires (until = "indefinite", enforced), whiledeferrednames 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 distinctionfissile auditnow 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 adeferredhard 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. Astructuralhard entry needs no twin, since it silences both tiers itself. Entries carry noEX-NNNid: what identifies one is the registry it lives in and the path it accepts, the same pairfissileleads its diagnostics with, and a slug on top of that is a second name that can go stale.fissilevalidates the sizes but not what a reason may claim, sotests/test_file_size_exceptions.pyenforces the rest: structural entries never expire, adeferreduntilmust 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.yamland CI installsfissile0.5.0 — whose registry schema isfissile_exceptions_version = 2— sopre-commit run --all-filesenforces the same budgets remotely. PR #59, PR #60 -
§AR-ci.8: CI gains a
commit-messagesjob 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 bypre-commit run --all-files, which reproduces only the stages whose input is a file list; thecommit-msgstage 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 reachedmain. The job scansbase..headon a pull request andbefore..shaon 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.pyreplaces 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-fileslimitation 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.tomlfirst, then.agents/grund.toml— andgrund initgenerates 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 carryingCargo.tomlandpackage.json; it stopped earning it at workspace members, where it meant onemember/.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.tomlalways did. Nothing existing breaks — no key changes meaning, no file becomes invalid, and there is nogrund_config_versionbump (§FS-config.5); a config written for the old rule is read identically by the new one, andinitnever 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 byls, 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 rootgrund.tomlanswers it from the listing, the wayCargo.tomlanswers "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 agit mvwith 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. Thegrund integrationsagent-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); rungrund integrations --writeto refresh. TheAGENTS.mdinit 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 forminitgenerates (§FS-init.2.3);grund checkreports a v6 block as outdated andgrund initre-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.tomlalone, 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), andgrund integrations --writerefreshes 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.tomlwins andgrund checkwarns 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 rootgrund.tomlmeans, 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 firstcheck. 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 validateandgrund config showreport 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 baregrund.tomlwas 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_confignow returnsResult<Config>instead ofResult<()>, so a caller can print the non-fatal findings the newconfig_warningsreports without loading the config twice. Embedders matching onOk(())needOk(_). It iseffective_configunder the name of the questiongrund config validateasks and delegates to it rather than repeating the body.print_config_warningsis exported alongside, so the published CLI and the deprecatedgrund_corecommand adapter (§AR-bindings.2) cannot drift on thewarning: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, onemod tests) becomes thirteentests_*modules plus a sharedtests_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 deprecatedconfigcommand adapter moves out ofcrates/grund-core/src/config.rsintoconfig_cmd.rs, taking that file from 936 to 732 lines. Because both crates are assembled byinclude!, the moves needed no module, visibility, or import changes. The 159 tests are unchanged and still pass. Two of the new modules importtests_supportfor 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 warningsrejects. PR #59