From 9aaa3a1c1ecddced7f56a1cdb404bde2734dae2a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 18 Jul 2026 20:37:58 +0300 Subject: [PATCH] =?UTF-8?q?feat(cfe-patch-method):=20=D0=B1=D0=B0=D1=82?= =?UTF-8?q?=D1=87=20-Check/-Actualize=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=80=D1=83=D0=B5=D0=BC=D1=8B=D1=85=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D0=BE=D0=B2=20(v2.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После обновления КФ контролируемые методы (&ИзменениеИКонтроль) молча уезжают в рассинхрон — платформа при загрузке не ругается, ошибка лишь в рантайме. Добавлены два явных режима по всему расширению (или -ModulePath/-MethodName для сужения): - -Check — отчёт: какие методы дрейфнули (ДРЕЙФ/КОНФЛИКТ/МЕТОД-ИСЧЕЗ), актуальные числом; ничего не пишет; exit 1 при наличии дрейфа. - -Actualize — чинит пачкой: авто-перенос + merge-воркспейс на конфликтах. Одиночный ресинк вынесен в общую функцию resync_one (report_only), одиночный путь и батч используют её. Зона ответственности узкая — только тело &ИзменениеИКонтроль. Merge-воркспейс переработан: тонкий index.md (список конфликтов + пути к .bsl расширения) + подпапка на метод (conflict.md с блоком/диффом + base/local/remote), без общей портянки. cfe-validate: крошка-указатель [INFO] при наличии контролируемых методов -> /cfe-patch-method -Check. Тесты: +check-clean/check-drift/actualize-batch. 16 кейсов, оба рантайма зелёные, байтовый паритет ps1<->py. verify-snapshots (реальная 1С) Windows ps+py — 16/16. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/cfe-patch-method/SKILL.md | 30 +- .../scripts/cfe-patch-method.ps1 | 474 +++++++++++------- .../scripts/cfe-patch-method.py | 338 ++++++++++--- .../cfe-validate/scripts/cfe-validate.ps1 | 11 + .../cfe-validate/scripts/cfe-validate.py | 15 + .../cfe-patch-method/actualize-batch.json | 33 ++ .../cases/cfe-patch-method/check-clean.json | 33 ++ .../cases/cfe-patch-method/check-drift.json | 33 ++ .../actualize-batch/CommonModules/БатчМод.xml | 23 + .../CommonModules/БатчМод/Ext/Module.bsl | 14 + .../actualize-batch/Configuration.xml | 252 ++++++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../Ext/CommonModules/БатчМод.xml | 18 + .../Ext/CommonModules/БатчМод/Ext/Module.bsl | 13 + .../actualize-batch/Ext/Configuration.xml | 72 +++ .../actualize-batch/Ext/Languages/Русский.xml | 13 + .../Ext/Roles/Тест_ОсновнаяРоль.xml | 10 + .../actualize-batch/Languages/Русский.xml | 16 + .../check-clean/CommonModules/БатчМод.xml | 23 + .../CommonModules/БатчМод/Ext/Module.bsl | 13 + .../snapshots/check-clean/Configuration.xml | 252 ++++++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../check-clean/Ext/CommonModules/БатчМод.xml | 18 + .../Ext/CommonModules/БатчМод/Ext/Module.bsl | 12 + .../check-clean/Ext/Configuration.xml | 72 +++ .../check-clean/Ext/Languages/Русский.xml | 13 + .../Ext/Roles/Тест_ОсновнаяРоль.xml | 10 + .../check-clean/Languages/Русский.xml | 16 + .../CommonModules/РесинкКонфл/Ext/Module.bsl | 2 +- 29 files changed, 1609 insertions(+), 256 deletions(-) create mode 100644 tests/skills/cases/cfe-patch-method/actualize-batch.json create mode 100644 tests/skills/cases/cfe-patch-method/check-clean.json create mode 100644 tests/skills/cases/cfe-patch-method/check-drift.json create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод/Ext/Module.bsl create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Configuration.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод/Ext/Module.bsl create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Configuration.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Languages/Русский.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Roles/Тест_ОсновнаяРоль.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Languages/Русский.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод/Ext/Module.bsl create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Configuration.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод/Ext/Module.bsl create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Configuration.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Languages/Русский.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Roles/Тест_ОсновнаяРоль.xml create mode 100644 tests/skills/cases/cfe-patch-method/snapshots/check-clean/Languages/Русский.xml diff --git a/.claude/skills/cfe-patch-method/SKILL.md b/.claude/skills/cfe-patch-method/SKILL.md index 0239fcbb..75c0f963 100644 --- a/.claude/skills/cfe-patch-method/SKILL.md +++ b/.claude/skills/cfe-patch-method/SKILL.md @@ -30,9 +30,11 @@ allowed-tools: |----------|----------|--------------| | `ExtensionPath` | Путь к расширению (обязат.) | — | | `ConfigPath` | Путь к конфигурации-источнику | обязат., кроме случая, когда `ModulePath` — путь к файлу | -| `ModulePath` | Логическое имя (`Тип.Имя.Модуль`) **или** путь к файлу модуля `.bsl` (обязат.) | — | -| `MethodName` | Имя перехватываемого метода (обязат.) | — | -| `InterceptorType` | `Before` / `After` / `Instead` / `ModificationAndControl` (обязат.) | — | +| `ModulePath` | Логическое имя (`Тип.Имя.Модуль`) **или** путь к файлу модуля `.bsl` | обязат. для генерации | +| `MethodName` | Имя перехватываемого метода | обязат. для генерации | +| `InterceptorType` | `Before` / `After` / `Instead` / `ModificationAndControl` | обязат. для генерации | +| `Check` | Проверить контролируемые методы на дрейф (только отчёт) | — | +| `Actualize` | Актуализировать дрейфнувшие контролируемые методы | — | Директива контекста, `Процедура`/`Функция`, сигнатура и обрамление определяются из оригинала автоматически — вручную задавать не нужно. @@ -82,6 +84,22 @@ allowed-tools: Повторный вызов `Before`/`After`/`Instead` для уже перехваченного метода не создаёт дубль (`[ПРОПУЩЕН]`). +## Проверка и актуализация пачкой (`-Check` / `-Actualize`) + +После обновления конфигурации контролируемые методы (`&ИзменениеИКонтроль`) могут разойтись с +исходником — платформа при загрузке об этом молчит, ошибка всплывает лишь в рантайме. Эти режимы +проходят по **всем** `&ИзменениеИКонтроль` расширения (или сузить: `-ModulePath` — модуль, `+ -MethodName` — один): + +- **`-Check`** — только отчёт: какие методы дрейфнули (`ДРЕЙФ` / `КОНФЛИКТ` / `МЕТОД-ИСЧЕЗ`); актуальные + считаются числом (`100/103 актуальны`). Ничего не пишет. Код возврата **1**, если есть что чинить. +- **`-Actualize`** — чинит: авто-переносит правки, для неоднозначных — merge-воркспейс. + +Зона ответственности — **только тело `&ИзменениеИКонтроль`** (не свойства/формы/сигнатуры). + +Merge-воркспейс конфликтов (`-Actualize`) в системном temp: `index.md` (тонкий список конфликтов с +путями к `.bsl` расширения для правки) + подпапка на каждый конфликтный метод с `conflict.md` +(что перенести + дифф) и `base/local/remote`. Путь печатается в выводе. + ## Команда ```powershell @@ -105,6 +123,12 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps # ModulePath как путь к файлу модуля-источника (без -ConfigPath) ... -ExtensionPath src\cfe\ИмяРасширения -ModulePath "src\cf\CommonModules\ОбщийМодуль\Ext\Module.bsl" -MethodName "ПолучитьДанные" -InterceptorType Instead + +# Проверить все контролируемые методы расширения на дрейф +... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Check + +# Актуализировать дрейфнувшие контролируемые методы пачкой +... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Actualize ``` ## Верификация diff --git a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 index 83aa1f13..2483f14f 100644 --- a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 +++ b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 @@ -1,4 +1,4 @@ -# cfe-patch-method v2.1 — Source-aware method interceptor for 1C extension (CFE) +# cfe-patch-method v2.2 — Source-aware method interceptor for 1C extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -6,15 +6,17 @@ param( [string]$ConfigPath, - [Parameter(Mandatory)] + # Generation: logical name or path to .bsl. Optional in -Check/-Actualize (scope narrowing). [string]$ModulePath, - [Parameter(Mandatory)] [string]$MethodName, - [Parameter(Mandatory)] - [ValidateSet("Before","After","Instead","ModificationAndControl")] - [string]$InterceptorType + [ValidateSet("", "Before", "After", "Instead", "ModificationAndControl")] + [string]$InterceptorType, + + # Batch modes over &ИзменениеИКонтроль of the extension (no generation): + [switch]$Check, # report drift only, no writes + [switch]$Actualize # actualize drifted controlled methods ) $ErrorActionPreference = "Stop" @@ -475,6 +477,199 @@ function Get-Truncated { return $t } +# Reverse of typeDirMap (plural dir -> singular type) for logical id display +$script:dirToType = @{ + "Catalogs"="Catalog"; "Documents"="Document"; "Enums"="Enum"; "CommonModules"="CommonModule" + "Reports"="Report"; "DataProcessors"="DataProcessor"; "ExchangePlans"="ExchangePlan" + "ChartsOfAccounts"="ChartOfAccounts"; "ChartsOfCharacteristicTypes"="ChartOfCharacteristicTypes" + "ChartsOfCalculationTypes"="ChartOfCalculationTypes"; "BusinessProcesses"="BusinessProcess" + "Tasks"="Task"; "InformationRegisters"="InformationRegister" + "AccumulationRegisters"="AccumulationRegister"; "AccountingRegisters"="AccountingRegister" + "CalculationRegisters"="CalculationRegister" +} + +# Rel-path segments (relative to ExtensionPath) -> logical ModulePath (Type.Name.Module / CommonModule.Name) +function Get-ModulePathFromRel { + param($relParts) + $dir0 = $relParts[0]; $name = $relParts[1] + $typ = if ($script:dirToType.ContainsKey($dir0)) { $script:dirToType[$dir0] } else { $dir0 } + if ($dir0 -eq "CommonModules") { return "CommonModule.$name" } + if ($relParts.Count -ge 7 -and $relParts[2] -eq "Forms") { return "$typ.$name.Form.$($relParts[3])" } + $mod = $relParts[$relParts.Count - 1] + if ($mod.EndsWith(".bsl")) { $mod = $mod.Substring(0, $mod.Length - 4) } + return "$typ.$name.$mod" +} + +function Get-RelPartsUnder { + param([string]$root, [string]$fullPath) + $r = [IO.Path]::GetFullPath($root).TrimEnd('\', '/') + $f = [IO.Path]::GetFullPath($fullPath) + $rel = $f.Substring($r.Length).TrimStart('\', '/') + return (($rel -replace '\\', '/').Split('/') | Where-Object { $_ -ne '' }) +} + +function Get-ResyncConflictReason { + param($disputed) + $kinds = @($disputed | ForEach-Object { $_.Kind } | Select-Object -Unique) + $parts = @() + if ($kinds -contains 'insert') { $parts += 'якорь вставки изменён' } + if ($kinds -contains 'delete') { $parts += 'удаляемое исчезло' } + return ($parts -join '; ') +} + +# Write per-method conflict folder: conflict.md + base/local/remote +function Write-ConflictFolder { + param($folder, $methodId, $extBsl, $existingName, $method, $v1, $markedBody, $v2, $v1norm, $v2norm, $disputed, $enc) + if (-not (Test-Path $folder)) { New-Item -ItemType Directory -Path $folder -Force | Out-Null } + [IO.File]::WriteAllText((Join-Path $folder 'base.bsl'), (($v1 -join "`r`n") + "`r`n"), $enc) + [IO.File]::WriteAllText((Join-Path $folder 'local.bsl'), (($markedBody -join "`r`n") + "`r`n"), $enc) + [IO.File]::WriteAllText((Join-Path $folder 'remote.bsl'), (($v2 -join "`r`n") + "`r`n"), $enc) + $md = @() + $md += "# $methodId" + $md += "Править: $extBsl" + $md += "Метод: $existingName (&ИзменениеИКонтроль(`"$($method.Canonical)`"))" + $md += "Причина: $(Get-ResyncConflictReason $disputed)" + $md += "" + $md += "## Не размещено — перенести в метод вручную" + foreach ($d in $disputed) { + if ($d.Kind -eq 'insert') { $md += '#Вставка'; foreach ($l in $d.Lines) { $md += $l }; $md += '#КонецВставки' } + else { $md += '// не удалось найти для удаления:'; foreach ($l in $d.Lines) { $md += ('// ' + $l.Trim()) } } + } + $md += "" + $md += "## Дифф base→remote (что изменилось в оригинале)" + foreach ($l in $v1) { if ($v2norm -notcontains (Get-Normalized $l)) { $md += "- $l" } } + foreach ($l in $v2) { if ($v1norm -notcontains (Get-Normalized $l)) { $md += "+ $l" } } + $md += "" + $md += "Рядом: base.bsl / local.bsl / remote.bsl" + [IO.File]::WriteAllText((Join-Path $folder 'conflict.md'), (($md -join "`r`n") + "`r`n"), $enc) +} + +# Resync one &ИзменениеИКонтроль interceptor. Returns status hashtable. +# ReportOnly: classify without writing. Otherwise: apply to module (+ conflict folder on disputes). +function Invoke-Resync { + param($extBsl, $extLines, $dup, $method, [string]$logicalModule, [string]$conflictFolder, [switch]$ReportOnly, $enc) + + $methodId = "$logicalModule.$($method.Canonical)" + $decLine = $dup.Line + $sigLineIdx = $decLine + 1 + if ($sigLineIdx -ge $extLines.Count -or $extLines[$sigLineIdx] -notmatch '^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(') { + return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру перехватчика' } + } + $existingName = $Matches[1] + $sig = Read-Signature $extLines $sigLineIdx + if (-not $sig) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не разобрать сигнатуру' } } + $sigEnd = $sig.EndLineIdx + $isFunc = ($extLines[$sigLineIdx] -imatch '^\s*(?:Асинх\s+)?Функция\b') + $endRe = if ($isFunc) { '^\s*КонецФункции\b' } else { '^\s*КонецПроцедуры\b' } + $blockEnd = -1 + for ($j = $sigEnd + 1; $j -lt $extLines.Count; $j++) { if ($extLines[$j] -imatch $endRe) { $blockEnd = $j; break } } + if ($blockEnd -lt 0) { return @{ Id = $methodId; Status = 'ОШИБКА'; ExtBsl = $extBsl; Reason = 'не найден конец перехватчика' } } + + $markedBody = @() + for ($j = $sigEnd + 1; $j -lt $blockEnd; $j++) { $markedBody += $extLines[$j] } + + $parsed = Parse-MarkedBody $markedBody + $v1 = $parsed.V1 + $ops = $parsed.Ops + $v2 = $method.BodyLines + $v1norm = @($v1 | ForEach-Object { Get-Normalized $_ }) + $v2norm = @($v2 | ForEach-Object { Get-Normalized $_ }) + + if (($v1norm -join "`n") -eq ($v2norm -join "`n")) { + return @{ Id = $methodId; Status = 'АКТУАЛЕН'; ExtBsl = $extBsl } + } + + $insertTop = @(); $insertAfter = @{}; $delStart = @{}; $delEnd = @{}; $disputed = @(); $transferred = @() + foreach ($op in $ops) { + if ($op.Kind -eq 'insert') { + $beforeLines = @() + if ($op.After -ge 0) { $bs = [Math]::Max(0, $op.After - 2); for ($m = $bs; $m -le $op.After; $m++) { $beforeLines += $v1norm[$m] } } + $afterLines = @() + $ae = [Math]::Min($v1norm.Count - 1, $op.After + 3) + for ($m = $op.After + 1; $m -le $ae; $m++) { $afterLines += $v1norm[$m] } + $k = Resolve-InsertionPoint $v2norm $beforeLines $afterLines + if ($null -eq $k) { $disputed += @{ Kind = 'insert'; Lines = $op.Lines } } + elseif ($k -lt 0) { $insertTop += ,$op.Lines; $transferred += @{ Kind = 'insert' } } + else { if (-not $insertAfter.ContainsKey($k)) { $insertAfter[$k] = @() }; $insertAfter[$k] += ,$op.Lines; $transferred += @{ Kind = 'insert' } } + } else { + $keys = @(); for ($m = $op.Start; $m -le $op.End; $m++) { $keys += $v1norm[$m] } + $p = Find-UniqueRun $v2norm $keys + if ($p -ge 0) { $delStart[$p] = $true; $delEnd[$p + $keys.Count - 1] = $true; $transferred += @{ Kind = 'delete' } } + else { $disputed += @{ Kind = 'delete'; Lines = $op.Lines } } + } + } + + if ($ReportOnly) { + $st = if ($disputed.Count -gt 0) { 'КОНФЛИКТ' } else { 'ДРЕЙФ' } + return @{ Id = $methodId; Status = $st; ExtBsl = $extBsl; Transferred = $transferred.Count; Disputed = $disputed.Count; Reason = (Get-ResyncConflictReason $disputed) } + } + + # assemble new marked body + $newBody = @() + foreach ($blk in $insertTop) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" } + for ($k = 0; $k -lt $v2.Count; $k++) { + if ($delStart.ContainsKey($k)) { $newBody += "#Удаление" } + $newBody += $v2[$k] + if ($delEnd.ContainsKey($k)) { $newBody += "#КонецУдаления" } + if ($insertAfter.ContainsKey($k)) { foreach ($blk in $insertAfter[$k]) { $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" } } + } + if ($disputed.Count -gt 0) { + $newBody += "`t// [РЕСИНК-КОНФЛИКТ] перенесите блоки ниже вручную — исходный якорь изменился в новой версии оригинала." + $newBody += "`t// Материалы: см. index.md / conflict.md в merge-воркспейсе (путь в выводе)." + foreach ($d in $disputed) { + if ($d.Kind -eq 'insert') { $newBody += "#Вставка"; foreach ($l in $d.Lines) { $newBody += $l }; $newBody += "#КонецВставки" } + else { $newBody += "`t// [РЕСИНК-КОНФЛИКТ] не удалось найти для удаления:"; foreach ($l in $d.Lines) { $newBody += ("`t// " + $l.Trim()) } } + } + } + $asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" } + $keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" } + $endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" } + $newBlock = @() + if ($method.Context) { $newBlock += $method.Context } + $newBlock += "&ИзменениеИКонтроль(`"$($method.Canonical)`")" + $newBlock += "$asyncPrefix$keyword $existingName($($method.ParamsText))" + $newBlock += $newBody + $newBlock += $endKeyword + + $blockStart = $decLine + if ($decLine -ge 1 -and (Test-ContextDirective $extLines[$decLine - 1].Trim())) { $blockStart = $decLine - 1 } + $out = @() + for ($j = 0; $j -lt $blockStart; $j++) { $out += $extLines[$j] } + foreach ($l in $newBlock) { $out += $l } + for ($j = $blockEnd + 1; $j -lt $extLines.Count; $j++) { $out += $extLines[$j] } + [IO.File]::WriteAllText($extBsl, (($out -join "`r`n") + "`r`n"), $enc) + + $conflictDir = $null + if ($disputed.Count -gt 0) { + $conflictDir = $conflictFolder + Write-ConflictFolder $conflictFolder $methodId $extBsl $existingName $method $v1 $markedBody $v2 $v1norm $v2norm $disputed $enc + } + $status = if ($disputed.Count -gt 0) { 'ЧАСТИЧНО' } else { 'АКТУАЛИЗИРОВАН' } + return @{ Id = $methodId; Status = $status; ExtBsl = $extBsl; Transferred = $transferred.Count; Disputed = $disputed.Count; ConflictDir = $conflictDir; Reason = (Get-ResyncConflictReason $disputed) } +} + +# Write run-root index.md (only if there are conflicts). Returns run-root or $null. +function Write-ResyncIndex { + param($runRoot, $results, [string]$extName, [string]$configPath, [string]$verb, $enc) + $conflicts = @($results | Where-Object { $_.Status -eq 'ЧАСТИЧНО' }) + if ($conflicts.Count -eq 0) { return $null } + if (-not (Test-Path $runRoot)) { New-Item -ItemType Directory -Path $runRoot -Force | Out-Null } + $total = $results.Count + $actual = @($results | Where-Object { $_.Status -eq 'АКТУАЛЕН' }).Count + $upd = @($results | Where-Object { $_.Status -eq 'АКТУАЛИЗИРОВАН' }).Count + $lines = @() + $lines += "[$verb] $extName -> $configPath" + $lines += "Итог: $actual/$total актуальны · $upd актуализировано · $($conflicts.Count) конфликтов" + $lines += "" + $lines += "Конфликты — править .bsl расширения:" + foreach ($c in $conflicts) { + $lines += " ЧАСТИЧНО $($c.Id)" + $lines += " -> $($c.ExtBsl) ($($c.Reason))" + } + [IO.File]::WriteAllText((Join-Path $runRoot 'index.md'), (($lines -join "`r`n") + "`r`n"), $enc) + return $runRoot +} + # ============================================================================ # Main # ============================================================================ @@ -494,6 +689,93 @@ $cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs) $prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null } $namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" } +$extNameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $cfgNs) } else { $null } +$extName = if ($extNameNode -and $extNameNode.InnerText) { $extNameNode.InnerText } else { "Расширение" } + +# ============================================================================ +# Batch modes: -Check / -Actualize over &ИзменениеИКонтроль of the extension +# ============================================================================ +if ($Check -or $Actualize) { + if ($Check -and $Actualize) { Write-Error "Укажите либо -Check, либо -Actualize, не оба."; exit 1 } + if ([string]::IsNullOrEmpty($ConfigPath)) { Write-Error "Для -Check/-Actualize нужен -ConfigPath (сверка с исходником)."; exit 1 } + if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) { $ConfigPath = Join-Path (Get-Location).Path $ConfigPath } + if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent } + if (-not (Test-Path (Join-Path $ConfigPath "Configuration.xml"))) { Write-Error "Configuration.xml не найден в конфигурации-источнике: $ConfigPath"; exit 1 } + + $enc = New-Object System.Text.UTF8Encoding($true) + $reportOnly = [bool]$Check + $verb = if ($Check) { "КОНТРОЛЬ" } else { "АКТУАЛИЗАЦИЯ" } + + # target .bsl set: -ModulePath → один модуль; иначе — все .bsl расширения + $targetBsls = @() + if (-not [string]::IsNullOrEmpty($ModulePath)) { + $relParts = Get-ModuleRelPath $ModulePath + $mb = $ExtensionPath; foreach ($p in $relParts) { $mb = Join-Path $mb $p } + if (Test-Path $mb) { $targetBsls += $mb } + } else { + $targetBsls = @(Get-ChildItem -Path $ExtensionPath -Recurse -Filter *.bsl -File | ForEach-Object { $_.FullName }) + } + + $runRoot = Join-Path ([IO.Path]::GetTempPath()) (Join-Path "cfe-resync" ($extName -replace '[\\/:*?"<>|]', '_')) + if ($Actualize -and (Test-Path $runRoot)) { Remove-Item $runRoot -Recurse -Force -ErrorAction SilentlyContinue } + + $results = @() + foreach ($tb in $targetBsls) { + $scan = @([IO.File]::ReadAllLines($tb, [Text.Encoding]::UTF8)) + $mnames = @(Get-Interceptors $scan | Where-Object { $_.Type -eq 'ИзменениеИКонтроль' } | ForEach-Object { $_.Method }) + if (-not [string]::IsNullOrEmpty($MethodName)) { $mnames = @($mnames | Where-Object { $_ -ieq $MethodName }) } + $mnames = @($mnames | Select-Object -Unique) + if ($mnames.Count -eq 0) { continue } + $rel = Get-RelPartsUnder $ExtensionPath $tb + $logicalModule = Get-ModulePathFromRel $rel + $srcBsl = $ConfigPath; foreach ($p in $rel) { $srcBsl = Join-Path $srcBsl $p } + $relJoined = ($rel -join '\') -replace '\.bsl$', '' + foreach ($mname in $mnames) { + $mid = "$logicalModule.$mname" + if (-not (Test-Path $srcBsl)) { $results += @{ Id = $mid; Status = 'ИСТОЧНИК-НЕ-НАЙДЕН'; ExtBsl = $tb }; continue } + $srcL = [IO.File]::ReadAllLines($srcBsl, [Text.Encoding]::UTF8) + $m = Extract-Method $srcL $mname + if (-not $m) { $results += @{ Id = $mid; Status = 'МЕТОД-ИСЧЕЗ'; ExtBsl = $tb }; continue } + # fresh read per method (writes shift line numbers) + $tbLines = @([IO.File]::ReadAllLines($tb, [Text.Encoding]::UTF8)) + $ic = @(Get-Interceptors $tbLines | Where-Object { $_.Type -eq 'ИзменениеИКонтроль' -and $_.Method -ieq $mname })[0] + if (-not $ic) { continue } + $folder = Join-Path $runRoot (Join-Path $relJoined $m.Canonical) + $results += (Invoke-Resync $tb $tbLines $ic $m $logicalModule $folder -ReportOnly:$reportOnly $enc) + } + } + + # --- report --- + $total = $results.Count + $actual = @($results | Where-Object { $_.Status -eq 'АКТУАЛЕН' }).Count + Write-Host "[$verb] $extName -> $ConfigPath (на контроле: $total)" + $listed = @($results | Where-Object { $_.Status -ne 'АКТУАЛЕН' }) + foreach ($p in $listed) { + $line = " {0,-16} {1}" -f $p.Status, $p.Id + if ($p.Reason) { $line += " $($p.Reason)" } + Write-Host $line + } + if ($Check) { + $drift = @($results | Where-Object { $_.Status -eq 'ДРЕЙФ' }).Count + $confl = @($results | Where-Object { $_.Status -eq 'КОНФЛИКТ' }).Count + $gone = @($results | Where-Object { $_.Status -in @('МЕТОД-ИСЧЕЗ', 'ИСТОЧНИК-НЕ-НАЙДЕН') }).Count + Write-Host "Итог: $actual/$total актуальны · $drift дрейф · $confl конфликт · $gone требуют внимания" + if ($listed.Count -gt 0) { Write-Host "Починить: /cfe-patch-method -Actualize -ExtensionPath $ExtensionPath -ConfigPath $ConfigPath"; exit 1 } else { exit 0 } + } else { + $upd = @($results | Where-Object { $_.Status -eq 'АКТУАЛИЗИРОВАН' }).Count + $part = @($results | Where-Object { $_.Status -eq 'ЧАСТИЧНО' }).Count + Write-Host "Итог: $actual/$total актуальны · $upd актуализировано · $part частично" + $idx = Write-ResyncIndex $runRoot $results $extName $ConfigPath $verb $enc + if ($idx) { Write-Host "Merge-воркспейс конфликтов (см. index.md): $idx" } + exit 0 + } +} + +# --- Generation mode: require ModulePath + MethodName + InterceptorType --- +if ([string]::IsNullOrEmpty($ModulePath) -or [string]::IsNullOrEmpty($MethodName) -or [string]::IsNullOrEmpty($InterceptorType)) { + Write-Error "Нужны -ModulePath, -MethodName, -InterceptorType (генерация перехватчика). Для проверки/актуализации контролируемых методов используйте -Check или -Actualize." + exit 1 +} # --- Resolve module file paths (ModulePath = logical name OR path to a .bsl) --- $hasConfigPath = -not [string]::IsNullOrEmpty($ConfigPath) @@ -568,171 +850,23 @@ if ($dup) { Write-Host " Файл: $extBsl" exit 0 } - # ---- RESYNC (&ИзменениеИКонтроль) ---- - # Locate existing block: decorator line -> signature -> body -> Конец* - $decLine = $dup.Line - # Interceptor procedure name from existing signature (line after decorator, skipping possible async) - $sigLineIdx = $decLine + 1 - if ($sigLineIdx -ge $extLines.Count -or $extLines[$sigLineIdx] -notmatch '^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(') { - Write-Error "Не удалось найти сигнатуру существующего перехватчика &ИзменениеИКонтроль(`"$MethodName`")"; exit 1 - } - $existingName = $Matches[1] - $sig = Read-Signature $extLines $sigLineIdx - if (-not $sig) { Write-Error "Не удалось разобрать сигнатуру существующего перехватчика"; exit 1 } - $sigEnd = $sig.EndLineIdx - $isFunc = ($extLines[$sigLineIdx] -imatch '^\s*(?:Асинх\s+)?Функция\b') - $endRe = if ($isFunc) { '^\s*КонецФункции\b' } else { '^\s*КонецПроцедуры\b' } - $blockEnd = -1 - for ($j = $sigEnd + 1; $j -lt $extLines.Count; $j++) { if ($extLines[$j] -imatch $endRe) { $blockEnd = $j; break } } - if ($blockEnd -lt 0) { Write-Error "Не найден конец существующего перехватчика"; exit 1 } - - # marked body - $markedBody = @() - for ($j = $sigEnd + 1; $j -lt $blockEnd; $j++) { $markedBody += $extLines[$j] } - - # reconstruct v1, ops - $parsed = Parse-MarkedBody $markedBody - $v1 = $parsed.V1 - $ops = $parsed.Ops - $v2 = $method.BodyLines - - $v1norm = @($v1 | ForEach-Object { Get-Normalized $_ }) - $v2norm = @($v2 | ForEach-Object { Get-Normalized $_ }) - - # no drift? - if (($v1norm -join "`n") -eq ($v2norm -join "`n")) { - Write-Host "[АКТУАЛЕН] &ИзменениеИКонтроль(`"$MethodName`") — оригинал не менялся, изменений нет." - Write-Host " Файл: $extBsl" - exit 0 - } - - # transfer ops onto v2 - $insertTop = @() - $insertAfter = @{} # v2 index -> list of blocks (each = @{Lines=..}) - $delStart = @{} # v2 index -> $true - $delEnd = @{} - $disputed = @() - $transferred = @() # for the summary: @{ Kind; Anchor } - - foreach ($op in $ops) { - if ($op.Kind -eq 'insert') { - # two-sided context anchor - $beforeLines = @() - if ($op.After -ge 0) { - $bs = [Math]::Max(0, $op.After - 2) - for ($m = $bs; $m -le $op.After; $m++) { $beforeLines += $v1norm[$m] } - } - $afterLines = @() - $ae = [Math]::Min($v1norm.Count - 1, $op.After + 3) - for ($m = $op.After + 1; $m -le $ae; $m++) { $afterLines += $v1norm[$m] } - - $k = Resolve-InsertionPoint $v2norm $beforeLines $afterLines - if ($null -eq $k) { - $disputed += @{ Kind = 'insert'; Lines = $op.Lines } - } elseif ($k -lt 0) { - $insertTop += ,$op.Lines - $transferred += @{ Kind = 'insert'; Anchor = '(в начало метода)' } - } else { - if (-not $insertAfter.ContainsKey($k)) { $insertAfter[$k] = @() } - $insertAfter[$k] += ,$op.Lines - $transferred += @{ Kind = 'insert'; Anchor = $v2[$k] } - } - } else { - $keys = @() - for ($m = $op.Start; $m -le $op.End; $m++) { $keys += $v1norm[$m] } - $p = Find-UniqueRun $v2norm $keys - if ($p -ge 0) { - $delStart[$p] = $true - $delEnd[$p + $keys.Count - 1] = $true - $transferred += @{ Kind = 'delete'; Anchor = $op.Lines[0] } - } else { - $disputed += @{ Kind = 'delete'; Lines = $op.Lines } - } + # ---- RESYNC (&ИзменениеИКонтроль) — via Invoke-Resync ---- + $rel = Get-RelPartsUnder $ExtensionPath $extBsl + $logicalModule = Get-ModulePathFromRel $rel + $relJoined = ($rel -join '') -replace '.bsl$', '' + $runRoot = Join-Path ([IO.Path]::GetTempPath()) (Join-Path "cfe-resync" ($extName -replace '[\/:*?"<>|]', '_')) + $folder = Join-Path $runRoot (Join-Path $relJoined $method.Canonical) + $res = Invoke-Resync $extBsl $extLines $dup $method $logicalModule $folder $enc + switch ($res.Status) { + 'АКТУАЛЕН' { Write-Host "[АКТУАЛЕН] &ИзменениеИКонтроль(`"$MethodName`") — оригинал не менялся, изменений нет." } + 'АКТУАЛИЗИРОВАН' { Write-Host "[АКТУАЛИЗИРОВАН] &ИзменениеИКонтроль(`"$MethodName`") — тело обновлено, перенесено правок: $($res.Transferred)" } + 'ЧАСТИЧНО' { + $idx = Write-ResyncIndex $runRoot @($res) $extName $ConfigPath "АКТУАЛИЗАЦИЯ" $enc + Write-Host "[АКТУАЛИЗИРОВАН-ЧАСТИЧНО] &ИзменениеИКонтроль(`"$MethodName`") — перенесено: $($res.Transferred), конфликтов: $($res.Disputed)" + Write-Host " Конфликт помечен // [РЕСИНК-КОНФЛИКТ]. Папка метода: $($res.ConflictDir)" + if ($idx) { Write-Host " Индекс: $idx" } } - } - - # assemble new marked body - $newBody = @() - foreach ($blk in $insertTop) { - $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" - } - for ($k = 0; $k -lt $v2.Count; $k++) { - if ($delStart.ContainsKey($k)) { $newBody += "#Удаление" } - $newBody += $v2[$k] - if ($delEnd.ContainsKey($k)) { $newBody += "#КонецУдаления" } - if ($insertAfter.ContainsKey($k)) { - foreach ($blk in $insertAfter[$k]) { - $newBody += "#Вставка"; foreach ($l in $blk) { $newBody += $l }; $newBody += "#КонецВставки" - } - } - } - # disputed blocks appended with conflict markers (never lost) - if ($disputed.Count -gt 0) { - $newBody += "`t// [РЕСИНК-КОНФЛИКТ] перенесите блоки ниже вручную — исходный якорь изменился в новой версии оригинала." - $newBody += "`t// Материалы для анализа см. в выводе команды (файлы base/local/remote/merged)." - foreach ($d in $disputed) { - if ($d.Kind -eq 'insert') { - $newBody += "#Вставка"; foreach ($l in $d.Lines) { $newBody += $l }; $newBody += "#КонецВставки" - } else { - $newBody += "`t// [РЕСИНК-КОНФЛИКТ] не удалось найти для удаления:" - foreach ($l in $d.Lines) { $newBody += ("`t// " + $l.Trim()) } - } - } - } - - # rebuild block: keep context (from v2), decorator, signature with existing name and v2 params - $asyncPrefix = if ($method.IsAsync) { "Асинх " } else { "" } - $keyword = if ($method.IsFunction) { "Функция" } else { "Процедура" } - $endKeyword = if ($method.IsFunction) { "КонецФункции" } else { "КонецПроцедуры" } - $newBlock = @() - if ($method.Context) { $newBlock += $method.Context } - $newBlock += "&ИзменениеИКонтроль(`"$($method.Canonical)`")" - $newBlock += "$asyncPrefix$keyword $existingName($($method.ParamsText))" - $newBlock += $newBody - $newBlock += $endKeyword - - # block start includes preceding context directive line if present - $blockStart = $decLine - if ($decLine -ge 1 -and (Test-ContextDirective $extLines[$decLine - 1].Trim())) { $blockStart = $decLine - 1 } - - $out = @() - for ($j = 0; $j -lt $blockStart; $j++) { $out += $extLines[$j] } - foreach ($l in $newBlock) { $out += $l } - for ($j = $blockEnd + 1; $j -lt $extLines.Count; $j++) { $out += $extLines[$j] } - - [System.IO.File]::WriteAllText($extBsl, (($out -join "`r`n") + "`r`n"), $enc) - - # transferred summary (shared by full-auto and partial) - function Write-TransferSummary { - param($items) - foreach ($t in $items) { - if ($t.Kind -eq 'insert') { Write-Host " • вставка после «$(Get-Truncated $t.Anchor)»" } - else { Write-Host " • удаление «$(Get-Truncated $t.Anchor)»" } - } - } - - if ($disputed.Count -gt 0) { - # merge workspace (git-mergetool convention: base/local/remote/merged) - $tmpRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("cfe-resync\" + ($ModulePath -replace '[\\/:*?"<>|]', '_') + "." + $MethodName) - if (-not (Test-Path $tmpRoot)) { New-Item -ItemType Directory -Path $tmpRoot -Force | Out-Null } - [System.IO.File]::WriteAllText((Join-Path $tmpRoot "base.bsl"), (($v1 -join "`r`n") + "`r`n"), $enc) - [System.IO.File]::WriteAllText((Join-Path $tmpRoot "local.bsl"), (($markedBody -join "`r`n") + "`r`n"), $enc) - [System.IO.File]::WriteAllText((Join-Path $tmpRoot "remote.bsl"), (($v2 -join "`r`n") + "`r`n"), $enc) - [System.IO.File]::WriteAllText((Join-Path $tmpRoot "merged.bsl"), (($newBlock -join "`r`n") + "`r`n"), $enc) - $diff = @() - $diff += "--- base -> remote (что изменилось в оригинале) ---" - foreach ($l in $v1) { if ($v2norm -notcontains (Get-Normalized $l)) { $diff += "- $l" } } - foreach ($l in $v2) { if ($v1norm -notcontains (Get-Normalized $l)) { $diff += "+ $l" } } - [System.IO.File]::WriteAllText((Join-Path $tmpRoot "diff.txt"), (($diff -join "`r`n") + "`r`n"), $enc) - - Write-Host "[АКТУАЛИЗИРОВАН-ЧАСТИЧНО] &ИзменениеИКонтроль(`"$MethodName`") — перенесено: $($transferred.Count), конфликтов: $($disputed.Count)" - Write-TransferSummary $transferred - Write-Host " Конфликтные блоки помечены // [РЕСИНК-КОНФЛИКТ] — разместите вручную." - Write-Host " Merge-воркспейс (base/local/remote/merged/diff):" - Write-Host " $tmpRoot" - } else { - Write-Host "[АКТУАЛИЗИРОВАН] &ИзменениеИКонтроль(`"$MethodName`") — тело обновлено, перенесено правок: $($transferred.Count)" - Write-TransferSummary $transferred + default { Write-Host "[$($res.Status)] &ИзменениеИКонтроль(`"$MethodName`") — $($res.Reason)" } } Write-Host " Файл: $extBsl" exit 0 diff --git a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py index 8a4f6b36..6c4a116b 100644 --- a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py +++ b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -# cfe-patch-method v2.1 — Source-aware method interceptor for 1C extension (CFE) +# cfe-patch-method v2.2 — Source-aware method interceptor for 1C extension (CFE) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os import re +import shutil import sys import tempfile import xml.etree.ElementTree as ET @@ -475,10 +476,12 @@ def main(): ) parser.add_argument("-ExtensionPath", required=True) parser.add_argument("-ConfigPath", required=False, default="") - parser.add_argument("-ModulePath", required=True) - parser.add_argument("-MethodName", required=True) - parser.add_argument("-InterceptorType", required=True, - choices=["Before", "After", "Instead", "ModificationAndControl"]) + parser.add_argument("-ModulePath", required=False, default="") + parser.add_argument("-MethodName", required=False, default="") + parser.add_argument("-InterceptorType", required=False, default="", + choices=["", "Before", "After", "Instead", "ModificationAndControl"]) + parser.add_argument("-Check", action="store_true") + parser.add_argument("-Actualize", action="store_true") args = parser.parse_args() extension_path = args.ExtensionPath @@ -486,6 +489,8 @@ def main(): module_path = args.ModulePath method_name = args.MethodName interceptor_type = args.InterceptorType + check_mode = args.Check + actualize_mode = args.Actualize # --- Resolve extension path --- if not os.path.isabs(extension_path): @@ -506,8 +511,111 @@ def main(): pn = props.find("md:NamePrefix", ns) if pn is not None and pn.text: name_prefix = pn.text + nn = props.find("md:Name", ns) + if nn is not None and nn.text: + ext_name = nn.text except ET.ParseError: pass + if 'ext_name' not in dir(): + ext_name = "Расширение" + + # --- Batch modes: -Check / -Actualize over &ИзменениеИКонтроль --- + if check_mode or actualize_mode: + if check_mode and actualize_mode: + die("Укажите либо -Check, либо -Actualize, не оба.") + if not config_path: + die("Для -Check/-Actualize нужен -ConfigPath (сверка с исходником).") + cp = config_path + if not os.path.isabs(cp): + cp = os.path.join(os.getcwd(), cp) + if os.path.isfile(cp): + cp = os.path.dirname(cp) + if not os.path.isfile(os.path.join(cp, "Configuration.xml")): + die("Configuration.xml не найден в конфигурации-источнике: %s" % cp) + report_only = check_mode + verb = "КОНТРОЛЬ" if check_mode else "АКТУАЛИЗАЦИЯ" + safe = re.sub(r'[\\/:*?"<>|]', "_", ext_name) + run_root = os.path.join(tempfile.gettempdir(), "cfe-resync", safe) + if actualize_mode and os.path.isdir(run_root): + shutil.rmtree(run_root, ignore_errors=True) + + targets = [] + if module_path: + rp = get_module_rel_path(module_path) + mb = os.path.join(extension_path, *rp) + if os.path.isfile(mb): + targets.append(mb) + else: + for dirpath, _dirs, files in os.walk(extension_path): + for fn in files: + if fn.endswith(".bsl"): + targets.append(os.path.join(dirpath, fn)) + + results = [] + for tb in targets: + scan = read_lines(tb) + mnames = [ic["method"] for ic in get_interceptors(scan) if ic["type"] == "ИзменениеИКонтроль"] + if method_name: + mnames = [m for m in mnames if m.lower() == method_name.lower()] + seen = set(); uniq = [] + for m in mnames: + if m.lower() not in seen: + seen.add(m.lower()); uniq.append(m) + if not uniq: + continue + rel = rel_parts_under(extension_path, tb) + logical_module = module_path_from_rel(rel) + src_bsl2 = os.path.join(cp, *rel) + rel_no_bsl = os.path.join(*rel) + if rel_no_bsl.endswith(".bsl"): + rel_no_bsl = rel_no_bsl[:-4] + for mname in uniq: + mid = "%s.%s" % (logical_module, mname) + if not os.path.isfile(src_bsl2): + results.append({"id": mid, "status": "ИСТОЧНИК-НЕ-НАЙДЕН", "ext_bsl": tb}); continue + m = extract_method(read_lines(src_bsl2), mname) + if not m: + results.append({"id": mid, "status": "МЕТОД-ИСЧЕЗ", "ext_bsl": tb}); continue + tb_lines = read_lines(tb) + ic = next((x for x in get_interceptors(tb_lines) + if x["type"] == "ИзменениеИКонтроль" and x["method"].lower() == mname.lower()), None) + if not ic: + continue + folder = os.path.join(run_root, rel_no_bsl, m["canonical"]) + results.append(resync_one(tb, tb_lines, ic, m, logical_module, folder, report_only)) + + total = len(results) + actual = sum(1 for r in results if r["status"] == "АКТУАЛЕН") + print("[%s] %s -> %s (на контроле: %d)" % (verb, ext_name, cp, total)) + listed = [r for r in results if r["status"] != "АКТУАЛЕН"] + for pr in listed: + line = " %-16s %s" % (pr["status"], pr["id"]) + if pr.get("reason"): + line += " %s" % pr["reason"] + print(line) + if check_mode: + drift = sum(1 for r in results if r["status"] == "ДРЕЙФ") + confl = sum(1 for r in results if r["status"] == "КОНФЛИКТ") + gone = sum(1 for r in results if r["status"] in ("МЕТОД-ИСЧЕЗ", "ИСТОЧНИК-НЕ-НАЙДЕН")) + print("Итог: %d/%d актуальны · %d дрейф · %d конфликт · %d требуют внимания" + % (actual, total, drift, confl, gone)) + if listed: + print("Починить: /cfe-patch-method -Actualize -ExtensionPath %s -ConfigPath %s" % (extension_path, cp)) + sys.exit(1) + sys.exit(0) + else: + upd = sum(1 for r in results if r["status"] == "АКТУАЛИЗИРОВАН") + part = sum(1 for r in results if r["status"] == "ЧАСТИЧНО") + print("Итог: %d/%d актуальны · %d актуализировано · %d частично" % (actual, total, upd, part)) + idx = write_resync_index(run_root, results, ext_name, cp, verb) + if idx: + print("Merge-воркспейс конфликтов (см. index.md): %s" % idx) + sys.exit(0) + + # --- Generation mode: require ModulePath + MethodName + InterceptorType --- + if not (module_path and method_name and interceptor_type): + die("Нужны -ModulePath, -MethodName, -InterceptorType (генерация перехватчика). " + "Для проверки/актуализации контролируемых методов используйте -Check или -Actualize.") # --- Resolve module file paths (ModulePath = logical name OR path to a .bsl) --- has_config = bool(config_path) @@ -580,7 +688,31 @@ def main(): % (decorator_ru, method_name)) print(" Файл: %s" % ext_bsl) sys.exit(0) - resync(ext_bsl, ext_lines, dup, method, method_name, module_path) + rel = rel_parts_under(extension_path, ext_bsl) + logical_module = module_path_from_rel(rel) + rel_no_bsl = os.path.join(*rel) + if rel_no_bsl.endswith(".bsl"): + rel_no_bsl = rel_no_bsl[:-4] + safe = re.sub(r'[\\/:*?"<>|]', "_", ext_name) + run_root = os.path.join(tempfile.gettempdir(), "cfe-resync", safe) + folder = os.path.join(run_root, rel_no_bsl, method["canonical"]) + res = resync_one(ext_bsl, ext_lines, dup, method, logical_module, folder, False) + st = res["status"] + if st == "АКТУАЛЕН": + print('[АКТУАЛЕН] &ИзменениеИКонтроль("%s") — оригинал не менялся, изменений нет.' % method_name) + elif st == "АКТУАЛИЗИРОВАН": + print('[АКТУАЛИЗИРОВАН] &ИзменениеИКонтроль("%s") — тело обновлено, перенесено правок: %d' + % (method_name, res.get("transferred", 0))) + elif st == "ЧАСТИЧНО": + idx = write_resync_index(run_root, [res], ext_name, config_path, "АКТУАЛИЗАЦИЯ") + print('[АКТУАЛИЗИРОВАН-ЧАСТИЧНО] &ИзменениеИКонтроль("%s") — перенесено: %d, конфликтов: %d' + % (method_name, res.get("transferred", 0), res.get("disputed", 0))) + print(" Конфликт помечен // [РЕСИНК-КОНФЛИКТ]. Папка метода: %s" % res.get("conflict_dir")) + if idx: + print(" Индекс: %s" % idx) + else: + print('[%s] &ИзменениеИКонтроль("%s") — %s' % (st, method_name, res.get("reason", ""))) + print(" Файл: %s" % ext_bsl) sys.exit(0) # --- New interceptor --- @@ -688,16 +820,87 @@ def place_new(ext_bsl, ext_lines, ext_exists, chain, core): place_new.placement = "создан модуль" -def resync(ext_bsl, ext_lines, dup, method, method_name, module_path): +DIR_TO_TYPE = { + "Catalogs": "Catalog", "Documents": "Document", "Enums": "Enum", "CommonModules": "CommonModule", + "Reports": "Report", "DataProcessors": "DataProcessor", "ExchangePlans": "ExchangePlan", + "ChartsOfAccounts": "ChartOfAccounts", "ChartsOfCharacteristicTypes": "ChartOfCharacteristicTypes", + "ChartsOfCalculationTypes": "ChartOfCalculationTypes", "BusinessProcesses": "BusinessProcess", + "Tasks": "Task", "InformationRegisters": "InformationRegister", + "AccumulationRegisters": "AccumulationRegister", "AccountingRegisters": "AccountingRegister", + "CalculationRegisters": "CalculationRegister", +} + + +def module_path_from_rel(rel_parts): + dir0, name = rel_parts[0], rel_parts[1] + typ = DIR_TO_TYPE.get(dir0, dir0) + if dir0 == "CommonModules": + return "CommonModule.%s" % name + if len(rel_parts) >= 7 and rel_parts[2] == "Forms": + return "%s.%s.Form.%s" % (typ, name, rel_parts[3]) + mod = rel_parts[-1][:-4] if rel_parts[-1].endswith(".bsl") else rel_parts[-1] + return "%s.%s.%s" % (typ, name, mod) + + +def rel_parts_under(root, full_path): + rel = os.path.relpath(os.path.abspath(full_path), os.path.abspath(root)) + return [s for s in rel.replace("\\", "/").split("/") if s and s != "."] + + +def conflict_reason(disputed): + kinds = set(d["kind"] for d in disputed) + parts = [] + if "insert" in kinds: + parts.append("якорь вставки изменён") + if "delete" in kinds: + parts.append("удаляемое исчезло") + return "; ".join(parts) + + +def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed): + os.makedirs(folder, exist_ok=True) + write_bsl(os.path.join(folder, "base.bsl"), v1) + write_bsl(os.path.join(folder, "local.bsl"), marked_body) + write_bsl(os.path.join(folder, "remote.bsl"), v2) + md = [] + md.append("# %s" % method_id) + md.append("Править: %s" % ext_bsl) + md.append('Метод: %s (&ИзменениеИКонтроль("%s"))' % (existing_name, method["canonical"])) + md.append("Причина: %s" % conflict_reason(disputed)) + md.append("") + md.append("## Не размещено — перенести в метод вручную") + for d in disputed: + if d["kind"] == "insert": + md.append("#Вставка"); md.extend(d["lines"]); md.append("#КонецВставки") + else: + md.append("// не удалось найти для удаления:") + for l in d["lines"]: + md.append("// " + l.strip()) + md.append("") + md.append("## Дифф base→remote (что изменилось в оригинале)") + for l in v1: + if normalize(l) not in v2norm: + md.append("- " + l) + for l in v2: + if normalize(l) not in v1norm: + md.append("+ " + l) + md.append("") + md.append("Рядом: base.bsl / local.bsl / remote.bsl") + write_bsl(os.path.join(folder, "conflict.md"), md) + + +def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder, report_only): + method_id = "%s.%s" % (logical_module, method["canonical"]) dec_line = dup["line"] sig_line_idx = dec_line + 1 name_re = re.compile(r'^\s*(?:Асинх\s+)?(?:Процедура|Функция)\s+([\w]+)\s*\(', re.IGNORECASE) - if sig_line_idx >= len(ext_lines) or not name_re.match(ext_lines[sig_line_idx]): - die('Не удалось найти сигнатуру существующего перехватчика &ИзменениеИКонтроль("%s")' % method_name) - existing_name = name_re.match(ext_lines[sig_line_idx]).group(1) + m0 = name_re.match(ext_lines[sig_line_idx]) if sig_line_idx < len(ext_lines) else None + if not m0: + return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру перехватчика"} + existing_name = m0.group(1) sig = read_signature(ext_lines, sig_line_idx) if not sig: - die("Не удалось разобрать сигнатуру существующего перехватчика") + return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не разобрать сигнатуру"} _params, sig_end = sig is_func = bool(re.match(r'^\s*(?:Асинх\s+)?Функция\b', ext_lines[sig_line_idx], re.IGNORECASE)) end_re = re.compile(r'^\s*КонецФункции\b' if is_func else r'^\s*КонецПроцедуры\b', re.IGNORECASE) @@ -707,29 +910,18 @@ def resync(ext_bsl, ext_lines, dup, method, method_name, module_path): block_end = j break if block_end < 0: - die("Не найден конец существующего перехватчика") + return {"id": method_id, "status": "ОШИБКА", "ext_bsl": ext_bsl, "reason": "не найден конец перехватчика"} marked_body = ext_lines[sig_end + 1:block_end] parsed = parse_marked_body(marked_body) - v1 = parsed["v1"] - ops = parsed["ops"] - v2 = method["body_lines"] - + v1 = parsed["v1"]; ops = parsed["ops"]; v2 = method["body_lines"] v1norm = [normalize(x) for x in v1] v2norm = [normalize(x) for x in v2] if "\n".join(v1norm) == "\n".join(v2norm): - print('[АКТУАЛЕН] &ИзменениеИКонтроль("%s") — оригинал не менялся, изменений нет.' % method_name) - print(" Файл: %s" % ext_bsl) - return - - insert_top = [] - insert_after = {} - del_start = set() - del_end = set() - disputed = [] - transferred = [] # for the summary: {"kind", "anchor"} + return {"id": method_id, "status": "АКТУАЛЕН", "ext_bsl": ext_bsl} + insert_top = []; insert_after = {}; del_start = set(); del_end = set(); disputed = []; transferred = 0 for op in ops: if op["kind"] == "insert": after = op["after"] @@ -739,26 +931,25 @@ def resync(ext_bsl, ext_lines, dup, method, method_name, module_path): if k is None: disputed.append({"kind": "insert", "lines": op["lines"]}) elif k < 0: - insert_top.append(op["lines"]) - transferred.append({"kind": "insert", "anchor": "(в начало метода)"}) + insert_top.append(op["lines"]); transferred += 1 else: - insert_after.setdefault(k, []).append(op["lines"]) - transferred.append({"kind": "insert", "anchor": v2[k]}) + insert_after.setdefault(k, []).append(op["lines"]); transferred += 1 else: keys = v1norm[op["start"]:op["end"] + 1] p = find_unique_run(v2norm, keys) if p >= 0: - del_start.add(p) - del_end.add(p + len(keys) - 1) - transferred.append({"kind": "delete", "anchor": op["lines"][0]}) + del_start.add(p); del_end.add(p + len(keys) - 1); transferred += 1 else: disputed.append({"kind": "delete", "lines": op["lines"]}) + if report_only: + st = "КОНФЛИКТ" if disputed else "ДРЕЙФ" + return {"id": method_id, "status": st, "ext_bsl": ext_bsl, "transferred": transferred, + "disputed": len(disputed), "reason": conflict_reason(disputed)} + new_body = [] for blk in insert_top: - new_body.append("#Вставка") - new_body.extend(blk) - new_body.append("#КонецВставки") + new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки") for k in range(len(v2)): if k in del_start: new_body.append("#Удаление") @@ -767,18 +958,13 @@ def resync(ext_bsl, ext_lines, dup, method, method_name, module_path): new_body.append("#КонецУдаления") if k in insert_after: for blk in insert_after[k]: - new_body.append("#Вставка") - new_body.extend(blk) - new_body.append("#КонецВставки") - + new_body.append("#Вставка"); new_body.extend(blk); new_body.append("#КонецВставки") if disputed: new_body.append("\t// [РЕСИНК-КОНФЛИКТ] перенесите блоки ниже вручную — исходный якорь изменился в новой версии оригинала.") - new_body.append("\t// Материалы для анализа см. в выводе команды (файлы base/local/remote/merged).") + new_body.append("\t// Материалы: см. index.md / conflict.md в merge-воркспейсе (путь в выводе).") for d in disputed: if d["kind"] == "insert": - new_body.append("#Вставка") - new_body.extend(d["lines"]) - new_body.append("#КонецВставки") + new_body.append("#Вставка"); new_body.extend(d["lines"]); new_body.append("#КонецВставки") else: new_body.append("\t// [РЕСИНК-КОНФЛИКТ] не удалось найти для удаления:") for l in d["lines"]: @@ -798,48 +984,36 @@ def resync(ext_bsl, ext_lines, dup, method, method_name, module_path): block_start = dec_line if dec_line >= 1 and is_context_directive(ext_lines[dec_line - 1].strip()): block_start = dec_line - 1 - - out = list(ext_lines[:block_start]) - out.extend(new_block) - out.extend(ext_lines[block_end + 1:]) + out = list(ext_lines[:block_start]) + new_block + list(ext_lines[block_end + 1:]) write_bsl(ext_bsl, out) - def print_transfer_summary(items): - for t in items: - if t["kind"] == "insert": - print(" • вставка после «%s»" % truncate(t["anchor"])) - else: - print(" • удаление «%s»" % truncate(t["anchor"])) - + conflict_dir = None if disputed: - # merge workspace (git-mergetool convention: base/local/remote/merged) - safe = re.sub(r'[\\/:*?"<>|]', '_', module_path) - tmp_root = os.path.join(tempfile.gettempdir(), "cfe-resync", safe + "." + method_name) - os.makedirs(tmp_root, exist_ok=True) - write_bsl(os.path.join(tmp_root, "base.bsl"), v1) - write_bsl(os.path.join(tmp_root, "local.bsl"), marked_body) - write_bsl(os.path.join(tmp_root, "remote.bsl"), v2) - write_bsl(os.path.join(tmp_root, "merged.bsl"), new_block) - diff = ["--- base -> remote (что изменилось в оригинале) ---"] - for l in v1: - if normalize(l) not in v2norm: - diff.append("- " + l) - for l in v2: - if normalize(l) not in v1norm: - diff.append("+ " + l) - write_bsl(os.path.join(tmp_root, "diff.txt"), diff) + conflict_dir = conflict_folder + write_conflict_folder(conflict_folder, method_id, ext_bsl, existing_name, method, v1, marked_body, v2, v1norm, v2norm, disputed) + status = "ЧАСТИЧНО" if disputed else "АКТУАЛИЗИРОВАН" + return {"id": method_id, "status": status, "ext_bsl": ext_bsl, "transferred": transferred, + "disputed": len(disputed), "conflict_dir": conflict_dir, "reason": conflict_reason(disputed)} - print('[АКТУАЛИЗИРОВАН-ЧАСТИЧНО] &ИзменениеИКонтроль("%s") — перенесено: %d, конфликтов: %d' - % (method_name, len(transferred), len(disputed))) - print_transfer_summary(transferred) - print(" Конфликтные блоки помечены // [РЕСИНК-КОНФЛИКТ] — разместите вручную.") - print(" Merge-воркспейс (base/local/remote/merged/diff):") - print(" %s" % tmp_root) - else: - print('[АКТУАЛИЗИРОВАН] &ИзменениеИКонтроль("%s") — тело обновлено, перенесено правок: %d' - % (method_name, len(transferred))) - print_transfer_summary(transferred) - print(" Файл: %s" % ext_bsl) + +def write_resync_index(run_root, results, ext_name, config_path, verb): + conflicts = [r for r in results if r["status"] == "ЧАСТИЧНО"] + if not conflicts: + return None + os.makedirs(run_root, exist_ok=True) + total = len(results) + actual = sum(1 for r in results if r["status"] == "АКТУАЛЕН") + upd = sum(1 for r in results if r["status"] == "АКТУАЛИЗИРОВАН") + lines = [] + lines.append("[%s] %s -> %s" % (verb, ext_name, config_path)) + lines.append("Итог: %d/%d актуальны · %d актуализировано · %d конфликтов" % (actual, total, upd, len(conflicts))) + lines.append("") + lines.append("Конфликты — править .bsl расширения:") + for c in conflicts: + lines.append(" ЧАСТИЧНО %s" % c["id"]) + lines.append(" -> %s (%s)" % (c["ext_bsl"], c.get("reason", ""))) + write_bsl(os.path.join(run_root, "index.md"), lines) + return run_root if __name__ == "__main__": diff --git a/.claude/skills/cfe-validate/scripts/cfe-validate.ps1 b/.claude/skills/cfe-validate/scripts/cfe-validate.ps1 index fcfc9155..39e9ff9c 100644 --- a/.claude/skills/cfe-validate/scripts/cfe-validate.ps1 +++ b/.claude/skills/cfe-validate/scripts/cfe-validate.ps1 @@ -930,6 +930,17 @@ if ($script:borrowedFormsWithTree.Count -eq 0) { Report-OK "13. TypeLink: clean" } +# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here --- +$extRootDir = Split-Path $resolvedPath -Parent +$ctrlCount = 0 +foreach ($bslFile in (Get-ChildItem -Path $extRootDir -Recurse -Filter *.bsl -File -ErrorAction SilentlyContinue)) { + $txt = [System.IO.File]::ReadAllText($bslFile.FullName, [System.Text.Encoding]::UTF8) + $ctrlCount += ([regex]::Matches($txt, '(?m)^\s*&ИзменениеИКонтроль\(')).Count +} +if ($ctrlCount -gt 0) { + Out-Line "[INFO] Контролируемых методов (&ИзменениеИКонтроль): $ctrlCount — их актуальность здесь не проверяется. Сверьте: /cfe-patch-method -Check -ExtensionPath -ConfigPath " +} + # --- Final output --- & $finalize diff --git a/.claude/skills/cfe-validate/scripts/cfe-validate.py b/.claude/skills/cfe-validate/scripts/cfe-validate.py index 870851e7..51902c34 100644 --- a/.claude/skills/cfe-validate/scripts/cfe-validate.py +++ b/.claude/skills/cfe-validate/scripts/cfe-validate.py @@ -885,6 +885,21 @@ def main(): elif check13_ok: r.ok('13. TypeLink: clean') + # --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here --- + ctrl_count = 0 + for dp, _dn, files in os.walk(config_dir): + for fn in files: + if fn.endswith('.bsl'): + try: + with open(os.path.join(dp, fn), 'r', encoding='utf-8-sig') as f: + for ln in f: + if re.match(r'^\s*&ИзменениеИКонтроль\(', ln): + ctrl_count += 1 + except OSError: + pass + if ctrl_count > 0: + r.out('[INFO] Контролируемых методов (&ИзменениеИКонтроль): %d — их актуальность здесь не проверяется. Сверьте: /cfe-patch-method -Check -ExtensionPath -ConfigPath ' % ctrl_count) + # --- Final output --- r.finalize(out_file) sys.exit(1 if r.errors > 0 else 0) diff --git a/tests/skills/cases/cfe-patch-method/actualize-batch.json b/tests/skills/cases/cfe-patch-method/actualize-batch.json new file mode 100644 index 00000000..dec30cdd --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/actualize-batch.json @@ -0,0 +1,33 @@ +{ + "name": "-Actualize: батч по всему расширению — дрейф чинится авто", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "CommonModule", "name": "БатчМод", "properties": { "Server": true } }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + }, + { + "writeFile": { + "path": "CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n\nФункция РасчётА() Экспорт\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n\nФункция РасчётБ() Экспорт\n\tY = 2;\n\tW = 5;\n\tВозврат Y;\nКонецФункции\n\n#КонецОбласти\n" + } + }, + { + "script": "cfe-init/scripts/cfe-init", + "args": { "-Name": "Тест", "-OutputDir": "{workDir}/ext", "-ConfigPath": "{workDir}" } + }, + { + "script": "cfe-borrow/scripts/cfe-borrow", + "args": { "-ExtensionPath": "{workDir}/ext", "-ConfigPath": "{workDir}", "-Object": "CommonModule.БатчМод" } + }, + { + "writeFile": { + "path": "ext/CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n&ИзменениеИКонтроль(\"РасчётА\")\nФункция Тест_РасчётА()\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n&ИзменениеИКонтроль(\"РасчётБ\")\nФункция Тест_РасчётБ()\n\tY = 2;\n\tВозврат Y;\nКонецФункции\n#КонецОбласти\n" + } + } + ], + "params": { "extensionPath": "ext" }, + "args_extra": ["-Actualize"], + "expect": { "stdoutContains": "1 актуализировано" } +} diff --git a/tests/skills/cases/cfe-patch-method/check-clean.json b/tests/skills/cases/cfe-patch-method/check-clean.json new file mode 100644 index 00000000..2055a03c --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/check-clean.json @@ -0,0 +1,33 @@ +{ + "name": "-Check: все контролируемые методы актуальны (exit 0)", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "CommonModule", "name": "БатчМод", "properties": { "Server": true } }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + }, + { + "writeFile": { + "path": "CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n\nФункция РасчётА() Экспорт\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n\nФункция РасчётБ() Экспорт\n\tY = 2;\n\tВозврат Y;\nКонецФункции\n\n#КонецОбласти\n" + } + }, + { + "script": "cfe-init/scripts/cfe-init", + "args": { "-Name": "Тест", "-OutputDir": "{workDir}/ext", "-ConfigPath": "{workDir}" } + }, + { + "script": "cfe-borrow/scripts/cfe-borrow", + "args": { "-ExtensionPath": "{workDir}/ext", "-ConfigPath": "{workDir}", "-Object": "CommonModule.БатчМод" } + }, + { + "writeFile": { + "path": "ext/CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n&ИзменениеИКонтроль(\"РасчётА\")\nФункция Тест_РасчётА()\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n&ИзменениеИКонтроль(\"РасчётБ\")\nФункция Тест_РасчётБ()\n\tY = 2;\n\tВозврат Y;\nКонецФункции\n#КонецОбласти\n" + } + } + ], + "params": { "extensionPath": "ext" }, + "args_extra": ["-Check"], + "expect": { "stdoutContains": "2/2 актуальны" } +} diff --git a/tests/skills/cases/cfe-patch-method/check-drift.json b/tests/skills/cases/cfe-patch-method/check-drift.json new file mode 100644 index 00000000..cfd7b9fd --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/check-drift.json @@ -0,0 +1,33 @@ +{ + "name": "-Check: дрейф контролируемого метода (exit 1, ничего не пишет)", + "preRun": [ + { + "script": "meta-compile/scripts/meta-compile", + "input": { "type": "CommonModule", "name": "БатчМод", "properties": { "Server": true } }, + "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" } + }, + { + "writeFile": { + "path": "CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n\nФункция РасчётА() Экспорт\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n\nФункция РасчётБ() Экспорт\n\tY = 2;\n\tW = 5;\n\tВозврат Y;\nКонецФункции\n\n#КонецОбласти\n" + } + }, + { + "script": "cfe-init/scripts/cfe-init", + "args": { "-Name": "Тест", "-OutputDir": "{workDir}/ext", "-ConfigPath": "{workDir}" } + }, + { + "script": "cfe-borrow/scripts/cfe-borrow", + "args": { "-ExtensionPath": "{workDir}/ext", "-ConfigPath": "{workDir}", "-Object": "CommonModule.БатчМод" } + }, + { + "writeFile": { + "path": "ext/CommonModules/БатчМод/Ext/Module.bsl", + "content": "#Область ПрограммныйИнтерфейс\n&ИзменениеИКонтроль(\"РасчётА\")\nФункция Тест_РасчётА()\n\tХ = 1;\n\tВозврат Х;\nКонецФункции\n&ИзменениеИКонтроль(\"РасчётБ\")\nФункция Тест_РасчётБ()\n\tY = 2;\n\tВозврат Y;\nКонецФункции\n#КонецОбласти\n" + } + } + ], + "params": { "extensionPath": "ext" }, + "args_extra": ["-Check"], + "expectError": true +} diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод.xml new file mode 100644 index 00000000..dac80d18 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод.xml @@ -0,0 +1,23 @@ + + + + + БатчМод + + + ru + Батч мод + + + + false + false + false + false + false + false + false + DontUse + + + diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод/Ext/Module.bsl b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод/Ext/Module.bsl new file mode 100644 index 00000000..9fa79bde --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/CommonModules/БатчМод/Ext/Module.bsl @@ -0,0 +1,14 @@ +#Область ПрограммныйИнтерфейс + +Функция РасчётА() Экспорт + Х = 1; + Возврат Х; +КонецФункции + +Функция РасчётБ() Экспорт + Y = 2; + W = 5; + Возврат Y; +КонецФункции + +#КонецОбласти diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Configuration.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Configuration.xml new file mode 100644 index 00000000..1c06fa48 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/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/cfe-patch-method/snapshots/actualize-batch/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод.xml new file mode 100644 index 00000000..db9dd27b --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод.xml @@ -0,0 +1,18 @@ + + + + + + Adopted + БатчМод + + UUID-002 + false + false + false + false + false + false + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод/Ext/Module.bsl b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод/Ext/Module.bsl new file mode 100644 index 00000000..119b60ad --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/CommonModules/БатчМод/Ext/Module.bsl @@ -0,0 +1,13 @@ +#Область ПрограммныйИнтерфейс +&ИзменениеИКонтроль("РасчётА") +Функция Тест_РасчётА() + Х = 1; + Возврат Х; +КонецФункции +&ИзменениеИКонтроль("РасчётБ") +Функция Тест_РасчётБ() + Y = 2; + W = 5; + Возврат Y; +КонецФункции +#КонецОбласти diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Configuration.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Configuration.xml new file mode 100644 index 00000000..91a980e8 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Configuration.xml @@ -0,0 +1,72 @@ + + + + + + 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 + + + + Adopted + Тест + + + ru + Тест + + + + Customization + true + Тест_ + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + Role.Тест_ОсновнаяРоль + + + + Language.Русский + + + + + + TaxiEnableVersion8_2 + + + Русский + Тест_ОсновнаяРоль + БатчМод + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Languages/Русский.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Languages/Русский.xml new file mode 100644 index 00000000..c21624f5 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Languages/Русский.xml @@ -0,0 +1,13 @@ + + + + + + Adopted + Русский + + UUID-002 + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Roles/Тест_ОсновнаяРоль.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Roles/Тест_ОсновнаяРоль.xml new file mode 100644 index 00000000..ec9dfbaf --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Ext/Roles/Тест_ОсновнаяРоль.xml @@ -0,0 +1,10 @@ + + + + + Тест_ОсновнаяРоль + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Languages/Русский.xml b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/actualize-batch/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод.xml new file mode 100644 index 00000000..dac80d18 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод.xml @@ -0,0 +1,23 @@ + + + + + БатчМод + + + ru + Батч мод + + + + false + false + false + false + false + false + false + DontUse + + + diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод/Ext/Module.bsl b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод/Ext/Module.bsl new file mode 100644 index 00000000..bcdf4818 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/CommonModules/БатчМод/Ext/Module.bsl @@ -0,0 +1,13 @@ +#Область ПрограммныйИнтерфейс + +Функция РасчётА() Экспорт + Х = 1; + Возврат Х; +КонецФункции + +Функция РасчётБ() Экспорт + Y = 2; + Возврат Y; +КонецФункции + +#КонецОбласти diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Configuration.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Configuration.xml new file mode 100644 index 00000000..1c06fa48 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/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/cfe-patch-method/snapshots/check-clean/Ext/ClientApplicationInterface.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод.xml new file mode 100644 index 00000000..db9dd27b --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод.xml @@ -0,0 +1,18 @@ + + + + + + Adopted + БатчМод + + UUID-002 + false + false + false + false + false + false + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод/Ext/Module.bsl b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод/Ext/Module.bsl new file mode 100644 index 00000000..9917a366 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/CommonModules/БатчМод/Ext/Module.bsl @@ -0,0 +1,12 @@ +#Область ПрограммныйИнтерфейс +&ИзменениеИКонтроль("РасчётА") +Функция Тест_РасчётА() + Х = 1; + Возврат Х; +КонецФункции +&ИзменениеИКонтроль("РасчётБ") +Функция Тест_РасчётБ() + Y = 2; + Возврат Y; +КонецФункции +#КонецОбласти diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Configuration.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Configuration.xml new file mode 100644 index 00000000..91a980e8 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Configuration.xml @@ -0,0 +1,72 @@ + + + + + + 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 + + + + Adopted + Тест + + + ru + Тест + + + + Customization + true + Тест_ + Version8_3_24 + ManagedApplication + + PlatformApplication + + Russian + + Role.Тест_ОсновнаяРоль + + + + Language.Русский + + + + + + TaxiEnableVersion8_2 + + + Русский + Тест_ОсновнаяРоль + БатчМод + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Languages/Русский.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Languages/Русский.xml new file mode 100644 index 00000000..c21624f5 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Languages/Русский.xml @@ -0,0 +1,13 @@ + + + + + + Adopted + Русский + + UUID-002 + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Roles/Тест_ОсновнаяРоль.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Roles/Тест_ОсновнаяРоль.xml new file mode 100644 index 00000000..ec9dfbaf --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Ext/Roles/Тест_ОсновнаяРоль.xml @@ -0,0 +1,10 @@ + + + + + Тест_ОсновнаяРоль + + + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Languages/Русский.xml b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/cfe-patch-method/snapshots/check-clean/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/cfe-patch-method/snapshots/resync-conflict/Ext/CommonModules/РесинкКонфл/Ext/Module.bsl b/tests/skills/cases/cfe-patch-method/snapshots/resync-conflict/Ext/CommonModules/РесинкКонфл/Ext/Module.bsl index 06f77da8..987ce40d 100644 --- a/tests/skills/cases/cfe-patch-method/snapshots/resync-conflict/Ext/CommonModules/РесинкКонфл/Ext/Module.bsl +++ b/tests/skills/cases/cfe-patch-method/snapshots/resync-conflict/Ext/CommonModules/РесинкКонфл/Ext/Module.bsl @@ -3,7 +3,7 @@ Функция Тест_Расчёт(А, Б) Возврат 0; // [РЕСИНК-КОНФЛИКТ] перенесите блоки ниже вручную — исходный якорь изменился в новой версии оригинала. - // Материалы для анализа см. в выводе команды (файлы base/local/remote/merged). + // Материалы: см. index.md / conflict.md в merge-воркспейсе (путь в выводе). #Вставка Итог = Итог + Б; #КонецВставки