# fix.mjs — entry-point правила `text`

## Огляд

Файл `npm/rules/text/fix.mjs` — це **подвійний entry-point** для правила `text` із пакета `@nitra/cursor`. Він виконує дві ролі одночасно:

1. **Library mode** — експортує функцію `run(ctx)`, яку імпортує зовнішня CLI-оркестрація (`npx @nitra/cursor fix text` або агрегований прогон усіх правил) для запуску стандартного fix-pipeline саме цього правила.
2. **Standalone mode** — якщо модуль виконується безпосередньо (`bun npm/rules/text/fix.mjs`), він самостійно піднімає повний CLI-оркестратор (`runRuleCli`) із завантаженням конфігурації, whitelist-фільтрацією та підсумковим виводом, повертаючи коректний `exit-code` для CI/IDE.

Сам файл не містить бізнес-логіки правила: він є **тонким адаптером** над двома утилітами (`runStandardRule` та `runRuleCli`) і покладається на конвенцію `import.meta.dirname` — тобто на те, що сусідні теки `lint/`, `policy/`, `js/` та файл `text.mdc` дають усю фактичну реалізацію (applies → JS-concerns → policy → mdc-refs).

Шлях фізично розміщений у директорії конкретного правила `text`, але код **повністю однаковий** для всіх правил репозиторію — `import.meta.dirname` робить його контекстно-залежним без жодних хардкодів `id` правила.

## Експорти / API

| Експорт | Тип                                             | Призначення                                                                                                                        |
| ------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `run`   | `function (ctx?: RuleContext): Promise<number>` | Іменований експорт для library mode. Викликається зовнішньою оркестрацією, повертає exit-code правила (`0` — OK, `1` — порушення). |

Файл **не має** default-експорту. Усі інші конструкції модуля (`if (isRunAsCli(...))`) виконуються при імпорті як side-effect, але активуються лише коли модуль є entry-point процесу — для звичайного `import` standalone-блок не спрацьовує.

## Функції

### `run(ctx)`

```js
export function run(ctx) {
  return runStandardRule(import.meta.dirname, ctx)
}
```

- **Сигнатура**: `function run(ctx?: RuleContext): Promise<number>`
- **Параметри**:
  - `ctx` _(необов'язковий)_ — об'єкт контексту прогону типу `RuleContext`, описаний у JSDoc-import у `../../scripts/lib/run-standard-rule.mjs`. Передається оркестратором і зазвичай містить кешовані результати walk'у файлової системи (`walkCache`) та інші спільні для пакетного прогону структури, щоб уникнути повторного сканування репозиторію кожним правилом.
- **Повертає**: `Promise<number>` — exit-code, який повертає `runStandardRule`. За конвенцією репозиторію:
  - `0` — порушень не знайдено (правило пройшло);
  - `1` — знайдено хоча б одне порушення (правило впало).
- **Side effects**: усі побічні ефекти делеговані в `runStandardRule`. Це включає:
  - читання файлів проєкту (без модифікацій);
  - виконання сабмодулів `applies`, `JS-concerns`, `policy`, `mdc-refs` із сусідніх тек;
  - друк діагностики у stdout/stderr (залежить від реалізації стандартного pipeline та `RuleContext`).
- **Передача `import.meta.dirname`**: ключова деталь — як перший аргумент передається **директорія цього файлу** (`npm/rules/text/`). Саме за нею `runStandardRule` ідентифікує правило (`id = basename(dirname)`) та знаходить його артефакти (mdc, applies, policy).

### Standalone-блок (top-level, не функція)

```js
if (isRunAsCli(import.meta.url)) {
  process.exit(await runRuleCli(import.meta.dirname))
}
```

- **Призначення**: дозволяє запускати модуль як автономний скрипт — `bun npm/rules/text/fix.mjs`. У такому режимі він має дати **повний еквівалент** виклику `npx @nitra/cursor fix text`, включно з:
  - завантаженням конфігурації проєкту;
  - застосуванням whitelist (відсіюванням файлів, що не підпадають під правило);
  - друком підсумку (summary) виконання.
- **Параметри**: немає (CLI-аргументи парсяться всередині `runRuleCli`).
- **Повертає / завершує**: `process.exit(n)` із `n`, який повернув `runRuleCli`. Це необхідно, щоб CI або IDE-інтеграція могли визначати успіх/невдачу за стандартним exit-code'ом.
- **Side effects**:
  - читання конфігурації пакета та проєкту;
  - читання й аналіз файлів проєкту;
  - вивід summary у stdout/stderr;
  - **завершення процесу** через `process.exit` (це окремо помічено `eslint-disable` коментарем `n/no-process-exit, unicorn/no-process-exit`, оскільки правила лінтера за замовчуванням забороняють `process.exit` поза entry-point файлами).
- **Перевірка `isRunAsCli`**: функція приймає `import.meta.url` і повертає `true` лише якщо саме цей файл — entry-point Node/Bun-процесу. Це класичний ідіоматичний еквівалент `if __name__ == "__main__"` із Python для ESM.

## Залежності

### Внутрішні (relative)

| Імпорт            | Модуль                                    | Що дає                                                                                                                                              |
| ----------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isRunAsCli`      | `../../scripts/lib/run-rule-cli.mjs`      | Предикат: чи запущений модуль як CLI entry-point (порівняння `import.meta.url` з `process.argv[1]`).                                                |
| `runRuleCli`      | `../../scripts/lib/run-rule-cli.mjs`      | Повний CLI-оркестратор одного правила: завантаження конфігурації, whitelist, виклик `run`, друк summary, повернення exit-code.                      |
| `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Стандартний fix-pipeline правила: послідовно виконує етапи **applies → JS-concerns → policy → mdc-refs**. Знаходить артефакти за `dirname` правила. |

Усі три імпорти — це shared-утиліти рівня `scripts/lib/`, спроєктовані саме для подвійного entry-point'у правил. Жодних зовнішніх npm-залежностей файл не імпортує напряму.

### Неявні (контекст виконання)

- **`import.meta.dirname`** — стандартне поле ESM (Node ≥ 20 / Bun): абсолютний шлях до директорії, що містить модуль. Використовується **двічі** і є ключовим для ідентифікації правила.
- **`import.meta.url`** — стандартне поле ESM: URL-форма шляху до модуля; передається в `isRunAsCli` для порівняння з `process.argv[1]`.
- **`process.exit`** — глобальний Node/Bun API; викликається лише у standalone-режимі.
- **Сусідні теки правила** (`./lint`, `./policy`, `./js`, `./text.mdc`, `./meta.json`) — фактичні артефакти, які `runStandardRule` знаходить через переданий `import.meta.dirname`. Файл `fix.mjs` сам їх не імпортує, але pipeline без них не запрацює.

## Потік виконання / Використання

### Сценарій 1 — Library mode (typical)

1. Зовнішній код (наприклад, агрегатор `npx @nitra/cursor fix`) робить `await import('npm/rules/text/fix.mjs')`.
2. Перевірка `isRunAsCli(import.meta.url)` повертає `false` (бо entry-point процесу — інший файл), standalone-блок **не виконується**.
3. Агрегатор викликає експортовану `run(ctx)`, передаючи спільний `ctx` (наприклад, з прогрітим `walkCache`).
4. `run` делегує виклик у `runStandardRule(import.meta.dirname, ctx)`.
5. `runStandardRule` ідентифікує правило за `basename(dirname)` = `text`, послідовно виконує етапи pipeline (applies → JS-concerns → policy → mdc-refs), читає сусідні артефакти, повертає exit-code.
6. Агрегатор отримує `Promise<number>` і інтегрує його в загальний підсумок (наприклад, агрегує максимум по всіх правилах).

### Сценарій 2 — Standalone mode

1. Розробник або CI виконує `bun npm/rules/text/fix.mjs` (або через npm-script).
2. Імпорти `isRunAsCli`, `runRuleCli`, `runStandardRule` зчитуються.
3. Експорт `run` стає доступним, але **ніким не використовується** в цьому сценарії.
4. `isRunAsCli(import.meta.url)` повертає `true`.
5. Виконується `await runRuleCli(import.meta.dirname)` — повний CLI-цикл: парсинг аргументів, завантаження конфігурації, whitelist, виклик внутрішнього еквівалента `run`, друк summary.
6. Результат передається в `process.exit(...)` — процес завершується із належним exit-code'ом для CI.

### Чому саме така архітектура

- **Однаковий код для всіх правил** — `fix.mjs` кожного правила в `npm/rules/<id>/` ідентичний; диференціація — виключно через директорію (`import.meta.dirname`) та артефакти в ній. Це усуває дублювання логіки entry-point'у та робить додавання нового правила механічним (скопіювати теку, поправити mdc/policy).
- **Подвійна роль (library + standalone)** — дозволяє з одного файлу і дебажити правило локально (швидкий feedback `bun rules/text/fix.mjs`), і інтегрувати його у пакетний прогон агрегатора без зміни вихідного коду.
- **`process.exit` лише в standalone-гілці** — у library-режимі повернення exit-code'у через `Promise<number>` дає агрегатору можливість зібрати всі результати й завершитися одним викликом, не давши окремому правилу обірвати процес передчасно.

### Rebuild Test (інваріанти, які має зберегти будь-яка перебудова файлу)

- Файл **має експортувати** функцію `run(ctx)`, що повертає `Promise<number>`.
- `run` **має** передавати `import.meta.dirname` як перший аргумент у `runStandardRule` (без хардкоду шляху або `id`).
- `run` **не повинна** викликати `process.exit` — лише повертати exit-code.
- Standalone-гілка **має** бути захищена `isRunAsCli(import.meta.url)`, щоб не спрацьовувати при `import` із зовнішнього коду.
- Standalone-гілка **має** використовувати `runRuleCli` (а не `runStandardRule` напряму), бо саме `runRuleCli` додає config-loading, whitelist та summary.
- Standalone-гілка **має** завершуватися `process.exit(await runRuleCli(import.meta.dirname))` із поверненням коректного exit-code'у — інакше CI не зможе зловити порушення.
- Файл **не повинен** містити власної логіки правила: вся специфіка `text` живе в сусідніх теках (`lint/`, `policy/`, `js/`) та `text.mdc`.
