feat(skd-decompile): scaffold — Ring 3 fail-fast, sentinel/warnings, query extraction

Layer 1 of the skd-decompile plan: SKILL.md with disable-model-invocation,
ps1 skeleton with XML→JSON pipeline, namespace probe for non-DCS root,
sentinel/warnings accumulator, and DataSetQuery extraction (query only).
Test case minimal-query demonstrates round-trip via skd-compile preRun.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-21 13:40:46 +03:00
parent 048edafc15
commit 5ec21f24b4
4 changed files with 253 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
---
name: skd-decompile
description: Декомпиляция схемы компоновки данных 1С (СКД) в JSON-черновик в формате skd-compile. Используй когда нужно создать новый отчёт по образцу существующего или провести структурный рефакторинг. Для точечных правок используй skd-edit
argument-hint: <TemplatePath> [-OutputPath <out.json>]
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /skd-decompile — извлечение JSON-черновика из Template.xml СКД
Читает существующий `Template.xml` (DataCompositionSchema) и эмитит JSON в формате, который принимает `/skd-compile`. Получившийся JSON — **черновик**: гарантируется только структурная эквивалентность, не байтовая.
## Когда использовать
- **Scaffold нового отчёта по образцу.** Взять существующий СКД, получить JSON, поправить параметры/поля/шаблоны, скомпилировать в новый отчёт.
- **Глобальный рефакторинг.** Когда правка структурная (переписать вариант, перерисовать шаблон), а не точечная.
## Когда **не** использовать
- **Точечные правки готового отчёта** — добавить поле, фильтр, итог, переименовать. Для этого есть `/skd-edit`: точечно, без полной реконструкции, без риска потерь.
- **Анализ схемы** — для обзора используй `/skd-info` (overview/query/fields/variant/templates).
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `TemplatePath` | Путь к Template.xml (обязательный) |
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
```powershell
# В файл (рядом, если есть warnings, кладётся <basename>.warnings.md)
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>" -OutputPath "<out.json>"
# В stdout
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>"
```
## Гарантии и ограничения
- **JSON всегда валиден** — компилируется через `skd-compile` без синтаксических ошибок.
- **Покрытие — DSL `skd-compile`.** Конструкции XML вне DSL отмечаются sentinel-объектом `{"__unsupported__": {...}}` и описаны в `<basename>.warnings.md` рядом с JSON. `skd-compile` фейлится при наличии sentinel — это специально, чтобы пользователь сначала разобрался с непокрытым.
- **Не байтовая эквивалентность.** После round-trip XML структурно эквивалентен оригиналу, но порядок атрибутов/секций может отличаться.
- **Стиль ячейки** определяется по совпадению с одним из built-in (`header`/`data`/`subheader`/`total`) или user-стилей из `presets/skills/skd/skd-styles.json`. Точечный custom appearance не сворачивается → sentinel.
## Не поддерживается (fail-fast)
- Picture cells в шаблонах (`<dcsat:Picture>`).
- Параметры типа ХранилищеЗначения.
- Sibling templates / templateCondition (вариативные шаблоны).
- Не-СКД корневые XML (например, spreadsheet `<document>` — для них есть `/mxl-decompile`).
При обнаружении — скрипт пишет в stderr понятное сообщение и завершается с ненулевым кодом.
## Верификация
```
/skd-compile -DefinitionFile <out.json> -OutputPath <new-Template.xml> — обратная компиляция
/skd-validate <new-Template.xml> — валидация результата
/skd-info <new-Template.xml> — визуальный осмотр
```
@@ -0,0 +1,159 @@
# skd-decompile v0.1 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[Alias('Path')]
[string]$TemplatePath,
[string]$OutputPath
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 0. Resolve and validate input ---
if (-not (Test-Path $TemplatePath)) {
Write-Error "Template not found: $TemplatePath"
exit 1
}
$TemplatePath = (Resolve-Path $TemplatePath).Path
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($TemplatePath)
$root = $xmlDoc.DocumentElement
# Ring 3: not a DataCompositionSchema → fail-fast
if ($root.LocalName -ne 'DataCompositionSchema') {
Write-Error "Root element <$($root.LocalName)> is not <DataCompositionSchema>. This is not a SKD template (perhaps a spreadsheet — use /mxl-decompile)."
exit 2
}
# --- 1. Namespace manager ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("dcs", "http://v8.1c.ru/8.1/data-composition-system/schema")
$ns.AddNamespace("dcscom", "http://v8.1c.ru/8.1/data-composition-system/common")
$ns.AddNamespace("dcscor", "http://v8.1c.ru/8.1/data-composition-system/core")
$ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings")
$ns.AddNamespace("dcsat", "http://v8.1c.ru/8.1/data-composition-system/areatemplate")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
# Root may use default namespace = schema; XPath needs explicit prefix
$rootNS = $root.NamespaceURI
if (-not $rootNS) { $rootNS = "http://v8.1c.ru/8.1/data-composition-system/schema" }
# Re-bind dcs to actual root namespace if different
$ns.AddNamespace("r", $rootNS)
# --- 2. Warnings accumulator ---
$script:warnings = @()
$script:warningCounter = 0
function Add-Warning {
param([string]$kind, [string]$loc, [string]$detail)
$script:warningCounter++
$id = "W{0:D3}" -f $script:warningCounter
$script:warnings += [ordered]@{ id = $id; kind = $kind; loc = $loc; detail = $detail }
return $id
}
function New-Sentinel {
param([string]$kind, [string]$loc, [string]$detail)
$id = Add-Warning -kind $kind -loc $loc -detail $detail
return [ordered]@{ '__unsupported__' = [ordered]@{ id = $id; kind = $kind; loc = $loc } }
}
# --- 3. Extract: dataSets (query only, no fields yet) ---
function Get-Text {
param($node, [string]$xpath)
if (-not $node) { return $null }
$n = $node.SelectSingleNode($xpath, $ns)
if ($n) { return $n.InnerText } else { return $null }
}
$dataSets = @()
$dsNodes = $root.SelectNodes("r:dataSet", $ns)
foreach ($dsNode in $dsNodes) {
$xsiType = $dsNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
$name = Get-Text $dsNode "r:name"
$ds = [ordered]@{ name = $name }
switch -Regex ($xsiType) {
'DataSetQuery$' {
$query = Get-Text $dsNode "r:query"
# Decode XML entities — XmlDocument already decoded &amp; → & in InnerText
$ds['query'] = $query
# fields — layer 3
}
'DataSetObject$' {
$ds['objectName'] = Get-Text $dsNode "r:objectName"
}
'DataSetUnion$' {
$ds['__unsupported__'] = (New-Sentinel -kind 'DataSetUnion' -loc "dataSet[name=$name]" -detail 'Реализуется в слое 15')['__unsupported__']
}
default {
$ds['__unsupported__'] = (New-Sentinel -kind "DataSetType:$xsiType" -loc "dataSet[name=$name]" -detail "Неизвестный тип набора данных")['__unsupported__']
}
}
$dataSets += $ds
}
# --- 4. Build top-level JSON object ---
$out = [ordered]@{
dataSets = $dataSets
}
# --- 5. Serialize ---
$json = $out | ConvertTo-Json -Depth 32
# Unescape \uXXXX → UTF-8 literals (PS 5.1 ConvertTo-Json escapes non-ASCII)
$json = [regex]::Replace($json, '\\u([0-9a-fA-F]{4})', {
param($m)
[char][int]("0x" + $m.Groups[1].Value)
})
if ($OutputPath) {
if (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
$OutputPath = Join-Path (Get-Location).Path $OutputPath
}
$enc = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($OutputPath, $json, $enc)
# Write warnings.md alongside, if any
if ($script:warnings.Count -gt 0) {
$wPath = [System.IO.Path]::ChangeExtension($OutputPath, $null).TrimEnd('.') + '.warnings.md'
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine("# skd-decompile warnings")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("Source: $TemplatePath")
[void]$sb.AppendLine("")
foreach ($w in $script:warnings) {
[void]$sb.AppendLine("- **$($w.id)** ($($w.kind)) at `$($w.loc)`: $($w.detail)")
}
[System.IO.File]::WriteAllText($wPath, $sb.ToString(), $enc)
Write-Host "Warnings written: $wPath ($($script:warnings.Count) issue(s))" -ForegroundColor Yellow
}
# Summary to stderr
$stats = "dataSets=$($dataSets.Count), warnings=$($script:warnings.Count)"
[Console]::Error.WriteLine("Decompiled: $stats")
} else {
Write-Output $json
if ($script:warnings.Count -gt 0) {
[Console]::Error.WriteLine("Warnings ($($script:warnings.Count)):")
foreach ($w in $script:warnings) {
[Console]::Error.WriteLine(" $($w.id) [$($w.kind)] $($w.loc): $($w.detail)")
}
}
}
@@ -0,0 +1,11 @@
{
"script": "skd-decompile/scripts/skd-decompile",
"setup": "none",
"args": [
{ "flag": "-TemplatePath", "from": "workPath", "field": "templatePath" }
],
"snapshot": {
"root": "workDir",
"normalizeUuids": false
}
}
@@ -0,0 +1,19 @@
{
"name": "Минимальный СКД с одним DataSetQuery",
"preRun": [
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "НаборДанных1",
"query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
"fields": ["Наименование"]
}]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"expect": { "stdoutContains": "НаборДанных1" }
}