@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
@@ -1310,7 +1310,7 @@ try {
1310
1310
  break
1311
1311
  }
1312
1312
  case 'skill': {
1313
- process.exitCode = await runSkillsCli(args)
1313
+ process.exitCode = runSkillsCli(args)
1314
1314
 
1315
1315
  break
1316
1316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.72",
3
+ "version": "1.13.74",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -7,7 +7,8 @@
7
7
  #
8
8
  # Перевіряє: якщо в `dependencies` є `vue`, то потрібні канонічні Vue/Vite
9
9
  # залежності, `devDependencies.vite` має бути мажорної версії ≥ 8, а `esbuild`
10
- # у dependencies/devDependencies заборонений (міграція на rolldown).
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 {
@@ -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
- .sort((a, b) => a.localeCompare(b))
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 {Promise<number>} exit code
171
+ * @param {{ packageRoot?: string, projectDir?: string, log?: (line: string) => void, logError?: (line: string) => void }} [options] перевизначення кореня пакета, каталогу проєкту та функцій виводу (для тестів)
172
+ * @returns {number} exit code
173
173
  */
174
- export async function runSkillsCli(argv, options = {}) {
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, { cwd: repoRoot })) {
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
- roots.add(relRoot === '' ? '.' : relRoot)
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