# Changelog

## [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`.
