# Changelog

## [1.118.11] - 2026-08-30

### Fixed

- `KNOWN_PLUGIN_RANGES` наздогнав версії плагінів воркспейсу: `lang-js` `^0.27`→`^0.34`, `lang-python` `^0.14`→`^0.15`, `lang-rust` `^0.17`→`^0.18`, `lang-php` `^0.4`→`^0.5`.

Наслідок дрейфу ширший за червоний гейт `known-plugin-ranges`: `ensurePluginInstalled` обмежує установку first-party плагіна саме цим range, тобто ставив би версію, старішу за ту, що вже лежить у воркспейсі.

Гейт лишається крихким за конструкцією — для `0.x` кожен minor-бамп плагіна ламає його знову, бо range треба піднімати вручну тим самим коммітом. Автоматизація не додається свідомо: `KNOWN_PLUGIN_RANGES` зникає разом із переходом на резолв за lock + OCI reference (робота Д1, `docs/plans/2026-08-29-js-rust-migration-completion-plan.md`).

## [1.118.10] - 2026-08-30

### Changed

- Записано форму, якою лікується half-apply у фіксерах, що пишуть у чужі файли репозиторію.

Привід — T0-фікс `bun/package_json` (єдиний виконавець свого фіксу назавжди, §2.92): він переписував чужі workflow, і лише потім зʼясовував, чи дія, заради якої переписував, узагалі відбудеться. Узагальнення просте: якщо фікс залишає репозиторій у неузгодженому стані, майже завжди причина в тому, що рішення ухвалюється ПІСЛЯ першого запису. Лікується це не додатковим прапорцем і не rollback-ом, а перенесенням усього рішення у read-only фазу — «порахувати повний план → застосувати цілком або нічого», рівно та форма, що вже діє в native-фіксів ядра (`crates/rules-core/src/concerns/fix.rs`), які віддають план хосту.

Гучна відмова (`error`, не `warn`) при цьому не заміна атомарності, а доповнення до неї: коли частина плану свідомо не виконується, консюмер має про це почути.

Другий, дрібніший урок із того самого місця: верифікаційний прохід, який перечитує з диска щойно написані тобою ж файли, інформації не додає, якщо детектор і мутатор ділять ту саму функцію пошуку. Він лише маскує те, що справжнє джерело блокування — інший клас файлів.

Деталі — §2.94 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.9] - 2026-08-30

### Changed

- Зріз 6 дистрибуції переспроєктовано: npm-канал знімається **повністю**, а не перетворюється на launcher над нативним бінарем.

Попереднє формулювання (`docs/specs/2026-08-01-rules-cli-phase8-skeleton.md`, розділ 5 п.6) припускало esbuild/biome-патерн — npm лишається, `bin` стає launcher-ом, платформні пакети розширюються з cdylib на повний бінар. Рішення власника: єдине джерело дистрибуції — GitHub Releases; CI-образи вже несуть встановлену версію; brew і аналоги на інших платформах — обгортки над Releases, не окремі канали.

Це не заміна транспорту: npm у цьому проєкті **і є системою плагінів** — шість first-party плагінів існують як npm-пакети, а `resolve-plugins.mjs` знаходить їх за конвенцією імені в залежностях консюмера. Тому зняття форсує три незалежні заміни: канал бінаря, формат і резолв плагінів, доля вбудованого набору `rules/**`.

Плагіни стають окремими артефактами, якими керує сам CLI (кеш, не `node_modules`) — свідомо проти вшивання в бінар (версія плагіна намертво = версія CLI, сторонній плагін неможливий) і проти гібрида (дві стежки резолву).

Найгостріший практичний наслідок: канонічний рядок виклику, який фіксери зашивають у ЧУЖІ файли консюмера, перестає бути `bunx n-rules lint <surface>` і стає `n-rules lint <surface>`. Перейти ДО зрізу 6 не можна, забути ПІСЛЯ — означає лишити консюмерів із викликом неіснуючого каналу. Тобто зріз 6 несе ще й міграцію вже переписаних файлів.

Розгорнуто — розділ 12 спеки (канали, десять відкритих питань, що доведеться спроєктувати замість того, що npm давав задарма, і що натомість спрощується) і §2.96 реєстру відкритих питань.

## [1.118.8] - 2026-08-30

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.118.7] - 2026-08-30

### Changed

- Зняття JS-канонів фіксу — партія `plugins/lang-js`, остання й найбільша (девʼятнадцять із двадцяти трьох). Разом із каноном `fix-<concern>.mjs` зникає не тест, а ПОВЕРХНЯ: `loadT0Patterns` резолвить фіксери у порядку native → wasm (`guestFix`) → `fix-<concern>.mjs`, і третій шар був глушником випадку «гість не резолвиться».

Гейт — табличний, у `wasm-plugin-parity.test.mjs` (`§2.93`), і перевіряє не відсутність файлів, а СКЛАД резолву тим самим `loadT0Patterns`, яким ходить прод: рівно один патерн на концерн, і той `guestFix`. Два патерни означали б повернення канону (подвійний фікс), нуль — зникнення гостя, тобто `--fix`, що МОВЧКИ перестав фіксити концерн.

Тверджень тут ТРИ, а не два, як у `ci-github` (§2.90). Там таблиця звірялась із живим маніфестом у бік «кожен концерн маніфеста або в таблиці, або у свідомому виключенні» — це працювало, бо всі вісімнадцять концернів гостя мали фікс. У `lang-js` фікс має меншість із пʼятдесяти, тож той самий бік звірки дав би тридцять «виключень», тобто шум замість гейта. Живих джерел натомість два, кожне зі свого боку: (2) кожен ключ таблиці мусить бути в живому маніфесті (`concerns` ∪ `fix_only_concerns`) — перейменований чи знятий у гості ключ валить гейт; (3) набір уцілілих `fix-*.mjs` плагіна мусить дорівнювати рівно чотирьом іменованим виняткам — новий канон для портованого концерну валить гейт навіть якщо його ключа в таблиці немає.

Гейт доведено живим ДО видалення (дешевший порядок §2.90): із наявними канонами перше твердження червоне по всіх девʼятнадцяти ключах, третє — по всіх двадцяти трьох файлах.

Fix-parity-секції `js/check`, `js/doc_comments`, `bun/licensee` і `js-run/runtime` не видалено, а ПЕРЕПИСАНО з «гість = канон» на «гість = очікуваний результат» (крок 4 зразка §2.88): очікуване тепер або літерал, або читається з того самого файлу-джерела, який гість вшиває `include_str!`-ом. Тестів у `wasm-plugin-parity.test.mjs` — 317 замість 314.

Зразок партії — §2.88, звіт — §2.93 `docs/plans/2026-08-05-open-questions-register.md`.

### Fixed

- Полагоджено дві межі wasm-мосту, які раніше були лише задокументовані (§2.92) — обидві з класу «гість мовчки дістає не те, що мав».

**Корінь `capabilities.fs-read`.** Read-only preopen для оголошених плагіном шляхів резолвився від робочої директорії ХОСТ-ПРОЦЕСУ, тоді як виклики концернів беруть корінь дерева окремим параметром: для прогону з явно заданим коренем (`lint --path <інше-дерево>`) плагін читав би не те дерево, яке лінтується — без жодної помилки, бо імена файлів у чужому дереві ті самі. Тепер preopens резолвляться від кореня ВИКЛИКУ; завантаження без кореня не падає назад на директорію процесу, а плагін із оголошеним `fs-read`, узятий таким шляхом, відмовляється виконувати `detect`/`fix` з типізованою помилкою (маніфест при цьому читається як і раніше). Спостережуваних наслідків не було — жоден чинний маніфест `fs-read` не оголошує; знято з латентного стану до появи першого споживача. Гейт — гість, який `fs-read` реально оголошує й реально читає диск, із коренем виклику, навмисно відмінним від директорії процесу.

**Порожній fix-батч через відсутні таргети діагностик.** Коли всі файли, названі діагностиками концерну, на диску відсутні (клас «канонічного файлу БРАКУЄ» — файл і має бути створений фіксом), хост пропускав їх усі й передавав гостю ПОРОЖНІЙ набір файлів при непорожніх діагностиках: фіксер мусив писати наосліп, а прогін звітував «чисто». Перевірка тепер дивиться на фактичний набір, а не на список шляхів до читання: батч відновлюється за оголошеним fix-скоупом контрибуції, потім за дельтою запиту, і лише коли нема ані того, ані іншого — виклик падає з поясненням замість тихого порожнього плану.

Деталі — §2.95 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.6] - 2026-08-29

### Changed

- Задокументовано дві межі wasm-мосту, які досі жили лише як припущення — обидві виявлені під час розвідки останнього непортованого концерну `lang-js`.

**Ціна `fix-glob`.** Непорожній `fix-glob` контрибуції вмикає full-scope fix-батч, і цей скоуп читається ТРИЧІ за один fix-виклик: сам батч, знімок до виклику гостя і знімок після (останні два — основа host-diff для фіксерів, що мутують диск напряму). Замір на цьому репозиторії: скоуп `**/*` після gitignore-обходу — 3389 файлів і 124,09 MiB, тобто ~91 MiB IO і ~30 MiB копії у лінійну памʼять гостя на кожен фікс одного файлу, при корисному скоупі 985 KiB. Висновок, який варто мати перед очима при кожній новій контрибуції: `fix-glob` — інструмент РОЗРИВУ скоупів (фіксу треба ширше, ніж детекту), а не канал «прочитай мені все репо».

**Корінь `capabilities.fs-read`.** Read-only preopen для оголошених плагіном шляхів резолвиться від робочої директорії ХОСТ-ПРОЦЕСУ, тоді як самі виклики концернів беруть корінь репозиторію окремим параметром. Для прогону з явно заданим коренем (`lint --path <інше-дерево>`) плагін читав би не те дерево, яке лінтується. Спостережуваних наслідків немає — жоден чинний маніфест `fs-read` не оголошує; знято з латентного стану до того, як зʼявиться перший споживач.

Деталі — §2.92 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.5] - 2026-08-29

### Changed

- Зняття JS-канонів фіксу — `plugins/lang-rust` (4 канони) і `plugins/lang-python` (3): третій і четвертий із шести плагінів, борг «спершу парність» у них закрито повністю. Лишаються три партії — ЯДРО, `lang-js`, `ci-github`.

Заміна поверхні — та сама, що в пілоті §2.88: разом із каноном зникає не тест, а третій шар `loadT0Patterns` (native → wasm (`guestFix`) → `fix-<concern>.mjs`), який глушив випадок «гість не резолвиться». Але форма гейта УТОЧНЕНА: пілот мав один концерн, тут їх чотири й три, тож гейт став ТАБЛИЧНИМ — один тест на плагін, що проганяє той самий `loadT0Patterns`, яким ходить прод, по ВСІХ ключах `Guest::fix` і звіряє одну таблицю «рівно один патерн, і той `guestFix`». Розходження видно з одного місця, а падіння називає САМЕ той концерн, що поїхав, і напрямок: нуль патернів — зник гість (`--fix` мовчки перестав фіксити), два — повернувся канон (подвійний фікс). Обидва гейти доведено живими: з тимчасово поверненим каноном червоні.

Крок «переписати характеризацію, а не видалити» тут уперше не був формальністю: чотири твердження трималися за канони. Три parity-сценарії вже мали двійників у формі «гість = очікуваний результат», четвертий (`cargo_mutants_config`, glob-workspaces `packages/*`, §2.28) двійника не мав і ПЕРЕНЕСЕНИЙ у fix-міст-форму.

Тестів: `wasm-plugin-parity-rust.test.mjs` 71 → 70, `wasm-plugin-parity-python.test.mjs` 65 → 65, обидва прогони `cargo test` без змін (90 і 80). Разом зникло 19 JS-тестів характеризації самих канонів — кожен із адресою в гостя, поіменний облік у §2.91.

Побічно: `knip.json` мав два мертві глоби (`rules/*/*/fix-*.mjs` в обох воркспейсах і, лише в `plugins/lang-python`, `rules/**/*.test.mjs` — після зняття цей плагін не має в `rules/` жодного тесту).

Деталі — §2.91 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.4] - 2026-08-29

### Changed

- Зняття JS-канонів фіксу — партія ЯДРА: усі 20 `npm/rules/**/fix-<concern>.mjs`, чий ключ уже в `NATIVE_FIXES`, видалено разом із їхніми `docs/`. Native — єдина реалізація фіксу цих концернів; `--fix` більше не має JS-fallback-у, який мовчки підмінив би native при незавантаженому аддоні.

`npm/rules/tauri/release/fix-release.mjs` НЕ знято й зніматись не має: його ключа немає в `NATIVE_FIXES` (format-preserving YAML свідомо не портований), тож він і далі єдиний виконавець свого фіксу.

Замість двадцяти поодиноких гейтів — ОДИН табличний (`npm/scripts/lib/lint-surface/tests/native-fix-single-source.test.mjs`, `test.each` по всьому реєстру): для кожного ключа `loadT0Patterns` (той самий резолвер, яким ходить прод) має віддати рівно один патерн, і той `native-fix:<key>`, а `fix-<concern>.mjs` не має існувати на диску. Друга половина потрібна саме в ядрі: для native-ключа `loadT0Patterns` повертається рано, тож повернутий канон складу резолву не змінює — він просто стає мертвим дублікатом. Гейт доведено живим окремо по кожній половині.

Тестів на тому самому наборі файлів стало 266 замість 188. 16 тестів у шести файлах зникли разом із каноном — кожен має названу native-заміну (§2.89); чотири файли не видалено, а переписано на `loadT0Patterns`, тож 53 тести `image-avif`/`tauri-updater`/`text-cspell`/`worktree-vscode_settings` тепер ганяють реальний виконавець `--fix`.

Деталі, порядкова звірка канон↔native і дві пастки переписування — §2.89 `docs/plans/2026-08-05-open-questions-register.md`.
- Зняття JS-канонів фіксу — партія `plugins/ci-github` (сімнадцять канонів; борг «спершу парність» у цьому плагіні закрито повністю). Разом із каноном `fix-<concern>.mjs` зникає не тест, а ПОВЕРХНЯ: `loadT0Patterns` резолвить фіксери у порядку native → wasm (`guestFix`) → `fix-<concern>.mjs`, і третій шар був глушником випадку «гість не резолвиться».

Тому характеризацію переведено не на `existsSync` видалених файлів, а на СКЛАД резолву тим самим `loadT0Patterns`, яким ходить прод — ОДИН табличний гейт по всіх сімнадцяти ключах (`wasm-plugin-parity-ci-github.test.mjs`, `§2.90`): рівно один патерн на концерн, і той `guestFix`. Два патерни означали б повернення канону (подвійний фікс), нуль — зникнення гостя, тобто `--fix`, що МОВЧКИ перестав фіксити концерн. Друге твердження гейта звіряє саму таблицю з живим маніфестом гостя, тож концерн, доданий у гість і не внесений у таблицю, валить гейт, а не лишається тихо неперевіреним. Гейт доведено живим: із наявними канонами він червоний по всіх сімнадцяти ключах.

Тестів у `wasm-plugin-parity-ci-github.test.mjs` — 51 замість 49. Ідемпотентність template-merge на канонічному вмісті, яку несли зняті JS-тести, відновлена в новій формі — додатковим твердженням «повторний fix на канонічному вмісті → порожній план» у гостьовому T0-циклі кожного з дванадцяти template-концернів.

Зразок партії — §2.88, звіт — §2.90 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.3] - 2026-08-29

### Changed

- Зняття JS-канонів фіксу — пілот на `plugins/lang-php` (перший із шести плагінів; лишається 62 канони). Разом із каноном `fix-<concern>.mjs` зникає не тест, а ПОВЕРХНЯ: `loadT0Patterns` резолвить фіксери у порядку native → wasm (`guestFix`) → `fix-<concern>.mjs`, і третій шар був глушником випадку «гість не резолвиться».

Тому характеризацію переведено не на `existsSync` видаленого файлу, а на СКЛАД резолву тим самим `loadT0Patterns`, яким ходить прод: рівно один патерн, і той `guestFix`. Два патерни означали б повернення канону (подвійний фікс), нуль — зникнення гостя, тобто `--fix`, що МОВЧКИ перестав фіксити концерн. Гейт доведено живим: із тимчасово поверненим каноном він червоний.

Тестів у `wasm-plugin-parity-php.test.mjs` стало 37 замість 36 — жодного зниклого.

Покроковий зразок для решти пʼятьох партій — §2.88 `docs/plans/2026-08-05-open-questions-register.md`.

### Fixed

- Заявлений `fix-glob` контрибуції (поле мажора контракту `4.0.0`) на host-боці майже не діяв: `run_wasm_concern_fix` читав його ЛИШЕ у гілці, де ЖОДНА діагностика не назвала файл. Для звичайного концерну, чиї діагностики несуть `file`, fix-батч і далі будувався з самих цих шляхів, а оголошений ширший fix-скоуп мовчки ігнорувався — рівно та вада, від якої поле й мало рятувати: вузький скоуп → порожній план → гейт `edits.length > 0` не пускає гість-пріоритет → JS-канон тихо робить фікс удруге.

Гірший підклас тієї самої гілки — концерн класу «канонічного файлу БРАКУЄ». Усі його діагностики несуть `file`, але жоден із цих шляхів на диску не існує, тож читання батчу пропускає їх усі, і гість отримує ПОРОЖНІЙ список файлів при непорожніх діагностиках. Це та сама двозначність, яку мала закривати типізована помилка «неоднозначний порожній fix-batch»: гейт дивиться на список шляхів ДО читання з диска, а не на фактичний батч, тож фіксер писав би наосліп, не відрізняючи «файлу немає» від «хост його не передав».

Полагоджено: НЕПОРОЖНІЙ `fix-glob` тепер вмикає full-scope fix-батч завжди, з union-ом названих діагностиками файлів, яких глоб не покрив. Opt-in, а не автоматика для всіх `scope: full`: обхід усього репо на кожен fix-виклик коштує, і платять його лише ті концерни, що самі це заявили. Для жодної чинної контрибуції поведінка не змінилась — до цієї зміни непорожнього `fix-glob` не оголошував ніхто.

Заразом полагоджено `rules-template-merge`: `jsonc-parser` підключався без фічі `preserve_order`, тож ключі розібраного JSON/JSONC лежали у ХЕШ-порядку `HashMap`, засіяному наново кожен процес. Будь-яка повна регенерація JSON-таргета (`jsconfig.json`, `package.json` у fallback-шляху template-мержу) переписувала файл консюмера з ключами у ВИПАДКОВОМУ порядку, і два прогони того самого фіксу давали різні файли. Хірургічний (comment-preserving) шлях на це не спирався й не змінюється.

Деталі — §2.87 `docs/plans/2026-08-05-open-questions-register.md`.
- `FileEdit::WriteBytes` тепер доїжджає до петлі `fix` — міст `crates/rules-fix/src/t0.rs` перекладає його прямо в `FileEditPlan::WriteBytes` (`n7n-llm-lib 0.5.0`), а не збирає на гучну відмову.

До цього релізу байтовий edit не виражався планом узагалі: `EditPlan` був ТЕКСТОВИЙ наскрізь (`Create::content` — `String`, `Anchored` рахує рядкові якорі, pre-image журналу — текст). Справжньою стіною був навіть не тип `content`, а `WriteGuard::capture_pre_image`: він знімав знімок `read_to_string`-ом, на бінарному файлі падав `Err`, і `check_write` перетворював це на `Decision::Block` — тобто будь-який запис у бінарний файл через guard був неможливий.

Наслідок для користувача: `image-compress/check` і `image-avif/avif_generation` (§2.85) працюють і в ладдер-контурі, не лише в основному диспатчі T0.

Заодно:
- `PreImage::ExistingBytes` (новий варіант llm-lib) перекладається БАЙТ-У-БАЙТ, без lossy-конверсії — саме вона на detect-боці перетворювала 12 байтів PNG-сигнатури на 18 байтів мозаїки (§2.83);
- прибрано машинерію збору «невиразних» edit-ів: усі три операції контракту мають прямий відповідник, а `FileEdit` не `#[non_exhaustive]`, тож четверта стане помилкою компіляції, а не тихим пропуском.

Залежності підняті узгоджено в пʼятьох крейтах (`n7n-llm-lib` 0.4 → 0.5, `n7n-harness` 0.2 → 0.3): часткове підняття давало дві копії крейта в графі й «expected `llm_lib::journal::Journal`, found `Journal`».

## [1.118.2] - 2026-08-29

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.118.1] - 2026-08-29

### Fixed

- Поверхня `fix-only-concerns` (мажор контракту `4.0.0`) не працювала на host-боці: `run_wasm_concern_fix` шукав контрибуцію концерну ЛИШЕ у `describe().concerns`, тому для ключа з другого списку вона не знаходилась. Наслідків було два, обидва тихі. По-перше, `diff_glob` ставав `None` — host-diff вимикався, тож фіксер класу exec-tool мутував диск, а план виходив ПОРОЖНІЙ: гейт `edits.length > 0` його не пускав, і JS-канон робив той самий фікс удруге. По-друге, у `lint --full` (без дельти) виклик падав `ambiguous_empty_fix_batch_err` із текстом «не заявлений у `describe().concerns`», хоч концерн був заявлений — просто в іншому списку.

Полагоджено ЄДИНИМ акцесором `Manifest::fix_contribution(key)`, який дивиться в обидва списки, — рівно з того самого мотиву, що `ConcernContribution::effective_fix_glob`: «забув подивитись у другий список» у новому місці виклику має бути неможливо написати. Взаємну виключність ключа гарантує валідатор маніфеста на завантаженні плагіна, тож порядок перегляду списків ні на що не впливає.

Дефект знайшов перший реальний споживач поверхні — порт T0-фікса `js/eslint` у гостя `@7n/rules-lang-js`. Тестом на фейковому маніфесті він не ловився: мажор віддав форму (WIT, JS-резолвер, валідатор), але не всі місця, де host читає контрибуцію.

Деталі — §2.86 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.118.0] - 2026-08-29

### Changed

- Бінарна родина `image-*` портована нативно — обидва концерни, які мажор `n-rules:plugin@4.0.0` розблокував, але не мав споживачами: `image-compress/check` і `image-avif/avif_generation`. Ключі додані в `NATIVE_FIXES`, тож `loadT0Patterns` тепер віддає для них рівно native-патерн; JS-канони лишаються на місці (політика «спершу парність»), але затінені.

**Байти пішли туди, де вони справді байти — і тільки туди.** `image-compress/check` бінарний наскрізь: єдина його дія — нові байти jpg/png/gif/webp/svg, і весь його план — `write-bytes`. `image-avif/avif_generation` **змішаний**, тож його план лишився текстовим на дві третини: згенеровані `.avif`-двійники їдуть `write-bytes`, rewrite raster-посилань у `.vue`/`.html` — звичайним текстовим `write`, прибирання `.avif`-сиріт — `delete`. Порядок значущий: байтові записи перші, видалення останні.

**Мовчазна вада спільного exec-tool хвоста — полагоджена.** Знімок «до» (`snapshot_before`) знімався `read_to_string`, тож не-UTF-8 файл давав `None` і до, і після прогону зовнішнього тула — а `None == None` означало «файл не змінився». Тобто тул, який реально переписав бінарний файл, не давав жодного edit-у, і конвеєр звітував «0 файлів» при змінених байтах на диску. Це нативний родич вади, яку §2.83 закрила на detect-боці (`String::from_utf8_lossy` калічив не-UTF-8 джерела), лише тихіший: там байти псувались, тут зникали безслідно. Знімок став байтовим, а форму edit-а тепер обирає сам вміст «після». Побічно з цього вийшла дедуплікація — `text/oxfmt` і `text/markdownlint` носили власні копії тих самих двох циклів, тепер усі шість exec-tool фіксів стоять на одному хвості.

**Чотири полагоджені дефекти канону, усі одного класу** — «варн у stdout і `{ touchedFiles: [] }`», що для рушія `--fix` нерозрізнюване від «усе вже гаразд»: відсутній `bunx` (image-compress), ненульовий код `--write`, відсутній `npx` (image-avif), ненульовий код `--avif` (там `console.log` навіть без `return` — фікс продовжував так, ніби генерація вдалася). Кожен тепер гучна помилка з install-підказкою або з кодом і stderr тула.

**П'ятий, змістовний: згенеровані `.avif` були невидимі.** Канон не клав у `touchedFiles` жодного згенерованого двійника — вони просто з'являлися на диску як side-effect спавна. Звіт казав «rewrote 3 file(s)», поки додалось ще п'ятнадцять бінарних файлів, яких не бачив ані журнал, ані collateral-veto, ані `git`-огляд. Тепер вони в плані.

Ще заблокованим лишається другий споживач планів — місток T0 петлі `fix` (`crates/rules-fix`): `llm_lib::edit_plan::EditPlan` текстовий наскрізь, тож `write-bytes` там і далі дає гучну типізовану відмову. Байтовий `FileEditPlan` написаний (гілка `feat/byte-file-edit-plan` у `git.7n.ai/7n/llm-lib`), але живе в окремому репозиторії і споживається тут лише після публікації нової версії крейта `n7n-llm-lib` — тобто після пушу тега, не мерджу.

Деталі — §2.85 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.117.1] - 2026-08-29

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.117.0] - 2026-08-29

### Changed

- Контракт wasm-плагінів бампнуто до `n-rules:plugin@4.0.0`. Це **перший мажор** цього контракту, і він ламає всіх пінованих гостей одномоментно: `check_world_version` звіряє major, тож плагін, зібраний під будь-яку `3.x`, цим хостом більше не завантажується. Усі шість first-party гостей, фікстура contract-test-kit і шаблон скіла `wasm-plugin` переведені тим самим комітом — проміжного стану «хост уже `4.0.0`, гість ще `3.2.0`» не існує.

Причина мажора виміряна, а не виведена (§2.83): Component Model не має width-subtyping, і кожна зміна ФОРМИ типу межі гість↔хост ламає інстанціацію вже пінованого гостя ще до `check_world_version` — `expected variant of 3 cases, found 2 cases` для нового case-а, `expected record of N fields, found N-1 fields` для нового поля; `option<...>` не рятує, бо звіряється арність запису. Оскільки мажор платиться один раз, він несе всі три накопичені зміни разом.

**`write-bytes` — бінарний вміст у fix-плані.** Новий case `variant file-edit`: `write-bytes-file { path, content: list<u8> }`. Доти `FixPlan` умів лише текст (`content` — WIT `string`), тож фіксер стиснутого зображення не мав чого туди покласти. У WIT байти їдуть `list<u8>` (одна копія в канонічному ABI), base64 з'являється рівно на межі napi→JS, де `Vec<u8>` виродився б у масив чисел. Побитий base64 у плані — гучна помилка, а не тихий запис обрізаного файлу.

**`fix-only-concerns` — концерн ЛИШЕ для фіксу.** Нове поле `manifest`: окремий список контрибуцій, для яких плагін дає `fix`, а детект лишається за чинним `main.mjs`/policy. Доти способу «лише fix» не існувало: будь-який ключ у `concerns` вмикає detect-шедоуїнг, тобто оголосити концерн заради фіксу означало **мовчки вимкнути його детект**. Ключ у обох списках одночасно — контрактна помилка: такий маніфест хост відхиляє на завантаженні, а не вгадує намір.

**`fix-glob` — окремий скоуп фіксу.** Нове поле `concern-contribution`. Доти `fix` годувався тим самим глобом, що й детект, тож вузький detect-glob беззвучно каструвався: план порожній, гейт `edits.length > 0` не пускає, і JS-канон тихо робить фікс удруге. Єдиним обхідним шляхом було розширити detect-скоуп заради fix-скоупу — вада, а не рішення. Порожній `fix-glob` означає «fix ділить скоуп із детектом», тож жодна чинна контрибуція поведінки не змінила.

Гейт «мінор лишається additive» не полагоджено оновленням фікстури — це викинуло б сам гейт. Він розділений на два: історичний (форма розійшлась із v3.0 рівно у трьох задекларованих типах, і major при цьому бампнуто — четверта тиха зміна валить тест) і новий additive-гейт відносно замороженої бази `4.0.0`. Бінарний стенд теж перевернуто: тепер він доводить, що гість попереднього мажора справді не вантажиться і що відмова пояснює невідповідність типів.

Мажор віддає поверхню, не споживачів: порт `image-compress/check`, `image-avif/avif_generation` і `js/eslint` — окремі задачі.

Деталі — §2.84 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.116.0] - 2026-08-29

### Changed

- Ще пʼять концернів `lang-js` тепер повністю обслуговує wasm-гість — і детект, і T0-фікс: `style/vscode_settings`, `js-run/jsconfig`, `js/jscpd_config`, `npm-module/emit_types_config` (усі чотири — rego через host-import `rego-engine`, тобто без спавну `conftest`) і `style/tooling` (детект переїхав раніше, тепер і фікс). Родина `vscode_*`/`zed_settings` закрита повністю: `style/vscode_settings` був її останнім незакритим членом.

`js/jscpd_config --fix` перестав псувати вже суворіший конфіг. Раніше будь-яке порушення концерну (напр. `gitignore` чи брак `reporters`) запускало deep-merge, який збивав `minLines` назад на мінімальний поріг `25` — навіть якщо там стояло `40`, яке сам детект вважає коректним. Тепер поріг трактується як мінімум: вище значення лишається, нижче — підтягується. Це та сама вада, що вже полагоджена для `@nitra/eslint-config` у `js/package_json`, лише числова.

`js-run/jsconfig --fix` тепер працює на `jsconfig.json` із коментарями. VS Code і TypeScript читають цей файл як JSONC, а канон читав його `JSON.parse` і на винятку мовчки нічого не робив: лінт червоний, `--fix` «спрацював», файл без змін.

`style/tooling --fix` тепер закриває порушення й тоді, коли `package.json` має `"stylelint"` не-обʼєктом (напр. рядком). Детект такий конфіг за наявний не вважає, а фікс виходив на будь-якому truthy-значенні — концерн не сходився ніколи. Бонусом `package.json` більше не переписується цілком: канонічне поле вставляється хірургічно, форматування й коментарі виживають.

`test/stryker_config` свідомо лишається на JS-фіксі: увесь його T0 тримається на повторному прогоні планувальника по всьому дереву, а fix-batch хост будує з файлів, названих діагностиками. Це обмеження host-мосту, не плагіна.

Виправлено два анти-дрейф-гейти, які лишались червоними після попередньої хвилі: очікувана кількість контрибуцій `plugin-lang-js` у `crates/rules-plugin-host/tests/plugin_lang_js.rs` і у `wasm-plugins.test.mjs` (40 → 50). Сортування violations у `runSingleFilePolicyBoth` замінено на «файл, потім повідомлення» — для багатофайлових (`walkGlob`) концернів однакового тексту deny-я на різних файлах достатньо, щоб парність збігалася випадково.

Parity-покриття: 13 нових тестів у `wasm-plugin-parity.test.mjs` — сім звіряють детект гостя з conftest-каноном біт-у-біт (включно з багатофайловим `walkGlob`), шість ганяють повний T0-цикл через РЕАЛЬНИЙ napi-міст.

Деталі — §2.80 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.115.1] - 2026-08-29

### Fixed

- wasm-parity гейт: холодний старт napi-мосту (dlopen аддона плюс перша компіляція wasm-компонента) винесено в beforeAll із власним таймаутом — перший тест більше не падає з Test timed out

## [1.115.0] - 2026-08-29

### Changed

- CI-«поодинокі» (розділ 4 плану міграції) — портовано **detect-половину двох** концернів: `ga/service_deploy_workflow` → `crates/plugin-ci-github`, `azure-pipelines/service_deploy_pipeline` → `crates/plugin-ci-azure`. Обидва до цього спавнили Go-бінарник `conftest`, тепер оцінюються host-імпортом `rego-engine` (один двигун на батч, `eval_rule` у циклі по файлах). Це перші walkGlob-концерни обох гостей і перші `scope: per-file` контрибуції в цих крейтах.

**T0-фікси обох концернів свідомо лишились у JS.** Вони кличуть `relevantDomains` (`ci-plan.mjs`) → `loadEnabledLintRules` + обхід піддерева сервісу на диску; гість не має ні реєстру ввімкнених правил (у контракті немає такого host-каналу), ні `fs_read`. Частковий порт був би ГІРШИМ за його відсутність: `guestFix` зупиняє `applyT0` на першому непорожньому плані гостя, тож фікс-заглушка з косметичними правками вимкнула б JS-канон, який створює `plan`-джобу й мігрує легасі-lint. `Guest::fix` цих ключів не реєструє; парність зафіксовано тестом «fix — порожній план» у кожному parity-файлі.

`ci_artifact/consume` НЕ портовано — це host-side інтегратор слот-механізму: читає graph плагінів (`resolveSlotGraph`/`getSlotContributions`), валідує чужі `ci.artifact@1` payload-и й вантажить canonical-шаблони з тек інших плагінів. WIT описує producer-бік слоту, не consumer-бік; порт інвертував би шар.

Полагоджено дефект хоста: `!`-патерн у глобі контрибуції мовчки не працював — `build_full_scope_files` (`crates/rules-napi`) віддавав його прямо в `globset`, де `!` — звичайний символ шляху, тож виключення не діяло й гість дістав би файли, які канон свідомо відсіює. Тепер `!`-префікс — окремий exclude-набір (латентний дефект: до цієї задачі жодна контрибуція `!` не декларувала). Побитий YAML у walkGlob-наборі дає видиму `policy-input-invalid`, а не мовчазний пропуск.

Заведено гейт, якого в цих крейтах не було: `vsi_vshyti_rego_polityky_evaliuiutsia_pid_regorus` реально ганяє regorus по КОЖНІЙ вшитій `.rego`-політиці (у `ci-github` — усі 15; попередній гейт брав пʼять) на її ВЛАСНОМУ каноні й вимагає нуль `deny`. Він вивів на окремий, ще не полагоджений дефект `ga/lint_ga` (файл без джоби `lint-ga` дає `rego-engine-error` замість канонічної deny) — деталі й підстави відкладення в §2.81.

Деталі — §2.81 `docs/plans/2026-08-05-open-questions-register.md`. JS-канони не видалено (політика «спершу парність»).

Розміри гостей: `plugin_ci_github.wasm` 1 624 940 → 1 644 634 Б; `plugin_ci_azure.wasm` 248 759 → 268 093 Б (стеля 10 MiB).
- Exec-tool пара `text/run-dotenv-linter` і `text/run-shellcheck` портована в ядро (`crates/rules-core`) — обидва ключі в `NATIVE_FIXES`, диспатч через `listNativeFixes()` → `nativeFixPattern`. JS-канони лишаються на диску (політика «спершу парність»), але `loadT0Patterns` їх більше не імпортує.

Разом із портом полагоджено чотири канали, у яких канон обчислював помилку й гасив її в «нічого не змінено». `patterns[0].apply` в обох концернах кличе `runDotenvLinter(cwd, false)` / `runShellcheckText(cwd, false)` БЕЗ присвоєння — обидві функції повертають `1` із чотирьох різних причин, і всі чотири оберталися на `{ touchedFiles: [] }`, нерозрізнюване від справді чистого дерева:

- **`dotenv-linter` відсутній у `PATH`** — вічний мовчазний no-op. Детектор при відсутньому тулі дає ту саму violation, що й «знайдено порушення», тож lint червоний → `--fix` «нічого не змінив» → lint знову червоний, і ніде ані слова, що тула просто немає. Тепер — помилка з install-підказкою.
- **Ненульовий код `dotenv-linter fix`** не читався взагалі (`try/catch` ловить лише невдалий спавн). Перевірено: `fix` віддає 0 і на нефіксабельних попередженнях, і на синтаксичному смітті у `.env` — ненульовий код означає справжній збій тула.
- **`shellcheck` або `patch` відсутні** — той самий мовчазний no-op, включно з окремою гілкою `patch`, яка мала власну install-підказку і власний відкинутий `return 1`.
- **Невдалий `patch -p1`** (конфлікт, зіпсований hunk, паралельна зміна файлу) губився так само. Тепер — помилка з іменем файлу і виводом `patch`.

Дрібніше: `listEnvFiles` більше не вичитує `node_modules` цілком, щоб потім викинути його фільтром; список `*.sh` для детекта й фікса тепер один (`list_shell_script_paths` у ядрі), а не дві копії; мертвий фінальний прогін `shellcheck` у fix-гілці (його код теж відкидався, edit-ів він не породжує) прибрано — залишковий стан перевіряє re-detect.

## [1.114.1] - 2026-08-29

### Fixed

- Кореневий `vitest.config.mjs` не мав ані env-ізоляції, ані піднятого `testTimeout` — обидва ключі жили ЛИШЕ в пакетному `npm/vitest.config.js`, який вантажиться тільки при `cd npm && vitest run`. Тобто репозиторний прогін (`bun run test` у корені, і CI) не отримував нічого з того, що пакетний конфіг документує як необхідне.

Заміряно через `resolveConfig` з `vitest/node`: кореневий давав `testTimeout = 5000` і `env = null`, пакетний — `20000` і обидві змінні.

Наслідки, не косметика:
- `GIT_TRACE2_EVENT=0` пакетний конфіг називає ROOT-CAUSE фіксом масових `Test timed out` (git-події в `af_unix`-сокет під `pool: 'forks'`) — репозиторний прогін, де git-важких тестів найбільше, його не мав;
- без `N_LLM_TRACE_PATH` КОЖЕН кореневий прогін дописував фейкові chain-записи (`probe/check`, `fake/min`, `fake/cloud`) у справжній `~/.n-cursor/llm-trace.jsonl`, засмічуючи аналітику myllm.

Це той самий клас вади, що `poolOptions.forks.maxForks` (2026-08-28): налаштування існує на папері й не застосовується там, де потрібне. Контракт `npm/tests/vitest-config-contract.test.mjs`, заведений САМЕ проти цього класу, перевіряв лише `maxWorkers` — і близнюків у тому ж файлі пропустив. Розширено на `testTimeout`, на env-канон і на порівняння спільних ключів обох конфігів ЦІЛИМИ обʼєктами, щоб наступний спільний ключ, доданий в один конфіг, не проскочив так само.

Підняття `testTimeout` НЕ замінює розігрів повільних wasm-фікстур: холодний старт wasmtime заміряно у 13-22 с, тобто вже за 20-секундною стелею — такі suite-и гріються у `beforeAll` окремо.

## [1.114.0] - 2026-08-29

### Changed

- Шість концернів `lang-js` — `js/vscode_extensions`, `style/vscode_extensions` і четвірка `package_json` (`js/`, `npm-module/npm_`, `npm-module/root_`, `style/`) — тепер повністю обслуговує wasm-гість: і детект (rego через host-import `rego-engine`, §2.66), і T0-фікс. Для споживача це означає, що ці перевірки більше не спавнять `conftest` як підпроцес.

`js/package_json --fix` перестав псувати вже коректний файл. Раніше будь-яке порушення концерну (напр. `"type"` чи `engines.node`) запускало deep-merge, який збивав `devDependencies["@nitra/eslint-config"]` назад на мінімальний поріг `^3.10.0` — навіть якщо там стояла новіша `^3.20.0`, яку сам детект вважає коректною. Тепер поріг трактується як мінімум: вища версія (і `workspace:`-протокол) лишається недоторканою, нижча — підтягується.

Гейт `npm/tests/rego-regorus-verbs.test.mjs` доповнено записом `plugin-lang-js` серед консюмерів `rules-rego-engine` і поясненням, що текстовий скан на `%q` — лише один із двох гейтів класу: другий (прогін кожної вшитої політики через двигун) живе в `cargo test -p plugin-lang-js` і саме він знайшов третю пастку `regorus` — безтілий факт `f("літерал")`.

Parity-покриття: 16 нових тестів у `wasm-plugin-parity.test.mjs` — вісім звіряють детект гостя з conftest-каноном біт-у-біт, вісім ганяють повний T0-цикл через РЕАЛЬНИЙ napi-міст (`runWasmConcern` → `runWasmConcernFix` → `applyPlanEdit` → повторний детект), включно з гейтом пастки §2.72 «глоб контрибуції годує й fix».

Деталі — §2.78 `docs/plans/2026-08-05-open-questions-register.md`.
- Гостьова трійка `vscode_extensions` (`php/vscode_extensions`, `python/vscode_extensions`, `rust/vscode_extensions`) портована у свої wasm-гості ЦІЛКОМ — і фікс (`vscode-ext-add`), і детект. Детект переїхав НЕ про запас: `detect.mjs::runConcernDetector` повністю заміняє policy-гілку, щойно концерн зʼявляється в `describe()`, тож порт самого лише фіксу мовчки вимкнув би перевірку. Rego виконується вшитий (`include_str!` того самого `.rego`, що читає conftest) через host-import `rego-engine` — у wasm-граф гостя `regorus` не входить.

Три дефекти JS-канону полагоджено, а не відтворені заради парності: (1) JSONC-вхід — `.vscode/extensions.json` із `//`-коментарем канон або валив (conftest, строгий JSON), або мовчки не фіксив (`JSON.parse` у `try/catch`); гість читає JSONC через спільний `rules-template-merge`; (2) справді побитий вміст цілі більше не тихий — видима діагностика `policy-input-invalid`; (3) не-обʼєктний корінь (`[...]`) більше не веде до запису, у якому `recommendations` тихо губиться.

Побічно: гейт `%q` (§2.76) розширено трьома плагінами — `regorus` не підтримує Go-верб `%q`, тож у трьох `.rego` він замінений на еквівалентний для рядків `\"%v\"` (текст повідомлення не змінився, `conftest verify` зелений).

Деталі — §2.77 `docs/plans/2026-08-05-open-questions-register.md`. JS-канони не видалено (політика «спершу парність»).

Тести: `cargo test -p plugin-lang-php` — 34 passed, `-p plugin-lang-python` — 80, `-p plugin-lang-rust` — 90 (по 11 нових на гостя); vitest — 172 passed у трьох parity-файлах (18 нових), `npm/tests/rego-regorus-verbs.test.mjs` — 14 passed; `cargo test -p rules-plugin-host` — 131 passed.
- Ядрові «поодинокі» (розділ 4 плану міграції) — портовано нативно два концерни з чотирьох: `tauri/updater` (`fix_tauri_updater.rs` — чотири T0-патерни `fix-updater.mjs`: package.json, Cargo.toml, lib.rs, capabilities) і `text/cspell` (`fix_cspell_config.rs` — merge-запис `.cspell.json` із вшитим `include_str!` каноном template). `tauri/updater` у стіну сусіда `tauri/release` не впирається: ні YAML, ні спавну процесу; детектор уже native, тож read-only хелпери перевикористані, а не задубльовані.

`doc-files/check` і `test/coverage` НЕ портовані свідомо — вони не T0-фікси взагалі, а fix-**воркери** LLM-драбини (`fix-worker.mjs`, без `fix-<concern>.mjs`). Ключ у `NATIVE_FIXES` створив би фіктивний T0-патерн і ЗАТІНИВ би єдиний робочий шлях. Для `doc-files/check` відсутність детермінованого фіксера — зафіксований інваріант: штамп свіжого CRC поверх старого тексту назавжди замаскував би дрейф доки.

Дефекти JS-канону полагоджено, а не відтворено заради парності: (1) `tauri/updater` НІКОЛИ не сходився, коли залежність сиділа в `devDependencies` — детектор читає її як ефективну, а фікс писав канон завжди в `dependencies`, тож порушення поверталось нескінченно; тепер канон пишеться в ту саму секцію; (2) побитий `capabilities/*.json` канон ковтав мовчки (`return false`) — тепер гучна помилка з іменем файлу; (3) JSONC-вхід (`//`-коментарі) в `.cspell.json` і capability-файлах канон читав `JSON.parse` і мовчки не фіксив нічого; (4) не-обʼєктний корінь `.cspell.json` — тепер явний no-op замість тихої втрати даних.

Деталі — §2.79 `docs/plans/2026-08-05-open-questions-register.md`. JS-канони не видалено (політика «спершу парність»).

Тести: `cargo test -p rules-core --lib concerns::fix` — 131 passed (19 нових); vitest — 11 passed у двох нових `fix-*-native.test.mjs` через продакшн-шлях napi.

## [1.113.0] - 2026-08-29

### Changed

- Родина `createTemplateFixPattern` ядрової колії портована нативно — пʼять концернів (`rego/vscode_settings`, `text/vscode_settings`, `worktree/vscode_settings`, `worktree/zed_settings`, `text/oxfmtrc`) одним конфіг-подібним шаром `TemplateFixCfg` поверх спільного крейта `rules-template-merge` (§2.71). Ядро — перший споживач його jsonc-only конфігурації: жодної YAML-залежності в графі `rules-core`.

Три дефекти JS-канону при цьому полагоджено, а не відтворено заради парності: (1) JSONC-вхід (`//`-коментарі, trailing кома у `.vscode/settings.json`) канон читав через `JSON.parse` і МОВЧКИ не фіксив нічого — тепер мерджиться, коментарі виживають через хірургічний splice; (2) не-обʼєктний корінь (`[1,2]`, `"текст"`) канон тихо перезаписував, знищуючи вміст — тепер файл не чіпається, а concern лишається червоним; (3) повна регенерація `JSON.stringify` більше не стирає форматування, коли доступний хірургічний шлях.

Деталі — §2.74 `docs/plans/2026-08-05-open-questions-register.md`. JS-канони не видалено (політика «спершу парність»).

Тести: `cargo test -p rules-core` — 1292 passed; `cargo test -p rules-template-merge` — 15 (jsonc-only) і 29 (`--features yaml`); vitest — 18 passed у пʼятьох нових `fix-*-native.test.mjs` через продакшн-шлях napi.

## [1.112.0] - 2026-08-29

### Added

- Анти-дрейф-гейт на Go-верб `%q` у rego-політиках, які виконує `regorus` (`npm/tests/rego-regorus-verbs.test.mjs`). `sprintf("%q", …)` легальний під `conftest`, але `regorus` його НЕ підтримує — політика падає HARD RUNTIME ERROR рівно в день, коли концерн переїжджає з субпроцесу на host-import `rego-engine`. Пастку ловили вручну ЧОТИРИ рази (§2.22, §2.66, §2.68, §2.69); гейт замінює ручний grep.

Скоуп гейта — ЯВНИЙ перелік `REGORUS_POLICY_PLUGINS` (`ci-github`, `ci-azure`, `lang-js`), а не глобальна заборона: під `conftest` верб цілком легальний, і політики ядра (`npm/rules/**`) та плагінів `lang-php`/`lang-python`/`lang-rust` мають повне право його вживати — глобальний гейт червонив би їх на рівному місці, і його б вимкнули.

Три властивості проти хибної впевненості: (1) кожен запис переліку мусить вказувати на існуючу теку з ≥1 `.rego`, інакше одруківка в імені перетворює гейт на «зелений» no-op; (2) будь-який крейт, що оголосив залежність `rules-rego-engine` і не значиться серед відомих консюмерів, валить гейт — наступний плагін (або `rules-core`), який перейде на `regorus`, не проскочить повз перелік мовчки; (3) рядки-коментарі зі скану виключено свідомо — `%q` у ci-github/ci-azure живе саме в доккоментах, що пояснюють цю саму заміну.

Тести: `npx vitest run npm/tests/rego-regorus-verbs.test.mjs` — 8 passed; негативний прогін (тимчасово повернутий `%q` в одну політику `lang-js`) валить гейт із точним `файл:рядок`. Деталі — §2.76 `docs/plans/2026-08-05-open-questions-register.md`.

### Changed

- Ядрова пʼятірка родини `vscode_extensions` (`doc-files`, `graphql`, `rego`, `tauri`, `text`) портована нативно (`crates/rules-core/src/concerns/fix_vscode_extensions.rs`, §2.75 `docs/plans/2026-08-05-open-questions-register.md`). Усі пʼять стояли на ОДНОМУ JS-рушії (`npm/scripts/lib/fix/vscode-ext-add.mjs`), тож і порт — один рушій плюс пʼять записів конфігурації з вшитим `include_str!` снапшотом свого концерну, а не пʼять реалізацій. JSONC-парс і серіалізацію бере спільний крейт `rules-template-merge` (§2.71, фіча `yaml` ядру не вмикається). JS-канони лишаються на диску за політикою «спершу парність», але `loadT0Patterns` для цих ключів повертає РІВНО native-патерн.

Полагоджено три дефекти канону (не відтворені заради парності):

- **`//`-коментарі в `.vscode/extensions.json`** — легальний для VS Code JSONC, який `JSON.parse` канону кидав → мовчазний no-op на цілком робочому файлі. Читання тепер через `parse_jsonc_document`; запис лишається повною регенерацією, тож форматування чесно втрачається, а ДАНІ — ні.
- **`graphql`/`tauri` не мали канонічного снапшота взагалі** — список розширень жив літералом лише в `.rego`, теки `template/` не існувало, тож фікс цих двох концернів ЗАВЖДИ повертав порожній результат: лінт світив порушення, `--fix` мовчки нічого не робив. Додано `template/extensions.json.snippet.json` обом — полагоджено в джерелі, тож працюють ОБИДВА шляхи (detect не змінюється: їхні `.rego` снапшот не читають).
- **Не-обʼєктний корінь цілі** — канон писав `parsed.recommendations` у масив і губив правку при `JSON.stringify`; тепер явний no-op.

Тести: `cargo test -p rules-core --lib vscode_extensions` — 13 passed; `cargo test -p rules-template-merge` (jsonc + `--features yaml`) — 15/29 passed; продакшн-шлях через реальний napi-міст — `npm/rules/{text,graphql}/vscode_extensions/tests/fix-vscode_extensions-native.test.mjs`, 10 passed.

## [1.111.0] - 2026-08-29

### Added

- wasm-фікс `rust/check` — ДРУГИЙ порт класу exec-tool fix у гостя (після `python/ruff`, §2.64 host-diff): `plugin-lang-rust` тепер сам виконує `cargo fmt --all` (edits синтезує host-diff зі знімка диска) і генерує `deny.toml` — `cargo deny init` там, де `cargo-deny` є, інакше детермінований мінімальний скаффолд декларативним edit-ом. Glob контрибуції `rust/check` розширено до канонічного (`**/*.rs`, `Cargo.toml`, `Cargo.lock`, `deny.toml`) — без цього host-diff не бачив би жодної fmt-мутації. Три мовчазні канали JS-канону стали гучними: нерезолвний `cargo`, ненульовий код `cargo fmt --all` і провал `cargo deny init` тепер логуються як помилка замість тихого no-op. JS-канон `fix-check.mjs` лишається на диску (парність, не заміна)
- Native fix ядра (T4, exec-tool): портовано T0-фіксер changelog/consistency у crates/rules-core — план створення change-файлу будує Rust (reuse change_file + канонічного resolve_auto_change_message детектора), git спавниться лише заради опису; полагоджено два дефекти JS-канону — мовчазний no-op при нерозпізнаній мітці воркспейсу тепер гучна помилка, а розбіжність між autoChangeMessage фіксера і resolveAutoChangeMessage детектора зникла. image-compress/check лишається JS (бінарний вміст не виражається String-only WriteFile::content), text/cspell-fix уже нативний як fix-воркер (crates/rules-fix) — деталі й пропозиція FileEdit::WriteBytes у §2.70 реєстру

### Changed

- Двигун template-merge винесено з `crates/plugin-ci-github` у спільний крейт `crates/rules-template-merge` — одне джерело істини семантики мержу для ОБОХ колiй міграції (нативної `rules-core` і wasm-гостей). Рефакторинг без зміни поведінки: 172 тести `plugin-ci-github` до = 143 + 29 після, розкладені за тим, що вони перевіряють.

Мотив — родина `vscode_*`/`zed_settings` розсипана між колiями: девʼять концернів у ядрі (`npm/rules/**`), шість у плагінах. Дублювався б не код, а семантика (`identity_key`, `contained_in`, порядок ключів, поведінка на масивах обʼєктів), і розходження копій було б ТИХИМ — обидві компілюються, обидві зелені, а `.vscode/settings.json` після ядрового й після плагінного концерну виходить різний.

Feature-гейт `jsonc` (default) / `yaml`: заміряно, що ядрова частина родини — виключно JSON/JSONC (`.vscode/extensions.json`, `.vscode/settings.json`, `.zed/settings.json`), тож `rules-core` братиме крейт БЕЗ `saphyr`. `is_yaml: bool` замінено на `Format`, чий варіант `Yaml` існує лише під фітом `yaml` — виклик YAML-шляху в jsonc-only збірці не компілюється, замість того щоб тихо повернути `None`.

Розмір `plugin_ci_github.wasm`: 1 602 445 → 1 624 940 Б (+1,4 %) — ціна crate-межі без workspace-LTO. Без per-package release-профілю для нового крейта приріст був би +190 398 Б (+11,9 %) на чистому переносі коду.
- Fix-бік wasm-мосту (`runWasmConcernFix`, `crates/rules-napi`) навчився будувати batch для `per-file`-концерну у ПОВНОМУ прогоні: `deltaFiles: undefined` — це заявлений `lint --full` (планувальник у цьому режимі списку не будує), тож хост робить той самий glob-обхід контрибуції, що `build_detect_batch_files` уже робить на detect-боці (§2.65). Доти exec-tool-фіксер `per-file`-концерну падав би `ambiguous_empty_fix_batch_err` при КОЖНОМУ `--full`, попри однозначно резолвний батч. Двозначність §2.52 не повернулась: `deltaFiles: []` (дельта є, але порожня) при непорожніх violations і далі падає гучно.

Це передумова порту T0-фіксера `style/lint` у wasm-гість `lang-js` — деталі й знайдений через нього дефект (`scope: full` на fix-боці ігнорує дельту запиту, тобто дельта-прогін ганяв би `stylelint --fix` по всьому репозиторію) — §2.73 `docs/plans/2026-08-05-open-questions-register.md`.

Тести: `cargo test -p rules-napi --lib run_wasm_concern_fix` — 9 passed (два нові: `per-file` з глобом і без дельти будує batch; порожня дельта і далі помилка). `cargo test -p rules-plugin-host` — 86 passed. `wasm-plugin-parity.test.mjs` — 276 passed.

## [1.110.0] - 2026-08-29

### Added

- ШОСТИЙ first-party wasm-гість — `crates/plugin-ci-azure` (`ci-azure/wasm-concerns`, `n-rules:plugin@3.2.0`), перший гість, заведений ПІСЛЯ підняття стелі розміру до 10 MiB і винесення `regorus` з wasm-гостя в хост (§2.55/§2.66): rego-двигун дістається БЕЗКОШТОВНО через host-import resource `rego-engine` з самого початку, без прямої `regorus`-залежності в `wasm32-wasip2` build graph.

Перша хвиля порту — два з десяти концернів `plugins/ci-azure/rules/`, представники ОБОХ форм: `azure-pipelines/lint_pipeline` (чистий rego-детект, без T0-фіксатора) і `azure-pipelines/vscode_extensions` (rego-детект + T0-фіксатор, той самий спільний рушій `vscode-ext-add.mjs`, що `ga/vscode_extensions` у `plugin-ci-github`). JS-канон усіх десяти концернів лишається недоторканим.

Знайдено й задокументовано нову пастку `regorus` (поверх `%q` з §2.66): built-in `walk()` потребує фіту `"graph"` у `rules-rego-engine` — без нього policy з `walk()` компілюється, але падає на еваluації з оманливою помилкою "unsafe variable", яка виглядає як баг Rego-безпеки, а насправді — відсутній builtin. Фікс — один рядок у спільному `crates/rules-rego-engine/Cargo.toml` (чисто адитивний фіт, безпечний для решти консюмерів).

Розмір гостя (release, `wasm32-wasip2`): порожній 63 266 Б → +174 735 Б (перший концерн, каркас) → +10 758 Б (другий концерн, майже безкоштовний reuse) = 248 759 Б підсумково — на порядок менше за `plugin-ci-github` (1 571 007 Б, 5 концернів).

Парність доведена через РЕАЛЬНИЙ napi-міст (критерій §2.47): `wasm-plugin-parity-ci-azure.test.mjs` — 7 passed. Golden-тест на `PluginHost` — `crates/rules-plugin-host/tests/plugin_ci_azure.rs` — 5 passed. Юніт-тести гостя — `cargo test -p plugin-ci-azure` — 16 passed. `cargo test -p rules-rego-engine` — 7 passed (5 наявних + 2 нових регресійних гейти на `walk()`/`graph.reachable` — доккомент crates/rules-rego-engine/src/lib.rs). `cargo test -p plugin-ci-github` — 156 passed (без регресій від спільного фіту). `conftest verify --policy plugins/ci-azure/rules` — 54 passed, 0 failures.

Деталі, вибір концернів, покроковий розмір, обидві rego-пастки — §2.68 `docs/plans/2026-08-05-open-questions-register.md`.
- `crates/plugin-ci-github` — родина `template-deep-merge` доперенесена цілком: усі дванадцять останніх JS-шимів (`abie/clean_merged_ignore_branches`, `docker/lint_docker_yml`, `ga/clean_ga_workflows`, `ga/clean_merged_branch`, `ga/git_ai`, `ga/lint_ga`, `ga/lint_repo_yml`, `ga/zizmor_yml`, `k8s/lint_k8s_yml`, `npm-module/npm_publish_yml`, `style/lint_style_yml`, `text/lint_text`) тепер мають wasm-детект (regorus для десяти, структурний `checkSnippet`-порт для двох без `.rego`) і wasm-фікс (той самий `fix_template_merge`, що третя хвиля); guest — 1 602 445 Б (15,3% стелі 10 MiB). Попутно виправлено `%q` (regorus не підтримує Go-verb) у шести `.rego`-файлах і знайдено реальний баг канону — незахищений `job.steps[_].uses` у `lint_ga.rego`/`lint_text.rego` валив увесь `job_uses_set` на кроці без `uses`.

### Fixed

- `--full` більше не бреше про чистоту `per-file` wasm-концернів. `run_wasm_concern` (`crates/rules-napi/src/lib.rs`) будував batch за задекларованим glob-ом контрибуції ЛИШЕ для `scope: full`, а `per-file`-концерн у full-прогоні (`files: None` — саме те, що передає планувальник для КОЖНОГО концерну в `--full`) діставав ПОРОЖНІЙ batch: гість бачив нуль файлів, повертав нуль діагностик, концерн звітував «чисто» — мовчки, без помилки чи попередження. Так у `--full` не перевірялись дев'ять контрибуцій у чотирьох гостях: `vue/tfm-translations`, `js/doc_comments`, `python/doc_comments`, `python/mypy`, `python/ruff`, `rust/doc_comments`, `rust/wasm_component`, `php/mago_fmt`, `php/mago_lint`.

Тепер хост будує batch за glob-ом НЕЗАЛЕЖНО від `scope` (`build_detect_batch_files`): для детекту це безпечно — він read-only, а `--full` для `per-file`-концерну означає рівно «перевір кожен файл під його glob-ом», те саме, що завжди робив JS-канон. Асиметрія з fix-контуром лишається свідомою: там glob-обхід виправляв би файли поза дельтою запиту, тому дельту й далі несе окремий аргумент `delta_files`. Стан, у якому batch побудувати НЕМОЖЛИВО (концерн не заявлений у `describe().concerns` або `per-file` без жодного glob-патерну), тепер падає типізованою помилкою з поясненням і підказкою — дзеркало `ambiguous_empty_fix_batch_err` на detect-боці; порожній glob у `scope: full` лишається легітимним заявленим наміром (`js/jscpd_duplicates`).

Разом із цим `python/mypy`/`python/ruff` додали до glob-а контрибуції `pyproject.toml`, а `php/mago_fmt`/`php/mago_lint` — `composer.json`: у full-прогоні «якорі» (`concern.json.lint.anchors`) до batch-у не потрапляють — їх знає лише JS-планувальник дельти, а WIT-контрибуція поля `anchors` не має, тож без цього кроку ті чотири детектори й далі мовчки виходили б у `Skip`, лише вже з непорожнім batch-ем.

Тести: `cargo test -p rules-napi` — 21 passed (було 13, +8), новий `wasm-detect-full-per-file.test.mjs` — 7 passed, по одному представнику з кожного з п'яти гостей крізь РЕАЛЬНИЙ napi-міст. Червоність доведено дією ДО фіксу: 3 з 4 нових napi-тестів і 6 із 7 bridge-тестів падали саме мовчазним `{"violations": []}`. Parity-гейти всіх п'яти гостей — 452 passed, детект/фікс-контур — 131 passed, без регресій.

Деталі — §2.65 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.109.0] - 2026-08-28

### Added

- Native fix ядра (T3, без wasm): портовано ще 3 T0-фіксери в crates/rules-core — text/oxfmt і text/markdownlint (exec-tool клас: oxfmt --write / npx markdownlint-cli2 --fix, spawn + before/after diff у тій самій Rust-функції, що будує FixPlan) та nginx-default-tpl/template (структурний, reuse геть детектора); JS-канон (fix-oxfmt.mjs/fix-markdownlint.mjs/fix-template.mjs) лишається на диску як фолбек, диспетчер (NATIVE_FIXES) тепер завжди бере нативний шлях

### Changed

- `regorus`-рушій (rego-оцінка `ga/workflows`/трьох policy-концернів `plugin-ci-github`) винесено з wasm-гостя в хост — гість більше не несе власну копію `regorus` у своєму component-артефакті, а кличе підготовлений двигун через НОВИЙ imported resource `rego-engine` (`n-rules:plugin@3.1.0` → `3.2.0`, `crates/rules-contract/wit/world.wit`). ПЕРШИЙ imported resource контракту цього репозиторію — доведено спершу мінімальним прикладом (`wit-bindgen` 0.60 + `wasmtime` 48), потім портовано на реальний rego-виклик.

Спільна тонка обгортка над `regorus::Engine` (add-policy/add-data-json/eval-rule) винесена в новий крейт `crates/rules-rego-engine` — ОДНЕ джерело істини для rego-семантики, яку споживають ДВОМА шляхами під `cfg`: `crates/rules-plugin-host` реалізує host-import resource для wasm32 (продакшн), а `crates/plugin-ci-github` під `cfg(not(target_arch = "wasm32"))` кличе той самий крейт in-process (нативні `cargo test`) — так 53 наявні нативні тести найризикованішої частини порту (YAML→JSON + rego-виконання) лишились ПОВНІСТЮ покритими, попри перенесення продакшн-шляху за component-межу. Семантику rego тепер пінує пін `regorus` у ХОСТІ (`0.11`, `["regex","std","arc"]` — `"arc"` додано понад пін гостя, бо `wasmtime::component::ResourceTable::push` вимагає `Send`, а `regorus::Engine` за замовчуванням тримає джерела в не-`Send` `Rc`), не пін плагіна — нова спільна поверхня сумісності, задокументована у версійному блоці WIT.

Розмір гостя (release, `wasm32-wasip2`): 2 700 455 → 1 571 007 Б (−1 129 448 Б, −41,8%). Батч-шлях (`ga/workflows`, `workflow_common`: підготувати двигун РАЗ, оцінити на N файлах) НЕ просів — виміряно 50 прогонів `detect(ga/workflows)` на 15 реальних workflow-файлах цього репозиторію через РЕАЛЬНИЙ napi-міст: 18,321 мс/прогін (до) → 15,847 мс/прогін (після), у межах шуму заміру, без регресії.

Тести: `cargo test -p plugin-ci-github` — 156 passed (без змін очікувань). `cargo test -p rules-plugin-host` — 126 passed (+ host-реалізація `HostRegoEngine`). `cargo test -p rules-rego-engine` — 5 passed (новий крейт). `cargo test -p rules-contract` — 63 passed (версійна константа world звірена з `3.2.0`). Bridge-тест `wasm-plugin-parity-ci-github.test.mjs` — 31 passed (без змін). `conftest verify -p plugins/ci-github/rules/` — 125 passed, 0 failures (rego-джерела не чіпались).

Деталі, доведення imported-resource-механізму, і числа — §2.66 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.108.0] - 2026-08-28

### Added

- exec-tool fix клас (host-diff): портовано python/ruff — перший фіксер, що мутує диск напряму через exec-tool (uv run --frozen ruff check --fix . + ruff format .); хост сам знімає знімок диска до/після виклику fix() і синтезує edits із різниці (runWasmConcernFix, crates/rules-napi/src/lib.rs), без зміни WIT

## [1.107.4] - 2026-08-28

### Fixed

- `plugin-ci-github` (`try_surgical_merge`) закрив останню виміряну межу хірургічного comment-preserving merge — вставка ВСЕРЕДИНУ однорядкового YAML flow-контейнера (`on: {push: {branches: [main]}}`, `branches: [main]`). Раніше `next_line_start` не мав де шукати `\n` у flow-контейнері, `surgical_merge_object`/`surgical_merge_array` поверталися в `None`, а через all-or-nothing обхід `surgical_merge_node` ОДНА нездійсненна flow-вставка будь-де в дереві валила ВЕСЬ документ на повну регенерацію — коментарі губились усі, не лише ті, що біля flow-вузла (§2.61/§2.62 виміряли це як «anchor/alias і flow-стиль непідтримні», що виявилось ширшим за факт: anchor/alias уже працював, справжня межа — рівно flow-nested-insert).

Новий `is_flow_container`/`flow_insert_point`/`write_yaml_flow_value` розпізнає flow-контейнер (перший байт спану — `{`/`[`) і вставляє новий вміст inline, безпосередньо перед закриваючим `}`/`]` (байтовий офсет якого `MarkedYamlOwned`-спан дає точно — на відміну від наївного припущення, `Span.end` дорівнює офсету самого закриваючого байта, не одному після нього), з комою-роздільником лише коли потрібно (порожній контейнер і наявна trailing-кома — окремі, перевірені випадки, друга кома не дописується). Жодної нової залежності (сім крейтів-кандидатів оцінено й відхилено раніше, §2.61/§2.62) — увесь код перевикористовує наявний `yaml_key`/`yaml_scalar` double-quote контракт.

Обовʼязковий post-generation guard (`try_surgical_merge` перепарсює результат і звіряє `is_subset` перед поверненням) не чіпався — валідність виводу так само не підлягає компромісу, як і раніше.

Тести: `cargo test -p plugin-ci-github` — 156 passed (було 148, +8: шість нових flow-фікстур і дві регресійні — anchor/alias без flow, чистий block-стиль). Доведена червоність усіх шести нових flow-тестів ДО фіксу (`git stash` продакшн-коду) — `try_surgical_merge` повертав `None`. Незалежно — новий bridge-тест крізь РЕАЛЬНИЙ `runWasmConcernFix` napi-міст (`wasm-plugin-parity-ci-github.test.mjs`, 31 passed, було 30) на production-канонічному snippet-і, теж доведено червоним на незібраному .wasm ДО фіксу (весь документ ішов у повну регенерацію, файловий коментар зникав). Розмір гостя: 2 698 833 → 2 700 455 Б (+1 622 Б, ~0,06% бази) — на порядки менше за будь-який попередній крок цього крейта і за обидва відхилені крейти-кандидати (§2.61: `yamlpatch` +665 933; §2.62: `yaml-edit`/`yaml-rt-core` +168 152/+367 517).

Деталі, повний список фікстур і аналіз пастки зі спаном flow-контейнера — §2.63 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.107.3] - 2026-08-28

### Changed

- `wasmtime` і `wasmtime-wasi` підняті з 47.0 до 48.0 — передумова переходу гостей на `wasm32-wasip3` (WASIp3-хост живе у `wasmtime-wasi` за feature-гейтом `p3`, який і в 47, і в 48 однаково позначений самим апстрімом як експериментальний, нестабільний і неповний; ця зміна його ще НЕ вмикає — лише піднімає базу). Одна ламка API: `wasmtime-wasi` 48 звів колишню пару `DirPerms`/`FilePerms` в один enum `FsPerms`, і `preopened_dir` став трипараметричним. Обидва прапорці в `PluginHost::new` і так виставлялись у `READ` разом — окремий rw-стан для теки й для файлу сенсу не мав, — тож заміна на `FsPerms::ReadOnly` семантику не змінює: плагін і далі читає preopen і не мутує його (рішення Е спеки). Тести `cargo test -p rules-plugin-host` — 126 passed, 1 ignored, без регресій.

## [1.107.2] - 2026-08-28

### Fixed

- `plugin-ci-github` (`fix_template_merge`, `fix_vscode_extensions`, `detect_policy`) отримав СПРАВЖНЮ JSONC comment-preserving підтримку для `.vscode/*.json`-таргетів — заміна floor-валідатора `is_strict_json` (§2.58), який раніше свідомо відхиляв БУДЬ-ЯКИЙ JSONC-вхід («не строгий JSON → не чіпаємо»), новим крейтом `jsonc-parser` (dprint, MIT, бездепний без явних фітів). `.vscode/settings.json`/`.vscode/extensions.json` з `//`/`/* */`-коментарями чи trailing-комою (продакшн-VS-Code-конвенція пише саме такий файл) тепер РЕАЛЬНО мерджиться — коментарі виживають байт-у-байт через ТУ САМУ хірургічну машинерію (`MNode`/`Edit`/`apply_edits`), що вже давала YAML-гілці comment-preserving merge (§2.56/§2.58) — не другий рушій: анотоване дерево `jsonc_parser::ast::Value` (байтові `Range` на кожному вузлі, коментарі НЕ входять у діапазон жодного з них — той самий «trivia»-контракт) конвертується в той самий `MNode`, що `saphyr::MarkedYamlOwned` для YAML.

Нова пастка, знайдена й закрита під час розробки: `is_strict_json` (§2.58) повністю відхиляла trailing-кому, тож `surgical_merge_object`/`surgical_merge_array` ніколи не бачили JSON-вхід з наявною trailing-комою ПЕРЕД точкою вставки — їхня безумовна синтетична кома була безпечна лише тому, що вхід ніколи не мав власної там. Реальна JSONC-підтримка прибирає цю гарантію (подвійна кома — синтаксично невалідний JSON, хоч і спіймана post-generation guard'ом § 2.58, що падає на безпечну, але не хірургічну повну регенерацію) — новий `already_has_trailing_comma` розпізнає наявну кому й НЕ дописує другу.

`fix_vscode_extensions` (простіший union-merge рушій, точний порт канонічного `vscode-ext-add.mjs`, який САМ завжди повністю регенерує вивід) отримав лише апгрейд ЧИТАННЯ — JSONC-вхід тепер реально мерджиться (дані не втрачені), але запис лишається повною регенерацією (коментарі не переживають запис, той самий контракт, що канон) — свідома, задокументована межа, не тиха.

Тести: `cargo test -p plugin-ci-github` — 148 passed (було 140). `wasm-plugin-parity-ci-github.test.mjs` — 30 passed. Доведена червоність нових fixture-тестів проти pre-fix коду (тимчасовий `git worktree`) — 7 з 8 нових поведінкових тестів падали (порожній `plan.edits`/`index out of bounds`), 8-й (plain-JSON регресія) вже проходив, як і очікувалось для guard-тесту. Розмір гостя: 2 658 330 → 2 698 833 Б (+40 503, ~0,39% стелі 10 MiB).

Деталі, повний список фікстур, доведена червоність і аналіз пастки з trailing-комою — §2.59 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.107.1] - 2026-08-28

### Fixed

- `plugin-ci-github` (`fix_template_merge`, `security/lint_security_yml` і `ga/vscode_settings`) прибрав єдину свідому розбіжність із каноном, яку залишила третя хвиля порту (§2.56): T0-фікс YAML/JSON-таргетів більше НЕ регенерує весь файл із нуля через `write_yaml_block`/`json_to_pretty_string` — новий хірургічний шлях (`try_surgical_merge`, `saphyr::MarkedYamlOwned`-спани на кожному вузлі наявного дерева) вставляє чи замінює лише байтові діапазони, яких реально бракує; недоторкані рядки лишаються байт-у-байт оригіналом — коментарі, порожні рядки, стиль лапок і відступи наявного файлу зберігаються, той самий контракт, що `yaml`-пакет JS-канону (`Document.setIn`/`addIn`, `npm/scripts/lib/fix/template-deep-merge.mjs`). Обсяг розширено самим власником репозиторію під час сесії: постановка просила лише YAML-гілку («JSON-гілку не чіпай»), але власник відповів, що JSON-регенерація — та сама вада канону, яку варто полагодити заразом, не дзеркалити. Коли хірургічний шлях недосяжний для конкретного дерева (порожній контейнер без запису для прив'язки вставки, тип вузла не збігається зі snippet-ом, чи для JSON — точка вставки вийшла б за межі власного `}`/`]`) — падає на старий шлях повної регенерації, завжди коректний (повторний детект чистий), не завжди comment-preserving — чесний fallback, не тихий. Знайдена й виправлена під час розробки пастка: block-стиль YAML не має явного закриваючого токена, тож `Span.end` контейнера, що є ОСТАННІМ реальним вмістом документа, резолвиться в EOF (за трейлінг-коментарем), не в кінець власного вмісту — `deepest_last_leaf_end` (рекурсивний спуск до останнього скалярного листка) обходить цю розбіжність.

Незалежне ревʼю знайшло реальний баг у першій версії: фікстура з КІЛЬКОМА одночасними вставками на різних рівнях глибини давала синтаксично невалідний YAML (кілька різних вставок структурно «дном впиралися» в ту саму найглибшу позицію документа, застосовувались у зворотному порядку). Виправлено двома незалежними шарами: (1) `apply_edits` тепер застосовує вставки з однаковим офсетом у порядку, що відповідає їхній фактичній вкладеності (найглибша — найближче до якоря); (2) `try_surgical_merge` ДОДАТКОВО повторно парсить результат і звіряє його зі snippet-ом ПЕРЕД тим, як повернути — будь-яка невідповідність (синтаксична чи семантична) падає на стару повну регенерацію, а не віддає непідтверджений вивід («валідність виводу не підлягає компромісу» — рішення власника). Перевірено трьома незалежними шляхами (Rust-юніт-тест, JS bridge-тест крізь РЕАЛЬНИЙ napi-міст з парсингом РЕАЛЬНИМ `yaml`-пакетом канону, і ручний standalone-репро) на фікстурі ревʼювера.

Той самий ширший прогін фікстур знайшов і полагодив ще одну ваду — вже НЕ з цього PR, а таку, що вже була на `main` (третя хвиля порту, #527): **втрата даних** на `.vscode/settings.json`/`.vscode/extensions.json` із JSONC-коментарями (продакшн-VS-Code-конвенція пише саме такий файл). `parse_yaml_document` — YAML 1.2-парсер, для якого `//` НЕ коментар: `// коментар\n"editor.formatOnSave": true` читався як один багаторядковий ключ, і РЕАЛЬНЕ налаштування користувача тихо зникало з файлу при фіксі — регрес проти JS-канону (`JSON.parse` там кидає на JSONC і файл не чіпається). Новий `is_strict_json` — ручний RFC 8259-валідатор (без побудови значення, лише bool) — гейтить усі три місця, де JSON-таргет читається толерантним YAML-парсером (`fix_template_merge`, `fix_vscode_extensions`, `detect_policy`): не строгий JSON тепер трактується РІВНО як побитий синтаксис — файл не чіпається, як у канону (floor, не спроба JSONC comment-preserving мержу — та мета лишається нереалізованою, рішення власника: коректність важливіша).

Розмір гостя 2 619 303 → 2 658 330 Б (+39 027, ~0,38% стелі 10 MiB, §2.55). Тести (кожен — точний очікуваний вміст або пряма перевірка валідності/is_subset, не лише «detect чистий»): YAML-фікстура з двома коментарями (усередині блоку, який merge зачіпає, і одразу перед точкою вставки), JSON-фікстура з нестандартним 4-пробільним відступом і незвичним порядком ключів, регрес-фікстура з кількома одночасними вставками на різних рівнях глибини (Rust + JS bridge), і JSONC-фікстури (провідний і хвостовий `//`-коментар, обидва JSON-таргети, Rust + JS bridge).

Деталі, доведена червоність нових тестів, обидва знайдені й виправлені баги, і де автор не згоден з постановкою — §2.58 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.107.0] - 2026-08-28

### Changed

- `plugin-ci-github` (wasm-гість `ci-github/wasm-concerns`) отримав третю хвилю порту — три policy-концерни (`ga/vscode_extensions`, `ga/vscode_settings`, `security/lint_security_yml`), кожен detect+fix, доказ, що обидва спільні JS T0-fix-рушені (`npm/scripts/lib/fix/vscode-ext-add.mjs` і `npm/scripts/lib/fix/template-deep-merge.mjs`, разом шимлять 15 концернів цього гостя) масштабуються на решту концернів без нової інфраструктури. `template-deep-merge`-концерни обрано як представники двох форм таргету (JSON — `ga/vscode_settings`, YAML — `security/lint_security_yml`), не як зручні. Detect-логіка всіх трьох — той самий IN-PROCESS `regorus`-шлях, що друга хвиля (`ga/workflows`): власний `.rego`-пакет на концерн, вшитий `include_str!`. Дві нові deep-subset/deep-merge Rust-функції ([`is_subset`]/[`merge_json_value`]) — точний порт `checkSnippet`/`mergeJsonValue` (`npm/scripts/lib/template.mjs`); два нові серіалізатори (`write_json_pretty` — pretty JSON, `write_yaml_block` — block-стиль YAML, СВІДОМО не comment-preserving). JS-реалізації не видалено — guestFix-пріоритет диспетчера (`T0Pattern.guestFix`) веде фікс крізь wasm автоматично. Розмір гостя 2 604 774 → 2 619 303 Б (+14 529, 62,4% стелі 4 MiB) — вимірювалось поетапно: перший порт (заводить JSON-інфраструктуру) коштував 6 091 Б, кожен наступний концерн на вже злінкованій інфраструктурі — 3 880–4 558 Б. Парність доведено крізь реальний napi-міст (`runWasmConcern` → `runWasmConcernFix` → застосування правок → повторний детект чистий), не прямим викликом гостя. Побічний фікс: три `.rego`-джерела (`vscode_extensions.rego`/`vscode_settings.rego`/`lint_security_yml.rego`) отримали ту саму заміну `%q` → `\"%v\"`, що друга хвиля застосувала до пʼяти `ga/workflows`-namespace-ів — regorus відкидає `%q` як runtime-помилку (byte-ідентично conftest-виводу для звичайних рядків, підтверджено `conftest verify` 12/12 без регресій).

Деталі, числа й відкриті питання — §2.56 `docs/plans/2026-08-05-open-questions-register.md`.

## [1.106.1] - 2026-08-28

### Changed

- Стелю розміру wasm-компонента піднято з 4 MiB до 10 MiB (10 485 760 Б). 4 MiB (попередня зміна) вже розблоковували перенесення родини `template-deep-merge`/`vscode-ext-add` у `plugin-ci-github` — 15 концернів, виміряні ~24 КБ на перший порт у гостя без JSON-інфраструктури, — але не лишали запасу на подальші хвилі того ж гостя. При 10 MiB найбільший гість (`plugin-ci-github`, 2 604 774 Б) займає ~24,8%, тобто стеля свідомо перестає бути робочим бюджетом і лишається backstop-ом від втечі розміру. Рішення прийнято усвідомлено, після того як зʼясувалось, що стеля стримувала не зростання гостей, а саму міграцію: число 2,5 MiB було оцінкою під ОДНОГО гостя (AST-крейт `plugin-lang-js`), а вперся в нього `plugin-ci-github`, який `oxc_parser` не містить узагалі, і під ним немає жодного зовнішнього обмеження (рантайм розмір компонента не обмежує, size-гейта в CI немає, npm-пакет стелею не обмежений). Відкрите питання §2.55 — окремі бюджети на гостя замість плоскої стелі — від цієї зміни стає гострішим, а не мʼякшим: розкид гостей тепер 1,4%…24,8%, і плоский backstop не ловить повзучого росту жодного з них.

## [1.106.0] - 2026-08-28

### Added

- `plugin-lang-js` (wasm-гість): портовано T0-фіксер `bun/layout` (`fix_bun_layout`, `crates/plugin-lang-js/src/lib.rs`) — точний семантичний порт трьох патернів видаленого `plugins/lang-js/rules/bun/layout/fix-layout.mjs`: `rm-forbidden-file` (видаляє заборонені lock/конфіг-файли чужих пакет-менеджерів, ім'я читається з `diagnostic.message`), `bun-bunfig-create` (створює канонічний `bunfig.toml`, лише коли його ще немає), `bun-yarn-dir-remove` (видаляє директорію `.yarn` цілком — `FileEdit::Delete` на КАТАЛОГ, перший реальний споживач цієї здатності в цьому крейті).

`bun/layout` — `scope: full`, WHOLE-BATCH-концерн (жодна діагностика не несе `file`), тож фікс іде штатною full-scope-гілкою `run_wasm_concern_fix` (`crates/rules-napi/src/lib.rs`), не через `delta_files`-фолбек.

Спершу знятий характеризаційний гейт на теперішній JS-поведінці (`plugins/lang-js/rules/bun/layout/tests/fix-layout.test.mjs`, 10 тестів, раніше цей фіксер не мав жодного тесту), потім порт, потім парність доведена через РЕАЛЬНИЙ napi-міст (`loadNative().runWasmConcern` → `runWasmConcernFix` → `applyPlanEdit` → повторний `runWasmConcern` чистий) — 4 нові сценарії в `npm/scripts/lib/lint-surface/tests/wasm-plugin-parity.test.mjs`, включно з `.yarn/`-каталогом із вкладеним вмістом. Прямий виклик гостя цим свідомо не замінено (§2.47/§2.49 open-questions-register): саме тестування через міст виявило, що локально зібраний `rules-napi`-аддон під час розробки підмінявся застарілим прекомпільованим `@7n/rules-darwin-arm64`-підпакетом (порожній `FixPlan` без жодної помилки) — не баг контракту чи гостя, а стандартний pin-drift дев-середовища; локальний `cargo build -p rules-napi --release` вирішив.

JS-фіксер (`fix-layout.mjs`) НЕ видалено — диспетчер `run-fix.mjs` (`T0Pattern.guestFix`) автоматично пріоритезує гостя.

Розмір гостя: 2 261 653 → 2 262 509 байт (+856, 86,3% бюджету 2 621 440). Тести: `cargo test -p plugin-lang-js` 401/401 (+11), `cargo test -p rules-napi` 13/13 без регресій, `wasm-plugin-parity.test.mjs` 264/264 без регресій, `fix-layout.test.mjs` 10/10.

## [1.105.1] - 2026-08-28

### Changed

- Стелю розміру wasm-компонента піднято з 2,5 MiB (2 621 440 Б) до 4 MiB (4 194 304 Б), а сам гейт зведено в один спільний модуль `npm/scripts/lib/lint-surface/tests/wasm-size-budget.mjs` замість пʼяти незалежних копій константи в parity-тестах кожного гостя. Причина: 2,5 MiB — не зовнішнє обмеження, а ОЦІНКА зі спайка `docs/specs/2026-08-01-wasm-ast-strategy.md` («549 KB парсер + запас під glue, бізнес-логіку пʼяти concern-ів і розширення»), порахована під одного-єдиного гостя — AST-крейт `plugin-lang-js`. Далі те саме число механічно скопіювали в гейти решти чотирьох, композиція яких із тим розрахунком не повʼязана. Уперся в стелю саме `plugin-ci-github` (2 604 774 Б, 99,4% — запасу 16 666 Б, тобто наступний порт туди не влазить), гість, що взагалі не містить `oxc_parser`, під який рахували стелю. Зовнішнього ліміту під цим числом немає: рантайм розмір компонента не обмежує, окремого size-гейта в `.github/workflows` немає, npm-пакет стелею не обмежений. Заразом уніфіковано розбіжність операторів (гейт `ci-github` перевіряв `toBeLessThan`, решта чотири — `toBeLessThanOrEqual`) і прибрано захардкоджений текст «2,5 MiB» із назв тестів — підпис тепер походить від самої константи. Гейт лишається кусючим: перевірено тимчасовим зниженням стелі до 2 MiB — `ci-github` падає `expected 2604774 to be less than or equal to 2097152`. Записано межу цього гейта: єдина плоска стеля на пʼятьох гостей із розкидом 5,6%…99,4% ловить різкий стрибок, але не повзучий ріст гостя з великим запасом (`plugin-lang-php` може вирости в 20 разів і лишитись зеленим) — окремі бюджети на гостя винесено відкритим питанням у реєстр.

## [1.105.0] - 2026-08-28

### Added

- `plugin-lang-js` (wasm-гість): портовано T0-фіксер `js-run/runtime` — `js-run-jsconfig-create` (`fix-runtime.mjs`, єдиний патерн концерну, чий детект уже цілком портований). Точний семантичний порт: для кожної діагностики «є каталог `src/`, але немає `jsconfig.json`» витягує workspace анхореним парсингом (`jsconfig_missing_ws`, точний порт `JSCONFIG_MISSING_WS_RE`) і пише канонічний `<ws>/jsconfig.json`, якщо файл ще відсутній у батчі. Канон (`js-run/jsconfig/template/jsconfig.json.snippet.json`) вшито через `include_str!` з анти-дрейф-тестом (той самий прецедент, що `KNIP_CANONICAL_JSON`). `js-run/runtime` — другий whole-batch концерн у репо (жодна діагностика не несе `file`), тому парність доведено через РЕАЛЬНИЙ napi-міст (`runWasmConcern` → `runWasmConcernFix`), а не прямий виклик гостя — новий describe у `wasm-plugin-parity.test.mjs`. Попередньо знято характеризаційний JS-гейт для `fix-runtime.mjs` (`plugins/lang-js/rules/js-run/runtime/tests/fix-runtime.test.mjs`, 11 тестів) — файл ніколи не мав тестів. Заразом виправлено застарілу тезу доккомента «Зріз 7» про те, що фіксер — текстовий патч `package.json#imports`: файл уже спрощений до одного FS-патерну задовго до цього порту. JS-фіксер свідомо НЕ видалено — спершу парність; диспетчер `run-fix.mjs` (`T0Pattern.guestFix`) автоматично пріоритезує гостя без жодної зміни коду. Розмір гостя: 2 261 653 → 2 262 700 байт (+1 047, 0.04% бюджету 2 621 440, запасу лишилось 358 740 байт). Тести: `cargo test -p plugin-lang-js` 396 → 405 (+9), `wasm-plugin-parity.test.mjs` 260 → 263 (+3), новий `fix-runtime.test.mjs` 11/11.

## [1.104.3] - 2026-08-28

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.104.2] - 2026-08-28

### Fixed

- JS-міст fix-плану (`npm/scripts/lib/lint-surface/run-fix.mjs::nativeFixPattern`/
`wasmFixPattern`) мовчки не застосовував `delete` на директорію — гучний
no-op, той самий клас вади, що #513/#517.

Rust-бік (`crates/rules-fix/src/t0.rs::to_edit_plan`) уже вміє видаляти
`Delete`-теку: розгортає її в пофайлові `Delete` всього піддерева й після
успішного commit прибирає спорожнілі теки знизу вгору (`sweep_empty_dirs`).
JS-бік цього не мав — обидва майже однакові цикли застосування плану
(`nativeFixPattern.apply`/`wasmFixPattern.apply`) робили голий `unlink(abs)`
на `delete`-правку в `try { ... } catch { /* best-effort */ }`. Дві вади:
`unlink` на директорію падає (EPERM на macOS, EISDIR на Linux) — гість, що
емітить `delete(".firebase")`/`delete(".yarn")` для концерну без
full-scope-обходу, лишався зеленим і безрезультатним; і голий `catch {}`
ковтав БУДЬ-яку помилку (права доступу, зайнятий файл, збій ФС), не лише
«шлях уже відсутній». Емпірично: старий код на `delete(".firebase")` кидав
`EPERM: operation not permitted, unlink`, ковтав його мовчки, директорія
лишалась на диску, `touchedFiles: []`.

Обидва цикли злиті у спільний `applyPlanEdit(edit, cwd, ctx)`. `delete` тепер
іде через `fs.rm(abs, {recursive:true})` — один виклик прибирає і файл, і
директорію рекурсивно, з тим самим кінцевим станом, що й Rust-міст (там
розгортання по файлах потрібне лише для журналу editLog, якого тут немає).
Ловиться ЛИШЕ `ENOENT` (шлях уже відсутній — задокументований best-effort,
той самий дух, що коментар `to_edit_plan` про "Delete на відсутній файл").
Будь-яка ІНША помилка більше не ковтається — кидається `Error` (з `cause`),
що зупиняє fix-прогін гучно замість тихого «0 файлів змінено» (`applyT0`
кличе `p.apply()` без обгортки try/catch — виняток піднімається аж до
`runFixPipeline`).

Заразом виправлено застарілий доккомент
`crates/rules-core/src/concerns/fix_abie_security.rs` (описував старий
`t0.rs::apply_plan`, якого вже нема, — стверджував, що виконавець «наразі
вміє лише `std::fs::remove_file`», хоча `to_edit_plan` вже розгортає
Delete-теки).

Нові тести `npm/scripts/lib/lint-surface/tests/run-fix.test.mjs`
(`applyPlanEdit — ...`): `delete` на директорію з вкладеними файлами прибирає
все піддерево; `delete` на відсутній шлях — `null`, без throw; `delete`, що
впирається у не-ENOENT помилку (ENOTDIR через шлях-крізь-файл, детерміновано
без залежності від прав root/CI) — кидає `Error`. Усі три падали до фіксу
(перевірено емпірично: тимчасовий відкат `run-fix.mjs` на `unlink` дав
`applyPlanEdit is not a function` для всіх трьох — функція взагалі не
існувала в старому коді).

## [1.104.1] - 2026-08-28

### Added

- `runWasmConcernFix` (`crates/rules-napi/src/lib.rs`) приймає шостий,
опційний аргумент `delta_files` — явний список файлів запиту (той самий, що
йде в `runWasmConcern` на детекті). Він знімає стан, який #517 зробив
гучною помилкою: `per-file`-концерн із АГРЕГОВАНИМИ діагностиками (одна на
весь прогін зовнішнього тула, який не атрибутує вивід по файлах — живий
приклад `python/ruff`) не давав хосту з чого побудувати `FixRequest::files`,
бо жодна діагностика не несла `file`.

Це рішення (б) розвилки, яку #517 лишив у WIT-доккоменті: не забороняти
`file: none` для `per-file` (варіант (а)), а визнати, що дельта — властивість
ЗАПИТУ, а не діагностики. Варіант (а) змусив би кожен агрегований детектор
парсити вивід тула й синтезувати per-file діагностики заради host-механіки,
якій відповідність «діагностика → файл» не потрібна: фіксер такого концерну
хоче список таргетів (`ruff check --fix <targets>`), а не мапу.

Розширювати контракт не довелось — `fix-request.files` існує з `3.0.0` як
окреме поле, не похідне від `diagnostics`; бракувало лише проводки
host → napi. Жодного ABI-руху й version bump: guest-и не чіпані, `.wasm`-пін
не змінюється.

Порядок джерел `FixRequest::files`: `diagnostics[].file` (найвужчий батч,
пріоритет незмінний) → full-scope glob-обхід для `scope: full` → явна дельта
запиту → типізована помилка. Помилка #517 лишається backstop-ом на проводку
(мовчазний порожній батч тут був би регресом до #513); порожня дельта
(`Some([])`) за відповідь НЕ приймається — це той самий нерозрізненний стан,
що й відсутня.

JS-бік (`npm/scripts/lib/lint-surface/run-fix.mjs`): `loadT0Patterns` бере
пʼятим аргументом дельту й вшиває її у wasm-патерн — з тієї ж причини, що й
`cwd`: `T0Pattern.test(violations)` не приймає `ctx`, а план будується вже в
`test()` і кешується під identity `violations`. Викликачі, які дельту не
передають, поведінки не міняють (`undefined` лишає хосту full-scope гілку).

Побічний наслідок: «Перешкода 1» з розбору порту `python/ruff` (PR #516)
цією зміною знята — порт лишається відкладеним тільки через межу exec-tool
у fix-контурі.

### Fixed

- Ліміт паралелізму vitest більше не мовчазна фікція: обидва конфіги (кореневий `vitest.config.mjs` і пакетний `npm/vitest.config.js`) переведено з `poolOptions: { forks: { maxForks: 4 } }` на верхньорівневий `maxWorkers: 4`. У Vitest 4 секцію `poolOptions` ВИДАЛЕНО, а невідомий ключ конфігу не є помилкою — його мовчки ігнорують, друкуючи в stderr лише рядок `DEPRECATED`. Тобто ліміт, доданий саме щоб приборкати кількість `node`-процесів при паралельній роботі кількох агентів/worktree, не діяв узагалі. Заміряно на 12 тестових файлах / 10 ядрах: зі старим ключем — 9 одночасних форків (пул брав ядра), з `maxWorkers: 4` — рівно 4. Щоб наступне перейменування опції не пройшло так само тихо, додано гейт `npm/tests/vitest-config-contract.test.mjs`: він розвʼязує обидва конфіги тим самим `resolveConfig` із `vitest/node`, що й сам раннер, і перевіряє РЕЗУЛЬТУЮЧЕ значення (`maxWorkers === 4`, `pool === 'forks'`), а не текст конфігу. Червоність гейта доведено: з поверненим `poolOptions` він падає `expected undefined to be 4`.

## [1.104.0] - 2026-08-28

### Added

- `plugin-ci-github` (wasm-гість): портовано другий T0-фіксер — `rust/toolchain_cache` (перша хвиля гостя, детект `scan_toolchain_steps` уже жив у гості). Точний семантичний порт двох чисто текстових T0-патернів `fix-toolchain_cache.mjs`: `insertRustCache` (вставляє `Swatinem/rust-cache@v2` одразу після `dtolnay/rust-toolchain@…`-кроку без cache-кроку в тому самому job-і) і `addCacheWorkspaces` (дописує `with.workspaces` у Tauri-job-ах, де `Cargo.toml` не в корені репо). Сканувальний рушій детектора (`scan_toolchain_steps`/`ToolchainStepScan`) перевикористано напряму — розширено трьома полями (`line`/`dash_col`/`cache_line`), які потрібні фіксеру для координат вставки, але не потрібні детекту; жодного другого скана не написано. `fix_toolchain_cache` компонує обидва трансформери послідовно на одному буфері, той самий мотив, що `fix_workflows` (#512). Жодного нового регекс-патерну не знадобилось — усі чотири текстові патерни цього концерну вже портовані вручну на detect-боці ще до #512. JS-фіксер (`fix-toolchain_cache.mjs`) свідомо НЕ видалено — спершу парність; диспетчер `run-fix.mjs` (T0Pattern.guestFix) автоматично пріоритезує гостя без жодної зміни коду. Бюджет мірявся поетапно (одна трансформація → зібрати → зміряти, як і вимагала постановка): крок 1 (`insert_rust_cache`) — +8 436 байт, крок 2 (`add_cache_workspaces`, дешевший — переюзав уже злінковану `Vec<String>`/`splice`/`format!`-машинерію кроку 1) — +5 896 байт. Розмір гостя: 2 590 442 → 2 604 774 байт (+14 332, 99.4% бюджету 2 621 440, запасу лишилось 16 666 байт). Тести: cargo `plugin-ci-github` 87 → 98 (+11, включно з двома незалежними round-trip-тестами й негативними тестами для обох kind-ів), `wasm-plugin-parity-ci-github.test.mjs` + `fix-toolchain_cache.test.mjs` разом 28/28 без змін, `conftest verify` по `plugins/ci-github/rules/ga/` — 88/88 без регресій, `.rego` не торкнуто.

### Fixed

- `runWasmConcernFix` (`crates/rules-napi/src/lib.rs`) — продовження фіксу #513
для НЕ-full-scope концернів. #513 додав full-scope glob-обхід, коли жодна
violation не несе `file` і концерн задекларований `scope: full` (напр.
`js/check`) — але лишив вужчу гілку `_ => Vec::new()` для решти випадків:
концерн з `scope: per-file` (чи взагалі не знайдений у `describe()`), чиї
РЕАЛЬНІ (непорожні) violations теж не несуть `file`, і далі тихо отримував
порожній `FixRequest::files` — той самий мовчазний батч, що спричинив #513,
лише для іншого класу концернів. Живий кандидат: `python/ruff`
(`crates/plugin-lang-python/src/lib.rs::detect_ruff`) — `scope: per-file`,
але одна агрегована діагностика на ВЕСЬ прогін `ruff check`/`ruff format
--check`, без `file`; порт реального фіксера цього концерну сьогодні впирався
б у мовчазну порожню поведінку (наразі — сумісна заглушка, `fix()` повертає
порожній план, тож нікого не зупиняло на практиці).

WIT-контракт (`crates/rules-contract/wit/world.wit`, `record diagnostic` /
`record fix-request`) МОВЧИТЬ про цей випадок — не забороняє `file: none` для
`per-file`-концерну (агрегована діагностика — легітимний `detect()`-результат)
і не визначає, як host має будувати `fix-request.files`, коли жодна
діагностика його не назвала. Це НЕ підтверджена помилка контракту концерну
(схема дозволяє `file: none`) і НЕ підтверджений безпечний legit-кейс:
full-scope glob-обхід тут небезпечний — для `per-file`-концерну він розширив
би fix ЗА МЕЖІ дельти, порушивши сам per-file-контракт. Тож замість вгадувати
(мовчки чи full-scope), `run_wasm_concern_fix` тепер падає з типізованою
помилкою (`ambiguous_empty_fix_batch_err`) — називає концерн, причину (жодна
діагностика не несе `file`) і що зробити (концерн має класти `file`, або WIT
потребує явного розширення під агреговані per-file fix-и — не вирішено тут,
задокументовано відкритим питанням у самому `world.wit`). Порожні
`diagnostics` (нема violations — нема що фіксити) лишаються `Vec::new()` без
помилки — двозначність лише коли violations є, а `file` немає в жодній.

JS-бік (`npm/scripts/lib/lint-surface/run-fix.mjs::computeWasmFixPlan`) ловить
цю помилку (той самий graceful-degrade контур, що вже несе `loadT0Patterns`
для битого `fix-<concern>.mjs` і `runConcernDetector` для wasm-падіння під час
`detect()`): `console.error` з причиною, деградація до порожнього плану —
concern далі йде в ladder, падіння ОДНОГО concern-а не вбиває весь fix-прогін
(`runWasmConcernFix` кличеться в циклі по концернах).

Регресія: три робочі шляхи не зачеплені — file-scoped фіксери
(`test/no-bun-test-import`, `js/doc_comments` — несуть `diagnostic.file`,
гілка `target_files.is_empty()` для них узагалі не виконується) і full-scope
`js/check` (`scope: full` і далі йде `build_full_scope_files`, як і раніше).
Нові тести `crates/rules-napi/src/lib.rs` (4, проти реальної guest-фікстури
`crates/test-plugin-guest`, нова `scope: full` fix-фікстура
`test/guest-fix-full-scope`) + наявні `wasm-fix-e2e.test.mjs` (5/5) і
`wasm-plugin-parity.test.mjs` (260/260, без регресій) підтверджують усі три
шляхи разом з новою помилкою.

## [1.103.2] - 2026-08-28

### Changed

- test(wasm-plugin-parity): закрити прогалину покриття fix-мосту для чотирьох файл-скоуп T0-фіксерів (`rust/cargo_mutants_config`, `rust/doc_comments`, `python/doc_comments`, `ga/workflows`) — §2.49 `docs/plans/2026-08-05-open-questions-register.md`. §2.46/§2.48 самі задокументували межу свого покриття: parity-гейти rust/python/ci-github тестували лише `detect`-парність, а fix-цикл замикався через `fix-<concern>.mjs` (транзитивний JS-канон) НАПРЯМУ — реальний `Guest::fix` цих чотирьох концернів (усі поза `NATIVE_FIXES`, у проді диспетчеризуються виключно через `wasmFixPattern` → `runWasmConcernFix`, `crates/rules-napi`) жоден тест через сам napi-міст не проганяв. Той самий клас прогалини, що §2.47 (`js/check`) уже закрила для lang-js і там-таки знайшла реальний баг у мості (затирання конфігів для whole-batch концернів).

Додано 4 нові сценарії (усі — повний цикл `runWasmConcern` → `runWasmConcernFix` → застосування едитів → повторний `runWasmConcern` чистий) у `wasm-plugin-parity-rust.test.mjs` (2), `wasm-plugin-parity-python.test.mjs` (1), `wasm-plugin-parity-ci-github.test.mjs` (1, перший fix-цикл цього файлу взагалі). Кожен сценарій доведено здатним падати — тимчасовий злам відповідної guest-фікс-функції (`fix_doc_comments`/`fix_cargo_mutants_config`/`prefix_bunx_n_command`) дав червоний тест, повернення — знову зелений. Додатково емпірично підтверджено, що жоден із чотирьох НЕ whole-batch (на відміну від `js/check`): тимчасове видалення full-scope fallback `run_wasm_concern_fix` (та сама мутація, що відтворює баг §2.47) валить РІВНО ті самі 2 тести `js/check`, усі 4 нові сценарії лишаються зеленими (400/402 по чотирьох parity-файлах разом).

Whole-batch-аудит: серед шести портованих T0-фіксерів `js/check` лишається ЄДИНИМ, чиї діагностики ніколи не несуть `diagnostic.file`. Знайдено кандидатів НА РИЗИК для майбутніх портів (fix ще не портований, детект уже без `file`): `rust/check` (усі діагностики через `plain_violation`) і файл-less-гілки самого `ga/workflows` (`simple_fail`/`push_tool_unavailable`) — задокументовано в реєстрі як застереження для наступного порту.

Продакшн-код не змінено (усі red-проби відкочено, `git diff` по `crates/` порожній).

## [1.103.1] - 2026-08-27

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.103.0] - 2026-08-27

### Added

- `plugin-ci-github` (wasm-гість): портовано T0-фіксер `ga/workflows` — перший реальний `fn fix` цього гостя (раніше — заглушка з порожнім планом). Точний семантичний порт трьох чисто текстових T0-патернів `fix-workflows.mjs`: `addPersistCredentials` (дописує `with: persist-credentials: false` у `actions/checkout`-кроки), `removePathsGlobs` (прибирає незматчені `on.*.paths` glob-и) і `prefixBunxNCursor` (`n-rules …` → `bunx n-rules …`). На відміну від JS-канону (три окремі `T0Pattern`, застосовуються послідовно з перечитуванням файла з диска між ними), `wasmFixPattern` синтезує РІВНО один патерн на весь wasm-концерн — `fix_workflows` тому сам компонує всі три трансформери на одному буфері за один виклик. `persist-credentials`/`bare-n-rules` — вручну (`strip_prefix`/`indent_of`, той самий стиль, що `rust/toolchain_cache`), `bare-n-rules`-префікс — через вже лінкований `regex`-крейт (той самий `\b`-патерн, що детект-бік `verify_no_bare_n_cursor`, жодної нової залежності). JS-фіксер свідомо НЕ видалено — спершу парність; диспетчер `run-fix.mjs` (T0Pattern.guestFix, #509) автоматично пріоритезує гостя без жодної зміни коду. Розмір гостя: 2 574 836 → 2 590 442 байт (+15 606, 98.8% бюджету 2 621 440). Тести: cargo 87 (+22), wasm-plugin-parity-ci-github 24, conftest verify 55/55 по пʼятьох policy-теках без регресій.
- `plugin-lang-js` (wasm-гість): портовано T0-фіксер `js/check` (`fix-check.mjs`) — усі три патерни (`js-check-eslint-config`, `js-check-oxlintrc`, `js-check-knip`), а не лише один. Обрано саме цей концерн через ПІДТВЕРДЖЕНЕ дублювання: `planOxlintrcFix` structurally дзеркалить `verifyOxlintRcAgainstCanonical`, вже портовану раніше, — фіксер і verify тепер поділяють ту саму трійку спецключів (`rules`/`ignorePatterns`/`jsPlugins`) в одному крейті. Канон `knip-canonical.json` вшито через `include_str!` (прецедент PR #508, `oxlint-canonical.json` вже був вшитий раніше) — 765 байт, анти-дрейф-тест байт-у-байт проти файлу-джерела. Розмір `.wasm` виріс на ~11 КБ (2 250 284 → 2 261 653 байт), запас до бюджету 2,5 МіБ — ~351 КБ.

Побічний фікс (виявлений тестуванням через реальний napi-міст, не юніт-тестами гостя): `runWasmConcernFix` (`crates/rules-napi`) будував `FixRequest::files` ЛИШЕ з `diagnostic.file`-полів — для whole-batch концернів на кшталт `js/check`, чиї діагностики НІКОЛИ не несуть `file` (стан цілого дерева, не одного файлу), це давало ЗАВЖДИ порожній батч: фіксер писав би наосліп, затираючи наявні `eslint.config.js`/`.oxlintrc.json` дефолтним/канонічним вмістом замість хірургічного merge. Додано full-scope fallback (той самий резолв, що вже робить `run_wasm_concern` для детекту) — активується лише коли жодна діагностика не назвала конкретний файл, тож file-scoped фіксери (`test/no-bun-test-import`, `js/doc_comments`, `rust/cargo_mutants_config`) поведінки не міняють.

JS-фіксер (`fix-check.mjs`) свідомо НЕ видалено — спершу парність (доказ: `wasm-plugin-parity.test.mjs`, новий блок «js/check T0-фікс», плюс гість-only `detect → fix → detect` раунд-трип у `crates/plugin-lang-js/src/lib.rs`), зняття подвійної реалізації — окрема хвиля.

## [1.102.0] - 2026-08-27

### Added

- `plugin-lang-rust`/`plugin-lang-python` (wasm-гості): портовано T0-фіксер `doc_comments` в ОБИДВА гості (третя й четверта ітерація вже відпрацьованого патерна — перша `js/doc_comments`, друга `rust/cargo_mutants_config`). `rust/doc_comments` підвищує суцільний `//`-блок до `///`/`//!` через LINE-based `data` (без UTF-16-конверсій еталона). `python/doc_comments` перетворює `#`-блок на docstring, обробляючи кілька `def`/`class` в одному файлі знизу вгору (спадання `headerEnd`), щоб вставки/видалення не зсували індекси. Обидва крейти без `serde_json` — `data` розбирається мінімальними рядковими helper-ами. JS-фіксери (`fix-doc_comments.mjs`) лишаються чинним каноном (зняття подвійної реалізації — окрема хвиля); диспетчер `run-fix.mjs` автоматично пріоритезує гостя без жодної зміни коду.

## [1.101.1] - 2026-08-27

### Changed

- sync-claude-config: детект-only PostToolUse lint-хук більше не вставляється в `.claude/settings.json` і `.codex/hooks.json` (холодний старт npx + cspell на кожен Edit/Write/apply_patch); наявні managed-записи в обох файлах активно прибираються при ресинку, а не лишаються стейлими

### Fixed

- T0-dispatch (loadT0Patterns/applyT0): гість (native/wasm) і fix-<concern>.mjs більше не застосовуються двічі до того самого concern-а — applyT0 зупиняється, щойно guestFix-патерн реально торкнув файли; битий fix-<concern>.mjs тепер сигналить console.error замість мовчазного пропуску

## [1.101.0] - 2026-08-27

### Added

- `plugin-lang-rust` (wasm-гість): портовано перший T0-фіксер — `rust/cargo_mutants_config`. `Guest::fix` більше не заглушка для цього concern-а: canonical baseline `.cargo/mutants.toml` вшито `include_str!` (той самий файл, що читає `fix-cargo_mutants_config.mjs`, прецедент — вшиті `.rego` `plugin-ci-github`), з анти-дрейф-тестом, що звіряє вшитий вміст із канонічним джерелом незалежним шляхом. JS-фіксер лишається чинним каноном (зняття подвійної реалізації — окрема хвиля)

## [1.100.18] - 2026-08-27

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.100.17] - 2026-08-27

### Fixed

- `mirror-parity.mjs`: `findMirrorDrift`/`listManagedMirrors` більше не плутають «дзеркало справді зовнішнє» з «канон мав би бути в задекларованому, але незарезолвленому плагіні» (напр. свіжий worktree без `bun install`) — раніше другий випадок мовчки звужував обсяг перевірки до підмножини й репортив це як «дрейфу нема»; тепер кидає помилку з переліком дзеркал і плагінів, не приховуючи дрейф серед резолвлених дзеркал

## [1.100.16] - 2026-08-27

### Changed

- `engines.bun` піднято до `>=1.4` (репо фактично вимагає 1.4); тести й фікстури `wasm-plugin-parity`/`js/check` вирівняні з новим порогом `js.package_json`

## [1.100.15] - 2026-08-27

### Fixed

- Ізольовано чотири тестові файли (graphql/tooling, check-rule-fixtures, check-empty-trees, hasura-native-parity) від Bun-розбіжності process.env ⇄ native-аддон: symlink node_modules/@7n/rules замість N_RULES_PACKAGE_ROOT у JS

## [1.100.14] - 2026-08-26

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.100.13] - 2026-08-26

### Fixed

- detect.mjs: помилка «немає main.mjs» тепер підказує про відсутній npm/wasm-plugins/builtin-pins.json, коли причина невідома

## [1.100.12] - 2026-08-26

### Fixed

- parity-гейт `rules-cli lint`: реальний дельта-прогін більше не падає на однобічній нотатці делегації (`ℹ️ … делегую в JS-CLI.`) — stderr звіряється byte-exact МІНУС рівно один такий рядок, причина якого мусить бути в списку задокументованих (недокументована причина, другий рядок чи будь-який інший вивід валять тест), а САМЕ native-виконання на реальному репо гейтить новий scoped-прогін `lint adr` (native-концерн + два policy-концерни одним батчем мосту), який мусить пройти БЕЗ делегації

## [1.100.11] - 2026-08-26

### Fixed

- `k8s/manifests` більше не спрацьовує хибно на YAML-фрагментах без `apiVersion`/`kind` (шматки маніфестів для вставки) і на GitHub Actions workflow, що лежать під шляхом із сегментом `k8s` — обидва класи раніше вимагали локального обходу через `.n-rules.json` `ignore` у кожному споживчому репо

## [1.100.10] - 2026-08-26

### Fixed

- Реєстр §2.31/§2.32 (`docs/plans/2026-08-05-open-questions-register.md`): `npm/tests/integration-repo-checks.test.mjs` тепер узгоджений з живим деревом на всіх десяти `check*`. `checkGa` резолвив `toolPaths` вручну (лише `git`+`shellcheck`, попри чотири задекларованих тули `plugin-ci-github`) — замінено на продакшн-шлях `resolveWasmConcernMap`, той самий, що справжній `n-rules lint`. Три тестові файли (`npm/rules/graphql/tooling/tests/tooling.test.mjs`, `npm/tests/check-empty-trees.test.mjs`, `npm/tests/check-rule-fixtures.test.mjs`) плюс сам інтеграційний тест читали `process.env.N_RULES_PACKAGE_ROOT` напряму замість `env` з `node:process` (js-run.mdc) — виправлено. Немає зміни поведінки опублікованого пакета: усі торкнуті файли — внутрішні тести пакета `@7n/rules`.
- lang-python/lang-rust (plugin-lang-python, plugin-lang-rust): мовчазний fail-open замінено видимою діагностикою у чотирьох каналах — `python/mypy`+`python/ruff` (тул недоступний у uv-середовищі → `mypy-unavailable`/`ruff-unavailable`), `python/project` (availability `pip-licenses` → `pip-licenses-unavailable`; вивід `pip-licenses` не парситься попри успішний спавн → `pip-licenses-parse-error`, знайдено понад перелік вихідного аудиту) і `rust/check` (`cargo deny --version` non-zero → `cargo-deny-unavailable`). Раніше всі чотири канали давали зелений лінт без виконаної перевірки
- fix(tests): «22 правила ядра» рахує теки з main.mdc, а осиротілі теки в npm/rules/ падають окремим гардом

## [1.100.9] - 2026-08-26

### Changed

- Закрито два з чотирьох «1:1-портів» реєстру §2.27 (`docs/plans/2026-08-05-open-questions-register.md`, §2.30): `sample_secret` і `hasura_migrations` тепер консультуються з `.n-rules.json:ignore` (обгортки `walk_repo`/`walk_under_repo` замість прямого `walk_dir_raw`). **Поведінкова зміна**: приклад-файли з bare-`secret` під виключеною текою і `down.sql` під виключеною підтекою `hasura/migrations/` більше НЕ дають violation. `k8s_manifests_kubescape` лишено без змін — ignore застосований рівнем вище, при виборі k8s-коренів (`find_k8s_roots`), повторна фільтрація була б no-op.

## [1.100.8] - 2026-08-26

### Fixed

- Реанімовано чотири групи безумовно-пропущених vitest-наборів (§2.31): k8s/hasura_*, k8s/dremio_logging та php/mago_lint тепер гейтяться на ensureToolAsync (PATH → кеш → GitHub Releases) замість голого PATH-скану, тож реально виконуються в CI; test.yml збирає plugin-lang-js/-php/-ci-github wasm-компоненти й генерує npm/wasm-plugins/builtin-pins.json для lang-js, що також оживило wasm-plugin-e2e/wasm-fix-e2e/wasm-builtin-pins і поправило застарілий список концернів lang-js. integration-repo-checks.test.mjs більше не мовчить безумовним describe.skip — набір реально впав на трьох живих проблемах репозиторію (graphql/tooling, k8s/manifests, js-run/runtime), кожна тепер видима в одному зведеному expect замість прихованою за early-abort.

## [1.100.7] - 2026-08-26

### Fixed

- ga/workflows (plugin-ci-github): мовчазний fail-open замінено видимою діагностикою у двох каналах — regorus (compile/set_input/eval → `rego-engine-error`) і зовнішні тули (`actionlint`/`zizmor` не запустились → `*-unavailable`). Раніше обидва канали давали зелений лінт без виконаної перевірки; для `zizmor` це означало мовчазний пропуск security-скану workflow-ів

## [1.100.6] - 2026-08-26

### Fixed

- `rust/cargo_mutants_config` (`resolve_all_cargo_manifests`, `crates/plugin-lang-rust`) і спільна JS-утиліта `resolveAllCargoManifests`/`resolveCargoManifest` (`npm/scripts/utils/resolve-cargo-manifest.mjs`, споживач — T0-фіксер `fix-cargo_mutants_config.mjs`) тепер СИМЕТРИЧНО розкривають `*`/`**`-glob у `workspaces`-записах `package.json` замість трактувати їх як літеральний сегмент шляху — типовий `"workspaces": ["packages/*"]` більше не пропускає перевірку `.cargo/mutants.toml` для кожного пакета монорепо, і фіксер тепер здатен закрити ту саму діагностику, яку видає детектор. Латентний баг консервувався байт-у-байт заради парності з JS-детектором `rust/cargo_mutants_config/main.mjs`, якого вже видалено разом із транзитивним періодом `lang-rust`; сама утиліта-джерело бага пережила видалення детектора і мала той самий дефект окремо (§2.28)

## [1.100.5] - 2026-08-25

### Changed

- Уніфіковано поверхню обходу дерева: `rules_core::scan::walk_dir` перейменовано на `walk_dir_raw` (примітив без consumer-ignore), додано обгортки `cursor_ignore::walk_repo`/`walk_under_repo`/`walk_with_ignore_paths` — рефакторинг без зміни поведінки, закриває клас дефекту з §2.25/§2.26 (незворотні консюмер-ignore на `walk_dir(cwd, &[])`)

## [1.100.4] - 2026-08-25

### Fixed

- `resolve_per_file_scope` (резолв файлового скоупу per-file концерну в петлі `fix`) тепер читає `.n-rules.json`/`.n-cursor.json` `ignore` перед обходом дерева — автофікс більше не отримує права редагувати файли з директорій, які консюмер явно виключив (напр. `npm/schemas/vendor`)

## [1.100.3] - 2026-08-25

### Fixed

- `build_full_scope_files` (napi-хост full-scope wasm-концернів) тепер читає `.n-rules.json`/`.n-cursor.json` `ignore` перед обходом дерева — консюмер-специфічні виключення (напр. `npm/schemas/vendor`) більше не потрапляють у batch full-scope wasm-концернів усіх пʼятьох гостей

## [1.100.2] - 2026-08-25

### Fixed

- KNOWN_PLUGIN_RANGES: піни 0.x-плагінів підняті до фактично випущених ліній (lang-js ^0.27, lang-python ^0.14, lang-rust ^0.17, lang-php ^0.4) — автодетект плагінів більше не резолвить застарілі minor-лінії

## [1.100.1] - 2026-08-25

### Changed

- wasm-parity: гейт ci-github (rust/toolchain_cache, ga/workflows) звіряється зі знятим еталоном JS-канону (N_WASM_PARITY_CAPTURE), не з живим main.mjs; parity-заглушку в npm/tests/integration-repo-checks.test.mjs переведено на runWasmConcern

## [1.100.0] - 2026-08-25

### Added

- plugin-ci-github: концерн ga/workflows у нативному порті — 851 рядок Rego виконується IN-PROCESS через regorus (Microsoft), без субпроцесу conftest; плюс git ls-files, actionlint, zizmor і проба shellcheck через exec-tool. JS-канон лишається під parity-гейтом

## [1.99.0] - 2026-08-24

### Added

- plugin-ci-github: пʼятий first-party wasm-гість (перший не-lang) — концерн rust/toolchain_cache у нативному порті, 97 КБ без жодної залежності понад wit-bindgen. JS-канон лишається під parity-гейтом

## [1.98.1] - 2026-08-24

### Changed

- wasm-parity: гейт lang-php звіряється зі знятим еталоном JS-канону (N_WASM_PARITY_CAPTURE), не з живим main.mjs; додано host-тести plugin_lang_php.rs на канал «mago нерезольваний → звичайне порушення, не крах»

## [1.98.0] - 2026-08-24

### Added

- plugin-lang-php: четвертий first-party wasm-гість — усі п'ять php-концернів (tooling, composer_manifest, project, mago_fmt, mago_lint) у нативному порті; перший гість, що декларує pinned-тул (mago через ensure-tool контур). JS-канон лишається під parity-гейтом

## [1.97.1] - 2026-08-24

### Changed

- wasm-parity: гейт lang-rust звіряється зі знятим еталоном JS-канону (N_WASM_PARITY_CAPTURE), не з живим main.mjs; додано замикання T0-циклів (гість → JS-фіксер → гість) для doc_comments і cargo_mutants_config

## [1.97.0] - 2026-08-24

### Added

- plugin-lang-rust: решта трьох концернів у нативному порті — rust/check (exec-tool ланцюг cargo fmt/clippy/deny), rust/cargo_mutants_config, rust/wasm_component; шість із шести, JS-канон лишається під parity-гейтом

## [1.96.0] - 2026-08-24

### Added

- plugin-lang-rust: третій first-party wasm-гість — концерни rust/applies, rust/doc_comments, rust/workspace_root у нативному порті (JS-канон лишається, parity-гейт звіряє біт-у-біт)

### Fixed

- plugin-lang-python: імʼя у python/doc_comments обрізається по ASCII, як JS-\w (ECMA-262) — Unicode-\w крейта regex захоплював кириличний хвіст (def aоблік → «aоблік» замість «a»)

## [1.95.1] - 2026-08-24

### Changed

- wasm-parity: гейт lang-python звіряється зі знятим еталоном JS-канону (N_WASM_PARITY_CAPTURE), не з живим main.mjs; спільний шар еталонів винесено з lang-js-гейта у wasm-parity-golden.mjs

## [1.95.0] - 2026-08-23

### Added

- wasm-порт python/mypy та python/ruff (exec-tool) у plugin-lang-python + parity-тести

## [1.94.2] - 2026-08-23

### Changed

- feat(plugin-lang-python): другий wasm-гість + applies/tooling/doc_comments (#474); chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.94.1] - 2026-08-23

### Changed

- test(plugin-lang-js): закрити борг покриття js-run/runtime (+66 тестів) (#472)

## [1.94.0] - 2026-08-23

### Changed

- wasm-parity: гейт vue/tfm-translations та інших lang-js концернів звіряється зі знятим еталоном JS-канону (N_WASM_PARITY_CAPTURE), не з живим main.mjs

### Removed

- прибрано JS-детектори кластера style/vue/bun (admin_table, gap, lint, quasar_fixes, tooling, packages, tfm-translations, layout, licensee) — логіка вже в crates/plugin-lang-js (wasm); T0-фіксери лишаються JS
- Прибрано JS-фолбек кластера js/*, npm-module/*, js-run/runtime, js-bun-redis/imports, js-mssql/deps, js-bun-db/safety, js/jscpd_duplicates (main.mjs + lib-сканери) — wasm/native Rust-порт canonical, T0-фіксери лишились у JS
- test/*-кластер (15 концернів): видалено JS lint-детектори (main.mjs), покриття перенесено в wasm-плагін crates/plugin-lang-js; T0-фіксери й спільні AST/утиліти лишаються JS-каноном

## [1.93.10] - 2026-08-23

### Changed

- feat(rules-core): nginx-default-tpl/template і changelog/consistency нативно (#470)

## [1.93.9] - 2026-08-23

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.93.8] - 2026-08-23

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.93.7] - 2026-08-23

### Changed

- text/run-shellcheck: read-only детектор портовано в rules-core (crates/rules-core/src/concerns/text_run_shellcheck.rs); main.mjs видалено, listShellScriptPaths/runShellcheckText (write-режим) перенесено у fix-run-shellcheck.mjs

## [1.93.6] - 2026-08-23

### Changed

- feat(rules-core): rego/opa_check, rego/regal, security/scan нативно (#465)

## [1.93.5] - 2026-08-23

### Changed

- feat(rules-core): rego/opa_check, rego/regal, security/scan нативно (#465)

## [1.93.4] - 2026-08-23

### Changed

- feat(rules-core): канал діагностик у native-контракті + tracked_symlink (#464); chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.93.3] - 2026-08-23

### Changed

- fix(text/cspell-fix): зняти подвійну реалізацію main.mjs + native (#462)

## [1.93.2] - 2026-08-23

### Changed

- fix(text/cspell-fix): зняти подвійну реалізацію main.mjs + native (#462); chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.93.1] - 2026-08-22

### Changed

- feat(rules-core): fix-поверхня k8s/manifests портована повністю (#460)

### Fixed

- changelog presence: додано change-файл для змін у npm

## [1.93.0] - 2026-08-19

### Added

- Native-контур фіксу отримав шар T0 — детерміновані фікси перед будь-яким
викликом моделі. Реєстр `NATIVE_FIXES` і диспетчер `run_concern_fix` існували
давно; бракувало саме підключення до петлі, через що шість concern-ів із
готовим детермінованим фіксом усе одно йшли в LLM-драбину.

Тепер `hasura/migrations` закривається так: `✅ закрито детермінованим фіксом,
без виклику моделі` — заборонений `down.sql` видаляється миттєво. Видалення
доступне саме T0, бо в наборі інструментів моделі `delete` немає й не буде
(порожній allowlist — головна гарантія цього контуру), а безпечне воно
конструктивно: фікс іде у worktree, і людина бачить результат у PR.

Заразом проставлено `fixability` для 13 concern-ів за результатами аудиту всіх
19 непозначених: три `structural` (`capacitor/platforms`, `k8s/kubeconform`,
`k8s/hasura_httproute`) і десять `config`. Шість, що лишились без позначки,
справді придатні для моделі — серед них `text/formatting`, доведений живим
прогоном.
- Чотири нові детерміновані T0-фікси: `abie/firebase_hosting`,
`security/sample_secret`, `abie/env_dns`, `k8s/dremio_logging`. Реєстр
`NATIVE_FIXES` виріс із шести до десяти — тобто десять concern-ів закриваються
без жодного виклику моделі.

`abie/firebase_hosting` прибирає і файли, і теку `.firebase/` — застосовувач
планів навчився розрізняти файл і директорію, бо контракт має один `Delete` на
все, а детектори знаходять і те, і те.

Свідомо НЕ покриті гілки, де детермінованої відповіді немає:
namespace-префікс у `env_dns` (з порушення видно лише вимогу-префікс, не повне
очікуване значення) і відсутній цілком тег `<root>` у `dremio_logging` (вставка
потребує знати `appender-ref`). Обидві дають порожній план і перевірені
окремими тестами: краще не зробити, ніж зробити навмання — T0 працює без
перевірки між кроками.
- `concern.json` отримав опційне поле `fixHint` — рецепт для МОДЕЛІ на випадок,
коли повідомлення детектора адресоване людині.

Повідомлення — елемент UX: воно радить зручну команду (`npx @7n/n ch`), але
цикл `fix` виконати її не може: shell поза його набором інструментів свідомо.
Для таких concern-ів єдиний наявний рецепт виявлявся забороненим, а іншого не
було — модель вигоряла всю стелю ходів, не спробувавши жодного запису.

Перший споживач — `changelog/presence`, єдиний concern з `fixability: code`,
чиє повідомлення командне (решту таких гейт до моделі не пускає).

Виміряно на локальній 26B: без підказки concern не закрився в жодному з семи
прогонів, з підказкою — у трьох із шести. Решта — те, заради чого драбина має
другий локальний рунг і ескалацію в хмару.

Підказка має ПОКАЗУВАТИ форму файлу по рядках, а не описувати її. Перший
варіант пояснював словами «frontmatter між рядками `---`» — модель обгортала
цими рядками весь файл замість самого frontmatter-а: 8-9 записів за прогін і
жодного успіху. Нумерований порядок рядків дав 2-3 записи і два успіхи з
трьох. Кількість правок тут — показник метушні, а не старанності.

### Changed

- Детектори `abie/firebase_hosting` і `security/sample_secret` кладуть шлях у
машинне поле `file` (а `sample_secret` ще й номер рядка в `data.line`), як це
давно робить `hasura/migrations`. Раніше весь контекст жив лише в тексті
повідомлення, і T0-фіксам доводилось розбирати його назад регулярками.

Наслідок для фіксів: розбір тексту зник, разом із ним — чотири регулярки й
ціла гілка «номер рядка невідомий, заміни перший збіг». Тепер адреса правки
або відома точно, або фікс не робить нічого: мовчазна заміна не того рядка
дорожча за невиправлене порушення, бо T0 працює без перевірки між кроками.

Захист від дрейфу теж чесніший: правимо рядок лише якщо він ДОСІ містить
порушення — джерелом істини став сам файл, а не те, що детектор колись бачив.

### Fixed

- `text/forbidden-prettier` позначено `fixability: structural` — його порушення
усувається видаленням файлу, тож для LLM-драбини він не придатний.

Знайдено живим прогоном: без цієї позначки concern вважався придатним для
коду (дефолт, коли поле відсутнє), модель не мала чим його закрити — у наборі
інструментів немає й не буде `delete` — і вигорала десятки ходів дарма.
Тепер драбина не запускається взагалі: 1.6 секунди замість хвилин.

Право на видалення файлів моделі свідомо НЕ додано: чинний JS-контур цей
concern теж не фіксить (ні native, ні wasm, ні `fix-*.mjs`), тобто прибрати
чужий `.prettierrc` — рішення людини, а не автоматики.
- `--native-fix` більше не дає фальшиво-зеленого результату на per-file
концернах. CLI завжди передавав порожній файловий скоуп, тож детектор такого
concern-а чесно повертав «порушень немає» — навіть коли порушення були на
диску. Уражені були всі три per-file концерни (`k8s/dremio_logging`,
`changelog/presence`, `k8s/kubeconform`), а для `k8s/dremio_logging` це ще й
означало, що його T0-фікс не викликався ЖОДНОГО разу.

Тепер скоуп резолвиться з глоба `concern.json`, коли викликач не передав
список файлів явно. `scope: "full"`-концерни, які обходять дерево самі, не
зачеплені.

Знайдено наскрізною перевіркою всіх десяти T0-фіксів — саме тим прогоном, що
мав лише підтвердити роботу.

### Removed

- Прибрано пін-тест `llm-lib-pin.test.mjs` разом із workspace-джерелом
`@7n/llm-lib`: Rust-крейти пакета виїхали в `git.7n.ai/7n/llm-lib`, а
JS-пакет заморожено на опублікованій `3.4.0` — споживачі беруть його з
реєстру (smoke-check імпортів: 177/177 чисто). Тест звіряв залежність
`@7n/rules` із версією workspace-копії, якої більше не існує; актуальність
піна тепер веде звичайний цикл оновлення залежностей (`taze`).

## [1.92.0] - 2026-08-12

### Added

- `n-rules lint --native-fix` приймає кілька concern-ів за виклик і прогоняє їх
послідовно зі спільним бюджетом найдорожчого тиру — саме так, як вимагає
рішення И спеки: кеп рахується на весь прогін, а не на кожен concern окремо.

Звіт друкується по кожному concern-у, код виходу — найгірший із них: успіх
одного не маскує провал іншого. Помилка одного concern-а не зупиняє решту, а
от невалідний формат ключа відхиляє виклик цілком.

## [1.91.0] - 2026-08-11

### Changed

- Native-контур фіксу доведений на багатофайловому порушенні живою локальною
моделлю. Зміни, кожна з яких — наслідок конкретного прогону, а не здогаду:

- **Схема інструмента `edit` сплощена** до одного фрагмента за виклик
  (`path`/`old_text`/`new_text`) замість масиву обʼєктів. Діагностика сирого
  tool-call-у показала, що 26B-модель плутає вкладеність і закриває `]`
  замість `}` — виклик виходив структурно невалідний. Після сплощення той
  самий прогін закрився з першого рунга й без жодного провалу парсера.
- **`verify_max` за паритетом із `run-fix.mjs`**: локальній моделі одна
  спроба виправитись, хмарній дві (було 2 для всіх).
- **`max_tokens` винесений у запит** (`N_LLM_FIX_MAX_TOKENS`) замість
  константи: значення, що рятує слабку модель від розгону, обрізає
  багатофайловий фікс на моделі з великим контекстом.
- **Стеля ходів керується `N_LLM_FIX_TURN_CEILING`**.
- **Звіт показує вартість кожної спроби** — скільки ходів і викликів
  інструментів. Без цього тюнінг стелі був би ворожінням: саме ці числа
  показали, що падає не бюджет, а генерація.
- Схема `self_check` більше не порожній обʼєкт — одне необовʼязкове поле
  замість `{"properties": {}}` як захист від strict-провайдерів.

## [1.90.2] - 2026-08-11

### Fixed

- fix(lint): per-file glob doc-files виводиться з doc-files.extensions contributions — зміна .rs запускає концерн у delta-режимі й PostToolUse hook (новий опційний lint.extensionsSlot у concern.json; статичний glob — fallback, доповнений rs)

## [1.90.1] - 2026-08-10

### Fixed

- Стеля ходів native-контуру фіксу піднята з 10 до 50 — паритет із
`agent-fix.mjs`, разом із тим самим env-ключем `N_LLM_FIX_TURN_CEILING` для перевизначення.

Знайдено першим живим прогоном на локальній моделі: зі стелею 10 обидва рунги
драбини вигоряли на `MaxTurnsError` ще до першої правки, бо модель спершу
читає файл і оглядає дерево — розвідка зʼїдала весь бюджет ходів. Після
підняття той самий прогін закрив порушення з першого рунга.

## [1.90.0] - 2026-08-10

### Added

- `n-rules lint --native-fix <rule/concern>` — власний Rust-контур фіксу
(спека `2026-08-08-llm-lib-acp-only-rust-goose.md`, §3.7 клас 3) став
доступним командою, а не лише з тестів: детектор `run_concern`, драбина
спроб, snapshot з відкотом і collateral-veto.

Прапорець — близнюк наявного `--native-detect`: опційний вмикач бінаря.
Без нього `lint` без `--no-fix` і далі делегується в JS-CLI, де живе чинний
fix-пайплайн, тож дефолтна поведінка не змінюється.

Приймається рівно один ключ concern-а за виклик; кілька ключів — помилка,
а не мовчазний вибір першого. Коди виходу за конвенцією lint-поверхні:
0 — закрито (чисто, детермінованим фіксом або спробою), 1 — порушення
лишилось, 2 — помилка самого прогону. Звіт друкує всі спроби з причинами
відмов, а не лише підсумок.

## [1.89.4] - 2026-08-10

### Fixed

- оновлено pin `@7n/llm-lib` до 3.2.0 (автосинк релізного трейна)

## [1.89.3] - 2026-08-09

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.89.2] - 2026-08-08

### Fixed

- Wasm-порт `test/no-relative-fs-path` синхронізовано з JS-каноном щодо
`symlink()`/`symlinkSync()`: перевіряється лише 2-й аргумент (шлях самого
посилання на диску), бо 1-й — це ціль посилання, де відносний рядок
легітимний. Parity-гейт `wasm-plugin-parity.test.mjs` доповнено двома
symlink-фікстурами, які фіксують саме цю мапу (`FS_PATH_ARG_POSITIONS`).

## [1.89.1] - 2026-08-08

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.89.0] - 2026-08-08

### Changed

- Концерн `js-run/runtime` переїхав з JS у wasm-компонент `lang-js/wasm-concerns`
(зріз 7 контракту v3.1) — найбільший поодинокий концерн плагіна: дев'ять
незалежних під-перевірок одного ключа (`jsconfig.json` при наявному `src/`,
заборона `@nitra/bunyan`, фабричні імпорти підключень поза `conn`-каталогом,
нейминг і експорти файлів `conn`-каталогу, `process.env`/`checkEnv`, пауза
через `new Promise` + `setTimeout`, `Temporal` у Bun-рантаймі, OTEL configmap,
декларація аліаса `#conn/*`).

Разом із портом прибрано вакуумний виклик `conftest` у гілці `jsconfig.json`:
він запускався на кожен workspace-пакет, але не міг віддати жодного порушення
(канон `js_run.jsconfig` очікує `--data`, якого цей виклик не передавав). Сама
структурна валідація `jsconfig.json` не втрачена — її робить окремий
policy-концерн `js-run/jsconfig`, у якого `--data` формується правильно.
- `k8s/manifests` — третій зріз порту в `rules-core`, після якого **весь** `lint()` концерну має native-дзеркало. Портовано kubescape-контур (`findKustomizationDirs` + `kubectl kustomize`, auto-exceptions C-0056/C-0018 для probe у `Job`/`CronJob`, користувацький `.kubescape-exceptions.json`, 5-хвилинний wall-clock ліміт із власним текстом порушення) і kustomize-резолюцію з пʼятьма залежними `validate*` (`collectResourceDescriptorsForKustomizationWalk`, `kustomizeResourceTreeHpaPdbDeploymentFlags`, `prodOverlayHpaPdbOverrideNeeds`, `kustomizationTreeHasHasuraDeployment`). Концерн у `NATIVE_CONCERNS` як і раніше **не** заведено — гейт `check-mjs-contract` вимагає, щоб у native-концерну не було `main.mjs`, тобто заведення в реєстр і видалення JS-канону є одним неподільним кроком, який чекає на порт fix-поверхні; поведінка лінту незмінна.

Полагоджено дефект канону: **обидва детектори застарілих `apiVersion` не спрацьовували ніколи**. `BATCH_V1BETA1_API_VERSION_LINE_RE` і `GATEWAY_HTTPROUTE_V1BETA1_LINE_RE` — рядкові якірні регулярні вирази (`^…$` без прапорця `m`), і саме так їх застосовує T0-фікс (`rewriteLine*` — по одному рядку), але `detectBatchV1beta1InK8sYamlFiles` і `detectGatewayHttpRouteV1beta1InK8sYamlFiles` звіряли їх із **усім** текстом файла. Такий збіг можливий лише на файлі, що цілком складається з одного рядка `apiVersion: batch/v1beta1`, тобто ніколи на справжньому маніфесті: заборона `batch/v1beta1` і `gateway.networking.k8s.io/v1beta1` була мертвою, попри повідомлення, T0-патерни `fix-manifests.mjs` і розділи `manifest.mdc` / `gateway.mdc`, що обіцяють автоматичне переписування. Тепер обидва детектори звіряють regex по рядках — так само, як сусідня перевірка `kind: HTTPRoute` робила від початку. **Зміна поведінки, напрямок fail-closed:** маніфести під `k8s` із застарілими `apiVersion: batch/v1beta1` та `apiVersion: gateway.networking.k8s.io/v1beta1` у `HTTPRoute` тепер дають порушення (з fix-hint, тож `--fix` їх виправляє), а раніше мовчки проходили.

## [1.88.1] - 2026-08-07

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.88.0] - 2026-08-05

### Changed

- Rust-CLI `rules-cli` розбирає argv через `clap` — одна граматика замість трьох ручних парсерів (Р11). Значення прапорців тепер приймаються в обох формах (`--root <шлях>` і `--root=<шлях>`; раніше `rename-yaml-extensions` розумів лише другу, решта команд — лише першу); прапорець без значення став помилкою розбору замість мовчазного `null`; за дубльованого прапорця береться останнє входження, а не перше. `rename-yaml-extensions` більше не ковтає невідомий аргумент мовчки — раніше друкарська помилка в прапорці тихо запускала перейменування файлів, тепер це usage-помилка (код `2`) і зʼявилась довідка `--help`; той самий код `2` тепер і в `changed-files` (був `1`). На поверхнях, які бінар ще ділить із JS-CLI (`lint`, `hook`, `ci plan`, `skill`), невідомий аргумент, як і раніше, не помилка — argv делегується в JS. Довідка `lint --help` лишилась байт-у-байт незмінною.

## [1.87.1] - 2026-08-05

### Changed

- docs(plans): реєстр — перенесення зі зрізу 6 і відновлення втраченого (#404)

## [1.87.0] - 2026-08-05

### Changed

- Ще два концерни переїхали з JS у wasm-компонент `lang-js/wasm-concerns` на
поверхні `exec-tool` (зріз 6 контракту v3.1): `style/lint` (обгортка
`stylelint`) і `js/jscpd_duplicates` (обгортка `jscpd`, перший реальний
споживач `scratch-out` — JSON-звіт тула хост забирає з тимчасового каталогу
обміну, дерево репо не мутується).

Резолв тулів у `manifest.tools` навчився третьої схеми — `npm:<name>`:
бінарник шукається в `node_modules/.bin` консюмер-репо, з фолбеком на PATH.
Це та сама послідовність, якою `style/lint` резолвив `stylelint` у JS.

T0-фікси обох концернів лишаються JS.
- `k8s/manifests` — другий зріз порту в `rules-core`. Портовано ще два з чотирьох шарів `lint()`: per-file цикл `checkK8sYamlFile` (modeline `# yaml-language-server: $schema=…`, звірка URL схеми з `apiVersion`/`kind`, виняток `HttpBackendGroup` Yandex ALB) і дві великі самодостатні `validate*` — `validateDeploymentHpaPdbAndTopology` (канон `topologySpreadConstraints`, заборона локальних `hpa.yaml`/`pdb.yaml` у `k8s/…/base/`, повний контракт sibling-каталогу `components/`) та `validateNetworkPoliciesForK8sWorkloads`. Концерн у `NATIVE_CONCERNS` як і раніше **не** заведено — він неподільний для диспатчу і зайде туди разом із двома шарами, що лишились (kubescape-контур і kustomize-резолюція з пʼятьма залежними `validate*`), тож поведінка лінту незмінна.

Полагоджено латентний дефект канону: `readDocsByKindInDir` обходив каталог у порядку `readdir`, тобто в порядку файлової системи (APFS впорядковує, ext4 віддає hash-порядок). Обхід став відсортованим. Спостережуваної зміни немає — обидва виклики передають фільтр за іменем файла, тож збігається щонайбільше один файл; сортування прибирає ту саму міну, на якій уже спіткнулись `k8s/hasura_configmap` і звірка імені ConfigMap.

## [1.86.0] - 2026-08-05

### Added

- Правило security: новий концерн tracked_symlink — repo-wide detector відстежених git-ом symlink-ів, що вказують за межі репозиторію (абсолютний шлях або відносний з виходом вгору за корінь). Ловить рецидив кшталту target -> /private/tmp/... (коміт 867ff7b3): у чужому клоні такий symlink робить збірку й тести недетермінованими, а .gitignore не рятує, бо ignore не покриває вже відстежені шляхи. Фіксу немає — detector друкує готову команду git rm --cached.

## [1.85.0] - 2026-08-05

### Added

- Контракт плагінів `n-rules:plugin` — мінор `3.1.0`: новий host-імпорт `exec-tool`
(`run-tool` плюс `cwd`, накладені змінні середовища й двобічний scratch-обмін
файлами) і слот `scratch-dir@1` на чинному `host-context`. Зміна additive:
плагін, зібраний під v3.0, лінкується й працює без повторної збірки.

Резолв тулів у `manifest.tools` навчився схем (рішення В спеки v3.1):
`path:<name>` резолвиться по PATH замість github-релізу; відсутність схеми —
як було (`pinned:`).

Перший концерн на новій поверхні — `bun/licensee`: детекція ліцензій
npm-залежностей переїхала з JS у wasm-компонент `lang-js/wasm-concerns`
(T0-фікс лишається JS). Спавнений тул виконується з правами хоста, поза
wasm-пісочницею — межа довіри плагіна це його пін, не пісочниця.

### Fixed

- регенерує `.cursor/rules/n-rust.mdc` (дрейф із каноном після #392) і піднімає `KNOWN_PLUGIN_RANGES['@7n/rules-lang-rust']` до `^0.16` слідом за релізом плагіна

## [1.84.0] - 2026-08-05

### Changed

- `k8s/manifests` — перший зріз порту в `rules-core` плюс полагоджений дефект канону. Портовано два з чотирьох шарів `lint()`: rego-контур (`runAllK8sRego` — усі девʼять namespace-ів, класифікатор fix-підказок, гілка `--data`/`templateData` у `conftest`) і чотири самодостатні cross-file перевірки (`assertNoForbiddenK8sDevPaths`, `validateSvcYamlAndSvcHlPairs`, `validateKustomizationPathRefsExistOnDisk`, `validateKustomizationIncludesSvcHlWithSvc`, `validateConfigMapNameMatchesDeployment`). Сам концерн у `NATIVE_CONCERNS` **не** заведено — він неподільний для диспатчу і зайде туди разом із рештою шарів, тож поведінка лінту незмінна.

**Зміна поведінки:** звірка `metadata.name` ConfigMap із Deployment (`k8s/base/configmap.yaml`) більше не спирається на «перший `kind: Deployment` за порядком `readdir`». Обхід файлів каталогу став відсортованим (детермінований на будь-якій файловій системі — раніше macOS і Linux-раннер CI могли давати різний вислід), і серед **усіх** Deployment каталогу беруться лише ті, що посилаються рівно на один ConfigMap. Наслідок — fail-closed: каталог, де перший Deployment ConfigMap не вживає, раніше мовчки випадав із перевірки, тепер перевіряється. Репозиторії з одним Deployment на каталог змін не побачать.

## [1.83.3] - 2026-08-05

### Changed

- wasm-плагін lang-js: зріз невживаних feature-ів залежностей — артефакт 2 492 361 → 2 168 151 байт (запас до бюджету 2,5 MB зріс із 7,5 KiB до 324 KiB)

## [1.83.2] - 2026-08-05

### Changed

- fix(lint): clippy sort_by_key і env з node:process у k8s-parity (#388)

## [1.83.1] - 2026-08-05

### Changed

- fix(lint): clippy sort_by_key і env з node:process у k8s-parity (#388); chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.83.0] - 2026-08-05

### Added

- `js/doc_comments` виконується у wasm-плагіні `lang-js/wasm-concerns` (36-та контрибуція, зріз 4 контракту v3.1 — `docs/specs/2026-08-01-plugin-contract-v31-surfaces.md`): портовано і `detect`, і T0-фікс (`export fix`) — розривати їх не можна, бо весь T0-контур концерну тримається на `violation.data.{start,end}`. Офсети конвертуються на межі WIT в обидва боки: `detect` віддає їх у **UTF-16** code units (як napi-`oxc-parser`, а не в байтах crate-`oxc_parser`), `fix` переводить назад у байти перед зрізом UTF-8-рядка; parity-фікстури з кирилицею й емодзі поза BMP тримають це живим. JS-канон (`main.mjs` + `fix-doc_comments.mjs`) лишається fallback-ом за чинним конвеєром skip-not-crash
- wasm-плагін lang-js: у контрибуцію додано `js/doc_comments` разом із T0-фіксером (зріз 4 контракту v3.1) — останній портовний концерн `lang-js`. Офсети napi-`oxc-parser` (UTF-16) конвертуються у байтові для crate-`oxc_parser` і назад; покрито фікстурами з не-ASCII вмістом, де наївний порт розходиться. Артефакт 2 492 361 байт — запас до бюджету 2,5 MB лише 7,5 KiB

### Changed

- rule-level гейт `applies` став **декларативним**: замість виконуваного модуля `<rule>/applies/main.mjs` правило оголошує предикат у полі `main.json:applies` (міні-DSL на 4 оператори — `pathExists`, `globMatches`, `jsonFieldContains`, комбінатор `any`; аварійний клапан `"applies": "dynamic"` лишає гейт на JS). Три чинні гейти (`lang-python/python`, `lang-rust/rust`, `lang-js/npm-module`) мігровано, їхній JS видалено. Дискавері перестало бути виконанням коду, тож native `ci plan` більше НЕ делегується в JS через саму лише наявність `applies` — гейт обчислюється в Rust (`rules-core::rule_applies`). Сумісність для сторонніх правил: `main.json` без поля `applies`, але з файлом `applies/main.mjs`, читається як `dynamic` — поведінка не змінюється.

## [1.82.1] - 2026-08-04

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.82.0] - 2026-08-04

### Changed

- `k8s/hasura_configmap` і `k8s/hasura_httproute` виконуються нативно в `rules-core` (28-й і 29-й native-концерни), JS-канон видалено: обидва `main.mjs`, їхні тести й доки, а разом із ними — ядра `validateHasuraConfigMapRemoteSchemaPermissions`, `validateHasuraHttpRouteCanon`, `collectHasuraDeploymentsAndHttpRoutes` та супутні хелпери з `npm/rules/k8s/manifests/main.mjs` (−189 рядків цього файлу). Порти підключені через registry `NATIVE_CONCERNS`, тобто працюють і в native-шляху `rules-cli`, і в чинному JS-оркестраторі — нової napi-поверхні не заведено (Р12 спеки `docs/specs/2026-07-30-rules-v2-rust-core-migration.md`). Rego лишається авторитетним для пер-документної валідації: новий `rules_core::conftest` — native-порт `runConftestBatch` (один спавн `conftest` на пару полісі/namespace, розбір `--output json`), спільна база для решти rego-концернів кластера; вбудовані полісі резолвить новий `rules_core::rules_package` (`node_modules/@7n/rules` → `npm/` вгору від cwd, override `N_RULES_PACKAGE_ROOT`). **Зміна поведінки 1:** гейт `k8s/hasura_configmap` більше не бере «перший `kind: Deployment` каталогу» — він питає, чи є в каталозі **хоч один** Hasura-Deployment. Раніше ConfigMap Hasura мовчки випадав із перевірки, якщо поруч лежав ще один Deployment і саме він трапився першим у `readdir`; той самий репозиторій міг давати різний вислід на macOS і на Linux-раннері (порядок файлової системи). Тепер під валідацію потрапляє строго більше ConfigMap — напрямок fail-closed; сусідній `k8s/hasura_httproute` цей самий гейт завжди робив правильно. **Зміна поведінки 2:** коли `conftest` не встановлено, а спарені файли є, концерн падає fail-closed із per-OS install-підказкою замість авто-встановлення через `ensureToolAsync` — рівно чинний режим `N_CURSOR_NO_AUTO_INSTALL`, той самий контракт, що вже зафіксував `k8s/kubeconform`

## [1.81.0] - 2026-08-04

### Added

- Команда `n-rules tools ensure [<тул>…] [--check]` і `n-rules tools list [--json]` у Rust-CLI — явне добування зовнішніх CLI-тулів замість авто-встановлення всередині лінту (компенсація за fail-closed `k8s/kubeconform` з PR #378; мінідизайн `docs/specs/2026-08-04-tools-ensure-design.md`). Реєстр тулів переїхав із коду в декларативний `npm/scripts/lib/tools.json` — **єдине джерело правди** для обох мов: JS `TOOLS` (`ensure-tool.mjs`) будується з нього, Rust (`rules_core::tool_registry`) вбудовує той самий файл на збірці; версії, як і раніше, в `tool-pins.json`. `brew`/`scoop` виконуються нативно під тим самим міжпроцесним локом, що бере JS (`ensure-tool/<toolId>`), а шлях GitHub Release делегується в чинний `ensureToolAsync` через зворотний міст (нова операція `ensureTool`, версія протоколу мосту 1 → 2) — другої реалізації завантаження не заводиться, поки JS-`ensureTool` усе одно потрібен тим споживачам, які ще не портовані. Крос-мовний дрейф гейтить `tools-registry-parity.test.mjs` (розгорнуті `asset`/`binPath` на закріпленій версії, тобто й мапінг архітектури)

## [1.80.1] - 2026-08-04

### Changed

- release: @7n/llm-lib@2.14.16, @7n/rules@1.80.0

## [1.80.0] - 2026-08-04

### Changed

- `k8s/kubeconform` виконується нативно в `rules-core` (27-й native-концерн), JS-канон `npm/rules/k8s/kubeconform/main.mjs` видалено. Порт підключений через registry `NATIVE_CONCERNS`, тобто працює й у native-шляху `rules-cli lint --no-fix --native-detect` (батч-сегмент), і в чинному JS-оркестраторі — нової napi-поверхні не заведено (Р12 спеки `docs/specs/2026-07-30-rules-v2-rust-core-migration.md`: міст інвертовано, napi більше не росте). Резолв зовнішнього тула — новий `rules_core::tool_resolve` на крейті `which` (PATH → керований кеш бінарників), без HTTP-клієнта в ядрі. **Зміна поведінки:** коли `kubeconform` не встановлено, а цілі для валідації є, концерн падає fail-closed із per-OS install-підказкою замість авто-встановлення через `ensureTool` — це рівно чинний режим `N_CURSOR_NO_AUTO_INSTALL`; добування тулів переїжджає в окрему команду `tools ensure`. Мовчазний пропуск був би fail-open: на ефемерному CI-раннері schema-валідація просто зникла б

### Fixed

- Loader napi-аддона (`npm/scripts/lib/native.mjs`) більше не перекриває локальну збірку опублікованим platform-підпакетом: у вихідному дереві репо (маркер `crates/rules-napi/Cargo.toml`) `target/release|debug` резолвиться ПЕРЕД `@7n/rules-<platform>-<arch>`, у встановленому пакеті порядок лишається старим (підпакет — авторитетне джерело). До фіксу крок `cargo build -p rules-napi` у `.github/workflows/test.yml` збирав аддон, якого loader не брав, тож native-тести мовчки перевіряли попередню збірку. Клас закритий гейтом `npm/scripts/lib/tests/native-env-gate.test.mjs`

## [1.79.2] - 2026-08-04

### Fixed

- Гейти `Lint JS` і `Lint repo-wide` знову зелені на `main` (були червоні щонайменше з 31.07): 37 порушень `js/eslint` (jsdoc, `no-empty-function`, `consistent-function-scoping`, `require-await`, `cognitive-complexity`, невалідний JSON-сніпет у `wasm-plugin/SKILL.md`, `workflow_dispatch:` без `{}` у `npm-publish.yml`), 6 клонів `js/jscpd_duplicates` і 2 clippy-помилки `plugin-lang-js`. Реальні дефекти полагоджено, а не заглушено: тест-хелпер `write` винесено з 28 copy-paste-копій concern-модулів у `crates/rules-core/src/concerns/test_support.rs`, спільний хвіст збирачів workspace-коренів — у `workspaces::sorted_workspace_roots`. Два клони лишились винятками в `.jscpd.json` із обґрунтуванням: `wasm-plugin/template/build.sh` — сировина скіла, байт-у-байт копія за задумом; `llm-lib/tests/native-packages.test.mjs` — дзеркало тесту незалежно публікованого пакета, спільний хелпер створив би нову звʼязність між `@7n/llm-lib` і `@7n/rules`

## [1.79.1] - 2026-08-03

### Changed

- chore(release): синк пінів платформних napi-пакетів @7n/rules

## [1.79.0] - 2026-08-03

### Added

- wasm-плагін lang-js: у контрибуцію додано `test/stryker_config` і `js/check` (зрізи 1–2 контракту плагінів v3.1) — канон oxlint вшито в компонент через `include_str!`, поверхні до package-асетів НЕ додано. Артефакт 2 417 693 → 2 473 332 байт, запас до бюджету 2,5 MB — 144,6 KiB
- Rust-CLI `rules-cli lint --no-fix --native-detect` (або `N_RULES_NATIVE_LINT=1`) виконує lint сам: план, диспатч, сортування, рендер і exit-код рахує `rules-core`, а концерни з `main.mjs`/policy виконує ОДИН дочірній node-процес на прогін (зворотний міст поверх unix-сокета, `npm/scripts/lib/lint-surface/bridge-host.mjs`); шлях не дефолтний і чесно делегується в JS-CLI там, де паритет недосяжний (`--path`, fix-пайплайн, wasm-концерни)

### Changed

- План міграції v2 (`docs/specs/2026-07-30-rules-v2-rust-core-migration.md`): зафіксовано рішення Р11 «порт ≠ дзеркало реалізації» — мета міграції це ефективне використання Rust-екосистеми, а не 1:1-переклад JS; зрілий крейт має пріоритет над ручним портом, JS-обходи обмежень Node/Bun у Rust не відтворюються, паритет поведінковий і доводиться тестами, побайтова рівність лишається тільки на зовнішньо спостережуваних поверхнях
- План міграції v2: зафіксовано рішення Р12 «інверсія мосту» — `rules-cli` стає основним виконавцем `lint`, а непортовані JS-концерни виконуються як його дочірні процеси; нова native-функціональність у napi більше не заводиться (чинні 25 експортів лишаються backend-ом публічних JS-фасадів і виводяться з останнім JS-концерном). Ревізовано рішення про `clap` у фазі 8: підстава для ручного парсингу знята рішенням Р11

### Fixed

- `ci_artifacts`/`tools` у `crates/plugin-lang-js/plugin.toml` і в шаблоні скіла `wasm-plugin` (`template/plugin.toml.tpl`) стояли ПІСЛЯ заголовка `[[concerns]]`, тож TOML читав їх як поля останнього концерну, а не як top-level поля маніфеста — довідник розходився і з `rules_contract::manifest::Manifest`, і з тим, що повертає `describe()`. Обидва файли виправлено (поля перенесено до `id`/`version`/`world_version`/`domains`), а анти-дрейф-гейт `plugin_toml_concern_keys_match_describe` переведено з порядкового сканування рядків на справжній TOML-парсинг і розширено на `tools`/`ci_artifacts`: попередній примітивний парсер цей клас дрейфу не бачив у принципі — рядок присутній, і байдуже, в якій таблиці він опинився

## [1.78.0] - 2026-08-01

### Added

- Батч 9 §3.5.5: `vue/packages` портовано у wasm-компонент lang-js (33 контрибуції; size-budget 2.5 MB дотримано — 2.42 MB, +22 KB за батч). Порт byte-exact: workspace-обхід, `src/vite-env.d.ts`/`jsconfig.json`/`vite.config.*`, AST-скан явних value-імпортів з `vue` і Node-нативних імпортів у SFC через той самий `oxc_parser`, скан згадок `esbuild` з тим самим лімітом 30. Інвентаризацію решти JS-канону lang-js перевірено наново: із 39 концернів з `export lint` придатних до порту більше немає — пʼять обгорток зовнішніх тулів (`bun/licensee`, `style/lint`, `js/eslint`, `js/jscpd_duplicates`, `js-run/runtime`), `js/knip` (programmatic API, поза `run-tool`), `js/check` і `test/stryker_config` (canonical-data з пакета плюс запис на диск під час detect), `js/doc_comments` (UTF-16-офсети в `data` + порт T0-фіксера). §3.5.5 вичерпано: далі потрібне розширення контракту або декларація тулів
- Rust-CLI `rules-cli` обробляє `hook` нативно там, де гілка не доходить до `detectAll` (валідація режиму й PostToolUse-payload, з якого не дістається жодного файлу — 2 мс замість 38–54 мс); решта чесно делегується в JS із переграним stdin, бо детектори лишаються JS-модулями

## [1.77.0] - 2026-08-01

### Added

- Rust CLI `rules-cli` виконує `skill list` і `rename-yaml-extensions` повністю нативно (зріз 2 фази 8), byte-exact із JS-еквівалентом — включно зі станом файлової системи після мутуючої команди
- Батч 7 §3.5.5: кластер `npm-module/rule_meta|skill_meta|header_doc_pointer|package_structure` і AST-концерн `js/dep-policy` портовано у wasm-компонент lang-js (28 контрибуцій; size-budget 2.5 MB дотримано — 2.27 MB), `plugin.toml` синхронізовано з `describe()` і закрито новим гейт-тестом, а в спільному `extract_import_sources` виправлено прихований порядок (статичні імпорти мають передувати walk-знахідкам, як у JS-каноні)
- Батч 8 §3.5.5: `bun/layout`, `style/tooling`, `test/sandbox-aware-test` і `test/vitest-api-conventions` портовано у wasm-компонент lang-js (32 контрибуції; size-budget 2.5 MB дотримано — 2.28 MB, +9,6 KB за батч). Решту JS-канону lang-js розібрано за класом причин: чотири концерни — обгортки навколо запуску зовнішнього тула (`licensee`/`stylelint`/`eslint`/`jscpd`), `js/knip` — programmatic API, `js-run/runtime` — conftest-підпроцес, `js/check` і `test/stryker_config` — потребують вшитих canonical-data файлів, `js/doc_comments` — окремого рішення про UTF-16-офсети; `vue/packages` лишається наступним кандидатом
- Rust-CLI `rules-cli` виконує `ci plan` нативно у репозиторіях без плагінів (читання `.n-rules.json` і `concern.json`, активність доменів і всі формати виводу — у `rules-core`), а там, де паритет недосяжний без резолву плагінів, чесно делегує команду в JS

### Fixed

- Порядок рядків у native-виводі lint більше не розходиться з JS-каноном: сортування порушень перейшло з байтового порівняння на наближення ICU root collation — підкреслення тепер перед дефісом, а велика латинська літера після малої, як у `localeCompare`
- Реліз більше не затирає платформні піни: синк lock перейшов з `bun update` (він переписує маніфести — з кореня дописував фантомні залежності, з теки воркспейса затирав піни неінстальовних платформ на порожній рядок) на `bun install`, який маніфести не чіпає; піни `@7n/rules-linux-x64` і `@7n/rules-win32-x64` відновлено, додано тест-гейт консистентності пінів

## [1.76.0] - 2026-08-01

### Added

- Фаза 8 зріз 1 (спека 2026-08-01-rules-cli-phase8-skeleton): скелет Rust CLI `crates/rules-cli` — нативні `lint --help` і plumbing `changed-files` поверх rules-core, транзитна делегація решти команд у JS-entrypoint (bun/node, argv-passthrough) і byte-exact parity-гейт `rules-cli-parity.test.mjs`
- Батч 6 §3.5.5: `test/storybook-vitest-config` і три rego-концерни `js-bun-db|js-bun-redis|js-mssql/package_json` портовано у wasm-компонент lang-js (23 контрибуції; size-budget 2.5 MB дотримано — 2.24 MB). Блокер батчу 5 (абсолютний `data.vitestConfigPath`, який споживає JS-фіксер) знято новим additive host-імпортом `host-context: func(slot) -> option<string>` зі слотом `repo-root@1` — розширення WIT-`record` зламало б ABI усіх запінених guest-ів, окремий import лінкується лише тим, хто його референсить. Rego-порти звірені байт-у-байт із живим conftest-прогоном через `evaluatePolicyConcern`; `js-bun-db/connection`, `js-bun-db/pg_format_identifiers`, `js-mssql/mssql-tvp` — `.mdc`-only guidance без детектора, портувати нема чого

### Fixed

- Усунуто корінь повторюваних падінь CI `lockfile had changes, but lockfile is frozen`: кроки синку платформних пінів у релізі запускали `bun update` з кореня репо, через що bun дописував платформні пакети в кореневі `dependencies` — ця правка не потрапляла в коміт, а отруєний `bun.lock` потрапляв; тепер `bun update` виконується з теки воркспейса, що декларує ці залежності

## [1.75.0] - 2026-08-01

### Added

- Fix-контур wasm-плагінів contract v3: host-виклик `export fix` з валідацією планів (safe-path, ліміти розміру), napi `runWasmConcernFix`, синтетичний `wasm-fix:*` T0Pattern у fix-конвеєрі, злиття дзеркала `FixPlan`/`FileEdit` (rules-core → rules-contract) і пілот `test/no-bun-test-import` — фікс портовано в guest, JS `fix-no-bun-test-import.mjs` видалено
- Батч 5 §3.5.5: storybook-сімейство (`test/storybook-{scope,hygiene,page-coverage,scaffold,ci}`) портовано у wasm-компонент lang-js — спільна scope-детекція `collectInScopeVuePackages` відтворена в batch-просторі full-scope мосту, hygiene-імпорти через oxc, byte-exact parity з JS-каноном (19 контрибуцій; size-budget 2.5 MB дотримано — 2.21 MB); tool-wrapper-и `js/eslint`/`js/knip` свідомо лишаються на JS (програмні API ESLint/knip і node_modules-резолюція поза run-tool контрактом)

## [1.74.1] - 2026-08-01

### Fixed

- Розблоковано синк git-пінів платформних napi-пакетів: гейт release-кроку більше не вимагає успіху `@7n/rules-win32-x64` (до bootstrap-публікації він падає на кожному релізі, через що піни застрягли на 1.66.0 при registry 1.73.0); піни й bun.lock синхронізовано разово вручну

## [1.74.0] - 2026-08-01

### Added

- Батч 4 §3.5.5: `js-bun-redis/imports`, `js-mssql/deps` і `js-bun-db/safety` портовано на повноцінний AST через oxc_parser у wasm-компоненті lang-js (заміна regex-groundwork, byte-exact parity з JS-каноном, 14 контрибуцій; size-budget 2.5 MB дотримано — 2.12 MB)

## [1.73.0] - 2026-08-01

### Changed

- Фаза 7 v2 (T2, п'ятий зріз): native fix plans розгорнуто з пілота на решту
T0-фіксів, що портуються — `tauri/gitignore_target` (splice відсутніх ignore-entries
з `violation.data.missing`), `tauri/linux_deps` (вставка apt-кроку перед
`dtolnay/rust-toolchain` / дописування канонічних пакетів зі збереженням
shell-continuation), `tauri/cargo_mutants_config` (canonical baseline /
append-блок лише відсутніх ключів після rescan `src-tauri/`-каталогів) і
`hasura/internal_urls` (rewrite `service`/`namespace` сегментів
`HASURA_GRAPHQL_ENDPOINT` проти `hasura/k8s/base/{svc-hl,namespace}.yaml` зі
збереженням `cluster`/`port`); реєстр `NATIVE_FIXES` тепер шість ключів,
чотири JS fix-файли видалені, їхні тести переведені на dispatch-рівень
(`loadT0Patterns`/`runNativeConcernFix`) з rollback-контракт-тестами через
реальний `snapshot.rollback()`. `tauri/release` (format-preserving
YAML-редагування + git-субпроцес) і `image-avif/avif_generation` (генерація
AVIF зовнішнім `npx @nitra/minify-image`) свідомо лишаються JS — причини
задокументовано в doc-коменті `crates/rules-core/src/concerns/fix.rs`.

## [1.72.0] - 2026-08-01

### Added

- Фаза 7 v2 (T1, четвертий зріз): native fix-домен для builtin-концернів —
пілот на двох T0-фіксах. `run_concern_fix` у `crates/rules-core/src/concerns/fix.rs`
будує декларативний `FixPlan`/`FileEdit`/`WriteFile` (форма дзеркалить
`rules-contract::fix` БЕЗ залежності на цей крейт — план злиття задокументовано
в doc-коменті модуля, за зразком diagnostics DTO фази 5) для `doc-files/marksman_config`
(копіювання canonical baseline — тепер вбудований у бінарник через `include_str!`)
і `hasura/migrations` (видалення заборонених `down.sql`); реєстр `NATIVE_FIXES`.
napi-біндінг `runNativeConcernFix` (`crates/rules-napi/src/lib.rs`) повертає план
або `null` для ключів поза реєстром. `run-fix.mjs`: `loadT0Patterns` синтезує
T0Pattern-обгортку над native-планом (`test()` = «план непорожній», `apply()`
застосовує `write`/`delete`-операції з `ctx.recordWrite` ПЕРЕД кожною мутацією —
rollback-контракт) замість dynamic import() JS fix-файлу; два JS T0-фікси
(`fix-marksman_config.mjs`, `fix-migrations.mjs`) видалені, їхні тести
переведені на native dispatch.

**Зміна семантики**: install-sanity-guard старої JS-версії marksman
(«canonical baseline відсутній на диску — перевстанови @7n/rules») недосяжний
у native-фіксі — baseline вшитий у бінарник на етапі компіляції, тож клас
помилки «baseline відсутній» структурно неможливий для native-шляху. Свідома
зміна поведінки зламаної інсталяції, задокументована в `fix.rs` і в оновленому
тесті `marksman_config.test.mjs`.
- Перші AST-концерни у wasm (батч 3 §3.5.5 за AST-стратегією): `js/utils_imports` і `test/no-relative-fs-path` через oxc_parser у guest-компоненті (пін =0.137.0 синхронно з npm oxc-parser + mirror-версійний тест; size-budget 2.5 MB дотримано — 1.92 MB)

## [1.71.0] - 2026-07-31

### Added

- Фаза §3.5.5 v2, задача Q2 батч 2: ще два lang-js concern-и у
`crates/plugin-lang-js` (`test/no-console-store-restore`,
`test/no-bun-test-import`) — маніфест плагіна тепер декларує девʼять
контрибуцій. Справжній 1:1 порт JS-регекс-детекторів, з T0-фіксом
`fix-no-bun-test-import.mjs`, який лишається JS і напряму споживає
wasm-violations — перевірено живим смок-тестом. Golden-тести
(`crates/rules-plugin-host/tests/plugin_lang_js.rs`) і JS⇄wasm parity-тест
(`npm/scripts/lib/lint-surface/tests/wasm-plugin-parity.test.mjs`) розширені
на обидва концерни.

Три додаткові концерни (`js-bun-redis/imports`, `js-bun-db/safety`,
`js-mssql/deps`) — JS-оригінали побудовані на справжньому oxc-parser AST, не
на regex; спроба regex-наближення НЕ пройшла parity-гейт для контрибуції
(рішення оркестратора: byte-inexact вивід недопустимий для concern-а, що
shadow-ить живу JS-реалізацію). Їхні detect-функції й unit-тести лишаються в
`crates/plugin-lang-js/src/lib.rs` як groundwork під майбутнє справжнє
AST-рішення, БЕЗ контрибуції в `describe()`/`plugin.toml`.
`npm/skills/wasm-plugin/SKILL.md` доповнено підрозділами про AST-based
(oxc-parser) JS-концерни (апроксимація допустима лише БЕЗ контрибуції) і про
перевірку T0-фіксів, що лишаються JS поверх wasm-детектора.
- Фаза 7 v2 (R2, третій зріз): batch-виконавець builtin-native concern-ів —
суцільні прогони `NATIVE_CONCERNS`-items у `detectAll`-плані виконуються
ОДНИМ native-викликом (`run_concerns_batch` у `crates/rules-core/src/concerns/batch.rs`,
napi-біндінг `runNativeConcernsBatch` у `crates/rules-napi/src/lib.rs` із
синхронним `onProgress`-колбеком, без `ThreadsafeFunction`) замість N окремих
`runNativeConcern`-викликів через napi-межу. `run-detectors.mjs`
(`partitionPlanIntoSegments`/`runNativeSegmentSync`) партиціонує послідовний
план на native-batch-сегменти й single-сегменти (wasm/policy/main.mjs) —
progress-репортинг і `DetectorError`-семантика (формат повідомлення,
зупинка на першій помилці) відтворені один-в-один із чинним per-item шляхом.
Конкурентний режим (`N_RULES_LINT_CONCURRENCY>1`) свідомо лишається per-item.
Диференційний parity-тест (мікс builtin-native + JS-концернів, той самий
чинний per-item шлях у concurrent-режимі як референс) і тест error-семантики
батчу.

## [1.70.0] - 2026-07-31

### Added

- `plugin-lang-js` (задача Q1 батч 1): порт пʼяти JS-концернів у native/wasm — `test/vitest-config-pool-forks`, `test/no-process-chdir`, `style/admin_table`, `style/quasar_fixes`, `test/location` (усі full-scope, whole-batch, 1:1 порт reason/message); маніфест і `plugin.toml` тепер декларують сім контрибуцій разом із чинними `vue/tfm-translations`/`style/gap`; golden-тести `plugin_lang_js.rs` і parity-тест `wasm-plugin-parity.test.mjs` покривають усі сім; JS-канон (`plugins/lang-js/rules/**/main.mjs`) не видалено.
- Фаза 7 v2 (R1, другий зріз): sort/render/exit-code контур lint-оркестрації
(`sortViolations`/`renderViolations`/похідний exit-code `detectAll`) портовано
в `rules-core` (`crates/rules-core/src/lint_render.rs`) з native-біндінгами
`renderViolations`/`sortAndRenderViolations` (комбінований виклик — сортує,
рендерить і рахує exit-code за один hop через napi-межу); `run-detectors.mjs`
і `render.mjs` делегують у native, JS-реалізації видалено. Diff-parity гейт
проти замороженої копії старого JS-алгоритму на синтетичних фікстурах
(колізії ключів сортування, українські повідомлення, відсутній `file`,
warn-severity, `data.line`).

## [1.69.0] - 2026-07-31

### Added

- Вбудовані first-party wasm-піни (задача O1 фази 6 v2, рішення Н): `npm/wasm-plugins/` доставляє `.wasm` first-party плагінів (`plugin-lang-js`) прямо в пакеті `@7n/rules` — `builtin-pins.json` (name → file+sha256, третє й найнижче пріоритетне джерело `wasmPlugins`, перекривається записом `.n-rules.json` консюмера) читає `wasm-plugins.mjs` поряд із собою; `npm/scripts/build-wasm-plugins.mjs` — локальна dev-петля й той самий CI-крок `npm-publish.yml` (build-native, ubuntu-рядок); `vue/tfm-translations`/`style/gap` тепер резолвляться через builtin-таблицю без ручного піна в `.n-rules.json` консюмера.
- Фаза 7 v2 (P1, перший зріз): plan-контур lint-оркестрації (`buildPlan` + п'ять
builders: scoped/scopedDelta/repoWide/full/delta, `planConcernForDelta`,
picomatch-паритетний glob-матчер) портовано в `rules-core`
(`crates/rules-core/src/lint_plan.rs`) з native-біндінгами `buildLintPlan`/
`matchLintGlobs`; `run-detectors.mjs` делегує обчислення плану в native (JS
лишає дискавері concern.json, capability- і applies-фільтри — межа
задокументована в doc-коментарі `lint_plan` модуля). Diff-parity гейт проти
замороженої копії старого picomatch-алгоритму на синтетичних і реальних
glob-фікстурах.

## [1.68.0] - 2026-07-31

### Added

- run-tool контур і перший плагін фази 6 v2: host виконує задекларовані tools через ensure-tool-резолв (typed-помилки, таймаут із kill процес-групи); маніфест concerns → contribution зі scope+glob; full-scope міст будує batch через native scan; `plugin-lang-js` (tfm-translations + gap) створений за скілом wasm-plugin — пілот виведено

## [1.67.0] - 2026-07-31

### Added

- Платформна матриця native-аддона `rules-core` розширена на win32-x64
(рішення О, §3.4a `docs/specs/2026-07-31-plugin-contract-v3-wasm-component.md`):
новий підпакет `@7n/rules-win32-x64`, `win32-x64-msvc`-суфікс і `.dll`-гілка
(без `lib`-префікса) у `npm/scripts/lib/native.mjs`, третій рядок build-native
матриці `npm-publish.yml` (windows-latest, лише rules-napi — llm-lib v1-межа
не розширюється), гейт пінів і release-publish синк/publish-кроки для трьох
rules-платформ. Перша публікація `@7n/rules-win32-x64` через OIDC неможлива
(пакет ще не існує в registry) — потрібен ручний bootstrap-publish перед тим,
як trusted publisher зможе підхопити подальші релізи (той самий канон, що й
для darwin-arm64/linux-x64).
- Канонічні піни wasm-плагінів (фаза 6 v2): форма `{name, url, sha256}` у `wasmPlugins` з завантаженням у кеш `~/.cache/@7n/rules/plugins/`, sha256-верифікацією (skip-not-crash при mismatch) і атомарним записом; dev-форма `{name, path}` — лише поза CI
- Скіл `wasm-plugin` (фаза 6 v2, рішення І): LLM-флоу авторингу wasm-плагінів contract v3 — scaffold від шаблонів → wasip2-збірка → golden-тести через plugin-host → publish із sha256-піном; детермінований CI-смок ганяє той самий конвеєр без LLM

### Changed

- PURE-фінал native rule engine (фаза 5 v2): `adr/hooks`, `capacitor/platforms`, `image-avif/avif_generation` у `rules-core` (native-реєстр — 26); `main.mjs` цих трьох concern-ів видалено, `fix-avif_generation.mjs` став самодостатнім (власна копія read-only AVIF-скану замість імпорту з видаленого `main.mjs`)

### Fixed

- js-run/runtime: у `npm/scripts/lib/lint-surface/tests/wasm-plugin-e2e.test.mjs` замінено прямий `process.env.N_RULES_PLUGIN_CACHE_DIR` на `env` з `node:process`.

## [1.66.0] - 2026-07-31

### Added

- Пілот wasm-плагінів (фаза 6 v2, contract v3): секція `wasmPlugins` у `.n-rules.json` (dev-форма піна `{name, path}`), dispatch wasm-концернів через embedded wasmtime-host у lint-прогоні зі skip-not-crash; пілотний компонент `vue/tfm-translations` доводить parity з JS-реалізацією

### Changed

- YAML-кластер ч.2 native rule engine (фаза 5 v2): `hasura/internal_urls`, `text/formatting`, `tauri/release` у `rules-core` разом зі спільним `gha-workflow` helper-модулем (парсинг GitHub Actions workflow); native-реєстр — 19
- Фінальний важкий батч PURE ч.1 native rule engine (фаза 5 v2): `tauri/updater`, `tauri/tool_surface`, `security/trufflehog`, `changelog/presence` у `rules-core` разом зі спільними helper-портами (`template_subset::check_text_subset`, `package_manifest::get_monorepo_project_root_dirs`, `change_file::read_change_files`); native-реєстр — 23

## [1.65.0] - 2026-07-31

### Changed

- YAML-кластер ч.1 native rule engine (фаза 5 v2): abie-трійка `hc_pairing`/`ua_node_selector`/`ua_http_route` у `rules-core` разом із портами yaml/k8s-tree/kustomization-patches/overlay-paths/http-route helpers (native-реєстр — 16); мультидокументний YAML через serde_yaml

### Fixed

- git-reconcile тепер відсіює semantic obsolete переноси до PR і детально пояснює збережені worktree.

## [1.64.0] - 2026-07-31

### Changed

- Пілот native rule engine (фаза 5 v2): diagnostics DTO і три core-концерни (`text/forbidden-prettier`, `security/sample_secret`, `k8s/dremio_logging`) виконуються в `rules-core`; dispatch за Rust-реєстром `listNativeConcerns` перед резолвом `main.mjs`; версія DTO-контракту → 2
- Батч 2 native rule engine (фаза 5 v2): ще шість core-концернів у `rules-core` — `rego/tooling`, `doc-files/marksman_config`, `abie/firebase_hosting`, `abie/env_dns`, `hasura/migrations`, `image-compress/package_setup` (native-реєстр — 9); install-sanity-guard baseline перенесено в T0-фікс marksman_config із дружнім повідомленням
- TOML-кластер native rule engine (фаза 5 v2): `tauri/cargo_mutants_config`, `tauri/gitignore_target`, `tauri/linux_deps`, `tauri/core_test_isolation` у `rules-core` (native-реєстр — 13) разом із портами helpers `cargo-workspace`/`workspaces`/`findSrcTauriDirs`; T0-фікси tauri зроблені самодостатніми

## [1.63.0] - 2026-07-30

### Changed

- Filesystem scan через native (фаза 4a v2): `walkDir` делегує у `rules-core` (ignore-крейт) із семантикою globby (вкладені `.gitignore`, dot-файли, симлінки, fail-safe) — зафіксовано диференційним parity-тестом; порядок результатів тепер детермінований (відсортований)

## [1.62.1] - 2026-07-30

### Removed

- видалено невикористаний модуль walk-cache.mjs (0 runtime-споживачів; policy-lint-adapter.mjs передає new Map() напряму)

## [1.62.0] - 2026-07-30

### Changed

- Енумерація changed files через native (фаза 3 v2): `collectChangedFiles`/`collectChangedFilesSince` делегують у `rules-core` (porcelain-межа всередині крейта), фільтр worktree-чекаутів — у Rust із mirror-тестом еквівалентності до JS-реалізації walkDir

## [1.61.0] - 2026-07-30

### Fixed

- тести auto-rules і package_knowledge резолвлять плагінні каталоги через пакетні specifier-и замість відносних шляхів — Stryker dry-run у sandbox більше не втрачає плагінні правила і не падає, розблоковано lint test coverage-gate

## [1.60.0] - 2026-07-30

### Changed

- Worktree lifecycle через native mt-core (фаза 2 v2): `auto-worktree` створює/прибирає worktree прямими викликами `rules-core` → `mt-core` (git-dep `nitra/mt-rust`) замість spawn зовнішнього `mt` CLI; санітизація імені — канонічна `mt_core::sanitize`

## [1.59.2] - 2026-07-30

### Fixed

- Виправлено публікацію platform-пакетів native-аддона: fail-closed registry-гейт добудовує і публікує `@7n/rules-darwin-arm64`/`@7n/rules-linux-x64`, якщо їх немає в registry (1.59.0/1.59.1 вийшли без них — native addon був недоступний споживачам)

## [1.59.1] - 2026-07-30

### Changed

- Додано `mago` (carthage-software/mago) у реєстр `ensure-tool` — PHP-тулчейн для нового
`@7n/rules-lang-php` (mago format/lint/analyze); підтримано releases без `v`-префікса в тегу
(`entry.tagPrefix`) — mago тегується як `1.45.0`, не `v1.45.0`. Оновлено сумісний range
`@7n/rules-lang-php` у `KNOWN_PLUGIN_RANGES` (`^0.2` → `^0.3`).

## [1.59.0] - 2026-07-30

### Added

- release-smoke: black-box перевірка ОПУБЛІКОВАНОГО npm-набору (bun add з реєстру + version/range reconciliation) після кожного npm-publish

### Changed

- Додано підтримку native-аддонів для darwin-arm64 та linux-x64
- chore(release): синк пінів платформних napi-пакетів @7n/llm-lib

### Fixed

- KNOWN_PLUGIN_RANGES: підняти застарілі minor-лінії lang-плагінів (lang-js ^0.25, lang-python ^0.12, lang-rust ^0.15) — автоінсталяція знову ставить актуальні сумісні релізи замість старих minor
- post-tool-use-check: stderr-writer інжектується в runPostToolUseCheckCli — тест error-шляху більше не ллє фейкову помилку в stderr vitest-прогону

## [1.58.4] - 2026-07-30

### Fixed

- оновлено pin `@7n/llm-lib` до 2.13.9 (автосинк релізного трейна)

## [1.58.3] - 2026-07-30

### Fixed

- git-reconcile: контрактні тести JS boundary — transport failure ACP приходить у `callRunner` структурованою помилкою (infrastructure failure без max retry), batch не зависає

## [1.58.2] - 2026-07-30

### Fixed

- очікування GitHub checks і remaining summary у git-reconcile

## [1.58.1] - 2026-07-30

### Fixed

- Уніфіковано LLM model resolution у execution consumers та оновлено native addon для env-selector policy.
- Doc-files більше не обриває генерацію великих документів за timeout-ом і показує heartbeat очікування.

## [1.58.0] - 2026-07-30

### Added

- ci4: додано resolver package knowledge domains і schema knowledge graph v1
- Додано fail-closed loader knowledge plugin adapters
- ci4: додано evidence-backed expected overlay і deterministic gap engine
- Додано protected zones і atomic package knowledge publication
- Додано детермінований planner semantic chunks для package knowledge
- ci4: додано structured LLM claims map/reduce з fail-closed coverage і cache
- Додано deterministic package knowledge Markdown renderer з privacy-safe topic projections.
- Збережено stable package knowledge topic identity та protected zones під час однозначних rename/move; split і merge тепер блокуються explicit migration plan.
- ci4: додано golden corpus для Changeability, Gap і projection quality gates package knowledge.
- Додано automatic evidence-backed Expected ingestion з protected zones, scoped accepted ADR/spec і active JS/TS test assertions для package knowledge.
- Додано parser-backed active test expectation evidence для JS, Rust, Python і PHP package knowledge domains.
- ci4: додає strict entailment gate для evidence-backed implemented і expected claims.
- ci4: додано deterministic ingestion package-owned config, schema evidence та API contracts.
- ci4: runner додає package-owned config і contract fragments до package knowledge candidate.
- ci4: інтегрує strict evidence entailment gate у package knowledge runner.
- ci4: додає evidence-backed semantic claims для package-owned config і contract artifacts.
- ci4: fail-closed виявляє потрібні language extensions до вибору knowledge adapters.
- ci4: runner блокує відсутній language adapter і підтримує contract-only package domains.

### Changed

- Додано foundation для package-level knowledge graph: детерміновані canonical ID, provenance та fail-closed нормалізацію language fragments.
- ci4: перетворює package knowledge на evidence-backed business та architecture AS-IS fragments.
- ci4: уніфікує taxonomy Implemented і Expected claims та додає declaration evidence для кожної semantic unit.
- ci4: спільно використовує domain path і parser-derived call-edge projection у package knowledge adapters.
- doc-files: поглинає CI4 package knowledge, Marksman і VSCode tooling; legacy rule id ci4 автоматично мігрує до doc-files.

### Fixed

- ci4: atomic publisher прибирає stale generated package-knowledge pages без видалення legacy docs.
- ci4: package knowledge claims обмежують evidence межами chunk та виконують dependency waves fail-closed.
- ci4: автоматично зіставляє expected та implemented claims перед gap evaluation.
- ci4: runner автоматично зіставляє expected та implemented claims перед gap render.
- Виправлено quality gates для structured package knowledge та мовних extractor-ів.
- ci4: стабілізує Expected mapping ladder та coverage package-knowledge gates.
- ci4: завершує package knowledge pipeline — розділяє runner на fail-closed stages, уніфікує test config language plugins і закриває coverage/doc quality gates.
- ci4: додає повні JSDoc контракти для structured sources і language knowledge extractor-ів.

## [1.57.8] - 2026-07-30

### Changed

- release: @7n/rules@1.57.7

## [1.57.7] - 2026-07-30

### Fixed

- Герметичні tool-dependent тести для чистих CI-раннерів (ensure-tool cache isolation, GNU sed/tr Cyrillic portability, cold-cache timeouts)

## [1.57.6] - 2026-07-30

### Fixed

- git-reconcile перевіряє native mt contract і прибирає partial worktree після несумісного create

## [1.57.5] - 2026-07-29

### Fixed

- resolveModel-виклики переведено на tier-контракт ('min') після уніфікації model resolution (#291) — лагодить 32 фейли adr/hooks, docgen-*, cspell-fix

## [1.57.4] - 2026-07-29

### Fixed

- оновлено pin `@7n/llm-lib` до 2.13.3 (автосинк релізного трейна)

## [1.57.3] - 2026-07-29

### Changed

- LLM consumers переведено на universal model resolver; ACP-виклики без tier
дозволено лише в explicit interactive mode, legacy Claude ACP shim видалено.

## [1.57.2] - 2026-07-29

### Fixed

- release: реліз `@7n/llm-lib` без `@7n/rules` тепер форсить patch-реліз rules із автосинком exact-pin `@7n/llm-lib` (рекурентний дрейф llm-lib-pin зник); resolveCmd — чистий JS-скан PATH без субпроцесу which (EAGAIN-флейки run-shellcheck під повним прогоном)

## [1.57.1] - 2026-07-29

### Changed

- feat(llm-lib): v0.2.3 — pi-тіри на локальні моделі (оновлене рішення З.1)

## [1.57.0] - 2026-07-29

### Changed

- Уніфікація adr-normalize та test/coverage classify() на batch-хвилі submitBatch: кожна LLM-стадія — один виклик на всі незалежні items замість послідовного циклу (спека 2026-07-27-batch-local-avg-real-batches, кластер E)
- Додано taze-diff для порівняння залежностей маніфестів та readJsonOrNull

### Fixed

- Синхронізовано pin @7n/llm-lib з актуальною workspace-версією (2.12.3) — CI release lock-крок раніше не оновив npm/package.json
- оновлено LLM runtime pin

## [1.56.0] - 2026-07-29

### Added

- Спільний тестовий канон ci.artifact@1 дескрипторів (describeCiArtifactDescriptors) для мовних плагінів — usable з @7n/rules-lang-php і @7n/rules-lang-js

### Changed

- SKILL doc-aggregate: формулювання про кодові розширення тепер посилається на doc-files.extensions замість хардкод-переліку (як doc-files SKILL)

### Fixed

- git-reconcile інвентаризує untracked stash, валідовує conflict-aware intent, безпечно очищає absorbed/duplicate stash і звітує фактичний залишок sources

## [1.55.3] - 2026-07-29

### Changed

- Git-reconcile ancestry-aware групує local branch із tracking upstream без фізичного fast-forward перед аналізом

## [1.55.2] - 2026-07-29

### Changed

- Nested Composer workspace detection: `KNOWN_LANG_PLUGINS.php.maxDepth` 0 → 2, авто-детект правила
`php` тепер бачить вкладені `composer.json` (`services/api/composer.json`, `backend/composer.json`)

## [1.55.1] - 2026-07-29

### Fixed

- acp: create_session тепер завжди повертає реальну помилку handshake (напр. auth cursor-agent), а taze не повторює той самий провалений ACP-виклик по кожному major-пакету

## [1.55.0] - 2026-07-29

### Added

- doc-files batch-шлях (T8, `'comment+behavior'`/повний конвеєр) тепер хвильовий (`docgen-wave-batch`): секції → critique → refine → best-of-2 → judge одним `submitBatch`-викликом на хвилю замість one-shot-промпта на файл — та сама якість, що й послідовний `generateDoc`, без per-file await. Помилка обов'язкового виклику валить лише свій файл.

### Fixed

- npm: skill/rule docs — замінено приклади голого 'bun test' на 'bun run test' (npm/package.json#scripts.test = vitest run; bare bun test обходить це й ганяє несумісний нативний Bun test runner)

## [1.54.3] - 2026-07-29

### Fixed

- Git-reconcile відсіює вже опубліковані release entries, перевіряє final `bun.lock`, не виносить agent transcript у PR, коректно порівнює CI baseline і прибирає лише безпечні obsolete worktree

## [1.54.2] - 2026-07-29

### Changed

- Міграція worktree-інтеграції на native CLI mt

## [1.54.1] - 2026-07-29

### Changed

- release: @7n/llm-lib@2.12.1, @7n/rules@1.54.0, @7n/rules-lang-js@0.23.4, @7n/rules-lang-python@0.11.3, @7n/rules-lang-rust@0.14.2

## [1.54.0] - 2026-07-29

### Changed

- fix(ci): resolve repository lint findings

### Fixed

- image-compress: T0-autofixer fix-check.mjs runs @nitra/minify-image via bunx instead of npx — matches the read-only detector fix (v4 uses Bun.Image, a bun-only global)

## [1.53.0] - 2026-07-28

### Changed

- oxfmtrc: canonical trailingComma=none, arrowParens=avoid

### Fixed

- template-deep-merge: оновлювати елемент масиву (name/uses) on-place при drift замість дублювання (steps з тим самим name у workflow-фіксерах)
- Уточнено діагностичні коментарі `glob-compat.mjs`: crash `yield* … is not async iterable` (#203) корелює з тим, чи зовнішній `n-rules` викликаний через `bun x`, а не з ОС/Bun-версією; задокументовано, що `bun x <локальна-devDependency>` не застосовує `patchedDependencies`.
- buildDetectPlan: застосовувати rule-level applies-гейт (filterByRuleApplies), як у detectAll
- image-compress/check: run @nitra/minify-image via bun instead of npx/Node

## [1.52.2] - 2026-07-27

### Fixed

- KNOWN_PLUGIN_RANGES оновлено на сумісні з plugin API v2 лінії (ci ^2, lang-js ^0.23, lang-python ^0.11, lang-rust ^0.14, lang-php ^0.2) — автоматична інсталяція більше не тягне legacy-плагіни, які новий core виключає зі slot graph

## [1.52.1] - 2026-07-27

### Fixed

- Тести `runCli` детерміновані під `bun run --bun vitest`: reset `process.exitCode` явним `0` замість `undefined` (Bun ігнорує присвоєння `undefined`, і код протікав між тестами) + перевірка успішного виходу через аргумент `reallyExit`

## [1.52.0] - 2026-07-27

### Changed

- Universal typed slot bus (plugin API v2, phase 1): resolveSlotGraph/getSlotContributions/getSlotConsumers/loadSlotConsumer у npm/scripts/lib/plugin-slots.mjs, envelope validation і path-безпека для slots.provides/consumes, PLUGIN_API_VERSION 1→2, machine-readable plugin-manifest.json schema.

## [1.51.0] - 2026-07-27

### Added

- doc-files/docgen-files-batch: `defaultLocalProviders()` перенесено в `@7n/llm-lib/local-providers` — batch-шлях тепер конфігурує і omlx, і litellm одночасно замість лише omlx

### Fixed

- doc-files/docgen-gen, release: переформулювання у docs/main.md і docs/release.md для проходження cspell (repo-wide text-lint fix); .cspell.json: додано rebuildable/rollups до словника (npm/CHANGELOG.md не редагується вручну)

## [1.50.2] - 2026-07-27

### Fixed

- тести: детермінізм і сумісність з обома раннерами (vitest, bun test) — mirror-parity (регенеровані дзеркала npm-module/vue + гард на невстановлені плагіни), ensure-tool під bun test (захоплення оригіналів до моків, синхронна фабрика, afterAll-відновлення mock.module — прибирає протікання withLock у run-standard-lint), docgen-gen judge-тести без залежності від ambient N_CLOUD_MIN_MODEL, skills-хелпери n-rules-cli з явним root-параметром

## [1.50.1] - 2026-07-27

### Changed

- Описи release PR із `.changes/` та lockfile тепер відокремлюють product intent від фактичного runtime diff.

## [1.50.0] - 2026-07-27

### Added

- doc-files без LLM рендерить test-сценарії та повністю прокоментовані JS, Rust і Python файли, а для коротких comments додає лише відсутню «Поведінку»

### Fixed

- Прискорено k8s full lint і додано live статус черги

## [1.49.26] - 2026-07-27

### Fixed

- gate incompatible plugin API version and pin first-party plugin installs to a compatible semver range

## [1.49.25] - 2026-07-27

### Fixed

- Збережено --path scope у doc-files fixer

## [1.49.24] - 2026-07-27

### Changed

- git-reconcile формує PR descriptions із пріоритетом бізнес-контексту та архітектури за validated final diff

## [1.49.23] - 2026-07-27

### Fixed

- Treat reconcile diffs containing only release entries as patch-equivalent

## [1.49.22] - 2026-07-27

### Fixed

- Retry initial PR check discovery and prune rebuildable forensic dependencies

## [1.49.21] - 2026-07-27

### Fixed

- v8r: validate VS Code extension recommendations with the local catalog

## [1.49.20] - 2026-07-27

### Fixed

- Allow committed branch changes and keep empty PR check rollups fail-closed

## [1.49.19] - 2026-07-27

### Fixed

- Pin @7n/rules to llm-lib with aligned native addon metadata

## [1.49.18] - 2026-07-27

### Fixed

- Preserve independently verified coverage test batches through concern rollback

## [1.49.17] - 2026-07-27

### Fixed

- Keep git-reconcile progress live and PR readiness fail-closed

## [1.49.16] - 2026-07-27

### Fixed

- Обмежено npm-module репозиторіями з npm publisher topology

## [1.49.15] - 2026-07-27

### Fixed

- refresh canonical mutation results after generated coverage tests

## [1.49.14] - 2026-07-27

### Changed

- release: @7n/rules@1.49.9

## [1.49.13] - 2026-07-26

### Fixed

- Виправлено фактичний шлях worktree, setup failure і spawnSync-діагностику в git-reconcile
- pass coverage mutation verdict feedback to the next ladder rung

## [1.49.12] - 2026-07-26

### Fixed

- Git-reconcile отримав ANSI-free progress, heartbeat, canonical fixer pass і bounded parallel PR jobs.

## [1.49.11] - 2026-07-26

### Fixed

- Додано окремий 180-секундний cloud budget для survived-mutant coverage batch і deferred решти batch-ів

## [1.49.10] - 2026-07-26

### Fixed

- Зареєстровано схему для .kubescape-exceptions.json у каталозі v8r
- Поважати .n-rules.json:ignore у full-скані v8r

## [1.49.9] - 2026-07-26

### Changed

- Add configurable Git delta policy

## [1.49.8] - 2026-07-26

### Changed

- release: @7n/llm-lib@2.9.6, @7n/rules@1.49.7

## [1.49.7] - 2026-07-26

### Fixed

- Усунено порожні PR, помилкові empty cherry-pick, неповний cleanup aliases і зайві LLM-виклики в `git-reconcile`.

## [1.49.6] - 2026-07-26

### Fixed

- Дроблено oversized source-file coverage mutants на ізольовані групи по 20.

## [1.49.5] - 2026-07-26

### Fixed

- Додано безпечну telemetry batch verdict для coverage timeout-ів.

## [1.49.4] - 2026-07-26

### Fixed

- Додано безпечну telemetry batch verdict для coverage timeout-ів.

## [1.49.3] - 2026-07-26

### Changed

- `n-git-reconcile` починає bounded LLM-кроки на `min`, переходить на `max` лише після валідної відповіді з validation failure, захищає detached worktree за OID, порівнює tests із baseline `origin/main`, запускає doc-files/lint лише для змінених code directories, безпечно пропускає empty cherry-pick, ізолює nested `npx`, повертає non-zero для неповного triage і показує точний фазовий progress.

## [1.49.2] - 2026-07-26

### Fixed

- @7n/rules синхронізує точну версію @7n/llm-lib під час спільного release.

## [1.49.1] - 2026-07-25

### Added

- Додано JS-оркестрований skill `n-git-reconcile` для аналізу гілок, worktree та stash і перенесення корисних змін у готові PR. CI smoke-check використовує завантажений Linux native addon до release, тому перевіряє native-залежні CLI-модулі в тому самому runtime, що й publish.

### Fixed

- Виправлено profile генерації тестів для survived Stryker-мутантів

## [1.49.0] - 2026-07-25

### Added

- Додано JS-оркестрований skill `n-git-reconcile` для аналізу гілок, worktree та stash і перенесення корисних змін у готові PR. CI smoke-check використовує завантажений Linux native addon до release, тому перевіряє native-залежні CLI-модулі в тому самому runtime, що й publish.

### Changed

- Rust-крейти перейменовано: llm-cascade → llm-lib, llm-cascade-napi → llm-lib-napi, CascadeError → LlmError; napi-артефакти llm-lib-napi.`triple`.node; git-споживачам — dependency-alias llm-cascade = { package = "llm-lib" }
- n-taze: callRunner для cursor/codex передає tier 'avg' у runAcpAgent — модель тіру замість персонального CLI-конфіга (паритет з pi-гілкою)
- changelog presence: додано change-файл для змін у npm
- doc-files: генерація N файлів одним 2b-batch (submitBatch @7n/llm-lib) з per-item ізоляцією помилок; фолбек на послідовний шлях без native-аддона чи з deadlineAt

### Fixed

- changelog consistency: додано change-файл для змін у npm/rules/changelog
- scanGlob: захист від Bun.Glob#scan(), що повертає Promise (self-hosted Linux Bun 1.3.14) — детектори не валять весь lint-прогін
- Виправлено profile генерації тестів для survived Stryker-мутантів

## [1.48.2] - 2026-07-24

### Fixed

- n-rules.js CLI dispatch guarded behind isRunAsCli — testable без сайд-ефектів на import

## [1.48.1] - 2026-07-24

### Fixed

- lint --full: self-upgrade devDependency відкладено до ПІСЛЯ worktree-ізоляції, не забруднює дерево перед dirty-гейтом
- js/eslint detector: relative-шлях від oxlint резолвиться проти cwd перед відносним обчисленням — усуває '..'-шлях і DetectorError у --full поза .worktrees/

## [1.48.0] - 2026-07-24

### Added

- концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)
- coverage: спільні lib manifest-roots/lcov, подовжений full-таймаут для мутаційного тестування (×4), пілот classify (0.7) у цьому репо

### Changed

- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header/export JSDoc у конфігах demo
- doc_comments rollout: header-JSDoc у vitest.config

## [1.47.1] - 2026-07-23

### Fixed

- doc-files: явна діагностика замість тихого 0 кандидатів, коли задекларований плагін (.n-rules.json) не встановлений у node_modules

## [1.47.0] - 2026-07-23

### Added

- концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)
- coverage: спільні lib manifest-roots/lcov, подовжений full-таймаут для мутаційного тестування (×4), пілот classify (0.7) у цьому репо

### Changed

- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header/export JSDoc у конфігах demo
- doc_comments rollout: header-JSDoc у vitest.config

## [1.46.0] - 2026-07-23

### Added

- концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)
- coverage: спільні lib manifest-roots/lcov, подовжений full-таймаут для мутаційного тестування (×4), пілот classify (0.7) у цьому репо

### Changed

- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header/export JSDoc у конфігах demo
- doc_comments rollout: header-JSDoc у vitest.config

## [1.45.0] - 2026-07-23

### Added

- концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)

### Changed

- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header/export JSDoc у конфігах demo
- doc_comments rollout: header-JSDoc у vitest.config

## [1.44.1] - 2026-07-23

### Changed

- worktree-only скіли (n-lint/n-taze/n-adr-normalize): preflight і ensureRunningInWorktree визнають .claude/worktrees/ (harness Claude Code) як вже ізольований worktree — не пропонують створювати вкладене .worktrees/ дерево

## [1.44.0] - 2026-07-22

### Added

- концерн coverage правила test: гейт покриття/мутаційки як lint-детектор (--no-fix = CI-гейт), CoverageProvider порт у plugin-api (spec 2026-07-22 absorb-7n-test)

### Changed

- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header-JSDoc у vitest.config (T0 promote)
- doc_comments rollout: header/export JSDoc у конфігах demo
- doc_comments rollout: header-JSDoc у vitest.config

## [1.43.1] - 2026-07-22

### Fixed

- Storybook (правило `storybook`): виправлення хвилі 2a за результатами живого пілота app-скафолда на `gt`. (1) `.storybook/main.js` app-варіанту більше НЕ знімає `vite-plugin-pages` у `viteFinal` — знімання ламало `storybook build` глобально через непідтримуваний `<route lang="yaml">`-блок (`scaffold/template/app-main.js`, `APP_MAIN_JS_MARKERS`). (2) Storybook vitest-проєкт app-пакетів отримує ВЛАСНІ `quasar()`/`AutoImport()`/`Pages()`-плагіни замість успадкованого урізаного unit-конфіга (нові `vitest-config/template/app-storybook-project-entry.js` і `vitest.config.app.baseline.mjs`, type-aware вибір у `fix-vitest-config.mjs`, нові маркер-перевірки в `main.mjs`/`adopt/main.mjs`). (3) `storybook/hygiene` (undeclared-import і sass-variables) тепер перевіряє лише `type: 'library'` пакети — на app-пакетах давав хибні спрацювання на Vite `resolve.alias`-специфікаторах і на свідомо відсутньому `sassVariables`-маркері app-`main.js`. (4) Додано канонічний шаблон `.storybook/vitest.setup.js` (стандартний `@storybook/addon-vitest`-boilerplate) — генерується/перевіряється `scaffold`-концерном для обох типів пакета. (5) `npm/schemas/n-rules.json`: додано `storybook.detectApps`/`storybook.optOut` до кореневої схеми — без цього `additionalProperties: false` відкидав ці вже задокументовані поля `.n-rules.json` як невідомі.

## [1.43.0] - 2026-07-22

### Added

- docker: n-rules:bun-no-compile-маркер (# n-rules:bun-no-compile: <причина>) — генералізує native-addon-виняток на будь-яку недосяжну для checker-а причину неможливості bun build --compile (напр. динамічний import() рантайм-конфігу); вимикає вимогу компіляції й дозволяє mirror.gcr.io/oven/bun:* як фінальний stage

## [1.42.0] - 2026-07-22

### Added

- warnAboutRulesWithoutConcerns: попередження, якщо rule-id з .n-rules.json#rules не знайдено в жодному rulesDir (ядро+плагіни) — ловить дрейф конфігу після переїзду concern-ів у плагін

## [1.41.0] - 2026-07-22

### Fixed

- doc-files: прибрано безумовний T0 CRC-штамп для crc-mismatch (fix-check.mjs) — свіжий CRC поверх застарілого тексту назавжди маскував дрейф доки; тепер застаріла дока завжди регенерується fix-worker-ом (docgen). Guardrail: detectRefusalFiller ловить нові живі refusal-фрази локальної моделі («мені потрібен сам код», «щоб написати точну документацію», «I need the code»)

## [1.40.1] - 2026-07-22

### Fixed

- `lint --full`: загублений `await` перед `ensureRunningInWorktree` ламав кожен прогін (у т.ч. зсередини `.worktrees/`) з `TypeError [ERR_INVALID_ARG_TYPE]`; заодно — guard проти видалення auto-created worktree при частковому провалі перенесення змін назад, і stack trace для programmer-помилок у top-level catch

## [1.40.0] - 2026-07-22

### Changed

- doc-files: пакет покращень генерації на малих локальних моделях (gemma-4, живий бенч на efes/backend) — R9 дет-зрізання чат-преамбул + штраф скорера; Behavior-наратив замість дубля «Публічного API»; STYLE-заборони мета-фраз; анкор лише в Behavior-промпті (без дублю в Огляді); scoped read-only гарантія (без over-claim, який валив LLM-суддя); юніт-дайджест замість сирого src для великих файлів (`N_CURSOR_DOCGEN_DIGEST_TOKENS`); judge-refine — один локальний фікс за зауваженнями судді з guard-ами (`N_CURSOR_DOCGEN_JUDGE_REFINE=0` — опт-аут)

## [1.39.0] - 2026-07-21

### Added

- workspace_root: канон одного кореневого uv workspace на репозиторій (дзеркало rust/workspace_root), main.json auto-glob розширено на вкладені pyproject.toml

## [1.38.0] - 2026-07-21

### Fixed

- tauri/gitignore_target, tauri/core_test_isolation: інваріант workspace-кореня замість хибної typo-версії

## [1.37.0] - 2026-07-21

### Added

- skills/storybook: скіл `n-storybook` — тонка обгортка запуску канону Storybook (`@7n/rules-lang-js`): звичайний прогін `lint storybook` і `--adopt`-режим для rollout-у на пакетах з уже наявним ручним Storybook (ADR канон-storybook-для-vue-компонентних-бібліотек, Кластер 8)

### Fixed

- k8s: hasura_configmap/hasura_httproute тепер gated власним main.mjs (без false positive на ConfigMap/HTTPRoute без сусіднього Hasura Deployment); kubescape більше не падає fatal на Job/CronJob через auto-generated per-resource виняток C-0056/C-0018

## [1.36.1] - 2026-07-20

### Changed

- test

## [1.36.0] - 2026-07-20

### Added

- doc-files: секція «Публічний API» рендериться дослівно для експортів із JSDoc-описом (0 LLM-токенів, 0 перефразувань), LLM викликається лише на прогалини (без опису або зі stub-заглушкою «опис.»)

### Changed

- тестовий no-op touch README для перевірки npm-publish CI

### Fixed

- workspaces/resolve-js-root/package-manifest/tauri core_test_isolation: node:fs/promises#glob → Bun.Glob — платформна прогалина Node-compat шиму на self-hosted Linux Bun (export 'glob' відсутній), спостережено в @7n/rules-ci-azure lint job
- hook під Node (npx): «detector changelog/consistency: import впав: Bun is not defined» — top-level `new Bun.Glob(...)` у workspaces.mjs/package-manifest.mjs валив import детектора; усі використання Bun.Glob (разом із resolve-js-root і tauri/core_test_isolation) переведені на runtime-нейтральний scanGlob (Bun.Glob під Bun, node:fs/promises#glob під Node)

## [1.35.0] - 2026-07-20

### Added

- tauri: додано концерн gitignore_target — перевірка й автофікс ignore-запису ws/src-tauri/target/ у корінному .gitignore (typo-інцидент nitra/task)

### Fixed

- run-v8r: не рахувати ajv schema-compile-помилку (несправна зовнішня схема, напр. unicodeRegExp-несумісність у офіційній azure-pipelines-vscode/service-schema.json) як порушення файлу — друкує ⚠-попередження й пропускає, замість постійного false-positive ✖
- acp-runner (skill claude): watchdog дочірнього ACP-процесу — fail-fast, якщо агент не запустився чи вийшов до кінця ходу, замість вічного зависання connection.prompt

## [1.34.1] - 2026-07-20

### Fixed

- reexecIfPackageVersionChanged порівнював шлях effectivePackageRoot з BUNDLED_PACKAGE_ROOT замість версії — коли npx резолвиться в локальний node_modules/@7n/rules, шлях до і після self-upgrade однаковий, re-exec пропускався, і процес доводив сесію до кінця зі старим кодом у пам'яті V8

## [1.34.0] - 2026-07-20

### Added

- doc-files: Vue SFC-екстрактор (`<script setup>`) — extractFactsVue/extractUnitsVue через optional peer vue/compiler-sfc; props/emits/expose/слоти як публічний контракт, юніти зі span-корекцією (ADR 260719-2155)

### Changed

- release: @7n/llm-lib@2.8.3, @7n/rules@1.29.2

## [1.33.0] - 2026-07-20

### Added

- Per-категорійний автодетект плагінів: явний непорожній plugins (лише з @7n/rules-`category`-*) домішує автодетектом категорії, яких у ньому немає

## [1.32.2] - 2026-07-20

### Changed

- doc-files: згенеровано файлові доки для `k8s/dremio_logging/main.mjs` і `skills/taze/js/migration-cache.mjs`, що доти ламали tree-wide doc-files/check; вручну виправлено фабрикації локальної моделі (розбиті backtick-маркери, невірні твердження про fail-safe/scope кешу міграцій)

## [1.32.1] - 2026-07-20

### Changed

- fix(n-rules/n-taze): не забруднювати дерево перед worktree-гейтом і не губити untracked-директорії при поверненні змін (#160)

## [1.32.0] - 2026-07-19

### Changed

- lint: правило test переїхало у @7n/rules-lang-js (vitest/stryker/ізоляція тестів), а концерн cargo_mutants_config — у правило rust плагіна @7n/rules-lang-rust (семантично канон rust-тулчейну; активація за glob Cargo.toml, у rust-only репо працює без lang-js) — у ядрі 23 правила, мовних правил не лишилось

## [1.31.0] - 2026-07-19

### Changed

- sync: діапазони вже оголошених плагінів @7n/rules-* у package.json підіймаються до ^latest тим самим кроком, що й ядро — фікси плагінів доїжджають без ручного bun update (bun i сам по собі поважає lockfile); недоступний registry для окремого плагіна — warning і пропуск
- lint: правило style (stylelint/css/vue-стилі) переїхало з ядра у @7n/rules-lang-js — фінальний крок фази 5 spec lang-plugins-extraction по фронтенд-сімʼї

## [1.30.0] - 2026-07-19

### Added

- ensure-tool: закріплені версії зовнішніх CLI-тулів у tool-pins.json (без latest-lookup на звичайному install-шляху) + tool-pins-refresh.mjs для ручного рефрешу + tool-pins-freshness.test.mjs, що нагадує оновити піни після 30 днів

### Fixed

- ensure-tool: GitHub-токен у lookup релізів, redirect-fallback без API і fail-open детекторів на транзієнтні збої авто-встановлення тулів (rate-limit без tag_name більше не валить lint exit 2)

## [1.29.3] - 2026-07-19

### Changed

- release: @7n/llm-lib@2.8.3, @7n/rules@1.29.2

## [1.29.2] - 2026-07-19

### Changed

- fix(auto-worktree): на брудному дереві перед auto-create — інтерактивний y/N-запит закомить і запушити (`npx @7n/n push`) замість одразу кидати
- chore(adr): батч фонової нормалізації — чернетки doc-files/worktree-lifecycle у канонічні ADR

## [1.29.1] - 2026-07-19

### Changed

- release: @7n/rules@1.29.0, @7n/rules-lang-js@0.5.0

## [1.29.0] - 2026-07-19

### Added

- skipLocalTier для text/markdownlint: local-tier емпірично 0/24 успіхів (llm-trace.jsonl), ladder одразу стартує з cloud-min (ADR 260718-0754)

## [1.28.1] - 2026-07-19

### Fixed

- schemas/v8r-catalog: записи для knip-canonical.json (офіційна схема knip@5) і oxlint-canonical.json (any-заглушка) — прибирає «Could not find a schema» у v8r по data-файлах правила js/tooling

## [1.28.0] - 2026-07-19

### Changed

- lint: JS-сімʼя правил (js, bun, vue, js-run, js-bun-db, js-bun-redis, js-mssql, npm-module, tool-surface) переїхала з ядра у @7n/rules-lang-js (фаза 5c spec lang-plugins-extraction); ядро лишає рушій (auto-rules предикати, glob-to-regex і js-source-signals у scripts/lib — плагін імпортує їх назад через @7n/rules/scripts); залежності eslint/knip/jscpd/oxlint переїхали з ядра у плагін

## [1.27.0] - 2026-07-19

### Added

- LLM-ladder: окремий (більший) таймаут cloud-avg (N_CLOUD_AVG_FIX_TIMEOUT_MS, дефолт 180s) та concern-рівневий skipLocalTier (concern.json) — пропуск local-min/local-min-retry для concern-ів, де local-tier емпірично не встигає дати результат (js/eslint увімкнено за реальними даними прогону)

### Changed

- doc-files: ядро без вбудованих кодових розширень (фаза 5b spec lang-plugins-extraction) — перелік розширень, OKF-типи та мовні екстрактори (факти/юніти) приходять лише з декларацій активних lang-плагінів (contributes.docFiles.extensions + handler doc-files); JS-специфіка (extractFacts, units-js, мапа типів js/mjs/ts/vue) переїхала у @7n/rules-lang-js; без активного lang-плагіна скан не бачить джерел (graceful тиша)

### Fixed

- Червоний lint на main: дубльовані тести мосту auto-worktree (auto-worktree.test.mjs ↔ orchestrate.test.mjs) зібрано у спільний набір `describeAutoWorktreeBridge` (scripts/utils/tests/auto-worktree-suite.mjs); `tests/**` виключено з npm-tarball; `jscpd` (спавниться як `bunx jscpd`) додано в knip ignoreDependencies

## [1.26.0] - 2026-07-19

### Changed

- Фаза 5a spec lang-plugins-extraction: ядро — двигун без мовної специфіки. npm/bun-гілка taze виїхала в плагін `@7n/rules-lang-js` (EcosystemProvider; бекап воркспейсів, `bunx taze -w -r latest` + `bun install`, collectTazeDiff, SKILL-фрагмент); semver-примітиви (parseVersion/isBreaking) тепер визначені в `@7n/rules/plugin-api` (було — реекспорт із diff.mjs). Оркестратор — чистий цикл провайдерів (без вбудованих гілок), звіт — рівноправні секції екосистем; CLI `n-rules taze diff` резолвиться через handler lang-js. Автодетект — кореневий package.json → lang-js

## [1.25.1] - 2026-07-19

### Fixed

- мінімальний @7n/llm-lib піднято до ^2.8.2 — діапазон ^2.7.0 допускав версії з видаленим у pi-coding-agent 0.80.10 API (ModelRegistry.create/AuthStorage), що системно валило doc-files та інші LLM-ланцюжки (registry: Cannot read properties of undefined (reading 'create'))

## [1.25.0] - 2026-07-19

### Added

- taze: по завершенню переносить зміни з автоствореного worktree назад у вихідне дерево (untracked) і прибирає worktree
- lint --full: worktree-only гейт (auto-create + bring-back + cleanup), спільна логіка з taze винесена у scripts/lib/auto-worktree.mjs

## [1.24.0] - 2026-07-19

### Added

- Фаза 4b spec lang-plugins-extraction: SKILL-фрагменти плагінів — sync доклеює конвенційні `skills/<id>/SKILL.fragment.md` активних плагінів до синхронізованого SKILL.md між стабільними маркерами (ідемпотентний ре-синк, блок зникає без активних фрагментів). Мовні гілки taze SKILL.md (Rust: детекція/cargo-команди/перевірки; Python: uv-цикл/PEP-класифікація) переїхали у фрагменти lang-плагінів — канон ядра описує npm/bun-гілку і generic-скелет

## [1.23.1] - 2026-07-18

### Changed

- Оновлено peer-версію @earendil-works/pi-coding-agent/pi-ai до ~0.80.10 — підтримка gpt-5.6-sol через openai-codex backend

## [1.23.0] - 2026-07-18

### Changed

- Фаза 4a spec lang-plugins-extraction: doc-files отримав мовний extension-point — розширення (`.rs` → 'Rust Module', `.py` → 'Python Module') декларуються в маніфесті плагіна (`contributes.docFiles.extensions`, синхронно для hot-path hook), екстрактори фактів/юнітів — handler-модулем (`contributes.handlers['doc-files']`, вантажиться лише на шляху генерації). Rust-екстрактори (extractFactsRust + units-rs) виїхали в `@7n/rules-lang-rust`; ядро документує js/mjs/ts/vue вбудовано, .rs/.py — за активним lang-плагіном

## [1.22.0] - 2026-07-18

### Added

- taze: по завершенню переносить зміни з автоствореного worktree назад у вихідне дерево (untracked) і прибирає worktree

### Changed

- taze: worktree-only гейт сам створює .worktrees/branch-taze і продовжує там замість throw-and-stop

## [1.21.0] - 2026-07-18

### Changed

- Фаза 3 spec lang-plugins-extraction: lint-правила `rust` і `python` (main.mdc, концерни, rego, шаблони) виїхали з ядра у плагіни `@7n/rules-lang-rust`/`@7n/rules-lang-python` (contributes.rules) — резолвляться через resolveRulesDirs, автодетект/дзеркала/auto-rules працюють без змін для репо з активними плагінами. Mixin-теки CI-плагінів (Rego-gate lint-rust.yml/lint-python.yml) доповнюють правила плагінів-власників, як і раніше

## [1.20.1] - 2026-07-18

### Changed

- docs(adr): brainstorm — внутрішній паралелізм lint-оркестратора (#86)
- Оновлено внутрішній lint-оркестратор.

## [1.20.0] - 2026-07-18

### Changed

- Автодетект `@7n/rules-lang-rust` покриває монорепо: сигнал Cargo.toml шукається не лише в корені, а й у підтеках до 3 рівнів (обмежений BFS повз приховані/node_modules/target — дешево для hot-path hook) — Tauri-кейс `app/src-tauri/Cargo.toml` вмикає плагін без ручного `.n-rules.json`. Python лишається кореневим (uv-провайдер v1 обробляє тільки кореневий pyproject.toml)

## [1.19.1] - 2026-07-18

### Changed

- експортовано `parseNRulesCmd` і `relevantDomains` з `scripts/lib/lint-surface/ci-plan.mjs` — спільні хелпери для fix-хендлерів автоміграції service-канону (ci-azure, ci-github), усунуто jscpd-дублікат

## [1.19.0] - 2026-07-18

### Added

- js/eslint T0-патерн js-eslint-mechanical-text-fix — текстові заміни для suggestion-only правил поза покриттям oxlint/eslint --fix (наразі unicorn/prefer-number-is-safe-integer); зменшує обсяг порушень, що йдуть у LLM-ладдер

## [1.18.1] - 2026-07-18

### Fixed

- `resolveChangedBase` стійкий до stale-ref: merge-base рахується для обох кандидатів (`origin/main` і локальна `main`) і береться новіший (descendant) — у git-worktree застрягла локальна `main` більше не дає застарілу базу з фантомними «зміненими» файлами давно влитих PR (T0-фіксер створював фейкові change-фрагменти для незачеплених воркспейсів); фолбек на єдиний доступний ref офлайн/без remote збережено

## [1.18.0] - 2026-07-18

### Changed

- Фаза 2 spec lang-plugins-extraction: Rust/Cargo-провайдер taze виїхав із ядра в окремий плагін `@7n/rules-lang-rust` (автодетект за кореневим Cargo.toml, extension-point `taze`) — сигнатури порту EcosystemProvider не змінились, оркестратор більше не має вбудованих провайдерів (усі екосистеми — з плагінів)

## [1.17.3] - 2026-07-18

### Changed

- ci-plan: `domainKey` експортовано — fix-хендлери плагінів (автоміграція pipeline-ів) генерують ті самі ключі outputs, що й `ci plan`

## [1.17.2] - 2026-07-18

### Fixed

- run-fix.mjs: rollback перед перекиданням DetectorError з canonical re-detect — раніше worker/LLM-пошкоджений файл (напр. невалідний YAML) лишався на диску, бо виняток абортував прогін до звичайного snapshot.rollback()

## [1.17.1] - 2026-07-18

### Fixed

- lint: `--base <ref>` тепер діє і в чистому delta-режимі (без `--path`) — раніше явна база застосовувалась лише до перетину path ∩ дельта

## [1.17.0] - 2026-07-18

### Changed

- lint --path став diff-aware (перетин path ∩ git diff, full-scope концерни виключені; стара поведінка — --path --full), дозволено `lint <domain> --path`, нові --base/--repo-wide і команда ci plan (outputs для GitHub Actions/Azure)

### Fixed

- bun/licensee: crash самого тула (stderr/die) — fail-open warn-діагностика замість блокувального порушення; несумісність @npmcli/arborist із деревом bun install перманентно червонила repo-wide CI-гейт

## [1.16.1] - 2026-07-18

### Fixed

- taze-оркестратор на репо без кореневого package.json (чисто-Python/Rust): npm/bun-гілка тихо пропускається (лог + без npm-рядків у звіті) замість смертельного `bun install → exit 1` до екосистемних провайдерів. Знайдено live-прогоном lang-python плагіна на реальному Python-репо

## [1.16.0] - 2026-07-18

### Added

- k8s: новий концерн dremio_logging — logback.xml Dremio Helm-чарту має містити WARN-оверрайди для шести відомо-шумних loggerів (rego, XML через conftest, detection-only)

## [1.15.0] - 2026-07-18

### Added

- Плагінна архітектура мовних екосистем (фаза 1 spec lang-plugins-extraction): новий порт `EcosystemProvider` у `@7n/rules/plugin-api` (detect/available/backup/bump/diff/promptFor/cleanup + `assertEcosystemProvider`), taze-оркестратор веде не-npm гілки генеричним циклом по провайдерах — Rust як вбудований first-party (`rust-provider.mjs`), Python/uv виїхав у окремий плагін `@7n/rules-lang-python` (автодетект за кореневим pyproject.toml, extension-point `taze` — перший реальний споживач handlers-API). Плагін без rules/ із `contributes.rules:false` тепер легальний (лише handlers); провал однієї екосистеми не зупиняє інших

## [1.14.1] - 2026-07-18

### Changed

- docs(adr): brainstorm — внутрішній паралелізм lint-оркестратора (#86)
- Оновлено внутрішній lint-оркестратор.

## [1.14.0] - 2026-07-18

### Changed

- js/eslint: fix-worker.mjs — обмежений паралельний пул (MAX_PARALLEL_FILES=4) замість послідовного циклу по файлах; профайлінг спростував гіпотезу про bootstrap-накладні витрати сесії

## [1.13.1] - 2026-07-18

### Changed

- doc-files: виключити Storybook *.stories.* файли з обов'язкової генерації docs/<stem>.md (той самий glob-список, що *.test.*/*.spec.*/*.d.ts)

## [1.13.0] - 2026-07-17

### Added

- `skill pi/cursor/codex taze` — Python/uv-гілку (`pyproject.toml`) тепер теж покриває оркестратор: детермінований `collectUvDiff` (`npm/skills/taze/js/uv-diff.mjs`, парсинг PEP 508/PEP 440 через `smol-toml`, той самий caret-семантики принцип, що й для npm/Cargo) замість ручної класифікації. Bump — по кожній прямій залежності `uv remove` + `uv add --bounds lower` (`uv` не має єдиної команди для major-стрибка), і по одному ізольованому виклику раннера на кожен major-пакет. Якщо `uv` не встановлено — Python-гілка пропускається без блокування npm/Rust-гілок (pyproject.toml лишається в звіті)

## [1.12.0] - 2026-07-17

### Changed

- Додано прапор `--path <dir>` для `n-rules lint`: звужує файловий набір per-file правил до заданої піддиректорії, лишаючи корінь прогону (root-guard, `.n-rules.json`) незмінним — на відміну від `--cwd`. Несумісний з позиційним rule/concern-фільтром; у парі з `--full` full-вісь ігнорується (без machine-wide локу).

## [1.11.0] - 2026-07-17

### Added

- js/eslint: fix-worker.mjs з per-file циклом замість одного великого промпту на весь concern — усунення 100% timeout на драбині, виміряно на реальних lint-прогонах

### Fixed

- text/markdownlint: включати причину провалу markdownlint-cli2 у violation-повідомлення; js/knip: fixability=structural (LLM-ладдер завжди приречений на timeout)

## [1.10.0] - 2026-07-17

### Added

- `skill pi/cursor/codex taze` — Rust-гілку (`Cargo.toml`) тепер теж покриває оркестратор: детермінований `collectCargoDiff` (`npm/skills/taze/js/cargo-diff.mjs`, парсинг через `smol-toml`, той самий caret-семантики принцип, що й для npm) замість ручної класифікації, і по одному ізольованому виклику раннера на кожен major-крейт. Якщо `cargo-edit` не встановлено — Rust-гілка пропускається без блокування npm-гілки (перелік Cargo.toml лишається в звіті)

## [1.9.0] - 2026-07-17

### Added

- `skill pi/cursor/codex taze` тепер виконується через оркестратор (`npm/skills/taze/js/orchestrate.mjs`) замість одного величезного непрозорого ходу на весь монорепо: детерміновані кроки (бекап/масовий bump/diff/прибирання) без LLM, і по одному ізольованому, обмеженому виклику раннера на кожен major-пакет — падіння/timeout одного пакета не втрачає прогрес по інших

## [1.8.4] - 2026-07-17

### Fixed

- text/run-v8r: включати причину провалу v8r (stdout+stderr, без noise) у violation-повідомлення замість голого коду виходу
- k8s/manifests: форматування tests/check-schema.test.mjs (oxfmt)

## [1.8.3] - 2026-07-17

### Changed

- release: @7n/rules@1.8.2

## [1.8.2] - 2026-07-17

### Fixed

- bun/licensee: розрізняти crash інструмента (stderr) від реального ліцензійного порушення (stdout, --errors-only)
- k8s/manifests: знизити когнітивну складність ensureHasuraConfigMapRequiredEnv нижче порогу sonarjs

## [1.8.1] - 2026-07-17

### Changed

- docs(adr): нормалізувати чернетки ADR у канонічний MADR-формат

## [1.8.0] - 2026-07-16

### Added

- js/check: T0-фікс для .oxlintrc.json (копіювання/merge канону без LLM-ладдер)

## [1.7.6] - 2026-07-16

### Fixed

- Прибрано борг self-lint: `CLAUDE.md`-генератор вставляв список без порожнього рядка перед ним (MD032); дедуплікація `collectCapabilityPermissionIds` (tauri/tool_surface ↔ tauri/updater) та скелета "обхід тестових файлів" (test/no-console-store-restore ↔ test/vitest-api-conventions, винесено у test/lib/collect-test-file-offenders.mjs)

## [1.7.5] - 2026-07-16

### Fixed

- npm: підняти нижню межу @7n/llm-lib до ^2.7.0 (skills-cli.mjs імпортує ./acp, доданий лише у 2.7.0 — старіші 2.x без цього subpath ламали npx @7n/rules з ERR_PACKAGE_PATH_NOT_EXPORTED)

## [1.7.4] - 2026-07-16

### Fixed

- lint не бачить worktree-чекаутів (.worktrees/, .claude/worktrees/): виключені у walkDir, delta-списку змінених файлів і js/eslint (ESLint+oxlint)

## [1.7.3] - 2026-07-16

### Fixed

- Краш oxlint (напр. відсутній jsPlugin з канонічного `.oxlintrc.json`) тепер показує exit-код і хвости stderr/stdout замість глухого «json не розпарсено» — причину видно на CI одразу

## [1.7.2] - 2026-07-16

### Changed

- tauri release: рекомендувати завжди --target aarch64-apple-darwin замість universal-apple-darwin (lipo-merge не зливає другорядні [[bin]]-таргети)

## [1.7.1] - 2026-07-16

### Fixed

- vue: відновлено втрачений guide-контент (composition-api, quasar-ui, structure, testing, tfm, vite-config, vite-env, nheader-layout) + виправлено застарілий шлях у main.mdc
- restore: відновлено втрачений guide-контент для 21 правила з коміту da05f89d (docs-only guide/ concern-и), виправлено застарілі шляхи в abie/ga/php/python
- rules: розкладено docs-only guide/ по всіх 22 правилах на окремі per-concern директорії, реалізовано реальні check/policy де це мало сенс (vue tfm-translations, capacitor workspace:*, k8s hpa apiVersion, style admin-table/gap/quasar-fixes, tauri tool_surface), решту залишено docs-only зі звітом де check недоцільний

## [1.7.0] - 2026-07-16

### Changed

- `skill cursor/codex` тепер виконуються через `@7n/llm-lib/acp` (napi-міст до Rust `llm_cascade::acp`) замість власного JS ACP-клієнта; deprecated `skill claude` лишається окремим JS-шимом (`acp-runner.mjs`), бо Rust-крейт `claude` не моделює

## [1.6.0] - 2026-07-16

### Changed

- CI-концерни провайдерів: 12 workflow-концернів (lint_*_yml, npm_publish_yml, toolchain_cache, clean_merged_ignore_branches) перенесено у @7n/rules-ci-github; multi-dir mdc-інлайн mixin-концернів у дзеркала правил; рефакторинг aggregateRuleSources

## [1.5.1] - 2026-07-16

### Fixed

- sync-claude-config: відновлено `.gitignore.snippet` для ADR Stop-hook (загублено при переструктуруванні rules/adr на per-concern layout)

## [1.5.0] - 2026-07-15

### Added

- Нове правило `local-ai` (auto: завжди): інтеграція rtk (Rust Token Killer) для стискання виводу CLI-команд агентів. Sync вставляє fail-open PreToolUse hook у `.claude/settings.json` (`rtk hook claude`), preToolUse у `.cursor/hooks.json` (`rtk hook cursor`) та vendored pi-extension `.pi/extensions/rtk.ts`; від користувача потрібен лише `brew install rtk-ai/tap/rtk`. Вимкнення — `disable-rules: ["local-ai"]`.
- Механізм плагінів: поле `plugins` у .n-rules.json + автодетект CI-провайдера (файли CI → repository.url) з авто-встановленням devDependency; multi-dir завантаження правил (ядро + плагіни, merge концернів), маніфест плагіна `n-rules` (capabilities/handlers); правило `ga` винесено у @7n/rules-ci-github
- tauri/release: change-файл — обовʼязковий крок вирішення задачі (як lint/doc-files), не post-hoc фікс на сигнал `changelog/presence`.

### Changed

- docs(adr): merge pending ADR normalization batch

## [1.4.1] - 2026-07-15

### Fixed

- Виправлено застарілі шляхи npm/rules/js/js/data/tooling у повідомленнях перевірок tooling/check (пост-реструктуризація da05f89d)

## [1.4.0] - 2026-07-15

### Added

- Нове правило `local-ai` (auto: завжди): інтеграція rtk (Rust Token Killer) для стискання виводу CLI-команд агентів. Sync вставляє fail-open PreToolUse hook у `.claude/settings.json` (`rtk hook claude`), preToolUse у `.cursor/hooks.json` (`rtk hook cursor`) та vendored pi-extension `.pi/extensions/rtk.ts`; від користувача потрібен лише `brew install rtk-ai/tap/rtk`. Вимкнення — `disable-rules: ["local-ai"]`.

## [1.3.2] - 2026-07-15

### Fixed

- template-deep-merge: eslint (cognitive-complexity, no-negated-array-predicate) і cspell-фікси після PR #66

## [1.3.1] - 2026-07-14

### Changed

- docs(adr): merge pending ADR normalization batch

## [1.3.0] - 2026-07-14

### Changed

- doc-files: мʼякий дедлайн fix-pipeline тепер діє і всередині файлу — `generateDoc` ріже per-call LLM-таймаути під залишок бюджету рунга (`deadlineAt`), тож генерація, що не вкладається в рунг, обривається transient-помилкою сама, без батчу-зомбі поверх наступного rung-а і без «fix timeout» від backstop-таймера

## [1.2.2] - 2026-07-14

### Changed

- release: @7n/llm-lib@2.6.1, @7n/rules@1.1.0

## [1.2.1] - 2026-07-14

### Fixed

- Bun-сумісність тестового прогону: namespace-імпорт zod (фантомний __esModule на ESM-неймспейсах ламає vitest-interop), явний env: process.env у spawnSync skills-cli (Bun дає дітям snapshot оточення), чистка bun-node-* shim-тек з PATH для дочірнього v8r (node-shebang під --bun резолвився в bun і падав на node:sea); тест run-v8r приведено до контракту verbose-виводу (#44); root scripts.test → bun run --bun vitest run
- package-manifest: VALID_MAX_BUMPS → Set (oxlint prefer-set-has), дока maxBump освіжена

## [1.2.0] - 2026-07-14

### Added

- `skill`-CLI: додано зовнішній Codex-раннер (`skill codex <id>` → `codex exec -`); Cursor-раннер (`skill cursor <id>`) більше не deprecated — обидва повноцінні альтернативи вбудованому `pi`. `n-taze` доповнено Rust/`cargo-edit`-гілкою (детекція `Cargo.toml`, `cargo upgrade --incompatible allow`, `cargo fmt`/`clippy`/`test`) поряд з існуючою npm/bun-гілкою.
- `n-rules release`: додано `package.json#release.maxBump` — стеля на bump цього workspace (`major|minor|patch`); change-файл із `bump: major` понад стелею обрізається й друкує попередження, замість підняти major-версію. `@7n/rules` тепер зафіксований на `maxBump: minor` — власна major-версія пакета більше не змінюється автоматично через реліз-CI.

### Removed

- Команду `mt-run-node` (B2 executor-міст) видалено: fix-вузли MT-графа виконує вбудований шлях MT — підписочні CLI `claude|codex|cursor|pi` з user-level ENV-конфігом (`MT_AGENT_CLI`, `MT_CLOUD_AGENT_CLIS`, `MT_AGENT_CLI_MODEL_MAP`; локальні omlx — через pi.dev CLI), за mt ADR `260713-2110` «ACP — єдиний транспорт AI-викликів». `mt-tail` більше не конфігурує `node_executor` у `.mt.json`; тир-канон вузлів — `MIN|AVG|MAX`

## [1.1.0] - 2026-07-13

### Changed

- Пакет перейменовано: `@nitra/cursor` → `@7n/rules` (bin `n-rules` + перехідний alias `n-cursor`, конфіг `.n-rules.json` з авто-міграцією з `.n-cursor.json`, маркери блоків `n-rules:*`, `$schema` на `unpkg.com/@7n/rules/...`); legacy-назви читаються як fallback для безшовної міграції споживачів

## [14.33.0] - 2026-07-13

### Changed

- test.package_json: scripts.test має містити --bun разом з vitest (bun run --bun vitest run) — без Bun-рушія forked pool-процеси не резолвлять Bun-нативні built-in модулі (напр. import { SQL } from 'bun'); explain-повідомлення при violation пояснює причину

## [14.32.1] - 2026-07-12

### Fixed

- adr-normalize-local: self-upgrade нотифікація ensureNitraCursorInRootDevDependencies писала у stdout і псувала JSON-контракт n-cursor adr-normalize-local — перенесено у stderr

## [14.32.0] - 2026-07-12

### Changed

- Додано схему `layers-config` та тести для валідації конфігу шарової документації

## [14.31.2] - 2026-07-12

### Fixed

- doc-files/docgen-extract: розпізнавати raw-SQL tagged-template мутації (pgWrite`UPDATE ...`) як не-read-only

## [14.31.1] - 2026-07-12

### Fixed

- live-e2e фікси петлі lint-хвіст→MT→pi-harness (2026-07-12, реальний прогін довів фікс bun:test→vitest локальною omlx): a.md у канонічній section-формі MT (## Model tier/## Skills — runner не парсив YAML model_tier); ## Check несе --cwd ../.. (MT ганяє його з node-dir); root-guard cwd-aware при явному lint --cwd; mt-run-node пропускає devDeps-ensure; parseRule бере правило як останній токен Check-рядка (стійко до флагів)

## [14.31.0] - 2026-07-12

### Changed

- text/cspell канон: gitignoreRoot: «.» у snippet-шаблоні — обмежує збір .gitignore коренем репо, щоб cspell у git-worktree не тягнув .gitignore основного дерева і не ігнорував мовчки всі файли; T0 cspell-merge розкатує поле на споживацькі проєкти

### Fixed

- cspell у git-worktree мовчки ігнорував усі файли (gitignore основного репо через межу worktree): .cspell.json#gitignoreRoot обмежено коренем конфіга; runCspellText попереджає, коли 0 із переданих delta-файлів перевірено

## [14.30.0] - 2026-07-12

### Added

- Фаза C: маховик телеметрії — успішні agentic-фікси (інлайн-драбина + MT-вузли через mt-run-node) пишуть oldText→newText у глобальний distillation-стор (~/.n-cursor/telemetry, recordFixTelemetry з Ф1 нарешті підключено); широкі fix-вузли (≥4 файлів або whole-repo) отримують audit: required (collateral-контроль штатним MT-аудитом, поріг N_LINT_MT_AUDIT_FILES)

## [14.29.0] - 2026-07-11

### Changed

- MT-tail: колапс гейта — прибрано env-прапорець N_LINT_MT_TAIL, єдиний гейт тепер наявність .mt.json (onboarded-репо); rollout-guard більше не потрібен (B2 executor-міст у main). Cursor не onboarded → нуль ефекту тут; спрацьовує лише в MT-репо

## [14.28.0] - 2026-07-11

### Added

- B2 executor-міст: mt-run-node — MT node_executor виконує fix-вузол нашим pi-harness (тир-канон omlx/pi замість Claude); парсинг контракту вузла, tier-мапінг MIM/AVG/MAX, stdout {applied,touchedFiles}. CLI-субкоманда npx @nitra/cursor mt-run-node. ensureNodeExecutor у mt-tail автоконфігурує .mt.json node_executor при матеріалізації

## [14.27.0] - 2026-07-11

### Added

- B1-wiring: run-fix кличе materializeTail на невиправленому хвості за env-гейтом N_LINT_MT_TAIL (fail-open, дефолт off) — завершує B1 на n-cursor-боці; renderRemaining повертає зібрані порушення

## [14.26.0] - 2026-07-11

### Added

- B1 mt-tail: матеріалізація lint-хвоста у вузли MT-графа (клас materializeTail — плаский root-level вузол task.md/a.md за контрактом graph.md, ідемпотентність за сигнатурою, fail-open коли MT недоступний, env-гейт N_LINT_MT_TAIL); чисте ядро без MT-залежності, wiring у драбину — окремо

## [14.25.2] - 2026-07-11

### Fixed

- гейтувати raw stdout v8r (run-v8r) за --verbose у lint

## [14.25.1] - 2026-07-11

### Changed

- fix(lint): гейтувати raw stdout зовнішніх тулів за --verbose (#42)

## [14.25.0] - 2026-07-11

### Changed

- lint fix: anchored-edits дефолт → cloud (bench A/B 2026-07-11: паритет коректності, +10% латентності = страховка від collateral; off-switch N_LLM_FIX_ANCHORED=0); bench навчився A/B-прапорців N_LLM_FIX_ANCHORED / N_LLM_FIX_BENCH_VERIFY

## [14.24.0] - 2026-07-11

### Added

- lint fix: anchored-edits opt-in (Фаза A2) — env N_LLM_FIX_ANCHORED (1 = усі тири, cloud = лише не-local) вмикає строгий hash-anchored edit-профіль рунга; дефолт OFF до bench-валідації

### Fixed

- fix(text): завендорити зовнішні $ref у schemas/vendor/cargo.json (cargo-lints-*, quikrun) — v8r більше не фетчить їх мережею на кожен прогін

## [14.23.1] - 2026-07-11

### Changed

- fix(text): прибрати шумні "unknown format" попередження ajv/v8r для vendored oxc-схем (oxfmtrc/oxlintrc/cargo/zizmor)

### Fixed

- тестові фікстури no-bun-test-import/location/package_structure збираються динамічно — T0-autofix більше не переписує навмисні bun:test-рядки

## [14.23.0] - 2026-07-11

### Added

- lint fix: evidence-гейт рунга (Фаза A1) — fixCtx.verify (item-scoped canonical re-detect) + verifyMax per tier прокидаються у runAgentFix; провал verify інʼєктиться фідбеком у ту саму pi-сесію замість одразу нового рунга

## [14.22.1] - 2026-07-11

### Fixed

- test/no-bun-test-import, test/no-process-chdir: винесено спільну isTestFile/collectTestFiles у scripts/lib/collect-test-files.mjs (jscpd-дублікат); text/oxfmt: відформатовано n-cursor.js, n-cursor.d.ts, package_structure.test.mjs, core_test_isolation.test.mjs (CI на main після PR #30)
- test/no-bun-test-import: спростити AS-regex і винести prefer-static-regex у module scope (CI на PR #30)
- cspell: додано слова 'виносено', 'фіксери'; oxfmt --write повторно для 4 файлів (drift після попереднього коміту)
- hk.pkl: npm-tsc-types fix-команда додатково прогонить oxfmt --write на n-cursor.d.ts — усуває нескінченний цикл tsc-регенерація/oxfmt-дрейф на кожному коміті

## [14.22.0] - 2026-07-11

### Added

- image-compress/check: T0-фіксер (npx @nitra/minify-image --write) закриває needs-compression детерміновано, без LLM-ladder
- bun/package_json, js-run/jsconfig: T0-фіксери (canon merge/strip) закривають policy-deny детерміновано, без ручного втручання

### Changed

- bun/package_json: fix-package_json.mjs адаптує (не лише видаляє) виклики scripts.lint* — шукає й переписує bun/npm/yarn/pnpm-виклики у workflow yml, інших package.json та власних chained-скриптах на bunx n-cursor lint; скрипт лишається, якщо десь є нерозпізнаний виклик

### Fixed

- прибрано jscpd-дублікат applyToFiles (rust/toolchain_cache, tauri/linux_deps, ga/workflows → спільна npm/scripts/utils/apply-to-files.mjs), додано cspell-слова (бампі, джобі, тригереному)

## [14.21.3] - 2026-07-11

### Changed

- feat(test): concern no-bun-test-import — T0 auto-fix bun:test → vitest

## [14.21.2] - 2026-07-11

### Changed

- release: @nitra/cursor@14.21.1

## [14.21.1] - 2026-07-11

### Fixed

- v8r: вендорована схема package.json забандлена self-contained (eslintrc/prettierrc/stylelintrc/ava/semantic-release/jscpd/nodemon/quikrun інлайн — без per-run мережевих фетчів $ref), cacheTtl HTTP-кешу v8r піднято до доби; guard-тест на внутрішні $ref у vendor-схемах і валідні шляхи каталогу

## [14.21.0] - 2026-07-11

### Added

- text/cspell: useGitignore:true у каноні .cspell.json — cspell поважає .gitignore, білд-артефакти (target/, dist/) не потрапляють у full-прогін

## [14.20.0] - 2026-07-11

### Added

- tauri/updater concern: новий чек `quasar-dialog-plugin-missing` — застосунок з `useUpdater()`, але без Quasar-плагіна `Dialog` у `src/main.{js,ts}`, отримує явне порушення. Без цього чека `check()`/`downloadAndInstall()` відпрацьовували штатно, а `$q.dialog(...)` мовчки падав з `TypeError: e.dialog is not a function` — реальний production-інцидент 2026-07-11.

## [14.19.1] - 2026-07-11

### Fixed

- v8r-каталог: vendored-схеми для Cargo.toml і .cargo/mutants.toml (без мережевого fallback)

## [14.19.0] - 2026-07-11

### Added

- tauri/release: три нові перевірки + T0-autofix із реального факапу myllm — (1) push-auth крок у changelog-release.yml (git remote set-url з GITHUB_TOKEN; checkout@v6/ga-канон persist-credentials:false не лишає токена і release-push мовчки падає), (2) <app>/.changes/.gitkeep (після релізу порожній .changes/ ламає unmatched-paths-glob), (3) zizmor ignore[cache-poisoning] на Swatinem/rust-cache у release.yml (конфлікт rust-канону з zizmor)

## [14.18.1] - 2026-07-11

### Fixed

- v8r-каталог: .mcp.json (vendor-схема), tauri.conf.*.json overlay, .marksman.toml і deny.toml → permissive any.json — офлайн-резолв без schemastore-fallback

## [14.18.0] - 2026-07-10

### Added

- Новий concern `tauri/linux_deps`: у Tauri-проєктах (`src-tauri/Cargo.toml`) вимагає в `.github/workflows/lint-rust.yml` крок встановлення системних залежностей Linux (`libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`), без яких Clippy падає на збірці `-sys`-крейтів. T0-автофікс вставляє канонічний apt-крок перед `dtolnay/rust-toolchain@…` або дописує відсутні пакети в наявний `apt-get install`.

## [14.17.3] - 2026-07-10

### Changed

- fix(test): стаб kubescape у PATH — check-k8s kustomization-тести без мережевих зависань (#25)

## [14.17.2] - 2026-07-10

### Removed

- Видалено осиротілий rust coverage-провайдер (npm/rules/rust/coverage/) з тестами — мертвий код після видалення coverage-підсистеми у f88de05e

## [14.17.1] - 2026-07-10

### Fixed

- тести: env_dns детектор матчить https:// internal URL; прибрано осиротілий rules/js/coverage після видалення coverage-підсистеми; build-agents-commands тест оновлено до канонічної lint-поверхні

## [14.17.0] - 2026-07-10

### Added

- tauri: секція про MCP-міст для LLM-верифікації

## [14.16.3] - 2026-07-10

### Fixed

- Тести fix-pipeline більше не засмічують реальний `~/.n-cursor/llm-trace.jsonl`: у vitest-конфізі `N_LLM_TRACE_PATH` відводить wire-trace (`@7n/llm-lib/trace`) у tmp — фейкові chain-записи (probe/check, fake/min, fake/cloud) не потрапляють в аналітику myllm і chains-report

## [14.16.2] - 2026-07-10

### Changed

- fix(cli): backward-compat аліаси lint-text/lint-ga → lint text/lint ga

## [14.16.1] - 2026-07-10

### Fixed

- тести run-fix: oxlint no-empty-function/consistent-function-scoping у нових chain-телеметрія тестах (зелений Lint JS на main)

## [14.16.0] - 2026-07-10

### Added

- fix-concern chain: телеметрія шапки ланцюжка у фінальному записі — extra.problem (зведення порушень: кількість/reasons/files/sample), extra.resolvedBy ('t0' | tier:model), extra.t0Applied, extra.touchedFiles/touchedTotal (cwd-relative зміни permanent T0 + closing rung; rollback-нуті rung-и не рахуються)

## [14.15.0] - 2026-07-10

### Changed

- lint: видалено --read-only alias, канонічний прапорець --no-fix (docs/rules/rego/workflows синхронізовано)

## [14.14.0] - 2026-07-09

### Added

- tauri: T0-autofix для release/updater concern-ів — деталізований append-only канон у package.json, Cargo.toml, capabilities/*.json, tauri.conf.json, changelog-release.yml, release.yml і #[cfg(desktop)]-guard у lib.rs

### Fixed

- прибрати накопичений js/eslint-борг, що блокував Lint JS CI по всьому репо (падав на `main` кілька комітів поспіль, не по діапазону конкретного PR): early-exit у changelog/presence, застарілий glob-виняток hasura/js/tests (правило переїхало в hasura/internal_urls, ловило http:// у внутрішніх cluster-URL як insecure), cognitive-complexity у rust/toolchain_cache і scripts/utils/with-lock.mjs (обидва >20 — розбито на менші функції), negated-array-predicate у tauri/core_test_isolation, дубльований код rego/opa_check↔regal і python/mypy↔ruff (винесено в npm/rules/{rego,python}/lib/), knip-хибний "unused" для навмисно непрямої залежності ajv (пінінг для стабільного bun-hoisting v8r/ajv-draft-04, fb0710c9)

## [14.13.0] - 2026-07-09

### Added

- tauri: регламент випуску версій (changelog-release → app@X/vX → DMG) і автооновлення через @7n/tauri-components useUpdater

## [14.12.2] - 2026-07-08

### Fixed

- doc-files: parseDocFrontmatter не парсив tier (readDocTier завжди null) — doc-files stamp губив tier: і judgeModel: з frontmatter усіх док; тепер stamp зберігає tier/judgeModel, guard cloud-avg працює

## [14.12.1] - 2026-07-08

### Fixed

- changelog/consistency: skip merge-коміту працює і під час git commit незавершеного merge (MERGE_HEAD); база порівняння feature-гілки — новіший merge-base з локальної та origin-версії базової гілки (застарілий локальний main більше не фейлить реліз-коміти з origin/main)
- lint doc-files у fix-режимі: durable-write семантика — rollback після fix-timeout більше не стирає вже згенеровані доки, батч ріже беклог під м'який дедлайн рунга (resumable); index.md без H1 (MD025 clean); детермінований refusal/filler пре-гейт перед docgen-judge; рукописні доки без docgen-frontmatter не перезаписуються без --overwrite (issue #16)

## [14.12.0] - 2026-07-08

### Added

- text/run-v8r: схема vendor/mt-config.json для .mt.json/.mt-override.json (@7n/mt) + запис у v8r-catalog.json

## [14.11.2] - 2026-07-08

### Fixed

- npm-publish CI: release більше не пушить тег/коміт до підтвердження успішного publish; фейл одного пакета (llm-lib) не блокує публікацію іншого (@nitra/cursor).
- Виправлено bin[n-cursor] шлях (без зайвого ./), додано ajv як прямий dependency для стабільного bun-hoisting у споживачів, і smoke-check-imports.mjs для раннього виявлення регресій named export.
- PostToolUse hook: звіт про порушення тепер іде у stderr, а не stdout — агент бачить причину блокування замість порожнього «blocking error».

## [14.11.1] - 2026-07-07

### Changed

- chore: sync root devDependency @nitra/cursor to ^14.11.0

### Fixed

- npm-publish CI: очистити NODE_AUTH_TOKEN перед публікацією, щоб npm trusted publishing (OIDC) не падав з ENEEDAUTH (setup-node сам проставляє порожній/невалідний token, який перебиває OIDC-обмін).

## [14.11.0] - 2026-07-07

### Changed

- doc-files: правило мігровано на unified lint surface — видалено дублюючий legacy-концерн `doc-files/lint` (`lint-doc-files`/`fix-doc-files` як окремі CLI-команди більше не існують, детект+fix-by-default тепер лише через `npx @nitra/cursor lint doc-files [--no-fix]`); прибрано мертвий CLI-обгортковий код (`runDocFilesScanCli`/`runDocFilesCheckCli`/`runDegradedReport`) з `docgen-scan.mjs`, оновлено підказки в `docgen-files-batch.mjs`, `main.mdc` і скіл `doc-aggregate`

## [14.10.7] - 2026-07-07

### Changed

- npm-module: задокументовано канонічний hk.pkl-крок `npm-changelog` (fix-only, autofix через `N_CURSOR_CHANGELOG_AUTOFIX=1`) і додано валідацію в `package_structure` — fail на застарілий `check changelog` (команду `check` прибрано в v14) та fail, якщо кроку `npm-changelog` немає взагалі.

## [14.10.6] - 2026-07-06

### Changed

- feat(tauri): add core_test_isolation rule for LLM agent/app-shell decoupling

## [14.10.5] - 2026-07-06

### Changed

- changelog: додано change-файл для наявних змін у npm workspace

## [14.10.4] - 2026-07-05

### Fixed

- lint fix-ladder: відновлено per-tier таймаути ADR 260620-0556 (`N_LOCAL_FIX_TIMEOUT_MS`=45s, `N_CLOUD_FIX_TIMEOUT_MS`=120s) — `rung.timeoutMs` прокидається через `fixCtx` у default-worker → `runAgentFix`, runner тримає backstop ×1.25 навколо worker-виклику; зависла cloud-SSE більше не блокує lint назавжди

## [14.10.3] - 2026-07-05

### Changed

- release: demo@0.0.9, @nitra/cursor@14.8.7

## [14.10.2] - 2026-07-05

### Changed

- release: @nitra/cursor@14.8.6

## [14.10.1] - 2026-07-05

### Changed

- test(sync): оновити фікстури на канонічну hook-команду

## [14.10.0] - 2026-07-05

### Added

- Додано semantic-collateral veto для відхилення змін поза target-set

## [14.9.2] - 2026-07-05

### Changed

- rule_meta: rule-level main.json.llmFix скасовано — заборона залишкового поля (fix-можливість концерну = наявність `fix-*.mjs`/`fix-worker.mjs`, канон scripts.mdc); поле llmFix прибрано зі схеми rule-meta.json і з main.json правил doc-files/text; text/cspell-fix: мертву inline LLM-гілку `runCspellText` видалено (класифікація живе у fix-worker.mjs, Central Runner Pipeline), сигнатура спрощена до `(cwd, files)`

## [14.9.1] - 2026-07-05

### Fixed

- sync: legacy-маркер `@nitra/cursor post-tool-use-fix` у MANAGED_HOOK_COMMAND_MARKERS — resync тепер прибирає стару мутуючу PostToolUse-групу з `.claude/settings.json` споживацьких репо

## [14.9.0] - 2026-07-05

### Fixed

- js/check: детекція воркспейс-типів (node/vue) для eslint.config — T0 scaffold/merge замість LLM-перезапису; vue-воркспейс обовʼязково у vue: [...] getConfig

## [14.8.7] - 2026-07-05

### Changed

- rule_meta: rule-level main.json.lint скасовано — заборона залишкового поля (lint-scope декларується у `<concern>/concern.json#lint`); прибрано parseRuleLintSpec і поле lint зі схеми rule-meta.json; поле lint видалено з main.json 14 правил

## [14.8.6] - 2026-07-05

### Fixed

- text/cspell: T0 cspell-merge — merge-запис .cspell.json замість wholesale-перезапису (існуючі words/flagWords/ignorePaths зберігаються, канон лише дописується)

## [14.8.5] - 2026-07-05

### Fixed

- ga/js workflow-політики стали pin-aware: SHA-пін `owner/action@<40-hex>` (zizmor ref-pin, з тег-коментарем чи без) задовольняє канонічний `@vN` з template — фіксер більше не даунгрейдить SHA-піни до тегів (lint_ga, clean_ga_workflows, clean_merged_branch, lint_js_yml)

## [14.8.4] - 2026-07-05

### Changed

- fix(deps): bunx-тули lint-оркестрації (github-actionlint, jscpd, oxlint, v8r) перенесено з devDependencies у dependencies — devDeps опублікованого пакета споживачам не встановлюються, тож піни версій у consumer-репо не діяли (bunx тягнув latest); тепер bunx резолвить пінені версії з node_modules споживача. Узгоджено підказки правил npm-module/npm_package_json і bun/package_json (dev-тулзи → root, bunx-тули → dependencies)

## [14.8.3] - 2026-07-05

### Changed

- npm-module/npm_publish_yml: шаблон npm-publish.yml переведено на hardened push-auth патерн (checkout з persist-credentials: false + крок «Configure git identity + push auth» із job-scoped GITHUB_TOKEN через git remote set-url)

## [14.8.2] - 2026-07-05

### Changed

- LLM-шар npm/lib/pi-* винесено в окремий workspace-пакет @nitra/llm-lib (Ф1 спеки docs/specs/2026-07-05-llm-lib-extraction-spec.md); на місці лишились тимчасові re-export shims (pi-model-tiers, pi-one-shot, pi-agent-fix з інʼєкцією ast-extract, pi-agent-skill) до Ф2 import-rewrite. Поведінка consumers не змінилась.
- Ф2 виносу @nitra/llm-lib: усі consumers (docgen-gen/judge, cspell-fix, adr/normalize-pipeline, lint-surface default-worker/run-fix/tier-sampling-bench, skills-cli) переведено на прямі імпорти @nitra/llm-lib; тимчасові shims npm/lib/pi-* видалено разом із текою npm/lib. default-worker інʼєктить oxc ast-extract як deps.astContext (n-cursor-специфічний дефолт, який пакет свідомо не має).

## [14.8.1] - 2026-07-05

### Changed

- chore: regenerate n-cursor.d.ts after lint --help addition; chore: self-lint sync — ADR consolidation, doc CRC restamp, lint --help; fix(lint-rules): T0-fixers for licensee/deny.toml/hasura config gaps

## [14.8.0] - 2026-07-04

### Changed

- 🤖 feat(pi-lib): per-call стеля відповіді maxTokens (дефолт 8192, env N_PI_MAX_TOKENS) для агентних/one-shot pi-сесій через streamFn-обгортку — раніше кожен виклик успадковував стелю моделі (32768) незалежно від задачі

## [14.7.0] - 2026-07-04

### Added

- OKF-сумісний frontmatter (type/title/description/resource/tags/timestamp) у файлових доках та автогенерація index.md у кожній docs/-директорії

### Changed

- lint-оркестрація: scoped `lint <rule>` тепер ганяє лінтер+конформність (= standalone lint-<rule>); oxfmt інтегровано в `n-cursor lint` (fix-режим); CI-воркфлоу (ga/text/js/style) переведено на `n-cursor lint <rule> --read-only`; python-правило в оркестраторі з readOnly

### Removed

- package_json-конформність більше не вимагає lint-\* обгорток: видалено script-вимоги у ga/rego/docker/python (теки package_json), js-lint/style-lint/security (script-частини), прибрано bun-агрегат (umbrella lint + oxfmt-суфікс). Єдина точка лінту — n-cursor lint --full; php/rust/image-compress лишаються (справжні тул-команди, не обгортки)

## [14.6.1] - 2026-07-04

### Changed

- fix(pi-lib): omlx memory-guard rejection terminates instead of retrying

## [14.6.0] - 2026-07-04

### Changed

- rust-cache required for all rust-toolchain CI jobs, not only lint
- Додано підтримку PI CLI та оновлено схеми для GitHub Actions та zizmor

## [14.5.0] - 2026-07-03

### Added

- feat(adr): ADR_HOOKS_SKIP пропускає сесії оркестраторів (lint/skill/taze/release) в ADR Stop-хуках; capture-decisions.sh переходить на селектор CAPTURE_DECISIONS_BACKEND з дефолтом pi (npm-first, офлайн, герметичні прапори) — claude/cursor-agent лишаються opt-in через явний CAPTURE_DECISIONS_BACKEND=claude|cursor-agent|auto (spec 2026-06-30)

## [14.4.6] - 2026-07-03

### Changed

- adr

## [14.4.5] - 2026-07-03

### Fixed

- fix(bun): @stryker-mutator/core додано в allowed_root_test_deps — vitest-runner@9 вимагає core як exact-pin peer явною залежністю; без цього bun/package_json та npm-module/npm_package_json заганяли споживача у глухий кут (root забороняє, workspace виганяє в root)

## [14.4.4] - 2026-07-03

### Fixed

- fix(js): main.mdc — битий template-лінк на видалений js/jscpd (→ js/jscpd_config); синк правила js падав у споживачах на inlineTemplateLinks після 14.4.3

## [14.4.3] - 2026-07-03

### Changed

- md

### Fixed

- fix(sync): AGENTS.template.md і build-agents-commands — застарілі команди npx @nitra/cursor fix / fix changelog замінено на lint / lint changelog (команд fix немає з unified lint surface); генерований AGENTS.md у споживачах більше не радить неіснуючу команду
- fix(js): прибрано дубльований концерн js/jscpd (старий namespace js_lint.jscpd) — повний двійник js/jscpd_config по тому ж .jscpd.json; дубль давав два однакові заголовки в згенерованому n-js.mdc (MD024 у споживачах) і подвійну policy-оцінку

## [14.4.2] - 2026-07-03

### Changed

- docs(doc-files): full-регенерація док lint-surface після черги lint --full

## [14.4.1] - 2026-07-03

### Fixed

- fix(lint): повернуто залежність cli-progress у dependencies — 14.3.0 падав у проєктах-споживачах на імпорті progress.mjs (Cannot find package 'cli-progress'); залежність загубилась при злитті гілок роботи над прогрес-баром

## [14.4.0] - 2026-07-03

### Added

- feat(lint): глобальна черга `lint --full` з видимим прогресом (spec docs/specs/2026-07-03-lint-global-queue-design.md) — один full-прогін на машину через machine-wide лок у tmpdir (lint-surface/lint-lock.mjs поверх withLock: PID-живість, перехоплення лока мертвого процесу, TTL-дедуплікація за деревом+варіантом виклику, fail-closed таймаут 45 хв). Процес у черзі показує позицію, список черги і живий прогрес-бар активного прогону (publisher пише знімки ProgressReporter у progress.json). Дельта/scoped/--no-fix — без лока, паралельно як раніше.

## [14.3.0] - 2026-07-03

### Added

- feat(lint): живий прогрес-бар для довгих lint/fix-прогонів — гібрид «бар по концернах + тикер порушень (знайдено/виправлено)» за spec docs/specs/2026-07-03-lint-progress-bar-design.md. Спільний ProgressReporter (lint-surface/progress.mjs) із семантикою лічильників «не бреше вниз»; TTY → однорядковий бар (cli-progress, функція-formatter — обхід несумісності з ESM-only string-width), не-TTY → append-рядки без ANSI (hooks/CI без змін). doc-files батч-генерація мігрована на той самий reporter (бар по файлах).

## [14.2.4] - 2026-07-03

### Changed

- docs(doc-files): масова регенерація файлової документації по репо

## [14.2.3] - 2026-07-03

### Changed

- docs(cspell-fix): актуалізувати доку main.mjs — detectCspell zero-files fix

## [14.2.2] - 2026-07-03

### Changed

- fix(lint-surface): policy-концерни оцінюються напряму з concern.json — прибрано generated main.mjs

## [14.2.1] - 2026-07-03

### Changed

- fix(lint-surface): policy-концерни оцінюються напряму з concern.json — прибрано generated main.mjs

## [14.2.0] - 2026-07-03

### Added

- Додано experiment-only harness для tier sampling/consensus у lint fix ladder.

## [14.1.0] - 2026-07-02

### Added

- feat(fix): `T0Pattern.standalone: true` — central fix-pipeline пропускає початковий detect для concern-ів, чиї T0-патерни всі самоаналізуючі (ідемпотентний `apply()` без потреби в `test()`-гейті на реальних violations); post-T0 re-detect лишається єдиним джерелом правди. Позначено `text/markdownlint`, `text/run-shellcheck`, `text/run-dotenv-linter`, `python/ruff`, `style/lint`, `rust/check` (fmt-крок) — економить один зайвий детект-прохід на "брудному" шляху. Зворотно сумісно: патерни без `standalone` працюють як раніше. Деталі — `docs/specs/2026-07-02-text-check-per-file-split-design.md` §8.

## [14.0.0] - 2026-07-02

### Changed

- breaking(lint): розбито bundled full-scope concerns на per-file (`text/check`, `python/check`, `php/check`, `rego/check`, kubeconform у `k8s/manifests`) + policy повних concern-ів, що не можуть коректно працювати на delta-підмножині (`js/knip`, `js/jscpd_duplicates`, `bun/licensee`, `security/scan`, `security/trufflehog`, `vue/packages`, `k8s/manifests`, `python/project`, `php/project`, `rego/conftest_verify`) — прибрано `lint.glob`, тепер лише `--full`/scoped. Новий дешевий per-file companion `changelog/presence` компенсує втрату delta-changeset-гейта в `changelog/consistency` (тепер теж full-only). `text/oxfmt` — `full`→`per-file` (main.mjs вже підтримував `ctx.files`). Паралелізовано registry-виклики в `changelog/consistency`. Деталі — `docs/specs/2026-07-02-text-check-per-file-split-design.md`.

## [13.4.2] - 2026-07-02

### Changed

- spec

## [13.4.1] - 2026-07-02

### Changed

- docs(fix): загострено опис `fixability` у схемі concern.json — per-category семантика (усі мітки виконують T0, різниця після неї: `code` → повна pi-agent-ladder; `config` — детермінований канон-фікс → fail-fast; `structural` — ризикована авто-правка → fail-fast), + мотив config-vs-structural розрізнення.

## [13.4.0] - 2026-07-02

### Changed

- feat(fix): fixability-маршрутизація fix-движка — config/structural concern-и більше не палять LLM-ladder. Нове поле `fixability` (`code`|`config`|`structural`, дефолт `code`) у `concern.json` (+ схема, + парсинг у concern-meta). `fixConcern` після фази T0 гейтить: `config` (canon/settings/tooling — детермінований regen) і `structural` (дублікати/розташування — ризикована авто-правка) повертають fail-fast без входу в pi-agent-драбину, замість марного ескалювання local→cloud tier-ів. Розмічено 76 config + 3 structural concern-и; 87 code-concern-ів (джерельні лінти, включно з k8s/abie yaml-маніфестами) лишаються ladder-eligible.

## [13.3.7] - 2026-07-02

### Fixed

- fix(js): jscpd-чистота self-lint — оновлено `.jscpd.json` ignore під міграційний+vendor дрейф (vendor-схеми `npm/schemas/vendor/**`; snippet-template glob із policy-path на concern-path `npm/rules/*/*/template/*.snippet.{yml,yaml}`; мігровані js-lint→js/k8s→manifests/test canonical-шляхи; `.codex/hooks/**` дзеркало). Реальний дубль `withTimeout` (тричі скопійований abort-aware timeout-танець) винесено у спільний `npm/lib/pi-with-timeout.mjs`, який тепер імпортують pi-one-shot/pi-agent-skill/pi-agent-fix. Тест-скаффолдинг adr `capture-decisions` (спільні `transcriptJsonl`/`runCaptureHook`) — jscpd-ignore одного з пари.

## [13.3.6] - 2026-07-02

### Fixed

- fix(js): knip-чистота self-lint — прибрано мертвий код (dispatcher/trace.mjs після видалення trace-субкоманди, doc-aggregate re-export shim на стару layout, exitCodeFor/generateDenyTomlLicenses unused-exports, осиротілі coverage-fix тести); виправлено JSDoc-import шлях docgen-extract-anchors; knip.json entry/ignoreDependencies під flat-concern layout і binary-deps (@7n/mt, github-actionlint, v8r).

## [13.3.5] - 2026-07-02

### Fixed

- fix(text): схема `concern.json` покриває реальні flat-concern форми — порожній `policy: {}` (маркер policy-поверхні), `policy.namespace` (rego-namespace), `policy.files.glob`. Разом із catalog-мапінгом усі 166 concern.json проходять v8r (замість «could not find a schema»); завершує self-lint `lint text --full` по v8r.

## [13.3.4] - 2026-07-02

### Changed

- release: @nitra/cursor@13.3.2

## [13.3.3] - 2026-07-02

### Fixed

- fix(text): `v8r`-крок self-lint (`lint text --full`) тепер повністю офлайн — усі схеми (власні: `n-cursor.json`, `rule-meta.json`, `skill-meta.json`, `concern.json`, `target.json`; сторонні: `package.json`, `tsconfig.json`, `jsconfig.json`, `.oxlintrc.json`, `.oxfmtrc.json`, `.cspell.json`, `knip.json`, `.jscpd.json`, `.claude/settings.json`, `.codex/hooks.json`, `action.yml` — вендорені в `npm/schemas/vendor/`) резолвляться локально через `customCatalog` замість мережевого `-c`-каталогу. Прогін ~7хв → ~5с. Якщо файл усе ж не покритий локальним каталогом, v8r іде мережевим fallback (schemastore.org) без фейлу, але тепер друкує попередження в stdout із порадою додати схему в `npm/schemas/v8r-catalog.json`. У споживачів немає нових вимог — фікс лише self-lint репозиторію-джерела.

## [13.3.2] - 2026-07-02

### Fixed

- fix(text): v8r-каталог мапить `concern.json` (flat-concern layout) на схему `@nitra/cursor/schemas/concern.json` — прибирає «could not find a schema» на self-lint репо-джерела (`lint text --full`), де існують `npm/rules/*/*/concern.json`. У споживачів таких файлів немає — поведінка не змінюється.

## [13.3.1] - 2026-07-02

### Changed

- chore(lint): кодова база та конфіги `@nitra/cursor` чисті під власним 13.x full-scan self-lint — eslint warn-advisory (jsdoc-типи, cognitive-complexity extraction), markdownlint (генеровані/дзеркальні теки в ignores, MD036/MD060 off), oxfmt, knip entry-патерни під flat-concern layout, cspell-словник (укр. форми). Поведінка не змінилась.

## [13.3.0] - 2026-07-01

### Changed

- feat(cli): `npx @nitra/cursor` self-upgrade — якщо `@nitra/cursor` уже в `devDependencies` зі старішим числовим піном, він апгрейдиться до `^<версія CLI>` при кожному запуску (ніколи не понижує; `workspace:*`/`latest`/git-піни й записи в `dependencies` не чіпаються). Прибирає дрейф версії self-lint у споживачів.
- chore(js): кодова база `@nitra/cursor` тепер чиста під власним 13.x oxlint+eslint-каноном (jsdoc-описи, no-await-expression-member, switch-case-braces, prefer-static-regex, prefer-await тощо). Розблоковує self-lint після апгрейду root-devDep до `^13.2.6`; поведінка не змінилась.

## [13.2.6] - 2026-07-01

### Changed

- chore(ci): lint-js re-runs on .oxlintrc.json change; clarify test override

## [13.2.5] - 2026-07-01

### Changed

- chore(eslint): disable prefer-specific-assertions for tests (3.10.3)

## [13.2.4] - 2026-07-01

### Changed

- fix(deps): drop root markdownlint-cli2 workaround (violated text policy)

## [13.2.3] - 2026-07-01

### Changed

- fix(text): add Ukrainian apostrophe paragraph to text rule mdc (+mirror)

## [13.2.2] - 2026-07-01

### Fixed

- CI-воркфлоу lint-_: усі шаблони lint-_.yml.snippet кличуть `bunx n-cursor` (не bare —
  падало exit 127 у CI). Виправлено ім'я правила у lint-js.yml (`js` замість неіснуючих
  `js-lint js-lint-ci`). jscpd_config_test.rego: аліас import (unsafe var під OPA 1.15).

## [13.2.1] - 2026-07-01

### Added

- ga/workflows: детекція + T0-фікс bare `n-cursor` у run-кроках → `bunx n-cursor`
  (у CI n-cursor не на PATH, `run: n-cursor …` падав exit 127). Оновлено шаблон lint-ga.

## [13.2.0] - 2026-07-01

### Added

- T0 детермінований автофікс без LLM для механічних родин порушень: ga/workflows
  (persist-credentials, paths-glob), k8s/manifests (spec.strategy, networkpolicy egress,
  schema-modeline, kustomization sort), а також прогін лінтерів у fix-режимі —
  oxlint/eslint `--fix`, oxfmt `--write` (новий концерн text/oxfmt), stylelint `--fix`,
  markdownlint `--fix`, ruff `check --fix`/`format`, cargo fmt, shellcheck, dotenv-linter.

### Changed

- c4

### Fixed

- Резолюція залежностей markdownlint: примусова інсталяція micromark 4.0.2 (bun не
  вкладав його для markdownlint@0.40, через що text/check падав на `import { parse }`).
  Оновлено @nitra/eslint-config до 3.10.3 (прибирає транзитивну micromark 2.x).

## [13.1.0] - 2026-06-30

### Changed

- k8s: auto-upgrade HTTPRoute apiVersion v1beta1 → v1, forbid v1beta1

## [13.0.3] - 2026-06-30

### Fixed

- doc-files/docgen-crc: `stampDoc` зрізає лише справжній провідний H1 (`# `), а не будь-який `#`-рядок — раніше `LEADING_H1_RE` помилково з'їдав перший H2-заголовок секції (напр. `## Огляд`) при переш­тампуванні frontmatter. Регекс звужено до `/^# [^\n]*\n+/u` (узгоджено з `H1_RE` у docgen-gen).

## [13.0.2] - 2026-06-30

### Fixed

- changelog/consistency: detector пропускає merge-коміти (HEAD з 2-м предком) — merge інтегрує вже задокументовану роботу, тож autofix більше не створює шумний «Merge…» changeset, який CI commit-back каскадив у зайвий patch-реліз.

## [13.0.1] - 2026-06-30

### Changed

- Merge remote-tracking branch 'origin/main' into unified-lint-surface

## [13.0.0] - 2026-06-30

### Changed

- vue rule: vite >=8.1 + css.transformer lightningcss
- Додано нові devDependencies та оновлено правила для Vite >= 8.1
- Додано підтримку ESLint та markdownlint-cli2 для лінтування
- Міграція concern-структури: `rules/<id>/js/<concern>.mjs` → `rules/<id>/<concern>/main.mjs` + `concern.json`; fix-\*.mjs і шаблони перенесено у concern-теки
- Unified lint surface: усі concern-и зведено до одного detector-контракту `lint(ctx) → { violations }` (read-only); fix став окремою роллю central pipeline (T0 → tier-ladder). Policy-concern-и генеруються codegen-ом (`@generated main.mjs` + source-hash drift-gate). CLI: `n-cursor lint` (fix-by-default) / `lint --no-fix` (detect-only); прибрано `fix`, `fix-t0`, `--read-only`, `llmFix`. Видалено старий conformance/orchestrator-стек (`run-lint`, `run-rule`, `orchestrator`, `run-conformance-check`, `t0`).

## [12.19.0] - 2026-06-28

### Added

- vitest-api-conventions: Vitest API конвенції для LLM-генераторів тестів

### Changed

- Оновлення форматування виводу для `doc-files`, логіки `fix` скриптів і детермінованого CRC-repair файлової документації.

## [12.18.0] - 2026-06-28

### Changed

- Видалено escalation-лог та його тести, оптимізовано pi-agent-fix та orchestrator

### Fixed

- CLI lint: не відкидати перший позиційний rule-аргумент, коли `--cwd` не передано

## [12.17.0] - 2026-06-27

### Changed

- Перехід на globby для обходу файлової системи з урахуванням .gitignore

## [12.16.2] - 2026-06-27

### Changed

- feat(orchestrator): skip non-actionable violation (не годувати агента tool-crash); feat(pi-agent-fix): логувати вхід LLM у trace (violation + розмір промпта)

## [12.16.1] - 2026-06-26

### Changed

- docs(skill-meta): уточнено опис tier — абстрактна тира (capability), не провайдер; кожна каскадить local→cloud

## [12.16.0] - 2026-06-26

### Added

- feat: skills-cli pi-runner + pi-agent-skill — надбудова skills-CLI поверх pi

### Changed

- feat: pi-native agentic fix-engine — міграція з omlx на pi (squash)

## [12.15.1] - 2026-06-26

### Changed

- spec

## [12.15.0] - 2026-06-26

### Changed

- Видалено скіл start-check та пов'язані файли

## [12.14.0] - 2026-06-26

### Changed

- Заміна picomatch на ignore для обробки glob-ів у docgen та resolve-target-files

## [12.13.0] - 2026-06-26

### Changed

- Заміна рекурсивного обходу файлів на використання globby для пошуку файлів

## [12.12.0] - 2026-06-25

### Changed

- Додано автоматичне додавання посилань на template-файли у main.mdc
- Додано підтримку Blue Oak Bronze+ для перевірки ліцензій у `bun`, `python` та `rust

## [12.11.3] - 2026-06-25

### Changed

- rm doc-aggregate; dep policy

## [12.11.2] - 2026-06-25

### Changed

- docs: файлова документація для fix-<concern>.mjs та суміжних файлів; feat(fix): розкладаємо T0-паттерни по правилах — fix-<concern>.mjs конвенція

## [12.11.1] - 2026-06-25

### Changed

- ♻️ refactor(scripts): T0-autofix контракт fix-<concern>.mjs — нова конвенція в scripts.mdc

## [12.11.0] - 2026-06-25

### Changed

- Додано перевірку на наявність `CHANGELOG.md` та його формату в workspace-ах

## [12.10.0] - 2026-06-24

### Changed

- Оновлено LLM-виклик для повернення багатого об'єкта з reasoning та thinkingBudget

## [12.9.0] - 2026-06-24

### Changed

- Додано підтримку hook-режиму для linting через `n-cursor
- Додано перевірку порту 8080 для HTTPRoute та політики HealthCheckPolicy

## [12.8.9] - 2026-06-22

### Changed

- ♻️ refactor(npm): Вдосконалено логіку `full`-scope правил у delta-режимі

## [12.8.8] - 2026-06-22

### Changed

- ✨ feat(rules): policy mdc для всіх npm/rules/\*/policy/<concern>/

## [12.8.7] - 2026-06-22

### Changed

- ✨ feat(rules/abie): http_route_base.mdc + активація + ua-домени у ua_http_route.mdc

## [12.8.6] - 2026-06-22

### Changed

- ✨ feat(rules/abie): http_route_base.mdc + активація + ua-домени у ua_http_route.mdc

## [12.8.5] - 2026-06-22

### Changed

- ♻️ refactor(npm): Перехід з `style-lint` на `style` у правил та конфігах

## [12.8.4] - 2026-06-22

### Changed

- ♻️ refactor(npm): Перехід з `style-lint` на `style` у правил та конфігах

## [12.8.3] - 2026-06-22

### Changed

- ♻️ refactor(npm): Перехід з `style-lint` на `style` у правил та конфігах

## [12.8.2] - 2026-06-22

### Changed

- ♻️ refactor(docs): Оновлення логіки та формату в документації

## [12.8.1] - 2026-06-22

### Changed

- ♻️ refactor(docs): Оновлення логіки та формату в документації

## [12.8.0] - 2026-06-21

### Added

- doc-files: orphan-detect і auto-purge — `lint-doc-files` і `fix-doc-files` тепер виявляють і видаляють `docs/<stem>.md` чиї source-файли більше не існують

### Changed

- Переміщено логіку linting до `js` та оновив `n-cursor.js

## [12.7.0] - 2026-06-21

### Added

- doc-files: orphan-detect і auto-purge — `lint-doc-files` і `fix-doc-files` тепер виявляють і видаляють `docs/<stem>.md` чиї source-файли більше не існують

### Changed

- Переміщено логіку linting до `js` та оновив `n-cursor.js

## [12.6.1] - 2026-06-21

### Changed

- Оновлено text rule docs і helper-приклади на n-cursor lint text без package.json lint-text wrapper.

## [12.6.0] - 2026-06-21

### Changed

- Видалено правила для package.json та оновлено логіку перевірок інструментів

## [12.5.0] - 2026-06-21

### Changed

- Уніфіковано image-compress, docker і k8s lint через n-cursor lint <rule> без package.json wrappers; image-compress read-only використовує @nitra/minify-image --json.

## [12.4.0] - 2026-06-21

### Changed

- Уніфіковано selection linter-фази: unscoped n-cursor lint бере правила з .n-cursor.json, а meta.json#lint лишається тільки scope-класифікацією.

## [12.3.3] - 2026-06-20

### Changed

- ♻️ refactor(pipeline): Оновлено логіку ADR-захоплення та цілісність збірки

## [12.3.2] - 2026-06-20

### Fixed

- fix-каскад: per-tier timeout (локалі fail-fast ~45s замість стіни 120s, env N_LOCAL_FIX_TIMEOUT_MS/N_CLOUD_FIX_TIMEOUT_MS) + хмарний транспортний збій (pi ETIMEDOUT/spawn) обриває драбину замість ескалації на cloud-avg — не палиться avg-бюджет

## [12.3.1] - 2026-06-20

### Changed

- npm-module конформність: module-level JSDoc → pointer (`/** @see ./docs/… */`) у docgen-crc/extract-anchors/files-batch/judge-measure, doc-files/lint, lint/orchestrate, doc-aggregate/docgen-ignore; doc-CRC перештамповано; eslint --fix-стан прийнято як канон

## [12.3.0] - 2026-06-19

### Added

- lint --full: резюме викликів моделей у stdout (локальна / cloud-min / cloud-avg) через reportRunStats/summarizeCalls

## [12.2.0] - 2026-06-19

### Changed

- lint fail-fast лише для --read-only: у fix-режимі per-file правило з ненульовим кодом не спиняє — прогін доходить до кроку виправлення (конформність-драбина). + applyChanges створює батьківську теку (mkdirSync) перед записом. + детермінований T0-патерн changelog-create-change-file (writeChange замість LLM)

## [12.1.0] - 2026-06-19

### Added

- fix-конформність: драбина ескалації моделей (local-min → local-min+feedback → cloud-min → cloud-avg) із per-рунговим escalation-логом (.n-cursor/fix-escalation.jsonl, поле diagnosis) і кепом --max-avg; прибрано --max-iter/MODEL_HEAVY
- analyze-escalation: аналіз escalation-логу хмарною avg-моделлю (чанкінг + map-reduce) → markdown-звіт із пропозиціями нових T0-патернів / правок .mdc / змін скриптів; CLI n-cursor analyze-escalation і авто-хук наприкінці lint --full (kill-switch N_CURSOR_FIX_ANALYZE)

## [12.0.3] - 2026-06-18

### Fixed

- release: анотовані теги пушаться на origin

## [12.0.2] - 2026-06-18

### Fixed

- worktree: інжектований preflight-снипет і правило `worktree.mdc` кличуть `npx @7n/mt worktree …` (локальний резолв через node_modules/.bin), а не bare `mt` — узгоджено зі стратегією залежності `@7n/mt` (через npx, не глобально).

## [12.0.1] - 2026-06-18

### Changed

- k8s: канонічний образ hasura/graphql-engine → v2.49.2.ubuntu.amd64 (мінімальна підтримувана версія)

## [12.0.0] - 2026-06-18

### Removed

- worktree: керування git-worktree винесено у `@7n/mt` (`mt worktree`), cursor на нього спирається (спека docs/specs/2026-06-16-worktree-lifecycle-to-mt.md). **Breaking:** прибрано команду `n-cursor worktree` (`worktree-cli.mjs`/`lib/worktree.mjs` + тести) і скіл `worktree`. Worktree-only-конвенція лишається: інжектований preflight-снипет (`worktree-notice.mjs`) і правило `worktree.mdc` тепер кличуть `mt worktree create` (ефемерний — `remove` прибирає й гілку); bootstrap спрощено до `bun install` (ETARGET-retry-обгортка прибрана — `mt` бінарник, не npx-резолв). Додано залежність `@7n/mt@^0.5.0` (платформні бінарники тягне транзитивно). `.worktrees/` лишається gitignored; інвентар тепер у `.worktrees/.meta/`.

## [11.4.1] - 2026-06-16

### Fixed

- release: commit-back більше не «вдається» мовчки за відхиленого push — результат `git push` тепер ЯВНО перевіряється (раніше тихий runGit повертав null, а реліз рапортував успіх, через що npm міг піти попереду git). За non-fast-forward (паралельний push у ту саму гілку) release-коміт автоматично rebase-иться на свіжий upstream, теги пересуваються на новий HEAD і push повторюється (до 5 спроб); без upstream або при rebase-конфлікті — кидаємо помилку (exit 1), тож CI-публікація не відбувається без приземленого commit-back.

## [11.4.0] - 2026-06-15

### Changed

- Додано логіку відсіювання неактуальних правил/скілів за `availableRules`/`availableSkills

## [11.3.0] - 2026-06-15

### Changed

- lint: opt-in `meta.json: llmFix:true` тепер реально дротується (раніше прапор був декоративний — opportunistic LLM-fix біг просто на `!readOnly`). `runLint` читає `llmFix` з meta правила й передає в `lint(files, cwd, { readOnly, llmFix })`; правило без прапора лишається detect-only. Це й забезпечує safety-тріаж зі спеки (логічні лінтери не вмикають LLM-fix випадково). doc-files і text позначені `llmFix:true`; cspell-класифікація гейтиться через `llmFix` (проведено `runLintTextCli`/`runLintTextSteps`/`runCspellText`), standalone `lint-text` передає `llmFix:true`. Принагідно: justified `no-unsanitized/method`-disable на package-internal динамічний import у `runLint` (pre-existing).

## [11.2.0] - 2026-06-15

### Changed

- cspell-fix: заміна зламаного whole-file rewrite на classify→.cspell.json. Стара схема (llmLintFix whole-file) давала timeout 120с та parse-fail на реальних файлах; нова класифікує знахідки через bounded omlx-запит (≤80 слів → компактний JSON) і авто-дописує валідні слова у .cspell.json словник (sorted+dedup), а ймовірні одруки емітує на рев'ю без авто-застосування. Підтверджено на репо: 73 слова у словник за ≈5с (vs 120с timeout раніше).
- cspell-fix: whole-file omlx-апплай → класифікація+словник (нова схема, спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md). Старий підхід (модель повертає весь файл як JSON) на укр+тех-репо операційно ламався — 120с-таймаути, memory-guard reject, parse-fail (вимір: 1406 знахідок / 292 файли, ~90% — валідні терміни, не одруки). Новий fix-режим робить **один bounded omlx-виклик**: класифікує distinct «Unknown word» → валідні слова авто-дописує у `.cspell.json#words` (sorted/dedup, видно в diff) → ймовірні одруки лишає списком на рев'ю (НЕ авто-виправляє — апплай небезпечний) → re-detect. read-only незмінний (нуль мутацій). Єдиний knob моделі — `N_LOCAL_MIN_MODEL` (прибрано `N_CURSOR_FIX_MODEL` із цього шляху). Preflight omlx (fast-skip замість приречених викликів), cap класифікації з логом надлишку. `groupFindingsByFile` → `unknownWords`/`appendWordsToDict`.
- llm: спільний `preflightLocalModel(model)` у `npm/lib/llm.mjs` — omlx health-check (memory-guard / down / auth) як одна цеглина opportunistic LLM-fix tier (спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md). Прибрано дубльовані локальні `preflightProblem` із `doc-files/js/docgen-files-batch.mjs` (генерація) і `text/lint/cspell-fix.mjs` (класифікація) — обидва тепер кличуть спільний хелпер із `N_LOCAL_MIN_MODEL`. Per-target loop/circuit-breaker лишається у doc-files (cspell — single-call). Поведінка незмінна.

## [11.1.0] - 2026-06-15

### Changed

- doc-files lint-крок: opportunistic LLM-fix tier (спека docs/specs/2026-06-15-opportunistic-llm-fix-tier.md). У fix-by-default (не `--read-only`) крок тепер не лише детектить застарілі доки, а й намагається їх **згенерувати** локальною моделлю, якщо omlx піднято; недоступний omlx → fix пропускається з повідомленням і лишається exit 1 (гейт тримається, без false-green). `--read-only` (CI/hook) лишається суто детектом — нуль мутацій/LLM. Прапор `meta.json: llmFix:true` (схема `rule-meta.json`) — opt-in лише для контент-правил; логічні лінтери НЕ вмикати (LLM-правка коду може змінити поведінку). Спільне ядро генерації винесено в `runGenerationBatch`/`preflightProblem` (експорт з `docgen-files-batch.mjs`) — перевикористовують і батч-CLI `fix-doc-files`, і lint-крок.

## [11.0.0] - 2026-06-15

### Removed

- CLI: прибрано надлишкові точки входу заради мінімальної поверхні. `lint-ci` видалено — це був чистий аліас `lint --read-only --full` (CI лишається тим самим прапор-комбо). Deprecated-аліас `doc-files <sub>` (`scan|check|gen|stamp`) видалено — 0 живих callerів (hook давно на `lint-doc-files`, скіл на `lint-doc-files`/`fix-doc-files`). Заодно полагоджено застарілу документацію точок входу в `bin/n-cursor.js` (мертві згадки `fix`, опис `lint` → data-driven по `meta.json:lint`) і схему `rule-meta.json` (enum `quick|ci` → `per-file|full`, відповідає реальним значенням і `parseRuleLintSpec`).

## [10.3.0] - 2026-06-15

### Added

- adr-normalize: локальний конвеєр-оркестратор як backend (retrieval→edge-judge→cluster→gen на малій моделі); субкоманда adr-normalize-local + backend-селектор у normalize-decisions.sh (local|claude|cursor)

## [10.2.0] - 2026-06-15

### Added

- tool-surface rule — паритет «UI ↔ LLM ↔ оркестратор»: будь-яка дія фронтенду виконувана без UI через спільний каталог тулів (catalog → dispatch → UI/CLI/LLM-адаптери). Платформо-незалежне ядро; per-stack деталі делегуються правилам tauri/vue/capacitor. Авто-активація на фронтенд-залежностях. Додано per-stack секцію «Tool Surface у Tauri» в правило tauri (делегація в Rust-крейт/бінарник, два транспорти, схеми через schemars, дозволи плагінів).

## [10.1.0] - 2026-06-15

### Changed

- Конформність-селекція: .n-cursor.json — єдине джерело правди. resolveCheckRuleIds бере активні правила прямо з конфіга (available ∩ enabled), .cursor/rules/\*.mdc лишається лише fallback'ом коли конфіга нема. Прибрано дрейф «правило enabled, але .mdc нема → тихо пропущено». Per-rule whitelist-гейт у runRuleCli видалено як дубль — гейтинг живе виключно у селекції.

## [10.0.1] - 2026-06-14

### Changed

- 📝 docs(fix/lint): omlx-генерація доків для нових/перероблених файлів (point 4 docgen)

## [10.0.0] - 2026-06-14

### Changed

- Інлайн \_fix-check/fix-t0 у прямі функції — видалено внутрішні subcommand'и. runFixCheck (scripts/lib/fix/run-fix-check) + listProjectRulesMdcFiles винесено з bin; orchestrator/t0/PostToolUse-хук/lint-конформність кличуть прямо (без subprocess-обгортки). Per-rule ізоляція збережена

## [9.4.0] - 2026-06-14

### Changed

- docgen: класифікація omlx-збоїв (transient/systemic/permanent) — ретрай ETIMEDOUT з backoff (2s→8s), circuit-breaker на systemic-каскад (3 підряд → abort, exit 2), permanent→skip; pre-send byte-guard (джерело > 0.5× контексту → instant-skip без LLM); scan поважає .gitignore; прибрано хардкод DEFAULT_OMLX_MODEL (fail-loud, модель через N_LOCAL_MIN_MODEL)

## [9.3.0] - 2026-06-14

### Added

- per-tool omlx-фіксер (point 4): cspell autofix одруків через omlx у fix-режимі lint-text; спільне ядро llm-fix-apply (parse/read/apply, перевикористане llm-worker); generic llm-lint-fix; re-detect лишає валідні терміни для словника. Відновлено канон lint-text.yml --read-only (відкочений паралельним агентом)

## [9.2.0] - 2026-06-14

### Changed

- docgen: класифікація omlx-збоїв (transient/systemic/permanent) — ретрай ETIMEDOUT з backoff, circuit-breaker на systemic-каскад (exit 2), permanent→skip; scan поважає .gitignore; прибрано хардкод DEFAULT_OMLX_MODEL (fail-loud, модель через N_LOCAL_MIN_MODEL)

## [9.1.1] - 2026-06-14

### Changed

- 📝 docs(lint): перенести doc оркестратора lint-cli.md → rules/lint/js/docs/orchestrate.md

## [9.1.0] - 2026-06-14

### Changed

- rules/lint/ home: оркестратор lint-cli.mjs → npm/rules/lint/js/orchestrate.mjs, rules/lint зареєстровано як правило (meta.json + no-op fix.mjs); движок лишається в scripts/lib/fix (spec consolidation §7)

## [9.0.0] - 2026-06-14

### Removed

- Повністю видалено скіл /n-fix (раніше делегат): функціонал у /n-lint. Прибрано npm/skills/fix, дзеркала .cursor/skills/n-fix + .claude/commands/n-fix.md, запис у CLAUDE.md; auto-skills тести оновлено

## [8.0.0] - 2026-06-14

### Changed

- CI lint read-only: lint-text.yml → n-cursor lint-text --read-only, lint-style.yml → stylelint без --fix; канон оновлено синхронно (snippet+formatting.mjs+rego-тести+mdc) — CI нуль мутацій

## [7.1.0] - 2026-06-14

### Added

- lint-text підкоманда приймає --read-only (детект без авто-фіксу markdownlint/shellcheck/dotenv) — для CI-режиму без мутацій

## [7.0.0] - 2026-06-14

### Changed

- lint --full поглинає конформність (колишній fix): read-only → детект через per-rule fix.mjs run(); fix → convergence-движок. Конформність як whole-repo фаза лише у --full
- lint приймає фільтр правил (lint <rule>) → конформність лише цих правил (мапить колишній fix <rule>); hk pre-commit changelog → lint changelog
- governance: знято заборону паралельного eslint — паралельно по диз'юнктних файлах дозволено; серіалізувати лише whole-tree прогони того самого корпусу (CLAUDE.md-секція + n-lint SKILL)

### Removed

- Видалено команди fix/check/fix-run; рух-движок конформності переміщено skills/fix/js→scripts/lib/fix і поглинуто в lint. PostToolUse-хук → read-only детект усіх правил (без роутингу). /n-fix → делегат на /n-lint. fix-t0/\_fix-check лишаються внутрішніми фазами движка

## [6.0.0] - 2026-06-14

### Added

- fix-doc-files: пер-файловий таймінг у виводі — `<total>s (llm <llmS>/<N> calls, orch <orchS>)`: видно, скільки часу зайняла модель (і кількість LLM-викликів) проти JS-оркестрації. `generateDoc` повертає `llmMs`/`llmCalls`; облік через прозору обгортку `callLlm` (синхронні spawnSync-виклики, послідовна генерація — без гонок).

### Changed

- lint: вісь scope per-file|full (база-origin) + вісь behavior fix-by-default/--read-only; meta.json:lint hard-rename quick|ci→per-file|full; lint-ci=--read-only --full; контракт lint(files,cwd,{readOnly})

## [5.4.0] - 2026-06-14

### Added

- doc-files: міграція скіл→правило `npm/rules/doc-files/` (git mv `js/`); нові команди `lint-doc-files` (детермінований детектор stale `missing`∪`crc-mismatch`, exit 1; режими `--json`/`--missing-only`/`--hook`/`--git`/`--degraded`) і `fix-doc-files` (local-only генерація + `--stamp`); `lint`-адаптер агрегатора (`lint:"quick"`); `doc-files <sub>` стає делегувальним deprecated-аліасом (plain `check` exit 2→1; `--hook`/`--git` лишають exit 2); hook-маркер `@nitra/cursor lint-doc-files` із cleanup legacy `doc-files check`; тонкий скіл doc-files.

### Fixed

- lint-js: 12 oxlint-помилок — RS\_\*-регекспи догори module scope і прибрані невикористані (docgen-extract, docgen-protected.test), повернуто JSDoc extractReasoning на місце (omlx), async без await (rule-predicates), сигнальний re-raise замість process.exit у with-lock (коректні коди 130/143), опис @returns (units-rs)
- doc-files check <paths>: перший позиційний шлях мовчки губився без --max (порівняння зі значенням argv[maxIdx+1] при maxIdx=-1); значення прапорців тепер виключаються за індексом (--max, --root) + регресійні тести

## [5.3.4] - 2026-06-11

### Changed

- ✨ feat(npm/skills/doc-files): Rust (.rs): тести extractFactsRust у docg…

## [5.3.3] - 2026-06-11

### Changed

- ✨ feat(npm/skills/doc-files): doc-files: підтримка Rust (.rs) — SOURCE\_…

## [5.3.2] - 2026-06-11

### Changed

- release: @nitra/cursor@5.3.1

## [5.3.1] - 2026-06-11

### Fixed

- lint: усунено eslint/oxlint-помилки (24) — ігнор кореневого `.worktrees/` в eslint.config, GENERIC_RE→масив дрібних patternів (sonarjs/regex-complexity), Array#sort→toSorted; cspell: додано `ollama` у словник

## [5.3.0] - 2026-06-11

### Added

- js-lint: нові JS-файли створюються з явним розширенням .mjs/.cjs (не .js); приклади нового вихідного коду в js-run/js-bun-db/vue переведено на .mjs. test: підтримка vitest.config.mjs — pool-forks і stryker_config приймають .mjs/.js (новий канон .mjs, legacy .js не дублюється), stryker.configFile приводиться до фактичного імені. style-lint: чекер розпізнає stylelint.config.mjs/.cjs та .stylelintrc.mjs/.cjs.
- llm wire-trace: always-on багатий JSONL-запис на кожен `callLlm` (обидва канали — reasoning + слід) у `<cwd>/.n-cursor/llm-trace.jsonl` (gitignored, недеструктивна ротація 50 MB, kill-switch `N_CURSOR_LLM_TRACE=0`); `callOmlxRaw` дістає reasoning_content/usage/finish_reason/attempts (`callOmlx` лишається `string`-обгорткою); `fix`+`coverage-classify` мігровано з прямого `callOmlx`/`pi`-spawn на спільний `callLlm` (caller-мітка). Спека: docs/specs/2026-06-10-omlx-wire-trace-capture-design.md

## [5.2.1] - 2026-06-11

### Changed

- adr

## [5.2.0] - 2026-06-11

### Changed

- doc-files: local-only конвеєр (Крок 0 за ADR 260610-2228) — спільний npm/lib/llm.mjs з роутингом за префіксом omlx/ і wire-trace; видалено cloud-ескалації та pre-route sym; degraded-маркер score/issues у frontmatter, gen --retry-degraded і check --degraded; preflight omlx (down/memory-guard/auth) перед масовим прогоном; callOmlx шле Authorization Bearer (N_CURSOR_OMLX_KEY або ~/.omlx/settings.json)
- doc-files: міграція корпусу док на CRC-контракт — 126 git-актуальним докам застамповано frontmatter (дока не старіша за джерело за git-історією); 76 файлів (73 git-відсталі доки + 3 без док) лишено stale як чесний беклог перегенерації локальним конвеєром

### Fixed

- lint: санація шарів, що ховалися за першим падінням oxlint — повний bun run lint доведено до lint-js/style зелених: JSDoc/regex/sonarjs фікси у ~25 файлах, slow-regex обмежено bounded-квантифікаторами, eslint ignore для згенерованих docs/\*.md, jscpd ignore (.git, COVERAGE.md, docs, відомі легасі-клони), knip ignoreBinaries (pi/claude/fix-t0), stylelint/cspell ignore згенерованих артефактів; docgen-ignore у doc-aggregate став re-export з doc-files

## [5.1.0] - 2026-06-10

### Changed

- local-inference: маршрут моделей за префіксом `omlx/` напряму в omlx HTTP (npm/lib/omlx.mjs) минаючи pi; coverage-classify і fix/llm-worker переведено на спільний callOmlx, docgen де-дубльовано; pi лишається шаром для хмари й агентних задач (ADR 260610-1349)

## [5.0.3] - 2026-06-10

### Changed

- docs(abie): regenerate hc-yaml + http-route via omlx-orchestrator

## [5.0.2] - 2026-06-10

### Changed

- release: @nitra/cursor@5.0.1

## [5.0.1] - 2026-06-09

### Fixed

- npm-module/npm_publish_yml: крок Release у канонічному сніпеті викликає бінарник `n-cursor release` з PATH замість `node npm/bin/n-cursor.js release` — шлях npm/bin/n-cursor.js не існує у downstream-споживачів і ламав їхній npm-publish (`Cannot find module .../npm/bin/n-cursor.js`, напр. @7n/n)
- ci/setup-bun-deps: синхронізовано `bun.lock` з `package.json` — застарілий lockfile валив `bun install --frozen-lockfile` у workflow `npm-publish` ще до кроку release, блокуючи публікацію
- npm-module/npm_publish_yml: крок Release викликає `bunx n-cursor release` замість `n-cursor release` — голий workflow-`run:` не має `node_modules/.bin` у PATH (на відміну від npm/bun-скриптів), тож `n-cursor` падав з `command not found` (exit 127); `bunx` резолвить бінарник із node_modules і працює і в @nitra/cursor, і в downstream-споживачів

## [5.0.0] - 2026-06-08

### Changed

- docs: додати план extraction Meta-task

### Fixed

- coverage `--changed` більше не залежить від runtime-стану `.flow.json`; scope визначається через git merge-base

### Removed

- видалено вбудовані flow і graph; Meta-task винесено в окремий пакет @7n/mt

## [4.1.2] - 2026-06-08

### Added

- 1

## [4.1.1] - 2026-06-08

### Changed

- @nitra/cursor 4

## [4.1.0] - 2026-06-07

### Added

- checkOllama() + ollama HTTP як primary path, pi як fallback у docgen-gen

## [4.0.0] - 2026-06-07

### Changed

- major version bump to 4.0.0
- docgen Tier 1: пряму ollama HTTP замінено на pi+resolveModel('min') — universally через каскад

## [3.29.0] - 2026-06-07

### Added

- resolveModel(tier) — прозорий каскадний fallback local→cloud для всіх 3 тирів (min/avg/max)

## [3.28.0] - 2026-06-06

### Added

- lib/models.mjs: global model tier classification (LOCAL*MIN/AVG/MAX, CLOUD_MIN/AVG/MAX) via N*\*\_MODEL env vars; fix llm-worker uses CLOUD_MIN/AVG by default

### Changed

- fix orchestrator: concise output — single line when clean, details only when issues occur
- llm-worker: rename MODEL_HAIKU/SONNET → MODEL/MODEL_HEAVY, helpful error when pi has no API key for provider

## [3.27.0] - 2026-06-06

### Added

- add fix-t0 CLI command: T0-auto pattern library for n-fix orchestrator (deterministic vscode-ext-add and rm-forbidden-file fixes, 0 LLM tokens)
- add fix-run: autonomous n-fix orchestrator (convergence-loop fix-t0+LLM, haiku→sonnet escalation, no agent needed)

## [3.26.0] - 2026-06-06

### Added

- Root-guard для всіх деструктивних точок: CLI-команди fix/lint/coverage/change/release + дефолтний sync хардом перевіряють cwd===git-toplevel (assertCwdIsProjectRoot); worktree-preflight підсилено root-assert (pwd vs toplevel); новий meta.json-флаг requireRoot + injectRootNotice для in-place root-only скілів (n-start-check); skillRequiresRoot як явна похідна ознака захисту; валідація requireRoot у npm-module

### Fixed

- n-changelog: directional version-drift — лише version ВПЕРЕД (> опублікованої/git-бази) валить як ручний bump; version ПОЗАДУ (локаль відстала від CI-релізу, ще не git pull) більше не блокує коміт (compareSemverCore/versionIsAhead)

## [3.25.1] - 2026-06-06

### Fixed

- coverage --fix: додано permissionMode:'bypassPermissions' у claude-agent-sdk query — без нього headless-агент не отримував дозволу на Edit/Bash і не редагував файли (крутився, палив токени, нічого не писав). Тепер автономний coverage --fix реально дописує тести й ганяє bun test

## [3.25.0] - 2026-06-06

### Added

- fix: прапорець `n-cursor fix --json` друкує компактний {total, failed, rules:[{ruleId, ok, output}]} у stdout (per-rule захоплення замість stdio inherit; у json-режимі пропускається ensureHkInstall, щоб stdout лишався чистим JSON). Скіл n-fix більше не парсить термінальний вивід — працює лише з rules[].ok:false. Дефолтна поведінка без прапорця незмінна

## [3.24.0] - 2026-06-05

### Added

- test/stryker_config: auto-augment існуючого stryker.config.mjs у Vue JS-roots — точкова вставка plugins/ignorers (vue-macros ignorer) string-splice-ом за AST-аналізом, зі збереженням полів і коментарів; idempotent, з graceful-skip для non-literal/динамічних default-export

## [3.23.1] - 2026-06-05

### Changed

- n-changelog: операційний абзац у STOP-блоці (релевантне завжди, тести/білд не знімають вимогу changelog); AGENTS-шаблон: тонкий інваріант post-change з рядком Changelog у відповіді

## [3.23.0] - 2026-06-05

### Added

- changelog: autofix-режим (env `N_CURSOR_CHANGELOG_AUTOFIX`) — за відсутності change-файлу правило саме створює його (patch/Changed, subject останнього коміту) і ставить у git-індекс, тож pre-commit-хук не падає
- coverage-fix: новий read-only CLI `n-cursor coverage-fix index|slice --file <path>` — скрипт парсить COVERAGE.md (може бути МБ) за 0 LLM-токенів і віддає агенту лише компактний index або зріз під один файл (контекст ±3 рядки). Скіл n-coverage-fix більше не змушує агента читати весь COVERAGE.md (вузьке місце ~700K токенів). Експортовано buildFixPrompt; модуль scripts/coverage-fix-extract.mjs
- taze: новий read-only CLI `n-cursor taze diff` — детермінований semver-diff package.json ↔ package.json.taze-bak (root + воркспейси), класифікує major за caret-семантикою (найлівіша ненульова компонента). Скіл n-taze (крок 3) більше не змушує LLM вручну порівнювати бекапи — отримує готовий {major[],minorPatch}. Модуль skills/taze/js/diff.mjs
- start-check: новий CLI `n-cursor start-check scan|run` — scan виявляє воркспейси зі start і класифікує тип (server/cli), run запускає один із grace-таймаутом (крос-платформно spawnSync, без perl alarm), класифікує OK/FAIL, парсить лог (ready/firstError) і рахує read-only git-sideEffects. Скіл n-start-check більше не змушує LLM розгортати glob, інтерпретувати exit-коди й regex-ити лог. Модуль skills/start-check/js/check.mjs

### Changed

- js-lint: додано e18e/\* deny-правила та канонічні ignorePatterns у .oxlintrc.json
- k8s.network_policy: додано NATS-порт TCP 4222 до in-cluster egress-allowlist (namespaceSelector: {}) канонічних snippet'ів deployment.snippet.yaml і stateful-set.snippet.yaml. Згенерований base/networkpolicy.yaml тепер дозволяє вихід на nats.nats.svc:4222 — поди з NATS-клієнтом більше не падають у CrashLoopBackOff. Bump k8s.mdc 1.42 → 1.43.
- docker rule: non-root сам по собі не є підставою переходити Alpine→Debian-slim — oven/bun:alpine уже має bun (uid/gid 1000); розписано два шляхи non-root (alpine standalone vs ship node_modules на bun)
- n-llm-patch: промпти вимагають change-file flow (npx @nitra/cursor change + fix changelog) замість ручного CHANGELOG.md / version bump

### Removed

- skills: злито n-fix-tests у канонічний n-coverage-fix — n-fix-tests видалено (skill+command+pi, прибрано з .n-cursor.json). coverage-fix увібрав детекцію test/coverage-команд з package.json#scripts (Крок 0) і early-skip уже свіжого COVERAGE.md; підказку у coverage.mjs renderMarkdown перенаправлено на /n-coverage-fix

## [3.22.0] - 2026-06-04

### Added

- js-lint quick-режим класифікує lint-findings на introduced (рядок у diff від HEAD) vs pre-existing (борг файлу) і позначає у виводі (🆕/🗄) — щоб дотик до файлу не плутав «моє чи старий борг». Захоплює oxlint/eslint --format=json, парсить added-lines git-diff, групує. Блокування без змін (#6/A). Краш інструмента не пропускається тихо.

### Changed

- detectLevel: сигнал складності (mdc/policy/rego/checker/правило/rules/суперечн/інваріант/violation тощо) разом із fix-дієсловом → L2, а не L0 — rules/checks-задачі не класифікуються як тривіальні. Лише складність перекриває L0 (L2-ключі порядок не міняють); 'правило/правила' замість 'правил' (не ловить 'правильно'); без 'conflict' (merge conflict не L2).

### Fixed

- Виправлено canonical clean-merged-branch.yml: dry_run використовує підтримуване action значення no.

## [3.21.1] - 2026-06-04

### Changed

- CLI sync: ховати блоки правил / 🧩 Skills / ⌨️ Commands / 🥧 Pi skills (рядки ⬇ і підсумок) за успішного прогону — друкувати лише коли fail > 0 (helper captureOutput)
- CLI sync: ховати post-sync рядки (📝 setup-bun-deps action / 📝 AGENTS.md / 📝 CLAUDE.md / 🤖 Claude-конфіг) за успішного прогону — через captureOutput, друк лише при помилці

## [3.21.0] - 2026-06-04

### Added

- Root-guard для всіх деструктивних точок: CLI-команди fix/lint/coverage/change/release + дефолтний sync хардом перевіряють cwd===git-toplevel (assertCwdIsProjectRoot); worktree-preflight підсилено root-assert (pwd vs toplevel); новий meta.json-флаг requireRoot + injectRootNotice для in-place root-only скілів (n-start-check); skillRequiresRoot як явна похідна ознака захисту; валідація requireRoot у npm-module
- sync .gitignore: ігнорувати .claude/scheduled_tasks.lock (runtime-lock планувальника)

### Changed

- Скорочено timestamp-префікси ADR і change-файлів до YYMMDD-HHMM; додано атомарний suffix для локальних колізій та заборону Temporal у Bun runtime.

### Fixed

- n-changelog: directional version-drift — лише version ВПЕРЕД (> опублікованої/git-бази) валить як ручний bump; version ПОЗАДУ (локаль відстала від CI-релізу, ще не git pull) більше не блокує коміт (compareSemverCore/versionIsAhead)

## [3.20.0] - 2026-06-03

### Added

- ensure-tool: авто-встановлення зовнішніх CLI-залежностей (hk, conftest, shellcheck, actionlint, dotenv-linter) — brew/scoop/GitHub Release per-platform; hk install після fix; conftest авто-встановлюється перед fix та lint-ga
- ensureTool: розширено на opa/regal/hadolint/kubeconform/kubescape (brew/scoop/GitHub Release per-platform) + підтримка сирих бінарників (archive:false → download+chmod, без tar). Мігровано call-sites rego-lint (opa/regal), docker (hadolint з docker-fallback), k8s-lint (kubeconform/kubescape). withBinRemovedFromPath виставляє N_CURSOR_NO_AUTO_INSTALL=1.
- Guard: дефолтний sync (`npx @nitra/cursor` без підкоманди) забороняє запуск із піддиректорії git-репо — STOP до мутацій, замість скаффолда .cursor/.claude/CLAUDE.md/.n-cursor.json не в той каталог

### Changed

- worktree-only скіли: bootstrap-виклик npx @nitra/cursor у новоствореному worktree тепер ретраїться при транзитних помилках реєстру/CDN (ETARGET/notarget, ENOTFOUND, ETIMEDOUT, EAI_AGAIN, ECONNRESET, 5xx) кожні 30с до 5 хв (env N_CURSOR_NPX_RETRY_MAX_MIN, ceiling 10 хв); реальний nonzero CLI віддається одразу. worktree-notice додає bun install у дереві (локальна копія усуває гонку з CDN) і shell-обгортку n_cursor_npx; fix-скіл кроки 1/6 використовують її
- npm-module: npm_publish_yml тепер звіряє ВЕСЬ канонічний сніпет напряму (target.json "check":"template", generic deep-subset) замість bespoke subset-of rego — редагування сніпета одразу змінює enforce, без правок rego й міграторів; масиви (steps) матчаться структурним subset-ом за наявністю (order/key-insensitive, зайві кроки дозволені); legacy publish-only workflow тепер падає check (вимагає release-publish job). Новий режим check:template перевикористовний для будь-якого whole-file концерну зі сніпетом.
- docker hadolint: прибрано docker-run fallback — hadolint тепер лише нативний бінарник через ensureTool (brew/scoop/GitHub Release). Видалено HADOLINT_IMAGE; оновлено docker.mdc і тести.

### Fixed

- nginx-default-tpl: error_log off → error_log /dev/null crit; (error_log off — НЕ валідний nginx, падає під readOnlyRootFilesystem); авто-заміна в шаблонах + оновлено канон .mdc/фікстуру

## [3.19.0] - 2026-06-03

### Changed

- image-compress переведено на glob-активацію (\*_/_.{png,jpg,jpeg,gif,svg}) замість залежності від bun — у проєктах без растрів/SVG правило більше не додається автоматично; globToRegex отримав підтримку brace-альтернатив {a,b,c}

## [3.18.2] - 2026-06-03

### Changed

- npm-publish.yml канон: workflow робить release+publish одним job (release-publish) — крок n-cursor release перед публікацією, contents: write + persist-credentials. Оновлено template, rego-референси та тести.

## [3.18.1] - 2026-06-03

### Fixed

- python: applies-гейт пропускає правило без pyproject.toml — позбулися хибного зауваження rego package_json у не-Python репо

## [3.18.0] - 2026-06-03

### Added

- docgen: трирівнева генерація md-доки (CLI `docgen scan|modules` + скіл); скіл-специфічні скрипти тепер живуть у npm/skills/<id>/js/, синк копіює лише top-level SKILL.md (підкаталоги js/ пропускаються)

## [3.17.0] - 2026-06-02

### Added

- skill changes (auto: завжди, worktree: true) — покласти change-файл у кожен зачеплений workspace через n-cursor change перед фінішем

## [3.16.0] - 2026-06-02

### Added

- graph: команда status — read-only скан DAG вузлів (docs/graphs/<g>/nodes/) і derive позиції (done/failed/awaiting-human/in_progress/ready/blocked); фундамент node-dag-state

## [3.15.0] - 2026-06-02

### Added

- parity-гард дзеркала правил: тест перевіряє, що .cursor/rules/n-<id>.mdc == канонічний npm/rules/<id>/<id>.mdc з inlined-шаблонами (хелпер mirror-parity.mjs), ловлячи дрейф рано. Разово регенеровано наявний дрейф (changelog/flow/ga/npm-module/test).

## [3.14.2] - 2026-06-02

### Fixed

- Додано pull-requests: read до clean-merged-branch.yml, щоб cleanup action міг перевіряти PR-и комітів без 403.
- detectLevel: ASCII L0-дієслова (fix/typo/bump/rename/hotfix) матчаться цілим словом, а не підрядком — опис на кшталт 'add prefix validation' більше не дає хибний L0 (раніше 'fix' ловився в 'prefix'/'fixture'/'suffix'). Кириличні L0-ключі лишаються підрядком (стемінг).

## [3.14.1] - 2026-06-02

### Changed

- flow review: рецензент верифікує cross-file твердження читанням (Read) — промпт дозволяє/зобов'язує дочитувати referenced-файли/spec точково, репортувати лише те, що вносить diff (не преіснуючі баги сусідів), і не видавати нефальсифіковних findings «з diff не видно». Зменшує хибні findings.

### Fixed

- coverage-gate: запускати Stryker із локально встановленого @stryker-mutator/core (резолв через package.json у node_modules пакета, bin запускається напряму через node-shebang), а не через npx/bunx — ті тягнуть core у власний кеш без vitest-runner, тож plugin-discovery падав 'Cannot find TestRunner plugin vitest' і flow verify coverage-gate червонів. Працює й з worktree без власного node_modules.

## [3.14.0] - 2026-06-02

### Changed

- k8s hasura_configmap: base/dev ConfigMap Hasura-Deployment має містити HASURA_GRAPHQL_ENABLED_APIS="metadata,graphql,pgdump" (точний рядок). Кожен не-base overlay (k8s/<env>/, env≠base/dev), що успадковує Hasura-base, має у kustomization.yaml перевизначати цей ключ до "metadata,graphql" (без pgdump) патчем JSON6902/Strategic Merge на ConfigMap — нова cross-file перевірка validateHasuraOverlayEnabledApisOverride

## [3.13.0] - 2026-06-02

### Changed

- k8s hasura_configmap: розширено перелік обов'язкових env у ConfigMap Hasura-Deployment — додатково до HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS="true" тепер вимагаються HASURA_GRAPHQL_ENABLE_RELAY="false", HASURA_GRAPHQL_ENABLE_TELEMETRY="false", HASURA_GRAPHQL_ENABLED_LOG_TYPES="startup,http-log" (точний рядок) і HASURA_GRAPHQL_DISABLE_EVENTING (ключ обов'язковий, значення довільне, за замовчуванням "true")

## [3.12.0] - 2026-06-02

### Added

- flow: cwd-незалежний резолвинг активного стану — spec/plan/verify/review/gate/release знаходять .flow.json поточної гілки навіть із головного дерева (швидкий шлях без git, toplevel-резолв, авторезолв єдиного активного flow), + опційний --branch <гілка>. Гейти виконуються у теці worktree.
- flow release: інференс зміненого воркспейсу з diff від base_commit — авто-додає --ws у change, якщо не задано явно (один змінений subworkspace → його .changes/; кілька → fail з підказкою явного --ws; лише корінь → дефолт). Усуває потрапляння change-файлу в корінь монорепо при змінах під підпакетом.

### Fixed

- Усунуто суперечність n-changelog.mdc ↔ n-npm-module.mdc: прибрано перевірки version/CHANGELOG у package_structure.mjs, що штовхали до ручного bump (єдиний артефакт змін — change-файл; узгодженість валідує changelog/consistency.mjs); npm-module.mdc делегує bump/CHANGELOG у changelog.mdc, який отримав post-release-інваріант.
- trace: резолв лінків front-matter відносно теки артефакту (+ root-relative fallback) — file-relative spec/plan лінки (`../specs/…`) більше не дають хибний «розрив ланцюга»; поле `flow` (runtime `.flow.json`) показується, але не рахується розривом. Розрив визначають лише chain-поля (adr/spec/plan/change/task).

## [3.11.0] - 2026-06-02

### Changed

- coverage: scoped режим --changed — flow-турнікет (DEFAULT_GATES) перевіряє лише змінені від base_commit файли (vitest --changed + Stryker --mutate), однаково для закомічених і незакомічених змін; повний coverage лишається для bun run coverage / n-coverage-fix

## [3.10.0] - 2026-06-01

### Added

- rust coverage: incremental mutation через CARGO_MUTANTS_BASE_REF → cargo-mutants --in-diff (мутуємо лише змінене у <ref>...HEAD)
- rust coverage: CARGO_MUTANTS_BASELINE=skip → cargo-mutants --baseline skip (пропуск немутованого baseline коли тести вже зелені); rust.mdc — гайд по CI-кешу target/ для coverage-job

### Removed

- internal: видалено мертвий JS-код — 19 експортованих функцій/констант, що викликались лише з тестів (k8s manifests: 5 предикатів, мігрованих у rego; gha-workflow: 9 предикатів, мігрованих у rego ga.workflow_common + 1 каскадний; abie/text/dispatcher: kustomizationHasAbieNginxRunHttpRoutePatch, getV8rCatalogPath, SUBCOMMANDS, resolveFlow) разом з їхніми тестами й осиротілими хелперами

## [3.9.0] - 2026-06-01

### Changed

- k8s: канонічний образ hasura/graphql-engine → v2.49.0.ubuntu.amd64 (ubuntu-база містить pg_dump 18, на відміну від ubi-варіанту)

### Removed

- k8s: видалено мертву JS-перевірку образа hasura (deploymentHasuraGraphqlEngineImageViolation + набір HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES) — пер-документна перевірка делегована rego-пакету k8s.manifest, тег тепер має єдине джерело істини (allowed_hasura_images)

## [3.8.0] - 2026-06-01

### Added

- flow: контракт — advanced elicitation (меню технік поглиблення вимог у фазі spec: Expand/Contract, Critique&Refine, Identify Risks, Tree-of-Thoughts, Stakeholder Roundtable, Self-Consistency; Elicitation History у спеці)

## [3.7.0] - 2026-06-01

### Added

- flow: risk-aware глибина review — detectRisk у init + risk зі spec-frontmatter керують кількістю рецензентів (max за рівнем і ризиком) і безпековим фокусом промпта

## [3.6.1] - 2026-06-01

### Fixed

- flow: resolveArtifact обирає артефакт за mtime + пріоритетом slug гілки замість лексикографічного (spec/plan фіксували не той doc при кількох на одну дату)

## [3.6.0] - 2026-06-01

### Added

- flow: команда gate — структурований вердикт релізної готовності (PASS/CONCERNS/FAIL + score + причини, синтез verify-гейтів і review-findings); release м'яко попереджає на FAIL

## [3.5.0] - 2026-06-01

### Added

- python: нове правило (uv-only, без Poetry) — автоактивація за pyproject.toml, заборона [tool.poetry]/poetry.lock, PEP 621 [project], lint-python (uv lock --check + uv sync --frozen + опц. ruff auto-fix: check --fix & format, mypy), CI workflow з astral-sh/setup-uv

## [3.4.1] - 2026-06-01

### Fixed

- worktree add: перевіряє зайнятість назви й автоматично обирає вільну (base, base2, base3, …) замість падіння на 'a branch named … already exists'

## [3.4.0] - 2026-06-01

### Added

- flow: команда review (adversarial diff-review за рівнем) + scale-adaptive level в init (L0–L3, керує глибиною review)

## [3.3.0] - 2026-06-01

### Added

- flow: фази spec і plan (brainstorm human↔agent + agent↔agent --panel), trace простежує лінк plan→flow, verify м'яко попереджає про відсутній план

## [3.2.3] - 2026-06-01

### Fixed

- k8s `workloadAppLabel`: CronJob/workload без `spec` (чи `spec: null`) повертає `null` замість TypeError — `check k8s` більше не падає на неповному маніфесті

## [3.2.2] - 2026-06-01

### Changed

- js-lint: додано e18e/\* deny-правила та канонічні ignorePatterns у .oxlintrc.json

### Fixed

- k8s NetworkPolicy: мітку `app` для Job беремо з `spec.template.metadata.labels.app`, для CronJob — з `spec.jobTemplate.spec.template.metadata.labels.app` (ручний `spec.selector` у Job/CronJob невалідний без `manualSelector: true`)

## [3.2.1] - 2026-06-01

### Fixed

- worktree-only skills: прибрано shell expansion з preflight, щоб агент створював worktree literal-командами без confirmation prompt

## [3.2.0] - 2026-06-01

### Added

- docker: правило «нативний .node-аддон не компілювати» — sharp/@img/\*/argon2 у deps + `bun build --compile` прапорцюється (компілятор не вшиває динамічний require → краш у рантаймі); канон — node_modules + `bun <entry>` на mirror.gcr.io/oven/bun:alpine (легітимний bun-рантайм як фінальний stage). lib/docker-native-addon.mjs + інтеграція в js/lint.mjs

### Changed

- CLI sync: блоки Skills/Commands/Pi skills (разом із рядками ⬇) друкуються лише за наявності помилок — за чистого прогону вивід коротший
- js-lint-ci правило стало Auto Attached (glob як у js-lint) замість alwaysApply — не висить у кожному контексті, тригериться на JS/конфіги лінтерів
- правила js-lint: knip/dependency-аналіз перенесено з js-lint у js-lint-ci (його декларована зона); додано заборону @nitra/as-integrations-fastify з міграцією на @as-integrations/fastify ^3.1.0 (specifier-only, код незмінний)

### Fixed

- worktree-only skills: preflight сам створює worktree від поточної гілки з коротким суфіксом без запиту назви

## [3.1.0] - 2026-06-01

### Added

- docker: для фінального nginx-unprivileged stage — окрема non-root-перевірка (lib/docker-nginx-user.mjs): жодного USER root/switch-back USER 101|nginx, COPY/ADD лише з --chown=nginx:nginx; gzip під дефолтним uid=101

### Changed

- worktree-only skills: банер у SKILL.md став жорстким fail-fast preflight (Крок 0, STOP/ABORT) замість поради; CLAUDE.md отримав секцію-правило про worktree:true скіли
- vue: увесь стек auto-import (заборона value-імпортів з vue, вимога 'vue' у AutoImport.imports та наявності VueMacros/AutoImport у vite.config) не застосовується до бібліотек компонентів — пакетів із vue у peerDependencies (їхні джерела не проходять через unplugin-auto-import споживача); інші перевірки (esbuild, npm_lifecycle_event) лишаються; додано isVueComponentLibraryPkg

## [3.0.0] - 2026-06-01

### Added

- n-cursor flow (v2.0-a Ф0-Ф1.1): каркас dispatcher + CLI `case 'flow'` (init/verify/release/run/resume/cancel/repair — поки stub-и), Capability Router з явною декларацією моделі (native/polyfill, default→polyfill лише за наявного runner-а), crash-safe state-store (.flow.json sibling, atomic temp+fsync+rename, fail-closed на corruption). 29 unit-тестів (withTmpDir).
- n-cursor flow v2.0-a Ф1.2-Ф1.4: WAL-журнал .events.jsonl (append-only, торований останній рядок толерується), per-branch lock через reuse withLock із fail-closed override (додано опцію onWaitTimeout у спільний with-lock, back-compat), cleanupFlowSiblings (.flow.json/.events.jsonl/lock). recordTransition (WAL: подія до зміни статусу). +22 тести.
- n-cursor flow v2.0-a Ф2 (verify): reviewer.mjs — Level-1 «Суддя» проганяє lint+coverage gates через ін'єктований runner (fail-fast, fingerprint на повному pass через reuse worktree-fingerprint); flow verify — Пасивний Турнікет: запускає gates у поточному worktree, записує результати+fingerprint у наявний стан (recordTransition), exit 0/1. +11 тестів.
- n-cursor flow v2.0-a Ф2·2: flow init (n-cursor worktree add + detect-existing-isolation через git rev-parse, init .flow.json з base_commit; reuse worktreePaths/sanitizeBranch) + flow release (n-cursor change + completion snapshot у стан і task record) + snapshot.mjs (buildCompletionSnapshot/upsertSummaryBlock/writeSummaryToTaskRecord). reviewer тепер захоплює вивід проваленого gate. Пасивний Турнікет (init/verify/release) завершено. +18 тестів.
- n-cursor flow Ф2·4: bundled-правило flow (матеріалізується як .cursor/rules/n-flow.mdc) — контракт Пасивного Турнікета для IDE-агентів (Cursor/Claude Code): init -> сам пишеш код (TDD) -> verify (3 спроби на фейл) -> release. alwaysApply; pure-doc + стандартний fix.mjs (runStandardRule). Авто-дискавериться, активується при sync. Завершує Фасад A повністю.
- n-cursor flow v2.0-a Ф3 (двигун-блоки): SubagentRunner (§15.1) — selectBackend (sdk>claude>cursor за ANTHROPIC_API_KEY/PATH, fail коли нічого нема), cliRunner (claude/cursor-agent -p, CLI-auth), sdkRunner (claude-agent-sdk, dynamic import); planner — parsePlan (валідація+нормалізація, fail-closed на невалідному) + generatePlan. Усе з мок-ін'єкцією (нуль реальних API/SDK у тестах). +24 тести.
- n-cursor flow v2.0-a Ф4·a: executor.mjs — серце Активного Раннера. Виконує план покроково: мікропромпт зі стану (не історія) -> спавн субагента -> verify -> commit ЛИШЕ після зеленого (commit-інваріант §4.1.7) -> repair (per-step retry) -> на вичерпанні HITL (blocked-on-human + structured question). microprompt/patchStep — pure. Усе з ін'єкцією runner/verify/commit (нуль реальних LLM/git у тестах). +6 тестів.
- n-cursor flow v2.0-a Ф4·b: Активний Раннер end-to-end. flow run (ensureWorktree -> planner -> executor; exit 0 done / 1 fail / 2 blocked-on-human), flow resume (safe-resume git reset + HITL-відповіді як hint + свіжі спроби), flow cancel (cleanup sibling-ів), flow repair (--discard-step-work / діагностика). ensureWorktree витягнуто як спільне для init/run. Усі 7 підкоманд реальні. +12 тестів (103 у dispatcher). v2.0-a (Фасади A+B) функціонально завершено.
- n-cursor flow v2.0-a: (1) budget guard для flow run --autonomous (withBudget обгортає runner, BudgetExceeded при перевищенні maxApiCalls з .n-cursor.json flow.autonomous; на abort -> status failed, exit 1). (2) Ф1.4 борг закрито: worktree remove тепер reuse-кличе cleanupFlowSiblings (flow-sibling-и не осиротіють). Заодно виправлено передіснуючі switch-case-braces у worktree-cli. +4 тести.
- n-cursor flow v2.0-b: команда n-cursor trace — наскрізна простежуваність (§5.4/§7). Читає front-matter артефактів у docs/{tasks,specs,plans,adr}, будує ланцюг за лінками adr/spec/plan/change/task, флагує розриви (лінк на неіснуючий файл) з exit 1; --json для machine-readable. parseFrontMatter/analyze/render — pure, FS ін'єктовний. Підтверджено на власних spec<->plan. +10 тестів.

### Changed

- n-cursor flow v2.0.0 (major): Dual-Mode Dispatcher — Пасивний Турнікет (flow init/verify/release) + Активний Раннер (flow run/resume/cancel/repair) + n-cursor trace + docs/{specs,plans} міграція

## [2.0.0] - 2026-05-31

### Added

- n-cursor flow (v2.0-a Ф0-Ф1.1): каркас dispatcher + CLI `case 'flow'` (init/verify/release/run/resume/cancel/repair — поки stub-и), Capability Router з явною декларацією моделі (native/polyfill, default→polyfill лише за наявного runner-а), crash-safe state-store (.flow.json sibling, atomic temp+fsync+rename, fail-closed на corruption). 29 unit-тестів (withTmpDir).
- n-cursor flow v2.0-a Ф1.2-Ф1.4: WAL-журнал .events.jsonl (append-only, торований останній рядок толерується), per-branch lock через reuse withLock із fail-closed override (додано опцію onWaitTimeout у спільний with-lock, back-compat), cleanupFlowSiblings (.flow.json/.events.jsonl/lock). recordTransition (WAL: подія до зміни статусу). +22 тести.
- n-cursor flow v2.0-a Ф2 (verify): reviewer.mjs — Level-1 «Суддя» проганяє lint+coverage gates через ін'єктований runner (fail-fast, fingerprint на повному pass через reuse worktree-fingerprint); flow verify — Пасивний Турнікет: запускає gates у поточному worktree, записує результати+fingerprint у наявний стан (recordTransition), exit 0/1. +11 тестів.
- n-cursor flow v2.0-a Ф2·2: flow init (n-cursor worktree add + detect-existing-isolation через git rev-parse, init .flow.json з base_commit; reuse worktreePaths/sanitizeBranch) + flow release (n-cursor change + completion snapshot у стан і task record) + snapshot.mjs (buildCompletionSnapshot/upsertSummaryBlock/writeSummaryToTaskRecord). reviewer тепер захоплює вивід проваленого gate. Пасивний Турнікет (init/verify/release) завершено. +18 тестів.
- n-cursor flow Ф2·4: bundled-правило flow (матеріалізується як .cursor/rules/n-flow.mdc) — контракт Пасивного Турнікета для IDE-агентів (Cursor/Claude Code): init -> сам пишеш код (TDD) -> verify (3 спроби на фейл) -> release. alwaysApply; pure-doc + стандартний fix.mjs (runStandardRule). Авто-дискавериться, активується при sync. Завершує Фасад A повністю.
- n-cursor flow v2.0-a Ф3 (двигун-блоки): SubagentRunner (§15.1) — selectBackend (sdk>claude>cursor за ANTHROPIC_API_KEY/PATH, fail коли нічого нема), cliRunner (claude/cursor-agent -p, CLI-auth), sdkRunner (claude-agent-sdk, dynamic import); planner — parsePlan (валідація+нормалізація, fail-closed на невалідному) + generatePlan. Усе з мок-ін'єкцією (нуль реальних API/SDK у тестах). +24 тести.
- n-cursor flow v2.0-a Ф4·a: executor.mjs — серце Активного Раннера. Виконує план покроково: мікропромпт зі стану (не історія) -> спавн субагента -> verify -> commit ЛИШЕ після зеленого (commit-інваріант §4.1.7) -> repair (per-step retry) -> на вичерпанні HITL (blocked-on-human + structured question). microprompt/patchStep — pure. Усе з ін'єкцією runner/verify/commit (нуль реальних LLM/git у тестах). +6 тестів.
- n-cursor flow v2.0-a Ф4·b: Активний Раннер end-to-end. flow run (ensureWorktree -> planner -> executor; exit 0 done / 1 fail / 2 blocked-on-human), flow resume (safe-resume git reset + HITL-відповіді як hint + свіжі спроби), flow cancel (cleanup sibling-ів), flow repair (--discard-step-work / діагностика). ensureWorktree витягнуто як спільне для init/run. Усі 7 підкоманд реальні. +12 тестів (103 у dispatcher). v2.0-a (Фасади A+B) функціонально завершено.
- n-cursor flow v2.0-a: (1) budget guard для flow run --autonomous (withBudget обгортає runner, BudgetExceeded при перевищенні maxApiCalls з .n-cursor.json flow.autonomous; на abort -> status failed, exit 1). (2) Ф1.4 борг закрито: worktree remove тепер reuse-кличе cleanupFlowSiblings (flow-sibling-и не осиротіють). Заодно виправлено передіснуючі switch-case-braces у worktree-cli. +4 тести.
- n-cursor flow v2.0-b: команда n-cursor trace — наскрізна простежуваність (§5.4/§7). Читає front-matter артефактів у docs/{tasks,specs,plans,adr}, будує ланцюг за лінками adr/spec/plan/change/task, флагує розриви (лінк на неіснуючий файл) з exit 1; --json для machine-readable. parseFrontMatter/analyze/render — pure, FS ін'єктовний. Підтверджено на власних spec<->plan. +10 тестів.

### Changed

- n-cursor flow v2.0.0 (major): Dual-Mode Dispatcher — Пасивний Турнікет (flow init/verify/release) + Активний Раннер (flow run/resume/cancel/repair) + n-cursor trace + docs/{specs,plans} міграція

## [1.41.0] - 2026-05-31

### Changed

- js-lint: канон oxlint оновлено — додано 13 правил @e18e/eslint-plugin і ignorePatterns (npm/types/\*\*, demo/node/rules-demo.js)
- js-lint: мінімальна версія @nitra/eslint-config піднята до 3.10.0 (Rego package_json + js-lint.mdc v1.27)

## [1.40.1] - 2026-05-31

### Changed

- js-lint: oxlint-canonical.json тепер source-of-truth — прибрано генерацію (rebuild-oxlint-canonical.mjs, oxlint-canonical-skeleton.json, oxlint-rules.tsv)

## [1.40.0] - 2026-05-31

### Added

- lint: розділення на `n-cursor lint` (quick, по змінених файлах) і `n-cursor lint-ci` (повний, по всьому репо) — data-driven за полем `meta.json.lint` (quick/ci); виконавець кроку — `js/lint.mjs` правила; jscpd+knip винесено в нове правило `js-lint-ci` (фаза ci)

## [1.39.1] - 2026-05-31

### Changed

- worktree rule: при завершенні гілки worktree за замовчуванням пропонувати squash-merge (один коміт)

## [1.39.0] - 2026-05-31

### Changed

- worktree skill: додати auto завжди до meta.json
- rules: автодетект перенесено з хардкоду auto-rules.mjs + auto.md на data-driven meta.json (glob/predicate/deps); увімкнено автодетект tauri

## [1.38.0] - 2026-05-31

### Added

- worktree: кросплатформний CLI n-cursor worktree (add/remove/list/prune) — виконавець конвенції .worktrees/ з інвентарним файлом-описом; тонкий skill worktree; pure-doc правило worktree

## [1.37.0] - 2026-05-31

### Changed

- skills: meta.json замість auto.md (+ worktree-прапорець з вшиванням у SKILL.md і забороною паралелі)

## [1.36.0] - 2026-05-31

### Added

- test rule v2.7: канони Console mocking (vi.spyOn), Sandbox-aware тестів (withTmpDir+git init як default, skipIf STRYKER_MUTATOR_WORKER як виняток для smoke-аудиту) і явний {cwd:dir} у child_process

### Changed

- withLock: стан локу спільний для всіх git-worktree через resolveLockCacheDir (git-common-dir) — серіалізація важких команд між worktree, не лише в одному checkout

## [1.35.5] - 2026-05-30

### Changed

- fix: computeLineOffsets — for..of по рядку із pos+=1 замість body.entries()

## [1.35.4] - 2026-05-30

### Changed

- fix: відновити for-loop із body.length у computeLineOffsets (String.prototype.entries не існує)

## [1.35.3] - 2026-05-30

### Fixed

- test: ізолювати listShellScriptPaths git-test через withTmpDir + git init (під Stryker sandbox-копія репо не git-дерево, через що тест блокував dry-run і не давав оновити mutation.json)

## [1.35.2] - 2026-05-30

### Fixed

- test: meta-тести no-process-chdir і test-helpers перенесено на конкатенацію/Identifier-аргументи, щоб не тригерити власні сканери (false positives)

## [1.35.1] - 2026-05-30

### Added

- Нові тести для підвищення coverage: run-dotenv-linter (spawnSync error paths), lint.mjs preflight-OK paths, ua_http_route.mjs (default cwd + readFile chmod-fail), auto-rules.mjs (readdir/JSON catch-блоки), run-shellcheck-errors, run-v8r, sync-claude-config, test-helpers absolute-path guard, rename-yaml-extensions sort, graphql langFromPath, capacitor segmentMinMajor, coverage-classify cache/retry/fallback, npm-module package_structure, changelog consistency, bun-sql-scan BinaryExpression. Lines coverage: ~83.59% → 88.32%.

## [1.35.0] - 2026-05-30

### Added

- LLM-класифікатор survived мутантів у n-cursor coverage: для кожного survived Claude Sonnet 4.6 виносить verdict (worth-testing/equivalent/defensive/glue/wrapper) з reasoning + confidence. Allowed gaps виключаються з знаменника mutation score. Cache по git-blob-hash. Graceful skip без API key. Threshold у .n-cursor.json#coverage.classifyConfidenceThreshold (default 1.1 — rollout mode).

## [1.34.1] - 2026-05-30

### Added

- Нові тест-файли (10 файлів, 108+ тестів): rules/abie/js/tests/ (firebase_hosting, env_dns, hc_pairing, ua_node_selector, ua_http_route), rules/abie/lib/tests/hc-yaml.test.mjs, rules/abie/lib/tests/k8s-tree.test.mjs, rules/docker/lib/tests/docker-hadolint.test.mjs, rules/docker/lint/tests/lint.test.mjs, scripts/tests/coverage-fix.test.mjs; розширено scripts/tests/upgrade-nitra-cursor-and-install.test.mjs. Lines coverage: 77.62% → 78.80%, Functions: 84.47% → 86.13%.

### Changed

- coverage: не додавати підсумковий рядок "Разом" коли провайдер один (дублював би єдиний рядок)

Усі помітні зміни цього модуля документуються тут.

Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).

## [1.34.0] - 2026-05-30

### Added

- Тести для `bun-sql-scan.mjs`: `findPgFormatShimDefinitionInText`, `findPgFormatLikeQueryWrapperInText`, `findUnsafeBunSqlInListMissingEmptyGuardInText`, `findPgListenNotifyUsageInText`
- Тести для `capacitor/platforms.mjs`: semver edge-cases, `recordCapacitorFromOnePackageJson`, `collectCapacitorDataFromAllPackageJson`, nitra-exceptions via config files
- Тести для `ga/workflows.mjs`: no .github/workflows dir, .yaml extension, MegaLinter detection, apply-k8s paths trigger
- Тести для `nginx-default-tpl/template.mjs` і `vue/packages.mjs` через check-rule-fixtures.test.mjs

## [1.33.0] - 2026-05-30

### Added

- `n-cursor change` / `n-cursor release` — change-файли `<ws>/.changes/*.md` замість ручного bump/CHANGELOG; реліз агрегує їх у CI, ставить git-тег `<name>@<version>`. Підтримка npm і Python workspace.

### Changed

- `n-changelog.mdc` v3.0: feature-флоу кладе change-файл; `fix changelog` приймає change-файл або ручний bump.

## [1.32.0] - 2026-05-30

### Added

- **`rules/ci4/js/marksman_config.mjs`** + **`rules/ci4/js/data/marksman_config/marksman.baseline.toml`** — новий JS-концерн `marksman_config` за зразком `test.stryker_config`: при `npx @nitra/cursor fix ci4` копіює canonical `.marksman.toml` baseline у корінь cwd, якщо файлу ще немає. Idempotent через паттерн `ensureBaselineFile` (existsSync → pass+skip vs copyFile → pass+create) — ручні правки користувача між прогонами зберігаються, повторний прогон не перетирає. Baseline містить три ключові опції: `[core] markdown.glfm = true` (GLFM-фічі portable subset — alerts/таблиці/todo), `[completion] wiki.style = "file-stem"` (ADR slug == ім'я файла; стабільний ідентифікатор у AUTOGEN `sources`/manifest/валідаторі — заголовок міняється, посилання не ламається), `[code_action] toc.enable = true` (Insert/Update TOC code action для довгих arc42-сторінок). Дефолти marksman (`title-slug-ref` + вимкнений GLFM) ламали б частину задокументованої навігації. Розміщення в `cwd` (корені репо) робить весь монорепо одним marksman-workspace — README.md і docs/ перехресно навігуються. Тести: `+4` сценарії у `rules/ci4/js/tests/marksman_config.test.mjs` через `withTmpDir` (порожній cwd → файл створюється; idempotency — кастомний контент не перетирається; валідні TOML-секції `[core]`/`[completion]`/`[code_action]`; exit 0 у обох сценаріях). 4/4 PASS через `bunx vitest run rules/ci4/js/tests/marksman_config.test.mjs`.

### Changed

- **`rules/ci4/ci4.mdc`** (`version` 3.1 → 3.2) — у секцію «Viewer/editor: Zed + marksman LSP» додано параграф про авто-створення `.marksman.toml` правилом ci4 з посиланням на canonical baseline і поясненням кожної з трьох ключових опцій (glfm/wiki.style/toc.enable). У frontmatter `description` додано пункт про `.marksman.toml`. Дзеркало `.cursor/rules/n-ci4.mdc` синхронізується наступним прогоном `npx @nitra/cursor`.

## [1.31.0] - 2026-05-30

### Added

- **`rules/ci4/policy/vscode_extensions/`** — нова policy за зразком text/style-lint/rego/ga/js-lint/graphql/nginx-default-tpl/rust/tauri: `vscode_extensions.rego` (deny якщо рекомендація з template-snippet відсутня в `.vscode/extensions.json`) + `vscode_extensions_test.rego` (5 сценаріїв: canonical, missing marksman, empty recommendations, extra recommendations пропускаються, drift-test що канон керується через `data.template`) + `template/extensions.json.snippet.json` (`{"recommendations": ["arr.marksman"]}`) + `target.json` (single `.vscode/extensions.json`). Призначення — контрибʼютори, що працюють у VSCode/Cursor замість Zed, отримують той самий шар marksman-навігації (cmd+click по `[link](file.md)`/`[[wiki-link]]`, find-references, refactor-перейменування заголовків) через офіційне розширення marksman LSP. Дзеркальна правка у репо: `.vscode/extensions.json` отримав `arr.marksman` у `recommendations`. Тести: 5/5 PASS через `opa test npm/rules/ci4/policy/vscode_extensions/`.

### Changed

- **`rules/ci4/ci4.mdc`** (`version` 3.0 → 3.1) — у секцію «Viewer/editor: Zed + marksman LSP» додано підсекцію **«VSCode-альтернатива»** з канонічним JSON-блоком `.vscode/extensions.json` (`recommendations: ["arr.marksman"]`) і посиланням на snippet-файл policy (за стилем `text.mdc`); також згадка про marksman-сумісні редактори (Neovim, Helix, Emacs). У frontmatter `description` додано пункт про VSCode-розширення. Дзеркало `.cursor/rules/n-ci4.mdc` синхронізується наступним прогоном `npx @nitra/cursor`.

### Fixed

- **`vitest.config.js`** — git-залежні тести (`rules/changelog/check.test.mjs`, `rules/ga/workflows.test.mjs`) масово таймаутили локально (`23 failed | 13 passed`, 187s; усі фейли — `Test timed out in 5000ms`, не assertion), хоча в CI зелені. Першопричина: глобальний `~/.gitconfig` тестової машини має `trace2.eventtarget=af_unix:stream:~/.git-ai/.../trace2.sock` (tooling `git-ai`), який успадковується tmp-репо в тестах → кожна git-команда під'єднується до Unix-сокета даемона; коли даемон деградований, запис у сокет блокується (~1s/команда не на CPU), а під `pool: 'forks'` десятки паралельних git-операцій × латентність > 5000ms `testTimeout`. Фікс: `env: { GIT_TRACE2_EVENT: '0' }` прибирає trace2-залежність із гарячого шляху тестового git (root-cause), плюс `testTimeout: 20000` як defence-in-depth проти будь-якої залишкової локальної I/O-латентності. Після фіксу `bun run vitest run rules/changelog/` → `36 passed` за ~7s (відтворювано); повний suite — `1248 passed | 2 skipped`. CI не зачеплено (там немає trace2-таргета).

## [1.30.1] - 2026-05-29

### Added

- **`rules/test/coverage/tests/coverage.test.mjs`** — три нові тести `/n-coverage-fix`-ітерації, що вбивають усі чотири вцілілі мутанти на `rules/test/coverage/coverage.mjs`. (1) `opts.fix=false → fixSurvivedMutants НЕ викликається` і (2) `opts.fix=true → fixSurvivedMutants викликається` фіксують умовну гілку `if (opts.fix)` (L189) через лог `'✓ Всі мутанти вбиті — доповнення тестів не потрібне'`, який друкує `fixSurvivedMutants` для порожнього `survived[]`. (3) `source 2-ї стрілки містить fix:false` перевіряє джерело захопленої callback-стрілки 2-го `withLock` через `Function.prototype.toString()`: токени `fix` і `false` мають бути присутні, а `fix: true` — заборонений; це поведінково невловимо (`{}` і `{ fix: false }` дають однаковий falsy `opts.fix`), тому інваріант на рівні джерела.

### Fixed

- **`CHANGELOG.md`** — заголовок секції `[1.30.0]` піднято з `#` на `##` (Keep a Changelog vN.M.M вимагає H2 для версій). Чек `npm-module.mdc` зчитував h1 як «без версії», знаходив `[1.29.5]` першою і скаржився на розбіжність із `package.json#version "1.30.0"`.

## [1.30.0] - 2026-05-29

### Changed

- **`rules/ci4/ci4.mdc`** (`version` 2.1 → 3.0) — viewer-story повністю переписано під **Zed + marksman LSP без site-generator-а**. MkDocs Material і pymdownx-розширення (`!!! note`, `??? engineer`, `=== "tab"`, кастомні audience-CSS) прибрані як рекомендація: їх не рендерить вбудований MD-preview Zed, що ламає принцип «відкрив файл = бачу фінальний вигляд». Collapsible-блоки для змішаної аудиторії — через нативний HTML5 `<details>` / `<summary>`, що рендериться скрізь (Zed preview, GitHub, будь-який майбутній збирач) без розширень парсера. Введено **portable-only subset**: CommonMark + GFM + Mermaid у fenced code + KaTeX + `<details>`; заборонені VitePress containers (`::: tip`), MDX/Astro-компоненти, Hugo shortcodes, AsciiDoc-вкраплення, RST-директиви. Конвенція маркера у `<summary>`: перше слово — аудиторія з фіксованого словника (`Engineer:`/`Ops:`/`Security:`/`Manager:`) → детермінований regex для валідатора. Валідатор отримав чотири нові перевірки: жоден `Engineer:`/`Ops:`/`Security:` блок у manager-only проекціях; кожен `<details>` має оточуючі порожні рядки (інакше `<summary>` рендериться як plain HTML); у `docs/` немає заборонених framework-specific конструкцій; inter-doc посилання працюють для marksman LSP (відносні шляхи або wiki-links). Промпт-скелет LLM-проекцій оновлено — генератор видає `<details>`, не `???`, і дотримується списку заборонених синтаксисів. Subset **forward-compatible**: будь-який збирач (MkDocs, VitePress, Antora) підключається пізніше без переписування контенту. Дзеркало `.cursor/rules/n-ci4.mdc` синхронізується наступним прогоном `npx @nitra/cursor`.

## [1.29.5] - 2026-05-29

### Added

- **`rules/js-bun-db/js-bun-db.mdc`** (`version` 1.11 → 1.12) і дзеркало `.cursor/rules/n-js-bun-db.mdc` — нова секція **`## sql.array(arr, type) для передачі масивів`**: обов'язкове використання `pgWrite.array(arr, type)` / `pgRead.array(arr, type)` замість прямої підстановки JS-масиву `${arr}` або cast-синтаксису `${arr}::type[]` у Bun SQL template literal. Другий аргумент типу обов'язковий — без нього Bun не може вивести pg-тип. Секція містить таблицю заборонених/дозволених патернів, таблицю відповідності JS↔PostgreSQL типів і повний приклад UNNEST + MERGE. Рядок `| Масив значень у \`unnest(...)\` | \`sql.array(arr, type)\` — обов'язково з типом |` додано до таблиці рішень.
- **`rules/bun/bun.mdc`** (`version` 2.0 → 2.1) і дзеркало `.cursor/rules/n-bun.mdc` — `@playwright/test` додано до **root-only** винятків у `devDependencies` кореневого `package.json` (поряд із Vitest/Stryker peer/tools для `n-cursor coverage`).
- **`rules/bun/policy/package_json/package_json.rego`** — `"@playwright/test"` додано до набору `allowed_root_test_deps`; дозволяє споживачам тримати пакет у root `devDependencies` без порушення bun-policy.
- **`rules/bun/policy/package_json/package_json_test.rego`** — новий тест `test_allow_playwright_test`: перевіряє, що `@playwright/test` у root `devDependencies` не генерує deny.

## [1.29.4] - 2026-05-29

### Fixed

- **`.claude-template/hooks/capture-decisions.sh`**, **`.claude-template/hooks/normalize-decisions.sh`** — директива sourcing-у helper-а змінена з `# shellcheck source=npm/.claude-template/hooks/lib/tooling-only.sh` на `# shellcheck source=lib/tooling-only.sh disable=SC1091`. Без `-x` shellcheck не «заходить» у sourced-файл і видає **SC1091** (info), а `runFinalShellcheck` у `rules/text/lint/run-shellcheck.mjs` валить `lint-text` на будь-якому ненульовому exit (info включно). Відносний `source=lib/tooling-only.sh` узгоджений із фікстурою `scripts/tests/sync-claude-config.test.mjs`; `disable=SC1091` глушить info у фінальному прогоні без `-x`. Проєктні копії в `.claude/hooks/` синкаються звідси.

## [1.29.3] - 2026-05-29

### Changed

- **`rules/test/js/stryker_config.mjs`** — концерн `stryker_config` додає у кореневий `.gitignore` ще й `**/coverage/` (весь ефемерний output vitest v8 coverage: `lcov.info` + HTML `lcov-report/`) поряд із `**/reports/stryker/`. Coverage регенерується кожним прогоном, фінальні метрики живуть у `COVERAGE.md`; gitignore не заважає `n-cursor coverage` читати `lcov.info` у тому ж прогоні. Секцію .gitignore перейменовано на `# Test artifacts: Stryker + coverage`. Документація — `test.mdc`.
- **`rules/test/js/stryker_config.mjs`**, **`rules/test/js/cargo_mutants_config.mjs`**, **`rules/tauri/js/cargo_mutants_config.mjs`** — `check()` → `check(cwd = process.cwd())` за test.mdc canon (production functions приймають перший параметр `cwd`). До цього усі три концерни читали лише `process.cwd()`, через що тести змушені були викликати `process.chdir(dir)` (з обходом regex-сканера `no-process-chdir` через `import { chdir } from 'node:process'`). Stryker крутить vitest у threads-pool (`@stryker-mutator/vitest-runner` форсує `pool: 'threads'`, перетираючи `pool: 'forks'` у `vitest.config.js`), де `process.chdir` не підтримується → dry-run обривався з `process.chdir() is not supported in workers`. Тести у `tests/stryker_config.test.mjs`, `tests/cargo_mutants_config.test.mjs` (обидва правила) переписано на `runCheckIn(dir) → check(dir)` без chdir.

### Fixed

- **`tests/integration-repo-checks.test.mjs`** — `test.skipIf(env.STRYKER_MUTATOR_WORKER)` для test `узгоджені з поточним деревом cursor` (env-import із `node:process` за `js-run.mdc`). Stryker копіює репо у `reports/stryker/.tmp/sandbox-XXX/` і запускає тести звідти; `REPO_ROOT = join(import.meta.dirname, '..', '..')` резолвиться у sandbox-копію, де `checkNpmModule` валідує CHANGELOG vs HEAD-version без живого `.git/` і повертає 1 → Stryker dry-run обривається (`ConfigError: There were failed tests in the initial test run.`). Для mutation-analysis інтеграція проти живого дерева не дає додаткової сигналу понад per-rule unit-тести.
- **`rules/js-lint/coverage/coverage.mjs:236`** `runStryker` — `bunx` → `npx` для запуску `@stryker-mutator/core`. Bunx **завжди** інсталює пакет у `T/bunx-<uid>-<pkg>@latest/node_modules/` і запускає Stryker звідти, навіть коли локальний install уже існує у hoisted node_modules. Stryker plugin-discovery (`@stryker-mutator/*`) globится відносно core-install-каталогу (`core/dist/src/di/plugin-loader.js:79` → `../../../../../@stryker-mutator/*`) — у bunx-temp бачить лише `core/api/instrumenter/util` (всі чотири у `IGNORED_PACKAGES`), а локально встановлений `@stryker-mutator/vitest-runner` залишається невидимим. Worker-процеси падають з `Cannot find TestRunner plugin "vitest". In fact, no TestRunner plugins were loaded.`. До зняття `mutate`-обмеження (1.29.2) баг маскувався: при єдиному файлі у scope Stryker incremental-кеш мав усі 141 мутант → fresh-workers не стартували → плагін не потрібен. Після розширення `mutate` 25052 мутантів вимагали свіжих workers — баг проявився. `npx` шукає у локальному `node_modules/.bin/` (walking up), запускає Stryker з cursor-репо install, де поряд лежить vitest-runner.

## [1.29.2] - 2026-05-29

### Changed

- **`npm/stryker.config.mjs`** — знято тимчасове обмеження `mutate: ['rules/test/coverage/coverage.mjs']` (yet single orchestrator-file). Тепер Stryker мутує весь production-код cursor-репо: `scripts/**/*.mjs` (lib/utils/CLI helpers), `rules/**/*.mjs` (`<r>/{js,lib,coverage,fix.mjs}`), `bin/**/*.{js,mjs}`. Виключаємо `**/tests/**`, `**/__fixtures__/**`, `**/fixtures/**`, `**/data/**`, `**/template/**`, `**/templates/**` — це або тести/фікстури (Stryker і так пропускає за іменем, але дублюємо явно для прозорості), або baseline-шаблони/JSON-канон, що копіюються консьюмерам як-є й не мають логіки для мутації (інфляція survived-рейтингу без сенсу). Explicit include для `rules/test/js/data/stryker_config/stryker-vue-macros-ignorer.mjs` — єдиний `data/`-файл із власними юніт-тестами (`tests/stryker-vue-macros-ignorer.test.mjs`). Мотивація — `lib/`-модулі правил (`abie/lib/env-dns.mjs`, `abie/lib/http-route.mjs` тощо) мають юніт-тести у `<rule>/lib/tests/`, але до зняття обмеження не потрапляли у mutation-score — JS-row у `COVERAGE.md` показував лише orchestrator.

## [1.29.1] - 2026-05-28

### Fixed

- **`rules/test/js/data/vitest_config/vitest.config.baseline.js`** canon — додано `exclude: ['**/node_modules/**', '**/dist/**', '**/reports/stryker/**']`. Без exclude vitest пiдхоплює sandbox-копії тестів зі `reports/stryker/.tmp/sandbox-…/`, що залишилися від інкрементальних або аборнутих Stryker-прогонів; ці тести запускаються поза реальним repo root і фейляться (типово — `integration-repo-checks.test.mjs`, яке очікує bun.lock, bunfig.toml, кореневий `package.json`). Дзеркальна правка у `npm/vitest.config.js` для самого cursor-репо.

## [1.29.0] - 2026-05-28

### Changed

- **`rules/ci4/ci4.mdc`** (`version` 2.0 → 2.1) — додано секцію **«Зв'язок із `.cursor/rules`»**: архітектурна документація у `docs/` не дублює зміст `.cursor/rules/*.mdc` (операційні правила лінту, тестів, CHANGELOG, версіонування), а посилається на потрібне правило через його ім'я у бектиках (наприклад, `див. **`changelog`**`). Дублікати розходяться з оригіналом і ламають automatic-перевірки `npx @nitra/cursor fix`/`check`; правки робляться в одному місці — у самому `.mdc`.

## [1.28.8] - 2026-05-28

### Added

- **`rules/test/js/data/stryker_config/stryker-vue-macros-ignorer.mjs`** — новий локальний Stryker `Ignore`-плагін `vue-macros`. `shouldIgnore(path)` повертає non-empty message для `CallExpression`, де `callee` — `Identifier` з ім'ям у наборі Vue `<script setup>`-макросів: `defineProps`, `defineEmits`, `defineModel`, `defineSlots`, `defineExpose`, `defineOptions`. Експортує `strykerPlugins: [{kind: 'Ignore', name: 'vue-macros', value: {shouldIgnore}}]` — це формат, який очікує stryker-core plugin-loader (`module.strykerPlugins`); без імпорту `@stryker-mutator/api`.
- **`rules/test/js/data/stryker_config/stryker.config.vue.baseline.mjs`** — vue-варіант baseline `stryker.config.mjs`: дефолтні поля + `plugins: ['@stryker-mutator/vitest-runner', './stryker-vue-macros-ignorer.mjs']` і `ignorers: ['vue-macros']` (поряд із vitest-runner тепер треба явно вказати runner, бо ручний `plugins` затирає Stryker default).
- **`rules/test/js/stryker_config.mjs`** — концерн `stryker_config` тепер детектить `.vue` файли під `<jsRoot>/src/**` (skip `node_modules`/`dist`/`reports`) і у JS-roots із SFC ставить vue-варіант baseline + копіює `stryker-vue-macros-ignorer.mjs` поряд із конфігом. Backward-compat: jsRoot без `.vue` отримує дефолтний baseline без `plugins`/`ignorers`. Обидва файли копіюються через `ensureBaselineFile` — idempotent, ручні модифікації не перетираються. Tests: `+5` сценаріїв у `rules/test/js/tests/stryker_config.test.mjs` (vue-detection happy path, no-vue → дефолт, mixed monorepo, `.vue` лише у node_modules — НЕ vue, idempotency для vue-файлів) + новий `rules/test/js/tests/stryker-vue-macros-ignorer.test.mjs` (всі 6 макросів, non-macro callee, non-CallExpression, MemberExpression callee, anonymous callee).
- **`rules/test/test.mdc`** (`version` 2.5 → 2.6) і дзеркало `.cursor/rules/n-test.mdc` — нова підсекція "Vue SFC (`<script setup>` macros)" у "Налаштування mutation-testing" з описом коли тригериться vue-варіант, які макроси скіпаються, чому без плагіна `@vue/compiler-sfc` падає на coverage-тернарнику Stryker. Мотивація — інакше boilerplate `// Stryker disable next-line` потрібен у кожному SFC.

## [1.28.7] - 2026-05-28

### Fixed

- **`rules/js-lint/coverage/coverage.mjs`** — `n-cursor coverage` із кореня тепер працює у monorepo з частковим покриттям тестами (наприклад, `ai`: тести лише у `gt/`, інші `cf/*`/`run/*` без тестів). До цього перший workspace без тестів обривав ланцюг із `JS coverage exit 1`. Зміни: `defaultRunner.runJsCoverage` отримує `--passWithNoTests` (vitest 4.x — exit 0 у workspace без тестів); `collect()` розпиляно на `collectOneRoot(jsRoot, cwd, runner)` (per-workspace) і публічний агрегатор. `collectOneRoot` повертає `null` для workspace із порожнім lcov — Stryker у такому випадку не запускається. Реальні помилки (vitest exit ≠ 0, mutation.json відсутній при наявних тестах, compile errors) — throw, не маскуються. Якщо тестів немає у жодному workspace — `collect()` повертає `[]`, оркестратор `rules/test/coverage/coverage.mjs:runCoverageSteps` обробляє це як exit 1 із explainer-ом. Helpers `addCoverage`/`addMutation` беруться з `rules/test/coverage/coverage.mjs` (DRY, замість локальних копій). Додано 4 тести: monorepo з порожніми workspaces (skip), all-empty (`[]`), single-package без тестів, vitest exit ≠ 0 у monorepo (throw). ADR — `docs/adr/2026-05-28-coverage-multi-workspace-iteration.md`.
- **`rules/test/test.mdc`** (`version` 2.4 → 2.5) і дзеркало `.cursor/rules/n-test.mdc` — секція "Multi-workspace iteration" з описом ітерації `resolveAllJsRoots()` і поведінки skip workspaces без тестів.

### Changed

- `abie` rule: `abie.package_json_docs` → `abie.package_json_shared`; пакет, що його перевіряє правило, перейменовано з `@nitra/abie-docs` на `@nitra/abie-shared` (паралель до `efes.package_json_shared` / `@nitra/efes-shared`). Реалізація: `npm/rules/abie/policy/package_json_shared/` (перейменовано з `package_json_docs/`). Bump `abie.mdc` `1.21` → `1.22`. Зачеплено: `.cursor/rules/conftest.mdc`.

## [1.28.5] - 2026-05-28

### Fixed

- **`scripts/utils/resolve-js-root.mjs`** — `resolveAllJsRoots()` тепер розгортає glob-патерни (`cf/*`, `packages/*` тощо) у списку `workspaces` через `node:fs/promises#glob`. Без цього `cf/*` як літерал не резолвився через `existsSync`, і `resolveJsRoot()` падав на fallback → `cwd`; `n-cursor coverage` із кореня запускав vitest у root-у проєкту без `vitest.config.js` (через `gt/tests/setup.mjs` не підвантажувався). `resolveJsRoot()` тепер тонкий wrapper над `resolveAllJsRoots()[0]`. Додано 2 тести: glob-розгортання `cf/*` і fallback на `[cwd]` при порожньому збігу.
- **`rules/js-lint/coverage/coverage.mjs#collect()`** — у monorepo тепер ітерує **всі** JS-roots з `resolveAllJsRoots()`, запускає vitest + Stryker у кожному окремо та сумує `lcov` (через локальний `addCoverage`) і `mutation` (через `addMutation`). Шляхи у `survived[].file` і `survived[].exampleTest.testFile` рібейзяться відносно `cwd` (`relative(cwd, jsRoot)`/`join(wsRel, file)`), щоб `coverage-fix.mjs#buildFixPrompt` коректно читав source-файли через `join(projectRoot, file)`. `detect()` повертає `true`, якщо vitest є хоча б в одному workspace АБО в кореневому `package.json`. Додано тест агрегування у monorepo з `cf/*`-glob.

## [1.28.4] - 2026-05-28

### Changed

- **`rules/test/auto.md` → `завжди`**, **`scripts/auto-rules.mjs`** — правило `test` тепер додається у `.n-cursor.json#rules` беззастережно (як `adr`/`security`/`text`), без вимоги наявності `*.test.mjs`. `AUTO_RULE_ORDER` отримало `test`; `addRule('test')` викликається з безумовного блоку. Тест `auto-rules.test.mjs` оновлено: `'test'` додано в `ALL_RULES` і в очікуваний результат «додає правила за ознаками проєкту». Мотивація — узгодження з `bun.mdc`-винятком на root-only Vitest/Stryker peer/tools: щоб умова «корінь єдине місце для `vitest`/`@vitest/coverage-v8`/`@stryker-mutator/vitest-runner`» була правдою у кожному споживачі `@nitra/cursor`, а не «лише у dog food-репо».
- **`rules/bun/bun.mdc`** (`version` 1.9 → 2.0) і дзеркало `.cursor/rules/n-bun.mdc` — переформульовано пункт про root-only Vitest/Stryker peer/tools: прибрано згадку «лише для dog food-репо `@nitra/cursor`», тепер виняток описано як **загальний** для будь-якого монорепо, що вмикає правило `test` і виконує `n-cursor coverage`. Структурна причина: published workspace-и за `npm-module.mdc` не мають `devDependencies`, а оркестратор coverage запускається з кореня. Збігається з фактичною поведінкою `policy/package_json/package_json.rego` (`allowed_root_test_deps` — глобальний whitelist, без перевірки імені репо).
- **`rules/bun/policy/package_json/package_json.rego`** — оновлено docstring модуля і коментар на `allowed_root_dev_dependency`: тепер посилаються на always-on `test/auto.md` + `npm-module.mdc`, без «dog food-прогонів». Логіка `allowed_root_test_deps` не змінилася — лише пояснення синхронізовано з `bun.mdc`.

## [1.28.3] - 2026-05-28

### Changed

- **`rules/efes/`** — слідом за перейменуванням `@nitra/efes-docs` → `@nitra/efes-shared` (репо `efes-cloud/docs` → `efes-cloud/shared`): rego-полісі `npm/rules/efes/policy/package_json_docs/` перейменовано на `package_json_shared/`, namespace `efes.package_json_docs` → `efes.package_json_shared`, перевірка тепер шукає `@nitra/efes-shared` у `devDependencies`. Оновлено `efes.mdc` (version `1.1` → `1.2`) та шлях до GraphQL-схеми у `graphql.mdc` (`node_modules/@nitra/efes-shared/schema/maya.graphql`). Споживачі efes-проєктів мають перейти на `@nitra/efes-shared`.

## [1.28.2] - 2026-05-28

### Changed

- **`rules/rust/coverage/coverage.mjs`** — `cargo mutants` запускається з `--jobs N` (дефолт `min(4, cpus/2)`, override через env `CARGO_MUTANTS_JOBS`). Прапорець `--in-place` прибраний — cargo-mutants створює власну sandbox-копію в `target/mutants.<i>/`, що **обов'язкове** для `--jobs > 1`. Ефект: ~7× прискорення Rust mutation testing на 8-ядерних машинах (виміряно на Tauri-проєкті: 8 год → ~65 хв). Edge-case `--in-place` лишається доступним користувачам напряму через `cargo mutants --in-place` (наш runner його більше не передає). Експортовано `resolveJobs(envValue)` і `buildCargoMutantsArgs({ manifestPath, outDir, jobs })` як чисті функції для unit-тестів.
- **`rules/tauri/js/cargo_mutants_config.mjs#TAURI_KEY_SNIPPETS.exclude_globs`** — додано `src/lib.rs` як Tauri runtime entrypoint (`pub fn run`). Один мутант там тримає весь app shell, тому ділить sandbox-фейл з `src/main.rs`. Дзеркальна правка в `tauri.mdc` (canon-TOML і пояснення семантики).
- **`rules/rust/rust.mdc`** (`version` 1.1 → 1.2), **`rules/tauri/tauri.mdc`** (`version` 1.3 → 1.4) — актуалізовано опис паралельного запуску cargo-mutants і Tauri exclude_globs.

## [1.28.0] - 2026-05-27

### BREAKING

- **`scripts/utils/test-helpers.mjs#withTmpCwd` видалено**. Замість нього — `withTmpDir(async dir => …)`, що **НЕ** мутує `process.cwd()`, а передає абсолютний шлях `dir` у callback. Усі callers (43 тестових файли у пакеті) переписано: `await writeJson(join(dir, …), …)`, `await ensureDir(join(dir, …))`, `execFile('git', […], { cwd: dir })`, `await check(dir)`. `writeJson` і `ensureDir` тепер вимагають абсолютних шляхів (кидають помилку на relative). Споживачі пакета — мігруйте за конвенцією з `test.mdc` (секція "Заборона `process.chdir` у тестах").
- **Production-API: 24 `check()`/`applies()` JS concerns приймають `cwd = process.cwd()` параметром** у `rules/{abie,adr,bun,capacitor,changelog,docker,ga,graphql,hasura,image-avif,image-compress,js-bun-db,js-lint,js-run,k8s,nginx-default-tpl,npm-module,rego,rust,security,style-lint,test,text,vue}/js/*.mjs`. Default зберігає CLI-сумісність (runner викликає без аргументів — default `process.cwd()` спрацює).

### Fixed

- **Race у `process.cwd()` між паралельними vitest workers** (root cause). У default `pool: 'threads'` усі workers ділять один процес. Паралельні `withTmpCwd` ламали один одному cwd: `git init`+`git commit` із фікстури `rules/changelog/.../check.test.mjs` (з `user.name=test`, `user.email=test@test`, `-m 'init'`) потрапляли в реальний робочий репозиторій, де відбувався vitest run, і знищували `npm/package.json` (зменшували до `{"name":"mono"}`) + `npm/CHANGELOG.md` (зрізали до плейсхолдера). Race множив rogue commits/branches (`feat/x`, `feat/docs`, `feat/sync`) щоразу при запуску `bun run coverage`/Stryker. Усунено повним переписуванням `withTmpCwd` → `withTmpDir` (без `chdir`), `cwd` параметром у production функціях і defense-in-depth `pool: 'forks'`.
- **`rules/test/coverage/coverage.mjs:192`** — dynamic import шлях `../../scripts/coverage-fix.mjs` резолвив у неіснуючий `npm/rules/scripts/coverage-fix.mjs`. Виправлено на `../../../scripts/coverage-fix.mjs` (реальний файл — у `npm/scripts/`). Усуває ERR_MODULE_NOT_FOUND у `n-cursor coverage --fix` і прибирає потребу у stub-стратегії, що створювала race-небезпечні fs-артефакти у production tree `rules/scripts/`.

### Added

- **`rules/test/js/no-process-chdir.mjs`** — JS concern: сканує `**/*.test.{js,mjs}` і падає на `process.chdir(`. Token-based regex (`/process\.chdir\s*\(/u`) — не зачіпає згадки у JSDoc. 8 unit-тестів.
- **`rules/test/js/no-relative-fs-path.mjs`** — AST-сканер (`oxc-parser`) для `**/*.test.{js,mjs}`: знаходить виклики FS-функцій з `node:fs`/`node:fs/promises` (`writeFile`, `copyFile`, `mkdir`, `readFile`, `existsSync`, `rename`, `symlink`, `cp`, `*Sync`-варіанти + `writeJson`/`ensureDir`), де path-аргумент — string literal без префікса `/`, `\`, `file:`, `http(s):`, `data:`, чи Windows-disk-letter. Покриває обидва path-arg позиції у `copyFile`/`rename`/`symlink`/`link`/`cp`. Виловив би інцидент 1.28.0 (`tests/check-rule-fixtures.test.mjs`): `copyFile(src, 'default.conf.template')` / `copyFile(src, 'values-dev.ini')` зливали fixture у production tree `npm/`. 17 unit-тестів; на власному репо знайдено 0 порушень серед 106 test files.
- **`rules/test/js/vitest-config-pool-forks.mjs`** — JS concern: substring-перевірка `pool: 'forks'` у `vitest.config.js`. Defense-in-depth. 6 unit-тестів.
- **`rules/test/js/data/vitest_config/vitest.config.baseline.js`** — canonical baseline тепер містить `pool: 'forks'` з обґрунтуванням race-bug у docstring.
- **`rules/test/test.mdc` — секція "Заборона `process.chdir` у тестах"** із описом інциденту, контрактом `withTmpDir`, посиланнями на нові concern'и.

### Changed

- **`scripts/utils/test-helpers.mjs`** — `withTmpDir(fn)` без `chdir`; `writeJson(absPath, data)` і `ensureDir(absPath)` валідують `isAbsolute`. Docstring описує інцидент.
- **`vitest.config.js`** — `pool: 'forks'` як defense-in-depth з повним коментарем.

## [1.27.9] - 2026-05-27

### Added

- **`test/coverage` — мутаційне покриття 98.58%**: додано 1 тест (`runCoverageCli з opts.fix=true: 2-й withLock-fn повертає число`) для знищення ArrowFunction-мутанта L210:33 (`() => runCoverageSteps({fix:false})` → `() => undefined`); у `coverage.mjs` L185 отримав `// Stryker disable next-line StringLiteral` (еквівалентний мутант: `writeFile(…,'')` ≡ `writeFile(…,'utf8')` у Bun/Node). Stryker: Killed 139/141 (2 ignored-equivalent), Survived 2 (equivalent ObjectLiteral/BooleanLiteral у тілі стрілки без активного cwd). Тестів: 57 → 58.

## [1.27.8] - 2026-05-27

### Added

- **`test/coverage` — мутаційне покриття 97.20%**: +20 тестів (describe-блоки: `allSurvived flatMap edge cases`, `renderMarkdown survived-таблиці`, `runCoverageSteps opts.fix branch`, `runCoverageCli withLock wrapper`). Stryker: Killed 139/143, Survived 2 (equivalent), NoCoverage 2 (unreachable without coverage-fix stub). `npm/rules/test/coverage/tests/coverage.test.mjs`: 19 → 57 тестів.

## [1.27.7] - 2026-05-27

### Fixed

- **Vitest `process.chdir` race у `withTmpCwd`**: у `npm/vitest.config.js` виставлено `pool: 'forks'`. У default `pool: 'threads'` усі workers ділять один процес, тож паралельні `withTmpCwd` із `scripts/utils/test-helpers.mjs` ламали один одному `process.cwd()`. Це призводило до того, що `git init`+`git commit` з `cwd: process.cwd()` у тестах `rules/changelog/.../check.test.mjs` (з `user.name=test`, `user.email=test@test`, `-m 'init'`) потрапляли в реальний репо, де відбувався vitest run, і знищували `npm/package.json` / `npm/CHANGELOG.md`. Forks ізолюють процеси, race усунутий. JSDoc у `withTmpCwd` доповнено попередженням.

## [1.27.6] - 2026-05-27

### Added

- **`test/coverage` — розширені мутаційні тести**: 18 нових тест-кейсів у `rules/test/coverage/tests/coverage.test.mjs` для покриття 19 вцілілих мутантів (StringLiteral, ConditionalExpression, ArrowFunction, BlockStatement-гілки). Загальна кількість тестів: 37.

## [1.27.5] - 2026-05-26

### Added

- **`text` rule — strict programmatic checks для Prettier-артефактів**: новий JS concern `rules/text/js/forbidden-prettier.mjs` падає (exit 1) при наявності у корені будь-якого з `.prettierignore`, `.prettierrc`, `.prettierrc.{json,jsonc,json5,yaml,yml,toml,js,cjs,mjs,ts,cts,mts}`, `prettier.config.{js,cjs,mjs,ts,cts,mts}`. Раніше `npx @nitra/cursor fix text` пропускав `.prettierignore` і нові 3.x-формати, бо у `formatting.mjs` мав hardcoded старий короткий список.
- **`text.package_json` Rego — token-based deny для `scripts.*`**: `bunx prettier --write .`, `npx prettier --check .`, `prettier --write src`, `./node_modules/.bin/prettier …` тепер ловляться у `deny` через regex `(^|[\s/"'])prettier($|[\s'"@])`. Покривається unit-тестами (`rules/text/policy/package_json/package_json_test.rego` + `js/tests/forbidden-prettier.test.mjs`).

### Changed

- **`rules/text/js/formatting.mjs`**: inline стародавній цикл по `['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']` видалено — Prettier-FS-сторону тепер цілком покриває окремий concern `text.forbidden-prettier`.
- **`rules/text/text.mdc` (`## Перевірка`)**: явно зафіксовано, що `npx @nitra/cursor fix text` падає на `.prettierignore`, `.prettierrc*`, `prettier.config.*` і будь-який `package.json#scripts` із токеном `prettier`.

## [1.27.4] - 2026-05-26

### Fixed

- **`lint-text` / v8r**: локальний catalog тепер явно матчить `tsconfig.json` у `.pi/extensions/*/` та `npm/.pi-template/extensions/*/`, щоб schema validation не падала на hidden/template шляхах.
- **`text` rule**: додано Rego-перевірку, що забороняє `prettier` у `package.json#scripts`; canonical formatter лишається `oxfmt`.

### Changed

- **Dog food dependencies**: bun-rule явно дозволяє root-only Vitest/Stryker peer/tools (`vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`) у цьому monorepo, бо `npm-module` забороняє `devDependencies` у published workspace `npm/`.

## [1.27.3] - 2026-05-26

### Fixed

- **`js-lint` coverage provider `detect()`**: у workspace-проєктах (типовий bun monorepo з hoisted node_modules і `npm-module` правилом, що забороняє devDeps у published workspace-у) перевіряємо `vitest` як у JS-root `package.json`, так і у кореневому. Раніше `detect()` повертав false у monorepo, де vitest коректно живе у кореневому `devDependencies` — `n-cursor coverage` мовчки виходив із «0 провайдерів».

### Changed

- **Dog food-міграція cursor → vitest**: 100 `*.test.mjs` файлів у `npm/` перенесено з `bun:test` на `vitest`; `npm/stryker.config.mjs` оновлено до canonical baseline (vitest-runner + perTest + incremental); додано `npm/vitest.config.js`; кореневий `package.json#devDependencies` отримав `vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`. Node-сумісність: `Bun.file().text()` → `readFile(..., 'utf8')`, `Bun.spawn` → `spawnSync`, `import.meta.dir` → `dirname(fileURLToPath(import.meta.url))`, EventEmitter duck-typing → `new EventEmitter()` (node-у `events.once` приймає лише EventEmitter екземпляри).
- **`npm/rules/vue/vue.mdc` v2.4**: секцію «Тестування» приведено у відповідність із новим canon-ом (`test.mdc` v2.4 → Vitest+happy-dom). Замість прямого заперечення Vitest у Vue-проєктах рекомендується frontend-варіант `vitest.config.js` з `environment: 'happy-dom'`.

## [1.27.2] - 2026-05-26

### Changed

- **`package.json#dependencies`**: `@anthropic-ai/claude-code` (^1.0.0) → `@anthropic-ai/claude-agent-sdk` (^0.3.0). У claude-code v2.x пакет реструктуризовано в CLI-only (binary через optionalDependencies, без `sdk.mjs`); SDK з функцією `query` винесли в окремий пакет `@anthropic-ai/claude-agent-sdk`. Сигнатура `query({ prompt, options })` і поля `options.cwd/maxTurns/allowedTools` зберігаються.
- **`scripts/coverage-fix.mjs`**: дзеркальна заміна імпорту `@anthropic-ai/claude-code` → `@anthropic-ai/claude-agent-sdk`. Тіло споживача без змін.

### Fixed

- **ADR Stop-hook у Node v26 / Zed**: `capture-decisions.sh` спавнить bare `claude -p` як subprocess. PATH у Zed Claude Agent-сесіях має `node_modules/.bin` попереду `/opt/homebrew/bin`, тож визначав локальний `@anthropic-ai/claude-code@1.0.128`, який краш-падає на старті під Node 26 (`TypeError: Cannot read properties of undefined (reading 'prototype')` у bundled google-auth-library коді, який припускає, що `require('stream')` повертає клас зі `.prototype`). Хук фіксував `empty response from LLM CLI` і виходив без створення чернетки. Після зняття v1-залежності з канона `node_modules/.bin/claude` shadow зникає, subprocess визначає системний `claude` (homebrew або інший global) — той працює під Node 26 без правок.

## [1.27.1] - 2026-05-26

### Changed

- Стилістичні правки бенчмарк-артефактів `benchmarks/runner-comparison/` (форматування таблиць, code blocks) після ручного `bun run lint` у dev-репо. Без змін у поведінці rules чи правила `test`.

## [1.27.0] - 2026-05-26

### Changed

- **`rules/test/js/data/stryker_config/stryker.config.baseline.mjs`**: канон Stryker перейшов з `command` runner (`bun test`, `concurrency: 1`, `inPlace: true`, `coverageAnalysis: 'off'`) на `vitest` runner з `coverageAnalysis: 'perTest'`. У verify-first spike (158 мутантів, `benchmarks/runner-comparison/SPIKE.md`) це дало 31×–57× прискорення повного прогону і ≈262× для incremental noop-прогону. `inPlace` більше не потрібен — vitest-runner ізолює мутантів через AST-patching у пам'яті, без копіювання node_modules у sandbox (стара проблема command runner у Bun monorepo).
- **`rules/test/js/stryker_config.mjs`**: концерн тепер копіює два canonical baseline-и у кожен JS-root: `stryker.config.mjs` + `vitest.config.js`. Ідемпотентність збережена — обидва файли копіюються лише якщо ще немає.
- **`rules/js-lint/coverage/coverage.mjs`**: `detect()` тепер шукає `vitest` у `dependencies`/`devDependencies` (раніше — `scripts.test:coverage` або `scripts.test` з `--coverage`). `runJsCoverage` спавнить `bunx vitest run --coverage --coverage.reporter=lcov --coverage.reportsDirectory=…` замість `bun run test:coverage --coverage-reporter=lcov`. `parseLcov` без змін — формат lcov у Vitest v8-coverage співпадає з тим, що віддавало `bun test --coverage`. Якщо vitest відсутній — `detect` повертає `false` із одноразовим hint у stderr.
- **`rules/test/policy/package_json/template/package.json.contains.json`**: канон scripts тепер містить додатково `"test": ["vitest"]` (substring-вимога). `coverage` як було — `["n-cursor coverage"]`.
- **`rules/test/test.mdc` v2.4**: нові розділи «Vitest baseline та `package.json#scripts`» і «Frontend-варіант (Vue/Vite + happy-dom)». Текст про purpose `bun test --coverage` оновлено на `vitest run --coverage`. `globs` додатково ловить `vitest.config.js`.

### Added

- **`rules/test/js/data/vitest_config/vitest.config.baseline.js`**: новий canonical baseline для Vitest (`environment: 'node'`, `coverage.provider: 'v8'` із lcov+text-summary, `include: ['**/*.test.{js,mjs}', 'tests/**/*.test.{js,mjs}']`). Концерн `stryker_config` копіює його як `vitest.config.js` у кожен JS-root.
- **`rules/test/js/tests/stryker_config.test.mjs`**: нові кейси — копіювання `vitest.config.js`, перевірка вмісту нового Stryker baseline (`testRunner: 'vitest'`, `coverageAnalysis: 'perTest'`), ідемпотентність `vitest.config.js`.
- **`rules/test/policy/package_json/package_json_test.rego`**: нові кейси для `scripts.test` — deny при відсутності/некоректному значенні, allow при substring-розширенні.

## [1.26.3] - 2026-05-26

### Added

- **`rules/tauri/js/cargo_mutants_config.mjs`**: новий концерн tauri-правила. Для кожного `<ws>/src-tauri/Cargo.toml` без дублювання гарантує наявність Tauri-канонічних ключів у `<ws>/src-tauri/.cargo/mutants.toml` — `additional_cargo_test_args = ["--lib", "--tests"]` та `exclude_globs` для `src/main.rs` (binary shell) і platform-bridge файлів (`*android.rs`, `*ios.rs`, `*mobile.rs`, `*desktop.rs`, `*macos.rs`, `*windows.rs`, `*linux.rs`). Семантика: ці файли — boundary, бізнес-логіка повинна жити у platform-neutral модулях. Файл відсутній → створює повний baseline; всі канонічні ключі є → `manual cargo-mutants config preserved`; частина ключів відсутня → додає лише відсутні в окремий блок у кінці, без зміни існуючих значень.
- **`rules/tauri/js/tests/cargo_mutants_config.test.mjs`**: 7 тестів — silent skip без Tauri, створення baseline, ідемпотентність (повторний прогон байт-в-байт), збереження ручних налаштувань, partial-merge (додаються лише відсутні ключі), кілька src-tauri у різних workspaces, augmentation поверх нейтрального test-rule baseline.
- **`rules/tauri/tauri.mdc` v1.3**: нові розділи «Виявлення проєкту Tauri» (опис маркерів і workspace-обходу) та «Mutation-testing: семантика app shell та platform bridge» з фіксованою семантикою boundary-файлів і ідемпотентністю взаємодії з `test`-rule.

### Changed

- **`rules/test/js/data/cargo_mutants_config/mutants.toml.baseline`**: видалено Tauri-specific `additional_cargo_test_args = ["--lib", "--tests"]` — `test`-rule baseline тепер універсальний (тільки коментар, ніяких exclude'ів та framework-припущень). Customization-семантика framework-rules-ів описана в коментарі baseline'а.
- **`rules/test/test.mdc` v2.3**: додано розділ «Універсальний baseline і framework-specific tuning» — `test` володіє нейтральним baseline, framework-rules (tauri, capacitor) зобов'язані доповнювати без дублювання і не перетирати ручні налаштування.
- **`rules/tauri/js/tooling.mjs`**: виявлення Tauri тепер обходить усі workspace-пакети через `getMonorepoPackageRootDirs()` (раніше — тільки корінь). Маркером є будь-що з: `<ws>/src-tauri/`, `<ws>/src-tauri/Cargo.toml`, `<ws>/src-tauri/tauri.conf.json`, `<ws>/tauri.conf.json`, `<ws>/package.json#dependencies/devDependencies` з `@tauri-apps/*`. Дозволяє tauri-rule працювати в monorepo-проєктах, де Tauri живе в одному з пакетів, а не в корені.
- **`rules/test/js/tests/cargo_mutants_config.test.mjs`**: тест baseline тепер перевіряє відсутність framework-specific ключів (`additional_cargo_test_args`, `exclude_globs`) у нейтральному baseline-файлі.

## [1.26.2] - 2026-05-26

### Changed

- **`rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json`**: канон тепер містить поле `ignore` із трьома обов'язковими патернами — `.claude/worktrees/**`, `**/dist/**`, `**/CHANGELOG.md`. `**/CHANGELOG.md` додано тому, що release-журнали різних пакетів структурно повторюються (заголовки `## [x.y.z] - YYYY-MM-DD`, секції `### Added` / `### Changed` / `### Fixed` за Keep a Changelog) і `jscpd` при `minLines: 25` фіксує їх як клон — false positive (кожен `CHANGELOG.md` per-package за каноном `n-changelog`). Існуючий rego (`policy/jscpd/jscpd.rego`) уже застосовує subset-of до будь-якого масиву в snippet, тож зміна не потребує правок коду — лише новий test-case у `jscpd_test.rego`.
- **`rules/js-lint/js-lint.mdc` v1.26**: оновлено приклад `.jscpd.json` і опис під ним (тепер посилається на snippet як source of truth і пояснює, чому `**/CHANGELOG.md` у каноні).

## [1.26.1] - 2026-05-26

### Fixed

- **`k8s/js/manifests.mjs`**: `Set.prototype.toSorted` не існує — повернено `Array.from(new Set(...)).toSorted(...)` (oxlint `--fix` помилково замінив `[...new Set(...)].sort(...)` на невалідний `new Set(...).toSorted(...)`).
- **`k8s/js/manifests.mjs`**: hardcoded CIDR-и (`35.191.0.0/16`, `130.211.0.0/22`, `10.0.0.0/8`) у `NETWORK_POLICY_GCLB_INGRESS_FROM` тепер з `// eslint-disable-next-line sonarjs/no-hardcoded-ip` (це канон GCLB, який не змінюється).
- **`k8s/js/manifests.mjs`**: `() => {}` default callback → іменована константа `noopFail` (eslint `no-empty-function`).
- **`k8s/js/tests/manifests/tests/check-schema.test.mjs`**: винесено `HR_YAML_RE`, `HTTPROUTE_NP_MAPPING_RE`, `K8S_MDC_RE`, `EXPECTED_GCLB_INGRESS_FROM`, `GCLB_HC_GLOBAL_CIDR` як module-scope константи (e18e `prefer-static-regex` + sonarjs `no-hardcoded-ip`); `mock(() => {})` → `mock(msg => msg)`.
- **`.cspell.json`**: додано `GCLB`, `gclb`, `байтово` до words.

## [1.26.0] - 2026-05-26

### Added

- **`k8s/js/manifests.mjs`**: нова `collectHttpRouteIngressForWorkload(dir, appLabel, fail)` — визначає HTTPRoute → `-hl` Service → `selector.app` mapping і повертає унікальні TCP-порти з `backendRefs[].port` для workload з міткою `appLabel`. Викликається з `appendNetworkPolicyDocuments` і `regenerateLegacyNetworkPolicyDocsInFile` під час `check k8s`.
- **`k8s/js/manifests.mjs:buildNetworkPolicyYaml`**: опційний 4-й параметр `gclbPorts: number[]` — якщо непорожній, додає ingress-правило з `ipBlock` 35.191.0.0/16, 130.211.0.0/22, 10.0.0.0/8 і TCP-портами (відсортовано). Без параметра output байтово ідентичний baseline canon.
- **`k8s.mdc` v1.42**: новий розділ «HTTPRoute → NetworkPolicy ingress (GCLB + Envoy)» з описом mapping і прикладом NetworkPolicy для HTTPRoute-paired workload.

### Fixed

- **Service blocking via NetworkPolicy** для workload-ів, прив'язаних до `HTTPRoute` через GKE Gateway: попередній canon допускав тільки `podSelector: {}` ingress, що блокувало трафік від Envoy data-plane (`10.10.0.0/23` для `us-central1-proxy-only`) і Google health checks (`35.191.0.0/16`, `130.211.0.0/22`). Тепер правило автоматично додається.

## [1.25.3] - 2026-05-26

### Fixed

- **JSDoc**: дописано опис `@returns`/`@param`-описи й типи в `rules/js-lint/coverage/coverage.mjs`, `rules/k8s/js/manifests.mjs`, `rules/adr/js/tests/*.test.mjs`, `rules/test/js/tests/*.test.mjs`, `scripts/coverage-fix.mjs`, `scripts/post-tool-use-fix.mjs`, `scripts/utils/tests/resolve-*.test.mjs` (oxlint/eslint jsdoc-правила).
- **`k8s/js/manifests.mjs`**: `JSON.parse(JSON.stringify(...))` → `structuredClone(...)` (unicorn `prefer-structured-clone`); інверсія запереченої умови в `validateNetworkPolicyForWorkload` (eslint `no-negated-condition`).
- **`k8s/policy/network_policy/network_policy.rego`**: `list_contains` → `contains_item` (regal `avoid-get-and-list-prefix`); `items[i] == item` → `some candidate in items` (`prefer-some-in-iteration`); `workload_kind` без зайвого `if {}` (`unconditional-assignment`); helper-правила переміщено після всіх `deny`, щоб задовольнити `messy-rule`. `network_policy_test.rego` переформатовано через `opa fmt`.
- **`scripts/tests/post-tool-use-fix.test.mjs`**: fake-child перероблено з `EventEmitter` на duck-typed `addListener`/`removeListener` (unicorn `prefer-event-target`).
- **`scripts/tests/cli-entry.test.mjs`**: symlink-тест /tmp ↔ /private/tmp використовує `mkdtempSync` з префіксом, зібраним з частин (sonarjs `publicly-writable-directories`).
- **`rules/test/coverage/coverage.mjs`**: множинні `push()` об’єднано в один виклик (unicorn `prefer-single-call`).
- Винесено повторно-компільовані regex у module scope (`e18e/prefer-static-regex`) у `coverage.mjs`, `test/coverage/tests/coverage.test.mjs`, `k8s/tests/manifests/tests/check-schema.test.mjs`.
- Видалено невикористаний `npm/lib/x.js` (unicorn `no-empty-file`).

### Changed

- **`.claude/hooks/{capture,normalize}-decisions.sh`** синхронізовано з `npm/.claude-template/hooks/` (включно з новим `lib/tooling-only.sh`).
- **`knip.json`**: додано entry-патерни для динамічно імпортованих/зовнішніх скриптів (stryker configs, pi extensions, fixtures, `coverage-fix.mjs`); `@anthropic-ai/claude-code`, `@anthropic-ai/sdk`, `@stryker-mutator/core` додано в `ignoreDependencies` (тип-only або dynamic import).
- **`.jscpd.json`**: ігнор-патерни розширено для template/canonical пар тієї ж природи, що вже були в винятках (`npm/.pi-template/**`, `knip-canonical.json`, `*.snippet.yaml`).
- **`.cspell.json`**: до `ignorePaths` додано `**/reports/stryker/**` (генеровані Stryker-репорти, вже в .gitignore).
- **Кореневий `package.json#scripts.lint`** — чейн `bun run lint-ga && lint-js && lint-rego && lint-security && lint-style && lint-text && oxfmt .` замість делегування до CLI (bun.mdc + security.mdc).

## [1.25.2] - 2026-05-26

### Added

- **`stryker.config.mjs` baseline**: `incremental: true` + `incrementalFile: 'reports/stryker/incremental.json'` — Stryker зберігає результати між запусками і відновлює після краш/kill (сигнал ОС). Важливо для машин з обмеженою RAM де Stryker вбивається системою після ~100 мутантів.

## [1.25.1] - 2026-05-26

### Added

- **`skills/coverage-fix/SKILL.md`** — автономна команда `/n-coverage-fix`: запускає `n-cursor coverage`, читає JSON-масив вцілілих мутантів із секції `## Вцілілі мутанти` у COVERAGE.md і ітеративно пише тести до конвергенції (max 3 ітерації). Включає заборону паралельного запуску (Stryker пише в одну директорію).

### Changed

- **`rules/test/coverage/coverage.mjs` → `renderMarkdown`**: секція вцілілих мутантів перейменована `## Recommendations` → `## Вцілілі мутанти`; доданий ` ```json ` блок з масивом survived перед таблицею — парситься skills `/n-fix-tests` і `/n-coverage-fix`.
- **`skills/fix-tests/SKILL.md`**: конвенція test-файлів оновлена — цільовий файл завжди `<dir>/tests/<basename>.test.mjs`; якщо знайдено co-located тест (`.test.js`/`.test.mjs`) — переноситься в `tests/` з оновленням imports.

## [1.19.2] - 2026-05-25

### Fixed

- **`js-lint` coverage провайдер**: виправлено `bunx stryker run` → `bunx @stryker-mutator/core run`. Стара команда (`bunx stryker`) визначає deprecated unscoped-пакет без CLI, через що `mutation.json` не створювався і coverage падав з помилкою.
- **`npm/stryker.config.mjs`**: додано `mutate: ['scripts/*.mjs', 'scripts/utils/*.mjs', 'rules/*/coverage/coverage.mjs']` — без обмеження Stryker намагався мутувати 422 файли, що робить coverage-прогін нереалістичним. `commandRunner.command` змінено на `bun test --parallel` (раніше `bun test` без флагу) — ізолює worker-процеси та запобігає git-race у withTmpCwd-тестах.

## [1.19.1] - 2026-05-25

### Fixed

- **`bun test --parallel`** як default у `npm/package.json` (`test`, `test:coverage`). Без флагу bun-test крутить усі 95 файлів у одному процесі — а `withTmpCwd` (`scripts/utils/test-helpers.mjs`) міняє глобальний `process.cwd()`, через що тести гонять один за одного: `prev = process.cwd()` ловить tmp-dir сусіднього тесту, restore робочої директорії падає `ENOENT` (бо сусід уже видалив свій tmp), або `git commit` з `cwd: process.cwd()` злітає в реальний repo з `npm/CHANGELOG.md`/`npm/package.json` як stub-fixture. `--parallel` дає окремий worker-процес на файл (з `process.cwd()` per-process), що геть знімає race. Знизило 22 тести з fail до pass, час suite'у — 211с → 47с.
- **`tests/integration-repo-checks.test.mjs`** — додано explicit `30000`ms timeout для `check-* на реальному репозиторії > узгоджені з поточним деревом cursor`. Тест послідовно ганяє 10 check-функцій із subprocess-викликами (shellcheck-стаб + conftest/opa/regal/kubeconform/kubescape) — на macOS виходить ~3-7с, дефолтний 5000ms-timeout bun-test'у не вистачає.

## [1.19.0] - 2026-05-25

### Added

- **Pi.dev інтеграція** — CLI під час синку генерує `.pi/skills/<dir>/SKILL.md` для кожного скілу з `.cursor/skills/<dir>/` із frontmatter `name`+`description` (формат pi.dev: 1-64 chars, `[a-z0-9-]`). Тіло — делегат `Виконай інструкції зі скілу .cursor/skills/<dir>/SKILL.md.`, симетрично до `.claude/commands/<dir>.md`. Always-on, без флагу. Покриває керовані (з пакета) і локальні скіли; orphan-cleanup видаляє `.pi/skills/n-*` директорії, яких немає у конфігу, і локальні директорії, яких більше немає у `.cursor/skills/`.
- `npm/bin/n-cursor.js`: константа `PI_SKILLS_DIR='.pi/skills'`, функція `formatPiSkillFrontmatter(name, desc)`, синки `syncPiSkills`/`syncLocalOnlyPiSkills` + cleanups `removeOrphanManagedPiSkillDirs`/`removeOrphanLocalPiSkillDirs`. Новий `runSyncStep('❌ Pi skills: ', …)` після Commands-блоку у головному потоці.

## [1.18.3] - 2026-05-25

### Changed

- Canonical `.cargo/mutants.toml` baseline: `additional_cargo_test_args = ["--lib", "--tests"]` — виключає `--bins` і `--doc` фази, які перебудовують Tauri-бінарник та doc-tests при кожному мутанті, збільшуючи час з секунд до хвилин.

## [1.18.2] - 2026-05-25

### Fixed

- `rules/adr/js/tests/capture-decisions-tooling-only.test.mjs`, `rules/adr/js/tests/normalize-decisions-tooling-only.test.mjs` — `process.env.HOME` → `env.HOME` із `'node:process'` (js-run.mdc: `process.env` deprecated, треба `env` з `node:process` або `@nitra/check-env`).

## [1.18.1] - 2026-05-25

### Fixed

- **`scripts/cli-entry.mjs::isRunAsCli`** + **`scripts/lib/run-rule-cli.mjs::isRunAsCli`** — функція приймала `()` без аргументів і всередині дивилася на власний `import.meta.url`, а не на caller'а. Через те, що `import.meta` лексично прив'язаний до файлу, де записаний, helper-функція ЗАВЖДИ бачила свій файл — `cli-entry.mjs` / `run-rule-cli.mjs` — і ніколи не дорівнювала `process.argv[1]`. Результат: усі ~40 `if (isRunAsCli())` у `rules/<id>/fix.mjs` / `lint/*.mjs` / `bin/rename-yaml-extensions.mjs` ЗАВЖДИ йшли в else-гілку, і `bun rules/<id>/fix.mjs` мовчки виходив `0` без жодного output'у. `npx @nitra/cursor fix <rule>` → `runFixCommand` → `spawnSync('bun', [fix.mjs])` → exit 0 без жодного reporter-звіту.
- **Fix:** функція тепер приймає `metaUrl` параметром: `isRunAsCli(import.meta.url)`. Реалізація через порівняння канонічних шляхів — це знімає різницю «symlink vs canonical» (macOS `/tmp` ↔ `/private/tmp`, pnpm content-addressable links, `node_modules/.bin/*` shim).
- **Консолідація:** `run-rule-cli.mjs::isRunAsCli` тепер `export { isRunAsCli } from '../cli-entry.mjs'` — одне джерело правди. Existing import paths у callers лишилися без змін.
- **Call sites:** всі ~40 викликів `isRunAsCli()` оновлено на `isRunAsCli(import.meta.url)`.
- **Tests:** додано три нові кейси у `scripts/tests/cli-entry.test.mjs` (entry-detection через spawn-fixture, symlink-нормалізація через `/tmp` → `/private/tmp`, no-arg fallback). Fixture — `scripts/tests/fixtures/cli-entry-as-cli.mjs`.

## [1.18.0] - 2026-05-25

### Added

- `text`: `docs/adr/**` у канонічному `ignorePaths` правила `.cspell.json` (`policy/cspell/template/.cspell.json.snippet.json`). Машинно-генеровані ADR-документи більше не валідуються cspell-ом — це розриває петлю «правка `.cspell.json` → новий ADR-draft → знову `cspell` ламається на ньому». Локальні розширення `ignorePaths` лишаються дозволені (rego subset-of).
- `adr`: ENV `ADR_NORMALIZE_SKIP_TOOLING_ONLY` (default `1`) — вимикає structural skip у capture-/normalize-хуках. Документація в `adr.mdc` (таблиця ENV) і `skills/adr-normalize/SKILL.md`.

### Changed

- `adr`: `.claude-template/hooks/capture-decisions.sh` — перед LLM-викликом перевіряє список `tool_use`-правок із transcript'у. Якщо всі правки у вузькому allowlist (`.cspell.json`, `docs/adr/*.md`, кореневі `AGENTS.md`/`CLAUDE.md`, `CHANGELOG.md`, `*/package.json` із diff виключно по ключу `version`) — `exit 0` із записом `skipping ADR capture: tooling-only session` у лог. Inline-функція `is_tooling_only_change` + `git_diff_only_version_field`, bash 3.2-сумісно.
- `adr`: `.claude-template/hooks/normalize-decisions.sh` — після формування батча для кожної чернетки читає `transcript:` із frontmatter і та сама перевірка allowlist'у. Tooling-only чернетки видаляються без виклику LLM; якщо батч порожній — `exit 0`.
- `text.mdc` 1.29 → 1.30: документація `docs/adr/**` у `ignorePaths`; приклади `.cspell.json` оновлено.
- `adr.mdc` 2.1 → 2.2: нова секція «Tooling-only skip» у Фазі 1, bullet у Фазі 2, рядок у таблиці ENV.

### Notes

- Існуючі ENV (`ADR_NORMALIZE_THRESHOLD`, `…_MIN_INTERVAL_HOURS`, `…_BATCH`, `…_DRY`, recursion-guard `CAPTURE_DECISIONS_RUNNING` / `ADR_NORMALIZE_RUNNING`) поведінку не змінюють.
- `cspell.rego` subset-of-перевірку зберігає — нічого не зламано для проєктів, де користувач уже руками додав `docs/adr/**` у свій `.cspell.json`.

## [1.17.4] - 2026-05-24

### Changed

- Концерн `stryker_config`: gitignore-патерн `**/reports/stryker/.tmp/` + `**/reports/stryker/mutation.json` замінено на один broader `**/reports/stryker/` — увесь каталог Stryker-output-у. Покриває не лише `.tmp/` + `mutation.json`, а й HTML/dashboard-репорти якщо користувач додасть інші reporter-и. Існуючі дрібніші патерни в `.gitignore` користувача не видаляються (idempotent helper лише дописує), але стають надлишковими — користувач може почистити вручну за бажанням.
- `test.mdc` 2.1 → 2.2: оновлено опис gitignore-керування під новий broader pattern.

## [1.17.3] - 2026-05-24

### Added

- Концерн `stryker_config` правила `test` тепер без дублювання додає у кореневий `.gitignore` патерни Stryker-output-у:
  - `**/reports/stryker/.tmp/` — in-place backup-каталог (з baseline-у `tempDirName`).
  - `**/reports/stryker/mutation.json` — JSON-репорт мутацій.
  - Header-секція `# Stryker mutation testing (test.mdc)`, sectioning через `ensureGitignoreEntries`.
- Спільний helper `npm/scripts/utils/ensure-gitignore-entries.mjs` — append-only модуль оновлення `.gitignore` з header-секціями. Idempotent (точне співпадіння рядка після `trim`), створює файл якщо немає, зберігає trailing-newline. 5 unit-тестів.

### Changed

- `test.mdc` 2.0 → 2.1: додано параграф про gitignore-керування Stryker-output-у в секцію «Налаштування mutation-testing».
- `stryker_config` concern: додано виклик `ensureGitignoreEntries` після копіювання baseline-ів; репортер видає pass-повідомлення про додані патерни.

## [1.17.2] - 2026-05-24

### Added

- Правило `test`: два нових концерни — `stryker_config` і `cargo_mutants_config`. Self-gating через `.n-cursor.json#rules`: концерн активний лише якщо відповідне залежне правило (`js-lint` / `rust`) enabled. **Iterate-all-workspaces**: при відсутності цільового файлу копіює canonical baseline у КОЖЕН workspace-каталог (не лише workspaces[0]).
  - `stryker.config.mjs` у кожному JS-root (всі workspaces з package.json, або cwd у single-package) — мінімум для роботи з `bun test`.
  - `.cargo/mutants.toml` у каталозі КОЖНОГО Cargo.toml-маніфесту: корінь + workspaces (з підтримкою Tauri-патерну `<ws>/src-tauri/Cargo.toml`) — коментар-плейсхолдер; cargo-mutants має робочі defaults.
- Спільні модулі визначення у `npm/scripts/utils/`: `resolveJsRoot` (single, для coverage-провайдера) + `resolveAllJsRoots` (plural, для test-концерну); `resolveCargoManifest` (single) + `resolveAllCargoManifests` (plural). Coverage-провайдери js-lint і rust повторно використовують single-варіанти.

### Changed

- `test.mdc` 1.2 → 2.0 (major): `alwaysApply: true → false`; явні `globs` (`.n-cursor.json`, `package.json`, `Cargo.toml`, mutation-config-цілі, `*.test.mjs`). Нова секція «Налаштування mutation-testing» з посиланнями на baselines.
- `js-lint/coverage/coverage.mjs`: hint при missing `mutation.json` тепер вказує на `npx @nitra/cursor fix test`. `resolveJsRoot` витягнуто у спільний модуль.
- `rust/coverage/coverage.mjs`: `resolveCargoManifest` витягнуто у спільний модуль (контракт `null` замість throw для missing manifest; user-facing throw зберігся на callsite).

## [1.17.1] - 2026-05-24

### Fixed

- **`js-lint/coverage/coverage.mjs` + `rust/coverage/coverage.mjs`** — `Bun.spawn` (runtime-only) замінено на `node:child_process.spawnSync`. CLI `n-cursor` запускається через `#!/usr/bin/env node` shebang, отже Node-runtime — `Bun.*` API недоступні в реальному прогоні (тести використовували ін'єктований runner і не виявляли цього). Тестова ін'єкція runner-а лишається тією самою (контракт `runJsCoverage`/`runStryker`/`runLlvmCov`/`runCargoMutants` mock-ів — без змін).

## [1.17.0] - 2026-05-24

### Added

- CLI-команда `n-cursor coverage` — оркестратор покриття + мутаційного тестування з discovery провайдерів через `.n-cursor.json#rules`. Канон `scripts.coverage` (контейнер `package.json`) у правилі `test`. Лок — прямий `withLock('coverage', ...)`.
- Провайдер `js-lint/coverage/` — `bun test --coverage --coverage-reporter=lcov` + `bunx stryker run`; парсить lcov.info і `reports/stryker/mutation.json`.
- Провайдер `rust/coverage/` — `cargo llvm-cov --json` + `cargo mutants --in-place`; парсить `data[0].totals` і `outcomes.json` (caught = caught + timeout; total = caught + missed; unviable виключено).
- Policy `test.package_json` з template `package.json.contains.json` — substring-вимога `scripts.coverage` містити `n-cursor coverage`.

### Fixed

- `test/coverage/coverage.mjs::loadProvider` — коли правило `test` присутнє у `.n-cursor.json#rules` (як у самому `@nitra/cursor`), оркестратор знаходив власний файл `npm/rules/test/coverage/coverage.mjs` і намагався викликати його як провайдер (`provider.detect is not a function`). `loadProvider` тепер перевіряє, що модуль експортує обидва `detect` і `collect` як функції — інакше silently skip. Regression-тест: `пропускає модулі без detect/collect (наприклад сам оркестратор)`.

### Changed

- `test.mdc` 1.1 → 1.2: додано секцію «Покриття + мутаційне тестування» з посиланням на template.
- `js-lint.mdc` 1.24 → 1.25: додано параграф із посиланням на JS-coverage-провайдер.
- `rust.mdc` 1.0 → 1.1: додано параграф із посиланням на Rust-coverage-провайдер.
- `npm/bin/n-cursor.js`: новий `case 'coverage'` + розширений help-string.

## [1.16.1] - 2026-05-24

### Fixed

- **`npm/rules/ga/js/workflows.mjs::GA_POLICY_DIR`** — flat-layout regression: `join(HERE, '..', '..', 'policy')` давав `npm/rules/policy/` замість `npm/rules/ga/policy/`. `HERE` для `rules/<rule>/js/<concern>.mjs` живе на 1 рівень ближче до `rules/<rule>/`, ніж попередній nested layout. Виправлено на `join(HERE, '..', 'policy')` — `loadTemplate(concernDir)` тепер реально читає `template/<workflow>.snippet.yml`, замість тихо повертати `{}` і втрачати `data.template.snippet.*` у rego-перевірках (`step0_with_canonical` і т.д. падали з-за `null`). Тест `check-ga: shellcheck в PATH > exit 0` тепер pass.
- **`npm/rules/adr/js/hooks.mjs::BUNDLED_HOOKS_DIR`** і **`npm/rules/adr/js/tests/hooks.test.mjs::BUNDLED_HOOKS_DIR`** — той самий клас regression-у: `..` на одне більше за потрібне після flat-layout, тож шлях зривався у `cursor/.claude-template/hooks/` замість `npm/.claude-template/hooks/`, через що `check-adr` та 4 тести у `hooks.test.mjs` падали з `ENOENT`/`канонічний скрипт не знайдено`. У `hooks.mjs` `..×4` → `..×3`; у тесті `..×5` → `..×4`.

## [1.16.0] - 2026-05-24

### Changed

- **`utils/` vs `lib/` (js-lint.mdc):** усі 10 каталогів `npm/rules/<rule>/utils/` перейменовано в `npm/rules/<rule>/lib/` — їхній вміст domain-bound (запускає hadolint, парсить kustomize/k8s-tree, конкретні AST-сканери правила, читання `.n-cursor.json` тощо), що за правилом `utils/` vs `lib/` має жити в `lib/`. Зачеплені правила: `abie`, `changelog`, `docker`, `graphql`, `js-bun-db`, `js-lint`, `js-mssql`, `js-run`, `rust`, `vue`. Тести й `__fixtures__/` переїхали разом із батьківським каталогом. 26 внутрішніх `'../utils/'`-імпортів у `js/`/`lint/` і 3 зовнішніх з `npm/scripts/auto-rules.mjs` оновлено на `'../lib/'` / `'../<rule>/lib/'`. JSDoc-шлях у `npm/rules/js-lint/lib/rebuild-oxlint-canonical.mjs` (приклад запуску) і JSDoc-натяк у `npm/rules/rust/lib/has-cargo-toml.mjs` теж оновлені.
- **`npm/scripts/utils/` розщеплено на `utils/` + `lib/`:** 19 файлів (`run-rule`, `run-rule-cli`, `run-standard-rule`, `run-standard-lint`, `run-lint-step`, `run-conftest-batch`, `discover-checkable-rules`, `discover-check-rules-from-cursor`, `list-rule-ids`, `load-cursor-config`, `read-n-cursor-config-lite`, `resolve-target-files`, `check-mdc-template-refs`, `check-reporter`, `gha-workflow`, `generated-markdown`, `inline-template-links`, `template`, `workspaces`) і 14 відповідних тестів + `__fixtures__/` переїхали у `npm/scripts/lib/`. У `npm/scripts/utils/` залишилися 9 справді generic-файлів (`ast-scan-utils`, `find-package-json-paths`, `pass`, `resolve-cmd`, `test-helpers`, `walk-cache`, `walkDir`, `with-lock`, `worktree-fingerprint`) + 4 їхні тести. `~220` імпортів `scripts/utils/<lib-file>` по всьому `npm/` оновлено на `scripts/lib/<lib-file>`; внутрішні lib→utils переходи (`check-reporter→pass`, `resolve-target-files→walkDir`, `run-conftest-batch→resolve-cmd`, `run-lint-step→resolve-cmd`, `run-rule-cli→walk-cache`, `run-standard-lint→with-lock`, `run-standard-rule→walk-cache,with-lock`) переписані на `'../utils/<file>'`; lib-тести з залежністю від `test-helpers` — на `'../../utils/test-helpers.mjs'`.
- **`scripts/utils/redis-imports.mjs` → `npm/rules/js-bun-redis/lib/redis-imports.mjs`** (+тест). Симетрично до `bunyan-imports`/`vue-forbidden-imports`: per-rule сканер живе в самому правилі, а не в спільних скриптах. Імпорт `scripts/utils/ast-scan-utils.mjs` зберігся (це справді generic helper).

### Added

- **Новий концерн `js-lint.utils_imports`** (`npm/rules/js-lint/js/utils_imports.mjs`): обходить кожен `utils/`-каталог у monorepo-воркспейсах і падає, якщо знаходить relative-імпорт з `..` у не-тестовому `.[cm]?[jt]sx?`-файлі. Дозволені лише same-dir (`./X`), bare-пакети та `node:*`; cross-rule, конфіги проєкту чи sibling-utils → fail з підказкою «перенеси у `lib/`». Тести (`*.test.mjs`) і будь-який `__fixtures__/` пропускаються — тестам легально треба `../X`. У `js-lint.mdc` під секцією «Структура спільних модулів: `utils/` vs `lib/`» додано абзац про автоматичну перевірку.

## [1.15.1] - 2026-05-24

### Fixed

- `adr.mdc`: виправлено stale template-лінк `./js/hooks/template/.gitignore.snippet` → `./js/templates/hooks/.gitignore.snippet` (після flat-layout міграції `js/<concern>.mjs` у комміті `6ecd84c` шлях не оновили, через що `inlineTemplateLinks` падав під час `bun start` із `file not found`).

## [1.15.0] - 2026-05-24

### Added

- **Нове правило `rust`** (`npm/rules/rust/`): канонічний скрипт `lint-rust` у `package.json` (`cargo fmt` → `cargo clippy --fix` → `cargo clippy ... -D warnings`), CI workflow `.github/workflows/lint-rust.yml` з `dtolnay/rust-toolchain@stable` (`components: rustfmt, clippy`) + `Swatinem/rust-cache@v2`, VSCode-розширення `rust-lang.rust-analyzer` + `tamasfe.even-better-toml`. Auto-trigger — наявність `Cargo.toml` (`hasCargoToml` fact у `auto-rules.mjs`). Три rego policy-пакети (`package_json`, `vscode_extensions`, `lint_rust_yml`) читають канон через `data.template.*` з drift-тестами.

### Changed

- **Правило `tauri` (1.1 → 1.2) звужено:** `rust-lang.rust-analyzer` більше не вимагається у `tauri.vscode_extensions` — перенесено в нове правило `rust`. Tauri-проєкт автоматично активує `rust` через `src-tauri/Cargo.toml`. Канон `tauri.mdc` оновлено: лишається лише `tauri-apps.tauri-vscode`.

## [1.14.0] - 2026-05-24

### Changed (BREAKING)

- **Flat концерн-лейаут:** кожен JS-концерн правила тепер один файл `npm/rules/<rule>/js/<concern>.mjs` замість вкладеного `js/<concern>/check.mjs`. Tests — у `js/tests/<concern>.test.mjs` (single) або `js/tests/<concern>/<name>.test.mjs` (multi+fixtures). Templates — у `js/templates/<concern>/`. Data (json/tsv) — у `js/data/<concern>/`. Helpers (cross-concern і concern-private) — у `<rule>/utils/<helper>.mjs` peer до `js/` (existing convention з `abie/utils/`).
- **`JsConcern.files` removed:** один файл на concern, поле більше не потрібне. `runRule` обчислює шлях як `<rule>/js/<concern.name>.mjs`; `resolveJsCheckPath` тепер `(bundledRulesDir, ruleId, concern)` без `fileName`.
- **`CHECK_FILENAME_RE` і `TEST_SUFFIX` removed:** discovery більше не використовує regex `check-*.mjs` — `listJsConcerns` фільтрує `*.mjs` без `.test.mjs` (підкаталоги скіпаються через `!isFile()`).
- **`scripts/sync-claude-config.mjs::ADR_GITIGNORE_SNIPPET_REL`** змінено: `rules/adr/js/hooks/template/.gitignore.snippet` → `rules/adr/js/templates/hooks/.gitignore.snippet`.
- **`scripts/utils/inline-template-links.mjs::TEMPLATE_SEGMENT_RE`** розширено з `/\/template\//` до `/\/templates?\//` — підтримує і `js/templates/` (нова конвенція), і `policy/<concern>/template/` (існуюча).

### Breaking (для зовнішніх інтеграторів)

- Каталог `npm/rules/<rule>/js/<concern>/check.mjs` тепер `npm/rules/<rule>/js/<concern>.mjs`. Tests → `js/tests/`, templates → `js/templates/`, data → `js/data/` (усе всередині `js/`); helpers → `<rule>/utils/<helper>.mjs` (peer до `js/`, як `abie/utils/`). Імпорти helpers з concern-файлів: `from '../utils/<helper>.mjs'`. Міграційний скрипт у git-історії — комміт `refactor(rules): flat layout js/<concern>.mjs (міграційний move)`.

### Notes

- Convention для helper-імен: namespace-префікс (`<rule>-` або `<concern>-`) робить колізії у плоскому `utils/` неможливими (як уже робить abie: `k8s-tree`, `kustomization-patches`; docker: `docker-mirror`; vue: `vue-forbidden-imports`).
- Шпаргалка імпорт-шляхів у `.cursor/rules/scripts.mdc` (1.10 → 1.11).
- `.cursor/rules/conftest.mdc` — алгоритм Rego-first переписаний під flat-layout.
- Канонічні `security.mdc` і `k8s.mdc` markdown-лінки на template-файли оновлені (`./js/templates/<concern>/`).

## [1.13.90] - 2026-05-24

### Added

- **`js-lint` 1.23 → 1.24 — конвенція `utils/` vs `lib/`:** додано секцію «Структура спільних модулів». `utils/` — низькорівневі generic helpers без домену (могли б жити окремим npm-пакетом); `lib/` — внутрішні модулі з доменним state/конфігом/side effects. Канонічні назви лише ці дві — не `shared/`, не `common/`. Дзеркало `.cursor/rules/n-js-lint.mdc` оновлено.

## [1.13.89] - 2026-05-23

### Changed

- **Stop-hook кличе `fix` замість deprecated `check`:** `scripts/claude-stop-hook.mjs` тепер спавнить `npx --no @nitra/cursor fix` — без deprecation-warning'а на кожен Stop event Claude Code.
- **`.claude-template/commands/n-check.md` видалено** (разом з локальним `.claude/commands/n-check.md`). Після CLI-перейменування `check` → `fix` slash-команда `/n-check` вказувала на застарілу команду. У `syncClaudeConfig` логіка sync `commands/*.md` залишилась; зараз темплейт порожній. Тест `створює settings.json + slash-команди` переписано на «без slash-команд, коли темплейт порожній».
- **JSDoc/docstring чистка:** `bin/n-cursor.js` (CLI usage header), `scripts/claude-stop-hook.mjs`, `scripts/sync-claude-config.mjs`, `rules/image-compress/js/package_setup/check.mjs` — згадки `npx @nitra/cursor check`, `/n-check`, `npm/scripts/check-*.mjs` оновлено на актуальну CLI (`fix`) і шляхи (`rules/<id>/fix.mjs`, `rules/<id>/js/<concern>/check.mjs`).
- **`.cursor/rules/conftest.mdc`** — алгоритм рішення / патерн Rego-authoritative / Workflow / Red-flags переписано під фактичну структуру `rules/<rule>/js/<concern>/check.mjs` + `rules/<rule>/policy/<name>/`. Прибрано згадки `npm/scripts/check-<rule>.mjs` та `npm/policy/<rule>/` (legacy шляхи); приклади `check abie`, `check ga` → `fix abie`, `fix ga`.
- **`docs/fix-cursor-skill.md`** — ASCII-діаграми, workflow-кроки та таблиця "Анатомія Skill-файлу" → `npx @nitra/cursor fix`; згадка `check-*.mjs скрипти` → `rules/<id>/fix.mjs правил`.

### Notes

- Споживачі: після оновлення вручну видалити `.claude/commands/n-check.md` (sync не вичищає orphan slash-команди з темплейту). Активна команда — `/n-fix` (зі скілу `n-fix`).
- В `.claude/settings.json` permission `Bash(npx @nitra/cursor check)` видалено як redundant — вайлдкард `Bash(npx @nitra/cursor *)` нижче вже покриває обидві команди.

## [1.13.88] - 2026-05-23

### Changed

- **`scripts/utils/with-lock.mjs` + тести:** локальна `sleep(ms)` через `new Promise(r => setTimeout(r, ms))` замінена на іменований імпорт `setTimeout as sleep` із `node:timers/promises`. Відповідає правилу `js-run` (без ручних `setTimeout`-промісів) — перевірка `npx @nitra/cursor fix js-run` стала зеленою.

## [1.13.87] - 2026-05-23

### Added

- **`scripts/utils/run-standard-lint.mjs`** — спільна точка входу для всіх `lint-<rule>` підкоманд, дзеркально до `runStandardRule` для `fix-<id>`. Виводить ключ локу зі шляху (`basename(dirname(lintDir))`) і прокидає `opts` у `withLock`. Місце для майбутніх крос-cutting розширень (телеметрія, env-toggle вимкнення локу, common preflight-логування) — патчиш одне місце, не 5 файлів.

### Changed

- **5 `rules/<rule>/lint/lint.mjs` (ga, rego, text, k8s, docker)** більше не імпортують `withLock` напряму — використовують `runStandardLint(import.meta.dirname, runLint<Foo>Steps)`. Ім'я правила в одному місці — у каталозі.
- **`.cursor/rules/scripts.mdc` 1.9 → 1.10:** канон патерну переписано на `runStandardLint` (а не прямий `withLock`); додано явну заборону імпортувати `withLock` у `rules/<rule>/lint/lint.mjs`. У кожному з 5 lint.mjs у top-JSDoc додано посилання «Канон патерну `lint-*` — `.cursor/rules/scripts.mdc`».

## [1.13.86] - 2026-05-23

### Fixed

- **`worktreeFingerprint` повертав `null` при untracked-файлах з не-ASCII іменами:** `git ls-files --others --exclude-standard` без `-z` повертає такі шляхи у C-escape виді (`"docs/adr/20260523-...кирилиця..."` з `\321\201`-послідовностями), і наступний `git hash-object <escaped>` не знаходить файл — увесь fingerprint падав у `null`, через що дедуп ніколи не спрацьовував у репах з кирилицею в untracked-іменах. Перехід на `-z` + `\0`-розбиття дає сирий байтовий шлях.

## [1.13.85] - 2026-05-23

### Changed

- **`withLock` розгорнуто на всі важкі CLI-команди:** додано серіалізацію + дедуп у `lint-rego`, `lint-text`, `lint-k8s`, `lint-docker` за тим самим зразком, що `lint-ga` (приватна `runLint<Foo>Steps()` + публічна `runLint<Foo>Cli = () => withLock('lint-<rule>', …)`).
- **`fix`-лок переїхав у `runStandardRule`:** замість зовнішньої обгортки навколо `runFixCommand` у `bin/n-cursor.js`, `withLock('fix-<ruleId>')` тепер всередині `scripts/utils/run-standard-rule.mjs`. Кожен `rules/<id>/fix.mjs` отримує лок «безкоштовно» через делегацію; `npx @nitra/cursor fix`, прямий `bun rules/<id>/fix.mjs` і `run(ctx)`-композиція проходять через одну точку. Per-rule гранулярність — різні правила паралельно, однакові серіалізуються.
- **`runLintRego` тепер async** (наслідок обгортки), додано окремий export `runLintRegoSteps(cwd)` для тестів — щоб не дедупувати проти попереднього прогону, який лишив cached result у `node_modules/.cache/n-cursor/lint-rego/`.
- **`.cursor/rules/scripts.mdc` 1.8 → 1.9:** додано канонічну секцію «Серіалізація важких CLI-команд: `withLock`» з патерном інтеграції, таблицею ключів і red flags.

### Fixed

- Тест `withLock integration > serializes parallel calls` падав через дедуп (обидва виклики бачили однаковий fingerprint і другий пропускав). Тест явно вимикає дедуп через `getFingerprint: () => null` — окремо тестується серіалізація, окремо дедуп.
- Тест `runLintTextCli` після обгортки повертає Promise; `withIsolatedPath` тепер `await fn()`.
- JSDoc-тип `withLock` opts розширено `getFingerprint?` (вже використовувався у runtime, але був відсутній у сигнатурі — TS видавав error 2353).

## [1.13.84] - 2026-05-23

### Changed

- **CLI команда `check` перейменована на `fix`** (узгоджено з ім'ям файла `rules/<id>/fix.mjs`). `npx @nitra/cursor fix [<rule>...]` — новий канонічний формат. Команда `check` залишається як deprecated alias з warning'ом — буде видалена в наступній major-версії.
- **CLI стає spawn-wrapper:** замість inline dynamic import у `runChecks`, `fix [<rule>...]` тепер просто послідовно спавнить `bun rules/<id>/fix.mjs` per rule (один шлях у коді — `fix.mjs` як єдина авторитативна точка входу). Discovery з `.cursor/rules/*.mdc` без аргументів зберігається.
- **`rules/<id>/fix.mjs` отримує standalone-режим:** блок `if (import.meta.main)` тепер делегує новій утиліті `runRuleCli`, яка читає `.n-cursor.json` (через light reader), перевіряє whitelist + друкує per-rule summary. `bun rules/<id>/fix.mjs` тепер повний еквівалент `npx @nitra/cursor fix <id>`.
- **Документація переписана:** всі `npx @nitra/cursor check <rule>` у `.mdc`, `.cursor/rules/`, `README.md`, skills, JSDoc pass-повідомленнях оновлено на `fix`. Один формат у документації; CLI alias `check` лишається тільки для backward compatibility.

### Added

- **`scripts/utils/run-rule-cli.mjs`** — standalone runner з config-loading + summary; використовується `fix.mjs::main` блоком.
- **`scripts/utils/read-n-cursor-config-lite.mjs`** — мінімальний read-only `.n-cursor.json` reader (без auto-detection / sync — це окрема справа CLI). API: `readNCursorConfigLite()`, `isRuleEnabled(config, ruleId)`. Open-by-default: якщо файл відсутній — правило вважається активним (для debug).

## [1.13.83] - 2026-05-23

### Changed

- **Per-rule `fix.mjs` entry-point + rename `fix/` → `js/`:** кожне з 30 правил тепер має `rules/<id>/fix.mjs` — 11-рядковий wrapper над новим `runStandardRule`. CLI більше не робить convention-based discovery на верхньому рівні — перебирає правила через `listRuleIds` і викликає `await import(rules/<id>/fix.mjs).run({ walkCache })`. Каталог `fix/<concern>/` перейменовано на `js/<concern>/` для усунення колізії з кореневим `fix.mjs` та узгодження з `policy/` (за технологією, не функцією).
- **Локальна логіка в `fix.mjs` заборонена** — розширення поведінки правил тільки через опції в `RuleContext` (зараз: `walkCache`; зарезервовано на майбутнє: `skipMdcRefs`, `skipApplies`, `onlyConcerns`). Простір варіацій повністю описано в `RuleContext` JSDoc; convention-drift виключений на рівні дизайну.
- **Shared `walkCache`** як module-level singleton у `scripts/utils/walk-cache.mjs` (`getOrCreateWalkCache` + `resetWalkCache` для тестів). CLI створює один cache на прогон і прокидає через ctx до всіх concerns.
- **Нові utils:** `scripts/utils/run-standard-rule.mjs`, `scripts/utils/list-rule-ids.mjs`, `scripts/utils/walk-cache.mjs`. Експорт `discoverOneRule(ruleDir, ruleId)` з `discover-checkable-rules.mjs` (виокремлено з існуючого `discoverCheckableRules` — DRY).
- **Нові тести:** `tests/fix-mjs-contract.test.mjs` (91 кейс — smoke на всі 30 правил), `tests/run-standard-rule.test.mjs`, `tests/list-rule-ids.test.mjs`, `tests/walk-cache.test.mjs`, `tests/discover-one-rule.test.mjs`. Існуючі тести оновлено в частині import-шляхів `/fix/<concern>` → `/js/<concern>` (логіка не змінювалась); видалено застарілий `discoverCheckableRules > legacy js/-структура ігнорується` — `js/` тепер canonical convention.

### Breaking

- **Для зовнішніх інтеграторів, що пишуть власні правила:** каталог `rules/<id>/fix/<concern>/check.mjs` перейменовано на `rules/<id>/js/<concern>/check.mjs`; додатково потрібен файл `rules/<id>/fix.mjs` з канонічним вмістом (див. будь-яке вбудоване правило для шаблону). CLI більше не запустить правило без `fix.mjs`.

### Notes

- Зворотна сумісність CLI: `npx @nitra/cursor fix` та `npx @nitra/cursor fix abie` працюють як раніше.
- Use-cases: `bun npm/rules/abie/fix.mjs` (debug); `bun npm/rules/${{ matrix.rule }}/fix.mjs` (CI per-rule jobs); IDE Run-button на `fix.mjs`.

## [1.13.82] - 2026-05-23

### Changed

- **`rules/test`: виняток для `*_test.rego` файлів — лишаються поряд із полісі (OPA/Conftest community-конвенція)**:
  - **Rego unit-тести (`*_test.rego`) лежать у тому самому каталозі, що й `<name>.rego`** — за загальноприйнятим патерном OPA/Conftest. `package <name>` (полісі) ↔ `package <name>_test` (тест) семантично зв'язані через `package`-декларації, а не локацію файлу; `conftest verify -p <dir>` рекурсивний, тож знаходить тест незалежно від місця, але спільнота тримає їх поруч (бачимо у OPA examples, Gatekeeper library, Datree, Styra DAS bundles). Це **легітимне відхилення** від внутрішньої JS-конвенції «`tests/` всюди» на користь OPA-ідіоми.
  - `rules/test/test.mdc` v1.1 — додано **окрему секцію про виняток** для Rego: «`*_test.rego` лишаються поряд із полісі, бо це загальноприйнятий OPA/Conftest community-патерн» (з прикладом структури `policy/<concern>/{<name>.rego, <name>_test.rego, target.json}`).
  - `rules/test/fix/location/check.mjs` — перевіряє **лише `*.test.mjs`**, `*_test.rego` свідомо виключено з область перевірки (зафіксовано у docstring).
  - Додано test-case у `rules/test/fix/location/tests/check.test.mjs`: `*_test.rego` поряд із полісі НЕ є порушенням.
- **Відкат переміщення `*_test.rego`**: 69 файлів, які раніше було помилково перенесено у `policy/<concern>/tests/<name>_test.rego`, повернуто у `policy/<concern>/<name>_test.rego` через `git mv`. Порожні `tests/` піддиректорії під `policy/` видалено.
- **`npx @nitra/cursor fix test`** охоплює лише JS-тести: «✅ Всі 77 файлів \*.test.mjs у каталозі tests/». Rego-тести продовжують перевірятись через `conftest verify` у правилі `rego`.

## [1.13.81] - 2026-05-23

### Fixed

- **`npm-module.package_structure`: carve-out для rule-name сегмента** у `classifyPublishedFileAsTest`. Раніше для шляху `rules/<X>/...` сегмент `<X>` піддавався TEST_DIR_NAMES-перевірці, що давало false positive на правилах із id, що збігається з test-style ім'ям (`test`, `tests`, `fixtures` тощо). Тепер сегмент індекс 1 (ім'я правила, коли індекс 0 — `rules`) пропускається; глибші сегменти (`rules/<r>/fix/<c>/tests/`) продовжують перевірятись.

### Added

- **Нове правило `test` (`npm/rules/test/`)** — програмний канон розміщення тестів (ADR `docs/adr/20260523-154806-...`):
  - `test.mdc` — конвенція «`*.test.mjs` живуть у `tests/` поряд із джерелом», з описом спецвипадків (root `tests/`, fixtures у `tests/__fixtures__/` і `tests/fixtures/`, test-helpers як shared-infra).
  - `fix/location/check.mjs` — обхід дерева `walkDir`'ом (зі стандартним skip-листом + `.n-cursor.json:ignore`); для кожного `*.test.mjs` басенейм батьківської директорії має бути `tests`, інакше fail з вказівкою куди перенести.
  - `fix/location/tests/check.test.mjs` — 6 тестів самого правила (eats own dogfood).
  - `auto.md` — auto-enable умова: «якщо у проекті є хоча б один файл `*.test.mjs`».
  - Додано `"test"` у `.n-cursor.json:rules` репо `@nitra/cursor`.
  - Додано `"ignore": [".claude/worktrees"]` у `.n-cursor.json` — щоб правило не звітувало про знімки в git worktrees.

### Changed

- **Тести переміщено з-поряд-із-файлом у `dir/tests/` піддиректорію** (ADR `docs/adr/20260523-154806-...`):
  - **73 sibling-тести** у `rules/...` і `scripts/...`: для кожного `dir/X.test.mjs` → `dir/tests/X.test.mjs` із автоматичним оновленням relative imports.
  - **3 integration-тести у `npm/tests/`** — без змін (вже відповідали конвенції).
  - **`npm/scripts/utils/__fixtures__/`** → `npm/scripts/utils/tests/__fixtures__/`.
  - **`npm/rules/nginx-default-tpl/fix/template/fixtures/`** → `.../tests/fixtures/`; посилання в `npm/tests/check-rule-fixtures.test.mjs` оновлено.
  - Ручні фіксапи 4 тестів із HERE/`..` path patterns (sync-setup-bun-deps-action, inline-template-links, rules/adr/fix/hooks, rules/abie/utils/enabled) — додано додатковий `..`, бо тести стали на рівень глибше.
  - `package.json#files` негативні globs (`!**/*.test.mjs`, `!**/__fixtures__/**`, `!**/fixtures/**`) працюють рекурсивно — без змін.
  - **77 тестів** проходять у новому layout (76 існуючих + 1 нового правила): `bun test` 843 pass / 2 fail (обидва — pre-existing `with-lock` issues, не пов'язані).
  - `npx @nitra/cursor fix test` → `✅ Всі 77 файлів *.test.mjs у каталозі tests/ (test.mdc)`.

## [1.13.79] - 2026-05-23

### Changed

- **Перенесення single-rule сканерів і canonical-конфігів з `npm/scripts/utils/` у `npm/rules/<rule>/fix/<sub>/`** (узгоджено з конвенцією `rules/ga/fix/workflows/`, `rules/nginx-default-tpl/fix/template/` тощо; ADR `docs/adr/20260523-114913-...`, який supersede `20260523-112217-...`):
  - **js-lint** (`rules/js-lint/fix/tooling/`): `knip-canonical.json`, `oxlint-canonical.json`, `oxlint-canonical-skeleton.json`, `oxlint-rules.tsv`, `rebuild-oxlint-canonical.mjs`. Константи `OXLINT_CANONICAL_JSON_PATH` / `KNIP_CANONICAL_JSON_PATH` у `check.mjs` стали локальними (без 4-річневих `..`).
  - **js-run** (`rules/js-run/fix/runtime/`): `bunyan-imports.mjs` (+test), `check-env-scan.mjs`, `conn-file-rules.mjs` (+test), `conn-imports-scan.mjs` (+test), `promise-settimeout-scan.mjs` (+test).
  - **docker** (`rules/docker/fix/lint/`): `docker-hadolint.mjs` (+test), `docker-mirror.mjs`. `rules/docker/lint/lint.mjs` тепер імпортує з `../fix/lint/docker-hadolint.mjs`.
  - **js-bun-db** (`rules/js-bun-db/fix/safety/`): `bun-sql-scan.mjs`.
  - **js-mssql** (`rules/js-mssql/fix/deps/`): `mssql-pool-scan.mjs`.
  - **changelog** (`rules/changelog/fix/consistency/`): `package-manifest.mjs` (+test).
  - **vue** (`rules/vue/fix/packages/`): `vue-forbidden-imports.mjs` (+test).
  - **graphql** (`rules/graphql/fix/tooling/`): `graphql-gql-scan.mjs`. Cross-rule імпорту немає: `extractVueScriptBlocks`, локалізована `contentForGqlScan` і власні `isGqlScanSourceFile` / `shouldSkipFileForGqlScan` (з власною source-regex і skip-list `.d.ts` / `auto-imports.d.ts` / `components.d.ts`) дубльовані всередині `graphql-gql-scan.mjs` — правила залишаються самодостатніми.
  - **`scripts/auto-rules.mjs`** оновлено: імпорти переадресовано на нові локації трьох сканерів (`bun-sql-scan`, `graphql-gql-scan`, `vue-forbidden-imports`).
  - **`.mdc`-документація** оновлена: `rules/js-lint/js-lint.mdc`, `rules/docker/docker.mdc`, `rules/vue/vue.mdc`, `.cursor/rules/n-js-lint.mdc`, `.cursor/rules/n-vue.mdc` — посилання на нові шляхи canonical-файлів і сканерів.

## [1.13.78] - 2026-05-23

### Changed

- **abie / k8s / hasura — актуалізація посилань на неіснуючий `check-abie.mjs`:** після реструктуризації `rules/abie/` на `fix/<concern>/check.mjs` (+ Rego-пакети у `policy/`) монолітного `check-abie.mjs` більше немає; застарілі посилання в активних `.mdc`/`.rego`/`.mjs` (поза історичним `CHANGELOG.md`) оновлено:
  - `npm/rules/abie/abie.mdc` — три згадки замінено: пер-документна перевірка HTTPRoute base hostnames → Rego `abie.http_route_base`; env-DNS-скан → `fix/env_dns/check.mjs`; cross-file/FS-логіку розбито за концернами (`hc_pairing/`, `ua_http_route/`, `ua_node_selector/`, `env_dns/`, `firebase_hosting/`) з поясненням, що `targetRef.name -hl` cross-check обчислюється з `hcp.metadata.name` у Rego.
  - `npm/rules/abie/policy/{http_route_base,base_deployment_preem,health_check_policy}/*.rego` — у шапках замінено `npm/scripts/check-abie.mjs` на актуальні джерела: cross-file gating через `policy/<pkg>/target.json` (glob), rule-level applies-гейт у `fix/applies/check.mjs`, FS-парність HCP↔Deployment у `fix/hc_pairing/check.mjs`. Прибрано згадки видалених JS-функцій (`validateAbieHcPolicy`, `deploymentDocumentHasAbieBasePreemNodeSelector`).
  - `npm/rules/k8s/k8s.mdc` — рядок про `targetRef -hl` для abie-проєктів вказує на Rego-пакет `abie.health_check_policy` + `abie.mdc` (замість `check-abie.mjs`).
  - `npm/rules/hasura/fix/internal_urls/check.mjs` — у JSDoc згадку `check-abie` замінено на нейтральне «abie-перевірки».

### Added

- **`with-lock`:** атомарний `mkdirSync`-лок + SHA-256 fingerprint-дедуп для важких команд; пілот — `lint-ga` автоматично серіалізує паралельні запуски та пропускає дублікати при незміненому робочому дереві (TTL 10 хв). Нові модулі: `scripts/utils/worktree-fingerprint.mjs`, `scripts/utils/with-lock.mjs`.

## [1.13.76] - 2026-05-22

### Added

- **`ga.workflow_common` — мінімальні версії marketplace actions у `uses:`:** `actions/checkout` >= major `v6` (`@v6` і `@v6.0.2` дозволені), `Infisical/secrets-action` >= `v1.0.16` (канон у `policy/workflow_common/template/uses-min-versions.snippet.json`; SHA-pin пропускається). `check-ga` передає template через `--data`. Bump `ga.mdc` `1.9` → `1.10`.

## [1.13.75] - 2026-05-22

### Removed

- **Скіли `abie-clean` та `abie-kustomize`** прибрано з пакета — abie-специфічні скіли перенесено до `@nitra/abie-docs` і перейменовано на `clean` / `kustomize` (запуск через `npx @nitra/abie-docs skill <id>`). Автоактивація цих скілів за правилом `abie` більше не діє. Зачеплено: [auto-skills.mjs](scripts/auto-skills.mjs), [auto-skills.test.mjs](scripts/auto-skills.test.mjs), `skills/abie-clean/`, `skills/abie-kustomize/`.
- **Скіл `efes-create-env`** прибрано з пакета — efes-специфічний скіл перенесено до `@nitra/efes-docs` і перейменовано на `create-env` (запуск через `npx @nitra/efes-docs skill create-env`). Автоактивація за правилом `efes` більше не діє. Зачеплено: [auto-skills.test.mjs](scripts/auto-skills.test.mjs), `skills/efes-create-env/`.

## [1.13.73] - 2026-05-21

### Fixed

- **Збір workspace-коренів** — `getMonorepoPackageRootDirs` / `getMonorepoProjectRootDirs` більше не трактують `package.json` у `node_modules/`, `.git/`, `.venv/`, `venv/` як воркспейси (glob ignore + `isIgnoredWorkspaceRoot`). Усуває хибні `check changelog` на транзитивних залежностях (наприклад `node-gyp/gyp`).

## [1.13.72] - 2026-05-21

### Changed

- **CLI скілів спрощено** — лише `npx @nitra/cursor skill list`, `skill <id> ["task"]` (промпт на stdout), `skill cursor <id> ["task"]`, `skill claude <id> ["task"]`. Прибрано `skill prompt`, bins `n-skills` / `n-claude`, підкоманду `claude` у `n-cursor`. Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs).

## [1.13.71] - 2026-05-21

### Added

- **Claude-first UX для скілів** — `npx @nitra/cursor claude taze "task"` і bin **`n-claude`** (замінено спрощеним `skill` у 1.13.72).

## [1.13.70] - 2026-05-21

### Added

- **CLI скілів без синку в проєкт** — `npx @nitra/cursor skill list|prompt|claude|cursor <id> "task"` і bin **`n-skills`** (`npx -p @nitra/cursor n-skills …`). Читає `skills/<id>/SKILL.md` з установленого пакета, збирає промпт із CWD (`package.json`, `tsconfig.json`, `.n-cursor.json`) і виводить на stdout або делегує в `claude -p` / `cursor-agent -p`. Id скілу — каталог у пакеті (`lint`, `fix`, …) або з префіксом `n-` (`n-lint` → `lint`). Зачеплено: [skills-cli.mjs](scripts/skills-cli.mjs), [n-skills.js](bin/n-skills.js), [n-cursor.js](bin/n-cursor.js).

## [1.13.69] - 2026-05-21

### Changed

- **CLI `check` без аргументів** більше не парсить `AGENTS.md` — список правил для прогону будується з **`*.mdc` у `.cursor/rules/`** (той самий дисковий індекс, що для `AGENTS.md` / `CLAUDE.md`): `n-bun.mdc` → `check bun`, ручні `conftest.mdc` тощо — за наявності programmatic check у пакеті. Явний `check bun ga` без змін. Зачеплено: [discover-check-rules-from-cursor.mjs](scripts/utils/discover-check-rules-from-cursor.mjs), [n-cursor.js](bin/n-cursor.js), [fix/SKILL.md](skills/fix/SKILL.md).

## [1.13.68] - 2026-05-21

### Changed

- ADR-хук **`normalize-decisions.sh`**: нормалізація тепер активніше повторно використовує наявні ADR замість створення нових файлів. У промпт додано принцип вибору операції — перш ніж `rewrite` (новий файл), агент звіряє тему драфта з clean-списком і рештою драфтів батча; якщо рішення по суті вже зафіксоване і драфт лише уточнює/доповнює/виправляє його — обирає `merge-into`. Правило `merge-into` тепер явно дозволяє `target` двох видів: clean-файл зі списку або `<slug>.md` `rewrite`-операції цього ж батча; суперечливе обмеження «не вигадуй target поза clean-списком» узгоджено з цим. Зачеплено: [normalize-decisions.sh](.claude-template/hooks/normalize-decisions.sh).

### Fixed

- ADR-хук **`normalize-decisions.sh`**: `merge-into` більше не падає в `skip … target missing`, коли драфт треба влити в clean-ADR, який створює `rewrite` того самого батча, або в наявний clean-ADR, на який LLM послався голим `<slug>.md` без timestamp-префікса. Операції тепер застосовуються двома впорядкованими групами (спершу `delete`/`rewrite`, потім `merge-into`), а `target` визначається за трьома кроками: точна назва → slug-мапа rewrite-ів цього батча → єдиний наявний clean-файл із суфіксом `-<slug>.md`. Цикл застосування переведено з pipe на читання з файлу — лічильники `applied`/`skipped` виживають і потрапляють у фінальний рядок логу `done (applied N, skipped M)`. Зачеплено: [normalize-decisions.sh](.claude-template/hooks/normalize-decisions.sh).

## [1.13.67] - 2026-05-21

### Changed

- Правило **`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`).
- Правило **`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).

### Fixed

- Правило **`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 і пропуску кореня монорепо).

## [1.13.66] - 2026-05-20

### Changed

- `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`.

## [1.13.65] - 2026-05-20

### Changed

- Скіл **`n-abie-clean`**: розділ перекладів — апострофи та спецсимволи в англійських рядках (`tr`); після очистки — обовʼязкова локальна перевірка `bun vite build` поряд із `check abie`.

## [1.13.64] - 2026-05-20

### Added

- **`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.

### Changed

- Правило **`adr`**: посилання на канон `.gitignore.snippet` і згадка автоматичного дописування під час sync.
- **`.gitignore.snippet`**: додано базові рядки `node_modules/`, `dist/`, `*.secret` (як у кореневому `.gitignore` пакета).

## [1.13.63] - 2026-05-20

### Fixed

- **`check changelog`**: новий local-only воркспейс (маніфест відсутній на merge-base з `dev`/`main`, напр. `demo/` на `main`) більше не вимагає штучного bump — достатньо початкової `version` і запису в `CHANGELOG.md` (раніше `Vbase === ∅` помилково трактувалось як «version не підвищено»).
- **`check changelog`**: на гілці **`main`** база порівняння — **`origin/main`** (або `HEAD~1` без remote), не `dev`; коли `origin/main` збігається з `HEAD`, diff порожній (не fallback на `HEAD~1`); feature-гілки — `merge-base` з `dev`, інакше з `main` (репо без `dev`).

### Changed

- Правило **`changelog`** ([changelog.mdc](rules/changelog/changelog.mdc) `2.5`): блок **STOP** перенесено на початок (тригер шляхів, інверсія, три кроки до завершення відповіді) — щоб агент не пропускав bump після правок у `npm/skills/` тощо, коли чеклист губився внизу довгого alwaysApply-правила.
- **`.cursor/rules/scripts.mdc`**: секція «Завершення задачі після правок у пакетному workspace» — cross-STOP з **n-changelog** (останні кроки сесії перед відповіддю).
- **`hk.pkl`**: pre-commit крок **`npm-changelog`** (`glob: npm/**`, `bun ./npm/bin/n-cursor.js check changelog`) — програмний стоп-кран при commit, якщо агент забув bump.

## [1.13.62] - 2026-05-20

### Changed

- Скіл **`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 лише щоб зеленіти лінт; без відповіді користувача — рефакторинг або червоний лінт з поясненням, без змін конфігу.

## [1.13.61] - 2026-05-20

### Fixed

- Скіл **`n-publish-telegram`** ([SKILL.md](skills/publish-telegram/SKILL.md)): усунено суперечність про хештеги. Шаблон і приклад ставили тег **першим рядком** поста й використовували **один** тег (`#dev`), але секція «Правила» вимагала «**2–4** хештеги **в кінці**» — агент не мав однозначного орієнтиру. Канон узгоджено за фактичною практикою: **рівно 1 хештег першим рядком поста**. Зачеплено два прозові рядки — вступ до переліку тегів («хештеги в кінці поста» → «один хештег першим рядком поста») і пункт «Правила» («Теги: 2–4 хештеги в кінці» → «Тег: рівно 1 хештег першим рядком поста»); шаблон і приклад не змінювалися — вони вже відповідали канону.

## [1.13.60] - 2026-05-20

### Fixed

- Генерація **`AGENTS.md`** і **`CLAUDE.md`** при `npx @nitra/cursor`: Mustache-секції більше не вставляють порожній рядок між кожним пунктом списку (MD012), а фінальний markdown згортає зайві `\n\n\n` на стиках секцій — не потрібен окремий `lint-text` лише заради зачистки згенерованих файлів. Зачеплено: [generated-markdown.mjs](scripts/utils/generated-markdown.mjs) (`expandMustacheSection` — trim inner + `join('\n')`, `collapseMultipleBlankLines`, `formatGeneratedMarkdownLines`), [n-cursor.js](bin/n-cursor.js) (імпорт утиліт замість inline-логіки), [generated-markdown.test.mjs](scripts/utils/generated-markdown.test.mjs).

## [1.13.59] - 2026-05-20

### Added

- Нове `alwaysApply`-правило **`feedback`** ([feedback.mdc](rules/feedback/feedback.mdc)) — ефемерний канал зворотного звʼязку до пакета `@nitra/cursor`. Виконуючи будь-який скіл пакета (`n-lint`, `n-fix`, `n-taze`, `n-adr-normalize`, `n-llm-patch`, `n-publish-telegram`, `mdc-check`), агент проходить крізь `.cursor/rules/`, `SKILL.md` і `npx @nitra/cursor fix` — і бачить «тертя»: неоднозначні інструкції, відсутні `check-*.mjs`, false positive, порушення без автофіксу, повторювані патерни. Правило вимагає наприкінці скілу, **після** основного резюме, додати у відповідь чату секцію `## 🔧 Покращення @nitra/cursor` з пунктами за схемою `target` (`rule`/`skill`/`check`) · `id` · `kind` (`ambiguous-doc`/`missing-check`/`false-positive`/`no-autofix`/`recurring-pattern`) · `evidence` · `suggestion`. Резюме **навмисно ефемерне** — живе лише у відповіді чату: правило забороняє запис файлів/чернеток, GitHub issue/PR і редагування самого пакета; розробник, читаючи відповідь, сам вирішує, чи переносити пункт у пакет. Якщо тертя не було — секція повністю пропускається. Правило чисто документаційне (як `ci4`), `check-*.mjs` не має, бо поведінка агента програмно не верифікується. Зачеплено: новий каталог [rules/feedback/](rules/feedback/) з `feedback.mdc` (`version: '1.0'`), додано `"feedback"` у `rules` кореневого `.n-cursor.json` — після синку правило копіюється як `.cursor/rules/n-feedback.mdc` і потрапляє в `AGENTS.md`.
- `check security`: новий concern **`security.sample_secret`** — placeholder фейкових credential-значень у прикладних файлах має бути `sample-secret`, а не bare `secret`. Причина: `sample-secret` містить підрядок `sample` із вшитого списку `DefaultFalsePositives` TruffleHog і відсіюється сканером гарантовано та незалежно від версії; bare `secret` наразі ігнорується лише тому, що випадково присутнє у словнику `fp_words.txt` — крихка поведінка, що залежить від версії інструмента. [check.mjs](rules/security/fix/sample_secret/check.mjs) обходить дерево, відбирає прикладні файли (basename із суфіксом `.example`/`.sample`/`.template`/`.dist` чи infix `.example.`/`.sample.`/`.template.`, а також усе всередині каталогів `fixtures`/`fixture`/`__fixtures__`) і порядково шукає `secret` у позиції значення — одразу після `=`, `:` або `=>` з опційними лапками; імена ключів (`client_secret`, `JWT_SECRET`) не чіпаються, бо матч прив'язаний до значення. Решта файлів не сканується — там `secret` майже завжди частина реального коду. Скан текстовий (regex, не AST/Rego): прикладні файли — різнорідні конфіги (`.env`, YAML, JSON, TOML, plain `.dist`) без єдиного AST, а відбір файлів потребує обходу дерева. Зачеплено: [check.mjs](rules/security/fix/sample_secret/check.mjs) і [check.test.mjs](rules/security/fix/sample_secret/check.test.mjs) (новий concern + 9 тестів), [security.mdc](rules/security/security.mdc) (нова секція «Placeholder для секретів — `sample-secret`» та секція «Перевірка»). Bump `security.mdc` `2.0` → `2.1`.

## [1.13.57] - 2026-05-19

### Changed

- `check js-bun-db`: новий **hard fail** на `sql.unsafe(template_literal_with_interpolation)` — будь-який виклик з template-літералом, що містить `${...}`-інтерполяцію, тепер падає **навіть з маркером** `// allow-unsafe`. Причина: шаблонна підстановка `${name}` у `sql.unsafe`-рядок не екранує identifier'ів (reserved words, спецсимволи, пробіли в імені) і не біндить значень; такий код виглядає звично через знайому tagged-template-форму, але насправді робить просту строкову конкатенацію без жодних гарантій. Канон — зібрати `text` окремо: identifiers через `@scaleleap/pg-format` `format('%I', name)`, values як позиційні `$N` + другий аргумент `sql.unsafe(text, [params])`. Раніше дозволений приклад `sql.unsafe(\\\`CREATE TABLE \\\${TABLE} (id int)\\\`)`з marker'ом тепер fail — переписати через`format('CREATE TABLE %I (id int)', TABLE)`. Не зачепило:`sql.unsafe('SELECT 1')`(статичний рядок),`sql.unsafe(\\\`SELECT 1\\\`)`(template без інтерполяції),`sql.unsafe(text, [params])`зі змінною`text`. Зачеплено: [bun-sql-scan.mjs](scripts/utils/bun-sql-scan.mjs) (новий експорт`findBunSqlUnsafeWithInterpolatedTemplateInText`, що флагає лише`obj.unsafe(TemplateLiteral)`з`expressions.length > 0`), [check.mjs](rules/js-bun-db/fix/safety/check.mjs) (новий лічильник`unsafeTemplateInterp`+ окреме повідомлення з порадою на`@scaleleap/pg-format`), [check.test.mjs](rules/js-bun-db/fix/safety/check.test.mjs) (попередній DDL-тест переписано на безпечний`format('%I', ...)`-варіант, додано **негативний** тест на template-interp + marker і **позитивний** тест на статичний template без інтерполяції), [js-bun-db.mdc](rules/js-bun-db/js-bun-db.mdc) (нова підсекція «sql.unsafe з template-літералом і ${...}-інтерполяцією — заборонено навіть з маркером» зі зразками поганого/гарного коду; основний приклад DDL у секції unsafe-allowlist переписано на`format`+ готовий`text`). Bump`js-bun-db.mdc` `1.10`→`1.11`.

## [1.13.56] - 2026-05-19

### Changed

- `check js-bun-db`: пакет **`pg`** більше не повністю заборонений — додано виключення для **PostgreSQL LISTEN/NOTIFY**, який Bun SQL поки не реалізує. Причина: dev-теми з notifications (черги нотифікацій, інвалідація кешу через `pg_notify`, бот-консьюмери на каналі) досі мають законну потребу у клієнті `pg`, а попереднє правило flat-out забороняло це навіть у файлах, що буквально нічого не роблять, окрім виклику `client.query` з рядком `LISTEN ...` плюс listener `client.on` на події `notification`. Тепер `dependencies.pg` дозволено, **якщо** AST-сканер знаходить у проєкті хоч один сигнал LISTEN/NOTIFY: метод `query` / `queryArray` / `queryStream` зі string- або template-літералом, що починається з `LISTEN`, `UNLISTEN` або `NOTIFY` (case-insensitive), або метод `on` із першим аргументом-рядком `notification`, або tagged template з тегом `sql` і першим quasi, що починається з тих самих ключових слів. Якщо жодного — `fail` з посиланням на нову секцію .mdc. Додатково — **per-file**: будь-який файл з `import 'pg'` (або `require('pg')`) повинен сам містити LISTEN/NOTIFY; звичайні `SELECT`/`INSERT`/`UPDATE` через `pg` лишаються забороненими (переписати на Bun SQL і лишити LISTEN/NOTIFY в окремому модулі). Заборона `pg-format` і `mysql2` не змінилася. Зачеплено: [bun-sql-scan.mjs](scripts/utils/bun-sql-scan.mjs) (нові експорти `textHasPgLibImport`, `findPgLibImportInText`, `findPgListenNotifyUsageInText` + AST-хелпери для розпізнавання pg-style LISTEN/NOTIFY-запитів і `notification`-listener'ів), [check.mjs](rules/js-bun-db/fix/safety/check.mjs) (нова функція `checkPgDependencyAndUsage`, що пробігає по всіх `package.json` і per-file pg-imports; перевірку `pg` повністю переведено з Rego в JS, бо Rego не бачить JS-коду), [package.json.deny.json](rules/js-bun-db/policy/package_json/template/package.json.deny.json) (прибрано `pg`, лишилися `pg-format`/`mysql2`), [package_json_test.rego](rules/js-bun-db/policy/package_json/package_json_test.rego) (`test_deny_pg` → `test_allow_pg_in_dependencies` + новий `test_deny_pg_format`), [check.test.mjs](rules/js-bun-db/fix/safety/check.test.mjs) (5 нових сценаріїв: успіх з LISTEN, успіх з notification-listener, помилка `pg` без LISTEN/NOTIFY, помилка змішаних файлів — один із LISTEN, інший зі звичайними запитами, успіх з `NOTIFY` як виправдання). `.mdc` отримало нову секцію «pg: виключення для LISTEN/NOTIFY» з прикладом окремого `pg-listen.ts`-модуля і явним переліком сигналів, які зважує сканер. Bump `js-bun-db.mdc` `1.9` → `1.10`.

## [1.13.55] - 2026-05-19

### Changed

- `check js-bun-db`: правило [js-bun-db.mdc](rules/js-bun-db/js-bun-db.mdc) **пом'якшено** для випадків, де Bun SQL принципово не може допомогти — **динамічних SQL identifiers** (назви schema/table/column/index/role/database) і whitelist-фрагментів типу `ASC`/`DESC`. Раніше для них рекомендувалось будувати рядок шаблонною підстановкою у `sql.unsafe`, але інтерполяція identifier'у в template literal не робить escape (reserved words, спецсимволи) — це слабкий захист. Тепер канон — окремий пакет **`@scaleleap/pg-format`** (scoped форк, не unscoped `pg-format`): виклик типу `format('SELECT * FROM %I', name)` повертає коректно екранований PostgreSQL identifier, далі рядок іде у `sql.unsafe(query, [bindParams])` з обов'язковим маркером `// allow-unsafe: <причина>`. Значення (user input, фільтри, INSERT/UPDATE) — **завжди** через Bun parameters (tagged template або `$N` + `sql.unsafe(text, values)`); `%L` для значень лишається забороненим, як і власні шими `format`/`pgFormat`/`quoteIdent` тощо. Unscoped `pg-format` лишається у [deny-списку](rules/js-bun-db/policy/package_json/template/package.json.deny.json) — виключення стосується **тільки** scoped `@scaleleap/pg-format`. Зачеплено: вступ секції «Заміна на Bun native SQL» (зафіксовано виключення), рядок таблиці ідіом для `%I` (тепер через `@scaleleap/pg-format`, не через `sql.unsafe` з шаблонним рядком), нова секція «Динамічна SQL-структура: @scaleleap/pg-format для identifiers» з прикладами (динамічний `ORDER BY` зі whitelist, multi-row `INSERT` через `VALUES %L`, dynamic `WHERE` через ручні `$N`) і коротка таблиця рішень. AST-сканер [bun-sql-scan.mjs](scripts/utils/bun-sql-scan.mjs) не зачеплений — він знаходить лише **визначення** функцій-шимів (`format` з `%L`/`%I`/`%s` у тілі), а імпорт `format` із `@scaleleap/pg-format` як зовнішня бібліотека не флагається. Bump `js-bun-db.mdc` `1.8` → `1.9`.

## [1.13.54] - 2026-05-19

### Changed

- `check k8s` / `lint-k8s`: правила під `npm/rules/k8s/` спрощено — за каноном `k8s.mdc` тримаємо лише `.yaml`, тож **rego-цілі** і **rego-вирази** очищено від `.yml`. Зачеплено: глоби `walkGlob` у `npm/rules/k8s/policy/{manifest,base_manifest,gateway,hpa_pdb}/target.json` — лише `**/*.yaml`; у [base_kustomization.rego](rules/k8s/policy/base_kustomization/base_kustomization.rego) `is_hpa_or_pdb_filename` більше не містить `hpa.yml` / `pdb.yml`; тест `test_deny_hpa_yml_in_subdir` → `test_deny_hpa_yaml_in_subdir`. **Safety-net** у [check.mjs](rules/k8s/fix/manifests/check.mjs) **збережено**: `findK8sYamlFiles` та `checkK8sYamlFile` все ще пропускають `.yml` далі, але одразу падають з повідомленням `розширення .yml — перейменуй на .yaml (див. k8s.mdc)` — щоб випадково створений `*.yml` під `k8s/` не залишився непоміченим (автоматичне перейменування — окрема ручна команда `npx @nitra/cursor rename-yaml-extensions`, яка з `check k8s` не викликається). Згадки про `.github/workflows/*.yml` у JSDoc лишилися (це чуже правило `ga.mdc`, де канон — `.yml`). Bump `k8s.mdc` `1.40` → `1.41`.

## [1.13.53] - 2026-05-19

### Changed

- `check k8s`: **NetworkPolicy переїхав з `components/` у `base/`**. Раніше канон вимагав `…/k8s/<pkg>/components/networkpolicy.yaml` (Kustomize Component, sibling до `base/`), а локальний `networkpolicy.yaml` у base був забороненим (file-existence error) — через що **dev-середовище** (рендер лише з base без overlay → без components) **не отримувало жодних мережевих обмежень** і pod'и були відкриті для будь-якого трафіку. Тепер NP лежить у `base/networkpolicy.yaml` поруч з workload-маніфестом і підключений через `base/kustomization.yaml` `resources:` — обмеження діють і на dev, і на всіх overlays через звичайний `resources: [- ../base]`. Канон `components/`: лише `hpa.yaml` + `pdb.yaml` (HPA/PDB лишаються env-залежними й підключаються тільки прод-overlays). У не-base overlays `networkpolicy.yaml` поруч з workload — опційний overlay-specific override. Зачеплено: [npm/rules/k8s/k8s.mdc](rules/k8s/k8s.mdc) (нова секція «NetworkPolicy у `base/`», оновлені приклади `components/kustomization.yaml` без NP і новий приклад `base/networkpolicy.yaml`), [npm/rules/k8s/fix/manifests/check.mjs](rules/k8s/fix/manifests/check.mjs) (видалено `failIfBaseLayerHasLocalNetworkPolicy` і `validateComponentsNetworkPolicyFile`; `validateNetworkPoliciesForK8sWorkloads` і `ensureNetworkPoliciesForWorkloadsInDir` тепер завжди шукають `networkpolicy.yaml` у `dir`, autofix додає його у `base/kustomization.yaml` `resources:`; `validateComponentsKustomizationManifest` більше не вимагає NP у resources), [npm/rules/k8s/policy/base_kustomization/base_kustomization.rego](rules/k8s/policy/base_kustomization/base_kustomization.rego) (deny прибирає `networkpolicy.yaml` зі списку заборонених у base resources — лишаються тільки HPA/PDB), [npm/rules/k8s/lint/lint.mjs](rules/k8s/lint/lint.mjs) (оновлено JSDoc про C-0260: NP тепер у base і kustomize-збірка нормалізує namespace природньо). Bump `k8s.mdc` `1.39` → `1.40`.

## [1.13.52] - 2026-05-19

### Added

- `check bun`: **зворотній інваріант** для `lint-<id>`-скриптів. Раніше `checkCursorRuleScripts` ([npm/rules/bun/fix/layout/check.mjs](rules/bun/fix/layout/check.mjs)) перевіряв лише пряму імплікацію — «правило в `.n-cursor.json:rules` → скрипт у `package.json`». Тепер також fail-имо, коли правило **відсутнє** в `rules` (або явно перенесене в **`disable-rules`**), але в кореневому `package.json` залишилися: (а) сам скрипт `lint-<id>`, або (б) виклик `bun run lint-<id>` у агрегованому `scripts.lint`. Причина: `n-cursor lint-<id>` запускається напряму й **ігнорує** `.n-cursor.json`, тож `bun run lint` падає на вимкненому правилі (як було з `disable-rules: ["k8s"]` у cursor-репо, де `lint-k8s` обходив template-сорці власного правила). Покриті скрипти і їхні правила-власники: `lint-docker` ← `docker`, `lint-k8s` ← `k8s`, `lint-image` ← `image-avif`/`image-compress` (multi-owner — скрипт лишається дозволеним, поки активний **хоч один** власник). Розпізнавання згадки `bun run lint-<id>` у chain'і — через токен-границі (regex `\\bbun run <script>\\b`), щоб не матчити префікси (`lint-k8s-foo` ≠ `lint-k8s`). Bump `bun.mdc` `1.8` → `1.9`.

## [1.13.51] - 2026-05-19

### Fixed

- `lint-k8s`: `kubescape scan -` (stdin), доданий у 1.13.49 і збережений у 1.13.50, **не працює в kubescape v4.x** — `-` трактується як шлях до файлу й сканер виходить з `no resources found to scan` (fatal), тож `bun run lint` падав на `lint-k8s` навіть на чистих manifest-файлах. Прапорця `--input`/`--stdin` у CLI також немає. Тепер `runKubescapeManifest` пише зібраний kustomize-маніфест у тимчасовий файл під `os.tmpdir()` (через `fs.mkdtempSync`) і запускає **`kubescape scan <tmp-file>`**; тимчасова директорія прибирається у `finally`. Bump `k8s.mdc` `1.38` → `1.39`.

## [1.13.50] - 2026-05-19

### Changed

- `lint-k8s`: kubescape тепер збирає kustomize-маніфест через **вшиту в kubectl підкоманду** — `kubectl kustomize <dir> | kubescape scan -` (замість окремого бінарника `kustomize build <dir>`, доданого в 1.13.49). Причина: на машинах без окремого `kustomize` lint-k8s падав з `kustomize не знайдено в PATH`, тоді як `kubectl` — штатний інструмент з вшитим Kustomize (рендеринг локальний, доступ до кластера не потрібен). PATH-залежність зведена з пари `kubectl+kustomize` до одного `kubectl`; крок `Install kustomize` у GHA-шаблоні `lint-k8s.yml` прибрано (на github-hosted runner'ах kubectl уже доступний). Bump `k8s.mdc` `1.37` → `1.38`.

## [1.13.49] - 2026-05-19

### Changed

- `lint-k8s`: kubescape тепер сканує **зібраний kustomize-маніфест** через stdin (`kustomize build <dir> | kubescape scan -`) для кожного dir-у з `kustomization.yaml` під `…/k8s` (Kustomize Components — `kind: Component` — пропускаються, вони не білдяться окремо). Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling `components/networkpolicy.yaml` без `metadata.namespace`: сирий dir-скан не виконував kustomize, бачив порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, через що `podSelector` не матчився. Якщо `kustomization.yaml` під коренем `…/k8s` немає — fallback на старий dir-скан. Нова PATH-залежність — `kustomize` (додано крок у GHA-шаблоні `lint-k8s.yml`). Bump `k8s.mdc` `1.36` → `1.37`.

## [1.13.48] - 2026-05-19

### Changed

- `k8s.network_policy`: канонічний egress NetworkPolicy більше **не дозволяє** `to.namespaceSelector: {}` без `ports:` (catch-all). У шаблоні `networkpolicy.snippet.yaml`, генераторі `buildNetworkPolicyYaml` і rego-policy `network_policy.rego` тепер in-cluster rule має явний список TCP-портів: `80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318`. Додатково: `fix k8s` під час прогону знаходить існуючі `networkpolicy.yaml` з legacy catch-all egress і **перезаписує** їх через `buildNetworkPolicyYaml` (повний rebuild за `metadata.name` + `app`-міткою). JS-валідатор `networkPolicyManifestViolations` не змінюється (порти enforce-ить rego). Bump `k8s.mdc` `1.35` → `1.36`. Спец: [docs/superpowers/specs/2026-05-19-networkpolicy-egress-explicit-ports-design.md](../../docs/superpowers/specs/2026-05-19-networkpolicy-egress-explicit-ports-design.md).

## [1.13.47] - 2026-05-19

### Fixed

- `js-bun-db`, `js-bun-redis` rules: додано markdown-посилання на `policy/package_json/template/package.json.deny.json` у канонічних `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) падав, бо канонічні `.mdc` не містили `[package.json.deny.json](./policy/package_json/template/package.json.deny.json)`. Bump rule versions: `js-bun-db.mdc` `1.7` → `1.8`, `js-bun-redis.mdc` `1.1` → `1.2`.

## [1.13.46] - 2026-05-19

### Changed

- `image-avif` rule: двопрохідний rewrite у `check image-avif` — спочатку pre-scan `.vue`/`.html` на raster-посилання (`VUE_RASTER_IMPORT_RE` + `VUE_RASTER_STATIC_SRC_RE`), і лише якщо є хоча б одне — запускається `npx @nitra/minify-image --avif`, rewrite та cleanup AVIF-сиріт. Якщо raster-посилань нема — вихід `0` без жодного side-effect. Bump `image-avif.mdc` `1.3` → `1.4`.

## [1.13.45] - 2026-05-19

### Fixed

- `inlineTemplateLinks` tests: оновлено очікувані рядки для фікстури `__fixtures__/inline-template/fix/foo/template/snippet.json` (перейшла на форматований варіант `{ "key": "val" }` ще в 1.13.38) та для інтеграційними testing тесту `security.mdc` (snippet `package.json` тепер multi-line після lint-проходу). Без зміни рантайм-логіки.
- `check-ga` тестова фікстура `setupCanonicalGaProject`: додано крок `Install conftest` у `.github/workflows/lint-ga.yml`, без якого `ga.lint_ga` rego-полісі забороняє workflow і `check()` повертав 1.

## [1.13.44] - 2026-05-18

### Added

- `abie` rule: новий policy-концерн `abie.package_json_docs` — у кореневому `package.json` `devDependencies` має містити `@nitra/abie-docs` (presence-only, версію не фіксуємо). Реалізація: `npm/rules/abie/policy/package_json_docs/` (target.json + .rego + \_test.rego). Bump `abie.mdc` `1.20` → `1.21`.
- `efes` rule: перший policy-концерн `efes.package_json_docs` — у кореневому `package.json` `devDependencies` має містити `@nitra/efes-docs` (узгоджено з `graphql.mdc`, де схема береться з `node_modules/@nitra/efes-docs/schema/maya.graphql`). Реалізація: `npm/rules/efes/policy/package_json_docs/`. Bump `efes.mdc` `1.0` → `1.1`.

## [1.13.43] - 2026-05-18

### Removed

- `npm/CLAUDE.md` як path-scoped нагадування для роботи в `npm/` повністю прибрано — фінальне завершення міграції з `1.13.42` (де вже прибрали `syncNpmClaudeMd` + Rego-first STOP перенесли у `scripts.mdc`): видалено сам `npm/CLAUDE.md`, темплейт `npm/.claude-template/npm-CLAUDE.md`, останні згадки в `bin/n-cursor.js` (повідомлення про `npm/CLAUDE.md` після sync; JSDoc) і опис у `schemas/n-cursor.json` `claude-config`. Реліз-правила (PR-bump + CHANGELOG) і так живуть у `n-changelog.mdc`/`n-npm-module.mdc` (alwaysApply).

## [1.13.42] - 2026-05-18

### Added

- `efes` rule: новий (поки що порожній) пакет правил для проєктів **github.com/efes-cloud/\***. Автодетект у `auto-rules.mjs` через `EFES_REPOSITORY_URL_MARKER` (`https://github.com/efes-cloud/`) — аналогічно до `abie`. Додано `npm/rules/efes/efes.mdc` + `auto.md`, прописано порядок в `AUTO_RULE_ORDER` і покрито тестами в `auto-rules.test.mjs`.
- `efes-create-env` skill: повʼязано з правилом `efes` через `skills/efes-create-env/auto.md` (`[efes]`) — активується автоматично, коли репозиторій відповідає efes-маркеру. Тести в `auto-skills.test.mjs` фіксують позитивний і негативний випадки.

## [1.13.41] - 2026-05-18

### Fixed

- `k8s` rule: yannh-патерн для груп з крапками — у назві файлу схеми зберігається лише **перший сегмент** `group` до першої крапки (`networking.k8s.io` → `networking`, `rbac.authorization.k8s.io` → `rbac`, `flowcontrol.apiserver.k8s.io` → `flowcontrol`); попередній `<group-з-крапками-як-дефіси>` давав 404 для всіх ресурсів `*.k8s.io` (Ingress, NetworkPolicy, ClusterRole, StorageClass, FlowSchema, RuntimeClass тощо). Виправлено в `expectedSchemaUrlForTypedManifest` і `buildNetworkPolicyYaml` (`check-k8s.mjs`); опис патерну в `k8s.mdc` переписано з прикладами для усіх типових груп. Bump `k8s.mdc` `1.34` → `1.35`.

## [1.13.40] - 2026-05-18

### Fixed

- `lint-text` CLI: preflight на `shellcheck`, `patch` і `dotenv-linter` до ланцюжка cspell/shellcheck/dotenv. Канон `lint-text.yml.snippet.yml` — кроки `Install shellcheck` (apt) і `Install dotenv-linter` (curl); rego `text.lint_text`. Bump `text.mdc` `1.28` → `1.29`.

## [1.13.39] - 2026-05-18

### Fixed

- `lint-ga` CLI: preflight на `conftest` (поряд із `shellcheck`/`uv`) з install-hint; глобальний `catch` у `bin/n-cursor.js` більше не ковтає повідомлення `failConftestMissing()`. Канон `lint-ga.yml.snippet.yml` — крок `Install conftest` для CI; rego `ga.lint_ga` вимагає curl на release conftest.

## [1.13.38] - 2026-05-18

### Added

- `js-run` rule: у backend `package.json#scripts` заборонено `env $(cat …) bun` — заміна на `bun --env-file=…` (по файлу з `cat`); Rego `scriptsForbidden` `env-cat-bun`. Bump `js-run.mdc` `1.10` → `1.11`.

## [1.13.37] - 2026-05-18

### Added

- `js-run` rule: у backend `package.json#scripts` заборонено запуск через `node` — один runtime **Bun** у dev і prod; Rego `js_run.package_json` (`scriptsForbidden` у `package.json.deny.json`), frontend з `vite` у `devDependencies` пропускається. Bump `js-run.mdc` `1.9` → `1.10`.

### Changed

- `k8s` rule: канон **NetworkPolicy** egress для всіх workload-ів — kube-dns; **TCP 80/443** на `0.0.0.0/0`; інші порти лише in-cluster (`namespaceSelector: {}`, `*.svc`). Заборонено `egress: [{}]`. Оновлено `buildNetworkPolicyYaml`, rego `k8s.network_policy`, template. Bump `k8s.mdc` `1.33` → `1.34`.

## [1.13.36] - 2026-05-18

### Changed

- `k8s` rule: **NetworkPolicy** обов'язковий не лише для **Deployment**, а й для **StatefulSet**, **DaemonSet**, **Job**, **CronJob** (`workloadAppLabel`, multi-doc `networkpolicy.yaml`, autofix/validate для всіх шарів `k8s`). HPA/PDB лишаються прив'язаними до Deployment. Bump `k8s.mdc` `1.32` → `1.33`.

## [1.13.35] - 2026-05-18

### Added

- `k8s` rule: для кожного **Deployment** під `k8s` обов'язковий **NetworkPolicy** — у `components/networkpolicy.yaml` для base (разом із HPA/PDB) або `networkpolicy.yaml` поруч у не-base оверлеях. Rego-пакет `k8s.network_policy`, перевірка прив'язки за `metadata.name` / міткою `app` у JS. **`check k8s`** автоматично створює відсутній `networkpolicy.yaml` і додає його в `components/kustomization.yaml` (`resources`). Bump `k8s.mdc` `1.31` → `1.32`.

## [1.13.34] - 2026-05-18

### Changed

- `k8s` rule: винесено канонічний приклад `.kubescape-exceptions.json` з inline-fenced-блоку в `k8s.mdc` у `fix/kubescape_exceptions/template/.kubescape-exceptions.json.snippet.json`; `.mdc` тепер посилається на template markdown-лінком, `inlineTemplateLinks` підставить вміст у `.cursor/rules/n-k8s.mdc` під час sync. Dogfood новій клаузі `scripts.mdc` ("Принцип поширюється і на pure-doc канони"). Bump `k8s.mdc` `1.30` → `1.31`.

## [1.13.33] - 2026-05-18

### Fixed

- `style-lint`, `image-avif` rules: markdown-посилання на `policy/*/template/*` у канонічних `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) падав, бо шаблони не були згадані в `npm/rules/<id>/<id>.mdc`. Bump: `style-lint.mdc` `1.3` → `1.4`, `image-avif.mdc` `1.2` → `1.3`.

## [1.13.32] - 2026-05-18

### Added

- `k8s` rule (`lint-k8s`): підтримка per-project винятків kubescape — якщо в корені проєкту є `.kubescape-exceptions.json`, `runKubescape` автоматично передає його через `--exceptions <file>`. Канонічний приклад — control **C-0012** (`Applications credentials in configuration files`) на ConfigMap з публічним JWT-конфігом (`HASURA_GRAPHQL_JWT_SECRET={"jwk_url": "https://…"}`): control тригериться лише на імʼя env, не на значення, тому точкове `postureExceptionPolicy` з `kind: ConfigMap` + `attributes.name` знімає false-positive без глобального вимкнення контролю. Bump `k8s.mdc` `1.29` → `1.30`. Документація — секція "Винятки kubescape" в `k8s.mdc`.

## [1.13.31] - 2026-05-18

### Changed

- `changelog` rule (`n-changelog.mdc` `2.3` → `2.4`): підтримка Python — `pyproject.toml` (`[project]` / Poetry) поряд із `package.json`; discovery воркспейсів через `getMonorepoProjectRootDirs`; PyPI-порівняння для registry-published Python-пакетів. `package-manifest.mjs` — уніфікований маніфест npm/python.

## [1.13.30] - 2026-05-18

### Changed

- `changelog` rule (`n-changelog.mdc` `2.2` → `2.3`): `alwaysApply: true` без `globs`; інверсія — не вимагати bump для `docs/`/`doc/` і шляхів з `.gitignore`. `check changelog`: інтеграційна база `dev` **або** `main` (перша наявна); на `dev`/`main` local-only пропускається; релевантні зміни фільтруються в git. Тести.

## [1.13.29] - 2026-05-18

### Changed

- `changelog` rule (`n-changelog.mdc` `2.1` → `2.2`): розширені `globs` (типові шляхи пакета + `*.rego` / `*.mdc`) — правило потрапляє в контекст агента при правках коду, не лише `package.json` / `CHANGELOG.md`; секція «Чеклист агента» для будь-якого репозиторію з правилом.
- `changelog/fix/consistency/check.mjs`: npm-published режим — якщо `version` збігається з реєстром, але в git є зміни workspace без bump (feature vs `dev` або незакомічене на `dev`) → fail. Регресійні тести.

## [1.13.28] - 2026-05-18

### Fixed

- `scripts/utils/template.mjs` (`stripJsonComments`): враховує контекст рядкових літералів. Раніше regex `\/\*[\s\S]*?\*\/` без розрізнення string-літералів агресивно вирізав блоки між `/*` і `*/`, які зустрічаються в glob-патернах JSON-значень (напр. `**/node_modules/**`, `**/k8s/**/*.yaml`), і канонічний `.cspell.json.snippet.json` чи `.oxfmtrc.json.snippet.json` після стрипу стягувався в один склеєний рядок замість 7-елементного масиву. Новий стриппер пропускає вміст `"..."` (з підтримкою backslash-escape) без змін і вирізає лише реальні JSONC-коментарі.

### Changed

- `hasura` rule (`hasura.svc_hl`): іменування Service узгоджено з `k8s.svc_hl_yaml` — headless (`spec.clusterIP: None`) має суфікс `-h-hl` (напр. `db-h` → `db-h-hl`), clusterIP у `svc.yaml` — `-h`. Target розширено на `hasura/k8s/base/svc.yaml` і `svc-hl.yaml`; додано `svc_hl_test.rego`. `hasura.mdc` і `fix/internal_urls` оновлено під headless DNS (`contract-h-hl`). Bump `hasura.mdc` `1.1` → `1.2`.

## [1.13.27] - 2026-05-18

### Fixed

- `text`, `js-lint`, `js-run` rules: додано markdown-посилання на template-файли у канонічні `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) раніше падав, бо канонічні `.mdc` не містили `[name](./policy/<concern>/template/<file>)` для власних шаблонів. Bump rule versions: `text.mdc` `1.27` → `1.28`, `js-lint.mdc` `1.22` → `1.23`, `js-run.mdc` `1.8` → `1.9`.

## [1.13.26] - 2026-05-17

### Changed

- `security` rule: міграція з gitleaks на TruffleHog. Канонічний `lint-security` тепер `trufflehog filesystem . --no-update --exclude-paths .trufflehog-exclude --results=verified,unknown --fail`; allowlist переїхав із TOML-файлу `.gitleaks.toml` (`[extend].useDefault=true` + `[allowlist].paths`) у plain-text `.trufflehog-exclude` (regex-pattern на рядок). Bump `security.mdc` version `1.1` → `2.0`.

### Added

- `npm/rules/security/fix/trufflehog/check.mjs` + `template/.trufflehog-exclude.snippet.txt` — JS-частина правила перевіряє існування `.trufflehog-exclude` та subset канонічних patterns через `checkTextSubset` (Rego не пасує plain-text-формату).
- `npm/rules/security/policy/lint_security_yml/` — Rego policy `security.lint_security_yml` із template `lint-security.yml.snippet.yml`; перевіряє, що `.github/workflows/lint-security.yml` містить крок з `uses: trufflesecurity/trufflehog@main`. У `security.mdc` inline-YAML замінено на template-link для single-source-of-truth (патерн із `php`/`style-lint`/`js-lint`). Workflow обовʼязковий (`target.json: required=true` + `missingMessage`), у корені cursor створено `.github/workflows/lint-security.yml` за каноном.

### Removed

- `npm/rules/security/fix/gitleaks/` + `npm/rules/security/policy/gitleaks/` (концерн gitleaks повністю видалено разом з Rego policy `security.gitleaks`).
- Кореневий `.gitleaks.toml` (замінено на `.trufflehog-exclude`).

## [1.13.25] - 2026-05-17

### Added

- `js-lint` rule template/ міграція (Phase 15, фінал): 4 концерни — `jscpd` (snippet з minLines >=N semantic), `vscode_extensions` (snippet-array), `package_json` (partial — `type`+`scripts.lint-js` у template; engines + eslint-config semver ranges у rego), `lint_js_yml` (full-canon з required uses + per-line run substrings з template's eslint-job steps; --fix anti-patterns + checkout persist-credentials у rego).

### Closed

- Template/ migration scope (Phases 1-15): 19 з 39 template-eligible концернів мігровано у попередніх фазах + 17 нових у Phase 6-15 = усі 39 завершено (100%). Залишилися лише non-eligible (AST-walks, multi-kind YAML, cross-file gating) — за дизайном живуть у JS/rego inline.

## [1.13.24] - 2026-05-17

### Added

- `text` rule template/ міграція (Phase 14): 6 концернів — `cspell` (snippet + contains + deny з `@cspell/dict-` як forbidden substrings), `markdownlint` (3-level walker для `config.MD024.siblings_only`), `oxfmtrc` (scalar leafs + array subset-of; required_keys presence лишається в rego), `package_json` (top-level deny + deps/devDeps deny; `@nitra/cspell-dict` semver range у rego), `vscode_extensions` (snippet-array), `vscode_settings` (top-level leafs + per-language blocks).

## [1.13.23] - 2026-05-17

### Added

- `style-lint` rule template/ міграція (Phase 13): 4 policy концерни — `package_json` (contains + 2-level snippet для `stylelint.extends`; `@nitra/stylelint-config` presence лишається в rego), `vscode_extensions` (snippet-array), `vscode_settings` (top-level leaf walker для `css/less/scss.validate`), `lint_style_yml` (full-canon з substring-маркером з template's stylelint-job steps). `fix/tooling` (`.stylelintignore` partial) лишається JS-managed.

## [1.13.22] - 2026-05-17

### Added

- `image-avif.package_json` template/ міграція (Phase 12): typo-keys у `template/package.json.deny.json` (`@nitra/minify-image.disabled-avif` як приклад typo до `disable-avif`). Type-перевірки (inverse-patterns) лишилися в rego.

## [1.13.21] - 2026-05-17

### Added

- `js-run` rule template/ міграція (Phase 11): 3 концерни (інвентар недорахував `jsconfig`) — `configmap` (contains, OTEL_RESOURCE_ATTRIBUTES substrings), `package_json` (deny на bunyan/@nitra/bunyan у deps/devDeps), `jsconfig` (snippet з generic 2-level walker + top-level array як множина для `include`).
- Drift-тести у кожному `*_test.rego`.

## [1.13.20] - 2026-05-17

### Added

- `abie.clean_merged_ignore_branches` template/ міграція (Phase 10): action marker (`uses:` substring) + required `ignore_branches` tokens (`dev,ua`) тепер у `template/clean-merged-branch.yml.snippet.yml`. Rego читає expected step із template's `jobs.cleanup_old_branches.steps[0]`. Drift test покриває зміну required-branches.
- `abie.mdc` — inline `ignore_branches` фрагмент замінено на template-link.

## [1.13.19] - 2026-05-17

### Added

- `docker` rule template/ міграція (Phase 9): `docker.package_json` (snippet, scripts.lint-docker з trim_space, conditional на наявність) + `docker.lint_docker_yml` (full-canon — paths, required uses, run substrings з template's steps).
- `docker.mdc` — 2 inline-блоки замінено на template-links.

## [1.13.18] - 2026-05-17

### Added

- `js-bun-db.package_json` + `js-bun-redis.package_json` template/ міграція (Phase 8): `template/package.json.deny.json` (forbidden deps з причинами). Rego — простий deny-walker. 2 нових `*_test.rego` (8 тестів).

## [1.13.17] - 2026-05-17

### Added

- `php` rule template/ міграція (Phase 7): `php.package_json` (fragment, contains-walker для `lint-php`) + `php.lint_php_yml` (full-canon, substring-маркер `bun run lint-php` з template's php-job steps). 2 нових `*_test.rego` (8 тестів).
- `php.mdc` — 2 inline-блоки замінено на template-links.

## [1.13.16] - 2026-05-17

### Added

- `image-compress.package_json` template/ міграція (Phase 6): `template/package.json.{contains,deny}.json` + новий `package_json_test.rego` (10 тестів). Generic contains-walker для substring-перевірок lint-image + generic deny-walker для заборонених deps; інверс-патерни (`--avif` заборонений підрядок + аґреґатор `lint` має містити `bun run lint-image`) лишилися в rego.
- `image-compress.mdc` — inline `package.json` snippet замінено на template-links.

## [1.13.15] - 2026-05-17

### Added

- `npm-module` rule template/ міграція (Phase 5): усі 4 policy концерни — `emit_types_config` (fragment, 2-level walker), `root_package_json` (fragment, snippet-array `workspaces`), `npm_package_json` (partial — `files` whitelist у template, regex `types` + `devDependencies`-must-be-empty лишаються у rego), `npm_publish_yml` (full-canon workflow з per-concern field-by-field rego).
- 3 нові `*_test.rego` (раніше тестів не було для `emit_types_config`, `root_package_json`, `npm_publish_yml`) — кожен покриває canonical + 4-6 негативних + drift.

### Changed

- `emit_types_config.rego` — generic 2-level snippet walker (як `bun.bunfig` / `ga.vscode_settings`).
- `root_package_json.rego` — generic snippet-array subset-of walker (під `workspaces` і потенційні наступні масиви).
- `npm_package_json.rego` — `files` whitelist тепер subset-of через `data.template.snippet`; `types` regex + `devDependencies`-must-be-empty лишаються у rego як inverse-patterns.
- `npm_publish_yml.rego` — повний канон workflow у `template/npm-publish.yml.snippet.yml` (як ga workflow concerns), expected paths/branches/permissions/uses-marker читаються з `data.template.snippet.<path>`.
- `npm-module.mdc` — inline `npm-publish.yml` блок (35 рядків) замінено на template-link; додано окрему секцію «Канонічні конфіги» з лінками на `root_package_json`, `npm_package_json`, `emit_types_config` template-файли.
- `docs/adr/template-dir-concern-inventory.md` — усі 4 `npm-module.*` концерни позначено ✓; додано Phase 5 у прогрес; tally: 19/39 (49%).

## [1.13.14] - 2026-05-17

### Added

- `bun` rule template/ міграція (Phase 4): 2 концерни — `bunfig` (snippet-walker 2-level, `[install].linker = "hoisted"`) + `package_json` (partial — top-level deny-fields у template).
- `bunfig_test.rego` створено з нуля (раніше тестів не було) — 5 позитивних/негативних + drift.

### Changed

- `bun.bunfig.rego` — використовує той самий 2-level snippet-walker, що `ga.vscode_settings` (leaf-by-leaf + guard на non-object section).
- `bun.package_json.rego` — top-level deny-fields (`packageManager`, `dependencies`) тепер читаються з `data.template.deny`. Сентинельний value у `object.get(input, field, "__bun_missing__")` зберігає behavior «field present навіть якщо порожній обʼєкт». Логіка `@nitra/*`-only у devDependencies та lint-aggregator (cross-script) лишається у rego — це inverse-patterns, які не виносяться у template.
- `bun.mdc` — inline `bunfig.toml` snippet замінено на template-link; додано посилання на `package.json.deny.json` для документації заборонених top-level полів.
- `docs/adr/template-dir-concern-inventory.md` — `bun.*` концерни позначено ✓; додано Phase 4 у прогрес-секцію; tally: 15/39 (38%).

## [1.13.13] - 2026-05-17

### Added

- `text` rule: до ланцюжка `lint-text` додано крок `dotenv-linter` (`runDotenvLinter()` у `npm/rules/text/lint/run-dotenv-linter.mjs`). На знайдених `.env*` рекурсивно по проєкту виконується `dotenv-linter fix -r --no-backup --quiet . --exclude node_modules --exclude .envrc`, після чого симетричний `check` для фінальної перевірки. Якщо інструмент відсутній у `PATH` — друкуються підказки встановлення (`brew install dotenv-linter` для macOS).
- `text.mdc` (canonical + `.cursor/rules/n-text.mdc` mirror) описує новий крок і вимоги до `dotenv-linter` (тільки в `PATH`, **не** у `dependencies`/`devDependencies`).
- `.cspell.json` — додано слово `envrc` (направлення на direnv-файл, не key=value).
- Тест `npm/rules/text/lint/run-dotenv-linter.test.mjs` — порожнє дерево, авто-фікс `LowercaseKey`, ігнор `node_modules`/`.envrc`.

## [1.13.12] - 2026-05-17

### Added

- `ga` rule template/ міграція доповнена (Phase 3.5 — 4 full-canon workflow концерни, пропущені в 1.13.9): `clean_ga_workflows`, `clean_merged_branch`, `lint_ga`, `git_ai`. Кожен має повний YAML канон у `template/<workflow>.yml.snippet.yml`, rego читає expected-значення з `data.template.snippet.<path>` (path лишається у rego, literals — у template). Для кожного — `*_test.rego` із canonical/wrong/drift тестами.
- Wiring у `ga/fix/workflows/check.mjs`: `runAllGaRego` тепер `await loadTemplate(concernDir)` і передає `templateData` у `runConftestBatch` для кожного workflow концерну.

### Changed

- `ga.mdc` — 4 inline YAML-блоки повних workflow канонів замінено на markdown-посилання до `template/<workflow>.yml.snippet.yml`. Файл скоротився суттєво — канон тепер живе як data, а не як прозовий приклад.
- `template/clean-merged-branch.yml.snippet.yml`: `dry_run: false` (явний bool) замість `dry_run: no` — `yaml` npm (YAML 1.2) лишає `no` рядком, а Go-yaml у conftest нормалізує до `false`; пишемо канонізовану форму під runtime conftest-парсингу.
- `docs/adr/template-dir-concern-inventory.md` — додано 4 нові full-canon ga.\* концерни з ✓; оновлено summary (89 концернів, 43 з template — мігровано 13/43 = 30%).

### TODO

- `ga.workflow_common` — cross-workflow forbidden-patterns (concurrency, depcheck deny, shell line-continuation). Не fully full-canon — окремий випадок, мігрується пізніше.

## [1.13.11] - 2026-05-17

### Added

- `rego` rule template/ міграція (Phase 3): 3 концерни — `package_json` (snippet із збереженням `trim_space` tolerance), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object 2-level + окремий deny на non-object block).
- Drift-тести у кожному `*_test.rego`.

### Changed

- `rego.package_json.rego` — замість двох inline-deny (missing + wrong-value через `regex/trim_space`) тепер один snippet-walker через `data.template.snippet`.
- `rego.vscode_extensions.rego` — замість inline `"tsandall.opa"` тепер subset-of через `data.template.snippet.recommendations`.
- `rego.vscode_settings.rego` — 2-рівневий snippet-walker з гардом `is_object(inner)` для випадку, коли block існує, але не обʼєкт.
- `rego.mdc` — inline `package.json` snippet замінено на template-link; додано посилання на `.vscode/{extensions,settings}.json` template-файли. Виправлено застаріле `Цілі — npm/policy/` → `npm/rules/`.
- `docs/adr/template-dir-concern-inventory.md` — позначено 3 `rego.*` концерни як ✓; додано Phase 3 у прогрес-секцію.

## [1.13.10] - 2026-05-17

### Fixed

- `runLintRego` (`npm/rules/rego/lint/lint.mjs`) — `LINT_TARGETS` вказував на застарілий шлях `npm/policy` (не існує після Phase 1 реструктуризації), тож `bun run lint-rego` мовчки exit 0 без реальної перевірки. Тепер `LINT_TARGETS = ['npm/rules']` — `opa check --strict`, `regal lint`, `conftest verify` реально проходять по всіх 111 `.rego`-файлах. TDD-регресія у `lint.test.mjs` (broken-syntax + well-formed fixtures).

### Changed

- `.regal/config.yaml` — додано `idiomatic.directory-package-mismatch` і `imports.unresolved-reference` у `ignore` (інтенціональні конвенції проєкту: package = `<rule>.<concern>` у `<rule>/policy/<concern>/`; `data.template.*` ін'єктиться runtime через `--data`). `style.line-length.max-line-length: 220` — узгоджено з `opa fmt` (тримає малі обʼєкти single-line).
- `*_test.rego` з порушенням `test-outside-test-package` (4 файли: `js-lint.jscpd`, `js-lint.vscode_extensions`, `security.gitleaks`, `vue.package_json`) — перейменовано в `<package>_test` із явним `import data.<package>`.
- `opa fmt -w npm/rules` — auto-fix форматування.
- `docs/adr/template-dir-concern-inventory.md` — додано 4 `ga.*` концерни з відміткою `✓` (мігровано); оновлено summary-числа (85 концернів, 39 з template — 46%); додано секцію прогресу міграції.

## [1.13.9] - 2026-05-17

### Added

- `ga` rule template/ міграція (Phase 2): 4 концерни — `package_json` (contains-style), `vscode_extensions` (snippet-array), `vscode_settings` (snippet-object), `zizmor_yml` (snippet з канонічним path `rules.unpinned-uses.config.policies."*"`).
- Drift-тести (`test_data_template_drives_*`) у кожному `*_test.rego` ловлять регресію, якщо rego перестане читати з `data.template`.

### Changed

- `ga.package_json.rego` — замість двох inline-deny з `is_string` + `regex.match` тепер один generic contains-walker через `data.template.contains`.
- `ga.vscode_extensions.rego` — замість inline `"github.vscode-github-actions"` тепер subset-of через `data.template.snippet.recommendations`.
- `ga.vscode_settings.rego` — 2-рівневий snippet-walker через `data.template.snippet` (літеральні keys `[github-actions-workflow]`, `editor.defaultFormatter`).
- `ga.zizmor_yml.rego` — замість substring `json.marshal` хака тепер структурний чек `rules.unpinned-uses.config.policies."*"` із expected value з `data.template.snippet`.
- `ga.mdc` — inline `package.json` snippet і `zizmor.yml` snippet блоки замінено на markdown-посилання на template-файли; додано посилання на нові template/ для `.vscode/{extensions,settings}.json`.

## [1.13.8] - 2026-05-17

### Changed

- Перенесено частину per-document логіки з `fix` у Rego policy:
  - `js-lint`: `.jscpd.json` і `.vscode/extensions.json`;
  - `ga`: `package.json#scripts.lint-ga`, `.vscode/extensions.json`, `.vscode/settings.json`, `.github/zizmor.yml`;
  - `security`: `.gitleaks.toml` (`[extend].useDefault = true`);
  - `vue`: залежності Vue/Vite-пакетів і заборону `esbuild`.
- Відповідні JS check-и спрощено до FS/cross-file/AST/tooling частини без дублювання Rego-умов.
- `ensureNitraCursorInRootDevDependencies` тепер додає `@nitra/cursor` тільки в `package.json` поруч із запуском, якщо в ньому є `workspaces`.
- `vue.mdc` уточнює тестування через Bun Test Runner + Vue Test Utils/happy-dom замість Vitest/jsdom.

### Fixed

- `npm/package.json#devDependencies` — прибрано self-reference `@nitra/cursor`, щоб published package знову відповідав `npm-module` compact-package canon.

## [1.13.7] - 2026-05-17

### Fixed

- `inlineTemplateLinks`: `String.replace(needle, replacement)` інтерпретує `$'`, `$&` тощо у `replacement`. Через це інлайнінг `.gitleaks.toml.snippet.toml` (де є `$'''`) ламав вивід — хвіст `.mdc` реінжектився всередину блока. Перехід на function-replacer (`(_) => replacement`) усуває це. Додано регресійний тест із фікстурою `with-dollar.toml`.

## [1.13.6] - 2026-05-17

### Added

- `npm/scripts/utils/inline-template-links.mjs` — `inlineTemplateLinks(text, ruleDir)`: під час sync знаходить markdown-лінки виду `[label](./…/template/…)` у `.mdc` і замінює їх inline fenced-блоком з вмістом відповідного файла. Відсутній файл — hard error (fail loud).

### Changed

- `readBundledRuleContent` у `npm/bin/n-cursor.js` тепер пропускає текст правила через `inlineTemplateLinks` перед записом у `.cursor/rules/n-*.mdc`. Template-посилання у скопійованих правилах більше не зламані.

## [1.13.5] - 2026-05-17

### Added

- Оркестратор `run-rule.mjs` тепер викликає `findMissingMdcRefs` для кожного правила — fail, якщо файл у `template/` не згаданий як markdown-посилання у `<id>.mdc`. Поки що активно лише для `security` (єдине правило з `template/`); готова страховка для Phase 2+.

### Fixed

- `check-mdc-template-refs.test.mjs` тест 3 — фіксував дубль test 1; тепер використовує окрему `no-templates` фікстуру, що дійсно валідує "no template/ dirs → empty result".

## [1.13.4] - 2026-05-17

### Removed

- `npm/package.json#devDependencies` — повторно видалено self-reference `@nitra/cursor` (порушує canon `npm-module`: «devDependencies не публікуються користувачам пакета»). Автоматично повертався у попередніх тасках template-dir роботи; цей коміт остаточно прибирає.

## [1.13.3] - 2026-05-17

### Changed

- `security/security.mdc` — прибрано inline merge-фрагменти (package.json snippet для `lint-security`, .gitleaks.toml повний канон), замість них markdown-посилання на файли в `template/` (single source of truth). Зміст правила залишається (описи для чого потрібен gitleaks, GitHub Actions), видалено дублювання фіксованого коду.

## [1.13.2] - 2026-05-17

### Changed

- **`adr` hook output тепер MADR v4.0.0 minimal** — capture/normalize prompts генерують ADR-и з canonical headings `Context and Problem Statement`, `Considered Options`, `Decision Outcome`, `Consequences`, `More Information`. Prompts стали evidence-bound: якщо transcript не містить альтернатив або підтверджених наслідків, hook явно пише, що даних немає, замість вигадування деталей.

## [1.13.1] - 2026-05-17

### Added

- **`adr` rule: Cursor Agent Stop-hook support** — `npx @nitra/cursor` тепер merge-ить project-level `.cursor/hooks.json` і додає managed `hooks.stop` entries для `.claude/hooks/capture-decisions.sh` та `.claude/hooks/normalize-decisions.sh`. Hook-скрипти приймають Cursor payload (`transcript_path`, `conversation_id` / `generation_id`, `workspace_roots[]`) і використовують той самий ADR capture/normalize pipeline, що й Claude Code.

## [1.13.0] - 2026-05-17

### Changed

- `security/fix/gitleaks/check.mjs` читає канон з `template/`, не з inline regex.
- `security/policy/package_json/package_json.rego` читає очікувані значення з `data.template.*`, не з inline literals.
- Оркестратор `run-rule.mjs` для policy-концернів вантажить `template/` через `resolveConcernTemplateData` і передає у `runConftestBatch.templateData`.
- Снепет `.gitleaks.toml.snippet.toml` тримає канонічний title + allowlist paths (description лишається user-specific).
- **9 правил переведено з `alwaysApply: true` на `alwaysApply: false` + `globs:`** — AI-контекст у Cursor/Claude Code підвантажується лише при роботі з релевантними файлами; програмна валідація через `npx check <rule>` залишається повністю функціональною незалежно від AI-контексту. Економить контекстне вікно у сесіях, де редагують код, далекий від відповідних конфігів.
  - **`bun`** (`1.7 → 1.8`) — `globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"`
  - **`capacitor`** (`1.0 → 1.1`) — `globs: "**/capacitor.config.json,**/android/**,**/ios/**"`
  - **`js-bun-db`** (`1.6 → 1.7`) — `globs: "**/package.json,**/src/conn/**"`
  - **`js-bun-redis`** (`1.0 → 1.1`) — `globs: "**/package.json,**/src/conn/**"`
  - **`js-mssql`** (`1.3 → 1.4`) — `globs: "**/package.json,**/src/conn/mssql-*"`
  - **`npm-module`** (`1.12 → 1.13`) — `globs: "npm/**,**/package.json,**/hk.pkl,.github/workflows/npm-publish.yml,**/tsconfig*.json"`
  - **`tauri`** (`1.0 → 1.1`) — `globs: "**/src-tauri/**,**/tauri.conf.json"`
  - **`js-lint`** (`1.21 → 1.22`) — `globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"`
  - **`js-run`** (`1.7 → 1.8`) — `globs: "**/package.json,**/jsconfig.json,**/src/**/*.{js,mjs,cjs,ts,tsx}"`
- **Мотивація:** усі 9 правил мають повне покриття `npx @nitra/cursor fix <rule>` (JS-перевірка + Rego policy). Тримати їх `alwaysApply: true` без потреби палить контекст AI у сесіях, де редагується непов'язаний код. Помилка → check ловить → AI виправляє — той самий цикл, що для `security@1.12.1` і `changelog`/`image-compress`/`php`/`vue`.
- **Залишено `alwaysApply: true`** — `text` (cross-cutting cspell-словник, апостроф), `adr` (про процес capture/normalize hooks), `ci4` (0 програмних чекерів — без AI-контексту правило мертве), `abie` (має JS `applies`-гейт, у не-abie репо мовчить; файли розкидані).

## [1.12.1] - 2026-05-17

### Changed

- **`npm/rules/security/security.mdc`** — `alwaysApply: true` → `alwaysApply: false` + `globs: "**/.gitleaks.toml,**/package.json,**/.github/workflows/**/*.yml"`. AI-контекст правила тепер підвантажується лише при роботі з релевантними файлами (за зразком `changelog`/`image-compress`/`php`), а не на кожен турн. Програмна валідація через `npx @nitra/cursor fix security` залишається завжди увімкненою (через `auto.md = завжди`) і ловить помилки незалежно від AI-контексту. Версія frontmatter `1.0` → `1.1`.
- **Мотивація:** правило має повне покриття перевіркою (Rego + JS-check); тримати його `alwaysApply: true` не дає AI додаткової цінності понад те, що `check security` ловить програмно — лише марно займає контекстне вікно при роботі з кодом, не повʼязаним з конфігурацією security.

## [1.12.0] - 2026-05-16

### Added

- **Нове правило `security`** (увімкнене за замовчуванням, як `text`/`adr`) — секрет-сканер на базі [gitleaks](https://github.com/gitleaks/gitleaks). Вимагає:
  - `scripts.lint-security` у `package.json` з викликом `gitleaks detect` (або `gitleaks git`);
  - `bun run lint-security` всередині агрегованого `scripts.lint` (якщо `lint` є);
  - `.gitleaks.toml` у корені з `useDefault = true` у блоці `[extend]` (без перетирання вбудованих правил);
  - `gitleaks` **не** у `dependencies`/`devDependencies` (інструмент глобальний, як `shellcheck`/`conftest`).
- **`npm/rules/security/{security.mdc,auto.md,fix/gitleaks/check.mjs,policy/package_json/{package_json.rego,target.json,package_json_test.rego}}`** — повна структура правила за зразком `image-compress` (Rego per-document валідація + JS-частина для FS). 9 rego-тестів + 5 JS-тестів.
- **`npm/scripts/auto-rules.mjs`** — `'security'` додано в `AUTO_RULE_ORDER` (alphabetical, між `rego` і `style-lint`) і викликається `addRule('security')` без умови. `auto-rules.test.mjs` оновлено.

### Motivation

Команда вже виявляла секрети у public-репо вручну (gitleaks локально + GitHub Push Protection); правило виносить інструмент у канонічний `bun run lint`, щоб витоки ловилися ще до push. Default-on, бо «опт-ін на secret-scanning» — це anti-pattern: репо, що не вмикав security вручну, найімовірніше і є той, що зливає секрети.

## [1.11.17] - 2026-05-16

### Fixed

- **`npm/rules/js-run/fix/runtime/check-fixture.test.mjs`** — фікстури двох тестів («0, якщо `import { SQL } from 'bun'` у `src/conn/`» і «враховує `package.json#imports['#conn/*']`») приведено у відповідність до канону `js-run`: іменування файлу `pg.js` → `pg-write.js` (шаблон `pg-{read|write}[-<id>]`) та іменований експорт `db` → `pgWrite` (camelCase від basename). Раніше фікстури використовували старий формат і check.mjs справедливо повертав `1`, через що очікування `toBe(0)` падало. Решта тестів conn-канону (`mssql-write.js`/`mssqlWrite` happy-path, `mssqlWriter` negative-path) у тому ж файлі вже були канонічними — їх не торкаюся.

### Removed

- **`npm/package.json#devDependencies`** — повторно видалено `@nitra/cursor: ^1.11.16` self-reference, який повернувся в коміті `8ae6e9e auto adr` (автоматичний stop-hook fix). Той самий блок уже прибирали в `1.11.14` (див. запис нижче) — потрапив назад через автофікс. `npx @nitra/cursor fix npm-module` знову зелений.

## [1.11.16] - 2026-05-16

### Changed

- **`npm/rules/adr/adr.mdc`**, **`npm/scripts/auto-rules.mjs`**, **`npm/rules/adr/auto.md`** — правило `adr` тепер **увімкнене за замовчуванням** (`addRule('adr')` без умови, поруч з `addRule('text')`; `auto.md` = `завжди`). Раніше — opt-in вручну через `"adr"` у `rules`. Щоб вимкнути для конкретного репо — `"adr"` у `disable-rules`. Текст правила і JSDoc у `sync-claude-config.mjs` оновлено відповідно. Існуючі `.n-cursor.json` із явним `"adr"` у `rules` лишаються валідними (`mergeConfigWithAutoDetected` дедуплікує).
- **Мотивація:** ADR/Runbook/Knowledge capture виявився корисним для всіх проєктів команди, а не лише тих, де його свідомо ввімкнули; opt-out зручніший за opt-in, бо інтегрує hooks у новий репо з коробки.

## [1.11.15] - 2026-05-16

### Changed

- **`npm/.claude-template/hooks/capture-decisions.sh`** (+`.claude/hooks/capture-decisions.sh` синк) — Stop-hook тепер генерує **slug-name** для чернетки замість session-hash суфікса. Раніше: `<timestamp>-<session-id[0:8]>.md` (наприклад `20260516-090349-e513a1f0.md`). Тепер: `<timestamp>-<slug>.md` (наприклад `20260516-090349-структура-директорій-правила-fix-lint-policy.md`). **Slug береться з вже згенерованого LLM-заголовка** першого `## [ADR|Runbook|Knowledge] <heading>` блоку — без додаткового LLM-виклику, та сама вартість Stop-hook. Конвенція slug-у синхронізована з `normalize-decisions.sh:171`: малі літери, цифри, дефіс, кирилиця; англомовні технічні терміни лишаються англійською (`fix`, `lint`, `policy` — не транслітеруються). Колізії в межах однієї секунди — суфікс `-2`, `-3`, …, як у `normalize-decisions.sh:244-257`. Fallback на старий `<timestamp>-<session-id[0:8]>.md` якщо heading не спарсився (response без `## ADR|Runbook|Knowledge` префікса).
- **Мотивація:** після прогону `normalize-decisions.sh` нові чернетки створювалися з абстрактними session-hash іменами і знову вимагали LLM-`rewrite`-операцію щоб отримати читабельний slug. Тепер capture одразу пише читабельний slug → наступна нормалізація обмежується лише `delete`/`merge-into` для дублікатів (rename-операції стають рідкісними).

## [1.11.14] - 2026-05-16

### Removed

- **`npm/package.json#devDependencies`** — повністю видалено блок (містив лише self-reference `@nitra/cursor: ^1.11.9`). `npx @nitra/cursor fix npm-module` зафіксував порушення: `devDependencies` не публікуються користувачам пакета, але інструменти, що **потрібні** для розробки пакета, мають жити в кореневому `package.json` (у workspace-root) як `@nitra/cursor: workspace:*` (там уже є). Self-reference у `npm/package.json` лишався з попередньої практики, але **публікувався** у npm-tarball (хоч і ігнорувався установником, оскільки лізе у nested deps), забруднюючи метадані пакета.
- **`knip.json#workspaces.npm.ignoreDependencies: ["@nitra/cursor"]`** — workaround, доданий у 1.11.13 для приховання knip-violation на self-reference, тепер не потрібен (першопричина прибрана). Знято, щоб конфіг лишався чистим.

## [1.11.13] - 2026-05-16

### Fixed

- **`npm/rules/{bun,image-compress,js-bun-redis,js-run,php,style-lint,text}/fix/<concern>/check.mjs`** — escape `@nitra` як `\@nitra` у JSDoc-блоках (`/** … */`), де `npx @nitra/cursor fix` стояв всередині backticks. ESLint-плагін `jsdoc/escape-inline-tags` парсив `@nitra` як інлайн-тег (false-positive у backticks) і видавав 8 warnings. Виправлення — escape-символ `\` перед `@`, як уже зроблено в інших місцях коду (CHANGELOG 1.11.5 для `js-run`). Pass-повідомлення та `//`-коментарі поза JSDoc не зачіпало — там парсер не активний.
- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк) — заголовок секції перейменовано з **«Скоуп»** на **«Scope»**, бо cspell флагав «Скоуп» як unknown word. Англійський «Scope» зрозумілий і не вимагає розширення словника. Тіло секції без змін.

## [1.11.12] - 2026-05-15

### Removed

- **`npm/scripts/utils/discover-checkable-rules.mjs`**, **`npm/scripts/utils/run-rule.mjs`** — фаза 3 реструктуризації: dual-mode підтримка `js/` (legacy) прибрана. Після завершення масового переїзду у 1.11.10 (всі 26 правил у `rules/<id>/fix/`) інфраструктура `n-cursor fix` тепер сканує **тільки** `rules/<id>/fix/<concern>/check*.mjs`. Конкретно: (1) у `discoverCheckableRules` видалено `listJsConcerns(js/, 'js')`-виклик, утиліту `mergeJsConcerns` (fatal на дублікат `js/`+`fix/`) і поле `rootDir` у `JsConcern`-типі; (2) у `run-rule.mjs::resolveJsCheckPath` `concern.rootDir ?? 'js'` замінено на хардкод `'fix'`; (3) JSDoc на початку обох файлів і на `evaluateAppliesGate` оновлено з `js/applies/check.mjs` на `fix/applies/check.mjs`; (4) коментар на `discoverCheckScripts` у `npm/bin/n-cursor.js` оновлено — згадку legacy `js/check.mjs` прибрано. Жодне правило в `rules/` не торкається — лише сканер.
- **`npm/scripts/utils/discover-checkable-rules.test.mjs`**, **`npm/scripts/utils/run-rule.test.mjs`** — прибрано тести dual-mode: `правило з тільки JS-концерном у legacy js/`, `правило з різними концернами у js/ і fix/`, `дублікат концерну в js/ і fix/ — fatal`, `пропускає js/utils/ як концерн`, `концерн з rootDir="fix"`, `концерн без rootDir (legacy-тести) fallback до js/`, `applies-гейт у fix/applies/`. Додано: `legacy js/-структура ігнорується (concern у js/<name>/ не підхоплюється)` — гарантує, що випадковий залишок `js/`-дерева у правилі не виконається, та `правило з кількома JS-концернами в fix/ — всі присутні, відсортовані`. Тестовий хелпер `addJsConcern` / `writeConcernJs` — за замовчуванням пишуть у `fix/` без параметра rootDir.

## [1.11.11] - 2026-05-15

### Removed

- **`npm/scripts/lint-conftest.mjs`** — скрипт видалено повністю. Його єдина функція — ітерувати policy-концерни через `discoverCheckableRules` і запускати `runConftestBatch` на реальних файлах — **повністю дублювала** `npx @nitra/cursor fix` (CHANGELOG 1.11.5: «`bun.bunfig`, `text.cspell`, `npm_module.npm_publish_yml` тепер прогоняються через CLI `check <id>` без додаткового `bun run lint-conftest`»). Окремий канал залишався лише як IDE-fast-feedback, але через одне джерело правди (`target.json` поруч з `.rego`) другий entry-point не дає нічого нового. Кореневий `package.json` оновлено: скрипт `lint-conftest` прибрано, ланцюжок `lint` тепер `bun run lint-rego && bun run lint-js && …` (без `lint-conftest`).

### Changed

- **`npm/README.md`** — секція «Структура пакету» переписана під поточний layout (`rules/<id>/<id>.mdc` замість застарілих `mdc/`). Додано підсекцію **«Структура одного правила»** з принципом fix/lint/policy: технологія реалізації визначає директорію — JS для `npx @nitra/cursor fix` у `fix/<concern>/`, JS для `bun run lint-<id>` у `lint/`, rego для `npx @nitra/cursor fix` у `policy/<concern>/`. Решта `mdc/`-посилань у README також виправлені на `rules/`.
- **`.cursor/rules/conftest.mdc`** — крок 5 у workflow «нова перевірка» переписано: окремої реєстрації нового rego-пакета в TARGETS більше не потрібно (TARGETS видалено разом із `lint-conftest.mjs`); `discoverCheckableRules` автоматично підхоплює пакет за наявності `target.json` поруч з `.rego`.
- **`.cursor/rules/scripts.mdc`** — згадку `lint-conftest.mjs` прибрано зі списку «крос-правильної інфраструктури» у `npm/scripts/`.
- **`npm/rules/abie/abie.mdc`** (cross-reference) — `npx @nitra/cursor lint-conftest` → `npx @nitra/cursor fix abie`; `npm/policy/abie/` → `npm/rules/abie/policy/`; `check-abie.mjs` → `fix/<concern>/check.mjs`.
- **`npm/rules/**/fix/<concern>/check.mjs`** (10 файлів) та **`npm/rules/**/policy/<concern>/<name>.rego`** (7 файлів) — у коментарях і `pass()`-повідомленнях `bun run lint-conftest` замінено на `npx @nitra/cursor fix` (структурна валідація живить fix-канал; окремого `lint-conftest`-каналу більше немає). Для conditional rego-полісі без `target.json` (ті, що не auto-discoverable) текст коментарів переформульовано — замість «глобально у `lint-conftest` НЕ реєструється» тепер «без `target.json` поруч (не auto-discoverable через `n-cursor fix`)».

## [1.11.10] - 2026-05-15

### Changed

- **`npm/rules/<rule>/js/`** — фаза 2 реструктуризації: усі 26 правил перенесені з `rules/<id>/js/` у `rules/<id>/fix/` (JS-концерни `check*.mjs`) та `rules/<id>/lint/` (CLI-entry `lint.mjs` + helper-runner-и). Директорія `rules/<id>/js/` більше не існує ні в жодному правилі. Перелік переміщеного: 18 правил (Category A) — лише концерни у `fix/`; 7 правил (Category B) — концерни у `fix/` та lint-entry у `lint/`: `ga`, `docker`, `php`, `rego`, `k8s`, `text`, а також `abie` (6 концернів, без lint-entry). Policy-каталоги (`rules/<id>/policy/`) — не рухались. Відносні imports у всіх переміщених файлах залишились дійсними (глибина `js/` = глибина `fix/`/`lint/`). Виправлено крос-модульні imports: `rules/abie/utils/k8s-tree.mjs`, `rules/ga/lint/lint.mjs`, `rules/docker/fix/lint/discover.test.mjs`, `rules/nginx-default-tpl/fix/template/check.mjs` — усі 4 оновлено з `*/js/*` на правильні нові шляхи. Оновлено hardcoded imports у `tests/check-rule-fixtures.test.mjs`, `tests/integration-repo-checks.test.mjs`, `tests/check-empty-trees.test.mjs`.
- **`npm/bin/n-cursor.js`** — 5 static imports CLI lint-entry перенаправлено: `rules/rego/js/lint.mjs` → `rules/rego/lint/lint.mjs`, `rules/ga/js/lint.mjs` → `rules/ga/lint/lint.mjs`, `rules/docker/js/run.mjs` → `rules/docker/lint/lint.mjs`, `rules/k8s/js/run.mjs` → `rules/k8s/lint/lint.mjs`, `rules/text/js/lint.mjs` → `rules/text/lint/lint.mjs`.
- **`npm/rules/k8s/lint/run-roots.test.mjs`** — import `./run.mjs` → `./lint.mjs` (файл перейменовано в межах переїзду).

## [1.11.9] - 2026-05-15

### Changed

- **`npm/scripts/utils/discover-checkable-rules.mjs`**, **`npm/scripts/utils/run-rule.mjs`** — фаза 1 реструктуризації `rules/<id>/js/` → `rules/<id>/{fix,lint}/`: інфраструктура `n-cursor fix` тепер **dual-mode** — сканує JS-концерни одночасно у `rules/<id>/js/<concern>/` (legacy) і `rules/<id>/fix/<concern>/` (новий формат). Кожен знайдений концерн штампується полем `rootDir: 'js' | 'fix'`; `runRule` використовує його для побудови шляху імпорту через `resolveJsCheckPath`. Концерн з однаковим іменем у обох каталогах одного правила — fatal-помилка з підказкою «заверши міграцію цього концерну у fix/ і видали `js/<name>/`», щоб не лишалось напіввиконаних move-ів. `utils/` пропускається в обох коренях. Жодне правило ще не переїхало у фазі 1 — це лише підготовка інфраструктури; для зворотної сумісності `resolveJsCheckPath` має fallback `rootDir ?? 'js'`, тож тести, що збирають `jsConcerns` вручну без `rootDir`, продовжують працювати без змін. CLI-точки входу `n-cursor lint-X` (статичні `import` у `npm/bin/n-cursor.js:79-83`) переїдуть пізніше — у фазах 2/3 (move + оновлення imports).
- **`npm/scripts/utils/discover-checkable-rules.test.mjs`**, **`npm/scripts/utils/run-rule.test.mjs`** — додано покриття нового `fix/`-кореня: окремі тести на discovery концерну у `fix/`, mix `js/`+`fix/` різних концернів одного правила, fatal на дублікат, пропуск `fix/utils/`, runRule з `rootDir: 'fix'`, applies-гейт у `fix/applies/`, та fallback на `js/` для концернів без `rootDir` (зворотна сумісність із наявними тестовими фікстурами).

## [1.11.8] - 2026-05-15

### Changed

- **`npm/bin/n-cursor.js::syncSkills`** — файл `auto.md` зі скілу більше **не** копіюється у `.cursor/skills/n-<id>/`. `auto.md` — це службова мета для CLI-сторони (`scripts/auto-skills.mjs` читає його з пакета, щоб вирішити, чи автоматично активувати скіл у `.n-cursor.json`), у проєкті він зайвий і лише засмічує `.cursor/skills/`. Каталог-приймач після цієї зміни лишається без `auto.md` — тільки `SKILL.md` (і будь-які інші файли скілу, якщо зʼявляться). Раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI **не чіпає** — їх потрібно прибрати вручну (свідома вимога користувача, щоб синк не видаляв нічого без явної згоди). Заголовний коментар у `npm/bin/n-cursor.js` оновлено відповідно.

## [1.11.7] - 2026-05-15

### Changed

- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк) — `/n-fix` більше **не запускає** `bun run lint` і **не делегує** до `/n-lint`. Крок 6 (`bun run lint` з делегуванням, доданий у 1.11.6) повністю видалено; замість нього на початку SKILL.md додано секцію **«Scope»**, де явно зафіксовано: `/n-fix` опікується лише структурою проєкту (правила `.cursor/rules/` + `npx @nitra/cursor fix`), а лінт-порушення у самому коді (ESLint/oxlint/jscpd/cspell/knip/sonarjs/stylelint) — поза скоупом і виправляються винятково через `/n-lint`. Кроки 7→6 і 8→7 переномеровано; остаточний пункт 7 додатково нагадує, що лінт-помилки не входять у критерій успіху `/n-fix`. Мета — щоб агент, що виконує `/n-fix`, не плутав свою задачу з `/n-lint` і не запускав важкий `bun run lint` без потреби; те, що діагностує `/n-lint`, виправляється там же, а не дублюється тут.

### Fixed

- **`npm/tests/check-rule-fixtures.test.mjs`** — `nginxFixDir` оновлено з `rules/nginx-default-tpl/js/fixtures` на `rules/nginx-default-tpl/js/template/fixtures`. Після phase 2 concern-split фікстура `default.conf.template` переїхала всередину концерну `template/`, тест падав з `ENOENT no such file or directory`.

## [1.11.6] - 2026-05-15

### Changed

- **`npm/rules/npm-module/npm-module.mdc`** — переформульовано вимогу про тести й фікстури. Раніше правило вимагало тримати їх **поза** будь-яким шляхом з `"files"` (канонічно — у `npm/tests/`). Тепер тести/фікстури можуть лежати **поруч з кодом** усередині `"files"`-шляхів, але `"files"` обовʼязково має містити **негативні glob-патерни**, що виключають їх із tarball (`!**/*.test.*`, `!**/*.spec.*`, `!**/test-helpers.*`, `!**/fixtures/**`, `!**/__tests__/**`, опційно `!**/*_test.rego`). Це краще відповідає реальному layout пакета (co-located test-файли у `rules/<id>/js/<concern>/`) і прибирає роз'їзд правила з фактичним `npm/package.json`. Версію `.mdc` піднято до `1.12`.
- **`npm/rules/npm-module/js/package_structure/check.mjs::checkNoTestsInPublishedFiles`** — текст fail-повідомлення тепер однозначно радить додати негативний glob у `"files"`, без альтернативи «винеси за межі шляхів з "files"». Логіка перевірки (walk positive ∖ negative + класифікація test-style) не змінилась — пере-кваліфіковано лише підказку для агента й людини.
- **`npm/skills/fix/SKILL.md`** (+`.cursor/skills/n-fix/SKILL.md` синк), **`npm/rules/style-lint/style-lint.mdc`** — розмежовано ролі скілів: `/n-fix` відповідає за **структуру** проєкту (правила `.cursor/rules/` + `npx @nitra/cursor fix`), `/n-lint` — за **чистоту коду** (`bun run lint`). Крок 6 у `n-fix` (перебір `lint-js`/`lint-text`/`lint-style`) замінено на одиничний `bun run lint` з делегуванням до `/n-lint` — лінт-логіку (auto-fix, sonarjs-рефакторинг, заборона паралельних запусків ESLint) `n-fix` більше не дублює. У `style-lint.mdc` cross-reference «повний набір `lint-*` (навичка `n-fix`)» оновлено на «`bun run lint` (навичка `/n-lint`)».

## [1.11.5] - 2026-05-15

### Added

- **`npm/bin/n-cursor.js`** — підкоманди `lint-rego`, `lint-k8s`, `lint-docker`, `lint-text` (раніше був лише `lint-ga`). Споживчі `package.json` тепер можуть використовувати уніфіковану форму `n-cursor lint-X` замість прямих посилань на файли `bun ./npm/scripts/*.mjs`, які після phase 2 концерн-сплету переїхали у `npm/rules/<id>/js/`. `lint-text` — композитний: послідовно `cspell .` → `runShellcheckText()` → `bunx markdownlint-cli2 --fix "**/*.md" "**/*.mdc"` → `runV8rWithGlobs()`.
- **`npm/rules/text/js/lint.mjs`** — новий ентрі-модуль для канонічного `lint-text`.
- **`npm/scripts/utils/run-lint-step.mjs`** — спільний хелпер `runLintStep(title, cmd, args)` для CLI-обгорток `lint-<rule>` (раніше дублювалося у `rules/ga/js/lint.mjs` і новому `rules/text/js/lint.mjs` — jscpd-clone).

### Changed

- **`npm/rules/k8s/js/run.mjs`**, **`npm/rules/docker/js/run.mjs`** — `main()` перейменовано і експортовано як `runLintK8s` / `runLintDocker` для виклику з CLI-маршрутизатора. `isRunAsCli()`-гілка прямого запуску збережена для зворотної сумісності.
- **`npm/rules/rego/js/lint.mjs`** — авто-виклик `runLintRego()` тепер обгорнуто `if (isRunAsCli())`, інакше імпорт модуля з CLI запускав би лінт як side-effect.
- **`npm/rules/rego/policy/package_json/`** — канонічне значення `scripts.lint-rego` змінено на `"n-cursor lint-rego"` (раніше `"bun ./npm/scripts/lint-rego.mjs"`). Аналогічно `npm/rules/docker/policy/package_json/` — на `"n-cursor lint-docker"`.
- **`npm/rules/text/js/formatting/check.mjs`** — `checkLintTextScript()` тепер вимагає рівно `"n-cursor lint-text"` замість попередньої складної валідації багатоступеневого ланцюжка (`cspell` → `run-shellcheck-text.mjs` → `markdownlint-cli2` → `run-v8r.mjs`). Канонічна форма — одне посилання на CLI, а зміст ланцюжка живе у `npm/rules/text/js/lint.mjs`.
- **`npm/package.json#files`** — додано негативний glob `"!**/*_test.rego"` (раніше були лише `*.test.mjs`, `test-helpers.mjs`, `fixtures/**`). 33 rego-юніт-тестових файли (`<policy>_test.rego`) більше не потрапляють у tarball — їх виконує лише `conftest verify` у dev-репо.

## [1.11.4] - 2026-05-15

### Fixed

- **`npm/rules/nginx-default-tpl/js/template/check.mjs`** — `findDefaultConfTemplatePaths` пропускає тестові `fixtures/` за будь-яким сегментом шляху, не лише `tests/fixtures/`. Після concern-split (фази 1-4) fixtures лежать у `rules/<rule>/js/<concern>/fixtures/`, і старий патерн пропускав їх повз → check шукав Dockerfile поруч із тестовим шаблоном і фалс-фейлив.

## [1.11.3] - 2026-05-15

### Fixed

- **`npm/bin/n-cursor.js`** — `detectAutoSkills` тепер отримує **ефективний** список правил (опт-ін вручну з `.n-cursor.json:rules` ∪ auto-detected, мінус `disable-rules`), а не лише auto-detected. Без цього скіли із залежністю на правило, додане вручну (наприклад, `adr` без `auto.md`-умови), не активувалися — у репо з `"rules": ["adr", …]` скіл `adr-normalize` залишався відсутнім, попри `[adr]` у його `skills/adr-normalize/auto.md`. Тепер `adr-normalize`, `abie-clean`, `abie-kustomize`, `taze` авто-додаються коректно як при auto-detected, так і при manual-opt-in відповідних правил.

## [1.11.2] - 2026-05-15

### Fixed

- **`npm/scripts/auto-skills.mjs`** — джерело правди для автоактивації скілів тепер `skills/<skill>/auto.md`, а не hardcoded мапа в JS. Парсер розпізнає три формати: `завжди` (always-on), `[rule, rule, …]` (умова на правила), відсутній/нерозпізнаний файл (opt-in). Експортовані константи `AUTO_SKILL_ORDER` та `AUTO_SKILL_RULE_DEPENDENCIES` тепер похідні від сканування `npm/skills/` під час завантаження модуля (зберігаються для зворотної сумісності). Побічно виправлено пропуск `abie-clean` у hardcoded мапі попри `[abie]` у його `auto.md` — тепер скіл коректно автоактивується разом з правилом `abie`.

## [1.11.1] - 2026-05-15

### Fixed

- **`npm/bin/n-cursor.js`** — `runSync()` (entry для `npx @nitra/cursor` без аргументів) шукав
  `<packageRoot>/mdc` після того, як phase 1-4 перейменував каталог у `rules/`. Виправлено: тепер
  вказує на коректний шлях `<packageRoot>/rules` — більше не кидає «Не знайдено каталог правил пакету».

## [1.11.0] - 2026-05-15

### Added

- **Concern-based JS + per-policy `target.json`** — нова інфраструктура для CLI `check`:
  - `npm/rules/<id>/js/<concern>/check*.mjs` — JS-концерни замість одного плаского `js/check.mjs`. Дзеркалить `policy/<name>/`: один `<name>` = одна одиниця відповідальності (rego, JS, або hybrid).
  - `npm/rules/<id>/policy/<name>/target.json` — декларативний маніфест поруч із `<name>.rego` описує, які файли фідити в conftest (`{ "files": { "single": "..." | "walkGlob": [...] } }`). CLI читає сам і викликає `runConftestBatch` — JS не зобовʼязаний дублювати.
  - **Pure-rego правила** працюють без жодного `.mjs`: CLI знаходить полісі за `target.json` і прогонить їх через `runConftestBatch`.
  - **Applies-гейт**: `rules/<id>/js/applies/check.mjs` може експортувати `applies()`. Якщо повертає `false` — CLI пропускає правило цілком (включно з policy-концернами).
  - **JSON Schema** у `npm/schemas/target.json` для IDE-валідації `target.json`.
  - **picomatch@^4.0.4** — runtime dependency для `walkGlob`-резолверу.
- **Нові утиліти** в `npm/scripts/utils/`:
  - `discover-checkable-rules.mjs` — обхід `rules/`, повертає `{ id, jsConcerns, policyConcerns }[]`. Legacy-fallback: плаский `js/check.mjs` маппиться у концерн `legacy`, щоб не ламати ще не мігровані правила під час переходу.
  - `resolve-target-files.mjs` — резолвер `files.single` / `files.walkGlob` з спільним walk-кешем на check-прогон (повторні таргети з тим самим `ignorePaths` не роблять додаткового `walkDir`). Path-traversal у `single` блокується.
  - `run-rule.mjs` — оркестратор одного правила: applies-гейт → JS-концерни → policy-концерни. Exit-код агрегується OR-ом.

### Changed

- **`npm/bin/n-cursor.js`** — `discoverCheckScripts` тепер делегує у `discoverCheckableRules` і повертає `CheckableRule[]` замість `string[]`. `runChecks` запускає `runRule` для кожного id зі shared `walkCache` (один обхід дерева на унікальний `ignorePaths`-сигнатуру).
- **`npm/rules/rego/`** — пілот міграції на нову структуру:
  - `js/check.mjs` → `js/applies/check.mjs` з експортами `applies()` (gate за наявністю `*.rego` у дереві) і `check()` (короткий context-pass). Виклики `runConftestBatch` прибрано — їх тепер виконує CLI через `target.json`.
  - `policy/{package_json,vscode_extensions,vscode_settings}/target.json` додано (`single` + `required: true` + кастомні `missingMessage`).
- **`npm/package.json`** — додано `picomatch` у `dependencies`.

- **Правило `adr` — фаза 2 (Normalize).** Новий Stop-hook `.claude/hooks/normalize-decisions.sh` батчево нормалізує ADR-чернетки через LLM: коли кількість файлів з `session:` у frontmatter досягає `ADR_NORMALIZE_THRESHOLD` (default 30), бере до `ADR_NORMALIZE_BATCH` найстарших, отримує JSON-операції (`rewrite` / `delete` / `merge-into`) і застосовує їх до робочого дерева. **Жодних git-операцій** — розробник дивиться `git status`/`git diff` і вирішує сам. Recursion guard `ADR_NORMALIZE_RUNNING=1`, мінімальний інтервал між спробами `ADR_NORMALIZE_MIN_INTERVAL_HOURS=6`, lock-файл, skip при mid-rebase/mid-merge, `ADR_NORMALIZE_DRY=1` для dry-run. Slug-стиль — kebab-case українською; дата у фінальному ADR береться з `captured` чернетки.
- **Skill `adr-normalize`** (slash-команда `/n-adr-normalize`) — ручний запуск normalize поза порогом і поза Stop-hook (виставляє `ADR_NORMALIZE_THRESHOLD=0` і `ADR_NORMALIZE_MIN_INTERVAL_HOURS=0`, корисно для dry-run або разової чистки). Авто-додається при `adr` у `rules`.
- **`sync-claude-config`**: експортовано `ADR_NORMALIZE_HOOK_COMMAND_MARKER` і функцію `syncAdrNormalizeHookScript`; managed-група normalize додається до `hooks.Stop` поряд з capture-групою (`async: true`, `timeout: 600`) при `"adr"` у `rules`. Маркер `.claude/hooks/normalize-decisions.sh` додано в `MANAGED_HOOK_COMMAND_MARKERS`.
- **Rego-перевірка `adr.settings_json`** тепер вимагає Stop-hook групу і для capture, і для normalize; **`adr.settings_local_json`** забороняє дублі обох хуків.
- **Інкрементальна міграція правил на `target.json`** (декларативні маніфести поруч із кожним `<concern>.rego`):
  - **Single-file правила** (11): `bun`, `text`, `style-lint`, `php`, `docker`, `npm-module`, `js-lint`, `image-compress`, `capacitor`, `hasura`, `adr` — `target.json` з `single` для кожного канонічного конфіг-файлу. Сумарно 27 нових маніфестів. `bun.bunfig`, `text.cspell`, `npm_module.npm_publish_yml` тощо тепер прогоняються через CLI `check <id>` без додаткового `bun run lint-conftest`.
  - **Walk-glob правила** (6): `js-mssql`, `js-bun-db`, `js-bun-redis`, `js-run` (package_json + configmap), `vue`, `image-avif` — `walkGlob: "**/package.json"` або відповідний патерн.
  - **k8s.\* концерни** (8): `manifest`, `gateway`, `hpa_pdb`, `kustomization`, `svc_yaml`, `svc_hl_yaml`, `base_kustomization`, `base_manifest` — `walkGlob` по YAML під сегментом `k8s/`; `base_manifest` використовує негативний glob для виключення `kustomization.yaml`.
  - **abie концерни** (4): `clean_merged_ignore_branches` (single), `health_check_policy` (walkGlob `**/k8s/**/hc.yaml`), `http_route_base` (walkGlob `**/k8s/**/base/**/hr.yaml`), `base_deployment_preem` (walkGlob `**/k8s/**/base/**/*.{yaml,yml}` з виключенням `kustomization.yaml`).
- **`capture-decisions.sh` тепер пише чернетки напряму в `docs/adr/<timestamp>-<sid>.md`** (раніше — у `docs/adr/_inbox/`). Сам каталог `_inbox/` більше не створюється, але `normalize-decisions.sh` бачить його рекурсивно — старі чернетки з `_inbox/` поступово розчищаються нормалізацією. Можна також одноразово `git mv docs/adr/_inbox/*.md docs/adr/` і прибрати порожній каталог.
- **Правило `adr` (`npm/rules/adr/adr.mdc`)**: повне переписування під дві фази (capture + normalize). Видалено згадки `_inbox/`. Версія `version: '2.0'`.
- **`npm/rules/adr/js/check.mjs`**: перевірка обох hook-скриптів (canonicity), обох log-файлів у `.gitignore`.
- **`npm/scripts/lint-conftest.mjs`**: повне переписування. Замість hardcoded `TARGETS`-таблиці (~280 рядків з регексами та walker-преди­катами) скрипт викликає `discoverCheckableRules` і читає `target.json` для кожного policy-концерну. Файл-резолвер — спільний з CLI `check` (`resolveTargetFiles` + walk-кеш). Поведінка для користувача однакова (`stdio: 'inherit'` зберігається для рідного форматування conftest), але джерело правди тепер — `target.json` поруч із `.rego`. Скрипт став `async` (для `await readFile`/`discoverCheckableRules`).
- **`npm/rules/abie/`** — завершено concern-split:
  - `js/check.mjs` (1153 рядки) видалено, його логіка розпорошена по `js/{applies,firebase_hosting,hc_pairing,ua_node_selector,ua_http_route,env_dns}/check.mjs`.
  - Спільні стан і утиліти у `utils/{enabled,k8s-tree,overlay-paths,kustomization-patches,http-route,hc-yaml,env-dns,yaml}.mjs`. `k8s-tree.mjs` тримає module-level кеш `findK8sYamlFiles` + `collectDeploymentDirs` — повторні виклики з різних концернів не роблять нового обходу дерева.
  - Виклики `runConftestBatch` прибрано з JS — їх тепер виконує CLI через `target.json`.

### Fixed

- `npm/rules/adr/js/check.{mjs,test.mjs}`: виправлено `BUNDLED_HOOKS_DIR` (після phase 3 co-location шлях `'..'` указував у `npm/rules/adr/.claude-template/`, потрібно `'../../..'` — до `npm/.claude-template/`).
- **`scripts/utils/run-rule.mjs`**: kebab-id правила (`style-lint`, `image-compress`, `js-lint`, `npm-module`, `js-mssql`, `js-bun-db`, `js-bun-redis`, `js-run`, `image-avif`, `nginx-default-tpl`) тепер коректно мапиться у snake-namespace rego (`style_lint.<concern>` тощо). Раніше `namespace: <id>.<concern>` давав `style-lint.package_json`, що не збігалося з `package style_lint.package_json` у `.rego` → conftest повертав 0 violations попри реальні порушення.
- **`scripts/utils/resolve-target-files.mjs`**: виправлено інтерпретацію негативних glob-патернів. `picomatch(['pos', '!neg'])` за дефолтом трактує `!neg` як окремий позитивний матчер «не-neg» (OR-логіка), що матчило майже всі шляхи. Тепер позитивні/негативні розділяються вручну, негативи застосовуються через `!isExcluded`. Виправляє `k8s.base_manifest`-таргет, який мав виключати `kustomization.yaml`, але до фіксу матчив усе дерево.
- **`scripts/utils/discover-checkable-rules.mjs`**: коли правило має й плаский `js/check.mjs`, і concern-підкаталоги, CLI прогонить **тільки** концерни. Раніше додавалися обидва → дублюючий вивід для правил у стані часткової міграції.

## [1.9.23] - 2026-05-14

### Fixed

- `npm/package.json#files`: додано негативні glob-патерни `!**/*.test.mjs`, `!**/test-helpers.mjs`, `!**/fixtures/**`, щоб після переїзду тестів у `rules/<rule>/js/`, `scripts/`, `scripts/utils/` вони не потрапляли в опубліковану npm-tarball (вимагає правило `npm-module`).
- `npm/package.json#devDependencies`: додано `@nitra/cursor: ^1.9.22` (auto-fill від `ensure-nitra-cursor-dev-dependencies.mjs`).

## [1.9.22] - 2026-05-14

### Changed

- **Rule-centric структура пакета.** Кожне правило тепер живе в одній директорії `npm/rules/{rule}/` з усіма своїми артефактами: `{rule}.mdc`, `auto.md` (умова автоактивації), `policy/` (rego-поліси), `js/` (check.mjs + опційні run/lint + co-located \*.test.mjs + fixtures/). Видалив каталог правила — правило зникло без слідів у `bin/auto-rules.md`, `npm/policy/`, `npm/scripts/`. Дзеркальна структура для скілів у `npm/skills/{skill}/` (SKILL.md + auto.md + js/).
- **Тести співрозташовуються з джерелами.** ~50 файлів з `npm/tests/` переїхали в `npm/rules/{rule}/js/*.test.mjs` (тести правил), `npm/scripts/*.test.mjs` (тести інфраструктури), `npm/scripts/utils/*.test.mjs` (тести утиліт). `tests/helpers.mjs` → `scripts/utils/test-helpers.mjs`. `npm/tests/` залишається тільки для 3 крос-правильних інтеграційних тестів.
- **`bin/n-cursor.js`**: `BUNDLED_MDC_DIR` → `BUNDLED_RULES_DIR`. `discoverBundledRuleNames` і `discoverCheckScripts` тепер обходять підкаталоги `rules/` замість файлів у `mdc/` чи `check-*.mjs` у `scripts/`. Резолвер check-скриптів: `rules/{rule}/js/check.mjs`. `readBundledRuleContent` читає `rules/{rule}/{rule}.mdc`.
- **`scripts/utils/run-conftest-batch.mjs` та `scripts/lint-conftest.mjs`**: шляхи до rego-полісі — `rules/{rule}/policy/{name}/` (замість `policy/{rule}/{name}/`). Snake_case `policyDirRel` у JS-call sites замінено на kebab-case.
- **`npm/package.json#files`**: `mdc` і `policy` видалено, додано `rules`. `scripts.test`: `bun test tests` → `bun test` (рекурсивний пошук `*.test.mjs`).
- **`.cursor/rules/scripts.mdc`** (v1.5): додано секцію «Структура правила» з документацією rule-centric layout для майбутніх правил. Path-references у `npm/CLAUDE.md` оновлено.

### Removed

- `npm/mdc/` (24 файли) — вміст переїхав у `npm/rules/{rule}/{rule}.mdc`.
- `npm/policy/` (24 каталоги) — вміст переїхав у `npm/rules/{rule}/policy/`.
- `npm/bin/auto-rules.md`, `npm/bin/auto-skills.md` — замінено на per-rule і per-skill `auto.md` в кожному каталозі.

## [1.9.20] - 2026-05-14

### Added

- **`check-rego.mjs` orchestrator + 3 rego-полісі для `rego.mdc`:**
  - JS gate у `npm/scripts/check-rego.mjs`: walk дерева від `cwd` (з типовими skip-ами і `.n-cursor.json:ignore`); якщо немає жодного `.rego` файла — `pass` (skip) ("rego-tooling не вимагається"). Інакше — FS-existence + content-валідація 3 файлів через `runConftestBatch`.
  - `rego.vscode_extensions` — `recommendations` ∋ `tsandall.opa`.
  - `rego.vscode_settings` — `[rego]` блок з `editor.defaultFormatter: "tsandall.opa"` + `editor.formatOnSave: true`; окремі deny на «не object», «неправильний defaultFormatter», «formatOnSave не true / відсутній».
  - `rego.package_json` — `scripts.lint-rego` присутній і дорівнює `"bun ./npm/scripts/lint-rego.mjs"` (точне значення, з підтримкою whitespace через `trim_space`).
  - +20 rego-тестів (5 + 7 + 8). Глобально у `lint-conftest` НЕ реєструються — це conditional правило, gating через JS.
- **`check-tauri.mjs` orchestrator + 1 rego-полісі для `tauri.mdc`:**
  - JS detector маркера Tauri-проєкту: `src-tauri/` каталог, `tauri.conf.json` у корені, або `@tauri-apps/*` у `dependencies`/`devDependencies` кореневого `package.json`. Якщо немає — `pass` (skip).
  - `tauri.vscode_extensions` — `recommendations` ∋ обидва: `tauri-apps.tauri-vscode` і `rust-lang.rust-analyzer`. Один deny з шаблоном повідомлень + `recommendations_set` поза deny (performance hint).
  - +6 rego-тестів (canonical, додаткові розширення, кожний відсутній маркер окремо, empty, no field).
- **`conftest verify`** — **293/293 pass** (+26).

### Changed

- **CLI auto-discovery** підхоплює `check-rego.mjs` і `check-tauri.mjs` через `discoverCheckScripts()` у `bin/n-cursor.js`. Окрема реєстрація не потрібна — будь-який `check-*.mjs` стає доступним через `npx @nitra/cursor fix <rule>`.

### Verified

- **На цьому репо `npx @nitra/cursor fix rego` детектує реальні гепи у `.vscode/extensions.json` (немає `tsandall.opa`) і `.vscode/settings.json` (немає `[rego]` блока).** Це true-positive: репо має `rego` у `.n-cursor.json:rules` і містить `.rego` файли, тож канонічний tooling-набір вимагається. Фікс — додати entries у `.vscode/*` згідно `rego.mdc`.

### Not migrated (explained)

- **`changelog.mdc` format-валідація** — пропущено: `conftest` не парсить markdown без pre-processing. Структурна валідація формату `## [version] - YYYY-MM-DD` лишається в `check-changelog.mjs` (JS), яке через regex розбирає текст. Перенесення вимагало б pre-processing markdown → JSON у JS перед викликом conftest — додаткова складність без виграшу.
- **`image-compress.mdc`** — вже має повне покриття rego через `image_compress.package_json` (8 deny, тести у [npm/policy/image_compress/package_json/](npm/policy/image_compress/package_json/)). `.gitignore` cross-file checks залишаються в `check-image-compress.mjs` як FS-логіка (rego не вміє).

## [1.9.19] - 2026-05-14

### Removed

- **`abie.mdc` (`1.19 → 1.20`) — повністю прибрано підтримку `ru`-overlay:** видалено секції «overlay **ru** і nginx-sidecar для WebSocket (Hasura)», «overlay **ru** і **Service** (headless → NodePort)», «overlay **ru** і HealthCheckPolicy»; з секцій «HTTPRoute (ua / ru)», «nodeSelector (overlay)», «env-файли», «Git branches» видалено `ru`-гілку. Залишається лише `dev` + `ua`. Таблиця env-файлів — без `ru.env` / `cluster.local` / YC. У workflow `clean-merged-branch.yml` обов'язкові токени `ignore_branches`: `dev,ua` (раніше `dev,ua,ru`).
- **`check-abie.mjs` — drop ru-логіки:** видалено всі функції з суфіксом / префіксом `Ru` (`isRuKustomizationPath`, `serviceDocumentRequiresAbieRuNodePortOverlay`, `ensureRuKustomizationHealthCheckDelete`, `ensureRuAbieServiceNodePortPatches`, `ensureAbieNginxSidecarForHasura` + усі допоміжні), regex / константи для `ru` overlay (`PATCH_PARENT_REF_NS_RU_RE`, `WEBSOCKET_ANNOTATION_RE`, `REMOVE_CLUSTER_IP*_RE`, `HASURA_IMAGE_MARKER`, `NGINX_SIDECAR_*`, `ABIE_RU_HTTPROUTE_HOST_MARKERS`, `HASURA_JWT_SECRET_IN_KUSTOMIZATION`). Перейменування: `ensureUaRuAbieNodeSelectorPatches` → `ensureUaAbieNodeSelectorPatches`, `ensureUaRuAbieHttpRoutePatches` → `ensureUaAbieHttpRoutePatches`. Тип `mode` — лише `'ua'`. Файл скоротився з ≈2013 до ≈880 рядків.
- **`check-k8s.mjs` — drop `ruKustomizationHasHealthCheckDeletePatch`:** export видалено разом з допоміжними regex; решта k8s-логіки без змін.
- **`check-hasura.mjs` — only `<cluster>.internal`:** `INTERNAL_HASURA_URL_RE` більше не приймає `cluster.local`; повідомлення про помилку згадує лише GKE-формат.
- **Rego — `abie.clean_merged_ignore_branches`:** `required_branches := {"dev", "ua"}` (раніше `{"dev", "ua", "ru"}`); тести оновлено.
- **`abie.base_deployment_preem` rego — коментар:** «Overlays (ua/ru)» → «Overlay ua».
- **`.cspell.json`:** зі списку слів прибрано `napitkivmeste` та `выбирайонлайн` (мову `ru-ru` у `language` залишено для коректного спелл-чеку коментарів/документації).
- **`k8s.mdc` приклади:** у переліку overlays залишилось `ua/`, `prod/` без `ru/`.
- **`hasura.mdc` / `tests/check-hasura.test.mjs`:** приклад "неправильного" публічного домену змінено з `napitkivmeste.tech` на `vybeerai.com.ua`.

### Tests

- **`tests/check-abie.test.mjs` — переписано (1210 → ≈480 рядків):** видалено всі тести `ru`-overlay (NodePort Service, HealthCheckPolicy delete, nginx-sidecar, websocket annotation, `ru-apruv` env-URL, ru parentRef regex). Залишено dev/ua сценарії.
- **`tests/check-hasura.test.mjs`:** видалено 2 тести на `cluster.local` / `ru-apruv`.
- **`tests/check-k8s-schema.test.mjs`:** видалено `describe('ruKustomizationHasHealthCheckDeletePatch')` і `isDevLikeK8sEnvSegment('ru')` assertion.
- **`tests/check-k8s-images.test.mjs`:** ASCII-збіг `ru: "true"` як ім'я label у фікстурі перейменовано на `preem: "false"`.

## [1.9.18] - 2026-05-13

### Changed

- **`docker.mdc` (`1.8 → 1.9`) — узгоджено канонічний `lint-docker.yml` з ga.mdc:** видалено 4 застарілих кроки з прикладу workflow (`actions/setup-node@v6` + `oven-sh/setup-bun@v2` + `actions/cache@v5` + `bun install --frozen-lockfile`) і замінено на один `uses: ./.github/actions/setup-bun-deps`. Раніше docker.mdc показував саме той патерн, який `ga.mdc` явно називає «❌ НЕПРАВИЛЬНО» і який `ga.workflow_common.rego` ловить через `forbidden_step_substrings` (3 заборонені підрядки). Конфлікт виник тому, що інші правила (`lint-style.mdc`, `lint-text.mdc`, `lint-js.mdc`) уже мігровано на composite, а docker.mdc пропустили. Hadolint-install залишається як окремий `curl`-крок (так само як `Install conftest` у `lint-ga.yml`).

### Added

- **`docker.package_json` rego — канонічний `scripts.lint-docker`:** deny, якщо ключ `scripts.lint-docker` присутній, але його значення ≠ `"bun ./npm/scripts/run-docker.mjs"`. Умовну обовʼязковість (правило `docker` у `.n-cursor.json` → `scripts.lint-docker` ЗОБОВ'ЯЗАНИЙ існувати) перевіряє `check-bun.mjs` cross-file, тут rego видно лише один документ. +6 тестів через `json.patch`-фікстури (canonical / lint-docker absent / whitespace / wrong value).
- **`docker.lint_docker_yml` rego — структура `.github/workflows/lint-docker.yml`:** 4 deny — (1) `on.push.paths` має містити 3 канонічні glob-и (`**/Dockerfile`, `**/*.Dockerfile`, `**/*.dockerfile`); (2) у `run:` будь-якого кроку має бути URL з версією `v2.12.0` (узгоджено з `HADOLINT_IMAGE` у `npm/scripts/utils/docker-hadolint.mjs`); (3) у `uses:` має бути `./.github/actions/setup-bun-deps` (canonical composite per ga.mdc); (4) у `run:` має бути `bun run lint-docker`. +9 тестів через `json.patch`-фікстури.
- **`lint-conftest.mjs` TARGETS — два нові entry:** `docker.package_json` (single: `package.json`) і `docker.lint_docker_yml` (single: `.github/workflows/lint-docker.yml`), обидва з `rule: 'docker'`. Цей репо (cursor) не має `docker` у `.n-cursor.json:rules` → docker таргети тут не активуються; полісі діятиме на проєкти-споживачі.
- **15 нових rego-тестів** (6 + 9), `conftest verify` — **267/267 pass** (+15).

### Regal fixes during migration

- `idiomatic/prefer-set-or-object-rule` у `lint_docker_yml.rego`: `all_step_uses := {u | …}` (comprehension) → `all_step_uses contains u if { … }` (incremental set rule).
- `style/line-length` × 3: винесено довгі hadolint-URL і шаблон повідомлення у проміжні константи через `concat`.

## [1.9.17] - 2026-05-13

### Added

- **2 нові rego-полісі для text.mdc VSCode-канону** (мігровано з `check-text.mjs`):
  - `text.vscode_extensions` — `recommendations` має містити три розширення: `DavidAnson.vscode-markdownlint`, `oxc.oxc-vscode`, `timonwong.shellcheck`. Шаблон повідомлень + множина `recommendations_set` (винесена поза `deny`, щоб не порушити `performance/non-loop-expression`).
  - `text.vscode_settings` — `editor.formatOnSave: true` плюс шість мов-блоків (`[javascript]`/`[typescript]`/`[json]`/`[vue]`/`[css]`/`[html]`) з `editor.defaultFormatter: "oxc.oxc-vscode"`. Окремі deny для «не object» і «неправильний defaultFormatter». Канон задає мінімум — додаткові lang-блоки дозволені.
- **18 нових rego-тестів** (7 для `vscode_extensions` + 11 для `vscode_settings`): happy path, додаткові поля, відсутність кожного розширення, відсутність `formatOnSave`, неправильний defaultFormatter, відсутні lang-блоки. `conftest verify` — **252/252 pass** (+18).
- **`lint-conftest.mjs` TARGETS — два нові entry для text:** `text.vscode_extensions` (`single: '.vscode/extensions.json'`) і `text.vscode_settings` (`single: '.vscode/settings.json'`), обидва з `rule: 'text'`. Глобально активуються для всіх проєктів з `text` у `.n-cursor.json:rules`.

### Removed

- **`check-text.mjs::checkVscodeTextExtensions` / `checkVscodeTextSettings` / `checkVscodeText`** — три JS-функції видалено разом з викликом `await checkVscodeText(pass, fail)` у `check()`. Зміст delegated у rego (`text.vscode_extensions` + `text.vscode_settings`).

### Changed

- **`check-text.mjs::checkTextConfigsExistence` — розширено двома записами:** тепер вимагає FS-існування `.vscode/extensions.json` і `.vscode/settings.json` поряд з `.oxfmtrc.json` / `.cspell.json` / `.markdownlint-cli2.jsonc`. lint-conftest з rego skip-ить неіснуючі файли, тому FS-existence лишається в JS — це працює як «єдина точка контролю наявності файлу + delegated content-валідація у rego».

## [1.9.16] - 2026-05-13

### Added

- **5 нових rego-полісі для `.vscode/extensions.json` / `.vscode/settings.json`** (мігровано канон з .mdc у rego, прибрано JS-дублі):
  - `style_lint.vscode_extensions` — `recommendations` має містити `stylelint.vscode-stylelint` (style-lint.mdc).
  - `style_lint.vscode_settings` — `css.validate` / `scss.validate` / `less.validate: false`; `editor.codeActionsOnSave` свідомо не enforced (smell-test, мдс показує як рекомендацію).
  - `graphql.vscode_extensions` — `recommendations` має містити `graphql.vscode-graphql` (graphql.mdc). НЕ реєструється глобально у `lint-conftest` TARGETS — правило conditional на наявність `gql\`…\``у джерелах; викликається з`check-graphql.mjs`через`runConftestBatch` після gql-scan.
  - `nginx_default_tpl.vscode_extensions` — `recommendations` має містити `ahmadalli.vscode-nginx-conf` (nginx-default-tpl.mdc).
  - `nginx_default_tpl.vscode_settings` — `editor.formatOnSave: true` і `[nginx].editor.defaultFormatter: "ahmadalli.vscode-nginx-conf"`. Обидва nginx-полісі викликаються з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише після виявлення `default.conf.template`.
- **28 нових тестів** до пʼяти полісі: 5 (style_lint.vscode_extensions) + 6 (style_lint.vscode_settings) + 5 (graphql.vscode_extensions) + 5 (nginx_default_tpl.vscode_extensions) + 7 (nginx_default_tpl.vscode_settings). `conftest verify` — **234/234 pass** (+28).

### Removed

- **`check-style-lint.mjs::checkVscodeStylelint`** — функція повністю видалена; зміст delegated у `style_lint.vscode_extensions` і `style_lint.vscode_settings`. JSDoc-преамбулу оновлено.
- **`check-graphql.mjs::checkExtensionsRecommendation` — JS-копія тіла перевірки видалена:** функція тепер є тонкою обгорткою над `runConftestBatch`, делегує `graphql.vscode_extensions`. Зник дубль JSON-парсингу й порівняння `recommendations`.
- **`check-nginx-default-tpl.mjs::checkVscodeNginx` — JS-копія тіла перевірки видалена:** функція тепер делегує `nginx_default_tpl.vscode_extensions` і `nginx_default_tpl.vscode_settings` через `runConftestBatch`. Зник дубль перевірок `editor.formatOnSave` і `[nginx].editor.defaultFormatter` у JS.

### Changed

- **`lint-conftest.mjs` TARGETS — два нові глобальні entry для style-lint:** `style_lint.vscode_extensions` (`single: .vscode/extensions.json`) і `style_lint.vscode_settings` (`single: .vscode/settings.json`), обидва з `rule: 'style-lint'`. Не-style-lint проєкти не зачіпають (filter по `activeRules` з `.n-cursor.json`).
- **graphql/nginx — НЕ реєструються глобально у `lint-conftest`:** правила conditional на per-package умовах, які lint-conftest не вміє виразити (`gql\`…\``у джерелах для graphql; наявність`default.conf.template`для nginx). Plan B: rego-authoritative + JS-orchestrator з`runConftestBatch`.

## [1.9.15] - 2026-05-13

### Added

- **`npm/policy/js_run/jsconfig/jsconfig_test.rego` — 12 нових тестів для канону `jsconfig.json`:** rego-полісі `js_run.jsconfig` (canonical compilerOptions — `lib: ["esnext"]`, `module/moduleResolution: NodeNext`, `target: esnext`, `checkJs: false`, `include: ["src/**/*"]`) існувала, але не мала тестів і не запускалась на реальних файлах. Додано happy path + 11 негативних кейсів через `json.patch`-фікстури.
- **`npm/policy/image_avif/package_json/` — структурна валідація опт-аут конфігу:** новий rego-пакет `image_avif.package_json` з 3 deny-правилами для `package.json`: значення `"@nitra/minify-image"` має бути обʼєктом (якщо присутнє), `disable-avif` має бути boolean (якщо присутнє), захист від typo `disabled-avif`. Поле опційне — більшість проєктів його не мають, deny спрацьовує лише на нелегітимну форму (typo або wrong type, що тихо ламає опт-аут). +11 тестів. Зареєстровано у `lint-conftest.mjs` TARGETS з `walk` по всіх `package.json` (з фільтром `rule: "image-avif"`).

### Changed

- **`check-js-run.mjs::checkBackendJsconfigWhenSrcPresent` — структуру `jsconfig.json` тепер валідує rego через `runConftestBatch`:** замість FS-existence-only + посилання на `lint-conftest` (яке насправді не запускалось — rego не була зареєстрована глобально), JS тепер викликає rego-пакет `js_run.jsconfig` через `runConftestBatch` після того, як визначить, що пакет — backend (без `vite` у `devDependencies`) з каталогом `src/`. Це Plan B: Rego-authoritative + JS-orchestrator. Глобальна реєстрація `js_run.jsconfig` у `lint-conftest.mjs` свідомо не додавалась — rule стосується лише workspace-пакетів певної форми, що lint-conftest filter (`activeRules` на рівні репо) не вміє виразити.

### Not done (Phase 1.5 — пізніше)

- **`rego.mdc`, `tauri.mdc`** — rego-полісі для канонічних `.vscode/extensions.json` / `.vscode/settings.json` потрібен JS-orchestrator. Ці правила conditional (rego — glob `**/*.rego`, tauri — лише Tauri-проєкти), тож запускати rego безумовно на кожний `.vscode/extensions.json` дало б false-positive порушення для всіх не-rego/не-tauri проєктів. Чисте розширення rego-полісі без `check-<rule>.mjs`-orchestrator-а тут не закриває правило.

## [1.9.14] - 2026-05-13

### Added

- **`text.markdownlint` rego — повний канон `.markdownlint-cli2.jsonc` тепер виноситься як deny:** раніше rego-полісі мала **рівно один** deny (`gitignore == true`), а решта канонічного блока з [text.mdc](mdc/text.mdc) (`config.default == true`, `MD013 == false`, `MD024.siblings_only == true`, `MD029 == false`, `MD040 == false`, `MD041 == false`) була показана як приклад, але не перевірялась. Додано 6 нових deny-правил, що покривають кожне поле канону; додаткові поля верхнього рівня (`ignores`) і додаткові MD-rules (`MD033` тощо) дозволені — канон задає мінімум. Шаблон повідомлень — через `concat` для regal style/line-length.
- **`npm/policy/text/markdownlint/markdownlint_test.rego` — 14 нових тестів:** happy path (канонічний `.markdownlint-cli2.jsonc`), дозволені розширення (`ignores`, `MD033`), порушення для `gitignore` (відсутній / `false`), `config.default` (відсутній / `false`), `MD013/029/040/041` (`true` або відсутній), `MD024` (не object / `siblings_only: false` / відсутній). `conftest verify` — **183/183 pass** (+14). `lint-conftest` на реальному `.markdownlint-cli2.jsonc` репо: 5/5 (раніше було 1/1 — додано 4 нові тестові кейси проти реального файлу).

## [1.9.13] - 2026-05-13

### Removed

- **`check-bun.mjs::isAllowedRootDevDependency` — видалено JS-копію, дубль rego:** функція експортувалася лише для тестів, у `check()` не викликалась; логіка «дозволено лише `@nitra/*` у кореневих `devDependencies`» давно живе у `npm/policy/bun/package_json/package_json.rego` (`not startswith(name, "@nitra/")`). Docstring помилково посилався на `check-text.mjs`, який цю функцію не імпортує. Тепер єдине джерело — rego; у `check-bun.mjs` додано коментар з посиланням на полісі.

### Added

- **`npm/policy/bun/package_json/package_json_test.rego` — rego-тести для bun.package_json:** 12 нових `test_*`-кейсів через `json.patch`-фікстури — happy path (без `devDependencies`, з кількома `@nitra/*`), 4 негативні `devDependencies` (`@cspell/dict-uk-ua`, `@cspell/cspell-lib`, `lodash`, `@types/node`), mixed-devDeps з конкретним повідомленням про `lodash`, заборона `packageManager`, заборона кореневих `dependencies` (порожній обʼєкт теж), агрегований `lint`-скрипт (відсутній / не покриває `bun run` / без `&& oxfmt .`). Покривається `bun run lint-rego` (169/169 pass).

### Changed

- **`npm/tests/check-bun.test.mjs` — прибрано `describe('isAllowedRootDevDependency')`:** імпорт і блок тестів видалено; залишено інтеграційні `check-bun`-тести у тимчасових каталогах (FS / cross-file частина).
- **Аудит інших `check-*.mjs`**: пройдено всі 22 скрипти на наявність дубля з rego (export не викликається внутрішньо + наявне `npm/policy/<rule>/<name>/<name>.rego`). Знайдено лише цей кейс; `httpRouteMatchesNginxDefaultTpl` у `check-nginx-default-tpl.mjs` залишається — не має rego-counterpart і свідомо лишений для майбутнього використання згідно docstring. Інші експорти або викликаються у відповідному `check()` / приватних helper-ах, або не мають rego-копії.

## [1.9.12] - 2026-05-13

### Removed

- **`check-js-lint.mjs` — видалено дубльовані JS-копії канону `lint-js`:** експорти `CANONICAL_LINT_JS`, `isCanonicalLintJs`, `normalizeLintJsScript`, `nitraEslintConfigMeetsMinVersion` і константу `WHITESPACE_RE` видалено. Ці функції експортувалися, але **не використовувалися** у `check()` — пер-документна перевірка кореневого `package.json` (канонічний `lint-js`, `@nitra/eslint-config ≥ 3.9.2`, `type: "module"`, `engines.{node,bun}`) давно мігровано до rego-полісі `npm/policy/js_lint/package_json/`. Залишені експорти створювали два джерела істини: при оновленні канону (`bunx knip --no-config-hints`) доводилось правити і `.rego`, і `.mjs`-константу, що відкриває дрифт. Тепер канон лише в rego, JS-копії немає. У `check-js-lint.mjs` додано коментар з посиланням на rego-полісі.

### Added

- **`npm/policy/js_lint/package_json/package_json_test.rego` — rego-тести для канону `lint-js`:** перенесено покриття з JS-тестів (`normalizeLintJsScript`, `isCanonicalLintJs`, `nitraEslintConfigMeetsMinVersion`) у rego через `json.patch`-фікстури: 16 нових `test_*`-кейсів — happy path, неправильний порядок команд, відсутність `bunx knip`, відсутність `--no-config-hints`, `type` не `module`, `engines.node < 24`, `engines.bun < 1.3`, `@nitra/eslint-config` < 3.9.2, `workspace:*` дозволено. Покривається `bun run lint-rego` (`conftest verify`).

### Changed

- **`npm/tests/check-js-lint.test.mjs` — прибрано тести для видалених JS-копій:** видалено блоки `describe('normalizeLintJsScript / isCanonicalLintJs')` і `describe('nitraEslintConfigMeetsMinVersion')` разом з імпортами. Залишилися тести `verifyOxlintRcAgainstCanonical` (там JS усе ще authoritative — потрібен readFile + рекурсивне порівняння канон-блока).

## [1.9.11] - 2026-05-13

### Changed

- **`lint-js` — `bunx knip --no-config-hints` у каноні (js-lint.mdc `1.20 → 1.21`):** до канонічного `lint-js`-скрипта додано прапор `--no-config-hints`, щоб knip не друкував щоразу інформаційну секцію «Configuration hints» (`Remove from ignoreDependencies/ignoreBinaries`). Hints не впливають на exit code і часто стосуються свідомо доданих ignore (`graphql` як peer-залежність) — щоразу їх читати немає сенсу. Оновлено: `CANONICAL_LINT_JS` у `check-js-lint.mjs`, `canonical_lint_js` у `npm_module_unused.../js_lint.package_json.rego`, приклади `lint-js`-скрипта і CI-блока у `npm/mdc/js-lint.mdc` + дзеркало `.cursor/rules/n-js-lint.mdc`, реальний `package.json#scripts.lint-js` у корені, реальний `.github/workflows/lint-js.yml`. Rego-deny `lint-js.yml: у run немає bunx knip` ловиться через `contains` — новий рядок з прапором проходить як раніше.

## [1.9.10] - 2026-05-13

### Added

- **`npm/scripts/utils/knip-canonical.json` — канонічний baseline `knip.json` для проєктів-споживачів:** покриває типові false-positives `bunx knip` для наших правил — `entry` зі CLI-конфігами (eslint/stylelint/oxlint/jscpd/markdownlint-cli2/commitlint), `project` для `**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}`, `ignore` для `**/__fixtures__/**`, `ignoreDependencies` (`@nitra/cspell-dict`, `/@cspell\/dict-.+/`), `ignoreBinaries` для CLI з канону `bunx`/`npx` (`actionlint`, `cspell`, `depcheck`, `eslint`, `git-ai`, `jscpd`, `markdownlint-cli2`, `oxfmt`, `oxlint`, `shellcheck`, `uvx`, `v8r`, `zizmor`). Структурно поряд з `oxlint-canonical.json` / `oxlint-canonical-skeleton.json` у `npm/scripts/utils/`.
- **`js-lint.mdc` — секція про канонічний `knip.json` (версія `1.19 → 1.20`):** правило тепер вимагає `knip.json` у корені проєкту як стартовий baseline з канонічного файлу пакета. Перевіряється **лише наявність** — зміст подальших модифікацій локально не валідується (`entry` / `project` / `ignore` / `ignoreDependencies` / `ignoreBinaries` дозволені будь-які). Дзеркало — `.cursor/rules/n-js-lint.mdc`. Прибрано згадку про обовʼязковий `ignoreDependencies: ["graphql"]` як зміст-вимогу (тепер це лише стартова рекомендація через канон).
- **`check-js-lint.mjs::checkKnipConfig` — auto-create з канону:** якщо `knip.json` відсутній у корені, чек копіює `KNIP_CANONICAL_JSON_PATH` у `knip.json` і повідомляє pass про створення. Раніше функція падала з fail на відсутність і додатково перевіряла `ignoreDependencies ∋ "graphql"` — обидві перевірки замінено на FS-existence + копію канону (side effect, описано у `js-lint.mdc`).

## [1.9.9] - 2026-05-13

### Changed

- **AGENTS.md — додано `bunx knip` до секції Commands:** `build-agents-commands.mjs` тепер завжди додає рядок `- **knip (невикористані залежності та експорти)**: \`bunx knip\``після`npx @nitra/cursor fix`; оновлено тест (`items.length`3 → 4, перевірка`toContain('bunx knip')`).
- **`knip.json` — `graphql` у `ignoreDependencies`:** повернуто `graphql` до кореневого `ignoreDependencies` (peer-залежність, яку knip фолсово репортить як unused; вимога `js-lint.mdc` / `check-js-lint.mjs`).

## [1.9.8] - 2026-05-12

### Changed

- **Корінь монорепо:** локальне перевизначення `sonarjs/cognitive-complexity` у `eslint.config.js` прибрано — поріг і severity задаються в `@nitra/eslint-config`.

## [1.9.7] - 2026-05-12

### Changed

- **js-lint (mdc v1.18 → v1.19) — `depcheck` мігровано на `knip`:** канонічний `lint-js` тепер `bunx oxlint --fix && bunx eslint --fix . && bunx jscpd . && bunx knip` (раніше — без `bunx knip`); крок `bunx knip` додано і в приклад workflow `lint-js.yml`. У корені має бути `knip.json` з мінімальним `ignoreDependencies: ["graphql"]` (peer-залежність, яку `knip` не розпізнає як використану). Пакет `knip` окремо в `devDependencies` не оголошуй — `bunx` тягне його ad-hoc. `CANONICAL_LINT_JS` у `npm/scripts/check-js-lint.mjs` і `canonical_lint_js` у `npm/policy/js_lint/package_json/package_json.rego` оновлено; додано `checkKnipConfig` (наявність файла + `ignoreDependencies` містить `graphql`) і `deny`-правило у `npm/policy/js_lint/lint_js_yml/` на відсутність `bunx knip` у `run:` кроці lint-js workflow.

- **ga (mdc v1.8 → v1.9) — заборона `depcheck` у workflow-файлах:** додано полісі `ga.workflow_common.deny` на будь-який виклик `depcheck` (через `npx`/`bunx`/`npm exec`/`pnpm exec` чи як standalone-команду) у `run:` кроку `.github/workflows/*.yml`. Перевірка невикористаних залежностей виконується разом з рештою лінтерів у `bun run lint-js` (`bunx knip`), окремий depcheck-крок у workflow зайвий. У `npm/mdc/ga.mdc` додано буліт «`depcheck`: не використовувати» з посиланням на `js-lint.mdc` і `ga.workflow_common`.

- **ci (тільки в цьому репо) — lint-ga встановлює conftest; knip.json налаштовано під монорепо:** `.github/workflows/lint-ga.yml` отримав крок `Install conftest` (curl-витяг релізу), бо `check-ga.mjs::runAllGaRego` ходить у `runConftestBatch` і hard-fail без бінарника. Кореневий `knip.json` розширено `workspaces.npm.entry` (всі CLI/scripts/tests як entry points — інакше knip false-positive репортить їх як unused), `ignoreBinaries` для `cspell`/`oxfmt`/`stylelint`/`vite` (всі через `bunx`/`npx`, не з deps), і `ignoreDependencies` для workspace self-refs. Це налаштування специфічне для цього репо; інші проєкти налаштовують `knip.json` під свою структуру.

### Removed

- **js-run (mdc v1.6 → v1.7) — секцію «depcheck у GitHub Actions з path-фільтром» прибрано:** правило про обовʼязковий `npx depcheck --ignores="graphql,bun"` з `working-directory` у path-scoped workflow більше не діє — `depcheck` повністю мігровано на `knip` (див. js-lint.mdc), окремий крок у per-package workflow не потрібен. Файл `npm/scripts/utils/depcheck-workflow.mjs` видалено. У `npm/scripts/check-js-run.mjs` прибрано `checkDepcheckInWorkflows`, імпорти `findDepcheckViolationsForPackage` / `readAllWorkflowFiles` і параметр `workflows` у `checkWorkspacePackage`. У `npm/tests/check-js-run-fixture.test.mjs` видалено `describe('check-js-run: depcheck у path-scoped workflow', …)` (9 тест-кейсів) і допоміжну `writeRepoWithCronJobAndWorkflow`. У `.github/workflows/npm-publish.yml` прибрано крок `npx depcheck --ignores="graphql,bun,bun:test,@nitra/cursor"` з `working-directory: npm` — `lint-js` workflow покриває цю перевірку через `bunx knip`.

## [1.9.6] - 2026-05-12

### Changed

- **js-lint — ігнорувати `.claude/worktrees/` для jscpd і всіх лінтів:** правило `js-lint.mdc` (v1.18) тепер документує, що каталог `.claude/worktrees/` (робочі копії, які Claude Code створює через superpowers-skill `using-git-worktrees`) має бути виключений з лінт-перевірок. Канонічне місце — `.gitignore` (паралельні воркті — це за визначенням не-комітні робочі копії; `gitignore: true` у `.jscpd.json` уже є, тож запис у `.gitignore` каскадно вимикає сканування). Як страховку на випадок запуску jscpd без `gitignore: true` рекомендовано додати `.claude/worktrees/**` у `ignore` `.jscpd.json` — приклад у правилі оновлено. Без цього `bunx jscpd .` фіксує дзеркальні «клони» між кореневим репо і його worktree-копією у `.claude/worktrees/<name>/…`.

## [1.9.5] - 2026-05-12

### Changed

- **npm-module — компактний пакет: whitelist `files`, без `devDependencies`, тести/фікстури поза опублікованим деревом:** правило `npm-module.mdc` тепер вимагає максимально компактний tarball. (1) Поле `"files"` у `npm/package.json` обовʼязкове як whitelist (без нього npm пакує майже все). (2) `npm/package.json` не повинен містити `devDependencies` — інструментарій для розробки тримаємо у кореневому `package.json` монорепо, щоб `npm install @nitra/<pkg>` не тягнув його кінцевим користувачам. (3) Тести й фікстури не повинні потрапляти у tarball: канонічне місце — `npm/tests/` (не додається до `"files"`); це стосується і test-style каталогів (`tests/`, `__tests__/`, `fixtures/`, `__fixtures__/`, `spec/`, `test/`), і файлів за патернами `*.test.*` / `*.spec.*`, і JS/TS-файлів з імпортами test-фреймворків (`bun:test`, `node:test`, `vitest`, `@jest/globals`, `mocha`, `jest`, `ava`, …). **Виняток — Rego (`*_test.rego`):** за конвенцією conftest юніт-тест лежить поруч з полісі у тому самому `package`, тож rego-тести дозволені всередині опублікованого `policy/`-каталогу і входять у tarball.
- **npm-module — пер-документні deny у rego (Rego-authoritative):** `npm/policy/npm_module/npm_package_json/npm_package_json.rego` розширено двома deny: (а) `"files"` як whitelist обовʼязковий (відсутній / не масив / порожній); (б) `"devDependencies"` мають бути відсутні або порожні. Додано `npm_package_json_test.rego` з happy-path + 7 негативних кейсів (`json.patch` фікстури). Покривається `bun run lint-rego` (`conftest verify`) і `bun run lint-conftest` (батч проти реального `npm/package.json`). Раніше я помилково реалізував ці перевірки у JS — це порушує `.cursor/rules/conftest.mdc` (Rego-default для пер-документних структурних перевірок). Тепер виправлено: JS-функцію `checkPackageCompactness` видалено з `check-npm-module.mjs` разом з виклик-сайтом.
- **npm-module — `check-npm-module.mjs` лишає лише FS/AST-частину:** функція `checkNoTestsInPublishedFiles` визначає позитивні patterns поля `files`, віднімає негативні (підтримка `!…` glob з `*` / `**` / `?`), і для кожного файлу-кандидата ловить test-style ім'я каталога/файлу або імпорт тест-фреймворку через oxc-parser (`module.staticImports` + `require()` + динамічний `import()`). `*_test.rego` свідомо не входить у `TEST_FILE_PATTERNS` — дозволений виняток для conftest-конвенції (юніт-тест поруч з полісі у тому самому `package`).
- **npm/package.json — приведено до правила:** видалено секцію `devDependencies` (`@nitra/cursor` вже є у корені як `workspace:*`). `policy/**/*_test.rego` свідомо лишаються у tarball — як виняток для conftest-конвенції.
- **conftest.mdc + npm/.claude-template/npm-CLAUDE.md — гостріший Rego-first сигнал:** у `.cursor/rules/conftest.mdc` додано STOP-блок перед `Edit` будь-якого `check-<rule>.mjs` (стосується і нових перевірок, і розширення вже існуючих; типовий ляп — `if (pkg.<field>) fail(…)` у JS замість ще одного `deny contains` у відповідному rego-пакеті). Перший пункт алгоритму уточнено прикладом «заборона/наявність ключа верхнього рівня типу `devDependencies` / `scripts.<name>`». У `npm-CLAUDE.md` секцію «Перш ніж писати `check-*.mjs`» переписано у self-check з 3 пунктів і червоним прапором. Регенеровано `npm/CLAUDE.md`.

## [1.9.4] - 2026-05-11

### Removed

- **graphql — вимога `scripts.dump-schema` у `package.json` прибрана:** правило `graphql.mdc` більше не вимагає канонічний скрипт `dump-schema` (раніше — `bunx graphqurl http://localhost:4040/v1/graphql -H 'X-Hasura-Admin-Secret: secret' --introspect > schema.graphql`) у корені проєкту за наявності gql tagged template literals. У `.mdc` відповідну буліт-точку та JSON-фрагмент видалено; фраза про «стандартний спосіб оновлення локальної `schema.graphql`» теж прибрана з підсумкового речення. Каталог `npm/policy/graphql/` (єдиний файл `package_json/package_json.rego` з deny-правилами на відсутність/неканонічний `scripts.dump-schema`) видалено повністю. Запис реєстру `graphql.package_json` (policyDir `graphql`, rule `graphql`, single `package.json`) прибрано з `npm/scripts/lint-conftest.mjs` (заголовок секції перейменовано — `graphql` вилучено). JSDoc-преамбулу `npm/scripts/check-graphql.mjs` оновлено: видалено абзац про rego-порт перевірки `dump-schema` і згадку `scripts.dump-schema` з JSDoc функції `check()`. Сам JS-чек і так не торкався `package.json` — після видалення rego-полісі ніяких runtime-перевірок `dump-schema` не лишається. У кореневому `package.json` репо cursor скрипт `dump-schema` теж видалено, оскільки тримати його як shim без правила немає сенсу.

## [1.9.3] - 2026-05-11

### Fixed

- **k8s — `pathHasK8sSegment` тепер відносно кореня репо; `.github/` явно поза скоупом:** функція `pathHasK8sSegment(filePath)` у `npm/scripts/check-k8s.mjs` та `npm/scripts/run-k8s.mjs` розбивала **абсолютний** шлях і шукала компонент `k8s`. У проєктах, де сам корінь репо називається `k8s/` (напр. `/Users/.../abie/k8s/`), сегмент `k8s` присутній в абсолютному шляху **усіх** файлів — і весь репозиторій, включно з `.github/workflows/*.yml`, потрапляв у `findK8sYamlFiles` як k8s-маніфести, після чого `checkK8sYamlFile` падав на «розширення .yml — перейменуй на .yaml» (територія `ga.mdc`, де канон протилежний). Виправлено: (1) сигнатура тепер `pathHasK8sSegment(filePath, root?)` — коли `root` передано, шлях спершу нормалізується через `node:path` `relative(root, filePath)`, і компоненти беруться **відносно кореня** (порожній relative — це сам root, повертає false); (2) `findK8sYamlFiles` у `check-k8s.mjs` і `check-abie.mjs`, а також `findK8sRoots` у `run-k8s.mjs` тепер передають `root` і додатково мають defense-in-depth ранній `return` для шляхів, що починаються з `.github/`; (3) `k8s.mdc` явно фіксує: правило стосується каталогів `k8s` відносно кореня; `.github/workflows/` і `.github/actions/` — поза скоупом (їх веде `ga.mdc`). Без `root` (юніт-тести з відносним шляхом) функція веде себе як раніше. Додано тести у `tests/check-k8s-schema.test.mjs` (worst-case з префіксом `/home/test/some/k8s/`) і `tests/run-k8s-roots.test.mjs` (інтеграційний — `findK8sRoots` у репі, корінь якого називається `k8s/`).

## [1.9.2] - 2026-05-11

### Changed

- **k8s — modeline `$schema` тепер опційний; `file:…` заборонено як плейсхолдер:** правило `k8s.mdc` уточнено — рядок `# yaml-language-server: $schema=…` обов'язковий **лише** коли для поєднання `apiVersion`/`kind` існує надійна публічна схема (kustomization / yannh / datree CRDs-catalog). Якщо публічної схеми немає, modeline **не додається зовсім** (раніше п. 5 розділу «Визначення схеми YAML» допускав `file:` за узгодженням — це створювало фальшиву видимість валідації, а автовиправлення n-fix залишало плейсхолдер `# yaml-language-server: $schema=file:.`). У `check-k8s.mjs`: (1) файли без modeline більше не падають як «перший рядок має бути коментарем», натомість `pass` із позначкою «без modeline — перевірка $schema пропущена»; (2) `$schema=file:…`тепер реєструється як помилка з підказкою прибрати modeline; (3) modeline нижче першого рядка все ще порушення; (4)`HttpBackendGroup`(Yandex ALB) як виняток без modeline залишається без змін.`lint-k8s`(kubeconform з прапорцем ignore-missing-schemas) продовжує покривати валідацію і для файлів без modeline. JSDoc на початку`check-k8s.mjs` оновлено.

## [1.9.1] - 2026-05-11

### Added

- **rego `k8s.base_kustomization` — defense-in-depth deny на HPA/PDB у `base/kustomization.yaml::resources:`:** додано пер-документне правило, що відмовляє, якщо `resources:` локально містить запис із basename `hpa.yaml`/`pdb.yaml`/`hpa.yml`/`pdb.yml` (у будь-якому підкаталозі). Канон k8s.mdc — HPA/PDB у sibling `components/` (Kustomize Component) і підключаються з overlay. Рекурсивний обхід дерева `resources:`/`components:`/`bases:` (із зануренням у вкладені kustomization.yaml) лишається у JS-оркестраторі `verifyK8sBaseKustomizeHasNoHpaPdb` (потребує fs-доступу). Rego-deny ловить найпоширеніший локальний випадок навіть якщо JS-крок упаде з винятку раніше. 5 нових rego-тестів (`hpa.yaml`/`pdb.yaml`/`hpa.yml` у `resources:`, чистий `resources:`, lookalike basename `myhpa.yaml`); `opa test` зелений (10/10).

### Fixed

- **`check-k8s.mjs`:** додано константу `GATEWAY_API_GROUP_PREFIX = 'gateway.networking.k8s.io/'`. Її відсутність кидала `ReferenceError` у `indexOneK8sYamlForHasuraCanon` (на лінії з `av.startsWith(GATEWAY_API_GROUP_PREFIX)`), яку ловив outer try/catch у `bin/n-cursor.js` і **тихо пропускав** усі наступні JS-валідатори в `check-k8s.mjs::check()` — серед них `validateKustomizeHpaPdbOnlyWithBaseDeployment`, `validateConfigMapNameMatchesDeployment`, `validateDeploymentHpaPdbAndTopology`, `validateProdKustomizationOverrides`. Наслідок у репах споживачів: правило «HPA/PDB заборонені у `k8s/base/`» не спрацьовувало (хоча `verifyK8sBaseKustomizeHasNoHpaPdb` логіку містив правильну), бо exception вилітав раніше за чергу JS-кроків. Rego-крок (`runAllK8sRego`) ішов **до** crash-точки й тому продовжував працювати — пер-документні перевірки залишалися активними, а cross-file JS — ні.

## [1.9.0] - 2026-05-11

### Changed

- **mdc frontmatter — `alwaysApply: false` + `globs` для файлово-чітких правил:** `ga` (`.github/workflows/*.yml`), `vue` (`**/*.vue`), `php` (`**/*.php`), `style-lint` (`**/*.{css,scss,vue}`), `nginx-default-tpl` (`**/default.{conf.template,tpl.conf}`), `image-avif` (`**/*.{png,jpg,jpeg,gif,avif,vue,html}`), `image-compress` (`**/*.{png,jpg,jpeg,gif,svg}`), `changelog` (`**/{CHANGELOG.md,package.json}`), `hasura` (`**/hasura/**,**/*.env`), `graphql` (`**/*.{vue,js,mjs,cjs,ts,tsx,jsx}`). Раніше тільки `docker`, `k8s`, `rego` тримали file-scoped формат; решта вантажилася в контекст Cursor завжди (`alwaysApply: true`). Тепер правило підтягується лише коли в контексті є файл за патерном — менше «шуму» у промптах для несуміжних задач. Версії bump-нуто на патч-крок у кожному `*.mdc`. Проєктно-широкі правила (`bun`, `npm-module`, `ci4`, `text`, `js-lint`) і opt-in (`abie`, `adr`) лишилися `alwaysApply: true` без globs.

## [1.8.229] - 2026-05-11

### Removed

- **k8s / `k8s.kustomize_managed`:** правило «`metadata.namespace` заборонено у YAML, досяжних через граф Kustomize» зняте — воно конфліктувало з `k8s.base_manifest`, який натомість **вимагає** `metadata.namespace` у `…/k8s/base/…` для namespaced kind. Перетин предикатів був порожній, що давало ~50 хибних помилок у канонічних деревах `base + overlays` (adminer, run/nginx, reference-grant, otel, dremio, gateway тощо). Видалено: правило з `mdc/k8s.mdc` (бульйт «Де не дублювати `metadata.namespace`»), rego-полісь `npm/policy/k8s/kustomize_managed/`, JS-helpers `metadataNamespaceForbiddenViolation` і `collectKustomizeManagedRelPaths` разом з відповідними тестами та плумінгом `kustomizeManagedRel` через `runAllK8sRego` / `checkK8sYamlFile`. Логіка `base_manifest` (`metadata.namespace` обов'язковий у `k8s/base/`) лишається; у overlays Kustomize це значення буде перезаписано полем `namespace:` з `kustomization.yaml`.

## [1.8.228] - 2026-05-10

### Changed

- **k8s / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав на початок `check-k8s.mjs::check()` через новий helper `runAllK8sRego` — батч-виклик `runConftestBatch` для 9 пакетів (`k8s.manifest`, `k8s.gateway`, `k8s.hpa_pdb`, `k8s.kustomization`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`, `k8s.base_kustomization`, `k8s.base_manifest`, `k8s.kustomize_managed`). JS у `check-k8s.mjs` робить лише cross-file orchestration + autofix + modeline. Cross-file orchestrators `validateHasuraConfigMapRemoteSchemaPermissions` і `validateHasuraHttpRouteCanon` рефакторнуто: JS відбирає paired-with-Hasura-Deployment файли, далі батч-conftest на `k8s.hasura_configmap`/`k8s.hasura_httproute`. Видалено JS-orchestrator-функції-дублі (≈10 шт): `scanForbiddenManifestsInYamlDocuments`, `failIfIngressInDocument`, `failIfAutoscalingV1InDocument`, `validateK8sYamlPolicyDocuments`, `failIfK8sPolicyNamespaceRulesViolated`, `failIfK8sPolicyResourceRulesViolated`, `runK8sYamlPolicyAndGatewayScans`, `scanGatewayApiRouteBackendRefsInYamlBody`, `failIfGatewayRouteUsesNonHeadlessService`, `validateKustomizationResourcesSortedAlphabetically`, `validateKustomizationPatchesStructuralSort`, `validateInlinePatchesSorted`, `validateKustomizationJson6902NoRemoveAddSamePath`, `auditJson6902OneKustomizationYamlFile`, `auditJson6902ForKustomizationYamlDoc`, `auditKustomizationPatchesJson6902`, `auditOneKustomizationJson6902Patch`, `auditJson6902PatchExternalFile`, `failIfJson6902RemoveAddConflictOnSamePath`, `verifyBaseKustomizationNamespaceOnFile`, `ensureBaseKustomizationHasNamespace`, `readFirstConfigMapDoc`. Видалено публічний predicate `isForbiddenAutoscalingV1Manifest` + його тест (rego `k8s.manifest` авторитативно). Решта predicates лишилися як публічні exports для back-compat (`hpaManifestViolations`, `pdbManifestViolations`, `deploymentTopologySpreadConstraintsViolation` все ще активно використовуються JS cross-file для expected-name/dev-like; інші — тестові shim, можна прибрати окремо).
- **`checkK8sYamlFile`** залишає тільки modeline + `$schema`-URL перевірки; per-document валідація (Ingress/autoscaling/v1 заборонено, Service GCP-анотації, Deployment resources/Hasura image/topologySpread, Gateway API backendRef правила, HCP, svc/svc-hl, namespace правила) — у rego, виконано на початку `check()`.

## [1.8.227] - 2026-05-10

### Changed

- **conftest.mdc (alwaysApply):** канонізовано патерн «Rego-authoritative + JS-orchestrator» (Plan B) як основний для всіх перевірок у репо. Розділ «Гібрид» переписано: замість «JS authoritative + rego-копія» (Plan A) — тепер чітко: пер-документне правило існує **рівно в одному місці** (rego), а `check-<rule>.mjs` делегує його через `runConftestBatch` (`npm/scripts/utils/run-conftest-batch.mjs`), один спавн на namespace. Додано конкретний шаблон `check()` (rego-крок перший, JS cross-file — після) і опис інтеграції з `lint-<rule>.mjs` (external-tools wrapper викликає `await checkX()` як останній крок). Реальні приклади — abie (пілот) і ga (повна централізація). Новий «червоний прапор» забороняє лишати JS-копію rego-правила «про всяк випадок» — це плодить дрифт.

## [1.8.226] - 2026-05-10

### Changed

- **ga / Plan B (rego-authoritative, повна централізація):** rego-крок переїхав із `lint-ga.mjs` у `check-ga.mjs::check()` як **перший крок**. Раніше `bun run lint-ga` сам викликав 4 per-workflow conftest + 1 batch для `ga.workflow_common`, а `npx @nitra/cursor fix ga` цю частину не робив — тепер вся ga-логіка (rego + JS cross-file) в одному `check-ga.check()`. `lint-ga.mjs::runLintGaCli` спрощено: preflight (shellcheck/uv) → actionlint → zizmor → `await checkGa()`. Видалено: `CONFTEST_TARGETS`, `GA_POLICY_DIR`, `runConftestStep`, `runConftestWorkflowCommon` — і непотрібні імпорти `existsSync`/`readdirSync`/`dirname`/`join`/`fileURLToPath`. `runLintGaCli` тепер `async`; `bin/n-cursor.js` оновлено на `await runLintGaCli()`. Тест `lint-ga.test.mjs` оновлено: `await fn()` замість `fn()`. Тест `check-ga.test.mjs::"exit 1 коли shellcheck відсутній"` переведений на точковий виклик експортованої `checkShellcheckInstalled` (бо `withBinRemovedFromPath('shellcheck')` на macOS заодно видаляв `/opt/homebrew/bin` де conftest, ламаючи hard-fail у `runConftestBatch`).
- **`check-ga.mjs::checkShellcheckInstalled`:** додано `export` (потрібен для точкового тесту після рефактору).
- **тестова фікстура `setupCanonicalGaProject` у check-ga.test.mjs:** додано секцію `concurrency` (з канонічними `group` і `cancel-in-progress: true`) у workflow `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `git-ai.yml` — `ga.workflow_common` rego тепер запускається у `check()`, а ці workflow раніше не мали concurrency у фікстурі (правило `lint-ga.yml` уже мало). Це **правильна** реакція: rego-перевірка тепер ловить порушення на тих самих фікстурах, на яких раніше не запускалась.

## [1.8.225] - 2026-05-10

### Added

- **utility `runConftestBatch`:** новий `npm/scripts/utils/run-conftest-batch.mjs` — спавнить `conftest test` одним викликом для batched-списку файлів, парсить `--output json`, повертає структуровані `{filename, namespace, message}` порушення. Hard-fail зі install-hint якщо `conftest` не у PATH (узгоджено з рішенням Plan B). Використовується з `check-*.mjs` для делегування пер-документної валідації у Rego-полісі без помітного сповільнення (один спавн на namespace, не на файл).

### Changed

- **abie / Plan B (rego-authoritative, pilot):** `npm/scripts/check-abie.mjs` рефакторнуто — пер-документна валідація 4 правил тепер делегується rego через `runConftestBatch`, JS залишає лише cross-file-оркестрацію (walking, path-фільтрацію, парність файлів). Видалені JS-функції-предикати (тепер єдине джерело істини — rego): `abieBaseHttpRouteHostnamesErrors`, `deploymentDocumentHasAbieBasePreemNodeSelector`, `parseCleanMergedIgnoreBranches`, `ignoreBranchesIncludesRequired`, `validateAbieHcPolicy`, плюс хелпери `collectAbieHostnames`, `isAllowedAbieBaseDevHostname`, `isAbiePreemTruthy`, `processBaseHttpRouteDoc`, `httpRouteHasNonEmptyHostnames`, `findHealthCheckPolicyInDocs` і константа `ABIE_REQUIRED_IGNORE_BRANCHES`. Імпорти `flattenWorkflowSteps`, `getStepUses`, `parseWorkflowYaml` (`./utils/gha-workflow.mjs`) теж прибрано — orphan після видалення JS-парсера workflow.
- **abie.health_check_policy (rego):** виправлено divergence з JS — тепер targetRef.name перевіряється точним match-ем `<hcp.metadata.name>-hl` (з нормалізацією: якщо name вже закінчується на `-hl`, береться як є). До цього rego перевіряло лише суфікс `-hl`, що дозволяло `targetRef.name=bar-hl` для HCP з `name=foo` — це не дзеркалило JS.
- **`validateAbieHcYaml` → `validateAbieHcModeline`:** export перейменовано — JS-частина перевірки hc.yaml тепер обмежується modeline (`# yaml-language-server: $schema=…`); парсинг YAML і структурна валідація HCP делеговано rego.
- **`npm/tests/check-abie.test.mjs`:** прибрано тести видалених JS-предикатів (8 тестів) — їх покриття тепер забезпечують `_test.rego` фікстури через `conftest verify`.
- **`npm/tests/cross-check-rego-abie.test.mjs`:** видалено — після Plan B JS-сторони для крос-чеку немає; `_test.rego` фікстури в кожному abie-пакеті дають аналогічне покриття.

## [1.8.224] - 2026-05-10

### Added

- **golden cross-check тести JS↔rego (abie):** додано `npm/tests/cross-check-rego-abie.test.mjs` (25 тестів), який для кожної пари (JS-предикат у `check-abie.mjs` ↔ rego-пакет у `npm/policy/abie/`) подає однаковий вхід у обидва імплементації через `opa eval --format json` і перевіряє інваріант **«обидва бачать порушення або обидва ні»**. Покриває: `deploymentDocumentHasAbieBasePreemNodeSelector` ↔ `abie.base_deployment_preem`; `parseCleanMergedIgnoreBranches`+`ignoreBranchesIncludesRequired` ↔ `abie.clean_merged_ignore_branches`; `abieBaseHttpRouteHostnamesErrors` ↔ `abie.http_route_base`; rego-only golden-фікстури для `abie.health_check_policy` (бо JS-функція `validateAbieHcPolicy` приватна). Тест автоматично пропускається, якщо `opa` не у PATH. Sanity-check ламанням rego навмисно — drift детектується.

## [1.8.223] - 2026-05-10

### Added

- **abie / нові rego-пакети:** `npm/policy/abie/base_deployment_preem/` (Deployment у `…/k8s/.../base/...` має `spec.template.spec.nodeSelector.preem` зі значенням, що вважається істинним — boolean `true` або рядок `"true"`); `npm/policy/abie/clean_merged_ignore_branches/` (у workflow `.github/workflows/clean-merged-branch.yml` крок `phpdocker-io/github-actions-delete-abandoned-branches` має `with.ignore_branches` з токенами `dev,ua,ru`, case-insensitive). Реєстрація в `lint-conftest.mjs` TARGETS: walk-pattern для base-resource YAML і single-target для workflow.
- **abie / `_test.rego` фікстури:** додано юніт-тести для всіх 4 abie-пакетів — нових (`base_deployment_preem_test.rego`, `clean_merged_ignore_branches_test.rego`) і існуючих (`http_route_base_test.rego`, `health_check_policy_test.rego`). 35 тестів покривають happy paths і deny-кейси.

### Changed

- **abie.health_check_policy (rego):** виправлено помилковий шлях `spec.config.httpHealthCheck` → правильний `spec.default.config.httpHealthCheck` (узгоджено з `validateAbieHcPolicy` у `check-abie.mjs`). Розширено перевірками: точна `apiVersion: networking.gke.io/v1`, `metadata.name` непорожній, `spec.default.config.type: HTTP`, `targetRef.kind: Service`. Cross-file звірка `<deployment.name>-hl` лишається у JS.
- **abie.mdc:** додано розділ «Швидкий gate через conftest (Rego)» зі списком rego-пакетів і опису того, що cross-file логіка (парність HCP↔Deployment, обчислений `<name>-hl`, валідація ru/ua-overlay JSON6902 patches, env→cluster DNS, cross-namespace backendRefs) лишається у `check-abie.mjs`.
- **lint-conftest.mjs TARGETS:** `abie.health_check_policy` і `abie.http_route_base` — `policyDir` уточнено до конкретного підкаталогу (`abie/health_check_policy`, `abie/http_route_base`) замість загального `abie`. Додано шляховий regex `K8S_BASE_RESOURCE_PATH_RE` для базових ресурсних YAML.

## [1.8.222] - 2026-05-10

### Added

- **k8s / rego-полісі:** розширено `npm/policy/k8s/manifest/manifest.rego` (Deployment cpu+memory у `requests`, Hasura image pin із білим списком тегів, канонічний `topologySpreadConstraints` з мітки `app` самого Deployment). Додано `manifest_test.rego` із вхідними фікстурами; rego тестується через `conftest verify` (опційний крок у `bun run lint-rego`). JS у `check-k8s.mjs` лишається authoritative — нові правила Rego — швидкий gate для одиничного manifest.
- **k8s / нові rego-пакети:** `npm/policy/k8s/gateway/` (Gateway API: backendRef з суфіксом `-hl`, redundant `namespace` у backendRef, HCP `targetRef.name` `-hl`); `npm/policy/k8s/kustomization/` (resources/patches алфавітне сортування, JSON6902 `remove`+`add` на той самий `path`); `npm/policy/k8s/svc_yaml/` (`Service.spec.type: ClusterIP`); `npm/policy/k8s/svc_hl_yaml/` (headless Service з суфіксом `-hl` і `clusterIP: None`); `npm/policy/k8s/base_kustomization/` (обов'язковий `namespace:`); `npm/policy/k8s/base_manifest/` (`metadata.namespace` у base, base-canon `cpu='0.02'`/`memory='128Mi'`); `npm/policy/k8s/kustomize_managed/` (заборона `metadata.namespace` у kustomize-managed файлах); `npm/policy/k8s/hasura_configmap/` (`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: "true"`); `npm/policy/k8s/hasura_httproute/` (канон 4 правил Hasura: `/ql` Exact + `/ql/` Exact + PathPrefix + WebSocket); `npm/policy/k8s/hpa_pdb/` (структурний gate HPA/PDB: `apiVersion`, `behavior.scaleUp/Down`, `metrics`, `selector.matchLabels`). До кожного пакета додано `*_test.rego` фікстури.
- **lint-rego:** додано опційний крок `conftest verify` у `npm/scripts/lint-rego.mjs` після `regal lint` для виконання `*_test.rego`. Якщо `conftest` не у PATH — крок мовчки пропускається з install-hint.

### Changed

- **lint-conftest:** `npm/scripts/lint-conftest.mjs` — `k8s.manifest` target тепер указує на `policyDir: 'k8s/manifest'` (вужчий policy-tree), додано targets для нових пакетів `k8s.gateway`, `k8s.hpa_pdb`, `k8s.kustomization`, `k8s.svc_yaml`, `k8s.svc_hl_yaml`, `k8s.base_kustomization`, `k8s.base_manifest`. Шляхові регекспи: `K8S_KUSTOMIZATION_PATH_RE`, `K8S_BASE_KUSTOMIZATION_PATH_RE`, `K8S_BASE_MANIFEST_PATH_RE`, `K8S_SVC_YAML_PATH_RE`, `K8S_SVC_HL_YAML_PATH_RE`. Пакети `kustomize_managed`, `hasura_configmap`, `hasura_httproute` потребують cross-file gating з `check-k8s.mjs` і не входять у `lint-conftest` walk-targets.
- **n-k8s.mdc:** додано розділ «Швидкий gate через conftest (Rego)» зі списком rego-пакетів і опису того, що cross-file логіка (резолюція kustomize-tree, парність svc.yaml/svc-hl.yaml, прив'язка ConfigMap/HTTPRoute до Hasura-Deployment, HPA/PDB by directory, env-залежні межі min/maxReplicas) лишається у `check-k8s.mjs`.
- **conftest.mdc (alwaysApply):** замість одного абзацу про «пріоритет conftest» — повний алгоритм рішення для нової перевірки: декізія-дерево (single-document → Rego за замовчуванням; cross-file/FS/autofix/text-pre-YAML → JS), workflow «спершу намалюй вхід → rego або гібрид», список «червоних прапорів» (Rego не вміє X — звір зі списком винятків). Мета: робити Rego default-вибором для нових перевірок.
- **npm/.claude-template/npm-CLAUDE.md:** додано path-scoped нагадування «Перш ніж писати `check-*.mjs`» з посиланням на алгоритм у `conftest.mdc`. Регенеровано `npm/CLAUDE.md`.

## [1.8.221] - 2026-05-10

### Changed

- **ci4.mdc:** наповнено правило людинозрозумілим описом C4-моделі як джерела істини. Markdown-файли (C4 + ADR + тести + документація) — офіційне джерело істини про проєкт. Перед змінами агент аналізує відповідні C4-файли; кожна зміна, що впливає на модель, супроводжується оновленням C4-схеми у тому ж PR. ADR описує вплив рішення на C4 (які контейнери/компоненти з'являються/зникають/змінюють відповідальність). Кожен C4-компонент має посилання на відповідні тести. C4-схеми — частина користувацької документації, не закритий артефакт. Алгоритмічної `check-ci4.mjs` поки немає — правило процесне; `## Перевірка` залишено для майбутньої формалізації.

## [1.8.220] - 2026-05-09

### Fixed

- **k8s / `prodOverlayHpaPdbOverrideNeeds`:** виключено Kustomize Component (`kind: Component`) з prod-overlay-перевірки. Раніше `<pkg>/k8s/components/kustomization.yaml` помилково тригерив `прод-оверлей має перевизначати spec.minReplicas/maxReplicas/minAvailable` — але Component є **джерелом** ресурсів для overlays, не overlay сам по собі. Прод-перезаписи живуть у `ru/` / `ua/` / `prod/` тощо, що підключають Component через `components:`. Додано ранній return за `kind: Component` у `npm/scripts/check-k8s.mjs`; уточнення додано до `npm/mdc/k8s.mdc` і регресійний тест у `npm/tests/check-k8s-schema.test.mjs`.

## [1.8.219] - 2026-05-09

### Added

- **skill `n-llm-patch`:** наповнено `npm/skills/llm-patch/SKILL.md` (та дзеркальну копію `.cursor/skills/n-llm-patch/SKILL.md`). Скіл готує самодостатній текстовий промпт для іншого Claude/Cursor-агента у цільовому проєкті: read-only аналіз CWD (`package.json`, `tree -L 2`, `README`, релевантні конфіги), формування єдиного markdown-блоку за шаблоном `Завдання → Контекст → Релевантні файли → Що треба зробити → Обмеження → Як перевірити`. Цільова LLM — Claude / Cursor agent; жодних змін у поточному репо, тимчасові артефакти — лише у `/tmp`.

### Changed

- **k8s / check-k8s:** канонічна структура HPA/PDB — через **Kustomize Component** з фіксованою назвою каталогу `components/` (sibling до `base/`). У `base/` HPA і PDB не існує: ні локальних `hpa.yaml` / `pdb.yaml`, ні через `resources` / `components`. Overlays підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень `/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`. Для кожного `Deployment` у `…/k8s/…/base/` тепер вимагається sibling каталог `…/k8s/…/components/` з валідним `kustomization.yaml` (`kind: Component`), `hpa.yaml` (dev-like `min=max=1`) і `pdb.yaml` (dev-like `minAvailable=0`).
- **k8s / check-k8s:** заборона локальних `base/hpa.yaml` і `base/pdb.yaml` (file-existence для обох). Якщо в дереві base-kustomize лишилися HPA або PDB через `resources` / `components` — fail (`HPA/PDB заборонені у base — переведіть у components/`).
- **k8s / check-k8s:** прод-overlay тригерить вимоги патчів `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB), коли overlay-tree містить HPA/PDB (тобто overlay підключив `components/`).
- **k8s.mdc:** оновлено опис, приклади `components/kustomization.yaml`, `components/hpa.yaml`, `components/pdb.yaml` і прикладу прод-overlay із `components: [- ../components]` + JSON6902-патчами.

### Removed

- **k8s / check-k8s:** прибрано застарілий механізм `$patch: delete` для HorizontalPodAutoscaler у `base/kustomization.yaml`. Видалено функції `verifyK8sBaseKustomizeHpaDeletedWhenInherited`, `kustomizationDeclaresHpaStrategicDelete`, `patchTextDeclaresHpaStrategicDelete` і константи-регекспи `HPA_STRATEGIC_DELETE_RE`, `HPA_KIND_LINE_RE` як мертві. Відповідні тести оновлено / видалено.

## [1.8.218] - 2026-05-09

### Added

- **auto-skills:** `llm-patch` додано як always-on скіл (без секції `[rules]` в `npm/bin/auto-skills.md`). Оновлено `AUTO_SKILL_ORDER` і `ALWAYS_ON_SKILLS` у `npm/scripts/auto-skills.mjs` та відповідні очікування у `npm/tests/auto-skills.test.mjs`. Сам вміст `npm/skills/llm-patch/SKILL.md` поки лишається стартовим — наповнюється окремо.

## [1.8.217] - 2026-05-09

### Added

- **js-run / conn-нейминг:** префікс `mssql-` тепер прийнятний у `src/conn/` нарівні з `mysql-` (`mssql-read.js`, `mssql-write-<id>.js` тощо). Сам npm-пакет `mssql` і раніше згадувався у правилі (як драйвер для MS SQL Server), але філенейм ставився під спільний `mysql-` префікс — це плутало читачів коду, де `import sql from 'mssql'` сусідив з файлом `mysql-write.js`. Тепер MSSQL має власний префікс із власним camelCase-експортом (`mssql-write-b2b` → `mssqlWriteB2b`). **Backward-compat:** проєкти, що вже використовують `mysql-…` для MSSQL-файлів, валідні без змін; рекомендований, але не обов'язковий рефактор — `git mv` цих файлів на `mssql-…` і відповідне перейменування іменованого експорту (`mysqlWrite` → `mssqlWrite`) зі оновленням імпортів через `#conn/*`.
- Регекс `CONN_FILENAME_RE` у `npm/scripts/utils/conn-file-rules.mjs` розширено до `(pg|mysql|mssql)-(read|write)(-<id>)?`; повідомлення про порушення в `npm/scripts/check-js-run.mjs` оновлено під чотири альтернативи; `mdc/js-run.mdc` має окремі пункти для MySQL і MSSQL.
- Тести: `npm/tests/conn-file-rules.test.mjs` (юніт-тести `isConnFileNameValid` / `kebabToCamel` / `findConnFileRuleViolations` під `mssql-`) та два інтеграційні кейси у `npm/tests/check-js-run-fixture.test.mjs` (`mssql-write.js` з валідним і невалідним експортом).

## [1.8.216] - 2026-05-09

### Changed

- **k8s / check-k8s:** орієнтир **`DEFAULT_CONTAINER_MEMORY_REQUEST`** поза base — **`512Mi`** (замість **`512`**).

## [1.8.215] - 2026-05-09

### Changed

- **k8s / check-k8s:** канон **`resources.requests.memory`** у шарі **`…/k8s/…/base/…`** — **`128Mi`** (замість **`128`**, щоб відповідати Quantity у Kubernetes); приймається **`Mi`** без урахування регістру.

## [1.8.214] - 2026-05-09

### Fixed

- **k8s / check-k8s:** конвертація image-replace patches → `images:` падала з `byPatch.keys(...).toSorted is not a function` (а в `rewriteInlinePatchWithoutOps` — з `(intermediate value).toSorted is not a function`), бо `Map.keys()` повертає ітератор, а `Set` — не масив, і `toSorted` на них немає. Тепер ключі/елементи матеріалізуються у масив через spread (`[...byPatch.keys()].toSorted(...)`, `[...new Set(opIndices)].toSorted(...)`).

### Changed

- **k8s / check-k8s:** у шарі **`…/k8s/…/base/…`** для **Deployment** жорстко **`resources.requests.cpu: '0.02'`** та **`memory: '128'`**; поза base обов’язкові **cpu** і **memory** (орієнтир **`0.5`** / **`512`** у підказках).
- **k8s / check-k8s:** заборона **`hpa.yaml`** у каталозі **`…/base/`**; якщо HPA є в дереві base — вимагається strategic-merge **`$patch: delete`** для **HorizontalPodAutoscaler** у **`base/kustomization.yaml`**.
- **k8s / check-k8s:** прод-оверлей вимагає patches на **HPA** (`minReplicas`/`maxReplicas`) лише якщо успадковане base **не** видаляє HPA через delete-patch; **PDB** **`minAvailable`** — якщо в base є PDB.
- **k8s.mdc:** оновлено правила та приклади під цю модель.

## [1.8.213] - 2026-05-09

### Added

- Нове правило `js-bun-redis` (`npm/mdc/js-bun-redis.mdc`): заміна `ioredis` /
  `node-redis` (включно з кореневим `redis` v4 і підпакетами `@redis/*`) на
  Bun native Redis (`import { redis } from 'bun'`,
  <https://bun.com/docs/runtime/redis>).
- AST-сканер `npm/scripts/utils/redis-imports.mjs` (`oxc-parser`) ловить
  `import` / `require` / динамічний `import()` пакетів `ioredis`, `node-redis`,
  `redis`, підшляхів `ioredis/...` / `redis/...` і `@redis/*`. Не зачіпає
  сторонні `redis-*` (наприклад, `redis-mock`).
- `npm/scripts/check-js-bun-redis.mjs` запускає AST-скан по JS/TS-джерелах і
  доступний як `npx @nitra/cursor fix js-bun-redis`.
- Rego-полісі `npm/policy/js_bun_redis/package_json/` — заборона
  `ioredis` / `node-redis` / `redis` / `@redis/*` у `dependencies` будь-якого
  `package.json` у дереві; зареєстрована таргетом у
  `npm/scripts/lint-conftest.mjs` (`bun run lint-conftest`).
- Авто-увімкнення правила в `.n-cursor.json`: `npm/scripts/auto-rules.mjs`
  додає `js-bun-redis`, якщо в `dependencies` хоч одного `package.json` є
  `ioredis` або `node-redis` (умова — у `npm/bin/auto-rules.md`).
- Тести: `npm/tests/redis-imports.test.mjs` (AST-сканер) і нові кейси у
  `npm/tests/auto-rules.test.mjs` (детект `ioredis` / `node-redis`).

## [1.8.212] - 2026-05-08

### Changed

- `npm/skills/taze/SKILL.md`: повний workflow замість шаблону-заглушки. Тепер
  скіл бекапить `package.json`/`bun.lock`, виконує `bunx taze -w -r latest` +
  `bun install`, виявляє major-стрибки порівнянням з бекапом, тягне breaking
  changes з CHANGELOG модуля або git-діфу `node_modules` (з фолбеком на
  встановлення старої версії в `/tmp`), шукає використання зачепленого API в
  коді через `rg`, рефакторить несумісні місця (нетривіальні міграції — TODO),
  прибирає тимчасові файли і віддає структурований звіт користувачу.

## [1.8.211] - 2026-05-08

### Added

- Окремий шлях автодетекту для скілів — `npm/bin/auto-skills.md` +
  `npm/scripts/auto-skills.mjs` (`detectAutoSkills`). Скіли отримують свій
  словник умов (`skill - [rules]`), залежний від уже виявлених правил, тож не
  дублюють файлові ознаки з `auto-rules.md`.
- Нові авто-скіли: `publish-telegram` (завжди) і `taze` (за правилом `bun`).
- `npm/tests/auto-skills.test.mjs` — окремі тести `detectAutoSkills`
  (завжди-додавані, залежності від rule-id, `disable-skills`, фільтр за
  `availableSkills`).

### Changed

- `npm/scripts/auto-rules.mjs`: `detectAutoRulesAndSkills` → `detectAutoRules`
  (повертає лише `{ rules }`); прибрано `AUTO_SKILL_ORDER` і скіл-логіку.
  `mergeConfigWithAutoDetected` лишається спільним і приймає вже виявлені
  rules+skills, тож публічний контракт `.n-cursor.json` не змінився.
- `npm/bin/n-cursor.js` тепер послідовно викликає `detectAutoRules` і
  `detectAutoSkills` (скіли отримують `detectedRules` як вхід).
- `npm/bin/auto-rules.md` залишає тільки правила; секція скілів винесена в
  `auto-skills.md` з посиланням з `auto-rules.md`.

## [1.8.210] - 2026-05-08

### Added

- `js-bun-db` v1.6: правило тепер забороняє локальні pg-format-сумісні шими у
  файлах з Bun SQL.
  - Розділ `## pg-format: повне видалення, без шимів` у `npm/mdc/js-bun-db.mdc`:
    типові ідіоми `format(...)` → tagged template, заборонений drop-in `format()`
    і `pg`-сумісна `query(text, params)`-обгортка над `sql.unsafe(...)`.
  - Два нові AST-детектори у `npm/scripts/utils/bun-sql-scan.mjs`:
    `findPgFormatShimDefinitionInText` (функції `format` / `pgFormat` /
    `sqlFormat` / `pgFmt` з `%L`/`%I`/`%s` у тілі, плюс `quoteLiteral` /
    `quoteIdent` / `escapeLiteral` / `escapeIdent` без додаткової перевірки)
    та `findPgFormatLikeQueryWrapperInText` (`{ query(text, params) { ...
<obj>.unsafe(...) ... } }`). Скан запускається лише у файлах з
    `import { sql|SQL } from 'bun'`.
  - `npm/scripts/check-js-bun-db.mjs` рапортує `pgFormatShim` / `queryWrapper` —
    окремі лічильники й `pass`-рядки, без зміни існуючих перевірок.

## [1.8.209] - 2026-05-08

### Removed

- Дедуплікація JS-перевірок, що вже покриті Rego-полісі (запускаються через
  `bun run lint-conftest`):
  - `npm/scripts/check-bun.mjs` — без `checkBunfigHoisted`, `checkDevDependencies`,
    `checkLintAggregate`, перевірок `pkg.packageManager` і кореневого
    `pkg.dependencies`. Лишилася FS-existence (`bun.lock`, `bunfig.toml`,
    `package.json`, заборонені lockfile, директорія `.yarn/`) і cross-file гейт
    `lint-docker` / `lint-k8s` від `.n-cursor.json:rules`.
  - `npm/scripts/check-php.mjs` — без перевірок `lint-php` скрипта і `run` у
    `lint-php.yml`. Лишилися FS-existence для `composer.json`, `package.json`,
    `lint-php.yml`.
  - `npm/scripts/check-style-lint.mjs` — без перевірок `lint-style` через
    `npx stylelint`, `@nitra/stylelint-config` у `devDependencies`,
    `stylelint.extends`, `npx stylelint` у `lint-style.yml`. Лишилися VSCode-
    конфіги, `.stylelintignore`, FS-existence workflow і альтернатива зовнішнього
    конфіг-файлу `stylelint`.
  - `npm/scripts/check-graphql.mjs` — без `checkPackageDumpSchemaScript` (структура
    `scripts.dump-schema`); решта логіки (gql AST-скан, `.graphqlrc.yml`,
    VSCode-розширення) лишилася.
  - `npm/scripts/check-image-compress.mjs` — без `checkLintImageScript`,
    `checkLintAggregateIncludesImage`, `checkMinifyImageNotInDeps`. Лишилися
    `.n-minify-image.tsv` НЕ в `.gitignore` і видалення застарілого
    `.minify-image-cache.tsv`.
  - `npm/scripts/check-js-bun-db.mjs` — без `checkForbiddenDependencies`
    (`pg`/`pg-format`/`mysql2`); AST-скан коду (`new SQL(...)` всередині функції,
    `unsafe()` без маркера, динамічні `IN (…)`) лишився.
  - `npm/scripts/check-text.mjs` — без `checkOxfmtRc`, `checkCspellConfig`,
    `checkCspellJsonDictImports`, `checkMarkdownlintConfig`, `prettier`/`@nitra/cspell-dict`/`markdownlint-cli2`/`@nitra/*` гейт у
    `checkPackageJsonTextDepsUsage`. Лишилися VSCode-конфіги, `.v8rignore`,
    Prettier-файли в корені, абзац про український апостроф у `.mdc`,
    складна валідація скрипта `lint-text` і виклик `bun run lint-text` у
    workflow.
  - `npm/scripts/check-vue.mjs` — без `checkViteVersion` (vite ≥ 8). AST-скан коду
    і vite-config-перевірки лишилися.
  - `npm/scripts/check-npm-module.mjs` — без `checkNpmTypesField`,
    `emitTypesConfigIssues`, перевірок полів `npm-publish.yml`,
    `workspaces ∋ "npm"` у кореневому `package.json`. Лишилися FS-existence,
    наявність файлу зі шляху `types`, hk.pkl-перевірки, CHANGELOG-version-match,
    git-dirty-bump.
  - `npm/scripts/check-js-lint.mjs` — без `checkPackageJsonLintDeps`
    (prettier-залежність, `@nitra/eslint-config ≥ 3.9.2`),
    `checkPackageJsonTypeModule` для root, `checkEnginesNode/Bun` для root,
    канонічний `lint-js`-скрипт, валідація `lint-js.yml` (`verifyLintJsWorkflowStructure`
    - fallback). Лишилися — `.oxlintrc.json` canonical-snapshot, VSCode-розширення,
      workspace-ітерація для `type: "module"` і engines, дубль JS-кроків у `lint.yml`,
      `.jscpd.json`. Прибрано непотрібні імпорти `parseWorkflowYaml`,
      `verifyLintJsWorkflowStructure` і `OXLINT_FIX_RE`.
  - `npm/scripts/check-js-run.mjs` — без перевірок `bunyan` / `@nitra/bunyan` у
    залежностях, canonical `jsconfig.json` через `deepEqualJson`,
    `OTEL_RESOURCE_ATTRIBUTES` у `configmap.yaml`. Лишилися AST-скан коду
    (bunyan, conn-aliases, process.env, setTimeout) і FS-existence для
    `jsconfig.json` / `configmap.yaml`. Прибрано `CANONICAL_BACKEND_JSCONFIG`,
    `deepEqualJson`.
  - `npm/scripts/check-adr.mjs` — без `settingsHaveAdrHookGroup`,
    `checkProjectSettings` структурного порівняння і
    `checkLocalSettingsNoDuplicate`. Лишилися hash-порівняння bash-скрипта,
    `.gitignore`-патерн, LLM CLI у PATH, FS-existence settings.json.
    Прибрано `HOOK_COMMAND_MARKER`, `PROJECT_LOCAL_SETTINGS_PATH` (для
    settings.local — Rego policy gating).
  - `npm/scripts/check-ga.mjs` — без `verifyConcurrencyBlock`,
    `verifyNoDirectBunOrCache`, `verifyNoRunShellLineContinuationBackslash`,
    `verifyCheckoutBeforeLocalSetupBunDeps`, `validateConcurrencyOnRoot`. Тепер
    усі workflow-структурні перевірки виконуються через conftest у `lint-ga.mjs`
    (`ga.workflow_common`); лишилася лише git-залежна перевірка `on.*.paths`
    glob-ів через `git ls-files :(glob)`. Прибрано константи
    `SETUP_BUN_PATTERNS`, `FORBIDDEN_BUN_PATTERNS`, `EXPECTED_CONCURRENCY_GROUP`
    і непотрібні імпорти з `gha-workflow.mjs`.

### Changed

- Тести `check-bun.test.mjs`, `check-image-compress.test.mjs`,
  `check-js-bun-db.test.mjs`, `check-js-run-fixture.test.mjs`,
  `check-adr.test.mjs` — прибрано / `skip` тести, що дублювали Rego-полісі;
  лишилися лише FS / cross-file сценарії.
- `npm/policy/{capacitor,js_mssql,abie,k8s,hasura}/**/*.rego` — у заголовках
  policy-файлів додано позначку, що JS-чек у відповідному `check-*.mjs`
  лишається authoritative (повна semver-семантика з OR-діапазонами для
  `capacitor`/`js-mssql`; ширший набір полів і cross-file Kustomize-контекст
  для `abie`/`k8s`; cross-file env-DNS-резолюція для `hasura`). Rego там — швидкий
  гейт для одиничного файлу (наприклад через IDE).

## [1.8.208] - 2026-05-08

### Added

- `mdc/js-run.mdc` (1.6, з 1.5): новий розділ «Нейминг файлів у `src/conn/`» — префікси `ql-` (GraphQL endpoint), `pg-`/`mysql-` з обовʼязковим `read`/`write` режимом і опційним ідентифікатором підключення для multi-БД (`pg-read-smart.js`, `pg-write-contract.js`); якщо режим не очевидний з імені env — визначати за наявністю операцій зміни даних. Також правило про експорти в `src/conn/`: заборонено `export default`, лише іменований експорт у camelCase від назви файла (`ql-smart.js` → `export const qlSmart`, `pg-write-contract.js` → `export const pgWriteContract`).
- `scripts/utils/conn-file-rules.mjs` + інтеграція в `scripts/check-js-run.mjs` — для кожного файла всередині `#conn/` каталогу пакета перевіряє: (а) basename відповідає канону `ql-<id>` / `(pg|mysql)-(read|write)[-<id>]` (kebab-case `[a-z0-9-]`); (б) відсутній `export default`; (в) серед іменованих експортів є рівно `<camelCase(basename)>` (`pg-write-contract.js` → `pgWriteContract`). `index.*` пропускається як reexport-барель. Розпізнає `export const/let/var`, `export function`, `export class` і `export { x as Y }` через AST на oxc-parser.

## [1.8.207] - 2026-05-08

### Added

- `npm/policy/ga/workflow_common/workflow_common.rego` — універсальні Rego-перевірки для **кожного** `.github/workflows/*.yml`: блок `concurrency` (group / cancel-in-progress), заборонені `oven-sh/setup-bun` / `actions/cache` / `bun install` у `uses`/`run` будь-якого кроку, заборонене shell-продовження `\` перед NL у `run:`, обовʼязковий `actions/checkout@…` перед локальним composite-action `setup-bun-deps`. Підключено в `lint-ga.mjs` як один прогін `conftest test <…all yml…> --namespace ga.workflow_common`.
- `npm/policy/bun/{bunfig,package_json}/*.rego` — порт `check-bun.mjs` (TOML і JSON-частина): `[install].linker == "hoisted"` у `bunfig.toml`; у кореневому `package.json` без `packageManager`, без `dependencies`, у `devDependencies` лише `@nitra/*`; агрегований `lint`-скрипт покриває всі `lint-*` через `bun run` і завершується `&& oxfmt .`.
- `npm/policy/text/{oxfmtrc,cspell,markdownlint,package_json}/*.rego` — порт `check-text.mjs`: `.oxfmtrc.json` обовʼязкові ключі і канонічні значення; `.cspell.json` `version "0.2"`, `language`, імпорт `@nitra/cspell-dict`, заборона `@cspell/dict-*`, обовʼязкові `ignorePaths`; `.markdownlint-cli2.jsonc` `gitignore: true`; `package.json` без Prettier, `@nitra/cspell-dict ^2.0.0+`, без `markdownlint-cli2` у залежностях.
- `npm/policy/style_lint/{package_json,lint_style_yml}/*.rego` — порт `check-style-lint.mjs`: скрипт `lint-style` через `npx stylelint`, `@nitra/stylelint-config` у `devDependencies`, `stylelint.extends == "@nitra/stylelint-config"`; у `lint-style.yml` хоча б один `run` з `npx stylelint`.
- `npm/policy/php/{package_json,lint_php_yml}/*.rego` — порт `check-php.mjs`: скрипт `lint-php` у `package.json`; у `lint-php.yml` хоча б один `run` з `bun run lint-php`.
- `npm/policy/npm_module/{root_package_json,npm_package_json,emit_types_config,npm_publish_yml}/*.rego` — порт `check-npm-module.mjs`: `workspaces ∋ "npm"` у кореневому `package.json`; у `npm/package.json` `types` відповідає одному з канонічних патернів і `files ∋ "types"`; `npm/tsconfig.emit-types.json` має канонічні `compilerOptions`; `.github/workflows/npm-publish.yml` має `on.push.paths ∋ "npm/**"`, `branches ∋ "main"`, `permissions.id-token: write` і крок `JS-DevTools/npm-publish` з `with.package: npm/package.json`.
- `npm/policy/k8s/manifest/manifest.rego` — порт пер-документних структурних правил `check-k8s.mjs`: `kind: Ingress` заборонено (Gateway API), `apiVersion: autoscaling/v1` заборонено (HPA → v2), у `kind: Service` заборонені анотації `cloud.google.com/neg` / `cloud.google.com/backend-config`, у `kind: Deployment` кожен контейнер `containers`+`initContainers` має непорожнє `resources.requests.cpu`. Cross-file Kustomize-логіка (svc/svc-hl, HPA/PDB, namespace base, kustomization patches) лишається в JS.
- `npm/policy/js_lint/{package_json,lint_js_yml}/*.rego` — порт `check-js-lint.mjs`: канонічний `lint-js`, `@nitra/eslint-config ≥ 3.9.2`, `engines.node ≥ 24`, `engines.bun ≥ 1.3`, `type: "module"`; у `lint-js.yml` `actions/checkout@v6` з `persist-credentials: false`, `setup-bun-deps`, `bunx oxlint/eslint/jscpd .`, без `--fix` у CI.
- `npm/policy/js_mssql/package_json/package_json.rego` — порт `check-js-mssql.mjs`: `dependencies.mssql ≥ 12.5.0` (підтримує `^12.5.0`, `>=12.5.0`, `workspace:*`).
- `npm/policy/js_bun_db/package_json/package_json.rego` — порт `check-js-bun-db.mjs`: у `dependencies` заборонені `pg`, `pg-format`, `mysql2`.
- `npm/policy/js_run/{package_json,jsconfig,configmap}/*.rego` — порт `check-js-run.mjs`: заборона `bunyan` / `@nitra/bunyan` у залежностях; `jsconfig.json` має канонічні `compilerOptions` і `include`; у k8s ConfigMap `OTEL_RESOURCE_ATTRIBUTES` містить `service.name=` і `service.namespace=`.
- `npm/policy/vue/package_json/package_json.rego` — порт `check-vue.mjs`: якщо `dependencies.vue` присутній, у `devDependencies` має бути `vite` мажорної версії ≥ 8.
- `npm/policy/graphql/package_json/package_json.rego` — порт `check-graphql.mjs`: `scripts.dump-schema` точно відповідає канонічному.
- `npm/policy/image_compress/package_json/package_json.rego` — порт `check-image-compress.mjs`: `lint-image` викликає `npx @nitra/minify-image --src=. --write` без `--avif`; агрегований `lint` містить `bun run lint-image`; `@nitra/minify-image` НЕ у `dependencies`/`devDependencies`.
- `npm/policy/hasura/svc_hl/svc_hl.rego` — порт `check-hasura.mjs` (мінімум): у `hasura/k8s/base/svc-hl.yaml` Service з `metadata.name` має закінчуватись на `-h`.
- `npm/policy/adr/{settings_json,settings_local_json}/*.rego` — порт `check-adr.mjs`: `.claude/settings.json` має містити Stop-hook з командою `.claude/hooks/capture-decisions.sh`; `.claude/settings.local.json` (якщо існує) — НЕ повинен мати дубля цього хука.
- `npm/policy/capacitor/package_json/package_json.rego` — порт `check-capacitor.mjs`: `dependencies['@capacitor/core']` мажорна ≥ 8 (підтримує `workspace:*`).
- `npm/policy/abie/{health_check_policy,http_route_base}/*.rego` — порт `check-abie.mjs`: `HealthCheckPolicy` (`networking.gke.io/v1`) має непорожній `requestPath` зі слешем, `port: 8080`, `targetRef.name` закінчується на `-hl`; `HTTPRoute` у `…/base/…` приймає лише hostnames у домені `aiml.live`.
- `npm/scripts/lint-conftest.mjs` (+ `bun run lint-conftest` у `package.json`) — єдиний раннер conftest по всіх нових polysi: для кожного namespace — single-file або walk-предикат, з gating-ом по `.n-cursor.json:rules`, як у `check-*.mjs`. Викликається в кореневому `lint` після `lint-rego`.

### Changed

- `npm/policy/ga/{lint_ga,clean_ga_workflows,clean_merged_branch,git_ai}/*.rego`: прибрано дублікати правил `concurrency` (group / cancel-in-progress / missing) — їх покриває `ga.workflow_common`. Заодно усунено мовчазний баг `not is_object(input.concurrency)` (коли поля немає, повертає `undefined`, не `true`); у `workflow_common` через `object.get(input, "concurrency", false)` дає визначене значення. Канонічна тригер-група `expected_concurrency_group` теж видалена з кожної per-workflow polysi.
- `npm/scripts/lint-ga.mjs`: до існуючих per-workflow conftest-таргетів додано фінальний прогін `ga.workflow_common` одним викликом `conftest test <усі .yml> --namespace ga.workflow_common`. Імпорт `readdirSync` з `node:fs` для перерахунку workflow-файлів.

## [1.8.206] - 2026-05-08

### Added

- `mdc/rego.mdc` (нова версія 1.1, з 1.0): VS Code-секція з рекомендованим розширенням `tsandall.opa` (LSP від автора OPA: підсвічування, hover, go-to-definition, format-on-save через `opa fmt`), `.vscode/extensions.json` і `.vscode/settings.json` сніпети для `[rego]` (`editor.defaultFormatter: tsandall.opa`, `formatOnSave: true`); опис кроків `lint-rego` (preflight `opa`+`regal`, далі `opa check --strict` і `regal lint`); `package.json`-сніпет зі скриптом `lint-rego`; install-команди (`brew install opa regal` + universal лінки); приклад `.regal/config.yaml`. Раніше файл містив лише placeholder `npx @nitra/cursor fix rego`.

### Changed

- `scripts/lint-rego.mjs`: додано preflight на `opa` (поряд з `regal`) з install-hint `brew install opa` і покликом до VS Code-розширення `tsandall.opa`; до `regal lint` додано попередній крок `opa check --strict <targets>` (типи + строгий режим: мертвий код, неоднозначні правила, незадекларовані змінні) — `opa check` ловить compile-помилки, які `regal` навмисно лишає поза скоупом. Якщо хоч один з `opa`/`regal` відсутній у `PATH` — exit 1 ще до запуску, з підказкою встановлення для обох.

## [1.8.205] - 2026-05-08

### Added

- `npm/policy/ga/lint_ga/lint_ga.rego` — порт `validateLintGaWorkflowStructure` + `validateLintGaOnTriggers`: `name` / `on.push.branches∋{dev,main}` / `on.pull_request.branches∋{dev,main}` / `on.push.paths∋{.github/actions/**,.github/workflows/**}` / `concurrency` / `jobs.lint-ga.runs-on` / `jobs.lint-ga.permissions.contents=read` / `steps` non-empty / `uses` set містить `actions/checkout@v6`, `./.github/actions/setup-bun-deps`, `astral-sh/setup-uv@v8.0.0` / `run` blob містить `bun run lint-ga`.
- `npm/policy/ga/git_ai/git_ai.rego` — порт `validateGitAiWorkflowStructure`: `name` / `on.pull_request.types∋closed` / `concurrency` / `jobs.git-ai.if` містить `merged == true` / `permissions.contents=write` / `run` blob містить `curl … usegitai.com … bash` і `git-ai ci github run`.

### Changed

- `scripts/lint-ga.mjs`: `CONFTEST_TARGETS` тепер охоплює всі 4 канонічні GA-workflow — `clean-ga-workflows.yml`, `clean-merged-branch.yml`, `lint-ga.yml`, `git-ai.yml` — кожен зі своїм `--namespace ga.<name>`.
- `scripts/check-ga.mjs`: видалено `validateLintGaWorkflowStructure`, `validateLintGaOnTriggers`, `validateGitAiWorkflowStructure`, `validateGitAiParsedYaml`, `hasPullRequestClosedTrigger`, `hasJobMergedCondition`, `checkLintGaWorkflow`, `checkGitAiWorkflow`, `checkCanonicalWorkflowsMatchRule`, локальний `isExactString` і відповідні імпорти `anyRunStepIncludes`/`flattenWorkflowSteps`/`getStepRun`/`getStepUses`. Файл скоротився з 1074 → 570 рядків (≈47%) — структурні перевірки канонічних GA-workflow повністю мігрували в conftest. У JS лишилися: file-existence (zizmor.yml, .vscode/settings.json, setup-bun-deps), `package.json` script `lint-ga`, MegaLinter-зачистка, `verifyConcurrencyBlock` для всіх workflow без винятків (включно з не-канонічними), `verifyNoDirectBunOrCache`, `verifyCheckoutBeforeLocalSetupBunDeps`, paths-globs через `git ls-files`, preflight `shellcheck`.

## [1.8.204] - 2026-05-07

### Changed

- Реструктурував `npm/policy/ga/` під namespaced sub-packages, які проходять regal: `ga/clean_ga_workflows/clean_ga_workflows.rego` та новий `ga/clean_merged_branch/clean_merged_branch.rego` (порт `validateCleanMergedBranch` з check-ga.mjs — `name` / `cron 0 1 15 * *` / `workflow_dispatch` / `concurrency` / `jobs.cleanup_old_branches` / step0 `phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3` з token / age=90 / ignore_branches main,dev / `dry_run: false` (YAML 1.1) / step1 `Get output` + `DELETED_BRANCHES` env + echo).
- `scripts/lint-ga.mjs`: `CONFTEST_TARGETS` тепер містить `clean-ga-workflows.yml` і `clean-merged-branch.yml`, conftest викликаємо з `--namespace ga.<name>` для ізоляції правил між workflow.
- `scripts/check-ga.mjs`: видалено `validateCleanGaWorkflows*` і `validateCleanMergedBranch*` — їх повністю покриває conftest у `lint-ga`. `checkCanonicalWorkflowsMatchRule` тепер валідує лише `lint-ga.yml` і `git-ai.yml` (наступні кандидати на міграцію).

### Added

- `.regal/config.yaml` у корені — вимикає `idiomatic.no-defined-entrypoint` (для conftest-полісі `deny`-правила є де-факто entrypoint-ами, формальна анотація не несе семантики).

## [1.8.203] - 2026-05-07

### Changed

- `check-k8s.mjs` (автоконверт `image-replace` patches → `images:`): тепер працює і для `patches[i].patch` із **кількома** ops, а не лише з одинокою image-replace op. Сканує всі ops у патчі, конвертує **кожну** `op: replace` на `/spec/template/spec/containers/<N>/image` (target `kind: Deployment`) у запис `images:`; якщо всі ops патча конвертовано — `patches[i]` видаляється повністю; інакше inline `patch:` переписується через `parseDocument` без конвертованих ops зі збереженням block-literal scalar (`|-`) і вихідного порядку решти ops. Реалізовано через нові функції `tryParseJson6902Array` (≥ 1 op, замість `tryParseSingleJson6902Array`) і `rewriteInlinePatchWithoutOps`; `imageReplaceDeploymentPatchInfo` повертає `{ deployName, totalOps, ops: [{ containerIndex, newImage, opIndex }] }` (раніше — одиничний `{ deployName, containerIndex, newImage }` лише за `length === 1`); `applyConversionsToDoc` групує конвертації по індексу патча й вирізає ops або сам патч за потреби. Сортування решти ops після видалення лишається поза цією зміною — за нього відповідає окрема перевірка `kustomizationInlinePatchOpsSortedViolation`.
- `mdc/k8s.mdc` (v1.26 → v1.27): уточнено крок 1 авто-перевірки в розділі «Зміна image — через `images:`, не через `patches[]`» — тепер описує і випадок, коли в `patches[i].patch` лишаються не-image ops (їх зберігає, у вихідному порядку, без коментарів).
- `check-js-lint.mjs` + `mdc/js-lint.mdc` (v1.16 → v1.17): мінімум `@nitra/eslint-config` піднято з `^3.8.0` до `^3.9.2`. Обґрунтування: з 3.9.2 у `getConfig` вбудовано ignore для `**/adr/**`, тож ADR-документи не валідуються ESLint, і консьюмерам не треба додавати цей glob у `eslint.config.js` локально. `nitraEslintConfigMeetsMinVersion` тепер повертає `false` для діапазонів `^3.8.x`–`^3.9.1`; `workspace:*` лишається ok без змін. Pass/fail-повідомлення `checkPackageJsonLintDeps` оновлено під новий мінімум; `for...in`-бан з 3.8.0 згадується як накопичена відмінність. Тести `nitraEslintConfigMeetsMinVersion` розширено: `^3.9.2`/`^3.9.10`/`^3.10.0`/`^4.0.0` — ok; `^3.9.1`/`^3.8.0`/`^3.6.12`/`^3.4.3` — ні.
- `bin/n-cursor.js` (`reexecIfPackageVersionChanged` + `spawnSync`-виклик): `process.env.NITRA_CURSOR_REEXEC` і `...process.env` замінено на `env.NITRA_CURSOR_REEXEC` і `...env` з `node:process` (`import { cwd, env } from 'node:process'`). Підстава: правило `js-run.mdc` забороняє прямий `process.env.*` у Node-коді; `NITRA_CURSOR_REEXEC` — опційна змінна (виставляється лише при re-exec), тож імпорт `env` з `node:process` (а не з `@nitra/check-env`) — канонічна форма для опційних. Поведінка не змінена; раніше `npm/scripts/check-js-run.mjs` помилявся на `bin/n-cursor.js:1136` (правило `process-env`), тепер integration-test `check-* на реальному репозиторії` проходить.

### Added

- `tests/check-k8s-images.test.mjs`: нова форма `imageReplaceDeploymentPatchInfo` (`ops`/`totalOps`/`opIndex`); e2e-тести на multi-op patch (image + `add nodeSelector`), три не-image ops + image у hasura-стилі (`add containers/-` + `add volumes` + `replace nodeSelector`), multi-image patch (containers/0 + containers/1 → обидва конвертовано, патч видаляється), mixed patch з digest у одному з image-values (звичайний tag конвертовано, digest op лишається у патчі) і одиничний digest-image (повертає `errors`, патч на диску не змінюється).

## [1.8.202] - 2026-05-07

### Added

- `bin/n-cursor.js`: новий хелпер `reexecIfPackageVersionChanged(effectivePackageRoot)` і його виклик у `runSync` одразу після `upgradeNitraCursorToLatestAndBunInstall`. Якщо self-upgrade встановив у `node_modules/@nitra/cursor` версію, відмінну від тієї, з якої стартував поточний процес (типово — npx-кеш), CLI спавнить `process.execPath <newBin> <args…>` через `spawnSync` (`stdio: 'inherit'`), додає в env `NITRA_CURSOR_REEXEC=1` і завершується з exit-кодом дочірнього процесу. Обґрунтування: ES-модулі (`RULE_MIGRATIONS`, `detectAutoRulesAndSkills`, списки правил) уже завантажені у V8 і нова логіка з-під свіжо встановленого пакета без re-exec невидима для поточного запуску — `import()` не вирішує цього, бо процес виконується з `bin/` у npx-кеші, а не з `node_modules/`. Захист від нескінченного циклу — раннє повернення при `process.env.NITRA_CURSOR_REEXEC === '1'`; додатково нічого не робить, якщо `effectivePackageRoot === BUNDLED_PACKAGE_ROOT` (реального апгрейду не сталося), якщо `version` не вдалося прочитати з обох `package.json`, або якщо у новому корені відсутній `bin/n-cursor.js`. `runChecks` свідомо не патчиться — він не виконує self-upgrade, тож версія процесу і пакета там завжди узгоджені. Імпорт `spawnSync` із `node:child_process` — єдина нова зовнішня залежність.

## [1.8.201] - 2026-05-07

### Changed

- `check-hasura.mjs`: `INTERNAL_HASURA_URL_RE` тепер приймає **обидва** кластерні DNS-суфікси у `HASURA_GRAPHQL_ENDPOINT` — `<cluster>.internal` (GKE/GCP, наприклад `abie-dev` / `abie-ua`) **і** `cluster.local` (стандартний k8s / Yandex Cloud). Раніше regex вимагав літеральний `.internal` у кінці, тож URL виду `http://apruv-h-hl.ru-apruv.svc.cluster.local:8080` (типовий для YC-кластера ru) помилково відхилявся. `parseInternalHasuraEndpoint` для YC повертає `cluster: 'cluster.local'` як повний суфікс, для GKE — ім'я кластера без `.internal` (зворотньо сумісно з попередньою поведінкою). Текст помилки в `checkEnvFile` оновлено — згадує обидва допустимі формати.
- `abie.mdc` (v1.17 → v1.19): нова секція «Внутрішньокластерні URL у env-файлах (dev / ua / ru)». Правило стосується **будь-якого** internal URL у env-файлах abie-проєкту — не лише `HASURA_GRAPHQL_ENDPOINT`, а й KVCMS, `auth-run-hl`, `file-link-hl` тощо. Таблиця `dev.env` / `ua.env` / `ru.env` → namespace-префікс + DNS-суфікс кластера (dev → `abie-dev.internal` + `dev-…`, ua → `abie-ua.internal` + `ua-…`, ru → `cluster.local` + `ru-…`); приклади з двома сервісами в одному файлі (Hasura + KVCMS). Загальне правило про **внутрішній** URL замість публічного домену для `HASURA_GRAPHQL_ENDPOINT` лишається у `hasura.mdc` (для nitra та abie).

### Added

- `check-abie.mjs`: новий валідатор `validateAbieEnvInternalUrls` (`String.prototype.matchAll` за `ABIE_INTERNAL_URL_GLOBAL_RE`) і helper `abieEnvNameFromBasename`. У функції `check()` додано крок `ensureAbieEnvFilesMatchClusterDns`, що сканує всі `*.env`-файли (basename `dev.env` / `ua.env` / `ru.env` опційно з провідною крапкою; `.env` без імені пропускається — як у `check-hasura.mjs`) і для **кожного** знайденого URL виду `http://<svc>.<ns>.svc.<dns>` перевіряє відповідність DNS-суфікса й namespace-префікса середовищу env-файла. Помилки додаються через `fail`, без зупинки на першому файлі — звіт показує всі порушення в усіх env-файлах одразу.
- `tests/check-hasura.test.mjs`: тести `parseInternalHasuraEndpoint` для GKE-style `abie-dev.internal` / `abie-ua.internal` та YC-style `cluster.local`; негативний кейс на сторонній суфікс (`svc.example.com`); інтеграційний тест `check()` для `hasura/.ru.env` з `cluster.local`.
- `tests/check-abie.test.mjs`: 7 unit-тестів на `abieEnvNameFromBasename` і `validateAbieEnvInternalUrls` (узгоджений dev/ua/ru, URL без порту, dev URL у ua-файлі, internal-суфікс у ru-файлі, ігнорування зовнішніх `https://` / `localhost`, кілька URL з різними порушеннями) і 4 інтеграційні (`.dev.env`+`.ua.env`+`.ru.env` узгоджені — 0; ua з dev URL у KVCMS — 1; ru з `.internal` замість `cluster.local` — 1; `.env` без імені пропускається).

## [1.8.200] - 2026-05-07

### Added

- `policy/ga/clean-ga-workflows.rego` + новий PoC-крок у `scripts/lint-ga.mjs`: запускає `conftest test` на `.github/workflows/clean-ga-workflows.yml` проти Rego-полісі (структура `name` / `on` / `concurrency` / `jobs.cleanup_old_workflows.steps[0]`). Якщо `conftest` не в PATH — `ℹ` skip без помилки (паралельні JS-перевірки в `check-ga.mjs` залишаються джерелом істини). Додав `policy` у `files` пакету.
- `check-k8s.mjs`: структурний сорт `patches[]` у `kustomization.yaml` за tuple `[target.kind, target.name, target.namespace, path]` (`localeCompare('en', base)`); поля `target.group` / `target.version` у tuple не входять (діє правило «patches[].target: лише kind і name»). Додатково: вміст inline `patches[i].patch` (literal block scalar — масив JSON6902) сортується за `path`, **але лише** коли всі ops — `add` / `replace` і всі `path` попарно дизʼюнктні (жоден не префікс іншого) — інакше порядок не чіпається, бо `move` / `copy` / `test` / `remove` чи спільні шляхи семантично залежні (RFC 6902). Експортовані чисті валідатори: `kustomizationPatchesSortedViolation`, `kustomizationInlinePatchOpsSortedViolation`.
- `tests/check-k8s-schema.test.mjs`: тести на обидва нові валідатори (приклад із `k8s.mdc`: `ReferenceGrant atlas/apruv` → `apruv/atlas`; `add /spec/minReplicas` + `replace /spec/maxReplicas` → пересорт за `path`; пропуск для `test` / `move` / `copy` / `remove` і недизʼюнктних шляхів типу `/spec` vs `/spec/template`).
- `mdc/k8s.mdc`: розділ «Структурний сорт `patches[]` і inline JSON6902» з обома прикладами «❌/✅».

## [1.8.199] - 2026-05-07

### Added

- `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` перед нормалізацією, потім записує оновлений конфіг (як і раніше — лише якщо вміст реально змінився).
- `tests/auto-rules.test.mjs`: тести `migrateRuleIds` (порядок, дедуплікація, no-op для актуальних id), `detectLegacyRuleIds`, `mergeConfigWithAutoDetected` з legacy `image` у `rules`/`disable-rules`/конфлікті з `image-compress`.

### Changed

- `n-cursor.js`: розширено імпорт з `auto-rules.mjs` (`detectLegacyRuleIds`, `RULE_MIGRATIONS`); виокремлено хелпер `logRuleMigrationsIfAny` (читає сирий конфіг, виводить пояснення, не мутує — мутацію виконує `migrateRuleIds` усередині `mergeConfigWithAutoDetected`). Завдяки цьому `npx @nitra/cursor` сам перебиває `image` на пару наступників — користувачу не треба руками правити `.n-cursor.json`.

## [1.8.198] - 2026-05-07

### Changed

- `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` лишався поки розробник не видаляв його вручну. Додано пояснювальний абзац у правило.
- `image-avif` (mdc v1.0 → v1.1): крок 1 (`npx @nitra/minify-image --src=. --write --avif`) явно требує ≥ 3.3.1 і документує, що sha1-перевірка для регенерації застарілого AVIF тепер живе у CLI; `@nitra/cursor` цю логіку **не дублює**.

## [1.8.197] - 2026-05-07

### Changed

- `image` правило розщеплене на два самостійні: **`image-compress`** (валідація `lint-image` / `.gitignore` / залежностей — стиснення raster/SVG через `@nitra/minify-image`) і **`image-avif`** (генерація AVIF-двійників, переписування raster-посилань у `.vue`/`.html` на `.avif`, прибирання AVIF-сиріт). Це дозволяє тримати компресію всюди, а AVIF — лише там, де його підтримка гарантована (адмінки), вимикаючи його для публічних сайтів через `disable-rules: ["image-avif"]` у `.n-cursor.json` чи опт-аут на рівні пакета (`"@nitra/minify-image": { "disable-avif": true }` у `package.json` сайту).
- `auto-rules.md` / `auto-rules.mjs`: автодетект `image-compress - [bun]` (всюди, де є `package.json`), `image-avif - [vue, image-compress]` (лише для проєктів з `.vue`-файлами і вже активним `image-compress`).
- Видалено `npm/scripts/check-image.mjs` і `npm/mdc/image.mdc` — їх замінили `check-image-compress.mjs` + `check-image-avif.mjs` і `image-compress.mdc` + `image-avif.mdc`.
- Канонічний `lint-image` залишається без `--avif` (його перевіряє `image-compress`); `npx @nitra/cursor fix image-avif` тепер є самостійною командою для AVIF-pipeline.

### Added

- `tests/auto-rules.test.mjs`: тест на `disable-rules: ["image-compress"]` → `image-avif` теж не додається (транзитивна залежність).

## [1.8.194] - 2026-05-07

### Fixed

- `check-image.mjs`: резолвер `resolveImagePath` був інлайн-наївний (`/path` → `<cwd>/<path>`, голий шлях → `null`), що в реальних Quasar/Vite-проєктах давало 0 rewrite-ів і помилковий ріст `failedRefs`. Замінено на `resolveImageCandidates`, який повертає **впорядкований список кандидатів**:
  - `./x.png` / `../x.png` → відносно файла-джерела;
  - `/x.png` → `<packageRoot>/public/x.png`, потім `<packageRoot>/x.png`, потім `<cwd>/x.png` (legacy fallback);
  - голий шлях з принаймні одним `/` (`assets/img.png`, `start-page-ua/logo.png`) → відносно файла-джерела + `<packageRoot>/public/<path>` (Quasar-конвенція);
  - bare без `/` → alias resolver невідомий, посилання тихо пропускаємо (без fail).
- `check-image.mjs`: `cleanupOrphanAvifs` тепер пропускає `.avif` у каталогах артефактів збірки (`build`, `android`, `ios`, `.output`, `.nuxt`, `.cache`) — раніше cleanup міг затирати продукт `bun run build` чи Capacitor sync.

### Added

- `tests/check-image.test.mjs`: 4 нових кейси — Quasar-style `src="/api-page/1.png"` через `<pkg>/public/`; `<img src="assets/images/x.png">` у `.html` через relative-to-source; `src="start-page-ua/logo.png"` у `.vue` через `<pkg>/public/`; cleanup не чіпає AVIF у `build/`/`android/`/`ios/`/`.output/`/`.nuxt/`/`.cache/`.

## [1.8.193] - 2026-05-07

### Fixed

- `check-image.mjs`: cleanup AVIF-сиріт більше не зачіпає `.avif` файли всередині пакетів з опт-аутом (`"@nitra/minify-image": { "disable-avif": true }`). Раніше: пакет з опт-аутом не сканувався на refs → його `.avif` потрапляли у список «сиріт» і видалялись, навіть якщо насправді використовувалися через alias / runtime-обчислений шлях. Тепер `checkVueAvifImports` повертає список абсолютних коренів opt-out пакетів, а `cleanupOrphanAvifs` пропускає `.avif` під ними.
- `check-image.mjs`: запис у `.vue`/`.html` тепер строго послідовний з cleanup (write-then-cleanup): перший виконує `checkVueAvifImports` (per-file `writeFile` після обробки), і тільки після цього `cleanupOrphanAvifs` читає вже оновлені `usedAvifAbs` і видаляє лише дійсних сиріт.
- `check-image.mjs`: введено агреговані лічильники `RewriteStats` (`rewrittenRefs` / `rewrittenFiles` / `failedRefs`) і єдиний фінальний рядок-підсумок `image: rewrote N references in M files; deleted K orphan AVIFs; failed to rewrite L references` — раніше підсумок дублювався per-package і не виокремлював orphan-cleanup vs failed-rewrites.

### Added

- `tests/check-image.test.mjs`: 5 нових кейсів — статичний `<img src="a.png">` авто-переписується (за наявності `a.png` і `a.png.avif`); реактивне `:src="dyn"` залишається незмінним і orphan AVIF видаляється; змішані форми у одному файлі (статичний + import + реактивний + `data-src=`) — переписуються лише покривані; opt-out пакет — AVIF всередині не вважається сиротою; ідемпотентність повторного `check image` на чистому стані.

## [1.8.192] - 2026-05-07

### Added

- `run-shellcheck-text.mjs`: для `lint-text` — перевірка наявності `shellcheck`/`patch`, авто-виправлення через `shellcheck -f diff` + `patch -p1`, фінальний прогін по tracked `*.sh` (git) або `**/*.sh` без `node_modules`.
- `text` (mdc v1.25 → v1.26): **shellcheck** у ланцюжку `lint-text`, рекомендація **`timonwong.shellcheck`**, тригер workflow **`**/\*.sh`**; тести `run-shellcheck-text.test.mjs`.

### Changed

- `check-text.mjs`: `lint-text` має містити `run-shellcheck-text.mjs`; `extensions.json` — `timonwong.shellcheck`.

## [1.8.191] - 2026-05-07

### Added

- `check-npm-module.mjs`: перший заголовок **`## [version]`** у `npm/CHANGELOG.md` має збігатися з **`version`** у `npm/package.json` (найсвіжіший реліз зверху — Keep a Changelog); якщо є незакомічені зміни під **`npm/`**, `version` у робочому `npm/package.json` має відрізнятися від **`HEAD`** (інакше ризик дописати новий функціонал без bump).

### Changed

- `npm-module` (mdc v1.9 → v1.10): розширено **«Build версія»** і **«CHANGELOG»** — чеклист для агента, заборона дописувати нові пункти в уже існуючу секцію релізу замість нового номера; впорядковано `CHANGELOG` (1.8.190 перед 1.8.189).

## [1.8.190] - 2026-05-07

### Added

- `js-run` (mdc v1.4 → v1.5): секція **`jsconfig.json`** — канонічний файл для backend-пакетів із каталогом **`src/`** (NodeNext, `include: ['src/**/*']`); для пакетів без `src/` вимога не діє.
- `check-js-run.mjs`: перевірка наявності та вмісту `jsconfig.json`, якщо в workspace-пакеті (без vite) є **`src/`**; тести у `check-js-run-fixture.test.mjs`.

## [1.8.189] - 2026-05-07

### Added

- Нове правило `adr` (вмикається **вручну** через `.n-cursor.json` `rules`): автоматичне копіювання канонічного `.claude/hooks/capture-decisions.sh` з пакета та керована Stop-група у `.claude/settings.json`, яка викликає скрипт асинхронно (`async: true`, timeout `180`s). Скрипт зчитує JSONL-транскрипт сесії, передає дайджест у LLM CLI і пише чернетки ADR/Runbook/Knowledge у `docs/adr/_inbox/`.
- `capture-decisions.sh`: fallback `claude` → `cursor-agent` (LLM CLI). Якщо `claude` відсутній, береться `cursor-agent -p --mode ask --output-format text`. Моделі задаються через ENV `CAPTURE_DECISIONS_CLAUDE_MODEL` (default `sonnet`) і `CAPTURE_DECISIONS_CURSOR_MODEL` (default `claude-4.6-sonnet-medium`).
- `check-adr.mjs`: програмна перевірка наявності та канонічності `.claude/hooks/capture-decisions.sh`, ADR-групи у `.claude/settings.json`, відсутності дубля у `.claude/settings.local.json`, ігнорування `.claude/hooks/capture-decisions.log` у `.gitignore`, інформативно — наявність бодай одного LLM CLI (`claude`/`cursor-agent`) у `PATH`.
- `tests/check-adr.test.mjs` (7 кейсів) і нові кейси у `tests/sync-claude-config.test.mjs`: copy + Stop-merge + ідемпотентність + автоматичне видалення managed-групи при видаленні `adr` з `rules`.

### Changed

- `sync-claude-config.mjs`: `MANAGED_HOOK_COMMAND_MARKERS` (масив) замість одиничного маркера; `mergeSettings(existing, template, { includeAdrHook })`; `syncClaudeConfig` приймає `rules` і умовно копіює ADR Stop-hook script + додає managed-групу до Stop. `syncClaudeConfig` повертає додатковий прапорець `adrHook`.
- `bin/n-cursor.js`: передає `rules` у `syncClaudeConfig` і логує `.claude/hooks/capture-decisions.sh` у підсумку Claude-конфіга.

## [1.8.188] - 2026-05-07

### Changed

- `vue` (mdc v1.6 → v1.7): для Volar/асетів канонічно лише **`jsconfig.json`** у корені пакета — прибрано альтернативу з `tsconfig.json`. `check-vue.mjs`: перевіряється лише наявність `jsconfig.json`.

## [1.8.187] - 2026-05-07

### Added

- `check-vue.mjs`: перевірка `src/vite-env.d.ts` з `/// <reference types="vite/client" />` та наявності `jsconfig.json` або `tsconfig.json` у корені кожного Vue-пакета (типи для імпортів асетів у `.vue`).

### Changed

- `vue` (mdc v1.5 → v1.6): секція **«Vite client types (Volar, імпорти асетів)»** — обов’язкові `vite-env.d.ts`, jsconfig/tsconfig; застереження щодо вузького `compilerOptions.types`. Оновлено блок **«Перевірка»**.

## [1.8.186] - 2026-05-07

### Added

- `check-js-run.mjs` + `scripts/utils/promise-settimeout-scan.mjs`: програмна перевірка нової секції js-run «Паузи через setTimeout». AST-сканер на `oxc-parser` ловить `new Promise(resolve => setTimeout(resolve, ms))` (з `await` чи без, arrow та function expression, concise та block body, тривіально загорнутий callback `() => resolve()`). Паттерни з передачею значення (`r => setTimeout(() => r(value), ms)`), іншим callback-ом замість resolve, або з додатковими стейтментами в блоці — поза правилом (це не «чиста» пауза).
- `tests/promise-settimeout-scan.test.mjs`: 13 модульних тестів (await/без, block-body, function expression, обгорнутий callback, false-positive guards, multiline номер рядка, кілька входжень, фільтр розширень).
- `tests/check-js-run-fixture.test.mjs`: 2 інтеграційні кейси на `check()` — fail при `await new Promise(r => setTimeout(r, ms))` у workspace-пакеті, pass при `await setTimeout(ms)` з `node:timers/promises`.

### Changed

- `js-run` (mdc v1.3 → v1.4): додано секцію **«Паузи через setTimeout»** — заборонено `await new Promise(resolve => setTimeout(resolve, ms))`, замість цього треба `await setTimeout(ms)` з `node:timers/promises`. Зауваження про затінення глобального `setTimeout` у тому ж файлі (за потреби callback-варіант імпортувати під іншим іменем, наприклад `setTimeoutCb` з `node:timers`).

## [1.8.185] - 2026-05-06

### Changed

- `image` (mdc v1.4 → v1.5): прапорець `--avif` у `lint-image` тепер **заборонений** (інакше `bun run lint` плодив би `.avif` для зображень, що ніде не вживаються); канонічний `lint-image` — `npx @nitra/minify-image --src=. --write`. AVIF-генерацію виконує **виключно** `npx @nitra/cursor fix image`. Секцію «AVIF-імпорти у `.vue`» переписано: тепер вона документує триетапну логіку `check image` — (1) запуск `npx @nitra/minify-image --src=. --write --avif`, (2) авто-заміна raster-посилань у `.vue`/`.html` на `.avif` у кожному workspace-пакеті, (3) прибирання AVIF-сиріт (файли `.avif` без жодного посилання у `.vue`/`.html` видаляються — AVIF лишається лише там, де заміна реально вдалася).
- `check-image.mjs`: `checkLintImageScript` більше не вимагає `--avif`, натомість фейлить за його наявністю; додано `runAvifGeneration` (best-effort `npx ... --avif`, опт-аут через `NITRA_CURSOR_NO_AVIF_RUN=1` для тестів), `cleanupOrphanAvifs` (видаляє `<...>.avif` без живого посилання), `hasAnyRasterImage`, `resolveImagePath`. `checkVueAvifImportsInPackage` тепер не лише валідує, а й переписує raster-посилання на `.avif` (коли AVIF-двійник реально існує на диску); якщо `.avif` нема — фейл, як раніше. Сканування поширено на `.html` файли (раніше було тільки `.vue`).
- `tests/check-image.test.mjs`: `CANONICAL_LINT_IMAGE` без `--avif`; кейс «без `--avif`» перейменовано/перекинуто на «з забороненим `--avif`»; додано тести на orphan-cleanup (`.avif` без посилань видаляється) та авто-заміну raster-імпорту, коли `.avif`-сусід реально існує.

## [1.8.184] - 2026-05-06

### Added

- `check-js-run.mjs`: програмна перевірка нового правила «depcheck у GitHub Actions з path-фільтром». Для кожного backend workspace-пакета сканується `.github/workflows/*.yml`; якщо `on.push.paths` або `on.pull_request.paths` містить glob, що починається з `<rootDir>/`, у job очікується крок `npx depcheck` з `working-directory: <rootDir>` і `--ignores`, що містить мінімум `graphql,bun` (інші значення допустимі). Логіка — у новому `scripts/utils/depcheck-workflow.mjs` (парсинг `--ignores="…"` з підтримкою single/double-quote і unquoted формату; класифікація `missing` / `wrong-cwd` / `missing-ignores`).
- `check-js-run-fixture.test.mjs`: 9 нових кейсів — нема `.github/workflows/`, глобальні paths без скоупу пакета, scoped-paths без depcheck (fail), depcheck з неправильним `working-directory` (fail), без `--ignores` (fail), `--ignores` без `bun` (fail), валідний з extra-ignores (pass), вкладений `cron-jobs/foo/src/**` як scope (pass).
- `.github/workflows/npm-publish.yml`: додано власний крок `npx depcheck --ignores="graphql,bun,bun:test,@nitra/cursor"` з `working-directory: npm`, щоб репо `@nitra/cursor` саме відповідало новому правилу js-run (`paths: ['npm/**']` обмежено пакетом `npm`); extra-ignores потрібні для self-reference `@nitra/cursor` у devDependencies та для `bun:test` як bun-built-in.

## [1.8.183] - 2026-05-06

### Changed

- `ga` (mdc v1.6 → v1.7): додано **універсальну** вимогу — кожен workflow у `.github/workflows/*.yml` обов'язково містить блок `concurrency` з `group: ${{ github.ref }}-${{ github.workflow }}` і `cancel-in-progress: true`. Без винятків — scheduled cleanup-воркфлоу, `pull_request: types: [closed]`, publish-воркфлоу теж. Канонічні приклади у правилі (`clean-ga-workflows.yml`, `clean-merged-branch.yml`, `git-ai.yml`) оновлено й тепер містять цей блок.
- `check-ga.mjs`: нова перевірка `verifyConcurrencyBlock` — запускається на кожному `*.yml` у `.github/workflows/` і структурно перевіряє рівно два поля (`concurrency.group` дорівнює канонічному рядку, `concurrency.cancel-in-progress === true`); відсутність блоку, інший `group` або `cancel-in-progress: false` — fail. Спільний `validateConcurrencyOnRoot` додано в усі канонічні структурні валідатори (clean-ga-workflows, clean-merged-branch, lint-ga, git-ai), щоб ці workflow перевірялися й через шаблонну, і через універсальну логіку.

## [1.8.182] - 2026-05-06

### Changed

- `js-run` (mdc v1.2 → v1.3): додано секцію **«depcheck у GitHub Actions з path-фільтром»** — якщо в `.github/workflows/*.yml` тригер `paths:` обмежено каталогом одного backend-пакета (наприклад `cron-jobs/refund-loyalty-points/**`), у job має бути крок `npx depcheck --ignores="graphql,bun"` з `working-directory`, що вказує на той самий каталог. Список `--ignores` обов'язково містить мінімум `graphql,bun` (peer-залежність GraphQL та рантайм Bun, які depcheck не розпізнає коректно), але може бути розширений значеннями через кому без пробілів. Не застосовується до глобальних workflow без `paths:` або з кореневими `**/*.js` патернами.

## [1.8.181] - 2026-05-06

### Changed

- `scripts/utils/find-package-json-paths.mjs`: винесено спільну `findAllPackageJsonPaths(repoRoot, ignorePaths)` з `check-js-bun-db.mjs` і `check-js-mssql.mjs`, щоб усунути jscpd-дублювання. Самі check-скрипти тепер імпортують її, як інші утиліти з `utils/`.
- `scripts/utils/walkDir.mjs` / `scripts/utils/load-cursor-config.mjs`: trimming trailing-slash переписано з регулярки `/\\/+$/` на `while (s.endsWith('/'))`, щоб уникнути попередження `sonarjs/slow-regex` (потенційний backtracking) — поведінка не змінилась.
- `scripts/check-k8s.mjs`: `failIfExplicitPatchTargetsHaveRedundantGroupVersion` рефакторено — логіка одного запису винесена в новий хелпер `describePatchTargetRedundancy`, основна функція тепер просто будує повідомлення з результату (зменшено sonarjs/cognitive-complexity 24→<15, поведінка не змінилась).
- `scripts/claude-stop-hook.mjs`: `readStdin` і `runStopHookCli` переписані з `new Promise(resolve => …)` на `events.once(stream, 'end' | 'exit')` — підказка `eslint-plugin-promise/avoid-new`, поведінка не змінилась.

### Fixed

- `scripts/utils/bun-sql-scan.mjs`: у JSDoc `findBunSqlUnsafeUseWithoutAllowMarkerInText` прибрано вкладений приклад із backslash-backtick (`sql\\`...\\${value}...\\``), який ламав парсер коментарів oxlint і призводив до false-positive `eslint-plugin-jsdoc(require-param)`/`(require-returns)` на функції з валідним JSDoc — текст переписано без екранованих backtick-ів.
- Десятки `eslint-plugin-jsdoc` правил у `npm/scripts/**` та `npm/tests/**`: додано відсутні описи `@param` / `@returns` (включно зі спільним `ignorePaths`-аргументом у нових сигнатурах walkDir-обгорток), прибрано неприпустимі дефолтні значення в JSDoc (`[name=...]`) — без зміни поведінки.

## [1.8.180] - 2026-05-05

### Changed

- `js-run` (mdc v1.1 → v1.2): додано секцію **«Область застосування»** — правило явно не застосовується до frontend-пакетів (маркер `vite` у `devDependencies`). У браузерному бандлі немає `node:process`, тому заміна `process.env.X` на `import { env } from 'node:process'` ламає рантайм (`TypeError: Cannot read properties of undefined (reading 'X')`); для frontend замість `process.env.NODE_ENV` — `import.meta.env.MODE` / `import.meta.env.PROD`, інші ENV — лише `import.meta.env.VITE_*`. Передумова — інцидент у abie/b2b `site/`, де LLM-агент за правилом замінив `process.env.NODE_ENV` у `src/main.js` і вибив прод-бандл.
- `check-js-run.mjs`: workspace-пакети з `vite` у `devDependencies` пропускаються — нова `packageJsonHasViteDevDependency(pkgJson)`, виклик одразу після `loadPackageJsonAndCheckBunyanDeps`. bunyan-залежність у `package.json` все одно перевіряється (бо це робиться до раннього виходу), але скан `process.env`, `#conn/*` і OTEL configmap для frontend-пакета не запускається. Тести: 2 нові кейси у `check-js-run-fixture.test.mjs` (vite-пакет з прямим `process.env` — pass; non-vite пакет з тим же кодом — fail).

## [1.8.179] - 2026-05-05

### Changed

- `abie` (`check-abie.mjs` / `mdc/abie.mdc`): `httpHealthCheck.requestPath` у `HealthCheckPolicy` (`hc.yaml`) тепер допускає будь-який непорожній шлях від кореня — рядок, що починається з `/` (`/healthz`, `/IsAlive`, `/api/live` тощо), замість жорсткої вимоги `/healthz`. Решта вимог незмінна: `type: HTTP`, `port: 8080`, `targetRef` на headless Service з суфіксом `-hl`. Канонічно рекомендується `/healthz`, але правило не блокує сервіси з власним liveness endpoint. JSDoc у `check-abie.mjs` і опис у `mdc/abie.mdc` приведено у відповідність до коду. Тести: 3 нові кейси у `check-abie.test.mjs` (нестандартний `/IsAlive`, відсутній лідируючий `/`, порожній рядок).

## [1.8.178] - 2026-05-05

### Added

- `vue` (mdc v1.4 → v1.5): у `.vue` SFC заборонено імпортувати Node-нативні модулі — як з префіксом `node:` (`node:timers/promises`, `node:fs` тощо), так і bare-ім’я вбудованого модуля Node (`fs`, `path`, `crypto`, `fs/promises` …). Vue SFC виконується у браузері, де Node API недоступне; такі імпорти ламають білд / рантайм. Логіку з Node API треба виносити у server-side утіліту (backend-пакет монорепо), а у компонентах використовувати браузерні замінники (`window.crypto`, `URL`, глобальний `setTimeout`, `AbortController` тощо). Правило торкається лише `.vue` файлів — `.ts`/`.js`-утіліти, що споживаються server-side, можуть імпортувати Node-built-ins без обмежень.
- `check-vue.mjs`: нова гілка `checkVueNodeImportViolations` обходить `.vue` файли пакета (виключаючи `node_modules`/`dist`/…) і парсить `<script>` блоки тим самим **oxc-parser**’ом — для кожного `staticImport` перевіряє специфікатор через `isNodeBuiltinSpecifier(spec)` (префікс `node:` або bare-ім’я з `module.builtinModules`, з підтримкою підшляхів типу `fs/promises`). У повідомленні про fail виводиться `rel:line` і фрагмент import.
- `vue-forbidden-imports.mjs`: експортовано `isNodeBuiltinSpecifier`, `findForbiddenNodeImportsInText`, `findForbiddenNodeImportsInVueFile` (для не-`.vue` повертає `[]`). Тести: 5 нових кейсів у `vue-forbidden-imports.test.mjs` (built-in detection / `node:` префікс / bare-built-in / лише script-блоки SFC / non-`.vue` skip) та 2 нові integration-кейси у `check-rule-fixtures.test.mjs` (fail при `node:timers/promises` і при bare `fs` у SFC).

## [1.8.177] - 2026-05-05

### Changed

- `changelog` (mdc v2.0): тепер дві моделі бази порівняння на рівні воркспейсу. **npm-published** (`name` + `files` + не `private: true`) — порівняння з опублікованою версією через `npm view <name> version` (git не задіяний; покриває кейс прямих комітів у `main` поза PR-flow). **local-only** (приватні / без `files`) — PR-scoped через `git merge-base <dev> HEAD`, що коректно обробляє: feature-гілку (видно лише унікальні коміти), `main` після merge `dev → main` (diff порожній → правило мовчить), direct-commit на `main` поза PR (ловиться як зміна, що потребує bump). Якщо реєстр недосяжний (офлайн / пакет не публікувався) — fail-safe pass, щоб локальна розробка не блокувалась.
- `check-changelog.mjs`: повний рефактор. Експорт `check(opts?)` з опційним `getPublishedVersion` для підстановки в тестах (CLI калить без аргументів — використовується дефолтний `npm view`-виклик з 10s таймаутом). Класифікація воркспейсів через `isNpmPublishable(pkg)`; для published — `checkPublishedWorkspace`, для local-only — окрема `runLocalOnlyChecks` із власною skip-логікою (no-git / on dev / no dev ref / no merge-base) і `resolveMergeBase(baseRef)` через `git merge-base`. Спільна `verifyChangelogEntry` для обох режимів.
- `n-changelog.mdc` / `mdc/changelog.mdc` (v1.1 → 2.0): переписано під дві моделі з прикладами кейсів.
- Тести `check-changelog.test.mjs`: 16 кейсів (раніше 11) — npm-mode (sync / out-of-sync / no CHANGELOG / no entry / files без `CHANGELOG.md` / offline), local-only skip-логіка, merge-base сценарії (feature-гілка, `main` після merge `dev → main`, direct-commit на `main`), змішаний режим.

## [1.8.176] - 2026-05-05

### Changed

- `changelog` стало єдиним правилом про CHANGELOG для всіх воркспейсів — включно з `npm/`. У `check-npm-module.mjs` прибрано `checkChangelog()` (і константу `CHANGELOG_PATH`); відповідну секцію `## CHANGELOG` видалено з `mdc/npm-module.mdc` (v1.9). Логіка перевірки `npm/CHANGELOG.md` лишилася незмінна за наповненням, але тепер вона PR-scoped (порівняння з `dev`), тож на feature-гілці bump і запис достатньо зробити **один раз — як суму по PR**, без bump-шуму в проміжних комітах.
- `check-changelog.mjs`: додано перевірку `files`-масиву — якщо `<ws>/package.json` його оголошує, у ньому має бути `"CHANGELOG.md"` (приватні воркспейси без `files` цей пункт пропускають). Прибрано `SKIP_WORKSPACE = 'npm'` — `npm/` тепер у звичайному циклі. Хелпер `readPackageJsonOrNull` об'єднує читання `package.json` (раніше було два окремі читачі — `version` і `files`).
- `auto-rules.mjs` / `auto-rules.md`: `changelog` переведено на `AUTO_RULE_DEPENDENCIES = ['bun']` (раніше — пряма умова `packageJsonExists`); тепер послідовно з рештою правил.
- `npm/.claude-template/npm-CLAUDE.md` (і згенерований `npm/CLAUDE.md`): оновлено — посилається на `n-changelog.mdc`, явно згадує `files: ["CHANGELOG.md"]`, наголошує на PR-scoped логіці.
- Тести `check-changelog.test.mjs`: кейс `npm/ пропускається` замінено на `npm/ перевіряється з files=["CHANGELOG.md"]`; додано окремий кейс fail при `files` без `CHANGELOG.md`.

## [1.8.175] - 2026-05-05

### Added

- `k8s.mdc` / `check-k8s.mjs`: у маршрутах Gateway API (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**, група `gateway.networking.k8s.io`) забороняється поле `namespace` у `spec.rules[*].backendRefs[*]` (і однини `backendRef`), якщо його значення збігається з `metadata.namespace` самого маршруту. За замовчуванням Gateway API визначає backend у тому ж namespace, що й маршрут — дублювання у `backendRef` мертве й заважає Kustomize-overlay, що міняє namespace маршруту. Cross-namespace backendRef (з відмінним `namespace`) правило не торкається. Експортовано `collectGatewayApiRouteBackendRefsWithRedundantNamespace(spec, routeNs)`; перевіряється усередині існуючого `failIfGatewayRouteUsesNonHeadlessService` (той самий обхід дерева, що й для headless-перевірки). Додано приклад «погано/добре» у `k8s.mdc` і відповідні юніт-тести.

## [1.8.174] - 2026-05-05

### Added

- Нове правило `changelog` (`mdc/changelog.mdc` + `scripts/check-changelog.mjs`): для «звичайних» Bun-монорепо проєктів вимагає, щоб у кожному workspace, який змінився відносно базової гілки `dev`, у поточному PR було підвищено `version` у `<ws>/package.json` і додано запис `## [version] - YYYY-MM-DD` у `<ws>/CHANGELOG.md` (Keep a Changelog 1.1.0). Перевірка PR-scoped: на самій гілці `dev` пропускається; на feature-гілці bump і запис достатньо зробити **один раз — як суму по всьому PR**, без бамп-шуму в проміжних комітах. Воркспейс `npm/` пропускається — його CHANGELOG покриває окреме правило `npm-module`. У `auto-rules.md` / `auto-rules.mjs` `changelog` додано до автодетекту з умовою «у корені є `package.json`» і до `AUTO_RULE_ORDER` між `capacitor` і `docker`.
- `.n-cursor.json` поле `ignore` (`schemas/n-cursor.json`): тепер не лише сигнал для AI, а й керує обходом усіх `check-*.mjs` / `run-*.mjs` — перелічені каталоги повністю виключаються з `walkDir`, як `node_modules` чи `.git`. Дозволяє безпечно тримати vendored Helm-чарти, генеровані маніфести, legacy-дерева у репо без false-positive’ів від check-скриптів. Розширено опис у схемі (стандартні виключення додавати не треба) і README отримав секцію «Виключення цілих дерев».
- `scripts/utils/load-cursor-config.mjs`: нова утиліта `loadCursorIgnorePaths(root)` — читає поле `ignore` з `.n-cursor.json` і нормалізує до абсолютних posix-шляхів без trailing-slash; пропускає не-рядки та порожні елементи; повертає `[]`, якщо файлу/поля нема або JSON невалідний.
- `scripts/utils/walkDir.mjs`: третій аргумент `ignorePaths` (за замовчуванням `[]`) — каталоги, які пропускаються разом з усім вмістом. Збіг — за повним шляхом (точний або з префіксом `/`), а не за basename, тож `postgres-master-test/` не пропускається коли в ignore лише `postgres-master/`. Стандартні пропуски (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`) працюють як раніше.

### Changed

- Усі скрипти, що обходять FS через `walkDir`, тепер на початку `check()` зчитують `loadCursorIgnorePaths(root)` і передають третім аргументом: `check-abie`, `check-docker`, `check-graphql`, `check-hasura`, `check-image`, `check-js-bun-db`, `check-js-mssql`, `check-js-run`, `check-k8s`, `check-nginx-default-tpl`, `check-npm-module`, `check-vue`, плюс `run-docker`, `run-k8s` і `rename-yaml-extensions`. Wrapper-функції (`findDockerfilePaths`, `findK8sYamlFiles`, `findLintDockerfilePaths`, `findK8sRoots`, `findDefaultConfTemplatePaths`, `migrateDefaultTplConfFiles`) отримали опційний параметр `ignorePaths` для прозорого пробросу.

## [1.8.172] - 2026-05-04

### Changed

- `auto-rules.md` / `auto-rules.mjs`: правило `php` тепер автоувімкається за наявністю `composer.json` у корені, а не за будь-яким `*.php` файлом у дереві. Прибрано константу `PHP_RE`, факт `hasPhpSource` і його збір у `updateFileFacts`/`collectAutoRuleFacts`; натомість у `detectAutoRulesAndSkills` додано прапорець `composerJsonExists` (за аналогією з `packageJsonExists` / `npmDirExists`).

## [1.8.171] - 2026-05-04

### Removed

- `abie.mdc` (v1.17) / `check-abie.mjs`: прибрано перевірку `.github/actionlint.yaml` (мітки `self-hosted-runner` `ua` / `dev` / `ru`). Видалено константи `ABIE_REQUIRED_ACTIONLINT_LABELS`, шаблон файлу та функції `parseActionlintSelfHostedLabels`, `abieMissingActionlintLabels`, `ensureAbieActionlintConfig`; знято відповідні юніт- та інтеграційні тести. Файл `.github/actionlint.yaml` більше не створюється і не валідовується правилом abie.

## [1.8.170] - 2026-05-03

### Changed

- `image.mdc` (v1.4) / `check-image.mjs`: правило перейшло на split-cache `@nitra/minify-image` ≥ **3.2.0**. Замість єдиного `.minify-image-cache.tsv` (який раніше мав бути або в `.gitignore`, або у `files`) тепер: (а) `.n-minify-image.tsv` у корені — committed source of truth з SHA-1/originalSize/size; правило вимагає, щоб він НЕ був у `.gitignore`; (б) `node_modules/.cache/@nitra/minify-image/mtime.tsv` — локальний fast-path, авто-gitignored через `node_modules/`, окремої перевірки не потребує. Додано міграційний fail: якщо `.minify-image-cache.tsv` лежить у корені або згадується в `.gitignore` — підказка з командою `git rm --cached` + `rm -f`. README + image.mdc-секція `## Split-cache` пояснюють, чому коміт hash-кешу осмислений (переживає `git clone`/`checkout`, на відміну від mtime).

## [1.8.169] - 2026-05-03

### Added

- `image.mdc` (v1.3) / `check-image.mjs`: нове правило `image` для оптимізації зображень через [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image). Перевіряє лише локальну конфігурацію (CI-workflow не вимагається — sharp/svgo тягнуть бінарні залежності, цінність на ubuntu-runner-ах нижча за час прогону): скрипт `lint-image` у `package.json` з обовʼязковим викликом `npx @nitra/minify-image --src=. --write --avif` (авто-оптимізація на місці + AVIF-двійники для PNG/JPEG/GIF), `bun run lint-image` в агрегованому `lint`, заборона `@nitra/minify-image` у `dependencies`/`devDependencies` (CLI лише через `npx`, симетрично до `markdownlint-cli2` у `text.mdc`) і рядок `.minify-image-cache.tsv` у `.gitignore` (або, рідше, у `files` пакета). AVIF-двійники (`<name>.<ext>.avif`) зберігаються в git як готові артефакти для віддачі браузеру.
- `image.mdc` (v1.3) / `check-image.mjs`: у `.vue` файлах кожного workspace-пакета raster-посилання мають вести на AVIF-двійник (`...png.avif`) у двох формах: (а) `import x from '...png|jpg|jpeg|gif'` (далі `:src="x"`); (б) прямі статичні атрибути `<img src="...png" />` у `<template>` (Vite перетворює їх на asset-імпорти при збірці). Реактивне `:src="..."` не сканується (JS-вираз — визначається через імпорт, який ловиться у формі (а)); `data-src=`, `obj.src=` у `<script>`, SVG-імпорти теж пропускаємо. Опт-аут на рівні воркспейс-пакета: `"@nitra/minify-image": { "disable-avif": true }` у `package.json` цього пакета. Дедуплікація обходу: при walk-у кореня `.` піддерева інших workspace-роди пропускаються (інакше `App.vue` у `demo/` доповідався б двічі).
- `auto-rules.mjs` / `auto-rules.md`: введено граф залежностей між правилами (`AUTO_RULE_DEPENDENCIES`, синтаксис у `auto-rules.md` — `rule - [other]`). Правило `image` описане як `image - [vue]` — варто автододати лише разом з `vue`, без дублювання вихідної умови «`.vue`-файли». Транзитивне розгортання дозволяє ланцюги (`a → b → c`) і поважає `disable-rules` (якщо vue вимкнено — image теж не додається).
- `vue.mdc` (v1.4) / `check-vue.mjs`: посилено перевірку `vite.config` — окрім згадки `AutoImport` тепер вимагається, щоб у виклику `AutoImport({ imports: [...] })` був присутній рядковий елемент `'vue'`. Без цього `unplugin-auto-import` не надасть `ref` / `createApp` / тощо, і прибирати явні value-імпорти з `'vue'` стає небезпечно (зламає код). Якщо `'vue'` у `imports` відсутній — value-імпорти більше не оголошуються забороненими, а fail зʼявляється на конфізі vite. Балансована екстракція аргументів `AutoImport(...)` через `extractAutoImportCallArgs` працює для багаторядкових об'єктів.

## [1.8.168] - 2026-05-03

### Added

- `lint-ga.mjs`: до preflight на `shellcheck` додано preflight на [`uv`](https://docs.astral.sh/uv/) (постачає `uvx` для `uvx zizmor`). Якщо `uv` відсутній у `PATH` — `n-cursor lint-ga` падає з exit 1 і підказками `brew install uv` / `curl -LsSf https://astral.sh/uv/install.sh | sh` / `pip install uv`. Обидва preflight’и повідомляються незалежно: якщо нема одночасно й `shellcheck`, і `uv`, користувач одразу бачить обидві підказки, а не лише першу.
- `lint-ga.mjs`: винесено внутрішній `PreflightDep` із `bin`/`winBins`/`explanation`/`install`/`successMsg` — однотипний pattern для додавання нових залежностей у preflight без копіпасти.

## [1.8.167] - 2026-05-03

### Added

- `lint-ga.mjs` / `bin/n-cursor.js`: нова CLI-підкоманда `n-cursor lint-ga` (експорт `runLintGaCli`). Робить preflight на `shellcheck` (exit 1 + brew/apt/pacman підказки, коли його немає в `PATH`), тоді послідовно запускає `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .` через `spawnSync` з `stdio: 'inherit'`. Тепер і `bun lint-ga` сигналізує про відсутність shellcheck — раніше це робила лише `check ga`.
- `ga.mdc` (v1.5): канонічний скрипт `lint-ga` у `package.json` тепер `n-cursor lint-ga` (а не `bunx github-actionlint && uvx zizmor …`); `check-ga.mjs` валідує саме цю форму. Виклик через bin-ім’я `n-cursor`, бо `bun run` транслює `npx` у `bun x`, а `bun x @nitra/cursor` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання.

## [1.8.166] - 2026-05-03

### Added

- `ga.mdc` (v1.4) / `check-ga.mjs`: нова перевірка локального [`shellcheck`](https://www.shellcheck.net/) у `PATH`. Без нього `actionlint` (`bunx github-actionlint`) мовчки пропускає shell-перевірки в `run:` блоках, тож локальний `bun lint-ga` дає зелений результат, який падає в CI на `ubuntu-latest` (де shellcheck передвстановлений). `npx @nitra/cursor fix ga` тепер `fail` з підказкою встановлення (`brew install shellcheck` / `apt-get install -y shellcheck` / `pacman -S shellcheck`).

### Changed

- `utils/resolve-cmd.mjs`: явно передаємо `process.env` у `spawnSync('which'/'where', ...)`, щоб у Bun зміни `PATH` у runtime (наприклад, підстановка стабів у тестах) бачилися дочірнім процесом. Без цього Bun використовував би snapshot оточення на старті.

## [1.8.165] - 2026-05-01

### Changed

- `ga.mdc` / `check-ga.mjs`: лінт workflow-ів через [`github-actionlint`](https://www.npmjs.com/package/github-actionlint) замість `node-actionlint`. Канонічний скрипт `lint-ga` тепер `bunx github-actionlint && uvx zizmor --offline --collect=workflows .`; `check-ga` вимагає у `package.json` саме `github-actionlint`.

## [1.8.164] - 2026-05-01

### Added

- `abie.mdc` (v1.16) / `check-abie.mjs`: нова перевірка `.github/actionlint.yaml`. Якщо файл відсутній — `npx @nitra/cursor fix abie` створює його з канонічним вмістом (`self-hosted-runner.labels: ['ua', 'dev', 'ru']`); якщо є — звіряє, що в `self-hosted-runner.labels` присутні мітки `ua`, `dev`, `ru` (порядок, інші мітки й формат лапок дозволені). Експортовано `ABIE_REQUIRED_ACTIONLINT_LABELS`, `parseActionlintSelfHostedLabels`, `abieMissingActionlintLabels`.

## [1.8.163] - 2026-05-01

### Changed

- `check-js-lint.mjs`: `ignorePatterns` у `.oxlintrc.json` тепер звіряється як `rules` — канонічні патерни мають бути присутні, додаткові локальні glob-и дозволені (раніше була строга рівність — будь-який зайвий запис зламував перевірку).
- `check-text.mjs`: `OXFMT_REQUIRED_IGNORE_PATTERNS` доповнено `**/auto-imports.d.ts` (узгоджено з каноном oxlint); перевірка `.oxfmtrc.json` уже працює як subset, тому локальні розширення не падають.
- `js-lint.mdc` / `n-js-lint.mdc`, `text.mdc` / `n-text.mdc`: документовано, що канон задає мінімум `ignorePatterns`, локальне розширення дозволене.

## [1.8.162] - 2026-05-01

### Changed

- `oxlint-canonical-skeleton.json` (та перебудований `oxlint-canonical.json`): `ignorePatterns` тепер містить `["**/schema.graphql", "**/auto-imports.d.ts"]` (узгоджено з `.oxfmtrc.json`). Споживачі мають синхронізувати корінь `.oxlintrc.json` із каноном — `check-js-lint` падатиме, поки масив не збігається.

## [1.8.161] - 2026-05-01

### Added

- `js-bun-db.mdc` (v1.5): нова секція «Прибирати pg-leftover виклики (`.connect()`, `.end()`)». У файлах з Bun SQL прапоруються `<obj>.connect(...)` і `<obj>.end(...)` як ручний lifecycle, який Bun SQL робить за тебе. Opt-out — маркер `// allow-pg-leftover: <причина>` (line- або block-коментар на тому ж рядку чи безпосередньо перед викликом).
- `bun-sql-scan.mjs`: новий сканер `findBunSqlPgLeftoverCallInText` (скоп — лише файли з `import { sql|SQL } from 'bun'`, щоб не давати false-positive на WebSocket/Stream `.end()`). Виділено спільний `hasMarkerCommentNear` для обох opt-in маркерів (`allow-unsafe`, `allow-pg-leftover`).

## [1.8.160] - 2026-05-01

### Changed

- `js-bun-db.mdc` (v1.4): `sql.unsafe(...)` тепер заборонено за замовчуванням — допустимо лише для підстановки назви таблиці/колонки чи dynamic SQL/DDL з code-controlled значенням; інакше переробляємо на tagged template `sql\`...${value}...\``. Кожен легітимний виклик має супроводжуватись маркером`// allow-unsafe: <причина>` на тому ж рядку або рядком вище.
- `check-js-bun-db.mjs`: замість вузької перевірки `sql.unsafe` із tagged-template і інтерполяцією тепер сканер `findBunSqlUnsafeUseWithoutAllowMarkerInText` падає на будь-якому `obj.unsafe(...)` без маркера-коментаря з непорожньою причиною (line- або block-коментар на тому ж рядку чи безпосередньо перед викликом).
- `ast-scan-utils.mjs`: додано `parseProgramAndCommentsOrNull` — окремий вхід для перевірок, яким потрібні коментарі поряд з AST.

## [1.8.159] - 2026-05-01

### Added

- Інтеграція з Claude Code: новий каталог `npm/.claude-template/` із `settings.template.json` (Stop hook + permissions allowlist), `npm-CLAUDE.md` (path-scoped нагадування для роботи в `npm/`) і slash-команду `/n-check`.
- `sync-claude-config.mjs`: під час `npx @nitra/cursor` синхронізує `.claude/settings.json` (merge — користувацькі поля зберігаються, наші hooks ідентифікуються маркером і перезаписуються), `npm/CLAUDE.md` і slash-команди checks.
- Subcommand `npx @nitra/cursor stop-hook` — точка входу Stop hook Claude Code (читає stdin, виходить 0 при `stop_hook_active=true` для захисту від рекурсії, інакше викликає `check`).
- Поле `claude-config` у `.n-cursor.json` (default `true`) для опт-ауту.
- Тести `npm/tests/sync-claude-config.test.mjs` — merge allow-list/hooks, інтеграція, ідемпотентність, опт-аут (12 кейсів).

### Changed

- `npm/schemas/n-cursor.json`: додано опис поля `claude-config`.
- `npm/package.json`: `.claude-template` додано в масив `files`, щоб публікувався з пакетом.

## [1.8.158] - 2026-05-01

### Changed

- `check-hasura.mjs`: файл `.env` без імені (локальний файл розробника) виключено з перевірки `HASURA_GRAPHQL_ENDPOINT` — скануються лише `*.env` із префіксом (`dev.env`, `production.env` тощо).
- `hasura.mdc`: явно зафіксовано виключення для `.env` без імені.

## [1.8.157] - 2026-04-30

### Added

- Правило `npm-module.mdc`: секція **CHANGELOG** — разом із bump build-версії в `npm/package.json` обовʼязково оновлювати `npm/CHANGELOG.md` (Keep a Changelog).
- `check-npm-module.mjs`: перевірка наявності `npm/CHANGELOG.md`, наявності в `files` у `npm/package.json` і запису для поточної версії.
- `check-hasura.mjs`: перевірка `HASURA_GRAPHQL_ENDPOINT` у `*.env` для проєктів **nitra** і **abie** — має бути внутрішнім кластерним URL виду `http://<service>.<namespace>.svc.<cluster>.internal:<port>`; за наявності `hasura/k8s/base/svc-hl.yaml` та `hasura/k8s/base/namespace.yaml` додатково звіряється `<service>` і `<namespace>`.

### Changed

- `npm/package.json`: `CHANGELOG.md` додано в масив `files`, щоб публікувався разом із пакетом.
- `hasura.mdc`: текст правила переформульовано як людинозрозумілий з прикладом і посиланням на `check-hasura.mjs`.
