@nitra/cursor 1.9.0 → 1.9.2

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,22 @@
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.2] - 2026-05-11
8
+
9
+ ### Changed
10
+
11
+ - **k8s — modeline `$schema` тепер опційний; `file:…` заборонено як заглушку:** правило `k8s.mdc` уточнено — рядок `# yaml-language-server: $schema=…` обов'язковий **лише** коли для поєднання `apiVersion`/`kind` існує надійна публічна схема (kustomization / yannh / datree CRDs-catalog). Якщо публічної схеми немає, modeline **не додається зовсім** (раніше п. 5 розділу «Визначення схеми YAML» допускав `file:` за узгодженням — це створювало фальшиву видимість валідації, а автовиправлення n-fix залишало заглушковий `# yaml-language-server: $schema=file:.`). У `check-k8s.mjs`: (1) файли без modeline більше не падають як «перший рядок має бути коментарем», натомість `pass` із позначкою «без modeline — перевірка $schema пропущена»; (2) `$schema=file:…` тепер реєструється як помилка з підказкою прибрати modeline; (3) modeline нижче першого рядка все ще порушення; (4) `HttpBackendGroup` (Yandex ALB) як виняток без modeline залишається без змін. `lint-k8s` (kubeconform з прапорцем ignore-missing-schemas) продовжує покривати валідацію і для файлів без modeline. JSDoc на початку `check-k8s.mjs` оновлено.
12
+
13
+ ## [1.9.1] - 2026-05-11
14
+
15
+ ### Added
16
+
17
+ - **rego `k8s.base_kustomization` — defense-in-depth deny на HPA/PDB у `base/kustomization.yaml::resources:`:** додано пер-документне правило, що відмовляє, якщо `resources:` локально містить запис із basename `hpa.yaml`/`pdb.yaml`/`hpa.yml`/`pdb.yml` (у будь-якому підкаталозі). Канон k8s.mdc — HPA/PDB у sibling `components/` (Kustomize Component) і підключаються з overlay. Рекурсивний обхід дерева `resources:`/`components:`/`bases:` (із зануренням у вкладені kustomization.yaml) лишається у JS-оркестраторі `verifyK8sBaseKustomizeHasNoHpaPdb` (потребує fs-доступу). Rego-deny ловить найпоширеніший локальний випадок навіть якщо JS-крок упаде з винятку раніше. 5 нових rego-тестів (`hpa.yaml`/`pdb.yaml`/`hpa.yml` у `resources:`, чистий `resources:`, lookalike basename `myhpa.yaml`); `opa test` зелений (10/10).
18
+
19
+ ### Fixed
20
+
21
+ - **`check-k8s.mjs`:** додано константу `GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'`. Її відсутність кидала `ReferenceError` у `indexOneK8sYamlForHasuraCanon` (на лінії з `av.startsWith(GATEWAY_API_GROUP_PREFIX)`), яку ловив outer try/catch у `bin/n-cursor.js` і **тихо пропускав** усі наступні JS-валідатори в `check-k8s.mjs::check()` — серед них `validateKustomizeHpaPdbOnlyWithBaseDeployment`, `validateConfigMapNameMatchesDeployment`, `validateDeploymentHpaPdbAndTopology`, `validateProdKustomizationOverrides`. Наслідок у репах споживачів: правило «HPA/PDB заборонені у `k8s/base/`» не спрацьовувало (хоча `verifyK8sBaseKustomizeHasNoHpaPdb` логіку містив правильну), бо exception вилітав раніше за чергу JS-кроків. Rego-крок (`runAllK8sRego`) ішов **до** crash-точки й тому продовжував працювати — пер-документні перевірки залишалися активними, а cross-file JS — ні.
22
+
7
23
  ## [1.9.0] - 2026-05-11
8
24
 
9
25
  ### Changed
package/mdc/ci4.mdc CHANGED
@@ -15,6 +15,54 @@ C4-діаграми проєкту живуть у Markdown поряд із ко
15
15
  тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
16
16
  важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
17
17
 
18
+ ## Специфікація як джерело істини (Spec-as-Source)
19
+
20
+ У AI-native розробці первинним артефактом, який підтримують інженери, є **специфікація**, а не
21
+ кодова база. Код — похідний артефакт, «будівельне риштування», яке агент генерує, верифікує або
22
+ повністю відновлює зі специфікації. Якщо поведінка системи має змінитися — **спочатку оновлюємо
23
+ специфікацію (C4/ADR/опис компонента), і лише потім** агент генерує відповідний код. Зворотний
24
+ порядок («код вже написаний, доку напишемо потім») перетворює специфікацію на декорацію.
25
+
26
+ ## Тест на повне відтворення (Rebuild Test)
27
+
28
+ Документація вважається повною лише тоді, коли проходить бінарний тест: якщо видалити теку
29
+ `src/`, відкрити нову LLM-сесію з чистим контекстом і дати агенту доступ лише до `.md`-файлів —
30
+ агент має **відтворити робочу кодову базу**. Архітектурні збої (агент не знає структуру тек),
31
+ інтеграційні (неописані міжсервісні контракти), падіння юніт-тестів (неописана бізнес-логіка) —
32
+ це не «складність задачі», а **прогалини документації**, які треба заповнити у тому ж PR.
33
+
34
+ ## Ефективність токенів і чистий Markdown
35
+
36
+ Вікно контексту LLM обмежене, а якість міркування деградує в міру його заповнення (ефект
37
+ «Lost in the Middle»). Тому документація **очищається від HTML-розмітки, CSS-класів,
38
+ навігаційних обгорток** і всього, що не несе семантичного навантаження. Чистий Markdown замість
39
+ HTML економить 80–90 % токенів (≈16 000 → 1 600 на сторінку), що прямо впливає на точність
40
+ згенерованого коду і знижує вартість API. Жодних `<div>`/`<span>`/класів у тілі `.md`/`.mdc`.
41
+
42
+ ## Контекстна незалежність розділів
43
+
44
+ RAG витягує **фрагменти**, не цілі документи. Тому кожен розділ має сенс **без сусіднього
45
+ тексту**. Заборонено: «як було згадано вище», «ця змінна», «попередній метод», «той самий
46
+ сервіс». Замість цього — щоразу явно повторюємо назву сутності: «автентифікація OAuth 2.0»,
47
+ «функція `calculateTotal()`», «контейнер `api-gateway`». Коли агент отримає лише цей фрагмент,
48
+ у нього має бути повний словник для коректної генерації.
49
+
50
+ ## Docs-as-Code
51
+
52
+ Документація живе у Git поруч із кодом, проходить **той самий Code Review**, версіонується і
53
+ автоматично перевіряється у CI (лінтери Markdown, валідатори посилань, `npx @nitra/cursor
54
+ check`). Биті посилання й документація, що «не компілюється», — **блокуючий баг**, не
55
+ косметика. Це продовження принципу «оновлення синхронно зі змінами» нижче: один PR — код +
56
+ схема + ADR + тести.
57
+
58
+ ## Трасування як документація недетермінованої поведінки
59
+
60
+ Для компонентів, де рішення приймає нейромережа або складна евристика, класичної форми
61
+ «вхід X → вихід Y» **недостатньо** — поведінка недетермінована. Джерелом істини стає
62
+ **пайплайн логування й трасування**: які інструменти використав агент, який контекст мав,
63
+ чому ухвалив це рішення. У C4-компоненті такого типу — посилання на дашборд/storage трасувань
64
+ обов'язкове нарівні з посиланнями на тести.
65
+
18
66
  ## Розташування
19
67
 
20
68
  C4-діаграми проєкту живуть у теці `docs/ci4/` — це **канонічне місце** для всіх рівнів
package/mdc/k8s.mdc CHANGED
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
3
- version: '1.27'
3
+ version: '1.28'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
7
7
 
8
8
  # Kubernetes YAML у шляхах з `k8s`
9
9
 
10
- Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`** (наприклад `site/k8s/base/deployment.yaml`), **перший рядок** — коментар-директива для [YAML Language Server](https://github.com/redhat-developer/yaml-language-server):
10
+ Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`** (наприклад `site/k8s/base/deployment.yaml`), якщо існує **публічна** схема (kustomization / yannh / datree CRDs-catalog — див. «Визначення схеми YAML»), **перший рядок** — коментар-директива для [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) з URL за `https://`:
11
11
 
12
12
  ```yaml
13
13
  # yaml-language-server: $schema=https://...
@@ -15,7 +15,9 @@ alwaysApply: false
15
15
 
16
16
  Далі — вміст маніфесту. Зайвий порожній рядок між коментарем і YAML не додавай, якщо в проєкті не прийнято інше.
17
17
 
18
- **Винятокбез modeline:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядокодразу YAML (`apiVersion:` тощо). Перевірка **`check-k8s.mjs`**.
18
+ **Modelineопційний:** якщо для конкретного поєднання `apiVersion`/`kind` **немає** надійної публічної схеми (yannh/datree/schemastore не покривають), залиш файл **без** рядка `# yaml-language-server: $schema=…`. **Заборонено** ставити `$schema=file:…` як заглушку це створює видимість валідації без неї. Без modeline `check-k8s` не валідує URL, але **`lint-k8s`** (kubeconform/kubescape) продовжить роботу. Якщо modeline присутній він обов'язково перший рядок і **тільки** `https://` URL, який відповідає очікуваному за `apiVersion`/`kind`.
19
+
20
+ **Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядок — одразу YAML (`apiVersion:` тощо). Перевірка — **`check-k8s.mjs`**.
19
21
 
20
22
  **Розширення:** усі маніфести під **`k8s`**, включно з **`kustomization.yaml`**, — лише **`.yaml`** (розширення **`.yml`** не використовуй).
21
23
 
@@ -695,7 +697,7 @@ patch: |-
695
697
  # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
696
698
  ```
697
699
 
698
- 5. **Немає надійного публічного URL** — не вигадуй: залиш коректний `$schema` або `file:` за узгодженням.
700
+ 5. **Немає надійного публічного URL** — не вигадуй URL і **не** використовуй `$schema=file:…` як заглушку (фальшива валідація). Залиш файл **без** рядка `# yaml-language-server: $schema=…` зовсім — `check-k8s` пропустить перевірку URL для таких файлів, а `lint-k8s` (kubeconform з `-ignore-missing-schemas`) усе ще покриє валідацію.
699
701
 
700
702
  ## Багатодокументні YAML
701
703
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -34,7 +34,37 @@ deny contains base_namespace_required_msg if {
34
34
  trim_space(ns) == ""
35
35
  }
36
36
 
37
+ # HPA/PDB у base заборонені — канон k8s.mdc: тримати у sibling каталозі `components/`
38
+ # і підключати з overlay (`components: [- ../components]`). Цей deny — швидкий gate
39
+ # на *локальний* `resources:` base/kustomization.yaml (точне ім'я `hpa.yaml`/`pdb.yaml`,
40
+ # у будь-якому підкаталозі). Рекурсивний обхід `resources:`/`components:`/`bases:`
41
+ # (із зануренням у вкладені kustomization.yaml) — JS-оркестратор
42
+ # `verifyK8sBaseKustomizeHasNoHpaPdb` у `check-k8s.mjs` (потребує fs-доступу). Цей
43
+ # rego-deny — defense-in-depth: спрацює навіть якщо JS-крок упаде з винятку раніше.
44
+ deny contains hpa_pdb_in_base_resources_msg(r) if {
45
+ is_kustomization
46
+ some r in object.get(input, "resources", [])
47
+ is_string(r)
48
+ is_hpa_or_pdb_filename(r)
49
+ }
50
+
51
+ hpa_pdb_in_base_resources_msg(file) := sprintf(
52
+ concat("", [
53
+ "у base/kustomization.yaml `resources:` містить '%v' — HPA/PDB заборонені у base, ",
54
+ "перенесіть у sibling каталог components/ і підключайте з overlay (k8s.mdc)",
55
+ ]),
56
+ [file],
57
+ )
58
+
37
59
  is_kustomization if {
38
60
  input.kind == "Kustomization"
39
61
  startswith(object.get(input, "apiVersion", ""), "kustomize.config.k8s.io/")
40
62
  }
63
+
64
+ is_hpa_or_pdb_filename(p) if {
65
+ basename(p) in {"hpa.yaml", "pdb.yaml", "hpa.yml", "pdb.yml"}
66
+ }
67
+
68
+ basename(p) := parts[count(parts) - 1] if {
69
+ parts := split(p, "/")
70
+ }
@@ -34,3 +34,40 @@ test_allow_non_kustomization if {
34
34
  "metadata": {"name": "cm"},
35
35
  }
36
36
  }
37
+
38
+ base_kust_ok := object.union(base_kust, {"namespace": "dev"})
39
+
40
+ test_deny_hpa_yaml_in_resources if {
41
+ count(base_kustomization.deny) > 0 with input as object.union(
42
+ base_kust_ok,
43
+ {"resources": ["deployment.yaml", "hpa.yaml"]},
44
+ )
45
+ }
46
+
47
+ test_deny_pdb_yaml_in_resources if {
48
+ count(base_kustomization.deny) > 0 with input as object.union(
49
+ base_kust_ok,
50
+ {"resources": ["pdb.yaml"]},
51
+ )
52
+ }
53
+
54
+ test_deny_hpa_yml_in_subdir if {
55
+ count(base_kustomization.deny) > 0 with input as object.union(
56
+ base_kust_ok,
57
+ {"resources": ["nested/dir/hpa.yml"]},
58
+ )
59
+ }
60
+
61
+ test_allow_resources_without_hpa_pdb if {
62
+ count(base_kustomization.deny) == 0 with input as object.union(
63
+ base_kust_ok,
64
+ {"resources": ["deployment.yaml", "service.yaml", "configmap.yaml"]},
65
+ )
66
+ }
67
+
68
+ test_allow_lookalike_basename if {
69
+ count(base_kustomization.deny) == 0 with input as object.union(
70
+ base_kust_ok,
71
+ {"resources": ["myhpa.yaml", "pdb-extra.yaml"]},
72
+ )
73
+ }
@@ -1,11 +1,16 @@
1
1
  /**
2
2
  * Перевіряє Kubernetes YAML у шляхах з сегментом `k8s` (див. k8s.mdc).
3
3
  *
4
- * Перший рядок `# yaml-language-server: $schema=…`, без дублікатів, розширення `.yaml`
4
+ * Перший рядок `# yaml-language-server: $schema=…` (URL за `https://`), без дублікатів, розширення `.yaml`
5
5
  * (окрім `kustomization.yaml`); URL схеми за першим документом — kustomization / yannh / datree
6
6
  * (**виняток:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` — рядка `# yaml-language-server:` у файлі бути не має).
7
7
  * (datree за замовчуванням: GitHub Pages `https://datreeio.github.io/CRDs-catalog/…`).
8
8
  *
9
+ * Modeline **опційний**: якщо публічної схеми немає (yannh/datree/schemastore не покривають це поєднання
10
+ * apiVersion/kind), залиш файл **без** рядка `# yaml-language-server: $schema=…` — `check-k8s` пропустить
11
+ * перевірку URL. **Заборонено** ставити `$schema=file:…` як заглушку (це фальшива валідація). Якщо modeline
12
+ * присутній, він має бути **першим рядком** і містити `https://` URL, що відповідає очікуваному за apiVersion/kind.
13
+ *
9
14
  * Додатково: у кожному YAML-документі з **`kind: Deployment`** у кожного контейнера
10
15
  * **`spec.template.spec.containers[]`** має бути **`resources.requests.cpu`** і **`resources.requests.memory`**
11
16
  * (непорожні скаляри). У шарі **`…/k8s/…/base/…`** значення жорстко **`cpu: '0.02'`**, **`memory: '128Mi'`**
@@ -309,6 +314,8 @@ const YANNH_GROUPS = new Set([
309
314
  'storagemigration.k8s.io'
310
315
  ])
311
316
 
317
+ const GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'
318
+
312
319
  const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
313
320
  const PATH_SPLIT_RE = /[/\\]/u
314
321
  const YAML_EXTENSION_RE = /\.ya?ml$/iu
@@ -3591,8 +3598,13 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
3591
3598
  // topologySpread, HCP, svc/svc-hl) — делегована rego, виконано у `runAllK8sRego` вище.
3592
3599
 
3593
3600
  if (schemaUrl.startsWith('file:')) {
3594
- pass(`${rel}: локальна схема (file:) — перевірка URL за apiVersion/kind пропущена`)
3595
- } else if (HTTPS_SCHEMA_RE.test(schemaUrl)) {
3601
+ fail(
3602
+ `${rel}: $schema=file:… заборонено (фальшива валідація без публічної схеми). ` +
3603
+ `Якщо публічної схеми для цього apiVersion/kind немає — прибери modeline зовсім (k8s.mdc)`
3604
+ )
3605
+ return
3606
+ }
3607
+ if (HTTPS_SCHEMA_RE.test(schemaUrl)) {
3596
3608
  const doc = firstYamlDocument(body)
3597
3609
  const { expected, reason } = expectedSchemaUrl(abs, doc)
3598
3610
 
@@ -3608,7 +3620,9 @@ function checkK8sYamlFileWithSchemaModeline(abs, rel, baseLower, lines, fail, pa
3608
3620
 
3609
3621
  pass(`${rel}: $schema узгоджено (${reason})`)
3610
3622
  } else {
3611
- fail(`${rel}: $schema має бути https URL або file: (див. k8s.mdc)`)
3623
+ fail(
3624
+ `${rel}: $schema має бути https URL (file: і інші схеми заборонені — якщо публічної схеми немає, прибери modeline; k8s.mdc)`
3625
+ )
3612
3626
  }
3613
3627
  }
3614
3628
 
@@ -3639,12 +3653,7 @@ async function checkK8sYamlFile(abs, root, fail, pass) {
3639
3653
  }
3640
3654
 
3641
3655
  const lines = toLines(raw)
3642
- if (lines.length === 0 || lines[0].trim() === '') {
3643
- fail(`${rel}: перший рядок порожній — потрібен # yaml-language-server: $schema=…`)
3644
- return
3645
- }
3646
-
3647
- const firstLineIsModeline = MODELINE_RE.test(lines[0])
3656
+ const firstLineIsModeline = lines.length > 0 && MODELINE_RE.test(lines[0])
3648
3657
  const bodyForFirstDoc = k8sYamlBodyForDocumentParse(lines)
3649
3658
  const isAlbHttpBackendGroup = k8sYamlFirstDocIsAlbYcHttpBackendGroup(bodyForFirstDoc)
3650
3659
 
@@ -3666,7 +3675,16 @@ async function checkK8sYamlFile(abs, root, fail, pass) {
3666
3675
  }
3667
3676
 
3668
3677
  if (!firstLineIsModeline) {
3669
- fail(`${rel}: перший рядок має бути коментарем # yaml-language-server: $schema=<url> (без префіксів перед #)`)
3678
+ // Modeline опційний: дозволено, якщо публічної схеми для apiVersion/kind немає (k8s.mdc).
3679
+ // Але `# yaml-language-server: $schema=…` дозволено **лише** у першому рядку — якщо він
3680
+ // зустрічається нижче, це порушення (yaml-language-server чекає на нього у заголовку файлу).
3681
+ if (countSchemaModelines(lines) > 0) {
3682
+ fail(
3683
+ `${rel}: рядок # yaml-language-server: $schema=… має бути першим у файлі (без префіксів перед #; k8s.mdc)`
3684
+ )
3685
+ return
3686
+ }
3687
+ pass(`${rel}: без modeline — перевірка $schema пропущена (немає публічної схеми; k8s.mdc)`)
3670
3688
  return
3671
3689
  }
3672
3690