all2md.linter.rules

Built-in rule implementations. Importing the all2md.linter.rules package triggers each rule module to register itself with the global all2md.linter.rule_registry.

Structural document rules (STR001-STR008).

These check high-level document shape: does it have a title, are heading levels consistent, are headings empty or orphaned, are sections too short, is the document empty, is block-level nesting excessive?

class all2md.linter.rules.structure.MissingTitleRule

Bases: LintRule

STR001: Flag documents that lack a top-level (H1) heading.

code: str = 'STR001'
name: str = 'missing-title'
category: str = 'structure'
description: str = 'Every document should have a top-level (H1) heading.'
default_severity: Severity = 3
check(ctx: LintContext) list[Violation]

Return a single violation if the document has no H1 heading.

class all2md.linter.rules.structure.MultipleH1Rule

Bases: LintRule

STR002: Flag documents that contain more than one H1 heading.

code: str = 'STR002'
name: str = 'multiple-h1'
category: str = 'structure'
description: str = 'A document should have exactly one H1 heading.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation per extra H1 (skipping the first one).

class all2md.linter.rules.structure.HeadingHierarchyRule

Bases: LintRule

STR003: Flag heading-level skips (for example, H1 followed directly by H3).

code: str = 'STR003'
name: str = 'heading-hierarchy'
category: str = 'structure'
description: str = 'Heading levels must not skip (e.g., H1 followed by H3).'
default_severity: Severity = 3
check(ctx: LintContext) list[Violation]

Return a violation for every heading that skips a level.

class all2md.linter.rules.structure.EmptyHeadingRule

Bases: LintRule

STR004: Flag headings that contain no text.

code: str = 'STR004'
name: str = 'empty-heading'
category: str = 'structure'
description: str = 'Headings must contain text.'
default_severity: Severity = 3
check(ctx: LintContext) list[Violation]

Return a violation for each empty heading.

class all2md.linter.rules.structure.OrphanHeadingRule

Bases: LintRule

STR005: Flag a heading that ends the document with no content after it.

code: str = 'STR005'
name: str = 'orphan-heading'
category: str = 'structure'
description: str = 'A heading at the end of the document with no content after it is likely a mistake.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation if the last child of the document is a heading.

class all2md.linter.rules.structure.ShortSectionRule

Bases: LintRule

STR006: Flag headings whose section contains fewer than N words of content.

A “section” is the run of top-level children between this heading and the next heading at the same-or-higher level (matching how readers perceive a section). The default threshold is 10 words; override via [tool.all2md.lint.rules] STR006.min_words.

code: str = 'STR006'
name: str = 'short-section'
category: str = 'structure'
description: str = 'Sections should have enough content to justify their heading.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each heading whose section is too short.

class all2md.linter.rules.structure.EmptyDocumentRule

Bases: LintRule

STR007: Flag documents whose children list is empty.

code: str = 'STR007'
name: str = 'empty-document'
category: str = 'structure'
description: str = 'A document should contain at least one block.'
default_severity: Severity = 3
check(ctx: LintContext) list[Violation]

Return a single violation if ctx.document.children is empty.

class all2md.linter.rules.structure.ExcessiveNestingRule

Bases: LintRule

STR008: Flag block-level nesting deeper than N levels.

“Block-level nesting” counts only BlockQuote and ListItem on the path from the document root to the deepest descendant. Default threshold is 4; override via STR008.max_depth.

code: str = 'STR008'
name: str = 'excessive-nesting'
category: str = 'structure'
description: str = 'Block-level nesting (blockquotes, list items) should not be excessively deep.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation per nested container exceeding max_depth.

Heading content rules (HDG001-HDG007).

Checks the text of each heading: punctuation, length, duplication, capitalization consistency, emphasis-wrapped headings, sentence-shaped headings, and URLs in heading text.

class all2md.linter.rules.headings.HeadingTrailingPunctuationRule

Bases: LintRule

HDG001: Flag headings that end with sentence-style punctuation.

code: str = 'HDG001'
name: str = 'heading-trailing-punctuation'
category: str = 'headings'
description: str = 'Headings should not end with sentence-ending punctuation.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each heading ending in ‘.’, ‘,’, ‘;’, or ‘:’.

class all2md.linter.rules.headings.HeadingLengthRule

Bases: LintRule

HDG002: Flag headings longer than a configurable character limit (default 80).

code: str = 'HDG002'
name: str = 'heading-length'
category: str = 'headings'
description: str = 'Headings should not exceed a configurable maximum length.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each heading exceeding max_length.

class all2md.linter.rules.headings.DuplicateHeadingsRule

Bases: LintRule

HDG003: Flag same-level headings with identical (case-insensitive) text.

code: str = 'HDG003'
name: str = 'duplicate-headings'
category: str = 'headings'
description: str = 'Headings at the same level should not have duplicate text.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each duplicate occurrence after the first.

class all2md.linter.rules.headings.HeadingCapitalizationRule

Bases: LintRule

HDG004: Flag headings at the same level that diverge from the dominant capitalization style.

code: str = 'HDG004'
name: str = 'heading-capitalization'
category: str = 'headings'
description: str = 'Headings at the same level should share a capitalization style.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each heading whose style doesn’t match the majority at its level.

class all2md.linter.rules.headings.HeadingEmphasisRule

Bases: LintRule

HDG005: Flag headings whose content is a single Strong or Emphasis node.

code: str = 'HDG005'
name: str = 'heading-emphasis'
category: str = 'headings'
description: str = 'Headings should not be wrapped entirely in emphasis or strong markup.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each heading wrapped entirely in emphasis.

class all2md.linter.rules.headings.HeadingAsSentenceRule

Bases: LintRule

HDG006: Flag headings that read like a full sentence.

A heading is sentence-shaped if it both (a) has more words than the configured threshold (default 12), and (b) ends with a sentence-final punctuation mark (., !, ?). Either alone is unreliable — short imperative headings sometimes end with !, and long noun phrases without punctuation are perfectly valid headings.

code: str = 'HDG006'
name: str = 'heading-as-sentence'
category: str = 'headings'
description: str = 'Headings should not read like full sentences.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each heading that looks like a sentence.

class all2md.linter.rules.headings.HeadingUrlRule

Bases: LintRule

HDG007: Flag headings whose text contains a URL.

code: str = 'HDG007'
name: str = 'heading-url'
category: str = 'headings'
description: str = 'Headings should not contain raw URLs.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each heading whose plain text contains a URL.

Checks link structure (empty text, missing URLs), duplicated URLs, bare URLs in plain text, low-quality link labels like “click here”, insecure http:// links, and links whose text is the raw URL.

class all2md.linter.rules.links.EmptyLinkTextRule

Bases: LintRule

LNK001: Flag links whose visible text is empty.

code: str = 'LNK001'
name: str = 'empty-link-text'
category: str = 'links'
description: str = 'Links must have visible text describing their destination.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each link with empty text.

class all2md.linter.rules.links.MissingUrlRule

Bases: LintRule

LNK002: Flag links with empty or whitespace-only URLs.

code: str = 'LNK002'
name: str = 'missing-url'
category: str = 'links'
description: str = 'Links must have a non-empty URL.'
default_severity: Severity = 3
check(ctx: LintContext) list[Violation]

Return a violation for each link with a blank URL.

class all2md.linter.rules.links.DuplicateUrlsRule

Bases: LintRule

LNK003: Flag URLs linked from more than one place in the document.

code: str = 'LNK003'
name: str = 'duplicate-urls'
category: str = 'links'
description: str = 'The same URL should typically only be linked once.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each repeated link to the same URL.

class all2md.linter.rules.links.BareUrlRule

Bases: LintRule

LNK004: Flag raw URLs that appear in prose instead of being wrapped in a Link.

code: str = 'LNK004'
name: str = 'bare-url'
category: str = 'links'
description: str = 'Raw URLs should be wrapped in link syntax.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each unwrapped URL found in Text content.

class all2md.linter.rules.links.LinkTextQualityRule

Bases: LintRule

LNK005: Flag links whose text is a low-quality filler phrase like ‘click here’.

code: str = 'LNK005'
name: str = 'link-text-quality'
category: str = 'links'
description: str = "Link text should describe the destination, not use generic phrases like 'click here'."
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each link whose text is in the generic-phrases list.

class all2md.linter.rules.links.InsecureLinkRule

Bases: LintRule

LNK006: Flag links that use http:// instead of https://.

No auto-fix in v2.0 — blindly upgrading to HTTPS can break a fraction of legitimate hosts. The lint output points the user at the link; they decide whether to upgrade.

code: str = 'LNK006'
name: str = 'insecure-link'
category: str = 'links'
description: str = 'Links should use HTTPS where possible.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each http:// Link URL.

class all2md.linter.rules.links.LinkTextIsUrlRule

Bases: LintRule

LNK007: Flag links whose visible text equals the URL itself.

Distinct from BareUrlRule (LNK004): LNK004 catches raw URLs in prose that aren’t wrapped in link syntax at all; LNK007 catches links of the form [https://x](https://x) where the markup is correct but the visible text adds nothing.

code: str = 'LNK007'
name: str = 'link-text-is-url'
category: str = 'links'
description: str = 'Link text should describe the destination, not duplicate the URL.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each link whose text equals its URL.

Typography rules (TYP001-TYP008).

Low-level text hygiene: trailing spaces, multiple spaces, straight quotes, double-hyphens that should be em-dashes, inconsistent list marker styles (adjacent sibling lists with mismatched ordered/unordered types), ellipsis character, space-before-punctuation, and consecutive punctuation.

Most TYP rules carry a SAFE auto-fix that mutates Text.content in place — see all2md.linter.fixes.

class all2md.linter.rules.typography.TrailingSpacesRule

Bases: LintRule

TYP001: Flag Text nodes that end with whitespace when they’re the last child of their parent.

code: str = 'TYP001'
name: str = 'trailing-spaces'
category: str = 'typography'
description: str = 'Text should not end with trailing whitespace.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each trailing-whitespace Text at the end of a container.

class all2md.linter.rules.typography.MultipleSpacesRule

Bases: LintRule

TYP002: Flag Text nodes containing runs of consecutive spaces.

code: str = 'TYP002'
name: str = 'multiple-spaces'
category: str = 'typography'
description: str = 'Text should not contain multiple consecutive spaces.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each Text containing two or more consecutive spaces.

class all2md.linter.rules.typography.StraightQuotesRule

Bases: LintRule

TYP003: Flag Text that uses straight ASCII quotes around a word.

code: str = 'TYP003'
name: str = 'straight-quotes'
category: str = 'typography'
description: str = 'Prefer curly quotes (“”‘’) over straight quotes.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each Text containing quoted words using straight quotes.

class all2md.linter.rules.typography.DoubleHyphensRule

Bases: LintRule

TYP004: Flag Text containing -- that should be an em-dash.

code: str = 'TYP004'
name: str = 'double-hyphens'
category: str = 'typography'
description: str = 'Prefer em-dashes (—) over double hyphens.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each Text containing a double-hyphen sequence.

class all2md.linter.rules.typography.MixedListMarkersRule

Bases: LintRule

TYP005: Flag adjacent sibling lists that disagree on ordered vs unordered style.

code: str = 'TYP005'
name: str = 'mixed-list-markers'
category: str = 'typography'
description: str = 'Adjacent sibling lists should share the same ordered/unordered style.'
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each list immediately following a sibling list of the opposite kind.

class all2md.linter.rules.typography.EllipsisCharacterRule

Bases: LintRule

TYP006: Flag Text containing ... instead of the ellipsis character.

code: str = 'TYP006'
name: str = 'ellipsis-character'
category: str = 'typography'
description: str = 'Prefer the ellipsis character (…) over three periods.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each Text containing ... (but not ....).

class all2md.linter.rules.typography.SpaceBeforePunctuationRule

Bases: LintRule

TYP007: Flag Text containing whitespace immediately before sentence punctuation.

code: str = 'TYP007'
name: str = 'space-before-punctuation'
category: str = 'typography'
description: str = 'Punctuation should not be preceded by whitespace.'
default_severity: Severity = 1
check(ctx: LintContext) list[Violation]

Return a violation for each Text where ,.;:!? is preceded by a space.

class all2md.linter.rules.typography.ConsecutivePunctuationRule

Bases: LintRule

TYP008: Flag Text containing repeated punctuation like ,, or !!.

Detects only ,, !, and ? repeats. ../... overlap with TYP006 (ellipsis) and are intentionally excluded — TYP006 already covers them. Repeats are not auto-fixed because the right replacement (collapse to one character vs. ellipsis vs. interrobang ?!) is reader judgement.

code: str = 'TYP008'
name: str = 'consecutive-punctuation'
category: str = 'typography'
description: str = "Punctuation should not be repeated (',,', '!!', '??', etc.)."
default_severity: Severity = 2
check(ctx: LintContext) list[Violation]

Return a violation for each Text containing a repeated punctuation mark.