@nitra/cursor 1.11.3 → 1.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.11.4] - 2026-05-15
8
+
9
+ ### Fixed
10
+
11
+ - **`npm/rules/nginx-default-tpl/js/template/check.mjs`** — `findDefaultConfTemplatePaths` пропускає тестові `fixtures/` за будь-яким сегментом шляху, не лише `tests/fixtures/`. Після concern-split (фази 1-4) fixtures лежать у `rules/<rule>/js/<concern>/fixtures/`, і старий патерн пропускав їх повз → check шукав Dockerfile поруч із тестовим шаблоном і фалс-фейлив.
12
+
7
13
  ## [1.11.3] - 2026-05-15
8
14
 
9
15
  ### Fixed
@@ -55,7 +61,7 @@
55
61
  - **Інкрементальна міграція правил на `target.json`** (декларативні маніфести поруч із кожним `<concern>.rego`):
56
62
  - **Single-file правила** (11): `bun`, `text`, `style-lint`, `php`, `docker`, `npm-module`, `js-lint`, `image-compress`, `capacitor`, `hasura`, `adr` — `target.json` з `single` для кожного канонічного конфіг-файлу. Сумарно 27 нових маніфестів. `bun.bunfig`, `text.cspell`, `npm_module.npm_publish_yml` тощо тепер прогоняються через CLI `check <id>` без додаткового `bun run lint-conftest`.
57
63
  - **Walk-glob правила** (6): `js-mssql`, `js-bun-db`, `js-bun-redis`, `js-run` (package_json + configmap), `vue`, `image-avif` — `walkGlob: "**/package.json"` або відповідний патерн.
58
- - **k8s.* концерни** (8): `manifest`, `gateway`, `hpa_pdb`, `kustomization`, `svc_yaml`, `svc_hl_yaml`, `base_kustomization`, `base_manifest` — `walkGlob` по YAML під сегментом `k8s/`; `base_manifest` використовує негативний glob для виключення `kustomization.yaml`.
64
+ - **k8s.\* концерни** (8): `manifest`, `gateway`, `hpa_pdb`, `kustomization`, `svc_yaml`, `svc_hl_yaml`, `base_kustomization`, `base_manifest` — `walkGlob` по YAML під сегментом `k8s/`; `base_manifest` використовує негативний glob для виключення `kustomization.yaml`.
59
65
  - **abie концерни** (4): `clean_merged_ignore_branches` (single), `health_check_policy` (walkGlob `**/k8s/**/hc.yaml`), `http_route_base` (walkGlob `**/k8s/**/base/**/hr.yaml`), `base_deployment_preem` (walkGlob `**/k8s/**/base/**/*.{yaml,yml}` з виключенням `kustomization.yaml`).
60
66
  - **`capture-decisions.sh` тепер пише чернетки напряму в `docs/adr/<timestamp>-<sid>.md`** (раніше — у `docs/adr/_inbox/`). Сам каталог `_inbox/` більше не створюється, але `normalize-decisions.sh` бачить його рекурсивно — старі чернетки з `_inbox/` поступово розчищаються нормалізацією. Можна також одноразово `git mv docs/adr/_inbox/*.md docs/adr/` і прибрати порожній каталог.
61
67
  - **Правило `adr` (`npm/rules/adr/adr.mdc`)**: повне переписування під дві фази (capture + normalize). Видалено згадки `_inbox/`. Версія `version: '2.0'`.
@@ -84,7 +90,7 @@
84
90
 
85
91
  ### Changed
86
92
 
87
- - **Rule-centric структура пакета.** Кожне правило тепер живе в одній директорії `npm/rules/{rule}/` з усіма своїми артефактами: `{rule}.mdc`, `auto.md` (умова автоактивації), `policy/` (rego-поліси), `js/` (check.mjs + опційні run/lint + co-located *.test.mjs + fixtures/). Видалив каталог правила — правило зникло без слідів у `bin/auto-rules.md`, `npm/policy/`, `npm/scripts/`. Дзеркальна структура для скілів у `npm/skills/{skill}/` (SKILL.md + auto.md + js/).
93
+ - **Rule-centric структура пакета.** Кожне правило тепер живе в одній директорії `npm/rules/{rule}/` з усіма своїми артефактами: `{rule}.mdc`, `auto.md` (умова автоактивації), `policy/` (rego-поліси), `js/` (check.mjs + опційні run/lint + co-located \*.test.mjs + fixtures/). Видалив каталог правила — правило зникло без слідів у `bin/auto-rules.md`, `npm/policy/`, `npm/scripts/`. Дзеркальна структура для скілів у `npm/skills/{skill}/` (SKILL.md + auto.md + js/).
88
94
  - **Тести співрозташовуються з джерелами.** ~50 файлів з `npm/tests/` переїхали в `npm/rules/{rule}/js/*.test.mjs` (тести правил), `npm/scripts/*.test.mjs` (тести інфраструктури), `npm/scripts/utils/*.test.mjs` (тести утиліт). `tests/helpers.mjs` → `scripts/utils/test-helpers.mjs`. `npm/tests/` залишається тільки для 3 крос-правильних інтеграційних тестів.
89
95
  - **`bin/n-cursor.js`**: `BUNDLED_MDC_DIR` → `BUNDLED_RULES_DIR`. `discoverBundledRuleNames` і `discoverCheckScripts` тепер обходять підкаталоги `rules/` замість файлів у `mdc/` чи `check-*.mjs` у `scripts/`. Резолвер check-скриптів: `rules/{rule}/js/check.mjs`. `readBundledRuleContent` читає `rules/{rule}/{rule}.mdc`.
90
96
  - **`scripts/utils/run-conftest-batch.mjs` та `scripts/lint-conftest.mjs`**: шляхи до rego-полісі — `rules/{rule}/policy/{name}/` (замість `policy/{rule}/{name}/`). Snake_case `policyDirRel` у JS-call sites замінено на kebab-case.
package/bin/n-cursor.js CHANGED
@@ -266,8 +266,9 @@ async function readConfig(paths = {}) {
266
266
  // правило, додане вручну (напр. `adr` без auto.md-умови), не активувало б залежні
267
267
  // скіли (`adr-normalize`).
268
268
  const disableRulesSet = new Set(disableRules)
269
- const effectiveRulesForSkills = [...new Set([...normalizeIdList(parsedConfig.rules), ...autoDetectedRules.rules])]
270
- .filter(id => !disableRulesSet.has(id))
269
+ const effectiveRulesForSkills = [
270
+ ...new Set([...normalizeIdList(parsedConfig.rules), ...autoDetectedRules.rules])
271
+ ].filter(id => !disableRulesSet.has(id))
271
272
  const autoDetectedSkills = detectAutoSkills({
272
273
  availableSkills,
273
274
  detectedRules: effectiveRulesForSkills,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -8,16 +8,16 @@ import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mj
8
8
  import { isAbieRuleEnabled } from '../../utils/enabled.mjs'
9
9
 
10
10
  /**
11
- * @returns {Promise<boolean>}
11
+ * @returns {Promise<boolean>} `true` — правило застосовне; `false` — пропустити
12
12
  */
13
- export async function applies() {
13
+ export function applies() {
14
14
  return isAbieRuleEnabled(process.cwd())
15
15
  }
16
16
 
17
17
  /**
18
- * @returns {Promise<number>}
18
+ * @returns {number} exit-код (0 — OK, 1 — порушення)
19
19
  */
20
- export async function check() {
20
+ export function check() {
21
21
  const reporter = createCheckReporter()
22
22
  reporter.pass('Правило abie увімкнено — виконуємо перевірки')
23
23
  return reporter.getExitCode()
@@ -15,7 +15,7 @@ import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-con
15
15
  import { abieEnvNameFromBasename, collectAbieEnvFiles, validateAbieEnvInternalUrls } from '../../utils/env-dns.mjs'
16
16
 
17
17
  /**
18
- * @returns {Promise<number>}
18
+ * @returns {Promise<number>} результат
19
19
  */
20
20
  export async function check() {
21
21
  const reporter = createCheckReporter()
@@ -12,7 +12,7 @@ import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mj
12
12
  const SKIP_TOP_DIR_NAMES = new Set(['.git', 'node_modules'])
13
13
 
14
14
  /**
15
- * @returns {Promise<number>}
15
+ * @returns {Promise<number>} результат
16
16
  */
17
17
  export async function check() {
18
18
  const reporter = createCheckReporter()
@@ -17,7 +17,7 @@ import { validateAbieHcModeline } from '../../utils/hc-yaml.mjs'
17
17
  import { collectDeploymentDirs, findK8sYamlFiles } from '../../utils/k8s-tree.mjs'
18
18
 
19
19
  /**
20
- * @returns {Promise<number>}
20
+ * @returns {Promise<number>} результат
21
21
  */
22
22
  export async function check() {
23
23
  const reporter = createCheckReporter()
@@ -50,8 +50,8 @@ export async function check() {
50
50
  continue
51
51
  }
52
52
  const modelineErr = validateAbieHcModeline(hcRaw, relHc)
53
- if (modelineErr !== null) fail(modelineErr)
54
- else pass(`${relHc}: modeline OK`)
53
+ if (modelineErr === null) pass(`${relHc}: modeline OK`)
54
+ else fail(modelineErr)
55
55
  }
56
56
 
57
57
  return reporter.getExitCode()
@@ -26,7 +26,7 @@ import {
26
26
  } from '../../utils/overlay-paths.mjs'
27
27
 
28
28
  /**
29
- * @returns {Promise<number>}
29
+ * @returns {Promise<number>} результат
30
30
  */
31
31
  export async function check() {
32
32
  const reporter = createCheckReporter()
@@ -78,8 +78,8 @@ export async function check() {
78
78
  }
79
79
  const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
80
80
  const v = validateAbieNginxRunHttpRoutePatches(combined, 'ua', raw, sharedAnalysis.refCount)
81
- if (v !== null) fail(`${rel}: ${v}`)
82
- else pass(`${rel}: HTTPRoute patch (ua) відповідає abie.mdc`)
81
+ if (v === null) pass(`${rel}: HTTPRoute patch (ua) відповідає abie.mdc`)
82
+ else fail(`${rel}: ${v}`)
83
83
  }
84
84
 
85
85
  return reporter.getExitCode()
@@ -16,7 +16,7 @@ import { kustomizationHasAbieDeploymentNodeSelectorPatch } from '../../utils/kus
16
16
  import { abieOverlayK8sTreeHasDeployment, isUaKustomizationPath } from '../../utils/overlay-paths.mjs'
17
17
 
18
18
  /**
19
- * @returns {Promise<number>}
19
+ * @returns {Promise<number>} результат
20
20
  */
21
21
  export async function check() {
22
22
  const reporter = createCheckReporter()
@@ -55,7 +55,9 @@ export async function check() {
55
55
  continue
56
56
  }
57
57
  if (!kustomizationHasAbieDeploymentNodeSelectorPatch(raw, 'ua')) {
58
- fail(`${rel}: потрібен patch target kind Deployment: path /spec/template/spec/nodeSelector та preem: false (abie.mdc)`)
58
+ fail(
59
+ `${rel}: потрібен patch target kind Deployment: path /spec/template/spec/nodeSelector та preem: false (abie.mdc)`
60
+ )
59
61
  continue
60
62
  }
61
63
  pass(`${rel}: nodeSelector patch (ua) відповідає abie.mdc`)
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
3
  "files": {
4
- "walkGlob": [
5
- "**/k8s/**/base/**/*.yaml",
6
- "**/k8s/**/base/**/*.yml",
7
- "!**/k8s/**/base/**/kustomization.yaml"
8
- ]
4
+ "walkGlob": ["**/k8s/**/base/**/*.yaml", "**/k8s/**/base/**/*.yml", "!**/k8s/**/base/**/kustomization.yaml"]
9
5
  }
10
6
  }
@@ -12,7 +12,7 @@ const CONFIG_FILE = '.n-cursor.json'
12
12
  /**
13
13
  * Чи увімкнено правило **abie** у `.n-cursor.json:rules`.
14
14
  * @param {string} root корінь репозиторію (cwd)
15
- * @returns {Promise<boolean>}
15
+ * @returns {Promise<boolean>} `true` — `rules` містить `abie`; `false` — інакше
16
16
  */
17
17
  export async function isAbieRuleEnabled(root) {
18
18
  const p = join(root, CONFIG_FILE)
@@ -23,8 +23,8 @@ const ABIE_ENV_CLUSTER_DNS_MAP = Object.freeze({
23
23
  /**
24
24
  * Дістає `dev` / `ua` з basename env-файлу abie.
25
25
  * Не-abie env-файли (`production.env`, `.env` без імені) → null.
26
- * @param {string} basenameOfEnvFile
27
- * @returns {('dev' | 'ua') | null}
26
+ * @param {string} basenameOfEnvFile опис.
27
+ * @returns {('dev' | 'ua') | null} результат
28
28
  */
29
29
  export function abieEnvNameFromBasename(basenameOfEnvFile) {
30
30
  const m = basenameOfEnvFile.match(ABIE_ENV_FILE_BASENAME_RE)
@@ -35,7 +35,7 @@ export function abieEnvNameFromBasename(basenameOfEnvFile) {
35
35
  * Сканує вміст env-файла, повертає помилки невідповідності кластерного DNS / namespace
36
36
  * для кожного internal URL (один URL у двох змінних = дві окремі помилки).
37
37
  * @param {string} content вміст env-файла (UTF-8)
38
- * @param {'dev' | 'ua'} envName
38
+ * @param {'dev' | 'ua'} envName опис.
39
39
  * @returns {string[]} порожній масив, якщо все OK
40
40
  */
41
41
  export function validateAbieEnvInternalUrls(content, envName) {
@@ -63,7 +63,7 @@ export function validateAbieEnvInternalUrls(content, envName) {
63
63
  * Збирає `*.env` файли, які є abie env (`dev.env`/`ua.env`, опц. з провідною крапкою).
64
64
  * @param {string} root корінь репозиторію
65
65
  * @param {string[]} ignorePaths абсолютні шляхи каталогів-виключень
66
- * @returns {Promise<string[]>}
66
+ * @returns {Promise<string[]>} результат
67
67
  */
68
68
  export async function collectAbieEnvFiles(root, ignorePaths) {
69
69
  /** @type {string[]} */
@@ -14,7 +14,7 @@ const ABIE_SHARED_CROSS_NS_BACKEND_SET = new Set(ABIE_SHARED_CROSS_NS_BACKEND_NA
14
14
 
15
15
  /**
16
16
  * Перевіряє один `backendRef`: якщо це спільний `-hl` сервіс, має бути `namespace: dev`.
17
- * @param {unknown} br
17
+ * @param {unknown} br опис.
18
18
  * @param {string} rel rel-шлях файла
19
19
  * @param {string[]} errors мутабельний список помилок
20
20
  * @returns {number} 1 — це shared backend, 0 — інакше
@@ -33,8 +33,8 @@ function checkSharedBackendRef(br, rel, errors) {
33
33
  /**
34
34
  * Збирає по HTTPRoute-документу кількість посилань на shared backends і порушення namespace.
35
35
  * @param {unknown} obj корінь YAML
36
- * @param {string} rel
37
- * @returns {{ refCount: number, errors: string[] }}
36
+ * @param {string} rel опис.
37
+ * @returns {{ refCount: number, errors: string[] }} статистика shared-backend посилань і порушення namespace
38
38
  */
39
39
  function httpRouteDocSharedCrossNsBackendStats(obj, rel) {
40
40
  /** @type {string[]} */
@@ -66,7 +66,7 @@ function httpRouteDocSharedCrossNsBackendStats(obj, rel) {
66
66
  * @param {string} root корінь репозиторію
67
67
  * @param {string} pkgAbs абсолютний шлях каталогу пакета
68
68
  * @param {string[]} yamlFilesAbs усі yaml під k8s
69
- * @returns {Promise<{ refCount: number, baseErrors: string[] }>}
69
+ * @returns {Promise<{ refCount: number, baseErrors: string[] }>} агрегована статистика й помилки base-шару
70
70
  */
71
71
  export async function analyzeAbieSharedBackendRefsInPackageK8s(root, pkgAbs, yamlFilesAbs) {
72
72
  const pkgRel = relative(root, pkgAbs).replaceAll('\\', '/') || pkgAbs
@@ -22,7 +22,7 @@ const deploymentCache = new Map()
22
22
 
23
23
  /**
24
24
  * Скидає кеш — тести мусять викликати між фікстурами.
25
- * @returns {void}
25
+ * @returns {void} результат
26
26
  */
27
27
  export function resetAbieK8sTreeCache() {
28
28
  yamlCache.clear()
@@ -31,9 +31,9 @@ export function resetAbieK8sTreeCache() {
31
31
 
32
32
  /**
33
33
  * Стабільний ключ кешу за (root, ignorePaths).
34
- * @param {string} root
35
- * @param {string[]} ignorePaths
36
- * @returns {string}
34
+ * @param {string} root опис.
35
+ * @param {string[]} ignorePaths опис.
36
+ * @returns {string} результат
37
37
  */
38
38
  function cacheKey(root, ignorePaths) {
39
39
  return `${root}|${[...ignorePaths].toSorted((a, b) => a.localeCompare(b)).join(':')}`
@@ -44,7 +44,7 @@ function cacheKey(root, ignorePaths) {
44
44
  * Каталог `.github/` свідомо пропускається (належить `ga.mdc`).
45
45
  * @param {string} root корінь репозиторію
46
46
  * @param {string[]} [ignorePaths] абсолютні шляхи каталогів-виключень
47
- * @returns {Promise<string[]>}
47
+ * @returns {Promise<string[]>} результат
48
48
  */
49
49
  export function findK8sYamlFiles(root, ignorePaths = []) {
50
50
  const key = cacheKey(root, ignorePaths)
@@ -77,7 +77,16 @@ export function findK8sYamlFiles(root, ignorePaths = []) {
77
77
  * @param {(msg: string) => void} [fail] репортер помилок парсингу (опц.)
78
78
  * @returns {Promise<Set<string>>} абсолютні шляхи директорій
79
79
  */
80
- export function collectDeploymentDirs(root, yamlAbs, fail = () => {}) {
80
+ /**
81
+ * No-op fail-handler за замовчуванням для `collectDeploymentDirs` — пошкоджені YAML
82
+ * під час cross-rule сканування мовчки пропускаються; формальний reporter передає сам caller.
83
+ * @param {string} _msg повідомлення про помилку (ігнорується)
84
+ */
85
+ const silentFail = _msg => {
86
+ // noop
87
+ }
88
+
89
+ export function collectDeploymentDirs(root, yamlAbs, fail = silentFail) {
81
90
  const key = `${root}|${[...yamlAbs].toSorted((a, b) => a.localeCompare(b)).join(':')}`
82
91
  const cached = deploymentCache.get(key)
83
92
  if (cached) return cached
@@ -24,8 +24,8 @@ const ABIE_UA_HTTPROUTE_HOST_MARKERS = ['abie.app', 'vybeerai.com.ua', '*.abie.a
24
24
 
25
25
  /**
26
26
  * Чи patch-рядок містить очікуваний ua nodeSelector (preem: false).
27
- * @param {string} patchText
28
- * @returns {boolean}
27
+ * @param {string} patchText опис.
28
+ * @returns {boolean} результат
29
29
  */
30
30
  function jsonPatchTextHasUaDeploymentNodeSelector(patchText) {
31
31
  if (typeof patchText !== 'string' || patchText.trim() === '') return false
@@ -36,9 +36,9 @@ function jsonPatchTextHasUaDeploymentNodeSelector(patchText) {
36
36
 
37
37
  /**
38
38
  * Чи один елемент `patches` відповідає abie nodeSelector для `mode`.
39
- * @param {unknown} p
40
- * @param {'ua'} mode
41
- * @returns {boolean}
39
+ * @param {unknown} p опис.
40
+ * @param {'ua'} mode опис.
41
+ * @returns {boolean} результат
42
42
  */
43
43
  function inlineKustomizationPatchMatchesAbieMode(p, mode) {
44
44
  if (p === null || typeof p !== 'object' || Array.isArray(p)) return false
@@ -55,9 +55,9 @@ function inlineKustomizationPatchMatchesAbieMode(p, mode) {
55
55
 
56
56
  /**
57
57
  * Чи документ Kustomization містить відповідний inline patch на Deployment.
58
- * @param {import('yaml').Document} doc
59
- * @param {'ua'} mode
60
- * @returns {boolean}
58
+ * @param {import('yaml').Document} doc опис.
59
+ * @param {'ua'} mode опис.
60
+ * @returns {boolean} результат
61
61
  */
62
62
  function kustomizationDocumentHasAbieDeploymentNodeSelectorPatch(doc, mode) {
63
63
  if (doc.errors.length > 0) return false
@@ -76,8 +76,8 @@ function kustomizationDocumentHasAbieDeploymentNodeSelectorPatch(doc, mode) {
76
76
  /**
77
77
  * Чи `kustomization.yaml` містить валідні inline patch для Deployment nodeSelector (ua).
78
78
  * @param {string} raw повний текст файла
79
- * @param {'ua'} mode
80
- * @returns {boolean}
79
+ * @param {'ua'} mode опис.
80
+ * @returns {boolean} результат
81
81
  */
82
82
  export function kustomizationHasAbieDeploymentNodeSelectorPatch(raw, mode) {
83
83
  const body = stripBom(raw)
@@ -100,8 +100,8 @@ export function kustomizationHasAbieDeploymentNodeSelectorPatch(raw, mode) {
100
100
  // ── HTTPRoute (ua) ────────────────────────────────────────────────────────
101
101
 
102
102
  /**
103
- * @param {unknown} p
104
- * @returns {string | null}
103
+ * @param {unknown} p опис.
104
+ * @returns {string | null} результат
105
105
  */
106
106
  function extractHttpRoutePatchString(p) {
107
107
  if (p === null || typeof p !== 'object' || Array.isArray(p)) return null
@@ -116,8 +116,8 @@ function extractHttpRoutePatchString(p) {
116
116
 
117
117
  /**
118
118
  * Збирає inline `patch`-рядки для HTTPRoute (непорожній `target.name`) з одного Kustomization-документа.
119
- * @param {import('yaml').Document} doc
120
- * @returns {string[]}
119
+ * @param {import('yaml').Document} doc опис.
120
+ * @returns {string[]} результат
121
121
  */
122
122
  function collectAbieHttpRoutePatchStringsFromKustomizationDoc(doc) {
123
123
  if (doc.errors.length > 0) return []
@@ -137,7 +137,7 @@ function collectAbieHttpRoutePatchStringsFromKustomizationDoc(doc) {
137
137
  /**
138
138
  * Збирає всі inline JSON6902-фрагменти HTTPRoute (непорожній `target.name`) у kustomization.yaml.
139
139
  * @param {string} raw повний текст файла
140
- * @returns {string}
140
+ * @returns {string} результат
141
141
  */
142
142
  export function getCombinedNginxRunPatchTextFromKustomization(raw) {
143
143
  const body = stripBom(raw)
@@ -162,8 +162,8 @@ export function getCombinedNginxRunPatchTextFromKustomization(raw) {
162
162
  /**
163
163
  * Рахує операції JSON6902 з `path: /spec/rules/.../backendRefs/.../namespace` і `value: ua[-…]`.
164
164
  * @param {string} combined сукупний текст patch
165
- * @param {'ua'} mode
166
- * @returns {number}
165
+ * @param {'ua'} mode опис.
166
+ * @returns {number} результат
167
167
  */
168
168
  function countAbieHttpRouteBackendRefNamespacePatchesInCombined(combined, mode) {
169
169
  if (mode !== 'ua') return 0
@@ -175,7 +175,7 @@ function countAbieHttpRouteBackendRefNamespacePatchesInCombined(combined, mode)
175
175
  /**
176
176
  * Перевіряє сукупний текст patch(ів) HTTPRoute на відповідність abie.mdc.
177
177
  * @param {string} combined сукупний текст patch
178
- * @param {'ua'} mode
178
+ * @param {'ua'} mode опис.
179
179
  * @param {string} [_fullKustomizationRaw] зберігається для API-сумісності, не використовується
180
180
  * @param {number} [sharedCrossNsBackendRefCount] кількість `auth-run-hl`/`file-link-hl` у base HTTPRoute
181
181
  * @returns {string | null} повідомлення про помилку або null
@@ -215,8 +215,8 @@ export function validateAbieNginxRunHttpRoutePatches(
215
215
  /**
216
216
  * Чи kustomization містить валідні patch для HTTPRoute (ua).
217
217
  * @param {string} raw повний текст kustomization.yaml
218
- * @param {'ua'} mode
219
- * @returns {boolean}
218
+ * @param {'ua'} mode опис.
219
+ * @returns {boolean} результат
220
220
  */
221
221
  export function kustomizationHasAbieNginxRunHttpRoutePatch(raw, mode) {
222
222
  const combined = getCombinedNginxRunPatchTextFromKustomization(raw)
@@ -17,7 +17,7 @@ const TRAILING_SLASH_RE = /\/$/u
17
17
  /**
18
18
  * Чи `rel` — це `…/ua/kustomization.yaml` (abie overlay).
19
19
  * @param {string} rel посі від кореня репозиторію
20
- * @returns {boolean}
20
+ * @returns {boolean} результат
21
21
  */
22
22
  export function isUaKustomizationPath(rel) {
23
23
  const norm = rel.replaceAll('\\', '/')
@@ -28,7 +28,7 @@ export function isUaKustomizationPath(rel) {
28
28
  * Каталог пакета (батько `k8s/`) для overlay `…/k8s/ua/kustomization.yaml`.
29
29
  * @param {string} root корінь репозиторію
30
30
  * @param {string} kustomizationAbs абсолютний шлях до ua kustomization
31
- * @returns {string | null}
31
+ * @returns {string | null} результат
32
32
  */
33
33
  export function abiePackageDirFromK8sOverlay(root, kustomizationAbs) {
34
34
  const rel = relative(root, kustomizationAbs).replaceAll('\\', '/') || kustomizationAbs
@@ -41,7 +41,7 @@ export function abiePackageDirFromK8sOverlay(root, kustomizationAbs) {
41
41
  * застосовується лише до Vite-пакетів.
42
42
  * @param {string} root корінь репозиторію
43
43
  * @param {string} kustomizationAbs абсолютний шлях до ua kustomization
44
- * @returns {boolean}
44
+ * @returns {boolean} результат
45
45
  */
46
46
  export function abieOverlayRequiresHttpRouteByVite(root, kustomizationAbs) {
47
47
  const pkg = abiePackageDirFromK8sOverlay(root, kustomizationAbs)
@@ -58,7 +58,7 @@ export function abieOverlayRequiresHttpRouteByVite(root, kustomizationAbs) {
58
58
  * @param {Set<string>} deploymentDirs абсолютні каталоги з Deployment
59
59
  * @param {string} root корінь репозиторію
60
60
  * @param {string} kustomizationAbs абсолютний шлях до ua kustomization
61
- * @returns {boolean}
61
+ * @returns {boolean} результат
62
62
  */
63
63
  export function abieOverlayK8sTreeHasDeployment(deploymentDirs, root, kustomizationAbs) {
64
64
  const pkg = abiePackageDirFromK8sOverlay(root, kustomizationAbs)
@@ -73,8 +73,8 @@ export function abieOverlayK8sTreeHasDeployment(deploymentDirs, root, kustomizat
73
73
 
74
74
  /**
75
75
  * Чи rel-шлях `…/k8s/base/…` (base-шар abie, не overlay).
76
- * @param {string} rel
77
- * @returns {boolean}
76
+ * @param {string} rel опис.
77
+ * @returns {boolean} результат
78
78
  */
79
79
  export function isAbieK8sBaseYamlPath(rel) {
80
80
  const norm = rel.replaceAll('\\', '/')
@@ -85,7 +85,7 @@ export function isAbieK8sBaseYamlPath(rel) {
85
85
  * Чи yaml належить до `<pkgRel>/k8s/**` поза `ua/` піддеревом (base-шар abie).
86
86
  * @param {string} relFromRoot шлях від кореня
87
87
  * @param {string} pkgRelFromRoot каталог пакета від кореня
88
- * @returns {boolean}
88
+ * @returns {boolean} результат
89
89
  */
90
90
  export function isK8sYamlInAbiePackageExcludingUaOverlay(relFromRoot, pkgRelFromRoot) {
91
91
  const normRel = relFromRoot.replaceAll('\\', '/')
@@ -12,7 +12,7 @@ export const LINE_SPLIT_RE = /\r?\n/u
12
12
  /**
13
13
  * Прибирає BOM на початку файлу.
14
14
  * @param {string} s вміст
15
- * @returns {string}
15
+ * @returns {string} результат
16
16
  */
17
17
  export function stripBom(s) {
18
18
  return s.startsWith('') ? s.slice(1) : s
@@ -21,7 +21,7 @@ export function stripBom(s) {
21
21
  /**
22
22
  * Чи YAML-документ — це `kind: Deployment`.
23
23
  * @param {unknown} obj корінь YAML-документа
24
- * @returns {boolean}
24
+ * @returns {boolean} результат
25
25
  */
26
26
  export function isDeploymentDoc(obj) {
27
27
  return (
@@ -46,8 +46,8 @@ export const silentFail = _msg => {
46
46
  * викликає `failFn` і повертає `null`.
47
47
  * @param {string} abs абсолютний шлях
48
48
  * @param {string} rel відносний (для повідомлень)
49
- * @param {(msg: string) => void} failFn
50
- * @returns {Promise<import('yaml').Document[] | null>}
49
+ * @param {(msg: string) => void} failFn опис.
50
+ * @returns {Promise<import('yaml').Document[] | null>} результат
51
51
  */
52
52
  export async function readAndParseYamlDocs(abs, rel, failFn) {
53
53
  let raw
@@ -143,7 +143,7 @@ async function checkEnvFile(relPath, expected, reporter) {
143
143
  const value = m[1].trim()
144
144
  const parsed = parseInternalHasuraEndpoint(value)
145
145
  if (!parsed.ok) {
146
- const example = "https://<service>.<namespace>.svc.<cluster>.internal:<port>"
146
+ const example = 'https://<service>.<namespace>.svc.<cluster>.internal:<port>'
147
147
  fail(
148
148
  `${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
149
149
  )
@@ -16,7 +16,11 @@ import { join, relative } from 'node:path'
16
16
 
17
17
  import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
18
18
  import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
19
- import { findRedisImportsInText, isRedisScanSourceFile, shouldSkipFileForRedisScan } from '../../../../scripts/utils/redis-imports.mjs'
19
+ import {
20
+ findRedisImportsInText,
21
+ isRedisScanSourceFile,
22
+ shouldSkipFileForRedisScan
23
+ } from '../../../../scripts/utils/redis-imports.mjs'
20
24
  import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
21
25
 
22
26
  /**
@@ -49,7 +49,10 @@ import {
49
49
  resolveConnDirFromPackageJson
50
50
  } from '../../../../scripts/utils/conn-imports-scan.mjs'
51
51
  import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
52
- import { findPromiseSetTimeoutInText, isPromiseSetTimeoutScanSourceFile } from '../../../../scripts/utils/promise-settimeout-scan.mjs'
52
+ import {
53
+ findPromiseSetTimeoutInText,
54
+ isPromiseSetTimeoutScanSourceFile
55
+ } from '../../../../scripts/utils/promise-settimeout-scan.mjs'
53
56
  import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
54
57
  import { getMonorepoPackageRootDirs } from '../../../../scripts/utils/workspaces.mjs'
55
58
 
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
3
  "files": {
4
- "walkGlob": [
5
- "**/k8s/**/base/**/*.yaml",
6
- "**/k8s/**/base/**/*.yml",
7
- "!**/k8s/**/base/**/kustomization.yaml"
8
- ]
4
+ "walkGlob": ["**/k8s/**/base/**/*.yaml", "**/k8s/**/base/**/*.yml", "!**/k8s/**/base/**/kustomization.yaml"]
9
5
  }
10
6
  }
@@ -34,7 +34,9 @@ const GZIP_CMD_RE = /\bgzip\b/u
34
34
  const GZIP_EXTENSION_RE = /\*\.(?:js|css)/u
35
35
 
36
36
  /**
37
- * Збирає абсолютні шляхи до **default.conf.template** у репозиторії; шлях `tests/fixtures` не обходиться як проєктний шаблон.
37
+ * Збирає абсолютні шляхи до **default.conf.template** у репозиторії; будь-який сегмент
38
+ * `fixtures/` у шляху виключається — це тестові артефакти (як `tests/fixtures/` так і
39
+ * co-located `rules/<rule>/js/<concern>/fixtures/`).
38
40
  * @param {string} root корінь cwd
39
41
  * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
40
42
  * @returns {Promise<string[]>} відсортовані абсолютні шляхи до шаблонів
@@ -47,7 +49,7 @@ export async function findDefaultConfTemplatePaths(root, ignorePaths = []) {
47
49
  p => {
48
50
  if (basename(p) !== 'default.conf.template') return
49
51
  const rel = relative(root, p).replaceAll('\\', '/')
50
- if (rel.includes('tests/fixtures/')) return
52
+ if (rel.split('/').includes('fixtures')) return
51
53
  out.push(p)
52
54
  },
53
55
  ignorePaths
@@ -32,7 +32,12 @@ import { promisify } from 'node:util'
32
32
 
33
33
  import { parseSync } from 'oxc-parser'
34
34
 
35
- import { dynamicImportModule, langFromPath, requireCallModule, walkAstWithAncestors } from '../../../../scripts/utils/ast-scan-utils.mjs'
35
+ import {
36
+ dynamicImportModule,
37
+ langFromPath,
38
+ requireCallModule,
39
+ walkAstWithAncestors
40
+ } from '../../../../scripts/utils/ast-scan-utils.mjs'
36
41
  import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
37
42
  import { loadCursorIgnorePaths } from '../../../../scripts/utils/load-cursor-config.mjs'
38
43
  import { walkDir } from '../../../../scripts/utils/walkDir.mjs'
@@ -45,9 +45,9 @@ export async function applies() {
45
45
 
46
46
  /**
47
47
  * Друкує короткий context-pass — самі полісі прогонить CLI через `policy/<name>/target.json`.
48
- * @returns {Promise<number>} 0 — все ок (фактичні порушення повертають policy-концерни)
48
+ * @returns {number} 0 — все ок (фактичні порушення повертають policy-концерни)
49
49
  */
50
- export async function check() {
50
+ export function check() {
51
51
  const reporter = createCheckReporter()
52
52
  reporter.pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')
53
53
  return reporter.getExitCode()
@@ -70,7 +70,9 @@ export async function check() {
70
70
 
71
71
  const extPath = '.vscode/extensions.json'
72
72
  if (!existsSync(extPath)) {
73
- fail(`${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" і "rust-lang.rust-analyzer" (tauri.mdc)`)
73
+ fail(
74
+ `${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" і "rust-lang.rust-analyzer" (tauri.mdc)`
75
+ )
74
76
  return reporter.getExitCode()
75
77
  }
76
78
  const violations = runConftestBatch({
@@ -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-залежностями.
@@ -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) {
@@ -68,7 +68,8 @@ async function runPolicyConcern(bundledRulesDir, ruleId, concernName, walkCache)
68
68
  if (files.length === 0) {
69
69
  if (target.files.required && target.files.single) {
70
70
  const msg =
71
- target.missingMessage ?? `${target.files.single} не існує — створи згідно ${ruleId}.mdc (${ruleId}.${concernName})`
71
+ target.missingMessage ??
72
+ `${target.files.single} не існує — створи згідно ${ruleId}.mdc (${ruleId}.${concernName})`
72
73
  reporter.fail(msg)
73
74
  }
74
75
  return reporter.getExitCode()
@@ -91,7 +92,7 @@ async function runPolicyConcern(bundledRulesDir, ruleId, concernName, walkCache)
91
92
 
92
93
  /**
93
94
  * Запускає одне правило: applies-гейт → JS-концерни → policy-концерни.
94
- * @param {import('./discover-checkable-rules.mjs').CheckableRule} rule
95
+ * @param {import('./discover-checkable-rules.mjs').CheckableRule} rule опис правила з discovery
95
96
  * @param {string} bundledRulesDir абсолютний шлях до `rules/`
96
97
  * @param {Map<string, Promise<string[]>>} walkCache shared cache (один на check-прогон)
97
98
  * @returns {Promise<number>} 0 — OK, 1 — є порушення в одному чи більше концернів
@@ -131,7 +131,6 @@ 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. Якщо англійська вже є, то прибираємо російську:
@@ -175,8 +174,8 @@ const t = tf.bind({ tr })
175
174
  import { tf } from '@nitra/tfm'
176
175
 
177
176
  const tr = {
178
- Так: { ru: 'Да' },
179
- Ні: { ru: 'Нет' }
177
+ Так: { ru: 'Да' },
178
+ Ні: { ru: 'Нет' }
180
179
  }
181
180
 
182
181
  на
@@ -184,11 +183,10 @@ const tr = {
184
183
  import { tf } from '@nitra/tfm'
185
184
 
186
185
  const tr = {
187
- Так: { ru: 'Yes' },
188
- Ні: { ru: 'No' }
186
+ Так: { ru: 'Yes' },
187
+ Ні: { ru: 'No' }
189
188
  }
190
189
 
191
-
192
190
  ## 6. Після очистки
193
191
 
194
192
  - Переконайся, що `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-результати).