📖 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.
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.tomlor[tool.papa]inpyproject.toml - CI gating: use
--max-gradeand 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
- Ingest: detect Markdown or text, strip non-prose, and preserve an offset map back to the original source.
- Analyze: run built-in analyzers for readability, passive voice, adverbs, and complex phrases.
- Aggregate: merge overlapping findings, compute document scores, and apply the optional max-grade gate.
- 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
--suggestworkflow for LLM-assisted rewrites - MDX, HTML, and reStructuredText ingestion
- Optional integrations with tools such as
proselint,alex, andvale - 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.