mdsmith
Esc
    v0.54.0 GitHub

    Markdown conventions

    Built-in Markdown conventions, the rule presets each one applies, and how user config layers on top via deep-merge.

    A convention is an opinionated bundle of rule settings that pairs a Markdown flavor with a set of style choices. Setting convention: at the top of your .mdsmith.yml selects one of the built-in bundles; the rule presets in that bundle are applied as a base layer beneath your own rule config. It answers “what kind of Markdown does this project write?” with one config knob instead of eight.

    A convention is distinct from a flavor. Flavor is a property of the renderer (CommonMark, GFM, goldmark — what the parser interprets). Convention is a property of the project (the team’s writing choices among forms the renderer treats equally). See the concepts doc for the full picture and where the concepts overlap.

    # Selecting a convention

    convention: portable

    That single line pins a flavor and a curated set of style-rule settings. convention: is a top-level config key, sibling to rules:, kinds:, and overrides:. An unknown name is a config error.

    Built-in values: portable, github, obsidian, plain, no-llm-tells, slidev, and the four <linter>-parity conventions below. The key is optional; omit it for no convention.

    You may also set flavor: inside markdown-flavor alongside convention:. If both are set, they must agree — a convention that pins a flavor (e.g. portable requires commonmark) rejects a conflicting flavor: at config load. Conventions that pin no flavor (slidev, no-llm-tells) do not enforce this check.

    # Built-in conventions

    # portable

    Markdown that renders the same in every CommonMark parser. Selects flavor: commonmark and turns on the strict-style rules with their recommended defaults.

    RuleSetting
    markdown-flavorflavor: commonmark
    no-inline-htmlenabled
    no-reference-styleallow-footnotes: false
    emphasis-stylebold: asterisk, italic: underscore
    horizontal-rule-stylestyle: dash, length: 3, require-blank-lines: true
    list-marker-stylestyle: dash
    ordered-list-numberingstyle: sequential, start: 1
    ambiguous-emphasismax-run: 2

    # github

    Markdown that renders well on github.com. Selects flavor: gfm and keeps the style rules light: the inline-HTML allowlist permits <details> and <summary>; emphasis and list-marker style are pinned for consistency; the rest of the strict rules stay off.

    RuleSetting
    markdown-flavorflavor: gfm
    no-inline-htmlallow: [details, summary]
    emphasis-stylebold: asterisk, italic: underscore
    list-marker-stylestyle: dash

    # obsidian

    Markdown written in an Obsidian vault. Selects flavor: gfm and turns on the Obsidian-specific validations — MDS027 resolves [[Page]] wikilink targets workspace-wide, and MDS067 checks every [!type] callout against the Obsidian type set.

    RuleSetting
    markdown-flavorflavor: gfm
    cross-file-reference-integritywikilinks: true, wikilink-style: obsidian
    callout-typeenabled (12 base Obsidian types and aliases)

    Standard style rules stay at their defaults so an Obsidian vault behaves like a GFM project unless the team layers more rules on top.

    To run these checks inside the editor, install the Obsidian plugin . It hosts the same engine as a WebAssembly runtime.

    # plain

    Markdown that survives cat. The rendered output should look about the same as the source viewed in a plaintext reader. Same activations as portable, plus allow-comments: false on no-inline-html so HTML comments do not leak through as literal <!-- ... --> text.

    Three additional rules (forbid */_ runs, require indented code blocks, prefer bare URLs) do not exist yet. When they ship, plain gains them and diverges from portable.

    # <linter>-parity

    Four conventions match a peer linter’s default rule set, for benchmarks or a fast peer-equivalent gate. Each picks flavor: gfm and leaves MDS034 opt-in.

    ConventionPeerRules
    gomarklint-paritygomarklint20
    mado-paritymado27
    rumdl-parityrumdl41
    markdownlint-paritymarkdownlint41

    Each enables the opt-in rules its peer runs and disables the defaults it skips. Only full covers count: a partial peer mapping does not run the heavier mdsmith rule. The CI-checked sets come from the coverage matrix . All disable MDS027 (the peers’ link-fragments cover same-file anchors only), so gomarklint-parity and mado-parity are parse-skip-safe. Rule tables are generated.

    # no-llm-tells

    Flags mechanical LLM-prose tells in CI. MDS056 blocks a curated vocabulary of LLM tell words and phrases; MDS055 blocks the banned sentence openers; MDS023 and MDS024 tighten readability budgets. The lists are sourced from slop-patterns.md ; a drift-checker test keeps them in sync.

    Pins no flavor; it does not enable markdown-flavor (MDS034). The curated words ship as inline contains:/starts: presets, which append, so a project’s terms join rather than replace them.

    # slidev

    Disables eight default-on rules that produce false positives on Slidev files, and enables slide-structure (MDS073) to validate per-slide layouts, slots, fields, and keys. Slidev uses --- as a slide separator (parsed as a setext underline); headings restart per slide. Pins no flavor.

    RuleWhy disabled
    heading-style (MDS002)--- separator parsed as setext underline
    heading-increment (MDS003)Each slide restarts at H1
    first-line-heading (MDS004)Front matter before first heading
    no-duplicate-headings (MDS005)Same title on multiple slides
    blank-line-around-headings (MDS013)Layout blocks interfere
    no-trailing-punctuation-in-heading (MDS017)Stylistic slide titles
    no-emphasis-as-heading (MDS018)Bold used for slide emphasis
    empty-section-body (MDS030)Layout-only slides have no body

    # How presets layer with user config

    Convention presets sit between built-in defaults and your explicit top-level rules. The merge order, oldest → newest, is:

    1. default — built-in defaults: rules in cfg.Rules that you did not set
    2. convention.<name> — the preset table
    3. user — your top-level rules block (rules you explicitly set in .mdsmith.yml)
    4. kinds.<name> — each kind in the file’s effective list
    5. overrides[i] — each matching override entry

    Each layer deep-merges: scalar leaves replace, maps recurse key by key, lists replace by default. The convention provides the floor; your rules: block overrides on top. mdsmith splits default and user around the convention so a convention can enable an opt-in rule without being overridden by the default’s Enabled: false.

    For example, the github convention sets no-inline-html.allow: [details, summary]. To extend the allowlist with <sub> and <sup>, write:

    convention: github
    rules:
      no-inline-html:
        allow: [sub, sup]

    Lists default to replace, so the effective allowlist becomes [sub, sup]. To keep the preset’s entries, explicitly list all of them: allow: [details, summary, sub, sup]. The lists: key is separate and always appends — see word-list files .

    # Disabling MDS034

    Convention presets apply at config load. A bool-only markdown-flavor: false entry in rules: toggles enabled without erasing preset settings — the rule is gated off but the other convention rules are untouched.

    # User-defined conventions

    The built-in conventions cover common cases. Teams that need something custom define it inline in .mdsmith.yml. The top-level conventions: key holds the map:

    conventions:
      our-team:
        flavor: gfm
        rules:
          no-inline-html:
            allow: [details, summary, kbd]
          list-marker-style:
            style: dash
          no-reference-style:
            allow-footnotes: true
    
    convention: our-team

    Each entry is a { flavor, rules } pair. The rules block uses the same schema as the top-level rules: block. To lift one out of .mdsmith.yml into its own file under .mdsmith/conventions/<name>.yaml, see the convention files reference .

    # Validation

    User-defined conventions are validated at config load:

    • flavor must be a recognised flavor string such as commonmark, gfm, or goldmark.
    • Each key under rules: must name a registered rule.
    • Each rule’s settings must pass the rule’s own schema check.

    Validation errors name the convention and the rule:

    convention "our-team" rule "no-inline-html": no-inline-html: unknown setting "allowed"

    # Reserved names

    The built-in names portable, github, obsidian, plain, no-llm-tells, slidev, and the four <linter>-parity conventions are reserved. Defining a conventions.portable entry is a config error. This keeps the built-in names stable across docs and tutorials.

    # Resolution order and layering

    The lookup checks user-defined conventions first, then the built-in table. A collision with a reserved name is a config error — shadowing is impossible. When neither matches, the error lists both sets:

    unknown convention "bogus" (valid: github, gomarklint-parity, mado-parity, markdownlint-parity, no-llm-tells, obsidian, our-team, plain, portable, rumdl-parity, slidev)

    User-defined conventions apply as a base layer, like the built-ins. A top-level rules: entry overrides the convention preset; mdsmith kinds resolve <file> labels user-convention layers with a (user) suffix.

    # Inspecting an effective convention

    mdsmith kinds resolve <file> shows the merge chain for every rule, including the convention.<name> layer. Use it to confirm which value won and where it came from.