@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,36 @@
1
+ ## Залежнісна політика (що не додавати)
2
+
3
+ `@e18e/eslint-plugin` окремо не додавай — він уже в залежностях `@nitra/eslint-config` (з **3.8.0**), oxlint підвантажує його з `node_modules`. Пакети `oxlint`/`eslint`/`jscpd`/`knip` теж не додавай у `devDependencies` без потреби монорепо — `bunx` тягне їх ad-hoc.
4
+
5
+ ### Заборона `ua-parser-js`
6
+
7
+ Пакет **`ua-parser-js`** заборонений починаючи з версії **2.x**, де ліцензія змінилась з MIT на **AGPL-3.0** — несумісну з комерційним використанням у закритому SaaS. Замінити на **`bowser`** (MIT, ~6 KB gzip):
8
+
9
+ ```javascript title="❌ до"
10
+ import { UAParser } from 'ua-parser-js'
11
+ const parser = new UAParser(userAgent)
12
+ const result = parser.getResult()
13
+ const browserName = result.browser.name
14
+ ```
15
+
16
+ ```javascript title="✅ після"
17
+ import Bowser from 'bowser'
18
+ const browser = Bowser.getParser(userAgent)
19
+ const browserName = browser.getBrowserName()
20
+ ```
21
+
22
+ Bowser надає ті самі поля: `getBrowserName()`, `getOSName()`, `getPlatform().vendor`. Міграція займає кілька рядків; логіка не змінюється.
23
+
24
+ ### Заборона `@nitra/as-integrations-fastify`
25
+
26
+ Пакет **`@nitra/as-integrations-fastify`** заборонений у **`dependencies`**, **`peerDependencies`** та в import-specifier-ах. Це чистий републіш upstream, застряглий на peer `@apollo/server: "^4.0.0"`, тож на Apollo 5 `bun install` дає `warn: incorrect peer dependency`. Заміна — upstream **`@as-integrations/fastify`** (**`^3.1.0`**): peer `@apollo/server: "^4.0.0 || ^5.0.0"` + `fastify: "^5.3.0"`.
27
+
28
+ Міграція — лише specifier у трьох місцях: залежність у `package.json`, `import`, та `vi.mock(...)` / `await import(...)` у тестах. **Код не міняється**, експорти ті самі: `default` → `fastifyApollo`, named → `fastifyApolloDrainPlugin`.
29
+
30
+ ```javascript title="❌ до"
31
+ import fastifyApollo, { fastifyApolloDrainPlugin } from '@nitra/as-integrations-fastify'
32
+ ```
33
+
34
+ ```javascript title="✅ після"
35
+ import fastifyApollo, { fastifyApolloDrainPlugin } from '@as-integrations/fastify'
36
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/js/dep-policy/main.mjs
5
+ docgen:
6
+ crc: 8acb4c08
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Файл сканує всі JS/TS-файли у поточному робочому каталозі, виключаючи конфігураційно визначені шляхи. Завдяки публічній функції main, він перевіряє відповідність усіх імпортів у коді політиці залежностей, визначеній у dep-policy.mdc. Ця **read-only** операція виявляє та фіксує будь-які імпорти, заборонені в dep-policy.mdc, надаючи підказки заміни з js.mdc. Механізм працює з fail-safe принципом, перехоплюючи помилки та не дозволяючи виняткам виходити назовні.
16
+
17
+ ## Поведінка
18
+
19
+ Поведінка:
20
+
21
+ 1. Виконується функція main.
22
+ 2. Аналізуються всі JS/TS-файли у поточному робочому каталозі, виключаючи шляхи, визначені конфігурацією.
23
+ 3. Для кожного файлу витягуються всі імпорти (статичні, динамічні та виклики require).
24
+ 4. Знаходяться імпорти, які знаходяться у списку заборонених especifiers (dep-policy.mdc).
25
+ 5. Якщо знайдений імпорт заборонений, реєструється помилка, яка вказує на шлях до файлу, заборонений імпорт та підказку про його заміну (js.mdc).
26
+ 6. Після перевірки всіх файлів, якщо заборонених імпортів не знайдено, реєструється повідомлення про успішне завершення перевірки.
27
+
28
+ ## Публічний API
29
+
30
+ main — перевіряє імпорти у файлах JavaScript та TypeScript на відповідність політиці залежностей.
31
+
32
+ ## Гарантії поведінки
33
+
34
+ - Read-only: не виконує операцій запису (ФС/БД).
35
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -0,0 +1,99 @@
1
+ /** @see ./docs/dep-policy.md */
2
+ import { readFile } from 'node:fs/promises'
3
+ import { relative } from 'node:path'
4
+
5
+ import { parseSync } from 'oxc-parser'
6
+
7
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
8
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
9
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
10
+ import {
11
+ dynamicImportModule,
12
+ langFromPath,
13
+ requireCallModule,
14
+ walkAstWithAncestors
15
+ } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
16
+
17
+ const JS_SOURCE_RE = /\.(?:[cm]?[jt]sx?)$/u
18
+
19
+ /**
20
+ * Пакети, заборонені як import-specifier у будь-якому JS/TS-файлі.
21
+ * Ключ — specifier, значення — підказка про заміну.
22
+ */
23
+ const BANNED_SPECIFIERS = new Map([
24
+ ['@nitra/as-integrations-fastify', 'використовуй @as-integrations/fastify'],
25
+ [
26
+ 'ua-parser-js',
27
+ 'замінити на bowser (MIT, ~6 KB) — npm i bowser. ua-parser-js v2 змінив ліцензію на AGPL-3.0, несумісну з комерційним використанням'
28
+ ]
29
+ ])
30
+
31
+ /**
32
+ * Витягає з джерела всі import-specifier'и (static + dynamic + require).
33
+ * @param {string} source текст файлу
34
+ * @param {string} filePath шлях до файлу (для вибору мови OXC-парсера)
35
+ * @returns {string[]} список specifier'ів
36
+ */
37
+ function extractImportSpecifiers(source, filePath) {
38
+ /** @type {string[]} */
39
+ const result = []
40
+ let parsed
41
+ try {
42
+ parsed = parseSync(filePath, source, { lang: langFromPath(filePath) })
43
+ } catch {
44
+ return result
45
+ }
46
+ for (const imp of parsed?.module?.staticImports ?? []) {
47
+ if (typeof imp?.moduleRequest?.value === 'string') result.push(imp.moduleRequest.value)
48
+ }
49
+ const program = parsed?.program
50
+ if (program && typeof program === 'object') {
51
+ walkAstWithAncestors(program, [], node => {
52
+ const dyn = dynamicImportModule(node)
53
+ if (dyn !== null) result.push(dyn)
54
+ const req = requireCallModule(node)
55
+ if (req !== null) result.push(req)
56
+ })
57
+ }
58
+ return result
59
+ }
60
+
61
+ /**
62
+ * Сканує всі JS/TS-файли проєкту на заборонені import-specifier'и (dep-policy.mdc).
63
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
64
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} Результат лінту з переліком порушень.
65
+ */
66
+ export async function lint(ctx) {
67
+ const reporter = createViolationReporter(ctx)
68
+ const cwd = ctx.cwd
69
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
70
+
71
+ const files = []
72
+ await walkDir(
73
+ cwd,
74
+ p => {
75
+ if (JS_SOURCE_RE.test(p)) files.push(p)
76
+ },
77
+ ignorePaths
78
+ )
79
+
80
+ let violations = 0
81
+ for (const absPath of files) {
82
+ const source = await readFile(absPath, 'utf8')
83
+ const specifiers = extractImportSpecifiers(source, absPath)
84
+ for (const spec of specifiers) {
85
+ const hint = BANNED_SPECIFIERS.get(spec)
86
+ if (hint !== undefined) {
87
+ const rel = relative(cwd, absPath)
88
+ reporter.fail(`${rel}: заборонений import '${spec}' — ${hint} (js.mdc dep-policy)`)
89
+ violations += 1
90
+ }
91
+ }
92
+ }
93
+
94
+ if (violations === 0) {
95
+ reporter.pass(`dep-policy: перевірено ${files.length} файлів — заборонених import-specifier'ів немає (js.mdc)`)
96
+ }
97
+
98
+ return reporter.result()
99
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/js
4
+ resource: plugins/lang-js/rules/js/
5
+ ---
6
+
7
+ # npm/rules/js
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "skipLocalTier": true,
4
+ "lint": {
5
+ "scope": "per-file",
6
+ "glob": ["**/*.{js,mjs,cjs,jsx,ts,tsx,vue}"]
7
+ }
8
+ }
@@ -0,0 +1,50 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-eslint.mjs
4
+ resource: plugins/lang-js/rules/js/eslint/fix-eslint.mjs
5
+ docgen:
6
+ crc: 38b4978d
7
+ model: manual
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ T0-autofix для `js/eslint`, два патерни в порядку застосування. `js-eslint-autofix`:
13
+ детермінований прогін лінтерів у fix-режимі (`oxlint --fix` + `eslint --fix`) по файлах, де
14
+ детектор знайшов порушення — виправляє лише авто-fixable правила. `js-eslint-mechanical-text-fix`:
15
+ для правил, які `--fix` НЕ покриває (suggestion-only у власній реалізації інструментів, перевірено
16
+ емпірично на реальних lint-прогонах), але заміна на позначеному `data.line` текстуально однозначна
17
+ й безпечна без AST-парсингу. Усе, що лишається непокритим — детектору на повторну перевірку, а
18
+ далі LLM-ладдеру. Запис незворотний (поза rollback).
19
+
20
+ ## Поведінка
21
+
22
+ `js-eslint-autofix`:
23
+ - Збирає унікальні файли з порушень концерну, лишає лише js-сімейство (`.mjs/.cjs/.js/.jsx/.ts/.tsx/.vue`).
24
+ - Послідовно застосовує `oxlint --fix` (CLI) і `eslint --fix` (через API з `outputFixes`).
25
+ - До списку змінених потрапляють лише файли, чий вміст фактично змінився (порівняння до/після).
26
+
27
+ `js-eslint-mechanical-text-fix`:
28
+ - `MECHANICAL_TEXT_FIXES` — реєстр `{ reasons, replace }`: `reasons` покриває обидва формати
29
+ `violation.reason` (eslint `"plugin/rule"`, oxlint `"plugin(rule)"` — той самий rule різні тули
30
+ віддають по-різному); `replace(line)` — чиста функція рядка, `null` якщо шаблон не знайдено.
31
+ - Групує порушення за файлом, для кожного цільового `data.line` (1-indexed) застосовує
32
+ відповідну `replace`; рядок без очікуваного шаблону (файл змінився з моменту detect-у) —
33
+ пропускається без гадання.
34
+ - Наразі покриває `unicorn/prefer-number-is-safe-integer` (`Number.isInteger` → `Number.isSafeInteger`).
35
+
36
+ ## Публічний API
37
+
38
+ - `patterns` — масив T0-патернів (`id` / `test` / `apply`), що його споживає центральний fix-pipeline:
39
+ `js-eslint-autofix` (лінтери в fix-режимі), `js-eslint-mechanical-text-fix` (текстові заміни для
40
+ suggestion-only правил).
41
+
42
+ ## Гарантії поведінки
43
+
44
+ - `js-eslint-autofix`: якщо серед порушень немає js-файлів — лінтери не запускаються, змін немає.
45
+ - `js-eslint-mechanical-text-fix`: пише лише файли з реальною зміною рядка (шаблон збігся); не
46
+ чіпає рядки поза `MECHANICAL_TEXT_FIXES`-реєстром.
47
+ - Обидва патерни повертають лише фактично змінені файли; перед записом кожен реєструється через
48
+ `recordWrite`.
49
+ - Межа CI: модуль належить fix-фазі; у `--no-fix` (CI) не запускається — узгоджено із
50
+ забороною `oxlint --fix`/`eslint --fix` у CI.
@@ -0,0 +1,31 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix-worker.mjs
4
+ resource: plugins/lang-js/rules/js/eslint/fix-worker.mjs
5
+ docgen:
6
+ crc: 6371bb44
7
+ model: manual
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ Custom fix-worker `js/eslint` (перекриває дефолтний `default-worker.mjs`): замість одного `runAgentFix`-виклику з усіма порушеннями з усіх файлів concern-а — окрема агентна сесія на кожен файл, ПАРАЛЕЛЬНО (обмежений пул, не більше `MAX_PARALLEL_FILES`). Мотивація й еволюція дизайну — `docs/adr/260718-0754-js-eslint-fix-worker-per-session-overhead.md`: послідовна версія стабільно впиралась у timeout на всіх 4 rung-ах драбини (local-min→cloud-avg); профайлінг реального прогону показав, що домінує кількість/довжина раундів моделі на файл, а не фіксовані bootstrap-витрати сесії — тож паралелізм (кілька файлів одночасно замість черги) дає пряме пришвидшення незалежно від причини.
13
+
14
+ ## Поведінка
15
+
16
+ 1. Групує вхідні `violations` за `file`; порушення без `file` ігноруються.
17
+ 2. Дедлайн — `DEADLINE_FRACTION` (0.8) від `ctx.timeoutMs`; гейтить лише СТАРТ нового файлу з черги (не скасовує вже запущені).
18
+ 3. `runPooled` — обмежений пул воркерів (`MAX_PARALLEL_FILES=4`): при `files.length ≤ MAX_PARALLEL_FILES` усі файли стартують майже одночасно й отримують практично весь бюджет незалежно один від одного; при більшій кількості черга природно звужує бюджет пізніших хвиль (`callTimeoutMs` рахується в момент старту файлу з черги, не наперед).
19
+ 4. На кожен файл — окремий `runAgentFix` із `targetFiles: [file]` і власним `verify` (`verifyFile`): item-scoped canonical re-detect лише цього файлу, не всього concern-а — інакше evidence-гейт хибно вважав би файл незакритим через порушення в ІНШИХ файлах.
20
+ 5. Один файл, що завершився з `error` АБО кинув виняток (try/catch у тілі воркера), не обриває пул — пропускається, решта файлів обробляються незалежно.
21
+ 6. Повертає лише `touchedFiles` з успішних (`!error`) викликів; success rung-а все одно визначає whole-concern canonical re-detect runner-а (`runRung`), не цей worker.
22
+
23
+ ## Публічний API
24
+
25
+ - `fixWorker(violations, ctx)` — контракт `FixWorkerFn`; резолвиться автоматично замість `default-worker.mjs`, бо лежить як `fix-worker.mjs` поруч із `main.mjs` concern-а.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Пише лише у файли з переданих `violations` (`targetFiles: [file]` на кожен виклик) — той самий semantic-collateral guard, що й у дефолтного worker-а.
30
+ - `recordWrite` (не durable) на кожен файл: rollback-контракт незмінний — якщо після worker-а в concern-і лишилось хоч одне порушення будь-де, `runRung` відкочує ВСІ правки цього rung-а, включно з уже полагодженими файлами.
31
+ - Конкурентні виклики `recordWrite` безпечні: central snapshot — синхронні Map-операції без `await` усередині, event loop не перериває їх посеред виконання.
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/js/eslint
4
+ resource: plugins/lang-js/rules/js/eslint/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------------------- | --------- |
9
+ | [fix-eslint.mjs](fix-eslint.md) | JS Module |
10
+ | [fix-worker.mjs](fix-worker.md) | JS Module |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,35 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/js/eslint/main.mjs
5
+ docgen:
6
+ crc: 8710f72f
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
+ Цей файл надає публічний інтерфейс для валідації коду, використовуючи інструменти Oxlint та ESLint. Він дозволяє визначати набір JavaScript-файлів для аналізу через функцію `filterJsFiles` та виконувати сам процес лінтингу за допомогою функції `lint`. Компонент є read-only, оскільки не здійснює змін до файлової системи чи баз даних. У разі виявлення помилок, механізм забезпечує fail-safe режим, перехоплюючи їх та не викликаючи винятків назовні.
16
+
17
+ ## Поведінка
18
+
19
+ Поведінка:
20
+ filterJsFiles фільтрує список вказаних файлів, залишаючи лише ті, що відповідають розширенням JavaScript.
21
+ lint запускає лінтинг для файлів. Якщо передаються файли, він аналізує їх на наявність порушень; якщо не передані, він виконує повний аналіз проекту.
22
+
23
+ ## Публічний API
24
+
25
+ filterJsFiles — відбирає файли для подальшої обробки.
26
+ lint — проводить статичний аналіз коду (пофайлово або по всьому проєкту).
27
+
28
+ ## Гарантії поведінки
29
+
30
+ - Read-only: не виконує операцій запису (ФС/БД).
31
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
32
+
33
+ **warnIgnored: false.** ESLint конструюється з `warnIgnored: false` — файли з delta-списку, що матчать ignore-патерни `eslint.config.js` (наприклад, синковані `.pi/extensions/**` чи згенеровані `npm/types/**`), не рахуються порушеннями.
34
+
35
+ **Worktree-чекаути ігноруються.** Обидва інструменти виключають `.worktrees/**` і `.claude/worktrees/**` (копії репо від сесійних git-worktree, яких споживацький конфіг зазвичай не виключає): ESLint — через global ignores у `overrideConfig`, oxlint — через `--ignore-pattern`. Діє і у full-режимі, і для явно переданих файлів.
@@ -0,0 +1,160 @@
1
+ /** @see ./docs/fix-eslint.md */
2
+
3
+ /**
4
+ * T0-autofix для `js/eslint` — детермінований прогін лінтерів у fix-режимі
5
+ * (`oxlint --fix` + `eslint --fix`) на файлах, де детектор знайшов порушення.
6
+ * Виправляє лише авто-fixable правила; решта лишається детектору на re-check
7
+ * (далі — LLM-ладдер). Запис permanent (поза rollback).
8
+ *
9
+ * Межа CI: цей модуль — частина fix-фази (`lint` без `--no-fix`); сам по собі
10
+ * detector-шлях його не вантажить. У CI (`--no-fix`) fix не запускається —
11
+ * узгоджено із забороною `oxlint --fix`/`eslint --fix` у CI (js.mdc / lint_js_yml).
12
+ */
13
+ import { spawnSync } from 'node:child_process'
14
+ import { readFileSync, writeFileSync } from 'node:fs'
15
+ import { resolve } from 'node:path'
16
+
17
+ import { ESLint } from 'eslint'
18
+
19
+ import { filterJsFiles } from './main.mjs'
20
+
21
+ /**
22
+ * Вміст файлу або null, якщо не читається (видалений/недоступний).
23
+ * @param {string} abs абсолютний шлях
24
+ * @returns {string|null} вміст або null
25
+ */
26
+ function readOrNull(abs) {
27
+ try {
28
+ return readFileSync(abs, 'utf8')
29
+ } catch {
30
+ return null
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Прогін `oxlint --fix` + `eslint --fix` по списку js-файлів. Повертає абсолютні
36
+ * шляхи фактично змінених файлів (порівняння до/після).
37
+ * @param {string[]} jsFiles posix-relative js-файли
38
+ * @param {string} cwd корінь проєкту
39
+ * @returns {Promise<string[]>} абсолютні шляхи змінених файлів
40
+ */
41
+ async function runLinterFix(jsFiles, cwd) {
42
+ const abs = jsFiles.map(f => resolve(cwd, f))
43
+ const before = new Map(abs.map(a => [a, readOrNull(a)]))
44
+
45
+ // oxlint --fix (CLI): авто-fixable oxc-правила.
46
+ spawnSync('bunx', ['oxlint', '--fix', ...jsFiles], { cwd, encoding: 'utf8' })
47
+
48
+ // eslint --fix (API): outputFixes пише виправлені файли на диск.
49
+ const eslint = new ESLint({ cwd, fix: true })
50
+ const results = await eslint.lintFiles(abs)
51
+ await ESLint.outputFixes(results)
52
+
53
+ /** @type {string[]} */
54
+ const touched = []
55
+ for (const a of abs) {
56
+ if (readOrNull(a) !== before.get(a)) touched.push(a)
57
+ }
58
+ return touched
59
+ }
60
+
61
+ /**
62
+ * Реєстр "механічних" правил `js/eslint`: `oxlint --fix`/`eslint --fix` (T0
63
+ * `js-eslint-autofix` вище) їх НЕ покриває (suggestion-only у власній реалізації
64
+ * інструментів — перевірено емпірично: лишаються порушеними після `--fix` на
65
+ * реальних прогонах), але текстуальна заміна на позначеному рядку однозначна й
66
+ * безпечна без повного AST-парсингу — проста заміна ідентифікатора/API-виклику.
67
+ * `reasons` — обидва формати `reason` з `main.mjs` (`f.rule`): eslint (`ruleId`,
68
+ * `"plugin/rule"`) і oxlint (`d.code`, `"plugin(rule)"`) — те саме правило різні
69
+ * тули віддають по-різному.
70
+ * @type {Array<{ reasons: string[], replace: (line: string) => string|null }>}
71
+ */
72
+ const MECHANICAL_TEXT_FIXES = [
73
+ {
74
+ // Number.isInteger(x) не ловить x поза Number.MIN/MAX_SAFE_INTEGER — сама заміна
75
+ // імені методу семантично точна для рядка, що ЛИШЕ так і викликає isInteger.
76
+ reasons: ['unicorn/prefer-number-is-safe-integer', 'unicorn(prefer-number-is-safe-integer)'],
77
+ replace: line =>
78
+ line.includes('Number.isInteger') ? line.replaceAll('Number.isInteger', 'Number.isSafeInteger') : null
79
+ }
80
+ ]
81
+
82
+ /**
83
+ * Знаходить механічний фікс для reason-у порушення (обидва формати tool-у).
84
+ * @param {string} reason `violation.reason`
85
+ * @returns {((line: string) => string|null)|null} replace-функція або null
86
+ */
87
+ function mechanicalFixFor(reason) {
88
+ return MECHANICAL_TEXT_FIXES.find(f => f.reasons.includes(reason))?.replace ?? null
89
+ }
90
+
91
+ /**
92
+ * Застосовує механічні текстові заміни по конкретних рядках (`data.line`, 1-indexed)
93
+ * файлу. Рядок без збігу очікуваного шаблону (файл змінився з моменту detect-у) —
94
+ * пропускається, не гадаємо.
95
+ * @param {string} content вміст файлу
96
+ * @param {Array<{ line: number, replace: (line: string) => string|null }>} targets рядки й replace-функції
97
+ * @returns {string|null} новий вміст або null, якщо жоден рядок не змінився
98
+ */
99
+ function applyMechanicalLineFixes(content, targets) {
100
+ const lines = content.split('\n')
101
+ let changed = false
102
+ for (const { line, replace } of targets) {
103
+ const idx = line - 1
104
+ if (idx < 0 || idx >= lines.length) continue
105
+ const next = replace(lines[idx])
106
+ if (next !== null && next !== lines[idx]) {
107
+ lines[idx] = next
108
+ changed = true
109
+ }
110
+ }
111
+ return changed ? lines.join('\n') : null
112
+ }
113
+
114
+ /** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
115
+ export const patterns = [
116
+ {
117
+ id: 'js-eslint-autofix',
118
+ test: violations => violations.some(v => v.file),
119
+ apply: async (violations, ctx) => {
120
+ const files = [...new Set(violations.map(v => v.file).filter(Boolean))]
121
+ const jsFiles = filterJsFiles(files)
122
+ if (jsFiles.length === 0) return { touchedFiles: [] }
123
+ const touchedFiles = await runLinterFix(jsFiles, ctx.cwd)
124
+ for (const a of touchedFiles) ctx.recordWrite?.(a)
125
+ return touchedFiles.length > 0
126
+ ? { touchedFiles, message: `oxlint/eslint --fix: ${touchedFiles.length} файл(ів)` }
127
+ : { touchedFiles: [] }
128
+ }
129
+ },
130
+ {
131
+ id: 'js-eslint-mechanical-text-fix',
132
+ test: violations => violations.some(v => v.file && v.data?.line && mechanicalFixFor(v.reason)),
133
+ apply: (violations, ctx) => {
134
+ /** @type {Map<string, Array<{ line: number, replace: (line: string) => string|null }>>} */
135
+ const byFile = new Map()
136
+ for (const v of violations) {
137
+ const replace = v.file && v.data?.line ? mechanicalFixFor(v.reason) : null
138
+ if (!replace) continue
139
+ const arr = byFile.get(v.file)
140
+ const target = { line: v.data.line, replace }
141
+ if (arr) arr.push(target)
142
+ else byFile.set(v.file, [target])
143
+ }
144
+ const touchedFiles = []
145
+ for (const [rel, targets] of byFile) {
146
+ const abs = resolve(ctx.cwd, rel)
147
+ const content = readOrNull(abs)
148
+ if (content === null) continue
149
+ const next = applyMechanicalLineFixes(content, targets)
150
+ if (next === null) continue
151
+ ctx.recordWrite?.(abs)
152
+ writeFileSync(abs, next)
153
+ touchedFiles.push(abs)
154
+ }
155
+ return touchedFiles.length > 0
156
+ ? { touchedFiles, message: `механічні текстові заміни: ${touchedFiles.length} файл(ів)` }
157
+ : { touchedFiles: [] }
158
+ }
159
+ }
160
+ ]
@@ -0,0 +1,139 @@
1
+ /**
2
+ * fix-worker для `js/eslint`: окрема агентна сесія НА ФАЙЛ замість одного великого промпту
3
+ * на всі файли concern-а одразу. Дефолтний worker (`default-worker.mjs`) шле ВСІ порушення
4
+ * з УСІХ файлів в один `runAgentFix`-виклик — на реальних lint-прогонах (tauri-components)
5
+ * це давало 100% timeout на всіх 4 rung-ах драбини (local-min→cloud-avg), навіть коли
6
+ * порушень було лише 20-90 у 1-3 файлах: одна сесія жонглює кількома файлами одразу і не
7
+ * встигає в rung-таймаут.
8
+ *
9
+ * Файли обробляються ПАРАЛЕЛЬНО (обмежений пул, `MAX_PARALLEL_FILES`), не послідовно —
10
+ * ADR docs/adr/260718-0754-js-eslint-fix-worker-per-session-overhead.md. Профайлінг
11
+ * реального прогону (2026-07-18, trace `runAgentFix`) спростував гіпотезу про фіксовані
12
+ * bootstrap-витрати сесії: `dispatch.test.js` (2 порушення) — 9 раундів моделі, 29.4с;
13
+ * тривіальний фікс так само вимагав багато tool-раундів, а не одноразової плати за bootstrap.
14
+ * Оскільки домінує саме кількість/довжина turn-раундів, а не старт сесії, паралелізм
15
+ * (кілька файлів одночасно замість черги) дає пряме пришвидшення незалежно від причини —
16
+ * секвенційна версія витрачала весь бюджет rung-а на 1 файл, лишаючи іншим 0 шансів
17
+ * навіть стартувати.
18
+ *
19
+ * Rollback-контракт незмінний: `recordWrite` (не durable) на кожен файл — правки наявного
20
+ * стороннього коду. Якщо після worker-а `runRung`-ів canonical re-detect (whole-concern)
21
+ * знайде хоч одне порушення будь-де, увесь rung однаково відкотиться (S1) — той самий
22
+ * контракт, що й у дефолтного worker-а; паралелізм підвищує ймовірність, що ЦЕЙ rung
23
+ * дійде до 0 порушень, а не замінює rollback-семантику. `snapshot.record`/`recordWrite` —
24
+ * синхронні Map-операції без `await` усередині, тож конкурентні виклики безпечні (Node
25
+ * event loop не перериває синхронний блок).
26
+ *
27
+ * Дедлайн (`DEADLINE_FRACTION` від `ctx.timeoutMs`, той самий підхід, що й
28
+ * `doc-files/check/fix-worker.mjs`) гейтить лише СТАРТ нового файлу з черги — не
29
+ * скасовує вже запущені. При files.length ≤ MAX_PARALLEL_FILES усі стартують майже
30
+ * одночасно й отримують практично весь бюджет незалежно один від одного (справжній
31
+ * паралелізм); при більшій кількості — черга природно звужує бюджет пізніших хвиль
32
+ * (`callTimeoutMs` рахується в момент старту, не наперед), не даючи перевищити
33
+ * backstop ×1.25 runner-а.
34
+ * @typedef {import('@7n/rules/scripts/lib/lint-surface/types.mjs').FixWorkerFn} FixWorkerFn
35
+ */
36
+ import { resolve } from 'node:path'
37
+
38
+ import { anchoredEnabled } from '@7n/rules/scripts/lib/lint-surface/default-worker.mjs'
39
+ import { renderViolations } from '@7n/rules/scripts/lib/lint-surface/render.mjs'
40
+ import { lint } from './main.mjs'
41
+
42
+ /** Частка ctx.timeoutMs, після якої черга не стартує новий файл (запас до backstop ×1.25). */
43
+ const DEADLINE_FRACTION = 0.8
44
+
45
+ /** Максимум файлів, що обробляються одночасно (без необмеженого burst на великих concern-ах). */
46
+ const MAX_PARALLEL_FILES = 4
47
+
48
+ /**
49
+ * Item-scoped (один файл) canonical re-detect для evidence-гейта `runAgentFix` —
50
+ * НЕ те саме, що whole-concern `ctx.verify` з `runRung` (той перевірив би ВСІ файли
51
+ * concern-а й дав би хибний "не готово" навіть коли саме ЦЕЙ файл уже чистий).
52
+ * @param {string} cwd абсолютний корінь проєкту
53
+ * @param {string} ruleId id правила
54
+ * @param {string} concernId id concern-а
55
+ * @param {string} file posix-relative шлях файлу, що перевіряється
56
+ * @returns {Promise<{ ok: boolean, output: string }>} вердикт verify-петлі
57
+ */
58
+ async function verifyFile(cwd, ruleId, concernId, file) {
59
+ const { violations: after } = await lint({ cwd, ruleId, concernId, files: [file] })
60
+ const stamped = after.map(v => ({ ...v, ruleId, concernId }))
61
+ return { ok: stamped.length === 0, output: renderViolations(stamped) }
62
+ }
63
+
64
+ /**
65
+ * Обробляє `items` обмеженим пулом воркерів (не більше `MAX_PARALLEL_FILES` одночасно) —
66
+ * власна черга замість `Promise.all(items.map(...))`, щоб великий concern не відкрив
67
+ * необмежену кількість конкурентних агентних сесій одразу.
68
+ * @param {string[]} items елементи черги (шляхи файлів)
69
+ * @param {(item: string) => Promise<void>} worker обробник одного елемента; винятки ловить сам
70
+ * @returns {Promise<void>}
71
+ */
72
+ async function runPooled(items, worker) {
73
+ const queue = [...items]
74
+ const runnerCount = Math.min(MAX_PARALLEL_FILES, items.length)
75
+ await Promise.all(
76
+ Array.from({ length: runnerCount }, async () => {
77
+ let item
78
+ while ((item = queue.shift())) {
79
+ await worker(item)
80
+ }
81
+ })
82
+ )
83
+ }
84
+
85
+ /** @type {FixWorkerFn} */
86
+ export async function fixWorker(violations, ctx) {
87
+ // lazy import — тримає detect-шлях вільним від pi/oxc (read-only --no-fix не вантажить їх).
88
+ const [{ runAgentFix }, { isLocalModel }, { extractContext }] = await Promise.all([
89
+ import('@7n/llm-lib/agent-fix'),
90
+ import('@7n/llm-lib/model-tiers'),
91
+ import('@7n/rules/scripts/utils/ast-extract.mjs')
92
+ ])
93
+
94
+ /** @type {Map<string, import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintViolation[]>} */
95
+ const byFile = new Map()
96
+ for (const v of violations) {
97
+ if (!v.file) continue
98
+ const arr = byFile.get(v.file)
99
+ if (arr) arr.push(v)
100
+ else byFile.set(v.file, [v])
101
+ }
102
+ const files = byFile.keys().toArray()
103
+ if (files.length === 0) return { touchedFiles: [] }
104
+
105
+ const deadlineAt = ctx.timeoutMs ? Date.now() + Math.round(ctx.timeoutMs * DEADLINE_FRACTION) : null
106
+ const anchoredEdits = anchoredEnabled(ctx.model, isLocalModel)
107
+
108
+ const touchedFiles = []
109
+ await runPooled(files, async file => {
110
+ if (deadlineAt && Date.now() >= deadlineAt) return
111
+ const callTimeoutMs = deadlineAt ? Math.max(1000, deadlineAt - Date.now()) : ctx.timeoutMs
112
+
113
+ let res
114
+ try {
115
+ res = await runAgentFix(ctx.ruleId, renderViolations(byFile.get(file)), ctx.cwd, {
116
+ model: ctx.model,
117
+ tier: ctx.tier,
118
+ timeoutMs: callTimeoutMs,
119
+ feedback: ctx.feedback ?? null,
120
+ caller: `fix:${ctx.ruleId}/${ctx.concernId}:${ctx.tier}:${file}`,
121
+ recordWrite: ctx.recordWrite,
122
+ chain: ctx.chain ?? null,
123
+ targetFiles: [file],
124
+ verify: () => verifyFile(ctx.cwd, ctx.ruleId, ctx.concernId, file),
125
+ verifyMax: ctx.verifyMax,
126
+ anchoredEdits,
127
+ deps: { astContext: p => extractContext(resolve(ctx.cwd, p)) }
128
+ })
129
+ } catch {
130
+ // Один файл впав винятком — не валимо решту паралельних воркерів у пулі; whole-concern
131
+ // canonical re-detect runner-а (не цей worker) визначить, чи rung закрито.
132
+ return
133
+ }
134
+
135
+ if (!res.error) touchedFiles.push(...(res.touchedFiles ?? []))
136
+ })
137
+
138
+ return { touchedFiles }
139
+ }