---
type: JS Module
title: main.mjs
resource: npm/rules/doc-files/docgen-gen/main.mjs
docgen:
  crc: 02fd101f
  model: openai-codex/gpt-5.4-mini
  tier: cloud-min
  score: 55
  issues: internal-name:isApiGap,internal-name:renderApiLine,internal-name:oneShotDoc,internal-name:finishUnsupported,anchor-miss:(foo.mdc),best-of-2:retry-error
---

## Огляд

Модуль перетворює джерело, факти й оцінку якості на готову поведінкову документацію для `generateDoc` через `loadSrcAndFacts` і `assemble`. Він охоплює побудову секцій, вставку захищеного тексту та формування результату, який або проходить як повний, або отримує позначку `degraded`, якщо бракує повноти чи узгодженості.

## Поведінка

Потік починається з `loadSrcAndFacts`: джерело й факти з мовного екстрактора приходять в одному місці, а далі генерація працює вже від стабільного факт-листа та сирого тексту. Якщо файл завеликий, спрацьовує ранній відсів ще до будь-якого LLM-виклику. LLM-відповідь очікується без автоматичного timeout, а foreground heartbeat кожні 30 секунд дозволяє бачити активність і свідомо скасувати процес через Ctrl-C. `DEFAULT_LOCAL_MODEL` задає базову модель через universal policy, а `generateDoc` з неї будує весь сценарій: збирає дані, вибирає режим, виконує генерацію, оцінює результат і позначає `degraded`, якщо якість не дотягує.

Під час підготовки тексту `stripSection`, `stripLeadingPreamble` і `stripSignatures` прибирають чат-обгортки та зайві сліди моделі, щоб у вихід не потрапляли службові фрагменти або сигнатурні шумові вставки. `splitProtected` і `insertProtected` зберігають захищену секцію про призначення: якщо вона є в наявній документації, вона проходить крізь генерацію незмінною і повертається на фіксоване місце в зібраному Markdown. `hasCompleteCommentDocumentation` і `commentDocumentationMode` вирішують, чи можна обійтися лише авторськими коментарями, чи потрібне коротке LLM-доповнення, і саме це визначає, чи піде `commentOnlyDoc` без моделі, чи з додатковою секцією поведінки.

`apiSectionPlan` і `buildApiSection` працюють як спільний шлях для публічного API: покриті JSDoc-описом експортовані символи лишаються детермінованими, а прогалини віддаються на LLM лише тоді, коли це справді потрібно. `resolvePromptSrc` підбирає, що саме показати моделі як кодовий контекст: повний source або стиснутий дайджест, якщо файл надто великий. `buildApiSection` не витрачає виклик на випадки без прогалин, а `insertTestScenarios` після цього додає окремо згенеровану JS-секцію тестових сценаріїв, не змішуючи її з основним текстом.

`generateDoc` з’єднує весь конвеєр: спершу отримує факти, потім обирає між comment-only, one-shot, orchestrated або unsupported-веткою, далі очищає й збирає Markdown через `assemble`, а наприкінці оцінює якість через `scoreDoc` і, за потреби, запускає judge-гейт. `scoreDoc` використовує той самий зібраний документ, щоб перевірити, чи не втрачено важливі анкори, чи не спотворено публічні символи, і чи секції відповідають фактам; саме його результат керує повтором і degraded-станом. Усі помилки на шляху класифікуються через `classifyDocgenError`, щоб batch-логіка відрізняла тимчасові збої від системних або безпечних для пропуску.

`prepareBatchItem` готує той самий набір даних, що й `generateDoc`, але без самого LLM-виклику: це дає batch-шару можливість робити один спільний submit на багато файлів, не дублюючи preflight. `finishBatchItem` потім застосовує той самий фінальний ланцюжок очищення, оцінки й збірки, що й послідовний шлях, але вже до готового batch-відповіді. Такий поділ зберігає однакові правила для одного файлу й для масового прогону, а кешування в межах прогону зменшує повторні звернення до однакових проміжних даних.

## Публічний API

- classifyDocgenError — Класифікує помилку генерації для batch-логіки (замінює `classifyOmlxError` після
pi-міграції — помилки приходять як винятки з generateDoc/pi-one-shot):
  - `permanent` — pre-send guard «Prompt too long» → skip (не ретраїти);
  - `systemic`  — модель/сервер/registry/RAM упали → circuit-breaker abort;
  - `transient` — таймаут (можна було б ретраїти);
  - `infra`     — інше (рахуємо як помилку, але без abort).
Живе тут (не в `docgen-files-batch`), щоб `docgen-wave-batch` (фаза 2) теж
могла її імпортувати без циклічної залежності між двома batch-оркестраторами.
- CRITIC_NONE_RE — Критик (E2/Wave C) відповідає рівно цим словом — дефектів немає, refine не потрібен.
- stripLeadingPreamble — R9: зрізає провідні чат-преамбули й дубль назви секції з початку тексту.
Ітерується, поки перший непорожній рядок лишається мета-нарацією — модель
інколи ставить дві поспіль («Як технічний письменник…» + «Ось оновлений…»).
- stripSection — Прибирає код-фенс-обгортку (потрійні бектіки), випадковий провідний
`##`-заголовок і чат-преамбули (R9) із секції.
- stripSignatures — Stage 2 (детермінований лінт, 0 токенів): зрізає сигнатури `name(args)` → `name`.
Два проходи — щоб зняти вкладені виклики на кшталт `check(cwd = process.cwd())`.
Не чіпає дужки без ідентифікатора перед ними (напр. `(abie.mdc)`, «(наприклад)»).
- splitProtected — Відокремлює захищену секцію `## Призначення` (Варіант B). Межа — наступний `## `
(H2); `###`+ усередині не обривають блок.
- insertProtected — Вставляє захищений блок `## Призначення` одразу після H1 (фіксована позиція).
- scoreDoc — Stage 2.5 — детермінований скоринг (0 токенів): перевіряє вихід проти фактів.
- apiSectionPlan — Stage 1/3 (гібрид doc-files, ADR 260719-2155), чиста частина: обчислює
дослівний блок покритих JSDoc-описом експортів (0 LLM) і список прогалин
(`isApiGap`), без жодного виклику моделі — виокремлено з
[`buildApiSection`] для повторного використання batch-оркестратором
хвиль (`docgen-wave-batch`), де LLM-виклик прогалини йде окремою
batch-хвилею, а не await тут-таки.
- buildApiSection — Stage 1/3 (гібрид doc-files, ADR 260719-2155): «Публічний API» — покриті
JSDoc-описом експорти рендеряться дослівно (`renderApiLine`, 0 токенів, 0
галюцинацій), LLM викликається лише на прогалини (`isApiGap`). Якщо прогалин
немає — секція збирається БЕЗ жодного LLM-виклику. Єдиний непокритий
експорт (як і раніше) лишається описаним лише в Поведінці — окремого виклику
на секцію з одного рядка не варте.
- hasCompleteCommentDocumentation — Чи коментарі автора повністю покривають машинну документацію: header дає
«Огляд», а змістовні описи всіх public API — відповідну секцію. У такому
разі LLM не потрібна: текст зберігається дослівно для JS, Rust і Python.
- commentDocumentationMode — Вибирає гібридний режим для повністю прокоментованого source. Короткий
header майже напевно є pointer-ом, а середній header разом із явним flow у
коді потребує короткого LLM-доповнення. Детальний наратив лишається 0-LLM.
- commentOnlyDoc — Збирає документ лише з авторських коментарів і детермінованих фактів.
- assemble — Stage 3: фіксовані заголовки у фіксованому порядку.
- insertTestScenarios — Додає test-сценарії до one-shot/batch-документа. Для unsupported мов основний
Markdown ще повертає LLM, але test-секція лишається виключно JS-рендером.
- behaviorOnlyDocument — Витягує LLM-секцію «Поведінка» для вузького semantic judge. Авторські
«Огляд»/API не оцінюються моделлю і не можуть бути нею переписані.
- resolvePromptSrc — №5 (бенч gemma-4): текст «коду файлу» для Behavior-промпта. Великий src
(понад UNIT_DIGEST_TOKENS) → юніт-дайджест (імʼя + JSDoc + call-graph + тіло
лише для непокритих юнітів) замість сирого коду: на ~6k токенів сирцю мала
модель втрачає фокус і пише водянисто. Анкори/CRC — завжди від повного src
(дайджест лише для промпта). units нема (парсинг упав чи мова без юніт-шару)
— повний src, як раніше.
- loadSrcAndFacts — Спільний pre-LLM preflight для `generateDoc` і `prepareBatchItem` (T8 2b-batch):
читає джерело, ріже гігантів до LLM-виклику (pre-send guard — «Prompt too long»
без жодного виклику) і резолвить факт-лист через мовний екстрактор lang-плагіна
(js/mjs/ts — lang-js, `.rs` — lang-rust; whole-file `unsupported`-fallback, якщо
екстрактора для розширення нема).
- DEFAULT_LOCAL_MODEL — Дефолтна модель: явний N_CURSOR_DOCGEN_MODEL або universal policy від
N_LOCAL_MIN_MODEL до N_CLOUD_MAX_MODEL. Без hardcoded model fallback: якщо жодна
сходинка не задана, preflight оркестратора фейлить гучно.
- generateDoc — Головний API: файл → md-дока з det-оцінкою.

Local-only (ADR 260610-2228): жодних cloud-ескалацій і pre-route — будь-який
файл генерується локальною моделлю. Якщо det-score нижче порогу, один retry
з вищою температурою (best-of-2); якщо й він не допоміг — результат
позначається `degraded`, рішення про перегенерацію приймає batch/користувач.
- prepareBatchItem — T8 (2b-batch, рішення Р): підготовка ОДНОГО item-у для `submitBatch` — та сама
pre-send guard і той самий факт-лист/one-shot messages, що й `oneShotDoc`/
`generateDoc`, але БЕЗ виклику LLM (виклик робить batch-шар одним `submit` на
всі файли разом). Кидає ту саму помилку pre-send guard, що й `generateDoc`
(класифікується `permanent` у batch-оркестраторі — skip, не помилка прогону).
- finishBatchItem — T8 (2b-batch): постобробка ОДНОГО результату `submitBatch` — той самий фініш,
що й `oneShotDoc`/`finishUnsupported`/det-скорер, тільки без LLM-виклику
(текст уже отримано з batch-у). Judge-гейт (Stage 3) у batch-шляху НЕ
викликається (мінімальний обсяг T8 — генерація; judge лишається опційним
розширенням послідовного шляху).

## Сценарії використання

- `npm/rules/doc-files/docgen-gen/tests/docgen-gen.test.mjs` (scoreDoc — R4 generic-overview; scoreDoc — R6 витік службових імен) — абстрактний Огляд штрафується і опускає score під поріг; конкретний Огляд не штрафується; неекспортована функція у Поведінці → internal-name; пропущений валідний анкор → anchor-miss + штраф; наявний анкор → без штрафу; ще 58

## Гарантії поведінки

- Кешує результати в межах одного прогону.
