DEV Community

ckmtools
ckmtools

Posted on

Build a Content Quality Checker in 10 Lines of TypeScript

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}`);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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);
          "
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or use the CLI for a quick check without writing any code:

npx textlens README.md
npx textlens README.md --json | jq '.readability.consensusGrade'
Enter fullscreen mode Exit fullscreen mode

Source

Disclosure: I built textlens to solve this for my own writing workflow. It's MIT licensed with zero dependencies.

Top comments (0)