@7n/rules 1.43.0 → 1.44.0

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/docs/vitest.config.md +14 -19
  3. package/package.json +1 -1
  4. package/rules/abie/lib/docs/index.md +0 -2
  5. package/rules/abie/lib/http-route.mjs +1 -0
  6. package/rules/abie/lib/yaml.mjs +2 -0
  7. package/rules/ci4/marksman_config/docs/index.md +10 -0
  8. package/rules/ci4/marksman_config/main.mjs +2 -0
  9. package/rules/doc-files/docgen-prompts/docs/index.md +9 -0
  10. package/rules/doc-files/docgen-prompts/main.mjs +5 -0
  11. package/rules/hasura/internal_urls/docs/index.md +0 -2
  12. package/rules/hasura/internal_urls/docs/main.md +27 -15
  13. package/rules/hasura/internal_urls/main.mjs +1 -0
  14. package/rules/image-avif/avif_generation/docs/index.md +10 -0
  15. package/rules/image-avif/avif_generation/main.mjs +2 -0
  16. package/rules/rego/vscode_settings/docs/fix-vscode_settings.md +12 -10
  17. package/rules/rego/vscode_settings/fix-vscode_settings.mjs +5 -0
  18. package/rules/tauri/cargo_mutants_config/docs/index.md +0 -2
  19. package/rules/tauri/cargo_mutants_config/docs/main.md +38 -12
  20. package/rules/tauri/cargo_mutants_config/main.mjs +5 -1
  21. package/rules/tauri/core_test_isolation/docs/main.md +19 -16
  22. package/rules/tauri/core_test_isolation/main.mjs +3 -1
  23. package/rules/tauri/linux_deps/docs/main.md +25 -18
  24. package/rules/tauri/linux_deps/main.mjs +2 -1
  25. package/rules/tauri/release/docs/main.md +20 -12
  26. package/rules/tauri/release/main.mjs +2 -0
  27. package/rules/tauri/updater/docs/main.md +31 -14
  28. package/rules/tauri/updater/main.mjs +4 -0
  29. package/rules/test/coverage/concern.json +9 -0
  30. package/rules/test/coverage/fix-worker.mjs +111 -0
  31. package/rules/test/coverage/lib/classify/apply.mjs +67 -0
  32. package/rules/test/coverage/lib/classify/cache.mjs +77 -0
  33. package/rules/test/coverage/lib/classify/docs/apply.md +28 -0
  34. package/rules/test/coverage/lib/classify/docs/cache.md +34 -0
  35. package/rules/test/coverage/lib/classify/docs/index.md +37 -0
  36. package/rules/test/coverage/lib/classify/docs/prompt.md +30 -0
  37. package/rules/test/coverage/lib/classify/docs/verdict-schema.md +30 -0
  38. package/rules/test/coverage/lib/classify/index.mjs +140 -0
  39. package/rules/test/coverage/lib/classify/prompt.mjs +136 -0
  40. package/rules/test/coverage/lib/classify/verdict-schema.mjs +163 -0
  41. package/rules/test/coverage/lib/llm.mjs +100 -0
  42. package/rules/test/coverage/main.mjs +161 -0
  43. package/rules/test/main.json +1 -0
  44. package/rules/test/main.mdc +140 -0
  45. package/rules/test/package_json/concern.json +11 -0
  46. package/rules/test/package_json/package_json.mdc +18 -0
  47. package/rules/test/package_json/package_json.rego +25 -0
  48. package/rules/test/package_json/template/package.json.contains.json +6 -0
  49. package/rules/text/oxfmtrc/fix-oxfmtrc.mjs +5 -0
  50. package/rules/text/vscode_settings/fix-vscode_settings.mjs +5 -0
  51. package/rules/worktree/vscode_settings/docs/fix-vscode_settings.md +10 -7
  52. package/rules/worktree/vscode_settings/fix-vscode_settings.mjs +5 -0
  53. package/rules/worktree/zed_settings/fix-zed_settings.mjs +5 -0
  54. package/schemas/n-rules.json +37 -0
  55. package/scripts/lib/adr/docs/index.md +0 -2
  56. package/scripts/lib/adr/docs/normalize-pipeline.md +44 -28
  57. package/scripts/lib/adr/normalize-pipeline.mjs +11 -0
  58. package/scripts/lib/docs/inline-template-links.md +17 -8
  59. package/scripts/lib/docs/plugin-api.md +1 -1
  60. package/scripts/lib/inline-template-links.mjs +5 -0
  61. package/scripts/lib/lint-surface/docs/run-detectors.md +25 -22
  62. package/scripts/lib/lint-surface/docs/tier-sampling-experiment.md +30 -14
  63. package/scripts/lib/lint-surface/run-detectors.mjs +1 -1
  64. package/scripts/lib/lint-surface/tier-sampling-experiment.mjs +1 -0
  65. package/scripts/lib/plugin-api.mjs +46 -0
  66. package/scripts/utils/docs/walkDir.md +26 -15
  67. package/scripts/utils/docs/worktree-fingerprint.md +16 -15
  68. package/scripts/utils/walkDir.mjs +9 -5
  69. package/scripts/utils/worktree-fingerprint.mjs +5 -0
  70. package/skills/storybook/SKILL.md +4 -4
  71. package/skills/taze/js/migration-cache.mjs +1 -1
@@ -3,34 +3,41 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/tauri/linux_deps/main.mjs
5
5
  docgen:
6
- crc: add9f0f3
6
+ crc: cf2b4f30
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge:error
11
+ judgeModel: openai-codex/gpt-5.4-mini
7
12
  ---
8
13
 
9
14
  ## Огляд
10
15
 
11
- Read-only detector концерну `tauri/linux_deps` (tauri.mdc). У Tauri-проєкті перевіряє, що `.github/workflows/lint-rust.yml` містить крок встановлення системних залежностей Linux без них Clippy падає на збірці `-sys`-крейтів (webkit2gtk, gtk, appindicator), чиї build-скрипти шукають системні бібліотеки через pkg-config.
16
+ Детектор `scanLinuxDeps` перевіряє, чи в Tauri-workspace файл `LINT_RUST_YML` містить крок `MISSING_LINUX_DEPS_STEP` з `apt-get install` для `REQUIRED_LINUX_PACKAGES` із `MISSING_LINUX_DEPS_PACKAGES`. Це потрібно, щоб Clippy у Linux не падав на збірці `-sys`-crate’ів через відсутні системні `.pc`-файли, які шукає `pkg-config` під час перевірки Rust-частини. `lint` застосовує цю перевірку лише до потрібного workflow і свідомо пропускає `.github` та `.git`, не зачіпаючи інші файли чи етапи збірки.
12
17
 
13
18
  ## Поведінка
14
19
 
15
- - Правило активується лише коли хоч у одному workspace-пакеті є `src-tauri/Cargo.toml`; інакшежодних порушень.
16
- - Якщо `.github/workflows/lint-rust.yml` відсутній — порушення не звітується: існування файла перевіряє `rust.lint_rust_yml`.
17
- - Аналіз текстовий (не YAML-AST), як у `rust/toolchain_cache`: шукається перший рядок з `apt-get install`, а канонічні пакети — як substring по всьому файлу (пакет може стояти на continuation-рядку багаторядкового `run: |`).
18
- - Немає жодного `apt-get install` → порушення `missing-linux-deps-step`.
19
- - apt-рядок є, але бракує канонічних пакетів → порушення `missing-linux-deps-packages` з переліком відсутніх у `data.missing`.
20
- - Перевірка — підмножина: додаткові пакети в apt-рядку дозволені.
20
+ Модуль працює лише для Tauri-проєкту: якщо в workspace є `src-tauri`, перевірка націлюється на `.github/workflows/lint-rust.yml`, а якщо таких проєктів немає завершується без результату. Файл workflow читається як plain text, бо потрібна саме поведінкова звірка готового CI-кроку, а не структурний розбір; `scanLinuxDeps` знаходить наявність apt-встановлення та збирає перелік канонічних пакетів, яких бракує у вмісті файла. `REQUIRED_LINUX_PACKAGES` задає мінімальний набір Linux dev-залежностей для Clippy на Tauri, а `LINT_RUST_YML` фіксує єдиний цільовий workflow. Якщо кроку встановлення немає, `lint` формує порушення з `MISSING_LINUX_DEPS_STEP="missing-linux-deps-step"`; якщо крок є, але не всі канонічні пакети присутні, використовується `MISSING_LINUX_DEPS_PACKAGES="missing-linux-deps-packages"`. У повідомленнях і правилах послідовно відображається очікування з (tauri.mdc), а відсутній `.github/workflows/lint-rust.yml` вважається поза сферою цього детектора, бо його існування контролюється окремо.
21
21
 
22
22
  ## Публічний API
23
23
 
24
- - `MISSING_LINUX_DEPS_STEP`, `MISSING_LINUX_DEPS_PACKAGES` стабільні reason-коди порушень.
25
- - `LINT_RUST_YML`шлях цільового workflow-файла.
26
- - `REQUIRED_LINUX_PACKAGES`канонічні dev-пакети Tauri v2: `libwebkit2gtk-4.1-dev` (WebView), `libayatana-appindicator3-dev` (tray), `librsvg2-dev` (іконки).
27
- - `scanLinuxDeps(content)`сканує вміст workflow: `{ aptLine, missing }` (індекс першого apt-рядка або −1; відсутні канонічні пакети).
28
- - `lint(ctx)` стандартна lint-поверхня концерну; повертає результат із порушеннями.
29
-
30
- ## Де використовується
31
-
32
- - Автофікс `fix-linux_deps.mjs` імпортує `scanLinuxDeps`/`REQUIRED_LINUX_PACKAGES` і reason-коди для T0-патернів.
24
+ - MISSING_LINUX_DEPS_STEP — Стабільний reason: у CI-workflow немає apt-кроку встановлення Linux-залежностей Tauri.
25
+ - MISSING_LINUX_DEPS_PACKAGESСтабільний reason: apt-крок є, але в ньому бракує канонічних пакетів.
26
+ - LINT_RUST_YMLЦільовий workflow-файл (канон `rust.lint_rust_yml`).
27
+ - REQUIRED_LINUX_PACKAGESКанонічні dev-пакети для компіляції Tauri v2 на ubuntu-runner-і:
28
+ webkit2gtk-4.1 (WebView), ayatana-appindicator (tray), rsvg (іконки).
29
+ Перевірка — підмножина: додаткові пакети в apt-рядку дозволені.
30
+ - scanLinuxDeps — Сканує вміст workflow: перший `apt-get install`-рядок і перелік канонічних
31
+ пакетів, яких немає ніде у файлі (substring — пакет може стояти на
32
+ continuation-рядку багаторядкового `run: |`).
33
+ - lint — запускає набір правил для Rust-робочого дерева та повідомляє про проблеми в проектах, залежностях і CI-налаштуваннях, пов’язаних із Linux; у повідомленнях використовує маркер (tauri.mdc)
34
+
35
+ Поводження:
36
+ - Якщо не вистачає Linux-залежностей, фіксує це через `MISSING_LINUX_DEPS_STEP="missing-linux-deps-step"` і `MISSING_LINUX_DEPS_PACKAGES="missing-linux-deps-packages"`, щоб окремо показати крок і перелік пакетів для встановлення.
37
+ - Для перевірок Rust CI орієнтується на `LINT_RUST_YML=".github/workflows/lint-rust.yml"`, щоб прив’язати результати до конкретного workflow-файла.
33
38
 
34
39
  ## Гарантії поведінки
35
40
 
36
- - Read-only: не виконує операцій запису (ФС/БД).
41
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
42
+ - Кешує результати в межах одного прогону.
43
+ - Свідомо пропускає шляхи: `.github`, `.git`.
@@ -20,8 +20,9 @@ import { join } from 'node:path'
20
20
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
21
21
  import { findSrcTauriDirs } from '../cargo_mutants_config/main.mjs'
22
22
 
23
- /** Стабільні reasons: apt-кроку немає взагалі / у ньому бракує канонічних пакетів. */
23
+ /** Стабільний reason: у CI-workflow немає apt-кроку встановлення Linux-залежностей Tauri. */
24
24
  export const MISSING_LINUX_DEPS_STEP = 'missing-linux-deps-step'
25
+ /** Стабільний reason: apt-крок є, але в ньому бракує канонічних пакетів. */
25
26
  export const MISSING_LINUX_DEPS_PACKAGES = 'missing-linux-deps-packages'
26
27
 
27
28
  /** Цільовий workflow-файл (канон `rust.lint_rust_yml`). */
@@ -3,30 +3,38 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/tauri/release/main.mjs
5
5
  docgen:
6
- crc: 91e1c73d
6
+ crc: fc5e2737
7
7
  model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
8
9
  score: 100
9
- issues: judge:inaccurate:0.98
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.99
10
11
  judgeModel: openai-codex/gpt-5.4-mini
11
12
  ---
12
13
 
13
14
  ## Огляд
14
15
 
15
- `lint` перевіряє готовність Tauri-застосунків у репозиторії до release-потоку на основі `tauri.conf.json` і `latest.json`. Він дає змогу зосередити перевірку на релізних налаштуваннях Tauri, не зачіпаючи `.github` та `.git`.
16
+ Файл запускає `lint` для перевірки Tauri-застосунків у workspace, знаходить їхні каталоги через `findTauriAppDirs` і окремо звіряє наявність workflow dispatch через `hasWorkflowDispatch`. Для релізного контуру використовує `CHANGELOG_RELEASE_WORKFLOW` і `RELEASE_WORKFLOW`, щоб тримати в полі зору canonical workflow для changelog і release. Свідомо пропускає `.github` і `.git`, звертається до мережі, працює з кешуванням у межах прогону та поводиться fail-safe: помилки перехоплює і не викидає назовні.
16
17
 
17
18
  ## Поведінка
18
19
 
19
- 1. `lint` знаходить усі workspace-каталоги з Tauri-застосунками, пропускаючи `.github` і `.git`, та бере до уваги лише репозиторії, де є `tauri.conf.json`.
20
- 2. Для кожного такого застосунку `lint` перевіряє готовність `tauri.conf.json` до release: чи увімкнено генерацію updater-артефактів, чи задано public key для updater і чи endpoint веде на `latest.json`.
21
- 3. `lint` контролює наявність `changelog-release.yml` як точки запуску release-процесу з changelog-змін, щоб релізи стартували від змін у `.changes`.
22
- 4. `lint` перевіряє, що `changelog-release.yml` реагує на push у відповідні `.changes`-шляхи для знайдених застосунків, має ручний запуск і захищений від повторного запуску release-циклом.
23
- 5. `lint` перевіряє, що `changelog-release.yml` має достатні права для запуску release-потоку та диспатчу наступного workflow.
24
- 6. `lint` контролює наявність `release.yml` як основного каналу збірки й публікації release-артефактів.
25
- 7. `lint` перевіряє, що `release.yml` запускається на тегах `v*`, підтримує ручний запуск і синхронізує версію в `tauri.conf.json` до кроку публікації через Tauri.
26
- 8. `lint` не змінює файли, працює read-only і повертає результат у fail-safe режимі: помилки фіксуються як порушення, а не пробиваються назовні винятками.
20
+ `lint` запускає повну перевірку Tauri-налаштувань у межах одного прогону: спершу `findTauriAppDirs` знаходить усі workspace з Tauri-застосунками за `tauri.conf.json`, після чого результати передаються в правила для конфігів і GitHub Actions. Для workflow-частини `CHANGELOG_RELEASE_WORKFLOW` і `RELEASE_WORKFLOW` задають канонічні шляхи `.github/workflows/changelog-release.yml` і `.github/workflows/release.yml`, а `hasWorkflowDispatch` використовується як спільна перевірка на наявність `workflow_dispatch` у корені workflow. Дані читаються з `tauri.conf.json` і `latest.json`, а також з workflow-файлів; результати йдуть у fail-safe reporting, без винесення винятків назовні. У межах прогону діє кешування, тому повторні звернення до вже прочитаних артефактів не дублюють роботу. Перевірки свідомо оминають `.github` і `.git`, щоб не змішувати службові каталоги з робочими шляхами застосунків. Для релізного циклу потік також враховує віддалений доступ через URL на кшталт `https://x-access-token:\`, щоб узгодити автоматизацію оновлень і публікації без ручного втручання.
21
+
22
+ ## Публічний API
23
+
24
+ - CHANGELOG_RELEASE_WORKFLOW Шлях workflow, що на push у main бампає версію з change-файлів і створює тег.
25
+ - RELEASE_WORKFLOW Шлях workflow, що на тег збирає й публікує реліз Tauri-застосунку.
26
+ - findTauriAppDirs Знаходить workspace-каталоги з Tauri-застосунком (`<ws>/src-tauri/tauri.conf.json` чи legacy `<ws>/tauri.conf.json`).
27
+ - hasWorkflowDispatch Чи `on.workflow_dispatch` присутній у корені workflow.
28
+ - lint — запускає перевірки для шляху з репозиторію; якщо шлях вказує на змінений файл, пропускає лише релевантні правила і не чіпає інше.
29
+ - CHANGELOG_RELEASE_WORKFLOW=".github/workflows/changelog-release.yml" — константа для workflow, що готує changelog-реліз.
30
+ - RELEASE_WORKFLOW=".github/workflows/release.yml" — константа для workflow, що запускає релізний процес.
31
+ - https://x-access-token:\ — базовий шаблон URL для доступу до GitHub через token-автентифікацію під час мережевих операцій.
32
+ - tauri.conf.json — джерело налаштувань Tauri, з яких код бере параметри застосунку.
33
+ - latest.json — файл з даними про останній доступний реліз, які використовуються для оновлень.
27
34
 
28
35
  ## Гарантії поведінки
29
36
 
30
- - Read-only: не виконує операцій запису (ФС/БД).
37
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
31
38
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
39
+ - Кешує результати в межах одного прогону.
32
40
  - Свідомо пропускає шляхи: `.github`, `.git`.
@@ -7,7 +7,9 @@ import { flattenWorkflowSteps, getStepRun, getStepUses, parseWorkflowYaml } from
7
7
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
8
8
  import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
9
9
 
10
+ /** Шлях workflow, що на push у main бампає версію з change-файлів і створює тег. */
10
11
  export const CHANGELOG_RELEASE_WORKFLOW = '.github/workflows/changelog-release.yml'
12
+ /** Шлях workflow, що на тег збирає й публікує реліз Tauri-застосунку. */
11
13
  export const RELEASE_WORKFLOW = '.github/workflows/release.yml'
12
14
  const VERSION_WORD_RE = /version/iu
13
15
 
@@ -3,30 +3,47 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/tauri/updater/main.mjs
5
5
  docgen:
6
- crc: d08660a5
7
- model: openai-codex/gpt-5.4-mini
6
+ crc: 98c501bd
7
+ model: openai-codex/gpt-5.5
8
+ tier: cloud-avg
8
9
  score: 100
9
- issues: judge:inaccurate:0.98
10
+ issues: judge-refine:kept-original,judge:inaccurate:0.99
10
11
  judgeModel: openai-codex/gpt-5.4-mini
11
12
  ---
12
13
 
13
14
  ## Огляд
14
15
 
15
- `lint` читає конфігурації Tauri-застосунків у монорепо та перевіряє, що налаштування й залежності узгоджені між `tauri.conf.json` і `package.json`. Якщо під час перевірки трапляється помилка, вона не виходить назовні; результатом лишається fail-safe повідомлення без змін у файловій системі чи БД.
16
+ Файл відповідає за fail-safe lint Tauri updater-сценарію в знайдених робочих просторах застосунків: через `findTauriAppWorkspaces` і `lint` перевіряє конфігурацію, залежності, Rust/Cargo-секції, permissions і frontend entrypoints. `MIN_TAURI_COMPONENTS_VERSION` задає мінімальну очікувану версію Tauri-компонентів, а `CARGO_TARGET_SECTION_RE` допомагає розпізнавати релевантні Cargo-секції. Перевірка потрібна, щоб виявляти неповне або некоректне підключення updater без зупинки виконання через винятки назовні.
16
17
 
17
18
  ## Поведінка
18
19
 
19
- 1. `lint` знаходить у монорепо workspace-каталоги з Tauri-застосунком за наявністю `tauri.conf.json`.
20
- 2. Для кожного такого workspace перевіряє `package.json`: чи є `@7n/tauri-components` не нижче `0.8`, а також `@tauri-apps/plugin-updater` і `@tauri-apps/plugin-process` версії `2`.
21
- 3. Перевіряє `src-tauri/Cargo.toml`: чи присутній `tauri-plugin-process`, а `tauri-plugin-updater` оголошений лише для desktop-збірки.
22
- 4. Перевіряє `src-tauri/src/lib.rs`: чи зареєстровані `tauri_plugin_process` і `tauri_plugin_updater`, та чи updater під захистом desktop-умови.
23
- 5. Перевіряє `src-tauri/capabilities/*.json`: чи надані permissions `updater:default` і `process:allow-restart`.
24
- 6. Перевіряє `src/**/*.vue`: чи бодай один Vue-компонент використовує `useUpdater` з `@7n/tauri-components/vue`.
25
- 7. Перевіряє `src/main.{js,ts}` Quasar-застосунків: чи підключено плагін `Dialog` (імпорт з `quasar` і присутність у `plugins: {...}`) без нього діалог оновлення від `useUpdater()` падає з `TypeError: e.dialog is not a function` мовчки, у `console.error`.
26
- 8. Якщо жодного Tauri-workspace не знайдено, `lint` завершується без зауважень.
27
- 9. Під час перевірок `lint` не змінює файлову систему і не кидає помилки назовні; усі порушення повертає як звіт.
20
+ `lint` запускає fail-safe перевірку Tauri updater-конфігурації: знаходить застосунки через `findTauriAppWorkspaces`, читає `tauri.conf.json`, `package.json`, Cargo, Rust, capabilities та Vue/Quasar entrypoints, після чого повертає структурований результат із pass/fail-повідомленнями замість винятків.
21
+
22
+ `findTauriAppWorkspaces` визначає workspace-и, де є Tauri-конфігурація, і саме цей список задає межі всіх подальших перевірок. Для кожного знайденого workspace-а `lint` послідовно звіряє JS-залежності, Rust-залежності, реєстрацію плагінів, permission-и, наявність виклику updater-хука та підключення Quasar Dialog, щоб гарантувати не лише встановлені пакети, а й видимий користувачу сценарій оновлення.
23
+
24
+ `MIN_TAURI_COMPONENTS_VERSION` задає мінімально прийнятну версію UI-компонентів updater-а; `meetsMinVersion` використовує нижню межу версійного діапазону для перевірки цього мінімуму, а `hasMajor` окремо контролює очікувану major-лінійку залежностей.
25
+
26
+ Для Cargo-перевірок `groupCargoDepsBySection` перетворює вміст маніфеста на секційний контекст, а `findSectionDeclaring` визначає, у якій секції оголошена потрібна залежність. `CARGO_TARGET_SECTION_RE` і `CARGO_MOBILE_SECTION_RE` відокремлюють платформні секції від загальних, щоб updater/process-залежності не потрапляли в мобільний або неправильний target-контекст. `CARGO_DESKTOP_TARGET_HEADER="target.\"` позначає desktop target-секцію Cargo, у якій очікуються desktop-специфічні залежності.
27
+
28
+ `collectCapabilityPermissionIds` збирає permission-ідентифікатори з capability-файлів workspace-а, після чого `lint` перевіряє наявність дозволів, потрібних для перевірки оновлень і перезапуску застосунку. Результати всіх підперевірок агрегуються в один lint-висновок без запису у файлову систему і без спільного стану між workspace-ами.
29
+
30
+ ## Публічний API
31
+
32
+ - MIN_TAURI_COMPONENTS_VERSION — Мінімально допустима версія tauri-plugin-updater-сумісних компонентів (major, minor, patch).
33
+ - CARGO_TARGET_SECTION_RE — Розпізнає target-специфічну секцію залежностей у Cargo.toml.
34
+ - CARGO_MOBILE_SECTION_RE — Розпізнає мобільну (Android/iOS) target-секцію — updater там не потрібен.
35
+ - CARGO_DESKTOP_TARGET_HEADER — Канонічний заголовок desktop-only секції залежностей, куди має потрапити updater-плагін.
36
+ - findTauriAppWorkspaces — Знаходить workspace-каталоги з Tauri-застосунком (`<ws>/src-tauri/tauri.conf.json` чи legacy `<ws>/tauri.conf.json`).
37
+ - meetsMinVersion — Чи нижня межа `range` >= `min` (порівняння major.minor.patch).
38
+ - hasMajor — Чи мажорна версія `range` дорівнює очікуваній.
39
+ - groupCargoDepsBySection — Групує рядки Cargo.toml за заголовком секції `[...]` для контекстного пошуку залежностей.
40
+ - findSectionDeclaring — Знаходить назву секції Cargo.toml, що оголошує задану залежність.
41
+ - collectCapabilityPermissionIds — Збирає всі permission-ідентифікатори з `capabilities/*.json` workspace-каталогу.
42
+ - `lint` — знаходить некоректно оформлені параметри `path` у Tauri/Cargo desktop-налаштуваннях і підказує, де очікується узгодження з конфігами `tauri.conf.json` та `package.json`.
43
+
44
+ Експортована константа-рядок `CARGO_DESKTOP_TARGET_HEADER="target.\"` позначає початок desktop-секції Cargo target, щоб правило відрізняло платформні налаштування від загальних.
28
45
 
29
46
  ## Гарантії поведінки
30
47
 
31
- - Read-only: не виконує операцій запису (ФС/БД).
48
+ - Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
32
49
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -8,12 +8,16 @@ import { globby } from 'globby'
8
8
  import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
9
9
  import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
10
10
 
11
+ /** Мінімально допустима версія tauri-plugin-updater-сумісних компонентів (major, minor, patch). */
11
12
  export const MIN_TAURI_COMPONENTS_VERSION = [0, 8, 0]
12
13
  const CARGO_TABLE_HEADER_RE = /^\[(.+)\]\s*$/u
13
14
  const CARGO_DEP_KEY_RE = /^([A-Za-z0-9_-]+)\s*=/u
14
15
  const SEMVER_FLOOR_RE = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/u
16
+ /** Розпізнає target-специфічну секцію залежностей у Cargo.toml. */
15
17
  export const CARGO_TARGET_SECTION_RE = /target\./u
18
+ /** Розпізнає мобільну (Android/iOS) target-секцію — updater там не потрібен. */
16
19
  export const CARGO_MOBILE_SECTION_RE = /android|ios/u
20
+ /** Канонічний заголовок desktop-only секції залежностей, куди має потрапити updater-плагін. */
17
21
  export const CARGO_DESKTOP_TARGET_HEADER =
18
22
  'target.\'cfg(not(any(target_os = "android", target_os = "ios")))\'.dependencies'
19
23
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "code",
4
+ "skipLocalTier": true,
5
+ "lint": {
6
+ "scope": "per-file",
7
+ "glob": ["**/*.{js,mjs,cjs,jsx,ts,tsx,vue}"]
8
+ }
9
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * fix-worker концерну `coverage` правила `test` (spec 2026-07-22 absorb-7n-test):
3
+ * LLM-догенерація тестів/stories і починка survived-мутантів через опційні
4
+ * fix-hooks coverage-провайдерів мовних плагінів.
5
+ *
6
+ * Мапа порушення → дія:
7
+ * - `coverage-below-threshold` з `v.file` (делта-режим) → файли нижче порогу:
8
+ * `.vue` → `provider.generateStories` (stories = компонентні тести,
9
+ * Storybook-вимір), решта → `provider.generateTests` (assess-need → gen-tests
10
+ * усередині провайдера). Розподіл за розширенням робить worker — кожен хук
11
+ * отримує лише свої файли (простіше за подвійну фільтрацію в провайдері).
12
+ * - `mutation-below-threshold` з `v.data.survived` → `provider.fixSurvived`
13
+ * (батчеві агентні сесії по мутантах).
14
+ * - після генерації → `provider.fixFailingTests` (тести, що впали після
15
+ * генерації; свіжий vitest-прогін усередині провайдера).
16
+ *
17
+ * Fix-hooks опційні в контракті провайдера — перевірка через `typeof`
18
+ * (assert порту вимагає лише detect/collect/collectPerFile). Хуки отримують
19
+ * FixContext-поля (model/tier/timeoutMs/recordWrite/chain/signal/feedback);
20
+ * recordWrite прокидається до кожного місця запису (rollback-контракт ladder-а).
21
+ * Дедлайн: DEADLINE_FRACTION від ctx.timeoutMs гейтить СТАРТ наступного хука
22
+ * (як js/eslint fix-worker); залишок бюджету передається хуку як timeoutMs.
23
+ * Власних retry-циклів немає — success визначає canonical re-detect runner-а.
24
+ * @typedef {import('../../../scripts/lib/lint-surface/types.mjs').FixWorkerFn} FixWorkerFn
25
+ * @typedef {import('../../../scripts/lib/lint-surface/types.mjs').FixContext} FixContext
26
+ * @typedef {import('../../../scripts/lib/lint-surface/types.mjs').LintViolation} LintViolation
27
+ */
28
+ import { resolveProviders } from './main.mjs'
29
+
30
+ /** Частка ctx.timeoutMs, після якої не стартує наступний хук (запас до backstop ×1.25). */
31
+ const DEADLINE_FRACTION = 0.8
32
+
33
+ /** `.vue`-файли → generateStories, решта → generateTests. */
34
+ const VUE_FILE_RE = /\.vue$/
35
+
36
+ /**
37
+ * Групує violations концерну за призначенням fix-hooks.
38
+ * @param {LintViolation[]} violations порушення концерну coverage
39
+ * @returns {{belowThreshold: Array<{file: string, pct: number, reason: string}>, survived: object[]}} файли нижче порогу + survived-групи
40
+ */
41
+ export function groupViolations(violations) {
42
+ const belowThreshold = []
43
+ const survived = []
44
+ for (const v of violations) {
45
+ if (v.reason === 'coverage-below-threshold' && v.file) {
46
+ belowThreshold.push({ file: v.file, pct: v.data?.pct ?? 0, reason: '' })
47
+ } else if (v.reason === 'mutation-below-threshold' && Array.isArray(v.data?.survived)) {
48
+ survived.push(...v.data.survived)
49
+ }
50
+ }
51
+ return { belowThreshold, survived }
52
+ }
53
+
54
+ /**
55
+ * FixContext-поля для fix-hook провайдера із залишком бюджету до дедлайну.
56
+ * @param {FixContext} ctx контекст рунга
57
+ * @param {number|null} deadlineAt epoch-ms дедлайн worker-а
58
+ * @returns {FixContext} копія ctx з обрізаним timeoutMs
59
+ */
60
+ function hookCtx(ctx, deadlineAt) {
61
+ const remaining = deadlineAt ? Math.max(1000, deadlineAt - Date.now()) : ctx.timeoutMs
62
+ return { ...ctx, timeoutMs: remaining }
63
+ }
64
+
65
+ /** @type {FixWorkerFn} */
66
+ export async function fixWorker(violations, ctx, deps = {}) {
67
+ // Дедлайн фіксується ДО резолву провайдерів — їх завантаження теж у бюджеті рунга.
68
+ const deadlineAt = ctx.timeoutMs ? Date.now() + Math.round(ctx.timeoutMs * DEADLINE_FRACTION) : null
69
+ const expired = () => deadlineAt !== null && Date.now() >= deadlineAt
70
+
71
+ const providers = await (deps.resolveProviders ?? resolveProviders)(ctx.cwd)
72
+ const { belowThreshold, survived } = groupViolations(violations)
73
+ const vueFiles = belowThreshold.filter(f => VUE_FILE_RE.test(f.file))
74
+ const jsFiles = belowThreshold.filter(f => !VUE_FILE_RE.test(f.file))
75
+
76
+ /** @type {string[]} */
77
+ const touchedFiles = []
78
+ /**
79
+ * Викликає опційний fix-hook провайдера, збирає touchedFiles; виняток хука не
80
+ * валить решту хуків/провайдерів — success визначає canonical re-detect.
81
+ * @param {object} provider coverage-провайдер плагіна
82
+ * @param {string} hook імʼя хука
83
+ * @param {object} args аргументи хука (без ctx)
84
+ * @returns {Promise<void>}
85
+ */
86
+ const runHook = async (provider, hook, args) => {
87
+ if (typeof provider[hook] !== 'function' || expired()) return
88
+ try {
89
+ const res = await provider[hook]({ ...args, cwd: ctx.cwd, ctx: hookCtx(ctx, deadlineAt) })
90
+ touchedFiles.push(...(res?.touchedFiles ?? []))
91
+ } catch (error) {
92
+ console.warn(
93
+ `⚠ coverage fix-worker: ${provider.id}.${hook} впав: ${String(error?.message ?? error).slice(0, 200)}`
94
+ )
95
+ }
96
+ }
97
+
98
+ for (const provider of providers) {
99
+ if (jsFiles.length > 0) await runHook(provider, 'generateTests', { files: jsFiles })
100
+ if (vueFiles.length > 0) await runHook(provider, 'generateStories', { files: vueFiles })
101
+ if (survived.length > 0) await runHook(provider, 'fixSurvived', { survived })
102
+ // Після генерації: тести, що впали (зокрема щойно згенеровані), чиняться
103
+ // окремим хуком — свіжий vitest-прогін усередині провайдера. Без жодної
104
+ // роботи вище (порожній профіль violations) хук не стартує.
105
+ if (survived.length > 0 || belowThreshold.length > 0) {
106
+ await runHook(provider, 'fixFailingTests', {})
107
+ }
108
+ }
109
+
110
+ return { touchedFiles }
111
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Застосовує verdicts до coverage rows: фільтрує survived мутантів,
3
+ * декрементує mutation.total на кількість allowed-gaps, повертає окремий
4
+ * список allowedGaps для рендеру в COVERAGE.md.
5
+ *
6
+ * Skip rule: verdict ∈ {equivalent,defensive,glue,wrapper} AND confidence ≥ threshold.
7
+ * Решта (включно з worth-testing і low-confidence skip-verdicts) залишаються в survived.
8
+ */
9
+
10
+ const SKIP_VERDICTS = new Set(['equivalent', 'defensive', 'glue', 'wrapper'])
11
+
12
+ /**
13
+ * Чи verdict кваліфікує мутанта як allowed-gap (виключити з Killable).
14
+ * @param {{verdict: string, confidence: number}} verdict verdict-об'єкт
15
+ * @param {number} threshold confidence threshold (наприклад 0.7)
16
+ * @returns {boolean} true якщо мутант — allowed gap
17
+ */
18
+ export function isAllowedGap(verdict, threshold) {
19
+ return SKIP_VERDICTS.has(verdict.verdict) && verdict.confidence >= threshold
20
+ }
21
+
22
+ /**
23
+ * Застосовує verdicts до coverage rows. Фільтрує `survived` за isAllowedGap,
24
+ * зменшує `mutation.total` на скільки мутантів стало allowed-gap.
25
+ * Не мутує вхідні дані.
26
+ * @param {Array<{area: string, coverage: object, mutation: {caught: number, total: number}, survived?: Array<{file: string, mutants: Array<object>, exampleTest?: object|null, recommendationText?: string|null}>}>} rows вхідні рядки
27
+ * @param {Array<{key: string, verdict: {verdict: string, confidence: number, reason: string}}>} verdicts класифіковані verdict-и
28
+ * @param {number} threshold confidence threshold для allowed-gap
29
+ * @returns {{rows: Array<object>, allowedGaps: Array<{file: string, mutant: object, verdict: object}>}} augmented rows + список allowed-gaps
30
+ */
31
+ export function applyVerdicts(rows, verdicts, threshold) {
32
+ const verdictByKey = new Map()
33
+ for (const { key, verdict } of verdicts) verdictByKey.set(key, verdict)
34
+
35
+ const allowedGaps = []
36
+
37
+ const augmentedRows = rows.map(row => {
38
+ const survived = row.survived ?? []
39
+ let skippedCount = 0
40
+ const remainingSurvived = []
41
+
42
+ for (const group of survived) {
43
+ const remainingMutants = []
44
+ for (const mutant of group.mutants) {
45
+ const key = `${group.file}:${mutant.line}:${mutant.col}:${mutant.replacement}`
46
+ const verdict = verdictByKey.get(key)
47
+ if (verdict && isAllowedGap(verdict, threshold)) {
48
+ allowedGaps.push({ file: group.file, mutant, verdict })
49
+ skippedCount += 1
50
+ } else {
51
+ remainingMutants.push(mutant)
52
+ }
53
+ }
54
+ if (remainingMutants.length > 0) {
55
+ remainingSurvived.push({ ...group, mutants: remainingMutants })
56
+ }
57
+ }
58
+
59
+ return {
60
+ ...row,
61
+ survived: remainingSurvived,
62
+ mutation: { ...row.mutation, total: row.mutation.total - skippedCount }
63
+ }
64
+ })
65
+
66
+ return { rows: augmentedRows, allowedGaps }
67
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * File-hash-keyed cache для coverage-classify verdicts.
3
+ *
4
+ * Cache key = `<blob-hash>:<line>:<col>:<base64url(replacement)>`.
5
+ * Blob hash рахуємо через `git hash-object <file>` (детерміновано на working tree)
6
+ * з fallback на sha1(readFile) якщо git недоступний.
7
+ *
8
+ * Cache schema:
9
+ * { version: 1, model: string|null, entries: Record<key, { verdict, confidence, reason, suggestedTest?, classifiedAt }> }
10
+ *
11
+ * Інвалідація: будь-яка зміна source → новий blob-hash → cache miss → re-classify.
12
+ */
13
+ import { execFileSync } from 'node:child_process'
14
+ import { createHash } from 'node:crypto'
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
16
+ import { dirname } from 'node:path'
17
+
18
+ const CACHE_VERSION = 1
19
+
20
+ /**
21
+ * Хеш контенту файла (sha1, 40 hex chars). Спочатку `git hash-object`,
22
+ * інакше sha1 контенту.
23
+ * @param {string} filePath абсолютний шлях до файла
24
+ * @returns {string | null} 40-char hex hash або null якщо файл недоступний
25
+ */
26
+ export function deriveBlobHash(filePath) {
27
+ if (!existsSync(filePath)) return null
28
+ try {
29
+ return execFileSync('git', ['hash-object', filePath], { encoding: 'utf8' }).trim()
30
+ } catch {
31
+ const content = readFileSync(filePath)
32
+ return createHash('sha256').update(content).digest('hex')
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Cache-ключ для конкретного мутанта в конкретному стані файла.
38
+ * @param {string} filePath абсолютний шлях до source файла
39
+ * @param {{line: number, col: number, replacement: string}} mutant параметри мутанта
40
+ * @returns {string | null} ключ або null якщо файл недоступний
41
+ */
42
+ export function deriveCacheKey(filePath, mutant) {
43
+ const blobHash = deriveBlobHash(filePath)
44
+ if (!blobHash) return null
45
+ const replacement = Buffer.from(mutant.replacement, 'utf8').toString('base64url')
46
+ return `${blobHash}:${mutant.line}:${mutant.col}:${replacement}`
47
+ }
48
+
49
+ /**
50
+ * Читає cache з диска. При будь-якій проблемі (file absent, corrupt JSON,
51
+ * schema/version mismatch, entries не object) — повертає empty cache.
52
+ * @param {string} cachePath абсолютний шлях до cache.json
53
+ * @returns {{version: number, model: string|null, entries: Record<string, object>}} cache
54
+ */
55
+ export function readCache(cachePath) {
56
+ const empty = { version: CACHE_VERSION, model: null, entries: {} }
57
+ if (!existsSync(cachePath)) return empty
58
+ try {
59
+ const data = JSON.parse(readFileSync(cachePath, 'utf8'))
60
+ if (data?.version !== CACHE_VERSION) return empty
61
+ if (!data.entries || typeof data.entries !== 'object' || Array.isArray(data.entries)) return empty
62
+ return data
63
+ } catch {
64
+ return empty
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Записує cache на диск. Створює батьківські директорії.
70
+ * @param {string} cachePath абсолютний шлях
71
+ * @param {{version: number, model: string|null, entries: Record<string, object>}} cache cache-об'єкт
72
+ * @returns {void}
73
+ */
74
+ export function writeCache(cachePath, cache) {
75
+ mkdirSync(dirname(cachePath), { recursive: true })
76
+ writeFileSync(cachePath, `${JSON.stringify(cache, null, 2)}\n`, 'utf8')
77
+ }
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: JS Module
3
+ title: apply.mjs
4
+ resource: npm/src/coverage-classify/apply.mjs
5
+ docgen:
6
+ crc: 0f54e6a0
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ judgeModel: openai-codex/gpt-5.4-mini
10
+ ---
11
+
12
+ ## Огляд
13
+
14
+ Файл відповідає за застосування вердиктів до рядків покриття, фільтруючи виживших мутантів. Застосовується Skip rule, якщо `verdict` належить до `{equivalent, defensive, glue, wrapper}` і `confidence` досягає заданого порогу. Для інших мутантів, включаючи ті, що мають `worth-testing` або низьку впевненість, мутанти залишаються в наборі виживших. Крім того, загальна кількість мутантів (`mutation.total`) декрементується на кількість `allowed-gaps`, а окремий список `allowedGaps` повертається для візуалізації в `COVERAGE.md`.
15
+
16
+ ## Поведінка
17
+
18
+ isAllowedGap визначає, чи мутант відповідає критеріям для віднесення до категорії allowed-gap, що дозволяє його ігнорувати при оцінці покриття.
19
+ applyVerdicts фільтрує виживших мутантів на основі наданих verdict-ів, зменшує загальну кількість мутантів та повертає новий набір рядків покриття разом зі списком allowedGaps.
20
+
21
+ ## Публічний API
22
+
23
+ isAllowedGap — Визначає, чи слід вважати мутанта прийнятним пропуском (виключає його з категорії потенційно "вбиваних" мутантів).
24
+ applyVerdicts — Призначає рішення (verdicts) до рядків покриття. Відфільтровує мутанти, які вижили, якщо вони кваліфікуються як прийнятний пропуск, і зменшує загальну кількість мутантів у системі відповідно до кількості таких прийнятних пропусків.
25
+
26
+ ## Гарантії поведінки
27
+
28
+ - (специфічних машинно-виведених гарантій немає)
@@ -0,0 +1,34 @@
1
+ ---
2
+ type: JS Module
3
+ title: cache.mjs
4
+ resource: npm/src/coverage-classify/cache.mjs
5
+ docgen:
6
+ crc: 53b251b1
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ issues: judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Механізм кешування фіксує вердикти класифікації мутантів для прискорення аналізу. Він використовує кеш, описаний у `cache.json`, де ключами є ідентифікатори контенту у форматі `<blob-hash>:<line>:<col>:<base64url>`, що динамічно формується на основі хешу файлу (`git hash-object` або `sha1`). Збережені дані включають вердикт, рівень впевненості та причину, а інвалідація кешу спрацьовує при зміні джерела коду, що призводить до нового хешу.
16
+
17
+ ## Поведінка
18
+
19
+ deriveBlobHash обчислює унікальний хеш контенту файлу, використовуючи `git hash-object` або SHA256 контенту у випадку відсутності Git.
20
+ deriveCacheKey створює унікальний ключ кешу для мутанта на основі хешу файлу, номера рядка, колонки та заміни.
21
+ readCache зчитує дані кешу з файлу, повертаючи порожній кеш у разі відсутності або некоректності.
22
+ writeCache зберігає об'єкт кешу на диск, автоматично створюючи необхідні батьківські директорії.
23
+
24
+ ## Публічний API
25
+
26
+ deriveBlobHash — Створює унікальний ідентифікатор вмісту файлу (SHA1 хеш).
27
+ deriveCacheKey — Генерує ключ для кешування, що описує зміну файлу у певному стані.
28
+ readCache — Завантажує кешовані дані з диска; повертає порожній кеш при будь-якій помилці зчитування.
29
+ writeCache — Зберігає кешовані дані на диск, створюючи необхідну структуру каталогів.
30
+
31
+ ## Гарантії поведінки
32
+
33
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
34
+ - Кешує результати в межах одного прогону.
@@ -0,0 +1,37 @@
1
+ ---
2
+ type: JS Module
3
+ title: index.mjs
4
+ resource: npm/src/coverage-classify/index.mjs
5
+ docgen:
6
+ crc: 6f120c02
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ ---
9
+
10
+ ## Огляд
11
+
12
+ Класифікатор визначає, чи слід вважати мутант, переданий у межах `classify`, мутантом, згенерованим тестом. Механізм працює через маршрутизацію через pi SDK (callText). Процес включає перевірку в кеші, пошук у локальній (Tier 1) моделі, пошук у хмарній (Tier 2) моделі, а у разі повної відмови систем — застосування консервативного результату. Усі результати класифікації зберігаються у кеші, який контролюється конфігурацією у `coverage-classify.cache.json`.
13
+
14
+ Ключовий механізм роботи:
15
+ Перевірка кешу з файлу `coverage-classify.cache.json`. Якщо відповідний запис знайдено, повертається збережений verdicts[]. Якщо кеш не містить результату, ініціюється послідовна класифікація: спочатку за допомогою локальної моделі (`N_LOCAL_MIN_MODEL`), а після невдачі — за допомогою хмарної моделі (`N_CLOUD_MIN_MODEL`). У випадку невдачі обох рівнів, застосовується консервативний fallback (worth-testing/confidence=0). Після успішної класифікації результат додається до кешу.
16
+
17
+ ## Поведінка
18
+
19
+ 1. Отримується кеш з файлу `coverage-classify.cache.json`. Якщо конфігурація моделей у кеші не відповідає поточній, кеш ініціалізується.
20
+ 2. Для кожного скомпільованого мутанта у наданому наборі виконується перевірка в кеші за унікальним ключем, що включає шлях до файлу, рядок, стовпець та заміну.
21
+ 3. Якщо знайдено відповідний запис у кеші, він повертається як результат класифікації.
22
+ 4. Якщо в кеші запису немає, ініціюється класифікація мутанта.
23
+ 5. Класифікація здійснюється послідовно через джерела ШІ: спочатку через модель, визначену у змінній `N_LOCAL_MIN_MODEL`, а якщо це не вдається, то через модель, визначену у змінній `N_CLOUD_MIN_MODEL`.
24
+ 6. У разі невдачі обох моделей, встановлюється консервативний результат, що вважається мутантом, вартим тестування з низькою впевненістю.
25
+ 7. Успішно класифікований результат додається до кешу з часовою відміткою.
26
+ 8. Після обробки всіх мутантів, оновлений кеш записується у файл `coverage-classify.cache.json`.
27
+ 9. Повертається список результатів класифікації для всіх мутантів.
28
+
29
+ ## Публічний API
30
+
31
+ - classify — Визначає, які мутанти вижили, використовуючи модель для локальних даних, потім для хмарних, а якщо це не вдається, застосовує запасний варіант.
32
+
33
+ ## Гарантії поведінки
34
+
35
+ - Read-only: не виконує операцій запису (ФС/БД).
36
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
37
+ - Кешує результати в межах одного прогону.