rumdl 0.1.51

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
# Global Settings Reference

This document provides a comprehensive reference for rumdl's global configuration settings. Global settings control rumdl's overall behavior and apply to all rules and operations.

## Overview

Global settings are configured in the `[global]` section of your configuration file (`.rumdl.toml` or
`pyproject.toml`). These settings control file selection, rule enablement, and general linting behavior.

> **Tip:** In `.rumdl.toml`, global keys can also be placed at the top level without a `[global]` section
> header, similar to [ruff.toml]https://docs.astral.sh/ruff/settings/. If both top-level and `[global]`
> keys exist, the `[global]` section takes precedence. See [Top-level shorthand]#top-level-shorthand for
> details.

## Quick Reference

| Setting                                   | Type       | Default        | Description                               |
| ----------------------------------------- | ---------- | -------------- | ----------------------------------------- |
| [`extends`]#extends                     | `string`   | not set        | Inherit settings from another config file |
| [`enable`]#enable                       | `string[]` | not set        | Enable only specific rules                |
| [`disable`]#disable                     | `string[]` | `[]`           | Disable specific rules                    |
| [`extend-enable`]#extend-enable         | `string[]` | `[]`           | Additional rules to enable (additive)     |
| [`extend-disable`]#extend-disable       | `string[]` | `[]`           | Additional rules to disable (additive)    |
| [`per-file-ignores`]#per-file-ignores   | `table`    | `{}`           | Disable specific rules for specific files |
| [`exclude`]#exclude                     | `string[]` | `[]`           | Files/directories to exclude              |
| [`include`]#include                     | `string[]` | `[]`           | Files/directories to include              |
| [`respect_gitignore`]#respect_gitignore | `boolean`  | `true`         | Respect .gitignore files                  |
| [`line_length`]#line_length             | `integer`  | `80`           | Default line length for rules             |
| [`flavor`]#flavor                       | `string`   | `"standard"`   | Markdown flavor to use                    |
| [`per-file-flavor`]#per-file-flavor     | `table`    | `{}`           | Per-file flavor overrides                 |
| [`output-format`]#output-format         | `string`   | `"text"`       | Output format for linting results         |
| [`cache`]#cache                         | `boolean`  | `true`         | Enable result caching                     |
| [`cache_dir`]#cache_dir                 | `string`   | `.rumdl_cache` | Directory for cache files                 |

## Configuration Examples

### TOML Configuration (`.rumdl.toml`)

```toml
[global]
# Disable specific rules
disable = ["MD013", "MD033"]

# Add opt-in rules on top of defaults (additive)
extend-enable = ["MD060", "MD063"]

# Exclude files and directories
exclude = [
    "node_modules",
    "build",
    "dist",
    "*.tmp.md",
    "docs/generated/**"
]

# Include only specific files
include = [
    "README.md",
    "docs/**/*.md",
    "**/*.markdown"
]

# Don't respect .gitignore files
respect_gitignore = false

# Set global line length (used by MD013 and other line-length rules)
line_length = 120

# Set markdown flavor (standard, mkdocs)
flavor = "mkdocs"

# Per-file flavor overrides (pattern → flavor)
[per-file-flavor]
"**/*.mdx" = "mdx"
"notebooks/**/*.qmd" = "quarto"

# Disable specific rules for specific files
[per-file-ignores]
"README.md" = ["MD033"]  # Allow inline HTML in README
"docs/api/**/*.md" = ["MD013", "MD041"]  # Generated API docs
"SUMMARY.md" = ["MD025"]  # MkDocs/mdBook table of contents
```

### pyproject.toml Configuration

```toml
[tool.rumdl]
# Global options at root level (both snake_case and kebab-case supported)
disable = ["MD013", "MD033"]
extend-enable = ["MD060"]
exclude = ["node_modules", "build", "dist"]
include = ["docs/*.md", "README.md"]
respect_gitignore = true
line_length = 120
flavor = "standard"

# Per-file rule ignores (both snake_case and kebab-case supported)
[tool.rumdl.per-file-ignores]
"README.md" = ["MD033"]
"SUMMARY.md" = ["MD025"]
```

### Top-level shorthand

In `.rumdl.toml`, you can place global keys at the top level without wrapping them in a `[global]` section.
This is convenient for simple configs and matches the convention used by [ruff.toml](https://docs.astral.sh/ruff/settings/):

```toml
line-length = 88
disable = []
exclude = []
respect-gitignore = true

[MD004]
style = "dash"

[MD013]
line_length = 88
code_blocks = false
```

This is equivalent to:

```toml
[global]
line-length = 88
disable = []
exclude = []
respect-gitignore = true

[MD004]
style = "dash"

[MD013]
line_length = 88
code_blocks = false
```

**Precedence:** If the same key appears both at the top level and inside `[global]`, the `[global]` value wins.

**Supported keys:** `enable`, `disable`, `include`, `exclude`, `extend-enable`, `extend-disable`,
`respect-gitignore`, `force-exclude`, `line-length`, `output-format`, `cache-dir`, `cache`, `fixable`,
`unfixable`, `flavor`.

**Notes:**

- Table-type settings (`per-file-ignores`, `per-file-flavor`, `code-block-tools`) still require
  their own section headers. Only scalar and array keys can be placed at the top level.
- This shorthand is only available in `.rumdl.toml` / `rumdl.toml`. In `pyproject.toml`, use
  `[tool.rumdl]` as shown in the pyproject.toml example above.

## Rule Selection Model

rumdl uses four settings to control which rules are active. These follow the same model as [Ruff's lint rule selection](https://docs.astral.sh/ruff/settings/#lint_select):

| rumdl            | Ruff equivalent | CLI flag    | Behavior                      |
| ---------------- | --------------- | ----------- | ----------------------------- |
| `enable`         | `select`        | `--enable`  | Set the enabled rules         |
| `disable`        | `ignore`        | `--disable` | Set the disabled rules        |
| `extend-enable`  | `extend-select` || Add rules to the enabled set  |
| `extend-disable` | `extend-ignore` || Add rules to the disabled set |

### How rules are resolved

1. **Start with the base set.** If `enable` is omitted, the base set is all rules except [opt-in rules]#opt-in-rules. If `enable` is set, only the listed rules form the base set.
2. **Merge `extend-enable`.** Any rules listed in `extend-enable` are added to the base set. This is the way to activate opt-in rules without replacing the entire default set.
3. **Apply `disable` and `extend-disable`.** Rules in either list are removed. Disabling always wins over enabling.

Key behaviors:

- **`enable` omitted** — all non-opt-in rules are active (the default)
- **`enable = []`** (empty list) — *no rules are active*; this is an explicit empty allowlist, not the same as omitting `enable`
- **`enable = ["ALL"]`** — every rule is active, including opt-in rules
- **`extend-enable = ["ALL"]`** — every rule is active (same effect as `enable = ["ALL"]`)
- **`disable = ["all"]`** with no `enable` — no rules are active
- **Disabling wins** — if a rule appears in both an enable list and a disable list, it is disabled

### Config precedence and merging

rumdl loads configuration from config files
(with [per-directory resolution](#per-directory-configuration) when available),
then applies CLI overrides on top.
When CLI flags overlap with file settings:

- `--enable` and `--disable` **replace** the config file value entirely.
- `extend-enable` and `extend-disable` (config file only) **merge** additively with the base `enable`/`disable` values.

```bash
# Config file has disable = ["MD013"]
# CLI replaces it — only MD033 is disabled, MD013 is re-enabled
rumdl check --disable MD033 .
```

To disable MD033 *in addition to* whatever the config file disables, use `extend-disable` in the config file instead of CLI `--disable`.

### Opt-in rules

Some rules are excluded from the default set because they are opinionated, project-specific,
or may require configuration.
These rules must be explicitly activated via `extend-enable` or `enable = ["ALL"]`.

Current opt-in rules:

| Rule  | Description                  |
| ----- | ---------------------------- |
| MD060 | Table column formatting      |
| MD063 | Heading capitalization       |
| MD070 | Nested code fence collision  |
| MD072 | Frontmatter key sort order   |
| MD073 | Table of contents validation |
| MD074 | MkDocs nav validation        |

```toml
[global]
extend-enable = ["MD060", "MD063"]
```

### Common patterns

**Use only a handful of rules (strict allowlist)**:

```toml
[global]
enable = ["MD001", "MD003", "MD022", "MD025"]
```

**Use all defaults but disable a few**:

```toml
[global]
disable = ["MD013", "MD033"]
```

**Use all defaults plus opt-in rules**:

```toml
[global]
extend-enable = ["MD060", "MD072"]
```

**Use every rule including opt-in, minus a few**:

```toml
[global]
enable = ["ALL"]
disable = ["MD013", "MD033"]
```

## Detailed Settings Reference

### `extends`

**Type**: `string`
**Default**: not set
**CLI Equivalent**: None (configuration file only)

Specifies a base configuration file to inherit settings from. The current config file's settings are merged on top of the base config.

This is a **top-level key** (not inside `[global]`).

**In `.rumdl.toml`:**

```toml
extends = "../base.rumdl.toml"

[global]
disable = ["MD013"]
```

**In `pyproject.toml`:**

```toml
[tool.rumdl]
extends = "../.rumdl.toml"

[tool.rumdl.global]
disable = ["MD013"]
```

**Path Resolution**:

- Relative paths are resolved relative to the config file's directory (not the working directory)
- `~/` prefix expands to the user's home directory
- Absolute paths are used as-is
- The extended file can be `.rumdl.toml`, `rumdl.toml`, or `pyproject.toml`

**Merge Behavior**:

When config B extends config A:

- **Replace fields** (`enable`, `disable`, `line-length`, `flavor`, `exclude`, etc.): B's value replaces A's if B specifies it
- **Union fields** (`extend-enable`, `extend-disable`): B's values accumulate with A's
- **Unspecified fields**: A's values are kept
- **Rule-specific settings**: B's `[MD007]` overrides A's `[MD007]` (per-key)

**Chains**:

Configs can chain: A extends B extends C. The base config is loaded first recursively, then each child merges on top. Maximum chain depth is 10.

**Circular Detection**:

Circular references (A extends B extends A) are detected and produce a clear error.

**Common Patterns**:

**Subdirectory override:**

```toml
# docs/.rumdl.toml — relaxed rules for documentation
extends = "../.rumdl.toml"

[global]
extend-disable = ["MD013"]
```

**Shared base config:**

```toml
# .rumdl.toml in each project
extends = "~/.config/rumdl/base.rumdl.toml"

[global]
disable = ["MD033"]
```

### `enable`

**Type**: `string[]`
**Default**: not set (all rules enabled; `disable` applies normally)
**CLI Equivalent**: `--enable`

Enables only the specified rules. When this option is set, all other rules are disabled except those explicitly listed.

```toml
[global]
enable = ["MD001", "MD003", "MD013", "MD022"]
```

**Usage Notes**:

- Rule IDs are case-insensitive but conventionally uppercase (e.g., "MD001")
- `enable = []` (empty list) disables **all** rules — nothing will be linted
- Omitting `enable` entirely uses the default: all non-opt-in rules enabled, `disable` applied normally
- `enable = ["ALL"]` explicitly enables every rule including [opt-in rules]#opt-in-rules; `disable` still applies on top
- If `enable` lists specific rules, only those rules run (subject to `disable` and `extend-disable`)
- When `enable` is set, `extend-enable` entries are merged into the allowlist
- To add opt-in rules without replacing the defaults, use [`extend-enable`]#extend-enable instead

**Example CLI usage**:

```bash
rumdl check --enable MD001,MD003,MD013 .
```

### `extend-enable`

**Type**: `string[]`
**Default**: `[]` (no additional rules)
**CLI Equivalent**: None (configuration file only)

Adds rules to the enabled set.
This is the primary way to activate [opt-in rules](#opt-in-rules)
(MD060, MD063, MD072, MD073, MD074) without replacing the default rule set.

When CLI `--enable` is used, it replaces the config file's `enable` list entirely.
`extend-enable` in the config file is always additive —
see [Config precedence and merging](#config-precedence-and-merging).

```toml
[global]
extend-enable = ["MD060", "MD063"]
```

**Usage Notes**:

- `extend-enable = ["ALL"]` enables every rule including opt-in rules (same as `enable = ["ALL"]`)
- Can be combined with `enable` — when both are set, `extend-enable` entries are merged into the `enable` allowlist
- Disabling always wins: if a rule appears in both `extend-enable` and `disable`/`extend-disable`, it is disabled
- Rule IDs are case-insensitive but conventionally uppercase

**Example: Enable opt-in rules alongside defaults**:

```toml
[global]
extend-enable = ["MD060", "MD072"]  # Add table formatting and frontmatter key sort
```

**Example: Combine with disable**:

```toml
[global]
extend-enable = ["MD060"]   # Add table formatting
disable = ["MD013"]          # Remove line length checking
```

### `disable`

**Type**: `string[]`
**Default**: `[]` (no rules disabled)
**CLI Equivalent**: `--disable`

Disables the specified rules. All other rules remain enabled.

```toml
[global]
disable = ["MD013", "MD033", "MD041"]
```

**Usage Notes**:

- Rule IDs are case-insensitive but conventionally uppercase
- `disable = ["all"]` disables every rule; only rules listed in `enable` (if set) are active
- Commonly disabled rules include:
  - `MD013`: Line length (for projects with longer lines)
  - `MD033`: Inline HTML (for projects that use HTML in Markdown)
  - `MD041`: First line heading (for files that don't start with headings)
- Disabling always wins over enabling — if a rule appears in both `enable` and `disable`, it is disabled
- CLI `--disable` replaces the config file's `disable` list; use [`extend-disable`]#extend-disable in the config file for values that shouldn't be overridden by CLI

**Example CLI usage**:

```bash
rumdl check --disable MD013,MD033 .
```

### `extend-disable`

**Type**: `string[]`
**Default**: `[]` (no additional rules disabled)
**CLI Equivalent**: None (configuration file only)

Adds rules to the disabled set. Always merges additively with `disable`, regardless of source.
Useful for adding disabled rules that survive CLI `--disable` overrides —
see [Config precedence and merging](#config-precedence-and-merging).

```toml
[global]
extend-disable = ["MD033", "MD041"]
```

**Usage Notes**:

- `extend-disable = ["ALL"]` disables every rule (effectively stops all linting)
- Disabling always wins over enabling: if a rule appears in both `extend-enable` and `extend-disable`, it is disabled
- Rule IDs are case-insensitive but conventionally uppercase

**Example: Disable rules that survive CLI overrides**:

```toml
[global]
disable = ["MD013"]
extend-disable = ["MD033"]  # Always disabled, even if --disable overrides the list above
```

With `rumdl check --disable MD041 .`, the result is: MD013 replaced by MD041 (from CLI), plus MD033 (from `extend-disable`, always additive). Final disabled set: MD041, MD033.

### `per-file-ignores`

**Type**: `table` (file patterns mapped to rule arrays)
**Default**: `{}` (no per-file ignores)
**CLI Equivalent**: None (configuration file only)

Disables specific rules for specific files or file patterns. This is useful when certain files have different requirements than the rest of your documentation.

```toml
[per-file-ignores]
# Disable inline HTML check for GitHub README (often has badges)
"README.md" = ["MD033"]

# Disable line length and first-line heading for generated API docs
"docs/api/**/*.md" = ["MD013", "MD041"]

# Allow multiple top-level headings in table of contents files
"SUMMARY.md" = ["MD025"]
"**/TOC.md" = ["MD025"]

# Disable heading style check for legacy documentation
"docs/legacy/**/*.md" = ["MD003", "MD022"]
```

**Pattern Syntax**:

Patterns use standard glob syntax:

- `*` matches any characters except path separators
- `**` matches any characters including path separators (recursive)
- `?` matches a single character
- `{a,b}` matches either `a` or `b` (brace expansion)

To match multiple specific files, use **brace expansion**:

```toml
[per-file-ignores]
# Match both AGENTS.md and README.md
"{AGENTS.md,README.md}" = ["MD033"]

# Match multiple directories
"{docs,guides}/**/*.md" = ["MD013"]
```

Alternatively, use separate entries (more verbose but equivalent):

```toml
[per-file-ignores]
"AGENTS.md" = ["MD033"]
"README.md" = ["MD033"]
```

> **Note**: Commas are literal characters in glob patterns. The pattern `"A.md,B.md"` matches a file literally named `A.md,B.md`, not two separate files. Use `"{A.md,B.md}"` to match multiple files.

**Usage Notes**:

- Rule IDs are case-insensitive but conventionally uppercase
- More specific patterns take precedence over general ones
- Useful for handling special files like:
  - `README.md` files (often contain badges, HTML, custom formatting)
  - Generated documentation (API docs, changelogs)
  - Table of contents files (`SUMMARY.md`, `TOC.md`)
  - Legacy or third-party documentation
- Combines with global `enable`/`disable` settings

**Behavior**:

1. Global rules are applied first
2. Per-file ignores override global settings for matching files
3. If a file matches multiple patterns, all ignores are combined

**Example with precedence**:

```toml
[global]
disable = ["MD013"]  # Disable line length globally

[per-file-ignores]
"README.md" = ["MD033", "MD041"]  # Also disable HTML and first-line heading for README
"docs/**/*.md" = ["MD033"]  # Allow HTML in docs
```

Result:

- `README.md`: MD013, MD033, MD041 disabled
- `docs/guide.md`: MD013, MD033 disabled
- `other.md`: MD013 disabled

**Common Use Cases**:

1. **MkDocs/mdBook Projects**:

    ```toml
    [per-file-ignores]
    "SUMMARY.md" = ["MD025"]  # Table of contents has multiple H1 headings
    ```

2. **GitHub Projects with Badges**:

    ```toml
    [per-file-ignores]
    "README.md" = ["MD033", "MD041"]  # Allow HTML badges, may not start with heading
    ```

3. **Generated Documentation**:

    ```toml
    [per-file-ignores]
    "docs/api/**/*.md" = ["MD013", "MD024", "MD041"]  # Relax rules for generated files
    ```

4. **Mixed Documentation Sources**:

    ```toml
    [per-file-ignores]
    "vendor/**/*.md" = ["MD013", "MD033", "MD041"]  # Third-party docs
    "legacy/**/*.md" = ["MD003", "MD022", "MD032"]  # Old docs with different style
    ```

5. **Documentation Generators with HTML Links**:

    For documentation generators (mdBook, Jekyll, Hugo) that compile markdown to HTML and place sources in different locations:

    ```toml
    [per-file-ignores]
    # mdBook projects - HTML links in book/ point to book/src/*.md sources
    "book/**/*.md" = ["MD057"]

    # Jekyll projects - HTML links in _posts/ point to generated files
    "_posts/**/*.md" = ["MD057"]
    "_docs/**/*.md" = ["MD057"]

    # Hugo projects - HTML links in content/ point to generated files
    "content/**/*.md" = ["MD057"]
    ```

    See [MD057 documentation]md057.md#handling-complex-generator-patterns for more details.

### `exclude`

**Type**: `string[]`
**Default**: `[]` (no files excluded)
**CLI Equivalent**: `--exclude`

Specifies files and directories to exclude from linting. Supports glob patterns.

```toml
[global]
exclude = [
    "node_modules",           # Exclude entire directory
    "build/**",              # Exclude directory and all subdirectories
    "*.tmp.md",              # Exclude files with specific pattern
    "docs/generated/**",     # Exclude generated documentation
    ".git",                  # Exclude version control directory
    "vendor/",               # Exclude third-party code
]
```

**Supported Patterns**:

- `directory/` - Exclude entire directory
- `**/*.ext` - Exclude all files with extension in any subdirectory
- `*.pattern` - Exclude files matching pattern in current directory
- `path/**/file` - Exclude specific files in any subdirectory of path

**Usage Notes**:

- Patterns are relative to the project root
- Exclude patterns are processed before include patterns
- More specific patterns take precedence over general ones
- Useful for excluding generated files, dependencies, and temporary files

**Example CLI usage**:

```bash
rumdl check --exclude "node_modules,build,*.tmp.md" .
```

### `include`

**Type**: `string[]`
**Default**: `[]` (all Markdown files included)
**CLI Equivalent**: `--include`

Specifies files and directories to include in linting. When set, only matching files are processed.

```toml
[global]
include = [
    "README.md",             # Include specific file
    "docs/**/*.md",          # Include all .md files in docs/
    "**/*.markdown",         # Include all .markdown files
    "CHANGELOG.md",          # Include specific files
    "src/**/*.md",           # Include documentation in source
]
```

**Usage Notes**:

- If `include` is empty, all Markdown files are included (subject to exclude patterns)
- When `include` is specified, only matching files are processed
- Combine with `exclude` for fine-grained control
- Useful for limiting linting to specific documentation areas

**Example CLI usage**:

```bash
rumdl check --include "docs/**/*.md,README.md" .
```

### `respect_gitignore`

**Type**: `boolean`
**Default**: `true`
**CLI Equivalent**: `--respect-gitignore` / `--respect-gitignore=false`

Controls whether rumdl respects `.gitignore` files when scanning for Markdown files.

```toml
[global]
respect_gitignore = true   # Default: respect .gitignore
# or
respect_gitignore = false  # Ignore .gitignore files
```

**Behavior**:

- `true` (default): Files and directories listed in ignore files are automatically excluded
- `false`: Ignore files are not considered, all Markdown files are scanned

**Supported ignore files**:

- `.gitignore` - Standard Git ignore patterns
- `.ignore` - Additional ignore patterns (used by ripgrep, fd, and other tools)

Both file types use the same gitignore pattern syntax and are respected at any level in the directory tree.

**Usage Notes**:

- This setting only affects directory scanning, not explicitly provided file paths
- Useful for linting files that are normally ignored (e.g., generated docs)
- When disabled, you may need more specific `exclude` patterns
- Use `.ignore` for rumdl-specific exclusions without affecting Git

**Example CLI usage**:

```bash
# Don't respect .gitignore files
rumdl check --respect-gitignore=false .
```

### `line_length`

**Type**: `integer`
**Default**: `80`
**CLI Equivalent**: None (rule-specific only)

Sets the global default line length used by rules that check line length (primarily MD013). Rules can override this value with their own configuration.

```toml
[global]
line_length = 120  # Set global line length to 120 characters
```

**Behavior**:

- Used as the default line length for MD013 and other line-length-related rules
- Rule-specific configurations override the global setting
- Useful for projects that want a consistent line length across all line-length rules

**Usage Notes**:

- Must be a positive integer
- Common values: 80 (traditional), 100 (relaxed), 120 (modern)
- Individual rules can still override this setting in their own configuration
- When importing from markdownlint configs, top-level `line-length` is mapped to this setting

**Example with rule override**:

```toml
[global]
line_length = 100  # Global default

[MD013]
line_length = 120  # MD013 uses 120, overriding global setting
```

### `flavor`

**Type**: `string`
**Default**: `"standard"`
**CLI Equivalent**: `--flavor`

Specifies the Markdown flavor to use for parsing and linting. Different flavors have different parsing rules and feature support.

```toml
[global]
flavor = "mkdocs"  # Use MkDocs flavor
```

**Available Flavors**:

- `"standard"` (default): [CommonMark 0.31.2]https://spec.commonmark.org/0.31.2/ + GFM extensions (tables, task lists, strikethrough, autolinks)
- `"gfm"`: GitHub Flavored Markdown with security-sensitive HTML warnings and extended autolinks
- `"mkdocs"`: MkDocs-specific extensions (admonitions, content tabs, autorefs, mkdocstrings)
- `"mdx"`: MDX with JSX components, attributes, expressions, and ESM imports
- `"quarto"`: Quarto/RMarkdown for scientific publishing (citations, shortcodes, div blocks)

**Aliases**: `"commonmark"` is an alias for `"standard"`, `"github"` is an alias for `"gfm"`

**Behavior**:

- The `standard` flavor is based on CommonMark 0.31.2 with widely-adopted GFM extensions enabled by default
- Each flavor adjusts specific rule behavior where that system differs from standard Markdown
- See [Flavors Overview]flavors.md for detailed rule adjustments per flavor

**Usage Notes**:

- Choose the flavor that matches your documentation system
- Use `standard` for generic Markdown or when you want the strictest linting
- Use `gfm` for GitHub-hosted documentation with security-conscious HTML handling
- Use `mkdocs` for MkDocs or Material for MkDocs projects
- Use `mdx` for React/Next.js documentation with JSX components
- Use `quarto` for scientific documents with R/Python code execution

**Example CLI usage**:

```bash
# Use MkDocs flavor for linting
rumdl check --flavor mkdocs docs/
```

### `per-file-flavor`

**Type**: `table` (file patterns mapped to flavors)
**Default**: `{}` (no per-file overrides)
**CLI Equivalent**: None (configuration file only)

Specifies Markdown flavors for specific files or file patterns. This allows different parts of your project to use different Markdown dialects.

```toml
[per-file-flavor]
"docs/**/*.md" = "mkdocs"
"**/*.mdx" = "mdx"
"**/*.qmd" = "quarto"
"examples/**/*.md" = "standard"
```

**Available Flavors**:

- `"standard"` (default): Standard Markdown with GFM extensions (tables, task lists, strikethrough)
- `"gfm"` or `"github"`: Alias for standard (pulldown-cmark already supports GFM)
- `"commonmark"`: Alias for standard
- `"mkdocs"`: MkDocs-specific extensions (auto-references, admonitions)
- `"mdx"`: MDX flavor with JSX and ESM support
- `"quarto"`: Quarto/RMarkdown for scientific publishing

**Behavior**:

- Uses "first match wins" semantics - order matters in the configuration
- Patterns are matched against relative paths from the project root
- Falls back to global `flavor` setting if no pattern matches
- Falls back to auto-detection by file extension if no global flavor is set

**Pattern Syntax**:

- `*` matches any characters except path separators
- `**` matches any characters including path separators
- `?` matches a single character
- Patterns are relative to the project root

**Usage Notes**:

- Useful for projects with mixed documentation (e.g., MkDocs site + MDX components)
- Order patterns from most specific to least specific
- Auto-detection works for common extensions: `.mdx` → MDX, `.qmd`/`.Rmd` → Quarto

**Example: Mixed Documentation Project**:

```toml
[global]
flavor = "standard"  # Default for files not matching any pattern

[per-file-flavor]
# MkDocs documentation
"docs/**/*.md" = "mkdocs"

# React components with MDX
"src/components/**/*.mdx" = "mdx"

# Jupyter/Quarto notebooks
"notebooks/**/*.qmd" = "quarto"

# Keep README and CHANGELOG as standard
"README.md" = "standard"
"CHANGELOG.md" = "standard"
```

**Example: Monorepo with Multiple Doc Systems**:

```toml
[per-file-flavor]
"packages/website/docs/**/*.md" = "mkdocs"
"packages/storybook/**/*.mdx" = "mdx"
"packages/api/docs/**/*.md" = "standard"
```

### `output-format`

**Type**: `string`
**Default**: `"text"`
**CLI Equivalent**: `--output-format`
**Environment Variable**: `RUMDL_OUTPUT_FORMAT`

Specifies the output format for linting results.

```toml
[global]
output-format = "github"  # Use GitHub Actions format
```

**Available Formats**:

- `"text"` (default): One line per warning with file, line, column, rule, and message
- `"full"`: Source lines with caret underlines highlighting the exact violation location
- `"concise"`: Minimal output (one line per warning, no brackets)
- `"grouped"`: Warnings grouped by file with a header per file
- `"json"`: JSON array of all warnings (collected across files)
- `"json-lines"`: One JSON object per warning (streaming)
- `"github"`: GitHub Actions annotation format (`::warning`/`::error`)
- `"gitlab"`: GitLab Code Quality report (JSON)
- `"pylint"`: Pylint-compatible format
- `"azure"`: Azure Pipelines logging commands
- `"sarif"`: SARIF 2.1.0 for static analysis tools
- `"junit"`: JUnit XML for CI test reporters

**Precedence**:

1. CLI flag (`--output-format`) wins
2. Environment variable (`RUMDL_OUTPUT_FORMAT`) overrides config
3. Config file setting (`output-format`)
4. Default (`"text"`)

**Usage Notes**:

- Use `github` format in GitHub Actions for inline annotations
- Use `json` or `sarif` for integration with other tools
- The environment variable is useful for CI/CD pipelines where you want to override the project config

**Example CLI usage**:

```bash
# Use GitHub format for Actions
rumdl check --output-format github .

# Or via environment variable
RUMDL_OUTPUT_FORMAT=github rumdl check .
```

**Example GitHub Actions workflow**:

```yaml
- name: Lint Markdown
  env:
    RUMDL_OUTPUT_FORMAT: github
  run: rumdl check .
```

### `cache`

**Type**: `boolean`
**Default**: `true`
**CLI Equivalent**: `--no-cache` (to disable)

Controls whether rumdl caches linting results to speed up subsequent runs.

```toml
[global]
cache = true   # Enable caching (default)
# or
cache = false  # Disable caching
```

**Behavior**:

- `true` (default): Results are cached based on file content hashes
- `false`: Every run processes all files from scratch

**Usage Notes**:

- Caching significantly speeds up repeated linting of unchanged files
- Cache is automatically invalidated when file content changes
- Disable caching during development when debugging rule changes
- Use `--no-cache` CLI flag for one-time cache bypass without changing config

**Example CLI usage**:

```bash
# Disable cache for this run only
rumdl check --no-cache .
```

### `cache_dir`

**Type**: `string`
**Default**: `.rumdl_cache`
**CLI Equivalent**: `--cache-dir`
**Environment Variable**: `RUMDL_CACHE_DIR`

Specifies the directory where rumdl stores cache files.

```toml
[global]
cache_dir = ".rumdl_cache"      # Default location
# or
cache_dir = "/tmp/rumdl-cache"  # Custom location
```

**Behavior**:

- Cache files are stored in this directory
- Directory is created automatically if it doesn't exist
- Each file's cache entry is based on its content hash

**Usage Notes**:

- Default `.rumdl_cache` is relative to the current working directory
- Use absolute paths for consistent behavior across different working directories
- Consider adding the cache directory to `.gitignore`
- Environment variable `RUMDL_CACHE_DIR` can override config file settings

**Example CLI usage**:

```bash
# Use custom cache directory
rumdl check --cache-dir /tmp/rumdl-cache .

# Or via environment variable
RUMDL_CACHE_DIR=/tmp/rumdl-cache rumdl check .
```

**Adding to .gitignore**:

```gitignore
# rumdl cache
.rumdl_cache/
```

## Per-Directory Configuration

When running `rumdl check .` from the project root, rumdl discovers and applies
configuration files on a per-directory basis. Files in a subdirectory with their
own `.rumdl.toml` will use that config instead of the root config.

This follows the same model as [Ruff's per-directory configuration](https://docs.astral.sh/ruff/configuration/config-file/#config-file-discovery).

### How it works

1. rumdl identifies the **project root** (the directory containing `.git`)
2. For each file being linted, rumdl searches from the file's directory upward to the project root for the nearest config file
3. Files are grouped by their effective config, and each group is linted with its own rules and settings

### Config file search order

At each directory level, rumdl checks for config files in this order:

1. `.rumdl.toml`
2. `rumdl.toml`
3. `.config/rumdl.toml`
4. `pyproject.toml` (only if it contains a `[tool.rumdl]` section)
5. Markdownlint config files (`.markdownlint.json`, `.markdownlint.yaml`, etc.) as fallback

### Subdirectory configs are standalone

Subdirectory configs are **independent** by default — they do not inherit from the root config. To inherit settings from a parent config, use [`extends`](#extends):

```text
project/
  .rumdl.toml              # line-length = 80
  README.md                 # linted with line-length = 80
  docs/
    .rumdl.toml             # standalone: only settings in this file apply
    guide.md                # linted with docs/.rumdl.toml
    api/
      endpoint.md           # also linted with docs/.rumdl.toml (walks up to docs/)
```

To make a subdirectory config inherit from the root:

```toml
# docs/.rumdl.toml
extends = "../.rumdl.toml"

[global]
line-length = 120          # Override just this setting; inherit everything else
```

### When per-directory resolution is active

Per-directory resolution only activates during **auto-discovery mode**. It is disabled when:

- `--config <file>` is used (the explicit config applies to all files)
- `--isolated` or `--no-config` is used (built-in defaults apply to all files)
- No project root is found (no `.git` directory)

### Example: monorepo with different standards

```text
monorepo/
  .rumdl.toml                  # strict defaults for most code
  docs/
    .rumdl.toml                # relaxed rules for user-facing docs
    getting-started.md
    reference/
      api.md                   # inherits docs/.rumdl.toml
  blog/
    .rumdl.toml                # different style for blog posts
    2024-01-post.md
  src/
    README.md                  # uses root .rumdl.toml (no subdirectory config)
```

```toml
# Root .rumdl.toml - strict defaults
[global]
line-length = 80
```

```toml
# docs/.rumdl.toml - relaxed for documentation
extends = "../.rumdl.toml"

[global]
line-length = 120
extend-disable = ["MD013"]
```

```toml
# blog/.rumdl.toml - standalone config for blog
[global]
line-length = 100
disable = ["MD033", "MD041"]
```

## Configuration Precedence

Settings are applied in the following order (later sources override earlier ones):

1. **Built-in defaults**
2. **Configuration file** (per-directory `.rumdl.toml` or `pyproject.toml`)
3. **Command-line arguments**

### Example: Precedence in Action

Given this configuration file:

```toml
[global]
disable = ["MD013", "MD033"]
exclude = ["node_modules", "build"]
```

And this command:

```bash
rumdl check --disable MD001,MD013 --exclude "temp/**" docs/
```

The final configuration will be:

- `disable`: `["MD001", "MD013"]` (CLI overrides file)
- `exclude`: `["temp/**"]` (CLI overrides file)
- Paths: `["docs/"]` (CLI argument)

## File Selection Logic

rumdl processes files using the following logic:

1. **Start with candidate files**:

    - If paths are provided via CLI: use those files/directories
    - Otherwise: recursively scan current directory for `.md` and `.markdown` files

2. **Apply .gitignore filtering** (if `respect_gitignore = true`):

    - Skip files/directories listed in `.gitignore` files

3. **Apply include patterns** (if specified):

    - Keep only files matching at least one include pattern

4. **Apply exclude patterns**:

    - Remove files matching any exclude pattern

5. **Apply rule filtering**:

    - Process remaining files with enabled rules only

### Example: File Selection

Given this configuration:

```toml
[global]
include = ["docs/**/*.md", "README.md"]
exclude = ["docs/temp/**", "*.draft.md"]
respect_gitignore = true
```

File selection process:

1. Start with: `docs/guide.md`, `docs/temp/test.md`, `README.md`, `notes.draft.md`
2. Apply includes: `docs/guide.md`, `docs/temp/test.md`, `README.md` (notes.draft.md excluded)
3. Apply excludes: `docs/guide.md`, `README.md` (docs/temp/test.md excluded)
4. Final files: `docs/guide.md`, `README.md`

## Common Configuration Patterns

### Strict Documentation Projects

For projects with strict documentation standards:

```toml
[global]
# Enable only essential rules
enable = [
    "MD001",  # Heading increment
    "MD003",  # Heading style
    "MD022",  # Blanks around headings
    "MD025",  # Single title
    "MD032",  # Blanks around lists
]

# Focus on documentation directories
include = [
    "README.md",
    "CHANGELOG.md",
    "docs/**/*.md",
    "guides/**/*.md",
]

# Set consistent line length for documentation
line_length = 100

# Exclude generated or temporary content
exclude = [
    "node_modules/**",
    "build/**",
    "docs/api/**",      # Generated API docs
    "*.draft.md",       # Draft documents
]
```

### Legacy Project Migration

For gradually adopting rumdl in existing projects:

```toml
[global]
# Start with basic rules only
enable = [
    "MD001",  # Heading increment
    "MD022",  # Blanks around headings
    "MD025",  # Single title
]

# Exclude problematic areas initially
exclude = [
    "legacy-docs/**",
    "third-party/**",
    "vendor/**",
]

# Process only main documentation
include = [
    "README.md",
    "docs/user-guide/**/*.md",
]
```

### Open Source Projects

For open source projects with community contributions:

```toml
[global]
# Disable rules that might be too strict for contributors
disable = [
    "MD013",  # Line length (can be restrictive)
    "MD033",  # Inline HTML (often needed for badges/formatting)
    "MD041",  # First line heading (README might start with badges)
]

# Include all documentation but exclude dependencies
include = ["**/*.md"]
exclude = [
    "node_modules/**",
    "vendor/**",
    ".git/**",
    "coverage/**",
]
```

### Development Workflow

For active development with frequent documentation changes:

```toml
[global]
# Enable rules that help maintain consistency
enable = [
    "MD003",  # Heading style
    "MD004",  # List style
    "MD022",  # Blanks around headings
    "MD032",  # Blanks around lists
    "MD047",  # Single trailing newline
]

# Include work-in-progress docs but exclude temp files
include = ["docs/**/*.md", "*.md"]
exclude = [
    "*.tmp.md",
    "*.draft.md",
    ".backup/**",
    "node_modules/**",
]

# Don't respect .gitignore to catch uncommitted docs
respect_gitignore = false
```

## Validation and Debugging

### View Effective Configuration

To see how your global settings are applied:

```bash
# View full effective configuration
rumdl config

# View only global settings
rumdl config get global

# View specific global setting
rumdl config get global.exclude
```

### Test File Selection

To see which files would be processed:

```bash
# Dry run with verbose output
rumdl check --verbose --dry-run .

# Or use a simple script to list files
find . -name "*.md" -o -name "*.markdown" | head -10
```

### Common Configuration Issues

1. **No files found**: Check your `include`/`exclude` patterns and `respect_gitignore` setting
2. **Too many files**: Add more specific `exclude` patterns or limit `include` patterns
3. **Rules not applying**: Verify rule names in `enable`/`disable` lists (case-insensitive but check spelling)
4. **Performance issues**: Exclude large directories like `node_modules`, `vendor`, or build outputs

## Integration with CI/CD

### GitHub Actions

```yaml
- name: Lint Markdown
  run: |
    rumdl check --output json . > lint-results.json
    rumdl check .  # Also show human-readable output
```

### Pre-commit Hook

```yaml
- repo: https://github.com/rvben/rumdl-pre-commit
  rev: v0.0.200
  hooks:
    - id: rumdl
      args: [--config=.rumdl.toml]
```

## See Also

- [Configuration Guide]../README.md#configuration - Basic configuration setup
- [Rules Reference]RULES.md - Complete list of available rules
- [CLI Reference]../README.md#command-line-interface - Command-line options
- [Rule-specific Configuration]../README.md#configuration-file-example - Configuring individual rules
- [Code Block Tools]code-block-tools.md - External linters/formatters for code blocks [preview]