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: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
- [![Version](https://img.shields.io/badge/version-3.19.1-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
8
+ [![Version](https://img.shields.io/badge/version-3.19.2-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)
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > [English version](README.en.md)
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
- [![Версия](https://img.shields.io/badge/версия-3.19.1-blueviolet)](https://github.com/ilyautov/humanizer-ru/blob/main/CHANGELOG.md)
8
+ [![Версия](https://img.shields.io/badge/версия-3.19.2-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)
@@ -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.1",
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.1
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
- _FENCED = re.compile(r"(?s)```.*?```")
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
- return _INLINE_CODE.sub("", text)
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=rhythm(prose),
137
+ rhythm=stats,
125
138
  morph=morph_stats(prose),
126
139
  structure=structure_stats(prose),
127
140
  )