Restore three rules the first prompt draft dropped and gate every battle-prompt rule by declared disposition

This commit is contained in:
Claude (backend session) 2026-08-20 15:48:06 +03:00
parent 7f15323a8d
commit 824bd209eb
2 changed files with 142 additions and 4 deletions

View file

@ -37,14 +37,22 @@
исходного не стоит. Буквальность здесь не достоинство. Если выбор стоит между точной калькой и
живой русской фразой — бери живую фразу.
ЧЕГО НЕЛЬЗЯ — четыре запрета, и они жёсткие:
У этой свободы одна граница, и она важнее всего списка выше: ПОВТОР, СТОЯЩИЙ В ПАРАЛЛЕЛЬНЫХ
ПОЗИЦИЯХ САМОГО ИСХОДНИКА, — авторский рефрен, а не бедность словаря. Такой повтор сохраняй и НЕ
«разнообразь» лексику там, где автор повторяется намеренно.
ЧЕГО НЕЛЬЗЯ — пять запретов, и они жёсткие:
1. Перевирать факт: участника действия, отрицание, число, ранг, меру, родство, кто кому что
сказал. Полярность и адресат неприкосновенны.
2. Терять смысловые ходы. Дробить и сливать можно; выбрасывать нельзя. Ничего не добавляй от себя
как факт — связка и уточнение допустимы, новое событие нет.
3. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже,
как факт — связка и уточнение допустимы, новое событие нет; сцены не пересочиняй.
3. Переводить буквально жест, идиому и реалию. Это три места, где подстрочник врёт чаще всего:
поклон — не «ударил головой»; идиому передавай смыслом, не сворачивая её в общее место и не
калькируя дословно; реалию сперва пойми, а потом переводи — неверно понятая реалия остаётся
ошибкой смысла, даже если фраза звучит гладко.
4. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже,
склоняя по контексту. Своих вариантов не придумывай.
4. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о
5. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о
решениях и markdown-заголовки.
РЕГИСТР — держи тот же градус, что в исходнике:

View file

@ -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)