From dcce32faed3a19745bf4b2b820df1b53efa0dd1b Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 18 Jul 2026 21:07:41 +0300 Subject: [PATCH] =?UTF-8?q?refactor(cfe-patch-method):=20=D1=8D=D1=80?= =?UTF-8?q?=D0=B3=D0=BE=D0=BD=D0=BE=D0=BC=D0=B8=D0=BA=D0=B0=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=B0=20=D0=BF=D0=BE=20=D0=B8=D1=82=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D0=BC=20dogfood?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit По результатам прогона субагентом реального сценария (адаптация метода Бухгалтерии + рефакторинг оригинала → конфликт): - SKILL.md: убрана протёкшая и УСТАРЕВШАЯ реализация — раздел про merge-воркспейс называл файлы merged.bsl/diff.txt, которых больше нет; раздел «Проверка/актуализация пачкой» дублировал рантайм-вывод. Оставлено только решенческое (режимы, область, зона ответственности, зачем проактивно). - conflict.md: к каждой неразмещённой вставке добавлена привязка к якорю (после/перед из local) и подсказка «куда переносить» (якорь вынесен/отрефакторен → ищи в диффе новый вызов, размещай пост-обработкой) + напоминание сохранить BOM. Диагноз дрейфа якоря теперь виден, не нужно грепать вручную. - Согласование числительных в итог-строках (было «1 конфликтов»). Паритет ps1<->py, 16 кейсов зелёные в обоих. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/cfe-patch-method/SKILL.md | 22 ++++--------- .../scripts/cfe-patch-method.ps1 | 28 ++++++++++++---- .../scripts/cfe-patch-method.py | 32 +++++++++++++++---- .../cfe-patch-method/actualize-batch.json | 2 +- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/.claude/skills/cfe-patch-method/SKILL.md b/.claude/skills/cfe-patch-method/SKILL.md index 75c0f963..826c6f13 100644 --- a/.claude/skills/cfe-patch-method/SKILL.md +++ b/.claude/skills/cfe-patch-method/SKILL.md @@ -80,25 +80,17 @@ allowed-tools: - `[АКТУАЛЕН]` — оригинал не менялся, изменений нет; - `[АКТУАЛИЗИРОВАН]` — тело обновлено по новому оригиналу, правки (`#Вставка`/`#Удаление`) перенесены автоматически (в выводе — сводка перенесённого); -- `[АКТУАЛИЗИРОВАН-ЧАСТИЧНО]` — часть правок не удалось разместить (окружающий контекст изменился). Они помечены `// [РЕСИНК-КОНФЛИКТ]` и не потеряны; в системный temp выгружается merge-воркспейс (`base.bsl` — предок, `local.bsl` — твоя версия с маркерами, `remote.bsl` — новый оригинал, `merged.bsl` — результат, `diff.txt` — base→remote) — путь печатается в выводе. Проверь конфликтные блоки и размести вручную. +- `[АКТУАЛИЗИРОВАН-ЧАСТИЧНО]` — часть правок не удалось разместить (якорь изменился в новом оригинале). Они помечены `// [РЕСИНК-КОНФЛИКТ]` в модуле и не потеряны. Путь к merge-воркспейсу печатается в выводе — начни с `index.md`, дальше по каждому конфликту его `conflict.md` (см. ниже), размести блоки вручную в `.bsl` расширения. Повторный вызов `Before`/`After`/`Instead` для уже перехваченного метода не создаёт дубль (`[ПРОПУЩЕН]`). -## Проверка и актуализация пачкой (`-Check` / `-Actualize`) +## Пакетно: `-Check` / `-Actualize` -После обновления конфигурации контролируемые методы (`&ИзменениеИКонтроль`) могут разойтись с -исходником — платформа при загрузке об этом молчит, ошибка всплывает лишь в рантайме. Эти режимы -проходят по **всем** `&ИзменениеИКонтроль` расширения (или сузить: `-ModulePath` — модуль, `+ -MethodName` — один): - -- **`-Check`** — только отчёт: какие методы дрейфнули (`ДРЕЙФ` / `КОНФЛИКТ` / `МЕТОД-ИСЧЕЗ`); актуальные - считаются числом (`100/103 актуальны`). Ничего не пишет. Код возврата **1**, если есть что чинить. -- **`-Actualize`** — чинит: авто-переносит правки, для неоднозначных — merge-воркспейс. - -Зона ответственности — **только тело `&ИзменениеИКонтроль`** (не свойства/формы/сигнатуры). - -Merge-воркспейс конфликтов (`-Actualize`) в системном temp: `index.md` (тонкий список конфликтов с -путями к `.bsl` расширения для правки) + подпапка на каждый конфликтный метод с `conflict.md` -(что перенести + дифф) и `base/local/remote`. Путь печатается в выводе. +По **всем** `&ИзменениеИКонтроль` расширения (сузить: `-ModulePath` — модуль, `+ -MethodName` — один); +затрагивают **только тело** контролируемых методов. `-Check` — отчёт по дрейфу (`exit 1`, если есть), +ничего не пишет; `-Actualize` — авто-перенос правок, конфликты уходят в merge-воркспейс (путь и порядок +разбора печатаются в выводе). Полезно после обновления КФ — о рассинхроне контроля платформа при +загрузке молчит. ## Команда 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 2483f14f..afe21e3f 100644 --- a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 +++ b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.ps1 @@ -530,10 +530,20 @@ function Write-ConflictFolder { $md += "Метод: $existingName (&ИзменениеИКонтроль(`"$($method.Canonical)`"))" $md += "Причина: $(Get-ResyncConflictReason $disputed)" $md += "" - $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 += "" + if ($d.Kind -eq 'insert') { + $md += "Вставка. В вашей версии (local) блок стоял по якорю:" + if ($d.Before -and $d.Before.Count -gt 0) { $md += " после:"; foreach ($l in $d.Before) { $md += " $l" } } else { $md += " после: (начало метода)" } + if ($d.After -and $d.After.Count -gt 0) { $md += " перед:"; foreach ($l in $d.After) { $md += " $l" } } else { $md += " перед: (конец метода)" } + $md += "Якорь изменился/исчез в новом оригинале (remote) — потому блок не лёг автоматически (см. дифф base→remote ниже)." + $md += "Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM). Блок:" + $md += "#Вставка"; foreach ($l in $d.Lines) { $md += $l }; $md += "#КонецВставки" + } else { + $md += "Удаление. Строки для удаления не найдены в новом оригинале (изменились/исчезли):" + foreach ($l in $d.Lines) { $md += " - $($l.Trim())" } + } } $md += "" $md += "## Дифф base→remote (что изменилось в оригинале)" @@ -588,7 +598,11 @@ function Invoke-Resync { $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 } } + if ($null -eq $k) { + $dbefore = @(); if ($op.After -ge 0) { $bz = [Math]::Max(0, $op.After - 2); for ($z = $bz; $z -le $op.After; $z++) { $dbefore += $v1[$z] } } + $dafter = @(); $az = [Math]::Min($v1.Count - 1, $op.After + 3); for ($z = $op.After + 1; $z -le $az; $z++) { $dafter += $v1[$z] } + $disputed += @{ Kind = 'insert'; Lines = $op.Lines; Before = $dbefore; After = $dafter } + } 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 { @@ -659,7 +673,7 @@ function Write-ResyncIndex { $upd = @($results | Where-Object { $_.Status -eq 'АКТУАЛИЗИРОВАН' }).Count $lines = @() $lines += "[$verb] $extName -> $configPath" - $lines += "Итог: $actual/$total актуальны · $upd актуализировано · $($conflicts.Count) конфликтов" + $lines += "Итог: $actual/$total актуальны · актуализировано: $upd · конфликтов: $($conflicts.Count)" $lines += "" $lines += "Конфликты — править .bsl расширения:" foreach ($c in $conflicts) { @@ -759,12 +773,12 @@ if ($Check -or $Actualize) { $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 требуют внимания" + 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 частично" + 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 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 6c4a116b..41583bc0 100644 --- a/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py +++ b/.claude/skills/cfe-patch-method/scripts/cfe-patch-method.py @@ -597,7 +597,7 @@ def main(): 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 требуют внимания" + 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)) @@ -606,7 +606,7 @@ def main(): 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)) + 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) @@ -868,14 +868,30 @@ def write_conflict_folder(folder, method_id, ext_bsl, existing_name, method, v1, md.append('Метод: %s (&ИзменениеИКонтроль("%s"))' % (existing_name, method["canonical"])) md.append("Причина: %s" % conflict_reason(disputed)) md.append("") - md.append("## Не размещено — перенести в метод вручную") + md.append("## Не размещено — перенести вручную") for d in disputed: + md.append("") if d["kind"] == "insert": + md.append("Вставка. В вашей версии (local) блок стоял по якорю:") + if d.get("before"): + md.append(" после:") + for l in d["before"]: + md.append(" " + l) + else: + md.append(" после: (начало метода)") + if d.get("after"): + md.append(" перед:") + for l in d["after"]: + md.append(" " + l) + else: + md.append(" перед: (конец метода)") + md.append("Якорь изменился/исчез в новом оригинале (remote) — потому блок не лёг автоматически (см. дифф base→remote ниже).") + md.append("Куда переносить: если якорного кода в новом методе больше нет — он, вероятно, вынесен/отрефакторен (ищите в диффе новый вызов/процедуру). Размести адаптацию по смыслу: например пост-обработкой после нового вызова, либо в заимствованной процедуре, куда переехал код. При правке файла сохрани кодировку (UTF-8 с BOM). Блок:") md.append("#Вставка"); md.extend(d["lines"]); md.append("#КонецВставки") else: - md.append("// не удалось найти для удаления:") + md.append("Удаление. Строки для удаления не найдены в новом оригинале (изменились/исчезли):") for l in d["lines"]: - md.append("// " + l.strip()) + md.append(" - " + l.strip()) md.append("") md.append("## Дифф base→remote (что изменилось в оригинале)") for l in v1: @@ -929,7 +945,9 @@ def resync_one(ext_bsl, ext_lines, dup, method, logical_module, conflict_folder, after_lines = v1norm[after + 1:after + 4] k = resolve_insertion_point(v2norm, before_lines, after_lines) if k is None: - disputed.append({"kind": "insert", "lines": op["lines"]}) + dbefore = v1[max(0, after - 2):after + 1] if after >= 0 else [] + dafter = v1[after + 1:after + 4] + disputed.append({"kind": "insert", "lines": op["lines"], "before": dbefore, "after": dafter}) elif k < 0: insert_top.append(op["lines"]); transferred += 1 else: @@ -1006,7 +1024,7 @@ def write_resync_index(run_root, results, ext_name, config_path, verb): 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("Итог: %d/%d актуальны · актуализировано: %d · конфликтов: %d" % (actual, total, upd, len(conflicts))) lines.append("") lines.append("Конфликты — править .bsl расширения:") for c in conflicts: diff --git a/tests/skills/cases/cfe-patch-method/actualize-batch.json b/tests/skills/cases/cfe-patch-method/actualize-batch.json index dec30cdd..8d65973c 100644 --- a/tests/skills/cases/cfe-patch-method/actualize-batch.json +++ b/tests/skills/cases/cfe-patch-method/actualize-batch.json @@ -29,5 +29,5 @@ ], "params": { "extensionPath": "ext" }, "args_extra": ["-Actualize"], - "expect": { "stdoutContains": "1 актуализировано" } + "expect": { "stdoutContains": "актуализировано: 1" } }