@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.
- package/CHANGELOG.md +12 -0
- package/package.json +14 -4
- package/rules/bun/bunfig/bunfig.mdc +17 -0
- package/rules/bun/bunfig/bunfig.rego +29 -0
- package/rules/bun/bunfig/concern.json +9 -0
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
- package/rules/bun/docs/index.md +11 -0
- package/rules/bun/layout/concern.json +16 -0
- package/rules/bun/layout/docs/fix-layout.md +29 -0
- package/rules/bun/layout/docs/main.md +34 -0
- package/rules/bun/layout/fix-layout.mjs +63 -0
- package/rules/bun/layout/layout.mdc +60 -0
- package/rules/bun/layout/main.mjs +53 -0
- package/rules/bun/licensee/concern.json +7 -0
- package/rules/bun/licensee/docs/fix-licensee.md +27 -0
- package/rules/bun/licensee/docs/index.md +12 -0
- package/rules/bun/licensee/docs/main.md +30 -0
- package/rules/bun/licensee/fix-licensee.mjs +31 -0
- package/rules/bun/licensee/main.mjs +68 -0
- package/rules/bun/lint-surface/concern.json +3 -0
- package/rules/bun/lint-surface/lint-surface.mdc +13 -0
- package/rules/bun/main.json +1 -0
- package/rules/bun/main.mdc +11 -0
- package/rules/bun/package_json/concern.json +9 -0
- package/rules/bun/package_json/docs/fix-package_json.md +28 -0
- package/rules/bun/package_json/docs/index.md +9 -0
- package/rules/bun/package_json/fix-package_json.mjs +311 -0
- package/rules/bun/package_json/package_json.mdc +14 -0
- package/rules/bun/package_json/package_json.rego +64 -0
- package/rules/bun/package_json/template/package.json.deny.json +4 -0
- package/rules/js/check/check.mdc +26 -0
- package/rules/js/check/concern.json +19 -0
- package/rules/js/check/docs/eslint-config.md +56 -0
- package/rules/js/check/docs/fix-check.md +48 -0
- package/rules/js/check/docs/index.md +13 -0
- package/rules/js/check/docs/main.md +48 -0
- package/rules/js/check/eslint-config.mjs +262 -0
- package/rules/js/check/fix-check.mjs +82 -0
- package/rules/js/check/main.mjs +310 -0
- package/rules/js/dep-policy/concern.json +4 -0
- package/rules/js/dep-policy/dep-policy.mdc +36 -0
- package/rules/js/dep-policy/docs/main.md +35 -0
- package/rules/js/dep-policy/main.mjs +99 -0
- package/rules/js/docs/index.md +11 -0
- package/rules/js/eslint/concern.json +8 -0
- package/rules/js/eslint/docs/fix-eslint.md +50 -0
- package/rules/js/eslint/docs/fix-worker.md +31 -0
- package/rules/js/eslint/docs/index.md +11 -0
- package/rules/js/eslint/docs/main.md +35 -0
- package/rules/js/eslint/fix-eslint.mjs +160 -0
- package/rules/js/eslint/fix-worker.mjs +139 -0
- package/rules/js/eslint/main.mjs +115 -0
- package/rules/js/file-extensions/concern.json +3 -0
- package/rules/js/file-extensions/file-extensions.mdc +12 -0
- package/rules/js/jscpd_config/concern.json +11 -0
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
- package/rules/js/jscpd_config/docs/index.md +9 -0
- package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
- package/rules/js/jscpd_config/jscpd_config.rego +44 -0
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
- package/rules/js/jscpd_duplicates/concern.json +7 -0
- package/rules/js/jscpd_duplicates/docs/main.md +29 -0
- package/rules/js/jscpd_duplicates/main.mjs +69 -0
- package/rules/js/knip/concern.json +7 -0
- package/rules/js/knip/docs/main.md +31 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +89 -0
- package/rules/js/lint-findings/concern.json +3 -0
- package/rules/js/lint-findings/docs/main.md +40 -0
- package/rules/js/lint-findings/main.mjs +125 -0
- package/rules/js/main.json +1 -0
- package/rules/js/main.mdc +18 -0
- package/rules/js/package_json/concern.json +9 -0
- package/rules/js/package_json/docs/fix-package_json.md +25 -0
- package/rules/js/package_json/docs/index.md +9 -0
- package/rules/js/package_json/fix-package_json.mjs +3 -0
- package/rules/js/package_json/package_json.mdc +15 -0
- package/rules/js/package_json/package_json.rego +142 -0
- package/rules/js/package_json/template/package.json.snippet.json +6 -0
- package/rules/js/tooling/concern.json +3 -0
- package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
- package/rules/js/tooling/docs/main.md +53 -0
- package/rules/js/tooling/main.mjs +183 -0
- package/rules/js/utils_imports/concern.json +4 -0
- package/rules/js/utils_imports/docs/main.md +50 -0
- package/rules/js/utils_imports/main.mjs +185 -0
- package/rules/js/utils_imports/utils_imports.mdc +15 -0
- package/rules/js/vscode_extensions/concern.json +11 -0
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/js/vscode_extensions/docs/index.md +11 -0
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
- package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/js-bun-db/connection/concern.json +3 -0
- package/rules/js-bun-db/connection/connection.mdc +42 -0
- package/rules/js-bun-db/docs/index.md +11 -0
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
- package/rules/js-bun-db/lib/docs/index.md +11 -0
- package/rules/js-bun-db/main.json +1 -0
- package/rules/js-bun-db/main.mdc +8 -0
- package/rules/js-bun-db/package_json/concern.json +9 -0
- package/rules/js-bun-db/package_json/package_json.mdc +31 -0
- package/rules/js-bun-db/package_json/package_json.rego +15 -0
- package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
- package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
- package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
- package/rules/js-bun-db/safety/concern.json +4 -0
- package/rules/js-bun-db/safety/docs/main.md +34 -0
- package/rules/js-bun-db/safety/main.mjs +430 -0
- package/rules/js-bun-db/safety/safety.mdc +458 -0
- package/rules/js-bun-redis/docs/index.md +11 -0
- package/rules/js-bun-redis/imports/concern.json +4 -0
- package/rules/js-bun-redis/imports/docs/main.md +36 -0
- package/rules/js-bun-redis/imports/imports.mdc +47 -0
- package/rules/js-bun-redis/imports/main.mjs +88 -0
- package/rules/js-bun-redis/lib/docs/index.md +11 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
- package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
- package/rules/js-bun-redis/main.json +1 -0
- package/rules/js-bun-redis/main.mdc +8 -0
- package/rules/js-bun-redis/package_json/concern.json +9 -0
- package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
- package/rules/js-bun-redis/package_json/package_json.rego +15 -0
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
- package/rules/js-mssql/deps/concern.json +4 -0
- package/rules/js-mssql/deps/docs/main.md +33 -0
- package/rules/js-mssql/deps/main.mjs +297 -0
- package/rules/js-mssql/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
- package/rules/js-mssql/main.json +1 -0
- package/rules/js-mssql/main.mdc +144 -0
- package/rules/js-mssql/mssql-tvp/concern.json +3 -0
- package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
- package/rules/js-mssql/package_json/concern.json +9 -0
- package/rules/js-mssql/package_json/package_json.mdc +9 -0
- package/rules/js-mssql/package_json/package_json.rego +57 -0
- package/rules/js-run/configmap/concern.json +9 -0
- package/rules/js-run/configmap/configmap.mdc +37 -0
- package/rules/js-run/configmap/configmap.rego +21 -0
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/docs/index.md +11 -0
- package/rules/js-run/jsconfig/concern.json +9 -0
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
- package/rules/js-run/jsconfig/docs/index.md +9 -0
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
- package/rules/js-run/jsconfig/jsconfig.rego +59 -0
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/lib/bunyan-imports.mjs +98 -0
- package/rules/js-run/lib/check-env-scan.mjs +338 -0
- package/rules/js-run/lib/conn-file-rules.mjs +214 -0
- package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
- package/rules/js-run/lib/docs/check-env-scan.md +438 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
- package/rules/js-run/lib/docs/index.md +16 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
- package/rules/js-run/lib/docs/temporal-scan.md +29 -0
- package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
- package/rules/js-run/lib/temporal-scan.mjs +52 -0
- package/rules/js-run/main.json +1 -0
- package/rules/js-run/main.mdc +16 -0
- package/rules/js-run/package_json/concern.json +9 -0
- package/rules/js-run/package_json/package_json.mdc +44 -0
- package/rules/js-run/package_json/package_json.rego +37 -0
- package/rules/js-run/package_json/template/package.json.deny.json +22 -0
- package/rules/js-run/project-structure/concern.json +3 -0
- package/rules/js-run/project-structure/project-structure.mdc +11 -0
- package/rules/js-run/runtime/concern.json +12 -0
- package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
- package/rules/js-run/runtime/docs/main.md +35 -0
- package/rules/js-run/runtime/fix-runtime.mjs +46 -0
- package/rules/js-run/runtime/main.mjs +496 -0
- package/rules/js-run/runtime/runtime.mdc +184 -0
- package/rules/js-run/scope/concern.json +3 -0
- package/rules/js-run/scope/scope.mdc +11 -0
- package/rules/npm-module/docs/index.md +11 -0
- package/rules/npm-module/emit_types_config/concern.json +9 -0
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
- package/rules/npm-module/emit_types_config/docs/index.md +9 -0
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
- package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/header_doc_pointer/concern.json +5 -0
- package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
- package/rules/npm-module/main.json +1 -0
- package/rules/npm-module/main.mdc +36 -0
- package/rules/npm-module/npm_package_json/concern.json +9 -0
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
- package/rules/npm-module/npm_package_json/docs/index.md +9 -0
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
- package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/package_structure/concern.json +5 -0
- package/rules/npm-module/package_structure/docs/main.md +37 -0
- package/rules/npm-module/package_structure/main.mjs +448 -0
- package/rules/npm-module/package_structure/package_structure.mdc +63 -0
- package/rules/npm-module/root_package_json/concern.json +9 -0
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
- package/rules/npm-module/root_package_json/docs/index.md +9 -0
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
- package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
- package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/rule_meta/concern.json +8 -0
- package/rules/npm-module/rule_meta/docs/main.md +35 -0
- package/rules/npm-module/rule_meta/main.mjs +119 -0
- package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
- package/rules/npm-module/skill_meta/concern.json +5 -0
- package/rules/npm-module/skill_meta/docs/main.md +149 -0
- package/rules/npm-module/skill_meta/main.mjs +91 -0
- package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
- package/rules/tool-surface/docs/index.md +11 -0
- package/rules/tool-surface/main.json +6 -0
- package/rules/tool-surface/main.mdc +72 -0
- package/rules/vue/composition-api/composition-api.mdc +82 -0
- package/rules/vue/composition-api/concern.json +3 -0
- package/rules/vue/docs/index.md +11 -0
- package/rules/vue/lib/docs/index.md +11 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
- package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
- package/rules/vue/main.json +1 -0
- package/rules/vue/main.mdc +18 -0
- package/rules/vue/nheader-layout/concern.json +3 -0
- package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
- package/rules/vue/package_json/concern.json +9 -0
- package/rules/vue/package_json/package_json.mdc +30 -0
- package/rules/vue/package_json/package_json.rego +140 -0
- package/rules/vue/packages/concern.json +6 -0
- package/rules/vue/packages/docs/index.md +11 -0
- package/rules/vue/packages/docs/main.md +35 -0
- package/rules/vue/packages/main.mjs +575 -0
- package/rules/vue/packages/packages.mdc +56 -0
- package/rules/vue/quasar-ui/concern.json +3 -0
- package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
- package/rules/vue/structure/concern.json +3 -0
- package/rules/vue/structure/structure.mdc +101 -0
- package/rules/vue/testing/concern.json +3 -0
- package/rules/vue/testing/testing.mdc +40 -0
- package/rules/vue/tfm-translations/concern.json +7 -0
- package/rules/vue/tfm-translations/docs/main.md +29 -0
- package/rules/vue/tfm-translations/main.mjs +55 -0
- package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
- package/rules/vue/vite-config/concern.json +3 -0
- package/rules/vue/vite-config/vite-config.mdc +153 -0
- package/rules/vue/vite-env/concern.json +3 -0
- 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,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
|
+
}
|