The linters

The 49 checkers that screen every entry: 28 deterministic, 14 hybrid (a deterministic gate book-wide plus a language-model pass per entry), 7 language-model reviews per entry. This page is generated from the manifest scripts/LINTERS.txt and from the scripts themselves at every build.

All run from the repo root; most take --key <entry> to restrict to one glossary entry. All are read-only unless a --fix flag is named below. None is build-breaking on its own, but the parbreak findings WILL break the main `make`.

Field coverage: every linter covers an entry's ABSTRACT as well as its description — the span-based checkers (articles, plurals, symbols, raw notation, parbreaks, bullshit, definite articles) naturally (articles/ plurals even reset first-use per field, matching the export), and check_claims (audits abstract sentences), check_wording_consistency / check_word_repetition (See-also list blanked instead of truncating there), check_seealso_usage (abstract \gls links count as usage), and check_gls_coverage (abstract scanned; matches tagged [abstract]) explicitly. Exception: check_demo_coverage maps the DESCRIPTION's paragraphs only — the abstract is a summary, not demo material.

Running Them

scripts/export_terms.py only builds (term-wise PDFs + Springer zips) and
runs NO linters. Run them on demand with the orchestrator:

  python3 scripts/run_linters.py                 # every linter, whole book
  python3 scripts/run_linters.py --key <k>       # restrict to one entry
  python3 scripts/run_linters.py --key <k> --pdf # + rendered-PDF passes
                                                 #   (export first)
  python3 scripts/run_linters.py --no-llm        # skip the OpenRouter step
  python3 scripts/run_linters.py --bib           # include check_literature
                                                 #   (extra deps + network)

check_symbol_semantics is skipped automatically when OPENROUTER_API_KEY is
not set. Exit code 1 when any checker reported findings. Individual
checkers can still be run directly with the flags listed above.

Typical Per-Entry Revision Sweep

  python3 scripts/run_linters.py --key <k>      # all linters
  python3 scripts/export_terms.py               # rebuild PDFs + zips
  python3 scripts/run_linters.py --key <k> --pdf  # rendered ground truth

The Dashboard

lint_dashboard.py       NOT a checker: it runs the suite per TERM and writes
                        one self-contained HTML page per term into the
                        untracked linterdashboards/, plus an index across
                        every term linted.

                        run_linters.py prints ~36 reports as one scroll of
                        text. That is right for one entry under revision and
                        wrong for a submission pass: nothing separates the
                        checker that found a real defect from the thirty that
                        ran clean. The page folds the clean ones away, groups
                        the rest by theme, and leads with a count of what
                        actually needs fixing.

                        The job list is NOT re-specified: it comes from
                        run_linters.build_jobs, so a checker added to the
                        runner appears here with no edit, and the two cannot
                        disagree about what "the suite" is. This file adds
                        only presentation -- a group, a blurb and a severity
                        per checker.

                        SEVERITY is the axis the raw text hides. build = fails
                        the main `make`; defect = a real defect in the artefact
                        Springer receives; advisory = a candidate, false
                        positives expected. Most of this suite is advisory BY
                        DESIGN, so a page showing 40 advisory hits beside one
                        PARBREAK has buried the thing that matters. One
                        severity per checker proved too coarse for
                        check_seealso_usage (247 advisory UNREACHED hits, but
                        a DUPLICATE/SELF/UNKNOWN key in the same output is a
                        defect and an UNKNOWN one breaks the build), so
                        ESCALATE gives a checker a base severity plus a
                        pattern that raises it when the hard class actually
                        appears in THIS term's output. check_figures escalates
                        the same way on UNLABELED/NOCAPTION/ORPHAN.

                        A checker the runner invokes book-wide even under
                        --key (check_literature, check_ref_identity) is
                        labelled "book-wide" on the card and left out of the
                        term's counts: its findings are the book's, not this
                        term's. Derived from the argv, so a checker that gains
                        --key stops being labelled by itself.

                        Counts come from the one convention the whole suite
                        shares: every checker ends with a line like
                        "12 bullshit/hedge occurrence(s) found". A parsed
                        count beats the exit code, because check_ref_identity
                        exits 1 while reporting "0 identity issue(s) over 0
                        source(s)" and a card reading FINDINGS over a report
                        saying zero is worse than no card.

                          python3 scripts/lint_dashboard.py --key overfitting
                          python3 scripts/lint_dashboard.py --springer --no-llm --jobs 4
                          python3 scripts/lint_dashboard.py --all --no-llm
                          python3 scripts/lint_dashboard.py --key sgd --pdf --open

                        --jobs runs several TERMS concurrently (each term's
                        checkers stay in order); the LLM checkers dominate the
                        wall clock. Exit 0 when no term has a build- or
                        defect-level finding.

Every rule and its screen

Every rule of CLAUDE.md maps to a checker or is marked "judgment". A rule with no screen has no memory: known issue 10 claimed "~190 instances" of first-person voice long after there were two. Add a row when a rule is added. Rows come from the rules, not from the bullet headings. The first version of this table had one row per bullet, so a rule stated INSIDE another rule's bullet got no row and the table silently claimed coverage for it: "short, standalone sentences; no comma/em-dash chaining" sits in the bullet on vague intensifiers, was mapped to check_bullshit, and check_bullshit screens words rather than sentence shape. A reviewer found the gap one commit later. The indented rows below are the rules found by re-reading the bullet bodies.

Style Rules
impersonal voice
check_house_rules FIRSTPERSON
no self-reference to the book
never "this dictionary"
entries stand alone
check_selfcontained EXTREF (a \ref to a label outside the entry — renders '??' in the standalone export), DEIXIS (positional back-references; advisory), ENTRYREF (prose naming the book's entry structure: "dedicated entries", "the entry \gls{x}", "the \gls{x} entry" — keep the \gls delegation, drop the word "entry"), + LLM standalone-reader pass (--key): DANGLING, ASSUMESNOTATION, OPAQUELINK
fixed-point interpretation
judgment (check_bertsekas reads for it)
>=1 application example
judgment (check_claims audits the ones present)
never open "In an MDP,
" .. check_paragraphs OPENSABSTRACT
American English -ize
check_spelling (US dictionary)
Latin phrases stay open
check_house_rules LATINHYPHEN
precise "model"
check_house_rules MODELVERB (fixed verbs only)
train a model / learn a hypo
consistent terminology
aligned with related entries
check_alignment SYMBOLCLASH (gate) + (LLM, --key) — the revision workflow's "alignment-check against the three most related terms", previously judgment
Conflict/Notationdrift/Termdrift
no big-O
check_house_rules BIGO (meaning-gated)
no bullshit words
dry register, no padding
no underspecified back-refs
no circular framing
check_circular CIRCULAR, RESTATED, INVERTED
properties fit their object
check_types TYPE (adjectives) + ISA (copula type errors: "one iteration
is an operator" — an iteration is the APPLICATION of one; caught by review 2026-09-06, kmeans, after the full pass missed it)
no academic-metaphor jargon
i.i.d. modifies RVs
correct collocations
no vague intensifiers/hedges
short, standalone sentences
check_house_rules CHAINED, LONGSENTENCE
no LLM-signature em-dashes
check_house_rules EMDASH (pair form; single em-dash: judgment)
words-as-words in \emph{}
check_house_rules WORDASWORD (trigger- noun mentions + quoted mentions; backlog at introduction: 22, a worklist)
plain English over jargon
no sentence opens on math
check_bertsekas SENTSTART
readable mathspeak
check_bertsekas MATHSPEAK ("Let $k>0$ be")
2-3-4: paragraph clause
check_bertsekas FOURLONG (sentence clause: TWOLINES under --hints; verb clause: the LLM small rule three-verbs) rule 8)
which/that
check_bertsekas WHICHTHAT
comma after i.e./e.g.
check_bertsekas ABBRCOMMA
no loose terms (slide 12)
check_bertsekas LOOSETERM ("random values", "chance"); "i.i.d. data" is check_house_rules IIDDATA
no unnecessary notation
check_bertsekas UNUSEDNOTATION
articles before abbreviations
check_articles (--pdf for ground truth)
algorithm abbreviation
open concrete, then abstract
check_paragraphs OPENSABSTRACT (heuristic: sentence one defines the term; plus the LLM pass with --key)
loss-specific
judgment (settled 2026-08-02, svm: the optimism reading of the penalty term is squared-error only; check_claims audits the citation such a passage carries)
route, never its ordinal
judgment (the regularization entry is the authority; fixed 2026-08-20)
the approximated function
judgment (check_claims reads the passage when asked; unnamed, the pillar fits anything)
Math Conventions
\text/\mathrm/\operatorname
check_math_style TEXTOP (operator names; \mathrm for a non-function symbol is correct and is not flagged)
never \text{} for operators
explicit domains
equations punctuated
macros first
semantic over generic macros
check_symbol_consistency (List of Symbols)
dataset split superscripts
use in each entry
judgment (no screen)
solutions wear a hat
check_house_rules HATSOLUTION (meaning-gated)
optimal objective VALUE
covered: the gate skips it
iteration symbols t / T
judgment
definite article for unique
every vector/matrix bold
subscripts index components
check_raw_notation (partial: the raw bold)
transpose ^{\top} braced
the intrinsic dimension
check_math_style SIMPLEXDIM (a bare \simplex{\nrcluster} / \sphere{\featuredim}, and \nrnodes indexing the sphere at all; a literal \sphere{1} is deliberate)
\dimlocalmodel is FL-only
check_math_style ALIASSCOPE (flags the macro in any file but chapter_fl.tex; the 2026-08-28 sweep cleared 62 uses)
Glossary Template And Demos
first={ABBR}
check_parenthetical_names FIRSTFORM (fires when the name's parenthetical equals text= — the abbreviation — and first= differs from name=)
stay out of first/text
Synonyms block is plain text
never guess a key
judgment + the main make (an UNKNOWN \gls key fails it) + dictml_lsp live diagnostics; See-also UNKNOWN is check_seealso_usage's
anchored to OUT_DIR
check_demo_plots (AXISLABEL, NOTITLE, OVERDRAW, BWPROOF, LEGENDBOX, HARDPATH)
entry's vocabulary
check_demo_scope a fallback from section comments and drops weak paragraph pairings)
changed demo (2026-09-06)
the gate IS the screen: run the demo, check_demo_plots --key, check_demo_scope --key, ruff check --select F (third-party; if absent, note the skip in the commit message)
Figures And Structure
every entry carries a figure
black/white proof
check_figures bw-proof (LLM, grayscale)
figure hygiene
check_figures (UNLABELED/ORPHAN/...)
prose introduced first
check_figures CAPTIONFIRST
captions without end period
check_figures CAPPUNCT
legends borderless
check_figures LEGENDBOX
no blank line in an entry
no \\ after \end{figure}
See also last, used, no dupes
check_seealso_usage positional key it originally took)
back-of-book index
check_index_coverage NOINDEX (real; the description calls no \index) + INDEXNAME (--hints, advisory: name= not among the indexed terms — a stale index after a rename, but plural/spelling variants are benign)
claims carry evidence
check_claims (+ its source pass over refs/)
the defining works are cited
check_canonical_refs (+ --currency: the 'recent' role, a must-cite survey/ development from --since onward)
citation pinpoints
standalone retrieval snippet
check_abstract_hook UNNAMED, DEIXIS, LONGHOOK, SHORTHOOK, DISPLAYMATH, ABSCITE, ABSFIGREF (gate) + LLM snippet verdict (--key) — the abstract's first sentence is published verbatim as the entry's llms.txt / terms.json / JSON-LD line, the bait that gets it found and cited
claim the abstract makes
check_abstract_coverage ABSGLS (gate: a term linked only in the abstract) + per-claim LLM verdict (--key): covered / weaker / contradicted / uncovered — the abstract is what a search hit or an LLM answer quotes, the description is where the reader then lands
validate
check_export_surfaces CFF-*, ZENODO-*, DOIMISMATCH, AUTHORDRIFT (root, always) + TJ-*, LLMS-*, HTML/CITEBOX/JSONLD/ SCHOLAR/CANONICAL (the last termsite/ build; DEFECT, not advisory)
main-ML-picture discipline
check_ml_picture UNFORMALIZED (gate) + GOALCONFLATED, MIXEDFORM, UNANCHORED (LLM, --key): a beyond-training promise must name its formalization of "any data point" (probabilistic i.i.d./risk, robustness/Lipschitz, or online/regret)

The checkers

Grouped as the manifest groups them. Each checker is listed with what it flags (from its own docstring), its kind, its command-line flags, the finding codes it prints, and the documentation of the manifest. Findings are advisory unless the text says otherwise: they name candidates, and the decision stays with the author.

Rendered-Text Checks (3) · Cross-Reference Checks (1) · Prose / Style Checks (16) · Math-Notation Checks (5) · Evidence Checks (13) · Build-Safety Checks (1) · Bibliography Checks (9) · Other Checks (1)

RENDERED-TEXT CHECKS (how an entry reads term-wise)

check_articles deterministic

check (and optionally fix) a/an before \gls{} TERM-WISE.

flags: --fix --key --pdf --selftest

a/an before \gls{} for the TERM-WISE rendering (first use expands, later uses abbreviate) and before literal abbreviations in prose ("a MDP"). --pdf reads termdrafts/*.pdf via pdftotext for a ground-truth pass; --fix applies corrections. --key names the ENTRY under review (its whole block), as in every other checker; it used to select the LINKED term, which made a per-entry run report on an entry it had not examined. --selftest pins that, and fails against the old behaviour.

check_plurals deterministic

check singular/plural consistency of \gls{} vs \glspl{}.

flags: --hints --key --pdf

singular/plural consistency of \gls{} vs \glspl{}: hand-pluralized singular calls (\gls{...}s) and singular determiners before \glspl{}. --hints adds attributive-prone cases; --pdf checks rendering. A third, hint-only code catches a plural determiner separated from the macro by one or two modifiers ("These user \gls{prediction} are obtained"), which the adjacency rule cannot see and which reached a rendered PDF unflagged. It is a SEPARATE pattern: widening the shared one put six false positives into the hard NUMBER code (intervening verbs) and swallowed real determiners. Attributive uses are suppressed, not warned about -- a head noun or another glossary macro after the term means it modifies something -- which takes the class from 22 book-wide hits to 0.

check_parenthetical_names deterministic

Check that a parenthetical disambiguator on a glossary entry's name= field does NOT leak into in-text rendering fields.

flags: --key · codes: FIRSTFORM SYNGLS

a parenthetical disambiguator in an entry's name= (e.g. "kernel (maths)") must not leak into the in-text rendering fields (first/text/plural). Also the entry-template rules FIRSTFORM (an abbreviated term's first= must equal name=, so the first \gls use typesets the full-form introduction; caught rsquared, majmin, netexpfam on its first run, 2026-09-06) and SYNGLS (the Synonyms block is plain text, no \gls).

CROSS-REFERENCE CHECKS

check_seealso_usage deterministic

double-check that every term in an entry's "See also" block is actually USED by the entry, directly or indirectly.

flags: --depth --key --verbose

each "See also" key must be USED by the entry: directly (\gls{} in the body) or indirectly (reachable through the body-link graph within --depth hops, default 2). UNREACHED keys are advisory candidates for removal (or for a missing body link); DUPLICATE / SELF / UNKNOWN keys are hard flags. --verbose shows the direct/indirect verdicts with hop distances.

PROSE / STYLE CHECKS

check_bullshit deterministic

flag the banned "bullshit words" and vague intensifiers/ hedges from CLAUDE.md in a glossary entry's prose.

flags: --key --list --selftest --window

banned bullshit words (paradigm, leverage, ...) and vague intensifiers/hedges from CLAUDE.md in description/abstract prose. Advisory; known benign hits (statistical leverage, legal framework) are for eyeballing.

check_register hybrid

dry register: say it in fewer words, or delete them.

flags: --api-key --base-url --dry-run --key --model --no-llm --rule --selftest · codes: NARRATION METATEXT STANCE LIGHTVERB TIGHTEN

dry register: does the sentence say in twelve words what six would say, and does any word carry nothing? The gap is measured, not assumed. Against the cav entry before 2f18636 -- "A user wants to know whether it arrives at that label through stripes", "One way to explain the behavior ... is by using", "genuinely nonlinear", "Understandable it is by construction" -- check_bullshit reported 0, check_house_rules 0, check_types 0, and check_jargon's LLM pass returned "clean"; check_pruning found one candidate, in the abstract, for a different reason. Nothing was broken: the word-list screens hold WORDS (hype, metaphor, hedges) and none of those phrases uses a listed word, while check_pruning judges whole SENTENCES, all of which had to stay. Register is the question in between, and it is inseparable from pruning -- both ask what can go -- so this screen asks them together. LEXICAL LAYER (no key, book-wide): NARRATION (the prose narrates a reader or user instead of stating the fact), METATEXT (a sentence about the exposition: "One way to X is", "It is worth noting that", "Note that"), STANCE (an adverb grading the claim instead of making it: clearly, genuinely, of course, indeed, in fact; "simply connected" is excluded, being a topological term), LIGHTVERB ("makes use of" -> uses, "provides a description of" -> describes). Whitespace is collapsed before matching, because blanking a \gls{...} leaves as many spaces as the macro had characters and that pushed the real cav sentence out of the METATEXT window. LLM LAYER (--key, one call): every sentence asked what can be said in fewer words (TIGHTEN, replacement written out) and which words carry nothing (DELETE-WORDS); punctuation, voice and equal-length synonym swaps are explicitly out of scope, and a replacement must obey house style. The vocabularies of check_bullshit and check_jargon are deliberately not repeated: a word belongs to one screen, so a finding names one fix. An EXPLETIVE rule for empty subjects was written and REMOVED at 0 true positives out of 8. Both its shapes are ambiguous in this subject matter: "there is a $\delta$ such that" is the existential quantifier written out (and guarding the idiom fails on "there is an index such that"), and "It is the smallest convex set that contains these points" is a definition whose "that" opens a restrictive relative clause, structurally identical to a cleft. Empty subjects are left to the LLM pass, whose prompt names them. --key / --rule / --no-llm / --selftest (18 cases). Book-wide: 43 METATEXT, 39 STANCE, 0 the rest -- NARRATION and LIGHTVERB caught the cav sentences and now stand as regression guards. Advisory.

check_jargon hybrid

flag academic-metaphor jargon that the fixed word list in check_bullshit.py cannot catch.

flags: --api-key --base-url --dry-run --hints --key --list --model --no-llm --selftest

academic-metaphor jargon check_bullshit's word list can't catch. Lexical layer (book-wide): operationalize, "algorithmic form", machinery, "flavors of", instantiate, embody, workhorse, "read off", "rests on", "lives on", ingredients, recipe, "umbrella term", levers, "boils down to", "sweet spot", "rule of thumb", vanilla, ... (--list prints all). --hints adds the soft layer: figurative verbs with a technical sense next door (captures, encodes, "sits in", "at hand"), which only the LLM pass can judge. --selftest pins 15 cases. Runs on save in the LSP since 2026-09-18. LLM pass (--key): flags sentences that gesture metaphorically instead of making a checkable claim, and re-judges the FULL bullshit vocabulary in context (a listed word passes only with an overwhelming argument, e.g. 'legal framework' as the literal legal term). Advisory.

check_demo_scope deterministic

flag concepts a demo shows the reader that its own dictionary entry never introduces.

flags: --key --min-len --springer --verbose

concepts a demo shows the reader that its own entry never introduces. Compares the demo's reader-visible surface (docstring, # comments, printed/check strings, matplotlib title/label/text) against the entry's \gls-resolved prose, and reports any other entry's display name that appears in the demo but nowhere in the entry. --key / --springer / --verbose. Advisory: ordinary-English uses ("by a wide margin") are expected hits; the fix is to reword the demo, or to add the concept to the entry when it belongs there.

check_demo_plots deterministic

hold the demos' matplotlib previews to the same rules the book applies to its TikZ/pgfplots figures.

flags: --key --rule --springer · codes: AXISLABEL NOTITLE OVERDRAW BWPROOF LEGENDBOX HARDPATH

the demos' matplotlib previews held to the book's figure rules (they are published beside the entries). AXISLABEL (both axes labelled), NOTITLE (the axes says what it shows), OVERDRAW (two bar series on one axes can hide each other — offset them or split the panel), BWPROOF (series told apart by colour alone — add hatch/linestyle/marker), LEGENDBOX (legend(frameon=False), the TikZ draw=none/fill=none rule), HARDPATH (every open/savefig/savetxt/... path anchored to OUT_DIR = Path(__file__).parent — a literal "pythondemos/x.png", a bare "x.png", or an absolute path is CWD/machine-dependent and broke real runs). Static AST analysis, no demo is executed. --key / --springer / --rule.

check_gls_coverage deterministic

Flag glossary-term display text that appears as plain prose inside an entry's description and could be wrapped in \gls{...} or \glspl{...}.

flags: --key --min-len --skip

glossary-term display text appearing as plain prose that could (should) be a \gls{} link — cross-reference coverage.

check_wording_consistency deterministic

flag entries that switch between synonyms.

flags: --key

synonym switching within one entry (map vs function, datapoint vs sample/instance, method vs approach, ... — the CLAUDE.md one-word-per-concept groups). Counts plain words AND \gls-linked uses; advisory (some mixes are legitimate).

check_annotation_coverage hybrid

does the suite catch what the author marked?

flags: --annotations --api-key --base-url --deterministic-only --dry-run --findings --key --model --pdf · codes: COVERED PARTIAL UNCAUGHT

does the suite catch what the author marked? Grades the automated findings against the author's annotated term PDF (tmp/<key>_Ju.pdf): per annotation COVERED / PARTIAL / UNCAUGHT (naming the checker that matches), plus a suite_gaps list — the UNCAUGHT classes are candidate new checkers.py; one gateway call; adapted 2026-09-08 from the sibling's annotation_coverage_lint_llm.py. TIMING: findings must come from the source state the PDF was annotated on — run BEFORE applying the revision, or in a worktree at the pre-revision commit (--findings takes a pre-captured sweep). Founding measurement (decisiontree round 2, pre-revision worktree, deterministic + 3 LLM checkers): 3/12 covered, 3 partial, 6 uncaught — the uncaught were content edits, algorithm-comparison claims and citation requests (the last would likely be covered had check_canonical_refs been in the captured sweep). Advisory.

check_alignment hybrid

cross-entry alignment of an entry with its most related entries (the ones it \gls-links).

flags: --api-key --base-url --key --model --no-llm --partners --selftest · codes: CONFLICT NOTATIONDRIFT TERMDRIFT

cross-entry alignment of an entry with its most related entries — the CLAUDE.md revision-workflow step ("alignment-check ... against its three most related terms"), previously judgment-only. Gate (book-wide, or --key): SYMBOLCLASH — gls-anchored symbol appositions ("the \gls{trainset} $\trainset$") harvested per entry, normalized by expanding the zero-argument macros of ml_macros.tex (aliases never clash) and stripping instance decorations (primes, single-letter indices; a roman-word superscript stays, which is what separates \trainset from a stray $\mathcal{D}^{(t)}$); a concept BOTH entries of a See-also edge introduce with disjoint symbols is a clash. Equationy fragments ($\vx \in \featurespace$) are never harvested. With --key, partners are the entry's See-also keys in curated order, then body links (reciprocal, then frequent); --partners N (default 3). LLM pass (--key): one call per partner pair over both descriptions — CONFLICT (incompatible claims), NOTATIONDRIFT, TERMDRIFT; complementary coverage is not a finding. Advisory; a shared letter may name a different local object, and the documented semantic-vs-generic split (\weights in an optimization entry vs \vx in a linear-algebra one) is a known-benign hit class. --selftest pins harvest, normalization, ranking and gate.

check_word_repetition deterministic

flag close repetitions of the same word.

flags: --k --key

the same word repeated within fewer than K words (K at the top of the script; --k overrides). Exempt: \gls-linked terms (repeating keyed nouns is required), the entry's own name words, stopwords, and words separated by a figure or display (character-distance guard). Advisory.

check_types deterministic

type-check predication: is this property defined for that object?

flags: --key --list --selftest

type check on predication: an adjective attached to an object for which the book defines a DIFFERENT property. The book measures a hypothesis space by its SIZE (VC dimension, Rademacher complexity), so "the hypothesis space is too RICH" makes a claim with no definition behind it, while the entry's own next paragraph makes the same claim in the defined vocabulary. Seeded by overfitting. A DENYLIST of (object, adjective) pairs, not an allowlist of permitted ones: an allowlist flags every ordinary adjective. Matches attributive ("a rich hypothesis space") and predicative ("the hypothesis space is too rich"). The hypothesis row also holds the REGULARIZATION family -- unregularized, regularized, pruned -- since all three forms of regularization act on the model, the loss or the training set, never on the hypothesis that comes out, so "the unregularized \gls{hypothesis}" attaches a property of the procedure to its output. "Trained hypothesis" is the same family and is deliberately NOT here: check_house_rules TRAINLEARN owns it, and a word belongs to one screen so a finding names one fix. The predicative gap admits at most three plain words and no punctuation, so a colon cannot hand the predicate to another subject. --list prints the table, --selftest pins nine cases. 3 findings book-wide. Advisory.

check_circular deterministic

flag circular framing: a sentence that concludes, or restates, membership in a category the surrounding prose has already granted.

flags: --key --selftest --verbose --window · codes: CIRCULAR RESTATED INVERTED

circular framing: a sentence that DERIVES or RESTATES membership in a category the prose already granted. An entry that announces "Three examples of T are ..." and then says of one of them "... is therefore a T" is arguing for its own premise. CIRCULAR when a conclusion marker is present (therefore, thus, hence, so), RESTATED when the sentence merely repeats the membership. Requires BOTH an announced category and an enumeration anaphor ("the first of them", "the second example", "it"), which is what keeps ordinary syllogisms quiet: deriving a category for a NAMED object from a stated rule is argument, not circularity. A membership claim followed by a qualifier defines rather than restates ("is a Hilbert space OF FUNCTIONS whose ..."), so RESTATED needs a bare claim. --window sets how many sentences an announcement governs (default 6); --selftest pins twelve cases, three of them real hilbertspace defects. INVERTED is the mirror image, and the shape CIRCULAR structurally cannot see: the category asserted FIRST and the property that establishes it derived from the assertion ("is an \gls{isometry}, and so preserves \glspl{innerproduct} as well as \glspl{distance}" -- an isometry is BY DEFINITION distance-preserving, so the conclusion was made a premise). Knowing what a category entails needs no hand-maintained table: the rule fires only on a category written as \gls{key}, and reads that entry's own description. Two discriminators, both found by testing: only \gls KEYS are compared, never incidental verbs, and a CONCRETE subject marks instantiation rather than derivation ("$\reals^{d}$ is a \gls{hilbertspace}, and so a \gls{cauchysequence} of \glspl{modelparam} converges" is ordinary exposition), so a "$" before the match suppresses it. Advisory. Book-wide backlog: 0 for all three codes.

check_flow deterministic

assess the discourse flow of a glossary entry and flag likely FLOW DISRUPTIONS between two consecutive paragraphs.

flags: --abstract --key --min-words --sentences --verbose · codes: ABRUPT DANGLING REDUNDANT

discourse-flow disruptions between two consecutive paragraphs of an entry (paragraphs split on top-level \\; figures/tables/math dropped, \gls unwrapped to its word). Flags: ABRUPT (cold topic switch — low content-word overlap AND no opening connective), DANGLING (paragraph opens with a bare deictic This/These/ Such with no antecedent), CONNECTIVE-MISMATCH (a contrast/consequence connective opening a paragraph that shares no vocabulary with its predecessor), REDUNDANT (near-total overlap, no new vocabulary). --sentences adds SENT-ABRUPT: cold jumps between adjacent sentences INSIDE a paragraph (the seam a deleted bridge sentence leaves; invisible to the paragraph pass) — stricter, suppressed by connectives, deictic/ headword openers, proof moves (Assume/Let/...), derivation gerunds, stance adverbs, figure pointers, mid-clause ties and shared math symbols (macro names in $...$ count as vocabulary; floats count as paragraph breaks). Heuristic, ADVISORY — eyeball every hit. --key, --abstract, --verbose (pair metrics), --min-words. Thresholds at the top.

check_margins deterministic

flag content that overruns the page margins in the COMPILED term PDFs (termdrafts/<key>.pdf).

flags: --include-refs --key --min-lines --pixels --tol

margin overflows in the COMPILED term PDFs (termdrafts/<key>.pdf) — the rendered artefact Springer receives. Text pass (default): pdftotext -bbox word boxes; text-block edges self-calibrated per document as the mode of justified line extents; words extending more than --tol pt (default 2) beyond an edge are flagged with page, overhang, and snippet. Catches wide displayed/inline math (overfull hboxes — build logs are deleted by the export, so the PDF is the only witness) AND figure overflow via embedded label text (e.g. the llm signal-flow chart). --pixels adds a raster pass (pdftocairo + PIL) for non-text ink. References-section overflows (long plain-text URLs) are a styling class deferred to Springer production and suppressed unless --include-refs. NOT advisory — findings are real layout defects. Book-wide baseline at introduction: 241 overflows across 60 terms. In run_linters only with --pdf.

check_paragraphs hybrid

paragraph STRUCTURE audit of glossary entries.

flags: --abstract --api-key --base-url --dry-run --key --max-sents --max-words --min-words --model --mono-words --no-llm --selftest --verbose · codes: SHORT LONG MONOLITH CHOPPY

paragraph STRUCTURE (sibling of check_flow, which judges the transitions): number and proportions of an entry's paragraphs. Flags: SHORT (< --min-words 20, or one sentence < 30 words, in a multi-paragraph entry; closing figure lead-ins exempt), LONG (> --max-words 220 or > --max-sents 10 sentences), MONOLITH (single unbroken paragraph >= --mono-words 260), CHOPPY (4+ paragraphs, median < 35 words). \gls resolved to display text and \ref to a number before splitting (reuses check_gls_coverage's expander). With --key (and an LLM key, unless --no-llm) adds one LLM call judging the narrative ARC of the paragraph sequence: abrupt-shift, out-of-order, redundant, does-not-belong, should-split/merge, weak-opening/-closing — the semantic pass the lexical heuristics of check_flow cannot make. --abstract adds a LONG screen on abstracts. Heuristic pass book-wide ~49 flags (advisory — deliberate exceptions are fine). --dry-run, --model, --base-url as in check_claims.

check_definite_articles deterministic

"the" before objects that might not be unique.

flags: --key --window

"the solution/minimizer/fixed point/..." where uniqueness is not established within --window chars (suppressors: unique, strictly/strongly convex, full column rank, positive definite, invertible, ...). Attributive uses ("the fixed-point iteration") are not flagged. Advisory: consider "a ..." or state why unique.

MATH-NOTATION CHECKS (macros first)

check_macros deterministic

Lint raw LaTeX that has a project macro equivalent.

flags: --key --list --only

curated rule table of raw LaTeX with a project macro equivalent (norms -> \normgeneric/\norm, \innerprod, hand-picked substitutions).

check_raw_notation deterministic

screen for raw notation that already has an ml_macros macro.

flags: --ambiguous --key --macros

auto-derived from ml_macros.tex: every raw bold vector/matrix, \mathcal space, \mathbb set that has a macro. --ambiguous adds overloaded symbols (>=3 candidate macros). Also covers COMPOSITE expressions of multi-argument macros (raw \kappa(\mQ) -> \condnumber, \lambda_{\min} -> \eigval, X^{+} -> \pinv, \mathcal{F}^{(eta)} -> \gdstep); generic bodies ((#1,#2), L(#1,#2)) and known-overloaded ones (\genericset, \bd, \siggen, the curated norm family) are never matched.

check_symbol_consistency deterministic

flag math symbols in a term that are documented in the List of Symbols but written with RAW notation instead of the macro the List uses, gated on the MEANING of the symbol matching the entry's topic.

flags: --ambiguous --fix --key --list --macros --unconfirmed

the narrower, meaning-aware screen: flags a raw symbol only when the rendered form AND the meaning match a List-of-Symbols row (concept keys from the row's description vs the entry's \gls{} links). Context-free symbols (\reals, \naturals) always flag. --unconfirmed lists meaning-suppressed occurrences; --fix applies unambiguous ones.

check_math_style deterministic

the math-typography rules of CLAUDE.md that had no screen of their own.

flags: --key --rule --selftest · codes: EQPUNCT INTDIFF TEXTOP SPLITSUP THEABBR

the math-typography rules that had no screen of their own -- five of the ten rows the manifest above used to mark "judgment". EQPUNCT (a displayed equation not closed with \text{,} or \text{.}; a bare , or . is accepted, and punctuation sitting inside a nested \end{array} counts, which is what removed seven false positives when the screen was written), INTDIFF (no thin space before the differential of an integral), TEXTOP (a named operator in \text{} or \mathrm{} instead of \operatorname{} -- \mathrm{err} and other multi-letter NON-function symbols stay correct and are not on the operator list), SPLITSUP (a dataset split written raw, or in the banned single-letter \mathcal{D}^{(t)} form), THEABBR ("the ADMM solves" -- only LITERAL abbreviations in prose, since a \gls{} renders as an expansion or an abbreviation depending on position and that is check_articles's business). --key / --rule / --selftest (22 cases). EQPUNCT is ported from the ml-theses.py, code EQ-NO-PUNCT): same rule, house spelling. Book-wide: 36 EQPUNCT, 2 TEXTOP, 1 THEABBR, 0 the rest. Advisory -- EQPUNCT is a backlog, not a regression.

check_symbol_semantics hybrid

LLM adjudication of raw-symbol MEANING, on top of check_symbol_consistency's lexical gate. Calls the OpenRouter.ai API.

flags: --api-key --base-url --confirmed --context --dry-run --key --limit --list --macros --model

LLM second stage on the gate above: sends the meaning-unconfirmed occurrences (grouped per entry+symbol, one LLM-gateway call each) to an LLM for an occurrence-level verdict — composite expressions of multi-argument macros included (e.g. is this \Delta^{(a)} the probability simplex \simplex{a} or a suboptimality gap?). Advisory only; needs AALTO_API_KEY (Aalto Azure OpenAI API, default; Aalto network/VPN only) or AALTO_LLM_KEY / OPENROUTER_API_KEY with --base-url; --dry-run shows prompts without a key; --confirmed re-checks the gate's positives; --model names a gateway model (default gpt-5-mini-2025-08-07, multimodal — check_figures uses it for images too).

EVIDENCE CHECKS

check_claims LLM

per-sentence evidence audit of one glossary entry (LLM).

flags: --api-key --base-url --chunk --dry-run --key --model --no-source --passages --verbose · codes: UNSUPPORTED PARTIAL

per-sentence evidence audit of ONE entry (--key required; one LLM-gateway call): classifies each sentence (definition/notation/crossref/example/ claim) and reports the backing of each claim AND each application example — citation, pythondemos/<key>.py experiment, in-entry figure, not-needed (only for worked examples the entry carries out in full), or NONE (unbacked: add a reference, a small numerical demo, or reword). A second, independent skeptical-reader pass asks of EVERY sentence — definitions above all — "what does that even mean precisely?" and flags IMPRECISE ones: wrong quantifiers, glosses that conflict with the formal definition, dangling referents, vague load-bearing language. Cited framing sentences and abstract compression are exempt when the entry or source carries the precise version. A THIRD pass reads the cited works themselves: a claim backed by a citation is checked against the extracted text under refs/ (see reftext.py), which is what turns "there is a citation here" into "the cited work says this". When the citation carries a PINPOINT the passage it names is used instead of the retrieval (locate() from check_ref_accuracy), and every pinpoint of that work in the entry is passed: sgd cites Bubeck's Thm. 3.2 and Thm. 3.9 from two different sentences, and feeding only the first made the second read as UNSUPPORTED. UNSUPPORTED = the retrieved passages are on topic and still do not carry the claim; PARTIAL = the source says something weaker or narrower. Retrieval can miss, so an off-topic retrieval is "unclear" and silent without --verbose; a sentence citing several works is judged per work, never blamed for what a sibling citation covers. --no-source skips the pass (and its extra call), --passages N sets how much of each source is retrieved (default 2). Advisory; --verbose prints the full sentence table and every source verdict; --dry-run shows the prompt without a key.

check_canonical_refs LLM

does the entry cite the works that define its term?

flags: --api-key --base-url --currency --dry-run --key --model --selftest --since --verbose · codes: CITED MISSING ABSENT

which works DEFINE this concept, and does the entry cite them? (--key required; one LLM-gateway call). Every other reference checker starts from the citations the entry already carries -- check_references asks whether a cited work fits the sentence, check_ref_accuracy whether the passage at the pinpoint carries the claim, check_literature whether the bib fields are right, check_claims whether a claim has any backing. All are blind to the same thing: a reference that is NOT there. An entry can cite three good works and still omit the paper that introduced the concept. The model names at most three, ranked by role: origin (the work that coined the term -- lasso -> Tibshirani 1996; the most valuable slot), standard (the textbook the field cites), survey. An empty list is the right answer for an ordinary mathematical definition. HALLUCINATION is the obvious risk, so no suggestion is printed as a bare instruction to cite. Each is matched against Literature.bib first (title containment >= 0.6 with at least two shared content tokens, plus year within one or a surname in common -- the year tolerance is for editions), and the verdict says what the match found: CITED (the entry cites it), MISSING (not cited, but PRESENT in the bib, whose entry is independent evidence the work exists -- the key is printed ready to paste), ABSENT (no bib match: real but missing from the bib, or invented, and this checker cannot tell which, so the line is a lead to VERIFY, never an instruction). The model is NOT told which works the entry cites: telling it invites "the works you cite are the canonical ones", and withholding makes a CITED verdict evidence rather than an echo. --verbose prints the rationale for the cited ones too. Advisory; exit 1 when any proposed reference is uncited. Tried on lasso, all three come back CITED (Tibshirani 1996 origin, ESL standard, Buehlmann-van de Geer survey), which also retires known issue 6 of CLAUDE.md ("Lasso: add Tibshirani (1996)") -- the entry has cited it for some time. CURRENCY (--currency): a go-to reference stays go-to by citing the CURRENT definitive treatment, not only the origin and the classic textbook. --currency adds a fourth role, 'recent' -- a survey or development from --since (default 2021) onward that has become a must-cite for the concept, at most one, empty for a settled/classical concept -- and runs it through the same bib-match/verdict pipeline (CITED/MISSING/ABSENT). Base mode is byte-for-byte unchanged (three roles, maxItems 3). Recent works carry the highest hallucination risk, so the ABSENT-is-a-lead guard matters most here.

check_index_coverage deterministic

every entry indexes its term for the back-of-book index.

flags: --hints --key --selftest · codes: NOINDEX INDEXNAME

the back-of-book index as a discoverability surface: every entry's description must call \index for its term (the template's "First\index{full name (ABBR)} sentence ..."), or the term is absent from the printed index. Deterministic, no LLM, book-wide or --key. NOINDEX (real) -- the description has no \index at all; 4 entries book-wide at introduction (hilbertspace, bagging, batchlearning, bootstrap). INDEXNAME (--hints, advisory) -- the entry's name= is not among the indexed terms after normalizing the makeindex sort key (sort@display), math ($k$-fold -> k-fold), dashes and case; a candidate STALE index term after a rename (the LSP rename touches \gls keys, never \index display text), but plural/spelling variants ("networked exponential families" for "... family") are benign, so it is a worklist to eyeball. --selftest pins 7 cases.

check_abstract_hook hybrid

the abstract's FIRST SENTENCE as a retrieval snippet.

flags: --api-key --base-url --dry-run --key --model --no-llm --selftest · codes: UNNAMED DEIXIS LONGHOOK SHORTHOOK DISPLAYMATH ABSCITE ABSFIGREF

the abstract's FIRST SENTENCE as a retrieval snippet. The site build publishes that one sentence verbatim as the entry's line in llms.txt (the index LLM crawlers fetch first), the opening of the terms.json record and of the schema.org DefinedTerm JSON-LD description, and it is what a search result or an LLM answer shows COLD -- the bait that decides whether the entry is found and cited rather than a textbook or Wikipedia. This is the discoverability leg the suite otherwise left unscreened: check_paragraphs OPENSABSTRACT judges the DESCRIPTION's opening, not the abstract's own first sentence as a standalone snippet. DETERMINISTIC GATE (book-wide, or --key), on entries that HAVE an abstract: UNNAMED (the first sentence never names the term -- a stem prefix accepts inflections, so "explainable" names "explainability"), DEIXIS (opens on a back-reference or bare connective that dangles alone), LONGHOOK (> 45 words, snippets truncate), SHORTHOOK (< 6 words, not a definition), and over the whole abstract DISPLAYMATH (a displayed equation breaks the one-line snippet), ABSCITE and ABSFIGREF (house style bans citations and figure refs in abstracts, and both are noise in a snippet). The snippet is approximated by expanding \gls calls to display text and dropping math/macros, matching the export. Entries with NO abstract are tallied on the summary line, not flagged per-entry -- the mandatory-abstract floor (TermsSpringer.xlsx + every revised entry) is not resolved here. LLM PASS (--key, one call): reads ONLY the rendered first sentence, cold, and judges it as a search snippet -- names the term, self-contained correct definition, no notation/ jargon that resolves only via the rest of the entry; strong/weak with a house-style rewrite. Book-wide at introduction: 6 gate findings (probdist/trainset/ transformer/innerproduct open on a generic ML sentence that never names the term; data opens "This entry ..."; featureimportance carries a citation). Advisory. --key / --no-llm / --dry-run / --selftest (5 cases).

check_abstract_coverage hybrid

is every claim of the abstract delivered by the description? (deterministic gate; LLM pass with --key)

flags: --api-key --base-url --dry-run --key --model --no-llm --selftest --verbose · codes: ABSGLS

is every claim of the ABSTRACT delivered by the DESCRIPTION? The abstract is what a search hit or an LLM answer quotes (llms.txt / terms.json / JSON-LD), and the description is where the reader then lands; an abstract that promises what the body weakens, contradicts or never states is a broken promise on the retrieval surface. The other abstract-aware checkers ask different questions: check_pruning asks what the abstract can LOSE (and rules description content is never "covered by" the abstract), check_claims IMPRECISE flags an abstract sentence only incidentally when the body never makes it precise, check_winston's cycle rule gives one whole-entry bit (the abstract cycles a different idea). None asked per abstract claim where the description states it. DETERMINISTIC GATE (book-wide, or --key), on entries that HAVE an abstract: ABSGLS -- a glossary key the abstract links that the description body never links (figure bodies and the Synonyms/See-also tail excluded; a key found only under See also is flagged with a note, since a list entry is a pointer, not a delivery; the entry's own key is exempt). LLM PASS (--key, one call): the abstract cut into atomic claims, each matched against the description -- covered (with the sentence or display that carries it), WEAKER (the body says less or narrower), CONTRADICTED (a different definition, hypothesis, direction or value; the verdict that matters, quoted both sides), UNCOVERED (never delivered). Direction fixed: abstract -> description; what the abstract omits is not a finding. Book-wide at introduction: 102 ABSGLS hits over the 105 entries with an abstract (17 of them See-also-only) -- a worklist, not a regression; the abstracts were written after most descriptions, so they name concepts (convex -> erm/hypospace/lossfunc, accuracy -> metric/validation) the body states in other words or not at all; eyeball before linking or rewriting. Advisory. --key / --no-llm / --verbose / --dry-run / --selftest (7 cases).

check_export_surfaces deterministic

validate the citation and retrieval surfaces.

flags: --build-dir --key · codes: DOIMISMATCH AUTHORDRIFT CONCEPT_DOI SITE_URL CANONICAL SCHOLAR CITEBOX JSONLD

the citation and retrieval SURFACES, the addressing an entry needs to be cited at all. The impact program builds them; nothing guarded them, so a malformed record degraded every citation pulled through it silently -- the exact "a rule with no screen has no memory" failure this file opens with, for the surfaces that carry the go-to-reference goal. Deterministic, no LLM, reads only committed and built files. ROOT (always): CITATION.cff carries title/author(family+ given)/DOI/license/url (CFF-*), .zenodo.json parses and carries title/creator/license/description/keywords (ZENODO-*), and the DOI (DOIMISMATCH) and author set (AUTHORDRIFT) agree across CITATION.cff, .zenodo.json and the export's own CONCEPT_DOI / SCHOLAR_AUTHORS constants (read out of export_term_site.py by regex, so the two never drift). BUILT (when termsite/ exists -- the last export, gitignored, rerun export_term_site.py to refresh): terms.json parses, its metadata block is complete, and every term record carries key/name/url/abstract/description with the canonical SITE_URL/terms/<key>.html url (TJ-*); llms.txt has a non-empty snippet line for every term (LLMS-*); each term page carries rel=canonical, a citation_author meta, a "Cite this entry" BibTeX box with author+title+doi+url and a non-empty DefinedTerm JSON-LD (HTML-MISSING / CANONICAL / SCHOLAR / CITEBOX / JSONLD). NOT advisory when a build is present -- a finding is a broken citation surface; with no build, only the root surfaces are checked and the note says to run the export. --key scopes the per-term page checks; --build-dir points elsewhere.

check_pruning LLM

redundant-sentence audit of one glossary entry (LLM).

flags: --api-key --base-url --dry-run --key --model --verbose

redundant-sentence audit of ONE entry (--key required; one LLM-gateway call): every sentence of the description AND abstract judged by one question — would the entry lose anything a reader needs if this sentence were deleted? PRUNE candidates printed with where the content already lives. The abstract is held stricter (every sentence must carry an essential point; true-but-secondary asides are prune there). Exempt: keyed-term repetition, formula restatements, topic sentences, the mandatory application sentence; coverage only counts description -> abstract, never the reverse. Advisory; --verbose prints every verdict.

check_ml_picture hybrid

how an entry uses the main ML picture.

flags: --api-key --base-url --dry-run --key --model --no-llm --selftest · codes: UNFORMALIZED GOALCONFLATED MIXEDFORM UNANCHORED OBJECTUNDEFINED INTERFACEBLUR UNITUNCLEAR

How an entry uses the main ML picture (pillars 1-2): learning = minimizing the average loss on a GIVEN training set; the ultimate goal = small loss on ANY data point; "any data point" requires a formalization (probabilistic i.i.d./risk, robustness/Lipschitz, or online/regret). Deterministic gate, book-wide (optional --key): UNFORMALIZED = a beyond-training promise with no named formalization in description+abstract (See-also blanked; "generalized X" and quantifier-any with a restricting relative do not count as promises). LLM pass with --key adds GOALCONFLATED (training loss presented as the goal itself), MIXEDFORM (formalizations switched silently), UNANCHORED (training/losses discussed with no anchor to the picture), and assesses the canonical PICTURE FIGURE (a SCATTERPLOT of train/val/test points + a curve depicting a hypothesis map; a deterministic figure inventory parses each figure's TikZ source for the two ingredients and grounds the verdict): present / partial (one ingredient missing, or the curve is not a hypothesis map) / suitable / unsuitable (only after a genuine attempt to conceive the figure, with the failed attempt described) — when suitable, it DRAFTS a house-style TikZ figure, which a deterministic vet then screens (unknown macros vs ml_macros.tex, \legend-with-parentheses, stray terminators, missing label/axis style, caption math outside $...$, \addplot+ colors). Advisory; --selftest pins nine gate cases. The LLM pass identifies the entry's learning PARADIGM first (supervised / unsupervised / reinforcement / generative / other / none) and asks the object questions in that paradigm, with three paradigm-aware codes: OBJECTUNDEFINED (a defining object relied on but never defined or linked), INTERFACEBLUR (consumes vs produces/controls ambiguous), UNITUNCLEAR (granularity of one training unit left to infer) — absorbed 2026-09-08 from the sibling's paradigm generalization.py (papers instead of entries; named erm_clarity_lint_llm.py until 2026-09-01) — keep aligned.

check_ml_relevance LLM

ML-relevance audit of one math entry (LLM).

flags: --any-type --api-key --base-url --dry-run --key --model --verbose

ML-relevance audit of ONE math entry (--key required; one LLM-gateway call): a math-glossary term must contain a DEDICATED passage (typically its own paragraph) explaining the concept's role for ML — tied to ERM/model training and naming concrete ML objects (loss, training set, hypothesis, GD), ideally with a formula. \gls{} links, See-also and caption asides do not count. Verdicts: dedicated (exit 0) / passing / missing (both exit 1, with a concrete content suggestion). Entries whose glossary type is not 'math' are skipped without an API call (--any-type forces the audit). Advisory.

check_demo_coverage LLM

does the term's python demo illustrate the entry paragraph by paragraph? (LLM)

flags: --api-key --base-url --dry-run --key --model --verbose

does pythondemos/<key>.py illustrate the entry PARAGRAPH BY PARAGRAPH? (--key required). No demo -> reported without an API call; otherwise one LLM-gateway call maps each entry paragraph to a demo block: covered / partial / none / exempt (nothing to demonstrate numerically), plus unmatched demo blocks. Advisory; --verbose, --dry-run as above.

check_ref_accuracy hybrid

verify the PINPOINT of a citation against the source.

flags: --api-key --base-url --dry-run --key --list-sources --model --no-llm --online --strict --verbose · codes: UNPAYWALL_EMAIL

Verifies the PINPOINT of a citation against the source itself, which no other checker reads: check_references judges the WORK from bib metadata, check_literature the bib FIELDS. Resolves the source from refs/<key>.pdf (or a fuzzy title match, or --online: the bib url/eprint, then Unpaywall by DOI (UNPAYWALL_EMAIL), OpenAlex, Semantic Scholar; a download that does not look like the cited work is discarded -> scripts/.refpdf_cache/), locates "Sect. 2.3.1" / "Thm. 11.5" / "Example 3.41" / "p. 426" in the text layer, and asks an LLM whether the passage there carries the claim. Two text layers are read: pdftotext -layout first (books keep their indentation) and, when the pinpoint is not found there, the reading-order extraction, which is what a two-column conference paper needs — -layout interleaves its columns, so "2. Class Activation Mapping" lands mid-line next to the other column. Heading forms accepted: "2.3.1 Title" and the conference style "2. Title"; a line ending in a period is not a heading (that is a footnote). A contents page is never judged, and its dot leaders are recognized in both spellings, "......" and ". . . . . .". MISSINGPIN (deterministic, no key) = the pinpoint is not in the document; MISMATCH = wrong passage; --strict adds INDIRECT, where the passage only implies or proves the claim instead of stating it (the Boyd 2.5.1-vs-2.3.1 class). --key, --no-llm, --online, --list-sources, --dry-run, --verbose.

check_example_text deterministic

sample text in a figure must come from the UDHR.

flags: --key --rule --selftest · codes: SPECIMEN FILLER

sample text in a figure must come from the UDHR (optional --key, --selftest). SPECIMEN: a run of >= 3 one-word TikZ nodes reading as running English that is not a contiguous UDHR excerpt (subword splits of UDHR words pass); FILLER: a stock placeholder sentence in the prose ("lorem ipsum", "the quick brown fox", ...). Undocumented here for a month until the run_linters drift alarm (ported 2026-09-08 from the sibling suite's check_readme_coverage) caught it.

check_figures hybrid

figure review: a deterministic source pass plus a Tufte-style LLM pass.

flags: --api-key --base-url --dry-run --key --model --no-llm --no-render --source-only --verbose · codes: UNLABELED ORPHAN NOCAPTION CAPPUNCT LEGENDBOX SMALLSCALE AXISBOX GRID THREED

Two passes. SOURCE PASS (no API key, no --key needed; book-wide with no arguments, one entry with --key, and all that runs under --no-llm): UNLABELED figure, ORPHAN label never \ref'd, NOCAPTION, CAPPUNCT caption ending in a period, LEGENDBOX legend without draw=none, SMALLSCALE scale below 0.6, AXISBOX axis lines=box/right, GRID gridlines, THREED 3D plot. Raw notation in figure math and the PARBREAK/FIGBREAK pair are NOT duplicated here — check_raw_notation / check_macros and check_parbreaks own them. LLM PASS: Tufte review of ONE term's figures (--key required; one OpenRouter call): each TikZ figure is judged per principle (data-ink, chartjunk, direct labeling, labeling, honesty, layering, color, bw-proof, caption, integration) as good/minor/major with TikZ-level suggestions. bw-proof = intelligible without color: color is never the only channel distinguishing elements (pair with dash pattern, marker shape, or a direct label). Rendered pages of termdrafts/<key>.pdf are attached when available (--no-render disables) — in color AND grayscale, so bw-proof is judged on what a black/white reader sees. No figure -> reported without an API call (the Springer brief expects one). Advisory; exit 1 on major findings.

BUILD-SAFETY CHECKS

check_parbreaks deterministic

flag line/paragraph-break constructs that break the book build but that the per-term export tolerates (so no other checker sees them).

flags: --key · codes: PARBREAK FIGBREAK

blank lines inside \newglossaryentry (PARBREAK) and '\\' straight after \end{figure}/\end{table} (FIGBREAK) — both break the main `make` while the per-term export tolerates them, so only this checker (or make itself) catches them.

BIBLIOGRAPHY CHECKS

check_references hybrid

verify the references used by glossary entries.

flags: --accuracy --api-key --base-url --dry-run --key --model --no-llm · codes: MISMATCH

verifies the references USED by entries, three layers: resolution (every \cite* key exists in Literature.bib — an unresolved key renders as "(author?)"; book-wide or --key), fields (cited entries carry author/editor, title, year), and an LLM fit stage (--key required; one OpenRouter call): does the cited work plausibly support the SENTENCE citing it — supports/plausible/MISMATCH with rationale (catches the right paper on the wrong claim). --accuracy delegates the cited keys to check_literature --only-keys. Citations inside figure captions are scanned for resolution but not judged for fit. Advisory; exit 1 on unresolved/incomplete/mismatch. [check_gls_coverage also runs a rendered-form COMPOUND pass: it expands \gls calls to their display text and re-matches multi-word key names, catching compounds split between plain text and a narrower link ("strongly \gls{convex}" vs strcvx, "\gls{gradient} step" vs gradstep) which the source pass cannot see (the \gls mask swallows half the compound). Reports the precise key and the currently linked narrower key(s). ~270 findings book-wide at introduction (2026-07-29) — a triage backlog, largely genuine.]

check_house_rules deterministic

the CLAUDE.md rules that had no screen of their own.

flags: --hints --key --selftest · codes: FIRSTPERSON SELFREF LATINHYPHEN TRAINLEARN MODELVERB TRANSPOSE IIDDATA CHAINED LONGSENTENCE BIGO HATSOLUTION

the CLAUDE.md rules that had no screen of their own: FIRSTPERSON (we/our/us/let us), SELFREF ("this dictionary/book/chapter", and "this entry" outside an abstract), LATINHYPHEN (post-hoc), TRAINLEARN ("train a hypothesis", "learn a model", "the trained hypothesis"), MODELVERB ("the model predicts"), TRANSPOSE (^T, ^{T}, unbraced ^\top), IIDDATA ("i.i.d. data" with no \sim in reach), CHAINED (a sentence over 45 words that also chains an em-dash apposition) and LONGSENTENCE (over 90 words). The two sentence categories judge the PROSE fields with math as one word each, figures dropped whole, and only "---" read as an em-dash: "--" is the en-dash of ranges and of names such as Mann--Kendall. Two categories are MEANING-GATED, since the same notation is legitimate in another sense: BIGO skips \mathcal{O} as the orthogonal group and as the measurable output set of the differential-privacy definition, and HATSOLUTION skips the star on a fixed point or a population optimum (2 of 32 occurrences are confirmed). --hints lists the occurrences whose sense the gate could not confirm, --selftest pins the 17 cases that define the behaviour. Deterministic, no API key.

check_explicit_domains deterministic

every \sum / \prod / \int must state its domain.

flags: --key · codes: INCOMPLETE

flags every \sum / \prod / \int whose domain is not explicit (--key optional): NO-DOMAIN (bare \sum, \int with no subscript), BARE-INDEX (\sum_{i} — index named, no range or ground set), INCOMPLETE (\sum_{i=1} with no upper limit and no in-subscript \ldots). Accepts \sum_{i=1}^{n}, \sum_{i \in S}, \sum_{i=1,\ldots,n}, relation-constrained subscripts (\sum_{k \ge 0}), and any subscripted integral. Scans everything incl. TikZ/captions; comments masked. \sup/\inf left to review. Advisory; exit 1 on findings.

check_mcenerney LLM

review one glossary entry against Larry McEnerney's "The Craft of Writing Effectively" (LLM, advisory).

flags: --api-key --base-url --dry-run --key --model --verbose

McEnerney reader-value review of ONE entry (--key required; one LLM call): "The Craft of Writing Effectively" adapted to entries — value over clarity ("clear and useless is useless"), no teacher-pattern (text that demonstrates knowledge without function), problem before solution, instability named with its concrete cost, no crossword-gap framing (value = error corrected / cost avoided / capability gained, never coverage). Also reports the READER PROBLEM the entry resolves and the instability words present. The reviews layer: Bertsekas = composition, Winston = memorability/clarity, McEnerney = reader value. Advisory.

check_selfcontained hybrid

is an entry a standalone text? (hybrid)

flags: --api-key --base-url --dry-run --key --model --no-llm --selftest · codes: EXTREF DEIXIS ENTRYREF DANGLING ASSUMESNOTATION OPAQUELINK

Standalone-text audit (hybrid). Deterministic gate book-wide: EXTREF (a \ref/\eqref whose \label lives outside the entry — an outright break, '??' in the standalone export), DEIXIS (positional back-references, advisory since the referent may sit within the entry) and ENTRYREF (prose naming the book's entry structure — "covered by dedicated entries", "the entry \gls{transformer}", "the \gls{ucb} entry"; the \gls delegation is fine, the wrapper is the self-reference; matrix/vector-component "entry" is excluded by key). LLM pass with --key plays the standalone reader (has the entry text only; \gls links unread but count as explicit delegation): DANGLING, ASSUMESNOTATION (book core notation and gls-apposition introductions excepted), OPAQUELINK. --selftest pins five gate cases.

check_winston LLM

review one glossary entry against Patrick Winston's clarity heuristics (LLM, advisory).

flags: --api-key --base-url --dry-run --key --model --verbose

Winston clarity review of ONE entry (--key required; one LLM call): the seven heuristics of "How to Speak" (MIT OCW RES.TLL-005) / "Make It Clear" (MIT Press 2020), adapted to entries — empowerment promise (the opening gives a reason to care), cycle (the ONE key idea recurs at opening / formal core / abstract, never as redundant sentences), build a fence (delimit from confusable neighbors), near miss (a contrasting/failure case), landmarks (paragraph openings announce topics), a handle (reports what serves as symbol / slogan / story, proposes a house-lexicon slogan when none), finish with value. House overrides: impersonal voice, no reader address, cycling is structural not repetitive, proposals obey the banned-word list. Advisory; exit 1 on findings.

check_bertsekas hybrid

Bertsekas's "Ten Simple Rules for Mathematical Writing" (MIT, April 2002; www.mit.edu/~dimitrib/Ten_Rules.pdf) applied to glossary entries. Hybrid: a deterministic gate for the SMALL RULES the slides make verifiable, book-wide and without a key, plus an LLM review of the ten COMPOSITION RULES for one entry (--key).

flags: --api-key --base-url --dry-run --hints --key --model --no-llm --rule --selftest --verbose · codes: SENTSTART MATHSPEAK FOURLONG TWOLINES BAREREF WHICHTHAT ABBRCOMMA LOOSETERM UNUSEDNOTATION

Bertsekas's "Ten Simple Rules for Mathematical Writing" (MIT 2002, the slides at www.mit.edu/~dimitrib/Ten_Rules.pdf), hybrid since 2026-09-17. The slides sort rules into SMALL (one sentence, checkable), BROAD (unverifiable) and COMPOSITION (how parts connect; the ten). The first version asked one LLM call for everything, small rules included, and that call had been failing on every entry: the gateway counts a reasoning model's thinking against max_tokens, the ten-verdict JSON was cut off, and "No parseable verdict" was all the checker ever said (llm_client now escalates the budget on a cut-off answer and says so on stderr). DETERMINISTIC GATE (book-wide, or --key; --rule, --hints, --selftest with 44 pinned cases): SENTSTART (a sentence opening on inline math, or a display after a full stop; a display continuing a comma/colon lead-in is not a sentence start, the fe759e7 case; "$k$-means" is a compound term), MATHSPEAK ("Let $k>0$ be" — say "Let $k$ be a positive integer"; a \defeq definition is not this), FOURLONG (a paragraph with more than four sentences past two printed lines = 30 words on check_flow's cleaned prose, the 2-3-4 rule's paragraph clause), TWOLINES (--hints; the sentence clause, 1211 sentences book-wide), BAREREF (an \eqref by number alone: "of \eqref{}", "Eq.~\eqref{}", sentence- initial; rule 8's cardinal sin, 39 book-wide), WHICHTHAT ("$T$ which is" — that, or ", which"; 12), ABBRCOMMA (i.e./e.g. without a comma; 4 in prose), LOOSETERM (slide 12: "random values", "chance"; 6), UNUSEDNOTATION ("denoted by $x$" and $x$ never written again — slide 21's "avoid unnecessary notation"; 2, both real: transpose then writes the raw $A^{\top}$, condprobdist never reuses its symbol). The verb clause of 2-3-4 stays with the LLM (stdlib has no tagger). LLM REVIEW (--key): one call, ten verdicts (pass/finding with quote and edit) with the entry as the SEGMENT (his half page to three pages is an entry's length; paragraphs are its blocks), plus small rules a regex cannot see: three-verbs, strange-symbols, grading-words ("very", "trivial", "easy", "nice", "fundamental"), mathspeak outside the Let-form. House overrides stated in the prompt: impersonal voice; $x^{(r)}$ superscript indices although slide 21 prefers subscripts; announce a result by STATING it (never "Note that" — check_register METATEXT); "dare to be repetitive" bounded by check_pruning. Advisory; --verbose prints all ten; exit 1 on any finding. gd and hilbertspace: 10 pass each, 5 and 3 small-rule hits, ~40 s per call. The rule-by-rule map to the deterministic checkers is the BERTSEKAS MAP below.

check_ref_identity deterministic

is the source under refs/<key> the work the bib entry names?

flags: --hints --key --selftest --verbose · codes: MISMATCH YEAR PREPRINT

asks whether the file under refs/<key> is the work the bib entry names. check_ref_accuracy locates a pinpoint in whatever file sits there and cannot tell a right section in the wrong book from a right one: seven citations to the 2017 second edition of Bauschke and Combettes were verified against the 2011 first, and every pinpoint resolved. This reads the doi, isbn and copyright year the source prints about itself and compares them with the entry (MISMATCH, YEAR). An identifier may legitimately differ — a print vs an e-ISBN, a book vs an article doi — so a difference is reported only when the source does not print the entry's title either. A third code, PREPRINT, is checked FIRST because every other test here is defeated by it: a preprint prints its published title, so the doi and isbn branches return "differs but the source prints the entry's title", and an arXiv PDF carries no copyright line, so YEAR never runs. That is how Samek2017 sat as arXiv:1509.06321 under a citation to IEEE TNNLS 28(11) while reporting clean, and how a pinpoint was verified against the wrong document. An entry that names the preprint as its venue (journal = {arXiv preprint arXiv:...}) or carries the matching eprint field is not flagged. --selftest pins eleven cases. Book-wide: 15 PREPRINT, of which Bubeck2015, HazanOCO, MLBasics, GradCamPaper, Luxburg2007 and ZhouCAM2016 carry 36 pinpoints between them that were verified against a preprint.

check_literature deterministic

Sanity-check entries in assets/Literature.bib.

flags: --hints --keys-only --limit --only-keys --selftest --skip-download --sleep · codes: KEYYEAR DATEADDED

sanity-checks assets/Literature.bib entries (fields, duplicates, well-formedness). Its key/year screen is stdlib-only and runs on every run_linters pass: a key ending in a year that contradicts the entry's own year field is reported KEYYEAR, and DATEADDED when that key year is the year the entry was typed (the signature of Axler2025 for a 2015 edition and Gujarati2021 for a 2009 one). Off-by-one is the normal preprint-or-conference case and needs --hints; DOIs, JMLR volume numbers, ISO numbers and hashed proceedings ids are not read as years. --selftest pins the nine cases that define this. The duplicate scan and the PDF verification still need --bib and four third-party packages.

OTHER CHECKS

check_spelling deterministic

spell-check the FLATTENED prose of glossary entries.

flags: --key --min-len

spell-checks the FLATTENED prose of entries (--key optional): \gls calls resolved to display text, math/environments/commands/paths masked, figure captions included. Flags all-lowercase words unknown to /usr/share/dict/words + light stemming + scripts/spelling_allowlist.txt + the glossary's own vocabulary. Catches typos ("basid", "stanard", "proceses") and British spellings ("behaviour", "-isation"), which are deliberately not allowlisted. Advisory; exit 1 on findings. Introduced 2026-07-30; first book-wide sweep fixed 15 typos/BE spellings across 13 entries.

The Bertsekas map

check_bertsekas's LLM pass judges each of the ten rules for one entry. Most
rules also have a deterministic screen elsewhere in the suite that does part
of the work book-wide; this map says which, so a finding names one checker,
and it records the three places where Bertsekas and house style disagree.

   1 Organize in segments ......... the ENTRY is the segment; its paragraphs
                                    are screened by check_paragraphs (SHORT/
                                    LONG/MONOLITH/CHOPPY) and FOURLONG; its
                                    standalone start/end by
                                    check_selfcontained and check_paragraphs
                                    OPENSABSTRACT (concrete opening)
   2 Write segments linearly ...... check_flow ABRUPT/DANGLING (a definition
                                    far from its use shows as a cold seam);
                                    the LLM judges ordering itself
   3 Hierarchical development ..... \gls delegation: check_gls_coverage (an
                                    unlinked term = inline background),
                                    check_seealso_usage
   4 Consistent notation .......... check_wording_consistency (one word per
                                    concept), check_alignment SYMBOLCLASH
                                    (across entries), check_symbol_consistency
                                    / check_raw_notation (macros), LOOSETERM,
                                    UNUSEDNOTATION; slide 21 prefers
                                    subscripts to $x^{(r)}$ — HOUSE WINS, the
                                    prompt says so
   5 State results consistently ... LLM only (entries rarely carry sibling
                                    theorem statements)
   6 Don't under-/overexplain ..... check_paragraphs OPENSABSTRACT (audience
                                    of sentence one), check_pruning (the
                                    overexplained side), check_mcenerney
                                    teacher-pattern; the LLM judges skipped
                                    steps
   7 Tell them what you'll tell ... check_winston landmarks (paragraph
                                    openings), check_paragraphs LLM arc
                                    (weak-opening); TENSION: Bertsekas's
                                    "It turns out that ... To see this" is
                                    check_register METATEXT — house says
                                    announce by STATING the result, the
                                    prompt forbids the metatext form
   8 Suggestive references ........ BAREREF (each bare \eqref), the LLM for
                                    frequency and reminders; TENSION:
                                    "dare to be repetitive" vs
                                    check_word_repetition / check_pruning —
                                    keyed nouns are exempt there already,
                                    and a restated sentence stays a defect
   9 Examples and counterexamples . the application example is judgment +
                                    check_claims (its backing);
                                    check_winston near-miss is the same
                                    question asked of the failure case; the
                                    LLM asks whether each assumption's need
                                    is shown
  10 Visualization ................ check_figures (source pass + Tufte LLM
                                    pass; captions that repeat the text are
                                    its "caption" principle); "prefer graphs
                                    over tables" is judgment (3 tabulars in
                                    entries book-wide)
  small rules (slide 8-9) ......... the deterministic gate above; "very/
                                    trivial/easy" is check_bullshit HEDGE
                                    ("very") + the LLM grading-words rule;
                                    "use 'we'" (active voice) — HOUSE WINS
  intuitive writing (slide 12) .... LOOSETERM, check_house_rules IIDDATA,
                                    check_wording_consistency; "better to
                                    skip a proof than give a sloppy one" is
                                    the book's no-proofs policy

How this book is built · Back to the index