# skill_meta.mjs

## Огляд

Модуль `skill_meta.mjs` — перевірка валідності метаданих скілів (`meta.json`) пакета `@nitra/cursor`. Належить до концерну правила `npm-module` (тека `npm/rules/npm-module/js/`).

Перевірка застосовується **виключно в репозиторії пакета**, де є каталог `npm/skills/`. Якщо каталог відсутній (наприклад, у проєктах-споживачах залежності), перевірка пропускається без помилки (повертає `pass`).

Для кожного підкаталогу `npm/skills/<id>/` модуль виконує контракт:

- забороняється залишковий файл `auto.md` (метадані мігровані на `meta.json`);
- файл `meta.json` має бути присутнім і валідним JSON-об'єктом з очікуваною структурою;
- поле `worktree` обов'язкове і має бути `boolean`;
- поле `auto` опціональне, але якщо є — має бути або рядком `"завжди"`, або непорожнім масивом рядків.

Результат акумулюється через `check-reporter` і повертається кодом виходу `0` (OK) або `1` (порушення).

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

| Експорт | Тип        | Призначення                                                                |
| ------- | ---------- | -------------------------------------------------------------------------- |
| `check` | `function` | Іменований експорт; функція-перевірка метаданих скілів у вказаному корені. |

Модуль є ESM (`.mjs`), без `default` експорту.

## Функції

### `check(cwd?)`

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

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

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

| Параметр | Тип      | Обов'язковий | За замовчуванням | Опис                                                             |
| -------- | -------- | ------------ | ---------------- | ---------------------------------------------------------------- |
| `cwd`    | `string` | ні           | `process.cwd()`  | Корінь репозиторію; від нього резолвиться шлях до `npm/skills/`. |

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

`Promise<number>` — код виходу від `reporter.getExitCode()`:

- `0` — усі перевірені скіли валідні (або каталог `npm/skills/` відсутній);
- `1` — щонайменше одне порушення зафіксовано через `reporter.fail(...)`.

Хоча всередині немає `async`/`await`, функція обертає результат у `Promise.resolve(...)` для однорідного API з іншими `check`-функціями в цьому проєкті.

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

1. Створюється локальний звітник: `reporter = createCheckReporter()`.
2. Будується шлях `skillsDir = join(cwd, 'npm', 'skills')`.
3. Якщо `skillsDir` не існує (`existsSync(...) === false`):
   - викликається `reporter.pass('npm/skills/ відсутній — немає скілів для валідації')`;
   - повертається `Promise.resolve(reporter.getExitCode())` (рання гілка).
4. Інакше — ітерація `readdirSync(skillsDir, { withFileTypes: true })`:
   - пропускаються не-директорії та назви, що починаються з крапки (`.` префікс);
   - для кожного скіла `id = entry.name` будується `skillDir = join(skillsDir, id)` і прапор `skillOk = true`.
5. Для кожного скіла послідовно перевіряється:
   - **Залишковий `auto.md`**: якщо `existsSync(join(skillDir, 'auto.md'))`, фіксується
     `fail("skills/<id>: залишковий auto.md — видали (метадані тепер у meta.json)")` і `skillOk = false`.
   - **`meta.json` присутній/валідний**: викликається `raw = readSkillMetaRaw(skillDir)`.
     Якщо `raw` falsy — фіксується `fail("skills/<id>: відсутній або невалідний meta.json ...")` і виконується `continue` (решта перевірок для цього скіла пропускається).
   - **Тип `worktree`**: якщо `typeof raw.worktree !== 'boolean'` — `fail("skills/<id>: meta.json.worktree має бути boolean")` і `skillOk = false`.
   - **Розпізнаваність `auto`**: якщо `raw.auto !== undefined` і `parseSkillAutoSpec(raw.auto) === null` — `fail("skills/<id>: meta.json.auto нерозпізнане — очікується \"завжди\" або непорожній масив правил")` і `skillOk = false`.
6. Якщо після всіх перевірок цього скіла `skillOk === true`, фіксується `pass("skills/<id>: meta.json валідний")`.
7. Після проходу всіх скілів повертається `Promise.resolve(reporter.getExitCode())`.

**Side effects:**

- читання файлової системи (`existsSync`, `readdirSync`) — синхронне;
- мутація стану внутрішнього звітника (`createCheckReporter()`) через `pass(...)` / `fail(...)`;
- запис у `stdout`/`stderr` залежить від реалізації `check-reporter` (зазвичай агрегує повідомлення для подальшого виводу викликачем);
- **не змінює** жодних файлів і **не** створює директорій.

**Поведінкові інваріанти:**

- Відсутність `npm/skills/` — не помилка, а явний `pass` (модуль працює і в репо-споживачі).
- Невалідний `meta.json` зупиняє подальші перевірки конкретного скіла (`continue`), але **не зупиняє** обхід інших скілів.
- Прихований запис (назва починається з `.`) ігнорується, тож `.DS_Store` чи `.tmp` не плутають перевірку.
- Перевірка детермінована: результат залежить тільки від вмісту `npm/skills/` у `cwd`.

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

### Стандартна бібліотека Node.js

| Модуль      | Імпортовані символи         | Використання                                                         |
| ----------- | --------------------------- | -------------------------------------------------------------------- |
| `node:fs`   | `existsSync`, `readdirSync` | перевірка наявності `npm/skills/`, `auto.md`; читання списку скілів. |
| `node:path` | `join`                      | побудова крос-платформних шляхів до каталогів і файлів.              |

### Внутрішні модулі проєкту

| Модуль                                    | Імпортовані символи                      | Використання                                                                |
| ----------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- |
| `../../../scripts/lib/check-reporter.mjs` | `createCheckReporter`                    | створення звітника з API `pass(msg)` / `fail(msg)` / `getExitCode()`.       |
| `../../../scripts/lib/skill-meta.mjs`     | `readSkillMetaRaw`, `parseSkillAutoSpec` | читання сирого `meta.json` зі скіла; парсинг/валідація специфікації `auto`. |

### Контракти зовнішніх модулів (як використовуються в коді)

- `createCheckReporter()` → об'єкт із методами `pass(message: string)`, `fail(message: string)`, `getExitCode(): number` (`0` якщо не було `fail`, `1` якщо хоч раз був).
- `readSkillMetaRaw(skillDir: string)` → розпарсений об'єкт `meta.json` або falsy (наприклад, `null`) якщо файл відсутній/невалідний.
- `parseSkillAutoSpec(value)` → нормалізована специфікація `auto` або `null`, якщо значення нерозпізнане. Прийнятні форми: рядок `"завжди"` або непорожній масив рядків.

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

### Контекст

Модуль викликається з раннера правил концерну `npm-module` (точка входу `npm/rules/npm-module/js/index.mjs` або аналог). У типовому пайплайні `bun run lint` / `n-cursor check` всі `check`-функції правил зібрані в реєстр і виконуються послідовно; результат об'єднується в загальний код виходу процесу.

### Типовий виклик

```js
import { check } from './skill_meta.mjs'

const exitCode = await check() // використає process.cwd()
process.exit(exitCode)
```

Або з явним коренем (тести/інтеграція):

```js
const exitCode = await check('/abs/path/to/repo-root')
```

### Сценарій 1: репо самого пакета `@nitra/cursor`

- `npm/skills/` існує, містить кілька скілів (наприклад, `n-docgen/`, `n-fix/`, `mdc-check/`).
- Для кожного скіла читається `meta.json`, перевіряються `worktree` (boolean) і `auto` (опціонально).
- Якщо хоч один скіл має `auto.md`, або відсутній/невалідний `meta.json`, або `worktree` не boolean, або `auto` нерозпізнане — функція повертає `1`.
- Інакше — `0`.

### Сценарій 2: проєкт-споживач `@nitra/cursor`

- `npm/skills/` відсутній (інша файлова структура).
- Функція фіксує `pass(...)`, повертає `0` і не виконує жодної ітерації.
- Це дозволяє правилам пакета транзитивно застосовуватися в чужих проєктах без false-negative.

### Сценарій 3: переходова міграція на `meta.json`

- У скілі лишилася стара пам'ятка-метадані `auto.md` поряд з новим `meta.json`.
- Перевірка явно фейлить такий скіл повідомленням `залишковий auto.md — видали (метадані тепер у meta.json)`.
- Це примушує закрити технічний борг міграції.

### Очікуваний формат `meta.json`

```json
{
  "worktree": true,
  "auto": "завжди"
}
```

або

```json
{
  "worktree": false,
  "auto": ["n-bun", "n-flow"]
}
```

або (мінімально):

```json
{
  "worktree": false
}
```

### Невалідні приклади (будуть зафейлені)

- відсутній файл `meta.json` → `відсутній або невалідний meta.json`;
- `{"worktree": "yes"}` → `meta.json.worktree має бути boolean`;
- `{"worktree": true, "auto": []}` або `{"worktree": true, "auto": "always"}` → `meta.json.auto нерозпізнане ...`;
- наявний `auto.md` поряд → `залишковий auto.md — видали`.

### Rebuild Test

Перевірка інваріантів модуля (можна виконати вручну для регресії):

1. У тимчасовому корені створити порожню директорію без `npm/skills/` → `check(tmp)` має повернути `0` і зафіксувати `pass` про відсутність каталогу.
2. Створити `npm/skills/foo/meta.json` з валідним вмістом `{"worktree": true}` → `check(tmp)` має повернути `0`.
3. Замінити `worktree` на рядок (`"true"`) → `check(tmp)` має повернути `1` з повідомленням про boolean.
4. Додати `npm/skills/foo/auto.md` → `check(tmp)` має повернути `1` з повідомленням про залишковий `auto.md`.
5. Видалити `meta.json` → `check(tmp)` має повернути `1` з повідомленням про відсутній/невалідний `meta.json`.
6. Створити `npm/skills/.hidden/` — перевірка має ігнорувати її (повернути `0`, якщо інших скілів немає).
7. Додати `npm/skills/bar/meta.json` з `{"worktree": false, "auto": "завжди"}` → `pass`; замінити `auto` на `"always"` → `fail` про нерозпізнане `auto`.
