@nitra/cursor 1.9.0 → 1.9.1

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,16 @@
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.1] - 2026-05-11
8
+
9
+ ### Added
10
+
11
+ - **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).
12
+
13
+ ### Fixed
14
+
15
+ - **`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 — ні.
16
+
7
17
  ## [1.9.0] - 2026-05-11
8
18
 
9
19
  ### 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
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
+ }
@@ -309,6 +309,8 @@ const YANNH_GROUPS = new Set([
309
309
  'storagemigration.k8s.io'
310
310
  ])
311
311
 
312
+ const GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'
313
+
312
314
  const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
313
315
  const PATH_SPLIT_RE = /[/\\]/u
314
316
  const YAML_EXTENSION_RE = /\.ya?ml$/iu