@nitra/cursor 1.11.7 → 1.11.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +48 -13
  3. package/bin/n-cursor.js +12 -9
  4. package/package.json +4 -1
  5. package/rules/abie/abie.mdc +1 -1
  6. package/rules/abie/utils/k8s-tree.mjs +1 -1
  7. package/rules/adr/{js → fix}/hooks/check.mjs +1 -1
  8. package/rules/bun/{js → fix}/layout/check.mjs +2 -2
  9. package/rules/ga/{js → lint}/lint.mjs +1 -1
  10. package/rules/graphql/policy/vscode_extensions/vscode_extensions.rego +2 -2
  11. package/rules/image-compress/{js → fix}/package_setup/check.mjs +2 -2
  12. package/rules/js-bun-db/{js → fix}/safety/check.mjs +1 -1
  13. package/rules/js-bun-redis/{js → fix}/imports/check.mjs +1 -1
  14. package/rules/js-lint/{js → fix}/tooling/check.mjs +1 -1
  15. package/rules/js-run/{js → fix}/runtime/check.mjs +4 -4
  16. package/rules/nginx-default-tpl/{js → fix}/template/check.mjs +1 -1
  17. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.rego +1 -1
  18. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.rego +1 -1
  19. package/rules/npm-module/{js → fix}/package_structure/check.mjs +2 -2
  20. package/rules/php/{js → fix}/tooling/check.mjs +3 -3
  21. package/rules/rego/policy/package_json/package_json.rego +1 -1
  22. package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +1 -1
  23. package/rules/rego/policy/vscode_settings/vscode_settings.rego +1 -1
  24. package/rules/style-lint/{js → fix}/tooling/check.mjs +3 -3
  25. package/rules/tauri/{js → fix}/tooling/check.mjs +1 -1
  26. package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +2 -2
  27. package/rules/text/{js → fix}/formatting/check.mjs +4 -4
  28. package/rules/vue/{js → fix}/packages/check.mjs +1 -1
  29. package/scripts/utils/discover-checkable-rules.mjs +17 -15
  30. package/scripts/utils/run-rule.mjs +7 -5
  31. package/scripts/lint-conftest.mjs +0 -147
  32. /package/rules/abie/{js → fix}/applies/check.mjs +0 -0
  33. /package/rules/abie/{js → fix}/env_dns/check.mjs +0 -0
  34. /package/rules/abie/{js → fix}/firebase_hosting/check.mjs +0 -0
  35. /package/rules/abie/{js → fix}/hc_pairing/check.mjs +0 -0
  36. /package/rules/abie/{js → fix}/ua_http_route/check.mjs +0 -0
  37. /package/rules/abie/{js → fix}/ua_node_selector/check.mjs +0 -0
  38. /package/rules/capacitor/{js → fix}/platforms/check.mjs +0 -0
  39. /package/rules/changelog/{js → fix}/consistency/check.mjs +0 -0
  40. /package/rules/docker/{js → fix}/lint/check.mjs +0 -0
  41. /package/rules/docker/{js/run.mjs → lint/lint.mjs} +0 -0
  42. /package/rules/ga/{js → fix}/workflows/check.mjs +0 -0
  43. /package/rules/graphql/{js → fix}/tooling/check.mjs +0 -0
  44. /package/rules/hasura/{js → fix}/internal_urls/check.mjs +0 -0
  45. /package/rules/image-avif/{js → fix}/avif_generation/check.mjs +0 -0
  46. /package/rules/js-mssql/{js → fix}/deps/check.mjs +0 -0
  47. /package/rules/k8s/{js → fix}/manifests/check.mjs +0 -0
  48. /package/rules/k8s/{js/run.mjs → lint/lint.mjs} +0 -0
  49. /package/rules/php/{js/run.mjs → lint/lint.mjs} +0 -0
  50. /package/rules/rego/{js → fix}/applies/check.mjs +0 -0
  51. /package/rules/rego/{js → lint}/lint.mjs +0 -0
  52. /package/rules/text/{js → lint}/lint.mjs +0 -0
  53. /package/rules/text/{js → lint}/run-shellcheck.mjs +0 -0
  54. /package/rules/text/{js → lint}/run-v8r.mjs +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,48 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.11.12] - 2026-05-15
8
+
9
+ ### Removed
10
+
11
+ - **`npm/scripts/utils/discover-checkable-rules.mjs`**, **`npm/scripts/utils/run-rule.mjs`** — фаза 3 реструктуризації: dual-mode підтримка `js/` (legacy) прибрана. Після завершення масового переїзду у 1.11.10 (всі 26 правил у `rules/<id>/fix/`) інфраструктура `n-cursor check` тепер сканує **тільки** `rules/<id>/fix/<concern>/check*.mjs`. Конкретно: (1) у `discoverCheckableRules` видалено `listJsConcerns(js/, 'js')`-виклик, утиліту `mergeJsConcerns` (fatal на дублікат `js/`+`fix/`) і поле `rootDir` у `JsConcern`-типі; (2) у `run-rule.mjs::resolveJsCheckPath` `concern.rootDir ?? 'js'` замінено на хардкод `'fix'`; (3) JSDoc на початку обох файлів і на `evaluateAppliesGate` оновлено з `js/applies/check.mjs` на `fix/applies/check.mjs`; (4) коментар на `discoverCheckScripts` у `npm/bin/n-cursor.js` оновлено — згадку legacy `js/check.mjs` прибрано. Жодне правило в `rules/` не торкається — лише сканер.
12
+ - **`npm/scripts/utils/discover-checkable-rules.test.mjs`**, **`npm/scripts/utils/run-rule.test.mjs`** — прибрано тести dual-mode: `правило з тільки JS-концерном у legacy js/`, `правило з різними концернами у js/ і fix/`, `дублікат концерну в js/ і fix/ — fatal`, `пропускає js/utils/ як концерн`, `концерн з rootDir="fix"`, `концерн без rootDir (legacy-тести) fallback до js/`, `applies-гейт у fix/applies/`. Додано: `legacy js/-структура ігнорується (concern у js/<name>/ не підхоплюється)` — гарантує, що випадковий залишок `js/`-дерева у правилі не виконається, та `правило з кількома JS-концернами в fix/ — всі присутні, відсортовані`. Тестовий хелпер `addJsConcern` / `writeConcernJs` — за замовчуванням пишуть у `fix/` без параметра rootDir.
13
+
14
+ ## [1.11.11] - 2026-05-15
15
+
16
+ ### Removed
17
+
18
+ - **`npm/scripts/lint-conftest.mjs`** — скрипт видалено повністю. Його єдина функція — ітерувати policy-концерни через `discoverCheckableRules` і запускати `runConftestBatch` на реальних файлах — **повністю дублювала** `npx @nitra/cursor check` (CHANGELOG 1.11.5: «`bun.bunfig`, `text.cspell`, `npm_module.npm_publish_yml` тепер прогоняються через CLI `check <id>` без додаткового `bun run lint-conftest`»). Окремий канал залишався лише як IDE-fast-feedback, але через одне джерело правди (`target.json` поруч з `.rego`) другий entry-point не дає нічого нового. Кореневий `package.json` оновлено: скрипт `lint-conftest` прибрано, ланцюжок `lint` тепер `bun run lint-rego && bun run lint-js && …` (без `lint-conftest`).
19
+
20
+ ### Changed
21
+
22
+ - **`npm/README.md`** — секція «Структура пакету» переписана під поточний layout (`rules/<id>/<id>.mdc` замість застарілих `mdc/`). Додано підсекцію **«Структура одного правила»** з принципом fix/lint/policy: технологія реалізації визначає директорію — JS для `npx @nitra/cursor check` у `fix/<concern>/`, JS для `bun run lint-<id>` у `lint/`, rego для `npx @nitra/cursor check` у `policy/<concern>/`. Решта `mdc/`-посилань у README також виправлені на `rules/`.
23
+ - **`.cursor/rules/conftest.mdc`** — крок 5 у workflow «нова перевірка» переписано: окремої реєстрації нового rego-пакета в TARGETS більше не потрібно (TARGETS видалено разом із `lint-conftest.mjs`); `discoverCheckableRules` автоматично підхоплює пакет за наявності `target.json` поруч з `.rego`.
24
+ - **`.cursor/rules/scripts.mdc`** — згадку `lint-conftest.mjs` прибрано зі списку «крос-правильної інфраструктури» у `npm/scripts/`.
25
+ - **`npm/rules/abie/abie.mdc`** (cross-reference) — `npx @nitra/cursor lint-conftest` → `npx @nitra/cursor check abie`; `npm/policy/abie/` → `npm/rules/abie/policy/`; `check-abie.mjs` → `fix/<concern>/check.mjs`.
26
+ - **`npm/rules/**/fix/<concern>/check.mjs`** (10 файлів) та **`npm/rules/**/policy/<concern>/<name>.rego`** (7 файлів) — у коментарях і `pass()`-повідомленнях `bun run lint-conftest` замінено на `npx @nitra/cursor check` (структурна валідація живить fix-канал; окремого `lint-conftest`-каналу більше немає). Для conditional rego-полісі без `target.json` (ті, що не auto-discoverable) текст коментарів переформульовано — замість «глобально у `lint-conftest` НЕ реєструється» тепер «без `target.json` поруч (не auto-discoverable через `n-cursor check`)».
27
+
28
+ ## [1.11.10] - 2026-05-15
29
+
30
+ ### Changed
31
+
32
+ - **`npm/rules/<rule>/js/`** — фаза 2 реструктуризації: усі 26 правил перенесені з `rules/<id>/js/` у `rules/<id>/fix/` (JS-концерни `check*.mjs`) та `rules/<id>/lint/` (CLI-entry `lint.mjs` + helper-runner-и). Директорія `rules/<id>/js/` більше не існує ні в жодному правилі. Перелік переміщеного: 18 правил (Category A) — лише концерни у `fix/`; 7 правил (Category B) — концерни у `fix/` та lint-entry у `lint/`: `ga`, `docker`, `php`, `rego`, `k8s`, `text`, а також `abie` (6 концернів, без lint-entry). Policy-каталоги (`rules/<id>/policy/`) — не рухались. Відносні imports у всіх переміщених файлах залишились дійсними (глибина `js/` = глибина `fix/`/`lint/`). Виправлено крос-модульні imports: `rules/abie/utils/k8s-tree.mjs`, `rules/ga/lint/lint.mjs`, `rules/docker/fix/lint/discover.test.mjs`, `rules/nginx-default-tpl/fix/template/check.mjs` — усі 4 оновлено з `*/js/*` на правильні нові шляхи. Оновлено hardcoded imports у `tests/check-rule-fixtures.test.mjs`, `tests/integration-repo-checks.test.mjs`, `tests/check-empty-trees.test.mjs`.
33
+ - **`npm/bin/n-cursor.js`** — 5 static imports CLI lint-entry перенаправлено: `rules/rego/js/lint.mjs` → `rules/rego/lint/lint.mjs`, `rules/ga/js/lint.mjs` → `rules/ga/lint/lint.mjs`, `rules/docker/js/run.mjs` → `rules/docker/lint/lint.mjs`, `rules/k8s/js/run.mjs` → `rules/k8s/lint/lint.mjs`, `rules/text/js/lint.mjs` → `rules/text/lint/lint.mjs`.
34
+ - **`npm/rules/k8s/lint/run-roots.test.mjs`** — import `./run.mjs` → `./lint.mjs` (файл перейменовано в межах переїзду).
35
+
36
+ ## [1.11.9] - 2026-05-15
37
+
38
+ ### Changed
39
+
40
+ - **`npm/scripts/utils/discover-checkable-rules.mjs`**, **`npm/scripts/utils/run-rule.mjs`** — фаза 1 реструктуризації `rules/<id>/js/` → `rules/<id>/{fix,lint}/`: інфраструктура `n-cursor check` тепер **dual-mode** — сканує JS-концерни одночасно у `rules/<id>/js/<concern>/` (legacy) і `rules/<id>/fix/<concern>/` (новий формат). Кожен знайдений концерн штампується полем `rootDir: 'js' | 'fix'`; `runRule` використовує його для побудови шляху імпорту через `resolveJsCheckPath`. Концерн з однаковим іменем у обох каталогах одного правила — fatal-помилка з підказкою «заверши міграцію цього концерну у fix/ і видали `js/<name>/`», щоб не лишалось напіввиконаних move-ів. `utils/` пропускається в обох коренях. Жодне правило ще не переїхало у фазі 1 — це лише підготовка інфраструктури; для зворотної сумісності `resolveJsCheckPath` має fallback `rootDir ?? 'js'`, тож тести, що збирають `jsConcerns` вручну без `rootDir`, продовжують працювати без змін. CLI-точки входу `n-cursor lint-X` (статичні `import` у `npm/bin/n-cursor.js:79-83`) переїдуть пізніше — у фазах 2/3 (move + оновлення imports).
41
+ - **`npm/scripts/utils/discover-checkable-rules.test.mjs`**, **`npm/scripts/utils/run-rule.test.mjs`** — додано покриття нового `fix/`-кореня: окремі тести на discovery концерну у `fix/`, mix `js/`+`fix/` різних концернів одного правила, fatal на дублікат, пропуск `fix/utils/`, runRule з `rootDir: 'fix'`, applies-гейт у `fix/applies/`, та fallback на `js/` для концернів без `rootDir` (зворотна сумісність із наявними тестовими фікстурами).
42
+
43
+ ## [1.11.8] - 2026-05-15
44
+
45
+ ### Changed
46
+
47
+ - **`npm/bin/n-cursor.js::syncSkills`** — файл `auto.md` зі скілу більше **не** копіюється у `.cursor/skills/n-<id>/`. `auto.md` — це службова мета для CLI-сторони (`scripts/auto-skills.mjs` читає його з пакета, щоб вирішити, чи автоматично активувати скіл у `.n-cursor.json`), у проєкті він зайвий і лише засмічує `.cursor/skills/`. Каталог-приймач після цієї зміни лишається без `auto.md` — тільки `SKILL.md` (і будь-які інші файли скілу, якщо зʼявляться). Раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI **не чіпає** — їх потрібно прибрати вручну (свідома вимога користувача, щоб синк не видаляв нічого без явної згоди). Заголовний коментар у `npm/bin/n-cursor.js` оновлено відповідно.
48
+
7
49
  ## [1.11.7] - 2026-05-15
8
50
 
9
51
  ### Changed
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  ## Як це працює
6
6
 
7
- Репозиторій `@nitra/cursor` містить cursor-правила у директорії `mdc/`. CLI копіює обрані правила з **каталогу `mdc/` того пакету, з якого виконується `bin/n-cursor.js`**: після `npm i` / `bun add` це зазвичай `node_modules/@nitra/cursor/mdc`; при **`npx @nitra/cursor`** пакет потрапляє в **кеш npx/npm**, і правила читаються з тієї розпакованої копії (у корені проєкту залежність не обов’язкова). Жодних окремих HTTP-запитів до CDN для файлів правил немає — лише те, що вже є в tarball пакету.
7
+ Репозиторій `@nitra/cursor` містить cursor-правила у директорії `rules/<id>/`. CLI копіює `<id>.mdc` обраних правил з **каталогу `rules/` того пакету, з якого виконується `bin/n-cursor.js`**: після `npm i` / `bun add` це зазвичай `node_modules/@nitra/cursor/rules/<id>/<id>.mdc`; при **`npx @nitra/cursor`** пакет потрапляє в **кеш npx/npm**, і правила читаються з тієї розпакованої копії (у корені проєкту залежність не обов’язкова). Жодних окремих HTTP-запитів до CDN для файлів правил немає — лише те, що вже є в tarball пакету.
8
8
 
9
- Наприклад, правило `mdc/text.mdc` буде збережено як `.cursor/rules/n-text.mdc`.
9
+ Наприклад, правило `rules/text/text.mdc` буде збережено як `.cursor/rules/n-text.mdc`.
10
10
 
11
11
  ## Підготовка
12
12
 
@@ -46,7 +46,7 @@
46
46
 
47
47
  ### Правило `k8s` і Kustomize
48
48
 
49
- У цільовому репозиторії з маніфестами під **`**/k8s`** дотримуйтесь **`mdc/k8s.mdc`** з пакету (після синку — `.cursor/rules/n-k8s.mdc`або копія з`node_modules/@nitra/cursor/mdc/k8s.mdc`).
49
+ У цільовому репозиторії з маніфестами під **`**/k8s`** дотримуйтесь **`rules/k8s/k8s.mdc`** з пакету (після синку — `.cursor/rules/n-k8s.mdc` або копія з `node_modules/@nitra/cursor/rules/k8s/k8s.mdc`).
50
50
 
51
51
  Коротко:
52
52
 
@@ -56,7 +56,7 @@
56
56
  - Рядки в **base**, які змінюються в overlays, позначайте коментарем на рядку (узгоджено в команді), наприклад: `# буде замінено через kustomize`.
57
57
  - Після перенесення в **`base`** / overlays **видаляйте** застарілі маніфести та каталоги, які більше не потрібні.
58
58
 
59
- Повний текст правил — у **`k8s.mdc`**; programmatic перевірки — у **`npm/scripts/check-k8s.mjs`** (у встановленому пакеті `scripts/check-k8s.mjs`).
59
+ Повний текст правил — у **`k8s.mdc`**; programmatic перевірки — у **`npm/rules/k8s/`**: JS-checks у `fix/<concern>/check.mjs`, rego-policies у `policy/<concern>/<name>.rego` (обидва запускаються через `npx @nitra/cursor check k8s`).
60
60
 
61
61
  ### v8r і власний каталог схем
62
62
 
@@ -76,7 +76,7 @@ CLI автоматично (команда завантаження правил
76
76
 
77
77
  1. Знайде або створить `.n-cursor.json` у поточній директорії (із полем `$schema` на JSON Schema пакету; якщо файл уже є без коректного `$schema`, поле буде додано або оновлено при зчитуванні конфігу)
78
78
  2. Створить директорію `.cursor/rules/`, якщо її ще немає
79
- 3. Скопіює кожне з перелічених у конфігу правило з `mdc/` установленого пакету і збереже файли з префіксом `n-`
79
+ 3. Скопіює кожне з перелічених у конфігу правило з `rules/<id>/<id>.mdc` установленого пакету і збереже файли з префіксом `n-`
80
80
  4. Після оновлення файлів на диску згенерує в корені проєкту **`AGENTS.md`**: повний вміст береться з шаблону пакету `AGENTS.template.md`, а список правил у шаблоні формується з **усіх наявних файлів `*.mdc`** у `.cursor/rules/` (відсортовано за ім’ям); секція команд — з **`package.json`** кореня (див. `{{#commands}}` у шаблоні).
81
81
 
82
82
  ## Приклад виводу
@@ -96,15 +96,50 @@ CLI автоматично (команда завантаження правил
96
96
 
97
97
  ```
98
98
  npm/
99
- ├── AGENTS.template.md # шаблон AGENTS.md для цільових репозиторіїв (потрапляє в npm-архів)
100
- ├── mdc/ # cursor-правила (без префікса n-; після синку .cursor/rules/n-<id>.mdc)
101
- │ ├── npm-module.mdc
102
- └── text.mdc
103
- ├── skills/ # skills (каталоги <id>/; після синку — .cursor/skills/n-<id>/)
99
+ ├── AGENTS.template.md # шаблон AGENTS.md для цільових репозиторіїв (потрапляє в npm-архів)
100
+ ├── rules/ # cursor-правила (підкаталог на правило, див. «Структура одного правила»)
101
+ │ ├── npm-module/
102
+ ├── text/
103
+ │ └── ...
104
+ ├── skills/ # скіли (каталоги <id>/; після синку — .cursor/skills/n-<id>/)
105
+ ├── scripts/ # CLI-утиліти, спільні runner-и, discovery
104
106
  └── bin/
105
- └── n-cursor.js # CLI-скрипт
107
+ └── n-cursor.js # CLI-скрипт (точка входу)
106
108
  ```
107
109
 
110
+ ### Структура одного правила
111
+
112
+ Кожне правило `npm/rules/<id>/` ділиться за **технологією реалізації** на три сиблінги — `fix/`, `lint/`, `policy/`:
113
+
114
+ ```
115
+ npm/rules/<id>/
116
+ ├── <id>.mdc # текст правила (після синку — .cursor/rules/n-<id>.mdc)
117
+ ├── auto.md # умова автоактивації скілу (опційно)
118
+ ├── fix/ # JS для `npx @nitra/cursor check`
119
+ │ └── <concern>/
120
+ │ ├── check.mjs # діагностика — повертає список violations
121
+ │ ├── check.test.mjs
122
+ │ └── autofix.mjs # опційно — програмний автофікс
123
+ ├── lint/ # JS, що живить `bun run lint-<id>` (для правил з канонічним lint-скриптом)
124
+ │ ├── lint.mjs # CLI entry для `n-cursor lint-<id>`
125
+ │ └── run-*.mjs # допоміжні runner-и (shellcheck, v8r тощо)
126
+ └── policy/ # rego для `npx @nitra/cursor check`
127
+ └── <concern>/
128
+ ├── <concern>.rego # правила (`deny contains msg if …`)
129
+ ├── <concern>_test.rego # юніт-тести (запускає `bun run lint-rego` → conftest verify)
130
+ └── target.json # які файли подавати в conftest (single / walkGlob)
131
+ ```
132
+
133
+ **Принцип:** технологія реалізації визначає директорію.
134
+
135
+ | Що реалізує | Канал виклику | Куди |
136
+ | --- | --- | --- |
137
+ | JS-діагностика + автофікс | `npx @nitra/cursor check` (fix-канал) | `fix/<concern>/` |
138
+ | JS-orchestrator лінту | `bun run lint-<id>` через `n-cursor lint-<id>` | `lint/` |
139
+ | Rego-діагностика | `npx @nitra/cursor check` (fix-канал) | `policy/<concern>/` |
140
+
141
+ `fix/` і `policy/` обидва живлять fix-канал (`npx @nitra/cursor check` запускає і JS-checks, і rego-policies), але **розділені за технологією**: JS у `fix/`, rego у `policy/`. `lint/` тримає лише JS, що оркеструє `bun run lint-<id>`.
142
+
108
143
  ## AGENTS.md у проєкті користувача
109
144
 
110
145
  Після кожного успішного проходу завантаження правил CLI **повністю перезаписує** файл **`AGENTS.md`** у корені поточної директорії (та сама директорія, де лежить `.n-cursor.json`).
@@ -117,7 +152,7 @@ npm/
117
152
 
118
153
  ### Зміна шаблону AGENTS
119
154
 
120
- 1. Редагуйте **`npm/AGENTS.template.md`**. Файл має бути перелічений у полі **`files`** у `npm/package.json`, щоб потрапляти в публікацію npm (разом з `mdc/` та `bin/`).
155
+ 1. Редагуйте **`npm/AGENTS.template.md`**. Файл має бути перелічений у полі **`files`** у `npm/package.json`, щоб потрапляти в публікацію npm (разом з `rules/`, `skills/`, `bin/`).
121
156
  2. Для вставки списку файлів правил використовуйте блок у стилі Mustache з ім’ям секції **`services`** і плейсхолдером **`{{name}}`**:
122
157
 
123
158
  ```markdown
@@ -136,7 +171,7 @@ npm/
136
171
 
137
172
  ### Логіка в коді CLI
138
173
 
139
- - Шлях до шаблону: поруч із `mdc/`, тобто `…/node_modules/@nitra/cursor/AGENTS.template.md` після встановлення пакету.
174
+ - Шлях до шаблону: поруч із `rules/`, тобто `…/node_modules/@nitra/cursor/AGENTS.template.md` після встановлення пакету.
140
175
  - Оновлення **`AGENTS.md`** виконується **після** циклу завантаження правил, щоб список відображав актуальний вміст `.cursor/rules/` на диску.
141
176
  - Якщо каталогу `.cursor/rules/` немає або в ньому немає `*.mdc`, блок `{{#services}}` стає порожнім; решта шаблону все одно записується в **`AGENTS.md`**.
142
177
  - Секція **`commands`** залежить лише від **`package.json` у корені cwd**; якщо файлу немає або `scripts` відсутній, у блоці лишаються мінімальні рядки (`bun i`, виклики CLI).
package/bin/n-cursor.js CHANGED
@@ -46,6 +46,9 @@
46
46
  * skills/<id>/ (без префікса); у проєкті — .cursor/skills/n-<id>/ (префікс n-, як n-*.mdc).
47
47
  * Якщо ключа skills немає, за замовчуванням підтягуються всі підкаталоги skills/ (лише імена без префікса n-).
48
48
  * Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
49
+ * Файл `auto.md` у скілі — джерело правди для auto-skills у CLI (`scripts/auto-skills.mjs`)
50
+ * і у проєкт не копіюється; раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI
51
+ * не чіпає — їх потрібно прибрати вручну.
49
52
  *
50
53
  * Якщо в корені є package.json і в ньому ще немає \@nitra/cursor у devDependencies (і не оголошено
51
54
  * в dependencies), CLI дописує devDependencies з діапазоном ^<version> поточного пакету — зручно після npx.
@@ -74,11 +77,11 @@ import { detectAutoSkills } from '../scripts/auto-skills.mjs'
74
77
  import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
75
78
  import { discoverCheckableRules } from '../scripts/utils/discover-checkable-rules.mjs'
76
79
  import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
77
- import { runLintDocker } from '../rules/docker/js/run.mjs'
78
- import { runLintGaCli } from '../rules/ga/js/lint.mjs'
79
- import { runLintK8s } from '../rules/k8s/js/run.mjs'
80
- import { runLintRego } from '../rules/rego/js/lint.mjs'
81
- import { runLintTextCli } from '../rules/text/js/lint.mjs'
80
+ import { runLintDocker } from '../rules/docker/lint/lint.mjs'
81
+ import { runLintGaCli } from '../rules/ga/lint/lint.mjs'
82
+ import { runLintK8s } from '../rules/k8s/lint/lint.mjs'
83
+ import { runLintRego } from '../rules/rego/lint/lint.mjs'
84
+ import { runLintTextCli } from '../rules/text/lint/lint.mjs'
82
85
  import { runRule } from '../scripts/utils/run-rule.mjs'
83
86
  import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
84
87
  import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
@@ -771,6 +774,7 @@ async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
771
774
  await mkdir(destDir, { recursive: true })
772
775
  const files = await readdir(srcDir)
773
776
  for (const file of files) {
777
+ if (file === 'auto.md') continue
774
778
  const content = await readFile(join(srcDir, file), 'utf8')
775
779
  await writeFile(join(destDir, file), content, 'utf8')
776
780
  }
@@ -1005,10 +1009,9 @@ function logRemovedManagedItems(title, basePath, names) {
1005
1009
  }
1006
1010
 
1007
1011
  /**
1008
- * Знаходить правила, для яких є хоча б щось прогонне: JS-концерн у `rules/<id>/js/<concern>/check*.mjs`
1009
- * (плюс legacy `rules/<id>/js/check.mjs` як концерн `legacy`) або policy-концерн у
1010
- * `rules/<id>/policy/<concern>/target.json`. Делегує у `discoverCheckableRules` —
1011
- * див. `scripts/utils/discover-checkable-rules.mjs`.
1012
+ * Знаходить правила, для яких є хоча б щось прогонне: JS-концерн у `rules/<id>/fix/<concern>/check*.mjs`
1013
+ * або policy-концерн у `rules/<id>/policy/<concern>/target.json`. Делегує у `discoverCheckableRules`
1014
+ * — див. `scripts/utils/discover-checkable-rules.mjs`.
1012
1015
  * @returns {import('../scripts/utils/discover-checkable-rules.mjs').CheckableRule[]} опис правил у алфавітному порядку
1013
1016
  */
1014
1017
  function discoverCheckScripts() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.11.7",
3
+ "version": "1.11.12",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -52,5 +52,8 @@
52
52
  "engines": {
53
53
  "bun": ">=1.3",
54
54
  "node": ">=25"
55
+ },
56
+ "devDependencies": {
57
+ "@nitra/cursor": "^1.11.9"
55
58
  }
56
59
  }
@@ -163,7 +163,7 @@ with:
163
163
 
164
164
  ### Швидкий gate через conftest (Rego)
165
165
 
166
- Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/policy/abie/`** (запускається через **`bun run lint-rego`** для `*_test.rego` тестів і **`npx @nitra/cursor lint-conftest`** для прогону по реальних YAML — деталі в **conftest.mdc** / **n-rego.mdc**). JS у **`check-abie.mjs`** authoritative — rego тільки швидкий gate для одиничного маніфеста (зокрема через IDE-розширення `tsandall.opa`).
166
+ Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/rules/abie/policy/`** (запускається через **`bun run lint-rego`** для `*_test.rego` юніт-тестів і через **`npx @nitra/cursor check abie`** для прогону по реальних YAML — деталі в **conftest.mdc** / **n-rego.mdc**). JS у **`fix/<concern>/check.mjs`** authoritative — rego тільки швидкий gate для одиничного маніфеста (зокрема через IDE-розширення `tsandall.opa`).
167
167
 
168
168
  Пакети (директорія в **`npm/policy/abie/`** → namespace → що перевіряє):
169
169
 
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { dirname, relative } from 'node:path'
10
10
 
11
- import { pathHasK8sSegment } from '../../k8s/js/manifests/check.mjs'
11
+ import { pathHasK8sSegment } from '../../k8s/fix/manifests/check.mjs'
12
12
  import { walkDir } from '../../../scripts/utils/walkDir.mjs'
13
13
  import { isDeploymentDoc, readAndParseYamlDocs } from './yaml.mjs'
14
14
 
@@ -113,7 +113,7 @@ async function checkHookScript(reporter, scriptName) {
113
113
  function checkProjectSettings(reporter) {
114
114
  const { pass, fail } = reporter
115
115
  if (existsSync(PROJECT_SETTINGS_PATH)) {
116
- pass(`${PROJECT_SETTINGS_PATH} є (Stop-hook перевіряє bun run lint-conftest → adr.settings_json)`)
116
+ pass(`${PROJECT_SETTINGS_PATH} є (Stop-hook перевіряє npx @nitra/cursor check → adr.settings_json)`)
117
117
  } else {
118
118
  fail(`${PROJECT_SETTINGS_PATH} не існує — запусти \`npx @nitra/cursor\``)
119
119
  }
@@ -9,7 +9,7 @@
9
9
  * `package.json` має бути відповідний скрипт `lint-docker` / `lint-k8s`
10
10
  * (cross-file: два JSON-файли).
11
11
  *
12
- * **Що покрила Rego** (`bun run lint-conftest`):
12
+ * **Що покрила Rego** (`npx @nitra/cursor check`):
13
13
  * - `npm/policy/bun/bunfig/` — `[install].linker == "hoisted"` у `bunfig.toml`;
14
14
  * - `npm/policy/bun/package_json/` — відсутність `packageManager` / `dependencies`
15
15
  * у кореневому `package.json`, у `devDependencies` лише `@nitra/*`, агрегований
@@ -97,7 +97,7 @@ export async function check() {
97
97
  }
98
98
 
99
99
  if (existsSync('bunfig.toml')) {
100
- pass('bunfig.toml є (структуру перевіряє bun run lint-conftest → bun.bunfig)')
100
+ pass('bunfig.toml є (структуру перевіряє npx @nitra/cursor check → bun.bunfig)')
101
101
  } else {
102
102
  fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
103
103
  }
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import { platform } from 'node:process'
23
23
 
24
- import { check as checkGa } from './workflows/check.mjs'
24
+ import { check as checkGa } from '../fix/workflows/check.mjs'
25
25
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
26
26
  import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
27
27
 
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # Викликається з `check-graphql.mjs` через `runConftestBatch` лише ПІСЛЯ того,
4
4
  # як JS виявив `gql\`…\`` tagged template literal у джерелах (умовне правило).
5
- # Тому в `lint-conftest.mjs` TARGETS глобально не реєструється інакше були б
6
- # false-positive порушення на проєктах без gql.
5
+ # Тому без `target.json` поруч (не auto-discoverable через `n-cursor check`)
6
+ # інакше були б false-positive порушення на проєктах без gql.
7
7
  #
8
8
  # Canonical (graphql.mdc):
9
9
  # { "recommendations": ["graphql.vscode-graphql"] }
@@ -9,7 +9,7 @@
9
9
  * - застарілий `.minify-image-cache.tsv` (з версій < 3.2) видалений з кореня та
10
10
  * з `.gitignore`.
11
11
  *
12
- * **Що покрила Rego** (`bun run lint-conftest`,
12
+ * **Що покрила Rego** (`npx @nitra/cursor check`,
13
13
  * `npm/policy/image_compress/package_json/`):
14
14
  * - `scripts.lint-image` викликає `npx \@nitra/minify-image --src=. --write`
15
15
  * без `--avif` (AVIF — окреме правило `image-avif`);
@@ -100,7 +100,7 @@ export async function check() {
100
100
  fail('package.json не знайдено в корені — додай (image-compress.mdc)')
101
101
  return reporter.getExitCode()
102
102
  }
103
- pass('package.json є (структуру перевіряє bun run lint-conftest → image_compress.package_json)')
103
+ pass('package.json є (структуру перевіряє npx @nitra/cursor check → image_compress.package_json)')
104
104
 
105
105
  await checkHashCacheNotIgnored(pass, fail)
106
106
  await checkLegacyCacheRemoved(pass, fail)
@@ -219,7 +219,7 @@ export async function check() {
219
219
  }
220
220
 
221
221
  // Перевірку `dependencies` (заборона `pg` / `pg-format` / `mysql2`) перенесено
222
- // в Rego-полісі `npm/policy/js_bun_db/package_json/`; `bun run lint-conftest`
222
+ // в Rego-полісі `npm/policy/js_bun_db/package_json/`; `npx @nitra/cursor check`
223
223
  // запускає її по всіх workspace-`package.json`. Тут лишився лише AST-скан коду.
224
224
 
225
225
  const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths)
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Перевірку `dependencies` (заборона `ioredis` / `node-redis` / `redis` / `@redis/*` у будь-якому
10
10
  * `package.json`) винесено в Rego-полісі `npm/policy/js_bun_redis/package_json/`; її запускає
11
- * `bun run lint-conftest`. Тут лишився AST-скан коду через `oxc-parser`.
11
+ * `npx @nitra/cursor check`. Тут лишився AST-скан коду через `oxc-parser`.
12
12
  */
13
13
  import { existsSync } from 'node:fs'
14
14
  import { readFile } from 'node:fs/promises'
@@ -393,7 +393,7 @@ async function checkVscodeExtensions(passFn, failFn) {
393
393
  */
394
394
  async function checkLintJsWorkflows(passFn, failFn) {
395
395
  if (existsSync('.github/workflows/lint-js.yml')) {
396
- passFn('.github/workflows/lint-js.yml є (структуру перевіряє bun run lint-conftest → js_lint.lint_js_yml)')
396
+ passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor check → js_lint.lint_js_yml)')
397
397
  } else {
398
398
  failFn('.github/workflows/lint-js.yml не існує — створи його (див. check-js-lint.mjs / js-lint.mdc)')
399
399
  }
@@ -331,7 +331,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
331
331
  // Frontend-пакети (vite у devDependencies) виходять за межі js-run:
332
332
  // браузерний бандл не має `node:process`, а `process.env.*` бандлер
333
333
  // обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
334
- // bunyan-залежність валідується в Rego (`bun run lint-conftest`).
334
+ // bunyan-залежність валідується в Rego (`npx @nitra/cursor check`).
335
335
  if (packageJsonHasViteDevDependency(pkgJson)) {
336
336
  passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
337
337
  return
@@ -392,7 +392,7 @@ function packageJsonHasViteDevDependency(pkgJson) {
392
392
  /**
393
393
  * Завантажує `package.json` пакета (якщо є). Заборону `@nitra/bunyan` / `bunyan`
394
394
  * у dependencies/devDependencies перенесено в Rego (`npm/policy/js_run/package_json/`);
395
- * `bun run lint-conftest` запускає її по всіх workspace `package.json`. Тут лишилася
395
+ * `npx @nitra/cursor check` запускає її по всіх workspace `package.json`. Тут лишилася
396
396
  * лише AST-перевірка імпортів.
397
397
  * @param {string} rootDir відносний шлях workspace
398
398
  * @returns {Promise<unknown>} розпарсений package.json або null
@@ -406,7 +406,7 @@ async function loadPackageJson(rootDir) {
406
406
  /**
407
407
  * Перевіряє наявність `k8s/base/configmap.yaml` пакета. Структуру (наявність
408
408
  * `OTEL_RESOURCE_ATTRIBUTES` з обов'язковими `service.name=` / `service.namespace=`)
409
- * перенесено в Rego (`npm/policy/js_run/configmap/`); `bun run lint-conftest`
409
+ * перенесено в Rego (`npm/policy/js_run/configmap/`); `npx @nitra/cursor check`
410
410
  * запускає її на всіх `k8s/base/configmap.yaml`.
411
411
  * @param {string} rootDir відносний шлях workspace
412
412
  * @param {(msg: string) => void} passFn успішне повідомлення
@@ -415,7 +415,7 @@ async function loadPackageJson(rootDir) {
415
415
  function checkOtelConfigmap(rootDir, passFn) {
416
416
  const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
417
417
  if (!existsSync(configmapPath)) return
418
- passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — bun run lint-conftest → js_run.configmap)`)
418
+ passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @nitra/cursor check → js_run.configmap)`)
419
419
  }
420
420
 
421
421
  /**
@@ -17,7 +17,7 @@ import { existsSync } from 'node:fs'
17
17
  import { readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'
18
18
  import { basename, dirname, join, relative } from 'node:path'
19
19
 
20
- import { findDockerfilePaths } from '../../../docker/js/lint/check.mjs'
20
+ import { findDockerfilePaths } from '../../../docker/fix/lint/check.mjs'
21
21
  import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
22
22
  import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
23
23
  import { runConftestBatch } from '../../../../scripts/utils/run-conftest-batch.mjs'
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
4
4
  # ПІСЛЯ того, як JS виявив `default.conf.template` у дереві (умовне правило).
5
- # Глобально у `lint-conftest.mjs` TARGETS не реєструється.
5
+ # Глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`).
6
6
  #
7
7
  # Canonical: `recommendations` має містити `ahmadalli.vscode-nginx-conf`.
8
8
  package nginx_default_tpl.vscode_extensions
@@ -1,7 +1,7 @@
1
1
  # Перевірка `.vscode/settings.json` для nginx-default-tpl (nginx-default-tpl.mdc).
2
2
  #
3
3
  # Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
4
- # ПІСЛЯ того, як JS виявив `default.conf.template`. Глобально у `lint-conftest`
4
+ # ПІСЛЯ того, як JS виявив `default.conf.template`. Без `target.json` поруч
5
5
  # не реєструється.
6
6
  #
7
7
  # Canonical:
@@ -204,7 +204,7 @@ function checkEmitTypesConfig(passFn, failFn) {
204
204
  )
205
205
  return
206
206
  }
207
- passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє bun run lint-conftest → npm_module.emit_types_config)`)
207
+ passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @nitra/cursor check → npm_module.emit_types_config)`)
208
208
  }
209
209
 
210
210
  /**
@@ -338,7 +338,7 @@ async function checkDirtyNpmRequiresVersionBump(passFn, failFn) {
338
338
  function checkPublishWorkflow(passFn, failFn) {
339
339
  const publishWf = '.github/workflows/npm-publish.yml'
340
340
  if (existsSync(publishWf)) {
341
- passFn(`${publishWf} є (структуру перевіряє bun run lint-conftest → npm_module.npm_publish_yml)`)
341
+ passFn(`${publishWf} є (структуру перевіряє npx @nitra/cursor check → npm_module.npm_publish_yml)`)
342
342
  } else {
343
343
  failFn(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
344
344
  }
@@ -5,7 +5,7 @@
5
5
  * - наявність `composer.json` у корені;
6
6
  * - наявність `.github/workflows/lint-php.yml`.
7
7
  *
8
- * **Що покрила Rego** (`bun run lint-conftest`):
8
+ * **Що покрила Rego** (`npx @nitra/cursor check`):
9
9
  * - `npm/policy/php/package_json/` — наявність скрипта `lint-php` у `package.json`;
10
10
  * - `npm/policy/php/lint_php_yml/` — крок `run: bun run lint-php` у workflow.
11
11
  */
@@ -28,14 +28,14 @@ export function check() {
28
28
  }
29
29
 
30
30
  if (existsSync('package.json')) {
31
- pass('package.json є (наявність lint-php перевіряє bun run lint-conftest → php.package_json)')
31
+ pass('package.json є (наявність lint-php перевіряє npx @nitra/cursor check → php.package_json)')
32
32
  } else {
33
33
  fail('package.json не знайдено в корені — додай (php.mdc)')
34
34
  }
35
35
 
36
36
  const wfPath = '.github/workflows/lint-php.yml'
37
37
  if (existsSync(wfPath)) {
38
- pass(`${wfPath} є (структуру перевіряє bun run lint-conftest → php.lint_php_yml)`)
38
+ pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor check → php.lint_php_yml)`)
39
39
  } else {
40
40
  fail(`${wfPath} не існує — створи згідно php.mdc`)
41
41
  }
@@ -1,7 +1,7 @@
1
1
  # Перевірка `package.json` для rego (rego.mdc).
2
2
  #
3
3
  # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
4
- # `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
4
+ # `.rego` файлів у дереві. Глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`).
5
5
  #
6
6
  # Canonical (rego.mdc): scripts.lint-rego має бути "n-cursor lint-rego" — CLI пакета `@nitra/cursor`
7
7
  # (бінарка з `node_modules/.bin/`) робить preflight `opa`/`regal` і прогонить
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ того, як
4
4
  # JS виявив `.rego` файли у дереві (умовне правило — проєкти без rego не
5
- # зобовʼязані ставити tsandall.opa). Глобально у `lint-conftest` НЕ
5
+ # зобовʼязані ставити tsandall.opa). Без `target.json` поруч НЕ
6
6
  # реєструється.
7
7
  #
8
8
  # Canonical (rego.mdc): `recommendations` має містити `tsandall.opa`.
@@ -1,7 +1,7 @@
1
1
  # Перевірка `.vscode/settings.json` для rego (rego.mdc).
2
2
  #
3
3
  # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
4
- # `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
4
+ # `.rego` файлів у дереві. Глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`).
5
5
  #
6
6
  # Canonical (rego.mdc):
7
7
  # { "[rego]": { "editor.defaultFormatter": "tsandall.opa",
@@ -7,7 +7,7 @@
7
7
  * (cross-file: треба знати, чи є поле, чи немає);
8
8
  * - `.stylelintignore` у корені.
9
9
  *
10
- * **Що покрила Rego** (`bun run lint-conftest`):
10
+ * **Що покрила Rego** (`npx @nitra/cursor check`):
11
11
  * - `npm/policy/style_lint/package_json/` — скрипт `lint-style` через `npx stylelint`,
12
12
  * `@nitra/stylelint-config` у `devDependencies`, поле `stylelint.extends`;
13
13
  * - `npm/policy/style_lint/lint_style_yml/` — `npx stylelint` у `run` workflow;
@@ -44,7 +44,7 @@ async function checkStylelintConfigPresence(reporter) {
44
44
  // `.vscode/extensions.json` (`stylelint.vscode-stylelint`) і `.vscode/settings.json`
45
45
  // (`css.validate`/`scss.validate`/`less.validate: false`) — у rego-пакетах
46
46
  // `style_lint.vscode_extensions` і `style_lint.vscode_settings`, прогоняє
47
- // `bun run lint-conftest`. JS-копії видалено, щоб не було двох джерел істини.
47
+ // `npx @nitra/cursor check`. JS-копії видалено, щоб не було двох джерел істини.
48
48
 
49
49
  /**
50
50
  * Перевіряє відповідність проєкту правилам style-lint.mdc
@@ -64,7 +64,7 @@ export async function check() {
64
64
 
65
65
  const wfPath = '.github/workflows/lint-style.yml'
66
66
  if (existsSync(wfPath)) {
67
- pass(`${wfPath} є (структуру перевіряє bun run lint-conftest → style_lint.lint_style_yml)`)
67
+ pass(`${wfPath} є (структуру перевіряє npx @nitra/cursor check → style_lint.lint_style_yml)`)
68
68
  } else {
69
69
  fail(`${wfPath} не існує — створи його`)
70
70
  }
@@ -12,7 +12,7 @@
12
12
  * 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
13
13
  * content `rego.tauri.vscode_extensions` через `runConftestBatch`.
14
14
  *
15
- * Rego-полісі глобально у `lint-conftest` НЕ реєструється — це conditional
15
+ * Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`) — це conditional
16
16
  * правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
17
17
  */
18
18
  import { existsSync, statSync } from 'node:fs'
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # Викликається з `check-tauri.mjs` через `runConftestBatch` лише ПІСЛЯ того,
4
4
  # як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
5
- # у будь-якому пакеті, або залежність `@tauri-apps/*`). Глобально у
6
- # `lint-conftest` НЕ реєструється — інакше false-positive порушення на не-Tauri проєктах.
5
+ # у будь-якому пакеті, або залежність `@tauri-apps/*`). Без `target.json` поруч
6
+ # (не auto-discoverable через `n-cursor check`) — інакше false-positive порушення на не-Tauri проєктах.
7
7
  #
8
8
  # Canonical (tauri.mdc): `recommendations` має містити обидва записи —
9
9
  # - tauri-apps.tauri-vscode
@@ -14,7 +14,7 @@
14
14
  * варіантах, run-shellcheck-text.mjs, обовʼязкові glob-и);
15
15
  * - workflow `lint-text.yml` має крок `bun run lint-text`.
16
16
  *
17
- * **Що покрила Rego** (`bun run lint-conftest`):
17
+ * **Що покрила Rego** (`npx @nitra/cursor check`):
18
18
  * - `npm/policy/text/oxfmtrc/` — обовʼязкові ключі `.oxfmtrc.json` і канонічні
19
19
  * значення (semi/singleQuote/tabWidth/useTabs/printWidth) + `ignorePatterns`
20
20
  * канонічні glob-и;
@@ -92,8 +92,8 @@ async function checkV8rIgnore(passFn, failFn) {
92
92
  // `.vscode/extensions.json` (`DavidAnson.vscode-markdownlint`, `oxc.oxc-vscode`,
93
93
  // `timonwong.shellcheck`) і `.vscode/settings.json` (`editor.formatOnSave` +
94
94
  // `[lang].editor.defaultFormatter`) валідують rego-пакети `text.vscode_extensions`
95
- // і `text.vscode_settings` (зареєстровані глобально у `lint-conftest.mjs` TARGETS
96
- // з `rule: 'text'`). FS-existence файлів — у `checkTextConfigsExistence`.
95
+ // і `text.vscode_settings` (auto-discovered через `target.json` поруч з `.rego`).
96
+ // FS-existence файлів — у `checkTextConfigsExistence`.
97
97
 
98
98
  /**
99
99
  * FS-existence стек текстових конфігів. Контент-валідація — у Rego
@@ -111,7 +111,7 @@ function checkTextConfigsExistence(passFn, failFn) {
111
111
  ['.vscode/settings.json', 'text.vscode_settings']
112
112
  ]) {
113
113
  if (existsSync(path)) {
114
- passFn(`${path} є (структуру перевіряє bun run lint-conftest → ${mdcRef})`)
114
+ passFn(`${path} є (структуру перевіряє npx @nitra/cursor check → ${mdcRef})`)
115
115
  } else {
116
116
  failFn(`${path} не існує — створи згідно n-text.mdc`)
117
117
  }
@@ -444,7 +444,7 @@ async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
444
444
 
445
445
  checkRequiredDep(deps, 'vue', prefix, passFn, fail, 'vue відсутній в dependencies')
446
446
  // `vite >= 8` перенесено в Rego (`npm/policy/vue/package_json/`); запуск
447
- // через `bun run lint-conftest`. Залишені вимоги — present-/missing-deps
447
+ // через `npx @nitra/cursor check`. Залишені вимоги — present-/missing-deps
448
448
  // (vue-macros, unplugin-auto-import, тощо) — у самому JS-чекері.
449
449
  checkRequiredDep(
450
450
  devDeps,
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * Discovery rules для CLI `check`. Шукає правила, для яких є щось «прогонне»:
3
- * - JS concerns: `rules/<id>/js/<concern>/<check.mjs | check-*.mjs>` — кожен concern окремий вузол.
3
+ * - JS concerns: `rules/<id>/fix/<concern>/<check.mjs | check-*.mjs>` — кожен concern окремий вузол.
4
4
  * - Policy concerns: `rules/<id>/policy/<concern>/target.json` — пара з `<concern>.rego`.
5
5
  *
6
- * Каталог `utils/` всередині `js/` свідомо пропускається — це хелпери, не концерни.
6
+ * Каталог `fix/utils/` свідомо пропускається — це хелпери, не концерни.
7
7
  * Файли `*.test.mjs` фільтруються regex (`^check(?:-.+)?\.mjs$`).
8
- * Top-level плаский `js/check.mjs` (legacy) більше не підтримується — усі вшиті правила
9
- * у пакеті розпиляні на concern-структуру.
10
8
  *
11
9
  * Намеренно НЕ парсимо `target.json` тут (це робить runner). Discovery — швидкий скан структури:
12
10
  * шляхи + назви, без I/O вмісту.
11
+ *
12
+ * Історичний контекст: до 1.11.12 існувала dual-mode підтримка `js/` (legacy) і `fix/` (новий),
13
+ * де концерн мав поле `rootDir`. Після переїзду всіх 26 правил у `fix/` (CHANGELOG 1.11.10)
14
+ * legacy-канал прибрано (CHANGELOG 1.11.12) — одне джерело правди.
13
15
  */
14
16
  import { existsSync } from 'node:fs'
15
17
  import { readdir } from 'node:fs/promises'
@@ -20,7 +22,7 @@ const TEST_SUFFIX = '.test.mjs'
20
22
 
21
23
  /**
22
24
  * @typedef {object} JsConcern
23
- * @property {string} name імʼя концерну (`<name>` у `js/<name>/`)
25
+ * @property {string} name імʼя концерну (`<name>` у `fix/<name>/`)
24
26
  * @property {string[]} files імена `check*.mjs` у концерні (відсортовані алфавітно)
25
27
  */
26
28
 
@@ -37,21 +39,21 @@ const TEST_SUFFIX = '.test.mjs'
37
39
  */
38
40
 
39
41
  /**
40
- * Перелічує JS-концерни одного правила: підкаталоги `js/<name>/` з принаймні одним `check*.mjs`.
42
+ * Перелічує JS-концерни одного правила: підкаталоги `fix/<name>/` з принаймні одним `check*.mjs`.
41
43
  *
42
- * `js/utils/` свідомо пропускається — це хелпери, а не концерни.
43
- * @param {string} jsDir абсолютний шлях `rules/<id>/js/`
44
+ * `fix/utils/` свідомо пропускається — це хелпери, а не концерни.
45
+ * @param {string} fixDir абсолютний шлях `rules/<id>/fix/`
44
46
  * @returns {Promise<JsConcern[]>} концерни в алфавітному порядку
45
47
  */
46
- async function listJsConcerns(jsDir) {
47
- if (!existsSync(jsDir)) return []
48
- const topLevel = await readdir(jsDir, { withFileTypes: true })
48
+ async function listJsConcerns(fixDir) {
49
+ if (!existsSync(fixDir)) return []
50
+ const topLevel = await readdir(fixDir, { withFileTypes: true })
49
51
 
50
52
  /** @type {JsConcern[]} */
51
53
  const concerns = []
52
54
  for (const entry of topLevel) {
53
55
  if (!entry.isDirectory() || entry.name === 'utils' || entry.name.startsWith('.')) continue
54
- const concernDir = join(jsDir, entry.name)
56
+ const concernDir = join(fixDir, entry.name)
55
57
  const dirContents = await readdir(concernDir)
56
58
  const files = dirContents
57
59
  .filter(n => CHECK_FILENAME_RE.test(n) && !n.endsWith(TEST_SUFFIX))
@@ -84,8 +86,8 @@ async function listPolicyConcerns(policyDir) {
84
86
  }
85
87
 
86
88
  /**
87
- * Сканує `rules/` і повертає правила, для яких є JS-концерни або policy-концерни.
88
- * Правила без жодної прогонної частини (тільки `.mdc` + `auto.md`) фільтруються.
89
+ * Сканує `rules/` і повертає правила, для яких є JS-концерни (у `fix/`) або policy-концерни
90
+ * (у `policy/`). Правила без жодної прогонної частини (тільки `.mdc` + `auto.md`) фільтруються.
89
91
  * @param {string} bundledRulesDir абсолютний шлях до `npm/rules/`
90
92
  * @returns {Promise<CheckableRule[]>} відсортовані за id
91
93
  */
@@ -97,7 +99,7 @@ export async function discoverCheckableRules(bundledRulesDir) {
97
99
  for (const entry of entries) {
98
100
  if (!entry.isDirectory() || entry.name.startsWith('.')) continue
99
101
  const ruleDir = join(bundledRulesDir, entry.name)
100
- const jsConcerns = await listJsConcerns(join(ruleDir, 'js'))
102
+ const jsConcerns = await listJsConcerns(join(ruleDir, 'fix'))
101
103
  const policyConcerns = await listPolicyConcerns(join(ruleDir, 'policy'))
102
104
  if (jsConcerns.length > 0 || policyConcerns.length > 0) {
103
105
  out.push({ id: entry.name, jsConcerns, policyConcerns })
@@ -2,9 +2,9 @@
2
2
  * Оркестратор одного правила під CLI `check`.
3
3
  *
4
4
  * Послідовність (concerns у межах правила — алфавітно):
5
- * 1. **applies-гейт** з `js/applies/check.mjs`. Якщо модуль експортує `applies()` і вона повертає
5
+ * 1. **applies-гейт** з `fix/applies/check.mjs`. Якщо модуль експортує `applies()` і вона повертає
6
6
  * false — друкуємо `✅ правило не застосовне` і завершуємо без подальших викликів.
7
- * 2. **JS-концерни** — кожен `check*.mjs` у `js/<concern>/`. Concern `applies` теж може мати
7
+ * 2. **JS-концерни** — кожен `check*.mjs` у `fix/<concern>/`. Concern `applies` теж може мати
8
8
  * `check()` для друку контексту (його `applies()` уже відпрацював на кроці 1, він не повторюється).
9
9
  * 3. **Policy-концерни** — кожен `policy/<concern>/target.json` через `runConftestBatch`.
10
10
  * Резолвер `resolveTargetFiles` ділить cache (`walkCache`) між концернами.
@@ -22,7 +22,9 @@ import { runConftestBatch } from './run-conftest-batch.mjs'
22
22
  const APPLIES_CONCERN_NAME = 'applies'
23
23
 
24
24
  /**
25
- * Обчислює абсолютний шлях до файла-check у JS-концерні.
25
+ * Обчислює абсолютний шлях до файла-check у JS-концерні: `rules/<id>/fix/<concern>/<file>`.
26
+ * Legacy `js/` корінь і поле `concern.rootDir` прибрані у 1.11.12 — усі правила переїхали
27
+ * у `fix/` ще у 1.11.10.
26
28
  * @param {string} bundledRulesDir абсолютний `rules/`
27
29
  * @param {string} ruleId id правила
28
30
  * @param {import('./discover-checkable-rules.mjs').JsConcern} concern опис концерну
@@ -30,11 +32,11 @@ const APPLIES_CONCERN_NAME = 'applies'
30
32
  * @returns {string} абсолютний шлях
31
33
  */
32
34
  function resolveJsCheckPath(bundledRulesDir, ruleId, concern, fileName) {
33
- return join(bundledRulesDir, ruleId, 'js', concern.name, fileName)
35
+ return join(bundledRulesDir, ruleId, 'fix', concern.name, fileName)
34
36
  }
35
37
 
36
38
  /**
37
- * Спробувати викликати applies() гейт з `js/applies/check.mjs` правила.
39
+ * Спробувати викликати applies() гейт з `fix/applies/check.mjs` правила.
38
40
  * Гейт активний лише за наявності концерну з імʼям `applies` і експортом-функцією `applies` у його
39
41
  * першому check-файлі (алфавіт).
40
42
  * @param {string} bundledRulesDir абсолютний `rules/`
@@ -1,147 +0,0 @@
1
- /**
2
- * Прогоняє `conftest test` по всіх Rego-полісі з `npm/rules/<rule>/policy/<concern>/`.
3
- *
4
- * Джерело правди — `target.json` поруч із кожним `<concern>.rego`. Маніфест декларує,
5
- * які файли проєкту фідити в conftest (`files.single` або `files.walkGlob`). Resolver
6
- * і walk-кеш — спільні з CLI `check` (`scripts/utils/resolve-target-files.mjs`),
7
- * discovery — `scripts/utils/discover-checkable-rules.mjs`.
8
- *
9
- * Фільтрація за `.n-cursor.json:rules` — не перевіряємо полісі правил, які проєкт
10
- * не активує (як було у попередній hardcoded TARGETS-таблиці).
11
- *
12
- * Поведінка fallback:
13
- * - якщо `conftest` не в PATH — `ℹ` install-hint, повертаємо 0 (структурні JS-перевірки
14
- * в `check-*.mjs` лишаються паралельно). Те саме рішення — у `rules/ga/js/lint.mjs`.
15
- * - якщо `rules/` каталог відсутній (нетипова інсталяція) — також `ℹ` skip.
16
- *
17
- * Перший ненульовий exit-код conftest — повертаємо як результат, але всі наступні цілі
18
- * все одно виконуємо, щоб одразу побачити повний список порушень.
19
- *
20
- * Експортовано `runLintConftestCli` — використовується з `bin/n-cursor.js` як підкоманда
21
- * `lint-conftest`, а також виконується напряму через `bun ./npm/scripts/lint-conftest.mjs`.
22
- */
23
- import { existsSync, readFileSync } from 'node:fs'
24
- import { readFile } from 'node:fs/promises'
25
- import { spawnSync } from 'node:child_process'
26
- import { dirname, join } from 'node:path'
27
- import { fileURLToPath } from 'node:url'
28
-
29
- import { discoverCheckableRules } from './utils/discover-checkable-rules.mjs'
30
- import { resolveCmd } from './utils/resolve-cmd.mjs'
31
- import { resolveTargetFiles } from './utils/resolve-target-files.mjs'
32
-
33
- /** Каталог пакету `@nitra/cursor`, від якого ресолвимо вшиті директорії правил. */
34
- const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
35
-
36
- /** Шлях до кореня правил. У npm-tarball публікується через `files: ["rules"]`. */
37
- const RULES_DIR = join(PACKAGE_ROOT, 'rules')
38
-
39
- /**
40
- * Зчитує `rules` з `.n-cursor.json` у cwd. Повертає множину рядків — або `null`,
41
- * якщо файлу немає чи поле некоректне (тоді гейтинг вимикаємо — як було в попередній версії).
42
- * @param {string} cwd корінь репо
43
- * @returns {Set<string> | null} множина активних правил або null
44
- */
45
- function loadActiveCursorRules(cwd) {
46
- const path = join(cwd, '.n-cursor.json')
47
- if (!existsSync(path)) return null
48
- try {
49
- const raw = JSON.parse(readFileSync(path, 'utf8'))
50
- if (!Array.isArray(raw?.rules)) return null
51
- return new Set(raw.rules.map(String))
52
- } catch {
53
- return null
54
- }
55
- }
56
-
57
- /**
58
- * Обчислює namespace rego-полісі за id правила і ім'ям концерну.
59
- * Rego не дозволяє '-' в імені пакета, тож kebab-id у `.n-cursor.json:rules`
60
- * мапиться на snake у namespace; ім'я концерну йде як є (вже snake у `policy/<concern>/`).
61
- * @param {string} ruleId id правила (kebab)
62
- * @param {string} concernName ім'я concern (підкаталог у `policy/`)
63
- * @returns {string} namespace для `conftest --namespace`
64
- */
65
- function computeNamespace(ruleId, concernName) {
66
- return `${ruleId.replaceAll('-', '_')}.${concernName}`
67
- }
68
-
69
- /**
70
- * Запускає conftest на одному policy-концерні. Повертає exit-код (0 — OK, 1+ — порушення).
71
- *
72
- * stdio: 'inherit' — щоб користувач бачив рідну форматовану табличку conftest у виводі
73
- * `bun run lint` (відрізняється від структурованого JSON-варіанта в `check`-команді).
74
- * @param {string} conftestBin абсолютний шлях до бінарника conftest
75
- * @param {string} ruleId id правила
76
- * @param {string} concernName ім'я concern
77
- * @param {string} namespace rego-пакет
78
- * @param {string[]} files список файлів (відносні/абсолютні шляхи)
79
- * @returns {number} exit-код
80
- */
81
- function runConftestForConcern(conftestBin, ruleId, concernName, namespace, files) {
82
- const policyAbs = join(RULES_DIR, ruleId, 'policy', concernName)
83
- if (!existsSync(policyAbs)) {
84
- return 0
85
- }
86
- console.log(`\n▶ conftest (${namespace} — ${files.length} файл(ів))`)
87
- const r = spawnSync(conftestBin, ['test', ...files, '-p', policyAbs, '--namespace', namespace, '--no-color'], {
88
- stdio: 'inherit',
89
- env: process.env
90
- })
91
- if (r.error) {
92
- console.error(`❌ Не вдалося запустити conftest: ${r.error.message}`)
93
- return 1
94
- }
95
- return r.status ?? 1
96
- }
97
-
98
- /**
99
- * Запускає `conftest test` по всіх policy-концернах із `target.json`-маніфестів.
100
- * Фільтрація — за `activeRules` (поле `rules` у `.n-cursor.json`). Перший ненульовий
101
- * exit-код запамʼятовується, але цикл йде до кінця.
102
- *
103
- * Якщо `conftest` не знайдено в PATH — друкує `ℹ` повідомлення і повертає 0.
104
- * @returns {Promise<number>} 0 — все OK або skip; інакше — перший ненульовий exit-код
105
- */
106
- export async function runLintConftestCli() {
107
- const conftestBin = resolveCmd('conftest')
108
- if (!conftestBin) {
109
- console.log(
110
- 'ℹ conftest не знайдено в PATH — пропускаю Rego-перевірки.\n' +
111
- ' Встанови, щоб запустити локально: brew install conftest (macOS) або https://www.conftest.dev/install/'
112
- )
113
- return 0
114
- }
115
- if (!existsSync(RULES_DIR)) {
116
- console.log(`ℹ Каталог правил не знайдено (${RULES_DIR}) — пропускаю conftest.`)
117
- return 0
118
- }
119
-
120
- const cwd = process.cwd()
121
- const activeRules = loadActiveCursorRules(cwd)
122
- const rules = await discoverCheckableRules(RULES_DIR)
123
- /** @type {Map<string, Promise<string[]>>} */
124
- const walkCache = new Map()
125
- let firstFailureCode = 0
126
-
127
- for (const rule of rules) {
128
- if (activeRules && !activeRules.has(rule.id)) continue
129
- for (const concern of rule.policyConcerns) {
130
- const targetPath = join(RULES_DIR, rule.id, 'policy', concern.name, 'target.json')
131
- /** @type {{ files: { single?: string, walkGlob?: string|string[], required?: boolean }, missingMessage?: string }} */
132
- const target = JSON.parse(await readFile(targetPath, 'utf8'))
133
- const files = await resolveTargetFiles(target.files, cwd, walkCache)
134
- if (files.length === 0) continue
135
- const namespace = computeNamespace(rule.id, concern.name)
136
- const code = runConftestForConcern(conftestBin, rule.id, concern.name, namespace, files)
137
- if (code !== 0 && firstFailureCode === 0) {
138
- firstFailureCode = code
139
- }
140
- }
141
- }
142
- return firstFailureCode
143
- }
144
-
145
- if (import.meta.url === `file://${process.argv[1]}`) {
146
- process.exitCode = (await runLintConftestCli()) ?? 0
147
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes