@nitra/cursor 12.10.0 → 12.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.11.0] - 2026-06-25
4
+
5
+ ### Changed
6
+
7
+ - Додано перевірку на наявність `CHANGELOG.md` та його формату в workspace-ах
8
+
3
9
  ## [12.10.0] - 2026-06-24
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.10.0",
3
+ "version": "12.11.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,40 +3,30 @@ type: JS Module
3
3
  title: hooks.mjs
4
4
  resource: npm/rules/adr/js/hooks.mjs
5
5
  docgen:
6
- crc: 3f239faf
6
+ crc: 7195231e
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
7
8
  score: 100
8
9
  ---
9
10
 
10
- Файл надає публічну функцію `check` для валідації даних. Код використовує конфігурацію з файлу `.local.json` для визначення логіки. Функція працює у режимі, де невдачі повертають `false` або `null` замість генерації винятків. Повідомлення про події передаються за допомогою маркера (adr.mdc).
11
+ ## Огляд
11
12
 
12
- ## Поведінка
13
-
14
- 1. Перевірка канонічності скриптів
15
- Проводиться перевірка наявності та відповідності логіки між скриптами, визначеними в артефактах, і їхніми канонічними версіями. У разі невідповідності повертається сигнал про необхідність повторного синхронізації.
16
-
17
- 2. Валідація налаштувань проєкту
18
- Перевіряється наявність файлу `.claude/settings.json` для підтвердження коректності конфігурації відповідно до вимог (adr.mdc).
19
-
20
- 3. Перевірка конфігурації хуків Cursor
21
- Перевіряється, чи містить конфігурацію Cursor необхідні стоп-хуки для кожного визначеного скрипта.
13
+ Модуль перевіряє стан проєкту, валідуючи конфігураційні файли (`settings.json`, `hooks.json`, `settings.local.json`) та перевіряючи наявність структур для документації (adr.mdc). Перевірка включає перевірку наявності необхідних елементів, при цьому шляхи `.git` свідомо ігноруються.
22
14
 
23
- 4. Перевірка ігнорування логів
24
- Перевіряється вміст файлу `.gitignore` на відповідність ігнорування кожного лог-файлу хука.
25
-
26
- 5. Перевірка покриття логів
27
- Перевіряється, чи покриває конфігурація `.gitignore` всі необхідні лог-файли.
28
-
29
- 6. Перевірка доступності CLI
30
- Перевіряється наявність бінарних файлів `claude` або `cursor-agent` у системному PATH. Якщо жодного не знайдено, це інформується як попередження, оскільки хук працюватиме у режимі мовчки.
31
-
32
- ## Публічний API
15
+ ## Поведінка
33
16
 
34
- check перевіряє, чи відповідає проєкт вимогам adr.mdc (adr.mdc).
17
+ 1. Викликається `check` для запуску повного набору перевірок проєкту.
18
+ 2. Перевіряється наявність та збіг конфігураційних скриптів хуків у проєкті з канонічними версіями.
19
+ 3. Перевіряється наявність конфігурацій проєкту, зокрема `settings.json`.
20
+ 4. Перевіряється, чи містить конфігурація хуків проєкту (`hooks.json`) необхідні маркери для зупинення (stop-hook) кожного артефакту хука.
21
+ 5. Перевіряється, чи ігнорує файл `.gitignore` лог-файли кожного хука.
22
+ 6. Перевіряється, чи ігнорує файл `.gitignore` файли стану та блокування, пов'язані з нормалізацією хуків.
23
+ 7. Перевіряється наявність каталогу `docs/adr/` для зберігання ADR-ів.
24
+ 8. Перевіряється доступність LLM CLI (`claude` або `cursor-agent`) у системному шляху.
25
+ 9. Повертається код виходу, що відображає результати всіх перевірок.
35
26
 
36
27
  ## Гарантії поведінки
37
28
 
38
- - Read-only: файл не виконує операцій запису у файлову систему.
29
+ - Read-only: не виконує операцій запису (ФС/БД).
39
30
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
40
- - За невдалої перевірки повертає `false`/`null` замість винятку.
31
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
41
32
  - Свідомо пропускає шляхи: `.git`.
42
- - Не звертається до мережі.
@@ -55,6 +55,9 @@ function gitignoreLineCoversHookLog(line, logPath) {
55
55
  if (line === '.claude/hooks/*.log' || line === '.claude/hooks/**/*.log') {
56
56
  return true
57
57
  }
58
+ if (line === '.claude/hooks/*' || line === '.claude/hooks/**') {
59
+ return true
60
+ }
58
61
  if (line === '*.log' || line === '**/*.log') {
59
62
  return true
60
63
  }
@@ -264,6 +267,60 @@ function checkLlmCliAvailable(reporter) {
264
267
  * @param {string} [cwd] корінь репозиторію
265
268
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
266
269
  */
270
+ /** Файли стану/блокування normalize-хука, які не мають потрапляти в git. */
271
+ const NORMALIZE_STATE_FILES = ['.normalize-state', '.normalize.lock']
272
+ const CLAUDE_HOOKS_REL = '.claude/hooks'
273
+
274
+ /**
275
+ * Перевіряє рядок `.gitignore` на покриття конкретного state/lock файлу.
276
+ * @param {string} line нормалізований (trim) рядок
277
+ * @param {string} statePath відносний шлях файлу (наприклад `.claude/hooks/.normalize-state`)
278
+ * @returns {boolean} true — рядок покриває файл
279
+ */
280
+ function gitignoreLineCoversStatePath(line, statePath) {
281
+ if (line === statePath) return true
282
+ // .claude/hooks/* або .claude/hooks/**
283
+ if (line === `${CLAUDE_HOOKS_REL}/*` || line === `${CLAUDE_HOOKS_REL}/**`) return true
284
+ return false
285
+ }
286
+
287
+ /**
288
+ * Перевіряє `.gitignore` на наявність рядків для файлів стану normalize-хука.
289
+ * @param {import('../../../scripts/lib/check-reporter.mjs').CheckReporter} reporter
290
+ * @param {string} cwd корінь репозиторію
291
+ * @returns {Promise<void>}
292
+ */
293
+ async function checkGitignoreForStateFiles(reporter, cwd) {
294
+ const { pass, fail } = reporter
295
+ const gitignoreAbs = join(cwd, '.gitignore')
296
+ const content = existsSync(gitignoreAbs) ? await readFile(gitignoreAbs, 'utf8') : ''
297
+ const lines = content.split(EOL_RE).map(l => l.trim())
298
+ for (const file of NORMALIZE_STATE_FILES) {
299
+ const statePath = `${CLAUDE_HOOKS_REL}/${file}`
300
+ if (lines.some(l => gitignoreLineCoversStatePath(l, statePath))) {
301
+ pass(`.gitignore покриває ${statePath}`)
302
+ } else {
303
+ fail(`.gitignore не ігнорує \`${statePath}\` — додай рядок (adr.mdc)`)
304
+ }
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Перевіряє наявність каталогу `docs/adr/` — обов'язкового місця зберігання ADR-ів.
310
+ * @param {import('../../../scripts/lib/check-reporter.mjs').CheckReporter} reporter
311
+ * @param {string} cwd корінь репозиторію
312
+ * @returns {void}
313
+ */
314
+ function checkDocsAdrDir(reporter, cwd) {
315
+ const { pass, fail } = reporter
316
+ const adrDir = join(cwd, 'docs', 'adr')
317
+ if (existsSync(adrDir)) {
318
+ pass('docs/adr/ існує (каталог ADR-ів)')
319
+ } else {
320
+ fail('docs/adr/ відсутній — створи каталог для ADR-ів (adr.mdc)')
321
+ }
322
+ }
323
+
267
324
  export async function check(cwd = process.cwd()) {
268
325
  const reporter = createCheckReporter()
269
326
  for (const { scriptName } of HOOK_ARTIFACTS) {
@@ -272,6 +329,8 @@ export async function check(cwd = process.cwd()) {
272
329
  checkProjectSettings(reporter, cwd)
273
330
  await checkCursorHooks(reporter, cwd)
274
331
  await checkGitignore(reporter, cwd)
332
+ await checkGitignoreForStateFiles(reporter, cwd)
333
+ checkDocsAdrDir(reporter, cwd)
275
334
  checkLlmCliAvailable(reporter)
276
335
  return reporter.getExitCode()
277
336
  }
@@ -1,5 +1,7 @@
1
1
  /** @see ./docs/consistency.md */
2
2
  import { execFile } from 'node:child_process'
3
+ import { existsSync } from 'node:fs'
4
+ import { readFile } from 'node:fs/promises'
3
5
  import { join } from 'node:path'
4
6
  import { promisify } from 'node:util'
5
7
 
@@ -379,6 +381,46 @@ function createDefaultGetPublishedVersion() {
379
381
  * @param {(msg: string) => void} pass параметр
380
382
  * @param {(msg: string) => void} fail параметр
381
383
  */
384
+ /**
385
+ * Перевіряє наявність `CHANGELOG.md` у воркспейсі.
386
+ * @param {string} ws відносний шлях воркспейсу від кореня репо
387
+ * @param {string} label мітка для повідомлень
388
+ * @param {string} cwd корінь репозиторію
389
+ * @param {(msg: string) => void} pass
390
+ * @param {(msg: string) => void} fail
391
+ * @returns {boolean} true — файл існує
392
+ */
393
+ function checkChangelogFileExists(ws, label, cwd, pass, fail) {
394
+ const path = join(cwd, ws, 'CHANGELOG.md')
395
+ if (existsSync(path)) {
396
+ pass(`${label}: CHANGELOG.md існує`)
397
+ return true
398
+ }
399
+ fail(`${label}: CHANGELOG.md відсутній — створи файл за форматом Keep a Changelog (n-changelog.mdc)`)
400
+ return false
401
+ }
402
+
403
+ /**
404
+ * Перевіряє базовий формат `CHANGELOG.md`: наявність H1 `# Changelog`.
405
+ * Версійні секції `## [x.y.z]` не вимагаються для нових workspace-ів без релізів.
406
+ * @param {string} ws відносний шлях воркспейсу від кореня репо
407
+ * @param {string} label мітка для повідомлень
408
+ * @param {string} cwd корінь репозиторію
409
+ * @param {(msg: string) => void} pass
410
+ * @param {(msg: string) => void} fail
411
+ * @returns {Promise<void>}
412
+ */
413
+ async function checkChangelogFormat(ws, label, cwd, pass, fail) {
414
+ const path = join(cwd, ws, 'CHANGELOG.md')
415
+ const content = await readFile(path, 'utf8')
416
+ const hasH1 = content.split('\n').some(l => l.trimEnd() === '# Changelog')
417
+ if (hasH1) {
418
+ pass(`${label}: CHANGELOG.md має рядок "# Changelog"`)
419
+ } else {
420
+ fail(`${label}: CHANGELOG.md не має рядка "# Changelog" — перший рядок має бути H1-заголовком (n-changelog.mdc)`)
421
+ }
422
+ }
423
+
382
424
  function checkNpmFilesArrayContainsChangelog(manifest, pass, fail) {
383
425
  if (manifest.kind !== 'npm' || !manifest.npmFiles) return
384
426
  const pkgPath = manifestFilePath(manifest.ws, manifest)
@@ -542,6 +584,10 @@ async function checkPublishedWorkspacePendingGitChanges(manifest, _Vcurrent, sub
542
584
  async function checkPublishedWorkspace(manifest, subWorkspaces, getPublishedVersion, autofix, pass, fail, cwd) {
543
585
  const label = workspaceLabel(manifest)
544
586
  const mf = manifestFilePath(manifest.ws, manifest)
587
+ const changelogExists = checkChangelogFileExists(manifest.ws, label, cwd, pass, fail)
588
+ if (changelogExists) {
589
+ await checkChangelogFormat(manifest.ws, label, cwd, pass, fail)
590
+ }
545
591
  const Vcurrent = manifest.version
546
592
  if (!Vcurrent) {
547
593
  fail(`${label}: у ${mf} відсутнє поле version (registry-published воркспейс)`)
@@ -635,6 +681,14 @@ async function checkLocalOnlyChangedWorkspace(comparisonRef, manifest, baseLabel
635
681
  async function runLocalOnlyChecks(localOnly, subWorkspaces, autofix, pass, fail, cwd) {
636
682
  if (localOnly.length === 0) return
637
683
 
684
+ for (const manifest of localOnly) {
685
+ const label = workspaceLabel(manifest)
686
+ const exists = checkChangelogFileExists(manifest.ws, label, cwd, pass, fail)
687
+ if (exists) {
688
+ await checkChangelogFormat(manifest.ws, label, cwd, pass, fail)
689
+ }
690
+ }
691
+
638
692
  if (!(await isInsideGitRepo(cwd))) {
639
693
  pass('changelog: не git-репозиторій — local-only перевірку пропущено')
640
694
  return
@@ -3,28 +3,35 @@ type: JS Module
3
3
  title: consistency.mjs
4
4
  resource: npm/rules/changelog/js/consistency.mjs
5
5
  docgen:
6
- crc: 92b7c3f0
6
+ crc: a9bebf31
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 95
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Компонент виконує перевірку стану проєктів у монорепозиторії. Він звертається до мережі для порівняння версій з реєстром, зокрема https://pypi.org/pypi/, та аналізує зміни, посилаючись на (n-changelog.mdc). Код спирається на конфігурацію, визначену у res.json. При виявленні невідповідностей компонент перехоплює помилки (fail-safe) і повертає порожнє значення замість кидання винятків.
13
+ Модуль сканує монорепозиторій, ідентифікує проєкти та порівнює їхні версії з даними, отриманими з https://pypi.org/pypi/, для верифікації відповідності версій. Для проєктів, що не призначені для публікації, виконуються локальні перевірки на відповідність файлу `CHANGELOG.md` конфігурації, визначеній у res.json.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- 1. `check` ініціалізує репортер та визначає режим автоматичного виправлення на основі змінної середовища.
18
- 2. `check` зчитує список усіх кореневих каталогів проєктів у монорепозиторії.
19
- 3. `check` класифікує знайдені проєкти на ті, що можуть бути опубліковані в реєстрі, та ті, що є локальними.
20
- 4. Для кожного проєкту, що може бути опублікований, `check` викликає перевірку опублікованого воркспейсу.
21
- 5. Перевірка опублікованого воркспейсу порівнює версію у маніфесті з опублікованою версією, отриманою через мережевий запит до реєстру (наприклад, https://pypi.org/pypi/).
22
- 6. Якщо версія у маніфесті випереджає опубліковану, `check` повідомляє про заборонений ручний bump поза CI.
23
- 7. Якщо версія у маніфесті відповідає опублікованій, `check` перевіряє, чи є релевантні зміни у файловій системі відносно базової точки порівняння (наприклад, `origin/main`).
24
- 8. Якщо зміни існують, але відсутній change-файл, `check` або автоматично створює його режимі autofix), або повідомляє про необхідність ручного створення, посилаючись на (n-changelog.mdc).
25
- 9. Для кожного локального проєкту `check` порівнює стан з базовою точкою порівняння.
26
- 10. Якщо зміни існують, але відсутній change-файл, `check` або автоматично створює його (у режимі autofix), або повідомляє про необхідність ручного створення, посилаючись на (n-changelog.mdc).
27
- 11. `check` повертає кінцевий код виходу на основі накопичених повідомлень про помилки.
17
+ 1. `check` ініціалізує репортер для збору результатів перевірки.
18
+ 2. `check` визначає робочий каталог та режим автоматичного виправлення на основі змінних середовища.
19
+ 3. `check` знаходить усі кореневі каталоги проєктів у монорепозиторії.
20
+ 4. `check` класифікує знайдені проєкти на ті, що можуть бути опубліковані в реєстрі, та ті, що є локальними.
21
+ 5. Для кожного проєкту, що може бути опублікованим, `check` перевіряє його відповідність вимогам:
22
+ а. Перевіряє наявність файлу `CHANGELOG.md`.
23
+ б. Перевіряє базовий формат `CHANGELOG.md` на наявність заголовка `# Changelog`.
24
+ в. Якщо проєкт має поле `version` у маніфесті, `check` порівнює його з опублікованою версією, отриманою через мережевий запит до https://pypi.org/pypi/ або `npm view`.
25
+ г. Якщо версія у проєкті випереджає опубліковану, `check` повідомляє про заборонений ручний bump.
26
+ д. Якщо версія у проєкті відстає від опублікованої, `check` повідомляє про відставання локальної копії від реєстру.
27
+ е. Якщо версії збігаються, `check` перевіряє, чи є незрелі зміни у проєкті відносно базового релізу.
28
+ ж. Якщо проєкт має незрелі зміни, `check` перевіряє наявність change-файлу. Якщо його немає, `check` або повідомляє про необхідність створити його (якщо не в режимі autofix), або автоматично створює його та додає до індексу.
29
+ 6. `check` виконує локальні перевірки для проєктів, які не призначені для публікації:
30
+ а. Для кожного локального проєкту `check` перевіряє наявність файлу `CHANGELOG.md` та його формат.
31
+ б. `check` визначає точку порівняння (базу) на основі поточної гілки.
32
+ в. `check` перевіряє, чи є релевантні зміни у проєкті відносно цієї бази.
33
+ г. Якщо зміни є, `check` перевіряє наявність change-файлу. Якщо його немає, `check` або повідомляє про необхідність створити його (якщо не в режимі autofix), або автоматично створює його та додає до індексу.
34
+ 7. `check` повертає кінцевий код завершення, що відображає результати всіх перевірок.
28
35
 
29
36
  ## Гарантії поведінки
30
37
 
@@ -6,6 +6,6 @@ resource: npm/rules/changelog/js/
6
6
 
7
7
  # npm/rules/changelog/js
8
8
 
9
- | Файл | Тип |
10
- | --------------------------------- | --------- |
9
+ | Файл | Тип |
10
+ |---|---|
11
11
  | [consistency.mjs](consistency.md) | JS Module |
@@ -43,6 +43,15 @@ deny contains msg if {
43
43
  msg := sprintf("lint-python.yml: відсутній step з `uses: %s` (python.mdc)", [required_use])
44
44
  }
45
45
 
46
+ deny contains msg if {
47
+ some job in object.get(input, "jobs", {})
48
+ some step in object.get(job, "steps", [])
49
+ object.get(step, "uses", "") == "actions/checkout@v6"
50
+ creds := object.get(object.get(step, "with", {}), "persist-credentials", true)
51
+ creds != false
52
+ msg := "lint-python.yml: actions/checkout@v6 потребує `with: persist-credentials: false` (python.mdc)"
53
+ }
54
+
46
55
  deny contains msg if {
47
56
  some job in data.template.snippet.jobs
48
57
  some step in job.steps
@@ -21,7 +21,7 @@ deny contains msg if {
21
21
  msg := sprintf("pyproject.toml: [tool.%s] — %s", [key, reason])
22
22
  }
23
23
 
24
- # ── PEP 621: обовʼязкові [project].name / [project].version ──────────────────
24
+ # ── PEP 621: обовʼязкові [project].* ─────────────────────────────────────────
25
25
 
26
26
  deny contains msg if {
27
27
  not project_field_set("name")
@@ -33,7 +33,21 @@ deny contains msg if {
33
33
  msg := "pyproject.toml: відсутній статичний [project].version (PEP 621, python.mdc)"
34
34
  }
35
35
 
36
+ deny contains msg if {
37
+ not project_field_set("requires-python")
38
+ msg := "pyproject.toml: відсутній [project].requires-python (наприклад '>=3.12') (PEP 621, python.mdc)"
39
+ }
40
+
41
+ deny contains msg if {
42
+ not project_key_exists("dependencies")
43
+ msg := "pyproject.toml: відсутній [project].dependencies (навіть порожній список `[]`) (PEP 621, python.mdc)"
44
+ }
45
+
36
46
  project_field_set(key) if {
37
47
  value := object.get(object.get(input, "project", {}), key, "")
38
48
  value != ""
39
49
  }
50
+
51
+ project_key_exists(key) if {
52
+ key in object.keys(object.get(input, "project", {}))
53
+ }
@@ -3,33 +3,28 @@ type: JS Module
3
3
  title: packages.mjs
4
4
  resource: npm/rules/vue/js/packages.mjs
5
5
  docgen:
6
- crc: 6119ae9c
7
- score: 85
6
+ crc: 8589151d
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
8
9
  ---
9
10
 
10
- isVueComponentLibraryPkg
11
- Перевіряє, чи є пакет бібліотекою компонентів Vue шляхом перевірки `peerDependencies`.
11
+ ## Огляд
12
12
 
13
- check
14
- Перевіряє залежності та конфігурацію vite.config одного Vue-пакета.
13
+ Модуль визначає, чи є пакет бібліотекою компонентів Vue, виходячи з даних у `package.json`, `jsconfig.json`, `package-lock.json`, `extensions.json`. Він також перевіряє відповідність усіх пакетів, що залежать від `vue`, критеріям, описаним у `vue.mdc`, і повертає код виходу.
15
14
 
16
15
  ## Поведінка
17
16
 
18
- isVueComponentLibraryPkg
19
- Визначає, чи є пакет бібліотекою компонентів Vue через peerDependencies
20
-
21
- check
22
- Перевіряє залежності та vite.config одного Vue-пакета
17
+ isVueComponentLibraryPkg визначає, чи є пакет бібліотекою компонентів Vue, перевіряючи наявність `vue` у `peerDependencies` його `package.json`.
18
+ check перевіряє відповідність проєкту правилам vue.mdc для всіх пакетів, що містять `vue` у `dependencies`, і повертає код виходу. При цьому ігноруються шляхи `.git` та `node_modules`.
23
19
 
24
20
  ## Публічний API
25
21
 
26
- isVueComponentLibraryPkg — забезпечує, що `+` використовується для підхоплення `vite-env.d.ts` та `.vue`.
27
- passFn — перевіряє наявність `prefixjsconfig.json`.
28
- check — перевіряє, чи є `vue` у `peerDependencies` пакету бібліотеки. Якщо `vue` є залежністю, то правило авто-імпорту (заборона value-імпортів з `'vue'`) не застосовується до цієї бібліотеки, оскільки імпорти з `'vue'` повинні бути явними.
22
+ isVueComponentLibraryPkg — визначає, чи є пакет бібліотекою компонентів Vue, щоб IDE коректно обробляла файли `.vue` та `vite-env.d.ts`.
23
+ passFn — підтверджує наявність файлу `jsconfig.json` у вказаній директорії.
24
+ check — перевіряє, чи відповідає проєкт вимогам vue.mdc, а саме: чи є `vue` у залежностях кореневого та всіх workspace-пакетів.
29
25
 
30
26
  ## Гарантії поведінки
31
27
 
32
- - Read-only: файл не виконує операцій запису у файлову систему.
33
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
28
+ - Read-only: не виконує операцій запису (ФС/БД).
29
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
34
30
  - Свідомо пропускає шляхи: `.git`, `node_modules`.
35
- - Не звертається до мережі.
@@ -1,5 +1,5 @@
1
1
  /** @see ./docs/packages.md */
2
- import { existsSync } from 'node:fs'
2
+ import { existsSync, readFileSync } from 'node:fs'
3
3
  import { readFile } from 'node:fs/promises'
4
4
  import { join, relative } from 'node:path'
5
5
 
@@ -500,6 +500,45 @@ async function checkVueVolarRecommendation(pass, fail, cwd) {
500
500
  }
501
501
  }
502
502
 
503
+ // Vitest-пакети мусять бути у кореневому devDependencies монорепо,
504
+ // бо npm-module правило забороняє devDeps у published Vue workspace.
505
+ const ROOT_VITEST_DEV_DEPS = ['vitest', '@vitest/coverage-v8', '@stryker-mutator/vitest-runner']
506
+
507
+ /**
508
+ * Перевіряє, що кореневий `package.json` монорепо містить vitest-залежності
509
+ * у `devDependencies`. Викликається тільки коли є Vue-пакети у воркспейсі.
510
+ * @param {string} cwd корінь репозиторію
511
+ * @param {(msg: string) => void} pass pass callback
512
+ * @param {(msg: string) => void} fail fail callback
513
+ * @returns {void}
514
+ */
515
+ function checkRootVitestDevDeps(cwd, pass, fail) {
516
+ const rootPkgPath = join(cwd, 'package.json')
517
+ if (!existsSync(rootPkgPath)) {
518
+ fail('vue: кореневий package.json не знайдено — неможливо перевірити vitest devDependencies')
519
+ return
520
+ }
521
+ let rootPkg
522
+ try {
523
+ rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'))
524
+ } catch {
525
+ fail('vue: кореневий package.json не вдалося розпарсити — неможливо перевірити vitest devDependencies')
526
+ return
527
+ }
528
+ const devDeps =
529
+ rootPkg.devDependencies && typeof rootPkg.devDependencies === 'object' ? Object.keys(rootPkg.devDependencies) : []
530
+ const missing = ROOT_VITEST_DEV_DEPS.filter(p => !devDeps.includes(p))
531
+ if (missing.length === 0) {
532
+ pass(`vue: кореневий devDependencies містить ${ROOT_VITEST_DEV_DEPS.join(', ')} (vue.mdc testing)`)
533
+ } else {
534
+ for (const pkg of missing) {
535
+ fail(
536
+ `vue: кореневий devDependencies не містить '${pkg}' — перенеси з Vue workspace у корінь монорепо (vue.mdc testing)`
537
+ )
538
+ }
539
+ }
540
+ }
541
+
503
542
  /**
504
543
  * Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
505
544
  * @param {string} [cwd] корінь репозиторію
@@ -519,6 +558,7 @@ export async function check(cwd = process.cwd()) {
519
558
  }
520
559
 
521
560
  await checkVueVolarRecommendation(pass, fail, cwd)
561
+ checkRootVitestDevDeps(cwd, pass, fail)
522
562
 
523
563
  const ignorePaths = await loadCursorIgnorePaths(cwd)
524
564
  for (const { rootDir, isComponentLibrary } of vueRoots) {
@@ -6,14 +6,14 @@ resource: npm/scripts/lib/fix/
6
6
 
7
7
  # npm/scripts/lib/fix
8
8
 
9
- | Файл | Тип |
10
- |---|---|
11
- | [analyze-escalation.mjs](analyze-escalation.md) | JS Module |
12
- | [escalation-log.mjs](escalation-log.md) | JS Module |
13
- | [llm-fix-apply.mjs](llm-fix-apply.md) | JS Module |
14
- | [llm-lint-fix.mjs](llm-lint-fix.md) | JS Module |
15
- | [llm-worker.mjs](llm-worker.md) | JS Module |
16
- | [orchestrator.mjs](orchestrator.md) | JS Module |
9
+ | Файл | Тип |
10
+ | ----------------------------------------------------- | --------- |
11
+ | [analyze-escalation.mjs](analyze-escalation.md) | JS Module |
12
+ | [escalation-log.mjs](escalation-log.md) | JS Module |
13
+ | [llm-fix-apply.mjs](llm-fix-apply.md) | JS Module |
14
+ | [llm-lint-fix.mjs](llm-lint-fix.md) | JS Module |
15
+ | [llm-worker.mjs](llm-worker.md) | JS Module |
16
+ | [orchestrator.mjs](orchestrator.md) | JS Module |
17
17
  | [run-conformance-check.mjs](run-conformance-check.md) | JS Module |
18
- | [t0.mjs](t0.md) | JS Module |
19
- | [verbose-block.mjs](verbose-block.md) | JS Module |
18
+ | [t0.mjs](t0.md) | JS Module |
19
+ | [verbose-block.mjs](verbose-block.md) | JS Module |
@@ -3,62 +3,32 @@ type: JS Module
3
3
  title: orchestrator.mjs
4
4
  resource: npm/scripts/lib/fix/orchestrator.mjs
5
5
  docgen:
6
- crc: 08d96254
7
- model: claude-sonnet-4-6
6
+ crc: c34d0630
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  ---
10
10
 
11
- Оркеструє повний цикл виправлення порушень конформності: детермінований T0-фікс без LLM → LLM-драбина ескалації (local-min → local-min-retry → cloud-min → cloud-avg) → фінальна перевірка. Кожне провальне правило проходить через тири послідовно до першого зеленого re-check або до обриву (no-key, systemic-помилка, cloud transport fail, вичерпаний avg-кеп).
11
+ ## Огляд
12
12
 
13
- ## Поведінка
14
-
15
- ### `buildLadder`
16
-
17
- Будує масив рунгів ескалації з чотирьох тирів:
18
-
19
- | Тир | Модель | Feedback | Timeout |
20
- | ----------------- | ----------- | -------- | ------------------------- |
21
- | `local-min` | `LOCAL_MIN` | ні | `LOCAL_TIMEOUT_MS` (45s) |
22
- | `local-min-retry` | `LOCAL_MIN` | так | `LOCAL_TIMEOUT_MS` |
23
- | `cloud-min` | `CLOUD_MIN` | так | `CLOUD_TIMEOUT_MS` (120s) |
24
- | `cloud-avg` | `CLOUD_AVG` | так | `CLOUD_TIMEOUT_MS` |
25
-
26
- Рунги з порожньою моделлю (`''`) відфільтровуються — драбина стискається до доступних тирів.
27
-
28
- ### `escalateRule`
13
+ Модуль відповідає за управління процесом вирішення порушень. Він будує послідовність тирів ескалації за допомогою `buildLadder`. Функція `parseOrchestratorArgs` визначає бюджет LLM та фільтр правил. Далі, `runOrchestratorCli` виконує процес виправлення правил, послідовно застосовуючи `escalateRule` по тирах до досягнення першого успішного вирішення.
29
14
 
30
- Проводить одне правило через драбину до першого зеленого re-check. На кожному рунзі:
31
-
32
- 1. Виклик `worker.runLlmWorker` (синхронно) з feedback від попереднього рунга.
33
- 2. Re-check через `check([ruleId], cwd)`.
34
- 3. Запис у escalation-лог (`logEscalation`).
35
- 4. Якщо re-check зелений → `{ resolved: true }`.
36
- 5. Якщо ні → `decideAfterFailure` визначає дію: `break` (no-key або cloud transport fail), `skip-model` (systemic omlx-помилка), або продовжити.
37
- 6. Avg-рунг пропускається, якщо `avgBudget <= 0` (з фіксацією у лог).
38
-
39
- Після кожного рунга виводить verbose-блок (`printVerboseBlock`), якщо `N_CURSOR_FIX_VERBOSE !== 'off'`.
40
-
41
- ### `parseOrchestratorArgs`
15
+ ## Поведінка
42
16
 
43
- Витягує `--max-avg N` (default: 3) і збирає позиційні аргументи як `ruleFilter`.
17
+ buildLadder будує послідовність тирів ескалації для вирішення порушень.
18
+ escalateRule проходить по послідовності тирів, намагаючись вирішити одне правило до першого успішного перевірки.
19
+ parseOrchestratorArgs парсить аргументи командного рядка для визначення максимального бюджету LLM та фільтра правил.
20
+ runOrchestratorCli виконує повний процес виправлення правил, використовуючи послідовність тирів та бюджет LLM.
44
21
 
45
- ### `runOrchestratorCli`
22
+ ## Публічний API
46
23
 
47
- Повний цикл:
24
+ buildLadder — Створює послідовність моделей для ескалації, виходячи з доступних рівнів. Недоступні рівні ігноруються.
48
25
 
49
- 1. Початкова conformance-перевірка якщо чисто, exit 0.
50
- 2. `runT0Step` — детермінований фікс без LLM; якщо після нього чисто, exit 0.
51
- 3. Для кожного правила, що лишилося — `escalateRule` з відстеженням `avgBudget`.
52
- 4. Фінальна перевірка → exit 0 якщо чисто, exit 1 якщо є невирішені.
26
+ escalateRule Виконує один етап перевірки за драбиною ескалації. Для кожного кроку викликається модель, відбувається повторна перевірка правила, і результат фіксується в лозі. Процес може зупинитися достроково при певних умовах або після вичерпання ліміту.
53
27
 
54
- ## Публічний API
28
+ parseOrchestratorArgs Витягує максимальне значення для середнього бюджету з аргументів командного рядка та збирає список фільтрів правил.
55
29
 
56
- - `buildLadder({ localMin, cloudMin, cloudAvg })` повертає масив рунгів ескалації.
57
- - `escalateRule(rule, cwd, deps)` — проводить одне правило через драбину; `deps` дозволяє ін'єкцію worker/check/clock для тестів; повертає `{ resolved, avgUsed }`.
58
- - `parseOrchestratorArgs(args)` — повертає `{ maxAvg, ruleFilter }`.
59
- - `runOrchestratorCli(args, cwd)` — CLI-точка входу; повертає `Promise<0|1>`.
30
+ runOrchestratorCli Запускає основний процес оркестрації, обробляючи аргументи та керуючи виконанням правил.
60
31
 
61
32
  ## Гарантії поведінки
62
33
 
63
- - Мутує файли проєкту лише через `worker.runLlmWorker` (apply-changes) і T0-auto.
64
- - Не кидає винятків назовні: помилки LLM перехоплює worker і повертає як `res.error`.
34
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -153,10 +153,6 @@ export async function escalateRule(rule, cwd, deps) {
153
153
  log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
154
154
  }
155
155
 
156
- // DEBUG
157
- console.error(
158
- `[DBG] verbose check: VERBOSE=${env.N_CURSOR_FIX_VERBOSE} promptSummary=${JSON.stringify(res.promptSummary)} reasoning=${res.reasoning}`
159
- )
160
156
  if (env.N_CURSOR_FIX_VERBOSE !== 'off' && res.promptSummary) {
161
157
  printVerboseBlock(rule.ruleId, res.promptSummary, res.reasoning ?? null, res.reasoningSource ?? null)
162
158
  }