@7n/rules 1.45.0 → 1.47.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.47.0] - 2026-07-23
4
+
5
+ ### Added
6
+
7
+ - концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)
8
+ - coverage: спільні lib manifest-roots/lcov, подовжений full-таймаут для мутаційного тестування (×4), пілот classify (0.7) у цьому репо
9
+
10
+ ### Changed
11
+
12
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
13
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
14
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
15
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
16
+ - doc_comments rollout: header/export JSDoc у конфігах demo
17
+ - doc_comments rollout: header-JSDoc у vitest.config
18
+
19
+ ## [1.46.0] - 2026-07-23
20
+
21
+ ### Added
22
+
23
+ - концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)
24
+ - coverage: спільні lib manifest-roots/lcov, подовжений full-таймаут для мутаційного тестування (×4), пілот classify (0.7) у цьому репо
25
+
26
+ ### Changed
27
+
28
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
29
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
30
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
31
+ - doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
32
+ - doc_comments rollout: header/export JSDoc у конфігах demo
33
+ - doc_comments rollout: header-JSDoc у vitest.config
34
+
3
35
  ## [1.45.0] - 2026-07-23
4
36
 
5
37
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.45.0",
3
+ "version": "1.47.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Мовно-агностичний пошук коренів екосистеми за маніфестом (спільна lib
3
+ * концерну coverage): каталоги з одним із маніфест-файлів у корені проєкту та
4
+ * на першому рівні вкладеності (типові розкладки: крейт/пакет у корені,
5
+ * `src-tauri/`, side-пакет у монорепо). Глибші члени workspace не повертаються
6
+ * — тулзи (`cargo llvm-cov`, `pytest`) покривають їх із кореня самі.
7
+ */
8
+ import { existsSync } from 'node:fs'
9
+ import { readdir } from 'node:fs/promises'
10
+ import { join } from 'node:path'
11
+
12
+ /** Службові теки, де маніфести першого рівня не шукаються. */
13
+ const IGNORE_DIRS = new Set([
14
+ 'node_modules',
15
+ 'dist',
16
+ 'build',
17
+ 'target',
18
+ 'coverage',
19
+ 'docs',
20
+ '.git',
21
+ '.claude',
22
+ '.worktrees',
23
+ '.cursor',
24
+ '.github'
25
+ ])
26
+
27
+ /**
28
+ * Корені під `cwd`, що мають хоча б один із `manifestNames`.
29
+ * @param {string} cwd корінь проєкту
30
+ * @param {string[]} manifestNames імена маніфестів (напр. `['Cargo.toml']`, `['pyproject.toml', 'setup.py']`)
31
+ * @returns {Promise<string[]>} абсолютні шляхи каталогів-коренів
32
+ */
33
+ export async function findManifestRoots(cwd, manifestNames) {
34
+ const hasManifest = dir => manifestNames.some(name => existsSync(join(dir, name)))
35
+ const roots = []
36
+ if (hasManifest(cwd)) roots.push(cwd)
37
+ let entries
38
+ try {
39
+ entries = await readdir(cwd, { withFileTypes: true })
40
+ } catch {
41
+ return roots
42
+ }
43
+ for (const entry of entries) {
44
+ if (!entry.isDirectory() || entry.name.startsWith('.') || IGNORE_DIRS.has(entry.name)) continue
45
+ const dir = join(cwd, entry.name)
46
+ if (hasManifest(dir)) roots.push(dir)
47
+ }
48
+ return roots
49
+ }
@@ -82,6 +82,11 @@
82
82
  "type": "number",
83
83
  "description": "Мінімальний mutation score у відсотках (лише повний вимір: lint --full / lint test).",
84
84
  "default": 80
85
+ },
86
+ "classifyConfidenceThreshold": {
87
+ "type": "number",
88
+ "description": "Confidence-поріг LLM-класифікації survived-мутантів (allowed gaps). ≤1 — увімкнено; дефолт 1.1 = вимкнено (rollout-mode).",
89
+ "default": 1.1
85
90
  }
86
91
  }
87
92
  },
@@ -3,45 +3,54 @@ type: JS Module
3
3
  title: lint-lock.mjs
4
4
  resource: npm/scripts/lib/lint-surface/lint-lock.mjs
5
5
  docgen:
6
- crc: 23bf1b2c
7
- model: openai-codex/gpt-5.5
8
- tier: cloud-avg
9
- score: 100
10
- issues: judge:inaccurate:0.98
6
+ crc: c1100ba8
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 90
10
+ issues: internal-name:withLock,judge-refine:kept-original,judge:inaccurate:0.99
11
11
  judgeModel: openai-codex/gpt-5.4-mini
12
12
  ---
13
13
 
14
14
  ## Огляд
15
15
 
16
- Файл серіалізує лише довгі machine-wide запуски `n-rules lint --full`, щоб на машині одночасно виконувався щонайбільше один full-прогін. Delta, scoped і `--no-fix` запуски не беруть глобальний лок і не стають у цю чергу.
16
+ Спільний машинний стан для `n-rules lint --full`: через `withLock` блокується лише `--full`, а scoped, delta та `--no-fix` запуски йдуть без черги. Це серіалізує довгі whole-tree прогони на одній машині, дає видимість позиції в черзі й показує живий прогрес активного запуску.
17
17
 
18
- Активний full-прогін записує власника лока в `owner.json` і публікує живий стан у `progress.json`, щоб процеси в очікуванні бачили поточного виконавця та прогрес. Процеси в черзі реєструються у `queue/<enqueuedAt>-<pid>.json`, що дає видимий список очікування й позицію кожного запуску.
18
+ `GLOBAL_CACHE_DIR` лежить в `os.tmpdir` і працює machine-wide. У ньому зберігаються `lock/owner.json` для власника лока, `queue/<enqueuedAt>-<pid>.json` для черги процесів і `progress.json` для знімка активного прогресу.
19
19
 
20
- Дедуплікація спирається на fingerprint і TTL у механіці лока, а не на файли стану. За неможливості безпечно дочекатися черги запуск завершується fail-closed, щоб не допустити мовчазного паралельного full-прогону.
20
+ `lintLockFingerprint` домішує варіант виклику до знімка дерева, щоб scoped-успіх не хибно перекривав ширший прогін. `staleThreshold` піднято до 6 год, `waitTimeout` — 45 хв; після цього очікування завершується fail-closed, а не мовчазним паралельним запуском.
21
+
22
+ `createProgressPublisher` оновлює `progress.json`, `renderWaitLine` відображає чергу й прогрес, а `withGlobalLintLock` є спільною точкою доступу до цього режиму.
21
23
 
22
24
  ## Поведінка
23
25
 
24
- - `GLOBAL_CACHE_DIR` задає спільне machine-wide місце стану для черги full-лінту, включно з `owner.json` і `progress.json`.
26
+ `GLOBAL_CACHE_DIR` спільний машинний кеш для всіх full-запусків lint: тут зберігаються власник лока, черга очікування і живий прогрес активного прогону. Саме цей стан робить чергу видимою між процесами та дозволяє не запускати кілька повних прогонів паралельно.
25
27
 
26
- - `lintLockFingerprint` формує ключ дедуплікації для full-лінту з урахуванням стану дерева й варіанта запуску; повертає порожнє значення, коли безпечно дедуплікувати неможливо.
28
+ `lintLockFingerprint` визначає, чи можна безпечно застосувати TTL-дедуплікацію для конкретного варіанта lint. Якщо знімок дерева недоступний або виклик не відповідає поточному робочому дереву, дедуплікація вимикається і запуск іде далі по черзі. Інакше fingerprint враховує і стан дерева, і варіант виклику, щоб короткий scoped-успіх не підміняв ширший full-прогін.
27
29
 
28
- - `createProgressPublisher` публікує throttled-знімки прогресу активного full-прогону в `progress.json`, щоб процеси в черзі бачили живий стан без зайвого навантаження на диск.
30
+ `withGlobalLintLock` точка входу для повного запуску: non-full варіанти проходять одразу, а full-прогони стають у глобальну чергу, чекають звільнення лока й виконуються по одному. Після входу в лок активний запуск публікує свій прогрес у `progress.json`, а процеси в черзі читають його та показують актуальний стан очікування. Якщо запуск уже був успішно дедуплікований, результат повертається без повторного виконання.
29
31
 
30
- - `renderWaitLine` створює однорядковий статус очікування з позицією в черзі, поточним власником лока з `owner.json`, прогресом із `progress.json` і рештою очікувачів.
32
+ `createProgressPublisher` пов’язує репортер прогресу активного прогону зі спільним станом: нові знімки потрапляють у файл прогресу з обмеженням частоти оновлення, щоб черга бачила живий, але стабільний стан. Це джерело правди для всіх очікувальних процесів.
31
33
 
32
- - `withGlobalLintLock` серіалізує лише `--full` запуски лінту через глобальну чергу, а короткі дельта/scoped/`--no-fix` запуски пропускає одразу; при timeout завершується fail-closed, щоб не допустити паралельний full-прогін.
34
+ `renderWaitLine` збирає однорядковий статус очікування з трьох джерел: поточного власника лока, живої черги і знімка прогресу активного прогону. У результаті процес у черзі бачить, хто зараз працює, яка в нього стадія, і яке місце займає сам у загальному порядку.
33
35
 
34
36
  ## Публічний API
35
37
 
36
- - GLOBAL_CACHE_DIR — зберігає спільний machine-wide стан лока й черги для всіх repo та worktree; спирається на owner.json і progress.json.
37
- - lintLockFingerprint — формує ключ для TTL-дедуплікації за станом робочого дерева та режимом запуску lint; повертає null поза git-repo або коли --cwd відрізняється від process cwd, щоб не прив’язати прогін до чужого дерева.
38
- - createProgressPublisher публікує прогрес активного lint-прогону в progress.json, щоб процеси в черзі бачили актуальний progress bar власника лока.
39
- - renderWaitLine — показує стан очікування в черзі: позицію процесу, поточного власника з pid і текою, його прогрес та інші queued процеси.
40
- - withGlobalLintLock запускає full lint під глобальним локом і чергою; delta, scoped та --no-fix прогони стартують одразу без очікування.
38
+ - GLOBAL_CACHE_DIR — Machine-wide директорія стану лока/черги спільна для всіх репо й worktree.
39
+ - lintLockFingerprint — Fingerprint для TTL-дедуплікації: стан робочого дерева + варіант виклику lint.
40
+ null (→ дедуплікація вимкнена, черга працює) коли:
41
+ - не в git-репо (worktreeFingerprint дасть null);
42
+ - `--cwd` вказує не на процесний cwd git-команди fingerprint-а виконуються
43
+ у `process.cwd()`, тож знімок відповідав би не тому дереву, що лінтиться.
44
+ - createProgressPublisher — Publisher прогресу активного прогону: приймає знімки від
45
+ `createProgressReporter({ onUpdate })` і (throttled) пише їх у стан-файл,
46
+ звідки процеси в черзі читають прогрес-бар активного прогону.
47
+ - renderWaitLine — Рядок стану черги для процесу, що стоїть у черзі: позиція, хто працює (pid + тека),
48
+ прогрес-бар власника і перелік решти черги.
49
+ - withGlobalLintLock — Виконує `runFn` під глобальним локом full-прогонів. Не-full варіанти
50
+ (дельта/scoped/`--no-fix`) виконуються одразу, без лока й черги.
41
51
 
42
52
  ## Гарантії поведінки
43
53
 
44
- - У кожен момент на машині виконується щонайбільше один `lint --full`; запуски в черзі стартують у порядку постановки.
45
- - Файлові операції стану (реєстрація в черзі, публікація прогресу, читання чужих записів) best-effort: їх збій не валить прогін, лише зменшує видимість; записи мертвих PID прибираються при читанні списку.
46
- - Таймаут очікування черги виняток (fail-closed), а не мовчазний паралельний запуск.
47
- - Ідентичний повторний `--full` на незміненому дереві дедуплікується за fingerprint у межах TTL (exit 0 без повторної роботи); поза git-репо чи при `--cwd` не на процесний cwd дедуплікація вимикається, черга лишається.
54
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
55
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
56
+ - Кешує результати в межах одного прогону.
@@ -46,6 +46,14 @@ const PROGRESS_FILE = join(GLOBAL_CACHE_DIR, 'progress.json')
46
46
  /** Дедлайн очікування в черзі: full-прогони довгі, 20 хв дефолту withLock замало. */
47
47
  const WAIT_TIMEOUT_MS = 45 * 60_000
48
48
 
49
+ /**
50
+ * Дедлайн, коли активний/очікуваний прогін включає концерн coverage правила
51
+ * `test` (spec absorb-7n-test, ризик 3.4): повне мутаційне тестування
52
+ * (Stryker/cargo-mutants) на великому проєкті не вкладається у 45 хв —
53
+ * інженерний запас ×4 замість вгадування точної тривалості.
54
+ */
55
+ const WAIT_TIMEOUT_WITH_COVERAGE_MS = 4 * WAIT_TIMEOUT_MS
56
+
49
57
  /** Поріг time-based staleness (див. модульний коментар). */
50
58
  const STALE_THRESHOLD_MS = 6 * 3_600_000
51
59
 
@@ -235,10 +243,13 @@ export function withGlobalLintLock(variant, runFn, opts = {}) {
235
243
  if (!variant.full) return Promise.resolve(runFn())
236
244
  const { isTTY, log, queueDir, progressFile, ...lockOpts } = opts
237
245
  const ui = createWaitUi({ isTTY, log, queueDir, progressFile })
246
+ // Full-прогін завжди включає концерн coverage (мутаційне тестування) —
247
+ // черга чекає з покриттєвим запасом; scoped `lint test` під лок не йде
248
+ // (variant.full=false), тож окремої осі для нього не треба.
238
249
  return withLock('lint-full', runFn, {
239
250
  cacheDir: GLOBAL_CACHE_DIR,
240
251
  staleThreshold: STALE_THRESHOLD_MS,
241
- waitTimeout: WAIT_TIMEOUT_MS,
252
+ waitTimeout: WAIT_TIMEOUT_WITH_COVERAGE_MS,
242
253
  onWaitTimeout: 'fail',
243
254
  getFingerprint: () => lintLockFingerprint(variant),
244
255
  ...ui,