@7n/rules 1.20.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +1 -1
  3. package/rules/bun/licensee/docs/fix-licensee.md +1 -2
  4. package/rules/bun/licensee/docs/main.md +1 -1
  5. package/rules/bun/licensee/fix-licensee.mjs +3 -4
  6. package/rules/bun/licensee/main.mjs +7 -7
  7. package/rules/docker/lib/docker-hadolint.mjs +6 -8
  8. package/rules/docker/lib/docs/docker-hadolint.md +1 -1
  9. package/rules/docker/lint/docs/main.md +1 -1
  10. package/rules/docker/lint/main.mjs +1 -1
  11. package/rules/image-compress/check/docs/fix-check.md +1 -2
  12. package/rules/image-compress/check/docs/main.md +1 -1
  13. package/rules/image-compress/check/fix-check.mjs +27 -22
  14. package/rules/image-compress/check/main.mjs +14 -14
  15. package/rules/js/jscpd_duplicates/docs/main.md +1 -1
  16. package/rules/js/jscpd_duplicates/main.mjs +7 -8
  17. package/rules/k8s/kubeconform/docs/main.md +1 -1
  18. package/rules/k8s/kubeconform/main.mjs +1 -1
  19. package/rules/k8s/manifests/main.mjs +52 -39
  20. package/rules/php/cs_fixer/docs/main.md +1 -2
  21. package/rules/php/cs_fixer/main.mjs +8 -7
  22. package/rules/php/phpcs/docs/main.md +1 -2
  23. package/rules/php/phpcs/main.mjs +12 -11
  24. package/rules/php/project/docs/main.md +1 -2
  25. package/rules/php/project/main.mjs +19 -16
  26. package/rules/rego/conftest_verify/docs/main.md +1 -2
  27. package/rules/rego/conftest_verify/main.mjs +12 -10
  28. package/rules/rego/lib/docs/run-external-tool.md +1 -1
  29. package/rules/rego/lib/run-external-tool.mjs +10 -7
  30. package/rules/rego/opa_check/docs/main.md +1 -1
  31. package/rules/rego/opa_check/main.mjs +3 -3
  32. package/rules/rego/regal/docs/main.md +1 -1
  33. package/rules/rego/regal/main.mjs +3 -3
  34. package/rules/security/scan/docs/main.md +1 -1
  35. package/rules/security/scan/main.mjs +9 -9
  36. package/rules/style/lint/docs/fix-lint.md +1 -1
  37. package/rules/style/lint/docs/main.md +1 -1
  38. package/rules/style/lint/fix-lint.mjs +8 -8
  39. package/rules/style/lint/main.mjs +6 -6
  40. package/rules/text/cspell-fix/docs/fix-worker.md +1 -1
  41. package/rules/text/cspell-fix/docs/main.md +1 -1
  42. package/rules/text/cspell-fix/fix-worker.mjs +1 -1
  43. package/rules/text/cspell-fix/main.mjs +14 -15
  44. package/rules/text/oxfmt/docs/fix-oxfmt.md +1 -1
  45. package/rules/text/oxfmt/docs/main.md +1 -1
  46. package/rules/text/oxfmt/fix-oxfmt.mjs +3 -3
  47. package/rules/text/oxfmt/main.mjs +6 -5
  48. package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +1 -2
  49. package/rules/text/run-dotenv-linter/docs/main.md +1 -1
  50. package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +2 -2
  51. package/rules/text/run-dotenv-linter/main.mjs +19 -24
  52. package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +1 -2
  53. package/rules/text/run-shellcheck/docs/main.md +1 -1
  54. package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +3 -3
  55. package/rules/text/run-shellcheck/main.mjs +46 -57
  56. package/rules/text/run-v8r/docs/main.md +1 -1
  57. package/rules/text/run-v8r/main.mjs +31 -26
  58. package/scripts/lib/docs/resolve-plugins.md +1 -1
  59. package/scripts/lib/lint-surface/blocking-inventory.mjs +12 -40
  60. package/scripts/lib/lint-surface/docs/blocking-inventory.md +1 -2
  61. package/scripts/utils/docs/spawn-async.md +1 -2
  62. package/scripts/utils/spawn-async.mjs +4 -1
  63. package/rules/python/applies/concern.json +0 -4
  64. package/rules/python/applies/docs/main.md +0 -29
  65. package/rules/python/applies/main.mjs +0 -24
  66. package/rules/python/docs/index.md +0 -11
  67. package/rules/python/lib/docs/index.md +0 -9
  68. package/rules/python/lib/docs/uv-run.md +0 -32
  69. package/rules/python/lib/uv-run.mjs +0 -48
  70. package/rules/python/main.json +0 -1
  71. package/rules/python/main.mdc +0 -8
  72. package/rules/python/mypy/concern.json +0 -7
  73. package/rules/python/mypy/docs/main.md +0 -32
  74. package/rules/python/mypy/main.mjs +0 -36
  75. package/rules/python/project/concern.json +0 -6
  76. package/rules/python/project/docs/main.md +0 -43
  77. package/rules/python/project/main.mjs +0 -115
  78. package/rules/python/pyproject_toml/concern.json +0 -9
  79. package/rules/python/pyproject_toml/pyproject_toml.mdc +0 -29
  80. package/rules/python/pyproject_toml/pyproject_toml.rego +0 -53
  81. package/rules/python/pyproject_toml/template/pyproject.toml.deny.toml +0 -2
  82. package/rules/python/pyproject_toml/template/pyproject.toml.snippet.toml +0 -12
  83. package/rules/python/ruff/concern.json +0 -7
  84. package/rules/python/ruff/docs/fix-ruff.md +0 -34
  85. package/rules/python/ruff/docs/main.md +0 -35
  86. package/rules/python/ruff/fix-ruff.mjs +0 -63
  87. package/rules/python/ruff/main.mjs +0 -52
  88. package/rules/python/tooling/concern.json +0 -15
  89. package/rules/python/tooling/docs/index.md +0 -11
  90. package/rules/python/tooling/docs/main.md +0 -35
  91. package/rules/python/tooling/main.mjs +0 -45
  92. package/rules/python/tooling/tooling.mdc +0 -22
  93. package/rules/rust/applies/concern.json +0 -4
  94. package/rules/rust/applies/docs/index.md +0 -11
  95. package/rules/rust/applies/docs/main.md +0 -30
  96. package/rules/rust/applies/main.mjs +0 -28
  97. package/rules/rust/check/check.mdc +0 -16
  98. package/rules/rust/check/concern.json +0 -7
  99. package/rules/rust/check/docs/fix-check.md +0 -30
  100. package/rules/rust/check/docs/index.md +0 -11
  101. package/rules/rust/check/docs/main.md +0 -37
  102. package/rules/rust/check/fix-check.mjs +0 -79
  103. package/rules/rust/check/main.mjs +0 -81
  104. package/rules/rust/docs/index.md +0 -11
  105. package/rules/rust/lib/docs/has-cargo-toml.md +0 -134
  106. package/rules/rust/lib/docs/index.md +0 -11
  107. package/rules/rust/lib/has-cargo-toml.mjs +0 -37
  108. package/rules/rust/main.json +0 -1
  109. package/rules/rust/main.mdc +0 -8
  110. package/rules/rust/package_json/concern.json +0 -10
  111. package/rules/rust/package_json/package_json.mdc +0 -12
  112. package/rules/rust/package_json/package_json.rego +0 -20
  113. package/rules/rust/vscode_extensions/concern.json +0 -11
  114. package/rules/rust/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
  115. package/rules/rust/vscode_extensions/docs/index.md +0 -11
  116. package/rules/rust/vscode_extensions/fix-vscode_extensions.mjs +0 -1
  117. package/rules/rust/vscode_extensions/template/extensions.json.snippet.json +0 -1
  118. package/rules/rust/vscode_extensions/vscode_extensions.mdc +0 -9
  119. package/rules/rust/vscode_extensions/vscode_extensions.rego +0 -15
@@ -44,7 +44,6 @@
44
44
  * незалежно від verbosity) і виводить у stdout окреме попередження на кожен такий файл із порадою
45
45
  * додати схему в `npm/schemas/v8r-catalog.json`.
46
46
  */
47
- import { spawnSync } from 'node:child_process'
48
47
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
49
48
  import { tmpdir } from 'node:os'
50
49
  import { basename, delimiter, dirname, isAbsolute, join } from 'node:path'
@@ -53,6 +52,7 @@ import { fileURLToPath } from 'node:url'
53
52
 
54
53
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
55
54
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
55
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
56
56
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
57
57
 
58
58
  /** Розширення, які валідує v8r — фільтр delta-списку файлів у `lint(ctx)`. */
@@ -183,31 +183,30 @@ export function stripBunNodeShimDirs(pathValue) {
183
183
  * (`lint()`) для вбудовування у violation-повідомлення, яке бачить LLM fix-worker: без нього
184
184
  * fix-ladder отримує лише "щось не пройшло" й не має шансів вгадати, що саме (спостережено —
185
185
  * усі 4 rung-и незмінно падають у timeout на v8r-порушеннях).
186
+ * Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
187
+ * (ADR 260716-1354).
186
188
  * @param {string[]} targets glob-и або конкретні шляхи файлів
187
189
  * @param {string} configPath шлях до `V8R_CONFIG_FILE`
188
190
  * @param {boolean} [verbose] друкувати повний raw stdout/stderr v8r при помилці; інакше — лише
189
191
  * рядки `✖ …` без `ℹ`-шуму (Pre-warming the cache, Processing <file>, Found schema in …)
190
- * @returns {{ exitError: true } | { exitError: false, code: number, detail: string }} помилка
191
- * spawn або код v8r (0/98 — трактує викликач) + деталь `✖ …`-рядків
192
+ * @returns {Promise<{ exitError: true } | { exitError: false, code: number, detail: string }>}
193
+ * помилка spawn або код v8r (0/98 — трактує викликач) + деталь `✖ …`-рядків
192
194
  */
193
- function runOneV8rInvocation(targets, configPath, verbose = false) {
195
+ async function runOneV8rInvocation(targets, configPath, verbose = false) {
194
196
  const bunPath = resolveCmd('bun') ?? process.execPath
195
- const result = spawnSync(bunPath, ['x', 'v8r', ...targets], {
196
- encoding: 'utf8',
197
- maxBuffer: 50 * 1024 * 1024,
198
- shell: false,
199
- stdio: ['ignore', 'pipe', 'pipe'],
200
- env: { ...env, PATH: stripBunNodeShimDirs(env.PATH), V8R_CONFIG_FILE: configPath }
201
- })
202
-
203
- if (result.error) {
204
- process.stderr.write(`${result.error.message}\n`)
197
+ let result
198
+ try {
199
+ result = await spawnAsync(bunPath, ['x', 'v8r', ...targets], {
200
+ env: { ...env, PATH: stripBunNodeShimDirs(env.PATH), V8R_CONFIG_FILE: configPath }
201
+ })
202
+ } catch (error) {
203
+ process.stderr.write(`${error.message}\n`)
205
204
  return { exitError: true }
206
205
  }
207
206
 
208
207
  warnAboutRemoteSchemaFallback(result.stderr ?? '')
209
208
 
210
- const exitCode = result.status ?? 1
209
+ const exitCode = result.exitCode ?? 1
211
210
  let detail = ''
212
211
  if (exitCode !== 0 && exitCode !== 98) {
213
212
  detail = extractFailureLines(`${result.stdout ?? ''}\n${result.stderr ?? ''}`)
@@ -225,12 +224,15 @@ function runOneV8rInvocation(targets, configPath, verbose = false) {
225
224
  * Запускає послідовні виклики v8r по glob-ам (full-режим); не змінює process.exitCode.
226
225
  * Один виклик на glob навмисно (не batch) — v8r падає з кодом 98, якщо хоч один переданий
227
226
  * glob не знаходить файлів, і тоді решта розширень не перевіряються в тому ж виклику.
227
+ * Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
228
+ * (ADR 260716-1354). Виклики по glob-ам лишаються строго послідовними (не Promise.all) — той
229
+ * самий один-виклик-на-glob підхід, лише блокуючий spawnSync замінено на await у циклі.
228
230
  * @param {string[]} [globs] патерни; за замовчуванням DEFAULT_V8R_GLOBS
229
231
  * @param {boolean} [verbose] друкувати повний raw вивід v8r при помилці (див. runOneV8rInvocation)
230
- * @returns {{ code: number, detail: string }} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
232
+ * @returns {Promise<{ code: number, detail: string }>} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
231
233
  * каталогу схем, інше — код v8r; `detail` — рядки `✖ …` (порожньо, якщо `code` не про валідацію)
232
234
  */
233
- export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
235
+ export async function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
234
236
  if (!existsSync(V8R_CATALOG_PATH)) {
235
237
  process.stderr.write(
236
238
  `run-v8r: не знайдено каталог схем за шляхом ${V8R_CATALOG_PATH} (очікується npm/schemas/v8r-catalog.json у пакеті)\n`
@@ -241,7 +243,7 @@ export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
241
243
  const configPath = writeResolvedV8rConfig()
242
244
 
243
245
  for (const pattern of globs) {
244
- const r = runOneV8rInvocation([pattern], configPath, verbose)
246
+ const r = await runOneV8rInvocation([pattern], configPath, verbose)
245
247
  if (r.exitError) return { code: 1, detail: '' }
246
248
  if (r.code !== 0 && r.code !== 98) return { code: r.code, detail: r.detail }
247
249
  }
@@ -251,12 +253,14 @@ export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
251
253
  /**
252
254
  * Запускає v8r по конкретному списку файлів (delta-режим) — один виклик, не по одному glob-у,
253
255
  * бо кожен переданий шлях уже існує (не glob), тож код 98 "порожній glob" тут не виникає.
256
+ * Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
257
+ * (ADR 260716-1354).
254
258
  * @param {string[]} files абсолютні або відносні до cwd v8r-процесу шляхи файлів
255
259
  * @param {boolean} [verbose] друкувати повний raw вивід v8r при помилці (див. runOneV8rInvocation)
256
- * @returns {{ code: number, detail: string }} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
260
+ * @returns {Promise<{ code: number, detail: string }>} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
257
261
  * каталогу схем, інше — код v8r; `detail` — рядки `✖ …` (порожньо, якщо `code` не про валідацію)
258
262
  */
259
- export function runV8rWithFiles(files, verbose = false) {
263
+ export async function runV8rWithFiles(files, verbose = false) {
260
264
  if (files.length === 0) return { code: 0, detail: '' }
261
265
  if (!existsSync(V8R_CATALOG_PATH)) {
262
266
  process.stderr.write(
@@ -266,7 +270,7 @@ export function runV8rWithFiles(files, verbose = false) {
266
270
  }
267
271
 
268
272
  const configPath = writeResolvedV8rConfig()
269
- const r = runOneV8rInvocation(files, configPath, verbose)
273
+ const r = await runOneV8rInvocation(files, configPath, verbose)
270
274
  if (r.exitError) return { code: 1, detail: '' }
271
275
  return { code: r.code === 98 ? 0 : r.code, detail: r.detail }
272
276
  }
@@ -285,28 +289,29 @@ function v8rFailMessage(detail) {
285
289
  /**
286
290
  * Detector text/run-v8r: read-only v8r по `ctx.files` (delta) або за дефолтними glob-ами (full).
287
291
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
288
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат detector-а
292
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат detector-а
289
293
  */
290
- export function lint(ctx) {
294
+ export async function lint(ctx) {
291
295
  const reporter = createViolationReporter(ctx)
292
296
  const { fail } = reporter
293
297
 
294
298
  const verbose = ctx.verbose === true
295
299
 
296
300
  if (ctx.files === undefined) {
297
- const { code, detail } = runV8rWithGlobs(DEFAULT_V8R_GLOBS, verbose)
301
+ const { code, detail } = await runV8rWithGlobs(DEFAULT_V8R_GLOBS, verbose)
298
302
  if (code !== 0) fail(v8rFailMessage(detail), 'v8r')
299
303
  return reporter.result()
300
304
  }
301
305
 
302
306
  const files = ctx.files.filter(f => V8R_EXT_RE.test(f))
303
307
  if (files.length === 0) return reporter.result()
304
- const { code, detail } = runV8rWithFiles(files, verbose)
308
+ const { code, detail } = await runV8rWithFiles(files, verbose)
305
309
  if (code !== 0) fail(v8rFailMessage(detail), 'v8r')
306
310
  return reporter.result()
307
311
  }
308
312
 
309
313
  if (isRunAsCli(import.meta.url)) {
310
314
  const globs = process.argv.length > 2 ? process.argv.slice(2) : DEFAULT_V8R_GLOBS
311
- process.exitCode = runV8rWithGlobs(globs).code
315
+ const { code } = await runV8rWithGlobs(globs)
316
+ process.exitCode = code
312
317
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: resolve-plugins.mjs
4
4
  resource: npm/scripts/lib/resolve-plugins.mjs
5
5
  docgen:
6
- crc: ab45e0ca
6
+ crc: 17248373
7
7
  ---
8
8
 
9
9
  Резолв плагінів @7n/rules: визначає, які пакети-плагіни активні у проєкті, де їхні `rules/`-каталоги, які capabilities вони надають і які handlers надають.
@@ -6,47 +6,18 @@
6
6
  * `execSync` (прямо або через спільний helper), бо це блокує event loop цілком і
7
7
  * зробило б паралельний пул ілюзорним.
8
8
  *
9
- * Нова міграція (наступний shared helper на `spawnAsync`, за протоколом
10
- * `runConftestBatch`/`runOxlintJson`): переведи helper → онови caller-и (`await`) →
11
- * прибери відповідний запис звідси розшир `docs/blocking-inventory-guard.test.mjs`
12
- * (guard-тест сам перевірить, що жоден із них більше не викликає `spawnSync`/`execSync`).
13
- */
14
-
15
- /**
16
- * Concern-и з прямим `spawnSync`/`execSync` у власному `main.mjs` кожен спавнить свій
17
- * зовнішній тул напряму, ще не через `spawnAsync`.
18
- */
19
- const DIRECT_SPAWN_CONCERNS = [
20
- 'text/run-dotenv-linter',
21
- 'text/oxfmt',
22
- 'text/cspell-fix',
23
- 'text/run-v8r',
24
- 'text/run-shellcheck',
25
- 'image-compress/check',
26
- 'php/phpcs',
27
- 'php/project',
28
- 'php/cs_fixer',
29
- 'js/jscpd_duplicates',
30
- 'k8s/manifests',
31
- 'style/lint',
32
- 'bun/licensee',
33
- 'python/ruff',
34
- 'python/project',
35
- 'python/mypy',
36
- 'security/scan',
37
- 'rust/check',
38
- 'rego/conftest_verify'
39
- ]
40
-
41
- /**
42
- * Concern-и, чий detector сам не викликає `spawnSync`/`execSync`, але делегує спільному
43
- * helper-у, який його викликає: `docker/lib/docker-hadolint.mjs` (hadolint) і
44
- * `rego/lib/run-external-tool.mjs` (regal/opa).
9
+ * Список зараз порожній: усі 22 concern-и, що на момент ADR мали прямий чи
10
+ * shared-helper `spawnSync`/`execSync`, мігровано на `spawnAsync`
11
+ * (`npm/scripts/utils/spawn-async.mjs`). Інвентар лишається як живий guard
12
+ * `tests/blocking-inventory.test.mjs` сканує активні concern-и репо і провалиться,
13
+ * щойно новий (чи regressed) concern звернеться до `spawnSync`/`execSync` без
14
+ * відповідного запису тут.
15
+ *
16
+ * Протокол для нового blocking concern-а: перевести на `spawnAsync` оновити
17
+ * caller-и (`await`) якщо міграція ще не завершена, додати запис сюди на час
18
+ * переходу; guard-тест сам підтвердить, коли можна прибрати.
45
19
  */
46
- const SHARED_HELPER_CONCERNS = ['docker/lint', 'rego/regal', 'rego/opa_check']
47
-
48
- /** Повний serial-lane список: `${ruleId}/${concernId}` — 19 прямих + 3 через shared helper = 22. */
49
- export const SERIAL_LANE_CONCERNS = new Set([...DIRECT_SPAWN_CONCERNS, ...SHARED_HELPER_CONCERNS])
20
+ export const SERIAL_LANE_CONCERNS = new Set()
50
21
 
51
22
  /**
52
23
  * Чи concern лишається у serial lane `detectAll()` (недоведений non-blocking).
@@ -55,5 +26,6 @@ export const SERIAL_LANE_CONCERNS = new Set([...DIRECT_SPAWN_CONCERNS, ...SHARED
55
26
  * @returns {boolean} true — serial lane; false — parallel-safe
56
27
  */
57
28
  export function isSerialLane(ruleId, concernId) {
29
+ // eslint-disable-next-line sonarjs/no-empty-collection -- навмисно порожній зараз (протокол вище); живий registry, не dead code
58
30
  return SERIAL_LANE_CONCERNS.has(`${ruleId}/${concernId}`)
59
31
  }
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: blocking-inventory.mjs
4
4
  resource: npm/scripts/lib/lint-surface/blocking-inventory.mjs
5
5
  docgen:
6
- crc: 2e0e8624
6
+ crc: dce1fafd
7
7
  model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
8
  score: 100
10
9
  issues: judge:inaccurate:0.98
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: spawn-async.mjs
4
4
  resource: npm/scripts/utils/spawn-async.mjs
5
5
  docgen:
6
- crc: cc5d92e0
6
+ crc: 0e8ce2ef
7
7
  model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
8
  score: 100
10
9
  issues: judge:inaccurate:0.98
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -43,15 +43,18 @@ class AbortError extends Error {
43
43
  * @param {number} [opts.killGraceMs] пауза між `SIGTERM` і ескалацією до `SIGKILL` (дефолт 5000)
44
44
  * @param {string} [opts.cwd] робочий каталог дочірнього процесу
45
45
  * @param {Record<string, string>} [opts.env] оточення дочірнього процесу
46
+ * @param {string} [opts.input] дані для запису в stdin дочірнього процесу (`utf8`, аналог
47
+ * `spawnSync`-опції `input`); stdin закривається (`end`) одразу після запису
46
48
  * @returns {Promise<SpawnAsyncResult>} нормалізований результат виконання
47
49
  */
48
50
  export async function spawnAsync(cmd, args, opts = {}) {
49
- const { signal, timeoutMs, killGraceMs = 5000, ...spawnOpts } = opts
51
+ const { signal, timeoutMs, killGraceMs = 5000, input, ...spawnOpts } = opts
50
52
  if (signal?.aborted) throw new AbortError()
51
53
 
52
54
  const child = spawn(cmd, args, spawnOpts)
53
55
  child.stdout?.setEncoding('utf8')
54
56
  child.stderr?.setEncoding('utf8')
57
+ if (input !== undefined) child.stdin?.end(input, 'utf8')
55
58
 
56
59
  let stdout = ''
57
60
  let stderr = ''
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
- "lint": { "scope": "full", "glob": ["pyproject.toml"] }
4
- }
@@ -1,29 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: main.mjs
4
- resource: npm/rules/python/applies/main.mjs
5
- docgen:
6
- crc: 871eb5c6
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
9
- issues: judge:inaccurate:0.97
10
- judgeModel: openai-codex/gpt-5.4-mini
11
- ---
12
-
13
- ## Огляд
14
-
15
- Перевіряє наявність файлу `pyproject.toml` у корені репозиторію для визначення, чи має застосовуватися правило до проєктів, що використовують Python. Публічні функції `applies` та `main` реалізують цю логіку.
16
-
17
- ## Поведінка
18
-
19
- applies визначає, чи застосовне правило, перевіряючи наявність файлу `pyproject.toml` у корені репозиторію.
20
- main повідомляє про успішне знаходження файлу `pyproject.toml`, що підтверджує застосування правила для Python.
21
-
22
- ## Публічний API
23
-
24
- - applies — визначає загальні налаштування та конфігурацію.
25
- - main — виводить загальний контекст, тоді як спеціалізовані блоки виконують конкретні перевірки.
26
-
27
- ## Гарантії поведінки
28
-
29
- - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,24 +0,0 @@
1
- /** @see ./docs/applies.md */
2
- import { existsSync } from 'node:fs'
3
- import { join } from 'node:path'
4
-
5
- import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
6
-
7
- /**
8
- * @param {string} [cwd] корінь репозиторію (`process.cwd()` у звичайному прогоні)
9
- * @returns {Promise<boolean>} `true` — правило застосовне; `false` — пропустити
10
- */
11
- export function applies(cwd = process.cwd()) {
12
- return Promise.resolve(existsSync(join(cwd, 'pyproject.toml')))
13
- }
14
-
15
- /**
16
- * Друкує короткий context-pass — самі перевірки виконують інші concerns.
17
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
18
- * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат context-pass
19
- */
20
- export function lint(ctx) {
21
- const reporter = createViolationReporter(ctx)
22
- reporter.pass('pyproject.toml знайдено в корені — застосовую python.mdc')
23
- return Promise.resolve(reporter.result())
24
- }
@@ -1,11 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/python
4
- resource: npm/rules/python/
5
- ---
6
-
7
- # npm/rules/python
8
-
9
- | Файл | Тип |
10
- | ------------------- | --------- |
11
- | [main.mjs](main.md) | JS Module |
@@ -1,9 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/rules/python/lib
4
- resource: npm/rules/python/lib/
5
- ---
6
-
7
- | Файл | Тип |
8
- | ----------------------- | --------- |
9
- | [uv-run.mjs](uv-run.md) | JS Module |
@@ -1,32 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: uv-run.mjs
4
- resource: npm/rules/python/lib/uv-run.mjs
5
- docgen:
6
- crc: 6aba3ab1
7
- model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
- score: 100
10
- issues: judge:inaccurate:0.97
11
- judgeModel: openai-codex/gpt-5.4-mini
12
- ---
13
-
14
- ## Огляд
15
-
16
- Спільний preflight для Python-детекторів `mypy` і `ruff`, виділений зі спільного дубльованого коду з `main.mjs`. Використовує `PY_EXT_RE`, щоб залишати лише Python-цілі; для `ctx.files` запускає перевірку по вибраних файлах, а без них — по всьому проєкту через `.`. За наявності `uvToolAvailable` готує запуск обох детекторів через `uv run --frozen`; `preparePythonRun` формує однаковий контракт підготовки для обох інструментів.
17
-
18
- ## Поведінка
19
-
20
- - `PY_EXT_RE` — визначає, чи файл належить до Python-цілей для delta-лінту.
21
- - `uvToolAvailable` — перевіряє, чи доступний Python-інструмент у середовищі `uv`.
22
- - `preparePythonRun` — готує спільний preflight для Python-лінтерів: пропускає без `pyproject.toml`, відсікає нерелевантні файли, перевіряє наявність `uv` і доступність інструменту, після чого повертає готові цілі для запуску через `uv run --frozen`.
23
-
24
- ## Публічний API
25
-
26
- - PY_EXT_RE — Відібрає лише Python-файли з delta-списку для `lint`.
27
- - uvToolAvailable — Підтверджує, що `uv` доступний у середовищі.
28
- - preparePythonRun — Готує запуск Python-цілі: звіряє `pyproject.toml`, визначає delta або full-обсяг, знаходить `uv` і перевіряє, чи є потрібний `tool`; якщо підготовка не вдалася, викликач завершує `reporter.result` раніше.
29
-
30
- ## Гарантії поведінки
31
-
32
- - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,48 +0,0 @@
1
- /**
2
- * Спільний preflight обох python-детекторів (`mypy`, `ruff`) — обидва per-file: приймають
3
- * `ctx.files`, інакше `.` (весь проєкт), обидва йдуть через `uv run --frozen`. Виділено зі
4
- * спільного дубльованого коду (jscpd) обох `main.mjs`.
5
- */
6
- import { spawnSync } from 'node:child_process'
7
- import { existsSync } from 'node:fs'
8
- import { join } from 'node:path'
9
-
10
- import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
11
-
12
- /** Розширення `.py` — фільтр delta-списку файлів у `lint(ctx)`. */
13
- export const PY_EXT_RE = /\.py$/u
14
-
15
- /**
16
- * @param {string} uv шлях до бінарника uv.
17
- * @param {string} tool ім'я інструменту в uv-середовищі.
18
- * @returns {boolean} true якщо інструмент доступний
19
- */
20
- export function uvToolAvailable(uv, tool) {
21
- const r = spawnSync(uv, ['run', '--frozen', tool, '--version'], { stdio: 'ignore', shell: false })
22
- return r.status === 0
23
- }
24
-
25
- /**
26
- * Спільний preflight: pyproject.toml, delta/full-цілі, резолв `uv`, доступність `tool` у
27
- * uv-середовищі. Викликач рано виходить (`reporter.result()`), коли повертається `null` —
28
- * `fail()` для випадку відсутнього `uv` уже викликаний усередині.
29
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
30
- * @param {(msg: string, opts: object) => void} fail колбек реєстрації порушення.
31
- * @param {string} tool ім'я інструменту (`mypy`|`ruff`) для перевірки доступності.
32
- * @returns {{uv: string, targets: string[]}|null} preflight-результат, або null (рано вийти).
33
- */
34
- export function preparePythonRun(ctx, fail, tool) {
35
- if (!existsSync(join(ctx.cwd, 'pyproject.toml'))) return null
36
-
37
- const targets = ctx.files === undefined ? ['.'] : ctx.files.filter(f => PY_EXT_RE.test(f))
38
- if (targets.length === 0) return null
39
-
40
- const uv = resolveCmd('uv')
41
- if (!uv) {
42
- fail('lint-python: `uv` не знайдено в PATH (потрібен при наявному pyproject.toml, python.mdc)', 'uv-missing')
43
- return null
44
- }
45
- if (!uvToolAvailable(uv, tool)) return null // tool недоступний у uv-середовищі → пропущено
46
-
47
- return { uv, targets }
48
- }
@@ -1 +0,0 @@
1
- { "auto": { "glob": "pyproject.toml" } }
@@ -1,8 +0,0 @@
1
- ---
2
- description: Python — пакет-менеджер uv (без Poetry), PEP 621, lint-python
3
- globs: "**/*.py,pyproject.toml,uv.lock,.github/workflows/lint-python.yml"
4
- alwaysApply: false
5
- version: '1.0'
6
- ---
7
-
8
- Python-проєкти ведуться **виключно** на [uv](https://docs.astral.sh/uv/) — єдиний пакет-менеджер і резолвер. **Poetry заборонено.** Середовище: `uv sync --frozen` (строго з `uv.lock`).
@@ -1,7 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
- "lint": {
4
- "scope": "per-file",
5
- "glob": ["**/*.py"]
6
- }
7
- }
@@ -1,32 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: main.mjs
4
- resource: npm/rules/python/mypy/main.mjs
5
- docgen:
6
- crc: 7cc910e3
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
- Please provide the draft section "overview" that needs revision, according to the context and requirements.
16
-
17
- ## Поведінка
18
-
19
- 1. Перевіряє наявність файлу `pyproject.toml` у кореневій директорії проєкту. Якщо його немає, перевірка не виконується.
20
- 2. Визначає цільові файли для перевірки: або всі `.py` файли у проєкті, або лише ті, що передані в контекст.
21
- 3. Перевіряє наявність у системному шляху інструмента `uv` та доступності `mypy` у середовищі `uv`. Якщо `uv` або `mypy` недоступні, перевірка пропускається.
22
- 4. Запускає `mypy` через `uv run --frozen` для визначених цільових файлів, передаючи поточну робочу директорію проєкту як робочу директорію для виконання.
23
- 5. Якщо `mypy` завершується з ненульовим кодом, фіксується порушення з виводом команди та кодом помилки.
24
- 6. Якщо `mypy` завершується успішно, результати перевірки вважаються позитивними.
25
-
26
- ## Публічний API
27
-
28
- lint — виявляє помилки в коді Python за допомогою mypy.
29
-
30
- ## Гарантії поведінки
31
-
32
- - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,36 +0,0 @@
1
- /**
2
- * lint-поверхня python/mypy: read-only detector (`mypy`, через `uv run --frozen`). Per-file:
3
- * приймає `ctx.files`, інакше `.` (весь проєкт). Виділено з колишнього bundled `python/check`
4
- * (spec docs/specs/2026-07-02-text-check-per-file-split-design.md "Рішення python/php/rego") —
5
- * mypy сам транзитивно підвантажує імпортовані модулі (follow-imports), тож дає коректні
6
- * діагнози й на підмножині переданих файлів. Немає fix capability (mypy — detect-only).
7
- */
8
- import { spawnSync } from 'node:child_process'
9
-
10
- import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
11
- import { preparePythonRun } from '../lib/uv-run.mjs'
12
-
13
- /**
14
- * Detector python/mypy (read-only).
15
- * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
16
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
17
- */
18
- export function lint(ctx) {
19
- const reporter = createViolationReporter(ctx)
20
- const { fail } = reporter
21
- const cwd = ctx.cwd
22
-
23
- const prepared = preparePythonRun(ctx, fail, 'mypy')
24
- if (!prepared) return reporter.result()
25
- const { uv, targets } = prepared
26
-
27
- const r = spawnSync(uv, ['run', '--frozen', 'mypy', ...targets], { cwd, encoding: 'utf8', shell: false })
28
- if (r.status !== 0) {
29
- const code = typeof r.status === 'number' ? r.status : 1
30
- const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
31
- const outSuffix = out ? `\n${out}` : ''
32
- fail(`lint-python: mypy — помилка (код ${code}, python.mdc)${outSuffix}`, 'mypy-violation')
33
- }
34
-
35
- return reporter.result()
36
- }
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
- "lint": {
4
- "scope": "full"
5
- }
6
- }
@@ -1,43 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: main.mjs
4
- resource: npm/rules/python/project/main.mjs
5
- docgen:
6
- crc: b47f2997
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- tier: local-min
9
- score: 100
10
- judgeModel: openai-codex/gpt-5.4-mini
11
- ---
12
-
13
- ## Огляд
14
-
15
- Огляд
16
- Цей детектор забезпечує перевірку цілісності оточення Python на рівні всього проекту. Він діагностує відповідність файлів блокування та ліцензій, виконуючи аудит на основі повного набору залежностей, а не лише змін у межах дельти.
17
-
18
- Поведінка
19
- Система перевіряє наявність файлу `pyproject.toml` у робочому каталозі; відсутність файлу призводить до успішного завершення.
20
- Визначає виконуваний файл `uv`; у разі відсутності він повідомляє про це (python.mdc).
21
- Виконує аудит цілісності файлу блокування за допомогою `uv lock --check`. Виявлені порушення фіксуються (python.mdc), і виконання негайно припиняється.
22
- Виконує збірку оточення через `uv sync --frozen`. Будь-які помилки фіксуються (python.mdc), зупиняючи процес.
23
- Сканує ліцензії усіх залежностей за допомогою `pip-licenses`. Якщо інструмент недоступний, перевірка пропускається. При помилці інструменту фіксується порушення (python.mdc). Якщо знайдено пакети, ліцензії яких не відповідають критеріям Blue Oak Bronze+, фіксується порушення (python.mdc) із переліком відповідних пакетів.
24
- Процес роботи є read-only, він не змінює файлової системи чи бази даних. Усі помилки перехоплюються з механізмом fail-safe, і винятки назовні не генеруються. На етапі виконання жодного кешування не відбувається.
25
-
26
- ## Поведінка
27
-
28
- Поведінка:
29
- 1. Перевіряє наявність файлу `pyproject.toml` у робочому каталозі. Якщо його немає, операція завершується успішно.
30
- 2. Знаходить виконуваний файл `uv`. Якщо він не знайдений, операція завершується з повідомленням про відсутність `uv` (python.mdc).
31
- 3. Виконує перевірку цілісності блокування за допомогою `uv lock --check`. Якщо виникає помилка, фіксує порушення (python.mdc) та завершує роботу.
32
- 4. Виконує збірку оточення за допомогою `uv sync --frozen`. Якщо виникає помилка, фіксує порушення (python.mdc) та завершує роботу.
33
- 5. Виконує перевірку ліцензій залежностей за допомогою `pip-licenses`. Якщо інструмент недоступний, перевірка пропускається. Якщо виникає помилка виконання, фіксує порушення (python.mdc). Якщо знайдено пакети з ліцензіями, що не відповідають критеріям Blue Oak Bronze+, фіксує порушення (python.mdc), перелічуючи знахідки.
34
- 6. Повертає результат виконання перевірок.
35
-
36
- ## Публічний API
37
-
38
- lint — знаходить проблеми в коді Python для даного проєкту.
39
-
40
- ## Гарантії поведінки
41
-
42
- - Read-only: не виконує операцій запису (ФС/БД).
43
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).