@7n/rules 1.20.0 → 1.20.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 +7 -0
- package/package.json +1 -1
- package/rules/bun/licensee/docs/fix-licensee.md +1 -2
- package/rules/bun/licensee/docs/main.md +1 -1
- package/rules/bun/licensee/fix-licensee.mjs +3 -4
- package/rules/bun/licensee/main.mjs +7 -7
- package/rules/docker/lib/docker-hadolint.mjs +6 -8
- package/rules/docker/lib/docs/docker-hadolint.md +1 -1
- package/rules/docker/lint/docs/main.md +1 -1
- package/rules/docker/lint/main.mjs +1 -1
- package/rules/image-compress/check/docs/fix-check.md +1 -2
- package/rules/image-compress/check/docs/main.md +1 -1
- package/rules/image-compress/check/fix-check.mjs +27 -22
- package/rules/image-compress/check/main.mjs +14 -14
- package/rules/js/jscpd_duplicates/docs/main.md +1 -1
- package/rules/js/jscpd_duplicates/main.mjs +7 -8
- package/rules/k8s/kubeconform/docs/main.md +1 -1
- package/rules/k8s/kubeconform/main.mjs +1 -1
- package/rules/k8s/manifests/main.mjs +52 -39
- package/rules/php/cs_fixer/docs/main.md +1 -2
- package/rules/php/cs_fixer/main.mjs +8 -7
- package/rules/php/phpcs/docs/main.md +1 -2
- package/rules/php/phpcs/main.mjs +12 -11
- package/rules/php/project/docs/main.md +1 -2
- package/rules/php/project/main.mjs +19 -16
- package/rules/python/lib/docs/uv-run.md +1 -2
- package/rules/python/lib/uv-run.mjs +12 -8
- package/rules/python/mypy/docs/main.md +1 -1
- package/rules/python/mypy/main.mjs +9 -8
- package/rules/python/project/docs/main.md +1 -2
- package/rules/python/project/main.mjs +33 -23
- package/rules/python/ruff/docs/fix-ruff.md +1 -2
- package/rules/python/ruff/docs/main.md +1 -1
- package/rules/python/ruff/fix-ruff.mjs +12 -10
- package/rules/python/ruff/main.mjs +13 -12
- package/rules/rego/conftest_verify/docs/main.md +1 -2
- package/rules/rego/conftest_verify/main.mjs +12 -10
- package/rules/rego/lib/docs/run-external-tool.md +1 -1
- package/rules/rego/lib/run-external-tool.mjs +10 -7
- package/rules/rego/opa_check/docs/main.md +1 -1
- package/rules/rego/opa_check/main.mjs +3 -3
- package/rules/rego/regal/docs/main.md +1 -1
- package/rules/rego/regal/main.mjs +3 -3
- package/rules/rust/check/docs/fix-check.md +1 -1
- package/rules/rust/check/docs/main.md +1 -1
- package/rules/rust/check/fix-check.mjs +13 -12
- package/rules/rust/check/main.mjs +19 -13
- package/rules/security/scan/docs/main.md +1 -1
- package/rules/security/scan/main.mjs +9 -9
- package/rules/style/lint/docs/fix-lint.md +1 -1
- package/rules/style/lint/docs/main.md +1 -1
- package/rules/style/lint/fix-lint.mjs +8 -8
- package/rules/style/lint/main.mjs +6 -6
- package/rules/text/cspell-fix/docs/fix-worker.md +1 -1
- package/rules/text/cspell-fix/docs/main.md +1 -1
- package/rules/text/cspell-fix/fix-worker.mjs +1 -1
- package/rules/text/cspell-fix/main.mjs +14 -15
- package/rules/text/oxfmt/docs/fix-oxfmt.md +1 -1
- package/rules/text/oxfmt/docs/main.md +1 -1
- package/rules/text/oxfmt/fix-oxfmt.mjs +3 -3
- package/rules/text/oxfmt/main.mjs +6 -5
- package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +1 -2
- package/rules/text/run-dotenv-linter/docs/main.md +1 -1
- package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +2 -2
- package/rules/text/run-dotenv-linter/main.mjs +19 -24
- package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +1 -2
- package/rules/text/run-shellcheck/docs/main.md +1 -1
- package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +3 -3
- package/rules/text/run-shellcheck/main.mjs +46 -57
- package/rules/text/run-v8r/docs/main.md +1 -1
- package/rules/text/run-v8r/main.mjs +31 -26
- package/scripts/lib/lint-surface/blocking-inventory.mjs +12 -40
- package/scripts/lib/lint-surface/docs/blocking-inventory.md +1 -2
- package/scripts/utils/docs/spawn-async.md +1 -2
- package/scripts/utils/spawn-async.mjs +4 -1
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* bundled `php/check` (spec docs/specs/2026-07-02-text-check-per-file-split-design.md "Рішення
|
|
5
5
|
* python/php/rego") — php-cs-fixer приймає список конкретних файлів аргументом.
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from 'node:child_process'
|
|
8
7
|
import { existsSync } from 'node:fs'
|
|
9
8
|
import { join, resolve } from 'node:path'
|
|
10
9
|
|
|
11
10
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
11
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
12
12
|
|
|
13
13
|
/** Розширення `.php` — фільтр delta-списку файлів у `lint(ctx)`. */
|
|
14
14
|
const PHP_EXT_RE = /\.php$/u
|
|
@@ -23,11 +23,12 @@ function vendorBin(root) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Detector php/cs_fixer (read-only).
|
|
26
|
+
* Detector php/cs_fixer (read-only). Async (не блокує event loop) — детектор може виконуватись
|
|
27
|
+
* у parallel lane `detectAll()` (ADR 260716-1354).
|
|
27
28
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
28
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
29
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
29
30
|
*/
|
|
30
|
-
export function lint(ctx) {
|
|
31
|
+
export async function lint(ctx) {
|
|
31
32
|
const reporter = createViolationReporter(ctx)
|
|
32
33
|
const { fail } = reporter
|
|
33
34
|
const root = ctx.cwd
|
|
@@ -40,9 +41,9 @@ export function lint(ctx) {
|
|
|
40
41
|
const abs = vendorBin(root)
|
|
41
42
|
if (!abs) return reporter.result() // php-cs-fixer відсутній у vendor/bin → пропущено
|
|
42
43
|
|
|
43
|
-
const r =
|
|
44
|
-
if (r.
|
|
45
|
-
const code = typeof r.
|
|
44
|
+
const r = await spawnAsync(abs, ['fix', '--dry-run', '--diff', ...targets], { cwd: root })
|
|
45
|
+
if (r.exitCode !== 0) {
|
|
46
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
46
47
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
47
48
|
const outSuffix = out ? `\n${out}` : ''
|
|
48
49
|
fail(`lint-php: PHP-CS-Fixer (dry-run) — помилка (код ${code}, php.mdc)${outSuffix}`, 'php-cs-fixer-violation')
|
package/rules/php/phpcs/main.mjs
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Виділено з колишнього bundled `php/check` (spec
|
|
5
5
|
* docs/specs/2026-07-02-text-check-per-file-split-design.md "Рішення python/php/rego").
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from 'node:child_process'
|
|
8
7
|
import { existsSync, statSync } from 'node:fs'
|
|
9
8
|
import { join, resolve } from 'node:path'
|
|
10
9
|
|
|
11
10
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
11
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
12
12
|
|
|
13
13
|
/** Розширення `.php` — фільтр delta-списку файлів у `lint(ctx)`. */
|
|
14
14
|
const PHP_EXT_RE = /\.php$/u
|
|
@@ -38,11 +38,12 @@ function vendorBin(root) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Detector php/phpcs (read-only).
|
|
41
|
+
* Detector php/phpcs (read-only). Async (не блокує event loop) — детектор може виконуватись
|
|
42
|
+
* у parallel lane `detectAll()` (ADR 260716-1354).
|
|
42
43
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
43
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
44
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
44
45
|
*/
|
|
45
|
-
export function lint(ctx) {
|
|
46
|
+
export async function lint(ctx) {
|
|
46
47
|
const reporter = createViolationReporter(ctx)
|
|
47
48
|
const { fail } = reporter
|
|
48
49
|
const root = ctx.cwd
|
|
@@ -55,13 +56,13 @@ export function lint(ctx) {
|
|
|
55
56
|
const abs = vendorBin(root)
|
|
56
57
|
if (!abs) return reporter.result() // phpcs відсутній у vendor/bin → пропущено
|
|
57
58
|
|
|
58
|
-
const r =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (r.
|
|
64
|
-
const code = typeof r.
|
|
59
|
+
const r = await spawnAsync(
|
|
60
|
+
abs,
|
|
61
|
+
['--standard=Security', '--ignore=*/vendor/*,*/node_modules/*,*/.git/*', ...targets],
|
|
62
|
+
{ cwd: root }
|
|
63
|
+
)
|
|
64
|
+
if (r.exitCode !== 0) {
|
|
65
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
65
66
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
66
67
|
const outSuffix = out ? `\n${out}` : ''
|
|
67
68
|
fail(`lint-php: phpcs (Security) — помилка (код ${code}, php.mdc)${outSuffix}`, 'phpcs-violation')
|
|
@@ -3,9 +3,8 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/php/project/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: b2bd6168
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
tier: local-min
|
|
9
8
|
score: 100
|
|
10
9
|
issues: judge:inaccurate:0.97
|
|
11
10
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
* audit. Не входять у delta-план (§5): спрацьовують лише через `n-rules lint --full` або
|
|
8
8
|
* scoped `n-rules lint php`.
|
|
9
9
|
*/
|
|
10
|
-
import { spawnSync } from 'node:child_process'
|
|
11
10
|
import { existsSync } from 'node:fs'
|
|
12
11
|
import { join, resolve } from 'node:path'
|
|
13
12
|
|
|
14
13
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
15
14
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
15
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {string} root корінь
|
|
@@ -25,19 +25,20 @@ function vendorBin(root, name) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Запускає тул і, на ненульовий код, реєструє порушення.
|
|
28
|
+
* Запускає тул і, на ненульовий код, реєструє порушення. Async (не блокує event loop) —
|
|
29
|
+
* детектор може виконуватись у parallel lane `detectAll()` (ADR 260716-1354).
|
|
29
30
|
* @param {string} label назва кроку
|
|
30
31
|
* @param {string} abs абсолютний шлях
|
|
31
32
|
* @param {string[]} args аргументи команди.
|
|
32
33
|
* @param {string} cwd робочий каталог.
|
|
33
34
|
* @param {(msg: string, reason: string) => void} fail колбек реєстрації порушення.
|
|
34
35
|
* @param {string} reason машиночитна причина порушення.
|
|
35
|
-
* @returns {boolean} true якщо OK, false якщо порушення
|
|
36
|
+
* @returns {Promise<boolean>} true якщо OK, false якщо порушення
|
|
36
37
|
*/
|
|
37
|
-
function runTool(label, abs, args, cwd, fail, reason) {
|
|
38
|
-
const r =
|
|
39
|
-
if (r.
|
|
40
|
-
const code = typeof r.
|
|
38
|
+
async function runTool(label, abs, args, cwd, fail, reason) {
|
|
39
|
+
const r = await spawnAsync(abs, args, { cwd })
|
|
40
|
+
if (r.exitCode === 0) return true
|
|
41
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
41
42
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
42
43
|
const outSuffix = out ? `\n${out}` : ''
|
|
43
44
|
fail(`lint-php: ${label} — помилка (код ${code}, php.mdc)${outSuffix}`, reason)
|
|
@@ -45,11 +46,11 @@ function runTool(label, abs, args, cwd, fail, reason) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
|
-
* Detector php/project (read-only).
|
|
49
|
+
* Detector php/project (read-only). Async — `runTool` викликає `spawnAsync` (ADR 260716-1354).
|
|
49
50
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
50
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
51
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
51
52
|
*/
|
|
52
|
-
export function lint(ctx) {
|
|
53
|
+
export async function lint(ctx) {
|
|
53
54
|
const reporter = createViolationReporter(ctx)
|
|
54
55
|
const { fail } = reporter
|
|
55
56
|
const root = ctx.cwd
|
|
@@ -62,7 +63,9 @@ export function lint(ctx) {
|
|
|
62
63
|
return reporter.result()
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
if (
|
|
66
|
+
if (
|
|
67
|
+
!(await runTool('composer audit', composer, ['audit', '--no-interaction'], root, fail, 'composer-audit-violation'))
|
|
68
|
+
) {
|
|
66
69
|
return reporter.result()
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -71,18 +74,18 @@ export function lint(ctx) {
|
|
|
71
74
|
* @param {string} label назва кроку.
|
|
72
75
|
* @param {string[]} args аргументи інструменту.
|
|
73
76
|
* @param {string} reason машиночитна причина порушення.
|
|
74
|
-
* @returns {boolean} true якщо OK / пропущено
|
|
77
|
+
* @returns {Promise<boolean>} true якщо OK / пропущено
|
|
75
78
|
*/
|
|
76
|
-
function runOptionalVendorTool(binName, label, args, reason) {
|
|
79
|
+
async function runOptionalVendorTool(binName, label, args, reason) {
|
|
77
80
|
const abs = vendorBin(root, binName)
|
|
78
81
|
if (!abs) return true // тул відсутній у vendor/bin → крок пропущено
|
|
79
|
-
return runTool(label, abs, args, root, fail, reason)
|
|
82
|
+
return await runTool(label, abs, args, root, fail, reason)
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
if (!runOptionalVendorTool('phpstan', 'PHPStan', ['analyse', '--no-progress'], 'phpstan-violation')) {
|
|
85
|
+
if (!(await runOptionalVendorTool('phpstan', 'PHPStan', ['analyse', '--no-progress'], 'phpstan-violation'))) {
|
|
83
86
|
return reporter.result()
|
|
84
87
|
}
|
|
85
|
-
runOptionalVendorTool('psalm', 'Psalm', ['--no-cache'], 'psalm-violation')
|
|
88
|
+
await runOptionalVendorTool('psalm', 'Psalm', ['--no-cache'], 'psalm-violation')
|
|
86
89
|
|
|
87
90
|
return reporter.result()
|
|
88
91
|
}
|
|
@@ -3,35 +3,39 @@
|
|
|
3
3
|
* `ctx.files`, інакше `.` (весь проєкт), обидва йдуть через `uv run --frozen`. Виділено зі
|
|
4
4
|
* спільного дубльованого коду (jscpd) обох `main.mjs`.
|
|
5
5
|
*/
|
|
6
|
-
import { spawnSync } from 'node:child_process'
|
|
7
6
|
import { existsSync } from 'node:fs'
|
|
8
7
|
import { join } from 'node:path'
|
|
9
8
|
|
|
10
9
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
10
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
11
11
|
|
|
12
12
|
/** Розширення `.py` — фільтр delta-списку файлів у `lint(ctx)`. */
|
|
13
13
|
export const PY_EXT_RE = /\.py$/u
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
17
|
+
* (ADR 260716-1354).
|
|
16
18
|
* @param {string} uv шлях до бінарника uv.
|
|
17
19
|
* @param {string} tool ім'я інструменту в uv-середовищі.
|
|
18
|
-
* @returns {boolean} true якщо інструмент доступний
|
|
20
|
+
* @returns {Promise<boolean>} true якщо інструмент доступний
|
|
19
21
|
*/
|
|
20
|
-
export function uvToolAvailable(uv, tool) {
|
|
21
|
-
const r =
|
|
22
|
-
return r.
|
|
22
|
+
export async function uvToolAvailable(uv, tool) {
|
|
23
|
+
const r = await spawnAsync(uv, ['run', '--frozen', tool, '--version'])
|
|
24
|
+
return r.exitCode === 0
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Спільний preflight: pyproject.toml, delta/full-цілі, резолв `uv`, доступність `tool` у
|
|
27
29
|
* uv-середовищі. Викликач рано виходить (`reporter.result()`), коли повертається `null` —
|
|
28
30
|
* `fail()` для випадку відсутнього `uv` уже викликаний усередині.
|
|
31
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
32
|
+
* (ADR 260716-1354).
|
|
29
33
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
30
34
|
* @param {(msg: string, opts: object) => void} fail колбек реєстрації порушення.
|
|
31
35
|
* @param {string} tool ім'я інструменту (`mypy`|`ruff`) для перевірки доступності.
|
|
32
|
-
* @returns {{uv: string, targets: string[]}|null} preflight-результат, або null (рано вийти).
|
|
36
|
+
* @returns {Promise<{uv: string, targets: string[]}|null>} preflight-результат, або null (рано вийти).
|
|
33
37
|
*/
|
|
34
|
-
export function preparePythonRun(ctx, fail, tool) {
|
|
38
|
+
export async function preparePythonRun(ctx, fail, tool) {
|
|
35
39
|
if (!existsSync(join(ctx.cwd, 'pyproject.toml'))) return null
|
|
36
40
|
|
|
37
41
|
const targets = ctx.files === undefined ? ['.'] : ctx.files.filter(f => PY_EXT_RE.test(f))
|
|
@@ -42,7 +46,7 @@ export function preparePythonRun(ctx, fail, tool) {
|
|
|
42
46
|
fail('lint-python: `uv` не знайдено в PATH (потрібен при наявному pyproject.toml, python.mdc)', 'uv-missing')
|
|
43
47
|
return null
|
|
44
48
|
}
|
|
45
|
-
if (!uvToolAvailable(uv, tool)) return null // tool недоступний у uv-середовищі → пропущено
|
|
49
|
+
if (!(await uvToolAvailable(uv, tool))) return null // tool недоступний у uv-середовищі → пропущено
|
|
46
50
|
|
|
47
51
|
return { uv, targets }
|
|
48
52
|
}
|
|
@@ -5,28 +5,29 @@
|
|
|
5
5
|
* mypy сам транзитивно підвантажує імпортовані модулі (follow-imports), тож дає коректні
|
|
6
6
|
* діагнози й на підмножині переданих файлів. Немає fix capability (mypy — detect-only).
|
|
7
7
|
*/
|
|
8
|
-
import { spawnSync } from 'node:child_process'
|
|
9
|
-
|
|
10
8
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
9
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
11
10
|
import { preparePythonRun } from '../lib/uv-run.mjs'
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Detector python/mypy (read-only).
|
|
14
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
15
|
+
* (ADR 260716-1354).
|
|
15
16
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
16
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
17
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
17
18
|
*/
|
|
18
|
-
export function lint(ctx) {
|
|
19
|
+
export async function lint(ctx) {
|
|
19
20
|
const reporter = createViolationReporter(ctx)
|
|
20
21
|
const { fail } = reporter
|
|
21
22
|
const cwd = ctx.cwd
|
|
22
23
|
|
|
23
|
-
const prepared = preparePythonRun(ctx, fail, 'mypy')
|
|
24
|
+
const prepared = await preparePythonRun(ctx, fail, 'mypy')
|
|
24
25
|
if (!prepared) return reporter.result()
|
|
25
26
|
const { uv, targets } = prepared
|
|
26
27
|
|
|
27
|
-
const r =
|
|
28
|
-
if (r.
|
|
29
|
-
const code = typeof r.
|
|
28
|
+
const r = await spawnAsync(uv, ['run', '--frozen', 'mypy', ...targets], { cwd })
|
|
29
|
+
if (r.exitCode !== 0) {
|
|
30
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
30
31
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
31
32
|
const outSuffix = out ? `\n${out}` : ''
|
|
32
33
|
fail(`lint-python: mypy — помилка (код ${code}, python.mdc)${outSuffix}`, 'mypy-violation')
|
|
@@ -5,27 +5,29 @@
|
|
|
5
5
|
* lockfile-аудит і ліцензійна перевірка project-wide за природою, не входять у delta-план
|
|
6
6
|
* (§5): спрацьовують лише через `n-rules lint --full` або scoped `n-rules lint python`.
|
|
7
7
|
*/
|
|
8
|
-
import { spawnSync } from 'node:child_process'
|
|
9
8
|
import { existsSync } from 'node:fs'
|
|
10
9
|
import { join } from 'node:path'
|
|
11
10
|
|
|
12
11
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
13
12
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
13
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
14
14
|
import { getBronzeAndAbove, isSpdxAllowed } from '../../../scripts/lib/blue-oak.mjs'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
18
|
+
* (ADR 260716-1354).
|
|
17
19
|
* @param {string} label назва кроку.
|
|
18
20
|
* @param {string} cmd команда для запуску.
|
|
19
21
|
* @param {string[]} args аргументи команди.
|
|
20
22
|
* @param {string} cwd робочий каталог.
|
|
21
23
|
* @param {(msg: string, reason: string) => void} fail колбек реєстрації порушення.
|
|
22
24
|
* @param {string} reason машиночитна причина порушення.
|
|
23
|
-
* @returns {boolean} true якщо OK
|
|
25
|
+
* @returns {Promise<boolean>} true якщо OK
|
|
24
26
|
*/
|
|
25
|
-
function runTool(label, cmd, args, cwd, fail, reason) {
|
|
26
|
-
const r =
|
|
27
|
-
if (r.
|
|
28
|
-
const code = typeof r.
|
|
27
|
+
async function runTool(label, cmd, args, cwd, fail, reason) {
|
|
28
|
+
const r = await spawnAsync(cmd, args, { cwd })
|
|
29
|
+
if (r.exitCode === 0) return true
|
|
30
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
29
31
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
30
32
|
const outSuffix = out ? `\n${out}` : ''
|
|
31
33
|
fail(`lint-python: ${label} — помилка (код ${code}, python.mdc)${outSuffix}`, reason)
|
|
@@ -33,30 +35,32 @@ function runTool(label, cmd, args, cwd, fail, reason) {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
|
38
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
39
|
+
* (ADR 260716-1354).
|
|
36
40
|
* @param {string} uv шлях до бінарника uv.
|
|
37
41
|
* @param {string} tool ім'я інструменту в uv-середовищі.
|
|
38
|
-
* @returns {boolean} true якщо інструмент доступний
|
|
42
|
+
* @returns {Promise<boolean>} true якщо інструмент доступний
|
|
39
43
|
*/
|
|
40
|
-
function uvToolAvailable(uv, tool) {
|
|
41
|
-
const r =
|
|
42
|
-
return r.
|
|
44
|
+
async function uvToolAvailable(uv, tool) {
|
|
45
|
+
const r = await spawnAsync(uv, ['run', '--frozen', tool, '--version'])
|
|
46
|
+
return r.exitCode === 0
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Перевірка ліцензій залежностей через pip-licenses (read-only).
|
|
51
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
52
|
+
* (ADR 260716-1354).
|
|
47
53
|
* @param {string} uv шлях до бінарника uv.
|
|
48
54
|
* @param {string} cwd робочий каталог.
|
|
49
55
|
* @param {(msg: string, reason: string) => void} fail колбек реєстрації порушення.
|
|
50
|
-
* @returns {boolean} true якщо OK / пропущено
|
|
56
|
+
* @returns {Promise<boolean>} true якщо OK / пропущено
|
|
51
57
|
*/
|
|
52
|
-
function checkPipLicenses(uv, cwd, fail) {
|
|
53
|
-
if (!uvToolAvailable(uv, 'pip-licenses')) return true // недоступний → пропущено
|
|
54
|
-
const r =
|
|
55
|
-
cwd
|
|
56
|
-
encoding: 'utf8',
|
|
57
|
-
shell: false
|
|
58
|
+
async function checkPipLicenses(uv, cwd, fail) {
|
|
59
|
+
if (!(await uvToolAvailable(uv, 'pip-licenses'))) return true // недоступний → пропущено
|
|
60
|
+
const r = await spawnAsync(uv, ['run', '--frozen', 'pip-licenses', '--from=mixed', '--format=spdx-json'], {
|
|
61
|
+
cwd
|
|
58
62
|
})
|
|
59
|
-
if (r.
|
|
63
|
+
if (r.exitCode !== 0) {
|
|
60
64
|
fail('lint-python: pip-licenses — помилка виконання', 'pip-licenses-error')
|
|
61
65
|
return false
|
|
62
66
|
}
|
|
@@ -90,10 +94,12 @@ function checkPipLicenses(uv, cwd, fail) {
|
|
|
90
94
|
|
|
91
95
|
/**
|
|
92
96
|
* Detector python/project (read-only).
|
|
97
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
98
|
+
* (ADR 260716-1354).
|
|
93
99
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
94
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
100
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
95
101
|
*/
|
|
96
|
-
export function lint(ctx) {
|
|
102
|
+
export async function lint(ctx) {
|
|
97
103
|
const reporter = createViolationReporter(ctx)
|
|
98
104
|
const { fail } = reporter
|
|
99
105
|
const cwd = ctx.cwd
|
|
@@ -106,10 +112,14 @@ export function lint(ctx) {
|
|
|
106
112
|
return reporter.result()
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
if (!runTool('uv lock --check', uv, ['lock', '--check'], cwd, fail, 'uv-lock-violation'))
|
|
110
|
-
|
|
115
|
+
if (!(await runTool('uv lock --check', uv, ['lock', '--check'], cwd, fail, 'uv-lock-violation'))) {
|
|
116
|
+
return reporter.result()
|
|
117
|
+
}
|
|
118
|
+
if (!(await runTool('uv sync --frozen', uv, ['sync', '--frozen'], cwd, fail, 'uv-sync-violation'))) {
|
|
119
|
+
return reporter.result()
|
|
120
|
+
}
|
|
111
121
|
|
|
112
|
-
checkPipLicenses(uv, cwd, fail)
|
|
122
|
+
await checkPipLicenses(uv, cwd, fail)
|
|
113
123
|
|
|
114
124
|
return reporter.result()
|
|
115
125
|
}
|
|
@@ -3,9 +3,8 @@ type: JS Module
|
|
|
3
3
|
title: fix-ruff.mjs
|
|
4
4
|
resource: npm/rules/python/ruff/fix-ruff.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: e0a79f8d
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
-
tier: local-min
|
|
9
8
|
score: 100
|
|
10
9
|
issues: judge:inaccurate:0.99
|
|
11
10
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* (spec docs/specs/2026-07-02-text-check-per-file-split-design.md "Рішення python/php/rego").
|
|
5
5
|
* Відсутній `uv` → no-op.
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from 'node:child_process'
|
|
8
7
|
import { readFileSync } from 'node:fs'
|
|
9
8
|
import { resolve } from 'node:path'
|
|
10
9
|
|
|
11
10
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
11
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Вміст файлу або null, якщо не читається.
|
|
@@ -25,12 +25,14 @@ function readOrNull(abs) {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Tracked *.py файли проєкту (через git).
|
|
28
|
+
* Async (не блокує event loop) — може виконуватись у parallel lane `detectAll()`
|
|
29
|
+
* (ADR 260716-1354).
|
|
28
30
|
* @param {string} cwd корінь
|
|
29
|
-
* @returns {string[]} posix-relative шляхи
|
|
31
|
+
* @returns {Promise<string[]>} posix-relative шляхи
|
|
30
32
|
*/
|
|
31
|
-
function listPyFiles(cwd) {
|
|
32
|
-
const r =
|
|
33
|
-
if (r.
|
|
33
|
+
async function listPyFiles(cwd) {
|
|
34
|
+
const r = await spawnAsync('git', ['ls-files', '-z', '--', '*.py'], { cwd })
|
|
35
|
+
if (r.exitCode !== 0) return []
|
|
34
36
|
return (r.stdout ?? '').split('\0').filter(Boolean)
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -41,17 +43,17 @@ export const patterns = [
|
|
|
41
43
|
standalone: true, // §8 Phase 2: ruff check --fix + format самі ре-аналізують, test() не потрібен
|
|
42
44
|
test: violations =>
|
|
43
45
|
violations.some(v => v.reason === 'ruff-check-violation' || v.reason === 'ruff-format-violation'),
|
|
44
|
-
apply: (violations, ctx) => {
|
|
46
|
+
apply: async (violations, ctx) => {
|
|
45
47
|
const uv = resolveCmd('uv')
|
|
46
48
|
if (!uv) return { touchedFiles: [] }
|
|
47
|
-
const files = listPyFiles(ctx.cwd)
|
|
49
|
+
const files = await listPyFiles(ctx.cwd)
|
|
48
50
|
if (files.length === 0) return { touchedFiles: [] }
|
|
49
51
|
|
|
50
52
|
const abs = files.map(f => resolve(ctx.cwd, f))
|
|
51
53
|
const before = new Map(abs.map(a => [a, readOrNull(a)]))
|
|
52
|
-
const opts = { cwd: ctx.cwd
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
const opts = { cwd: ctx.cwd }
|
|
55
|
+
await spawnAsync(uv, ['run', '--frozen', 'ruff', 'check', '--fix', '.'], opts)
|
|
56
|
+
await spawnAsync(uv, ['run', '--frozen', 'ruff', 'format', '.'], opts)
|
|
55
57
|
|
|
56
58
|
const touchedFiles = abs.filter(a => readOrNull(a) !== before.get(a))
|
|
57
59
|
for (const a of touchedFiles) ctx.recordWrite?.(a)
|
|
@@ -5,24 +5,25 @@
|
|
|
5
5
|
* "Рішення python/php/rego") — ruff сам транзитивно підвантажує імпортовані модулі
|
|
6
6
|
* (follow-imports), тож коректний і на підмножині переданих файлів.
|
|
7
7
|
*/
|
|
8
|
-
import { spawnSync } from 'node:child_process'
|
|
9
|
-
|
|
10
8
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
9
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
11
10
|
import { preparePythonRun } from '../lib/uv-run.mjs'
|
|
12
11
|
|
|
13
12
|
/**
|
|
13
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
14
|
+
* (ADR 260716-1354).
|
|
14
15
|
* @param {string} label назва кроку.
|
|
15
16
|
* @param {string} uv шлях до бінарника uv.
|
|
16
17
|
* @param {string[]} args аргументи ruff (без `uv run --frozen ruff`-префіксу).
|
|
17
18
|
* @param {string} cwd робочий каталог.
|
|
18
19
|
* @param {(msg: string, opts: object) => void} fail колбек реєстрації порушення.
|
|
19
20
|
* @param {string} reason машиночитна причина порушення.
|
|
20
|
-
* @returns {boolean} true якщо OK
|
|
21
|
+
* @returns {Promise<boolean>} true якщо OK
|
|
21
22
|
*/
|
|
22
|
-
function runRuffStep(label, uv, args, cwd, fail, reason) {
|
|
23
|
-
const r =
|
|
24
|
-
if (r.
|
|
25
|
-
const code = typeof r.
|
|
23
|
+
async function runRuffStep(label, uv, args, cwd, fail, reason) {
|
|
24
|
+
const r = await spawnAsync(uv, ['run', '--frozen', 'ruff', ...args], { cwd })
|
|
25
|
+
if (r.exitCode === 0) return true
|
|
26
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
26
27
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
27
28
|
const outSuffix = out ? `\n${out}` : ''
|
|
28
29
|
fail(`lint-python: ${label} — помилка (код ${code}, python.mdc)${outSuffix}`, reason)
|
|
@@ -32,21 +33,21 @@ function runRuffStep(label, uv, args, cwd, fail, reason) {
|
|
|
32
33
|
/**
|
|
33
34
|
* Detector python/ruff (read-only).
|
|
34
35
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
35
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
36
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
36
37
|
*/
|
|
37
|
-
export function lint(ctx) {
|
|
38
|
+
export async function lint(ctx) {
|
|
38
39
|
const reporter = createViolationReporter(ctx)
|
|
39
40
|
const { fail } = reporter
|
|
40
41
|
const cwd = ctx.cwd
|
|
41
42
|
|
|
42
|
-
const prepared = preparePythonRun(ctx, fail, 'ruff')
|
|
43
|
+
const prepared = await preparePythonRun(ctx, fail, 'ruff')
|
|
43
44
|
if (!prepared) return reporter.result()
|
|
44
45
|
const { uv, targets } = prepared
|
|
45
46
|
|
|
46
|
-
if (!runRuffStep('ruff check', uv, ['check', ...targets], cwd, fail, 'ruff-check-violation')) {
|
|
47
|
+
if (!(await runRuffStep('ruff check', uv, ['check', ...targets], cwd, fail, 'ruff-check-violation'))) {
|
|
47
48
|
return reporter.result()
|
|
48
49
|
}
|
|
49
|
-
runRuffStep('ruff format --check', uv, ['format', '--check', ...targets], cwd, fail, 'ruff-format-violation')
|
|
50
|
+
await runRuffStep('ruff format --check', uv, ['format', '--check', ...targets], cwd, fail, 'ruff-format-violation')
|
|
50
51
|
|
|
51
52
|
return reporter.result()
|
|
52
53
|
}
|