@nitra/cursor 1.13.65 → 1.13.67

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.
@@ -334,7 +334,19 @@ jq -c '.operations[]' "$RESPONSE_CLEAN_FILE" | while IFS= read -r op_json; do
334
334
  continue
335
335
  ;;
336
336
  esac
337
- DEST_PATH=$(resolve_unique_slug_path "$SLUG")
337
+ # Keep the draft's `YYYYMMDD-HHMMSS-` prefix on the clean file: the name
338
+ # stays anchored to capture time, only the slug part changes between draft
339
+ # and clean, and docs/adr/ keeps sorting chronologically. Drafts without a
340
+ # timestamp prefix fall back to a bare `<slug>.md`.
341
+ case "$FILE" in
342
+ [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]-*)
343
+ DEST_SLUG="$(printf '%s' "$FILE" | cut -c1-15)-$SLUG"
344
+ ;;
345
+ *)
346
+ DEST_SLUG="$SLUG"
347
+ ;;
348
+ esac
349
+ DEST_PATH=$(resolve_unique_slug_path "$DEST_SLUG")
338
350
  printf '%s\n' "$CONTENT" > "$DEST_PATH"
339
351
  rm -- "$SRC_PATH"
340
352
  log "rewrite: $FILE → $(basename "$DEST_PATH")"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@
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.67] - 2026-05-21
8
+
9
+ ### Changed
10
+
11
+ - Правило **`changelog`**: перевірка `changelog/consistency` більше не вимагає version-bump і запису в `CHANGELOG.md` за зміни синхронізованого з `@nitra/cursor` інструментарію. Інверсію шляхів розширено: до `docs/` / `doc/` додано префікси `.cursor/` (канонічні правила та скіли) і `.claude/` (ADR-хуки). Причина: синк tooling-пакета — це дзеркало `@nitra/cursor`, а не зміна логіки воркспейсу, тож раніше кожен `npx @nitra/cursor` тягнув за собою зайвий bump і секцію CHANGELOG, де описувалося лише оновлення інструментарію. Кореневі `AGENTS.md` / `CLAUDE.md` окремого запису в інверсії не потребують — їх покриває пропуск кореня монорепо (нижче). Джерело правил у самому репо `@nitra/cursor` лежить під `npm/`, тож на нього інверсія не поширюється — реальні зміни правил і далі вимагають bump. Зачеплено: [check.mjs](rules/changelog/fix/consistency/check.mjs) (`CHANGELOG_IGNORE_PATH_PREFIXES`), [changelog.mdc](rules/changelog/changelog.mdc) (секція «Інверсія», bump `2.5` → `2.6`).
12
+ - Правило **`changelog`**: корінь монорепо (воркспейс `.` за наявності підпакетів) більше не перевіряється на bump/CHANGELOG. Причина: кореневий `package.json` монорепо — це glue/конфіг/tooling (`private`, `workspaces`), власного продуктового CHANGELOG він не веде, а помітні зміни документують підпакети. Раніше будь-яка правка в корені (конфіги, синк правил, bump `@nitra/cursor` у `devDependencies`) хибно вимагала bump кореневої `version`. Одно-пакетні репозиторії (корінь = єдиний воркспейс) перевіряються як і раніше. Зачеплено: [check.mjs](rules/changelog/fix/consistency/check.mjs) (`isMonorepoRoot` у `check()`), [changelog.mdc](rules/changelog/changelog.mdc).
13
+
14
+ ### Fixed
15
+
16
+ - Правило **`changelog`**: перевірка `changelog/consistency` коректно опрацьовує файли з не-ASCII іменами (кирилиця тощо). `git diff` / `git ls-files` без `-z` застосовують `core.quotePath` і повертають такі шляхи у C-quoted формі `"docs/\320\262..."` — рядок не збігався з префіксами інверсії, тож, наприклад, чернетка ADR з кириличною назвою під `docs/` хибно вважалася зміною, що потребує bump, і валила перевірку. Усі переліки шляхів тепер читаються через `-z` (`NUL`-розділення, без quoting). Зачеплено: [check.mjs](rules/changelog/fix/consistency/check.mjs) (`splitNulPaths`, `listChangedPathsAgainstBase`), [check.test.mjs](rules/changelog/fix/consistency/check.test.mjs) (тести quotePath, синку tooling і пропуску кореня монорепо).
17
+
18
+ ## [1.13.66] - 2026-05-20
19
+
20
+ ### Changed
21
+
22
+ - `adr`: `normalize-decisions.sh` тепер зберігає `YYYYMMDD-HHMMSS-`-префікс чернетки в імені clean-файлу — операція `rewrite` пише результат у `<timestamp>-<slug>.md` замість bare `<slug>.md`. Причина: під час нормалізації LLM генерує `slug` заново, тож раніше чернетка `20260518-092807-foo.md` ставала clean-файлом з абсолютно іншим іменем `bar.md` — назва «стрибала» цілком. Тепер timestamp-префікс лишається стабільним якорем: між draft і clean змінюється лише slug-частина, а `docs/adr/` сортується хронологічно (capture-час). Чернетки без `YYYYMMDD-HHMMSS-`-префікса лишаються на fallback bare `<slug>.md`. Колізії resolve'яться як і раніше — детермінований суфікс `-2`, `-3`, тепер на повному імені `<timestamp>-<slug>-N.md`. Зачеплено: [normalize-decisions.sh](.claude-template/hooks/normalize-decisions.sh) (нова `case`-гілка у rewrite-операції обчислює `DEST_SLUG` з timestamp-префіксом перед `resolve_unique_slug_path`), [adr.mdc](rules/adr/adr.mdc) (опис clean-формату, рядок таблиці `rewrite`, дерево каталогу `docs/adr/` й абзац про `slug`), [SKILL.md](skills/adr-normalize/SKILL.md) (опис rewrite-результату й дублів імен). Bump `adr.mdc` `2.0` → `2.1`.
23
+
7
24
  ## [1.13.65] - 2026-05-20
8
25
 
9
26
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.65",
3
+ "version": "1.13.67",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
package/rules/adr/adr.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Автоматичний збір ADR/Runbook/Knowledge-чернеток і батч-нормалізація у `docs/adr/` через Stop-хуки Claude Code та Cursor Agent
3
3
  alwaysApply: true
4
- version: '2.0'
4
+ version: '2.1'
5
5
  ---
6
6
 
7
7
  ## MADR v4 і дві фази
@@ -19,7 +19,7 @@ ADR живуть у єдиному каталозі **`docs/adr/`**. Clean ADR-
19
19
  Є два стани файлу, які відрізняються YAML frontmatter:
20
20
 
21
21
  - **Draft** — файл з frontmatter `session: …`, `captured: …`, `transcript: …` та timestamp-іменем `YYYYMMDD-HHMMSS-<sid>.md`. Пише `capture-decisions.sh` після кожної сесії.
22
- - **Clean** — файл без frontmatter, з kebab-case-іменем `<slug>.md` (наприклад `ланцюжок-запуску-abie.md`). Створює `normalize-decisions.sh` або людина руками.
22
+ - **Clean** — файл без frontmatter, з kebab-case-іменем. `normalize-decisions.sh` зберігає timestamp-префікс чернетки → `YYYYMMDD-HHMMSS-<slug>.md` (наприклад `20260518-092807-ланцюжок-запуску-abie.md`); створений руками clean-файл може мати просто `<slug>.md`.
23
23
 
24
24
  `normalize-decisions.sh` ніколи не чіпає clean-файли — крім випадку `merge-into`, коли дописує `## Update YYYY-MM-DD` в кінець наявного clean-файлу.
25
25
 
@@ -44,10 +44,10 @@ LLM повертає масив операцій:
44
44
  | `op` | Семантика | Поля |
45
45
  | --- | --- | --- |
46
46
  | `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
47
- | `rewrite` | Чернетка стає окремим clean-файлом MADR v4 minimal: frontmatter знімається, ім'я → `<slug>.md`, додаються `**Status:** Accepted`, `**Date:**` з `captured` і canonical MADR headings. | `file`, `slug`, `content` |
47
+ | `rewrite` | Чернетка стає окремим clean-файлом MADR v4 minimal: frontmatter знімається, ім'я → `<timestamp>-<slug>.md` (timestamp-префікс чернетки збережено), додаються `**Status:** Accepted`, `**Date:**` з `captured` і canonical MADR headings. | `file`, `slug`, `content` |
48
48
  | `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
49
49
 
50
- `slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. Колізія slug-ів обробляється детермінованим суфіксом `-2`, `-3`.
50
+ `slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. До імені clean-файлу скрипт додає `YYYYMMDD-HHMMSS-` чернетки, тож запис лишається прив'язаним до часу capture, а `docs/adr/` сортується хронологічно. Колізія імен обробляється детермінованим суфіксом `-2`, `-3`.
51
51
 
52
52
  ### Жодних git-операцій
53
53
 
@@ -82,8 +82,8 @@ LLM повертає масив операцій:
82
82
 
83
83
  ```text
84
84
  docs/adr/
85
- ├── YYYYMMDD-HHMMSS-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
86
- └── <slug>.md # clean ADR-и (без frontmatter)
85
+ ├── YYYYMMDD-HHMMSS-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
86
+ └── YYYYMMDD-HHMMSS-<slug>.md # clean ADR-и (без frontmatter, timestamp-префікс чернетки збережено)
87
87
  .claude/hooks/
88
88
  ├── capture-decisions.sh # auto-synced з пакета
89
89
  ├── normalize-decisions.sh # auto-synced з пакета
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: CHANGELOG.md в кожному workspace, з двома моделями бази порівняння (npm і Python)
3
- version: '2.5'
3
+ version: '2.6'
4
4
  alwaysApply: true
5
5
  ---
6
6
 
@@ -14,7 +14,7 @@ alwaysApply: true
14
14
 
15
15
  **Тригер шляхів (приклади):** `npm/**`, `packages/foo/**`, будь-який каталог з власним `package.json` / `pyproject.toml`, куди потрапили правки.
16
16
 
17
- **Інверсія (bump не потрібен):** лише `docs/` / `doc/`; лише `.gitignore`; лише сам релізний крок (`CHANGELOG.md` + `version`).
17
+ **Інверсія (bump не потрібен):** лише `docs/` / `doc/`; синхронізований із `@nitra/cursor` інструментарій (`.cursor/`, `.claude/`); лише `.gitignore`; лише сам релізний крок (`CHANGELOG.md` + `version`). **Корінь монорепо** (воркспейс `.` за наявності підпакетів) не перевіряється взагалі — отже й кореневі `AGENTS.md` / `CLAUDE.md` та bump `@nitra/cursor` у `devDependencies`.
18
18
 
19
19
  **Pre-commit (людина):** `hk` у цьому репо також запускає `check changelog` при змінах під `npm/**` — агент не покладайся лише на commit hook; виконай кроки 1–3 **до** фінальної відповіді.
20
20
 
@@ -36,10 +36,12 @@ alwaysApply: true
36
36
  **Інверсія (за замовчуванням не вимагають bump/CHANGELOG):**
37
37
 
38
38
  - зміни **лише** під `docs/` або `doc/`;
39
+ - синхронізований із `@nitra/cursor` інструментарій під `.cursor/` (канонічні правила та скіли) і `.claude/` (ADR-хуки) — це дзеркало tooling-пакета, а не логіка воркспейсу;
40
+ - будь-які зміни в **корені монорепо** (воркспейс `.` за наявності підпакетів) — корінь веде glue/конфіг/tooling, власного CHANGELOG не має; помітні зміни документують підпакети. Сюди потрапляють і кореневі `AGENTS.md` / `CLAUDE.md`, і bump `@nitra/cursor` у `devDependencies`;
39
41
  - файли під **`.gitignore`**;
40
42
  - правки **лише** `CHANGELOG.md` або поля `version` у маніфесті як сам релізний крок.
41
43
 
42
- **Вимагають bump + нову секцію CHANGELOG** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо).
44
+ **Вимагають bump + нову секцію CHANGELOG** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо). Виняток `.cursor/` / `.claude/` **не** поширюється на джерело правил у репо `@nitra/cursor` — воно лежить під `npm/`, тож зміни в ньому далі вимагають bump.
43
45
 
44
46
  Перевірка програмна (`changelog/fix/consistency/check.mjs`).
45
47
 
@@ -38,11 +38,14 @@ const FEATURE_BASE_BRANCH_CANDIDATES = Object.freeze(['dev', 'main'])
38
38
  /** Гілка `dev`: local-only не активний (крім незакомічених registry-published). */
39
39
  const LOCAL_ONLY_SKIP_BRANCH = 'dev'
40
40
 
41
- /** Префікси шляхів (posix), які не вважаються релізними змінами — інверсія glob (n-changelog.mdc). */
42
- const CHANGELOG_IGNORE_PATH_PREFIXES = Object.freeze(['docs/', 'doc/'])
43
-
44
- /** Точні шляхи каталогів документації (posix), без bump. */
45
- const CHANGELOG_IGNORE_PATH_EXACT = Object.freeze(['docs', 'doc'])
41
+ /**
42
+ * Префікси шляхів (posix), які не вважаються релізними змінами — інверсія glob (n-changelog.mdc):
43
+ * документація (`docs/`, `doc/`) та синхронізований із `@nitra/cursor` інструментарій
44
+ * (`.cursor/` канонічні правила й скіли, `.claude/` — ADR-хуки). Останнє — дзеркало tooling-пакета,
45
+ * не логіка самого воркспейсу, тож bump CHANGELOG не потрібен. Джерело правил у репо `@nitra/cursor`
46
+ * лежить під `npm/`, тож на нього ця інверсія не поширюється.
47
+ */
48
+ const CHANGELOG_IGNORE_PATH_PREFIXES = Object.freeze(['docs/', 'doc/', '.cursor/', '.claude/'])
46
49
 
47
50
  /** Таймаут на `npm view` / PyPI (мс) */
48
51
  const REGISTRY_TIMEOUT_MS = 10_000
@@ -117,9 +120,6 @@ async function resolveBranchRef(branchName) {
117
120
  */
118
121
  function isChangelogIgnoredPath(relPath) {
119
122
  const p = relPath.replaceAll('\\', '/').replace(LEADING_DOTSLASH_RE, '')
120
- if (CHANGELOG_IGNORE_PATH_EXACT.includes(p)) {
121
- return true
122
- }
123
123
  return CHANGELOG_IGNORE_PATH_PREFIXES.some(prefix => p.startsWith(prefix))
124
124
  }
125
125
 
@@ -196,29 +196,31 @@ function pathspecForWorkspace(ws, subWorkspaces) {
196
196
  return ['.', ...subWorkspaces.filter(s => s !== '.').map(s => `:(exclude)${s}/`)]
197
197
  }
198
198
 
199
+ /**
200
+ * Шляхи з `NUL`-розділеного виводу git (прапорець `-z`).
201
+ *
202
+ * `-z` критичний: без нього git застосовує `core.quotePath` і повертає не-ASCII імена файлів
203
+ * (кирилиця тощо) у C-quoted формі `"docs/\320\262..."`. Такий рядок не збігається з
204
+ * префіксами інверсії (`docs/`, `.cursor/`, ...), тож файл хибно вважався б зміною, що потребує bump.
205
+ * @param {string | null} nulSeparated сирий вивід git або `null`
206
+ * @returns {string[]} шляхи без обгортки/escape
207
+ */
208
+ function splitNulPaths(nulSeparated) {
209
+ if (typeof nulSeparated !== 'string') {
210
+ return []
211
+ }
212
+ return nulSeparated.split('\0').filter(p => p.length > 0)
213
+ }
214
+
199
215
  /**
200
216
  * @param {string} baseRef параметр
201
217
  * @param {string[]} pathspec параметр
202
218
  * @returns {Promise<string[]>} результат
203
219
  */
204
220
  async function listChangedPathsAgainstBase(baseRef, pathspec) {
205
- /**
206
- @type {string[]}
207
- */
208
- const out = []
209
- const diffArgs =
210
- baseRef === 'HEAD'
211
- ? ['diff', '--name-only', 'HEAD', '--', ...pathspec]
212
- : ['diff', '--name-only', baseRef, '--', ...pathspec]
213
- const diffOut = await gitOrNull(diffArgs)
214
- if (typeof diffOut === 'string' && diffOut.trim().length > 0) {
215
- out.push(...diffOut.trim().split('\n'))
216
- }
217
- const untrackedOut = await gitOrNull(['ls-files', '--others', '--exclude-standard', '--', ...pathspec])
218
- if (typeof untrackedOut === 'string' && untrackedOut.trim().length > 0) {
219
- out.push(...untrackedOut.trim().split('\n'))
220
- }
221
- return [...new Set(out)]
221
+ const diffOut = await gitOrNull(['diff', '--name-only', '-z', baseRef, '--', ...pathspec])
222
+ const untrackedOut = await gitOrNull(['ls-files', '--others', '--exclude-standard', '-z', '--', ...pathspec])
223
+ return [...new Set([...splitNulPaths(diffOut), ...splitNulPaths(untrackedOut)])]
222
224
  }
223
225
 
224
226
  /**
@@ -551,6 +553,9 @@ export async function check(opts = {}) {
551
553
 
552
554
  const workspaces = await getMonorepoProjectRootDirs(process.cwd())
553
555
  const subWorkspaces = workspaces.filter(w => w !== '.')
556
+ // Корінь монорепо (`.` за наявності підпакетів) — це glue/конфіг/tooling, а не логіка
557
+ // продукту: власного CHANGELOG він не веде, помітні зміни документують підпакети.
558
+ const isMonorepoRoot = subWorkspaces.length > 0
554
559
 
555
560
  /**
556
561
  @type {import('../../../../scripts/utils/package-manifest.mjs').PackageManifest[]}
@@ -562,6 +567,12 @@ export async function check(opts = {}) {
562
567
  const localOnly = []
563
568
 
564
569
  for (const ws of workspaces) {
570
+ if (ws === '.' && isMonorepoRoot) {
571
+ pass(
572
+ '<root>: корінь монорепо (glue/конфіг/tooling) — перевірку CHANGELOG пропущено; помітні зміни документують підпакети'
573
+ )
574
+ continue
575
+ }
565
576
  const manifest = await readPackageManifest(ws)
566
577
  if (!manifest) {
567
578
  continue
@@ -48,7 +48,7 @@ description: >-
48
48
  git diff docs/adr/
49
49
  ```
50
50
 
51
- Видалені файли — `delete`-операція. Нові файли `<slug>.md` — `rewrite`. Модифіковані clean-файли — `merge-into`.
51
+ Видалені файли — `delete`-операція. Нові файли `<timestamp>-<slug>.md` (timestamp-префікс чернетки збережено) — `rewrite`. Модифіковані clean-файли — `merge-into`.
52
52
 
53
53
  4. **Прийняти / відкотити:**
54
54
  - Прийняти все: `git add docs/adr/ && git commit -m "adr: normalize batch"`.
@@ -67,4 +67,4 @@ description: >-
67
67
 
68
68
  - LLM повернув криву JSON → у логу буде `invalid JSON response (first 200 chars): …`. Запусти ще раз — нерідко це разовий збій.
69
69
  - Скрипт виходить миттєво без логу → перевір `ADR_NORMALIZE_RUNNING` у env (recursion guard) і чи репо не у стані merge/rebase.
70
- - Перейменування зробило слаги-дублі (`<slug>-2.md`) → це нормально, скрипт детермінований; під час review можна обʼєднати руками й видалити `-2`.
70
+ - Перейменування зробило дублі імен (`<timestamp>-<slug>-2.md`) → це нормально, скрипт детермінований; під час review можна обʼєднати руками й видалити `-2`.