humanizer-ru 3.21.0 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # humanizer-ru
2
2
 
3
- > [Русская версия: основная](README.md)
3
+ > [Русская версия: основная](README.md) · [中文](README.zh.md)
4
4
 
5
5
  Claude Code / Cowork plugin. Kills AI smell in Russian text. The English [humanizer](https://github.com/blader/humanizer) won't help here. Russian AI markers are their own beast: bureaucratic noun-chains (канцелярит), English-syntax calques, missing particles like "же" and "ведь" that make Russian sound alive.
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
- [![Version](https://img.shields.io/badge/version-3.21.0-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
8
+ [![Version](https://img.shields.io/badge/version-3.22.0-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
9
9
  [![Stars](https://img.shields.io/github/stars/ilyautov/humanizer-ru?style=social)](https://github.com/ilyautov/humanizer-ru/stargazers)
10
10
  [![skills.sh](https://skills.sh/b/ilyautov/humanizer-ru)](https://skills.sh/ilyautov/humanizer-ru/humanizer-ru)
11
11
  [![npm](https://img.shields.io/npm/v/humanizer-ru?label=npm%20%C2%B7%20dsh)](https://www.npmjs.com/package/humanizer-ru)
12
+ [![HOL](https://img.shields.io/endpoint?url=https%3A%2F%2Fhol.org%2Fapi%2Fregistry%2Fbadges%2Fplugin%3Fslug%3Dilya-utov%252Fhumanizer-ru%26metric%3Dstatus%26style%3Dflat)](https://hol.org/registry/plugins/ilya-utov%2Fhumanizer-ru)
12
13
 
13
14
  <p align="center">
14
15
  <a href="https://humanizer-ru.aifrontier.tech/">
@@ -18,14 +19,20 @@ Claude Code / Cowork plugin. Kills AI smell in Russian text. The English [humani
18
19
 
19
20
  🧪 **Live demo:** [humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/#audit). Paste a Russian text, get a 0-100 cleanliness score and highlighted markers. Same scanner as in the skill, runs in the browser.
20
21
 
21
- **Quick start** in Claude Code, two commands:
22
+ **Quick start**, one command for any agent (Claude Code, Cursor, Codex, Copilot, Cline and dozens more):
23
+
24
+ ```
25
+ npx skills add ilyautov/humanizer-ru
26
+ ```
27
+
28
+ The CLI detects your installed agents and asks where to put the skill. In Claude Code you can also install it as a plugin, then updates arrive via `/plugin`:
22
29
 
23
30
  ```
24
31
  /plugin marketplace add ilyautov/humanizer-ru
25
32
  /plugin install humanizer-ru@ilyautov-plugins
26
33
  ```
27
34
 
28
- Cursor, Copilot, Cline and other agents: `npx skills add https://github.com/ilyautov/humanizer-ru/tree/main/skills/humanizer-ru`. Claude.ai, Codex, DeepSeek Harness and team rollout are covered in [Install](#install).
35
+ Claude.ai, DeepSeek Harness and team rollout are covered in [Install](#install).
29
36
 
30
37
  📖 **Docs & write-ups (RU):** [humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/): do AI detectors work on Russian, the 64 markers, plagiarism vs AI detection.
31
38
 
package/README.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  > Скилл для Claude. Убирает 64 признака нейросети в русском тексте: канцелярит, кальки, фингерпринты ChatGPT и Claude. Метит в то, что измеряют GPTZero, DivEye, RuBERT: поднимает perplexity и burstiness. 21 жёстких банов, quad-pass аудит, калибровка под голос автора, eval-харнес с метриками до/после. Живое демо сканера на сайте.
4
4
 
5
- > [English version](README.en.md)
5
+ > [English version](README.en.md) · [中文](README.zh.md)
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
- [![Версия](https://img.shields.io/badge/версия-3.21.0-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
8
+ [![Версия](https://img.shields.io/badge/версия-3.22.0-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
9
9
  [![Звёзды](https://img.shields.io/github/stars/ilyautov/humanizer-ru?style=social)](https://github.com/ilyautov/humanizer-ru/stargazers)
10
10
  [![skills.sh](https://skills.sh/b/ilyautov/humanizer-ru)](https://skills.sh/ilyautov/humanizer-ru/humanizer-ru)
11
11
  [![npm](https://img.shields.io/npm/v/humanizer-ru?label=npm%20%C2%B7%20dsh)](https://www.npmjs.com/package/humanizer-ru)
12
+ [![HOL](https://img.shields.io/endpoint?url=https%3A%2F%2Fhol.org%2Fapi%2Fregistry%2Fbadges%2Fplugin%3Fslug%3Dilya-utov%252Fhumanizer-ru%26metric%3Dstatus%26style%3Dflat)](https://hol.org/registry/plugins/ilya-utov%2Fhumanizer-ru)
12
13
 
13
14
  <p align="center">
14
15
  <a href="https://humanizer-ru.aifrontier.tech/">
@@ -23,14 +24,20 @@
23
24
 
24
25
  🧪 **Живое демо:** [humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/#audit). Вставьте свой текст, получите оценку чистоты 0-100 и подсветку найденных оборотов. Тот же сканер, что в скилле, считается в браузере.
25
26
 
26
- **Быстрый старт** в Claude Code, две команды:
27
+ **Быстрый старт**, одна команда для любого агента (Claude Code, Cursor, Codex, Copilot, Cline и ещё десятки):
28
+
29
+ ```
30
+ npx skills add ilyautov/humanizer-ru
31
+ ```
32
+
33
+ CLI найдёт установленных агентов и спросит, куда ставить. В Claude Code можно и плагином, тогда обновления приходят через `/plugin`:
27
34
 
28
35
  ```
29
36
  /plugin marketplace add ilyautov/humanizer-ru
30
37
  /plugin install humanizer-ru@ilyautov-plugins
31
38
  ```
32
39
 
33
- Cursor, Copilot, Cline и другие агенты: `npx skills add https://github.com/ilyautov/humanizer-ru/tree/main/skills/humanizer-ru`. Claude.ai, Codex, DeepSeek Harness и раскатка на команду в [разделе «Установка»](#установка).
40
+ Claude.ai, DeepSeek Harness и раскатка на команду в [разделе «Установка»](#установка).
34
41
 
35
42
  📖 **Документация и разборы:** [humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/): [работают ли AI-детекторы на русском](https://humanizer-ru.aifrontier.tech/ai-detektory-na-russkom.html), [64 признака AI-текста](https://humanizer-ru.aifrontier.tech/52-priznaka-ai-teksta.html), [антиплагиат и нейросеть](https://humanizer-ru.aifrontier.tech/antiplagiat-i-neyroset.html).
36
43
 
package/README.zh.md ADDED
@@ -0,0 +1,99 @@
1
+ # humanizer-ru
2
+
3
+ > [Русская версия: основная](README.md) · [English](README.en.md)
4
+
5
+ 面向 Claude Code、Cursor、Codex、Gemini CLI 等编程智能体的开源 Skill:去除俄语文本中的 AI 痕迹,让机器生成的俄语读起来像俄罗斯人写的。MIT 许可,无订阅,无 API Key,在你已经在用的智能体里直接运行。
6
+
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+ [![Stars](https://img.shields.io/github/stars/ilyautov/humanizer-ru?style=social)](https://github.com/ilyautov/humanizer-ru/stargazers)
9
+ [![skills.sh](https://skills.sh/b/ilyautov/humanizer-ru)](https://skills.sh/ilyautov/humanizer-ru/humanizer-ru)
10
+
11
+ 🧪 **在线试用,无需安装:** [humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/#audit)。粘贴一段俄语,得到 0 到 100 的「干净度」评分和被标出的 AI 套话。扫描器在浏览器里运行,文本不会上传。
12
+
13
+ ## 给谁用
14
+
15
+ - 跨境电商团队:Ozon、Wildberries、Yandex Market、速卖通俄区的商品详情、店铺公告、客服回复。用 DeepSeek、豆包、GPT 生成的俄语文案,俄罗斯买家一眼就看出是机器写的,转化和评分都会受影响。
16
+ - 出海企业的市场和公关:官网、新闻稿、宣传册、社媒帖子的俄语版本。
17
+ - 本地化和翻译团队:机器翻译或 LLM 初稿之后的润色,让文本符合俄语母语者的阅读习惯。
18
+ - 用俄语写邮件、报告、论文的任何人。
19
+
20
+ ## 为什么俄语需要单独的工具
21
+
22
+ 所有大模型都是「经由英语」生成俄语的。于是俄语 AI 文本的破绽和英语的不一样:英语式句法的直译(калька)、一句一个「является」、堆叠的名词链公文体(канцелярит)、丢失的语气小词「же」「ведь」「вот」、每隔一句一个长破折号。英语的 humanizer 看不到这些,中文的「去 AI 味」工具也不处理俄语。
23
+
24
+ 这个 Skill 收录了 **64 个俄语 AI 特征**(分 14 类)和 **21 条硬性禁用结构**,每一条都写成编辑规则,而不是简单的词表。附带一个确定性的扫描器,把修改变成可度量的事:「原来 9 分,现在 82 分」。
25
+
26
+ ## 一分钟安装
27
+
28
+ 任何智能体一条命令(Claude Code、Cursor、Codex、Copilot、Cline 等几十种):
29
+
30
+ ```
31
+ npx skills add ilyautov/humanizer-ru
32
+ ```
33
+
34
+ CLI 会检测本机已安装的智能体并询问装到哪里。Claude Code 也可以作为插件安装,之后通过 `/plugin` 更新:
35
+
36
+ ```
37
+ /plugin marketplace add ilyautov/humanizer-ru
38
+ /plugin install humanizer-ru@ilyautov-plugins
39
+ ```
40
+
41
+ DeepSeek Harness:
42
+
43
+ ```
44
+ dsh plugin --profile web add humanizer-ru
45
+ ```
46
+
47
+ Claude.ai 网页版:下载 [humanizer-ru.zip](https://github.com/ilyautov/humanizer-ru/releases/latest/download/humanizer-ru.zip),在 Settings → Capabilities → Skills 里上传。其他安装方式见[俄语 README](README.md#установка)。
48
+
49
+ ## 怎么用
50
+
51
+ 装好之后,对智能体说(俄语或中文都可以,但文本本身必须是俄语):
52
+
53
+ - 「очеловечь этот текст」或「把这段俄语改得像人写的」:完整改写,五步流程,保留原文事实。
54
+ - 「проверь текст на следы нейросети」或「检查这段俄语的 AI 痕迹」:只做审计,列出发现的特征和优先级,不改写。
55
+ - 「убери канцелярит」:只处理指定的一类问题。
56
+
57
+ 也可以在命令行直接跑扫描器(需要 Python 3 和 `pip install razdel pymorphy3`):
58
+
59
+ ```
60
+ python3 skills/humanizer-ru/scripts/scan.py текст.txt
61
+ python3 skills/humanizer-ru/scripts/scan.py после.txt --before до.txt
62
+ ```
63
+
64
+ 第二条命令是「事实锁」:比较改写前后的文本,列出凭空出现或丢失的数字、日期、人名、链接和断言。编造的数据比任何套话都糟糕。
65
+
66
+ ## 里面有什么
67
+
68
+ - 64 个特征、14 个类别:公文体、英语直译、情感空洞、说服套路、信息节奏、模糊限定词,以及 2025 到 2026 年新出现的模型指纹(单词成句的「碎片式冥想」、伪苏格拉底式自问自答、每个列表项一个装饰性 emoji、伪心理治疗口吻、无生命主语、结尾说教等)。
69
+ - 21 条硬性禁用:出现即删除的结构,包括长破折号、「не просто X, а Y」、「в современном мире」、「играет ключевую роль」。
70
+ - 扫描器:0 到 100 的干净度评分,按类别列出命中,句长节奏、名词动词比、Markdown 感知(代码块和引用不计入)。学术、法律、文学文本有单独的体裁模式 `--genre`,否则正式文本会被误判。
71
+ - 作者声音校准:给几篇你自己写的俄语文本,Skill 会按你的节奏和用词改写,而不是改成千篇一律的「人味」。
72
+ - 四轮审计:检测器视角、路人视角、句子「心电图」、列表「骨架」。
73
+
74
+ ## 数据校准
75
+
76
+ 规则不是凭感觉定的。目录在三个公开语料上校准过,共 **5.5 万篇**带人机标注的俄语文本,包括 GigaChat 和 YandexGPT 的输出:
77
+
78
+ | 语料 | 规模 | 发现 |
79
+ |---|---|---|
80
+ | AINL-Eval 2025 | 35 158 篇学术摘要 | 严格模式下规则命中人类作者的比例不低于模型,「является」和「данный」在学术体裁里是正常用法,因此加入了体裁过滤 |
81
+ | M4 | 5 759 对 | 社交媒体和百科文本,gpt-3.5 时代的指纹 |
82
+ | LLMTrace | 14 763 篇,30 个生成器 | 长破折号更取决于体裁而非作者;俄罗斯模型(GigaChat-Max 77%、YandexGPT-5 74%)比 GPT 更爱用破折号;人类每百词 0.29 个特征,模型 0.67 个 |
83
+
84
+ 所有数字可以复现:校准脚本和报告在 `eval/` 目录,语料是公开的。
85
+
86
+ ## 它不做什么
87
+
88
+ - 不用来骗过 AI 检测器。不插入错别字,不用拉丁字母替换西里尔字母。为了通过检测而损坏文本,是在欺骗读者。目标只有一个:写出俄罗斯人真的会写的文本。
89
+ - 不编造细节。原文没有数字和案例,Skill 就用节奏、语气和结构让文本活起来,或者问你要材料。
90
+ - 不判定作者身份。扫描器的分数是修改的依据,不是「这是 AI 写的」的裁决。
91
+
92
+ ## 链接
93
+
94
+ - 源码:[github.com/ilyautov/humanizer-ru](https://github.com/ilyautov/humanizer-ru)
95
+ - 网站与在线扫描:[humanizer-ru.aifrontier.tech](https://humanizer-ru.aifrontier.tech/)
96
+ - 更新日志:[CHANGELOG.md](CHANGELOG.md)
97
+ - 引用的研究来源:[SOURCES.md](SOURCES.md)
98
+
99
+ 作者:Ilya Utov([GitHub](https://github.com/ilyautov))。欢迎 issue 和 PR,包括中文的。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanizer-ru",
3
- "version": "3.21.0",
3
+ "version": "3.22.0",
4
4
  "description": "Agent skill for DeepSeek Harness: rewrites Russian text to remove 64 markers of AI generation (bureaucratese, calques, ChatGPT fingerprints), with a corpus-calibrated scanner, audit mode and author-voice calibration.",
5
5
  "license": "MIT",
6
6
  "author": "Ilya Utov",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: humanizer-ru
3
- description: "Скилл для очеловечивания русскоязычного текста. Убирает признаки AI-генерации, делает текст живым. Используй ВСЕГДА, когда пользователь просит: очеловечить текст, убрать следы нейросети, сделать текст живым/естественным, переписать как человек, humanize на русском, убрать канцелярит, убрать водянистость, сделать текст менее формальным. Также используй если пользователь вставляет русскоязычный текст и говорит что-то вроде 'перепиши', 'сделай лучше', 'звучит как робот', 'слишком искусственно'. Работает ТОЛЬКО с русским языком. Для английского используй оригинальный humanizer. НЕ используй для: перевод, написание с нуля, грамматика, код."
3
+ description: "Скилл для очеловечивания русскоязычного текста. Убирает признаки AI-генерации, делает текст живым. Используй ВСЕГДА, когда пользователь просит: очеловечить текст, убрать следы нейросети, сделать текст живым/естественным, переписать как человек, humanize на русском, убрать канцелярит, убрать водянистость, сделать текст менее формальным. Также используй если пользователь вставляет русскоязычный текст и говорит что-то вроде 'перепиши', 'сделай лучше', 'звучит как робот', 'слишком искусственно'. Работает ТОЛЬКО с русским языком. Для английского используй оригинальный humanizer. НЕ используй для: перевод, написание с нуля, грамматика, код. Триггеры на других языках, если сам текст русский: 'humanize this Russian text', 'make this Russian sound natural', '把这段俄语改得像人写的', '去除俄语文本的 AI 味', '检查俄语的 AI 痕迹'."
4
4
  license: MIT
5
5
  ---
6
6
 
7
- # Humanizer-RU v3.21.0
7
+ # Humanizer-RU v3.22.0
8
8
 
9
9
  Ты редактор. Превращаешь стерильный AI-текст в живую русскую речь. Убираешь маркеры нейросети и возвращаешь в текст автора: мнение, ритм, характер.
10
10
 
@@ -150,7 +150,7 @@ license: MIT
150
150
 
151
151
  **Шаг 3. Переписывание по разметке.** Работай по светофору из Шага 1: красные абзацы перепиши целиком, жёлтые правь точечно, зелёные не трогай. В красных и жёлтых абзацах: сначала убери HARD BANS, затем пройдись контрастным вычитанием (в каждом предложении замени самое предсказуемое слово на менее вероятное, но уместное). На каждый усилитель, вводное и образ гони двойной гейт сразу на этом шаге, не при финальной вычитке: тест на удаление (убрал, смысл не изменился = вода, паттерны #3/#40, удаляй) и тест на обналичивание (какой референт? паттерн #53). Одновременно добавляй голос и варьируй структуру. Сверяйся с паспортом голоса.
152
152
 
153
- > **Факт-замок (приоритет над любым паттерном).** Факты исходника (числа, даты, имена, названия, проценты, единицы) переносятся без искажений. Добавлять факты, которых в исходнике НЕТ (цифры, исследования, кейсы, «у себя в команде вижу...»), запрещено: конкретика для паттернов 1-2 и секции «Как звучит живой текст» берётся ТОЛЬКО из исходника или из того, что явно сообщил пользователь. В исходнике нет конкретики? Оживляй голосом, ритмом и структурой, либо спроси пользователя, чем наполнить. Выдуманная цифра хуже канцелярита: канцелярит палит стиль, выдумка делает текст ложью. В примерах документации пометка «(факты автора)» в заголовке означает: конкретику в этом «После» дал автор текста, а не придумал редактор. Пример без такой пометки новых фактов не содержит, и гейт `scripts/check_examples.py` это проверяет. Есть исходник и результат в файлах и можно запускать команды? Проверь замок механически: `python3 <папка скилла>/scripts/scan.py стало.txt --before было.txt`. Сканер печатает «было N, стало M» по чистоте и перечисляет числа, даты, имена, ссылки и код, которые пропали или появились. Новый факт без источника это ошибка правки, а не находка.
153
+ > **Факт-замок (приоритет над любым паттерном).** Факты исходника (числа, даты, имена, названия, проценты, единицы) переносятся без искажений. Добавлять факты, которых в исходнике НЕТ (цифры, исследования, кейсы, «у себя в команде вижу...»), запрещено: конкретика для паттернов 1-2 и секции «Как звучит живой текст» берётся ТОЛЬКО из исходника или из того, что явно сообщил пользователь. В исходнике нет конкретики? Оживляй голосом, ритмом и структурой, либо спроси пользователя, чем наполнить. Выдуманная цифра хуже канцелярита: канцелярит палит стиль, выдумка делает текст ложью. В примерах документации пометка «(факты автора)» в заголовке означает: конкретику в этом «После» дал автор текста, а не придумал редактор. Пример без такой пометки новых фактов не содержит, и гейт `scripts/check_examples.py` это проверяет. Есть исходник и результат в файлах и можно запускать команды? Проверь замок механически: `python3 <папка скилла>/scripts/scan.py стало.txt --before было.txt`. Сканер печатает «было N, стало M» по чистоте и перечисляет числа, даты, имена, ссылки и код, которые пропали или появились. Новый факт без источника это ошибка правки, а не находка. Отдельно береги слова, которые звучат как пафос, а несут факт: «первый в России», «единственный», «впервые», «рекордный», «до сих пор». Срезать «первый в России сервис» до «сервис» значит исказить, а не оживить: выбрасывай пустое усиление («по-настоящему», «крайне важно»), а не само утверждение. Сканер считает такие кванторы отдельно и предупреждает, если они пропали или появились.
154
154
 
155
155
  > **ВНИМАНИЕ: гомогенизация.** LLM при переписывании склонен удалять разговорные обороты, анекдоты, личные примеры, заменяя их нейтральными формулировками (+70% нейтральных эссе при использовании LLM). Даже промпт «только грамматика» меняет семантику. Если в оригинале есть личная история, специфический оборот, разговорная фраза - СОХРАНИ их. Не заменяй живое на нейтральное. Одинаковая трансформация всех текстов через один инструмент создаёт «гуманизированный» стиль, который сам по себе детектируется (DAMAGE). Варьируй подход: разные тексты переписывай по-разному.
156
156
 
@@ -158,7 +158,7 @@ license: MIT
158
158
 
159
159
  Проход 1, «Детектор»: перечитай черновик, ищи остатки каждой из 14 категорий паттернов (A-N). Достаточно 30 секунд на категорию.
160
160
 
161
- Проход 2, «Человек с улицы»: забудь что ты редактор. Прочитай текст как случайный читатель в ленте. Вопрос: «Увидев этот текст без контекста, подумал бы я, что его писала нейросеть?» Если да - найди что именно выдаёт и исправь.
161
+ Проход 2, «Человек с улицы»: забудь что ты редактор. Прочитай текст как случайный читатель в ленте. Вопрос: «Увидев этот текст без контекста, подумал бы я, что его писала нейросеть?» Если да - найди что именно выдаёт и исправь. Второй вопрос того же прохода: «Это ещё автор или уже обезличенная версия автора?» Если второе, верни ему его манеру: повтор, длинную фразу, сомнение, странную деталь.
162
162
 
163
163
  Красные флаги второго прохода:
164
164
  - Слишком гладко, нет ни одной шероховатости
@@ -174,7 +174,7 @@ license: MIT
174
174
 
175
175
  Проход 5, «Обналичивание» (только для экспертных и технических текстов): пройди по всем образам и терминам, которые утверждают механизм. Каждый должен обналичиваться в конкретный ответ на «что именно происходит?» (паттерн #53). Пустой = переписать в механизм, неверный = исправить механизм, плавающий = заякорить одной формой в точке ввода. Для остальных жанров отдельный проход не нужен: хватает гейта из Шага 3.
176
176
 
177
- **Шаг 5.** Отдай финальный текст и короткий список ключевых изменений (3-5 пунктов).
177
+ **Шаг 5.** Отдай финальный текст и короткий список ключевых изменений (3-5 пунктов). Добавь одну строку «Не тронуто: ...» про то, что оставил намеренно: повтор слова, длинную фразу, авторское тире, зелёные абзацы. Читатель должен видеть, что нетронутое место это решение, а не пропуск.
178
178
 
179
179
  ---
180
180
 
@@ -347,7 +347,7 @@ davinci-003). Метод и полные таблицы: `eval/AINL-CALIBRATION.
347
347
 
348
348
  **Обрыв на полуслове:** последнее предложение не закончено, текст упёрся в лимит токенов
349
349
 
350
- **Технические маркеры:** латинские символы вместо кириллических (c, o, a, e, p), «невидимые» символы (zero-width, word joiner, bidi-метки: клавиатурой не набираются), идеальная типографика в неформальном тексте
350
+ **Технические маркеры:** латинские символы вместо кириллических (c, o, a, e, p), «невидимые» символы (zero-width, word joiner, bidi-метки, узкий неразрывный пробел U+202F: клавиатурой не набираются), идеальная типографика в неформальном тексте
351
351
 
352
352
  **Артефакты копипасты:** технические следы копирования из интерфейса чат-бота, однозначные улики: `:contentReference[oaicite:N]`, `oai_citation`, `?utm_source=chatgpt.com`/`=openai` в ссылках, `grok_card://`, `attached_file://`, `vertexaisearch`, метки `turnNsearchN`/`turnNfileN`/`citeturn...`, `【N†source】`, `[citation:N]`, ссылки `](sandbox:/mnt/data/...)`, невидимые символы U+E200-E204, остатки `</think>`. В человеческом тексте не встречаются: нашёл хотя бы один = текст вставлен из чат-бота, проверяй целиком
353
353
 
@@ -18,6 +18,14 @@
18
18
  Мелкие количества («два», «половина», «вдвое») факты мягкие: в русском они
19
19
  идиоматичны («с одной стороны») и обычно пересказывают число исходника. Они
20
20
  считаются, но не валят проверку.
21
+
22
+ Утверждения: слова-кванторы, которые звучат как пафос, а несут факт:
23
+ «первый в России», «единственный», «впервые», «рекордный», «до сих пор».
24
+ Срезать «первый в России сервис» до «сервис» значит исказить исходник, а
25
+ не оживить его. Потерянный квантор попадает в список потерь, появившийся
26
+ выносится отдельным предупреждением: кванторы идиоматичны («первый шаг»,
27
+ «самое время»), поэтому валить проверку автоматически нельзя, но глазами
28
+ такое место обязано проверить.
21
29
  """
22
30
 
23
31
  from __future__ import annotations
@@ -45,7 +53,7 @@ TOKEN_RE = re.compile(r"[A-Za-z][A-Za-z\d\-]*|[А-Яа-яЁё][А-Яа-яЁё\-]
45
53
 
46
54
  SOFT_QUANTITIES = {
47
55
  "один", "два", "две", "три", "оба", "обе", "пара",
48
- "первый", "второй", "третий",
56
+ "второй", "третий",
49
57
  "вдвое", "втрое", "дважды", "трижды",
50
58
  "половина", "треть", "четверть", "полтора",
51
59
  }
@@ -56,6 +64,12 @@ HARD_NUMERALS = {
56
64
  "девяносто", "сто", "двести", "триста", "тысяча", "миллион", "миллиард",
57
65
  "десяток", "сотня", "дюжина",
58
66
  }
67
+ # Кванторы-утверждения: громкие слова, за которыми стоит проверяемый факт.
68
+ CLAIM_WORDS = {
69
+ "первый", "единственный", "впервые", "самый", "рекордный", "крупнейший",
70
+ "старейший", "никогда", "никто", "навсегда", "беспрецедентный",
71
+ }
72
+ CLAIM_PHRASE_RE = re.compile(r"\bдо сих пор\b|\bв мире\b|\bв россии\b", re.IGNORECASE)
59
73
  # Одна сущность под разными именами не считается новым фактом.
60
74
  ALIASES = {
61
75
  "ai": ("искусственный", "интеллект", "ии", "нейросеть", "модель"),
@@ -69,6 +83,7 @@ class Facts:
69
83
  hard: set[str] = field(default_factory=set) # "число:13", "имя:стэнфорд", "ссылка:…"
70
84
  soft: set[str] = field(default_factory=set) # "два", "половина"
71
85
  words: set[str] = field(default_factory=set) # все слова в нормальной форме, для алиасов
86
+ claims: set[str] = field(default_factory=set) # "утверждение:первый", "утверждение:до сих пор"
72
87
 
73
88
 
74
89
  @dataclass
@@ -77,6 +92,7 @@ class FactsDiff:
77
92
  added: list[str] # появились в результате, в исходнике не было
78
93
  kept: int # жёстких фактов исходника дожило
79
94
  soft_added: list[str]
95
+ claims_added: list[str] = field(default_factory=list) # кванторы, которых в исходнике не было
80
96
 
81
97
  @property
82
98
  def ok(self) -> bool:
@@ -84,7 +100,8 @@ class FactsDiff:
84
100
 
85
101
  def as_dict(self) -> dict:
86
102
  return {"ok": self.ok, "lost": self.lost, "added": self.added,
87
- "kept": self.kept, "soft_added": self.soft_added}
103
+ "kept": self.kept, "soft_added": self.soft_added,
104
+ "claims_added": self.claims_added}
88
105
 
89
106
 
90
107
  def _norm(word: str) -> str:
@@ -126,6 +143,8 @@ def extract_facts(text: str) -> Facts:
126
143
  body = CODE_RE.sub(" ", URL_RE.sub(" ", text))
127
144
  for m in MONTH_RE.finditer(body):
128
145
  f.hard.add("месяц:" + m.group(1).lower())
146
+ for m in CLAIM_PHRASE_RE.finditer(body):
147
+ f.claims.add("утверждение:" + m.group(0).lower().replace("ё", "е"))
129
148
  starts = _sentence_starts(body)
130
149
  for m in TOKEN_RE.finditer(body):
131
150
  tok = m.group(0)
@@ -138,6 +157,8 @@ def extract_facts(text: str) -> Facts:
138
157
  f.words.add(norm)
139
158
  if norm in SOFT_QUANTITIES:
140
159
  f.soft.add(norm)
160
+ elif norm in CLAIM_WORDS:
161
+ f.claims.add("утверждение:" + norm)
141
162
  elif norm in HARD_NUMERALS:
142
163
  f.hard.add("число словами:" + norm)
143
164
  elif _is_proper(tok, any(abs(m.start() - s) <= 1 for s in starts)):
@@ -154,10 +175,11 @@ def _alias_covered(fact: str, before_words: set[str]) -> bool:
154
175
 
155
176
  def diff_facts(before: str, after: str) -> FactsDiff:
156
177
  b, a = extract_facts(before), extract_facts(after)
157
- lost = sorted(b.hard - a.hard)
178
+ lost = sorted(b.hard - a.hard) + sorted(b.claims - a.claims)
158
179
  added = sorted(x for x in a.hard - b.hard if not _alias_covered(x, b.words))
159
180
  return FactsDiff(lost=lost, added=added, kept=len(b.hard & a.hard),
160
- soft_added=sorted(a.soft - b.soft))
181
+ soft_added=sorted(a.soft - b.soft),
182
+ claims_added=sorted(a.claims - b.claims))
161
183
 
162
184
 
163
185
  def facts_verdict(d: FactsDiff) -> str:
@@ -165,4 +187,7 @@ def facts_verdict(d: FactsDiff) -> str:
165
187
  return f"✗ новых фактов без источника: {len(d.added)} (выдумка хуже канцелярита)"
166
188
  if d.lost:
167
189
  return f"⚠ факты исходника на месте, но {len(d.lost)} потеряно: проверьте, намеренно ли"
190
+ if d.claims_added:
191
+ return (f"⚠ факты целы, но появилось утверждений без источника: {len(d.claims_added)} "
192
+ "(«впервые», «единственный» это факт, а не украшение)")
168
193
  return f"✓ факт-замок цел: {d.kept} фактов исходника перенесено, новых нет"
@@ -189,10 +189,13 @@ SCANNER: dict[str, list[str | tuple[str, str]]] = {
189
189
  ("латиница внутри кириллического слова", r"(?<=[а-яё])[a-z](?=[а-яё])"),
190
190
  # zero-width (ZWSP/ZWNJ/ZWJ/BOM) + word joiner, Mongolian vowel
191
191
  # separator, bidi-метки и изоляторы: клавиатурой не набираются,
192
- # в живом русском тексте не встречаются.
192
+ # в живом русском тексте не встречаются. Плюс узкий неразрывный
193
+ # пробел U+202F: его ставят o3/o4-mini и GPT-5 перед единицами, на
194
+ # LLMTrace 0 из 6 392 человеческих текстов. Обычный U+00A0 сюда
195
+ # НЕ входит: у людей он в 10 раз чаще (вёрстка, редактура).
193
196
  ("невидимые символы (zero-width, bidi)",
194
197
  "[​‌‍⁠᠎‎‏"
195
- "‪-‮⁦-⁩؜]"),
198
+ "‪-‮⁦-⁩؜ ]"),
196
199
  ],
197
200
  # Паттерн #55: подлежащее-предмет при глаголе намерения. Английские
198
201
  # источники описывают то же самое как смену AI-словаря на emphasizing /
@@ -192,6 +192,8 @@ def main() -> int:
192
192
  print(f" ✗ новое: {x}")
193
193
  for x in fdiff.lost:
194
194
  print(f" ⚠ потеряно: {x}")
195
+ for x in fdiff.claims_added:
196
+ print(f" ⚠ утверждение появилось: {x}")
195
197
  if fdiff.soft_added:
196
198
  print(f" ℹ мелкие количества появились: {', '.join(fdiff.soft_added)} "
197
199
  "(идиомы и пересказ чисел не считаются, проверьте глазами)")