@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.
|
|
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
|
-
|
|
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** — не
|
|
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
|
@@ -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
|
+
}
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє Kubernetes YAML у шляхах з сегментом `k8s` (див. k8s.mdc).
|
|
3
3
|
*
|
|
4
|
-
* Перший рядок `# yaml-language-server: $schema
|
|
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
|
-
|
|
3595
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|