# `npm/rules/feedback/fix.mjs`

## Огляд

Файл є точкою входу (entry-point) для правила `feedback` у CLI-наборі `@nitra/cursor`. Він реалізує дві паралельні ролі того ж самого модуля:

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

Сам файл логіки правила не містить — він є тонким адаптером, що делегує роботу до спільного раннера `runStandardRule`. Стандартний потік правила, який запускається через цей адаптер: `applies → JS-concerns → policy → mdc-refs` (тобто перевіряється застосовність до проєкту, далі — JS-специфічні перевірки, далі — policy-шар, наприкінці — синхронізація посилань у відповідному `.mdc`-файлі).

Модуль використовує `import.meta.dirname`, тому він повністю прив'язаний до місця свого розташування: каталог правила (`npm/rules/feedback/`) автоматично стає коренем, у якому раннер шукає `meta.json`, `feedback.mdc` та інші артефакти.

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

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

Side-експортів немає. Default-експорту немає.

Поведінка при прямому запуску файлу як CLI (через `process.exit(await runRuleCli(...))`) — це не експорт, а top-level side-effect, активний лише коли `isRunAsCli(import.meta.url)` повертає `true`.

## Функції

### `run(ctx)`

```js
export function run(ctx)
```

- **Призначення:** запустити правило `feedback` у library-режимі. Делегує всю роботу до `runStandardRule`, передаючи каталог поточного файлу як ідентифікатор правила.
- **Параметри:**
  - `ctx` — `RuleContext | undefined`. Опціональний контекст прогону. Тип імпортується через JSDoc-посилання `import('../../scripts/lib/run-standard-rule.mjs').RuleContext`. Зокрема, контекст несе спільні структури між правилами (наприклад, `walkCache` — кеш обходу файлової системи, щоб не сканувати дерево кілька разів у разі прогону кількох правил поспіль). Якщо `ctx` не переданий, `runStandardRule` створює власний внутрішній контекст.
- **Повертає:** `Promise<number>` — exit-code:
  - `0` — правило застосовне і порушень не знайдено, або правило не застосовне до поточного проєкту (`applies` повернув `false`);
  - `1` — знайдені порушення (policy / JS-concerns / mdc-refs).
- **Side effects:** жодних прямих у цій функції. Опосередковано через `runStandardRule` можливі: читання файлів проєкту, читання `meta.json` і `feedback.mdc`, лог у stdout/stderr, мутація переданого `walkCache`.
- **Винятки:** функція сама не кидає; будь-яка помилка приходить як rejected promise від `runStandardRule`.

### Top-level standalone-блок

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

- **Призначення:** перетворити цей же модуль на CLI-скрипт. Якщо файл був запущений напряму (а не імпортований), виконується повний CLI-цикл правила — еквівалент `npx @nitra/cursor fix feedback`.
- **Виклик:** `runRuleCli(import.meta.dirname)` отримує абсолютний шлях до каталогу правила; всередині раннера це задає, який саме `meta.json`/`*.mdc` і `applies/policy/...`-функції підтягуються.
- **Повертає / завершення:** результат `runRuleCli` (число) йде в `process.exit(...)`, тобто процес одразу завершується з відповідним exit-code. Це навмисно: standalone entry-point має повернути код виходу для CI/IDE-інтеграцій.
- **Спеціальні маркери:**
  - `// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit` — свідома відмова від загальної заборони `process.exit` саме тут, бо це standalone-точка входу.
- **Side effects:** завершує процес Node/Bun.

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

Файл імпортує два внутрішні модулі (relative-шляхи, без зовнішніх npm-пакетів):

| Імпорт            | З файлу                                   | Що використовується                     | Роль                                                                                                                                                   |
| ----------------- | ----------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isRunAsCli`      | `../../scripts/lib/run-rule-cli.mjs`      | Функція-предикат                        | Визначає, чи цей `.mjs`-файл був запущений напряму як CLI (через `import.meta.url`), а не імпортований як модуль.                                      |
| `runRuleCli`      | `../../scripts/lib/run-rule-cli.mjs`      | Async-функція                           | Виконує повний CLI-цикл правила: завантаження конфігу, whitelist-фільтрацію, прогін стандартного раннера, форматування підсумку, повернення exit-code. |
| `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Async-функція + JSDoc-тип `RuleContext` | Стандартний раннер для правил: `applies → JS-concerns → policy → mdc-refs`. Шукає артефакти у переданому каталозі.                                     |

Глобали з рантайму:

- `import.meta.dirname` — абсолютний шлях каталогу поточного `.mjs`-файлу (Bun / Node ≥ 20.11). Використовується як «корінь правила».
- `import.meta.url` — file-URL модуля; передається в `isRunAsCli` для надійного порівняння з `process.argv[1]`.
- `process.exit` — викликається лише у standalone-гілці.

Зовнішні пакети не імпортуються.

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

### Сценарій A — імпорт як library

```js
import { run } from './npm/rules/feedback/fix.mjs'

const code = await run(/* ctx */)
if (code !== 0) {
  // правило знайшло порушення
}
```

Послідовність всередині:

1. Викликається `run(ctx)`.
2. `run` повертає `runStandardRule(import.meta.dirname, ctx)`.
3. Усередині `runStandardRule`:
   - перевіряє `applies` (чи правило застосовне до проєкту);
   - проганяє JS-concerns (lint/structure);
   - проганяє policy-перевірки;
   - звіряє `mdc-refs` (узгодженість посилань між кодом і `feedback.mdc`).
4. Повертається `0` або `1`.

Top-level `if (isRunAsCli(...))` у цьому випадку не спрацьовує — `isRunAsCli(import.meta.url)` повертає `false`, бо стартова точка процесу — інший файл.

### Сценарій B — прямий запуск як CLI

```bash
bun npm/rules/feedback/fix.mjs
# або еквівалент:
npx @nitra/cursor fix feedback
```

Послідовність:

1. Node/Bun завантажує модуль; виконуються `import`-и.
2. Перевіряється `isRunAsCli(import.meta.url)` → `true`.
3. Викликається `await runRuleCli(import.meta.dirname)`. Цей раннер:
   - завантажує загальний конфіг проєкту;
   - застосовує whitelist (якщо в конфізі обмежений набір правил);
   - всередині все одно проганяє ту ж саму `runStandardRule`-логіку;
   - друкує форматований summary;
   - повертає exit-code.
4. `process.exit(code)` миттєво завершує процес із отриманим кодом, який потім читає CI/IDE.

### Чому дві ролі в одному файлі

Конвенція в `npm/rules/<id>/fix.mjs` дозволяє:

- оркестратору проганяти всі правила одним процесом, шарячи `walkCache` (швидко, для batch-режимів);
- розробнику запустити **одне** правило точково з шелла без обгорток і отримати CI-сумісний exit-code.

Жодного дублювання логіки немає — обидві гілки в кінцевому рахунку викликають один і той самий `runStandardRule`, просто standalone-гілка додає шар CLI-обгортки (`runRuleCli`).

## Rebuild Test

Чи можна за цим документом без доступу до файлу відтворити модуль еквівалентної поведінки? Так:

- ім'я експорту: `run`, сигнатура `(ctx) => Promise<number>`;
- тіло: `return runStandardRule(import.meta.dirname, ctx)`;
- top-level `if`: `isRunAsCli(import.meta.url)` → `process.exit(await runRuleCli(import.meta.dirname))`;
- два імпорти з точно вказаних шляхів (`../../scripts/lib/run-rule-cli.mjs` для `isRunAsCli`/`runRuleCli`, `../../scripts/lib/run-standard-rule.mjs` для `runStandardRule`);
- ESLint-disable коментар над `process.exit` для `n/no-process-exit` і `unicorn/no-process-exit`;
- JSDoc-блок над `run` із посиланням на тип `RuleContext` через `import('...')`-нотацію.

Усі ці елементи описані вище — реконструкція еквівалентного файлу можлива.
