Files
cc-1c-skills/tests/skills/check-error-streams.mjs
T
Nick ShirokovandClaude Opus 5 4f61ef77ca fix(py-порты): ошибки печатать в тот же поток, что и PS
Двадцать один py-порт печатал ошибки в stderr, тогда как их PS-мастера пишут
через Write-Host в stdout. Счётчики совпадали один в один (14↔14, 19↔19,
13↔13) — сообщения были те же, разъехался только поток. Это нарушало
соответствие из docs/python-porting-guide.md, где Write-Host сопоставлен
обычному print.

Это не косметика. Харнесс не чередует потоки, а группирует: сначала весь
stderr, потом весь stdout. Из-за этого в py-порте вердикт «Error dumping
configuration (code: 1)» печатался ПЕРЕД строками, которые его объясняют, а
причина из лога платформы оказывалась в самом низу — причинный порядок вывода
переворачивался. Порт, работающий на macOS, читался хуже того, что работает на
Windows.

Тесты этого не ловили по построению: текст ошибки сверяют только кейсы со
строковым expectError, а он смотрит в stderr — потому такие кейсы есть лишь у
семейства, где потоки сходятся, а в db-* их ноль.

Добавлен гард check-error-streams.mjs: нет записи в stderr в PS-порте — не
должно быть и в py, и симметрично. Он сразу нашёл пять навыков сверх тех, что
я насчитал вручную, и отсеял два ложных срабатывания (в meta-remove слово
Write-Error стоит в комментарии «почему НЕ Write-Error»).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 19:03:34 +03:00

72 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// Инвариант: сообщения об ошибках уходят в ОДИН И ТОТ ЖЕ поток в обоих портах навыка.
//
// Соответствие задано в docs/python-porting-guide.md: `Write-Host` → `print`,
// `Write-Error` → `print(..., file=sys.stderr)`. Тринадцать навыков группы db-*/epf-*/web-*
// его нарушали: PS писал ошибки через Write-Host (stdout), а py-порт — в stderr. Счётчики
// совпадали один в один (14↔14, 19↔19, 13↔13) — то есть сообщения были те же, разъехался
// только поток.
//
// Почему это не косметика: харнесс не чередует потоки, а группирует — сначала весь stderr,
// потом весь stdout. Из-за этого в py-порте вердикт «Error dumping configuration (code: 1)»
// печатался ПЕРЕД строками, которые его объясняют, а причина из лога платформы оказывалась
// в самом низу. Причинный порядок вывода переворачивался, и порт для macOS читался хуже
// того, что работает на Windows.
//
// Почему не ловилось тестами: текст ошибки сверяют только кейсы со строковым `expectError`,
// а он смотрит в stderr — поэтому такие кейсы есть лишь у семейства, где потоки сходятся.
// В db-* их ноль, и дыра пряталась за собственным следствием.
//
// Запуск: node tests/skills/check-error-streams.mjs
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const SKILLS = join(ROOT, '.claude', 'skills');
const errors = [];
let checked = 0;
for (const skill of readdirSync(SKILLS)) {
const dir = join(SKILLS, skill, 'scripts');
if (!existsSync(dir)) continue;
for (const file of readdirSync(dir)) {
if (!file.endsWith('.ps1')) continue;
const base = file.slice(0, -4);
const pyPath = join(dir, base + '.py');
if (!existsSync(pyPath)) continue;
// Форм записи в stderr по две с каждой стороны, и считать надо обе: PS пишет через
// Write-Error и через [Console]::Error.WriteLine, py — через file=sys.stderr и
// sys.stderr.write. Комментарии выбрасываем: в meta-remove.ps1 слово Write-Error стоит
// в пояснении «почему НЕ Write-Error», и по одной форме гард давал ложную тревогу.
const strip = (text, marker) => text.split('\n')
.filter(l => !l.trimStart().startsWith(marker)).join('\n');
const ps = strip(readFileSync(join(dir, file), 'utf8').replace(/^/, ''), '#');
const py = strip(readFileSync(pyPath, 'utf8'), '#');
const psErr = (ps.match(/Write-Error|Console\]::Error\.Write/g) || []).length;
const pyErr = (py.match(/file=sys\.stderr|sys\.stderr\.write/g) || []).length;
checked++;
if (psErr === 0 && pyErr > 0) {
errors.push(`${skill}/${base}: PS не использует Write-Error, а py пишет в stderr `
+ `(${pyErr} мест). Ошибки должны идти в тот же поток, что и в PS — убрать file=sys.stderr.`);
}
if (psErr > 0 && pyErr === 0) {
errors.push(`${skill}/${base}: PS использует Write-Error (${psErr} мест), а py пишет всё `
+ `в stdout. Ошибки должны идти в тот же поток — добавить file=sys.stderr.`);
}
}
}
console.log(`Проверено пар портов: ${checked}`);
if (errors.length === 0) {
console.log('OK — потоки сообщений об ошибках совпадают в обоих портах.');
process.exit(0);
}
console.log(`\n${errors.length} РАСХОЖДЕНИЙ:`);
for (const e of errors) console.log(` [ERROR] ${e}`);
console.log('\nСоответствие потоков: docs/python-porting-guide.md, таблица маппинга PS → Python.');
process.exit(1);