@nitra/cursor 11.3.0 → 11.4.1
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/docs/n-cursor.md +1 -1
- package/bin/n-cursor.js +12 -3
- package/package.json +1 -1
- package/rules/release/release.mjs +40 -1
- package/rules/vue/vue.mdc +19 -1
- package/scripts/auto-rules.mjs +42 -5
- package/scripts/docs/auto-rules.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [11.4.1] - 2026-06-16
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- release: commit-back більше не «вдається» мовчки за відхиленого push — результат `git push` тепер ЯВНО перевіряється (раніше тихий runGit повертав null, а реліз рапортував успіх, через що npm міг піти попереду git). За non-fast-forward (паралельний push у ту саму гілку) release-коміт автоматично rebase-иться на свіжий upstream, теги пересуваються на новий HEAD і push повторюється (до 5 спроб); без upstream або при rebase-конфлікті — кидаємо помилку (exit 1), тож CI-публікація не відбувається без приземленого commit-back.
|
|
8
|
+
|
|
9
|
+
## [11.4.0] - 2026-06-15
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Додано логіку відсіювання неактуальних правил/скілів за `availableRules`/`availableSkills
|
|
14
|
+
|
|
3
15
|
## [11.3.0] - 2026-06-15
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/bin/docs/n-cursor.md
CHANGED
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
#### Вкладена normalizeConfigWithAutoRules(parsedConfig)
|
|
174
174
|
|
|
175
175
|
- **Сигнатура:** `async function normalizeConfigWithAutoRules(parsedConfig: Record<string, unknown>): Promise<Record<string, unknown>>`
|
|
176
|
-
- **Призначення:** перевіряє типи полів, обчислює `auto-detected rules` (`detectAutoRules`), будує ефективний список правил (поточні + auto, мінус `disable-rules`), за яким `detectAutoSkills` визначає скіли. Далі `mergeConfigWithAutoDetected` зливає
|
|
176
|
+
- **Призначення:** перевіряє типи полів, обчислює `auto-detected rules` (`detectAutoRules`), будує ефективний список правил (поточні + auto, мінус `disable-rules`), за яким `detectAutoSkills` визначає скіли. Далі `mergeConfigWithAutoDetected` зливає дані (передаючи `availableRules`/`availableSkills` із каталогів пакета, щоб відсіяти з `rules`/`skills` неактуальні id, яких уже немає у пакеті — прибрані логуються через `🧹`), після чого `$schema` вирівнюється до `CONFIG_SCHEMA_URL`, додаються `disable-rules`/`disable-skills` (якщо непорожні), результат проходить через `sortConfigIdArrays`.
|
|
177
177
|
|
|
178
178
|
### logRuleMigrationsIfAny(parsedConfig)
|
|
179
179
|
|
package/bin/n-cursor.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* `npx \@nitra/cursor lint-text` — канонічний lint-text (text.mdc): `cspell` → `shellcheck` (з auto-fix) →
|
|
26
26
|
* `markdownlint-cli2 --fix` → `v8r` (json/json5/yaml/yml/toml)
|
|
27
27
|
* `npx \@nitra/cursor lint-doc-files` — детермінований детектор застарілості файлових док (`stale`: `missing`|`crc-mismatch`); правило doc-files (ignore-glob у `npm/rules/doc-files/js/docgen-ignore.mjs`; тека `docs/` поряд із джерелом). Режими: повний (exit 1), `--json` (exit 0), `--missing-only`, `--hook`/`--git` (hook-протокол, exit 2), `--degraded`
|
|
28
|
-
* `npx \@nitra/cursor fix-doc-files` — JS-оркестрована генерація файлових док (роутинг local/cloud) зі штампом CRC (`--limit`/`--from`/`--overwrite
|
|
28
|
+
* `npx \@nitra/cursor fix-doc-files` — JS-оркестрована генерація файлових док (роутинг local/cloud) зі штампом CRC (`--limit`/`--from`/`--overwrite`); `--stamp` — детерміноване перештампування CRC без LLM
|
|
29
29
|
* `npx \@nitra/cursor doc-aggregate modules` — JSON-лістинг логічних модулів (межі за `package.json`) для Tier 2 скілу doc-aggregate
|
|
30
30
|
* `npx \@nitra/cursor skill list` — скіли пакета без синку в проєкт
|
|
31
31
|
* `npx \@nitra/cursor skill taze` — промпт на stdout
|
|
@@ -315,9 +315,18 @@ async function readConfig(paths = {}) {
|
|
|
315
315
|
const merged = mergeConfigWithAutoDetected({
|
|
316
316
|
config: parsedConfig,
|
|
317
317
|
detectedRules: autoDetectedRules.rules,
|
|
318
|
-
detectedSkills: autoDetectedSkills.skills
|
|
318
|
+
detectedSkills: autoDetectedSkills.skills,
|
|
319
|
+
availableRules,
|
|
320
|
+
availableSkills
|
|
319
321
|
})
|
|
320
322
|
|
|
323
|
+
if (merged.pruned) {
|
|
324
|
+
const parts = []
|
|
325
|
+
if (merged.pruned.rules.length > 0) parts.push(`rules: ${merged.pruned.rules.join(', ')}`)
|
|
326
|
+
if (merged.pruned.skills.length > 0) parts.push(`skills: ${merged.pruned.skills.join(', ')}`)
|
|
327
|
+
console.log(`🧹 Прибрано з ${CONFIG_FILE} неактуальні (немає у пакеті) — ${parts.join('; ')}\n`)
|
|
328
|
+
}
|
|
329
|
+
|
|
321
330
|
const rest = Object.fromEntries(Object.entries(parsedConfig).filter(([k]) => k !== '$schema'))
|
|
322
331
|
const normalized = {
|
|
323
332
|
$schema: CONFIG_SCHEMA_URL,
|
|
@@ -1650,7 +1659,7 @@ try {
|
|
|
1650
1659
|
}
|
|
1651
1660
|
case 'fix-doc-files': {
|
|
1652
1661
|
// n-cursor fix-doc-files — local-only генерація файлових док (omlx) + CRC-штамп
|
|
1653
|
-
// (--limit/--from/--overwrite
|
|
1662
|
+
// (--limit/--from/--overwrite); --stamp — детерміноване
|
|
1654
1663
|
// перештампування source+crc без LLM. У CI не запускається (потрібна локальна модель).
|
|
1655
1664
|
if (args.includes('--stamp')) {
|
|
1656
1665
|
const { runDocFilesStampCli } = await import('../rules/doc-files/js/docgen-files-batch.mjs')
|
package/package.json
CHANGED
|
@@ -64,6 +64,45 @@ async function collectChangeFiles(cwd, manifest, runGit) {
|
|
|
64
64
|
return [{ file: null, entry: synthesized }]
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Пушить release-коміт (із тегами) у апстрім, переживаючи паралельні push у ту саму гілку.
|
|
69
|
+
* `runGit` — ТИХИЙ раннер (повертає null при помилці), тож non-fast-forward push не кидає, а
|
|
70
|
+
* повертає null; цей хелпер ЯВНО перевіряє результат, щоб реліз не «вдався» без приземленого
|
|
71
|
+
* commit-back (саме така мовчазна поразка лишала npm попереду git). За відмовою push:
|
|
72
|
+
* fetch + rebase release-коміту на свіжий апстрім, пересунути теги на новий HEAD і повторити
|
|
73
|
+
* (до `attempts` разів). Без апстріму або при rebase-конфлікті — кидаємо, а не маскуємо.
|
|
74
|
+
* @param {(args: string[]) => Promise<string | null>} runGit git-раннер
|
|
75
|
+
* @param {string[]} tags теги релізу (вже створені на поточному HEAD)
|
|
76
|
+
* @param {number} [attempts] максимум спроб push
|
|
77
|
+
* @returns {Promise<void>} результат; кидає, якщо push так і не приземлився
|
|
78
|
+
*/
|
|
79
|
+
async function pushReleaseWithRetry(runGit, tags, attempts = 5) {
|
|
80
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
81
|
+
const pushed = await runGit(['push', '--follow-tags'])
|
|
82
|
+
if (pushed !== null) return
|
|
83
|
+
if (attempt === attempts) break
|
|
84
|
+
// push відхилено (найімовірніше non-fast-forward — апстрім пішов уперед) → інтегруємо й пробуємо ще
|
|
85
|
+
const upstream = (await runGit(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']))?.trim()
|
|
86
|
+
if (!upstream) {
|
|
87
|
+
throw new Error('release: git push відхилено, а upstream для rebase немає — commit-back не приземлився')
|
|
88
|
+
}
|
|
89
|
+
const remote = upstream.includes('/') ? upstream.slice(0, upstream.indexOf('/')) : 'origin'
|
|
90
|
+
await runGit(['fetch', remote])
|
|
91
|
+
const rebased = await runGit(['rebase', upstream])
|
|
92
|
+
if (rebased === null) {
|
|
93
|
+
await runGit(['rebase', '--abort'])
|
|
94
|
+
throw new Error(`release: push відхилено і rebase на ${upstream} дав конфлікт — розв'яжи вручну`)
|
|
95
|
+
}
|
|
96
|
+
// після rebase хеш release-коміту змінився → пересуваємо теги на новий HEAD
|
|
97
|
+
for (const tag of tags) {
|
|
98
|
+
await runGit(['tag', '-f', tag])
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(
|
|
102
|
+
`release: git push не вдався після ${attempts} спроб (non-fast-forward?) — commit-back не приземлився, реліз неуспішний`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
67
106
|
/**
|
|
68
107
|
* @param {object} [opts] опції
|
|
69
108
|
* @param {string} [opts.cwd] корінь
|
|
@@ -112,7 +151,7 @@ export async function release(opts = {}) {
|
|
|
112
151
|
for (const tag of tags) {
|
|
113
152
|
await runGit(['tag', tag])
|
|
114
153
|
}
|
|
115
|
-
await runGit
|
|
154
|
+
await pushReleaseWithRetry(runGit, tags)
|
|
116
155
|
}
|
|
117
156
|
return released
|
|
118
157
|
}
|
package/rules/vue/vue.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Vue
|
|
3
|
-
version: '2.
|
|
3
|
+
version: '2.3'
|
|
4
4
|
globs: "**/*.vue"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -110,6 +110,24 @@ const additionalInstructions = `
|
|
|
110
110
|
- **debounce/throttle** для частих подій.
|
|
111
111
|
- Після ручних **addEventListener** / підписок — прибирай у **onUnmounted**.
|
|
112
112
|
|
|
113
|
+
### Функції в шаблоні
|
|
114
|
+
|
|
115
|
+
Виклики функцій у шаблоні дозволені **лише** в обробниках подій (`@click`, `@change` тощо). У всіх інших місцях — `v-if`, `v-show`, атрибутах (`:prop`), інтерполяціях (`{{ }}`) — замінюй функції на `computed`-властивості: функція виконується при **кожному** render-і, тоді як `computed` кешується і перераховується лише при зміні залежностей.
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<!-- ❌ функція в умові, атрибуті та інтерполяції -->
|
|
119
|
+
<q-item v-if="getItems(order).length" :label="getLabel(item)">
|
|
120
|
+
{{ formatName(user) }}
|
|
121
|
+
</q-item>
|
|
122
|
+
|
|
123
|
+
<!-- ✅ реактивні змінні / computed / props -->
|
|
124
|
+
<q-item v-if="itemsMap[order.id].length" :label="item.label">
|
|
125
|
+
{{ user.displayName }}
|
|
126
|
+
</q-item>
|
|
127
|
+
<!-- обробник події — виклик функції дозволений -->
|
|
128
|
+
<q-btn @click="doSomething(item)" />
|
|
129
|
+
```
|
|
130
|
+
|
|
113
131
|
### Безпека
|
|
114
132
|
|
|
115
133
|
- Не довіряй **v-html** без санітизації; для форм/API — **CSRF**-захист за потреби; валідація **на сервері** обов’язкова.
|
package/scripts/auto-rules.mjs
CHANGED
|
@@ -392,14 +392,45 @@ export async function detectAutoRules({
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
/**
|
|
395
|
-
*
|
|
395
|
+
* Розділяє список id на доступні в пакеті й застарілі (відсутні).
|
|
396
|
+
* Без `available` нічого не прибирає — усе вважається доступним.
|
|
397
|
+
* @param {string[]} ids перелік id (rules або skills)
|
|
398
|
+
* @param {string[] | undefined} available id, що реально є у каталозі пакета
|
|
399
|
+
* @returns {{ kept: string[], pruned: string[] }} відфільтровані й прибрані id
|
|
400
|
+
*/
|
|
401
|
+
function partitionByAvailability(ids, available) {
|
|
402
|
+
if (!available) return { kept: ids, pruned: [] }
|
|
403
|
+
const availableSet = new Set(available)
|
|
404
|
+
const kept = []
|
|
405
|
+
const pruned = []
|
|
406
|
+
for (const id of ids) {
|
|
407
|
+
if (availableSet.has(id)) kept.push(id)
|
|
408
|
+
else pruned.push(id)
|
|
409
|
+
}
|
|
410
|
+
return { kept, pruned }
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Доповнює конфіг автодетектом (лише додає; існуючі вручну задані елементи не прибирає),
|
|
415
|
+
* а за наявності `availableRules`/`availableSkills` ще й прибирає з `rules`/`skills`
|
|
416
|
+
* неактуальні id, яких уже немає у пакеті (наприклад, правило чи скіл видалено з нової
|
|
417
|
+
* версії \@nitra/cursor) — інакше sync щоразу падав би на завантаженні відсутнього
|
|
418
|
+
* `rules/<id>.mdc` чи `skills/<id>/`. Прибрані id повертаються у полі `pruned` (для логу).
|
|
396
419
|
* @param {object} params параметри оновлення
|
|
397
420
|
* @param {{ rules: unknown, skills?: unknown, ['disable-rules']?: unknown, ['disable-skills']?: unknown }} params.config розпарсений `.n-cursor.json`
|
|
398
421
|
* @param {string[]} params.detectedRules правила, визначені автодетектом
|
|
399
422
|
* @param {string[]} params.detectedSkills skills, визначені автодетектом
|
|
400
|
-
* @
|
|
423
|
+
* @param {string[]} [params.availableRules] id правил, наявних у каталозі `rules/` пакета (для відсіву неактуальних)
|
|
424
|
+
* @param {string[]} [params.availableSkills] id skills, наявних у каталозі `skills/` пакета (для відсіву неактуальних)
|
|
425
|
+
* @returns {{ rules: string[], skills: string[], pruned?: { rules: string[], skills: string[] } } & Record<string, unknown>} новий нормалізований конфіг
|
|
401
426
|
*/
|
|
402
|
-
export function mergeConfigWithAutoDetected({
|
|
427
|
+
export function mergeConfigWithAutoDetected({
|
|
428
|
+
config,
|
|
429
|
+
detectedRules,
|
|
430
|
+
detectedSkills,
|
|
431
|
+
availableRules,
|
|
432
|
+
availableSkills
|
|
433
|
+
}) {
|
|
403
434
|
const existingRules = migrateRuleIds(normalizeIdList(config.rules))
|
|
404
435
|
const existingSkills = normalizeIdList(config.skills)
|
|
405
436
|
const disableRules = migrateRuleIds(normalizeIdList(config['disable-rules']))
|
|
@@ -419,13 +450,19 @@ export function mergeConfigWithAutoDetected({ config, detectedRules, detectedSki
|
|
|
419
450
|
}
|
|
420
451
|
}
|
|
421
452
|
|
|
422
|
-
|
|
423
|
-
const
|
|
453
|
+
const { kept: keptRules, pruned: prunedRules } = partitionByAvailability(rules, availableRules)
|
|
454
|
+
const { kept: keptSkills, pruned: prunedSkills } = partitionByAvailability(skills, availableSkills)
|
|
455
|
+
|
|
456
|
+
/** @type {{ rules: string[], skills: string[], pruned?: { rules: string[], skills: string[] } } & Record<string, unknown>} */
|
|
457
|
+
const normalized = { rules: keptRules, skills: keptSkills }
|
|
424
458
|
if (disableRules.length > 0) {
|
|
425
459
|
normalized['disable-rules'] = disableRules
|
|
426
460
|
}
|
|
427
461
|
if (disableSkills.length > 0) {
|
|
428
462
|
normalized['disable-skills'] = disableSkills
|
|
429
463
|
}
|
|
464
|
+
if (prunedRules.length > 0 || prunedSkills.length > 0) {
|
|
465
|
+
normalized.pruned = { rules: prunedRules, skills: prunedSkills }
|
|
466
|
+
}
|
|
430
467
|
return normalized
|
|
431
468
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/scripts/auto-rules.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: d50b922f
|
|
5
5
|
score: 90
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -29,7 +29,7 @@ detectAutoRules
|
|
|
29
29
|
Визначає активні правила на основі spec, перевіряючи їх проти згенерованих фактів.
|
|
30
30
|
|
|
31
31
|
mergeConfigWithAutoDetected
|
|
32
|
-
Доповнює конфігурацію, додаючи визначені автоправила та налаштування, з урахуванням legacy-ID.
|
|
32
|
+
Доповнює конфігурацію, додаючи визначені автоправила та налаштування, з урахуванням legacy-ID; за наявності `availableRules`/`availableSkills` ще й відсіює з `rules`/`skills` неактуальні id, яких немає у пакеті (повертає їх у полі `pruned`).
|
|
33
33
|
|
|
34
34
|
## Публічний API
|
|
35
35
|
|