@nitra/cursor 1.13.49 → 1.13.50

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,12 @@
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.50] - 2026-05-19
8
+
9
+ ### Changed
10
+
11
+ - `lint-k8s`: kubescape тепер збирає kustomize-маніфест через **вшиту в kubectl підкоманду** — `kubectl kustomize <dir> | kubescape scan -` (замість окремого бінарника `kustomize build <dir>`, доданого в 1.13.49). Причина: на машинах без окремого `kustomize` lint-k8s падав з `kustomize не знайдено в PATH`, тоді як `kubectl` — штатний інструмент з вшитим Kustomize (рендеринг локальний, доступ до кластера не потрібен). PATH-залежність зведена з пари `kubectl+kustomize` до одного `kubectl`; крок `Install kustomize` у GHA-шаблоні `lint-k8s.yml` прибрано (на github-hosted runner'ах kubectl уже доступний). Bump `k8s.mdc` `1.37` → `1.38`.
12
+
7
13
  ## [1.13.49] - 2026-05-19
8
14
 
9
15
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.49",
3
+ "version": "1.13.50",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -40,8 +40,10 @@ all_run_text := concat("\n", [run_text |
40
40
  run_text := step_run_to_text(step)
41
41
  ])
42
42
 
43
+ # conftest парсить YAML 1.1, тож канонічний `on:` без лапок стає булевим ключем
44
+ # `true` (як у `ga.lint_ga`). Тому читаємо через `input["true"]`.
43
45
  push_paths_set := {p |
44
- some p in object.get(object.get(object.get(input, "on", {}), "push", {}), "paths", [])
46
+ some p in object.get(object.get(object.get(input, "true", {}), "push", {}), "paths", [])
45
47
  }
46
48
 
47
49
  # ── deny: on.push.paths subset-of ──────────────────────────────────────
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.37'
3
+ version: '1.38'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
@@ -29,11 +29,11 @@ alwaysApply: false
29
29
 
30
30
  Окремо від modeline `$schema` у редакторі варто ганяти CLI-лінтери (**kubeconform** і **kubescape**) по тих самих дерев’ях **`…/k8s`**.
31
31
 
32
- **Залежності:** виконувані файли kubeconform, kubescape і kustomize у **PATH**; не додавай їх у **devDependencies**.
32
+ **Залежності:** виконувані файли kubeconform, kubescape і kubectl у **PATH** (kustomize використовуємо як вшиту підкоманду **`kubectl kustomize`** — окремий бінарник `kustomize` не потрібен); не додавай їх у **devDependencies**.
33
33
 
34
34
  **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`check-k8s.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
35
35
 
36
- **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kustomize build <dir> | kubescape scan -`** з порогом **`--severity-threshold high`**. Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling **`components/networkpolicy.yaml`** без `metadata.namespace`: сирий dir-скан не виконує kustomize-збірку й бачить порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, тож `podSelector` не матчиться. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
36
+ **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir> | kubescape scan -`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling **`components/networkpolicy.yaml`** без `metadata.namespace`: сирий dir-скан не виконує kustomize-збірку й бачить порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, тож `podSelector` не матчиться. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
37
37
 
38
38
  ### Винятки kubescape: `.kubescape-exceptions.json`
39
39
 
@@ -99,10 +99,8 @@ jobs:
99
99
  curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
100
100
  echo "$HOME/.kubescape/bin" >> $GITHUB_PATH
101
101
 
102
- - name: Install kustomize
103
- run: |
104
- curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
105
- sudo mv kustomize /usr/local/bin/
102
+ # kustomize не встановлюємо окремо — використовуємо вбудовану підкоманду `kubectl kustomize`
103
+ # (kubectl уже доступний на github-hosted runner'ах: https://github.com/actions/runner-images).
106
104
 
107
105
  - name: Lint K8s
108
106
  run: bun run lint-k8s
@@ -178,14 +178,16 @@ export async function findKustomizationDirs(dir) {
178
178
  }
179
179
 
180
180
  /**
181
- * Запускає `kustomize build <dir>` і повертає stdout як буфер. stderr інхеритимо в термінал,
182
- * щоб помилки збірки (биті посилання, недозволені плагіни) було видно одразу.
183
- * @param {string} kustomizePath абсолютний шлях до бінарника kustomize
181
+ * Запускає `kubectl kustomize <dir>` і повертає stdout як буфер. stderr інхеритимо в термінал,
182
+ * щоб помилки збірки (биті посилання, недозволені плагіни) було видно одразу. Використовуємо
183
+ * вшитий у kubectl kustomize замість окремого бінарника — kubectl є штатним інструментом і не
184
+ * потребує доступу до кластера для підкоманди `kustomize` (рендеринг локальний).
185
+ * @param {string} kubectlPath абсолютний шлях до бінарника kubectl
184
186
  * @param {string} dir абсолютний шлях до каталогу з `kustomization.yaml`
185
187
  * @returns {{ status: number, stdout: Buffer }} статус процесу і зібраний маніфест
186
188
  */
187
- function runKustomizeBuild(kustomizePath, dir) {
188
- const r = spawnSync(kustomizePath, ['build', dir], {
189
+ function runKustomizeBuild(kubectlPath, dir) {
190
+ const r = spawnSync(kubectlPath, ['kustomize', dir], {
189
191
  stdio: ['ignore', 'pipe', 'inherit'],
190
192
  shell: false
191
193
  })
@@ -211,7 +213,7 @@ function runKubescapeStdin(kubescapePath, manifest, exceptionsArgs) {
211
213
 
212
214
  /**
213
215
  * Запускає kubescape по зібраному kustomize-маніфесту для кожного `…/k8s`-кореня. Для кожного
214
- * dir-у з `kustomization.yaml` (крім `kind: Component`) робимо `kustomize build <dir>` і піпимо
216
+ * dir-у з `kustomization.yaml` (крім `kind: Component`) робимо `kubectl kustomize <dir>` і піпимо
215
217
  * stdout у `kubescape scan -`. Це усуває false-positive C-0260 (`Missing network policy`) у випадках,
216
218
  * коли NetworkPolicy живе у sibling `components/` без `metadata.namespace` (намспейс інжектить
217
219
  * overlay через `kustomization.namespace`); сирий dir-скан не виконує kustomize й бачить порожній
@@ -224,7 +226,7 @@ function runKubescapeStdin(kubescapePath, manifest, exceptionsArgs) {
224
226
  * (точкові винятки control'ів, напр. C-0012 на ConfigMap з публічним JWT-конфігом; див. k8s.mdc).
225
227
  * @param {string[]} dirs абсолютні шляхи до `…/k8s`
226
228
  * @param {string} root корінь репозиторію (для пошуку exceptions-файлу)
227
- * @returns {Promise<number>} 0 при успіху, інакше код невдалого процесу або 127, якщо kubescape/kustomize відсутні
229
+ * @returns {Promise<number>} 0 при успіху, інакше код невдалого процесу або 127, якщо kubescape/kubectl відсутні
228
230
  */
229
231
  async function runKubescape(dirs, root) {
230
232
  const exceptionsArgs = buildKubescapeExceptionsArgs(root)
@@ -236,7 +238,7 @@ async function runKubescape(dirs, root) {
236
238
  console.error('kubescape не знайдено в PATH. Встанови з https://github.com/kubescape/kubescape#readme')
237
239
  return 127
238
240
  }
239
- let kustomizePath = null
241
+ let kubectlPath = null
240
242
  for (const d of dirs) {
241
243
  const kdirs = await findKustomizationDirs(d)
242
244
  if (kdirs.length === 0) {
@@ -252,16 +254,16 @@ async function runKubescape(dirs, root) {
252
254
  if (r.status !== 0) return r.status ?? 1
253
255
  continue
254
256
  }
255
- if (kustomizePath === null) {
256
- kustomizePath = resolveCmd('kustomize')
257
- if (!kustomizePath) {
258
- console.error('kustomize не знайдено в PATH. Встанови з https://kubectl.docs.kubernetes.io/installation/kustomize/')
257
+ if (kubectlPath === null) {
258
+ kubectlPath = resolveCmd('kubectl')
259
+ if (!kubectlPath) {
260
+ console.error('kubectl не знайдено в PATH. Встанови з https://kubernetes.io/docs/tasks/tools/#kubectl')
259
261
  return 127
260
262
  }
261
263
  }
262
264
  for (const kdir of kdirs) {
263
- console.log(`run-k8s: kustomize build ${kdir} | kubescape scan -`)
264
- const build = runKustomizeBuild(kustomizePath, kdir)
265
+ console.log(`run-k8s: kubectl kustomize ${kdir} | kubescape scan -`)
266
+ const build = runKustomizeBuild(kubectlPath, kdir)
265
267
  if (build.status !== 0) return build.status
266
268
  const ks = runKubescapeStdin(kubescapePath, build.stdout, exceptionsArgs)
267
269
  if (ks.enoent) {
@@ -19,6 +19,10 @@ expected_runs_on := data.template.snippet.jobs.text["runs-on"]
19
19
 
20
20
  expected_perms := data.template.snippet.jobs.text.permissions
21
21
 
22
+ # conftest парсить YAML 1.1, де канонічний `on:` без лапок стає булевим ключем
23
+ # `true` (як у `ga.lint_ga`). Тому читаємо on-блок через `input["true"]`.
24
+ gha_on := input["true"]
25
+
22
26
  job := input.jobs.text
23
27
 
24
28
  job_uses_set contains job.steps[_].uses
@@ -45,17 +49,17 @@ deny contains msg if {
45
49
  }
46
50
 
47
51
  deny contains msg if {
48
- not branches_superset_of(input.on.push.branches, expected_push_branches)
52
+ not branches_superset_of(gha_on.push.branches, expected_push_branches)
49
53
  msg := "lint-text.yml: on.push.branches має містити dev і main (text.mdc)"
50
54
  }
51
55
 
52
56
  deny contains msg if {
53
- not branches_superset_of(input.on.pull_request.branches, expected_pr_branches)
57
+ not branches_superset_of(gha_on.pull_request.branches, expected_pr_branches)
54
58
  msg := "lint-text.yml: on.pull_request.branches має містити dev і main (text.mdc)"
55
59
  }
56
60
 
57
61
  deny contains msg if {
58
- not paths_superset_of(input.on.push.paths, expected_push_paths)
62
+ not paths_superset_of(gha_on.push.paths, expected_push_paths)
59
63
  msg := "lint-text.yml: on.push.paths має містити очікувані glob-и (text.mdc)"
60
64
  }
61
65