@7n/rules 1.11.0 → 1.12.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.12.0] - 2026-07-17
4
+
5
+ ### Changed
6
+
7
+ - Додано прапор `--path <dir>` для `n-rules lint`: звужує файловий набір per-file правил до заданої піддиректорії, лишаючи корінь прогону (root-guard, `.n-rules.json`) незмінним — на відміну від `--cwd`. Несумісний з позиційним rule/concern-фільтром; у парі з `--full` full-вісь ігнорується (без machine-wide локу).
8
+
3
9
  ## [1.11.0] - 2026-07-17
4
10
 
5
11
  ### Added
package/bin/n-rules.js CHANGED
@@ -12,7 +12,9 @@
12
12
  * `npx \@7n/rules lint` — data-driven оркестратор lint+конформності по `rules/<id>/meta.json` (`lint: per-file|full`):
13
13
  * за замовчуванням fix-by-default по дельті vs origin (лише `per-file` правила); `--full` =
14
14
  * весь репо (`per-file` ∪ `full`); `--no-fix` = без мутацій/LLM (CI); позиційні
15
- * (не-флаг) аргументи — фільтр правил конформності (мапить колишній `fix <rule>`).
15
+ * (не-флаг) аргументи — фільтр правил конформності (мапить колишній `fix <rule>`);
16
+ * `--path <dir>` = звузити файловий набір до піддиректорії, корінь прогону (root-guard,
17
+ * `.n-rules.json`) лишається поточним каталогом/`--cwd`, не сумісний з rule-фільтром.
16
18
  * CI = `lint --no-fix --full` (весь репо, нуль мутацій/LLM).
17
19
  * `npx \@7n/rules skill list` — скіли пакета без синку в проєкт
18
20
  * `npx \@7n/rules skill taze` — промпт на stdout
@@ -1632,7 +1634,16 @@ function printLintHelp() {
1632
1634
  Один --full на машину: паралельні запуски стають у чергу
1633
1635
  й бачать живий прогрес активного прогону.
1634
1636
  --no-fix Лише детекція, без мутацій.
1635
- --cwd <path> Робочий каталог замість поточного.
1637
+ --cwd <path> Робочий каталог (корінь прогону) замість поточного —
1638
+ root-guard, .n-rules.json і devDependencies читаються
1639
+ з нього.
1640
+ --path <dir> Звузити файловий набір до піддиректорії, лишивши корінь
1641
+ прогону незмінним (config/root-guard — з поточного каталогу
1642
+ чи --cwd). per-file правила фільтруються по файлах <dir>;
1643
+ full-scope правила (jscpd, cspell тощо) при спрацюванні
1644
+ все одно йдуть по всьому репо. Несумісний з позиційним
1645
+ rule/concern-фільтром; з --full full-вісь ігнорується
1646
+ (немає machine-wide локу).
1636
1647
  --verbose Розширений вивід детекції та виправлення.
1637
1648
  --help, -h Ця довідка.
1638
1649
 
@@ -1644,6 +1655,7 @@ function printLintHelp() {
1644
1655
  npx @7n/rules lint --full
1645
1656
  npx @7n/rules lint --no-fix eslint
1646
1657
  npx @7n/rules lint --cwd ./packages/foo --verbose
1658
+ npx @7n/rules lint --path npm/scripts/lib/lint-surface --no-fix
1647
1659
  `)
1648
1660
  }
1649
1661
 
@@ -1717,8 +1729,29 @@ try {
1717
1729
  // Fix-by-default: detect → T0 → LLM-ladder (run-fix). --no-fix: лише detect.
1718
1730
  const cwdIdx = args.indexOf('--cwd')
1719
1731
  const cwdArg = cwdIdx === -1 ? cwd() : resolve(args[cwdIdx + 1])
1720
- const rules = args.filter((a, i) => !a.startsWith('-') && !(cwdIdx !== -1 && i === cwdIdx + 1))
1721
- const lintOpts = { cwd: cwdArg, full: args.includes('--full'), rules, verbose: args.includes('--verbose') }
1732
+ // --path: на відміну від --cwd (підміняє корінь), лишає корінь/config
1733
+ // незмінними й лише звужує файловий набір до заданої піддиректорії
1734
+ // (той самий explicitFiles-шлях, що вже годує hook --post-tool-use/--stop).
1735
+ const pathIdx = args.indexOf('--path')
1736
+ const pathArg = pathIdx === -1 ? null : args[pathIdx + 1]
1737
+ const rules = args.filter(
1738
+ (a, i) => !a.startsWith('-') && !(cwdIdx !== -1 && i === cwdIdx + 1) && !(pathIdx !== -1 && i === pathIdx + 1)
1739
+ )
1740
+ if (pathArg !== null && rules.length > 0) {
1741
+ throw new Error(
1742
+ '--path не можна поєднувати зі scoped rule/concern фільтром (позиційні аргументи) — оберіть щось одне'
1743
+ )
1744
+ }
1745
+ let pathFiles = null
1746
+ if (pathArg !== null) {
1747
+ const { collectPathScopedFiles } = await import('../scripts/lib/lint-surface/path-scope.mjs')
1748
+ pathFiles = await collectPathScopedFiles(cwdArg, pathArg)
1749
+ }
1750
+ // --full + --path: buildPlan ігнорує full, коли передано explicitFiles (той самий
1751
+ // шлях), тож і тут full-вісь вважаємо неактивною — інакше глобальний machine-wide
1752
+ // лок --full брався б для швидкого scoped-прогону без причини.
1753
+ const full = args.includes('--full') && pathArg === null
1754
+ const lintOpts = { cwd: cwdArg, full, rules, verbose: args.includes('--verbose'), files: pathFiles }
1722
1755
  const noFix = args.includes('--no-fix')
1723
1756
  // Глобальна черга full-прогонів (spec 2026-07-03): одночасно виконується один
1724
1757
  // `lint --full` на машину, паралельні --full чекають лока і бачать чергу та
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -13,6 +13,7 @@ resource: npm/scripts/lib/lint-surface/
13
13
  | [ladder.mjs](ladder.md) | JS Module |
14
14
  | [lint-lock.mjs](lint-lock.md) | JS Module |
15
15
  | [mt-tail.mjs](mt-tail.md) | JS Module |
16
+ | [path-scope.mjs](path-scope.md) | JS Module |
16
17
  | [policy-lint-adapter.mjs](policy-lint-adapter.md) | JS Module |
17
18
  | [policy-test-step.mjs](policy-test-step.md) | JS Module |
18
19
  | [progress.mjs](progress.md) | JS Module |
@@ -0,0 +1,31 @@
1
+ ---
2
+ type: JS Module
3
+ title: path-scope.mjs
4
+ resource: npm/scripts/lib/lint-surface/path-scope.mjs
5
+ docgen:
6
+ crc: 0971e78e
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge:inaccurate:0.94
11
+ judgeModel: openai-codex/gpt-5.4-mini
12
+ ---
13
+
14
+ ## Огляд
15
+
16
+ Файл звужує `n-rules lint --path <dir>` до каталогу всередині вже вибраного кореня прогону, не змінюючи сам root і не підміняючи правила з `.n-rules.json`. Він збирає всі файли під заданою піддиректорією та передає їх у `buildPlan` як `explicitFiles`, тим самим шляхом, що вже живить `hook --post-tool-use`/`--stop`; per-file concerns фільтруються за цими файлами, а `full`-scope concerns запускаються при збігу glob і все одно проходять whole-repo.
17
+
18
+ ## Поведінка
19
+
20
+ 1. `collectPathScopedFiles` приймає корінь прогону та значення `--path`, звужує область lint до каталогу всередині цього кореня й не змінює сам корінь прогону.
21
+ 2. `collectPathScopedFiles` відхиляє шлях, якщо він веде поза межі кореня або не є каталогом.
22
+ 3. `collectPathScopedFiles` збирає всі файли в межах вказаного каталогу, відсікаючи виключення з кореневих ignore-налаштувань і `.gitignore`-поведінки, а порожній каталог вважає валідним порожнім результатом.
23
+ 4. `collectPathScopedFiles` повертає відсортований список шляхів відносно кореня прогону у форматі, придатному для передачі в `buildPlan` як `explicitFiles`.
24
+
25
+ ## Публічний API
26
+
27
+ - collectPathScopedFiles — збирає posix-відносні від `cwd` шляхи всіх файлів у каталозі з `--path`, з урахуванням `.gitignore` і `.n-rules.json:ignore` у корені; порожній каталог повертає порожній набір без помилки
28
+
29
+ ## Гарантії поведінки
30
+
31
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Резолвер explicit-files списку для `n-rules lint --path <dir>`.
3
+ *
4
+ * На відміну від `--cwd` (підміняє корінь прогону — root-guard, `.n-rules.json`,
5
+ * devDependencies), `--path` лишає корінь незмінним і лише звужує файловий
6
+ * набір: збирає всі файли під заданою піддиректорією й передає їх у
7
+ * `buildPlan` як `explicitFiles`, тим самим шляхом, що вже годує
8
+ * `hook --post-tool-use`/`--stop` (per-file concerns фільтруються по цих
9
+ * файлах; `full`-scope concerns запускаються при збігу glob, але самі
10
+ * все одно проходять whole-repo — так уже поводиться delta-режим).
11
+ */
12
+ import { existsSync, statSync } from 'node:fs'
13
+ import { isAbsolute, relative, resolve, sep } from 'node:path'
14
+
15
+ import { loadCursorIgnorePaths } from '../load-cursor-config.mjs'
16
+ import { walkDir } from '../../utils/walkDir.mjs'
17
+
18
+ /**
19
+ * Перевіряє, що резолвлений `--path` лежить усередині `cwd` (не traversal
20
+ * через `..` і не абсолютний шлях поза коренем прогону).
21
+ * @param {string} cwd абсолютний корінь прогону.
22
+ * @param {string} target абсолютний резолв значення `--path`.
23
+ * @returns {void}
24
+ */
25
+ function assertWithinCwd(cwd, target) {
26
+ const rel = relative(cwd, target)
27
+ if (rel === '') return
28
+ if (rel.startsWith('..') || isAbsolute(rel)) {
29
+ throw new Error(`--path має вказувати каталог усередині ${cwd} (отримано поза межами: ${target})`)
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Збирає posix-відносні (від `cwd`) шляхи всіх файлів під `--path`-каталогом,
35
+ * поважаючи `.gitignore` і `.n-rules.json:ignore` кореня. Порожній каталог —
36
+ * валідний порожній результат (план виявиться порожнім), не помилка.
37
+ * @param {string} cwd абсолютний корінь прогону (root-guard уже пройдено).
38
+ * @param {string} pathArg значення `--path` (відносний або абсолютний шлях).
39
+ * @returns {Promise<string[]>} відсортовані posix-відносні шляхи від `cwd`.
40
+ */
41
+ export async function collectPathScopedFiles(cwd, pathArg) {
42
+ const target = resolve(cwd, pathArg)
43
+ assertWithinCwd(cwd, target)
44
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
45
+ throw new Error(`--path не є каталогом: ${target}`)
46
+ }
47
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
48
+ /** @type {string[]} */
49
+ const out = []
50
+ await walkDir(
51
+ target,
52
+ abs => {
53
+ out.push(relative(cwd, abs).split(sep).join('/'))
54
+ },
55
+ ignorePaths
56
+ )
57
+ return out.toSorted((a, b) => a.localeCompare(b))
58
+ }