Merge branch 'dev'

This commit is contained in:
Nick Shirokov
2026-02-16 17:38:33 +03:00
11 changed files with 1861 additions and 18 deletions
+14 -10
View File
@@ -38,7 +38,7 @@ allowed-tools:
"user": "Admin",
"password": "",
"aliases": ["dev", "разработка"],
"branches": ["dev", "develop"],
"branches": ["dev", "develop", "feature/*"],
"configSrc": "C:\\WS\\myapp\\cfsrc"
},
{
@@ -77,22 +77,26 @@ allowed-tools:
| `user` | string | нет | Имя пользователя 1С |
| `password` | string | нет | Пароль |
| `aliases` | string[] | нет | Альтернативные имена для быстрого доступа |
| `branches` | string[] | нет | Git-ветки, привязанные к этой базе |
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`), привязанные к этой базе |
| `configSrc` | string | нет | Каталог XML-выгрузки конфигурации |
## Алгоритм разрешения базы данных
Этот алгоритм используется ВСЕМИ навыками `db-*` для определения целевой базы.
Этот алгоритм используется ВСЕМИ навыками (`db-*`, `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) для определения целевой базы.
1. Прочитай `.v8-project.json` (в корне проекта или ближайшем родительском каталоге)
2. Если пользователь указал базу — ищи совпадение в таком порядке:
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
2. Если пользователь указал **базу по имени** — ищи совпадение в таком порядке:
1. По `id` (точное совпадение)
2. По `aliases` (совпадение в массиве с учётом морфологии: «тестовую» = «тестовая» = «тестовой»)
3. По `branches` (совпадение с текущей Git-веткой)
4. По `name` (нечёткое совпадение с учётом морфологии и регистра)
3. Если пользователь не указал базу — используй `default`
4. Если не найдено или неоднозначно — спроси пользователя
5. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
3. По `name` (нечёткое совпадение с учётом морфологии и регистра)
3. Если пользователь **не указал** базу — сопоставь текущую ветку Git с `databases[].branches`:
- Точное совпадение: ветка `dev``"branches": ["dev"]`
- Glob-паттерн: ветка `release/2.1``"branches": ["release/*"]`
4. Если ветка не совпала — используй `default`
5. Если не найдено или неоднозначно — спроси пользователя
6. Если файл `.v8-project.json` не найден — спроси параметры подключения и предложи создать файл
После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`.
### Автоопределение платформы
+88
View File
@@ -0,0 +1,88 @@
---
name: epf-validate
description: Валидация внешней обработки 1С (EPF). Используй после создания или модификации обработки для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /epf-validate — валидация внешней обработки (EPF)
Проверяет структурную корректность XML-исходников внешней обработки: корневую структуру, InternalInfo, свойства, ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов.
Скрипт также работает для внешних отчётов (ERF) — автоопределение по типу элемента. См. `/erf-validate`.
## Использование
```
/epf-validate <ObjectPath>
```
## Параметры
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу обработки |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\epf-validate\scripts\epf-validate.ps1 -ObjectPath "<путь>"
```
## Выполняемые проверки
| # | Проверка | Серьёзность |
|----|-------------------------------------------------------|--------------|
| 1 | Root structure: MetaDataObject/ExternalDataProcessor | ERROR |
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
| 3 | Properties: Name (identifier), Synonym | ERROR / WARN |
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
| 5 | Cross-references: DefaultForm → Form, AuxiliaryForm | ERROR / WARN |
| 6 | Attributes: UUID, Name, Type | ERROR |
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
## Вывод
```
=== Validation: EPF.МояОбработка ===
[OK] 1. Root structure: MetaDataObject/ExternalDataProcessor, version 2.17
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
[OK] 3. Properties: Name="МояОбработка", Synonym present, DefaultForm set
[OK] 4. ChildObjects: Attribute(3), TabularSection(1), Form(1)
[OK] 5. Cross-references: DefaultForm valid
[OK] 6. Attributes: 3 checked (UUID, Name, Type)
[OK] 7. TabularSections: 1 sections, 5 inner attributes
[OK] 8. Name uniqueness: 6 names, all unique
[OK] 9. File existence: 3 files verified
[OK] 10. Form descriptors: 1 checked
=== Result: 0 errors, 0 warnings ===
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Верификация
```
/epf-init <Name> — создать обработку
/epf-validate src/<Name>.xml — проверить результат
/epf-build <Name> — собрать EPF
```
## Когда использовать
- **После `/epf-init`**: проверить scaffold
- **После добавления формы/макета**: убедиться что ChildObjects, файлы и ссылки корректны
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
- **При отладке сборки**: найти причину ошибки Designer
@@ -0,0 +1,824 @@
# epf-validate v1.0 — Validate 1C external data processor / report structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
param(
[Parameter(Mandatory)]
[string]$ObjectPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
$ObjectPath = Join-Path (Get-Location).Path $ObjectPath
}
if (Test-Path $ObjectPath -PathType Container) {
$dirName = Split-Path $ObjectPath -Leaf
$candidate = Join-Path $ObjectPath "$dirName.xml"
if (Test-Path $candidate) {
$ObjectPath = $candidate
} else {
$xmlFiles = @(Get-ChildItem $ObjectPath -Filter "*.xml" -File | Select-Object -First 1)
if ($xmlFiles.Count -gt 0) {
$ObjectPath = $xmlFiles[0].FullName
} else {
Write-Host "[ERROR] No XML file found in directory: $ObjectPath"
exit 1
}
}
}
if (-not (Test-Path $ObjectPath)) {
Write-Host "[ERROR] File not found: $ObjectPath"
exit 1
}
$resolvedPath = (Resolve-Path $ObjectPath).Path
$srcDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
Out-Line "[OK] $msg"
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
$classIds = @{
"ExternalDataProcessor" = "c3831ec8-d8d5-4f93-8a22-f9bfae07327f"
"ExternalReport" = "e41aff26-25cf-4bb6-b6c1-3f478a75f374"
}
$allowedChildTypes = @("Attribute","TabularSection","Form","Template","Command")
# Expected order of child types in ChildObjects
$childTypeOrder = @{
"Attribute" = 0
"TabularSection" = 1
"Form" = 2
"Template" = 3
"Command" = 4
}
$validPropertyValues = @{
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
}
# Detect type: ExternalDataProcessor or ExternalReport
$typeNode = $null
$mdType = ""
$childElements = @()
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.NamespaceURI -eq $expectedNs) {
$childElements += $child
}
}
if ($childElements.Count -eq 0) {
Report-Error "1. No metadata type element found inside MetaDataObject"
& $finalize
exit 1
} elseif ($childElements.Count -gt 1) {
Report-Error "1. Multiple type elements found: $($childElements | ForEach-Object { $_.LocalName })"
$check1Ok = $false
}
$typeNode = $childElements[0]
$mdType = $typeNode.LocalName
if ($mdType -ne "ExternalDataProcessor" -and $mdType -ne "ExternalReport") {
Report-Error "1. Unexpected type '$mdType' (expected ExternalDataProcessor or ExternalReport)"
& $finalize
exit 1
}
$typeUuid = $typeNode.GetAttribute("uuid")
if (-not $typeUuid) {
Report-Error "1. Missing uuid on <$mdType>"
$check1Ok = $false
} elseif ($typeUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$typeUuid' on <$mdType>"
$check1Ok = $false
}
# Get object name
$propsNode = $typeNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
$shortType = if ($mdType -eq "ExternalDataProcessor") { "EPF" } else { "ERF" }
$script:output.Insert(0, "=== Validation: $shortType.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/$mdType, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $typeNode.SelectSingleNode("md:InternalInfo", $ns)
if (-not $internalInfo) {
Report-Error "2. InternalInfo block missing"
} else {
$check2Ok = $true
# ContainedObject / ClassId
$containedObj = $internalInfo.SelectSingleNode("xr:ContainedObject", $ns)
if (-not $containedObj) {
Report-Error "2. InternalInfo: missing xr:ContainedObject"
$check2Ok = $false
} else {
$classIdNode = $containedObj.SelectSingleNode("xr:ClassId", $ns)
$objectIdNode = $containedObj.SelectSingleNode("xr:ObjectId", $ns)
$expectedClassId = $classIds[$mdType]
if (-not $classIdNode -or -not $classIdNode.InnerText) {
Report-Error "2. Missing ClassId in ContainedObject"
$check2Ok = $false
} elseif ($classIdNode.InnerText -ne $expectedClassId) {
Report-Error "2. ClassId is '$($classIdNode.InnerText)', expected '$expectedClassId' for $mdType"
$check2Ok = $false
}
if ($objectIdNode -and $objectIdNode.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ObjectId UUID"
$check2Ok = $false
}
}
# GeneratedType — expect exactly 1 with category "Object"
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
if ($genTypes.Count -eq 0) {
Report-Error "2. No GeneratedType entries found"
$check2Ok = $false
} else {
foreach ($gt in $genTypes) {
$gtName = $gt.GetAttribute("name")
$gtCategory = $gt.GetAttribute("category")
if ($gtCategory -ne "Object") {
Report-Warn "2. Unexpected GeneratedType category '$gtCategory' (expected 'Object')"
}
# Name format: ExternalDataProcessorObject.Name or ExternalReportObject.Name
$expectedPrefix = "${mdType}Object."
if ($gtName -and $objName -ne "(unknown)" -and -not $gtName.StartsWith($expectedPrefix)) {
Report-Warn "2. GeneratedType name '$gtName' does not start with '$expectedPrefix'"
}
$typeId = $gt.SelectSingleNode("xr:TypeId", $ns)
$valueId = $gt.SelectSingleNode("xr:ValueId", $ns)
if ($typeId -and $typeId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid TypeId UUID in GeneratedType"
$check2Ok = $false
}
if ($valueId -and $valueId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ValueId UUID in GeneratedType"
$check2Ok = $false
}
}
}
if ($check2Ok) {
Report-OK "2. InternalInfo: ClassId correct, $($genTypes.Count) GeneratedType"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Properties ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Properties: Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Properties: Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
if ($nameVal.Length -gt 80) {
Report-Warn "3. Properties: Name '$nameVal' exceeds 80 characters ($($nameVal.Length))"
}
}
# Synonym
$synNode = $propsNode.SelectSingleNode("md:Synonym", $ns)
$synPresent = $false
if ($synNode) {
$synItem = $synNode.SelectSingleNode("v8:item", $ns)
if ($synItem) {
$synContent = $synItem.SelectSingleNode("v8:content", $ns)
if ($synContent -and $synContent.InnerText) {
$synPresent = $true
}
}
}
# DefaultForm cross-reference (collected now, checked after ChildObjects)
$defaultFormNode = $propsNode.SelectSingleNode("md:DefaultForm", $ns)
$defaultFormVal = if ($defaultFormNode -and $defaultFormNode.InnerText.Trim()) { $defaultFormNode.InnerText.Trim() } else { "" }
# AuxiliaryForm cross-reference
$auxFormNode = $propsNode.SelectSingleNode("md:AuxiliaryForm", $ns)
$auxFormVal = if ($auxFormNode -and $auxFormNode.InnerText.Trim()) { $auxFormNode.InnerText.Trim() } else { "" }
# ERF-specific: MainDataCompositionSchema
$mainDCSVal = ""
if ($mdType -eq "ExternalReport") {
$mainDCSNode = $propsNode.SelectSingleNode("md:MainDataCompositionSchema", $ns)
$mainDCSVal = if ($mainDCSNode -and $mainDCSNode.InnerText.Trim()) { $mainDCSNode.InnerText.Trim() } else { "" }
}
if ($check3Ok) {
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
$extras = ""
if ($defaultFormVal) { $extras += ", DefaultForm set" }
if ($mainDCSVal) { $extras += ", MainDCS set" }
Report-OK "3. Properties: Name=`"$objName`", $synInfo$extras"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: ChildObjects — allowed types and ordering ---
$childObjNode = $typeNode.SelectSingleNode("md:ChildObjects", $ns)
$formNames = @()
$templateNames = @()
$allChildNames = @{}
if ($childObjNode) {
$check4Ok = $true
$childCounts = @{}
$lastOrder = -1
$orderOk = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTag = $child.LocalName
if ($allowedChildTypes -notcontains $childTag) {
Report-Error "4. ChildObjects: disallowed element '$childTag'"
$check4Ok = $false
continue
}
if (-not $childCounts.ContainsKey($childTag)) {
$childCounts[$childTag] = 0
}
$childCounts[$childTag]++
# Check ordering
$thisOrder = $childTypeOrder[$childTag]
if ($thisOrder -lt $lastOrder -and $orderOk) {
Report-Warn "4. ChildObjects: '$childTag' appears after higher-order elements (expected: Attribute, TabularSection, Form, Template, Command)"
$orderOk = $false
}
$lastOrder = $thisOrder
# Collect Form and Template names (simple text content)
if ($childTag -eq "Form") {
$formNames += $child.InnerText.Trim()
} elseif ($childTag -eq "Template") {
$templateNames += $child.InnerText.Trim()
}
}
if ($check4Ok) {
$summary = ($childCounts.GetEnumerator() | Sort-Object { $childTypeOrder[$_.Name] } | ForEach-Object { "$($_.Name)($($_.Value))" }) -join ", "
if ($summary) {
Report-OK "4. ChildObjects: $summary"
} else {
Report-OK "4. ChildObjects: empty"
}
}
} else {
Report-OK "4. ChildObjects: absent"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: DefaultForm / MainDCS cross-references ---
$check5Ok = $true
if ($defaultFormVal) {
# Format: ExternalDataProcessor.Name.Form.FormName or ExternalReport.Name.Form.FormName
$expectedPrefix = "$mdType.$objName.Form."
if ($defaultFormVal.StartsWith($expectedPrefix)) {
$refFormName = $defaultFormVal.Substring($expectedPrefix.Length)
if ($formNames -notcontains $refFormName) {
Report-Error "5. DefaultForm references '$refFormName', but no such Form in ChildObjects"
$check5Ok = $false
}
} else {
Report-Warn "5. DefaultForm value '$defaultFormVal' has unexpected prefix (expected '$expectedPrefix...')"
}
}
if ($auxFormVal) {
$expectedPrefix = "$mdType.$objName.Form."
if ($auxFormVal.StartsWith($expectedPrefix)) {
$refFormName = $auxFormVal.Substring($expectedPrefix.Length)
if ($formNames -notcontains $refFormName) {
Report-Error "5. AuxiliaryForm references '$refFormName', but no such Form in ChildObjects"
$check5Ok = $false
}
}
}
if ($mainDCSVal -and $mdType -eq "ExternalReport") {
$expectedPrefix = "ExternalReport.$objName.Template."
if ($mainDCSVal.StartsWith($expectedPrefix)) {
$refTplName = $mainDCSVal.Substring($expectedPrefix.Length)
if ($templateNames -notcontains $refTplName) {
Report-Error "5. MainDataCompositionSchema references '$refTplName', but no such Template in ChildObjects"
$check5Ok = $false
}
} else {
Report-Warn "5. MainDataCompositionSchema value '$mainDCSVal' has unexpected prefix"
}
}
if ($check5Ok) {
$refs = @()
if ($defaultFormVal) { $refs += "DefaultForm" }
if ($auxFormVal) { $refs += "AuxiliaryForm" }
if ($mainDCSVal) { $refs += "MainDCS" }
if ($refs.Count -gt 0) {
Report-OK "5. Cross-references: $($refs -join ', ') valid"
} else {
Report-OK "5. Cross-references: none to check"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: Attributes — UUID, Name, Type ---
function Check-Attribute {
param(
[System.Xml.XmlNode]$node,
[string]$context
)
$uuid = $node.GetAttribute("uuid")
if (-not $uuid) {
Report-Error "6. $context Attribute missing uuid"
return $false
} elseif ($uuid -notmatch $guidPattern) {
Report-Error "6. $context Attribute has invalid uuid '$uuid'"
return $false
}
$elProps = $node.SelectSingleNode("md:Properties", $ns)
if (-not $elProps) {
Report-Error "6. $context Attribute (uuid=$uuid) missing Properties"
return $false
}
$elName = $elProps.SelectSingleNode("md:Name", $ns)
if (-not $elName -or -not $elName.InnerText) {
Report-Error "6. $context Attribute (uuid=$uuid) missing or empty Name"
return $false
}
$nameVal = $elName.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "6. $context Attribute '$nameVal' has invalid identifier"
return $false
}
$typeEl = $elProps.SelectSingleNode("md:Type", $ns)
if (-not $typeEl) {
Report-Error "6. $context Attribute '$nameVal' missing Type block"
return $false
}
$v8Types = $typeEl.SelectNodes("v8:Type", $ns)
$v8TypeSets = $typeEl.SelectNodes("v8:TypeSet", $ns)
if ($v8Types.Count -eq 0 -and $v8TypeSets.Count -eq 0) {
Report-Error "6. $context Attribute '$nameVal' Type block has no v8:Type or v8:TypeSet"
return $false
}
return $true
}
if ($childObjNode) {
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns)
$check6Ok = $true
$attrCount = 0
foreach ($attr in $attrs) {
if ($script:stopped) { break }
$ok = Check-Attribute -node $attr -context ""
if (-not $ok) { $check6Ok = $false }
$attrCount++
# Collect name for uniqueness
$ap = $attr.SelectSingleNode("md:Properties/md:Name", $ns)
if ($ap -and $ap.InnerText) {
$allChildNames["Attr:$($ap.InnerText)"] = $ap.InnerText
}
}
if ($attrCount -gt 0) {
if ($check6Ok) {
Report-OK "6. Attributes: $attrCount checked (UUID, Name, Type)"
}
} else {
Report-OK "6. Attributes: none"
}
} else {
Report-OK "6. Attributes: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: TabularSections ---
if ($childObjNode) {
$tsSections = $childObjNode.SelectNodes("md:TabularSection", $ns)
if ($tsSections.Count -gt 0) {
$check7Ok = $true
$tsCount = 0
$tsAttrTotal = 0
foreach ($ts in $tsSections) {
if ($script:stopped) { break }
$tsCount++
$tsUuid = $ts.GetAttribute("uuid")
if (-not $tsUuid -or $tsUuid -notmatch $guidPattern) {
Report-Error "7. TabularSection #${tsCount}: invalid or missing uuid"
$check7Ok = $false
}
$tsProps = $ts.SelectSingleNode("md:Properties", $ns)
$tsNameNode = if ($tsProps) { $tsProps.SelectSingleNode("md:Name", $ns) } else { $null }
$tsName = if ($tsNameNode -and $tsNameNode.InnerText) { $tsNameNode.InnerText } else { "(unnamed)" }
if (-not $tsNameNode -or -not $tsNameNode.InnerText) {
Report-Error "7. TabularSection #${tsCount}: missing or empty Name"
$check7Ok = $false
} elseif ($tsName -notmatch $identPattern) {
Report-Error "7. TabularSection '$tsName': invalid identifier"
$check7Ok = $false
}
$allChildNames["TS:$tsName"] = $tsName
# InternalInfo — expect 2 GeneratedType
$tsIntInfo = $ts.SelectSingleNode("md:InternalInfo", $ns)
if ($tsIntInfo) {
$tsGens = $tsIntInfo.SelectNodes("xr:GeneratedType", $ns)
if ($tsGens.Count -lt 2) {
Report-Warn "7. TabularSection '$tsName': expected 2 GeneratedType, found $($tsGens.Count)"
}
}
# Inner attributes
$tsChildObj = $ts.SelectSingleNode("md:ChildObjects", $ns)
if ($tsChildObj) {
$tsAttrs = $tsChildObj.SelectNodes("md:Attribute", $ns)
$tsAttrNames = @{}
foreach ($ta in $tsAttrs) {
$taOk = Check-Attribute -node $ta -context "TabularSection '$tsName'."
if (-not $taOk) { $check7Ok = $false }
$tsAttrTotal++
$taProps = $ta.SelectSingleNode("md:Properties/md:Name", $ns)
if ($taProps -and $taProps.InnerText) {
if ($tsAttrNames.ContainsKey($taProps.InnerText)) {
Report-Error "7. Duplicate attribute '$($taProps.InnerText)' in TabularSection '$tsName'"
$check7Ok = $false
} else {
$tsAttrNames[$taProps.InnerText] = $true
}
}
}
}
}
if ($check7Ok) {
Report-OK "7. TabularSections: $tsCount sections, $tsAttrTotal inner attributes"
}
} else {
Report-OK "7. TabularSections: none"
}
} else {
Report-OK "7. TabularSections: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Name uniqueness ---
$check8Ok = $true
# Collect all names: attributes + tabular sections + forms + templates + commands
$allNames = @{}
if ($childObjNode) {
$nameKinds = @(
@{ XPath = "md:Attribute"; Kind = "Attribute" },
@{ XPath = "md:TabularSection"; Kind = "TabularSection" },
@{ XPath = "md:Command"; Kind = "Command" }
)
foreach ($nk in $nameKinds) {
$nodes = $childObjNode.SelectNodes($nk.XPath, $ns)
foreach ($node in $nodes) {
$np = $node.SelectSingleNode("md:Properties/md:Name", $ns)
if ($np -and $np.InnerText) {
$nameVal = $np.InnerText
$key = "$($nk.Kind):$nameVal"
if ($allNames.ContainsKey($nameVal)) {
Report-Error "8. Duplicate name '$nameVal' ($($nk.Kind) conflicts with $($allNames[$nameVal]))"
$check8Ok = $false
} else {
$allNames[$nameVal] = $nk.Kind
}
}
}
}
# Forms and Templates are simple text nodes
foreach ($fn in $formNames) {
if ($allNames.ContainsKey($fn)) {
Report-Error "8. Duplicate name '$fn' (Form conflicts with $($allNames[$fn]))"
$check8Ok = $false
} else {
$allNames[$fn] = "Form"
}
}
foreach ($tn in $templateNames) {
if ($allNames.ContainsKey($tn)) {
Report-Error "8. Duplicate name '$tn' (Template conflicts with $($allNames[$tn]))"
$check8Ok = $false
} else {
$allNames[$tn] = "Template"
}
}
}
if ($check8Ok) {
Report-OK "8. Name uniqueness: $($allNames.Count) names, all unique"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: File existence (forms and templates on disk) ---
$check9Ok = $true
$filesChecked = 0
# Object directory: same level as root XML, named after the object
$objDir = Join-Path $srcDir $objName
foreach ($fn in $formNames) {
# FormName.xml — form descriptor
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
if (-not (Test-Path $formMetaXml)) {
Report-Error "9. Missing form descriptor: Forms/$fn.xml"
$check9Ok = $false
} else {
$filesChecked++
}
# FormName/Ext/Form.xml — form layout
$formXml = Join-Path (Join-Path (Join-Path (Join-Path $objDir "Forms") $fn) "Ext") "Form.xml"
if (-not (Test-Path $formXml)) {
Report-Error "9. Missing form layout: Forms/$fn/Ext/Form.xml"
$check9Ok = $false
} else {
$filesChecked++
}
}
foreach ($tn in $templateNames) {
# TemplateName.xml — template descriptor
$tplMetaXml = Join-Path (Join-Path $objDir "Templates") "$tn.xml"
if (-not (Test-Path $tplMetaXml)) {
Report-Error "9. Missing template descriptor: Templates/$tn.xml"
$check9Ok = $false
} else {
$filesChecked++
}
# TemplateName/Ext/Template.* — template content (extension varies)
$tplExtDir = Join-Path (Join-Path (Join-Path $objDir "Templates") $tn) "Ext"
if (Test-Path $tplExtDir) {
$tplFiles = @(Get-ChildItem $tplExtDir -Filter "Template.*" -File)
if ($tplFiles.Count -eq 0) {
Report-Error "9. Missing template content: Templates/$tn/Ext/Template.*"
$check9Ok = $false
} else {
$filesChecked++
}
} else {
Report-Error "9. Missing template Ext directory: Templates/$tn/Ext/"
$check9Ok = $false
}
}
# ObjectModule.bsl
$objModule = Join-Path (Join-Path $objDir "Ext") "ObjectModule.bsl"
if (Test-Path $objModule) {
$filesChecked++
}
if ($check9Ok) {
if ($filesChecked -gt 0) {
Report-OK "9. File existence: $filesChecked files verified"
} else {
Report-OK "9. File existence: no forms/templates to check"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 10: Form descriptors structure ---
$check10Ok = $true
$formsChecked = 0
foreach ($fn in $formNames) {
$formMetaXml = Join-Path (Join-Path $objDir "Forms") "$fn.xml"
if (-not (Test-Path $formMetaXml)) { continue }
try {
$fDoc = New-Object System.Xml.XmlDocument
$fDoc.PreserveWhitespace = $false
$fDoc.Load($formMetaXml)
$fRoot = $fDoc.DocumentElement
if ($fRoot.LocalName -ne "MetaDataObject") {
Report-Error "10. Form '$fn': root element is '$($fRoot.LocalName)', expected 'MetaDataObject'"
$check10Ok = $false
continue
}
$fTypeNode = $fRoot.SelectSingleNode("md:Form", $ns)
if (-not $fTypeNode) {
Report-Error "10. Form '$fn': missing <Form> element"
$check10Ok = $false
continue
}
$fUuid = $fTypeNode.GetAttribute("uuid")
if (-not $fUuid -or $fUuid -notmatch $guidPattern) {
Report-Error "10. Form '$fn': invalid or missing uuid"
$check10Ok = $false
}
$fProps = $fTypeNode.SelectSingleNode("md:Properties", $ns)
if ($fProps) {
$fName = $fProps.SelectSingleNode("md:Name", $ns)
if ($fName -and $fName.InnerText -ne $fn) {
Report-Error "10. Form '$fn': Name in descriptor is '$($fName.InnerText)', expected '$fn'"
$check10Ok = $false
}
# FormType should be Managed
$fType = $fProps.SelectSingleNode("md:FormType", $ns)
if ($fType -and $fType.InnerText -ne "Managed") {
Report-Warn "10. Form '$fn': FormType is '$($fType.InnerText)' (expected 'Managed')"
}
}
$formsChecked++
} catch {
Report-Error "10. Form '$fn': XML parse error: $($_.Exception.Message)"
$check10Ok = $false
}
}
if ($check10Ok) {
if ($formsChecked -gt 0) {
Report-OK "10. Form descriptors: $formsChecked checked"
} else {
Report-OK "10. Form descriptors: none to check"
}
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0
+88
View File
@@ -0,0 +1,88 @@
---
name: erf-validate
description: Валидация внешнего отчёта 1С (ERF). Используй после создания или модификации отчёта для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /erf-validate — валидация внешнего отчёта (ERF)
Проверяет структурную корректность XML-исходников внешнего отчёта: корневую структуру, InternalInfo, свойства (включая MainDataCompositionSchema), ChildObjects, реквизиты, табличные части, уникальность имён, наличие файлов форм и макетов.
Использует тот же скрипт, что и `/epf-validate` — автоопределение по типу элемента (ExternalReport).
## Использование
```
/erf-validate <ObjectPath>
```
## Параметры
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------------------|
| ObjectPath | да | — | Путь к корневому XML или каталогу отчёта |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\epf-validate\scripts\epf-validate.ps1 -ObjectPath "<путь>"
```
## Выполняемые проверки
| # | Проверка | Серьёзность |
|----|-------------------------------------------------------|--------------|
| 1 | Root structure: MetaDataObject/ExternalReport | ERROR |
| 2 | InternalInfo: ClassId, ContainedObject, GeneratedType | ERROR / WARN |
| 3 | Properties: Name, Synonym, MainDataCompositionSchema | ERROR / WARN |
| 4 | ChildObjects: допустимые типы, порядок | ERROR / WARN |
| 5 | Cross-references: DefaultForm, MainDCS → Template | ERROR / WARN |
| 6 | Attributes: UUID, Name, Type | ERROR |
| 7 | TabularSections: UUID, Name, GeneratedType, Attributes | ERROR / WARN |
| 8 | Уникальность имён (Attribute, TS, Form, Template, Command) | ERROR |
| 9 | Файлы: формы (.xml + Ext/Form.xml), макеты | ERROR |
| 10 | Дескрипторы форм: корневая структура, uuid, Name, FormType | ERROR / WARN |
## Вывод
```
=== Validation: ERF.МойОтчёт ===
[OK] 1. Root structure: MetaDataObject/ExternalReport, version 2.17
[OK] 2. InternalInfo: ClassId correct, 1 GeneratedType
[OK] 3. Properties: Name="МойОтчёт", Synonym present, MainDCS set
[OK] 4. ChildObjects: Form(1), Template(1)
[OK] 5. Cross-references: DefaultForm, MainDCS valid
[OK] 6. Attributes: none
[OK] 7. TabularSections: none
[OK] 8. Name uniqueness: 2 names, all unique
[OK] 9. File existence: 4 files verified
[OK] 10. Form descriptors: 1 checked
=== Result: 0 errors, 0 warnings ===
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Верификация
```
/erf-init <Name> --with-skd — создать отчёт с СКД
/erf-validate src/<Name>.xml — проверить результат
/erf-build <Name> — собрать ERF
```
## Когда использовать
- **После `/erf-init`**: проверить scaffold
- **После добавления формы/макета/СКД**: убедиться что ChildObjects и MainDCS корректны
- **После ручного редактирования XML**: выявить структурные ошибки до сборки
- **При отладке сборки**: найти причину ошибки Designer
+136
View File
@@ -0,0 +1,136 @@
---
name: meta-remove
description: Удалить объект метаданных из конфигурации 1С. Используй когда пользователь просит удалить, убрать объект из конфигурации
argument-hint: <ConfigDir> -Object <Type.Name>
allowed-tools:
- Bash
- Read
- Glob
- AskUserQuestion
---
# /meta-remove — удаление объекта метаданных
Безопасно удаляет объект из XML-выгрузки конфигурации. Перед удалением проверяет ссылки на объект в реквизитах, коде и других метаданных. Если ссылки найдены — удаление блокируется.
## Использование
```
/meta-remove <ConfigDir> -Object <Type.Name>
```
## Параметры
| Параметр | Обязательный | Описание |
|------------|:------------:|-------------------------------------------------|
| ConfigDir | да | Корневая директория выгрузки (где Configuration.xml) |
| Object | да | Тип и имя объекта: `Catalog.Товары`, `Document.Заказ` и т.д. |
| DryRun | нет | Только показать что будет удалено, без изменений |
| KeepFiles | нет | Не удалять файлы, только дерегистрировать |
| Force | нет | Удалить несмотря на найденные ссылки |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-remove\scripts\meta-remove.ps1 -ConfigDir "<путь>" -Object "Catalog.Товары"
```
## Что делает
1. **Находит файлы объекта**: `{TypePlural}/{Name}.xml` и `{TypePlural}/{Name}/`
2. **Проверяет ссылки** (блокирует при наличии, если нет `-Force`):
- XML-типы в реквизитах других объектов: `CatalogRef.Имя`, `DocumentRef.Имя` и т.д.
- BSL-код: `Справочники.Имя`, `Catalogs.Имя`, вызовы общих модулей
- Журналы документов, подписки на события, определяемые типы
3. **Удаляет из Configuration.xml**: убирает из `<ChildObjects>`
4. **Очищает подсистемы**: рекурсивно удаляет из `<Content>`
5. **Удаляет файлы**: XML-файл и каталог объекта
## Поддерживаемые типы
Catalog, Document, Enum, Constant, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, ExchangePlan, DocumentJournal, Report, DataProcessor, CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService, DefinedType, Role, Subsystem, CommonForm, CommonTemplate, CommonPicture, CommonAttribute, SessionParameter, FunctionalOption, FunctionalOptionsParameter, Sequence, FilterCriterion, SettingsStorage, XDTOPackage, WSReference, StyleItem, Language
## Вывод (объект без ссылок)
```
=== meta-remove: Catalog.Устаревший ===
[FOUND] Catalogs/Устаревший.xml
[FOUND] Catalogs/Устаревший/ (8 files)
--- Reference check ---
[OK] No references found
--- Configuration.xml ---
[OK] Removed <Catalog>Устаревший</Catalog> from ChildObjects
[OK] Configuration.xml saved
--- Subsystems ---
[OK] Removed from subsystem 'Справочники'
--- Files ---
[OK] Deleted directory: Catalogs/Устаревший/
[OK] Deleted file: Catalogs/Устаревший.xml
=== Done: 4 actions performed (1 subsystem references removed) ===
```
## Вывод (объект со ссылками — блокировка)
```
=== meta-remove: Catalog.Валюты ===
[FOUND] Catalogs/Валюты.xml
[FOUND] Catalogs/Валюты/ (4 files)
--- Reference check ---
[WARN] Found 3 reference(s) to Catalog.Валюты:
Documents/СчетНаОплату.xml
pattern: CatalogRef.Валюты
InformationRegisters/КурсыВалют.xml
pattern: CatalogRef.Валюты
CommonModules/РаботаСВалютами/Ext/Module.bsl
pattern: Справочники.Валюты
[ERROR] Cannot remove: object has 3 reference(s).
Use -Force to remove anyway, or fix references first.
```
Код возврата: 0 = успешно, 1 = ошибки или найдены ссылки.
## Проверяемые ссылки
| Категория | Паттерны поиска |
|-----------|----------------|
| XML-типы реквизитов | `CatalogRef.Name`, `DocumentRef.Name`, `EnumRef.Name` и др. |
| BSL-код (рус.) | `Справочники.Name`, `Документы.Name`, `Перечисления.Name` и др. |
| BSL-код (англ.) | `Catalogs.Name`, `Documents.Name`, `Enums.Name` и др. |
| Общие модули | `Name.` (вызовы методов), `<Handler>Name.`, `<MethodName>Name.` |
Ссылки из Configuration.xml, ConfigDumpInfo.xml и подсистем НЕ считаются блокирующими — они очищаются автоматически.
## Примеры
```powershell
# Проверка ссылок + dry run
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -DryRun
# Удалить объект без ссылок
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший"
# Принудительно удалить несмотря на ссылки
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -Force
# Только дерегистрировать (файлы оставить)
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Report.Старый" -KeepFiles
# Удалить общий модуль
... -ConfigDir src -Object "CommonModule.МойМодуль"
```
## Когда использовать
- **Рефакторинг**: удаление неиспользуемых объектов
- **Очистка**: удаление временных/тестовых объектов
- **Перенос**: удаление объекта перед пересозданием с другой структурой
@@ -0,0 +1,475 @@
# meta-remove v1.1 — Remove metadata object from 1C configuration dump
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ConfigDir,
[Parameter(Mandatory)]
[string]$Object,
[switch]$DryRun,
[switch]$KeepFiles,
[switch]$Force
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Type → plural directory mapping ---
$typePluralMap = @{
"Catalog" = "Catalogs"
"Document" = "Documents"
"Enum" = "Enums"
"Constant" = "Constants"
"InformationRegister" = "InformationRegisters"
"AccumulationRegister" = "AccumulationRegisters"
"AccountingRegister" = "AccountingRegisters"
"CalculationRegister" = "CalculationRegisters"
"ChartOfAccounts" = "ChartsOfAccounts"
"ChartOfCharacteristicTypes" = "ChartsOfCharacteristicTypes"
"ChartOfCalculationTypes" = "ChartsOfCalculationTypes"
"BusinessProcess" = "BusinessProcesses"
"Task" = "Tasks"
"ExchangePlan" = "ExchangePlans"
"DocumentJournal" = "DocumentJournals"
"Report" = "Reports"
"DataProcessor" = "DataProcessors"
"CommonModule" = "CommonModules"
"ScheduledJob" = "ScheduledJobs"
"EventSubscription" = "EventSubscriptions"
"HTTPService" = "HTTPServices"
"WebService" = "WebServices"
"DefinedType" = "DefinedTypes"
"Role" = "Roles"
"Subsystem" = "Subsystems"
"CommonForm" = "CommonForms"
"CommonTemplate" = "CommonTemplates"
"CommonPicture" = "CommonPictures"
"CommonAttribute" = "CommonAttributes"
"SessionParameter" = "SessionParameters"
"FunctionalOption" = "FunctionalOptions"
"FunctionalOptionsParameter" = "FunctionalOptionsParameters"
"Sequence" = "Sequences"
"FilterCriterion" = "FilterCriteria"
"SettingsStorage" = "SettingsStorages"
"XDTOPackage" = "XDTOPackages"
"WSReference" = "WSReferences"
"StyleItem" = "StyleItems"
"Language" = "Languages"
}
# --- Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ConfigDir)) {
$ConfigDir = Join-Path (Get-Location).Path $ConfigDir
}
if (-not (Test-Path $ConfigDir -PathType Container)) {
Write-Host "[ERROR] Config directory not found: $ConfigDir"
exit 1
}
$configXml = Join-Path $ConfigDir "Configuration.xml"
if (-not (Test-Path $configXml)) {
Write-Host "[ERROR] Configuration.xml not found in: $ConfigDir"
exit 1
}
# --- Parse object spec ---
$parts = $Object -split "\.", 2
if ($parts.Count -ne 2 -or -not $parts[0] -or -not $parts[1]) {
Write-Host "[ERROR] Invalid object format '$Object'. Expected: Type.Name (e.g. Catalog.Товары)"
exit 1
}
$objType = $parts[0]
$objName = $parts[1]
if (-not $typePluralMap.ContainsKey($objType)) {
Write-Host "[ERROR] Unknown type '$objType'. Supported: $($typePluralMap.Keys -join ', ')"
exit 1
}
$typePlural = $typePluralMap[$objType]
Write-Host "=== meta-remove: ${objType}.${objName} ==="
Write-Host ""
if ($DryRun) {
Write-Host "[DRY-RUN] No changes will be made"
Write-Host ""
}
$actions = 0
$errors = 0
# --- 1. Find object files ---
$typeDir = Join-Path $ConfigDir $typePlural
$objXml = Join-Path $typeDir "$objName.xml"
$objDir = Join-Path $typeDir $objName
$hasXml = Test-Path $objXml
$hasDir = Test-Path $objDir -PathType Container
if (-not $hasXml -and -not $hasDir) {
Write-Host "[WARN] Object files not found: $typePlural/$objName.xml"
Write-Host " Proceeding with deregistration only..."
} else {
if ($hasXml) { Write-Host "[FOUND] $typePlural/$objName.xml" }
if ($hasDir) {
$fileCount = @(Get-ChildItem $objDir -Recurse -File).Count
Write-Host "[FOUND] $typePlural/$objName/ ($fileCount files)"
}
}
# --- 2. Reference check ---
Write-Host ""
Write-Host "--- Reference check ---"
# Build search patterns based on object type
# Type → reference type name (used in XML <v8:Type> elements)
$typeRefNames = @{
"Catalog" = @("CatalogRef","CatalogObject")
"Document" = @("DocumentRef","DocumentObject")
"Enum" = @("EnumRef")
"ExchangePlan" = @("ExchangePlanRef","ExchangePlanObject")
"ChartOfAccounts" = @("ChartOfAccountsRef","ChartOfAccountsObject")
"ChartOfCharacteristicTypes" = @("ChartOfCharacteristicTypesRef","ChartOfCharacteristicTypesObject")
"ChartOfCalculationTypes" = @("ChartOfCalculationTypesRef","ChartOfCalculationTypesObject")
"BusinessProcess" = @("BusinessProcessRef","BusinessProcessObject")
"Task" = @("TaskRef","TaskObject")
}
# Type → Russian manager name (used in BSL code: Справочники.Товары)
$typeRuManager = @{
"Catalog" = "Справочники"
"Document" = "Документы"
"Enum" = "Перечисления"
"Constant" = "Константы"
"InformationRegister" = "РегистрыСведений"
"AccumulationRegister" = "РегистрыНакопления"
"AccountingRegister" = "РегистрыБухгалтерии"
"CalculationRegister" = "РегистрыРасчета"
"ChartOfAccounts" = "ПланыСчетов"
"ChartOfCharacteristicTypes" = "ПланыВидовХарактеристик"
"ChartOfCalculationTypes" = "ПланыВидовРасчета"
"BusinessProcess" = "БизнесПроцессы"
"Task" = "Задачи"
"ExchangePlan" = "ПланыОбмена"
"Report" = "Отчеты"
"DataProcessor" = "Обработки"
"DocumentJournal" = "ЖурналыДокументов"
"CommonModule" = $null
}
$searchPatterns = @()
# 1) XML type references: CatalogRef.Name, CatalogObject.Name
if ($typeRefNames.ContainsKey($objType)) {
foreach ($refName in $typeRefNames[$objType]) {
$searchPatterns += "$refName.$objName"
}
}
# 2) BSL code references: Справочники.Name, Catalogs.Name
$ruMgr = $typeRuManager[$objType]
if ($ruMgr) {
$searchPatterns += "$ruMgr.$objName"
}
# English manager = plural directory name
$searchPatterns += "$typePlural.$objName"
# 3) CommonModule: method calls in BSL (ModuleName.)
if ($objType -eq "CommonModule") {
$searchPatterns += "$objName."
}
# 4) ScheduledJob/EventSubscription handler references
if ($objType -eq "CommonModule") {
$searchPatterns += "<Handler>$objName."
$searchPatterns += "<MethodName>$objName."
}
# Exclude object's own files from search
$excludeDirs = @()
if ($hasDir) { $excludeDirs += $objDir }
$excludeFile = ""
if ($hasXml) { $excludeFile = $objXml }
# Search all XML and BSL files
$references = @()
$searchExtensions = @("*.xml", "*.bsl")
foreach ($ext in $searchExtensions) {
$files = @(Get-ChildItem $ConfigDir -Filter $ext -Recurse -File -ErrorAction SilentlyContinue)
foreach ($file in $files) {
# Skip own files
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
if ($excludeDirs.Count -gt 0) {
$skip = $false
foreach ($ed in $excludeDirs) {
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
}
if ($skip) { continue }
}
# Skip auto-cleaned files (Configuration.xml, ConfigDumpInfo.xml, Subsystems)
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
if ($relPath -eq "Configuration.xml" -or $relPath -eq "ConfigDumpInfo.xml" -or $relPath.StartsWith("Subsystems")) { continue }
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
foreach ($pat in $searchPatterns) {
if ($content.Contains($pat)) {
$references += @{ File = $relPath; Pattern = $pat }
break # one match per file is enough
}
}
}
}
# Also check for Type.Name references (subsystem content, doc journal, etc.) — but NOT in own files
$typeNameRef = "${objType}.${objName}"
$files = @(Get-ChildItem $ConfigDir -Filter "*.xml" -Recurse -File -ErrorAction SilentlyContinue)
foreach ($file in $files) {
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
if ($excludeDirs.Count -gt 0) {
$skip = $false
foreach ($ed in $excludeDirs) {
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
}
if ($skip) { continue }
}
# Skip Configuration.xml and Subsystems — they will be cleaned automatically
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
if ($relPath -eq "Configuration.xml") { continue }
if ($relPath -eq "ConfigDumpInfo.xml") { continue }
if ($relPath.StartsWith("Subsystems")) { continue }
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
if ($content.Contains($typeNameRef)) {
# Check it's not already in references
$alreadyFound = $false
foreach ($r in $references) {
if ($r.File -eq $relPath) { $alreadyFound = $true; break }
}
if (-not $alreadyFound) {
$references += @{ File = $relPath; Pattern = $typeNameRef }
}
}
}
if ($references.Count -gt 0) {
Write-Host "[WARN] Found $($references.Count) reference(s) to ${objType}.${objName}:"
Write-Host ""
$shown = 0
foreach ($ref in $references) {
Write-Host " $($ref.File)"
Write-Host " pattern: $($ref.Pattern)"
$shown++
if ($shown -ge 20) {
$remaining = $references.Count - $shown
if ($remaining -gt 0) {
Write-Host " ... and $remaining more"
}
break
}
}
Write-Host ""
if (-not $Force) {
Write-Host "[ERROR] Cannot remove: object has $($references.Count) reference(s)."
Write-Host " Use -Force to remove anyway, or fix references first."
exit 1
} else {
Write-Host "[WARN] -Force specified, proceeding despite references"
}
} else {
Write-Host "[OK] No references found"
}
# --- 3. Remove from Configuration.xml ChildObjects ---
Write-Host ""
Write-Host "--- Configuration.xml ---"
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true
$xmlDoc.Load($configXml)
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$cfgNode = $xmlDoc.DocumentElement.SelectSingleNode("md:Configuration", $ns)
if (-not $cfgNode) {
Write-Host "[ERROR] Configuration element not found in Configuration.xml"
$errors++
} else {
$childObjects = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
if ($childObjects) {
$found = $false
foreach ($child in @($childObjects.ChildNodes)) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq $objType -and $child.InnerText.Trim() -eq $objName) {
$found = $true
if (-not $DryRun) {
# Remove preceding whitespace if present
$prev = $child.PreviousSibling
if ($prev -and $prev.NodeType -eq 'Whitespace') {
$childObjects.RemoveChild($prev) | Out-Null
}
$childObjects.RemoveChild($child) | Out-Null
}
Write-Host "[OK] Removed <$objType>$objName</$objType> from ChildObjects"
$actions++
break
}
}
if (-not $found) {
Write-Host "[WARN] <$objType>$objName</$objType> not found in ChildObjects"
}
}
# Save Configuration.xml
if ($actions -gt 0 -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($configXml, $false, $enc)
$xmlDoc.Save($sw)
$sw.Close()
Write-Host "[OK] Configuration.xml saved"
}
}
# --- 4. Remove from subsystem Content ---
Write-Host ""
Write-Host "--- Subsystems ---"
$subsystemsDir = Join-Path $ConfigDir "Subsystems"
$subsystemsFound = 0
$subsystemsCleaned = 0
function Remove-FromSubsystems {
param([string]$dir)
$xmlFiles = @(Get-ChildItem $dir -Filter "*.xml" -File -ErrorAction SilentlyContinue)
foreach ($xmlFile in $xmlFiles) {
$ssDoc = New-Object System.Xml.XmlDocument
$ssDoc.PreserveWhitespace = $true
try { $ssDoc.Load($xmlFile.FullName) } catch { continue }
$ssNs = New-Object System.Xml.XmlNamespaceManager($ssDoc.NameTable)
$ssNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ssNs.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ssNode = $ssDoc.DocumentElement.SelectSingleNode("md:Subsystem", $ssNs)
if (-not $ssNode) { continue }
$propsNode = $ssNode.SelectSingleNode("md:Properties", $ssNs)
if (-not $propsNode) { continue }
$contentNode = $propsNode.SelectSingleNode("md:Content", $ssNs)
if (-not $contentNode) { continue }
$ssNameNode = $propsNode.SelectSingleNode("md:Name", $ssNs)
$ssName = if ($ssNameNode) { $ssNameNode.InnerText } else { $xmlFile.BaseName }
# Content items are <v8:Value>Type.Name</v8:Value>
$targetRef = "${objType}.${objName}"
$modified = $false
foreach ($item in @($contentNode.ChildNodes)) {
if ($item.NodeType -ne 'Element') { continue }
$val = $item.InnerText.Trim()
# Content format: "Subsystem.X" or "Catalog.X" etc.
if ($val -eq $targetRef) {
$script:subsystemsFound++
if (-not $DryRun) {
$prev = $item.PreviousSibling
if ($prev -and $prev.NodeType -eq 'Whitespace') {
$contentNode.RemoveChild($prev) | Out-Null
}
$contentNode.RemoveChild($item) | Out-Null
$modified = $true
}
Write-Host "[OK] Removed from subsystem '$ssName'"
$script:subsystemsCleaned++
}
}
if ($modified -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($xmlFile.FullName, $false, $enc)
$ssDoc.Save($sw)
$sw.Close()
}
# Recurse into child subsystems
$childDir = Join-Path $dir ($xmlFile.BaseName)
$childSubsystems = Join-Path $childDir "Subsystems"
if (Test-Path $childSubsystems -PathType Container) {
Remove-FromSubsystems -dir $childSubsystems
}
}
}
if (Test-Path $subsystemsDir -PathType Container) {
Remove-FromSubsystems -dir $subsystemsDir
if ($subsystemsCleaned -eq 0) {
Write-Host "[OK] Not referenced in any subsystem"
}
} else {
Write-Host "[OK] No Subsystems directory"
}
# --- 5. Delete object files ---
Write-Host ""
Write-Host "--- Files ---"
if (-not $KeepFiles) {
if ($hasDir -and -not $DryRun) {
Remove-Item $objDir -Recurse -Force
Write-Host "[OK] Deleted directory: $typePlural/$objName/"
$actions++
} elseif ($hasDir) {
Write-Host "[DRY] Would delete directory: $typePlural/$objName/"
$actions++
}
if ($hasXml -and -not $DryRun) {
Remove-Item $objXml -Force
Write-Host "[OK] Deleted file: $typePlural/$objName.xml"
$actions++
} elseif ($hasXml) {
Write-Host "[DRY] Would delete file: $typePlural/$objName.xml"
$actions++
}
if (-not $hasXml -and -not $hasDir) {
Write-Host "[OK] No files to delete"
}
} else {
Write-Host "[SKIP] File deletion skipped (-KeepFiles)"
}
# --- Summary ---
Write-Host ""
$totalActions = $actions + $subsystemsCleaned
if ($DryRun) {
Write-Host "=== Dry run complete: $totalActions actions would be performed ==="
} else {
Write-Host "=== Done: $totalActions actions performed ($subsystemsCleaned subsystem references removed) ==="
}
if ($errors -gt 0) {
exit 1
}
exit 0
+6 -3
View File
@@ -21,14 +21,14 @@
| Группа | Навыки | Описание | Гайд |
|--------|--------|----------|------|
| Внешние обработки (EPF) | 6 навыков `/epf-*` | Создание, сборка, разборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
| Внешние отчёты (ERF) | 3 навыка `/erf-*` | Создание, сборка, разборка внешних отчётов | [Подробнее](docs/epf-guide.md#внешние-отчёты-erf) |
| Внешние обработки (EPF) | 7 навыков `/epf-*` | Создание, сборка, разборка, валидация обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
| Внешние отчёты (ERF) | 4 навыка `/erf-*` | Создание, сборка, разборка, валидация внешних отчётов | [Подробнее](docs/epf-guide.md#внешние-отчёты-erf) |
| Универсальные операции | `/template-add`, `/template-remove`, `/help-add`, `/form-remove` | Добавление/удаление макетов, форм, справки для любых объектов | [Подробнее](docs/epf-guide.md#универсальные-навыки) |
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
| Управляемые формы (Form) | 6 навыков `/form-*` | Создание, анализ, генерация, модификация, валидация управляемых форм | [Подробнее](docs/form-guide.md) |
| Роли (Role) | 3 навыка `/role-*` | Анализ прав роли, создание из JSON DSL, валидация | [Подробнее](docs/role-guide.md) |
| Схема компоновки (СКД) | 4 навыка `/skd-*` | Анализ, генерация из JSON DSL, точечное редактирование, валидация схем компоновки данных | [Подробнее](docs/skd-guide.md) |
| Метаданные конфигурации | 4 навыка `/meta-*` | Создание, анализ, редактирование, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
| Метаданные конфигурации | 5 навыков `/meta-*` | Создание, анализ, редактирование, удаление, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
| Корневая конфигурация | 4 навыка `/cf-*` | Создание, анализ, редактирование, валидация корневых файлов конфигурации | [Подробнее](docs/cf-guide.md) |
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | — |
@@ -70,9 +70,11 @@
├── epf-dump/ # Разборка EPF
├── epf-bsp-init/ # Регистрация БСП
├── epf-bsp-add-command/ # Команда БСП
├── epf-validate/ # Валидация обработки
├── erf-init/ # Создание внешнего отчёта
├── erf-build/ # Сборка ERF
├── erf-dump/ # Разборка ERF
├── erf-validate/ # Валидация отчёта
├── template-add/ # Добавление макета (универсальный)
├── template-remove/ # Удаление макета (универсальный)
├── form-add/ # Добавление формы (универсальный)
@@ -97,6 +99,7 @@
├── meta-info/ # Структура объекта метаданных
├── meta-compile/ # Создание объекта метаданных
├── meta-edit/ # Редактирование объекта метаданных
├── meta-remove/ # Удаление объекта метаданных
├── meta-validate/ # Валидация объекта метаданных
├── cf-info/ # Анализ структуры конфигурации
├── cf-init/ # Создание пустой конфигурации
+10 -3
View File
@@ -51,7 +51,7 @@
"user": "Admin",
"password": "",
"aliases": ["dev", "разработка"],
"branches": ["dev", "develop"],
"branches": ["dev", "develop", "feature/*"],
"configSrc": "C:\\WS\\myapp\\cfsrc"
},
{
@@ -82,12 +82,19 @@
| `user` | string | нет | Пользователь 1С |
| `password` | string | нет | Пароль |
| `aliases` | string[] | нет | Альтернативные имена |
| `branches` | string[] | нет | Git-ветки |
| `branches` | string[] | нет | Git-ветки или glob-паттерны (`release/*`, `feature/*`) |
| `configSrc` | string | нет | Каталог XML-выгрузки |
### Разрешение базы
Все навыки `db-*` используют единый алгоритм: id → alias → branch → name → default → спросить пользователя.
Все навыки `db-*` (а также `epf-build`, `epf-dump`, `erf-build`, `erf-dump`) используют единый алгоритм:
1. Если пользователь указал **параметры подключения** (путь, сервер) — используй напрямую
2. Если указал **базу по имени** — ищи: `id``aliases` (с учётом морфологии) → `name` (нечёткое)
3. Если **не указал** — сопоставь текущую ветку Git с `branches` (точно или по glob-паттерну)
4. Fallback на `default`
5. Если не найдено — спроси пользователя
6. После выполнения: если использованная база не зарегистрирована — предложи добавить через `/db-list add`
## Сценарии использования
+2
View File
@@ -12,6 +12,7 @@
| `/epf-dump` | `<EpfFile>` | Разобрать EPF в XML (через 1cv8.exe) |
| `/epf-bsp-init` | `<ProcessorName> <Вид>` | Добавить регистрацию БСП (СведенияОВнешнейОбработке) |
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в дополнительную обработку БСП |
| `/epf-validate` | `<ObjectPath> [-MaxErrors 30]` | Валидация структурной корректности обработки (10 проверок) |
## Внешние отчёты (ERF)
@@ -20,6 +21,7 @@
| `/erf-init` | `<ReportName> [Synonym] [--WithSKD]` | Создать новый отчёт (корневой XML + модуль объекта + опционально СКД) |
| `/erf-build` | `<ReportName>` | Собрать ERF из XML (через 1cv8.exe) |
| `/erf-dump` | `<ErfFile>` | Разобрать ERF в XML (через 1cv8.exe) |
| `/erf-validate` | `<ObjectPath> [-MaxErrors 30]` | Валидация структурной корректности отчёта (10 проверок) |
Флаг `--WithSKD` создаёт макет `ОсновнаяСхемаКомпоновкиДанных` и привязывает его к `MainDataCompositionSchema`.
+13 -2
View File
@@ -9,6 +9,7 @@
| `/meta-info` | `<ObjectPath> [-Mode] [-Name]` | Анализ структуры объекта: реквизиты, ТЧ, формы, движения, типы (8 режимов) |
| `/meta-compile` | `<JsonPath> <OutputPath>` | Создание объекта метаданных из JSON DSL: реквизиты, ТЧ, свойства, формы |
| `/meta-edit` | `<ObjectPath> -Operation <op> -Value "<val>"` | Точечное редактирование: 30+ атомарных операций (add/remove/modify/set) |
| `/meta-remove` | `<ConfigDir> -Object <Type.Name> [-DryRun] [-Force]` | Безопасное удаление объекта с проверкой ссылок (блокирует при наличии) |
| `/meta-validate` | `<ObjectPath> [-MaxErrors 20]` | Валидация структурной корректности: ~40 проверок |
## Рабочий цикл
@@ -16,13 +17,15 @@
```
Описание объекта (текст) → JSON DSL → /meta-compile → XML-исходники → /meta-validate
↕ /meta-edit → /meta-info
↕ /meta-remove (безопасное удаление)
```
1. Claude формирует JSON-определение объекта (тип, реквизиты, ТЧ, свойства)
2. `/meta-compile` генерирует XML-исходники с корректными UUID, namespace, структурой ChildObjects
3. `/meta-edit` вносит точечные изменения: добавление/удаление реквизитов, ТЧ, владельцев, движений и т.д.
4. `/meta-validate` проверяет корректность XML
5. `/meta-info` выводит компактную сводку для визуальной проверки
4. `/meta-remove` безопасно удаляет объект (проверяет ссылки в реквизитах, коде, формах; чистит Configuration.xml и подсистемы)
5. `/meta-validate` проверяет корректность XML
6. `/meta-info` выводит компактную сводку для визуальной проверки
## Поддерживаемые типы объектов (23 типа)
@@ -147,6 +150,14 @@ Claude вызовет `/meta-edit` дважды: `add-attribute` для рекв
Claude вызовет `/meta-edit` с операцией `set-registerRecords`.
### Удаление неиспользуемого объекта
```
> Удали справочник Catalogs/Устаревший из конфигурации src/
```
Claude вызовет `/meta-remove` с `-DryRun`, покажет что будет удалено и проверит ссылки. Если объект нигде не используется — удалит файлы, уберёт из Configuration.xml и подсистем. Если есть ссылки — покажет список и заблокирует удаление.
### Проверка объекта после изменений
```
+205
View File
@@ -0,0 +1,205 @@
# Подсистемы и командный интерфейс
Навыки групп `/subsystem-*` и `/interface-*` позволяют анализировать, создавать, редактировать и проверять подсистемы 1С и их командный интерфейс — XML-файлы Subsystem и CommandInterface.xml из выгрузки конфигурации.
## Навыки
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/subsystem-info` | `<SubsystemPath> [-Mode] [-Name]` | Анализ структуры подсистемы: состав, дочерние, CI, дерево иерархии (4 режима) |
| `/subsystem-compile` | `<JsonPath> <OutputDir> [-Parent]` | Генерация подсистемы из JSON DSL: XML + регистрация в Configuration.xml |
| `/subsystem-edit` | `<SubsystemPath> -Operation <op> -Value "<value>"` | Точечное редактирование: 5 операций (add/remove content/child, set-property) |
| `/subsystem-validate` | `<SubsystemPath> [-MaxErrors 30]` | Валидация структурной корректности: 13 проверок |
| `/interface-edit` | `<CIPath> -Operation <op> -Value "<value>"` | Настройка CommandInterface.xml: 6 операций (hide/show/place/order) |
| `/interface-validate` | `<CIPath> [-MaxErrors 30]` | Валидация CommandInterface.xml: 13 проверок |
## Рабочий цикл
```
Описание раздела (текст) → JSON DSL → /subsystem-compile → XML → /subsystem-validate
↕ /subsystem-edit → /subsystem-info
/interface-edit → CommandInterface.xml → /interface-validate
```
1. Claude формирует JSON-определение подсистемы (имя, состав, дочерние, картинка)
2. `/subsystem-compile` генерирует XML-файл подсистемы и регистрирует в Configuration.xml
3. `/subsystem-edit` вносит точечные изменения: добавление объектов, дочерних подсистем, изменение свойств
4. `/interface-edit` настраивает командный интерфейс: скрытие/показ команд, размещение в группах, порядок
5. `/subsystem-validate` и `/interface-validate` проверяют корректность XML
6. `/subsystem-info` выводит компактную сводку для визуальной проверки
## JSON DSL — формат подсистемы
Подсистемы описываются в JSON:
### Минимальный пример
```json
{
"name": "Тест"
}
```
Умолчания: `includeInCommandInterface = true`, `useOneCommand = false`, synonym генерируется из name.
### Полный пример
```json
{
"name": "Продажи",
"synonym": "Продажи",
"comment": "",
"includeInCommandInterface": true,
"useOneCommand": false,
"explanation": "Управление продажами и взаимодействием с клиентами",
"picture": "CommonPicture.Продажи",
"content": ["Catalog.Товары", "Document.Заказ", "Report.Продажи"],
"children": ["Настройки", "Отчёты"]
}
```
### Свойства
| Свойство | Тип | Умолчание | Описание |
|----------|-----|-----------|----------|
| `name` | string | *(обязательно)* | Идентификатор подсистемы |
| `synonym` | string | из name | Отображаемое имя |
| `comment` | string | `""` | Комментарий |
| `includeInCommandInterface` | bool | `true` | Включать в командный интерфейс |
| `useOneCommand` | bool | `false` | Режим одной команды (требует ровно 1 элемент в content) |
| `explanation` | string | `""` | Описание раздела (подсказка) |
| `picture` | string | — | Ссылка на общую картинку (`CommonPicture.Имя`) |
| `content` | string[] | `[]` | Состав: `"Тип.Имя"` (Catalog.X, Document.Y, Report.Z, ...) |
| `children` | string[] | `[]` | Имена дочерних подсистем |
## Командный интерфейс — операции
Навык `/interface-edit` управляет файлом `CommandInterface.xml` подсистемы.
### Форматы ссылок на команды
| Формат | Пример |
|--------|--------|
| StandardCommand | `Catalog.Товары.StandardCommand.OpenList` |
| Command | `Report.Продажи.Command.Отчёт` |
| CommonCommand | `CommonCommand.МояКоманда` |
### Операции
| Операция | Значение | Описание |
|----------|----------|----------|
| `hide` | `"Cmd.Name"` или массив | Скрыть команду (CommandsVisibility, false) |
| `show` | `"Cmd.Name"` или массив | Показать команду (CommandsVisibility, true) |
| `place` | `{"command":"...","group":"CommandGroup.X"}` | Разместить команду в группе |
| `order` | `{"group":"...","commands":[...]}` | Задать порядок команд в группе |
| `subsystem-order` | `["Subsystem.X.Subsystem.A",...]` | Порядок дочерних подсистем |
| `group-order` | `["NavigationPanelOrdinary",...]` | Порядок групп |
### Примеры
```powershell
# Скрыть команду
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
# Показать команду
... -Operation show -Value "Report.Продажи.Command.Отчёт"
# Разместить в группе
... -Operation place -Value '{"command":"Report.X.Command.Y","group":"CommandGroup.Отчеты"}'
# Задать порядок подсистем
... -Operation subsystem-order -Value '["Subsystem.X.Subsystem.A","Subsystem.X.Subsystem.B"]'
```
## Сценарии использования
### Анализ структуры подсистем
```
> Покажи дерево подсистем конфигурации
```
Claude вызовет `/subsystem-info` (tree → overview → ci) и опишет:
- иерархию подсистем с маркерами [CI], [OneCmd], [Скрыт]
- состав каждой подсистемы (объекты по типам)
- настройки командного интерфейса (видимость, размещение, порядок)
### Создание подсистемы по описанию
```
> Создай подсистему Продажи: справочник Товары, документ Заказ, отчёт Продажи.
> Дочерние подсистемы: Настройки, Отчёты. Картинка — CommonPicture.Продажи.
```
Claude сформирует JSON:
```json
{
"name": "Продажи",
"synonym": "Продажи",
"content": ["Catalog.Товары", "Document.Заказ", "Report.Продажи"],
"children": ["Настройки", "Отчёты"],
"picture": "CommonPicture.Продажи"
}
```
И вызовет `/subsystem-compile``/subsystem-validate``/subsystem-info`.
### Добавление объектов в подсистему
```
> Добавь Document.Счёт и Report.Задолженность в подсистему Продажи
```
Claude вызовет `/subsystem-edit` с операцией `add-content`:
```powershell
... -SubsystemPath Subsystems/Продажи.xml -Operation add-content -Value '["Document.Счёт","Report.Задолженность"]'
```
### Настройка командного интерфейса
```
> Скрой команду открытия списка товаров и размести отчёт Продажи в группе Отчёты
```
Claude вызовет `/interface-edit`:
```powershell
# Скрыть команду
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
# Разместить в группе
... -Operation place -Value '{"command":"Report.Продажи.Command.Отчёт","group":"CommandGroup.Отчёты"}'
```
### Проверка подсистемы
```
> Проверь подсистему Subsystems/Продажи.xml
```
Claude вызовет `/subsystem-validate` и `/interface-validate`, покажет результат: ошибки (невалидный XML, отсутствующие файлы, дубликаты) и предупреждения.
## Структура файлов подсистемы
```
Subsystems/
├── ИмяПодсистемы.xml # Определение подсистемы (UUID, свойства, Content)
└── ИмяПодсистемы/
├── Ext/
│ └── CommandInterface.xml # Командный интерфейс (видимость, размещение, порядок)
└── Subsystems/ # Вложенные подсистемы
├── Дочерняя.xml
└── Дочерняя/
└── Ext/
└── CommandInterface.xml
```
Регистрация в `Configuration.xml`:
```xml
<ChildObjects>
<Subsystem>ИмяПодсистемы</Subsystem>
</ChildObjects>
```
## Спецификация
- [1c-subsystem-spec.md](1c-subsystem-spec.md) — XML-формат подсистем, CommandInterface.xml, namespace, элементы