|
| 1 | +--- |
| 2 | +title: Convention files under `.mdsmith/conventions/` |
| 3 | +weight: 22 |
| 4 | +summary: >- |
| 5 | + Each file under `.mdsmith/conventions/` declares one |
| 6 | + user convention. The basename is the convention name; |
| 7 | + the file body carries a `flavor:` plus a `rules:` map. |
| 8 | + Sits alongside inline `conventions.<name>:` in |
| 9 | + `.mdsmith.yml`. |
| 10 | +--- |
| 11 | +# Convention files under `.mdsmith/conventions/` |
| 12 | + |
| 13 | +A **convention file** is a YAML file under |
| 14 | +`.mdsmith/conventions/` whose basename is the |
| 15 | +convention's name and whose body is the full convention |
| 16 | +bundle. One file per convention, no nesting. The |
| 17 | +directory sits next to `.mdsmith.yml` at the workspace |
| 18 | +root. |
| 19 | + |
| 20 | +```text |
| 21 | +.mdsmith.yml # unchanged |
| 22 | +.mdsmith/ |
| 23 | + kinds/ # plan 208 |
| 24 | + audit-log.yaml |
| 25 | + conventions/ |
| 26 | + portable-strict.yaml |
| 27 | + long-form-docs.yaml |
| 28 | +``` |
| 29 | + |
| 30 | +Use convention files when the `conventions:` block has |
| 31 | +grown large. Each rule edit dirties the same |
| 32 | +`.mdsmith.yml` as every other config change. Splitting |
| 33 | +conventions into one file each isolates the history. The |
| 34 | +read path shortens too: open `portable-strict.yaml` to |
| 35 | +see the whole `portable-strict` convention. |
| 36 | + |
| 37 | +Built-in conventions (`portable`, `github`, `plain`, and |
| 38 | +the rest listed in the |
| 39 | +[conventions reference](conventions.md)) stay compiled |
| 40 | +into the binary. Convention files hold only the |
| 41 | +conventions you define. |
| 42 | + |
| 43 | +## File shape |
| 44 | + |
| 45 | +The file body matches the inline `conventions.<name>:` |
| 46 | +body — a [`UserConvention`](conventions.md): a `flavor:` |
| 47 | +key plus a `rules:` map. The `rules:` block uses the same |
| 48 | +schema as the top-level `rules:` block. A key outside |
| 49 | +that set is a config error naming the key and file. |
| 50 | + |
| 51 | +```yaml |
| 52 | +# .mdsmith/conventions/portable-strict.yaml |
| 53 | +flavor: commonmark |
| 54 | +rules: |
| 55 | + line-length: |
| 56 | + max: 72 |
| 57 | + no-bare-urls: true |
| 58 | + no-inline-html: |
| 59 | + allow: [details, summary] |
| 60 | +``` |
| 61 | +
|
| 62 | +A convention must declare a `flavor:` to be selectable. |
| 63 | +The flavor must be a recognised flavor string such as |
| 64 | +`commonmark`, `gfm`, or `goldmark`. Each key under |
| 65 | +`rules:` must name a registered rule and pass that rule's |
| 66 | +own schema check, exactly as an inline convention does. |
| 67 | + |
| 68 | +Select the convention the same way as any other — with |
| 69 | +the top-level `convention:` key in `.mdsmith.yml`: |
| 70 | + |
| 71 | +```yaml |
| 72 | +convention: portable-strict |
| 73 | +``` |
| 74 | + |
| 75 | +The `convention:` selector stays in `.mdsmith.yml`; it is |
| 76 | +not externalized. A convention file only supplies the |
| 77 | +bundle, never picks it. |
| 78 | + |
| 79 | +## Basename rule |
| 80 | + |
| 81 | +The convention's name is the basename minus extension. |
| 82 | +The basename must match `[a-z][a-z0-9-]*` — lower case, |
| 83 | +starting with a letter, with optional hyphen-separated |
| 84 | +segments. The rule applies only to filenames (OS case |
| 85 | +folding, path safety); inline `conventions.<name>:` keys |
| 86 | +stay unvalidated. |
| 87 | + |
| 88 | +Both `*.yaml` and `*.yml` are scanned. Two convention |
| 89 | +files with the same basename across the two extensions is |
| 90 | +a config error naming both files. |
| 91 | + |
| 92 | +Subdirectories under `.mdsmith/conventions/` are |
| 93 | +rejected, as are symlinks. A file larger than 1 MB is |
| 94 | +rejected. One convention per file, flat layout. |
| 95 | + |
| 96 | +## Composition with `.mdsmith.yml` |
| 97 | + |
| 98 | +`conventions.<name>:` blocks inside `.mdsmith.yml` remain |
| 99 | +a first-class source. A project can mix inline and |
| 100 | +file-defined conventions freely. |
| 101 | + |
| 102 | +The same convention name declared in **both** a file and |
| 103 | +inline is a config error naming both sources. The two |
| 104 | +sources do **not** merge — a merged convention would |
| 105 | +defeat the "read one file to know one convention" |
| 106 | +property convention files ship. |
| 107 | + |
| 108 | +A name colliding with a built-in convention (`portable`, |
| 109 | +`github`, `plain`, and the others in the |
| 110 | +[conventions reference](conventions.md)) is a config |
| 111 | +error: the built-in name is reserved. This keeps the |
| 112 | +built-in names stable across docs and tutorials. |
| 113 | + |
| 114 | +The top-level `convention:` selector and the |
| 115 | +`overrides:`, `kinds:`, and `ignore:` blocks all stay in |
| 116 | +`.mdsmith.yml`. A `convention: <name>` entry references a |
| 117 | +convention by name — inline or file convention — with no |
| 118 | +extra wiring. |
| 119 | + |
| 120 | +## Splitting an inline convention |
| 121 | + |
| 122 | +To move an existing inline convention into its own file, |
| 123 | +cut the body under `conventions.<name>:` and drop the |
| 124 | +inline entry. Take this `.mdsmith.yml`: |
| 125 | + |
| 126 | +```yaml |
| 127 | +conventions: |
| 128 | + our-team: |
| 129 | + flavor: gfm |
| 130 | + rules: |
| 131 | + no-inline-html: |
| 132 | + allow: [details, summary, kbd] |
| 133 | + list-marker-style: |
| 134 | + style: dash |
| 135 | +
|
| 136 | +convention: our-team |
| 137 | +``` |
| 138 | + |
| 139 | +Write the body to a file named for the convention, and |
| 140 | +delete the `conventions:` block: |
| 141 | + |
| 142 | +```yaml |
| 143 | +# .mdsmith/conventions/our-team.yaml |
| 144 | +flavor: gfm |
| 145 | +rules: |
| 146 | + no-inline-html: |
| 147 | + allow: [details, summary, kbd] |
| 148 | + list-marker-style: |
| 149 | + style: dash |
| 150 | +``` |
| 151 | + |
| 152 | +The `convention: our-team` selector stays in |
| 153 | +`.mdsmith.yml`. Move the body, do not copy it: declaring |
| 154 | +the same name both inline and in a file is a config error |
| 155 | +naming both sources. The effective rules are byte-equal |
| 156 | +either way. |
| 157 | + |
| 158 | +## Audit |
| 159 | + |
| 160 | +`mdsmith kinds resolve <file>` prints the active |
| 161 | +convention and the file that defined it, so you can jump |
| 162 | +straight to the right source: |
| 163 | + |
| 164 | +```text |
| 165 | +file: docs/guide.md |
| 166 | +effective kinds: |
| 167 | + (none) |
| 168 | +convention: portable-strict (user) defined-in .mdsmith/conventions/portable-strict.yaml |
| 169 | +``` |
| 170 | + |
| 171 | +The `(user)` tag marks a user-defined convention. |
| 172 | +Built-in conventions carry no tag and no defining-source |
| 173 | +path — they are compiled into the binary. |
| 174 | + |
| 175 | +The JSON shape (`--json`) carries a `convention` object |
| 176 | +with `name` and `source-path` keys. That parallels each |
| 177 | +resolved kind's `source-path`, so editor integrations can |
| 178 | +key off a stable field. |
| 179 | + |
| 180 | +`mdsmith kinds resolve <file>` also shows the full merge |
| 181 | +chain for every rule, including the `convention.<name>` |
| 182 | +layer. Use it to confirm which value won and where it |
| 183 | +came from. |
| 184 | + |
| 185 | +See the [conventions reference](conventions.md) for the |
| 186 | +built-in bundles, the merge order, and how presets layer |
| 187 | +with your top-level rules. |
0 commit comments