Skip to main content

📖 Papa

Hemingway-style readability linting for Markdown and text.

Papa flags hard sentences, passive voice, adverbs, and complex phrasing with a readability grade. It is an open-source alpha CLI and Python library that can run locally or in CI, emit JSON for automation, and generate a self-contained HTML report.

CI PyPI Downloads License: MIT Stars

Quickstart · CI usage · LLM workflow · How it works · Roadmap

View a sample HTML report generated by Papa. It highlights hard sentences, passive voice, adverbs, and complex phrases with a readability grade panel. The report is self-contained and uses no network assets.


Status

Papa is currently alpha software. The supported surface is intentionally small:

  • CLI command: papa
  • Python package: papa-lint
  • Input: Markdown, MDX, and plain text files, folders, quoted globs, and stdin
  • Reports: terminal, concise (file:line:col), JSON, and self-contained HTML
  • Config: papa.toml or [tool.papa] in pyproject.toml
  • CI gating: use --max-grade and the process exit code

GitHub Action packaging, SARIF/Markdown reporters, optional external linters, and built-in LLM rewriting are roadmap items.

Why Papa?

The Hemingway Editor is useful, but it is closed, manual, and not designed for docs pipelines. Other open-source writing tools exist, but they tend to have separate output formats and workflows.

Papa focuses on a narrow first job: make prose readability checks scriptable.

  • Scores readability with ARI, Flesch-Kincaid, and Gunning fog.
  • Highlights like Hemingway by marking hard sentences, passive voice, adverbs, and complex phrases.
  • Handles Markdown and MDX by ignoring frontmatter, code, tags, link URLs, and badges while preserving offsets back to the original file.
  • Emits JSON so agents, scripts, or dashboards can consume the findings.
  • Gates CI by exiting non-zero when prose is harder than your max grade.

⚡ Quickstart

pipx install papa-lint

papa post.md                          # check one file
papa docs/ --max-grade 10             # check a folder, fail above grade 10
cat post.md | papa                    # read stdin
papa post.md --report concise         # file:line:col lines for editors
papa post.md --report html -o report.html
papa --help                           # all flags, examples, exit codes

Example terminal output:

all-findings.md  ·  Grade 15, very hard to read

  1 very hard sentence · 2 passive · 2 complex phrases · 3 adverbs
  mean of ARI 14.4, FK 13.4, Fog 16.7 · 6 sentences

  7:15    passive    'was shipped'
  7:39    complex    'in order to', consider 'to'
  8:4     adverb     'basically'
  15:1    very hard  grade 18+ "Due to the fact that the multifaceted impleme…"
  15:279  passive    'was postponed'

Tips
  very hard  Split the sentence or cut words. Aim for one idea per sentence.
  passive    Name who does the action and make them the subject.
  complex    Use the plainer phrase shown in each finding.
  adverb     Cut the adverb or use a stronger verb.

Each finding starts with line:column. The grade is the mean of three readability formulas, shown as a US school grade.

Papa grades prose only. It ignores code, HTML and JSX tags, MDX imports, link URLs, images, badges, and emphasis marks. Each list item, heading, and table cell counts as its own unit, and short headings or labels are not graded.

Long output is capped. One file shows its 25 most severe findings. A run over more than 10 files shows one line per file. Add --all to see every finding. JSON and concise output always include every finding.

⚙️ Config

Put shared settings in papa.toml at the root of your repo:

[papa]
max-grade = 10
exclude = ["docs/generated/**", "CHANGELOG.md"]

You can also use [tool.papa] in pyproject.toml. Papa looks for the config in the current folder and its parents, and stops at the repo root. Exclude globs are relative to the config file. Command-line flags override the config. Use --config FILE to pick a file, or --no-config to ignore it. The terminal report names the config file that it used.

🚦 Exit codes

Code Meaning
0 No file failed --max-grade. Findings alone never fail a run.
1 At least one file is harder than --max-grade.
2 Usage, config, or input error, or no input had prose to check.

Papa skips files that have no prose, such as a file with only code blocks. It writes a note on stderr for each skipped file.

🧪 CI usage

Papa does not need a dedicated GitHub Action yet. Install it in your workflow and let --max-grade fail the job when prose crosses your threshold:

name: readability
on: pull_request
jobs:
  papa:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: python -m pip install papa-lint
      - run: papa README.md docs/ --max-grade 10 --report concise

--report concise prints one file:line:col: severity: message line for each finding. A failed gate is the only error line.

For local development, run the same command before opening a PR.

🤖 LLM workflow

Papa does not call an LLM directly. It emits structured JSON that you can hand to an agent or script:

papa post.md --report json > findings.json

The JSON lists each file with its score and findings. Each finding has source offsets, a line and column, and a hint:

{
  "version": "0.2",
  "files": [
    {
      "file": "post.md",
      "score": { "consensus": 11.4, "reading_grade": "Grade 11, hard to read", "verdict": "fail" },
      "findings": [
        {
          "start": 1423, "end": 1434, "line": 42, "column": 7,
          "category": "passive",
          "message": "Passive voice: 'is measured'",
          "hint": "Name who does the action and make them the subject."
        }
      ]
    }
  ],
  "skipped": [],
  "errors": [],
  "summary": { "files": 1, "analyzed": 1, "passed": 0, "failed": 1 }
}

The example above shows only some of the fields. See docs/llm-contract.md for a prompt pattern that uses Papa findings to guide a rewrite.

🔍 How it works

input -> Ingestor -> Analyzers -> Aggregator -> Reporters
         strip code  readability  merge spans  terminal
         + SVG       passive      + scores     json
         offset map  adverbs                  html
                     complex phrase
  1. Ingest: detect Markdown or text, strip non-prose, and preserve an offset map back to the original source.
  2. Analyze: run built-in analyzers for readability, passive voice, adverbs, and complex phrases.
  3. Aggregate: merge overlapping findings, compute document scores, and apply the optional max-grade gate.
  4. Report: render terminal, concise, JSON, or HTML output and set the exit code.

Python API

from papa import analyze

result = analyze(open("post.md", encoding="utf-8").read(), path="post.md", max_grade=10)
print(result.score.reading_grade)
print(result.score.verdict)

🗺️ Roadmap

  • GitHub Action wrapper
  • SARIF and Markdown reporters for PR annotations and summaries
  • Built-in --suggest workflow for LLM-assisted rewrites
  • MDX, HTML, and reStructuredText ingestion
  • Optional integrations with tools such as proselint, alex, and vale
  • npm, Homebrew, and Docker distribution

🆚 Comparison

Papa alpha Hemingway App write-good vale
Readability grade ✅ ✅ ❌ ❌
Sentence highlights ✅ ✅ ⚠️ ⚠️
Markdown code-block awareness ✅ ❌ ❌ ✅
CLI ✅ ❌ ✅ ✅
CI gate via exit code ✅ ❌ ✅ ✅
JSON for automation ✅ ❌ ⚠️ ✅
Open source ✅ ❌ ✅ ✅

🙏 Built on the shoulders of

Papa currently uses textstat for readability formulas and includes small built-in heuristics for the Hemingway style findings. Future optional integrations may include proselint, write-good, vale, and alex.

🤝 Contributing

Issues and PRs welcome. See CONTRIBUTING.md and our Code of Conduct. Good first issues are labeled.

📄 License

MIT © Bharadwaj Pendyala

Metadata

Release files for papa-lint 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for papa-lint 0.1.0
File Size Uploaded Provenance Actions
papa_lint-0.1.0.tar.gz 63.4 kB With provenance Details for papa_lint-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for papa-lint 0.1.0
File Interpreter ABI Platform Actions
papa_lint-0.1.0-py3-none-any.whl Python 3 none any Details for papa_lint-0.1.0-py3-none-any.whl

Total release size: 98.8 kB

Release files / papa_lint-0.1.0.tar.gz

Download URL papa_lint-0.1.0.tar.gz
Size 63.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4374dabbda8c09cea41228d5b04aad76381c2664b11719a37f4ea3b3ed092b09
BLAKE2b-256 checksum
How to use checksums
3b4306bf9957c0b5bbd182ce1bfcd04fcad14b37b29cfa95bcf1d27d57e60f82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / papa_lint-0.1.0-py3-none-any.whl

Download URL papa_lint-0.1.0-py3-none-any.whl
Size 35.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
99c7fc8d4bd732c32de7e2c2ebd373c47f23762391d02def70d9b56abbc44959
BLAKE2b-256 checksum
How to use checksums
239534faee7d8d63801e502964521f0ab3b75864fa506509a29704ec4d6b0d53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for papa-lint 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for papa_lint-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page