@nitra/cursor 3.22.0 → 3.23.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 (228) hide show
  1. package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
  2. package/CHANGELOG.md +31 -3
  3. package/bin/docs/n-cursor.md +636 -0
  4. package/bin/docs/rename-yaml-extensions.md +207 -0
  5. package/bin/n-cursor.js +30 -3
  6. package/package.json +1 -1
  7. package/rules/abie/docs/fix.md +18 -0
  8. package/rules/abie/js/docs/applies.md +26 -0
  9. package/rules/abie/js/docs/env_dns.md +32 -0
  10. package/rules/abie/js/docs/firebase_hosting.md +23 -0
  11. package/rules/abie/js/docs/hc_pairing.md +35 -0
  12. package/rules/abie/js/docs/ua_http_route.md +28 -0
  13. package/rules/abie/js/docs/ua_node_selector.md +28 -0
  14. package/rules/abie/lib/docs/enabled.md +29 -0
  15. package/rules/abie/lib/docs/env-dns.md +35 -0
  16. package/rules/abie/lib/docs/hc-yaml.md +33 -0
  17. package/rules/abie/lib/docs/http-route.md +44 -0
  18. package/rules/abie/lib/docs/k8s-tree.md +40 -0
  19. package/rules/abie/lib/docs/kustomization-patches.md +47 -0
  20. package/rules/abie/lib/docs/overlay-paths.md +38 -0
  21. package/rules/abie/lib/docs/yaml.md +29 -0
  22. package/rules/adr/docs/fix.md +148 -0
  23. package/rules/adr/js/docs/hooks.md +259 -0
  24. package/rules/bun/docs/fix.md +156 -0
  25. package/rules/bun/js/docs/layout.md +393 -0
  26. package/rules/capacitor/docs/fix.md +121 -0
  27. package/rules/capacitor/js/docs/platforms.md +295 -0
  28. package/rules/changelog/changelog.mdc +2 -2
  29. package/rules/changelog/docs/fix.md +174 -0
  30. package/rules/changelog/js/consistency.mjs +114 -13
  31. package/rules/changelog/js/docs/consistency.md +387 -0
  32. package/rules/changelog/lib/docs/package-manifest.md +210 -0
  33. package/rules/ci4/docs/fix.md +179 -0
  34. package/rules/ci4/js/docs/marksman_config.md +128 -0
  35. package/rules/docker/docker.mdc +8 -3
  36. package/rules/docker/docs/fix.md +171 -0
  37. package/rules/docker/js/docs/lint.md +258 -0
  38. package/rules/docker/lib/docs/docker-hadolint.md +184 -0
  39. package/rules/docker/lib/docs/docker-mirror.md +247 -0
  40. package/rules/docker/lib/docs/docker-native-addon.md +170 -0
  41. package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
  42. package/rules/docker/lint/docs/lint.md +193 -0
  43. package/rules/efes/docs/fix.md +203 -0
  44. package/rules/feedback/docs/fix.md +140 -0
  45. package/rules/flow/docs/fix.md +152 -0
  46. package/rules/ga/docs/fix.md +158 -0
  47. package/rules/ga/js/docs/lint.md +100 -0
  48. package/rules/ga/js/docs/workflows.md +217 -0
  49. package/rules/ga/lint/docs/lint.md +209 -0
  50. package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
  51. package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
  52. package/rules/graphql/docs/fix.md +126 -0
  53. package/rules/graphql/js/docs/tooling.md +264 -0
  54. package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
  55. package/rules/hasura/docs/fix.md +120 -0
  56. package/rules/hasura/hasura.mdc +14 -0
  57. package/rules/hasura/js/docs/internal_urls.md +326 -0
  58. package/rules/image-avif/docs/fix.md +132 -0
  59. package/rules/image-avif/js/docs/avif_generation.md +241 -0
  60. package/rules/image-compress/docs/fix.md +150 -0
  61. package/rules/image-compress/js/docs/package_setup.md +191 -0
  62. package/rules/js-bun-db/docs/fix.md +148 -0
  63. package/rules/js-bun-db/js/docs/safety.md +231 -0
  64. package/rules/js-bun-db/js-bun-db.mdc +42 -13
  65. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
  66. package/rules/js-bun-redis/docs/fix.md +123 -0
  67. package/rules/js-bun-redis/js/docs/imports.md +176 -0
  68. package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
  69. package/rules/js-lint/docs/fix.md +117 -0
  70. package/rules/js-lint/js/docs/lint.md +250 -0
  71. package/rules/js-lint/js/docs/tooling.md +348 -0
  72. package/rules/js-lint/js/docs/utils_imports.md +207 -0
  73. package/rules/js-lint-ci/docs/fix.md +154 -0
  74. package/rules/js-lint-ci/js/docs/lint.md +144 -0
  75. package/rules/js-mssql/docs/fix.md +128 -0
  76. package/rules/js-mssql/js/docs/deps.md +263 -0
  77. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
  78. package/rules/js-run/docs/fix.md +144 -0
  79. package/rules/js-run/js/docs/runtime.md +388 -0
  80. package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
  81. package/rules/js-run/lib/docs/check-env-scan.md +433 -0
  82. package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
  83. package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
  84. package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
  85. package/rules/k8s/docs/fix.md +129 -0
  86. package/rules/k8s/js/docs/manifests.md +344 -0
  87. package/rules/k8s/js/manifests.mjs +6 -2
  88. package/rules/k8s/k8s.mdc +4 -2
  89. package/rules/k8s/lint/docs/lint.md +411 -0
  90. package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
  91. package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
  92. package/rules/nginx-default-tpl/docs/fix.md +124 -0
  93. package/rules/nginx-default-tpl/js/docs/template.md +378 -0
  94. package/rules/npm-module/docs/fix.md +98 -0
  95. package/rules/npm-module/js/docs/package_structure.md +274 -0
  96. package/rules/npm-module/js/docs/rule_meta.md +137 -0
  97. package/rules/npm-module/js/docs/skill_meta.md +190 -0
  98. package/rules/php/docs/fix.md +107 -0
  99. package/rules/php/js/docs/tooling.md +152 -0
  100. package/rules/php/lint/docs/lint.md +215 -0
  101. package/rules/python/docs/fix.md +163 -0
  102. package/rules/python/js/docs/applies.md +108 -0
  103. package/rules/python/js/docs/tooling.md +153 -0
  104. package/rules/python/lint/docs/lint.md +322 -0
  105. package/rules/rego/docs/fix.md +121 -0
  106. package/rules/rego/js/docs/applies.md +174 -0
  107. package/rules/rego/js/docs/lint.md +118 -0
  108. package/rules/rego/lint/docs/lint.md +204 -0
  109. package/rules/release/docs/change.md +185 -0
  110. package/rules/release/docs/fix.md +119 -0
  111. package/rules/release/docs/release.md +222 -0
  112. package/rules/release/lib/docs/aggregate.md +246 -0
  113. package/rules/release/lib/docs/change-file.md +200 -0
  114. package/rules/release/lib/docs/fallback.md +203 -0
  115. package/rules/rust/docs/fix.md +129 -0
  116. package/rules/rust/js/docs/applies.md +140 -0
  117. package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
  118. package/rules/security/docs/fix.md +86 -0
  119. package/rules/security/js/docs/lint.md +171 -0
  120. package/rules/security/js/docs/sample_secret.md +190 -0
  121. package/rules/security/js/docs/trufflehog.md +137 -0
  122. package/rules/security/js/lint.mjs +9 -1
  123. package/rules/style-lint/docs/fix.md +155 -0
  124. package/rules/style-lint/js/docs/lint.md +184 -0
  125. package/rules/style-lint/js/docs/tooling.md +194 -0
  126. package/rules/tauri/docs/fix.md +158 -0
  127. package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
  128. package/rules/tauri/js/docs/tooling.md +228 -0
  129. package/rules/test/coverage/coverage.mjs +15 -3
  130. package/rules/test/docs/fix.md +132 -0
  131. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
  132. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
  133. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
  134. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
  135. package/rules/test/js/docs/cargo_mutants_config.md +173 -0
  136. package/rules/test/js/docs/location.md +136 -0
  137. package/rules/test/js/docs/no-process-chdir.md +160 -0
  138. package/rules/test/js/docs/no-relative-fs-path.md +271 -0
  139. package/rules/test/js/docs/stryker_config.md +152 -0
  140. package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
  141. package/rules/text/docs/fix.md +118 -0
  142. package/rules/text/js/docs/forbidden-prettier.md +143 -0
  143. package/rules/text/js/docs/formatting.md +256 -0
  144. package/rules/text/js/docs/lint.md +122 -0
  145. package/rules/text/lint/docs/lint.md +220 -0
  146. package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
  147. package/rules/text/lint/docs/run-shellcheck.md +212 -0
  148. package/rules/text/lint/docs/run-v8r.md +197 -0
  149. package/rules/vue/docs/fix.md +127 -0
  150. package/rules/vue/js/docs/packages.md +335 -0
  151. package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
  152. package/rules/worktree/docs/fix.md +161 -0
  153. package/schemas/rule-meta.json +5 -1
  154. package/scripts/auto-rules.mjs +7 -4
  155. package/scripts/coverage-classify/docs/apply.md +202 -0
  156. package/scripts/coverage-classify/docs/cache.md +203 -0
  157. package/scripts/coverage-classify/docs/index.md +218 -0
  158. package/scripts/coverage-classify/docs/prompt.md +132 -0
  159. package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
  160. package/scripts/coverage-fix-extract.mjs +122 -0
  161. package/scripts/coverage-fix.mjs +1 -1
  162. package/scripts/dispatcher/docs/graph.md +346 -0
  163. package/scripts/dispatcher/docs/index.md +236 -0
  164. package/scripts/dispatcher/docs/trace.md +296 -0
  165. package/scripts/dispatcher/index.mjs +1 -1
  166. package/scripts/dispatcher/lib/active.mjs +4 -8
  167. package/scripts/dispatcher/lib/commands.mjs +7 -11
  168. package/scripts/dispatcher/lib/docs/active.md +348 -0
  169. package/scripts/dispatcher/lib/docs/artifact.md +232 -0
  170. package/scripts/dispatcher/lib/docs/budget.md +167 -0
  171. package/scripts/dispatcher/lib/docs/capability.md +196 -0
  172. package/scripts/dispatcher/lib/docs/commands.md +210 -0
  173. package/scripts/dispatcher/lib/docs/events.md +182 -0
  174. package/scripts/dispatcher/lib/docs/executor.md +190 -0
  175. package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
  176. package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
  177. package/scripts/dispatcher/lib/docs/gate.md +231 -0
  178. package/scripts/dispatcher/lib/docs/level.md +335 -0
  179. package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
  180. package/scripts/dispatcher/lib/docs/plan.md +200 -0
  181. package/scripts/dispatcher/lib/docs/planner.md +269 -0
  182. package/scripts/dispatcher/lib/docs/review.md +255 -0
  183. package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
  184. package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
  185. package/scripts/dispatcher/lib/docs/spec.md +203 -0
  186. package/scripts/dispatcher/lib/docs/state-store.md +303 -0
  187. package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
  188. package/scripts/dispatcher/lib/executor.mjs +6 -1
  189. package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
  190. package/scripts/dispatcher/lib/level.mjs +29 -3
  191. package/scripts/dispatcher/lib/review.mjs +1 -1
  192. package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
  193. package/scripts/docs/auto-rules.md +376 -0
  194. package/scripts/docs/auto-skills.md +173 -0
  195. package/scripts/docs/build-agents-commands.md +183 -0
  196. package/scripts/docs/cli-entry.md +153 -0
  197. package/scripts/docs/coverage-fix.md +177 -0
  198. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
  199. package/scripts/lib/changed-files.mjs +4 -1
  200. package/scripts/lib/docs/changed-files.md +149 -0
  201. package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
  202. package/scripts/lib/docs/check-reporter.md +175 -0
  203. package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
  204. package/scripts/lib/docs/discover-checkable-rules.md +165 -0
  205. package/scripts/lib/docs/ensure-tool.md +254 -0
  206. package/scripts/lib/docs/generated-markdown.md +275 -0
  207. package/scripts/lib/docs/gha-workflow.md +326 -0
  208. package/scripts/lib/docs/inline-template-links.md +303 -0
  209. package/scripts/lib/docs/list-rule-ids.md +156 -0
  210. package/scripts/lib/docs/load-cursor-config.md +147 -0
  211. package/scripts/lib/docs/mirror-parity.md +167 -0
  212. package/scripts/lib/worktree.mjs +26 -0
  213. package/scripts/worktree-cli.mjs +12 -2
  214. package/skills/coverage-fix/SKILL.md +34 -45
  215. package/skills/docgen/SKILL.md +44 -23
  216. package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
  217. package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
  218. package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
  219. package/skills/docgen/js/docgen-ignore.mjs +54 -0
  220. package/skills/docgen/js/docgen-scan.mjs +37 -21
  221. package/skills/llm-patch/SKILL.md +23 -2
  222. package/skills/start-check/SKILL.md +26 -53
  223. package/skills/start-check/js/check.mjs +211 -0
  224. package/skills/taze/SKILL.md +9 -3
  225. package/skills/taze/js/diff.mjs +154 -0
  226. package/types/bin/n-cursor.d.ts +1 -1
  227. package/skills/fix-tests/SKILL.md +0 -119
  228. package/skills/fix-tests/meta.json +0 -1
@@ -0,0 +1,176 @@
1
+ # imports.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль реалізує AST/текстову перевірку правила `js-bun-redis.mdc` для JavaScript/TypeScript-джерел проєкту. Його завдання — гарантувати, що в усіх JS/TS-файлах репозиторію відсутні заборонені статичні чи динамічні імпорти (а також CommonJS `require`) Redis-клієнтів `ioredis`, `node-redis`, `redis` і підпакетів `@redis/*` / підшляхів `ioredis/...` / `redis/...`. Замість них код повинен використовувати Bun native Redis API: `import { redis } from 'bun'` (див. <https://bun.com/docs/runtime/redis>).
6
+
7
+ Модуль експортує одну головну функцію `check()`, яка:
8
+
9
+ 1. Перевіряє наявність `package.json` у корені репозиторію (інакше перевірка пропускається як неактуальна).
10
+ 2. Завантажує список ігнорованих шляхів через cursor-конфіг.
11
+ 3. Обходить дерево репозиторію, збираючи JS/TS-джерела, придатні для скану.
12
+ 4. Сканує кожне зібране джерело шукачем заборонених імпортів і генерує звіт через стандартний check-reporter.
13
+
14
+ Перевірка `package.json` (заборона залежностей `ioredis` / `node-redis` / `redis` / `@redis/*`) реалізована окремо — у Rego-полісі `npm/policy/js_bun_redis/package_json/`, яку запускає `npx @nitra/cursor check`. Поточний файл відповідає виключно за AST/текстовий скан коду.
15
+
16
+ ## Експорти / API
17
+
18
+ | Експорт | Тип | Призначення |
19
+ | ------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
20
+ | `check` | `async function(): Promise<number>` | Головна точка входу check-скрипта правила `js-bun-redis`. Повертає код виходу: `0` — порушень немає, `1` — є порушення або є інша помилка, зафіксована репортером. |
21
+
22
+ Інші функції (`findAllSourcePathsForRedisScan`, `scanSourcesForRedisImports`) є приватними допоміжними і не експортуються.
23
+
24
+ ## Функції
25
+
26
+ ### `findAllSourcePathsForRedisScan(repoRoot, ignorePaths)`
27
+
28
+ Збирає список абсолютних шляхів JS/TS-джерел, які треба просканувати на заборонені redis-імпорти.
29
+
30
+ - Сигнатура: `async function findAllSourcePathsForRedisScan(repoRoot: string, ignorePaths: string[]): Promise<string[]>`
31
+ - Параметри:
32
+ - `repoRoot` — абсолютний шлях до кореня репозиторію, від якого формуються відносні шляхи.
33
+ - `ignorePaths` — масив абсолютних шляхів каталогів, повністю виключених з обходу `walkDir`.
34
+ - Повертає: `Promise<string[]>` — абсолютні шляхи знайдених файлів, відсортовані за їх відносним шляхом (локалекомпаратор за `relative(repoRoot, ...)`).
35
+ - Поведінка / логіка:
36
+ 1. Створює локальний масив `paths`.
37
+ 2. Викликає `walkDir(repoRoot, callback, ignorePaths)`. Для кожного абсолютного шляху callback:
38
+ - Будує відносний шлях `relative(repoRoot, absPath)` і нормалізує сепаратори (Windows `\\` → `/`).
39
+ - Якщо `isRedisScanSourceFile(rel)` повертає `true` (тобто це JS/TS-подібне джерело, цільове для скану) і `shouldSkipFileForRedisScan(rel)` повертає `false` — додає `absPath` у `paths`.
40
+ 3. Сортує `paths` за відносним шляхом через `String.prototype.localeCompare`.
41
+ - Side effects: лише читання структури директорій через `walkDir` (синхронні чи асинхронні `stat`/`readdir` за реалізацією утиліти). Файли не читаються на цьому етапі.
42
+
43
+ ### `scanSourcesForRedisImports(sourcePaths, repoRoot, fail)`
44
+
45
+ Сканує текстовий вміст кожного з переданих файлів на заборонені redis-імпорти та звітує про порушення.
46
+
47
+ - Сигнатура: `async function scanSourcesForRedisImports(sourcePaths: string[], repoRoot: string, fail: (msg: string) => void): Promise<number>`
48
+ - Параметри:
49
+ - `sourcePaths` — абсолютні шляхи джерел, отримані з `findAllSourcePathsForRedisScan`.
50
+ - `repoRoot` — абсолютний шлях до кореня репозиторію (для нормалізації відносного шляху у повідомленнях).
51
+ - `fail` — callback репортера, який треба викликати з повідомленням про кожне порушення; саме він контролює exitCode.
52
+ - Повертає: `Promise<number>` — загальну кількість виявлених порушень (`violations`).
53
+ - Поведінка / логіка:
54
+ 1. Ініціалізує лічильник `violations = 0`.
55
+ 2. Для кожного `absPath` зі `sourcePaths`:
56
+ - Обчислює `rel = relative(repoRoot, absPath)` з нормалізацією `\\` → `/`.
57
+ - Читає файл `await readFile(absPath, 'utf8')`.
58
+ - Викликає `findRedisImportsInText(content, rel)` і ітерує його результат `v` (об’єкти з полями `line`, `module`, `snippet`).
59
+ - Для кожного `v` інкрементує `violations` і викликає `fail(...)` з форматованим повідомленням:
60
+ `js-bun-redis: <rel>:<line> — заміни '<module>' на Bun native Redis (import { redis } from 'bun', https://bun.com/docs/runtime/redis): <snippet>`.
61
+ 3. Повертає підсумкову `violations`.
62
+ - Side effects:
63
+ - Читання файлів з диска (`readFile`) — потенційно великий I/O залежно від розміру репозиторію.
64
+ - Виклик `fail` має побічний ефект: змінює внутрішній стан репортера так, що його `getExitCode()` поверне ненульовий код.
65
+
66
+ ### `check()` (експортована)
67
+
68
+ Головна точка входу check-скрипта правила `js-bun-redis`.
69
+
70
+ - Сигнатура: `export async function check(): Promise<number>`
71
+ - Параметри: відсутні. Робочий каталог визначається через `process.cwd()`.
72
+ - Повертає: `Promise<number>` — фінальний код виходу: `0` — все гаразд, `1` (або інший ненульовий за реалізацією репортера) — є порушення або інші зафіксовані помилки.
73
+ - Поведінка / логіка:
74
+ 1. Створює репортер `reporter = createCheckReporter()` і деструктурує з нього `pass`, `fail`.
75
+ 2. Визначає `repoRoot = process.cwd()`.
76
+ 3. Перевіряє наявність `package.json`: якщо `existsSync(join(repoRoot, 'package.json'))` повертає `false` — викликає `pass('js-bun-redis: package.json у корені відсутній — перевірку пропущено')` і повертає `reporter.getExitCode()` (рання гілка).
77
+ 4. Завантажує `ignorePaths` через `await loadCursorIgnorePaths(repoRoot)`.
78
+ 5. Збирає `sourcePaths = await findAllSourcePathsForRedisScan(repoRoot, ignorePaths)`.
79
+ 6. Якщо `sourcePaths.length === 0` — викликає `pass('js-bun-redis: немає JS/TS файлів для скану імпортів ioredis / node-redis / redis')` і повертає `reporter.getExitCode()`.
80
+ 7. Інакше викликає `violations = await scanSourcesForRedisImports(sourcePaths, repoRoot, fail)`.
81
+ 8. Якщо `violations === 0` — викликає `pass('js-bun-redis: немає імпортів \'ioredis\' / \'node-redis\' / \'redis\' / \'@redis/*\' у джерелах (використовується Bun native Redis або redis взагалі не задіяно)')`.
82
+ 9. Повертає `reporter.getExitCode()`.
83
+ - Side effects:
84
+ - Читання файлової системи (`existsSync`, `walkDir`, `readFile`).
85
+ - Виведення повідомлень через репортер (через `pass`/`fail` — у консоль або інше призначене місце, що визначається реалізацією `createCheckReporter`).
86
+ - Залежить від поточного робочого каталогу процесу (`process.cwd()`).
87
+
88
+ ## Залежності
89
+
90
+ ### Зовнішні (node:\* / runtime)
91
+
92
+ - `node:fs`
93
+ - `existsSync` — синхронна перевірка наявності `package.json` у корені.
94
+ - `node:fs/promises`
95
+ - `readFile` — асинхронне читання вмісту кожного JS/TS-файлу у кодуванні `utf8`.
96
+ - `node:path`
97
+ - `join` — побудова шляху до `package.json`.
98
+ - `relative` — обчислення відносних шляхів для логування та сортування.
99
+
100
+ ### Внутрішні (проєктні)
101
+
102
+ - `../../../scripts/lib/check-reporter.mjs`
103
+ - `createCheckReporter` — фабрика стандартного репортера для check-скриптів правил. Дає API `{ pass, fail, getExitCode }`.
104
+ - `../../../scripts/lib/load-cursor-config.mjs`
105
+ - `loadCursorIgnorePaths` — завантажує абсолютні шляхи, які слід виключити з обходу (з cursor-конфігу/ignore-файлів).
106
+ - `../lib/redis-imports.mjs`
107
+ - `findRedisImportsInText(text, relPath)` — шукає у тексті заборонені redis-імпорти/`require`/динамічні `import()`; повертає масив порушень з полями `line`, `module`, `snippet`.
108
+ - `isRedisScanSourceFile(relPath)` — предикат: чи цей відносний шлях належить до набору JS/TS-джерел, придатних для скану.
109
+ - `shouldSkipFileForRedisScan(relPath)` — предикат: чи слід пропустити конкретний файл (наприклад, тестові фікстури, генеровані файли тощо).
110
+ - `../../../scripts/utils/walkDir.mjs`
111
+ - `walkDir(rootAbs, visit, ignorePaths)` — асинхронний обхід директорії з викликом `visit(absPath)` для кожного файлу та пропуском заданих `ignorePaths`.
112
+
113
+ ## Потік виконання / Використання
114
+
115
+ Файл є частиною інфраструктури check-скриптів правил Cursor для проєкту. Очікувано він викликається оркестратором, який імпортує `check` із цього модуля і запускає її з робочою директорією, що дорівнює кореню репозиторію.
116
+
117
+ Узагальнений потік виконання `check()`:
118
+
119
+ 1. Інстанціювання репортера `createCheckReporter()`.
120
+ 2. Гілка раннього виходу: відсутній кореневий `package.json` → `pass(...)` → `return reporter.getExitCode()`.
121
+ 3. Завантаження `ignorePaths` через `loadCursorIgnorePaths(repoRoot)`.
122
+ 4. Збір кандидатів через `findAllSourcePathsForRedisScan(repoRoot, ignorePaths)`:
123
+ - Обхід `walkDir` з відсіюванням за `isRedisScanSourceFile` і `shouldSkipFileForRedisScan`.
124
+ - Детермінований порядок через `localeCompare` за відносним шляхом.
125
+ 5. Гілка раннього виходу: порожній список кандидатів → `pass(...)` → `return reporter.getExitCode()`.
126
+ 6. Скан кожного джерела через `scanSourcesForRedisImports`:
127
+ - Читання `readFile(..., 'utf8')`.
128
+ - Виклик `findRedisImportsInText(content, rel)`.
129
+ - На кожне порушення — `fail('js-bun-redis: <rel>:<line> — заміни \'<module>\' на Bun native Redis (...): <snippet>')` та `violations++`.
130
+ 7. Якщо `violations === 0` — повідомлення про успіх через `pass(...)`.
131
+ 8. Фінальне повернення `reporter.getExitCode()`:
132
+ - `0` — лише `pass`/нуль порушень.
133
+ - Ненульове значення — щонайменше один `fail`.
134
+
135
+ Приклад типового підключення з оркестратора правил (схематично):
136
+
137
+ ```js
138
+ import { check } from './imports.mjs'
139
+
140
+ const code = await check()
141
+ process.exit(code)
142
+ ```
143
+
144
+ Команда `npx @nitra/cursor check` запускає цей скрипт у пайплайні разом із Rego-полісом `npm/policy/js_bun_redis/package_json/`, який перевіряє відсутність заборонених redis-пакетів у `dependencies`/`devDependencies`/`peerDependencies` будь-якого `package.json` у репозиторії. Цей файл доповнює полісі рівнем скану реальних імпортів у коді, бо самих лише обмежень у `package.json` недостатньо: пакет може бути транзитивно встановлений або підвантажений з підшляху.
145
+
146
+ Особливості та граничні випадки:
147
+
148
+ - Шляхи нормалізуються до прямих слешів (`/`) перед тим як прогнатись через предикати, щоб логіка `isRedisScanSourceFile` / `shouldSkipFileForRedisScan` працювала однаково на Linux/macOS і Windows.
149
+ - Сортування результатів `findAllSourcePathsForRedisScan` забезпечує стабільний детермінований порядок виводу повідомлень про порушення, що корисно для CI-діффів.
150
+ - Якщо у файлі знайдено кілька порушень — кожне репортується окремим повідомленням, але сумарний `violations` повертається з усієї пробіжки.
151
+ - `check()` свідомо не кидає винятків самостійно: усі помилки оформлюються як `fail(...)` для коректного коду виходу через `reporter.getExitCode()`. Винятки від `readFile`/`walkDir` будуть проксі через `await` і призведуть до rejected promise (це залишається на відповідальності оркестратора).
152
+
153
+ ## Rebuild Test
154
+
155
+ Якщо за цією документацією треба відновити модуль, він повинен:
156
+
157
+ 1. Імпортувати з `node:fs` — `existsSync`; з `node:fs/promises` — `readFile`; з `node:path` — `join`, `relative`.
158
+ 2. Імпортувати з `../../../scripts/lib/check-reporter.mjs` — `createCheckReporter`.
159
+ 3. Імпортувати з `../../../scripts/lib/load-cursor-config.mjs` — `loadCursorIgnorePaths`.
160
+ 4. Імпортувати з `../lib/redis-imports.mjs` — `findRedisImportsInText`, `isRedisScanSourceFile`, `shouldSkipFileForRedisScan`.
161
+ 5. Імпортувати з `../../../scripts/utils/walkDir.mjs` — `walkDir`.
162
+ 6. Оголосити приватну `findAllSourcePathsForRedisScan(repoRoot, ignorePaths)`, що:
163
+ - обходить `walkDir(repoRoot, cb, ignorePaths)`;
164
+ - усередині callback нормалізує `relative(repoRoot, absPath)` до прямих слешів, фільтрує через `isRedisScanSourceFile && !shouldSkipFileForRedisScan` і пушить у локальний масив;
165
+ - сортує масив за `relative(repoRoot, x).localeCompare(...)`;
166
+ - повертає його.
167
+ 7. Оголосити приватну `scanSourcesForRedisImports(sourcePaths, repoRoot, fail)`, що:
168
+ - ітерує `sourcePaths`, для кожного читає `readFile(..., 'utf8')`, нормалізує `rel`, прогоняє `findRedisImportsInText` і на кожне порушення інкрементує лічильник + викликає `fail(...)` з повідомленням формату `js-bun-redis: <rel>:<line> — заміни '<module>' на Bun native Redis (import { redis } from 'bun', https://bun.com/docs/runtime/redis): <snippet>`;
169
+ - повертає кількість порушень.
170
+ 8. Експортувати `async function check()`, що:
171
+ - створює `reporter = createCheckReporter()`, бере `pass`/`fail` з нього;
172
+ - якщо `package.json` у `process.cwd()` відсутній — `pass('... перевірку пропущено')` і повертає `reporter.getExitCode()`;
173
+ - завантажує `ignorePaths` через `loadCursorIgnorePaths(repoRoot)`;
174
+ - збирає `sourcePaths`, якщо порожньо — `pass('... немає JS/TS файлів для скану ...')` і вихід;
175
+ - інакше викликає `scanSourcesForRedisImports`, на нуль порушень — `pass('... немає імпортів ioredis / node-redis / redis / @redis/* ...')`;
176
+ - повертає `reporter.getExitCode()`.
@@ -0,0 +1,223 @@
1
+ # redis-imports.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `redis-imports.mjs` — це сканер вихідного коду, який знаходить **заборонені** імпорти Redis-клієнтів (`ioredis`, `node-redis`, кореневий пакет `redis` та супутні підпакети сімейства `@redis/*`) у JavaScript/TypeScript-файлах. Метою сканера є **виявлення місць, які треба замінити на Bun native Redis** — стандартизований API `import { redis } from 'bun'` (див. `https://bun.com/docs/runtime/redis`).
6
+
7
+ Семантичне розпізнавання імпортів виконується через AST-парсер **`oxc-parser`** (а не regex по тілу файлу), тож сканер коректно відрізняє рядкові літерали, коментарі та реальні `import`/`require`. Підтримуються три форми входу модулів:
8
+
9
+ 1. **Статичні ES-імпорти** — через `result.module.staticImports`.
10
+ 2. **CommonJS `require('...')`** — через обхід AST і утиліту `requireCallModule`.
11
+ 3. **Динамічні `import('...')`** — через обхід AST і утиліту `dynamicImportModule`.
12
+
13
+ Це дає змогу єдиним проходом покривати і ESM-код, і CommonJS, і змішані сценарії з динамічним підвантаженням у межах одного файлу.
14
+
15
+ Сканер є **частиною правила** `js-bun-redis.mdc` у системі правил `n-cursor` (директорія `npm/rules/js-bun-redis/`) і використовується чек-скриптами правила для пошуку порушень у пакетах монорепо.
16
+
17
+ Сканер **не вимагає**, щоб файл був синтаксично коректним: якщо `oxc-parser` повертає помилку або `result.errors` непорожній, функція повертає порожній масив порушень. Тобто спершу треба полагодити синтаксис, а вже потім перезапускати перевірку — інакше сканер просто «нічого не знайде».
18
+
19
+ ## Експорти / API
20
+
21
+ Модуль експортує **три** іменовані функції:
22
+
23
+ | Експорт | Тип | Призначення |
24
+ | ----------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------- |
25
+ | `findRedisImportsInText(content, virtualPath?)` | `function` | Повертає масив знайдених заборонених імпортів Redis у переданому коді. |
26
+ | `isRedisScanSourceFile(relativePath)` | `function` | Предикат: чи треба сканувати файл за розширенням (`.js/.mjs/.cjs/.jsx/.ts/.mts/.cts/.tsx`). |
27
+ | `shouldSkipFileForRedisScan(relativePosix)` | `function` | Предикат: чи слід пропустити файл (декларації типів `.d.ts`). |
28
+
29
+ Default export відсутній.
30
+
31
+ ## Функції
32
+
33
+ ### `isForbiddenRedisModule(mod)` (внутрішня)
34
+
35
+ **Сигнатура:** `function isForbiddenRedisModule(mod: string): boolean`
36
+
37
+ **Параметри:**
38
+
39
+ - `mod` — рядкове значення специфікатора модуля з конструкцій `import '...'`, `require('...')` або `import('...')`.
40
+
41
+ **Повертає:** `true`, якщо `mod`:
42
+
43
+ - входить у множину точних збігів `FORBIDDEN_MODULE_NAMES`: `'ioredis'`, `'node-redis'`, `'redis'`, `'@redis/client'`, `'@redis/json'`, `'@redis/search'`, `'@redis/time-series'`, `'@redis/bloom'`;
44
+ - **або** починається з префіксу `'ioredis/'`, `'redis/'`, `'@redis/'` — для підшляхів (`ioredis/built/utils`, `redis/dist/...`, `@redis/<sub>`).
45
+
46
+ Інакше повертає `false`. Префіксний підхід **навмисно** ловить підшляхи Redis-пакетів, але **не зачіпає** сторонні пакети-сусіди типу `redis-mock` (бо вони не починаються з `redis/`, а саме з `redis-`).
47
+
48
+ **Side effects:** немає (чиста функція).
49
+
50
+ **Видимість:** локальна для модуля; не експортується.
51
+
52
+ ### `findRedisImportsInText(content, virtualPath = 'scan.ts')`
53
+
54
+ **Сигнатура:**
55
+
56
+ ```js
57
+ export function findRedisImportsInText(
58
+ content: string,
59
+ virtualPath?: string
60
+ ): { line: number, snippet: string, module: string }[]
61
+ ```
62
+
63
+ **Параметри:**
64
+
65
+ - `content` — повний текст вихідного файлу як рядок.
66
+ - `virtualPath` _(необовʼязковий, дефолт `'scan.ts'`)_ — шлях, який передається в парсер і використовується для визначення мови через `langFromPath(...)`. Може бути «віртуальним», тобто не існувати на ФС — потрібен лише для вибору режиму парсингу (наприклад, `.ts` vs `.tsx`). Якщо передати порожній рядок чи `undefined`, всередині буде підставлено `'scan.ts'`.
67
+
68
+ **Повертає:** масив обʼєктів-порушень, де кожен елемент має поля:
69
+
70
+ - `line: number` — 1-based номер рядка, де починається `import`/`require`/`import(...)` (через `offsetToLine(content, start)`);
71
+ - `snippet: string` — нормалізований фрагмент коду цього вузла (через `normalizeSnippet(content.slice(start, end))`);
72
+ - `module: string` — фактичний специфікатор модуля (`'ioredis'`, `'redis/dist/...'`, `'@redis/json'` тощо).
73
+
74
+ Якщо файл синтаксично некоректний (`parseSync` кинув виключення) або `result.errors` непорожній — повертає `[]`.
75
+
76
+ **Side effects:** немає (чиста функція; не звертається до ФС, не пише в stdout).
77
+
78
+ **Алгоритм:**
79
+
80
+ 1. Обирає `lang` для парсера через `langFromPath(pathForLang)`.
81
+ 2. Викликає `parseSync(pathForLang, content, { lang, sourceType: 'module' })` у `try/catch`.
82
+ 3. Якщо парсинг кинув / повернув помилки — `return []`.
83
+ 4. Проходить **статичні імпорти** через `result.module?.staticImports ?? []`; для кожного, де `imp.moduleRequest?.value` — рядок і `isForbiddenRedisModule(...)` істинне, додає запис у вихідний масив.
84
+ 5. Проходить AST програми через `walkAstWithAncestors(result.program, [], node => {...})`:
85
+ - якщо `requireCallModule(node)` повертає рядок із забороненим модулем — додає запис і **повертається** (`return`), щоб не дублювати спробу як динамічний `import` для того самого вузла;
86
+ - інакше пробує `dynamicImportModule(node)` і так само додає запис при збігу.
87
+ 6. Повертає накопичений масив `out`.
88
+
89
+ ### `isRedisScanSourceFile(relativePath)`
90
+
91
+ **Сигнатура:** `export function isRedisScanSourceFile(relativePath: string): boolean`
92
+
93
+ **Параметри:**
94
+
95
+ - `relativePath` — відносний шлях до файлу.
96
+
97
+ **Повертає:** `true`, якщо шлях підпадає під регулярний вираз `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u`, тобто має одне з розширень `.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.mts`, `.cts`, `.tsx`. Інакше — `false`.
98
+
99
+ **Side effects:** немає.
100
+
101
+ ### `shouldSkipFileForRedisScan(relativePosix)`
102
+
103
+ **Сигнатура:** `export function shouldSkipFileForRedisScan(relativePosix: string): boolean`
104
+
105
+ **Параметри:**
106
+
107
+ - `relativePosix` — шлях з POSIX-роздільниками (`/`).
108
+
109
+ **Повертає:** `true`, якщо файл закінчується на `.d.ts` — декларації типів TypeScript не виконуваний код, у них не може бути «реальних» імпортів-споживачів Redis, тому їх сканувати не варто. Для решти файлів — `false`.
110
+
111
+ **Side effects:** немає.
112
+
113
+ ## Залежності
114
+
115
+ ### Зовнішні (npm)
116
+
117
+ - **`oxc-parser`** — використовується через іменований імпорт `parseSync` для отримання AST програми та переліку статичних імпортів модуля.
118
+
119
+ ### Внутрішні (репозиторій)
120
+
121
+ Імпорт із `'../../../scripts/utils/ast-scan-utils.mjs'` — спільні утиліти AST-сканера для всіх правил у `npm/rules/*/lib/`:
122
+
123
+ - `dynamicImportModule(node)` — якщо вузол AST є динамічним `import('mod')`, повертає рядок `'mod'`, інакше `null`/`undefined`.
124
+ - `langFromPath(path)` — обчислює `lang` для `oxc-parser` за розширенням шляху.
125
+ - `normalizeSnippet(src)` — нормалізує сирий зріз коду (приведення пробілів/обрізання) для зручного звіту.
126
+ - `offsetToLine(content, offset)` — конвертує абсолютний offset символа у 1-based номер рядка.
127
+ - `requireCallModule(node)` — якщо вузол є викликом `require('mod')`, повертає рядок `'mod'`, інакше `null`/`undefined`.
128
+ - `walkAstWithAncestors(program, ancestors, visitor)` — обхід AST програми з накопиченням предків.
129
+
130
+ ### Константи модуля
131
+
132
+ - `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u` — регекс розширень JS/TS-сімʼї.
133
+ - `FORBIDDEN_MODULE_NAMES` — `Set<string>` із вісьмома точними іменами заборонених модулів.
134
+
135
+ ## Потік виконання / Використання
136
+
137
+ ### Типовий сценарій
138
+
139
+ 1. Обхідник пакета монорепо отримує список файлів через `readdir/walk`.
140
+ 2. Для кожного файлу:
141
+ - перевіряє `shouldSkipFileForRedisScan(relativePosix)` → якщо `true`, файл пропускається (наприклад, `*.d.ts`);
142
+ - перевіряє `isRedisScanSourceFile(relativePosix)` → якщо `false`, файл пропускається (нерелевантне розширення);
143
+ - інакше читає вміст файлу та викликає `findRedisImportsInText(content, virtualPath)`.
144
+ 3. Отриманий масив порушень агрегується у звіт правила `js-bun-redis`.
145
+
146
+ ### Приклад
147
+
148
+ ```js
149
+ import { readFileSync } from 'node:fs'
150
+ import { findRedisImportsInText, isRedisScanSourceFile, shouldSkipFileForRedisScan } from './redis-imports.mjs'
151
+
152
+ const file = 'pkg/src/cache.ts'
153
+ if (!shouldSkipFileForRedisScan(file) && isRedisScanSourceFile(file)) {
154
+ const content = readFileSync(file, 'utf8')
155
+ const violations = findRedisImportsInText(content, file)
156
+ for (const v of violations) {
157
+ console.log(`${file}:${v.line} forbidden import of ${v.module} -> ${v.snippet}`)
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### Що саме розпізнається як порушення
163
+
164
+ Усі наступні форми у `cache.ts` будуть знайдені:
165
+
166
+ ```ts
167
+ import Redis from 'ioredis' // ESM static, точна назва
168
+ import { createClient } from 'redis' // ESM static, кореневий node-redis
169
+ import json from '@redis/json' // ESM static, підпакет @redis/*
170
+ import utils from 'ioredis/built/utils' // ESM static, підшлях ioredis/
171
+ const Redis2 = require('ioredis') // CommonJS require
172
+ const lib = await import('node-redis') // динамічний ESM import
173
+ ```
174
+
175
+ А ось такі **не** будуть позначені (бо це сторонні пакети):
176
+
177
+ ```ts
178
+ import { mock } from 'redis-mock' // not ioredis/redis/@redis
179
+ import x from 'my-redis-helpers' // довільний сторонній пакет
180
+ ```
181
+
182
+ ### Поведінка на помилках
183
+
184
+ - Якщо `parseSync` кинув виняток — порожній результат `[]`.
185
+ - Якщо `result.errors?.length` істинний — порожній результат `[]`.
186
+ - Користувач має спершу полагодити синтаксис файлу, тільки тоді повторно запускати перевірку.
187
+
188
+ ### Інтеграція з правилом
189
+
190
+ Модуль є **бібліотечним** шаром (`lib/`) правила `js-bun-redis`. Чек-скрипт правила (типово `npm/rules/js-bun-redis/check-*.mjs`) імпортує ці три функції, обходить файли пакета і формує звіт. Заміна — на Bun native Redis (`import { redis } from 'bun'`).
191
+
192
+ ## Rebuild Test
193
+
194
+ Контрольний перелік, за яким можна **відтворити** функціонал модуля «з нуля»:
195
+
196
+ 1. Створити файл `redis-imports.mjs`, що імпортує `parseSync` з `oxc-parser`.
197
+ 2. Імпортувати з `'../../../scripts/utils/ast-scan-utils.mjs'` шість утиліт: `dynamicImportModule`, `langFromPath`, `normalizeSnippet`, `offsetToLine`, `requireCallModule`, `walkAstWithAncestors`.
198
+ 3. Оголосити константу `SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u`.
199
+ 4. Оголосити константу `FORBIDDEN_MODULE_NAMES` як `Set` із елементами: `'ioredis'`, `'node-redis'`, `'redis'`, `'@redis/client'`, `'@redis/json'`, `'@redis/search'`, `'@redis/time-series'`, `'@redis/bloom'`.
200
+ 5. Реалізувати приватну функцію `isForbiddenRedisModule(mod)`: повертає `true`, якщо `FORBIDDEN_MODULE_NAMES.has(mod)` **або** `mod.startsWith('ioredis/')` **або** `mod.startsWith('redis/')` **або** `mod.startsWith('@redis/')`.
201
+ 6. Експортувати функцію `findRedisImportsInText(content, virtualPath = 'scan.ts')`:
202
+ 1. `pathForLang = virtualPath || 'scan.ts'`;
203
+ 2. `lang = langFromPath(pathForLang)`;
204
+ 3. у `try { parseSync(pathForLang, content, { lang, sourceType: 'module' }) } catch { return [] }`;
205
+ 4. якщо `result.errors?.length` — `return []`;
206
+ 5. ініціалізувати `out = []`;
207
+ 6. пройти `result.module?.staticImports ?? []` — для кожного, де `imp.moduleRequest?.value` рядок і `isForbiddenRedisModule(...)` істинне, пушити `{ line: offsetToLine(content, imp.start), snippet: normalizeSnippet(content.slice(imp.start, imp.end)), module: mod }`;
208
+ 7. викликати `walkAstWithAncestors(result.program, [], node => {...})`:
209
+ - спершу спробувати `requireCallModule(node)`; на збігу пушити запис і `return`;
210
+ - потім `dynamicImportModule(node)`; на збігу пушити запис;
211
+ 8. повернути `out`.
212
+ 7. Експортувати функцію `isRedisScanSourceFile(relativePath)`: `return SOURCE_FILE_RE.test(relativePath)`.
213
+ 8. Експортувати функцію `shouldSkipFileForRedisScan(relativePosix)`: `return relativePosix.endsWith('.d.ts')`.
214
+
215
+ Контрольні очікування:
216
+
217
+ - Виклик `findRedisImportsInText("import x from 'ioredis'", 'a.ts')` повертає масив із одного елемента, де `module === 'ioredis'`, `line === 1`.
218
+ - Виклик `findRedisImportsInText("import x from 'redis-mock'", 'a.ts')` повертає `[]`.
219
+ - Виклик `findRedisImportsInText("const r = require('@redis/json')", 'a.js')` повертає масив із одного елемента, де `module === '@redis/json'`.
220
+ - Виклик `findRedisImportsInText("await import('redis/dist/x')", 'a.mjs')` повертає масив із одного елемента, де `module === 'redis/dist/x'`.
221
+ - Виклик `findRedisImportsInText("import 'ioredis'; syntax error here {{", 'a.ts')` повертає `[]` (через помилку парсингу/`errors`).
222
+ - `isRedisScanSourceFile('foo.ts')` → `true`; `isRedisScanSourceFile('foo.md')` → `false`.
223
+ - `shouldSkipFileForRedisScan('types/foo.d.ts')` → `true`; `shouldSkipFileForRedisScan('src/foo.ts')` → `false`.
@@ -0,0 +1,117 @@
1
+ # fix.mjs — точка входу правила `js-lint`
2
+
3
+ ## Огляд
4
+
5
+ Файл `npm/rules/js-lint/fix.mjs` — це тонкий ентрі-поінт (entry-point) правила з ідентифікатором `js-lint` для CLI `@nitra/cursor`. Він не містить власної логіки перевірок: уся механіка прогону правила інкапсульована у бібліотечних модулях `run-standard-rule.mjs` (стандартний пайплайн правил) та `run-rule-cli.mjs` (CLI-обгортка).
6
+
7
+ Модуль виконує **подвійну роль**:
8
+
9
+ 1. **Library mode** — експортує функцію `run(ctx)`, яку імпортують інші частини оркестратора правил. У цьому режимі правило виконується в межах єдиного процесу, отримує контекст (`ctx`) із кешем обходу файлової системи (`walkCache`) та інших спільних структур, і повертає exit-код як значення Promise.
10
+ 2. **Standalone mode** — якщо файл запущено напряму як CLI (`bun rules/js-lint/fix.mjs`), виконується повноцінний CLI-цикл: завантаження конфігурації, застосування whitelist-фільтрів, виведення підсумків і завершення процесу з відповідним exit-кодом (повний еквівалент команди `npx @nitra/cursor fix js-lint`).
11
+
12
+ Стандартний пайплайн, який стоїть за `runStandardRule`, складається з чотирьох послідовних фаз: `applies` (визначення цільових файлів) → `JS-concerns` (специфічні для правила перевірки/фікси) → `policy` (агрегування політик) → `mdc-refs` (узгодження з `.mdc`-метаописами правил).
13
+
14
+ ## Експорти / API
15
+
16
+ | Символ | Тип | Призначення |
17
+ | ------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `run` | `function(ctx?): Promise<number>` | Library-API: запускає правило `js-lint` в межах поточного процесу, повертає exit-код (0 — OK, 1 — порушення). Експортується як іменований експорт через `export function run(ctx)`. |
19
+
20
+ Default-експорт відсутній. Інших іменованих експортів немає.
21
+
22
+ ## Функції
23
+
24
+ ### `run(ctx)`
25
+
26
+ ```js
27
+ export function run(ctx)
28
+ ```
29
+
30
+ - **Сигнатура:** `run(ctx?: RuleContext): Promise<number>`
31
+ - **Параметри:**
32
+ - `ctx` (необовʼязковий) — обʼєкт контексту прогону правила типу `RuleContext` (визначений у `../../scripts/lib/run-standard-rule.mjs`). Зазвичай містить кешовані структури на кшталт `walkCache` (кеш обходу файлової системи), що дозволяє оркестратору перевикористовувати дані між послідовними правилами без повторного обходу диску. Якщо аргумент відсутній — кеші ініціалізуються всередині `runStandardRule`.
33
+ - **Повертає:** `Promise<number>` — exit-код прогону правила:
34
+ - `0` — правило не знайшло порушень або всі знайдені порушення були автоматично виправлені;
35
+ - `1` — присутні порушення, які залишилися після фази fix (тобто потрібне ручне втручання або правило в режимі lint-only).
36
+ - **Реалізація:** повертає результат виклику `runStandardRule(import.meta.dirname, ctx)`. Перший аргумент `import.meta.dirname` — абсолютний шлях до директорії, у якій лежить `fix.mjs` (тобто `.../npm/rules/js-lint`). Саме за цим шляхом `runStandardRule` знаходить решту артефактів правила (наприклад, `applies.mjs`, `policy.mjs`, `mdc`-довідники, та власне fix-логіку).
37
+ - **Side effects:** делегуються до `runStandardRule`. Стандартно включають читання вихідних файлів проєкту, можливе перезаписування файлів коду під час фази fix, читання конфігурації правил, а також виведення прогресу/підсумків у stdout/stderr (залежить від конкретної реалізації стандартного пайплайна).
38
+
39
+ ### Standalone-блок (top-level)
40
+
41
+ ```js
42
+ if (isRunAsCli(import.meta.url)) {
43
+ process.exit(await runRuleCli(import.meta.dirname))
44
+ }
45
+ ```
46
+
47
+ - **Сигнатура:** не є функцією — це top-level умовний блок, який виконується один раз при завантаженні модуля.
48
+ - **Логіка:**
49
+ 1. `isRunAsCli(import.meta.url)` визначає, чи файл запущено як головний модуль (через `bun fix.mjs` / `node fix.mjs`) або імпортовано як бібліотеку. Стандартна ідіома: порівнює `import.meta.url` із URL процесу запуску.
50
+ 2. Якщо запуск standalone — виконується `await runRuleCli(import.meta.dirname)`, який повертає exit-код. Цей виклик інкапсулює CLI-обвʼязки: парсинг аргументів, завантаження користувацької/проєктної конфігурації, застосування whitelist (фільтра шляхів), виклик `run(ctx)` під капотом і друк фінального підсумку.
51
+ 3. `process.exit(...)` гарантовано завершує процес із отриманим exit-кодом — це необхідно для коректної інтеграції з CI-системами та IDE, які орієнтуються на код виходу.
52
+ - **Side effects:** завершення процесу Node/Bun. У standalone-режимі правило ніколи не повертає керування викликачу — замість return-у виконується `process.exit`.
53
+ - **Зауваження по лінтерам:** перед викликом `process.exit` стоїть директива `// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE` — це свідомий виняток із загальної заборони на `process.exit` для standalone entry-point-ів.
54
+
55
+ ## Залежності
56
+
57
+ ### Внутрішні (модулі репозиторію)
58
+
59
+ - `../../scripts/lib/run-rule-cli.mjs` — імпортуються:
60
+ - `isRunAsCli` — детектор режиму запуску (CLI vs library).
61
+ - `runRuleCli` — повноцінна CLI-обгортка для правила: парсинг аргументів, конфіг, whitelist, виклик `run`, друк summary; повертає `Promise<number>` (exit-код).
62
+ - `../../scripts/lib/run-standard-rule.mjs` — імпортується:
63
+ - `runStandardRule` — стандартний пайплайн правил у чотири фази (`applies` → `JS-concerns` → `policy` → `mdc-refs`), приймає шлях до директорії правила та опційний контекст; повертає `Promise<number>`.
64
+ - Також звідси типізовано `RuleContext` (через JSDoc-тип `import('../../scripts/lib/run-standard-rule.mjs').RuleContext`).
65
+
66
+ ### Зовнішні / runtime
67
+
68
+ - `import.meta.dirname` — Node/Bun runtime API (стабільний з Node 20.11). Повертає абсолютний шлях до директорії модуля. Використовується як «адреса» правила для оркестратора.
69
+ - `import.meta.url` — URL поточного модуля; передається у `isRunAsCli` для розрізнення CLI-запуску й import-режиму.
70
+ - `process.exit` — Node/Bun global, термінує процес із заданим exit-кодом.
71
+ - Top-level `await` — використовується для `await runRuleCli(...)`; вимагає ES-modules (`.mjs`) і середовища з підтримкою top-level await.
72
+
73
+ Жодних npm-пакетів модуль напряму не імпортує.
74
+
75
+ ## Потік виконання / Використання
76
+
77
+ ### Сценарій 1: запуск як CLI (standalone)
78
+
79
+ ```bash
80
+ bun npm/rules/js-lint/fix.mjs
81
+ # або еквівалент через головний CLI:
82
+ npx @nitra/cursor fix js-lint
83
+ ```
84
+
85
+ 1. ES-модуль завантажується, виконуються імпорти `isRunAsCli`, `runRuleCli`, `runStandardRule`.
86
+ 2. Оголошується експорт `run` (але одразу не викликається).
87
+ 3. Виконується top-level `if (isRunAsCli(import.meta.url))`. У standalone це `true`.
88
+ 4. Викликається `await runRuleCli(import.meta.dirname)`:
89
+ - читається конфігурація проєкту (whitelist, exclude, опції правил);
90
+ - визначається область дії (які файли потрапляють у правило `js-lint`);
91
+ - усередині викликається `run(ctx)` із підготовленим контекстом;
92
+ - друкується підсумок (порушення, виправлення, час).
93
+ 5. `process.exit(<код>)` завершує процес. CI-система отримує `0` (успіх) або `1` (є порушення).
94
+
95
+ ### Сценарій 2: виклик як бібліотеки (library mode)
96
+
97
+ ```js
98
+ import { run } from './npm/rules/js-lint/fix.mjs'
99
+
100
+ const ctx = { walkCache: /* спільний кеш */, /* ... */ }
101
+ const exitCode = await run(ctx)
102
+ if (exitCode !== 0) {
103
+ // обробка порушень: повторний прогін, escalation у CI тощо
104
+ }
105
+ ```
106
+
107
+ 1. Сторонній модуль (зазвичай головний оркестратор `@nitra/cursor`) імпортує `run`.
108
+ 2. `isRunAsCli(import.meta.url)` для `fix.mjs` поверне `false` (бо `import.meta.url` модуля не збігається з URL процесу) — standalone-блок не виконується, `process.exit` не викликається.
109
+ 3. Викликач передає у `run(ctx)` готовий контекст із попередньо побудованими кешами.
110
+ 4. `run` делегує у `runStandardRule(import.meta.dirname, ctx)`, який виконує чотирифазний пайплайн правила і повертає exit-код.
111
+ 5. Викликач сам вирішує, що робити з кодом (агрегувати з іншими правилами, ескалувати, друкувати тощо). Завершення процесу — на боці викликача.
112
+
113
+ ### Інтеграція в загальну архітектуру правил
114
+
115
+ - Кожне правило в каталозі `npm/rules/<id>/` має власний `fix.mjs` за **однаковим шаблоном**: експортує `run(ctx)`, що делегує у `runStandardRule(import.meta.dirname, ctx)`, плюс standalone-блок із `runRuleCli`. Конкретні перевірки/фікси правила лежать у сусідніх файлах директорії (`applies.mjs`, `policy.mjs`, `check-*.mjs` тощо) і автоматично підхоплюються стандартним пайплайном за конвенцією шляхів.
116
+ - Для правила `js-lint` стандартний пайплайн виконує: відбір JS/TS/Vue-файлів (фаза `applies`) → специфічні JS-перевірки (фаза `JS-concerns`, реалізована в інших файлах цієї ж директорії) → агрегування політик (`policy`) → крос-перевірку з `.mdc`-описом правила (`mdc-refs`).
117
+ - Контракт «`fix.mjs` = тонка обгортка» уніфікує всі правила: уся складність захована за `runStandardRule` і `runRuleCli`. Завдяки цьому файл `fix.mjs` будь-якого правила залишається маленьким (одиниці рядків коду) і легко піддається ревʼю/документації.