# marksman_config.mjs

## Огляд

Модуль `marksman_config.mjs` — це концерн правила `ci4` (`ci4.mdc`), що відповідає за матеріалізацію canonical-конфіга для Marksman LSP у корені проєкту. Модуль експортує єдину функцію `check`, яка перевіряє наявність файлу `.marksman.toml` у заданому робочому каталозі (`cwd`) і, якщо файлу немає, копіює туди baseline-варіант, що поставляється з пакета `@nitra/cursor`.

Призначення модуля — забезпечити стабільне, передбачуване середовище для Marksman LSP, який використовується для роботи з Markdown-документами в репо (ADR, документація, README). Без явного `.marksman.toml` Marksman використовує свої вбудовані дефолти, які НЕ збігаються з вимогами правила `ci4.mdc`:

- `ci4.mdc` вимагає увімкненого GLFM (GitHub-Flavored Markdown) — для підтримки `> [!NOTE]`-alerts, таблиць, `- [ ]` task-lists.
- `ci4.mdc` вимагає стилю wiki-link-посилань `file-stem` (ADR-slug дорівнює імені файлу без розширення), а дефолт Marksman — `title-slug-ref`.

Модуль ідемпотентний: якщо `.marksman.toml` уже існує у `cwd` (навіть із ручними кастомними правками користувача), він НЕ перетирається. Користувач отримає тільки звіт про факт існування, а ручні модифікації зберігаються між прогонами.

Файл цілеспрямовано копіюється у `cwd` (а не глибше у дерево), бо Marksman визначає workspace-root за розташуванням свого `.marksman.toml`. Розташування файлу в корені репозиторію дає Marksman можливість бачити одночасно директорії `docs/` та `README.md` усіх workspaces як один загальний workspace.

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

Модуль експортує одну named-функцію:

- `check(cwd?: string): Promise<number>` — асинхронна перевірка/матеріалізація `.marksman.toml`.

Інших публічних експортів немає. Внутрішні константи (`HERE`, `MARKSMAN_BASELINE_PATH`, `MARKSMAN_TARGET_FILENAME`) — module-private і не призначені для прямого використання ззовні.

## Функції

### `check(cwd?)`

**Сигнатура:**

```js
export async function check(cwd = process.cwd()): Promise<number>
```

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

- `cwd` (`string`, опціональний) — абсолютний шлях до кореня проєкту, де має бути матеріалізований `.marksman.toml`. Значення за замовчуванням — `process.cwd()`. Дефолт зроблено для сумісності з прямим викликом із CLI (без передачі аргументів).

**Повертає:**

- `Promise<number>` — exit-код перевірки, отриманий через `reporter.getExitCode()`:
  - `0` — успіх. Можливі підстани:
    - Файл `.marksman.toml` уже існував у `cwd` (репортується через `reporter.pass(...)`, нічого не змінюється).
    - Файл був успішно створений із canonical baseline (репортується через `reporter.pass(...)` з префіксом «створено з canonical baseline»).
  - `1` — фейл. Підстава: canonical baseline (`data/marksman_config/marksman.baseline.toml`) відсутній всередині самого пакета `@nitra/cursor` — означає, що пакет встановлений некоректно. Користувачу пропонується перевстановити `@nitra/cursor`.

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

1. Створити instance `reporter` через `createCheckReporter()` із `scripts/lib/check-reporter.mjs`.
2. Перевірити, що baseline-файл усередині пакета існує (`existsSync(MARKSMAN_BASELINE_PATH)`).
   - Якщо НЕ існує — викликати `reporter.fail(...)` з повідомленням про зламану установку та повернути exit-код (це буде `1`).
3. Сформувати цільовий шлях `target = join(cwd, '.marksman.toml')`.
4. Перевірити, чи `target` уже існує (`existsSync(target)`).
   - Якщо існує — викликати `reporter.pass(...)` із повідомленням «`.marksman.toml` існує (<relative-шлях>)» і повернути exit-код (`0`). Файл НЕ перезаписується.
5. Якщо файл НЕ існує — скопіювати baseline у `target` через `await copyFile(MARKSMAN_BASELINE_PATH, target)`.
6. Викликати `reporter.pass(...)` з повідомленням «`.marksman.toml` створено з canonical baseline (<relative-шлях>) (ci4.mdc)» і повернути exit-код (`0`).

**Side effects:**

- Читання файлової системи: два виклики `existsSync` (синхронно).
- Запис у файлову систему: один виклик `copyFile` — створює файл `<cwd>/.marksman.toml`, копіюючи вміст із baseline-файлу пакета. Існуючі файли НЕ перезаписуються.
- Виклики `reporter.pass(...)` / `reporter.fail(...)` мають свої побічні ефекти (typovo — вивід у stdout/stderr, акумуляція стану звіту в `reporter`). Деталі залежать від реалізації `createCheckReporter`.

**Помилки:**

- Якщо `copyFile` кине помилку (наприклад, через відсутність прав запису в `cwd` чи фізично заповнений диск) — вона НЕ перехоплюється всередині `check` і прокинеться вище, в caller. Caller відповідає за відлов і репорт.
- Якщо `cwd` не існує — `copyFile` теж кине ENOENT.

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

### Імпорти з Node.js стандартної бібліотеки

- `existsSync` із `node:fs` — синхронна перевірка існування файлів. Використовується двічі: для baseline і для target.
- `copyFile` із `node:fs/promises` — асинхронне копіювання файлу.
- `dirname`, `join`, `relative` із `node:path` — конструювання та форматування шляхів:
  - `dirname` — отримати директорію поточного модуля.
  - `join` — сконструювати абсолютний шлях до baseline та цільового файлу.
  - `relative` — обчислити шлях відносно `cwd` для людиночитного логування.
- `fileURLToPath` із `node:url` — конвертувати `import.meta.url` у файловий шлях (ESM-патерн отримання `__dirname`).

### Внутрішні залежності проєкту

- `../../../scripts/lib/check-reporter.mjs` — модуль, що експортує `createCheckReporter`. Реалізує уніфікований інтерфейс репорту для concern-функцій правил: методи `pass(message)`, `fail(message)` і `getExitCode()`. Усі concern-модулі правил `ci4` (та інші правила) використовують цей репортер для єдиного UX.

### Файлові залежності (data assets)

- `./data/marksman_config/marksman.baseline.toml` — canonical-baseline TOML-конфіг Marksman LSP, що поставляється разом із пакетом `@nitra/cursor`. Цей файл — джерело правди для дефолтного `.marksman.toml`. Якщо файл відсутній — `check` поверне `1`.

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

### Типовий контекст виклику

Модуль викликається фреймворком правил `ci4.mdc` як один із concern-чеків (поряд із іншими файлами у `npm/rules/ci4/js/`). Кожен concern перевіряє один аспект конфігурації проєкту відповідно до правила `ci4.mdc`. `marksman_config` відповідає за аспект «Marksman LSP має консистентну конфігурацію».

### Інтеграція

1. Зовнішній runner (зазвичай CLI `n-cursor` або агрегатор правил) імпортує `check` і викликає його:
   ```js
   import { check } from '@nitra/cursor/npm/rules/ci4/js/marksman_config.mjs'
   const code = await check(process.cwd())
   process.exit(code)
   ```
2. `check` повертає exit-код:
   - `0` — concern пройдений, можна продовжувати.
   - `1` — fatal, треба зупинитися або репортувати юзеру.

### Сценарії

- **Свіжий клон репо без `.marksman.toml`**: на першому прогоні правила `ci4` файл буде створений із baseline. Подальші прогони побачать існуючий файл і не змінюватимуть його.
- **Користувач уже має кастомний `.marksman.toml`**: модуль НЕ перетирає його. Користувач може налаштувати Marksman під свої потреби (наприклад, додати specific completion-теги), і ця конфігурація переживе всі майбутні запуски `ci4`.
- **Зламана установка `@nitra/cursor` (baseline відсутній)**: модуль миттєво повертає `1` із пояснювальним повідомленням про необхідність перевстановити пакет. Жодних змін у `cwd` не вноситься.

### Ідемпотентність і безпека

- Функція ідемпотентна: повторні виклики на тому самому `cwd` дають однаковий результат — або `0` із пояснювальним «існує», або `0` із «створено» лише на першому прогоні.
- Безпека для користувацьких правок: модуль НЕ змінює існуючий `.marksman.toml`, навіть якщо його вміст застарів чи розходиться з baseline. Це свідома trade-off на користь юзерських кастомізацій. Якщо потрібно примусове оновлення baseline — користувач має видалити файл вручну.

## Rebuild Test

Перевірка повноти документації — чи можна за цим описом відтворити поведінку модуля з нуля без перегляду коду:

- Public API: `check(cwd?)` — задокументовано (сигнатура, дефолт, повернення, exit-коди).
- Алгоритм: 6 кроків (reporter → перевірка baseline → формування target → перевірка target → copy → repоrt) — задокументовано.
- Залежності: всі імпорти (`node:fs`, `node:fs/promises`, `node:path`, `node:url`, `check-reporter.mjs`, baseline TOML) — перелічено з призначенням.
- Side effects: I/O (read+write), вивід у репортер — описано.
- Граничні випадки: відсутній baseline (exit 1), існуючий target (no-op, exit 0), створення нового файлу (exit 0), помилки copyFile (прокидаються нагору) — описано.
- Контекст і мотивація: чому файл у `cwd`, чому НЕ перезаписується, чому потрібен явний конфіг (GLFM, file-stem) — описано.

Реконструкція коду на основі цього документа повинна давати функціонально еквівалентний модуль.
