@nitra/cursor 1.13.31 → 1.13.34

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.34] - 2026-05-18
8
+
9
+ ### Changed
10
+
11
+ - `k8s` rule: винесено канонічний приклад `.kubescape-exceptions.json` з inline-fenced-блоку в `k8s.mdc` у `fix/kubescape_exceptions/template/.kubescape-exceptions.json.snippet.json`; `.mdc` тепер посилається на template markdown-лінком, `inlineTemplateLinks` підставить вміст у `.cursor/rules/n-k8s.mdc` під час sync. Dogfood новій клаузі `scripts.mdc` ("Принцип поширюється і на pure-doc канони"). Bump `k8s.mdc` `1.30` → `1.31`.
12
+
13
+ ## [1.13.33] - 2026-05-18
14
+
15
+ ### Fixed
16
+
17
+ - `style-lint`, `image-avif` rules: markdown-посилання на `policy/*/template/*` у канонічних `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) падав, бо шаблони не були згадані в `npm/rules/<id>/<id>.mdc`. Bump: `style-lint.mdc` `1.3` → `1.4`, `image-avif.mdc` `1.2` → `1.3`.
18
+
19
+ ## [1.13.32] - 2026-05-18
20
+
21
+ ### Added
22
+
23
+ - `k8s` rule (`lint-k8s`): підтримка per-project винятків kubescape — якщо в корені проєкту є `.kubescape-exceptions.json`, `runKubescape` автоматично передає його через `--exceptions <file>`. Канонічний приклад — control **C-0012** (`Applications credentials in configuration files`) на ConfigMap з публічним JWT-конфігом (`HASURA_GRAPHQL_JWT_SECRET={"jwk_url": "https://…"}`): control тригериться лише на імʼя env, не на значення, тому точкове `postureExceptionPolicy` з `kind: ConfigMap` + `attributes.name` знімає false-positive без глобального вимкнення контролю. Bump `k8s.mdc` `1.29` → `1.30`. Документація — секція "Винятки kubescape" в `k8s.mdc`.
24
+
7
25
  ## [1.13.31] - 2026-05-18
8
26
 
9
27
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.31",
3
+ "version": "1.13.34",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
3
- version: '1.2'
3
+ version: '1.3'
4
4
  globs: "**/*.{png,jpg,jpeg,gif,avif,vue,html}"
5
5
  alwaysApply: false
6
6
  ---
@@ -31,7 +31,7 @@ AVIF-двійники **зберігаємо в git** — це готові ар
31
31
 
32
32
  ## Опт-аут для конкретного пакета
33
33
 
34
- У workspace-пакеті, де AVIF-імпорти небажані (наприклад, мобільний бандл або публічний сайт без гарантованої AVIF-підтримки), додай у `package.json` цього пакета:
34
+ У workspace-пакеті, де AVIF-імпорти небажані (наприклад, мобільний бандл або публічний сайт без гарантованої AVIF-підтримки), додай у `package.json` цього пакета. Заборонений typo `disabled-avif` (канон — `disable-avif`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json).
35
35
 
36
36
  ```json title="apps/site/package.json"
37
37
  {
@@ -0,0 +1,21 @@
1
+ [
2
+ {
3
+ "name": "hasura-jwt-public-config",
4
+ "policyType": "postureExceptionPolicy",
5
+ "actions": ["alertOnly"],
6
+ "resources": [
7
+ {
8
+ "designatorType": "Attributes",
9
+ "attributes": {
10
+ "kind": "ConfigMap",
11
+ "name": "hasura-config"
12
+ }
13
+ }
14
+ ],
15
+ "posturePolicies": [
16
+ {
17
+ "controlID": "C-0012"
18
+ }
19
+ ]
20
+ }
21
+ ]
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.29'
3
+ version: '1.31'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
@@ -35,6 +35,14 @@ alwaysApply: false
35
35
 
36
36
  **kubescape:** типово **`kubescape scan <каталог-k8s>`**; поріг серйозності підлаштуй під проєкт (наприклад **`--severity-threshold high`**). Перший запуск може завантажувати артефакти — у 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
+ ### Винятки kubescape: `.kubescape-exceptions.json`
39
+
40
+ Якщо в **корені проєкту** є файл **`.kubescape-exceptions.json`** — `lint-k8s` автоматично передає його в `kubescape scan` через **`--exceptions`** ([postureExceptionPolicy](https://github.com/kubescape/kubescape/blob/master/docs/exceptions.md)). Файл — JSON-масив об'єктів з полями `name`, `policyType: "postureExceptionPolicy"`, `actions` (`["alertOnly"]` — знижує fail до alert, не блокує lint), `resources` (resource designator) і `posturePolicies` (масив `controlID`).
41
+
42
+ Канонічний кейс — **C-0012** (`Applications credentials in configuration files`, High): control тригериться на **імʼя** env, що містить підрядок `secret`/`password`/`key`/`token`, а **не** на значення. Для `HASURA_GRAPHQL_JWT_SECRET` у ConfigMap значення — публічний JWT-конфіг (`jwk_url`, `issuer`), не credentials, але kubescape падає лише через імʼя. Точкове виключення для ConfigMap із цим env — канон: [.kubescape-exceptions.json.snippet.json](./fix/kubescape_exceptions/template/.kubescape-exceptions.json.snippet.json)
43
+
44
+ Підстав свою `attributes.name` (рядок або regex), якщо ConfigMap зветься інакше; виключай контрольно, а не глобально (не додавай винятки без `attributes.name`/`labels`, бо тоді C-0012 знімається для усіх ConfigMap-ів проєкту і реальні витоки credentials теж пройдуть).
45
+
38
46
  У репозиторії пакета **`@nitra/cursor`** скрипт **`lint-k8s`** делегує до CLI **`n-cursor lint-k8s`** (реалізація — **`npm/rules/k8s/js/run.mjs`**). У інших проєктах достатньо встановити **`@nitra/cursor`** у `devDependencies` — бінарка **`n-cursor`** буде у **`node_modules/.bin/`**.
39
47
 
40
48
  ```json title="package.json"
@@ -13,13 +13,17 @@
13
13
  * Kubescape не має аналога цього прапорця; орієнтир цільового кластера — та сама лінія релізу (див. k8s.mdc).
14
14
  */
15
15
  import { spawnSync } from 'node:child_process'
16
- import { basename, dirname, relative } from 'node:path'
16
+ import { existsSync } from 'node:fs'
17
+ import { basename, dirname, join, relative } from 'node:path'
17
18
 
18
19
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
19
20
  import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
20
21
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
21
22
  import { walkDir } from '../../../scripts/utils/walkDir.mjs'
22
23
 
24
+ /** Per-project kubescape exceptions file; підмішується через --exceptions, якщо існує в корені. */
25
+ const KUBESCAPE_EXCEPTIONS_FILE = '.kubescape-exceptions.json'
26
+
23
27
  const PATH_SEPARATOR_RE = /[/\\]/u
24
28
  const YAML_EXT_RE = /\.yaml$/iu
25
29
 
@@ -118,20 +122,41 @@ function runKubeconform(dirs) {
118
122
  return r.status ?? 1
119
123
  }
120
124
 
125
+ /**
126
+ * Будує аргументи `--exceptions <file>` для kubescape, якщо в корені проєкту є
127
+ * `.kubescape-exceptions.json`. Інакше — порожній масив.
128
+ * @param {string} root корінь репозиторію
129
+ * @returns {string[]} `['--exceptions', '<abs-path>']` або `[]`
130
+ */
131
+ export function buildKubescapeExceptionsArgs(root) {
132
+ const exceptionsPath = join(root, KUBESCAPE_EXCEPTIONS_FILE)
133
+ return existsSync(exceptionsPath) ? ['--exceptions', exceptionsPath] : []
134
+ }
135
+
121
136
  /**
122
137
  * Запускає kubescape scan для кожного каталогу окремо (узгоджено з прикладами CLI).
123
138
  * Немає прапорця версії Kubernetes — за потреби додай `scan framework <ім’я>` під CIS/інші набори.
139
+ *
140
+ * Якщо в корені проєкту є `.kubescape-exceptions.json` — підмішується через `--exceptions <file>`.
141
+ * Файл потрібен для точкових винятків control'ів kubescape (напр. C-0012 на ConfigMap, що містить
142
+ * публічний JWT-конфіг типу `HASURA_GRAPHQL_JWT_SECRET={"jwk_url": "https://…"}` — control тригериться
143
+ * на ім'я env, а не на значення; див. приклад у `k8s.mdc`).
124
144
  * @param {string[]} dirs абсолютні шляхи до `…/k8s`
145
+ * @param {string} root корінь репозиторію (для пошуку exceptions-файлу)
125
146
  * @returns {number} 0 при успіху, інакше код останнього невдалого scan або 127, якщо kubescape відсутній у PATH
126
147
  */
127
- function runKubescape(dirs) {
148
+ function runKubescape(dirs, root) {
149
+ const exceptionsArgs = buildKubescapeExceptionsArgs(root)
150
+ if (exceptionsArgs.length > 0) {
151
+ console.log(`run-k8s: kubescape exceptions — ${KUBESCAPE_EXCEPTIONS_FILE}`)
152
+ }
128
153
  for (const d of dirs) {
129
154
  const kubescapePath = resolveCmd('kubescape')
130
155
  if (!kubescapePath) {
131
156
  console.error('kubescape не знайдено в PATH. Встанови з https://github.com/kubescape/kubescape#readme')
132
157
  return 127
133
158
  }
134
- const r = spawnSync(kubescapePath, ['scan', d, '--severity-threshold', 'high'], {
159
+ const r = spawnSync(kubescapePath, ['scan', d, '--severity-threshold', 'high', ...exceptionsArgs], {
135
160
  stdio: 'inherit',
136
161
  shell: false
137
162
  })
@@ -165,7 +190,7 @@ export async function runLintK8s() {
165
190
  const kc = runKubeconform(dirs)
166
191
  if (kc !== 0) return kc
167
192
 
168
- const ks = runKubescape(dirs)
193
+ const ks = runKubescape(dirs, root)
169
194
  return ks
170
195
  }
171
196
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Правила стилів CSS та SCSS
3
- version: '1.3'
3
+ version: '1.4'
4
4
  globs: "**/*.{css,scss,vue}"
5
5
  alwaysApply: false
6
6
  ---
@@ -12,6 +12,25 @@ alwaysApply: false
12
12
  - **Запуск stylelint:** лише **`npx stylelint`**. Локально — через скрипт **`lint-style`** (`bun run lint-style`); у **GitHub Actions** у кроці **`run`** викликай `npx stylelint '**/*.{css,scss,vue}' --fix` напряму (не через **`bun run lint-style`**). Не використовуй **`bunx stylelint`**. Після змін запускай **`bun run lint-style`** і виправляй усе, що лишилось після auto-fix; за потреби — повний `bun run lint` (навичка **`/n-lint`**).
13
13
  - **Не розширюй винятки:** не додавай зайві **`stylelint-disable`** без потреби; краще підлаштувати стилі під правила проєкту.
14
14
 
15
+ ## Канон
16
+
17
+ ### `package.json`
18
+
19
+ - `lint-style` (substring requirement): [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
20
+ - `stylelint.extends`: [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
21
+
22
+ ### `.vscode/extensions.json`
23
+
24
+ - Канон `recommendations`: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
25
+
26
+ ### `.vscode/settings.json`
27
+
28
+ - Вимкнення вбудованої CSS-валідації VS Code: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
29
+
30
+ ### CI: `.github/workflows/lint-style.yml`
31
+
32
+ - Канон: [lint-style.yml.snippet.yml](./policy/lint_style_yml/template/lint-style.yml.snippet.yml)
33
+
15
34
  **`package.json`:**
16
35
 
17
36
  ```json title="package.json"