Linting
cheatmd --lint validates your cheats without opening the picker. It checks
DSL syntax, references, and structural integrity.
Usage
cheatmd --lint # Lint current directory
cheatmd --lint ~/cheats # Lint specific path
cheatmd --lint --strict # Treat warnings as errors (non-zero exit) Output format
Findings are printed in GCC style for easy integration with editors and CI:
file.md:12:1: error: import "common" does not resolve to any exported module
file.md:25:1: warning: duplicate cheat name "list pods" (also at other.md:8:1) What it checks
Errors
| Check | Example |
|---|---|
| Invalid DSL syntax | Malformed var, if, chain lines |
| Unresolved imports | import foo when no export foo exists |
| Duplicate exports | Two blocks both export docker_container |
| Undeclared command variables | $host in command but no var host |
Warnings
| Check | Example |
|---|---|
| Duplicate cheat names | Two cheats with ## list pods (across any files) |
| Empty headings | A ## with no text |
| Missing chain steps | Chain has step 1 and 3 but no 2 |
| Duplicate chain steps | Two cheats both chain release 2 |
| Cheat with no code block | A heading + metadata but no fenced command |
Language-aware variable detection
The linter uses the code fence language hint to avoid false positives on language-native variables.
Shell (sh, bash, zsh)
These are not flagged as undeclared:
- Built-in variables:
$HOME,$USER,$PATH,$PWD,$SHELL,$?,$!,$$,$#,$@,$*,$0-$9,$OPTARG,$OPTIND, etc. - Loop variables:
for x in ...declares$x - Assignments:
x=valuedeclares$x read x,local x,declare x,export x=value- all declare$x- Single-quoted strings are not scanned
- Variables inside
$(...)subshells are not scanned
PowerShell
These are not flagged:
- Automatic variables:
$_,$true,$false,$null,$PSItem,$PSScriptRoot,$env:PATH, etc. - Provider namespaces:
$env:,$Env:prefixes - Assignments:
$x = valuedeclares$x foreach ($x in ...),param($x), function parameters- Method chains:
$obj.Propertyis treated as accessing$obj
Angle bracket syntax
When var_syntax includes angle, <name> references are strict - there
are no language-built-in angle-bracket variables. Every <name> must be
declared.
Strict mode
By default, only errors cause a non-zero exit code. With --strict, warnings
also fail:
cheatmd --lint --strict ~/cheats || echo "Lint failed" Useful in CI pipelines.
Bypass checks
To suppress a specific lint check, add a literal var to the cheat that exactly matches the text you want to suppress:
## My Cheat
```sh title:"My Cheat"
echo $status
```
<!-- cheat
var status := $status
--> When CheatMD returns this cheat it will literally return echo $status as a result.