@nitra/cursor 1.25.0 → 1.25.2

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,86 +4,22 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
- ## [1.25.0] - 2026-05-26
7
+ ## [1.25.2] - 2026-05-26
8
8
 
9
9
  ### Added
10
10
 
11
- - **`.claude-template/hooks/lib/tooling-only.sh`** спільний bash-helper із функціями `is_tooling_only_change` і `git_diff_only_version_field`. Source'ається з `capture-decisions.sh` і `normalize-decisions.sh` через `. "$SCRIPT_DIR/lib/tooling-only.sh"` caller'и більше не дублюють ~27 рядків логіки розпізнавання "tooling-only" сесій. Bash 3.2 (macOS `/bin/bash`) сумісний.
12
- - **`syncAdrHookLibScripts(projectRoot, templateDir)`** у `npm/scripts/sync-claude-config.mjs` — копіює всі `*.sh` із `.claude-template/hooks/lib/` у `.claude/hooks/lib/` проєкту-споживача без exec-біта (source-only). Експортується разом із новою `removeOrphanAdrHookLib(projectRoot)` для cleanup-у при вимкненому `adr` правилі.
13
- - Поле `adrHookLib: string[]` у відповіді `syncClaudeConfig` — список синкнутих lib-файлів. `npm/bin/n-cursor.js` виводить їх у `🤖 Claude-конфіг` логу окремими записами.
11
+ - **`stryker.config.mjs` baseline**: `incremental: true` + `incrementalFile: 'reports/stryker/incremental.json'` Stryker зберігає результати між запусками і відновлює після краш/kill (SIGURG). Важливо для машин з обмеженою RAM де Stryker вбивається системою після ~100 мутантів.
14
12
 
15
- ### Changed
16
-
17
- - **`.claude-template/hooks/{capture,normalize}-decisions.sh`** — обидва скрипти `source`-ять спільний `lib/tooling-only.sh` замість локальних копій функцій. Заголовок `# shellcheck source=lib/tooling-only.sh` тримає shellcheck-аналіз чистим.
18
-
19
- ### Fixed
20
-
21
- - Усунено jscpd-дублікат `is_tooling_only_change`/`git_diff_only_version_field` між `capture-decisions.sh` і `normalize-decisions.sh` (~27 рядків / ~194 токени). Споживачам більше не потрібен ignore-запис для цих файлів у `.jscpd.json`.
22
-
23
- ## [1.24.0] - 2026-05-26
13
+ ## [1.25.1] - 2026-05-26
24
14
 
25
15
  ### Added
26
16
 
27
- - **`.pi-template/extensions/n-cursor-adr/tsconfig.json`** — мінімальний TS-конфіг шаблону pi.dev extension, який синкається у `.pi/extensions/n-cursor-adr/` разом із `index.ts`. Дозволяє IDE/TS-серверу резолвити `node:*` модулі без project-wide tsconfig у проєкті-споживачі (`types: ["node"]`, `module/target: ES2022 + NodeNext`, `noEmit`, `isolatedModules`). Споживачу потрібен `@types/node` у devDeps (зазвичай уже є транзитивно).
28
- - **`pi` manifest у `npm/package.json`** — `{"skills":"skills","extensions":".pi-template/extensions"}`. Pi.dev під час `pi install npm:@nitra/cursor` тепер бачить explicit-шляхи до bundled-ресурсів, замість convention-auto-discovery. `extensions: ".pi-template/extensions"` критичний — pi за замовч. шукає `extensions/` у корені пакета, а у нас шлях нестандартний.
29
- - **`"pi-package"` keyword** у `npm/package.json` — пакет з’являється у pi-gallery для discoverability.
17
+ - **`skills/coverage-fix/SKILL.md`** — автономна команда `/n-coverage-fix`: запускає `n-cursor coverage`, читає JSON-масив вижилих мутантів із секції `## Вижилі мутанти` у COVERAGE.md і ітеративно пише тести до конвергенції (max 3 ітерації). Включає заборону паралельного запуску (Stryker пише в одну директорію).
30
18
 
31
19
  ### Changed
32
20
 
33
- - **`syncPiExtensions`** тепер копіює **всі файли** з теки `.pi-template/extensions/<name>/`, а не лише `index.ts`. Контракт повернення: `{ written, path, files }` `path` тепер тека не файл), `files` відсортований список базових імен. У `🤖 Claude-конфіг` логу та у `result.piExtension` caller-стороні виводиться тека.
34
- - **`.pi-template/extensions/n-cursor-adr/index.ts`** порядок імпортів виправлено (всі `node:*` імпорти підняті на самий верх, перед `interface PiContext`). Усувало `import/first` ESLint-помилку; функціонально без змін.
35
-
36
- ## [1.23.0] - 2026-05-25
37
-
38
- ### Added
39
-
40
- - **Pi.dev ADR hooks** — bundled TS-extension `npm/.pi-template/extensions/n-cursor-adr/index.ts` копіюється у `.pi/extensions/n-cursor-adr/index.ts` проєкту-споживача коли `adr` ∈ `.n-cursor.json#rules`. На pi `agent_end` event серіалізує `ctx.sessionManager.getEntries()` у Claude-сумісний JSONL у `tmpdir()`, спавнить існуючі `.claude/hooks/{capture,normalize}-decisions.sh` через `pi.exec` (async, `signal: ctx.signal`, timeouts 180s/600s). Жодного дублювання bash-логіки: skip/throttle/LLM-CLI-selection лишається у bash. Recursion guard через env-vars `CAPTURE_DECISIONS_RUNNING` / `ADR_NORMALIZE_RUNNING`, які bash виставляє перед спавном LLM CLI.
41
- - `npm/scripts/sync-claude-config.mjs`: експорт `PI_DIR`, `PI_EXTENSIONS_DIR`, `PI_TEMPLATE_DIR_NAME`, `PI_EXTENSION_NAME`; нова функція `syncPiExtensions(projectRoot, bundledPackageRoot)` (copy) і `removeOrphanPiExtension(projectRoot)` (cleanup); поле `piExtension: boolean` у відповіді `syncClaudeConfig` (gated на `adr` ∈ rules).
42
- - `npm/package.json` `files` array: додано `.pi-template` — bundled-директорія шипиться разом із пакетом.
43
- - `npm/bin/n-cursor.js`: у `🤖 Claude-конфіг`-логу після sync додається `.pi/extensions/n-cursor-adr/index.ts` коли pi-extension згенерована.
44
-
45
- ## [1.22.0] - 2026-05-25
46
-
47
- ### Added
48
-
49
- - **`npx @nitra/cursor lint`** — оркестратор лінт-ланцюжка з тайменгом на кожен крок. Послідовно запускає присутні у root `package.json` скрипти з фіксованого списку (`lint-ga`, `lint-js`, `lint-rego`, `lint-style`, `lint-text`, `lint-security`, `oxfmt`), **fail-fast** на першому ненульовому exit-коді. Наприкінці друкує таблицю `⏱ Lint timing` з часом кожного кроку — для атрибуції повільних кроків замість анонімного `&&`-агрегатора.
50
- - **`runFixCommand` тепер друкує `⏱ Fix timing`** після прогону всіх `rules/<id>/fix.mjs` — per-rule час + сума. Маркер `❌` на впалих рядках.
51
- - `npm/scripts/lib/timing-summary.mjs` — чистий форматер `formatTimingSummary(title, entries)` (спільний для fix і lint). 9 тестів у `tests/timing-summary.test.mjs`.
52
- - `npm/scripts/lib/run-lint-cli.mjs` — `runLintCli({ cwd, spawnSyncFn, now, log, logError })` з DI для юніт-тестів. 7 тестів у `tests/run-lint-cli.test.mjs`.
53
-
54
- ### Changed
55
-
56
- - Кореневий `package.json` цього монорепо: `lint` → `n-cursor lint`; додано окремий скрипт `oxfmt: "oxfmt ."`, який раніше йшов у хвості ланцюжка прямою командою.
57
- - Скіли `/n-fix` і `/n-lint`: додано вимогу копіювати таблицю `⏱` з виводу інструмента у фінальне резюме відповіді користувачу.
58
-
59
- ## [1.21.0] - 2026-05-25
60
-
61
- ### Changed
62
-
63
- - **Stop-hook → PostToolUse з маршрутизацією за типом файла** (BREAKING для консьюмерів із кастомним `stop-hook` записом). `.claude-template/settings.template.json` тепер реєструє `PostToolUse` (matcher `Edit|Write|MultiEdit`, timeout 300) із командою `npx --no @nitra/cursor post-tool-use-fix` замість попереднього синхронного `Stop`-хука, що ганяв повний `fix` усіх правил на кожному turn-і. Новий хук читає `tool_input.file_path` зі stdin і запускає `fix` **лише** з релевантними правилами: `*.{mjs,js,cjs,ts,tsx,jsx}` → `js-lint`; `*.vue` → `js-lint style-lint vue`; `*.{css,scss,sass}` → `style-lint`; `**/k8s/**/*.{yaml,yml}` → `k8s`; `*.rego` → `rego`; `Dockerfile`/`*.Dockerfile` → `docker`; `.github/workflows/*.{yml,yaml}` → `ga`; `package.json` → `npm-module bun`; `*.sh` → `security`; `*.md` → `text` (поза `docs/adr/**` — там покриває async `normalize-decisions.sh`).
64
- - **CLI**: підкоманду `npx @nitra/cursor stop-hook` видалено; замість неї — `npx @nitra/cursor post-tool-use-fix`. `MANAGED_HOOK_COMMAND_MARKER` у `sync-claude-config.mjs` змінено на `@nitra/cursor post-tool-use-fix`; legacy-маркер `@nitra/cursor stop-hook` лишається у `MANAGED_HOOK_COMMAND_MARKERS` для автоматичного cleanup-у старих entries при наступному `npx @nitra/cursor`. `mergeHooks` тепер обходить union usually template+existing events, тому застарілі managed-групи у вже-непотрібних подіях (`Stop` у даному випадку) теж зачищаються.
65
-
66
- ### Added
67
-
68
- - `npm/scripts/post-tool-use-fix.mjs` — реалізація `routeFilePathToRules(filePath)` (чиста функція, picomatch) і `runPostToolUseFixCli({ stdinJson, spawnFn })` (DI-friendly для тестів). 21 тест у `npm/scripts/tests/post-tool-use-fix.test.mjs`.
69
- - `LEGACY_STOP_HOOK_COMMAND_MARKER` — публічний export для тестів і потенційних консьюмерів, які перевіряють відсутність застарілого хука.
70
-
71
- ### Removed
72
-
73
- - `npm/scripts/claude-stop-hook.mjs` — більше не потрібен.
74
-
75
- ## [1.20.0] - 2026-05-25
76
-
77
- ### Added
78
-
79
- - **NetworkPolicy: два повних канон-snippets**: `deployment.snippet.yaml` (для `Deployment`/`Job`/`CronJob`/`DaemonSet`) і `statefulset.snippet.yaml` (повний канон для `StatefulSet` з intra-replica правилами). Жодного runtime-merge — JS-генератор/rego обирають один за `kind` workload-у через анотацію `metadata.annotations['nitra.dev/workload-kind']`. Нові publiс exports: `loadSnippetSpec('deployment'|'statefulset')`, `KIND_TO_SNIPPET`, `snippetNameForKind(kind)`. `buildNetworkPolicyYaml(deployName, appLabel, kind)` — `kind` тепер обовʼязковий (throws на невідомий). Rego (`network_policy.rego`) робить superset-перевірку проти обраного канону; safety-net deny на allow-all `egress: [{}]`. GKE NodeLocal DNSCache: link-local `169.254.0.0/16` UDP/TCP 53 — у обох канонах. **Breaking** з v1.19.x: видалено `networkPolicyManifestViolations` (структуру тримає rego); `buildNetworkPolicyYaml` без `kind` тепер throws. Перейменування `common.snippet.yaml` → `deployment.snippet.yaml`; `data.template.snippet` → `data.template.deployment_snippet` у rego.
80
- - **`rules/js-lint/coverage`**: `parseStrykerReport` тепер зчитує оригінальний код вижилих мутантів (`extractOriginal`), групує по файлах і повертає `survived: [{file, mutants: [{line,col,mutantType,original,replacement}], exampleTest, recommendationText}]`; `findExampleTest` + `extractFirstTestBlock` знаходять і витягують перший тест-блок із тест-файлу поруч — для стилю.
81
- - **LLM-рекомендації у COVERAGE.md**: коли встановлено `ANTHROPIC_API_KEY`, `n-cursor coverage` робить один Anthropic API-виклик на кожен файл з вижилими мутантами та записує рекомендацію «Що треба протестувати» у секцію `## Recommendations`. Модель: `claude-haiku-4-5-20251001` з prompt caching (`ephemeral`). Без ключа — секція генерується без LLM-тексту.
82
- - **`rules/js-lint/coverage/lib/generate-recommendation.mjs`**: `generateMutantRecommendation(client, sourceContent, mutants)` — ізольований модуль LLM-виклику.
83
- - **`@anthropic-ai/sdk`** у dependencies — потрібен для LLM-рекомендацій (опціонально: якщо `ANTHROPIC_API_KEY` не задано, sdk не викликається).
84
- - **`rules/test/coverage`**: `renderMarkdown` генерує секцію `## Recommendations` з per-file підрозділами (`### <file>`) — таблиця мутантів + приклад тесту + LLM-текст (якщо є).
85
- - **Stryker incremental mode** у `stryker.config.baseline.mjs`: `incremental: true` + `incrementalFile: 'reports/stryker/stryker-incremental.json'` — Stryker зберігає прогрес між прогонами, відновлює стан після переривання (SIGURG, OOM тощо).
86
- - **`skills/coverage-fix`**: новий скіл `/n-coverage-fix` — читає `## Recommendations` з COVERAGE.md і ітеративно дописує тести до конвергенції mutation score, включаючи LLM-рекомендації та приклади тестів у промпт агента.
21
+ - **`rules/test/coverage/coverage.mjs` `renderMarkdown`**: секція вижилих мутантів перейменована `## Recommendations` `## Вижилі мутанти`; доданий ` ```json ` блок з масивом survived перед таблицею парситься скілами `/n-fix-tests` і `/n-coverage-fix`.
22
+ - **`skills/fix-tests/SKILL.md`**: конвенція test-файлів оновлена цільовий файл завжди `<dir>/tests/<basename>.test.mjs`; якщо знайдено co-located тест (`.test.js`/`.test.mjs`) переноситься в `tests/` з оновленням imports.
87
23
 
88
24
  ## [1.19.2] - 2026-05-25
89
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.25.0",
3
+ "version": "1.25.2",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -92,7 +92,12 @@ export function renderMarkdown(rows) {
92
92
 
93
93
  const allSurvived = rows.flatMap(r => r.survived ?? [])
94
94
  if (allSurvived.length > 0) {
95
- lines.push('', '## Recommendations')
95
+ lines.push('', '## Вижилі мутанти')
96
+ // JSON-блок для /n-fix-tests skill — парситься скілом для написання тестів
97
+ lines.push('', '```json')
98
+ lines.push(JSON.stringify(allSurvived, null, 2))
99
+ lines.push('```')
100
+ // Людиночитабельна таблиця
96
101
  for (const group of allSurvived) {
97
102
  lines.push('', `### ${group.file}`, '')
98
103
  lines.push('| Рядок | Оригінал | Заміна | Тип |')
@@ -9,7 +9,7 @@ export default {
9
9
  reporters: ['json', 'clear-text'],
10
10
  jsonReporter: { fileName: 'reports/stryker/mutation.json' },
11
11
  coverageAnalysis: 'off',
12
- // incremental: зберігає прогрес між прогонами відновлення після переривання без старту з нуля.
12
+ // incremental: зберігає результати між запусками, відновлює після краш/kill.
13
13
  incremental: true,
14
- incrementalFile: 'reports/stryker/stryker-incremental.json',
14
+ incrementalFile: 'reports/stryker/incremental.json',
15
15
  }
@@ -1,114 +1,138 @@
1
1
  ---
2
2
  name: n-coverage-fix
3
3
  description: >-
4
- Автономна команда: запускає coverage, читає ## Recommendations у COVERAGE.md, ітеративно пише тести для вижилих мутантів до конвергенції
4
+ Автономна команда: запускає n-cursor coverage читає вижилих мутантів ітеративно пише тести до конвергенції (max 3 ітерації)
5
5
  ---
6
6
 
7
- # /n-coverage-fix — ітеративне підвищення mutation score
7
+ # n-coverage-fix — підвищення mutation score
8
8
 
9
- ## Важливо
9
+ ## Мета
10
10
 
11
- ⚠️ Не запускати паралельно з іншим `/n-coverage-fix` або `bun coverage` — Stryker пише `mutation.json` і `incremental.json` в одну директорію. `n-cursor coverage` всередині вже серіалізований через `withLock('coverage')`, але паралельний запуск двох ітерацій скілу зіпсує дані.
11
+ Автоматично підвищити mutation score: запускає coverage, знаходить survived mutants, пише тести, повторює до конвергенції.
12
12
 
13
- ## Мета
13
+ ## ⚠️ Не запускати паралельно
14
+
15
+ Цей скіл **не можна** запускати паралельно в різних агентах або Bash-задачах.
14
16
 
15
- Автономно підвищити mutation score: запустити `bun coverage`, записати тести для вижилих мутантів, повторити до конвергенції (score перестав зростати).
17
+ `n-cursor coverage` всередині серіалізований через `withLock('coverage')` другий виклик чекатиме. Але Stryker пише `mutation.json` і `incremental.json` в одну директорію: паралельний запуск **зіпсує обидва файли**. Запускай тільки один `/n-coverage-fix` одночасно.
16
18
 
17
- ## Алгоритм
19
+ ## Передумови
20
+
21
+ - Поточна директорія — корінь проєкту (де `.n-cursor.json` і `COVERAGE.md`)
22
+ - `n-cursor coverage` доступний (`npx @nitra/cursor coverage` або `bun run coverage`)
23
+ - Залежності встановлені (`bun i`)
24
+
25
+ ## Workflow
18
26
 
19
27
  ### Крок 1: Запусти coverage
20
28
 
21
29
  ```bash
22
- bun coverage
30
+ n-cursor coverage
23
31
  ```
24
32
 
25
- (або `bun run coverage` якщо команда у `package.json`)
33
+ Або якщо є у `package.json#scripts`:
26
34
 
27
- Чекай завершення. Якщо команди немає у `package.json` — запусти `n-cursor coverage` з кореня.
35
+ ```bash
36
+ bun run coverage
37
+ ```
38
+
39
+ Ця команда генерує `COVERAGE.md`. Якщо є survived mutants — COVERAGE.md матиме секцію `## Вижилі мутанти` з JSON-блоком.
40
+
41
+ ### Крок 2: Перевір вижилих
28
42
 
29
- ### Крок 2: Прочитай вижилих мутантів
43
+ Прочитай `COVERAGE.md`. Знайди секцію `## Вижилі мутанти`. Знайди фенсований блок ` ```json ` і розпарси JSON-масив.
30
44
 
31
- Прочитай `COVERAGE.md` знайди секцію `## Recommendations`.
45
+ Якщо секція відсутня або масив порожній — зупинись:
32
46
 
33
- Якщо секції немає або вона порожня:
34
47
  ```
35
- Нема вижилих мутантів — mutation score повний
48
+ Жодних вижилих мутантів — mutation score повний. Coverage завершено.
36
49
  ```
37
- → DONE
38
50
 
39
- Запам'ятай поточний mutation score як `baseline_score` (рядок `| **Разом** |` з таблиці у COVERAGE.md).
51
+ Запам'ятай `prevCount = масив.length`.
40
52
 
41
- ### Крок 3: Для кожного файлу з Recommendations пиши тести
53
+ ### Крок 3: Для кожного файлу — спауни Agent
42
54
 
43
- Для кожного `### <file>` у секції:
55
+ Згрупуй мутанти по полю `file`. Для кожної групи:
44
56
 
45
- **3a. Читай контекст:**
46
- - Source-файл (`<file>` від кореня проєкту)
47
- - Таблицю вижилих мутантів: рядок, оригінал, заміна, тип
48
- - Блок `**Приклад наявного тесту:**` — style guide для нових тестів
57
+ **3a. Визнач test файл (завжди у `tests/` директорії):**
49
58
 
50
- **3b. Знайди тестовий файл:**
51
- Перший що існує:
52
- 1. `<dir>/<basename>.test.js` — поруч із source
53
- 2. `<dir>/<basename>.spec.js`
54
- 3. `test/<basename>.test.js` від кореня
55
- 4. `tests/<basename>.test.js` від кореня
59
+ Цільовий: `<dir>/tests/<basename>.test.mjs`
60
+ (де `<dir>` — директорія source-файлу, `<basename>` — ім'я source без розширення)
56
61
 
57
- Якщо жоден не знайдено — створи `<dir>/<basename>.test.js` з правильними imports (орієнтуйся на сусідні файли).
62
+ 1. Якщо `<dir>/tests/<basename>.test.mjs` існує використай
63
+ 2. Якщо `<dir>/<basename>.test.js` або `<dir>/<basename>.test.mjs` існує (co-located) →
64
+ - Перенеси до `<dir>/tests/<basename>.test.mjs`
65
+ - Оновити відносні imports (тепер `../` рівень вгору до source)
66
+ 3. Жоден не знайдено → буде створено `<dir>/tests/<basename>.test.mjs`
58
67
 
59
- **3c. Напиши тести що вбивають кожен мутант:**
68
+ **3b. Сформуй промпт для Agent:**
69
+
70
+ ```
71
+ Тобі дані вижилі мутанти зі Stryker для файлу `<file>`.
72
+ Ці мутанти вижили бо наявні тести НЕ вловили конкретні зміни коду.
60
73
 
61
- Керуйся типом мутації:
62
- - `ConditionalExpression` (`→ false` / `→ true`): протестуй обидва branch явно — значення що робить умову `true` і значення що робить її `false`
63
- - `BooleanLiteral` (`true → false`): перевір початковий стан — `initialValue === false`
64
- - `LogicalOperator` (`&&` ↔ `||`): передай `null` та `undefined` **окремо**, перевір що результат різний для кожного
65
- - `StringLiteral` / `EqualityOperator`: перевір точний рядок/значення, а не лише happy-path
74
+ **Вихідний код** (`<file>`):
75
+ \`\`\`
76
+ <зміст source-файлу>
77
+ \`\`\`
66
78
 
79
+ **Наявні тести** (`<test-file>`):
80
+ \`\`\`
81
+ <зміст test-файлу або "файл ще не існує">
82
+ \`\`\`
83
+
84
+ **Вижилі мутанти** (кожен — зміна коду що НЕ вловлена):
85
+ <для кожного мутанта:>
86
+ - Рядок <line>, колонка <col>: `<original>` → `<replacement>` (тип: <mutantType>)
87
+
88
+ **Завдання:**
89
+ Допиши мінімальні test-cases у файл `<test-file>` які вловлять кожен мутант.
67
90
  Правила:
68
91
  - НЕ видаляй і НЕ змінюй наявні тести
69
- - Стиль: той самий `describe`/`it`/`expect`, мова коментарів як у прикладі тесту
70
- - Якщо `**Приклад наявного тесту:**` відсутнійорієнтуйся на інші test-файли у тій самій директорії
71
-
72
- **3d. Після написання тестів:**
73
- ```bash
74
- bun test <testFile>
92
+ - Стиль тестів відповідно до наявного файлу (той самий фреймворк, describe/test)
93
+ - Якщо файл ще не існуєствори `<dir>/tests/<basename>.test.mjs` з правильними імпортами.
94
+ Приклад: source `src/services/auth-store.js` → import `import { ... } from '../auth-store.js'`
95
+ - Після написання запусти: `bun test <test-file>` і переконайся що тести проходять (виправ якщо падають, 1-2 спроби)
75
96
  ```
76
97
 
77
- Якщо FAIL — виправи саме ті тести що впали (до 2 спроб). Якщо не вдалося логуй і переходь до наступного файлу.
98
+ **3c. Запусти Agent** з цим промптом. Дочекайся завершення.
78
99
 
79
- ### Крок 4: Перевір що весь suite проходить
100
+ ### Крок 4: Перевір що всі тести проходять
80
101
 
81
102
  ```bash
82
103
  bun test
83
104
  ```
84
105
 
85
- Якщо FAIL:
86
- - Не відкочувати зміни
87
- - Показати: яка помилка, які файли змінені, що вже покращено
88
- - Очікувати рішення від user: [виправити вручну → продовжити] / [пропустити файл] / [зупинити]
106
+ Якщо падають — поверни відповідний Agent з помилкою і попроси виправити.
89
107
 
90
- ### Крок 5: Запусти coverage і перевір конвергенцію
108
+ ### Крок 5: Запусти coverage і порівняй
91
109
 
92
110
  ```bash
93
- bun coverage
111
+ n-cursor coverage
94
112
  ```
95
113
 
96
- Якщо CRASH (SIGURG, memory pressure): нагадати user — Stryker incremental зберіг прогрес, перезапустити `bun coverage`.
97
-
98
- Прочитай новий COVERAGE.md. Візьми `new_score` з рядка `| **Разом** |`.
114
+ Прочитай новий `COVERAGE.md`. Розпарси JSON-масив вижилих.
115
+ `newCount = новий масив.length`
99
116
 
100
117
  **Рішення:**
101
- - Якщо `new_score > baseline_score` → `baseline_score = new_score` → перейти до Кроку 2 (наступна ітерація)
102
- - Якщо `new_score <= baseline_score` → конвергенція:
118
+
119
+ - `newCount < prevCount` AND iterations < 3 повтор з Кроку 2 з оновленим масивом
120
+ - `newCount >= prevCount` → конвергенція:
103
121
  ```
104
122
  ✓ Конвергенція: mutation score більше не покращується.
105
- Baseline: <baseline_score> Фінал: <new_score>
123
+ Було вижилих: <prevCount>, стало: <newCount>.
124
+ ```
125
+ - iterations == 3 → зупинись:
126
+ ```
127
+ ⚠️ Досягнуто максимум ітерацій (3).
128
+ Вижило: <newCount> мутантів. Деякі можуть бути невбивними (dead code, external state).
106
129
  ```
107
- → DONE
130
+
131
+ ## Конвергенція — нормальний результат
132
+
133
+ Деякі мутанти неможливо вбити: захищений зовнішній стан, недетермінована логіка, еквівалентні мутації. Не намагайся виправити те що не змінилось після ітерації.
108
134
 
109
135
  ## Нотатки
110
136
 
111
137
  - Stryker `incremental` (`incrementalFile`) зберігає прогрес між запусками — crash ≠ перезапуск з нуля
112
138
  - Не комітити зміни автоматично — user вирішує коли комітити
113
- - Пріоритет файлів: більше вижилих мутантів = важливіший (першим у Recommendations = найважливіший)
114
- - Якщо `COVERAGE.md` відсутній — запусти `bun coverage` спочатку
@@ -43,13 +43,18 @@ description: >-
43
43
 
44
44
  Згрупуй мутанти по полю `file`. Для кожної групи виконай:
45
45
 
46
- **3a. Знайди файли:**
46
+ **3a. Знайди / визнач test файл (завжди у `tests/` директорії):**
47
+
48
+ Цільовий файл завжди: `<dir>/tests/<basename>.test.mjs`
49
+ (де `<dir>` — директорія source-файлу, `<basename>` — ім'я без розширення)
50
+
47
51
  - Source: `<cwd>/<file>` (прочитай вміст)
48
- - Test файл (перший що існує):
49
- - `<dir>/<basename>.test.<ext>` поруч із source
50
- - `<dir>/tests/<basename>.test.<ext>`
51
- - `tests/<basename>.test.<ext>` від кореня
52
- - Якщо жоден не знайдено буде створено поруч із source
52
+ - Test файл:
53
+ 1. Якщо `<dir>/tests/<basename>.test.mjs` існує використай його
54
+ 2. Якщо `<dir>/<basename>.test.js` або `<dir>/<basename>.test.mjs` існує (co-located) →
55
+ - Перенеси файл до `<dir>/tests/<basename>.test.mjs`
56
+ - Оновити відносні `import` шляхи якщо є (тепер треба `../` рівень вгору)
57
+ 3. Якщо жоден не знайдено → буде створено `<dir>/tests/<basename>.test.mjs`
53
58
 
54
59
  **3b. Сформуй промпт для Agent:**
55
60
 
@@ -76,7 +81,9 @@ description: >-
76
81
  Правила:
77
82
  - НЕ видаляй і НЕ змінюй наявні тести
78
83
  - Стиль тестів — відповідно до наявного файлу (той самий фреймворк, той самий стиль describe/test)
79
- - Якщо файл ще не існує — створи його з правильними імпортами відповідно до файлів у тому самому каталозі
84
+ - Якщо файл ще не існує — створи `<dir>/tests/<basename>.test.mjs` з правильними імпортами.
85
+ Приклад: source `src/services/auth-store.js` → test `src/services/tests/auth-store.test.mjs`,
86
+ import: `import { ... } from '../auth-store.js'`
80
87
  - Після написання запусти: `bun test <test-file>` і переконайся що всі тести проходять (виправ якщо падають)
81
88
  ```
82
89