Show HN: Writing-eval, local deterministic style checks for AI-written drafts Majestic Labs released writing-eval, a local, deterministic tool that scores AI-written drafts against a reusable style profile built from reference prose, with no hosted models or uploaded source material. The Python 3.11+ tool runs on CPU, produces deterministic results, and includes a corpus evaluation pipeline for comparing writing systems. It is designed to help teams measure editorial voice consistency and run regression checks after changes to prompts, models, or reference corpora. Built by David Paluy https://github.com/dpaluy from Majestic Labs https://majesticlabs.dev/?utm source=github&utm medium=readme&utm campaign=writing-eval . writing-eval gives teams a repeatable way to measure a draft against a chosen editorial voice. Build a reusable style profile from reference prose, check a draft against it, and get specific evidence about differences in clarity, readability, sentence rhythm, vocabulary, and detected writing patterns. The project also includes a corpus evaluation pipeline for comparing generated outputs with a reference corpus. Use it for local diagnostics, regression checks, and repeatable comparisons between writing systems. Everything runs locally on the CPU and produces deterministic results. The tool does not call hosted models, upload source material, train models, optimize detectors, or reproduce a proprietary evaluation method. Python 3.11 or newer and uv are required. uv sync The release version is the project .version value in pyproject.toml . Update this value for every release. To show the installed release version, run: ./writing-eval --version Build a style profile from a directory of an author's posts, then check a draft against it: ./writing-eval profile build acme --from posts/acme ./writing-eval check draft.md --style acme The first command ingests the .md and .txt files under posts/acme and writes a reusable profile named acme into data/profiles/acme/ . The second audits draft.md and produces a scored, profile-relative assessment. The human-readable report shows four section scores, actionable issues with current and target values, editing instructions, success criteria, source locations, and general statistics. The profile name is kept out of the report body; it remains in JSON metadata for reproducibility. The repository includes an agent skill at skills/writing-eval/SKILL.md /majesticlabs-dev/writing-eval/blob/master/skills/writing-eval/SKILL.md . Agent harnesses that support SKILL.md instructions can load it to select the correct command, build or choose a profile, interpret exit codes and JSON, protect private source material, and report results without overstating the heuristic score.The skill controls the local CLI and does not bundle the executable. Use it from a repository checkout after running uv sync . Example requests: Use writing-eval to check docs/draft.md against the acme profile. Summarize the highest-priority issues with their source locations. Build a writing-eval profile named product-docs from the authorized prose in data/product-docs, then check docs/new-guide.md against it. Compare the writing systems in runs/release-candidate against data/reference-corpus.jsonl and explain the report verdict. | Capability | Use it for | |---|---| | Style profiles | Build a reusable baseline from approved prose | | Draft checks | Compare one Markdown or plain-text file with a profile | | Rule-based linting | Locate configurable writing tendencies | | Corpus evaluation | Compare several output systems consistently | | Markdown and JSON reports | Support human review, automation, and regression gates | AI-generated writing is easy to demo and difficult to evaluate consistently. A draft can be grammatically correct while still missing an organization's voice, preferred structure, or editorial constraints. writing-eval turns those expectations into a local, versioned measurement process. Teams can run the same checks after changing a prompt, model, reference corpus, or rule set and see what improved or regressed. This project is one narrow example of a broader Majestic Labs principle: an AI workflow needs a company-controlled definition of acceptable work. Read Building Private AI Evals https://majesticlabs.dev/blog/202607/building-private-ai-evals?utm source=github&utm medium=readme&utm campaign=writing-eval for the broader approach. Use with LLM agents use-with-llm-agents Single-document checks single-document-check Style profiles style-profiles Metrics metrics Corpus evaluation and benchmark corpus-evaluation-and-benchmark Limitations limitations License and contributions license-and-contributions Managed hosting managed-hosting About Majestic Labs about-majestic-labs ./writing-eval check audits one draft the way a linter audits one source file, without any JSONL wrapping. It takes a Markdown or plain-text file, or - to read from standard input. ./writing-eval check draft.md ./writing-eval check draft.md --references data/reference-corpus.jsonl cat draft.md | ./writing-eval check - Options: --rules selects the rule file default: the builtin rule set that ships with the package . The repository also ships an optional overlay with extra AI-writing tells; see The anti-ai overlay the-anti-ai-overlay . --references is an optional JSONL reference corpus. When omitted, the token 1-gram L2 metric is skipped, rendered as n/a , and a note is printed to standard error. --style compares the draft against a named style profile and renders a scored assessment see Style profiles style-profiles . It is mutually exclusive with --references ; passing both is a usage error. --profiles-root locates profiles for --style default data/profiles . --format text|json selects human-readable text or JSON on standard output default text . --json PATH writes the same JSON result to a file while preserving the selected standard-output format. Without --style , text output retains the linter format: one finding per line, sorted by position, using 1-indexed line and column offsets computed from the real character positions of each match, followed by a metrics block: draft.md:1:1 warn metadiscourse openers: Remove the metadiscourse opener and state the point directly. | span: In this article, draft.md:1:21 warn polish vocab: Replace overused polish vocabulary with specific language. | span: delve metrics: word count: 15 tell rates by severity: warn: 400.000000 mean sentence length: 7.500000 sentence length variance: 2.250000 repeated opening rate: 0.000000 token 1gram l2: n/a quality metrics informational : flesch reading ease: 52.000000 flesch kincaid grade: 9.000000 mtld: 14.000000 paragraph stats: paragraph count: 1.000000 mean paragraph sentence count: 2.000000 single sentence paragraph rate: 0.000000 The example values are illustrative. Scores below 10 tokens render mtld as n/a , and text without a sentence renders the readability scores as n/a . With --style , text output uses the scored assessment described in Check a draft against a profile check-a-draft-against-a-profile . Exit codes distinguish completed checks from input errors: 0 : the check completed, with or without findings. 1 : a usage or input error missing file, unreadable rules, invalid JSONL . A style profile is a deterministic fingerprint of one author's writing, built from a corpus of their prose. Build a profile once, then check any draft against it to see how far the draft sits from that voice and which vocabulary and structure differ. The author's own voice is just one profile among many. ./writing-eval profile build