@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.
- package/CHANGELOG.md +66 -0
- package/bin/n-cursor.js +3 -2
- package/mdc/abie.mdc +13 -0
- package/mdc/changelog.mdc +3 -2
- package/mdc/ci4.mdc +8 -0
- package/mdc/ga.mdc +3 -2
- package/mdc/graphql.mdc +3 -2
- package/mdc/hasura.mdc +3 -2
- package/mdc/image-avif.mdc +3 -2
- package/mdc/image-compress.mdc +3 -2
- package/mdc/k8s.mdc +1 -3
- package/mdc/nginx-default-tpl.mdc +3 -1
- package/mdc/php.mdc +3 -2
- package/mdc/style-lint.mdc +3 -2
- package/mdc/vue.mdc +3 -2
- package/package.json +1 -1
- package/policy/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
- package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
- package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
- package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
- package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
- package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
- package/policy/k8s/kustomization/kustomization.rego +2 -2
- package/policy/k8s/manifest/manifest.rego +4 -2
- package/scripts/check-abie.mjs +102 -369
- package/scripts/check-ga.mjs +89 -9
- package/scripts/check-k8s.mjs +129 -704
- package/scripts/lint-conftest.mjs +25 -2
- package/scripts/lint-ga.mjs +18 -132
- package/scripts/utils/run-conftest-batch.mjs +117 -0
- package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
- 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
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
package/mdc/image-avif.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
|
|
3
|
-
|
|
4
|
-
|
|
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`). Перевірка робить три кроки в порядку:
|
package/mdc/image-compress.mdc
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Оптимізація raster/SVG через @nitra/minify-image у локальному lint
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
-
|
|
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.
|
|
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
package/mdc/style-lint.mdc
CHANGED
package/mdc/vue.mdc
CHANGED
package/package.json
CHANGED
|
@@ -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
|
+
}
|