@nitra/cursor 6.0.0 → 7.0.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 +12 -0
- package/bin/n-cursor.js +22 -41
- package/package.json +1 -1
- package/scripts/lint-cli.mjs +52 -2
- package/scripts/post-tool-use-fix.mjs +17 -65
- package/skills/fix/SKILL.md +13 -14
- package/skills/lint/SKILL.md +7 -8
- /package/{skills/fix/js → scripts/lib/fix}/docs/llm-worker.md +0 -0
- /package/{skills/fix/js → scripts/lib/fix}/docs/orchestrator.md +0 -0
- /package/{skills/fix/js → scripts/lib/fix}/docs/t0.md +0 -0
- /package/{skills/fix/js → scripts/lib/fix}/llm-worker.mjs +0 -0
- /package/{skills/fix/js → scripts/lib/fix}/orchestrator.mjs +0 -0
- /package/{skills/fix/js → scripts/lib/fix}/t0.mjs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [7.0.0] - 2026-06-14
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- lint --full поглинає конформність (колишній fix): read-only → детект через per-rule fix.mjs run(); fix → convergence-движок. Конформність як whole-repo фаза лише у --full
|
|
8
|
+
- lint приймає фільтр правил (lint <rule>) → конформність лише цих правил (мапить колишній fix <rule>); hk pre-commit changelog → lint changelog
|
|
9
|
+
- governance: знято заборону паралельного eslint — паралельно по диз'юнктних файлах дозволено; серіалізувати лише whole-tree прогони того самого корпусу (CLAUDE.md-секція + n-lint SKILL)
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- Видалено команди fix/check/fix-run; рух-движок конформності переміщено skills/fix/js→scripts/lib/fix і поглинуто в lint. PostToolUse-хук → read-only детект усіх правил (без роутингу). /n-fix → делегат на /n-lint. fix-t0/_fix-check лишаються внутрішніми фазами движка
|
|
14
|
+
|
|
3
15
|
## [6.0.0] - 2026-06-14
|
|
4
16
|
|
|
5
17
|
### 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:
|
|
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
|
-
'
|
|
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, '', '
|
|
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`
|
|
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
|
-
// Внутрішня команда
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1764,19 +1752,12 @@ try {
|
|
|
1764
1752
|
|
|
1765
1753
|
break
|
|
1766
1754
|
}
|
|
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
1755
|
case 'fix-t0': {
|
|
1775
|
-
//
|
|
1776
|
-
// Запускає
|
|
1756
|
+
// Внутрішня фаза движка конформності (не публічний API): T0-auto рівень.
|
|
1757
|
+
// Запускає _fix-check, знаходить violation-output із детермінованим паттерном
|
|
1777
1758
|
// (vscode-ext-add, rm-forbidden-file тощо), застосовує програмний фікс (0 LLM),
|
|
1778
|
-
// перевіряє check-gate.
|
|
1779
|
-
const { runT0AutoCli } = await import('../
|
|
1759
|
+
// перевіряє check-gate. Викликається orchestrator.mjs (fix-режим конформності lint).
|
|
1760
|
+
const { runT0AutoCli } = await import('../scripts/lib/fix/t0.mjs')
|
|
1780
1761
|
process.exitCode = await runT0AutoCli(args, cwd())
|
|
1781
1762
|
|
|
1782
1763
|
break
|
|
@@ -1892,7 +1873,7 @@ try {
|
|
|
1892
1873
|
default: {
|
|
1893
1874
|
console.error(`❌ Невідома команда: ${command}`)
|
|
1894
1875
|
console.error(
|
|
1895
|
-
` Очікується: (без аргументів) синхронізація правил,
|
|
1876
|
+
` Очікується: (без аргументів) синхронізація правил, 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
1877
|
)
|
|
1897
1878
|
process.exitCode = 1
|
|
1898
1879
|
}
|
package/package.json
CHANGED
package/scripts/lint-cli.mjs
CHANGED
|
@@ -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:
|
|
3
|
-
*
|
|
4
|
-
* замінює дорогий синхронний `Stop`-хук, що ганяв повний `fix` усіх правил на кожному
|
|
5
|
-
* turn-і.
|
|
2
|
+
* PostToolUse hook для Claude Code: read-only детект конформності **всіх** активованих правил
|
|
3
|
+
* після редагування файлу. Запускається після кожного `Edit` / `Write` / `MultiEdit`.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
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
|
|
107
|
-
* @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів
|
|
108
|
-
* @returns {Promise<number>} exit code (0 — пропущено /
|
|
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
|
-
|
|
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: не вдалося запустити
|
|
78
|
+
process.stderr.write(`post-tool-use-fix: не вдалося запустити детект конформності — ${error.message}\n`)
|
|
127
79
|
return 1
|
|
128
80
|
}
|
|
129
81
|
}
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: n-fix
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
+
**Використовуй `/n-lint`** замість цього скіла:
|
|
12
15
|
|
|
13
|
-
|
|
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
|
-
|
|
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`.
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -91,18 +91,17 @@ bun run lint
|
|
|
91
91
|
- Якщо тестів **немає** або вони **не покривають** блок, який змінюєш — **спочатку** додай/розшир тести, переконайся, що вони стабільно проходять, **потім** роби рефакторинг, **потім** знову прогони тести й **`bun run lint`**, щоб підтвердити, що функціональність коректна й лінт чистий.
|
|
92
92
|
- Якщо після рефакторингу тести або лінт падають — **не** залишай «половинчастий» рефакторинг: відкотись або доведи зміни до зеленого стану.
|
|
93
93
|
|
|
94
|
-
##
|
|
94
|
+
## Паралелізм і навантаження на macOS
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
**Паралельно по різних файлах — дозволено.** Диз'юнктні набори (per-file `n-cursor lint` на змінених vs origin) не конфліктують: кожен процес обробляє свій підмножину файлів, тож гонки за тим самим корпусом немає.
|
|
97
97
|
|
|
98
|
-
|
|
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.
|
|
103
|
-
2.
|
|
104
|
-
3. Якщо
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|