slopscore 0.9.0 adds a dimension for manufactured sincerity
A probe document dense with I have to be honest, honest framing, and truth be told used to
score 16.9 out of 100 on slopscore, labeled “low”, with zero evidence spans. All fifteen slop
dimensions read 0.0. The document also earned positive credit on human_writing_signals, so the
sincerity filler was pushing the score down.
Version 0.9.0 adds a sixteenth dimension, performative_candor, and the same probe now scores
56.0, “elevated”, with 12 spans. A more realistic 243-word passage of the same kind scores 68.6.
pip install -U slopscore-lint
slopscore is a prose linter for AI-slop writing patterns.
It reads a file and returns a 0 to 100 score, a per-dimension breakdown, and the exact phrases that
triggered each finding. It scores patterns in text and says nothing about who wrote it. Version
0.9.0 is MIT licensed, on PyPI as slopscore-lint, and runs on Python 3.11 and up.
What the old scorer missed
The gap was structural. slopscore already had a dimension for fake insight, insight_signaling,
which flags phrases that announce a realization instead of containing one. It already had one for
fake evidence, weasel_attribution, covering impersonal attribution and unearned certainty. It had
nothing for fake vulnerability: a writer performing the act of being forthright.
The miss compounded. human_signals.py was crediting the word “told” inside truth be told as a
human writing signal, so a candor span was scored as evidence of a person rather than against the
document. In 0.9.0 that credit is withdrawn, and a span is no longer penalized and rewarded at the
same time.
The pattern shows up in assistant output as filler that softens a critique by framing it as a confession, or that lowers a reader’s resistance with confessional styling. The tell is the frame and the density. “Honestly” in speech is ordinary English.
The rules
Ten rules run by default. Three more sit behind --broad, the opt-in tier for higher-false-positive
patterns.
| Rule | Severity | Fires on |
|---|---|---|
CANDOR_CONFESSIONAL_FRAME | medium | I have to be honest, let me be candid, if I'm being honest |
CANDOR_DISCLOSURE_MARKER | medium | truth be told, full disclosure:, cards on the table |
CANDOR_HONEST_ABSTRACT_NOUN | medium | honest framing, honest limits, candid appraisal |
CANDOR_HONEST_COMMON_NOUN | low | the honest answer, an honest assessment |
CANDOR_ADVERB_PARENTHETICAL | low | Honestly, and Frankly, as a clause opener |
CANDOR_TO_BE_HONEST | low | to be honest, in all honesty |
CANDOR_SINCERITY_COLLOCATION | low | genuinely interesting, truly remarkable |
CANDOR_NOT_LIGHTLY | medium | I don't say this lightly, I'm not going to pretend |
CANDOR_ADMISSION | low | I have to admit, used as a transition |
CANDOR_PUSH_BACK | low | I'm pushing back on that |
The canonical case is CANDOR_HONEST_ABSTRACT_NOUN. Writing honest limits where “limitations”
would do adds a claim about the writer’s character to a sentence that was about the software. The
head noun is the gate: the rule matches honest framing and honest reckoning but not
honest work, and conversation is deliberately absent from the list, because “an honest
conversation about the budget” is ordinary workplace prose.
Every example above sits in a code span in this file. slopscore’s Markdown reader skips code spans and fenced blocks, so a post about the rules does not trip them.
Why it is a separate dimension
The obvious move was to add these as more insight_signaling rules. That fails, because the genre
multipliers point in opposite directions.
| Profile | performative_candor | insight_signaling |
|---|---|---|
| social | 0.6 | 1.15 |
| marketing | 1.2 | 0.90 |
| blog | 1.1 | 1.15 |
| essay | 1.1 | 1.20 |
| technical | 0.8 | 0.60 |
| academic | 1.0 | 0.60 |
Conversational “honestly” is native human speech in a social post, so candor is softened there while
insight signaling is boosted. Marketing inverts the pair: manufactured sincerity is marketing’s
house style, while “first principles” and “the crux” are comparatively harmless in a landing page. A
shared dimension could express neither. The 243-word passage above scores 68.6 under the blog
profile and 45.9 under social, which is the multiplier doing its job.
The rules that have to stay quiet
The negative cases took more work than the positive ones. tests/test_performative_candor.py
asserts that nothing fires on fourteen ordinary sentences, including “He is an honest man who did
honest work for thirty years”, “It was an honest mistake; the clerk fixed it the same day”, “The
company disclosed the full disclosure schedule under Regulation FD”, “Real talk shows dominated
daytime television in 1994”, and the email sign-offs “Sincerely,” and “Yours truly,”. The rules
compile under MULTILINE, so an unanchored Sincerely, pattern would have fired on the last line
of every email in a corpus.
Two of those fourteen are the ones that matter most:
Honestly speaking, my first winter in Helsinki was very hard.
Frankly speaking, the training was too fast for me.
Both are calques common in non-native English. Neither fires, because
CANDOR_ADVERB_PARENTHETICAL requires a comma directly after the adverb. A code comment calls this
the rule set’s most important fairness property, and benchmark.jsonl carries non_native rows
asserting it rather than leaving the guarantee to unit tests.
Two core rules do fire on the protected slices: CANDOR_TO_BE_HONEST on 1 of 15 non-native rows
(0.07) and CANDOR_ADVERB_PARENTHETICAL on 1 of 17 simple-English rows (0.06). Both sit under the
project’s 0.10 and 0.15 acceptance bars, and document-level false-positive rate stays at 0.00 on
both slices. Those rates are published in MODEL_CARD.md rather than tuned away.
The dimension is also deliberately dull. It saturates at 4.0 weighted hits per 100 words instead of
the 3.0 the other packs use, because at 3.0 a single low-severity hit in a 60-word document scores
0.56 and crosses the corroboration gate’s threshold. At 4.0 it scores 0.42 and stays under. Bare
sincerity adverbs are --broad-only, the same call already made for bare quantifiers and
intensifiers. Running the new code against the old dataset leaves the rule scorer’s catch rate at a
1% false-positive threshold unchanged at 0.800, so the new pack did not cost precision elsewhere.
Where this still fails
performative_candor joins the set of weak dimensions, which means its contribution is multiplied
by 0.3 when nothing else in the document is elevated. The consequence is documented in the model
card: a 119-word passage that fires 13 candor rules scores 5.6. The dimension is the only thing
elevated, so it gets damped, and human_writing_signals saturates at 1.0 on the passage’s dates and
figures.
That is the intended behavior, and it is worth understanding before you rely on the score. A concrete, specific, first-person passage carrying heavy candor filler reads as plausible human memoir. The benchmark carries no positive fixture of that shape on purpose, because labeling it slop would teach the evaluation set to punish specific writing. The findings still appear as evidence spans. Only the composite score stays low.
Two more limits. The fairness command builds its scorer with default settings, so it never
measures the --broad tier, and CANDOR_BROAD_BARE_CANDOR fires on ESL rows by construction. That
unmeasured risk is why the tier is off by default. And these rules carry the source tag
AI-tell (2024-2026), meaning reported observation. The lexical markers cite published work
(Juzek and Ward, 2024 and Kobak et al., 2025); the candor pack
does not, and the file says so.
Upgrading
SCHEMA_VERSION is now 0.9.0 and the JSON report gains a performative_candor entry under
dimensions. For pre-commit, bump rev to v0.9.0; the GitHub Action reference
jman4162/slopscore@v0 floats forward on its own.
The upgrade note that matters: ten new rules run by default, so scores move on content you have not
touched, and a pipeline gated with --fail-on can start failing. Record a baseline first.
slopscore-lint baseline ./content --recursive -o .slopscore-baseline.json
slopscore-lint scan ./content --recursive --baseline-file .slopscore-baseline.json --fail-on-new
0.9.0 also fixes a display bug from 0.8.0: insight_signaling was missing from the label map, so it
rendered in neither the Markdown nor the console dimension table while still contributing to the
score. The published evaluation tables were refreshed from 128 to 141 examples in the same release.
What this looks like in my own projects
I run slopscore daily. Every draft for this site goes through it before it ships, and it runs as a
pre-commit hook and a CI gate at --fail-on high across my repos, with a recorded baseline so
existing files do not block a commit. The rules in this release came out of that use: the candor
pack exists because I kept reading assistant output that scored clean and read wrong.
This post scores 3.0 out of 100 under the blog profile, with no evidence spans. The only two
dimensions above zero are genericity at 0.24 and human_writing_signals, which saturates at 1.0 on
the post’s dates and figures. The rule names and example phrases above sit in code spans, which the
Markdown reader skips, so that number covers the prose. And the saturated human-writing signal is
doing most of the work, which is the same mechanism that lets the 119-word passage above score 5.6.
The wider argument for publishing a linter’s failure modes alongside its numbers is in how to measure a grader; the 5.6 passage above is exactly the kind of case a report card has to name. The original design writeup, covering the rule system, the benchmark, and why authorship detection is the wrong question, is in the slopscore introduction. For how I think about grouping this sort of failure before trying to fix it, see failure taxonomies.
Try it
Scan text in your browser at slopscore.john-hodge.com. The repo, the docs, the changelog, and the PyPI package have the rest.
slopscore is beta software and intentionally conservative. The benchmark and fairness numbers are the project’s own evaluation on small labeled sets, so read them as a sense of scale rather than a guarantee.
slopscore is an independent project I build on my own time. The views are my own and do not represent any current or former employer.
Frequently asked questions
What is performative candor in writing?
Sincerity used as a rhetorical move rather than as information. Three shapes cover most of it: a point framed as a difficult confession, a sincerity adjective bolted to an abstract noun, and manufactured reluctance. The words themselves are ordinary English. The tell is the frame and the density.
Does slopscore detect whether text was written by AI?
No, and it must never be used that way. It scores writing patterns in text, not authorship, and says nothing about who wrote a document. Authorship detectors are unreliable and biased against non-native English speakers, which is why slopscore is not one.
Will upgrading to 0.9.0 change the scores of my existing content?
Yes. Ten new rules run by default, so documents that scored clean under 0.8.1 can produce new findings. If you gate CI with --fail-on, record a baseline with slopscore-lint baseline and switch to --fail-on-new before you upgrade, so the first run does not turn into a cleanup project.
Why is performative_candor a separate dimension instead of more insight_signaling rules?
The genre multipliers point in opposite directions. On the social profile, insight_signaling is boosted to 1.15 while performative_candor is softened to 0.6, because conversational 'honestly' is native human speech. On marketing the pair inverts: 1.2 for candor against 0.9 for insight signaling. One dimension cannot carry both weights.
Does the new dimension over-flag non-native English?
The calques 'Honestly speaking' and 'Frankly speaking' do not fire, because the adverb rule requires a comma directly after the adverb. On the protected evaluation slices, one rule fires on 1 of 15 non-native rows and another on 1 of 17 simple-English rows, both under the project's acceptance bars, with document-level false-positive rate at 0.00. Bare sincerity adverbs are opt-in through --broad for the same reason.
More in Side projects
- Gumline, a private dental habit tracker · 2026-07-09
- slopscore: a transparent linter for AI-slop writing patterns · 2026-06-25