agentleFS
HefrockHefrock/agent-skills/skills/style-obfuscator/SKILL.md

style-obfuscator

Skill0 starsChanged 4 months agoMITView on GitHub

Computes a deterministic stylometric fingerprint of a piece of writing — function-word frequency, sentence-length distribution, punctuation habits (em dash rate, contraction rate), approximate passive-voice rate, and repeated-phrase detection — and optionally scores its similarity against a reference corpus of your own known writing (your vault, past posts). --corpus pools many of your own pseudonymous drafts into one aggregate fingerprint and reports the "aggregation gap" against a single-document check — catching the case where individually-clean posts are collectively identifying, the way real cluster-based stylometric matching actually works. No model or network call; classic authorship-attribution stylometry (Mosteller & Wallace, Burrows' Delta) has always been word-frequency statistics, not semantic understanding. Use when the user wants to check whether a draft (a GitHub PR/README/commit message, a LinkedIn post, any public professional writing) carries a distinctive fingerprint that could later be used to match it against something they wrote under a different identity, asks "does this sound too much like me," "will this be traceable back to my other writing," "do my pseudonymous posts collectively give me away even if each one looks fine," or wants a stylometric self-check before publishing publicly. Triggers on "check my writing style," "stylometric fingerprint," "does this sound like me," "will this be traceable to my other posts," "aggregation gap," "style-obfuscator," and the script fingerprint.py. Does NOT rewrite or obscure text — see "What's NOT built here" for why that's a separate, harder, not-yet-built tier.

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every other mode here compares one draft to your reference.

    line 108
  2. Very specific:

    Never present a similarity score as a verdict ("this is/isn't the same author") — it's a number for you to judge, the same way agent-eval's …

    line 215
  3. Very specific:

    Always surface the short-input noise warning when word count is under 100 — a precise-looking percentage on a tiny sample is exactly the …

    line 218
  4. Very specific:

    Never print top_phrases' verbatim text by default, in either the human report or --json — a tool meant to help you avoid becoming …

    line 225
  5. Very specific:

    Never write the fingerprint feature vector or top_phrases to --log-dir — a durable trend log should hold the minimum needed to answer "is …

    line 229

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Style Obfuscator

Tier 1 of a two-tier design: flag, don't rewrite. This version computes a stylometric fingerprint and tells you what's distinctive about a draft's writing style; it never touches the text itself.

The actual threat model

Publishing under your real name on GitHub or LinkedIn isn't itself the risk — that writing is already attributed to you. The risk is that a distinctive enough public writing fingerprint becomes the reference corpus an adversary (or, per privacy- threat-oracle's autonomous_ai_agent adversary class, an automated scraper doing this at machine scale) uses to match you to something you didn't attribute your name to — a pseudonymous post, an anonymous opinion, anything you'd rather keep separate from your professional profile. Hardening the public side makes it a weaker reference for that kind of matching.

The most directly useful workflow follows from that: build a reference corpus from your own known writing, then run a pseudonymous draft against it before posting — not to check the public draft alone, but to see whether the anonymous one leans on the same distinctive habits (em dash rate, favorite hedges, a specific function-word profile) that a matcher would key on.

Why this is deterministic-only, not model-based

Unlike privacy-linter's deferred stylometric class (genuinely blocked on a local- model decision that hasn't been made — see its references/leak-taxonomy.md), computing a stylometric fingerprint doesn't need a model at all. Real authorship stylometry research (the Federalist Papers disputed-authorship study, Burrows' Delta) has always been built on function-word frequency and sentence statistics — topic- independent, unconsciously produced, and fully computable with collections.Counter and arithmetic. What genuinely needs judgment is the comparison step — deciding what a similarity score means for a specific threat model, and whether two corpora really are the same author — which is exactly why this tool reports numbers and deltas, not a verdict.

One consequence worth being direct about: because the content here is about to be published publicly anyway (a GitHub PR, a LinkedIn draft), sending it to an external LLM for a rewrite suggestion would not be a new disclosure the way it would for privacy-linter's private-vault content — that's the exact reason a future rewrite tier is a fundamentally different, more tractable design problem than privacy- linter's deferred stylometric detection. See "What's NOT built here."

How this works

python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference known_writing.md
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference ~/vault/Journal/
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --text -
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference known.md --json
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference known.md --emit-findings
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference known.md --log-dir ~/.style-obfuscator-log
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --show-phrase-text
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --corpus ~/pseudonymous_drafts/ --reference ~/vault/
  1. Fingerprint the draft. Reports word/sentence/paragraph counts, mean sentence length and its standard deviation, vocabulary richness (type-token ratio), em-dash rate, comma/semicolon/exclamation rates, contraction rate, an approximate passive-voice rate (a regex heuristic — see the caveat below, not real POS tagging), the most frequent function words, and any 3-4 word phrase that repeats. Repeated phrases are redacted by default in both the text and --json report — shown only as a count + n-gram length ("3 found (3/4-word, text redacted...)") — because top_phrases is verbatim draft text, and a fingerprint report that leaks the actual phrases undermines the reason this tool exists if that report is ever shared, pasted, or logged. Pass --show-phrase-text to reveal the real phrases for your own terminal.
  2. Compare against a reference, if given (--reference, a single file or a directory — a directory's .md/.txt files are concatenated into one corpus, since a reference is "everything I know is really you," not one sample). Reports an overall similarity score (0-100%) plus which specific features drive it — trivial zero-vs-zero absences (neither text ever uses a semicolon) are filtered out of the headline "closely matches"/"most different" lists, since an absence isn't an actionable tell the way a shared presence is.
  3. Act on what it tells you, don't trust a single score. A 70% similarity on a 35-word draft is noisy — short input gets an explicit warning in the report, not a confident-looking number. The tool never says "this is/isn't the same author"; it reports comparable numbers and leaves the judgment call to you.
  4. --emit-findings turns the comparison into a privacy-linter-shaped Finding list instead of a report — {severity, leak_class: "stylometric", finding, reason, location}, the exact shape scan_diff.py --json already produces and privacy-threat-oracle's --from-linter-json already reads. Below --emit-findings-threshold (default 60%) it emits [] rather than a synthetic low-severity finding — the same "report nothing for clean content" convention privacy-linter uses. Requires --reference; there's nothing to fingerprint-match without one.
  5. --log-dir DIR appends one timestamped JSON record per run — {timestamp, label, reference_label, similarity_score, severity, mode, document_count} — for trend-tracking whether your stylometric distinctiveness is rising or falling over time. It never writes the fingerprint's numeric feature vector or top_phrases: even the feature rates, while not literal draft text, ARE the stylometric signal this tool exists to expose, so a durable log of them (unlike a one-off report you read and discard) is exactly the kind of data-at-rest this tool should minimize, not accumulate. Also requires --reference.
  6. --corpus DIR_OR_FILE is an alternative to --file/--text for the draft side: a directory (or single file) of your own pseudonymous writing, pooled into one aggregate fingerprint — see "The aggregation gap" below for why this exists and what it catches that single-document checking can't.

The aggregation gap: why single-document checking isn't enough

Every other mode here compares one draft to your reference. But real stylometric deanonymization research doesn't usually match one anonymous document at a time — it clusters many pseudonymous documents together first (more text, less noise, a much stronger signal), then matches the pooled cluster. That means you can run every pseudonymous post through --file individually, get a clean score each time, and still have those posts collectively converge on a stable "shadow" fingerprint of the anonymous identity itself — because pooling several noisy, individually-clean documents can average out to a stronger match than any one of them showed alone.

--corpus, paired with --reference, simulates exactly that attack against yourself and reports the aggregation gap — how much higher the pooled corpus scores than its own highest-scoring individual document:

python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --corpus ~/pseudonymous_drafts/ --reference ~/vault/
# --- Aggregation gap (12 document(s) pooled) ---
# Pooled corpus similarity: 78.0%
# Highest individual document: 55.0% (posts/2026-03-14-thoughts.md)
# Aggregation gap: +23.0 points -- the pooled corpus is more identifying than any
# single document in it. This is the blind spot a cluster-based stylometric match
# exploits: no single post triggers a check, but pooled together they would.

A large gap (>= 10 points, AGGREGATION_GAP_NOTABLE) means your pseudonymous posts are individually clean but collectively convergent. A small or negative gap is reassuring: pooling didn't reveal anything a single-post check wouldn't have already caught. Requires you to have kept local copies of your own pseudonymous writing to point --corpus at — the tool can't discover these on its own (no crawling LinkedIn/ GitHub for "everything I've posted under this identity"), same trust-the-input precondition --reference already has.

Drift over time falls out of existing infrastructure, not a new one. There's no separate trend engine: re-run --corpus ... --log-dir ... periodically as your pseudonymous folder grows, and the existing --log-dir records (now carrying mode and document_count) give you the history to read back. --emit-findings and --log-dir both work on --corpus unchanged, since they only ever consumed a (score, severity, label) triple to begin with — --corpus's pooled score just becomes another value for that same triple, with the aggregation-gap sentence folded into the Finding's reason when the gap is notable.

A concrete example of the reference-corpus workflow

# Build a reference from everything already attributed to you:
python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file pseudonymous_draft.md --reference ~/vault/
# High similarity on em_dash_per_1k, fw_but_per_1k, contraction_per_1k?
# Those are the specific things to vary before posting pseudonymously.

What's NOT built here

  • Rewriting/obscuring. Detection only — never touches the draft. Auto-rewriting to reduce distinctiveness is generation, not detection, and a crude rule-based version (synonym substitution, sentence restructuring) is a historically weak defense — tools like Anonymouth used this approach with mixed real-world results, and it risks mangling meaning. A real version needs an actual paraphrase-quality LLM call, presented as a diff to review and never auto-applied — a genuine follow-up, not attempted in this version.
  • Real POS-based passive-voice detection. passive_voice_per_1k is a regex heuristic (is/are/was/were/be/been/being + a past participle) — it will false-positive on an adjectival use ("was excited") and false-negative on any irregular participle not in its fixed list. Treated as a directional signal only.
  • Cross-identity matching against a third party's writing. The reference corpus here is always your own known writing. This tool tells you what a matcher would key on; it doesn't run an actual match against someone else's corpus, and never sends anything anywhere to do so.
  • Markdown-aware tokenization. Headers, list markers, and code fences aren't stripped before analysis — this is built for prose (a LinkedIn draft, a PR description, README prose), and heavily-structured markdown will skew the sentence/ paragraph statistics. Run it against the prose portions.
  • Any presence at the actual point of disclosure (a LinkedIn post editor or GitHub PR box typed directly in-browser, never touching a file fingerprint.py can read). A structural property of this whole stack's file-based architecture, not something fixable in this skill alone — see privacy-threat-oracle/SKILL.md's "What's NOT built here" for the full writeup and what was considered and deferred.
  • Automatic discovery of your own pseudonymous posts. --corpus needs you to have already saved copies locally; it can't crawl any platform to assemble that folder for you.
  • Time-windowed drift analysis. --corpus pools its whole input as one all-time blob — it can't tell you whether the last three months of posts are trending worse than older ones, only the aggregate. Period-bucketing (by file mtime or a frontmatter date) would need real design work, not attempted here.

Pairing

  • privacy-threat-oracle — --emit-findings closes the loop that used to be a "designed to fit together, not actually wired up" note here: it emits a {leak_class: "stylometric", ...} Finding in scan_diff.py's own JSON shape, so it pipes straight into --from-linter-json exactly like a privacy-linter scan does:
    python ${CLAUDE_PLUGIN_ROOT}/scripts/fingerprint.py --file draft.md --reference ~/vault/ --emit-findings | \
      python ../privacy-threat-oracle/scripts/oracle.py \
        --source-compartment personal --target-compartment public_professional \
        --target-exposure public_internet --from-linter-json -
    One thing worth knowing about that bridge: the oracle's content_classes_from_linter_ json only reads each finding's leak_class, not its severity — so a "low" and a "high" severity stylometric finding both resolve to the same fixed stylometric → medium sensitivity tier on the oracle side. This tool's own severity field is real (it drives the ranking if you're reading --emit-findings output directly) but doesn't currently change the oracle's tier once it crosses.
  • privacy-linter — a distinct, deferred leak class in that skill's own taxonomy; this skill is what actually builds the flagging half of it, scoped to a fingerprint rather than the full inference-cue class privacy-linter also defers.

Output discipline

  • Never present a similarity score as a verdict ("this is/isn't the same author") — it's a number for you to judge, the same way agent-eval's calibration step never lets a judge score stand in for ground truth without a human spot-check.
  • Always surface the short-input noise warning when word count is under 100 — a precise-looking percentage on a tiny sample is exactly the kind of false confidence this repo's other tools (agent-eval's sample-size honesty rule) already guard against.
  • Report the passive-voice rate as a heuristic every time it's shown, never bare — the same "don't let a degraded/approximate check look like a precise one" discipline privacy-linter already applies to its Pillow-unavailable metadata fallback.
  • Never print top_phrases' verbatim text by default, in either the human report or --json — a tool meant to help you avoid becoming identifiable shouldn't itself leak identifying substrings in its own output. --show-phrase-text opts in explicitly, for your own terminal only.
  • Never write the fingerprint feature vector or top_phrases to --log-dir — a durable trend log should hold the minimum needed to answer "is this getting more or less distinctive over time," not accumulate a second historical copy of the actual writing signal, the same "findings/score only, never the content" discipline privacy-linter's own --log-dir established first.
  • A small aggregation gap is real, useful information, not a null result — report it in the same reassuring-but-not-dismissive tone as a low similarity score, not as "nothing to see here." A cluster-based check finding nothing beyond what a single document already showed doesn't rule out gap #5's ceiling: a richer adversary with a different feature set entirely is still out of scope for this or any deterministic check.

Files

PathWhat it is
scripts/fingerprint.pyThe fingerprint computation + comparison + CLI
scripts/test_fingerprint.pyUnit + CLI test suite (stdlib unittest)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.