index.md
July 19, 2026 · View on GitHub
Build and test reference for mdsmith contributors. See also:
- Adding a peer linter
- Architecture audit log
- Architecture audit log archive
- Architecture audit log archive (2)
- Architecture principles
- Coverage Gate
- Design system
- File Placement
- High-Performance Go
- Merge Queue
- PGO and the uncommitted profile
- PR Fixup Workflow
- Public Markdown Library
- Release Pipeline
- Release Tooling Architecture
- Secret Rotations
- Ship and adopt new directive syntax
- Website configuration
Build & Test Commands
Requires Go 1.25+. Dev tools (golangci-lint and
gobco) build from tools/go.mod, which needs Go
1.25.8+; go.mod itself stays tool-free so
go install consumers never inherit a dev tool's
go floor.
go build ./...— build all packagesgo test ./...— run all testsgo test -run TestName ./...— run a specific testgo run ./cmd/mdsmith check .— lint markdowngo run ./cmd/mdsmith fix .— auto-fix markdowngo tool -modfile=tools/go.mod golangci-lint run— run lintergo vet ./...— run go vet
Project Layout
Follows the standard Go project layout:
cmd/mdsmith/— main entry point.internal/— private packages.internal/rules/<rule-name>/— rule code (e.g.paragraphstructure/).internal/rules/MDS###-<rule-name>/— rule README and good/bad fixtures (e.g.MDS024-paragraph-structure/).testdata/— shared markdown fixtures.pkg/goldmark/— vendored goldmark fork.
Code Style
- Follow standard Go conventions (gofmt, goimports).
- Use golangci-lint for linting.
- Keep functions small and focused.
- Error messages: lowercase, no trailing punctuation.
- Prefer returning errors over panicking.
Defensive Code
Add a defensive branch only when you can drive it red/green. Write the failing test first. Then add the code that takes the branch.
Allocation Budget
A rule's Check allocates ≤ 10 times per call on
representative input. Enforced by
internal/integration/alloc_budget_test.go; most
rules allocate 0–6.
- Walk
f.Lines/f.ASTdirectly. - Prefer
bytes.IndexByte/bytes.Containsoverregexpfor fixed searches. - Compile every
regexp.Regexpat package scope. - Pre-size slices with
make([]X, 0, n). - Reuse loop-local buffers via
buf = buf[:0]. - Return
nil, not an empty slice, on no diagnostics.
Test Fixtures
Rule test fixtures live in
internal/rules/MDS###-<rule-name>/ (e.g.
MDS024-paragraph-structure/). Each rule has good/
and bad/ examples (or good.md / bad.md).
Good fixtures must pass all default-enabled rules
plus the rule under test. Opt-in rules are skipped:
a good MDS001 fixture need not also satisfy MDS043.
When a good fixture uses non-default settings,
override them in .mdsmith.yml so mdsmith check .
also passes. Bad fixtures are excluded via the
ignore: section.
When adding or changing a rule, add both:
- Unit tests in
rule_test.go(inline markdown, fast red/green). Userequirefor preconditions andassertfor checks;Same/NotSamefor pointer identity. - Fixture tests under
internal/rules/MDS###-<rule-name>/with YAML frontmatter specifying expected diagnostics. Discovered automatically byinternal/integration/rules_test.go.
Config Merge Semantics
Layered config (defaults → kinds → overrides) is deep-merged rule by rule:
- Maps merge key by key; siblings set in earlier layers survive partial overrides.
- Scalar leaves are replaced wholesale.
- List settings replace by default. Opt into
appendby implementingrule.ListMerger.SettingMergeMode(key). The placeholder vocabulary is the canonical example. - A bool-only layer (
rule-name: false) togglesenabledwithout erasing inherited settings.
New list-typed settings must document the choice
next to their ApplySettings handler.
Generated Sections
Content between <?directive ... ?> and
<?/directive?> markers is auto-generated. Edit
directive parameters or the source file, then run
mdsmith fix <file> — never the body by hand. Run
mdsmith merge-driver install [files...] once per
clone so generated-section conflicts resolve
automatically.