Skip to content

PROJECT: action-code-style — deterministic black formatting for Python in MyST/markdown #320

@mmcky

Description

@mmcky

Summary

Build a standalone GitHub Action that runs black over Python code embedded in MyST {code-cell} directives and standard markdown fenced code blocks, keeping lecture code PEP8-compliant and consistently formatted. The tool is deterministic (no AI), cheap, and safe to run automatically.

This issue captures the essence of the closed Copilot PR #240 and the original request in #221, plus the architecture decision reached in discussion (a separate repo, not folded into action-style-guide), so the feature can be cleanly re-implemented once it has a home.

Background

  • #221 requested automatic black style checks on Python contained in MyST code-cell directives and standard fenced markdown code blocks.
  • #240 (Copilot) implemented a code-style-checker action inside meta (.github/actions/code-style-checker/), comment-triggered via @quantecon-code-style. It was never reviewed/merged and is structured the old (in-meta) way, contrary to the migration of actions into individual repos (#244). It has been closed in favour of this documented project.
  • This is a real, still-unmet need: no current QuantEcon repo provides deterministic black formatting of code embedded in markdown. action-style-guide is adjacent but fundamentally different (see below).

Architecture decision: separate action-code-style, not part of action-style-guide

The two share the phrase "code style" but differ on every axis that matters for packaging:

Axis black formatting (this project) action-style-guide
Mechanism Deterministic, fixed rules AI (Claude), non-deterministic, confidence-scored
Dependencies black only — no API key, free LLM API key, cost per run
Auto-run Safe on every PR / push Comment-triggered only (cost + noise)
Cadence Tiny, stable utility Large, evolving (prompt versioning, rules DB)
Audience Any MyST/Jupyter Book project QuantEcon-opinionated

Decisive points:

  1. Invocation policy differsblack wants to be cheap and always-on (a normal CI check); the AI style guide must stay comment-triggered to avoid being intrusive/expensive. One repo cannot cleanly serve both runtime profiles.
  2. Coupling a stable formatter to a fast-moving AI tool creates release friction — every prompt/rule change would churn a repo that also houses a formatter that rarely changes.
  3. Small, single-purpose repos match the #244 grain (action-link-checker, action-check-warnings, …).

The one genuine overlap is the MyST/markdown code-extraction parser (both need to pull Python out of {code-cell} and fenced blocks). If duplication becomes annoying, factor that into a small shared helper rather than merging two tools with opposite runtime profiles.

➡️ Home: a new standalone QuantEcon/action-code-style repo. (The standalone-vs-centralised question is being worked through more broadly in #266 — the final home should be consistent with whatever that lands on.)

Relationship to myst-lint (#268)

#268 proposes myst-lint, a deterministic MyST quality tool (wraps markdownlint + MyST rules: directive/role validation, math-delimiter matching, code-block delimiters, code-cell validation). It shares this project's exact design philosophy (deterministic, no API cost, fast, CI/pre-commit) but a different concern:

Open question to resolve before building: one tool or two? They could ship as separate siblings, or black-formatting could be a module/rule-set within myst-lint (one deterministic "MyST quality" tool with both a structure-lint pass and a code-format pass). Decide this jointly with #268 rather than in isolation.

Scope / requirements

(capturing the essence of #221 + #240)

  • Parse md files and extract Python from both MyST {code-cell} directives and standard fenced ```python blocks
  • Only format blocks tagged python, python3, ipython, ipython3; skip others (e.g. julia) with an informative log line
  • Run black over each extracted block and write the formatted code back into the file, preserving the surrounding markdown/directive structure exactly
  • Configurable inputs:
    • files — explicit list and/or glob patterns (e.g. lecture/**/*.md)
    • toggle MyST code-cell scanning (default on)
    • toggle standard markdown block scanning (default on)
    • python-languages (default python,python3,ipython,ipython3)
    • black-args (e.g. --line-length=88)
  • Design intent: deterministic and cheap → support always-on / push-triggered CI usage, in addition to an optional comment trigger (@quantecon-code-style)
  • Check mode (non-mutating): report diffs / fail CI without committing, so it can run as a status check
  • Fix mode: commit formatted results, per-file, with a message like [{filename}] applying black changes to code (per #221)
  • Tests: unformatted MyST+markdown, already-formatted (no-op), non-python (skip), glob expansion

Reference implementation

#240 (closed) is a usable starting point — it already had a working line-by-line MyST/markdown parser, language detection, glob handling, and a test suite. Re-implement cleanly in the new repo rather than porting the Copilot bulk wholesale.

Related

  • #221 — original feature request (closed, superseded by this project)
  • #240 — closed Copilot PR; reference implementation
  • #244 — migration of actions into individual repos (the grain this follows)
  • #268myst-lint, sibling deterministic MyST quality tool; resolve "one tool or two?" jointly
  • #266 — mono-repo vs centralised actions vs hybrid (informs the home decision)
  • QuantEcon/action-style-guide — adjacent AI style tool; intentionally kept separate
  • QuantEcon/actions — build/preview/publish actions monorepo

Suggested next steps

  1. Create QuantEcon/action-code-style (+ a test-action-code-style fixture repo, matching the action-style-guide pattern)
  2. Port/clean the parser + black runner from #240
  3. Implement both check mode (always-on CI) and fix mode (comment trigger / commit)
  4. Pilot on one lecture repo, then roll out

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions