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 blocks nothing, 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)
Watch
No concept film fits this tool closely yet. The walkthrough below covers it in text, with real commands and output.
Video walkthrough: coming with the next release.
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 score never changes the gate. Under 30 words it returns 0, because there is too little text to say anything.
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 have no findings and fewer than 30 words, so they read unverifiable. The tool abstains and makes no clean claim about 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. It holds no Trusted or Approved field. It certifies that the result can be re-derived, and nothing about authority.
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 has no findings, 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 mechanical fixes with no model. 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. Python 3.9 or newer; the checker uses the standard library only and makes no network call.
$ 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
- It does not establish who or what wrote a text. A flag is evidence of a pattern, not proof of authorship.
- The checks are patterns over the text. They cannot see token probability, so device-clean generated prose can still score low.
- It is not an evasion tool. The editor works toward writing quality and never tunes prose toward a lower detector score.
- A clean gate or a Match receipt says the prose was screened under a named ruleset. It says nothing about whether the content is true; 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 allow only 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?
Only the profile's level. The findings are identical; "flavored" blocks the HIGH tier and "off" blocks nothing.
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. Nothing else added to it here.
Why is the last paragraph unverifiable and not clean?
It has no findings and fewer than 30 words. Below that floor the tool abstains and makes no confident clean claim.
Editing the text gives Unverifiable, while editing the receipt gives Drift. Why the difference?
A changed text no longer matches the receipt's hash, so there is nothing valid to re-derive. A changed receipt still matches the text and ruleset, so the replay runs and disagrees with what the receipt says.