@nitra/cursor 1.27.3 → 1.27.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/hooks/capture-decisions.sh +1 -1
- package/.claude-template/hooks/normalize-decisions.sh +1 -1
- package/.pi-template/extensions/n-cursor-adr/tsconfig.json +1 -0
- package/CHANGELOG.md +61 -32
- package/bin/n-cursor.js +2 -2
- package/package.json +1 -1
- package/rules/bun/bun.mdc +1 -1
- package/rules/bun/policy/package_json/package_json.rego +14 -4
- package/rules/changelog/js/consistency.mjs +1 -1
- package/rules/image-avif/js/avif_generation.mjs +2 -2
- package/rules/js-lint/js-lint.mdc +1 -1
- package/rules/js-mssql/js/deps.mjs +1 -1
- package/rules/k8s/js/manifests.mjs +19 -19
- package/rules/k8s/k8s.mdc +9 -9
- package/rules/k8s/policy/network_policy/network_policy.rego +5 -5
- package/rules/tauri/js/cargo_mutants_config.mjs +3 -3
- package/rules/tauri/tauri.mdc +2 -2
- package/rules/test/coverage/coverage.mjs +4 -4
- package/rules/test/js/cargo_mutants_config.mjs +2 -2
- package/rules/test/js/data/cargo_mutants_config/mutants.toml.baseline +1 -1
- package/rules/test/js/data/stryker_config/stryker.config.baseline.mjs +1 -1
- package/rules/test/js/stryker_config.mjs +3 -3
- package/rules/test/test.mdc +5 -5
- package/rules/text/js/forbidden-prettier.mjs +59 -0
- package/rules/text/js/formatting.mjs +1 -4
- package/rules/text/policy/package_json/package_json.rego +16 -0
- package/rules/text/text.mdc +1 -1
- package/rules/vue/vue.mdc +1 -1
- package/schemas/v8r-catalog.json +6 -0
- package/scripts/coverage-fix.mjs +12 -12
- package/scripts/lib/run-lint-cli.mjs +5 -5
- package/scripts/lib/run-lint-step.mjs +1 -1
- package/scripts/lib/run-rule.mjs +1 -1
- package/scripts/lib/timing-summary.mjs +3 -3
- package/scripts/post-tool-use-fix.mjs +4 -4
- package/scripts/sync-claude-config.mjs +2 -2
- package/scripts/utils/ensure-gitignore-entries.mjs +2 -2
- package/scripts/utils/resolve-cargo-manifest.mjs +1 -1
- package/scripts/utils/resolve-js-root.mjs +1 -1
- package/scripts/utils/walkDir.mjs +2 -2
- package/skills/coverage-fix/SKILL.md +15 -12
- package/skills/fix-tests/SKILL.md +13 -13
- /package/rules/k8s/policy/network_policy/template/{statefulset.snippet.yaml → stateful-set.snippet.yaml} +0 -0
package/schemas/v8r-catalog.json
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
"description": "Каталог схем Schema Store (v8r-catalog.json у пакеті)",
|
|
20
20
|
"url": "https://json.schemastore.org/schema-catalog.json",
|
|
21
21
|
"fileMatch": ["npm/schemas/v8r-catalog.json"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "tsconfig",
|
|
25
|
+
"description": "TypeScript config для pi extension templates, які Schema Store не матчить через вкладений/прихований шлях",
|
|
26
|
+
"url": "https://json.schemastore.org/tsconfig.json",
|
|
27
|
+
"fileMatch": [".pi/extensions/*/tsconfig.json", "npm/.pi-template/extensions/*/tsconfig.json"]
|
|
22
28
|
}
|
|
23
29
|
]
|
|
24
30
|
}
|
package/scripts/coverage-fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `n-cursor coverage --fix`: запускає Claude Code агента для написання тестів
|
|
3
|
-
* по
|
|
4
|
-
* (file, line, оригінальний код,
|
|
3
|
+
* по вцілілих мутантах Stryker. Агент отримує список мутантів з контекстом
|
|
4
|
+
* (file, line, оригінальний код, вцілілий варіант, тип мутації) і самостійно
|
|
5
5
|
* знаходить або створює відповідні test-файли.
|
|
6
6
|
*
|
|
7
7
|
* Залежить від `@anthropic-ai/claude-agent-sdk` (dependencies у npm/package.json).
|
|
@@ -15,8 +15,8 @@ import { join } from 'node:path'
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Запускає Claude Code агента для написання тестів по
|
|
19
|
-
* @param {SurvivedFileGroup[]} survived
|
|
18
|
+
* Запускає Claude Code агента для написання тестів по вцілілих мутантах.
|
|
19
|
+
* @param {SurvivedFileGroup[]} survived вцілілі мутанти, згруповані по файлах
|
|
20
20
|
* @param {string} projectRoot абсолютний шлях до кореня проєкту
|
|
21
21
|
* @returns {Promise<void>}
|
|
22
22
|
*/
|
|
@@ -28,7 +28,7 @@ export async function fixSurvivedMutants(survived, projectRoot) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const prompt = await buildFixPrompt(survived, projectRoot)
|
|
31
|
-
console.log(`\n🤖 coverage --fix: запускаю агента для ${totalMutants}
|
|
31
|
+
console.log(`\n🤖 coverage --fix: запускаю агента для ${totalMutants} вцілілих мутантів...\n`)
|
|
32
32
|
|
|
33
33
|
// Dynamic import: @anthropic-ai/claude-agent-sdk завантажується лише при --fix,
|
|
34
34
|
// щоб не гальмувати звичайний coverage-прогін за відсутності пакету.
|
|
@@ -48,9 +48,9 @@ export async function fixSurvivedMutants(survived, projectRoot) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Формує rich-промпт для агента: список
|
|
51
|
+
* Формує rich-промпт для агента: список вцілілих мутантів згрупований по файлах,
|
|
52
52
|
* з контекстом ±3 рядки навколо кожного мутанта з source-файлу.
|
|
53
|
-
* @param {SurvivedFileGroup[]} survived групи
|
|
53
|
+
* @param {SurvivedFileGroup[]} survived групи вцілілих мутантів по файлах
|
|
54
54
|
* @param {string} projectRoot корінь проєкту
|
|
55
55
|
* @returns {Promise<string>} текст rich-промпту
|
|
56
56
|
*/
|
|
@@ -66,7 +66,7 @@ async function buildFixPrompt(survived, projectRoot) {
|
|
|
66
66
|
// файл може бути недоступним — пропускаємо контекст, але продовжуємо
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const
|
|
69
|
+
const mutantDescriptions = mutants
|
|
70
70
|
.map(m => {
|
|
71
71
|
const ctxStart = Math.max(0, m.line - 4)
|
|
72
72
|
const ctxEnd = Math.min(srcLines.length, m.line + 3)
|
|
@@ -89,15 +89,15 @@ async function buildFixPrompt(survived, projectRoot) {
|
|
|
89
89
|
? `\n\nПриклад тесту з \`${exampleTest.testFile}\`:\n\`\`\`js\n${exampleTest.code}\n\`\`\``
|
|
90
90
|
: ''
|
|
91
91
|
|
|
92
|
-
sections.push(`### \`${file}\`${exampleSection}\n${
|
|
92
|
+
sections.push(`### \`${file}\`${exampleSection}\n${mutantDescriptions}`)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
return [
|
|
96
|
-
'Твоє завдання — написати unit-тести, що вбивають наступні
|
|
96
|
+
'Твоє завдання — написати unit-тести, що вбивають наступні вцілілі мутанти Stryker.',
|
|
97
97
|
'Для кожного мутанта: знайди або створи відповідний test-файл, додай тест-кейс,',
|
|
98
|
-
'що явно перевіряє цю гілку/умову і провалиться якщо код замінити на "
|
|
98
|
+
'що явно перевіряє цю гілку/умову і провалиться якщо код замінити на "вцілілий варіант".',
|
|
99
99
|
'',
|
|
100
|
-
'##
|
|
100
|
+
'## Вцілілі мутанти',
|
|
101
101
|
'',
|
|
102
102
|
...sections,
|
|
103
103
|
'',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `n-cursor lint` — оркестратор лінт-ланцюжка з
|
|
2
|
+
* `n-cursor lint` — оркестратор лінт-ланцюжка з вимірюванням часу на кожен крок.
|
|
3
3
|
*
|
|
4
4
|
* Замість агрегатора `bun run lint-ga && bun run lint-js && ... && oxfmt .` у кореневому
|
|
5
|
-
* `package.json` (де child-процеси анонімні і час кожного не видно), цей
|
|
5
|
+
* `package.json` (де child-процеси анонімні і час кожного не видно), цей оркестратор:
|
|
6
6
|
*
|
|
7
7
|
* - читає `scripts` з кореневого `package.json`,
|
|
8
8
|
* - бере **присутні** ключі з фіксованого списку `LINT_SCRIPTS` (відсутні мовчки пропускає),
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Список + порядок зумисне фіксований: збігається з канонічним ланцюжком, що його раніше
|
|
16
16
|
* тримав root `package.json`. Динамічний discovery (`scripts/^lint-/`) дав би непередбачуваний
|
|
17
|
-
* порядок і небажану інтерпретацію
|
|
17
|
+
* порядок і небажану інтерпретацію власних `lint-*` користувача.
|
|
18
18
|
*
|
|
19
19
|
* `oxfmt` — окремий рядок поза префіксом `lint-`, ставиться в кінець (як було у `lint`).
|
|
20
20
|
*/
|
|
@@ -72,9 +72,9 @@ function readRootScripts(root) {
|
|
|
72
72
|
*/
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Виконує лінт-ланцюжок з
|
|
75
|
+
* Виконує лінт-ланцюжок з вимірюванням часу. Повертає exit-код, не кидає винятків (для прямого
|
|
76
76
|
* присвоєння у `process.exitCode`).
|
|
77
|
-
* @param {RunLintCliOptions} [options] DI для тестів (
|
|
77
|
+
* @param {RunLintCliOptions} [options] DI для тестів (підміняємо spawn / fs / clock)
|
|
78
78
|
* @returns {number} 0 = успіх, ненульовий = code першого впалого скрипта, або 1 при структурних проблемах
|
|
79
79
|
*/
|
|
80
80
|
export function runLintCli(options = {}) {
|
|
@@ -11,7 +11,7 @@ import { spawnSync } from 'node:child_process'
|
|
|
11
11
|
import { resolveCmd } from '../utils/resolve-cmd.mjs'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Запускає один крок lint-обгортки:
|
|
14
|
+
* Запускає один крок lint-обгортки: визначає `cmd` у PATH і `spawnSync` із успадкованим stdio.
|
|
15
15
|
* @param {string} title заголовок для логу (наприклад `actionlint`)
|
|
16
16
|
* @param {string} cmd ім'я команди (`bunx`, `uvx`, `npx`, …)
|
|
17
17
|
* @param {string[]} args аргументи команди
|
package/scripts/lib/run-rule.mjs
CHANGED
|
@@ -54,7 +54,7 @@ async function evaluateAppliesGate(bundledRulesDir, rule) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Запускає одну policy-полісі через `runConftestBatch`. Створює локальний репортер,
|
|
57
|
-
* читає `target.json`,
|
|
57
|
+
* читає `target.json`, визначає файли, фіксує fail/pass — і повертає exit-код.
|
|
58
58
|
* @param {string} bundledRulesDir абсолютний `rules/`
|
|
59
59
|
* @param {string} ruleId id правила
|
|
60
60
|
* @param {string} concernName імʼя полісі (= підкаталог у `policy/`)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Формат таблиці-резюме часу виконання для
|
|
2
|
+
* Формат таблиці-резюме часу виконання для orchestrator `fix` / `lint`.
|
|
3
3
|
*
|
|
4
4
|
* Дві спільні точки використання:
|
|
5
5
|
* - `runFixCommand` у `bin/n-cursor.js` — після прогону всіх `rules/<id>/fix.mjs`.
|
|
6
6
|
* - `runLintCli` у `scripts/lib/run-lint-cli.mjs` — після прогону `lint-*` скриптів з кореневого `package.json`.
|
|
7
7
|
*
|
|
8
8
|
* Чиста функція без I/O — повертає готовий рядок (з фінальним `\n`), друк — на стороні виклику.
|
|
9
|
-
* Час виводиться як `<ціла>.<десята>s`, навіть для
|
|
9
|
+
* Час виводиться як `<ціла>.<десята>s`, навіть для коротших за секунду інтервалів — щоб одиниця була стабільна.
|
|
10
10
|
*
|
|
11
11
|
* Маркер `❌` на рядку — якщо `ok === false`.
|
|
12
12
|
* @typedef {{ id: string, ms: number, ok: boolean }} TimingEntry
|
|
@@ -40,7 +40,7 @@ export function formatDurationMs(ms) {
|
|
|
40
40
|
* total <sum>
|
|
41
41
|
* ```
|
|
42
42
|
*
|
|
43
|
-
* Ширина колонки id вирівнюється під найдовший id у списку. Мінімальна ширина
|
|
43
|
+
* Ширина колонки id вирівнюється під найдовший id у списку. Мінімальна ширина risk — 14
|
|
44
44
|
* (узгоджено з типовою довжиною заголовків `fix-js-lint` / `lint-security`).
|
|
45
45
|
* @param {string} title заголовок таблиці (наприклад, `Fix timing` або `Lint timing`)
|
|
46
46
|
* @param {TimingEntry[]} timings записи в порядку запуску — друкуються як є, не сортуються
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Контракт:
|
|
8
8
|
* - stdin Claude Code: JSON із `tool_input.file_path` (відносний шлях зміненого файла);
|
|
9
|
-
* - exit 0, якщо файл не
|
|
9
|
+
* - exit 0, якщо файл не має маршрут (PostToolUse не блокує turn у будь-якому випадку,
|
|
10
10
|
* але ми лишаємо exit-код прозорим — для діагностики);
|
|
11
|
-
* - інакше spawn `npx --no @nitra/cursor fix <rules…>` із
|
|
11
|
+
* - інакше spawn `npx --no @nitra/cursor fix <rules…>` із передаванням exit-коду.
|
|
12
12
|
*
|
|
13
13
|
* Маршрути впорядковані від найбільш специфічного до загального; перший збіг — переможець.
|
|
14
14
|
* `docs/adr/**\/*.md` свідомо повертає `[]`: ADR-нормалізація вже покривається async
|
|
@@ -83,7 +83,7 @@ async function readStdin() {
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Дістає `tool_input.file_path` зі stdin JSON Claude Code. Невалідний JSON
|
|
86
|
-
* або відсутнє поле → `null` (не помилка: дехто з
|
|
86
|
+
* або відсутнє поле → `null` (не помилка: дехто з інструментів — напр. Bash — не пише `file_path`).
|
|
87
87
|
* @param {string} stdinJson сирий вміст stdin
|
|
88
88
|
* @returns {string | null} відносний шлях або `null`
|
|
89
89
|
*/
|
|
@@ -102,7 +102,7 @@ function extractFilePath(stdinJson) {
|
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Точка входу. Викликається з `bin/n-cursor.js` коли argv[0] === `post-tool-use-fix`.
|
|
105
|
-
* Параметри
|
|
105
|
+
* Параметри доступні для інʼєкції для тестів: `stdinJson` обходить read від `process.stdin`,
|
|
106
106
|
* `spawnFn` — заміна `node:child_process.spawn` (повертає EventEmitter-сумісний об'єкт).
|
|
107
107
|
* @param {{ stdinJson?: string, spawnFn?: typeof spawn }} [options] параметри для тестів (ін'єкція stdin/spawn)
|
|
108
108
|
* @returns {Promise<number>} exit code (0 — пропущено / fix ОК; інше — exit-код `fix`)
|
|
@@ -467,7 +467,7 @@ export async function removeOrphanAdrHookLib(projectRoot) {
|
|
|
467
467
|
* перезаписується. Якщо bundled template відсутній (legacy-версії пакета без `.pi-template/`)
|
|
468
468
|
* або в ньому немає `index.ts` — повертаємо `{written: false}` без помилки.
|
|
469
469
|
*
|
|
470
|
-
* Розширення поверх `index.ts` (tsconfig тощо) потрібні, бо `.pi/extensions/`
|
|
470
|
+
* Розширення поверх `index.ts` (tsconfig тощо) потрібні, бо `.pi/extensions/` синхронізується як є
|
|
471
471
|
* у проєкти-споживачі, а IDE/TS-сервер мусить резолвити `node:*` модулі без додаткових
|
|
472
472
|
* project-wide конфігів.
|
|
473
473
|
* @param {string} projectRoot корінь проєкту-споживача
|
|
@@ -637,7 +637,7 @@ export async function syncClaudeConfig({ projectRoot, bundledPackageRoot, enable
|
|
|
637
637
|
? await syncAdrNormalizeHookScript(projectRoot, templateDir)
|
|
638
638
|
: { written: false, path: '' }
|
|
639
639
|
// Lib-файли мають сенс лише з активним хоча б одним ADR-хуком — без caller'а
|
|
640
|
-
// нікому source-ити; при вимкненому правилі прибираємо
|
|
640
|
+
// нікому source-ити; при вимкненому правилі прибираємо осиротілу-теку.
|
|
641
641
|
const adrHookLibEntries = includeAdrHook
|
|
642
642
|
? await syncAdrHookLibScripts(projectRoot, templateDir)
|
|
643
643
|
: (await removeOrphanAdrHookLib(projectRoot), [])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Idempotent append-only
|
|
2
|
+
* Idempotent append-only модуль оновлення `.gitignore` у корені проєкту. Перевіряє,
|
|
3
3
|
* чи задані entries уже присутні (точне співпадіння рядка після `trim`); відсутні
|
|
4
|
-
* дописує під header
|
|
4
|
+
* дописує під header-коментар, не порушуючи решту файлу. Якщо `.gitignore` немає —
|
|
5
5
|
* створюється з заданими entries + header.
|
|
6
6
|
*
|
|
7
7
|
* Викликається з test-концерну `stryker_config` (gitignore Stryker temp dirs).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Резолвить шлях до Cargo.toml у проєкті: cwd/Cargo.toml або в одному з
|
|
3
3
|
* workspace-підкаталогів (з підтримкою Tauri-патерну `<workspace>/src-tauri/`).
|
|
4
4
|
* Спільна утиліта для coverage-провайдера rust і test-концерну cargo_mutants_config.
|
|
5
|
-
* Повертає null (а не throw) щоб callsite-и могли gracefully skip
|
|
5
|
+
* Повертає null (а не throw) щоб callsite-и могли gracefully skip-пропустити.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from 'node:fs'
|
|
8
8
|
import { readFile } from 'node:fs/promises'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Резолвить корінь JS-коду в проєкті: для workspace-projects — перший workspace
|
|
3
|
-
* (наприклад `app/` у
|
|
3
|
+
* (наприклад `app/` у mail app), для single-package — корінь cwd. Спільна утиліта
|
|
4
4
|
* для coverage-провайдера js-lint і test-концерну stryker_config (DRY).
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from 'node:fs'
|
|
@@ -42,7 +42,7 @@ function isIgnoredDir(dirAbsPosix, ignorePosix) {
|
|
|
42
42
|
* @param {string} dir абсолютний шлях
|
|
43
43
|
* @param {(filePath: string) => void} onFile виклик для кожного файлу
|
|
44
44
|
* @param {string[]} [ignorePaths] шляхи каталогів (відносні від cwd або абсолютні), що повністю виключаються з обходу
|
|
45
|
-
* @returns {Promise<void>}
|
|
45
|
+
* @returns {Promise<void>} визначається по завершенню обходу
|
|
46
46
|
*/
|
|
47
47
|
export async function walkDir(dir, onFile, ignorePaths = []) {
|
|
48
48
|
const ignorePosix = ignorePaths.map(p => toAbsPosix(p))
|
|
@@ -54,7 +54,7 @@ export async function walkDir(dir, onFile, ignorePaths = []) {
|
|
|
54
54
|
* @param {string} dir абсолютний шлях каталогу для обходу
|
|
55
55
|
* @param {(filePath: string) => void} onFile колбек, що викликається для кожного звичайного файлу
|
|
56
56
|
* @param {string[]} ignorePosix вже нормалізовані абсолютні posix-шляхи ігнорованих каталогів
|
|
57
|
-
* @returns {Promise<void>}
|
|
57
|
+
* @returns {Promise<void>} визначається по завершенню рекурсії
|
|
58
58
|
*/
|
|
59
59
|
async function walkDirInner(dir, onFile, ignorePosix) {
|
|
60
60
|
if (ignorePosix.length > 0 && isIgnoredDir(toAbsPosix(dir), ignorePosix)) return
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: n-coverage-fix
|
|
3
3
|
description: >-
|
|
4
|
-
Автономна команда: запускає n-cursor coverage → читає
|
|
4
|
+
Автономна команда: запускає n-cursor coverage → читає вцілілих мутантів → ітеративно пише тести до конвергенції (max 3 ітерації)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# n-coverage-fix — підвищення mutation score
|
|
@@ -36,21 +36,21 @@ n-cursor coverage
|
|
|
36
36
|
bun run coverage
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Ця команда генерує `COVERAGE.md`. Якщо є survived mutants — COVERAGE.md матиме секцію `##
|
|
39
|
+
Ця команда генерує `COVERAGE.md`. Якщо є survived mutants — COVERAGE.md матиме секцію `## Вцілілі мутанти` з JSON-блоком.
|
|
40
40
|
|
|
41
|
-
### Крок 2: Перевір
|
|
41
|
+
### Крок 2: Перевір вцілілих
|
|
42
42
|
|
|
43
|
-
Прочитай `COVERAGE.md`. Знайди секцію `##
|
|
43
|
+
Прочитай `COVERAGE.md`. Знайди секцію `## Вцілілі мутанти`. Знайди огороджений блок ` ```json ` і розбери JSON-масив.
|
|
44
44
|
|
|
45
45
|
Якщо секція відсутня або масив порожній — зупинись:
|
|
46
46
|
|
|
47
47
|
```
|
|
48
|
-
✓ Жодних
|
|
48
|
+
✓ Жодних вцілілих мутантів — mutation score повний. Coverage завершено.
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Запам'ятай `prevCount = масив.length`.
|
|
52
52
|
|
|
53
|
-
### Крок 3: Для кожного файлу —
|
|
53
|
+
### Крок 3: Для кожного файлу — запускає Agent
|
|
54
54
|
|
|
55
55
|
Згрупуй мутанти по полю `file`. Для кожної групи:
|
|
56
56
|
|
|
@@ -68,8 +68,8 @@ bun run coverage
|
|
|
68
68
|
**3b. Сформуй промпт для Agent:**
|
|
69
69
|
|
|
70
70
|
```
|
|
71
|
-
Тобі дані
|
|
72
|
-
Ці мутанти
|
|
71
|
+
Тобі дані вцілілі мутанти зі Stryker для файлу `<file>`.
|
|
72
|
+
Ці мутанти вціліли, бо наявні тести НЕ вловили конкретні зміни коду.
|
|
73
73
|
|
|
74
74
|
**Вихідний код** (`<file>`):
|
|
75
75
|
\`\`\`
|
|
@@ -81,7 +81,7 @@ bun run coverage
|
|
|
81
81
|
<зміст test-файлу або "файл ще не існує">
|
|
82
82
|
\`\`\`
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
**Вцілілі мутанти** (кожен — зміна коду що НЕ вловлена):
|
|
85
85
|
<для кожного мутанта:>
|
|
86
86
|
- Рядок <line>, колонка <col>: `<original>` → `<replacement>` (тип: <mutantType>)
|
|
87
87
|
|
|
@@ -111,21 +111,24 @@ bun test
|
|
|
111
111
|
n-cursor coverage
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Прочитай новий `COVERAGE.md`.
|
|
114
|
+
Прочитай новий `COVERAGE.md`. Розбери JSON-масив вцілілих.
|
|
115
115
|
`newCount = новий масив.length`
|
|
116
116
|
|
|
117
117
|
**Рішення:**
|
|
118
118
|
|
|
119
119
|
- `newCount < prevCount` AND iterations < 3 → повтор з Кроку 2 з оновленим масивом
|
|
120
120
|
- `newCount >= prevCount` → конвергенція:
|
|
121
|
+
|
|
121
122
|
```
|
|
122
123
|
✓ Конвергенція: mutation score більше не покращується.
|
|
123
|
-
Було
|
|
124
|
+
Було вцілілих: <prevCount>, стало: <newCount>.
|
|
124
125
|
```
|
|
126
|
+
|
|
125
127
|
- iterations == 3 → зупинись:
|
|
128
|
+
|
|
126
129
|
```
|
|
127
130
|
⚠️ Досягнуто максимум ітерацій (3).
|
|
128
|
-
|
|
131
|
+
Вціліло: <newCount> мутантів. Деякі можуть бути стійкими (dead code, external state).
|
|
129
132
|
```
|
|
130
133
|
|
|
131
134
|
## Конвергенція — нормальний результат
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: n-fix-tests
|
|
3
3
|
description: >-
|
|
4
|
-
Ітеративно дописати тести щоб підвищити mutation score — читає
|
|
4
|
+
Ітеративно дописати тести щоб підвищити mutation score — читає вцілілі мутанти з COVERAGE.md і запускає агент до конвергенції
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# n-fix-tests — підвищення mutation score
|
|
8
8
|
|
|
9
9
|
## Мета
|
|
10
10
|
|
|
11
|
-
Читає структурований JSON-блок
|
|
11
|
+
Читає структурований JSON-блок вцілілих мутантів з `COVERAGE.md` і ітеративно дописує тести що їх вловлюють. Зупиняється коли score перестає покращуватись (конвергенція).
|
|
12
12
|
|
|
13
13
|
## Передумови
|
|
14
14
|
|
|
15
|
-
- У `COVERAGE.md` є секція `##
|
|
15
|
+
- У `COVERAGE.md` є секція `## Вцілілі мутанти` з JSON-блоком
|
|
16
16
|
- Залежності встановлені (`bun i`)
|
|
17
17
|
- `bun run coverage` (або `n-cursor coverage`) доступний
|
|
18
18
|
|
|
19
19
|
## Workflow
|
|
20
20
|
|
|
21
|
-
### Крок 1: Зчитай
|
|
21
|
+
### Крок 1: Зчитай вцілілих мутантів
|
|
22
22
|
|
|
23
|
-
Прочитай `COVERAGE.md`. Знайди секцію `##
|
|
23
|
+
Прочитай `COVERAGE.md`. Знайди секцію `## Вцілілі мутанти`. Знайди огороджений блок ` ```json ` у цій секції і розбери JSON-масив.
|
|
24
24
|
|
|
25
25
|
Якщо секція відсутня або масив порожній — зупинись з повідомленням:
|
|
26
|
-
`✓ Жодних
|
|
26
|
+
`✓ Жодних вцілілих мутантів — mutation score повний`
|
|
27
27
|
|
|
28
|
-
Запамʼятай поточну кількість
|
|
28
|
+
Запамʼятай поточну кількість вцілілих: `prevCount = масив.length`
|
|
29
29
|
|
|
30
30
|
### Крок 2: Знайди test-команду і coverage-команду
|
|
31
31
|
|
|
@@ -41,7 +41,7 @@ description: >-
|
|
|
41
41
|
1. `scripts["coverage"]` з `package.json` → виклик: `bun run coverage`
|
|
42
42
|
2. fallback: `n-cursor coverage`
|
|
43
43
|
|
|
44
|
-
### Крок 3: Для кожного файлу —
|
|
44
|
+
### Крок 3: Для кожного файлу — запускає Agent
|
|
45
45
|
|
|
46
46
|
Згрупуй мутанти по полю `file`. Для кожної групи виконай:
|
|
47
47
|
|
|
@@ -61,8 +61,8 @@ description: >-
|
|
|
61
61
|
**3b. Сформуй промпт для Agent:**
|
|
62
62
|
|
|
63
63
|
```
|
|
64
|
-
Тобі дані
|
|
65
|
-
Ці мутанти
|
|
64
|
+
Тобі дані вцілілі мутанти зі Stryker для файлу `<file>`.
|
|
65
|
+
Ці мутанти вціліли, тому що наявні тести НЕ вловили конкретні зміни коду.
|
|
66
66
|
|
|
67
67
|
**Вихідний код** (`<file>`):
|
|
68
68
|
\`\`\`
|
|
@@ -74,7 +74,7 @@ description: >-
|
|
|
74
74
|
<зміст test-файлу або "файл ще не існує">
|
|
75
75
|
\`\`\`
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
**Вцілілі мутанти** (кожен — зміна коду що НЕ вловлена):
|
|
78
78
|
<для кожного мутанта:>
|
|
79
79
|
- Рядок <line>, колонка <col>: `<original>` → `<replacement>` (тип мутації: <mutantType>)
|
|
80
80
|
|
|
@@ -105,14 +105,14 @@ bun test # або test-команда з кроку 2
|
|
|
105
105
|
bun run coverage # або coverage-команда з кроку 2
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
Прочитай новий `COVERAGE.md`, знайди і
|
|
108
|
+
Прочитай новий `COVERAGE.md`, знайди і розбери JSON-масив вцілілих.
|
|
109
109
|
`newCount = новий масив.length`
|
|
110
110
|
|
|
111
111
|
**Рішення:**
|
|
112
112
|
|
|
113
113
|
- Якщо `newCount < prevCount` → повтор з Кроку 1 з оновленим масивом
|
|
114
114
|
- Якщо `newCount >= prevCount` → зупинись:
|
|
115
|
-
`✓ Конвергенція: mutation score більше не покращується.
|
|
115
|
+
`✓ Конвергенція: mutation score більше не покращується. Вціліло: <newCount> мутантів.`
|
|
116
116
|
|
|
117
117
|
## Зупинка після конвергенції
|
|
118
118
|
|
|
File without changes
|