@7n/rules 1.45.0 → 1.46.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,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.46.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
|
+
|
|
3
19
|
## [1.45.0] - 2026-07-23
|
|
4
20
|
|
|
5
21
|
### Added
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/schemas/n-rules.json
CHANGED
|
@@ -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:
|
|
7
|
-
model: openai-codex/gpt-5.
|
|
8
|
-
tier: cloud-
|
|
9
|
-
score:
|
|
10
|
-
issues: judge:inaccurate:0.
|
|
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
|
-
|
|
16
|
+
Спільний машинний стан для `n-rules lint --full`: через `withLock` блокується лише `--full`, а scoped, delta та `--no-fix` запуски йдуть без черги. Це серіалізує довгі whole-tree прогони на одній машині, дає видимість позиції в черзі й показує живий прогрес активного запуску.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
`GLOBAL_CACHE_DIR` лежить в `os.tmpdir` і працює machine-wide. У ньому зберігаються `lock/owner.json` для власника лока, `queue/<enqueuedAt>-<pid>.json` для черги процесів і `progress.json` для знімка активного прогресу.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
`lintLockFingerprint` домішує варіант виклику до знімка дерева, щоб scoped-успіх не хибно перекривав ширший прогін. `staleThreshold` піднято до 6 год, `waitTimeout` — 45 хв; після цього очікування завершується fail-closed, а не мовчазним паралельним запуском.
|
|
21
|
+
|
|
22
|
+
`createProgressPublisher` оновлює `progress.json`, `renderWaitLine` відображає чергу й прогрес, а `withGlobalLintLock` є спільною точкою доступу до цього режиму.
|
|
21
23
|
|
|
22
24
|
## Поведінка
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
`GLOBAL_CACHE_DIR` — спільний машинний кеш для всіх full-запусків lint: тут зберігаються власник лока, черга очікування і живий прогрес активного прогону. Саме цей стан робить чергу видимою між процесами та дозволяє не запускати кілька повних прогонів паралельно.
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
`lintLockFingerprint` визначає, чи можна безпечно застосувати TTL-дедуплікацію для конкретного варіанта lint. Якщо знімок дерева недоступний або виклик не відповідає поточному робочому дереву, дедуплікація вимикається і запуск іде далі по черзі. Інакше fingerprint враховує і стан дерева, і варіант виклику, щоб короткий scoped-успіх не підміняв ширший full-прогін.
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
`withGlobalLintLock` — точка входу для повного запуску: non-full варіанти проходять одразу, а full-прогони стають у глобальну чергу, чекають звільнення лока й виконуються по одному. Після входу в лок активний запуск публікує свій прогрес у `progress.json`, а процеси в черзі читають його та показують актуальний стан очікування. Якщо запуск уже був успішно дедуплікований, результат повертається без повторного виконання.
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
`createProgressPublisher` пов’язує репортер прогресу активного прогону зі спільним станом: нові знімки потрапляють у файл прогресу з обмеженням частоти оновлення, щоб черга бачила живий, але стабільний стан. Це джерело правди для всіх очікувальних процесів.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
`renderWaitLine` збирає однорядковий статус очікування з трьох джерел: поточного власника лока, живої черги і знімка прогресу активного прогону. У результаті процес у черзі бачить, хто зараз працює, яка в нього стадія, і яке місце займає сам у загальному порядку.
|
|
33
35
|
|
|
34
36
|
## Публічний API
|
|
35
37
|
|
|
36
|
-
- GLOBAL_CACHE_DIR —
|
|
37
|
-
- lintLockFingerprint —
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
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
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
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:
|
|
252
|
+
waitTimeout: WAIT_TIMEOUT_WITH_COVERAGE_MS,
|
|
242
253
|
onWaitTimeout: 'fail',
|
|
243
254
|
getFingerprint: () => lintLockFingerprint(variant),
|
|
244
255
|
...ui,
|