diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 index db8ce08a..aa94d7d6 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.ps1 @@ -1,4 +1,4 @@ -# cfe-borrow v1.15 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.16 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][string]$ExtensionPath, @@ -388,6 +388,20 @@ $script:formatVersion = Detect-FormatVersion $extDir # --- 8. Namespaces declaration for object XML --- $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $script:formatVersion) -ge 221) { + $script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + # --- 9. Parse -Object into items --- $items = @() foreach ($part in $Object.Split(";;")) { @@ -824,11 +838,22 @@ function Borrow-Form { } } - # Extract the
opening tag from source text (preserves namespace declarations) + # Открывающий тег берём из исходной формы — ради её объявлений пространств имён, + # но version подставляем СВОЮ: форма обязана нести версию расширения, иначе платформа + # отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег копировался целиком, + # и версия источника молча побеждала. $xmlDecl = '' $formTag = "" if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] } - if ($srcFormContent -match '(]*>)') { $formTag = $Matches[1] } + if ($srcFormContent -match '(]*>)') { + $srcTag = $Matches[1] + $srcNs = $srcTag -replace '^$', '' -replace '\s*version="[^"]*"', '' + # 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style. + if ((Get-FormatRank $formVersion) -ge 221 -and $srcNs -notmatch 'xmlns:pal=') { + $srcNs = $srcNs -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' + } + $formTag = if ($srcNs) { "" } else { "" } + } # Build output Form.xml $formXmlSb = New-Object System.Text.StringBuilder diff --git a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py index 5383948e..a4a4f03d 100644 --- a/.claude/skills/cfe-borrow/scripts/cfe-borrow.py +++ b/.claude/skills/cfe-borrow/scripts/cfe-borrow.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# cfe-borrow v1.15 — Borrow objects from configuration into extension (CFE) +# cfe-borrow v1.16 — Borrow objects from configuration into extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -323,6 +323,23 @@ def detect_format_version(d): return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + +def apply_pal_ns(format_version): + """2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + дописать в конец нельзя.""" + global XMLNS_DECL + if format_rank(format_version) >= 221: + XMLNS_DECL = XMLNS_DECL.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + + def get_child_indent(container): if container.text and "\n" in container.text: after_nl = container.text.rsplit("\n", 1)[-1] @@ -491,6 +508,7 @@ def main(): cfg_dir = os.path.dirname(cfg_resolved) format_version = detect_format_version(ext_dir) + apply_pal_ns(format_version) # --- 2. Load extension Configuration.xml --- xml_parser = etree.XMLParser(remove_blank_text=False) @@ -1509,7 +1527,10 @@ def main(): else: warn(f" Enum.{enum_name} not found in source config") - # Extract the opening tag from source text + # Открывающий тег берём из исходной формы — ради её объявлений пространств + # имён, но version подставляем СВОЮ: форма обязана нести версию расширения, иначе + # платформа отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег + # копировался целиком, и версия источника молча побеждала. xml_decl = '' form_tag = f'' m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content) @@ -1517,7 +1538,15 @@ def main(): xml_decl = m_decl.group(1) m_tag = re.search(r'(]*>)', src_form_content) if m_tag: - form_tag = m_tag.group(1) + src_ns = re.sub(r'^$', '', src_ns) + src_ns = re.sub(r'\s*version="[^"]*"', '', src_ns) + # 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style. + if format_rank(form_version) >= 221 and 'xmlns:pal=' not in src_ns: + src_ns = src_ns.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + form_tag = f'' if src_ns else f'' # Build output parts = [] diff --git a/.claude/skills/cfe-init/scripts/cfe-init.ps1 b/.claude/skills/cfe-init/scripts/cfe-init.ps1 index 07fc791a..e8c4dfa7 100644 --- a/.claude/skills/cfe-init/scripts/cfe-init.ps1 +++ b/.claude/skills/cfe-init/scripts/cfe-init.ps1 @@ -1,4 +1,4 @@ -# cfe-init v1.5 — Create 1C configuration extension scaffold (CFE) +# cfe-init v1.6 — Create 1C configuration extension scaffold (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -151,6 +151,20 @@ $childObjectsXml += "`r`n`t`t" # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $formatVersion) -ge 221) { + $xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + # --- Configuration.xml --- $cfgXml = @" diff --git a/.claude/skills/cfe-init/scripts/cfe-init.py b/.claude/skills/cfe-init/scripts/cfe-init.py index cb4252c3..2d328d9b 100644 --- a/.claude/skills/cfe-init/scripts/cfe-init.py +++ b/.claude/skills/cfe-init/scripts/cfe-init.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -# cfe-init v1.5 — Create 1C configuration extension scaffold (CFE) +# cfe-init v1.6 — Create 1C configuration extension scaffold (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills """Generates minimal XML source files for a 1C configuration extension.""" -import sys, os, argparse, uuid +import sys, os, re, argparse, uuid from xml.etree import ElementTree as ET def esc_xml(s): @@ -25,6 +25,12 @@ def write_xml_file(path, content): write_utf8_bom(path, text) +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def main(): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") @@ -193,6 +199,14 @@ def main(): ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ) + + # 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + # Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + # дописать в конец нельзя. + if format_rank(format_version) >= 221: + xmlns_decl = xmlns_decl.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') for i in range(7): contained_objects += f"""\t\t\t \t\t\t\t{class_ids[i]} diff --git a/.claude/skills/form-add/scripts/form-add.ps1 b/.claude/skills/form-add/scripts/form-add.ps1 index f720d744..3c890f23 100644 --- a/.claude/skills/form-add/scripts/form-add.ps1 +++ b/.claude/skills/form-add/scripts/form-add.ps1 @@ -1,4 +1,4 @@ -# form-add v1.20 — Add managed form to 1C config object +# form-add v1.21 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -169,6 +169,13 @@ function Detect-FormatVersion([string]$dir) { return "2.17" } +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + # --- Фаза 1: Определение типа объекта --- # Resolve ObjectPath (directory → .xml) @@ -197,6 +204,14 @@ $script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Pa $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' $script:formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $script:formatVersion) -ge 221) { + $script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' + $script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + $xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc.PreserveWhitespace = $true $xmlDoc.Load($objectXmlFull.Path) diff --git a/.claude/skills/form-add/scripts/form-add.py b/.claude/skills/form-add/scripts/form-add.py index f676bbe8..fb873168 100644 --- a/.claude/skills/form-add/scripts/form-add.py +++ b/.claude/skills/form-add/scripts/form-add.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-add v1.20 — Add managed form to 1C config object +# form-add v1.21 — Add managed form to 1C config object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -210,6 +210,12 @@ def detect_format_version(d): return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def _detect_xml_style(path): """Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding / финальный перенос. None → файл новый (сохранить текущее поведение).""" @@ -356,6 +362,14 @@ def main(): ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ) + # 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + # Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + # дописать в конец нельзя. + if format_rank(format_version) >= 221: + pal = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' + xmlns_decl = xmlns_decl.replace(' xmlns:style=', pal) + form_ns_decl = form_ns_decl.replace(' xmlns:style=', pal) + parser_xml = etree.XMLParser(remove_blank_text=False) tree = etree.parse(object_xml_full, parser_xml) root = tree.getroot() diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 5d027b69..cc4fbfca 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.183 — Compile 1C managed form from JSON or object metadata +# form-compile v1.184 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -1350,6 +1350,13 @@ function Detect-FormatVersion([string]$dir) { return "2.17" } +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + # --- Support guard (Ext/ParentConfigurations.bin) --- # See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" / # read-only configs unless allowed. Trigger = bin present; reaction from @@ -1489,6 +1496,13 @@ $script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $script:formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $script:formatVersion) -ge 221) { + $script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + # --- 0. Path normalization and mode dispatch --- # Form name → purpose mapping diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 5d29cf78..c3de992e 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.183 — Compile 1C managed form from JSON or object metadata +# form-compile v1.184 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -6075,6 +6075,12 @@ def detect_format_version(d): return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def _normalize_elements(defn): """Convert dict-style elements from --from-object generators to list-style expected by compiler. Generator format: elements = {"ИмяЭлемента": {"element": "input", "path": "..."}, ...} @@ -6236,6 +6242,14 @@ def main(): ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ) + # 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + # Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + # дописать в конец нельзя. + if format_rank(format_version) >= 221: + form_ns_decl = form_ns_decl.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + # --- 0. From-object mode --- if args.FromObject: # Resolve object path and purpose from OutputPath convention: diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1 index 239b389c..be8d5f77 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.16 — Compile 1C role from JSON +# role-compile v1.17 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -659,7 +659,14 @@ function Detect-FormatVersion([string]$dir) { return "2.17" } -$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +$resolvedOutputDir =if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } Assert-EditAllowed $resolvedOutputDir 'editable' $formatVersion = Detect-FormatVersion $resolvedOutputDir @@ -678,6 +685,11 @@ X ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"' X ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"' X ' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"' X ' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"' +# 2.21 (8.5) добавила в шапку пространство палитры. Место строгое — после lf, перед style: +# платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт (проверено по выгрузке 8.5). +if ((Get-FormatRank $formatVersion) -ge 221) { + X ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"' +} X ' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"' X ' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"' X ' xmlns:v8="http://v8.1c.ru/8.1/data/core"' diff --git a/.claude/skills/role-compile/scripts/role-compile.py b/.claude/skills/role-compile/scripts/role-compile.py index f8d78a13..b542b198 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.16 — Compile 1C role from JSON +# role-compile v1.17 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -203,6 +203,12 @@ def detect_format_version(d): return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def detect_eol(text): # Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам, # которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47). @@ -687,6 +693,10 @@ def main(): lines.append(' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"') lines.append(' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"') lines.append(' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"') + # 2.21 (8.5) добавила в шапку пространство палитры. Место строгое — после lf, перед style: + # платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт (проверено по выгрузке 8.5). + if format_rank(format_version) >= 221: + lines.append(' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"') lines.append(' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"') lines.append(' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"') lines.append(' xmlns:v8="http://v8.1c.ru/8.1/data/core"') diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 b/.claude/skills/subsystem-compile/scripts/subsystem-compile.ps1 index 3b6746ec..35bcd53e 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.18 — Create 1C subsystem from JSON definition +# subsystem-compile v1.19 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -451,6 +451,20 @@ $formatVersion = Detect-FormatVersion $OutputDir # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $formatVersion) -ge 221) { + $script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + # --- 4. Build XML --- $uuid = New-Guid-String $indent = "`t`t`t" diff --git a/.claude/skills/subsystem-compile/scripts/subsystem-compile.py b/.claude/skills/subsystem-compile/scripts/subsystem-compile.py index 5dcf1432..a725fa50 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.18 — Create 1C subsystem from JSON definition +# subsystem-compile v1.19 — Create 1C subsystem from JSON definition # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -203,6 +203,12 @@ def detect_format_version(d): d = parent return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def detect_eol(text): # Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам, @@ -271,6 +277,17 @@ XMLNS_DECL = ( ) +def apply_pal_ns(format_version): + """2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + дописать в конец нельзя.""" + global XMLNS_DECL + if format_rank(format_version) >= 221: + XMLNS_DECL = XMLNS_DECL.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + + def write_child_subsystem_stub(child_path, child_name, format_version): child_uuid = new_uuid() lines = [] @@ -422,6 +439,7 @@ def main(): return f'{type_part}.{name_part}' format_version = detect_format_version(output_dir) + apply_pal_ns(format_version) xmlns_decl = XMLNS_DECL # --- 3. Resolve defaults --- diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 b/.claude/skills/subsystem-edit/scripts/subsystem-edit.ps1 index 87c6da6f..ebab6572 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.14 — Edit existing 1C subsystem XML +# subsystem-edit v1.15 — Edit existing 1C subsystem XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, @@ -265,6 +265,20 @@ if (-not $script:formatVersion) { $script:formatVersion = "2.17" } # Объявления пространств имён — одной переменной: место эмиссии её только интерполирует. # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' + +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $script:formatVersion) -ge 221) { + $script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} $script:utf8Bom = New-Object System.Text.UTF8Encoding($true) $script:addCount = 0 diff --git a/.claude/skills/subsystem-edit/scripts/subsystem-edit.py b/.claude/skills/subsystem-edit/scripts/subsystem-edit.py index dd3943b8..2341f2fa 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.14 — Edit existing 1C subsystem XML +# subsystem-edit v1.15 — Edit existing 1C subsystem XML # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -226,6 +226,23 @@ XMLNS_DECL = ( ) +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + +def apply_pal_ns(format_version): + """2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + дописать в конец нельзя.""" + global XMLNS_DECL + if format_rank(format_version) >= 221: + XMLNS_DECL = XMLNS_DECL.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + + def write_child_subsystem_stub(child_path, child_name, format_version): child_uuid = new_uuid() lines = [] @@ -543,6 +560,7 @@ def main(): tree = etree.parse(resolved_path, xml_parser) xml_root = tree.getroot() format_version = xml_root.get("version") or "2.17" + apply_pal_ns(format_version) add_count = 0 remove_count = 0 diff --git a/.claude/skills/template-add/scripts/add-template.ps1 b/.claude/skills/template-add/scripts/add-template.ps1 index d91937ca..9cfe00bb 100644 --- a/.claude/skills/template-add/scripts/add-template.ps1 +++ b/.claude/skills/template-add/scripts/add-template.ps1 @@ -1,4 +1,4 @@ -# template-add v1.19 — Add template to 1C object +# template-add v1.20 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -241,6 +241,20 @@ $formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $formatVersion) -ge 221) { + $xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + # --- 1. Метаданные макета (Templates/.xml) --- $templateUuid = [guid]::NewGuid().ToString() diff --git a/.claude/skills/template-add/scripts/add-template.py b/.claude/skills/template-add/scripts/add-template.py index 86621191..a52ef0ed 100644 --- a/.claude/skills/template-add/scripts/add-template.py +++ b/.claude/skills/template-add/scripts/add-template.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# template-add v1.19 — Add template to 1C object +# template-add v1.20 — Add template to 1C object # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -284,6 +284,12 @@ def detect_format_version(d): d = parent return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def main(): sys.stdout.reconfigure(encoding="utf-8") @@ -331,6 +337,14 @@ def main(): ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ) + # 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. + # Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, + # дописать в конец нельзя. + if format_rank(format_version) >= 221: + xmlns_decl = xmlns_decl.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + # --- Checks --- object_type_folders = [ diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 index 83525f03..ddbffff6 100644 --- a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 @@ -1,4 +1,4 @@ -# xdto-compile v1.7 — Build a 1C XDTO package from an XML Schema (XSD) +# xdto-compile v1.8 — Build a 1C XDTO package from an XML Schema (XSD) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory=$true, ParameterSetName='File')] @@ -838,6 +838,20 @@ $script:formatVersion = Detect-FormatVersion $OutputDir # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте. $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209. +# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка. +function Get-FormatRank([string]$ver) { + if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] } + return 0 +} + +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if ((Get-FormatRank $script:formatVersion) -ge 221) { + $script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' +} + $pkgRoot = Join-Path $OutputDir "XDTOPackages" $pkgDir = Join-Path $pkgRoot $Name $extDir = Join-Path $pkgDir "Ext" diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.py b/.claude/skills/xdto-compile/scripts/xdto-compile.py index a6018ecf..7911d1d9 100644 --- a/.claude/skills/xdto-compile/scripts/xdto-compile.py +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.py @@ -1,4 +1,4 @@ -# xdto-compile v1.7 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) +# xdto-compile v1.8 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -119,6 +119,12 @@ def detect_format_version(d): d = parent return "2.17" +def format_rank(ver): + """"2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17").""" + m = re.match(r'^(\d+)\.(\d+)$', ver or '') + return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 + + def assert_edit_allowed(target_path): d = os.path.abspath(target_path) @@ -883,6 +889,14 @@ xmlns_decl = ( ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ) +# 2.21 (8.5) добавила в шапку пространство палитры — ради у значений перечисления. +# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту, +# дописать в конец нельзя. +if format_rank(format_version) >= 221: + xmlns_decl = xmlns_decl.replace( + ' xmlns:style=', + ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=') + pkg_root = os.path.join(args.OutputDir, "XDTOPackages") pkg_dir = os.path.join(pkg_root, name) ext_dir = os.path.join(pkg_dir, "Ext") diff --git a/tests/skills/cases/skd-compile/horizontal-merge.json b/tests/skills/cases/skd-compile/horizontal-merge.json index 2fdcadf6..05372b52 100644 --- a/tests/skills/cases/skd-compile/horizontal-merge.json +++ b/tests/skills/cases/skd-compile/horizontal-merge.json @@ -37,11 +37,14 @@ "validatePath": "Template.xml", "expect": { "files": ["Template.xml"], - "contains": [ - "ОбъединятьПоГоризонтали", - "ОбъединятьПоВертикали", - "Поступление", - "Выбытие" - ] + "fileContains": { + "file": "Template.xml", + "text": [ + "ОбъединятьПоГоризонтали", + "ОбъединятьПоВертикали", + "Поступление", + "Выбытие" + ] + } } } diff --git a/tests/skills/cases/skd-edit/add-selection-auto-dedup.json b/tests/skills/cases/skd-edit/add-selection-auto-dedup.json index 2a6e4749..9688a750 100644 --- a/tests/skills/cases/skd-edit/add-selection-auto-dedup.json +++ b/tests/skills/cases/skd-edit/add-selection-auto-dedup.json @@ -26,6 +26,6 @@ "value": "Auto ;; Поле1 ;; Поле2" }, "expect": { - "stdout": "WARN.*SelectedItemAuto already exists" + "stdoutContains": "[WARN] SelectedItemAuto already exists in variant \"Основной\"" } } diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index 6256b073..d8ff7df7 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -311,21 +311,18 @@ const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi // was about (PS wrote `` plus a trailing newline, python wrote `` without one), // so port parity was being checked through the mask. Do not bring them back: the byte // canon itself is now asserted per case via `preserves`. -function normalizeXmlContent(text, opts = {}) { +// +// Четвёртый шаг — вырезание объявлений xmlns — снят по той же причине: он прятал +// целый класс расхождений (лишнее/недостающее объявление в шапке, как xmlns:pal +// формата 2.21) и к моменту снятия был мёртвым — ни один кейс на него не опирался. +function normalizeXmlContent(text) { let s = text; // 1. XML declaration: normalize quotes and encoding case s = s.replace( /<\?xml\s+version=['"]1\.0['"]\s+encoding=['"]([^'"]+)['"]\s*\?>/gi, (_, enc) => `` ); - // 2. Strip xmlns declarations (Python etree strips unused ones). - // Skipped for Configuration.xml: those declarations are load-bearing (they back - // xsi:type values like app:ApplicationUsePurpose in UsePurposes) and dropping them - // is exactly the corruption of issue #38 — keeping them lets the test guard against it. - if (!opts.keepXmlns) { - s = s.replace(/\s+xmlns(?::[\w]+)?="[^"]*"/g, ''); - } - // 3. Collapse whitespace between tags: "> \n\t <" → "><". Kept: the ports indent a + // 2. Collapse whitespace between tags: "> \n\t <" → "><". Kept: the ports indent a // few blocks differently, which is formatting rather than the byte canon. s = s.replace(/>\s+<'); return s; @@ -338,8 +335,7 @@ function normalizeContent(text, config, relFile) { s = s.replace(/\r\n/g, '\n'); // Normalize XML differences (Python etree serialization quirks) if (config?.runtime === 'python') { - const base = relFile ? relFile.split(/[\\/]/).pop() : ''; - s = normalizeXmlContent(s, { keepXmlns: base === 'Configuration.xml' }); + s = normalizeXmlContent(s); } // Normalize UUIDs @@ -359,6 +355,42 @@ function normalizeContent(text, config, relFile) { return s; } +// ─── Проверка содержимого файла по СЫРЫМ байтам ──────────────────────────── +// Снэпшотное сравнение в py-прогоне режет объявления xmlns (normalizeXmlContent), +// поэтому наличие/отсутствие конкретного объявления через снэпшот не проверить — +// он совпадёт при любом исходе. Эта проверка читает файл как есть. +// spec: { file, text } | { file, text: [...] }. Возвращает массив ошибок. +function checkFileContains(workDir, spec, expectPresent) { + const errs = []; + const target = join(workDir, spec.file); + if (!existsSync(target)) { + errs.push(`${expectPresent ? 'fileContains' : 'fileNotContains'}: file not found: ${spec.file}`); + return errs; + } + const text = readFileSync(target).toString('utf8').replace(/^/, ''); + const needles = Array.isArray(spec.text) ? spec.text : [spec.text]; + for (const needle of needles) { + const found = text.includes(needle); + if (expectPresent && !found) errs.push(`${spec.file} does not contain "${needle}"`); + if (!expectPresent && found) errs.push(`${spec.file} unexpectedly contains "${needle}"`); + } + return errs; +} + +// Ключи expect, которые раннер действительно умеет. Неизвестный ключ = кейс, +// который молча ничего не проверяет (так уже было с 9 кейсами meta-edit) — +// поэтому он ошибка, а не игнор. +const KNOWN_EXPECT_KEYS = new Set([ + 'files', 'stdoutContains', 'stdoutNotContains', 'preserves', + 'fileContains', 'fileNotContains', +]); + +function checkExpectKeys(caseData) { + if (!caseData.expect) return []; + const unknown = Object.keys(caseData.expect).filter(k => !KNOWN_EXPECT_KEYS.has(k)); + return unknown.map(k => `expect.${k}: раннер такого ключа не знает — кейс ничего не проверяет`); +} + // ─── Byte-style preservation check (round-trip #44/#46/#47, канон #57) ────── // Проверяет СЫРЫЕ байты файла (в обход normalizeContent): BOM / EOL / регистр // encoding / финальный перенос / отсутствие / форма пустого элемента. @@ -709,6 +741,7 @@ async function runCaseAsync(testCase, opts) { // Assertions const errors = []; + errors.push(...checkExpectKeys(caseData)); if (caseData.expectError) { if (exitCode === 0) errors.push('Expected error (non-zero exit) but got exitCode=0'); if (typeof caseData.expectError === 'string' && !stderr.includes(caseData.expectError)) { @@ -748,6 +781,16 @@ async function runCaseAsync(testCase, opts) { ? caseData.expect.preserves : [caseData.expect.preserves]; for (const spec of specs) errors.push(...checkPreserves(workDir, spec)); } + if (caseData.expect?.fileContains) { + const specs = Array.isArray(caseData.expect.fileContains) + ? caseData.expect.fileContains : [caseData.expect.fileContains]; + for (const spec of specs) errors.push(...checkFileContains(workDir, spec, true)); + } + if (caseData.expect?.fileNotContains) { + const specs = Array.isArray(caseData.expect.fileNotContains) + ? caseData.expect.fileNotContains : [caseData.expect.fileNotContains]; + for (const spec of specs) errors.push(...checkFileContains(workDir, spec, false)); + } if (errors.length === 0 && !caseData.expectError && !workspace.readOnly) { const snapshotConfig = { ...skillConfig.snapshot, runtime: opts.runtime }; if (opts.updateSnapshots) { @@ -898,6 +941,7 @@ function runCase(testCase, opts) { // 4. Assertions const errors = []; + errors.push(...checkExpectKeys(caseData)); if (caseData.expectError) { // Negative case — expect failure @@ -949,6 +993,16 @@ function runCase(testCase, opts) { ? caseData.expect.preserves : [caseData.expect.preserves]; for (const spec of specs) errors.push(...checkPreserves(workDir, spec)); } + if (caseData.expect?.fileContains) { + const specs = Array.isArray(caseData.expect.fileContains) + ? caseData.expect.fileContains : [caseData.expect.fileContains]; + for (const spec of specs) errors.push(...checkFileContains(workDir, spec, true)); + } + if (caseData.expect?.fileNotContains) { + const specs = Array.isArray(caseData.expect.fileNotContains) + ? caseData.expect.fileNotContains : [caseData.expect.fileNotContains]; + for (const spec of specs) errors.push(...checkFileContains(workDir, spec, false)); + } // Snapshot comparison (skip for external/read-only workspaces) if (errors.length === 0 && !caseData.expectError && !workspace.readOnly) {