@nitra/cursor 9.4.0 → 10.0.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
+ ## [10.0.1] - 2026-06-14
4
+
5
+ ### Changed
6
+
7
+ - 📝 docs(fix/lint): omlx-генерація доків для нових/перероблених файлів (point 4 docgen)
8
+
9
+ ## [10.0.0] - 2026-06-14
10
+
11
+ ### Changed
12
+
13
+ - Інлайн _fix-check/fix-t0 у прямі функції — видалено внутрішні subcommand'и. runFixCheck (scripts/lib/fix/run-fix-check) + listProjectRulesMdcFiles винесено з bin; orchestrator/t0/PostToolUse-хук/lint-конформність кличуть прямо (без subprocess-обгортки). Per-rule ізоляція збережена
14
+
3
15
  ## [9.4.0] - 2026-06-14
4
16
 
5
17
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -97,8 +97,7 @@ import { readSkillMetaRaw } from '../scripts/lib/skill-meta.mjs'
97
97
  import { injectWorktreeNotice } from '../scripts/lib/worktree-notice.mjs'
98
98
  import { injectRootNotice } from '../scripts/lib/root-notice.mjs'
99
99
  import { runPostToolUseFixCli } from '../scripts/post-tool-use-fix.mjs'
100
- import { discoverCheckRulesFromCursorRules } from '../scripts/lib/discover-check-rules-from-cursor.mjs'
101
- import { listRuleIds } from '../scripts/lib/list-rule-ids.mjs'
100
+ import { listProjectRulesMdcFiles } from '../scripts/lib/list-project-rules-mdc.mjs'
102
101
  import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
103
102
  import { assertCwdIsProjectRoot } from '../scripts/lib/assert-project-root.mjs'
104
103
  import { runLintDocker } from '../rules/docker/lint/lint.mjs'
@@ -114,8 +113,6 @@ import { runSkillsCli } from '../scripts/skills-cli.mjs'
114
113
  import { runWorktreeCli } from '../scripts/worktree-cli.mjs'
115
114
  import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
116
115
  import { runLint } from '../rules/lint/js/orchestrate.mjs'
117
- import { formatTimingSummary } from '../scripts/lib/timing-summary.mjs'
118
- import { ensureHkInstall, ensureTool } from '../scripts/lib/ensure-tool.mjs'
119
116
 
120
117
  const PACKAGE_NAME = '@nitra/cursor'
121
118
  const CONFIG_FILE = '.n-cursor.json'
@@ -525,19 +522,6 @@ function formatPiSkillFrontmatter(skillName, descriptionRaw) {
525
522
  return `---\nname: ${skillName}\ndescription: >-\n ${text}\n---\n\n`
526
523
  }
527
524
 
528
- /**
529
- * Повертає відсортовані імена *.mdc у .cursor/rules поточного проєкту
530
- * @returns {Promise<string[]>} базові імена файлів (лише .mdc)
531
- */
532
- async function listProjectRulesMdcFiles() {
533
- const rulesDir = join(cwd(), RULES_DIR)
534
- if (!existsSync(rulesDir)) {
535
- return []
536
- }
537
- const names = await readdir(rulesDir)
538
- return names.filter(n => n.endsWith('.mdc')).toSorted((a, b) => a.localeCompare(b))
539
- }
540
-
541
525
  /**
542
526
  * Базові імена файлів .mdc, які очікуються згідно з .n-cursor.json (префікс n-).
543
527
  * @param {string[]} configRules елементи масиву rules з конфігу
@@ -1242,120 +1226,6 @@ function logRemovedManagedItems(title, basePath, names) {
1242
1226
  }
1243
1227
  }
1244
1228
 
1245
- /**
1246
- * Визначає список правил для fix: явно задані (з валідацією проти available) або
1247
- * discovery з `.cursor/rules/*.mdc`. Друкує діагностику й кидає на невідомих правилах.
1248
- * @param {string[]} requestedRules запитані правила (порожній → discovery)
1249
- * @param {string[]} available доступні в пакеті rule-id
1250
- * @param {boolean} json json-режим (впливає на early-return вивід при порожньому discovery)
1251
- * @returns {Promise<string[]|null>} список id або null — нічого запускати (вивід уже зроблено)
1252
- */
1253
- async function resolveFixRuleIds(requestedRules, available, json) {
1254
- if (requestedRules.length > 0) {
1255
- const unknown = requestedRules.filter(id => !available.includes(id))
1256
- if (unknown.length > 0) {
1257
- console.error(`❌ Невідомі правила: ${unknown.join(', ')}`)
1258
- console.log(` Доступні: ${available.join(', ')}`)
1259
- throw new Error(`Unknown rules: ${unknown.join(', ')}`)
1260
- }
1261
- return requestedRules
1262
- }
1263
-
1264
- const mdcFiles = await listProjectRulesMdcFiles()
1265
- if (mdcFiles.length === 0) {
1266
- throw new Error(
1267
- `Немає файлів *.mdc у ${RULES_DIR}/. Запустіть \`npx ${PACKAGE_NAME}\` або вкажіть правила: \`npx ${PACKAGE_NAME} fix bun ga\``
1268
- )
1269
- }
1270
- const idsToRun = discoverCheckRulesFromCursorRules(available, mdcFiles)
1271
- if (idsToRun.length === 0) {
1272
- if (json) {
1273
- process.stdout.write(`${JSON.stringify({ total: 0, failed: 0, rules: [] })}\n`)
1274
- return null
1275
- }
1276
- console.log(
1277
- `\n🔍 ${PACKAGE_NAME} fix — у ${RULES_DIR}/ немає правил з programmatic перевіркою ` +
1278
- `(відповідного fix.mjs у пакеті). Нічого не запущено.\n`
1279
- )
1280
- return null
1281
- }
1282
- return idsToRun
1283
- }
1284
-
1285
- /**
1286
- * Прогоняє `fix.mjs` кожного правила окремим процесом; збирає timings і (для json) per-rule output.
1287
- * @param {string[]} idsToRun правила до запуску
1288
- * @param {boolean} json json-режим — захоплює stdout/stderr у структуру замість inherit у термінал
1289
- * @returns {{ totalFailed: number, timings: {id:string, ms:number, ok:boolean}[], ruleResults: {ruleId:string, ok:boolean, output:string}[] }} підсумок прогону
1290
- */
1291
- function runRuleFixProcesses(idsToRun, json) {
1292
- let totalFailed = 0
1293
- const timings = []
1294
- const ruleResults = []
1295
- for (const id of idsToRun) {
1296
- const fixPath = join(BUNDLED_RULES_DIR, id, 'fix.mjs')
1297
- const startedAt = Date.now()
1298
- const result = json
1299
- ? spawnSync('bun', [fixPath], { encoding: 'utf8' })
1300
- : spawnSync('bun', [fixPath], { stdio: 'inherit' })
1301
- const ok = result.status === 0
1302
- timings.push({ id: `fix-${id}`, ms: Date.now() - startedAt, ok })
1303
- if (json) {
1304
- ruleResults.push({ ruleId: id, ok, output: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim() })
1305
- }
1306
- if (!ok) totalFailed++
1307
- }
1308
- return { totalFailed, timings, ruleResults }
1309
- }
1310
-
1311
- /**
1312
- * Spawn-wrapper для `npx \@nitra/cursor fix [<rule>...]`. Один шлях у коді: для кожного правила
1313
- * робить `bun rules/<id>/fix.mjs` як окремий процес. Сам `fix.mjs` читає `.n-cursor.json`,
1314
- * перевіряє whitelist (`runRuleCli`) і друкує per-rule summary.
1315
- *
1316
- * Без аргументів — discover з `.cursor/rules/*.mdc` у проекті-споживачі.
1317
- *
1318
- * Серіалізація паралельних запусків — per-rule, всередині `runStandardRule` (`withLock('fix-<id>')`).
1319
- * На рівні `runFixCommand` локу нема: різні набори правил можуть прогресувати незалежно,
1320
- * а однакові правила серіалізуються в spawn'ах нижче.
1321
- * @param {string[]} requestedRules імена правил; порожній масив — discovery з `.cursor/rules/`
1322
- * @param {{json?: boolean}} [opts] json — друкувати компактний JSON `{total, failed, rules:[{ruleId, ok, output}]}`
1323
- * замість per-rule stdio + timing (для скілу n-fix: агент читає лише впалі правила, не парсить текст)
1324
- * @returns {Promise<void>}
1325
- */
1326
- async function runFixCommand(requestedRules, opts = {}) {
1327
- const json = opts.json === true
1328
- // json-режим — діагностичний read-only вивід для скілу: пропускаємо встановлення
1329
- // git-hook (`ensureHkInstall` друкує «Installed hk hook…» у stdout і забруднив би
1330
- // чистий JSON; сам pre-commit hook для діагностики не потрібен).
1331
- const hkBin = ensureTool('hk')
1332
- if (!json) ensureHkInstall(hkBin)
1333
- ensureTool('conftest')
1334
-
1335
- const available = await listRuleIds(BUNDLED_RULES_DIR)
1336
- if (available.length === 0) {
1337
- console.error('❌ Не знайдено жодного правила у пакеті')
1338
- throw new Error('No rules found')
1339
- }
1340
-
1341
- // json-режим: захоплюємо stdout/stderr правила у структуру (а не inherit у термінал),
1342
- // щоб віддати агенту згруповано {ruleId, ok, output} і він читав лише впалі.
1343
- const idsToRun = await resolveFixRuleIds(requestedRules, available, json)
1344
- if (idsToRun === null) return
1345
-
1346
- const { totalFailed, timings, ruleResults } = runRuleFixProcesses(idsToRun, json)
1347
-
1348
- if (json) {
1349
- process.stdout.write(`${JSON.stringify({ total: idsToRun.length, failed: totalFailed, rules: ruleResults })}\n`)
1350
- } else {
1351
- process.stdout.write(formatTimingSummary('Fix timing', timings))
1352
- }
1353
-
1354
- if (totalFailed > 0) {
1355
- throw new Error(`${totalFailed} з ${idsToRun.length} правил мають проблеми`)
1356
- }
1357
- }
1358
-
1359
1229
  /**
1360
1230
  * Читає поле `version` з `package.json` пакету за абсолютним шляхом до його кореня.
1361
1231
  * @param {string} packageRoot корінь пакету (тека з `package.json`)
@@ -1643,13 +1513,6 @@ try {
1643
1513
  }
1644
1514
  await ensureNitraCursorInRootDevDependencies(cwd())
1645
1515
  switch (command) {
1646
- case '_fix-check': {
1647
- // Внутрішня команда движка конформності (не публічний API): per-rule fix.mjs run() = детект.
1648
- // Повертає JSON {total, failed, rules:[{ruleId, ok, output}]} у stdout. Викликається
1649
- // конформність-фазою `lint` (read-only) і движком orchestrator/t0.
1650
- await runFixCommand(args, { json: true })
1651
- break
1652
- }
1653
1516
  case 'rename-yaml-extensions': {
1654
1517
  const code = await runRenameYamlExtensionsCli(args)
1655
1518
  if (code !== 0) {
@@ -1753,16 +1616,6 @@ try {
1753
1616
 
1754
1617
  break
1755
1618
  }
1756
- case 'fix-t0': {
1757
- // Внутрішня фаза движка конформності (не публічний API): T0-auto рівень.
1758
- // Запускає _fix-check, знаходить violation-output із детермінованим паттерном
1759
- // (vscode-ext-add, rm-forbidden-file тощо), застосовує програмний фікс (0 LLM),
1760
- // перевіряє check-gate. Викликається orchestrator.mjs (fix-режим конформності lint).
1761
- const { runT0AutoCli } = await import('../scripts/lib/fix/t0.mjs')
1762
- process.exitCode = await runT0AutoCli(args, cwd())
1763
-
1764
- break
1765
- }
1766
1619
  case 'change': {
1767
1620
  const { runChangeCli } = await import('../rules/release/change.mjs')
1768
1621
  process.exitCode = await runChangeCli(args)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "9.4.0",
3
+ "version": "10.0.1",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,27 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/lint/fix.mjs
4
+ crc: f85a9e1d
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # fix.mjs
10
+
11
+ ## Огляд
12
+
13
+ Модуль забезпечує виконання логіки, визначеної правилом. Він ініціює виконання правила через публічну функцію `run`.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Викликати функцію `run` для виконання логіки правила.
18
+ 2. Якщо код виконується як CLI, викликати функцію для запуску правила через CLI.
19
+
20
+ ## Публічний API
21
+
22
+ run — виконує правило `lint` в lint-оркестраторі. Якщо правило не містить перевірок (concern-ів/policy), то `run` не робить нічого (no-op), оскільки не знаходить жодних concern-ів.
23
+
24
+ ## Гарантії поведінки
25
+
26
+ - Read-only: файл не виконує операцій запису у файлову систему.
27
+ - Не звертається до мережі.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  docgen:
3
- source: rules/lint/js/orchestrate.mjs
4
- crc: 34ce8f70
3
+ source: npm/rules/lint/js/orchestrate.mjs
4
+ crc: 2e5db1e7
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
5
6
  score: 100
6
7
  ---
7
8
 
@@ -9,20 +10,21 @@ docgen:
9
10
 
10
11
  ## Огляд
11
12
 
12
- Файл оркеструє запуск `n-cursor lint` (quick) або `n-cursor lint-ci` (full). Він збирає налаштування правил з файлів `meta.json` і послідовно виконує перевірку відповідно до визначених режимів.
13
+ Оркестратор `n-cursor lint` визначає, які правила лінтування застосовувати, керуючись двома ортогональними осями: консолідацією правил та уніфікацією режиму `readonly`. Вибір правил відбувається на основі конфігурацій, зокрема файлів `meta.json`, які визначають обсяг дії (`per-file` чи `full`) для кожного правила. Запуск лінтування може сканувати лише змінені файли відносно origin (за замовчуванням) або весь репозиторій при використанні прапорця `--full`.
13
14
 
14
15
  ## Поведінка
15
16
 
16
- selectLintRules
17
- Вибирає id правил для фази алфавітним порядком
18
-
19
- runLint
20
- Запускає lint-оркестрацію
17
+ Поведінка
18
+ selectLintRules вибирає і сортує ідентифікатори правил на основі їхнього обсягу дії (`per-file` або `full`) та прапорця `--full`.
19
+ runLint запускає оркестрацію лінтування: або виконує перевірку конформності для заданих правил, або ітерує по алфавітно відсортованих правилах, запускаючи лінтер для змінених файлів (за замовчуванням), або виконує перевірку конформності всього репозиторію при використанні прапорця `--full`.
21
20
 
22
21
  ## Публічний API
23
22
 
24
- selectLintRules — Вибирає ідеальну послідовність правил для фази, упорядковуючи їх за алфавітом.
25
- runLint — Запускає повний процес перевірки (lint-оркестрацію).
23
+ selectLintRules — Вибирає ідентифікатори правил для контексту в алфавітному порядку.
24
+ runLint — Запускає процес лінтування.
25
+ full — Сканує весь репозиторій порівняно з початковим станом.
26
+ readOnly — Виявляє проблеми без внесення змін.
27
+ rules — Перевіряє лише відповідність заданого набору правил, ігноруючи повне сканування.
26
28
 
27
29
  ## Гарантії поведінки
28
30
 
@@ -14,7 +14,6 @@
14
14
  import { existsSync, readdirSync } from 'node:fs'
15
15
  import { dirname, join } from 'node:path'
16
16
  import { fileURLToPath } from 'node:url'
17
- import { spawnSync } from 'node:child_process'
18
17
  import { cwd as processCwd } from 'node:process'
19
18
 
20
19
  import { parseRuleLintSpec, readRuleMetaRaw } from '../../../scripts/lib/rule-meta.mjs'
@@ -23,7 +22,6 @@ import { collectChangedFilesSince, resolveChangedBase } from '../../../scripts/l
23
22
  // Цей файл: npm/rules/lint/js/orchestrate.mjs → PACKAGE_ROOT = npm (чотири dirname угору).
24
23
  const PACKAGE_ROOT = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
25
24
  const RULES_DIR = join(PACKAGE_ROOT, 'rules')
26
- const N_CURSOR_BIN = join(PACKAGE_ROOT, 'bin', 'n-cursor.js')
27
25
 
28
26
  /**
29
27
  * Конформність-фаза lint (whole-repo: config/file/workflow conformance — те, що раніше робив `fix`).
@@ -41,20 +39,11 @@ async function runConformance(cwd, readOnly, log, filter = []) {
41
39
  const { runOrchestratorCli } = await import('../../../scripts/lib/fix/orchestrator.mjs')
42
40
  return runOrchestratorCli(filter, cwd)
43
41
  }
44
- const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...filter], { cwd, encoding: 'utf8', timeout: 600_000 })
45
- let parsed = null
46
- try {
47
- parsed = JSON.parse((r.stdout ?? '').trim())
48
- } catch {
49
- parsed = null
50
- }
51
- if (!parsed) {
52
- log('❌ lint: конформність — помилка перевірки (_fix-check не повернув JSON)\n')
53
- return 1
54
- }
55
- const failed = parsed.rules.filter(/** @param {{ok:boolean}} x */ x => !x.ok)
42
+ const { runFixCheck } = await import('../../../scripts/lib/fix/run-fix-check.mjs')
43
+ const { rules } = await runFixCheck(filter, cwd)
44
+ const failed = rules.filter(x => !x.ok)
56
45
  if (failed.length === 0) return 0
57
- log(`❌ lint: конформність — ${failed.length} порушень: ${failed.map(/** @param {{ruleId:string}} x */ x => x.ruleId).join(', ')}\n`)
46
+ log(`❌ lint: конформність — ${failed.length} порушень: ${failed.map(x => x.ruleId).join(', ')}\n`)
58
47
  for (const f of failed) if (f.output) log(`${f.output}\n`)
59
48
  return 1
60
49
  }
@@ -0,0 +1,28 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/text/lint/cspell-fix.mjs
4
+ crc: 3ce66d8a
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 90
7
+ ---
8
+
9
+ # cspell-fix.mjs
10
+
11
+ ## Огляд
12
+
13
+ Модуль інтегрує `cspell` у ланцюжок `lint-text` для виявлення орфографічних помилок. У режимі з можливістю виправлення, процес відбувається шляхом: детект (захоплення виводу) $\rightarrow$ групування знахідок по файлах $\rightarrow$ per-file `omlx-фікс` справжніх помилок (`llmLintFix`) $\rightarrow$ re-detect. У read-only режимі виконується лише детект, що гарантує нуль мутацій. Валідні терміни omlx залишаються, їх ловить повторний `cspell` (далі — у словник `@nitra/cspell-dict`).
14
+
15
+ ## Поведінка
16
+
17
+ groupFindingsByFile групує вивід `cspell` за файлами, виділяючи знахідки.
18
+ runCspellText запускає `cspell` для виявлення орфографічних помилок, а при вимкненому режимі читання виконує автоматичне виправлення помилок за допомогою зовнішнього інструменту для файлів, що містять справжні помилки, і повторно перевіряє файли.
19
+
20
+ ## Публічний API
21
+
22
+ groupFindingsByFile — збирає знайдені помилки cspell у групи за файлами.
23
+ runCspellText — виконує перевірку тексту за правилами cspell, застосовуючи автоматичне виправлення від omlx.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Read-only: файл не виконує операцій запису у файлову систему.
28
+ - Не звертається до мережі.
@@ -1,226 +1,38 @@
1
1
  ---
2
2
  docgen:
3
3
  source: npm/rules/text/lint/lint.mjs
4
- crc: bdaef0f8
4
+ crc: d475ffbb
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 90
5
7
  ---
6
8
 
7
- # `lint.mjs` — CLI-обгортка `lint-text`
9
+ # lint.mjs
8
10
 
9
11
  ## Огляд
10
12
 
11
- Модуль `lint.mjs` це CLI-обгортка над канонічним підкомандним конвеєром `lint-text` (правило `text.mdc`). Він призначений для запуску ланцюжка лінтерів текстових і конфігураційних артефактів проєкту з автоматичним передвстановленням залежностей та послідовним виконанням кроків з ранньою зупинкою на першій помилці.
13
+ runLintTextCli надає CLI-обгортку для послідовного лінтингу текстових файлів (text.mdc). Обгортка автоматично встановлює необхідні інструменти (`shellcheck`, `dotenv-linter`) через `ensureTool` перед виконанням ланцюжка перевірок. Ланцюжок включає: перевірку правопису (`cspell`), аналіз скриптів (`shellcheck`), перевірку конфігураційних файлів (`dotenv-linter`), форматування Markdown (`markdownlint-cli2`) та валідацію схем (`v8r`). При виявленні першого ненульового коду в ланцюжку, цей код повертається як код виходу, і подальші кроки не виконуються. runLintTextCli експортується для використання як підкоманда `lint-text` з `bin/n-cursor.js`.
12
14
 
13
- Що робить файл:
15
+ ## Поведінка
14
16
 
15
- 1. Авто-встановлює зовнішні бінарники `shellcheck` і `dotenv-linter` через `ensureTool` (механізм brew/scoop/GitHub Release per-platform).
16
- 2. Перевіряє наявність системного `patch` (необхідний для авто-фіксу `shellcheck`) і друкує install-hint, якщо `patch` відсутній.
17
- 3. Послідовно запускає п'ять кроків лінтінгу:
18
- - `cspell .` перевірка правопису з використанням словника `@nitra/cspell-dict`;
19
- - `runShellcheckText()` авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
20
- - `runDotenvLinter()` авто-фікс і фінальна перевірка `.env*` через `dotenv-linter`;
21
- - `bunx markdownlint-cli2 --fix "**/*.md" "**/*.mdc"` авто-фікс Markdown;
22
- - `runV8rWithGlobs()` schema-валідація `json/json5/yaml/yml/toml` через `v8r`.
23
- 4. Першу ненульову exit-код у ланцюжку повертає як код виходу всього прогону; наступні кроки не запускаються.
17
+ 1. Викликається `runLintTextCli` для запуску процесу лінтингу.
18
+ 2. Якщо передано опцію для режиму лише перевірки, всі кроки виконуються без автоматичного виправлення.
19
+ 3. Перед виконанням лінтингу автоматично встановлюються необхідні інструменти (`shellcheck`, `dotenv-linter`).
20
+ 4. Перевіряється наявність інструменту `patch` для можливого автоматичного виправлення помилок `shellcheck`.
21
+ 5. Виконується перевірка правопису за допомогою `cspell`.
22
+ 6. Виконується автоматичне виправлення та фінальна перевірка скриптів `.sh` за допомогою `shellcheck`.
23
+ 7. Виконується автоматичне виправлення та фінальна перевірка файлів `.env*` за допомогою `dotenv-linter`.
24
+ 8. Виконується автоматичне виправлення файлів Markdown (`.md`, `.mdc`) за допомогою `markdownlint-cli2`.
25
+ 9. Виконується валідація схем для файлів JSON, JSON5, YAML, YML, TOML за допомогою `v8r`.
26
+ 10. Якщо будь-який крок повертає ненульовий код, виконання зупиняється, і повертається код цього першого невдалого кроку.
27
+ 11. У разі успішного виконання всіх кроків повертається код 0.
28
+ 12. У разі відсутності необхідних бінарників, `runLintTextCli` виводить повідомлення про помилку з інструкціями щодо встановлення (text.mdc).
24
29
 
25
- Призначення preflight-блоку: без авто-встановлення локальний прогін може успішно пройти `cspell` і `markdownlint`, а CI на `ubuntu-latest` (де `shellcheck` є передвстановленим, але `dotenv-linter` — ні) падає на кроці `dotenv-linter` з неінформативним повідомленням. `ensureTool` збирає всі відсутні бінарники до запуску першого кроку.
30
+ ## Публічний API
26
31
 
27
- Канон патерну `lint-*` (серіалізація через `runStandardLint`, без прямого `withLock`) описаний у `.cursor/rules/scripts.mdc`, секція «Серіалізація важких CLI-команд».
32
+ runLintTextCli Виконує лінтинг тексту через командний інтерфейс, синхронізуючи доступ за допомогою блокування та усуваючи дублікати на основі стану Git-дерева. (text.mdc)
28
33
 
29
- ## Експорти / API
34
+ ## Гарантії поведінки
30
35
 
31
- | Експорт | Тип | Призначення |
32
- | ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
- | `runLintTextCli` | `() => Promise<number>` | Публічна CLI-форма команди `lint-text`. Використовується з `bin/n-cursor.js` як підкоманда `lint-text`. Серіалізує виконання через `withLock('lint-text')` і додатково дедуплікує запуски за станом git-дерева через `runStandardLint`. |
34
-
35
- Інші ідентифікатори модуля (`PATCH_PREFLIGHT`, `resolvePreflightBin`, `printPreflightMissingMessage`, `preflight`, `runLintTextSteps`) — внутрішні; не експортуються і не призначені для зовнішнього використання.
36
-
37
- ## Функції
38
-
39
- ### `resolvePreflightBin(dep)`
40
-
41
- Шукає шлях до бінарника `dep.bin` у `PATH`. На Windows (`process.platform === 'win32'`) додатково перебирає альтернативні імена з `dep.winBins` (наприклад, для випадків з `.exe`/`.cmd` варіаціями).
42
-
43
- - Сигнатура: `function resolvePreflightBin(dep: PreflightDep): string | null`
44
- - Параметри:
45
- - `dep` — опис залежності з canon-списку preflight-перевірок (тип `PreflightDep`).
46
- - Повертає: абсолютний шлях до знайденого бінарника або `null`, якщо бінарник не знайдено.
47
- - Side effects: відсутні (виклик `resolveCmd` лише читає `PATH`, не модифікує процес).
48
-
49
- ### `printPreflightMissingMessage(dep)`
50
-
51
- Друкує stderr-повідомлення про відсутній бінарник: рядок-маркер з іменем, пояснення наслідків, install-команди і посилання на правило `text.mdc`.
52
-
53
- - Сигнатура: `function printPreflightMissingMessage(dep: PreflightDep): void`
54
- - Параметри:
55
- - `dep` — опис залежності, джерело пояснення (`dep.explanation`) та install-команд (`dep.install`).
56
- - Повертає: нічого (`undefined`).
57
- - Side effects: виводить кілька рядків у `console.error` (stderr). Структура виводу:
58
- - `❌ <bin> не знайдено в PATH.`
59
- - відступний рядок з `dep.explanation`;
60
- - заголовок ` Встанови:`;
61
- - по рядку для кожного елемента `dep.install`;
62
- - підказку ` Деталі: text.mdc → секція про lint-text.`
63
-
64
- ### `preflight(dep)`
65
-
66
- Виконує preflight-перевірку наявності бінарника й сигналізує результат через консоль.
67
-
68
- - Сигнатура: `function preflight(dep: PreflightDep): boolean`
69
- - Параметри:
70
- - `dep` — опис залежності для перевірки в `PATH`.
71
- - Повертає: `true`, якщо бінарник знайдено; `false`, якщо відсутній.
72
- - Side effects:
73
- - На pass-шляху друкує `dep.successMsg` у `console.log`;
74
- - На fail-шляху викликає `printPreflightMissingMessage(dep)` (вивід у `console.error`).
75
-
76
- ### `runLintTextSteps()`
77
-
78
- Внутрішня функція, що послідовно прокручує всі кроки `lint-text` без захоплення локу (лок забезпечується зовнішньою обгорткою `runStandardLint`).
79
-
80
- - Сигнатура: `function runLintTextSteps(): number`
81
- - Параметри: немає.
82
- - Повертає: `0`, якщо всі кроки успішні; інакше — exit-код першого кроку, що повернув ненульове значення.
83
- - Алгоритм:
84
- 1. `ensureTool('shellcheck')` — авто-встановлення `shellcheck`; кидає виключення на провал (поширюється як exit `1` через зовнішній `runStandardLint`).
85
- 2. `ensureTool('dotenv-linter')` — те саме для `dotenv-linter`.
86
- 3. `preflight(PATCH_PREFLIGHT)` — hint-only перевірка `patch`; якщо `patch` відсутній — повертає `1` без подальших спроб.
87
- 4. `runLintStep('cspell', 'npx', ['cspell', '.'])` — `cspell .` через `npx`; ранній return при ненульовому коді.
88
- 5. Друк заголовку `▶ shellcheck (авто-фікс + фінальна перевірка *.sh)` і виклик `runShellcheckText()`; ранній return при ненульовому коді.
89
- 6. Друк заголовку `▶ dotenv-linter (авто-фікс + фінальна перевірка .env*)` і виклик `runDotenvLinter()`; ранній return при ненульовому коді.
90
- 7. `runLintStep('markdownlint', 'bunx', ['markdownlint-cli2', '--fix', '**/*.md', '**/*.mdc'])` — авто-фікс Markdown; ранній return при ненульовому коді.
91
- 8. Друк заголовку `▶ v8r (schema-валідація json/json5/yaml/yml/toml)` і повернення результату `runV8rWithGlobs()` як підсумкового exit-коду.
92
- - Side effects: записує лог-рядки у `stdout`; запускає дочірні процеси через `runLintStep` / `ensureTool` / спеціалізовані обгортки; модифікує файлову систему через авто-фікс-кроки (`shellcheck -f diff` + `patch -p1`, `dotenv-linter --fix`, `markdownlint-cli2 --fix`).
93
-
94
- ### `runLintTextCli` (експорт)
95
-
96
- Публічна CLI-форма команди.
97
-
98
- - Сигнатура: `const runLintTextCli: () => Promise<number>`
99
- - Параметри: немає.
100
- - Повертає: `Promise<number>` — фінальний exit-код прогону (після lock + дедупу).
101
- - Реалізація: делегує до `runStandardLint(import.meta.dirname, () => runLintTextSteps())`, тобто:
102
- - `runStandardLint` бере лок з ім'ям, похідним від директорії скрипту (`import.meta.dirname`);
103
- - дедуплікує запуски за станом git-дерева (якщо нічого не змінилося з попереднього успішного прогону — повторного виконання не буде);
104
- - всередині локу викликає `runLintTextSteps()`.
105
- - Side effects: лок-файл, лог-рядки, дочірні процеси (через внутрішній `runLintTextSteps`).
106
-
107
- ## Типи
108
-
109
- ### `PreflightDep`
110
-
111
- Опис однієї залежності preflight-блоку. Визначений локальним JSDoc `@typedef`-ом.
112
-
113
- | Поле | Тип | Опис |
114
- | ------------- | ----------------- | ---------------------------------------------------------------------------- |
115
- | `bin` | `string` | Ім'я виконуваного файлу (POSIX-варіант). |
116
- | `winBins` | `string[]` (опц.) | Альтернативні імена бінарника на Windows. |
117
- | `explanation` | `string` | Наслідки відсутності бінарника (для людино-зрозумілого stderr-повідомлення). |
118
- | `install` | `string[]` | Команди встановлення, по одному рядку на спосіб/платформу. |
119
- | `successMsg` | `string` | Повідомлення для `console.log` на pass-шляху preflight. |
120
-
121
- ## Константи
122
-
123
- ### `PATCH_PREFLIGHT`
124
-
125
- Єдиний об'єкт типу `PreflightDep`, що описує системний `patch` як hint-only залежність:
126
-
127
- - `bin: 'patch'`;
128
- - `explanation: 'Без `patch` не застосуються авто-виправлення shellcheck (`shellcheck -f diff`+`patch -p1`).'` (зібраний через `[...].join('\n ')` з одного елемента; результат — рівно одна логічна підказка з відступом сумісним з шаблоном виводу `printPreflightMissingMessage`);
129
- - `install`:
130
- - `'macOS: зазвичай уже є в системі'`;
131
- - `'Debian/Ubuntu: sudo apt-get install -y patch'`;
132
- - `successMsg: '✅ patch знайдено в PATH — shellcheck auto-fix працюватиме'`.
133
-
134
- `winBins` не задано — на Windows для `patch` шукається лише власне ім'я.
135
-
136
- ## Залежності
137
-
138
- ### Зовнішні модулі (Node built-ins)
139
-
140
- - `node:process` — імпортується іменована змінна `platform` для детекції Windows у `resolvePreflightBin`.
141
-
142
- ### Внутрішні модулі (відносні імпорти)
143
-
144
- | Модуль | Що використовується |
145
- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
146
- | `../../../scripts/lib/run-lint-step.mjs` | `runLintStep` — обгортка для запуску одного кроку лінту з префіксованим логом і нормалізованим exit-кодом. Використовується для `cspell` і `markdownlint`. |
147
- | `../../../scripts/utils/resolve-cmd.mjs` | `resolveCmd` — пошук бінарника в `PATH`. Викликається з `resolvePreflightBin`. |
148
- | `../../../scripts/lib/run-standard-lint.mjs` | `runStandardLint` — каноном обгортка `lint-*`: серіалізація через `withLock` + дедуп за станом git-дерева. Викликається з `runLintTextCli`. |
149
- | `../../../scripts/lib/ensure-tool.mjs` | `ensureTool` — авто-встановлення бінарників (brew/scoop/GitHub Release). Викликається для `shellcheck` і `dotenv-linter` на початку `runLintTextSteps`. |
150
- | `./run-dotenv-linter.mjs` | `runDotenvLinter` — авто-фікс + фінальна перевірка `.env*`. |
151
- | `./run-shellcheck.mjs` | `runShellcheckText` — авто-фікс + фінальна перевірка `*.sh` через `shellcheck`. |
152
- | `./run-v8r.mjs` | `runV8rWithGlobs` — schema-валідація `json/json5/yaml/yml/toml`. |
153
-
154
- ### Зовнішні CLI-інструменти (запускаються як дочірні процеси)
155
-
156
- - `npx cspell .` — перевірка правопису (зі словником `@nitra/cspell-dict`).
157
- - `shellcheck` — статичний аналізатор `*.sh` (передвстановлюється `ensureTool`).
158
- - `dotenv-linter` — лінтер `.env*` (передвстановлюється `ensureTool`).
159
- - `patch` — потрібен для `shellcheck -f diff | patch -p1` (hint-only, не встановлюється авто).
160
- - `bunx markdownlint-cli2 --fix '**/*.md' '**/*.mdc'` — авто-фікс Markdown.
161
- - `v8r` — schema-валідація конфігів (через `runV8rWithGlobs`).
162
-
163
- ### Правила-довідники
164
-
165
- - `.cursor/rules/text.mdc` (`text.mdc`) — канонічний опис набору `lint-text`.
166
- - `.cursor/rules/scripts.mdc` — секція «Серіалізація важких CLI-команд», що описує патерн `runStandardLint`/`withLock`.
167
-
168
- ## Потік виконання / Використання
169
-
170
- ### Виклик з CLI
171
-
172
- Файл експортує `runLintTextCli`, який підключається з `bin/n-cursor.js` як підкоманда `lint-text`. Очікувана схема виклику:
173
-
174
- ```bash
175
- n-cursor lint-text
176
- ```
177
-
178
- Команда повертає exit-код процесу, рівний фінальному коду повернення з `runLintTextCli`.
179
-
180
- ### Послідовність кроків (happy path)
181
-
182
- 1. Зовнішній `runStandardLint` намагається взяти лок `lint-text`. Якщо лок вже зайнятий або стан git-дерева не змінився — прогон може дедуплікуватися або зачекати.
183
- 2. Всередині локу викликається `runLintTextSteps()`:
184
- 1. `ensureTool('shellcheck')` — авто-встановлення (на CI/локально).
185
- 2. `ensureTool('dotenv-linter')` — те саме.
186
- 3. `preflight(PATCH_PREFLIGHT)` — друкує `✅ patch знайдено в PATH — shellcheck auto-fix працюватиме` або hint про встановлення.
187
- 4. `cspell .` — друк префіксованих логів через `runLintStep`.
188
- 5. `▶ shellcheck (авто-фікс + фінальна перевірка *.sh)` → `runShellcheckText()`.
189
- 6. `▶ dotenv-linter (авто-фікс + фінальна перевірка .env*)` → `runDotenvLinter()`.
190
- 7. `markdownlint-cli2 --fix '**/*.md' '**/*.mdc'` через `bunx`.
191
- 8. `▶ v8r (schema-валідація json/json5/yaml/yml/toml)` → `runV8rWithGlobs()`; повертає його результат.
192
- 3. `runStandardLint` повертає підсумковий exit-код як `Promise<number>`.
193
-
194
- ### Семантика помилок
195
-
196
- - Якщо `ensureTool` падає (не вдалося встановити `shellcheck` або `dotenv-linter`) — викидає виключення, яке поширюється з `runLintTextSteps` і обробляється на верхньому рівні (`runStandardLint`) як exit `1`.
197
- - Якщо `patch` відсутній — `preflight` повертає `false`, `runLintTextSteps` повертає `1` ще до запуску `cspell`.
198
- - Будь-який наступний крок (`cspell`, `shellcheck`, `dotenv-linter`, `markdownlint`, `v8r`), який повернув ненульовий код, припиняє ланцюжок: цей код повертається з `runLintTextSteps` і далі з `runLintTextCli`.
199
-
200
- ### Приклад використання у скрипті (Node)
201
-
202
- ```js
203
- import { runLintTextCli } from './lint.mjs'
204
-
205
- const code = await runLintTextCli()
206
- process.exit(code)
207
- ```
208
-
209
- ### Побічні ефекти на файлову систему
210
-
211
- Кроки `runShellcheckText`, `runDotenvLinter`, `markdownlint-cli2 --fix` і потенційно `v8r` можуть модифікувати файли (авто-фікс). Це штатна поведінка `lint-text` — після прогону можливі правки в робочому дереві.
212
-
213
- ## Rebuild Test
214
-
215
- З цієї документації можна відновити поведінку модуля:
216
-
217
- - Один експорт — асинхронна функція `runLintTextCli()` без параметрів, повертає `Promise<number>` (exit-код).
218
- - Реалізація `runLintTextCli`: `() => runStandardLint(import.meta.dirname, () => runLintTextSteps())`.
219
- - `runLintTextSteps()` синхронно:
220
- 1. викликає `ensureTool('shellcheck')` і `ensureTool('dotenv-linter')`;
221
- 2. виконує `preflight(PATCH_PREFLIGHT)` і повертає `1`, якщо `patch` відсутній;
222
- 3. послідовно запускає кроки `cspell` → `runShellcheckText` → `runDotenvLinter` → `markdownlint-cli2 --fix '**/*.md' '**/*.mdc'` → `runV8rWithGlobs`, друкуючи відповідні `▶`-заголовки перед shellcheck/dotenv/v8r;
223
- 4. ранній return на першому ненульовому коді; інакше повертає результат `runV8rWithGlobs()`.
224
- - Допоміжні функції: `resolvePreflightBin` (з підтримкою `winBins` на Windows), `printPreflightMissingMessage` (формат stderr-повідомлення), `preflight` (об'єднання resolve+print і друк `successMsg` на pass).
225
- - Константа `PATCH_PREFLIGHT` з полями `bin/explanation/install/successMsg` (без `winBins`).
226
- - Залежності: `node:process` (`platform`); локальні модулі `run-lint-step`, `resolve-cmd`, `run-standard-lint`, `ensure-tool`, `run-dotenv-linter`, `run-shellcheck`, `run-v8r`.
36
+ - Read-only: файл не виконує операцій запису у файлову систему.
37
+ - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
38
+ - Не звертається до мережі.
@@ -0,0 +1,28 @@
1
+ ---
2
+ docgen:
3
+ source: npm/scripts/lib/list-project-rules-mdc.mjs
4
+ crc: e17e0855
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # list-project-rules-mdc.mjs
10
+
11
+ ## Огляд
12
+
13
+ Експортує константу CURSOR_RULES_DIR, яка вказує на каталог правил у проєкті-споживачі. Надає функцію для отримання відсортованого списку всіх файлів правил `.mdc` з цього каталогу.
14
+
15
+ ## Поведінка
16
+
17
+ CURSOR_RULES_DIR — Вказує на каталог правил у проєкті-споживачі.
18
+ listProjectRulesMdcFiles — Повертає відсортований список імен файлів з розширенням `.mdc` у каталозі правил проєкту, або порожній масив, якщо каталог не існує.
19
+
20
+ ## Публічний API
21
+
22
+ CURSOR_RULES_DIR — Шлях до каталогу правил у проєкті-споживачі.
23
+ listProjectRulesMdcFiles — Збирає список файлів MDC, що містять правила проєкту.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Read-only: файл не виконує операцій запису у файлову систему.
28
+ - Не звертається до мережі.
@@ -0,0 +1,31 @@
1
+ ---
2
+ docgen:
3
+ source: npm/scripts/lib/fix/llm-fix-apply.mjs
4
+ crc: 80befb00
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # llm-fix-apply.mjs
10
+
11
+ ## Огляд
12
+
13
+ Модуль є спільним ядром для застосування виправлень, згенерованих LLM, використовуючи конформні (`llm-worker.mjs`) та інструментальні (`llm-lint-fix.mjs`) механізми для уникнення дублювання логіки парсингу та застосування змін. Він парсить структуровану відповідь моделі, що містить список змін у форматі `{changes:[{path,content}]}`. Далі, він зчитує вміст файлів, зазначених у цих змінах, та застосовує оновлений вміст до відповідних файлів. Усі операції реалізовані з механізмом fail-safe: при невдачах функції повертають значення помилки (false/null/Err) замість викидання винятків.
14
+
15
+ ## Поведінка
16
+
17
+ parseChangesResponse парсить сирий текст відповіді моделі, щоб витягнути структуру змін у форматі патчу або повернути null у разі невдачі парсингу.
18
+ readFilesForFix зчитує вміст файлів, зазначених у списку відносних шляхів, відносно кореня проєкту, повертаючи список об'єктів з шляхом та вмістом або null для неіснуючих файлів.
19
+ applyChanges записує нові вмісти в файли, використовуючи наданий список змін, відносно кореня проєкту, повертаючи статус успіху або помилки.
20
+
21
+ ## Публічний API
22
+
23
+ parseChangesResponse — витягує структуру змін із JSON-відповіді моделі.
24
+ readFilesForFix — зчитує вміст файлів за заданими шляхами для використання у запиті.
25
+ applyChanges — замінює вміст файлів на нові дані, отримані у змінних змін.
26
+
27
+ ## Гарантії поведінки
28
+
29
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
30
+ - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
31
+ - Не звертається до мережі.
@@ -0,0 +1,33 @@
1
+ ---
2
+ docgen:
3
+ source: npm/scripts/lib/fix/llm-lint-fix.mjs
4
+ crc: de4439e9
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 90
7
+ ---
8
+
9
+ # llm-lint-fix.mjs
10
+
11
+ ## Огляд
12
+
13
+ Модуль реалізує механізм `omlx-фікс` для обробки знахідок лінтера, що стосуються `detect-only` тулів, які не мають нативного механізму виправлення (відповідно до `lint-orchestrator-fix-readonly`). Він зчитує уражені файли, ініціює запит до моделі через `callLlm` (маршрутизація через `omlx/<model>` з фолбеком каскаду), щоб отримати пропоновані зміни. Ці зміни застосовуються до файлової системи за допомогою спільного ядра `llm-fix-apply`.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Зчитує вміст файлів, зазначених у `filePaths`, використовуючи `projectRoot`.
18
+ 2. Формує детальний запит для моделі, включаючи назву тула, інструкцію, сирий вивід знахідок та вміст зчитаних файлів.
19
+ 3. Надсилає сформований запит до моделі для отримання виправлень.
20
+ 4. Парсить відповідь моделі для вилучення пропонованих змін.
21
+ 5. Перевіряє, чи містить відповідь помилку або не містить жодних змін.
22
+ 6. Застосовує вилучені зміни до файлової системи, використовуючи `projectRoot`.
23
+ 7. Повертає результат виконання `llmLintFix`, що включає статус успіху, можливу помилку або список шляхів, які були успішно виправлені.
24
+
25
+ ## Публічний API
26
+
27
+ llmLintFix — автоматично виправляє помилки, знайдені лінтером, використовуючи omlx.
28
+
29
+ ## Гарантії поведінки
30
+
31
+ - Read-only: файл не виконує операцій запису у файлову систему.
32
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
33
+ - Не звертається до мережі.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  docgen:
3
- source: npm/skills/fix/js/llm-worker.mjs
4
- crc: 6507b5b7
3
+ source: npm/scripts/lib/fix/llm-worker.mjs
4
+ crc: 00730451
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
5
6
  score: 100
6
7
  ---
7
8
 
@@ -9,27 +10,22 @@ docgen:
9
10
 
10
11
  ## Огляд
11
12
 
12
- MODEL визначає основну модель для виконання операцій. MODEL_HEAVY визначає модель для посиленої обробки. runLlmWorker корегує одне визначене правило порушення через LLM.
13
+ Модуль визначає моделі для стандартних та складних виправлень коду. Він надає функції для ініціалізації моделей (`MODEL`, `MODEL_HEAVY`) та для виконання роботи з мовною моделлю (`runLlmWorker`), застосовуючи згенеровані зміни до файлів проєкту.
13
14
 
14
15
  ## Поведінка
15
16
 
16
- MODEL
17
- Визначає модель за замовчуванням для роботи.
18
-
19
- MODEL_HEAVY
20
- Визначає модель для важкої ескалації.
21
-
22
- runLlmWorker
23
- Виправляє одне правило порушення через LLM.
17
+ MODEL визначає модель за замовчуванням для виправлення, використовуючи змінну середовища або значення за замовчуванням.
18
+ MODEL_HEAVY визначає модель для важких виправлень, використовуючи змінну середовища або значення за замовчуванням.
19
+ runLlmWorker виправляє одне правило-порушення, викликаючи LLM для генерації змін, а потім застосовує ці зміни до файлів проєкту.
24
20
 
25
21
  ## Публічний API
26
22
 
27
- MODEL — Створює модель.
28
- MODEL_HEAVY — Створює важку модель.
29
- runLlmWorker — Виправляє одне порушення правила через pi (C1 pattern).
23
+ MODEL — Основна модель для обробки даних.
24
+ MODEL_HEAVY — Важка модель для складних обчислень.
25
+ runLlmWorker — Виправляє одне порушення правила, використовуючи шаблон C1.
30
26
 
31
27
  ## Гарантії поведінки
32
28
 
29
+ - Read-only: файл не виконує операцій запису у файлову систему.
33
30
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
34
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
35
31
  - Не звертається до мережі.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  docgen:
3
- source: npm/skills/fix/js/orchestrator.mjs
4
- crc: e77aaf7b
3
+ source: npm/scripts/lib/fix/orchestrator.mjs
4
+ crc: 3a66072d
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
5
6
  score: 100
6
7
  ---
7
8
 
@@ -9,37 +10,19 @@ docgen:
9
10
 
10
11
  ## Огляд
11
12
 
12
- runOrchestratorCli
13
- Запускає ітеративний цикл для перевірки набору правил. Проходить через кроки T0-auto та T1, взаємодіючи з LLM-воркерами. Завершує роботу, перевіряючи, чи всі правила виконані, повертаючи нуль або одиницю залежно від кінцевого стану.
13
+ Модуль керує процесом валідації та виправлення правил. Він ініціює перевірку всіх правил. Якщо виявлено порушення, процес ітеративно застосовує детермінований механізм виправлення (T0) та виправлення на основі великої мовної моделі (T1) до невирішених правил. Цей цикл повторюється до досягнення стану, коли всі правила відповідають вимогам, або до перевищення встановленого ліміту ітерацій. Функція `runOrchestratorCli` запускає цей процес.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- 1. Парсинг аргументів
18
- Приймає аргументи командного рядка після 'fix'
19
- Визначає максимальну кількість ітерацій
20
- Визначає фільтр правил
21
- 2. Початкова перевірка
22
- Запускає перевірку стану
23
- Отримує початковий набір правил
24
- Перевіряє наявність помилок
25
- 3. Ітеративний цикл
26
- Повторює ітерації до максимального ліміту
27
- Виконує крок T0-auto
28
- Перевіряє кількість провалів після кроку T0
29
- Якщо провалів немає, завершує цикл
30
- Виконує крок T1
31
- Запускає роботу з LLM-воркерами для кожного правила
32
- Збільшує лічильник провалів для невдалих правил
33
- Перевіряє стан після LLM
34
- Перевіряє наявність невирішених правил
35
- 4. Завершення
36
- Перевіряє, чи всі правила вирішено
37
- Повертає нуль у разі чистого стану
38
- Повертає одиницю у разі невирішеного стану
17
+ 1. `runOrchestratorCli` виконує початкову перевірку всіх правил. Якщо порушень немає, процес завершується успішно.
18
+ 2. Якщо порушення виявлено, ініціюється ітеративний цикл до максимальної кількості ітерацій.
19
+ 3. На кожній ітерації виконується детермінований фікс (крок T0). Це зменшує кількість правил, що потребують подальшої обробки.
20
+ 4. Якщо після кроку T0 залишилися невирішені правила, виконується LLM-фікс (крок T1). Для кожного невирішеного правила застосовується відповідна модель, яка може ескалуватися при повторних провалах.
21
+ 5. Після LLM-фіксу виконується повторна перевірка всіх правил.
22
+ 6. Якщо після перевірки всі правила чисті, процес завершується успішно.
23
+ 7. Якщо після завершення всіх ітерацій залишаються невирішені правила, процес завершується з позначкою про невирішені проблеми.
39
24
 
40
25
  ## Гарантії поведінки
41
26
 
42
27
  - Read-only: файл не виконує операцій запису у файлову систему.
43
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
44
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
45
28
  - Не звертається до мережі.
@@ -0,0 +1,34 @@
1
+ ---
2
+ docgen:
3
+ source: npm/scripts/lib/fix/run-fix-check.mjs
4
+ crc: 2a0e5f0a
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
7
+ ---
8
+
9
+ # run-fix-check.mjs
10
+
11
+ ## Огляд
12
+
13
+ Викликає конформність-фазу `lint` (read-only), движок (`orchestrator.mjs`, `t0.mjs`) та PostToolUse-хук. Перевірка конформності виконується як пряма функція, без зовнішньої обгортки через `subprocess`. Ізоляція на рівні кожного правила зберігається: кожен файл `rules/<id>/fix.mjs` все ще запускається окремим процесом `bun` (включаючи завантаження конфігурації, whitelist та ізоляцію від збоїв).
14
+
15
+ ## Поведінка
16
+
17
+ 1. Визначається наявність інструменту `conftest`.
18
+ 2. Отримується список усіх доступних ідентифікаторів правил з каталогу правил.
19
+ 3. Визначається список ідентифікаторів правил для прогону:
20
+ а. Якщо надано явний список правил, перевіряється, чи всі ці правила доступні.
21
+ б. Якщо явний список не надано, скануються файли `.cursor/rules/*.mdc` у корені проєкту. На основі знайдених файлів визначається список правил для прогону.
22
+ 4. Якщо визначено ідентифікатори правил для прогону, для кожного ідентифікатора запускається окремий процес `bun` з файлом `fix.mjs` відповідного правила.
23
+ 5. Захоплюється вивід кожного процесу.
24
+ 6. Підраховується загальна кількість правил, що не пройшли перевірку.
25
+ 7. Повертається результат, що містить загальну кількість перевірених правил, кількість невдалих перевірок та детальний список результатів для кожного правила.
26
+
27
+ ## Публічний API
28
+
29
+ runFixCheck — Визначає відповідність коду заданим правилам, виконуючи перевірку без внесення змін.
30
+
31
+ ## Гарантії поведінки
32
+
33
+ - Read-only: файл не виконує операцій запису у файлову систему.
34
+ - Не звертається до мережі.
@@ -1,37 +1,29 @@
1
1
  ---
2
2
  docgen:
3
- source: npm/skills/fix/js/t0.mjs
4
- crc: 51311329
5
- score: 90
3
+ source: npm/scripts/lib/fix/t0.mjs
4
+ crc: 0321ecc1
5
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
6
+ score: 100
6
7
  ---
7
8
 
8
9
  # t0.mjs
9
10
 
10
11
  ## Огляд
11
12
 
12
- applyT0Auto застосовує паттерни T0-auto до вихідного результату.
13
-
14
- filterT0AutoRules фільтрує список правил, для яких присутній хоча б один T0-auto паттерн.
15
-
16
- runT0AutoCli запускає перевірку, застосовує T0-auto до провальних правил, виводить результат та перевіряє стан через check-gate.
13
+ Модуль керує автоматичним застосуванням паттернів до правил для виправлення виводів порушень. Він визначає, які автоматичні паттерни можуть бути застосовані до правил, використовуючи `filterT0AutoRules`, і запускає повний процес виправлення для всіх відповідних правил через `applyT0Auto` та `runT0AutoCli`. Код працює у режимі fail-safe, перехоплюючи помилки та не викидаючи винятків назовні. Робота модуля залежить від конфігурацій `extensions.json` та `package-lock.json`.
17
14
 
18
15
  ## Поведінка
19
16
 
20
- applyT0Auto
21
- Застосовує паттерни T0-auto до одного вихідного результату
22
-
23
- filterT0AutoRules
24
- Фільтрує список правил, для яких існує хоча б один T0-auto паттерн
25
-
26
- runT0AutoCli
27
- Запускає перевірку, застосовує T0-auto до провальних правил, виводить результат та перевіряє стан через check-gate
17
+ Поведінка
18
+ applyT0Auto застосовує визначені автоматичні паттерни до одного виводу порушення, щоб виправити його.
19
+ filterT0AutoRules повертає список ідентифікаторів правил, для яких існують відповідні автоматичні паттерни.
20
+ runT0AutoCli запускає процес виправлення T0-автоматично для всіх провальних правил, застосовуючи паттерни, повторно перевіряючи стан і виводячи підсумок.
28
21
 
29
22
  ## Публічний API
30
23
 
31
- - applyT0Auto — застосовує всі T0-auto шаблони до одного виводу порушення.
32
- - filterT0AutoRules — повертає ідентифікатори правил, що мають хоча б один T0-auto шаблон у виводі порушення.
33
- - runT0AutoCli — виконує команду `n-cursor fix-t0 [правило...]`.
34
- - Запускає `fix --json`, застосовує T0-auto до кожного порушення, повторно перевіряє check-gate, виводить звіт.
24
+ applyT0Auto — Впроваджує всі шаблони T0-auto до одного результату виявлених проблем.
25
+ filterT0AutoRules — Визначає, які правила мають хоча б один шаблон T0-auto, виходячи з результату `fix --json`.
26
+ runT0AutoCli — Запускає команду `n-cursor fix-t0 [rule...]`, виконує `fix --json`, застосовує T0-auto до кожної проблеми, повторно перевіряє check-gate та надає звіт.
35
27
 
36
28
  ## Гарантії поведінки
37
29
 
@@ -1,11 +1,7 @@
1
1
  /** @see ./docs/orchestrator.md */
2
2
 
3
- import { spawnSync } from 'node:child_process'
4
- import { fileURLToPath } from 'node:url'
5
- import { join } from 'node:path'
6
-
7
- const HERE = fileURLToPath(new URL('.', import.meta.url))
8
- const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
3
+ import { runFixCheck } from './run-fix-check.mjs'
4
+ import { runT0AutoCli } from './t0.mjs'
9
5
 
10
6
  const DEFAULT_MAX_ITER = 3
11
7
  const ESCALATE_AFTER = 2
@@ -29,13 +25,13 @@ function parseOrchestratorArgs(args) {
29
25
  * @param {string} cwd корінь проєкту
30
26
  * @param {string[]} ruleFilter фільтр правил
31
27
  * @param {Array<{ ruleId: string }>} failed правила перед кроком
32
- * @returns {Array<{ ruleId: string, ok: boolean, output: string }>} правила після T0
28
+ * @returns {Promise<Array<{ ruleId: string, ok: boolean, output: string }>>} правила після T0
33
29
  */
34
- function runT0Step(cwd, ruleFilter, failed) {
35
- spawnSync('bun', [N_CURSOR_BIN, 'fix-t0', ...ruleFilter], { cwd, stdio: 'pipe' })
30
+ async function runT0Step(cwd, ruleFilter, failed) {
31
+ await runT0AutoCli([...ruleFilter], cwd)
36
32
 
37
- const afterT0 = runFixCheck(cwd, ruleFilter)
38
- const failedAfterT0 = afterT0?.rules.filter(r => !r.ok) ?? failed
33
+ const afterT0 = await runFixCheck(ruleFilter, cwd)
34
+ const failedAfterT0 = afterT0.rules.filter(r => !r.ok)
39
35
  const t0Fixed = failed.filter(r => !failedAfterT0.some(f => f.ruleId === r.ruleId))
40
36
 
41
37
  if (t0Fixed.length > 0) {
@@ -84,12 +80,7 @@ export async function runOrchestratorCli(args, cwd) {
84
80
  const failCount = new Map()
85
81
 
86
82
  // ── Перша перевірка (тихо) ──
87
- const initial = runFixCheck(cwd, ruleFilter)
88
- if (!initial) {
89
- console.error(`❌ fix: помилка перевірки`)
90
- return 1
91
- }
92
-
83
+ const initial = await runFixCheck(ruleFilter, cwd)
93
84
  let failed = initial.rules.filter(r => !r.ok)
94
85
  const total = initial.total
95
86
 
@@ -104,14 +95,14 @@ export async function runOrchestratorCli(args, cwd) {
104
95
  if (ruleFilter.length) console.log(` filter: ${ruleFilter.join(', ')}`)
105
96
 
106
97
  for (let iter = 1; iter <= maxIter; iter++) {
107
- failed = runT0Step(cwd, ruleFilter, failed)
98
+ failed = await runT0Step(cwd, ruleFilter, failed)
108
99
  if (failed.length === 0) break
109
100
 
110
101
  await runLlmStep(failed, cwd, failCount, worker)
111
102
 
112
103
  // Перевірка після LLM
113
- const afterLLM = runFixCheck(cwd, ruleFilter)
114
- failed = afterLLM?.rules.filter(r => !r.ok) ?? failed
104
+ const afterLLM = await runFixCheck(ruleFilter, cwd)
105
+ failed = afterLLM.rules.filter(r => !r.ok)
115
106
  if (failed.length === 0) break
116
107
  }
117
108
 
@@ -123,25 +114,3 @@ export async function runOrchestratorCli(args, cwd) {
123
114
  console.log(`❌ fix: ${failed.length} невирішених — ${failed.map(r => r.ruleId).join(', ')}`)
124
115
  return 1
125
116
  }
126
-
127
- /**
128
- * Внутрішня check-gate: запускає fix-перевірки і повертає структурований результат.
129
- * Не є публічним CLI — викликається лише оркестратором.
130
- * @param {string} cwd корінь проєкту
131
- * @param {string[]} ruleFilter список ID правил (порожній — усі)
132
- * @returns {{ total: number, failed: number, rules: Array<{ ruleId: string, ok: boolean, output: string }> } | null} JSON-результат або null якщо stdout порожній/невалідний
133
- */
134
- function runFixCheck(cwd, ruleFilter = []) {
135
- const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], {
136
- cwd,
137
- encoding: 'utf8',
138
- timeout: 120_000
139
- })
140
- const stdout = r.stdout?.trim()
141
- if (!stdout) return null
142
- try {
143
- return JSON.parse(stdout)
144
- } catch {
145
- return null
146
- }
147
- }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Конформність-детект (колишній subcommand `_fix-check`) як ПРЯМА функція — без subprocess-обгортки
3
+ * `bun n-cursor.js _fix-check`. Викликають конформність-фаза `lint` (read-only), движок
4
+ * (`orchestrator.mjs`, `t0.mjs`) і PostToolUse-хук.
5
+ *
6
+ * Per-rule ізоляція зберігається: кожне `rules/<id>/fix.mjs` усе ще запускається окремим
7
+ * процесом `bun` (config-loading + whitelist + crash-isolation). Прибрано лише зовнішній
8
+ * wrapper-subprocess, що його раніше шелили оркестратор/хук.
9
+ */
10
+ import { spawnSync } from 'node:child_process'
11
+ import { dirname, join } from 'node:path'
12
+ import { fileURLToPath } from 'node:url'
13
+ import { cwd as processCwd } from 'node:process'
14
+
15
+ import { listRuleIds } from '../list-rule-ids.mjs'
16
+ import { ensureTool } from '../ensure-tool.mjs'
17
+ import { discoverCheckRulesFromCursorRules } from '../discover-check-rules-from-cursor.mjs'
18
+ import { listProjectRulesMdcFiles } from '../list-project-rules-mdc.mjs'
19
+
20
+ // Цей файл: npm/scripts/lib/fix/run-fix-check.mjs → npm/rules (чотири dirname угору + rules).
21
+ const BUNDLED_RULES_DIR = join(dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url))))), 'rules')
22
+
23
+ /**
24
+ * Визначає id правил для прогону: явні (з валідацією) або discovery з `.cursor/rules/*.mdc`.
25
+ * @param {string[]} requestedRules запитані (порожній → discovery)
26
+ * @param {string[]} available доступні rule-id у пакеті
27
+ * @param {string} cwd корінь
28
+ * @returns {Promise<string[]>} id для прогону (можливо порожній)
29
+ * @throws {Error} на невідомих явно заданих правилах
30
+ */
31
+ async function resolveCheckRuleIds(requestedRules, available, cwd) {
32
+ if (requestedRules.length > 0) {
33
+ const unknown = requestedRules.filter(id => !available.includes(id))
34
+ if (unknown.length > 0) throw new Error(`Unknown rules: ${unknown.join(', ')}`)
35
+ return requestedRules
36
+ }
37
+ const mdcFiles = await listProjectRulesMdcFiles(cwd)
38
+ if (mdcFiles.length === 0) return []
39
+ return discoverCheckRulesFromCursorRules(available, mdcFiles)
40
+ }
41
+
42
+ /**
43
+ * Прогоняє `fix.mjs` кожного правила окремим процесом, захоплюючи output.
44
+ * @param {string[]} idsToRun правила
45
+ * @param {string} cwd корінь
46
+ * @returns {{ totalFailed:number, rules:Array<{ruleId:string, ok:boolean, output:string}> }} результат
47
+ */
48
+ function runRuleFixProcesses(idsToRun, cwd) {
49
+ let totalFailed = 0
50
+ const rules = []
51
+ for (const id of idsToRun) {
52
+ const r = spawnSync('bun', [join(BUNDLED_RULES_DIR, id, 'fix.mjs')], { cwd, encoding: 'utf8' })
53
+ const ok = r.status === 0
54
+ rules.push({ ruleId: id, ok, output: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() })
55
+ if (!ok) totalFailed++
56
+ }
57
+ return { totalFailed, rules }
58
+ }
59
+
60
+ /**
61
+ * Конформність-детект: per-rule `fix.mjs run()` (= перевірка, без мутацій).
62
+ * @param {string[]} [requestedRules] фільтр (порожній → discovery з `.cursor/rules/`)
63
+ * @param {string} [cwd] корінь
64
+ * @returns {Promise<{ total:number, failed:number, rules:Array<{ruleId:string, ok:boolean, output:string}> }>} результат
65
+ */
66
+ export async function runFixCheck(requestedRules = [], cwd = processCwd()) {
67
+ ensureTool('conftest')
68
+ const available = await listRuleIds(BUNDLED_RULES_DIR)
69
+ if (available.length === 0) return { total: 0, failed: 0, rules: [] }
70
+
71
+ const idsToRun = await resolveCheckRuleIds(requestedRules, available, cwd)
72
+ if (idsToRun.length === 0) return { total: 0, failed: 0, rules: [] }
73
+
74
+ const { totalFailed, rules } = runRuleFixProcesses(idsToRun, cwd)
75
+ return { total: idsToRun.length, failed: totalFailed, rules }
76
+ }
@@ -1,8 +1,8 @@
1
1
  /** @see ./docs/t0.md */
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
- import { dirname, join } from 'node:path'
4
- import { spawnSync } from 'node:child_process'
5
- import { fileURLToPath } from 'node:url'
3
+ import { join } from 'node:path'
4
+
5
+ import { runFixCheck } from './run-fix-check.mjs'
6
6
 
7
7
  const REC_REQUIRE_RE = /recommendations має містити "[^"]+"/
8
8
  const REC_MATCH_ALL_RE = /recommendations має містити "([^"]+)"/g
@@ -109,27 +109,6 @@ export function filterT0AutoRules(failedRules) {
109
109
 
110
110
  // ─── CLI entry-point ──────────────────────────────────────────────────────────
111
111
 
112
- const HERE = dirname(fileURLToPath(import.meta.url))
113
- /** Абсолютний шлях до npm/bin/n-cursor.js відносно цього файлу */
114
- const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
115
-
116
- /**
117
- * Запускає `_fix-check` і парсить JSON-результат.
118
- * @param {string[]} ruleFilter список rule-ids (порожній — усі)
119
- * @param {string} cwd корінь проєкту
120
- * @returns {{ rules: Array<{ ruleId: string, ok: boolean, output: string }> } | { _empty: true } | { _badJson: true }} JSON або маркер помилки
121
- */
122
- function fixCheck(ruleFilter, cwd) {
123
- const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], { cwd, encoding: 'utf8', timeout: 120_000 })
124
- const raw = r.stdout?.trim()
125
- if (!raw) return { _empty: true, stderr: r.stderr }
126
- try {
127
- return JSON.parse(raw)
128
- } catch {
129
- return { _badJson: true }
130
- }
131
- }
132
-
133
112
  /**
134
113
  * Застосовує T0-auto до кожного провального правила, розділяючи на applied/skipped.
135
114
  * @param {Array<{ ruleId: string, output: string }>} failed провальні правила
@@ -154,26 +133,16 @@ function applyT0ToFailed(failed, cwd) {
154
133
  * CLI підкоманда `n-cursor fix-t0 [rule...]`.
155
134
  * Запускає `fix --json`, застосовує T0-auto для кожного violation,
156
135
  * повторно перевіряє check-gate, виводить підсумок.
157
- * @param {string[]} args аргументи підкоманди (опційний список rule-ids)
136
+ * @param {string[]} args аргументи (опційний список rule-ids)
158
137
  * @param {string} cwd корінь проєкту
159
138
  * @returns {Promise<number>} 0 — T0-auto закрив всі або немає порушень; 1 — лишились
160
139
  */
161
- export function runT0AutoCli(args, cwd) {
140
+ export async function runT0AutoCli(args, cwd) {
162
141
  const ruleFilter = args.filter(a => !a.startsWith('--'))
163
142
  const verbose = args.includes('--verbose') || args.includes('-v')
164
143
 
165
- // 1. Запустити fix --json
166
- const fixJson = fixCheck(ruleFilter, cwd)
167
- if (fixJson._empty) {
168
- console.error(`n-cursor fix-t0: fix --json повернув порожній stdout`)
169
- console.error(fixJson.stderr?.slice(0, 300) ?? '')
170
- return 1
171
- }
172
- if (fixJson._badJson) {
173
- console.error(`n-cursor fix-t0: fix --json повернув невалідний JSON`)
174
- return 1
175
- }
176
-
144
+ // 1. Конформність-детект (пряма функція, без subprocess)
145
+ const fixJson = await runFixCheck(ruleFilter, cwd)
177
146
  const failed = fixJson.rules.filter(r => !r.ok)
178
147
  if (failed.length === 0) {
179
148
  console.log(`✅ fix-t0: всі правила чисті — T0 не потрібен`)
@@ -195,11 +164,7 @@ export function runT0AutoCli(args, cwd) {
195
164
  }
196
165
 
197
166
  // 4. Check-gate: перевірити лише ті правила, що ми чіпали
198
- const recheckJson = fixCheck(applied.map(a => a.ruleId), cwd)
199
- if (recheckJson._empty) {
200
- console.error(`fix-t0: check-gate: fix --json повернув порожній stdout`)
201
- return 1
202
- }
167
+ const recheckJson = await runFixCheck(applied.map(a => a.ruleId), cwd)
203
168
  const stillFailed = recheckJson.rules.filter(r => !r.ok)
204
169
 
205
170
  if (verbose) {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Список `.mdc`-файлів правил у `.cursor/rules/` проєкту-споживача (відсортований).
3
+ * Винесено зі `bin/n-cursor.js`, щоб ділити між CLI-dispatch і `run-fix-check` (конформність-детект).
4
+ */
5
+ import { existsSync } from 'node:fs'
6
+ import { readdir } from 'node:fs/promises'
7
+ import { join } from 'node:path'
8
+ import { cwd as processCwd } from 'node:process'
9
+
10
+ /** Каталог правил у проєкті-споживачі (відносно кореня). */
11
+ export const CURSOR_RULES_DIR = '.cursor/rules'
12
+
13
+ /**
14
+ * @param {string} [cwd] корінь проєкту
15
+ * @returns {Promise<string[]>} імена `*.mdc` (відсортовані), або `[]` якщо каталогу немає
16
+ */
17
+ export async function listProjectRulesMdcFiles(cwd = processCwd()) {
18
+ const dir = join(cwd, CURSOR_RULES_DIR)
19
+ if (!existsSync(dir)) return []
20
+ const names = await readdir(dir)
21
+ return names.filter(n => n.endsWith('.mdc')).toSorted((a, b) => a.localeCompare(b))
22
+ }
@@ -8,11 +8,13 @@
8
8
  *
9
9
  * Контракт:
10
10
  * - stdin Claude Code: JSON із `tool_input.file_path`; якщо файлу немає (напр. Bash) — exit 0 (skip);
11
- * - інакше spawn `_fix-check` (детект усіх правил), exit-код прозоро пробрасуємо (PostToolUse
12
- * не блокує turn, але код лишаємо інформативним: 1 — є порушення конформності).
11
+ * - інакше пряма `runFixCheck` (детект усіх правил, без subprocess-обгортки), exit-код прозоро:
12
+ * 1 — є порушення конформності (PostToolUse не блокує turn, але код лишаємо інформативним).
13
13
  */
14
- import { spawn } from 'node:child_process'
15
14
  import { once } from 'node:events'
15
+ import { cwd as processCwd } from 'node:process'
16
+
17
+ import { runFixCheck } from './lib/fix/run-fix-check.mjs'
16
18
 
17
19
  /**
18
20
  * Зчитує stdin до EOF як utf8 рядок. На TTY — повертає `''` одразу.
@@ -57,9 +59,9 @@ export function extractFilePath(stdinJson) {
57
59
  /**
58
60
  * Точка входу. Викликається з `bin/n-cursor.js` коли argv[0] === `post-tool-use-fix`.
59
61
  * Параметри доступні для інʼєкції для тестів: `stdinJson` обходить read від `process.stdin`,
60
- * `spawnFn` — заміна `node:child_process.spawn`.
61
- * @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів
62
- * @returns {Promise<number>} exit code (0 — пропущено / конформність ОК; інше — є порушення)
62
+ * `runFixCheckFn` — заміна `runFixCheck`.
63
+ * @param {{ stdinJson?: string, runFixCheckFn?: typeof runFixCheck }} [options] параметри для тестів
64
+ * @returns {Promise<number>} exit code (0 — пропущено / конформність ОК; 1 — є порушення)
63
65
  */
64
66
  export async function runPostToolUseFixCli(options = {}) {
65
67
  const stdinJson = options.stdinJson ?? (await readStdin())
@@ -68,12 +70,15 @@ export async function runPostToolUseFixCli(options = {}) {
68
70
  if (filePath === null) {
69
71
  return 0
70
72
  }
71
- const spawnFn = options.spawnFn ?? spawn
72
- // Один read-only виклик: детект конформності всіх активованих правил, без роутингу.
73
- const child = spawnFn('npx', ['--no', '@nitra/cursor', '_fix-check'], { stdio: 'inherit' })
73
+ const check = options.runFixCheckFn ?? runFixCheck
74
+ // Один read-only детект конформності всіх активованих правил (пряма функція, без subprocess).
74
75
  try {
75
- const [code] = await once(child, 'exit')
76
- return code ?? 1
76
+ const { failed, rules } = await check([], processCwd())
77
+ if (failed === 0) return 0
78
+ for (const r of rules.filter(x => !x.ok)) {
79
+ if (r.output) process.stderr.write(`${r.output}\n`)
80
+ }
81
+ return 1
77
82
  } catch (error) {
78
83
  process.stderr.write(`post-tool-use-fix: не вдалося запустити детект конформності — ${error.message}\n`)
79
84
  return 1