@nitra/cursor 1.26.1 → 1.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.26.3] - 2026-05-26
8
+
9
+ ### Added
10
+
11
+ - **`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`; частина ключів відсутня → додає лише відсутні в окремий блок у кінці, без зміни існуючих значень.
12
+ - **`rules/tauri/js/tests/cargo_mutants_config.test.mjs`**: 7 тестів — silent skip без Tauri, створення baseline, ідемпотентність (повторний прогон байт-в-байт), збереження ручних налаштувань, partial-merge (додаються лише відсутні ключі), кілька src-tauri у різних workspaces, augmentation поверх нейтрального test-rule baseline.
13
+ - **`rules/tauri/tauri.mdc` v1.3**: нові розділи «Виявлення проєкту Tauri» (опис маркерів і workspace-обходу) та «Mutation-testing: семантика app shell та platform bridge» з фіксованою семантикою boundary-файлів і ідемпотентністю взаємодії з `test`-rule.
14
+
15
+ ### Changed
16
+
17
+ - **`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'а.
18
+ - **`rules/test/test.mdc` v2.3**: додано розділ «Універсальний baseline і framework-specific tuning» — `test` володіє нейтральним baseline, framework-rules (tauri, capacitor) зобов'язані доповнювати ідемпотентно і не перетирати ручні налаштування.
19
+ - **`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 живе в одному з пакетів, а не в корені.
20
+ - **`rules/test/js/tests/cargo_mutants_config.test.mjs`**: тест базлайну тепер перевіряє відсутність framework-specific ключів (`additional_cargo_test_args`, `exclude_globs`) у нейтральному baseline-файлі.
21
+
22
+ ## [1.26.2] - 2026-05-26
23
+
24
+ ### Changed
25
+
26
+ - **`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 до будь-якого масиву в снипеті, тож зміна не потребує правок коду — лише новий test-case у `jscpd_test.rego`.
27
+ - **`rules/js-lint/js-lint.mdc` v1.26**: оновлено приклад `.jscpd.json` і опис під ним (тепер посилається на снипет як source of truth і пояснює, чому `**/CHANGELOG.md` у каноні).
28
+
7
29
  ## [1.26.1] - 2026-05-26
8
30
 
9
31
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.26.1",
3
+ "version": "1.26.3",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -2,7 +2,7 @@
2
2
  description: Перевірка JavaScript коду
3
3
  globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"
4
4
  alwaysApply: false
5
- version: '1.25'
5
+ version: '1.26'
6
6
  ---
7
7
 
8
8
  **oxlint**, **ESLint**, **jscpd**, **knip**. У скрипті **`lint-js`** і в CI — **`bunx oxlint`**, **`bunx eslint`**, **`bunx jscpd`**, **`bunx knip`** (у CI без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config` мінімум `^3.9.2`** (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint); пакет **`@e18e/eslint-plugin`** окремо не додавай. Пакети oxlint/eslint/jscpd/knip не додавай без потреби монорепо.
@@ -42,17 +42,19 @@ version: '1.25'
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`, спільної історії не існує. Без цього в монорепо легко зловити блокуюче `bun run lint` на парі CHANGELOG-ів довжиною від ~25 рядків.
46
+
45
47
  ```json title=".jscpd.json"
46
48
  {
47
49
  "gitignore": true,
48
50
  "exitCode": 1,
49
51
  "reporters": ["console"],
50
52
  "minLines": 25,
51
- "ignore": [".claude/worktrees/**", "**/dist/**"]
53
+ "ignore": [".claude/worktrees/**", "**/dist/**", "**/CHANGELOG.md"]
52
54
  }
53
55
  ```
54
56
 
55
- Канон базових ключів `.jscpd.json` (`gitignore`, `exitCode`, `reporters`, `minLines`): [.jscpd.json.snippet.json](./policy/jscpd/template/.jscpd.json.snippet.json)
57
+ Канон ключів `.jscpd.json` (`gitignore`, `exitCode`, `reporters`, `minLines`, `ignore` як subset-of): [.jscpd.json.snippet.json](./policy/jscpd/template/.jscpd.json.snippet.json)
56
58
 
57
59
  ```text title=".gitignore (фрагмент)"
58
60
  .claude/worktrees/
@@ -2,5 +2,6 @@
2
2
  "gitignore": true,
3
3
  "exitCode": 1,
4
4
  "reporters": ["console"],
5
- "minLines": 25
5
+ "minLines": 25,
6
+ "ignore": [".claude/worktrees/**", "**/dist/**", "**/CHANGELOG.md"]
6
7
  }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Концерн `cargo_mutants_config` правила tauri (tauri.mdc): для кожного
3
+ * `<workspace>/src-tauri/Cargo.toml` ідемпотентно гарантує наявність
4
+ * Tauri-specific cargo-mutants налаштувань у `<workspace>/src-tauri/.cargo/mutants.toml`.
5
+ *
6
+ * Семантика (фіксована між Tauri-проєктами):
7
+ * - `src/main.rs` — binary shell entrypoint (smoke/e2e, не mutation unit);
8
+ * - `src/**\/{android,ios,mobile}.rs` — mobile plugin bridge / platform glue;
9
+ * - `src/**\/{macos,windows,linux,desktop}.rs` — desktop platform bridge / OS integration glue.
10
+ *
11
+ * Self-gating: silently skip, якщо в monorepo не знайдено жодного
12
+ * `<ws>/src-tauri/Cargo.toml` (test rule сам створить нейтральний baseline там,
13
+ * де потрібно).
14
+ *
15
+ * Ідемпотентність:
16
+ * - якщо файл відсутній — створює з Tauri-canonical baseline;
17
+ * - якщо файл існує і всі канонічні ключі вже є — `manual cargo-mutants config preserved`;
18
+ * - якщо файл існує, але якихось канонічних top-level ключів немає — додає
19
+ * лише відсутні ключі окремим блоком у кінці; існуючих значень не торкається.
20
+ */
21
+ import { existsSync } from 'node:fs'
22
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
23
+ import { dirname, join, relative } from 'node:path'
24
+
25
+ import { parse as parseToml } from 'smol-toml'
26
+
27
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
28
+ import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
29
+
30
+ const TAURI_BASELINE_HEADER = `# .cargo/mutants.toml — Tauri canonical cargo-mutants config (tauri.mdc).
31
+ # Виключаємо --bins і --doc щоб бінарник Tauri та doc-tests не перезбиралися
32
+ # з нуля під кожного мутанта (секунди → хвилини).
33
+ `
34
+
35
+ const TAURI_KEY_SNIPPETS = Object.freeze({
36
+ additional_cargo_test_args: 'additional_cargo_test_args = ["--lib", "--tests"]\n',
37
+ exclude_globs: `# Platform bridge / app shell — boundary-файли (тестуються smoke/e2e, не mutation unit).
38
+ # Якщо у bridge-файлі з'являється pure/business logic — винеси її у platform-neutral
39
+ # модуль (src/auth/oauth.rs, src/gmail/message.rs, ...) і тестуй mutation-testing там.
40
+ exclude_globs = [
41
+ "src/main.rs",
42
+ "src/**/android.rs",
43
+ "src/**/ios.rs",
44
+ "src/**/mobile.rs",
45
+ "src/**/desktop.rs",
46
+ "src/**/macos.rs",
47
+ "src/**/windows.rs",
48
+ "src/**/linux.rs"
49
+ ]
50
+ `
51
+ })
52
+
53
+ const TAURI_CANONICAL_KEYS = Object.freeze(Object.keys(TAURI_KEY_SNIPPETS))
54
+
55
+ /**
56
+ * Знаходить усі `<ws>/src-tauri/` каталоги з власним `Cargo.toml` у монорепо.
57
+ * Обходить workspace-пакети через `getMonorepoPackageRootDirs` (корінь + усі workspaces).
58
+ * @param {string} cwd корінь проєкту
59
+ * @returns {Promise<string[]>} абсолютні шляхи до знайдених `src-tauri/` каталогів
60
+ */
61
+ async function findSrcTauriDirs(cwd) {
62
+ const roots = await getMonorepoPackageRootDirs(cwd)
63
+ const result = []
64
+ for (const root of roots) {
65
+ const srcTauriCargo = join(cwd, root, 'src-tauri', 'Cargo.toml')
66
+ if (existsSync(srcTauriCargo)) {
67
+ result.push(join(cwd, root, 'src-tauri'))
68
+ }
69
+ }
70
+ return result
71
+ }
72
+
73
+ /**
74
+ * Зчитує існуючий `.cargo/mutants.toml` і повертає top-level ключі, яких ще немає.
75
+ * @param {string} targetPath абсолютний шлях до файла
76
+ * @returns {Promise<string[]>} список відсутніх канонічних ключів (зі збереженням порядку TAURI_CANONICAL_KEYS)
77
+ */
78
+ async function detectMissingKeys(targetPath) {
79
+ const existing = await readFile(targetPath, 'utf8')
80
+ const parsed = parseToml(existing)
81
+ return TAURI_CANONICAL_KEYS.filter(k => !(k in parsed))
82
+ }
83
+
84
+ /**
85
+ * Будує append-блок з відсутніх ключів. Існуючий вміст не торкається.
86
+ * @param {string} existing поточний вміст файла
87
+ * @param {string[]} missingKeys ключі, які треба додати
88
+ * @returns {string} новий вміст файла
89
+ */
90
+ function buildAppended(existing, missingKeys) {
91
+ const tail = existing.endsWith('\n') ? existing : `${existing}\n`
92
+ const block = ['\n# Tauri canonical cargo-mutants additions (tauri.mdc)\n']
93
+ for (const key of missingKeys) block.push(TAURI_KEY_SNIPPETS[key])
94
+ return tail + block.join('')
95
+ }
96
+
97
+ /**
98
+ * Будує повний Tauri-canonical baseline (для випадку, коли файла ще немає).
99
+ * @returns {string} вміст для нового `.cargo/mutants.toml`
100
+ */
101
+ function buildBaseline() {
102
+ return TAURI_BASELINE_HEADER + TAURI_CANONICAL_KEYS.map(k => TAURI_KEY_SNIPPETS[k]).join('\n')
103
+ }
104
+
105
+ /**
106
+ * Обробляє один `src-tauri/` каталог: створює або ідемпотентно доповнює `.cargo/mutants.toml`.
107
+ * @param {string} srcTauriDir абсолютний шлях до `src-tauri/`
108
+ * @param {string} cwd корінь проєкту (для relative-шляхів у репортах)
109
+ * @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер концерну
110
+ * @returns {Promise<void>}
111
+ */
112
+ async function processOneSrcTauri(srcTauriDir, cwd, reporter) {
113
+ const target = join(srcTauriDir, '.cargo', 'mutants.toml')
114
+ const rel = relative(cwd, target)
115
+
116
+ if (!existsSync(target)) {
117
+ await mkdir(dirname(target), { recursive: true })
118
+ await writeFile(target, buildBaseline())
119
+ reporter.pass(`.cargo/mutants.toml створено з Tauri canonical baseline (${rel}) (tauri.mdc)`)
120
+ return
121
+ }
122
+
123
+ const missing = await detectMissingKeys(target)
124
+ if (missing.length === 0) {
125
+ reporter.pass(`.cargo/mutants.toml: manual cargo-mutants config preserved (${rel})`)
126
+ return
127
+ }
128
+
129
+ const existing = await readFile(target, 'utf8')
130
+ await writeFile(target, buildAppended(existing, missing))
131
+ reporter.pass(`.cargo/mutants.toml: додано відсутні Tauri-ключі [${missing.join(', ')}] (${rel}) (tauri.mdc)`)
132
+ }
133
+
134
+ /**
135
+ * @returns {Promise<number>} 0 — OK або silently skipped, 1 — порушення
136
+ */
137
+ export async function check() {
138
+ const reporter = createCheckReporter()
139
+ const cwd = process.cwd()
140
+ const srcTauriDirs = await findSrcTauriDirs(cwd)
141
+ if (srcTauriDirs.length === 0) {
142
+ return reporter.getExitCode()
143
+ }
144
+ for (const dir of srcTauriDirs) {
145
+ await processOneSrcTauri(dir, cwd, reporter)
146
+ }
147
+ return reporter.getExitCode()
148
+ }
@@ -3,11 +3,15 @@
3
3
  * проєктів, у яких є маркер Tauri.
4
4
  *
5
5
  * Cross-file gating (JS):
6
- * 1. Tauri-маркер визначаємо за **будь-яким** з:
7
- * - існує каталог `src-tauri/` у `cwd`;
8
- * - існує файл `tauri.conf.json` у `cwd` або в workspace-пакетах;
9
- * - кореневий `package.json#devDependencies` або `dependencies` містить
10
- * ключ з префіксом `@tauri-apps/`.
6
+ * 1. Tauri-маркер визначаємо обходом усіх workspace-пакетів через
7
+ * `getMonorepoPackageRootDirs()` (корінь + workspaces). Кожен workspace
8
+ * перевіряється за **будь-яким** з:
9
+ * - існує каталог `<ws>/src-tauri/`;
10
+ * - існує файл `<ws>/src-tauri/Cargo.toml`;
11
+ * - існує файл `<ws>/src-tauri/tauri.conf.json`;
12
+ * - існує файл `<ws>/tauri.conf.json` (legacy flat-layout);
13
+ * - `<ws>/package.json#dependencies` чи `devDependencies` містить ключ
14
+ * з префіксом `@tauri-apps/`.
11
15
  * 2. Якщо маркера немає — пропустити перевірку (tauri-tooling не вимагається).
12
16
  * 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
13
17
  * content `rego.tauri.vscode_extensions` через `runConftestBatch`.
@@ -17,9 +21,11 @@
17
21
  */
18
22
  import { existsSync, statSync } from 'node:fs'
19
23
  import { readFile } from 'node:fs/promises'
24
+ import { join } from 'node:path'
20
25
 
21
26
  import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
22
27
  import { runConftestBatch } from '../../../scripts/lib/run-conftest-batch.mjs'
28
+ import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
23
29
 
24
30
  /**
25
31
  * Чи є префікс `@tauri-apps/` у ключах `dependencies` або `devDependencies`.
@@ -39,16 +45,34 @@ function packageHasTauriDep(pkg) {
39
45
  }
40
46
 
41
47
  /**
42
- * Чи є у проєкті маркер Tauri: `src-tauri/`, `tauri.conf.json` (root або
43
- * workspace), або `@tauri-apps/*` у залежностях кореневого `package.json`.
48
+ * Чи має одиничний workspace-пакет маркер Tauri.
49
+ * @param {string} cwd корінь репо
50
+ * @param {string} ws відносний шлях workspace ('.' для root)
51
+ * @returns {Promise<boolean>} true, якщо в цьому workspace є Tauri
52
+ */
53
+ async function workspaceHasTauriMarker(cwd, ws) {
54
+ const base = ws === '.' ? cwd : join(cwd, ws)
55
+ const srcTauri = join(base, 'src-tauri')
56
+ if (existsSync(srcTauri) && statSync(srcTauri).isDirectory()) return true
57
+ if (existsSync(join(base, 'src-tauri', 'Cargo.toml'))) return true
58
+ if (existsSync(join(base, 'src-tauri', 'tauri.conf.json'))) return true
59
+ if (existsSync(join(base, 'tauri.conf.json'))) return true
60
+ const pkgPath = join(base, 'package.json')
61
+ if (!existsSync(pkgPath)) return false
62
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
63
+ return packageHasTauriDep(pkg)
64
+ }
65
+
66
+ /**
67
+ * Чи є у проєкті (root або будь-якому workspace-пакеті) маркер Tauri.
44
68
  * @returns {Promise<boolean>} true, якщо проєкт використовує Tauri
45
69
  */
46
70
  async function projectHasTauriMarker() {
47
- if (existsSync('src-tauri') && statSync('src-tauri').isDirectory()) return true
48
- if (existsSync('tauri.conf.json')) return true
49
- if (!existsSync('package.json')) return false
50
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
51
- if (packageHasTauriDep(pkg)) return true
71
+ const cwd = process.cwd()
72
+ const roots = await getMonorepoPackageRootDirs(cwd)
73
+ for (const ws of roots) {
74
+ if (await workspaceHasTauriMarker(cwd, ws)) return true
75
+ }
52
76
  return false
53
77
  }
54
78
 
@@ -2,7 +2,7 @@
2
2
  description: Tauri
3
3
  globs: "**/src-tauri/**,**/tauri.conf.json"
4
4
  alwaysApply: false
5
- version: '1.2'
5
+ version: '1.3'
6
6
  ---
7
7
 
8
8
  У `.vscode/extensions.json` `recommendations` має містити `tauri-apps.tauri-vscode`:
@@ -14,3 +14,53 @@ version: '1.2'
14
14
  ```
15
15
 
16
16
  Розширені Rust-вимоги (`rust-lang.rust-analyzer`, `tamasfe.even-better-toml`, скрипт `lint-rust`, CI `lint-rust.yml`) — у правилі **`rust`** (`n-rust.mdc`). Tauri-проєкт завжди має `src-tauri/Cargo.toml`, тому `rust` активується автоматично разом з `tauri`.
17
+
18
+ ## Виявлення проєкту Tauri
19
+
20
+ Правило активується, якщо хоч у одному workspace-пакеті (корінь або будь-який пакет з `package.json#workspaces`) виявлений маркер Tauri:
21
+
22
+ - існує каталог `<ws>/src-tauri/`;
23
+ - існує `<ws>/src-tauri/Cargo.toml`;
24
+ - існує `<ws>/src-tauri/tauri.conf.json`;
25
+ - існує `<ws>/tauri.conf.json` (legacy flat-layout);
26
+ - `<ws>/package.json#dependencies` чи `devDependencies` містить ключ з префіксом `@tauri-apps/`.
27
+
28
+ Виявлення виконує `js/tooling.mjs` через `getMonorepoPackageRootDirs()`. Це дозволяє Tauri-rule працювати у монорепо, де Tauri живе в одному з пакетів (а не в корені), не вимагаючи кореневих маркерів.
29
+
30
+ ## Mutation-testing: семантика app shell та platform bridge
31
+
32
+ `tauri` rule володіє Tauri-specific семантикою mutation-testing для каталога `src-tauri/`. Концерн `js/cargo_mutants_config.mjs` ідемпотентно додає у `<ws>/src-tauri/.cargo/mutants.toml` такі канонічні ключі:
33
+
34
+ ```toml title="<ws>/src-tauri/.cargo/mutants.toml"
35
+ additional_cargo_test_args = ["--lib", "--tests"]
36
+
37
+ exclude_globs = [
38
+ "src/main.rs",
39
+ "src/**/android.rs",
40
+ "src/**/ios.rs",
41
+ "src/**/mobile.rs",
42
+ "src/**/desktop.rs",
43
+ "src/**/macos.rs",
44
+ "src/**/windows.rs",
45
+ "src/**/linux.rs"
46
+ ]
47
+ ```
48
+
49
+ Семантика (фіксована між Tauri-проєктами):
50
+
51
+ - **`src/main.rs`** — binary shell entrypoint: запускає Tauri runtime, реєструє plugins/handlers і повертає управління циклу подій. Тестується smoke/e2e (запуск бінарника), не mutation unit;
52
+ - **`*android.rs`, `*ios.rs`, `*mobile.rs`** — mobile plugin bridge / platform glue: тонка обгортка над JNI/Objective-C виклики, mapping platform errors, виклики Tauri AppHandle і native API;
53
+ - **`*macos.rs`, `*windows.rs`, `*linux.rs`, `*desktop.rs`** — desktop platform bridge / OS integration glue: opener/window APIs, OS-specific I/O, mapping platform errors.
54
+
55
+ Ці файли мають містити **тільки platform boundary**: виклик plugin/native API, Tauri AppHandle, opener/window APIs, OS-specific I/O, mapping platform errors. Якщо у bridge-файлі з'являється pure/business logic — її потрібно винести у platform-neutral модуль (`src/auth/oauth.rs`, `src/gmail/message.rs`, …) і тестувати mutation-testing там.
56
+
57
+ Це створює фіксовану семантику: `*android.rs`/`*macos.rs` — boundary-файли, а не місце для бізнес-логіки.
58
+
59
+ ### Ідемпотентність і взаємодія з `test`-rule
60
+
61
+ - `test` rule створює універсальний нейтральний `.cargo/mutants.toml` (порожній з коментом) для кожного Cargo.toml-manifesta — без framework-specific exclude'ів. Це наш baseline.
62
+ - `tauri` rule додає Tauri-канонічні ключі **поверх** того, що вже є у `<ws>/src-tauri/.cargo/mutants.toml`:
63
+ - якщо файла немає — створює з повного Tauri-baseline;
64
+ - якщо обидва канонічні ключі (`additional_cargo_test_args`, `exclude_globs`) вже присутні — `manual cargo-mutants config preserved`, нічого не зміниться;
65
+ - якщо якийсь канонічний ключ відсутній — додається окремим блоком у кінці файла, без зміни існуючих значень.
66
+ - Послідовний `fix test` → `fix tauri` створює Tauri-config; повторний `fix tauri` не дублює секцій; повторний `fix test` не перетирає Tauri-tuning.
@@ -1,8 +1,7 @@
1
- # .cargo/mutants.toml — конфігурація cargo-mutants (опційно).
1
+ # .cargo/mutants.toml — universal cargo-mutants baseline (test.mdc).
2
+ # Цей baseline нейтральний: він не робить припущень про framework/app shell,
3
+ # не виключає platform glue, generated wrappers або binary entrypoints.
4
+ # Framework-specific tuning (Tauri, Capacitor тощо) належить відповідним
5
+ # правилам — вони ідемпотентно доповнюють цей файл, не перетирають його.
2
6
  # cargo-mutants має робочі defaults; цей файл — стартова точка для customization.
3
7
  # Документація: https://mutants.rs/
4
- # Канон постачає правило `test` (@nitra/cursor).
5
-
6
- # Виключаємо --bins і --doc: бінарник Tauri та doc-tests щоразу перезбираються
7
- # з нуля навіть з кешем, що збільшує час кожного мутанту з секунд до хвилин.
8
- additional_cargo_test_args = ["--lib", "--tests"]
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: JS-тести (*.test.mjs) живуть у tests/. Правило `test` керує stryker.config.mjs (якщо js-lint enabled) і .cargo/mutants.toml (якщо rust enabled).
3
- version: '2.2'
3
+ version: '2.3'
4
4
  globs: "**/{.n-cursor.json,package.json,Cargo.toml,stryker.config.mjs,.cargo/mutants.toml},**/*.test.mjs"
5
5
  alwaysApply: false
6
6
  ---
@@ -82,4 +82,15 @@ Recursive globs ловлять файли всередині `tests/` так с
82
82
 
83
83
  Customization (mutate patterns, exclude rules, timeout) — відповідальність проєкту-споживача; концерни лише забезпечують наявність файлу як стартового baseline в кожному з виявлених workspace-каталогів.
84
84
 
85
+ ### Універсальний baseline і framework-specific tuning
86
+
87
+ Правило `test` відповідає лише за **універсальний baseline** mutation-testing:
88
+
89
+ - створює `.cargo/mutants.toml` для Rust crates (порожній, з коментом);
90
+ - не робить припущень про framework/app shell;
91
+ - не виключає platform glue, generated wrappers або binary entrypoints;
92
+ - framework-specific tuning (`--lib`/`--tests`, `exclude_globs` для app-shell і platform bridge файлів) належить відповідним правилам (`tauri`, `capacitor` тощо).
93
+
94
+ Якщо інше правило спеціалізує mutation-behavior — воно зобов'язане **доповнювати** існуючий `.cargo/mutants.toml` ідемпотентно (додавати лише відсутні ключі) і **не перетирати** ручні налаштування. Послідовний запуск `npx @nitra/cursor fix test` після `fix tauri` не має скидати tauri-tuning, і навпаки — повторний `fix tauri` не дублює секції.
95
+
85
96
  Додатково: коли `js-lint` enabled, концерн `stryker_config` ідемпотентно додає у кореневий `.gitignore` патерн `**/reports/stryker/` — увесь каталог Stryker-output-у (backup'и `tempDirName`, `mutation.json`, HTML/dashboard-репорти якщо додасте інші reporter-и). Це запобігає випадковому коміту build-артефактів.