GlyphGuard provides an interface for inspecting documents, pasting text, inspecting suspicious Unicode and AI artefacts in real time, tuning what gets flagged, and exporting the full report when needed. It works locally, so you can review what was found and decide what matters without sending your text anywhere.
GlyphGuard is a small local inspection tool for text and documents. It shows suspicious Unicode, hidden characters, unusual punctuation, private-use markers, leaked citation plumbing, raw tool metadata and repeated generated-writing patterns. It does not prove authorship and it does not send the text to an external detector. It gives you a structured set of things to check.
Some problems are easy to see, such as a paragraph that is full of generic generated prose or a copied tool marker left in the final text. Other problems are much easier to miss: zero-width characters inside words, non-breaking spaces that break search, direction controls that change display order, private-use citation markers, malformed metadata, or odd Unicode that causes problems in diffs, moderation tools, filenames and parsers.
GlyphGuard is useful for:
- checking pasted text before publishing it;
- reviewing documents, wiki edits, Markdown, logs or support text without using an online detector;
- finding hidden or unusual Unicode that is not obvious in normal editors;
- spotting leaked AI/tool artefacts, citation placeholders and raw metadata;
- comparing weak writing-style hints with stronger evidence such as copied tool output;
- exporting a simple report when you need to show what was found and where.
It is a small tool I made for practical review work, not a polished detection service. The point is to make the suspicious bits visible so you can decide what matters.
GlyphGuard does not prove that a passage was written by AI. Its heuristic rules highlight review signals: unusual glyphs, leaked tool markers, copied citation plumbing, generic generated-prose patterns and other artefacts worth checking.
This is the high-level overview. Later sections explain how to interpret the results, run the scorer, add samples and update rules.
GlyphGuard is built with Tkinter and standard Python libraries. Report generation uses matplotlib. It runs locally on Windows, macOS and Linux.
Paste text or load a supported file and GlyphGuard lists non-ASCII characters with their code point, Unicode name, category, concern level, count and positions. It covers invisible controls, non-breaking spaces, bracket alternatives, dash and quote variants, fullwidth forms, private-use markers and other derived Unicode oddities.
This is useful for AI review because some AI tools leak private-use markers or odd citation characters. It is also useful outside AI detection because hidden characters can be used for prompt injection, confusing diffs, broken search, bad copy/paste, parser failures, misleading identifiers, or text that displays differently from how it is stored.
Useful checks include:
- zero-width spaces, joiners and non-joiners hidden inside ordinary words;
- direction controls and invisible formatting characters;
- curly quotes, long dashes, fullwidth letters or strange brackets before pasting into strict systems;
- private-use characters left behind by citation or rendering tools;
- replacement suggestions for making a clean copy of the text.
The UI also uses semantic badges. Character rows show category badges, and holistic rules are tagged by evidence type such as LLM Style, Structure, Evidence Gap, Citation Leak, Metadata Leak, API Leak, Punctuation Pattern and vendor/model tags where the rule is specific enough. Dense badge rows collapse behind a +N expander when needed.
GlyphGuard uses a curated set of regular expressions to catch vocabulary, phrasing, structure, citation leaks, tool artefacts and API metadata that are often worth reviewing in generated or copy-pasted AI output.
A compact example of the sort of thing it is meant to catch:
The source says this is supported by [12†L44-L51] and turn0file3.
The exact rendered marker changes by tool, but this is the shape of the problem: citation plumbing that should have been converted into normal prose, a normal footnote, or a normal link before the text was published.
Rule families cover OpenAI browser and Deep Research citations, private-use citation markers, source IDs, Harmony tokens, File Search metadata, Claude citation metadata, Gemini grounding metadata and Perplexity/Sonar search metadata.
Holistic rules are broader writing-pattern checks. They look for repeated generated-prose habits, formulaic structures, vague attribution, suspiciously generic framing, evidence gaps and punctuation patterns.
These rules are scored because a single weak phrase is not very meaningful. Repeated matches across a short text are more interesting than one isolated match in a long document. The score is a review priority, not a probability and not an authorship verdict.
Each holistic rule can include static examples, live matches from the scanned text, source references, semantic tags, positions, severity and a suggested review action. Examples explain the rule. Matches are the actual snippets found in your text.
You can paste text directly into the input pane or import a file. Supported input covers plain text, Markdown, logs, CSV/TSV, JSON, YAML, TOML, INI, source-code files, HTML/XML/SVG, DOCX, ODT, RTF and text-based PDFs.
The application scans pasted text and loaded document content as you work, updating counts and flags immediately without server calls. You can collapse noisy panels, click position pills to jump back to the source text, hover source badges for provenance, and adjust character detection and replacement behaviour in preferences.
For plain text sources, GlyphGuard can save cleaned text back to the original file or save a clean copy. For document formats such as DOCX, ODT, RTF and PDF, extraction is read-only; use Save as or Save clean copy.
GlyphGuard can export detailed PDF and plain-text reports. Reports include summary scores, glyph detections, holistic matches, examples, sources, positions and charts.
The headless scorer can also scan files and folders without opening the Tkinter UI.
The samples/ folder currently contains samples/wikipedia_signs_ai_writing/, a bundled set of 198 text samples extracted from Wikipedia's "Signs of AI writing" page. The set contains 175 quote examples and 23 preformatted examples.
The corpus is for detector tuning, regression checks and quick scoring experiments. It is not a claim that every sample is AI-generated.
- Python 3.8 or newer.
matplotlibfor PDF report generation.- Tkinter for the desktop UI. It is bundled with many Python installs. On some Linux distributions you may need to install it separately, for example with
python3-tk. pytestif you want to run tests.
From a fresh clone:
git clone <repo-url>
cd glyphguard
python -m pip install matplotlibFor development:
python -m pip install matplotlib pytestThere is no packaging wrapper at the moment. Run the files from the repository root.
python glyphguard.pyThe app opens a resizable desktop window. Paste text into the input pane or use Import to load a document. If the window does not open on Linux, check that Tkinter is installed.
Default run, using the bundled sample corpus when present:
python score_samples.pyScore the bundled corpus explicitly:
python score_samples.py samples/wikipedia_signs_ai_writing --out resultsScore a specific file or folder:
python score_samples.py README.md docs --out resultsChoose extensions when scoring a mixed folder:
python score_samples.py docs --extensions .txt,.md,.html,.docx,.pdf --out resultsRun a quick smoke test:
python score_samples.py samples/wikipedia_signs_ai_writing --limit 10 --out /tmp/glyphguard_score_smokeThe older --samples option is still accepted as an alias for one input file or folder, but positional paths are the normal form now.
- Launch GlyphGuard.
- Paste text, or click Import and choose a file.
- Review the score cards and detection lists.
- Expand rows to see positions, examples, tags and sources.
- Use the clean-text preview to check replacements.
- Export a PDF or text report if you need to share the findings.
GlyphGuard results should be read as review prompts. Strong artefacts such as leaked private-use markers or raw citation metadata are different from weak style hints such as a generic transition. The useful question is not "did the tool accuse this text?" but "what exactly did it find, how strong is that signal, and does the surrounding text support that concern?"
A good review usually works in this order:
- Check very-high and high severity artefacts first.
- Check whether the match is a hard leak, a hidden character, or only a writing-style hint.
- Use positions and snippets to inspect the original context.
- Treat repeated weak signals as more interesting than isolated weak signals.
- Keep false positives and false negatives in mind.
GlyphGuard uses practical concern levels:
- None - visible but not normally a problem, or kept as a no-concern review signal.
- Low - weak hint or low-risk formatting oddity.
- Medium - worth checking, especially if repeated.
- High - likely to matter, such as malformed markup, suspicious metadata or stronger style patterns.
- Very high - serious glyph concern or mapped critical artefact.
- Critical - internal holistic-rule severity for hard tool leaks, prompt leaks, raw metadata or placeholder artefacts. In the UI this maps into the very-high concern bucket.
Rules have static examples in the code and reports. Those examples are not evidence from the current scan; they are there to show what the rule is looking for.
Matches are different. A match is text found in the document you scanned, with positions where possible. When reviewing a rule row, use examples to understand the rule and matches to inspect the actual evidence.
Glyph rows show the character code point, Unicode name, category, concern level, count, positions and possible replacement. Unknown non-ASCII characters can still be derived from Python's Unicode metadata and shown for review even when they are not in the curated list.
Replacement suggestions are deliberately conservative. Some characters should be removed, some should be replaced with plain ASCII, and some should be kept because they are normal in the right context. The clean-text preview is there so you can check the result before saving anything.
Holistic rules use semantic tags such as LLM Style, Structure, Evidence Gap, Citation Leak, Metadata Leak, API Leak, Punctuation Pattern, OpenAI, Claude, Gemini and Perplexity. Character rows use category badges such as space, bracket, punctuation, invisible format and safe symbol.
Sources are provenance, not proof. A source badge explains where the idea for a rule came from or which public documentation describes that artefact shape. Hover text in the UI and the exported reports include the source name and description so the rule is easier to audit later.
GlyphGuard is not an authorship detector. It will not prove a person used AI, and it will not prove a person did not use AI.
False positives are expected. Humans write generic transitions. Humans use dashes. Humans paste weird Unicode by accident. False negatives are also expected. AI output can be edited, paraphrased or generated without obvious tool leaks.
Treat hard artefacts differently from weak style hints. A leaked private-use citation marker is strong copy/paste evidence. A generic transition is only a prompt to look closer.
Scores are for prioritising review, not judging authorship.
- Raw score adds up weighted matches.
- Contextual score adjusts the raw evidence using density and spread across the text.
- Character score covers glyph-level findings.
- Holistic score covers writing-pattern and tool-artefact rules.
- Total score combines both.
A short text with one hard citation leak can still score high. A long document with a few mild signals should not be treated the same way. That is why the contextual score exists.
| Family | Clean | Trace | Low | Medium | High | Very high |
|---|---|---|---|---|---|---|
| Glyph | 0 | >0 and < 2 | >= 2 and < 6 | >= 6 and < 20 | >= 20 and < 50 | >= 50 |
| Holistic | 0 | >0 and < 1.5 | >= 1.5 and < 4 | >= 4 and < 10 | >= 10 and < 20 | >= 20 |
| Total | 0 | >0 and < 3 | >= 3 and < 10 | >= 10 and < 30 | >= 30 and < 70 | >= 70 |
Raw score is the direct evidence total. For glyphs, each detected character occurrence contributes a concern weight. For holistic rules, each matched pattern contributes the pattern weight from HOLISTIC_AI_PATTERN_SPECS.
Contextual score keeps that raw evidence as the anchor, then adjusts it using density and spread. Dense evidence in a short text is more concerning than the same raw score scattered through a long text. Evidence spread across many sentences or paragraphs is also more concerning than one localised match.
This is still a heuristic. It is meant to sort review priority, not produce a probability.
The maths uses smooth saturation rather than hard cut-offs. The main idea is that ratios such as "matched words divided by total words" should increase concern quickly at first, then flatten out instead of growing forever. The function used for that is based on exponential decay: Exponential decay. The weighted combination is just a weighted average: Weighted arithmetic mean.
For a ratio x and a scale value s, GlyphGuard uses:
Where:
xis the observed ratio, such as matched words divided by all words.sis the scale that controls how quickly the curve approaches 1.S(x, s)is the bounded factor used by the contextual multiplier.
Small values of x still count. Very large values do not explode the score.
For glyphs, each concern level has a weight:
| Concern | Weight |
|---|---|
| None | 0 |
| Low | 0.5 |
| Medium | 1.0 |
| High | 2.0 |
| Very high | 3.0 |
The raw glyph score is:
Glyph context uses character density and line spread:
Those ratios are converted into saturation factors:
The glyph context factor is:
The final glyph multiplier and contextual glyph score are:
For holistic matches, raw score is the sum of matched pattern weights:
The scorer only counts matches that overlap prose blocks or blocks with matches. This avoids treating every bit of boilerplate or non-prose file content as equally meaningful.
Holistic context uses word density, sentence spread and paragraph spread:
Word density uses saturation directly:
Sentence and paragraph spread also get a small-sample confidence factor so one sentence or one paragraph cannot look stronger than it is:
The holistic context factor is weighted towards word density because paragraph counts can be volatile in short text:
The final multiplier and contextual holistic score are:
The total score just adds the two families:
The UI shows contextual score first and raw score in brackets, because contextual score is usually the better review priority.
The detection rules are defined in glyphguard.py so the app remains self-contained, might expand it in future or allow loading from somewhere in future maybe.
Unicode and hidden-character rules
Glyph-level detections live in TRACKED_CHAR_SPECS until I think of a better name. The app also derives fallback metadata from Python's Unicode database for non-ASCII characters that are not in the curated list. Rules cover invisible controls, private-use markers, bracket variants, whitespace variants, dash and quote variants, fullwidth characters, replacement suggestions and derived unknown Unicode.
The table lists code points and escaped examples rather than inserting every raw character directly into the README. That keeps invisible/private-use characters from hiding inside the documentation itself.
| Code | Visibility | Concern | Categories | Description | Kind | Example | Replacement | Keyboard | HTML/raw |
|---|---|---|---|---|---|---|---|---|---|
U+0009 |
Visible | Low concern | Tracked, Space, Control | Tab | Tracked character | word ? word |
? 4 spaces | Tab |
- |
U+000A |
Visible | No concern | Tracked, Space, Control | Line Feed (LF) | Tracked character | line1\\nline2 |
keep in double | Enter |
- |
U+000C |
Visible | High concern | Tracked, Control | Form Feed | Tracked character | - | remove | - | - |
U+000D |
Visible | No concern | Tracked, Space, Control | Carriage Return (CR) | Tracked character | - | keep in double | Enter |
- |
U+001C |
Invisible | Very high concern | Tracked, Invisible format, Control | File Separator | Tracked character | - | remove | - | - |
U+0020 |
Visible | No concern | Tracked, Keyboard, Space | Regular Space | Tracked character | word word |
keep | Spacebar |
- |
U+00A0 |
Visible | Medium concern | Tracked, Space | No-Break Space | Tracked character | word word |
? space | - | - |
U+00A2 |
Visible | No concern | Tracked, Safe symbol, Symbol | Cent Sign | Common safe symbol | 50\\xa2 |
keep | - | \\xa2 |
U+00A3 |
Visible | No concern | Tracked, Keyboard, Safe symbol, Symbol, GB language char | Pound Sign | Common safe symbol | \\xa310 |
keep | \\xa3 |
\\xa3 |
U+00A4 |
Visible | No concern | Tracked, Safe symbol, Symbol | Currency Sign | Common safe symbol | \\xa4 |
keep | - | \\xa4 |
U+00A5 |
Visible | No concern | Tracked, Safe symbol, Symbol | Yen Sign | Common safe symbol | \\xa51000 |
keep | - | \\xa5 |
U+00A7 |
Visible | No concern | Tracked, Safe symbol, Punctuation | Section Sign | Common safe symbol | \\xa7 1 |
keep | - | \\xa7 |
U+00A9 |
Visible | No concern | Tracked, Safe symbol, Symbol | Copyright Sign | Common safe symbol | \\xa9 2026 |
keep | - | \\xa9 |
U+00AD |
Invisible | Very high concern | Tracked, Invisible format, Control | Soft Hyphen | Tracked character | - | remove | - | \\xad |
U+00AE |
Visible | No concern | Tracked, Safe symbol, Symbol | Registered Sign | Common safe symbol | Name\\xae |
keep | - | \\xae |
U+00B0 |
Visible | No concern | Tracked, Safe symbol, Symbol | Degree Sign | Common safe symbol | 21\\xb0C |
keep | - | \\xb0 |
U+00B6 |
Visible | No concern | Tracked, Safe symbol, Punctuation | Pilcrow Sign | Common safe symbol | \\xb6 |
keep | - | \\xb6 |
U+0192 |
Visible | No concern | Tracked, Safe symbol, Letter, International char | Latin Small Letter F With Hook / Florin | Common safe symbol | \\u019210 |
keep | - | \\u0192 |
U+034F |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Grapheme Joiner | Tracked character | - | remove | - | \\u034f |
U+115F |
Visible | Low concern | Tracked, Letter, International char | Hangul Choseong Filler | Tracked character | - | remove | - | \\u115f |
U+1160 |
Visible | Low concern | Tracked, Letter, International char | Hangul Jungseong Filler | Tracked character | - | remove | - | \\u1160 |
U+1680 |
Visible | Medium concern | Tracked, Space | Ogham Space Mark | Tracked character | - | ? space | - | - |
U+17B4 |
Visible | Low concern | Tracked, Mark, International char | Khmer Vowel Inherent AQ | Tracked character | - | remove | - | \\u17b4 |
U+17B5 |
Visible | Low concern | Tracked, Mark, International char | Khmer Vowel Inherent AA | Tracked character | - | remove | - | \\u17b5 |
U+180B |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Mongolian VS-1 | Tracked character | - | remove | - | \\u180b |
U+180C |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Mongolian VS-2 | Tracked character | - | remove | - | \\u180c |
U+180D |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Mongolian VS-3 | Tracked character | - | remove | - | \\u180d |
U+180E |
Visible | Medium concern | Tracked, Space, Control | Mongolian Vowel Sep. | Tracked character | - | ? space | - | \\u180e |
U+2000 |
Visible | Medium concern | Tracked, Space | En Quad | Tracked character | - | ? space | - | - |
U+2001 |
Visible | Medium concern | Tracked, Space | Em Quad | Tracked character | - | ? space | - | - |
U+2002 |
Visible | Medium concern | Tracked, Space | En Space | Tracked character | - | ? space | - | - |
U+2003 |
Visible | Medium concern | Tracked, Space | Em Space | Tracked character | - | ? space | - | - |
U+2004 |
Visible | Medium concern | Tracked, Space | Three-Per-Em Space | Tracked character | - | ? space | - | - |
U+2005 |
Visible | Medium concern | Tracked, Space | Four-Per-Em Space | Tracked character | - | ? space | - | - |
U+2006 |
Visible | Medium concern | Tracked, Space | Six-Per-Em Space | Tracked character | - | ? space | - | - |
U+2007 |
Visible | Medium concern | Tracked, Space | Figure Space | Tracked character | - | ? space | - | - |
U+2008 |
Visible | Medium concern | Tracked, Space | Punctuation Space | Tracked character | - | ? space | - | - |
U+2009 |
Visible | Medium concern | Tracked, Space | Thin Space | Tracked character | - | ? space | - | - |
U+200A |
Visible | Medium concern | Tracked, Space | Hair Space | Tracked character | - | ? space | - | - |
U+200B |
Invisible | Very high concern | Tracked, Invisible format, Control | Zero-Width Space | Tracked character | - | ? space | - | - |
U+200C |
Invisible | Very high concern | Tracked, Invisible format, Control | Zero-Width Non-Joiner | Tracked character | - | remove | - | \\u200c |
U+200D |
Invisible | Very high concern | Tracked, Invisible format, Control | Zero-Width Joiner | Tracked character | - | remove | - | \\u200d |
U+200E |
Invisible | Very high concern | Tracked, Invisible format, Control | Left-To-Right Mark | Tracked character | - | remove | - | \\u200e |
U+200F |
Invisible | Very high concern | Tracked, Invisible format, Control | Right-To-Left Mark | Tracked character | - | remove | - | \\u200f |
U+2013 |
Visible | Medium concern | Tracked, Punctuation | En Dash | Tracked character | pages 1\\u201310 |
- | - | \\u2013 |
U+2014 |
Visible | Medium concern | Tracked, Punctuation | Em Dash | Tracked character | word\\u2014word |
-- or - | - | \\u2014 |
U+2018 |
Visible | Medium concern | Tracked, Punctuation | Left Single Quote | Tracked character | 'hello |
' | - | \\u2018 |
U+2019 |
Visible | Medium concern | Tracked, Punctuation | Right Single Quote | Tracked character | don't |
' | - | \\u2019 |
U+201C |
Visible | Medium concern | Tracked, Punctuation | Left Double Quote | Tracked character | "hello |
" | - | \\u201c |
U+201D |
Visible | Medium concern | Tracked, Punctuation | Right Double Quote | Tracked character | hello" |
" | - | \\u201d |
U+2026 |
Visible | Medium concern | Tracked, Punctuation | Horizontal Ellipsis | Tracked character | wait... |
... | - | \\u2026 |
U+2028 |
Invisible | Very high concern | Tracked, Invisible format | Line Separator | Tracked character | - | ? \n | - | - |
U+2029 |
Invisible | Very high concern | Tracked, Invisible format | Paragraph Separator | Tracked character | - | ? \n\n | - | - |
U+202A |
Invisible | Very high concern | Tracked, Invisible format, Control | LTR Embedding | Tracked character | - | remove | - | \\u202a |
U+202B |
Invisible | Very high concern | Tracked, Invisible format, Control | RTL Embedding | Tracked character | - | remove | - | \\u202b |
U+202C |
Invisible | Very high concern | Tracked, Invisible format, Control | Pop Directional Fmt | Tracked character | - | remove | - | \\u202c |
U+202D |
Invisible | Very high concern | Tracked, Invisible format, Control | LTR Override | Tracked character | - | remove | - | \\u202d |
U+202E |
Invisible | Very high concern | Tracked, Invisible format, Control | RTL Override | Tracked character | - | remove | - | \\u202e |
U+202F |
Visible | Medium concern | Tracked, Space | Narrow NBSP | Tracked character | - | ? space | - | - |
U+2045 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Left Square Bracket With Quill | Bracket alternative | \\u2045text |
[ | [ |
\\u2045 |
U+2046 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Right Square Bracket With Quill | Bracket alternative | text\\u2046 |
] | ] |
\\u2046 |
U+205F |
Visible | Medium concern | Tracked, Space | Math Space | Tracked character | - | ? space | - | - |
U+2060 |
Invisible | Very high concern | Tracked, Invisible format, Control | Word Joiner | Tracked character | - | remove | - | \\u2060 |
U+2061 |
Invisible | Very high concern | Tracked, Invisible format, Control | Function Application | Tracked character | - | remove | - | \\u2061 |
U+2062 |
Invisible | Very high concern | Tracked, Invisible format, Control | Invisible Times | Tracked character | - | ? "x" | - | \\u2062 |
U+2063 |
Invisible | Very high concern | Tracked, Invisible format, Control | Invisible Separator | Tracked character | - | ? "," | - | \\u2063 |
U+2064 |
Invisible | Very high concern | Tracked, Invisible format, Control | Invisible Plus | Tracked character | - | ? "+" | - | \\u2064 |
U+2066 |
Invisible | Very high concern | Tracked, Invisible format, Control | LTR Isolate | Tracked character | - | remove | - | \\u2066 |
U+2067 |
Invisible | Very high concern | Tracked, Invisible format, Control | RTL Isolate | Tracked character | - | remove | - | \\u2067 |
U+2068 |
Invisible | Very high concern | Tracked, Invisible format, Control | First Strong Isolate | Tracked character | - | remove | - | \\u2068 |
U+2069 |
Invisible | Very high concern | Tracked, Invisible format, Control | Pop Directional Isolate | Tracked character | - | remove | - | \\u2069 |
U+206A |
Invisible | Very high concern | Tracked, Invisible format, Control | Inhibit Symmetric Swap | Tracked character | - | remove | - | \\u206a |
U+206B |
Invisible | Very high concern | Tracked, Invisible format, Control | Activate Symmetric Swap | Tracked character | - | remove | - | \\u206b |
U+206C |
Invisible | Very high concern | Tracked, Invisible format, Control | Inhibit Arabic Form Shaping | Tracked character | - | remove | - | \\u206c |
U+206D |
Invisible | Very high concern | Tracked, Invisible format, Control | Activate Arabic Form Shaping | Tracked character | - | remove | - | \\u206d |
U+206E |
Invisible | Very high concern | Tracked, Invisible format, Control | National Digit Shapes | Tracked character | - | remove | - | \\u206e |
U+206F |
Invisible | Very high concern | Tracked, Invisible format, Control | Nominal Digit Shapes | Tracked character | - | remove | - | \\u206f |
U+20A1 |
Visible | No concern | Tracked, Safe symbol, Symbol | Colon Sign | Common safe symbol | \\u20a110 |
keep | - | \\u20a1 |
U+20A6 |
Visible | No concern | Tracked, Safe symbol, Symbol | Naira Sign | Common safe symbol | \\u20a610 |
keep | - | \\u20a6 |
U+20A9 |
Visible | No concern | Tracked, Safe symbol, Symbol | Won Sign | Common safe symbol | \\u20a91000 |
keep | - | \\u20a9 |
U+20AA |
Visible | No concern | Tracked, Safe symbol, Symbol | New Sheqel Sign | Common safe symbol | \\u20aa10 |
keep | - | \\u20aa |
U+20AB |
Visible | No concern | Tracked, Safe symbol, Symbol | Dong Sign | Common safe symbol | \\u20ab10 |
keep | - | \\u20ab |
U+20AC |
Visible | No concern | Tracked, Safe symbol, Symbol | Euro Sign | Common safe symbol | \\u20ac10 |
keep | - | \\u20ac |
U+20AD |
Visible | No concern | Tracked, Safe symbol, Symbol | Kip Sign | Common safe symbol | \\u20ad10 |
keep | - | \\u20ad |
U+20B1 |
Visible | No concern | Tracked, Safe symbol, Symbol | Peso Sign | Common safe symbol | \\u20b110 |
keep | - | \\u20b1 |
U+20B2 |
Visible | No concern | Tracked, Safe symbol, Symbol | Guarani Sign | Common safe symbol | \\u20b210 |
keep | - | \\u20b2 |
U+20B4 |
Visible | No concern | Tracked, Safe symbol, Symbol | Hryvnia Sign | Common safe symbol | \\u20b410 |
keep | - | \\u20b4 |
U+20B9 |
Visible | No concern | Tracked, Safe symbol, Symbol | Indian Rupee Sign | Common safe symbol | \\u20b9500 |
keep | - | \\u20b9 |
U+20BA |
Visible | No concern | Tracked, Safe symbol, Symbol | Turkish Lira Sign | Common safe symbol | \\u20ba10 |
keep | - | \\u20ba |
U+20BD |
Visible | No concern | Tracked, Safe symbol, Symbol | Ruble Sign | Common safe symbol | \\u20bd100 |
keep | - | \\u20bd |
U+20BF |
Visible | No concern | Tracked, Safe symbol, Symbol | Bitcoin Sign | Common safe symbol | \\u20bf1 |
keep | - | \\u20bf |
U+2122 |
Visible | No concern | Tracked, Safe symbol, Symbol | Trade Mark Sign | Common safe symbol | Name\\u2122 |
keep | - | \\u2122 |
U+27E6 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Mathematical Left White Square Bracket | Bracket alternative | \\u27e6text |
[ | [ |
\\u27e6 |
U+27E7 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Mathematical Right White Square Bracket | Bracket alternative | text\\u27e7 |
] | ] |
\\u27e7 |
U+27E8 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Mathematical Left Angle Bracket | Bracket alternative | \\u27e8text |
< | < |
\\u27e8 |
U+27E9 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Mathematical Right Angle Bracket | Bracket alternative | text\\u27e9 |
> | > |
\\u27e9 |
U+2800 |
Visible | Medium concern | Tracked, Space, Symbol | Braille Blank | Tracked character | - | ? space | - | \\u2800 |
U+3000 |
Visible | Medium concern | Tracked, Space | Ideographic Space | Tracked character | - | ? space | - | - |
U+3010 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Left Black Lenticular Bracket | Bracket alternative | \\u3010text |
[ | [ |
\\u3010 |
U+3011 |
Visible | Medium concern | Tracked, Bracket, Punctuation | Right Black Lenticular Bracket | Bracket alternative | text\\u3011 |
] | ] |
\\u3011 |
U+3016 |
Visible | High concern | Tracked, Bracket, Punctuation | Left White Lenticular Bracket | AI citation bracket alternative | \\u30166\\u2020L9-L11 |
[ | [ |
\\u3016 |
U+3017 |
Visible | High concern | Tracked, Bracket, Punctuation | Right White Lenticular Bracket | AI citation bracket alternative | 6\\u2020L9-L11\\u3017 |
] | ] |
\\u3017 |
U+3164 |
Visible | Medium concern | Tracked, Space, Letter, International char | Hangul Filler | Tracked character | - | ? space | - | \\u3164 |
U+E200 |
Visible | Very high concern | Tracked, Control | Private Use Citation Start | OpenAI citation marker | \\ue200cite |
remove | - | \\ue200 |
U+E201 |
Visible | Very high concern | Tracked, Control | Private Use Citation Stop | OpenAI citation marker | \\ue201 |
remove | - | \\ue201 |
U+E202 |
Visible | Very high concern | Tracked, Control | Private Use Citation Delimiter | OpenAI citation marker | \\ue202 |
remove | - | \\ue202 |
U+FE00 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-1 | Tracked character | - | remove | - | \\ufe00 |
U+FE01 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-2 | Tracked character | - | remove | - | \\ufe01 |
U+FE02 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-3 | Tracked character | - | remove | - | \\ufe02 |
U+FE03 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-4 | Tracked character | - | remove | - | \\ufe03 |
U+FE04 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-5 | Tracked character | - | remove | - | \\ufe04 |
U+FE05 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-6 | Tracked character | - | remove | - | \\ufe05 |
U+FE06 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-7 | Tracked character | - | remove | - | \\ufe06 |
U+FE07 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-8 | Tracked character | - | remove | - | \\ufe07 |
U+FE08 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-9 | Tracked character | - | remove | - | \\ufe08 |
U+FE09 |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-10 | Tracked character | - | remove | - | \\ufe09 |
U+FE0A |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-11 | Tracked character | - | remove | - | \\ufe0a |
U+FE0B |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-12 | Tracked character | - | remove | - | \\ufe0b |
U+FE0C |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-13 | Tracked character | - | remove | - | \\ufe0c |
U+FE0D |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-14 | Tracked character | - | remove | - | \\ufe0d |
U+FE0E |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-15 | Tracked character | - | remove | - | \\ufe0e |
U+FE0F |
Invisible | Very high concern | Tracked, Invisible format, Mark, International char | Variation Selector-16 | Tracked character | - | remove | - | \\ufe0f |
U+FEFF |
Invisible | Very high concern | Tracked, Invisible format, Control | Zero-Width NBSP / BOM | Tracked character | - | remove | - | \\ufeff |
U+FF3B |
Visible | Medium concern | Tracked, Bracket, Punctuation | Fullwidth Left Square Bracket | Bracket alternative | \\uff3btext |
[ | [ |
\\uff3b |
U+FF3D |
Visible | Medium concern | Tracked, Bracket, Punctuation | Fullwidth Right Square Bracket | Bracket alternative | text\\uff3d |
] | ] |
\\uff3d |
U+FFA0 |
Visible | Medium concern | Tracked, Space, Letter, International char | Half-width Hangul Filler | Tracked character | - | ? space | - | \\uffa0 |
U+FFFC |
Visible | High concern | Tracked, Symbol | Object Replacement | Tracked character | - | ? "[OBJECT]" | - | \\ufffc |
The holistic rule sections below line up with the section comments in glyphguard.py. The summary tables include the rule metadata used by the app. Regexes are in collapsible blocks after each table because putting the full patterns directly in the table makes the README almost unreadable.
General prose, style and structure heuristics from the first rule block in glyphguard.py.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
ai_vocabulary |
(?i)\b(tapestry|delve|nuance|multifaceted|leverage|holistic|seamless|comprehensive|robust|foster|garner|intricate|poignant|vibrant)\b |
medium | 1.0 | LLM Style, Word Choice | wikipedia_signs_ai_writing | Generic high-polish vocabulary associated with LLM prose. Significance and promotional terms are split into more precise sibling rules. | Prefer concrete wording and remove decorative vocabulary when it adds no meaning. | The report delves into a vibrant tapestry of ideas.The proposal offers a holistic and multifaceted view. |
rule_of_three |
\b\w{3,},\s+\w{3,},\s+and\s+\w{3,}\b |
low | 0.5 | Structure, List Pattern | wikipedia_signs_ai_writing | Three-item list structure that generated prose can overuse. | Vary list structures or restructure the sentence. | Clearer, faster, and safer delivery matters.Teams need planning, testing, and monitoring. |
negative_parallelism |
(?i)\b(?:it\s+is|it'?s)\s+not\s+(?:just|merely|only|simply)\s+[^.?!\n]{1,120}?(?:,|;|\u2014|--|\s+but\s+(?:also|rather|instead)\b) |
high | 2.0 | Structure, Rhetorical Pattern | wikipedia_signs_ai_writing | Formulaic not-just contrast framing. | State the contrast directly without the not-just formula. | It's not just speed, it's reliability.It is not merely a tool, but also a platform. |
copula_avoidance |
(?i)\b(?:serves?\s+as|stands?\s+as|represents?\s+(?:an?\s+)?(?:important|major|significant|key|pivotal|crucial)?\s*\w+|marks?\s+(?:an?\s+)?(?:important|major|significant|key|pivotal|crucial|historic)\s+\w+|holds?\s+the\s+distinction\s+of)\b |
medium | 1.5 | Grammar, Marketing Style, Low Specificity | wikipedia_signs_ai_writing | Ornate replacements for simple is/are descriptions, narrowed to puffery-like copula substitutes. | Prefer direct descriptions where ornate framing adds no evidence. | The gallery serves as the organisation\u2019s exhibition space.The release marks a significant milestone for the project. |
prestige_boast_framing |
(?i)\b(?:boasts?\s+(?:an?\s+)?(?:impressive|extensive|rich|diverse|robust|unique)?\s*\w+|renowned\s+for|celebrated\s+as|acclaimed\s+for|recognised\s+as|recognized\s+as)\b |
high | 2.0 | Marketing Style, Low Specificity | wikipedia_signs_ai_writing | Prestige or boast framing separated from generic promotional buzzwords. | Replace prestige framing with verifiable facts. | The platform boasts extensive analytics.The artist is renowned for a unique visual language. |
promotional_buzzwords |
(?i)\b(?:groundbreaking|cutting[-\s]?edge|state[-\s]?of[-\s]?the[-\s]?art|game[-\s]?changer|revolutionary|breathtaking|stunning|unprecedented|unparalleled|transformative|disruptive|innovative\s+(?:solution|approach))\b |
high | 2.0 | Marketing Style, Low Specificity | wikipedia_signs_ai_writing | Standalone promotional buzzwords, separated from copula/boast framing. | Use specific, concrete descriptions instead of buzzwords. | This groundbreaking solution changes the market.The tool offers a state-of-the-art approach. |
vague_attribution |
(?i)\b(?:industry\s+experts\s+(?:say|believe|agree)|observers\s+(?:note|suggest|point)|critics\s+(?:argue|contend|suggest)|some\s+(?:argue|say|believe|suggest)|many\s+(?:believe|argue|contend|suggest)|it\s+is\s+widely\s+(?:believed|accepted|recognized|recognised|acknowledged)|research\s+(?:shows|suggests|indicates|demonstrates)\s+that)\b |
high | 2.0 | Evidence Gap, Attribution | wikipedia_signs_ai_writing | Vague attribution without a named source or citation. | Cite a specific source or remove the claim. | Industry experts say adoption will grow.Research suggests that teams are more productive. |
collaborative_artifact |
(?i)\b(?:I\s+hope\s+this\s+helps|let\s+me\s+know\s+if|would\s+you\s+like\s+(?:me\s+)?to|of\s+course!|certainly!|absolutely!|I'?d\s+be\s+(?:happy|glad|pleased)\s+to\s+help|is\s+there\s+anything\s+else|feel\s+free\s+to\s+ask)\b |
critical | 3.0 | Chatbot Artefact | wikipedia_signs_ai_writing | Chatbot conversation artefacts left in final text. | Remove these phrases entirely. | I hope this helps.Let me know if you want changes. |
formal_filler_phrase |
(?i)\b(?:in\s+order\s+to|due\s+to\s+the\s+fact\s+that|at\s+this\s+point\s+in\s+time|without\s+(?:a\s+)?doubt|needless\s+to\s+say|last\s+but\s+not\s+least)\b |
medium | 1.0 | LLM Style, Filler | wikipedia_signs_ai_writing | Formal filler phrases that add bulk without much information. | Cut the phrase and start directly with the main point. | In order to deploy, run the script.Due to the fact that costs vary, estimates changed. |
ing_superficial |
\b\w+ing,\s+(?:ensuring|reflecting|highlighting|underscoring|emphasizing|emphasising|demonstrating|illustrating|showcasing)\b |
high | 2.0 | Structure, Rhetorical Pattern | wikipedia_signs_ai_writing | Superficial -ing analysis clause used as a shortcut. | Replace with direct statements about cause, effect or evidence. | The update improves caching, ensuring faster loads.The rollout supports scaling, highlighting adoption. |
negative_conclusion |
(?i)\b(?:despite\s+(?:these\s+|the\s+|some\s+)?\w*\s*challenges?|faces?\s+several\s+challenges?|moving\s+forward|looking\s+ahead|going\s+forward|as\s+we\s+(?:move|look)\s+(?:forward|ahead))\b |
medium | 1.0 | Essay Style, Structure | wikipedia_signs_ai_writing | Generic challenge/future-outlook framing used to sound balanced. | Be specific about the challenge or future action. | Despite these challenges, the project continued.Looking ahead, the team plans to iterate. |
hedging |
(?i)\b(?:it\s+(?:seems|appears)\s+(?:that|to\s+be)|arguably|one\s+might\s+(?:say|argue|suggest)|it\s+could\s+be\s+(?:said|argued)\s+that|in\s+(?:many|some)\s+(?:ways|respects)|to\s+(?:some|a\s+(?:certain|large))\s+extent)\b |
medium | 1.0 | Cautious Style, Low Specificity | wikipedia_signs_ai_writing | Hedging language that weakens or genericises claims. | State the claim directly when evidence supports it. | It seems that the model is improving.Arguably, the trade-off is acceptable. |
conclusion_transition |
(?i)\b(?:in\s+conclusion|to\s+sum\s+up|wrapping\s+up|to\s+(?:recap|recapitulate)|all\s+in\s+all|in\s+(?:the\s+)?final\s+analysis)\b |
medium | 1.5 | Essay Style, Structure, Transition | wikipedia_signs_ai_writing | Formulaic ending transitions. Summary-specific variants are split into section_summary_transition. | End naturally or use a specific summary point. | In conclusion, the change worked.To sum up, the release was successful. |
conjunctive_adverb_overuse |
(?i)\b(?:furthermore|moreover|additionally|consequently|nevertheless|nonetheless|notwithstanding|whereas|hereby|thereof|therein|wherein)\b |
medium | 1.5 | Formal Style, Transition | wikipedia_signs_ai_writing | Formal conjunctive adverbs that can make prose mechanically academic. | Use simpler transitions where clearer. | Furthermore, the API supports retries.Nevertheless, latency remains a risk. |
universal_claim |
(?i)\b(?:everyone\s+(?:knows|agrees|understands)|no\s+one\s+(?:can\s+)?deny|it'?s?\s+universally\s+(?:accepted|acknowledged)|we\s+all\s+(?:know|agree|understand)|across\s+the\s+(?:board|globe|world))\b |
high | 2.0 | Evidence Gap, Overclaim | wikipedia_signs_ai_writing | Universal generalisations used to imply consensus without evidence. | Qualify the claim or provide evidence. | Everyone knows this approach is best.No one can deny the impact. |
numbered_benefits |
(?i)\b(?:three|3|four|4|five|5)\s+(?:key|main|primary|major|significant)\s+(?:benefits|advantages|reasons|factors|ways)\b |
medium | 1.5 | List Pattern, Structure | wikipedia_signs_ai_writing | Listicle-style numbered benefit or reason framing. | Introduce benefits naturally instead of announcing the count. | Three key benefits are speed, safety, and cost.5 main reasons explain the change. |
OpenAI citation, source, prompt and UI leak rules.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
openai_browser_line_citation |
(?:\u3010|\u3016)\s*\d+\s*\u2020\s*L\d+(?:-L\d+)?\s*(?:\u3011|\u3017) |
critical | 5.0 | Tool Artefact, Citation Leak, OpenAI, ChatGPT, Harmony | openai_harmony_docs | Raw OpenAI browser, Deep Research or Harmony-style line citation marker. | Render the citation properly or strip the marker. | See the source here \u30166\u2020L9-L11\u3017.The claim appears at \u30109\u2020L9-L11\u3011. |
openai_assistants_source_placeholder |
(?:\u3010|\u3016)\s*\d+:\d+\s*[\u2020\u2021]\s*[^\u3011\u3017\r\n]{1,120}\s*(?:\u3011|\u3017) |
critical | 5.0 | Tool Artefact, Citation Leak, OpenAI, Assistants, File Search | stackoverflow_openai_assistants_placeholder | Raw OpenAI Assistants/File Search placeholder citation. | Replace with a visible citation or strip it. | Raw marker \u30164:2\u2020source\u3017 appeared.Another marker was \u301012:1\u2021source\u3011. |
openai_private_use_citation |
\uE200cite\uE202(?:[A-Za-z][\w.-]*|turn(?:\d+|X)\w*(?:\d+|Y)?)(?:\uE202(?:L\d+(?:-L\d+)?|P(?:aragraph)?\s*\d+|[A-Za-z][\w.-]*|turn\d+\w+\d+))*\uE201 |
critical | 5.0 | Tool Artefact, Citation Leak, OpenAI, API | openai_citation_formatting_docs, stackoverflow_openai_assistants_placeholder | Raw OpenAI private-use Unicode citation marker. | Remove or render the citation properly. | \uE200cite\uE202turn0file1\uE202L8-L13\uE201\uE200cite\uE202turnXfileY\uE201 |
openai_private_use_generic_marker |
\ue200(?!cite\ue202)[^\r\n\ue201]{1,240}\ue202[^\r\n\ue201]{1,240}\ue201 |
critical | 4.5 | Tool Artefact, Citation Leak, OpenAI, ChatGPT | openai_citation_formatting_docs, observed_chatgpt_content_reference | Raw OpenAI/ChatGPT private-use UI marker with an internal delimiter. | Remove or render the marker properly. | \uE200filecite\uE202turn0file0\uE201\uE200products\uE202{"id":1}\uE201 |
openai_content_reference_marker |
:contentReference\[(?:oaicite|[^\]\s:]+):\d+\]\{index=\d+\} |
critical | 4.0 | Tool Artefact, Citation Leak, OpenAI, ChatGPT | wikipedia_signs_ai_writing, observed_chatgpt_content_reference | Raw ChatGPT/OpenAI contentReference citation marker. | Remove or render the reference properly. | :contentReference[oaicite:3]{index=3}:contentReference[source:17]{index=17} |
openai_naked_source_id |
(?<!\ue202)\bturn\d+(?:file|search|news|url|block|image|product|finance|forecast|sports|time)\d+\b |
high | 3.0 | Tool Artefact, Citation Leak, OpenAI, API | openai_citation_formatting_docs, wikipedia_signs_ai_writing | OpenAI-style source reference ID leaked without its citation wrapper. | Check whether a raw citation/source ID leaked. | turn0file0turn2search5 |
openai_harmony_special_token |
<\|(?:start|end|message|channel|constrain|call|return)\|> |
critical | 5.0 | Tool Artefact, Prompt Leak, OpenAI, Harmony | openai_harmony_docs | OpenAI Harmony prompt/message special token leaked into visible text. | Remove internal prompt-format tokens. | <|start|><|message|> |
openai_tool_channel_leak |
\bValid channels:\s*analysis\s*,\s*(?:commentary\s*,\s*)?final\b|\bto=(?:functions|browser|python|web|file_search|image_gen|automations)\.[A-Za-z_]\w*\b |
critical | 4.0 | Tool Artefact, Prompt Leak, OpenAI, Tool use | openai_harmony_docs | OpenAI-style tool/channel instruction leaked into visible text. | Remove internal tool-call or channel-routing text. | Valid channels: analysis, commentary, finalto=functions.get_current_weather |
Cross-vendor API and grounding metadata rules.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
openai_file_citation_json |
"type"\s*:\s*"file_citation"|"annotations"\s*:\s*\[|"file_id"\s*:\s*"file-[A-Za-z0-9_-]+ |
high | 3.0 | Metadata Leak, API Leak, OpenAI, File Search | openai_file_search_docs | Raw OpenAI Responses/File Search citation annotation JSON. | Treat as API/debug output rather than final prose. | "type": "file_citation""file_id": "file-abc123" |
anthropic_citation_json |
"cited_text"\s*:|"document_index"\s*:|"start_char_index"\s*:|"end_char_index"\s*:|"start_page_number"\s*:|"end_page_number"\s*:|"start_block_index"\s*:|"end_block_index"\s*: |
high | 2.5 | Metadata Leak, API Leak, Anthropic, Claude | anthropic_claude_citations_docs | Raw Anthropic/Claude citation metadata. | Treat as API metadata rather than final prose. | "cited_text": "...""start_char_index": 42 |
gemini_grounding_metadata_json |
"groundingMetadata"\s*:|"webSearchQueries"\s*:|"groundingChunks"\s*:|"groundingSupports"\s*:|"groundingChunkIndices"\s*:|"searchEntryPoint"\s*: |
high | 2.5 | Metadata Leak, API Leak, Google, Gemini | google_gemini_grounding_docs | Raw Gemini grounding/citation metadata. | Treat as Gemini API grounding metadata. | "groundingMetadata": {}"webSearchQueries": ["x"] |
perplexity_search_metadata_json |
"citations"\s*:\s*\[|"search_results"\s*:\s*\[|"search_context_size"\s*: |
medium | 2.0 | Metadata Leak, API Leak, Perplexity, Sonar | perplexity_sonar_features_docs | Raw Perplexity/Sonar search or citation metadata. | Treat as API/search metadata rather than final prose. | "citations": ["https://example.com"]"search_context_size": "low" |
Model/vendor-specific rules where the marker is narrow enough to tag.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
openai_creature_metaphor_tic |
(?i)\b(?:(?:little|tiny|small|feral|chaos|formatting|file[-\s]?format|filesystem|spreadsheet|cache|parser|prompt|token|tool|build|deploy(?:ment)?|perf|regex|yaml|json|state[-\s]?machine|clipboard|merge|git|branch|tooltip|ui|ux)\s+(?:goblin|goblins|gremlin|gremlins|troll|trolls|ogre|ogres|raccoon|raccoons|pigeon|pigeons)|(?:goblin|gremlin|troll|ogre|raccoon|pigeon)[-\s]?(?:mode|math|brain|energy|gearbox|bandwidth|version|logic))\b |
high | 3.0 | LLM Style, Word Choice, LLM Metaphor, OpenAI, Codex, GPT-5.1+, GPT-5.4-Nerdy, GPT-5.5-Codex | openai_goblin_article, tidbits_gpt55_prompt_discussion | Creature-metaphor lexical tic associated with OpenAI GPT-5.1+ / Codex style leakage. | Review whether the metaphor is intentional. | The filesystem goblin ate the config.There is a little perf gremlin running unattended. |
Dash-aside punctuation heuristics kept separate because they are noisy unless repeated.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
unicode_dash_delimited_parenthetical |
(?<!\S)(?:\u2014\s*[^\n\u2014]{8,180}\s*\u2014|\u2013\s*[^\n\u2013]{8,180}\s*\u2013)(?!\S) |
medium | 2.0 | Punctuation Pattern, Parenthetical Aside, Structure | wikipedia_signs_ai_writing | Unicode dash-delimited parenthetical aside using paired em/en dashes. | Consider commas, parentheses or a separate sentence. | The parser \u2014 somehow still? alive \u2014 handled the payload.The model \u2013 trying to sound casual \u2013 added another aside. |
ascii_dash_delimited_parenthetical |
(?<!\S)(?:--\s*(?=[^\n]{8,180}?\s*--)(?=[^\n]*[A-Za-z])(?:(?!--)[^\n]){8,180}?\s*--|-\s+(?=[^\n]{8,180}?\s+-)(?=[^\n]*[A-Za-z])(?:(?!\s+-)[^\n]){8,180}?\s+-)(?!\S) |
low | 1.25 | Punctuation Pattern, Parenthetical Aside, Structure | wikipedia_signs_ai_writing | ASCII dash-delimited parenthetical aside. | Weak structural signal unless it appears with other markers. | The cache -- because, naturally, it hates us -- lied.The UI - in its infinite wisdom - ate the tooltip. |
Rules for Wikipedia-style lead, prose and formatting patterns.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
leads_list_title_proper_noun |
(?im)^\s*(?:The\s+)?(?:[\u201C"]?List\s+of\s+[^\n.]{3,120}[\u201D"]?|[A-Z][A-Za-z0-9\u2019\'-]+(?:\s+[A-Z][A-Za-z0-9\u2019\'-]+){0,4}\s+editions)\s+(?:is|are|refers\s+to|constitutes|comprises)\b |
medium | 1.0 | Structure, Lead | wikipedia_signs_ai_writing | Lead sentence treats a list/article title as a standalone proper noun. | Introduce lists and descriptive titles naturally. |
The \u201CList of songs about Mexico\u201D is a curated compilation of musical works.EuroGames editions is the chronological list of the biennial EuroGames.
|
negative_parallelism_not_only |
(?i)\bnot\s+only\b(?:[^.?!\n]{0,180}\bbut\s+(?:also|rather|instead)\b)? |
medium | 1.5 | Structure, Rhetorical Pattern | wikipedia_signs_ai_writing | Broad not only / but also parallelism marker. | Use direct comparison or contrast where possible. |
This choice is not only dismissive but also harsh.The portrait constitutes not only self-representation but a visual document.
|
negative_parallelism_not_but |
(?i)\b(?:it\s+is|it'?s|this\s+is|that\s+is)?\s*not\s+(?:an?\s+)?[^.?!\n\u2014-]{1,90}?\s+(?:but|rather)\b|\bnot\s+[^.?!\n\u2014-]{1,40}?,\s*not\s+[^.?!\n\u2014-]{1,40}?,\s*not\s+[^.?!\n\u2014-]{1,80}?(?:\u2014|--|,)?\s*(?:just|only|rather|but)\b |
high | 2.0 | Structure, Rhetorical Pattern | wikipedia_signs_ai_writing | Separate negative-parallelism shape for not X but Y and repeated not-list contrast. | Avoid formulaic rejection/reframe statements. |
It is not a career but an algorithmic moment.Not a career, not a body of work, not sustained relevance \u2014 just an algorithmic moment.
|
title_case_headings |
(?m)^\s*(?:#{1,6}\s*)?(?=[^\n]{12,120}$)(?:[A-Z][A-Za-z0-9\u2019\'-]+|of|and|the|for|in|on|to|with)(?:\s+(?:[A-Z][A-Za-z0-9\u2019\'-]+|of|and|the|for|in|on|to|with)){2,}\s*$ |
none | 0.25 | No Concern, Headings, Formatting | wikipedia_signs_ai_writing | Title Case heading-like line. Added as an opt-in/no-concern review signal because this is noisy outside heading context. | Only flag if the user opts into no-concern/low-confidence style review. |
Impact of Technology and DigitalizationSustainable Development and Environmental Law
|
boldface_inline_header_list |
(?m)^\s*(?:[-*\u2022]|\d+[.)])\s+(?:(?:'''[^'\n:]{2,80}:?'''|\*\*[^*\n:]{2,80}:?\*\*)|(?:'''[^'\n]{2,80}'''|\*\*[^*\n]{2,80}\*\*)\s*:)\s+ |
medium | 1.5 | Structure, List Pattern, Formatting | wikipedia_signs_ai_writing | List item begins with a bold inline header and colon. | Use proper list markup and avoid slide-deck style inline headers. |
1. **Historical context:** The world was changing after WWII.- **Conflict of interest:** Please review the sourcing.
|
boldface_repeated_spans |
(?s)(?:'''[^'\n]{2,120}'''|\*\*[^*\n]{2,120}\*\*)(?:(?!\n\s*\n).){0,300}(?:'''[^'\n]{2,120}'''|\*\*[^*\n]{2,120}\*\*) |
low | 1.0 | Formatting, Low Specificity | wikipedia_signs_ai_writing | Multiple bold spans inside one paragraph or compact block. | Use emphasis sparingly. |
**Background** text continues, then **Impact** appears in the same paragraph.'''History''' is followed by prose and '''Reception''' in one paragraph.
|
inline_header_list |
(?m)^\s*(?:[-*\u2022#]|\d+[.)])\s+(?!'''|\*\*)(?:[A-Z][^:\n]{2,80})\s*:\s+ |
medium | 1.0 | Structure, List Pattern | wikipedia_signs_ai_writing | List item with a colon-headed mini-section. | Integrate short items into prose or use proper headings/lists. |
\u2022 Historical context: The world was changing after WWII.1. Historical context post-WWII era: The world was rapidly changing.
|
thematic_break_heading |
(?m)^\s*----\s*\n\s*==+\s*[^\n=]+\s*==+ |
medium | 1.0 | Structure, Markup, Wikitext | wikipedia_signs_ai_writing | Markdown-like thematic break immediately before a wikitext heading. | Remove extraneous horizontal rules before headings. |
----<br>== History ==----<br>=== Form and construction ===
|
Rules for copied chatbot disclaimers, placeholder text, renderer leaks and tracking parameters.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
knowledge_cutoff_disclaimer |
(?i)\b(?:as\s+of\s+(?:my\s+)?(?:last\s+)?(?:knowledge|training)\s+update|up\s+to\s+my\s+last\s+training\s+update|my\s+knowledge\s+(?:only\s+)?(?:goes|extends)\s+(?:up\s+)?to)\b |
critical | 2.5 | Chatbot Artefact, Evidence Gap, Disclaimer | wikipedia_signs_ai_writing | Explicit model knowledge/training cutoff disclaimer. | Remove chatbot-cutoff text and verify current sources. | As of my last knowledge update in January 2022, I do not have current information.Up to my last training update, this was not listed. |
source_scarcity_disclaimer |
(?i)\b(?:while\s+specific\s+(?:details|information)\s+(?:is|are)\s+(?:limited|scarce)|not\s+widely\s+(?:available|documented|disclosed)|based\s+on\s+available\s+information|maintains\s+a\s+low\s+profile|keeps\s+personal\s+details\s+private)\b |
medium | 1.5 | Evidence Gap, Source Scarcity, Disclaimer | wikipedia_signs_ai_writing | Source-scarcity speculation often used to pad missing evidence. | State only documented information or mark gaps explicitly. | Specific details are not widely documented in available sources.Based on available information, the village has limited coverage. |
placeholder_text |
(?i)(?:\[\s*(?:Describe|Enter|Your Name|specify|Provide)[^\]\n]{0,120}\]|\b(?:INSERT|PASTE|SOURCE|URL|ACCESS[_-]DATE|DATE)_[A-Z0-9_]+\b|\b\d{4}-(?:XX|xx)-(?:XX|xx)\b) |
critical | 2.5 | Placeholder, Template, Chatbot Artefact | wikipedia_signs_ai_writing | Template placeholder or fill-in-the-blank token left in final text. | Replace placeholders with real content or remove them. | [Describe the specific section that needs editing]SOURCE_URL_30 and access-date=2025-XX-XX remain in the citation. |
openai_oai_citation_renderer_leak |
(?i)(?:(?<!:)contentReference\[oaicite:\d+\](?!\{index=\d+\})|oai_citation|attached_file:\d+|Example\+\d+) |
critical | 4.0 | Tool Artefact, Citation Leak, OpenAI, UI marker | observed_chatgpt_content_reference, wikipedia_signs_ai_writing | OpenAI-ish citation renderer or attachment marker leak not covered by the stricter contentReference rule. | Remove renderer artefacts or replace with proper citations. | contentReference[oaicite:3]attached_file:2 was left in the copied answer. |
grok_citation_renderer_leak |
(?i)\b(?:grok_card|grok_render_citation_card_json|referrer=grok\.com)\b |
high | 3.5 | Tool Artefact, Citation Leak, Grok, UI marker | wikipedia_signs_ai_writing | Grok/X citation renderer artefact. | Strip renderer metadata and add normal citations. | grok_card appeared in the copied text.grok_render_citation_card_json was pasted into the article. |
triple_colon_writing_block |
(?m)^:::[^\W\d_][\w-]*\{[^}\n]*(?:variant|variante|id)\s*= |
critical | 4.0 | Tool Artefact, UI marker, Chatbot Artefact | observed_chatgpt_content_reference | ChatGPT writing-block fence leaked into text, including non-English block names. | Remove the UI wrapper and keep only the intended content. | :::writing{variant="document" id="12345"}:::\u00E9criture{variante="document" id="67890"} |
ai_referral_url_parameter |
(?i)\b(?:utm_source|utm_medium|utm_campaign|referrer)=(?:openai|chatgpt(?:\.com)?|copilot(?:\.microsoft\.com|\.com)?|grok(?:\.com)?|perplexity|claude|anthropic|gemini|bard)\b |
high | 3.5 | URL Parameter, Citation Leak, Tool Artefact | wikipedia_signs_ai_writing | AI-tool referral/tracking parameter copied with a URL. | Strip tracking parameters and verify the source URL. | https://example.com/?utm_source=chatgpt.comhttps://x.example/?referrer=grok.com |
generic_utm_parameter |
(?i)\b(?:utm_source|utm_medium|utm_campaign|utm_term|utm_content)=([^\s&#]+) |
low | 0.5 | URL Parameter, Tracking Parameter | wikipedia_signs_ai_writing | Generic UTM tracking parameter. Not AI-specific, but useful citation hygiene. | Remove tracking parameters when preserving citations. | https://example.com/?utm_source=newsletterhttps://site.test/?utm_campaign=spring |
Rules for talk-page phrasing, subject-line comments and edit-summary style artefacts.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
canned_quality_goodfaith |
(?i)\b(?:aligns?\s+with\s+Wikipedia(?:['\u2019]s)?\s+(?:aim|goal|mission|standards)|adheres?\s+to\s+(?:good\s+faith|neutrality|Wikipedia(?:['\u2019]s)?\s+principles)|assume\s+good\s+faith|constructive\s+and\s+collaborative)\b |
low | 0.75 | Talk Page, Good Faith, Formal Style | wikipedia_signs_ai_writing | Canned talk-page good-faith or quality language. | Use only where it is genuinely part of the discussion. | This aligns with Wikipedia\u2019s mission.Please assume good faith and keep the discussion constructive. |
canned_constructive_criticism |
(?i)\b(?:if\s+you\s+have\s+any\s+(?:concerns|suggestions|feedback)|if\s+there\s+are\s+any\s+issues|constructive\s+(?:criticism|feedback)|please\s+feel\s+free\s+to\s+(?:improve|comment|respond))\b |
low | 0.5 | Talk Page, Constructive Criticism, Chatbot Artefact | wikipedia_signs_ai_writing | Generic constructive-feedback boilerplate. | Keep only if it adds context to the conversation. | If you have any concerns, please let me know.Constructive feedback is welcome. |
canned_focus_on_content |
(?i)\b(?:let\s*'?s\s+focus\s+on\s+(?:improving\s+)?(?:the\s+)?(?:article|content)|in\s+the\s+spirit\s+of\s+collaboration|focus\s+on\s+content\s+rather\s+than\s+contributors)\b |
low | 0.5 | Talk Page, Focus Shift | wikipedia_signs_ai_writing | Canned talk-page focus-shift wording. | Use specific article/content issues instead of generic moderation language. | Let's focus on improving the article.Please focus on content rather than contributors. |
subject_line_comment |
(?m)^\s*Subject:\s+[^\n]{1,160}$ |
low | 0.75 | Talk Page, Subject Line, Template | wikipedia_signs_ai_writing | Email/form-style subject line left inside a comment. | Remove the subject line or convert it into a heading only if appropriate. | Subject: Request for reviewSubject: Sources for this article |
Rules for small tables, headings, categories, reference reuse, book citation pages and policy shortcut clusters.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
unusual_small_table |
(?s:\{\x7c(?=.{0,700}\x7c\})(?=(?:[^\n]*\n){1,10})[^{}]{0,700}\x7c\})|(?m:^\s*\x7c\s*(?:Path|Key|Source|Item|Name)\s*\x7c[^\n]*\x7c\s*\n\s*\x7c(?:\s*:?-{3,}:?\s*\x7c){2,5}\s*\n(?!(?:\s*\x7c[^\n]*\x7c\s*\n?){7})(?:\s*\x7c[^\n]*(?:\[[^\]]+\]\([^)]+\)|https?://)[^\n]*\x7c\s*\n?){1,6}) |
low | 0.75 | Table, Markup, Wikitext | wikipedia_signs_ai_writing | Very small wikitext table, or small generated-looking Markdown index/link table. | Review whether the table is complete and valid. For short link/index lists, use bullets unless a table adds real structure. |
{| class="wikitable"<br>| Example<br>|}{|<br>! A<br>| B<br>|}| Path | Description |<br>| --- | --- |<br>| [samples/x](samples/x) | Example sample set. |
|
skip_heading_levels |
(?m)^\s*={3,}\s*[^=\n][^\n]*={3,}\s*$ |
low | 0.5 | Headings, Markup, Wikitext | wikipedia_signs_ai_writing | Potential skipped heading level in wikitext. | Check heading hierarchy. |
=== History ======= Reception ====
|
broken_category_syntax |
\[\[Category:[^\]\|\n]{0,120}[/<>][^\]\n]{0,120}\]\] |
high | 2.0 | Category, Markup, Malformed | wikipedia_signs_ai_writing | Malformed category link with slash or angle-bracket placeholder. | Fix or remove malformed category syntax. |
[[Category:Example/Bad]][[Category:<Insert category>]]
|
attributable_index_json |
(?:\(attribution\)\{[^}\n]*"attributableIndex"|"attributableIndex"\s*:\s*"?\d+(?:-\d+)?"?) |
high | 2.5 | Metadata Leak, API Leak, Citation Leak | wikipedia_signs_ai_writing | Attribution/citation JSON metadata leaked into text. | Strip API metadata and retain only visible citations. |
(attribution){"attributableIndex":"1-2"}"attributableIndex": "7"
|
invalid_reference_reuse |
(?is)<ref\b[^>]*>[^<]{0,400}<ref\b|</ref>\s*</ref> |
medium | 1.5 | Markup, Citation Leak, Malformed | wikipedia_signs_ai_writing | Nested reference tag or duplicate closing reference tag. | Fix the reference markup before publication. |
<ref>first <ref>nested</ref>citation</ref></ref>
|
book_citation_missing_page |
(?is)\{\{\s*cite\s+book\b(?=[^}]{0,600}\|\s*title\s*=)(?![^}]{0,600}\|\s*pages?\s*=)[^}]{0,600}\}\} |
none | 0.25 | No Concern, Citation Quality, Book Citation | wikipedia_signs_ai_writing | Book citation without page/pages field. Citation-quality signal, not AI evidence. | Only flag in a future citation-quality/no-concern mode. |
{{cite book |title=Example book |author=A. Writer}}{{ cite book | title=Long book | publisher=Press }}
|
edit_summary_first_person |
(?i)\b(?:I\s+(?:revised|updated|improved|adjusted|expanded|refined|corrected|added|removed|ensured|avoided)\b|my\s+(?:edit|changes|summary)\b) |
low | 0.75 | Edit Summary, First Person | wikipedia_signs_ai_writing | First-person edit-summary language in copied text. | Check whether an edit summary or chatbot response was pasted into content. |
I revised the article for neutrality.My edit improves the sourcing.
|
afc_submission_statement |
(?i)(?:\bReviewer\s+note\s*\(for\s+AfC\)|\bThis\s+draft\s+is\s+a\s+neutral\s+and\s+well[-\s]sourced\s+biography\b|\bAfC\s+submission\b) |
high | 2.0 | AfC, Template, Markup | wikipedia_signs_ai_writing | AfC-specific generated/review artefact. | Remove review boilerplate from article prose. |
Reviewer note (for AfC): this draft has been improved.This draft is a neutral and well-sourced biography.
|
wikilawyering_multiple_shortcuts |
(?im)^(?=(?:.*\bWP:[A-Z0-9]{1,}\b){3,}).{0,400}$ |
medium | 1.25 | Talk Page, Policy | wikipedia_signs_ai_writing | Talk-page line with multiple policy shortcuts. Useful but not necessarily AI. | Review whether the policy references are meaningful and relevant. |
WP:NPOV WP:V WP:OR all apply here.This violates WP:RS, WP:BLP and WP:UNDUE.
|
Lower-confidence formatting, notability, summary and incomplete-copy rules.
| Rule | Regex | Severity | Weight | Tags | Sources | Description | Suggestion | Examples |
|---|---|---|---|---|---|---|---|---|
emoji_bulletpoint |
(?m)^\s*[\U0001F300-\U0001FAFF\u2600-\u27BF]\s+ |
low | 0.75 | Emoji, Emoji Bullet, Formatting | wikipedia_signs_ai_writing | Emoji used as a bullet marker. | Use plain list markup if this is formal text. | \u2705 Completed the rewrite.\u1F539 Added source details. |
emoji_formatting_inline |
(?m)(?<!^\s)[\U0001F300-\U0001FAFF\u2600-\u27BF] |
low | 0.5 | Emoji, Formatting | wikipedia_signs_ai_writing | Emoji used as inline formatting or emphasis. | Remove decorative emoji from formal content unless intentional. | This looks polished \u2705Please review the section \u1F50D. |
didactic_disclaimer |
(?i)\b(?:it\s+is\s+(?:important|worth|essential)\s+to\s+note\s+that|it\s+should\s+be\s+noted\s+that)\b |
medium | 1.0 | LLM Style, Filler | wikipedia_signs_ai_writing | Didactic note-signposting phrase split from generic filler. | Remove the phrase and state the point directly. | It is important to note that costs vary.It should be noted that the source is incomplete. |
section_summary_transition |
(?i)\b(?:in\s+summary|to\s+summari[sz]e|overall)\b |
medium | 1.0 | Essay Style, Transition | wikipedia_signs_ai_writing | Section-summary transition split from conclusion-transition phrasing. | Use a specific summary point or omit the transition. | In summary, the change worked.Overall, the release was successful. |
ai_prompt_refusal |
(?i)\b(?:as\s+an\s+AI\s+language\s+model|I\s+cannot\s+(?:assist|help)\s+with|I\s+can\s+not\s+(?:assist|help)\s+with|I\s+am\s+unable\s+to\s+comply)\b |
critical | 3.0 | Chatbot Artefact, Refusal, Prompt Leak | wikipedia_signs_ai_writing | Raw chatbot refusal or model-identity phrase. | Remove prompt-response artefacts from final text. | As an AI language model, I cannot assist with that.I am unable to comply with this request. |
abrupt_cutoff |
(?m)\b[A-Za-z]{3,}(?:\.{2,}|\u2026)\s*$ |
low | 0.5 | Incomplete | wikipedia_signs_ai_writing | Abrupt trailing ellipsis or incomplete copy at end of line. | Check whether text was truncated during copy/paste or generation. | The article continues with additional...The sentence trails off\u2026 |
significance_legacy_trend |
(?i)\b(?:plays\s+a\s+(?:significant|pivotal|crucial)\s+role|represents\s+a\s+(?:key|significant|major|pivotal)\s+(?:shift|moment)|marks\s+a\s+(?:turning\s+point|milestone)|serves\s+as\s+a\s+(?:catalyst|focal\s+point)|drives\s+the\s+(?:growth|evolution)\s+of|mirrors\s+(?:broader|wider)\s+trends|embodies\s+the\s+(?:ongoing|contemporary)\s+discourse|underscores\s+the\s+(?:importance|legacy|relevance))\b |
medium | 1.75 | Marketing Style, Legacy Hype, Low Specificity | wikipedia_signs_ai_writing | Phrase-level significance/legacy/trend hype split out from vocabulary and promotional rules. | Replace broad significance claims with concrete sourced facts. | The album plays a significant role in the scene.The release mirrors broader trends in digital media. |
canned_notability_media |
(?i)\b(?:independent\s+(?:local|regional|national|international)\s+(?:media|news|press)\s+(?:outlets|coverage)|profiled\s+in\s+(?:[^,\n]+,\s*){1,3}(?:and\s+)?[^,\n]+|featured\s+in\s+(?:[^,\n]+,\s*){1,3}(?:and\s+)?[^,\n]+|(?:active|strong)\s+(?:social\s+media|online)\s+presence|on\s+(?:platforms|social\s+media)\s+such\s+as\s+[^.\n]{3,80})\b |
medium | 1.25 | Notability, Evidence Gap, Marketing Style | wikipedia_signs_ai_writing | Canned notability/media-coverage argument pattern. | Name specific reliable independent sources and avoid generic notability claims. | The subject has independent local media coverage.She was profiled in Example News, Local Times, and Arts Weekly. |
exhaustive_edit_summary |
(?i)\bI\s+(?:revised|updated|improved|ensured|expanded|removed)\s+[^\n]{50,180}\b(?:WP:[A-Za-z]{2,}|Neutral\s+Point\s+of\s+View|Verifiability|Original\s+Research|NPOV|reliable\s+sources)\b |
medium | 1.5 | Edit Summary, First Person, Policy | wikipedia_signs_ai_writing | Verbose first-person edit summary with policy-heavy justification. | Keep edit summaries out of article prose. | I revised the lead, improved sourcing, removed unsourced claims, and ensured compliance with WP:NPOV.I updated the draft to improve sourcing, remove unsupported claims, clarify the lead, and improve Verifiability. |
hallucinated_category_name |
\[\[Category:[^\]\n]{30,120}\]\] |
low | 0.5 | Category, Hallucination | wikipedia_signs_ai_writing | Improbably long category name. Weak signal; can be legitimate. | Verify that the category exists and is appropriate. | [[Category:Contemporary interdisciplinary artists from the upper valley region]][[Category:People associated with regional innovation and digital outreach]] |
This covers the sample material in the repository, including the public sample corpus and the smaller test fixture sets.
samples/wikipedia_signs_ai_writing/- Bundled Wikipedia "Signs of AI writing" example corpus used for detector tuning and scorer checks. Contains 198.txtsamples: 175 quote examples and 23 preformatted examples.tests/samples/- Small text fixtures used by tests and manual checks. Contains 6 direct.txtfiles, including short/long AI-style burger essays, a medium article sample, OpenAI Harmony page text and quick smoke-test text.tests/samples/ai_filetype_detection_samples/- File-type extraction fixture set used bytests/test_score_samples.py. Contains 36 files covering plain text, code, CSV/TSV, JSON/JSONL, YAML, TOML, INI, properties, log, Markdown, LaTeX, HTML/XML, EML, EPUB, XLSX, DOCX, ODT, RTF, PDF, image, binary and SQLite cases.
The Wikipedia corpus filenames include the index, block type and source section slug, for example:
001_quote_undue_emphasis_on_significance_legacy_and_broader_trends.txt
066_pre_not_x_but_y.txt
The file-type fixture names are deliberately literal, for example:
sample_markdown_fenced_block.md
sample_word_document.docx
sample_searchable_text_pdf.pdf
score_samples.py writes these files by default under results/ or another folder passed with --out:
file_scores.csv- one row per scored file, including source kind, match counts and scores.rules_by_file.csv- one row per matched rule per file.rule_frequency.csv- aggregate rule frequency across the scanned files.skipped_files.csv- files ignored or failed during extraction.file_scores.json- full structured output.
The scorer imports local glyphguard.py headlessly and uses the same rule loader, text extractor and score-breakdown methods as the app.
Add new corpora under samples/<corpus_name>/ as plain .txt files. Keep one source block per file where possible. Name files with an index, block type and short source slug so sorting is stable and the source is still recognisable.
If the corpus was generated from a page or document, include a manifest with source path, block kind, character count and word count so future scoring runs are traceable. Then run the scorer and review the generated CSV/JSON output before committing sample changes.
Example:
python score_samples.py samples/<corpus_name> --out resultsGlyphGuard has unit tests for the app/rule behaviours, a one-test-per-rule suite for holistic rules, and scorer tests.
Run the full test suite:
python -m pytest -qRun the main app/rule behaviour tests:
python -m pytest -q tests/test_glyphguard.pyRun the one-test-per-rule suite:
python -m pytest -q tests/test_holistic_rules.pyRun scorer tests:
python -m pytest -q tests/test_score_samples.pytests/test_holistic_rules.py tests every production pattern explicitly. It includes flattened examples, extra positive and negative cases, metadata checks and overlap guards where similar rules could double-count the same text.
python score_samples.py samples/wikipedia_signs_ai_writing --out /tmp/glyphguard_score_smoke
python score_samples.py samples/wikipedia_signs_ai_writing --limit 10 --out /tmp/glyphguard_score_smoke_limitedThis repository contains the following files and folders:
glyphguard.py- Main application module with UI, text extraction, detection logic, scoring, cleaning and report export.README.md- Project overview and usage notes.score_samples.py- Headless scorer for files and folders.samples/- Bundled corpora used for tuning and regression checks.samples/wikipedia_signs_ai_writing/- Extracted Wikipedia example-block corpus.tests/test_glyphguard.py- Unit tests for core app and rule behaviours.tests/test_holistic_rules.py- One-test-per-rule suite for the holistic rules.tests/test_score_samples.py- Tests for the headless scorer.docs/- Screenshots, generated UI images and sample report pages.docs/generate_screenshots.py- Helper for regenerating documentation screenshots.CHANGELOG.md- User-visible change history.LICENSE- MIT licence text.
Preferences are saved to glyphguard_settings.json. The app first tries to write that file beside glyphguard.py; if that is not writable it falls back to ~/.glyphguard_settings.json.
Saved settings include:
- per-character detection toggles;
- replacement toggles and replacement overrides;
- detection-table visibility and category filters;
- panel collapsed/expanded state and remembered widths.
Delete the settings file to reset preferences.
The main rule data lives in these constants:
TRACKED_CHAR_SPECS- glyph-level detections and replacement defaults.HOLISTIC_AI_PATTERN_SPECS- holistic writing-pattern and tool-artefact rules.HOLISTIC_SOURCE_CATALOG- source metadata used by holistic rules.HOLISTIC_TAG_COLOURS- colour mapping for semantic tags.
The UI can export:
- PDF reports with summary scores, charts, glyph rows, holistic rule rows, original text and cleaned text.
- Plain-text reports with the same review information in a simpler form.
The scorer writes CSV and JSON output under results/ by default, or the folder passed with --out.
- Results are heuristic and can be wrong.
- The PDF extractor is small and stdlib-only. It works best on text-based PDFs and does not do OCR.
- DOCX, ODT and RTF extraction is best-effort visible-text extraction, not a full document renderer.
- It does not inspect images, screenshots, white-on-white text, CSS tricks or layout-level hiding.
- Some legitimate writing will hit AI-style rules. That is expected.
- A clean scan does not prove a document is safe, human-written or free from hidden intent.
Potential improvements:
- American/British/International English/Multilingual modes
- american mode picks up british stuff
- British/International English modes pick up american spelling and stuff
- British takes Welsh letters as No concern
- European discounts all European native languge characters (broad).
- Multilingual ignores all region based characters as No concern.
- rework report completely, utter garbage atm
- cleaner command-line output for batch scoring
- better generated screenshot flow
- richer sample corpus metadata
- need actual docs of different types with real examples
- need to find a good source of tagged AI stuff
- need to find a good detector of AI slop
- Webpage detector/scorer - Help avoid AI slop webpages quickly
- browser plugin, colud do taperscript?
- wonder how many pages have hidden chars (wonder if its a SEO way to get more traffic from LLMS lol)
- stronger extraction for awkward PDFs and document formats
- more file support
- AI code detection speific rules/mode (gotta look up research papers about this)
- more model/vendor specific detections.
- model/vendor guess when evidence is statistically strong enough.
- optional packaging or a requirements file
Contributions are welcome. Try to keep the app local and practical.
Holistic rules need to have sources or strong evidence of some kind, please reference your sources!
Please include tests and documentation updates when changing user-visible behaviour, adding rules etc.
Avoid uncommon heavyweight dependencies unless they are clearly justified.
Add an entry to TRACKED_CHAR_SPECS with the code point, visibility, description, example, replacement behaviour, keyboard equivalent, HTML/raw value and any kind or concern override. Then add or update tests in tests/test_glyphguard.py for detection, concern level and cleaning behaviour.
If the character is not explicitly tracked, check whether the derived Unicode metadata path already handles it. Add a curated entry when the character has a specific replacement, source, concern level or explanation that the fallback metadata cannot know.
Add a rule to HOLISTIC_AI_PATTERN_SPECS. Include:
nameregexweightseverityexamplesdescriptionsuggestionsourcestags
Use a stable snake_case name. Keep the regex narrow enough to explain. Add examples that show the intended match, and add negative tests for wording that should not match. If you use a new source key, add it to HOLISTIC_SOURCE_CATALOG. If you use a new tag, add it to HOLISTIC_TAG_COLOURS. Add an explicit test in tests/test_holistic_rules.py, including overlap guards where nearby rules might also match.
Keep one source block per .txt file. Use clear filenames with an index, block type and source slug. If possible, add a manifest. Then run:
python score_samples.py samples/<corpus_name> --out resultsReview file_scores.csv, rules_by_file.csv, rule_frequency.csv and file_scores.json before committing. If a rule change deliberately shifts scores across the corpus, mention that in the changelog.
Update tests when changing glyph rules, holistic rules, sample extraction, score output or overlap behaviour. Rule changes should include positive examples, negative examples and overlap guards where nearby rules might also match.
These references are used as provenance for review heuristics, background or related materials.
- Wikipedia: Signs of AI writing -
wikipedia_signs_ai_writing: Secondary source and community field guide for prose-level AI writing signs. It frames signs as indicators to combine with context, not deterministic proof. - OpenAI Harmony response format -
openai_harmony_docs: OpenAI documentation for Harmony message formatting, channels, tool calls and browser citation markers. - OpenAI citation formatting guide -
openai_citation_formatting_docs: OpenAI documentation for private-use citation markers, source reference IDs and optional line or block locators. - OpenAI File Search guide -
openai_file_search_docs: OpenAI documentation showing file-search citation annotations, file IDs, filenames and raw API metadata. - Stack Overflow: OpenAI Assistants placeholder report -
stackoverflow_openai_assistants_placeholder: Community report showing streamed OpenAI Assistants/File Search placeholder leaks. - Observed ChatGPT contentReference leak -
observed_chatgpt_content_reference: Empirical UI/copy-paste artefact observed in copied ChatGPT or markdown output. This is an observed/local source, not an external URL. - Anthropic Claude citations documentation -
anthropic_claude_citations_docs: Anthropic documentation for Claude citation blocks and fields. - Google Gemini grounding documentation -
google_gemini_grounding_docs: Google Gemini documentation for groundingMetadata, webSearchQueries and groundingChunks. - Perplexity Sonar features documentation -
perplexity_sonar_features_docs: Perplexity documentation showing search/citation metadata fields. - OpenAI: Where the goblins came from -
openai_goblin_article: OpenAI investigation into GPT-5.1+, GPT-5.4 Nerdy personality and GPT-5.5/Codex creature-metaphor tics. - TidBITS Talk: ChatGPT's Goblin Obsession Evades OpenAI's Fixes -
tidbits_gpt55_prompt_discussion: Discussion of Codex/GPT-5.5 prompt constraints and creature-metaphor examples.
- Promptfoo: Hidden Unicode attacks - useful background on hidden Unicode and invisible prompt-injection characters. This is background for the README rather than a current
HOLISTIC_SOURCE_CATALOGentry.
See CHANGELOG.md for release notes. Record user-visible detection changes, corpus updates, scoring changes and README/test structure changes there.
This project is distributed under the MIT License. See LICENSE for details.



