@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
package/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.207] - 2026-05-08
8
+
9
+ ### Added
10
+
11
+ - `npm/policy/ga/workflow_common/workflow_common.rego` — універсальні Rego-перевірки для **кожного** `.github/workflows/*.yml`: блок `concurrency` (group / cancel-in-progress), заборонені `oven-sh/setup-bun` / `actions/cache` / `bun install` у `uses`/`run` будь-якого кроку, заборонене shell-продовження `\` перед NL у `run:`, обовʼязковий `actions/checkout@…` перед локальним composite-action `setup-bun-deps`. Підключено в `lint-ga.mjs` як один прогін `conftest test <…all yml…> --namespace ga.workflow_common`.
12
+ - `npm/policy/bun/{bunfig,package_json}/*.rego` — порт `check-bun.mjs` (TOML і JSON-частина): `[install].linker == "hoisted"` у `bunfig.toml`; у кореневому `package.json` без `packageManager`, без `dependencies`, у `devDependencies` лише `@nitra/*`; агрегований `lint`-скрипт покриває всі `lint-*` через `bun run` і завершується `&& oxfmt .`.
13
+ - `npm/policy/text/{oxfmtrc,cspell,markdownlint,package_json}/*.rego` — порт `check-text.mjs`: `.oxfmtrc.json` обовʼязкові ключі і канонічні значення; `.cspell.json` `version "0.2"`, `language`, імпорт `@nitra/cspell-dict`, заборона `@cspell/dict-*`, обовʼязкові `ignorePaths`; `.markdownlint-cli2.jsonc` `gitignore: true`; `package.json` без Prettier, `@nitra/cspell-dict ^2.0.0+`, без `markdownlint-cli2` у залежностях.
14
+ - `npm/policy/style_lint/{package_json,lint_style_yml}/*.rego` — порт `check-style-lint.mjs`: скрипт `lint-style` через `npx stylelint`, `@nitra/stylelint-config` у `devDependencies`, `stylelint.extends == "@nitra/stylelint-config"`; у `lint-style.yml` хоча б один `run` з `npx stylelint`.
15
+ - `npm/policy/php/{package_json,lint_php_yml}/*.rego` — порт `check-php.mjs`: скрипт `lint-php` у `package.json`; у `lint-php.yml` хоча б один `run` з `bun run lint-php`.
16
+ - `npm/policy/npm_module/{root_package_json,npm_package_json,emit_types_config,npm_publish_yml}/*.rego` — порт `check-npm-module.mjs`: `workspaces ∋ "npm"` у кореневому `package.json`; у `npm/package.json` `types` відповідає одному з канонічних патернів і `files ∋ "types"`; `npm/tsconfig.emit-types.json` має канонічні `compilerOptions`; `.github/workflows/npm-publish.yml` має `on.push.paths ∋ "npm/**"`, `branches ∋ "main"`, `permissions.id-token: write` і крок `JS-DevTools/npm-publish` з `with.package: npm/package.json`.
17
+ - `npm/policy/k8s/manifest/manifest.rego` — порт пер-документних структурних правил `check-k8s.mjs`: `kind: Ingress` заборонено (Gateway API), `apiVersion: autoscaling/v1` заборонено (HPA → v2), у `kind: Service` заборонені анотації `cloud.google.com/neg` / `cloud.google.com/backend-config`, у `kind: Deployment` кожен контейнер `containers`+`initContainers` має непорожнє `resources.requests.cpu`. Cross-file Kustomize-логіка (svc/svc-hl, HPA/PDB, namespace base, kustomization patches) лишається в JS.
18
+ - `npm/policy/js_lint/{package_json,lint_js_yml}/*.rego` — порт `check-js-lint.mjs`: канонічний `lint-js`, `@nitra/eslint-config ≥ 3.9.2`, `engines.node ≥ 24`, `engines.bun ≥ 1.3`, `type: "module"`; у `lint-js.yml` `actions/checkout@v6` з `persist-credentials: false`, `setup-bun-deps`, `bunx oxlint/eslint/jscpd .`, без `--fix` у CI.
19
+ - `npm/policy/js_mssql/package_json/package_json.rego` — порт `check-js-mssql.mjs`: `dependencies.mssql ≥ 12.5.0` (підтримує `^12.5.0`, `>=12.5.0`, `workspace:*`).
20
+ - `npm/policy/js_bun_db/package_json/package_json.rego` — порт `check-js-bun-db.mjs`: у `dependencies` заборонені `pg`, `pg-format`, `mysql2`.
21
+ - `npm/policy/js_run/{package_json,jsconfig,configmap}/*.rego` — порт `check-js-run.mjs`: заборона `bunyan` / `@nitra/bunyan` у залежностях; `jsconfig.json` має канонічні `compilerOptions` і `include`; у k8s ConfigMap `OTEL_RESOURCE_ATTRIBUTES` містить `service.name=` і `service.namespace=`.
22
+ - `npm/policy/vue/package_json/package_json.rego` — порт `check-vue.mjs`: якщо `dependencies.vue` присутній, у `devDependencies` має бути `vite` мажорної версії ≥ 8.
23
+ - `npm/policy/graphql/package_json/package_json.rego` — порт `check-graphql.mjs`: `scripts.dump-schema` точно відповідає канонічному.
24
+ - `npm/policy/image_compress/package_json/package_json.rego` — порт `check-image-compress.mjs`: `lint-image` викликає `npx @nitra/minify-image --src=. --write` без `--avif`; агрегований `lint` містить `bun run lint-image`; `@nitra/minify-image` НЕ у `dependencies`/`devDependencies`.
25
+ - `npm/policy/hasura/svc_hl/svc_hl.rego` — порт `check-hasura.mjs` (мінімум): у `hasura/k8s/base/svc-hl.yaml` Service з `metadata.name` має закінчуватись на `-h`.
26
+ - `npm/policy/adr/{settings_json,settings_local_json}/*.rego` — порт `check-adr.mjs`: `.claude/settings.json` має містити Stop-hook з командою `.claude/hooks/capture-decisions.sh`; `.claude/settings.local.json` (якщо існує) — НЕ повинен мати дубля цього хука.
27
+ - `npm/policy/capacitor/package_json/package_json.rego` — порт `check-capacitor.mjs`: `dependencies['@capacitor/core']` мажорна ≥ 8 (підтримує `workspace:*`).
28
+ - `npm/policy/abie/{health_check_policy,http_route_base}/*.rego` — порт `check-abie.mjs`: `HealthCheckPolicy` (`networking.gke.io/v1`) має непорожній `requestPath` зі слешем, `port: 8080`, `targetRef.name` закінчується на `-hl`; `HTTPRoute` у `…/base/…` приймає лише hostnames у домені `aiml.live`.
29
+ - `npm/scripts/lint-conftest.mjs` (+ `bun run lint-conftest` у `package.json`) — єдиний раннер conftest по всіх нових polysi: для кожного namespace — single-file або walk-предикат, з gating-ом по `.n-cursor.json:rules`, як у `check-*.mjs`. Викликається в кореневому `lint` після `lint-rego`.
30
+
31
+ ### Changed
32
+
33
+ - `npm/policy/ga/{lint_ga,clean_ga_workflows,clean_merged_branch,git_ai}/*.rego`: прибрано дублікати правил `concurrency` (group / cancel-in-progress / missing) — їх покриває `ga.workflow_common`. Заодно усунено мовчазний баг `not is_object(input.concurrency)` (коли поля немає, повертає `undefined`, не `true`); у `workflow_common` через `object.get(input, "concurrency", false)` дає визначене значення. Канонічна тригер-група `expected_concurrency_group` теж видалена з кожної per-workflow polysi.
34
+ - `npm/scripts/lint-ga.mjs`: до існуючих per-workflow conftest-таргетів додано фінальний прогін `ga.workflow_common` одним викликом `conftest test <усі .yml> --namespace ga.workflow_common`. Імпорт `readdirSync` з `node:fs` для перерахунку workflow-файлів.
35
+
36
+ ## [1.8.206] - 2026-05-08
37
+
38
+ ### Added
39
+
40
+ - `mdc/rego.mdc` (нова версія 1.1, з 1.0): VS Code-секція з рекомендованим розширенням `tsandall.opa` (LSP від автора OPA: підсвічування, hover, go-to-definition, format-on-save через `opa fmt`), `.vscode/extensions.json` і `.vscode/settings.json` сніпети для `[rego]` (`editor.defaultFormatter: tsandall.opa`, `formatOnSave: true`); опис кроків `lint-rego` (preflight `opa`+`regal`, далі `opa check --strict` і `regal lint`); `package.json`-сніпет зі скриптом `lint-rego`; install-команди (`brew install opa regal` + universal лінки); приклад `.regal/config.yaml`. Раніше файл містив лише placeholder `npx @nitra/cursor check rego`.
41
+
42
+ ### Changed
43
+
44
+ - `scripts/lint-rego.mjs`: додано preflight на `opa` (поряд з `regal`) з install-hint `brew install opa` і покликом до VS Code-розширення `tsandall.opa`; до `regal lint` додано попередній крок `opa check --strict <targets>` (типи + строгий режим: мертвий код, неоднозначні правила, незадекларовані змінні) — `opa check` ловить compile-помилки, які `regal` навмисно лишає поза скоупом. Якщо хоч один з `opa`/`regal` відсутній у `PATH` — exit 1 ще до запуску, з підказкою встановлення для обох.
45
+
46
+ ## [1.8.205] - 2026-05-08
47
+
48
+ ### Added
49
+
50
+ - `npm/policy/ga/lint_ga/lint_ga.rego` — порт `validateLintGaWorkflowStructure` + `validateLintGaOnTriggers`: `name` / `on.push.branches∋{dev,main}` / `on.pull_request.branches∋{dev,main}` / `on.push.paths∋{.github/actions/**,.github/workflows/**}` / `concurrency` / `jobs.lint-ga.runs-on` / `jobs.lint-ga.permissions.contents=read` / `steps` non-empty / `uses` set містить `actions/checkout@v6`, `./.github/actions/setup-bun-deps`, `astral-sh/setup-uv@v8.0.0` / `run` blob містить `bun run lint-ga`.
51
+ - `npm/policy/ga/git_ai/git_ai.rego` — порт `validateGitAiWorkflowStructure`: `name` / `on.pull_request.types∋closed` / `concurrency` / `jobs.git-ai.if` містить `merged == true` / `permissions.contents=write` / `run` blob містить `curl … usegitai.com … bash` і `git-ai ci github run`.
52
+
53
+ ### Changed
54
+
55
+ - `scripts/lint-ga.mjs`: `CONFTEST_TARGETS` тепер охоплює всі 4 канонічні GA-workflow — `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `lint-ga.yml`, `git-ai.yml` — кожен зі своїм `--namespace ga.<name>`.
56
+ - `scripts/check-ga.mjs`: видалено `validateLintGaWorkflowStructure`, `validateLintGaOnTriggers`, `validateGitAiWorkflowStructure`, `validateGitAiParsedYaml`, `hasPullRequestClosedTrigger`, `hasJobMergedCondition`, `checkLintGaWorkflow`, `checkGitAiWorkflow`, `checkCanonicalWorkflowsMatchRule`, локальний `isExactString` і відповідні імпорти `anyRunStepIncludes`/`flattenWorkflowSteps`/`getStepRun`/`getStepUses`. Файл скоротився з 1074 → 570 рядків (≈47%) — структурні перевірки канонічних GA-workflow повністю мігрували в conftest. У JS лишилися: file-existence (zizmor.yml, .vscode/settings.json, setup-bun-deps), `package.json` script `lint-ga`, MegaLinter-зачистка, `verifyConcurrencyBlock` для всіх workflow без винятків (включно з не-канонічними), `verifyNoDirectBunOrCache`, `verifyCheckoutBeforeLocalSetupBunDeps`, paths-globs через `git ls-files`, preflight `shellcheck`.
57
+
7
58
  ## [1.8.204] - 2026-05-07
8
59
 
9
60
  ### Changed
@@ -120,7 +171,7 @@
120
171
  ### Added
121
172
 
122
173
  - `run-shellcheck-text.mjs`: для `lint-text` — перевірка наявності `shellcheck`/`patch`, авто-виправлення через `shellcheck -f diff` + `patch -p1`, фінальний прогін по tracked `*.sh` (git) або `**/*.sh` без `node_modules`.
123
- - `text` (mdc v1.25 → v1.26): **shellcheck** у ланцюжку `lint-text`, рекомендація **`timonwong.shellcheck`**, тригер workflow **`**/*.sh`**; тести `run-shellcheck-text.test.mjs`.
174
+ - `text` (mdc v1.25 → v1.26): **shellcheck** у ланцюжку `lint-text`, рекомендація **`timonwong.shellcheck`**, тригер workflow **`**/\*.sh`**; тести `run-shellcheck-text.test.mjs`.
124
175
 
125
176
  ### Changed
126
177
 
package/bin/auto-rules.md CHANGED
@@ -42,6 +42,8 @@ npm-module - якщо в корені присутня директорія npm
42
42
 
43
43
  php - якщо в корені є composer.json
44
44
 
45
+ rego - якщо в проекті є хоч один rego
46
+
45
47
  style-lint - якщо присутній хоч один vue або css файл
46
48
 
47
49
  text - завжди
package/mdc/rego.mdc ADDED
@@ -0,0 +1,77 @@
1
+ ---
2
+ description: Opa, Rego — інструментарій (VS Code, lint-rego)
3
+ version: '1.1'
4
+ globs: "**/*.rego"
5
+ alwaysApply: false
6
+ ---
7
+
8
+ # Rego (opa)
9
+
10
+ Синтаксичні правила (`rego.v1`, `import rego.v1`, заборона legacy v0) — у `conftest.mdc` (alwaysApply). Цей файл — про **інструментарій**: VS Code, лінтери, форматування.
11
+
12
+ ## VS Code
13
+
14
+ Розширення `tsandall.opa` (від автора OPA): підсвічування, hover, go-to-definition, оцінка виразів і `format-on-save` через `opa fmt`. Працює лише за наявності `opa` у `PATH` — встановити нижче.
15
+
16
+ ```json title=".vscode/extensions.json"
17
+ {
18
+ "recommendations": ["tsandall.opa"]
19
+ }
20
+ ```
21
+
22
+ ```json title=".vscode/settings.json"
23
+ {
24
+ "[rego]": {
25
+ "editor.defaultFormatter": "tsandall.opa",
26
+ "editor.formatOnSave": true
27
+ }
28
+ }
29
+ ```
30
+
31
+ `opa.checkOnSave` за замовчуванням увімкнено в розширенні — діагностика від `opa check` показується в редакторі, тож синтаксичні/типові помилки видно одразу, без запуску `lint-rego`.
32
+
33
+ ## Перевірка
34
+
35
+ ```bash
36
+ bun run lint-rego
37
+ ```
38
+
39
+ Скрипт делегує до `bun ./npm/scripts/lint-rego.mjs`. Послідовність:
40
+
41
+ 1. **preflight** — наявність `opa` і `regal` у `PATH`; якщо хоча б одного нема — exit 1 з підказкою встановлення;
42
+ 2. `opa check --strict <targets>` — компіляція з типами та `--strict` (мертвий код, неоднозначні правила, незадекларовані змінні);
43
+ 3. `regal lint <targets>` — статичний лінтер Rego ([Styra Regal](https://docs.styra.com/regal)): ловить v0-синтаксис, неявні set-rules і відхилення від `rego.v1`, плюс bugs/idiomatic/style-правила.
44
+
45
+ Цілі — `npm/policy/` (де живуть Rego-полісі пакета). Інші *.rego поза деревом додай у `LINT_TARGETS` у `lint-rego.mjs`.
46
+
47
+ ### Встановлення інструментів
48
+
49
+ - macOS: `brew install opa regal`
50
+ - Linux/Windows:
51
+ - opa — <https://www.openpolicyagent.org/docs/latest/#1-download-opa>
52
+ - regal — <https://docs.styra.com/regal#installation>
53
+
54
+ Обидва — лише в `PATH`, **не** додавай у `dependencies` / `devDependencies` (як `shellcheck` у `text.mdc`).
55
+
56
+ ### `package.json`
57
+
58
+ ```json title="package.json"
59
+ {
60
+ "scripts": {
61
+ "lint-rego": "bun ./npm/scripts/lint-rego.mjs"
62
+ }
63
+ }
64
+ ```
65
+
66
+ У кореневому `lint` (з `text.mdc` і дотичних) включай `bun run lint-rego` — щоб локальний прогін співпадав з CI.
67
+
68
+ ## Конфіг regal
69
+
70
+ У корені — `.regal/config.yaml`. Дозволено вимикати окремі правила під специфіку репо (наприклад, conftest-полісі — `deny`-правила як де-факто entrypoint-и):
71
+
72
+ ```yaml title=".regal/config.yaml"
73
+ rules:
74
+ idiomatic:
75
+ no-defined-entrypoint:
76
+ level: ignore
77
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.204",
3
+ "version": "1.8.207",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,73 @@
1
+ # Порт мінімальної структурної перевірки `HealthCheckPolicy` з
2
+ # `npm/scripts/check-abie.mjs` (abie.mdc).
3
+ #
4
+ # Запуск (локально):
5
+ # conftest test path/to/k8s/.../hc.yaml -p npm/policy/abie \
6
+ # --namespace abie.health_check_policy
7
+ #
8
+ # Перевіряє, для документів з `kind: HealthCheckPolicy` (apiVersion
9
+ # `networking.gke.io/v1`):
10
+ # - `spec.config.httpHealthCheck.requestPath` — непорожній шлях, що починається з `/`;
11
+ # - `spec.config.httpHealthCheck.port` (або `spec.targetRef.name` суфікс) — `8080`;
12
+ # - `spec.targetRef.name` має закінчуватись на `-hl` (headless backend).
13
+ #
14
+ # Cross-file gating (`abie` правило в `.n-cursor.json`, парність з Deployment-каталогу,
15
+ # узгодження з `metadata.name` Deployment) — у JS (`check-abie.mjs`).
16
+ #
17
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
18
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
19
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
20
+ package abie.health_check_policy
21
+
22
+ import rego.v1
23
+
24
+ req_path_starts_with_slash_template := concat(" ", [
25
+ "HealthCheckPolicy: requestPath має починатись з `/`",
26
+ "(зараз %q) (abie.mdc)",
27
+ ])
28
+
29
+ # ── deny: requestPath ──────────────────────────────────────────────────────
30
+
31
+ deny contains msg if {
32
+ is_health_check_policy
33
+ req_path == ""
34
+ msg := "HealthCheckPolicy: spec.config.httpHealthCheck.requestPath має бути непорожнім (abie.mdc)"
35
+ }
36
+
37
+ deny contains msg if {
38
+ is_health_check_policy
39
+ req_path != ""
40
+ not startswith(req_path, "/")
41
+ msg := sprintf(req_path_starts_with_slash_template, [req_path])
42
+ }
43
+
44
+ # ── deny: port == 8080 ────────────────────────────────────────────────────
45
+
46
+ deny contains msg if {
47
+ is_health_check_policy
48
+ port := object.get(http_health_check, "port", null)
49
+ port != null
50
+ port != 8080
51
+ msg := sprintf("HealthCheckPolicy: port має бути 8080 (зараз %v) (abie.mdc)", [port])
52
+ }
53
+
54
+ # ── deny: targetRef.name закінчується на `-hl` ────────────────────────────
55
+
56
+ deny contains msg if {
57
+ is_health_check_policy
58
+ name := object.get(object.get(input.spec, "targetRef", {}), "name", "")
59
+ name != ""
60
+ not endswith(name, "-hl")
61
+ msg := sprintf("HealthCheckPolicy: targetRef.name має закінчуватись на `-hl` (зараз %q) (abie.mdc)", [name])
62
+ }
63
+
64
+ # ── helpers ────────────────────────────────────────────────────────────────
65
+
66
+ is_health_check_policy if {
67
+ input.kind == "HealthCheckPolicy"
68
+ startswith(object.get(input, "apiVersion", ""), "networking.gke.io/")
69
+ }
70
+
71
+ http_health_check := object.get(object.get(object.get(input, "spec", {}), "config", {}), "httpHealthCheck", {})
72
+
73
+ req_path := object.get(http_health_check, "requestPath", "")
@@ -0,0 +1,45 @@
1
+ # Порт перевірки `HTTPRoute` у шарі `…/k8s/.../base/...` з
2
+ # `npm/scripts/check-abie.mjs` (abie.mdc): дозволені лише hostnames з домену
3
+ # `aiml.live` (включно з піддоменами та `*.aiml.live`).
4
+ #
5
+ # Запуск (локально):
6
+ # conftest test path/to/k8s/base/hr.yaml -p npm/policy/abie \
7
+ # --namespace abie.http_route_base
8
+ #
9
+ # Cross-file gating (саме шлях `…/base/…` визначає, чи застосовувати правило)
10
+ # — у JS: conftest викликаємо лише на YAML-ах з base/. Тут — лише валідація вмісту
11
+ # `spec.hostnames`.
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 abie.http_route_base
17
+
18
+ import rego.v1
19
+
20
+ allowed_apex := "aiml.live"
21
+
22
+ deny contains msg if {
23
+ input.kind == "HTTPRoute"
24
+ some host in object.get(object.get(input, "spec", {}), "hostnames", [])
25
+ is_string(host)
26
+ not host_matches_aiml_live(host)
27
+ msg := sprintf("HTTPRoute (base): %q має бути в домені aiml.live (abie.mdc)", [host])
28
+ }
29
+
30
+ # Чи hostname належить до aiml.live (точна відповідність, піддомен `*.aiml.live`
31
+ # або довільний субдомен `*.aiml.live`).
32
+ host_matches_aiml_live(host) if {
33
+ host_lower := lower(host)
34
+ host_lower == allowed_apex
35
+ }
36
+
37
+ host_matches_aiml_live(host) if {
38
+ host_lower := lower(host)
39
+ endswith(host_lower, sprintf(".%s", [allowed_apex]))
40
+ }
41
+
42
+ host_matches_aiml_live(host) if {
43
+ host_lower := lower(host)
44
+ host_lower == sprintf("*.%s", [allowed_apex])
45
+ }
@@ -0,0 +1,31 @@
1
+ # Порт перевірки `.claude/settings.json` з `npm/scripts/check-adr.mjs` (adr.mdc):
2
+ # `hooks.Stop[*]` має містити групу, де хоча б один елемент `hooks[]` має `command`
3
+ # зі substring `.claude/hooks/capture-decisions.sh`.
4
+ #
5
+ # Запуск (локально):
6
+ # conftest test .claude/settings.json -p npm/policy/adr \
7
+ # --namespace adr.settings_json
8
+ #
9
+ # Hash-порівняння bash-скрипта з канонічним bundled-варіантом і `.gitignore`-перевірки
10
+ # — у JS (`check-adr.mjs`).
11
+ #
12
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
13
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
14
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
15
+ package adr.settings_json
16
+
17
+ import rego.v1
18
+
19
+ hook_command_marker := ".claude/hooks/capture-decisions.sh"
20
+
21
+ deny contains msg if {
22
+ not has_adr_stop_hook
23
+ msg := ".claude/settings.json: відсутній Stop-hook для `capture-decisions.sh` у hooks.Stop (adr.mdc)"
24
+ }
25
+
26
+ # Чи є в `hooks.Stop[*].hooks[*].command` рядок з маркером скрипта.
27
+ has_adr_stop_hook if {
28
+ some group in object.get(object.get(input, "hooks", {}), "Stop", [])
29
+ some hook in object.get(group, "hooks", [])
30
+ contains(object.get(hook, "command", ""), hook_command_marker)
31
+ }
@@ -0,0 +1,28 @@
1
+ # Порт перевірки `.claude/settings.local.json` з `npm/scripts/check-adr.mjs`
2
+ # (adr.mdc): після переходу на project-shared `settings.json` цей файл (якщо є)
3
+ # НЕ повинен мати дубля Stop-хука з маркером `.claude/hooks/capture-decisions.sh`,
4
+ # інакше один і той самий скрипт виконається двічі на одну подію.
5
+ #
6
+ # Запуск (локально):
7
+ # conftest test .claude/settings.local.json -p npm/policy/adr \
8
+ # --namespace adr.settings_local_json
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 adr.settings_local_json
14
+
15
+ import rego.v1
16
+
17
+ hook_command_marker := ".claude/hooks/capture-decisions.sh"
18
+
19
+ duplicate_template := concat(" ", [
20
+ ".claude/settings.local.json: видали дубль Stop-хука для",
21
+ "`capture-decisions.sh` — він уже у project-shared settings.json (adr.mdc)",
22
+ ])
23
+
24
+ deny contains duplicate_template if {
25
+ some group in object.get(object.get(input, "hooks", {}), "Stop", [])
26
+ some hook in object.get(group, "hooks", [])
27
+ contains(object.get(hook, "command", ""), hook_command_marker)
28
+ }
@@ -0,0 +1,33 @@
1
+ # Порт перевірки `checkBunfigHoisted` з `npm/scripts/check-bun.mjs` (bun.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test bunfig.toml -p npm/policy/bun --namespace bun.bunfig
5
+ #
6
+ # Conftest парсить `.toml` нативно: секція `[install]` стає обʼєктом `input.install`.
7
+ # FS-перевірки (наявність самого `bunfig.toml`, `bun.lock`, заборонені lockfile-и
8
+ # `package-lock.json` тощо, директорія `.yarn/`) живуть у `check-bun.mjs` — Rego
9
+ # працює лише з вже завантаженим input.
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 bun.bunfig
15
+
16
+ import rego.v1
17
+
18
+ deny contains msg if {
19
+ # `object.get(…, false)` дає визначене значення, коли поля немає, інакше
20
+ # `not is_object(input.install)` повернув би `undefined`, і правило мовчки
21
+ # не спрацювало б (той самий патерн, що й у `ga.workflow_common`).
22
+ not is_object(object.get(input, "install", false))
23
+ msg := "bunfig.toml: відсутня секція [install] (bun.mdc)"
24
+ }
25
+
26
+ deny contains msg if {
27
+ is_object(object.get(input, "install", false))
28
+
29
+ # `object.get(…, null)` робить значення визначеним, інакше при відсутньому
30
+ # `linker` порівняння `!= "hoisted"` дало б `undefined`, не `true`.
31
+ object.get(input.install, "linker", null) != "hoisted"
32
+ msg := "bunfig.toml: у секції [install] має бути linker = \"hoisted\" (bun.mdc)"
33
+ }
@@ -0,0 +1,94 @@
1
+ # Порт структурних перевірок `package.json` з `npm/scripts/check-bun.mjs` (bun.mdc).
2
+ #
3
+ # Запуск (локально, КОРЕНЕВИЙ package.json):
4
+ # conftest test package.json -p npm/policy/bun --namespace bun.package_json
5
+ #
6
+ # Перевіряє: відсутність `packageManager`, відсутність кореневих `dependencies`,
7
+ # у `devDependencies` лише `@nitra/*`, агрегований `lint`-скрипт (якщо є `lint-*`
8
+ # скрипти): покриває всі lint-* через `bun run`, закінчується на `&& oxfmt .`.
9
+ #
10
+ # Перевірки, які ЗАЛИШИЛИСЬ у JS (потребують FS / cross-file):
11
+ # - `lint-docker` / `lint-k8s` коли `.n-cursor.json:rules` містить відповідне
12
+ # правило (потрібен другий файл-вхід — у Rego без `--combine` не зробити).
13
+ #
14
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
15
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
16
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
17
+ package bun.package_json
18
+
19
+ import rego.v1
20
+
21
+ # ── Шаблони повідомлень ────────────────────────────────────────────────────
22
+
23
+ # Через `concat` — дотримуємося regal style/line-length.
24
+ lint_aggregate_missing_template := concat(" ", [
25
+ "У package.json є скрипти %v, але немає агрегованого `lint`.",
26
+ "Додай скрипт, який запускає їх через `bun run` (bun.mdc)",
27
+ ])
28
+
29
+ # ── deny: заборонені поля ──────────────────────────────────────────────────
30
+
31
+ deny contains msg if {
32
+ pm := object.get(input, "packageManager", "")
33
+ pm != ""
34
+ msg := sprintf("package.json містить поле packageManager: %q — видали його (bun.mdc)", [pm])
35
+ }
36
+
37
+ # `dependencies` не повинно бути взагалі — навіть пусте `{}`. Сентинельний рядок
38
+ # дозволяє відрізнити «поле відсутнє» від «поле є з будь-яким значенням».
39
+ deny contains msg if {
40
+ object.get(input, "dependencies", "__bun_missing__") != "__bun_missing__"
41
+ msg := "Кореневий package.json не повинен містити поле dependencies — додай залежності в workspace-пакети (bun.mdc)"
42
+ }
43
+
44
+ # ── deny: devDependencies — лише `@nitra/*` ───────────────────────────────
45
+
46
+ deny contains msg if {
47
+ is_object(input.devDependencies)
48
+ some name, _ in input.devDependencies
49
+ not startswith(name, "@nitra/")
50
+ msg := sprintf("Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: %s (bun.mdc)", [name])
51
+ }
52
+
53
+ # ── deny: агрегований lint-скрипт ─────────────────────────────────────────
54
+ #
55
+ # Якщо в `scripts` є хоч один `lint-*`, має бути скрипт `lint`, у якому
56
+ # через `bun run <ім'я>` викликається кожен такий скрипт; рядок завершується
57
+ # на `&& oxfmt .`.
58
+
59
+ deny contains msg if {
60
+ count(lint_prefixed_scripts) > 0
61
+ lint_script == ""
62
+ msg := sprintf(lint_aggregate_missing_template, [lint_prefixed_scripts])
63
+ }
64
+
65
+ deny contains msg if {
66
+ count(lint_prefixed_scripts) > 0
67
+ lint_script != ""
68
+ some script in lint_prefixed_scripts
69
+ not contains(lint_script, sprintf("bun run %s", [script]))
70
+ msg := sprintf("Скрипт `lint` має викликати `%s` через `bun run` (bun.mdc)", [script])
71
+ }
72
+
73
+ deny contains msg if {
74
+ count(lint_prefixed_scripts) > 0
75
+ lint_script != ""
76
+
77
+ # Перевіряємо, що рядок завершується `&& oxfmt .` (з можливими пробілами/табами).
78
+ # Trim не потрібен — пробіли/таби в кінці допускаємо в самому regex (`[ \t]*$`).
79
+ not regex.match(`&&[ \t]+oxfmt[ \t]+\.[ \t]*$`, lint_script)
80
+ msg := "Скрипт `lint` має закінчуватися на `&& oxfmt .` (bun.mdc)"
81
+ }
82
+
83
+ # ── helpers ────────────────────────────────────────────────────────────────
84
+
85
+ # Ключі скриптів, що починаються з `lint-` (наприклад `lint-js`, `lint-ga`).
86
+ lint_prefixed_scripts := [name |
87
+ some name, _ in object.get(input, "scripts", {})
88
+ startswith(name, "lint-")
89
+ ]
90
+
91
+ # Значення `scripts.lint` як рядок (порожній, якщо поля немає або тип не string).
92
+ default lint_script := ""
93
+
94
+ lint_script := input.scripts.lint if is_string(input.scripts.lint)
@@ -0,0 +1,45 @@
1
+ # Порт перевірки версії `@capacitor/core` з `npm/scripts/check-capacitor.mjs`
2
+ # (capacitor.mdc) — мінімальна мажорна версія = 8.
3
+ #
4
+ # Запуск (локально, у пакеті з Capacitor):
5
+ # conftest test path/to/package.json -p npm/policy/capacitor \
6
+ # --namespace capacitor.package_json
7
+ #
8
+ # Перевіряє: якщо в `dependencies['@capacitor/core']` присутній (gating: пакет
9
+ # реально використовує Capacitor), то перша мажорна цифра в діапазоні має бути ≥ 8.
10
+ # Підтримує `^8.0.0`, `>=8`, `8.x`, `workspace:*` тощо.
11
+ #
12
+ # Цей порт спрощує JS-логіку — повна семантика OR-діапазонів (`a || b`) і нижня
13
+ # межа діапазону лишається в JS (`check-capacitor.mjs`: `capacitorVersionRangeMinMajor`).
14
+ # JS-перевірка лишилась authoritative й бігає через `npx @nitra/cursor check capacitor`;
15
+ # ця Rego — швидкий gate для одиничного `package.json` (наприклад через IDE).
16
+ #
17
+ # FS-сканування пакетів через workspaces, iOS-специфічна логіка (Podfile), вибір
18
+ # каталогу пакета з Capacitor — у JS.
19
+ #
20
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
21
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
22
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
23
+ package capacitor.package_json
24
+
25
+ import rego.v1
26
+
27
+ deny contains msg if {
28
+ range := object.get(object.get(input, "dependencies", {}), "@capacitor/core", "")
29
+ range != ""
30
+ not capacitor_major_at_least_8(range)
31
+ msg := sprintf("@capacitor/core має бути >= 8 (зараз %q) (capacitor.mdc)", [range])
32
+ }
33
+
34
+ # `workspace:*` / `*` / `x` / `latest` — пропускаємо (як у JS).
35
+ capacitor_major_at_least_8(range) if startswith(trim_space(range), "workspace:")
36
+
37
+ capacitor_major_at_least_8(range) if {
38
+ first_major(range) >= 8
39
+ }
40
+
41
+ first_major(range) := major if {
42
+ match := regex.find_n(`\d+`, range, 1)
43
+ count(match) > 0
44
+ major := to_number(match[0])
45
+ }
@@ -20,21 +20,12 @@ import rego.v1
20
20
  # interpolation. Збираємо очікувані рядки з фрагментів через `concat`, як це
21
21
  # зроблено в check-ga.mjs, щоб і Rego-парсер, і людина-читач не плуталися.
22
22
 
23
- expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
24
-
25
23
  expected_github_token := concat("", ["$", "{{ github.token }}"])
26
24
 
27
25
  expected_name := "Clean action for removing completed workflow runs"
28
26
 
29
27
  expected_cron := "0 1 16 * *"
30
28
 
31
- # Шаблон повідомлення про відсутню `concurrency`-секцію — винесено через `concat`,
32
- # щоб дотриматися regal style/line-length.
33
- concurrency_missing_template := concat(" ", [
34
- "clean-ga-workflows.yml: відсутня секція concurrency —",
35
- "додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
36
- ])
37
-
38
29
  # ── Аліаси на input ────────────────────────────────────────────────────────
39
30
  #
40
31
  # GHA YAML quirk: ключ `on:` — YAML 1.1 boolean `true`, конфтест серіалізує його
@@ -62,23 +53,6 @@ deny contains msg if {
62
53
  msg := "clean-ga-workflows.yml: має бути workflow_dispatch: {} (ga.mdc)"
63
54
  }
64
55
 
65
- deny contains msg if {
66
- not is_object(input.concurrency)
67
- msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
68
- }
69
-
70
- deny contains msg if {
71
- is_object(input.concurrency)
72
- input.concurrency.group != expected_concurrency_group
73
- msg := sprintf("clean-ga-workflows.yml: concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
74
- }
75
-
76
- deny contains msg if {
77
- is_object(input.concurrency)
78
- input.concurrency["cancel-in-progress"] != true
79
- msg := "clean-ga-workflows.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
80
- }
81
-
82
56
  deny contains msg if {
83
57
  not input.jobs.cleanup_old_workflows
84
58
  msg := "clean-ga-workflows.yml: jobs.cleanup_old_workflows відсутній (ga.mdc)"
@@ -16,8 +16,6 @@ import rego.v1
16
16
  # Шаблонні токени GitHub Actions (`${{ … }}`) збираємо з фрагментів через
17
17
  # `concat`, бо `{{` у Rego починає string interpolation.
18
18
 
19
- expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
20
-
21
19
  expected_github_token := concat("", ["$", "{{ github.token }}"])
22
20
 
23
21
  expected_deleted_branches_expr := concat("", ["$", "{{ steps.delete_stuff.outputs.deleted_branches }}"])
@@ -28,12 +26,6 @@ expected_name := "Clean abandoned branches"
28
26
 
29
27
  expected_cron := "0 1 15 * *"
30
28
 
31
- # Шаблони повідомлень — через `concat` для regal style/line-length.
32
- concurrency_missing_template := concat(" ", [
33
- "clean-merged-branch.yml: відсутня секція concurrency —",
34
- "додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
35
- ])
36
-
37
29
  # ── Аліаси на input ────────────────────────────────────────────────────────
38
30
  #
39
31
  # YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
@@ -63,23 +55,6 @@ deny contains msg if {
63
55
  msg := "clean-merged-branch.yml: має бути workflow_dispatch: {} (ga.mdc)"
64
56
  }
65
57
 
66
- deny contains msg if {
67
- not is_object(input.concurrency)
68
- msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
69
- }
70
-
71
- deny contains msg if {
72
- is_object(input.concurrency)
73
- input.concurrency.group != expected_concurrency_group
74
- msg := sprintf("clean-merged-branch.yml: concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
75
- }
76
-
77
- deny contains msg if {
78
- is_object(input.concurrency)
79
- input.concurrency["cancel-in-progress"] != true
80
- msg := "clean-merged-branch.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
81
- }
82
-
83
58
  deny contains msg if {
84
59
  not input.jobs.cleanup_old_branches
85
60
  msg := "clean-merged-branch.yml: jobs.cleanup_old_branches відсутній (ga.mdc)"