@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
@@ -0,0 +1,322 @@
1
+ # `lint.mjs` — лінт-крок `lint-python`
2
+
3
+ ## Огляд
4
+
5
+ Модуль `npm/rules/python/lint/lint.mjs` реалізує крок `lint-python` — частину
6
+ загального лінт-пайплайну монорепозиторію. Крок виконує перевірку Python-частини
7
+ проєкту відповідно до правила `python.mdc` і базується на пакетному менеджері
8
+ [uv](https://docs.astral.sh/uv/).
9
+
10
+ Поведінкові ключові точки:
11
+
12
+ - Якщо у корені, переданому як `cwd` (за замовчуванням `process.cwd()`), немає
13
+ файлу `pyproject.toml`, крок завершується успіхом (`exit code 0`) без запуску
14
+ будь-яких інструментів. Це дозволяє безпечно вмикати крок у репозиторіях
15
+ без Python-частини.
16
+ - Якщо `pyproject.toml` присутній, але бінарника `uv` немає в `PATH`, крок
17
+ завершується помилкою. Інших пакет-менеджерів (Poetry, pip, pdm тощо) модуль
18
+ не підтримує — `uv` є єдиним каноном.
19
+ - Обовʼязкові кроки `uv lock --check` і `uv sync --frozen` запускаються завжди,
20
+ якщо `uv` доступний.
21
+ - Опційні лінтери (`ruff check --fix`, `ruff format`, `mypy`) запускаються
22
+ лише якщо вони доступні через `uv run --frozen <tool> --version`. Якщо
23
+ відповідного інструмента у uv-середовищі немає — крок пропускається з
24
+ pass-повідомленням (аналогічно «optional vendor-tools» у `php.mdc`).
25
+ - `ruff` працює в auto-fix-режимі (`--fix`, потім `format`), тобто може
26
+ мутувати робоче дерево, подібно до `markdownlint-cli2 --fix` у `lint-text`
27
+ чи `clippy --fix` у `lint-rust`.
28
+ - Серіалізація запусків CLI організована через `runStandardLint` (а не через
29
+ безпосередній `withLock`) — це відповідає канону патерну `lint-*`, описаному
30
+ в `.cursor/rules/scripts.mdc` (секція «Серіалізація важких CLI-команд»).
31
+
32
+ Файл одночасно є:
33
+
34
+ 1. Бібліотекою (експортує функції `runLintPythonSteps` та `runLintPython`).
35
+ 2. CLI-точкою входу — при запуску напряму (`isRunAsCli`) виконує
36
+ `runLintPython()` і виставляє `process.exitCode`.
37
+
38
+ ## Експорти / API
39
+
40
+ | Експорт | Тип | Призначення |
41
+ | -------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `runLintPythonSteps` | `function` | Виконує внутрішні кроки `lint-python` (без зовнішнього локу). Призначений для повторного використання з обгортки `runStandardLint` та для тестування. |
43
+ | `runLintPython` | `() => Promise<number>` | Публічна CLI-форма: запускає `runLintPythonSteps` через `runStandardLint`, який бере глобальний лок `lint-python` і дедупає прогони за станом git-дерева. |
44
+
45
+ Модуль не має default-export. Усі експорти — іменовані ES Module exports.
46
+
47
+ Side effect модуля верхнього рівня: якщо файл запущений як CLI
48
+ (`isRunAsCli(import.meta.url)` повертає `true`), на верхньому рівні
49
+ виконується `await runLintPython()` і результат записується у
50
+ `process.exitCode`.
51
+
52
+ ## Функції
53
+
54
+ ### `runTool(label, cmd, args, pass, fail)`
55
+
56
+ Внутрішня (не експортується) функція-обгортка над `child_process.spawnSync`,
57
+ яка запускає вказаний CLI-крок і репортить результат через колбеки репортера.
58
+
59
+ - Сигнатура: `runTool(label: string, cmd: string, args: string[], pass: (msg: string) => void, fail: (msg: string) => void): boolean`
60
+ - Параметри:
61
+ - `label` — людиночитана назва кроку, використовується у повідомленнях
62
+ (`lint-python: <label> — OK` / `lint-python: <label> — помилка ...`).
63
+ - `cmd` — абсолютний шлях до виконуваного файлу (наприклад, отриманий через
64
+ `resolveCmd('uv')`).
65
+ - `args` — масив аргументів CLI.
66
+ - `pass` — callback репортера для успіху.
67
+ - `fail` — callback репортера для невдачі.
68
+ - Повертає: `true`, якщо процес завершився з `status === 0`, інакше `false`.
69
+ - Спосіб запуску: `spawnSync(cmd, args, { stdio: 'inherit', shell: false })`.
70
+ Це означає, що stdout/stderr CLI-кроку успадковуються від батьківського
71
+ процесу (видно користувачу), а інтерпретація аргументів shell-ом
72
+ вимкнена — аргументи передаються «as is».
73
+ - Обробка статусу: якщо `r.status` не число (наприклад, процес був убитий
74
+ сигналом), у повідомлення про помилку підставляється `1`.
75
+ - Side effects: запуск зовнішнього процесу; запис у stdout/stderr батька;
76
+ виклик `pass` або `fail` репортера.
77
+
78
+ ### `uvToolAvailable(uv, tool)`
79
+
80
+ Внутрішня (не експортується) перевірка наявності лінтера всередині
81
+ uv-середовища.
82
+
83
+ - Сигнатура: `uvToolAvailable(uv: string, tool: string): boolean`
84
+ - Параметри:
85
+ - `uv` — абсолютний шлях до бінарника `uv`.
86
+ - `tool` — назва бінарника, що перевіряється (`ruff`, `mypy`, тощо).
87
+ - Повертає: `true`, якщо `uv run --frozen <tool> --version` завершився з
88
+ кодом `0`, інакше `false`.
89
+ - Спосіб запуску: `spawnSync(uv, ['run', '--frozen', tool, '--version'],
90
+ { stdio: 'ignore', shell: false })`. `stdio: 'ignore'` гасить весь вивід
91
+ пробної команди, щоб не засмічувати лог.
92
+ - Side effects: запуск дочірнього процесу `uv run --frozen <tool> --version`.
93
+ Опція `--frozen` гарантує, що `uv` не намагатиметься оновлювати lock-файл
94
+ під час перевірки.
95
+
96
+ ### `runLintPythonSteps(cwd?)`
97
+
98
+ Експортована функція. Виконує всю послідовність кроків `lint-python` без
99
+ зовнішнього серіалізаційного локу.
100
+
101
+ - Сигнатура: `runLintPythonSteps(cwd?: string): number`
102
+ - Параметри:
103
+ - `cwd` — корінь репозиторію. За замовчуванням `process.cwd()`.
104
+ - Повертає: код виходу — `0`, якщо всі обовʼязкові кроки пройшли успішно,
105
+ `1` — якщо хоча б один крок зафейлив. Кінцевий код повертається через
106
+ `reporter.getExitCode()` (інстансу `createCheckReporter`).
107
+ - Алгоритм:
108
+ 1. Створює репортер: `const reporter = createCheckReporter()`,
109
+ дістає колбеки `{ pass, fail }`.
110
+ 2. Перевіряє `existsSync(join(cwd, 'pyproject.toml'))`. Якщо файла
111
+ немає — викликає `pass(...)` з повідомленням «кроки Python пропущено»
112
+ і повертає `reporter.getExitCode()`.
113
+ 3. `const uv = resolveCmd('uv')` — резолвить абсолютний шлях до `uv`.
114
+ Якщо `uv` не знайдено — `fail(...)` і повернення коду.
115
+ 4. Виконує `runTool('uv lock --check', uv, ['lock', '--check'], pass, fail)`.
116
+ За невдачі — повертає поточний код (далі не йде).
117
+ 5. Виконує `runTool('uv sync --frozen', uv, ['sync', '--frozen'], pass,
118
+ fail)`. За невдачі — повертає поточний код.
119
+ 6. Створює локальний хелпер `runOptionalUvTool(tool, label, args)`
120
+ (див. нижче) і послідовно запускає:
121
+ - `runOptionalUvTool('ruff', 'ruff check --fix', ['check', '--fix', '.'])`
122
+ - `runOptionalUvTool('ruff', 'ruff format', ['format', '.'])`
123
+ - `runOptionalUvTool('mypy', 'mypy', ['.'])`
124
+ За першої ж справжньої невдачі (повертає `false`) — повернення поточного
125
+ коду виходу.
126
+ 7. Повертає `reporter.getExitCode()`.
127
+ - Side effects:
128
+ - Запуск зовнішніх процесів (`uv lock`, `uv sync`, `uv run ruff`,
129
+ `uv run mypy`).
130
+ - `ruff check --fix` та `ruff format` можуть **модифікувати файли
131
+ проєкту** (auto-fix Python-коду).
132
+ - `uv sync --frozen` може створювати або оновлювати `.venv` (з повним
133
+ дотриманням `uv.lock`).
134
+ - Запис у stdout/stderr через `stdio: 'inherit'`.
135
+
136
+ ### `runOptionalUvTool(tool, label, args)` (вкладена у `runLintPythonSteps`)
137
+
138
+ Внутрішній замикач, доступний лише всередині `runLintPythonSteps`. Захоплює
139
+ `uv`, `pass`, `fail` із зовнішньої області видимості.
140
+
141
+ - Сигнатура: `runOptionalUvTool(tool: string, label: string, args: string[]): boolean`
142
+ - Параметри:
143
+ - `tool` — імʼя інструмента (`ruff`, `mypy`).
144
+ - `label` — назва кроку для повідомлень.
145
+ - `args` — аргументи, які слід передати інструменту після `uv run --frozen <tool>`.
146
+ - Повертає: `true`, якщо крок успішно завершився **або** інструмент
147
+ відсутній у uv-середовищі (тоді крок пропускається з pass-повідомленням).
148
+ `false` повертається тільки коли інструмент доступний і завершився з
149
+ ненульовим статусом.
150
+ - Логіка:
151
+ 1. `if (!uvToolAvailable(uv, tool))` → `pass(...)` з повідомленням «крок
152
+ пропущено» і повертає `true` (це коректне продовження пайплайну,
153
+ інструмент трактується як optional).
154
+ 2. Інакше викликає `runTool(label, uv, ['run', '--frozen', tool, ...args],
155
+ pass, fail)`.
156
+ - Side effects: ті ж, що й у `runTool` / `uvToolAvailable` (запуск дочірніх
157
+ процесів, оновлення репортера).
158
+
159
+ ### `runLintPython`
160
+
161
+ Публічна обгортка-стрілкова функція.
162
+
163
+ - Сигнатура: `runLintPython(): Promise<number>`
164
+ - Параметри: немає.
165
+ - Повертає: `Promise<number>` — код виходу, отриманий з `runStandardLint`.
166
+ - Реалізація: `runStandardLint(import.meta.dirname, runLintPythonSteps)`.
167
+ Сенс параметрів:
168
+ - `import.meta.dirname` — директорія самого модуля; використовується
169
+ `runStandardLint` як ідентифікатор для дедуплікації / стану git-дерева.
170
+ - `runLintPythonSteps` — функція кроків, яку `runStandardLint` викличе
171
+ всередині глобального локу `lint-python`.
172
+ - Серіалізація: `runStandardLint` бере глобальний лок `lint-python` (як
173
+ описано в `scripts.mdc`) та дедупає прогони за станом git-дерева, тому
174
+ паралельні виклики `runLintPython()` не перетинатимуться по запуску
175
+ `uv`.
176
+ - Side effects: ті самі, що й у `runLintPythonSteps`, плюс блокування на
177
+ файловому локу.
178
+
179
+ ## CLI-вхід (верхній рівень модуля)
180
+
181
+ ```js
182
+ if (isRunAsCli(import.meta.url)) {
183
+ process.exitCode = await runLintPython()
184
+ }
185
+ ```
186
+
187
+ - Перевірка `isRunAsCli(import.meta.url)` встановлює, чи запущений файл
188
+ безпосередньо як CLI-точка входу (наприклад, `node lint.mjs` або через
189
+ `n-cursor`), а не імпортований як модуль.
190
+ - Якщо так — виконується top-level `await runLintPython()`, а результат
191
+ кладеться у `process.exitCode`. Це означає, що Node завершиться з цим
192
+ кодом після того, як event loop спорожніє.
193
+ - Якщо файл імпортовано як модуль, цей блок не виконується — викликач сам
194
+ вирішує, як використати експортовані функції.
195
+
196
+ ## Залежності
197
+
198
+ ### Стандартна бібліотека Node.js
199
+
200
+ - `node:child_process` → `spawnSync` — синхронний запуск зовнішніх процесів
201
+ (`uv`, `uv run …`).
202
+ - `node:fs` → `existsSync` — перевірка наявності `pyproject.toml`.
203
+ - `node:path` → `join` — побудова повного шляху до `pyproject.toml` від `cwd`.
204
+
205
+ ### Внутрішні модулі репозиторію
206
+
207
+ - `../../../scripts/cli-entry.mjs` → `isRunAsCli` — детекція CLI-режиму
208
+ через `import.meta.url`.
209
+ - `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` —
210
+ фабрика репортера з методами `pass`, `fail`, `getExitCode`. Цей патерн
211
+ єдиний для всіх лінт-кроків.
212
+ - `../../../scripts/utils/resolve-cmd.mjs` → `resolveCmd` — пошук
213
+ виконуваного файлу в `PATH` (повертає абсолютний шлях або `null`).
214
+ - `../../../scripts/lib/run-standard-lint.mjs` → `runStandardLint` —
215
+ стандартизована обгортка над лінт-кроком (глобальний лок + дедуплікація
216
+ за станом git-дерева).
217
+
218
+ ### Зовнішні бінарники (runtime-залежності)
219
+
220
+ - `uv` — обовʼязковий у `PATH`, якщо в репозиторії є `pyproject.toml`.
221
+ - `ruff` — опційний, перевіряється через `uv run --frozen ruff --version`.
222
+ - `mypy` — опційний, перевіряється через `uv run --frozen mypy --version`.
223
+
224
+ ### Артефакти у проєкті
225
+
226
+ - `pyproject.toml` (у корені `cwd`) — тригер запуску Python-частини.
227
+ - `uv.lock` — використовується `uv lock --check` та `uv sync --frozen`,
228
+ має бути актуальним.
229
+
230
+ ## Потік виконання / Використання
231
+
232
+ ### Сценарій 1: Python-частини немає
233
+
234
+ 1. `runLintPython()` → `runStandardLint(...)` → `runLintPythonSteps()`.
235
+ 2. `existsSync('<cwd>/pyproject.toml')` повертає `false`.
236
+ 3. Репортер фіксує pass-повідомлення «немає pyproject.toml у корені — кроки
237
+ Python пропущено».
238
+ 4. Повертається `0`.
239
+
240
+ ### Сценарій 2: Python є, але `uv` не встановлений
241
+
242
+ 1. `existsSync('pyproject.toml')` → `true`.
243
+ 2. `resolveCmd('uv')` → `null`.
244
+ 3. `fail('lint-python: `uv` не знайдено в PATH ...')`.
245
+ 4. Повертається `1`.
246
+
247
+ ### Сценарій 3: Повний прогон з усіма лінтерами
248
+
249
+ 1. `uv lock --check` — перевірка lock-файлу. За невдачі вихід `1`.
250
+ 2. `uv sync --frozen` — інсталяція середовища строго за `uv.lock`. За
251
+ невдачі вихід `1`.
252
+ 3. `uvToolAvailable(uv, 'ruff')` → `true` → `uv run --frozen ruff check
253
+ --fix .`. Може **змінити файли**.
254
+ 4. `uv run --frozen ruff format .`. Також може **змінити файли**.
255
+ 5. `uvToolAvailable(uv, 'mypy')` → `true` → `uv run --frozen mypy .`.
256
+ Лише читає, не змінює дерево.
257
+ 6. Якщо всі кроки повернули `0` — підсумок `0`. Інакше — перший
258
+ ненульовий код розриває послідовність і повертається.
259
+
260
+ ### Сценарій 4: `ruff` або `mypy` не встановлені у uv-середовищі
261
+
262
+ - Для відповідного інструмента `uvToolAvailable` поверне `false`.
263
+ - Виводиться pass-повідомлення «<tool> недоступний у uv-середовищі —
264
+ крок пропущено».
265
+ - Інші кроки виконуються штатно.
266
+
267
+ ### Як викликати з коду
268
+
269
+ ```js
270
+ import { runLintPython, runLintPythonSteps } from './lint.mjs'
271
+
272
+ // Стандартний шлях: з локом, дедуплікацією, асинхронно.
273
+ const code = await runLintPython()
274
+ process.exit(code)
275
+
276
+ // Прямий виклик без локу (наприклад, у тестах або з власною серіалізацією):
277
+ const codeRaw = runLintPythonSteps('/path/to/repo')
278
+ ```
279
+
280
+ ### Як викликати з CLI
281
+
282
+ Файл є виконуваною точкою входу для лінт-пайплайну. У звичайному монорепо
283
+ він викликається через спільний раннер (`n-cursor`, `bun run lint` тощо).
284
+ Прямий запуск:
285
+
286
+ ```bash
287
+ node npm/rules/python/lint/lint.mjs
288
+ ```
289
+
290
+ Кодом виходу буде число з `runLintPython()` (`0` — OK, `1` — є помилки).
291
+
292
+ ## Rebuild Test
293
+
294
+ За цією документацією можна відтворити модуль так:
295
+
296
+ 1. Створити ES Module-файл, що імпортує `spawnSync` з `node:child_process`,
297
+ `existsSync` з `node:fs`, `join` з `node:path`, а також `isRunAsCli`,
298
+ `createCheckReporter`, `resolveCmd`, `runStandardLint` з відповідних
299
+ шляхів `../../../scripts/...`.
300
+ 2. Реалізувати приватну `runTool(label, cmd, args, pass, fail)`:
301
+ `spawnSync` з `stdio: 'inherit'`, `shell: false`; при `status === 0`
302
+ викликати `pass`, інакше `fail` з кодом (типу number або `1` при
303
+ неприродному завершенні); повертати `boolean`.
304
+ 3. Реалізувати `uvToolAvailable(uv, tool)`: `spawnSync(uv, ['run',
305
+ '--frozen', tool, '--version'], { stdio: 'ignore', shell: false })` →
306
+ `r.status === 0`.
307
+ 4. Експортувати `runLintPythonSteps(cwd = process.cwd())`:
308
+ - створити репортер;
309
+ - якщо `pyproject.toml` відсутній → `pass(...)` і повернути код;
310
+ - резолвити `uv`; якщо немає → `fail(...)` і повернути;
311
+ - послідовно: `uv lock --check`, `uv sync --frozen` (обовʼязкові);
312
+ - опційні через локальну функцію-замикач `runOptionalUvTool`: `ruff
313
+ check --fix .`, `ruff format .`, `mypy .` — кожен через
314
+ `uvToolAvailable` + `runTool`;
315
+ - повернути `reporter.getExitCode()`.
316
+ 5. Експортувати `runLintPython = () => runStandardLint(import.meta.dirname,
317
+ runLintPythonSteps)`.
318
+ 6. На верхньому рівні: `if (isRunAsCli(import.meta.url)) process.exitCode =
319
+ await runLintPython()`.
320
+
321
+ Результат повинен поведінково збігтися з оригіналом: ті самі повідомлення,
322
+ ті самі коди виходу, така ж серіалізація та обробка опційних інструментів.
@@ -0,0 +1,121 @@
1
+ # fix.mjs — точка входу правила `rego`
2
+
3
+ ## Огляд
4
+
5
+ Файл `npm/rules/rego/fix.mjs` — це **entry-point правила `rego`** з кодової бази `@nitra/cursor`. Він є тонкою (thin) обгорткою навколо стандартного пайплайну виконання правила і виконує **дві ролі одночасно**:
6
+
7
+ 1. **Library mode** — експортує функцію `run(ctx)`, яку CLI-оркестратор (`@nitra/cursor fix <id>`) викликає через динамічний `import`, передаючи спільний контекст прогону (наприклад, `walkCache`).
8
+ 2. **Standalone mode** — якщо файл запущено напряму як CLI (наприклад, `bun npm/rules/rego/fix.mjs`), він виконує **повний еквівалент** команди `npx @nitra/cursor fix rego`: завантажує конфіг, застосовує whitelist, друкує summary та повертає процесові exit-code.
9
+
10
+ Внутрішня логіка правила (що саме перевіряється/виправляється у файлах `.rego`, OPA-політиках тощо) **не реалізована в цьому файлі** — вся послідовність кроків (`applies → JS-concerns → policy → mdc-refs`) інкапсульована у спільному хелпері `runStandardRule`. Сам `fix.mjs` лише делегує виконання, передаючи `import.meta.dirname` як точку відліку для конвенційного пошуку файлів-супутників правила (наприклад, `applies.mjs`, `check-*.mjs`, `policy/`, `mdc/`).
11
+
12
+ Така структура повторюється для кожного правила в `npm/rules/<id>/fix.mjs` — це шаблонний стандартний boilerplate, у якому індивідуальною є лише тека `<id>` (тут — `rego`).
13
+
14
+ ## Експорти / API
15
+
16
+ | Назва | Тип | Опис |
17
+ | ----- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
18
+ | `run` | `function(ctx?): Promise<number>` | Named-експорт. Library-точка входу для оркестратора. Запускає стандартний пайплайн правила `rego` у директорії цього файлу. |
19
+
20
+ Файл **не має default-експорту** і **не експортує** жодних інших символів. Side-effect на верхньому рівні модуля — перевірка `isRunAsCli(import.meta.url)`; якщо вона істинна, виконується `process.exit(await runRuleCli(...))`.
21
+
22
+ ### Сигнатура `run`
23
+
24
+ ```js
25
+ export function run(ctx)
26
+ ```
27
+
28
+ - **Параметр `ctx`** — необов'язковий об'єкт типу `RuleContext` (тип імпортується через JSDoc із `../../scripts/lib/run-standard-rule.mjs`). У контексті, зокрема, передається `walkCache` — кеш обходу файлів, який дозволяє багаторазовим прогонам різних правил не дублювати дискові операції під час однієї сесії `@nitra/cursor fix`.
29
+ - **Повертає** — `Promise<number>`, де `0` означає **OK** (порушень не знайдено / усе виправлено), а `1` означає **наявність порушень** (failure). Це класична CLI-конвенція exit-code, узгоджена з типами в `runStandardRule`.
30
+ - **Прив'язка до директорії правила** — `import.meta.dirname` (нативне Node.js property для ESM, доступне у сучасних рантаймах) дає абсолютний шлях до теки, де лежить сам `fix.mjs`. Цей шлях передається у `runStandardRule` як «корінь правила», аби той знаходив супровідні файли (`applies.mjs`, `check-*.mjs`, `policy/*.rego`, `mdc/*` тощо) за конвенцією.
31
+
32
+ ## Функції
33
+
34
+ ### `run(ctx)`
35
+
36
+ - **Сигнатура:** `function run(ctx?: RuleContext): Promise<number>`
37
+ - **Параметри:**
38
+ - `ctx` (опційний) — контекст прогону правила. Передається оркестратором (`runRuleCli` / `runMany`), коли правило запускається не самостійно, а в складі батча; містить, серед іншого, спільний `walkCache`. У standalone-режимі (див. нижче) `ctx` не передається — використовується дефолт, який сконструює `runStandardRule` самостійно.
39
+ - **Повертає:** `Promise<number>` — exit-code:
40
+ - `0` — правило пройшло (`OK`).
41
+ - `1` — правило виявило порушення (`violation`).
42
+ - **Side effects:**
43
+ - Не має жодних побічних ефектів на рівні самої функції-обгортки.
44
+ - Усі побічні ефекти (читання файлів, запис виправлень, друк у `stdout`/`stderr`, доступ до `walkCache`, мережа/диск) делеговані у `runStandardRule` і виконуються вже в його реалізації.
45
+ - **Чому така тонка обгортка?** Оркестратор `@nitra/cursor fix` під час батчового прогону **динамічно імпортує** `fix.mjs` кожного правила й викликає `run(ctx)`. Якби логіка пайплайну дублювалася в кожному `fix.mjs`, її було б неможливо змінювати централізовано — `runStandardRule` забезпечує єдину точку зміни поведінки для всіх правил.
46
+
47
+ ### Standalone-блок (не функція, top-level код)
48
+
49
+ ```js
50
+ if (isRunAsCli(import.meta.url)) {
51
+ process.exit(await runRuleCli(import.meta.dirname))
52
+ }
53
+ ```
54
+
55
+ - **Умова входу:** `isRunAsCli(import.meta.url)` повертає `true`, якщо модуль запущений як головний (а не імпортований іншим). Реалізація хелпера — у `npm/scripts/lib/run-rule-cli.mjs`.
56
+ - **Що виконує:** `runRuleCli(import.meta.dirname)` запускає **повний CLI** для цього правила — еквівалент `npx @nitra/cursor fix rego`. На відміну від `run(ctx)`, цей шлях додатково завантажує конфіг репозиторію, застосовує whitelist (ігнорування файлів/паттернів за конфігом), друкує summary прогону тощо.
57
+ - **`process.exit(...)`** — потрібен, щоб standalone-запуск повертав **коректний exit-code** у виклик shell/CI (інакше `bun` міг би завершитися з `0` навіть за провалу).
58
+ - **ESLint-винятки:** `n/no-process-exit` і `unicorn/no-process-exit` локально відключені коментарем — це свідома відмова на користь правильного CI/IDE-поведінки (standalone entry-point **зобов'язаний** повертати exit-code).
59
+ - **`await` на top-level:** використовується top-level `await`, що в ESM-модулях Node 20+ є штатною можливістю.
60
+
61
+ ## Залежності
62
+
63
+ Файл імпортує лише **два внутрішніх модулі** (relative imports), зовнішніх npm-пакетів не використовує безпосередньо.
64
+
65
+ | Імпорт | Звідки | Призначення |
66
+ | ----------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Предикат: повертає `true`, якщо файл запущено напряму (а не імпортовано). Використовується у standalone-блоці. |
68
+ | `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Запуск **повного CLI** для одного правила (config-loading + whitelist + summary). Викликається в standalone-режимі. |
69
+ | `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Запуск **стандартного пайплайну** правила: `applies → JS-concerns → policy → mdc-refs`. Викликається з library-функції `run(ctx)`. |
70
+
71
+ Додатково через JSDoc підтягується **тип** `RuleContext` (для типізації `ctx`) із того ж `../../scripts/lib/run-standard-rule.mjs`.
72
+
73
+ Інші **неявні залежності** (через `runStandardRule` / `runRuleCli`):
74
+
75
+ - файлова система — для пошуку файлів за конвенцією правила;
76
+ - сам `import.meta.dirname` — як спосіб самоідентифікації правила без хардкоду шляху.
77
+
78
+ ## Потік виконання / Використання
79
+
80
+ ### Сценарій 1: Library mode (виклик з оркестратора)
81
+
82
+ 1. Користувач виконує `bun cursor fix` або `npx @nitra/cursor fix`.
83
+ 2. Оркестратор знаходить усі активні правила (зокрема `rego`) і динамічно імпортує `npm/rules/rego/fix.mjs`.
84
+ 3. Оркестратор викликає `run(ctx)`, де `ctx` містить, зокрема, `walkCache`.
85
+ 4. `run(ctx)` повертає `runStandardRule(import.meta.dirname, ctx)`.
86
+ 5. `runStandardRule` послідовно виконує стандартні етапи правила:
87
+ - **applies** — визначає, до яких файлів застосовується правило (через супровідний `applies.mjs`);
88
+ - **JS-concerns** — перевірки/виправлення, що стосуються JS-коду навколо правила;
89
+ - **policy** — застосування полісі/політик (для `rego` це конкретні OPA-rego-фрагменти);
90
+ - **mdc-refs** — звірка з посиланнями на правила в `.mdc`-документах.
91
+ 6. Результат — `Promise<number>` (`0` або `1`) — повертається оркестратору, який агрегує підсумок по всіх правилах.
92
+
93
+ ### Сценарій 2: Standalone mode (прямий запуск)
94
+
95
+ 1. Розробник виконує, наприклад, `bun npm/rules/rego/fix.mjs` (або еквівалентно `node npm/rules/rego/fix.mjs`).
96
+ 2. Виконується top-level код модуля; `isRunAsCli(import.meta.url)` повертає `true`.
97
+ 3. Викликається `await runRuleCli(import.meta.dirname)` — це **повний CLI-флоу** для одного правила (включно з config-loading, whitelist та summary).
98
+ 4. Результат — `number` (exit-code) — передається у `process.exit(...)`, який негайно завершує процес із цим кодом.
99
+ 5. Shell / IDE / CI отримує exit-code: `0` (OK) або `1` (violations) — і може відповідно сигналізувати про результат.
100
+
101
+ ### Як викликати `run` із зовнішнього коду (приклад)
102
+
103
+ ```js
104
+ import { run } from './npm/rules/rego/fix.mjs'
105
+
106
+ const code = await run() // або: await run({ walkCache: myCache })
107
+ if (code !== 0) {
108
+ // правило знайшло порушення
109
+ }
110
+ ```
111
+
112
+ ### Як викликати у standalone
113
+
114
+ ```sh
115
+ bun npm/rules/rego/fix.mjs
116
+ echo $? # 0 — OK, 1 — violations
117
+ ```
118
+
119
+ ### Подвійна роль файлу
120
+
121
+ Дві ролі — `library` (іменований експорт `run`) та `standalone` (top-level CLI з `process.exit`) — навмисно поєднані в одному файлі: це дозволяє оркестратору не дублювати code path для одного й того самого правила, а розробнику — швидко прогнати окреме правило без запуску всього батча. Конвенція повторюється для всіх правил у `npm/rules/<id>/fix.mjs`, тож структура файлу `rego/fix.mjs` ідентична іншим правилам у каталозі — змінюється лише імпліцитна тека-«корінь» (`import.meta.dirname`).
@@ -0,0 +1,174 @@
1
+ # applies.mjs — applies-гейт правила `rego`
2
+
3
+ ## Огляд
4
+
5
+ Модуль `applies.mjs` реалізує **rule-level applies-гейт** для правила `rego` (див. `rego.mdc`). Призначення модуля — відповісти на запитання: «Чи має CLI взагалі застосовувати правило `rego` до цього репозиторію?»
6
+
7
+ Логіка гейту проста: правило **застосовне лише тоді**, коли в дереві репозиторію (з урахуванням типових skip-каталогів і ігнор-патернів із `.n-cursor.json:ignore`) існує хоча б один `.rego`-файл. Якщо таких файлів немає — CLI пропускає правило **цілком**, включно з усіма його полісі-концернами (`package_json`, `vscode_extensions`, `vscode_settings`), оскільки вимоги rego-tooling стають неактуальними для проєкту без OPA/rego-коду.
8
+
9
+ Чому це не виражено декларативно через `target.json`-маніфести? Бо це **cross-file** гейт: вирішення базується на пошуку файлу по дереву (`walkDir`), а не на перевірці властивостей одного конкретного файлу. Декларативна модель `target.json` для цього не підходить, тому залишається імперативний JS.
10
+
11
+ Окрім самого гейта, файл також експортує невелику функцію `check()`, яка лише друкує контекстне pass-повідомлення (фактичні порушення вже повертають окремі policy-концерни).
12
+
13
+ ## Експорти / API
14
+
15
+ Модуль експортує два public-символи:
16
+
17
+ | Символ | Тип | Призначення |
18
+ | --------------- | ---------------- | ---------------------------------------------------------------------- |
19
+ | `applies(cwd?)` | `async function` | Rule-level applies-гейт: чи застосовне правило `rego` до репозиторію. |
20
+ | `check()` | `function` | Друкує короткий context-pass; повертає exit-code від `check-reporter`. |
21
+
22
+ Внутрішня (не експортована) допоміжна функція:
23
+
24
+ | Символ | Тип | Призначення |
25
+ | ---------------------------------------- | ---------------- | ------------------------------------------------------------------ |
26
+ | `projectHasRegoFiles(root, ignorePaths)` | `async function` | Чи є хоча б один `.rego` у дереві від `root` (зупинка на першому). |
27
+
28
+ ## Функції
29
+
30
+ ### `projectHasRegoFiles(root, ignorePaths)` (internal)
31
+
32
+ **Сигнатура**
33
+
34
+ ```js
35
+ async function projectHasRegoFiles(root: string, ignorePaths: string[]): Promise<boolean>
36
+ ```
37
+
38
+ **Параметри**
39
+
40
+ - `root` — абсолютний шлях до кореня репозиторію, від якого починати обхід.
41
+ - `ignorePaths` — масив шляхів каталогів, повністю виключених з обходу (зазвичай результат `loadCursorIgnorePaths(cwd)`).
42
+
43
+ **Повертає**
44
+
45
+ - `Promise<boolean>` — `true`, якщо в дереві знайдено принаймні один `.rego`-файл; інакше `false`.
46
+
47
+ **Side effects / нотатки**
48
+
49
+ - Виконує файлову систему-операцію через `walkDir`.
50
+ - **Не короткозамикається** ранньо у сенсі «зупинити walk» — `walkDir` тут не отримує сигнал на дострокове припинення, але всередині callback просто переписує локальну змінну `found = true` при першому матчі (подальші зустрічі залишають значення тим самим). Таким чином, фактично функція **проходить усе дерево**, проте семантично відповідає на питання «чи знайшовся хоч один».
51
+ - Жодних throw-ів не передбачено в самому коді функції — будь-які помилки I/O бульбашиться з `walkDir`.
52
+
53
+ ### `applies(cwd = process.cwd())` (exported)
54
+
55
+ **Сигнатура**
56
+
57
+ ```js
58
+ export async function applies(cwd?: string): Promise<boolean>
59
+ ```
60
+
61
+ **Параметри**
62
+
63
+ - `cwd` _(опц.)_ — корінь репозиторію. За замовчуванням — поточна робоча директорія процесу (`process.cwd()`).
64
+
65
+ **Повертає**
66
+
67
+ - `Promise<boolean>` — `true`, якщо правило `rego` застосовне (в репо знайдено принаймні один `.rego`); `false` — якщо правило слід пропустити.
68
+
69
+ **Side effects**
70
+
71
+ - Читає `.n-cursor.json` через `loadCursorIgnorePaths(cwd)`, щоб одержати каталоги, які слід ігнорувати.
72
+ - Виконує файловий обхід через `projectHasRegoFiles` (внутрішньо — `walkDir`).
73
+ - Не модифікує файлову систему, не пише в stdout/stderr.
74
+
75
+ ### `check()` (exported)
76
+
77
+ **Сигнатура**
78
+
79
+ ```js
80
+ export function check(): number
81
+ ```
82
+
83
+ **Параметри**
84
+
85
+ - Немає.
86
+
87
+ **Повертає**
88
+
89
+ - `number` — exit-code, який повертає `reporter.getExitCode()` (зазвичай `0`, оскільки тут викликається лише `reporter.pass(...)`).
90
+
91
+ **Side effects**
92
+
93
+ - Створює локальний `check-reporter` через `createCheckReporter()`.
94
+ - Друкує context-pass повідомлення: `Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc`.
95
+ - Фактичні порушення rego-правила цей `check()` **не повертає** — вони приходять від окремих policy-концернів, які CLI запускає декларативно через `policy/<name>/target.json`.
96
+
97
+ ## Залежності
98
+
99
+ Модуль явно імпортує три внутрішніх допоміжних модулі:
100
+
101
+ | Імпорт | Шлях | Роль |
102
+ | ----------------------- | --------------------------------------------- | ------------------------------------------------------------------------ |
103
+ | `createCheckReporter` | `../../../scripts/lib/check-reporter.mjs` | Фабрика репортера для check-функцій; дає `pass()`, `getExitCode()` тощо. |
104
+ | `loadCursorIgnorePaths` | `../../../scripts/lib/load-cursor-config.mjs` | Зчитує `.n-cursor.json` і повертає список ігнор-шляхів для обходу. |
105
+ | `walkDir` | `../../../scripts/utils/walkDir.mjs` | Рекурсивний обхід директорії з підтримкою skip-патернів і ignorePaths. |
106
+
107
+ Зовнішніх npm-залежностей у файлі немає; стандартних Node-API напряму не використовується (окрім `process.cwd()` як значення дефолту параметра).
108
+
109
+ ## Потік виконання / Використання
110
+
111
+ ### Контракт CLI
112
+
113
+ Файл `applies.mjs` — стандартна точка входу для CLI правила. CLI (npm/n-cursor) обходить правила (`npm/rules/*/`) і для кожного шукає `js/applies.mjs`. Якщо файл є — CLI імпортує `applies` і викликає її, передаючи `cwd` репозиторію. Залежно від результату:
114
+
115
+ - `applies(cwd)` повернуло `true` → CLI продовжує: запускає всі полісі-концерни правила (`policy/<name>/target.json`), а також (опціонально) викликає `check()` для друку контекстного pass-повідомлення.
116
+ - `applies(cwd)` повернуло `false` → CLI **повністю пропускає** правило `rego`: жодний полісі не запускається, `check()` не викликається.
117
+
118
+ ### Внутрішня послідовність викликів `applies(cwd)`
119
+
120
+ 1. **Отримати ignorePaths** — викликати `loadCursorIgnorePaths(cwd)`. Результат — масив шляхів каталогів з `.n-cursor.json:ignore`, які слід виключити з обходу.
121
+ 2. **Перевірити наявність `.rego`** — викликати `projectHasRegoFiles(cwd, ignorePaths)`:
122
+ - `walkDir` обходить дерево від `cwd`, оминаючи `ignorePaths` і типові skip-каталоги (`node_modules`, `.git`, тощо — згідно семантики `walkDir`).
123
+ - Кожен знайдений шлях `p` перевіряється на суфікс `.rego`; перший збіг встановлює `found = true`.
124
+ 3. **Повернути результат** — `Promise<boolean>`.
125
+
126
+ ### Внутрішня послідовність викликів `check()`
127
+
128
+ 1. Створити репортер: `const reporter = createCheckReporter()`.
129
+ 2. Зареєструвати pass-подію: `reporter.pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')`.
130
+ 3. Повернути exit-code: `return reporter.getExitCode()`.
131
+
132
+ ### Типовий сценарій для проєкту з OPA-полісі
133
+
134
+ Якщо у вашому репозиторії під CI/security лежать файли на kшталт `policies/foo.rego`, `infra/opa/*.rego` тощо:
135
+
136
+ - `applies(cwd)` поверне `true`.
137
+ - CLI прогонить полісі-концерни `rego`-правила: перевірить, що `package.json` містить очікувані скрипти/devDependencies, що `.vscode/extensions.json` рекомендує OPA-розширення, що `.vscode/settings.json` має канонічні налаштування — все це декларативно через `policy/<name>/target.json`.
138
+ - Додатково CLI може надрукувати pass-рядок із `check()`.
139
+
140
+ ### Типовий сценарій для проєкту без rego
141
+
142
+ Якщо `.rego` файлів у дереві немає:
143
+
144
+ - `applies(cwd)` поверне `false`.
145
+ - CLI пропустить правило `rego` цілком — користувач не побачить жодних повідомлень про відсутні rego-tooling-конфіги, бо вони неактуальні.
146
+
147
+ ### Приклад прямого виклику з тестів або сервісного коду
148
+
149
+ ```js
150
+ import { applies, check } from './applies.mjs'
151
+
152
+ const shouldRun = await applies('/abs/path/to/repo')
153
+ if (shouldRun) {
154
+ const code = check()
155
+ process.exit(code)
156
+ }
157
+ ```
158
+
159
+ ## Rebuild Test
160
+
161
+ Маючи лише цей документ, інженер має змогу відтворити файл наступним чином:
162
+
163
+ 1. Створити модуль `applies.mjs` у каталозі `npm/rules/rego/js/`.
164
+ 2. Імпортувати три залежності з відносними шляхами `../../../scripts/lib/check-reporter.mjs`, `../../../scripts/lib/load-cursor-config.mjs`, `../../../scripts/utils/walkDir.mjs`.
165
+ 3. Реалізувати **приватну** `async function projectHasRegoFiles(root, ignorePaths)`: завести локальну змінну `found = false`, викликати `await walkDir(root, callback, ignorePaths)`, де callback перевіряє `p.endsWith('.rego')` і виставляє `found = true`. Повернути `found`.
166
+ 4. Експортувати **`async function applies(cwd = process.cwd())`**: одержати `ignorePaths` через `await loadCursorIgnorePaths(cwd)`, повернути результат `projectHasRegoFiles(cwd, ignorePaths)`.
167
+ 5. Експортувати **`function check()`**: створити `reporter` через `createCheckReporter()`, викликати `reporter.pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')`, повернути `reporter.getExitCode()`.
168
+ 6. Додати JSDoc-коментарі: file-header пояснює призначення applies-гейта і чому JS, а не `target.json`; кожна з трьох функцій має JSDoc із параметрами/повертанням.
169
+
170
+ Очікуваний зовнішній контракт після rebuild:
171
+
172
+ - `applies('/repo/with/rego/files')` → `Promise<true>`.
173
+ - `applies('/repo/without/rego/files')` → `Promise<false>`.
174
+ - `check()` → синхронно повертає число (exit-code від check-reporter), друкує pass-повідомлення.