@nitra/cursor 3.21.1 → 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 (231) hide show
  1. package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
  2. package/CHANGELOG.md +37 -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/js/lint-findings.mjs +110 -0
  74. package/rules/js-lint/js/lint.mjs +86 -15
  75. package/rules/js-lint-ci/docs/fix.md +154 -0
  76. package/rules/js-lint-ci/js/docs/lint.md +144 -0
  77. package/rules/js-mssql/docs/fix.md +128 -0
  78. package/rules/js-mssql/js/docs/deps.md +263 -0
  79. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
  80. package/rules/js-run/docs/fix.md +144 -0
  81. package/rules/js-run/js/docs/runtime.md +388 -0
  82. package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
  83. package/rules/js-run/lib/docs/check-env-scan.md +433 -0
  84. package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
  85. package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
  86. package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
  87. package/rules/k8s/docs/fix.md +129 -0
  88. package/rules/k8s/js/docs/manifests.md +344 -0
  89. package/rules/k8s/js/manifests.mjs +6 -2
  90. package/rules/k8s/k8s.mdc +4 -2
  91. package/rules/k8s/lint/docs/lint.md +411 -0
  92. package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
  93. package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
  94. package/rules/nginx-default-tpl/docs/fix.md +124 -0
  95. package/rules/nginx-default-tpl/js/docs/template.md +378 -0
  96. package/rules/npm-module/docs/fix.md +98 -0
  97. package/rules/npm-module/js/docs/package_structure.md +274 -0
  98. package/rules/npm-module/js/docs/rule_meta.md +137 -0
  99. package/rules/npm-module/js/docs/skill_meta.md +190 -0
  100. package/rules/php/docs/fix.md +107 -0
  101. package/rules/php/js/docs/tooling.md +152 -0
  102. package/rules/php/lint/docs/lint.md +215 -0
  103. package/rules/python/docs/fix.md +163 -0
  104. package/rules/python/js/docs/applies.md +108 -0
  105. package/rules/python/js/docs/tooling.md +153 -0
  106. package/rules/python/lint/docs/lint.md +322 -0
  107. package/rules/rego/docs/fix.md +121 -0
  108. package/rules/rego/js/docs/applies.md +174 -0
  109. package/rules/rego/js/docs/lint.md +118 -0
  110. package/rules/rego/lint/docs/lint.md +204 -0
  111. package/rules/release/docs/change.md +185 -0
  112. package/rules/release/docs/fix.md +119 -0
  113. package/rules/release/docs/release.md +222 -0
  114. package/rules/release/lib/docs/aggregate.md +246 -0
  115. package/rules/release/lib/docs/change-file.md +200 -0
  116. package/rules/release/lib/docs/fallback.md +203 -0
  117. package/rules/rust/docs/fix.md +129 -0
  118. package/rules/rust/js/docs/applies.md +140 -0
  119. package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
  120. package/rules/security/docs/fix.md +86 -0
  121. package/rules/security/js/docs/lint.md +171 -0
  122. package/rules/security/js/docs/sample_secret.md +190 -0
  123. package/rules/security/js/docs/trufflehog.md +137 -0
  124. package/rules/security/js/lint.mjs +9 -1
  125. package/rules/style-lint/docs/fix.md +155 -0
  126. package/rules/style-lint/js/docs/lint.md +184 -0
  127. package/rules/style-lint/js/docs/tooling.md +194 -0
  128. package/rules/tauri/docs/fix.md +158 -0
  129. package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
  130. package/rules/tauri/js/docs/tooling.md +228 -0
  131. package/rules/test/coverage/coverage.mjs +15 -3
  132. package/rules/test/docs/fix.md +132 -0
  133. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
  134. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
  135. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
  136. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
  137. package/rules/test/js/docs/cargo_mutants_config.md +173 -0
  138. package/rules/test/js/docs/location.md +136 -0
  139. package/rules/test/js/docs/no-process-chdir.md +160 -0
  140. package/rules/test/js/docs/no-relative-fs-path.md +271 -0
  141. package/rules/test/js/docs/stryker_config.md +152 -0
  142. package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
  143. package/rules/text/docs/fix.md +118 -0
  144. package/rules/text/js/docs/forbidden-prettier.md +143 -0
  145. package/rules/text/js/docs/formatting.md +256 -0
  146. package/rules/text/js/docs/lint.md +122 -0
  147. package/rules/text/lint/docs/lint.md +220 -0
  148. package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
  149. package/rules/text/lint/docs/run-shellcheck.md +212 -0
  150. package/rules/text/lint/docs/run-v8r.md +197 -0
  151. package/rules/vue/docs/fix.md +127 -0
  152. package/rules/vue/js/docs/packages.md +335 -0
  153. package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
  154. package/rules/worktree/docs/fix.md +161 -0
  155. package/schemas/rule-meta.json +5 -1
  156. package/scripts/auto-rules.mjs +7 -4
  157. package/scripts/coverage-classify/docs/apply.md +202 -0
  158. package/scripts/coverage-classify/docs/cache.md +203 -0
  159. package/scripts/coverage-classify/docs/index.md +218 -0
  160. package/scripts/coverage-classify/docs/prompt.md +132 -0
  161. package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
  162. package/scripts/coverage-fix-extract.mjs +122 -0
  163. package/scripts/coverage-fix.mjs +1 -1
  164. package/scripts/dispatcher/docs/graph.md +346 -0
  165. package/scripts/dispatcher/docs/index.md +236 -0
  166. package/scripts/dispatcher/docs/trace.md +296 -0
  167. package/scripts/dispatcher/index.mjs +1 -1
  168. package/scripts/dispatcher/lib/active.mjs +4 -8
  169. package/scripts/dispatcher/lib/commands.mjs +7 -11
  170. package/scripts/dispatcher/lib/docs/active.md +348 -0
  171. package/scripts/dispatcher/lib/docs/artifact.md +232 -0
  172. package/scripts/dispatcher/lib/docs/budget.md +167 -0
  173. package/scripts/dispatcher/lib/docs/capability.md +196 -0
  174. package/scripts/dispatcher/lib/docs/commands.md +210 -0
  175. package/scripts/dispatcher/lib/docs/events.md +182 -0
  176. package/scripts/dispatcher/lib/docs/executor.md +190 -0
  177. package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
  178. package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
  179. package/scripts/dispatcher/lib/docs/gate.md +231 -0
  180. package/scripts/dispatcher/lib/docs/level.md +335 -0
  181. package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
  182. package/scripts/dispatcher/lib/docs/plan.md +200 -0
  183. package/scripts/dispatcher/lib/docs/planner.md +269 -0
  184. package/scripts/dispatcher/lib/docs/review.md +255 -0
  185. package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
  186. package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
  187. package/scripts/dispatcher/lib/docs/spec.md +203 -0
  188. package/scripts/dispatcher/lib/docs/state-store.md +303 -0
  189. package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
  190. package/scripts/dispatcher/lib/executor.mjs +6 -1
  191. package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
  192. package/scripts/dispatcher/lib/level.mjs +29 -3
  193. package/scripts/dispatcher/lib/review.mjs +1 -1
  194. package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
  195. package/scripts/docs/auto-rules.md +376 -0
  196. package/scripts/docs/auto-skills.md +173 -0
  197. package/scripts/docs/build-agents-commands.md +183 -0
  198. package/scripts/docs/cli-entry.md +153 -0
  199. package/scripts/docs/coverage-fix.md +177 -0
  200. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
  201. package/scripts/lib/changed-files.mjs +4 -1
  202. package/scripts/lib/diff-added-lines.mjs +85 -0
  203. package/scripts/lib/docs/changed-files.md +149 -0
  204. package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
  205. package/scripts/lib/docs/check-reporter.md +175 -0
  206. package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
  207. package/scripts/lib/docs/discover-checkable-rules.md +165 -0
  208. package/scripts/lib/docs/ensure-tool.md +254 -0
  209. package/scripts/lib/docs/generated-markdown.md +275 -0
  210. package/scripts/lib/docs/gha-workflow.md +326 -0
  211. package/scripts/lib/docs/inline-template-links.md +303 -0
  212. package/scripts/lib/docs/list-rule-ids.md +156 -0
  213. package/scripts/lib/docs/load-cursor-config.md +147 -0
  214. package/scripts/lib/docs/mirror-parity.md +167 -0
  215. package/scripts/lib/worktree.mjs +26 -0
  216. package/scripts/worktree-cli.mjs +12 -2
  217. package/skills/coverage-fix/SKILL.md +34 -45
  218. package/skills/docgen/SKILL.md +44 -23
  219. package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
  220. package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
  221. package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
  222. package/skills/docgen/js/docgen-ignore.mjs +54 -0
  223. package/skills/docgen/js/docgen-scan.mjs +37 -21
  224. package/skills/llm-patch/SKILL.md +23 -2
  225. package/skills/start-check/SKILL.md +26 -53
  226. package/skills/start-check/js/check.mjs +211 -0
  227. package/skills/taze/SKILL.md +9 -3
  228. package/skills/taze/js/diff.mjs +154 -0
  229. package/types/bin/n-cursor.d.ts +1 -1
  230. package/skills/fix-tests/SKILL.md +0 -119
  231. package/skills/fix-tests/meta.json +0 -1
@@ -0,0 +1,261 @@
1
+ # vue-forbidden-imports.mjs
2
+
3
+ ## Огляд
4
+
5
+ Модуль `vue-forbidden-imports.mjs` — це бібліотека статичного аналізу `import`-декларацій, призначена для виявлення двох категорій порушень у вихідному коді Vue-проєкту:
6
+
7
+ 1. **Явні (runtime) імпорти з модуля `vue`** у будь-яких файлах, що сканує правило. За конвенцією `vue.mdc` у проєкті працює `unplugin-auto-import`, тому імпорти `ref`, `computed`, `watch` тощо мають бути неявними. Дозволено лише: side-effect форму (`import 'vue'`), повністю type-only імпорти (`import type { ... } from 'vue'`) та змішані форми, де **всі** іменовані записи мають флаг `isType` (наприклад, `import { type A, type B } from 'vue'`).
8
+ 2. **Імпорти Node-нативних модулів усередині `.vue` SFC** — `node:fs`, `node:timers/promises`, а також bare-форми вбудованих модулів (`fs`, `path`, `crypto`, `fs/promises` тощо). Vue Single-File Components виконуються в браузерному середовищі, де Node API недоступне, тому такі імпорти зривають збірку чи призводять до runtime-помилок.
9
+
10
+ Аналіз виконується через **oxc-parser** (`parseSync`) — ESTree-сумісний AST-парсер, що повертає об'єкт із полем `module.staticImports`. Це усуває потребу в крихких регулярних виразах для розпізнавання структури імпортів і коректно обробляє TypeScript-синтаксис (включно з type-only записами через флаг `entries[].isType`).
11
+
12
+ Для `.vue` файлів виконується попередній етап: регулярним виразом витягуються вмісти всіх тегів `<script>` / `<script setup>` (template ігнорується), і вже цей конкатенований код подається парсеру з віртуальним ім'ям `*.ts`, щоб увімкнути TypeScript-режим.
13
+
14
+ Модуль чисто функціональний: жодних звернень до файлової системи, мережі чи глобального стану — усі функції приймають уже прочитаний контент і повертають структуровані дані про порушення.
15
+
16
+ ## Експорти / API
17
+
18
+ Усі експорти — іменовані (named exports), default export відсутній.
19
+
20
+ | Експорт | Тип | Призначення |
21
+ | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------ |
22
+ | `extractVueScriptBlocks(sfc)` | function | Витягує конкатенований код усіх `<script>` блоків з `.vue` SFC. |
23
+ | `contentForVueImportScan(content, filePath)` | function | Повертає текст для сканування: для `.vue` — лише script-блоки, інакше — увесь вміст. |
24
+ | `findForbiddenVueImportsInText(content, virtualPath?)` | function | Знаходить заборонені static-імпорти з `vue` у вже підготовленому тексті. |
25
+ | `shouldSkipFileForVueImportScan(relativePosix)` | function | Чи пропустити файл під час обходу пакета (генерація, `.d.ts`). |
26
+ | `isVueImportScanSourceFile(relativePath)` | function | Чи розширення файлу підходить для сканування. |
27
+ | `findForbiddenVueImportsInSourceFile(content, relativePath)` | function | Об'єднує підготовку контенту та парсинг для одного файлу. |
28
+ | `isNodeBuiltinSpecifier(spec)` | function | Чи специфікатор імпорту відповідає Node-нативному модулю. |
29
+ | `findForbiddenNodeImportsInText(content, virtualPath?)` | function | Знаходить заборонені Node-імпорти у тексті. |
30
+ | `findForbiddenNodeImportsInVueFile(content, relativePath)` | function | Знаходить заборонені Node-імпорти лише у `.vue` файлах (template ігнорується). |
31
+
32
+ Внутрішні (не експортовані) допоміжні функції: `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isAllowedVueStaticImport`, `virtualPathForParse`.
33
+
34
+ Внутрішні (не експортовані) константи:
35
+
36
+ - `NODE_BUILTIN_MODULES` — `Set<string>` з повного списку `builtinModules` Node.js на момент запуску (наприклад, `fs`, `path`, `crypto`, ...).
37
+ - `VUE_EXT_RE` — регекс `/\.vue$/u` для перевірки розширення `.vue`.
38
+ - `SOURCE_FILE_RE` — регекс `/\.(vue|[cm]?[jt]sx?)$/`, що покриває `.vue`, `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`.
39
+
40
+ ## Функції
41
+
42
+ ### `langFromPath(filePath)` — внутрішня
43
+
44
+ - **Сигнатура:** `(filePath: string) => 'js' | 'jsx' | 'ts' | 'tsx'`
45
+ - **Параметри:**
46
+ - `filePath` — віртуальний або реальний шлях; розпізнавання йде за суфіксом у lowercase.
47
+ - **Повертає:** значення опції `lang` для `parseSync`. Розширення `.ts`, `.mts`, `.cts` → `'ts'`; `.tsx` → `'tsx'`; `.jsx` → `'jsx'`; решта (включно з `.js`, `.mjs`, `.cjs`, відсутність розширення) → `'js'`.
48
+ - **Side effects:** немає.
49
+
50
+ ### `offsetToLine(content, offset)` — внутрішня
51
+
52
+ - **Сигнатура:** `(content: string, offset: number) => number`
53
+ - **Параметри:**
54
+ - `content` — повний текст файлу.
55
+ - `offset` — байтове зміщення (точніше — індекс кодової одиниці UTF-16) початку фрагмента.
56
+ - **Повертає:** 1-based номер рядка для зміщення. Алгоритм лінійно проходить діапазон `[0, min(offset, content.length))` і збільшує лічильник для кожного `\n` (code point `10`).
57
+ - **Side effects:** немає.
58
+
59
+ ### `normalizeSnippet(s)` — внутрішня
60
+
61
+ - **Сигнатура:** `(s: string) => string`
62
+ - **Параметри:**
63
+ - `s` — довільний фрагмент коду.
64
+ - **Повертає:** однорядковий рядок: усі послідовності whitespace замінено одним пробілом, обрізано початкові/кінцеві пробіли, обмежено перші 160 символів.
65
+ - **Side effects:** немає.
66
+
67
+ ### `isAllowedVueStaticImport(imp)` — внутрішня
68
+
69
+ - **Сигнатура:** `(imp: { moduleRequest: { value: string }, entries: { isType: boolean }[] }) => boolean`
70
+ - **Параметри:**
71
+ - `imp` — один запис із масиву `module.staticImports`, отриманого від `parseSync`.
72
+ - **Повертає:** `true`, якщо:
73
+ - `entries.length === 0` (це `import 'vue'` — side-effect форма), **або**
74
+ - усі `entries` мають `isType === true` (повністю type-only або змішана форма з `import { type X } from 'vue'`).
75
+ - **Side effects:** немає.
76
+
77
+ ### `extractVueScriptBlocks(sfc)` — експортована
78
+
79
+ - **Сигнатура:** `(sfc: string) => string`
80
+ - **Параметри:**
81
+ - `sfc` — повний вміст `.vue` файлу.
82
+ - **Повертає:** конкатенацію (роздільник `\n\n`) тіл усіх знайдених `<script>` блоків. Регекс `/<script\b[^>]*>([\s\S]*?)<\/script>/gi` дозволяє атрибути після `<script` (наприклад, `setup`, `lang="ts"`, `generic="T"`) і не чіпає шаблон `<template>` чи стилі `<style>`.
83
+ - **Side effects:** немає.
84
+
85
+ ### `contentForVueImportScan(content, filePath)` — експортована
86
+
87
+ - **Сигнатура:** `(content: string, filePath: string) => string`
88
+ - **Параметри:**
89
+ - `content` — сирий вміст файлу.
90
+ - `filePath` — шлях, потрібний лише для перевірки суфікса `.vue`.
91
+ - **Повертає:** для `.vue` — результат `extractVueScriptBlocks(content)`; інакше — `content` без змін.
92
+ - **Side effects:** немає.
93
+
94
+ ### `virtualPathForParse(relativePath)` — внутрішня
95
+
96
+ - **Сигнатура:** `(relativePath: string) => string`
97
+ - **Параметри:**
98
+ - `relativePath` — шлях файлу в пакеті/репо.
99
+ - **Повертає:** якщо суфікс `.vue` — той самий шлях із заміною `.vue` на `.ts` (для `langFromPath` → `'ts'`); інакше — шлях без змін.
100
+ - **Side effects:** немає.
101
+
102
+ ### `findForbiddenVueImportsInText(content, virtualPath?)` — експортована
103
+
104
+ - **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string }[]`
105
+ - **Параметри:**
106
+ - `content` — вже підготовлений текст для парсингу (для `.vue` — лише `<script>` блоки).
107
+ - `virtualPath` — необов'язковий шлях для вибору `lang`. Значення за замовчуванням — `'scan.ts'` (TypeScript-режим). Якщо передано порожнє/falsy значення — використовується той самий fallback `'scan.ts'`.
108
+ - **Повертає:** масив об'єктів `{ line, snippet }`, де:
109
+ - `line` — 1-based номер рядка, де починається `import`,
110
+ - `snippet` — стиснений однорядковий фрагмент тексту `content.slice(imp.start, imp.end)` (≤ 160 символів).
111
+ - **Поведінка:**
112
+ - У разі будь-якої exception з `parseSync` (`try/catch`) повертає `[]`.
113
+ - Якщо `result.errors?.length > 0` — повертає `[]` (тобто за наявності синтаксичних помилок порушення не репортяться; коментар у файлі прямо радить «спочатку виправ синтаксис»).
114
+ - Інакше проходить `result.module.staticImports` і додає в результат запис, якщо `moduleRequest.value === 'vue'` **і** `!isAllowedVueStaticImport(imp)`.
115
+ - **Side effects:** немає (читання Node-builtins трапилось лише на старті модуля).
116
+
117
+ ### `shouldSkipFileForVueImportScan(relativePosix)` — експортована
118
+
119
+ - **Сигнатура:** `(relativePosix: string) => boolean`
120
+ - **Параметри:**
121
+ - `relativePosix` — шлях файлу з posix-слешами (форвард-слеш як роздільник).
122
+ - **Повертає:** `true`, якщо:
123
+ - basename дорівнює `auto-imports.d.ts` або `components.d.ts` (типові згенеровані файли від `unplugin-auto-import` / `unplugin-vue-components`), **або**
124
+ - шлях закінчується на `.d.ts` (будь-який type-declaration файл).
125
+ - **Side effects:** немає.
126
+
127
+ ### `isVueImportScanSourceFile(relativePath)` — експортована
128
+
129
+ - **Сигнатура:** `(relativePath: string) => boolean`
130
+ - **Параметри:**
131
+ - `relativePath` — відносний шлях.
132
+ - **Повертає:** `true`, якщо суфікс файлу відповідає `SOURCE_FILE_RE` (тобто `.vue`, `.js`, `.cjs`, `.mjs`, `.jsx`, `.ts`, `.cts`, `.mts`, `.tsx`).
133
+ - **Side effects:** немає.
134
+
135
+ ### `findForbiddenVueImportsInSourceFile(content, relativePath)` — експортована
136
+
137
+ - **Сигнатура:** `(content: string, relativePath: string) => { line: number, snippet: string }[]`
138
+ - **Параметри:**
139
+ - `content` — сирий вміст файлу.
140
+ - `relativePath` — шлях відносно кореня пакета чи репо.
141
+ - **Повертає:** результат `findForbiddenVueImportsInText(scan, virtualPath)`, де:
142
+ - `scan = contentForVueImportScan(content, relativePath)` — для `.vue` витягнуті `<script>` блоки, для решти — `content`;
143
+ - `virtualPath = virtualPathForParse(relativePath)` — для `.vue` замінено суфікс на `.ts`, для решти — без змін.
144
+ - **Side effects:** немає.
145
+
146
+ ### `isNodeBuiltinSpecifier(spec)` — експортована
147
+
148
+ - **Сигнатура:** `(spec: string) => boolean`
149
+ - **Параметри:**
150
+ - `spec` — значення `moduleRequest.value` (текст специфікатора імпорту).
151
+ - **Повертає:** `true`, якщо специфікатор — Node-нативний модуль. Послідовність перевірок:
152
+ 1. Якщо `spec` не рядок або порожній → `false`.
153
+ 2. Префікс `node:` → `true` (покриває `node:fs`, `node:timers/promises`, `node:test` тощо).
154
+ 3. Точне співпадіння в `NODE_BUILTIN_MODULES` → `true` (наприклад, `fs`, `path`, `crypto`).
155
+ 4. Якщо є слеш на позиції > 0 — перевірити «head» (`spec.slice(0, slashIdx)`); якщо head у `NODE_BUILTIN_MODULES` → `true` (покриває підшляхи на кшталт `fs/promises`, `stream/web`, `timers/promises`).
156
+ 5. Інакше → `false`.
157
+ - **Side effects:** немає.
158
+
159
+ ### `findForbiddenNodeImportsInText(content, virtualPath?)` — експортована
160
+
161
+ - **Сигнатура:** `(content: string, virtualPath?: string) => { line: number, snippet: string, specifier: string }[]`
162
+ - **Параметри:**
163
+ - `content` — підготовлений текст (для `.vue` — `<script>` блоки).
164
+ - `virtualPath` — шлях для вибору `lang`. Default — `'scan.ts'`; при falsy використовується той самий fallback.
165
+ - **Повертає:** масив `{ line, snippet, specifier }`. Структура `line` / `snippet` ідентична `findForbiddenVueImportsInText`; поле `specifier` — це сирий рядок з `imp.moduleRequest.value` (наприклад, `'node:fs'` або `'fs/promises'`).
166
+ - **Поведінка:**
167
+ - Парсинг через `parseSync(pathForLang, content, { lang, sourceType: 'module' })` всередині `try/catch` — будь-яка exception → `[]`.
168
+ - Якщо `result.errors?.length > 0` → `[]`.
169
+ - Для кожного `imp` із `result.module.staticImports` перевіряється `isNodeBuiltinSpecifier(spec)`; при `true` додається запис у результат.
170
+ - Зверніть увагу: правило репортить **усі** Node-імпорти у Vue-контексті, у тому числі type-only (`import type { Stats } from 'fs'`) — коментар у вихідному коді пояснює, що type-only імпорти Node-модулів у SFC заплутують і доцільніше тримати такий код у server-side утилітах.
171
+ - **Side effects:** немає.
172
+
173
+ ### `findForbiddenNodeImportsInVueFile(content, relativePath)` — експортована
174
+
175
+ - **Сигнатура:** `(content: string, relativePath: string) => { line: number, snippet: string, specifier: string }[]`
176
+ - **Параметри:**
177
+ - `content` — сирий вміст файлу.
178
+ - `relativePath` — шлях відносно кореня пакета чи репо.
179
+ - **Повертає:**
180
+ - Якщо суфікс не `.vue` → `[]` (правило стосується лише SFC; композаблі та утиліти на Node-side можуть жити у `.ts`/`.js`).
181
+ - Інакше: `findForbiddenNodeImportsInText(extractVueScriptBlocks(content), virtualPathForParse(relativePath))`.
182
+ - **Side effects:** немає.
183
+
184
+ ## Залежності
185
+
186
+ ### Зовнішні модулі
187
+
188
+ - **`node:module` (Node.js builtin)** — імпортується `builtinModules` для формування `NODE_BUILTIN_MODULES`. Список фіксується на момент запуску модуля (одноразово).
189
+ - **`oxc-parser`** — `parseSync(filename, source, options)`. Очікувані опції:
190
+ - `lang: 'js' | 'jsx' | 'ts' | 'tsx'` — обирається `langFromPath`;
191
+ - `sourceType: 'module'` — фіксовано як ES-module.
192
+ - Результат використовується через:
193
+ - `result.errors` — масив синтаксичних помилок; за наявності повертається `[]`;
194
+ - `result.module.staticImports` — масив записів виду `{ moduleRequest: { value }, entries: [{ isType }], start, end }`.
195
+
196
+ ### Внутрішні залежності між функціями цього файлу
197
+
198
+ - `findForbiddenVueImportsInSourceFile` → `contentForVueImportScan`, `virtualPathForParse`, `findForbiddenVueImportsInText`.
199
+ - `findForbiddenVueImportsInText` → `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isAllowedVueStaticImport`.
200
+ - `findForbiddenNodeImportsInVueFile` → `extractVueScriptBlocks`, `virtualPathForParse`, `findForbiddenNodeImportsInText`.
201
+ - `findForbiddenNodeImportsInText` → `langFromPath`, `offsetToLine`, `normalizeSnippet`, `isNodeBuiltinSpecifier`.
202
+ - `contentForVueImportScan` → `extractVueScriptBlocks`.
203
+ - `isNodeBuiltinSpecifier` → `NODE_BUILTIN_MODULES`.
204
+
205
+ ### Зовнішні споживачі (за конвенцією)
206
+
207
+ Файл лежить у `npm/rules/vue/lib/` і призначений для використання з `check-*.mjs` сценаріїв правила `n-vue`-родини. Сценарії-перевірки самі обходять пакети, читають файли з диска, фільтрують їх через `shouldSkipFileForVueImportScan` та `isVueImportScanSourceFile`, і викликають `findForbiddenVueImportsInSourceFile` / `findForbiddenNodeImportsInVueFile`.
208
+
209
+ ## Потік виконання / Використання
210
+
211
+ ### Типовий потік для сканера-перевірки
212
+
213
+ 1. Сценарій-перевірка обходить файли в пакеті, отримуючи `(relativePath, absolutePath)`.
214
+ 2. Фільтрація:
215
+ - `if (shouldSkipFileForVueImportScan(relativePosix)) continue;` — пропустити `.d.ts` і згенеровані файли.
216
+ - `if (!isVueImportScanSourceFile(relativePath)) continue;` — пропустити не-source файли (скажімо, `.json`, `.md`).
217
+ 3. Прочитати `content = fs.readFileSync(absolutePath, 'utf8')`.
218
+ 4. Знайти порушення:
219
+ - `const vueViolations = findForbiddenVueImportsInSourceFile(content, relativePath);`
220
+ - `const nodeViolations = findForbiddenNodeImportsInVueFile(content, relativePath);`
221
+ 5. Якщо масиви непорожні — зрепортити користувачу: `relativePath:${line}` + `snippet` (+ `specifier` для Node-порушень).
222
+
223
+ ### Точкове використання
224
+
225
+ - **Тільки сирий код (без файлів):** виклик `findForbiddenVueImportsInText(code, 'scan.ts')` або `findForbiddenNodeImportsInText(code, 'scan.ts')` напряму — корисно для unit-тестів модуля.
226
+ - **Лише витяг `<script>` блоків:** `extractVueScriptBlocks(sfc)` повертає конкатенований код; зручно для інших сканерів, що працюють лише з JS/TS-частиною SFC.
227
+
228
+ ### Контракти й тонкі моменти
229
+
230
+ - **Помилки парсингу = «не репортимо».** Якщо `parseSync` кинув exception **або** повернув `result.errors.length > 0`, обидві `find*InText` функції повертають `[]`. Це навмисний дизайн: правило не намагається лагодити синтаксис — спочатку файл має бути коректним, тоді сканер дасть осмислений вихід.
231
+ - **Default `virtualPath = 'scan.ts'`.** Без передачі `virtualPath` режим парсингу — TypeScript. Це безпечно і для чистого JS (TS-парсер приймає JS-синтаксис), і дозволяє type-only синтаксис.
232
+ - **Type-only синтаксис у `vue` дозволено.** `import type { Ref } from 'vue'` і `import { type Ref } from 'vue'` пройдуть перевірку через `entries[].isType === true`. Це не суперечить ідеї auto-import: типи завжди потрібно імпортувати явно.
233
+ - **Type-only синтаксис у Node-імпортах НЕ дозволено.** Для `.vue` навіть `import type { Stats } from 'fs'` буде в результаті — модуль не розглядає `isType` для Node-перевірки.
234
+ - **`offsetToLine` рахує `\n` посимвольно.** Складність `O(offset)`. На великих файлах із багатьма імпортами це сумарно `O(N·M)`, але на практиці прийнятно (файли SFC рідко > 10k рядків).
235
+ - **`normalizeSnippet` обмежує 160 символів** і стискає whitespace — формат повідомлення про порушення стабільний, незалежно від форматування у коді.
236
+
237
+ ## Rebuild Test
238
+
239
+ За цією документацією має бути можливо відновити функціональний еквівалент модуля. Контрольні точки відтворення:
240
+
241
+ - **Експорти:** `extractVueScriptBlocks`, `contentForVueImportScan`, `findForbiddenVueImportsInText`, `shouldSkipFileForVueImportScan`, `isVueImportScanSourceFile`, `findForbiddenVueImportsInSourceFile`, `isNodeBuiltinSpecifier`, `findForbiddenNodeImportsInText`, `findForbiddenNodeImportsInVueFile` — усі named, default відсутній.
242
+ - **Регулярні вирази:**
243
+ - `<script>` екстрактор: `/<script\b[^>]*>([\s\S]*?)<\/script>/gi`.
244
+ - `.vue` суфікс: `/\.vue$/u`.
245
+ - Source files: `/\.(vue|[cm]?[jt]sx?)$/`.
246
+ - **Skip-файли:** basename `auto-imports.d.ts` / `components.d.ts` або суфікс `.d.ts`.
247
+ - **`langFromPath` мапінг:** `.tsx`→`tsx`; `.ts|.mts|.cts`→`ts`; `.jsx`→`jsx`; default→`js` (через lowercase порівняння суфіксів).
248
+ - **Vue allow-list:** порожній `entries` (side-effect `import 'vue'`) або `entries.every(e => e.isType)`.
249
+ - **Node-builtin перевірка:**
250
+ 1. Не рядок або порожній → `false`.
251
+ 2. `startsWith('node:')` → `true`.
252
+ 3. У `Set(builtinModules)` → `true`.
253
+ 4. Якщо є слеш (індекс > 0) і head у Set → `true`.
254
+ 5. Інакше → `false`.
255
+ - **Парсер:** `parseSync(virtualPath, content, { lang, sourceType: 'module' })`; обробка `try/catch` + перевірка `result.errors?.length`.
256
+ - **Результат-формат:** Vue-порушення — `{ line, snippet }`; Node-порушення — `{ line, snippet, specifier }`.
257
+ - **Default `virtualPath`:** `'scan.ts'` (включно з fallback при falsy значенні).
258
+ - **`findForbiddenNodeImportsInVueFile` гарантія:** для не-`.vue` повертає `[]` навіть якщо файл містить Node-імпорти.
259
+ - **`virtualPathForParse`:** `.vue` → той самий шлях з суфіксом `.ts`; інакше — без змін.
260
+ - **Snippet:** `replaceAll(/\s+/g, ' ').trim().slice(0, 160)`.
261
+ - **`offsetToLine`:** 1-based, лічить `\n` (code point 10) у діапазоні `[0, min(offset, length))`.
@@ -0,0 +1,161 @@
1
+ # fix.mjs — точка входу правила `worktree` (fix)
2
+
3
+ ## Огляд
4
+
5
+ Файл `npm/rules/worktree/fix.mjs` — це **точка входу правила `worktree`** для пакета `@nitra/cursor`. Він реалізує **дві ролі одночасно** (dual role):
6
+
7
+ 1. **Library mode** — експортує функцію `run(ctx)`, яку CLI-оркестратор `@nitra/cursor` (або інший runner) викликає через `import { run } from '.../fix.mjs'` для запуску правила в межах батч-прогону всіх правил.
8
+ 2. **Standalone mode** — якщо файл запускається напряму через `bun npm/rules/worktree/fix.mjs`, він самостійно ініціалізує CLI-обгортку (config-loading, whitelist, summary) і завершує процес `exit-code`-ом, придатним для CI/IDE.
9
+
10
+ Сам файл **не містить власної логіки перевірки** — він лише делегує виконання стандартному раннеру `runStandardRule`, який послідовно виконує підкроки правила в наступному порядку:
11
+
12
+ - **applies** — детектор, чи застосовне правило до конкретного файлу/директорії;
13
+ - **JS-concerns** — JS-перевірки (зокрема `check-*.mjs` у директорії правила);
14
+ - **policy** — політики/декларативні перевірки;
15
+ - **mdc-refs** — перевірка посилань у відповідному `.mdc`-документі правила.
16
+
17
+ Каталог `worktree/` стосується доменного правила про **git worktree** (див. `.cursor/rules/n-worktree.mdc`) — конвенцій ізольованих робочих дерев у `.worktrees/<branch>/` та інвентарних описів. Сам `fix.mjs` не реалізує цих перевірок безпосередньо — він лише диспетчеризує їх через стандартний пайплайн.
18
+
19
+ ## Експорти / API
20
+
21
+ | Експорт | Тип | Призначення |
22
+ | ------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `run` | `function (ctx?: RuleContext): Promise<number>` | Library-entry. Запускає стандартний пайплайн правила для директорії, в якій знаходиться `fix.mjs`. Повертає **exit-code**: `0` — порушень немає, `1` — є порушення. |
24
+
25
+ Файл також містить **side-effect блок** (виконується тільки при прямому запуску як CLI), який не є експортом, але є частиною контракту:
26
+
27
+ - При `isRunAsCli(import.meta.url) === true` модуль викликає `runRuleCli(import.meta.dirname)` і завершує процес `process.exit(<code>)`.
28
+
29
+ ## Функції
30
+
31
+ ### `run(ctx)`
32
+
33
+ **Сигнатура:**
34
+
35
+ ```js
36
+ export function run(ctx)
37
+ ```
38
+
39
+ **Параметри:**
40
+
41
+ | Параметр | Тип | Обов'язковий | Опис |
42
+ | -------- | ----------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | `ctx` | `RuleContext` (з `../../scripts/lib/run-standard-rule.mjs`) | Ні | Контекст прогону правила. Передається оркестратором із `@nitra/cursor` і містить, зокрема, спільні структури на кшталт `walkCache` (кеш обходу файлової системи між кількома правилами в одному прогоні). Якщо не передано — раннер створить дефолтний контекст. |
44
+
45
+ **Повертає:** `Promise<number>` — exit-code:
46
+
47
+ - `0` — правило виконалося без порушень;
48
+ - `1` — знайдено порушення (інтерпретація залежить від `runStandardRule`).
49
+
50
+ **Side effects:**
51
+
52
+ - Сама `run` не пише в `stdout` напряму та не змінює FS — усі побічні ефекти інкапсульовані в `runStandardRule` (форматований вивід summary, потенційне читання `.mdc`/`check-*.mjs`-файлів сусідньої директорії, обхід проєктних файлів через `walkCache`).
53
+ - `run` **не** викликає `process.exit` — це відповідальність standalone-блоку нижче.
54
+
55
+ **Поведінкова ідіома:**
56
+
57
+ `run` — це тонкий **прокладочний шар**: він фіксує **директорію** правила (`import.meta.dirname`), бо саме за нею раннер визначає `id` правила (ім'я каталогу `worktree`), знаходить сусідні файли (`policy.mjs`, `applies.mjs`, `check-*.mjs`, `<id>.mdc`) та збирає пайплайн.
58
+
59
+ ### Standalone-блок (top-level)
60
+
61
+ Анонімний side-effect блок, що виконується лише при прямому запуску:
62
+
63
+ ```js
64
+ if (isRunAsCli(import.meta.url)) {
65
+ process.exit(await runRuleCli(import.meta.dirname))
66
+ }
67
+ ```
68
+
69
+ **Поведінка:**
70
+
71
+ - `isRunAsCli(import.meta.url)` повертає `true`, якщо модуль є **головним** entry-point процесу (а не імпортований). Це типова заміна паттерну `require.main === module` для ESM.
72
+ - `runRuleCli(import.meta.dirname)` — повна CLI-обгортка над `run`: завантажує проєктний config, застосовує whitelist (наприклад, виключення з `.cursorignore`/конфігу), друкує summary після виконання та повертає підсумковий exit-code.
73
+ - `process.exit(code)` — закриває процес з кодом, придатним для CI/IDE. Лінт-винятки `n/no-process-exit` та `unicorn/no-process-exit` свідомо вимкнені коментарем, бо standalone entry-point має лагідно виходити з конкретним кодом, інакше CI не отримає сигналу про fail.
74
+
75
+ ## Залежності
76
+
77
+ Внутрішні залежності (відносні до `npm/scripts/lib/`):
78
+
79
+ | Імпорт | З файлу | Призначення |
80
+ | ----------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
81
+ | `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Перевіряє, чи поточний модуль є головним процесом (заміна `require.main === module` для ESM). |
82
+ | `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Standalone-обгортка: config-loading + whitelist + summary + exit-code. Еквівалент `npx @nitra/cursor fix <id>` для одного правила. |
83
+ | `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Стандартний пайплайн правила: applies → JS-concerns → policy → mdc-refs. Приймає директорію правила та опційний `RuleContext`. |
84
+
85
+ Зовнішні залежності та глобальні API:
86
+
87
+ - `import.meta.dirname` — ESM-аналог `__dirname`; використовується для передачі шляху до каталогу правила раннерам.
88
+ - `import.meta.url` — використовується `isRunAsCli` для визначення головного модуля.
89
+ - `process.exit(code)` — Node.js/Bun runtime API для встановлення exit-code.
90
+
91
+ Файл **не залежить** напряму від конкретних `check-*.mjs`, `policy.mjs`, `<id>.mdc` сусідньої директорії — їх відкриває та інтерпретує `runStandardRule`.
92
+
93
+ ## Потік виконання / Використання
94
+
95
+ ### Сценарій A — Library mode (виклик з оркестратора)
96
+
97
+ ```js
98
+ import { run } from '@nitra/cursor/rules/worktree/fix.mjs'
99
+
100
+ const ctx = { walkCache: new Map(/* ... */) }
101
+ const exitCode = await run(ctx)
102
+ // exitCode === 0 — OK, 1 — порушення
103
+ ```
104
+
105
+ Послідовність:
106
+
107
+ 1. Оркестратор (`@nitra/cursor fix` без аргументів або з переліком правил) проходить по списку правил і для кожного робить `import('.../fix.mjs')`.
108
+ 2. Викликає `run(sharedCtx)`, де `sharedCtx.walkCache` спільний для всіх правил у прогоні (економить FS-обходи).
109
+ 3. `run` делегує в `runStandardRule(import.meta.dirname, ctx)`.
110
+ 4. `runStandardRule` послідовно виконує під-кроки правила `worktree`: `applies` → JS-перевірки → `policy` → `mdc-refs`.
111
+ 5. Повертається `number` (0/1), оркестратор агрегує результати всіх правил у фінальний summary.
112
+
113
+ Жодних `process.exit` у цьому сценарії — control flow залишається в оркестратора.
114
+
115
+ ### Сценарій B — Standalone mode (прямий запуск)
116
+
117
+ ```bash
118
+ bun npm/rules/worktree/fix.mjs
119
+ # або (еквівалент)
120
+ npx @nitra/cursor fix worktree
121
+ ```
122
+
123
+ Послідовність:
124
+
125
+ 1. Bun завантажує файл; `import.meta.url` вказує на сам файл як головний entry-point.
126
+ 2. `isRunAsCli(import.meta.url)` повертає `true`.
127
+ 3. Виконується `await runRuleCli(import.meta.dirname)`:
128
+ - читає проєктний config (`.cursor`/`package.json`);
129
+ - застосовує whitelist (включення/виключення файлів);
130
+ - формує `RuleContext` та викликає `run(ctx)` (опосередковано, через стандартний пайплайн);
131
+ - друкує summary в stdout (кількість порушень, перелік фейлів тощо).
132
+ 4. `process.exit(<exitCode>)` — процес закривається з кодом для CI/IDE.
133
+
134
+ ### Чому така архітектура (dual role)
135
+
136
+ - Library mode дає **спільний батч**: один обхід FS, одна summary, паралельний прогін правил.
137
+ - Standalone mode зручний для **локальної налагодки** одного правила і для **IDE-інтеграцій** (Cursor запускає окремий `fix.mjs` й отримує exit-code).
138
+ - Логіка не дублюється — `runRuleCli` всередині все одно використовує той самий `runStandardRule`.
139
+
140
+ ### Конвенція директорії правила
141
+
142
+ Для коректної роботи цього `fix.mjs` у сусідній директорії `npm/rules/worktree/` мають бути (опційно — будь-які з):
143
+
144
+ - `worktree.mdc` (або `<id>.mdc`) — людинозрозумілий опис правила;
145
+ - `applies.mjs` — детектор застосовності;
146
+ - `policy.mjs` — декларативні політики;
147
+ - `check-*.mjs` — JS-перевірки (детальна логіка);
148
+ - `meta.json` — метадані (у т.ч. `worktree: true` для skill-ів).
149
+
150
+ Сам `fix.mjs` нічого з цього не імпортує напряму — все шукає `runStandardRule` за `import.meta.dirname`.
151
+
152
+ ## Rebuild Test
153
+
154
+ Перевірка, що файл можна відновити з цієї документації:
155
+
156
+ 1. **Імпорти:** два named-імпорти — `isRunAsCli`, `runRuleCli` з `../../scripts/lib/run-rule-cli.mjs`; `runStandardRule` з `../../scripts/lib/run-standard-rule.mjs`.
157
+ 2. **Export `run(ctx)`:** JSDoc з `@param ctx` (опційний, тип `RuleContext` із `run-standard-rule.mjs`) та `@returns {Promise<number>}` (0 — OK, 1 — порушення); тіло — `return runStandardRule(import.meta.dirname, ctx)`.
158
+ 3. **Top-level `if`:** `if (isRunAsCli(import.meta.url)) { process.exit(await runRuleCli(import.meta.dirname)) }`.
159
+ 4. **Eslint-disable коментар:** перед `process.exit` — `// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE`.
160
+ 5. **Коментарі:** перед `if`-блоком — пояснення про standalone-режим та еквівалентність `npx @nitra/cursor fix <id>`; над `run` — JSDoc про послідовність applies → JS-concerns → policy → mdc-refs та library mode.
161
+ 6. **Стиль:** ESM, без `;` у кінці рядків, single quotes, без default-експортів.
@@ -6,7 +6,11 @@
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "properties": {
9
- "lint": { "type": "string", "enum": ["quick", "ci"], "description": "Фаза lint-кроку: quick (по змінених, у lint і lint-ci) або ci (лише lint-ci)." },
9
+ "lint": {
10
+ "type": "string",
11
+ "enum": ["quick", "ci"],
12
+ "description": "Фаза lint-кроку: quick (по змінених, у lint і lint-ci) або ci (лише lint-ci)."
13
+ },
10
14
  "auto": {
11
15
  "description": "Умова автоактивації правила: \"завжди\", масив id правил-залежностей, glob, або іменований предикат.",
12
16
  "oneOf": [
@@ -71,9 +71,7 @@ export function discoverRuleAutoActivation(rulesDir = RULES_DIR) {
71
71
  const RULE_AUTO_ACTIVATION = discoverRuleAutoActivation()
72
72
 
73
73
  /** Стабільний алфавітний порядок (замість хардкод-масиву). */
74
- export const AUTO_RULE_ORDER = Object.freeze(
75
- Object.keys(RULE_AUTO_ACTIVATION).toSorted((a, b) => a.localeCompare(b))
76
- )
74
+ export const AUTO_RULE_ORDER = Object.freeze(Object.keys(RULE_AUTO_ACTIVATION).toSorted((a, b) => a.localeCompare(b)))
77
75
 
78
76
  /** Граф залежностей із meta (Type C) — замість хардкод-константи. */
79
77
  export const AUTO_RULE_DEPENDENCIES = Object.freeze(
@@ -360,7 +358,12 @@ async function specMatches(spec, ctx) {
360
358
  * @param {string[]} [params.disableRules] список `disable-rules` з конфігу
361
359
  * @returns {Promise<{ rules: string[] }>} список id у стабільному порядку (за `AUTO_RULE_ORDER`)
362
360
  */
363
- export async function detectAutoRules({ root, availableRules, packageJsonParsed, disableRules = DEFAULT_DISABLED_LIST }) {
361
+ export async function detectAutoRules({
362
+ root,
363
+ availableRules,
364
+ packageJsonParsed,
365
+ disableRules = DEFAULT_DISABLED_LIST
366
+ }) {
364
367
  const facts = await collectAutoRuleFacts(root)
365
368
  const paths = await collectRepoPaths(root)
366
369
  const normalizedRules = new Set(availableRules.map(r => r.trim().toLowerCase()))