Lint (dm.lint)¶
Static checker against the dartwork-mpl anti-pattern catalog (0.4+).
dm.lint runs a Python source string through the 15-rule
anti-pattern catalog shipped at
asset/prompt/02-anti-patterns.yaml. It is the same engine
behind the MCP lint_dartwork_mpl_code tool and the
dartwork-mpl lint CLI, so editor integrations, CI, and AI
assistants all see the same violations.
Quick start¶
import dartwork_mpl as dm
source = """
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6.7, 4.0))
plt.tight_layout()
"""
issues = dm.lint.lint(source)
for issue in issues:
print(issue.rule_id, issue.severity, issue.message)
# Or render a human-readable report
print(dm.lint.format_report(issues))
Each rule entry has an id, severity
(critical / warning / info), a short message, an
optional why blurb, and a recommended fix_suggestion. The
catalog lives in YAML so adding or tightening a rule is a single
file edit; the lint engine reloads it on call.
Catalog highlights¶
figsize-direct— rawfigsize=(w, h)tuples are forbidden; usefigsize=dm.figsize("<n>cm", "<aspect>").dm-subplots-removed—dm.subplots/dm.figurewere removed; useplt.subplots(figsize=dm.figsize(...)).raw-width-number— bare numbers passed todm.figsizeare rejected because they carry no unit.tight-layout—plt.tight_layout()is forbidden; usedm.simple_layout(fig).width-token— deprecated 0.3 width tokens (dm.SW/MW/TW/DW).oversize-width— widths beyond 17 cm break most page layouts.fontsize-literal/linewidth-literal— pass numeric values viadm.fs(n)/dm.lw(n)so they track the active style.raw-hex-color— prefer named palette tokens (oc.,tw.,dc., …) over inline hex.jet-cmap— flag rainbow colormaps that misrepresent ordinal data.
The full list (always authoritative) is at
asset/prompt/02-anti-patterns.yaml in the source tree, or via
the MCP resource dartwork-mpl://guide/anti-patterns.
API¶
dartwork-mpl lint engine.
Loads the anti-pattern catalog from
asset/prompt/02-anti-patterns.yaml and applies it to a Python
source string. Used by the MCP lint_dartwork_mpl_code tool, the
dartwork-mpl lint CLI, and CI drift tests.
The catalog is the single source of truth: code never inlines rule text. Add or change rules in the YAML file; this module loads them verbatim.
- class dartwork_mpl.lint.Issue(rule_id: str, severity: str, message: str, line: int | None = None, snippet: str | None = None, column: int | None = None, fix_suggestion: str | None = None)[source]¶
Bases:
objectA detected violation.
columnis the absolute byte offset of the match in the source string (0-indexed). It is included to disambiguate multiple violations on the same line —(rule_id, line)alone collapses them and hides the second occurrence from auto-fixers.fix_suggestionmirrors the YAML field of the same name and is surfaced inline byformat_report()so AI agents can apply a fix without a second round-trip.- column: int | None = None¶
- fix_suggestion: str | None = None¶
- line: int | None = None¶
- message: str¶
- rule_id: str¶
- severity: str¶
- snippet: str | None = None¶
- class dartwork_mpl.lint.Rule(id: str, severity: str, detector_kind: str, detector_value: str, message: str, why: str | None = None, fix_suggestion: str | None = None)[source]¶
Bases:
objectA single anti-pattern definition.
- detector_kind: str¶
- detector_value: str¶
- fix_suggestion: str | None = None¶
- id: str¶
- message: str¶
- severity: str¶
- why: str | None = None¶
- dartwork_mpl.lint.apply_lint_fixes(code: str) tuple[str, list[Issue], list[Issue]][source]¶
Apply safe mechanical fixes for a curated subset of lint rules.
Performs identifier- and call-level rewrites for rules whose replacement does not depend on caller-supplied parameters (currently
plt-style-useand the no-arg form oftight-layout). Each rule is applied as a whole-source regex substitution, after which the linter re-runs to compute the diff betweenbeforeandafterissue sets.
- dartwork_mpl.lint.format_report(issues: list[Issue]) str[source]¶
Render issues as a multi-line
[SEV] rule-id: messagereport.The full message is preserved (including any subsequent lines from a YAML
|block scalar) and indented under the header line so reports stay readable in plain-text MCP/CLI output.If a rule provides a
fix_suggestion, it is emitted on its own line directly after the message as→ fix: <suggestion>so AI agents can lift the replacement directly without a second round-trip.
- dartwork_mpl.lint.lint(code: str, *, rules: Iterable[Rule] | None = None) list[Issue][source]¶
Apply anti-pattern rules to a Python source string.
Note
codemust be Python source, not YAML/Markdown/JSON. The rules are regex-based, so feeding non-Python content (e.g. the anti-patterns YAML itself) will produce false positives.- Parameters:
code (str) – Python source to scan.
rules (Iterable[Rule] | None, optional) – Override the rule set (e.g. for tests). Defaults to
load_rules()output.
- Returns:
Issues in declaration order, deduplicated by
(rule_id, column)so multiple violations on the same line are reported separately.- Return type:
list[Issue]
- dartwork_mpl.lint.load_rules(path: Path | None = None) list[Rule][source]¶
Load and parse the anti-pattern catalog.
- Parameters:
path (Path | None, optional) – Override path for testing. Defaults to the bundled
02-anti-patterns.yaml.- Returns:
Parsed rule objects in declaration order.
- Return type:
list[Rule]
- dartwork_mpl.lint.migrate_legacy_code(code: str) str[source]¶
Best-effort regex rewrite from 0.3-era to 0.4 dartwork-mpl idioms.
Two passes:
Safe substitutions are applied in place (
dm.cm2in→dm.cm,plt.style.use→dm.style.use).Context-dependent patterns (deprecated width tokens, the removed
dm.subplots/dm.figure, rawfigsize=(w,h)tuples,tight_layout()calls, and the removeddm.agent_utils/dm.xplotnamespaces) get a# TODO(dm-migrate): …comment inserted above the offending line so the agent can see what to change without losing the original code.
- Parameters:
code (str) – 0.3-era Python source.
- Returns:
Rewritten source. Always returned (never raises). Use
lint()on the result to confirm no critical issues remain after the agent applies the manual hints.- Return type:
str
Notes
AST-based migration is intentionally out of scope (see
docs/superpowers/specs/2026-05-01-ai-readiness-0.5-roadmap.md, “Out of Scope”). Inputs that don’t match any pattern are returned unchanged.