chore(repo): consolidate tooling configuration into .config/ - #53
Merged
Conversation
The root mixed normative content, project documents and tool configuration, and nothing distinguished the files that must be at the root — Git, EditorConfig, Task, none of which accepts a config-path flag — from the ones that were merely there by default. Every new linter arrived at the root and the set only grew. Adopt the convention musher-dev/development-container and musher-dev/platform already share: bucket by concern under .config/, no leading dot on filenames, and every caller names its config with the tool's own flag. lefthook.yml stays at .config/'s top level because lefthook's config search does not descend past .config/lefthook.* — bucketing it would stop every hook running, silently. Enforce it rather than document it. Platform shipped the prose version of this rule with no gate, and in that state two non-configs accreted inside the directory and four lint tools ran on defaults. tools/src/config.ts implements CFG-01..CFG-08 with the same codes the sibling repositories report, so the three share a vocabulary for reporting a breach even though this one runs the check from Bun rather than Python. Moving cspell surfaced the hazard the rule is about. cspell resolves ignorePaths against globRoot, which defaults to the config file's directory, so the move re-rooted every repo-relative ignore against .config/spelling/ where none of them matched — the check kept passing while covering strictly less. globRoot is now set explicitly. Verified by file count, not by a red build: a check that has stopped enforcing anything does not fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Two more tool configs into .config/, both passed by explicit path. actionlint ran on defaults. Its config now declares an empty self-hosted-runner label list and an empty config-variables list, which turns `vars.TYPO` from an empty string that silently changes what a job does into a build failure -- verified by planting an undeclared variable and watching it fail. markdownlint is new. Five relaxations, each with its reason in the file: MD033 allows `a` and `br` because the spec.md anchors are load-bearing (conformance fixtures link to them) and `br` is the only in-cell line break; MD036 because a bold lead-in is this repository's house style in every ADR; MD060 because it is presentation, has no autofix, and adopting it would mean hand-realigning 263 table pipes across three normative spec.md files for no rendering difference. pull_request_template.md is excluded from the glob: it is a form fragment GitHub renders into a textarea, so MD041 would be asking it to grow a title. The rest of the backlog was small enough to fix outright, and markdownlint made both edits itself: two bare e-mail addresses are now autolinked, and three double blank lines in spec.md are single. No prose changed. Both flags verified live against a path that does not exist -- exit 3 and 2 respectively, not a silent fallback to defaults. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
One 330-line file held setup, build, eighteen checks, the CI entry points and
the ledger writes. It now holds the entry points and includes the rest, which
is the shape musher-dev/development-container and musher-dev/platform already
use.
Every pre-existing task name is unchanged -- CI, the hooks and CONTRIBUTING all
call them by name. `task --list` before and after differs only by the three new
tasks. Two mechanics make that hold, and both were traps worth recording in the
files themselves:
- A relative `dir:` in an included taskfile resolves against that file's own
directory, so `dir: tools` would have silently meant `taskfiles/tools`. Every
moved task uses `{{.ROOT_DIR}}`.
- A bare `task: check:format` inside an included file resolves within that
file's namespace, as `ci:check:format`, and fails. The `ci` module anchors
each reference to the root with a leading colon.
`check` and `setup` stay in the root rather than becoming a module `default`.
An included `default` is reachable as the bare namespace, but `task --list`
renders it `check:default`, which hides the command a newcomer most needs.
Also documents the new layout: CLAUDE.md gains .config/ and taskfiles/ in the
layout block and a non-negotiable for where tool config goes, and CONTRIBUTING
gains check:config and check:md in the table of what `task check` runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
CONTRIBUTING.md, CODE_OF_CONDUCT.md and SECURITY.md are the three GitHub resolves from .github/ as readily as from the root, and moving them leaves the root holding the contract and the documents that describe it. Every link that pointed at them moved in the same change; check:links is what proves it. Three stay at the root, and not by omission: - LICENSE and NOTICE, because .github/ is not a supported location for either. GitHub's license detection reads the root only, so moving LICENSE would drop the licence from the repository page and the API, and Apache-2.0 expects NOTICE to travel with the work. - GOVERNANCE.md, which GitHub *does* support in .github/, because a normative spec.md links to it and so does every ADR. It is contract surface, and ADR 0011's own test puts it at the root: what is visible there is content you read and edit, what is dotted is machinery that operates on it. Renumbers the tooling-configuration ADR from 0007 to 0011. 0007 was already taken by the naming-conventions ADR on the commit this branch started from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
.editorconfig and devcontainer.json each stated whitespace, line endings and encoding independently, and they disagreed. .editorconfig exempts Markdown from trailing-whitespace trimming because two trailing spaces are a hard line break; devcontainer.json trimmed unconditionally. In-container VS Code was quietly reformatting prose against the rule the repository had written down. musher-dev/development-container resolves this the other way -- it deletes .editorconfig and keeps devcontainer.json -- and its own rationale names the exception this repository falls under: those settings reach VS Code inside the container and nothing else, so a project with contributors working outside it should keep .editorconfig. CONTRIBUTING documents that path, and anyone may send a patch to a public specification. So .editorconfig keeps the whitespace rules and gains the reason for the Markdown exemption, devcontainer.json drops the four duplicated keys, and the EditorConfig extension is added so in-container VS Code actually reads the file. devcontainer.json keeps what is genuinely VS Code's own: rulers, format-on-save, formatter bindings, the local schema associations. Recorded as ADR 0011 §6. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Consolidates every tool configuration into
.config/, adopting the conventionmusher-dev/development-containerandmusher-dev/platformalready share, andmakes it a build failure rather than a comment. Along the way: two new linters,
the
Taskfile.ymlsplit into modules, and the community health files moved into.github/.Nothing under
specifications/orconformance/changes behaviour, and nocommit is scoped to a family — this releases nothing.
The layout
Root keeps only what can be nowhere else:
Taskfile.yml,.gitignore,.gitattributes,.editorconfig,LICENSE,NOTICE,README.md,GOVERNANCE.md, and the two published data artifacts.tools/biome.jsonandtools/tsconfig.jsonstay with their package.The gate
tools/src/config.ts(task check:config) enforces CFG-01..CFG-08 with thesame codes the sibling repositories report from their Python
repo config check— three repositories, one convention, one vocabulary.config.test.tsprovokes every code against a throwaway tree, because a gate that cannot fail is
indistinguishable from no gate.
Two silent failures this found
cspell's
globRoot. cspell resolvesignorePathsagainst the config file'sown directory, so moving it two levels down re-rooted every repo-relative ignore
against
.config/spelling/, where none matched. The check kept passing whilecovering strictly less. Caught by comparing file counts (53 before, 53 after),
not by a red build — a check that has stopped enforcing anything does not fail.
globRootis now explicit..editorconfigvsdevcontainer.json. Both stated whitespace independentlyand disagreed:
.editorconfigexempts Markdown from trailing-whitespacetrimming because two trailing spaces are a hard line break,
devcontainer.jsontrimmed unconditionally. In-container VS Code was reformatting prose against the
repository's own rule.
.editorconfigis now the single source.New linters
config-variables: []turnsvars.TYPOfrom anempty string that silently changes a job into a build failure — verified by
planting one.
tools/. Five relaxations, each with itsreason in the file.
MD060is off: it has no autofix, and adopting it meanthand-realigning 263 table pipes across three normative
spec.mdfiles for norendering difference. The rest of the backlog was fixed — two bare e-mail
addresses autolinked, three double blank lines closed.
Every new flag was verified live against a path that does not exist, so a
silently-ignored
--configcannot hide behind a green build.Taskfile split
Every pre-existing task name is unchanged;
task --listdiffers only bycheck:config,check:mdandfmt:md. Two traps are recorded in the files: arelative
dir:in an included taskfile resolves against that file'sdirectory, and a bare
task: check:formatinside an included file resolveswithin its own namespace.
Community health files
CONTRIBUTING.md,CODE_OF_CONDUCT.mdandSECURITY.mdmoved to.github/.LICENSEandNOTICEcannot — GitHub detects a licence at the root only.GOVERNANCE.mdcould, but a normativespec.mdlinks to it and so does everyADR: it is contract surface, not a community file.
Why
Root dotfiles accumulate because every tool's README says to put them there, and
nothing distinguishes root-by-necessity (Git, EditorConfig, Task) from
root-by-default (lefthook, cspell). Platform shipped the prose version of this
rule with no gate and watched two non-configs accrete inside the directory and
four lint tools run on defaults. Rationale in
ADR 0011.
Verification
task checkgreen locally, including all three new checks.task --listdiffed before and after the split: only the three new tasks.CFG-0Nprovoked deliberately and confirmed to fire, then reverted.rm -rf .git/hooks && task setup:hooks) andobserved running from
.config/lefthook.yml, including the newconfigjob.git statusclean underconformance/; the two byte-sensitive-textparserfixtures untouched.
Known gap
YAML linting is not in this PR. yamllint is Python-only and this dev
container deliberately ships no Python or uv — its header says so. The agreed
alternative,
google/yamlfmtin lint mode, could not be installed either: thisenvironment truncates large release downloads from GitHub's asset CDN, so it
could not be run or its output evaluated even once. Adding a check whose first
real execution is in CI would break the rule this PR just wrote down. Worth a
follow-up from a machine that can run it.