@nitra/cursor 1.23.0 → 1.24.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.
@@ -10,6 +10,12 @@
10
10
  * перед спавном LLM CLI.
11
11
  */
12
12
 
13
+ import { randomUUID } from 'node:crypto'
14
+ import { writeFileSync } from 'node:fs'
15
+ import { tmpdir } from 'node:os'
16
+ import { join } from 'node:path'
17
+ import { env } from 'node:process'
18
+
13
19
  interface PiContext {
14
20
  cwd: string
15
21
  sessionId?: string
@@ -36,18 +42,12 @@ interface PiExec {
36
42
  ) => void
37
43
  }
38
44
 
39
- import { writeFileSync } from 'node:fs'
40
- import { tmpdir } from 'node:os'
41
- import { join } from 'node:path'
42
- import { randomUUID } from 'node:crypto'
43
- import { env } from 'node:process'
44
-
45
45
  const CAPTURE_HOOK = '.claude/hooks/capture-decisions.sh'
46
46
  const NORMALIZE_HOOK = '.claude/hooks/normalize-decisions.sh'
47
47
 
48
48
  /**
49
49
  * Pi extension entry point.
50
- * @param pi pi.dev extension API
50
+ * @param {PiExec} pi pi.dev extension API
51
51
  */
52
52
  export default function (pi: PiExec): void {
53
53
  pi.on('agent_end', async (_event, ctx) => {
@@ -0,0 +1,16 @@
1
+ {
2
+ "$comment": "TS-конфіг для pi.dev extension. Не компілюється сам пакетом (синкається як є у .pi/extensions/<name>/), потрібен лише для IDE/TS-сервера у проєкті-споживачі, щоб резолвити node:* модулі. Споживачу треба мати @types/node у devDependencies (зазвичай уже є транзитивно).",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "target": "ES2022",
7
+ "lib": ["ES2022"],
8
+ "types": ["node"],
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "isolatedModules": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["index.ts"]
16
+ }
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.24.0] - 2026-05-26
8
+
9
+ ### Added
10
+
11
+ - **`.pi-template/extensions/n-cursor-adr/tsconfig.json`** — мінімальний TS-конфіг шаблону pi.dev extension, який синкається у `.pi/extensions/n-cursor-adr/` разом із `index.ts`. Дозволяє IDE/TS-серверу резолвити `node:*` модулі без project-wide tsconfig у проєкті-споживачі (`types: ["node"]`, `module/target: ES2022 + NodeNext`, `noEmit`, `isolatedModules`). Споживачу потрібен `@types/node` у devDeps (зазвичай уже є транзитивно).
12
+ - **`pi` manifest у `npm/package.json`** — `{"skills":"skills","extensions":".pi-template/extensions"}`. Pi.dev під час `pi install npm:@nitra/cursor` тепер бачить explicit-шляхи до bundled-ресурсів, замість convention-auto-discovery. `extensions: ".pi-template/extensions"` критичний — pi за замовч. шукає `extensions/` у корені пакета, а у нас шлях нестандартний.
13
+ - **`"pi-package"` keyword** у `npm/package.json` — пакет з’являється у pi-gallery для discoverability.
14
+
15
+ ### Changed
16
+
17
+ - **`syncPiExtensions`** тепер копіює **всі файли** з теки `.pi-template/extensions/<name>/`, а не лише `index.ts`. Контракт повернення: `{ written, path, files }` — `path` тепер тека (а не файл), `files` — відсортований список базових імен. У `🤖 Claude-конфіг` логу та у `result.piExtension` caller-стороні виводиться тека.
18
+ - **`.pi-template/extensions/n-cursor-adr/index.ts`** — порядок імпортів виправлено (всі `node:*` імпорти підняті на самий верх, перед `interface PiContext`). Усувало `import/first` ESLint-помилку; функціонально без змін.
19
+
7
20
  ## [1.23.0] - 2026-05-25
8
21
 
9
22
  ### Added
package/bin/n-cursor.js CHANGED
@@ -1402,7 +1402,7 @@ async function runSync() {
1402
1402
  if (result.adrHook) parts.push('.claude/hooks/capture-decisions.sh')
1403
1403
  if (result.adrNormalizeHook) parts.push('.claude/hooks/normalize-decisions.sh')
1404
1404
  if (result.gitignoreAdr) parts.push('.gitignore (adr fragment)')
1405
- if (result.piExtension) parts.push('.pi/extensions/n-cursor-adr/index.ts')
1405
+ if (result.piExtension) parts.push('.pi/extensions/n-cursor-adr/')
1406
1406
  if (parts.length > 0) {
1407
1407
  console.log(`🤖 Claude-конфіг: ${parts.join(', ')}`)
1408
1408
  }
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "cursor",
8
8
  "cursor-rules",
9
9
  "mdc",
10
- "n"
10
+ "n",
11
+ "pi-package"
11
12
  ],
13
+ "pi": {
14
+ "skills": "skills",
15
+ "extensions": ".pi-template/extensions"
16
+ },
12
17
  "homepage": "https://github.com/n/cursor#readme",
13
18
  "bugs": {
14
19
  "url": "https://github.com/n/cursor/issues"
@@ -415,32 +415,41 @@ export function syncAdrNormalizeHookScript(projectRoot, templateDir) {
415
415
  }
416
416
 
417
417
  /**
418
- * Копіює bundled pi.dev TS-extension `npm/.pi-template/extensions/n-cursor-adr/index.ts`
419
- * у `.pi/extensions/n-cursor-adr/index.ts` проєкту-споживача. Файл fully-owned: при кожному
420
- * sync-у перезаписується. Якщо bundled template відсутній (наприклад, у legacy-версіях
421
- * пакета без `.pi-template/`) — повертаємо `{written: false}` без помилки.
418
+ * Копіює bundled pi.dev TS-extension `npm/.pi-template/extensions/n-cursor-adr/` (усі файли —
419
+ * `index.ts`, `tsconfig.json`, потенційні `package.json`/`.gitignore` тощо) у
420
+ * `.pi/extensions/n-cursor-adr/` проєкту-споживача. Тека fully-owned: при кожному sync-у
421
+ * перезаписується. Якщо bundled template відсутній (legacy-версії пакета без `.pi-template/`)
422
+ * або в ньому немає `index.ts` — повертаємо `{written: false}` без помилки.
422
423
  *
424
+ * Розширення поверх `index.ts` (tsconfig тощо) потрібні, бо `.pi/extensions/` синкається як є
425
+ * у проєкти-споживачі, а IDE/TS-сервер мусить резолвити `node:*` модулі без додаткових
426
+ * project-wide конфігів.
423
427
  * @param {string} projectRoot корінь проєкту-споживача
424
428
  * @param {string} bundledPackageRoot корінь установленого `@nitra/cursor` (із `.pi-template/`)
425
- * @returns {Promise<{ written: boolean, path: string }>} чи писали файл, та його відносний шлях
429
+ * @returns {Promise<{ written: boolean, path: string, files: string[] }>} чи писали; відносний шлях до теки розширення; список скопійованих базових імен (відсортований)
426
430
  */
427
431
  export async function syncPiExtensions(projectRoot, bundledPackageRoot) {
428
- const srcPath = join(
429
- bundledPackageRoot,
430
- PI_TEMPLATE_DIR_NAME,
431
- 'extensions',
432
- PI_EXTENSION_NAME,
433
- 'index.ts'
434
- )
435
- if (!existsSync(srcPath)) {
436
- return { written: false, path: '' }
432
+ const srcDir = join(bundledPackageRoot, PI_TEMPLATE_DIR_NAME, 'extensions', PI_EXTENSION_NAME)
433
+ const indexPath = join(srcDir, 'index.ts')
434
+ if (!existsSync(indexPath)) {
435
+ return { written: false, path: '', files: [] }
437
436
  }
438
- const content = await readFile(srcPath, 'utf8')
439
437
  const destDir = join(projectRoot, PI_EXTENSIONS_DIR, PI_EXTENSION_NAME)
440
438
  await mkdir(destDir, { recursive: true })
441
- const destPath = join(destDir, 'index.ts')
442
- await writeFile(destPath, content, 'utf8')
443
- return { written: true, path: `${PI_EXTENSIONS_DIR}/${PI_EXTENSION_NAME}/index.ts` }
439
+ const entries = await readdir(srcDir, { withFileTypes: true })
440
+ const copied = []
441
+ for (const entry of entries) {
442
+ if (!entry.isFile()) continue
443
+ const name = entry.name
444
+ const content = await readFile(join(srcDir, name), 'utf8')
445
+ await writeFile(join(destDir, name), content, 'utf8')
446
+ copied.push(name)
447
+ }
448
+ return {
449
+ written: true,
450
+ path: `${PI_EXTENSIONS_DIR}/${PI_EXTENSION_NAME}`,
451
+ files: copied.toSorted((a, b) => a.localeCompare(b))
452
+ }
444
453
  }
445
454
 
446
455
  /**
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: n-coverage-fix
3
+ description: >-
4
+ Автономна команда: запускає coverage, читає ## Recommendations у COVERAGE.md, ітеративно пише тести для вижилих мутантів до конвергенції
5
+ ---
6
+
7
+ # /n-coverage-fix — ітеративне підвищення mutation score
8
+
9
+ ## Важливо
10
+
11
+ ⚠️ Не запускати паралельно з іншим `/n-coverage-fix` або `bun coverage` — Stryker пише `mutation.json` і `incremental.json` в одну директорію. `n-cursor coverage` всередині вже серіалізований через `withLock('coverage')`, але паралельний запуск двох ітерацій скілу зіпсує дані.
12
+
13
+ ## Мета
14
+
15
+ Автономно підвищити mutation score: запустити `bun coverage`, записати тести для вижилих мутантів, повторити до конвергенції (score перестав зростати).
16
+
17
+ ## Алгоритм
18
+
19
+ ### Крок 1: Запусти coverage
20
+
21
+ ```bash
22
+ bun coverage
23
+ ```
24
+
25
+ (або `bun run coverage` якщо команда у `package.json`)
26
+
27
+ Чекай завершення. Якщо команди немає у `package.json` — запусти `n-cursor coverage` з кореня.
28
+
29
+ ### Крок 2: Прочитай вижилих мутантів
30
+
31
+ Прочитай `COVERAGE.md` — знайди секцію `## Recommendations`.
32
+
33
+ Якщо секції немає або вона порожня:
34
+ ```
35
+ ✓ Нема вижилих мутантів — mutation score повний
36
+ ```
37
+ → DONE
38
+
39
+ Запам'ятай поточний mutation score як `baseline_score` (рядок `| **Разом** |` з таблиці у COVERAGE.md).
40
+
41
+ ### Крок 3: Для кожного файлу з Recommendations — пиши тести
42
+
43
+ Для кожного `### <file>` у секції:
44
+
45
+ **3a. Читай контекст:**
46
+ - Source-файл (`<file>` від кореня проєкту)
47
+ - Таблицю вижилих мутантів: рядок, оригінал, заміна, тип
48
+ - Блок `**Приклад наявного тесту:**` — style guide для нових тестів
49
+
50
+ **3b. Знайди тестовий файл:**
51
+ Перший що існує:
52
+ 1. `<dir>/<basename>.test.js` — поруч із source
53
+ 2. `<dir>/<basename>.spec.js`
54
+ 3. `test/<basename>.test.js` від кореня
55
+ 4. `tests/<basename>.test.js` від кореня
56
+
57
+ Якщо жоден не знайдено — створи `<dir>/<basename>.test.js` з правильними imports (орієнтуйся на сусідні файли).
58
+
59
+ **3c. Напиши тести що вбивають кожен мутант:**
60
+
61
+ Керуйся типом мутації:
62
+ - `ConditionalExpression` (`→ false` / `→ true`): протестуй обидва branch явно — значення що робить умову `true` і значення що робить її `false`
63
+ - `BooleanLiteral` (`true → false`): перевір початковий стан — `initialValue === false`
64
+ - `LogicalOperator` (`&&` ↔ `||`): передай `null` та `undefined` **окремо**, перевір що результат різний для кожного
65
+ - `StringLiteral` / `EqualityOperator`: перевір точний рядок/значення, а не лише happy-path
66
+
67
+ Правила:
68
+ - НЕ видаляй і НЕ змінюй наявні тести
69
+ - Стиль: той самий `describe`/`it`/`expect`, мова коментарів — як у прикладі тесту
70
+ - Якщо `**Приклад наявного тесту:**` відсутній — орієнтуйся на інші test-файли у тій самій директорії
71
+
72
+ **3d. Після написання тестів:**
73
+ ```bash
74
+ bun test <testFile>
75
+ ```
76
+
77
+ Якщо FAIL — виправи саме ті тести що впали (до 2 спроб). Якщо не вдалося — логуй і переходь до наступного файлу.
78
+
79
+ ### Крок 4: Перевір що весь suite проходить
80
+
81
+ ```bash
82
+ bun test
83
+ ```
84
+
85
+ Якщо FAIL:
86
+ - Не відкочувати зміни
87
+ - Показати: яка помилка, які файли змінені, що вже покращено
88
+ - Очікувати рішення від user: [виправити вручну → продовжити] / [пропустити файл] / [зупинити]
89
+
90
+ ### Крок 5: Запусти coverage і перевір конвергенцію
91
+
92
+ ```bash
93
+ bun coverage
94
+ ```
95
+
96
+ Якщо CRASH (SIGURG, memory pressure): нагадати user — Stryker incremental зберіг прогрес, перезапустити `bun coverage`.
97
+
98
+ Прочитай новий COVERAGE.md. Візьми `new_score` з рядка `| **Разом** |`.
99
+
100
+ **Рішення:**
101
+ - Якщо `new_score > baseline_score` → `baseline_score = new_score` → перейти до Кроку 2 (наступна ітерація)
102
+ - Якщо `new_score <= baseline_score` → конвергенція:
103
+ ```
104
+ ✓ Конвергенція: mutation score більше не покращується.
105
+ Baseline: <baseline_score> → Фінал: <new_score>
106
+ ```
107
+ → DONE
108
+
109
+ ## Нотатки
110
+
111
+ - Stryker `incremental` (`incrementalFile`) зберігає прогрес між запусками — crash ≠ перезапуск з нуля
112
+ - Не комітити зміни автоматично — user вирішує коли комітити
113
+ - Пріоритет файлів: більше вижилих мутантів = важливіший (першим у Recommendations = найважливіший)
114
+ - Якщо `COVERAGE.md` відсутній — запусти `bun coverage` спочатку
@@ -0,0 +1 @@
1
+ [js-lint]