@nitra/cursor 1.8.228 → 1.9.0

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 CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.9.0] - 2026-05-11
8
+
9
+ ### Changed
10
+
11
+ - **mdc frontmatter — `alwaysApply: false` + `globs` для файлово-чітких правил:** `ga` (`.github/workflows/*.yml`), `vue` (`**/*.vue`), `php` (`**/*.php`), `style-lint` (`**/*.{css,scss,vue}`), `nginx-default-tpl` (`**/default.{conf.template,tpl.conf}`), `image-avif` (`**/*.{png,jpg,jpeg,gif,avif,vue,html}`), `image-compress` (`**/*.{png,jpg,jpeg,gif,svg}`), `changelog` (`**/{CHANGELOG.md,package.json}`), `hasura` (`**/hasura/**,**/*.env`), `graphql` (`**/*.{vue,js,mjs,cjs,ts,tsx,jsx}`). Раніше тільки `docker`, `k8s`, `rego` тримали file-scoped формат; решта вантажилася в контекст Cursor завжди (`alwaysApply: true`). Тепер правило підтягується лише коли в контексті є файл за патерном — менше «шуму» у промптах для несуміжних задач. Версії bump-нуто на патч-крок у кожному `*.mdc`. Проєктно-широкі правила (`bun`, `npm-module`, `ci4`, `text`, `js-lint`) і opt-in (`abie`, `adr`) лишилися `alwaysApply: true` без globs.
12
+
13
+ ## [1.8.229] - 2026-05-11
14
+
15
+ ### Removed
16
+
17
+ - **k8s / `k8s.kustomize_managed`:** правило «`metadata.namespace` заборонено у YAML, досяжних через граф Kustomize» зняте — воно конфліктувало з `k8s.base_manifest`, який натомість **вимагає** `metadata.namespace` у `…/k8s/base/…` для namespaced kind. Перетин предикатів був порожній, що давало ~50 хибних помилок у канонічних деревах `base + overlays` (adminer, run/nginx, reference-grant, otel, dremio, gateway тощо). Видалено: правило з `mdc/k8s.mdc` (бульйт «Де не дублювати `metadata.namespace`»), rego-полісь `npm/policy/k8s/kustomize_managed/`, JS-helpers `metadataNamespaceForbiddenViolation` і `collectKustomizeManagedRelPaths` разом з відповідними тестами та плумінгом `kustomizeManagedRel` через `runAllK8sRego` / `checkK8sYamlFile`. Логіка `base_manifest` (`metadata.namespace` обов'язковий у `k8s/base/`) лишається; у overlays Kustomize це значення буде перезаписано полем `namespace:` з `kustomization.yaml`.
18
+
7
19
  ## [1.8.228] - 2026-05-10
8
20
 
9
21
  ### Changed
package/mdc/changelog.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: CHANGELOG.md в кожному workspace, з двома моделями бази порівняння
3
- alwaysApply: true
4
- version: '2.0'
3
+ version: '2.1'
4
+ globs: "**/{CHANGELOG.md,package.json}"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  Bun monorepo: у кожному workspace із кореневого `package.json.workspaces` (плюс кореневий пакет, плюс `npm/`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** — кожен пакет веде свій. Правило `npm-module` відповідає лише за публікацію типів і workflow, а CHANGELOG — за цим правилом.
package/mdc/ga.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: Правила форматів для .github/workflows
3
- alwaysApply: true
4
- version: '1.7'
3
+ version: '1.8'
4
+ globs: ".github/workflows/*.yml"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  У `.github/workflows/` лише **`.yml`**. Мають бути **`clean-ga-workflows.yml`**, **`clean-merged-branch.yml`**, **`lint-ga.yml`**, **`git-ai.yml`**. Якщо є **`apply-k8s.yml`** / **`apply-nats-consumer.yml`** — paths у тригері як у фрагментах.
package/mdc/graphql.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: GraphQL у коді (tagged template `gql`) — GraphQL Config і розширення VS Code
3
- alwaysApply: true
4
- version: '1.0'
3
+ version: '1.1'
4
+ globs: "**/*.{vue,js,mjs,cjs,ts,tsx,jsx}"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  Якщо в **`.vue`** або в **JavaScript / TypeScript** джерелах (`.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.jsx` тощо) зустрічається **tagged template literal** з тегом **`gql`** (типово `gql\`query …\`` для GraphQL-запиту), у **корені репозиторію** мають бути:
package/mdc/hasura.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: Правила для директорії з hasura graphql-engine
3
- alwaysApply: true
4
- version: '1.0'
3
+ version: '1.1'
4
+ globs: "**/hasura/**,**/*.env"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  ## Підключення для оновлення метаданих у CI (Nitra та Abinbevefes)
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
3
- alwaysApply: true
4
- version: '1.1'
3
+ version: '1.2'
4
+ globs: "**/*.{png,jpg,jpeg,gif,avif,vue,html}"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor check image-avif` — у `lint-image` прапорець `--avif` заборонений (це валідує правило `image-compress`). Перевірка робить три кроки в порядку:
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: Оптимізація raster/SVG через @nitra/minify-image у локальному lint
3
- alwaysApply: true
4
- version: '1.1'
3
+ version: '1.2'
4
+ globs: "**/*.{png,jpg,jpeg,gif,svg}"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **3.3.1**) запускається через `npx` (як `markdownlint-cli2` у text.mdc) і **не** додається в `dependencies` / `devDependencies`. Канонічний `lint-image` — авто-оптимізація з прапорцем `--write`: стискає raster/SVG на місці. **AVIF-генерація (`--avif`) у `lint-image` заборонена** — її виконує окреме правило `image-avif` (`npx @nitra/cursor check image-avif`), яке заодно прибирає AVIF-сироти. Split-cache робить повторні прогони дешевими — і локально, і після `git clone`.
package/mdc/k8s.mdc CHANGED
@@ -313,9 +313,7 @@ data:
313
313
 
314
314
  - **`base/kustomization.yaml`:** поле **`namespace:`** має бути **непорожнім** (перевіряє **check k8s**, якщо файл є).
315
315
 
316
- - **Де не дублювати `metadata.namespace`:** у YAML, досяжних через **граф Kustomize** (шляхи з **`kustomization.yaml`**, як у логіці **`collectKustomizeManagedRelPaths`** / **check k8s**). **Namespace** задає **`namespace:`** у kustomization.
317
-
318
- - **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`**, який **не** в графі жодного kustomization — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`check-k8s.mjs`**). Якщо namespace у маніфесті не потрібен — підключи файл через **`resources`** / **`patches`** тощо.
316
+ - **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** непорожній **`metadata.namespace`** для namespaced **kind** (винятки кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`check-k8s.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
319
317
 
320
318
  - **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
321
319
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  description: Правила nginx для статичних файлів
3
- version: '1.2'
3
+ version: '1.3'
4
+ globs: "**/default.{conf.template,tpl.conf}"
5
+ alwaysApply: false
4
6
  ---
5
7
 
6
8
  > **Автоматична міграція:** `npx @nitra/cursor check nginx-default-tpl` автоматично перейменовує `default.tpl.conf` → `default.conf.template` (або перезаписує вміст, якщо обидва файли існують). Якщо шаблон відсутній — перевірка пропускається.
package/mdc/php.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: PHP
3
- alwaysApply: true
4
- version: '1.0'
3
+ version: '1.1'
4
+ globs: "**/*.php"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  Весь код повинен відповідати PHP 8.5, перевіряти за допомогою PHPCompatibility, конвертувати за допомогою Rector.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: Правила стилів CSS та SCSS
3
- alwaysApply: true
4
- version: '1.2'
3
+ version: '1.3'
4
+ globs: "**/*.{css,scss,vue}"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  ## Генерація та редагування стилів (Cursor і інші агенти)
package/mdc/vue.mdc CHANGED
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  description: Vue
3
- alwaysApply: true
4
- version: '1.7'
3
+ version: '1.8'
4
+ globs: "**/*.vue"
5
+ alwaysApply: false
5
6
  ---
6
7
 
7
8
  # Vue 3 Composition API — правила для .cursorrules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.228",
3
+ "version": "1.9.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -117,11 +117,11 @@ is_kustomization if {
117
117
  }
118
118
 
119
119
  resources_present if {
120
- _ := input.resources
120
+ "resources" in object.keys(input)
121
121
  }
122
122
 
123
123
  patches_present if {
124
- _ := input.patches
124
+ "patches" in object.keys(input)
125
125
  }
126
126
 
127
127
  # Список непорожніх рядкових шляхів resources у порядку файлу (для повідомлення).
@@ -215,7 +215,8 @@ has_non_empty_cpu_request(container) if {
215
215
 
216
216
  # Чи у контейнера в реальності присутнє поле resources.requests.cpu (хай і порожнє).
217
217
  has_cpu_field(container) if {
218
- _ := container.resources.requests.cpu
218
+ requests := object.get(object.get(container, "resources", {}), "requests", {})
219
+ "cpu" in object.keys(requests)
219
220
  }
220
221
 
221
222
  # Чи у контейнера є непорожнє resources.requests.memory (рядок або число > 0).
@@ -233,7 +234,8 @@ has_non_empty_memory_request(container) if {
233
234
 
234
235
  # Чи у контейнера в реальності присутнє поле resources.requests.memory.
235
236
  has_memory_field(container) if {
236
- _ := container.resources.requests.memory
237
+ requests := object.get(object.get(container, "resources", {}), "requests", {})
238
+ "memory" in object.keys(requests)
237
239
  }
238
240
 
239
241
  # Чи рядок `image` посилається на репозиторій `hasura/graphql-engine` (з тегом
@@ -360,18 +360,6 @@ export function isForbiddenK8sDevPath(rel) {
360
360
  return n.includes('/k8s/dev/')
361
361
  }
362
362
 
363
- /**
364
- * Відносний шлях від кореня репозиторію у вигляді з `/` (для множини kustomize).
365
- * @param {string} root корінь cwd
366
- * @param {string} abs абсолютний шлях
367
- * @returns {string | null} posix-відносний шлях або null, якщо поза root
368
- */
369
- function posixRelFromAbs(root, abs) {
370
- const r = (relative(root, abs) || abs).replaceAll('\\', '/')
371
- if (r.startsWith('..')) return null
372
- return r
373
- }
374
-
375
363
  /**
376
364
  * Вбудовані та поширені **кластерні** `kind`, для яких **`metadata.namespace`** не застосовується.
377
365
  * CRD з невідомим kind лишаються з вимогою namespace, якщо файл не в kustomization — за потреби додай path у `resources`.
@@ -887,104 +875,6 @@ async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail)
887
875
  }
888
876
  }
889
877
 
890
- /**
891
- * Збирає відносні шляхи (posix) до YAML, підключених до Kustomize з будь-якого **`kustomization.yaml`** під `k8s`.
892
- * Обходить **`resources`**, **`bases`**, **`components`**, **`crds`**, **`patches[].path`**, **`patchesStrategicMerge`**;
893
- * для каталогу з **`kustomization.yaml`** виконує рекурсивний обхід.
894
- * @param {string} root корінь репозиторію
895
- * @param {string[]} yamlFilesAbs відсортовані абсолютні шляхи до `*.yaml` / `*.yml` під k8s (для `.yml` check-k8s вимагає перейменувати на `.yaml`)
896
- * @returns {Promise<Set<string>>} множина відносних шляхів до керованих файлів
897
- */
898
- export async function collectKustomizeManagedRelPaths(root, yamlFilesAbs) {
899
- /** @type {Set<string>} */
900
- const managed = new Set()
901
- const kustomizationAbsList = yamlFilesAbs.filter(abs => {
902
- const b = basename(abs).toLowerCase()
903
- return b === 'kustomization.yaml'
904
- })
905
-
906
- /** @type {Set<string>} */
907
- const visitedKustomization = new Set()
908
-
909
- /**
910
- * @param {string} kustAbs абсолютний шлях до kustomization.yaml
911
- * @returns {Promise<void>}
912
- */
913
- async function walkKustomization(kustAbs) {
914
- const normKust = resolve(kustAbs)
915
- if (visitedKustomization.has(normKust)) return
916
- visitedKustomization.add(normKust)
917
-
918
- let raw
919
- try {
920
- raw = await readFile(normKust, 'utf8')
921
- } catch {
922
- return
923
- }
924
- const lines = toLines(raw)
925
- const body = yamlBodyAfterModeline(lines)
926
-
927
- /** @type {import('yaml').Document[] | undefined} */
928
- let docs
929
- try {
930
- docs = parseAllDocuments(body)
931
- } catch {
932
- return
933
- }
934
- const first = docs[0]?.toJSON()
935
- if (first === null || first === undefined || typeof first !== 'object' || Array.isArray(first)) return
936
-
937
- const kustDir = dirname(normKust)
938
- const pathRefs = pathsFromKustomizationObject(first)
939
-
940
- /**
941
- * @param {string} ref шлях з kustomization
942
- * @returns {Promise<void>}
943
- */
944
- async function handleKustomizeManagedPathRef(ref) {
945
- if (ref.includes('://')) {
946
- return
947
- }
948
- const resolved = resolve(kustDir, ref)
949
- let st
950
- try {
951
- st = await stat(resolved)
952
- } catch {
953
- st = undefined
954
- }
955
- if (!st) {
956
- return
957
- }
958
- if (st.isFile()) {
959
- if (YAML_EXTENSION_RE.test(resolved)) {
960
- const pr = posixRelFromAbs(root, resolved)
961
- if (pr !== null) {
962
- managed.add(pr)
963
- }
964
- }
965
- return
966
- }
967
- if (!st.isDirectory()) {
968
- return
969
- }
970
- const childK = existsSync(join(resolved, 'kustomization.yaml')) ? join(resolved, 'kustomization.yaml') : null
971
- if (childK !== null) {
972
- await walkKustomization(childK)
973
- }
974
- }
975
-
976
- for (const ref of pathRefs) {
977
- await handleKustomizeManagedPathRef(ref)
978
- }
979
- }
980
-
981
- for (const k of kustomizationAbsList) {
982
- await walkKustomization(k)
983
- }
984
-
985
- return managed
986
- }
987
-
988
878
  /**
989
879
  * Шляхи лише з полів ресурсів Kustomization (**без** patch-файлів).
990
880
  * @param {unknown} obj корінь першого документа Kustomization
@@ -1331,7 +1221,7 @@ async function kustomizationTreeHasDeploymentUnderK8sBase(kustAbs, rootNorm) {
1331
1221
 
1332
1222
  /**
1333
1223
  * Збирає дескриптори ресурсів з **`resources` / `bases` / `components` / `crds`** для одного дерева kustomization.
1334
- * Повторний вхід у той самий **`kustomization.yaml`** дає порожній внесок (як у **`collectKustomizeManagedRelPaths`**).
1224
+ * Повторний вхід у той самий **`kustomization.yaml`** дає порожній внесок.
1335
1225
  * @param {string} kustAbs абсолютний шлях до **kustomization.yaml**
1336
1226
  * @param {string} rootNorm нормалізований абсолютний корінь репозиторію
1337
1227
  * @param {Set<string>} visitedKustomization нормалізовані абсолютні шляхи відвіданих **kustomization.yaml**
@@ -3505,22 +3395,6 @@ async function validateHasuraHttpRouteCanon(root, yamlFiles, fail) {
3505
3395
  }
3506
3396
  }
3507
3397
 
3508
- /**
3509
- * Для маніфестів, **підключених** до Kustomize (шлях у `resources` / `patches` / …), **metadata.namespace** не додають.
3510
- * @param {unknown} manifest корінь YAML-документа
3511
- * @returns {string | null} текст порушення або null, якщо поля немає
3512
- */
3513
- export function metadataNamespaceForbiddenViolation(manifest) {
3514
- if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
3515
- return null
3516
- const rec = /** @type {Record<string, unknown>} */ (manifest)
3517
- const meta = rec.metadata
3518
- if (meta !== null && typeof meta === 'object' && !Array.isArray(meta) && 'namespace' in meta) {
3519
- return 'metadata.namespace заборонено — namespace задає kustomization.yaml (поле namespace); файл підключено через resources / patches / … (див. k8s.mdc)'
3520
- }
3521
- return null
3522
- }
3523
-
3524
3398
  /**
3525
3399
  * Вимагає непорожній **metadata.namespace** для namespaced-документів (крім кластерних kind).
3526
3400
  * @param {unknown} manifest корінь YAML-документа
@@ -3574,7 +3448,7 @@ export function isK8sBaseManifestYamlPath(rel, baseLower) {
3574
3448
 
3575
3449
  // Plan B: per-document валідаційне ядро для k8s YAML повністю в rego —
3576
3450
  // `k8s.manifest`, `k8s.gateway`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`,
3577
- // `k8s.kustomize_managed`, `k8s.base_manifest`. Виклик через `runAllK8sRego`.
3451
+ // `k8s.base_manifest`. Виклик через `runAllK8sRego`.
3578
3452
  // JS-функції failIfK8sPolicyNamespaceRulesViolated, failIfK8sPolicyResourceRulesViolated,
3579
3453
  // validateK8sYamlPolicyDocuments видалено.
3580
3454
 
@@ -3679,13 +3553,12 @@ function countSchemaModelines(lines) {
3679
3553
  * @param {string[]} lines рядки файлу
3680
3554
  * @param {(msg: string) => void} fail реєстрація помилки
3681
3555
  * @param {(msg: string) => void} pass реєстрація успіху
3682
- * @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
3683
3556
  * @returns {void}
3684
3557
  */
3685
- function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass, _kustomizeManagedRel) {
3558
+ function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass) {
3686
3559
  // Per-document валідація (Ingress/autoscaling/v1 заборонено, Gateway API backendRef,
3687
- // metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.kustomize_managed`,
3688
- // `k8s.base_manifest`), батч-виклик з `runAllK8sRego` на початку `check()`.
3560
+ // metadata.namespace правила) — у rego (`k8s.manifest`, `k8s.gateway`, `k8s.base_manifest`),
3561
+ // батч-виклик з `runAllK8sRego` на початку `check()`.
3689
3562
  pass(`${rel}: HttpBackendGroup (alb.yc.io/v1alpha1) — modeline $schema не застосовується (k8s.mdc)`)
3690
3563
  }
3691
3564
 
@@ -3697,10 +3570,9 @@ function checkK8sYamlHttpBackendGroupFile(rel, _baseLower, _lines, _fail, pass,
3697
3570
  * @param {string[]} lines рядки файлу
3698
3571
  * @param {(msg: string) => void} fail реєстрація помилки
3699
3572
  * @param {(msg: string) => void} pass реєстрація успіху
3700
- * @param {Set<string>} kustomizeManagedRel kustomize-managed шляхи
3701
3573
  * @returns {void}
3702
3574
  */
3703
- function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass, kustomizeManagedRel) {
3575
+ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass) {
3704
3576
  const match = lines[0].match(MODELINE_RE)
3705
3577
  if (!match) {
3706
3578
  fail(`${rel}: некоректний modeline $schema у першому рядку`)
@@ -3746,10 +3618,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
3746
3618
  * @param {string} root корінь репозиторію
3747
3619
  * @param {(msg: string) => void} fail реєстрація помилки
3748
3620
  * @param {(msg: string) => void} pass реєстрація успіху
3749
- * @param {Set<string>} kustomizeManagedRel відносні posix-шляхи з collectKustomizeManagedRelPaths
3750
3621
  * @returns {Promise<void>}
3751
3622
  */
3752
- async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
3623
+ async function checkK8sYamlFile(abs, root, fail, pass) {
3753
3624
  const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
3754
3625
  const base = basename(abs)
3755
3626
  const baseLower = base.toLowerCase()
@@ -3790,7 +3661,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
3790
3661
  )
3791
3662
  return
3792
3663
  }
3793
- checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass, kustomizeManagedRel)
3664
+ checkK8sYamlHttpBackendGroupFile(rel, baseLower, lines, fail, pass)
3794
3665
  return
3795
3666
  }
3796
3667
 
@@ -3799,7 +3670,7 @@ async function checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel) {
3799
3670
  return
3800
3671
  }
3801
3672
 
3802
- checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass, kustomizeManagedRel)
3673
+ checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pass)
3803
3674
  }
3804
3675
 
3805
3676
  /**
@@ -6030,19 +5901,18 @@ async function runKustomizationImagesCleanup(kustAbs, rel, fail, pass) {
6030
5901
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
6031
5902
  */
6032
5903
  /**
6033
- * Plan B (rego-authoritative): на початку `check()` батч-викликаємо всі 9 path-фільтрованих
6034
- * rego-пакетів з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
5904
+ * Plan B (rego-authoritative): на початку `check()` батч-викликаємо path-фільтровані
5905
+ * rego-пакети з `npm/policy/k8s/` через `runConftestBatch`. Пакети hasura_configmap і
6035
5906
  * hasura_httproute мають cross-file gating (паруються з Hasura-Deployment) — вони запускаються
6036
5907
  * з відповідних orchestrator-функцій (`validateHasuraConfigMapRemoteSchemaPermissions`,
6037
5908
  * `validateHasuraHttpRouteCanon`). Структурна частина HPA/PDB (`k8s.hpa_pdb`) тут на всіх yaml,
6038
5909
  * env-залежні межі min/maxReplicas і expected-name — JS-cross-file у `validateDeploymentHpaPdbAndTopology`.
6039
5910
  * @param {string} root корінь репозиторію (cwd)
6040
5911
  * @param {string[]} yamlFiles абсолютні шляхи знайдених *.yaml під `…/k8s/`
6041
- * @param {Set<string>} kustomizeManagedRel відносні posix-шляхи kustomize-managed файлів
6042
5912
  * @param {(msg: string) => void} fail callback при помилці
6043
5913
  * @returns {void}
6044
5914
  */
6045
- function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
5915
+ function runAllK8sRego(root, yamlFiles, fail) {
6046
5916
  const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
6047
5917
 
6048
5918
  const allYaml = yamlFiles
@@ -6055,7 +5925,6 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
6055
5925
  if (!K8S_BASE_SEGMENT_RE.test(r)) return false
6056
5926
  return basename(p).toLowerCase() !== 'kustomization.yaml'
6057
5927
  })
6058
- const kustomizeManagedFiles = yamlFiles.filter(p => kustomizeManagedRel.has(relOf(p)))
6059
5928
 
6060
5929
  /** @type {Array<{ ns: string, dir: string, files: string[] }>} */
6061
5930
  const targets = [
@@ -6066,8 +5935,7 @@ function runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail) {
6066
5935
  { ns: 'k8s.svc_yaml', dir: 'k8s/svc_yaml', files: svcYaml },
6067
5936
  { ns: 'k8s.svc_hl_yaml', dir: 'k8s/svc_hl_yaml', files: svcHlYaml },
6068
5937
  { ns: 'k8s.base_kustomization', dir: 'k8s/base_kustomization', files: baseKustYaml },
6069
- { ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml },
6070
- { ns: 'k8s.kustomize_managed', dir: 'k8s/kustomize_managed', files: kustomizeManagedFiles }
5938
+ { ns: 'k8s.base_manifest', dir: 'k8s/base_manifest', files: baseResourceYaml }
6071
5939
  ]
6072
5940
 
6073
5941
  for (const t of targets) {
@@ -6103,16 +5971,14 @@ export async function check() {
6103
5971
 
6104
5972
  assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
6105
5973
 
6106
- const kustomizeManagedRel = await collectKustomizeManagedRelPaths(root, yamlFiles)
6107
-
6108
5974
  // Plan B: пер-документні структурні правила — у rego-полісі `npm/policy/k8s/*`,
6109
5975
  // викликаємо одним батчем на namespace через runConftestBatch. JS нижче робить
6110
5976
  // лише cross-file orchestration, modeline та FS-existence перевірки.
6111
- runAllK8sRego(root, yamlFiles, kustomizeManagedRel, fail)
5977
+ runAllK8sRego(root, yamlFiles, fail)
6112
5978
  pass(`Rego-полісі (npm/policy/k8s/*) виконано на ${yamlFiles.length} файл(ах)`)
6113
5979
 
6114
5980
  for (const abs of yamlFiles) {
6115
- await checkK8sYamlFile(abs, root, fail, pass, kustomizeManagedRel)
5981
+ await checkK8sYamlFile(abs, root, fail, pass)
6116
5982
  }
6117
5983
 
6118
5984
  await validateSvcYamlAndSvcHlPairs(root, yamlFiles, fail)
@@ -102,7 +102,7 @@ export function runConftestBatch(opts) {
102
102
  let parsed
103
103
  try {
104
104
  parsed = JSON.parse(result.stdout)
105
- } catch (e) {
105
+ } catch {
106
106
  throw new Error(`conftest stdout не парситься як JSON: ${(result.stdout || '').slice(0, 200)}`)
107
107
  }
108
108
  /** @type {ConftestViolation[]} */
@@ -1,31 +0,0 @@
1
- # Порт перевірки `metadataNamespaceForbiddenViolation` з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc): для файлів, які підключено до якогось
3
- # `kustomization.yaml` через `resources` / `patches` / `…`, поле
4
- # `metadata.namespace` забороняється — namespace задає сам kustomization.
5
- #
6
- # Запуск (локально, лише для одного kustomize-managed YAML):
7
- # conftest test path/to/manifest.yaml -p npm/policy/k8s/kustomize_managed \
8
- # --namespace k8s.kustomize_managed
9
- #
10
- # JS відбирає kustomize-managed файли через `collectKustomizeManagedRelPaths`
11
- # і викликає conftest з цією намеспейс. JS authoritative
12
- # (`check-k8s.mjs`: `metadataNamespaceForbiddenViolation`,
13
- # `failIfK8sPolicyNamespaceRulesViolated`).
14
- #
15
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
17
- package k8s.kustomize_managed
18
-
19
- import rego.v1
20
-
21
- namespace_forbidden_msg := concat(" ", [
22
- "metadata.namespace заборонено — namespace задає kustomization.yaml",
23
- "(поле namespace); файл підключено через resources / patches / …",
24
- "(k8s.mdc)",
25
- ])
26
-
27
- deny contains namespace_forbidden_msg if {
28
- meta := object.get(input, "metadata", null)
29
- is_object(meta)
30
- "namespace" in object.keys(meta)
31
- }
@@ -1,30 +0,0 @@
1
- # Тести для `k8s.kustomize_managed`. Запуск:
2
- # conftest verify -p npm/policy/k8s/kustomize_managed --namespace k8s.kustomize_managed
3
- package k8s.kustomize_managed_test
4
-
5
- import rego.v1
6
-
7
- import data.k8s.kustomize_managed
8
-
9
- test_deny_metadata_with_namespace if {
10
- count(kustomize_managed.deny) > 0 with input as {
11
- "apiVersion": "v1",
12
- "kind": "ConfigMap",
13
- "metadata": {"name": "cm", "namespace": "dev"},
14
- }
15
- }
16
-
17
- test_allow_metadata_without_namespace if {
18
- count(kustomize_managed.deny) == 0 with input as {
19
- "apiVersion": "v1",
20
- "kind": "ConfigMap",
21
- "metadata": {"name": "cm"},
22
- }
23
- }
24
-
25
- test_allow_no_metadata if {
26
- count(kustomize_managed.deny) == 0 with input as {
27
- "apiVersion": "v1",
28
- "kind": "ConfigMap",
29
- }
30
- }