@nitra/cursor 1.11.4 → 1.11.6

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/n-cursor.js +37 -4
  3. package/package.json +2 -1
  4. package/rules/abie/utils/http-route.mjs +1 -1
  5. package/rules/abie/utils/k8s-tree.mjs +9 -10
  6. package/rules/abie/utils/overlay-paths.mjs +1 -1
  7. package/rules/adr/adr.mdc +2 -2
  8. package/rules/adr/js/hooks/check.mjs +5 -5
  9. package/rules/docker/docker.mdc +2 -2
  10. package/rules/docker/js/run.mjs +3 -2
  11. package/rules/docker/policy/package_json/package_json.rego +1 -1
  12. package/rules/ga/js/lint.mjs +3 -26
  13. package/rules/k8s/js/run.mjs +3 -2
  14. package/rules/k8s/k8s.mdc +2 -4
  15. package/rules/npm-module/js/package_structure/check.mjs +2 -2
  16. package/rules/npm-module/npm-module.mdc +3 -3
  17. package/rules/rego/js/lint.mjs +4 -1
  18. package/rules/rego/policy/package_json/package_json.rego +5 -3
  19. package/rules/rego/rego.mdc +3 -3
  20. package/rules/style-lint/js/tooling/check.mjs +1 -1
  21. package/rules/style-lint/style-lint.mdc +1 -1
  22. package/rules/text/js/formatting/check.mjs +8 -24
  23. package/rules/text/js/lint.mjs +34 -0
  24. package/rules/text/js/run-shellcheck.mjs +2 -2
  25. package/rules/text/js/run-v8r.mjs +2 -2
  26. package/rules/text/text.mdc +5 -5
  27. package/schemas/v8r-catalog.json +6 -0
  28. package/scripts/auto-skills.mjs +1 -3
  29. package/scripts/utils/resolve-target-files.mjs +1 -1
  30. package/scripts/utils/run-lint-step.mjs +33 -0
  31. package/scripts/utils/run-rule.mjs +2 -1
  32. package/skills/abie-clean/SKILL.md +9 -5
  33. package/skills/fix/SKILL.md +3 -7
  34. package/rules/abie/policy/base_deployment_preem/base_deployment_preem_test.rego +0 -60
  35. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +0 -48
  36. package/rules/abie/policy/health_check_policy/health_check_policy_test.rego +0 -99
  37. package/rules/abie/policy/http_route_base/http_route_base_test.rego +0 -64
  38. package/rules/bun/policy/package_json/package_json_test.rego +0 -109
  39. package/rules/docker/policy/lint_docker_yml/lint_docker_yml_test.rego +0 -104
  40. package/rules/docker/policy/package_json/package_json_test.rego +0 -42
  41. package/rules/graphql/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  42. package/rules/image-avif/policy/package_json/package_json_test.rego +0 -69
  43. package/rules/js-lint/policy/package_json/package_json_test.rego +0 -130
  44. package/rules/js-run/policy/jsconfig/jsconfig_test.rego +0 -88
  45. package/rules/k8s/policy/base_kustomization/base_kustomization_test.rego +0 -73
  46. package/rules/k8s/policy/base_manifest/base_manifest_test.rego +0 -94
  47. package/rules/k8s/policy/gateway/gateway_test.rego +0 -122
  48. package/rules/k8s/policy/hasura_configmap/hasura_configmap_test.rego +0 -49
  49. package/rules/k8s/policy/hasura_httproute/hasura_httproute_test.rego +0 -148
  50. package/rules/k8s/policy/hpa_pdb/hpa_pdb_test.rego +0 -101
  51. package/rules/k8s/policy/kustomization/kustomization_test.rego +0 -128
  52. package/rules/k8s/policy/manifest/manifest_test.rego +0 -309
  53. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml_test.rego +0 -42
  54. package/rules/k8s/policy/svc_yaml/svc_yaml_test.rego +0 -41
  55. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions_test.rego +0 -30
  56. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings_test.rego +0 -53
  57. package/rules/npm-module/policy/npm_package_json/npm_package_json_test.rego +0 -81
  58. package/rules/rego/policy/package_json/package_json_test.rego +0 -42
  59. package/rules/rego/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  60. package/rules/rego/policy/vscode_settings/vscode_settings_test.rego +0 -55
  61. package/rules/style-lint/policy/vscode_extensions/vscode_extensions_test.rego +0 -39
  62. package/rules/style-lint/policy/vscode_settings/vscode_settings_test.rego +0 -49
  63. package/rules/tauri/policy/vscode_extensions/vscode_extensions_test.rego +0 -44
  64. package/rules/text/policy/markdownlint/markdownlint_test.rego +0 -98
  65. package/rules/text/policy/vscode_extensions/vscode_extensions_test.rego +0 -51
  66. package/rules/text/policy/vscode_settings/vscode_settings_test.rego +0 -85
@@ -0,0 +1,34 @@
1
+ /**
2
+ * CLI-обгортка над канонічним `lint-text` (text.mdc): послідовно
3
+ * 1) `cspell .` — перевірка правопису з `@nitra/cspell-dict`;
4
+ * 2) `runShellcheckText()` — авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
5
+ * 3) `bunx markdownlint-cli2 --fix "**\/*.md" "**\/*.mdc"` — авто-фікс Markdown;
6
+ * 4) `runV8rWithGlobs()` — schema-валідація json/json5/yaml/yml/toml через v8r з каталогом `@nitra/cursor`.
7
+ *
8
+ * Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
9
+ * Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
10
+ */
11
+ import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
12
+ import { runShellcheckText } from './run-shellcheck.mjs'
13
+ import { runV8rWithGlobs } from './run-v8r.mjs'
14
+
15
+ /**
16
+ * Виконує канонічний `lint-text`: cspell → run-shellcheck → markdownlint-cli2 → run-v8r.
17
+ * Першу помилку повертаємо як код виходу; наступні кроки не запускаються.
18
+ * Усі кроки синхронні (`spawnSync` + sync-ентрі з пакета), тому функція не async.
19
+ * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
20
+ */
21
+ export function runLintTextCli() {
22
+ const cspellCode = runLintStep('cspell', 'npx', ['cspell', '.'])
23
+ if (cspellCode !== 0) return cspellCode
24
+
25
+ console.log('\n▶ shellcheck (авто-фікс + фінальна перевірка *.sh)')
26
+ const shellcheckCode = runShellcheckText()
27
+ if (shellcheckCode !== 0) return shellcheckCode
28
+
29
+ const markdownlintCode = runLintStep('markdownlint', 'bunx', ['markdownlint-cli2', '--fix', '**/*.md', '**/*.mdc'])
30
+ if (markdownlintCode !== 0) return markdownlintCode
31
+
32
+ console.log('\n▶ v8r (schema-валідація json/json5/yaml/yml/toml)')
33
+ return runV8rWithGlobs()
34
+ }
@@ -82,7 +82,7 @@ export function listShellScriptPaths(cwd) {
82
82
  return []
83
83
  }
84
84
  const files = ls.stdout.split('\0').filter(Boolean)
85
- return new Set(files).toSorted()
85
+ return [...new Set(files)].toSorted()
86
86
  }
87
87
  }
88
88
 
@@ -90,7 +90,7 @@ export function listShellScriptPaths(cwd) {
90
90
  cwd,
91
91
  exclude: p => p.includes('node_modules') || p.startsWith(`node_modules/`) || p.split('/').includes('node_modules')
92
92
  })
93
- return new Set(fromGlob.map(p => p.replaceAll('\\', '/'))).toSorted()
93
+ return [...new Set(fromGlob.map(p => p.replaceAll('\\', '/')))].toSorted()
94
94
  }
95
95
 
96
96
  /**
@@ -24,8 +24,8 @@ import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
24
24
  /** Типові glob-и для форматів, які обробляє v8r (див. опис CLI v8r). */
25
25
  export const DEFAULT_V8R_GLOBS = ['**/*.json', '**/*.json5', '**/*.yml', '**/*.yaml', '**/*.toml']
26
26
 
27
- /** Абсолютний шлях до `schemas/v8r-catalog.json` поруч з цим скриптом у пакеті `@nitra/cursor`. */
28
- export const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '../schemas/v8r-catalog.json')
27
+ /** Абсолютний шлях до `schemas/v8r-catalog.json` у корені пакета `@nitra/cursor` (`npm/schemas/`). */
28
+ export const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '../../../schemas/v8r-catalog.json')
29
29
 
30
30
  /**
31
31
  * Повертає шлях до каталогу схем v8r для пакета (для тестів і діагностики).
@@ -113,14 +113,14 @@ version: '1.26'
113
113
 
114
114
  **`package.json`:** скрипт **`lint-text`** і devDependencies **`@nitra/cspell-dict`** (**`^2.0.0`** або новіший у лінії 2.x) — з **2.0.0** у пакет транзитивно входять типові **`@cspell/dict-*`**, тому **не** додавай їх окремо в корінь. **`markdownlint-cli2`** викликай у `lint-text` лише через **`bunx markdownlint-cli2`**, не додавай пакет до devDependencies. **`v8r`** лише через **`bunx v8r`** (зазвичай **`bunx v8r`**), не в devDependencies. Окремий пакет **`markdownlint`** не потрібний.
115
115
 
116
- **shellcheck:** інструмент лише в **`PATH`** (як у **ga.mdc** для `lint-ga`), **не** додавай до `dependencies` / `devDependencies`. Якщо `shellcheck` відсутній — встанови локально (**macOS:** `brew install shellcheck`; **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`; **Arch:** `sudo pacman -S shellcheck`). У **`lint-text`** після **`cspell`** викликай **`bun ./npm/scripts/run-shellcheck-text.mjs`** (у споживачі після синку — `node_modules/@nitra/cursor/scripts/run-shellcheck-text.mjs`): під капотом циклічно **`shellcheck -f diff`** і **`patch -p1`** для авто-виправлень, потім повний прогін **`shellcheck`** по всіх tracked `*.sh` (у git) або по `**/*.sh` без `node_modules`. Потрібен також **`patch`** у `PATH` (на macOS зазвичай уже є).
116
+ **shellcheck:** інструмент лише в **`PATH`** (як у **ga.mdc** для `lint-ga`), **не** додавай до `dependencies` / `devDependencies`. Якщо `shellcheck` відсутній — встанови локально (**macOS:** `brew install shellcheck`; **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`; **Arch:** `sudo pacman -S shellcheck`). Канонічний **`lint-text`** делегує до CLI **`n-cursor lint-text`** (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`): після **`cspell .`** виконується **`runShellcheckText()`** з пакета — циклічно **`shellcheck -f diff`** і **`patch -p1`** для авто-виправлень, потім повний прогін **`shellcheck`** по всіх tracked `*.sh` (у git) або по `**/*.sh` без `node_modules`. Потрібен також **`patch`** у `PATH` (на macOS зазвичай уже є).
117
117
 
118
- У v8r **немає** прапорця тихого режиму; рекомендовано скрипт **`run-v8r.mjs`** з репозиторію пакета `@nitra/cursor` (`npm/scripts/run-v8r.mjs`): один виклик у `lint-text` — під капотом послідовні **`bunx v8r`** для кожного типу (**json**, **json5**, **yml**, **yaml**, **toml**), бо один процес v8r з кількома глобами падає з **98**, якщо хоч один glob порожній, і тоді інші розширення не перевіряються. Вивід при кодах **0** і **98** не показується. Каталог схем **`schemas/v8r-catalog.json`** пакета `@nitra/cursor` скрипт підставляє в v8r сам. За бажання можна передати власні glob-и аргументами скрипта. Шлях до скрипта: `./npm/scripts/…`, `./scripts/…` після копіювання, або `node_modules/@nitra/cursor/scripts/…`.
118
+ У v8r **немає** прапорця тихого режиму; CLI-обгортка **`n-cursor lint-text`** на четвертому кроці викликає **`runV8rWithGlobs()`** з пакета (реалізація `npm/rules/text/js/run-v8r.mjs`): під капотом послідовні **`bunx v8r`** для кожного типу (**json**, **json5**, **yml**, **yaml**, **toml**), бо один процес v8r з кількома глобами падає з **98**, якщо хоч один glob порожній, і тоді інші розширення не перевіряються. Вивід при кодах **0** і **98** не показується. Каталог схем **`schemas/v8r-catalog.json`** пакета `@nitra/cursor` обгортка підставляє в v8r сама.
119
119
 
120
120
  ```json title="package.json"
121
121
  {
122
122
  "scripts": {
123
- "lint-text": "npx cspell . && bun ./npm/scripts/run-shellcheck-text.mjs && bunx markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdc\" && bun ./npm/scripts/run-v8r.mjs"
123
+ "lint-text": "n-cursor lint-text"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@nitra/cspell-dict": "^2.1.0"
@@ -239,7 +239,7 @@ jobs:
239
239
  ```json title="package.json"
240
240
  {
241
241
  "scripts": {
242
- "lint-text": "npx cspell . && bun ./npm/scripts/run-shellcheck-text.mjs && bunx markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdc\" && bun ./npm/scripts/run-v8r.mjs"
242
+ "lint-text": "n-cursor lint-text"
243
243
  },
244
244
  "devDependencies": {
245
245
  "@nitra/cspell-dict": "^2.1.0"
@@ -256,7 +256,7 @@ jobs:
256
256
  ```json title="package.json"
257
257
  {
258
258
  "scripts": {
259
- "lint-text": "npx cspell . && bun ./npm/scripts/run-shellcheck-text.mjs && bunx markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdc\" && bun ./npm/scripts/run-v8r.mjs"
259
+ "lint-text": "n-cursor lint-text"
260
260
  },
261
261
  "devDependencies": {
262
262
  "@nitra/cspell-dict": "^2.1.0"
@@ -8,6 +8,12 @@
8
8
  "url": "https://unpkg.com/@nitra/cursor/schemas/n-cursor.json",
9
9
  "fileMatch": [".n-cursor.json", "**/.n-cursor.json", ".n-cursor.example.json", "**/.n-cursor.example.json"]
10
10
  },
11
+ {
12
+ "name": "rego-target.json",
13
+ "description": "Маніфест rego-полісі (npm/rules/<id>/policy/<name>/target.json) — які файли проєкту фідити в conftest",
14
+ "url": "https://unpkg.com/@nitra/cursor/schemas/target.json",
15
+ "fileMatch": ["npm/rules/*/policy/*/target.json", "rules/*/policy/*/target.json"]
16
+ },
11
17
  {
12
18
  "name": "schema-catalog",
13
19
  "description": "Каталог схем Schema Store (v8r-catalog.json у пакеті)",
@@ -109,9 +109,7 @@ export function detectAutoSkills({ availableSkills, detectedRules, disableSkills
109
109
 
110
110
  for (const [skillId, spec] of Object.entries(SKILL_AUTO_ACTIVATION)) {
111
111
  if (!normalizedSkills.has(skillId) || disableSkillsSet.has(skillId)) continue
112
- if ('always' in spec) {
113
- detected.add(skillId)
114
- } else if (spec.rules.every(d => detectedRulesSet.has(d))) {
112
+ if ('always' in spec || spec.rules.every(d => detectedRulesSet.has(d))) {
115
113
  detected.add(skillId)
116
114
  }
117
115
  }
@@ -61,7 +61,7 @@ async function walkAllRelative(root, ignorePaths) {
61
61
 
62
62
  /**
63
63
  * Витягує (або обчислює і кешує) список усіх файлів у дереві для заданого набору ignore-шляхів.
64
- * Кеш — мapа `signature → Promise<string[]>`, тож паралельні виклики одного й того ж набору
64
+ * Кеш — мапа `signature → Promise<string[]>`, тож паралельні виклики одного й того ж набору
65
65
  * чекають один обхід.
66
66
  * @param {string} root абсолютний корінь репозиторію
67
67
  * @param {string[]} ignorePaths абсолютні posix-шляхи виключених каталогів
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Спільний хелпер для CLI-обгорток `lint-<rule>`: запускає один крок ланцюжка з логуванням
3
+ * команди і прокидає stdout/stderr на користувацькі stream-и (`stdio: 'inherit'`), щоб виглядало
4
+ * як прямий виклик у shell.
5
+ *
6
+ * Використовується з `n-cursor lint-ga`, `n-cursor lint-text` та інших підкоманд, щоб не дублювати
7
+ * одну й ту саму обгортку у кожному `rules/<id>/js/lint.mjs` (jscpd-clone).
8
+ */
9
+ import { spawnSync } from 'node:child_process'
10
+
11
+ import { resolveCmd } from './resolve-cmd.mjs'
12
+
13
+ /**
14
+ * Запускає один крок lint-обгортки: резолвить `cmd` у PATH і `spawnSync` із успадкованим stdio.
15
+ * @param {string} title заголовок для логу (наприклад `actionlint`)
16
+ * @param {string} cmd ім'я команди (`bunx`, `uvx`, `npx`, …)
17
+ * @param {string[]} args аргументи команди
18
+ * @returns {number} код виходу дочірнього процесу: 0 — OK, 127 — команда відсутня в PATH, інше — помилка
19
+ */
20
+ export function runLintStep(title, cmd, args) {
21
+ console.log(`\n▶ ${title}: ${cmd} ${args.join(' ')}`)
22
+ const resolved = resolveCmd(cmd)
23
+ if (!resolved) {
24
+ console.error(`❌ ${cmd} не знайдено в PATH (${title}).`)
25
+ return 127
26
+ }
27
+ const r = spawnSync(resolved, args, { stdio: 'inherit', env: process.env })
28
+ if (r.error) {
29
+ console.error(`❌ Не вдалося запустити ${cmd}: ${r.error.message}`)
30
+ return 1
31
+ }
32
+ return r.status ?? 1
33
+ }
@@ -7,7 +7,7 @@
7
7
  * 2. **JS-концерни** — кожен `check*.mjs` у `js/<concern>/`. Concern `applies` теж може мати
8
8
  * `check()` для друку контексту (його `applies()` уже відпрацював на кроці 1, він не повторюється).
9
9
  * 3. **Policy-концерни** — кожен `policy/<concern>/target.json` через `runConftestBatch`.
10
- * Реcолвер `resolveTargetFiles` ділить cache (`walkCache`) між концернами.
10
+ * Резолвер `resolveTargetFiles` ділить cache (`walkCache`) між концернами.
11
11
  *
12
12
  * Кожен concern має власний `createCheckReporter` — їхні exit-коди OR-яться в один на рівні правила.
13
13
  * Це дає той самий 0/1 контракт, що й попередня модель «один check.mjs на правило».
@@ -45,6 +45,7 @@ async function evaluateAppliesGate(bundledRulesDir, rule) {
45
45
  const concern = rule.jsConcerns.find(c => c.name === APPLIES_CONCERN_NAME)
46
46
  if (!concern || concern.files.length === 0) return true
47
47
  const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern, concern.files[0])
48
+ // eslint-disable-next-line no-unsanitized/method -- path будується з discovered concern/file, які пройшли regex CHECK_FILENAME_RE
48
49
  const mod = await import(path)
49
50
  if (typeof mod.applies !== 'function') return true
50
51
  return Boolean(await mod.applies())
@@ -137,8 +137,9 @@ RUN bun install && bun vite build --mode "prod-$BRANCH" --base="$BASE"
137
137
 
138
138
  Приклад БУЛО:
139
139
 
140
+ ```vue
140
141
  <template>
141
- <h5>{{ t`Привіт` }}</h5>
142
+ <h5>{{ t`Привіт` }}</h5>
142
143
  </template>
143
144
 
144
145
  <script setup>
@@ -146,16 +147,18 @@ import tf from '@nitra/tf/webpack'
146
147
 
147
148
  // Translate
148
149
  const tr = {
149
- 'Привіт': 'Привет'
150
+ Привіт: 'Привет'
150
151
  }
151
152
 
152
153
  const t = tf.bind({ tr })
153
154
  </script>
155
+ ```
154
156
 
155
- СТАЛО
157
+ СТАЛО:
156
158
 
159
+ ```vue
157
160
  <template>
158
- <h5>{{ t`Привіт` }}</h5>
161
+ <h5>{{ t`Привіт` }}</h5>
159
162
  </template>
160
163
 
161
164
  <script setup>
@@ -163,11 +166,12 @@ import tf from '@nitra/tf/webpack'
163
166
 
164
167
  // Translate
165
168
  const tr = {
166
- 'Привіт': 'Hello'
169
+ Привіт: 'Hello'
167
170
  }
168
171
 
169
172
  const t = tf.bind({ tr })
170
173
  </script>
174
+ ```
171
175
 
172
176
  або
173
177
 
@@ -36,17 +36,13 @@ bun i
36
36
  oxfmt .
37
37
  ```
38
38
 
39
- 6. **Лінтери**знайди в кореневому `package.json` всі скрипти з префіксом `lint-` і запусти кожен:
39
+ 6. **Лінт коду** викликай скіл **`/n-lint`** (один канонічний прогон `bun run lint`):
40
40
 
41
41
  ```bash
42
- bun run lint-js
43
- bun run lint-text
44
- bun run lint-style
42
+ bun run lint
45
43
  ```
46
44
 
47
- На помилках: auto-fix (якщо є), інакше правки в коді/конфігах; повторюй доки скрипт завершується з `0`.
48
-
49
- Підсумок: Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.
45
+ Лінт-логіку (auto-fix, рефакторинг під `sonarjs/cognitive-complexity`, обмеження `cspell`/`jscpd`/`knip`, заборона паралельних запусків ESLint) **не дублюй** тут вона повністю у скілі **`/n-lint`**. Цей скіл (`/n-fix`) відповідає лише за **структуру** проєкту (правила `.cursor/rules/` + `npx @nitra/cursor check`); за **чистоту коду** відповідає **`/n-lint`**.
50
46
 
51
47
  7. **Верифікація** — перевір що все виправлено:
52
48
 
@@ -1,60 +0,0 @@
1
- # Тести для `abie.base_deployment_preem`. Запуск:
2
- # conftest verify -p npm/policy/abie/base_deployment_preem
3
- package abie.base_deployment_preem_test
4
-
5
- import rego.v1
6
-
7
- import data.abie.base_deployment_preem
8
-
9
- mk_deployment(node_selector) := {
10
- "apiVersion": "apps/v1",
11
- "kind": "Deployment",
12
- "metadata": {"name": "api", "namespace": "dev"},
13
- "spec": {"template": {"spec": object.union(
14
- {"containers": [{"name": "main", "image": "x"}]},
15
- {"nodeSelector": node_selector},
16
- )}},
17
- }
18
-
19
- test_deny_no_node_selector if {
20
- input_doc := {
21
- "apiVersion": "apps/v1",
22
- "kind": "Deployment",
23
- "metadata": {"name": "api"},
24
- "spec": {"template": {"spec": {"containers": [{"name": "main", "image": "x"}]}}},
25
- }
26
- count(base_deployment_preem.deny) > 0 with input as input_doc
27
- }
28
-
29
- test_deny_node_selector_without_preem if {
30
- count(base_deployment_preem.deny) > 0 with input as mk_deployment({"role": "worker"})
31
- }
32
-
33
- test_deny_preem_false if {
34
- count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": false})
35
- }
36
-
37
- test_deny_preem_string_false if {
38
- count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": "false"})
39
- }
40
-
41
- test_allow_preem_boolean_true if {
42
- count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": true})
43
- }
44
-
45
- test_allow_preem_string_true if {
46
- count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "true"})
47
- }
48
-
49
- test_allow_preem_string_uppercase_true if {
50
- count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "TRUE"})
51
- }
52
-
53
- # Не Deployment — пакет не діє (дзеркало JS-предиката).
54
- test_allow_non_deployment if {
55
- count(base_deployment_preem.deny) == 0 with input as {
56
- "apiVersion": "v1",
57
- "kind": "ConfigMap",
58
- "metadata": {"name": "x"},
59
- }
60
- }
@@ -1,48 +0,0 @@
1
- # Тести для `abie.clean_merged_ignore_branches`. Запуск:
2
- # conftest verify -p npm/policy/abie/clean_merged_ignore_branches
3
- package abie.clean_merged_ignore_branches_test
4
-
5
- import rego.v1
6
-
7
- import data.abie.clean_merged_ignore_branches
8
-
9
- # Каркас workflow з одним job, що містить step із заданим with.
10
- mk_workflow(step_with) := {"jobs": {"cleanup": {"steps": [{
11
- "uses": "phpdocker-io/github-actions-delete-abandoned-branches@v2",
12
- "with": step_with,
13
- }]}}}
14
-
15
- other_step_workflow := {"jobs": {"cleanup": {"steps": [{"uses": "actions/checkout@v6"}]}}}
16
-
17
- # Workflow без потрібного кроку.
18
- test_deny_step_missing if {
19
- count(clean_merged_ignore_branches.deny) > 0 with input as other_step_workflow
20
- }
21
-
22
- test_deny_ignore_branches_missing if {
23
- count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({})
24
- }
25
-
26
- test_deny_missing_required_token if {
27
- count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "dev"})
28
- }
29
-
30
- test_deny_completely_wrong_tokens if {
31
- count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "main,develop"})
32
- }
33
-
34
- test_allow_required_tokens if {
35
- count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": "dev,ua"})
36
- }
37
-
38
- # Регістронезалежне порівняння і пропуск пробілів.
39
- test_allow_uppercase_with_spaces if {
40
- count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": " DEV , UA "})
41
- }
42
-
43
- extra_branches_workflow := mk_workflow({"ignore_branches": "dev,ua,main,release/*"})
44
-
45
- # Додаткові гілки після обов'язкових — дозволено.
46
- test_allow_extra_branches if {
47
- count(clean_merged_ignore_branches.deny) == 0 with input as extra_branches_workflow
48
- }
@@ -1,99 +0,0 @@
1
- # Тести для `abie.health_check_policy`. Запуск:
2
- # conftest verify -p npm/policy/abie/health_check_policy
3
- package abie.health_check_policy_test
4
-
5
- import rego.v1
6
-
7
- import data.abie.health_check_policy
8
-
9
- valid_hcp := {
10
- "apiVersion": "networking.gke.io/v1",
11
- "kind": "HealthCheckPolicy",
12
- "metadata": {"name": "api"},
13
- "spec": {
14
- "default": {"config": {
15
- "type": "HTTP",
16
- "httpHealthCheck": {"requestPath": "/healthz", "port": 8080},
17
- }},
18
- "targetRef": {"group": "", "kind": "Service", "name": "api-hl"},
19
- },
20
- }
21
-
22
- # ── happy path ────────────────────────────────────────────────────────────
23
-
24
- test_allow_canonical if {
25
- count(health_check_policy.deny) == 0 with input as valid_hcp
26
- }
27
-
28
- # ── apiVersion ────────────────────────────────────────────────────────────
29
-
30
- test_deny_wrong_api_version if {
31
- bad := json.patch(valid_hcp, [{"op": "replace", "path": "/apiVersion", "value": "networking.gke.io/v1beta1"}])
32
- count(health_check_policy.deny) > 0 with input as bad
33
- }
34
-
35
- # ── metadata.name ─────────────────────────────────────────────────────────
36
-
37
- test_deny_empty_name if {
38
- bad := json.patch(valid_hcp, [{"op": "replace", "path": "/metadata/name", "value": ""}])
39
- count(health_check_policy.deny) > 0 with input as bad
40
- }
41
-
42
- # ── spec.default.config.type ──────────────────────────────────────────────
43
-
44
- test_deny_config_type_not_http if {
45
- bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/default/config/type", "value": "TCP"}])
46
- count(health_check_policy.deny) > 0 with input as bad
47
- }
48
-
49
- # ── requestPath ───────────────────────────────────────────────────────────
50
-
51
- test_deny_empty_request_path if {
52
- bad := json.patch(valid_hcp, [{
53
- "op": "replace",
54
- "path": "/spec/default/config/httpHealthCheck/requestPath",
55
- "value": "",
56
- }])
57
- count(health_check_policy.deny) > 0 with input as bad
58
- }
59
-
60
- test_deny_request_path_without_slash if {
61
- bad := json.patch(valid_hcp, [{
62
- "op": "replace",
63
- "path": "/spec/default/config/httpHealthCheck/requestPath",
64
- "value": "healthz",
65
- }])
66
- count(health_check_policy.deny) > 0 with input as bad
67
- }
68
-
69
- # ── port ──────────────────────────────────────────────────────────────────
70
-
71
- test_deny_port_not_8080 if {
72
- bad := json.patch(valid_hcp, [{
73
- "op": "replace",
74
- "path": "/spec/default/config/httpHealthCheck/port",
75
- "value": 9090,
76
- }])
77
- count(health_check_policy.deny) > 0 with input as bad
78
- }
79
-
80
- # ── targetRef ─────────────────────────────────────────────────────────────
81
-
82
- test_deny_target_ref_kind_not_service if {
83
- bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/kind", "value": "Gateway"}])
84
- count(health_check_policy.deny) > 0 with input as bad
85
- }
86
-
87
- test_deny_target_ref_name_without_hl if {
88
- bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/name", "value": "api"}])
89
- count(health_check_policy.deny) > 0 with input as bad
90
- }
91
-
92
- # Не HCP — пакет не діє.
93
- test_allow_other_kind if {
94
- count(health_check_policy.deny) == 0 with input as {
95
- "apiVersion": "v1",
96
- "kind": "ConfigMap",
97
- "metadata": {"name": "x"},
98
- }
99
- }
@@ -1,64 +0,0 @@
1
- # Тести для `abie.http_route_base`. Запуск:
2
- # conftest verify -p npm/policy/abie/http_route_base
3
- package abie.http_route_base_test
4
-
5
- import rego.v1
6
-
7
- import data.abie.http_route_base
8
-
9
- mk_route(hostnames) := {
10
- "apiVersion": "gateway.networking.k8s.io/v1",
11
- "kind": "HTTPRoute",
12
- "metadata": {"name": "r", "namespace": "dev"},
13
- "spec": {"hostnames": hostnames},
14
- }
15
-
16
- # ── allow ────────────────────────────────────────────────────────────────
17
-
18
- test_allow_apex if {
19
- count(http_route_base.deny) == 0 with input as mk_route(["aiml.live"])
20
- }
21
-
22
- test_allow_subdomain if {
23
- count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live"])
24
- }
25
-
26
- test_allow_wildcard if {
27
- count(http_route_base.deny) == 0 with input as mk_route(["*.aiml.live"])
28
- }
29
-
30
- test_allow_uppercase_apex if {
31
- count(http_route_base.deny) == 0 with input as mk_route(["AIML.LIVE"])
32
- }
33
-
34
- test_allow_multiple_subdomains if {
35
- count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live", "admin.aiml.live"])
36
- }
37
-
38
- # ── deny ─────────────────────────────────────────────────────────────────
39
-
40
- test_deny_other_apex if {
41
- count(http_route_base.deny) > 0 with input as mk_route(["example.com"])
42
- }
43
-
44
- test_deny_wrong_subdomain if {
45
- count(http_route_base.deny) > 0 with input as mk_route(["api.example.com"])
46
- }
47
-
48
- test_deny_mixed_one_bad if {
49
- count(http_route_base.deny) > 0 with input as mk_route(["api.aiml.live", "evil.com"])
50
- }
51
-
52
- test_deny_aiml_live_substring if {
53
- # "aiml.live.example.com" не має закінчуватись на ".aiml.live" — це інший домен.
54
- count(http_route_base.deny) > 0 with input as mk_route(["aiml.live.example.com"])
55
- }
56
-
57
- # Не HTTPRoute — пакет не діє.
58
- test_allow_non_httproute if {
59
- count(http_route_base.deny) == 0 with input as {
60
- "apiVersion": "v1",
61
- "kind": "Service",
62
- "metadata": {"name": "x"},
63
- }
64
- }
@@ -1,109 +0,0 @@
1
- # Тести для `bun.package_json`. Запуск:
2
- # conftest verify -p npm/policy/bun/package_json
3
- package bun.package_json_test
4
-
5
- import rego.v1
6
-
7
- import data.bun.package_json
8
-
9
- valid_pkg := {
10
- "name": "n-cursor",
11
- "devDependencies": {"@nitra/eslint-config": "^3.9.2"},
12
- }
13
-
14
- # ── happy path ────────────────────────────────────────────────────────────
15
-
16
- test_allow_minimal if {
17
- count(package_json.deny) == 0 with input as valid_pkg
18
- }
19
-
20
- test_allow_multiple_nitra_deps if {
21
- pkg := json.patch(valid_pkg, [{
22
- "op": "replace",
23
- "path": "/devDependencies",
24
- "value": {"@nitra/eslint-config": "^3.9.2", "@nitra/cspell-dict": "^2.0.0", "@nitra/stylelint-config": "^1.0.0"},
25
- }])
26
- count(package_json.deny) == 0 with input as pkg
27
- }
28
-
29
- test_allow_no_dev_dependencies if {
30
- pkg := json.patch(valid_pkg, [{"op": "remove", "path": "/devDependencies"}])
31
- count(package_json.deny) == 0 with input as pkg
32
- }
33
-
34
- # ── deny: devDependencies лише @nitra/* ──────────────────────────────────
35
-
36
- test_deny_non_nitra_devdep if {
37
- cases := [
38
- {"@cspell/dict-uk-ua": "^2.0.0"},
39
- {"@cspell/cspell-lib": "^9.0.0"},
40
- {"lodash": "*"},
41
- {"@types/node": "^24.0.0"},
42
- ]
43
- some bad in cases
44
- pkg := json.patch(valid_pkg, [{"op": "replace", "path": "/devDependencies", "value": bad}])
45
- count(package_json.deny) > 0 with input as pkg
46
- }
47
-
48
- test_deny_mixed_dev_deps_only_flags_non_nitra if {
49
- pkg := json.patch(valid_pkg, [{
50
- "op": "replace",
51
- "path": "/devDependencies",
52
- "value": {"@nitra/eslint-config": "^3.9.2", "lodash": "*"},
53
- }])
54
- some msg in package_json.deny with input as pkg
55
- contains(msg, "lodash")
56
- }
57
-
58
- # ── deny: packageManager ─────────────────────────────────────────────────
59
-
60
- test_deny_package_manager_field if {
61
- pkg := json.patch(valid_pkg, [{"op": "add", "path": "/packageManager", "value": "pnpm@9.0.0"}])
62
- count(package_json.deny) > 0 with input as pkg
63
- }
64
-
65
- # ── deny: dependencies у кореневому ──────────────────────────────────────
66
-
67
- test_deny_root_dependencies_present if {
68
- pkg := json.patch(valid_pkg, [{"op": "add", "path": "/dependencies", "value": {"lodash": "*"}}])
69
- count(package_json.deny) > 0 with input as pkg
70
- }
71
-
72
- test_deny_empty_dependencies_object if {
73
- pkg := json.patch(valid_pkg, [{"op": "add", "path": "/dependencies", "value": {}}])
74
- count(package_json.deny) > 0 with input as pkg
75
- }
76
-
77
- # ── deny: агрегований lint ───────────────────────────────────────────────
78
-
79
- test_deny_lint_prefixed_without_aggregate if {
80
- pkg := json.patch(valid_pkg, [{"op": "add", "path": "/scripts", "value": {"lint-js": "echo"}}])
81
- count(package_json.deny) > 0 with input as pkg
82
- }
83
-
84
- test_allow_lint_aggregate_calls_subscript_and_oxfmt if {
85
- pkg := json.patch(valid_pkg, [{
86
- "op": "add",
87
- "path": "/scripts",
88
- "value": {"lint-js": "echo", "lint": "bun run lint-js && oxfmt ."},
89
- }])
90
- count(package_json.deny) == 0 with input as pkg
91
- }
92
-
93
- test_deny_lint_aggregate_missing_oxfmt if {
94
- pkg := json.patch(valid_pkg, [{
95
- "op": "add",
96
- "path": "/scripts",
97
- "value": {"lint-js": "echo", "lint": "bun run lint-js"},
98
- }])
99
- count(package_json.deny) > 0 with input as pkg
100
- }
101
-
102
- test_deny_lint_aggregate_missing_subscript_via_bun_run if {
103
- pkg := json.patch(valid_pkg, [{
104
- "op": "add",
105
- "path": "/scripts",
106
- "value": {"lint-js": "echo", "lint-text": "echo", "lint": "bun run lint-js && oxfmt ."},
107
- }])
108
- count(package_json.deny) > 0 with input as pkg
109
- }