@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,83 @@
1
+ # Порт перевірки `validateGitAiWorkflowStructure` з `npm/scripts/check-ga.mjs` (ga.mdc).
2
+ #
3
+ # Запуск (локально):
4
+ # conftest test .github/workflows/git-ai.yml \
5
+ # -p npm/policy/ga --namespace ga.git_ai
6
+ #
7
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
8
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
9
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
10
+ package ga.git_ai
11
+
12
+ import rego.v1
13
+
14
+ # ── Очікувані значення ─────────────────────────────────────────────────────
15
+
16
+ expected_name := "Git AI"
17
+
18
+ expected_if_substring := "github.event.pull_request.merged == true"
19
+
20
+ expected_install_substring := "curl -fsSL https://usegitai.com/install.sh | bash"
21
+
22
+ expected_run_substring := "git-ai ci github run"
23
+
24
+ # ── Аліаси на input ────────────────────────────────────────────────────────
25
+ #
26
+ # YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
27
+
28
+ gha_on := input["true"]
29
+
30
+ # Job-id містить дефіс — звертаємося через `[…]`. Імʼя `job` (без префіксу пакету)
31
+ # — щоб уникнути regal-правила `rule-name-repeats-package`.
32
+ job := input.jobs["git-ai"]
33
+
34
+ # Усі `run:` зі steps цього job-а, склеєні в один blob — для substring-перевірки.
35
+ job_run_blob := concat("\n", [run |
36
+ run := job.steps[_].run
37
+ ])
38
+
39
+ # ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
40
+
41
+ deny contains msg if {
42
+ input.name != expected_name
43
+ msg := sprintf("git-ai.yml: name має бути %q (ga.mdc)", [expected_name])
44
+ }
45
+
46
+ deny contains msg if {
47
+ not "closed" in {t | some t in gha_on.pull_request.types}
48
+ msg := "git-ai.yml: on.pull_request.types має містити closed (ga.mdc)"
49
+ }
50
+
51
+ deny contains msg if {
52
+ not job
53
+ msg := "git-ai.yml: jobs.git-ai відсутній (ga.mdc)"
54
+ }
55
+
56
+ deny contains msg if {
57
+ not contains(job_if_str, expected_if_substring)
58
+ msg := "git-ai.yml: job має містити if: github.event.pull_request.merged == true (ga.mdc)"
59
+ }
60
+
61
+ deny contains msg if {
62
+ job.permissions.contents != "write"
63
+ msg := "git-ai.yml: permissions мають бути contents: write (ga.mdc)"
64
+ }
65
+
66
+ deny contains msg if {
67
+ not contains(job_run_blob, expected_install_substring)
68
+ msg := "git-ai.yml: має встановлювати git-ai через curl | bash (ga.mdc)"
69
+ }
70
+
71
+ deny contains msg if {
72
+ not contains(job_run_blob, expected_run_substring)
73
+ msg := "git-ai.yml: має виконувати git-ai ci github run (ga.mdc)"
74
+ }
75
+
76
+ # ── helpers ────────────────────────────────────────────────────────────────
77
+
78
+ # `if` поле job-а може бути відсутнім — тоді `sprintf` дає невизначене значення
79
+ # і спрацьовує `default`, повертаючи порожній рядок; `contains(…)` нижче дасть
80
+ # false і відповідне `deny`-правило спрацює зі зрозумілим повідомленням.
81
+ default job_if_str := ""
82
+
83
+ job_if_str := sprintf("%v", [job.if])
@@ -0,0 +1,118 @@
1
+ # Порт перевірок `validateLintGaWorkflowStructure` + `validateLintGaOnTriggers` з
2
+ # `npm/scripts/check-ga.mjs` (ga.mdc).
3
+ #
4
+ # Запуск (локально):
5
+ # conftest test .github/workflows/lint-ga.yml \
6
+ # -p npm/policy/ga --namespace ga.lint_ga
7
+ #
8
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
9
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
10
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
11
+ package ga.lint_ga
12
+
13
+ import rego.v1
14
+
15
+ # ── Очікувані значення ─────────────────────────────────────────────────────
16
+
17
+ expected_name := "Lint GA"
18
+
19
+ expected_branches := {"dev", "main"}
20
+
21
+ expected_push_paths := {".github/actions/**", ".github/workflows/**"}
22
+
23
+ # ── Аліаси на input ────────────────────────────────────────────────────────
24
+ #
25
+ # YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
26
+
27
+ gha_on := input["true"]
28
+
29
+ # Job-id містить дефіс — звертаємося через `[…]`. Імʼя `job` (без префіксу пакету)
30
+ # — щоб уникнути regal-правила `rule-name-repeats-package`.
31
+ job := input.jobs["lint-ga"]
32
+
33
+ # Усі `uses:` зі steps цього job-а — для перевірки членства.
34
+ job_uses_set contains job.steps[_].uses
35
+
36
+ # Усі `run:` зі steps цього job-а, склеєні в один blob — для substring-перевірки.
37
+ job_run_blob := concat("\n", [run |
38
+ run := job.steps[_].run
39
+ ])
40
+
41
+ # ── deny rules (контигно — regal: messy-rule) ──────────────────────────────
42
+
43
+ deny contains msg if {
44
+ input.name != expected_name
45
+ msg := sprintf("lint-ga.yml: name має бути %q (ga.mdc)", [expected_name])
46
+ }
47
+
48
+ deny contains msg if {
49
+ not push_branches_have_dev_and_main
50
+ msg := "lint-ga.yml: on.push.branches має містити dev і main (ga.mdc)"
51
+ }
52
+
53
+ deny contains msg if {
54
+ not pr_branches_have_dev_and_main
55
+ msg := "lint-ga.yml: on.pull_request.branches має містити dev і main (ga.mdc)"
56
+ }
57
+
58
+ deny contains msg if {
59
+ not push_paths_have_required
60
+ msg := "lint-ga.yml: on.push.paths має містити .github/actions/** і .github/workflows/** (ga.mdc)"
61
+ }
62
+
63
+ deny contains msg if {
64
+ not job
65
+ msg := "lint-ga.yml: jobs.lint-ga відсутній (ga.mdc)"
66
+ }
67
+
68
+ deny contains msg if {
69
+ job["runs-on"] != "ubuntu-latest"
70
+ msg := "lint-ga.yml: runs-on має бути ubuntu-latest (ga.mdc)"
71
+ }
72
+
73
+ deny contains msg if {
74
+ job.permissions.contents != "read"
75
+ msg := "lint-ga.yml: permissions мають бути contents: read (ga.mdc)"
76
+ }
77
+
78
+ deny contains msg if {
79
+ count(job.steps) == 0
80
+ msg := "lint-ga.yml: jobs.lint-ga.steps відсутні (ga.mdc)"
81
+ }
82
+
83
+ deny contains msg if {
84
+ not "actions/checkout@v6" in job_uses_set
85
+ msg := "lint-ga.yml: має бути uses: actions/checkout@v6 (ga.mdc)"
86
+ }
87
+
88
+ deny contains msg if {
89
+ not "./.github/actions/setup-bun-deps" in job_uses_set
90
+ msg := "lint-ga.yml: має бути uses: ./.github/actions/setup-bun-deps (ga.mdc)"
91
+ }
92
+
93
+ deny contains msg if {
94
+ not "astral-sh/setup-uv@v8.0.0" in job_uses_set
95
+ msg := "lint-ga.yml: має бути uses: astral-sh/setup-uv@v8.0.0 (ga.mdc)"
96
+ }
97
+
98
+ deny contains msg if {
99
+ not contains(job_run_blob, "bun run lint-ga")
100
+ msg := "lint-ga.yml: має бути крок run: bun run lint-ga (ga.mdc)"
101
+ }
102
+
103
+ # ── helpers ────────────────────────────────────────────────────────────────
104
+
105
+ push_branches_have_dev_and_main if {
106
+ branches := gha_on.push.branches
107
+ expected_branches & {b | some b in branches} == expected_branches
108
+ }
109
+
110
+ pr_branches_have_dev_and_main if {
111
+ branches := gha_on.pull_request.branches
112
+ expected_branches & {b | some b in branches} == expected_branches
113
+ }
114
+
115
+ push_paths_have_required if {
116
+ paths := gha_on.push.paths
117
+ expected_push_paths & {p | some p in paths} == expected_push_paths
118
+ }
@@ -0,0 +1,161 @@
1
+ # Універсальні перевірки для будь-якого `.github/workflows/*.yml` (ga.mdc).
2
+ #
3
+ # Порт `verifyNoDirectBunOrCache`, `verifyNoRunShellLineContinuationBackslash`,
4
+ # `verifyCheckoutBeforeLocalSetupBunDeps` та `validateConcurrencyOnRoot` з
5
+ # `npm/scripts/check-ga.mjs`. На відміну від `lint_ga`/`clean_ga_workflows`/
6
+ # `clean_merged_branch`/`git_ai`, цей пакет не привʼязаний до конкретного
7
+ # workflow — `conftest test` запускається на кожному файлі окремо з
8
+ # `--namespace ga.workflow_common`, і `input` — це окремий розпарсений YAML.
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 ga.workflow_common
14
+
15
+ import rego.v1
16
+
17
+ # ── Очікувані значення ─────────────────────────────────────────────────────
18
+
19
+ expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
20
+
21
+ # Локальні composite setup-bun-deps (ga.mdc) — два варіанти шляху:
22
+ # `.github/actions/...` (cursor-репо) і `npm/github-actions/...` (npm-пакет dev-локально).
23
+ local_setup_bun_markers := {
24
+ "./.github/actions/setup-bun-deps",
25
+ "./npm/github-actions/setup-bun-deps",
26
+ }
27
+
28
+ # Заборонені підрядки в кроках `uses` та `run` (ga.mdc): дублюючі setup/cache/install,
29
+ # які мають бути всередині composite-action `setup-bun-deps`.
30
+ forbidden_step_substrings := {
31
+ "oven-sh/setup-bun": "використовуй .github/actions/setup-bun-deps замість oven-sh/setup-bun",
32
+ "actions/cache": "використовуй .github/actions/setup-bun-deps замість actions/cache",
33
+ "bun install": "використовуй .github/actions/setup-bun-deps замість bun install",
34
+ }
35
+
36
+ # Шаблони довгих повідомлень — через `concat`, щоб дотримуватися regal style/line-length.
37
+
38
+ concurrency_missing_template := concat(" ", [
39
+ "відсутня секція concurrency —",
40
+ "додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
41
+ ])
42
+
43
+ shell_continuation_template := concat(" ", [
44
+ "jobs.%s.steps[%d]: у run заборонено продовження рядків через зворотний сліш;",
45
+ "оформи як folded block (run: >-) (ga.mdc)",
46
+ ])
47
+
48
+ setup_bun_no_checkout_template := concat(" ", [
49
+ "jobs.%s: перед локальним setup-bun-deps потрібен крок actions/checkout@v6 —",
50
+ "інакше runner не знайде action.yml (ga.mdc)",
51
+ ])
52
+
53
+ # ── Аліаси на input ────────────────────────────────────────────────────────
54
+
55
+ # Усі jobs (з гарантією, що це обʼєкт) — щоб не падати на нетипових YAML.
56
+ jobs := input.jobs
57
+
58
+ # Плоский список усіх кроків з усіх jobs з метаданими — для перевірок, де job-id
59
+ # і позиція кроку нам потрібні в повідомленні (shell line continuation).
60
+ all_flat_steps contains entry if {
61
+ some job_id, step_index
62
+ step := jobs[job_id].steps[step_index]
63
+ entry := {"job_id": job_id, "step_index": step_index, "step": step}
64
+ }
65
+
66
+ # ── deny: заборонені setup-bun/cache/install у будь-якому кроці ────────────
67
+
68
+ deny contains msg if {
69
+ some entry in all_flat_steps
70
+ some pattern, hint in forbidden_step_substrings
71
+ step_uses_or_run_blob(entry.step) != ""
72
+ contains(step_uses_or_run_blob(entry.step), pattern)
73
+ msg := sprintf("jobs.%s.steps[%d]: %s (ga.mdc)", [entry.job_id, entry.step_index, hint])
74
+ }
75
+
76
+ # ── deny: shell-продовження `\` перед переносом рядка у `run:` ─────────────
77
+ #
78
+ # `\` + `\n` — bash line-continuation; у workflow замінюй на folded block `>-`
79
+ # без зворотних слішів (ga.mdc).
80
+
81
+ deny contains msg if {
82
+ some entry in all_flat_steps
83
+ run_text := step_run_text(entry.step)
84
+ regex.match(`\\\r?\n`, run_text)
85
+ msg := sprintf(shell_continuation_template, [entry.job_id, entry.step_index])
86
+ }
87
+
88
+ # ── deny: setup-bun-deps без попереднього checkout у тому ж job ────────────
89
+ #
90
+ # Без `actions/checkout` локальний composite-action недоступний — runner не
91
+ # знайде `action.yml` у дереві. Перевіряємо порядок індексів кроків у кожному
92
+ # job: setup-bun-deps має бути після принаймні одного `actions/checkout@`.
93
+
94
+ deny contains msg if {
95
+ some job_id, job in jobs
96
+ first_setup := first_local_setup_bun_index(job)
97
+ first_setup >= 0
98
+ not has_checkout_before(job, first_setup)
99
+ msg := sprintf(setup_bun_no_checkout_template, [job_id])
100
+ }
101
+
102
+ # ── deny: concurrency блок ─────────────────────────────────────────────────
103
+ #
104
+ # Дублює окремі per-workflow перевірки для clean-ga-workflows / clean-merged-branch /
105
+ # lint-ga / git-ai, але вкриває й решту workflow-файлів (apply-k8s, lint-js, …),
106
+ # для яких поки немає виділеної polysi.
107
+
108
+ deny contains msg if {
109
+ # `object.get(…, default)` повертає `false` коли ключа немає — інакше `not is_object(…)`
110
+ # над відсутнім полем дає `undefined`, не `true`, і правило мовчки не спрацьовує.
111
+ not is_object(object.get(input, "concurrency", false))
112
+ msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
113
+ }
114
+
115
+ deny contains msg if {
116
+ is_object(object.get(input, "concurrency", false))
117
+ input.concurrency.group != expected_concurrency_group
118
+ msg := sprintf("concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
119
+ }
120
+
121
+ deny contains msg if {
122
+ is_object(object.get(input, "concurrency", false))
123
+ input.concurrency["cancel-in-progress"] != true
124
+ msg := "concurrency.cancel-in-progress має бути true (ga.mdc)"
125
+ }
126
+
127
+ # ── helpers ────────────────────────────────────────────────────────────────
128
+
129
+ # Об'єднаний рядок `uses` + `run` для одного кроку — для substring-пошуку
130
+ # заборонених патернів. `run` може бути рядком або масивом рядків (YAML).
131
+ step_uses_or_run_blob(step) := blob if {
132
+ uses := object.get(step, "uses", "")
133
+ run_text := step_run_text(step)
134
+ blob := concat("\n", [uses, run_text])
135
+ }
136
+
137
+ # Текст `run:` як один рядок: підтримує рядкові та масивові форми (YAML).
138
+ step_run_text(step) := step.run if is_string(step.run)
139
+
140
+ else := concat("\n", [s | some s in step.run]) if is_array(step.run)
141
+
142
+ else := ""
143
+
144
+ # Індекс першого кроку з локальним setup-bun-deps; -1 якщо такого немає.
145
+ first_local_setup_bun_index(job) := min(indices) if {
146
+ indices := [i |
147
+ some i, step in job.steps
148
+ uses := object.get(step, "uses", "")
149
+ some marker in local_setup_bun_markers
150
+ contains(uses, marker)
151
+ ]
152
+ count(indices) > 0
153
+ } else := -1
154
+
155
+ # Чи є в `job.steps[0..before]` крок `actions/checkout@…`.
156
+ has_checkout_before(job, before) if {
157
+ some i, step in job.steps
158
+ i < before
159
+ uses := object.get(step, "uses", "")
160
+ contains(uses, "actions/checkout@")
161
+ }
@@ -0,0 +1,35 @@
1
+ # Порт перевірки `package.json` з `npm/scripts/check-graphql.mjs` (graphql.mdc).
2
+ #
3
+ # Запуск (локально, ЯКЩО проект містить `gql\`…\`` теги — gating робить JS-частина
4
+ # через oxc-parser-скан):
5
+ # conftest test package.json -p npm/policy/graphql --namespace graphql.package_json
6
+ #
7
+ # Перевіряє: `scripts.dump-schema` точно відповідає канону graphql.mdc.
8
+ #
9
+ # AST-скан коду на `gql\`…\`` template literals і FS-перевірки (наявність
10
+ # `.graphqlrc.yml`, `.vscode/extensions.json` з `graphql.vscode-graphql`) — у JS.
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 graphql.package_json
16
+
17
+ import rego.v1
18
+
19
+ required_dump_schema := concat("", [
20
+ "bunx graphqurl http://localhost:4040/v1/graphql ",
21
+ "-H 'X-Hasura-Admin-Secret: secret' --introspect > schema.graphql",
22
+ ])
23
+
24
+ deny contains msg if {
25
+ scripts := object.get(input, "scripts", {})
26
+ not "dump-schema" in object.keys(scripts)
27
+ msg := "package.json: відсутній scripts.dump-schema (graphql.mdc)"
28
+ }
29
+
30
+ deny contains msg if {
31
+ dump := object.get(object.get(input, "scripts", {}), "dump-schema", "")
32
+ dump != ""
33
+ dump != required_dump_schema
34
+ msg := sprintf("package.json: scripts.dump-schema має бути канонічним з graphql.mdc (зараз %q)", [dump])
35
+ }
@@ -0,0 +1,27 @@
1
+ # Порт мінімальної структурної перевірки `hasura/k8s/base/svc-hl.yaml` з
2
+ # `npm/scripts/check-hasura.mjs` (hasura.mdc): для кожного Service у файлі
3
+ # `metadata.name` має закінчуватись на `-h` (headless-сервіс Hasura).
4
+ #
5
+ # Запуск (локально):
6
+ # conftest test hasura/k8s/base/svc-hl.yaml -p npm/policy/hasura \
7
+ # --namespace hasura.svc_hl
8
+ #
9
+ # Решта логіки `check-hasura.mjs` (звірення `HASURA_GRAPHQL_ENDPOINT` в `.env`-файлах
10
+ # з `<service>.<namespace>.svc.<cluster>` через regex по всьому дереву репо, gating
11
+ # на `repository` у кореневому `package.json`) — у JS: вона потребує текстового
12
+ # парсингу `.env`-файлів, обходу дерева й cross-file resolution.
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 hasura.svc_hl
18
+
19
+ import rego.v1
20
+
21
+ deny contains msg if {
22
+ input.kind == "Service"
23
+ name := object.get(object.get(input, "metadata", {}), "name", "")
24
+ name != ""
25
+ not endswith(name, "-h")
26
+ msg := sprintf("hasura svc-hl.yaml: Service %q має закінчуватись на `-h` (hasura.mdc / k8s.mdc)", [name])
27
+ }
@@ -0,0 +1,94 @@
1
+ # Порт перевірки `package.json` з `npm/scripts/check-image-compress.mjs`
2
+ # (image-compress.mdc).
3
+ #
4
+ # Запуск (локально):
5
+ # conftest test package.json -p npm/policy/image_compress \
6
+ # --namespace image_compress.package_json
7
+ #
8
+ # Перевіряє: скрипт `lint-image` викликає `npx @nitra/minify-image` з `--src=.`
9
+ # і `--write`, без `--avif` (AVIF — окреме правило); агрегатор `lint` (якщо є)
10
+ # містить `bun run lint-image`; `@nitra/minify-image` НЕ в dependencies / devDependencies.
11
+ #
12
+ # FS-перевірки (`.minify-image-cache.tsv` legacy-файл, `.gitignore` правил для
13
+ # `.n-minify-image.tsv`) — у JS.
14
+ #
15
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
18
+ package image_compress.package_json
19
+
20
+ import rego.v1
21
+
22
+ minify_pkg := "@nitra/minify-image"
23
+
24
+ dep_template := concat(" ", [
25
+ "package.json: %q не повинен бути в %s —",
26
+ "використовуй npx (image-compress.mdc)",
27
+ ])
28
+
29
+ avif_in_lint_image_template := concat(" ", [
30
+ "package.json: lint-image не має містити `--avif` —",
31
+ "AVIF-генерацію виконує check image-avif (image-compress.mdc)",
32
+ ])
33
+
34
+ # ── deny: lint-image ──────────────────────────────────────────────────────
35
+
36
+ deny contains msg if {
37
+ scripts := object.get(input, "scripts", {})
38
+ not "lint-image" in object.keys(scripts)
39
+ msg := "package.json: відсутній scripts.lint-image (image-compress.mdc)"
40
+ }
41
+
42
+ deny contains msg if {
43
+ lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
44
+ lint_image != ""
45
+ not contains(lint_image, sprintf("npx %s", [minify_pkg]))
46
+ msg := sprintf("package.json: lint-image має викликати `npx %s` (image-compress.mdc)", [minify_pkg])
47
+ }
48
+
49
+ deny contains msg if {
50
+ lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
51
+ contains(lint_image, sprintf("npx %s", [minify_pkg]))
52
+ not has_src_flag(lint_image)
53
+ msg := "package.json: lint-image має містити `--src=.` (image-compress.mdc)"
54
+ }
55
+
56
+ deny contains msg if {
57
+ lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
58
+ contains(lint_image, sprintf("npx %s", [minify_pkg]))
59
+ not contains(lint_image, "--write")
60
+ msg := "package.json: lint-image має містити `--write` (image-compress.mdc)"
61
+ }
62
+
63
+ deny contains avif_in_lint_image_template if {
64
+ lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
65
+ contains(lint_image, "--avif")
66
+ }
67
+
68
+ # ── deny: агрегований `lint` має кликати `bun run lint-image` ─────────────
69
+
70
+ deny contains msg if {
71
+ "lint-image" in object.keys(object.get(input, "scripts", {}))
72
+ lint := object.get(object.get(input, "scripts", {}), "lint", "")
73
+ lint != ""
74
+ not contains(lint, "bun run lint-image")
75
+ msg := "package.json: агрегований `lint` має містити `bun run lint-image` (image-compress.mdc)"
76
+ }
77
+
78
+ # ── deny: `@nitra/minify-image` НЕ в dependencies/devDependencies ────────
79
+
80
+ deny contains msg if {
81
+ minify_pkg in object.keys(object.get(input, "dependencies", {}))
82
+ msg := sprintf(dep_template, [minify_pkg, "dependencies"])
83
+ }
84
+
85
+ deny contains msg if {
86
+ minify_pkg in object.keys(object.get(input, "devDependencies", {}))
87
+ msg := sprintf(dep_template, [minify_pkg, "devDependencies"])
88
+ }
89
+
90
+ # ── helpers ────────────────────────────────────────────────────────────────
91
+
92
+ has_src_flag(s) if contains(s, "--src=.")
93
+
94
+ has_src_flag(s) if contains(s, "--src .")
@@ -0,0 +1,28 @@
1
+ # Порт перевірки залежностей `package.json` з `npm/scripts/check-js-bun-db.mjs`
2
+ # (js-bun-db.mdc).
3
+ #
4
+ # Запуск (локально, для будь-якого `package.json` у дереві):
5
+ # conftest test path/to/package.json -p npm/policy/js_bun_db \
6
+ # --namespace js_bun_db.package_json
7
+ #
8
+ # Перевіряє: у `dependencies` не повинно бути `pg`, `pg-format`, `mysql2` —
9
+ # заміна на Bun native SQL (https://bun.com/docs/runtime/sql).
10
+ #
11
+ # AST-скан коду (`new SQL(...)` всередині функцій, `unsafe()` без маркера
12
+ # `// allow-unsafe`, pg-leftover виклики, динамічні `IN (…)` через `.join(',')`)
13
+ # лишається у JS (потребує парсингу `.js` / `.ts` через oxc-parser).
14
+ #
15
+ # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
+ # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
17
+ # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
18
+ package js_bun_db.package_json
19
+
20
+ import rego.v1
21
+
22
+ forbidden_dependencies := {"pg", "pg-format", "mysql2"}
23
+
24
+ deny contains msg if {
25
+ some pkg_name in forbidden_dependencies
26
+ pkg_name in object.keys(object.get(input, "dependencies", {}))
27
+ msg := sprintf("dependencies містить заборонений %q — заміни на Bun native SQL (js-bun-db.mdc)", [pkg_name])
28
+ }
@@ -0,0 +1,98 @@
1
+ # Порт перевірки `.github/workflows/lint-js.yml` з `npm/scripts/check-js-lint.mjs`
2
+ # (js-lint.mdc) — структурні очікування `verifyLintJsWorkflowStructure`.
3
+ #
4
+ # Запуск (локально):
5
+ # conftest test .github/workflows/lint-js.yml -p npm/policy/js_lint \
6
+ # --namespace js_lint.lint_js_yml
7
+ #
8
+ # Перевіряє: є крок `actions/checkout@v6` з `with.persist-credentials: false`,
9
+ # є крок `./.github/actions/setup-bun-deps`, у `run` є `bunx oxlint`, `bunx eslint .`,
10
+ # `bunx jscpd .`, у `run` НЕМАЄ `oxlint --fix` чи `eslint --fix` (CI не повинен
11
+ # редагувати код).
12
+ #
13
+ # Універсальні workflow-перевірки (concurrency, заборонені setup-bun/cache, shell
14
+ # line-continuation) — у `ga.workflow_common`. Дубль JS-перевірок у `lint.yml` —
15
+ # у JS-частині `check-js-lint.mjs` (потребує другого workflow-файлу).
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 js_lint.lint_js_yml
21
+
22
+ import rego.v1
23
+
24
+ # Усі кроки з усіх jobs — для substring-перевірок.
25
+ all_steps contains step if {
26
+ some job in object.get(input, "jobs", {})
27
+ some step in object.get(job, "steps", [])
28
+ }
29
+
30
+ all_uses_blob := concat("\n", [u |
31
+ some step in all_steps
32
+ u := object.get(step, "uses", "")
33
+ ])
34
+
35
+ all_run_blob := concat("\n", [r |
36
+ some step in all_steps
37
+ r := step_run_text(step)
38
+ ])
39
+
40
+ # ── deny: required uses ────────────────────────────────────────────────────
41
+
42
+ deny contains msg if {
43
+ not contains(all_uses_blob, "actions/checkout@v6")
44
+ msg := "lint-js.yml: відсутній крок uses: actions/checkout@v6 (js-lint.mdc)"
45
+ }
46
+
47
+ deny contains msg if {
48
+ not contains(all_uses_blob, "./.github/actions/setup-bun-deps")
49
+ msg := "lint-js.yml: відсутній крок uses: ./.github/actions/setup-bun-deps (js-lint.mdc)"
50
+ }
51
+
52
+ deny contains msg if {
53
+ not has_checkout_persist_credentials_false
54
+ msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
55
+ }
56
+
57
+ # ── deny: required run substrings ─────────────────────────────────────────
58
+
59
+ deny contains msg if {
60
+ not contains(all_run_blob, "bunx oxlint")
61
+ msg := "lint-js.yml: у run немає `bunx oxlint` (js-lint.mdc)"
62
+ }
63
+
64
+ deny contains msg if {
65
+ not contains(all_run_blob, "bunx eslint .")
66
+ msg := "lint-js.yml: у run немає `bunx eslint .` (js-lint.mdc)"
67
+ }
68
+
69
+ deny contains msg if {
70
+ not contains(all_run_blob, "bunx jscpd .")
71
+ msg := "lint-js.yml: у run немає `bunx jscpd .` (js-lint.mdc)"
72
+ }
73
+
74
+ # ── deny: --fix у CI заборонено ───────────────────────────────────────────
75
+
76
+ deny contains msg if {
77
+ regex.match(`bunx\s+oxlint[^\n]*--fix`, all_run_blob)
78
+ msg := "lint-js.yml: у run є oxlint з `--fix` (у CI заборонено) (js-lint.mdc)"
79
+ }
80
+
81
+ deny contains msg if {
82
+ contains(all_run_blob, "eslint --fix")
83
+ msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js-lint.mdc)"
84
+ }
85
+
86
+ # ── helpers ────────────────────────────────────────────────────────────────
87
+
88
+ has_checkout_persist_credentials_false if {
89
+ some step in all_steps
90
+ contains(object.get(step, "uses", ""), "actions/checkout@v6")
91
+ step.with["persist-credentials"] == false
92
+ }
93
+
94
+ step_run_text(step) := step.run if is_string(step.run)
95
+
96
+ else := concat("\n", [s | some s in step.run]) if is_array(step.run)
97
+
98
+ else := ""