@nitra/cursor 1.13.72 → 1.13.74
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
|
@@ -4,6 +4,12 @@
|
|
|
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.73] - 2026-05-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Збір workspace-коренів** — `getMonorepoPackageRootDirs` / `getMonorepoProjectRootDirs` більше не трактують `package.json` у `node_modules/`, `.git/`, `.venv/`, `venv/` як воркспейси (glob ignore + `isIgnoredWorkspaceRoot`). Усуває хибні `check changelog` на транзитивних залежностях (наприклад `node-gyp/gyp`).
|
|
12
|
+
|
|
7
13
|
## [1.13.72] - 2026-05-21
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/bin/n-cursor.js
CHANGED
package/package.json
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
#
|
|
8
8
|
# Перевіряє: якщо в `dependencies` є `vue`, то потрібні канонічні Vue/Vite
|
|
9
9
|
# залежності, `devDependencies.vite` має бути мажорної версії ≥ 8, а `esbuild`
|
|
10
|
-
#
|
|
10
|
+
# (міграція на rolldown), `vitest` (заміна на Bun Test Runner) і `jsdom`
|
|
11
|
+
# (заміна на happy-dom) у dependencies/devDependencies заборонені.
|
|
11
12
|
#
|
|
12
13
|
# AST-сканування коду (заборона явних value-імпортів `from 'vue'`, заборона
|
|
13
14
|
# Node-нативних модулів у `.vue` SFC, перевірка `vite.config` на
|
|
@@ -65,6 +66,18 @@ deny contains msg if {
|
|
|
65
66
|
msg := "Vue-пакет: esbuild заборонено — заміни на rolldown і прибери залежність (vue.mdc)"
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
deny contains msg if {
|
|
70
|
+
uses_vue
|
|
71
|
+
"vitest" in all_dependency_names
|
|
72
|
+
msg := "Vue-пакет: vitest заборонено — використовуй Bun Test Runner (`bun test`) і прибери залежність (vue.mdc)"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
deny contains msg if {
|
|
76
|
+
uses_vue
|
|
77
|
+
"jsdom" in all_dependency_names
|
|
78
|
+
msg := "Vue-пакет: jsdom заборонено — використовуй happy-dom і прибери залежність (vue.mdc)"
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
# ── helpers ────────────────────────────────────────────────────────────────
|
|
69
82
|
|
|
70
83
|
uses_vue if {
|
package/scripts/skills-cli.mjs
CHANGED
|
@@ -32,8 +32,8 @@ const USAGE_LINES = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @param {string} name
|
|
36
|
-
* @returns {boolean}
|
|
35
|
+
* @param {string} name ім'я бінарника
|
|
36
|
+
* @returns {boolean} чи знайдено бінарник у PATH
|
|
37
37
|
*/
|
|
38
38
|
function isBinaryInPath(name) {
|
|
39
39
|
const probe = spawnSync('command', ['-v', name], { shell: true, encoding: 'utf8' })
|
|
@@ -53,7 +53,7 @@ export function normalizeSkillId(name) {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* @param {string} skillsRoot абсолютний шлях до `skills/` пакета
|
|
56
|
-
* @returns {string[]}
|
|
56
|
+
* @returns {string[]} відсортовані id скілів, що мають `SKILL.md`
|
|
57
57
|
*/
|
|
58
58
|
export function listSkillIds(skillsRoot) {
|
|
59
59
|
if (!existsSync(skillsRoot)) {
|
|
@@ -64,32 +64,32 @@ export function listSkillIds(skillsRoot) {
|
|
|
64
64
|
.filter(entry => entry.isDirectory())
|
|
65
65
|
.map(entry => entry.name)
|
|
66
66
|
.filter(name => existsSync(join(skillsRoot, name, 'SKILL.md')))
|
|
67
|
-
.
|
|
67
|
+
.toSorted((a, b) => a.localeCompare(b))
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* @param {string} skillsRoot
|
|
71
|
+
* @param {string} skillsRoot абсолютний шлях до `skills/` пакета
|
|
72
72
|
* @param {string} skillId нормалізований id (без префікса n-)
|
|
73
|
-
* @returns {string}
|
|
73
|
+
* @returns {string} шлях до `SKILL.md` скілу
|
|
74
74
|
*/
|
|
75
75
|
function getSkillMdPath(skillsRoot, skillId) {
|
|
76
76
|
return join(skillsRoot, skillId, 'SKILL.md')
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* @param {string} path
|
|
81
|
-
* @returns {string | null}
|
|
80
|
+
* @param {string} path шлях до файлу
|
|
81
|
+
* @returns {string | null} вміст файлу або `null`, якщо файлу немає
|
|
82
82
|
*/
|
|
83
83
|
function readIfExists(path) {
|
|
84
84
|
return existsSync(path) ? readFileSync(path, 'utf8') : null
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* @param {string} skillsRoot
|
|
89
|
-
* @param {string} rawSkillName
|
|
90
|
-
* @param {string} task
|
|
91
|
-
* @param {string} [projectDir]
|
|
92
|
-
* @returns {string}
|
|
88
|
+
* @param {string} skillsRoot абсолютний шлях до `skills/` пакета
|
|
89
|
+
* @param {string} rawSkillName ім'я скілу з CLI (можливо з префіксом n-)
|
|
90
|
+
* @param {string} task текст завдання для скілу
|
|
91
|
+
* @param {string} [projectDir] корінь цільового проєкту (типово — CWD)
|
|
92
|
+
* @returns {string} промпт: інструкція скілу + контекст поточного проєкту
|
|
93
93
|
*/
|
|
94
94
|
export function buildSkillPrompt(skillsRoot, rawSkillName, task, projectDir = cwd()) {
|
|
95
95
|
const skillId = normalizeSkillId(rawSkillName)
|
|
@@ -124,10 +124,10 @@ export function buildSkillPrompt(skillsRoot, rawSkillName, task, projectDir = cw
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
|
-
* @param {'claude' | 'cursor'} kind
|
|
128
|
-
* @param {string} prompt
|
|
129
|
-
* @param {string} projectDir
|
|
130
|
-
* @returns {number}
|
|
127
|
+
* @param {'claude' | 'cursor'} kind який LLM CLI запускати
|
|
128
|
+
* @param {string} prompt промпт для передачі у stdin
|
|
129
|
+
* @param {string} projectDir робочий каталог дочірнього процесу
|
|
130
|
+
* @returns {number} exit code дочірнього процесу
|
|
131
131
|
*/
|
|
132
132
|
function runLlmCli(kind, prompt, projectDir) {
|
|
133
133
|
if (kind === 'claude') {
|
|
@@ -159,8 +159,8 @@ function runLlmCli(kind, prompt, projectDir) {
|
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
161
|
* Корінь пакета `@nitra/cursor` (каталог з `skills/`, `rules/`, …).
|
|
162
|
-
* @param {string} [fromModuleUrl] для тестів — `import.meta.url
|
|
163
|
-
* @returns {string}
|
|
162
|
+
* @param {string} [fromModuleUrl] для тестів — `import.meta.url`, відносно якого шукати корінь
|
|
163
|
+
* @returns {string} абсолютний шлях до кореня пакета
|
|
164
164
|
*/
|
|
165
165
|
export function resolveBundledPackageRoot(fromModuleUrl = import.meta.url) {
|
|
166
166
|
return join(dirname(fileURLToPath(fromModuleUrl)), '..')
|
|
@@ -168,10 +168,10 @@ export function resolveBundledPackageRoot(fromModuleUrl = import.meta.url) {
|
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
170
|
* @param {string[]} argv аргументи після `skill` у `n-cursor`
|
|
171
|
-
* @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options]
|
|
172
|
-
* @returns {
|
|
171
|
+
* @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options] перевизначення кореня пакета, каталогу проєкту та функцій виводу (для тестів)
|
|
172
|
+
* @returns {number} exit code
|
|
173
173
|
*/
|
|
174
|
-
export
|
|
174
|
+
export function runSkillsCli(argv, options = {}) {
|
|
175
175
|
const log = options.log ?? (line => console.log(line))
|
|
176
176
|
const logError = options.logError ?? (line => console.error(line))
|
|
177
177
|
const packageRoot = options.packageRoot ?? resolveBundledPackageRoot()
|
|
@@ -8,7 +8,7 @@ import { dirname, join, relative } from 'node:path'
|
|
|
8
8
|
|
|
9
9
|
import { parse as parseToml } from 'smol-toml'
|
|
10
10
|
|
|
11
|
-
import { getMonorepoPackageRootDirs } from './workspaces.mjs'
|
|
11
|
+
import { getMonorepoPackageRootDirs, isIgnoredWorkspaceRoot } from './workspaces.mjs'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {'npm' | 'python'} PackageKind
|
|
@@ -132,12 +132,12 @@ export async function getMonorepoProjectRootDirs(repoRoot = '.') {
|
|
|
132
132
|
const absDir = dirname(join(repoRoot, relPy))
|
|
133
133
|
const relRoot = relative(repoRoot, absDir)
|
|
134
134
|
const ws = relRoot === '' ? '.' : relRoot
|
|
135
|
-
if (!existsSync(join(repoRoot, ws, 'package.json'))) {
|
|
135
|
+
if (!isIgnoredWorkspaceRoot(ws) && !existsSync(join(repoRoot, ws, 'package.json'))) {
|
|
136
136
|
roots.add(ws)
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const list = [...roots]
|
|
140
|
+
const list = [...roots].filter(ws => !isIgnoredWorkspaceRoot(ws))
|
|
141
141
|
list.sort((a, b) => {
|
|
142
142
|
if (a === '.') return -1
|
|
143
143
|
if (b === '.') return 1
|
|
@@ -9,6 +9,22 @@ import { glob, readFile } from 'node:fs/promises'
|
|
|
9
9
|
import { dirname, join, relative } from 'node:path'
|
|
10
10
|
|
|
11
11
|
const TRAILING_SLASH_RE = /\/$/
|
|
12
|
+
const LEADING_DOTSLASH_RE = /^\.\//
|
|
13
|
+
|
|
14
|
+
/** Glob-ігнор для workspace-патернів із `*` (узгоджено з `package-manifest.mjs`). */
|
|
15
|
+
export const WORKSPACE_GLOB_IGNORE = Object.freeze(['**/node_modules/**', '**/.git/**', '**/.venv/**', '**/venv/**'])
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Чи слід виключити каталог зі списку workspace-коренів (не стосується `.`).
|
|
19
|
+
* @param {string} ws відносний шлях воркспейсу
|
|
20
|
+
* @returns {boolean} true — пропустити
|
|
21
|
+
*/
|
|
22
|
+
export function isIgnoredWorkspaceRoot(ws) {
|
|
23
|
+
if (ws === '.') return false
|
|
24
|
+
const p = ws.replaceAll('\\', '/').replace(LEADING_DOTSLASH_RE, '')
|
|
25
|
+
const segments = new Set(p.split('/'))
|
|
26
|
+
return segments.has('node_modules') || segments.has('.git') || segments.has('.venv') || segments.has('venv')
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
/**
|
|
14
30
|
* Нормалізує workspace-патерн до POSIX-формату і прибирає хвостові `/`.
|
|
@@ -33,16 +49,22 @@ function normalizeWorkspacePattern(pattern) {
|
|
|
33
49
|
async function addWorkspaceRootsByPattern(roots, repoRoot, workspacePattern) {
|
|
34
50
|
if (workspacePattern.includes('*')) {
|
|
35
51
|
const globPat = `${workspacePattern}/package.json`
|
|
36
|
-
for await (const relPkgJsonPath of glob(globPat, {
|
|
52
|
+
for await (const relPkgJsonPath of glob(globPat, {
|
|
53
|
+
cwd: repoRoot,
|
|
54
|
+
ignore: [...WORKSPACE_GLOB_IGNORE]
|
|
55
|
+
})) {
|
|
37
56
|
const absPkgJsonPath = join(repoRoot, relPkgJsonPath)
|
|
38
57
|
const relRoot = relative(repoRoot, dirname(absPkgJsonPath))
|
|
39
|
-
|
|
58
|
+
const ws = relRoot === '' ? '.' : relRoot
|
|
59
|
+
if (!isIgnoredWorkspaceRoot(ws)) {
|
|
60
|
+
roots.add(ws)
|
|
61
|
+
}
|
|
40
62
|
}
|
|
41
63
|
return
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
const pkgJsonPath = join(repoRoot, workspacePattern, 'package.json')
|
|
45
|
-
if (existsSync(pkgJsonPath)) {
|
|
67
|
+
if (existsSync(pkgJsonPath) && !isIgnoredWorkspaceRoot(workspacePattern)) {
|
|
46
68
|
roots.add(workspacePattern)
|
|
47
69
|
}
|
|
48
70
|
}
|
|
@@ -77,7 +99,7 @@ export async function getMonorepoPackageRootDirs(repoRoot = '.') {
|
|
|
77
99
|
const workspacePattern = normalizeWorkspacePattern(raw)
|
|
78
100
|
await addWorkspaceRootsByPattern(roots, repoRoot, workspacePattern)
|
|
79
101
|
}
|
|
80
|
-
const list = [...roots]
|
|
102
|
+
const list = [...roots].filter(ws => !isIgnoredWorkspaceRoot(ws))
|
|
81
103
|
list.sort((a, b) => {
|
|
82
104
|
if (a === '.') return -1
|
|
83
105
|
if (b === '.') return 1
|