mdsmith
Esc
    v0.52.0 GitHub

    Flavor, rule, convention, kind

    How “flavor” (a property of the renderer), “rule” (a single check), “convention” (a project-wide bundle), and “kind” (a per-file role tag) differ in mdsmith, the cases where they overlap, and how the four concepts compose.

    mdsmith uses four related but distinct ideas to constrain Markdown. A reader who is unsure which mechanism they want — or whether they want more than one — should read this page first.

    # TL;DR

    AxisFlavorRuleConventionKind
    What it isA renderer’s grammarA single lint checkA project-wide bundle of rulesA per-file role tag with rules
    Source of truthAn external spec or implementationAn mdsmith rule packageBuilt-ins in the codebase; user-defined in configThe user’s .mdsmith.yml
    Question answeredWill the renderer interpret this?Does this match this rule?What kind of Markdown do we write?What role does this file play?
    ScopeProject-wide (one)Per-featureProject-wide (one)Per-file (zero or many composed)
    Exampleflavor: gfm on MDS034MDS044 horizontal-rule-styleconvention: portablekinds: { plan: { rules: ... } }

    # What each concept does

    # Flavor

    A flavor describes what a renderer does. CommonMark won’t auto-link a bare URL. GFM doesn’t render footnotes. Goldmark recognizes heading IDs but the default GFM renderer does not. These are facts about the parser/renderer, not opinions a team can change. Flavor is read by MDS034 only.

    # Rule

    A rule describes one constraint. Most rules walk the AST and emit diagnostics when a pattern matches. Some rules also auto-fix. Each rule has an ID, a name, a category, and a settings schema. Rules exist independently; they don’t know about conventions or kinds.

    # Convention

    A convention is a named bundle that pairs a flavor with a set of rule presets. Selecting a convention applies both: MDS034 runs against the named flavor, and the named rule presets are applied as a base layer beneath the user’s own rule config. The built-in conventions are portable, github, obsidian, plain, no-llm-tells, and four <linter>-parity conventions. Each one runs the rule set a peer linter (gomarklint, mado, rumdl, markdownlint) enables by default, so mdsmith check matches that peer’s work for a like-for-like comparison. A project can also define its own convention inline in .mdsmith.yml or as a file under .mdsmith/conventions/. See the conventions reference and convention files .

    # Kind

    A kind is a named bundle of rule settings tagged to specific files via front-matter kinds: or glob kind-assignment:. Files of different kinds get different settings. A document can carry zero or many kinds; settings compose. See the file-kinds guide .

    # Why convention is not flavor

    A flavor today corresponds to a renderer. Promoting “portable” to a flavor would suggest there is a renderer by that name, which there is not. Flavor stays factual — does this parse and render? Convention is opinionated — among forms the renderer treats equally, which do we prefer?

    Three concepts, three words: flavor (renderer), convention (project), kind (file). Each word telegraphs scope.

    # Why convention is not kind

    Convention and kind both bundle rule settings, both deep-merge, and both surface as labeled provenance layers. The difference is the selector.

    Convention selector says “this project, always.” One per project. Project-homogeneous.

    Kind selector says “files matching front-matter or glob.” Many per project. File-heterogeneous.

    A convention also implies a flavor. The renderer is a project property; you cannot sensibly target two renderers in one project. A kind doesn’t, and shouldn’t, since the renderer is fixed by where the project publishes.

    The clearest framing:

    • Convention answers “what kind of Markdown does this project write?” (one answer)
    • Kind answers “what kind of file is this?” (zero or many answers)

    # The fuzzy middle: features that parse but do not render

    Some syntax parses on every flavor but is only recognized as a feature on some. A GFM-style table inside a CommonMark file parses fine. The renderer emits a paragraph of literal pipes instead of an HTML table. MDS034 still flags it because the renderer does not render the syntax as the named feature. The diagnostic uses the wording “<flavor> does not interpret <feature> as a feature” to make this explicit.

    Bare URLs are the cleanest case where flavor and rule overlap legitimately.

    MDS034 with flavor: commonmark flags them because CommonMark won’t auto-link them. MDS012 (no-bare-urls) flags them on style grounds regardless of flavor. Both diagnostics fire on the same source for different reasons.

    That overlap is intentional. A team using flavor: gfm may still prefer explicit <https://example.com> for readability. MDS012 enforces that even when the renderer would auto-link.

    # How the four compose

    The merge order, oldest → newest:

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

    A team can write:

    convention: portable
    kinds:
      plan:
        rules:
          max-file-length: { max-bytes: 50000 }

    Plan-tagged files get portable’s preset (over defaults), the plan-specific cap on top, then overrides. Deep-merge handles the rest. The split of default and user around convention is what lets a convention enable an opt-in rule like MDS034 that is EnabledByDefault: false. Without the split, the default’s Enabled: false would land on top of the convention’s Enabled: true and silently disable the rule the user just asked the convention to enable.

    # Practical guidance

    Pick a flavor when the project targets a specific renderer (GitHub, MkDocs, a static site generator, plaintext readers). Pick rules when the team wants to enforce one specific constraint. Pick a convention when you want a curated bundle for the whole project. Pick a kind when files of different classes need different settings.

    If you’re seeing a diagnostic and aren’t sure which mechanism produced it, run mdsmith kinds resolve <file> . The per-rule merge chain shows whether the value came from a convention, default, kind, or override.