@nitra/cursor 3.22.0 → 3.23.1

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 (229) hide show
  1. package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
  2. package/AGENTS.template.md +4 -0
  3. package/CHANGELOG.md +37 -3
  4. package/bin/docs/n-cursor.md +636 -0
  5. package/bin/docs/rename-yaml-extensions.md +207 -0
  6. package/bin/n-cursor.js +30 -3
  7. package/package.json +1 -1
  8. package/rules/abie/docs/fix.md +18 -0
  9. package/rules/abie/js/docs/applies.md +26 -0
  10. package/rules/abie/js/docs/env_dns.md +32 -0
  11. package/rules/abie/js/docs/firebase_hosting.md +23 -0
  12. package/rules/abie/js/docs/hc_pairing.md +35 -0
  13. package/rules/abie/js/docs/ua_http_route.md +28 -0
  14. package/rules/abie/js/docs/ua_node_selector.md +28 -0
  15. package/rules/abie/lib/docs/enabled.md +29 -0
  16. package/rules/abie/lib/docs/env-dns.md +35 -0
  17. package/rules/abie/lib/docs/hc-yaml.md +33 -0
  18. package/rules/abie/lib/docs/http-route.md +44 -0
  19. package/rules/abie/lib/docs/k8s-tree.md +40 -0
  20. package/rules/abie/lib/docs/kustomization-patches.md +47 -0
  21. package/rules/abie/lib/docs/overlay-paths.md +38 -0
  22. package/rules/abie/lib/docs/yaml.md +29 -0
  23. package/rules/adr/docs/fix.md +148 -0
  24. package/rules/adr/js/docs/hooks.md +259 -0
  25. package/rules/bun/docs/fix.md +156 -0
  26. package/rules/bun/js/docs/layout.md +393 -0
  27. package/rules/capacitor/docs/fix.md +121 -0
  28. package/rules/capacitor/js/docs/platforms.md +295 -0
  29. package/rules/changelog/changelog.mdc +4 -2
  30. package/rules/changelog/docs/fix.md +174 -0
  31. package/rules/changelog/js/consistency.mjs +114 -13
  32. package/rules/changelog/js/docs/consistency.md +387 -0
  33. package/rules/changelog/lib/docs/package-manifest.md +210 -0
  34. package/rules/ci4/docs/fix.md +179 -0
  35. package/rules/ci4/js/docs/marksman_config.md +128 -0
  36. package/rules/docker/docker.mdc +8 -3
  37. package/rules/docker/docs/fix.md +171 -0
  38. package/rules/docker/js/docs/lint.md +258 -0
  39. package/rules/docker/lib/docs/docker-hadolint.md +184 -0
  40. package/rules/docker/lib/docs/docker-mirror.md +247 -0
  41. package/rules/docker/lib/docs/docker-native-addon.md +170 -0
  42. package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
  43. package/rules/docker/lint/docs/lint.md +193 -0
  44. package/rules/efes/docs/fix.md +203 -0
  45. package/rules/feedback/docs/fix.md +140 -0
  46. package/rules/flow/docs/fix.md +152 -0
  47. package/rules/ga/docs/fix.md +158 -0
  48. package/rules/ga/js/docs/lint.md +100 -0
  49. package/rules/ga/js/docs/workflows.md +217 -0
  50. package/rules/ga/lint/docs/lint.md +209 -0
  51. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
  52. package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
  53. package/rules/graphql/docs/fix.md +126 -0
  54. package/rules/graphql/js/docs/tooling.md +264 -0
  55. package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
  56. package/rules/hasura/docs/fix.md +120 -0
  57. package/rules/hasura/hasura.mdc +14 -0
  58. package/rules/hasura/js/docs/internal_urls.md +326 -0
  59. package/rules/image-avif/docs/fix.md +132 -0
  60. package/rules/image-avif/js/docs/avif_generation.md +241 -0
  61. package/rules/image-compress/docs/fix.md +150 -0
  62. package/rules/image-compress/js/docs/package_setup.md +191 -0
  63. package/rules/js-bun-db/docs/fix.md +148 -0
  64. package/rules/js-bun-db/js/docs/safety.md +231 -0
  65. package/rules/js-bun-db/js-bun-db.mdc +42 -13
  66. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
  67. package/rules/js-bun-redis/docs/fix.md +123 -0
  68. package/rules/js-bun-redis/js/docs/imports.md +176 -0
  69. package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
  70. package/rules/js-lint/docs/fix.md +117 -0
  71. package/rules/js-lint/js/docs/lint.md +250 -0
  72. package/rules/js-lint/js/docs/tooling.md +348 -0
  73. package/rules/js-lint/js/docs/utils_imports.md +207 -0
  74. package/rules/js-lint-ci/docs/fix.md +154 -0
  75. package/rules/js-lint-ci/js/docs/lint.md +144 -0
  76. package/rules/js-mssql/docs/fix.md +128 -0
  77. package/rules/js-mssql/js/docs/deps.md +263 -0
  78. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
  79. package/rules/js-run/docs/fix.md +144 -0
  80. package/rules/js-run/js/docs/runtime.md +388 -0
  81. package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
  82. package/rules/js-run/lib/docs/check-env-scan.md +433 -0
  83. package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
  84. package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
  85. package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
  86. package/rules/k8s/docs/fix.md +129 -0
  87. package/rules/k8s/js/docs/manifests.md +344 -0
  88. package/rules/k8s/js/manifests.mjs +6 -2
  89. package/rules/k8s/k8s.mdc +4 -2
  90. package/rules/k8s/lint/docs/lint.md +411 -0
  91. package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
  92. package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
  93. package/rules/nginx-default-tpl/docs/fix.md +124 -0
  94. package/rules/nginx-default-tpl/js/docs/template.md +378 -0
  95. package/rules/npm-module/docs/fix.md +98 -0
  96. package/rules/npm-module/js/docs/package_structure.md +274 -0
  97. package/rules/npm-module/js/docs/rule_meta.md +137 -0
  98. package/rules/npm-module/js/docs/skill_meta.md +190 -0
  99. package/rules/php/docs/fix.md +107 -0
  100. package/rules/php/js/docs/tooling.md +152 -0
  101. package/rules/php/lint/docs/lint.md +215 -0
  102. package/rules/python/docs/fix.md +163 -0
  103. package/rules/python/js/docs/applies.md +108 -0
  104. package/rules/python/js/docs/tooling.md +153 -0
  105. package/rules/python/lint/docs/lint.md +322 -0
  106. package/rules/rego/docs/fix.md +121 -0
  107. package/rules/rego/js/docs/applies.md +174 -0
  108. package/rules/rego/js/docs/lint.md +118 -0
  109. package/rules/rego/lint/docs/lint.md +204 -0
  110. package/rules/release/docs/change.md +185 -0
  111. package/rules/release/docs/fix.md +119 -0
  112. package/rules/release/docs/release.md +222 -0
  113. package/rules/release/lib/docs/aggregate.md +246 -0
  114. package/rules/release/lib/docs/change-file.md +200 -0
  115. package/rules/release/lib/docs/fallback.md +203 -0
  116. package/rules/rust/docs/fix.md +129 -0
  117. package/rules/rust/js/docs/applies.md +140 -0
  118. package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
  119. package/rules/security/docs/fix.md +86 -0
  120. package/rules/security/js/docs/lint.md +171 -0
  121. package/rules/security/js/docs/sample_secret.md +190 -0
  122. package/rules/security/js/docs/trufflehog.md +137 -0
  123. package/rules/security/js/lint.mjs +9 -1
  124. package/rules/style-lint/docs/fix.md +155 -0
  125. package/rules/style-lint/js/docs/lint.md +184 -0
  126. package/rules/style-lint/js/docs/tooling.md +194 -0
  127. package/rules/tauri/docs/fix.md +158 -0
  128. package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
  129. package/rules/tauri/js/docs/tooling.md +228 -0
  130. package/rules/test/coverage/coverage.mjs +15 -3
  131. package/rules/test/docs/fix.md +132 -0
  132. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
  133. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
  134. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
  135. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
  136. package/rules/test/js/docs/cargo_mutants_config.md +173 -0
  137. package/rules/test/js/docs/location.md +136 -0
  138. package/rules/test/js/docs/no-process-chdir.md +160 -0
  139. package/rules/test/js/docs/no-relative-fs-path.md +271 -0
  140. package/rules/test/js/docs/stryker_config.md +152 -0
  141. package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
  142. package/rules/text/docs/fix.md +118 -0
  143. package/rules/text/js/docs/forbidden-prettier.md +143 -0
  144. package/rules/text/js/docs/formatting.md +256 -0
  145. package/rules/text/js/docs/lint.md +122 -0
  146. package/rules/text/lint/docs/lint.md +220 -0
  147. package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
  148. package/rules/text/lint/docs/run-shellcheck.md +212 -0
  149. package/rules/text/lint/docs/run-v8r.md +197 -0
  150. package/rules/vue/docs/fix.md +127 -0
  151. package/rules/vue/js/docs/packages.md +335 -0
  152. package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
  153. package/rules/worktree/docs/fix.md +161 -0
  154. package/schemas/rule-meta.json +5 -1
  155. package/scripts/auto-rules.mjs +7 -4
  156. package/scripts/coverage-classify/docs/apply.md +202 -0
  157. package/scripts/coverage-classify/docs/cache.md +203 -0
  158. package/scripts/coverage-classify/docs/index.md +218 -0
  159. package/scripts/coverage-classify/docs/prompt.md +132 -0
  160. package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
  161. package/scripts/coverage-fix-extract.mjs +122 -0
  162. package/scripts/coverage-fix.mjs +1 -1
  163. package/scripts/dispatcher/docs/graph.md +346 -0
  164. package/scripts/dispatcher/docs/index.md +236 -0
  165. package/scripts/dispatcher/docs/trace.md +296 -0
  166. package/scripts/dispatcher/index.mjs +1 -1
  167. package/scripts/dispatcher/lib/active.mjs +4 -8
  168. package/scripts/dispatcher/lib/commands.mjs +7 -11
  169. package/scripts/dispatcher/lib/docs/active.md +348 -0
  170. package/scripts/dispatcher/lib/docs/artifact.md +232 -0
  171. package/scripts/dispatcher/lib/docs/budget.md +167 -0
  172. package/scripts/dispatcher/lib/docs/capability.md +196 -0
  173. package/scripts/dispatcher/lib/docs/commands.md +210 -0
  174. package/scripts/dispatcher/lib/docs/events.md +182 -0
  175. package/scripts/dispatcher/lib/docs/executor.md +190 -0
  176. package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
  177. package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
  178. package/scripts/dispatcher/lib/docs/gate.md +231 -0
  179. package/scripts/dispatcher/lib/docs/level.md +335 -0
  180. package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
  181. package/scripts/dispatcher/lib/docs/plan.md +200 -0
  182. package/scripts/dispatcher/lib/docs/planner.md +269 -0
  183. package/scripts/dispatcher/lib/docs/review.md +255 -0
  184. package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
  185. package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
  186. package/scripts/dispatcher/lib/docs/spec.md +203 -0
  187. package/scripts/dispatcher/lib/docs/state-store.md +303 -0
  188. package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
  189. package/scripts/dispatcher/lib/executor.mjs +6 -1
  190. package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
  191. package/scripts/dispatcher/lib/level.mjs +29 -3
  192. package/scripts/dispatcher/lib/review.mjs +1 -1
  193. package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
  194. package/scripts/docs/auto-rules.md +376 -0
  195. package/scripts/docs/auto-skills.md +173 -0
  196. package/scripts/docs/build-agents-commands.md +183 -0
  197. package/scripts/docs/cli-entry.md +153 -0
  198. package/scripts/docs/coverage-fix.md +177 -0
  199. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
  200. package/scripts/lib/changed-files.mjs +4 -1
  201. package/scripts/lib/docs/changed-files.md +149 -0
  202. package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
  203. package/scripts/lib/docs/check-reporter.md +175 -0
  204. package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
  205. package/scripts/lib/docs/discover-checkable-rules.md +165 -0
  206. package/scripts/lib/docs/ensure-tool.md +254 -0
  207. package/scripts/lib/docs/generated-markdown.md +275 -0
  208. package/scripts/lib/docs/gha-workflow.md +326 -0
  209. package/scripts/lib/docs/inline-template-links.md +303 -0
  210. package/scripts/lib/docs/list-rule-ids.md +156 -0
  211. package/scripts/lib/docs/load-cursor-config.md +147 -0
  212. package/scripts/lib/docs/mirror-parity.md +167 -0
  213. package/scripts/lib/worktree.mjs +26 -0
  214. package/scripts/worktree-cli.mjs +12 -2
  215. package/skills/coverage-fix/SKILL.md +34 -45
  216. package/skills/docgen/SKILL.md +44 -23
  217. package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
  218. package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
  219. package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
  220. package/skills/docgen/js/docgen-ignore.mjs +54 -0
  221. package/skills/docgen/js/docgen-scan.mjs +37 -21
  222. package/skills/llm-patch/SKILL.md +23 -2
  223. package/skills/start-check/SKILL.md +26 -53
  224. package/skills/start-check/js/check.mjs +211 -0
  225. package/skills/taze/SKILL.md +9 -3
  226. package/skills/taze/js/diff.mjs +154 -0
  227. package/types/bin/n-cursor.d.ts +1 -1
  228. package/skills/fix-tests/SKILL.md +0 -119
  229. package/skills/fix-tests/meta.json +0 -1
@@ -26,3 +26,17 @@ HASURA_GRAPHQL_ENDPOINT=http://contract-h-hl.ua-contract.svc.abie-ua.internal:80
26
26
  Правило застосовується для проєктів **nitra** (у кореневому `package.json` `"repository": "https://github.com/nitra/*"`) і **abie** (`"repository": "https://github.com/abinbevefes/*"`); для інших репозиторіїв перевірка пропускається.
27
27
 
28
28
  Файл .env це (без імені) це виключення з цього правила, його змінювати не потрібно
29
+
30
+ ## Міграції: лише `up.sql`, без `down.sql`
31
+
32
+ При створенні будь-якої нової директорії міграції у `hasura/migrations/` **не створювати файл `down.sql`**.
33
+ У директорії міграції має бути **тільки `up.sql`**.
34
+
35
+ Приклад коректної структури:
36
+
37
+ ```
38
+ hasura/migrations/default/1781247030100_add_foo/
39
+ └── up.sql ✓
40
+ ```
41
+
42
+ Файл `down.sql` у цьому проєкті **не використовується** і не повинен з'являтися.
@@ -0,0 +1,326 @@
1
+ # internal_urls.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `npm/rules/hasura/js/internal_urls.mjs` реалізує перевірку правила `hasura.mdc` для проєктів **nitra** та **abie**. Його єдина мета — гарантувати, що значення змінної середовища `HASURA_GRAPHQL_ENDPOINT`, заданої в будь-якому файлі `*.env` репозиторію, є **внутрішнім кластерним URL** (GKE/GCP DNS-суфікс `<cluster>.internal`), а не публічним доменом.
6
+
7
+ Логіка активується лише за умови, що поле `repository` кореневого `package.json` вказує на одну з організацій:
8
+
9
+ - `https://github.com/nitra/...`
10
+ - `https://github.com/abinbevefes/...`
11
+
12
+ Якщо ці маркери відсутні, перевірка пропускається без помилок (аналогічно до інших abie-перевірок).
13
+
14
+ Очікуваний формат URL:
15
+
16
+ ```
17
+ http://<service>.<namespace>.svc.<cluster>.internal:<port>
18
+ ```
19
+
20
+ Приклад валідного значення:
21
+
22
+ ```
23
+ http://contract-h-hl.ua-contract.svc.abie-ua.internal:8080
24
+ ```
25
+
26
+ Сегменти `<service>` та `<namespace>` за наявності YAML-файлів `hasura/k8s/base/svc-hl.yaml` (поле `metadata.name`, headless-сервіс із суфіксом `-h-hl`) та `hasura/k8s/base/namespace.yaml` (поле `metadata.name`) додатково звіряються з фактичними значеннями kubernetes-маніфестів.
27
+
28
+ Скануються всі файли, що відповідають масці `*.env` (наприклад, `dev.env`, `production.env`). Файл з іменем рівно `.env` (локальний файл розробника) **виключається** з перевірки. Не обходяться службові каталоги: `node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next` (відповідно до конфігурації `walkDir`).
29
+
30
+ ## Експорти / API
31
+
32
+ Модуль є ES-модулем (`.mjs`) і експортує:
33
+
34
+ | Експорт | Тип | Призначення |
35
+ | ----------------------------- | --------------- | ------------------------------------------------------------------------------------------------- |
36
+ | `parseInternalHasuraEndpoint` | функція | Розбирає URL-рядок на сегменти кластерної DNS-адреси (`service`, `namespace`, `cluster`, `port`). |
37
+ | `isEnvFile` | функція | Чи підлягає файл за відносним шляхом перевірці hasura.mdc. |
38
+ | `isNitraOrAbieRepository` | функція | Чи репозиторій належить організаціям nitra / abinbevefes. |
39
+ | `check` | функція (async) | Точка входу: запускає повну перевірку для cwd, повертає exit-код процесу. |
40
+
41
+ Внутрішні (не експортовані) допоміжні функції: `readYamlMetadataName`, `collectEnvFiles`, `checkEnvFile`, `readRootRepositoryUrl`.
42
+
43
+ Внутрішні константи:
44
+
45
+ | Константа | Значення | Призначення |
46
+ | ----------------------------- | ----------------------------------- | ----------------------------------------------------------------------- |
47
+ | `NITRA_REPOSITORY_URL_MARKER` | `'https://github.com/nitra/'` | Маркер репозиторіїв організації nitra. |
48
+ | `ABIE_REPOSITORY_URL_MARKER` | `'https://github.com/abinbevefes/'` | Маркер репозиторіїв організації abinbevefes (abie). |
49
+ | `HASURA_BASE_DIR` | `'hasura/k8s/base'` | Базовий каталог k8s-маніфестів Hasura. |
50
+ | `HASURA_SVC_HL_FILE` | `'hasura/k8s/base/svc-hl.yaml'` | Шлях до headless-сервіса. |
51
+ | `HASURA_NAMESPACE_FILE` | `'hasura/k8s/base/namespace.yaml'` | Шлях до namespace-маніфесту. |
52
+ | `ENV_FILE_RE` | `/\.env$/u` | Регулярка-маркер `*.env` файлів. |
53
+ | `HASURA_ENDPOINT_LINE_RE` | (див. нижче) | Регулярка для знаходження рядка `HASURA_GRAPHQL_ENDPOINT=...` у `.env`. |
54
+ | `INTERNAL_HASURA_URL_RE` | (див. нижче) | Регулярка валідації внутрішнього кластерного URL. |
55
+ | `INTERNAL_DNS_SUFFIX` | `'.internal'` | DNS-суфікс GKE/GCP-кластера. |
56
+
57
+ Регулярні вирази:
58
+
59
+ - `HASURA_ENDPOINT_LINE_RE = /^[ \t]*(?:export[ \t]+)?HASURA_GRAPHQL_ENDPOINT[ \t]*=[ \t]*['"]?([^'"\r\n#]+)/mu`
60
+ - Multiline, Unicode. Підтримує `export ` префікс, табуляції/пробіли навколо `=`, опційне обрамлення лапками (`'` або `"`). Капчурить значення до символа лапки, перенесення рядка або `#` (коментар).
61
+ - `INTERNAL_HASURA_URL_RE = /^http:\/\/([^./]+)\.([^./]+)\.svc\.([^./:]+\.internal):(\d+)\/?$/u`
62
+ - Дозволяє лише схему `http://` (TLS усередині кластера зайвий).
63
+ - Капчурить чотири сегменти: `service`, `namespace`, DNS-суфікс (з обов'язковим `.internal`), `port`.
64
+ - Допускає необов'язковий завершальний слеш.
65
+
66
+ ## Функції
67
+
68
+ ### `parseInternalHasuraEndpoint(url)`
69
+
70
+ **Сигнатура:**
71
+
72
+ ```js
73
+ export function parseInternalHasuraEndpoint(url)
74
+ ```
75
+
76
+ **Параметри:**
77
+
78
+ - `url` (`string`) — значення `HASURA_GRAPHQL_ENDPOINT`, попередньо очищене від обрамляючих лапок (для надійності функція ще раз застосовує `.trim()`).
79
+
80
+ **Повертає:**
81
+
82
+ - При успіху: `{ ok: true, service: string, namespace: string, cluster: string, port: string }`. Поле `cluster` містить ім'я кластера **без** суфіксу `.internal` (наприклад, `abie-ua`).
83
+ - При невідповідності формату: `{ ok: false }`.
84
+
85
+ **Поведінка та особливості:**
86
+
87
+ - Дозволяє виключно протокол `http://` і DNS-суфікс `<cluster>.internal` (GKE/GCP-конвенція).
88
+ - Усі сегменти повертаються як рядки (включно з `port`, попри те що значення цифрове).
89
+ - Side effects відсутні.
90
+
91
+ ### `readYamlMetadataName(absPath, kind)` (внутрішня)
92
+
93
+ **Сигнатура:**
94
+
95
+ ```js
96
+ async function readYamlMetadataName(absPath, kind)
97
+ ```
98
+
99
+ **Параметри:**
100
+
101
+ - `absPath` (`string`) — абсолютний шлях до YAML-файла.
102
+ - `kind` (`string`) — очікуваний `kind` ресурсу (`'Service'`, `'Namespace'` тощо).
103
+
104
+ **Повертає:** `Promise<string | null>` — значення `metadata.name` першого документа з відповідним `kind`, або `null`, якщо:
105
+
106
+ - файл не існує;
107
+ - парсинг YAML не вдався (`parseAllDocuments` кинув виключення);
108
+ - жоден документ у файлі не має заданого `kind` або не має `metadata.name`.
109
+
110
+ **Side effects:** одне читання з файлової системи (`readFile`); жодних винятків назовні не пропускає.
111
+
112
+ ### `isEnvFile(relPath)`
113
+
114
+ **Сигнатура:**
115
+
116
+ ```js
117
+ export function isEnvFile(relPath)
118
+ ```
119
+
120
+ **Параметри:**
121
+
122
+ - `relPath` (`string`) — posix-шлях файла відносно кореня репозиторію.
123
+
124
+ **Повертає:** `boolean`.
125
+
126
+ - `true` — для файлів, у яких є ім'я перед `.env`: `dev.env`, `nitra.env`, `production.env`.
127
+ - `false` — для всіх інших, **зокрема** для файла рівно `.env` без імені (локальний файл розробника, виключений з правила hasura.mdc).
128
+
129
+ **Side effects:** немає.
130
+
131
+ ### `collectEnvFiles(root, ignorePaths)` (внутрішня)
132
+
133
+ **Сигнатура:**
134
+
135
+ ```js
136
+ async function collectEnvFiles(root, ignorePaths)
137
+ ```
138
+
139
+ **Параметри:**
140
+
141
+ - `root` (`string`) — абсолютний шлях кореня репозиторію.
142
+ - `ignorePaths` (`string[]`) — абсолютні шляхи каталогів, які слід повністю пропустити при обході (зчитуються з cursor-конфігу).
143
+
144
+ **Повертає:** `Promise<string[]>` — відсортовані за `localeCompare` posix-шляхи `*.env` файлів відносно `root`.
145
+
146
+ **Поведінка:**
147
+
148
+ - Використовує `walkDir` з callback-фільтром.
149
+ - Конвертує windows-роздільники `\` у posix-роздільники `/` перед перевіркою `isEnvFile`.
150
+ - Працює дитерміновано завдяки сортуванню `toSorted`.
151
+
152
+ **Side effects:** обхід файлової системи.
153
+
154
+ ### `checkEnvFile(relPath, cwd, expected, reporter)` (внутрішня)
155
+
156
+ **Сигнатура:**
157
+
158
+ ```js
159
+ async function checkEnvFile(relPath, cwd, expected, reporter)
160
+ ```
161
+
162
+ **Параметри:**
163
+
164
+ - `relPath` (`string`) — відносний posix-шлях файла (для повідомлень репортера).
165
+ - `cwd` (`string`) — корінь репозиторію (для побудови абсолютного шляху).
166
+ - `expected` (`{ service: string | null, namespace: string | null }`) — очікувані сегменти, прочитані з YAML-маніфестів; `null`-поля пропускаються.
167
+ - `reporter` (`{ pass: (msg: string) => void, fail: (msg: string) => void }`) — обробник результатів (зазвичай з `createCheckReporter`).
168
+
169
+ **Повертає:** `Promise<void>`. Результат комунікується через `reporter`.
170
+
171
+ **Поведінка по гілках:**
172
+
173
+ 1. Якщо в файлі **немає** змінної `HASURA_GRAPHQL_ENDPOINT` — функція мовчки виходить без виклику `pass`/`fail`.
174
+ 2. Якщо значення не парситься як внутрішній кластерний URL — викликає `fail` з прикладом очікуваного формату (`https://<service>.<namespace>.svc.<cluster>.internal:<port>`).
175
+ 3. Якщо `expected.service` задане і не збігається — `fail` з посиланням на `hasura/k8s/base/svc-hl.yaml`.
176
+ 4. Якщо `expected.namespace` задане і не збігається — `fail` з посиланням на `hasura/k8s/base/namespace.yaml`.
177
+ 5. Інакше — `pass` з підтвердженням, що URL внутрішній кластерний.
178
+
179
+ **Side effects:** читання файла, виклики методів `reporter`.
180
+
181
+ **Примітка:** в `fail`-повідомленні приклад наведений зі схемою `https://`, хоча сама регулярка `INTERNAL_HASURA_URL_RE` дозволяє лише `http://` (так задумано — TLS усередині кластера зайвий).
182
+
183
+ ### `readRootRepositoryUrl(cwd)` (внутрішня)
184
+
185
+ **Сигнатура:**
186
+
187
+ ```js
188
+ async function readRootRepositoryUrl(cwd)
189
+ ```
190
+
191
+ **Параметри:**
192
+
193
+ - `cwd` (`string`) — корінь репозиторію.
194
+
195
+ **Повертає:** `Promise<string | null>` — URL з поля `repository` (нормалізований через `getRepositoryUrl`) або `null`, якщо:
196
+
197
+ - `package.json` не існує;
198
+ - JSON не валідний;
199
+ - поле `repository` відсутнє / нерозпізнаний формат.
200
+
201
+ **Side effects:** одне читання `package.json`.
202
+
203
+ ### `isNitraOrAbieRepository(url)`
204
+
205
+ **Сигнатура:**
206
+
207
+ ```js
208
+ export function isNitraOrAbieRepository(url)
209
+ ```
210
+
211
+ **Параметри:**
212
+
213
+ - `url` (`string | null | undefined`) — URL репозиторію.
214
+
215
+ **Повертає:** `boolean`.
216
+
217
+ - `true`, якщо `url` — рядок і містить (case-insensitive) маркер `https://github.com/nitra/` або `https://github.com/abinbevefes/`.
218
+ - `false` — у решті випадків (включно з `null`, `undefined`, не-рядковими значеннями).
219
+
220
+ **Side effects:** немає.
221
+
222
+ ### `check(cwd?)`
223
+
224
+ **Сигнатура:**
225
+
226
+ ```js
227
+ export async function check(cwd = process.cwd())
228
+ ```
229
+
230
+ **Параметри:**
231
+
232
+ - `cwd` (`string`, опційний, за замовчуванням `process.cwd()`) — корінь репозиторію.
233
+
234
+ **Повертає:** `Promise<number>` — exit-код процесу (`0` — OK або правило не застосовується, `1` — є хоча б одне порушення). Конкретне значення формує `reporter.getExitCode()`.
235
+
236
+ **Поведінка (послідовно):**
237
+
238
+ 1. Створює репортер через `createCheckReporter()`.
239
+ 2. Зчитує URL репозиторію з `package.json`. Якщо це не nitra/abie — `pass('Пропущено: …')` і повертає exit-код (зазвичай `0`).
240
+ 3. Зчитує очікувані `service` і `namespace` з YAML-маніфестів (обидва можуть бути `null`).
241
+ 4. Завантажує `ignorePaths` з cursor-конфігу (`loadCursorIgnorePaths`).
242
+ 5. Збирає всі `*.env` файли. Якщо їх немає — `pass('Не знайдено жодного *.env файла — нічого перевіряти')` і повертає exit-код.
243
+ 6. Послідовно (без паралелізму) викликає `checkEnvFile` для кожного знайденого файла.
244
+ 7. Якщо після перевірок exit-код залишився `0` і **жоден** файл не мав `HASURA_GRAPHQL_ENDPOINT` (тобто не було ні `pass`, ні `fail` зі змістом перевірки), додає підсумкове `pass` з кількістю та іменами перевірених файлів.
245
+
246
+ **Side effects:** читання файлів, обхід каталогів, виведення повідомлень репортера (зазвичай у stdout/stderr).
247
+
248
+ ## Залежності
249
+
250
+ ### Зовнішні модулі (npm)
251
+
252
+ - `yaml` — функція `parseAllDocuments` для парсингу мульти-документних YAML-файлів kubernetes-маніфестів.
253
+
254
+ ### Node.js core
255
+
256
+ - `node:fs` — `existsSync` для синхронної перевірки наявності `package.json` та YAML-файлів.
257
+ - `node:fs/promises` — `readFile` для асинхронного читання файлів у UTF-8.
258
+ - `node:path` — `basename`, `join`, `relative` для роботи зі шляхами.
259
+
260
+ ### Внутрішні модулі репозиторію
261
+
262
+ - `../../../scripts/auto-rules.mjs` — `getRepositoryUrl` для нормалізації поля `repository` у `package.json` (підтримує і рядкову, і об'єктну форму).
263
+ - `../../../scripts/lib/check-reporter.mjs` — `createCheckReporter` для уніфікованого механізму репортингу `pass`/`fail` та обчислення exit-коду.
264
+ - `../../../scripts/lib/load-cursor-config.mjs` — `loadCursorIgnorePaths` для отримання списку каталогів, виключених з обходу (повертає абсолютні шляхи).
265
+ - `../../../scripts/utils/walkDir.mjs` — `walkDir` для рекурсивного обходу файлової системи з callback-логікою і набором default-ignore (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`).
266
+
267
+ ### Файли, що читаються в рантаймі
268
+
269
+ - `<cwd>/package.json` — джерело поля `repository`.
270
+ - `<cwd>/hasura/k8s/base/svc-hl.yaml` — джерело очікуваного `service` (якщо існує).
271
+ - `<cwd>/hasura/k8s/base/namespace.yaml` — джерело очікуваного `namespace` (якщо існує).
272
+ - `<cwd>/**/*.env` (без рівно `.env` і без ignore-каталогів) — файли, що перевіряються.
273
+
274
+ ## Потік виконання / Використання
275
+
276
+ ### Сценарій 1. Запуск як check-функції правила hasura.mdc
277
+
278
+ Модуль очікувано викликається з єдиної точки входу `check()` через інфраструктуру `npm/rules`/`scripts`. Типове використання:
279
+
280
+ ```js
281
+ import { check } from './internal_urls.mjs'
282
+
283
+ const exitCode = await check(process.cwd())
284
+ process.exitCode = exitCode
285
+ ```
286
+
287
+ ### Сценарій 2. Послідовність кроків при виконанні `check()`
288
+
289
+ 1. **Препроцесинг репозиторію.** Зчитується `package.json`. Якщо `repository` не вказує на nitra/abie — правило мовчки пропускається з повідомленням `pass`.
290
+ 2. **Підготовка очікуваних значень.** Зчитуються `metadata.name` з `svc-hl.yaml` (як `Service`) і `namespace.yaml` (як `Namespace`). Кожне поле може бути `null` (тоді відповідна перевірка для сегмента URL не виконується).
291
+ 3. **Збір кандидатів.** Через `walkDir` з ignore-списком з cursor-конфігу збираються усі `*.env` файли (відсортовано). Якщо колекція порожня — повідомляється `pass` і завершується.
292
+ 4. **Перевірка кожного файла.** Для кожного знайденого `*.env`:
293
+ - Якщо у файлі немає `HASURA_GRAPHQL_ENDPOINT` — пропуск без репортингу.
294
+ - Інакше значення розбирається `parseInternalHasuraEndpoint`. Якщо парсинг не вдався — `fail`.
295
+ - Якщо `expected.service` заданий і `parsed.service` не збігається — `fail`.
296
+ - Якщо `expected.namespace` заданий і `parsed.namespace` не збігається — `fail`.
297
+ - Інакше — `pass`.
298
+ 5. **Підсумок.** Якщо за результатом усіх перевірок exit-код залишився `0` (тобто жодного `fail` не було), додається підсумкове `pass` з переліком імен файлів. Це покриває кейс, коли всі `*.env` не містили змінної взагалі.
299
+ 6. **Exit-код.** Повертається через `reporter.getExitCode()`: `0` — успіх, `1` — є порушення.
300
+
301
+ ### Сценарій 3. Що вважається валідним URL
302
+
303
+ Валідні приклади:
304
+
305
+ - `http://contract-h-hl.ua-contract.svc.abie-ua.internal:8080`
306
+ - `http://hasura-h-hl.nitra-prod.svc.nitra-cluster.internal:8080/`
307
+
308
+ Невалідні приклади (викличуть `fail`):
309
+
310
+ - `https://my.public.domain:443` — публічний домен.
311
+ - `http://hasura-h-hl.nitra-prod.svc.cluster.local:8080` — DNS-суфікс `.local`, а не `.internal`.
312
+ - `http://hasura-h-hl.svc.abie-ua.internal:8080` — відсутній сегмент `namespace`.
313
+ - `http://hasura-h-hl.nitra-prod.svc.abie-ua.internal` — відсутній port.
314
+ - `http://hasura-h-hl.nitra-prod.svc.abie-ua.internal:8080/graphql` — є шлях після `/`.
315
+
316
+ ### Сценарій 4. Інтеграція в репортинг
317
+
318
+ Для цілісного запуску модуль не виводить нічого сам — усі повідомлення (`pass`/`fail`) делегуються `createCheckReporter()`. Це означає, що формат логів, кольори та сумарний exit-код узгоджені з рештою rule-перевірок у `npm/rules`.
319
+
320
+ ### Сценарій 5. Граничні випадки
321
+
322
+ - **Файл `.env`** (без імені, локальний розробницький) **пропускається** на рівні `isEnvFile`.
323
+ - **Відсутнє `repository` в `package.json`** → правило пропускається з `pass`.
324
+ - **YAML-файли відсутні / невалідні** → відповідне поле `expected` стане `null` і перевірка сегмента не виконується (але формат URL все одно валідується).
325
+ - **`HASURA_GRAPHQL_ENDPOINT` у `.env` закоментований через `#` після значення** → регулярка зупиниться на `#`, повернувши значення до коментаря.
326
+ - **Значення в подвійних/одинарних лапках** → лапки відкидаються регуляркою-капчуром.
@@ -0,0 +1,132 @@
1
+ # `fix.mjs` — entry-point правила `image-avif`
2
+
3
+ ## Огляд
4
+
5
+ Файл `npm/rules/image-avif/fix.mjs` — це **подвійний entry-point** одного з правил пакета `@nitra/cursor`. Він входить до інфраструктури «standard rules», де кожне правило живе в окремій теці (`npm/rules/<id>/`) і складається з:
6
+
7
+ - `fix.mjs` — тонкий wrapper-orchestrator (цей файл);
8
+ - `meta.json` — метадані (наприклад, `auto`-залежності, тут `{"auto": ["vue", "image-compress"]}`);
9
+ - `<id>.mdc` — людино-зрозумілий опис правила для Cursor;
10
+ - підтек `js/` (concerns на рівні JS-AST/тексту) і `policy/` (rego-policy).
11
+
12
+ Конкретно цей файл виконує дві ролі:
13
+
14
+ 1. **Library-режим.** Експортує функцію `run(ctx)`, яку CLI-orchestrator вищого рівня (`@nitra/cursor fix`) імпортує й викликає для прогону правила в межах загального пайплайну (з кешем walk-обходу й спільним підсумком).
15
+ 2. **Standalone-режим.** Якщо файл запущено напряму (`bun npm/rules/image-avif/fix.mjs` або еквівалент), він самостійно піднімає повний CLI-цикл (`runRuleCli`) — з підвантаженням конфігурації, whitelist та виведенням підсумку — і завершує процес кодом 0/1.
16
+
17
+ Сам файл **не містить** логіки перевірки контенту: уся робота делегується в `runStandardRule`, який послідовно проганяє чотири фази правила — `applies → JS-concerns → policy → mdc-refs`.
18
+
19
+ ## Експорти / API
20
+
21
+ | Експорт | Тип | Призначення |
22
+ | ------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `run` | `function (ctx?: RuleContext) => Promise<number>` | Library-точка входу правила. Викликається CLI-orchestratorом для запуску правила `image-avif`. Повертає exit-код (`0` — OK, `1` — є порушення). |
24
+
25
+ Інших іменованих чи default-експортів файл не має.
26
+
27
+ Окрім експорту, у файлі є **top-level side-effect**: блок `if (isRunAsCli(import.meta.url)) { … process.exit(await runRuleCli(...)) }`. Він спрацьовує **лише** коли цей `.mjs` запущено як CLI-entry (а не імпортовано як модуль), і завершує процес кодом, який повернув `runRuleCli`.
28
+
29
+ ## Функції
30
+
31
+ ### `run(ctx)`
32
+
33
+ ```js
34
+ export function run(ctx) {
35
+ return runStandardRule(import.meta.dirname, ctx)
36
+ }
37
+ ```
38
+
39
+ - **Сигнатура:** `run(ctx?: RuleContext): Promise<number>`.
40
+ - **Параметри:**
41
+ - `ctx` — необов'язковий контекст прогону (`RuleContext`), імпортується з `../../scripts/lib/run-standard-rule.mjs`. У документації самого файла зазначено, що в `ctx` передається, наприклад, `walkCache` — спільний кеш обходу файлової системи, щоб не повторювати walk між правилами в межах одного CLI-прогону. Якщо `ctx` не передано, `runStandardRule` сам ініціалізує необхідне.
42
+ - **Повертає:** `Promise<number>` — числовий exit-код:
43
+ - `0` — правило виконано без порушень;
44
+ - `1` — знайдені порушення (несумісність із `image-avif`).
45
+ - **Що робить усередині:** єдиним викликом делегує всю роботу в `runStandardRule(dir, ctx)`. Перший аргумент — `import.meta.dirname`, тобто **абсолютний шлях до теки `npm/rules/image-avif/`** на диску. Саме цей шлях `runStandardRule` використовує, щоб знайти сусідні `meta.json`, теку `js/` (JS-concerns) та теку `policy/` (rego-policy) і прогнати їх по конвеєру.
46
+ - **Side effects:** прямих side-effects сама функція не виконує. Усі ефекти (читання файлів, виклик `opa`, форматування звіту) інкапсульовано в `runStandardRule`. Функція асинхронна (повертає `Promise`), бо `runStandardRule` повертає `Promise`.
47
+
48
+ ### Top-level CLI-блок (не функція, але важливий side-effect)
49
+
50
+ ```js
51
+ if (isRunAsCli(import.meta.url)) {
52
+ // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
53
+ process.exit(await runRuleCli(import.meta.dirname))
54
+ }
55
+ ```
56
+
57
+ - **Умова входу:** `isRunAsCli(import.meta.url)` повертає `true`, лише якщо файл стартовано напряму як скрипт (Node.js/Bun), а не імпортовано як модуль. Це класичний `__main__`-патерн для ESM.
58
+ - **Що робить:** викликає `runRuleCli(import.meta.dirname)` — повноцінний CLI-обгортувач, який, на відміну від `runStandardRule`, додатково підтягує конфігурацію проєкту, застосовує whitelist і друкує summary (як коментар у файлі: «повний еквівалент `npx @nitra/cursor fix <id>`»). Результатом є `Promise<number>` із exit-кодом.
59
+ - **Завершення:** `process.exit(...)` примусово завершує процес із отриманим exit-кодом. Це навмисно — для CI/IDE інтеграцій, які зчитують exit-код. Через це поряд стоять директиви `eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit` із поясненням («standalone entry-point має повертати exit-code для CI/IDE»).
60
+ - **`await` на top-level:** доступний завдяки тому, що файл є ESM-модулем (`.mjs`) і виконується в середовищі з підтримкою top-level await (Node.js ≥ 14.8 / Bun).
61
+
62
+ ## Залежності
63
+
64
+ ### Імпорти з сусідніх модулів пакета
65
+
66
+ | Імпорт | Звідки | Призначення |
67
+ | ----------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
68
+ | `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Предикат: чи поточний модуль виконано напряму як CLI (порівнює `import.meta.url` зі скриптом, який стартував процес). |
69
+ | `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Standalone CLI-обгортка: завантажує конфігурацію, формує whitelist, прогоняє правило (через той самий `runStandardRule` усередині) і повертає exit-код. |
70
+ | `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Стандартний пайплайн правила: послідовно виконує фази **applies → JS-concerns → policy → mdc-refs**, базуючись на структурі теки правила (передано через `dirname`). |
71
+
72
+ Шляхи відносні: `../../scripts/lib/...` означає `npm/scripts/lib/...` (з огляду на розташування файла в `npm/rules/image-avif/`).
73
+
74
+ ### JSDoc-залежності типів
75
+
76
+ У JSDoc вказано тип `import('../../scripts/lib/run-standard-rule.mjs').RuleContext` для параметра `ctx`. Це **type-only** імпорт: на runtime він не існує, лише підказує IDE/типчекеру форму об'єкта контексту.
77
+
78
+ ### Сусідні артефакти правила (не імпортуються тут, але необхідні `runStandardRule`)
79
+
80
+ - `npm/rules/image-avif/meta.json` — `{ "auto": ["vue", "image-compress"] }`. Поле `auto` декларує, які інші правила автоматично «тягнуться» разом із `image-avif`.
81
+ - `npm/rules/image-avif/js/` — каталог JS-concerns (тек з функціями перевірки/трансформації).
82
+ - `npm/rules/image-avif/policy/` — каталог rego-policy для фази `policy`.
83
+ - `npm/rules/image-avif/image-avif.mdc` — людинозрозумілий опис правила (для Cursor) — використовується у фазі `mdc-refs`.
84
+
85
+ ### Зовнішні залежності
86
+
87
+ Прямих імпортів зовнішніх npm-пакетів у файлі немає. Усі залежності — внутрішні до пакета `@nitra/cursor`.
88
+
89
+ ## Потік виконання / Використання
90
+
91
+ ### Сценарій 1. Library-режим (через CLI-orchestrator)
92
+
93
+ Коли користувач запускає `npx @nitra/cursor fix` (або відповідну скіл-команду на кшталт `/n-fix`), верхньорівневий orchestrator:
94
+
95
+ 1. Обходить теку `npm/rules/`, знаходить правило `image-avif`.
96
+ 2. Динамічно імпортує `npm/rules/image-avif/fix.mjs` як ESM-модуль. У цей момент top-level `if (isRunAsCli(...))` повертає `false` (файл не запущено напряму), тож `process.exit` **не** викликається.
97
+ 3. Викликає `run(ctx)`, передаючи спільний контекст (наприклад, із заздалегідь побудованим `walkCache`).
98
+ 4. Усередині `run` делегує в `runStandardRule(import.meta.dirname, ctx)`, який послідовно проходить чотири фази:
99
+ - **applies** — визначає, до яких файлів проєкту правило взагалі застосовне;
100
+ - **JS-concerns** — запускає функції з теки `js/` для AST/текстових перевірок;
101
+ - **policy** — викликає `opa` з rego-файлами з `policy/`;
102
+ - **mdc-refs** — звіряє посилання у `.mdc`.
103
+ 5. Отримує `0`/`1`, передає назад orchestratorу, який агрегує результати всіх правил у спільний summary.
104
+
105
+ ### Сценарій 2. Standalone-режим (для дебагу/CI)
106
+
107
+ Користувач (або IDE/CI) запускає файл напряму:
108
+
109
+ ```bash
110
+ bun npm/rules/image-avif/fix.mjs
111
+ ```
112
+
113
+ 1. Бунт/Node стартує файл як ESM-скрипт. Виконуються імпорти.
114
+ 2. Експорт `run` зареєстровано, але **не викликається** — поза CLI-orchestratorом нікому його смикати.
115
+ 3. Виконується top-level `if`: `isRunAsCli(import.meta.url)` повертає `true`.
116
+ 4. Викликається `await runRuleCli(import.meta.dirname)` — повний еквівалент `npx @nitra/cursor fix image-avif`: завантаження конфігурації проєкту, whitelist, прогін правила (через той самий `runStandardRule` усередині `runRuleCli`), друк summary.
117
+ 5. `process.exit(<exitCode>)` завершує процес із кодом, який бачить CI/IDE.
118
+
119
+ ### Дизайн-патерн: dual-role entry-point
120
+
121
+ У коментарі автор файла прямо називає цей патерн: «Дві ролі fix.mjs: library (run) + standalone (main)». Це повторюваний патерн для всіх правил пакета — кожне правило має свій `fix.mjs` із такою ж двоїстою структурою. Завдяки цьому:
122
+
123
+ - CLI-orchestrator може **переюзати** ту саму функцію `run`, не дублюючи виклик `process.exit`;
124
+ - розробник може **локально дебажити** одне правило, запустивши його `fix.mjs` напряму;
125
+ - логіка пайплайну живе в одному місці (`runStandardRule`), а entry-point залишається тонким.
126
+
127
+ ### Що цей файл **не** робить
128
+
129
+ - Не визначає, **які саме** файли проєкту порушують `image-avif` — це робить тека `js/` і `policy/`.
130
+ - Не парсить аргументи CLI — це робить `runRuleCli`.
131
+ - Не пише в stdout/stderr напряму — увесь вивід формує `runStandardRule`/`runRuleCli`.
132
+ - Не модифікує файли проєкту — назва `fix` тут історична (контракт стандартного правила); фактична семантика — це check + (потенційно) auto-fix усередині concerns, але сам `fix.mjs` лише оркеструє.