fix(xdto-edit): преflight-проверка соседних навыков

xdto-edit — единственный навык с жёсткой зависимостью: без xdto-decompile
и xdto-compile модельные операции не работают. Остальные пять межнавыковых
вызовов в репозитории ведут только к *-validate, необязательному post-шагу
с деградацией в [SKIP], так что это отступление, а не следование практике.

Раньше отсутствие соседа обнаруживалось на середине правки. Теперь комплектность
проверяется до начала работы, с указанием, что навыки ставятся комплектом.
Операции над объектом метаданных (rename, set-synonym, set-comment) соседей
не требуют и работают в одиночку — проверка их не блокирует.

В SKILL.md зависимость намеренно не описана: это раздуло бы инструкцию и подало
бы исключение как допустимую практику. Причины, по которым не сделана копия
(конвертер — скрипт, а не библиотека; вторая реализация разошлась бы, чему есть
прямая улика в learning_meta_edit_emitter_ports; гарантия байт-точности держится
на тождестве кода) записаны в debug/xdto/FINDINGS.md — там, где их увидит тот,
кто соберётся «починить» это дублированием.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-26 12:43:57 +03:00
co-authored by Claude Opus 5
parent 318abe3fc9
commit 679f820e52
2 changed files with 43 additions and 0 deletions
@@ -127,6 +127,27 @@ $decompileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-decompile") "s
$compileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-compile") "scripts\xdto-compile.ps1"
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-validate") "scripts\xdto-validate.ps1"
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD ↔ модель
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
# в нетронутом» держится именно на том, что код тот же самый.
# Проверяем комплектность заранее, чтобы не падать на середине правки.
function Assert-SiblingsPresent([string]$operation) {
$needed = @{}
if (@("rename", "set-synonym", "set-comment") -notcontains $operation) {
$needed["xdto-decompile"] = $decompileScript
$needed["xdto-compile"] = $compileScript
}
$missing = @()
foreach ($k in $needed.Keys) { if (-not (Test-Path $needed[$k])) { $missing += $k } }
if ($missing.Count -gt 0) {
throw ("Навык неработоспособен: рядом нет " + ($missing -join ", ") + ".`n" +
"Операция `"$operation`" выполняется через них, поэтому обойтись без них нельзя.`n" +
"Навыки устанавливаются комплектом — скопируйте каталог .claude/skills целиком, " +
"а не отдельные подкаталоги.")
}
}
function Invoke-Sibling([string]$script, [string[]]$argList, [string]$what) {
if (-not (Test-Path $script)) { throw "Не найден навык $what по пути: $script" }
$out = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script @argList 2>&1
@@ -444,6 +465,8 @@ function Apply-ModelOperation($schema) {
$metaOps = @("rename", "set-synonym", "set-comment")
$touchesModel = ($metaOps -notcontains $Operation)
Assert-SiblingsPresent $Operation
Write-Host "Пакет: $pkgName"
if ($Operation -eq "rename") {
@@ -153,6 +153,24 @@ COMPILE = os.path.join(SKILLS, "xdto-compile", "scripts", "xdto-compile.py")
VALIDATE = os.path.join(SKILLS, "xdto-validate", "scripts", "xdto-validate.py")
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD <-> модель
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
# в нетронутом» держится именно на том, что код тот же самый.
# Проверяем комплектность заранее, чтобы не падать на середине правки.
def assert_siblings_present(operation):
needed = {}
if operation not in ("rename", "set-synonym", "set-comment"):
needed["xdto-decompile"] = DECOMPILE
needed["xdto-compile"] = COMPILE
missing = [k for k, v in needed.items() if not os.path.exists(v)]
if missing:
die("Навык неработоспособен: рядом нет " + ", ".join(missing) + ".\n"
+ f'Операция "{operation}" выполняется через них, поэтому обойтись без них нельзя.\n'
+ "Навыки устанавливаются комплектом — скопируйте каталог .claude/skills целиком, "
"а не отдельные подкаталоги.")
def invoke_sibling(script, argv, what):
if not os.path.exists(script):
die(f"Не найден навык {what} по пути: {script}")
@@ -433,6 +451,8 @@ def apply_model_operation(schema):
# ── dispatch ─────────────────────────────────────────────────
assert_siblings_present(args.Operation)
print(f"Пакет: {pkg_name}")
old_namespace = None