@nitra/cursor 1.27.3 → 1.27.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/hooks/capture-decisions.sh +1 -1
- package/.claude-template/hooks/normalize-decisions.sh +1 -1
- package/.pi-template/extensions/n-cursor-adr/tsconfig.json +1 -0
- package/CHANGELOG.md +61 -32
- package/bin/n-cursor.js +2 -2
- package/package.json +1 -1
- package/rules/bun/bun.mdc +1 -1
- package/rules/bun/policy/package_json/package_json.rego +14 -4
- package/rules/changelog/js/consistency.mjs +1 -1
- package/rules/image-avif/js/avif_generation.mjs +2 -2
- package/rules/js-lint/js-lint.mdc +1 -1
- package/rules/js-mssql/js/deps.mjs +1 -1
- package/rules/k8s/js/manifests.mjs +19 -19
- package/rules/k8s/k8s.mdc +9 -9
- package/rules/k8s/policy/network_policy/network_policy.rego +5 -5
- package/rules/tauri/js/cargo_mutants_config.mjs +3 -3
- package/rules/tauri/tauri.mdc +2 -2
- package/rules/test/coverage/coverage.mjs +4 -4
- package/rules/test/js/cargo_mutants_config.mjs +2 -2
- package/rules/test/js/data/cargo_mutants_config/mutants.toml.baseline +1 -1
- package/rules/test/js/data/stryker_config/stryker.config.baseline.mjs +1 -1
- package/rules/test/js/stryker_config.mjs +3 -3
- package/rules/test/test.mdc +5 -5
- package/rules/text/js/forbidden-prettier.mjs +59 -0
- package/rules/text/js/formatting.mjs +1 -4
- package/rules/text/policy/package_json/package_json.rego +16 -0
- package/rules/text/text.mdc +1 -1
- package/rules/vue/vue.mdc +1 -1
- package/schemas/v8r-catalog.json +6 -0
- package/scripts/coverage-fix.mjs +12 -12
- package/scripts/lib/run-lint-cli.mjs +5 -5
- package/scripts/lib/run-lint-step.mjs +1 -1
- package/scripts/lib/run-rule.mjs +1 -1
- package/scripts/lib/timing-summary.mjs +3 -3
- package/scripts/post-tool-use-fix.mjs +4 -4
- package/scripts/sync-claude-config.mjs +2 -2
- package/scripts/utils/ensure-gitignore-entries.mjs +2 -2
- package/scripts/utils/resolve-cargo-manifest.mjs +1 -1
- package/scripts/utils/resolve-js-root.mjs +1 -1
- package/scripts/utils/walkDir.mjs +2 -2
- package/skills/coverage-fix/SKILL.md +15 -12
- package/skills/fix-tests/SKILL.md +13 -13
- /package/rules/k8s/policy/network_policy/template/{statefulset.snippet.yaml → stateful-set.snippet.yaml} +0 -0
|
@@ -36,7 +36,7 @@ log() { printf '%s %s\n' "$(date -Iseconds)" "$*" >> "$LOG"; }
|
|
|
36
36
|
|
|
37
37
|
# Підвантажуємо спільний helper (sourcing — не sub-shell, функції видимі поточному скрипту).
|
|
38
38
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
39
|
-
# shellcheck source=lib/tooling-only.sh
|
|
39
|
+
# shellcheck source=npm/.claude-template/hooks/lib/tooling-only.sh
|
|
40
40
|
. "$SCRIPT_DIR/lib/tooling-only.sh"
|
|
41
41
|
|
|
42
42
|
log "fired: $SESSION_ID"
|
|
@@ -41,7 +41,7 @@ log() { printf '%s %s\n' "$(date -Iseconds)" "$*" >> "$LOG"; }
|
|
|
41
41
|
|
|
42
42
|
# Підвантажуємо спільний helper (sourcing — не sub-shell, функції видимі поточному скрипту).
|
|
43
43
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
44
|
-
# shellcheck source=lib/tooling-only.sh
|
|
44
|
+
# shellcheck source=npm/.claude-template/hooks/lib/tooling-only.sh
|
|
45
45
|
. "$SCRIPT_DIR/lib/tooling-only.sh"
|
|
46
46
|
|
|
47
47
|
# Витягає поле `transcript:` з YAML frontmatter ADR-чернетки.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig.json",
|
|
2
3
|
"$comment": "TS-конфіг для pi.dev extension. Не компілюється сам пакетом (синкається як є у .pi/extensions/<name>/), потрібен лише для IDE/TS-сервера у проєкті-споживачі, щоб резолвити node:* модулі. Споживачу треба мати @types/node у devDependencies (зазвичай уже є транзитивно).",
|
|
3
4
|
"compilerOptions": {
|
|
4
5
|
"module": "NodeNext",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.27.6] - 2026-05-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`test/coverage` — розширені мутаційні тести**: 18 нових тест-кейсів у `rules/test/coverage/tests/coverage.test.mjs` для покриття 19 вцілілих мутантів (StringLiteral, ConditionalExpression, ArrowFunction, BlockStatement-гілки). Загальна кількість тестів: 37.
|
|
12
|
+
|
|
13
|
+
## [1.27.5] - 2026-05-26
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`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 старий короткий список.
|
|
18
|
+
- **`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`).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`rules/text/js/formatting.mjs`**: inline стародавній цикл по `['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']` видалено — Prettier-FS-сторону тепер цілком покриває окремий concern `text.forbidden-prettier`.
|
|
23
|
+
- **`rules/text/text.mdc` (`## Перевірка`)**: явно зафіксовано, що `npx @nitra/cursor fix text` падає на `.prettierignore`, `.prettierrc*`, `prettier.config.*` і будь-який `package.json#scripts` із токеном `prettier`.
|
|
24
|
+
|
|
25
|
+
## [1.27.4] - 2026-05-26
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`lint-text` / v8r**: локальний catalog тепер явно матчить `tsconfig.json` у `.pi/extensions/*/` та `npm/.pi-template/extensions/*/`, щоб schema validation не падала на hidden/template шляхах.
|
|
30
|
+
- **`text` rule**: додано Rego-перевірку, що забороняє `prettier` у `package.json#scripts`; canonical formatter лишається `oxfmt`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **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/`.
|
|
35
|
+
|
|
7
36
|
## [1.27.3] - 2026-05-26
|
|
8
37
|
|
|
9
38
|
### Fixed
|
|
@@ -12,19 +41,19 @@
|
|
|
12
41
|
|
|
13
42
|
### Changed
|
|
14
43
|
|
|
15
|
-
- **
|
|
44
|
+
- **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 екземпляри).
|
|
16
45
|
- **`npm/rules/vue/vue.mdc` v2.4**: секцію «Тестування» приведено у відповідність із новим canon-ом (`test.mdc` v2.4 → Vitest+happy-dom). Замість прямого заперечення Vitest у Vue-проєктах рекомендується frontend-варіант `vitest.config.js` з `environment: 'happy-dom'`.
|
|
17
46
|
|
|
18
47
|
## [1.27.2] - 2026-05-26
|
|
19
48
|
|
|
20
49
|
### Changed
|
|
21
50
|
|
|
22
|
-
- **`package.json#dependencies`**: `@anthropic-ai/claude-code` (^1.0.0) → `@anthropic-ai/claude-agent-sdk` (^0.3.0). У claude-code v2.x пакет реструктуризовано в CLI-only (
|
|
51
|
+
- **`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` зберігаються.
|
|
23
52
|
- **`scripts/coverage-fix.mjs`**: дзеркальна заміна імпорту `@anthropic-ai/claude-code` → `@anthropic-ai/claude-agent-sdk`. Тіло споживача без змін.
|
|
24
53
|
|
|
25
54
|
### Fixed
|
|
26
55
|
|
|
27
|
-
- **ADR Stop-hook у Node v26 / Zed**: `capture-decisions.sh` спавнить bare `claude -p` як subprocess. PATH у Zed Claude Agent-сесіях має `node_modules/.bin` попереду `/opt/homebrew/bin`, тож
|
|
56
|
+
- **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 без правок.
|
|
28
57
|
|
|
29
58
|
## [1.27.1] - 2026-05-26
|
|
30
59
|
|
|
@@ -36,7 +65,7 @@
|
|
|
36
65
|
|
|
37
66
|
### Changed
|
|
38
67
|
|
|
39
|
-
- **`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
|
|
68
|
+
- **`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).
|
|
40
69
|
- **`rules/test/js/stryker_config.mjs`**: концерн тепер копіює два canonical baseline-и у кожен JS-root: `stryker.config.mjs` + `vitest.config.js`. Ідемпотентність збережена — обидва файли копіюються лише якщо ще немає.
|
|
41
70
|
- **`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.
|
|
42
71
|
- **`rules/test/policy/package_json/template/package.json.contains.json`**: канон scripts тепер містить додатково `"test": ["vitest"]` (substring-вимога). `coverage` як було — `["n-cursor coverage"]`.
|
|
@@ -52,23 +81,23 @@
|
|
|
52
81
|
|
|
53
82
|
### Added
|
|
54
83
|
|
|
55
|
-
- **`rules/tauri/js/cargo_mutants_config.mjs`**: новий концерн tauri-правила. Для кожного `<ws>/src-tauri/Cargo.toml`
|
|
84
|
+
- **`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`; частина ключів відсутня → додає лише відсутні в окремий блок у кінці, без зміни існуючих значень.
|
|
56
85
|
- **`rules/tauri/js/tests/cargo_mutants_config.test.mjs`**: 7 тестів — silent skip без Tauri, створення baseline, ідемпотентність (повторний прогон байт-в-байт), збереження ручних налаштувань, partial-merge (додаються лише відсутні ключі), кілька src-tauri у різних workspaces, augmentation поверх нейтрального test-rule baseline.
|
|
57
86
|
- **`rules/tauri/tauri.mdc` v1.3**: нові розділи «Виявлення проєкту Tauri» (опис маркерів і workspace-обходу) та «Mutation-testing: семантика app shell та platform bridge» з фіксованою семантикою boundary-файлів і ідемпотентністю взаємодії з `test`-rule.
|
|
58
87
|
|
|
59
88
|
### Changed
|
|
60
89
|
|
|
61
90
|
- **`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'а.
|
|
62
|
-
- **`rules/test/test.mdc` v2.3**: додано розділ «Універсальний baseline і framework-specific tuning» — `test` володіє нейтральним baseline, framework-rules (tauri, capacitor) зобов'язані доповнювати
|
|
91
|
+
- **`rules/test/test.mdc` v2.3**: додано розділ «Універсальний baseline і framework-specific tuning» — `test` володіє нейтральним baseline, framework-rules (tauri, capacitor) зобов'язані доповнювати без дублювання і не перетирати ручні налаштування.
|
|
63
92
|
- **`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 живе в одному з пакетів, а не в корені.
|
|
64
|
-
- **`rules/test/js/tests/cargo_mutants_config.test.mjs`**: тест
|
|
93
|
+
- **`rules/test/js/tests/cargo_mutants_config.test.mjs`**: тест baseline тепер перевіряє відсутність framework-specific ключів (`additional_cargo_test_args`, `exclude_globs`) у нейтральному baseline-файлі.
|
|
65
94
|
|
|
66
95
|
## [1.26.2] - 2026-05-26
|
|
67
96
|
|
|
68
97
|
### Changed
|
|
69
98
|
|
|
70
|
-
- **`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 до будь-якого масиву в
|
|
71
|
-
- **`rules/js-lint/js-lint.mdc` v1.26**: оновлено приклад `.jscpd.json` і опис під ним (тепер посилається на
|
|
99
|
+
- **`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`.
|
|
100
|
+
- **`rules/js-lint/js-lint.mdc` v1.26**: оновлено приклад `.jscpd.json` і опис під ним (тепер посилається на snippet як source of truth і пояснює, чому `**/CHANGELOG.md` у каноні).
|
|
72
101
|
|
|
73
102
|
## [1.26.1] - 2026-05-26
|
|
74
103
|
|
|
@@ -84,7 +113,7 @@
|
|
|
84
113
|
|
|
85
114
|
### Added
|
|
86
115
|
|
|
87
|
-
- **`k8s/js/manifests.mjs`**: нова `collectHttpRouteIngressForWorkload(dir, appLabel, fail)` —
|
|
116
|
+
- **`k8s/js/manifests.mjs`**: нова `collectHttpRouteIngressForWorkload(dir, appLabel, fail)` — визначає HTTPRoute → `-hl` Service → `selector.app` mapping і повертає унікальні TCP-порти з `backendRefs[].port` для workload з міткою `appLabel`. Викликається з `appendNetworkPolicyDocuments` і `regenerateLegacyNetworkPolicyDocsInFile` під час `check k8s`.
|
|
88
117
|
- **`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.
|
|
89
118
|
- **`k8s.mdc` v1.42**: новий розділ «HTTPRoute → NetworkPolicy ingress (GCLB + Envoy)» з описом mapping і прикладом NetworkPolicy для HTTPRoute-paired workload.
|
|
90
119
|
|
|
@@ -97,7 +126,7 @@
|
|
|
97
126
|
### Fixed
|
|
98
127
|
|
|
99
128
|
- **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-правила).
|
|
100
|
-
- **`k8s/js/manifests.mjs`**: `JSON.parse(JSON.stringify(...))` → `structuredClone(...)` (unicorn `prefer-structured-clone`); інверсія
|
|
129
|
+
- **`k8s/js/manifests.mjs`**: `JSON.parse(JSON.stringify(...))` → `structuredClone(...)` (unicorn `prefer-structured-clone`); інверсія запереченої умови в `validateNetworkPolicyForWorkload` (eslint `no-negated-condition`).
|
|
101
130
|
- **`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`.
|
|
102
131
|
- **`scripts/tests/post-tool-use-fix.test.mjs`**: fake-child перероблено з `EventEmitter` на duck-typed `addListener`/`removeListener` (unicorn `prefer-event-target`).
|
|
103
132
|
- **`scripts/tests/cli-entry.test.mjs`**: symlink-тест /tmp ↔ /private/tmp використовує `mkdtempSync` з префіксом, зібраним з частин (sonarjs `publicly-writable-directories`).
|
|
@@ -117,38 +146,38 @@
|
|
|
117
146
|
|
|
118
147
|
### Added
|
|
119
148
|
|
|
120
|
-
- **`stryker.config.mjs` baseline**: `incremental: true` + `incrementalFile: 'reports/stryker/incremental.json'` — Stryker зберігає результати між запусками і відновлює після краш/kill (
|
|
149
|
+
- **`stryker.config.mjs` baseline**: `incremental: true` + `incrementalFile: 'reports/stryker/incremental.json'` — Stryker зберігає результати між запусками і відновлює після краш/kill (сигнал ОС). Важливо для машин з обмеженою RAM де Stryker вбивається системою після ~100 мутантів.
|
|
121
150
|
|
|
122
151
|
## [1.25.1] - 2026-05-26
|
|
123
152
|
|
|
124
153
|
### Added
|
|
125
154
|
|
|
126
|
-
- **`skills/coverage-fix/SKILL.md`** — автономна команда `/n-coverage-fix`: запускає `n-cursor coverage`, читає JSON-масив
|
|
155
|
+
- **`skills/coverage-fix/SKILL.md`** — автономна команда `/n-coverage-fix`: запускає `n-cursor coverage`, читає JSON-масив вцілілих мутантів із секції `## Вцілілі мутанти` у COVERAGE.md і ітеративно пише тести до конвергенції (max 3 ітерації). Включає заборону паралельного запуску (Stryker пише в одну директорію).
|
|
127
156
|
|
|
128
157
|
### Changed
|
|
129
158
|
|
|
130
|
-
- **`rules/test/coverage/coverage.mjs` → `renderMarkdown`**: секція
|
|
159
|
+
- **`rules/test/coverage/coverage.mjs` → `renderMarkdown`**: секція вцілілих мутантів перейменована `## Recommendations` → `## Вцілілі мутанти`; доданий ` ```json ` блок з масивом survived перед таблицею — парситься skills `/n-fix-tests` і `/n-coverage-fix`.
|
|
131
160
|
- **`skills/fix-tests/SKILL.md`**: конвенція test-файлів оновлена — цільовий файл завжди `<dir>/tests/<basename>.test.mjs`; якщо знайдено co-located тест (`.test.js`/`.test.mjs`) — переноситься в `tests/` з оновленням imports.
|
|
132
161
|
|
|
133
162
|
## [1.19.2] - 2026-05-25
|
|
134
163
|
|
|
135
164
|
### Fixed
|
|
136
165
|
|
|
137
|
-
- **`js-lint` coverage провайдер**: виправлено `bunx stryker run` → `bunx @stryker-mutator/core run`. Стара команда (`bunx stryker`)
|
|
166
|
+
- **`js-lint` coverage провайдер**: виправлено `bunx stryker run` → `bunx @stryker-mutator/core run`. Стара команда (`bunx stryker`) визначає deprecated unscoped-пакет без CLI, через що `mutation.json` не створювався і coverage падав з помилкою.
|
|
138
167
|
- **`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-тестах.
|
|
139
168
|
|
|
140
169
|
## [1.19.1] - 2026-05-25
|
|
141
170
|
|
|
142
171
|
### Fixed
|
|
143
172
|
|
|
144
|
-
- **`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 сусіднього тесту,
|
|
173
|
+
- **`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с.
|
|
145
174
|
- **`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'у не вистачає.
|
|
146
175
|
|
|
147
176
|
## [1.19.0] - 2026-05-25
|
|
148
177
|
|
|
149
178
|
### Added
|
|
150
179
|
|
|
151
|
-
- **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-*`
|
|
180
|
+
- **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/`.
|
|
152
181
|
- `npm/bin/n-cursor.js`: константа `PI_SKILLS_DIR='.pi/skills'`, функція `formatPiSkillFrontmatter(name, desc)`, синки `syncPiSkills`/`syncLocalOnlyPiSkills` + cleanups `removeOrphanManagedPiSkillDirs`/`removeOrphanLocalPiSkillDirs`. Новий `runSyncStep('❌ Pi skills: ', …)` після Commands-блоку у головному потоці.
|
|
153
182
|
|
|
154
183
|
## [1.18.3] - 2026-05-25
|
|
@@ -167,10 +196,10 @@
|
|
|
167
196
|
|
|
168
197
|
### Fixed
|
|
169
198
|
|
|
170
|
-
- **`scripts/cli-entry.mjs::isRunAsCli`** + **`scripts/lib/run-rule-cli.mjs::isRunAsCli`** — функція приймала `()` без аргументів і всередині дивилася на власний `import.meta.url`, а не на caller'а. Через те, що `import.meta` лексично прив'язаний до файлу, де записаний, helper-функція
|
|
171
|
-
- **Fix:** функція тепер приймає `metaUrl` параметром: `isRunAsCli(import.meta.url)`. Реалізація через
|
|
199
|
+
- **`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-звіту.
|
|
200
|
+
- **Fix:** функція тепер приймає `metaUrl` параметром: `isRunAsCli(import.meta.url)`. Реалізація через порівняння канонічних шляхів — це знімає різницю «symlink vs canonical» (macOS `/tmp` ↔ `/private/tmp`, pnpm content-addressable links, `node_modules/.bin/*` shim).
|
|
172
201
|
- **Консолідація:** `run-rule-cli.mjs::isRunAsCli` тепер `export { isRunAsCli } from '../cli-entry.mjs'` — одне джерело правди. Existing import paths у callers лишилися без змін.
|
|
173
|
-
- **
|
|
202
|
+
- **Call sites:** всі ~40 викликів `isRunAsCli()` оновлено на `isRunAsCli(import.meta.url)`.
|
|
174
203
|
- **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`.
|
|
175
204
|
|
|
176
205
|
## [1.18.0] - 2026-05-25
|
|
@@ -197,17 +226,17 @@
|
|
|
197
226
|
### Changed
|
|
198
227
|
|
|
199
228
|
- Концерн `stryker_config`: gitignore-патерн `**/reports/stryker/.tmp/` + `**/reports/stryker/mutation.json` замінено на один broader `**/reports/stryker/` — увесь каталог Stryker-output-у. Покриває не лише `.tmp/` + `mutation.json`, а й HTML/dashboard-репорти якщо користувач додасть інші reporter-и. Існуючі дрібніші патерни в `.gitignore` користувача не видаляються (idempotent helper лише дописує), але стають надлишковими — користувач може почистити вручну за бажанням.
|
|
200
|
-
- `test.mdc` 2.1 → 2.2: оновлено опис gitignore-керування під новий broader
|
|
229
|
+
- `test.mdc` 2.1 → 2.2: оновлено опис gitignore-керування під новий broader pattern.
|
|
201
230
|
|
|
202
231
|
## [1.17.3] - 2026-05-24
|
|
203
232
|
|
|
204
233
|
### Added
|
|
205
234
|
|
|
206
|
-
- Концерн `stryker_config` правила `test` тепер
|
|
235
|
+
- Концерн `stryker_config` правила `test` тепер без дублювання додає у кореневий `.gitignore` патерни Stryker-output-у:
|
|
207
236
|
- `**/reports/stryker/.tmp/` — in-place backup-каталог (з baseline-у `tempDirName`).
|
|
208
237
|
- `**/reports/stryker/mutation.json` — JSON-репорт мутацій.
|
|
209
238
|
- Header-секція `# Stryker mutation testing (test.mdc)`, sectioning через `ensureGitignoreEntries`.
|
|
210
|
-
- Спільний helper `npm/scripts/utils/ensure-gitignore-entries.mjs` — append-only
|
|
239
|
+
- Спільний helper `npm/scripts/utils/ensure-gitignore-entries.mjs` — append-only модуль оновлення `.gitignore` з header-секціями. Idempotent (точне співпадіння рядка після `trim`), створює файл якщо немає, зберігає trailing-newline. 5 unit-тестів.
|
|
211
240
|
|
|
212
241
|
### Changed
|
|
213
242
|
|
|
@@ -220,8 +249,8 @@
|
|
|
220
249
|
|
|
221
250
|
- Правило `test`: два нових концерни — `stryker_config` і `cargo_mutants_config`. Self-gating через `.n-cursor.json#rules`: концерн активний лише якщо відповідне залежне правило (`js-lint` / `rust`) enabled. **Iterate-all-workspaces**: при відсутності цільового файлу копіює canonical baseline у КОЖЕН workspace-каталог (не лише workspaces[0]).
|
|
222
251
|
- `stryker.config.mjs` у кожному JS-root (всі workspaces з package.json, або cwd у single-package) — мінімум для роботи з `bun test`.
|
|
223
|
-
- `.cargo/mutants.toml` у каталозі КОЖНОГО Cargo.toml-маніфесту: корінь + workspaces (з підтримкою Tauri-патерну `<ws>/src-tauri/Cargo.toml`) —
|
|
224
|
-
- Спільні
|
|
252
|
+
- `.cargo/mutants.toml` у каталозі КОЖНОГО Cargo.toml-маніфесту: корінь + workspaces (з підтримкою Tauri-патерну `<ws>/src-tauri/Cargo.toml`) — коментар-плейсхолдер; cargo-mutants має робочі defaults.
|
|
253
|
+
- Спільні модулі визначення у `npm/scripts/utils/`: `resolveJsRoot` (single, для coverage-провайдера) + `resolveAllJsRoots` (plural, для test-концерну); `resolveCargoManifest` (single) + `resolveAllCargoManifests` (plural). Coverage-провайдери js-lint і rust повторно використовують single-варіанти.
|
|
225
254
|
|
|
226
255
|
### Changed
|
|
227
256
|
|
|
@@ -524,7 +553,7 @@
|
|
|
524
553
|
|
|
525
554
|
### Fixed
|
|
526
555
|
|
|
527
|
-
- ADR-хук **`normalize-decisions.sh`**: `merge-into` більше не падає в `skip … target missing`, коли драфт треба влити в clean-ADR, який створює `rewrite` того самого батча, або в наявний clean-ADR, на який LLM послався голим `<slug>.md` без timestamp-префікса. Операції тепер застосовуються двома впорядкованими групами (спершу `delete`/`rewrite`, потім `merge-into`), а `target`
|
|
556
|
+
- 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).
|
|
528
557
|
|
|
529
558
|
## [1.13.67] - 2026-05-21
|
|
530
559
|
|
|
@@ -638,7 +667,7 @@
|
|
|
638
667
|
|
|
639
668
|
### Fixed
|
|
640
669
|
|
|
641
|
-
- `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` навіть на чистих
|
|
670
|
+
- `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`.
|
|
642
671
|
|
|
643
672
|
## [1.13.50] - 2026-05-19
|
|
644
673
|
|
|
@@ -674,7 +703,7 @@
|
|
|
674
703
|
|
|
675
704
|
### Fixed
|
|
676
705
|
|
|
677
|
-
- `inlineTemplateLinks` tests: оновлено очікувані рядки для фікстури `__fixtures__/inline-template/fix/foo/template/snippet.json` (перейшла на форматований варіант `{ "key": "val" }` ще в 1.13.38) та для
|
|
706
|
+
- `inlineTemplateLinks` tests: оновлено очікувані рядки для фікстури `__fixtures__/inline-template/fix/foo/template/snippet.json` (перейшла на форматований варіант `{ "key": "val" }` ще в 1.13.38) та для інтеграційними testing тесту `security.mdc` (snippet `package.json` тепер multi-line після lint-проходу). Без зміни рантайм-логіки.
|
|
678
707
|
- `check-ga` тестова фікстура `setupCanonicalGaProject`: додано крок `Install conftest` у `.github/workflows/lint-ga.yml`, без якого `ga.lint_ga` rego-полісі забороняє workflow і `check()` повертав 1.
|
|
679
708
|
|
|
680
709
|
## [1.13.44] - 2026-05-18
|
|
@@ -1517,7 +1546,7 @@
|
|
|
1517
1546
|
|
|
1518
1547
|
- **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.
|
|
1519
1548
|
- **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` разом з виклик-сайтом.
|
|
1520
|
-
- **npm-module — `check-npm-module.mjs` лишає лише FS/AST-частину:** функція `checkNoTestsInPublishedFiles`
|
|
1549
|
+
- **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`).
|
|
1521
1550
|
- **npm/package.json — приведено до правила:** видалено секцію `devDependencies` (`@nitra/cursor` вже є у корені як `workspace:*`). `policy/**/*_test.rego` свідомо лишаються у tarball — як виняток для conftest-конвенції.
|
|
1522
1551
|
- **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`.
|
|
1523
1552
|
|
|
@@ -1619,7 +1648,7 @@
|
|
|
1619
1648
|
|
|
1620
1649
|
### Added
|
|
1621
1650
|
|
|
1622
|
-
- **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 для одиничного
|
|
1651
|
+
- **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.
|
|
1623
1652
|
- **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` фікстури.
|
|
1624
1653
|
- **lint-rego:** додано опційний крок `conftest verify` у `npm/scripts/lint-rego.mjs` після `regal lint` для виконання `*_test.rego`. Якщо `conftest` не у PATH — крок мовчки пропускається з install-hint.
|
|
1625
1654
|
|
|
@@ -2187,7 +2216,7 @@
|
|
|
2187
2216
|
|
|
2188
2217
|
### Added
|
|
2189
2218
|
|
|
2190
|
-
- `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
|
|
2219
|
+
- `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` і відповідні юніт-тести.
|
|
2191
2220
|
|
|
2192
2221
|
## [1.8.174] - 2026-05-05
|
|
2193
2222
|
|
|
@@ -2225,7 +2254,7 @@
|
|
|
2225
2254
|
### Added
|
|
2226
2255
|
|
|
2227
2256
|
- `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 як готові артефакти для віддачі браузеру.
|
|
2228
|
-
- `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-вираз —
|
|
2257
|
+
- `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/` доповідався б двічі).
|
|
2229
2258
|
- `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 теж не додається).
|
|
2230
2259
|
- `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` працює для багаторядкових об'єктів.
|
|
2231
2260
|
|
package/bin/n-cursor.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* дістає `tool_input.file_path`, маршрутизує його у відповідні правила
|
|
14
14
|
* (`*.mjs` → `js-lint`, `*.vue` → `js-lint style-lint vue` тощо) і викликає
|
|
15
15
|
* `fix` лише з ними. Прописується автоматично в `.claude/settings.json`.
|
|
16
|
-
* `npx \@nitra/cursor lint` — оркестратор lint-ланцюжка з кореневого `package.json` з
|
|
16
|
+
* `npx \@nitra/cursor lint` — оркестратор lint-ланцюжка з кореневого `package.json` з вимірюванням часу
|
|
17
17
|
* кожного `lint-*` / `oxfmt` скрипта (fail-fast); канонічна заміна
|
|
18
18
|
* раніше ручного `lint-ga && lint-js && …` агрегатора.
|
|
19
19
|
* `npx \@nitra/cursor lint-ga` — канонічний lint-ga (ga.mdc): preflight на `shellcheck` →
|
|
@@ -1456,7 +1456,7 @@ try {
|
|
|
1456
1456
|
break
|
|
1457
1457
|
}
|
|
1458
1458
|
case 'lint': {
|
|
1459
|
-
// Оркестратор lint-ланцюжка з
|
|
1459
|
+
// Оркестратор lint-ланцюжка з вимірюванням часу на кожен крок (fail-fast).
|
|
1460
1460
|
// Замінює раніше використовуваний агрегатор `bun run lint-ga && bun run lint-js && …` у root package.json.
|
|
1461
1461
|
process.exitCode = runLintCli()
|
|
1462
1462
|
|
package/package.json
CHANGED
package/rules/bun/bun.mdc
CHANGED
|
@@ -42,7 +42,7 @@ Lockfile у репозиторії: `bun.lock`.
|
|
|
42
42
|
- Якщо залежність потрібна лише одному пакету, додавати її в директорії цього пакета.
|
|
43
43
|
- У CI та локально запускати скрипти через `bun run`.
|
|
44
44
|
|
|
45
|
-
В кореневому в package.json не повинно бути `dependencies`, а в `devDependencies` — тільки модулі `@nitra/*`. Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn.
|
|
45
|
+
В кореневому в package.json не повинно бути `dependencies`, а в `devDependencies` — тільки модулі `@nitra/*`. Виняток для цього dog food-репозиторію `@nitra/cursor`: root-only Vitest/Stryker peer/tools (`vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`), бо published workspace `npm/` не має devDependencies за `npm-module.mdc`. Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn.
|
|
46
46
|
|
|
47
47
|
- Заборонені top-level поля у root `package.json` (з причинами): [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
|
|
48
48
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# (top-level fields заборонені у root).
|
|
6
6
|
#
|
|
7
7
|
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
8
|
-
# - `devDependencies` лише `@nitra/*`
|
|
8
|
+
# - `devDependencies` лише `@nitra/*` + root-only тестові peer/tools для dog food (inverse-pattern)
|
|
9
9
|
# - Агрегований `lint` скрипт (cross-script aggregation logic)
|
|
10
10
|
#
|
|
11
11
|
# Перевірки, які ЗАЛИШИЛИСЬ у JS (потребують FS / cross-file):
|
|
@@ -32,13 +32,13 @@ deny contains msg if {
|
|
|
32
32
|
msg := sprintf("package.json: поле %s — %s", [field, reason])
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
# ── deny: devDependencies — лише `@nitra/*`
|
|
35
|
+
# ── deny: devDependencies — лише `@nitra/*` + root-only тестові peer/tools ─
|
|
36
36
|
|
|
37
37
|
deny contains msg if {
|
|
38
38
|
is_object(input.devDependencies)
|
|
39
39
|
some name, _ in input.devDependencies
|
|
40
|
-
not
|
|
41
|
-
msg := sprintf("Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: %s (bun.mdc)", [name])
|
|
40
|
+
not allowed_root_dev_dependency(name)
|
|
41
|
+
msg := sprintf("Кореневі devDependencies: дозволені лише @nitra/* або root-only test peers — прибери або перенеси: %s (bun.mdc)", [name])
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
# ── deny: агрегований lint-скрипт (cross-script aggregation logic) ───────
|
|
@@ -66,6 +66,16 @@ deny contains msg if {
|
|
|
66
66
|
|
|
67
67
|
# ── helpers ────────────────────────────────────────────────────────────────
|
|
68
68
|
|
|
69
|
+
allowed_root_test_deps := {"vitest", "@vitest/coverage-v8", "@stryker-mutator/vitest-runner"}
|
|
70
|
+
|
|
71
|
+
allowed_root_dev_dependency(name) if {
|
|
72
|
+
startswith(name, "@nitra/")
|
|
73
|
+
} else if {
|
|
74
|
+
# Ці пакети потрібні на рівні root для dog food-прогонів Vitest/Stryker у монорепо,
|
|
75
|
+
# але npm-module забороняє класти devDependencies у published workspace `npm/`.
|
|
76
|
+
name in allowed_root_test_deps
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
lint_prefixed_scripts := [name |
|
|
70
80
|
some name, _ in object.get(input, "scripts", {})
|
|
71
81
|
startswith(name, "lint-")
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
|
|
33
33
|
const execFileAsync = promisify(execFile)
|
|
34
34
|
|
|
35
|
-
/** Кандидати
|
|
35
|
+
/** Кандидати інтеграційними тести гілки для feature-гілок (перша наявна; див. n-changelog.mdc). */
|
|
36
36
|
const FEATURE_BASE_BRANCH_CANDIDATES = Object.freeze(['dev', 'main'])
|
|
37
37
|
|
|
38
38
|
/** Гілка `dev`: local-only не активний (крім незакомічених registry-published). */
|
|
@@ -100,7 +100,7 @@ function packageHasAvifDisabled(pkg) {
|
|
|
100
100
|
* `public/`, потім сам корінь пакета (на випадок mono-репо без `public/`), нарешті
|
|
101
101
|
* `<cwd>/x.png` як legacy fallback (щоб не зламати проєкти з кореневими ассетами).
|
|
102
102
|
* - голий шлях з принаймні одним `/` (`assets/img.png`, `start-page-ua/logo.png`) — у
|
|
103
|
-
* HTML/Vue браузер
|
|
103
|
+
* HTML/Vue браузер визначає його відносно документа, тому повертаємо relative-to-source
|
|
104
104
|
* та `<packageRoot>/public/<path>` як другий кандидат (Quasar-проєкти кладуть public-assets
|
|
105
105
|
* саме туди).
|
|
106
106
|
* - bare без `/` (`foo`) — ймовірно alias resolver (Vite/Webpack), резолвити не вміємо,
|
|
@@ -165,7 +165,7 @@ function resolveImageCandidates(importPath, sourceAbsPath, packageRootAbs) {
|
|
|
165
165
|
* хоч одне посилання у `.vue`/`.html` (доповнюється у цій функції)
|
|
166
166
|
* @param {RewriteStats} stats глобальні лічильники, що мутуються тут
|
|
167
167
|
* @param {(msg: string) => void} fail callback при помилці
|
|
168
|
-
* @returns {Promise<void>}
|
|
168
|
+
* @returns {Promise<void>} визначається по завершенню перевірки одного пакета
|
|
169
169
|
*/
|
|
170
170
|
async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, ignorePaths, usedAvifAbs, stats, fail) {
|
|
171
171
|
const absRoot = join(process.cwd(), packageRoot)
|
|
@@ -42,7 +42,7 @@ version: '1.26'
|
|
|
42
42
|
|
|
43
43
|
Каталог `.claude/worktrees/` (робочі копії, які Claude Code створює через **superpowers:using-git-worktrees**) має ігноруватися: додай його у кореневий `.gitignore` (це штатне місце для не-комітних робочих копій), а в `.jscpd.json` додай `.claude/worktrees/**` у `ignore` як страховку на випадок запуску без `gitignore: true`. Без цього jscpd сканує паралельну копію репо в worktree і фіксує самозбіги між дзеркальними файлами.
|
|
44
44
|
|
|
45
|
-
`**/CHANGELOG.md` теж у каноні `ignore`: release-журнали різних пакетів структурно повторюються (заголовки `## [x.y.z] - YYYY-MM-DD`, секції `### Added` / `### Changed` / `### Fixed` за Keep a Changelog), і `jscpd` за `minLines: 25` фіксує їх як клон, хоч це false positive — кожен `CHANGELOG.md` веде свій per-package журнал за каноном `n-changelog`, спільної історії не існує. Без цього в монорепо легко зловити
|
|
45
|
+
`**/CHANGELOG.md` теж у каноні `ignore`: release-журнали різних пакетів структурно повторюються (заголовки `## [x.y.z] - YYYY-MM-DD`, секції `### Added` / `### Changed` / `### Fixed` за Keep a Changelog), і `jscpd` за `minLines: 25` фіксує їх як клон, хоч це false positive — кожен `CHANGELOG.md` веде свій per-package журнал за каноном `n-changelog`, спільної історії не існує. Без цього в монорепо легко зловити критичне `bun run lint` на парі CHANGELOG-ів довжиною від ~25 рядків.
|
|
46
46
|
|
|
47
47
|
```json title=".jscpd.json"
|
|
48
48
|
{
|
|
@@ -242,7 +242,7 @@ function reportZeroMssqlSourceViolations(counters, pass) {
|
|
|
242
242
|
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
243
243
|
* @param {(msg: string) => void} pass pass callback
|
|
244
244
|
* @param {(msg: string) => void} fail fail callback
|
|
245
|
-
* @returns {Promise<void>}
|
|
245
|
+
* @returns {Promise<void>} визначається по завершенню аудиту всіх знайдених джерел
|
|
246
246
|
*/
|
|
247
247
|
async function auditMssqlSources(repoRoot, ignorePaths, pass, fail) {
|
|
248
248
|
const sourcePaths = await findAllSourcePathsForMssqlScan(repoRoot, ignorePaths)
|