@nitra/cursor 1.8.222 → 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.
Files changed (33) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/n-cursor.js +3 -2
  3. package/mdc/abie.mdc +13 -0
  4. package/mdc/changelog.mdc +3 -2
  5. package/mdc/ci4.mdc +8 -0
  6. package/mdc/ga.mdc +3 -2
  7. package/mdc/graphql.mdc +3 -2
  8. package/mdc/hasura.mdc +3 -2
  9. package/mdc/image-avif.mdc +3 -2
  10. package/mdc/image-compress.mdc +3 -2
  11. package/mdc/k8s.mdc +1 -3
  12. package/mdc/nginx-default-tpl.mdc +3 -1
  13. package/mdc/php.mdc +3 -2
  14. package/mdc/style-lint.mdc +3 -2
  15. package/mdc/vue.mdc +3 -2
  16. package/package.json +1 -1
  17. package/policy/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
  18. package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
  19. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
  20. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
  21. package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
  22. package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
  23. package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
  24. package/policy/k8s/kustomization/kustomization.rego +2 -2
  25. package/policy/k8s/manifest/manifest.rego +4 -2
  26. package/scripts/check-abie.mjs +102 -369
  27. package/scripts/check-ga.mjs +89 -9
  28. package/scripts/check-k8s.mjs +129 -704
  29. package/scripts/lint-conftest.mjs +25 -2
  30. package/scripts/lint-ga.mjs +18 -132
  31. package/scripts/utils/run-conftest-batch.mjs +117 -0
  32. package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
  33. package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +0 -30
package/CHANGELOG.md CHANGED
@@ -4,6 +4,72 @@
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
+
19
+ ## [1.8.228] - 2026-05-10
20
+
21
+ ### Changed
22
+
23
+ - **k8s / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав на початок `check-k8s.mjs::check()` через новий helper `runAllK8sRego` — батч-виклик `runConftestBatch` для 9 пакетів (`k8s.manifest`, `k8s.gateway`, `k8s.hpa_pdb`, `k8s.kustomization`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`, `k8s.base_kustomization`, `k8s.base_manifest`, `k8s.kustomize_managed`). JS у `check-k8s.mjs` робить лише cross-file orchestration + autofix + modeline. Cross-file orchestrators `validateHasuraConfigMapRemoteSchemaPermissions` і `validateHasuraHttpRouteCanon` рефакторнуто: JS відбирає paired-with-Hasura-Deployment файли, далі батч-conftest на `k8s.hasura_configmap`/`k8s.hasura_httproute`. Видалено JS-orchestrator-функції-дублі (≈10 шт): `scanForbiddenManifestsInYamlDocuments`, `failIfIngressInDocument`, `failIfAutoscalingV1InDocument`, `validateK8sYamlPolicyDocuments`, `failIfK8sPolicyNamespaceRulesViolated`, `failIfK8sPolicyResourceRulesViolated`, `runK8sYamlPolicyAndGatewayScans`, `scanGatewayApiRouteBackendRefsInYamlBody`, `failIfGatewayRouteUsesNonHeadlessService`, `validateKustomizationResourcesSortedAlphabetically`, `validateKustomizationPatchesStructuralSort`, `validateInlinePatchesSorted`, `validateKustomizationJson6902NoRemoveAddSamePath`, `auditJson6902OneKustomizationYamlFile`, `auditJson6902ForKustomizationYamlDoc`, `auditKustomizationPatchesJson6902`, `auditOneKustomizationJson6902Patch`, `auditJson6902PatchExternalFile`, `failIfJson6902RemoveAddConflictOnSamePath`, `verifyBaseKustomizationNamespaceOnFile`, `ensureBaseKustomizationHasNamespace`, `readFirstConfigMapDoc`. Видалено публічний predicate `isForbiddenAutoscalingV1Manifest` + його тест (rego `k8s.manifest` авторитативно). Решта predicates лишилися як публічні exports для back-compat (`hpaManifestViolations`, `pdbManifestViolations`, `deploymentTopologySpreadConstraintsViolation` все ще активно використовуються JS cross-file для expected-name/dev-like; інші — тестові shim, можна прибрати окремо).
24
+ - **`checkK8sYamlFile`** залишає тільки modeline + `$schema`-URL перевірки; per-document валідація (Ingress/autoscaling/v1 заборонено, Service GCP-анотації, Deployment resources/Hasura image/topologySpread, Gateway API backendRef правила, HCP, svc/svc-hl, namespace правила) — у rego, виконано на початку `check()`.
25
+
26
+ ## [1.8.227] - 2026-05-10
27
+
28
+ ### Changed
29
+
30
+ - **conftest.mdc (alwaysApply):** канонізовано патерн «Rego-authoritative + JS-orchestrator» (Plan B) як основний для всіх перевірок у репо. Розділ «Гібрид» переписано: замість «JS authoritative + rego-копія» (Plan A) — тепер чітко: пер-документне правило існує **рівно в одному місці** (rego), а `check-<rule>.mjs` делегує його через `runConftestBatch` (`npm/scripts/utils/run-conftest-batch.mjs`), один спавн на namespace. Додано конкретний шаблон `check()` (rego-крок перший, JS cross-file — після) і опис інтеграції з `lint-<rule>.mjs` (external-tools wrapper викликає `await checkX()` як останній крок). Реальні приклади — abie (пілот) і ga (повна централізація). Новий «червоний прапор» забороняє лишати JS-копію rego-правила «про всяк випадок» — це плодить дрифт.
31
+
32
+ ## [1.8.226] - 2026-05-10
33
+
34
+ ### Changed
35
+
36
+ - **ga / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав із `lint-ga.mjs` у `check-ga.mjs::check()` як **перший крок**. Раніше `bun run lint-ga` сам викликав 4 per-workflow conftest + 1 batch для `ga.workflow_common`, а `npx @nitra/cursor check ga` цю частину не робив — тепер вся ga-логіка (rego + JS cross-file) в одному `check-ga.check()`. `lint-ga.mjs::runLintGaCli` спрощено: preflight (shellcheck/uv) → actionlint → zizmor → `await checkGa()`. Видалено: `CONFTEST_TARGETS`, `GA_POLICY_DIR`, `runConftestStep`, `runConftestWorkflowCommon` — і непотрібні імпорти `existsSync`/`readdirSync`/`dirname`/`join`/`fileURLToPath`. `runLintGaCli` тепер `async`; `bin/n-cursor.js` оновлено на `await runLintGaCli()`. Тест `lint-ga.test.mjs` оновлено: `await fn()` замість `fn()`. Тест `check-ga.test.mjs::"exit 1 коли shellcheck відсутній"` переведений на точковий виклик експортованої `checkShellcheckInstalled` (бо `withBinRemovedFromPath('shellcheck')` на macOS заодно видаляв `/opt/homebrew/bin` де conftest, ламаючи hard-fail у `runConftestBatch`).
37
+ - **`check-ga.mjs::checkShellcheckInstalled`:** додано `export` (потрібен для точкового тесту після рефактору).
38
+ - **тестова фікстура `setupCanonicalGaProject` у check-ga.test.mjs:** додано секцію `concurrency` (з канонічними `group` і `cancel-in-progress: true`) у workflow `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `git-ai.yml` — `ga.workflow_common` rego тепер запускається у `check()`, а ці workflow раніше не мали concurrency у фікстурі (правило `lint-ga.yml` уже мало). Це **правильна** реакція: rego-перевірка тепер ловить порушення на тих самих фікстурах, на яких раніше не запускалась.
39
+
40
+ ## [1.8.225] - 2026-05-10
41
+
42
+ ### Added
43
+
44
+ - **utility `runConftestBatch`:** новий `npm/scripts/utils/run-conftest-batch.mjs` — спавнить `conftest test` одним викликом для batched-списку файлів, парсить `--output json`, повертає структуровані `{filename, namespace, message}` порушення. Hard-fail зі install-hint якщо `conftest` не у PATH (узгоджено з рішенням Plan B). Використовується з `check-*.mjs` для делегування пер-документної валідації у Rego-полісі без помітного сповільнення (один спавн на namespace, не на файл).
45
+
46
+ ### Changed
47
+
48
+ - **abie / Plan B (rego-authoritative, pilot):** `npm/scripts/check-abie.mjs` рефакторнуто — пер-документна валідація 4 правил тепер делегується rego через `runConftestBatch`, JS залишає лише cross-file-оркестрацію (walking, path-фільтрацію, парність файлів). Видалені JS-функції-предикати (тепер єдине джерело істини — rego): `abieBaseHttpRouteHostnamesErrors`, `deploymentDocumentHasAbieBasePreemNodeSelector`, `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`, `validateAbieHcPolicy`, плюс хелпери `collectAbieHostnames`, `isAllowedAbieBaseDevHostname`, `isAbiePreemTruthy`, `processBaseHttpRouteDoc`, `httpRouteHasNonEmptyHostnames`, `findHealthCheckPolicyInDocs` і константа `ABIE_REQUIRED_IGNORE_BRANCHES`. Імпорти `flattenWorkflowSteps`, `getStepUses`, `parseWorkflowYaml` (`./utils/gha-workflow.mjs`) теж прибрано — orphan після видалення JS-парсера workflow.
49
+ - **abie.health_check_policy (rego):** виправлено divergence з JS — тепер targetRef.name перевіряється точним match-ем `<hcp.metadata.name>-hl` (з нормалізацією: якщо name вже закінчується на `-hl`, береться як є). До цього rego перевіряло лише суфікс `-hl`, що дозволяло `targetRef.name=bar-hl` для HCP з `name=foo` — це не дзеркалило JS.
50
+ - **`validateAbieHcYaml` → `validateAbieHcModeline`:** export перейменовано — JS-частина перевірки hc.yaml тепер обмежується modeline (`# yaml-language-server: $schema=…`); парсинг YAML і структурна валідація HCP делеговано rego.
51
+ - **`npm/tests/check-abie.test.mjs`:** прибрано тести видалених JS-предикатів (8 тестів) — їх покриття тепер забезпечують `_test.rego` фікстури через `conftest verify`.
52
+ - **`npm/tests/cross-check-rego-abie.test.mjs`:** видалено — після Plan B JS-сторони для крос-чеку немає; `_test.rego` фікстури в кожному abie-пакеті дають аналогічне покриття.
53
+
54
+ ## [1.8.224] - 2026-05-10
55
+
56
+ ### Added
57
+
58
+ - **golden cross-check тести JS↔rego (abie):** додано `npm/tests/cross-check-rego-abie.test.mjs` (25 тестів), який для кожної пари (JS-предикат у `check-abie.mjs` ↔ rego-пакет у `npm/policy/abie/`) подає однаковий вхід у обидва імплементації через `opa eval --format json` і перевіряє інваріант **«обидва бачать порушення або обидва ні»**. Покриває: `deploymentDocumentHasAbieBasePreemNodeSelector` ↔ `abie.base_deployment_preem`; `parseCleanMergedIgnoreBranches`+`ignoreBranchesIncludesRequired` ↔ `abie.clean_merged_ignore_branches`; `abieBaseHttpRouteHostnamesErrors` ↔ `abie.http_route_base`; rego-only golden-фікстури для `abie.health_check_policy` (бо JS-функція `validateAbieHcPolicy` приватна). Тест автоматично пропускається, якщо `opa` не у PATH. Sanity-check ламанням rego навмисно — drift детектується.
59
+
60
+ ## [1.8.223] - 2026-05-10
61
+
62
+ ### Added
63
+
64
+ - **abie / нові rego-пакети:** `npm/policy/abie/base_deployment_preem/` (Deployment у `…/k8s/.../base/...` має `spec.template.spec.nodeSelector.preem` зі значенням, що вважається істинним — boolean `true` або рядок `"true"`); `npm/policy/abie/clean_merged_ignore_branches/` (у workflow `.github/workflows/clean-merged-branch.yml` крок `phpdocker-io/github-actions-delete-abandoned-branches` має `with.ignore_branches` з токенами `dev,ua,ru`, case-insensitive). Реєстрація в `lint-conftest.mjs` TARGETS: walk-pattern для base-resource YAML і single-target для workflow.
65
+ - **abie / `_test.rego` фікстури:** додано юніт-тести для всіх 4 abie-пакетів — нових (`base_deployment_preem_test.rego`, `clean_merged_ignore_branches_test.rego`) і існуючих (`http_route_base_test.rego`, `health_check_policy_test.rego`). 35 тестів покривають happy paths і deny-кейси.
66
+
67
+ ### Changed
68
+
69
+ - **abie.health_check_policy (rego):** виправлено помилковий шлях `spec.config.httpHealthCheck` → правильний `spec.default.config.httpHealthCheck` (узгоджено з `validateAbieHcPolicy` у `check-abie.mjs`). Розширено перевірками: точна `apiVersion: networking.gke.io/v1`, `metadata.name` непорожній, `spec.default.config.type: HTTP`, `targetRef.kind: Service`. Cross-file звірка `<deployment.name>-hl` лишається у JS.
70
+ - **abie.mdc:** додано розділ «Швидкий gate через conftest (Rego)» зі списком rego-пакетів і опису того, що cross-file логіка (парність HCP↔Deployment, обчислений `<name>-hl`, валідація ru/ua-overlay JSON6902 patches, env→cluster DNS, cross-namespace backendRefs) лишається у `check-abie.mjs`.
71
+ - **lint-conftest.mjs TARGETS:** `abie.health_check_policy` і `abie.http_route_base` — `policyDir` уточнено до конкретного підкаталогу (`abie/health_check_policy`, `abie/http_route_base`) замість загального `abie`. Додано шляховий regex `K8S_BASE_RESOURCE_PATH_RE` для базових ресурсних YAML.
72
+
7
73
  ## [1.8.222] - 2026-05-10
8
74
 
9
75
  ### Added
package/bin/n-cursor.js CHANGED
@@ -1327,8 +1327,9 @@ try {
1327
1327
  break
1328
1328
  }
1329
1329
  case 'lint-ga': {
1330
- // Канонічний lint-ga з preflight на shellcheck → actionlint → zizmor (ga.mdc).
1331
- process.exitCode = runLintGaCli()
1330
+ // Канонічний lint-ga з preflight на shellcheck → actionlint → zizmor → check-ga (ga.mdc).
1331
+ // Останній крок (check-ga) async — тому await обов'язковий, інакше process.exitCode буде Promise.
1332
+ process.exitCode = await runLintGaCli()
1332
1333
 
1333
1334
  break
1334
1335
  }
package/mdc/abie.mdc CHANGED
@@ -388,3 +388,16 @@ with:
388
388
  ## Перевірка
389
389
 
390
390
  **`npx @nitra/cursor check abie`**
391
+
392
+ ### Швидкий gate через conftest (Rego)
393
+
394
+ Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/policy/abie/`** (запускається через **`bun run lint-rego`** для `*_test.rego` тестів і **`npx @nitra/cursor lint-conftest`** для прогону по реальних YAML — деталі в **conftest.mdc** / **n-rego.mdc**). JS у **`check-abie.mjs`** authoritative — rego тільки швидкий gate для одиничного маніфеста (зокрема через IDE-розширення `tsandall.opa`).
395
+
396
+ Пакети (директорія в **`npm/policy/abie/`** → namespace → що перевіряє):
397
+
398
+ - **`http_route_base/`** → `abie.http_route_base` — у HTTPRoute під `…/k8s/.../base/...` усі `spec.hostnames` мають бути в домені `aiml.live` (включно з `*.aiml.live` та піддоменами). **Цільові файли:** `…/k8s/.../base/.../hr.yaml`.
399
+ - **`health_check_policy/`** → `abie.health_check_policy` — структура HealthCheckPolicy: `apiVersion: networking.gke.io/v1`, `metadata.name`, `spec.default.config.type: HTTP`, `httpHealthCheck.requestPath` починається з `/`, `port: 8080`, `targetRef.kind: Service`, `targetRef.name` має суфікс `-hl`. **Цільові файли:** `…/k8s/.../hc.yaml`.
400
+ - **`base_deployment_preem/`** → `abie.base_deployment_preem` — Deployment у base/ має `spec.template.spec.nodeSelector.preem` зі значенням `true` (boolean або рядок). **Цільові файли:** ресурсні YAML під `…/k8s/.../base/...`.
401
+ - **`clean_merged_ignore_branches/`** → `abie.clean_merged_ignore_branches` — у workflow `.github/workflows/clean-merged-branch.yml` крок з `uses: phpdocker-io/github-actions-delete-abandoned-branches` має `with.ignore_branches`, що містить токени `dev,ua,ru` (case-insensitive). **Цільові файли:** `.github/workflows/clean-merged-branch.yml`.
402
+
403
+ Cross-file логіка (парність HCP↔Deployment у каталозі, обчислений `<deployment.name>-hl` для `targetRef.name`, валідація ru/ua-overlay JSON6902 patches на Service/HTTPRoute, env→cluster DNS, аналіз cross-namespace backendRefs у пакетах) лишається у **`check-abie.mjs`** — Rego не читає файлову систему й не робить cross-document резолюцію.
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/ci4.mdc CHANGED
@@ -15,6 +15,14 @@ C4-діаграми проєкту живуть у Markdown поряд із ко
15
15
  тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
16
16
  важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
17
17
 
18
+ ## Розташування
19
+
20
+ C4-діаграми проєкту живуть у теці `docs/ci4/` — це **канонічне місце** для всіх рівнів
21
+ моделі: `01-context.md`, `02-containers.md`, `03-components.md`, `04-code.md`, плюс
22
+ супутні `README.md` (вступ) і `decisions.md` (зведення ADR-впливів на C4). Інших місць
23
+ для C4-схем у репозиторії немає: розпорошення по підтеках сервісів ламає навігацію
24
+ «від системи вглиб» і робить агентський аналіз перед зміною дорожчим.
25
+
18
26
  ## Аналіз перед зміною
19
27
 
20
28
  Перш ніж вносити зміни, агент **читає відповідні C4-файли**: контекст, контейнери, компоненти
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.222",
3
+ "version": "1.9.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,56 @@
1
+ # Порт перевірки `deploymentDocumentHasAbieBasePreemNodeSelector` з
2
+ # `npm/scripts/check-abie.mjs` (abie.mdc): кожен `Deployment` у файлах під
3
+ # `…/k8s/.../base/…` має `spec.template.spec.nodeSelector.preem` зі
4
+ # значенням, що вважається істинним (boolean `true` або рядок `"true"`
5
+ # без урахування регістру). Overlays (ua/ru) далі підміняють селектор
6
+ # JSON6902-патчами на `preem: false` / `yandex.cloud/preemptible: false`.
7
+ #
8
+ # Запуск (локально, лише для одного base-YAML з Deployment):
9
+ # conftest test path/to/k8s/base/deployment.yaml \
10
+ # -p npm/policy/abie/base_deployment_preem \
11
+ # --namespace abie.base_deployment_preem
12
+ #
13
+ # JS відбирає файли під `…/k8s/.../base/…` (через `isAbieK8sBaseYamlPath`) і
14
+ # викликає conftest з цією намеспейс. JS authoritative (`check-abie.mjs`:
15
+ # `deploymentDocumentHasAbieBasePreemNodeSelector` + `ensureAbieBaseDeploymentPreemNodeSelector`).
16
+ # Cross-file gating (правило `abie` у `.n-cursor.json`, шлях файла) — у JS.
17
+ #
18
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
19
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
20
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
21
+ package abie.base_deployment_preem
22
+
23
+ import rego.v1
24
+
25
+ deny_msg := concat(" ", [
26
+ "Deployment у base: потрібен spec.template.spec.nodeSelector.preem:",
27
+ "true (або 'true') — abie.mdc",
28
+ ])
29
+
30
+ deny contains deny_msg if {
31
+ input.kind == "Deployment"
32
+ not has_truthy_preem
33
+ }
34
+
35
+ # preem truthy: boolean true або рядок "true" (case-insensitive, з обрізаними пробілами).
36
+ has_truthy_preem if {
37
+ preem := object.get(node_selector, "preem", null)
38
+ is_preem_truthy(preem)
39
+ }
40
+
41
+ is_preem_truthy(true)
42
+
43
+ is_preem_truthy(v) if {
44
+ is_string(v)
45
+ lower(trim_space(v)) == "true"
46
+ }
47
+
48
+ node_selector := object.get(
49
+ object.get(
50
+ object.get(object.get(input, "spec", {}), "template", {}),
51
+ "spec",
52
+ {},
53
+ ),
54
+ "nodeSelector",
55
+ {},
56
+ )
@@ -0,0 +1,60 @@
1
+ # Тести для `abie.base_deployment_preem`. Запуск:
2
+ # conftest verify -p npm/policy/abie/base_deployment_preem
3
+ package abie.base_deployment_preem_test
4
+
5
+ import rego.v1
6
+
7
+ import data.abie.base_deployment_preem
8
+
9
+ mk_deployment(node_selector) := {
10
+ "apiVersion": "apps/v1",
11
+ "kind": "Deployment",
12
+ "metadata": {"name": "api", "namespace": "dev"},
13
+ "spec": {"template": {"spec": object.union(
14
+ {"containers": [{"name": "main", "image": "x"}]},
15
+ {"nodeSelector": node_selector},
16
+ )}},
17
+ }
18
+
19
+ test_deny_no_node_selector if {
20
+ input_doc := {
21
+ "apiVersion": "apps/v1",
22
+ "kind": "Deployment",
23
+ "metadata": {"name": "api"},
24
+ "spec": {"template": {"spec": {"containers": [{"name": "main", "image": "x"}]}}},
25
+ }
26
+ count(base_deployment_preem.deny) > 0 with input as input_doc
27
+ }
28
+
29
+ test_deny_node_selector_without_preem if {
30
+ count(base_deployment_preem.deny) > 0 with input as mk_deployment({"role": "worker"})
31
+ }
32
+
33
+ test_deny_preem_false if {
34
+ count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": false})
35
+ }
36
+
37
+ test_deny_preem_string_false if {
38
+ count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": "false"})
39
+ }
40
+
41
+ test_allow_preem_boolean_true if {
42
+ count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": true})
43
+ }
44
+
45
+ test_allow_preem_string_true if {
46
+ count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "true"})
47
+ }
48
+
49
+ test_allow_preem_string_uppercase_true if {
50
+ count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "TRUE"})
51
+ }
52
+
53
+ # Не Deployment — пакет не діє (дзеркало JS-предиката).
54
+ test_allow_non_deployment if {
55
+ count(base_deployment_preem.deny) == 0 with input as {
56
+ "apiVersion": "v1",
57
+ "kind": "ConfigMap",
58
+ "metadata": {"name": "x"},
59
+ }
60
+ }
@@ -0,0 +1,100 @@
1
+ # Порт перевірки `parseCleanMergedIgnoreBranches` + `ignoreBranchesIncludesRequired`
2
+ # з `npm/scripts/check-abie.mjs` (abie.mdc): у workflow
3
+ # `.github/workflows/clean-merged-branch.yml` крок з
4
+ # `uses: phpdocker-io/github-actions-delete-abandoned-branches` має у
5
+ # `with.ignore_branches` містити усі обовʼязкові токени `dev,ua,ru`
6
+ # (case-insensitive, кома-розділені).
7
+ #
8
+ # Запуск (локально):
9
+ # conftest test .github/workflows/clean-merged-branch.yml \
10
+ # -p npm/policy/abie/clean_merged_ignore_branches \
11
+ # --namespace abie.clean_merged_ignore_branches
12
+ #
13
+ # JS authoritative (`check-abie.mjs`: `checkCleanMergedBranch`,
14
+ # `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`); ця Rego —
15
+ # швидкий gate для одиничного workflow YAML. Cross-file гейтинг (правило
16
+ # `abie` у `.n-cursor.json`) — у JS.
17
+ #
18
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
19
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
20
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
21
+ package abie.clean_merged_ignore_branches
22
+
23
+ import rego.v1
24
+
25
+ # Обовʼязкові гілки в `ignore_branches` (узгоджено з `ABIE_REQUIRED_IGNORE_BRANCHES`).
26
+ required_branches := {"dev", "ua", "ru"}
27
+
28
+ # Префікс `uses:` для GitHub Action, у якого читаємо `with.ignore_branches`.
29
+ target_action_marker := "phpdocker-io/github-actions-delete-abandoned-branches"
30
+
31
+ step_missing_msg := concat(" ", [
32
+ "clean-merged-branch.yml: не знайдено крок з uses: phpdocker-io/github-actions-delete-abandoned-branches",
33
+ "(abie.mdc)",
34
+ ])
35
+
36
+ ignore_branches_missing_msg := concat(" ", [
37
+ "clean-merged-branch.yml: не знайдено with.ignore_branches у кроці",
38
+ "phpdocker-io/github-actions-delete-abandoned-branches (abie.mdc)",
39
+ ])
40
+
41
+ # ── deny: крок не знайдено ────────────────────────────────────────────────
42
+
43
+ deny contains step_missing_msg if {
44
+ count(target_steps) == 0
45
+ }
46
+
47
+ # ── deny: з step нема with.ignore_branches ────────────────────────────────
48
+
49
+ deny contains ignore_branches_missing_msg if {
50
+ count(target_steps) > 0
51
+ not has_ignore_branches_value
52
+ }
53
+
54
+ # ── deny: ignore_branches не містить усіх обов'язкових токенів ────────────
55
+
56
+ deny contains msg if {
57
+ count(target_steps) > 0
58
+ ignore_branches_value != ""
59
+ missing := required_branches - parsed_ignore_tokens(ignore_branches_value)
60
+ count(missing) > 0
61
+ msg := sprintf(
62
+ "clean-merged-branch.yml: ignore_branches має містити %v (зараз: %q; не вистачає: %v) (abie.mdc)",
63
+ [concat(",", sort(required_branches)), ignore_branches_value, concat(",", sort(missing))],
64
+ )
65
+ }
66
+
67
+ # ── helpers ───────────────────────────────────────────────────────────────
68
+
69
+ # Усі steps з усіх jobs у workflow (підтримує jobs.<job>.steps[]).
70
+ target_steps contains step if {
71
+ some job in object.get(input, "jobs", {})
72
+ some step in object.get(job, "steps", [])
73
+ uses := object.get(step, "uses", "")
74
+ is_string(uses)
75
+ contains(uses, target_action_marker)
76
+ }
77
+
78
+ # Чи у знайдених steps хоча б у одного є with.ignore_branches непорожнім рядком.
79
+ has_ignore_branches_value if {
80
+ some step in target_steps
81
+ v := object.get(object.get(step, "with", {}), "ignore_branches", null)
82
+ is_string(v)
83
+ }
84
+
85
+ default ignore_branches_value := ""
86
+
87
+ ignore_branches_value := values[0] if {
88
+ values := [v |
89
+ some step in target_steps
90
+ v := object.get(object.get(step, "with", {}), "ignore_branches", null)
91
+ is_string(v)
92
+ ]
93
+ count(values) > 0
94
+ }
95
+
96
+ # Розбирає `ignore_branches` як `,`-розділений список, нормалізує через trim+lower.
97
+ parsed_ignore_tokens(value) := {lower(trim_space(part)) |
98
+ some part in split(value, ",")
99
+ trim_space(part) != ""
100
+ }
@@ -0,0 +1,48 @@
1
+ # Тести для `abie.clean_merged_ignore_branches`. Запуск:
2
+ # conftest verify -p npm/policy/abie/clean_merged_ignore_branches
3
+ package abie.clean_merged_ignore_branches_test
4
+
5
+ import rego.v1
6
+
7
+ import data.abie.clean_merged_ignore_branches
8
+
9
+ # Каркас workflow з одним job, що містить step із заданим with.
10
+ mk_workflow(step_with) := {"jobs": {"cleanup": {"steps": [{
11
+ "uses": "phpdocker-io/github-actions-delete-abandoned-branches@v2",
12
+ "with": step_with,
13
+ }]}}}
14
+
15
+ other_step_workflow := {"jobs": {"cleanup": {"steps": [{"uses": "actions/checkout@v6"}]}}}
16
+
17
+ # Workflow без потрібного кроку.
18
+ test_deny_step_missing if {
19
+ count(clean_merged_ignore_branches.deny) > 0 with input as other_step_workflow
20
+ }
21
+
22
+ test_deny_ignore_branches_missing if {
23
+ count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({})
24
+ }
25
+
26
+ test_deny_missing_required_token if {
27
+ count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "dev,ua"})
28
+ }
29
+
30
+ test_deny_completely_wrong_tokens if {
31
+ count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "main,develop"})
32
+ }
33
+
34
+ test_allow_all_three_tokens if {
35
+ count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": "dev,ua,ru"})
36
+ }
37
+
38
+ # Регістронезалежне порівняння і пропуск пробілів.
39
+ test_allow_uppercase_with_spaces if {
40
+ count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": " DEV , UA , RU "})
41
+ }
42
+
43
+ extra_branches_workflow := mk_workflow({"ignore_branches": "dev,ua,ru,main,release/*"})
44
+
45
+ # Додаткові гілки після обов'язкових — дозволено.
46
+ test_allow_extra_branches if {
47
+ count(clean_merged_ignore_branches.deny) == 0 with input as extra_branches_workflow
48
+ }