diff --git a/.claude/skills/cf-edit/scripts/cf-edit.ps1 b/.claude/skills/cf-edit/scripts/cf-edit.ps1 index 3cc88cc91..d6a187649 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.ps1 +++ b/.claude/skills/cf-edit/scripts/cf-edit.ps1 @@ -1,4 +1,4 @@ -# cf-edit v1.19 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.20 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, @@ -10,6 +10,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Mode validation --- @@ -639,10 +658,7 @@ function Do-SetPanels($valArg) { # Accept string (JSON), PSCustomObject, or hashtable $layout = $valArg if ($layout -is [string]) { - try { $layout = $layout | ConvertFrom-Json } catch { - Write-Error "set-panels value must be valid JSON object, got: $valArg" - exit 1 - } + $layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-panels'" "a JSON object with panel layout" } if (-not $layout) { Write-Error "set-panels value is empty" @@ -826,9 +842,7 @@ $indent function Do-SetHomePage($valArg) { $layout = $valArg if ($layout -is [string]) { - try { $layout = $layout | ConvertFrom-Json } catch { - Write-Error "set-home-page value must be valid JSON object"; exit 1 - } + $layout = ConvertFrom-JsonInput $layout "-Value for operation 'set-home-page'" "a JSON object with home page layout" } if (-not $layout) { Write-Error "set-home-page value is empty"; exit 1 } @@ -943,7 +957,7 @@ if ($DefinitionFile) { $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile } $jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile - $ops = $jsonText | ConvertFrom-Json + $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile if ($ops -is [System.Array]) { foreach ($op in $ops) { $operations += $op } } else { diff --git a/.claude/skills/cf-edit/scripts/cf-edit.py b/.claude/skills/cf-edit/scripts/cf-edit.py index ec0afaa14..68a5954f8 100644 --- a/.claude/skills/cf-edit/scripts/cf-edit.py +++ b/.claude/skills/cf-edit/scripts/cf-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cf-edit v1.19 — Edit 1C configuration root (Configuration.xml) +# cf-edit v1.20 — Edit 1C configuration root (Configuration.xml) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -14,6 +14,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -821,11 +844,8 @@ def main(): nonlocal modify_count layout = value if isinstance(layout, str): - try: - layout = ci_json(json.loads(layout)) - except json.JSONDecodeError: - print(f"set-panels value must be valid JSON object", file=sys.stderr) - sys.exit(1) + layout = ci_json(parse_json_input( + layout, "-Value for operation 'set-panels'", "a JSON object with panel layout")) if not isinstance(layout, dict) or not layout: print("set-panels value must be non-empty object", file=sys.stderr) sys.exit(1) @@ -976,11 +996,8 @@ def main(): nonlocal modify_count layout = value if isinstance(layout, str): - try: - layout = ci_json(json.loads(layout)) - except json.JSONDecodeError: - print("set-home-page value must be valid JSON object", file=sys.stderr) - sys.exit(1) + layout = ci_json(parse_json_input( + layout, "-Value for operation 'set-home-page'", "a JSON object with home page layout")) if not isinstance(layout, dict) or not layout: print("set-home-page value must be non-empty object", file=sys.stderr) sys.exit(1) @@ -1045,7 +1062,7 @@ def main(): if not os.path.isabs(def_file): def_file = os.path.join(os.getcwd(), def_file) with open(def_file, "r", encoding="utf-8-sig") as fh: - ops = ci_json(json.loads(fh.read())) + ops = ci_json(parse_json_input(fh.read(), def_file)) if isinstance(ops, list): operations = ops else: diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 0300f6768..7c170ee91 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) +# form-compile v1.193 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -14,6 +14,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # ═══════════════════════════════════════════════════════════════════════════ @@ -300,7 +319,7 @@ function Load-Preset([string]$PresetName, [string]$ScriptDir) { $presetDir = Join-Path (Split-Path $ScriptDir -Parent) "presets" $builtInPath = Join-Path $presetDir "$PresetName.json" if (Test-Path $builtInPath) { - $presetJson = Get-Content -Raw -Encoding UTF8 $builtInPath | ConvertFrom-Json + $presetJson = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $builtInPath) $builtInPath # Convert PSCustomObject to hashtable recursively $toHash = { param($obj) @@ -327,7 +346,7 @@ function Load-Preset([string]$PresetName, [string]$ScriptDir) { while ($scanDir) { $projPreset = Join-Path (Join-Path (Join-Path (Join-Path $scanDir "presets") "skills") "form") "$PresetName.json" if (Test-Path $projPreset) { - $projJson = Get-Content -Raw -Encoding UTF8 $projPreset | ConvertFrom-Json + $projJson = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $projPreset) $projPreset $projHash = & $toHash $projJson foreach ($k in @($projHash.Keys)) { $defaults[$k] = & $deepMerge $defaults[$k] $projHash[$k] @@ -1656,7 +1675,7 @@ if ($FromObject) { } $json = Get-Content -Raw -Encoding UTF8 $JsonPath - $def = $json | ConvertFrom-Json + $def = ConvertFrom-JsonInput $json $JsonPath } # Базовая директория для @file-ссылок в query динсписка (зеркало skd-compile) diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 4b521be9b..96b091053 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) +# form-compile v1.193 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -15,6 +15,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -543,7 +566,7 @@ def load_preset(preset_name, script_dir, out_path_resolved): built_in_path = os.path.join(preset_dir, f'{preset_name}.json') if os.path.isfile(built_in_path): with open(built_in_path, 'r', encoding='utf-8-sig') as f: - preset_data = ci_json(json.load(f)) + preset_data = ci_json(parse_json_input(f.read(), built_in_path)) for k in list(preset_data.keys()): defaults[k] = _deep_merge(defaults.get(k), preset_data[k]) @@ -553,7 +576,7 @@ def load_preset(preset_name, script_dir, out_path_resolved): proj_preset = os.path.join(scan_dir, 'presets', 'skills', 'form', f'{preset_name}.json') if os.path.isfile(proj_preset): with open(proj_preset, 'r', encoding='utf-8-sig') as f: - proj_data = json.load(f) + proj_data = parse_json_input(f.read(), proj_preset) for k in list(proj_data.keys()): defaults[k] = _deep_merge(defaults.get(k), proj_data[k]) break @@ -6456,7 +6479,7 @@ def main(): sys.exit(1) with open(json_path, 'r', encoding='utf-8-sig') as f: - defn = ci_json(json.load(f)) + defn = ci_json(parse_json_input(f.read(), json_path)) global QUERY_BASE_DIR QUERY_BASE_DIR = os.path.dirname(os.path.abspath(json_path)) diff --git a/.claude/skills/form-edit/scripts/form-edit.ps1 b/.claude/skills/form-edit/scripts/form-edit.ps1 index 9f30e6e2d..91d22d1b8 100644 --- a/.claude/skills/form-edit/scripts/form-edit.ps1 +++ b/.claude/skills/form-edit/scripts/form-edit.ps1 @@ -1,4 +1,4 @@ -# form-edit v1.14 — Edit 1C managed form elements (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) +# form-edit v1.15 — Edit 1C managed form elements (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -10,6 +10,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Support guard (Ext/ParentConfigurations.bin) --- @@ -175,7 +194,7 @@ $root = $xmlDoc.DocumentElement # === 2. Load JSON === -$def = Get-Content -Raw -Encoding UTF8 $JsonPath | ConvertFrom-Json +$def = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $JsonPath) $JsonPath # === 3. Form name + header === diff --git a/.claude/skills/form-edit/scripts/form-edit.py b/.claude/skills/form-edit/scripts/form-edit.py index b8cf70102..53e2aa81c 100644 --- a/.claude/skills/form-edit/scripts/form-edit.py +++ b/.claude/skills/form-edit/scripts/form-edit.py @@ -1,4 +1,4 @@ -# form-edit v1.14 — Edit 1C managed form elements (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) +# form-edit v1.15 — Edit 1C managed form elements (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -13,6 +13,29 @@ sys.stderr.reconfigure(encoding="utf-8") # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -318,7 +341,7 @@ root = tree.getroot() # ── 2. Load JSON ──────────────────────────────────────────── with open(json_path, "r", encoding="utf-8-sig") as f: - defn = ci_json(json.load(f)) + defn = ci_json(parse_json_input(f.read(), json_path)) # ── 3. Form name + header ─────────────────────────────────── diff --git a/.claude/skills/interface-edit/scripts/interface-edit.ps1 b/.claude/skills/interface-edit/scripts/interface-edit.ps1 index c6eb0f29f..2d5e82c95 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.ps1 +++ b/.claude/skills/interface-edit/scripts/interface-edit.ps1 @@ -1,4 +1,4 @@ -# interface-edit v1.18 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) +# interface-edit v1.19 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$CIPath, @@ -17,6 +17,25 @@ $ErrorActionPreference = "Stop" if ($DefinitionFile -and $Operation) { Write-Error "Cannot use both -DefinitionFile and -Operation"; exit 1 } if (-not $DefinitionFile -and -not $Operation) { Write-Error "Either -DefinitionFile or -Operation is required"; exit 1 } +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} + # --- Resolve path --- if (-not [System.IO.Path]::IsPathRooted($CIPath)) { $CIPath = Join-Path (Get-Location).Path $CIPath @@ -351,10 +370,10 @@ function Ensure-Section([string]$sectionName) { } # --- Parse value: string or JSON array --- -function Parse-ValueList([string]$val) { +function Parse-ValueList([string]$val, [string]$opName) { $val = $val.Trim() if ($val.StartsWith("[")) { - $arr = $val | ConvertFrom-Json + $arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of command names" $result = @(); foreach ($item in $arr) { $result += "$item" } return ,$result } @@ -519,7 +538,7 @@ function Do-Show([string[]]$commands) { } function Do-Place([string]$jsonVal) { - $def = $jsonVal | ConvertFrom-Json + $def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'place'" "a JSON object {command, group}" $cmdName = Normalize-CmdName "$($def.command)" $groupName = "$($def.group)" if (-not $cmdName -or -not $groupName) { Write-Error "place requires {command, group}"; exit 1 } @@ -552,7 +571,7 @@ function Do-Place([string]$jsonVal) { } function Do-Order([string]$jsonVal) { - $def = $jsonVal | ConvertFrom-Json + $def = ConvertFrom-JsonInput $jsonVal "-Value for operation 'order'" "a JSON object {group, commands:[...]}" $groupName = "$($def.group)" $commands = @($def.commands | ForEach-Object { Normalize-CmdName "$_" }) if (-not $groupName -or $commands.Count -eq 0) { Write-Error "order requires {group, commands:[...]}"; exit 1 } @@ -590,7 +609,7 @@ function Do-Order([string]$jsonVal) { } function Do-SubsystemOrder([string]$jsonVal) { - $parsed = $jsonVal | ConvertFrom-Json + $parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'subsystem-order'" "a JSON array of subsystem paths" $subsystems = @(); foreach ($s in $parsed) { $subsystems += "$s" } if ($subsystems.Count -eq 0) { Write-Error "subsystem-order requires array of subsystem paths"; exit 1 } @@ -618,7 +637,7 @@ function Do-SubsystemOrder([string]$jsonVal) { } function Do-GroupOrder([string]$jsonVal) { - $parsed = $jsonVal | ConvertFrom-Json + $parsed = ConvertFrom-JsonInput $jsonVal "-Value for operation 'group-order'" "a JSON array of group names" $groups = @(); foreach ($g in $parsed) { $groups += "$g" } if ($groups.Count -eq 0) { Write-Error "group-order requires array of group names"; exit 1 } @@ -652,7 +671,7 @@ if ($DefinitionFile) { $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile } $jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile - $ops = $jsonText | ConvertFrom-Json + $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile if ($ops -is [System.Array]) { foreach ($op in $ops) { $operations += $op } } else { @@ -669,8 +688,8 @@ foreach ($op in $operations) { $opValue = if ($opValueRaw -is [string]) { $opValueRaw } else { $opValueRaw | ConvertTo-Json -Compress } switch ($opName) { - "hide" { Do-Hide (Parse-ValueList $opValue) } - "show" { Do-Show (Parse-ValueList $opValue) } + "hide" { Do-Hide (Parse-ValueList $opValue $opName) } + "show" { Do-Show (Parse-ValueList $opValue $opName) } "place" { Do-Place $opValue } "order" { Do-Order $opValue } "subsystem-order" { Do-SubsystemOrder $opValue } diff --git a/.claude/skills/interface-edit/scripts/interface-edit.py b/.claude/skills/interface-edit/scripts/interface-edit.py index a362a3a03..11fe90a01 100644 --- a/.claude/skills/interface-edit/scripts/interface-edit.py +++ b/.claude/skills/interface-edit/scripts/interface-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# interface-edit v1.18 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) +# interface-edit v1.19 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -348,10 +348,32 @@ def import_ci_fragment(xml_string): return nodes -def parse_value_list(val): +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + +def parse_value_list(val, op_name): val = val.strip() if val.startswith("["): - arr = ci_json(json.loads(val)) + arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of command names")) return [str(item) for item in arr] return [val] @@ -647,7 +669,8 @@ def main(): def do_place(json_val): nonlocal add_count, modify_count - defn = ci_json(json_val if isinstance(json_val, dict) else json.loads(json_val)) + defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input( + json_val, "-Value for operation 'place'", "a JSON object {command, group}")) cmd_name = normalize_cmd_name(str(defn["command"])) group_name = str(defn["group"]) if not cmd_name or not group_name: @@ -675,7 +698,8 @@ def main(): def do_order(json_val): nonlocal add_count, remove_count - defn = ci_json(json_val if isinstance(json_val, dict) else json.loads(json_val)) + defn = ci_json(json_val if isinstance(json_val, dict) else parse_json_input( + json_val, "-Value for operation 'order'", "a JSON object {group, commands:[...]}")) group_name = str(defn["group"]) commands = [normalize_cmd_name(str(c)) for c in defn["commands"]] if not group_name or not commands: @@ -709,7 +733,8 @@ def main(): def do_subsystem_order(json_val): nonlocal add_count, remove_count - parsed = ci_json(json_val if isinstance(json_val, list) else json.loads(json_val)) + parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input( + json_val, "-Value for operation 'subsystem-order'", "a JSON array of subsystem paths")) subsystems = [str(s) for s in parsed] if not subsystems: print("subsystem-order requires array of subsystem paths", file=sys.stderr) @@ -734,7 +759,8 @@ def main(): def do_group_order(json_val): nonlocal add_count, remove_count - parsed = ci_json(json_val if isinstance(json_val, list) else json.loads(json_val)) + parsed = ci_json(json_val if isinstance(json_val, list) else parse_json_input( + json_val, "-Value for operation 'group-order'", "a JSON array of group names")) groups = [str(g) for g in parsed] if not groups: print("group-order requires array of group names", file=sys.stderr) @@ -764,7 +790,7 @@ def main(): if not os.path.isabs(def_file): def_file = os.path.join(os.getcwd(), def_file) with open(def_file, "r", encoding="utf-8-sig") as fh: - ops = ci_json(json.loads(fh.read())) + ops = ci_json(parse_json_input(fh.read(), def_file)) if isinstance(ops, list): operations = ops else: @@ -779,9 +805,9 @@ def main(): op_value = op.get("value", args.Value or "") if op_key == "hide": - do_hide(parse_value_list(op_value)) + do_hide(parse_value_list(op_value, op_name)) elif op_key == "show": - do_show(parse_value_list(op_value)) + do_show(parse_value_list(op_value, op_name)) elif op_key == "place": do_place(op_value) elif op_key == "order": diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1 index da9df4464..b304352ad 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.ps1 +++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1 @@ -1,4 +1,4 @@ -# meta-compile v1.95 — Compile 1C metadata object from JSON +# meta-compile v1.96 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -9,6 +9,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- 1. Load and validate JSON --- @@ -19,7 +38,7 @@ if (-not (Test-Path $JsonPath)) { } $json = Get-Content -Raw -Encoding UTF8 $JsonPath -$def = $json | ConvertFrom-Json +$def = ConvertFrom-JsonInput $json $JsonPath # --- Support guard (Ext/ParentConfigurations.bin) --- # See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" / diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py index 7dd8b4fed..4fca1b998 100644 --- a/.claude/skills/meta-compile/scripts/meta-compile.py +++ b/.claude/skills/meta-compile/scripts/meta-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-compile v1.95 — Compile 1C metadata object from JSON +# meta-compile v1.96 — Compile 1C metadata object from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -23,6 +23,29 @@ sys.stderr.reconfigure(encoding="utf-8") # молча терял свойства DSL, написанные в другом регистре. Обёртки ниже выравнивают поведение. # ============================================================ + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -377,7 +400,7 @@ if not os.path.isfile(json_path): with open(json_path, 'r', encoding='utf-8-sig') as f: json_text = f.read() -defn = ci_json(json.loads(json_text)) +defn = ci_json(parse_json_input(json_text, json_path)) assert_edit_allowed(output_dir, "editable") diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index 2ed885fd9..ff3755da3 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.ps1 +++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1 @@ -1,4 +1,4 @@ -# meta-edit v1.38 — Edit existing 1C metadata object XML +# meta-edit v1.39 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -31,6 +31,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # ============================================================ @@ -122,7 +141,7 @@ if ($DefinitionFile) { exit 1 } $jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile - $def = $jsonText | ConvertFrom-Json + $def = ConvertFrom-JsonInput $jsonText $DefinitionFile } # --- Resolve object path --- diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py index da492c045..8141e0367 100644 --- a/.claude/skills/meta-edit/scripts/meta-edit.py +++ b/.claude/skills/meta-edit/scripts/meta-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# meta-edit v1.38 — Edit existing 1C metadata object XML +# meta-edit v1.39 — Edit existing 1C metadata object XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -13,6 +13,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -3255,7 +3278,7 @@ def main(): if not os.path.exists(args.DefinitionFile): die(f"Definition file not found: {args.DefinitionFile}") with open(args.DefinitionFile, "r", encoding="utf-8-sig") as f: - definition = ci_json(json.load(f)) + definition = ci_json(parse_json_input(f.read(), args.DefinitionFile)) # --- Resolve object path --- object_path = args.ObjectPath diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index 2760ec11f..663477631 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 @@ -1,4 +1,4 @@ -# mxl-compile v1.51 — Compile 1C spreadsheet from JSON +# mxl-compile v1.52 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -9,6 +9,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Support guard (Ext/ParentConfigurations.bin) --- @@ -190,7 +209,7 @@ if (-not (Test-Path $JsonPath)) { } $json = Get-Content -Raw -Encoding UTF8 $JsonPath -$def = $json | ConvertFrom-Json +$def = ConvertFrom-JsonInput $json $JsonPath # Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина # (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index 1f7dcaf1e..ddf7d4fd7 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.py +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-compile v1.51 — Compile 1C spreadsheet from JSON +# mxl-compile v1.52 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import hashlib @@ -13,6 +13,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -805,7 +828,7 @@ def main(): sys.exit(1) with open(json_path, 'r', encoding='utf-8-sig') as f: - defn = ci_json(json.load(f)) + defn = ci_json(parse_json_input(f.read(), json_path)) # Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина # (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1 index 3dff5dc89..78a3c76a0 100644 --- a/.claude/skills/role-compile/scripts/role-compile.ps1 +++ b/.claude/skills/role-compile/scripts/role-compile.ps1 @@ -1,4 +1,4 @@ -# role-compile v1.28 — Compile 1C role from JSON +# role-compile v1.29 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -9,6 +9,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Support guard (Ext/ParentConfigurations.bin) --- @@ -150,7 +169,7 @@ if (-not (Test-Path $JsonPath)) { } $json = Get-Content -Raw -Encoding UTF8 $JsonPath -$def = $json | ConvertFrom-Json +$def = ConvertFrom-JsonInput $json $JsonPath if (-not $def.name) { Write-Error "JSON must have 'name' field (role programmatic name)" diff --git a/.claude/skills/role-compile/scripts/role-compile.py b/.claude/skills/role-compile/scripts/role-compile.py index 7d014b593..e965ce1be 100644 --- a/.claude/skills/role-compile/scripts/role-compile.py +++ b/.claude/skills/role-compile/scripts/role-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# role-compile v1.28 — Compile 1C role from JSON +# role-compile v1.29 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -13,6 +13,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -1082,7 +1105,7 @@ def main(): sys.exit(1) with open(json_path, 'r', encoding='utf-8-sig') as f: - defn = ci_json(json.load(f)) + defn = ci_json(parse_json_input(f.read(), json_path)) if not defn.get('name'): print("JSON must have 'name' field (role programmatic name)", file=sys.stderr) diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index c731b3d4f..aad8e741c 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -1,4 +1,4 @@ -# skd-compile v1.117 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# skd-compile v1.118 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -8,6 +8,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Support guard (Ext/ParentConfigurations.bin) --- @@ -161,11 +180,13 @@ if ($DefinitionFile) { exit 1 } $json = Get-Content -Raw -Encoding UTF8 $DefinitionFile + $jsonSource = $DefinitionFile } else { $json = $Value + $jsonSource = "-Value" } -$def = $json | ConvertFrom-Json +$def = ConvertFrom-JsonInput $json $jsonSource # --- Sentinel check: refuse to compile if JSON contains skd-decompile sentinels --- # These mark places the decompiler couldn't reverse cleanly; user must resolve @@ -1806,7 +1827,7 @@ while ($scanDir) { } foreach ($stylesFile in $searchPaths) { if (Test-Path $stylesFile) { - $userStyles = Get-Content -Raw -Encoding UTF8 $stylesFile | ConvertFrom-Json + $userStyles = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $stylesFile) $stylesFile foreach ($prop in $userStyles.PSObject.Properties) { $preset = @{} # Start from 'data' defaults diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index 9b6b28757..2bd1dda43 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.py +++ b/.claude/skills/skd-compile/scripts/skd-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# skd-compile v1.117 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# skd-compile v1.118 — Compile 1C DCS from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -12,6 +12,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -1629,7 +1652,7 @@ def load_user_styles(base_dir, output_path=None): for p in search_paths: if os.path.isfile(p): with open(p, 'r', encoding='utf-8-sig') as f: - user_styles = ci_json(json.load(f)) + user_styles = ci_json(parse_json_input(f.read(), p)) for name, overrides in user_styles.items(): base = dict(AREA_STYLE_PRESETS.get(name, AREA_STYLE_PRESETS['data'])) base.update(overrides) @@ -3063,10 +3086,12 @@ def main(): sys.exit(1) with open(def_file, 'r', encoding='utf-8-sig') as f: json_text = f.read() + json_source = def_file else: json_text = args.Value + json_source = "-Value" - defn = ci_json(json.loads(json_text)) + defn = ci_json(parse_json_input(json_text, json_source)) if not defn.get('dataSets') or len(defn['dataSets']) == 0: print("JSON must have at least one entry in 'dataSets'", file=sys.stderr) diff --git a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 index daf3b8c7d..9a8f4a097 100644 --- a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 +++ b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 @@ -1,4 +1,4 @@ -# skd-decompile v0.92 — Decompile 1C DCS Template.xml to JSON DSL (draft) +# skd-decompile v0.93 — Decompile 1C DCS Template.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -9,6 +9,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- 0. Resolve and validate input --- @@ -1140,7 +1159,7 @@ function Load-UserStyles { if (-not $dirPath) { return } $stylesPath = Join-Path $dirPath 'skd-styles.json' if (-not (Test-Path $stylesPath)) { return } - $raw = Get-Content -Raw -Encoding UTF8 $stylesPath | ConvertFrom-Json + $raw = ConvertFrom-JsonInput (Get-Content -Raw -Encoding UTF8 $stylesPath) $stylesPath $script:existingUserPresetsRaw = $raw foreach ($prop in $raw.PSObject.Properties) { # Compile-логика: data defaults → built-in if name match → user keys diff --git a/.claude/skills/skd-decompile/scripts/skd-decompile.py b/.claude/skills/skd-decompile/scripts/skd-decompile.py index 723c92811..e8b78b8e2 100644 --- a/.claude/skills/skd-decompile/scripts/skd-decompile.py +++ b/.claude/skills/skd-decompile/scripts/skd-decompile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# skd-decompile v0.92 — Decompile 1C DCS Template.xml to JSON DSL (draft) +# skd-decompile v0.93 — Decompile 1C DCS Template.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -9,6 +9,29 @@ import xml.etree.ElementTree as ET # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + def ci_parse_args(parser, argv=None): """parse_args по правилам PS: имена параметров и значения choices регистронезависимы.""" argv = list(sys.argv[1:] if argv is None else argv) @@ -1362,9 +1385,8 @@ def load_user_styles(dir_path): styles_path = os.path.join(dir_path, 'skd-styles.json') if not os.path.exists(styles_path): return - import json as _json with open(styles_path, 'r', encoding='utf-8-sig') as f: - raw = _json.load(f) + raw = parse_json_input(f.read(), styles_path) existing_user_presets_raw = raw for prop_name, prop_value in raw.items(): preset = {} diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 index e5f8ff14c..b21e59deb 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 @@ -1,4 +1,4 @@ -# subsystem-compile v1.26 — Create 1C subsystem from JSON definition +# subsystem-compile v1.27 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -9,6 +9,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- 1. Load JSON --- @@ -30,11 +49,13 @@ if ($DefinitionFile) { exit 1 } $json = Get-Content -Raw -Encoding UTF8 $DefinitionFile + $jsonSource = $DefinitionFile } else { $json = $Value + $jsonSource = "-Value" } -$def = $json | ConvertFrom-Json +$def = ConvertFrom-JsonInput $json $jsonSource if (-not $def.name) { Write-Error "JSON must have 'name' field" diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py index 69706af1e..5ed621ea2 100644 --- a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py +++ b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# subsystem-compile v1.26 — Create 1C subsystem from JSON definition +# subsystem-compile v1.27 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -13,6 +13,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -470,10 +493,12 @@ def main(): sys.exit(1) with open(def_file, 'r', encoding='utf-8-sig') as f: json_text = f.read() + json_source = def_file else: json_text = args.Value + json_source = "-Value" - defn = ci_json(json.loads(json_text)) + defn = ci_json(parse_json_input(json_text, json_source)) if not defn.get('name'): print("JSON must have 'name' field", file=sys.stderr) diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 b/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 index a6ca16f1f..476ac16c6 100644 --- a/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 +++ b/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 @@ -1,4 +1,4 @@ -# subsystem-edit v1.21 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) +# subsystem-edit v1.22 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, @@ -10,6 +10,25 @@ param( ) $ErrorActionPreference = "Stop" + +# --- Разбор пользовательского JSON --- +# Одна строка в stderr вместо дампа исключения ConvertFrom-Json (issue #80): агент по стектрейсу +# идёт чинить скрипт, а не свой вызов. $source — файл или параметр. $expected заполняем только +# для полиморфного входа: у файла подсказка была бы наполнителем. +# Возврат через -NoEnumerate: без него одноэлементный +# JSON-массив разворачивался бы в скаляр вторым анруллингом. +function ConvertFrom-JsonInput([string]$text, [string]$source, [string]$expected) { + try { + $parsed = $text | ConvertFrom-Json + } catch { + $what = if ($expected) { "$source expects $expected" } else { "Invalid JSON in $source" } + $got = ($text -replace '\s+', ' ').Trim() + if ($got.Length -gt 60) { $got = $got.Substring(0, 60) + '...' } + [Console]::Error.WriteLine("[ERROR] ${what}, got: ${got} ($($_.Exception.Message))") + exit 1 + } + Write-Output -NoEnumerate $parsed +} [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- Content type normalization (plural→singular, Russian→English) --- @@ -429,10 +448,10 @@ function Expand-SelfClosingElement($container, $parentIndent) { } # --- Parse value: string or JSON array --- -function Parse-ValueList([string]$val) { +function Parse-ValueList([string]$val, [string]$opName) { $val = $val.Trim() if ($val.StartsWith("[")) { - $arr = $val | ConvertFrom-Json + $arr = ConvertFrom-JsonInput $val "-Value for operation '$opName'" "a JSON array of object names" $result = @(); foreach ($item in $arr) { $result += "$item" } return ,$result } @@ -565,7 +584,7 @@ function Do-RemoveChild([string]$childName) { } function Do-SetProperty([string]$jsonVal) { - $propDef = $jsonVal | ConvertFrom-Json + $propDef = ConvertFrom-JsonInput $jsonVal "-Value for operation 'set-property'" "a JSON object {name, value}" $propName = "$($propDef.name)" $propValue = "$($propDef.value)" @@ -639,7 +658,7 @@ if ($DefinitionFile) { $DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile } $jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile - $ops = $jsonText | ConvertFrom-Json + $ops = ConvertFrom-JsonInput $jsonText $DefinitionFile if ($ops -is [System.Array]) { foreach ($op in $ops) { $operations += $op } } else { @@ -654,8 +673,8 @@ foreach ($op in $operations) { $opValue = if ($op.value) { "$($op.value)" } else { "$Value" } switch ($opName) { - "add-content" { Do-AddContent (Parse-ValueList $opValue) } - "remove-content" { Do-RemoveContent (Parse-ValueList $opValue) } + "add-content" { Do-AddContent (Parse-ValueList $opValue $opName) } + "remove-content" { Do-RemoveContent (Parse-ValueList $opValue $opName) } "add-child" { Do-AddChild $opValue } "remove-child" { Do-RemoveChild $opValue } "set-property" { Do-SetProperty $opValue } diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.py b/.claude/skills/subsystem-edit/scripts/subsystem-edit.py index 03d8d97c2..88cc352cb 100644 --- a/.claude/skills/subsystem-edit/scripts/subsystem-edit.py +++ b/.claude/skills/subsystem-edit/scripts/subsystem-edit.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# subsystem-edit v1.21 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) +# subsystem-edit v1.22 — Edit existing 1C subsystem XML (+тип Bot; cfe-diff/cfe-borrow: недостающие типы) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -13,6 +13,29 @@ from lxml import etree # Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet] # регистр не различают, в argparse совпадение точное. + +def parse_json_input(text, source, expected=None): + """Разбор пользовательского JSON: одна строка в stderr вместо traceback (issue #80). + + expected заполняем только для полиморфного входа: у файла подсказка + была бы наполнителем — имя файла и текст парсера самодостаточны. + + Импорты внутри тела: копия функции живёт в навыках с разными именами модулей + (skd-decompile импортирует json локально как _json), а тело обязано быть одинаковым. + """ + import json as _pj + import sys as _psys + try: + return _pj.loads(text) + except ValueError as exc: + what = "%s expects %s" % (source, expected) if expected else "Invalid JSON in %s" % source + got = " ".join(str(text).split()) + if len(got) > 60: + got = got[:60] + "..." + print("[ERROR] %s, got: %s (%s)" % (what, got, exc), file=_psys.stderr) + _psys.exit(1) + + class CIDict(dict): # Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные # реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки @@ -515,11 +538,11 @@ def import_fragment(xml_string, doc_root): return nodes -def parse_value_list(val): +def parse_value_list(val, op_name): """Parse a string or JSON array into a list of strings.""" val = val.strip() if val.startswith("["): - arr = ci_json(json.loads(val)) + arr = ci_json(parse_json_input(val, "-Value for operation '%s'" % op_name, "a JSON array of object names")) return [str(item) for item in arr] return [val] @@ -777,7 +800,8 @@ def main(): def do_set_property(json_val): nonlocal modify_count - prop_def = ci_json(json.loads(json_val)) + prop_def = ci_json(parse_json_input( + json_val, "-Value for operation 'set-property'", "a JSON object {name, value}")) prop_name = str(prop_def["name"]) prop_value = str(prop_def.get("value", "")) @@ -874,7 +898,7 @@ def main(): if not os.path.isabs(def_file): def_file = os.path.join(os.getcwd(), def_file) with open(def_file, "r", encoding="utf-8-sig") as fh: - ops = ci_json(json.loads(fh.read())) + ops = ci_json(parse_json_input(fh.read(), def_file)) if isinstance(ops, list): operations = ops else: @@ -889,9 +913,9 @@ def main(): op_value = op.get("value", args.Value or "") if op_key == "add-content": - do_add_content(parse_value_list(op_value)) + do_add_content(parse_value_list(op_value, op_name)) elif op_key == "remove-content": - do_remove_content(parse_value_list(op_value)) + do_remove_content(parse_value_list(op_value, op_name)) elif op_key == "add-child": do_add_child(op_value) elif op_key == "remove-child": diff --git a/tests/skills/README.md b/tests/skills/README.md index f32c0ef1f..ab91cfbd6 100644 --- a/tests/skills/README.md +++ b/tests/skills/README.md @@ -295,6 +295,7 @@ ibcmd-проход автоматически `○ skipped`, если рядом |---|---|---| | `name` | да | Название теста (отображается в отчёте) | | `input` | нет | JSON-объект, передаётся навыку через temp-файл | +| `inputRaw` | нет | Строка, пишется во входной файл дословно. Нужна для негативных кейсов про битый JSON: через `input` такой вход невыразим — `JSON.stringify` всегда даёт валидный документ. Приоритетнее `input` | | `params` | нет | Параметры для `case.` и `workPath` маппинга | | `setup` | нет | Переопределение setup из `_skill.json` | | `outputPath` | нет | Относительный путь для навыков с `-OutputPath` | diff --git a/tests/skills/cases/cf-edit/bad-value-json.json b/tests/skills/cases/cf-edit/bad-value-json.json new file mode 100644 index 000000000..a1a140610 --- /dev/null +++ b/tests/skills/cases/cf-edit/bad-value-json.json @@ -0,0 +1,10 @@ +{ + "name": "Строка вместо JSON у set-panels: ожидаемая форма в сообщении (issue #80)", + "input": [ + { + "operation": "set-panels", + "value": "Левая панель" + } + ], + "expectError": "-Value for operation 'set-panels' expects a JSON object with panel layout" +} diff --git a/tests/skills/cases/interface-edit/bad-definition-file.json b/tests/skills/cases/interface-edit/bad-definition-file.json new file mode 100644 index 000000000..77e501a41 --- /dev/null +++ b/tests/skills/cases/interface-edit/bad-definition-file.json @@ -0,0 +1,23 @@ +{ + "name": "Битый JSON в файле определения: в сообщении есть имя файла (issue #80)", + "preRun": [ + { + "script": "subsystem-compile/scripts/subsystem-compile", + "input": { + "name": "Продажи" + }, + "args": { + "-DefinitionFile": "{inputFile}", + "-OutputDir": "{workDir}" + } + } + ], + "params": { + "ciPath": "Subsystems/Продажи/CommandInterface" + }, + "inputRaw": "{ \"operation\": \"hide\",", + "expectError": "Invalid JSON in", + "expect": { + "stderrContains": "__input.json" + } +} diff --git a/tests/skills/cases/interface-edit/bad-value-json.json b/tests/skills/cases/interface-edit/bad-value-json.json new file mode 100644 index 000000000..89d7fbc5c --- /dev/null +++ b/tests/skills/cases/interface-edit/bad-value-json.json @@ -0,0 +1,25 @@ +{ + "name": "Строка вместо JSON у order: сообщение с ожидаемой формой, а не стектрейс (issue #80)", + "preRun": [ + { + "script": "subsystem-compile/scripts/subsystem-compile", + "input": { + "name": "Продажи" + }, + "args": { + "-DefinitionFile": "{inputFile}", + "-OutputDir": "{workDir}" + } + } + ], + "params": { + "ciPath": "Subsystems/Продажи/CommandInterface" + }, + "input": [ + { + "operation": "order", + "value": "Catalog.Товары" + } + ], + "expectError": "-Value for operation 'order' expects a JSON object {group, commands:[...]}" +} diff --git a/tests/skills/cases/interface-edit/group-order-single.json b/tests/skills/cases/interface-edit/group-order-single.json new file mode 100644 index 000000000..4063badc1 --- /dev/null +++ b/tests/skills/cases/interface-edit/group-order-single.json @@ -0,0 +1,27 @@ +{ + "name": "group-order из одного элемента: массив не разворачивается в скаляр", + "preRun": [ + { + "script": "subsystem-compile/scripts/subsystem-compile", + "input": { + "name": "Продажи" + }, + "args": { + "-DefinitionFile": "{inputFile}", + "-OutputDir": "{workDir}" + } + } + ], + "params": { + "ciPath": "Subsystems/Продажи/CommandInterface" + }, + "input": [ + { + "operation": "group-order", + "value": "[\"NavigationPanel.Important\"]" + } + ], + "expect": { + "stdoutContains": "Set group order: 1 entries" + } +} diff --git a/tests/skills/cases/interface-edit/snapshots/group-order-single/Configuration.xml b/tests/skills/cases/interface-edit/snapshots/group-order-single/Configuration.xml new file mode 100644 index 000000000..212eec45d --- /dev/null +++ b/tests/skills/cases/interface-edit/snapshots/group-order-single/Configuration.xml @@ -0,0 +1,252 @@ + + + + + + UUID-002 + UUID-003 + + + UUID-004 + UUID-005 + + + UUID-006 + UUID-007 + + + UUID-008 + UUID-009 + + + UUID-010 + UUID-011 + + + UUID-012 + UUID-013 + + + UUID-014 + UUID-015 + + + + TestConfig + + + ru + TestConfig + + + + + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + + + + false + false + false + + + + + + + + + + + + + + + + + + + + + + Biometrics + true + + + Location + false + + + BackgroundLocation + false + + + BluetoothPrinters + false + + + WiFiPrinters + false + + + Contacts + false + + + Calendars + false + + + PushNotifications + false + + + LocalNotifications + false + + + InAppPurchases + false + + + PersonalComputerFileExchange + false + + + Ads + false + + + NumberDialing + false + + + CallProcessing + false + + + CallLog + false + + + AutoSendSMS + false + + + ReceiveSMS + false + + + SMSLog + false + + + Camera + false + + + Microphone + false + + + MusicLibrary + false + + + PictureAndVideoLibraries + false + + + AudioPlaybackAndVibration + false + + + BackgroundAudioPlaybackAndVibration + false + + + InstallPackages + false + + + OSBackup + true + + + ApplicationUsageStatistics + false + + + BarcodeScanning + false + + + BackgroundAudioRecording + false + + + AllFilesAccess + false + + + Videoconferences + false + + + NFC + false + + + DocumentScanning + false + + + SpeechToText + false + + + Geofences + false + + + IncomingShareRequests + false + + + AllIncomingShareRequestsTypesProcessing + false + + + + + + Normal + + + Language.Русский + + + + + + Managed + NotAutoFree + DontUse + DontUse + TaxiEnableVersion8_2 + DontUse + Version8_3_24 + + + + Русский + Продажи + + + \ No newline at end of file diff --git a/tests/skills/cases/interface-edit/snapshots/group-order-single/Ext/ClientApplicationInterface.xml b/tests/skills/cases/interface-edit/snapshots/group-order-single/Ext/ClientApplicationInterface.xml new file mode 100644 index 000000000..3c1161b2d --- /dev/null +++ b/tests/skills/cases/interface-edit/snapshots/group-order-single/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/interface-edit/snapshots/group-order-single/Languages/Русский.xml b/tests/skills/cases/interface-edit/snapshots/group-order-single/Languages/Русский.xml new file mode 100644 index 000000000..37c60d786 --- /dev/null +++ b/tests/skills/cases/interface-edit/snapshots/group-order-single/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи.xml b/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи.xml new file mode 100644 index 000000000..68a6298dc --- /dev/null +++ b/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи.xml @@ -0,0 +1,22 @@ + + + + + Продажи + + + ru + Продажи + + + + true + true + false + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи/CommandInterface b/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи/CommandInterface new file mode 100644 index 000000000..e0c5e1106 --- /dev/null +++ b/tests/skills/cases/interface-edit/snapshots/group-order-single/Subsystems/Продажи/CommandInterface @@ -0,0 +1,6 @@ + + + + NavigationPanel.Important + + \ No newline at end of file diff --git a/tests/skills/cases/subsystem-compile/bad-definition-file.json b/tests/skills/cases/subsystem-compile/bad-definition-file.json new file mode 100644 index 000000000..00d7d9ec2 --- /dev/null +++ b/tests/skills/cases/subsystem-compile/bad-definition-file.json @@ -0,0 +1,11 @@ +{ + "name": "Битый JSON в файле определения: сообщение с именем файла, а не стектрейс (issue #80)", + "inputRaw": "{ \"name\": \"Сломано\",", + "expectError": "Invalid JSON in", + "expect": { + "stderrContains": "__input.json", + "filesAbsent": [ + "Subsystems" + ] + } +} diff --git a/tests/skills/check-inline-drift.mjs b/tests/skills/check-inline-drift.mjs index bd2b60bc8..f9c9b4216 100644 --- a/tests/skills/check-inline-drift.mjs +++ b/tests/skills/check-inline-drift.mjs @@ -394,6 +394,19 @@ const FAMILIES = [ ], }, + // ─── Разбор пользовательского JSON ───────────────────────────────────── + // Сообщение об ошибке разбора одинаково во всех навыках (issue #80): стектрейс парсера + // агент читает как «скрипт сломан» и идёт чинить не то место. Вся специфика навыка — + // в аргументах source/expected на месте вызова, тело функции общее. + { + name: 'parse_json_input', py: 'parse_json_input', ps1: 'ConvertFrom-JsonInput', + variants: [ + { id: 'base', authority: 'interface-edit', + consumers: ['cf-edit', 'form-compile', 'form-edit', 'meta-compile', 'meta-edit', 'mxl-compile', + 'role-compile', 'skd-compile', 'skd-decompile', 'subsystem-compile', 'subsystem-edit'] }, + ], + }, + ]; // ─── Семьи, разъехавшиеся целиком ─────────────────────────────────────────── diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index c097a82e5..29f7d549d 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -799,7 +799,12 @@ async function runCaseAsync(testCase, opts) { } // Write input - if (caseData.input !== undefined) { + // inputRaw пишется дословно: негативный кейс про битый JSON через case.input невыразим — + // JSON.stringify всегда даёт валидный документ. + if (caseData.inputRaw !== undefined) { + inputFile = join(workDir, '__input.json'); + writeFileSync(inputFile, caseData.inputRaw, 'utf8'); + } else if (caseData.input !== undefined) { inputFile = join(workDir, '__input.json'); writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8'); } @@ -1020,8 +1025,11 @@ function runCase(testCase, opts) { } } - // 3. Write input JSON if needed - if (caseData.input !== undefined) { + // 3. Write input JSON if needed (inputRaw — дословно, см. выше) + if (caseData.inputRaw !== undefined) { + inputFile = join(workDir, '__input.json'); + writeFileSync(inputFile, caseData.inputRaw, 'utf8'); + } else if (caseData.input !== undefined) { inputFile = join(workDir, '__input.json'); writeFileSync(inputFile, JSON.stringify(caseData.input, null, 2), 'utf8'); }