mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-29 06:29:41 +03:00
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fdeae5c87f
commit
4f61ef77ca
@@ -111,6 +111,7 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
||||
| `check-form-purposes.mjs` | таблица назначений форм в `form-add`: состав видов, свойство «основная форма» и оба порта сходятся с `docs/1c-form-spec.md` |
|
||||
| `check-positional-binding.mjs` | read-only навыки (`*-info` / `*-validate` / `cfe-diff`): позиционным остаётся только путь ко входу, лишний позиционный аргумент не перезаписывает указанный файл |
|
||||
| `check-agent-portability.mjs` | исходники навыков не привязаны к конкретному AI-агенту: единственная разрешённая форма — плейсхолдер `${CLAUDE_SKILL_DIR}/`, который разворачивает `scripts/switch.py` |
|
||||
| `check-error-streams.mjs` | сообщения об ошибках идут в один и тот же поток в обоих портах навыка (соответствие из `docs/python-porting-guide.md`) |
|
||||
| `check-nonascii-fs.mjs` | `fsutil`: удаление и копирование держат не-ASCII пути (кириллический `%TEMP%`, кириллические имена объектов 1С), обе копии модуля не разошлись |
|
||||
|
||||
`check-inline-drift.mjs` держит реестр семей внутри себя: у каждой семьи перечислены варианты, у
|
||||
|
||||
@@ -17,6 +17,7 @@ const GUARDS = [
|
||||
['check-form-purposes.mjs', 'назначения форм в form-add: согласованы со спецификацией и между портами'],
|
||||
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
|
||||
['check-agent-portability.mjs', 'исходники навыков: без привязки к конкретному AI-агенту'],
|
||||
['check-error-streams.mjs', 'сообщения об ошибках: один и тот же поток в обоих портах'],
|
||||
['check-nonascii-fs.mjs', 'fsutil: удаление и копирование держат не-ASCII пути, копии не разошлись'],
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/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);
|
||||
Reference in New Issue
Block a user