@nitra/cursor 12.0.3 → 12.2.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.2.0] - 2026-06-19
4
+
5
+ ### Changed
6
+
7
+ - lint fail-fast лише для --read-only: у fix-режимі per-file правило з ненульовим кодом не спиняє — прогін доходить до кроку виправлення (конформність-драбина). + applyChanges створює батьківську теку (mkdirSync) перед записом. + детермінований T0-патерн changelog-create-change-file (writeChange замість LLM)
8
+
9
+ ## [12.1.0] - 2026-06-19
10
+
11
+ ### Added
12
+
13
+ - fix-конформність: драбина ескалації моделей (local-min → local-min+feedback → cloud-min → cloud-avg) із per-рунговим escalation-логом (.n-cursor/fix-escalation.jsonl, поле diagnosis) і кепом --max-avg; прибрано --max-iter/MODEL_HEAVY
14
+ - analyze-escalation: аналіз escalation-логу хмарною avg-моделлю (чанкінг + map-reduce) → markdown-звіт із пропозиціями нових T0-патернів / правок .mdc / змін скриптів; CLI n-cursor analyze-escalation і авто-хук наприкінці lint --full (kill-switch N_CURSOR_FIX_ANALYZE)
15
+
3
16
  ## [12.0.3] - 2026-06-18
4
17
 
5
18
  ### Fixed
package/bin/n-cursor.js CHANGED
@@ -1599,6 +1599,15 @@ try {
1599
1599
 
1600
1600
  break
1601
1601
  }
1602
+ case 'analyze-escalation': {
1603
+ // n-cursor analyze-escalation — читає весь escalation-лог (.n-cursor/fix-escalation.jsonl),
1604
+ // чанкує й просить хмарну avg-модель запропонувати, як зменшити LLM-залежність fix-
1605
+ // конформності (нові T0-патерни / правки .mdc / зміни скриптів). Звіт → markdown.
1606
+ const { runEscalationAnalysisCli } = await import('../scripts/lib/fix/analyze-escalation.mjs')
1607
+ process.exitCode = await runEscalationAnalysisCli(args)
1608
+
1609
+ break
1610
+ }
1602
1611
  case 'taze': {
1603
1612
  // n-cursor taze diff — read-only semver-diff package.json ↔ package.json.taze-bak
1604
1613
  // (root + воркспейси) для скілу n-taze: скрипт класифікує major-оновлення,
@@ -1697,7 +1706,7 @@ try {
1697
1706
  default: {
1698
1707
  console.error(`❌ Невідома команда: ${command}`)
1699
1708
  console.error(
1700
- ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-fix, adr-normalize-local, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, taze, start-check, change, release, skill, trace, doc-aggregate`
1709
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-fix, adr-normalize-local, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, analyze-escalation, taze, start-check, change, release, skill, trace, doc-aggregate`
1701
1710
  )
1702
1711
  process.exitCode = 1
1703
1712
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.0.3",
3
+ "version": "12.2.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -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: aed5ffe7
6
+ crc: f4eb439d
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
@@ -14,7 +14,9 @@ docgen:
14
14
 
15
15
  Поведінка
16
16
  selectLintRules вибирає і сортує ідентифікатори правил на основі їхнього обсягу дії (`per-file` або `full`) та прапорця `--full`.
17
- runLint запускає оркестрацію лінтування: або виконує перевірку конформності для заданих правил, або ітерує по алфавітно відсортованих правилах, запускаючи лінтер для змінених файлів (за замовчуванням), або виконує перевірку конформності всього репозиторію при використанні прапорця `--full`.
17
+ runLint запускає оркестрацію лінтування: або виконує перевірку конформності для заданих правил, або ітерує по алфавітно відсортованих правилах (`runPerFileRules`), запускаючи лінтер для змінених файлів (за замовчуванням), або виконує перевірку конформності всього репозиторію при використанні прапорця `--full`.
18
+ **Fail-fast — лише в `--read-only`** (CI/детект): перший ненульовий код спиняє. У fix-режимі (default) ненульовий код per-file правила НЕ спиняє — проганяються всі правила й виконується крок виправлення (конформність-драбина), а повертається найгірший код.
19
+ У режимі `--full` без `--read-only` після конформність-фази (`runFullConformancePhase`) викликається escalation-аналітика (`analyze-escalation.mjs`): фіксує зсув escalation-логу до фази, після — аналізує записи саме цього прогону. Аналіз не впливає на exit-код lint.
18
20
 
19
21
  ## Публічний API
20
22
 
@@ -9,7 +9,9 @@
9
9
  * викликає `rules/<id>/js/lint.mjs` → `lint(files, cwd, { readOnly })`:
10
10
  * - default scope: `files` = змінені відносно origin (`collectChangedFilesSince`);
11
11
  * - `--full`: `files = undefined` — весь проєкт.
12
- * Порядок правил — алфавітний. Fail-fast: перший ненульовий код спиняє.
12
+ * Порядок правил — алфавітний. Fail-fast **лише в `--read-only`** (CI/детект): перший
13
+ * ненульовий код спиняє. У fix-режимі (default) ненульовий код НЕ спиняє — проганяємо всі
14
+ * правила й доходимо до кроку виправлення (конформність-драбина), повертаючи найгірший код.
13
15
  */
14
16
  import { existsSync, readdirSync } from 'node:fs'
15
17
  import { dirname, join } from 'node:path'
@@ -80,6 +82,53 @@ function readAllMeta(rulesDir) {
80
82
  return out
81
83
  }
82
84
 
85
+ /**
86
+ * Per-file фаза: проганяє лінтер кожного правила. Fail-fast лише в read-only.
87
+ * @param {string[]} ids id правил (алфавітно)
88
+ * @param {{ rulesDir: string, changed: string[]|undefined, cwd: string, readOnly: boolean, metaById: Record<string, {llmFix?: boolean}>, log: (s: string) => void }} ctx контекст
89
+ * @returns {Promise<{ stop: boolean, code: number }>} `stop` — read-only fail-fast; `code` — найгірший код
90
+ */
91
+ async function runPerFileRules(ids, ctx) {
92
+ const { rulesDir, changed, cwd, readOnly, metaById, log } = ctx
93
+ let worst = 0
94
+ for (const id of ids) {
95
+ const lintPath = join(rulesDir, id, 'js', 'lint.mjs')
96
+ if (!existsSync(lintPath)) {
97
+ log(`⚠️ lint: правило ${id} має lint-фазу, але немає js/lint.mjs — пропускаю.\n`)
98
+ continue
99
+ }
100
+ // lintPath = join(rulesDir, id, …) — суто package-internal (rulesDir пакета + id зі
101
+ // selectLintRules за власним meta), не зовнішній вхід → ін'єкції немає.
102
+ // eslint-disable-next-line no-unsanitized/method
103
+ const mod = await import(lintPath)
104
+ // `llmFix` (opt-in opportunistic LLM-fix, спека 2026-06-15): лише правила з
105
+ // `meta.json: llmFix:true` отримують fix-сходинку; решта — detect-only.
106
+ const llmFix = metaById[id]?.llmFix === true
107
+ const code = await mod.lint(changed, cwd, { readOnly, llmFix })
108
+ if (code !== 0) {
109
+ if (readOnly) return { stop: true, code } // read-only — fail-fast (детект для CI)
110
+ worst = code // fix-режим — фіксуємо, але йдемо далі до кроку виправлення
111
+ }
112
+ }
113
+ return { stop: false, code: worst }
114
+ }
115
+
116
+ /**
117
+ * Конформність-фаза `--full` (поглинула `fix`): escalation-аналітику обрамляє зсувом логу
118
+ * (записи саме цього прогону), у fix-режимі по конформності викликає аналіз.
119
+ * @param {string} cwd корінь
120
+ * @param {boolean} readOnly лише детект
121
+ * @param {(s: string) => void} log логер
122
+ * @returns {Promise<number>} код конформності
123
+ */
124
+ async function runFullConformancePhase(cwd, readOnly, log) {
125
+ const { escalationLogSize, maybeAnalyzeEscalation } = await import('../../../scripts/lib/fix/analyze-escalation.mjs')
126
+ const escOffset = readOnly ? 0 : escalationLogSize()
127
+ const conformanceCode = await runConformance(cwd, readOnly, log)
128
+ if (!readOnly) maybeAnalyzeEscalation(cwd, escOffset, log)
129
+ return conformanceCode
130
+ }
131
+
83
132
  /**
84
133
  * Запускає lint-оркестрацію.
85
134
  * @param {{ full?: boolean, readOnly?: boolean, rules?: string[], cwd?: string, rulesDir?: string, log?: (s: string) => void }} [opts] параметри
@@ -110,29 +159,18 @@ export async function runLint(opts = {}) {
110
159
 
111
160
  const metaById = readAllMeta(rulesDir)
112
161
  const ids = selectLintRules(metaById, full)
113
- for (const id of ids) {
114
- const lintPath = join(rulesDir, id, 'js', 'lint.mjs')
115
- if (!existsSync(lintPath)) {
116
- log(`⚠️ lint: правило ${id} має lint-фазу, але немає js/lint.mjs — пропускаю.\n`)
117
- continue
118
- }
119
- // lintPath = join(rulesDir, id, …) — суто package-internal (rulesDir пакета + id зі
120
- // selectLintRules за власним meta), не зовнішній вхід → ін'єкції немає.
121
- // eslint-disable-next-line no-unsanitized/method
122
- const mod = await import(lintPath)
123
- // `llmFix` (opt-in opportunistic LLM-fix, спека 2026-06-15): лише правила з
124
- // `meta.json: llmFix:true` отримують fix-сходинку; решта — detect-only. Це й
125
- // забезпечує safety-тріаж (логічні лінтери не вмикають LLM-fix випадково).
126
- const llmFix = metaById[id]?.llmFix === true
127
- const code = await mod.lint(changed, cwd, { readOnly, llmFix })
128
- if (code !== 0) return code
129
- }
162
+ const perFile = await runPerFileRules(ids, { rulesDir, changed, cwd, readOnly, metaById, log })
163
+ if (perFile.stop) return perFile.code
164
+ let worst = perFile.code
130
165
 
131
- // Конформність-фаза (поглинула `fix`): whole-repo, лише у `--full`. Кастомний rulesDir
132
- // (юніт-тести селектора) — реальний пакет недоступний, тож пропускаємо.
166
+ // Конформність-фаза: whole-repo, лише у `--full`. Кастомний rulesDir (юніт-тести
167
+ // селектора) — реальний пакет недоступний, тож пропускаємо.
133
168
  if (full && opts.rulesDir === undefined) {
134
- const conformanceCode = await runConformance(cwd, readOnly, log)
135
- if (conformanceCode !== 0) return conformanceCode
169
+ const conformanceCode = await runFullConformancePhase(cwd, readOnly, log)
170
+ if (conformanceCode !== 0) {
171
+ if (readOnly) return conformanceCode
172
+ worst = conformanceCode
173
+ }
136
174
  }
137
- return 0
175
+ return worst
138
176
  }
@@ -3,224 +3,26 @@ type: JS Module
3
3
  title: release.mjs
4
4
  resource: npm/rules/release/release.mjs
5
5
  docgen:
6
- crc: 9aa2796b
6
+ crc: 06d3556b
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
7
10
  ---
8
11
 
9
- Модуль `npm/rules/release/release.mjs` — це ядро команди `n-cursor release`. Він агрегує per-workspace change-файли (накопичені у `CHANGES_DIR` кожного воркспейсу) у:
12
+ ## Огляд
10
13
 
11
- 1. version-bump у маніфесті пакета (`package.json` для npm-пакетів або `pyproject.toml` для Python),
12
- 2. новий розділ у `CHANGELOG.md` відповідного воркспейсу,
13
- 3. git-коміт зі стандартизованим subject `release: <name@version>, ...`,
14
- 4. git-теги у форматі `<name>@<version>` для кожного зрелізованого пакета,
15
- 5. фізичне видалення «спожитих» change-файлів,
16
- 6. `git push --follow-tags`.
14
+ Файл автоматизує процес випуску версій. Він агрегує зміна-файли для всіх робочих просторів у `version-bump` та `CHANGELOG`, комітить зміни, ставить тег `<name>@<version>` та видаляє використані зміна-файли. Цей процес виконується у CI на гілці `main` (n-cursor-release-design, варіант A). Публічні функції, що надає модуль, — `release` та `runReleaseCli`.
17
15
 
18
- Модуль розрахований на запуск у CI на гілці `main` (варіант A з ADR `n-cursor-release-design`). Сам він **нічого не публікує** у реєстри (npm/PyPI) — цим займаються окремі CI-кроки, орієнтовані на створені теги.
16
+ ## Поведінка
19
17
 
20
- Підтримуються:
18
+ release агрегує change-файли для всіх робочих просторів, оновлює версії в маніфестах, додає секції до `CHANGELOG.md`, видаляє використані change-файли, створює коміт та анотовані теги для всіх зібраних релізів, а потім намагається пушити їх у апстрім з повторними спробами.
19
+ runReleaseCli виконує процес релізу, викликаючи функцію release, та виводить відповідний статус у консоль.
21
20
 
22
- - monorepo (root має суб-воркспейси) — root-пакет автоматично пропускається, релізиться лише кожен суб-воркспейс окремо;
23
- - single-package репо (`workspaces === ['.']`) — релізиться сам root.
21
+ ## Публічний API
24
22
 
25
- Якщо явних change-файлів у воркспейсі немає, але в історії з останнього тегу `<name>@<version>` є коміти, модуль робить **fallback-синтез** запису з commit log (через `synthesizeChangeFromCommits`).
23
+ release агрегує change-файли для кожного робочого простору у version-bump та CHANGELOG, комітить зміни, ставить тег `<name>@<version>` та видаляє використані change-файли.
24
+ runReleaseCli — виконує команди командного інтерфейсу для запуску процесу випуску.
26
25
 
27
- ## Експорти / API
26
+ ## Гарантії поведінки
28
27
 
29
- | Символ | Тип | Призначення |
30
- | ----------------------------- | ---------------- | ------------------------------------------------------------------------------------------------- |
31
- | `release(opts?)` | `async function` | Програмний API: виконує повний реліз-цикл і повертає масив зрелізованих пакетів. |
32
- | `runReleaseCli(_args, opts?)` | `async function` | CLI-обгортка: запускає `release`, друкує підсумок у stdout/stderr, повертає exit-код (`0` / `1`). |
33
-
34
- Внутрішні (не експортовані) функції-помічники: `writeManifestVersion`, `prependWorkspaceChangelog`, `collectChangeFiles`.
35
-
36
- Внутрішні константи: `SEMVER_LINE_RE`, `PY_VERSION_LINE_RE` — regex для in-place заміни рядка `version` у відповідних типах маніфесту.
37
-
38
- ## Функції
39
-
40
- ### `writeManifestVersion(cwd, manifest, newVersion)` (внутрішня)
41
-
42
- Записує нову version у маніфест пакета, зберігаючи форматування файлу.
43
-
44
- - **Сигнатура:** `async (cwd: string, manifest: PackageManifest, newVersion: string) => Promise<void>`
45
- - **Параметри:**
46
- - `cwd` — абсолютний шлях кореня репо (root проекту);
47
- - `manifest` — об'єкт маніфесту з типу `PackageManifest`, що містить поля `ws` (відносний шлях до воркспейсу, або `.` для root), `manifestRel` (відносний шлях до файлу маніфесту в межах воркспейсу) і `kind` (`'npm'` чи інший — трактується як Python);
48
- - `newVersion` — новий рядок версії (SemVer для npm, PEP 440 / SemVer для Python — не валідується тут).
49
- - **Повертає:** `Promise<void>` після успішного запису.
50
- - **Side effects:**
51
- - читає файл маніфесту з диска;
52
- - перезаписує його зі зміненим рядком версії;
53
- - **обирає regex за типом маніфесту**: `SEMVER_LINE_RE` для `'npm'`, `PY_VERSION_LINE_RE` інакше;
54
- - **кидає `Error`**, якщо в файлі не знайдено патерн рядка `version` (тобто `text.replace(...)` не змінив текст).
55
-
56
- ### `prependWorkspaceChangelog(cwd, ws, sectionBlock)` (внутрішня)
57
-
58
- Доклеює (prepend) новий блок CHANGELOG до початку `<ws>/CHANGELOG.md`; якщо файл не існує — створює.
59
-
60
- - **Сигнатура:** `async (cwd: string, ws: string, sectionBlock: string) => Promise<void>`
61
- - **Параметри:**
62
- - `cwd` — корінь репо;
63
- - `ws` — відносний шлях воркспейсу від `cwd` (наприклад, `'npm'`, `'.'`);
64
- - `sectionBlock` — готовий markdown-блок нового розділу (формат сформований `aggregateWorkspace`).
65
- - **Повертає:** `Promise<void>`.
66
- - **Side effects:** читає (за наявності) і пише `<cwd>/<ws>/CHANGELOG.md`. Логіку конкатенації керує `prependChangelogSection` з `./lib/aggregate.mjs` — модуль `release.mjs` лише викликає її, не дублюючи правил вставки.
67
-
68
- ### `collectChangeFiles(cwd, manifest, runGit)` (внутрішня)
69
-
70
- Збирає всі change-записи для воркспейсу: спочатку явні файли з `CHANGES_DIR`, інакше — fallback-синтез з історії комітів.
71
-
72
- - **Сигнатура:** `async (cwd: string, manifest: PackageManifest, runGit: (args: string[]) => Promise<string | null>) => Promise<Array<{ file: string | null, entry: { bump: string, section: string, description: string } }>>`
73
- - **Параметри:**
74
- - `cwd` — корінь репо;
75
- - `manifest` — маніфест воркспейсу;
76
- - `runGit` — git-раннер; повертає stdout як рядок, або `null`, якщо команда зафейлилася.
77
- - **Повертає:** масив об'єктів `{ file, entry }`:
78
- - `file` — ім'я change-файлу у `CHANGES_DIR` (для подальшого видалення), або `null` для синтезованого запису;
79
- - `entry` — нормалізований опис зміни: `{ bump, section, description }` (типи `bump`/`section` визначаються форматом change-файлу і `synthesizeChangeFromCommits`).
80
- - **Поведінка:**
81
- 1. Викликає `readChangeFiles(manifest.ws, cwd)`; якщо результат непустий — повертає його (явні мають пріоритет).
82
- 2. Інакше: якщо у маніфесті немає `name` — повертає `[]` (без імені неможливо знайти попередній тег для fallback).
83
- 3. Інакше викликає `synthesizeChangeFromCommits(name, ws, { runGit })`; якщо нічого не синтезувалося — `[]`.
84
- 4. Якщо синтезовано — друкує в stderr попередження `⚠️ <ws>: немає change-файлів — синтезовано запис із комітів (fallback)` і повертає `[{ file: null, entry: synthesized }]`.
85
- - **Side effects:** виклики git через `runGit` (всередині `synthesizeChangeFromCommits`); `console.warn` при fallback.
86
-
87
- ### `release(opts?)` (експорт)
88
-
89
- Основний програмний вхід — виконує повний реліз-цикл для всіх релевантних воркспейсів.
90
-
91
- - **Сигнатура:**
92
- ```
93
- async (opts?: {
94
- cwd?: string,
95
- date?: string,
96
- runGit?: (args: string[]) => Promise<string | null>,
97
- }) => Promise<Array<{ ws: string, name: string | null, newVersion: string }>>
98
- ```
99
- - **Параметри `opts` (усі необов'язкові):**
100
- - `cwd` — корінь репо; за замовчуванням `process.cwd()`;
101
- - `date` — рядок у форматі `YYYY-MM-DD` для дати релізу в CHANGELOG; за замовчуванням сьогоднішня UTC-дата (`new Date().toISOString().slice(0, 10)`);
102
- - `runGit` — інжектований git-раннер; за замовчуванням `defaultRunGit(cwd)` (виконує справжні git-команди у `cwd`).
103
- - **Повертає:** масив зрелізованих пакетів, кожен — `{ ws, name, newVersion }`. Якщо релізити нічого не було — повертає `[]` (без коміту/тегу/пушу).
104
- - **Алгоритм:**
105
- 1. Отримує список воркспейсів через `getMonorepoProjectRootDirs(cwd)`.
106
- 2. Визначає, чи це monorepo: `subWorkspaces = workspaces.filter(w => w !== '.')`, `isMonorepoRoot = subWorkspaces.length > 0`.
107
- 3. Для кожного `ws`:
108
- - якщо `ws === '.'` і `isMonorepoRoot` — пропустити (root у monorepo не релізиться сам по собі);
109
- - читає маніфест через `readPackageManifest(ws, cwd)`; якщо манfest відсутній або без `version` — пропустити;
110
- - збирає change-записи через `collectChangeFiles`;
111
- - викликає `aggregateWorkspace({ currentVersion, changeFiles, date })` — якщо повертає `null` (нема чого релізити, всі записи відфільтровано) — пропустити;
112
- - інакше: записує нову версію у маніфест (`writeManifestVersion`), prepend новий розділ у `CHANGELOG.md` (`prependWorkspaceChangelog`), видаляє кожен спожитий change-файл (`rm` у `<cwd>/<ws>/<CHANGES_DIR>/<file>`), додає запис у `released`, а якщо є `manifest.name` — формує тег `<name>@<newVersion>`.
113
- 4. Якщо `released.length > 0`:
114
- - формує subject коміту: список тегів через `, `, або (якщо тегів нема — наприклад, у пакетів без `name`) — `<ws>@<newVersion>`-значення;
115
- - `git add -A`;
116
- - `git commit -m "release: <subject>"` — якщо `runGit` повертає `null` (коміт не вдався), кидає `Error('release: git commit не вдався — теги та push скасовано')`;
117
- - для кожного тегу — `git tag <tag>`;
118
- - `git push --follow-tags`.
119
- - **Side effects:**
120
- - читання/запис файлів маніфестів і `CHANGELOG.md`;
121
- - видалення change-файлів з диска (`rm`);
122
- - виконання git-команд через `runGit` (включно з push до remote);
123
- - `console.warn` при fallback (через `collectChangeFiles`).
124
- - **Помилки:** кидаються через `throw new Error(...)` у двох місцях:
125
- - не знайдено патерн `version` у маніфесті (`writeManifestVersion`);
126
- - не вдався `git commit` (`runGit` повернув `null`).
127
-
128
- ### `runReleaseCli(_args, opts?)` (експорт)
129
-
130
- CLI-фасад: викликає `release(opts)`, друкує підсумок, мапить помилки на exit-код.
131
-
132
- - **Сигнатура:** `async (_args: string[], opts?: { cwd?: string, date?: string, runGit?: ... }) => Promise<number>`
133
- - **Параметри:**
134
- - `_args` — позиційні CLI-аргументи (поточна імплементація опцій з CLI не приймає, тому ігнорується; параметр підкреслений `_` для лінту);
135
- - `opts` — ті самі опції, що в `release` (використовується тестами для інжекції `cwd`/`date`/`runGit`).
136
- - **Повертає:** `Promise<number>` — exit-код:
137
- - `0` — успіх (включно з випадком «немає що релізити»);
138
- - `1` — будь-яка помилка з `release`.
139
- - **Поведінка:**
140
- - якщо `released.length === 0` — друкує `release: немає змін для релізу`;
141
- - інакше для кожного запису — `console.log` рядок `✅ <name або ws>@<newVersion>`;
142
- - при exception — `console.error("❌ <message>")` і повертає `1`. Підтримує і `Error` (бере `.message`), і не-`Error`-значення (приводить через `String(...)`).
143
-
144
- ## Залежності
145
-
146
- ### Стандартна бібліотека Node.js
147
-
148
- - `node:fs` — `existsSync` (для перевірки існування `CHANGELOG.md`);
149
- - `node:fs/promises` — `readFile`, `writeFile`, `rm`;
150
- - `node:path` — `join`.
151
-
152
- ### Внутрішні модулі проекту
153
-
154
- - `../changelog/lib/package-manifest.mjs`:
155
- - `getMonorepoProjectRootDirs(cwd)` — повертає список воркспейсів (включно з `.`);
156
- - `readPackageManifest(ws, cwd)` — читає маніфест воркспейсу;
157
- - тип `PackageManifest` (через JSDoc-імпорт).
158
- - `./lib/aggregate.mjs`:
159
- - `aggregateWorkspace({ currentVersion, changeFiles, date })` — обчислює `newVersion` і `sectionBlock` CHANGELOG за зібраними change-записами; повертає `null`, якщо немає змін до релізу;
160
- - `prependChangelogSection(existing, sectionBlock)` — формує новий вміст `CHANGELOG.md` зі вставкою блоку на початок.
161
- - `./lib/change-file.mjs`:
162
- - константа `CHANGES_DIR` — назва теки з change-файлами всередині воркспейсу;
163
- - `readChangeFiles(ws, cwd)` — читає всі change-файли воркспейсу.
164
- - `./lib/fallback.mjs`:
165
- - `defaultRunGit(cwd)` — фабрика git-раннера з прив'язкою до `cwd`;
166
- - `synthesizeChangeFromCommits(name, ws, { runGit })` — синтезує change-запис із commit-історії з останнього тегу `<name>@*`.
167
-
168
- ### Зовнішні залежності
169
-
170
- Жодних npm-пакетів — лише вбудовані модулі Node.js та внутрішні модулі проекту.
171
-
172
- ## Потік виконання / Використання
173
-
174
- ### CLI-використання (через диспатчер `n-cursor`)
175
-
176
- `runReleaseCli` під'єднується до точки входу `n-cursor release`. Зазвичай викликається в CI на `main` після злиття PR:
177
-
178
- ```bash
179
- n-cursor release
180
- ```
181
-
182
- Exit-код `0` — навіть якщо нічого не зрелізовано (в стандартний випадок «нічого не змінилось»). Exit-код `1` — фейл (помилка запису маніфесту, фейл `git commit`, тощо).
183
-
184
- ### Програмне використання (у тестах / інших скриптах)
185
-
186
- ```js
187
- import { release, runReleaseCli } from './release.mjs'
188
-
189
- // 1) Прямий виклик
190
- const released = await release({
191
- cwd: '/abs/path/to/repo',
192
- date: '2026-06-03',
193
- runGit: async args => '...stdout...' // або null при помилці
194
- })
195
-
196
- // 2) Через CLI-фасад
197
- const code = await runReleaseCli([], { cwd, date, runGit })
198
- process.exit(code)
199
- ```
200
-
201
- ### Послідовність кроків у `release()`
202
-
203
- 1. **Дискавер воркспейсів** — `getMonorepoProjectRootDirs(cwd)`.
204
- 2. **Класифікація** — root пропускається у monorepo (де є суб-воркспейси).
205
- 3. **Для кожного воркспейсу:**
206
- - читання маніфесту;
207
- - збір change-файлів (явні → fallback-синтез з комітів);
208
- - агрегація → `newVersion` + `sectionBlock`;
209
- - запис маніфесту;
210
- - prepend CHANGELOG;
211
- - видалення «спожитих» change-файлів;
212
- - реєстрація запису і (за наявності `name`) тегу.
213
- 4. **Якщо є зрелізоване хоча б одне:**
214
- - формування subject коміту;
215
- - `git add -A` → `git commit -m "release: <subject>"`;
216
- - перевірка успішності коміту (інакше throw);
217
- - проставляння всіх тегів;
218
- - `git push --follow-tags`.
219
- 5. **Повернення масиву** зрелізованих пакетів.
220
-
221
- ### Інваріанти
222
-
223
- - Жодних версій/тегів/коміту, якщо немає реальних змін у жодному воркспейсі — масив `released` залишається порожнім, і блок git взагалі не виконується.
224
- - Якщо хоча б у одному воркспейсі не вдалось оновити маніфест — функція кидає виняток до `git add -A`; часткові зміни на диску можуть залишитися (виклик не транзакційний — це відповідальність CI/runner-а відкотити робоче дерево).
225
- - Якщо `git commit` зафейлився — теги не проставляються і push не виконується; кидається явна помилка.
226
- - Усі git-операції проходять через інжектований `runGit`, що дає змогу тестувати функцію без реальних git-викликів.
28
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: JS Module
3
+ title: fix.mjs
4
+ resource: npm/rules/tool-surface/fix.mjs
5
+ docgen:
6
+ crc: 38cf876b
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.93
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Цей файл запускає логіку перевірки відповідності політики через функцію `run`, використовуючи контекст прогону, який містить дані, кешовані у межах цього прогону. При самостійному запуску скрипт ініціює повний цикл: завантажує конфігурацію, перевіряє білий список та формує підсумок. Результат виконання визначається кодом виходу, що сигналізує про успішне виконання або виявлені порушення.
16
+
17
+ ## Поведінка
18
+
19
+ 1. Викликається функція `run` для запуску правила.
20
+ 2. Правило виконується, використовуючи контекст прогону, що може містити кеш.
21
+ 3. Якщо скрипт виконується як окрема програма (standalone), запускається оркестрація правила.
22
+ 4. Оркестрація виконує завантаження конфігурації, перевірку білого списку та підсумок.
23
+ 5. Код завершує роботу з кодом виходу, який вказує на успіх або наявність порушень.
24
+
25
+ ## Публічний API
26
+
27
+ run — виконує послідовність перевірок: застосовує правила, аналізує JS-занепокоєння, перевіряє політику та посилання MDC.
28
+
29
+ ## Гарантії поведінки
30
+
31
+ - Read-only: не виконує операцій запису (ФС/БД).
32
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/tool-surface
4
+ resource: npm/rules/tool-surface/
5
+ ---
6
+
7
+ # npm/rules/tool-surface
8
+
9
+ | Файл | Тип |
10
+ |---|---|
11
+ | [fix.mjs](fix.md) | JS Module |
@@ -3,36 +3,45 @@ type: JS Module
3
3
  title: normalize-pipeline.mjs
4
4
  resource: npm/scripts/lib/adr/normalize-pipeline.mjs
5
5
  docgen:
6
- crc: 6eb6ba69
6
+ crc: 9f2d42d3
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
+ issues: judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
9
11
  ---
10
12
 
11
- Файл реалізує локально-орієнтований конвеєр для нормалізації чернеток ADR. Він використовує LLM лише для вузьких, верифікованих бінарних суджень. Конвеєр працює у послідовних стадіях: JS виконує пошук кандидатів-ребер на основі лексичної схожості, LLM оцінює ці ребра (Stage 1: `same/different`) та драфти (Stage 1b: `standalone/trivial`), JS кластеризує підтверджені ребра (використовуючи `union-find`), LLM реформатує анотера (Stage 2: `gen-MADR`), а LLM генерує доповнення для злиття (Stage 3: `gen-merge`). Глобальний стан (кластери, слаги, покриття) зберігається в JS. Конвеєр повертає операції у форматі `operations[]`, сумісного з контрактом `apply-ops`.
13
+ ## Огляд
14
+
15
+ Файл реалізує локально-орієнтований конвеєр для нормалізації чернеток архітектурних рішень (ADR). Він працює за принципом інверсії керування: JavaScript оркеструє процес, а LLM відповідає лише на вузькі, верифіковані запитання, будучи заточеним під малу локальну модель (omlx/gemma-4b). LLM використовується лише для бінарного судження схожості між записами (наприклад, через `edge-judge`) та витягування змісту секцій у JSON (через `gen-MADR`). Конвеєр збирає, валідує та складає повний MADR-каркас, використовуючи кластеризацію (`cluster` та `union-find`) та проходить через `validation gate`, повертаючи результат у вигляді операцій для застосування.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- tokenize токенізує назву або слаг у множину значущих токенів, виключаючи стоп-слова.
16
- jaccard обчислює Jaccard-схожість між двома множинами токенів.
17
- draftTitle витягує заголовок чернетки, надаючи пріоритет заголовку ADR.
18
- isNoDecision визначає, чи не прийнято рішення у чернетці, аналізуючи секцію Decision Outcome.
19
- buildEdges будує кандидати-ребра між чернетками та між чернетками і чистими ADR на основі лексичної схожості.
20
- validateMadr перевіряє згенерований контент на відповідність канону чистого MADR.
21
- normalizePipeline виконує повний конвеєр нормалізації, групуючи чернетки, оцінюючи їх та генеруючи операції для застосування.
19
+ tokenize: Токенізує назву або слаг, видаляючи стоп-слова та розширюючи його на значущі токени.
20
+ jaccard: Обчислює коефіцієнт Jaccard між двома множинами токенів.
21
+ draftTitle: Витягує заголовок з тіла чернетки, використовуючи ADR-шаблон або перший не-MADR заголовок.
22
+ isNoDecision: Детерміновано визначає, чи рішення в чернетці явно не прийняте.
23
+ buildEdges: Будує кандидати-ребра між чернетками та між чернетками і чистими ADR-файлами на основі лексичної схожості.
24
+ validateMadr: Перевіряє згенерований MADR-текст на відповідність вимогам OKF та структурним елементам.
25
+ madrDate: Детерміновано визначає ISO-дату для поля **Date:**, використовуючи дані з чернетки або імені файлу.
26
+ normalizeSections: Нормалізує сирий JSON-вивід LLM у строгу структуру секцій MADR, толерантно до дрібних відхилень моделі.
27
+ assembleMadr: Збирає канонічний MADR-markdown, використовуючи заголовок, дату та нормалізований контент секцій.
28
+ genMadr: Витягує зміст архітектурного рішення з чернетки у JSON, а потім збирає його у валідний MADR-текст.
29
+ normalizePipeline: Виконує повний конвеєр нормалізації, кластеризуючи чернетки та генеруючи операції для застосування.
22
30
 
23
31
  ## Публічний API
24
32
 
25
- tokenize — розбиває назву чи слаг на значущі частини, замінюючи пробіли та дефіси, і відсіюючи загальні слова.
26
- jaccard — обчислює ступінь подібності між двома наборами слів.
27
- draftTitle — витягує заголовок з чернетки, надаючи пріоритет заголовку у форматі `## ADR <title>`, а у відсутності такого — використовує перший не-ADR заголовок або ім'я файлу.
28
- isNoDecision — визначає, чи не містить чернетка чіткого рішення, що робить її непотрібною для окремого ADR.
29
- buildEdges — створює потенційні зв'язки між елементами на основі схожості слів.
30
- validateMadr — перевіряє якість згенерованого документа ADR.
31
- normalizePipelineвиконує повний процес обробки, повертаючи список виконаних кроків та статистику.
33
+ tokenize — Розбиває назву чи слаг на значущі слова, ігноруючи стоп-слова.
34
+ jaccard — Вимірює схожість двох наборів слів.
35
+ draftTitle — Витягує заголовок з чернетки, надаючи пріоритет заголовку ADR.
36
+ isNoDecision — Визначає, чи не було прийнято рішення у чернетці, щоб уникнути створення зайвих ADR.
37
+ buildEdges — Створює потенційні зв'язки між елементами на основі схожості слів.
38
+ validateMadr — Перевіряє якість згенерованого документа ADR.
39
+ madrDateФормує стандартизовану дату для документа, використовуючи метадані або ім'я файлу.
40
+ normalizeSections — Приводить неструктурований вивід генеративної моделі до чіткого формату секцій.
41
+ assembleMadr — Збирає повний, стандартизований документ ADR, використовуючи фіксовані шаблони.
42
+ genMadr — Створює чернетку документа ADR на основі вхідних даних.
43
+ normalizePipeline — Виконує повний потік обробки даних, повертаючи результати та статистику.
32
44
 
33
45
  ## Гарантії поведінки
34
46
 
35
- - Read-only: файл не виконує операцій запису у файлову систему.
36
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
37
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
38
- - Не звертається до мережі.
47
+ - (специфічних машинно-виведених гарантій немає)
@@ -3,29 +3,28 @@ type: JS Module
3
3
  title: worktree-notice.mjs
4
4
  resource: npm/scripts/lib/worktree-notice.mjs
5
5
  docgen:
6
- crc: dc4fba22
6
+ crc: 1f7d5e0d
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
7
10
  ---
8
11
 
9
- Цей файл вбудовує інструкції щодо використання git-worktree, коли `meta.json.worktree` встановлено в `true`. Він забезпечує паралельне виконання скілу лише в окремому git-worktree, запобігаючи потенційним проблемам з паралелізмом. Цей механізм дозволяє уникнути гонки з CDN та забезпечує надійний запуск скілу з локальною копією CLI.
12
+ ## Огляд
13
+
14
+ Цей файл вшиває worktree-інструкцію у синкнутий `SKILL.md` (рішення D2 зі spec). Коли `meta.json.worktree === true`, скіл вставляє/замінює ідемпотентний ре-синкнутий блок, що містить маркери WORKTREE_START та WORKTREE_END, забезпечуючи виконання скілу в окремому git-worktree та запобігаючи паралелізації. Функція `injectWorktreeNotice` керує наявністю або відсутністю цього блоку в `SKILL.md` на основі конфігурації.
10
15
 
11
16
  ## Поведінка
12
17
 
13
- WORKTREE_START: вставляє маркер початку worktree-блоку.
14
- WORKTREE_END: вставляє маркер кінця worktree-блоку.
15
- injectWorktreeNotice: вставляє або видаляє worktree-блок у `SKILL.md` на основі значення `meta.json.worktree`. Якщо `meta.json.worktree` `true`, вставляє блок; інакше видаляє. Якщо блок вже існує, замінює його; якщо ні додає. Враховує наявність YAML-frontmatter та вставляє блок після нього. Використовує транслітерацію для створення суфікса гілки. Реалізує retry-обгортку для `npx` з обмеженням часу та інтервалом для перевірки.
18
+ WORKTREE_START Маркер початку блоку інструкцій для роботи в окремому git-worktree.
19
+ WORKTREE_END Маркер кінця блоку інструкцій для роботи в окремому git-worktree.
20
+ injectWorktreeNotice — Вставляє, оновлює або видаляє блок інструкцій для роботи в worktree у вмісті SKILL.md залежно від булевого значення.
16
21
 
17
22
  ## Публічний API
18
23
 
19
- WORKTREE_START — Початок блоку worktree.
20
- WORKTREE_END — Кінець блоку worktree.
21
- injectWorktreeNotice — Змінює вміст SKILL.md, додаючи, оновлюючи або видаляючи worktree-блок.
24
+ WORKTREE_START — Позначає початок секції, що описує робоче дерево.
25
+ WORKTREE_END — Позначає кінець секції, що описує робоче дерево.
26
+ injectWorktreeNotice — Вставляє, змінює або видаляє блок інформації про робоче дерево у файлі `SKILL.md`.
22
27
 
23
28
  ## Гарантії поведінки
24
29
 
25
- Якщо `meta.json.worktree === true`, то скіл виконується в окремому git-worktree.
26
- Скіл не паралелізується.
27
- Після створення worktree виконується `bun install` у цьому worktree.
28
- Виконується shell-обгортка `n_cursor_npx` навколо `npx` для bootstrap-виклику.
29
- Обгортка `n_cursor_npx` виконує retry на транзитні помилки реєстру/мережі (інтервал 30с, дефолт 5 хв, `N_CURSOR_NPX_RETRY_MAX_MIN`, ceiling 10 хв).
30
- При виникненні nonzero CLI повертається одразу.
31
- Команди, що вимагають command substitution, виконуються після створення worktree.
30
+ - Read-only: не виконує операцій запису (ФС/БД).