fix(db-repo,db-load-cf,epf-build): объявления цепочки v8path выше точки вызова

PowerShell ищет функцию в момент вызова и видит только уже выполнившиеся
объявления. В трёх навыках блок Find-V8Project/Test-SamePath/Find-ProjectDatabase
оказался ниже строки, где отрабатывает Find-ProjectV8Path: CommandNotFoundException
уходил в stderr, результат становился $null, и выбор платформы по записи базы молча
откатывался на корневой v8path. Порты .py не задеты — Python связывает имя при
вызове, так что расхождение PS↔PY было бы тихим.

Гард check-ps-define-before-call.mjs проверяет порядок объявлений во всех .ps1,
где отрабатывает эта цепочка (15 навыков), и на прежнем состоянии даёт ровно те
три диагностики. Кейс db-repo/v8path-from-database ловит тот же дефект поведением.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-19 21:16:22 +03:00
co-authored by Claude Opus 5
parent 874ae650ca
commit 77c797e6d0
9 changed files with 175 additions and 71 deletions
@@ -0,0 +1,33 @@
{
"name": "Платформа берётся из v8path записи базы",
"fakePlatform": {
"log": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект не был изменен: Справочник.Номенклатура\r\n---- Операция с хранилищем конфигурации завершена ----\r\nПомещение изменений объектов в хранилище успешно завершено\r\n"
},
"preRun": [
{
"writeFile": {
"path": ".v8-project.json",
"content": {
"v8path": "{workDir}/nowhere/1cv8.exe",
"databases": [
{ "id": "dev", "name": "Dev", "type": "file", "path": "{workDir}/ib", "v8path": "{fakePlatform}" }
]
}
}
}
],
"args_extra": [
"-InfoBasePath", "{workDir}/ib",
"-RepositoryPath", "{workDir}/repo",
"-RepositoryUser", "Admin",
"-Command", "commit",
"-Objects", "Справочник.Номенклатура",
"-Comment", "x"
],
"cwd": "workDir",
"expect": {
"stdoutContains": "Новая версия в хранилище НЕ создана",
"stdoutNotContains": "nowhere"
},
"noSnapshot": "проверяется выбор платформы по stdout, файлов на выходе нет"
}
+1
View File
@@ -14,6 +14,7 @@ const GUARDS = [
['check-uuid-invariant.mjs', 'сохранение uuid объекта и сущностей при правке'],
['check-minimal-diff.mjs', 'правка роли: диф только там, где просили'],
['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'],
['check-ps-define-before-call.mjs', 'порядок в .ps1: функция объявлена выше точки вызова'],
['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'],
['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'],
['check-form-purposes.mjs', 'назначения форм в form-add: согласованы со спецификацией и между портами'],
@@ -0,0 +1,73 @@
#!/usr/bin/env node
// PowerShell ищет функцию в момент ВЫЗОВА и видит только те определения, которые к этому
// моменту уже выполнились. Объявление ниже точки вызова отказывает молчаливо-наполовину:
// CommandNotFoundException уходит в stderr, вызывающая функция получает $null и спокойно
// идёт по ветке «не нашли». Так per-database v8path оказался no-op сразу в трёх навыках
// (#93) — тесты этого не заметили, потому что откат на корневой v8path выглядит штатно.
//
// В .py-порте такого класса ошибок нет: Python связывает имя при вызове, порядок определений
// в модуле не важен. Поэтому гард только для .ps1 — и поэтому расхождение портов молчаливое.
//
// Что проверяем: для каждой функции из списка — если навык её вызывает на верхнем уровне
// (или из функции, которая вызывается на верхнем уровне), определение обязано стоять выше
// первой строки, где начинается цепочка.
//
// Запуск: node tests/skills/check-ps-define-before-call.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 CHAINS = [
{
entry: 'Find-ProjectV8Path',
// вызов точки входа на верхнем уровне скрипта
entryCall: /^\s*\$V8Path\s*=\s*Find-ProjectV8Path\s*$/,
callees: ['Find-ProjectDatabase', 'Test-SamePath', 'Find-V8Project'],
},
];
const errors = [];
let checked = 0;
for (const skill of readdirSync(SKILLS)) {
const scriptsDir = join(SKILLS, skill, 'scripts');
if (!existsSync(scriptsDir)) continue;
for (const file of readdirSync(scriptsDir)) {
if (!file.endsWith('.ps1')) continue;
const path = join(scriptsDir, file);
const lines = readFileSync(path, 'utf8').split('\n');
for (const chain of CHAINS) {
const callLine = lines.findIndex(l => chain.entryCall.test(l));
if (callLine < 0) continue;
checked++;
const defLine = name => lines.findIndex(l => l.trimStart().startsWith(`function ${name}`));
for (const name of [chain.entry, ...chain.callees]) {
const def = defLine(name);
if (def < 0) {
errors.push(`${skill}/${file}: ${chain.entry} зовёт ${name}, но в навыке её нет`);
continue;
}
if (def > callLine) {
errors.push(`${skill}/${file}: function ${name} объявлена на строке ${def + 1}, `
+ `а цепочка ${chain.entry} выполняется на строке ${callLine + 1}`
+ `PowerShell её не увидит (отказ уйдёт в stderr, результат станет $null)`);
}
}
}
}
}
if (errors.length) {
console.log(`${errors.length} РАСХОЖДЕНИЙ:`);
for (const e of errors) console.log(` [ERROR] ${e}`);
console.log('\nПоднимите определения выше точки вызова — порядок в .ps1 значим.');
process.exit(1);
}
console.log(`Проверено цепочек: ${checked}`);
console.log('OK — все функции объявлены выше точки вызова.');