Agent skill

Canvas Humanizer

by X-isdoingreat in X-isdoingreat/canvas-pilot

Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that…

AGPL-3.0Auto-check: notesWriting & Content

Install Canvas Humanizer

skills CLI
$ npx skills add X-isdoingreat/canvas-pilot --skill canvas-humanizer -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install X-isdoingreat/canvas-pilot canvas-humanizer --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/X-isdoingreat/canvas-pilot.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/canvas-humanizer .claude/skills/canvas-humanizer && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
canvas-humanizer
GitHub stars
125
Token cost
~10k tokens
SKILL.md length
3,777 words
Files
1
Skills in repo
32
Repo updated
First seen
Licence
AGPL-3.0

At a glance

Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that…

  • Works in 5 steps: LLM-as-judge is self-referential. v1… → ±5% word-count clamp was a structural… → Banned-words list is cosmetic. AI… → …
  • Tasks that involve Humanizing AI text
  • SKILL.md covers §1 — Identity & contract…, §2 — Why v2 (read this once,…, §3 — Pipeline overview and §4 — Preservation locks, plus 5 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Canvas Humanizer is an agent skill from X-isdoingreat/canvas-pilot. Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that maximises deterministic structural divergence from the LLM-shaped original while preserving meaning and voice register. v2 abandons v1's LLM-as-judge convergence loop (self-referential, can't beat real detectors), replacing it with (a) sentence-level segmentation, (b) K-candidate generation via round-trip translation, (c)…

Its SKILL.md is about 10k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Writing & Content, covering Humanizing AI text, Translation and LLM evaluation. It works with Microsoft Word. The repository describes itself as: Local-first Canvas LMS AI agent that learns each course's recurring assignment workflow and reuses it through scan - approval - execute with student review. The licence is AGPL-3.0.

When your agent uses it

  • Tasks that involve Humanizing AI text
  • Tasks that involve Translation
  • Tasks that involve LLM evaluation

Example prompts

  • “s LLM-as-judge convergence loop (self-referential, can”
  • “Use the canvas-humanizer skill to reduce AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation…”
  • “/canvas-humanizer”

Requirements

  • Python 3
  • Pre-approved tools (allowed-tools): Bash, Read, Write, Edit, Grep, Agent

Workflow steps

5 steps, taken from the first numbered list in SKILL.md.

  1. LLM-as-judge is self-referential. v1 used Claude/GPT to score Claude/GPT-generated text. Audit pass ≠ Turnitin pass.
  2. ±5% word-count clamp was a structural straitjacket. Real humanization needs sentence restructuring, not lexical substitution.
  3. Banned-words list is cosmetic. AI fingerprint is in token-probability distributions, not in 30 high-frequency words.
  4. 3 sequential passes (vocab → sentence → texture) all happen inside the LLM-English distribution. Each pass stays in the mode it's trying…
  5. No adversarial feedback loop. v1 couldn't query the actual detector, so it had no idea whether it was getting closer to or further from…

What it can do on your machine

Read from SKILL.md and the folder at commit 6b79d5b. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves these tools, so the agent can use them without asking each time:

    • Bash
    • Read
    • Write
    • Edit
    • Grep
    • Agent

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are python, yaml and json).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Canvas Humanizer loads about 10k tokens when it runs. Until then it costs about 179 tokens; SKILL.md has 3,777 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~179
When it runs · the whole SKILL.md, loaded when a task matches
~10k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NotePre-approves every shell command (allowed-tools: Bash)SKILL.md
    allowed-tools: Bash, Read, Write, Edit, Grep, Agent

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from X-isdoingreat/canvas-pilot at commit 6b79d5b, republished under its AGPL-3.0 licence (© X-isdoingreat). 3,777 words, ~10,008 tokens.

Download SKILL.mdSave it as .claude/skills/canvas-humanizer/SKILL.md (or your agent's skills folder).
name
canvas-humanizer
description
Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that maximises deterministic structural divergence from the LLM-shaped original while preserving meaning and voice register. v2 abandons v1's LLM-as-judge convergence loop (self-referential, can't beat real detectors), replacing it with (a) sentence-level segmentation, (b) K-candidate generation via round-trip translation, (c) Levenshtein-based divergence scoring, (d) optional pluggable real-detector adapter. Designed for unlimited-token / unlimited-iteration callers where wallclock matters more than spend.
allowed-tools
Bash, Read, Write, Edit, Grep, Agent

canvas-humanizer v2 — round-trip translation + deterministic divergence search

§1 — Identity & contract (caller-facing, identical to v1)

Inputs (parsed from caller's prose context line; pattern-matching is the same as v1):

ArgRequiredDefault / fallbackExample
draft_pathyes—C:\...\essay.docx (.docx or .md)
output_pathyes—C:\...\essay.humanized.docx
voice_registeryes—advanced-academic-english
student_identitynofalls back to voice_register valueadvanced-academic-english

If the caller's prose context omits student_identity, v2 silently uses voice_register as the identity. This restores backward compatibility with v1 callers that may have hardcoded only three args.

Outputs:

  • output_path — humanized draft
  • <output_dir>/humanizer_log.json — per-segment trace (candidates, scores, language paths)

Status return:

  • ok — every segment cleared the meaning+voice gate AND structural divergence > 0.30
  • partial — at least one segment had to fall back to original because no candidate passed meaning gate
  • error — input validation failure (missing file, missing arg, etc.)

Caller compatibility: input/output contract is identical to v1. canvas-essay §7.5 invocation works unchanged.


§2 — Why v2 (read this once, then never again)

v1 failed in production with a Turnitin AI-detection result of 75% AI despite v1's internal 3D audit reporting status: ok. Five root causes (do not re-discuss in v2 SKILL.md — they are settled):

  1. LLM-as-judge is self-referential. v1 used Claude/GPT to score Claude/GPT-generated text. Audit pass ≠ Turnitin pass.
  2. ±5% word-count clamp was a structural straitjacket. Real humanization needs sentence restructuring, not lexical substitution.
  3. Banned-words list is cosmetic. AI fingerprint is in token-probability distributions, not in 30 high-frequency words.
  4. 3 sequential passes (vocab → sentence → texture) all happen inside the LLM-English distribution. Each pass stays in the mode it's trying to escape.
  5. No adversarial feedback loop. v1 couldn't query the actual detector, so it had no idea whether it was getting closer to or further from passing.

v2's response to each:

v1 failurev2 fix
LLM-as-judgeDeterministic Levenshtein-based divergence; no LLM scoring of LLM output
±5% clamp±20% per segment (relaxed); paragraph total allowed ±15% (vs v1's hard preserve)
Banned-wordsKept as a post-translation sanity check only, NOT as primary mechanism
In-distribution rewritingRound-trip translation through non-English intermediate forces re-encoding through a different token distribution
No adversarial signalPluggable detector adapter; supports manual mode where caller pastes external score and the skill iterates against it

§3 — Pipeline overview

Input docx/md
  ↓
[§4] Identify preservation locks   →  list of verbatim spans that NEVER round-trip
  ↓
[§5] Segment to sentences          →  flat list of segments, each marked humanizable/locked
  ↓
[§6] Round-trip K candidates       →  per humanizable segment, K=3 round-trips via different intermediate languages
  ↓
[§7] Score candidates              →  for each: (meaning_preserved × voice_intact × divergence) ; pick max
  ↓
[§8] Reassemble                    →  substitute locks back, glue segments into paragraphs preserving doc_index
  ↓
[§9] (Optional) Detector loop      →  if overlay's detector_api != none, call adapter; iterate from §6 with deeper perturbation if score > target
  ↓
[§10] Write artifacts              →  output_path + humanizer_log.json

§4 — Preservation locks

Some spans MUST survive every round-trip byte-for-byte:

  • Direct quotes — anything between "..." (or “...”) that matches the source text
  • Named entities — author names, place names, proper nouns the caller supplies
  • Numbers + dates — 38 percent, 1982, 22 April 2026, two to three percent
  • Caller-supplied hard locks — overlay field hard_locks (per-essay list)
§4a — Lock identification

Run a Python helper to extract candidate locks:

python
import re, json
from pathlib import Path

def extract_locks(text: str, hard_locks: list[str]) -> list[tuple[int, int, str]]:
    """Return list of (start_char, end_char, span_text) for spans that must not be touched.
    Order: leftmost first; non-overlapping (longer wins on conflict)."""
    locks = []
    # 1. Quoted spans (straight or curly)
    for m in re.finditer(r'"([^"]+)"|"([^"]+)"', text):
        s, e = m.span()
        locks.append((s, e, text[s:e]))
    # 2. Years 1900-2099
    for m in re.finditer(r'\b(19|20)\d{2}\b', text):
        locks.append((m.start(), m.end(), m.group(0)))
    # 3. Percent literals (38 percent, 2 to 3 percent, etc.)
    for m in re.finditer(r'\b\d+(?:\s*(?:to|and)\s*\d+)?\s+percent\b', text, re.I):
        locks.append((m.start(), m.end(), m.group(0)))
    # 4. Dates like "22 April 2026" / "April 22, 2026" / "May 22"
    for m in re.finditer(r'\b\d{1,2}\s+(January|February|March|April|May|June|July|August|September|October|November|December)(?:\s+\d{4})?\b|\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{1,2}(?:,\s*\d{4})?\b', text):
        locks.append((m.start(), m.end(), m.group(0)))
    # 5. Age-cohort terms (Gen Z, Gen X, millennials, etc.) — Claude routinely
    #    translates "Gen Z" → "Generation Z" / "Z 世代" without the agent NE pass
    #    catching it. Regex coverage prevents this.
    for m in re.finditer(r'\b(Gen\s*(?:Z|X|Y|Alpha)|Generation\s*(?:Z|X|Y|Alpha)|Millennials?|Boomers?|Gen\s*Z-ers|Gen-Zers)\b', text, re.I):
        locks.append((m.start(), m.end(), m.group(0)))
    # 6. Caller-supplied hard locks (search literally)
    for span in hard_locks:
        for m in re.finditer(re.escape(span), text):
            locks.append((m.start(), m.end(), m.group(0)))
    # Deduplicate + resolve overlaps: sort by start asc, length desc; greedy keep
    locks.sort(key=lambda t: (t[0], -(t[1]-t[0])))
    out, last_end = [], -1
    for s, e, sp in locks:
        if s >= last_end:
            out.append((s, e, sp))
            last_end = e
    return out
§4b — Named-entity locks via Agent

After regex extraction, spawn ONE agent to find named-entity locks the regex missed:

Task: list every proper noun OR domain-specific term in this text that should be preserved verbatim across translation. Return a JSON list of exact strings as they appear in the text. Include:

  • Person names (e.g., [Author A], [Author B])
  • Organizations (e.g., Government Accountability Office, Federal Trade Commission)
  • Publications (e.g., [Publication], Times Opinion)
  • Geographic locations (e.g., Shanghai, Oregon)
  • Brand / product / platform names (e.g., Etsy, YouTube, Canvas)
  • Age-cohort terms (e.g., Gen Z, Gen X, Millennials, Boomers) — these are commonly translated/expanded into Generation Z / Z 世代 by translators and must be locked
  • Technical / discipline-specific terms (e.g., Current Population Survey, noncompete agreements)

Do NOT include common nouns, generic abstractions, or verbs. Do NOT paraphrase entries. Return at most 25 items as a JSON array of strings.

Text:

{text}

Add the returned strings to hard_locks before §4a's pass.

§4c — Mask placeholders

Replace each lock span with [LOCK_N] where N is the lock index (0-based). The masked text passes to translation; the lock list passes alongside. After round-trip, substitute [LOCK_N] → exact original bytes.

§4d — Order of operations: split THEN mask, not mask THEN split

Important (per 2026-05-22 full-essay run): do the §5 sentence splitting on the unmasked text first, then mask each sentence with paragraph-global lock indices. The reverse order — mask-then-split — fails when a lock span absorbs sentence-ending punctuation (e.g. a quote ending in ." followed by This finding...), because the splitter can no longer find the sentence boundary inside [LOCK_N]. Always split first, mask second, but keep lock indices consistent across the whole paragraph (don't reuse index 0 in two different sentences).


§5 — Sentence segmentation

Default level: sentence. Overlay can override to clause or paragraph.

§5a — Sentence tokenizer

Use Python regex (avoid pulling NLTK/spaCy — zero-dependency policy):

python
def split_sentences(text: str) -> list[str]:
    """Conservative sentence splitter: handles common abbreviations, decimals, ellipses."""
    # Protect common abbreviations + numeric decimals + ellipses
    protected = text
    abbrevs = ['Mr.', 'Mrs.', 'Ms.', 'Dr.', 'Prof.', 'Inc.', 'Ltd.', 'St.', 'Jr.', 'Sr.', 'vs.', 'e.g.', 'i.e.', 'etc.', 'Co.', 'U.S.', 'U.K.']
    for ab in abbrevs:
        protected = protected.replace(ab, ab.replace('.', '\x00'))
    # Protect decimals (3.14, 1,000.50). Use lambda — a raw-string replacement r'\1\x00\2'
    # would inject the LITERAL 4-char sequence \x00, not a NUL byte, because Python raw
    # strings don't interpret \x escapes. Lambda lets us insert a real NUL.
    protected = re.sub(r'(\d)\.(\d)', lambda m: m.group(1) + '\x00' + m.group(2), protected)
    # Protect ellipses
    protected = protected.replace('...', '\x00\x00\x00')
    # Split on .!? followed by whitespace+capital, OR end-of-string
    parts = re.split(r'(?<=[.!?])\s+(?=[A-Z"\'])', protected)
    # Unprotect
    parts = [p.replace('\x00\x00\x00', '...').replace('\x00', '.') for p in parts]
    return [p.strip() for p in parts if p.strip()]
§5b — Per-segment metadata

For each sentence:

python
{
  "seg_id": "S{n}",
  "doc_paragraph_index": <int>,   # which paragraph in docx
  "intra_para_index": <int>,      # 0-based position within paragraph
  "original": "<text>",
  "word_count": <int>,
  "contains_lock": <bool>,        # does any LOCK_N placeholder appear?
  "humanizable": <bool>,          # not entirely a lock, not too short (<5 words)
}

Skip segments where humanizable == False: name-block lines, title, short interjections, sentences that are ≥80% lock content.


§6 — Candidate generation (K candidates via TWO parallel strategies)

For each humanizable segment, generate K candidates (default K=6) via two parallel strategies:

  • Strategy R (round-trip translation) — K_R candidates, each a round-trip through a different intermediate language. Perturbs prose via cross-lingual re-encoding. Default K_R = 3.
  • Strategy P (structured English paraphrase) — K_P candidates, each applying a different deterministic structural transformation in English. Perturbs prose via explicit syntactic surgery. Default K_P = 3.

K = K_R + K_P. Both strategies feed the same downstream scoring (§7). Selection is "max divergence subject to meaning + voice preservation" across the unified pool, regardless of which strategy produced the candidate.

Honest framing (per §2's design review): round-trip via Claude itself is perturbed diversification, not true distribution escape — the intermediate Chinese/Japanese/German output is still rendered by the same model. Strategy R + Strategy P together act as diverse paraphrase generators; the deterministic Levenshtein selection in §7 is what gives the pool detector-relevant value.

Empirical observation (2026-05-22 full-essay run on 23 humanizable sentences): Strategy R won 0 of 23 times when round-trip is executed by Claude. Strategy P won 23 of 23 — clause_reorder dominated (13), with voice_flip (5) and nominalize_flip (5) splitting the rest. The honest read: in-Claude round-trip produces candidates that cluster too close to the original (divergence range 0.05-0.30), while structured English paraphrase produces explicit syntactic perturbation that consistently scores higher on Levenshtein. If real distribution escape is the target (e.g. trying to take Turnitin score from 75% to <20%), the round-trip hop needs an external engine (DeepL / Google Translate API) — the K_R bucket then becomes load-bearing. Until then, the K_R candidates are essentially "free additional variety" with low win rate; K_P is doing the actual humanization work.

Dispatch mechanism (clarified after 2026-05-22 smoke test):

  • Preferred (parallel): when overlay has parallel_candidate_dispatch: true (the default), the orchestrator spawns the K candidates for one segment as K parallel Agent tool calls in a single message. This collapses ~5x wallclock and is the production-intended mode when canvas-essay §7.5 invokes the skill via Skill tool from a Claude Code session.
  • Acceptable (inline): when the skill is being executed by a sub-agent that prefers in-context generation (e.g., a wrapping general-purpose agent doing a smoke test), the same prompts in §6b and §6d can be executed as direct LLM completions rather than spawned Agent calls. Outputs are identical; only parallelism is lost.

Both modes follow the same §6b/§6d prompt templates and the same §7 selection rule. Choose based on whether the executing context has the Agent tool available and a clear parallelism win.

§6a — Strategy R: round-trip translation

Language pool (overlay-overridable as languages_to_round_trip_through):

yaml
- zh         # Chinese — strong typological distance from English
- ja         # Japanese
- de         # German — close to English syntactically; gentle round-trip
- es         # Spanish — Latin family
- fr         # French
- ko         # Korean

For each candidate k_r in [0, K_R), pick language pool[k_r % len(pool)]. K_R=3 → uses zh, ja, de.

§6b — Translation prompts

Hop 1: EN → intermediate language

Spawn 1 Agent per candidate. Prompt:

Task: Translate the following English sentence to {language}. Constraints:

  • Preserve every [LOCK_N] placeholder byte-for-byte. Do NOT transliterate, reorder, translate, or convert to characters in another script. The literal sequence [LOCK_N] (left bracket, the letters LOCK, underscore, digit, right bracket) must appear in your output exactly as in the input. Treat the [LOCK_N] token as if it were a code variable, not a translatable phrase.
  • Preserve meaning faithfully.
  • Do not add commentary, parentheticals, or footnotes.
  • Output ONLY the translation, no preamble.

Sentence:

{masked_segment}

Hop 2: intermediate → EN (with register restoration)

Spawn 1 Agent per candidate. Prompt:

Task: Translate the following {language} sentence to English, in the register of {voice_register}.

Constraints:

  • Preserve every [LOCK_N] placeholder byte-for-byte. Do NOT transliterate, reorder, translate, or convert to characters in another script. The literal sequence [LOCK_N] (left bracket, the letters LOCK, underscore, digit, right bracket) must appear in your output exactly as in the input. Treat the [LOCK_N] token as if it were a code variable, not a translatable phrase.
  • Use natural English; do not produce a literal word-by-word back-translation.
  • Voice register guidance for {voice_register}:
    • advanced-academic-english: long complex sentences, formal vocabulary, no contractions, no slang
    • b1-b2-international-student: simple sentence structures, occasional missing articles, present-tense leaning
    • (others fall back to native fluent English)
  • Do not introduce banned words from this list: {banned_list}
  • Output ONLY the English translation, no preamble, no quotation marks around the output.

Sentence:

{intermediate_text}
§6c — Substitute locks back

After hop 2, [LOCK_N] placeholders in the candidate get replaced with the exact original bytes from §4. If a candidate has lost a lock placeholder (translation broke it), discard that candidate and retry once with the EN → intermediate hop. If still broken after one retry, mark candidate as lock_lost and exclude from selection.

Per-segment lock check (per 2026-05-22 full-essay run): when checking "did this candidate lose any locks?", check only against the locks that appeared in this segment's masked original, not against the full paragraph's lock list. Paragraph-wide checks falsely fail candidates whose segment didn't contain a lock that other segments did. Compute required_locks_for_this_segment = {N for N in re.findall(r'\[LOCK_(\d+)\]', masked_segment_input)} and only verify those indices are present in the candidate output.

§6d — Strategy P: structured English paraphrase

For each candidate k_p in [0, K_P), apply one structural transformation to the original sentence. Each transformation is a deterministic LLM-prompted rewrite that targets a specific syntactic dimension.

Default transformation set (overlay-overridable as paraphrase_strategies):

IndexNameTransformation
0voice_flippassive ↔ active. If sentence is in active voice, rewrite as passive (and vice versa). Subject and object swap; verb morphology changes.
1nominalize_flipnominalization ↔ verb. Convert abstract-noun phrases ("the diagnosis", "the recognition that") into verbal clauses ("she diagnoses", "we recognize that"), or vice versa.
2clause_reordermove subordinate clause from sentence-final to sentence-initial position (or vice versa). E.g., "X, because Y." → "Because Y, X."
3merge_splitif sentence has two independent clauses, split into two sentences; if it has one clause, optionally merge with a borrowed connector from the surrounding context.
4lead_swapmove the most informative noun phrase from object/oblique position to subject position via voice or pivot.
5connector_swapreplace discourse connectors ("however", "yet", "by contrast", "instead", "rather") with structurally different alternatives, including dropping the connector and letting sequencing carry the contrast.

For K_P=3, default transformations used are voice_flip, nominalize_flip, clause_reorder. Overlay can list a different subset.

Per-candidate prompt (one Agent spawn per K_P transformation):

Task: Rewrite the following English sentence by applying the {transformation_name} transformation:

{transformation_description}

Constraints:

  • Preserve every [LOCK_N] placeholder byte-for-byte. Do NOT transliterate, reorder, translate, or convert to characters in another script. The literal sequence [LOCK_N] must appear in your output exactly as in the input.
  • Preserve meaning faithfully. If the transformation is genuinely impossible for this sentence (e.g., voice_flip on an intransitive sentence), output the sentence unchanged.
  • Voice register: {voice_register} — keep formal/informal register intact.
  • Do not introduce banned words from this list: {banned_list}
  • Output ONLY the rewritten sentence, no preamble, no quotation marks around the output, no explanation of what you did.

Sentence:

{masked_segment}

If the transformation prompt returns the unchanged input verbatim (transformation was impossible), mark candidate as paraphrase_inapplicable and exclude from §7 selection — but the candidate is not a failure, just a non-contribution to the pool.

Strategy P candidates do NOT round-trip through a non-English language. They are direct in-English rewrites. The candidate's value comes from the structural transformation being explicit (not "rewrite to be different"), producing reliably high Levenshtein divergence on the dimension targeted by the transformation.

§6e — Word-count gate per candidate (sliding tolerance)

Compute candidate_word_count / original_word_count. Tolerance scales by original sentence length — short sentences need wider bands because a single em-dash parenthetical or fronted adverbial clause adds 3-5 words that blow a tight band.

python
def word_count_tolerance(original_wc: int) -> tuple[float, float]:
    """Return (lower, upper) ratio band. Short sentences get wider bands."""
    if original_wc <= 15:
        return (0.70, 1.40)   # ±40% — covers clause_reorder parentheticals on short sentences
    elif original_wc <= 25:
        return (0.75, 1.30)   # ±30%
    else:
        return (0.80, 1.20)   # ±20% — original v2 default for longer segments

Outside band → discard candidate. Applies to both Strategy R and Strategy P candidates.

Rationale: smoke-test (2026-05-22 intro pass) found that on 25-word sentences, the highest-divergence candidates (clause_reorder, divergence 0.81 and 0.65) were disqualified by a fixed ±20% band, costing the pipeline its best perturbation. Sliding tolerance keeps the high-divergence candidates while still blocking truly runaway expansions.


§7 — Candidate scoring + selection (deterministic, no LLM judge)

For each surviving candidate, compute three scores:

§7a — meaning_preserved (LLM-judge, binary 0/1)

Spawn 1 Agent per (segment × candidate) pair. Prompt:

Task: Given the original sentence and a candidate rewording, return JSON {"meaning_preserved": true | false, "voice_register_intact": true | false, "rationale": "<one sentence>"}.

A meaning is preserved if the candidate makes the same claim about the same subject; minor surface differences (word order, synonyms) are fine. Voice register is {voice_register} — flag if candidate drifts to a noticeably different register (e.g. casual when advanced-academic expected).

Original:

{original}

Candidate:

{candidate}

Output strict JSON only.

This is the ONLY LLM-judge call in v2, and it's a binary semantic question (was meaning preserved?), not a graded AI-detection question. Self-reference risk is much lower because the judge isn't asked "does this look LLM-shaped?" — it's asked "is the meaning the same?".

§7b — voice_register_intact (same agent, same JSON response)

Returned by the §7a agent.

§7c — structural_divergence (deterministic)

Computed in Python — no LLM:

python
def levenshtein(a: str, b: str) -> int:
    """Standard DP edit distance, character-level."""
    if not a: return len(b)
    if not b: return len(a)
    prev = list(range(len(b)+1))
    for i, ca in enumerate(a, 1):
        cur = [i] + [0]*len(b)
        for j, cb in enumerate(b, 1):
            cost = 0 if ca == cb else 1
            cur[j] = min(cur[j-1]+1, prev[j]+1, prev[j-1]+cost)
        prev = cur
    return prev[-1]

def divergence(original: str, candidate: str) -> float:
    """0.0 = identical; 1.0 = totally different. Higher = more humanized."""
    if not original and not candidate: return 0.0
    L = max(len(original), len(candidate))
    return levenshtein(original, candidate) / L

divergence is in [0.0, 1.0]. We want it high (more humanized = more different from LLM-shaped original).

§7d — Final score + selection
python
def candidate_score(c) -> float:
    if not c["meaning_preserved"] or not c["voice_register_intact"]:
        return 0.0
    return c["divergence"]

Pick argmax. If max score == 0.0 (no candidate passed meaning gate), fall back to original sentence and mark segment as partial in the log.

Why this works: the selection rule is "biggest structural change subject to meaning preservation". v1 picked "passes LLM-judge audit". v2's selection signal is computable and adversarial-aware: Levenshtein distance is a property of the actual text, not an LLM's opinion of the text.


Show full SKILL.md (1,503 more words)Show less

§8 — Reassemble

python
# After all segments processed
paragraphs = {}  # doc_paragraph_index → list of (intra_index, final_text)
for seg in segments:
    text = seg["final_text"] if seg["humanizable"] else seg["original"]
    paragraphs.setdefault(seg["doc_paragraph_index"], []).append((seg["intra_para_index"], text))

# Sort within paragraph, join with single space
final_paragraphs = {}
for pi, segs in paragraphs.items():
    segs.sort(key=lambda t: t[0])
    final_paragraphs[pi] = " ".join(t for _, t in segs)
§8a — .docx output
python
from docx import Document
out = Document(draft_path)  # start from input to preserve styles, headers, tables, italics
for i, para in enumerate(out.paragraphs):
    if i in final_paragraphs and final_paragraphs[i] != para.text:
        # Replace text while preserving first run's formatting
        if para.runs:
            para.runs[0].text = final_paragraphs[i]
            for run in para.runs[1:]:
                run.text = ""
        else:
            para.add_run(final_paragraphs[i])
out.save(output_path)
§8b — .md output

Same array order; join with \n\n.

§8c — Paragraph-level word-count gate

After reassembly, for each humanized paragraph compute final_wc / original_wc. Must be in [0.85, 1.15] (paragraph-level ±15%). If outside band, the caller is at risk of breaching their hard-constraint band (e.g. 550-650 total). Log a paragraph-drift warning; do NOT fail the run (caller's verify is the authority).


§9 — Atomic single-pass; iteration is the caller's responsibility

v2 is atomic: one invocation = one humanization pass over the full draft. The skill runs §4-§8 once, writes the artifacts (§10), and returns. It does NOT internally loop against a detector score.

This is a deliberate design choice driven by two facts:

  1. Claude Code's Bash tool runs -NonInteractive — read from stdin within a Skill execution context will not block-and-wait for user paste. A "manual mode" loop inside the skill cannot poll the user for a Turnitin score between iterations.
  2. Iteration is a caller-level concern — canvas-essay §7.5 already owns the post-humanizer flow. If the caller wants to iterate based on an external detector score, the caller (a) reads humanizer_log.json, (b) presents the output to the human for testing, (c) decides whether to re-invoke the skill with deeper perturbation.
§9a — Metadata field for caller-side iteration logic

Overlay can declare detector_api and detector_target for the caller's convenience — but the skill does not consume these fields during execution:

yaml
detector_api: manual   # "gptzero" | "sapling" | "originality" | "manual" | "none" — INFORMATIONAL ONLY
detector_target: 30    # AI-probability target; caller decides when to re-invoke

These fields appear in humanizer_log.json.config so the caller can read them. Use them in caller-side glue code (canvas-essay §7.5 wrapper, or a manual orchestration loop) rather than expecting the skill to act on them.

§9b — Re-invocation pattern (caller cookbook)

When the caller wants a deeper humanization pass after seeing a high external-detector score:

  1. Caller writes a new overlay (or modifies inline) with progressively more aggressive settings:
    • K = 6 → 10 (more candidates per segment)
    • languages_to_round_trip_through rotated to languages not used in prior pass
    • paraphrase_strategies set to include more transformations
    • segment_level: sentence → clause (smaller units, more perturbation surface)
  2. Caller re-invokes canvas-humanizer with the new overlay. v2 runs another atomic pass.
  3. Caller re-checks external detector score, decides whether to stop.

This pattern moves the "feedback loop" out of the skill and into caller orchestration where stdin / API / human interaction are viable.


§10 — Write artifacts

output_path (humanized draft)

Written by §8.

humanizer_log.json
json
{
  "version": 2,
  "draft_path": "...",
  "output_path": "...",
  "voice_register": "...",
  "student_identity": "...",
  "config": {
    "K_total": 6,
    "K_roundtrip": 3,
    "K_paraphrase": 3,
    "languages_used": ["zh", "ja", "de"],
    "paraphrase_strategies_used": ["voice_flip", "nominalize_flip", "clause_reorder"],
    "segment_level": "sentence",
    "word_count_tolerance_per_segment": 0.20,
    "detector_api": "manual",
    "detector_target": 30
  },
  "total_paragraphs": <int>,
  "total_segments": <int>,
  "humanizable_segments": <int>,
  "locked_segments": <int>,
  "total_llm_calls": <int>,
  "avg_divergence": <float>,
  "fallback_count": <int>,
  "status": "ok" | "partial" | "error",
  "segments": [
    {
      "seg_id": "S1",
      "doc_paragraph_index": 5,
      "intra_para_index": 0,
      "original_word_count": 47,
      "final_word_count": 51,
      "winning_strategy": "roundtrip" | "paraphrase",
      "winning_method": "zh" | "voice_flip" | "...",
      "candidates_considered": 6,
      "candidates_passed_meaning_gate": 4,
      "candidates_lost_lock": 1,
      "candidates_paraphrase_inapplicable": 1,
      "final_divergence": 0.62,
      "status": "ok",
      "fallback_to_original": false
    }
  ]
}
§10a — Atomic writes

Write to .tmp first, then os.replace. Same as v1.


§11 — Token budget & telemetry

v2 has no hard cap on token spend per the caller spec. But it logs every Agent call to enable caller-side monitoring:

python
log_entry = {
  "seg_id": seg_id,
  "phase": "ne_extract" | "translate_to_intermediate" | "translate_back" | "paraphrase" | "meaning_check",
  "strategy": "roundtrip" | "paraphrase",
  "method": lang_code | transformation_name,
  "input_tokens_est": int(len(prompt) / 4),
  "output_tokens_est": int(len(response) / 4),
}

These rows accumulate in humanizer_log.json under agent_calls. Caller can sum / monitor without an enforced cap.

Realistic wallclock expectation (per Subagent B review): for a 600-word essay with ~40 humanizable segments × K=6 candidates × (1-2 agent calls per candidate) + 1 meaning-check per candidate = roughly 300-500 sequential Agent calls per atomic pass. At 1-3 seconds per Agent call, total wallclock is 5-15 minutes. Callers should treat this as a non-interactive batch step, not a live workflow turn.

Soft warning logged at 5M token estimate (informational only; no abort).

§11a — Parallel candidate generation (optional optimization)

If runtime is unacceptable, candidates within the SAME segment can be spawned in parallel (single message, multiple Agent tool calls). The K_R round-trip candidates' Hop 1 (EN→intermediate) plus the K_P paraphrase candidates can all be issued as one batched parallel dispatch (K_R + K_P = K agents fired simultaneously). Hop 2 (intermediate→EN) for the K_R candidates can also be parallel. Meaning-gate checks can be parallel. This collapses ~5x wallclock factor at the cost of more bursty token usage.

Per-segment iteration is still sequential (segment N+1 doesn't start until segment N is selected), since the win for parallelism is within-segment and segment results are mostly independent.


§12 — Return summary

Skill returns to caller via plain prose:

canvas-humanizer v2 complete. status: <ok|partial|error>. <humanizable_segments> humanizable segments across <total_paragraphs> paragraphs. avg structural divergence: <float>. <fallback_count> segments fell back to original (meaning gate failed). Strategy R won <r_win_count>, Strategy P won <p_win_count>. Log: <output_dir>/humanizer_log.json. Output: <output_path>.

Caller decides whether to re-invoke for another atomic pass based on external detector score (see §9b).


§13 — Overlay (optional, with inline fallback)

Read _private/canvas-humanizer-app.md if it exists. Otherwise use the inline defaults:

yaml
# Candidate pool — Strategy R (round-trip) + Strategy P (paraphrase)
K_roundtrip: 3                       # number of round-trip candidates per segment
K_paraphrase: 3                      # number of structured-paraphrase candidates per segment
                                     # K_total = K_roundtrip + K_paraphrase

# Strategy R config
languages_to_round_trip_through: [zh, ja, de, es, fr, ko]

# Strategy P config
paraphrase_strategies: [voice_flip, nominalize_flip, clause_reorder, merge_split, lead_swap, connector_swap]
# Order matters: K_paraphrase candidates use paraphrase_strategies[0:K_paraphrase]

# Common
segment_level: sentence              # "sentence" | "clause" | "paragraph"
word_count_tolerance_per_segment: 0.20
word_count_tolerance_per_paragraph: 0.15
parallel_candidate_dispatch: true    # see §11a; if true, spawn K candidates in parallel within a segment

# Detector metadata — INFORMATIONAL ONLY, skill does not loop on these
detector_api: manual                 # "none" | "manual" | "gptzero" | "sapling" | "originality"
detector_target: 30

meaning_gate_threshold: true_required  # candidates with meaning_preserved=false are discarded

banned_words_post_translation:        # kept from v1, but only as POST-translation sanity check
  - delve
  - leverage
  - tapestry
  - multifaceted
  - plethora
  - paradigm
  - holistic
  - Moreover
  - Furthermore
  - In conclusion
  - It's important to note
  # ... (full v1 list — applied AFTER round-trip; if banned word reappears, retry round-trip once)

hard_locks: []                        # caller can supply additional verbatim spans

Overlay deep-merges over these. Missing fields stay at default.


§14 — Worked example (concrete numbers for one segment)

Original segment (from a real essay):

"She cites [Author B] and his colleagues, who analyzed Current Population Survey data from 1982 through 2023 and found that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"

§4 lock identification:

  • LOCK_0: [Author B]
  • LOCK_1: Current Population Survey
  • LOCK_2: 1982
  • LOCK_3: 2023
  • LOCK_4: "employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s"

Masked segment:

"She cites [LOCK_0] and his colleagues, who analyzed [LOCK_1] data from [LOCK_2] through [LOCK_3] and found that [LOCK_4]."

§6 candidate pool K=6 (K_R=3 round-trip + K_P=3 paraphrase):

Strategy R candidates (round-trip):

kStrategyMethodRound-tripped result (after lock substitution)
0Rzh"She draws on the work of [Author B] and his colleagues, who studied Current Population Survey data spanning 1982 to 2023 and concluded that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"
1Rja"She references [Author B] and his collaborators, whose analysis of Current Population Survey data covering the span from 1982 to 2023 yielded the finding that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"
2Rde"She cites [Author B] and his colleagues, who analyzed Current Population Survey data running from 1982 to 2023 and established that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"

Strategy P candidates (structured paraphrase):

kStrategyMethodParaphrased result
3Pvoice_flip"[Author B] and his colleagues are cited for an analysis of Current Population Survey data from 1982 through 2023, which found that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"
4Pnominalize_flip"She cites the analysis by [Author B] and his colleagues of Current Population Survey data covering 1982 through 2023, finding that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"
5Pclause_reorder"Analyzing Current Population Survey data from 1982 through 2023, [Author B] and his colleagues — whom she cites — found that 'employed workers today are about half as likely to receive a better-paying outside offer as they were in the 1980s.'"

§7 scoring:

kstrategymeaningvoicedivergencescore
0R-zhtruetrue0.410.41
1R-jatruetrue0.550.55
2R-detruetrue0.180.18
3P-voice_fliptruetrue0.490.49
4P-nominalizetruetrue0.330.33
5P-clause_reordertruetrue0.610.61 (max)

Pick k=5 (clause_reorder, Strategy P). Moving the analytical clause to sentence-initial position with the citation as a parenthetical dash insertion produced the most structural change while preserving meaning + voice + all 5 locks intact.

This is the per-segment unit. Multiply by N segments × K=6 candidates × (translation hops + meaning check) = the token spend per atomic pass. With ~40 segments in a 600-word essay that runs ~300-500 Agent calls; wallclock 5-15 minutes sequential or ~1-3 minutes with parallel candidate dispatch (§11a).


§15 — What v2 does NOT do (carry-overs from v1's MUST NOT)

  • Do not re-choose voice register. Caller supplies it; v2 preserves it.
  • Do not change paragraph count. Segments reassemble within their source paragraphs.
  • Do not humanize quote contents or named entities. Locks are immutable.
  • Do not audit spec compliance / plagiarism / argument quality. That's the caller's audit (canvas-essay §Y).
  • Do not submit anything to Canvas. Pure file I/O.
  • Do not trust meaning_preserved=true if the candidate has lost a lock placeholder. Lock loss → discard.
  • Do not fall through to original silently when meaning gate fails. Mark fallback_to_original: true so the caller can surface for human review.

§16 — Differences from v1 at a glance (cheat sheet)

Dimensionv1v2
Segment levelparagraphsentence
Per-segment passes3 sequential (vocab, sentence, texture)K=6 candidates: 3 round-trip + 3 structured paraphrase
Cross-distribution claimno (all LLM rewriting in English)partial — round-trip via Claude is perturbed diversification, not true distribution escape; structured paraphrase adds explicit syntactic perturbation
ScoringLLM-judge 3D (burstiness/perplexity/vocab)deterministic Levenshtein + binary meaning gate
Convergence signalLLM-judge threshold (self-referential)structural divergence (computable, adversarial-aware)
Internal iteration4 passes per paragraph (capped)none — atomic single pass; caller iterates via re-invocation if needed
Word-count tolerance±5% per paragraph±20% per segment, ±15% per paragraph
Banned-words roleprimary mechanismpost-translation sanity check only
Detector adapternonemetadata-only fields in overlay; caller acts on them (skill does not)
Languagesn/azh, ja, de, es, fr, ko (overlay-configurable)
Paraphrase transformationsn/avoice_flip, nominalize_flip, clause_reorder, merge_split, lead_swap, connector_swap (overlay-configurable)
student_identity argrequiredoptional (falls back to voice_register)
Wallclock per pass~2-5 min5-15 min (300-500 sequential Agent calls; parallel mode collapses ~5x)

End of v2 SKILL.md.

© X-isdoingreat, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/canvas-humanizer of X-isdoingreat/canvas-pilot.

Open the folder on GitHubat commit 6b79d5b

Compare with similar skills

Canvas Humanizer next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Canvas Humanizer compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Canvas Humanizer this skillX-isdoingreat/canvas-pilot125—~10kAutomated safety check: NotesAGPL-3.0
Persian Writingali2000hos/persian-writing368—~4.4kAutomated safety check: WarnMIT
Aigc Detectorfree-revalution/AIGC-Detector-Pro142—~2.8kAutomated safety check: PassMIT
Sloptrimseyedehsanhadi/sloptrim220—~5.2kAutomated safety check: NotesApache-2.0
Humanized Chinese Writing PolisherEthanYoQ/agent-xiaohongshu-workbench154—~1.1kAutomated safety check: PassMIT
Humanizer Zhai-zixun/humanizer-zh179—~1.2kAutomated safety check: PassMIT

Similar skills

  • Persian Writing

    ali2000hos/persian-writing

    Write natural, human-sounding Persian (Farsi) and build correct right-to-left Persian deliverables.

    368 GitHub stars~4.4k tokensUpdated 6 days ago
    Documents & OfficeAuto-check: warnings
  • Aigc Detector

    free-revalution/AIGC-Detector-Pro

    Academic paper AI content detection, rewriting, and thesis writing assistant.

    142 GitHub stars~2.8k tokensUpdated 4 mo ago
    EducationAuto-check passed
  • Sloptrim

    seyedehsanhadi/sloptrim

    A skill your agent uses when the user wants to humanize text, trim slop, de-AI or de-slop writing, remove AI tells, fix robotic or ChatGPT-sounding prose, or make writing sound human and natural.

    220 GitHub stars~5.2k tokensUpdated 5 days ago
    Writing & ContentAuto-check: notes
  • Humanized Chinese Writing Polisher

    EthanYoQ/agent-xiaohongshu-workbench

    A skill your agent uses when a user asks to polish, rewrite, humanize, de-AI, localize, or oralize Chinese writing, including short-video scripts, articles, social posts, speeches, newsletters, and…

    154 GitHub stars~1.1k tokensUpdated 1 mo ago
    Writing & ContentAuto-check passed
  • Humanizer Zh

    ai-zixun/humanizer-zh

    Remove signs of AI-generated, translated, or overly mechanical Chinese prose.

    179 GitHub stars~1.2k tokensUpdated 4 mo ago
    Writing & ContentAuto-check passed
  • Academic Humanizer

    Yila-AI/awesome-research-skills

    A skill your agent uses when researchers ask to remove generic, templated, or AI-like patterns from Chinese or English academic prose, make an AI-assisted draft sound more like the author's own…

    133 GitHub stars~1.7k tokensUpdated 1 mo ago
    Writing & ContentAuto-check passed

More from X-isdoingreat/canvas-pilot

All 32 skills in this repo
  • Daily Work Tweet

    X-isdoingreat/canvas-pilot

    A skill your agent uses when verified work from today or another day should become an X/Twitter post, build-in-public update, ship log, or bilingual draft.

    125 GitHub stars~1.6k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Awkward Syntax

    X-isdoingreat/canvas-pilot

    A skill your agent uses when a short local academic draft needs role-aware syntax diversification while preserving meaning, locks, source grounding, rubric-critical openings, and document structure.

    125 GitHub stars~2k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Cron

    X-isdoingreat/canvas-pilot

    A skill your agent uses when managing Canvas Pilot schedules: install, inspect, pause, change, delete, or safely test scheduled scans and runs.

    125 GitHub stars~2.1k tokensUpdated 2 mo ago
    Auto-check: notes
  • Canvas Essay

    X-isdoingreat/canvas-pilot

    A skill your agent uses for an approved long academic-writing assignment routed by canvas-execute after the deterministic writing router selects essay.

    125 GitHub stars~2.2k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Generic

    X-isdoingreat/canvas-pilot

    A skill your agent uses for an approved Canvas assignment that no specialized course skill can handle.

    125 GitHub stars~2.4k tokensUpdated 2 mo ago
    Auto-check passed
  • Canvas Humanizer

    X-isdoingreat/canvas-pilot

    A skill your agent uses when a local academic draft needs a meaning-preserving humanizing pass with less uniform syntax while retaining rubric, source, lock, voice, and length constraints.

    125 GitHub stars~1.8k tokensUpdated 2 mo ago
    Auto-check passed

Works with

Questions about Canvas Humanizer

What does Canvas Humanizer do?

Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that…. Canvas Humanizer is an agent skill from X-isdoingreat/canvas-pilot. Reduces AI-detection signals in drafted text by routing every non-locked sentence through round-trip translation (English → intermediate language → English) and selecting the candidate that maximises deterministic structural divergence from the LLM-shaped original while preserving meaning and voice register.

When should I use Canvas Humanizer?

Canvas Humanizer fits situations like: tasks that involve Humanizing AI text; tasks that involve Translation; tasks that involve LLM evaluation.

How do I install Canvas Humanizer in Claude Code?

Run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-humanizer -a claude-code`. Or copy the skill folder (.claude/skills/canvas-humanizer in X-isdoingreat/canvas-pilot) into .claude/skills/canvas-humanizer in your project. Claude Code loads it when a task matches its description.

How do I install Canvas Humanizer in Codex?

Run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-humanizer -a codex`. Or copy the skill folder (.claude/skills/canvas-humanizer in X-isdoingreat/canvas-pilot) into .agents/skills/canvas-humanizer in your project. Codex loads it when a task matches its description.

Can I use Canvas Humanizer in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add X-isdoingreat/canvas-pilot --skill canvas-humanizer -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/canvas-humanizer, .gemini/skills/canvas-humanizer, .github/skills/canvas-humanizer and .opencode/skills/canvas-humanizer in your project.

What does Canvas Humanizer need to run?

SKILL.md names no scripts, command-line tools or credentials: Canvas Humanizer is instructions for the agent only. Our summary lists: Python 3. Its frontmatter pre-approves these tools: Bash, Read, Write, Edit, Grep, Agent.

Does Canvas Humanizer access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Canvas Humanizer safe to install?

Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Canvas Humanizer use?

Canvas Humanizer is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Canvas Humanizer use?

About 10k tokens (SKILL.md is roughly 40k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Canvas Humanizer?

Skills that share tags, products or a category with Canvas Humanizer: Persian Writing (ali2000hos/persian-writing, 368 stars), Aigc Detector (free-revalution/AIGC-Detector-Pro, 142 stars), Sloptrim (seyedehsanhadi/sloptrim, 220 stars) and Humanized Chinese Writing Polisher (EthanYoQ/agent-xiaohongshu-workbench, 154 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Canvas Humanizer?

X-isdoingreat (a GitHub user) maintains it in X-isdoingreat/canvas-pilot, which has 125 GitHub stars. The repository holds 32 skills in this directory. The repository was last updated on July 31, 2026.

Source: X-isdoingreat/canvas-pilot on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.