@nitra/cursor 6.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.1.0] - 2026-06-14
4
+
5
+ ### Added
6
+
7
+ - lint-text підкоманда приймає --read-only (детект без авто-фіксу markdownlint/shellcheck/dotenv) — для CI-режиму без мутацій
8
+
9
+ ## [7.0.0] - 2026-06-14
10
+
11
+ ### Changed
12
+
13
+ - lint --full поглинає конформність (колишній fix): read-only → детект через per-rule fix.mjs run(); fix → convergence-движок. Конформність як whole-repo фаза лише у --full
14
+ - lint приймає фільтр правил (lint <rule>) → конформність лише цих правил (мапить колишній fix <rule>); hk pre-commit changelog → lint changelog
15
+ - governance: знято заборону паралельного eslint — паралельно по диз'юнктних файлах дозволено; серіалізувати лише whole-tree прогони того самого корпусу (CLAUDE.md-секція + n-lint SKILL)
16
+
17
+ ### Removed
18
+
19
+ - Видалено команди fix/check/fix-run; рух-движок конформності переміщено skills/fix/js→scripts/lib/fix і поглинуто в lint. PostToolUse-хук → read-only детект усіх правил (без роутингу). /n-fix → делегат на /n-lint. fix-t0/_fix-check лишаються внутрішніми фазами движка
20
+
3
21
  ## [6.0.0] - 2026-06-14
4
22
 
5
23
  ### Added
package/bin/n-cursor.js CHANGED
@@ -637,15 +637,16 @@ async function removeOrphanManagedSkillDirs(skillsRoot, configSkills) {
637
637
  }
638
638
 
639
639
  /**
640
- * Рендерить коротку секцію для CLAUDE.md: не розпаралелювати лінт (ESLint) між shells/субагентами.
640
+ * Рендерить коротку секцію для CLAUDE.md: паралелізм лінту по диз'юнктних файлах дозволено,
641
+ * серіалізувати лише whole-tree прогони того самого корпусу.
641
642
  * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
642
643
  */
643
644
  function buildClaudeLintParallelismSectionLines() {
644
645
  return [
645
646
  '',
646
- '## Лінт і ESLint (без паралельних запусків)',
647
+ '## Лінт і ESLint (паралелізм)',
647
648
  '',
648
- 'Щоб не запускати **кілька** одночасних **`eslint`** не перевантажувати диск/CPU), **заборонено** стартувати `bun run lint` / `lint-js` / `eslint` **паралельно** в різних Bash-задачах, **фонових** shells чи **субагентах** (Task тощо). Має бути **один** послідовний прогон на сесію; команда **`/n-lint`****не** ділити на паралельні підзадачі. Деталі: `.cursor/skills/n-lint/SKILL.md`.',
649
+ 'Паралельний лінт по **різних** файлах **дозволено**: диз\'юнктні набори (per-file `lint` на змінених vs origin) не конфліктують і не перевантажують диск/CPU. Серіалізувати треба лише **whole-tree** прогони того самого корпусу (`bun run lint`, `n-cursor lint --full` по всьому репо) щоб не дублювати важкий full-scan. Деталі: `.cursor/skills/n-lint/SKILL.md`.',
649
650
  ''
650
651
  ]
651
652
  }
@@ -1597,7 +1598,7 @@ async function runSync() {
1597
1598
  * `--root`-команди `lint-doc-files`/`fix-doc-files`/`doc-files`/`doc-aggregate`/`rename-yaml-extensions`, `worktree`,
1598
1599
  * sub-лінтери) гард не зачіпає.
1599
1600
  */
1600
- const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'fix', 'check', 'lint', 'coverage', 'change', 'release'])
1601
+ const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'lint', 'coverage', 'change', 'release'])
1601
1602
 
1602
1603
  /**
1603
1604
  * Короткий опис дії для тексту root-guard помилки за іменем команди.
@@ -1610,12 +1611,8 @@ function describeRootGuardedAction(cmd) {
1610
1611
  case '': {
1611
1612
  return 'Дефолтна синхронізація скаффолдить .cursor/, .claude/, CLAUDE.md, .n-cursor.json і робить bun install у поточному каталозі'
1612
1613
  }
1613
- case 'fix':
1614
- case 'check': {
1615
- return '`fix` запускає programmatic-перевірки правил, що переписують конфіги в поточному каталозі'
1616
- }
1617
1614
  case 'lint': {
1618
- return '`lint` запускає авто-fix лінтерів (oxfmt/eslint --fix/stylelint --fix) у поточному каталозі'
1615
+ return '`lint` за замовчуванням авто-fix лінтерів (oxfmt/eslint --fix/stylelint --fix) і конформності (--full) у поточному каталозі'
1619
1616
  }
1620
1617
  case 'coverage': {
1621
1618
  return '`coverage` генерує COVERAGE.md і Stryker-артефакти в поточному каталозі'
@@ -1646,29 +1643,13 @@ try {
1646
1643
  }
1647
1644
  await ensureNitraCursorInRootDevDependencies(cwd())
1648
1645
  switch (command) {
1649
- case 'fix': {
1650
- const { runOrchestratorCli } = await import('../skills/fix/js/orchestrator.mjs')
1651
- process.exitCode = await runOrchestratorCli(args, cwd())
1652
- break
1653
- }
1654
1646
  case '_fix-check': {
1655
- // Внутрішня команда оркестратора не є публічним API.
1656
- // Повертає JSON {total, failed, rules:[{ruleId, ok, output}]} у stdout.
1647
+ // Внутрішня команда движка конформності (не публічний API): per-rule fix.mjs run() = детект.
1648
+ // Повертає JSON {total, failed, rules:[{ruleId, ok, output}]} у stdout. Викликається
1649
+ // конформність-фазою `lint` (read-only) і движком orchestrator/t0.
1657
1650
  await runFixCommand(args, { json: true })
1658
1651
  break
1659
1652
  }
1660
- case 'check': {
1661
- // Backward-compatibility alias. Перейменовано на `fix` у 1.13.84 (узгоджено з ім'ям файла `rules/<id>/fix.mjs`).
1662
- console.warn(
1663
- `⚠️ Команда \`check\` deprecated — використовуйте \`fix\` (\`npx ${PACKAGE_NAME} fix [<rule>...]\`)`
1664
- )
1665
- await runFixCommand(
1666
- args.filter(a => a !== '--json'),
1667
- { json: args.includes('--json') }
1668
- )
1669
-
1670
- break
1671
- }
1672
1653
  case 'rename-yaml-extensions': {
1673
1654
  const code = await runRenameYamlExtensionsCli(args)
1674
1655
  if (code !== 0) {
@@ -1687,7 +1668,14 @@ try {
1687
1668
  }
1688
1669
  case 'lint': {
1689
1670
  // Дві ортогональні осі: --full (scope: весь репо vs дельта vs origin) × --read-only (behavior).
1690
- process.exitCode = await runLint({ full: args.includes('--full'), readOnly: args.includes('--read-only') })
1671
+ // Позиційні (не-флаг) аргументи фільтр правил конформності (напр. `lint changelog`):
1672
+ // прогнати лише конформність цих правил, без лінтер-скану (мапить колишній `fix <rule>`).
1673
+ const rules = args.filter(a => !a.startsWith('-'))
1674
+ process.exitCode = await runLint({
1675
+ full: args.includes('--full'),
1676
+ readOnly: args.includes('--read-only'),
1677
+ rules
1678
+ })
1691
1679
 
1692
1680
  break
1693
1681
  }
@@ -1723,8 +1711,9 @@ try {
1723
1711
  break
1724
1712
  }
1725
1713
  case 'lint-text': {
1726
- // Канонічний lint-text: cspell → run-shellcheck → markdownlint-cli2 --fix run-v8r (text.mdc).
1727
- process.exitCode = await runLintTextCli()
1714
+ // Канонічний lint-text: cspell → shellcheck → dotenv → markdownlint → v8r (text.mdc).
1715
+ // `--read-only` (CI): без авто-фіксу (markdownlint/shellcheck/dotenv) — нуль мутацій.
1716
+ process.exitCode = await runLintTextCli({ readOnly: args.includes('--read-only') })
1728
1717
 
1729
1718
  break
1730
1719
  }
@@ -1764,19 +1753,12 @@ try {
1764
1753
 
1765
1754
  break
1766
1755
  }
1767
- case 'fix-run': {
1768
- // Backward-compatibility alias → перенаправляємо на `fix`.
1769
- console.warn(`⚠️ \`fix-run\` deprecated — використовуйте \`fix\``)
1770
- const { runOrchestratorCli } = await import('../skills/fix/js/orchestrator.mjs')
1771
- process.exitCode = await runOrchestratorCli(args, cwd())
1772
- break
1773
- }
1774
1756
  case 'fix-t0': {
1775
- // n-cursor fix-t0 [rule...] T0-auto рівень n-fix оркестратора.
1776
- // Запускає fix --json, знаходить violation-output із детермінованим паттерном
1757
+ // Внутрішня фаза движка конформності (не публічний API): T0-auto рівень.
1758
+ // Запускає _fix-check, знаходить violation-output із детермінованим паттерном
1777
1759
  // (vscode-ext-add, rm-forbidden-file тощо), застосовує програмний фікс (0 LLM),
1778
- // перевіряє check-gate. Exit 0 = усі T0-паттерни закриті; 1 = є решта для LLM.
1779
- const { runT0AutoCli } = await import('../skills/fix/js/t0.mjs')
1760
+ // перевіряє check-gate. Викликається orchestrator.mjs (fix-режим конформності lint).
1761
+ const { runT0AutoCli } = await import('../scripts/lib/fix/t0.mjs')
1780
1762
  process.exitCode = await runT0AutoCli(args, cwd())
1781
1763
 
1782
1764
  break
@@ -1892,7 +1874,7 @@ try {
1892
1874
  default: {
1893
1875
  console.error(`❌ Невідома команда: ${command}`)
1894
1876
  console.error(
1895
- ` Очікується: (без аргументів) синхронізація правил, fix, check, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, taze, start-check, fix-t0, change, release, skill, worktree, lint-ci, trace, doc-files, doc-aggregate`
1877
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, lint-doc-files, fix-doc-files, coverage, coverage-fix, taze, start-check, change, release, skill, worktree, lint-ci, trace, doc-files, doc-aggregate`
1896
1878
  )
1897
1879
  process.exitCode = 1
1898
1880
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "6.0.0",
3
+ "version": "7.1.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -14,6 +14,7 @@
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'
17
18
  import { cwd as processCwd } from 'node:process'
18
19
 
19
20
  import { parseRuleLintSpec, readRuleMetaRaw } from './lib/rule-meta.mjs'
@@ -21,6 +22,41 @@ import { collectChangedFilesSince, resolveChangedBase } from './lib/changed-file
21
22
 
22
23
  const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
23
24
  const RULES_DIR = join(PACKAGE_ROOT, 'rules')
25
+ const N_CURSOR_BIN = join(PACKAGE_ROOT, 'bin', 'n-cursor.js')
26
+
27
+ /**
28
+ * Конформність-фаза lint (whole-repo: config/file/workflow conformance — те, що раніше робив `fix`).
29
+ * Per-file декомпозиції немає, тож виконується лише у `--full`.
30
+ * - read-only: детект через `_fix-check` (per-rule `fix.mjs run()` = перевірка, без мутацій);
31
+ * - fix: convergence-движок (check → Tier0 → omlx) через orchestrator.
32
+ * @param {string} cwd корінь
33
+ * @param {boolean} readOnly true → лише детект (нуль мутацій)
34
+ * @param {(s: string) => void} log логер
35
+ * @param {string[]} [filter] фільтр правил (порожній — усі)
36
+ * @returns {Promise<number>} 0 — чисто, 1 — порушення/помилка
37
+ */
38
+ async function runConformance(cwd, readOnly, log, filter = []) {
39
+ if (!readOnly) {
40
+ const { runOrchestratorCli } = await import('./lib/fix/orchestrator.mjs')
41
+ return runOrchestratorCli(filter, cwd)
42
+ }
43
+ const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...filter], { cwd, encoding: 'utf8', timeout: 600_000 })
44
+ let parsed = null
45
+ try {
46
+ parsed = JSON.parse((r.stdout ?? '').trim())
47
+ } catch {
48
+ parsed = null
49
+ }
50
+ if (!parsed) {
51
+ log('❌ lint: конформність — помилка перевірки (_fix-check не повернув JSON)\n')
52
+ return 1
53
+ }
54
+ const failed = parsed.rules.filter(/** @param {{ok:boolean}} x */ x => !x.ok)
55
+ if (failed.length === 0) return 0
56
+ log(`❌ lint: конформність — ${failed.length} порушень: ${failed.map(/** @param {{ruleId:string}} x */ x => x.ruleId).join(', ')}\n`)
57
+ for (const f of failed) if (f.output) log(`${f.output}\n`)
58
+ return 1
59
+ }
24
60
 
25
61
  /**
26
62
  * Вибирає id правил для контексту, алфавітно.
@@ -56,18 +92,25 @@ function readAllMeta(rulesDir) {
56
92
 
57
93
  /**
58
94
  * Запускає lint-оркестрацію.
59
- * @param {{ full?: boolean, readOnly?: boolean, cwd?: string, rulesDir?: string, log?: (s: string) => void }} [opts] параметри
95
+ * @param {{ full?: boolean, readOnly?: boolean, rules?: string[], cwd?: string, rulesDir?: string, log?: (s: string) => void }} [opts] параметри
60
96
  * - `full` — весь репо (`true`) проти дельти vs origin (`false`, default);
61
- * - `readOnly` — лише детект без мутацій (`true`) проти fix (`false`, default).
97
+ * - `readOnly` — лише детект без мутацій (`true`) проти fix (`false`, default);
98
+ * - `rules` — непорожній фільтр → лише конформність цих правил (без лінтер-скану; мапить `fix <rule>`).
62
99
  * @returns {Promise<number>} exit code
63
100
  */
64
101
  export async function runLint(opts = {}) {
65
102
  const full = opts.full === true
66
103
  const readOnly = opts.readOnly === true
104
+ const rules = Array.isArray(opts.rules) ? opts.rules : []
67
105
  const cwd = opts.cwd ?? processCwd()
68
106
  const rulesDir = opts.rulesDir ?? RULES_DIR
69
107
  const log = opts.log ?? (s => process.stdout.write(s))
70
108
 
109
+ // Rule-filter режим (напр. `lint changelog` із hk): лише конформність указаних правил, без лінтерів.
110
+ if (rules.length > 0) {
111
+ return runConformance(cwd, readOnly, log, rules)
112
+ }
113
+
71
114
  // Default scope — дельта vs origin (merge-base main/origin/main); `--full` — весь репо.
72
115
  const changed = full ? undefined : collectChangedFilesSince(resolveChangedBase(cwd), cwd)
73
116
  if (!full && changed.length === 0) {
@@ -86,5 +129,12 @@ export async function runLint(opts = {}) {
86
129
  const code = await mod.lint(changed, cwd, { readOnly })
87
130
  if (code !== 0) return code
88
131
  }
132
+
133
+ // Конформність-фаза (поглинула `fix`): whole-repo, лише у `--full`. Кастомний rulesDir
134
+ // (юніт-тести селектора) — реальний пакет недоступний, тож пропускаємо.
135
+ if (full && opts.rulesDir === undefined) {
136
+ const conformanceCode = await runConformance(cwd, readOnly, log)
137
+ if (conformanceCode !== 0) return conformanceCode
138
+ }
89
139
  return 0
90
140
  }
@@ -1,65 +1,19 @@
1
1
  /**
2
- * PostToolUse hook для Claude Code: точкова маршрутизація `npx \@nitra/cursor fix`
3
- * за типом зміненого файла. Запускається після кожного `Edit` / `Write` / `MultiEdit`;
4
- * замінює дорогий синхронний `Stop`-хук, що ганяв повний `fix` усіх правил на кожному
5
- * turn-і.
2
+ * PostToolUse hook для Claude Code: read-only детект конформності **всіх** активованих правил
3
+ * після редагування файлу. Запускається після кожного `Edit` / `Write` / `MultiEdit`.
6
4
  *
7
- * Контракт:
8
- * - stdin Claude Code: JSON із `tool_input.file_path` (відносний шлях зміненого файла);
9
- * - exit 0, якщо файл не має маршрут (PostToolUse не блокує turn у будь-якому випадку,
10
- * але ми лишаємо exit-код прозорим — для діагностики);
11
- * - інакше spawn `npx --no \@nitra/cursor fix <rules…>` із передаванням exit-коду.
5
+ * Раніше хук маршрутизував змінений файл у релевантні правила й ганяв повний `fix` (автофікс
6
+ * + LLM) дорого, тож звужували. Тепер хук — **детект** (нуль мутацій, нуль LLM), тож роутинг
7
+ * зайвий: один виклик `_fix-check` (per-rule `fix.mjs run()` = перевірка) по всіх правилах.
12
8
  *
13
- * Маршрути впорядковані від найбільш специфічного до загального; перший збіг — переможець.
14
- * `docs/adr/**\/*.md` свідомо повертає `[]`: ADR-нормалізація вже покривається async
15
- * Stop-hook'ом `normalize-decisions.sh` повторний `fix adr` тут лише сповільнював би turn.
9
+ * Контракт:
10
+ * - stdin Claude Code: JSON із `tool_input.file_path`; якщо файлу немає (напр. Bash) — exit 0 (skip);
11
+ * - інакше spawn `_fix-check` (детект усіх правил), exit-код прозоро пробрасуємо (PostToolUse
12
+ * не блокує turn, але код лишаємо інформативним: 1 — є порушення конформності).
16
13
  */
17
14
  import { spawn } from 'node:child_process'
18
15
  import { once } from 'node:events'
19
16
 
20
- import picomatch from 'picomatch'
21
-
22
- /**
23
- * @typedef {object} Route
24
- * @property {string} pattern picomatch glob (з підтримкою `**` і `{a,b}`)
25
- * @property {string[]} rules ID правил `npm/rules/<id>` (бо `fix.mjs` обов'язковий)
26
- */
27
-
28
- /** Порядок важливий: специфічні маршрути (`.github/workflows/*`, `**\/k8s/**`) — перед загальними. */
29
- /** @type {readonly Route[]} */
30
- const ROUTES = Object.freeze([
31
- { pattern: 'docs/adr/**/*.md', rules: [] },
32
- { pattern: '.github/workflows/*.{yml,yaml}', rules: ['ga'] },
33
- { pattern: '**/k8s/**/*.{yaml,yml}', rules: ['k8s'] },
34
- { pattern: '**/*.vue', rules: ['js-lint', 'style-lint', 'vue'] },
35
- { pattern: '**/*.{mjs,js,cjs,ts,tsx,jsx}', rules: ['js-lint'] },
36
- { pattern: '**/*.{css,scss,sass}', rules: ['style-lint'] },
37
- { pattern: '**/*.rego', rules: ['rego'] },
38
- { pattern: '{**/,}Dockerfile', rules: ['docker'] },
39
- { pattern: '**/*.Dockerfile', rules: ['docker'] },
40
- { pattern: '**/*.sh', rules: ['security'] },
41
- { pattern: '{**/,}package.json', rules: ['npm-module', 'bun'] },
42
- { pattern: '**/*.md', rules: ['text'] }
43
- ])
44
-
45
- /**
46
- * Повертає список правил, які слід прогнати для зміненого `filePath`.
47
- * Перший збіг із `ROUTES` — переможець; невідомі шляхи / некоректні входи → `[]`.
48
- * @param {unknown} filePath відносний шлях зміненого файла зі stdin Claude Code
49
- * @returns {string[]} ID правил для `npx \@nitra/cursor fix`
50
- */
51
- export function routeFilePathToRules(filePath) {
52
- if (typeof filePath !== 'string' || filePath === '') {
53
- return []
54
- }
55
- for (const { pattern, rules } of ROUTES) {
56
- if (picomatch.isMatch(filePath, pattern, { dot: true })) {
57
- return [...rules]
58
- }
59
- }
60
- return []
61
- }
62
-
63
17
  /**
64
18
  * Зчитує stdin до EOF як utf8 рядок. На TTY — повертає `''` одразу.
65
19
  * @returns {Promise<string>} вміст stdin
@@ -87,7 +41,7 @@ async function readStdin() {
87
41
  * @param {string} stdinJson сирий вміст stdin
88
42
  * @returns {string | null} відносний шлях або `null`
89
43
  */
90
- function extractFilePath(stdinJson) {
44
+ export function extractFilePath(stdinJson) {
91
45
  if (!stdinJson) {
92
46
  return null
93
47
  }
@@ -103,27 +57,25 @@ function extractFilePath(stdinJson) {
103
57
  /**
104
58
  * Точка входу. Викликається з `bin/n-cursor.js` коли argv[0] === `post-tool-use-fix`.
105
59
  * Параметри доступні для інʼєкції для тестів: `stdinJson` обходить read від `process.stdin`,
106
- * `spawnFn` — заміна `node:child_process.spawn` (повертає EventEmitter-сумісний об'єкт).
107
- * @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів (ін'єкція stdin/spawn)
108
- * @returns {Promise<number>} exit code (0 — пропущено / fix ОК; інше — exit-код `fix`)
60
+ * `spawnFn` — заміна `node:child_process.spawn`.
61
+ * @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів
62
+ * @returns {Promise<number>} exit code (0 — пропущено / конформність ОК; інше — є порушення)
109
63
  */
110
64
  export async function runPostToolUseFixCli(options = {}) {
111
65
  const stdinJson = options.stdinJson ?? (await readStdin())
112
66
  const filePath = extractFilePath(stdinJson)
67
+ // Тільки після редагування файлу (Edit/Write/MultiEdit мають file_path); Bash тощо — skip.
113
68
  if (filePath === null) {
114
69
  return 0
115
70
  }
116
- const rules = routeFilePathToRules(filePath)
117
- if (rules.length === 0) {
118
- return 0
119
- }
120
71
  const spawnFn = options.spawnFn ?? spawn
121
- const child = spawnFn('npx', ['--no', '@nitra/cursor', 'fix', ...rules], { stdio: 'inherit' })
72
+ // Один read-only виклик: детект конформності всіх активованих правил, без роутингу.
73
+ const child = spawnFn('npx', ['--no', '@nitra/cursor', '_fix-check'], { stdio: 'inherit' })
122
74
  try {
123
75
  const [code] = await once(child, 'exit')
124
76
  return code ?? 1
125
77
  } catch (error) {
126
- process.stderr.write(`post-tool-use-fix: не вдалося запустити npx @nitra/cursor fix — ${error.message}\n`)
78
+ process.stderr.write(`post-tool-use-fix: не вдалося запустити детект конформності — ${error.message}\n`)
127
79
  return 1
128
80
  }
129
81
  }
@@ -1,23 +1,22 @@
1
1
  ---
2
2
  name: n-fix
3
3
  description: >-
4
- Виправити проєкт відповідно до всіх правил в .cursor/rules/
4
+ DEPRECATED використовуй /n-lint. fix злито в lint: `n-cursor lint` тепер і
5
+ детектить, і виправляє (конформність + лінтери) за один прохід.
5
6
  ---
6
7
 
7
- # n-fix — автоматичне виправлення проєкту
8
+ # n-fix — DEPRECATED (делегат на /n-lint)
8
9
 
9
- ## Scope
10
+ Команду `n-cursor fix` **видалено**: рух-движок конформності (convergence-loop /
11
+ check-gate / Tier0 / LLM) поглинуто в `lint` (спека
12
+ `docs/specs/2026-06-14-lint-orchestrator-fix-readonly-unification-design.md`).
10
13
 
11
- Цей скіл відповідає **лише за структуру** проєкту: щоб `.cursor/rules/` + `npx @nitra/cursor fix` були задоволені (наявність конфігів, залежностей, скриптів, GitHub workflows, відсутність заборонених файлів). **Лінт-порушення у самому коді** (ESLint, oxlint, jscpd, cspell, knip, sonarjs, stylelint тощо) — **поза скоупом**; їх діагностує й виправляє **`/n-lint`** (`bun run lint`).
14
+ **Використовуй `/n-lint`** замість цього скіла:
12
15
 
13
- ## Workflow
16
+ - `n-cursor lint` — дельта vs origin, **fix за замовчуванням** (лінтери на змінених файлах);
17
+ - `n-cursor lint --full` — весь репо + **конформність** (колишній `fix`: конфіги/файли/воркфлоу
18
+ через convergence-движок);
19
+ - `n-cursor lint --read-only [--full]` — лише детект, нуль мутацій (CI / pre-commit);
20
+ - `n-cursor lint <rule>` — конформність одного правила (колишній `fix <rule>`).
14
21
 
15
- ```bash
16
- n_cursor_npx fix
17
- ```
18
-
19
- Exit 0 = чисто, 1 = є unresolved (перевір вивід — буде список правил що не закрились після 3 ітерацій).
20
-
21
- Якщо змінились залежності — `bun i`. Якщо змінились JS/TS файли — `oxfmt .`.
22
-
23
- Для конкретних правил: `n_cursor_npx fix bun ga`.
22
+ Цей скіл лишено як тонкий делегат до наступного major; уся логіка — у `/n-lint`.
@@ -91,18 +91,17 @@ bun run lint
91
91
  - Якщо тестів **немає** або вони **не покривають** блок, який змінюєш — **спочатку** додай/розшир тести, переконайся, що вони стабільно проходять, **потім** роби рефакторинг, **потім** знову прогони тести й **`bun run lint`**, щоб підтвердити, що функціональність коректна й лінт чистий.
92
92
  - Якщо після рефакторингу тести або лінт падають — **не** залишай «половинчастий» рефакторинг: відкотись або доведи зміни до зеленого стану.
93
93
 
94
- ## Навантаження на macOS (багато процесів `eslint` / лаги)
94
+ ## Паралелізм і навантаження на macOS
95
95
 
96
- Часто це **не** «один ESLint розпаралелив себе всередині», а **кілька паралельних запусків** одного й того ж ланцюжка (**`bun run lint`**, **`bun run lint-js`**) у різних Bash-задачах агента (кілька shells). Кожен запуск = окремий процес **`eslint`** плюс **`oxlint`**, **`jscpd`** тощо диск і CPU перегружаються.
96
+ **Паралельно по різних файлах дозволено.** Диз'юнктні набори (per-file `n-cursor lint` на змінених vs origin) не конфліктують: кожен процес обробляє свій підмножину файлів, тож гонки за тим самим корпусом немає.
97
97
 
98
- **Чому взагалі кілька `eslint`:** оркестратор (Claude Code, Cursor тощо) може **розпаралелити** роботу: кілька **субагентів** / **паралельних Bash-задач** / **фонових shell**, і кожен **сам** виконує **`/n-lint`** або запускає **`eslint`**. Тоді навантаження **множиться**: не один прогон лінту, а **N прогонів** одночасно.
98
+ Проблема не паралелізм як такий, а **кілька одночасних whole-tree прогонів того самого корпусу** (**`bun run lint`**, **`n-cursor lint --full`**) у різних Bash-задачах/shells: кожен повторно сканує **весь** репо (eslint + oxlint + jscpd + knip), і диск/CPU перевантажуються дублюванням важкого full-scan.
99
99
 
100
- ### Що робити агенту під час виконання цього скілу (обов’язково)
100
+ ### Що робити агенту під час виконання цього скілу
101
101
 
102
- 1. **Один** запуск **`bun run lint`** (або всі кроки **`lint`**, як у **`package.json`**) — у **одному** foreground shell, **без** `run_in_background` / фонових копій тієї ж команди.
103
- 2. **Не** викликати **паралельні субагенти** (subagent, Task, «розбий на N паралельних завдань») лише заради лінту в одному репозиторії. Лінт не потребує шардінгу: один процес, послідовно.
104
- 3. Якщо ти **батьок-сесія** й уже делегував дочірнім задачам **інші** кроки **не** доручай дочірнім і **`bun run lint`**, залиш **лише** собі один **послідовний** прогон **після** змін у файлах, або **один** виклик цілого скілу на сесію.
105
- 4. Якщо сесія/користувач уже запускає лінт — **не** дублювати; зачекати завершення або **не** стартувати лінт повторно в іншому shell.
102
+ 1. **Whole-tree** прогін (**`bun run lint`** / **`lint --full`**) — **один** за раз, у одному foreground shell; **не** запускати другий full-прогін того самого корпусу паралельно.
103
+ 2. **Per-file** лінт по диз'юнктних файлах (різні субагенти на різні файли) **дозволено** й не потребує серіалізації.
104
+ 3. Якщо сесія/користувач уже запускає **whole-tree** лінтне дублювати його; зачекати завершення.
106
105
 
107
106
  **Що можна змінити у проєкті (локально або в `package.json`)**
108
107
 
File without changes
File without changes