From 824bd209eb9691a9c4eef69aead581c36208b672 Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Thu, 20 Aug 2026 15:48:06 +0300 Subject: [PATCH] Restore three rules the first prompt draft dropped and gate every battle-prompt rule by declared disposition --- eval/dovodka/prompts/onepass-core.md | 16 +++- eval/dovodka/rol.py | 130 +++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/eval/dovodka/prompts/onepass-core.md b/eval/dovodka/prompts/onepass-core.md index bf78f563..9e3a41ca 100644 --- a/eval/dovodka/prompts/onepass-core.md +++ b/eval/dovodka/prompts/onepass-core.md @@ -37,14 +37,22 @@ исходного не стоит. Буквальность здесь не достоинство. Если выбор стоит между точной калькой и живой русской фразой — бери живую фразу. -ЧЕГО НЕЛЬЗЯ — четыре запрета, и они жёсткие: +У этой свободы одна граница, и она важнее всего списка выше: ПОВТОР, СТОЯЩИЙ В ПАРАЛЛЕЛЬНЫХ +ПОЗИЦИЯХ САМОГО ИСХОДНИКА, — авторский рефрен, а не бедность словаря. Такой повтор сохраняй и НЕ +«разнообразь» лексику там, где автор повторяется намеренно. + +ЧЕГО НЕЛЬЗЯ — пять запретов, и они жёсткие: 1. Перевирать факт: участника действия, отрицание, число, ранг, меру, родство, кто кому что сказал. Полярность и адресат неприкосновенны. 2. Терять смысловые ходы. Дробить и сливать можно; выбрасывать нельзя. Ничего не добавляй от себя - как факт — связка и уточнение допустимы, новое событие нет. -3. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже, + как факт — связка и уточнение допустимы, новое событие нет; сцены не пересочиняй. +3. Переводить буквально жест, идиому и реалию. Это три места, где подстрочник врёт чаще всего: + поклон — не «ударил головой»; идиому передавай смыслом, не сворачивая её в общее место и не + калькируя дословно; реалию сперва пойми, а потом переводи — неверно понятая реалия остаётся + ошибкой смысла, даже если фраза звучит гладко. +4. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже, склоняя по контексту. Своих вариантов не придумывай. -4. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о +5. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о решениях и markdown-заголовки. РЕГИСТР — держи тот же градус, что в исходнике: diff --git a/eval/dovodka/rol.py b/eval/dovodka/rol.py index 02b16e98..5b62978f 100644 --- a/eval/dovodka/rol.py +++ b/eval/dovodka/rol.py @@ -150,6 +150,124 @@ def rol_msgs(p: str, src: str) -> list[dict]: return out +# ── ГЕЙТ ПОКРЫТИЯ БОЕВЫХ ПРОМТОВ ───────────────────────────────────────────────────────────── +# ⚠⚠ ЗАВЕДЁН ПО ЗАМЕЧАНИЮ ВЛАДЕЛЬЦА 20.08 («там же довольно много пропущено?»), И ЗАМЕЧАНИЕ БЫЛО +# ВЕРНЫМ. Первая редакция нового промта молча потеряла три правила боевых промтов, и одно из них +# потеряла ОПАСНО: «повтор в параллельных позициях исходника — авторский рефрен, сохраняй его» — +# при том что новый промт прямо говорит «буквальность не достоинство», то есть толкает в обратную +# сторону. Ещё два: «кальки жестов и идиом · неверно понятые реалии» и «не пересочиняй сцены». +# +# Обещание «я всё перенёс» проверяемо только механизмом, поэтому вот он. Каждое ПРАВИЛО боевых +# промтов перечислено ниже характерной подстрокой, и у каждого объявлена ДИСПОЗИЦИЯ: +# "промт" — правило стоит в ядре нового промта (проверяется вхождением ключевых слов); +# "блок" — правило приезжает пар-блоком дословно (проверяется вхождением самой строки); +# "снято" — правило снято СОЗНАТЕЛЬНО, с основанием прямо здесь. +# Селфтест роняет замер, если хоть одна строка боевого промта не покрыта ни одной диспозицией: +# правка боевого промта чужой зоной сломает мой гейт ГРОМКО, а не тихо изменит смысл арма. +COVERAGE: tuple[tuple[str, str, str, str], ...] = ( + # (файл, характерная подстрока боевого правила, диспозиция, чем закрыто / основание) + ("translator", "Хонорифики:", "промт", "Хонорифики"), + ("translator", "Баланс форенизация", "промт", "форенизация/доместикация"), + ("translator", "Сноски:", "промт", "Сноски"), + ("translator", "ничего не пропускай", "промт", "выбрасывать нельзя"), + ("translator", "не добавляй от себя", "промт", "не добавляй от себя"), + # ⚠ «смысл» — правило покрыто ПО СОДЕРЖАНИЮ другим, более полным местом промта, но не + # дословно. Диспозиция заведена отдельно от «блок» намеренно: смешивать «перенесено буквой» и + # «покрыто смыслом» значит потерять различие, на котором и погорела первая редакция. + ("translator", "НЕ копируй построчную разбивку", "смысл", + "Один повествовательный ход оформляй ОДНИМ русским абзацем"), + ("translator", "избегай канцелярита", "промт", "канцелярита"), + ("translator", "Единицы и приёмы", "блок", "Единицы и приёмы"), + ("translator", "Выведи ТОЛЬКО перевод", "промт", "Выведи ТОЛЬКО перевод"), + ("editor", "искажения смысла", "промт", "Перевирать факт"), + ("editor", "неверно понятые реалии", "промт", "реалию сперва пойми"), + ("editor", "кальки жестов и идиом", "промт", "жест, идиому и реалию"), + ("editor", "а не подстрочником", "промт", "подстрочник врёт"), + ("editor", "лишние повторы", "промт", "разнообразь"), + ("editor", "авторский рефрен", "промт", "авторский рефрен"), + ("editor", "СТРОГО по приведённому глоссарию", "промт", "Менять канон"), + ("editor", "склоняя по контексту", "промт", "склоняя по контексту"), + ("editor", "не придумывай иных вариантов", "промт", "Своих вариантов не придумывай"), + ("editor", "естественные русские абзацы", "смысл", + "Один повествовательный ход оформляй ОДНИМ русским абзацем"), + ("editor", "Реплики прямой речи", "смысл", + "Каждую новую реплику начинай с нового абзаца через тире"), + ("editor", "Сохраняй ПОЛНОТУ", "промт", "Терять смысловые ходы"), + ("editor", "не пересочиняй сцены", "промт", "сцены не пересочиняй"), + ("editor", "ДИСКУРС-ПЕРЕВЁРСТКА", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"), + # строки, которые приезжают ПАР-БЛОКАМИ дословно (их тело — данные пары, не наше) + ("translator", "МЕРЫ", "блок", "Единицы и приёмы"), + ("translator", "СТИХИ и названия классики", "блок", "Единицы и приёмы"), + ("translator", "Род и форму имён", "блок", "Единицы и приёмы"), + ("translator", "РАМОЧНЫЙ РАССКАЗ", "блок", "Единицы и приёмы"), + ("translator", "ФРАНЦУЗСКИЙ СЛОЙ", "блок", "Единицы и приёмы"), + ("translator", "РЕЧЕВЫЕ ПОРТРЕТЫ", "блок", "Единицы и приёмы"), + ("translator", "КАЛЬКИ en→ru", "блок", "Единицы и приёмы"), + ("translator", "ЗАГЛАВНЫЕ РЕАЛИИ", "блок", "Единицы и приёмы"), + # у редактора СВОЯ сжатая копия единиц; в промт едет БОЛЕЕ ПОЛНАЯ версия переводчика + ("editor", "Единицы и приёмы", "смысл", "Единицы и приёмы"), + ("editor", "Молча определи смысловые ходы", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"), + ("editor", "Один повествовательный ход", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"), + ("editor", "Каждую новую реплику", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"), + ("editor", "Разрешено объединять и делить", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"), + ("editor", "Хонорифики:", "промт", "Хонорифики"), + # ⚠ ЕДИНСТВЕННОЕ СОЗНАТЕЛЬНОЕ СНЯТИЕ, И ОНО ОБЪЯВЛЕНО ЗДЕСЬ, А НЕ УМОЛЧАНО. Боевой промт + # переводчика требует «не сокращай»; решение владельца 16.08 («вольность не ошибка, живой + # язык — приоритет») это правило отменяет, и новый промт прямо разрешает «сворачивать + # описание». Полнота при этом охраняется НЕ запретом сокращать, а запретом терять смысловые + # ХОДЫ — то есть на уровне содержания, а не числа слов. + ("translator", "не сокращай", "снято", "решение владельца 16.08: вольность не ошибка"), + # Роль редактора-правщика однопроходке не передаётся по построению: чинить нечего. + ("editor", "Тебе даны ИСХОДНЫЙ текст и его черновой перевод", "снято", "черновика нет"), + ("editor", "Сверяй смысл черновика", "снято", "черновика нет"), + ("editor", "художественная редактура черновика", "снято", "черновика нет"), + ("editor", "Ты — литературный редактор", "снято", "роль задана заново: переводчик"), + ("editor", "Выведи ТОЛЬКО отредактированный", "снято", "своя строка вывода у переводчика"), + ("translator", "Ты — профессиональный литературный переводчик", "снято", "роль задана заново"), + ("translator", "Жанр книги:", "снято", "бриф перенесён своим блоком"), + ("editor", "Жанр книги:", "снято", "бриф перенесён своим блоком"), +) +_RULE_MARK = ("- ", "1.", "2.", "3.", "4.", "Ты —", "Жанр", "Твой мандат", "Выведи") + + +def coverage_report(p: str) -> list[tuple[str, str, str]]: + """[(файл, строка боевого промта, вердикт)] — что покрыто, чем и что не покрыто вовсе.""" + cfg = PAIRS[p] + cfg["wire"]() + d = PR.PAIR_DIR[cfg["para"]().key] + u = chosen(p)[0] + sysall = "\n".join(x["content"] for x in rol_msgs(p, u["source"]) if x["role"] == "system") + ub, rb = pair_blocks(p) + out = [] + for fname in ("translator", "editor"): + core, _f, _us = PR.load_template(d / f"{fname}.md") + for ln in core.splitlines(): + s = ln.strip() + if len(s) < 25 or not s.startswith(_RULE_MARK): + continue + hits = [c for c in COVERAGE if c[0] == fname and c[1] in s] + if not hits: + out.append((fname, s[:90], "⛔ НЕ ОБЪЯВЛЕНО НИ ОДНОЙ ДИСПОЗИЦИЕЙ")) + continue + for _f2, _sub, disp, what in hits: + if disp == "снято": + out.append((fname, s[:90], f"снято: {what}")) + elif disp == "смысл": + out.append((fname, s[:90], "покрыто смыслом" + if what in sysall else f"⛔ «{what}» НЕТ в промте")) + elif disp == "блок": + # ⚠ Сверяется САМА СТРОКА боевого промта, а не начало блока: слабая проверка + # («блок непустой») пропустила бы усечённый блок, из которого выпала половина + # правил. Строка рендерится тем же брифом, иначе `{{venuti}}` не совпадёт. + line = PR.render(s, getattr(PR, PR.PAIR_BRIEF[cfg["para"]().key]), "", "") + out.append((fname, s[:90], + "блок пары ДОСЛОВНО" if line in sysall else "⛔ строка НЕ доехала")) + else: + out.append((fname, s[:90], + "в промте" if what in sysall else f"⛔ «{what}» НЕТ в промте")) + return out + + def tag(p: str, arm: str, uid: str) -> str: return f"dv-{PAIRS[p]['letter']}-{arm.lower()}-{uid}" @@ -354,6 +472,13 @@ def cmd_selftest() -> int: ck(f"{p}: фаза заведена в кассе", cfg["phase"] in MONEY.CEILINGS) ck(f"{p}: две генерации несущей модели — РАЗНЫЕ теги", tag(p, "RN", u["uid"]) != tag(p, "RN2", u["uid"])) + # ⚠ ГЕЙТ ПОКРЫТИЯ — главная проверка этого файла. Обещание «я перенёс всё из боевых + # промтов» проверяемо только механизмом; замечание владельца 20.08 показало, что на слово + # его брать нельзя. + rep = coverage_report(p) + miss = [r for r in rep if r[2].startswith("⛔")] + ck(f"{p}: КАЖДОЕ правило боевых промтов имеет диспозицию и она исполнена", + not miss, f"не покрыто {len(miss)} из {len(rep)}: " + " | ".join(r[1][:50] for r in miss[:3])) ck("пары не делят буквы тегов", len({v["letter"] for v in PAIRS.values()}) == len(PAIRS)) ck("несущий контраст объявлен один", len(PRIMARY) == 2 and PRIMARY == ("RN", "ZP")) for a, (mo, _g) in ARMS.items(): @@ -379,6 +504,11 @@ if __name__ == "__main__": cmd_show(next((x for x in a[1:] if x in PAIRS), "zh")) elif a[0] == "--canon": canon() + elif a[0] == "--coverage": + for p in ([x for x in a[1:] if x in PAIRS] or list(PAIRS)): + print(f"\n=== ПОКРЫТИЕ БОЕВЫХ ПРОМТОВ, пара {p} ===") + for fname, line, verdict in coverage_report(p): + print(f" [{verdict:32s}] {fname}: {line}") elif a[0] == "--buy": pr = [x for x in a[1:] if x in PAIRS] or list(PAIRS) ar = [x for x in a[1:] if x in ARMS] or list(ARMS)