@nitra/cursor 1.11.8 → 1.11.13
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.
- package/CHANGELOG.md +45 -2
- package/README.md +48 -13
- package/bin/n-cursor.js +10 -12
- package/package.json +4 -1
- package/rules/abie/abie.mdc +1 -1
- package/rules/abie/utils/k8s-tree.mjs +1 -1
- package/rules/adr/{js → fix}/hooks/check.mjs +1 -1
- package/rules/bun/{js → fix}/layout/check.mjs +2 -2
- package/rules/ga/{js → lint}/lint.mjs +1 -1
- package/rules/graphql/policy/vscode_extensions/vscode_extensions.rego +2 -2
- package/rules/image-compress/{js → fix}/package_setup/check.mjs +2 -2
- package/rules/js-bun-db/{js → fix}/safety/check.mjs +1 -1
- package/rules/js-bun-redis/{js → fix}/imports/check.mjs +1 -1
- package/rules/js-lint/{js → fix}/tooling/check.mjs +1 -1
- package/rules/js-run/{js → fix}/runtime/check.mjs +4 -4
- package/rules/nginx-default-tpl/{js → fix}/template/check.mjs +1 -1
- package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.rego +1 -1
- package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.rego +1 -1
- package/rules/npm-module/{js → fix}/package_structure/check.mjs +2 -2
- package/rules/php/{js → fix}/tooling/check.mjs +3 -3
- package/rules/rego/policy/package_json/package_json.rego +1 -1
- package/rules/rego/policy/vscode_extensions/vscode_extensions.rego +1 -1
- package/rules/rego/policy/vscode_settings/vscode_settings.rego +1 -1
- package/rules/style-lint/{js → fix}/tooling/check.mjs +3 -3
- package/rules/tauri/{js → fix}/tooling/check.mjs +1 -1
- package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +2 -2
- package/rules/text/{js → fix}/formatting/check.mjs +4 -4
- package/rules/vue/{js → fix}/packages/check.mjs +1 -1
- package/scripts/utils/discover-checkable-rules.mjs +17 -15
- package/scripts/utils/run-rule.mjs +7 -5
- package/skills/fix/SKILL.md +1 -1
- package/scripts/lint-conftest.mjs +0 -147
- /package/rules/abie/{js → fix}/applies/check.mjs +0 -0
- /package/rules/abie/{js → fix}/env_dns/check.mjs +0 -0
- /package/rules/abie/{js → fix}/firebase_hosting/check.mjs +0 -0
- /package/rules/abie/{js → fix}/hc_pairing/check.mjs +0 -0
- /package/rules/abie/{js → fix}/ua_http_route/check.mjs +0 -0
- /package/rules/abie/{js → fix}/ua_node_selector/check.mjs +0 -0
- /package/rules/capacitor/{js → fix}/platforms/check.mjs +0 -0
- /package/rules/changelog/{js → fix}/consistency/check.mjs +0 -0
- /package/rules/docker/{js → fix}/lint/check.mjs +0 -0
- /package/rules/docker/{js/run.mjs → lint/lint.mjs} +0 -0
- /package/rules/ga/{js → fix}/workflows/check.mjs +0 -0
- /package/rules/graphql/{js → fix}/tooling/check.mjs +0 -0
- /package/rules/hasura/{js → fix}/internal_urls/check.mjs +0 -0
- /package/rules/image-avif/{js → fix}/avif_generation/check.mjs +0 -0
- /package/rules/js-mssql/{js → fix}/deps/check.mjs +0 -0
- /package/rules/k8s/{js → fix}/manifests/check.mjs +0 -0
- /package/rules/k8s/{js/run.mjs → lint/lint.mjs} +0 -0
- /package/rules/php/{js/run.mjs → lint/lint.mjs} +0 -0
- /package/rules/rego/{js → fix}/applies/check.mjs +0 -0
- /package/rules/rego/{js → lint}/lint.mjs +0 -0
- /package/rules/text/{js → lint}/lint.mjs +0 -0
- /package/rules/text/{js → lint}/run-shellcheck.mjs +0 -0
- /package/rules/text/{js → lint}/run-v8r.mjs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,17 +4,60 @@
|
|
|
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.13] - 2026-05-16
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`npm/rules/{bun,image-compress,js-bun-redis,js-run,php,style-lint,text}/fix/<concern>/check.mjs`** — escape `@nitra` як `\@nitra` у JSDoc-блоках (`/** … */`), де `npx @nitra/cursor check` стояв всередині backticks. ESLint-плагін `jsdoc/escape-inline-tags` парсив `@nitra` як інлайн-тег (false-positive у backticks) і видавав 8 warnings. Виправлення — escape-символ `\` перед `@`, як уже зроблено в інших місцях коду (CHANGELOG 1.11.5 для `js-run`). Pass-повідомлення та `//`-коментарі поза JSDoc не зачіпало — там парсер не активний.
|
|
12
|
+
- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк) — заголовок секції перейменовано з **«Скоуп»** на **«Scope»**, бо cspell флагав «Скоуп» як unknown word. Англійський «Scope» зрозумілий і не вимагає розширення словника. Тіло секції без змін.
|
|
13
|
+
|
|
14
|
+
## [1.11.12] - 2026-05-15
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- **`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/` не торкається — лише сканер.
|
|
19
|
+
- **`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.
|
|
20
|
+
|
|
21
|
+
## [1.11.11] - 2026-05-15
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- **`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`).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **`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/`.
|
|
30
|
+
- **`.cursor/rules/conftest.mdc`** — крок 5 у workflow «нова перевірка» переписано: окремої реєстрації нового rego-пакета в TARGETS більше не потрібно (TARGETS видалено разом із `lint-conftest.mjs`); `discoverCheckableRules` автоматично підхоплює пакет за наявності `target.json` поруч з `.rego`.
|
|
31
|
+
- **`.cursor/rules/scripts.mdc`** — згадку `lint-conftest.mjs` прибрано зі списку «крос-правильної інфраструктури» у `npm/scripts/`.
|
|
32
|
+
- **`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`.
|
|
33
|
+
- **`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`)».
|
|
34
|
+
|
|
35
|
+
## [1.11.10] - 2026-05-15
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **`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`.
|
|
40
|
+
- **`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`.
|
|
41
|
+
- **`npm/rules/k8s/lint/run-roots.test.mjs`** — import `./run.mjs` → `./lint.mjs` (файл перейменовано в межах переїзду).
|
|
42
|
+
|
|
43
|
+
## [1.11.9] - 2026-05-15
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **`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).
|
|
48
|
+
- **`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` (зворотна сумісність із наявними тестовими фікстурами).
|
|
49
|
+
|
|
7
50
|
## [1.11.8] - 2026-05-15
|
|
8
51
|
|
|
9
52
|
### Changed
|
|
10
53
|
|
|
11
|
-
- **`npm/bin/n-cursor.js::syncSkills`** — файл `auto.md` зі скілу більше **не** копіюється у `.cursor/skills/n-<id>/`. `auto.md` — це службова мета для CLI-сторони (`scripts/auto-skills.mjs` читає його з пакета, щоб вирішити, чи автоматично активувати скіл у `.n-cursor.json`), у проєкті він зайвий і лише засмічує `.cursor/skills/`.
|
|
54
|
+
- **`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` оновлено відповідно.
|
|
12
55
|
|
|
13
56
|
## [1.11.7] - 2026-05-15
|
|
14
57
|
|
|
15
58
|
### Changed
|
|
16
59
|
|
|
17
|
-
- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк) — `/n-fix` більше **не запускає** `bun run lint` і **не делегує** до `/n-lint`. Крок 6 (`bun run lint` з делегуванням, доданий у 1.11.6) повністю видалено; замість нього на початку SKILL.md додано секцію
|
|
60
|
+
- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк) — `/n-fix` більше **не запускає** `bun run lint` і **не делегує** до `/n-lint`. Крок 6 (`bun run lint` з делегуванням, доданий у 1.11.6) повністю видалено; замість нього на початку SKILL.md додано секцію **«Scope»**, де явно зафіксовано: `/n-fix` опікується лише структурою проєкту (правила `.cursor/rules/` + `npx @nitra/cursor check`), а лінт-порушення у самому коді (ESLint/oxlint/jscpd/cspell/knip/sonarjs/stylelint) — поза скоупом і виправляються винятково через `/n-lint`. Кроки 7→6 і 8→7 переномеровано; остаточний пункт 7 додатково нагадує, що лінт-помилки не входять у критерій успіху `/n-fix`. Мета — щоб агент, що виконує `/n-fix`, не плутав свою задачу з `/n-lint` і не запускав важкий `bun run lint` без потреби; те, що діагностує `/n-lint`, виправляється там же, а не дублюється тут.
|
|
18
61
|
|
|
19
62
|
### Fixed
|
|
20
63
|
|
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
## Як це працює
|
|
6
6
|
|
|
7
|
-
Репозиторій `@nitra/cursor` містить cursor-правила у директорії `
|
|
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
|
-
Наприклад, правило `
|
|
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`** дотримуйтесь **`
|
|
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/
|
|
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
|
|
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
|
|
100
|
-
├──
|
|
101
|
-
│ ├── npm-module
|
|
102
|
-
│
|
|
103
|
-
|
|
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
|
|
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 (разом з `
|
|
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
|
-
- Шлях до шаблону: поруч із `
|
|
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
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
* Якщо ключа skills немає, за замовчуванням підтягуються всі підкаталоги skills/ (лише імена без префікса n-).
|
|
48
48
|
* Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
|
|
49
49
|
* Файл `auto.md` у скілі — джерело правди для auto-skills у CLI (`scripts/auto-skills.mjs`)
|
|
50
|
-
* і у проєкт не копіюється; раніше синхронізовані `auto.md`
|
|
50
|
+
* і у проєкт не копіюється; раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI
|
|
51
|
+
* не чіпає — їх потрібно прибрати вручну.
|
|
51
52
|
*
|
|
52
53
|
* Якщо в корені є package.json і в ньому ще немає \@nitra/cursor у devDependencies (і не оголошено
|
|
53
54
|
* в dependencies), CLI дописує devDependencies з діапазоном ^<version> поточного пакету — зручно після npx.
|
|
@@ -76,11 +77,11 @@ import { detectAutoSkills } from '../scripts/auto-skills.mjs'
|
|
|
76
77
|
import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
|
|
77
78
|
import { discoverCheckableRules } from '../scripts/utils/discover-checkable-rules.mjs'
|
|
78
79
|
import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
79
|
-
import { runLintDocker } from '../rules/docker/
|
|
80
|
-
import { runLintGaCli } from '../rules/ga/
|
|
81
|
-
import { runLintK8s } from '../rules/k8s/
|
|
82
|
-
import { runLintRego } from '../rules/rego/
|
|
83
|
-
import { runLintTextCli } from '../rules/text/
|
|
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'
|
|
84
85
|
import { runRule } from '../scripts/utils/run-rule.mjs'
|
|
85
86
|
import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
|
|
86
87
|
import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
|
|
@@ -777,8 +778,6 @@ async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
|
777
778
|
const content = await readFile(join(srcDir, file), 'utf8')
|
|
778
779
|
await writeFile(join(destDir, file), content, 'utf8')
|
|
779
780
|
}
|
|
780
|
-
const stalePath = join(destDir, 'auto.md')
|
|
781
|
-
if (existsSync(stalePath)) await unlink(stalePath)
|
|
782
781
|
console.log(`✅`)
|
|
783
782
|
success++
|
|
784
783
|
} catch (error) {
|
|
@@ -1010,10 +1009,9 @@ function logRemovedManagedItems(title, basePath, names) {
|
|
|
1010
1009
|
}
|
|
1011
1010
|
|
|
1012
1011
|
/**
|
|
1013
|
-
* Знаходить правила, для яких є хоча б щось прогонне: JS-концерн у `rules/<id>/
|
|
1014
|
-
*
|
|
1015
|
-
* `rules
|
|
1016
|
-
* див. `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`.
|
|
1017
1015
|
* @returns {import('../scripts/utils/discover-checkable-rules.mjs').CheckableRule[]} опис правил у алфавітному порядку
|
|
1018
1016
|
*/
|
|
1019
1017
|
function discoverCheckScripts() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.13",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"picomatch": "^4.0.4",
|
|
50
50
|
"yaml": "^2.8.3"
|
|
51
51
|
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@nitra/cursor": "^1.11.9"
|
|
54
|
+
},
|
|
52
55
|
"engines": {
|
|
53
56
|
"bun": ">=1.3",
|
|
54
57
|
"node": ">=25"
|
package/rules/abie/abie.mdc
CHANGED
|
@@ -163,7 +163,7 @@ with:
|
|
|
163
163
|
|
|
164
164
|
### Швидкий gate через conftest (Rego)
|
|
165
165
|
|
|
166
|
-
Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/
|
|
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/
|
|
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 перевіряє
|
|
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** (`
|
|
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 є (структуру перевіряє
|
|
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 '
|
|
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
|
-
# Тому
|
|
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** (`
|
|
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 є (структуру перевіряє
|
|
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/`; `
|
|
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
|
-
* `
|
|
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 є (структуру перевіряє
|
|
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 (`
|
|
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
|
-
* `
|
|
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/`); `
|
|
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 —
|
|
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/
|
|
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
|
-
# Глобально
|
|
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`.
|
|
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} є (структуру перевіряє
|
|
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} є (структуру перевіряє
|
|
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** (`
|
|
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 перевіряє
|
|
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} є (структуру перевіряє
|
|
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` файлів у дереві. Глобально
|
|
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).
|
|
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` файлів у дереві. Глобально
|
|
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** (`
|
|
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
|
-
// `
|
|
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} є (структуру перевіряє
|
|
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-полісі глобально
|
|
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
|
-
# `
|
|
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** (`
|
|
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` (
|
|
96
|
-
//
|
|
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} є (структуру перевіряє
|
|
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
|
-
// через `
|
|
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>/
|
|
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/`
|
|
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>` у `
|
|
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-концерни одного правила: підкаталоги `
|
|
42
|
+
* Перелічує JS-концерни одного правила: підкаталоги `fix/<name>/` з принаймні одним `check*.mjs`.
|
|
41
43
|
*
|
|
42
|
-
* `
|
|
43
|
-
* @param {string}
|
|
44
|
+
* `fix/utils/` свідомо пропускається — це хелпери, а не концерни.
|
|
45
|
+
* @param {string} fixDir абсолютний шлях `rules/<id>/fix/`
|
|
44
46
|
* @returns {Promise<JsConcern[]>} концерни в алфавітному порядку
|
|
45
47
|
*/
|
|
46
|
-
async function listJsConcerns(
|
|
47
|
-
if (!existsSync(
|
|
48
|
-
const topLevel = await readdir(
|
|
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(
|
|
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, '
|
|
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-гейт** з `
|
|
5
|
+
* 1. **applies-гейт** з `fix/applies/check.mjs`. Якщо модуль експортує `applies()` і вона повертає
|
|
6
6
|
* false — друкуємо `✅ правило не застосовне` і завершуємо без подальших викликів.
|
|
7
|
-
* 2. **JS-концерни** — кожен `check*.mjs` у `
|
|
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, '
|
|
35
|
+
return join(bundledRulesDir, ruleId, 'fix', concern.name, fileName)
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
* Спробувати викликати applies() гейт з `
|
|
39
|
+
* Спробувати викликати applies() гейт з `fix/applies/check.mjs` правила.
|
|
38
40
|
* Гейт активний лише за наявності концерну з імʼям `applies` і експортом-функцією `applies` у його
|
|
39
41
|
* першому check-файлі (алфавіт).
|
|
40
42
|
* @param {string} bundledRulesDir абсолютний `rules/`
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
|
|
7
7
|
# n-fix — автоматичне виправлення проєкту
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Scope
|
|
10
10
|
|
|
11
11
|
Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor check` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`). Не запускай `bun run lint` із цього скілу і не намагайся виправляти його порушення тут — це задача `/n-lint`. Якщо `npx @nitra/cursor check` чистий, а `bun run lint` лишився червоним — запусти `/n-lint` окремо.
|
|
12
12
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|