import { readability, statistics, sentiment } from 'textlens';
const text = await Bun.file('article.md').text(); // or fs.readFileSync
const stats = statistics(text);
const scores = readability(text);
const mood = sentiment(text);
const pass = scores.consensusGrade <= 10
&& stats.avgSentenceLength < 25
&& mood.label !== 'negative';
console.log(pass ? 'PASS' : 'FAIL', `Grade ${scores.consensusGrade}`);
That's a working content quality gate. It reads a file, checks the grade level, sentence length, and sentiment — then passes or fails. You can run it locally, in CI, or as a pre-commit hook.
Here's how each piece works.
Why grade level matters
The average American reads at an 8th-grade level. If your blog post scores at grade 14, you've lost most of your audience before paragraph two.
Grade level isn't a judgment on your readers. It's a measure of cognitive load. Short sentences and common words reduce friction. The NYT writes at grade 6-8. So does every high-performing landing page you've ever read.
Install
npm install textlens
Zero dependencies. Ships ESM and CommonJS with TypeScript types.
Step 1: Get text statistics
import { statistics } from 'textlens';
const stats = statistics(`
Search engines now penalize thin content. Your article needs
enough depth to rank, but sentences short enough to keep readers.
Finding that balance is the entire game.
`);
console.log(stats.words); // 30
console.log(stats.sentences); // 3
console.log(stats.avgSentenceLength); // 10
console.log(stats.avgSyllablesPerWord); // 1.57
avgSentenceLength above 20 is a red flag. Above 25 and readers start re-reading to parse meaning.
Step 2: Check readability
import { readability } from 'textlens';
const scores = readability(text);
console.log(scores.consensusGrade); // 8.2
console.log(scores.fleschReadingEase.score); // 62 (higher = easier)
textlens runs 8 readability formulas — Flesch-Kincaid, Coleman-Liau, Gunning Fog, SMOG, ARI, Dale-Chall, Linsear Write, and Flesch Reading Ease — then averages them into a single consensusGrade. One number, backed by seven formulas.
Target grades by audience:
| Audience | Grade |
|---|---|
| Blog post | 6-8 |
| Technical docs | 8-12 |
| Academic paper | 12+ |
Step 3: Check sentiment
import { sentiment } from 'textlens';
const mood = sentiment('This product has terrible UX and crashes constantly.');
console.log(mood.label); // 'negative'
console.log(mood.negative); // ['terrible', 'crashes']
A negative-sentiment README or changelog entry sends the wrong signal. This check catches it before you publish.
Put it together: a CI quality gate
Here's a GitHub Actions step that blocks merges when docs are too complex:
name: Content quality
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install textlens
- name: Check readability
run: |
node -e "
const { readability } = require('textlens');
const fs = require('fs');
const files = fs.readdirSync('docs').filter(f => f.endsWith('.md'));
let failed = false;
for (const file of files) {
const text = fs.readFileSync('docs/' + file, 'utf8');
const r = readability(text);
const grade = r.consensusGrade;
const status = grade <= 12 ? 'PASS' : 'FAIL';
console.log(status + ' ' + file + ' (grade ' + grade + ')');
if (grade > 12) failed = true;
}
if (failed) process.exit(1);
"
This scans every markdown file in docs/ and fails the build if any score above grade 12.
Beyond the basics
The analyze() function returns everything at once — readability, statistics, reading time, keywords, sentiment, and an extractive summary:
import { analyze } from 'textlens';
const result = analyze(text);
console.log(result.readingTime.minutes); // 4
console.log(result.keywords[0].word); // 'readability'
console.log(result.summary.sentences); // top 3 sentences
Or use the CLI for a quick check without writing any code:
npx textlens README.md
npx textlens README.md --json | jq '.readability.consensusGrade'
Source
Disclosure: I built textlens to solve this for my own writing workflow. It's MIT licensed with zero dependencies.
Top comments (0)