@nitra/cursor 1.11.4 → 1.11.7

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 +35 -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 +6 -14
  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
@@ -147,36 +147,20 @@ async function checkPackageJsonText(passFn, failFn) {
147
147
  }
148
148
 
149
149
  /**
150
- * Перевіряє скрипт lint-text на коректність v8r-виклику.
151
- * @param {unknown} lintText параметр lintText
150
+ * Перевіряє скрипт lint-text: канонічний `n-cursor lint-text` (CLI пакета `@nitra/cursor` робить
151
+ * `cspell` `runShellcheckText()` `bunx markdownlint-cli2 --fix` → `runV8rWithGlobs()`).
152
+ * Дозволено whitespace навколо команди.
153
+ * @param {unknown} lintText значення `scripts.lint-text` з package.json
152
154
  * @param {(msg: string) => void} passFn callback при успішній перевірці
153
155
  * @param {(msg: string) => void} failFn callback при помилці
154
156
  */
155
157
  function checkLintTextScript(lintText, passFn, failFn) {
156
- const lt = typeof lintText === 'string' ? lintText : ''
157
- const v8rCalls = (lt.match(/bunx v8r/g) || []).length
158
- const quietCalls = (lt.match(/run-v8r?\.mjs/g) || []).length
159
- const eq98Hints = (lt.match(/eq 98/g) || []).length
160
- const legacyV8r = v8rCalls >= 4 && eq98Hints >= 4
161
- const quietBundled = quietCalls === 1
162
- const quietLegacy4x = quietCalls >= 4
163
- const v8rTextOk = legacyV8r || quietBundled || quietLegacy4x
164
- const globsRequired = legacyV8r || quietLegacy4x
165
- const globsOk =
166
- lt.includes('**/*.json') && lt.includes('**/*.yml') && lt.includes('**/*.yaml') && lt.includes('**/*.toml')
167
- const ok =
168
- lt &&
169
- lt.includes('cspell') &&
170
- lt.includes('run-shellcheck-text.mjs') &&
171
- lt.includes('bunx markdownlint-cli2') &&
172
- lt.includes('**/*.mdc') &&
173
- v8rTextOk &&
174
- (!globsRequired || globsOk)
175
- if (ok) {
176
- passFn('package.json: lint-text — shellcheck (run-shellcheck-text.mjs), v8r: run-v8r.mjs або чотири bunx v8r')
158
+ const lt = typeof lintText === 'string' ? lintText.trim() : ''
159
+ if (lt === 'n-cursor lint-text') {
160
+ passFn('lint-text делегує CLI n-cursor lint-text (cspell + shellcheck + markdownlint + v8r)')
177
161
  } else {
178
162
  failFn(
179
- 'package.json: lint-text додай bun ./…/run-shellcheck-text.mjs; v8r: bun ./…/run-v8r.mjs або чотири (bunx v8r "<glob>" || [ $? -eq 98 ]) (див. n-text.mdc)'
163
+ 'package.json: lint-text має бути "n-cursor lint-text" CLI пакета @nitra/cursor виконує cspell shellcheck markdownlint-cli2 v8r (text.mdc)'
180
164
  )
181
165
  }
182
166
  }
@@ -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
 
@@ -6,6 +6,10 @@ description: >-
6
6
 
7
7
  # n-fix — автоматичне виправлення проєкту
8
8
 
9
+ ## Скоуп
10
+
11
+ Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor check` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`). Не запускай `bun run lint` із цього скілу і не намагайся виправляти його порушення тут — це задача `/n-lint`. Якщо `npx @nitra/cursor check` чистий, а `bun run lint` лишився червоним — запусти `/n-lint` окремо.
12
+
9
13
  ## Workflow
10
14
 
11
15
  1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `AGENTS.md`, для яких є `check-*.mjs`; повний набір — явні аргументи: `npx @nitra/cursor check bun ga …`):
@@ -36,22 +40,10 @@ bun i
36
40
  oxfmt .
37
41
  ```
38
42
 
39
- 6. **Лінтери**знайди в кореневому `package.json` всі скрипти з префіксом `lint-` і запусти кожен:
40
-
41
- ```bash
42
- bun run lint-js
43
- bun run lint-text
44
- bun run lint-style
45
- ```
46
-
47
- На помилках: auto-fix (якщо є), інакше правки в коді/конфігах; повторюй доки скрипт завершується з `0`.
48
-
49
- Підсумок: Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.
50
-
51
- 7. **Верифікація** — перевір що все виправлено:
43
+ 6. **Верифікація**перевір що все виправлено:
52
44
 
53
45
  ```bash
54
46
  npx @nitra/cursor check
55
47
  ```
56
48
 
57
- 8. **Результат** — всі `❌` мають стати `✅`. Якщо залишились `❌` — повтори кроки 3-7.
49
+ 7. **Результат** — всі `❌` від `npx @nitra/cursor check` мають стати `✅`. Якщо залишились `❌` — повтори кроки 3-6. Лінт-помилки від `bun run lint` тут **не виправляй** — вони на скіл `/n-lint`.
@@ -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
- }