@nitra/cursor 1.11.3 → 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 (86) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/bin/n-cursor.js +40 -6
  3. package/package.json +2 -1
  4. package/rules/abie/js/applies/check.mjs +4 -4
  5. package/rules/abie/js/env_dns/check.mjs +1 -1
  6. package/rules/abie/js/firebase_hosting/check.mjs +1 -1
  7. package/rules/abie/js/hc_pairing/check.mjs +3 -3
  8. package/rules/abie/js/ua_http_route/check.mjs +3 -3
  9. package/rules/abie/js/ua_node_selector/check.mjs +4 -2
  10. package/rules/abie/policy/base_deployment_preem/target.json +1 -5
  11. package/rules/abie/utils/enabled.mjs +1 -1
  12. package/rules/abie/utils/env-dns.mjs +4 -4
  13. package/rules/abie/utils/http-route.mjs +5 -5
  14. package/rules/abie/utils/k8s-tree.mjs +23 -15
  15. package/rules/abie/utils/kustomization-patches.mjs +20 -20
  16. package/rules/abie/utils/overlay-paths.mjs +8 -8
  17. package/rules/abie/utils/yaml.mjs +4 -4
  18. package/rules/adr/adr.mdc +2 -2
  19. package/rules/adr/js/hooks/check.mjs +5 -5
  20. package/rules/docker/docker.mdc +2 -2
  21. package/rules/docker/js/run.mjs +3 -2
  22. package/rules/docker/policy/package_json/package_json.rego +1 -1
  23. package/rules/ga/js/lint.mjs +3 -26
  24. package/rules/hasura/js/internal_urls/check.mjs +1 -1
  25. package/rules/js-bun-redis/js/imports/check.mjs +5 -1
  26. package/rules/js-run/js/runtime/check.mjs +4 -1
  27. package/rules/k8s/js/run.mjs +3 -2
  28. package/rules/k8s/k8s.mdc +2 -4
  29. package/rules/k8s/policy/base_manifest/target.json +1 -5
  30. package/rules/nginx-default-tpl/js/template/check.mjs +4 -2
  31. package/rules/npm-module/js/package_structure/check.mjs +8 -3
  32. package/rules/npm-module/npm-module.mdc +3 -3
  33. package/rules/rego/js/applies/check.mjs +2 -2
  34. package/rules/rego/js/lint.mjs +4 -1
  35. package/rules/rego/policy/package_json/package_json.rego +5 -3
  36. package/rules/rego/rego.mdc +3 -3
  37. package/rules/style-lint/js/tooling/check.mjs +1 -1
  38. package/rules/style-lint/style-lint.mdc +1 -1
  39. package/rules/tauri/js/tooling/check.mjs +3 -1
  40. package/rules/text/js/formatting/check.mjs +8 -24
  41. package/rules/text/js/lint.mjs +34 -0
  42. package/rules/text/js/run-shellcheck.mjs +2 -2
  43. package/rules/text/js/run-v8r.mjs +2 -2
  44. package/rules/text/text.mdc +5 -5
  45. package/schemas/v8r-catalog.json +6 -0
  46. package/scripts/auto-skills.mjs +3 -7
  47. package/scripts/utils/discover-checkable-rules.mjs +4 -3
  48. package/scripts/utils/resolve-target-files.mjs +1 -1
  49. package/scripts/utils/run-lint-step.mjs +33 -0
  50. package/scripts/utils/run-rule.mjs +5 -3
  51. package/skills/abie-clean/SKILL.md +13 -11
  52. package/skills/adr-normalize/SKILL.md +0 -1
  53. package/skills/fix/SKILL.md +3 -7
  54. package/rules/abie/policy/base_deployment_preem/base_deployment_preem_test.rego +0 -60
  55. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +0 -48
  56. package/rules/abie/policy/health_check_policy/health_check_policy_test.rego +0 -99
  57. package/rules/abie/policy/http_route_base/http_route_base_test.rego +0 -64
  58. package/rules/bun/policy/package_json/package_json_test.rego +0 -109
  59. package/rules/docker/policy/lint_docker_yml/lint_docker_yml_test.rego +0 -104
  60. package/rules/docker/policy/package_json/package_json_test.rego +0 -42
  61. package/rules/graphql/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  62. package/rules/image-avif/policy/package_json/package_json_test.rego +0 -69
  63. package/rules/js-lint/policy/package_json/package_json_test.rego +0 -130
  64. package/rules/js-run/policy/jsconfig/jsconfig_test.rego +0 -88
  65. package/rules/k8s/policy/base_kustomization/base_kustomization_test.rego +0 -73
  66. package/rules/k8s/policy/base_manifest/base_manifest_test.rego +0 -94
  67. package/rules/k8s/policy/gateway/gateway_test.rego +0 -122
  68. package/rules/k8s/policy/hasura_configmap/hasura_configmap_test.rego +0 -49
  69. package/rules/k8s/policy/hasura_httproute/hasura_httproute_test.rego +0 -148
  70. package/rules/k8s/policy/hpa_pdb/hpa_pdb_test.rego +0 -101
  71. package/rules/k8s/policy/kustomization/kustomization_test.rego +0 -128
  72. package/rules/k8s/policy/manifest/manifest_test.rego +0 -309
  73. package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml_test.rego +0 -42
  74. package/rules/k8s/policy/svc_yaml/svc_yaml_test.rego +0 -41
  75. package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions_test.rego +0 -30
  76. package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings_test.rego +0 -53
  77. package/rules/npm-module/policy/npm_package_json/npm_package_json_test.rego +0 -81
  78. package/rules/rego/policy/package_json/package_json_test.rego +0 -42
  79. package/rules/rego/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
  80. package/rules/rego/policy/vscode_settings/vscode_settings_test.rego +0 -55
  81. package/rules/style-lint/policy/vscode_extensions/vscode_extensions_test.rego +0 -39
  82. package/rules/style-lint/policy/vscode_settings/vscode_settings_test.rego +0 -49
  83. package/rules/tauri/policy/vscode_extensions/vscode_extensions_test.rego +0 -44
  84. package/rules/text/policy/markdownlint/markdownlint_test.rego +0 -98
  85. package/rules/text/policy/vscode_extensions/vscode_extensions_test.rego +0 -51
  86. package/rules/text/policy/vscode_settings/vscode_settings_test.rego +0 -85
@@ -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 у пакеті)",
@@ -52,7 +52,7 @@ function parseSkillAutoSpec(text) {
52
52
  * Сканує `npm/skills/<id>/auto.md`. Скіли без `auto.md` або з нерозпізнаним
53
53
  * вмістом не потрапляють у результат — їх можна вмикати лише вручну в конфізі.
54
54
  * @param {string} [skillsDir] override для тестів
55
- * @returns {Record<string, SkillAutoSpec>}
55
+ * @returns {Record<string, SkillAutoSpec>} мапа `skillId → spec`
56
56
  */
57
57
  export function discoverSkillAutoActivation(skillsDir = SKILLS_DIR) {
58
58
  if (!existsSync(skillsDir)) return {}
@@ -75,9 +75,7 @@ const SKILL_AUTO_ACTIVATION = discoverSkillAutoActivation()
75
75
  * Стабільний алфавітний порядок скілів з автоактивацією. Експортовано для зворотної
76
76
  * сумісності (попередня версія мала жорстко прописаний `AUTO_SKILL_ORDER`).
77
77
  */
78
- export const AUTO_SKILL_ORDER = Object.freeze(
79
- Object.keys(SKILL_AUTO_ACTIVATION).toSorted((a, b) => a.localeCompare(b))
80
- )
78
+ export const AUTO_SKILL_ORDER = Object.freeze(Object.keys(SKILL_AUTO_ACTIVATION).toSorted((a, b) => a.localeCompare(b)))
81
79
 
82
80
  /**
83
81
  * Похідна view на `SKILL_AUTO_ACTIVATION`: лише скіли з rule-залежностями.
@@ -111,9 +109,7 @@ export function detectAutoSkills({ availableSkills, detectedRules, disableSkills
111
109
 
112
110
  for (const [skillId, spec] of Object.entries(SKILL_AUTO_ACTIVATION)) {
113
111
  if (!normalizedSkills.has(skillId) || disableSkillsSet.has(skillId)) continue
114
- if ('always' in spec) {
115
- detected.add(skillId)
116
- } else if (spec.rules.every(d => detectedRulesSet.has(d))) {
112
+ if ('always' in spec || spec.rules.every(d => detectedRulesSet.has(d))) {
117
113
  detected.add(skillId)
118
114
  }
119
115
  }
@@ -32,8 +32,8 @@ const TEST_SUFFIX = '.test.mjs'
32
32
  /**
33
33
  * @typedef {object} CheckableRule
34
34
  * @property {string} id ідентифікатор правила (імʼя каталогу `rules/<id>/`)
35
- * @property {JsConcern[]} jsConcerns
36
- * @property {PolicyConcern[]} policyConcerns
35
+ * @property {JsConcern[]} jsConcerns JS-концерни правила (алфавітно)
36
+ * @property {PolicyConcern[]} policyConcerns policy-концерни правила (алфавітно)
37
37
  */
38
38
 
39
39
  /**
@@ -52,7 +52,8 @@ async function listJsConcerns(jsDir) {
52
52
  for (const entry of topLevel) {
53
53
  if (!entry.isDirectory() || entry.name === 'utils' || entry.name.startsWith('.')) continue
54
54
  const concernDir = join(jsDir, entry.name)
55
- const files = (await readdir(concernDir))
55
+ const dirContents = await readdir(concernDir)
56
+ const files = dirContents
56
57
  .filter(n => CHECK_FILENAME_RE.test(n) && !n.endsWith(TEST_SUFFIX))
57
58
  .toSorted((a, b) => a.localeCompare(b))
58
59
  if (files.length > 0) {
@@ -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())
@@ -68,7 +69,8 @@ async function runPolicyConcern(bundledRulesDir, ruleId, concernName, walkCache)
68
69
  if (files.length === 0) {
69
70
  if (target.files.required && target.files.single) {
70
71
  const msg =
71
- target.missingMessage ?? `${target.files.single} не існує — створи згідно ${ruleId}.mdc (${ruleId}.${concernName})`
72
+ target.missingMessage ??
73
+ `${target.files.single} не існує — створи згідно ${ruleId}.mdc (${ruleId}.${concernName})`
72
74
  reporter.fail(msg)
73
75
  }
74
76
  return reporter.getExitCode()
@@ -91,7 +93,7 @@ async function runPolicyConcern(bundledRulesDir, ruleId, concernName, walkCache)
91
93
 
92
94
  /**
93
95
  * Запускає одне правило: applies-гейт → JS-концерни → policy-концерни.
94
- * @param {import('./discover-checkable-rules.mjs').CheckableRule} rule
96
+ * @param {import('./discover-checkable-rules.mjs').CheckableRule} rule опис правила з discovery
95
97
  * @param {string} bundledRulesDir абсолютний шлях до `rules/`
96
98
  * @param {Map<string, Promise<string[]>>} walkCache shared cache (один на check-прогон)
97
99
  * @returns {Promise<number>} 0 — OK, 1 — є порушення в одному чи більше концернів
@@ -131,15 +131,15 @@ RUN bun install && bun vite build --mode "prod-$BRANCH" --base="$BASE"
131
131
 
132
132
  Те саме стосується `nginx`-конфігів (`server_name`, `proxy_pass` з ru-доменами), `*.sh`-скриптів та `package.json` scripts (`build:ru`, `deploy:ru`, `prod-ru` тощо).
133
133
 
134
-
135
134
  ## 5. Переклади
136
135
 
137
136
  Замінити переклад з російської на англійську в @nitra/tfm, @nitra/tf та @nitra/tfm-node. Якщо англійська вже є, то прибираємо російську:
138
137
 
139
138
  Приклад БУЛО:
140
139
 
140
+ ```vue
141
141
  <template>
142
- <h5>{{ t`Привіт` }}</h5>
142
+ <h5>{{ t`Привіт` }}</h5>
143
143
  </template>
144
144
 
145
145
  <script setup>
@@ -147,16 +147,18 @@ import tf from '@nitra/tf/webpack'
147
147
 
148
148
  // Translate
149
149
  const tr = {
150
- 'Привіт': 'Привет'
150
+ Привіт: 'Привет'
151
151
  }
152
152
 
153
153
  const t = tf.bind({ tr })
154
154
  </script>
155
+ ```
155
156
 
156
- СТАЛО
157
+ СТАЛО:
157
158
 
159
+ ```vue
158
160
  <template>
159
- <h5>{{ t`Привіт` }}</h5>
161
+ <h5>{{ t`Привіт` }}</h5>
160
162
  </template>
161
163
 
162
164
  <script setup>
@@ -164,19 +166,20 @@ import tf from '@nitra/tf/webpack'
164
166
 
165
167
  // Translate
166
168
  const tr = {
167
- 'Привіт': 'Hello'
169
+ Привіт: 'Hello'
168
170
  }
169
171
 
170
172
  const t = tf.bind({ tr })
171
173
  </script>
174
+ ```
172
175
 
173
176
  або
174
177
 
175
178
  import { tf } from '@nitra/tfm'
176
179
 
177
180
  const tr = {
178
- Так: { ru: 'Да' },
179
- Ні: { ru: 'Нет' }
181
+ Так: { ru: 'Да' },
182
+ Ні: { ru: 'Нет' }
180
183
  }
181
184
 
182
185
  на
@@ -184,11 +187,10 @@ const tr = {
184
187
  import { tf } from '@nitra/tfm'
185
188
 
186
189
  const tr = {
187
- Так: { ru: 'Yes' },
188
- Ні: { ru: 'No' }
190
+ Так: { ru: 'Yes' },
191
+ Ні: { ru: 'No' }
189
192
  }
190
193
 
191
-
192
194
  ## 6. Після очистки
193
195
 
194
196
  - Переконайся, що `kustomization.yaml` у кожній директорії `k8s/` не посилається на видалені overlay або файли.
@@ -51,7 +51,6 @@ description: >-
51
51
  Видалені файли — `delete`-операція. Нові файли `<slug>.md` — `rewrite`. Модифіковані clean-файли — `merge-into`.
52
52
 
53
53
  4. **Прийняти / відкотити:**
54
-
55
54
  - Прийняти все: `git add docs/adr/ && git commit -m "adr: normalize batch"`.
56
55
  - Відкотити конкретний файл: `git checkout -- docs/adr/<file>` (для `rewrite` цього мало — треба ще `git restore --staged` і `rm` нового).
57
56
  - Відкотити весь батч: `git checkout -- docs/adr/ && git clean -f docs/adr/` (видалить і untracked rewrite-результати).
@@ -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
- }