@7n/rules 1.10.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 +16 -0
- package/bin/n-rules.js +37 -4
- package/package.json +1 -1
- package/rules/js/eslint/docs/fix-worker.md +30 -0
- package/rules/js/eslint/docs/index.md +11 -0
- package/rules/js/eslint/fix-worker.mjs +99 -0
- package/rules/js/knip/concern.json +1 -0
- package/rules/text/markdownlint/docs/main.md +11 -22
- package/rules/text/markdownlint/main.mjs +12 -4
- package/scripts/lib/lint-surface/docs/index.md +1 -0
- package/scripts/lib/lint-surface/docs/path-scope.md +31 -0
- package/scripts/lib/lint-surface/path-scope.mjs +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
9
|
+
## [1.11.0] - 2026-07-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- js/eslint: fix-worker.mjs з per-file циклом замість одного великого промпту на весь concern — усунення 100% timeout на драбині, виміряно на реальних lint-прогонах
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- text/markdownlint: включати причину провалу markdownlint-cli2 у violation-повідомлення; js/knip: fixability=structural (LLM-ладдер завжди приречений на timeout)
|
|
18
|
+
|
|
3
19
|
## [1.10.0] - 2026-07-17
|
|
4
20
|
|
|
5
21
|
### 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
|
-
|
|
1721
|
-
|
|
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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-worker.mjs
|
|
4
|
+
resource: npm/rules/js/eslint/fix-worker.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 1eb9c7fc
|
|
7
|
+
model: manual
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Custom fix-worker `js/eslint` (перекриває дефолтний `default-worker.mjs`): замість одного `runAgentFix`-виклику з усіма порушеннями з усіх файлів concern-а — окрема агентна сесія на кожен файл, у межах спільного дедлайну rung-а. Мотивація — виміряно на реальних lint-прогонах: одна сесія, що жонглює кількома файлами одразу, стабільно впирається у timeout на всіх 4 rung-ах драбини (local-min→cloud-avg), навіть при малому обсязі порушень; та сама модель, scoped на один файл, укладається в бюджет і закриває більшість порушень.
|
|
13
|
+
|
|
14
|
+
## Поведінка
|
|
15
|
+
|
|
16
|
+
1. Групує вхідні `violations` за `file`; порушення без `file` ігноруються.
|
|
17
|
+
2. Дедлайн — `DEADLINE_FRACTION` (0.8) від `ctx.timeoutMs`; цикл перевіряє дедлайн ПЕРЕД стартом кожного файлу і не починає новий, якщо час вичерпано.
|
|
18
|
+
3. Кожен файл отримує РЕШТУ бюджету до дедлайну (не фіксований поділ `timeoutMs / files.length`) — перший (часто найважчий) файл отримує найбільше часу.
|
|
19
|
+
4. На кожен файл — окремий `runAgentFix` із `targetFiles: [file]` і власним `verify` (`verifyFile`): item-scoped canonical re-detect лише цього файлу, не всього concern-а — інакше evidence-гейт хибно вважав би файл незакритим через порушення в ІНШИХ файлах.
|
|
20
|
+
5. Один файл, що завершився з `error`, не обриває цикл — пропускається, наступні файли все одно обробляються в межах дедлайну.
|
|
21
|
+
6. Повертає лише `touchedFiles` з успішних (`!error`) викликів; success rung-а все одно визначає whole-concern canonical re-detect runner-а (`runRung`), не цей worker.
|
|
22
|
+
|
|
23
|
+
## Публічний API
|
|
24
|
+
|
|
25
|
+
- `fixWorker(violations, ctx)` — контракт `FixWorkerFn`; резолвиться автоматично замість `default-worker.mjs`, бо лежить як `fix-worker.mjs` поруч із `main.mjs` concern-а.
|
|
26
|
+
|
|
27
|
+
## Гарантії поведінки
|
|
28
|
+
|
|
29
|
+
- Пише лише у файли з переданих `violations` (`targetFiles: [file]` на кожен виклик) — той самий semantic-collateral guard, що й у дефолтного worker-а.
|
|
30
|
+
- `recordWrite` (не durable) на кожен файл: rollback-контракт незмінний — якщо після worker-а в concern-і лишилось хоч одне порушення будь-де, `runRung` відкочує ВСІ правки цього rung-а, включно з уже полагодженими файлами.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/js/eslint
|
|
4
|
+
resource: npm/rules/js/eslint/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------- | --------- |
|
|
9
|
+
| [fix-eslint.mjs](fix-eslint.md) | JS Module |
|
|
10
|
+
| [fix-worker.mjs](fix-worker.md) | JS Module |
|
|
11
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fix-worker для `js/eslint`: окрема агентна сесія НА ФАЙЛ замість одного великого промпту
|
|
3
|
+
* на всі файли concern-а одразу. Дефолтний worker (`default-worker.mjs`) шле ВСІ порушення
|
|
4
|
+
* з УСІХ файлів в один `runAgentFix`-виклик — на реальних lint-прогонах (tauri-components)
|
|
5
|
+
* це давало 100% timeout на всіх 4 rung-ах драбини (local-min→cloud-avg), навіть коли
|
|
6
|
+
* порушень було лише 20-90 у 1-3 файлах: одна сесія жонглює кількома файлами одразу і не
|
|
7
|
+
* встигає в rung-таймаут. Виміряно (2026-07-17, ручний timing-експеримент поза pipeline):
|
|
8
|
+
* одна сесія, scoped на ОДИН найважчий файл (21 порушення), закрила 76% (16/21) за 91с із
|
|
9
|
+
* бюджету 120с (cloud-min) — per-file scoping вкладається, mega-prompt — ні.
|
|
10
|
+
*
|
|
11
|
+
* Rollback-контракт незмінний: `recordWrite` (не durable) на кожен файл — правки наявного
|
|
12
|
+
* стороннього коду. Якщо після worker-а `runRung`-ів canonical re-detect (whole-concern)
|
|
13
|
+
* знайде хоч одне порушення будь-де, увесь rung однаково відкотиться (S1) — той самий
|
|
14
|
+
* контракт, що й у дефолтного worker-а; батчинг підвищує ймовірність, що ЦЕЙ rung дійде
|
|
15
|
+
* до 0 порушень, а не замінює rollback-семантику.
|
|
16
|
+
*
|
|
17
|
+
* Дедлайн (`DEADLINE_FRACTION` від `ctx.timeoutMs`, той самий підхід, що й
|
|
18
|
+
* `doc-files/check/fix-worker.mjs`): цикл не стартує наступний файл, якщо дедлайн
|
|
19
|
+
* настав — worker повертає часткову роботу штатно, замість фонової сесії, що триває
|
|
20
|
+
* поверх backstop ×1.25 runner-а. Кожен файл отримує РЕШТУ бюджету до дедлайну (не
|
|
21
|
+
* фіксований `timeoutMs / files.length`) — перший (часто найважчий) файл отримує
|
|
22
|
+
* найбільше часу, а не штучно урізаний рівний шматок.
|
|
23
|
+
* @typedef {import('../../../scripts/lib/lint-surface/types.mjs').FixWorkerFn} FixWorkerFn
|
|
24
|
+
*/
|
|
25
|
+
import { resolve } from 'node:path'
|
|
26
|
+
|
|
27
|
+
import { anchoredEnabled } from '../../../scripts/lib/lint-surface/default-worker.mjs'
|
|
28
|
+
import { renderViolations } from '../../../scripts/lib/lint-surface/render.mjs'
|
|
29
|
+
import { lint } from './main.mjs'
|
|
30
|
+
|
|
31
|
+
/** Частка ctx.timeoutMs, після якої цикл не стартує наступний файл (запас до backstop ×1.25). */
|
|
32
|
+
const DEADLINE_FRACTION = 0.8
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Item-scoped (один файл) canonical re-detect для evidence-гейта `runAgentFix` —
|
|
36
|
+
* НЕ те саме, що whole-concern `ctx.verify` з `runRung` (той перевірив би ВСІ файли
|
|
37
|
+
* concern-а й дав би хибний "не готово" навіть коли саме ЦЕЙ файл уже чистий).
|
|
38
|
+
* @param {string} cwd абсолютний корінь проєкту
|
|
39
|
+
* @param {string} ruleId id правила
|
|
40
|
+
* @param {string} concernId id concern-а
|
|
41
|
+
* @param {string} file posix-relative шлях файлу, що перевіряється
|
|
42
|
+
* @returns {Promise<{ ok: boolean, output: string }>} вердикт verify-петлі
|
|
43
|
+
*/
|
|
44
|
+
async function verifyFile(cwd, ruleId, concernId, file) {
|
|
45
|
+
const { violations: after } = await lint({ cwd, ruleId, concernId, files: [file] })
|
|
46
|
+
const stamped = after.map(v => ({ ...v, ruleId, concernId }))
|
|
47
|
+
return { ok: stamped.length === 0, output: renderViolations(stamped) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @type {FixWorkerFn} */
|
|
51
|
+
export async function fixWorker(violations, ctx) {
|
|
52
|
+
// lazy import — тримає detect-шлях вільним від pi/oxc (read-only --no-fix не вантажить їх).
|
|
53
|
+
const [{ runAgentFix }, { isLocalModel }, { extractContext }] = await Promise.all([
|
|
54
|
+
import('@7n/llm-lib/agent-fix'),
|
|
55
|
+
import('@7n/llm-lib/model-tiers'),
|
|
56
|
+
import('../../../scripts/utils/ast-extract.mjs')
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
/** @type {Map<string, import('../../../scripts/lib/lint-surface/types.mjs').LintViolation[]>} */
|
|
60
|
+
const byFile = new Map()
|
|
61
|
+
for (const v of violations) {
|
|
62
|
+
if (!v.file) continue
|
|
63
|
+
const arr = byFile.get(v.file)
|
|
64
|
+
if (arr) arr.push(v)
|
|
65
|
+
else byFile.set(v.file, [v])
|
|
66
|
+
}
|
|
67
|
+
const files = byFile.keys().toArray()
|
|
68
|
+
if (files.length === 0) return { touchedFiles: [] }
|
|
69
|
+
|
|
70
|
+
const deadlineAt = ctx.timeoutMs ? Date.now() + Math.round(ctx.timeoutMs * DEADLINE_FRACTION) : null
|
|
71
|
+
const anchoredEdits = anchoredEnabled(ctx.model, isLocalModel)
|
|
72
|
+
|
|
73
|
+
const touchedFiles = []
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
if (deadlineAt && Date.now() >= deadlineAt) break
|
|
76
|
+
const callTimeoutMs = deadlineAt ? Math.max(1000, deadlineAt - Date.now()) : ctx.timeoutMs
|
|
77
|
+
|
|
78
|
+
const res = await runAgentFix(ctx.ruleId, renderViolations(byFile.get(file)), ctx.cwd, {
|
|
79
|
+
model: ctx.model,
|
|
80
|
+
tier: ctx.tier,
|
|
81
|
+
timeoutMs: callTimeoutMs,
|
|
82
|
+
feedback: ctx.feedback ?? null,
|
|
83
|
+
caller: `fix:${ctx.ruleId}/${ctx.concernId}:${ctx.tier}:${file}`,
|
|
84
|
+
recordWrite: ctx.recordWrite,
|
|
85
|
+
chain: ctx.chain ?? null,
|
|
86
|
+
targetFiles: [file],
|
|
87
|
+
verify: () => verifyFile(ctx.cwd, ctx.ruleId, ctx.concernId, file),
|
|
88
|
+
verifyMax: ctx.verifyMax,
|
|
89
|
+
anchoredEdits,
|
|
90
|
+
deps: { astContext: p => extractContext(resolve(ctx.cwd, p)) }
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Один файл не впорався — не кидаємо, пробуємо решту в межах дедлайну; whole-concern
|
|
94
|
+
// canonical re-detect runner-а (не цей worker) визначить, чи rung закрито.
|
|
95
|
+
if (!res.error) touchedFiles.push(...(res.touchedFiles ?? []))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { touchedFiles }
|
|
99
|
+
}
|
|
@@ -3,38 +3,27 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/text/markdownlint/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
8
|
-
tier: cloud-avg
|
|
9
|
-
score: 95
|
|
10
|
-
issues: anchor-miss:(text.mdc),judge:inaccurate:0.98
|
|
11
|
-
judgeModel: openai-codex/gpt-5.4-mini
|
|
6
|
+
crc: 6aec4713
|
|
7
|
+
model: manual
|
|
12
8
|
---
|
|
13
9
|
|
|
14
10
|
## Огляд
|
|
15
11
|
|
|
16
|
-
|
|
12
|
+
Multi-surface detector `text/markdownlint`: `policy` перевіряє наявність `.markdownlint-cli2.jsonc`, `lint` запускає сам `markdownlint-cli2` (delta — по `ctx.files`, full — за glob `**/*.md`/`**/*.mdc`) і повертає обидва результати в одному `LintResult`.
|
|
17
13
|
|
|
18
14
|
## Поведінка
|
|
19
15
|
|
|
20
|
-
1. `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
4. `lint` не запускає markdownlint, якщо серед цільових файлів немає Markdown-документів, і повертає лише вже зібрані порушення політики.
|
|
27
|
-
|
|
28
|
-
5. `lint` приглушує прямий вивід markdownlint, щоб detector повертав уніфікований результат lint-перевірки без шуму в консолі.
|
|
29
|
-
|
|
30
|
-
6. `lint` додає порушення з маркером ``, якщо markdownlint знаходить проблеми у Markdown або MDC-файлах.
|
|
31
|
-
|
|
32
|
-
7. `lint` повертає спільний список порушень для policy- та lint-поверхонь, щоб markdown-концерн поводився як один цілісний detector.
|
|
16
|
+
1. Policy-перевірка (`evaluatePolicyConcern`) додає порушення, якщо `.markdownlint-cli2.jsonc` відсутній.
|
|
17
|
+
2. Якщо серед цільових файлів немає Markdown/MDC — `markdownlint-cli2` не запускається, повертаються лише policy-порушення.
|
|
18
|
+
3. `logMessage` (banner, `Finding:`/`Found:`/`Linting:`/`Summary:` прогрес-текст) — no-op, не деталь порушення.
|
|
19
|
+
4. `logError` (один готовий рядок на порушення від дефолтного форматера markdownlint-cli2: `<file>:<line>:<col> <rule> <опис> [<деталь>]`) — накопичується в масив і вбудовується у violation-повідомлення. Без цього LLM fix-worker (і non-verbose підсумок) бачив лише голе "markdownlint знайшов порушення", без файлу/рядка/правила.
|
|
20
|
+
5. Ненульовий exit-код `markdownlint-cli2` → одне порушення `reason: 'markdownlint'` з накопиченою деталлю.
|
|
33
21
|
|
|
34
22
|
## Публічний API
|
|
35
23
|
|
|
36
|
-
- lint —
|
|
24
|
+
- `lint(ctx)` — detector-контракт unified lint surface; повертає `{ violations }` (policy + lint-порушення разом).
|
|
37
25
|
|
|
38
26
|
## Гарантії поведінки
|
|
39
27
|
|
|
40
|
-
- Read-only: не
|
|
28
|
+
- Read-only: не пише `.markdownlint-cli2.jsonc` сам — генерація конфігу окремим T0 (не в цьому detector-і).
|
|
29
|
+
- `logError`-деталь — єдине джерело причини провалу; текстові евристики за кодом виходу не використовуються.
|
|
@@ -32,22 +32,30 @@ export async function lint(ctx) {
|
|
|
32
32
|
const targets = ctx.files === undefined ? DEFAULT_MD_GLOBS : ctx.files.filter(f => MD_EXT_RE.test(f))
|
|
33
33
|
if (targets.length === 0) return { violations }
|
|
34
34
|
|
|
35
|
+
// logError отримує один готовий рядок на порушення через default output formatter
|
|
36
|
+
// markdownlint-cli2 ("<file>:<line>:<col> <rule> <опис> [<деталь>]") — раніше глушився,
|
|
37
|
+
// detector повертав лише голе "щось не пройшло" без файлу/правила/причини, тож LLM
|
|
38
|
+
// fix-worker (як і non-verbose підсумок) не мав інформації, що саме виправляти
|
|
39
|
+
// (той самий патерн, що й text/run-v8r до фіксу). logMessage лишається no-op —
|
|
40
|
+
// banner/Finding/Found/Linting/Summary прогрес-текст, не деталь порушення.
|
|
41
|
+
const errorLines = []
|
|
35
42
|
const code = await markdownlintCli2({
|
|
36
43
|
directory: ctx.cwd,
|
|
37
44
|
argv: targets,
|
|
38
45
|
logMessage: () => {
|
|
39
|
-
//
|
|
46
|
+
// прогрес-статус markdownlint-cli2 (banner, Finding/Found/Linting/Summary) — не деталь
|
|
40
47
|
},
|
|
41
|
-
logError:
|
|
42
|
-
|
|
48
|
+
logError: message => {
|
|
49
|
+
errorLines.push(message)
|
|
43
50
|
}
|
|
44
51
|
})
|
|
45
52
|
if (code !== 0) {
|
|
53
|
+
const detail = errorLines.length > 0 ? `:\n${errorLines.join('\n')}` : ''
|
|
46
54
|
violations.push({
|
|
47
55
|
ruleId: ctx.ruleId,
|
|
48
56
|
concernId: ctx.concernId,
|
|
49
57
|
reason: 'markdownlint',
|
|
50
|
-
message:
|
|
58
|
+
message: `markdownlint знайшов порушення у *.md/*.mdc (text.mdc)${detail}`
|
|
51
59
|
})
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -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
|
+
}
|