@nitra/cursor 1.13.68 → 1.13.72
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 +24 -0
- package/bin/n-cursor.js +24 -54
- package/package.json +1 -1
- package/scripts/skills-cli.mjs +219 -0
- package/scripts/utils/discover-check-rules-from-cursor.mjs +40 -0
- package/skills/fix/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.13.72] - 2026-05-21
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **CLI скілів спрощено** — лише `npx @nitra/cursor skill list`, `skill <id> ["task"]` (промпт на stdout), `skill cursor <id> ["task"]`, `skill claude <id> ["task"]`. Прибрано `skill prompt`, bins `n-skills` / `n-claude`, підкоманду `claude` у `n-cursor`. Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs).
|
|
12
|
+
|
|
13
|
+
## [1.13.71] - 2026-05-21
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Claude-first UX для скілів** — `npx @nitra/cursor claude taze "task"` і bin **`n-claude`** (замінено спрощеним `skill` у 1.13.72).
|
|
18
|
+
|
|
19
|
+
## [1.13.70] - 2026-05-21
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **CLI скілів без синку в проєкт** — `npx @nitra/cursor skill list|prompt|claude|cursor <id> "task"` і bin **`n-skills`** (`npx -p @nitra/cursor n-skills …`). Читає `skills/<id>/SKILL.md` з установленого пакета, збирає промпт із CWD (`package.json`, `tsconfig.json`, `.n-cursor.json`) і виводить на stdout або делегує в `claude -p` / `cursor-agent -p`. Id скілу — каталог у пакеті (`lint`, `fix`, …) або з префіксом `n-` (`n-lint` → `lint`). Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs), [n-skills.js](bin/n-skills.js), [n-cursor.js](bin/n-cursor.js).
|
|
24
|
+
|
|
25
|
+
## [1.13.69] - 2026-05-21
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **CLI `check` без аргументів** більше не парсить `AGENTS.md` — список правил для прогону будується з **`*.mdc` у `.cursor/rules/`** (той самий дисковий індекс, що для `AGENTS.md` / `CLAUDE.md`): `n-bun.mdc` → `check bun`, ручні `conftest.mdc` тощо — за наявності programmatic check у пакеті. Явний `check bun ga` без змін. Зачеплено: [discover-check-rules-from-cursor.mjs](scripts/utils/discover-check-rules-from-cursor.mjs), [n-cursor.js](bin/n-cursor.js), [fix/SKILL.md](skills/fix/SKILL.md).
|
|
30
|
+
|
|
7
31
|
## [1.13.68] - 2026-05-21
|
|
8
32
|
|
|
9
33
|
### Changed
|
package/bin/n-cursor.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Використання:
|
|
7
7
|
* `npx \@nitra/cursor` — завантажити cursor-правила
|
|
8
|
-
* `npx \@nitra/cursor check` — перевірити
|
|
8
|
+
* `npx \@nitra/cursor check` — перевірити правила з `.cursor/rules/*.mdc`, для яких у пакеті є check/policy;
|
|
9
9
|
* якщо в корені вже є `.n-cursor.json`, спочатку зчитується конфіг і за потреби дописується `$schema`
|
|
10
|
-
* `npx \@nitra/cursor check bun` — перевірити лише вказані правила (ігнорує
|
|
10
|
+
* `npx \@nitra/cursor check bun` — перевірити лише вказані правила (ігнорує `.cursor/rules/`)
|
|
11
11
|
* `npx \@nitra/cursor rename-yaml-extensions` — k8s `*.yml` → `*.yaml`, `.github` `*.yaml` → `*.yml` (опції: `--dry-run`, `--root=…`; див. bin/rename-yaml-extensions.mjs)
|
|
12
12
|
* `npx \@nitra/cursor stop-hook` — точка входу Stop hook Claude Code (читає stdin, виходить 0 при `stop_hook_active`,
|
|
13
13
|
* інакше викликає `check`); прописується автоматично в `.claude/settings.json`
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* `npx \@nitra/cursor lint-docker` — канонічний lint-docker (docker.mdc): `hadolint` по `Dockerfile`/`*.Dockerfile`
|
|
20
20
|
* `npx \@nitra/cursor lint-text` — канонічний lint-text (text.mdc): `cspell` → `shellcheck` (з auto-fix) →
|
|
21
21
|
* `markdownlint-cli2 --fix` → `v8r` (json/json5/yaml/yml/toml)
|
|
22
|
+
* `npx \@nitra/cursor skill list` — скіли пакета без синку в проєкт
|
|
23
|
+
* `npx \@nitra/cursor skill taze` — промпт на stdout
|
|
24
|
+
* `npx \@nitra/cursor skill cursor taze ["task"]` — Cursor CLI (`cursor-agent -p`)
|
|
25
|
+
* `npx \@nitra/cursor skill claude taze ["task"]` — Claude Code CLI (`claude -p`)
|
|
22
26
|
*
|
|
23
27
|
* Agent інтеграція: під час синку, окрім `.cursor/rules` і `.claude/commands` (з skills), CLI ще раз
|
|
24
28
|
* синхронізує `.claude/settings.json` (hooks + permissions; merge — користувацькі поля зберігаються),
|
|
@@ -78,6 +82,7 @@ import {
|
|
|
78
82
|
} from '../scripts/auto-rules.mjs'
|
|
79
83
|
import { detectAutoSkills } from '../scripts/auto-skills.mjs'
|
|
80
84
|
import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
|
|
85
|
+
import { discoverCheckRulesFromCursorRules } from '../scripts/utils/discover-check-rules-from-cursor.mjs'
|
|
81
86
|
import { discoverCheckableRules } from '../scripts/utils/discover-checkable-rules.mjs'
|
|
82
87
|
import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
83
88
|
import { runLintDocker } from '../rules/docker/lint/lint.mjs'
|
|
@@ -89,6 +94,7 @@ import { runRule } from '../scripts/utils/run-rule.mjs'
|
|
|
89
94
|
import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
|
|
90
95
|
import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
|
|
91
96
|
import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
|
|
97
|
+
import { runSkillsCli } from '../scripts/skills-cli.mjs'
|
|
92
98
|
import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
|
|
93
99
|
|
|
94
100
|
const PACKAGE_NAME = '@nitra/cursor'
|
|
@@ -978,57 +984,10 @@ function discoverCheckScripts() {
|
|
|
978
984
|
}
|
|
979
985
|
|
|
980
986
|
/**
|
|
981
|
-
*
|
|
982
|
-
* @param {string} mdcBasename наприклад n-bun.mdc або script.mdc
|
|
983
|
-
* @returns {string} id без суфікса .mdc та без префікса n- для керованих правил
|
|
984
|
-
*/
|
|
985
|
-
function mdcBasenameToCheckId(mdcBasename) {
|
|
986
|
-
const base = basename(mdcBasename)
|
|
987
|
-
const withoutExt = base.endsWith('.mdc') ? base.slice(0, -'.mdc'.length) : base
|
|
988
|
-
return withoutExt.startsWith(RULE_PREFIX) ? withoutExt.slice(RULE_PREFIX.length) : withoutExt
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* Зчитує AGENTS.md і повертає унікальні id перевірок у порядку згадування, лише ті що є в available
|
|
993
|
-
* @param {string[]} available імена з discoverCheckScripts()
|
|
994
|
-
* @returns {Promise<string[]>} унікальні id перевірок у порядку згадування в AGENTS.md
|
|
995
|
-
*/
|
|
996
|
-
async function discoverCheckRulesFromAgentsMd(available) {
|
|
997
|
-
const agentsPath = join(cwd(), AGENTS_FILE)
|
|
998
|
-
if (!existsSync(agentsPath)) {
|
|
999
|
-
throw new Error(
|
|
1000
|
-
`Немає ${AGENTS_FILE}. Запустіть \`npx ${PACKAGE_NAME}\` або вкажіть правила: \`npx ${PACKAGE_NAME} check bun ga\``
|
|
1001
|
-
)
|
|
1002
|
-
}
|
|
1003
|
-
const text = await readFile(agentsPath, 'utf8')
|
|
1004
|
-
const re = /\.cursor\/rules\/([^\s#`>]+\.mdc)/g
|
|
1005
|
-
const raw = []
|
|
1006
|
-
let m
|
|
1007
|
-
while ((m = re.exec(text)) !== null) {
|
|
1008
|
-
raw.push(m[1])
|
|
1009
|
-
}
|
|
1010
|
-
if (raw.length === 0) {
|
|
1011
|
-
throw new Error(
|
|
1012
|
-
`У ${AGENTS_FILE} немає посилань \`.cursor/rules/….mdc\`. Оновіть файл (\`npx ${PACKAGE_NAME}\`) або передайте правила явно.`
|
|
1013
|
-
)
|
|
1014
|
-
}
|
|
1015
|
-
const seen = new Set()
|
|
1016
|
-
const ordered = []
|
|
1017
|
-
for (const pathFragment of raw) {
|
|
1018
|
-
const id = mdcBasenameToCheckId(pathFragment)
|
|
1019
|
-
if (available.includes(id) && !seen.has(id)) {
|
|
1020
|
-
seen.add(id)
|
|
1021
|
-
ordered.push(id)
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
return ordered
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Запускає перевірки: без аргументів — за списком у AGENTS.md; з аргументами — лише вказані правила.
|
|
987
|
+
* Запускає перевірки: без аргументів — за `*.mdc` у `.cursor/rules/`; з аргументами — лише вказані правила.
|
|
1029
988
|
* Делегує оркестрацію concern-ів (JS-checks + policy через `runConftestBatch`) у `runRule`;
|
|
1030
989
|
* сам `runChecks` відповідає лише за фільтр id, агрегацію exit-кодів і shared walk-cache на прогон.
|
|
1031
|
-
* @param {string[]} requestedRules імена правил; порожній масив — брати з
|
|
990
|
+
* @param {string[]} requestedRules імена правил; порожній масив — брати з `.cursor/rules/*.mdc`
|
|
1032
991
|
* @returns {Promise<void>}
|
|
1033
992
|
*/
|
|
1034
993
|
async function runChecks(requestedRules) {
|
|
@@ -1054,10 +1013,16 @@ async function runChecks(requestedRules) {
|
|
|
1054
1013
|
if (requestedRules.length > 0) {
|
|
1055
1014
|
idsToCheck = requestedRules
|
|
1056
1015
|
} else {
|
|
1057
|
-
|
|
1016
|
+
const mdcFiles = await listProjectRulesMdcFiles()
|
|
1017
|
+
if (mdcFiles.length === 0) {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`Немає файлів *.mdc у ${RULES_DIR}/. Запустіть \`npx ${PACKAGE_NAME}\` або вкажіть правила: \`npx ${PACKAGE_NAME} check bun ga\``
|
|
1020
|
+
)
|
|
1021
|
+
}
|
|
1022
|
+
idsToCheck = discoverCheckRulesFromCursorRules(available, mdcFiles)
|
|
1058
1023
|
if (idsToCheck.length === 0) {
|
|
1059
1024
|
console.log(
|
|
1060
|
-
`\n🔍 ${PACKAGE_NAME} check — у ${
|
|
1025
|
+
`\n🔍 ${PACKAGE_NAME} check — у ${RULES_DIR}/ немає правил з programmatic перевіркою ` +
|
|
1061
1026
|
`(відповідного check-*.mjs або policy/<name>/target.json у пакеті). Нічого не запущено.\n`
|
|
1062
1027
|
)
|
|
1063
1028
|
return
|
|
@@ -1344,6 +1309,11 @@ try {
|
|
|
1344
1309
|
|
|
1345
1310
|
break
|
|
1346
1311
|
}
|
|
1312
|
+
case 'skill': {
|
|
1313
|
+
process.exitCode = await runSkillsCli(args)
|
|
1314
|
+
|
|
1315
|
+
break
|
|
1316
|
+
}
|
|
1347
1317
|
case undefined:
|
|
1348
1318
|
case '': {
|
|
1349
1319
|
await runSync()
|
|
@@ -1353,7 +1323,7 @@ try {
|
|
|
1353
1323
|
default: {
|
|
1354
1324
|
console.error(`❌ Невідома команда: ${command}`)
|
|
1355
1325
|
console.error(
|
|
1356
|
-
` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text`
|
|
1326
|
+
` Очікується: (без аргументів) синхронізація правил, check, rename-yaml-extensions, stop-hook, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, skill`
|
|
1357
1327
|
)
|
|
1358
1328
|
process.exitCode = 1
|
|
1359
1329
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI запуску скілів пакета `@nitra/cursor` без синку правил у проєкт.
|
|
3
|
+
*
|
|
4
|
+
* Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
|
|
5
|
+
* Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
|
|
6
|
+
* `.n-cursor.json`) — далі stdout або делегування в `cursor-agent` / `claude`.
|
|
7
|
+
*
|
|
8
|
+
* Підтримувані формати:
|
|
9
|
+
* `npx @nitra/cursor skill list`
|
|
10
|
+
* `npx @nitra/cursor skill taze`
|
|
11
|
+
* `npx @nitra/cursor skill cursor taze`
|
|
12
|
+
* `npx @nitra/cursor skill cursor taze "онови залежності"`
|
|
13
|
+
* `npx @nitra/cursor skill claude taze` — те саме через Claude Code CLI
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from 'node:child_process'
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
18
|
+
import { dirname, join } from 'node:path'
|
|
19
|
+
import { cwd } from 'node:process'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const RUNNERS = new Set(['cursor', 'claude'])
|
|
23
|
+
|
|
24
|
+
const USAGE_LINES = [
|
|
25
|
+
'Usage:',
|
|
26
|
+
' npx @nitra/cursor skill list',
|
|
27
|
+
' npx @nitra/cursor skill <skill-id> ["task"]',
|
|
28
|
+
' npx @nitra/cursor skill cursor <skill-id> ["task"]',
|
|
29
|
+
' npx @nitra/cursor skill claude <skill-id> ["task"]',
|
|
30
|
+
'',
|
|
31
|
+
'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} name
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function isBinaryInPath(name) {
|
|
39
|
+
const probe = spawnSync('command', ['-v', name], { shell: true, encoding: 'utf8' })
|
|
40
|
+
return probe.status === 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} name ім'я скілу з CLI або каталогу `.cursor/skills/n-*`
|
|
45
|
+
* @returns {string} id каталогу в `npm/skills/<id>/`
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeSkillId(name) {
|
|
48
|
+
if (!name || typeof name !== 'string') {
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
return name.startsWith('n-') ? name.slice(2) : name
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} skillsRoot абсолютний шлях до `skills/` пакета
|
|
56
|
+
* @returns {string[]}
|
|
57
|
+
*/
|
|
58
|
+
export function listSkillIds(skillsRoot) {
|
|
59
|
+
if (!existsSync(skillsRoot)) {
|
|
60
|
+
return []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return readdirSync(skillsRoot, { withFileTypes: true })
|
|
64
|
+
.filter(entry => entry.isDirectory())
|
|
65
|
+
.map(entry => entry.name)
|
|
66
|
+
.filter(name => existsSync(join(skillsRoot, name, 'SKILL.md')))
|
|
67
|
+
.sort((a, b) => a.localeCompare(b))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} skillsRoot
|
|
72
|
+
* @param {string} skillId нормалізований id (без префікса n-)
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function getSkillMdPath(skillsRoot, skillId) {
|
|
76
|
+
return join(skillsRoot, skillId, 'SKILL.md')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} path
|
|
81
|
+
* @returns {string | null}
|
|
82
|
+
*/
|
|
83
|
+
function readIfExists(path) {
|
|
84
|
+
return existsSync(path) ? readFileSync(path, 'utf8') : null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} skillsRoot
|
|
89
|
+
* @param {string} rawSkillName
|
|
90
|
+
* @param {string} task
|
|
91
|
+
* @param {string} [projectDir]
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
export function buildSkillPrompt(skillsRoot, rawSkillName, task, projectDir = cwd()) {
|
|
95
|
+
const skillId = normalizeSkillId(rawSkillName)
|
|
96
|
+
const skillPath = getSkillMdPath(skillsRoot, skillId)
|
|
97
|
+
|
|
98
|
+
if (!skillId || !existsSync(skillPath)) {
|
|
99
|
+
const available = listSkillIds(skillsRoot).join(', ')
|
|
100
|
+
throw new Error(`Unknown skill "${rawSkillName}". Available skills: ${available || '(none)'}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const skill = readFileSync(skillPath, 'utf8')
|
|
104
|
+
const packageJson = readIfExists(join(projectDir, 'package.json'))
|
|
105
|
+
const tsconfig = readIfExists(join(projectDir, 'tsconfig.json'))
|
|
106
|
+
const nCursorJson = readIfExists(join(projectDir, '.n-cursor.json'))
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
'# Task',
|
|
110
|
+
task || 'Execute the skill instructions for this project.',
|
|
111
|
+
'',
|
|
112
|
+
'# Skill',
|
|
113
|
+
skill,
|
|
114
|
+
'',
|
|
115
|
+
'# Current project',
|
|
116
|
+
`Directory: ${projectDir}`,
|
|
117
|
+
'',
|
|
118
|
+
packageJson ? `## package.json\n\n\`\`\`json\n${packageJson}\n\`\`\`` : '',
|
|
119
|
+
tsconfig ? `## tsconfig.json\n\n\`\`\`json\n${tsconfig}\n\`\`\`` : '',
|
|
120
|
+
nCursorJson ? `## .n-cursor.json\n\n\`\`\`json\n${nCursorJson}\n\`\`\`` : ''
|
|
121
|
+
]
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.join('\n\n')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {'claude' | 'cursor'} kind
|
|
128
|
+
* @param {string} prompt
|
|
129
|
+
* @param {string} projectDir
|
|
130
|
+
* @returns {number}
|
|
131
|
+
*/
|
|
132
|
+
function runLlmCli(kind, prompt, projectDir) {
|
|
133
|
+
if (kind === 'claude') {
|
|
134
|
+
if (!isBinaryInPath('claude')) {
|
|
135
|
+
throw new Error('`claude` not found in PATH. Install Claude Code CLI or use `skill cursor`.')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = spawnSync('claude', ['-p'], {
|
|
139
|
+
input: prompt,
|
|
140
|
+
cwd: projectDir,
|
|
141
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
142
|
+
encoding: 'utf8'
|
|
143
|
+
})
|
|
144
|
+
return result.status ?? 1
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!isBinaryInPath('cursor-agent')) {
|
|
148
|
+
throw new Error('`cursor-agent` not found in PATH. Install Cursor CLI or use `skill claude`.')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result = spawnSync('cursor-agent', ['-p'], {
|
|
152
|
+
input: prompt,
|
|
153
|
+
cwd: projectDir,
|
|
154
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
155
|
+
encoding: 'utf8'
|
|
156
|
+
})
|
|
157
|
+
return result.status ?? 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Корінь пакета `@nitra/cursor` (каталог з `skills/`, `rules/`, …).
|
|
162
|
+
* @param {string} [fromModuleUrl] для тестів — `import.meta.url` викликача
|
|
163
|
+
* @returns {string}
|
|
164
|
+
*/
|
|
165
|
+
export function resolveBundledPackageRoot(fromModuleUrl = import.meta.url) {
|
|
166
|
+
return join(dirname(fileURLToPath(fromModuleUrl)), '..')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {string[]} argv аргументи після `skill` у `n-cursor`
|
|
171
|
+
* @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options]
|
|
172
|
+
* @returns {Promise<number>} exit code
|
|
173
|
+
*/
|
|
174
|
+
export async function runSkillsCli(argv, options = {}) {
|
|
175
|
+
const log = options.log ?? (line => console.log(line))
|
|
176
|
+
const logError = options.logError ?? (line => console.error(line))
|
|
177
|
+
const packageRoot = options.packageRoot ?? resolveBundledPackageRoot()
|
|
178
|
+
const skillsRoot = join(packageRoot, 'skills')
|
|
179
|
+
const projectDir = options.projectDir ?? cwd()
|
|
180
|
+
|
|
181
|
+
const [first, second, ...rest] = argv
|
|
182
|
+
const skillIds = listSkillIds(skillsRoot)
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
if (!first) {
|
|
186
|
+
logError(USAGE_LINES.join('\n'))
|
|
187
|
+
return 1
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (first === 'list') {
|
|
191
|
+
log('Available skills:')
|
|
192
|
+
for (const id of skillIds) {
|
|
193
|
+
log(`- ${id}`)
|
|
194
|
+
}
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (RUNNERS.has(first)) {
|
|
199
|
+
if (!second) {
|
|
200
|
+
throw new Error(`Skill name is required after "${first}"`)
|
|
201
|
+
}
|
|
202
|
+
const task = rest.join(' ')
|
|
203
|
+
const prompt = buildSkillPrompt(skillsRoot, second, task, projectDir)
|
|
204
|
+
return runLlmCli(/** @type {'claude' | 'cursor'} */ (first), prompt, projectDir)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (skillIds.includes(normalizeSkillId(first))) {
|
|
208
|
+
const task = [second, ...rest].filter(Boolean).join(' ')
|
|
209
|
+
log(buildSkillPrompt(skillsRoot, first, task, projectDir))
|
|
210
|
+
return 0
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
logError(USAGE_LINES.join('\n'))
|
|
214
|
+
return 1
|
|
215
|
+
} catch (error) {
|
|
216
|
+
logError(error instanceof Error ? error.message : String(error))
|
|
217
|
+
return 1
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Визначає список id правил для `npx @nitra/cursor check` без аргументів:
|
|
3
|
+
* зчитує базові імена `*.mdc` у `.cursor/rules/` і залишає лише ті id,
|
|
4
|
+
* для яких у пакеті є programmatic перевірка (JS-концерн або policy з target.json).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Префікс керованих правил пакета у `.cursor/rules/`. */
|
|
8
|
+
export const MANAGED_RULE_FILE_PREFIX = 'n-'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Перетворює базове ім'я `.mdc` у id правила для `check <id>`.
|
|
12
|
+
* @param {string} mdcBasename наприклад `n-bun.mdc` або `my-rule.mdc`
|
|
13
|
+
* @returns {string} id без `.mdc`; для `n-*` — без префікса `n-`
|
|
14
|
+
*/
|
|
15
|
+
export function mdcBasenameToCheckId(mdcBasename) {
|
|
16
|
+
const base = mdcBasename.includes('/') ? mdcBasename.slice(mdcBasename.lastIndexOf('/') + 1) : mdcBasename
|
|
17
|
+
const withoutExt = base.endsWith('.mdc') ? base.slice(0, -'.mdc'.length) : base
|
|
18
|
+
return withoutExt.startsWith(MANAGED_RULE_FILE_PREFIX)
|
|
19
|
+
? withoutExt.slice(MANAGED_RULE_FILE_PREFIX.length)
|
|
20
|
+
: withoutExt
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Будує впорядкований список id перевірок за файлами правил на диску.
|
|
25
|
+
* @param {string[]} available id з `discoverCheckableRules` (алфавітний порядок пакета)
|
|
26
|
+
* @param {string[]} mdcBasenames відсортовані імена `*.mdc` з `.cursor/rules/`
|
|
27
|
+
* @returns {string[]} унікальні id у порядку `mdcBasenames`, лише присутні в `available`
|
|
28
|
+
*/
|
|
29
|
+
export function discoverCheckRulesFromCursorRules(available, mdcBasenames) {
|
|
30
|
+
const seen = new Set()
|
|
31
|
+
const ordered = []
|
|
32
|
+
for (const basename of mdcBasenames) {
|
|
33
|
+
const id = mdcBasenameToCheckId(basename)
|
|
34
|
+
if (available.includes(id) && !seen.has(id)) {
|
|
35
|
+
seen.add(id)
|
|
36
|
+
ordered.push(id)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return ordered
|
|
40
|
+
}
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
|
-
1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з
|
|
15
|
+
1. **Діагностика** — запусти перевірку (за замовчуванням лише правила з `.cursor/rules/*.mdc`, для яких у пакеті є programmatic check; повний набір — явні аргументи: `npx @nitra/cursor check bun ga …`):
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npx @nitra/cursor check
|