@7n/test 0.3.2 → 0.4.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 +6 -0
- package/package.json +1 -1
- package/src/fix-tests.mjs +146 -0
- package/src/run.mjs +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-06-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Додано `fix-tests.mjs`: виявляє падаючі unit-тести через vitest JSON reporter і викликає pi-агента для їх виправлення. Інтегровано в основний пайплайн `run.mjs` — якщо coverage порожня після першої ітерації, автоматично запускається фаза виправлення тестів.
|
|
8
|
+
|
|
3
9
|
## [0.3.2] - 2026-06-26
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/package.json
CHANGED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fix-tests: виявляє падаючі unit-тести і викликає pi-агента для їх виправлення.
|
|
3
|
+
*
|
|
4
|
+
* Порядок роботи:
|
|
5
|
+
* 1. getFailingTests(dir) — запускає `bunx vitest run --reporter=json` і повертає
|
|
6
|
+
* список падаючих файлів з повідомленнями про помилки.
|
|
7
|
+
* 2. fixFailingTests(dir, opts) — якщо є падіння, будує prompt і викликає pi CLI
|
|
8
|
+
* в агентному режимі; перевіряє результат повторним getFailingTests.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
12
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
13
|
+
import { tmpdir } from 'node:os'
|
|
14
|
+
import { join, relative } from 'node:path'
|
|
15
|
+
import { env } from 'node:process'
|
|
16
|
+
|
|
17
|
+
const MODEL = env.N_CURSOR_FIX_TESTS_MODEL ?? env.N_CLOUD_MAX_MODEL ?? ''
|
|
18
|
+
const MAX_ERRORS_PER_FILE = 5
|
|
19
|
+
const MAX_ERROR_LINES = 10
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runs vitest in JSON mode and returns failing test files with error messages.
|
|
23
|
+
* @param {string} dir project root
|
|
24
|
+
* @returns {Promise<Array<{file: string, errors: string[]}>>}
|
|
25
|
+
*/
|
|
26
|
+
export async function getFailingTests(dir) {
|
|
27
|
+
const tmpDir = await mkdtemp(join(tmpdir(), '7n-fix-'))
|
|
28
|
+
const outputFile = join(tmpDir, 'results.json')
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
spawnSync(
|
|
32
|
+
'bunx',
|
|
33
|
+
['vitest', 'run', '--reporter=json', `--outputFile=${outputFile}`, '--passWithNoTests'],
|
|
34
|
+
{ cwd: dir, stdio: 'inherit', env }
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if (!existsSync(outputFile)) return []
|
|
38
|
+
|
|
39
|
+
let data
|
|
40
|
+
try {
|
|
41
|
+
data = JSON.parse(readFileSync(outputFile, 'utf8'))
|
|
42
|
+
} catch {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (data.testResults ?? [])
|
|
47
|
+
.filter(r => r.status === 'failed')
|
|
48
|
+
.map(r => ({
|
|
49
|
+
file: relative(dir, r.testFilePath),
|
|
50
|
+
errors: (r.assertionResults ?? [])
|
|
51
|
+
.filter(a => a.status === 'failed')
|
|
52
|
+
.slice(0, MAX_ERRORS_PER_FILE)
|
|
53
|
+
.map(a => {
|
|
54
|
+
const name = [...(a.ancestorTitles ?? []), a.title].join(' > ')
|
|
55
|
+
const msg = (a.failureMessages?.[0] ?? '').split('\n').slice(0, MAX_ERROR_LINES).join('\n')
|
|
56
|
+
return `${name}:\n${msg}`
|
|
57
|
+
})
|
|
58
|
+
}))
|
|
59
|
+
.filter(f => !f.file.startsWith('..') && f.errors.length > 0)
|
|
60
|
+
} finally {
|
|
61
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => {})
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Builds a prompt for pi agent to fix failing tests.
|
|
67
|
+
* @param {Array<{file: string, errors: string[]}>} failures
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
export function buildFixTestsPrompt(failures) {
|
|
71
|
+
const fileList = failures
|
|
72
|
+
.map(({ file, errors }) => {
|
|
73
|
+
const errBlock = errors.join('\n\n')
|
|
74
|
+
return `### \`${file}\`\n\`\`\`\n${errBlock}\n\`\`\``
|
|
75
|
+
})
|
|
76
|
+
.join('\n\n')
|
|
77
|
+
|
|
78
|
+
return [
|
|
79
|
+
'Виправ падаючі unit-тести. Для кожного файлу:',
|
|
80
|
+
'1. Прочитай тест-файл і source-файл що він тестує',
|
|
81
|
+
'2. Запусти: `bunx vitest run <testFile>` — переконайся в точній помилці',
|
|
82
|
+
'3. Виправ ЛИШЕ тест-файл — source-файли не чіпай',
|
|
83
|
+
'4. Запусти тест ще раз — переконайся що зелений',
|
|
84
|
+
'',
|
|
85
|
+
'## Правила:',
|
|
86
|
+
'- Міняй виключно тест-файли',
|
|
87
|
+
'- Зберігай структуру тестів (describe/it/expect)',
|
|
88
|
+
'- Якщо тест перевіряє неіснуючий API — адаптуй до реального',
|
|
89
|
+
'- НЕ видаляй тести — лише виправляй',
|
|
90
|
+
'- Якщо тест тестує видалений функціонал — закоментуй з поясненням чому',
|
|
91
|
+
'',
|
|
92
|
+
'## Падаючі файли та помилки:',
|
|
93
|
+
'',
|
|
94
|
+
fileList
|
|
95
|
+
].join('\n')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Calls pi CLI as agent (live output) to fix failing tests.
|
|
100
|
+
* @param {string} prompt
|
|
101
|
+
* @param {string} model provider/model-id або '' для pi-дефолту
|
|
102
|
+
* @param {string} cwd
|
|
103
|
+
*/
|
|
104
|
+
function callPi(prompt, model, cwd) {
|
|
105
|
+
const modelArgs = model ? ['--model', model] : []
|
|
106
|
+
spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session'], {
|
|
107
|
+
cwd,
|
|
108
|
+
stdio: 'inherit',
|
|
109
|
+
timeout: 900_000
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Detects and fixes failing tests using a pi agent.
|
|
115
|
+
* Returns immediately with count=0 if all tests are already passing.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} dir project root
|
|
118
|
+
* @param {{
|
|
119
|
+
* failures?: Array<{file: string, errors: string[]}>,
|
|
120
|
+
* callPi?: (prompt: string, model: string, cwd: string) => void
|
|
121
|
+
* }} [opts]
|
|
122
|
+
* @returns {Promise<{count: number, fixed: number, remaining: number}>}
|
|
123
|
+
*/
|
|
124
|
+
export async function fixFailingTests(dir, opts = {}) {
|
|
125
|
+
const callPiFn = opts.callPi ?? callPi
|
|
126
|
+
const failures = opts.failures ?? (await getFailingTests(dir))
|
|
127
|
+
|
|
128
|
+
if (failures.length === 0) return { count: 0, fixed: 0, remaining: 0 }
|
|
129
|
+
|
|
130
|
+
console.log(`\n🔧 Виправляю ${failures.length} падаючих test-файлів (pi agent)...\n`)
|
|
131
|
+
for (const f of failures) {
|
|
132
|
+
console.log(` • ${f.file} (${f.errors.length} помилок)`)
|
|
133
|
+
}
|
|
134
|
+
console.log()
|
|
135
|
+
|
|
136
|
+
const prompt = buildFixTestsPrompt(failures)
|
|
137
|
+
callPiFn(prompt, MODEL, dir)
|
|
138
|
+
|
|
139
|
+
const after = await getFailingTests(dir)
|
|
140
|
+
const fixed = failures.length - after.length
|
|
141
|
+
|
|
142
|
+
if (fixed > 0) console.log(`✓ Виправлено: ${fixed}/${failures.length} файлів`)
|
|
143
|
+
if (after.length > 0) console.log(`⚠ Залишились падати: ${after.length} файлів`)
|
|
144
|
+
|
|
145
|
+
return { count: failures.length, fixed, remaining: after.length }
|
|
146
|
+
}
|
package/src/run.mjs
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { measureCoveragePerFile, getUncoveredFiles } from './coverage-per-file.mjs'
|
|
15
15
|
import { assessNeed } from './assess-need.mjs'
|
|
16
16
|
import { generateTests } from './gen-tests.mjs'
|
|
17
|
+
import { fixFailingTests, getFailingTests } from './fix-tests.mjs'
|
|
17
18
|
import { runCoverageSteps } from './coverage/coverage.mjs'
|
|
18
19
|
import { withLock } from './scripts/utils/with-lock.mjs'
|
|
19
20
|
|
|
@@ -35,6 +36,12 @@ export async function runAutoTest(dir, opts = {}) {
|
|
|
35
36
|
|
|
36
37
|
const allFiles = await measureCoveragePerFile(dir)
|
|
37
38
|
if (allFiles.length === 0) {
|
|
39
|
+
const failures = await getFailingTests(dir)
|
|
40
|
+
if (failures.length > 0) {
|
|
41
|
+
console.log(`\n── ${failures.length} тестів падають — виправляю (pi agent) ──\n`)
|
|
42
|
+
const { remaining } = await fixFailingTests(dir, { failures })
|
|
43
|
+
if (remaining === 0) continue
|
|
44
|
+
}
|
|
38
45
|
console.log('⚠ Vitest coverage не повернула даних — перевір налаштування vitest.')
|
|
39
46
|
break
|
|
40
47
|
}
|