Every AI-generated paragraph I read these days has the same problem. Grammar: fine. Spelling: perfect. Rhythm: a staircase where every step is exactly the same height. Sentences run 15, 16, 14, 15 words, then reset. That uniformity is what screams "machine wrote this" long before any detector gets involved.
So I built a linter for it. Not a rewriter — a linter. The distinction matters more than I expected when I started.
Why a Linter and Not Another Humanizer
The humanizer market is crowded with tools that rewrite your text wholesale. They work, sort of. But the output has its own synthetic texture, and you lose your voice in the exchange. A linter takes the opposite bet: keep the author's words, flag the mechanical patterns, let the human decide what to change.
This also makes the tool deterministic. Same input, same diagnosis, every time. Try building a QA process around a stochastic rewriter and you'll see why that property matters.
The Architecture: Zero Dependencies, 8 Languages
humanizer-lint is a single Node script with zero npm dependencies. The detector stack:
- Lexical packs for 8 languages (en, id, es, ms, fr, de, pt, zh) — stock phrases are language-specific
- Indonesian morphology handling, because affixes like "memperbaiki" and "diperbaiki" are the same root
- Rhythm metrics: sentence-length burstiness CV, paragraph CV, adjacent delta
- MATTR for lexical richness, n-gram repetition for templated phrasing
- A compression-ratio check that catches low-entropy text
Every metric is reported, not just pass/fail. The numbers are indicators to review, not mechanical style targets — we learned that distinction the hard way.
What Dogfooding Taught Us
Running the linter against our own technical blog surfaced an honest limitation: SEO pillar articles need to repeat product terms for keyword coverage, which drives text compression ratio below the generic threshold (technical article: ~0.56, general prose: ~0.73). That's not slop — it's the economics of topic-focused writing colliding with a text-entropy metric. We shipped --min-score as an escape hatch, and the finding itself is worth a follow-up post.
This is also why the rhythm gate only kicks in at 8+ sentences: short copy (2-4 sentences) gets SKIPped for rhythm while lexical and domain checks still run. Calibrating gates by content type beat pretending one threshold fits all.
Try It
The whole thing runs locally — nothing leaves your machine. Install via npm, or paste text into the browser demo. It's MIT-licensed and ships as an agent skill, so your coding assistant can lint its own output before it reaches you.
Repo: github.com/yanstd888/humanizer-lint | Live demo: humanizer.aiclub.ai.id | Skills-sh : skills.sh/yanstd888/humanizer-lint/humanizer-lint

Top comments (0)