gfm-math-lint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. gfm_math_lint-0.1.0/.gitignore +33 -0
  2. gfm_math_lint-0.1.0/CHANGELOG.md +12 -0
  3. gfm_math_lint-0.1.0/LICENSE +21 -0
  4. gfm_math_lint-0.1.0/PKG-INFO +363 -0
  5. gfm_math_lint-0.1.0/README.md +333 -0
  6. gfm_math_lint-0.1.0/pyproject.toml +161 -0
  7. gfm_math_lint-0.1.0/src/gfm_math_lint/__init__.py +46 -0
  8. gfm_math_lint-0.1.0/src/gfm_math_lint/__main__.py +10 -0
  9. gfm_math_lint-0.1.0/src/gfm_math_lint/cli.py +425 -0
  10. gfm_math_lint-0.1.0/src/gfm_math_lint/comment.py +68 -0
  11. gfm_math_lint-0.1.0/src/gfm_math_lint/config.py +126 -0
  12. gfm_math_lint-0.1.0/src/gfm_math_lint/fixer.py +91 -0
  13. gfm_math_lint-0.1.0/src/gfm_math_lint/linter.py +279 -0
  14. gfm_math_lint-0.1.0/src/gfm_math_lint/py.typed +0 -0
  15. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/__init__.py +113 -0
  16. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/base.py +35 -0
  17. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/code_rules.py +174 -0
  18. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/diagram_rules.py +99 -0
  19. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/image_rules.py +74 -0
  20. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/math_rules.py +1123 -0
  21. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/shell_rules.py +69 -0
  22. gfm_math_lint-0.1.0/src/gfm_math_lint/rules/table_rules.py +92 -0
  23. gfm_math_lint-0.1.0/src/gfm_math_lint/tokenizer.py +553 -0
  24. gfm_math_lint-0.1.0/src/gfm_math_lint/verifier.py +386 -0
  25. gfm_math_lint-0.1.0/tests/__init__.py +31 -0
  26. gfm_math_lint-0.1.0/tests/conftest.py +25 -0
  27. gfm_math_lint-0.1.0/tests/test_audit_regressions.py +261 -0
  28. gfm_math_lint-0.1.0/tests/test_cli.py +204 -0
  29. gfm_math_lint-0.1.0/tests/test_code_rules.py +46 -0
  30. gfm_math_lint-0.1.0/tests/test_comment.py +47 -0
  31. gfm_math_lint-0.1.0/tests/test_config.py +58 -0
  32. gfm_math_lint-0.1.0/tests/test_diagram_rules.py +34 -0
  33. gfm_math_lint-0.1.0/tests/test_fixer.py +49 -0
  34. gfm_math_lint-0.1.0/tests/test_github_api_oracle.py +100 -0
  35. gfm_math_lint-0.1.0/tests/test_image_rules.py +49 -0
  36. gfm_math_lint-0.1.0/tests/test_math_rules.py +296 -0
  37. gfm_math_lint-0.1.0/tests/test_shell_rules.py +31 -0
  38. gfm_math_lint-0.1.0/tests/test_suppression.py +68 -0
  39. gfm_math_lint-0.1.0/tests/test_table_rules.py +45 -0
  40. gfm_math_lint-0.1.0/tests/test_tokenizer.py +152 -0
  41. gfm_math_lint-0.1.0/tests/test_verifier.py +169 -0
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Environments
10
+ .venv/
11
+ .env
12
+
13
+ # Testing & Coverage
14
+ .coverage*
15
+ htmlcov/
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .mypy_cache/
19
+
20
+ # IDE & OS
21
+ .idea/
22
+ .vscode/
23
+ .zed/
24
+ .DS_Store
25
+
26
+ # Temporary & Scratch
27
+ temp/
28
+ *.html
29
+ preview.html
30
+ .direnv/
31
+ *.pem
32
+ *.key
33
+
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-09-30
9
+
10
+ - Initial release.
11
+
12
+ [0.1.0]: https://github.com/jacobhammond/gfm-math-lint/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Hammond
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,363 @@
1
+ Metadata-Version: 2.5
2
+ Name: gfm-math-lint
3
+ Version: 0.1.0
4
+ Summary: Linter and auto-fixer for GitHub-Flavored Markdown math (MathJax), tables, code fences, and Mermaid
5
+ Project-URL: Homepage, https://github.com/jacobhammond/gfm-math-lint
6
+ Project-URL: Documentation, https://github.com/jacobhammond/gfm-math-lint#readme
7
+ Project-URL: Repository, https://github.com/jacobhammond/gfm-math-lint
8
+ Project-URL: Issues, https://github.com/jacobhammond/gfm-math-lint/issues
9
+ Project-URL: Changelog, https://github.com/jacobhammond/gfm-math-lint/blob/main/CHANGELOG.md
10
+ Author-email: Jacob Hammond <114956625+jacobhammond@users.noreply.github.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: gfm,katex,linter,markdown,mathjax,pre-commit
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Documentation
25
+ Classifier: Topic :: Software Development
26
+ Classifier: Topic :: Utilities
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+
31
+ # gfm-math-lint
32
+
33
+ [![ci](https://github.com/jacobhammond/gfm-math-lint/actions/workflows/ci.yml/badge.svg)](https://github.com/jacobhammond/gfm-math-lint/actions/workflows/ci.yml)
34
+ [![pypi version](https://img.shields.io/pypi/v/gfm-math-lint.svg)](https://pypi.org/project/gfm-math-lint/)
35
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jacobhammond/gfm-math-lint/blob/main/LICENSE)
36
+ [![python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
37
+
38
+ Zero-dependency, high-performance GitHub Flavored Markdown (GFM) linter and auto-fixer specializing in LaTeX/MathJax math syntax, CommonMark table escaping, fenced code nesting, Mermaid diagram boundaries, shell script expansion guards, and modern AVIF asset recommendations.
39
+
40
+ ---
41
+
42
+ ## Key Features
43
+
44
+ - **Pure Standard Library**: Zero third-party runtime dependencies. Runs instantly with Python 3.11+.
45
+ - **8-Zone Lexical State Machine**: High-fidelity tokenizer classifying document lines into `FRONTMATTER`, `FENCED_CODE`, `INLINE_CODE`, `DISPLAY_MATH`, `INLINE_MATH`, `TABLE`, `COMMENT`, and `PROSE`. Prevents false positives by ensuring rules only inspect their designated semantic zones.
46
+ - **Reverse-Offset Edit Algebra**: Deterministic bottom-up, right-to-left fix application guaranteeing idempotency: `fix(fix(c)) ≡ fix(c)`.
47
+ - **Unified Diff & In-Place Fixing**: Inspect exact changes in advance with `--diff` or auto-fix in-place with `fix` / `--fix`.
48
+ - **GitHub Rendering Oracle (`verify`)**: Directly audits documents against GitHub's live REST API (`POST /markdown` or `gh api /markdown`) to detect unrendered math in prose, HTML tag collisions, and broken table grids.
49
+ - **KaTeX Browser Preview with Zero Red Boxes**: Generates standalone HTML (`--html`) or opens a live browser preview (`--browser`) with automated HTML entity normalization (`&amp;gt;` → `>`) and duplicate MathML clipboard suppression (`output: "html"`), delivering clean math preview across Firefox, Chrome, and Safari.
50
+ - **Safe GitHub Commenting**: Post markdown-rendered comments to PRs and issues via `gh` CLI with stdin streaming (`--body-file -`), preventing shell expansion corruption of LaTeX equations, with live `--preview` rendering.
51
+ - **Pre-commit Native**: Exit code contract (`0` clean, `1` violations/fixes, `2` error) designed specifically for CI/CD and git hook workflows.
52
+
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ### With `uv` (Recommended)
59
+
60
+ ```bash
61
+ uv tool install gfm-math-lint
62
+ ```
63
+
64
+ ### With `pip`
65
+
66
+ ```bash
67
+ pip install gfm-math-lint
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Usage
73
+
74
+ ### 1. Checking & Fixing Markdown Files
75
+
76
+ Check all markdown files in a directory or individually, preview diffs, and automatically fix violations:
77
+
78
+ ```bash
79
+ # Check files for violations
80
+ gfm-math-lint check docs/ README.md
81
+
82
+ # Output violations in GitHub Actions workflow command format (::warning / ::error)
83
+ gfm-math-lint check --format github docs/
84
+
85
+ # Specify an explicit configuration file
86
+ gfm-math-lint check --config pyproject.toml docs/
87
+
88
+ # Preview automated fixes as a unified diff without modifying files
89
+ gfm-math-lint check --diff docs/
90
+
91
+ # Automatically fix violations in-place using the 'fix' shortcut
92
+ gfm-math-lint fix docs/ README.md
93
+
94
+ # Or equivalently via the check subcommand:
95
+ gfm-math-lint check --fix docs/ README.md
96
+ ```
97
+
98
+ ### 2. Scanning Shell Scripts & Workflows
99
+
100
+ Scan bash/sh scripts and GitHub Actions workflow files for unsafe double-quoted body arguments (`-b "..."` or `--body "..."`) in `gh pr comment` / `gh issue comment` commands that expose LaTeX `$x$` expressions to shell expansion:
101
+
102
+ ```bash
103
+ gfm-math-lint check-shell .github/workflows/ scripts/
104
+ ```
105
+
106
+ ### 3. Safe GitHub Comment Posting
107
+
108
+ Post comments safely without shell expansion or argument size limits by piping content via stdin directly to `gh`:
109
+
110
+ ```bash
111
+ # Preview rendered markdown via GitHub REST API (POST /markdown)
112
+ gfm-math-lint comment --preview --file report.md
113
+
114
+ # Post safely to a pull request
115
+ gfm-math-lint comment --pr 42 --file report.md
116
+
117
+ # Post safely to an issue
118
+ gfm-math-lint comment --issue 108 --file notes.md
119
+ ```
120
+
121
+ ### 4. Semantic Verification & Browser Preview (GitHub API Oracle)
122
+
123
+ Verify your documents directly against GitHub's live REST API markdown rendering oracle (`POST /markdown` or `gh api /markdown`) to catch unrendered math, HTML tag collisions, or table fracturing:
124
+
125
+ ```bash
126
+ # Verify all markdown files against GitHub API oracle
127
+ gfm-math-lint verify docs/ README.md
128
+
129
+ # Authenticate with an explicit GitHub token (or uses $GITHUB_TOKEN / $GH_TOKEN)
130
+ gfm-math-lint verify --token "$GITHUB_TOKEN" docs/
131
+
132
+ # Verify and export full rendered HTML preview
133
+ gfm-math-lint verify --html preview.html docs/specification.md
134
+
135
+ # Verify and immediately launch KaTeX-rendered preview in local web browser
136
+ # (Features automated entity normalization & MathML duplicate clipboard suppression)
137
+ gfm-math-lint verify --browser docs/specification.md
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Rule Catalog
143
+
144
+ ### Math & LaTeX Rules (`GFM-M**`)
145
+
146
+ | Rule ID | Name | Severity | Auto-Fix | Description |
147
+ | :--- | :--- | :---: | :---: | :--- |
148
+ | `GFM-M01` | `math-code-fence-to-display` | Error | Yes | Convert `` ```math `` code fences to standalone `$$` display math blocks. |
149
+ | `GFM-M02` | `display-math-isolation` | Error | Yes | Enforce display math `$$` opening/closing delimiters sit on isolated lines with blank line padding and no internal blanks. |
150
+ | `GFM-M03` | `text-mode-underscore-escape` | Error | Yes | Enforce double-escaped underscores (`\\_`) inside text-mode LaTeX macros (`\text{...}`, `\mathrm{...}`). |
151
+ | `GFM-M04` | `inline-math-whitespace` | Error | Yes | Strip leading and trailing inner whitespace from inline math delimiters (`$ x $` → `$x$`). |
152
+ | `GFM-M05` | `curly-brace-escape` | Error | Yes | Convert `\{` and `\}` delimiters in math expressions to `\lbrace` and `\rbrace` (e.g. `\left\{` → `\left\lbrace`). |
153
+ | `GFM-M06` | `relational-operator-collision` | Error | Yes | Encode relational operator (`<` to `\lt`) before letters in math to avoid HTML parser entity consumption. |
154
+ | `GFM-M07` | `asterisk-emphasis-collision` | Error | Yes | Convert raw asterisks (`*`) in superscripts, subscripts, and expressions to `\ast` to prevent GFM emphasis stripping. |
155
+ | `GFM-M08` | `disallowed-macro-conversion` | Error | Yes | Replace `\operatorname{...}` with `\mathop{\mathrm{...}}` and `\operatorname*{...}` with `\mathop{\mathrm{...}}\limits` to preserve operator limits. |
156
+ | `GFM-M09` | `align-environment` | Error | Yes | Replace unsupported `align`/`align*` environments with KaTeX `aligned` environment. |
157
+ | `GFM-M10` | `currency-dollar-sign` | Warning | Yes | Escape currency dollar signs on prose lines containing multiple dollars or coexisting with math (`$10 to $20` → `\$10 to \$20`). |
158
+ | `GFM-M11` | `latex-delimiters-conversion` | Error | Yes | Convert standard LaTeX display delimiters (`\[ ... \]`) to `$$` and inline delimiters (`\( ... \)`) to `$`. |
159
+ | `GFM-M12` | `inline-math-underscore-collision` | Error | Yes | Escape unescaped underscores (`\_`) in inline and single-line display dollar math spans to prevent CommonMark emphasis collision (`<em>...</em>`). |
160
+ | `GFM-M13` | `math-delimiter-boundaries` | Error | Yes | Enforce GitHub-compliant math delimiter boundaries: combine hyphenated math pairs (`$A$–$B$` → `$A\text{–}B$`), separate glued units (`$15\ ^\circ$C` → `$15\ ^\circ$ C`), and prevent bracket collisions (`$G(L)$)` → `$G(L)$ )`). |
161
+
162
+ ### Table Rules (`GFM-T**`)
163
+
164
+ | Rule ID | Name | Severity | Auto-Fix | Description |
165
+ | :--- | :--- | :---: | :---: | :--- |
166
+ | `GFM-T01` | `table-pipe-collision` | Error | Yes | Escape raw pipes in table math spans (`&#124;`) and code spans (`\|`) to prevent column fracturing. |
167
+
168
+ ### Code & Fence Nesting Rules (`GFM-C**`)
169
+
170
+ | Rule ID | Name | Severity | Auto-Fix | Description |
171
+ | :--- | :--- | :---: | :---: | :--- |
172
+ | `GFM-C01` | `nested-fence-length` | Error | Yes | Ensure outer fenced code blocks enclosing inner backtick fences use strictly longer backtick runs per CommonMark §4.5. |
173
+ | `GFM-C02` | `inline-code-backticks` | Error | Yes | Ensure inline code spans containing backticks use multi-backtick delimiters (`` `...` ``) per CommonMark §6.3. |
174
+
175
+ ### Diagram Rules (`GFM-D**`)
176
+
177
+ | Rule ID | Name | Severity | Auto-Fix | Description |
178
+ | :--- | :--- | :---: | :---: | :--- |
179
+ | `GFM-D01` | `mermaid-identifier` | Warning | No | Enforce alphanumeric IDs for Mermaid subgraphs and multi-word node labels with explicit quoted titles. |
180
+ | `GFM-D02` | `code-fence-symmetry` | Error | Yes | Require identical opening and closing fence lengths and characters for code blocks. |
181
+
182
+ ### Shell Scanner Rules (`GFM-S**`)
183
+
184
+ | Rule ID | Name | Severity | Auto-Fix | Description |
185
+ | :--- | :--- | :---: | :---: | :--- |
186
+ | `GFM-S01` | `unsafe-shell-expansion` | Error | No | Scan shell scripts and workflows for unsafe double-quoted body arguments in `gh` CLI invocations. |
187
+
188
+ ### Asset & Image Rules (`GFM-I**`)
189
+
190
+ | Rule ID | Name | Severity | Auto-Fix | Description |
191
+ | :--- | :--- | :---: | :---: | :--- |
192
+ | `GFM-I01` | `image-avif-format` | Warning | No | Enforce modern `.avif` format for markdown images and figures. |
193
+
194
+ ---
195
+
196
+ ## Architectural Rationale & Standards Alignment
197
+
198
+ `gfm-math-lint` is engineered around the multi-stage parsing pipeline implemented by GitHub's rendering engine (`cmark-gfm` followed by client-side MathJax). Understanding this compilation model clarifies why these rules exist, why specific conversions are preferred, and how they align with authoritative web standards.
199
+
200
+ ### 1. The Multi-Stage GFM Parsing Pipeline
201
+
202
+ When GitHub processes a Markdown document containing mathematical formulas, it executes stages in strict sequence:
203
+ 1. **CommonMark / GFM Inline Parser (`cmark-gfm`)**: Parses block structure, escapes, links, code spans, HTML tags, and emphasis.
204
+ 2. **HTML Sanitization**: Filters and scrubs disallowed HTML elements and attributes.
205
+ 3. **Client-Side Math Engine (MathJax)**: Identifies delimited math zones (`$...$` and `$$...$$`) and compiles LaTeX syntax into HTML/MathML DOM nodes.
206
+
207
+ Because **Markdown parsing executes before LaTeX parsing**, standard TeX escaping conventions can be intercepted and destroyed by CommonMark.
208
+
209
+ ### 2. CommonMark §2.4 Backslash Escapes
210
+
211
+ Per [CommonMark Spec 0.30 §2.4 (Backslash escapes)](https://spec.commonmark.org/0.30/#backslash-escapes):
212
+ > Any ASCII punctuation character may be backslash-escaped:
213
+ > `! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~`
214
+ > Backslashes before other characters are treated as literal backslashes.
215
+
216
+ This rule directly dictates the design of several core rules:
217
+ - **`GFM-M03` (`text-mode-underscore-escape`)**: In LaTeX, an underscore inside text mode is escaped as `\_` (e.g. `\text{ave\_shift}`). However, because `_` is one of CommonMark's 32 ASCII punctuation characters, `cmark-gfm` strips the backslash during stage 1. KaTeX receives `\text{ave_shift}`, triggering a fatal syntax error. `GFM-M03` enforces double-escaped backslashes (`\\_`), ensuring one backslash survives stage 1 to reach KaTeX as `\_`.
218
+ - **`GFM-M05` (`curly-brace-escape`)**: In TeX, dynamic curly braces are written `\left\{ ... \right\}`. Because `{` and `}` are CommonMark ASCII punctuation characters, the backslashes are consumed during stage 1. KaTeX receives `\left{`, throwing `Missing or unrecognized delimiter for \left`. `GFM-M05` converts `\{` and `\}` to `\lbrace` and `\rbrace` (e.g. `\left\lbrace ... \right\rbrace`), which do not rely on fragile punctuation escapes.
219
+ - **CommonMark §6.1 / §6.3 Immunity of Code Spans**: Per [CommonMark Spec 0.30 §6.1](https://spec.commonmark.org/0.30/#code-spans), *"Backslash escapes do not work in code spans."* Inside backticks, backslashes are preserved literally. That is why backtick math (`` $`\text{ave_shift}`$ ``) is natively immune to backslash stripping, whereas standard dollar math requires double escaping.
220
+
221
+ ### 3. Delimiter Portability & Standards Alignment
222
+
223
+ #### `GFM-M01`: Why Convert `` ```math `` to `$$`?
224
+ [GitHub's official guide on writing mathematical expressions](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions) documents ```` ```math ```` code fences as an alternative display block syntax.
225
+ However, `gfm-math-lint` converts ```` ```math ```` to canonical `$$\n...\n$$` blocks for cross-platform portability:
226
+ - Outside GitHub's proprietary web view, tools such as **VS Code Markdown Preview, PyCharm, Obsidian, Jupyter Notebooks, Pandoc, MkDocs (Material), and Sphinx** do *not* recognize ```` ```math ```` as math blocks, rendering them as unrendered code fences.
227
+ - Converting to standalone `$$` ensures 100% universal rendering across all major Markdown and LaTeX tooling while rendering flawlessly on GitHub.
228
+
229
+ #### `GFM-M11`: Why Convert `\[ ... \]` and `\( ... \)`?
230
+ Standard LaTeX documents written for paper publishing or ArXiv use `\[ ... \]` for display math and `\( ... \)` for inline math.
231
+ - GitHub's Markdown parser does **not** recognize `\[ ... \]` or `\( ... \)` as math delimiters.
232
+ - Furthermore, per CommonMark §2.4, `[` and `(` are ASCII punctuation characters. When `cmark-gfm` runs, it strips the backslashes, leaving raw unrendered text (e.g., `[ x = 1 ]` or `( x + y )`).
233
+ - `GFM-M11` converts standard LaTeX delimiters to GitHub-compatible delimiters (`$$` and `$`).
234
+
235
+ #### `GFM-M02`: Display Math Blank Line Isolation
236
+ GitHub Docs notes that display blocks can be preceded by a backslash newline. However, `gfm-math-lint` enforces full blank line padding above and below `$$` display blocks:
237
+ - CommonMark §4.5 (Lazy Paragraph Continuation) allows lines following a paragraph to be grouped into the same block unless separated by a blank line.
238
+ - Enforcing blank lines guarantees clear AST separation and satisfies common Markdown linters such as `markdownlint` (rule MD031).
239
+
240
+ #### `GFM-M10`: Currency Dollar Signs (`\$` vs `<span>$</span>`)
241
+ GitHub Docs mentions using `<span>$</span>` to prevent literal dollar signs from triggering math rendering.
242
+ `gfm-math-lint` deliberately enforces backslash escaping (`\$` per CommonMark §2.4) instead:
243
+ - `\$` maintains pure, readable Markdown without polluting documents with raw HTML tags.
244
+ - Both GitHub and CommonMark officially specify `\$` as an escaped literal character.
245
+
246
+ #### `GFM-M13`: GitHub Delimiter Boundary & Punctuation Constraints
247
+ GitHub's inline math parser enforces strict boundary conditions on opening and closing `$` delimiters:
248
+ - **Opening `$` Delimiter Boundary:** An opening `$` is only parsed as math if preceded by whitespace, start-of-line, or an opening parenthesis `(`. Preceding punctuation characters such as hyphens (`-`), en-dashes (`–`), em-dashes (`—`), slashes (`/`), or brackets (`[`) cause GitHub to reject the `$` as math. Expressions like `$A$–$B$` leave `–$B$` as plain text. `GFM-M13` combines compound expressions into `$A\text{–}B$`.
249
+ - **Closing `$` Delimiter Boundary:** A closing `$` is rejected if immediately followed by an ASCII letter or digit (`[a-zA-Z0-9]`). Units glued to math expressions (`$15\ ^\circ$C`, `$0.759$dB`) fail to close and render as literal text. `GFM-M13` automatically separates trailing alphanumeric units with a space (`$15\ ^\circ$ C`, `$0.759$ dB`).
250
+ - **Parenthesis Collisions:** In nested parenthetical prose like `($A = B$ instead of $G(L)$)`, the closing `$` directly preceding `)` collides with bracket matching in `cmark-gfm`. `GFM-M13` inserts a space (`$G(L)$ )`) ensuring flawless delimiter recognition.
251
+
252
+ ---
253
+
254
+ ## Configuration
255
+
256
+ Configure `gfm-math-lint` in your project's `pyproject.toml` under `[tool.gfm-math-lint]`:
257
+
258
+ ```toml
259
+ [tool.gfm-math-lint]
260
+ # Files or glob patterns to exclude from linting
261
+ exclude = [
262
+ "docs/archive/*",
263
+ "vendor/**",
264
+ ]
265
+
266
+ # Globally ignored rule IDs
267
+ ignore = [
268
+ "GFM-I01", # Allow PNG/JPEG images without AVIF conversion warning
269
+ ]
270
+
271
+ # Per-file rule ignores
272
+ [tool.gfm-math-lint.per-file-ignores]
273
+ "docs/legacy/*.md" = ["GFM-M08", "GFM-M10"]
274
+ "docs/math_guide.md" = ["GFM-M05"]
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Inline Rule Suppression
280
+
281
+ Suppress rules directly in Markdown files using HTML comments:
282
+
283
+ ### Multi-line Block Suppression
284
+
285
+ ```markdown
286
+ <!-- gfm-math-lint-disable GFM-M02, GFM-M04 -->
287
+ $$
288
+ E = mc^2 \\
289
+ $$
290
+ <!-- gfm-math-lint-enable GFM-M02, GFM-M04 -->
291
+ ```
292
+
293
+ ### Next-Line Suppression
294
+
295
+ ```markdown
296
+ <!-- gfm-math-lint-disable-next-line GFM-M10 -->
297
+ The price varied between $10 and $20 per unit.
298
+ ```
299
+
300
+ Omit the rule IDs to disable or enable all rules for the target scope:
301
+
302
+ ```markdown
303
+ <!-- gfm-math-lint-disable-next-line -->
304
+ | Metric | $||x|| < 1e-9$ |
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Pre-Commit Hook Integration
310
+
311
+ ### Using `gfm-math-lint` in Your Repository
312
+
313
+ Add `gfm-math-lint` to your `.pre-commit-config.yaml` to validate equations and guard shell scripts:
314
+
315
+ ```yaml
316
+ repos:
317
+ - repo: https://github.com/jacobhammond/gfm-math-lint
318
+ rev: v0.1.0
319
+ hooks:
320
+ # Validates math, table, code fence, and boundary issues
321
+ - id: gfm-math-lint
322
+ # Guards against unsafe shell expansions in CI workflows and scripts
323
+ - id: gfm-shell-lint
324
+ ```
325
+
326
+ ---
327
+
328
+ ## Contributing & Development
329
+
330
+ ```bash
331
+ # Clone the repository
332
+ git clone https://github.com/jacobhammond/gfm-math-lint.git
333
+ cd gfm-math-lint
334
+
335
+ # Install development virtualenv and tools with uv
336
+ uv sync
337
+
338
+ # Install local pre-commit git hooks
339
+ uv run pre-commit install
340
+
341
+ # Run all pre-commit hooks across the repository
342
+ uv run --no-sync pre-commit run --all-files
343
+
344
+ # Run the test suite with coverage
345
+ uv run pytest
346
+
347
+ # Run linting and formatting checks
348
+ uv run ruff check .
349
+ uv run ruff format --check .
350
+
351
+ # Run strict type checking
352
+ uv run mypy src tests --strict
353
+
354
+ # Run self-checks on repository documents and scripts
355
+ uv run gfm-math-lint check README.md CHANGELOG.md CONTRIBUTING.md docs/ .github/
356
+ uv run gfm-math-lint check-shell .github/
357
+ ```
358
+
359
+ ---
360
+
361
+ ## License
362
+
363
+ MIT License. See [LICENSE](https://github.com/jacobhammond/gfm-math-lint/blob/main/LICENSE) for details.