@7n/rules 1.90.1 → 1.91.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 +30 -0
- package/package.json +5 -5
- package/rules/doc-files/check/concern.json +2 -1
- package/schemas/concern.json +5 -0
- package/scripts/lib/concern-meta.mjs +5 -1
- package/scripts/lib/docs/concern-meta.md +11 -4
- package/scripts/lib/lint-surface/docs/run-detectors.md +5 -12
- package/scripts/lib/lint-surface/run-detectors.mjs +69 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.91.0] - 2026-08-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Native-контур фіксу доведений на багатофайловому порушенні живою локальною
|
|
8
|
+
моделлю. Зміни, кожна з яких — наслідок конкретного прогону, а не здогаду:
|
|
9
|
+
|
|
10
|
+
- **Схема інструмента `edit` сплощена** до одного фрагмента за виклик
|
|
11
|
+
(`path`/`old_text`/`new_text`) замість масиву обʼєктів. Діагностика сирого
|
|
12
|
+
tool-call-у показала, що 26B-модель плутає вкладеність і закриває `]`
|
|
13
|
+
замість `}` — виклик виходив структурно невалідний. Після сплощення той
|
|
14
|
+
самий прогін закрився з першого рунга й без жодного провалу парсера.
|
|
15
|
+
- **`verify_max` за паритетом із `run-fix.mjs`**: локальній моделі одна
|
|
16
|
+
спроба виправитись, хмарній дві (було 2 для всіх).
|
|
17
|
+
- **`max_tokens` винесений у запит** (`N_LLM_FIX_MAX_TOKENS`) замість
|
|
18
|
+
константи: значення, що рятує слабку модель від розгону, обрізає
|
|
19
|
+
багатофайловий фікс на моделі з великим контекстом.
|
|
20
|
+
- **Стеля ходів керується `N_LLM_FIX_TURN_CEILING`**.
|
|
21
|
+
- **Звіт показує вартість кожної спроби** — скільки ходів і викликів
|
|
22
|
+
інструментів. Без цього тюнінг стелі був би ворожінням: саме ці числа
|
|
23
|
+
показали, що падає не бюджет, а генерація.
|
|
24
|
+
- Схема `self_check` більше не порожній обʼєкт — одне необовʼязкове поле
|
|
25
|
+
замість `{"properties": {}}` як захист від strict-провайдерів.
|
|
26
|
+
|
|
27
|
+
## [1.90.2] - 2026-08-11
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- fix(lint): per-file glob doc-files виводиться з doc-files.extensions contributions — зміна .rs запускає концерн у delta-режимі й PostToolUse hook (новий опційний lint.extensionsSlot у concern.json; статичний glob — fallback, доповнений rs)
|
|
32
|
+
|
|
3
33
|
## [1.90.1] - 2026-08-10
|
|
4
34
|
|
|
5
35
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/rules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.91.0",
|
|
4
4
|
"description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"rename-yaml-extensions": "bun ./bin/n-rules.js rename-yaml-extensions"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@7n/llm-lib": "3.
|
|
69
|
+
"@7n/llm-lib": "3.3.0",
|
|
70
70
|
"ajv": "^8.20.0",
|
|
71
71
|
"cli-progress": "^3.12.0",
|
|
72
72
|
"github-actionlint": "^1.7.12",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"zod": "^4.4.3"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@7n/rules-darwin-arm64": "1.
|
|
87
|
-
"@7n/rules-linux-x64": "1.
|
|
88
|
-
"@7n/rules-win32-x64": "1.
|
|
86
|
+
"@7n/rules-darwin-arm64": "1.91.0",
|
|
87
|
+
"@7n/rules-linux-x64": "1.91.0",
|
|
88
|
+
"@7n/rules-win32-x64": "1.91.0",
|
|
89
89
|
"@earendil-works/pi-ai": "0.80.10",
|
|
90
90
|
"@earendil-works/pi-coding-agent": "0.80.10"
|
|
91
91
|
},
|
package/schemas/concern.json
CHANGED
|
@@ -165,6 +165,11 @@
|
|
|
165
165
|
}
|
|
166
166
|
],
|
|
167
167
|
"description": "per-file: фільтр delta-файлів; full: delta-тригер."
|
|
168
|
+
},
|
|
169
|
+
"extensionsSlot": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"minLength": 1,
|
|
172
|
+
"description": "Ім'я slot-а з extension-map contributions (value: мапа '.ext' → мітка, напр. doc-files.extensions). Ефективний glob концерну виводиться з розширень усіх активних contributions цього slot-а (`**/*.{…}`), тож lang-плагіни розширюють delta-тригер без правки concern.json. Статичний `glob` лишається fallback-ом, коли жодний активний плагін не декларує розширень (плагіни не встановлені)."
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
},
|
|
@@ -10,6 +10,9 @@ import { join } from 'node:path'
|
|
|
10
10
|
* @typedef {object} LintSurface
|
|
11
11
|
* @property {'per-file'|'full'} scope область лінту: per-file чи повний прогін.
|
|
12
12
|
* @property {string[]} glob масив glob-ів (нормалізований з string|string[]); порожній якщо не задано
|
|
13
|
+
* @property {string|undefined} extensionsSlot slot із extension-map contributions (напр.
|
|
14
|
+
* `doc-files.extensions`) — ефективний glob виводиться з розширень активних плагінів
|
|
15
|
+
* (резолвиться у run-detectors, де відомий cwd); статичний `glob` — fallback без contributions
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -68,7 +71,8 @@ function parseLintSurface(rawLint) {
|
|
|
68
71
|
} else if (typeof rawGlob === 'string') {
|
|
69
72
|
glob = [rawGlob]
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
const extensionsSlot = typeof rawLint.extensionsSlot === 'string' ? rawLint.extensionsSlot : undefined
|
|
75
|
+
return { scope, glob, extensionsSlot }
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
/**
|
|
@@ -3,16 +3,23 @@ type: JS Module
|
|
|
3
3
|
title: concern-meta.mjs
|
|
4
4
|
resource: npm/scripts/lib/concern-meta.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model: omlx/gemma-4-
|
|
6
|
+
crc: 877433c9
|
|
7
|
+
model: omlx/gemma-4-26b-a4b-it
|
|
8
8
|
tier: local-min
|
|
9
|
-
score:
|
|
9
|
+
score: 100
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
## Огляд
|
|
13
14
|
|
|
14
15
|
Парсер і нормалізатор `concern.json`. Єдине місце де читається і валідується схема concern-а.
|
|
15
16
|
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
`readConcernMeta` повертає нормалізовані дані з `concern.json` або `null`, якщо файл відсутній, не є об'єктом або не відповідає мінімальним вимогам до структури (наприклад, через невалідний scope лінту). Якщо конфіг не містить жодної активної поверхні (lint, policy або check), він вважається невалідним.
|
|
20
|
+
|
|
21
|
+
`listConcerns` повертає відсортований за алфавітом масив об'єктів, що відповідають вимогам `concern.json`. Підкаталоги, які не містять валідного конфігу, ігноруються та не потрапляють у результат.
|
|
22
|
+
|
|
16
23
|
## Публічний API
|
|
17
24
|
|
|
18
25
|
- readConcernMeta — Читає і нормалізує `concern.json` з каталогу.
|
|
@@ -22,7 +29,7 @@ docgen:
|
|
|
22
29
|
|
|
23
30
|
## Сценарії використання
|
|
24
31
|
|
|
25
|
-
- `npm/scripts/lib/tests/concern-meta.test.mjs` (concern-meta — policy.engine derivation; concern-meta — lint surface) — явний engine:; legacy check:; legacy без engine/check (Rego) → engine:; lint scope/glob нормалізується (string → array);
|
|
32
|
+
- `npm/scripts/lib/tests/concern-meta.test.mjs` (concern-meta — policy.engine derivation; concern-meta — lint surface) — явний engine:; legacy check:; legacy без engine/check (Rego) → engine:; lint scope/glob нормалізується (string → array); extensionsSlot проходить у LintSurface; не-string ігнорується; ще 3
|
|
26
33
|
|
|
27
34
|
## Гарантії поведінки
|
|
28
35
|
|
|
@@ -3,10 +3,11 @@ type: JS Module
|
|
|
3
3
|
title: run-detectors.mjs
|
|
4
4
|
resource: npm/scripts/lib/lint-surface/run-detectors.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model: omlx/gemma-4-
|
|
6
|
+
crc: a057f918
|
|
7
|
+
model: omlx/gemma-4-26b-a4b-it
|
|
8
8
|
tier: local-min
|
|
9
|
-
score:
|
|
9
|
+
score: 70
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
## Огляд
|
|
@@ -17,14 +18,6 @@ Discovery → scope-selection → `lint(ctx)` per concern → нормалізо
|
|
|
17
18
|
Без мутацій, без LLM. Fix-pipeline (T0 + ladder) обгортає цей модуль і споживає
|
|
18
19
|
його violations; сам detect ніколи не пише в дерево.
|
|
19
20
|
|
|
20
|
-
## Поведінка
|
|
21
|
-
|
|
22
|
-
DEFAULT_RULES_DIR надає вбудований корінь для пошуку правил у випадках, коли користувач не вказує явний каталог.
|
|
23
|
-
buildDetectPlan створює впорядкований план прогону, що є спільним джерелом для як detection-only, так і fix-pipeline.
|
|
24
|
-
loadEnabledLintRules повертає зібрані концерни та набір активних правил, що є вихідними даними для споживачів поза detection/fix-конвеєром.
|
|
25
|
-
computeActiveDomains визначає, які домени стають активними для заданого файлового набору, де домен вважається активним, якщо хоча б один його per-file concern тригериться на цих файлах.
|
|
26
|
-
detectAll запускає detection-only прохід і повертає всі зібрані порушення, код виходу та список виконаних елементів плану.
|
|
27
|
-
|
|
28
21
|
## Публічний API
|
|
29
22
|
|
|
30
23
|
- DEFAULT_RULES_DIR — Цей файл: npm/scripts/lib/lint-surface/run-detectors.mjs → PACKAGE_ROOT = npm (4 dirname угору).
|
|
@@ -44,7 +37,7 @@ concern тригериться на цих файлах (та сама табл
|
|
|
44
37
|
|
|
45
38
|
## Сценарії використання
|
|
46
39
|
|
|
47
|
-
- `npm/scripts/lib/lint-surface/tests/run-detectors.test.mjs` (detectAll — exit codes; detectAll — scoping) — clean → exit 0; violations → exit 1, ruleId/concernId домішані з ctx; detector кидає → exit 2; невалідний violation (без reason) → exit 2; absolute file-path відхиляється → exit 2; ще
|
|
40
|
+
- `npm/scripts/lib/lint-surface/tests/run-detectors.test.mjs` (detectAll — exit codes; detectAll — scoping) — clean → exit 0; violations → exit 1, ruleId/concernId домішані з ctx; detector кидає → exit 2; невалідний violation (без reason) → exit 2; absolute file-path відхиляється → exit 2; ще 19
|
|
48
41
|
|
|
49
42
|
## Гарантії поведінки
|
|
50
43
|
|
|
@@ -17,7 +17,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
17
17
|
import { listConcerns } from '../concern-meta.mjs'
|
|
18
18
|
import { collectChangedFilesSince, resolveChangedBase } from '../changed-files.mjs'
|
|
19
19
|
import { loadNative } from '../native.mjs'
|
|
20
|
-
import { getActiveCapabilities, resolveRulesDirs } from '../plugin-slots.mjs'
|
|
20
|
+
import { getActiveCapabilities, getSlotContributions, resolveRulesDirs, resolveSlotGraph } from '../plugin-slots.mjs'
|
|
21
21
|
import { readNRulesConfigLite, isRuleEnabled } from '../read-n-rules-config-lite.mjs'
|
|
22
22
|
import { evaluateAppliesNode, readRuleApplies } from '../rule-applies.mjs'
|
|
23
23
|
import { isSerialLane } from './blocking-inventory.mjs'
|
|
@@ -284,6 +284,71 @@ async function enabledRuleIds(byRule, cwd, rulesDirs) {
|
|
|
284
284
|
return Object.keys(byRule).filter(id => isRuleEnabled(config, id))
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/**
|
|
288
|
+
* Розширення (без крапки) з extension-map contributions slot-а — злиття `value`-мап
|
|
289
|
+
* (`'.rs' → 'Rust Module'`) усіх активних contributions версії 1.
|
|
290
|
+
* @param {ReturnType<typeof resolveSlotGraph>} graph slot graph від {@link resolveSlotGraph}.
|
|
291
|
+
* @param {string} slot ім'я slot-а (напр. `doc-files.extensions`).
|
|
292
|
+
* @returns {string[]} відсортовані розширення без крапки (порожньо — жодних contributions).
|
|
293
|
+
*/
|
|
294
|
+
function slotExtensions(graph, slot) {
|
|
295
|
+
/** @type {Set<string>} */
|
|
296
|
+
const exts = new Set()
|
|
297
|
+
for (const c of getSlotContributions(graph, slot, [1])) {
|
|
298
|
+
if (!c.value || typeof c.value !== 'object' || Array.isArray(c.value)) continue
|
|
299
|
+
for (const ext of Object.keys(c.value)) {
|
|
300
|
+
if (ext.startsWith('.') && ext.length > 1) exts.add(ext.slice(1))
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return [...exts].toSorted()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Підставляє concern-ам із `lint.extensionsSlot` ефективний glob, виведений з
|
|
308
|
+
* extension-map contributions активних плагінів (напр. `doc-files.extensions@1`:
|
|
309
|
+
* lang-js дає js/mjs/ts/vue, lang-rust — rs) — щоб delta-режим і PostToolUse hook
|
|
310
|
+
* запускали concern на файлах УСІХ плагінних мов, а не лише зі статичного glob-а
|
|
311
|
+
* (раніше зміна `.rs` не запускала doc-files, хоча повний скан його бачив).
|
|
312
|
+
* Без жодної contribution (плагіни не встановлені в node_modules) статичний glob
|
|
313
|
+
* лишається fallback-ом — concern далі тригериться і його diagnostics можуть
|
|
314
|
+
* повідомити про недоступні плагіни.
|
|
315
|
+
* @param {Record<string, ConcernMeta[]>} byRule concerns згруповані за rule-id.
|
|
316
|
+
* @param {string} cwd корінь репозиторію, який лінтиться.
|
|
317
|
+
* @returns {Promise<Record<string, ConcernMeta[]>>} concerns із виведеними glob-ами.
|
|
318
|
+
*/
|
|
319
|
+
async function resolveSlotGlobs(byRule, cwd) {
|
|
320
|
+
const needsResolve = Object.values(byRule).some(concerns => concerns.some(c => c.lint.extensionsSlot !== undefined))
|
|
321
|
+
if (!needsResolve) return byRule
|
|
322
|
+
const config = await readNRulesConfigLite(cwd)
|
|
323
|
+
const graph = resolveSlotGraph(cwd, { plugins: config.plugins }, { allowInstall: false, quiet: true })
|
|
324
|
+
/** @type {Record<string, ConcernMeta[]>} */
|
|
325
|
+
const out = {}
|
|
326
|
+
for (const [ruleId, concerns] of Object.entries(byRule)) {
|
|
327
|
+
out[ruleId] = concerns.map(c => {
|
|
328
|
+
if (c.lint.extensionsSlot === undefined) return c
|
|
329
|
+
const exts = slotExtensions(graph, c.lint.extensionsSlot)
|
|
330
|
+
if (exts.length === 0) return c
|
|
331
|
+
const glob = exts.length === 1 ? [`**/*.${exts[0]}`] : [`**/*.{${exts.join(',')}}`]
|
|
332
|
+
return { ...c, lint: { ...c.lint, glob } }
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
return out
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Спільний discovery-конвеєр detect/fix/ci-plan: rules-каталоги (ядро + плагіни) →
|
|
340
|
+
* concerns за rule-id → capability-фільтр → rule-applies-гейт → резолюція
|
|
341
|
+
* slot-derived glob-ів ({@link resolveSlotGlobs}).
|
|
342
|
+
* @param {{ rulesDir?: string, rulesDirs?: string[], capabilities?: Iterable<string>, cwd: string }} opts опції прогону.
|
|
343
|
+
* @returns {Promise<{ rulesDirs: string[], byRule: Record<string, ConcernMeta[]> }>} rules-каталоги і застосовні concerns.
|
|
344
|
+
*/
|
|
345
|
+
async function discoverConcernsByRule(opts) {
|
|
346
|
+
const rulesDirs = await effectiveRulesDirs(opts)
|
|
347
|
+
const capable = await filterByCapabilities(await readLintConcernsByRuleMulti(rulesDirs), opts)
|
|
348
|
+
const byRule = await resolveSlotGlobs(await filterByRuleApplies(capable, opts.cwd), opts.cwd)
|
|
349
|
+
return { rulesDirs, byRule }
|
|
350
|
+
}
|
|
351
|
+
|
|
287
352
|
/**
|
|
288
353
|
* @typedef {{ entry: LintEntry, files: string[]|undefined }} PlanItem
|
|
289
354
|
*/
|
|
@@ -337,9 +402,7 @@ function fromPlanItems(items, byRule) {
|
|
|
337
402
|
* @returns {Promise<PlanItem[]>} впорядкований план прогону.
|
|
338
403
|
*/
|
|
339
404
|
export async function buildDetectPlan(opts) {
|
|
340
|
-
const rulesDirs = await
|
|
341
|
-
const capable = await filterByCapabilities(await readLintConcernsByRuleMulti(rulesDirs), opts)
|
|
342
|
-
const byRule = await filterByRuleApplies(capable, opts.cwd)
|
|
405
|
+
const { rulesDirs, byRule } = await discoverConcernsByRule(opts)
|
|
343
406
|
return buildPlan({
|
|
344
407
|
byRule,
|
|
345
408
|
full: opts.full === true,
|
|
@@ -360,9 +423,7 @@ export async function buildDetectPlan(opts) {
|
|
|
360
423
|
* @returns {Promise<{ byRule: Record<string, ConcernMeta[]>, enabledSet: Set<string> }>} concerns і активні правила.
|
|
361
424
|
*/
|
|
362
425
|
export async function loadEnabledLintRules(opts) {
|
|
363
|
-
const rulesDirs = await
|
|
364
|
-
const capable = await filterByCapabilities(await readLintConcernsByRuleMulti(rulesDirs), opts)
|
|
365
|
-
const byRule = await filterByRuleApplies(capable, opts.cwd)
|
|
426
|
+
const { rulesDirs, byRule } = await discoverConcernsByRule(opts)
|
|
366
427
|
const enabledSet = new Set(await enabledRuleIds(byRule, opts.cwd, rulesDirs))
|
|
367
428
|
return { byRule, enabledSet }
|
|
368
429
|
}
|
|
@@ -747,9 +808,7 @@ export async function detectAll(opts) {
|
|
|
747
808
|
const verbose = opts.verbose === true
|
|
748
809
|
const baseLog = opts.log ?? (s => process.stdout.write(s))
|
|
749
810
|
|
|
750
|
-
const rulesDirs = await
|
|
751
|
-
const capable = await filterByCapabilities(await readLintConcernsByRuleMulti(rulesDirs), opts)
|
|
752
|
-
const byRule = await filterByRuleApplies(capable, cwd)
|
|
811
|
+
const { rulesDirs, byRule } = await discoverConcernsByRule(opts)
|
|
753
812
|
const plan = await buildPlan({
|
|
754
813
|
byRule,
|
|
755
814
|
full,
|