@7n/test 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.3] - 2026-07-03
4
+
5
+ ### Added
6
+
7
+ - COVERAGE.md тепер записується і при --no-mutation: береться per-file line coverage з останньої ітерації циклу тестування (без мутаційних даних) замість повного пропуску запису звіту.
8
+
3
9
  ## [0.10.2] - 2026-07-02
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/test",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "CLI-утиліта @7n/test",
5
5
  "keywords": [
6
6
  "7n",
@@ -148,6 +148,35 @@ export function getUncoveredFiles(files, threshold = 80) {
148
148
  return files.filter(f => f.pct < threshold)
149
149
  }
150
150
 
151
+ /**
152
+ * Рендерить per-file line coverage як Markdown-таблицю без мутаційних даних.
153
+ * Використовується для `COVERAGE.md`, коли мутаційне тестування пропущено
154
+ * (`--no-mutation`) і провайдери `.n-cursor.json#rules` не викликались.
155
+ * @param {Array<{file: string, pct: number, linesFound: number, linesCovered: number}>} files per-file coverage rows
156
+ * @returns {string} Markdown з заголовком `# Coverage`
157
+ */
158
+ export function renderPerFileMarkdown(files) {
159
+ const sorted = files.toSorted((a, b) => a.pct - b.pct || a.file.localeCompare(b.file))
160
+ const lines = [
161
+ '# Coverage',
162
+ '',
163
+ '_Мутаційне тестування пропущено (`--no-mutation`) — лише per-file покриття рядків._',
164
+ '',
165
+ '| Файл | Рядки | % |',
166
+ '| --- | --- | --- |'
167
+ ]
168
+ let coveredTotal = 0
169
+ let foundTotal = 0
170
+ for (const f of sorted) {
171
+ lines.push(`| ${f.file} | ${f.linesCovered}/${f.linesFound} | ${f.pct.toFixed(2)}% |`)
172
+ coveredTotal += f.linesCovered
173
+ foundTotal += f.linesFound
174
+ }
175
+ const totalPct = foundTotal === 0 ? 100 : (coveredTotal / foundTotal) * 100
176
+ lines.push(`| **Разом** | ${coveredTotal}/${foundTotal} | ${totalPct.toFixed(2)}% |`)
177
+ return `${lines.join('\n')}\n`
178
+ }
179
+
151
180
  const SOURCE_EXT_RE = /\.(mjs|js|ts|vue|py)$/
152
181
  const IGNORE_DIRS = new Set([
153
182
  'node_modules',
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: coverage-per-file.mjs
4
4
  resource: npm/src/coverage-per-file.mjs
5
5
  docgen:
6
- crc: 54eea127
6
+ crc: edc508cb
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -20,12 +20,14 @@ docgen:
20
20
  measureCoveragePerFile запускає Vitest з генерацією покриття та звітністю у форматі JSON у єдиному проході, повертаючи дані про покриття по файлах та список тестів, що не пройшли. При цьому ігноруються папки та файли, такі як node_modules та .git, і винятки у процесі вимірювання обробляються, не викликаючи збоїв.
21
21
  getUncoveredFiles відфільтровує файли, покриття яких нижче заданого порогового значення.
22
22
  findSourceFiles рекурсивно знаходить усі файли вихідного коду в заданому каталозі, виключаючи тестові файли та директорії, визначені як ігнорувані.
23
+ renderPerFileMarkdown рендерить per-file покриття рядків (без мутаційних даних) як Markdown-таблицю з підсумковим рядком «Разом», відсортовану за зростанням відсотка покриття.
23
24
 
24
25
  ## Публічний API
25
26
 
26
27
  - measureCoveragePerFile — Вимірює покриття коду за файлами, збираючи результати покриття Vitest та логуючи провалені тести за один прохід.
27
28
  - getUncoveredFiles — Повертає список файлів, які не досягли встановленого порогу покриття.
28
29
  - findSourceFiles — Рекурсивно шукає файли вихідного коду в директорії, ігноруючи тестові файли та проігноровані каталоги, використовуючи це для ініціалізації у разі відсутності даних про покриття.
30
+ - renderPerFileMarkdown — Форматує масив per-file coverage рядків у Markdown-документ `# Coverage` для запису в `COVERAGE.md`, коли мутаційне тестування пропущено.
29
31
 
30
32
  ## Гарантії поведінки
31
33
 
package/src/docs/run.md CHANGED
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: run.mjs
4
4
  resource: npm/src/run.mjs
5
5
  docgen:
6
- crc: b932be1d
6
+ crc: f71ad579
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.95
@@ -12,21 +12,22 @@ docgen:
12
12
 
13
13
  ## Огляд
14
14
 
15
- Main auto-test loop забезпечує автоматизований цикл тестування з метою підвищення покриття коду. У першій фазі відбувається вимірювання покриття коду по файлах через vitest+lcov. Якщо покриття нижче заданого порогу, генеруються тести для відповідних файлів. Процес повторюється, доки покриття не досягне максимуму або не буде досягнуто обмежень ітерацій. Друга фаза включає мутаційне тестування, яке проводиться з використанням існуючих провайдерів покриття, залежність від .n-cursor.json. Після виявлення виживших мутантів, pi agent автоматично їх виправляє. Функція runAutoTest перехоплює можливі помилки, запобігаючи виняткам на виході, але не виконує кешування.
15
+ Main auto-test loop забезпечує автоматизований цикл тестування з метою підвищення покриття коду. У першій фазі відбувається вимірювання покриття коду по файлах через vitest+lcov. Якщо покриття нижче заданого порогу, генеруються тести для відповідних файлів. Процес повторюється, доки покриття не досягне максимуму або не буде досягнуто обмежень ітерацій. Друга фаза включає мутаційне тестування, яке проводиться з використанням існуючих провайдерів покриття, залежність від .n-cursor.json. Після виявлення виживших мутантів, pi agent автоматично їх виправляє. З опцією `noMutation` друга фаза повністю пропускається, а натомість `COVERAGE.md` записується з per-file покриттям, отриманим в останній ітерації першої фази. Функція runAutoTest перехоплює можливі помилки, запобігаючи виняткам на виході, але не виконує кешування.
16
16
 
17
17
  ## Поведінка
18
18
 
19
19
  Поведінка
20
20
 
21
21
  1. Викликається `runAutoTest` для запуску циклу автоматичного тестування.
22
- 2. У циклі до п'яти ітерацій вимірюється покриття кожної функції у проекті.
22
+ 2. У циклі до п'яти ітерацій вимірюється покриття кожної функції у проекті; результат останньої ітерації зберігається для можливого запису в `COVERAGE.md`.
23
23
  3. Якщо існують падіння тестів, вони автоматично виправляються через агент, і цикл продовжується.
24
24
  4. Якщо немає тестів, відбувається запуск процесу Bootstrap: скануються вихідні файли, класифікуються, і для тих, що потребують тестів, генеруються початкові тести.
25
25
  5. Обчислюється загальне покриття. Якщо воно досягло встановленого порогу, цикл зупиняється.
26
26
  6. Якщо покриття не покращилося порівняно з попередньою ітерацією, цикл зупиняється.
27
27
  7. Якщо покриття недостатнє, генеруються тести для некритичних файлів і цикл повторюється.
28
- 8. Після завершення циклу, якщо не вказано інакше, запускається мутаційне тестування, яке залежить від конфігурації в `.n-cursor.json`.
29
- 9. У разі успішного виживання мутантів, виконується повторний запуск покриття для перевірки.
28
+ 8. Якщо передано опцію `noMutation`, мутаційне тестування пропускається: `COVERAGE.md` записується з per-file покриттям останньої ітерації (без мутаційних даних), або лишається незаписаним, якщо даних покриття не отримано.
29
+ 9. Інакше запускається мутаційне тестування, яке залежить від конфігурації в `.n-cursor.json`.
30
+ 10. У разі успішного виживання мутантів, виконується повторний запуск покриття для перевірки.
30
31
 
31
32
  ## Гарантії поведінки
32
33
 
package/src/run.mjs CHANGED
@@ -7,17 +7,24 @@
7
7
  * 3. Bootstrap: when no tests exist, scan sources and quickClassify locally.
8
8
  * 4. Repeat until coverage maxes out or no improvement.
9
9
  *
10
- * Phase 2:
10
+ * Phase 2 (skipped entirely with --no-mutation, which instead writes
11
+ * COVERAGE.md from the last Phase 1 per-file measurement):
11
12
  * 5. Mutation testing via existing coverage providers (requires .n-cursor.json#rules).
12
13
  * 6. Auto-fix survived mutants via pi agent.
13
14
  */
14
- import { measureCoveragePerFile, getUncoveredFiles, findSourceFiles } from './coverage-per-file.mjs'
15
+ import {
16
+ measureCoveragePerFile,
17
+ getUncoveredFiles,
18
+ findSourceFiles,
19
+ renderPerFileMarkdown
20
+ } from './coverage-per-file.mjs'
15
21
  import { quickClassify } from './assess-need.mjs'
16
22
  import { generateTests } from './gen-tests.mjs'
17
23
  import { fixFailingTests } from './fix-tests.mjs'
18
24
  import { runCoverageSteps } from './coverage/coverage.mjs'
19
25
  import { withLock } from './scripts/utils/with-lock.mjs'
20
26
  import { readFileSync } from 'node:fs'
27
+ import { writeFile } from 'node:fs/promises'
21
28
  import { join } from 'node:path'
22
29
 
23
30
  const MAX_ITERATIONS = 5
@@ -25,18 +32,20 @@ const COVERAGE_THRESHOLD = 80
25
32
 
26
33
  /**
27
34
  * @param {string} dir absolute path to project root
28
- * @param {{ noMutation?: boolean }} [opts]
35
+ * @param {{ noMutation?: boolean }} [opts] опції запуску
29
36
  * @returns {Promise<number>} exit code
30
37
  */
31
38
  export async function runAutoTest(dir, opts = {}) {
32
39
  console.log(`\n📁 ${dir}\n`)
33
40
 
34
41
  let prevUncoveredCount = Infinity
42
+ let lastFiles = []
35
43
 
36
44
  for (let i = 1; i <= MAX_ITERATIONS; i++) {
37
45
  console.log(`\n── Ітерація ${i}/${MAX_ITERATIONS}: coverage ──\n`)
38
46
 
39
47
  const { files: allFiles, failingTests } = await measureCoveragePerFile(dir)
48
+ lastFiles = allFiles
40
49
 
41
50
  if (failingTests.length > 0) {
42
51
  console.log(`\n── ${failingTests.length} тестів падають — виправляю (pi agent) ──\n`)
@@ -107,6 +116,12 @@ export async function runAutoTest(dir, opts = {}) {
107
116
 
108
117
  if (opts.noMutation) {
109
118
  console.log('\n── Мутаційне тестування пропущено (--no-mutation) ──\n')
119
+ if (lastFiles.length > 0) {
120
+ await writeFile(join(dir, 'COVERAGE.md'), renderPerFileMarkdown(lastFiles), 'utf8')
121
+ console.log('✓ COVERAGE.md (тільки per-file покриття, без мутантів)')
122
+ } else {
123
+ console.log('⚠ Немає даних покриття — COVERAGE.md не записано.')
124
+ }
110
125
  return 0
111
126
  }
112
127