@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.
- package/CHANGELOG.md +17 -0
- package/package.json +1 -3
- package/rules/abie/js/applies.mjs +1 -5
- package/rules/abie/js/env_dns.mjs +1 -9
- package/rules/abie/js/firebase_hosting.mjs +1 -5
- package/rules/abie/js/hc_pairing.mjs +1 -8
- package/rules/abie/js/ua_http_route.mjs +1 -10
- package/rules/abie/js/ua_node_selector.mjs +1 -8
- package/rules/adr/js/hooks.mjs +1 -20
- package/rules/bun/js/layout.mjs +1 -19
- package/rules/capacitor/js/platforms.mjs +1 -23
- package/rules/changelog/js/consistency.mjs +1 -29
- package/rules/ci4/js/marksman_config.mjs +1 -19
- package/rules/docker/js/lint.mjs +1 -34
- package/rules/ga/docs/fix.md +4 -4
- package/rules/ga/js/docs/lint.md +3 -3
- package/rules/ga/js/docs/workflows.md +14 -14
- package/rules/ga/js/workflows.mjs +1 -16
- package/rules/ga/lint/docs/lint.md +9 -9
- package/rules/graphql/js/tooling.mjs +1 -9
- package/rules/hasura/js/internal_urls.mjs +1 -24
- package/rules/image-avif/js/avif_generation.mjs +1 -27
- package/rules/image-compress/js/package_setup.mjs +1 -18
- package/rules/js-bun-db/js/safety.mjs +1 -31
- package/rules/js-bun-redis/js/imports.mjs +1 -12
- package/rules/js-lint/js/docs/lint-findings.md +30 -0
- package/rules/js-lint/js/lint-findings.mjs +1 -7
- package/rules/js-lint/js/lint.mjs +1 -10
- package/rules/js-lint/js/tooling.mjs +1 -13
- package/rules/js-lint/js/utils_imports.mjs +1 -18
- package/rules/js-lint-ci/js/lint.mjs +1 -6
- package/rules/js-mssql/js/deps.mjs +1 -10
- package/rules/js-run/js/runtime.mjs +1 -37
- package/rules/js-run/lib/docs/temporal-scan.md +25 -0
- package/rules/k8s/js/manifests.mjs +1 -137
- package/rules/nginx-default-tpl/js/template.mjs +1 -18
- package/rules/npm-module/js/docs/header_doc_pointer.md +25 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +82 -0
- package/rules/npm-module/js/package_structure.mjs +1 -28
- package/rules/npm-module/js/rule_meta.mjs +1 -10
- package/rules/npm-module/js/skill_meta.mjs +1 -13
- package/rules/php/js/tooling.mjs +1 -11
- package/rules/python/js/applies.mjs +1 -8
- package/rules/python/js/tooling.mjs +1 -21
- package/rules/rego/js/applies.mjs +1 -11
- package/rules/rust/js/applies.mjs +1 -7
- package/rules/security/js/sample_secret.mjs +1 -28
- package/rules/security/js/trufflehog.mjs +1 -8
- package/rules/style-lint/js/lint.mjs +1 -5
- package/rules/style-lint/js/tooling.mjs +1 -19
- package/rules/tauri/js/cargo_mutants_config.mjs +1 -20
- package/rules/tauri/js/tooling.mjs +1 -21
- package/rules/test/js/cargo_mutants_config.mjs +1 -12
- package/rules/test/js/location.mjs +1 -9
- package/rules/test/js/no-process-chdir.mjs +1 -21
- package/rules/test/js/no-relative-fs-path.mjs +1 -23
- package/rules/test/js/stryker_config.mjs +4 -25
- package/rules/test/js/vitest-config-pool-forks.mjs +1 -17
- package/rules/text/js/forbidden-prettier.mjs +1 -10
- package/rules/text/js/formatting.mjs +1 -31
- package/rules/vue/js/packages.mjs +1 -24
- package/scripts/coverage-classify/index.mjs +60 -72
- package/scripts/coverage-fix.mjs +26 -23
- package/scripts/dispatcher/lib/subagent-runner.mjs +33 -102
- package/scripts/docs/coverage-fix-extract.md +32 -0
- package/scripts/docs/lint-cli.md +25 -0
- package/scripts/docs/post-tool-use-fix.md +27 -0
- package/scripts/docs/rename-yaml-extensions.md +36 -0
- package/scripts/docs/skills-cli.md +35 -0
- package/scripts/docs/sync-claude-config.md +52 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +26 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +29 -0
- package/scripts/docs/worktree-cli.md +46 -0
- package/scripts/lib/docs/assert-project-root.md +28 -0
- package/scripts/lib/docs/diff-added-lines.md +34 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +28 -0
- package/scripts/lib/docs/resolve-target-files.md +34 -0
- package/scripts/lib/docs/root-notice.md +28 -0
- package/scripts/lib/docs/rule-meta-helpers.md +34 -0
- package/scripts/lib/docs/rule-meta.md +34 -0
- package/scripts/lib/docs/rule-predicates.md +30 -0
- package/scripts/lib/docs/run-conftest-batch.md +26 -0
- package/scripts/lib/docs/run-lint-step.md +25 -0
- package/scripts/lib/docs/run-rule-cli.md +27 -0
- package/scripts/lib/docs/run-rule.md +32 -0
- package/scripts/lib/docs/run-standard-lint.md +22 -0
- package/scripts/lib/docs/run-standard-rule.md +24 -0
- package/scripts/lib/docs/skill-meta.md +31 -0
- package/scripts/lib/docs/sync-gitignore-worktree.md +31 -0
- package/scripts/lib/docs/template.md +40 -0
- package/scripts/lib/docs/timing-summary.md +24 -0
- package/scripts/lib/docs/workspaces.md +30 -0
- package/scripts/lib/docs/worktree-notice.md +27 -0
- package/scripts/lib/docs/worktree.md +38 -0
- package/scripts/utils/docs/ast-scan-utils.md +50 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +28 -0
- package/scripts/utils/docs/find-package-json-paths.md +26 -0
- package/scripts/utils/docs/lock-cache-dir.md +25 -0
- package/scripts/utils/docs/pass.md +25 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +23 -0
- package/scripts/utils/docs/resolve-cmd.md +29 -0
- package/scripts/utils/docs/resolve-js-root.md +25 -0
- package/scripts/utils/docs/test-helpers.md +36 -0
- package/scripts/utils/docs/walk-cache.md +27 -0
- package/scripts/utils/docs/walkDir.md +32 -0
- package/scripts/utils/docs/with-lock.md +25 -0
- package/scripts/utils/docs/worktree-fingerprint.md +27 -0
- package/skills/docgen/js/docgen-batch.mjs +95 -0
- package/skills/docgen/js/docgen-extract.mjs +33 -18
- package/skills/docgen/js/docgen-gen.mjs +140 -154
- package/skills/docgen/js/docgen-ignore.mjs +1 -6
- package/skills/docgen/js/docgen-prompts.mjs +33 -22
- package/skills/docgen/js/docgen-scan.mjs +1 -8
- package/skills/docgen/js/docs/docgen-extract.md +28 -0
- package/skills/docgen/js/docs/docgen-gen.md +41 -0
- package/skills/docgen/js/docs/docgen-ignore.md +24 -0
- package/skills/docgen/js/docs/docgen-prompts.md +24 -0
- package/skills/docgen/js/docs/docgen-scan.md +48 -0
- package/skills/fix/js/docs/llm-worker.md +27 -0
- package/skills/fix/js/docs/orchestrator.md +32 -0
- package/skills/fix/js/docs/t0.md +29 -0
- package/skills/fix/js/llm-worker.mjs +64 -29
- package/skills/fix/js/orchestrator.mjs +45 -54
- package/skills/fix/js/t0.mjs +16 -32
- package/skills/start-check/js/check.mjs +1 -16
- package/skills/start-check/js/docs/check.md +34 -0
- package/skills/taze/js/diff.mjs +1 -15
- 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(
|
|
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
|
-
*
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
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
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @param {
|
|
34
|
-
* @
|
|
35
|
-
* @
|
|
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
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
54
|
+
// Tier 1: resolveModel('min') — каскад local→cloud якщо локалі нема
|
|
48
55
|
try {
|
|
49
|
-
|
|
56
|
+
const text = callPiFn(prompt, resolveModel('min'))
|
|
57
|
+
return parseVerdict(text)
|
|
50
58
|
} catch {
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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 !==
|
|
83
|
+
if (cache.model !== cacheModel) {
|
|
59
84
|
cache.entries = {}
|
|
60
|
-
cache.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 =
|
|
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
|
-
}
|
package/scripts/coverage-fix.mjs
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `n-cursor coverage --fix`: запускає
|
|
2
|
+
* `n-cursor coverage --fix`: запускає pi-агента для написання тестів
|
|
3
3
|
* по вцілілих мутантах Stryker. Агент отримує список мутантів з контекстом
|
|
4
4
|
* (file, line, оригінальний код, вцілілий варіант, тип мутації) і самостійно
|
|
5
5
|
* знаходить або створює відповідні test-файли.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
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
|
-
* Запускає
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
const callPiFn = opts.callPi ?? callPi
|
|
40
|
+
callPiFn(prompt, MODEL, { cwd: projectRoot })
|
|
41
|
+
}
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
/**
|