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

## Огляд

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

1. **Library mode** — експортує функцію `run(ctx)`, яка викликається з оркестратора CLI (наприклад, з `runRuleCli` або з агрегаторів типу `n-fix`), що дозволяє запускати правило в межах загального прогону всіх правил без породження окремого процесу.
2. **Standalone mode** — якщо файл виконується безпосередньо (через `bun rules/<id>/fix.mjs` або `node rules/<id>/fix.mjs`), він виступає самостійним CLI-входом, повністю еквівалентним `npx @nitra/cursor fix hasura`: підвантажує config, застосовує whitelist, друкує summary та повертає процесний exit-code.

Уся фактична логіка правила (послідовність кроків `applies → JS-concerns → policy → mdc-refs`) винесена в спільну реалізацію `runStandardRule` із бібліотечного шару `scripts/lib/`. Файл-обгортка несе лише прив'язку правила до власної директорії (через `import.meta.dirname`) та dispatch між двома режимами запуску.

Відповідає конвенції двороль­ового `fix.mjs`, ухваленій для всіх «стандартних» правил у `npm/rules/*/`, тож має мінімальну поверхню коду й не містить специфіки `hasura`: тип правила, скоупи, перевірки та поліcі описуються деінде (`meta.json`, `hasura.mdc`, `js/`, `policy/`).

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

| Експорт | Тип                                      | Призначення                                                                                                                      |
| ------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `run`   | `(ctx?: RuleContext) => Promise<number>` | Library-mode прогін правила в спільному CLI orchestration. Повертає `0` при відсутності порушень або `1` при наявності порушень. |

Файл не має `default`-експорту й не експортує жодних інших символів.

Поведінка при безпосередньому запуску файлу як скрипта — побічний ефект (top-level `await runRuleCli(...)` + `process.exit(...)`) і не є частиною програмного API.

## Функції

### `run(ctx)`

Сигнатура: `export function run(ctx)`.

- **Параметри:**
  - `ctx` (`RuleContext`, опційний) — контекст прогону, що передається з оркестратора. Тип імпортовано (через JSDoc-tag `@param`) з `../../scripts/lib/run-standard-rule.mjs`. Очікувані поля контексту (за конвенцією `runStandardRule`) — спільні для всіх правил, серед них зокрема `walkCache` (кеш обходу файлової системи, щоб не повторювати walk між кількома правилами в одному прогоні). Якщо `ctx` не передано (`undefined`), `runStandardRule` сам ініціалізує мінімально необхідний контекст.
- **Повертає:** `Promise<number>` — exit-code правила:
  - `0` — застосовні файли пройшли всі етапи (`applies → JS-concerns → policy → mdc-refs`) без виявлених порушень.
  - `1` — знайдено принаймні одне порушення (або зафіксована помилка валідації, що інтерпретується як failure).
- **Side effects:**
  - Делегує всю роботу `runStandardRule(import.meta.dirname, ctx)`, який, своєю чергою, читає файли правила (`meta.json`, `*.mdc`, `js/*.mjs`, `policy/*.rego`) із директорії, обходить цільові файли проєкту, друкує діагностичні повідомлення у stdout/stderr та повертає підсумкове число порушень. Сам `run` стану не модифікує.
  - Прив'язує правило до своєї директорії саме через `import.meta.dirname`, тож при переміщенні файлу `fix.mjs` правило автоматично «переїде» разом із його артефактами (без хардкоду шляхів).

### Безіменна процедура top-level (standalone-блок)

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

- **Тип:** виконується один раз під час завантаження модуля.
- **Параметри:** немає (CLI-аргументи зчитуються всередині `runRuleCli` через `process.argv`).
- **Повертає:** нічого (виконує `process.exit`).
- **Side effects:**
  - `isRunAsCli(import.meta.url)` визначає, чи модуль завантажено як прямий entry-point (а не як `import`).
  - У випадку прямого запуску викликає `runRuleCli(import.meta.dirname)`, який повертає числовий exit-code, і негайно завершує процес із цим кодом через `process.exit`.
  - Через `process.exit` подальші асинхронні задачі (мікротаски, відкриті ресурси) можуть бути обірвані — це свідомо: standalone entry-point повинен повертати чіткий код для CI/IDE-інтеграцій.
- **Локальні правила лінту:**
  - Поряд із викликом стоїть прагма `// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE`, оскільки за загальним правилом `process.exit` заборонений, але для CLI-входів дозволений.

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

### Внутрішні (відносні імпорти)

- `../../scripts/lib/run-rule-cli.mjs`
  - `isRunAsCli(metaUrl: string): boolean` — детектор «чи запущено модуль як скрипт». Зазвичай порівнює `import.meta.url` із URL виконуваного скрипта (`process.argv[1]`), щоб розрізнити `import` та прямий запуск.
  - `runRuleCli(ruleDir: string): Promise<number>` — повноцінний CLI-runner: парсить аргументи, читає глобальний config (whitelist, severity, опції), запускає правило з директорії `ruleDir` і друкує summary. Повертає exit-code.
- `../../scripts/lib/run-standard-rule.mjs`
  - `runStandardRule(ruleDir: string, ctx?: RuleContext): Promise<number>` — стандартна послідовність кроків для «звичайного» правила: `applies → JS-concerns → policy → mdc-refs`. Це публічне ядро правил-обгорток на кшталт `fix.mjs`.
  - `RuleContext` (тип) — імпортовано лише в JSDoc для типізації параметра `run`.

### Зовнішні

Прямих залежностей від npm-пакетів файл не має. Усі сторонні бібліотеки, які можуть знадобитися (наприклад, walker, парсер `.mdc`, OPA-обгортки тощо), використовуються транзитивно через `run-standard-rule.mjs` та `run-rule-cli.mjs`.

### Залежність від файлів-побратимів у директорії правила

Хоча `fix.mjs` сам нічого з них не читає, `runStandardRule(import.meta.dirname)` спирається на сусідні артефакти у тій же директорії правила `hasura/`:

- `meta.json` — метадані правила (id, scope, опції тощо);
- `hasura.mdc` — людинозрозумілий опис правила у форматі Cursor `.mdc`;
- `js/` — JS-перевірки правила (`check-*.mjs`, fix-helpers);
- `policy/` — Rego-полісі для OPA-кроку.

Тож структурно файл `fix.mjs` має сенс лише як частина повної папки правила.

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

### Сценарій A — Library mode (з оркестратора)

1. Оркестратор (`n-cursor` CLI, агрегатор скілів, тести) робить `import { run } from '<path>/npm/rules/hasura/fix.mjs'`.
2. Викликає `await run(ctx)`, передаючи контекст із попередньо побудованим `walkCache` та іншими опціями.
3. `run` синхронно повертає результат виклику `runStandardRule(import.meta.dirname, ctx)`.
4. `runStandardRule`:
   - читає `meta.json` із директорії правила;
   - проганяє послідовність `applies` (фільтр цільових файлів) → JS-concerns (динамічно підвантажені `js/check-*.mjs`) → policy (OPA з `policy/*.rego`) → mdc-refs (валідація `.mdc`-посилань);
   - агрегує знахідки й повертає кількість порушень як exit-code (`0`/`1`).
5. Оркестратор підсумовує exit-коди всіх правил.

### Сценарій B — Standalone CLI

1. Користувач/CI виконує `bun npm/rules/hasura/fix.mjs [args...]`.
2. На завантаженні модуля Node/Bun обчислює `isRunAsCli(import.meta.url)` — для прямого запуску результат `true`.
3. Виконується `await runRuleCli(import.meta.dirname)`:
   - парсинг CLI-аргументів (`--whitelist`, `--severity`, прапори `-q`/`-v` тощо — деталі в `run-rule-cli.mjs`);
   - підвантаження конфіг-файлу пакета;
   - застосування whitelist;
   - виклик внутрішнього еквівалента `runStandardRule` для цієї ж директорії;
   - друк summary-таблиці результатів.
4. `process.exit(<exit-code>)` миттєво завершує процес зі значенням, яке повернув `runRuleCli`.

### Типові випадки використання

- **CI:** `bun npm/rules/hasura/fix.mjs` як standalone-крок у пайплайні; ненульовий exit-code зриває build.
- **Локально:** `npx @nitra/cursor fix hasura` запускає той самий entry-point через диспетчер CLI пакета (а не напряму).
- **Інтеграція в IDE:** редактор може імпортувати `run` і викликати його в окремому процесі для отримання структурованого результату по правилу `hasura` (наприклад, через виклик `bun -e "import('...fix.mjs').then(m => m.run())"`).
- **Агрегатори скілів** (наприклад, `n-fix`): паралельно або послідовно імпортують `run` із усіх правил `npm/rules/*/fix.mjs`, передають спільний `ctx` із `walkCache`, щоб уникнути повторного обходу файлової системи.

### Технічні зауваження щодо реалізації

- `import.meta.dirname` (Node ≥ 20.11 / Bun) повертає абсолютний шлях до директорії модуля без потреби в `fileURLToPath(import.meta.url)`. Це поточний рекомендований спосіб локалізації власної директорії в ESM-модулях.
- Прапор `await` у `process.exit(await runRuleCli(...))` працює завдяки top-level await у ESM — додаткової `main()`-обгортки не потрібно.
- Свідомо обираний `process.exit` (а не «м'який» вихід через `process.exitCode = ...`) гарантує детермінований код повернення для CI/IDE навіть якщо хтось у фоновому таску щось «недописав».
- Файл нейтральний щодо помилок: `runStandardRule` та `runRuleCli` самі вирішують, чи виносити exception у консоль і конвертувати її в exit-code; `fix.mjs` не обгортає виклики в `try/catch`.
