@nitra/cursor 1.8.204 → 1.8.207

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 (61) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/bin/auto-rules.md +2 -0
  3. package/mdc/rego.mdc +77 -0
  4. package/package.json +1 -1
  5. package/policy/abie/health_check_policy/health_check_policy.rego +73 -0
  6. package/policy/abie/http_route_base/http_route_base.rego +45 -0
  7. package/policy/adr/settings_json/settings_json.rego +31 -0
  8. package/policy/adr/settings_local_json/settings_local_json.rego +28 -0
  9. package/policy/bun/bunfig/bunfig.rego +33 -0
  10. package/policy/bun/package_json/package_json.rego +94 -0
  11. package/policy/capacitor/package_json/package_json.rego +45 -0
  12. package/policy/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -26
  13. package/policy/ga/clean_merged_branch/clean_merged_branch.rego +0 -25
  14. package/policy/ga/git_ai/git_ai.rego +83 -0
  15. package/policy/ga/lint_ga/lint_ga.rego +118 -0
  16. package/policy/ga/workflow_common/workflow_common.rego +161 -0
  17. package/policy/graphql/package_json/package_json.rego +35 -0
  18. package/policy/hasura/svc_hl/svc_hl.rego +27 -0
  19. package/policy/image_compress/package_json/package_json.rego +94 -0
  20. package/policy/js_bun_db/package_json/package_json.rego +28 -0
  21. package/policy/js_lint/lint_js_yml/lint_js_yml.rego +98 -0
  22. package/policy/js_lint/package_json/package_json.rego +137 -0
  23. package/policy/js_mssql/package_json/package_json.rego +57 -0
  24. package/policy/js_run/configmap/configmap.rego +45 -0
  25. package/policy/js_run/jsconfig/jsconfig.rego +66 -0
  26. package/policy/js_run/package_json/package_json.rego +31 -0
  27. package/policy/k8s/manifest/manifest.rego +130 -0
  28. package/policy/npm_module/emit_types_config/emit_types_config.rego +37 -0
  29. package/policy/npm_module/npm_package_json/npm_package_json.rego +55 -0
  30. package/policy/npm_module/npm_publish_yml/npm_publish_yml.rego +79 -0
  31. package/policy/npm_module/root_package_json/root_package_json.rego +28 -0
  32. package/policy/php/lint_php_yml/lint_php_yml.rego +32 -0
  33. package/policy/php/package_json/package_json.rego +19 -0
  34. package/policy/style_lint/lint_style_yml/lint_style_yml.rego +35 -0
  35. package/policy/style_lint/package_json/package_json.rego +49 -0
  36. package/policy/text/cspell/cspell.rego +91 -0
  37. package/policy/text/markdownlint/markdownlint.rego +21 -0
  38. package/policy/text/oxfmtrc/oxfmtrc.rego +90 -0
  39. package/policy/text/package_json/package_json.rego +88 -0
  40. package/policy/vue/package_json/package_json.rego +54 -0
  41. package/scripts/auto-rules.mjs +10 -0
  42. package/scripts/check-adr.mjs +7 -3
  43. package/scripts/check-bun.mjs +21 -117
  44. package/scripts/check-ga.mjs +0 -284
  45. package/scripts/check-graphql.mjs +6 -45
  46. package/scripts/check-hasura.mjs +4 -5
  47. package/scripts/check-image-avif.mjs +3 -3
  48. package/scripts/check-image-compress.mjs +25 -132
  49. package/scripts/check-js-bun-db.mjs +3 -50
  50. package/scripts/check-js-run.mjs +9 -12
  51. package/scripts/check-k8s.mjs +6 -5
  52. package/scripts/check-npm-module.mjs +17 -8
  53. package/scripts/check-php.mjs +16 -51
  54. package/scripts/check-style-lint.mjs +28 -52
  55. package/scripts/check-text.mjs +47 -219
  56. package/scripts/check-vue.mjs +3 -16
  57. package/scripts/lint-conftest.mjs +351 -0
  58. package/scripts/lint-ga.mjs +49 -2
  59. package/scripts/lint-rego.mjs +67 -21
  60. package/scripts/run-shellcheck-text.mjs +3 -6
  61. package/scripts/utils/depcheck-workflow.mjs +2 -6
@@ -0,0 +1,32 @@
1
+ # Порт перевірки `lint-php.yml` з `npm/scripts/check-php.mjs` (php.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .github/workflows/lint-php.yml -p npm/policy/php \
5
+ # --namespace php.lint_php_yml
6
+ #
7
+ # Перевіряє: хоча б один крок `run` містить `bun run lint-php`. Універсальні
8
+ # workflow-перевірки — у `ga.workflow_common`.
9
+ #
10
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
12
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
13
+ package php.lint_php_yml
14
+
15
+ import rego.v1
16
+
17
+ all_run_text := concat("\n", [run_text |
18
+ some job in object.get(input, "jobs", {})
19
+ some step in object.get(job, "steps", [])
20
+ run_text := step_run_to_text(step)
21
+ ])
22
+
23
+ deny contains msg if {
24
+ not contains(all_run_text, "bun run lint-php")
25
+ msg := "lint-php.yml: жоден крок run не містить `bun run lint-php` (php.mdc)"
26
+ }
27
+
28
+ step_run_to_text(step) := step.run if is_string(step.run)
29
+
30
+ else := concat("\n", [s | some s in step.run]) if is_array(step.run)
31
+
32
+ else := ""
@@ -0,0 +1,19 @@
1
+ # Порт перевірки `package.json` з `npm/scripts/check-php.mjs` (php.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test package.json -p npm/policy/php --namespace php.package_json
5
+ #
6
+ # Перевіряє: наявність скрипта `lint-php`. FS-перевірки (`composer.json`, наявність
7
+ # `package.json` як такого) — у JS.
8
+ #
9
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
10
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
11
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
12
+ package php.package_json
13
+
14
+ import rego.v1
15
+
16
+ deny contains msg if {
17
+ not object.get(object.get(input, "scripts", {}), "lint-php", false)
18
+ msg := "package.json: додай скрипт \"lint-php\" (php.mdc)"
19
+ }
@@ -0,0 +1,35 @@
1
+ # Порт перевірки `lint-style.yml` з `npm/scripts/check-style-lint.mjs` (style-lint.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .github/workflows/lint-style.yml -p npm/policy/style_lint \
5
+ # --namespace style_lint.lint_style_yml
6
+ #
7
+ # Перевіряє: хоча б один крок `run` містить `npx stylelint` (саме через npx, не
8
+ # `bun run lint-style`). Універсальні workflow-перевірки (concurrency, заборонені
9
+ # setup-bun/cache/install) — у `ga.workflow_common`.
10
+ #
11
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
12
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
13
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
14
+ package style_lint.lint_style_yml
15
+
16
+ import rego.v1
17
+
18
+ # Усі тексти `run:` зі steps усіх jobs, склеєні в один blob — для substring-перевірки.
19
+ all_run_text := concat("\n", [run_text |
20
+ some job in object.get(input, "jobs", {})
21
+ some step in object.get(job, "steps", [])
22
+ run_text := step_run_to_text(step)
23
+ ])
24
+
25
+ deny contains msg if {
26
+ not contains(all_run_text, "npx stylelint")
27
+ msg := "lint-style.yml: жоден крок run не містить `npx stylelint` (style-lint.mdc)"
28
+ }
29
+
30
+ # Текст `run:` як один рядок: підтримує string і array форми (YAML).
31
+ step_run_to_text(step) := step.run if is_string(step.run)
32
+
33
+ else := concat("\n", [s | some s in step.run]) if is_array(step.run)
34
+
35
+ else := ""
@@ -0,0 +1,49 @@
1
+ # Порт перевірок `package.json` з `npm/scripts/check-style-lint.mjs` (style-lint.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test package.json -p npm/policy/style_lint --namespace style_lint.package_json
5
+ #
6
+ # Перевіряє: наявність скрипта `lint-style` з `npx stylelint`, `@nitra/stylelint-config`
7
+ # у `devDependencies`, поле `stylelint.extends == "@nitra/stylelint-config"`. FS-частина
8
+ # (зовнішні `.stylelintrc.*` як альтернатива полю; `.stylelintignore`) лишається у JS.
9
+ #
10
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
12
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
13
+ package style_lint.package_json
14
+
15
+ import rego.v1
16
+
17
+ # ── deny: lint-style скрипт ───────────────────────────────────────────────
18
+
19
+ deny contains msg if {
20
+ not object.get(object.get(input, "scripts", {}), "lint-style", false)
21
+ msg := "package.json не містить скрипт \"lint-style\" (style-lint.mdc)"
22
+ }
23
+
24
+ deny contains msg if {
25
+ lint_style := object.get(object.get(input, "scripts", {}), "lint-style", "")
26
+ lint_style != ""
27
+ not contains(lint_style, "npx stylelint")
28
+ msg := sprintf("lint-style має викликати stylelint через npx (зараз: %q) (style-lint.mdc)", [lint_style])
29
+ }
30
+
31
+ # ── deny: @nitra/stylelint-config у devDependencies ───────────────────────
32
+
33
+ deny contains msg if {
34
+ dev := object.get(input, "devDependencies", {})
35
+ not "@nitra/stylelint-config" in object.keys(dev)
36
+ msg := "@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config (style-lint.mdc)"
37
+ }
38
+
39
+ # ── deny: поле stylelint.extends = "@nitra/stylelint-config" ──────────────
40
+ #
41
+ # JS-перевірка дозволяє альтернативу: окремий файл `.stylelintrc.*`. Цю частину
42
+ # перевіряємо в JS (FS-вибірка); тут — лише структурна валідація поля, якщо воно є.
43
+
44
+ deny contains msg if {
45
+ cfg := object.get(input, "stylelint", null)
46
+ is_object(cfg)
47
+ object.get(cfg, "extends", null) != "@nitra/stylelint-config"
48
+ msg := "package.json: stylelint.extends має бути \"@nitra/stylelint-config\" (style-lint.mdc)"
49
+ }
@@ -0,0 +1,91 @@
1
+ # Порт перевірок `.cspell.json` з `npm/scripts/check-text.mjs` (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .cspell.json -p npm/policy/text --namespace text.cspell
5
+ #
6
+ # Перевіряє: `version: "0.2"`, наявність `language`, імпорт `@nitra/cspell-dict`,
7
+ # відсутність прямих імпортів `@cspell/dict-*`, обовʼязкові glob-и в `ignorePaths`
8
+ # (text.mdc).
9
+ #
10
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
12
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
13
+ package text.cspell
14
+
15
+ import rego.v1
16
+
17
+ # ── Очікувані значення ─────────────────────────────────────────────────────
18
+
19
+ # Канонічні `ignorePaths` з text.mdc — кожен має бути присутнім.
20
+ required_ignore_paths := {
21
+ "**/node_modules/**",
22
+ "**/vscode-extension/**",
23
+ "**/.git/**",
24
+ ".vscode",
25
+ "report",
26
+ "*.svg",
27
+ "**/k8s/**/*.yaml",
28
+ }
29
+
30
+ nitra_cspell_dict_marker := "@nitra/cspell-dict"
31
+
32
+ legacy_dict_marker := "@cspell/dict-"
33
+
34
+ # Шаблон повідомлення про заборонений імпорт `@cspell/dict-*` — через `concat`
35
+ # для regal style/line-length.
36
+ legacy_dict_import_template := concat(" ", [
37
+ ".cspell.json не має імпортувати @cspell/dict-* —",
38
+ "використовуй лише @nitra/cspell-dict (знайдено: %s) (text.mdc)",
39
+ ])
40
+
41
+ # ── deny: version / language ──────────────────────────────────────────────
42
+
43
+ deny contains msg if {
44
+ object.get(input, "version", null) != "0.2"
45
+ msg := ".cspell.json: version має бути \"0.2\" (text.mdc)"
46
+ }
47
+
48
+ deny contains msg if {
49
+ not object.get(input, "language", false)
50
+ msg := ".cspell.json: відсутнє поле language (text.mdc)"
51
+ }
52
+
53
+ # ── deny: imports ─────────────────────────────────────────────────────────
54
+
55
+ deny contains msg if {
56
+ imports := object.get(input, "import", [])
57
+ is_array(imports)
58
+ not has_nitra_dict_import(imports)
59
+ msg := ".cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json (text.mdc)"
60
+ }
61
+
62
+ deny contains msg if {
63
+ imports := object.get(input, "import", [])
64
+ is_array(imports)
65
+ some imp in imports
66
+ is_string(imp)
67
+ contains(imp, legacy_dict_marker)
68
+ msg := sprintf(legacy_dict_import_template, [imp])
69
+ }
70
+
71
+ # ── deny: ignorePaths ─────────────────────────────────────────────────────
72
+
73
+ deny contains msg if {
74
+ not is_array(object.get(input, "ignorePaths", null))
75
+ msg := ".cspell.json: додай масив ignorePaths з канонічними glob-ами (text.mdc)"
76
+ }
77
+
78
+ deny contains msg if {
79
+ is_array(input.ignorePaths)
80
+ some path in required_ignore_paths
81
+ not path in {p | some p in input.ignorePaths}
82
+ msg := sprintf(".cspell.json ignorePaths: додай %q (text.mdc)", [path])
83
+ }
84
+
85
+ # ── helpers ────────────────────────────────────────────────────────────────
86
+
87
+ has_nitra_dict_import(imports) if {
88
+ some imp in imports
89
+ is_string(imp)
90
+ contains(imp, nitra_cspell_dict_marker)
91
+ }
@@ -0,0 +1,21 @@
1
+ # Порт перевірки `.markdownlint-cli2.jsonc` з `npm/scripts/check-text.mjs` (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .markdownlint-cli2.jsonc -p npm/policy/text \
5
+ # --namespace text.markdownlint --parser json
6
+ #
7
+ # Конфтест парсить `.jsonc` як JSON лише якщо файл — валідний JSON (без коментарів).
8
+ # У випадку справжнього JSONC з `//` коментарями цей крок мовчки ігноруватиметься
9
+ # (conftest skip). FS-перевірка (наявність файлу) живе у JS.
10
+ #
11
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
12
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
13
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
14
+ package text.markdownlint
15
+
16
+ import rego.v1
17
+
18
+ deny contains msg if {
19
+ object.get(input, "gitignore", null) != true
20
+ msg := ".markdownlint-cli2.jsonc: додай на верхньому рівні \"gitignore\": true (text.mdc)"
21
+ }
@@ -0,0 +1,90 @@
1
+ # Порт перевірок `.oxfmtrc.json` з `npm/scripts/check-text.mjs` (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .oxfmtrc.json -p npm/policy/text --namespace text.oxfmtrc
5
+ #
6
+ # Перевіряє: обовʼязкові ключі, канонічні значення (`semi=false`, `singleQuote=true`,
7
+ # `tabWidth=2`, `useTabs=false`, `printWidth=120`), масив `ignorePatterns` з
8
+ # канонічними glob-ами (hasura/metadata, schema.graphql, auto-imports.d.ts).
9
+ #
10
+ # FS-перевірки (наявність самого `.oxfmtrc.json`, `.prettierrc.*` файлів) живуть
11
+ # у `check-text.mjs`. Тут — лише про вже завантажений input.
12
+ #
13
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
14
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
15
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
16
+ package text.oxfmtrc
17
+
18
+ import rego.v1
19
+
20
+ # ── Очікувані значення ─────────────────────────────────────────────────────
21
+
22
+ required_keys := [
23
+ "arrowParens",
24
+ "printWidth",
25
+ "bracketSpacing",
26
+ "bracketSameLine",
27
+ "semi",
28
+ "singleQuote",
29
+ "tabWidth",
30
+ "trailingComma",
31
+ "useTabs",
32
+ ]
33
+
34
+ required_ignore_patterns := {
35
+ "**/hasura/metadata/**",
36
+ "**/schema.graphql",
37
+ "**/auto-imports.d.ts",
38
+ }
39
+
40
+ # ── deny: обовʼязкові ключі ────────────────────────────────────────────────
41
+
42
+ deny contains msg if {
43
+ some key in required_keys
44
+ not key in object.keys(input)
45
+ msg := sprintf(".oxfmtrc.json: відсутній обовʼязковий ключ %q (text.mdc)", [key])
46
+ }
47
+
48
+ # ── deny: канонічні значення ───────────────────────────────────────────────
49
+ #
50
+ # `object.get(…, sentinel)` робить значення визначеним — інакше при відсутньому
51
+ # ключі порівняння дало б `undefined`, не `true`, і правило мовчки не спрацювало б.
52
+
53
+ deny contains msg if {
54
+ object.get(input, "semi", null) != false
55
+ msg := ".oxfmtrc.json: semi має бути false (text.mdc)"
56
+ }
57
+
58
+ deny contains msg if {
59
+ object.get(input, "singleQuote", null) != true
60
+ msg := ".oxfmtrc.json: singleQuote має бути true (text.mdc)"
61
+ }
62
+
63
+ deny contains msg if {
64
+ object.get(input, "tabWidth", null) != 2
65
+ msg := ".oxfmtrc.json: tabWidth має бути 2 (text.mdc)"
66
+ }
67
+
68
+ deny contains msg if {
69
+ object.get(input, "useTabs", null) != false
70
+ msg := ".oxfmtrc.json: useTabs має бути false (text.mdc)"
71
+ }
72
+
73
+ deny contains msg if {
74
+ object.get(input, "printWidth", null) != 120
75
+ msg := ".oxfmtrc.json: printWidth має бути 120 (text.mdc)"
76
+ }
77
+
78
+ # ── deny: ignorePatterns ───────────────────────────────────────────────────
79
+
80
+ deny contains msg if {
81
+ not is_array(object.get(input, "ignorePatterns", null))
82
+ msg := ".oxfmtrc.json: додай масив ignorePatterns з канонічними glob-ами (text.mdc)"
83
+ }
84
+
85
+ deny contains msg if {
86
+ is_array(input.ignorePatterns)
87
+ some pattern in required_ignore_patterns
88
+ not pattern in {p | some p in input.ignorePatterns}
89
+ msg := sprintf(".oxfmtrc.json ignorePatterns: додай %q (text.mdc)", [pattern])
90
+ }
@@ -0,0 +1,88 @@
1
+ # Порт текст-специфічних перевірок `package.json` з `npm/scripts/check-text.mjs` (text.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test package.json -p npm/policy/text --namespace text.package_json
5
+ #
6
+ # Перевіряє: відсутність Prettier (поле + конфіги в deps), `@nitra/cspell-dict ^2.0.0+`
7
+ # у `devDependencies`, заборона `markdownlint-cli2` у dependencies/devDependencies.
8
+ #
9
+ # Перевірка скрипта `lint-text` (cspell, run-shellcheck-text.mjs, markdownlint, v8r,
10
+ # обовʼязкові glob-и для v8r) — у JS-частині (`check-text.mjs`): занадто варіативна
11
+ # для декларативної політики (3 режими v8r з різними вимогами до глобів).
12
+ #
13
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
14
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
15
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
16
+ package text.package_json
17
+
18
+ import rego.v1
19
+
20
+ # ── Заборонені пакети у dependencies/devDependencies ──────────────────────
21
+
22
+ forbidden_packages := {
23
+ "prettier": "Prettier заборонено — використовуй oxfmt (text.mdc)",
24
+ "@nitra/prettier-config": "Prettier-конфіг заборонено — використовуй oxfmt (text.mdc)",
25
+ }
26
+
27
+ # ── deny: заборонене поле `prettier` у package.json ───────────────────────
28
+
29
+ deny contains msg if {
30
+ object.get(input, "prettier", null) != null
31
+ msg := "package.json містить поле \"prettier\" — видали його (text.mdc)"
32
+ }
33
+
34
+ # ── deny: prettier у dependencies/devDependencies ─────────────────────────
35
+
36
+ deny contains msg if {
37
+ some pkg, hint in forbidden_packages
38
+ pkg in object.keys(object.get(input, "dependencies", {}))
39
+ msg := sprintf("package.json: dependencies містить %q — %s", [pkg, hint])
40
+ }
41
+
42
+ deny contains msg if {
43
+ some pkg, hint in forbidden_packages
44
+ pkg in object.keys(object.get(input, "devDependencies", {}))
45
+ msg := sprintf("package.json: devDependencies містить %q — %s", [pkg, hint])
46
+ }
47
+
48
+ # ── deny: markdownlint-cli2 не повинен бути у залежностях ─────────────────
49
+ #
50
+ # Канонічний виклик — `bunx markdownlint-cli2` у `lint-text`, без оголошення пакета.
51
+
52
+ deny contains msg if {
53
+ "markdownlint-cli2" in object.keys(object.get(input, "dependencies", {}))
54
+ msg := "package.json: dependencies містить markdownlint-cli2 — використовуй bunx у lint-text (text.mdc)"
55
+ }
56
+
57
+ deny contains msg if {
58
+ "markdownlint-cli2" in object.keys(object.get(input, "devDependencies", {}))
59
+ msg := "package.json: devDependencies містить markdownlint-cli2 — використовуй bunx у lint-text (text.mdc)"
60
+ }
61
+
62
+ # ── deny: @nitra/cspell-dict ^2.0.0+ обовʼязковий ─────────────────────────
63
+
64
+ deny contains msg if {
65
+ dev := object.get(input, "devDependencies", {})
66
+ not "@nitra/cspell-dict" in object.keys(dev)
67
+ msg := "@nitra/cspell-dict у devDependencies обовʼязковий — bun add -d @nitra/cspell-dict@^2.0.0 (text.mdc)"
68
+ }
69
+
70
+ deny contains msg if {
71
+ range := object.get(object.get(input, "devDependencies", {}), "@nitra/cspell-dict", "")
72
+ range != ""
73
+ not cspell_dict_major_at_least_2(range)
74
+ msg := sprintf("@nitra/cspell-dict має бути ^2.0.0 або новіший (зараз %q) (text.mdc)", [range])
75
+ }
76
+
77
+ # ── helpers ────────────────────────────────────────────────────────────────
78
+
79
+ # Чи мажорна версія cspell-dict ≥ 2. Підтримує `^2.0.0`, `~2.x`, `2.5.0`,
80
+ # `>=2.0.0`, `workspace:*` (тоді fallback false), із префіксом і без.
81
+ # Regex `^[\^~>=<]*\s*(\d+)` дістає першу цифру після опціональних range-операторів.
82
+ cspell_dict_major_at_least_2(range) if {
83
+ # `regex.find_n` повертає масив збігів; беремо перший і дивимось на перше число.
84
+ match := regex.find_n(`^[\^~>=<]*\s*(\d+)`, range, 1)
85
+ count(match) > 0
86
+ major := to_number(regex.replace(match[0], `^[\^~>=<]*\s*`, ""))
87
+ major >= 2
88
+ }
@@ -0,0 +1,54 @@
1
+ # Порт перевірки версій з `package.json` для Vue+Vite пакетів з
2
+ # `npm/scripts/check-vue.mjs` (vue.mdc).
3
+ #
4
+ # Запуск (локально, у Vue+Vite-пакеті):
5
+ # conftest test path/to/package.json -p npm/policy/vue \
6
+ # --namespace vue.package_json
7
+ #
8
+ # Перевіряє: якщо в `dependencies` є `vue`, то у `devDependencies.vite` має бути
9
+ # мажорна версія ≥ 8.
10
+ #
11
+ # AST-сканування коду (заборона явних value-імпортів `from 'vue'`, заборона
12
+ # Node-нативних модулів у `.vue` SFC, перевірка `vite.config` на
13
+ # `process.env.npm_lifecycle_event`, vue-macros, auto-import тощо), а також
14
+ # FS-перевірки (`src/vite-env.d.ts`, `jsconfig.json` у корені пакета) — у JS.
15
+ #
16
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
17
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
18
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
19
+ package vue.package_json
20
+
21
+ import rego.v1
22
+
23
+ deny contains msg if {
24
+ uses_vue
25
+ not vite_in_dev_dependencies
26
+ msg := "Vue-пакет: відсутня залежність `vite` у devDependencies (vue.mdc)"
27
+ }
28
+
29
+ deny contains msg if {
30
+ uses_vue
31
+ vite_in_dev_dependencies
32
+ not vite_major_at_least_8
33
+ vite_range := input.devDependencies.vite
34
+ msg := sprintf("Vue-пакет: vite має бути >= 8 (зараз %q) (vue.mdc)", [vite_range])
35
+ }
36
+
37
+ # ── helpers ────────────────────────────────────────────────────────────────
38
+
39
+ uses_vue if {
40
+ "vue" in object.keys(object.get(input, "dependencies", {}))
41
+ }
42
+
43
+ vite_in_dev_dependencies if {
44
+ "vite" in object.keys(object.get(input, "devDependencies", {}))
45
+ }
46
+
47
+ vite_major_at_least_8 if {
48
+ range := input.devDependencies.vite
49
+
50
+ # Перша мажорна цифра з рядка: `^8`, `>=8.0.0`, `8.x` → 8.
51
+ match := regex.find_n(`\d+`, range, 1)
52
+ count(match) > 0
53
+ to_number(match[0]) >= 8
54
+ }
@@ -41,6 +41,7 @@ export const AUTO_RULE_ORDER = Object.freeze([
41
41
  'nginx-default-tpl',
42
42
  'npm-module',
43
43
  'php',
44
+ 'rego',
44
45
  'style-lint',
45
46
  'text',
46
47
  'vue'
@@ -104,6 +105,7 @@ export const AUTO_RULE_DEPENDENCIES = Object.freeze(
104
105
  const ABIE_REPOSITORY_URL_MARKER = 'https://github.com/abinbevefes/'
105
106
  const HASURA_CONFIG_MARKER = 'metadata_directory: metadata'
106
107
  const JS_LIKE_RE = /\.(?:mjs|cjs|js|jsx|ts|tsx)$/iu
108
+ const REGO_RE = /\.rego$/iu
107
109
  const STYLE_RE = /\.(?:css|vue)$/iu
108
110
  const VUE_RE = /\.vue$/iu
109
111
  const NGINX_DEFAULT_FILES = new Set(['default.conf.template', 'default.conf', 'nginx.conf'])
@@ -287,6 +289,7 @@ function updateDirFacts(dirName, facts) {
287
289
  * hasDockerfile: boolean,
288
290
  * hasJsLikeSource: boolean,
289
291
  * hasNginxDefaultTplFile: boolean,
292
+ * hasRegoFile: boolean,
290
293
  * hasVueOrCssSource: boolean,
291
294
  * hasVueSource: boolean
292
295
  * }} facts агреговані факти
@@ -311,6 +314,9 @@ function updateFileFacts(fileName, relPath, facts) {
311
314
  if (STYLE_RE.test(relPath)) {
312
315
  facts.hasVueOrCssSource = true
313
316
  }
317
+ if (REGO_RE.test(relPath)) {
318
+ facts.hasRegoFile = true
319
+ }
314
320
  }
315
321
 
316
322
  /**
@@ -401,6 +407,7 @@ async function updateHasuraFactFromFile(absPath, fileName, facts) {
401
407
  * hasHasuraConfig: boolean,
402
408
  * hasJsLikeSource: boolean,
403
409
  * hasNginxDefaultTplFile: boolean,
410
+ * hasRegoFile: boolean,
404
411
  * hasVueOrCssSource: boolean,
405
412
  * hasVueSource: boolean
406
413
  * }} facts агреговані факти
@@ -489,6 +496,7 @@ export function isMonorepoPackage(packageJson) {
489
496
  * hasJsLikeSource: boolean,
490
497
  * hasK8sDir: boolean,
491
498
  * hasNginxDefaultTplFile: boolean,
499
+ * hasRegoFile: boolean,
492
500
  * hasTempoDir: boolean,
493
501
  * hasVueSource: boolean,
494
502
  * hasVueOrCssSource: boolean
@@ -505,6 +513,7 @@ export async function collectAutoRuleFacts(root) {
505
513
  hasJsLikeSource: false,
506
514
  hasK8sDir: false,
507
515
  hasNginxDefaultTplFile: false,
516
+ hasRegoFile: false,
508
517
  hasTempoDir: false,
509
518
  hasVueSource: false,
510
519
  hasVueOrCssSource: false
@@ -650,6 +659,7 @@ export async function detectAutoRulesAndSkills({
650
659
  { enabled: facts.hasNginxDefaultTplFile, id: 'nginx-default-tpl' },
651
660
  { enabled: npmDirExists, id: 'npm-module' },
652
661
  { enabled: composerJsonExists, id: 'php' },
662
+ { enabled: facts.hasRegoFile, id: 'rego' },
653
663
  { enabled: facts.hasVueOrCssSource, id: 'style-lint' }
654
664
  ]
655
665
  for (const item of autoRuleChecks) {
@@ -26,6 +26,7 @@ const PROJECT_SETTINGS_PATH = '.claude/settings.json'
26
26
  const PROJECT_LOCAL_SETTINGS_PATH = '.claude/settings.local.json'
27
27
  const PROJECT_LOG_PATH = '.claude/hooks/capture-decisions.log'
28
28
  const HOOK_COMMAND_MARKER = '.claude/hooks/capture-decisions.sh'
29
+ const EOL_RE = /\r?\n/u
29
30
 
30
31
  const here = dirname(fileURLToPath(import.meta.url))
31
32
  /** Канонічний bundled-скрипт у пакеті — джерело правди для звірки з проєктним. */
@@ -68,7 +69,10 @@ async function checkHookScript(reporter) {
68
69
  fail(`канонічний скрипт у пакеті не знайдено: ${BUNDLED_HOOK_PATH} — перевстанови @nitra/cursor`)
69
70
  return
70
71
  }
71
- const [project, bundled] = await Promise.all([readFile(PROJECT_HOOK_PATH, 'utf8'), readFile(BUNDLED_HOOK_PATH, 'utf8')])
72
+ const [project, bundled] = await Promise.all([
73
+ readFile(PROJECT_HOOK_PATH, 'utf8'),
74
+ readFile(BUNDLED_HOOK_PATH, 'utf8')
75
+ ])
72
76
  if (project === bundled) {
73
77
  pass(`${PROJECT_HOOK_PATH} збігається з канонічним`)
74
78
  } else {
@@ -180,9 +184,9 @@ async function checkGitignore(reporter) {
180
184
  }
181
185
  const content = await readFile('.gitignore', 'utf8')
182
186
  const covers = content
183
- .split(/\r?\n/u)
187
+ .split(EOL_RE)
184
188
  .map(l => l.trim())
185
- .some(gitignoreLineCoversHookLog)
189
+ .some(line => gitignoreLineCoversHookLog(line))
186
190
  if (covers) {
187
191
  pass(`.gitignore покриває ${PROJECT_LOG_PATH}`)
188
192
  } else {