@nitra/cursor 1.13.45 → 1.13.48

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,24 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.48] - 2026-05-19
8
+
9
+ ### Changed
10
+
11
+ - `k8s.network_policy`: канонічний egress NetworkPolicy більше **не дозволяє** `to.namespaceSelector: {}` без `ports:` (catch-all). У шаблоні `networkpolicy.snippet.yaml`, генераторі `buildNetworkPolicyYaml` і rego-policy `network_policy.rego` тепер in-cluster rule має явний список TCP-портів: `80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318`. Додатково: `fix k8s` під час прогону знаходить існуючі `networkpolicy.yaml` з legacy catch-all egress і **перезаписує** їх через `buildNetworkPolicyYaml` (повний rebuild за `metadata.name` + `app`-міткою). JS-валідатор `networkPolicyManifestViolations` не змінюється (порти enforce-ить rego). Bump `k8s.mdc` `1.35` → `1.36`. Спец: [docs/superpowers/specs/2026-05-19-networkpolicy-egress-explicit-ports-design.md](../../docs/superpowers/specs/2026-05-19-networkpolicy-egress-explicit-ports-design.md).
12
+
13
+ ## [1.13.47] - 2026-05-19
14
+
15
+ ### Fixed
16
+
17
+ - `js-bun-db`, `js-bun-redis` rules: додано markdown-посилання на `policy/package_json/template/package.json.deny.json` у канонічних `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) падав, бо канонічні `.mdc` не містили `[package.json.deny.json](./policy/package_json/template/package.json.deny.json)`. Bump rule versions: `js-bun-db.mdc` `1.7` → `1.8`, `js-bun-redis.mdc` `1.1` → `1.2`.
18
+
19
+ ## [1.13.46] - 2026-05-19
20
+
21
+ ### Changed
22
+
23
+ - `image-avif` rule: двопрохідний rewrite у `check image-avif` — спочатку pre-scan `.vue`/`.html` на raster-посилання (`VUE_RASTER_IMPORT_RE` + `VUE_RASTER_STATIC_SRC_RE`), і лише якщо є хоча б одне — запускається `npx @nitra/minify-image --avif`, rewrite та cleanup AVIF-сиріт. Якщо raster-посилань нема — вихід `0` без жодного side-effect. Bump `image-avif.mdc` `1.3` → `1.4`.
24
+
7
25
  ## [1.13.45] - 2026-05-19
8
26
 
9
27
  ### Fixed
@@ -15,7 +33,7 @@
15
33
 
16
34
  ### Added
17
35
 
18
- - `abie` rule: новий policy-концерн `abie.package_json_docs` — у кореневому `package.json` `devDependencies` має містити `@nitra/abie-docs` (presence-only, версію не фіксуємо). Реалізація: `npm/rules/abie/policy/package_json_docs/` (target.json + .rego + _test.rego). Bump `abie.mdc` `1.20` → `1.21`.
36
+ - `abie` rule: новий policy-концерн `abie.package_json_docs` — у кореневому `package.json` `devDependencies` має містити `@nitra/abie-docs` (presence-only, версію не фіксуємо). Реалізація: `npm/rules/abie/policy/package_json_docs/` (target.json + .rego + \_test.rego). Bump `abie.mdc` `1.20` → `1.21`.
19
37
  - `efes` rule: перший policy-концерн `efes.package_json_docs` — у кореневому `package.json` `devDependencies` має містити `@nitra/efes-docs` (узгоджено з `graphql.mdc`, де схема береться з `node_modules/@nitra/efes-docs/schema/maya.graphql`). Реалізація: `npm/rules/efes/policy/package_json_docs/`. Bump `efes.mdc` `1.0` → `1.1`.
20
38
 
21
39
  ## [1.13.43] - 2026-05-18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.45",
3
+ "version": "1.13.48",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -2,7 +2,7 @@
2
2
  description: Використання pg / mysql2 / Bun SQL у Node.js та Bun
3
3
  globs: "**/package.json,**/src/conn/**"
4
4
  alwaysApply: false
5
- version: '1.7'
5
+ version: '1.8'
6
6
  ---
7
7
 
8
8
  ## Підтримувані версії баз даних
@@ -17,6 +17,8 @@ PostgreSQL 18+, MariaDB 10.6+ (сумісний з MySQL-протоколом,
17
17
  - Видалити з коду: усі `import` / `require` цих пакетів та власні обгортки над ними.
18
18
  - Замінити на `import { sql, SQL } from 'bun'` — Bun має вбудований клієнт із пулом, prepared statements та tagged templates.
19
19
 
20
+ Канон заборонених `dependencies` (`pg`, `pg-format`, `mysql2`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json).
21
+
20
22
  `pg-format` — це ручне форматування SQL через escape (`format('... %L ...', value)`); такі рядки легко поламати неправильним типом, locale-залежним escape або забутим `%L`. Tagged template Bun SQL параметризує значення нативно (`sql\`... ${value} ...\``) і не лишає простору для injection — окремий «форматер» не потрібен.
21
23
 
22
24
  ## `pg-format`: повне видалення, без шимів
@@ -2,7 +2,7 @@
2
2
  description: Використання Redis/Valkey з Bun
3
3
  globs: "**/package.json,**/src/conn/**"
4
4
  alwaysApply: false
5
- version: '1.1'
5
+ version: '1.2'
6
6
  ---
7
7
 
8
8
  ## Підтримувані версії redis
@@ -16,3 +16,5 @@ Redis 7.2+
16
16
  - Видалити з `dependencies`: `ioredis`, `node-redis`.
17
17
  - Видалити з коду: усі `import` / `require` цих пакетів та власні обгортки над ними.
18
18
  - Замінити на `import { redis } from 'bun'`
19
+
20
+ Канон заборонених `dependencies` (`ioredis`, `node-redis`, `redis`, `@redis/*`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json).
@@ -4252,9 +4252,16 @@ export function pdbManifestViolations(manifest, expectedAppLabel, isDevLike) {
4252
4252
  return errs
4253
4253
  }
4254
4254
 
4255
+ /**
4256
+ * Канонічний список in-cluster TCP-портів у `to: [{namespaceSelector: {}}]` rule (k8s.mdc).
4257
+ * Зовнішній доступ (80/443 → 0.0.0.0/0) і kube-dns (53 UDP/TCP) — окремі rule вище.
4258
+ * Catch-all (`namespaceSelector: {}` без `ports:`) — заборонено.
4259
+ */
4260
+ const NETWORK_POLICY_IN_CLUSTER_DEFAULT_PORTS = [80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318]
4261
+
4255
4262
  /**
4256
4263
  * Канонічний блок `spec.egress` NetworkPolicy (k8s.mdc): kube-dns; TCP 80/443 на 0.0.0.0/0;
4257
- * інші порти — `namespaceSelector: {}` (in-cluster, зокрема `*.svc`).
4264
+ * in-cluster `namespaceSelector: {}` зі списком `NETWORK_POLICY_IN_CLUSTER_DEFAULT_PORTS`.
4258
4265
  */
4259
4266
  const NETWORK_POLICY_EGRESS_YAML = ` egress:
4260
4267
  - to:
@@ -4279,6 +4286,8 @@ const NETWORK_POLICY_EGRESS_YAML = ` egress:
4279
4286
  port: 443
4280
4287
  - to:
4281
4288
  - namespaceSelector: {}
4289
+ ports:
4290
+ ${NETWORK_POLICY_IN_CLUSTER_DEFAULT_PORTS.map(p => ` - protocol: TCP\n port: ${p}`).join('\n')}
4282
4291
  `
4283
4292
 
4284
4293
  /**
@@ -6406,6 +6415,76 @@ async function appendNetworkPolicyDocuments(npAbs, toAdd, npRel, passFn) {
6406
6415
  }
6407
6416
  }
6408
6417
 
6418
+ /**
6419
+ * Перевіряє, чи `spec.egress` містить in-cluster rule з порожнім namespaceSelector БЕЗ ports
6420
+ * (legacy catch-all — заборонено новим каноном k8s.mdc).
6421
+ * @param {unknown} doc розпарсений NetworkPolicy-документ
6422
+ * @returns {boolean} true якщо doc має legacy catch-all rule
6423
+ */
6424
+ function networkPolicyHasLegacyCatchAllEgress(doc) {
6425
+ if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) return false
6426
+ const spec = /** @type {Record<string, unknown>} */ (doc).spec
6427
+ if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) return false
6428
+ const egress = /** @type {Record<string, unknown>} */ (spec).egress
6429
+ if (!Array.isArray(egress)) return false
6430
+ for (const rule of egress) {
6431
+ if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) continue
6432
+ const ruleRec = /** @type {Record<string, unknown>} */ (rule)
6433
+ const to = ruleRec.to
6434
+ if (!Array.isArray(to)) continue
6435
+ const hasEmptyNsPeer = to.some(peer => {
6436
+ if (peer === null || typeof peer !== 'object' || Array.isArray(peer)) return false
6437
+ const ns = /** @type {Record<string, unknown>} */ (peer).namespaceSelector
6438
+ return ns !== null && typeof ns === 'object' && !Array.isArray(ns) && Object.keys(ns).length === 0
6439
+ })
6440
+ if (!hasEmptyNsPeer) continue
6441
+ const ports = ruleRec.ports
6442
+ if (!Array.isArray(ports) || ports.length === 0) return true
6443
+ }
6444
+ return false
6445
+ }
6446
+
6447
+ /**
6448
+ * Migrate legacy `networkpolicy.yaml`: якщо хоч один документ має catch-all in-cluster egress —
6449
+ * перезаписати **всі** документи у файлі через `buildNetworkPolicyYaml(name, appLabel)`. Деталі — k8s.mdc.
6450
+ * @param {string} npAbs абсолютний шлях до networkpolicy.yaml
6451
+ * @returns {Promise<boolean>} true якщо файл переписаний
6452
+ */
6453
+ export async function regenerateLegacyNetworkPolicyDocsInFile(npAbs) {
6454
+ if (!existsSync(npAbs)) return false
6455
+ const docs = await readAllDocsByKindFromFile(npAbs, 'NetworkPolicy')
6456
+ if (docs.length === 0) return false
6457
+ const needsMigration = docs.some(d => networkPolicyHasLegacyCatchAllEgress(d))
6458
+ if (!needsMigration) return false
6459
+ /**
6460
+ @type {Array<{ name: string, appLabel: string }>}
6461
+ */
6462
+ const specs = []
6463
+ for (const doc of docs) {
6464
+ const name = manifestMetadataName(doc)
6465
+ const spec = /** @type {Record<string, unknown>} */ (doc).spec
6466
+ let appLabel = ''
6467
+ if (spec !== null && typeof spec === 'object' && !Array.isArray(spec)) {
6468
+ const podSelector = /** @type {Record<string, unknown>} */ (spec).podSelector
6469
+ if (podSelector !== null && typeof podSelector === 'object' && !Array.isArray(podSelector)) {
6470
+ const matchLabels = /** @type {Record<string, unknown>} */ (podSelector).matchLabels
6471
+ if (matchLabels !== null && typeof matchLabels === 'object' && !Array.isArray(matchLabels)) {
6472
+ const a = /** @type {Record<string, unknown>} */ (matchLabels).app
6473
+ if (typeof a === 'string') appLabel = a
6474
+ }
6475
+ }
6476
+ }
6477
+ if (typeof name === 'string' && name !== '' && appLabel !== '') specs.push({ name, appLabel })
6478
+ }
6479
+ if (specs.length === 0) return false
6480
+ const blocks = specs.map(({ name, appLabel }, i) => {
6481
+ const block = buildNetworkPolicyYaml(name, appLabel)
6482
+ return i === 0 ? block.trimEnd() : stripYamlLanguageServerModeline(block).trimEnd()
6483
+ })
6484
+ await writeFile(npAbs, `${blocks.join('\n---\n')}\n`, 'utf8')
6485
+ return true
6486
+ }
6487
+
6409
6488
  /**
6410
6489
  * Створює відсутні NetworkPolicy для workload-ів у каталозі (base → `components/`, інакше — поруч).
6411
6490
  * @param {string} dir абсолютний каталог workload-маніфесту
@@ -6420,6 +6499,12 @@ async function ensureNetworkPoliciesForWorkloadsInDir(dir, workloads, rootNorm,
6420
6499
  const isBase = isK8sYamlUnderBaseDirectory(`${relDir}/probe.yaml`)
6421
6500
  const npAbs = isBase ? join(dir, '..', COMPONENTS_DIR, NETWORK_POLICY_FILENAME) : join(dir, NETWORK_POLICY_FILENAME)
6422
6501
  const npRel = (relative(rootNorm, npAbs) || npAbs).replaceAll('\\', '/')
6502
+ if (existsSync(npAbs)) {
6503
+ const migrated = await regenerateLegacyNetworkPolicyDocsInFile(npAbs)
6504
+ if (migrated) {
6505
+ passFn(`${npRel}: міграція legacy catch-all egress → канон з явними in-cluster портами (k8s.mdc)`)
6506
+ }
6507
+ }
6423
6508
  const existing = await existingNetworkPolicyNames(npAbs)
6424
6509
  /**
6425
6510
  @type {Array<{ name: string, appLabel: string, kind: string }>}
package/rules/k8s/k8s.mdc CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
3
- version: '1.35'
3
+ version: '1.36'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
@@ -388,7 +388,7 @@ images:
388
388
 
389
389
  - **`kustomization.yaml`** — `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`, `resources: [hpa.yaml, networkpolicy.yaml, pdb.yaml]` (відсортовано за алфавітом).
390
390
  - **`hpa.yaml`** — `autoscaling/v2`, `HorizontalPodAutoscaler`, **без** `metadata.namespace` (namespace задає kustomization-споживач), `spec.scaleTargetRef.name` **= `metadata.name`** Deployment з base, dev-like значення `minReplicas: 1`, `maxReplicas: 1`.
391
- - **`networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace`; один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у sibling `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns (UDP/TCP 53); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні, включно з metadata `169.254.169.254:80`); **інші порти** — лише in-cluster через `to.namespaceSelector: {}` (трафік на `*.svc` / Pod-и в кластері; Postgres лише `*.svc`, без Cloud SQL). Заборонено `egress: [{}]`. Канон: [networkpolicy.snippet.yaml](./policy/network_policy/template/networkpolicy.snippet.yaml). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
391
+ - **`networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace`; один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у sibling `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns (UDP/TCP 53); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні, включно з metadata `169.254.169.254:80`); **in-cluster** `to.namespaceSelector: {}` з **явним списком TCP-портів** (`80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318`; трафік на `*.svc` / Pod-и в кластері). Заборонено: `egress: [{}]`; `to.namespaceSelector: {}` без `ports:` (catch-all). Додаткові in-cluster порти можна додати вручну у `ports:` цього rule. Канон: [networkpolicy.snippet.yaml](./policy/network_policy/template/networkpolicy.snippet.yaml). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
392
392
  - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, **без** `metadata.namespace`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment, dev-like `minAvailable: 0`.
393
393
 
394
394
  Інші назви каталогу (`scale/`, `hpa-component/`, `pdb-component/`) — fail.
@@ -509,6 +509,25 @@ spec:
509
509
  port: 443
510
510
  - to:
511
511
  - namespaceSelector: {}
512
+ ports:
513
+ - protocol: TCP
514
+ port: 80
515
+ - protocol: TCP
516
+ port: 443
517
+ - protocol: TCP
518
+ port: 5432
519
+ - protocol: TCP
520
+ port: 3306
521
+ - protocol: TCP
522
+ port: 1433
523
+ - protocol: TCP
524
+ port: 6379
525
+ - protocol: TCP
526
+ port: 8080
527
+ - protocol: TCP
528
+ port: 4317
529
+ - protocol: TCP
530
+ port: 4318
512
531
  ```
513
532
 
514
533
  ```yaml title="k8s/components/hpa.yaml"
@@ -85,6 +85,13 @@ deny contains "spec.egress: потрібен to.namespaceSelector: {} (інші
85
85
  not egress_has_cluster_namespace_selector(spec)
86
86
  }
87
87
 
88
+ deny contains "spec.egress: to.namespaceSelector: {} мусить мати непорожні ports — catch-all заборонено (k8s.mdc)" if {
89
+ is_np_doc
90
+ spec := object.get(input, "spec", null)
91
+ is_object(spec)
92
+ cluster_egress_rule_without_ports(spec)
93
+ }
94
+
88
95
  is_np_doc if input.kind == "NetworkPolicy"
89
96
 
90
97
  is_np_doc if startswith(object.get(input, "apiVersion", ""), "networking.k8s.io/")
@@ -156,3 +163,19 @@ egress_has_cluster_namespace_selector(spec) if {
156
163
  is_object(ns)
157
164
  count(ns) == 0
158
165
  }
166
+
167
+ cluster_egress_rule_without_ports(spec) if {
168
+ egress := object.get(spec, "egress", null)
169
+ is_array(egress)
170
+ some rule in egress
171
+ is_object(rule)
172
+ to_list := object.get(rule, "to", null)
173
+ is_array(to_list)
174
+ some peer in to_list
175
+ is_object(peer)
176
+ ns := object.get(peer, "namespaceSelector", null)
177
+ is_object(ns)
178
+ count(ns) == 0
179
+ ports := object.get(rule, "ports", [])
180
+ count(ports) == 0
181
+ }
@@ -30,3 +30,22 @@ spec:
30
30
  port: 443
31
31
  - to:
32
32
  - namespaceSelector: {}
33
+ ports:
34
+ - protocol: TCP
35
+ port: 80
36
+ - protocol: TCP
37
+ port: 443
38
+ - protocol: TCP
39
+ port: 5432
40
+ - protocol: TCP
41
+ port: 3306
42
+ - protocol: TCP
43
+ port: 1433
44
+ - protocol: TCP
45
+ port: 6379
46
+ - protocol: TCP
47
+ port: 8080
48
+ - protocol: TCP
49
+ port: 4317
50
+ - protocol: TCP
51
+ port: 4318