humanizer-ru 3.19.1 → 3.19.2
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
|
@@ -5,7 +5,7 @@
|
|
|
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)
|
|
8
|
-
[](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
|
|
9
9
|
[](https://github.com/ilyautov/humanizer-ru/stargazers)
|
|
10
10
|
[](https://skills.sh/ilyautov/humanizer-ru/humanizer-ru)
|
|
11
11
|
[](https://www.npmjs.com/package/humanizer-ru)
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> [English version](README.en.md)
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
|
-
[](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
|
|
9
9
|
[](https://github.com/ilyautov/humanizer-ru/stargazers)
|
|
10
10
|
[](https://skills.sh/ilyautov/humanizer-ru/humanizer-ru)
|
|
11
11
|
[](https://www.npmjs.com/package/humanizer-ru)
|
|
@@ -133,12 +133,6 @@ cp -r humanizer-ru/skills/humanizer-ru ~/.codex/skills/
|
|
|
133
133
|
вызов: `$humanizer-ru` или автоматически по триггер-фразам («очеловечь»,
|
|
134
134
|
«убери канцелярит»). Проектная установка: та же папка в `.codex/skills/` внутри репозитория.
|
|
135
135
|
|
|
136
|
-
Или клонированием:
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
git clone https://github.com/ilyautov/humanizer-ru.git ~/.claude/skills/humanizer-ru
|
|
140
|
-
```
|
|
141
|
-
|
|
142
136
|
### Проверка установочного пакета
|
|
143
137
|
|
|
144
138
|
Перед релизом соберите ZIP тем же скриптом, который использует CI, затем
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "humanizer-ru",
|
|
3
|
-
"version": "3.19.
|
|
3
|
+
"version": "3.19.2",
|
|
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",
|
|
@@ -4,7 +4,7 @@ description: "Скилл для очеловечивания русскоязы
|
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Humanizer-RU v3.19.
|
|
7
|
+
# Humanizer-RU v3.19.2
|
|
8
8
|
|
|
9
9
|
Ты редактор. Превращаешь стерильный AI-текст в живую русскую речь. Убираешь маркеры нейросети и возвращаешь в текст автора: мнение, ритм, характер.
|
|
10
10
|
|
|
@@ -77,7 +77,7 @@ license: MIT
|
|
|
77
77
|
|
|
78
78
|
> **Машинная половина аудита.** Со скиллом едет детерминированный сканер: `scripts/scan.py` рядом с этим SKILL.md (в полном клоне репозитория: `skills/humanizer-ru/scripts/scan.py`). Он точно считает то, что LLM мерит на глаз: HARD BANS, 21 категорию маркеров, ритм предложений, морфологию. Если в среде можно запускать команды и есть python3 с пакетами `razdel` и `pymorphy3`, сначала прогони текст через него: `python3 <папка скилла>/scripts/scan.py файл.txt`, либо `echo "текст" | python3 <папка скилла>/scripts/scan.py -` (флаг `--json` для машинного вывода). Текст не маркетинговый? Добавь `--genre academic`, `--genre legal` или `--genre fiction`: флаг снимает маркеры, законные для регистра, иначе научный и юридический текст тонет в ложных срабатываниях (цифры в секции «Ложные срабатывания»). Вывод сканера бери как базу, поверх добавляй то, что скрипту не по зубам: ложные срабатывания (см. одноимённую секцию), нелексические паттерны, приоритеты A-D. Пакетов нет? Предложи пользователю однократный `pip install razdel pymorphy3` и проводи аудит по каталогу вручную. Сканер недоступен или упал: молча работай по каталогу, как обычно. Аудит обязан состояться при любом раскладе.
|
|
79
79
|
|
|
80
|
-
> **Score чистоты (0-100).** Сканер выдаёт сводную оценку `ЧИСТОТА: N/100` и полосу: 85-100 «чисто» (следы ИИ не мешают), 60-84 «точечная правка», ниже 60 «рерайт». Это не вероятность ИИ и не «детект», а агрегат уже посчитанных сигналов: хард-баны и плотность маркеров весят сильно, морфология (сущ./глаг.) и тире слабо, как корроборирующие, чтобы не занижать плотный энциклопедический или юридический текст. Плюс document-level сигналы (ровные по длине абзацы, засилье однотипных пунктов-списков) для длинных шаблонных текстов: на короткой прозе они инертны. Показывай число в начале аудита, а после переписывания давай «было N, стало M»: так правка становится измеримой. Сканер не запускался (нет python, claude.ai web)? Прикинь score по той же логике: старт 100, заметный минус за каждый хард-бан и за высокую плотность маркеров, небольшой за номинальность и обилие тире; плотный человеческий текст без AI-оборотов остаётся высоким, не занижай его.
|
|
80
|
+
> **Score чистоты (0-100).** Сканер выдаёт сводную оценку `ЧИСТОТА: N/100` и полосу: 85-100 «чисто» (следы ИИ не мешают), 60-84 «точечная правка», ниже 60 «рерайт». Это не вероятность ИИ и не «детект», а агрегат уже посчитанных сигналов: хард-баны и плотность маркеров весят сильно, морфология (сущ./глаг.) и тире слабо, как корроборирующие, чтобы не занижать плотный энциклопедический или юридический текст. Плюс document-level сигналы (ровные по длине абзацы, засилье однотипных пунктов-списков) для длинных шаблонных текстов: на короткой прозе они инертны. Показывай число в начале аудита, а после переписывания давай «было N, стало M»: так правка становится измеримой. Сканер не запускался (нет python, claude.ai web)? Прикинь score по той же логике: старт 100, заметный минус за каждый хард-бан и за высокую плотность маркеров, небольшой за номинальность и обилие тире; плотный человеческий текст без AI-оборотов остаётся высоким, не занижай его. Такую прикидку подписывай явно: «≈70, оценка без сканера». Число от сканера и число на глаз в одном формате путают читателя, а измеримость правки наше главное отличие.
|
|
81
81
|
|
|
82
82
|
**Точечная правка** (по запросу: «исправь только X», «убери канцелярит»). Работаешь только с указанной категорией паттернов. Остальное не трогаешь.
|
|
83
83
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
|
-
from dataclasses import dataclass
|
|
15
|
+
from dataclasses import dataclass, replace
|
|
16
16
|
|
|
17
17
|
from .burstiness import RhythmStats, rhythm, rhythm_verdict
|
|
18
18
|
from .markers import (
|
|
@@ -84,9 +84,15 @@ class Report:
|
|
|
84
84
|
# не склеиваются («От «В современном мире…» до клише» не станет «От до»).
|
|
85
85
|
GAP = "·"
|
|
86
86
|
QUOTE_MAX_WORDS = 12
|
|
87
|
-
|
|
87
|
+
# Ограда блока кода стоит только в начале строки (CommonMark): три обратные
|
|
88
|
+
# кавычки внутри строки кода не закрывают блок. Ленивый `(?s)```.*?``` ` на
|
|
89
|
+
# `x = "```"` съезжал по границам и уносил в код следующий абзац прозы.
|
|
90
|
+
_FENCED = re.compile(r"(?ms)^[ \t]{0,3}(`{3,}|~{3,})[^\n]*\n.*?^[ \t]{0,3}\1[ \t]*$")
|
|
88
91
|
_INLINE_CODE = re.compile(r"`[^`\n]*`")
|
|
89
92
|
_QUOTE = re.compile(r"«[^«»]*»")
|
|
93
|
+
# Markdown-цитата через «>» это чужой текст целиком: разбор плохого примера
|
|
94
|
+
# получал «рерайт» за штампы, которые автор как раз критикует.
|
|
95
|
+
_BLOCKQUOTE = re.compile(r"(?m)^[ \t]*>.*$")
|
|
90
96
|
|
|
91
97
|
|
|
92
98
|
def _blank(match: re.Match[str]) -> str:
|
|
@@ -101,27 +107,34 @@ def _blank_short_quote(match: re.Match[str]) -> str:
|
|
|
101
107
|
|
|
102
108
|
|
|
103
109
|
def mask_code_and_quotes(text: str) -> str:
|
|
104
|
-
"""Текст для лексического сканера:
|
|
105
|
-
смещения и номера строк сохранены."""
|
|
110
|
+
"""Текст для лексического сканера: код, markdown-цитаты и короткие цитаты
|
|
111
|
+
в ёлочках заглушены, смещения и номера строк сохранены."""
|
|
106
112
|
text = _FENCED.sub(_blank, text)
|
|
107
113
|
text = _INLINE_CODE.sub(_blank, text)
|
|
114
|
+
text = _BLOCKQUOTE.sub(_blank, text)
|
|
108
115
|
return _QUOTE.sub(_blank_short_quote, text)
|
|
109
116
|
|
|
110
117
|
|
|
111
118
|
def strip_code(text: str) -> str:
|
|
112
|
-
"""Текст для ритма и морфологии: код
|
|
119
|
+
"""Текст для ритма и морфологии: код и markdown-цитаты удалены, проза
|
|
120
|
+
оставлена как есть (ёлочки не трогаем, они внутри предложений автора)."""
|
|
113
121
|
text = _FENCED.sub("", text)
|
|
114
|
-
|
|
122
|
+
text = _INLINE_CODE.sub("", text)
|
|
123
|
+
return _BLOCKQUOTE.sub("", text)
|
|
115
124
|
|
|
116
125
|
|
|
117
126
|
def analyze(text: str) -> Report:
|
|
118
127
|
"""Полный детерминированный прогон текста."""
|
|
119
128
|
lexical = mask_code_and_quotes(text)
|
|
120
129
|
prose = strip_code(text)
|
|
130
|
+
stats = rhythm(prose)
|
|
131
|
+
# Тире внутри короткой цитаты («Война — и мир») не типографика автора:
|
|
132
|
+
# считаем его по заглушенному тексту, ритм по прозе с цитатами.
|
|
133
|
+
stats = replace(stats, em_dash=lexical.count("—"))
|
|
121
134
|
return Report(
|
|
122
135
|
hard_bans=scan_hard_bans(lexical),
|
|
123
136
|
markers=scan_markers(lexical),
|
|
124
|
-
rhythm=
|
|
137
|
+
rhythm=stats,
|
|
125
138
|
morph=morph_stats(prose),
|
|
126
139
|
structure=structure_stats(prose),
|
|
127
140
|
)
|