# applies.mjs

## Огляд

Модуль `applies.mjs` реалізує **applies-гейт** для правила `rust` у системі правил `n-cursor`. Його завдання — швидко відповісти на питання: «чи має взагалі сенс прогонити це правило в поточному репозиторії?». Якщо репозиторій не містить жодного Rust-кореня (тобто немає `Cargo.toml` ані в корені, ані в жодному вкладеному робочому каталозі), функція повертає `false`, і ранер `runStandardRule` пропускає всі концерни цього правила — як JS-перевірки, так і policy-концерни. Це уникає зайвої роботи в репозиторіях без Rust-коду.

Маркером застосовності є файл `Cargo.toml`:

- спершу швидка перевірка в `cwd` через `existsSync` (синхронно, без I/O-обходу);
- якщо в корені маркера немає — рекурсивний обхід дерева через `hasCargoTomlInTree`, який ігнорує штучні / службові директорії (`node_modules`, `.git`, `.next`, `.turbo`).

Окремо експортується функція `check()` — це **context-pass** репортер: коли applies повернув `true`, ранер усе одно очікує, що файл правила розрукує хоча б один контекст. `check()` просто друкує повідомлення «знайдено Cargo.toml — застосовуємо правила rust.mdc» і повертає exit-код `0`. Жодної реальної логіки лінтування / перевірок у цьому файлі немає — вся робота винесена в **policy-концерни** правила `rust`.

Файл написаний у форматі ESM (`.mjs`) та сумісний з Bun і Node.js (≥ модулів з підтримкою ESM та `node:` префіксу).

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

| Експорт   | Тип                                  | Призначення                                                              |
| --------- | ------------------------------------ | ------------------------------------------------------------------------ |
| `applies` | `(cwd?: string) => Promise<boolean>` | Гейт-функція: чи активувати правило `rust` для заданого `cwd`.           |
| `check`   | `() => number`                       | Context-pass репортер: друкує позитивне повідомлення, повертає exit-код. |

Інших публічних експортів немає. Константа `IGNORED_DIR_NAMES` — внутрішня (module-level), назовні не експортується.

## Функції

### `applies(cwd = process.cwd())`

**Сигнатура:** `applies(cwd?: string): Promise<boolean>`

**Параметри:**

- `cwd` _(string, опційний)_ — абсолютний (або відносний від процесу) шлях до кореня репозиторію, який треба перевірити. За замовчуванням — `process.cwd()` (поточний робочий каталог процесу Node/Bun).

**Повертає:** `Promise<boolean>`

- `true` — правило `rust` застосовне (знайдено хоча б один `Cargo.toml` у корені або десь у дереві);
- `false` — застосовувати правило не треба (Rust у репозиторії не знайдено).

**Алгоритм:**

1. Складає шлях `join(cwd, 'Cargo.toml')` і синхронно перевіряє його існування через `existsSync`.
2. Якщо файл є — одразу повертає `Promise.resolve(true)` (швидкий вихід; решта дерева не сканується).
3. Якщо в корені маркера немає — викликає `hasCargoTomlInTree(cwd, IGNORED_DIR_NAMES)` і повертає його результат, загорнутий у промісі (значення може бути проміс або bool — у будь-якому разі `Promise.resolve` нормалізує до `Promise<boolean>`).

**Side effects:**

- Один синхронний виклик `existsSync` (file-system stat) для шляху `<cwd>/Cargo.toml`.
- За потреби — рекурсивний обхід директорії `cwd` (виконує `hasCargoTomlInTree`); у межах цього обходу директорії з `IGNORED_DIR_NAMES` пропускаються.
- Не модифікує файлову систему, не пише в `stdout`/`stderr`, не виконує мережевих запитів.

**Не кидає винятків** у нормальних умовах: `existsSync` повертає `false` для неіснуючих/недоступних шляхів. Виняткові ситуації можуть прилетіти лише з глибин `hasCargoTomlInTree` (наприклад, помилка `readdir`), але це поведінка залежного модуля, а не самого `applies`.

### `check()`

**Сигнатура:** `check(): number`

**Параметри:** немає.

**Повертає:** `number` — exit-код:

- `0` — все OK (стандартний шлях; функція завжди йде цим шляхом, бо вона лише друкує `pass`);
- `1` — порушення (теоретично можливо, якщо репортер у майбутньому почне накопичувати помилки; зараз цей кейс не використовується).

**Алгоритм:**

1. Створює інстанс репортера через `createCheckReporter()`.
2. Викликає `reporter.pass('Знайдено Cargo.toml — застосовуємо правила rust.mdc')` — реєструє позитивний context-pass.
3. Повертає `reporter.getExitCode()`.

**Side effects:**

- Друкує повідомлення про `pass` у stdout у форматі, заданому `createCheckReporter` (зазвичай — рядок з префіксом / маркером).
- Не торкається файлової системи й мережі.

**Контракт із ранером:** `runStandardRule` викликає `check()` лише після того, як `applies()` повернув `true`. Тому повідомлення в стилі «знайдено Cargo.toml» завжди коректне на момент друку — applies-гейт уже відпрацював перед цим.

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

### Зовнішні (стандартна бібліотека Node.js)

- `node:fs` → `existsSync` — синхронна перевірка існування файлу.
- `node:path` → `join` — крос-платформне склеювання сегментів шляху.

### Внутрішні (відносні до файлу)

- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика репортера для context-pass / помилок із підрахунком exit-коду.
- `../lib/has-cargo-toml.mjs` → `hasCargoTomlInTree` — рекурсивний обхід дерева директорій з пошуком `Cargo.toml` і пропуском ігнор-папок.

### Module-level константи

- `IGNORED_DIR_NAMES: Set<string>` — `{ 'node_modules', '.git', '.next', '.turbo' }`. Передається в `hasCargoTomlInTree` як набір імен директорій, у які заходити не треба під час рекурсивного пошуку.

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

### Типовий сценарій (ранер правил)

1. `runStandardRule` для правила `rust` спершу імпортує модуль `applies.mjs`.
2. Викликає `await applies()` (без аргументів — буде використано `process.cwd()`).
3. Якщо результат `false` — ранер пропускає правило цілком: ні JS-концерни (`check`), ні policy-концерни не виконуються; правило вважається «not applicable».
4. Якщо результат `true` — ранер послідовно виконує всі концерни правила:
   - викликає експортований `check()` як «context-pass» — щоб у звіті був видимий маркер «правило застосовано»;
   - запускає policy-концерни (фактичні перевірки коду / структури), які живуть в інших файлах модуля `rust`.

### Приклад прямого виклику

```js
import { applies, check } from './npm/rules/rust/js/applies.mjs'

const ok = await applies('/path/to/some/repo')
if (ok) {
  const code = check()
  process.exit(code)
}
```

### Ребра (edge cases)

- **`cwd` не існує / недоступний:** `existsSync` поверне `false`; далі `hasCargoTomlInTree` обходитиме неіснуючий шлях (поведінка визначається ним).
- **`Cargo.toml` лежить глибоко під ігнор-папкою** (наприклад, всередині `node_modules/.../Cargo.toml`): його **не** знайдуть — `IGNORED_DIR_NAMES` свідомо пропускає такі директорії, бо це не «справжній» Rust-корінь репозиторію.
- **`Cargo.toml` у корені `cwd`:** виконується лише `existsSync` — рекурсивний обхід не запускається (оптимізація).
- **Множинні `Cargo.toml` глибоко в дереві:** достатньо знайти хоча б один — `hasCargoTomlInTree` має повернути `true` при першому ж збігу.

### Інваріанти

- `applies()` завжди повертає `Promise<boolean>` — навіть для синхронного fast-path через `Promise.resolve(true)`.
- `check()` синхронна — не повертає Promise; завжди повертає число (exit-код).
- `applies()` ідемпотентна та не має станy між викликами (немає кешу).

## Rebuild Test

Якщо видалити файл `applies.mjs` і відновлювати його з нуля по цій документації, реалізація має:

- бути ESM-модулем (`.mjs`) з ES-імпортами `node:fs` / `node:path`;
- імпортувати `createCheckReporter` із `../../../scripts/lib/check-reporter.mjs` і `hasCargoTomlInTree` із `../lib/has-cargo-toml.mjs`;
- містити module-level `Set` з рівно цими іменами: `'node_modules'`, `'.git'`, `'.next'`, `'.turbo'`;
- експортувати **named** функцію `applies(cwd = process.cwd())` з логікою «`existsSync(join(cwd, 'Cargo.toml'))` → інакше делегат у `hasCargoTomlInTree(cwd, IGNORED_DIR_NAMES)`», результати загорнуті в `Promise.resolve`;
- експортувати **named** синхронну функцію `check()`, яка створює репортер, реєструє `pass('Знайдено Cargo.toml — застосовуємо правила rust.mdc')` і повертає `reporter.getExitCode()`;
- не мати default-експорту;
- не мати побічних ефектів на рівні модуля (тільки декларація `Set` — допустимо).
