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

CheckExample
Invalid DSL syntaxMalformed var, if, chain lines
Unresolved importsimport foo when no export foo exists
Duplicate exportsTwo blocks both export docker_container
Undeclared command variables$host in command but no var host

Warnings

CheckExample
Duplicate cheat namesTwo cheats with ## list pods (across any files)
Empty headingsA ## with no text
Missing chain stepsChain has step 1 and 3 but no 2
Duplicate chain stepsTwo cheats both chain release 2
Cheat with no code blockA 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=value declares $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 = value declares $x
  • foreach ($x in ...), param($x), function parameters
  • Method chains: $obj.Property is 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.

See also

  • [Writing Cheats](/docs/writing cheats) - cheat structure
  • Variables - variable declaration forms
  • Dump - exporting metadata for tooling