diff --git a/.claude/skills/skd-edit/SKILL.md b/.claude/skills/skd-edit/SKILL.md index 41cd2ad8..751a737e 100644 --- a/.claude/skills/skd-edit/SKILL.md +++ b/.claude/skills/skd-edit/SKILL.md @@ -253,13 +253,15 @@ Value — имена ресурсов (как в полях/вычисляемы ### patch-query — точечная замена в тексте запроса -Shorthand: `"старое => новое"`. Заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`. +Shorthand: `"старое => новое [@once]"`. По умолчанию заменяет все вхождения подстроки. Поддерживает пакетный режим и `-DataSet`. ``` "СубконтоДт1) В => СубконтоКт1) В" -"ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ" +"ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ @once" ``` +`@once` — упасть с ошибкой, если в запросе не **ровно одно** вхождение. Защищает от случайных замен в комментариях и однотипных идентификаторах. + ### set-outputParameter — установить параметр вывода ``` diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index a6874103..f5875e6f 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -1,4 +1,4 @@ -# skd-edit v1.15 — Atomic 1C DCS editor +# skd-edit v1.16 — Atomic 1C DCS editor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -2472,6 +2472,12 @@ switch ($Operation) { } foreach ($val in $values) { + $once = $false + if ($val -match '@once\b') { + $once = $true + $val = ($val -replace '\s*@once\b', '').Trim() + } + $sepIdx = $val.IndexOf(" => ") if ($sepIdx -lt 0) { Write-Error "patch-query value must contain ' => ' separator: old => new" @@ -2480,12 +2486,20 @@ switch ($Operation) { $oldStr = $val.Substring(0, $sepIdx) $newStr = $val.Substring($sepIdx + 4) $queryText = $queryEl.InnerText - if (-not $queryText.Contains($oldStr)) { + + $count = ([regex]::Matches($queryText, [regex]::Escape($oldStr))).Count + if ($count -eq 0) { Write-Error "Substring not found in query of dataset '$dsName': $oldStr" exit 1 } + if ($once -and $count -ne 1) { + Write-Error "@once: expected 1 occurrence of '$oldStr' in dataset '$dsName', found $count" + exit 1 + } + $queryEl.InnerText = $queryText.Replace($oldStr, $newStr) - Write-Host "[OK] Query patched in dataset `"$dsName`": replaced '$oldStr'" + $suffix = if ($once) { " (1 occurrence)" } else { " ($count occurrence(s))" } + Write-Host "[OK] Query patched in dataset `"$dsName`": replaced '$oldStr'$suffix" } } diff --git a/.claude/skills/skd-edit/scripts/skd-edit.py b/.claude/skills/skd-edit/scripts/skd-edit.py index b222f952..88f4d11d 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.py +++ b/.claude/skills/skd-edit/scripts/skd-edit.py @@ -1,4 +1,4 @@ -# skd-edit v1.15 — Atomic 1C DCS editor (Python port) +# skd-edit v1.16 — Atomic 1C DCS editor (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -2064,6 +2064,11 @@ elif operation == "patch-query": print(f"No element found in dataset '{ds_name}'", file=sys.stderr) sys.exit(1) for val in values: + once = False + if re.search(r'@once\b', val): + once = True + val = re.sub(r'\s*@once\b', '', val).strip() + sep_idx = val.find(" => ") if sep_idx < 0: print("patch-query value must contain ' => ' separator: old => new", file=sys.stderr) @@ -2071,11 +2076,18 @@ elif operation == "patch-query": old_str = val[:sep_idx] new_str = val[sep_idx + 4:] query_text = query_el.text or "" - if old_str not in query_text: + + count = query_text.count(old_str) + if count == 0: print(f"Substring not found in query of dataset '{ds_name}': {old_str}", file=sys.stderr) sys.exit(1) + if once and count != 1: + print(f"@once: expected 1 occurrence of '{old_str}' in dataset '{ds_name}', found {count}", file=sys.stderr) + sys.exit(1) + query_el.text = query_text.replace(old_str, new_str) - print(f'[OK] Query patched in dataset "{ds_name}": replaced \'{old_str}\'') + suffix = " (1 occurrence)" if once else f" ({count} occurrence(s))" + print(f'[OK] Query patched in dataset "{ds_name}": replaced \'{old_str}\'{suffix}') elif operation == "set-outputParameter": settings = resolve_variant_settings() diff --git a/tests/skills/cases/skd-edit/patch-query-once-multi-fail.json b/tests/skills/cases/skd-edit/patch-query-once-multi-fail.json new file mode 100644 index 00000000..3780ea05 --- /dev/null +++ b/tests/skills/cases/skd-edit/patch-query-once-multi-fail.json @@ -0,0 +1,22 @@ +{ + "name": "patch-query @once: множественные вхождения — ошибка", + "preRun": [ + { + "script": "skd-compile/scripts/skd-compile", + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.Поле1, Т.Поле2 ИЗ Регистр КАК Т ГДЕ Т.Поле1 = Т.Поле2", + "fields": ["Поле1: string", "Поле2: string"] + }] + }, + "args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" } + } + ], + "params": { + "templatePath": "Template.xml", + "operation": "patch-query", + "value": "Поле1 => Новое @once" + }, + "expectError": "expected 1 occurrence" +} diff --git a/tests/skills/cases/skd-edit/patch-query-once.json b/tests/skills/cases/skd-edit/patch-query-once.json new file mode 100644 index 00000000..270efe4c --- /dev/null +++ b/tests/skills/cases/skd-edit/patch-query-once.json @@ -0,0 +1,21 @@ +{ + "name": "patch-query @once: 1 вхождение — успешная замена", + "preRun": [ + { + "script": "skd-compile/scripts/skd-compile", + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.УникальноеИмя ИЗ Регистр КАК Т", + "fields": ["УникальноеИмя: string"] + }] + }, + "args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" } + } + ], + "params": { + "templatePath": "Template.xml", + "operation": "patch-query", + "value": "УникальноеИмя => НовоеИмя @once" + } +} diff --git a/tests/skills/cases/skd-edit/snapshots/patch-query-once/Template.xml b/tests/skills/cases/skd-edit/snapshots/patch-query-once/Template.xml new file mode 100644 index 00000000..bf0521a4 --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/patch-query-once/Template.xml @@ -0,0 +1,44 @@ + + + + ИсточникДанных1 + Local + + + Основной + + УникальноеИмя + УникальноеИмя + + xs:string + + 0 + Variable + + + + ИсточникДанных1 + ВЫБРАТЬ Т.НовоеИмя ИЗ Регистр КАК Т + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + +