Skip to main content

check-literal-math-letters.yml

Diff-scoped, warn-only check that flags literal Latin and Greek letters in math where a semantic macro from Morrison-Lab/macros belongs. The lab’s rule is that every math symbol is a semantic macro (\outvar, \predvar, \param, …), so no literal letter appears in math outside a passage about the notation itself, except a one-off with no semantic category. This is check 1 of gha#1077.

The check flags, inside inline ($...$) and display ($$...$$, \begin{equation}, \begin{align}, and the like) math:

Each finding names the letters on the line. Where the macro library has a macro for the letter, the message suggests it: y for \outvar, Y for \Outvar, x for \predvar, X for \Predvar, n for \nobs, L for \loss, \theta for \param, \beta for \regcoef, \varepsilon for \eps and \mu for \mean. These suggestions are conditional (“if this is the outcome variable”), since a letter alone does not say what it names.

The check does not flag:

The check is strict: there is no allow-list for indices or dummy variables. The rule says to find the category first, so the check starts strict and the false-positive rate is measured (see below) before anyone decides to relax it.

Scope

Only lines a pull request adds are reported, so a corpus that already carries literal letters is not reflagged on every unrelated edit. The whole file is parsed, because a display block opens on one line and its letters sit on a later one, and only findings on added lines are kept. An added line whose exact text was also deleted in the same diff counts as moved rather than new.

With no base-ref, or when the diff cannot be computed (a shallow clone, an unresolvable ref), the check skips with a warning. It never falls back to a whole-tree scan, which would reflag every existing letter. Set whole-tree: true to scan every tracked file in full on purpose.

Every run prints how many lines, files and math spans it examined, so a run that examined nothing reads differently from one that examined everything and found nothing.

Limitations

  • Inline math that spans a line break is not recognized. A $...$ pair must open and close on one source line, which semantic line breaks normally preserve.
  • A brand new untracked file is invisible to git diff. Stage it to have a local run examine it.
  • A run of two or more letters (xy) is not a single letter and is not flagged.
  • A passage about the notation must be in a .notation div to be exempt. A heading titled “Notation” is not enough.

False-positive rate

Measured on 2026-10-16 in whole-tree mode over chapters/*.qmd:

Repository Files Math spans Lines flagged
psw 37 376 242 of 5,624 lines (4.3%)
lds 1,658 15,856 7,312 of 78,669 lines (9.3%)

A random sample of 65 flagged lines across the two repositories held no hit outside math, so the parser’s own error rate is low. Whether a literal index letter is a false positive is a policy question for the maintainers: lines whose only letters are the common index letters i j k l m n t are 5% of the psw hits and 11% of the lds hits, so an allow-list for indices would remove little of the volume.

Inputs

Input Type Default Description
globs string '*.qmd *.md *.Rmd' Space-separated git pathspecs of tracked files to check (recursive by default).
paths-ignore string '' Comma- or newline-separated glob patterns (relative paths) to skip. Supports *, ? and recursive **.
fail boolean false Fail the workflow when a literal letter is found. Warn-only by default.
whole-tree boolean false Scan every tracked file in full instead of only a PR’s added lines.
python-version string '3.x' Python version to set up.
mode string 'all' all flags every standalone letter. known flags only a letter or Greek command that has a semantic macro (y, Y, x, X, n, L, e, \theta, \beta, \varepsilon, \mu), so index letters pass.
deprecated-files string '' Space-separated interpretation files (for example latex-macros/interpretations.tsv). A row saying deprecated, use \x marks that macro deprecated, and a use on an added line is flagged.
submodules string 'false' Passed to actions/checkout, so a macros submodule is available for deprecated-files.
runs-on string empty (CI_RUNNER variable, else ubuntu-latest) Runner label for the job. Use a self-hosted label only for a private repository; never for a public one, whose pull requests can run arbitrary code on it (#1167).

Permissions

Only contents: read (the default), so no permissions: block is needed.

Example

# Copy to .github/workflows/check-literal-math-letters.yml in your repo.
# Diff-scoped check: flags literal Latin and Greek letters in math (a bare `y`,
# `\theta`) on lines a PR adds, where a semantic macro from Morrison-Lab/macros
# (`\outvar`, `\predvar`, `\param`) belongs. Warn-only by default; skips code,
# comments, `\text{}`-like commands and any `::: {.notation}` div.
# Only contents:read is needed (the default), so no permissions block is required.
name: Check literal letters in math

on:
  push:
    branches:
      - main
  pull_request:
  workflow_dispatch:

jobs:
  check:
    uses: Morrison-Lab/gha/.github/workflows/check-literal-math-letters.yml@v3
    # with:
    #   globs: '*.qmd *.md *.Rmd'              # pathspecs to check (recursive)
    #   paths-ignore: 'chapters/outtakes/**'   # skip known-noisy paths
    #   fail: true                             # block instead of warn (defaults to false)
    #   whole-tree: true                       # scan every line, not just a PR's additions
    #   runs-on: private-runner                # self-hosted runner label (private repositories only)

See examples/check-literal-math-letters.yml for the full caller stub.