@nitra/cursor 1.13.61 → 1.13.63
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 +19 -0
- package/package.json +1 -1
- package/rules/changelog/changelog.mdc +26 -14
- package/rules/changelog/fix/consistency/check.mjs +112 -64
- package/skills/lint/SKILL.md +45 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
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.63] - 2026-05-20
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`check changelog`**: новий local-only воркспейс (маніфест відсутній на merge-base з `dev`/`main`, напр. `demo/` на `main`) більше не вимагає штучного bump — достатньо початкової `version` і запису в `CHANGELOG.md` (раніше `Vbase === ∅` помилково трактувалось як «version не підвищено»).
|
|
12
|
+
- **`check changelog`**: на гілці **`main`** база порівняння — **`origin/main`** (або `HEAD~1` без remote), не `dev`; коли `origin/main` збігається з `HEAD`, diff порожній (не fallback на `HEAD~1`); feature-гілки — `merge-base` з `dev`, інакше з `main` (репо без `dev`).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Правило **`changelog`** ([changelog.mdc](rules/changelog/changelog.mdc) `2.5`): блок **STOP** перенесено на початок (тригер шляхів, інверсія, три кроки до завершення відповіді) — щоб агент не пропускав bump після правок у `npm/skills/` тощо, коли чеклист губився внизу довгого alwaysApply-правила.
|
|
17
|
+
- **`.cursor/rules/scripts.mdc`**: секція «Завершення задачі після правок у пакетному workspace» — cross-STOP з **n-changelog** (останні кроки сесії перед відповіддю).
|
|
18
|
+
- **`hk.pkl`**: pre-commit крок **`npm-changelog`** (`glob: npm/**`, `bun ./npm/bin/n-cursor.js check changelog`) — програмний стоп-кран при commit, якщо агент забув bump.
|
|
19
|
+
|
|
20
|
+
## [1.13.62] - 2026-05-20
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Скіл **`n-lint`** ([SKILL.md](skills/lint/SKILL.md)): перед правкою конфігів з винятками (`.jscpd.json` → `ignore`/`minLines`, `.cspell.json` → `words`/`ignorePaths`, `knip.json`, eslint/oxlint ignores, `eslint-disable` тощо) агент **зупиняється** і питає користувача через **`AskQuestion`** — рефакторинг (за замовчуванням), точковий виняток у конфігу (`ignore-once`), пропуск (`skip`) або детальніше пояснення (`explain`). Заборонено мовчки розширювати ignore/words лише щоб зеленіти лінт; без відповіді користувача — рефакторинг або червоний лінт з поясненням, без змін конфігу.
|
|
25
|
+
|
|
7
26
|
## [1.13.61] - 2026-05-20
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: CHANGELOG.md в кожному workspace, з двома моделями бази порівняння (npm і Python)
|
|
3
|
-
version: '2.
|
|
3
|
+
version: '2.5'
|
|
4
4
|
alwaysApply: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## STOP — перед завершенням відповіді агента
|
|
8
|
+
|
|
9
|
+
> **Якщо в цій сесії ти змінив(ла) файли в пакетному workspace** (код, rego, правила, скіли, скрипти, конфіги, тести — **не** лише `docs/` / `doc/`) — **не завершуй задачу**, поки не виконаєш **усі три** кроки нижче в **тому ж** наборі змін. Це не «опційно після синку» — це частина PR.
|
|
10
|
+
|
|
11
|
+
1. **`version`** у `<ws>/package.json` (або `[project].version` у `pyproject.toml`) → **patch +1** відносно `git show HEAD:<ws>/package.json`, якщо ще не піднято.
|
|
12
|
+
2. **`CHANGELOG.md`** того workspace → **нова** секція `## [версія] - YYYY-MM-DD` **зверху** (не bullet-и в стару версію).
|
|
13
|
+
3. **`npx @nitra/cursor check changelog`** (у репо `@nitra/cursor`: `bun ./npm/bin/n-cursor.js check changelog`) → exit **`0`**.
|
|
14
|
+
|
|
15
|
+
**Тригер шляхів (приклади):** `npm/**`, `packages/foo/**`, будь-який каталог з власним `package.json` / `pyproject.toml`, куди потрапили правки.
|
|
16
|
+
|
|
17
|
+
**Інверсія (bump не потрібен):** лише `docs/` / `doc/`; лише `.gitignore`; лише сам релізний крок (`CHANGELOG.md` + `version`).
|
|
18
|
+
|
|
19
|
+
**Pre-commit (людина):** `hk` у цьому репо також запускає `check changelog` при змінах під `npm/**` — агент не покладайся лише на commit hook; виконай кроки 1–3 **до** фінальної відповіді.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
7
23
|
У кожному **пакетному** workspace (каталог із `package.json` або `pyproject.toml`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** — кожен пакет веде свій.
|
|
8
24
|
|
|
9
25
|
**Маніфест версії:**
|
|
@@ -13,7 +29,9 @@ alwaysApply: true
|
|
|
13
29
|
|
|
14
30
|
Каталоги лише з `pyproject.toml` (без `package.json`) теж враховуються; `node_modules/`, `.venv/`, `venv/` при пошуку ігноруються.
|
|
15
31
|
|
|
16
|
-
## Чеклист агента (
|
|
32
|
+
## Чеклист агента (деталі)
|
|
33
|
+
|
|
34
|
+
Повний алгоритм — у блоці **STOP** вище; тут лише уточнення.
|
|
17
35
|
|
|
18
36
|
**Інверсія (за замовчуванням не вимагають bump/CHANGELOG):**
|
|
19
37
|
|
|
@@ -21,13 +39,7 @@ alwaysApply: true
|
|
|
21
39
|
- файли під **`.gitignore`**;
|
|
22
40
|
- правки **лише** `CHANGELOG.md` або поля `version` у маніфесті як сам релізний крок.
|
|
23
41
|
|
|
24
|
-
**Вимагають bump + нову секцію CHANGELOG** — усі інші зміни в каталозі workspace (код, rego, правила, конфіги, тести тощо).
|
|
25
|
-
|
|
26
|
-
Перед завершенням задачі:
|
|
27
|
-
|
|
28
|
-
1. **`version`** у `<ws>/package.json` або `[project].version` у `<ws>/pyproject.toml` підвищено (build/patch +1 на PR), якщо зміни входять у реліз.
|
|
29
|
-
2. **`CHANGELOG.md`** — **нова** секція `## [версія] - YYYY-MM-DD` зверху (не дописуй bullet-и в стару версію).
|
|
30
|
-
3. **`npx @nitra/cursor check changelog`** — exit `0`.
|
|
42
|
+
**Вимагають bump + нову секцію CHANGELOG** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо).
|
|
31
43
|
|
|
32
44
|
Перевірка програмна (`changelog/fix/consistency/check.mjs`).
|
|
33
45
|
|
|
@@ -48,14 +60,14 @@ alwaysApply: true
|
|
|
48
60
|
|
|
49
61
|
### local-only
|
|
50
62
|
|
|
51
|
-
**npm:** `private: true` або без `files`. **Python:** без пари name+version для реєстру. База
|
|
63
|
+
**npm:** `private: true` або без `files`. **Python:** без пари name+version для реєстру. База залежить від гілки:
|
|
52
64
|
|
|
53
65
|
1. На **`dev`** local-only не активний (крім незакомічених registry-published).
|
|
54
|
-
2. На
|
|
55
|
-
3.
|
|
56
|
-
4.
|
|
66
|
+
2. На **`main`** — diff від **`origin/main`** (попередній опублікований `main`); без remote — від `HEAD~1`. **`dev` не використовується** як база на `main`.
|
|
67
|
+
3. На **feature-гілці** — `merge-base` з **`dev`**, якщо є; інакше з **`main`** (репо без `dev`).
|
|
68
|
+
4. Bump + CHANGELOG **раз на PR** / direct-commit на `main`.
|
|
57
69
|
|
|
58
|
-
Якщо немає git або немає `dev`/`main` — local-only пропускається.
|
|
70
|
+
Якщо немає git або немає `dev`/`main`/`origin/main` — local-only пропускається.
|
|
59
71
|
|
|
60
72
|
## Формат CHANGELOG.md
|
|
61
73
|
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* Якщо локальна версія відрізняється — потрібен CHANGELOG; для npm також `"CHANGELOG.md"`
|
|
11
11
|
* у `files`. Якщо версії збігаються, але в git є релевантні зміни без bump — fail.
|
|
12
12
|
*
|
|
13
|
-
* 2) **local-only** (приватні npm, без `files`, Python без імені/версії для реєстру):
|
|
14
|
-
*
|
|
13
|
+
* 2) **local-only** (приватні npm, без `files`, Python без імені/версії для реєстру):
|
|
14
|
+
* feature-гілка — `merge-base` з `dev`, інакше з `main`; на `main` — diff від
|
|
15
|
+
* `origin/main` (попередній опублікований main) або `HEAD~1` без remote.
|
|
15
16
|
*
|
|
16
17
|
* Усі `git` і зовнішні виклики — через `execFile` / `fetch`, без shell-інтерполяції.
|
|
17
18
|
*/
|
|
@@ -31,11 +32,11 @@ import {
|
|
|
31
32
|
|
|
32
33
|
const execFileAsync = promisify(execFile)
|
|
33
34
|
|
|
34
|
-
/** Кандидати інтеграційної гілки (перша
|
|
35
|
-
const
|
|
35
|
+
/** Кандидати інтеграційної гілки для feature-гілок (перша наявна; див. n-changelog.mdc). */
|
|
36
|
+
const FEATURE_BASE_BRANCH_CANDIDATES = Object.freeze(['dev', 'main'])
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
const
|
|
38
|
+
/** Гілка `dev`: local-only не активний (крім незакомічених registry-published). */
|
|
39
|
+
const LOCAL_ONLY_SKIP_BRANCH = 'dev'
|
|
39
40
|
|
|
40
41
|
/** Префікси шляхів (posix), які не вважаються релізними змінами — інверсія glob (n-changelog.mdc). */
|
|
41
42
|
const CHANGELOG_IGNORE_PATH_PREFIXES = Object.freeze(['docs/', 'doc/'])
|
|
@@ -78,14 +79,6 @@ async function currentBranchName() {
|
|
|
78
79
|
return typeof out === 'string' ? out.trim() : null
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
/**
|
|
82
|
-
* @param {string | null} branch параметр
|
|
83
|
-
* @returns {boolean} результат
|
|
84
|
-
*/
|
|
85
|
-
function isIntegrationBranch(branch) {
|
|
86
|
-
return branch !== null && INTEGRATION_BRANCHES.includes(branch)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
82
|
/**
|
|
90
83
|
* @param {string} ref параметр
|
|
91
84
|
* @returns {string} результат
|
|
@@ -94,6 +87,30 @@ function baseRefLabel(ref) {
|
|
|
94
87
|
return ref.startsWith('origin/') ? ref.slice('origin/'.length) : ref
|
|
95
88
|
}
|
|
96
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} ancestor предок
|
|
92
|
+
* @param {string} descendant нащадок
|
|
93
|
+
* @returns {Promise<boolean>} результат
|
|
94
|
+
*/
|
|
95
|
+
async function isGitAncestor(ancestor, descendant) {
|
|
96
|
+
const out = await gitOrNull(['merge-base', '--is-ancestor', ancestor, descendant])
|
|
97
|
+
return typeof out === 'string' && out.trim() === 'true'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} branchName локальна або remote-tracking гілка
|
|
102
|
+
* @returns {Promise<string | null>} ref для git або null
|
|
103
|
+
*/
|
|
104
|
+
async function resolveBranchRef(branchName) {
|
|
105
|
+
for (const ref of [branchName, `origin/${branchName}`]) {
|
|
106
|
+
const out = await gitOrNull(['rev-parse', '--verify', '--quiet', ref])
|
|
107
|
+
if (typeof out === 'string' && out.trim().length > 0) {
|
|
108
|
+
return ref
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
/**
|
|
98
115
|
* @param {string} relPath параметр
|
|
99
116
|
* @returns {boolean} результат
|
|
@@ -119,21 +136,6 @@ async function isPathGitIgnored(relPath) {
|
|
|
119
136
|
}
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
/**
|
|
123
|
-
* @returns {Promise<string | null>} результат
|
|
124
|
-
*/
|
|
125
|
-
async function resolveBaseRef() {
|
|
126
|
-
for (const name of BASE_BRANCH_CANDIDATES) {
|
|
127
|
-
for (const ref of [name, `origin/${name}`]) {
|
|
128
|
-
const out = await gitOrNull(['rev-parse', '--verify', '--quiet', ref])
|
|
129
|
-
if (typeof out === 'string' && out.trim().length > 0) {
|
|
130
|
-
return ref
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return null
|
|
135
|
-
}
|
|
136
|
-
|
|
137
139
|
/**
|
|
138
140
|
* @param {string} baseRef параметр
|
|
139
141
|
* @returns {Promise<string | null>} результат
|
|
@@ -145,6 +147,47 @@ async function resolveMergeBase(baseRef) {
|
|
|
145
147
|
return sha.length > 0 ? sha : null
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Точка порівняння git для changelog (ref або SHA для `git diff` / `git show`).
|
|
152
|
+
* @param {string | null} branch поточна гілка
|
|
153
|
+
* @returns {Promise<{ ref: string, label: string } | null>} результат
|
|
154
|
+
*/
|
|
155
|
+
async function resolveChangelogComparisonPoint(branch) {
|
|
156
|
+
if (branch === LOCAL_ONLY_SKIP_BRANCH) {
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (branch === 'main') {
|
|
161
|
+
const originMainSha = (await gitOrNull(['rev-parse', '--verify', '--quiet', 'origin/main']))?.trim()
|
|
162
|
+
const headSha = (await gitOrNull(['rev-parse', 'HEAD']))?.trim()
|
|
163
|
+
if (
|
|
164
|
+
originMainSha &&
|
|
165
|
+
headSha &&
|
|
166
|
+
(originMainSha === headSha || (await isGitAncestor('origin/main', 'HEAD')))
|
|
167
|
+
) {
|
|
168
|
+
return { ref: 'origin/main', label: 'main' }
|
|
169
|
+
}
|
|
170
|
+
const parent = await gitOrNull(['rev-parse', '--verify', '--quiet', 'HEAD~1'])
|
|
171
|
+
if (typeof parent === 'string' && parent.trim().length > 0) {
|
|
172
|
+
return { ref: parent.trim(), label: 'main~1' }
|
|
173
|
+
}
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const name of FEATURE_BASE_BRANCH_CANDIDATES) {
|
|
178
|
+
const baseRef = await resolveBranchRef(name)
|
|
179
|
+
if (!baseRef) {
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
const mergeBase = await resolveMergeBase(baseRef)
|
|
183
|
+
if (!mergeBase) {
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
return { ref: mergeBase, label: baseRefLabel(baseRef) }
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
148
191
|
/**
|
|
149
192
|
* @param {string} ws параметр
|
|
150
193
|
* @param {string[]} subWorkspaces параметр
|
|
@@ -355,7 +398,8 @@ async function checkPublishedWorkspacePendingGitChanges(manifest, Vcurrent, subW
|
|
|
355
398
|
}
|
|
356
399
|
|
|
357
400
|
const branch = await currentBranchName()
|
|
358
|
-
|
|
401
|
+
|
|
402
|
+
if (branch === LOCAL_ONLY_SKIP_BRANCH) {
|
|
359
403
|
if (await workspaceHasRelevantChangesAgainstBase('HEAD', manifest.ws, subWorkspaces)) {
|
|
360
404
|
fail(
|
|
361
405
|
`${label}: у registry-published пакеті є незакомічені зміни при version ${Vcurrent}, що вже в реєстрі. ` +
|
|
@@ -365,28 +409,32 @@ async function checkPublishedWorkspacePendingGitChanges(manifest, Vcurrent, subW
|
|
|
365
409
|
return
|
|
366
410
|
}
|
|
367
411
|
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
412
|
+
const comparison = await resolveChangelogComparisonPoint(branch)
|
|
413
|
+
if (comparison && (await workspaceHasRelevantChangesAgainstBase(comparison.ref, manifest.ws, subWorkspaces))) {
|
|
414
|
+
const Vbase = await readBaseVersion(comparison.ref, manifest)
|
|
415
|
+
const baseLabel = comparison.label
|
|
416
|
+
if (Vbase === null) {
|
|
417
|
+
pass(
|
|
418
|
+
`${label}: новий registry-published воркспейс (на ${baseLabel} відсутній ${mf}) — перевіряємо CHANGELOG для ${Vcurrent}`
|
|
419
|
+
)
|
|
420
|
+
await verifyChangelogEntry(manifest.ws, Vcurrent, pass, fail)
|
|
421
|
+
checkNpmFilesArrayContainsChangelog(manifest, pass, fail)
|
|
422
|
+
} else if (Vbase === Vcurrent) {
|
|
423
|
+
fail(
|
|
424
|
+
`${label}: у цій гілці є зміни в registry-published пакеті, але version у ${mf} ` +
|
|
425
|
+
`не підвищено (на ${baseLabel} — ${Vbase}). Bump + запис у CHANGELOG.md обов'язкові (n-changelog.mdc)`
|
|
426
|
+
)
|
|
427
|
+
} else {
|
|
428
|
+
pass(`${label}: version змінено (${Vbase} → ${Vcurrent}) — очікується запис CHANGELOG після bump`)
|
|
429
|
+
}
|
|
378
430
|
}
|
|
379
431
|
|
|
380
|
-
|
|
381
|
-
const baseLabel = baseRefLabel(baseRef)
|
|
382
|
-
if (Vbase === null || Vbase === Vcurrent) {
|
|
432
|
+
if (branch === 'main' && (await workspaceHasRelevantChangesAgainstBase('HEAD', manifest.ws, subWorkspaces))) {
|
|
383
433
|
fail(
|
|
384
|
-
`${label}: у
|
|
385
|
-
|
|
434
|
+
`${label}: у registry-published пакеті є незакомічені зміни при version ${Vcurrent}, що вже в реєстрі. ` +
|
|
435
|
+
`Підвищ version у ${mf} і додай запис у CHANGELOG.md (n-changelog.mdc)`
|
|
386
436
|
)
|
|
387
|
-
return
|
|
388
437
|
}
|
|
389
|
-
pass(`${label}: version змінено (${Vbase} → ${Vcurrent}) — очікується запис CHANGELOG після bump`)
|
|
390
438
|
}
|
|
391
439
|
|
|
392
440
|
/**
|
|
@@ -426,13 +474,13 @@ async function checkPublishedWorkspace(manifest, subWorkspaces, getPublishedVers
|
|
|
426
474
|
}
|
|
427
475
|
|
|
428
476
|
/**
|
|
429
|
-
* @param {string}
|
|
477
|
+
* @param {string} comparisonRef ref/SHA для `git diff` / `git show`
|
|
430
478
|
* @param {import('../../../../scripts/utils/package-manifest.mjs').PackageManifest} manifest параметр
|
|
431
479
|
* @param {string} baseLabel параметр
|
|
432
480
|
* @param {(msg: string) => void} pass параметр
|
|
433
481
|
* @param {(msg: string) => void} fail параметр
|
|
434
482
|
*/
|
|
435
|
-
async function checkLocalOnlyChangedWorkspace(
|
|
483
|
+
async function checkLocalOnlyChangedWorkspace(comparisonRef, manifest, baseLabel, pass, fail) {
|
|
436
484
|
const label = workspaceLabel(manifest)
|
|
437
485
|
const mf = manifestFilePath(manifest.ws, manifest)
|
|
438
486
|
const Vcurrent = manifest.version
|
|
@@ -440,10 +488,16 @@ async function checkLocalOnlyChangedWorkspace(mergeBase, manifest, baseLabel, pa
|
|
|
440
488
|
fail(`${label}: у ${mf} відсутнє поле version (потрібне для запису в CHANGELOG)`)
|
|
441
489
|
return
|
|
442
490
|
}
|
|
443
|
-
const Vbase = await readBaseVersion(
|
|
444
|
-
if (Vbase === null
|
|
491
|
+
const Vbase = await readBaseVersion(comparisonRef, manifest)
|
|
492
|
+
if (Vbase === null) {
|
|
493
|
+
pass(`${label}: новий воркспейс (на ${baseLabel} відсутній ${mf}) — перевіряємо CHANGELOG для ${Vcurrent}`)
|
|
494
|
+
if (!(await verifyChangelogEntry(manifest.ws, Vcurrent, pass, fail))) return
|
|
495
|
+
checkNpmFilesArrayContainsChangelog(manifest, pass, fail)
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
if (Vbase === Vcurrent) {
|
|
445
499
|
fail(
|
|
446
|
-
`${label}: у цій гілці є зміни, але version у ${mf} не підвищено (на ${baseLabel} — ${Vbase
|
|
500
|
+
`${label}: у цій гілці є зміни, але version у ${mf} не підвищено (на ${baseLabel} — ${Vbase}). Bump + запис у CHANGELOG.md обов'язкові на PR`
|
|
447
501
|
)
|
|
448
502
|
return
|
|
449
503
|
}
|
|
@@ -466,30 +520,24 @@ async function runLocalOnlyChecks(localOnly, subWorkspaces, pass, fail) {
|
|
|
466
520
|
return
|
|
467
521
|
}
|
|
468
522
|
const branch = await currentBranchName()
|
|
469
|
-
if (branch ===
|
|
523
|
+
if (branch === LOCAL_ONLY_SKIP_BRANCH) {
|
|
470
524
|
pass('changelog: поточна гілка = dev — local-only перевірку пропущено')
|
|
471
525
|
return
|
|
472
526
|
}
|
|
473
|
-
const
|
|
474
|
-
if (!
|
|
527
|
+
const comparison = await resolveChangelogComparisonPoint(branch)
|
|
528
|
+
if (!comparison) {
|
|
475
529
|
pass('changelog: ref dev/main (та origin/*) не знайдено — local-only перевірку пропущено')
|
|
476
530
|
return
|
|
477
531
|
}
|
|
478
|
-
const mergeBase = await resolveMergeBase(baseRef)
|
|
479
|
-
if (!mergeBase) {
|
|
480
|
-
pass(`changelog: merge-base з ${baseRef} не знайдено — local-only перевірку пропущено`)
|
|
481
|
-
return
|
|
482
|
-
}
|
|
483
532
|
|
|
484
|
-
const baseLabel = baseRefLabel(baseRef)
|
|
485
533
|
let checkedAny = false
|
|
486
534
|
for (const manifest of localOnly) {
|
|
487
|
-
if (!(await workspaceHasRelevantChangesAgainstBase(
|
|
535
|
+
if (!(await workspaceHasRelevantChangesAgainstBase(comparison.ref, manifest.ws, subWorkspaces))) continue
|
|
488
536
|
checkedAny = true
|
|
489
|
-
await checkLocalOnlyChangedWorkspace(
|
|
537
|
+
await checkLocalOnlyChangedWorkspace(comparison.ref, manifest, comparison.label, pass, fail)
|
|
490
538
|
}
|
|
491
539
|
if (!checkedAny) {
|
|
492
|
-
pass(`changelog: local-only воркспейси без змін відносно
|
|
540
|
+
pass(`changelog: local-only воркспейси без змін відносно ${comparison.label}`)
|
|
493
541
|
}
|
|
494
542
|
}
|
|
495
543
|
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ bun run lint
|
|
|
25
25
|
|
|
26
26
|
2. **Якщо exit code не 0** — проаналізуй вивід (останній упавший крок у ланцюжку **`lint`** часто видно з stderr / логів):
|
|
27
27
|
- Де скрипт уже робить **auto-fix** (**`--fix`**, **`markdownlint-cli2 --fix`**, **`oxfmt`** тощо) — перезапусти **`bun run lint`** після змін файлів.
|
|
28
|
-
- Де auto-fix **немає** (наприклад, **jscpd**, **cspell**, **zizmor**, перевірки без прапорця fix) —
|
|
28
|
+
- Де auto-fix **немає** (наприклад, **jscpd**, **cspell**, **zizmor**, перевірки без прапорця fix) — **за замовчуванням рефактори код проєкту**, щоб усунути порушення: перейменуй ідентифікатори, перепиши логіку, видали дублікати тощо. **Не** розширюй конфіги з винятками «мовчки» — див. блок **«Винятки в конфігурації»** нижче.
|
|
29
29
|
- Якщо спрацьовує **`sonarjs/cognitive-complexity`** — див. окремий блок нижче.
|
|
30
30
|
|
|
31
31
|
3. **Цикл** — повторюй кроки 1–2, доки **`bun run lint`** не завершиться успішно. Після суттєвих правок за потреби ще раз **`bun run lint`**, щоб переконатися, що не зламав наступний крок у скрипті **`lint`**.
|
|
@@ -38,6 +38,50 @@ bun run lint
|
|
|
38
38
|
|
|
39
39
|
5. **Результат** — коротко опиши, що саме виправлено; якщо щось блокує нульовий exit code — залиш чітке пояснення й наступні кроки для людини.
|
|
40
40
|
|
|
41
|
+
## Винятки в конфігурації — інтерактивне рішення
|
|
42
|
+
|
|
43
|
+
Коли порушення **не** зникає auto-fix і перша думка — «додати в ignore / words / minLines» — **STOP**. **Заборонено** мовчки редагувати конфіги лише щоб зеленіти лінт без згоди користувача.
|
|
44
|
+
|
|
45
|
+
**Конфіги і коментарі, які потребують зупинки** (неповний список — будь-який аналог):
|
|
46
|
+
|
|
47
|
+
| Інструмент | Типові файли / зміни |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| **jscpd** | `.jscpd.json` → `ignore`, `minLines` |
|
|
50
|
+
| **cspell** | `.cspell.json` → `words`, `ignorePaths`; `.cspellignore` |
|
|
51
|
+
| **knip** | `knip.json` → `ignore`, `ignoreDependencies`, `ignoreBinaries`, `entry` |
|
|
52
|
+
| **oxlint / ESLint** | `.oxlintrc.json` → `ignorePatterns`; `eslint.config.js` → `ignores`; `eslint-disable` / `oxlint-disable` у коді |
|
|
53
|
+
| **інше** | `.v8rignore`, `.stylelintignore`, `.trufflehog-exclude`, розширення `ignores` у workflow-конфігах |
|
|
54
|
+
|
|
55
|
+
Політика узгоджена з **`.cursor/rules/`** (зокрема **n-js-lint**, **n-text**): виняток допустимий лише з **обґрунтованою** причиною, не як заміна рефакторингу для справжніх клонів / дублікатів.
|
|
56
|
+
|
|
57
|
+
### Коли обовʼязково питати користувача
|
|
58
|
+
|
|
59
|
+
Перед **будь-якою** правкою рядків із таблиці вище (або коментарем-винятком у коді) для **конкретного** порушення з поточного виводу лінту:
|
|
60
|
+
|
|
61
|
+
1. **Зупини** автоматичні правки конфігів.
|
|
62
|
+
2. **Один** виклик **`AskQuestion`** (або еквівалентне повідомлення з варіантами, якщо інструмент недоступний) — **одне питання на одне порушення** (або на одну логічну групу однакових jscpd-клонів у тому ж файлі).
|
|
63
|
+
3. У тексті питання коротко дай **контекст**: інструмент, файл:рядок, суть порушення (1–2 речення), **що саме** пропонується додати в конфіг (точний glob / слово / ключ).
|
|
64
|
+
|
|
65
|
+
**Варіанти відповіді** (мінімум такі; `allow_multiple: false`):
|
|
66
|
+
|
|
67
|
+
| id | label (українською) | Дія агента |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `refactor` | **Рефакторинг коду** — усунути дублікат / помилку в коді (рекомендовано за замовчуванням) | Рефакторинг; конфіг **не** чіпати |
|
|
70
|
+
| `ignore-once` | **Точковий виняток у конфігу** — додати ignore/words/minLines з обґрунтуванням у коментарі PR/відповіді | Після вибору — мінімальна зміна конфігу + 1 речення **чому** це не рефакторинг |
|
|
71
|
+
| `skip` | **Залишити як є** — не чіпати ні код, ні конфіг зараз | Не змінювати; у фінальному резюме — що лишилось червоним |
|
|
72
|
+
| `explain` | **Потрібні деталі** — поясни варіанти глибше | Розгорнути порівняння refactor vs ignore; **знову** запитати той самий набір варіантів |
|
|
73
|
+
|
|
74
|
+
Якщо користувач обрав **`ignore-once`** — у відповіді після зміни зафіксуй: який ключ конфігу змінено, який glob/слово додано, чому рефакторинг був недоречний (генерований код, формальний шаблон, легітимний термін без перекладу тощо).
|
|
75
|
+
|
|
76
|
+
Якщо користувач **не** відповів (сесія без інтерактиву) — **не** додавай винятки в конфіг; роби **рефакторинг** або залиш порушення з поясненням у кроці 5 workflow.
|
|
77
|
+
|
|
78
|
+
### Приклад формулювання (jscpd)
|
|
79
|
+
|
|
80
|
+
> **jscpd:** клон 42 рядки в `src/foo.ts` ↔ `src/bar.ts` (однакова логіка валідації).
|
|
81
|
+
> Варіанти: (A) винести спільну функцію; (B) додати `src/bar.ts` у `.jscpd.json` → `ignore`; (C) пропустити зараз.
|
|
82
|
+
|
|
83
|
+
Не виконуй (B), поки користувач явно не обрав **`ignore-once`**.
|
|
84
|
+
|
|
41
85
|
## sonarjs/cognitive-complexity
|
|
42
86
|
|
|
43
87
|
- **Не** додавай **`eslint-disable`** (у т.ч. на **`sonarjs/cognitive-complexity`**) чи інші коментарі-винятки лише щоб приховати порушення — потрібен саме **рефакторинг коду**, щоб зменшити **cognitive complexity**.
|