@nitra/cursor 1.13.63 → 1.13.64
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 +11 -0
- package/bin/n-cursor.js +1 -0
- package/package.json +1 -1
- package/rules/adr/adr.mdc +1 -1
- package/rules/adr/fix/hooks/template/.gitignore.snippet +8 -0
- package/rules/changelog/fix/consistency/check.mjs +5 -7
- package/scripts/sync-claude-config.mjs +72 -4
- package/skills/lint/SKILL.md +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
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.64] - 2026-05-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`sync-claude-config`**: при увімкненому правилі `adr` `npx @nitra/cursor` дописує в кореневий `.gitignore` канонічний фрагмент `rules/adr/fix/hooks/template/.gitignore.snippet` (`.claude/hooks/*.log`, `.normalize-state`, `.normalize.lock`) — логи ADR Stop-hook більше не потрапляють у git status.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Правило **`adr`**: посилання на канон `.gitignore.snippet` і згадка автоматичного дописування під час sync.
|
|
16
|
+
- **`.gitignore.snippet`**: додано базові рядки `node_modules/`, `dist/`, `*.secret` (як у кореневому `.gitignore` пакета).
|
|
17
|
+
|
|
7
18
|
## [1.13.63] - 2026-05-20
|
|
8
19
|
|
|
9
20
|
### Fixed
|
package/bin/n-cursor.js
CHANGED
|
@@ -1274,6 +1274,7 @@ async function runSync() {
|
|
|
1274
1274
|
if (result.commands.length > 0) parts.push(`${result.commands.length} slash-commands`)
|
|
1275
1275
|
if (result.adrHook) parts.push('.claude/hooks/capture-decisions.sh')
|
|
1276
1276
|
if (result.adrNormalizeHook) parts.push('.claude/hooks/normalize-decisions.sh')
|
|
1277
|
+
if (result.gitignoreAdr) parts.push('.gitignore (adr fragment)')
|
|
1277
1278
|
if (parts.length > 0) {
|
|
1278
1279
|
console.log(`🤖 Claude-конфіг: ${parts.join(', ')}`)
|
|
1279
1280
|
}
|
package/package.json
CHANGED
package/rules/adr/adr.mdc
CHANGED
|
@@ -95,7 +95,7 @@ docs/adr/
|
|
|
95
95
|
└── hooks.json # Cursor Agent stop-hooks для тих самих скриптів
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
`.gitignore` повинен містити
|
|
98
|
+
`.gitignore` у корені проєкту повинен містити базові рядки (`node_modules/`, `dist/`, `*.secret`) і патерни для ADR Stop-hook (**`.claude/hooks/*.log`**, `.claude/hooks/.normalize-state`, `.claude/hooks/.normalize.lock`). Канонічний фрагмент (дописується `npx @nitra/cursor`, коли правило `adr` увімкнене): [.gitignore.snippet](./fix/hooks/template/.gitignore.snippet).
|
|
99
99
|
|
|
100
100
|
## Stop-hook у `.claude/settings.json`
|
|
101
101
|
|
|
@@ -158,13 +158,11 @@ async function resolveChangelogComparisonPoint(branch) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
if (branch === 'main') {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
(originMainSha === headSha || (await isGitAncestor('origin/main', 'HEAD')))
|
|
167
|
-
) {
|
|
161
|
+
const originMainRaw = await gitOrNull(['rev-parse', '--verify', '--quiet', 'origin/main'])
|
|
162
|
+
const originMainSha = originMainRaw?.trim()
|
|
163
|
+
const headRaw = await gitOrNull(['rev-parse', 'HEAD'])
|
|
164
|
+
const headSha = headRaw?.trim()
|
|
165
|
+
if (originMainSha && headSha && (originMainSha === headSha || (await isGitAncestor('origin/main', 'HEAD')))) {
|
|
168
166
|
return { ref: 'origin/main', label: 'main' }
|
|
169
167
|
}
|
|
170
168
|
const parent = await gitOrNull(['rev-parse', '--verify', '--quiet', 'HEAD~1'])
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* Stop-hook (батч-нормалізація чернеток); умови — ті самі, що для `capture`.
|
|
19
19
|
* - `.cursor/hooks.json` — **merge**: користувацькі hooks зберігаються; ADR stop
|
|
20
20
|
* entries додаються, коли правило `adr` увімкнене, і видаляються, коли вимкнене.
|
|
21
|
+
* - `.gitignore` — **merge** (лише з `adr`): дописує відсутні рядки з канонічного
|
|
22
|
+
* фрагмента `rules/adr/fix/hooks/template/.gitignore.snippet` (`node_modules/`, `dist/`,
|
|
23
|
+
* `*.secret`, логи capture/normalize, `.normalize-state`, `.normalize.lock`); існуючі
|
|
24
|
+
* рядки не перезаписуються.
|
|
21
25
|
*
|
|
22
26
|
* Опт-аут — `claude-config: false` у `.n-cursor.json`.
|
|
23
27
|
*/
|
|
@@ -51,6 +55,10 @@ const CURSOR_HOOKS_FILE = `${CURSOR_DIR}/hooks.json`
|
|
|
51
55
|
const ADR_HOOK_SCRIPT_NAME = 'capture-decisions.sh'
|
|
52
56
|
const ADR_NORMALIZE_HOOK_SCRIPT_NAME = 'normalize-decisions.sh'
|
|
53
57
|
const TEMPLATE_DIR_NAME = '.claude-template'
|
|
58
|
+
/** Відносний шлях до канонічного фрагмента `.gitignore` для ADR Stop-hook'ів у tarball пакета. */
|
|
59
|
+
export const ADR_GITIGNORE_SNIPPET_REL = 'rules/adr/fix/hooks/template/.gitignore.snippet'
|
|
60
|
+
const GITIGNORE_FILE = '.gitignore'
|
|
61
|
+
const EOL_RE = /\r?\n/u
|
|
54
62
|
|
|
55
63
|
/** Канонічна група hooks для ADR capture Stop-hook'а — додається в settings, коли `adr` у `rules`. */
|
|
56
64
|
const ADR_STOP_HOOK_GROUP = Object.freeze({
|
|
@@ -387,6 +395,60 @@ export function syncAdrNormalizeHookScript(projectRoot, templateDir) {
|
|
|
387
395
|
return syncHookScript(projectRoot, templateDir, ADR_NORMALIZE_HOOK_SCRIPT_NAME)
|
|
388
396
|
}
|
|
389
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Повертає змістовні (не коментар, не порожній) рядки з text-фрагмента `.gitignore`.
|
|
400
|
+
* @param {string} raw вміст snippet-файлу
|
|
401
|
+
* @returns {string[]} нормалізовані рядки патернів
|
|
402
|
+
*/
|
|
403
|
+
function parseGitignoreFragmentLines(raw) {
|
|
404
|
+
return raw
|
|
405
|
+
.split(EOL_RE)
|
|
406
|
+
.map(l => l.trim())
|
|
407
|
+
.filter(l => l !== '' && !l.startsWith('#'))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Дописує в кореневий `.gitignore` проєкту відсутні рядки з канонічного ADR-фрагмента.
|
|
412
|
+
* @param {string} projectRoot корінь проєкту-споживача
|
|
413
|
+
* @param {string} bundledPackageRoot корінь установленого `@nitra/cursor`
|
|
414
|
+
* @returns {Promise<{ written: boolean, path: string }>} чи змінено файл і відносний шлях
|
|
415
|
+
*/
|
|
416
|
+
export async function syncGitignoreAdrFragment(projectRoot, bundledPackageRoot) {
|
|
417
|
+
const snippetPath = join(bundledPackageRoot, ADR_GITIGNORE_SNIPPET_REL)
|
|
418
|
+
if (!existsSync(snippetPath)) {
|
|
419
|
+
return { written: false, path: '' }
|
|
420
|
+
}
|
|
421
|
+
const fragment = await readFile(snippetPath, 'utf8')
|
|
422
|
+
const required = parseGitignoreFragmentLines(fragment)
|
|
423
|
+
if (required.length === 0) {
|
|
424
|
+
return { written: false, path: '' }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const destPath = join(projectRoot, GITIGNORE_FILE)
|
|
428
|
+
const existing = existsSync(destPath) ? await readFile(destPath, 'utf8') : ''
|
|
429
|
+
const existingLines = new Set(
|
|
430
|
+
existing
|
|
431
|
+
.split(EOL_RE)
|
|
432
|
+
.map(l => l.trim())
|
|
433
|
+
.filter(l => l !== '' && !l.startsWith('#'))
|
|
434
|
+
)
|
|
435
|
+
const missing = required.filter(l => !existingLines.has(l))
|
|
436
|
+
if (missing.length === 0) {
|
|
437
|
+
return { written: false, path: GITIGNORE_FILE }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const sectionHeader = '# @nitra/cursor (adr) — локальні артефакти Stop-hook, не коміти'
|
|
441
|
+
const hasHeader = existing.split(EOL_RE).some(l => l.trim() === sectionHeader)
|
|
442
|
+
const block = hasHeader ? missing.join('\n') : [sectionHeader, ...missing].join('\n')
|
|
443
|
+
let prefix = ''
|
|
444
|
+
if (existing.length > 0) {
|
|
445
|
+
prefix = existing.endsWith('\n') ? existing : `${existing}\n`
|
|
446
|
+
}
|
|
447
|
+
const next = `${prefix}${block}\n`
|
|
448
|
+
await writeFile(destPath, next, 'utf8')
|
|
449
|
+
return { written: true, path: GITIGNORE_FILE }
|
|
450
|
+
}
|
|
451
|
+
|
|
390
452
|
/**
|
|
391
453
|
* Копіює всі slash-команди з `templateDir/commands/` у `.claude/commands/`.
|
|
392
454
|
* Команди ідентифікуються тим, що вони лежать у темплейті — не перетинаються
|
|
@@ -422,7 +484,7 @@ export async function syncClaudeCommands(projectRoot, templateDir) {
|
|
|
422
484
|
* @param {string} options.bundledPackageRoot корінь установленого `@nitra/cursor`
|
|
423
485
|
* @param {boolean} options.enabled чи увімкнено sync (з `.n-cursor.json` `claude-config`)
|
|
424
486
|
* @param {string[]} [options.rules] список увімкнених правил із `.n-cursor.json` — впливає на ADR Stop-hook (`adr`)
|
|
425
|
-
* @returns {Promise<{ settings: boolean, cursorHooks: boolean, commands: string[], adrHook: boolean, adrNormalizeHook: boolean }>} прапорці записів settings/Cursor hooks/ADR-hook(s) та список
|
|
487
|
+
* @returns {Promise<{ settings: boolean, cursorHooks: boolean, commands: string[], adrHook: boolean, adrNormalizeHook: boolean, gitignoreAdr: boolean }>} прапорці записів settings/Cursor hooks/ADR-hook(s)/`.gitignore` та список slash-команд
|
|
426
488
|
*/
|
|
427
489
|
export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enabled, rules = [] }) {
|
|
428
490
|
if (!enabled) {
|
|
@@ -431,7 +493,8 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
|
|
|
431
493
|
cursorHooks: false,
|
|
432
494
|
commands: [],
|
|
433
495
|
adrHook: false,
|
|
434
|
-
adrNormalizeHook: false
|
|
496
|
+
adrNormalizeHook: false,
|
|
497
|
+
gitignoreAdr: false
|
|
435
498
|
}
|
|
436
499
|
}
|
|
437
500
|
const templateDir = join(bundledPackageRoot, TEMPLATE_DIR_NAME)
|
|
@@ -441,7 +504,8 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
|
|
|
441
504
|
cursorHooks: false,
|
|
442
505
|
commands: [],
|
|
443
506
|
adrHook: false,
|
|
444
|
-
adrNormalizeHook: false
|
|
507
|
+
adrNormalizeHook: false,
|
|
508
|
+
gitignoreAdr: false
|
|
445
509
|
}
|
|
446
510
|
}
|
|
447
511
|
const includeAdrHook = Array.isArray(rules) && rules.includes('adr')
|
|
@@ -449,6 +513,9 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
|
|
|
449
513
|
const adrNormalizeHook = includeAdrHook
|
|
450
514
|
? await syncAdrNormalizeHookScript(projectRoot, templateDir)
|
|
451
515
|
: { written: false, path: '' }
|
|
516
|
+
const gitignoreAdr = includeAdrHook
|
|
517
|
+
? await syncGitignoreAdrFragment(projectRoot, bundledPackageRoot)
|
|
518
|
+
: { written: false, path: '' }
|
|
452
519
|
const settings = await syncClaudeSettings(projectRoot, templateDir, { includeAdrHook })
|
|
453
520
|
const cursorHooks = await syncCursorHooksConfig(projectRoot, { includeAdrHook })
|
|
454
521
|
const commands = await syncClaudeCommands(projectRoot, templateDir)
|
|
@@ -457,6 +524,7 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
|
|
|
457
524
|
cursorHooks: cursorHooks.written,
|
|
458
525
|
commands,
|
|
459
526
|
adrHook: adrHook.written,
|
|
460
|
-
adrNormalizeHook: adrNormalizeHook.written
|
|
527
|
+
adrNormalizeHook: adrNormalizeHook.written,
|
|
528
|
+
gitignoreAdr: gitignoreAdr.written
|
|
461
529
|
}
|
|
462
530
|
}
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -44,13 +44,13 @@ bun run lint
|
|
|
44
44
|
|
|
45
45
|
**Конфіги і коментарі, які потребують зупинки** (неповний список — будь-який аналог):
|
|
46
46
|
|
|
47
|
-
| Інструмент
|
|
48
|
-
|
|
|
49
|
-
| **jscpd**
|
|
50
|
-
| **cspell**
|
|
51
|
-
| **knip**
|
|
47
|
+
| Інструмент | Типові файли / зміни |
|
|
48
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| **jscpd** | `.jscpd.json` → `ignore`, `minLines` |
|
|
50
|
+
| **cspell** | `.cspell.json` → `words`, `ignorePaths`; `.cspellignore` |
|
|
51
|
+
| **knip** | `knip.json` → `ignore`, `ignoreDependencies`, `ignoreBinaries`, `entry` |
|
|
52
52
|
| **oxlint / ESLint** | `.oxlintrc.json` → `ignorePatterns`; `eslint.config.js` → `ignores`; `eslint-disable` / `oxlint-disable` у коді |
|
|
53
|
-
| **інше**
|
|
53
|
+
| **інше** | `.v8rignore`, `.stylelintignore`, `.trufflehog-exclude`, розширення `ignores` у workflow-конфігах |
|
|
54
54
|
|
|
55
55
|
Політика узгоджена з **`.cursor/rules/`** (зокрема **n-js-lint**, **n-text**): виняток допустимий лише з **обґрунтованою** причиною, не як заміна рефакторингу для справжніх клонів / дублікатів.
|
|
56
56
|
|
|
@@ -64,12 +64,12 @@ bun run lint
|
|
|
64
64
|
|
|
65
65
|
**Варіанти відповіді** (мінімум такі; `allow_multiple: false`):
|
|
66
66
|
|
|
67
|
-
| id
|
|
68
|
-
|
|
|
69
|
-
| `refactor`
|
|
70
|
-
| `ignore-once` | **Точковий виняток у конфігу** — додати ignore/words/minLines з обґрунтуванням у коментарі PR/відповіді | Після вибору — мінімальна зміна конфігу + 1 речення **чому** це не рефакторинг
|
|
71
|
-
| `skip`
|
|
72
|
-
| `explain`
|
|
67
|
+
| id | label (українською) | Дія агента |
|
|
68
|
+
| ------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
69
|
+
| `refactor` | **Рефакторинг коду** — усунути дублікат / помилку в коді (рекомендовано за замовчуванням) | Рефакторинг; конфіг **не** чіпати |
|
|
70
|
+
| `ignore-once` | **Точковий виняток у конфігу** — додати ignore/words/minLines з обґрунтуванням у коментарі PR/відповіді | Після вибору — мінімальна зміна конфігу + 1 речення **чому** це не рефакторинг |
|
|
71
|
+
| `skip` | **Залишити як є** — не чіпати ні код, ні конфіг зараз | Не змінювати; у фінальному резюме — що лишилось червоним |
|
|
72
|
+
| `explain` | **Потрібні деталі** — поясни варіанти глибше | Розгорнути порівняння refactor vs ignore; **знову** запитати той самий набір варіантів |
|
|
73
73
|
|
|
74
74
|
Якщо користувач обрав **`ignore-once`** — у відповіді після зміни зафіксуй: який ключ конфігу змінено, який glob/слово додано, чому рефакторинг був недоречний (генерований код, формальний шаблон, легітимний термін без перекладу тощо).
|
|
75
75
|
|