@7n/rules 1.90.1 → 1.90.2

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.90.2] - 2026-08-11
4
+
5
+ ### Fixed
6
+
7
+ - fix(lint): per-file glob doc-files виводиться з doc-files.extensions contributions — зміна .rs запускає концерн у delta-режимі й PostToolUse hook (новий опційний lint.extensionsSlot у concern.json; статичний glob — fallback, доповнений rs)
8
+
3
9
  ## [1.90.1] - 2026-08-10
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.90.1",
3
+ "version": "1.90.2",
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.2.2",
69
+ "@7n/llm-lib": "3.2.3",
70
70
  "ajv": "^8.20.0",
71
71
  "cli-progress": "^3.12.0",
72
72
  "github-actionlint": "^1.7.12",
@@ -2,6 +2,7 @@
2
2
  "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
3
  "lint": {
4
4
  "scope": "per-file",
5
- "glob": ["**/*.{js,mjs,ts,vue,py}"]
5
+ "glob": ["**/*.{js,mjs,ts,vue,py,rs}"],
6
+ "extensionsSlot": "doc-files.extensions"
6
7
  }
7
8
  }
@@ -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
- return { scope, glob }
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: d452f9b7
7
- model: omlx/gemma-4-e2b-it-4bit
6
+ crc: 877433c9
7
+ model: omlx/gemma-4-26b-a4b-it
8
8
  tier: local-min
9
- score: 80
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); skipLocalTier: true нормалізується, дефолт false; ще 2
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: 838c79de
7
- model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ crc: a057f918
7
+ model: omlx/gemma-4-26b-a4b-it
8
8
  tier: local-min
9
- score: 95
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; ще 16
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 effectiveRulesDirs(opts)
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 effectiveRulesDirs(opts)
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 effectiveRulesDirs(opts)
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,
@@ -1,6 +0,0 @@
1
- {
2
- "lang-js": {
3
- "file": "plugin-lang-js.wasm",
4
- "sha256": "ebe23852b4d6236e858a675600496f18452be66fcfcb4b359c1ab424aa0b941b"
5
- }
6
- }
Binary file