Convention files under `.mdsmith/conventions/`
Each file under .mdsmith/conventions/ declares one user convention. The basename is the convention name; the file body carries a flavor: plus a rules: map. Sits alongside inline conventions.<name>: in .mdsmith.yml.
A convention file is a YAML file under
.mdsmith/conventions/ whose basename is the
convention’s name and whose body is the full convention
bundle. One file per convention, no nesting. The
directory sits next to .mdsmith.yml at the workspace
root.
.mdsmith.yml # unchanged
.mdsmith/
kinds/
audit-log.yaml
conventions/
portable-strict.yaml
long-form-docs.yamlUse convention files when the conventions: block has
grown large. Each rule edit dirties the same
.mdsmith.yml as every other config change. Splitting
conventions into one file each isolates the history. The
read path shortens too: open portable-strict.yaml to
see the whole portable-strict convention.
Built-in conventions (portable, github, plain, and
the rest listed in the
conventions reference
) stay compiled
into the binary. Convention files hold only the
conventions you define.
# File shape
The file body matches the inline conventions.<name>:
body — a UserConvention
: a flavor:
key plus a rules: map. The rules: block uses the same
schema as the top-level rules: block. A key outside
that set is a config error naming the key and file.
# .mdsmith/conventions/portable-strict.yaml
flavor: commonmark
rules:
line-length:
max: 72
no-bare-urls: true
no-inline-html:
allow: [details, summary]A convention must declare a flavor: to be selectable.
The flavor must be a recognised flavor string such as
commonmark, gfm, or goldmark. Each key under
rules: must name a registered rule and pass that rule’s
own schema check, exactly as an inline convention does.
Select the convention the same way as any other — with
the top-level convention: key in .mdsmith.yml:
convention: portable-strictThe convention: selector stays in .mdsmith.yml; it is
not externalized. A convention file only supplies the
bundle, never picks it.
# Basename rule
The convention’s name is the basename minus extension.
The basename must match [a-z][a-z0-9-]* — lower case,
starting with a letter, with optional hyphen-separated
segments. The rule applies only to filenames (OS case
folding, path safety); inline conventions.<name>: keys
stay unvalidated.
Both *.yaml and *.yml are scanned. Two convention
files with the same basename across the two extensions is
a config error naming both files.
Subdirectories under .mdsmith/conventions/ are
rejected, as are symlinks. A file larger than 1 MB is
rejected. One convention per file, flat layout.
#
Composition with .mdsmith.yml
conventions.<name>: blocks inside .mdsmith.yml remain
a first-class source. A project can mix inline and
file-defined conventions freely.
The same convention name declared in both a file and inline is a config error naming both sources. The two sources do not merge — a merged convention would defeat the “read one file to know one convention” property convention files ship.
A name colliding with a built-in convention (portable,
github, plain, and the others in the
conventions reference
) is a config
error: the built-in name is reserved. This keeps the
built-in names stable across docs and tutorials.
The top-level convention: selector and the
overrides:, kinds:, and ignore: blocks all stay in
.mdsmith.yml. A convention: <name> entry references a
convention by name — inline or file convention — with no
extra wiring.
# Splitting an inline convention
To move an existing inline convention into its own file,
cut the body under conventions.<name>: and drop the
inline entry. Take this .mdsmith.yml:
conventions:
our-team:
flavor: gfm
rules:
no-inline-html:
allow: [details, summary, kbd]
list-marker-style:
style: dash
convention: our-teamWrite the body to a file named for the convention, and
delete the conventions: block:
# .mdsmith/conventions/our-team.yaml
flavor: gfm
rules:
no-inline-html:
allow: [details, summary, kbd]
list-marker-style:
style: dashThe convention: our-team selector stays in
.mdsmith.yml. Move the body, do not copy it: declaring
the same name both inline and in a file is a config error
naming both sources. The effective rules are byte-equal
either way.
# Audit
mdsmith kinds resolve <file> prints the active
convention and the file that defined it, so you can jump
straight to the right source:
file: docs/guide.md
effective kinds:
(none)
convention: portable-strict (user) defined-in .mdsmith/conventions/portable-strict.yamlThe (user) tag marks a user-defined convention.
Built-in conventions carry no tag and no defining-source
path — they are compiled into the binary.
The JSON shape (--json) carries a convention object
with name and source-path keys. That parallels each
resolved kind’s source-path, so editor integrations can
key off a stable field.
mdsmith kinds resolve <file> also shows the full merge
chain for every rule, including the convention.<name>
layer. Use it to confirm which value won and where it
came from.
See the conventions reference for the built-in bundles, the merge order, and how presets layer with your top-level rules.