diff --git a/docs/CHAPTER_STRUCTURE_SESSION_PROMPT.md b/docs/CHAPTER_STRUCTURE_SESSION_PROMPT.md index 825f5067..8c7f6266 100644 --- a/docs/CHAPTER_STRUCTURE_SESSION_PROMPT.md +++ b/docs/CHAPTER_STRUCTURE_SESSION_PROMPT.md @@ -39,7 +39,7 @@ **Поле не переделывать; чинить словарь и формулировку.** - ⛔ **Валидатор V2-7 «формат провёл ВСЕ границы» (`ingest.go:170-176`) — ЗАМЕЩАЕТСЯ конкуренцией (§2.4), а не сохраняется рядом с ней.** После конкуренции его предикат - (`len(parts)>1 && len(chapters)==len(parts)`, `:177`) становится мёртвым кодом. Не пытайся + (`len(parts)>1 && len(chapters)==len(parts)`, `ingest.go:178`; ⚠-блок разбора — `:172-177`) становится мёртвым кодом. Не пытайся сохранить обе логики — это прямо противоречащие друг другу правила. - **Доминирующий юнит с полом ≥2** для CJK уже есть (`detectChapterUnit`, `ingest.go:249-263`) — это мини-индуктор, а не список паттернов. **Пол ≥2 — единственный валидатор, живущий в паке 1.** @@ -89,6 +89,21 @@ workdir платформы, **гейту §3 не видна и в него не - `marker`/`units`/выбор читателя номеров/грамматика шаблона → **файл структуры ИСТОЧНИКА**, свой загрузчик по `Book.SourceLang`, **по образцу `reader.txt`**. - ⛔ **В пар-пак класть НЕЛЬЗЯ:** книга без каталога пары идёт с nil-паком (`runner.go:397-407`). +- ⛔ **ОДИН ИСТОЧНИК НА ФАКТ — и дрейф здесь ЖИВОЙ, а не гипотетический.** Замерено 05.09: + `cjk-section.txt` (embedded, читает ИНГЕСТ) несёт **пять** юнитов `章节節回話`, а `zh-ru/heading.txt` + (пар-пак, читает ЧАНКЕР) — **четыре** `章节節回`; `parseHeading` кладёт `units` дословно и на + `CJKSection` не смотрит. ⇒ комментарий `ingest.go:184-185` («the SAME data … ONE source, no + ingest↔chunker byte-drift») **сам протух** — класс строки 299, поправить вместе с кодом. + ⇒ **Пак не предотвращает возврат дрейфа, а ЗАКРЫВАЕТ живой**, и инвентарь — РЕШЕНИЕ, а не переезд. + **Ратифицировано: инвентарь — ПЯТЬ юнитов, источник `cjk-section.txt`.** Довод: пятый (話) добавлен + `D39.60` §3.1 и на нём стоит пин ja-детекции; ингест уже режет по пяти, отстал именно чанкер, и + сведение к пяти НЕ трогает разрез — меняется только `stripHeading`. + ⚠ **И это обязано быть ЗАМЕРЕНО шагом 0:** встречается ли `話` шапкой в 蛊真人. Встречается — + меняются чанки и контент-хеши, и это идёт в ветку (б) гейта §3, а не проходит молча. +- ⛔ **Инвентарь скрипт-дефолта CJK берётся ИЗ `cjk-section.txt`, НИКОГДА из пар-файла.** У `ja` + каталога пары нет вовсе (`configs/langpacks/` — только `ru/`, `zh/`, `zh-ru/`), значит ja-книга + идёт по лестнице на скрипт-дефолт. Соберёшь дефолт из опустошаемого `zh-ru/heading.txt` — потеряешь + `話`, и `TestChapterUnitJaWebnovel` (`chunker_heading_test.go:73`) покраснеет НЕ заказанным. - ⛔ **ОДИН ИСТОЧНИК НА ФАКТ.** Ингест И `stripHeading` чанкера читают **тот же самый файл**. Сегодня чанкер берёт `HeadingRule` пары; после разведения `heading.txt` оставляет ТОЛЬКО `template`. Оставить marker в обоих местах = вернуть дрейф ingest↔chunker, который pair-14 закрыл @@ -178,8 +193,19 @@ and the read-only path so status/export/report reproduce the identical snapshot ⛔ **И ГЛАВНОЕ — ЧТО С БАЙТАМИ ПРОИГНОРИРОВАННОГО `\f`.** `text.NormalizeSource` form feed **не трогает** (`ingest.go:44-46`). Сегодня он до провода не доходит, потому что он и есть символ разреза; после конкуренции проигнорированный `\f` **останется в тексте главы** и уедет модели, в контент-хеш -и в чекеры. ⇒ **игнорируемый `\f` заменяется на разрыв абзаца (`\n\n`) в ингесте, до нормализации**, -и считается. +и в чекеры. ⇒ **игнорируемый `\f` заменяется на разрыв абзаца (`\n\n`) в ингесте, до нормализации**, и считается. + +⛔ **ПОРЯДОК ОПЕРАЦИЙ — НАЗВАН ЯВНО, потому что наивный даёт ЦИКЛ и МОЛЧАЛИВУЮ ПОТЕРЮ ГЛАВЫ.** +Решить «есть ли шапки» до замены НЕЛЬЗЯ: перед `\f` нет `\n`, поэтому шапка сразу за ним лежит на +ОДНОЙ СТРОКЕ с прозой предыдущей главы — её отвергают сразу два предиката (длина строки против +`chapterHeaderMaxRunes`, и якорь `^` регэкспа). Заменить безусловно ДО детекции тоже нельзя — +уничтожается разделитель, нужный ветке отката. **Форма:** +**(1)** `\f` → `\n\n` на РАБОЧЕЙ КОПИИ; **(2)** детекция шапок идёт ПО КОПИИ; **(3)** шапки +победили → режем копию, книга `detected`, форм-фиды сочтены; **(4)** шапки проиграли → режем +**ОРИГИНАЛ** по `\f`, книга `delimited`. +⚠ **Существующий тест этого НЕ ловит:** он проверяет только `doc.Structure`, а при наивном порядке +ярлык остаётся верным (`detected`) — **глава теряется молча при зелёном тесте**. Это ровно тот класс, +который пак объявляет своим ⛔ (`D39.205`). **Слабость выражается СЧЁТОМ, не классом:** `form feeds ignored: N`, `documents attached: M` — в WARN и в отчёт, **НЕ в манифест**: потребителя на проводе у счёта сегодня нет, а поле без потребителя — @@ -261,11 +287,13 @@ and the read-only path so status/export/report reproduce the identical snapshot 3. **Данные структуры реально двигают `cutTag`** (и через него unit-id) **и в payload `manifestKey` (`manifest.go:336-354`), и НЕ двигают снапшот волн** — тест на каждое из трёх. 4. **Байты игнорируемого `\f`** — тест, что в тексте главы его не остаётся. -5. **Выравнивание `title_raw` с ПЛОТНОЙ нумерацией** — тест с ПУСТОЙ главой между двумя непустыми: +5. ⛔ **ЧИСЛО ГЛАВ, а не только ярлык, на фикстуре «шапка сразу за `\f`»** (`ingest_test.go:74`): + сегодня тест смотрит только `doc.Structure`, и потерянная глава проходит зелёной. Тест на СЧЁТ. +6. **Выравнивание `title_raw` с ПЛОТНОЙ нумерацией** — тест с ПУСТОЙ главой между двумя непустыми: пустая глава номер не потребляет, а `title_raw` поедет параллельным срезом, и промах на одну позицию даст заголовок главы N на главе N+1 **молча**. Тот же класс уже чинили для ruby (`ingest.go:568-605`, `denseNo`). -6. **Именованный артефакт:** посади дефект в разбор `nav` так, чтобы одна глава потерялась молча, +7. **Именованный артефакт:** посади дефект в разбор `nav` так, чтобы одна глава потерялась молча, и предъяви **имя покрасневшего теста**. Не краснеет ничто — это находка, а не повод двигаться. ⚠ **Каталог мутаций** (`backend/cmd/tmmutate`): `-root` обязан быть КОПИЕЙ; копия вне git ⇒ @@ -333,7 +361,8 @@ and the read-only path so status/export/report reproduce the identical snapshot · тест V2-7 «формат провёл ВСЕ границы» — меняются ДВА кейса, не один: «один случайный `\f` среди шапок» (шапки побеждают, книга `detected`) **и ПЕРВЫЙ кейс `ingest_test.go:68` «the format drew every boundary» (`ГЛАВА A\fГЛАВА B` → `StructureDeclared`), который становится - `delimited`**. ⛔ И **само имя** `TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` + `delimited`**. ⚠ А у кейса `ingest_test.go:74` ожидание НЕ меняется (`detected` и до, и после) — + у него меняется **ЧИСЛО ГЛАВ**, которого никто не проверяет: см. §4 п.5. ⛔ И **само имя** `TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` после конкуренции ни во что не разрешается — **переименовать тем же актом** (класс строки 299, который этот же промт требует ниже для комментариев); · `ingest_encoding_test.go:218-227` (GB18030, `第一章…\f第二章…`) — **счёт глав сохранит** (две