@nitra/cursor 1.9.19 → 1.9.21

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,35 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.9.20] - 2026-05-14
8
+
9
+ ### Added
10
+
11
+ - **`check-rego.mjs` orchestrator + 3 rego-полісі для `rego.mdc`:**
12
+ - JS gate у `npm/scripts/check-rego.mjs`: walk дерева від `cwd` (з типовими skip-ами і `.n-cursor.json:ignore`); якщо немає жодного `.rego` файла — `pass` (skip) ("rego-tooling не вимагається"). Інакше — FS-existence + content-валідація 3 файлів через `runConftestBatch`.
13
+ - `rego.vscode_extensions` — `recommendations` ∋ `tsandall.opa`.
14
+ - `rego.vscode_settings` — `[rego]` блок з `editor.defaultFormatter: "tsandall.opa"` + `editor.formatOnSave: true`; окремі deny на «не object», «неправильний defaultFormatter», «formatOnSave не true / відсутній».
15
+ - `rego.package_json` — `scripts.lint-rego` присутній і дорівнює `"bun ./npm/scripts/lint-rego.mjs"` (точне значення, з підтримкою whitespace через `trim_space`).
16
+ - +20 rego-тестів (5 + 7 + 8). Глобально у `lint-conftest` НЕ реєструються — це conditional правило, gating через JS.
17
+ - **`check-tauri.mjs` orchestrator + 1 rego-полісі для `tauri.mdc`:**
18
+ - JS detector маркера Tauri-проєкту: `src-tauri/` каталог, `tauri.conf.json` у корені, або `@tauri-apps/*` у `dependencies`/`devDependencies` кореневого `package.json`. Якщо немає — `pass` (skip).
19
+ - `tauri.vscode_extensions` — `recommendations` ∋ обидва: `tauri-apps.tauri-vscode` і `rust-lang.rust-analyzer`. Один deny з шаблоном повідомлень + `recommendations_set` поза deny (performance hint).
20
+ - +6 rego-тестів (canonical, додаткові розширення, кожний відсутній маркер окремо, empty, no field).
21
+ - **`conftest verify`** — **293/293 pass** (+26).
22
+
23
+ ### Changed
24
+
25
+ - **CLI auto-discovery** підхоплює `check-rego.mjs` і `check-tauri.mjs` через `discoverCheckScripts()` у `bin/n-cursor.js`. Окрема реєстрація не потрібна — будь-який `check-*.mjs` стає доступним через `npx @nitra/cursor check <rule>`.
26
+
27
+ ### Verified
28
+
29
+ - **На цьому репо `npx @nitra/cursor check rego` детектує реальні гепи у `.vscode/extensions.json` (немає `tsandall.opa`) і `.vscode/settings.json` (немає `[rego]` блока).** Це true-positive: репо має `rego` у `.n-cursor.json:rules` і містить `.rego` файли, тож канонічний tooling-набір вимагається. Фікс — додати entries у `.vscode/*` згідно `rego.mdc`.
30
+
31
+ ### Not migrated (explained)
32
+
33
+ - **`changelog.mdc` format-валідація** — пропущено: `conftest` не парсить markdown без pre-processing. Структурна валідація формату `## [version] - YYYY-MM-DD` лишається в `check-changelog.mjs` (JS), яке через regex розбирає текст. Перенесення вимагало б pre-processing markdown → JSON у JS перед викликом conftest — додаткова складність без виграшу.
34
+ - **`image-compress.mdc`** — вже має повне покриття rego через `image_compress.package_json` (8 deny, тести у [npm/policy/image_compress/package_json/](npm/policy/image_compress/package_json/)). `.gitignore` cross-file checks залишаються в `check-image-compress.mjs` як FS-логіка (rego не вміє).
35
+
7
36
  ## [1.9.19] - 2026-05-14
8
37
 
9
38
  ### Removed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.9.19",
3
+ "version": "1.9.21",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,35 @@
1
+ # Перевірка `package.json` для rego (rego.mdc).
2
+ #
3
+ # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
4
+ # `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
5
+ #
6
+ # Canonical (rego.mdc): scripts.lint-rego має бути "bun ./npm/scripts/lint-rego.mjs".
7
+ #
8
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
9
+ package rego.package_json
10
+
11
+ import rego.v1
12
+
13
+ canonical_lint_rego := "bun ./npm/scripts/lint-rego.mjs"
14
+
15
+ lint_rego_template := concat(" ", [
16
+ "package.json: scripts.lint-rego має бути %q",
17
+ "(зараз: %q) (rego.mdc)",
18
+ ])
19
+
20
+ deny contains msg if {
21
+ scripts := object.get(input, "scripts", {})
22
+ not "lint-rego" in object.keys(scripts)
23
+ msg := concat(" ", [
24
+ "package.json: відсутній scripts.lint-rego — додай",
25
+ "\"lint-rego\": \"bun ./npm/scripts/lint-rego.mjs\" (rego.mdc)",
26
+ ])
27
+ }
28
+
29
+ deny contains msg if {
30
+ scripts := object.get(input, "scripts", {})
31
+ lint_rego := object.get(scripts, "lint-rego", "")
32
+ lint_rego != ""
33
+ trim_space(lint_rego) != canonical_lint_rego
34
+ msg := sprintf(lint_rego_template, [canonical_lint_rego, lint_rego])
35
+ }
@@ -0,0 +1,42 @@
1
+ # Тести для `rego.package_json`. Запуск:
2
+ # conftest verify -p npm/policy/rego/package_json
3
+ package rego.package_json_test
4
+
5
+ import rego.v1
6
+
7
+ import data.rego.package_json
8
+
9
+ canonical_lint_rego := "bun ./npm/scripts/lint-rego.mjs"
10
+
11
+ test_allow_canonical if {
12
+ pkg := {"scripts": {"lint-rego": canonical_lint_rego}}
13
+ count(package_json.deny) == 0 with input as pkg
14
+ }
15
+
16
+ test_allow_with_other_scripts if {
17
+ pkg := {"scripts": {"lint-rego": canonical_lint_rego, "test": "bun test"}}
18
+ count(package_json.deny) == 0 with input as pkg
19
+ }
20
+
21
+ test_allow_with_whitespace if {
22
+ pkg := {"scripts": {"lint-rego": concat("", [" ", canonical_lint_rego, " "])}}
23
+ count(package_json.deny) == 0 with input as pkg
24
+ }
25
+
26
+ test_deny_missing_lint_rego if {
27
+ count(package_json.deny) > 0 with input as {"scripts": {}}
28
+ }
29
+
30
+ test_deny_no_scripts if {
31
+ count(package_json.deny) > 0 with input as {"name": "x"}
32
+ }
33
+
34
+ test_deny_wrong_value if {
35
+ pkg := {"scripts": {"lint-rego": "opa check ."}}
36
+ count(package_json.deny) > 0 with input as pkg
37
+ }
38
+
39
+ test_deny_npx_form if {
40
+ pkg := {"scripts": {"lint-rego": "npx opa check ."}}
41
+ count(package_json.deny) > 0 with input as pkg
42
+ }
@@ -0,0 +1,19 @@
1
+ # Перевірка `.vscode/extensions.json` для rego (rego.mdc).
2
+ #
3
+ # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ того, як
4
+ # JS виявив `.rego` файли у дереві (умовне правило — проєкти без rego не
5
+ # зобовʼязані ставити tsandall.opa). Глобально у `lint-conftest` НЕ
6
+ # реєструється.
7
+ #
8
+ # Canonical (rego.mdc): `recommendations` має містити `tsandall.opa`.
9
+ #
10
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
+ package rego.vscode_extensions
12
+
13
+ import rego.v1
14
+
15
+ deny contains msg if {
16
+ recs := object.get(input, "recommendations", [])
17
+ not "tsandall.opa" in {r | some r in recs}
18
+ msg := ".vscode/extensions.json: recommendations має містити \"tsandall.opa\" (rego.mdc)"
19
+ }
@@ -0,0 +1,34 @@
1
+ # Тести для `rego.vscode_extensions`. Запуск:
2
+ # conftest verify -p npm/policy/rego/vscode_extensions
3
+ package rego.vscode_extensions_test
4
+
5
+ import rego.v1
6
+
7
+ import data.rego.vscode_extensions
8
+
9
+ test_allow_with_required_extension if {
10
+ cfg := {"recommendations": ["tsandall.opa"]}
11
+ count(vscode_extensions.deny) == 0 with input as cfg
12
+ }
13
+
14
+ test_allow_with_additional_extensions if {
15
+ cfg := {"recommendations": [
16
+ "dbaeumer.vscode-eslint",
17
+ "tsandall.opa",
18
+ "oxc.oxc-vscode",
19
+ ]}
20
+ count(vscode_extensions.deny) == 0 with input as cfg
21
+ }
22
+
23
+ test_deny_missing_extension if {
24
+ cfg := {"recommendations": ["dbaeumer.vscode-eslint"]}
25
+ count(vscode_extensions.deny) > 0 with input as cfg
26
+ }
27
+
28
+ test_deny_empty_recommendations if {
29
+ count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
30
+ }
31
+
32
+ test_deny_no_recommendations_field if {
33
+ count(vscode_extensions.deny) > 0 with input as {}
34
+ }
@@ -0,0 +1,38 @@
1
+ # Перевірка `.vscode/settings.json` для rego (rego.mdc).
2
+ #
3
+ # Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
4
+ # `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
5
+ #
6
+ # Canonical (rego.mdc):
7
+ # { "[rego]": { "editor.defaultFormatter": "tsandall.opa",
8
+ # "editor.formatOnSave": true } }
9
+ #
10
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
+ package rego.vscode_settings
12
+
13
+ import rego.v1
14
+
15
+ # ── deny: [rego] block ──────────────────────────────────────────────────
16
+
17
+ deny contains msg if {
18
+ not is_object(object.get(input, "[rego]", null))
19
+ msg := concat(" ", [
20
+ ".vscode/settings.json: \"[rego]\" має бути обʼєктом з",
21
+ "\"editor.defaultFormatter\": \"tsandall.opa\" і",
22
+ "\"editor.formatOnSave\": true (rego.mdc)",
23
+ ])
24
+ }
25
+
26
+ deny contains msg if {
27
+ rego_block := object.get(input, "[rego]", {})
28
+ is_object(rego_block)
29
+ object.get(rego_block, "editor.defaultFormatter", null) != "tsandall.opa"
30
+ msg := ".vscode/settings.json: \"[rego].editor.defaultFormatter\" має бути \"tsandall.opa\" (rego.mdc)"
31
+ }
32
+
33
+ deny contains msg if {
34
+ rego_block := object.get(input, "[rego]", {})
35
+ is_object(rego_block)
36
+ object.get(rego_block, "editor.formatOnSave", null) != true
37
+ msg := ".vscode/settings.json: \"[rego].editor.formatOnSave\" має бути true (rego.mdc)"
38
+ }
@@ -0,0 +1,55 @@
1
+ # Тести для `rego.vscode_settings`. Запуск:
2
+ # conftest verify -p npm/policy/rego/vscode_settings
3
+ package rego.vscode_settings_test
4
+
5
+ import rego.v1
6
+
7
+ import data.rego.vscode_settings
8
+
9
+ valid_cfg := {"[rego]": {
10
+ "editor.defaultFormatter": "tsandall.opa",
11
+ "editor.formatOnSave": true,
12
+ }}
13
+
14
+ test_allow_canonical if {
15
+ count(vscode_settings.deny) == 0 with input as valid_cfg
16
+ }
17
+
18
+ test_allow_with_additional_lang_blocks if {
19
+ cfg := json.patch(
20
+ valid_cfg,
21
+ [{"op": "add", "path": "/[javascript]", "value": {"editor.defaultFormatter": "oxc.oxc-vscode"}}],
22
+ )
23
+ count(vscode_settings.deny) == 0 with input as cfg
24
+ }
25
+
26
+ test_deny_rego_block_missing if {
27
+ count(vscode_settings.deny) > 0 with input as {}
28
+ }
29
+
30
+ test_deny_rego_block_not_object if {
31
+ count(vscode_settings.deny) > 0 with input as {"[rego]": "tsandall.opa"}
32
+ }
33
+
34
+ test_deny_wrong_default_formatter if {
35
+ cfg := json.patch(
36
+ valid_cfg,
37
+ [{"op": "replace", "path": "/[rego]/editor.defaultFormatter", "value": "prettier"}],
38
+ )
39
+ count(vscode_settings.deny) > 0 with input as cfg
40
+ }
41
+
42
+ test_deny_default_formatter_missing if {
43
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[rego]/editor.defaultFormatter"}])
44
+ count(vscode_settings.deny) > 0 with input as cfg
45
+ }
46
+
47
+ test_deny_format_on_save_false if {
48
+ cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/[rego]/editor.formatOnSave", "value": false}])
49
+ count(vscode_settings.deny) > 0 with input as cfg
50
+ }
51
+
52
+ test_deny_format_on_save_missing if {
53
+ cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[rego]/editor.formatOnSave"}])
54
+ count(vscode_settings.deny) > 0 with input as cfg
55
+ }
@@ -0,0 +1,28 @@
1
+ # Перевірка `.vscode/extensions.json` для tauri (tauri.mdc).
2
+ #
3
+ # Викликається з `check-tauri.mjs` через `runConftestBatch` лише ПІСЛЯ того,
4
+ # як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
5
+ # у будь-якому пакеті, або залежність `@tauri-apps/*`). Глобально у
6
+ # `lint-conftest` НЕ реєструється — інакше false-positive порушення на не-Tauri проєктах.
7
+ #
8
+ # Canonical (tauri.mdc): `recommendations` має містити обидва записи —
9
+ # - tauri-apps.tauri-vscode
10
+ # - rust-lang.rust-analyzer
11
+ #
12
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
13
+ package tauri.vscode_extensions
14
+
15
+ import rego.v1
16
+
17
+ required_extensions := {"tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"}
18
+
19
+ missing_extension_template := ".vscode/extensions.json: recommendations має містити %q (tauri.mdc)"
20
+
21
+ # Множина усіх записів `recommendations` (поза deny — performance/non-loop-expression).
22
+ recommendations_set := {r | some r in object.get(input, "recommendations", [])}
23
+
24
+ deny contains msg if {
25
+ some required in required_extensions
26
+ not required in recommendations_set
27
+ msg := sprintf(missing_extension_template, [required])
28
+ }
@@ -0,0 +1,44 @@
1
+ # Тести для `tauri.vscode_extensions`. Запуск:
2
+ # conftest verify -p npm/policy/tauri/vscode_extensions
3
+ package tauri.vscode_extensions_test
4
+
5
+ import rego.v1
6
+
7
+ import data.tauri.vscode_extensions
8
+
9
+ canonical := {"recommendations": [
10
+ "tauri-apps.tauri-vscode",
11
+ "rust-lang.rust-analyzer",
12
+ ]}
13
+
14
+ test_allow_canonical if {
15
+ count(vscode_extensions.deny) == 0 with input as canonical
16
+ }
17
+
18
+ test_allow_with_additional_extensions if {
19
+ cfg := {"recommendations": [
20
+ "dbaeumer.vscode-eslint",
21
+ "tauri-apps.tauri-vscode",
22
+ "rust-lang.rust-analyzer",
23
+ "oxc.oxc-vscode",
24
+ ]}
25
+ count(vscode_extensions.deny) == 0 with input as cfg
26
+ }
27
+
28
+ test_deny_missing_tauri if {
29
+ cfg := {"recommendations": ["rust-lang.rust-analyzer"]}
30
+ count(vscode_extensions.deny) > 0 with input as cfg
31
+ }
32
+
33
+ test_deny_missing_rust_analyzer if {
34
+ cfg := {"recommendations": ["tauri-apps.tauri-vscode"]}
35
+ count(vscode_extensions.deny) > 0 with input as cfg
36
+ }
37
+
38
+ test_deny_empty_recommendations if {
39
+ count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
40
+ }
41
+
42
+ test_deny_no_recommendations_field if {
43
+ count(vscode_extensions.deny) > 0 with input as {}
44
+ }
@@ -76,7 +76,6 @@ const UA_KUSTOMIZATION_PATH_RE = /(^|\/)ua\/kustomization\.yaml$/u
76
76
  const OVERLAY_PACKAGE_DIR_RE = /^(.+)\/k8s\/ua\/kustomization\.yaml$/u
77
77
  const BASE_SEGMENT_RE = /(^|\/)base\//u
78
78
  const YAML_EXTENSION_RE = /\.ya?ml$/iu
79
- const K8S_PACKAGE_DIR_RE = /^(.+)\/k8s\//u
80
79
  const PATCH_NODE_SELECTOR_PATH_RE = /path:\s*\/spec\/template\/spec\/nodeSelector\b/u
81
80
  const PATCH_PREEM_FALSE_RE = /\bpreem:\s*['"]?false['"]?\b/u
82
81
  const TRAILING_SLASH_RE = /\/$/u
@@ -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 = 'http://<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
  )
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Перевіряє інструментарій rego (rego.mdc): VSCode та `package.json` для проєктів,
3
+ * які мають хоча б один `.rego` файл у дереві.
4
+ *
5
+ * Cross-file gating (JS):
6
+ * 1. Walk дерева від `cwd` (з типовими skip-ами і `.n-cursor.json:ignore`).
7
+ * 2. Якщо немає жодного `.rego` — пропустити перевірку (rego-tooling не вимагається).
8
+ * 3. Інакше — для кожного канонічного файла:
9
+ * - FS-existence (з повідомленням, якщо відсутній);
10
+ * - делегувати content-валідацію rego-пакетам через `runConftestBatch`:
11
+ * `rego.vscode_extensions` — `.vscode/extensions.json`: `tsandall.opa`
12
+ * у `recommendations`;
13
+ * `rego.vscode_settings` — `.vscode/settings.json`: `[rego]` з
14
+ * `editor.defaultFormatter: "tsandall.opa"` і `editor.formatOnSave: true`;
15
+ * `rego.package_json` — `package.json#scripts.lint-rego` має бути
16
+ * канонічним `"bun ./npm/scripts/lint-rego.mjs"`.
17
+ *
18
+ * Rego-полісі глобально у `lint-conftest` НЕ реєструються — це conditional
19
+ * правило (без `.rego` файлів вимоги не діють). Plan B: Rego-authoritative +
20
+ * JS-orchestrator з `runConftestBatch`.
21
+ *
22
+ * `bun run lint-rego` (`npm/scripts/lint-rego.mjs`) — окрема перевірка САМИХ
23
+ * rego-полісі (opa check / regal lint / conftest verify), не плутати з цим
24
+ * скриптом, який перевіряє ПРОЄКТНЕ оточення для роботи з rego.
25
+ */
26
+ import { existsSync } from 'node:fs'
27
+
28
+ import { createCheckReporter } from './utils/check-reporter.mjs'
29
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
30
+ import { runConftestBatch } from './utils/run-conftest-batch.mjs'
31
+ import { walkDir } from './utils/walkDir.mjs'
32
+
33
+ /** Список (path, namespace, policyDirRel) для трьох канонічних конфігів rego.mdc. */
34
+ const REGO_TARGETS = [
35
+ ['.vscode/extensions.json', 'rego.vscode_extensions', 'rego/vscode_extensions'],
36
+ ['.vscode/settings.json', 'rego.vscode_settings', 'rego/vscode_settings'],
37
+ ['package.json', 'rego.package_json', 'rego/package_json']
38
+ ]
39
+
40
+ /**
41
+ * Чи є хоча б один `.rego` файл у дереві від `cwd`.
42
+ * @param {string} root абсолютний шлях кореня
43
+ * @param {string[]} ignorePaths шляхи каталогів, повністю виключених з обходу
44
+ * @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.rego`
45
+ */
46
+ async function projectHasRegoFiles(root, ignorePaths) {
47
+ let found = false
48
+ await walkDir(
49
+ root,
50
+ p => {
51
+ if (p.endsWith('.rego')) {
52
+ found = true
53
+ }
54
+ },
55
+ ignorePaths
56
+ )
57
+ return found
58
+ }
59
+
60
+ /**
61
+ * Делегує content-валідацію одного канонічного конфіга rego-пакету через
62
+ * `runConftestBatch`. FS-існування — попередньо перевірено.
63
+ * @param {string} path відносний шлях до файлу
64
+ * @param {string} namespace rego-пакет (наприклад `rego.vscode_extensions`)
65
+ * @param {string} policyDirRel піддиректорія у `npm/policy/`
66
+ * @param {(msg: string) => void} pass success-репортер
67
+ * @param {(msg: string) => void} fail fail-репортер
68
+ * @returns {void}
69
+ */
70
+ function runRegoPolicyOnPath(path, namespace, policyDirRel, pass, fail) {
71
+ const violations = runConftestBatch({ policyDirRel, namespace, files: [path] })
72
+ if (violations.length === 0) {
73
+ pass(`${path} відповідає ${namespace} (rego)`)
74
+ return
75
+ }
76
+ for (const v of violations) fail(v.message)
77
+ }
78
+
79
+ /**
80
+ * Перевіряє відповідність проєкту правилам rego.mdc.
81
+ * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
82
+ */
83
+ export async function check() {
84
+ const reporter = createCheckReporter()
85
+ const { pass, fail } = reporter
86
+
87
+ const root = process.cwd()
88
+ const ignorePaths = await loadCursorIgnorePaths(root)
89
+ const hasRego = await projectHasRegoFiles(root, ignorePaths)
90
+ if (!hasRego) {
91
+ pass('Немає *.rego у дереві — rego-tooling не вимагається (rego.mdc)')
92
+ return reporter.getExitCode()
93
+ }
94
+
95
+ pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')
96
+
97
+ for (const [path, namespace, policyDirRel] of REGO_TARGETS) {
98
+ if (!existsSync(path)) {
99
+ fail(`${path} не існує — створи згідно rego.mdc (${namespace})`)
100
+ continue
101
+ }
102
+ runRegoPolicyOnPath(path, namespace, policyDirRel, pass, fail)
103
+ }
104
+
105
+ return reporter.getExitCode()
106
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Перевіряє інструментарій Tauri (tauri.mdc): VSCode `extensions.json` для
3
+ * проєктів, у яких є маркер Tauri.
4
+ *
5
+ * Cross-file gating (JS):
6
+ * 1. Tauri-маркер визначаємо за **будь-яким** з:
7
+ * - існує каталог `src-tauri/` у `cwd`;
8
+ * - існує файл `tauri.conf.json` у `cwd` або в workspace-пакетах;
9
+ * - кореневий `package.json#devDependencies` або `dependencies` містить
10
+ * ключ з префіксом `@tauri-apps/`.
11
+ * 2. Якщо маркера немає — пропустити перевірку (tauri-tooling не вимагається).
12
+ * 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
13
+ * content `rego.tauri.vscode_extensions` через `runConftestBatch`.
14
+ *
15
+ * Rego-полісі глобально у `lint-conftest` НЕ реєструється — це conditional
16
+ * правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
17
+ */
18
+ import { existsSync, statSync } from 'node:fs'
19
+ import { readFile } from 'node:fs/promises'
20
+
21
+ import { createCheckReporter } from './utils/check-reporter.mjs'
22
+ import { runConftestBatch } from './utils/run-conftest-batch.mjs'
23
+
24
+ /**
25
+ * Чи є префікс `@tauri-apps/` у ключах `dependencies` або `devDependencies`.
26
+ * @param {Record<string, unknown> | null | undefined} pkg розпарсений `package.json`
27
+ * @returns {boolean} true, якщо знайдено хоча б один `@tauri-apps/*`
28
+ */
29
+ function packageHasTauriDep(pkg) {
30
+ if (!pkg || typeof pkg !== 'object') return false
31
+ for (const field of ['dependencies', 'devDependencies']) {
32
+ const deps = /** @type {Record<string, unknown> | undefined} */ (pkg[field])
33
+ if (!deps || typeof deps !== 'object') continue
34
+ for (const name of Object.keys(deps)) {
35
+ if (name.startsWith('@tauri-apps/')) return true
36
+ }
37
+ }
38
+ return false
39
+ }
40
+
41
+ /**
42
+ * Чи є у проєкті маркер Tauri: `src-tauri/`, `tauri.conf.json` (root або
43
+ * workspace), або `@tauri-apps/*` у залежностях кореневого `package.json`.
44
+ * @returns {Promise<boolean>} true, якщо проєкт використовує Tauri
45
+ */
46
+ async function projectHasTauriMarker() {
47
+ if (existsSync('src-tauri') && statSync('src-tauri').isDirectory()) return true
48
+ if (existsSync('tauri.conf.json')) return true
49
+ if (!existsSync('package.json')) return false
50
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
51
+ if (packageHasTauriDep(pkg)) return true
52
+ return false
53
+ }
54
+
55
+ /**
56
+ * Перевіряє відповідність проєкту правилам tauri.mdc.
57
+ * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
58
+ */
59
+ export async function check() {
60
+ const reporter = createCheckReporter()
61
+ const { pass, fail } = reporter
62
+
63
+ const hasTauri = await projectHasTauriMarker()
64
+ if (!hasTauri) {
65
+ pass('Немає маркера Tauri (src-tauri/, tauri.conf.json, @tauri-apps/*) — tauri-tooling не вимагається')
66
+ return reporter.getExitCode()
67
+ }
68
+
69
+ pass('Знайдено маркер Tauri — перевіряємо канонічні конфіги tauri.mdc')
70
+
71
+ const extPath = '.vscode/extensions.json'
72
+ if (!existsSync(extPath)) {
73
+ fail(`${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" і "rust-lang.rust-analyzer" (tauri.mdc)`)
74
+ return reporter.getExitCode()
75
+ }
76
+ const violations = runConftestBatch({
77
+ policyDirRel: 'tauri/vscode_extensions',
78
+ namespace: 'tauri.vscode_extensions',
79
+ files: [extPath]
80
+ })
81
+ if (violations.length === 0) {
82
+ pass(`${extPath} відповідає tauri.vscode_extensions (rego)`)
83
+ } else {
84
+ for (const v of violations) fail(v.message)
85
+ }
86
+
87
+ return reporter.getExitCode()
88
+ }
@@ -131,7 +131,65 @@ 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
- ## 5. Після очистки
134
+
135
+ ## 5. Переклади
136
+
137
+ Замінити переклад з російської на англійську в @nitra/tfm, @nitra/tf та @nitra/tfm-node. Якщо англійська вже є, то прибираємо російську:
138
+
139
+ Приклад БУЛО:
140
+
141
+ <template>
142
+ <h5>{{ t`Привіт` }}</h5>
143
+ </template>
144
+
145
+ <script setup>
146
+ import tf from '@nitra/tf/webpack'
147
+
148
+ // Translate
149
+ const tr = {
150
+ 'Привіт': 'Привет'
151
+ }
152
+
153
+ const t = tf.bind({ tr })
154
+ </script>
155
+
156
+ СТАЛО
157
+
158
+ <template>
159
+ <h5>{{ t`Привіт` }}</h5>
160
+ </template>
161
+
162
+ <script setup>
163
+ import tf from '@nitra/tf/webpack'
164
+
165
+ // Translate
166
+ const tr = {
167
+ 'Привіт': 'Hello'
168
+ }
169
+
170
+ const t = tf.bind({ tr })
171
+ </script>
172
+
173
+ або
174
+
175
+ import { tf } from '@nitra/tfm'
176
+
177
+ const tr = {
178
+ Так: { ru: 'Да' },
179
+ Ні: { ru: 'Нет' }
180
+ }
181
+
182
+ на
183
+
184
+ import { tf } from '@nitra/tfm'
185
+
186
+ const tr = {
187
+ Так: { ru: 'Yes' },
188
+ Ні: { ru: 'No' }
189
+ }
190
+
191
+
192
+ ## 6. Після очистки
135
193
 
136
194
  - Переконайся, що `kustomization.yaml` у кожній директорії `k8s/` не посилається на видалені overlay або файли.
137
195
  - Пройдись `git grep` по репозиторію на залишки: `git grep -n -i -e '\bru\b' -e cr\.yandex -e country/ru -e prod-ru -e values-ru -e "'ya'"` — переглянь усі знахідки вручну, бо `ru` як слово може траплятися в легітимних контекстах (наприклад, `truncate`, `Aurum`, `cruft`). Видаляй лише ті входження, що належать ru-середовищу.