@7n/rules-lang-js 0.3.1 → 0.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 (258) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +14 -4
  3. package/rules/bun/bunfig/bunfig.mdc +17 -0
  4. package/rules/bun/bunfig/bunfig.rego +29 -0
  5. package/rules/bun/bunfig/concern.json +9 -0
  6. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
  7. package/rules/bun/docs/index.md +11 -0
  8. package/rules/bun/layout/concern.json +16 -0
  9. package/rules/bun/layout/docs/fix-layout.md +29 -0
  10. package/rules/bun/layout/docs/main.md +34 -0
  11. package/rules/bun/layout/fix-layout.mjs +63 -0
  12. package/rules/bun/layout/layout.mdc +60 -0
  13. package/rules/bun/layout/main.mjs +53 -0
  14. package/rules/bun/licensee/concern.json +7 -0
  15. package/rules/bun/licensee/docs/fix-licensee.md +27 -0
  16. package/rules/bun/licensee/docs/index.md +12 -0
  17. package/rules/bun/licensee/docs/main.md +30 -0
  18. package/rules/bun/licensee/fix-licensee.mjs +31 -0
  19. package/rules/bun/licensee/main.mjs +68 -0
  20. package/rules/bun/lint-surface/concern.json +3 -0
  21. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  22. package/rules/bun/main.json +1 -0
  23. package/rules/bun/main.mdc +11 -0
  24. package/rules/bun/package_json/concern.json +9 -0
  25. package/rules/bun/package_json/docs/fix-package_json.md +28 -0
  26. package/rules/bun/package_json/docs/index.md +9 -0
  27. package/rules/bun/package_json/fix-package_json.mjs +311 -0
  28. package/rules/bun/package_json/package_json.mdc +14 -0
  29. package/rules/bun/package_json/package_json.rego +64 -0
  30. package/rules/bun/package_json/template/package.json.deny.json +4 -0
  31. package/rules/js/check/check.mdc +26 -0
  32. package/rules/js/check/concern.json +19 -0
  33. package/rules/js/check/docs/eslint-config.md +56 -0
  34. package/rules/js/check/docs/fix-check.md +48 -0
  35. package/rules/js/check/docs/index.md +13 -0
  36. package/rules/js/check/docs/main.md +48 -0
  37. package/rules/js/check/eslint-config.mjs +262 -0
  38. package/rules/js/check/fix-check.mjs +82 -0
  39. package/rules/js/check/main.mjs +310 -0
  40. package/rules/js/dep-policy/concern.json +4 -0
  41. package/rules/js/dep-policy/dep-policy.mdc +36 -0
  42. package/rules/js/dep-policy/docs/main.md +35 -0
  43. package/rules/js/dep-policy/main.mjs +99 -0
  44. package/rules/js/docs/index.md +11 -0
  45. package/rules/js/eslint/concern.json +8 -0
  46. package/rules/js/eslint/docs/fix-eslint.md +50 -0
  47. package/rules/js/eslint/docs/fix-worker.md +31 -0
  48. package/rules/js/eslint/docs/index.md +11 -0
  49. package/rules/js/eslint/docs/main.md +35 -0
  50. package/rules/js/eslint/fix-eslint.mjs +160 -0
  51. package/rules/js/eslint/fix-worker.mjs +139 -0
  52. package/rules/js/eslint/main.mjs +115 -0
  53. package/rules/js/file-extensions/concern.json +3 -0
  54. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  55. package/rules/js/jscpd_config/concern.json +11 -0
  56. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
  57. package/rules/js/jscpd_config/docs/index.md +9 -0
  58. package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
  59. package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
  60. package/rules/js/jscpd_config/jscpd_config.rego +44 -0
  61. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
  62. package/rules/js/jscpd_duplicates/concern.json +7 -0
  63. package/rules/js/jscpd_duplicates/docs/main.md +29 -0
  64. package/rules/js/jscpd_duplicates/main.mjs +69 -0
  65. package/rules/js/knip/concern.json +7 -0
  66. package/rules/js/knip/docs/main.md +32 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +68 -0
  69. package/rules/js/lint-findings/concern.json +3 -0
  70. package/rules/js/lint-findings/docs/main.md +40 -0
  71. package/rules/js/lint-findings/main.mjs +125 -0
  72. package/rules/js/main.json +1 -0
  73. package/rules/js/main.mdc +18 -0
  74. package/rules/js/package_json/concern.json +9 -0
  75. package/rules/js/package_json/docs/fix-package_json.md +25 -0
  76. package/rules/js/package_json/docs/index.md +9 -0
  77. package/rules/js/package_json/fix-package_json.mjs +3 -0
  78. package/rules/js/package_json/package_json.mdc +15 -0
  79. package/rules/js/package_json/package_json.rego +142 -0
  80. package/rules/js/package_json/template/package.json.snippet.json +6 -0
  81. package/rules/js/tooling/concern.json +3 -0
  82. package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
  83. package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
  84. package/rules/js/tooling/docs/main.md +53 -0
  85. package/rules/js/tooling/main.mjs +183 -0
  86. package/rules/js/utils_imports/concern.json +4 -0
  87. package/rules/js/utils_imports/docs/main.md +50 -0
  88. package/rules/js/utils_imports/main.mjs +185 -0
  89. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  90. package/rules/js/vscode_extensions/concern.json +11 -0
  91. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  92. package/rules/js/vscode_extensions/docs/index.md +11 -0
  93. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  94. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
  95. package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
  96. package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
  97. package/rules/js-bun-db/connection/concern.json +3 -0
  98. package/rules/js-bun-db/connection/connection.mdc +42 -0
  99. package/rules/js-bun-db/docs/index.md +11 -0
  100. package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
  101. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
  102. package/rules/js-bun-db/lib/docs/index.md +11 -0
  103. package/rules/js-bun-db/main.json +1 -0
  104. package/rules/js-bun-db/main.mdc +8 -0
  105. package/rules/js-bun-db/package_json/concern.json +9 -0
  106. package/rules/js-bun-db/package_json/package_json.mdc +31 -0
  107. package/rules/js-bun-db/package_json/package_json.rego +15 -0
  108. package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
  109. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  110. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  111. package/rules/js-bun-db/safety/concern.json +4 -0
  112. package/rules/js-bun-db/safety/docs/main.md +34 -0
  113. package/rules/js-bun-db/safety/main.mjs +430 -0
  114. package/rules/js-bun-db/safety/safety.mdc +458 -0
  115. package/rules/js-bun-redis/docs/index.md +11 -0
  116. package/rules/js-bun-redis/imports/concern.json +4 -0
  117. package/rules/js-bun-redis/imports/docs/main.md +36 -0
  118. package/rules/js-bun-redis/imports/imports.mdc +47 -0
  119. package/rules/js-bun-redis/imports/main.mjs +88 -0
  120. package/rules/js-bun-redis/lib/docs/index.md +11 -0
  121. package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
  122. package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
  123. package/rules/js-bun-redis/main.json +1 -0
  124. package/rules/js-bun-redis/main.mdc +8 -0
  125. package/rules/js-bun-redis/package_json/concern.json +9 -0
  126. package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
  127. package/rules/js-bun-redis/package_json/package_json.rego +15 -0
  128. package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
  129. package/rules/js-mssql/deps/concern.json +4 -0
  130. package/rules/js-mssql/deps/docs/main.md +33 -0
  131. package/rules/js-mssql/deps/main.mjs +297 -0
  132. package/rules/js-mssql/docs/index.md +11 -0
  133. package/rules/js-mssql/lib/docs/index.md +11 -0
  134. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
  135. package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
  136. package/rules/js-mssql/main.json +1 -0
  137. package/rules/js-mssql/main.mdc +144 -0
  138. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  139. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  140. package/rules/js-mssql/package_json/concern.json +9 -0
  141. package/rules/js-mssql/package_json/package_json.mdc +9 -0
  142. package/rules/js-mssql/package_json/package_json.rego +57 -0
  143. package/rules/js-run/configmap/concern.json +9 -0
  144. package/rules/js-run/configmap/configmap.mdc +37 -0
  145. package/rules/js-run/configmap/configmap.rego +21 -0
  146. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
  147. package/rules/js-run/docs/index.md +11 -0
  148. package/rules/js-run/jsconfig/concern.json +9 -0
  149. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
  150. package/rules/js-run/jsconfig/docs/index.md +9 -0
  151. package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
  152. package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
  153. package/rules/js-run/jsconfig/jsconfig.rego +59 -0
  154. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
  155. package/rules/js-run/lib/bunyan-imports.mjs +98 -0
  156. package/rules/js-run/lib/check-env-scan.mjs +338 -0
  157. package/rules/js-run/lib/conn-file-rules.mjs +214 -0
  158. package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
  159. package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
  160. package/rules/js-run/lib/docs/check-env-scan.md +438 -0
  161. package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
  162. package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
  163. package/rules/js-run/lib/docs/index.md +16 -0
  164. package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
  165. package/rules/js-run/lib/docs/temporal-scan.md +29 -0
  166. package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
  167. package/rules/js-run/lib/temporal-scan.mjs +52 -0
  168. package/rules/js-run/main.json +1 -0
  169. package/rules/js-run/main.mdc +16 -0
  170. package/rules/js-run/package_json/concern.json +9 -0
  171. package/rules/js-run/package_json/package_json.mdc +44 -0
  172. package/rules/js-run/package_json/package_json.rego +37 -0
  173. package/rules/js-run/package_json/template/package.json.deny.json +22 -0
  174. package/rules/js-run/project-structure/concern.json +3 -0
  175. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  176. package/rules/js-run/runtime/concern.json +12 -0
  177. package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
  178. package/rules/js-run/runtime/docs/main.md +35 -0
  179. package/rules/js-run/runtime/fix-runtime.mjs +46 -0
  180. package/rules/js-run/runtime/main.mjs +496 -0
  181. package/rules/js-run/runtime/runtime.mdc +184 -0
  182. package/rules/js-run/scope/concern.json +3 -0
  183. package/rules/js-run/scope/scope.mdc +11 -0
  184. package/rules/npm-module/docs/index.md +11 -0
  185. package/rules/npm-module/emit_types_config/concern.json +9 -0
  186. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
  187. package/rules/npm-module/emit_types_config/docs/index.md +9 -0
  188. package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
  189. package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
  190. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
  191. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  192. package/rules/npm-module/header_doc_pointer/concern.json +5 -0
  193. package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
  194. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
  195. package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
  196. package/rules/npm-module/main.json +1 -0
  197. package/rules/npm-module/main.mdc +36 -0
  198. package/rules/npm-module/npm_package_json/concern.json +9 -0
  199. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
  200. package/rules/npm-module/npm_package_json/docs/index.md +9 -0
  201. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
  202. package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
  203. package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
  204. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
  205. package/rules/npm-module/package_structure/concern.json +5 -0
  206. package/rules/npm-module/package_structure/docs/main.md +37 -0
  207. package/rules/npm-module/package_structure/main.mjs +448 -0
  208. package/rules/npm-module/package_structure/package_structure.mdc +63 -0
  209. package/rules/npm-module/root_package_json/concern.json +9 -0
  210. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
  211. package/rules/npm-module/root_package_json/docs/index.md +9 -0
  212. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
  213. package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
  214. package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
  215. package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
  216. package/rules/npm-module/rule_meta/concern.json +8 -0
  217. package/rules/npm-module/rule_meta/docs/main.md +35 -0
  218. package/rules/npm-module/rule_meta/main.mjs +119 -0
  219. package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
  220. package/rules/npm-module/skill_meta/concern.json +5 -0
  221. package/rules/npm-module/skill_meta/docs/main.md +149 -0
  222. package/rules/npm-module/skill_meta/main.mjs +91 -0
  223. package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
  224. package/rules/tool-surface/docs/index.md +11 -0
  225. package/rules/tool-surface/main.json +6 -0
  226. package/rules/tool-surface/main.mdc +72 -0
  227. package/rules/vue/composition-api/composition-api.mdc +82 -0
  228. package/rules/vue/composition-api/concern.json +3 -0
  229. package/rules/vue/docs/index.md +11 -0
  230. package/rules/vue/lib/docs/index.md +11 -0
  231. package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
  232. package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
  233. package/rules/vue/main.json +1 -0
  234. package/rules/vue/main.mdc +18 -0
  235. package/rules/vue/nheader-layout/concern.json +3 -0
  236. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  237. package/rules/vue/package_json/concern.json +9 -0
  238. package/rules/vue/package_json/package_json.mdc +30 -0
  239. package/rules/vue/package_json/package_json.rego +140 -0
  240. package/rules/vue/packages/concern.json +6 -0
  241. package/rules/vue/packages/docs/index.md +11 -0
  242. package/rules/vue/packages/docs/main.md +35 -0
  243. package/rules/vue/packages/main.mjs +575 -0
  244. package/rules/vue/packages/packages.mdc +56 -0
  245. package/rules/vue/quasar-ui/concern.json +3 -0
  246. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  247. package/rules/vue/structure/concern.json +3 -0
  248. package/rules/vue/structure/structure.mdc +101 -0
  249. package/rules/vue/testing/concern.json +3 -0
  250. package/rules/vue/testing/testing.mdc +40 -0
  251. package/rules/vue/tfm-translations/concern.json +7 -0
  252. package/rules/vue/tfm-translations/docs/main.md +29 -0
  253. package/rules/vue/tfm-translations/main.mjs +55 -0
  254. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  255. package/rules/vue/vite-config/concern.json +3 -0
  256. package/rules/vue/vite-config/vite-config.mdc +153 -0
  257. package/rules/vue/vite-env/concern.json +3 -0
  258. package/rules/vue/vite-env/vite-env.mdc +61 -0
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-package_json.mjs
4
+ resource: plugins/lang-js/rules/bun/package_json/fix-package_json.mjs
5
+ docgen:
6
+ crc: cf834366
7
+ model: openai-codex/gpt-5.4-mini
8
+ score: 100
9
+ issues: judge:inaccurate:0.97
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Файл знаходить `package.json` із порушеннями policy `bun/package_json` серед заборонених top-level полів і `scripts.lint` / `scripts.lint-*` та запускає T0-autofix для цих випадків. Він видаляє top-level поля за каноном `template/package.json.deny.json` і `package.json.deny.json`, а lint-скрипти переписує на канонічний `bunx n-rules lint ...` лише після репо-wide пошуку всіх їхніх викликів; нерозпізнані виклики лишаються без змін, і в такому разі скрипт не видаляється. Помилки обробляє fail-safe і не кидає назовні.
16
+
17
+ ## Поведінка
18
+
19
+ 1. `patterns` знаходить package.json, у яких є порушення policy для `bun/package_json`, і запускає T0-autofix лише для них.
20
+ 2. Для кожного такого package.json вона орієнтується на канон deny-списку з `template/package.json.deny.json` і прибирає заборонені top-level поля.
21
+ 3. Якщо в `package.json` є `scripts.lint` або `scripts.lint-*`, вона спершу шукає їхні виклики в репозиторії та переписує їх на канонічний `bunx n-rules lint ...`, щоб не зламати споживачів цих скриптів.
22
+ 4. Власні скрипти цього ж `package.json` теж оновлюються до канонічного запуску, якщо вони посилаються на lint-скрипти, що плануються до видалення.
23
+ 5. Скрипт видаляє `scripts.lint*` лише тоді, коли для них не лишається нерозпізнаних викликів; якщо десь є незрозумілий спосіб запуску, він лишає скрипт на місці й лише повідомляє про це.
24
+ 6. Після змін вона записує оновлений `package.json` і повертає перелік зачеплених файлів та короткий підсумок змін; назовні помилки не пробиваються.
25
+
26
+ ## Гарантії поведінки
27
+
28
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/bun/package_json
4
+ resource: plugins/lang-js/rules/bun/package_json/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------------------------------- | --------- |
9
+ | [fix-package_json.mjs](fix-package_json.md) | JS Module |
@@ -0,0 +1,311 @@
1
+ /**
2
+ * T0-autofix для policy-concern-а `bun/package_json`: видаляє заборонені top-level поля
3
+ * (канон — `template/package.json.deny.json`, той самий `data.template.deny`, що бачить rego)
4
+ * і `scripts.lint` / `scripts.lint-*` (bun.mdc — лінт лише через `n-rules lint`, не npm-скрипти).
5
+ *
6
+ * Просте видалення `scripts.lint*` небезпечне: якщо десь у репо (workflow yml, інший
7
+ * npm-скрипт) є виклик `bun run lint-js` тощо, видалення ключа зламає цей виклик.
8
+ * Тому перед видаленням шукаємо всі виклики кожного lint-скрипта репо-вайд і переписуємо
9
+ * їх на прямий `bunx n-rules lint <surface>` (canonical — той самий, що вимагають
10
+ * lint_js_yml/lint_style_yml). Скрипт видаляється лише якщо ВСІ його виклики вдалось
11
+ * переписати (файли, де виклик не розпізнано — залишаємо як є, скрипт теж лишається,
12
+ * щоб не зламати консьюмера мовчки).
13
+ */
14
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
15
+ import { join } from 'node:path'
16
+
17
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
18
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
19
+
20
+ const LINT_SCRIPT_RE = /^lint(-.*)?$/u
21
+ const WORKFLOW_YML_RE = /^\.github\/workflows\/.*\.ya?ml$/u
22
+ const LOCKFILE_RE = /(^|\/)(bun\.lockb?|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$/u
23
+ const PACKAGE_MANAGER_SCRIPT_PREFIX_RE = /\b(?:(?:bun|yarn|pnpm)(?:\s+run)?|npm\s+run)\s+/gu
24
+ const SCRIPT_NAME_CONTINUATION_RE = /[\w-]/u
25
+
26
+ // `lint-<suffix>` → rule-id канонічного `n-rules lint <rule-id>`; bare `lint` → без rule-id.
27
+ const SURFACE_MAP = { image: 'image-compress' }
28
+
29
+ /**
30
+ * @param {string} scriptName напр. `lint-js`, `lint`
31
+ * @returns {string} суфікс аргументу для `n-rules lint` (порожній рядок для bare `lint`)
32
+ */
33
+ function surfaceArgFor(scriptName) {
34
+ const suffix = scriptName.slice('lint'.length)
35
+ if (suffix.length === 0) return ''
36
+ const normalized = suffix.startsWith('-') ? suffix.slice(1) : suffix
37
+ return ` ${SURFACE_MAP[normalized] ?? normalized}`
38
+ }
39
+
40
+ /**
41
+ * Знаходить діапазони виклику package-manager-ом заданого npm-скрипта: `bun[ run] X`,
42
+ * `yarn[ run] X`, `pnpm[ run] X`, `npm run X` (голий `npm X` — не валідний npm-синтаксис,
43
+ * окрім start/test). Right-boundary — НЕ `\b` (hyphen — non-word char, тож `lint\b`
44
+ * матчить всередині `lint-js`), а перевірка проти `[\w-]`, щоб `lint` не «зжирав»
45
+ * префікс `lint-js`.
46
+ * @param {string} content вміст для пошуку
47
+ * @param {string} scriptName ім'я npm-скрипта
48
+ * @returns {Array<[number, number]>} діапазони повного виклику в content
49
+ */
50
+ function invocationRanges(content, scriptName) {
51
+ /** @type {Array<[number, number]>} */
52
+ const ranges = []
53
+ PACKAGE_MANAGER_SCRIPT_PREFIX_RE.lastIndex = 0
54
+ let match
55
+ while ((match = PACKAGE_MANAGER_SCRIPT_PREFIX_RE.exec(content))) {
56
+ const scriptStart = PACKAGE_MANAGER_SCRIPT_PREFIX_RE.lastIndex
57
+ if (!content.startsWith(scriptName, scriptStart)) continue
58
+ const scriptEnd = scriptStart + scriptName.length
59
+ if (SCRIPT_NAME_CONTINUATION_RE.test(content[scriptEnd] ?? '')) continue
60
+ ranges.push([match.index, scriptEnd])
61
+ }
62
+ return ranges
63
+ }
64
+
65
+ /**
66
+ * Читає JSON-файл template-а з deny-полями концерну; відсутній/невалідний → {}.
67
+ * @param {string} path абсолютний шлях
68
+ * @returns {Record<string, string>} мапа `field -> reason`
69
+ */
70
+ function readDenyTemplate(path) {
71
+ if (!existsSync(path)) return {}
72
+ try {
73
+ return JSON.parse(readFileSync(path, 'utf8'))
74
+ } catch {
75
+ return {}
76
+ }
77
+ }
78
+
79
+ /**
80
+ * `workflow`/`package-json` — відомий, безпечно переписуваний формат; `other` — будь-який
81
+ * інший текстовий файл (Makefile, README, shell-скрипт…), де ми ЛИШЕ детектуємо виклик,
82
+ * але не переписуємо (немає надійного canonical-заміщення для довільного формату).
83
+ * @typedef {'workflow'|'package-json'|'other'} FileKind
84
+ */
85
+
86
+ /**
87
+ * @param {string} rel posix-відносний шлях від кореня репо
88
+ * @param {string} abs абсолютний шлях
89
+ * @param {string} rootPkgAbs абсолютний шлях кореневого package.json (сам він — не кандидат)
90
+ * @returns {FileKind|null} категорія файлу, або null якщо файл взагалі не кандидат (лок-файли)
91
+ */
92
+ function classifyCandidate(rel, abs, rootPkgAbs) {
93
+ if (LOCKFILE_RE.test(rel)) return null
94
+ if (WORKFLOW_YML_RE.test(rel)) return 'workflow'
95
+ if (rel.endsWith('package.json')) return abs === rootPkgAbs ? null : 'package-json'
96
+ return 'other'
97
+ }
98
+
99
+ /**
100
+ * Знаходить УСІ файли репозиторію (крім кореневого package.json і лок-файлів) як кандидатів
101
+ * на пошук викликів npm-скриптів — виклик може бути де завгодно (Makefile, README, CI,
102
+ * інший package.json), не лише у відомих форматах.
103
+ * @param {string} cwd абсолютний корінь репозиторію
104
+ * @param {string} rootPkgAbs абсолютний шлях кореневого package.json (виключити)
105
+ * @returns {Promise<Array<{ abs: string, kind: FileKind }>>} кандидати з категорією
106
+ */
107
+ async function findUsageCandidateFiles(cwd, rootPkgAbs) {
108
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
109
+ /** @type {Array<{ abs: string, kind: FileKind }>} */
110
+ const out = []
111
+ await walkDir(
112
+ cwd,
113
+ abs => {
114
+ const rel = abs
115
+ .slice(cwd.length + 1)
116
+ .split('\\')
117
+ .join('/')
118
+ const kind = classifyCandidate(rel, abs, rootPkgAbs)
119
+ if (kind) out.push({ abs, kind })
120
+ },
121
+ ignorePaths
122
+ )
123
+ return out
124
+ }
125
+
126
+ /**
127
+ * Переписує один файл-кандидат: кожен знайдений виклик `scriptName` замінюється на
128
+ * канонічний `bunx n-rules lint<surface>` (workflow yml — з `--no-fix`, package.json
129
+ * scripts-чейни — без, дев-контекст хоче autofix).
130
+ * @param {string} content вміст файлу
131
+ * @param {string} scriptName ім'я lint-скрипта
132
+ * @param {boolean} isWorkflow чи це workflow yml (інакше — package.json)
133
+ * @returns {{ content: string, matched: boolean }} новий вміст і чи був матч
134
+ */
135
+ function rewriteUsages(content, scriptName, isWorkflow) {
136
+ const ranges = invocationRanges(content, scriptName)
137
+ if (ranges.length === 0) return { content, matched: false }
138
+ const canonical = `bunx n-rules lint${surfaceArgFor(scriptName)}${isWorkflow ? ' --no-fix' : ''}`
139
+ let next = ''
140
+ let lastIndex = 0
141
+ for (const [start, end] of ranges) {
142
+ next += content.slice(lastIndex, start) + canonical
143
+ lastIndex = end
144
+ }
145
+ next += content.slice(lastIndex)
146
+ return { content: next, matched: true }
147
+ }
148
+
149
+ /**
150
+ * Для кожного lint-скрипта шукає й переписує його виклики по всіх кандидатах.
151
+ * `workflow`/`package-json` — переписуємо на канонічний `bunx n-rules lint`; `other`
152
+ * (Makefile, README, довільний shell) — лише детектуємо, без canonical-заміщення для
153
+ * довільного формату скрипт НЕ видаляємо (блокуючий, а не мовчки проігнорований, збіг).
154
+ * @param {string[]} scriptNames кандидати на видалення (`lint`, `lint-js`, …)
155
+ * @param {Array<{ abs: string, kind: FileKind }>} candidateFiles файли-кандидати з категорією
156
+ * @param {(absPath: string) => void} recordWrite реєстрація запису для rollback
157
+ * @returns {{ safeToRemove: Set<string>, touchedFiles: string[] }} скрипти без залишкових
158
+ * викликів (безпечно видалити) і список фактично змінених файлів
159
+ */
160
+ function adaptUsages(scriptNames, candidateFiles, recordWrite) {
161
+ const unresolved = new Set()
162
+ const touchedFiles = []
163
+ const sortedNames = scriptNames.toSorted((a, b) => b.length - a.length)
164
+
165
+ for (const { abs, kind } of candidateFiles) {
166
+ if (kind === 'other') continue
167
+ const isWorkflow = kind === 'workflow'
168
+ let content
169
+ try {
170
+ content = readFileSync(abs, 'utf8')
171
+ } catch {
172
+ continue
173
+ }
174
+ let next = content
175
+ // Найдовші імена першими (defense-in-depth поряд із right-boundary lookahead) —
176
+ // `lint-js` перепишеться до того, як коротший `lint` встигне зачепити його префікс.
177
+ for (const name of sortedNames) next = rewriteUsages(next, name, isWorkflow).content
178
+ if (next === content) continue
179
+ recordWrite?.(abs)
180
+ writeFileSync(abs, next)
181
+ touchedFiles.push(abs)
182
+ }
183
+
184
+ // Другий прохід (після переписів) — чи лишився десь виклик, який ми не розпізнали
185
+ // (`other`-формат або невідомий package-manager-синтаксис); такий скрипт НЕ видаляємо.
186
+ for (const { abs } of candidateFiles) {
187
+ let content
188
+ try {
189
+ content = readFileSync(abs, 'utf8')
190
+ } catch {
191
+ continue
192
+ }
193
+ for (const name of scriptNames) {
194
+ if (invocationRanges(content, name).length > 0) unresolved.add(name)
195
+ }
196
+ }
197
+
198
+ return { safeToRemove: new Set(scriptNames.filter(n => !unresolved.has(n))), touchedFiles }
199
+ }
200
+
201
+ /**
202
+ * Адаптує виклики lint-скриптів в ІНШИХ скриптах ТОГО Ж package.json (напр. `precommit`:
203
+ * `bun run lint-js && bun test`) — цей файл не потрапляє у зовнішній `findUsageCandidateFiles`,
204
+ * бо він же ціль видалення. Мутує `pkg.scripts` на місці.
205
+ * @param {object} pkg розпарсений package.json (мутується)
206
+ * @param {string[]} scriptNames кандидати на видалення
207
+ * @returns {Set<string>} імена, для яких лишився нерозпізнаний виклик у власних скриптах
208
+ */
209
+ function adaptOwnScripts(pkg, scriptNames) {
210
+ const unresolved = new Set()
211
+ if (!pkg.scripts || typeof pkg.scripts !== 'object') return unresolved
212
+
213
+ const sorted = scriptNames.toSorted((a, b) => b.length - a.length)
214
+ const otherEntries = Object.entries(pkg.scripts).filter(
215
+ ([key, value]) => !scriptNames.includes(key) && typeof value === 'string'
216
+ )
217
+
218
+ for (const [key, value] of otherEntries) {
219
+ let next = value
220
+ for (const name of sorted) next = rewriteUsages(next, name, false).content
221
+ if (next !== value) pkg.scripts[key] = next
222
+ }
223
+ // Пере-перевірка після мутації: чи лишився десь нерозпізнаний виклик.
224
+ for (const [key] of otherEntries) {
225
+ const current = pkg.scripts[key]
226
+ if (typeof current !== 'string') continue
227
+ for (const name of scriptNames) {
228
+ if (invocationRanges(current, name).length > 0) unresolved.add(name)
229
+ }
230
+ }
231
+ return unresolved
232
+ }
233
+
234
+ /**
235
+ * Видаляє заборонені top-level поля й `scripts.lint*` (лише ті, чиї виклики вже адаптовано)
236
+ * з `package.json`.
237
+ * @param {object} pkg розпарсений package.json
238
+ * @param {Record<string, string>} denyFields канон заборонених top-level полів
239
+ * @param {Set<string>} safeToRemove скрипти без залишкових зовнішніх викликів
240
+ * @returns {string[]} список видалених ключів (для message)
241
+ */
242
+ function stripDenied(pkg, denyFields, safeToRemove) {
243
+ const removed = []
244
+ for (const field of Object.keys(denyFields)) {
245
+ if (!Object.hasOwn(pkg, field)) continue
246
+ delete pkg[field]
247
+ removed.push(field)
248
+ }
249
+ if (pkg.scripts && typeof pkg.scripts === 'object') {
250
+ for (const name of Object.keys(pkg.scripts)) {
251
+ if (!LINT_SCRIPT_RE.test(name) || !safeToRemove.has(name)) continue
252
+ delete pkg.scripts[name]
253
+ removed.push(`scripts.${name}`)
254
+ }
255
+ }
256
+ return removed
257
+ }
258
+
259
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
260
+ export const patterns = [
261
+ {
262
+ id: 'bun-package_json-strip-denied',
263
+ test: violations => violations.some(v => v.reason === 'policy-deny' && v.file),
264
+ apply: async (violations, ctx) => {
265
+ const denyFields = readDenyTemplate(join(ctx.concernDir ?? '', 'template', 'package.json.deny.json'))
266
+ const files = [...new Set(violations.filter(v => v.file).map(v => v.file))]
267
+ const touchedFiles = []
268
+ const messages = []
269
+
270
+ for (const rel of files) {
271
+ const abs = join(ctx.cwd, rel)
272
+ let pkg
273
+ try {
274
+ pkg = JSON.parse(readFileSync(abs, 'utf8'))
275
+ } catch {
276
+ continue
277
+ }
278
+
279
+ const lintScriptNames =
280
+ pkg.scripts && typeof pkg.scripts === 'object'
281
+ ? Object.keys(pkg.scripts).filter(n => LINT_SCRIPT_RE.test(n))
282
+ : []
283
+
284
+ let safeToRemove = new Set(lintScriptNames)
285
+ if (lintScriptNames.length > 0) {
286
+ const ownUnresolved = adaptOwnScripts(pkg, lintScriptNames)
287
+ const candidateFiles = await findUsageCandidateFiles(ctx.cwd, abs)
288
+ const adapted = adaptUsages(lintScriptNames, candidateFiles, ctx.recordWrite)
289
+ safeToRemove = new Set([...adapted.safeToRemove].filter(n => !ownUnresolved.has(n)))
290
+ touchedFiles.push(...adapted.touchedFiles)
291
+ const skipped = lintScriptNames.filter(n => !safeToRemove.has(n))
292
+ if (skipped.length > 0) {
293
+ messages.push(
294
+ `${rel}: не видаляю scripts.${skipped.join(', scripts.')} — знайдено нерозпізнаний виклик деінде`
295
+ )
296
+ }
297
+ }
298
+
299
+ const removed = stripDenied(pkg, denyFields, safeToRemove)
300
+ if (removed.length === 0) continue
301
+
302
+ ctx.recordWrite?.(abs)
303
+ writeFileSync(abs, `${JSON.stringify(pkg, null, 2)}\n`)
304
+ touchedFiles.push(abs)
305
+ messages.push(`${rel}: -${removed.join(', -')}`)
306
+ }
307
+
308
+ return touchedFiles.length > 0 ? { touchedFiles, message: messages.join('; ') } : { touchedFiles: [] }
309
+ }
310
+ }
311
+ ]
@@ -0,0 +1,14 @@
1
+ ## Rego-gate: заборонені поля та devDependencies у кореневому package.json
2
+
3
+ Rego-пакет: `bun.package_json`
4
+
5
+ Цільовий файл: кореневий `package.json`.
6
+
7
+ Список заборонених top-level полів (template-driven): [package.json.deny.json](./template/package.json.deny.json)
8
+
9
+ Gate виносить два класи deny:
10
+
11
+ 1. **Заборонені top-level поля** — будь-яке поле з `package.json.deny.json` присутнє у файлі (навіть із порожнім значенням `{}`).
12
+ 2. **devDependencies не з білого списку** — дозволені лише `@nitra/*`/`@7n/*` та root-only тестові peer/tools (`vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`, `@playwright/test`). Будь-який інший пакет → deny. CLI-тули, які `n-rules lint` спавнить через `bunx` (oxlint, jscpd, v8r, github-actionlint тощо), у root не пінуються — вони приїжджають як `dependencies` пакета `@7n/rules` (npm-module.mdc).
13
+
14
+ Перевірки, що потребують FS або cross-file контексту (наприклад наявність `yarn.lock`), лишаються у JS-шарі.
@@ -0,0 +1,64 @@
1
+ # Перевірка кореневого `package.json` для bun (bun.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "deny": ... } }
4
+ # Структура --data сформована з template/package.json.deny.json
5
+ # (top-level fields заборонені у root).
6
+ #
7
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
8
+ # - `devDependencies` лише `@nitra/*` + root-only тестові peer/tools для `@7n/test coverage`
9
+ # (правило `test` enabled завжди — див. `test/auto.md`; published workspace-и не мають
10
+ # `devDependencies` за `npm-module.mdc`)
11
+ #
12
+ # Перевірки, які потребують FS / cross-file контексту, лишаються у JS.
13
+ package bun.package_json
14
+
15
+ import rego.v1
16
+
17
+ # ── deny: заборонені top-level поля (template-driven) ─────────────────────
18
+
19
+ # Сентинельний value відрізняє «поле відсутнє» від «поле є з будь-яким значенням»
20
+ # (наприклад `dependencies: {}` — присутнє але порожнє → теж заборонено).
21
+ deny contains msg if {
22
+ some field, reason in data.template.deny
23
+ object.get(input, field, "__bun_missing__") != "__bun_missing__"
24
+ msg := sprintf("package.json: поле %s — %s", [field, reason])
25
+ }
26
+
27
+ # ── deny: scripts.lint / scripts.lint-* заборонені (bun.mdc lint) ────────
28
+
29
+ deny contains msg if {
30
+ is_object(input.scripts)
31
+ some script_name, _ in input.scripts
32
+ regex.match(`^lint(-.*)?$`, script_name)
33
+ msg := sprintf(
34
+ "package.json: scripts.%s заборонений — лінт запускається через n-rules lint, не через package.json-скрипти (bun.mdc)",
35
+ [script_name],
36
+ )
37
+ }
38
+
39
+ # ── deny: devDependencies — лише `@nitra/*`/`@7n/*` + root-only тестові peer/tools ─
40
+
41
+ deny contains msg if {
42
+ is_object(input.devDependencies)
43
+ some name, _ in input.devDependencies
44
+ not allowed_root_dev_dependency(name)
45
+ msg := sprintf("Кореневі devDependencies: дозволені лише @nitra/*/@7n/* або root-only test peers — прибери або перенеси: %s (bun.mdc)", [name])
46
+ }
47
+
48
+ # ── helpers ────────────────────────────────────────────────────────────────
49
+
50
+ # @stryker-mutator/core — обов'язковий exact-pin peer vitest-runner@9+ (раніше тягнувся транзитивно)
51
+ # @7n/test — оркестратор `coverage` (npx @7n/test coverage); devDependency, щоб npx резолвив
52
+ # локально без мережевого fetch щоразу.
53
+ allowed_root_test_deps := {"vitest", "@vitest/coverage-v8", "@stryker-mutator/vitest-runner", "@stryker-mutator/core", "@playwright/test", "@7n/test"}
54
+
55
+ allowed_root_dev_dependency(name) if {
56
+ startswith(name, "@nitra/")
57
+ } else if {
58
+ startswith(name, "@7n/")
59
+ } else if {
60
+ # Vitest/Stryker peer/tools + @7n/test (`coverage`) для `@7n/test coverage` живуть у корені
61
+ # будь-якого монорепо-споживача: правило `test` enabled завжди, а published workspace-и
62
+ # не мають `devDependencies` (`npm-module.mdc`).
63
+ name in allowed_root_test_deps
64
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "packageManager": "видали поле — Bun не потребує packageManager (bun.mdc)",
3
+ "dependencies": "кореневий package.json не повинен містити dependencies — додай у workspace-пакети (bun.mdc)"
4
+ }
@@ -0,0 +1,26 @@
1
+ ## `for...in` заборонено — рефакторити на `for...of`
2
+
3
+ Конструкція `for (const k in obj)` обходить успадковані ключі прототипу, тому майже завжди тягне `Object.hasOwn(obj, k)`-guard. Заборонена у `@nitra/eslint-config` через `no-restricted-syntax` для `ForInStatement` (з версії **3.8.0**). У каноні oxlint (`guard-for-in: "deny"` у [oxlint-canonical.json](../tooling/data/tooling/oxlint-canonical.json)) лишається `guard-for-in` як часткова страховка (oxlint не підтримує `no-restricted-syntax`) — `checkOxlintRc` у `check/main.mjs` звіряє `.oxlintrc.json` проєкту з цим каноном.
4
+
5
+ Замість цього обходь масив напряму, а об'єкт — через `Object.entries` / `Object.keys` / `Object.values`. У такому коді guard за `Object.hasOwn` стає непотрібним і має зникнути разом із `for...in`.
6
+
7
+ ```javascript title="❌ до"
8
+ for (const k in obj) {
9
+ if (!Object.hasOwn(obj, k)) continue
10
+ use(k, obj[k])
11
+ }
12
+
13
+ for (const i in arr) {
14
+ use(arr[i])
15
+ }
16
+ ```
17
+
18
+ ```javascript title="✅ після"
19
+ for (const [k, v] of Object.entries(obj)) {
20
+ use(k, v)
21
+ }
22
+
23
+ for (const item of arr) {
24
+ use(item)
25
+ }
26
+ ```
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": [
6
+ "eslint.config.js",
7
+ "eslint.config.mjs",
8
+ "**/package.json",
9
+ ".oxlintrc.json",
10
+ ".github/workflows/lint-js.yml",
11
+ ".github/workflows/lint.yml",
12
+ "knip.json",
13
+ ".eslintrc",
14
+ ".eslintrc.js",
15
+ ".eslintrc.json",
16
+ ".eslintrc.yml"
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: JS Module
3
+ title: eslint-config.mjs
4
+ resource: plugins/lang-js/rules/js/check/eslint-config.mjs
5
+ docgen:
6
+ crc: 6626a862
7
+ model: manual
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ Детекція воркспейс-типів (node/vue) репозиторію і чистий (read-only) планувальник
13
+ scaffold/merge для `eslint.config.js` consumer-репо. Спільне джерело правди для
14
+ detector-а `main.mjs` (перевірка, що кожен vue-воркспейс присутній у `vue: [...]`
15
+ аргументах `getConfig`) і T0-фіксера `fix-check.mjs` (детерміноване створення або
16
+ хірургічне оновлення конфігу — без повного перезапису файлу).
17
+
18
+ ## Поведінка
19
+
20
+ - Тип воркспейсу визначається за root `package.json`: записи `workspaces`
21
+ (glob-и типу `packages/*` розгортаються у наявні директорії); воркспейс — vue,
22
+ якщо його `package.json` має `vue`/`nuxt` у deps або в ньому є хоч один `.vue`
23
+ файл (`node_modules`/`dist` ігноруються). Без `workspaces` класифікується сам
24
+ корінь як `.`.
25
+ - Merge наявного конфігу — хірургічний: додає відсутній `**/auto-imports.d.ts`
26
+ у перший `ignores: [...]`, вставляє відсутні vue-воркспейси у `vue: [...]`
27
+ (або нову властивість одразу після `getConfig({`), вилучає ці ж воркспейси з
28
+ `node: [...]`. Кастомні ignores, overrides і коментарі не змінюються.
29
+ - Fail-safe: якщо структура конфігу не розпізнається (немає `getConfig({`),
30
+ файл не чіпається — порушення лишається для ручного розгляду.
31
+
32
+ ## Публічний API
33
+
34
+ - `detectWorkspaceTypes(cwd)` → `{ node: string[], vue: string[] }` — директорії
35
+ за типами.
36
+ - `parseVueList(raw)` → нормалізовані записи `vue: [...]` з тексту конфігу.
37
+ - `renderEslintConfigScaffold(types)` → повний шаблон `eslint.config.js`
38
+ (лише непорожні типи).
39
+ - `mergeEslintConfig(raw, types)` → новий вміст (`=== raw`, якщо merge
40
+ неможливий чи не потрібен).
41
+ - `planEslintConfigFix(cwd)` → `{ path, content, message } | null` — ідемпотентний
42
+ план для T0 (`null` на виправленому дереві).
43
+ - Константи reason-ів: `ESLINT_CONFIG_MISSING`, `ESLINT_CONFIG_IGNORES`,
44
+ `ESLINT_CONFIG_VUE_WORKSPACE`; `AUTO_IMPORTS_IGNORE`.
45
+
46
+ ## Де використовується
47
+
48
+ - `npm/rules/js/check/main.mjs` — detector (reason-и порушень, перевірка
49
+ vue-воркспейсів).
50
+ - `npm/rules/js/check/fix-check.mjs` — T0-фіксер (виконання плану).
51
+
52
+ ## Гарантії поведінки
53
+
54
+ - Read-only: модуль лише читає ФС і повертає план; запис виконує викликач.
55
+ - Ідемпотентність: повторний `planEslintConfigFix` на виправленому дереві → `null`.
56
+ - Биті/відсутні JSON-файли не кидають виняток (повертається `null`-об'єкт).
@@ -0,0 +1,48 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-check.mjs
4
+ resource: plugins/lang-js/rules/js/check/fix-check.mjs
5
+ docgen:
6
+ crc: d4e460e8
7
+ model: manual
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ T0-autofix для `js/check`: два детерміновані патерни, обидва без LLM.
13
+
14
+ - `js-check-eslint-config` — scaffold/merge `eslint.config.js` за планом
15
+ `planEslintConfigFix` (детекція воркспейс-типів node/vue). Раніше ці
16
+ порушення йшли у LLM-ладдер, який переписував конфіг цілком і вгадував типи
17
+ (інцидент: у vue-монорепо записано `getConfig({ node: ['npm'] })` — eslint
18
+ перестав обробляти .vue файли).
19
+ - `js-check-oxlintrc` — scaffold/merge `.oxlintrc.json` за планом
20
+ `planOxlintrcFix` (`../tooling/main.mjs`). Раніше ці порушення теж йшли у
21
+ LLM-ладдер: 15 КБ канону дешева модель не відтворює byte-perfect (verify
22
+ fail), а дорожча не встигає за один rung-таймаут.
23
+
24
+ ## Поведінка
25
+
26
+ - `js-check-eslint-config` тригериться на reason-и `eslint-config-missing` /
27
+ `eslint-config-ignores` / `eslint-config-vue-workspace`. `apply` перераховує
28
+ план із поточного стану диска (ідемпотентно): відсутній конфіг —
29
+ створюється з детектованими типами; наявний — зазнає хірургічного merge, без
30
+ повного перезапису. Якщо план порожній (`null`) — жодних змін
31
+ (`touchedFiles: []`).
32
+ - `js-check-oxlintrc` тригериться на reason-и `oxlintrc-missing` /
33
+ `oxlintrc-drift` (`OXLINTRC_MISSING` / `OXLINTRC_DRIFT` з `../tooling/main.mjs`).
34
+ `apply` читає наявний `.oxlintrc.json` (`null`, якщо відсутній або невалідний
35
+ JSON) і канон, будує злитий обʼєкт через `planOxlintrcFix` і перезаписує
36
+ файл цілком (JSON, 2-space, з кінцевим переносом рядка).
37
+ - Решта порушень `js/check` (engines, workflows) — поза цим T0, стандартний
38
+ шлях (ladder/manual).
39
+ - Обидва патерни викликають `ctx.recordWrite` перед записом (pre-image для
40
+ central rollback).
41
+
42
+ ## Гарантії поведінки
43
+
44
+ - `js-check-eslint-config` записує лише `eslint.config.js`/`eslint.config.mjs`
45
+ у корені репо; наявний конфіг ніколи не перезаписується цілком — тільки
46
+ точкові вставки/заміни.
47
+ - `js-check-oxlintrc` записує лише `.oxlintrc.json` у корені репо; результат
48
+ завжди проходить `verifyOxlintRcAgainstCanonical` без ручного втручання.
@@ -0,0 +1,13 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/js/check
4
+ resource: plugins/lang-js/rules/js/check/
5
+ ---
6
+
7
+ # npm/rules/js/check
8
+
9
+ | Файл | Тип |
10
+ | ------------------------------------- | --------- |
11
+ | [eslint-config.mjs](eslint-config.md) | JS Module |
12
+ | [fix-check.mjs](fix-check.md) | JS Module |
13
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,48 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/js/check/main.mjs
5
+ docgen:
6
+ crc: 1da6a724
7
+ model: manual
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ Detector concern-а `js/check`: read-only перевірка відповідності проєкту правилам
13
+ `js.mdc`. Накопичує порушення через violation-reporter і повертає їх runner-у;
14
+ нічого не друкує і (за одним винятком — baseline `knip.json`) нічого не пише.
15
+
16
+ ## Поведінка
17
+
18
+ - `eslint.config.js`/`eslint.config.mjs`: файл існує (інакше reason
19
+ `eslint-config-missing`), містить `getConfig` та імпорт `@nitra/eslint-config`,
20
+ в ignores є `**/auto-imports.d.ts` (reason `eslint-config-ignores`).
21
+ - Воркспейс-типи: кожен vue-воркспейс (детекція з `eslint-config.mjs` — root
22
+ `workspaces` + vue/nuxt-залежність або `.vue` файли) має бути у `vue: [...]`
23
+ аргументах `getConfig`, інакше reason `eslint-config-vue-workspace` — без
24
+ цього eslint не парсить `.vue` файли воркспейсу. Ці три reason-и детерміновано
25
+ виправляє T0 `fix-check.mjs`.
26
+ - Кожен workspace `package.json`: `"type": "module"`, `engines.node >= 24`,
27
+ `engines.bun >= 1.3` (кореневий `package.json` валідує Rego
28
+ `npm/policy/js_lint/package_json/`).
29
+ - `.oxlintrc.json`: існує, валідний JSON, збігається з каноном oxlint із пакета
30
+ `@7n/rules` (`verifyOxlintRcAgainstCanonical` з `../tooling/main.mjs`).
31
+ Відсутність — reason `oxlintrc-missing`, розходження з каноном — reason
32
+ `oxlintrc-drift` (константи `OXLINTRC_MISSING`/`OXLINTRC_DRIFT`); обидва
33
+ детерміновано виправляє T0 `fix-check.mjs` (патерн `js-check-oxlintrc`).
34
+ - `.github/workflows/lint-js.yml`: існує; `lint.yml` (якщо є) не дублює
35
+ oxlint/eslint/jscpd кроки.
36
+ - `knip.json`: якщо відсутній — копіюється канонічний baseline із пакета
37
+ (side effect, описаний у js.mdc); подальший вміст локально не валідується.
38
+ - Застарілі конфіги ESLint (`.eslintrc`, `.eslintrc.js`, `.eslintrc.json`,
39
+ `.eslintrc.yml`) — порушення: лише flat config.
40
+
41
+ ## Публічний API
42
+
43
+ - `lint(ctx)` → `LintResult` — перелік порушень concern-а.
44
+
45
+ ## Гарантії поведінки
46
+
47
+ - Read-only, крім єдиного side effect — створення `knip.json` з канону.
48
+ - Порушення повертаються reporter-ом, не друкуються.