@nitra/cursor 1.13.83 → 1.13.85

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 (96) hide show
  1. package/.claude-template/commands/n-check.md +2 -2
  2. package/CHANGELOG.md +56 -27
  3. package/README.md +10 -10
  4. package/bin/n-cursor.js +40 -60
  5. package/package.json +1 -1
  6. package/rules/abie/abie.mdc +2 -2
  7. package/rules/abie/fix.mjs +5 -1
  8. package/rules/adr/fix.mjs +5 -1
  9. package/rules/adr/js/hooks/check.mjs +1 -1
  10. package/rules/bun/bun.mdc +1 -1
  11. package/rules/bun/fix.mjs +5 -1
  12. package/rules/bun/js/layout/check.mjs +1 -1
  13. package/rules/capacitor/fix.mjs +5 -1
  14. package/rules/capacitor/policy/package_json/package_json.rego +3 -3
  15. package/rules/changelog/changelog.mdc +1 -1
  16. package/rules/changelog/fix.mjs +5 -1
  17. package/rules/ci4/ci4.mdc +1 -1
  18. package/rules/ci4/fix.mjs +5 -1
  19. package/rules/docker/docker.mdc +6 -6
  20. package/rules/docker/fix.mjs +5 -1
  21. package/rules/docker/lint/lint.mjs +10 -3
  22. package/rules/docker/policy/package_json/package_json.rego +1 -1
  23. package/rules/efes/efes.mdc +1 -1
  24. package/rules/efes/fix.mjs +5 -1
  25. package/rules/feedback/feedback.mdc +2 -2
  26. package/rules/feedback/fix.mjs +5 -1
  27. package/rules/ga/fix.mjs +5 -1
  28. package/rules/ga/lint/lint.mjs +5 -5
  29. package/rules/ga/policy/workflow_common/workflow_common.rego +1 -1
  30. package/rules/graphql/fix.mjs +5 -1
  31. package/rules/graphql/policy/vscode_extensions/vscode_extensions.rego +2 -2
  32. package/rules/hasura/fix.mjs +5 -1
  33. package/rules/image-avif/fix.mjs +5 -1
  34. package/rules/image-avif/image-avif.mdc +1 -1
  35. package/rules/image-avif/js/avif_generation/check.mjs +1 -1
  36. package/rules/image-compress/fix.mjs +5 -1
  37. package/rules/image-compress/js/package_setup/check.mjs +1 -1
  38. package/rules/js-bun-db/fix.mjs +5 -1
  39. package/rules/js-bun-redis/fix.mjs +5 -1
  40. package/rules/js-lint/fix.mjs +5 -1
  41. package/rules/js-lint/js/tooling/check.mjs +2 -2
  42. package/rules/js-lint/js-lint.mdc +1 -1
  43. package/rules/js-mssql/fix.mjs +5 -1
  44. package/rules/js-mssql/policy/package_json/package_json.rego +2 -2
  45. package/rules/js-run/fix.mjs +5 -1
  46. package/rules/js-run/js/runtime/check.mjs +3 -3
  47. package/rules/k8s/fix.mjs +5 -1
  48. package/rules/k8s/js/manifests/check.mjs +1 -1
  49. package/rules/k8s/k8s.mdc +12 -12
  50. package/rules/k8s/lint/lint.mjs +12 -5
  51. package/rules/k8s/policy/base_kustomization/base_kustomization.rego +3 -3
  52. package/rules/k8s/policy/base_manifest/base_manifest.rego +2 -2
  53. package/rules/k8s/policy/gateway/gateway.rego +2 -2
  54. package/rules/k8s/policy/hasura_configmap/hasura_configmap.rego +3 -3
  55. package/rules/k8s/policy/hasura_httproute/hasura_httproute.rego +1 -1
  56. package/rules/k8s/policy/hpa_pdb/hpa_pdb.rego +1 -1
  57. package/rules/k8s/policy/kustomization/kustomization.rego +2 -2
  58. package/rules/k8s/policy/manifest/manifest.rego +4 -4
  59. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml.rego +2 -2
  60. package/rules/k8s/policy/svc_yaml/svc_yaml.rego +2 -2
  61. package/rules/nginx-default-tpl/fix.mjs +5 -1
  62. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions.rego +2 -2
  63. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings.rego +1 -1
  64. package/rules/npm-module/fix.mjs +5 -1
  65. package/rules/npm-module/js/package_structure/check.mjs +2 -2
  66. package/rules/php/fix.mjs +5 -1
  67. package/rules/php/js/tooling/check.mjs +2 -2
  68. package/rules/rego/fix.mjs +5 -1
  69. package/rules/rego/lint/lint.mjs +12 -2
  70. package/rules/security/fix.mjs +5 -1
  71. package/rules/security/security.mdc +1 -1
  72. package/rules/style-lint/fix.mjs +5 -1
  73. package/rules/style-lint/js/tooling/check.mjs +2 -2
  74. package/rules/tauri/fix.mjs +5 -1
  75. package/rules/tauri/js/tooling/check.mjs +1 -1
  76. package/rules/tauri/policy/vscode_extensions/vscode_extensions.rego +2 -2
  77. package/rules/test/fix.mjs +5 -1
  78. package/rules/test/test.mdc +1 -1
  79. package/rules/text/fix.mjs +5 -1
  80. package/rules/text/js/formatting/check.mjs +2 -2
  81. package/rules/text/lint/lint.mjs +9 -2
  82. package/rules/text/text.mdc +1 -1
  83. package/rules/vue/fix.mjs +5 -1
  84. package/rules/vue/js/packages/check.mjs +1 -1
  85. package/rules/vue/policy/package_json/package_json.rego +1 -1
  86. package/rules/vue/vue.mdc +1 -1
  87. package/schemas/n-cursor.json +1 -1
  88. package/scripts/build-agents-commands.mjs +1 -1
  89. package/scripts/claude-stop-hook.mjs +1 -1
  90. package/scripts/utils/discover-check-rules-from-cursor.mjs +1 -1
  91. package/scripts/utils/read-n-cursor-config-lite.mjs +59 -0
  92. package/scripts/utils/run-rule-cli.mjs +37 -0
  93. package/scripts/utils/run-standard-rule.mjs +12 -3
  94. package/scripts/utils/with-lock.mjs +2 -2
  95. package/skills/fix/SKILL.md +5 -5
  96. package/skills/lint/SKILL.md +1 -1
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -347,9 +347,9 @@ async function checkOxlintRc(passFn, failFn) {
347
347
  */
348
348
  async function checkLintJsWorkflows(passFn, failFn) {
349
349
  if (existsSync('.github/workflows/lint-js.yml')) {
350
- passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor check → js_lint.lint_js_yml)')
350
+ passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor fix → js_lint.lint_js_yml)')
351
351
  } else {
352
- failFn('.github/workflows/lint-js.yml не існує — створи його (див. check-js-lint.mjs / js-lint.mdc)')
352
+ failFn('.github/workflows/lint-js.yml не існує — створи його (див. rules/js-lint/fix.mjs / js-lint.mdc)')
353
353
  }
354
354
 
355
355
  if (existsSync('.github/workflows/lint.yml')) {
@@ -64,7 +64,7 @@ version: '1.23'
64
64
 
65
65
  У корені проєкту має бути **`knip.json`**, який стартує з канонічного baseline з пакета `@nitra/cursor` — файл [`npm/rules/js-lint/js/tooling/knip-canonical.json`](./js/tooling/knip-canonical.json). Він покриває типові false-positives для наших правил: `entry` зі CLI-конфігами (eslint, stylelint, oxlint, jscpd, markdownlint-cli2, `commitlint`), `project` для `**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}`, `ignore` для `**/__fixtures__/**`, `ignoreDependencies` для пакетів, посилання на які є лише в не-JS-конфігах (`@nitra/cspell-dict`, `/@cspell\/dict-.+/`), і `ignoreBinaries` для CLI, які канон вимагає викликати через `npx`/`bunx` і яких заборонено додавати в `devDependencies` (`actionlint`, `cspell`, `depcheck`, `eslint`, `git-ai`, `jscpd`, `markdownlint-cli2`, `oxfmt`, `oxlint`, `shellcheck`, `uvx`, `v8r`, `zizmor`).
66
66
 
67
- Якщо `knip.json` відсутній — `npx @nitra/cursor check js-lint` копіює канон у корінь проєкту (side effect). Після створення модифікуй файл під свій проєкт як завгодно: перевіряємо лише наявність, зміст подальших змін не валідується.
67
+ Якщо `knip.json` відсутній — `npx @nitra/cursor fix js-lint` копіює канон у корінь проєкту (side effect). Після створення модифікуй файл під свій проєкт як завгодно: перевіряємо лише наявність, зміст подальших змін не валідується.
68
68
 
69
69
  Пакет `knip` окремо в `devDependencies` не додавай — `bunx knip` тягне його ad-hoc, як oxlint/eslint/jscpd.
70
70
 
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -1,4 +1,4 @@
1
- # Порт перевірки версії `mssql` з `npm/scripts/check-js-mssql.mjs` (js-mssql.mdc).
1
+ # Порт перевірки версії `mssql` з `npm/scripts/rules/js-mssql/fix.mjs` (js-mssql.mdc).
2
2
  #
3
3
  # Запуск (локально, для будь-якого `package.json`):
4
4
  # conftest test path/to/package.json -p npm/policy/js_mssql \
@@ -9,7 +9,7 @@
9
9
  #
10
10
  # AST-скан коду на per-request `new sql.ConnectionPool(...)` всередині функцій
11
11
  # (потребує парсингу `.js` / `.ts` через oxc-parser), а також full-semver
12
- # (`major.minor.patch` triple-compare у `check-js-mssql.mjs`) лишаються у JS:
12
+ # (`major.minor.patch` triple-compare у `rules/js-mssql/fix.mjs`) лишаються у JS:
13
13
  # JS-перевірка authoritative, ця Rego — швидкий gate для одиничного `package.json`.
14
14
  #
15
15
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -7,7 +7,7 @@
7
7
  * (див. `utils/bunyan-imports.mjs`);
8
8
  * - наявність `OTEL_RESOURCE_ATTRIBUTES` зі значеннями `service.name=` та `service.namespace=`
9
9
  * у `k8s/base/configmap.yaml`, якщо такий файл існує (відповідність імені ConfigMap імені
10
- * Deployment перевіряється в `check-k8s.mjs`);
10
+ * Deployment перевіряється в `rules/k8s/fix.mjs`);
11
11
  * - «Внутрішні аліаси» (`#conn/*`): імпорти `bun#SQL`, будь-який `mssql`, `@nitra/graphql-request#GraphQLClient`
12
12
  * дозволені лише у каталозі conn (за замовчуванням `src/conn/`; за наявності
13
13
  * `package.json#imports['#conn/*']` — у його цільовому каталозі); поза ним — порушення
@@ -334,7 +334,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
334
334
  // Frontend-пакети (vite у devDependencies) виходять за межі js-run:
335
335
  // браузерний бандл не має `node:process`, а `process.env.*` бандлер
336
336
  // обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
337
- // bunyan-залежність валідується в Rego (`npx @nitra/cursor check`).
337
+ // bunyan-залежність валідується в Rego (`npx @nitra/cursor fix`).
338
338
  if (packageJsonHasViteDevDependency(pkgJson)) {
339
339
  passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
340
340
  return
@@ -418,7 +418,7 @@ async function loadPackageJson(rootDir) {
418
418
  function checkOtelConfigmap(rootDir, passFn) {
419
419
  const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
420
420
  if (!existsSync(configmapPath)) return
421
- passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @nitra/cursor check → js_run.configmap)`)
421
+ passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @nitra/cursor fix → js_run.configmap)`)
422
422
  }
423
423
 
424
424
  /**
package/rules/k8s/fix.mjs CHANGED
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -79,7 +79,7 @@
79
79
  * Явні винятки до загальної логіки yannh/datree — таблиця **`EXPLICIT_K8S_SCHEMAS`** (`Map`): ключ
80
80
  * **`apiVersion`, `kind`, `type`** (для CRD без поля `type` у маніфесті — зірочка **`*`** як третій
81
81
  * компонент). Спочатку шукається збіг за фактичним `type`, потім за **`*`**.
82
- * Dockerfile — правило docker.mdc, скрипт check-docker.mjs.
82
+ * Dockerfile — правило docker.mdc, скрипт rules/docker/fix.mjs.
83
83
  *
84
84
  * **Структура `HTTPRoute` для Hasura-Deployment:** звіряється канон 4 правил у **`spec.rules`** (редиректи **`<prefix>/ql`** і **`<prefix>/ql/`** на **`<prefix>/ql/console`** 302, **`PathPrefix <prefix>/ql`** + **URLRewrite** на **`/`**, окреме WebSocket-правило з **`RequestHeaderModifier`** remove **`Authorization`**). **Префікс параметризовано** (рядок перед **`/ql`** у першому Hasura-правилі). **Прив'язка** — за **`metadata.name`** у тому ж каталозі, що й **Deployment** з образом **`hasura/graphql-engine`** (див. k8s.mdc). **Додаткові правила** поверх канону дозволені.
85
85
  *
package/rules/k8s/k8s.mdc CHANGED
@@ -17,13 +17,13 @@ alwaysApply: false
17
17
 
18
18
  **Modeline — опційний:** якщо для конкретного поєднання `apiVersion`/`kind` **немає** надійної публічної схеми (yannh/datree/schemastore не покривають), залиш файл **без** рядка `# yaml-language-server: $schema=…`. **Заборонено** ставити `$schema=file:…` як заглушку — це створює видимість валідації без неї. Без modeline `check-k8s` не валідує URL, але **`lint-k8s`** (kubeconform/kubescape) продовжить роботу. Якщо modeline присутній — він обов'язково перший рядок і **тільки** `https://` URL, який відповідає очікуваному за `apiVersion`/`kind`.
19
19
 
20
- **Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядок — одразу YAML (`apiVersion:` тощо). Перевірка — **`check-k8s.mjs`**.
20
+ **Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядок — одразу YAML (`apiVersion:` тощо). Перевірка — **`rules/k8s/fix.mjs`**.
21
21
 
22
22
  **Розширення:** усі маніфести під **`k8s`**, включно з **`kustomization.yaml`**, — лише **`.yaml`** (розширення **`.yml`** не використовуй).
23
23
 
24
24
  **Скоп — поза `.github/`:** правило стосується YAML-маніфестів у каталогах **`k8s`** (визначаються відносно кореня репо, не за абсолютним шляхом). Файли під **`.github/workflows/`** та **`.github/actions/`** ця перевірка **не** зачіпає — їхній скоп визначає **`ga.mdc`** (там канон — **`.yml`**). Це робить два правила несуперечливими навіть у проєктах, де сам корінь репо випадково має ім'я `k8s/` (тоді сегмент `k8s` присутній у абсолютному шляху всіх файлів, але **відносно кореня** його там немає).
25
25
 
26
- **Dockerfile / hadolint** — окреме правило **`docker.mdc`** і **`npx @nitra/cursor check docker`**.
26
+ **Dockerfile / hadolint** — окреме правило **`docker.mdc`** і **`npx @nitra/cursor fix docker`**.
27
27
 
28
28
  ## lint-k8s: kubeconform і kubescape
29
29
 
@@ -31,7 +31,7 @@ alwaysApply: false
31
31
 
32
32
  **Залежності:** виконувані файли kubeconform, kubescape і kubectl у **PATH** (kustomize використовуємо як вшиту підкоманду **`kubectl kustomize`** — окремий бінарник `kustomize` не потрібен); не додавай їх у **devDependencies**.
33
33
 
34
- **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`check-k8s.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
34
+ **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`rules/k8s/fix.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
35
35
 
36
36
  **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Збірка через kustomize нормалізує namespace на workload-маніфестах і **NetworkPolicy у `base/networkpolicy.yaml`** (через `base/kustomization.yaml` `namespace:`), що дає коректний матчинг `podSelector` у `C-0260` (`Missing network policy`) і дозволяє kubescape бачити дерево overlays / components зі справжніми ресурсами. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
37
37
 
@@ -53,7 +53,7 @@ alwaysApply: false
53
53
  }
54
54
  ```
55
55
 
56
- Якщо правило **`k8s`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **мають** бути скрипт **`lint-k8s`** і виклик **`bun run lint-k8s`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor check bun`**.
56
+ Якщо правило **`k8s`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **мають** бути скрипт **`lint-k8s`** і виклик **`bun run lint-k8s`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor fix bun`**.
57
57
 
58
58
  Додай workflow **`.github/workflows/lint-k8s.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**). **Не** дублюй **`setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** і **`bun install`** у job — після **`checkout`** використовуй локальний composite **`setup-bun-deps`** (шлях **`./.github/actions/setup-bun-deps`** або **`./npm/github-actions/setup-bun-deps`**, як у репозиторії). Встановлення **kubeconform** / **kubescape** лишаються окремими кроками.
59
59
 
@@ -152,7 +152,7 @@ patches:
152
152
 
153
153
  Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
154
154
 
155
- Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег із константи **`HASURA_GRAPHQL_ENGINE_IMAGE`** у **`check-k8s.mjs`** (допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
155
+ Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег із константи **`HASURA_GRAPHQL_ENGINE_IMAGE`** у **`rules/k8s/fix.mjs`** (допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
156
156
 
157
157
  ### HTTPRoute для Deployment з `hasura/graphql-engine`
158
158
 
@@ -254,7 +254,7 @@ spec:
254
254
 
255
255
  Пара файлів для кластерного й headless **Service**: **`svc.yaml`** + **`svc-hl.yaml`** в одному каталозі, **`spec.type: ClusterIP`** / **`clusterIP: None`**, імена **`-hl`**, узгодженість пар, маршрути **`gateway.networking.k8s.io`** (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**) — **backendRef** лише на сервіси з суфіксом **`-hl`**; якщо **kustomization** посилається на **`svc.yaml`**, у **тому ж** **`kustomization.yaml`** має бути посилання на sibling **`svc-hl.yaml`**. Скрипт **не** створює файли — додай **`svc-hl.yaml`** вручну (копія з правками **name** / **clusterIP**).
256
256
 
257
- **Точні умови та повідомлення `fail`** — верхній JSDoc **`npm/scripts/check-k8s.mjs`**.
257
+ **Точні умови та повідомлення `fail`** — верхній JSDoc **`npm/scripts/rules/k8s/fix.mjs`**.
258
258
 
259
259
  ### Gateway API: не дублюй namespace у `backendRef`
260
260
 
@@ -293,11 +293,11 @@ spec:
293
293
 
294
294
  ## ConfigMap: ім'я збігається з Deployment
295
295
 
296
- Якщо в `k8s/base/` є **`configmap.yaml`** і **Deployment**, і цей Deployment посилається рівно на **один** ConfigMap — `metadata.name` ConfigMap має збігатися з `metadata.name` Deployment. Точні умови перевірки — **`check-k8s.mjs`**.
296
+ Якщо в `k8s/base/` є **`configmap.yaml`** і **Deployment**, і цей Deployment посилається рівно на **один** ConfigMap — `metadata.name` ConfigMap має збігатися з `metadata.name` Deployment. Точні умови перевірки — **`rules/k8s/fix.mjs`**.
297
297
 
298
298
  ## ConfigMap для Hasura-Deployment
299
299
 
300
- Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково** має бути ключ **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`**. Точні умови перевірки — **`check-k8s.mjs`**.
300
+ Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково** має бути ключ **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`**. Точні умови перевірки — **`rules/k8s/fix.mjs`**.
301
301
 
302
302
  ```yaml
303
303
  data:
@@ -323,7 +323,7 @@ data:
323
323
 
324
324
  - **`base/kustomization.yaml`:** поле **`namespace:`** має бути **непорожнім** (перевіряє **check k8s**, якщо файл є).
325
325
 
326
- - **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`check-k8s.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
326
+ - **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`rules/k8s/fix.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
327
327
 
328
328
  - **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
329
329
 
@@ -408,11 +408,11 @@ images:
408
408
  - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment.
409
409
  - **`topologySpreadConstraints`** — запис з `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app` рівне тій самій мітці `app`.
410
410
 
411
- **Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`check-k8s.mjs`**.
411
+ **Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`rules/k8s/fix.mjs`**.
412
412
 
413
413
  **Локальні шляхи в `kustomization.yaml`:** кожен запис без `://` (remote) з `resources` / `bases` / `components` / `crds`, `patchesStrategicMerge`, `patches[].path`, `patchesJson6902[].path`, `configurations[]`, `replacements[].path` має вказувати на **існуючий** у репозиторії файл (`.yaml` / `.yml`) або **каталог**; биті посилання — помилка **`check k8s`**.
414
414
 
415
- **Порядок `resources`:** у маніфесті з **`apiVersion: kustomize.config.k8s.io/…`**, **`kind: Kustomization`**, елементи **`resources:`** (лише непорожні рядки) мають бути **відсортовані за алфавітом (англ. локаль, як `localeCompare('en')` у `check-k8s.mjs`)**. Поля **`bases`**, **`components`**, **`crds`** цією перевіркою **не** впорядковуються.
415
+ **Порядок `resources`:** у маніфесті з **`apiVersion: kustomize.config.k8s.io/…`**, **`kind: Kustomization`**, елементи **`resources:`** (лише непорожні рядки) мають бути **відсортовані за алфавітом (англ. локаль, як `localeCompare('en')` у `rules/k8s/fix.mjs`)**. Поля **`bases`**, **`components`**, **`crds`** цією перевіркою **не** впорядковуються.
416
416
 
417
417
  ### Env-залежні межі (за сегментом після `/k8s/`)
418
418
 
@@ -617,7 +617,7 @@ spec:
617
617
  app: backend-api
618
618
  ```
619
619
 
620
- Точні умови та повідомлення `fail` — JSDoc на початку `npm/scripts/check-k8s.mjs` і функції `validateComponentsForBaseDeployment` / `prodOverlayHpaPdbOverrideNeeds`.
620
+ Точні умови та повідомлення `fail` — JSDoc на початку `npm/scripts/rules/k8s/fix.mjs` і функції `validateComponentsForBaseDeployment` / `prodOverlayHpaPdbOverrideNeeds`.
621
621
 
622
622
  ## HorizontalPodAutoscaler: `autoscaling/v2`
623
623
 
@@ -2,14 +2,14 @@
2
2
  * Запуск kubeconform та kubescape для каталогів `…/k8s`, де є YAML-маніфести (див. k8s.mdc).
3
3
  *
4
4
  * Знаходить унікальні корені каталогів із іменем `k8s` за шляхами файлів **`*.yaml`**
5
- * (той самий принцип сегмента `k8s`, що й у check-k8s.mjs; розширення **`.yml`** під `k8s` не використовується). Якщо таких файлів немає — вихід 0
5
+ * (той самий принцип сегмента `k8s`, що й у rules/k8s/fix.mjs; розширення **`.yml`** під `k8s` не використовується). Якщо таких файлів немає — вихід 0
6
6
  * без виклику зовнішніх CLI.
7
7
  *
8
8
  * kubeconform перевіряє маніфести проти OpenAPI-схем Kubernetes; kubescape — сканування на
9
9
  * misconfiguration / compliance (NSA, MITRE, CIS тощо). Обидві утиліти очікуються в PATH
10
10
  * (локально: Homebrew, релізи GitHub; у CI — крок установки з k8s.mdc).
11
11
  *
12
- * Версія `-kubernetes-version` для kubeconform узгоджена з PIN yannh у check-k8s.mjs / k8s.mdc.
12
+ * Версія `-kubernetes-version` для kubeconform узгоджена з PIN yannh у rules/k8s/fix.mjs / k8s.mdc.
13
13
  * Kubescape не має аналога цього прапорця; орієнтир цільового кластера — та сама лінія релізу (див. k8s.mdc).
14
14
  */
15
15
  import { spawnSync } from 'node:child_process'
@@ -24,6 +24,7 @@ import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
24
24
  import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
25
25
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
26
26
  import { walkDir } from '../../../scripts/utils/walkDir.mjs'
27
+ import { withLock } from '../../../scripts/utils/with-lock.mjs'
27
28
 
28
29
  /** Per-project kubescape exceptions file; підмішується через --exceptions, якщо існує в корені. */
29
30
  const KUBESCAPE_EXCEPTIONS_FILE = '.kubescape-exceptions.json'
@@ -320,11 +321,10 @@ async function runKubescape(dirs, root) {
320
321
  }
321
322
 
322
323
  /**
323
- * Головна точка входу: kubeconform + kubescape для усіх знайдених дерев `k8s`.
324
- * Експортовано як `runLintK8s` — використовується з `bin/n-cursor.js` як підкоманда `lint-k8s`.
324
+ * Внутрішні кроки `lint-k8s` без локу: kubeconform + kubescape для усіх знайдених дерев `k8s`.
325
325
  * @returns {Promise<number>} код виходу для `process.exitCode` (0 — успіх або пропуск)
326
326
  */
327
- export async function runLintK8s() {
327
+ async function runLintK8sSteps() {
328
328
  const root = process.cwd()
329
329
  const ignorePaths = await loadCursorIgnorePaths(root)
330
330
  const dirs = await findK8sRoots(root, ignorePaths)
@@ -344,6 +344,13 @@ export async function runLintK8s() {
344
344
  return ks
345
345
  }
346
346
 
347
+ /**
348
+ * Публічна CLI-форма: серіалізує через `withLock('lint-k8s')` + дедуп за станом git-дерева.
349
+ * Експортовано як `runLintK8s` — використовується з `bin/n-cursor.js` як підкоманда `lint-k8s`.
350
+ * @returns {Promise<number>} код виходу
351
+ */
352
+ export const runLintK8s = () => withLock('lint-k8s', runLintK8sSteps)
353
+
347
354
  if (isRunAsCli()) {
348
355
  process.exitCode = await runLintK8s()
349
356
  }
@@ -1,4 +1,4 @@
1
- # Порт перевірки `k8s/base/kustomization.yaml` з `npm/scripts/check-k8s.mjs`
1
+ # Порт перевірки `k8s/base/kustomization.yaml` з `npm/scripts/rules/k8s/fix.mjs`
2
2
  # (k8s.mdc): у base-kustomization обов'язково має бути непорожнє поле
3
3
  # `namespace:`.
4
4
  #
@@ -7,7 +7,7 @@
7
7
  # -p npm/policy/k8s/base_kustomization \
8
8
  # --namespace k8s.base_kustomization
9
9
  #
10
- # JS authoritative (`check-k8s.mjs`: `baseKustomizationNamespaceViolation`,
10
+ # JS authoritative (`rules/k8s/fix.mjs`: `baseKustomizationNamespaceViolation`,
11
11
  # `isBaseKustomizationPath` для відбору файла, `ensureBaseKustomizationHasNamespace`
12
12
  # як оркестратор).
13
13
  #
@@ -39,7 +39,7 @@ deny contains base_namespace_required_msg if {
39
39
  # на *локальний* `resources:` base/kustomization.yaml (точне ім'я `hpa.yaml`/`pdb.yaml`,
40
40
  # у будь-якому підкаталозі). Рекурсивний обхід `resources:`/`components:`/`bases:`
41
41
  # (із зануренням у вкладені kustomization.yaml) — JS-оркестратор
42
- # `verifyK8sBaseKustomizeHasNoHpaPdb` у `check-k8s.mjs` (потребує fs-доступу). Цей
42
+ # `verifyK8sBaseKustomizeHasNoHpaPdb` у `rules/k8s/fix.mjs` (потребує fs-доступу). Цей
43
43
  # rego-deny — defense-in-depth: спрацює навіть якщо JS-крок упаде з винятку раніше.
44
44
  #
45
45
  # NetworkPolicy у base — навпаки, обов'язковий (k8s.mdc): обмеження мережі мають
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # JS відбирає файли під `…/k8s/.../base/…` (окрім `kustomization.yaml`) і
9
9
  # викликає conftest з цією намеспейс. JS authoritative
10
- # (`check-k8s.mjs`: `metadataNamespaceRequiredViolation` з `inBaseDir=true`,
10
+ # (`rules/k8s/fix.mjs`: `metadataNamespaceRequiredViolation` з `inBaseDir=true`,
11
11
  # `deploymentResourcesViolation` з `inK8sBaseLayer=true`,
12
12
  # `isK8sBaseManifestYamlPath`).
13
13
  #
@@ -25,7 +25,7 @@ package k8s.base_manifest
25
25
  import rego.v1
26
26
 
27
27
  # Cluster-scoped kind (не вимагають metadata.namespace) — узгоджено з
28
- # `CLUSTER_SCOPED_KINDS` у `check-k8s.mjs`.
28
+ # `CLUSTER_SCOPED_KINDS` у `rules/k8s/fix.mjs`.
29
29
  cluster_scoped_kinds := {
30
30
  "APIService",
31
31
  "CertificateSigningRequest",
@@ -1,5 +1,5 @@
1
1
  # Порт пер-документних перевірок для Gateway API і HealthCheckPolicy з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc).
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc).
3
3
  #
4
4
  # Запуск (локально, по одному файлу або по дереву):
5
5
  # conftest test path/to/manifest.yaml -p npm/policy/k8s/gateway \
@@ -14,7 +14,7 @@
14
14
  # `metadata.namespace` маршруту, — заборонено (надлишкове поле, ламається при
15
15
  # overlay-перенесеннях).
16
16
  #
17
- # JS authoritative (`check-k8s.mjs` — функції `failIfGatewayRouteUsesNonHeadlessService`,
17
+ # JS authoritative (`rules/k8s/fix.mjs` — функції `failIfGatewayRouteUsesNonHeadlessService`,
18
18
  # `healthCheckPolicyTargetRefHeadlessServiceViolation`,
19
19
  # `collectGatewayApiRouteBackendServiceNames`,
20
20
  # `collectGatewayApiRouteBackendRefsWithRedundantNamespace`); ця Rego — швидкий
@@ -1,5 +1,5 @@
1
1
  # Порт перевірки ConfigMap для Hasura-Deployment з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc): у ConfigMap, що сусідствує з
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc): у ConfigMap, що сусідствує з
3
3
  # Hasura-Deployment, у `data` обов'язково має бути ключ
4
4
  # `HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS` зі значенням `"true"`.
5
5
  #
@@ -8,7 +8,7 @@
8
8
  # -p npm/policy/k8s/hasura_configmap \
9
9
  # --namespace k8s.hasura_configmap
10
10
  #
11
- # Прив'язка ConfigMap-Deployment cross-file — у JS (`check-k8s.mjs`:
11
+ # Прив'язка ConfigMap-Deployment cross-file — у JS (`rules/k8s/fix.mjs`:
12
12
  # `validateHasuraConfigMapRemoteSchemaPermissions` шукає Hasura-Deployment
13
13
  # у тому ж dir-у і викликає conftest з цією намеспейс лише для відповідних
14
14
  # ConfigMap-ів). JS authoritative (`hasuraConfigMapRemoteSchemaPermissionsViolation`,
@@ -20,7 +20,7 @@ package k8s.hasura_configmap
20
20
 
21
21
  import rego.v1
22
22
 
23
- # Обов'язковий ключ у `data` (узгоджено з `check-k8s.mjs`).
23
+ # Обов'язковий ключ у `data` (узгоджено з `rules/k8s/fix.mjs`).
24
24
  required_key := "HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS"
25
25
 
26
26
  key_missing_template := concat(" ", [
@@ -1,4 +1,4 @@
1
- # Порт перевірки `httpRouteHasuraCanonViolation` з `npm/scripts/check-k8s.mjs`
1
+ # Порт перевірки `httpRouteHasuraCanonViolation` з `npm/scripts/rules/k8s/fix.mjs`
2
2
  # (k8s.mdc): HTTPRoute, що сусідствує з Hasura-Deployment з тим самим
3
3
  # `metadata.name`, має містити канон з 4 правил у такому порядку:
4
4
  #
@@ -1,5 +1,5 @@
1
1
  # Порт **структурних** пер-документних перевірок HPA та PDB з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc). Перевіряє лише ті властивості, що
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc). Перевіряє лише ті властивості, що
3
3
  # не залежать від cross-file контексту (`expectedDeployName`, `expectedAppLabel`,
4
4
  # `isDevLike`-сегмента). Cross-file перевірки лишаються в JS
5
5
  # (`hpaManifestViolations`, `pdbManifestViolations`,
@@ -1,5 +1,5 @@
1
1
  # Порт пер-документних структурних перевірок `kustomization.yaml` з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc).
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc).
3
3
  #
4
4
  # Запуск (локально, на одному kustomization.yaml):
5
5
  # conftest test path/to/kustomization.yaml -p npm/policy/k8s/kustomization \
@@ -14,7 +14,7 @@
14
14
  #
15
15
  # JS authoritative: повна резолюція kustomize-дерева, перевірка існування
16
16
  # refs на диску, парність `svc.yaml`/`svc-hl.yaml`, вибір conftest-цілей за
17
- # patternом `kustomization.yaml` — у `check-k8s.mjs`.
17
+ # patternом `kustomization.yaml` — у `rules/k8s/fix.mjs`.
18
18
  #
19
19
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
20
20
  # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
@@ -1,4 +1,4 @@
1
- # Порт пер-документних структурних перевірок з `npm/scripts/check-k8s.mjs`
1
+ # Порт пер-документних структурних перевірок з `npm/scripts/rules/k8s/fix.mjs`
2
2
  # (k8s.mdc). Цей пакет описує лише ті правила, що дивляться на ОДИН манифест
3
3
  # (один YAML-документ): conftest за замовчуванням розрізає файли по `---` і
4
4
  # запускає policy на кожен документ окремо.
@@ -24,8 +24,8 @@
24
24
  # CROSS-FILE логіка (Kustomize-резолюція ресурсів, парність svc.yaml/svc-hl.yaml,
25
25
  # HPA/PDB/topologySpreadConstraints за каталогом, BackendConfig-сепарація,
26
26
  # yaml-language-server schema modeline, namespace-перевірки за деревом
27
- # `…/k8s/base/`) лишається у `check-k8s.mjs`: вона потребує файлової системи.
28
- # JS authoritative (`check-k8s.mjs` робить ці ж пер-документні перевірки в ширшому
27
+ # `…/k8s/base/`) лишається у `rules/k8s/fix.mjs`: вона потребує файлової системи.
28
+ # JS authoritative (`rules/k8s/fix.mjs` робить ці ж пер-документні перевірки в ширшому
29
29
  # контексті); ця Rego — швидкий gate для одиничного маніфеста.
30
30
  #
31
31
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
@@ -45,7 +45,7 @@ forbidden_service_annotations := {
45
45
  }
46
46
 
47
47
  # Дозволені посилання на образ `hasura/graphql-engine` (узгоджено з
48
- # `HASURA_GRAPHQL_ENGINE_IMAGE` у `check-k8s.mjs`). Зараз — один канонічний тег
48
+ # `HASURA_GRAPHQL_ENGINE_IMAGE` у `rules/k8s/fix.mjs`). Зараз — один канонічний тег
49
49
  # у двох варіантах префіксу (із `docker.io/` і без). Digest (`@sha256:…`)
50
50
  # відрізається перед звіркою.
51
51
  allowed_hasura_images := {
@@ -1,12 +1,12 @@
1
1
  # Порт пер-документної структурної перевірки `svc-hl.yaml` з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc): headless Service з суфіксом
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc): headless Service з суфіксом
3
3
  # `metadata.name` `-hl` і `spec.clusterIP: None`.
4
4
  #
5
5
  # Запуск (локально, лише для одного svc-hl.yaml):
6
6
  # conftest test path/to/k8s/.../svc-hl.yaml -p npm/policy/k8s/svc_hl_yaml \
7
7
  # --namespace k8s.svc_hl_yaml
8
8
  #
9
- # JS authoritative (`check-k8s.mjs`: `serviceSvcHlYamlHeadlessViolation`,
9
+ # JS authoritative (`rules/k8s/fix.mjs`: `serviceSvcHlYamlHeadlessViolation`,
10
10
  # вибір файла `svc-hl.yaml` через walk).
11
11
  #
12
12
  # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
@@ -1,12 +1,12 @@
1
1
  # Порт пер-документної структурної перевірки `svc.yaml` з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc): `Service` у файлі `svc.yaml` має
2
+ # `npm/scripts/rules/k8s/fix.mjs` (k8s.mdc): `Service` у файлі `svc.yaml` має
3
3
  # мати `spec.type: ClusterIP`.
4
4
  #
5
5
  # Запуск (локально, лише для одного svc.yaml):
6
6
  # conftest test path/to/k8s/.../svc.yaml -p npm/policy/k8s/svc_yaml \
7
7
  # --namespace k8s.svc_yaml
8
8
  #
9
- # JS authoritative (`check-k8s.mjs`: `serviceSvcYamlClusterIpTypeViolation`,
9
+ # JS authoritative (`rules/k8s/fix.mjs`: `serviceSvcYamlClusterIpTypeViolation`,
10
10
  # вибір файла `svc.yaml` через walk). Цю Rego JS викликає окремою таргет-командою
11
11
  # лише для basename == `svc.yaml`.
12
12
  #
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -1,8 +1,8 @@
1
1
  # Перевірка `.vscode/extensions.json` для nginx-default-tpl (nginx-default-tpl.mdc).
2
2
  #
3
- # Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
3
+ # Викликається з `rules/nginx-default-tpl/fix.mjs` через `runConftestBatch` лише
4
4
  # ПІСЛЯ того, як JS виявив `default.conf.template` у дереві (умовне правило).
5
- # Глобально без `target.json` поруч (не auto-discoverable через `n-cursor check`).
5
+ # Глобально без `target.json` поруч (не auto-discoverable через `n-cursor fix`).
6
6
  #
7
7
  # Canonical: `recommendations` має містити `ahmadalli.vscode-nginx-conf`.
8
8
  package nginx_default_tpl.vscode_extensions
@@ -1,6 +1,6 @@
1
1
  # Перевірка `.vscode/settings.json` для nginx-default-tpl (nginx-default-tpl.mdc).
2
2
  #
3
- # Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
3
+ # Викликається з `rules/nginx-default-tpl/fix.mjs` через `runConftestBatch` лише
4
4
  # ПІСЛЯ того, як JS виявив `default.conf.template`. Без `target.json` поруч
5
5
  # не реєструється.
6
6
  #
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }
@@ -204,7 +204,7 @@ function checkEmitTypesConfig(passFn, failFn) {
204
204
  )
205
205
  return
206
206
  }
207
- passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @nitra/cursor check → npm_module.emit_types_config)`)
207
+ passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @nitra/cursor fix → 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} є (структуру перевіряє npx @nitra/cursor check → npm_module.npm_publish_yml)`)
341
+ passFn(`${publishWf} є (структуру перевіряє npx @nitra/cursor fix → npm_module.npm_publish_yml)`)
342
342
  } else {
343
343
  failFn(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
344
344
  }
package/rules/php/fix.mjs CHANGED
@@ -2,6 +2,7 @@ import { runStandardRule } from '../../scripts/utils/run-standard-rule.mjs'
2
2
 
3
3
  /**
4
4
  * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
5
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
5
6
  * @param {import('../../scripts/utils/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
6
7
  * @returns {Promise<number>} 0 — OK, 1 — порушення
7
8
  */
@@ -10,6 +11,9 @@ export function run(ctx) {
10
11
  }
11
12
 
12
13
  if (import.meta.main) {
14
+ // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
15
+ // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
16
+ const { runRuleCli } = await import('../../scripts/utils/run-rule-cli.mjs')
13
17
  // eslint-disable-next-line unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
14
- process.exit(await run())
18
+ process.exit(await runRuleCli(import.meta.dirname))
15
19
  }