@nitra/cursor 1.8.212 → 1.8.216

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,55 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.216] - 2026-05-09
8
+
9
+ ### Changed
10
+
11
+ - **k8s / check-k8s:** орієнтир **`DEFAULT_CONTAINER_MEMORY_REQUEST`** поза base — **`512Mi`** (замість **`512`**).
12
+
13
+ ## [1.8.215] - 2026-05-09
14
+
15
+ ### Changed
16
+
17
+ - **k8s / check-k8s:** канон **`resources.requests.memory`** у шарі **`…/k8s/…/base/…`** — **`128Mi`** (замість **`128`**, щоб відповідати Quantity у Kubernetes); приймається **`Mi`** без урахування регістру.
18
+
19
+ ## [1.8.214] - 2026-05-09
20
+
21
+ ### Fixed
22
+
23
+ - **k8s / check-k8s:** конвертація image-replace patches → `images:` падала з `byPatch.keys(...).toSorted is not a function`, бо `Map.keys()` повертає ітератор без `toSorted`. Тепер ключі спершу матеріалізуються у масив (`[...byPatch.keys()].toSorted(...)`).
24
+
25
+ ### Changed
26
+
27
+ - **k8s / check-k8s:** у шарі **`…/k8s/…/base/…`** для **Deployment** жорстко **`resources.requests.cpu: '0.02'`** та **`memory: '128'`**; поза base обов’язкові **cpu** і **memory** (орієнтир **`0.5`** / **`512`** у підказках).
28
+ - **k8s / check-k8s:** заборона **`hpa.yaml`** у каталозі **`…/base/`**; якщо HPA є в дереві base — вимагається strategic-merge **`$patch: delete`** для **HorizontalPodAutoscaler** у **`base/kustomization.yaml`**.
29
+ - **k8s / check-k8s:** прод-оверлей вимагає patches на **HPA** (`minReplicas`/`maxReplicas`) лише якщо успадковане base **не** видаляє HPA через delete-patch; **PDB** **`minAvailable`** — якщо в base є PDB.
30
+ - **k8s.mdc:** оновлено правила та приклади під цю модель.
31
+
32
+ ## [1.8.213] - 2026-05-09
33
+
34
+ ### Added
35
+
36
+ - Нове правило `js-bun-redis` (`npm/mdc/js-bun-redis.mdc`): заміна `ioredis` /
37
+ `node-redis` (включно з кореневим `redis` v4 і підпакетами `@redis/*`) на
38
+ Bun native Redis (`import { redis } from 'bun'`,
39
+ <https://bun.com/docs/runtime/redis>).
40
+ - AST-сканер `npm/scripts/utils/redis-imports.mjs` (`oxc-parser`) ловить
41
+ `import` / `require` / динамічний `import()` пакетів `ioredis`, `node-redis`,
42
+ `redis`, підшляхів `ioredis/...` / `redis/...` і `@redis/*`. Не зачіпає
43
+ сторонні `redis-*` (наприклад, `redis-mock`).
44
+ - `npm/scripts/check-js-bun-redis.mjs` запускає AST-скан по JS/TS-джерелах і
45
+ доступний як `npx @nitra/cursor check js-bun-redis`.
46
+ - Rego-полісі `npm/policy/js_bun_redis/package_json/` — заборона
47
+ `ioredis` / `node-redis` / `redis` / `@redis/*` у `dependencies` будь-якого
48
+ `package.json` у дереві; зареєстрована таргетом у
49
+ `npm/scripts/lint-conftest.mjs` (`bun run lint-conftest`).
50
+ - Авто-увімкнення правила в `.n-cursor.json`: `npm/scripts/auto-rules.mjs`
51
+ додає `js-bun-redis`, якщо в `dependencies` хоч одного `package.json` є
52
+ `ioredis` або `node-redis` (умова — у `npm/bin/auto-rules.md`).
53
+ - Тести: `npm/tests/redis-imports.test.mjs` (AST-сканер) і нові кейси у
54
+ `npm/tests/auto-rules.test.mjs` (детект `ioredis` / `node-redis`).
55
+
7
56
  ## [1.8.212] - 2026-05-08
8
57
 
9
58
  ### Changed
@@ -54,7 +103,7 @@
54
103
  `sqlFormat` / `pgFmt` з `%L`/`%I`/`%s` у тілі, плюс `quoteLiteral` /
55
104
  `quoteIdent` / `escapeLiteral` / `escapeIdent` без додаткової перевірки)
56
105
  та `findPgFormatLikeQueryWrapperInText` (`{ query(text, params) { ...
57
- <obj>.unsafe(...) ... } }`). Скан запускається лише у файлах з
106
+ <obj>.unsafe(...) ... } }`). Скан запускається лише у файлах з
58
107
  `import { sql|SQL } from 'bun'`.
59
108
  - `npm/scripts/check-js-bun-db.mjs` рапортує `pgFormatShim` / `queryWrapper` —
60
109
  окремі лічильники й `pass`-рядки, без зміни існуючих перевірок.
@@ -105,10 +154,10 @@
105
154
  (prettier-залежність, `@nitra/eslint-config ≥ 3.9.2`),
106
155
  `checkPackageJsonTypeModule` для root, `checkEnginesNode/Bun` для root,
107
156
  канонічний `lint-js`-скрипт, валідація `lint-js.yml` (`verifyLintJsWorkflowStructure`
108
- + fallback). Лишилися — `.oxlintrc.json` canonical-snapshot, VSCode-розширення,
109
- workspace-ітерація для `type: "module"` і engines, дубль JS-кроків у `lint.yml`,
110
- `.jscpd.json`. Прибрано непотрібні імпорти `parseWorkflowYaml`,
111
- `verifyLintJsWorkflowStructure` і `OXLINT_FIX_RE`.
157
+ - fallback). Лишилися — `.oxlintrc.json` canonical-snapshot, VSCode-розширення,
158
+ workspace-ітерація для `type: "module"` і engines, дубль JS-кроків у `lint.yml`,
159
+ `.jscpd.json`. Прибрано непотрібні імпорти `parseWorkflowYaml`,
160
+ `verifyLintJsWorkflowStructure` і `OXLINT_FIX_RE`.
112
161
  - `npm/scripts/check-js-run.mjs` — без перевірок `bunyan` / `@nitra/bunyan` у
113
162
  залежностях, canonical `jsconfig.json` через `deepEqualJson`,
114
163
  `OTEL_RESOURCE_ATTRIBUTES` у `configmap.yaml`. Лишилися AST-скан коду
@@ -220,7 +269,7 @@
220
269
  - `check-k8s.mjs` (автоконверт `image-replace` patches → `images:`): тепер працює і для `patches[i].patch` із **кількома** ops, а не лише з одинокою image-replace op. Сканує всі ops у патчі, конвертує **кожну** `op: replace` на `/spec/template/spec/containers/<N>/image` (target `kind: Deployment`) у запис `images:`; якщо всі ops патча конвертовано — `patches[i]` видаляється повністю; інакше inline `patch:` переписується через `parseDocument` без конвертованих ops зі збереженням block-literal scalar (`|-`) і вихідного порядку решти ops. Реалізовано через нові функції `tryParseJson6902Array` (≥ 1 op, замість `tryParseSingleJson6902Array`) і `rewriteInlinePatchWithoutOps`; `imageReplaceDeploymentPatchInfo` повертає `{ deployName, totalOps, ops: [{ containerIndex, newImage, opIndex }] }` (раніше — одиничний `{ deployName, containerIndex, newImage }` лише за `length === 1`); `applyConversionsToDoc` групує конвертації по індексу патча й вирізає ops або сам патч за потреби. Сортування решти ops після видалення лишається поза цією зміною — за нього відповідає окрема перевірка `kustomizationInlinePatchOpsSortedViolation`.
221
270
  - `mdc/k8s.mdc` (v1.26 → v1.27): уточнено крок 1 авто-перевірки в розділі «Зміна image — через `images:`, не через `patches[]`» — тепер описує і випадок, коли в `patches[i].patch` лишаються не-image ops (їх зберігає, у вихідному порядку, без коментарів).
222
271
  - `check-js-lint.mjs` + `mdc/js-lint.mdc` (v1.16 → v1.17): мінімум `@nitra/eslint-config` піднято з `^3.8.0` до `^3.9.2`. Обґрунтування: з 3.9.2 у `getConfig` вбудовано ignore для `**/adr/**`, тож ADR-документи не валідуються ESLint, і консьюмерам не треба додавати цей glob у `eslint.config.js` локально. `nitraEslintConfigMeetsMinVersion` тепер повертає `false` для діапазонів `^3.8.x`–`^3.9.1`; `workspace:*` лишається ok без змін. Pass/fail-повідомлення `checkPackageJsonLintDeps` оновлено під новий мінімум; `for...in`-бан з 3.8.0 згадується як накопичена відмінність. Тести `nitraEslintConfigMeetsMinVersion` розширено: `^3.9.2`/`^3.9.10`/`^3.10.0`/`^4.0.0` — ok; `^3.9.1`/`^3.8.0`/`^3.6.12`/`^3.4.3` — ні.
223
- - `bin/n-cursor.js` (`reexecIfPackageVersionChanged` + `spawnSync`-виклик): `process.env.NITRA_CURSOR_REEXEC` і `...process.env` замінено на `env.NITRA_CURSOR_REEXEC` і `...env` з `node:process` (`import { cwd, env } from 'node:process'`). Підстава: правило `js-run.mdc` забороняє прямий `process.env.*` у Node-коді; `NITRA_CURSOR_REEXEC` — опційна змінна (виставляється лише при re-exec), тож імпорт `env` з `node:process` (а не з `@nitra/check-env`) — канонічна форма для опційних. Поведінка не змінена; раніше `npm/scripts/check-js-run.mjs` помилявся на `bin/n-cursor.js:1136` (правило `process-env`), тепер intergation-test `check-* на реальному репозиторії` проходить.
272
+ - `bin/n-cursor.js` (`reexecIfPackageVersionChanged` + `spawnSync`-виклик): `process.env.NITRA_CURSOR_REEXEC` і `...process.env` замінено на `env.NITRA_CURSOR_REEXEC` і `...env` з `node:process` (`import { cwd, env } from 'node:process'`). Підстава: правило `js-run.mdc` забороняє прямий `process.env.*` у Node-коді; `NITRA_CURSOR_REEXEC` — опційна змінна (виставляється лише при re-exec), тож імпорт `env` з `node:process` (а не з `@nitra/check-env`) — канонічна форма для опційних. Поведінка не змінена; раніше `npm/scripts/check-js-run.mjs` помилявся на `bin/n-cursor.js:1136` (правило `process-env`), тепер integration-test `check-* на реальному репозиторії` проходить.
224
273
 
225
274
  ### Added
226
275
 
package/bin/auto-rules.md CHANGED
@@ -34,6 +34,8 @@ js-mssql - якщо в хоч одному package.json в секції dependen
34
34
 
35
35
  js-bun-db - якщо в хоч одному package.json в секції dependencies присутній пакет pg, pg-format або mysql2 або є імпорт sql/SQL з Bun (приклад: import { sql } from "bun")
36
36
 
37
+ js-bun-redis - якщо в хоч одному package.json в секції dependencies присутній пакет ioredis або node-redis
38
+
37
39
  k8s - якщо присутня хоч одна директорія k8s
38
40
 
39
41
  nginx-default-tpl - якщо присутній хоч один файл з переліку - default.conf.template, default.conf, nginx.conf
package/mdc/js-bun-db.mdc CHANGED
@@ -26,7 +26,7 @@ PostgreSQL 18+, MariaDB 10.6+ (сумісний з MySQL-протоколом,
26
26
  - допоміжні `quoteLiteral`, `quoteIdent`, `escapeLiteral`, `escapeIdent` як публічні експорти модуля;
27
27
  - обгортки `pgRead.query(text, params)` / `pgWrite.query(text, params)` / `db.query(text, params)`, які складають SQL-рядок (з або без `format`) і викликають `sql.unsafe(text, params)` — це повертає injection-поверхню, від якої ми йдемо, тільки під «зручним» іменем.
28
28
 
29
- Замість цього всі точки використання потрібно перевести на tagged template `sql\`...\${value}...\``. Кожне `${value}` стає окремим параметром bind, без рядкового екранування.
29
+ Замість цього всі точки використання потрібно перевести на tagged template `sql\`...\${value}...\``. Кожне`${value}` стає окремим параметром bind, без рядкового екранування.
30
30
 
31
31
  ### Типові ідіоми `pg-format` → Bun SQL
32
32
 
@@ -0,0 +1,21 @@
1
+ ---
2
+ description: Використання Redis/Valkey з Bun
3
+ alwaysApply: true
4
+ version: '1.0'
5
+ ---
6
+
7
+ ## Підтримувані версії redis
8
+
9
+ Redis 7.2+
10
+
11
+ ## Заміна на Bun native Redis
12
+
13
+ Якщо в проєкті використовуються бібліотеки `ioredis`, `node-redis`, їх потрібно замінити на Bun native Redis: <https://bun.com/docs/runtime/redis>.
14
+
15
+ - Видалити з `dependencies`: `ioredis`, `node-redis`.
16
+ - Видалити з коду: усі `import` / `require` цих пакетів та власні обгортки над ними.
17
+ - Замінити на `import { redis } from 'bun'`
18
+
19
+ ## Перевірка
20
+
21
+ `npx @nitra/cursor check js-bun-redis`.
package/mdc/k8s.mdc CHANGED
@@ -96,19 +96,51 @@ jobs:
96
96
  run: bun run lint-k8s
97
97
  ```
98
98
 
99
- ## Deployment: `resources.requests.cpu`
99
+ ## Deployment: `resources.requests` (CPU і memory)
100
100
 
101
- У **`Deployment`** у кожному **`containers`** має бути **`resources.requests.cpu`** непорожнє значення (наприклад **`"500m"`**, **`"100m"`** або число на кшталт **`0.5`**). Це гарантує, що Kubernetes планує pod з мінімальним бюджетом CPU і коректно працює з HPA (CPU target %).
101
+ У кожному контейнері **`Deployment`** обов’язкові **`resources.requests.cpu`** і **`resources.requests.memory`** (непорожні скаляри Kubernetes Quantity).
102
102
 
103
- Якщо для сервісу ще не обрано конкретне значення — став **`"0.5"`** як безпечний мінімум:
103
+ ### Шар **`…/k8s/…/base/…`** (dev / щільний packing)
104
+
105
+ У **всіх** `Deployment` у файлах під **`…/k8s/…/base/…`** значення **жорстко фіксовані** (для **cpu** допускається число **`0.02`** у YAML):
106
+
107
+ ```yaml
108
+ resources:
109
+ requests:
110
+ cpu: '0.02'
111
+ memory: '128Mi'
112
+ ```
113
+
114
+ **HPA у base не тримаємо** у локальному **`hpa.yaml`** поруч із `Deployment`. Якщо **HorizontalPodAutoscaler** потрапляє в дерево Kustomize з **`resources`** / **`components`** / **`bases`**, у **`…/base/kustomization.yaml`** додай strategic-merge patch з **`$patch: delete`** і **`kind: HorizontalPodAutoscaler`** (і **`metadata.name`**, що збігається з ресурсом, який треба прибрати). **`check k8s`** перевіряє наявність такого patch, коли в дереві base одночасно є **Deployment** і **HPA**.
115
+
116
+ ### Поза base (оверлеї, окремі каталоги)
117
+
118
+ Якщо ще не підібрано власні ліміти під сервіс, орієнтир для **`requests`**:
104
119
 
105
120
  ```yaml
106
121
  resources:
107
122
  requests:
108
- cpu: '0.5' # довільне значення; 0.5 — дефолт, якщо нічого специфічного ще не обрано
123
+ cpu: '0.5'
124
+ memory: '512Mi'
125
+ ```
126
+
127
+ У прод-оверлеях підіймай **`cpu` / `memory`** до реального споживання через **`patches`** або окремі фрагменти маніфестів. **`check k8s`** не вимагає саме **`0.5` / `512Mi`** поза base — лише непорожні **`requests.cpu`** і **`requests.memory`**.
128
+
129
+ ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
130
+ patches:
131
+ - target:
132
+ kind: Deployment
133
+ name: backend-api
134
+ patch: |-
135
+ - op: replace
136
+ path: /spec/template/spec/containers/0/resources/requests/cpu
137
+ value: '500m'
138
+ - op: replace
139
+ path: /spec/template/spec/containers/0/resources/requests/memory
140
+ value: 1Gi
109
141
  ```
110
142
 
111
- **`check k8s`** перевіряє присутність і непорожність **`resources.requests.cpu`** у кожному документі **Deployment** під **`k8s`**. Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
143
+ Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
112
144
 
113
145
  Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег із константи **`HASURA_GRAPHQL_ENGINE_IMAGE`** у **`check-k8s.mjs`** (допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
114
146
 
@@ -341,15 +373,17 @@ images:
341
373
 
342
374
  **`check k8s`:** заборонено **`kind: Ingress`**.
343
375
 
344
- ## Deployment: обов'язкові `hpa.yaml`, `pdb.yaml`, `topologySpreadConstraints`
376
+ ## Deployment: `pdb.yaml`, `topologySpreadConstraints`, HPA поза dev-base
345
377
 
346
- Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) у **тому ж каталозі** мають бути **`hpa.yaml`** (HPA) і **`pdb.yaml`** (PDB), а сам Deployment — канонічні **`spec.template.spec.topologySpreadConstraints`**. Інші workload-и (**CronJob**, **Job** тощо) або каталоги без шару **`base`** цими вимогами не охоплюються **`check k8s`** їх не змушує додавати HPA/PDB поруч. Скрипт звіряє прив’язку за іменами:
378
+ Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) у **тому ж каталозі** має бути **`pdb.yaml`** (PDB), а сам Deployment — канонічні **`spec.template.spec.topologySpreadConstraints`**. Локальний **`hpa.yaml`** у каталозі **`…/base/`** **заборонено** для dev HPA з дерева Kustomize прибирається через **`$patch: delete`** у **`base/kustomization.yaml`** (див. розділ про **`resources.requests`** вище).
347
379
 
348
- - **`hpa.yaml`** `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
380
+ У **не-base** оверлеях поруч із `Deployment` лишається звична схема: окремий **`hpa.yaml`**, якщо потрібен HPA для цього середовища. **`check k8s`** звіряє прив’язку за іменами:
381
+
382
+ - **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
349
383
  - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment.
350
384
  - **`topologySpreadConstraints`** — запис з `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app` рівне тій самій мітці `app`.
351
385
 
352
- **Kustomize `base` і overlay:** у дереві, зібраному з `k8s/…/base/kustomization.yaml` (`resources` / `bases` / `components` / `crds`, рекурсивно), **HorizontalPodAutoscaler** і **PodDisruptionBudget** допустимі **лише якщо** в тому ж дереві kustomize є хоча б один **`Deployment`** у YAML під **`…/k8s/…/base/`**. У `kustomization.yaml` overlay, який підключає цей `base` (наприклад, `../base` у `resources`), не додавай окремі YAML-файли з HPA / PDB, доки в наслідуваному `base` у дереві не з’явиться такий Deployment. Перевіряє **`check-k8s.mjs`**.
386
+ **Kustomize `base` і overlay:** у дереві, зібраному з `k8s/…/base/kustomization.yaml` (`resources` / `bases` / `components` / `crds`, рекурсивно), **HorizontalPodAutoscaler** і **PodDisruptionBudget** допустимі **лише якщо** в тому ж дереві kustomize є хоча б один **`Deployment`** у YAML під **`…/k8s/…/base/`**. Якщо після збору в дереві є і **Deployment**, і **HPA**, **base** `kustomization.yaml` має містити strategic-merge видалення **HorizontalPodAutoscaler** (`$patch: delete`). У `kustomization.yaml` overlay, який підключає цей `base`, не додавай окремі YAML-файли з HPA / PDB, доки в наслідуваному `base` у дереві не з’явиться такий Deployment. Перевіряє **`check-k8s.mjs`**.
353
387
 
354
388
  **Локальні шляхи в `kustomization.yaml`:** кожен запис без `://` (remote) з `resources` / `bases` / `components` / `crds`, `patchesStrategicMerge`, `patches[].path`, `patchesJson6902[].path`, `configurations[]`, `replacements[].path` має вказувати на **існуючий** у репозиторії файл (`.yaml` / `.yml`) або **каталог**; биті посилання — помилка **`check k8s`**.
355
389
 
@@ -359,7 +393,7 @@ images:
359
393
 
360
394
  **Dev-like середовища** — сегмент `base`, `dev`, або з суфіксом `-qa` (напр. `tr-qa`):
361
395
 
362
- - HPA: `minReplicas` — рівно **1**, `maxReplicas` — рівно **1** (у деві не масштабуємо).
396
+ - У **зібраному** маніфесті (після Kustomize), якщо лишився **HPA**: `minReplicas` — рівно **1**, `maxReplicas` — рівно **1**.
363
397
  - PDB: `minAvailable` — рівно **0**.
364
398
 
365
399
  **Прод-середовища** — усе інше (будь-який overlay без суфікса `-qa`):
@@ -369,12 +403,12 @@ images:
369
403
 
370
404
  ### Прод-оверрайди у `kustomization.yaml`
371
405
 
372
- Оскільки `base/` (і dev-like середовища) тримають dev-значення (`1`/`1`/`0`), у **кожному** прод-накладенні `kustomization.yaml` у `patches[]` **обов'язково** має бути перевизначення **лише якщо** цей оверлей наслідує base-дерево, де є **Deployment** і **HPA/PDB** (тобто base реально дає dev-like HPA/PDB, які треба підняти в проді):
406
+ У прод-накладенні `kustomization.yaml` у `patches[]` **обов’язкові** перевизначення залежно від того, що дає успадковане **base**-дерево:
373
407
 
374
- - для `HorizontalPodAutoscaler`: `spec.minReplicas` **і** `spec.maxReplicas` (щоб у проді вийшло2).
375
- - для `PodDisruptionBudget`: `spec.minAvailable` (щоб у проді вийшло ≥1).
408
+ - **`PodDisruptionBudget`**: `spec.minAvailable` якщо в base-дереві є **Deployment** і **PDB** (типово dev-like `0` прод 1).
409
+ - **`HorizontalPodAutoscaler`**: `spec.minReplicas` **і** `spec.maxReplicas` **лише якщо** в base-дереві є **HPA**, який **не** прибирається в **base** через **`$patch: delete`**. Якщо base видаляє HPA для dev, у прод-оверлеї додай **HPA** окремим YAML у **`resources`** (або patches на ресурс, що з’являється з іншого шару) з прод-мінімумами.
376
410
 
377
- Формат patch — JSON6902 або Strategic Merge; `check k8s` перевіряє **наявність** перевизначення відповідного поля. Конкретне значення має задовольняти прод-мінімуми — це видно у вмісті patch і остаточно матеріалізується під час збірки Kustomize.
411
+ Формат patch — JSON6902 або Strategic Merge; `check k8s` перевіряє **наявність** відповідних JSON Pointer-ів у **`patches[]`**.
378
412
 
379
413
  ```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
380
414
  patches:
@@ -397,9 +431,30 @@ patches:
397
431
  value: 1
398
432
  ```
399
433
 
434
+ ### Приклад: прибрати HPA у `base/kustomization.yaml`
435
+
436
+ Якщо **HPA** підключений з **`../component`** (або іншого шляху в **`resources`**), а у dev він не потрібен:
437
+
438
+ ```yaml title="k8s/base/kustomization.yaml (фрагмент)"
439
+ resources:
440
+ - ../component
441
+ - deploy.yaml
442
+ - pdb.yaml
443
+ patches:
444
+ - target:
445
+ kind: HorizontalPodAutoscaler
446
+ name: backend-api
447
+ patch: |-
448
+ $patch: delete
449
+ apiVersion: autoscaling/v2
450
+ kind: HorizontalPodAutoscaler
451
+ metadata:
452
+ name: backend-api
453
+ ```
454
+
400
455
  ### Приклади
401
456
 
402
- ```yaml title="k8s/base/hpa.yaml"
457
+ ```yaml title="k8s/prod/hpa.yaml (або компонент з HPA — не в …/base/ локальному hpa.yaml)"
403
458
  # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/horizontalpodautoscaler-autoscaling-v2.json
404
459
  apiVersion: autoscaling/v2
405
460
  kind: HorizontalPodAutoscaler
@@ -410,8 +465,8 @@ spec:
410
465
  apiVersion: apps/v1
411
466
  kind: Deployment
412
467
  name: backend-api
413
- minReplicas: 1 # dev-like; прод-накладення перевизначають на ≥ 2
414
- maxReplicas: 1 # dev-like; прод-накладення перевизначають на ≥ 2
468
+ minReplicas: 2
469
+ maxReplicas: 10
415
470
  metrics:
416
471
  - type: Resource
417
472
  resource:
@@ -456,6 +511,13 @@ spec:
456
511
  spec:
457
512
  template:
458
513
  spec:
514
+ containers:
515
+ - name: backend-api
516
+ image: example.registry/backend-api:tag
517
+ resources:
518
+ requests:
519
+ cpu: '0.02'
520
+ memory: '128Mi'
459
521
  topologySpreadConstraints:
460
522
  - maxSkew: 1
461
523
  topologyKey: kubernetes.io/hostname
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.212",
3
+ "version": "1.8.216",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,37 @@
1
+ # Перевірка `dependencies` для правила `js-bun-redis.mdc` — паралель до
2
+ # `npm/policy/js_bun_db/package_json/package_json.rego`.
3
+ #
4
+ # Запуск (локально, для будь-якого `package.json` у дереві):
5
+ # conftest test path/to/package.json -p npm/policy/js_bun_redis \
6
+ # --namespace js_bun_redis.package_json
7
+ #
8
+ # Перевіряє: у `dependencies` не повинно бути `ioredis`, `node-redis`,
9
+ # `redis` або жодного з підпакетів `@redis/*` — заміна на Bun native Redis
10
+ # (https://bun.com/docs/runtime/redis).
11
+ #
12
+ # AST-скан коду (`import` / `require` / dynamic `import()` тих самих пакетів)
13
+ # лишається у `npm/scripts/check-js-bun-redis.mjs` (потребує `oxc-parser`).
14
+ #
15
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
18
+ package js_bun_redis.package_json
19
+
20
+ import rego.v1
21
+
22
+ forbidden_dependencies := {
23
+ "ioredis",
24
+ "node-redis",
25
+ "redis",
26
+ "@redis/client",
27
+ "@redis/json",
28
+ "@redis/search",
29
+ "@redis/time-series",
30
+ "@redis/bloom",
31
+ }
32
+
33
+ deny contains msg if {
34
+ some pkg_name in forbidden_dependencies
35
+ pkg_name in object.keys(object.get(input, "dependencies", {}))
36
+ msg := sprintf("dependencies містить заборонений %q — заміни на Bun native Redis (js-bun-redis.mdc)", [pkg_name])
37
+ }
@@ -2,9 +2,9 @@
2
2
  * Автовизначення правил для `.n-cursor.json` за умовами з `npm/bin/auto-rules.md`.
3
3
  *
4
4
  * Модуль аналізує дерево проєкту (наявність файлів/директорій, `gql\`...\`` у source,
5
- * залежності `mssql` / `pg` / `pg-format` / `mysql2` у `package.json`, імпорт `sql`/`SQL` з `bun`, кореневий
6
- * `package.json`, `config.yaml` з рядком `metadata_directory: metadata` для hasura)
7
- * та повертає ідентифікатори правил, які потрібно автододати.
5
+ * залежності `mssql` / `pg` / `pg-format` / `mysql2` / `ioredis` / `node-redis` у `package.json`,
6
+ * імпорт `sql`/`SQL` з `bun`, кореневий `package.json`, `config.yaml` з рядком
7
+ * `metadata_directory: metadata` для hasura) та повертає ідентифікатори правил, які потрібно автододати.
8
8
  *
9
9
  * Враховує винятки `disable-rules`: елементи зі списку не додаються автоматично.
10
10
  *
@@ -39,6 +39,7 @@ export const AUTO_RULE_ORDER = Object.freeze([
39
39
  'js-lint',
40
40
  'js-mssql',
41
41
  'js-bun-db',
42
+ 'js-bun-redis',
42
43
  'js-run',
43
44
  'k8s',
44
45
  'nginx-default-tpl',
@@ -602,10 +603,18 @@ export async function detectAutoRules({
602
603
  : null
603
604
  )
604
605
  const isAbie = typeof repositoryUrl === 'string' && repositoryUrl.toLowerCase().includes(ABIE_REPOSITORY_URL_MARKER)
605
- const depHits = await collectDependencyKeysPresentInPackageJsonTree(root, ['mssql', 'pg', 'pg-format', 'mysql2'])
606
+ const depHits = await collectDependencyKeysPresentInPackageJsonTree(root, [
607
+ 'mssql',
608
+ 'pg',
609
+ 'pg-format',
610
+ 'mysql2',
611
+ 'ioredis',
612
+ 'node-redis'
613
+ ])
606
614
  const hasMssqlDependency = depHits.has('mssql')
607
615
  const hasJsBunDbSignal =
608
616
  depHits.has('pg') || depHits.has('pg-format') || depHits.has('mysql2') || facts.hasBunSqlImport
617
+ const hasJsBunRedisSignal = depHits.has('ioredis') || depHits.has('node-redis')
609
618
  const hasNestedNodePackage = await hasNestedPackageJsonWithoutViteDevDependency(root)
610
619
 
611
620
  /** @type {string[]} */
@@ -634,6 +643,7 @@ export async function detectAutoRules({
634
643
  { enabled: facts.hasJsLikeSource, id: 'js-lint' },
635
644
  { enabled: hasMssqlDependency, id: 'js-mssql' },
636
645
  { enabled: hasJsBunDbSignal, id: 'js-bun-db' },
646
+ { enabled: hasJsBunRedisSignal, id: 'js-bun-redis' },
637
647
  { enabled: hasNestedNodePackage, id: 'js-run' },
638
648
  { enabled: facts.hasK8sDir, id: 'k8s' },
639
649
  { enabled: facts.hasNginxDefaultTplFile, id: 'nginx-default-tpl' },
@@ -12,13 +12,7 @@
12
12
  */
13
13
 
14
14
  /** Порядок автододавання skills відповідно до `auto-skills.md`. */
15
- export const AUTO_SKILL_ORDER = Object.freeze([
16
- 'abie-kustomize',
17
- 'fix',
18
- 'lint',
19
- 'publish-telegram',
20
- 'taze'
21
- ])
15
+ export const AUTO_SKILL_ORDER = Object.freeze(['abie-kustomize', 'fix', 'lint', 'publish-telegram', 'taze'])
22
16
 
23
17
  /**
24
18
  * Залежність скілів від правил (`auto-skills.md` синтаксис `skill - [rules]`).
@@ -148,9 +148,8 @@ async function checkEnvFile(relPath, expected, reporter) {
148
148
  const value = m[1].trim()
149
149
  const parsed = parseInternalHasuraEndpoint(value)
150
150
  if (!parsed.ok) {
151
-
152
151
  const example =
153
- "https://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>"
152
+ 'https://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>'
154
153
  fail(
155
154
  `${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
156
155
  )
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Перевіряє правило `js-bun-redis.mdc`.
3
+ *
4
+ * Заборонено в JS/TS-джерелах будь-який `import` / `require` / динамічний `import()` пакетів
5
+ * `ioredis`, `node-redis`, `redis` (та підпакетів `@redis/*`, підшляхів `ioredis/...` /
6
+ * `redis/...`). Замість них треба використовувати Bun native Redis:
7
+ * `import { redis } from 'bun'` (<https://bun.com/docs/runtime/redis>).
8
+ *
9
+ * Перевірку `dependencies` (заборона `ioredis` / `node-redis` / `redis` / `@redis/*` у будь-якому
10
+ * `package.json`) винесено в Rego-полісі `npm/policy/js_bun_redis/package_json/`; її запускає
11
+ * `bun run lint-conftest`. Тут лишився AST-скан коду через `oxc-parser`.
12
+ */
13
+ import { existsSync } from 'node:fs'
14
+ import { readFile } from 'node:fs/promises'
15
+ import { join, relative } from 'node:path'
16
+
17
+ import { createCheckReporter } from './utils/check-reporter.mjs'
18
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
19
+ import { findRedisImportsInText, isRedisScanSourceFile, shouldSkipFileForRedisScan } from './utils/redis-imports.mjs'
20
+ import { walkDir } from './utils/walkDir.mjs'
21
+
22
+ /**
23
+ * Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану заборонених redis-імпортів.
24
+ * @param {string} repoRoot абсолютний шлях до кореня репозиторію
25
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
26
+ * @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
27
+ */
28
+ async function findAllSourcePathsForRedisScan(repoRoot, ignorePaths) {
29
+ /** @type {string[]} */
30
+ const paths = []
31
+ await walkDir(
32
+ repoRoot,
33
+ absPath => {
34
+ const rel = relative(repoRoot, absPath).split('\\').join('/')
35
+ if (isRedisScanSourceFile(rel) && !shouldSkipFileForRedisScan(rel)) {
36
+ paths.push(absPath)
37
+ }
38
+ },
39
+ ignorePaths
40
+ )
41
+ paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
42
+ return paths
43
+ }
44
+
45
+ /**
46
+ * Сканує JS/TS-джерела на заборонені імпорти/require пакетів `ioredis` / `node-redis` / `redis`.
47
+ * @param {string[]} sourcePaths абсолютні шляхи джерел
48
+ * @param {string} repoRoot абсолютний шлях до кореня
49
+ * @param {(msg: string) => void} fail callback при помилці
50
+ * @returns {Promise<number>} кількість знайдених порушень
51
+ */
52
+ async function scanSourcesForRedisImports(sourcePaths, repoRoot, fail) {
53
+ let violations = 0
54
+ for (const absPath of sourcePaths) {
55
+ const rel = relative(repoRoot, absPath).split('\\').join('/')
56
+ const content = await readFile(absPath, 'utf8')
57
+ for (const v of findRedisImportsInText(content, rel)) {
58
+ violations++
59
+ fail(
60
+ `js-bun-redis: ${rel}:${v.line} — заміни '${v.module}' на Bun native Redis ` +
61
+ `(import { redis } from 'bun', https://bun.com/docs/runtime/redis): ${v.snippet}`
62
+ )
63
+ }
64
+ }
65
+ return violations
66
+ }
67
+
68
+ /**
69
+ * Перевіряє відповідність проєкту правилу `js-bun-redis.mdc`.
70
+ * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
71
+ */
72
+ export async function check() {
73
+ const reporter = createCheckReporter()
74
+ const { pass, fail } = reporter
75
+
76
+ const repoRoot = process.cwd()
77
+ if (!existsSync(join(repoRoot, 'package.json'))) {
78
+ pass('js-bun-redis: package.json у корені відсутній — перевірку пропущено')
79
+ return reporter.getExitCode()
80
+ }
81
+
82
+ const ignorePaths = await loadCursorIgnorePaths(repoRoot)
83
+ const sourcePaths = await findAllSourcePathsForRedisScan(repoRoot, ignorePaths)
84
+ if (sourcePaths.length === 0) {
85
+ pass('js-bun-redis: немає JS/TS файлів для скану імпортів ioredis / node-redis / redis')
86
+ return reporter.getExitCode()
87
+ }
88
+
89
+ const violations = await scanSourcesForRedisImports(sourcePaths, repoRoot, fail)
90
+ if (violations === 0) {
91
+ pass(
92
+ "js-bun-redis: немає імпортів 'ioredis' / 'node-redis' / 'redis' / '@redis/*' у джерелах " +
93
+ '(використовується Bun native Redis або redis взагалі не задіяно)'
94
+ )
95
+ }
96
+
97
+ return reporter.getExitCode()
98
+ }