Починка spintax-шаблонов

Починка — это найти дефект → применить один канонический рецепт его класса → перепроверить. Никогда не свободное переписывание. И она соблюдает порядок, которому учила серия: структура, затем переменные, затем синонимы — на уровне синонимов нельзя починить то, что принадлежит уровню переменных.

Почему шаблоны ломаются при рендере, а не в исходнике

validate() судит шаблон. Шаблон может быть безупречен и всё равно выдавать сломанный текст, потому что дефект живёт в комбинации выборов: два соседних слота случайно выбирают одно слово, существительное из одного слота встречает относительное местоимение из другого и они расходятся в роде, пустая ветка оставляет после себя голые скобки. Ничего из этого не видно в исходнике, и ничего из этого не переживает чтение исходника человеком — оно появляется только в части рендеров.

Поэтому починка начинается с отрендеренных вариантов, а не с шаблона. Операция Lint в n8n-ноде существует ровно для этого: она судит рендер, никогда не шаблон, и на реальном пуле она насчитала 2% чистых из 200 рендеров на первом проходе и 100% после того, как починили слоты, на которые она указала. Самая частая находка в том пуле — существительное из одного слота рядом с местоимением из другого: сюжет, в которой попало в 18% статей, прежде чем кто-то заметил.

Практическое следствие: если читать только шаблон, дефект уедет в продакшен. Рендерьте десятки вариантов, прогоняйте их линтом и чините то, на что он указывает.

Золотое правило: чинить по рецепту, а не по наитию

У дефекта есть класс, и у каждого класса — один канонический способ починки. Свободная правка — человеком в спешке или моделью, которой сказали «почини» — ломает грамматику, пока чинит грамматику. Модель, которую попросили починить дополнение, который, охотно подставит синоним другого рода и вернёт расширение, который: местоимение, о котором её спрашивали, по-прежнему неверно, а новая ветка уже выдумана. Получив вместо этого именованный рецепт — зависимое слово внутрь каждой ветки — она делает одно преобразование и останавливается.

Вторая половина правила — порядок. Гайд по грамматике формулирует его для авторства: «Не добавляйте {a|b}, пока не зафиксированы permutations, переменные и форма предложения». Для починки он читается так: структура → переменные → синонимы. Дефект, который идёт от существительного, повторённого в трёх падежах, — проблема уровня переменных; латать синонимы вокруг него значит починить один рендер и оставить сломанным следующий.

Две починки, в этом порядке

  1. Починка синтаксиса — по диагностике validate(). У каждой есть строка, колонка и код; модели показывают пронумерованный шаблон и велят менять как можно меньше, оставив всё остальное байт-в-байт. Такой промпт собирает @spintax/authoring-prompt (buildRepairPrompt()), n8n-нода отдаёт его как Build Repair Prompt, а кнопка Fix в Spintax Studio делает тот же раунд. Если этот шаг находит ошибки — он первый: шаблон, который не парсится, нечего рендерить и нечего линтить.
  2. Починка по рендер-линту — по находкам на отрендеренных вариантах. Находка несёт код, проблемный фрагмент и сообщение, но не позицию в шаблоне. Этот гайд — про этот шаг: сопоставить код рецепту, найти ветки, породившие фрагмент, применить рецепт, перерендерить, перелинтить.

Каталог «дефект → рецепт»

Для каждого класса ниже названо, кто его находит — линт, по коду, или вы, читая сэмплы, — и дан один рецепт с «до» и «после». Рендеры «до» — настоящий вывод движка; сообщения линта процитированы в том виде, в каком их печатает линт n8n-ноды. Грамматика за каждым рецептом объяснена один раз, в гайде по грамматически безопасной синонимизации; ссылки ведут в точную секцию.

Артикль или определитель (английский)

Находит: вы, по сэмплам — машинно не проверяется. Симптом: a encrypted connection. Рецепт: втянуть определитель внутрь ветки.

a {secure|encrypted} connection
{a secure|an encrypted} connection

Разобрано в секции об английском согласовании.

Подлежащее и сказуемое, род и число

Находит: вы, по сэмплам. Симптом: the team respond; по-русски сайт удобна, команда доступны. Рецепт: связать подлежащее и сказуемое в одной ветке. Два независимых слота согласовать нельзя никогда; один слот, несущий обе половины, — всегда.

{the team|all members} {responds|respond}
{the team responds|all members respond}
{сайт|платформа} {удобен|удобна}
{сайт удобен|платформа удобна}

Разобрано в секциях о согласовании и «Род и число».

Зависимое слово: относительное местоимение (русский)

Находит: линт, код agreement.relative. Он смотрит не более чем на два слова назад от каждой формы который, угадывает род существительного по окончанию и молчит, когда окончание двусмысленно (путь, тень), вместо того чтобы гадать. Симптом — ровно так, как печатает линт:

"дополнение … который" — neuter noun with a masculine relative pronoun

Рецепт A: зависимое слово внутрь каждой ветки — в той форме, которая нужна этой ветке.

{дополнение|плагин}, который ускоряет сайт
{дополнение, которое|плагин, который} ускоряет сайт

На 40 рендерах линт пометил 18 у первого шаблона и ни одного у второго.

Рецепт B: вынести существительное в #def одного рода, когда оно повторяется. Возьмите синонимы одного рода, бросок делается один раз, и каждое последующее упоминание согласовано по построению:

#def %Ext% = {дополнение|расширение}
%Ext%, которое ускоряет сайт

Тоже 40 из 40 чистых. Условие, которое идёт вместе с рецептом B: он держится только пока существительное стоит в одном падеже. Объявленный отдельно %ExtGen% с %Ext% не коррелирует — два определения это два броска, — поэтому существительному, которому нужно несколько падежей, положена лестница падежных семей: синонимы, склоняющиеся одинаково, с окончанием снаружи определения, или одна основа в #def, на которую ссылается каждая падежная форма.

Предлог + падеж (русский, немецкий)

Находит: вы, по сэмплам — падеж внутри слота линт не судит. Симптом: после первом заказе, für dem Team. Рецепт: предлог уезжает в ветку вместе со своей управляемой формой.

{после|при} первом заказе
{после первого заказа|при первом заказе}
{mit|für} dem Team
{mit dem Team|für das Team}

Когда одно и то же существительное управляется в нескольких местах, альтернатива — падежная переменная, %OrderGen%, %OrderLoc%, объявленная семьёй. Разобрано в секции «Предлог + управляемая форма».

Повторённый корень

Находит: линт, код repeat.word. Он сканирует окно в шесть слов — то есть ловит повтор на расстоянии до пяти слов — и сравнивает основы, так что вопрос / вопросов считаются одним словом. Шесть — число измеренное: при девяти 82% попаданий были обычной связностью текста, при шести каждое попадание было настоящим. Симптом, как печатается:

"сразу" and "сразу" repeat 4 words apart

Рецепт: корень только в одном слоте. Его несёт либо перечисление, либо фиксированный текст, никогда оба; когда повторяется сущность — вынести её в #def и упомянуть один раз.

{Сразу|Немедленно} после установки сайт сразу ускоряется
{Сразу|Немедленно} после установки сайт начинает отвечать быстрее

18 из 40 рендеров помечены до, ни одного после. Английская форма та же — «Коллизия повторяющихся слов» показывает которым нужно получить… могут получить.

Стык, которого линт не видит. Перестановка делает соседями любые два блока, поэтому заголовок и вступление после него могут делить корень через границу блока. Линт намеренно не флагит повторы «заголовок — тело» (именно там жила «обычная связность»), так что стыки держит дисциплина в источнике — разведите словарь на границе — или линт-проход по целому собранному шаблону.

Регистр и однородность слота

Находит: вы, по сэмплам. Симптом: формальная ветка рядом с разговорной или нарративная связка, меняющая регистр посреди абзаца (Следовательно / Ну и вот). Рецепт: один регистр на слот; связки зафиксировать. Убрать чужую ветку, а не переписывать слот. Правила — в «Правилах синонимизации».

Пунктуация, оставшаяся внутри ветки

Находит: линт, код punctuation.empty-pair. Симптом: empty brackets or quotes — необязательная ветка оставила за собой свою обёртку. Рецепт: перенести пунктуацию в ту ветку, которой она нужна.

Works in any browser ({fast|})
Works in any browser{ (fast)|}

18 из 40 рендеров чистых до, 40 из 40 после.

У той же семьи есть ещё три кода — punctuation.double-space, punctuation.space-before и punctuation.duplicated, — и от движка вы их почти не увидите: его пост-процессинг сам нормализует двойной пробел и пробел перед запятой прямо в рендере. Это сеть для текста, собранного вне движка, или для хоста, который рендерит с выключенным пост-процессингом. Рецепт тот же: пунктуация уходит внутрь ветки, которой принадлежит.

Переменные: самая сильная починка

У половины каталога выше есть лекарство на уровне переменных, и оно бьёт любую заплатку на уровне синонимов, потому что убирает саму комбинацию, в которой жил дефект. Метод:

  1. Выносить до синонимизации. Склоняемая сущность, которая повторяется по тексту, сначала уходит в #def; только потом проход синонимизации идёт по тексту вокруг неё. В обратном порядке инлайн-замена синонима молча меняет форму, с которой согласовано остальное предложение. В кампании wpps-ru одна статья прошла от 52 чистых рендеров из 200 до 200 из 200 на одном этом шаге: #def %Ext% = {дополнение|расширение}, оба среднего рода, и инлайн-синонимы её больше не трогали.
  2. Называть формы. %PascalCase% для именительного, суффикс для каждого используемого падежа — %OrderGen%, %OrderDat%, %OrderLoc% — по конвенции гайда о переменных.
  3. Держать семью на одном броске — единственный способ, которым движок согласует формы. Синонимы, склоняющиеся одинаково, с окончанием снаружи определения (#def %e% = {магазин|сайт|проект}для %e%а); или одна основа в #def, на которую ссылается каждая падежная форма (#def %s% = {посетител|гост}, #def %VisitorsGen% = %s%ей); или вся фраза в одном перечислении. Два независимых определения — два броска, и они не коррелируют никогда — ни на #set, ни на #def.
  4. #def, а не #set, для всего, в чём два упоминания должны совпасть. #set перебрасывает при каждой ссылке и годится там, где вариативность нужна; #def бросает один раз за рендер.

Когда не спинить

Некоторые участки не чинят — их вынимают из перечисления целиком. Имена собственные, бренды, цены, URL и юридические формулировки никогда не живут внутри ветки. Как и существительное с длинным хвостом зависимых — прилагательное, причастие и придаточное, все согласованные с ним, — потому что каждой ветке пришлось бы нести весь хвост. Варьируйте текст вокруг них, а их держите фиксированными.

Починка с помощью LLM

Модель чинит хорошо, когда ей дают четыре вещи и отказывают в пятой: шаблон, конкретный дефект с фрагментом, названный рецепт к применению и указание применить только его — и никакой лицензии что-либо улучшать. Запрос никогда не звучит как «почини это»; он звучит как «примени рецепт X к участку, породившему Y». Промпт ниже — форма, которая выдержала реальный материал.

You are repairing a spintax template. Do NOT rewrite freely and do NOT improve the wording.

A lint pass over rendered variants found this defect:
  code:     <agreement.relative | repeat.word | punctuation.empty-pair | …>
  fragment: "<the offending rendered text>"
  message:  "<the lint message>"

Apply exactly ONE recipe from https://spintax.net/docs/repairing-spintax.md
  recipe: <its name, e.g. "bind the governed word into each branch">
to the branch or branches that produced the fragment — and to nothing else.

Rules:
- Keep meaning, structure, variables and every other branch byte-for-byte unchanged.
- Do not add words, clauses, prepositions or relative pronouns the template does not
  already have.
- If the recipe cannot be applied without changing meaning, leave the template as it is
  and reply: unfixable
- Return only the corrected template — no explanation, no quotes, no code fences.

Две привычки из той же кампании держат модель в рамках. Substitution-only разметка, когда модель размечает готовый текст: ей можно заменять слова и короткие обороты и нельзя вводить союзы, придаточные, предлоги и относительные местоимения, которых не было в источнике — выдуманный который и выдуманный предлог были источником большинства дефектов согласования и падежа, и правило их убрало. И линт-проход на каждом размеченном абзаце, потому что даже substitution-only сносит: один абзац вернулся как {Совпадает|Совпадает ли} — careful, no. {Совпадает ли|…} — английская «мысль вслух» протекла в русскую ветку плюс дубль корня. Петля это поймала; человек бы не заметил.

Ещё два указателя для модели: девять ошибок, которые модели делают в spintax, и фрагмент грамматического аудита в гайде по грамматике — авторская половина этого промпта.

Автоматизация починки

Для пайплайна форма, которая довела три long-form шаблона до линт-чистых 200 из 200, выглядит так, на один абзац:

draft = markup(concrete_text)            # substitution-only
repeat up to K:
  errors = validate(draft)
  if errors:        draft = repair(draft, errors); continue   # синтаксис: модель чинит спаны
  if any(lint(render(draft, seed_i)) for i in 1..N):
                    draft = markup(concrete_text); continue   # грязно: заново, не «свободно чинить»
  return draft                                                # чисто
fail loud

Читайте асимметрию: ошибку синтаксиса модель чинит по точным спанам; находка линта либо отправляет абзац на перегенерацию, либо — в полной версии петли — чинится применением именованного рецепта из каталога к участку. Петля никогда не позволяет модели чинить линт-дефект свободно. При N = 8 рендерах на абзац и K = 3 попытках второй шаблон кампании прошёл от 95 чистых из 200 до 200 из 200.

В n8n та же петля — четыре ноды: Validate направляет элемент в Valid или Invalid, Build Repair Prompt превращает выход Invalid в промпт-починку для вашей LLM-ноды (ограничьте цикл, двух попыток достаточно), Render Many набирает выборку, а Lint направляет её в Clean или Defective — выборка чиста, только когда чист каждый вытянутый документ. Те же правила работают и как обычная функция над любым текстом — пайплайн кампании гонял собственную их копию, ту самую, из которой портирован линт ноды.

Три вещи, которые нужно знать до подключения:

  • Провенанс строите вы. Находка линта несёт отрендеренный фрагмент, а не позицию в шаблоне. Чтобы применить рецепт, нужно найти ветку, породившую фрагмент — искать слова фрагмента по шаблону, ветка за веткой, — и именно этот кусок обвязки в итоге пишет каждый инструмент починки.
  • Выборка вероятностна. Восемь рендеров на абзац ловят частые комбинации; редкая всплывает на объёме. Финальные ворота по собранному шаблону — 200 рендеров, каждый через линт — остаются сетью, и операция выборки у линта сделана ровно под это (50 рендеров по умолчанию, до 500).
  • Стыки остаются за человеком. Поабзацная петля не видит повтор через границу блоков; их держит проход по целому шаблону или дисциплина источника, как выше.

Шпаргалка

ДефектНаходитРецептРазобрано
Артикль / определитель (EN)вы, по сэмпламвтянуть определитель внутрь веткисогласование
Подлежащее и сказуемое, род и числовы, по сэмпламсвязать подлежащее и сказуемое в одной веткесогласование, род и число
Относительное местоимение не согласовано (RU)линт agreement.relativeзависимое слово внутрь каждой ветки; или вынести существительное в #def одного родападежные семьи
Предлог + падеж (RU, DE)вы, по сэмплампредлог уезжает в ветку вместе с формой; или падежная переменнаяпредлог + падеж
Глагольное управление, квантификаторы (RU)вы, по сэмпламсвязать глагол и дополнение / квантификатор и существительное в одной веткеуправление, квантификаторы
Повторённый кореньлинт repeat.wordкорень только в одном слоте; сущность — в #def и одно упоминаниеколлизия
Регистр / однородность слотавы, по сэмпламодин регистр на слот; связки зафиксироватьправила
Пустая пара, лишний пробел, сдвоенный знаклинт punctuation.empty-pair, punctuation.double-space, punctuation.space-before, punctuation.duplicatedперенести пунктуацию в ту ветку, которой она принадлежитэта страница

Одна строка на память: найти, назвать рецепт, применить, перепроверить. Если рецепта для того, что вы видите, нет — дефект в структуре или в переменных, и чинится он в серии, которая идёт до этого гайда.


Продолжить серию