@7n/rules 1.10.0 → 1.11.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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.11.0] - 2026-07-17
4
+
5
+ ### Added
6
+
7
+ - js/eslint: fix-worker.mjs з per-file циклом замість одного великого промпту на весь concern — усунення 100% timeout на драбині, виміряно на реальних lint-прогонах
8
+
9
+ ### Fixed
10
+
11
+ - text/markdownlint: включати причину провалу markdownlint-cli2 у violation-повідомлення; js/knip: fixability=structural (LLM-ладдер завжди приречений на timeout)
12
+
3
13
  ## [1.10.0] - 2026-07-17
4
14
 
5
15
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -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
+ }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "structural",
3
4
  "lint": {
4
5
  "scope": "full"
5
6
  }
@@ -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: 08324688
7
- model: openai-codex/gpt-5.5
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
- Файл забезпечує markdown-концерн для двох поверхонь: `policy` перевіряє наявність `.markdownlint-cli2.jsonc`, а `lint` запускає markdownlint-cli2 для Markdown/MDC-файлів. Він потрібен, щоб delta- та full-режими однаково застосовували правила markdownlint до релевантних файлів і повертали повідомлення з маркером ``.
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. `lint` перевіряє markdown-концерн у двох площинах: наявність конфігурації markdownlint і фактичну якість Markdown-документів.
21
-
22
- 2. `lint` спочатку додає до результату порушення політики, якщо в проєкті немає `.markdownlint-cli2.jsonc`.
23
-
24
- 3. `lint` у delta-режимі перевіряє лише передані Markdown-файли, а у full-режимі — всі файли Markdown і MDC у робочій директорії.
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 — знаходить для `text.mdc` маркери повідомлень, контролює наявність policy-конфігурації та запускає markdownlint; результати обох етапів збирає в один звіт 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
- // вивід markdownlint-cli2 глушимоdetector лише повертає код
46
+ // прогрес-статус markdownlint-cli2 (banner, Finding/Found/Linting/Summary) не деталь
40
47
  },
41
- logError: () => {
42
- // помилки markdownlint-cli2 глушимо — detector лише повертає код
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: 'markdownlint знайшов порушення у *.md/*.mdc (text.mdc)'
58
+ message: `markdownlint знайшов порушення у *.md/*.mdc (text.mdc)${detail}`
51
59
  })
52
60
  }
53
61