@nitra/cursor 10.3.0 → 11.1.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [11.1.0] - 2026-06-15
4
+
5
+ ### Changed
6
+
7
+ - doc-files lint-крок: opportunistic LLM-fix tier (спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md). У fix-by-default (не `--read-only`) крок тепер не лише детектить застарілі доки, а й намагається їх **згенерувати** локальною моделлю, якщо omlx піднято; недоступний omlx → fix пропускається з повідомленням і лишається exit 1 (гейт тримається, без false-green). `--read-only` (CI/hook) лишається суто детектом — нуль мутацій/LLM. Прапор `meta.json: llmFix:true` (схема `rule-meta.json`) — opt-in лише для контент-правил; логічні лінтери НЕ вмикати (LLM-правка коду може змінити поведінку). Спільне ядро генерації винесено в `runGenerationBatch`/`preflightProblem` (експорт з `docgen-files-batch.mjs`) — перевикористовують і батч-CLI `fix-doc-files`, і lint-крок.
8
+
9
+ ## [11.0.0] - 2026-06-15
10
+
11
+ ### Removed
12
+
13
+ - CLI: прибрано надлишкові точки входу заради мінімальної поверхні. `lint-ci` видалено — це був чистий аліас `lint --read-only --full` (CI лишається тим самим прапор-комбо). Deprecated-аліас `doc-files <sub>` (`scan|check|gen|stamp`) видалено — 0 живих callerів (hook давно на `lint-doc-files`, скіл на `lint-doc-files`/`fix-doc-files`). Заодно полагоджено застарілу документацію точок входу в `bin/n-cursor.js` (мертві згадки `fix`, опис `lint` → data-driven по `meta.json:lint`) і схему `rule-meta.json` (enum `quick|ci` → `per-file|full`, відповідає реальним значенням і `parseRuleLintSpec`).
14
+
3
15
  ## [10.3.0] - 2026-06-15
4
16
 
5
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "10.3.0",
3
+ "version": "11.1.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -63,7 +63,7 @@ function selectTargets(root, all, { overwrite, retryDegraded }) {
63
63
  * Preflight локального бекенда: для omlx-моделі — мінімальний chat-виклик.
64
64
  * @returns {string|null} текст фатальної проблеми або null якщо можна генерувати
65
65
  */
66
- function preflightProblem() {
66
+ export function preflightProblem() {
67
67
  if (!DEFAULT_LOCAL_MODEL) {
68
68
  return 'модель не задано. Вистав N_LOCAL_MIN_MODEL (напр. omlx/mlx-community--gemma-4-e4b-it-OptiQ-4bit) і повтори.'
69
69
  }
@@ -214,13 +214,29 @@ export async function runDocFilesGenCli(argv) {
214
214
  return 0
215
215
  }
216
216
 
217
+ return runGenerationBatch(targets, root, {
218
+ headline: `📋 doc-files: до генерації ${targets.length} файл(ів)${modeSuffix({ overwrite, retryDegraded })}`
219
+ })
220
+ }
221
+
222
+ /**
223
+ * Спільне ядро генерації: preflight локального бекенда → послідовний прогін
224
+ * `targets` через `generateOne` з circuit-breaker'ом (K systemic-збоїв підряд →
225
+ * abort) → підсумковий звіт. Перевикористовують і батч-CLI (`runDocFilesGenCli`),
226
+ * і opportunistic lint-крок doc-files (scoped-набір змінених файлів).
227
+ * @param {Array<object>} targets елементи scanForDocFiles (sourcePath/docPath)
228
+ * @param {string} root абсолютний корінь
229
+ * @param {{ headline?: string }} [opts] headline — рядок-шапка прогону у stdout
230
+ * @returns {Promise<number>} 0 — без помилок; 1 — фейл preflight або є помилки; 2 — systemic-abort
231
+ */
232
+ export async function runGenerationBatch(targets, root, { headline } = {}) {
217
233
  const problem = preflightProblem()
218
234
  if (problem) {
219
235
  console.error(`✗ fix-doc-files: ${problem}`)
220
236
  return 1
221
237
  }
222
238
 
223
- console.log(`📋 doc-files: до генерації ${targets.length} файл(ів)${modeSuffix({ overwrite, retryDegraded })}`)
239
+ if (headline) console.log(headline)
224
240
  const stats = { ok: 0, degraded: 0, err: 0, errors: [], skipped: [] }
225
241
 
226
242
  let done = 0
@@ -9,6 +9,7 @@ import { docPathForSource } from './docgen-scan.mjs'
9
9
  import { extractFacts } from './docgen-extract.mjs'
10
10
  import { extractAnchors, anchorTokens } from './docgen-extract-anchors.mjs'
11
11
  import { QUALITY_THRESHOLD } from './docgen-crc.mjs'
12
+ import { JUDGE_ENABLED, judgeDoc, judgeFailsDoc } from './docgen-judge.mjs'
12
13
  import {
13
14
  oneShotMessages,
14
15
  sectionMessages,
@@ -449,6 +450,18 @@ export function generateDoc(file, { model = DEFAULT_LOCAL_MODEL, threshold = QUA
449
450
  }
450
451
  }
451
452
 
453
+ // Stage 3 (опц.): семантичний judge-гейт — лише за N_CURSOR_DOCGEN_JUDGE=1 і на
454
+ // доках, що ПРОЙШЛИ det-скорер (там ховаються false-positives). Scope: inaccurate.
455
+ let judge = null
456
+ if (JUDGE_ENABLED && score >= threshold) {
457
+ try {
458
+ judge = judgeDoc(src, r.md)
459
+ if (judgeFailsDoc(judge)) issues = [...issues, `judge:inaccurate:${judge.confidence}`]
460
+ } catch (error) {
461
+ issues = [...issues, `judge:error: ${error.message.slice(0, 80)}`]
462
+ }
463
+ }
464
+
452
465
  return {
453
466
  ...r,
454
467
  ms: Date.now() - t0,
@@ -456,7 +469,8 @@ export function generateDoc(file, { model = DEFAULT_LOCAL_MODEL, threshold = QUA
456
469
  llmCalls: llmMeter.calls,
457
470
  score,
458
471
  issues,
459
- degraded: score < threshold,
472
+ judge,
473
+ degraded: score < threshold || judgeFailsDoc(judge),
460
474
  model
461
475
  }
462
476
  }
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Usage:
16
16
  * node docgen-judge-measure.mjs <file1> <file2> ...
17
- * MEASURE_CACHE=/tmp/x N_CURSOR_DOCGEN_JUDGE_MODEL=openai-codex/gpt-5.4 node docgen-judge-measure.mjs ...
17
+ * MEASURE_CACHE=/tmp/x N_CLOUD_MIN_MODEL=openai-codex/gpt-5.4 node docgen-judge-measure.mjs ...
18
18
  */
19
19
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
20
20
  import { createHash } from 'node:crypto'
@@ -25,7 +25,7 @@ import { QUALITY_THRESHOLD } from './docgen-crc.mjs'
25
25
 
26
26
  const env = process.env
27
27
  const GEN_MODEL = env.N_LOCAL_MIN_MODEL ?? 'omlx/gemma-4-e4b-it-OptiQ-4bit'
28
- const JUDGE_MODEL = env.N_CURSOR_DOCGEN_JUDGE_MODEL ?? 'openai-codex/gpt-5.4-mini'
28
+ const JUDGE_MODEL = env.N_CLOUD_MIN_MODEL ?? 'openai-codex/gpt-5.4-mini'
29
29
  const THRESHOLD = Number(env.N_CURSOR_DOC_FILES_THRESHOLD ?? QUALITY_THRESHOLD) || 70
30
30
  const CACHE_DIR = env.MEASURE_CACHE ?? '/tmp/docgen-judge-measure'
31
31
  const JUDGE_TIMEOUT = Number(env.MEASURE_JUDGE_TIMEOUT_MS ?? 120_000)
@@ -48,7 +48,12 @@ function cacheSet(key, val) {
48
48
  writeFileSync(join(CACHE_DIR, key + '.json'), JSON.stringify(val))
49
49
  }
50
50
 
51
- /** Генерує (з кешем за хешем src). */
51
+ /**
52
+ * Генерує док (з кешем за хешем src).
53
+ * @param {string} file абсолютний шлях джерела
54
+ * @param {string} src вміст файлу
55
+ * @returns {{md: string, score: number|null, issues: string[], degraded: boolean, cached: boolean}} результат генерації
56
+ */
52
57
  function genCached(file, src) {
53
58
  const key = 'gen-' + sha(GEN_MODEL + '\0' + src)
54
59
  const hit = cacheGet(key)
@@ -59,7 +64,12 @@ function genCached(file, src) {
59
64
  return { ...out, cached: false }
60
65
  }
61
66
 
62
- /** Судить (з кешем за хешем src+doc). */
67
+ /**
68
+ * Судить док сильною моделлю (з кешем за хешем src+doc).
69
+ * @param {string} src вміст вихідного файлу
70
+ * @param {string} doc згенерована документація
71
+ * @returns {{verdict: string, confidence: number, reason: string, offending?: string[], cached: boolean}} verdict судді
72
+ */
63
73
  function judgeCached(src, doc) {
64
74
  const key = 'judge-' + sha(JUDGE_MODEL + '\0' + src + '\0' + doc)
65
75
  const hit = cacheGet(key)
@@ -67,7 +77,7 @@ function judgeCached(src, doc) {
67
77
  const user = `SOURCE FILE:\n\`\`\`\n${src.slice(0, 12000)}\n\`\`\`\n\nGENERATED DOC:\n\`\`\`md\n${doc.slice(0, 8000)}\n\`\`\`\n\nReturn the JSON verdict.`
68
78
  const raw = callLlm([{ role: 'system', content: SYSTEM }, { role: 'user', content: user }], JUDGE_MODEL, { timeoutMs: JUDGE_TIMEOUT, temperature: 0 })
69
79
  const a = raw.indexOf('{'), b = raw.lastIndexOf('}')
70
- if (a < 0 || b < 0) throw new Error('no JSON in judge reply: ' + raw.slice(0, 160))
80
+ if (a === -1 || b === -1) throw new Error('no JSON in judge reply: ' + raw.slice(0, 160))
71
81
  const v = JSON.parse(raw.slice(a, b + 1))
72
82
  cacheSet(key, v)
73
83
  return { ...v, cached: false }
@@ -85,11 +95,11 @@ function main() {
85
95
  for (const [i, file] of files.entries()) {
86
96
  const tag = `(${i + 1}/${files.length}) ${file}`
87
97
  let src
88
- try { src = readFileSync(file, 'utf8') } catch (e) { console.error(`[skip] ${tag}: read ${e.message}`); continue }
98
+ try { src = readFileSync(file, 'utf8') } catch (error) { console.error(`[skip] ${tag}: read ${error.message}`); continue }
89
99
 
90
100
  let gen
91
- try { gen = genCached(file, src) } catch (e) { console.error(`[gen-err] ${tag}: ${e.message.slice(0, 120)}`); rows.push({ file, error: 'gen', detail: e.message.slice(0, 200) }); continue }
92
- if (gen.score == null) { console.error(`[unsupported] ${tag}`); rows.push({ file, score: null, unsupported: true }); continue }
101
+ try { gen = genCached(file, src) } catch (error) { console.error(`[gen-err] ${tag}: ${error.message.slice(0, 120)}`); rows.push({ file, error: 'gen', detail: error.message.slice(0, 200) }); continue }
102
+ if (gen.score === null) { console.error(`[unsupported] ${tag}`); rows.push({ file, score: null, unsupported: true }); continue }
93
103
 
94
104
  const passed = gen.score >= THRESHOLD
95
105
  const row = { file, score: gen.score, degraded: gen.degraded, passed, genCached: gen.cached }
@@ -100,7 +110,7 @@ function main() {
100
110
  const v = judgeCached(src, gen.md)
101
111
  row.verdict = v.verdict; row.confidence = v.confidence; row.reason = v.reason; row.offending = v.offending; row.judgeCached = v.cached
102
112
  console.error(` [judge${v.cached ? '*' : ''}] ${v.verdict} (${v.confidence}) — ${(v.reason || '').slice(0, 90)}`)
103
- } catch (e) { row.judgeError = e.message.slice(0, 200); console.error(` [judge-err] ${e.message.slice(0, 120)}`) }
113
+ } catch (error) { row.judgeError = error.message.slice(0, 200); console.error(` [judge-err] ${error.message.slice(0, 120)}`) }
104
114
  }
105
115
  rows.push(row)
106
116
  }
@@ -0,0 +1,72 @@
1
+ /** @see ./docs/docgen-judge.md */
2
+ /**
3
+ * docgen-judge — опціональний семантичний verdict-гейт (spec
4
+ * `docs/specs/2026-06-14-docgen-judge-design.md`).
5
+ *
6
+ * Доповнює детермінований `scoreDoc`: ловить `inaccurate`-доки (твердження, що
7
+ * суперечать джерелу), яких структурно-лексичний скорер не бачить у принципі.
8
+ * Активується АВТОМАТИЧНО, якщо задано `N_CLOUD_MIN_MODEL` (бо суддя потребує
9
+ * сильнішої за генератор хмарної моделі — без неї судити нема чим). Працює лише на
10
+ * доках що ПРОЙШЛИ det-скорер (`score ≥ threshold`) — саме там ховаються
11
+ * false-positives. Scope строго `inaccurate` (вимір показав generic=0%). Без
12
+ * `N_CLOUD_MIN_MODEL` → 0 змін поведінки. Патерн дзеркалить `scripts/coverage-classify`.
13
+ */
14
+ import { env } from 'node:process'
15
+ import { callLlm } from '../../../lib/llm.mjs'
16
+ import { CLOUD_MIN } from '../../../lib/models.mjs'
17
+
18
+ /** Модель-суддя = `N_CLOUD_MIN_MODEL` (хмарний cloud-min tier). */
19
+ export const JUDGE_MODEL = CLOUD_MIN
20
+ /** Гейт активується АВТОМАТИЧНО, коли задано `N_CLOUD_MIN_MODEL` (без нього нема надійного судді). */
21
+ export const JUDGE_ENABLED = Boolean(CLOUD_MIN)
22
+ /** Мін. впевненість, щоб verdict `inaccurate` позначив док як degraded. */
23
+ export const JUDGE_CONFIDENCE = Number(env.N_CURSOR_DOCGEN_JUDGE_THRESHOLD ?? 0.7) || 0.7
24
+
25
+ const JUDGE_SYSTEM = `You are a strict technical-documentation reviewer. You receive a SOURCE file and an auto-generated Markdown DOC describing it. Classify the DOC into exactly one verdict:
26
+ - "accurate": specific to THIS file AND every factual claim is supported by the source.
27
+ - "generic": vague/boilerplate; could describe almost any file of this kind.
28
+ - "inaccurate": contains at least one claim NOT supported by, or contradicted by, the source code (e.g. wrong return behavior, false "no network"/"read-only", invented symbols/fields).
29
+ Prefer "inaccurate" if any claim is wrong. Respond with ONLY a JSON object, no prose:
30
+ {"verdict":"accurate|generic|inaccurate","confidence":0.0-1.0,"reason":"<10-300 chars>"}`
31
+
32
+ const VERDICTS = new Set(['accurate', 'generic', 'inaccurate'])
33
+
34
+ /**
35
+ * Витягує й валідує verdict-JSON із сирої відповіді LLM (як `parseVerdict` у coverage-classify).
36
+ * @param {string} rawText сира текстова відповідь судді
37
+ * @returns {{verdict: string, confidence: number, reason: string}} провалідований verdict
38
+ * @throws {Error} якщо JSON відсутній/невалідний або не відповідає схемі
39
+ */
40
+ export function parseDocVerdict(rawText) {
41
+ const a = rawText.indexOf('{')
42
+ const b = rawText.lastIndexOf('}')
43
+ if (a === -1 || b === -1) throw new Error('judge: no JSON object in response')
44
+ const v = JSON.parse(rawText.slice(a, b + 1))
45
+ if (!VERDICTS.has(v.verdict)) throw new Error(`judge: bad verdict "${v.verdict}"`)
46
+ if (typeof v.confidence !== 'number' || v.confidence < 0 || v.confidence > 1) {
47
+ throw new Error('judge: bad confidence')
48
+ }
49
+ return { verdict: v.verdict, confidence: v.confidence, reason: String(v.reason ?? '').slice(0, 500) }
50
+ }
51
+
52
+ /**
53
+ * Судить згенерований док сильною моделлю проти джерела.
54
+ * @param {string} src вміст вихідного файлу
55
+ * @param {string} doc згенерована документація
56
+ * @param {{model?: string, timeoutMs?: number}} [opts] override моделі/таймауту
57
+ * @returns {{verdict: string, confidence: number, reason: string}} verdict судді
58
+ */
59
+ export function judgeDoc(src, doc, { model = JUDGE_MODEL, timeoutMs = 120_000 } = {}) {
60
+ const user = `SOURCE FILE:\n\`\`\`\n${src.slice(0, 12_000)}\n\`\`\`\n\nGENERATED DOC:\n\`\`\`md\n${doc.slice(0, 8000)}\n\`\`\`\n\nReturn the JSON verdict.`
61
+ const raw = callLlm([{ role: 'system', content: JUDGE_SYSTEM }, { role: 'user', content: user }], model, { timeoutMs, temperature: 0 })
62
+ return parseDocVerdict(raw)
63
+ }
64
+
65
+ /**
66
+ * Чи позначає verdict док як degraded (лише `inaccurate` із достатньою впевненістю).
67
+ * @param {{verdict: string, confidence: number}|null} verdict verdict судді або null
68
+ * @returns {boolean} true якщо док треба вважати degraded через семантичну неточність
69
+ */
70
+ export function judgeFailsDoc(verdict) {
71
+ return verdict !== null && verdict.verdict === 'inaccurate' && verdict.confidence >= JUDGE_CONFIDENCE
72
+ }
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/doc-files/js/docgen-extract.mjs
4
- crc: e790ff64
4
+ crc: a0680e77
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
5
6
  score: 100
6
7
  ---
7
8
 
@@ -9,31 +10,32 @@ docgen:
9
10
 
10
11
  ## Огляд
11
12
 
12
- Файл витягує факти про конфігурацію та структуру проекту. Визначає мову та заголовок файлу. Витягує публічні експорти та імпорти з різних джерел. Витягує внутрішні та локальні символи. Документація перевіряє наявність операцій запису, обробки помилок, повернення хибних значень при невдачах, мережевих викликів, механізмів кешування та пропуск певних шляхів.
13
+ Витягує структурований факт-лист з вмісту файлів, аналізуючи їх залежно від мови. Для Rust витягує модульний опис, публічні експорти, локальні символи та класифікує імпорти й поведінкові маркери. Для JavaScript/TypeScript/MJS витягує опис, експортовані елементи з JSDoc, класифікує імпорти та визначає поведінкові маркери. При аналізі ігноруються директорії: .github, .git, node_modules, base/, ua/, .firebase. Звертається до мережі та кешує дані протягом одного прогону.
13
14
 
14
15
  ## Поведінка
15
16
 
16
- 1. Витягується факт про файл.
17
- 2. Визначається мова файлу.
18
- 3. Витягується заголовок файлу.
19
- 4. Витягуються публічні експорти.
20
- 5. Витягуються імпорти, класифіковані за джерелом.
21
- 6. Витягуються внутрішні символи, імпортовані з внутрішніх модулів.
22
- 7. Витягуються локальні символи, неекспортовані функції.
23
- 8. Визначаються евристичні маркери.
24
- * readOnly: перевірка на наявність операцій запису.
25
- * catchesErrors: перевірка на наявність обробки помилок.
26
- * returnsFalsyOnFail: перевірка на повернення хибних значень при невдачах.
27
- * network: перевірка на наявність мережевих викликів.
28
- * caches: перевірка на наявність механізмів кешування.
29
- * skips: пропуск шляхів .github, .git, node_modules, base/, ua/, .firebase.
17
+ 1. Витягує факт-лист з вмісту файлу.
18
+ 2. Визначає мову файлу за розширенням.
19
+ 3. Якщо мова — Rust, виконує аналіз Rust-коду:
20
+ а. Витягує модульний опис з `//!`.
21
+ б. Визначає публічні експорти (структури, функції, енуми) на основі `pub` префікса або експозиційних атрибутів.
22
+ в. Визначає локальні (приватні) символи, які не є публічними.
23
+ г. Класифікує імпорти (`std`, зовнішні, внутрішні).
24
+ д. Визначає поведінкові маркери (readOnly, network, caches тощо) на основі специфічних Rust-конструкцій.
25
+ 4. Якщо мова JavaScript/TypeScript/MJS, виконує аналіз JS-коду:
26
+ а. Витягує загальний опис файлу з верхнього блоку коментарів.
27
+ б. Витягує експортовані функції та класи разом із їхніми JSDoc-описами.
28
+ в. Класифікує імпорти на стандартні бібліотеки, NPM-пакети та внутрішні модулі.
29
+ г. Визначає локальні (неекспортовані) функції та класи.
30
+ д. Визначає поведінкові маркери (readOnly, network, caches тощо) на основі евристик.
31
+ 5. При аналізі JS-коду, свідомо ігнорує шляхи: .github, .git, node_modules, base/, ua/, .firebase.
32
+ 6. Повертає структуру фактів, що містить метадані про файл.
30
33
 
31
34
  ## Публічний API
32
35
 
33
- extractFacts — код файлу перетворює на список фактів
36
+ extractFacts — витягує факти з вмісту файлу.
34
37
 
35
38
  ## Гарантії поведінки
36
39
 
37
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
38
40
  - Кешує результати в межах одного прогону.
39
41
  - Свідомо пропускає шляхи: `.github`, `.git`, `node_modules`, `base/`, `ua/`, `.firebase`.
@@ -0,0 +1,31 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/doc-files/js/docgen-judge-measure.mjs
4
+ crc: 7f72e520
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # docgen-judge-measure.mjs
10
+
11
+ ## Огляд
12
+
13
+ Файл зчитує список файлів для аналізу, спираючись на конфігурацію `report.json`. Для кожного файлу генерується технічна документація за допомогою локальної моделі. Після генерації документація оцінюється хмарною моделлю. Результати оцінки кешуються у межах прогону, а потім збираються у звіт. Звіт зберігається у `report.json` та виводиться у консоль.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Зчитує список файлів для аналізу з командного рядка.
18
+ 2. Для кожного файлу зчитує його вміст.
19
+ 3. Генерує документацію для файлу, використовуючи локальну модель. Результат кешується за хешем вмісту джерела.
20
+ 4. Якщо генерація документації неможлива або не пройшла встановлений поріг якості, фіксує це як помилку або непідтримуваний файл.
21
+ 5. Якщо документація пройшла поріг якості, вона передається для оцінки.
22
+ 6. Оцінює згенеровану документацію за допомогою потужної хмарної моделі. Результат оцінки кешується за хешем вмісту джерела та документації.
23
+ 7. Якщо оцінка неможлива, фіксує помилку оцінки.
24
+ 8. Збирає результати для всіх файлів.
25
+ 9. Обчислює звіт, включаючи загальну кількість файлів, кількість успішно згенерованих та оцінених, а також відсоток хибнопозитивних результатів (false-positive rate).
26
+ 10. Зберігає повний звіт у файл `report.json` у каталозі кешу.
27
+ 11. Виводить консольний звіт з ключовими показниками.
28
+
29
+ ## Гарантії поведінки
30
+
31
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,35 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/doc-files/js/docgen-judge.mjs
4
+ crc: c6ab093a
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # docgen-judge.mjs
10
+
11
+ ## Огляд
12
+
13
+ Модуль оцінює згенеровану документацію, порівнюючи її з вихідним файлом за допомогою великої мовної моделі. Це дозволяє визначити відповідність та якість документації. Модуль надає функції для запуску оцінки (`judgeDoc`, `judgeFailsDoc`), отримання результатів (`parseDocVerdict`), визначення моделі (`JUDGE_MODEL`), перевірки статусу оцінювача (`JUDGE_ENABLED`) та отримання рівня впевненості (`JUDGE_CONFIDENCE`).
14
+
15
+ ## Поведінка
16
+
17
+ JUDGE_MODEL — Вказує модель, яку використовує суддя для оцінки документації.
18
+ JUDGE_ENABLED — Позначає, чи активна функціональність судді.
19
+ JUDGE_CONFIDENCE — Визначає мінімальний рівень впевненості, необхідний для позначення документації як деградованої.
20
+ parseDocVerdict — Витягує та валідує об'єкт з оцінкою документації з сирого текстового виводу судді.
21
+ judgeDoc — Виконує оцінку згенерованої документації проти вмісту вихідного файлу за допомогою великої мовної моделі.
22
+ judgeFailsDoc — Визначає, чи слід вважати документацію деградованою на основі оцінки судді.
23
+
24
+ ## Публічний API
25
+
26
+ JUDGE_MODEL — Визначає модель-суддю як `N_CLOUD_MIN_MODEL` (хмарний мінімальний рівень).
27
+ JUDGE_ENABLED — Автоматично вмикає механізм судді, якщо обрано `N_CLOUD_MIN_MODEL`.
28
+ JUDGE_CONFIDENCE — Встановлює мінімальний рівень впевненості, необхідний для позначення документа як погіршеного (degraded) за неточним вердиктом.
29
+ parseDocVerdict — Витягує та перевіряє структуру вердикту у форматі JSON із сирих даних від великої мовної моделі.
30
+ judgeDoc — Оцінює згенерований документ потужною моделлю, порівнюючи його з вихідним джерелом.
31
+ judgeFailsDoc — Визначає, чи повинен документ бути позначений як погіршений (degraded) на підставі вердикту, якщо він неточний і має достатню впевненість.
32
+
33
+ ## Гарантії поведінки
34
+
35
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/doc-files/js/docgen-prompts.mjs
4
- crc: 72ac304f
4
+ crc: 28c9e818
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
5
6
  score: 100
6
7
  ---
7
8
 
@@ -9,31 +10,29 @@ docgen:
9
10
 
10
11
  ## Огляд
11
12
 
12
- Файл надає інструкції для створення лаконічної поведінкової документації українською мовою. Він містить інструменти для формування, перевірки та виправлення тексту документації, а також для визначення гарантій щодо роботи файлу
13
+ Генерує технічну документацію на основі коду. Створює компоненти документації, такі як огляд (`overviewMessages`), повідомлення для секцій (`sectionMessages`), повідомлення для критики (`criticMessages`), повідомлення для уточнення (`refineMessages`) та гарантії, отримані з маркерів (`guaranteesFromMarkers`), застосовуючи заданий стиль (`STYLE`).
13
14
 
14
15
  ## Поведінка
15
16
 
16
- Поведінка:
17
- - STYLE: Надає інструкцію для генерації лаконічної поведінкової документації українською мовою.
18
- - sectionMessages: Генерує секційні промпти для різних частин документації.
19
- - overviewMessages: Формує текст для узагальнення ролі та призначення файлу.
20
- - criticMessages: Створює запити для перевірки чорновиків документації на наявність дефектів.
21
- - refineMessages: Переписує та виправляє чорновки документації відповідно до критики.
22
- - guaranteesFromMarkers: Формує список гарантій щодо поведінки файлу на основі маркерів.
23
- - oneShotMessages: Створює універсальний запит для генерації повної документації.
17
+ STYLE: Пише лаконічну поведінкову документацію до коду українською мовою, використовуючи чистий Markdown.
18
+ sectionMessages: Генерує набір промптів для різних секцій документації, використовуючи метадані файлу та вміст коду.
19
+ overviewMessages: Створює узагальнений огляд файлу, базуючись на вже згенерованій секції «Поведінка».
20
+ criticMessages: Перевіряє чорнетку секції на відповідність технічним критеріям, виявляючи дефекти.
21
+ refineMessages: Переписує чорнетку секції, виправляючи дефекти, виявлені критиком.
22
+ guaranteesFromMarkers: Формує список гарантій поведінки, виходячи з машинно-виведених маркерів файлу.
23
+ oneShotMessages: Генерує базовий промпт для генерації повної документації за один виклик.
24
24
 
25
25
  ## Публічний API
26
26
 
27
- STYLE — Формулює стиль файлу.
28
- sectionMessages — Збирає повідомлення з мінімальним контекстом для кожної секції.
29
- overviewMessages — Надає узагальнення поведінки файлу.
30
- criticMessages — Виявляє дефекти в чорнетці секції.
31
- refineMessages — Виправляє чорнетку, усуваючи виявлені дефекти.
32
- guaranteesFromMarkers — Створює детермінований шаблон гарантій поведінки з фактів.
27
+ STYLE — Визначає загальний стиль та намір файлу.
28
+ sectionMessages — Містить мінімально необхідний контекст для кожної секції.
29
+ overviewMessages — Генерує узагальнений огляд на основі вже визначеної поведінки.
30
+ criticMessages — Виявляє конкретні недоліки у чорновому тексті секції, повертаючи список проблем або відсутність проблем.
31
+ refineMessages — Переписує чорновий текст, виправляючи виявлені недоліки.
32
+ guaranteesFromMarkers — Створює чіткий шаблон секції «Гарантії поведінки» на основі фактів.
33
+ oneShotMessages — Надає приклади для порівняння.
33
34
 
34
35
  ## Гарантії поведінки
35
36
 
36
- - Read-only: файл не виконує операцій запису у файлову систему.
37
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
37
+ - Read-only: не виконує операцій запису (ФС/БД).
38
38
  - Кешує результати в межах одного прогону.
39
- - Не звертається до мережі.
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Адаптер агрегатора `n-cursor lint` для правила doc-files.
2
+ * Адаптер агрегатора `n-cursor lint` для правила doc-files (opportunistic LLM-fix
3
+ * tier, спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md).
3
4
  *
4
5
  * Quick-фаза отримує список змінених файлів і мапить їх у пари в **обидва** боки:
5
6
  * - змінене **джерело** (`.js/.mjs/.ts/.vue/.py/.rs`) → перевірка його доки `<dir>/docs/<stem>.md`;
@@ -7,8 +8,11 @@
7
8
  * (той самий stem у каталозі над текою `docs`).
8
9
  * Ci-фаза (files === undefined) проганяє повний скан дерева.
9
10
  *
10
- * Порушення — `missing` ∪ `crc-mismatch` (детермінований CRC-детект, 0 LLM-токенів);
11
- * degraded не блокує. Exit 1 є stale; 0 — все свіже (конвенція агрегатора).
11
+ * Детект — `missing` ∪ `crc-mismatch` (детермінований CRC, 0 LLM-токенів); degraded не блокує.
12
+ * Поведінка за осями (правило має `meta.json: llmFix:true`):
13
+ * - `readOnly` (CI/hook): **лише детект** — нуль мутацій/LLM, exit 1 на stale (детермінований гейт);
14
+ * - fix-by-default + omlx **піднято**: opportunistic-генерація stale-доків → re-detect → 0 якщо полагоджено;
15
+ * - fix-by-default + omlx **недоступно**: fix пропущено (повідомлення) + exit 1 — гейт тримається, без false-green.
12
16
  */
13
17
  import { join, dirname, basename, extname } from 'node:path'
14
18
  import { existsSync, readdirSync } from 'node:fs'
@@ -79,18 +83,34 @@ function reportStale(stale) {
79
83
  }
80
84
 
81
85
  /**
82
- * Крок агрегатора lint для doc-files.
86
+ * Збирає застарілі (missing crc-mismatch) описи у scope кроку.
83
87
  * @param {string[] | undefined} files quick: лише ці файли; undefined: весь репозиторій
84
- * @param {string} [cwd] корінь репо
85
- * @returns {Promise<number>} 0 OK, 1 — є застарілі доки
88
+ * @param {string} cwd корінь репо
89
+ * @returns {Array<{sourcePath:string, docPath:string, reason:string|null}>} stale-описи (готові як targets генерації)
86
90
  */
87
- export function lint(files, cwd = process.cwd()) {
88
- if (files === undefined) {
89
- const stale = scanForDocFiles(cwd).filter(f => f.stale)
90
- return Promise.resolve(reportStale(stale))
91
- }
91
+ function collectStale(files, cwd) {
92
+ if (files === undefined) return scanForDocFiles(cwd).filter(f => f.stale)
92
93
  const sources = sourcesFromChanged(files, cwd)
93
- if (sources.length === 0) return Promise.resolve(0)
94
- const stale = sources.map(src => describeFile(cwd, src)).filter(f => f.stale)
95
- return Promise.resolve(reportStale(stale))
94
+ return sources.map(src => describeFile(cwd, src)).filter(f => f.stale)
95
+ }
96
+
97
+ /**
98
+ * Крок агрегатора lint для doc-files (opportunistic LLM-fix tier).
99
+ * @param {string[] | undefined} files quick: лише ці файли; undefined: весь репозиторій
100
+ * @param {string} [cwd] корінь репо
101
+ * @param {{ readOnly?: boolean }} [opts] readOnly: лише детект (CI/hook), без мутацій/LLM
102
+ * @returns {Promise<number>} 0 — доки свіжі; 1 — є застарілі (детект, fix пропущено чи помилка генерації)
103
+ */
104
+ export async function lint(files, cwd = process.cwd(), { readOnly = false } = {}) {
105
+ const stale = collectStale(files, cwd)
106
+ if (stale.length === 0) return 0
107
+ if (readOnly) return reportStale(stale)
108
+
109
+ // fix-by-default: opportunistic-генерація через спільне ядро (preflight omlx →
110
+ // батч із circuit-breaker'ом). omlx недоступний → runGenerationBatch друкує причину
111
+ // й повертає !=0; ми re-detect'имо й через reportStale віддаємо exit 1 (гейт тримається).
112
+ process.stdout.write(`ℹ️ doc-files: ${stale.length} застарілих — пробую авто-фікс (omlx)…\n`)
113
+ const { runGenerationBatch } = await import('./docgen-files-batch.mjs')
114
+ await runGenerationBatch(stale, cwd, { headline: `📋 doc-files: генерація ${stale.length} файл(ів)` })
115
+ return reportStale(collectStale(files, cwd))
96
116
  }
@@ -1 +1 @@
1
- { "auto": "завжди", "lint": "per-file" }
1
+ { "auto": "завжди", "lint": "per-file", "llmFix": true }
@@ -11,6 +11,10 @@
11
11
  "enum": ["per-file", "full"],
12
12
  "description": "Scope lint-кроку: per-file (декомпозиція по змінених файлах, дельта vs origin) або full (нероздільно крос-файловий, лише `lint --full`)."
13
13
  },
14
+ "llmFix": {
15
+ "type": "boolean",
16
+ "description": "Opt-in opportunistic LLM-fix: у fix-by-default (не --read-only) lint-крок намагається виправити порушення локальною моделлю (omlx), якщо її піднято; інакше пропускає й лишає exit 1. Лише для контент-правил (doc-files, cspell) — НЕ для логічних лінтерів (зміна коду може змінити поведінку). Деталі: docs/specs/2026-06-15-opportunistic-llm-fix-tier.md."
17
+ },
14
18
  "auto": {
15
19
  "description": "Умова автоактивації правила: \"завжди\", масив id правил-залежностей, glob, або іменований предикат.",
16
20
  "oneOf": [