@nitra/cursor 5.3.3 → 5.4.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 (157) hide show
  1. package/.claude-template/settings.template.json +2 -2
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +13 -24
  3. package/CHANGELOG.md +17 -0
  4. package/bin/n-cursor.js +43 -22
  5. package/lib/docs/llm.md +23 -12
  6. package/lib/docs/models.md +29 -18
  7. package/lib/docs/omlx-trace.md +51 -0
  8. package/lib/docs/omlx.md +31 -15
  9. package/lib/omlx.mjs +2 -5
  10. package/package.json +1 -1
  11. package/rules/abie/docs/fix.md +17 -11
  12. package/rules/adr/docs/fix.md +25 -140
  13. package/rules/bun/docs/fix.md +18 -151
  14. package/rules/capacitor/docs/fix.md +16 -13
  15. package/rules/capacitor/js/docs/platforms.md +31 -43
  16. package/rules/changelog/docs/fix.md +25 -169
  17. package/rules/ci4/docs/fix.md +11 -14
  18. package/rules/doc-files/doc-files.mdc +60 -0
  19. package/rules/doc-files/docs/fix.md +31 -0
  20. package/rules/doc-files/fix.mjs +19 -0
  21. package/{skills → rules}/doc-files/js/docgen-extract.mjs +42 -19
  22. package/{skills → rules}/doc-files/js/docgen-ignore.mjs +2 -1
  23. package/{skills → rules}/doc-files/js/docgen-scan.mjs +9 -1
  24. package/{skills → rules}/doc-files/js/docs/docgen-crc.md +1 -1
  25. package/rules/doc-files/js/docs/docgen-extract-anchors.md +45 -0
  26. package/rules/doc-files/js/docs/docgen-extract.md +39 -0
  27. package/rules/doc-files/js/docs/docgen-files-batch.md +35 -0
  28. package/rules/doc-files/js/docs/docgen-gen.md +46 -0
  29. package/rules/doc-files/js/docs/docgen-ignore.md +37 -0
  30. package/rules/doc-files/js/docs/docgen-prompts.md +39 -0
  31. package/rules/doc-files/js/docs/docgen-scan.md +54 -0
  32. package/rules/doc-files/js/docs/lint.md +36 -0
  33. package/rules/doc-files/js/docs/units-js.md +31 -0
  34. package/rules/doc-files/js/docs/units-rs.md +35 -0
  35. package/rules/doc-files/js/docs/units.md +30 -0
  36. package/rules/doc-files/js/lint.mjs +96 -0
  37. package/{skills → rules}/doc-files/js/units-rs.mjs +37 -17
  38. package/rules/doc-files/lint/docs/lint.md +37 -0
  39. package/rules/doc-files/lint/lint.mjs +105 -0
  40. package/rules/doc-files/meta.json +1 -0
  41. package/rules/docker/docs/fix.md +21 -161
  42. package/rules/efes/docs/fix.md +23 -194
  43. package/rules/feedback/docs/fix.md +10 -8
  44. package/rules/ga/docs/fix.md +10 -5
  45. package/rules/graphql/docs/fix.md +23 -119
  46. package/rules/hasura/docs/fix.md +19 -5
  47. package/rules/hasura/js/docs/internal_urls.md +34 -307
  48. package/rules/image-avif/docs/fix.md +16 -127
  49. package/rules/image-compress/docs/fix.md +20 -141
  50. package/rules/image-compress/js/docs/package_setup.md +22 -182
  51. package/rules/js-bun-db/docs/fix.md +23 -139
  52. package/rules/js-bun-db/js/docs/safety.md +33 -221
  53. package/rules/js-bun-redis/docs/fix.md +25 -114
  54. package/rules/js-bun-redis/js/docs/imports.md +18 -166
  55. package/rules/js-lint/docs/fix.md +30 -108
  56. package/rules/js-lint/js/docs/lint-findings.md +37 -17
  57. package/rules/js-lint/js/docs/lint.md +22 -238
  58. package/rules/js-lint/js/docs/tooling.md +34 -331
  59. package/rules/js-lint-ci/docs/fix.md +16 -149
  60. package/rules/js-lint-ci/js/docs/lint.md +16 -136
  61. package/rules/js-mssql/docs/fix.md +18 -123
  62. package/rules/js-mssql/js/docs/deps.md +28 -251
  63. package/rules/js-run/docs/fix.md +23 -138
  64. package/rules/js-run/js/docs/runtime.md +24 -378
  65. package/rules/k8s/docs/fix.md +18 -123
  66. package/rules/nginx-default-tpl/docs/fix.md +22 -118
  67. package/rules/nginx-default-tpl/js/docs/template.md +38 -360
  68. package/rules/npm-module/docs/fix.md +27 -89
  69. package/rules/npm-module/js/docs/header_doc_pointer.md +15 -15
  70. package/rules/npm-module/js/docs/package_structure.md +36 -258
  71. package/rules/npm-module/js/docs/rule_meta.md +25 -127
  72. package/rules/npm-module/js/docs/skill_meta.md +18 -180
  73. package/rules/php/docs/fix.md +21 -98
  74. package/rules/php/js/docs/tooling.md +20 -143
  75. package/rules/python/docs/fix.md +25 -157
  76. package/rules/python/js/docs/applies.md +20 -98
  77. package/rules/python/js/docs/tooling.md +27 -144
  78. package/rules/rego/docs/fix.md +24 -112
  79. package/rules/rego/js/docs/applies.md +20 -164
  80. package/rules/rego/js/docs/lint.md +15 -110
  81. package/rules/release/docs/fix.md +16 -114
  82. package/rules/rust/docs/fix.md +24 -119
  83. package/rules/rust/js/docs/applies.md +20 -129
  84. package/rules/security/docs/fix.md +21 -78
  85. package/rules/security/js/docs/sample_secret.md +23 -182
  86. package/rules/security/js/docs/trufflehog.md +19 -128
  87. package/rules/style-lint/docs/fix.md +16 -150
  88. package/rules/style-lint/js/docs/lint.md +21 -172
  89. package/rules/style-lint/js/docs/tooling.md +19 -184
  90. package/rules/tauri/docs/fix.md +26 -152
  91. package/rules/tauri/js/docs/cargo_mutants_config.md +21 -159
  92. package/rules/tauri/js/docs/tooling.md +20 -217
  93. package/rules/test/docs/fix.md +19 -127
  94. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +15 -127
  95. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +17 -153
  96. package/rules/test/js/docs/cargo_mutants_config.md +24 -164
  97. package/rules/test/js/docs/location.md +24 -126
  98. package/rules/test/js/docs/no-process-chdir.md +20 -151
  99. package/rules/test/js/docs/no-relative-fs-path.md +24 -261
  100. package/rules/test/js/docs/stryker_config.md +48 -148
  101. package/rules/test/js/docs/vitest-config-pool-forks.md +21 -164
  102. package/rules/text/docs/fix.md +25 -113
  103. package/rules/text/js/docs/forbidden-prettier.md +21 -132
  104. package/rules/text/js/docs/formatting.md +60 -251
  105. package/rules/text/js/docs/lint.md +17 -114
  106. package/rules/vue/docs/fix.md +25 -118
  107. package/rules/vue/js/docs/packages.md +25 -323
  108. package/rules/worktree/docs/fix.md +31 -150
  109. package/scripts/coverage-classify/docs/index.md +23 -209
  110. package/scripts/coverage-classify/docs/verdict-schema.md +14 -159
  111. package/scripts/dispatcher/docs/trace.md +35 -0
  112. package/scripts/docs/auto-rules.md +37 -361
  113. package/scripts/docs/lint-cli.md +12 -13
  114. package/scripts/docs/post-tool-use-fix.md +16 -15
  115. package/scripts/docs/skills-cli.md +26 -23
  116. package/scripts/docs/sync-claude-config.md +94 -34
  117. package/scripts/docs/worktree-cli.md +11 -34
  118. package/scripts/lib/docs/assert-project-root.md +14 -16
  119. package/scripts/lib/docs/changed-files.md +24 -139
  120. package/scripts/lib/docs/discover-check-rules-from-cursor.md +14 -146
  121. package/scripts/lib/docs/rule-predicates.md +20 -17
  122. package/scripts/lib/docs/run-rule-cli.md +14 -18
  123. package/scripts/lib/docs/run-rule.md +13 -20
  124. package/scripts/lib/docs/run-standard-rule.md +12 -15
  125. package/scripts/lib/docs/sync-gitignore-worktree.md +15 -18
  126. package/scripts/lib/rule-predicates.mjs +1 -1
  127. package/scripts/sync-claude-config.mjs +4 -1
  128. package/scripts/utils/docs/with-lock.md +19 -12
  129. package/scripts/utils/with-lock.mjs +4 -2
  130. package/skills/doc-aggregate/SKILL.md +2 -2
  131. package/skills/doc-aggregate/js/docgen-ignore.mjs +6 -6
  132. package/skills/doc-aggregate/js/docs/docgen-ignore.md +1 -1
  133. package/skills/doc-aggregate/js/docs/docgen-scan.md +78 -0
  134. package/skills/doc-files/.changes/260612-0012.md +5 -0
  135. package/skills/doc-files/.changes/260612-0031.md +5 -0
  136. package/skills/doc-files/.changes/260612-0036.md +5 -0
  137. package/skills/doc-files/.changes/260612-0114.md +5 -0
  138. package/skills/doc-files/SKILL.md +6 -6
  139. package/skills/fix/js/docs/llm-worker.md +17 -15
  140. package/skills/fix/js/docs/orchestrator.md +30 -23
  141. package/skills/fix/js/docs/t0.md +26 -16
  142. package/skills/start-check/js/docs/check.md +26 -22
  143. package/skills/taze/js/docs/diff.md +44 -20
  144. package/skills/doc-files/js/docs/docgen-extract-anchors.md +0 -27
  145. package/skills/doc-files/js/docs/docgen-extract.md +0 -29
  146. package/skills/doc-files/js/docs/docgen-files-batch.md +0 -25
  147. package/skills/doc-files/js/docs/docgen-gen.md +0 -30
  148. package/skills/doc-files/js/docs/docgen-prompts.md +0 -32
  149. package/skills/doc-files/js/docs/docgen-scan.md +0 -25
  150. package/skills/doc-files/js/docs/units-rs.md +0 -35
  151. /package/{skills → rules}/doc-files/js/docgen-crc.mjs +0 -0
  152. /package/{skills → rules}/doc-files/js/docgen-extract-anchors.mjs +0 -0
  153. /package/{skills → rules}/doc-files/js/docgen-files-batch.mjs +0 -0
  154. /package/{skills → rules}/doc-files/js/docgen-gen.mjs +0 -0
  155. /package/{skills → rules}/doc-files/js/docgen-prompts.mjs +0 -0
  156. /package/{skills → rules}/doc-files/js/units-js.mjs +0 -0
  157. /package/{skills → rules}/doc-files/js/units.mjs +0 -0
@@ -1,152 +1,52 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/test/js/stryker_config.mjs
4
+ crc: 7ea109c6
5
+ score: 95
6
+ ---
7
+
1
8
  # stryker_config.mjs
2
9
 
3
10
  ## Огляд
4
11
 
5
- Модуль `stryker_config` це концерн (check) правила `test` (відповідає правилу `test.mdc` із набору `.cursor/rules/`). Його роль гарантувати, що в усіх JS-roots проєкту присутні canonical baseline-файли конфігурації для mutation testing (Stryker) і unit-test runner-а (Vitest), а також що тестові артефакти не потрапляють у git.
6
-
7
- Концерн виконує такі задачі:
8
-
9
- - Self-gating: якщо в `.n-cursor.json` правило `js-lint` не включене або явно вимкнене (`disable-rules`), концерн нічого не робить і тихо повертає успіх.
10
- - Визначення всіх JS-roots проєкту — кожен workspace із `package.json` у monorepo, або сам `cwd` у single-package режимі.
11
- - Для кожного JS-root перевіряє наявність файлу `stryker.config.mjs`; якщо його немає — копіює canonical baseline з пакета. Для roots із Vue 3 SFC-файлами (`<script setup>`) використовує спеціальний vue-варіант baseline і додатково копіює локальний Stryker-плагін `stryker-vue-macros-ignorer.mjs`.
12
- - Аналогічно для `vitest.config.js`.
13
- - Додає в кореневий `.gitignore` патерни `**/reports/stryker/` і `**/coverage/`, щоб тестові артефакти не комітилися.
14
-
15
- Файл є idempotent: повторні запуски на тому ж дереві не змінюють вже існуючі конфіги і не дублюють `.gitignore`-патерни.
16
-
17
- ## Експорти / API
18
-
19
- Модуль експортує одну іменовану асинхронну функцію:
20
-
21
- - `check(cwd?: string): Promise<number>` entry-point концерна. Повертає exit code: `0` (успіх або silently skipped), `1` (порушення).
22
-
23
- Інші функції модуля (`hasVueFiles`, `ensureBaselineFile`) є внутрішніми і не експортуються.
24
-
25
- ## Функції
26
-
27
- ### `hasVueFiles(jsRoot)`
28
-
29
- ```js
30
- async function hasVueFiles(jsRoot)
31
- ```
32
-
33
- - **Параметри:**
34
- - `jsRoot` (`string`) абсолютний шлях до workspace-каталогу (JS-root).
35
- - **Повертає:** `Promise<boolean>` `true`, якщо в межах `<jsRoot>/src/**/*.vue` (виключаючи `node_modules`, `dist`, `reports`) знайдено хоча б один `.vue`-файл; інакше `false`.
36
- - **Поведінка:** використовує `node:fs/promises#glob` із patterns `VUE_GLOB_PATTERN = 'src/**/*.vue'` і ignore-списком `VUE_GLOB_IGNORE = ['**/node_modules/**', '**/dist/**', '**/reports/**']`. Як тільки знаходить перший збіг — миттєво повертає `true` через `return` у тілі циклу `for await…of`, тому повний обхід директорії не виконується.
37
- - **Side effects:** немає (read-only filesystem операція).
38
-
39
- ### `ensureBaselineFile(reporter, cwd, baselinePath, target, label)`
40
-
41
- ```js
42
- async function ensureBaselineFile(reporter, cwd, baselinePath, target, label)
43
- ```
44
-
45
- - **Параметри:**
46
- - `reporter` (`ReturnType<typeof createCheckReporter>`) — check-reporter для логування статусів pass/fail.
47
- - `cwd` (`string`) — корінь проєкту, використовується для побудови relative-шляхів у повідомленнях.
48
- - `baselinePath` (`string`) — абсолютний шлях до canonical baseline-файла у пакеті.
49
- - `target` (`string`) — абсолютний шлях, куди копіювати baseline.
50
- - `label` (`string`) — людиночитна мітка для логу (наприклад, `"stryker.config.mjs"` чи `"vitest.config.js"`).
51
- - **Повертає:** `Promise<void>`.
52
- - **Поведінка:** якщо `target` вже існує (`existsSync`) — логує `reporter.pass` із позначкою "існує" і виходить. Якщо ні — копіює `baselinePath` у `target` через `copyFile` і логує `reporter.pass` із поміткою "створено з canonical baseline ... (test.mdc)".
53
- - **Side effects:** запис файлу через `copyFile` (тільки якщо target був відсутній); запис у reporter.
54
- - **Idempotent:** так — повторний виклик на існуючому target не перезаписує файл.
55
-
56
- ### `check(cwd = process.cwd())`
57
-
58
- ```js
59
- export async function check(cwd = process.cwd())
60
- ```
61
-
62
- - **Параметри:**
63
- - `cwd` (`string`, опціональний) — корінь проєкту. За замовчуванням `process.cwd()` для CLI-сумісності.
64
- - **Повертає:** `Promise<number>` — exit code (`0` — OK або silently skipped, `1` — порушення).
65
- - **Side effects:**
66
- - Читає `.n-cursor.json` через `readNCursorConfigLite`.
67
- - Викликає `resolveAllJsRoots` для отримання списку JS-roots.
68
- - Перевіряє існування canonical baseline-файлів у самому пакеті.
69
- - Копіює `stryker.config.mjs` (canonical або vue-варіант), `vitest.config.js`, а для Vue-roots — `stryker-vue-macros-ignorer.mjs` у кожен JS-root, де відповідний файл відсутній.
70
- - Додає у кореневий `.gitignore` патерни `**/reports/stryker/` і `**/coverage/`, якщо їх там немає, через `ensureGitignoreEntries` із коментарем-секцією `"Test artifacts: Stryker + coverage (test.mdc)"`.
71
- - Накопичує статуси у reporter і повертає його exit code.
72
-
73
- ## Залежності
74
-
75
- Стандартні модулі Node.js:
76
-
77
- - `node:fs` — `existsSync` (синхронна перевірка наявності файла).
78
- - `node:fs/promises` — `copyFile` (копіювання baseline у target), `glob` (пошук `.vue` файлів).
79
- - `node:path` — `dirname`, `join`, `relative` (побудова абсолютних і relative шляхів).
80
- - `node:url` — `fileURLToPath` (конвертація `import.meta.url` у POSIX-шлях для `dirname`).
81
-
82
- Внутрішні залежності пакета:
83
-
84
- - `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика reporter-а з API `pass(msg)`, `fail(msg)`, `getExitCode()`.
85
- - `../../../scripts/lib/read-n-cursor-config-lite.mjs` → `readNCursorConfigLite` — читання `.n-cursor.json`, повертає об'єкт із полями `rules: string[]`, `disableRules: string[]`.
86
- - `../../../scripts/utils/ensure-gitignore-entries.mjs` → `ensureGitignoreEntries(cwd, entries, sectionLabel)` — idempotent додавання патернів у кореневий `.gitignore`. Повертає `{ added: string[] }`.
87
- - `../../../scripts/utils/resolve-js-root.mjs` → `resolveAllJsRoots(cwd)` — повертає масив абсолютних шляхів до всіх JS-roots: усі workspaces із `package.json` у monorepo, або `[cwd]` у single-package.
88
-
89
- Зовнішні дані-файли (canonical baseline у пакеті `@nitra/cursor`):
90
-
91
- - `data/stryker_config/stryker.config.baseline.mjs` — стандартний baseline Stryker (vitest-runner + perTest, mutate-патерни на Stryker defaults `src/**/*.{js,mjs,ts,jsx,tsx,cjs}`).
92
- - `data/stryker_config/stryker.config.vue.baseline.mjs` — vue-варіант baseline; реєструє локальний Ignore-плагін `vue-macros`, щоб Stryker не загортав `defineProps`/`defineEmits`/... у coverage-тернарник (інакше `@vue/compiler-sfc` падає при компіляції SFC).
93
- - `data/stryker_config/stryker-vue-macros-ignorer.mjs` — сам Stryker-плагін, який копіюється поруч із vue-варіантом baseline і реєструється з нього.
94
- - `data/vitest_config/vitest.config.baseline.js` — мінімальний baseline Vitest для runner-а.
95
-
96
- ## Константи модуля
97
-
98
- - `HERE` — каталог самого `stryker_config.mjs` (`dirname(fileURLToPath(import.meta.url))`).
99
- - `STRYKER_BASELINE_PATH` — абсолютний шлях до стандартного `stryker.config.baseline.mjs`.
100
- - `STRYKER_VUE_BASELINE_PATH` — абсолютний шлях до vue-варіанта baseline.
101
- - `STRYKER_VUE_PLUGIN_PATH` — абсолютний шлях до `stryker-vue-macros-ignorer.mjs` у пакеті.
102
- - `STRYKER_VUE_PLUGIN_FILENAME = 'stryker-vue-macros-ignorer.mjs'` — ім'я файлу, під яким плагін копіюється у jsRoot.
103
- - `VITEST_BASELINE_PATH` — абсолютний шлях до canonical `vitest.config.baseline.js`.
104
- - `TEST_GITIGNORE_ENTRIES = ['**/reports/stryker/', '**/coverage/']` — патерни, які додаються в корений `.gitignore`. Подвійний-зірочка-префікс `**/` забезпечує покриття всіх workspaces у monorepo (єдиний root `.gitignore`).
105
- - `VUE_GLOB_PATTERN = 'src/**/*.vue'` — scope пошуку `.vue` файлів (відповідає Stryker mutate defaults для `src/`).
106
- - `VUE_GLOB_IGNORE = ['**/node_modules/**', '**/dist/**', '**/reports/**']` — пропускаються build-артефакти і чужі `node_modules`, щоб не активувати vue-варіант через transitive-deps.
107
-
108
- ## Потік виконання / Використання
109
-
110
- Концерн запускається або через CLI пакета `@nitra/cursor` (як один із checks правила `test`), або імпортно з іншого скрипта.
111
-
112
- Алгоритм `check(cwd)` крок за кроком:
113
-
114
- 1. Створює `reporter` через `createCheckReporter()`.
115
- 2. Читає конфіг `.n-cursor.json` через `readNCursorConfigLite(cwd)`.
116
- 3. **Self-gate:** якщо `js-lint` відсутнє в `config.rules`, або присутнє в `config.disableRules` — повертає `reporter.getExitCode()` без жодних повідомлень (silently skipped). Це навмисна поведінка, щоб не шуміти у проєктах без JS coverage tooling.
117
- 4. Викликає `resolveAllJsRoots(cwd)`. Якщо повернувся порожній масив — це аномалія (`js-lint` enabled, але немає `package.json`); `reporter.fail` із повідомленням `'test: js-lint enabled, але кореневий package.json не знайдено (test.mdc)'` і повернення exit code.
118
- 5. Перевіряє існування всіх чотирьох canonical baseline-файлів у пакеті (`STRYKER_BASELINE_PATH`, `STRYKER_VUE_BASELINE_PATH`, `STRYKER_VUE_PLUGIN_PATH`, `VITEST_BASELINE_PATH`). Якщо будь-якого з них немає — `reporter.fail` із вимогою перевстановити `@nitra/cursor` і early-return.
119
- 6. Для кожного `jsRoot` із `jsRoots`:
120
- - Визначає `isVueRoot = await hasVueFiles(jsRoot)`.
121
- - Обирає `strykerBaseline = isVueRoot ? STRYKER_VUE_BASELINE_PATH : STRYKER_BASELINE_PATH`.
122
- - Через `ensureBaselineFile` копіює `strykerBaseline` у `<jsRoot>/stryker.config.mjs` (якщо відсутній).
123
- - Якщо `isVueRoot` — додатково через `ensureBaselineFile` копіює `STRYKER_VUE_PLUGIN_PATH` у `<jsRoot>/stryker-vue-macros-ignorer.mjs`.
124
- - Через `ensureBaselineFile` копіює `VITEST_BASELINE_PATH` у `<jsRoot>/vitest.config.js`.
125
- 7. Викликає `ensureGitignoreEntries(cwd, TEST_GITIGNORE_ENTRIES, 'Test artifacts: Stryker + coverage (test.mdc)')` — додає в кореневий `.gitignore` патерни `**/reports/stryker/` і `**/coverage/` (якщо їх там немає). Якщо щось дійсно було додане (`added.length > 0`) — `reporter.pass` із перелічуванням доданих патернів.
126
- 8. Повертає `reporter.getExitCode()` — `0`, якщо жодного `fail` не було, інакше `1`.
127
-
128
- ### Приклад використання
129
-
130
- ```js
131
- import { check } from '@nitra/cursor/rules/test/js/stryker_config.mjs'
132
-
133
- const exitCode = await check(process.cwd())
134
- process.exit(exitCode)
135
- ```
136
-
137
- Зазвичай безпосередній виклик не потрібен — концерн запускається диспетчером правила `test` (`test.mdc`) у складі команди `n-cursor fix` / `n-cursor check`.
138
-
139
- ### Спостережувані ефекти у файловій системі
140
-
141
- Після успішного прогону в кожному JS-root з'являються:
142
-
143
- - `stryker.config.mjs` (стандартний або vue-варіант, залежно від наявності `.vue` у `src/`).
144
- - `vitest.config.js`.
145
- - `stryker-vue-macros-ignorer.mjs` — тільки для Vue-roots.
146
-
147
- У кореневому `.gitignore` гарантовано присутні:
148
-
149
- - `**/reports/stryker/`
150
- - `**/coverage/`
151
-
152
- Якщо файл уже існував — він не перезаписується, що дозволяє користувачу безпечно кастомізувати baseline під специфіку проєкту.
12
+ Цей файл виконує перевірку наявності певних файлів та конфігурацій у кореневих пакетах. Код працює з конфігами, визначеними у `mutation.json` та `package.json`, для забезпечення певних станів у системі.
13
+
14
+ ## Поведінка
15
+
16
+ 1. Перевірити наявність `js-lint` у конфігурації.
17
+ 2. Зібрати кореневі пакети.
18
+ 3. Перевірити наявність базових конфігурацій.
19
+ 4. Перевірити наявність базлайн-файлів.
20
+ 5. Для кожного кореневого пакета перевірити наявність `.vue` файлів.
21
+ 6. Для кожного кореневого пакета згенерувати ім'я конфігурації.
22
+ 7. Для кожного кореневого пакета перевірити наявність необхідних базлайн-файлів.
23
+ 8. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
24
+ 9. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
25
+ 10. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
26
+ 11. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
27
+ 12. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
28
+ 13. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
29
+ 14. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
30
+ 15. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
31
+ 16. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
32
+ 17. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
33
+ 18. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
34
+ 19. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
35
+ 20. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
36
+ 21. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
37
+ 22. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
38
+ 23. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
39
+ 24. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
40
+ 25. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
41
+ 26. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
42
+ 27. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
43
+ 28. Для кожного кореневого пакета перевірити наявність базових базлайн-файлів.
44
+ 29. Для кожного кореневого пакета перевірити наявність `stryker.config.mjs`.
45
+ 30. Для кожного кореневого пакета перевірити наявність `vitest.config.mjs` або `vitest.config.js`.
46
+
47
+ ## Гарантії поведінки
48
+
49
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
50
+ - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
51
+ - Свідомо пропускає шляхи: `node_modules`.
52
+ - Не звертається до мережі.
@@ -1,174 +1,31 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/test/js/vitest-config-pool-forks.mjs
4
+ crc: bb04a54b
5
+ score: 100
6
+ ---
7
+
1
8
  # vitest-config-pool-forks.mjs
2
9
 
3
10
  ## Огляд
4
11
 
5
- Модуль реалізує статичну перевірку конфігураційного файлу `vitest.config.js` у корені репозиторію: цей файл, якщо він присутній, повинен містити налаштування `pool: 'forks'`. Перевірка є частиною групи правил `test` (див. `test.mdc`, секція «Заборона `process.chdir` у тестах») і виконує роль **defense-in-depth** проти race-conditions у `process.cwd()` між паралельними test-файлами.
6
-
7
- Контекст і мотивація:
8
-
9
- - У `vitest` за замовчуванням використовується `pool: 'threads'`, де всі workers ділять один процес (Node.js worker_threads). Усі такі workers мають **спільний** `process.cwd()`. Якщо хоч один тест (або стороння залежність всередині нього) викликає `process.chdir(...)`, це впливає на всі інші тести, що виконуються паралельно — типовий race-bug.
10
- - Простої заборони `process.chdir(` у тестах недостатньо: third-party код у залежностях може робити `chdir` всередині vitest worker'а непомітно.
11
- - `pool: 'forks'` ізолює кожен test-файл у власному child-процесі, тож `process.cwd()` стає локальним для тесту.
12
-
13
- Алгоритм перевірки — навмисно простий: substring/regex-пошук у сирому тексті `vitest.config.js`. AST-парсинг свідомо не використовується, бо конфіг може бути в довільному форматі експорту (ESM default, named export, CommonJS, factory-функція тощо), а сам ключ `pool: 'forks'` достатньо унікальний, щоб ідентифікувати його без парсера.
14
-
15
- Скіп-семантика: якщо `vitest.config.js` відсутній — це означає, що в репозиторії немає налаштованого vitest, і правило **пропускається** (pass, не fail). Якщо файл є — `pool: 'forks'` обов'язковий.
16
-
17
- ## Експорти / API
18
-
19
- | Експорт | Тип | Призначення |
20
- | ------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
21
- | `check` | `async function` (named export) | Точка входу для виконання перевірки конфігу. Повертає exit-code сумісний з CI-runner'ом. |
22
-
23
- Модуль не має default export. Регулярний вираз `POOL_FORKS_RE` оголошений на рівні модуля, але **не** експортується — це внутрішня деталь реалізації.
24
-
25
- ## Функції
26
-
27
- ### `check(cwdParam?)`
28
-
29
- Асинхронна перевірка наявності `pool: 'forks'` у `vitest.config.js`.
30
-
31
- **Сигнатура:**
32
-
33
- ```js
34
- export async function check(cwdParam = process.cwd()): Promise<number>
35
- ```
36
-
37
- **Параметри:**
38
-
39
- | Ім'я | Тип | За замовчуванням | Опис |
40
- | ---------- | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
- | `cwdParam` | `string` | `process.cwd()` | Абсолютний шлях до кореня репозиторію, в якому шукається `vitest.config.js`. Призначений насамперед для тестів (можна підставити фікстурну директорію). |
42
-
43
- **Повертає:** `Promise<number>` — exit-code з `reporter.getExitCode()`:
44
-
45
- - `0` — успіх (`pool: 'forks'` знайдено) **або** skip (`vitest.config.js` відсутній);
46
- - `1` — помилка (файл є, але не містить `pool: 'forks'`).
47
-
48
- **Side effects:**
49
-
50
- - Створює локальний reporter через `createCheckReporter()`. Reporter виводить повідомлення `pass(...)` / `fail(...)` у консоль (формат залежить від реалізації reporter'а).
51
- - Синхронно перевіряє існування файлу через `existsSync` (звернення до файлової системи).
52
- - Якщо файл існує — асинхронно читає його повністю в пам'ять через `readFile(..., 'utf8')`.
53
- - **Не** модифікує файлову систему та **не** мутує глобальний стан.
54
-
55
- **Сценарії виконання:**
56
-
57
- 1. **`vitest.config.js` не існує** → `pass('vitest.config.js відсутній — pool-перевірку пропущено')` → повертає `0`.
58
- 2. **Файл існує, regex `/pool\s*:\s*['"]forks['"]/u` матчить** → `pass("vitest.config.js містить pool: 'forks' (test.mdc)")` → повертає `0`.
59
- 3. **Файл існує, але regex не матчить** → `fail("vitest.config.js має містити pool: 'forks' — defense-in-depth для race у process.cwd() між паралельними test files (test.mdc)")` → повертає `1`.
60
-
61
- **Деталі регексу `POOL_FORKS_RE`:**
62
-
63
- ```js
64
- const POOL_FORKS_RE = /pool\s*:\s*['"]forks['"]/u
65
- ```
66
-
67
- Регекс:
68
-
69
- - `pool` — буквальний ключ;
70
- - `\s*:\s*` — двокрапка з довільним whitespace навколо;
71
- - `['"]forks['"]` — значення `forks` у одинарних або подвійних лапках;
72
- - Прапор `u` — Unicode-мода для коректної семантики escape-послідовностей.
73
-
74
- Регекс свідомо **не** перевіряє, що лапки парні (`'forks"` теоретично матчить). На практиці це не проблема, бо у валідному JS такі змішані лапки не зустрінуться (parse-error до того). Whitespace між токенами дозволений, але переноси рядка між `pool` і двокрапкою — лише ті, що покриваються `\s*`.
75
-
76
- ## Залежності
77
-
78
- **Стандартна бібліотека Node.js:**
79
-
80
- | Модуль | Імпортовані символи | Використання |
81
- | ------------------ | ------------------- | --------------------------------------------------------------- |
82
- | `node:fs` | `existsSync` | Синхронна перевірка існування `vitest.config.js`. |
83
- | `node:fs/promises` | `readFile` | Асинхронне читання вмісту конфігу у utf-8. |
84
- | `node:path` | `join` | Побудова шляху `${cwdParam}/vitest.config.js` крос-платформово. |
85
-
86
- **Внутрішні модулі проєкту:**
87
-
88
- | Шлях | Імпортовані символи | Використання |
89
- | ----------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
90
- | `../../../scripts/lib/check-reporter.mjs` | `createCheckReporter` | Фабрика репортера з методами `pass(msg)`, `fail(msg)` та `getExitCode()`. Уніфікує вивід для всіх `check-*.mjs` правил у репозиторії. |
91
-
92
- **Зовнішніх npm-залежностей не має.**
93
-
94
- ## Потік виконання / Використання
95
-
96
- ### Типовий потік виконання `check()`
97
-
98
- 1. Створюється reporter: `const reporter = createCheckReporter()`.
99
- 2. Деструктуризація методів: `const { pass, fail } = reporter`.
100
- 3. Будується шлях до конфігу: `configPath = join(cwdParam, 'vitest.config.js')`.
101
- 4. Перевіряється існування файлу через `existsSync(configPath)`:
102
- - Якщо файла **немає** — викликається `pass(...)` з повідомленням про skip, і функція негайно повертає `reporter.getExitCode()`.
103
- 5. Якщо файл є — він читається повністю: `body = await readFile(configPath, 'utf8')`.
104
- 6. Виконується `POOL_FORKS_RE.test(body)`:
105
- - `true` → `pass(...)` з підтвердженням;
106
- - `false` → `fail(...)` з поясненням і покликанням на `test.mdc`.
107
- 7. Повертається `reporter.getExitCode()` — `0` або `1` залежно від того, чи був хоч один `fail`.
108
-
109
- ### Використання як npm/CI-перевірки
110
-
111
- Модуль слідує конвенції rule-checker'ів проєкту (правила в `npm/rules/<group>/<lang>/check-*.mjs` або тематичних файлах). Запускається або:
112
-
113
- - безпосередньо `node` як CLI-входу (якщо обгорнуто скриптом-раннером);
114
- - з агрегованого runner'а, який послідовно викликає `check()` для кожного правила і збирає exit-коди.
115
-
116
- Приклад прямого виклику з іншого модуля:
117
-
118
- ```js
119
- import { check } from './vitest-config-pool-forks.mjs'
120
-
121
- const exitCode = await check(process.cwd())
122
- process.exit(exitCode)
123
- ```
124
-
125
- Приклад виклику для нестандартного кореня (наприклад, у тесті):
126
-
127
- ```js
128
- import { check } from './vitest-config-pool-forks.mjs'
129
-
130
- const exitCode = await check('/tmp/fixture-repo-with-vitest')
131
- // exitCode === 0 якщо у фікстурі є vitest.config.js з pool: 'forks',
132
- // 1 якщо файл є без потрібного pool, 0 якщо файла нема взагалі
133
- ```
134
-
135
- ### Приклад валідного `vitest.config.js`
136
-
137
- ```js
138
- import { defineConfig } from 'vitest/config'
139
-
140
- export default defineConfig({
141
- test: {
142
- pool: 'forks'
143
- // ... інші опції
144
- }
145
- })
146
- ```
147
-
148
- ### Приклад невалідного `vitest.config.js`
149
-
150
- ```js
151
- import { defineConfig } from 'vitest/config'
12
+ Огляд
13
+ Файл виконує перевірку конфігураційного файлу для підтвердження наявності певного параметра. Це робиться для запобігання гонці у process.cwd між паралельними тестовими файлами (test.mdc).
152
14
 
153
- export default defineConfig({
154
- test: {
155
- // pool не вказано → effective pool: 'threads' (default vitest) → race на process.cwd()
156
- }
157
- })
158
- ```
15
+ ## Поведінка
159
16
 
160
- або
17
+ 1. Знайти файл конфігурації з назвою vitest.config.mjs або vitest.config.js у вказаному шляху.
18
+ 2. Якщо файл знайдено, прочитати його вміст.
19
+ 3. Перевірити вміст файлу на наявність патерну pool: 'forks'.
20
+ 4. Якщо патерн знайдено, повідомити про успішне виконання перевірки.
21
+ 5. Якщо патерн відсутній, повідомити про невдачу, вказуючи, що конфігурація повинна містити pool: 'forks' для захисту від гонки у process.cwd між паралельними test files (test.mdc).
22
+ 6. Якщо файл конфігурації не знайдено, повідомити, що перевірка пропущено.
161
23
 
162
- ```js
163
- export default {
164
- test: { pool: 'threads' } // явно threads — те саме, що default
165
- }
166
- ```
24
+ ## Публічний API
167
25
 
168
- Обидва варіанти призведуть до `fail` і exit-code `1`.
26
+ check перевіряє, чи налаштування `vitest.config.{mjs,js}` використовує `pool: 'forks'`. (test.mdc)
169
27
 
170
- ### Межі застосовності
28
+ ## Гарантії поведінки
171
29
 
172
- - Перевірка **не** валідовує JS-синтаксис конфігу. Якщо `vitest.config.js` синтаксично битий, але містить рядок `pool: 'forks'` у коментарі чи рядковому літералі, перевірка пройде. Це усвідомлений trade-off на користь простоти — реальні false-positive у проєкті малоймовірні.
173
- - Перевірка дивиться **тільки** на `vitest.config.js` у корені, переданому через `cwdParam`. Альтернативні імена (`vitest.config.ts`, `vitest.config.mjs`, `vite.config.js` зі вбудованою `test`-секцією) **не** обробляються — це окремі правила, якщо знадобляться.
174
- - Skip при відсутності файлу — поведінка дизайну: правило не «обов'язково додай vitest», а «якщо ти вже маєш vitest, налаштуй pool правильно».
30
+ - Read-only: файл не виконує операцій запису у файлову систему.
31
+ - Не звертається до мережі.
@@ -1,127 +1,39 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/text/fix.mjs
4
- crc: 12fc1644
4
+ crc: 38cf876b
5
+ score: 100
5
6
  ---
6
7
 
7
- # fix.mjs — entry-point правила `text`
8
+ # fix.mjs
8
9
 
9
10
  ## Огляд
10
11
 
11
- Файл `npm/rules/text/fix.mjs` це **подвійний entry-point** для правила `text` із пакета `@nitra/cursor`. Він виконує дві ролі одночасно:
12
+ Виконує застосування JS-занепокоєних на наданому контексті прогону, застосовує визначену політику, генерує посилання MDC та повертає результат. У режимі CLI виконується як автономний скрипт, який імітує команду `npx @nitra/cursor fix <id>`, завантажує конфігурацію, перевіряє дозволені записи, генерує зведену інформацію та визначає код виходу процесу.
12
13
 
13
- 1. **Library mode** — експортує функцію `run(ctx)`, яку імпортує зовнішня CLI-оркестрація (`npx @nitra/cursor fix text` або агрегований прогон усіх правил) для запуску стандартного fix-pipeline саме цього правила.
14
- 2. **Standalone mode** — якщо модуль виконується безпосередньо (`bun npm/rules/text/fix.mjs`), він самостійно піднімає повний CLI-оркестратор (`runRuleCli`) із завантаженням конфігурації, whitelist-фільтрацією та підсумковим виводом, повертаючи коректний `exit-code` для CI/IDE.
14
+ ## Поведінка
15
15
 
16
- Сам файл не містить бізнес-логіки правила: він є **тонким адаптером** над двома утилітами (`runStandardRule` та `runRuleCli`) і покладається на конвенцію `import.meta.dirname` тобто на те, що сусідні теки `lint/`, `policy/`, `js/` та файл `text.mdc` дають усю фактичну реалізацію (applies → JS-concerns → policy → mdc-refs).
16
+ 1. Запуск правила.
17
+ * Приймає контекст прогону.
18
+ * Виконує застосування JS-занепокоєних.
19
+ * Застосовує політику.
20
+ * Генерує посилання MDC.
21
+ * Повертає результат прогону.
22
+ 2. Виконання у режимі CLI.
23
+ * Виконується як автономний скрипт.
24
+ * Виконує повний еквівалент команди `npx @nitra/cursor fix <id>`.
25
+ * Виконує завантаження конфігурації.
26
+ * Виконує перевірку дозволених записів.
27
+ * Генерує зведену інформацію.
28
+ * Визначає код виходу процесу.
17
29
 
18
- Шлях фізично розміщений у директорії конкретного правила `text`, але код **повністю однаковий** для всіх правил репозиторію — `import.meta.dirname` робить його контекстно-залежним без жодних хардкодів `id` правила.
30
+ ## Публічний API
19
31
 
20
- ## Експорти / API
32
+ run запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
33
+ Library mode — викликається CLI orchestration через `import + run`.
21
34
 
22
- | Експорт | Тип | Призначення |
23
- | ------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
24
- | `run` | `function (ctx?: RuleContext): Promise<number>` | Іменований експорт для library mode. Викликається зовнішньою оркестрацією, повертає exit-code правила (`0` — OK, `1` — порушення). |
35
+ ## Гарантії поведінки
25
36
 
26
- Файл **не має** default-експорту. Усі інші конструкції модуля (`if (isRunAsCli(...))`) виконуються при імпорті як side-effect, але активуються лише коли модуль є entry-point процесу для звичайного `import` standalone-блок не спрацьовує.
27
-
28
- ## Функції
29
-
30
- ### `run(ctx)`
31
-
32
- ```js
33
- /**
34
- *
35
- */
36
- export function run(ctx) {
37
- return runStandardRule(import.meta.dirname, ctx)
38
- }
39
- ```
40
-
41
- - **Сигнатура**: `function run(ctx?: RuleContext): Promise<number>`
42
- - **Параметри**:
43
- - `ctx` _(необов'язковий)_ — об'єкт контексту прогону типу `RuleContext`, описаний у JSDoc-import у `../../scripts/lib/run-standard-rule.mjs`. Передається оркестратором і зазвичай містить кешовані результати walk'у файлової системи (`walkCache`) та інші спільні для пакетного прогону структури, щоб уникнути повторного сканування репозиторію кожним правилом.
44
- - **Повертає**: `Promise<number>` — exit-code, який повертає `runStandardRule`. За конвенцією репозиторію:
45
- - `0` — порушень не знайдено (правило пройшло);
46
- - `1` — знайдено хоча б одне порушення (правило впало).
47
- - **Side effects**: усі побічні ефекти делеговані в `runStandardRule`. Це включає:
48
- - читання файлів проєкту (без модифікацій);
49
- - виконання сабмодулів `applies`, `JS-concerns`, `policy`, `mdc-refs` із сусідніх тек;
50
- - друк діагностики у stdout/stderr (залежить від реалізації стандартного pipeline та `RuleContext`).
51
- - **Передача `import.meta.dirname`**: ключова деталь — як перший аргумент передається **директорія цього файлу** (`npm/rules/text/`). Саме за нею `runStandardRule` ідентифікує правило (`id = basename(dirname)`) та знаходить його артефакти (mdc, applies, policy).
52
-
53
- ### Standalone-блок (top-level, не функція)
54
-
55
- ```js
56
- if (isRunAsCli(import.meta.url)) {
57
- process.exit(await runRuleCli(import.meta.dirname))
58
- }
59
- ```
60
-
61
- - **Призначення**: дозволяє запускати модуль як автономний скрипт — `bun npm/rules/text/fix.mjs`. У такому режимі він має дати **повний еквівалент** виклику `npx @nitra/cursor fix text`, включно з:
62
- - завантаженням конфігурації проєкту;
63
- - застосуванням whitelist (відсіюванням файлів, що не підпадають під правило);
64
- - друком підсумку (summary) виконання.
65
- - **Параметри**: немає (CLI-аргументи парсяться всередині `runRuleCli`).
66
- - **Повертає / завершує**: `process.exit(n)` із `n`, який повернув `runRuleCli`. Це необхідно, щоб CI або IDE-інтеграція могли визначати успіх/невдачу за стандартним exit-code'ом.
67
- - **Side effects**:
68
- - читання конфігурації пакета та проєкту;
69
- - читання й аналіз файлів проєкту;
70
- - вивід summary у stdout/stderr;
71
- - **завершення процесу** через `process.exit` (це окремо помічено `eslint-disable` коментарем `n/no-process-exit, unicorn/no-process-exit`, оскільки правила лінтера за замовчуванням забороняють `process.exit` поза entry-point файлами).
72
- - **Перевірка `isRunAsCli`**: функція приймає `import.meta.url` і повертає `true` лише якщо саме цей файл — entry-point Node/Bun-процесу. Це класичний ідіоматичний еквівалент `if __name__ == "__main__"` із Python для ESM.
73
-
74
- ## Залежності
75
-
76
- ### Внутрішні (relative)
77
-
78
- | Імпорт | Модуль | Що дає |
79
- | ----------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
80
- | `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Предикат: чи запущений модуль як CLI entry-point (порівняння `import.meta.url` з `process.argv[1]`). |
81
- | `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Повний CLI-оркестратор одного правила: завантаження конфігурації, whitelist, виклик `run`, друк summary, повернення exit-code. |
82
- | `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Стандартний fix-pipeline правила: послідовно виконує етапи **applies → JS-concerns → policy → mdc-refs**. Знаходить артефакти за `dirname` правила. |
83
-
84
- Усі три імпорти — це shared-утиліти рівня `scripts/lib/`, спроєктовані саме для подвійного entry-point'у правил. Жодних зовнішніх npm-залежностей файл не імпортує напряму.
85
-
86
- ### Неявні (контекст виконання)
87
-
88
- - **`import.meta.dirname`** — стандартне поле ESM (Node ≥ 20 / Bun): абсолютний шлях до директорії, що містить модуль. Використовується **двічі** і є ключовим для ідентифікації правила.
89
- - **`import.meta.url`** — стандартне поле ESM: URL-форма шляху до модуля; передається в `isRunAsCli` для порівняння з `process.argv[1]`.
90
- - **`process.exit`** — глобальний Node/Bun API; викликається лише у standalone-режимі.
91
- - **Сусідні теки правила** (`./lint`, `./policy`, `./js`, `./text.mdc`, `./meta.json`) — фактичні артефакти, які `runStandardRule` знаходить через переданий `import.meta.dirname`. Файл `fix.mjs` сам їх не імпортує, але pipeline без них не запрацює.
92
-
93
- ## Потік виконання / Використання
94
-
95
- ### Сценарій 1 — Library mode (typical)
96
-
97
- 1. Зовнішній код (наприклад, агрегатор `npx @nitra/cursor fix`) робить `await import('npm/rules/text/fix.mjs')`.
98
- 2. Перевірка `isRunAsCli(import.meta.url)` повертає `false` (бо entry-point процесу — інший файл), standalone-блок **не виконується**.
99
- 3. Агрегатор викликає експортовану `run(ctx)`, передаючи спільний `ctx` (наприклад, з прогрітим `walkCache`).
100
- 4. `run` делегує виклик у `runStandardRule(import.meta.dirname, ctx)`.
101
- 5. `runStandardRule` ідентифікує правило за `basename(dirname)` = `text`, послідовно виконує етапи pipeline (applies → JS-concerns → policy → mdc-refs), читає сусідні артефакти, повертає exit-code.
102
- 6. Агрегатор отримує `Promise<number>` і інтегрує його в загальний підсумок (наприклад, агрегує максимум по всіх правилах).
103
-
104
- ### Сценарій 2 — Standalone mode
105
-
106
- 1. Розробник або CI виконує `bun npm/rules/text/fix.mjs` (або через npm-script).
107
- 2. Імпорти `isRunAsCli`, `runRuleCli`, `runStandardRule` зчитуються.
108
- 3. Експорт `run` стає доступним, але **ніким не використовується** в цьому сценарії.
109
- 4. `isRunAsCli(import.meta.url)` повертає `true`.
110
- 5. Виконується `await runRuleCli(import.meta.dirname)` — повний CLI-цикл: парсинг аргументів, завантаження конфігурації, whitelist, виклик внутрішнього еквівалента `run`, друк summary.
111
- 6. Результат передається в `process.exit(...)` — процес завершується із належним exit-code'ом для CI.
112
-
113
- ### Чому саме така архітектура
114
-
115
- - **Однаковий код для всіх правил** — `fix.mjs` кожного правила в `npm/rules/<id>/` ідентичний; диференціація — виключно через директорію (`import.meta.dirname`) та артефакти в ній. Це усуває дублювання логіки entry-point'у та робить додавання нового правила механічним (скопіювати теку, поправити mdc/policy).
116
- - **Подвійна роль (library + standalone)** — дозволяє з одного файлу і дебажити правило локально (швидкий feedback `bun rules/text/fix.mjs`), і інтегрувати його у пакетний прогон агрегатора без зміни вихідного коду.
117
- - **`process.exit` лише в standalone-гілці** — у library-режимі повернення exit-code'у через `Promise<number>` дає агрегатору можливість зібрати всі результати й завершитися одним викликом, не давши окремому правилу обірвати процес передчасно.
118
-
119
- ### Rebuild Test (інваріанти, які має зберегти будь-яка перебудова файлу)
120
-
121
- - Файл **має експортувати** функцію `run(ctx)`, що повертає `Promise<number>`.
122
- - `run` **має** передавати `import.meta.dirname` як перший аргумент у `runStandardRule` (без хардкоду шляху або `id`).
123
- - `run` **не повинна** викликати `process.exit` — лише повертати exit-code.
124
- - Standalone-гілка **має** бути захищена `isRunAsCli(import.meta.url)`, щоб не спрацьовувати при `import` із зовнішнього коду.
125
- - Standalone-гілка **має** використовувати `runRuleCli` (а не `runStandardRule` напряму), бо саме `runRuleCli` додає config-loading, whitelist та summary.
126
- - Standalone-гілка **має** завершуватися `process.exit(await runRuleCli(import.meta.dirname))` із поверненням коректного exit-code'у — інакше CI не зможе зловити порушення.
127
- - Файл **не повинен** містити власної логіки правила: вся специфіка `text` живе в сусідніх теках (`lint/`, `policy/`, `js/`) та `text.mdc`.
37
+ - Read-only: файл не виконує операцій запису у файлову систему.
38
+ - Кешує результати в межах одного прогону.
39
+ - Не звертається до мережі.