@nitra/cursor 3.27.0 → 3.29.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 (128) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +1 -3
  3. package/rules/abie/js/applies.mjs +1 -5
  4. package/rules/abie/js/env_dns.mjs +1 -9
  5. package/rules/abie/js/firebase_hosting.mjs +1 -5
  6. package/rules/abie/js/hc_pairing.mjs +1 -8
  7. package/rules/abie/js/ua_http_route.mjs +1 -10
  8. package/rules/abie/js/ua_node_selector.mjs +1 -8
  9. package/rules/adr/js/hooks.mjs +1 -20
  10. package/rules/bun/js/layout.mjs +1 -19
  11. package/rules/capacitor/js/platforms.mjs +1 -23
  12. package/rules/changelog/js/consistency.mjs +1 -29
  13. package/rules/ci4/js/marksman_config.mjs +1 -19
  14. package/rules/docker/js/lint.mjs +1 -34
  15. package/rules/ga/docs/fix.md +4 -4
  16. package/rules/ga/js/docs/lint.md +3 -3
  17. package/rules/ga/js/docs/workflows.md +14 -14
  18. package/rules/ga/js/workflows.mjs +1 -16
  19. package/rules/ga/lint/docs/lint.md +9 -9
  20. package/rules/graphql/js/tooling.mjs +1 -9
  21. package/rules/hasura/js/internal_urls.mjs +1 -24
  22. package/rules/image-avif/js/avif_generation.mjs +1 -27
  23. package/rules/image-compress/js/package_setup.mjs +1 -18
  24. package/rules/js-bun-db/js/safety.mjs +1 -31
  25. package/rules/js-bun-redis/js/imports.mjs +1 -12
  26. package/rules/js-lint/js/docs/lint-findings.md +30 -0
  27. package/rules/js-lint/js/lint-findings.mjs +1 -7
  28. package/rules/js-lint/js/lint.mjs +1 -10
  29. package/rules/js-lint/js/tooling.mjs +1 -13
  30. package/rules/js-lint/js/utils_imports.mjs +1 -18
  31. package/rules/js-lint-ci/js/lint.mjs +1 -6
  32. package/rules/js-mssql/js/deps.mjs +1 -10
  33. package/rules/js-run/js/runtime.mjs +1 -37
  34. package/rules/js-run/lib/docs/temporal-scan.md +25 -0
  35. package/rules/k8s/js/manifests.mjs +1 -137
  36. package/rules/nginx-default-tpl/js/template.mjs +1 -18
  37. package/rules/npm-module/js/docs/header_doc_pointer.md +25 -0
  38. package/rules/npm-module/js/header_doc_pointer.mjs +82 -0
  39. package/rules/npm-module/js/package_structure.mjs +1 -28
  40. package/rules/npm-module/js/rule_meta.mjs +1 -10
  41. package/rules/npm-module/js/skill_meta.mjs +1 -13
  42. package/rules/php/js/tooling.mjs +1 -11
  43. package/rules/python/js/applies.mjs +1 -8
  44. package/rules/python/js/tooling.mjs +1 -21
  45. package/rules/rego/js/applies.mjs +1 -11
  46. package/rules/rust/js/applies.mjs +1 -7
  47. package/rules/security/js/sample_secret.mjs +1 -28
  48. package/rules/security/js/trufflehog.mjs +1 -8
  49. package/rules/style-lint/js/lint.mjs +1 -5
  50. package/rules/style-lint/js/tooling.mjs +1 -19
  51. package/rules/tauri/js/cargo_mutants_config.mjs +1 -20
  52. package/rules/tauri/js/tooling.mjs +1 -21
  53. package/rules/test/js/cargo_mutants_config.mjs +1 -12
  54. package/rules/test/js/location.mjs +1 -9
  55. package/rules/test/js/no-process-chdir.mjs +1 -21
  56. package/rules/test/js/no-relative-fs-path.mjs +1 -23
  57. package/rules/test/js/stryker_config.mjs +4 -25
  58. package/rules/test/js/vitest-config-pool-forks.mjs +1 -17
  59. package/rules/text/js/forbidden-prettier.mjs +1 -10
  60. package/rules/text/js/formatting.mjs +1 -31
  61. package/rules/vue/js/packages.mjs +1 -24
  62. package/scripts/coverage-classify/index.mjs +60 -72
  63. package/scripts/coverage-fix.mjs +26 -23
  64. package/scripts/dispatcher/lib/subagent-runner.mjs +33 -102
  65. package/scripts/docs/coverage-fix-extract.md +32 -0
  66. package/scripts/docs/lint-cli.md +25 -0
  67. package/scripts/docs/post-tool-use-fix.md +27 -0
  68. package/scripts/docs/rename-yaml-extensions.md +36 -0
  69. package/scripts/docs/skills-cli.md +35 -0
  70. package/scripts/docs/sync-claude-config.md +52 -0
  71. package/scripts/docs/sync-setup-bun-deps-action.md +26 -0
  72. package/scripts/docs/upgrade-nitra-cursor-and-install.md +29 -0
  73. package/scripts/docs/worktree-cli.md +46 -0
  74. package/scripts/lib/docs/assert-project-root.md +28 -0
  75. package/scripts/lib/docs/diff-added-lines.md +34 -0
  76. package/scripts/lib/docs/read-n-cursor-config-lite.md +28 -0
  77. package/scripts/lib/docs/resolve-target-files.md +34 -0
  78. package/scripts/lib/docs/root-notice.md +28 -0
  79. package/scripts/lib/docs/rule-meta-helpers.md +34 -0
  80. package/scripts/lib/docs/rule-meta.md +34 -0
  81. package/scripts/lib/docs/rule-predicates.md +30 -0
  82. package/scripts/lib/docs/run-conftest-batch.md +26 -0
  83. package/scripts/lib/docs/run-lint-step.md +25 -0
  84. package/scripts/lib/docs/run-rule-cli.md +27 -0
  85. package/scripts/lib/docs/run-rule.md +32 -0
  86. package/scripts/lib/docs/run-standard-lint.md +22 -0
  87. package/scripts/lib/docs/run-standard-rule.md +24 -0
  88. package/scripts/lib/docs/skill-meta.md +31 -0
  89. package/scripts/lib/docs/sync-gitignore-worktree.md +31 -0
  90. package/scripts/lib/docs/template.md +40 -0
  91. package/scripts/lib/docs/timing-summary.md +24 -0
  92. package/scripts/lib/docs/workspaces.md +30 -0
  93. package/scripts/lib/docs/worktree-notice.md +27 -0
  94. package/scripts/lib/docs/worktree.md +38 -0
  95. package/scripts/utils/docs/ast-scan-utils.md +50 -0
  96. package/scripts/utils/docs/ensure-gitignore-entries.md +28 -0
  97. package/scripts/utils/docs/find-package-json-paths.md +26 -0
  98. package/scripts/utils/docs/lock-cache-dir.md +25 -0
  99. package/scripts/utils/docs/pass.md +25 -0
  100. package/scripts/utils/docs/resolve-cargo-manifest.md +23 -0
  101. package/scripts/utils/docs/resolve-cmd.md +29 -0
  102. package/scripts/utils/docs/resolve-js-root.md +25 -0
  103. package/scripts/utils/docs/test-helpers.md +36 -0
  104. package/scripts/utils/docs/walk-cache.md +27 -0
  105. package/scripts/utils/docs/walkDir.md +32 -0
  106. package/scripts/utils/docs/with-lock.md +25 -0
  107. package/scripts/utils/docs/worktree-fingerprint.md +27 -0
  108. package/skills/docgen/js/docgen-batch.mjs +95 -0
  109. package/skills/docgen/js/docgen-extract.mjs +33 -18
  110. package/skills/docgen/js/docgen-gen.mjs +140 -154
  111. package/skills/docgen/js/docgen-ignore.mjs +1 -6
  112. package/skills/docgen/js/docgen-prompts.mjs +33 -22
  113. package/skills/docgen/js/docgen-scan.mjs +1 -8
  114. package/skills/docgen/js/docs/docgen-extract.md +28 -0
  115. package/skills/docgen/js/docs/docgen-gen.md +41 -0
  116. package/skills/docgen/js/docs/docgen-ignore.md +24 -0
  117. package/skills/docgen/js/docs/docgen-prompts.md +24 -0
  118. package/skills/docgen/js/docs/docgen-scan.md +48 -0
  119. package/skills/fix/js/docs/llm-worker.md +27 -0
  120. package/skills/fix/js/docs/orchestrator.md +32 -0
  121. package/skills/fix/js/docs/t0.md +29 -0
  122. package/skills/fix/js/llm-worker.mjs +64 -29
  123. package/skills/fix/js/orchestrator.mjs +45 -54
  124. package/skills/fix/js/t0.mjs +16 -32
  125. package/skills/start-check/js/check.mjs +1 -16
  126. package/skills/start-check/js/docs/check.md +34 -0
  127. package/skills/taze/js/diff.mjs +1 -15
  128. package/skills/taze/js/docs/diff.md +33 -0
@@ -1,31 +1,4 @@
1
- /**
2
- * FS-частина правила `security`: concern `sample_secret`.
3
- *
4
- * Перевіряє, що фейкові credential-значення у *прикладних* файлах записані як
5
- * канонічний placeholder `sample-secret`, а не як bare `secret`.
6
- *
7
- * `sample-secret` містить підрядок `sample`, який є у вшитому списку
8
- * `DefaultFalsePositives` TruffleHog — таке значення сканер відсіює
9
- * гарантовано й незалежно від версії. Bare `secret` наразі не фіксується сканером
10
- * лише тому, що випадково присутнє у словнику `fp_words.txt`; це крихка поведінка,
11
- * що залежить від версії інструмента, на яку не варто покладатися.
12
- *
13
- * Прикладними вважаються файли, чий basename має суфікс `.example` / `.sample`
14
- * / `.template` / `.dist` або infix `.example.` / `.sample.` / `.template.`, а
15
- * також будь-які файли всередині каталогів `fixtures` / `fixture` /
16
- * `__fixtures__`. Решта файлів не сканується — там `secret` майже завжди
17
- * частина реального коду, а не placeholder.
18
- *
19
- * Порушенням є лише `secret` у *позиції значення* — одразу після `=`, `:` чи
20
- * `=>` (з опційними лапками). Імена ключів (`client_secret`, `JWT_SECRET`) не
21
- * чіпаються: матч прив'язаний до значення, не до ключа.
22
- *
23
- * Чому regex, а не AST: прикладні файли — різнорідні конфіги (`.env`, YAML,
24
- * JSON, TOML, plain `.dist`), єдиного AST для них немає, тож скан порядковий.
25
- * Чому JS, а не Rego: щоб знайти прикладні файли, треба обійти дерево
26
- * (`readdir`), а вміст — неструктурований текст (conftest парсить лише
27
- * структуровані документи).
28
- */
1
+ /** @see ./docs/sample_secret.md */
29
2
  import { readFile } from 'node:fs/promises'
30
3
  import { relative, sep } from 'node:path'
31
4
 
@@ -1,11 +1,4 @@
1
- /**
2
- * FS-частина правила `security`.
3
- *
4
- * Перевіряє:
5
- * - наявність `package.json` (структуру валідує policy security.package_json);
6
- * - наявність `.trufflehog-exclude` у корені та subset канонічних patterns
7
- * (text-subset, бо `.trufflehog-exclude` — plain text, не структурований).
8
- */
1
+ /** @see ./docs/trufflehog.md */
9
2
  import { existsSync } from 'node:fs'
10
3
  import { readFile } from 'node:fs/promises'
11
4
  import { dirname, join } from 'node:path'
@@ -1,8 +1,4 @@
1
- /**
2
- * Quick-крок lint правила style-lint: stylelint --fix по css/scss/vue.
3
- *
4
- * `files` (quick) → лише style-файли з них; undefined (ci) → весь glob `**\/*.{css,scss,vue}`.
5
- */
1
+ /** @see ./docs/lint.md */
6
2
  import { spawnSync } from 'node:child_process'
7
3
 
8
4
  const STYLE_EXT_RE = /\.(?:css|scss|vue)$/u
@@ -1,22 +1,4 @@
1
- /**
2
- * Перевіряє CSS/SCSS лінт за правилом style-lint.mdc.
3
- *
4
- * **Що тут лишилося** (FS / cross-file — не покривається conftest):
5
- * - наявність зовнішнього файлу конфігу stylelint (`.stylelintrc.*`,
6
- * `stylelint.config.js`) як альтернатива полю `stylelint` у `package.json`
7
- * (cross-file: треба знати, чи є поле, чи немає);
8
- * - `.stylelintignore` у корені.
9
- *
10
- * **Що покрила Rego** (`npx \@nitra/cursor check`):
11
- * - `npm/policy/style_lint/package_json/` — скрипт `lint-style` через `npx stylelint`,
12
- * `@nitra/stylelint-config` у `devDependencies`, поле `stylelint.extends`;
13
- * - `npm/policy/style_lint/lint_style_yml/` — `npx stylelint` у `run` workflow;
14
- * - `npm/policy/style_lint/vscode_extensions/` — `stylelint.vscode-stylelint`
15
- * у `recommendations` `.vscode/extensions.json`;
16
- * - `npm/policy/style_lint/vscode_settings/` — `css.validate`/`scss.validate`/
17
- * `less.validate: false` у `.vscode/settings.json`.
18
- * @param {string} cwd корінь репозиторію
19
- */
1
+ /** @see ./docs/tooling.md */
20
2
  import { existsSync } from 'node:fs'
21
3
  import { readFile } from 'node:fs/promises'
22
4
  import { join } from 'node:path'
@@ -1,23 +1,4 @@
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
- */
1
+ /** @see ./docs/cargo_mutants_config.md */
21
2
  import { existsSync } from 'node:fs'
22
3
  import { mkdir, readFile, writeFile } from 'node:fs/promises'
23
4
  import { dirname, join, relative } from 'node:path'
@@ -1,24 +1,4 @@
1
- /**
2
- * Перевіряє інструментарій Tauri (tauri.mdc): VSCode `extensions.json` для
3
- * проєктів, у яких є маркер Tauri.
4
- *
5
- * Cross-file gating (JS):
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/`.
15
- * 2. Якщо маркера немає — пропустити перевірку (tauri-tooling не вимагається).
16
- * 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
17
- * content `rego.tauri.vscode_extensions` через `runConftestBatch`.
18
- *
19
- * Rego-полісі глобально без `target.json` поруч (не auto-discoverable через `n-cursor fix`) — це conditional
20
- * правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
21
- */
1
+ /** @see ./docs/tooling.md */
22
2
  import { existsSync, statSync } from 'node:fs'
23
3
  import { readFile } from 'node:fs/promises'
24
4
  import { join } from 'node:path'
@@ -1,15 +1,4 @@
1
- /**
2
- * Концерн `cargo_mutants_config` правила test (test.mdc): якщо `rust` присутнє
3
- * в `.n-cursor.json#rules` і не у `disable-rules` — визначає ВСІ Cargo.toml
4
- * (cwd і всі workspaces, з підтримкою Tauri-патерну) і копіює canonical
5
- * baseline `.cargo/mutants.toml` у каталог кожного manifest'а, якщо файлу немає.
6
- *
7
- * Self-gating: концерн silently skips коли `rust` не enabled.
8
- * Якщо `rust` enabled, але жодного Cargo.toml не знайдено — теж silently skip
9
- * (manifest може з'явитися пізніше; це не помилка).
10
- *
11
- * Baseline — порожній файл з коментарем; cargo-mutants має робочі defaults.
12
- */
1
+ /** @see ./docs/cargo_mutants_config.md */
13
2
  import { existsSync } from 'node:fs'
14
3
  import { copyFile, mkdir } from 'node:fs/promises'
15
4
  import { dirname, join, relative } from 'node:path'
@@ -1,12 +1,4 @@
1
- /**
2
- * Перевіряє, що всі `*.test.mjs` лежать у каталозі `tests/` (а не поряд із джерельним файлом).
3
- *
4
- * Конвенція (test.mdc): `dir/foo.mjs` → тест у `dir/tests/foo.test.mjs`.
5
- * `*_test.rego` виключені: Rego unit-тести живуть поряд із полісі (OPA community convention).
6
- *
7
- * Пропускає: `node_modules`, `.git`, `dist`, `build`, `.venv`, `venv` (через `walkDir`)
8
- * і шляхи з `.n-cursor.json:ignore`.
9
- */
1
+ /** @see ./docs/location.md */
10
2
  import { basename, dirname, relative } from 'node:path'
11
3
 
12
4
  import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
@@ -1,24 +1,4 @@
1
- /**
2
- * Заборона `process.chdir(...)` у тестах.
3
- *
4
- * Контекст (test.mdc, секція "Заборона `process.chdir` у тестах"):
5
- * `process.chdir` — process-wide мутація. Vitest за замовчуванням ставить
6
- * `pool: 'threads'`, де workers ділять один процес: паралельний test file
7
- * може перехопити cwd сусіда посеред FS- або `git`-операції. Реальний
8
- * інцидент — `git init`+`git commit` із tmp-фікстури `withTmpCwd` потрапив у
9
- * реальний робочий репозиторій і створив rogue commit з автором `test
10
- * <test@test>`. Тому канон: `withTmpDir(async dir => ...)` зі
11
- * `scripts/utils/test-helpers.mjs` (без `chdir`) + явні `cwd: dir` у child-
12
- * процесах + `await check(dir)` для concern-функцій.
13
- *
14
- * Цей concern сканує `**\/*.test.{js,mjs}` і падає на будь-яке вживання
15
- * `process.chdir(`. Виняток — коментарі/документація: regex знаходить лише
16
- * викликний паттерн (відкривна дужка), тож згадки у JSDoc типу
17
- * "не використовуй `process.chdir`" не тригерять.
18
- *
19
- * Скіпи: `node_modules`, `.git`, `dist`, `build`, `.venv`, `venv` (через
20
- * `walkDir`) і шляхи з `.n-cursor.json:ignore`.
21
- */
1
+ /** @see ./docs/no-process-chdir.md */
22
2
  import { readFile } from 'node:fs/promises'
23
3
  import { basename, relative } from 'node:path'
24
4
 
@@ -1,26 +1,4 @@
1
- /**
2
- * Заборона **relative-path** аргументів у FS-функціях усередині тестів.
3
- *
4
- * Контекст (test.mdc, секція "Заборона `process.chdir` у тестах"):
5
- * Після видалення `withTmpCwd` усі тести отримують `dir` параметром і мають
6
- * будувати **абсолютні** шляхи через `join(dir, …)`. Якщо хтось забуде префікс
7
- * і напише `writeFile('foo.json', …)` чи `copyFile(src, 'foo.json')` —
8
- * relative-path резолвиться у `process.cwd()` (= `npm/`), що зливає тестову
9
- * фікстуру у production tree. Інцидент v1.28.0: `tests/check-rule-fixtures.test.mjs`
10
- * залишив `copyFile(src, 'values-dev.ini')` і `copyFile(src, 'default.conf.template')` —
11
- * створило файли `npm/values-dev.ini` і `npm/default.conf.template`.
12
- *
13
- * Сканер AST-based (oxc-parser): знаходить виклики `node:fs`/`node:fs/promises`
14
- * функцій із **string literal** аргументом-шляхом, який НЕ починається з:
15
- * - `/`, `\\` — POSIX/Windows absolute;
16
- * - `file:`/`http`/`data:` — URL-схема (передається до `new URL(...)`);
17
- * - `${…}` (template-literal з виразом) і `\`…\${dir}\`` патерни — обчислений шлях;
18
- * - `:` для Windows-літер диску `C:\…` (рідко в тестах, але legit).
19
- * Виклики, чий path-аргумент — НЕ literal (CallExpression `join(...)`, BinaryExpression,
20
- * Identifier, MemberExpression) — пропускаємо: припускаємо що це абсолютний шлях.
21
- *
22
- * Скани: `**\/*.test.{js,mjs}` з загальними `walkDir` skip + `.n-cursor.json#ignore`.
23
- */
1
+ /** @see ./docs/no-relative-fs-path.md */
24
2
  import { readFile } from 'node:fs/promises'
25
3
  import { basename, relative } from 'node:path'
26
4
 
@@ -1,27 +1,4 @@
1
- /**
2
- * Концерн `stryker_config` правила test (test.mdc): якщо `js-lint` присутнє в
3
- * `.n-cursor.json#rules` і не у `disable-rules` — визначає ВСІ JS-roots
4
- * (всі workspaces з package.json, або cwd у single-package) і копіює canonical
5
- * baseline `stryker.config.mjs` + `vitest.config.js` у кожен root, де файлу немає.
6
- *
7
- * Для JS-roots із `.vue` файлами (Vue 3 + `<script setup>`) копіюється vue-варіант
8
- * baseline, який реєструє локальний Ignore-плагін `vue-macros` — інакше Stryker
9
- * огортає виклики `defineProps`/`defineEmits`/... у coverage-тернарник і
10
- * `@vue/compiler-sfc` падає при компіляції SFC. Плагін копіюється у той самий
11
- * jsRoot як `stryker-vue-macros-ignorer.mjs`.
12
- *
13
- * Augment (drift-hole): якщо у Vue-root `stryker.config.mjs` уже існував (проєкт
14
- * мав non-vue config ще до 3.x Vue-підтримки), `ensureBaselineFile` його не
15
- * перетирає — тож `augmentVueStrykerConfig` точково вставляє `plugins`/`ignorers`
16
- * у наявний файл (string-splice за AST-аналізом), зберігаючи решту полів і
17
- * коментарів. Idempotent: повторний прогон нічого не дублює.
18
- *
19
- * Self-gating: концерн silently skips коли `js-lint` не enabled — це навмисно,
20
- * щоб не шуміти у single-language проєктах без JS coverage tooling.
21
- *
22
- * Baseline — мінімум для запуску Stryker з vitest-runner + perTest; mutate-патерни
23
- * лишаються на Stryker defaults (`src/**\/*.{js,mjs,ts,jsx,tsx,cjs}`).
24
- */
1
+ /** @see ./docs/stryker_config.md */
25
2
  import { existsSync } from 'node:fs'
26
3
  import { copyFile, glob, readFile, writeFile } from 'node:fs/promises'
27
4
  import { dirname, join, relative } from 'node:path'
@@ -310,7 +287,9 @@ async function augmentVueStrykerConfig(reporter, cwd, jsRoot) {
310
287
  try {
311
288
  recheck = parseSync(target, next, { lang: 'js', sourceType: 'module' })
312
289
  } catch (error) {
313
- reporter.fail(`stryker.config.mjs: augment дав некоректний результат (${rel}): ${error.message} — відкат, додай вручну`)
290
+ reporter.fail(
291
+ `stryker.config.mjs: augment дав некоректний результат (${rel}): ${error.message} — відкат, додай вручну`
292
+ )
314
293
  return
315
294
  }
316
295
  if (recheck.errors?.length) {
@@ -1,20 +1,4 @@
1
- /**
2
- * `vitest.config.js` має ставити `pool: 'forks'` — defense-in-depth для
3
- * race-bug у `process.cwd()` (test.mdc, секція "Заборона `process.chdir` у тестах").
4
- *
5
- * Чому не достатньо самої заборони `process.chdir(`: third-party код у залежностях
6
- * може робити chdir всередині vitest worker'а. У `pool: 'threads'` (default) усі
7
- * workers ділять один процес → race на `process.cwd()` між паралельними test
8
- * files. `pool: 'forks'` ізолює кожен test file у власному child-процесі.
9
- *
10
- * Перевірка — substring у source-тексті `vitest.config.js`. Не парсимо JS AST,
11
- * бо це може бути будь-який export-формат (ESM default, named, CommonJS).
12
- * Достатньо знайти `pool:` із значенням `'forks'`/`"forks"` (whitespace дозволений).
13
- *
14
- * Скіпи: правило не застосовне, якщо `vitest.config.js` не існує (нема vitest
15
- * у проєкті) — це не помилка, лише skip. Якщо файл є — `pool: 'forks'`
16
- * обов'язковий.
17
- */
1
+ /** @see ./docs/vitest-config-pool-forks.md */
18
2
  import { existsSync } from 'node:fs'
19
3
  import { readFile } from 'node:fs/promises'
20
4
  import { join } from 'node:path'
@@ -1,13 +1,4 @@
1
- /**
2
- * Suspect FS-перевірка: жоден Prettier-артефакт у корені проєкту не дозволений.
3
- *
4
- * `text.mdc` забороняє `prettier`, `@nitra/prettier-config` і всі прояви Prettier-конфігів.
5
- * Rego-полісі `text.package_json` ловить scripts/dependencies/devDependencies; цей concern
6
- * ловить FS-сторону — конфіги й ignore-файли, які runner Prettier зчитує автоматично.
7
- *
8
- * Список синхронізовано з конфіг-форматами Prettier 3.x
9
- * (https://prettier.io/docs/configuration). Якщо Prettier додасть новий формат — додай рядок.
10
- */
1
+ /** @see ./docs/forbidden-prettier.md */
11
2
  import { existsSync } from 'node:fs'
12
3
  import { join } from 'node:path'
13
4
 
@@ -1,34 +1,4 @@
1
- /**
2
- * Перевіряє текстовий стек і форматування за правилом text.mdc.
3
- *
4
- * **Що тут лишилося** (FS / VSCode-конфіги / markdown / лінт-скрипт):
5
- * - `.v8rignore` (текстовий формат, рядки шляхів);
6
- * - `.vscode/extensions.json` рекомендації (markdownlint, oxc, shellcheck) і
7
- * `.vscode/settings.json` (`editor.formatOnSave`, `[lang].editor.defaultFormatter`);
8
- * - наявність FS-файлів `.oxfmtrc.json`, `.cspell.json`, `.markdownlint-cli2.jsonc`,
9
- * `package.json` (саме *існування* — структуру вже валідує Rego);
10
- * - абзац про український апостроф у `.cursor/rules/n-text.mdc` /
11
- * `npm/mdc/text.mdc` (markdown-текст, не JSON/YAML);
12
- * - складна валідація скрипта `lint-text` (cspell, markdownlint, v8r у трьох
13
- * варіантах, run-shellrules/text/fix.mjs, обовʼязкові glob-и);
14
- * - workflow `lint-text.yml` має крок `bun run lint-text` (структура — rego `text.lint_text`).
15
- *
16
- * **Що покрила Rego** (`npx \@nitra/cursor check`):
17
- * - `npm/policy/text/oxfmtrc/` — обовʼязкові ключі `.oxfmtrc.json` і канонічні
18
- * значення (semi/singleQuote/tabWidth/useTabs/printWidth) + `ignorePatterns`
19
- * канонічні glob-и;
20
- * - `npm/policy/text/cspell/` — `.cspell.json` `version "0.2"`, `language`,
21
- * імпорт `@nitra/cspell-dict`, заборона `@cspell/dict-*`, обовʼязкові
22
- * `ignorePaths`;
23
- * - `npm/policy/text/markdownlint/` — `.markdownlint-cli2.jsonc` `gitignore: true`
24
- * (працює лише якщо файл — валідний JSON без коментарів);
25
- * - `npm/policy/text/package_json/` — заборона Prettier (`prettier` поле +
26
- * `prettier`/`@nitra/prettier-config` у залежностях), `@nitra/cspell-dict ^2.0.0+`
27
- * у `devDependencies`, заборона `markdownlint-cli2` у залежностях.
28
- * - `npm/policy/bun/package_json/` — у `devDependencies` лише `@nitra/*`
29
- * (раніше дублювалося тут).
30
- * @param {string} cwd корінь репозиторію
31
- */
1
+ /** @see ./docs/formatting.md */
32
2
  import { existsSync } from 'node:fs'
33
3
  import { readFile } from 'node:fs/promises'
34
4
  import { join } from 'node:path'
@@ -1,27 +1,4 @@
1
- /**
2
- * Знаходить пакети з `vue` у dependencies і перевіряє їх за правилом vue.mdc.
3
- *
4
- * Вміст `vite.config`, editor-файли для Vite client types, у репозиторії —
5
- * рекомендацію розширення Vue.volar.
6
- *
7
- * Залежності Vue/Vite (`vite >= 8`, `@vitejs/plugin-vue`, `vue-macros`,
8
- * `unplugin-auto-import`, `vite-plugin-vue-layouts-next`, заборона `esbuild`) —
9
- * у policy `vue.package_json`.
10
- *
11
- * У кожному Vue+Vite-пакеті очікується `src/vite-env.d.ts` з `/// <reference types="vite/client" />`
12
- * та `jsconfig.json` у корені пакета (типи для імпортів асетів у `.vue`).
13
- *
14
- * У `vite.config.*` заборонено використовувати `process.env.npm_lifecycle_event` (Bun не підставляє його як npm),
15
- * натомість використовуй `mode` з `defineConfig(({ mode }) => ...)`.
16
- *
17
- * Заборонені явні value-імпорти з `vue` у джерелах пакета — сканування `.vue`/`.ts`/`.js` тощо
18
- * через **oxc-parser** (`module.staticImports`; див. `./vue-forbidden-imports.mjs`); дозволені лише type-only та side-effect `import 'vue'`.
19
- *
20
- * Окремо в `.vue` SFC заборонено імпорти Node-нативних модулів — `node:*` префікс або bare-ім’я
21
- * вбудованого модуля Node (`fs`, `path`, `timers/promises` тощо). Vue SFC виконується у браузері,
22
- * де Node API недоступне; такий код треба тримати у server-side утілітах.
23
- * @param {string} cwd корінь репозиторію
24
- */
1
+ /** @see ./docs/packages.md */
25
2
  import { existsSync } from 'node:fs'
26
3
  import { readFile } from 'node:fs/promises'
27
4
  import { join, relative } from 'node:path'
@@ -1,26 +1,20 @@
1
1
  /**
2
2
  * Public API класифікатора: classify(survived, cwd, opts) → verdicts[]
3
3
  *
4
- * Orchestration:
5
- * 1. Перевірка ANTHROPIC_API_KEY + dynamic import SDK (graceful skip).
6
- * 2. Для кожного мутанта: cache lookup класифікаціяcache write.
7
- * 3. На неуспішну класифікацію після retries conservative fallback worth-testing/confidence=0.
8
- *
9
- * Prompt caching: system-prompt передається з cache_control: ephemeral —
10
- * усі мутанти одного прогону reuse кешований префікс на стороні API.
4
+ * Routing:
5
+ * 1. Cache lookup hit використати збережений verdict.
6
+ * 2. Cache miss Tier 1 (LOCAL_MIN через pi) parseVerdict.
7
+ * 3. Tier 1 fail (pi error / bad JSON / Zod) → Tier 2 (CLOUD_MIN через pi).
8
+ * 4. Tier 2 fail → conservative fallback worth-testing/confidence=0.
11
9
  */
10
+ import { spawnSync } from 'node:child_process'
12
11
  import { join } from 'node:path'
13
- import { env } from 'node:process'
14
- import { setTimeout } from 'node:timers/promises'
15
12
 
13
+ import { CLOUD_MIN, resolveModel } from '../../lib/models.mjs'
16
14
  import { deriveCacheKey, readCache, writeCache } from './cache.mjs'
17
15
  import { buildUserPrompt, SYSTEM_PROMPT } from './prompt.mjs'
18
16
  import { parseVerdict } from './verdict-schema.mjs'
19
17
 
20
- const MODEL = 'claude-sonnet-4-6'
21
- const MAX_RETRIES = 2
22
- const DEFAULT_RETRY_DELAY_MS = 1000
23
-
24
18
  const FALLBACK_VERDICT = {
25
19
  verdict: 'worth-testing',
26
20
  confidence: 0,
@@ -28,36 +22,67 @@ const FALLBACK_VERDICT = {
28
22
  }
29
23
 
30
24
  /**
31
- * Класифікує survived мутантів через Claude API.
32
- * Без API key / без SDK / при критичних помилках — повертає [] (graceful skip).
33
- * @param {Array<{file: string, mutants: Array<object>, exampleTest?: object|null, recommendationText?: string|null}>} survived список survived груп (як у COVERAGE.md)
34
- * @param {string} cwd корінь проєкту
35
- * @param {{cachePath?: string, client?: object, retryDelayMs?: number}} [opts] ін'єкції для тестів
36
- * @returns {Promise<Array<{key: string, verdict: object}>>} verdicts
25
+ * Викликає pi і повертає raw stdout.
26
+ * @param {string} prompt
27
+ * @param {string} model provider/model-id або '' для pi-дефолту
28
+ * @returns {string}
29
+ * @throws якщо pi не знайдено або повертає ненульовий exit code
37
30
  */
38
- export async function classify(survived, cwd, opts = {}) {
39
- const cachePath = opts.cachePath ?? join(cwd, 'npm/reports/coverage-classify.cache.json')
40
- const retryDelayMs = opts.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS
31
+ function callPi(prompt, model) {
32
+ const modelArgs = model ? ['--model', model] : []
33
+ const r = spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session', '--mode', 'text', '--no-tools'], {
34
+ encoding: 'utf8',
35
+ timeout: 60_000
36
+ })
37
+ if (r.error) throw new Error(`pi error: ${r.error.message}`)
38
+ if (r.status !== 0) throw new Error(`pi exit ${r.status}: ${r.stderr?.slice(0, 200) ?? ''}`)
39
+ return r.stdout?.trim() ?? ''
40
+ }
41
41
 
42
- if (!env.ANTHROPIC_API_KEY) {
43
- console.warn('⚠ coverage classify: ANTHROPIC_API_KEY not set, classification skipped')
44
- return []
45
- }
42
+ /**
43
+ * Два тири: LOCAL_MIN Tier 2 CLOUD_MIN → FALLBACK_VERDICT.
44
+ * @param {{file: string, mutants: object[]}} group
45
+ * @param {object} mutant
46
+ * @param {string} cwd
47
+ * @param {(prompt: string, model: string) => string} callPiFn ін'єкція для тестів
48
+ * @returns {object} verdict
49
+ */
50
+ function classifyOne(group, mutant, cwd, callPiFn) {
51
+ const prompt = `${SYSTEM_PROMPT}\n\n${buildUserPrompt({ ...mutant, file: group.file }, cwd)}`
52
+ const loc = `${group.file}:${mutant.line}:${mutant.col}`
46
53
 
47
- let SDK
54
+ // Tier 1: resolveModel('min') — каскад local→cloud якщо локалі нема
48
55
  try {
49
- SDK = await import('@anthropic-ai/sdk')
56
+ const text = callPiFn(prompt, resolveModel('min'))
57
+ return parseVerdict(text)
50
58
  } catch {
51
- console.warn('⚠ coverage classify: @anthropic-ai/sdk not installed, classification skipped')
52
- return []
59
+ // Tier 2: CLOUD_MIN
60
+ try {
61
+ const text = callPiFn(prompt, CLOUD_MIN)
62
+ return parseVerdict(text)
63
+ } catch (e) {
64
+ console.warn(`⚠ coverage classify: ${loc} both tiers failed: ${e.message}`)
65
+ return { ...FALLBACK_VERDICT }
66
+ }
53
67
  }
54
- const Anthropic = SDK.default
55
- const client = opts.client ?? new Anthropic()
68
+ }
69
+
70
+ /**
71
+ * Класифікує survived мутантів через pi (LOCAL_MIN → CLOUD_MIN → fallback).
72
+ * @param {Array<{file: string, mutants: object[], exampleTest?: object|null, recommendationText?: string|null}>} survived
73
+ * @param {string} cwd корінь проєкту
74
+ * @param {{cachePath?: string, callPi?: Function}} [opts] ін'єкції для тестів
75
+ * @returns {Promise<Array<{key: string, verdict: object}>>} verdicts
76
+ */
77
+ export async function classify(survived, cwd, opts = {}) {
78
+ const cachePath = opts.cachePath ?? join(cwd, 'npm/reports/coverage-classify.cache.json')
79
+ const callPiFn = opts.callPi ?? callPi
80
+ const cacheModel = `${resolveModel('min') || 'default'}+${CLOUD_MIN || 'cloud'}`
56
81
 
57
82
  const cache = readCache(cachePath)
58
- if (cache.model !== MODEL) {
83
+ if (cache.model !== cacheModel) {
59
84
  cache.entries = {}
60
- cache.model = MODEL
85
+ cache.model = cacheModel
61
86
  }
62
87
 
63
88
  const verdicts = []
@@ -77,7 +102,7 @@ export async function classify(survived, cwd, opts = {}) {
77
102
  }
78
103
  }
79
104
  if (!verdict) {
80
- verdict = await classifyOne(client, group, mutant, cwd, retryDelayMs)
105
+ verdict = classifyOne(group, mutant, cwd, callPiFn)
81
106
  if (cacheKey) {
82
107
  cache.entries[cacheKey] = { ...verdict, classifiedAt: new Date().toISOString() }
83
108
  }
@@ -90,40 +115,3 @@ export async function classify(survived, cwd, opts = {}) {
90
115
  writeCache(cachePath, cache)
91
116
  return verdicts
92
117
  }
93
-
94
- /**
95
- * Один виклик API з retry. На фейл після MAX_RETRIES — повертає FALLBACK_VERDICT.
96
- * @param {{messages: {create: Function}}} client SDK client
97
- * @param {{file: string}} group group для контексту
98
- * @param {object} mutant mutant data
99
- * @param {string} cwd корінь
100
- * @param {number} retryDelayMs base delay для exp-backoff (0 у тестах)
101
- * @returns {Promise<object>} verdict (parsed або fallback)
102
- */
103
- async function classifyOne(client, group, mutant, cwd, retryDelayMs) {
104
- const userPrompt = buildUserPrompt({ ...mutant, file: group.file }, cwd)
105
- let lastError = null
106
-
107
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
108
- try {
109
- const response = await client.messages.create({
110
- model: MODEL,
111
- max_tokens: 1024,
112
- system: [{ type: 'text', text: SYSTEM_PROMPT, cache_control: { type: 'ephemeral' } }],
113
- messages: [{ role: 'user', content: userPrompt }]
114
- })
115
- const text = response?.content?.[0]?.text ?? ''
116
- return parseVerdict(text)
117
- } catch (error) {
118
- lastError = error
119
- if (attempt < MAX_RETRIES && retryDelayMs > 0) {
120
- await setTimeout(retryDelayMs * 2 ** attempt)
121
- }
122
- }
123
- }
124
-
125
- console.warn(
126
- `⚠ coverage classify: ${group.file}:${mutant.line}:${mutant.col} failed after ${MAX_RETRIES + 1} attempts: ${lastError?.message ?? 'unknown'}`
127
- )
128
- return { ...FALLBACK_VERDICT }
129
- }
@@ -1,13 +1,18 @@
1
1
  /**
2
- * `n-cursor coverage --fix`: запускає Claude Code агента для написання тестів
2
+ * `n-cursor coverage --fix`: запускає pi-агента для написання тестів
3
3
  * по вцілілих мутантах Stryker. Агент отримує список мутантів з контекстом
4
4
  * (file, line, оригінальний код, вцілілий варіант, тип мутації) і самостійно
5
5
  * знаходить або створює відповідні test-файли.
6
6
  *
7
- * Залежить від `@anthropic-ai/claude-agent-sdk` (dependencies у npm/package.json).
7
+ * Модель: CLOUD_MAX (складна агентна задача) або N_CURSOR_COVERAGE_FIX_MODEL.
8
8
  */
9
9
  import { readFile } from 'node:fs/promises'
10
10
  import { join } from 'node:path'
11
+ import { spawnSync } from 'node:child_process'
12
+
13
+ import { resolveModel } from '../lib/models.mjs'
14
+
15
+ const MODEL = process.env.N_CURSOR_COVERAGE_FIX_MODEL ?? resolveModel('max')
11
16
 
12
17
  /**
13
18
  * @typedef {{line:number, col:number, mutantType:string, original:string, replacement:string}} MutantDetail
@@ -15,12 +20,13 @@ import { join } from 'node:path'
15
20
  */
16
21
 
17
22
  /**
18
- * Запускає Claude Code агента для написання тестів по вцілілих мутантах.
23
+ * Запускає pi-агента для написання тестів по вцілілих мутантах.
19
24
  * @param {SurvivedFileGroup[]} survived вцілілі мутанти, згруповані по файлах
20
25
  * @param {string} projectRoot абсолютний шлях до кореня проєкту
26
+ * @param {{ callPi?: (prompt: string, model: string, opts: { cwd: string }) => void }} [opts] ін'єкції для тестів
21
27
  * @returns {Promise<void>}
22
28
  */
23
- export async function fixSurvivedMutants(survived, projectRoot) {
29
+ export async function fixSurvivedMutants(survived, projectRoot, opts = {}) {
24
30
  const totalMutants = survived.reduce((s, g) => s + g.mutants.length, 0)
25
31
  if (totalMutants === 0) {
26
32
  console.log('✓ Всі мутанти вбиті — доповнення тестів не потрібне')
@@ -30,26 +36,23 @@ export async function fixSurvivedMutants(survived, projectRoot) {
30
36
  const prompt = await buildFixPrompt(survived, projectRoot)
31
37
  console.log(`\n🤖 coverage --fix: запускаю агента для ${totalMutants} вцілілих мутантів...\n`)
32
38
 
33
- // Dynamic import: @anthropic-ai/claude-agent-sdk завантажується лише при --fix,
34
- // щоб не гальмувати звичайний coverage-прогін за відсутності пакету.
35
- const { query } = await import('@anthropic-ai/claude-agent-sdk')
39
+ const callPiFn = opts.callPi ?? callPi
40
+ callPiFn(prompt, MODEL, { cwd: projectRoot })
41
+ }
36
42
 
37
- for await (const msg of query({
38
- prompt,
39
- options: {
40
- cwd: projectRoot,
41
- maxTurns: 20,
42
- allowedTools: ['Read', 'Edit', 'Bash'],
43
- // Без permissionMode headless-агент SDK не отримує дозволу на запис/Bash —
44
- // він крутиться, палить токени, але НЕ редагує файли (підтверджено пробом).
45
- // `bypassPermissions` потрібен, бо агент і пише тести (Edit), і ганяє `bun test` (Bash);
46
- // `acceptEdits` авто-підтверджує лише edits, а Bash лишився б заблокованим.
47
- permissionMode: 'bypassPermissions'
48
- }
49
- })) {
50
- if (msg.type === 'text') process.stdout.write(msg.text)
51
- }
52
- process.stdout.write('\n')
43
+ /**
44
+ * Викликає pi в агентному режимі з live-output до stdout.
45
+ * @param {string} prompt
46
+ * @param {string} model provider/model-id або '' для pi-дефолту
47
+ * @param {{ cwd?: string }} [piOpts]
48
+ */
49
+ function callPi(prompt, model, { cwd } = {}) {
50
+ const modelArgs = model ? ['--model', model] : []
51
+ spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session'], {
52
+ cwd,
53
+ stdio: 'inherit',
54
+ timeout: 900_000
55
+ })
53
56
  }
54
57
 
55
58
  /**