What it does for you
Articulate reads a draft and points at the exact words that make it read as stock or generated: a corporate verb, an antithesis, a stock connective, an em dash. It tells you whether the draft passes the bar for its kind of writing, and it can record that result so anyone can replay it later. The checks make no network call.
- Findings with locationsEach finding names its rule, its precision tier and the line and column it sits on.
- A bar that fits the registerA profile decides which tiers block: fiction reports and passes, a README blocks the precise tier, a procedure blocks more.
- A replayable receipt
articulate verifyre-derives the result and answers Match, Drift or Unverifiable. - Where you writeA CLI, a library call, SARIF for CI, an LSP server for editors and an MCP server for agent hosts.
Source: README.md at c94bf17 (version 0.9.0)
How it works, one step at a time
Scroll, or use the step buttons. The panel follows one short draft through the checker. Every finding, score and verdict below is output from Articulate at commit c94bf17, and the same output came from articulate-writing 0.9.0 installed from PyPI.
- 01
One draft, six findings
Here is a short blog post. Run
articulate check post.md --profile readme --verboseand six spans light up, each with a named rule: a corporate verb, a marketing superlative, a possible triad, an antithesis, a stock connective and an em dash.Every finding carries a precision tier. HIGH is the precise device tier. MEDIUM adds register tells. LOW is advisory and never blocks.
Source: src/articulate/detector.py,
scan_linesandGATE_TIERS - 02
The profile decides what blocks
The findings stay the same. The profile sets a level, and the level sets which tiers block.
readmeis "flavored": the three HIGH findings block, so the gate reads blocked and--gateexits 1.Switch to
narrativeand the level is "off": nothing blocks, and the gate reads ok while the findings are still reported. Switch toprocedureand the level is "strict": HIGH and MEDIUM both block, five in all.Source: detector.py
check_text; profiles.py - 03
A graded score beside the gate
The texture score is a separate 0 to 100 read of how machine-textured the whole draft is. Each HIGH or MEDIUM finding is worth 8 points per 100 words. Five findings in 57 words gives 70.2, rounded to 70. Uniform sentence lengths, repeated openers, and adverb or passive rates above a threshold add more.
The gate decides pass or fail; the score sits beside it. Under 30 words the score is 0, because the sample is too short to grade.
Source: detector.py,
texture_score - 04
Find the paragraph, and abstain on the rest
A whole-file score can hide one generated paragraph in otherwise clean prose.
--spansscores each block on its own. The middle paragraph is flagged at 100. The heading and the last paragraph are free of findings and under 30 words, so they read unverifiable: the tool abstains on them.Source: detector.py,
MIN_WORDS_FOR_VERDICT = 30; docs/walkthrough.md, step 2 - 05
Record a receipt
A receipt stores the result with the two things needed to reproduce it: the SHA-256 of the exact text and a fingerprint of the ruleset that ran. Its verdicts are Match, Drift and Unverifiable. It certifies that anyone can re-derive the result.
Source: src/articulate/receipt.py, module docstring
- 06
Replay it
verifychecks the ruleset fingerprint and the text hash, then runs the checker again and compares the findings, gate and texture score with the receipt. Same text, same ruleset, same result: Match, exit 0.Edit the receipt's gate, or drop a finding from it, and the re-derived result disagrees: Drift, exit 1. Change one word of the text, or replay under a different ruleset, and there is nothing valid to compare: Unverifiable, exit 2. Try each case in the panel.
Source: receipt.py,
verify_receipt - 07
Rewrite, then check again
The fix is to say the concrete thing. The rewrite in the panel was written for this page, outside Articulate's editor. Under the same profile it comes back clean, with a texture score of 0, and
--gateexits 0.Articulate's own editor (
fix,judge,polish) uses the calling host's model, a backend you configure, or offline mechanical fixes. Every result names the backend that made it. A rewrite is a suggestion to read against the original.Source: README.md, "Edit" and "Editing from a host or the command line"; docs/walkthrough.md, step 4
Walkthrough
Install it, run it once, then use the main feature. Each command below is real, and so is its output.
Install
Install from PyPI. The checker needs Python 3.9 or newer, runs on the standard library and works offline.
$ python -m pip install articulate-writingFirst run: check a draft
Check a draft against a profile. With
--gatethe command exits 1 when high findings block it.$ articulate check post.md --profile readme --gate [articulate] post.md [readme]: 3 high, 2 medium (blocked) texture 70/100Seal a receipt and re-derive it
Write a receipt for the check, then verify it against the same file.
$ articulate receipt post.md --profile readme > post.receipt.json $ articulate verify post.receipt.json post.md [articulate] Match: re-derived 6 findings, gate blocked, texture 70
Output from articulate-writing 0.9.0 installed from PyPI, on the post.md shown above, with the summary line of each command. The first command exits 1 because the gate is blocked.
What it does not do
- A flag is evidence of a pattern in the text. Authorship is a separate question that a flag leaves open.
- The checks are patterns over the text, so device-clean generated prose can score low.
- The editor works toward writing quality for the reader. What it accepts is set by the gate and the quality scores.
- A clean gate or a Match receipt says the prose was screened under a named ruleset. Truth is checked elsewhere: a clearly written proof can still be false.
- A content-free audit receipt drops the matched words, yet the rule and line remain. For a closed-vocabulary rule that narrows the flagged word to a small public set.
- The optional editor sends text to whichever model backend you configure, under that provider's terms. Set
ARTICULATE_LOCAL_ONLY=1to keep editing on loopback Ollama and mechanical edits.
Source: README.md "Privacy" and "Scientific and mathematical writing"; detector.py texture_score; docs/boundaries.md, at c94bf17
Check what stuck
Answer each one in your head before you open it.
The same draft is blocked under readme and ok under narrative. What changed?
The profile's level. The findings are identical; "flavored" blocks the HIGH tier and "off" reports them and passes.
Where does the texture score of 70 come from?
Five HIGH or MEDIUM findings at 8 points each per 100 words, over 57 words: 70.2, rounded to 70. Those findings are the whole score here.
Why does the last paragraph read unverifiable?
It is free of findings and under 30 words. Below that floor the tool abstains, because a short text is too small a sample to call clean.
Editing the text gives Unverifiable, while editing the receipt gives Drift. Why the difference?
A changed text has a new hash, so the receipt's findings belong to a different text and the replay stops. A changed receipt still matches the text and ruleset, so the replay runs and disagrees with what the receipt says.