@nitra/cursor 1.8.197 → 1.8.199

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,24 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.199] - 2026-05-07
8
+
9
+ ### Added
10
+
11
+ - `auto-rules.mjs`: автоматична міграція застарілих rule-id у `.n-cursor.json` через карту `RULE_MIGRATIONS`. Перший зареєстрований запис — `image` → `image-compress` + `image-avif` (split з 1.8.197). Застосовується і до `rules`, і до `disable-rules`, з дедуплікацією. CLI `n-cursor.js` логує `📦 Авто-міграція .n-cursor.json: image → image-compress, image-avif` перед нормалізацією, потім записує оновлений конфіг (як і раніше — лише якщо вміст реально змінився).
12
+ - `tests/auto-rules.test.mjs`: тести `migrateRuleIds` (порядок, дедуплікація, no-op для актуальних id), `detectLegacyRuleIds`, `mergeConfigWithAutoDetected` з legacy `image` у `rules`/`disable-rules`/конфлікті з `image-compress`.
13
+
14
+ ### Changed
15
+
16
+ - `n-cursor.js`: розширено імпорт з `auto-rules.mjs` (`detectLegacyRuleIds`, `RULE_MIGRATIONS`); виокремлено хелпер `logRuleMigrationsIfAny` (читає сирий конфіг, виводить пояснення, не мутує — мутацію виконує `migrateRuleIds` усередині `mergeConfigWithAutoDetected`). Завдяки цьому `npx @nitra/cursor` сам перебиває `image` на пару наступників — користувачу не треба руками правити `.n-cursor.json`.
17
+
18
+ ## [1.8.198] - 2026-05-07
19
+
20
+ ### Changed
21
+
22
+ - `image-compress` (mdc v1.0 → v1.1): мінімум `@nitra/minify-image` піднято з **3.2.0** до **3.3.1**. У `3.3.1` upstream CLI порівнює sha1 raster-сорсу зі збереженим у `.n-minify-image.tsv` і автоматично перегенеровує `<source>.avif` при зміні контенту оригіналу — раніше stale `.avif` лишався поки розробник не видаляв його вручну. Додано пояснювальний абзац у правило.
23
+ - `image-avif` (mdc v1.0 → v1.1): крок 1 (`npx @nitra/minify-image --src=. --write --avif`) явно требує ≥ 3.3.1 і документує, що sha1-перевірка для регенерації застарілого AVIF тепер живе у CLI; `@nitra/cursor` цю логіку **не дублює**.
24
+
7
25
  ## [1.8.197] - 2026-05-07
8
26
 
9
27
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -56,7 +56,13 @@ import { cwd } from 'node:process'
56
56
  import { fileURLToPath } from 'node:url'
57
57
 
58
58
  import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
59
- import { detectAutoRulesAndSkills, mergeConfigWithAutoDetected, normalizeIdList } from '../scripts/auto-rules.mjs'
59
+ import {
60
+ detectAutoRulesAndSkills,
61
+ detectLegacyRuleIds,
62
+ mergeConfigWithAutoDetected,
63
+ normalizeIdList,
64
+ RULE_MIGRATIONS
65
+ } from '../scripts/auto-rules.mjs'
60
66
  import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
61
67
  import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
62
68
  import { runLintGaCli } from '../scripts/lint-ga.mjs'
@@ -301,6 +307,7 @@ async function readConfig(paths = {}) {
301
307
  } catch {
302
308
  throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
303
309
  }
310
+ logRuleMigrationsIfAny(config)
304
311
  const normalized = await normalizeConfigWithAutoRules(config)
305
312
  if (JSON.stringify(normalized) !== JSON.stringify(config)) {
306
313
  await writeFile(configPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
@@ -309,6 +316,31 @@ async function readConfig(paths = {}) {
309
316
  return normalized
310
317
  }
311
318
 
319
+ /**
320
+ * Якщо у `rules` чи `disable-rules` є застарілі rule-id з `RULE_MIGRATIONS`,
321
+ * виводить пояснювальний лог про автоматичну заміну (саму заміну виконує
322
+ * `migrateRuleIds` у `mergeConfigWithAutoDetected` — тут лише користувацька комунікація).
323
+ * @param {Record<string, unknown>} parsedConfig сирий обʼєкт `.n-cursor.json` після `JSON.parse`
324
+ * @returns {void}
325
+ */
326
+ function logRuleMigrationsIfAny(parsedConfig) {
327
+ /** @type {Set<string>} */
328
+ const seen = new Set()
329
+ for (const key of /** @type {const} */ (['rules', 'disable-rules'])) {
330
+ const list = parsedConfig[key]
331
+ if (!Array.isArray(list)) continue
332
+ const legacy = detectLegacyRuleIds(normalizeIdList(list))
333
+ for (const id of legacy) seen.add(id)
334
+ }
335
+ if (seen.size === 0) return
336
+ console.log(`📦 Авто-міграція ${CONFIG_FILE}:`)
337
+ for (const id of seen) {
338
+ const replacement = RULE_MIGRATIONS[id].join(', ')
339
+ console.log(` • ${id} → ${replacement}`)
340
+ }
341
+ console.log('')
342
+ }
343
+
312
344
  /**
313
345
  * Витягує чисте ім'я файлу правила (без шляху, але зберігає .mdc)
314
346
  * "npm/mdc/text.mdc" → "text.mdc"
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
3
3
  alwaysApply: true
4
- version: '1.0'
4
+ version: '1.1'
5
5
  ---
6
6
 
7
7
  AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor check image-avif` — у `lint-image` прапорець `--avif` заборонений (це валідує правило `image-compress`). Перевірка робить три кроки в порядку:
8
8
 
9
- 1. Запускає `npx @nitra/minify-image --src=. --write --avif` — генерує `<name>.<ext>.avif` поряд з кожним PNG/JPEG/GIF.
9
+ 1. Запускає `npx @nitra/minify-image --src=. --write --avif` (≥ **3.3.1**) — генерує `<name>.<ext>.avif` поряд з кожним PNG/JPEG/GIF. **Перегенерація при оновленні оригіналу:** з 3.3.1 CLI порівнює sha1 кожного raster-сорсу зі збереженим у `.n-minify-image.tsv` і автоматично перезаписує `<source>.avif`, якщо оригінал відредагували після останнього прогону. `@nitra/cursor` цю логіку не дублює — sha1-кеш живе всередині `@nitra/minify-image`.
10
10
  2. Сканує `.vue` (а також `.html`) файли в кожному workspace-пакеті (root + workspaces) і автоматично переписує raster-посилання на AVIF-двійник у двох формах:
11
11
  - **Імпорт-пов'язані** — `import x from '...png|jpg|jpeg|gif'` (далі `:src="x"` у шаблоні);
12
12
  - **Прямі статичні** — `<img src="...png" />` у `<template>` (Vite перетворює такий шлях на asset-імпорт на етапі збірки, тож вимога та сама).
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  description: Оптимізація raster/SVG через @nitra/minify-image у локальному lint
3
3
  alwaysApply: true
4
- version: '1.0'
4
+ version: '1.1'
5
5
  ---
6
6
 
7
- CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **3.2.0**) запускається через `npx` (як `markdownlint-cli2` у text.mdc) і **не** додається в `dependencies` / `devDependencies`. Канонічний `lint-image` — авто-оптимізація з прапорцем `--write`: стискає raster/SVG на місці. **AVIF-генерація (`--avif`) у `lint-image` заборонена** — її виконує окреме правило `image-avif` (`npx @nitra/cursor check image-avif`), яке заодно прибирає AVIF-сироти. Split-cache робить повторні прогони дешевими — і локально, і після `git clone`.
7
+ CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **3.3.1**) запускається через `npx` (як `markdownlint-cli2` у text.mdc) і **не** додається в `dependencies` / `devDependencies`. Канонічний `lint-image` — авто-оптимізація з прапорцем `--write`: стискає raster/SVG на місці. **AVIF-генерація (`--avif`) у `lint-image` заборонена** — її виконує окреме правило `image-avif` (`npx @nitra/cursor check image-avif`), яке заодно прибирає AVIF-сироти. Split-cache робить повторні прогони дешевими — і локально, і після `git clone`.
8
+
9
+ Мінімум `3.3.1` важливий для правила `image-avif`: починаючи з цієї версії, CLI порівнює sha1 raster-сорсу зі збереженим у `.n-minify-image.tsv` і **перегенеровує `<source>.avif`** при будь-якій зміні контенту оригіналу (раніше stale `.avif` лишався, поки розробник не видаляв його вручну).
8
10
 
9
11
  Перевірка лише локальна — у CI `lint-image` не запускаємо (sharp/svgo тягнуть бінарні залежності, цінність на ubuntu-runner-ах нижча за час прогону). Окремий workflow `lint-image.yml` створювати не треба.
10
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.197",
3
+ "version": "1.8.199",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -49,6 +49,45 @@ export const AUTO_RULE_ORDER = Object.freeze([
49
49
  /** Порядок автододавання skills відповідно до `auto-rules.md`. */
50
50
  export const AUTO_SKILL_ORDER = Object.freeze(['abie-kustomize', 'fix', 'lint'])
51
51
 
52
+ /**
53
+ * Карта міграції застарілих rule-id у `.n-cursor.json` на актуальні.
54
+ * Застосовується автоматично при читанні конфігу (як для `rules`, так і для `disable-rules`).
55
+ * Приклад: `image` → `image-compress` + `image-avif` (правило розщеплене у 1.8.197).
56
+ */
57
+ export const RULE_MIGRATIONS = Object.freeze(
58
+ /** @type {Record<string, readonly string[]>} */ ({
59
+ image: Object.freeze(['image-compress', 'image-avif'])
60
+ })
61
+ )
62
+
63
+ /**
64
+ * Розгортає застарілі rule-id у списку згідно з `RULE_MIGRATIONS`. Зберігає порядок,
65
+ * дедуплікує. Чистий хелпер: не мутує вхід, не логує.
66
+ * @param {string[]} ids нормалізований список id (як з `normalizeIdList`)
67
+ * @returns {string[]} список з legacy-id, заміненими на нові; решта без змін
68
+ */
69
+ export function migrateRuleIds(ids) {
70
+ /** @type {string[]} */
71
+ const out = []
72
+ for (const id of ids) {
73
+ const replacement = Object.hasOwn(RULE_MIGRATIONS, id) ? RULE_MIGRATIONS[id] : [id]
74
+ for (const newId of replacement) {
75
+ if (!out.includes(newId)) out.push(newId)
76
+ }
77
+ }
78
+ return out
79
+ }
80
+
81
+ /**
82
+ * Повертає лише ті legacy rule-id зі списку, для яких є запис у `RULE_MIGRATIONS`.
83
+ * Використовується для людинозрозумілого логування міграції при синхронізації CLI.
84
+ * @param {string[]} ids нормалізований список id
85
+ * @returns {string[]} legacy id, які потребуватимуть заміни у `migrateRuleIds`
86
+ */
87
+ export function detectLegacyRuleIds(ids) {
88
+ return ids.filter(id => Object.hasOwn(RULE_MIGRATIONS, id))
89
+ }
90
+
52
91
  /**
53
92
  * Граф залежностей між правилами (`auto-rules.md` синтаксис `rule - [other]`).
54
93
  * Ключ варто автододати, коли всі правила-залежності вже додані до конфігу — щоб
@@ -649,9 +688,9 @@ export async function detectAutoRulesAndSkills({
649
688
  * @returns {{ rules: string[], skills: string[] } & Record<string, unknown>} новий нормалізований конфіг
650
689
  */
651
690
  export function mergeConfigWithAutoDetected({ config, detectedRules, detectedSkills }) {
652
- const existingRules = normalizeIdList(config.rules)
691
+ const existingRules = migrateRuleIds(normalizeIdList(config.rules))
653
692
  const existingSkills = normalizeIdList(config.skills)
654
- const disableRules = normalizeIdList(config['disable-rules'])
693
+ const disableRules = migrateRuleIds(normalizeIdList(config['disable-rules']))
655
694
  const disableSkills = normalizeIdList(config['disable-skills'])
656
695
 
657
696
  const rules = [...existingRules]