@7n/rules-lang-js 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +12 -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 +31 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +89 -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,22 @@
1
+ {
2
+ "dependencies": {
3
+ "bunyan": "використовуй стандартні логери (js-run.mdc)",
4
+ "@nitra/bunyan": "використовуй стандартні логери (js-run.mdc)"
5
+ },
6
+ "devDependencies": {
7
+ "bunyan": "використовуй стандартні логери (js-run.mdc)",
8
+ "@nitra/bunyan": "використовуй стандартні логери (js-run.mdc)"
9
+ },
10
+ "scriptsForbidden": [
11
+ {
12
+ "id": "node-runner",
13
+ "pattern": "\\bnode(\\s|$)",
14
+ "message": "заміни `node` на `bun` у scripts — один runtime у dev і prod (js-run.mdc)"
15
+ },
16
+ {
17
+ "id": "env-cat-bun",
18
+ "pattern": "\\benv\\s+\\$\\(cat\\s+[^)]+\\)\\s+bun\\b",
19
+ "message": "заміни `env $(cat A B) bun` на `bun --env-file=A --env-file=B` (нативний Bun, js-run.mdc)"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,11 @@
1
+ ## Структура проекту
2
+
3
+ Рекомендується використовувати таку структуру проекту:
4
+
5
+ ```
6
+ k8s/ # тут всі файли для деплойменту в Kubernetes, включаючи kustomize
7
+ src/ # тут всі файли необхідні для роботи проекту
8
+ Dockerfile
9
+ package.json
10
+ readme.md
11
+ ```
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": [
6
+ "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}",
7
+ "**/package.json",
8
+ "**/jsconfig.json",
9
+ "**/k8s/base/configmap.yaml"
10
+ ]
11
+ }
12
+ }
@@ -0,0 +1,27 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-runtime.mjs
4
+ resource: plugins/lang-js/rules/js-run/runtime/fix-runtime.mjs
5
+ docgen:
6
+ crc: a357e26a
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
10
+ ---
11
+
12
+ Процес T0-autofix автоматично створює стандартизований `jsconfig.json` у backend-пакетах, що знаходяться в каталозі `src/`, якщо такий конфігураційний файл відсутній. Для визначення канонічного вигляду цього файлу використовується шаблон, визначений у `policy/jsconfig/template/jsconfig.json.snippet.json`.
13
+
14
+ Конфіги, на які спирається код: jsconfig.json, jsconfig.json.snippet.json
15
+
16
+ ## Поведінка
17
+
18
+ 1. Ініціалізується контент канонічного файлу `jsconfig.json` шляхом читання з `jsconfig.json.snippet.json`.
19
+ 2. Протягується масив `patterns` для ідентифікації робочих простірців, яким бракує файлу `jsconfig.json`.
20
+ 3. Для кожного шаблону в `patterns` перевіряється наявність підходящого вихідного стану.
21
+ 4. Якщо шаблон спрацьовує, здійснюється пошук усіх відповідних робочих простірців у контексті.
22
+ 5. Для кожного знайденого робочого простірця, який містить каталог `src/` та не має `jsconfig.json`, відбувається запис канонічного контенту у файл `jsconfig.json`.
23
+ 6. У результаті, що створюються файли `jsconfig.json` у потрібних пакетах фіксується та повертається відповідний статус.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/js-run/runtime/main.mjs
5
+ docgen:
6
+ crc: 9e5d4e7b
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Цей файл забезпечує відповідність проєкту затвердженим правилам `js-run.mdc`, спираючись на конфігурації `jsconfig.json` та `package.json`. Він перевіряє конфігурації, імпорти, неймінг, змінні середовища та таймери для кожного пакета, ігноруючи перевірку для frontend-пакетів, що використовують `vite`.
16
+
17
+ ## Поведінка
18
+
19
+ Поведінка:
20
+ pgWriteContract: Не є публічною функцією.
21
+ main: Перевіряє відповідність проєкту правилам `js-run.mdc` для кожного workspace-пакета, пропускаючи перевірку для frontend-пакетів, що використовують `vite`. Для пакетів відбувається валідація конфігурацій, імпортів, неймінгу файлів, використання змінних середовища та таймерів.
22
+
23
+ ## Публічний API
24
+
25
+ _Конфігураційні файли, на які спирається код: jsconfig.json, package.json_
26
+
27
+ - pgWriteContract — Формує та впроваджує контракт до бази даних, використовуючи специфічний інструментарій.\*
28
+ - main — Аудитує відповідність правил виконання JavaScript для окремих пакетів у структурі workspace, ігноруючи корінь репозиторію.
29
+
30
+ ## Гарантії поведінки
31
+
32
+ - Read-only: не виконує операцій запису (ФС/БД).
33
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
34
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
35
+ - Свідомо пропускає шляхи: `base/`.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * T0-autofix для `js-run/runtime` — детерміноване створення канонічного
3
+ * `jsconfig.json` у backend-пакетах із `src/` де він відсутній. Канон читається
4
+ * з `jsconfig/template/jsconfig.json.snippet.json` (єдине джерело істини).
5
+ *
6
+ * Unified lint surface: structured violations (test(violations)/apply(violations,ctx)).
7
+ * Workspace-и читаються з `v.message` ("[<ws>] є каталог src/, але немає jsconfig.json").
8
+ */
9
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
10
+ import { join } from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ const JSCONFIG_MISSING_RE = /є каталог src\/, але немає jsconfig\.json/u
14
+ /** Витягує workspace із message-а одного violation (`[<ws>] є каталог src/…`). */
15
+ const JSCONFIG_MISSING_WS_RE = /^\[([^\]]*)\] є каталог src\/, але немає jsconfig\.json/u
16
+
17
+ const JSCONFIG_CONTENT =
18
+ readFileSync(
19
+ fileURLToPath(new URL('../jsconfig/template/jsconfig.json.snippet.json', import.meta.url)),
20
+ 'utf8'
21
+ ).trimEnd() + '\n'
22
+
23
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
24
+ export const patterns = [
25
+ {
26
+ id: 'js-run-jsconfig-create',
27
+ test: violations => violations.some(v => JSCONFIG_MISSING_RE.test(v.message)),
28
+ apply: (violations, ctx) => {
29
+ const cwd = ctx.cwd
30
+ const touchedFiles = []
31
+ for (const v of violations) {
32
+ const m = JSCONFIG_MISSING_WS_RE.exec(v.message)
33
+ if (!m) continue
34
+ const ws = m[1]
35
+ const target = join(cwd, ws, 'jsconfig.json')
36
+ if (!existsSync(target)) {
37
+ ctx.recordWrite?.(target)
38
+ writeFileSync(target, JSCONFIG_CONTENT, 'utf8')
39
+ touchedFiles.push(target)
40
+ }
41
+ }
42
+ if (touchedFiles.length === 0) return { touchedFiles: [] }
43
+ return { touchedFiles, message: `створено jsconfig.json: ${touchedFiles.join(', ')}` }
44
+ }
45
+ }
46
+ ]
@@ -0,0 +1,496 @@
1
+ /** @see ./docs/runtime.md */
2
+ import { existsSync, statSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { join, relative } from 'node:path'
5
+
6
+ import { findBunyanImportsInText, isBunyanScanSourceFile, shouldSkipFileForBunyanScan } from '../lib/bunyan-imports.mjs'
7
+ import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from '../lib/check-env-scan.mjs'
8
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
9
+ import { runConftestBatch } from '@7n/rules/scripts/lib/run-conftest-batch.mjs'
10
+ import { findConnFileRuleViolations, isConnFileRulesSourceFile } from '../lib/conn-file-rules.mjs'
11
+ import {
12
+ findConnFactoryImportsInText,
13
+ isConnImportsScanSourceFile,
14
+ isInsideConnDir,
15
+ resolveConnDirFromPackageJson
16
+ } from '../lib/conn-imports-scan.mjs'
17
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
18
+ import { findPromiseSetTimeoutInText, isPromiseSetTimeoutScanSourceFile } from '../lib/promise-settimeout-scan.mjs'
19
+ import { findTemporalUsageInText, isTemporalScanSourceFile } from '../lib/temporal-scan.mjs'
20
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
21
+ import { getMonorepoPackageRootDirs } from '@7n/rules/scripts/lib/workspaces.mjs'
22
+
23
+ /**
24
+ * Чи існує непорожній за змістом маркер каталогу `src/` (рекомендована структура js-run).
25
+ * @param {string} absPackageRoot абсолютний корінь пакета
26
+ * @returns {boolean} true, якщо `src/` існує і є каталогом
27
+ */
28
+ function backendPackageHasSrcDir(absPackageRoot) {
29
+ const srcPath = join(absPackageRoot, 'src')
30
+ try {
31
+ return statSync(srcPath).isDirectory()
32
+ } catch {
33
+ return false
34
+ }
35
+ }
36
+
37
+ /**
38
+ * FS-existence + структурна валідація `jsconfig.json` у backend-пакеті з
39
+ * каталогом `src/`. Структуру (canonical `compilerOptions` і `include`)
40
+ * делегуємо у rego-пакет `js_run.jsconfig` через `runConftestBatch` — Plan B:
41
+ * Rego-authoritative, JS оркеструє per-package gate (frontend з `vite` сюди
42
+ * взагалі не доходить, бо викликається лише з backend-гілки).
43
+ * @param {string} rootDir відносний шлях workspace
44
+ * @param {string} absPackageRoot абсолютний корінь пакета
45
+ * @param {string} label префікс `[pkg] `
46
+ * @param {(msg: string) => void} fail callback для повідомлень про порушення
47
+ * @param {(msg: string) => void} passFn callback для повідомлень про успішну перевірку
48
+ * @returns {Promise<void>} результат
49
+ * @param {string} cwd корінь репозиторію
50
+ */
51
+ async function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd) {
52
+ if (!backendPackageHasSrcDir(absPackageRoot)) return
53
+
54
+ const jcPath = join(cwd, rootDir, 'jsconfig.json')
55
+ if (!existsSync(jcPath)) {
56
+ fail(
57
+ `${label}є каталог src/, але немає jsconfig.json — додай канонічний файл з js-run.mdc ` +
58
+ `(NodeNext, include: src/**/*).`
59
+ )
60
+ return
61
+ }
62
+ const violations = await runConftestBatch({
63
+ policyDirRel: 'js-run/jsconfig',
64
+ // Правило живе у плагіні поза вбудованим rules/ ядра — резолвимо policy-теку від себе.
65
+ policyDirAbs: new URL('../jsconfig/', import.meta.url).pathname,
66
+ namespace: 'js_run.jsconfig',
67
+ files: [jcPath]
68
+ })
69
+ if (violations.length === 0) {
70
+ passFn(`${label}jsconfig.json відповідає js_run.jsconfig (rego)`)
71
+ return
72
+ }
73
+ for (const v of violations) fail(`${label}${v.message}`)
74
+ }
75
+
76
+ /**
77
+ * Перетворює абсолютний шлях у posix-формі відносно кореня пакета.
78
+ * @param {string} absPackageRoot абсолютний корінь пакета
79
+ * @param {string} absPath абсолютний шлях до файлу
80
+ * @returns {string} відносний posix-шлях
81
+ */
82
+ function relPosix(absPackageRoot, absPath) {
83
+ return relative(absPackageRoot, absPath).split('\\').join('/')
84
+ }
85
+
86
+ /**
87
+ * Сканує джерела пакета на заборонені імпорти `@nitra/bunyan` / `bunyan`.
88
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
89
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
90
+ * @param {string} label префікс повідомлення `[<pkg>] `
91
+ * @param {(msg: string) => void} fail callback при помилці
92
+ * @returns {Promise<number>} кількість знайдених порушень
93
+ */
94
+ async function checkBunyanImports(absPackageRoot, ignorePaths, label, fail) {
95
+ /** @type {string[]} */
96
+ const sourcePaths = []
97
+ await walkDir(
98
+ absPackageRoot,
99
+ absPath => {
100
+ const rel = relPosix(absPackageRoot, absPath)
101
+ if (!shouldSkipFileForBunyanScan(rel) && isBunyanScanSourceFile(rel)) {
102
+ sourcePaths.push(absPath)
103
+ }
104
+ },
105
+ ignorePaths
106
+ )
107
+
108
+ let violations = 0
109
+ for (const absPath of sourcePaths) {
110
+ const rel = relPosix(absPackageRoot, absPath)
111
+ const content = await readFile(absPath, 'utf8')
112
+ for (const v of findBunyanImportsInText(content, rel)) {
113
+ violations++
114
+ fail(`${label}${rel}:${v.line} — заміни '${v.module}' на '@nitra/pino': ${v.snippet}`)
115
+ }
116
+ }
117
+ return violations
118
+ }
119
+
120
+ /**
121
+ * Збирає всі JS/TS-файли пакета (без node_modules, dist тощо).
122
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
123
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
124
+ * @returns {Promise<string[]>} абсолютні шляхи до файлів
125
+ */
126
+ async function collectSourceFiles(absPackageRoot, ignorePaths) {
127
+ /** @type {string[]} */
128
+ const out = []
129
+ await walkDir(
130
+ absPackageRoot,
131
+ absPath => {
132
+ const rel = relPosix(absPackageRoot, absPath)
133
+ if (isCheckEnvScanSourceFile(rel)) out.push(absPath)
134
+ },
135
+ ignorePaths
136
+ )
137
+ return out
138
+ }
139
+
140
+ /**
141
+ * Перевіряє правило «Внутрішні аліаси» для пакета.
142
+ * @param {string} absPackageRoot абсолютний корінь пакета
143
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів
144
+ * @param {unknown} pkgJson розпарсений `package.json` пакета (або null)
145
+ * @param {string} label префікс повідомлення `[<pkg>] `
146
+ * @param {(msg: string) => void} fail callback при помилці
147
+ * @returns {Promise<number>} кількість порушень
148
+ */
149
+ async function checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fail) {
150
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
151
+ let violations = 0
152
+ for (const absPath of sourcePaths) {
153
+ const rel = relPosix(absPackageRoot, absPath)
154
+ if (!isConnImportsScanSourceFile(rel)) continue
155
+ if (isInsideConnDir(rel, connDir)) continue
156
+ const content = await readFile(absPath, 'utf8')
157
+ for (const v of findConnFactoryImportsInText(content, rel)) {
158
+ violations++
159
+ const target = v.specifier === '*' ? `'${v.module}'` : `{ ${v.specifier} } from '${v.module}'`
160
+ fail(
161
+ `${label}${rel}:${v.line} — імпорт ${target} має бути в '${connDir}/' і реекспортуватися через '#conn/*': ${v.snippet}`
162
+ )
163
+ }
164
+ }
165
+ return violations
166
+ }
167
+
168
+ /**
169
+ * Перевіряє правила нейминга та експортів для файлів усередині `#conn/`.
170
+ *
171
+ * Канон імені: `ql-<id>` для GraphQL, `(pg|mysql|mssql)-(read|write)[-<id>]` для БД (js-run.mdc,
172
+ * розділ «Нейминг файлів у `src/conn/`»). Експорт у файлі — лише іменований, з імʼям, що
173
+ * дорівнює camelCase від basename файла (`pg-write-contract.js` → `export const pgWriteContract`).
174
+ * @param {string} absPackageRoot абсолютний корінь пакета
175
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів пакета
176
+ * @param {unknown} pkgJson розпарсений package.json пакета (або null)
177
+ * @param {string} label префікс повідомлення `[<pkg>] `
178
+ * @param {(msg: string) => void} fail callback при помилці
179
+ * @returns {Promise<number>} кількість порушень
180
+ */
181
+ async function checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail) {
182
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
183
+ let violations = 0
184
+ for (const absPath of sourcePaths) {
185
+ const rel = relPosix(absPackageRoot, absPath)
186
+ if (!isConnFileToCheck(rel, connDir)) continue
187
+ const content = await readFile(absPath, 'utf8')
188
+ for (const v of findConnFileRuleViolations(content, rel)) {
189
+ violations++
190
+ fail(formatConnFileViolation(v, label, rel, connDir))
191
+ }
192
+ }
193
+ return violations
194
+ }
195
+
196
+ /**
197
+ * Чи `rel` — це conn-файл, який треба валідувати: під `connDir/`, з JS/TS-розширенням,
198
+ * не `index.*` (який є реекспортним барелем).
199
+ * @param {string} rel відносний шлях у posix-форматі
200
+ * @param {string} connDir каталог conn-файлів (наприклад `src/conn`)
201
+ * @returns {boolean} true, якщо файл потрібно перевірити
202
+ */
203
+ function isConnFileToCheck(rel, connDir) {
204
+ if (!isInsideConnDir(rel, connDir)) return false
205
+ if (!isConnFileRulesSourceFile(rel)) return false
206
+ const base = rel.slice(rel.lastIndexOf('/') + 1)
207
+ return !base.startsWith('index.')
208
+ }
209
+
210
+ /**
211
+ * Будує повідомлення про конкретне порушення canon-у файла з `connDir/`.
212
+ * @param {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }} v опис порушення
213
+ * @param {string} label префікс повідомлення `[<pkg>] `
214
+ * @param {string} rel відносний шлях файла
215
+ * @param {string} connDir каталог conn-файлів
216
+ * @returns {string} повний текст повідомлення для `fail(...)`
217
+ */
218
+ function formatConnFileViolation(v, label, rel, connDir) {
219
+ if (v.kind === 'name') {
220
+ return (
221
+ `${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
222
+ `'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
223
+ `(kebab-case, [a-z0-9-])`
224
+ )
225
+ }
226
+ if (v.kind === 'default-export') {
227
+ return `${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`
228
+ }
229
+ const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
230
+ return (
231
+ `${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
232
+ `(camelCase від назви файла); знайдено: ${found}`
233
+ )
234
+ }
235
+
236
+ /**
237
+ * Перевіряє правило «CheckEnv» для пакета.
238
+ * @param {string} absPackageRoot абсолютний корінь пакета
239
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів
240
+ * @param {string} label префікс повідомлення `[<pkg>] `
241
+ * @param {(msg: string) => void} fail callback при помилці
242
+ * @returns {Promise<number>} кількість порушень
243
+ */
244
+ async function checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail) {
245
+ let violations = 0
246
+ for (const absPath of sourcePaths) {
247
+ const rel = relPosix(absPackageRoot, absPath)
248
+ const content = await readFile(absPath, 'utf8')
249
+ for (const v of findUncheckedProcessEnvInText(content, rel)) {
250
+ violations++
251
+ const message =
252
+ v.kind === 'process-env'
253
+ ? `${label}${rel}:${v.line} — process.env.${v.name}: заміни на env з '@nitra/check-env' (обов'язкова змінна + checkEnv(['${v.name}'])) або з 'node:process' (опційна)`
254
+ : `${label}${rel}:${v.line} — env.${v.name} (з '@nitra/check-env') без checkEnv(['${v.name}']) (або '// @7n/rules ignore-next-line checkEnv' попереду)`
255
+ fail(message)
256
+ }
257
+ }
258
+ return violations
259
+ }
260
+
261
+ /**
262
+ * Сканує джерела пакета на паттерн `new Promise(resolve => setTimeout(resolve, ms))`.
263
+ * @param {string} absPackageRoot абсолютний корінь пакета
264
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів
265
+ * @param {string} label префікс повідомлення `[<pkg>] `
266
+ * @param {(msg: string) => void} fail callback при помилці
267
+ * @returns {Promise<number>} кількість порушень
268
+ */
269
+ async function checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, fail) {
270
+ let violations = 0
271
+ for (const absPath of sourcePaths) {
272
+ const rel = relPosix(absPackageRoot, absPath)
273
+ if (!isPromiseSetTimeoutScanSourceFile(rel)) continue
274
+ const content = await readFile(absPath, 'utf8')
275
+ for (const v of findPromiseSetTimeoutInText(content, rel)) {
276
+ violations++
277
+ fail(
278
+ `${label}${rel}:${v.line} — заміни 'new Promise(r => setTimeout(r, ms))' на 'await setTimeout(ms)' з 'node:timers/promises': ${v.snippet}`
279
+ )
280
+ }
281
+ }
282
+ return violations
283
+ }
284
+
285
+ /**
286
+ * Сканує джерела пакета на `Temporal`, який у Bun runtime ще недоступний.
287
+ * @param {string} absPackageRoot абсолютний корінь пакета
288
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів
289
+ * @param {string} label префікс повідомлення `[<pkg>] `
290
+ * @param {(msg: string) => void} fail callback при помилці
291
+ * @returns {Promise<number>} кількість порушень
292
+ */
293
+ async function checkTemporalUsage(absPackageRoot, sourcePaths, label, fail) {
294
+ let violations = 0
295
+ for (const absPath of sourcePaths) {
296
+ const rel = relPosix(absPackageRoot, absPath)
297
+ if (!isTemporalScanSourceFile(rel)) continue
298
+ const content = await readFile(absPath, 'utf8')
299
+ for (const v of findTemporalUsageInText(content, rel)) {
300
+ violations++
301
+ fail(
302
+ `${label}${rel}:${v.line} — Temporal API заборонений у Bun runtime; використовуй Date або інʼєктований timestamp`
303
+ )
304
+ }
305
+ }
306
+ return violations
307
+ }
308
+
309
+ /**
310
+ * Перевіряє відповідність правилам js-run.mdc для одного workspace-пакета.
311
+ * @param {string} rootDir відносний шлях workspace (не `'.'`)
312
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
313
+ * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
314
+ * @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
315
+ * @returns {Promise<void>} завершується після перевірок цього пакета
316
+ * @param {string} cwd корінь репозиторію
317
+ */
318
+ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn, cwd) {
319
+ const label = `[${rootDir}] `
320
+ const absPackageRoot = join(cwd, rootDir)
321
+ const pkgJson = await loadPackageJson(rootDir, cwd)
322
+
323
+ // Frontend-пакети (vite у devDependencies) виходять за межі js-run:
324
+ // браузерний бандл не має `node:process`, а `process.env.*` бандлер
325
+ // обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
326
+ // bunyan-залежність валідується в Rego (`npx @7n/rules fix`).
327
+ if (packageJsonHasViteDevDependency(pkgJson)) {
328
+ passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
329
+ return
330
+ }
331
+
332
+ await checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd)
333
+
334
+ const importViolations = await checkBunyanImports(absPackageRoot, ignorePaths, label, fail)
335
+ if (importViolations === 0) {
336
+ passFn(`${label}немає імпортів '@nitra/bunyan' / 'bunyan' у джерелах`)
337
+ }
338
+
339
+ const sourcePaths = await collectSourceFiles(absPackageRoot, ignorePaths)
340
+
341
+ const connViolations = await checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fail)
342
+ if (connViolations === 0) {
343
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
344
+ passFn(`${label}імпорти підключень (bun#SQL / mssql / @nitra/graphql-request#GraphQLClient) лише в '${connDir}/'`)
345
+ }
346
+
347
+ const connFileViolations = await checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail)
348
+ if (connFileViolations === 0) {
349
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
350
+ passFn(
351
+ `${label}файли в '${connDir}/' дотримують канону js-run: нейминг (ql-/pg-/mysql-/mssql-…) і іменований експорт у camelCase від basename`
352
+ )
353
+ }
354
+
355
+ const envViolations = await checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail)
356
+ if (envViolations === 0) {
357
+ passFn(
358
+ `${label}немає прямого process.env.*; усі env.* з '@nitra/check-env' закриті checkEnv(['…']) (або '// @7n/rules ignore-next-line checkEnv')`
359
+ )
360
+ }
361
+
362
+ const pauseViolations = await checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, fail)
363
+ if (pauseViolations === 0) {
364
+ passFn(`${label}немає 'new Promise(r => setTimeout(r, ms))' — паузи через 'node:timers/promises'`)
365
+ }
366
+
367
+ const temporalViolations = await checkTemporalUsage(absPackageRoot, sourcePaths, label, fail)
368
+ if (temporalViolations === 0) {
369
+ passFn(`${label}немає Temporal API у Bun runtime-коді`)
370
+ }
371
+
372
+ checkOtelConfigmap(rootDir, passFn, fail, cwd)
373
+
374
+ const connAliasViolations = checkConnAliasDeclaration(absPackageRoot, sourcePaths, pkgJson, label, fail)
375
+ if (connAliasViolations === 0) {
376
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
377
+ passFn(`${label}package.json.imports["#conn/*"] оголошений (або файлів у '${connDir}/' немає)`)
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Чи має пакет `vite` у `devDependencies` (маркер frontend-пакета — vite/quasar/capacitor SPA).
383
+ * Семантично ідентично `packageJsonLacksViteDevDependency` з `auto-rules.mjs`, але
384
+ * приймає вже розпарсений pkgJson.
385
+ * @param {unknown} pkgJson розпарсений `package.json` пакета (або null)
386
+ * @returns {boolean} true, якщо `vite` присутній у `devDependencies`
387
+ */
388
+ function packageJsonHasViteDevDependency(pkgJson) {
389
+ if (!pkgJson || typeof pkgJson !== 'object' || Array.isArray(pkgJson)) return false
390
+ const devDeps = /** @type {Record<string, unknown>} */ (pkgJson).devDependencies
391
+ if (!devDeps || typeof devDeps !== 'object' || Array.isArray(devDeps)) return false
392
+ return Object.hasOwn(devDeps, 'vite')
393
+ }
394
+
395
+ /**
396
+ * Завантажує `package.json` пакета (якщо є). Заборону `@nitra/bunyan` / `bunyan`
397
+ * у dependencies/devDependencies перенесено в Rego (`npm/policy/js_run/package_json/`);
398
+ * `npx \@7n/rules check` запускає її по всіх workspace `package.json`. Тут лишилася
399
+ * лише AST-перевірка імпортів.
400
+ * @param {string} rootDir відносний шлях workspace
401
+ * @returns {Promise<unknown>} розпарсений package.json або null
402
+ * @param {string} cwd корінь репозиторію
403
+ */
404
+ async function loadPackageJson(rootDir, cwd) {
405
+ const pkgPath = join(cwd, rootDir, 'package.json')
406
+ if (!existsSync(pkgPath)) return null
407
+ return JSON.parse(await readFile(pkgPath, 'utf8'))
408
+ }
409
+
410
+ /**
411
+ * Перевіряє наявність `k8s/base/configmap.yaml` пакета. Структуру (наявність
412
+ * `OTEL_RESOURCE_ATTRIBUTES` з обов'язковими `service.name=` / `service.namespace=`)
413
+ * перенесено в Rego (`npm/policy/js_run/configmap/`); `npx \@7n/rules check`
414
+ * запускає її на всіх `k8s/base/configmap.yaml`.
415
+ * @param {string} rootDir відносний шлях workspace
416
+ * @param {(msg: string) => void} passFn успішне повідомлення
417
+ * @returns {void}
418
+ * @param {string} cwd корінь репозиторію
419
+ */
420
+ /**
421
+ * Перевіряє наявність `package.json#imports["#conn/*"]` коли у `connDir` є файли.
422
+ * @param {string} absPackageRoot абсолютний корінь пакета
423
+ * @param {string[]} sourcePaths абсолютні шляхи до файлів пакета
424
+ * @param {unknown} pkgJson розпарсений package.json пакета (або null)
425
+ * @param {string} label префікс повідомлення
426
+ * @param {(msg: string) => void} fail callback при помилці
427
+ * @returns {number} 0 — ок, 1 — порушення
428
+ */
429
+ function checkConnAliasDeclaration(absPackageRoot, sourcePaths, pkgJson, label, fail) {
430
+ const connDir = resolveConnDirFromPackageJson(pkgJson)
431
+ const hasConnFiles = sourcePaths.some(absPath => isInsideConnDir(relPosix(absPackageRoot, absPath), connDir))
432
+ if (!hasConnFiles) return 0
433
+
434
+ const imports =
435
+ pkgJson && typeof pkgJson === 'object' && !Array.isArray(pkgJson)
436
+ ? /** @type {Record<string, unknown>} */ (pkgJson).imports
437
+ : null
438
+ if (imports && typeof imports === 'object' && !Array.isArray(imports) && imports['#conn/*']) return 0
439
+
440
+ fail(
441
+ `${label}є файли у '${connDir}/', але в package.json відсутній аліас "#conn/*" — ` +
442
+ `додай "imports": { "#conn/*": "./${connDir}/*" } (js-run.mdc conn-aliases)`
443
+ )
444
+ return 1
445
+ }
446
+
447
+ /**
448
+ * Перевіряє наявність OTEL configmap для workspace-пакета.
449
+ * @param {string} rootDir корінь пакета відносно cwd
450
+ * @param {(msg: string) => void} passFn репортер успішної перевірки
451
+ * @param {(msg: string) => void} failFn репортер порушення
452
+ * @param {string} cwd робочий каталог
453
+ * @returns {void}
454
+ */
455
+ function checkOtelConfigmap(rootDir, passFn, failFn, cwd) {
456
+ const k8sDir = join(cwd, rootDir, 'k8s')
457
+ if (!existsSync(k8sDir)) {
458
+ passFn(`${rootDir}: немає каталогу k8s/ — перевірку OTEL configmap пропущено`)
459
+ return
460
+ }
461
+ const configmapPath = join(k8sDir, 'base', 'configmap.yaml')
462
+ if (!existsSync(configmapPath)) {
463
+ failFn(
464
+ `${rootDir}/k8s/base/configmap.yaml відсутній — додай з полем OTEL_RESOURCE_ATTRIBUTES ` +
465
+ `(service.name=, service.namespace=), js-run.mdc`
466
+ )
467
+ return
468
+ }
469
+ passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @7n/rules fix → js_run.configmap)`)
470
+ }
471
+
472
+ /**
473
+ * Перевіряє відповідність проєкту правилам js-run.mdc лише для workspace-пакетів (не корінь репо).
474
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
475
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} перелік порушень
476
+ */
477
+ export async function lint(ctx) {
478
+ const cwd = ctx.cwd
479
+ const reporter = createViolationReporter(ctx)
480
+ const { pass, fail } = reporter
481
+
482
+ const roots = await getMonorepoPackageRootDirs(cwd)
483
+ const workspaceRoots = roots.filter(r => r !== '.')
484
+
485
+ if (workspaceRoots.length === 0) {
486
+ pass('js-run: немає workspace-пакетів у кореневому package.json — перевірку залежностей і k8s у пакетах пропущено')
487
+ return reporter.result()
488
+ }
489
+
490
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
491
+ for (const r of workspaceRoots) {
492
+ await checkWorkspacePackage(r, ignorePaths, fail, pass, cwd)
493
+ }
494
+
495
+ return reporter.result()
496
+ }