@nitra/cursor 12.6.0 → 12.7.0

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 (269) hide show
  1. package/.claude-template/settings.template.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/bin/docs/n-cursor.md +4 -20
  4. package/bin/n-cursor.js +7 -53
  5. package/docs/stryker.config.md +20 -28
  6. package/package.json +1 -1
  7. package/rules/abie/docs/index.md +1 -0
  8. package/rules/abie/docs/main.md +29 -0
  9. package/rules/abie/{fix.mjs → main.mjs} +5 -3
  10. package/rules/adr/docs/index.md +1 -0
  11. package/rules/adr/docs/main.md +29 -0
  12. package/rules/adr/{fix.mjs → main.mjs} +5 -3
  13. package/rules/bun/docs/index.md +1 -0
  14. package/rules/bun/docs/main.md +30 -0
  15. package/rules/bun/js/docs/layout.md +11 -36
  16. package/rules/bun/{fix.mjs → main.mjs} +5 -3
  17. package/rules/capacitor/docs/index.md +1 -0
  18. package/rules/capacitor/docs/main.md +29 -0
  19. package/rules/capacitor/{fix.mjs → main.mjs} +5 -3
  20. package/rules/changelog/docs/index.md +1 -0
  21. package/rules/changelog/docs/main.md +27 -0
  22. package/rules/changelog/main.mjs +20 -0
  23. package/rules/ci4/docs/index.md +1 -0
  24. package/rules/ci4/docs/main.md +30 -0
  25. package/rules/ci4/main.mjs +20 -0
  26. package/rules/doc-files/docs/index.md +1 -0
  27. package/rules/doc-files/docs/main.md +31 -0
  28. package/rules/doc-files/js/docgen-files-batch.mjs +47 -3
  29. package/rules/doc-files/js/docgen-scan.mjs +89 -9
  30. package/rules/doc-files/js/docs/docgen-files-batch.md +15 -15
  31. package/rules/doc-files/js/docs/docgen-scan.md +34 -34
  32. package/rules/doc-files/js/docs/index.md +1 -0
  33. package/rules/doc-files/js/docs/run-lint.md +27 -0
  34. package/rules/doc-files/{lint/lint.mjs → js/run-lint.mjs} +23 -9
  35. package/rules/doc-files/{js/lint.mjs → main.mjs} +60 -10
  36. package/rules/docker/docs/index.md +1 -0
  37. package/rules/docker/docs/main.md +28 -0
  38. package/rules/docker/js/docs/lint.md +26 -54
  39. package/rules/docker/js/lint.mjs +11 -0
  40. package/rules/docker/lib/docker-hadolint.mjs +1 -1
  41. package/rules/docker/lib/docs/docker-hadolint.md +16 -173
  42. package/rules/docker/main.mjs +20 -0
  43. package/rules/efes/docs/index.md +1 -0
  44. package/rules/efes/docs/main.md +29 -0
  45. package/rules/efes/main.mjs +20 -0
  46. package/rules/feedback/docs/index.md +1 -0
  47. package/rules/feedback/docs/main.md +30 -0
  48. package/rules/feedback/main.mjs +20 -0
  49. package/rules/ga/docs/index.md +1 -0
  50. package/rules/ga/docs/main.md +29 -0
  51. package/rules/ga/{lint/lint.mjs → main.mjs} +36 -10
  52. package/rules/graphql/docs/index.md +1 -0
  53. package/rules/graphql/docs/main.md +36 -0
  54. package/rules/graphql/main.mjs +20 -0
  55. package/rules/hasura/docs/index.md +1 -0
  56. package/rules/hasura/docs/main.md +30 -0
  57. package/rules/hasura/main.mjs +20 -0
  58. package/rules/image-avif/docs/index.md +1 -0
  59. package/rules/image-avif/docs/main.md +30 -0
  60. package/rules/image-avif/js/docs/avif_generation.md +20 -233
  61. package/rules/image-avif/main.mjs +20 -0
  62. package/rules/image-compress/docs/index.md +1 -0
  63. package/rules/image-compress/docs/main.md +29 -0
  64. package/rules/image-compress/js/docs/package_setup.md +12 -11
  65. package/rules/image-compress/{js/lint.mjs → main.mjs} +21 -5
  66. package/rules/js-bun-db/docs/index.md +1 -0
  67. package/rules/js-bun-db/docs/main.md +30 -0
  68. package/rules/js-bun-db/main.mjs +20 -0
  69. package/rules/js-bun-redis/docs/index.md +1 -0
  70. package/rules/js-bun-redis/docs/main.md +29 -0
  71. package/rules/js-bun-redis/main.mjs +20 -0
  72. package/rules/js-lint/docs/index.md +1 -0
  73. package/rules/js-lint/docs/main.md +29 -0
  74. package/rules/js-lint/js/check.mjs +268 -0
  75. package/rules/js-lint/js/docs/check.md +39 -0
  76. package/rules/js-lint/js/docs/index.md +1 -1
  77. package/rules/js-lint/js/docs/tooling.md +12 -32
  78. package/rules/js-lint/js/tooling.mjs +1 -265
  79. package/rules/js-lint/{js/lint.mjs → main.mjs} +19 -2
  80. package/rules/js-lint-ci/docs/index.md +1 -0
  81. package/rules/js-lint-ci/docs/main.md +27 -0
  82. package/rules/js-lint-ci/main.mjs +33 -0
  83. package/rules/js-mssql/docs/index.md +1 -0
  84. package/rules/js-mssql/docs/main.md +30 -0
  85. package/rules/js-mssql/main.mjs +20 -0
  86. package/rules/js-run/docs/index.md +1 -0
  87. package/rules/js-run/docs/main.md +30 -0
  88. package/rules/js-run/main.mjs +20 -0
  89. package/rules/k8s/docs/index.md +1 -0
  90. package/rules/k8s/docs/main.md +40 -0
  91. package/rules/k8s/js/docs/index.md +12 -0
  92. package/rules/k8s/{lint/lint.mjs → main.mjs} +32 -10
  93. package/rules/nginx-default-tpl/docs/index.md +1 -0
  94. package/rules/nginx-default-tpl/docs/main.md +30 -0
  95. package/rules/nginx-default-tpl/main.mjs +20 -0
  96. package/rules/npm-module/docs/index.md +1 -0
  97. package/rules/npm-module/docs/main.md +29 -0
  98. package/rules/npm-module/js/docs/rule_meta.md +17 -16
  99. package/rules/npm-module/js/rule_meta.mjs +13 -3
  100. package/rules/npm-module/main.mjs +20 -0
  101. package/rules/php/docs/index.md +1 -0
  102. package/rules/php/docs/main.md +33 -0
  103. package/rules/php/js/docs/tooling.md +10 -10
  104. package/rules/php/{lint/lint.mjs → main.mjs} +32 -6
  105. package/rules/python/docs/index.md +1 -0
  106. package/rules/python/docs/main.md +31 -0
  107. package/rules/python/js/docs/tooling.md +17 -17
  108. package/rules/python/{lint/lint.mjs → main.mjs} +29 -5
  109. package/rules/rego/docs/index.md +1 -0
  110. package/rules/rego/docs/main.md +37 -0
  111. package/rules/rego/{lint/lint.mjs → main.mjs} +27 -5
  112. package/rules/release/docs/index.md +1 -0
  113. package/rules/release/docs/main.md +29 -0
  114. package/rules/release/docs/release.md +0 -3
  115. package/rules/release/release.mdc +10 -0
  116. package/rules/rust/docs/index.md +1 -0
  117. package/rules/rust/docs/main.md +27 -0
  118. package/rules/rust/{js/lint.mjs → main.mjs} +20 -3
  119. package/rules/security/docs/index.md +1 -0
  120. package/rules/security/docs/main.md +28 -0
  121. package/rules/security/main.mjs +45 -0
  122. package/rules/style-lint/docs/index.md +1 -0
  123. package/rules/style-lint/docs/main.md +29 -0
  124. package/rules/style-lint/{js/lint.mjs → main.mjs} +19 -1
  125. package/rules/tauri/docs/index.md +1 -0
  126. package/rules/tauri/docs/main.md +29 -0
  127. package/rules/tauri/main.mjs +20 -0
  128. package/rules/test/docs/index.md +1 -0
  129. package/rules/test/docs/main.md +30 -0
  130. package/rules/test/main.mjs +20 -0
  131. package/rules/text/docs/index.md +1 -0
  132. package/rules/text/docs/main.md +29 -0
  133. package/rules/text/js/docs/cspell-fix.md +30 -0
  134. package/rules/text/js/docs/formatting.md +12 -45
  135. package/rules/text/js/docs/index.md +4 -0
  136. package/rules/text/js/docs/run-dotenv-linter.md +31 -0
  137. package/rules/text/js/docs/run-shellcheck.md +28 -0
  138. package/rules/text/js/docs/run-v8r.md +29 -0
  139. package/rules/text/{lint/lint.mjs → main.mjs} +38 -9
  140. package/rules/text/text.mdc +5 -14
  141. package/rules/tool-surface/docs/index.md +1 -0
  142. package/rules/tool-surface/docs/main.md +29 -0
  143. package/rules/tool-surface/main.mjs +20 -0
  144. package/rules/vue/docs/index.md +1 -0
  145. package/rules/vue/docs/main.md +29 -0
  146. package/rules/vue/main.mjs +20 -0
  147. package/rules/worktree/docs/index.md +1 -0
  148. package/rules/worktree/docs/main.md +28 -0
  149. package/rules/worktree/main.mjs +20 -0
  150. package/scripts/docs/index.md +1 -0
  151. package/scripts/docs/post-tool-use-check.md +29 -0
  152. package/scripts/docs/sync-claude-config.md +64 -92
  153. package/scripts/lib/adr/docs/normalize-cli.md +0 -3
  154. package/scripts/lib/adr/docs/normalize-pipeline.md +0 -3
  155. package/scripts/lib/docs/gha-workflow.md +25 -317
  156. package/scripts/lib/docs/index.md +1 -0
  157. package/scripts/lib/docs/list-project-rules-mdc.md +5 -4
  158. package/scripts/lib/docs/list-rule-ids.md +15 -148
  159. package/scripts/lib/docs/read-n-cursor-config-lite.md +12 -16
  160. package/scripts/lib/docs/run-lint-step.md +13 -13
  161. package/scripts/lib/docs/run-lint.md +30 -0
  162. package/scripts/lib/docs/run-rule-cli.md +14 -10
  163. package/scripts/lib/docs/run-standard-lint.md +27 -10
  164. package/scripts/lib/docs/run-standard-rule.md +12 -11
  165. package/scripts/lib/docs/timing-summary.md +11 -12
  166. package/scripts/lib/docs/worktree-notice.md +0 -3
  167. package/scripts/lib/fix/docs/index.md +1 -0
  168. package/scripts/lib/fix/docs/orchestrator.md +23 -18
  169. package/scripts/lib/fix/docs/run-conformance-check.md +32 -0
  170. package/scripts/lib/fix/docs/t0.md +10 -9
  171. package/scripts/lib/fix/orchestrator.mjs +5 -5
  172. package/scripts/lib/fix/{run-fix-check.mjs → run-conformance-check.mjs} +13 -13
  173. package/scripts/lib/fix/t0.mjs +3 -3
  174. package/scripts/lib/gha-workflow.mjs +1 -1
  175. package/scripts/lib/list-project-rules-mdc.mjs +1 -1
  176. package/scripts/lib/list-rule-ids.mjs +12 -3
  177. package/scripts/lib/read-n-cursor-config-lite.mjs +2 -2
  178. package/scripts/lib/run-lint-step.mjs +1 -1
  179. package/{rules/lint/js/orchestrate.mjs → scripts/lib/run-lint.mjs} +42 -20
  180. package/scripts/lib/run-rule-cli.mjs +4 -4
  181. package/scripts/lib/run-standard-lint.mjs +19 -6
  182. package/scripts/lib/run-standard-rule.mjs +4 -4
  183. package/scripts/lib/timing-summary.mjs +1 -1
  184. package/scripts/{post-tool-use-fix.mjs → post-tool-use-check.mjs} +9 -9
  185. package/scripts/sync-claude-config.mjs +2 -2
  186. package/rules/changelog/fix.mjs +0 -18
  187. package/rules/ci4/fix.mjs +0 -18
  188. package/rules/doc-files/fix.mjs +0 -19
  189. package/rules/doc-files/js/docs/lint.md +0 -34
  190. package/rules/doc-files/lint/docs/index.md +0 -11
  191. package/rules/doc-files/lint/docs/lint.md +0 -35
  192. package/rules/docker/fix.mjs +0 -18
  193. package/rules/docker/lint/docs/index.md +0 -11
  194. package/rules/docker/lint/docs/lint.md +0 -200
  195. package/rules/docker/lint/lint.mjs +0 -95
  196. package/rules/efes/fix.mjs +0 -18
  197. package/rules/feedback/fix.mjs +0 -18
  198. package/rules/ga/fix.mjs +0 -18
  199. package/rules/ga/js/docs/lint.md +0 -20
  200. package/rules/ga/js/lint.mjs +0 -12
  201. package/rules/ga/lint/docs/index.md +0 -11
  202. package/rules/ga/lint/docs/lint.md +0 -31
  203. package/rules/graphql/fix.mjs +0 -18
  204. package/rules/hasura/fix.mjs +0 -18
  205. package/rules/image-avif/fix.mjs +0 -18
  206. package/rules/image-compress/fix.mjs +0 -18
  207. package/rules/image-compress/js/docs/lint.md +0 -24
  208. package/rules/js-bun-db/fix.mjs +0 -18
  209. package/rules/js-bun-redis/fix.mjs +0 -18
  210. package/rules/js-lint/fix.mjs +0 -18
  211. package/rules/js-lint/js/docs/lint.md +0 -32
  212. package/rules/js-lint-ci/fix.mjs +0 -18
  213. package/rules/js-lint-ci/js/docs/lint.md +0 -22
  214. package/rules/js-lint-ci/js/lint.mjs +0 -15
  215. package/rules/js-mssql/fix.mjs +0 -18
  216. package/rules/js-run/fix.mjs +0 -18
  217. package/rules/k8s/fix.mjs +0 -18
  218. package/rules/k8s/js/lint.mjs +0 -14
  219. package/rules/k8s/lint/docs/index.md +0 -11
  220. package/rules/k8s/lint/docs/lint.md +0 -413
  221. package/rules/lint/docs/fix.md +0 -25
  222. package/rules/lint/docs/index.md +0 -11
  223. package/rules/lint/fix.mjs +0 -18
  224. package/rules/lint/js/docs/index.md +0 -11
  225. package/rules/lint/js/docs/orchestrate.md +0 -31
  226. package/rules/lint/meta.json +0 -1
  227. package/rules/nginx-default-tpl/fix.mjs +0 -18
  228. package/rules/npm-module/fix.mjs +0 -18
  229. package/rules/php/fix.mjs +0 -18
  230. package/rules/php/js/docs/lint.md +0 -20
  231. package/rules/php/js/lint.mjs +0 -15
  232. package/rules/php/lint/docs/index.md +0 -11
  233. package/rules/php/lint/docs/lint.md +0 -219
  234. package/rules/python/fix.mjs +0 -18
  235. package/rules/python/js/docs/lint.md +0 -21
  236. package/rules/python/js/lint.mjs +0 -14
  237. package/rules/python/lint/docs/index.md +0 -11
  238. package/rules/python/lint/docs/lint.md +0 -29
  239. package/rules/rego/fix.mjs +0 -18
  240. package/rules/rego/js/docs/lint.md +0 -21
  241. package/rules/rego/js/lint.mjs +0 -12
  242. package/rules/rego/lint/docs/index.md +0 -11
  243. package/rules/rego/lint/docs/lint.md +0 -208
  244. package/rules/rust/fix.mjs +0 -18
  245. package/rules/rust/js/docs/lint.md +0 -21
  246. package/rules/security/fix.mjs +0 -18
  247. package/rules/security/js/docs/lint.md +0 -175
  248. package/rules/security/js/lint.mjs +0 -26
  249. package/rules/style-lint/fix.mjs +0 -18
  250. package/rules/style-lint/js/docs/lint.md +0 -31
  251. package/rules/tauri/fix.mjs +0 -18
  252. package/rules/test/fix.mjs +0 -18
  253. package/rules/text/fix.mjs +0 -18
  254. package/rules/text/js/docs/lint.md +0 -23
  255. package/rules/text/js/lint.mjs +0 -15
  256. package/rules/text/lint/docs/cspell-fix.md +0 -32
  257. package/rules/text/lint/docs/index.md +0 -15
  258. package/rules/text/lint/docs/lint.md +0 -36
  259. package/rules/text/lint/docs/run-dotenv-linter.md +0 -161
  260. package/rules/text/lint/docs/run-shellcheck.md +0 -216
  261. package/rules/text/lint/docs/run-v8r.md +0 -201
  262. package/rules/tool-surface/fix.mjs +0 -18
  263. package/rules/vue/fix.mjs +0 -18
  264. package/rules/worktree/fix.mjs +0 -18
  265. /package/rules/release/{fix.mjs → main.mjs} +0 -0
  266. /package/rules/text/{lint → js}/cspell-fix.mjs +0 -0
  267. /package/rules/text/{lint → js}/run-dotenv-linter.mjs +0 -0
  268. /package/rules/text/{lint → js}/run-shellcheck.mjs +0 -0
  269. /package/rules/text/{lint → js}/run-v8r.mjs +0 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/ga/main.mjs
5
+ docgen:
6
+ crc: f6b5f0b3
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 90
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Цей модуль є CLI-обгорткою над канонічним `lint-ga` (ga.mdc). Він автоматично встановлює `shellcheck` та `conftest` через `ensureTool` (використовуючи brew/scoop/GitHub Release залежно від платформи), перевіряє наявність `uv` (для `uvx zizmor`), а потім послідовно виконує `bunx github-actionlint`, `uvx zizmor --offline --collect=workflows .` та делегує до `rules/ga/check.mjs::check`. Функція `lint` викликає `runLintGaCli`, який є частиною оркестраторного адаптера `n-cursor lint ga`. При відсутності `uv`, користувачеві надається підказка з командою встановлення, наприклад, https://astral.sh/uv/install.sh, оскільки `uv` не в реєстрі `ensureTool`.
14
+
15
+ ## Поведінка
16
+
17
+ run виконує стандартну перевірку правила, застосовуючи логіку, описану в `mdc-refs`.
18
+ runLintGaCli виконує повний канонічний процес `lint-ga`: автоматично встановлює `shellcheck` та `conftest`, перевіряє наявність `uv`, а потім послідовно запускає `github-actionlint`, `zizmor` та перевірку Rego-полісі через `rules/ga/check.mjs`.
19
+ lint делегує виконання повного канонічного процесу `lint-ga` через `runLintGaCli`.
20
+
21
+ ## Публічний API
22
+
23
+ run — виконує основну перевірку, яка охоплює логіку застосування до JS-задач, політики та посилання на mdc-референси.
24
+ runLintGaCli — виконує лінтинг з використанням інструментів actionlint/zizmor та перевірку ga.
25
+ lint — керує процесом лінтингу, делегуючи виконання `runLintGaCli`.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -2,13 +2,13 @@
2
2
  * CLI-обгортка над канонічним `lint-ga` (ga.mdc): авто-встановлює `shellcheck` і `conftest`
3
3
  * через `ensureTool` (brew/scoop/GitHub Release per-platform), перевіряє наявність `uv` (для `uvx`),
4
4
  * тоді послідовно виконує `bunx github-actionlint`, `uvx zizmor --offline --collect=workflows .` і
5
- * делегує до `rules/ga/fix.mjs::check()` — там і Rego-частина (через `runConftestBatch`),
5
+ * делегує до `rules/ga/check.mjs::check()` — там і Rego-частина (через `runConftestBatch`),
6
6
  * і JS cross-file перевірки правил `ga.mdc`.
7
7
  *
8
8
  * Plan B-патерн (rego-authoritative): Rego-полісі (`npm/policy/ga/`) запускає вже сам
9
- * `rules/ga/fix.mjs::check()` як перший крок — `lint-ga.mjs` про це не знає. Раніше `lint-ga.mjs` сам
9
+ * `rules/ga/check.mjs::check()` як перший крок — `lint-ga.mjs` про це не знає. Раніше `lint-ga.mjs` сам
10
10
  * спавнив conftest для `ga.<name>` per-workflow і `ga.workflow_common` (PoC); тепер ця логіка
11
- * централізована у `rules/ga/fix.mjs`, тож одне джерело істини, без дублювання між
11
+ * централізована у `rules/ga/check.mjs`, тож одне джерело істини, без дублювання між
12
12
  * `lint-ga` і `npx \@nitra/cursor check ga`.
13
13
  *
14
14
  * Без preflight `actionlint` (через `bunx github-actionlint`) мовчки пропускає shell-перевірки в
@@ -18,18 +18,30 @@
18
18
  * `uv` потрібен для `uvx zizmor`. Якщо його нема — `uvx zizmor` падає неінформативно; підказка
19
19
  * з командою встановлення коротша й корисніша. `uv` не в реєстрі ensureTool → hint-only.
20
20
  *
21
- * Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
21
+ * Експортовано окремо `runLintGaCli` — викликається через `n-cursor lint ga` (оркестраторний адаптер `lint()` делегує сюди); окремої bin-підкоманди `lint-ga` немає.
22
22
  *
23
23
  * Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) —
24
24
  * `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
25
25
  */
26
26
  import { platform } from 'node:process'
27
27
 
28
- import { check as checkGa } from '../js/workflows.mjs'
29
- import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
30
- import { runLintStep } from '../../../scripts/lib/run-lint-step.mjs'
31
- import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
32
- import { ensureTool } from '../../../scripts/lib/ensure-tool.mjs'
28
+ import { check as checkGa } from './js/workflows.mjs'
29
+ import { resolveCmd } from '../../scripts/utils/resolve-cmd.mjs'
30
+ import { runLintStep } from '../../scripts/lib/run-lint-step.mjs'
31
+ import { runStandardLint } from '../../scripts/lib/run-standard-lint.mjs'
32
+ import { ensureTool } from '../../scripts/lib/ensure-tool.mjs'
33
+ import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
34
+ import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
35
+
36
+ /**
37
+ * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня (applies → JS-concerns
38
+ * → policy → mdc-refs); `lint()` нижче — lint-поверхня (actionlint/zizmor + check-ga), імпл інлайн тут.
39
+ * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону
40
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
41
+ */
42
+ export function run(ctx) {
43
+ return runStandardRule(import.meta.dirname, ctx)
44
+ }
33
45
 
34
46
  /**
35
47
  * Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
@@ -109,7 +121,7 @@ function preflight(dep) {
109
121
  * 2) preflight: `uv` (для `uvx zizmor`) — hint-only, без авто-install;
110
122
  * 3) `bunx github-actionlint`;
111
123
  * 4) `uvx zizmor --offline --collect=workflows .`;
112
- * 5) `rules/ga/fix.mjs::check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file
124
+ * 5) `rules/ga/check.mjs::check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file
113
125
  * перевірки правил `ga.mdc`. Це **те саме**, що робить `npx \@nitra/cursor check ga`, тож
114
126
  * `lint-ga` тепер є суперсетом перевірки правила: external-tools + check.
115
127
  * @returns {Promise<number>} 0 — все OK, інакше — код першого кроку, що впав
@@ -133,3 +145,17 @@ async function runLintGaSteps() {
133
145
  }
134
146
 
135
147
  export const runLintGaCli = () => runStandardLint(import.meta.dirname, runLintGaSteps)
148
+
149
+ /**
150
+ * Оркестраторний адаптер `n-cursor lint ga`: делегує у `runLintGaCli`.
151
+ * @param {string[] | undefined} _files ігнорується (whole-repo аналіз)
152
+ * @returns {Promise<number>} exit code
153
+ */
154
+ export function lint(_files) {
155
+ return runLintGaCli()
156
+ }
157
+
158
+ if (isRunAsCli(import.meta.url)) {
159
+ // Standalone: bun rules/ga/main.mjs — повний еквівалент `npx @nitra/cursor check ga`.
160
+ process.exitCode = await runRuleCli(import.meta.dirname)
161
+ }
@@ -9,3 +9,4 @@ resource: npm/rules/graphql/
9
9
  | Файл | Тип |
10
10
  |---|---|
11
11
  | [fix.mjs](fix.md) | JS Module |
12
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,36 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/graphql/main.mjs
5
+ docgen:
6
+ crc: 762b6875
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль валідує дані на відповідність політикам, використовуючи конфігурації з meta.json, логіку визначення зацікавленості та контекст. При виконанні як CLI, він завантажує конфігурації, перевіряє білий список, агрегує стан валідації та завершує роботу відповідним кодом.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Викликається функція `run` для виконання перевірки.
18
+ 2. Виконання перевірки застосовує:
19
+ * Конфігурації, визначені у `meta.json`.
20
+ * Логіку, пов'язану з JS-зацікавленістю.
21
+ * Політику, визначену в контексті.
22
+ * Посилання на MDC.
23
+ 3. Якщо скрипт виконується як окремий інструмент (CLI), виконується повний цикл правил:
24
+ * Завантаження конфігурацій.
25
+ * Перевірка білого списку.
26
+ * Зведення результатів.
27
+ * Вихід із кодом, що інформує про успіх чи порушення.
28
+
29
+ ## Публічний API
30
+
31
+ run — єдиний вхідний пункт правила, який виконує послідовність перевірок: застосовує логіку, обробляє JS-специфічні моменти, перевіряє політику та посилання на MDC.
32
+
33
+ ## Гарантії поведінки
34
+
35
+ - Read-only: не виконує операцій запису (ФС/БД).
36
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,20 @@
1
+ import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
2
+ import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
3
+
4
+ /**
5
+ * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня: applies →
6
+ * JS-concerns → policy → mdc-refs (через runStandardRule). Lint-поверхні правило не має
7
+ * (`meta.json` без `lint`), тож експорту `lint` тут немає.
8
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
9
+ * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
10
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
11
+ */
12
+ export function run(ctx) {
13
+ return runStandardRule(import.meta.dirname, ctx)
14
+ }
15
+
16
+ if (isRunAsCli(import.meta.url)) {
17
+ // Standalone: bun rules/<id>/main.mjs — повний еквівалент `npx @nitra/cursor check <id>`
18
+ // (config-loading + whitelist + summary): library-роль (run) + standalone-роль (CLI-блок).
19
+ process.exitCode = await runRuleCli(import.meta.dirname)
20
+ }
@@ -9,3 +9,4 @@ resource: npm/rules/hasura/
9
9
  | Файл | Тип |
10
10
  |---|---|
11
11
  | [fix.mjs](fix.md) | JS Module |
12
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/hasura/main.mjs
5
+ docgen:
6
+ crc: 762b6875
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль виконує логіку, визначену у конфігурації `meta.json`. Він валідує дані відповідно до правил, застосовує визначену політику та збирає посилання до MDC. При запуску через публічну функцію `run` ініціюється виконання правила. Модуль є read-only і не виконує операцій запису у файлову систему чи бази даних.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Викликається публічна функція `run`.
18
+ 2. Виконується перевірка на відповідність конфігурації, що описується у `meta.json`.
19
+ 3. Виконується застосування політики, що визначається в конфігурації.
20
+ 4. Здійснюється збір посилань до MDC.
21
+ 5. Якщо код виконується як окрема утиліта (CLI), викликається механізм виконання правила.
22
+
23
+ ## Публічний API
24
+
25
+ run — виконує повний цикл перевірки: застосовує логіку, аналізує JS-залежності, перевіряє політику та посилання на MDC.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
30
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,20 @@
1
+ import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
2
+ import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
3
+
4
+ /**
5
+ * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня: applies →
6
+ * JS-concerns → policy → mdc-refs (через runStandardRule). Lint-поверхні правило не має
7
+ * (`meta.json` без `lint`), тож експорту `lint` тут немає.
8
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
9
+ * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
10
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
11
+ */
12
+ export function run(ctx) {
13
+ return runStandardRule(import.meta.dirname, ctx)
14
+ }
15
+
16
+ if (isRunAsCli(import.meta.url)) {
17
+ // Standalone: bun rules/<id>/main.mjs — повний еквівалент `npx @nitra/cursor check <id>`
18
+ // (config-loading + whitelist + summary): library-роль (run) + standalone-роль (CLI-блок).
19
+ process.exitCode = await runRuleCli(import.meta.dirname)
20
+ }
@@ -9,3 +9,4 @@ resource: npm/rules/image-avif/
9
9
  | Файл | Тип |
10
10
  |---|---|
11
11
  | [fix.mjs](fix.md) | JS Module |
12
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/image-avif/main.mjs
5
+ docgen:
6
+ crc: 762b6875
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль виконує перевірку на основі логіки правил, використовуючи конфігурації з meta.json, політики та посилання на MDC. При запуску як окрема утиліта, він завантажує конфігурації, застосовує білі списки для фільтрації та формує зведений звіт. Результат виконання визначає код виходу процесу. Кешування відбувається у межах прогону. Модуль є Read-only, тобто не здійснює записів у файлову систему чи бази даних.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Викликається функція `run` для виконання перевірки.
18
+ 2. Виконання `run` застосовує логіку правила, включаючи обробку конфігурацій, перевірку політик та посилання на MDC.
19
+ 3. Якщо код виконується як окрема утиліта (CLI), ініціюється повний запуск правила.
20
+ 4. Запуск правила як утиліта включає завантаження конфігурацій, застосування білих списків та формування зведеного звіту.
21
+ 5. Результат виконання визначає код виходу процесу.
22
+
23
+ ## Публічний API
24
+
25
+ run — виконує основну логіку правила: застосовує перевірки до коду, аналізує аспекти JavaScript та застосовує політику, використовуючи посилання на метадані.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Read-only: не виконує операцій запису (ФС/БД).
30
+ - Кешує результати в межах одного прогону.
@@ -3,244 +3,31 @@ type: JS Module
3
3
  title: avif_generation.mjs
4
4
  resource: npm/rules/image-avif/js/avif_generation.mjs
5
5
  docgen:
6
- crc: 101bb0dd
6
+ crc: 3f00d274
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
7
9
  ---
8
10
 
9
- Модуль реалізує перевірку правила `image-avif.mdc`: генерацію AVIF-двійників растрових зображень і ув'язування цих двійників із посиланнями у `.vue`- та `.html`-файлах монорепо. Експортує функцію `check`, яку викликає CLI `n-cursor` для команди `check image-avif` / `fix image-avif`.
11
+ Модуль трансформує посилання на растрові зображення у шаблонах `.vue` та `.html` у workspace-пакетах, замінюючи їх на формат AVIF. Процес залежить від конфігурацій, визначених у package.json. Модуль оновлює всі посилання на зображення, якщо перевірка AVIF не вимкнена, створюючи AVIF-двійники. Також він видаляє посилання на AVIF-файли, які не існують.
10
12
 
11
- Загальний сценарій роботи `check`:
13
+ ## Поведінка
12
14
 
13
- 1. **Pre-scan** шукає у `.vue`/`.html` хоча б одне raster-посилання (через `import x from '...png'` або `<img src="...png" />`), яке потенційно треба переписати на AVIF-двійник. Пакети з opt-out `"@nitra/minify-image": { "disable-avif": true }` у `package.json` пропускаються. Якщо жодного raster-посилання не знайдено модуль одразу повертає успіх і не запускає ні `npx`, ні rewrite, ні cleanup-пасс.
14
- 2. **AVIF-генерація** викликає `npx @nitra/minify-image --src=. --write --avif`, який створює AVIF-двійники поряд з оригіналами.
15
- 3. **Rewrite-пасс** — для кожного workspace-пакета (без opt-out) переписує raster-посилання у `.vue`/`.html` на `<...>.avif`, якщо двійник реально існує на диску. Якщо двійника немає (наприклад, оригіналу теж нема) — фейлить конкретне посилання.
16
- 4. **Cleanup-пасс** видаляє AVIF-сироти (`<...>.avif`, на які не лишилось жодного посилання у `.vue`/`.html` репозиторію), реалізуючи умову «AVIF лишається лише там, де заміна вдалася».
15
+ 1. Перевіряється, чи містить репозиторій посилання на raster-зображення у файлах `.vue` або `.html` у workspace-пакетах, які не мають вимкненої перевірки AVIF. Якщо посилань немає, процес AVIF-генерації та очищення пропускається.
16
+ 2. Запускається генерація AVIF-двійників за допомогою зовнішнього інструменту.
17
+ 3. Скануються workspace-пакети:
18
+ а. Визначається, чи вимкнено примусове застосування AVIF у пакеті через конфігурацію `package.json`. Якщо так, пакет пропускається для подальшої перевірки шаблонів.
19
+ б. Для пакетів, де AVIF не вимкнено, скануються файли `.vue` та `.html`.
20
+ в. У знайдених посиланнях на raster-зображення, якщо відповідний AVIF-двійник існує, посилання замінюється на `.avif`.
21
+ г. Якщо AVIF-двійник не існує, фіксується помилка.
22
+ д. Після обробки файлу, він записується з новими змінами.
23
+ е. Збирається список абсолютних шляхів AVIF-двійників, на які залишилося живе посилання.
24
+ 4. Видаляються AVIF-сироти — файли `.avif`, які не були відмічені як вжиті у кроці 3.б.
25
+ 5. Повертається код виходу, що відображає успішність виконання всіх етапів.
17
26
 
18
- Модуль свідомо не дублює перевірки cache/dependency policy з правила `image-compress`. Правило `image-avif` самостійне й вмикається лише там, де AVIF підтримується (адмінки), а не у публічних сайтах.
27
+ ## Публічний API
19
28
 
20
- ## Експорти / API
29
+ check генерує AVIF-зображення, автоматично замінює посилання на растрові зображення у `.vue`/`.html` файлах та очищає від неіснуючих AVIF-файлів.
21
30
 
22
- | Експорт | Тип | Призначення |
23
- | ------------- | ---------------- | ----------------------------------------------------------------------------------- |
24
- | `check(cwd?)` | `async function` | Точка входу перевірки `image-avif`; повертає exit-код (`0` — OK, `1` — є проблеми). |
31
+ ## Гарантії поведінки
25
32
 
26
- Інші функції у модулі (`packageHasAvifDisabled`, `resolveImageCandidates`, `checkVueAvifImportsInPackage`, `checkVueAvifImports`, `hasAnyVueRasterReference`, `runAvifGeneration`, `cleanupOrphanAvifs`) — внутрішні, не експортуються.
27
-
28
- ## Константи
29
-
30
- | Константа | Значення / форма | Призначення |
31
- | -------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
32
- | `MINIFY_PACKAGE_NAME` | `'@nitra/minify-image'` | Імʼя CLI-пакета, який генерує AVIF (викликається через `npx`). |
33
- | `PKG_CONFIG_FIELD` | `'@nitra/minify-image'` | Поле у `package.json` для конфігу `@nitra/minify-image` (`disable-avif: true` тощо). |
34
- | `CLEANUP_EXTRA_IGNORE_DIR_NAMES` | `Set` з `'build'`, `'android'`, `'ios'`, `'.output'`, `'.nuxt'`, `'.cache'` | Імена каталогів, які cleanup-пасс ігнорує додатково до стандартних (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`, які вже скіпає `walkDir`). Це артефакти збірки / нативних платформ — AVIF всередині є продуктом попереднього `bun run build` / Capacitor sync. |
35
- | `VUE_RASTER_IMPORT_RE` | `/import\s+\w[\w$]\*\s+from\s+['"]([^'"\n]+\.(?:png | jpe?g | gif))['"]/giu` | Регексп для `import name from '...ext'` у `.vue`/`.html`; група 1 — повний шлях до зображення. |
36
- | `VUE_RASTER_STATIC_SRC_RE` | `/(?<![:-_.])\bsrc\s*=\s*['"]([^'"\s]+\.(?:png | jpe?g | gif))['"]/giu` | Регексп для статичних `<img src="...png" />` у шаблоні `.vue`. Lookbehind `(?<![:\-_.])` виключає `:src="..."` (реактивний JS-вираз), `data-src="..."` і `obj.src=...`. |
37
- | `VUE_AVIF_REF_RE` | `/['"]([^'"\s]+\.(?:png | jpe?g | gif)\.avif)['"]/giu` | Регексп для готових AVIF-посилань у `.vue`/`.html`. Використовується тільки для збору множини «живих» AVIF — щоб після rewrite знати, які `<...>.avif` ще на щось посилаються. |
38
-
39
- ## Типи
40
-
41
- ### `RewriteStats`
42
-
43
- ```text
44
- {
45
- rewrittenRefs: number // скільки конкретних посилань переписано на .avif
46
- rewrittenFiles: number // у скількох .vue/.html файлах хоч одне посилання змінилося
47
- failedRefs: number // скільки конкретних посилань не вдалося переписати (.avif не існував)
48
- }
49
- ```
50
-
51
- Аґреговані лічильники по проходу `check image-avif`, мутуються `checkVueAvifImportsInPackage`, потрапляють у фінальний `pass`-меседж.
52
-
53
- ## Функції
54
-
55
- ### `packageHasAvifDisabled(pkg)`
56
-
57
- - **Сигнатура:** `(pkg: Record<string, unknown>) => boolean`
58
- - **Параметри:**
59
- - `pkg` — розібраний обʼєкт `package.json` пакета.
60
- - **Повертає:** `true`, якщо у `package.json` встановлено `"@nitra/minify-image": { "disable-avif": true }`, інакше `false`.
61
- - **Side effects:** немає.
62
-
63
- ### `resolveImageCandidates(importPath, sourceAbsPath, packageRootAbs)`
64
-
65
- - **Сигнатура:** `(importPath: string, sourceAbsPath: string, packageRootAbs: string | null) => string[]`
66
- - **Параметри:**
67
- - `importPath` — шлях з `import x from '...'` або `src="..."`.
68
- - `sourceAbsPath` — абсолютний шлях файла-джерела (`.vue`/`.html`).
69
- - `packageRootAbs` — абсолютний корінь workspace-пакета, у якому лежить джерело (для резолвера `/path` як `<root>/public<path>`); `null`, якщо невідомо.
70
- - **Повертає:** впорядкований список абсолютних шляхів-кандидатів, по яких caller перевіряє існування `<candidate>` або `<candidate>.avif`.
71
- - **Правила резолва:**
72
- - `./x.png`, `../x.png` — відносно файла-джерела.
73
- - `/x.png` (Vite/Quasar-конвенція) — спочатку `<packageRoot>/public/x.png`, потім `<packageRoot>/x.png`, нарешті `<cwd>/x.png` як legacy fallback.
74
- - голий шлях з принаймні одним `/` (наприклад `assets/img.png`) — relative-to-source, плюс `<packageRoot>/public/<path>` як другий кандидат.
75
- - bare-шлях без `/` (наприклад `foo`) — ймовірно alias-resolver Vite/Webpack; повертається порожній список → caller просто пропускає посилання, не звітує fail.
76
- - **Side effects:** немає; шляхи будуються через `path.join`, файли не зачіпаються.
77
-
78
- ### `checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, ignorePaths, usedAvifAbs, stats, fail, cwd)`
79
-
80
- - **Сигнатура:** `async (packageRoot: string, otherRootsAbs: string[], ignorePaths: string[], usedAvifAbs: Set<string>, stats: RewriteStats, fail: (msg: string) => void, cwd: string) => Promise<void>`
81
- - **Параметри:**
82
- - `packageRoot` — відносний шлях до кореня workspace-пакета (`'.'` або `'demo'`, тощо).
83
- - `otherRootsAbs` — абсолютні шляхи інших workspace-коренів; їхні піддерева пропускаються, щоб не сканувати один файл двічі.
84
- - `ignorePaths` — абсолютні шляхи каталогів, повністю виключених з обходу (зі `.cursorignore` тощо).
85
- - `usedAvifAbs` — мутабельна множина абсолютних шляхів `.avif`, що мають хоч одне посилання у `.vue`/`.html`; функція доповнює її.
86
- - `stats` — глобальні лічильники `RewriteStats`, мутуються тут.
87
- - `fail` — callback для звіту про помилку.
88
- - `cwd` — корінь репозиторію.
89
- - **Повертає:** `Promise<void>`, який резолвиться по завершенню обробки пакета.
90
- - **Side effects:**
91
- - Читає всі `.vue`/`.html` файли пакета через `walkDir`.
92
- - Перезаписує файли, у яких хоч одне посилання вдалось переписати (write-then-fail: запис відбувається ОДРАЗУ після обробки одного файла; провал на наступному файлі не відкочує вже записані зміни попередніх).
93
- - Мутує `usedAvifAbs` і `stats`.
94
- - Викликає `fail(msg)` для кожного raster-посилання, для якого AVIF-двійника немає на диску.
95
- - **Логіка:**
96
- - Збирає `targetFiles` — лише `.vue`/`.html` у `absRoot`, що не належать іншим workspace-кореням.
97
- - Для кожного файла застосовує `processMatches` із двома регекспами: `VUE_RASTER_IMPORT_RE` і `VUE_RASTER_STATIC_SRC_RE`. У `replaceAll` для кожного матчу резолвить кандидатів через `resolveImageCandidates`; якщо `existsSync(c + '.avif')` знаходить двійник — переписує посилання на `<importPath>.avif`, інкрементує `rewrittenRefs`, додає до `usedAvifAbs`; якщо ні — інкрементує `failedRefs` і викликає `fail`. Bare-alias (порожній список кандидатів) — пропускається без fail.
98
- - Окремо проходить `VUE_AVIF_REF_RE` по оновленому контенту й додає до `usedAvifAbs` усі AVIF-кандидати, які існують на диску (це треба, щоб cleanup-пасс не видалив AVIF, на який є посилання поза rewrite-патернами).
99
- - Якщо контент змінився — записує файл і інкрементує `rewrittenFiles`.
100
-
101
- ### `checkVueAvifImports(ignorePaths, usedAvifAbs, stats, pass, fail, cwd)`
102
-
103
- - **Сигнатура:** `async (ignorePaths: string[], usedAvifAbs: Set<string>, stats: RewriteStats, pass: (msg: string) => void, fail: (msg: string) => void, cwd: string) => Promise<string[]>`
104
- - **Параметри:**
105
- - `ignorePaths` — абсолютні шляхи каталогів, повністю виключених з обходу.
106
- - `usedAvifAbs` — мутабельна множина абсолютних шляхів `.avif`, що мають живі посилання (заповнюється у викликаних функціях).
107
- - `stats` — глобальні лічильники `RewriteStats`, мутуються нижче.
108
- - `pass` — callback при успішній перевірці пакета.
109
- - `fail` — callback при помилці.
110
- - `cwd` — корінь репозиторію.
111
- - **Повертає:** `Promise<string[]>` — абсолютні шляхи коренів пакетів з активним opt-out (`disable-avif: true`).
112
- - **Side effects:**
113
- - Читає кожен `package.json` workspace-пакета.
114
- - Для пакетів з opt-out — викликає `pass` з повідомленням про вимикач і додає корінь до `optedOutAbs`.
115
- - Для решти — викликає `checkVueAvifImportsInPackage`, який може писати у файли.
116
- - **Призначення `optedOutAbs`:** AVIF всередині opt-out пакета НЕ можна вважати сиротою лише на підставі відсутності посилань у його `.vue`/`.html` (ми взагалі не сканували його шаблони) — інакше cleanup помилково затирав би AVIF, що використовуються через alias / runtime-обчислений шлях / зовнішні посилання.
117
-
118
- ### `hasAnyVueRasterReference(ignorePaths, cwd)`
119
-
120
- - **Сигнатура:** `async (ignorePaths: string[], cwd: string) => Promise<boolean>`
121
- - **Параметри:**
122
- - `ignorePaths` — абсолютні шляхи каталогів, виключених з обходу.
123
- - `cwd` — корінь репозиторію.
124
- - **Повертає:** `true`, якщо у `.vue`/`.html` пакетів без opt-out знайдено принаймні одне raster-посилання (`VUE_RASTER_IMPORT_RE` або `VUE_RASTER_STATIC_SRC_RE`); `false` — інакше.
125
- - **Side effects:** немає (тільки читання файлів).
126
- - **Призначення:** дешевий pre-scan, що дозволяє пропустити дорогий `npx @nitra/minify-image --avif` і rewrite/cleanup у проєктах, де AVIF не вживається.
127
- - **Нюанс:** перед кожним `test`/`replaceAll` функція скидає `lastIndex` регекспа на `0`, оскільки регекспи з прапором `g` тримають стан між викликами.
128
-
129
- ### `runAvifGeneration(cwd)`
130
-
131
- - **Сигнатура:** `(cwd: string) => void`
132
- - **Параметри:**
133
- - `cwd` — корінь репозиторію, у якому запускається `npx`.
134
- - **Повертає:** `void`.
135
- - **Side effects:**
136
- - Викликає `spawnSync(npxPath, ['@nitra/minify-image', '--src=.', '--write', '--avif'], { stdio: 'inherit', cwd, env })`, який генерує AVIF-двійники.
137
- - Логує попередження (`console.log`) при відсутності `npx` у PATH, помилці спавна або ненульовому коді виходу — без падіння перевірки.
138
- - **Best-effort семантика:** якщо мережа/кеш недоступні чи бінарника нема — лог-варн без винятку; перевірка vue/html все одно виявить файли, для яких не вистачає `.avif`.
139
- - **Опт-аут запуску:** якщо `process.env.NITRA_CURSOR_NO_AVIF_RUN === '1'` — функція no-op (потрібно для тестів та ізольованих середовищ).
140
- - **Resolver `npx`:** `resolveCmd('npx')` повертає повний шлях; якщо `null` — функція друкує попередження і виходить.
141
-
142
- ### `cleanupOrphanAvifs(usedAvifAbs, optedOutAbs, ignorePaths, cwd)`
143
-
144
- - **Сигнатура:** `async (usedAvifAbs: Set<string>, optedOutAbs: string[], ignorePaths: string[], cwd: string) => Promise<number>`
145
- - **Параметри:**
146
- - `usedAvifAbs` — абсолютні шляхи `.avif`, що мають живі посилання (їх не чіпаємо).
147
- - `optedOutAbs` — абсолютні шляхи коренів opt-out пакетів; AVIF під ними не вважаємо сиротами.
148
- - `ignorePaths` — абсолютні шляхи каталогів, виключених з обходу.
149
- - `cwd` — корінь репозиторію.
150
- - **Повертає:** `Promise<number>` — кількість видалених сиріт.
151
- - **Side effects:** видаляє `.avif` файли через `unlink`.
152
- - **Фільтр кандидатів:**
153
- - файл закінчується на `.avif`;
154
- - не присутній у `usedAvifAbs`;
155
- - не лежить під жодним з `optedOutAbs`;
156
- - жоден сегмент шляху не входить у `CLEANUP_EXTRA_IGNORE_DIR_NAMES` (`build`, `android`, `ios`, `.output`, `.nuxt`, `.cache`).
157
- - **Ідемпотентність:** opt-out гарантує, що повторний `check image-avif` не починає циклічно видаляти AVIF в пакетах, що вимкнули правило (наприклад, мобільний бандл).
158
-
159
- ### `check(cwd = process.cwd())` — експортована точка входу
160
-
161
- - **Сигнатура:** `async (cwd?: string) => Promise<number>`
162
- - **Параметри:**
163
- - `cwd` — корінь репозиторію; за замовчуванням `process.cwd()`.
164
- - **Повертає:** `Promise<number>` — exit-код (`0` — OK, `1` — є проблеми), отриманий з `reporter.getExitCode()`.
165
- - **Side effects:**
166
- - Створює `createCheckReporter()` для агрегації pass/fail-меседжів.
167
- - Завантажує `ignorePaths` через `loadCursorIgnorePaths(cwd)`.
168
- - Якщо `hasAnyVueRasterReference` повернула `false` — викликає `pass(...)` з відповідним повідомленням і одразу повертає exit-код (без AVIF-генерації, rewrite, cleanup).
169
- - Інакше: викликає `runAvifGeneration(cwd)`, потім `checkVueAvifImports(...)` (rewrite + збір usedAvifAbs + список optedOutAbs), потім `cleanupOrphanAvifs(...)`, фіксує підсумкове `pass(...)` з кількістю переписаних посилань, файлів, видалених сиріт і фейлів. Фейли всередині rewrite-пасу йдуть через `fail(...)` і впливають на exit-код.
170
-
171
- ## Залежності
172
-
173
- ### Node.js builtins
174
-
175
- - `node:fs` — `existsSync` (синхронна перевірка наявності файла, бо викликається у тісному `replaceAll`-циклі).
176
- - `node:fs/promises` — `readFile`, `writeFile`, `unlink`.
177
- - `node:path` — `join`, `relative`.
178
- - `node:child_process` — `spawnSync` для запуску `npx @nitra/minify-image`.
179
- - `node:process` — `env` (для опт-ауту `NITRA_CURSOR_NO_AVIF_RUN=1`); також глобально використовується `process.cwd()` у `resolveImageCandidates` і дефолтному параметрі `check`.
180
-
181
- ### Внутрішні модулі (n-cursor)
182
-
183
- - `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика обʼєкта зі `pass`/`fail`/`getExitCode`.
184
- - `../../../scripts/lib/load-cursor-config.mjs` → `loadCursorIgnorePaths` — завантажує абсолютні шляхи каталогів, які треба ігнорувати при обході.
185
- - `../../../scripts/utils/resolve-cmd.mjs` → `resolveCmd` — резолвить абсолютний шлях до CLI-бінарника у `PATH`.
186
- - `../../../scripts/utils/walkDir.mjs` → `walkDir` — рекурсивний обхід директорії з вбудованим скіпом `node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next` та користувацьким callback.
187
- - `../../../scripts/lib/workspaces.mjs` → `getMonorepoPackageRootDirs` — повертає відносні шляхи коренів workspace-пакетів монорепо.
188
-
189
- ### Зовнішні CLI
190
-
191
- - `npx` — резолвиться через `resolveCmd`; запускає `@nitra/minify-image` через `npx`.
192
- - `@nitra/minify-image` — окремий npm-пакет, який і генерує AVIF-двійники (`--src=. --write --avif`).
193
-
194
- ## Потік виконання / Використання
195
-
196
- ### Виклик з CLI
197
-
198
- Модуль викликається з реєстру правил `n-cursor` як check-функція правила `image-avif`. Очікувана крапка входу:
199
-
200
- ```js
201
- import { check } from './npm/rules/image-avif/js/avif_generation.mjs'
202
-
203
- const exitCode = await check(process.cwd())
204
- process.exit(exitCode)
205
- ```
206
-
207
- ### Послідовність кроків у `check(cwd)`
208
-
209
- 1. Створюється reporter (`createCheckReporter`).
210
- 2. Завантажується `ignorePaths` через `loadCursorIgnorePaths(cwd)`.
211
- 3. **Pre-scan**: `hasAnyVueRasterReference(ignorePaths, cwd)` обходить пакети без opt-out, шукає raster-посилання. Якщо нема — `pass(...)` і ранній вихід.
212
- 4. **AVIF-генерація**: `runAvifGeneration(cwd)` — викликає `npx @nitra/minify-image --src=. --write --avif`, який створює AVIF-двійники. Опціонально вимикається через `NITRA_CURSOR_NO_AVIF_RUN=1`.
213
- 5. **Rewrite-пасс**: `checkVueAvifImports(...)` обходить кожен workspace-пакет:
214
- - Якщо `package.json` має `disable-avif: true` — `pass(...)` і додає корінь у `optedOutAbs`.
215
- - Інакше — `checkVueAvifImportsInPackage(...)` обробляє кожен `.vue`/`.html` у пакеті: переписує raster-посилання на `.avif` (якщо двійник існує), фейлить ті, для яких двійника нема, збирає `usedAvifAbs`.
216
- 6. **Cleanup-пасс**: `cleanupOrphanAvifs(usedAvifAbs, optedOutAbs, ignorePaths, cwd)` видаляє `.avif`, на які не лишилось живих посилань, з врахуванням opt-out і списку артефактів збірки.
217
- 7. Фінальний `pass(...)` з підсумком: скільки посилань переписано, у скількох файлах, скільки сиріт видалено, скільки фейлів rewrite.
218
- 8. Повертається `reporter.getExitCode()` — `1`, якщо був хоч один `fail`, інакше `0`.
219
-
220
- ### Опт-аут на рівні пакета
221
-
222
- Щоб вимкнути AVIF-перевірку у конкретному workspace-пакеті, у його `package.json` додається:
223
-
224
- ```json
225
- {
226
- "@nitra/minify-image": {
227
- "disable-avif": true
228
- }
229
- }
230
- ```
231
-
232
- Наслідки:
233
-
234
- - pre-scan ігнорує цей пакет (його raster-посилання не провокують запуск `npx --avif`);
235
- - rewrite-пасс не сканує і не змінює його `.vue`/`.html`;
236
- - cleanup-пасс не видаляє `.avif` під його коренем (бо ми не зібрали `usedAvifAbs` для нього).
237
-
238
- ### Опт-аут запуску `npx`
239
-
240
- Змінна середовища `NITRA_CURSOR_NO_AVIF_RUN=1` повністю вимикає виклик `npx @nitra/minify-image --avif`. Pre-scan, rewrite і cleanup при цьому працюють як зазвичай — потрібно для юніт-тестів і ізольованих CI-середовищ.
241
-
242
- ### Семантика помилок
243
-
244
- - **Бракує `.avif`-двійника** — fail на конкретний `.vue`/`.html`-файл і конкретний `importPath` з підказкою про `npx @nitra/cursor fix image-avif` та локальний opt-out.
245
- - **`npx` недоступний / падає** — лише warn у `console.log`, без переривання перевірки; fail прийде пізніше від rewrite-пасу, якщо `.avif` так і не з'явилися.
246
- - **Bare alias** (`'foo'` без `/`) — резолвера нема, посилання пропускається без fail.
33
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,20 @@
1
+ import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
2
+ import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
3
+
4
+ /**
5
+ * Єдиний entrypoint правила (ADR 2026-06-21). `run()` — check-поверхня: applies →
6
+ * JS-concerns → policy → mdc-refs (через runStandardRule). Lint-поверхні правило не має
7
+ * (`meta.json` без `lint`), тож експорту `lint` тут немає.
8
+ * Library mode: викликається CLI orchestration через `import + run(ctx)`.
9
+ * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
10
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
11
+ */
12
+ export function run(ctx) {
13
+ return runStandardRule(import.meta.dirname, ctx)
14
+ }
15
+
16
+ if (isRunAsCli(import.meta.url)) {
17
+ // Standalone: bun rules/<id>/main.mjs — повний еквівалент `npx @nitra/cursor check <id>`
18
+ // (config-loading + whitelist + summary): library-роль (run) + standalone-роль (CLI-блок).
19
+ process.exitCode = await runRuleCli(import.meta.dirname)
20
+ }
@@ -9,3 +9,4 @@ resource: npm/rules/image-compress/
9
9
  | Файл | Тип |
10
10
  |---|---|
11
11
  | [fix.mjs](fix.md) | JS Module |
12
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/image-compress/main.mjs
5
+ docgen:
6
+ crc: abe40746
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 90
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль забезпечує механізми для виконання та валідації контенту відповідно до визначених правил. Функція `run` виконує перевірку відповідності контенту визначеним критеріям у межах поточного контексту прогону. Функція `lint` виконує виявлення або стиснення зображень залежно від режиму `readOnly`.
14
+
15
+ ## Поведінка
16
+
17
+ run виконує стандартну перевірку, використовуючи контекст прогону.
18
+ lint виконує перевірку або стиснення зображень, залежно від опцій: якщо `readOnly` встановлено, виконується лише детект, інакше виконується стиснення.
19
+
20
+ ## Публічний API
21
+
22
+ run — головна точка входу для виконання правил, де відбувається перевірка на відповідність вимогам (JS-занепокоєння $\rightarrow$ політика $\rightarrow$ посилання MDC).
23
+ lint — перевірка зображень за допомогою `@nitra/minify-image`, що може працювати у режимі лише читання або з можливістю автоматичного виправлення.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Read-only: не виконує операцій запису (ФС/БД).
28
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
29
+ - Кешує результати в межах одного прогону.