@nitra/cursor 12.2.0 → 12.3.1

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
+ ## [12.3.1] - 2026-06-20
4
+
5
+ ### Changed
6
+
7
+ - npm-module конформність: module-level JSDoc → pointer (`/** @see ./docs/… */`) у docgen-crc/extract-anchors/files-batch/judge-measure, doc-files/lint, lint/orchestrate, doc-aggregate/docgen-ignore; doc-CRC перештамповано; eslint --fix-стан прийнято як канон
8
+
9
+ ## [12.3.0] - 2026-06-19
10
+
11
+ ### Added
12
+
13
+ - lint --full: резюме викликів моделей у stdout (локальна / cloud-min / cloud-avg) через reportRunStats/summarizeCalls
14
+
3
15
  ## [12.2.0] - 2026-06-19
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.2.0",
3
+ "version": "12.3.1",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -68,4 +68,4 @@
68
68
  "skills": "skills",
69
69
  "extensions": ".pi-template/extensions"
70
70
  }
71
- }
71
+ }
@@ -1,34 +1,4 @@
1
- /**
2
- * CRC32 джерела + YAML-frontmatter файлової документації.
3
- *
4
- * Кожна файлова дока несе у frontmatter контрольну суму байтів джерела на момент
5
- * генерації. Це детермінований маркер застарілості: `crc32(поточне джерело)` звіряється
6
- * з `crc` у доці — розбіжність (або відсутня дока) означає, що дока відстала від коду.
7
- * CRC не залежить від git-стану (rebase, незакомічене, гілки), тож придатний і для
8
- * per-edit hook (бачить лише змінений файл), і для повного сканування.
9
- *
10
- * Degraded-маркер (ADR 260610-2228): якщо локальний конвеєр не дотягнув до порогу
11
- * якості, дока все одно пишеться, а frontmatter додатково несе `score` (det-оцінка)
12
- * та `issues` (коди проблем). CRC при цьому свіжий — Stop-гейт не блокує задачі через
13
- * слабкість моделі; борг видимий через `check --degraded` і автоматично доретраюється
14
- * наступним `gen` (рівно один раз на версію джерела — далі `retried: true` у frontmatter).
15
- *
16
- * Frontmatter — єдиний дозволений виняток із правила «чистий Markdown без HTML»:
17
- * це машинні метадані, не контент. Формат:
18
- *
19
- * ---
20
- * docgen:
21
- * source: src/lib/foo.js
22
- * crc: a3f1c9e0
23
- * model: omlx/gemma-4-e4b-it-OptiQ-4bit
24
- * score: 55
25
- * issues: short-behavior,internal-name:bar
26
- * ---
27
- *
28
- * `model` — повний id моделі-генератора (як повертає resolveModel, із префіксом
29
- * провайдера). Пасивна метадата: маркер «віку» доки за моделлю на додачу до CRC
30
- * джерела. На staleness НЕ впливає — звіряється лише `crc`.
31
- */
1
+ /** @see ./docs/docgen-crc.md */
32
2
  import { existsSync, readFileSync } from 'node:fs'
33
3
  import { basename, extname } from 'node:path'
34
4
  import { crc32 as zlibCrc32 } from 'node:zlib'
@@ -173,6 +143,9 @@ export function buildDocFrontmatter(source, crc, quality = null, model = null) {
173
143
  */
174
144
  const LEADING_H1_RE = /^#[^\n]*\n+/u
175
145
 
146
+ /**
147
+ *
148
+ */
176
149
  export function stampDoc(md, source, crc, quality = null, model = null) {
177
150
  const { body } = parseDocFrontmatter(md)
178
151
  const cleanBody = body.replace(LEADING_NEWLINES_RE, '').replace(LEADING_H1_RE, '')
@@ -1,18 +1,4 @@
1
- /**
2
- * E1 (Fact-anchoring): детермінований витяг «анкорів» — конкретних фрагментів
3
- * з коду, які LLM зобовʼязана згадати в документації, щоб не зісковзнути на
4
- * generic-фрази.
5
- *
6
- * Категорії анкорів:
7
- * - urls : усі https?://… у вихідному коді
8
- * - magicStrings : export const X = '…' з непорожнім value (≤120 символів)
9
- * - errorMarkers : суфікси повідомлень про помилки виду `(rule.mdc)`
10
- * - configRefs : посилання на .json-конфіги проєкту (.n-cursor.json, …)
11
- * - examples : ```…```-блоки у file-header JSDoc (першому коментарі файла)
12
- *
13
- * Всі регулярки — на сирому src без AST: дешево, безпечно, без false-positive
14
- * критичної ваги (надмір — менша проблема, ніж пропуск).
15
- */
1
+ /** @see ./docs/docgen-extract-anchors.md */
16
2
 
17
3
  const URL_RE = /https?:\/\/[^\s'"`)<>]+/g
18
4
  // Після обрізання template-частини URL має лишитися host (R10).
@@ -1,15 +1,4 @@
1
- /**
2
- * JS-оркестрація генерації файлових док (local-only, ADR 260610-2228).
3
- *
4
- * Уся черга/батчинг/CRC-штамп живуть тут, а не в контексті моделі — тому
5
- * масовий перший прогін на сотні файлів не «заморює» агента. Конвеєр суто
6
- * локальний: жодних cloud-ескалацій; якщо det-score нижче порогу — дока все
7
- * одно пишеться з degraded-маркером (`score`/`issues` у frontmatter), а наступний
8
- * `gen` автоматично доретраює такі доки (один раз на версію джерела — далі `retried:true`).
9
- *
10
- * Перед масовим прогоном — health-check omlx: memory-guard зайнятої 8GB машини
11
- * означає «відклади прогін», а не сотні хибних «✗» у звіті.
12
- */
1
+ /** @see ./docs/docgen-files-batch.md */
13
2
  import { readFileSync, readdirSync, mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs'
14
3
  import { basename, dirname, join, relative } from 'node:path'
15
4
 
@@ -1,21 +1,4 @@
1
- #!/usr/bin/env node
2
- /**
3
- * docgen-judge-measure.mjs — Q4 офлайн-вимірювач (spec 2026-06-14-docgen-judge-design).
4
- *
5
- * Міряє false-positive rate детермінованого `scoreDoc`: серед доків, що ПРОЙШЛИ
6
- * (score ≥ threshold), який % сильна хмарна модель-суддя класифікує як
7
- * `generic`/`inaccurate`. Це число вирішує, чи будувати рантайм-judge-гейт.
8
- *
9
- * Генерація: локальна (N_LOCAL_MIN_MODEL, omlx/* → прямий HTTP) — реальний пайплайн.
10
- * Суддя: openai-codex/gpt-5.4-mini (сильніша хмара, ніж генератор — інакше вимір беззмістовний).
11
- * Обидва — через існуючий `../../../lib/llm.mjs callLlm` (маршрутизація за префіксом).
12
- *
13
- * Кеш на диску (за хешем контенту) → повторні прогони не регенерують і не пересуджують.
14
- *
15
- * Usage:
16
- * node docgen-judge-measure.mjs <file1> <file2> ...
17
- * MEASURE_CACHE=/tmp/x N_CLOUD_MIN_MODEL=openai-codex/gpt-5.4 node docgen-judge-measure.mjs ...
18
- */
1
+ /** @see ./docs/docgen-judge-measure.md */
19
2
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
20
3
  import { createHash } from 'node:crypto'
21
4
  import { join } from 'node:path'
@@ -39,10 +22,16 @@ Prefer "inaccurate" over "generic" if any claim is wrong. Respond with ONLY a JS
39
22
 
40
23
  const sha = s => createHash('sha256').update(s).digest('hex').slice(0, 16)
41
24
 
25
+ /**
26
+ *
27
+ */
42
28
  function cacheGet(key) {
43
29
  const p = join(CACHE_DIR, key + '.json')
44
30
  return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : null
45
31
  }
32
+ /**
33
+ *
34
+ */
46
35
  function cacheSet(key, val) {
47
36
  if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true })
48
37
  writeFileSync(join(CACHE_DIR, key + '.json'), JSON.stringify(val))
@@ -83,6 +72,9 @@ function judgeCached(src, doc) {
83
72
  return { ...v, cached: false }
84
73
  }
85
74
 
75
+ /**
76
+ *
77
+ */
86
78
  function main() {
87
79
  const files = process.argv.slice(2).filter(f => !f.startsWith('--'))
88
80
  if (!files.length) {
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-crc.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-crc.mjs
5
5
  docgen:
6
- crc: 6baf999b
6
+ crc: cca0a79f
7
7
  ---
8
8
 
9
9
  Детермінований маркер актуальності файлових док: контрольна сума джерела у frontmatter плюс опційний degraded-маркер якості. Єдине джерело правди про «дока свіжа/застаріла/неякісна» для генерації, перевірок і хуків.
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-extract-anchors.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-extract-anchors.mjs
5
5
  docgen:
6
- crc: 49749e9c
6
+ crc: b675f159
7
7
  score: 80
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-files-batch.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-files-batch.mjs
5
5
  docgen:
6
- crc: 975d6cfa
6
+ crc: 18c96a58
7
7
  score: 95
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-judge-measure.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-judge-measure.mjs
5
5
  docgen:
6
- crc: 7f72e520
6
+ crc: e6e19732
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: lint.mjs
4
4
  resource: npm/rules/doc-files/js/lint.mjs
5
5
  docgen:
6
- crc: fcdc1c2a
6
+ crc: ca055f8f
7
7
  score: 100
8
8
  ---
9
9
 
@@ -1,19 +1,4 @@
1
- /**
2
- * Адаптер агрегатора `n-cursor lint` для правила doc-files (opportunistic LLM-fix
3
- * tier, спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md).
4
- *
5
- * Quick-фаза отримує список змінених файлів і мапить їх у пари в **обидва** боки:
6
- * - змінене **джерело** (`.js/.mjs/.ts/.vue/.py/.rs`) → перевірка його доки `<dir>/docs/<stem>.md`;
7
- * - змінена/видалена **дока** (`<dir>/docs/<stem>.md`) → перевірка відповідного джерела
8
- * (той самий stem у каталозі над текою `docs`).
9
- * Ci-фаза (files === undefined) проганяє повний скан дерева.
10
- *
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.
16
- */
1
+ /** @see ./docs/lint.md */
17
2
  import { join, dirname, basename, extname } from 'node:path'
18
3
  import { existsSync, readdirSync } from 'node:fs'
19
4
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: orchestrate.mjs
4
4
  resource: npm/rules/lint/js/orchestrate.mjs
5
5
  docgen:
6
- crc: f4eb439d
6
+ crc: 0ab5b22c
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
@@ -16,7 +16,7 @@ docgen:
16
16
  selectLintRules вибирає і сортує ідентифікатори правил на основі їхнього обсягу дії (`per-file` або `full`) та прапорця `--full`.
17
17
  runLint запускає оркестрацію лінтування: або виконує перевірку конформності для заданих правил, або ітерує по алфавітно відсортованих правилах (`runPerFileRules`), запускаючи лінтер для змінених файлів (за замовчуванням), або виконує перевірку конформності всього репозиторію при використанні прапорця `--full`.
18
18
  **Fail-fast — лише в `--read-only`** (CI/детект): перший ненульовий код спиняє. У fix-режимі (default) ненульовий код per-file правила НЕ спиняє — проганяються всі правила й виконується крок виправлення (конформність-драбина), а повертається найгірший код.
19
- У режимі `--full` без `--read-only` після конформність-фази (`runFullConformancePhase`) викликається escalation-аналітика (`analyze-escalation.mjs`): фіксує зсув escalation-логу до фази, після — аналізує записи саме цього прогону. Аналіз не впливає на exit-код lint.
19
+ У режимі `--full` без `--read-only` після конформність-фази (`runFullConformancePhase`) друкується резюме викликів моделей за прогін (`reportRunStats`: локальна / cloud-min / cloud-avg) і викликається escalation-аналітика (`analyze-escalation.mjs`): фіксує зсув escalation-логу до фази, після — аналізує записи саме цього прогону. Жодне з цього не впливає на exit-код lint.
20
20
 
21
21
  ## Публічний API
22
22
 
@@ -1,18 +1,4 @@
1
- /**
2
- * Оркестратор `n-cursor lint` — дві ортогональні осі (spec 2026-06-14-lint-rule-consolidation
3
- * + компаньйон 2026-06-14-lint-orchestrator-fix-readonly-unification):
4
- * - **scope** (`--full`): default = дельта vs origin (лише `per-file` правила);
5
- * `--full` = весь репо (`per-file` ∪ `full` правила);
6
- * - **behavior** (`--read-only`): default = fix; `--read-only` = лише детект без мутацій.
7
- *
8
- * Data-driven: сканує `rules/<id>/meta.json` за полем `lint` (`per-file`|`full`),
9
- * викликає `rules/<id>/js/lint.mjs` → `lint(files, cwd, { readOnly })`:
10
- * - default scope: `files` = змінені відносно origin (`collectChangedFilesSince`);
11
- * - `--full`: `files = undefined` — весь проєкт.
12
- * Порядок правил — алфавітний. Fail-fast **лише в `--read-only`** (CI/детект): перший
13
- * ненульовий код спиняє. У fix-режимі (default) ненульовий код НЕ спиняє — проганяємо всі
14
- * правила й доходимо до кроку виправлення (конформність-драбина), повертаючи найгірший код.
15
- */
1
+ /** @see ./docs/orchestrate.md */
16
2
  import { existsSync, readdirSync } from 'node:fs'
17
3
  import { dirname, join } from 'node:path'
18
4
  import { fileURLToPath } from 'node:url'
@@ -122,10 +108,15 @@ async function runPerFileRules(ids, ctx) {
122
108
  * @returns {Promise<number>} код конформності
123
109
  */
124
110
  async function runFullConformancePhase(cwd, readOnly, log) {
125
- const { escalationLogSize, maybeAnalyzeEscalation } = await import('../../../scripts/lib/fix/analyze-escalation.mjs')
111
+ const { escalationLogSize, maybeAnalyzeEscalation, reportRunStats } = await import(
112
+ '../../../scripts/lib/fix/analyze-escalation.mjs'
113
+ )
126
114
  const escOffset = readOnly ? 0 : escalationLogSize()
127
115
  const conformanceCode = await runConformance(cwd, readOnly, log)
128
- if (!readOnly) maybeAnalyzeEscalation(cwd, escOffset, log)
116
+ if (!readOnly) {
117
+ reportRunStats(escOffset, log) // резюме викликів моделей (локальна / cloud-min / cloud-avg)
118
+ maybeAnalyzeEscalation(cwd, escOffset, log)
119
+ }
129
120
  return conformanceCode
130
121
  }
131
122
 
@@ -14,6 +14,7 @@
14
14
  * Гейт: валідні слова після дописування у словник зникають; нерозкласифіковані та
15
15
  * typo лишаються → cspell повертає !=0 → exit 1 (людина доправляє одруки вручну).
16
16
  */
17
+ import { env } from 'node:process'
17
18
  import { spawnSync } from 'node:child_process'
18
19
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
19
20
  import { join } from 'node:path'
@@ -27,7 +28,7 @@ const UNKNOWN_WORD_RE = /Unknown word \(([^)]+)\)/u
27
28
  const MAX_CLASSIFY_WORDS = 80
28
29
 
29
30
  /** Локальна fix-модель (рішення: єдиний knob `N_LOCAL_MIN_MODEL`). */
30
- const fixModel = () => process.env.N_LOCAL_MIN_MODEL || ''
31
+ const fixModel = () => env.N_LOCAL_MIN_MODEL || ''
31
32
 
32
33
  /**
33
34
  * Запускає `cspell .` із захопленням виводу.
@@ -3,24 +3,30 @@ type: JS Module
3
3
  title: cspell-fix.mjs
4
4
  resource: npm/rules/text/lint/cspell-fix.mjs
5
5
  docgen:
6
- crc: b5585e0f
6
+ crc: 7b40e8f9
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 95
9
+ issues: anchor-miss:meta.json,judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
9
11
  ---
10
12
 
11
- Модуль інтегрує `cspell` у ланцюжок `lint-text` для виявлення орфографічних помилок. У режимі з можливістю виправлення, процес відбувається шляхом: детект (захоплення виводу) $\rightarrow$ групування знахідок по файлах $\rightarrow$ per-file `omlx-фікс` справжніх помилок (`llmLintFix`) $\rightarrow$ re-detect. У read-only режимі виконується лише детект, що гарантує нуль мутацій. Валідні терміни omlx залишаються, їх ловить повторний `cspell` (далі — у словник `@nitra/cspell-dict`).
13
+ ## Огляд
14
+
15
+ Цей модуль інтегрує cspell у ланцюжок lint-text для класифікації невідомих слів згідно зі схемою omlx. Він витягує невідомі слова, класифікує їх за допомогою LLM (detect $\rightarrow$ omlx-класифікація) та автоматично дописує валідні терміни до словника `.cspell.json`. Нерозкласифіковані та ймовірні одруки залишаються для ручного рев'ю. Процес є read-only, оскільки він лише класифікує знахідки, а не переписує файли.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- groupFindingsByFile групує вивід `cspell` за файлами, виділяючи знахідки.
16
- runCspellText запускає `cspell` для виявлення орфографічних помилок, а при вимкненому режимі читання виконує автоматичне виправлення помилок за допомогою зовнішнього інструменту для файлів, що містять справжні помилки, і повторно перевіряє файли.
19
+ unknownWords витягує унікальні невідомі слова з виводу cspell.
20
+ appendWordsToDict дописує класифіковані валідні слова до файлу .cspell.json, оновлюючи словник.
21
+ runCspellText запускає cspell, класифікує знахідки за допомогою LLM (якщо увімкнено) та повторно перевіряє код, повертаючи 0 при чистоті або 1 при знахідках.
17
22
 
18
23
  ## Публічний API
19
24
 
20
- groupFindingsByFileзбирає знайдені помилки cspell у групи за файлами.
21
- runCspellTextвиконує перевірку тексту за правилами cspell, застосовуючи автоматичне виправлення від omlx.
25
+ unknownWordsЗбирає унікальні слова, які не були знайдені у словнику cspell.
26
+ appendWordsToDictДодає зібрані слова до файлу `.cspell.json#words` у відсортованому та унікальному вигляді для перегляду у Git.
27
+ runCspellText — Виконує перевірку тексту за допомогою cspell, класифікуючи слова та оновлюючи словник за новою схемою.
22
28
 
23
29
  ## Гарантії поведінки
24
30
 
25
- - Read-only: файл не виконує операцій запису у файлову систему.
26
- - Не звертається до мережі.
31
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
32
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
@@ -3,26 +3,27 @@ type: JS Module
3
3
  title: normalize-cli.mjs
4
4
  resource: npm/scripts/lib/adr/normalize-cli.mjs
5
5
  docgen:
6
- crc: ce2f13af
6
+ crc: 63a18347
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 90
9
+ judgeModel: openai-codex/gpt-5.4-mini
9
10
  ---
10
11
 
11
- Цей файл є CLI-обгорткою для локального ADR-нормалізатора (`n-cursor adr-normalize-local`). Він використовується скриптом `.claude/hooks/normalize-decisions.sh` як локальний бекенд для обробки батчу чернеток та списку чистих ADR. Обгортка зчитує шляхи до чернеток та параметри з аргументів командного рядка та змінних середовища, а потім проганяє `normalizePipeline`. Результатом роботи є вивід JSON-контракту з операціями у stdout, який парситься зовнішнім скриптом. Прогрес відображається у stderr.
12
+ ## Огляд
13
+
14
+ Цей файл є CLI-обгорткою для локального нормалізатора ADR. Він зчитує шляхи до чернеток (`--batch <file>`) та список чистих ADR (`--clean <file>`), використовуючи директорію ADR (`--adr-dir <dir>`) для резолву шляхів. Обгортка запускає `normalizePipeline`, яка генерує JSON-контракт у форматі `{ "operations": [...] }` та виводить його у stdout для подальшого парсингу bash-скриптом. Прогрес та помилки записуються у stderr. Поведінка нормалізатора може бути змінена через змінні середовища: `ADR_NORMALIZE_ALLOW_CLOUD` контролює можливість хмарної ескалації, а `ADR_NORMALIZE_VOTES` визначає кількість голосів self-consistency для чистих ADR.
12
15
 
13
16
  ## Поведінка
14
17
 
15
- 1. `runAdrNormalizeLocalCli` парсить вхідні аргументи для визначення шляху до чернеток батчу, списку чистих ADR та каталогу ADR.
16
- 2. Якщо не надано файл батчу, `runAdrNormalizeLocalCli` виводить повідомлення про використання та завершує роботу з кодом помилки.
17
- 3. `runAdrNormalizeLocalCli` зчитує шляхи до чернеток батчу з вказаного файлу.
18
- 4. Для кожної чернетки `runAdrNormalizeLocalCli` зчитує вміст файлу, резолвячи шлях відносно каталогу ADR, і створює об'єкт чернетки.
19
- 5. `runAdrNormalizeLocalCli` зчитує список назв чистих ADR з файлу, якщо він наданий.
20
- 6. `runAdrNormalizeLocalCli` зчитує значення змінних середовища `ADR_NORMALIZE_ALLOW_CLOUD` та `ADR_NORMALIZE_VOTES` для визначення параметрів нормалізації.
21
- 7. `runAdrNormalizeLocalCli` викликає логіку нормалізації, передаючи чернетки, список чистих ADR та параметри, при цьому прогрес виводиться у stderr.
22
- 8. `runAdrNormalizeLocalCli` виводить кількість операцій та статистику нормалізації у stderr.
23
- 9. `runAdrNormalizeLocalCli` виводить деталі рішень нормалізації у stderr.
24
- 10. `runAdrNormalizeLocalCli` друкує JSON-об'єкт, що містить операції, у stdout.
25
- 11. `runAdrNormalizeLocalCli` завершує роботу з кодом успіху.
18
+ 1. Викликати runAdrNormalizeLocalCli.
19
+ 2. Зчитувати список шляхів до чернеток батчу з файлу, вказаного через аргумент `--batch`.
20
+ 3. Зчитувати список імен чистих ADR (кандидатів до злиття) з файлу, вказаного через аргумент `--clean`, якщо він наданий.
21
+ 4. Визначати директорію ADR, використовуючи аргумент `--adr-dir` або за замовчуванням `cwd/docs/adr`.
22
+ 5. Зчитувати вміст кожної чернетки батчу, резолвя шляхи відносно `--adr-dir`.
23
+ 6. Зчитувати значення змінних середовища `ADR_NORMALIZE_ALLOW_CLOUD` та `ADR_NORMALIZE_VOTES`.
24
+ 7. Викликати внутрішній механізм нормалізації, передаючи зібрані чернетки, список чистих ADR, та конфігурацію, отриману з змінних середовища.
25
+ 8. Виводити інформацію про прогрес та статистику в stderr.
26
+ 9. Виводити JSON-об'єкт, що містить операції, у stdout.
26
27
 
27
28
  ## Публічний API
28
29
 
@@ -30,5 +31,4 @@ runAdrNormalizeLocalCli — запускає субкоманду, виводя
30
31
 
31
32
  ## Гарантії поведінки
32
33
 
33
- - Read-only: файл не виконує операцій запису у файлову систему.
34
- - Не звертається до мережі.
34
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -16,6 +16,7 @@
16
16
  * ADR_NORMALIZE_ALLOW_CLOUD=1 дозволити хмарну ескалацію tier-каскаду (default off)
17
17
  * ADR_NORMALIZE_VOTES=N голосів self-consistency для clean-ребер (default 2)
18
18
  */
19
+ import { env } from 'node:process'
19
20
  import { readFileSync } from 'node:fs'
20
21
  import { basename, isAbsolute, join } from 'node:path'
21
22
  import { normalizePipeline } from './normalize-pipeline.mjs'
@@ -57,8 +58,8 @@ export function runAdrNormalizeLocalCli(argv) {
57
58
  })
58
59
  const cleanList = args.clean ? readLines(args.clean).map((c) => basename(c)) : []
59
60
 
60
- const allowCloud = process.env.ADR_NORMALIZE_ALLOW_CLOUD === '1'
61
- const votes = Number(process.env.ADR_NORMALIZE_VOTES) || 2
61
+ const allowCloud = env.ADR_NORMALIZE_ALLOW_CLOUD === '1'
62
+ const votes = Number(env.ADR_NORMALIZE_VOTES) || 2
62
63
 
63
64
  const { operations, stats, trace } = normalizePipeline(drafts, cleanList, {
64
65
  allowCloud,
@@ -107,6 +107,41 @@ export function readEscalationRecords(path, sinceOffset = 0) {
107
107
  return out
108
108
  }
109
109
 
110
+ /** Маркер skip-запису avg-рунга (кеп вичерпано) — НЕ фактичний виклик моделі. */
111
+ const AVG_SKIP_MARKER = 'cloud-avg cap reached'
112
+
113
+ /**
114
+ * Рахує фактичні виклики моделей за тирами (skip-записи avg-кепу не рахуються).
115
+ * @param {object[]} records записи рунгів
116
+ * @returns {{ local: number, cloudMin: number, cloudAvg: number }} лічильники викликів
117
+ */
118
+ export function summarizeCalls(records) {
119
+ const stats = { local: 0, cloudMin: 0, cloudAvg: 0 }
120
+ for (const r of records) {
121
+ if (r.callError === AVG_SKIP_MARKER) continue
122
+ if (r.tier === 'cloud-avg') stats.cloudAvg++
123
+ else if (r.tier === 'cloud-min') stats.cloudMin++
124
+ else if (typeof r.tier === 'string' && r.tier.startsWith('local')) stats.local++
125
+ }
126
+ return stats
127
+ }
128
+
129
+ /**
130
+ * Друкує резюме викликів моделей за цей прогін (локальна / cloud-min / cloud-avg).
131
+ * No-op, якщо викликів не було. Читає записи від `sinceOffset`.
132
+ * @param {number} sinceOffset байтовий зсув логу перед прогоном
133
+ * @param {(s: string) => void} log логер
134
+ * @returns {void}
135
+ */
136
+ export function reportRunStats(sinceOffset, log) {
137
+ const { local, cloudMin, cloudAvg } = summarizeCalls(readEscalationRecords(escalationLogPath(), sinceOffset))
138
+ if (local + cloudMin + cloudAvg === 0) return
139
+ log(
140
+ `\n📊 LLM-виклики fix-конформності (цей прогін): ` +
141
+ `локальна ${local} · cloud-min ${cloudMin} · cloud-avg ${cloudAvg}\n`
142
+ )
143
+ }
144
+
110
145
  /**
111
146
  * Стискає запис до полів, важливих для аналізу (без ts/ms-шуму).
112
147
  * @param {object} r сирий запис рунга
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: analyze-escalation.mjs
4
4
  resource: npm/scripts/lib/fix/analyze-escalation.mjs
5
5
  docgen:
6
- crc: f802e47f
6
+ crc: 5a586df6
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
@@ -16,6 +16,8 @@ docgen:
16
16
 
17
17
  ## Публічний API
18
18
 
19
+ - `summarizeCalls(records)` — лічильники фактичних викликів за тирами `{ local, cloudMin, cloudAvg }` (skip-записи avg-кепу не рахуються).
20
+ - `reportRunStats(sinceOffset, log)` — друкує резюме викликів моделей за прогін (no-op, якщо викликів не було).
19
21
  - `analysisEnabled()` — чи дозволено авто-аналіз (kill-switch `N_CURSOR_FIX_ANALYZE`).
20
22
  - `escalationLogSize(path?)` — розмір логу в байтах (since-offset).
21
23
  - `readEscalationRecords(path, sinceOffset?)` — записи від зсуву.
@@ -1,9 +1,2 @@
1
- /**
2
- * Re-export спільного списку ignore-глобів із правила doc-files.
3
- *
4
- * Канонічне джерело — `npm/rules/doc-files/js/docgen-ignore.mjs`: скіл doc-aggregate
5
- * і правило doc-files мусять бачити однакове дерево кодових файлів, інакше агрегат
6
- * посилатиметься на файли без док (або навпаки). Залежність спрямована
7
- * doc-aggregate → doc-files за ADR про розбиття docgen.
8
- */
1
+ /** @see ./docs/docgen-ignore.md */
9
2
  export * from '../../../rules/doc-files/js/docgen-ignore.mjs'
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-ignore.mjs
4
4
  resource: npm/skills/doc-aggregate/js/docgen-ignore.mjs
5
5
  docgen:
6
- crc: 5faaffd0
6
+ crc: 3b579230
7
7
  ---
8
8
 
9
9
  Re-export спільного списку ignore-глобів зі скіла doc-files: обидва скіли документації (пофайлові доки й агрегати) мусять бачити однакове дерево кодових файлів, інакше агрегат посилатиметься на файли без док або навпаки.