@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
@@ -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 = spawnSync(abs, ['fix', '--dry-run', '--diff', ...targets], { cwd: root, encoding: 'utf8', shell: false })
44
- if (r.status !== 0) {
45
- const code = typeof r.status === 'number' ? r.status : 1
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')
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/php/phpcs/main.mjs
5
5
  docgen:
6
- crc: 3da9ada2
6
+ crc: 389cb568
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
  * Виділено з колишнього 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 = spawnSync(abs, ['--standard=Security', '--ignore=*/vendor/*,*/node_modules/*,*/.git/*', ...targets], {
59
- cwd: root,
60
- encoding: 'utf8',
61
- shell: false
62
- })
63
- if (r.status !== 0) {
64
- const code = typeof r.status === 'number' ? r.status : 1
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: 0a3716eb
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 = spawnSync(abs, args, { cwd, encoding: 'utf8', shell: false })
39
- if (r.status === 0) return true
40
- const code = typeof r.status === 'number' ? r.status : 1
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 (!runTool('composer audit', composer, ['audit', '--no-interaction'], root, fail, 'composer-audit-violation')) {
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,9 +3,8 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/rego/conftest_verify/main.mjs
5
5
  docgen:
6
- crc: 2ef485ea
6
+ crc: e95d6632
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- tier: local-min
9
8
  score: 100
10
9
  judgeModel: openai-codex/gpt-5.4-mini
11
10
  ---
@@ -7,9 +7,9 @@
7
7
  */
8
8
  import { resolve } from 'node:path'
9
9
  import { existsSync } from 'node:fs'
10
- import { spawnSync } from 'node:child_process'
11
10
 
12
11
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
12
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
13
13
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
14
14
 
15
15
  const LINT_TARGETS = ['npm/rules']
@@ -19,23 +19,25 @@ const LINT_TARGETS = ['npm/rules']
19
19
  * @param {string} bin абсолютний шлях до бінарника
20
20
  * @param {string[]} args аргументи
21
21
  * @param {string} cwd робоча директорія
22
- * @returns {{ status: number, output: string }} код завершення й обрізаний stdout+stderr
22
+ * @returns {Promise<{ status: number, output: string }>} код завершення й обрізаний stdout+stderr
23
23
  */
24
- function runStep(bin, args, cwd) {
25
- const result = spawnSync(bin, args, { cwd, encoding: 'utf8', env: process.env, shell: false })
26
- if (result.error) {
27
- return { status: 1, output: `Не вдалося запустити ${bin}: ${result.error.message}` }
24
+ async function runStep(bin, args, cwd) {
25
+ let result
26
+ try {
27
+ result = await spawnAsync(bin, args, { cwd, env: process.env })
28
+ } catch (error) {
29
+ return { status: 1, output: `Не вдалося запустити ${bin}: ${error.message}` }
28
30
  }
29
31
  const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().slice(0, 2000)
30
- return { status: result.status ?? 1, output }
32
+ return { status: result.exitCode ?? 1, output }
31
33
  }
32
34
 
33
35
  /**
34
36
  * Detector rego/conftest_verify (read-only).
35
37
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
36
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
38
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
37
39
  */
38
- export function lint(ctx) {
40
+ export async function lint(ctx) {
39
41
  const reporter = createViolationReporter(ctx)
40
42
  const { fail } = reporter
41
43
  const root = resolve(ctx.cwd)
@@ -46,7 +48,7 @@ export function lint(ctx) {
46
48
  const conftest = resolveCmd('conftest')
47
49
  if (!conftest) return reporter.result() // conftest відсутній → пропускаємо verify (старий код повертав 0)
48
50
 
49
- const verifyRes = runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
51
+ const verifyRes = await runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
50
52
  if (verifyRes.status !== 0) {
51
53
  const verifySuffix = verifyRes.output ? `\n${verifyRes.output}` : ''
52
54
  fail(`lint-rego: conftest verify — помилка (код ${verifyRes.status})${verifySuffix}`, 'conftest-verify-violation')
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: run-external-tool.mjs
4
4
  resource: npm/rules/rego/lib/run-external-tool.mjs
5
5
  docgen:
6
- crc: 0f1da640
6
+ crc: ca820179
7
7
  model: openai-codex/gpt-5.4-mini
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.99
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import { existsSync } from 'node:fs'
7
7
  import { resolve } from 'node:path'
8
- import { spawnSync } from 'node:child_process'
8
+
9
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
9
10
 
10
11
  /** Full-режим (ctx.files undefined): корінь policy-дерева, якщо існує. */
11
12
  export const FULL_TARGET = 'npm/rules'
@@ -44,13 +45,15 @@ export function resolveTargets(files, root) {
44
45
  * @param {string} bin абсолютний шлях до бінарника
45
46
  * @param {string[]} args аргументи
46
47
  * @param {string} cwd робоча директорія
47
- * @returns {{ status: number, output: string }} код завершення й обрізаний stdout+stderr
48
+ * @returns {Promise<{ status: number, output: string }>} код завершення й обрізаний stdout+stderr
48
49
  */
49
- export function runStep(bin, args, cwd) {
50
- const result = spawnSync(bin, args, { cwd, encoding: 'utf8', env: process.env, shell: false })
51
- if (result.error) {
52
- return { status: 1, output: `Не вдалося запустити ${bin}: ${result.error.message}` }
50
+ export async function runStep(bin, args, cwd) {
51
+ let result
52
+ try {
53
+ result = await spawnAsync(bin, args, { cwd, env: process.env })
54
+ } catch (error) {
55
+ return { status: 1, output: `Не вдалося запустити ${bin}: ${error.message}` }
53
56
  }
54
57
  const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().slice(0, 2000)
55
- return { status: result.status ?? 1, output }
58
+ return { status: result.exitCode ?? 1, output }
56
59
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/rego/opa_check/main.mjs
5
5
  docgen:
6
- crc: e2f54bd8
6
+ crc: d5d66b52
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.96
@@ -14,9 +14,9 @@ import { resolveTargets, runStep } from '../lib/run-external-tool.mjs'
14
14
  /**
15
15
  * Detector rego/opa_check (read-only).
16
16
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
17
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
17
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
18
  */
19
- export function lint(ctx) {
19
+ export async function lint(ctx) {
20
20
  const reporter = createViolationReporter(ctx)
21
21
  const { fail } = reporter
22
22
  const root = resolve(ctx.cwd)
@@ -25,7 +25,7 @@ export function lint(ctx) {
25
25
  if (targets.length === 0) return reporter.result()
26
26
 
27
27
  const opa = ensureTool('opa')
28
- const opaRes = runStep(opa, ['check', '--strict', ...targets], root)
28
+ const opaRes = await runStep(opa, ['check', '--strict', ...targets], root)
29
29
  if (opaRes.status !== 0) {
30
30
  const opaSuffix = opaRes.output ? `\n${opaRes.output}` : ''
31
31
  fail(`lint-rego: opa check --strict — помилка (код ${opaRes.status})${opaSuffix}`, 'opa-check-violation')
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/rego/regal/main.mjs
5
5
  docgen:
6
- crc: c628819a
6
+ crc: a101cc38
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.96
@@ -14,9 +14,9 @@ import { resolveTargets, runStep } from '../lib/run-external-tool.mjs'
14
14
  /**
15
15
  * Detector rego/regal (read-only).
16
16
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
17
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
17
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
18
  */
19
- export function lint(ctx) {
19
+ export async function lint(ctx) {
20
20
  const reporter = createViolationReporter(ctx)
21
21
  const { fail } = reporter
22
22
  const root = resolve(ctx.cwd)
@@ -25,7 +25,7 @@ export function lint(ctx) {
25
25
  if (targets.length === 0) return reporter.result()
26
26
 
27
27
  const regal = ensureTool('regal')
28
- const regalRes = runStep(regal, ['lint', ...targets], root)
28
+ const regalRes = await runStep(regal, ['lint', ...targets], root)
29
29
  if (regalRes.status !== 0) {
30
30
  const regalSuffix = regalRes.output ? `\n${regalRes.output}` : ''
31
31
  fail(`lint-rego: regal lint — помилка (код ${regalRes.status})${regalSuffix}`, 'regal-lint-violation')
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/security/scan/main.mjs
5
5
  docgen:
6
- crc: 254fe129
6
+ crc: a57f9d26
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -2,18 +2,18 @@
2
2
  * lint-поверхня security/scan: read-only detector секретів (`trufflehog` filesystem скан
3
3
  * усього репо). Фіксу немає — детектор лише сигналить про знайдені секрети.
4
4
  */
5
- import { spawnSync } from 'node:child_process'
6
-
7
5
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
8
6
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
7
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
9
8
 
10
9
  /**
11
10
  * Detector security/scan: trufflehog filesystem скан (read-only). Якщо бінарника немає —
12
- * скан пропускається (як і раніше при відсутності інструмента).
11
+ * скан пропускається (як і раніше при відсутності інструмента). Async (не блокує event loop) —
12
+ * детектор може виконуватись у parallel lane `detectAll()` (ADR 260716-1354).
13
13
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
14
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат detector-а
14
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат detector-а
15
15
  */
16
- export function lint(ctx) {
16
+ export async function lint(ctx) {
17
17
  const reporter = createViolationReporter(ctx)
18
18
  const { fail } = reporter
19
19
  const cwd = ctx.cwd
@@ -26,7 +26,7 @@ export function lint(ctx) {
26
26
  }
27
27
  }
28
28
 
29
- const r = spawnSync(
29
+ const r = await spawnAsync(
30
30
  trufflehog,
31
31
  [
32
32
  'filesystem',
@@ -37,12 +37,12 @@ export function lint(ctx) {
37
37
  '--results=verified,unknown',
38
38
  '--fail'
39
39
  ],
40
- { cwd, encoding: 'utf8', shell: false }
40
+ { cwd }
41
41
  )
42
- if (r.status !== 0) {
42
+ if (r.exitCode !== 0) {
43
43
  const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 4000)
44
44
  const outSuffix = out ? `\n${out}` : ''
45
- fail(`security/scan: trufflehog знайшов секрети (код ${r.status})${outSuffix}`, 'secret-found')
45
+ fail(`security/scan: trufflehog знайшов секрети (код ${r.exitCode})${outSuffix}`, 'secret-found')
46
46
  }
47
47
  return reporter.result()
48
48
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: fix-lint.mjs
4
4
  resource: npm/rules/style/lint/fix-lint.mjs
5
5
  docgen:
6
- crc: ef3a0112
6
+ crc: 140458e8
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/style/lint/main.mjs
5
5
  docgen:
6
- crc: fe57567b
6
+ crc: cd4833fb
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.99
@@ -7,11 +7,11 @@
7
7
  * режимі), форматує їх і повертає лише фактично змінені. Запис permanent. Відсутній
8
8
  * stylelint → no-op.
9
9
  */
10
- import { spawnSync } from 'node:child_process'
11
10
  import { readFileSync } from 'node:fs'
12
11
  import { resolve } from 'node:path'
13
12
 
14
13
  import { filterStyleFiles, resolveStylelint } from './main.mjs'
14
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
15
15
 
16
16
  /**
17
17
  * Вміст файлу або null, якщо не читається.
@@ -31,12 +31,12 @@ function readOrNull(abs) {
31
31
  * css/scss/vue через `git ls-files`.
32
32
  * @param {string} cwd корінь
33
33
  * @param {string[]|undefined} ctxFiles файли з контексту (дельта) або undefined (повний режим)
34
- * @returns {string[]} posix-relative шляхи
34
+ * @returns {Promise<string[]>} posix-relative шляхи
35
35
  */
36
- function listStyleFiles(cwd, ctxFiles) {
36
+ async function listStyleFiles(cwd, ctxFiles) {
37
37
  if (ctxFiles !== undefined) return filterStyleFiles(ctxFiles)
38
- const r = spawnSync('git', ['ls-files', '-z', '--', '*.css', '*.scss', '*.vue'], { cwd, encoding: 'utf8' })
39
- if (r.status !== 0) return []
38
+ const r = await spawnAsync('git', ['ls-files', '-z', '--', '*.css', '*.scss', '*.vue'], { cwd })
39
+ if (r.exitCode !== 0) return []
40
40
  return (r.stdout ?? '').split('\0').filter(Boolean)
41
41
  }
42
42
 
@@ -46,15 +46,15 @@ export const patterns = [
46
46
  id: 'style-stylelint-fix',
47
47
  standalone: true, // §8 Phase 2: apply бере ctx.files (не violations), stylelint --fix сам ре-аналізує
48
48
  test: violations => violations.some(v => v.reason === 'stylelint-violation'),
49
- apply: (violations, ctx) => {
49
+ apply: async (violations, ctx) => {
50
50
  const stylelint = resolveStylelint(ctx.cwd)
51
51
  if (!stylelint) return { touchedFiles: [] }
52
- const files = listStyleFiles(ctx.cwd, ctx.files)
52
+ const files = await listStyleFiles(ctx.cwd, ctx.files)
53
53
  if (files.length === 0) return { touchedFiles: [] }
54
54
 
55
55
  const abs = files.map(f => resolve(ctx.cwd, f))
56
56
  const before = new Map(abs.map(a => [a, readOrNull(a)]))
57
- spawnSync(stylelint, ['--fix', ...files], { cwd: ctx.cwd, encoding: 'utf8' })
57
+ await spawnAsync(stylelint, ['--fix', ...files], { cwd: ctx.cwd })
58
58
 
59
59
  const touchedFiles = abs.filter(a => readOrNull(a) !== before.get(a))
60
60
  for (const a of touchedFiles) ctx.recordWrite?.(a)
@@ -3,12 +3,12 @@
3
3
  * Per-file (ctx.files) або весь проєкт (ctx.files === undefined). Якщо stylelint
4
4
  * недоступний — крок пропускається (без npx-автовстановлення).
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 { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
11
10
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
11
+ import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
12
12
 
13
13
  const STYLE_EXT_RE = /\.(?:css|scss|vue)$/u
14
14
 
@@ -34,9 +34,9 @@ export function resolveStylelint(cwd) {
34
34
  /**
35
35
  * Detector style/lint (read-only).
36
36
  * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd, files)
37
- * @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
37
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
38
38
  */
39
- export function lint(ctx) {
39
+ export async function lint(ctx) {
40
40
  const reporter = createViolationReporter(ctx)
41
41
  const { fail } = reporter
42
42
  const cwd = ctx.cwd
@@ -58,11 +58,11 @@ export function lint(ctx) {
58
58
  return reporter.result()
59
59
  }
60
60
 
61
- const r = spawnSync(stylelint, targets, { cwd, encoding: 'utf8', shell: false })
62
- if (r.status !== 0) {
61
+ const r = await spawnAsync(stylelint, targets, { cwd, shell: false })
62
+ if (r.exitCode !== 0) {
63
63
  const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
64
64
  const outSuffix = out ? `\n${out}` : ''
65
- fail(`lint-style: stylelint — порушення (код ${r.status ?? 1}, style.mdc)${outSuffix}`, 'stylelint-violation')
65
+ fail(`lint-style: stylelint — порушення (код ${r.exitCode ?? 1}, style.mdc)${outSuffix}`, 'stylelint-violation')
66
66
  }
67
67
  return reporter.result()
68
68
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: fix-worker.mjs
4
4
  resource: npm/rules/text/cspell-fix/fix-worker.mjs
5
5
  docgen:
6
- crc: 59f849d4
6
+ crc: f7dc29a5
7
7
  model: openai-codex/gpt-5.5
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/text/cspell-fix/main.mjs
5
5
  docgen:
6
- crc: 22853b73
6
+ crc: 8a2b75ff
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -40,7 +40,7 @@ export async function fixWorker(violations, ctx) {
40
40
  const bin = resolveCmd('npx')
41
41
  if (!bin) return { touchedFiles: [] }
42
42
 
43
- const first = detectCspell(ctx.cwd, bin, ctx.files)
43
+ const first = await detectCspell(ctx.cwd, bin, ctx.files)
44
44
  if (first.code === 0) return { touchedFiles: [] }
45
45
 
46
46
  const words = unknownWords(first.out)