diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1
index e2331d05..fb460651 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.ps1
+++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1
@@ -1,4 +1,4 @@
-# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
+# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -1974,8 +1974,19 @@ function Modify-Properties($propsDef) {
}
if (-not $propEl) {
- Warn "Property '$propName' not found in Properties"
- return
+ # create-if-missing: известное свойство создаём (порядок 1С терпит — append); неизвестное → ошибка (опечатка)
+ if ($script:knownObjectProps -notcontains $propName) {
+ Write-Error "modify-property: неизвестное свойство '$propName' — нет такого свойства объекта (опечатка?)"
+ exit 1
+ }
+ $newNodes = Import-Fragment "<$propName/>"
+ if ($newNodes.Count -gt 0) {
+ Insert-PropertyInOrder $script:propertiesEl $newNodes[0] $null $propName
+ $propEl = $newNodes[0]
+ } else {
+ Warn "Property '$propName': could not create element"
+ return
+ }
}
# Complex property: Owners, RegisterRecords, BasedOn, InputByString
@@ -2248,7 +2259,23 @@ function Modify-ChildElements($modifyDef, [string]$childType) {
Info "Modified $xmlTag '$elemName'.$changeProp = $valueStr"
$script:modifyCount++
} else {
- Warn "$xmlTag '$elemName': property '$changeProp' not found"
+ # create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка)
+ if ($script:knownChildProps -notcontains $changeProp) {
+ Write-Error "modify: неизвестное свойство '$changeProp' у $xmlTag '$elemName' (опечатка?)"
+ exit 1
+ }
+ $valueStr = "$changeValue"
+ if ($changeValue -is [bool]) {
+ $valueStr = if ($changeValue) { "true" } else { "false" }
+ } else {
+ $valueStr = Normalize-EnumValue $changeProp $valueStr
+ }
+ $newNodes = Import-Fragment "<$changeProp>$(Esc-Xml $valueStr)$changeProp>"
+ if ($newNodes.Count -gt 0) {
+ Insert-PropertyInOrder $propsEl $newNodes[0] $script:attrPropOrder $changeProp
+ Info "Created $xmlTag '$elemName'.$changeProp = $valueStr"
+ $script:modifyCount++
+ }
}
}
}
@@ -2286,6 +2313,73 @@ $script:complexPropertyMap = @{
"InputByString" = @{ tag = "xr:Field"; attr = $null }
}
+# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
+# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка.
+$script:knownObjectProps = @(
+ 'ActionPeriod','ActionPeriodUse','Addressing','AutoOrderByCode','Autonumbering','AuxiliaryChoiceForm',
+ 'AuxiliaryFolderChoiceForm','AuxiliaryFolderForm','AuxiliaryForm','AuxiliaryListForm','AuxiliaryObjectForm',
+ 'AuxiliaryRecordForm','AuxiliarySettingsForm','BaseCalculationTypes','BasePeriod','BasedOn',
+ 'CharacteristicExtValues','Characteristics','ChartOfAccounts','ChartOfCalculationTypes','CheckUnique',
+ 'ChoiceDataGetModeOnInputByString','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput','ChoiceMode',
+ 'ChoiceParameterLinks','ChoiceParameters','CodeAllowedLength','CodeLength','CodeMask','CodeSeries','CodeType',
+ 'Comment','Correspondence','CreateOnInput','CreateTaskInPrivilegedMode','CurrentPerformer','DataHistory',
+ 'DataLockControlMode','DataLockFields','DefaultChoiceForm','DefaultFolderChoiceForm','DefaultFolderForm',
+ 'DefaultForm','DefaultListForm','DefaultObjectForm','DefaultPresentation','DefaultRecordForm','DefaultSettingsForm',
+ 'DefaultVariantForm','DependenceOnCalculationTypes','DescriptionLength','DistributedInfoBase','EditFormat',
+ 'EditType','EnableTotalsSliceFirst','EnableTotalsSliceLast','EnableTotalsSplitting',
+ 'ExecuteAfterWriteDataHistoryVersionProcessing','Explanation','ExtDimensionTypes','ExtendedEdit',
+ 'ExtendedListPresentation','ExtendedObjectPresentation','ExtendedPresentation','ExtendedRecordPresentation',
+ 'FillChecking','FoldersOnTop','Format','FullTextSearch','FullTextSearchOnInputByString','Hierarchical',
+ 'HierarchyType','IncludeConfigurationExtensions','IncludeHelpInContents','InformationRegisterPeriodicity',
+ 'InputByString','LevelCount','LimitLevelCount','LinkByType','ListPresentation','MainAddressingAttribute',
+ 'MainDataCompositionSchema','MainFilterOnPeriod','MarkNegatives','Mask','MaxExtDimensionCount','MaxValue',
+ 'MinValue','MultiLine','Name','NumberAllowedLength','NumberLength','NumberPeriodicity','NumberType','Numerator',
+ 'ObjectPresentation','OrderLength','Owners','PasswordMode','PeriodAdjustmentLength','Periodicity',
+ 'PostInPrivilegedMode','Posting','PredefinedDataUpdate','QuickChoice','RealTimePosting','RecordPresentation',
+ 'RegisterRecords','RegisterRecordsDeletion','RegisterRecordsWritingOnPost','RegisterType','RegisteredDocuments',
+ 'Schedule','ScheduleDate','ScheduleValue','SearchStringModeOnInputByString','SequenceFilling','SettingsStorage',
+ 'StandardAttributes','StandardTabularSections','SubordinationUse','Synonym','Task','TaskNumberAutoPrefix',
+ 'ToolTip','Type','UnpostInPrivilegedMode','UpdateDataHistoryImmediatelyAfterWrite','UseStandardCommands',
+ 'VariantsStorage','WriteMode'
+)
+
+# Известные свойства дочерних элементов (union Attribute/Dimension/Resource по корпусу) — allowlist default-ветки
+# modify-attribute/-dimension/-resource.
+$script:knownChildProps = @(
+ 'AccountingFlag','Balance','BaseDimension','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput',
+ 'ChoiceParameterLinks','ChoiceParameters','Comment','CreateOnInput','DataHistory','DenyIncompleteValues',
+ 'DocumentMap','EditFormat','ExtDimensionAccountingFlag','ExtendedEdit','FillChecking','FillFromFillingValue',
+ 'FillValue','Format','FullTextSearch','Indexing','LinkByType','MainFilter','MarkNegatives','Mask','Master',
+ 'MaxValue','MinValue','MultiLine','Name','PasswordMode','QuickChoice','RegisterRecordsMap','ScheduleLink',
+ 'Synonym','ToolTip','Type','Use','UseInTotals'
+)
+
+# Канонический порядок свойств реквизита (последовательность Build-AttributeFragment) — для вставки в позицию.
+# Порядок 1С терпит (cert), но держим канонический для консистентности с meta-compile.
+$script:attrPropOrder = @(
+ 'Name','Synonym','Comment','Type','PasswordMode','Format','EditFormat','ToolTip','MarkNegatives','Mask',
+ 'MultiLine','ExtendedEdit','MinValue','MaxValue','FillFromFillingValue','FillValue','FillChecking',
+ 'ChoiceFoldersAndItems','ChoiceParameterLinks','ChoiceParameters','QuickChoice','CreateOnInput','ChoiceForm',
+ 'LinkByType','ChoiceHistoryOnInput','Use','Indexing','FullTextSearch','DataHistory'
+)
+
+# Вставить новый элемент свойства в Properties в канонической позиции (по orderArray); если свойства нет в
+# orderArray (или orderArray пуст) — append. Порядок 1С терпит, канонический — для консистентности/снапшотов.
+function Insert-PropertyInOrder($propsEl, $newNode, $orderArray, $propName) {
+ $childIndent = "$(Get-ChildIndent $propsEl)"
+ $refNode = $null
+ $idx = if ($orderArray) { [array]::IndexOf($orderArray, $propName) } else { -1 }
+ if ($idx -ge 0) {
+ foreach ($ch in $propsEl.ChildNodes) {
+ if ($ch.NodeType -eq 'Element') {
+ $ci = [array]::IndexOf($orderArray, $ch.LocalName)
+ if ($ci -gt $idx) { $refNode = $ch; break }
+ }
+ }
+ }
+ Insert-BeforeElement $propsEl $newNode $refNode $childIndent
+}
+
function Find-PropertyElement([string]$propName) {
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py
index 7d496acd..8d17135e 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.py
+++ b/.claude/skills/meta-edit/scripts/meta-edit.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
+# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1858,8 +1858,17 @@ def modify_properties(props_def):
break
if prop_el is None:
- warn(f"Property '{prop_name}' not found in Properties")
- continue
+ # create-if-missing: известное свойство создаём (порядок 1С терпит); неизвестное → ошибка (опечатка)
+ if prop_name not in known_object_props:
+ print(f"meta-edit: modify-property: неизвестное свойство '{prop_name}' — нет такого свойства объекта (опечатка?)", file=sys.stderr)
+ sys.exit(1)
+ new_nodes = import_fragment(f"<{prop_name}/>")
+ if new_nodes:
+ insert_property_in_order(properties_el, new_nodes[0], None, prop_name)
+ prop_el = new_nodes[0]
+ else:
+ warn(f"Property '{prop_name}': could not create element")
+ continue
# Complex property: Owners, RegisterRecords, BasedOn, InputByString
if prop_name in complex_property_map:
@@ -2100,7 +2109,20 @@ def modify_child_elements(modify_def, child_type):
info(f"Modified {xml_tag} '{elem_name}'.{change_prop} = {value_str}")
modify_count += 1
else:
- warn(f"{xml_tag} '{elem_name}': property '{change_prop}' not found")
+ # create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка)
+ if change_prop not in known_child_props:
+ print(f"meta-edit: modify: неизвестное свойство '{change_prop}' у {xml_tag} '{elem_name}' (опечатка?)", file=sys.stderr)
+ sys.exit(1)
+ value_str = str(change_value)
+ if isinstance(change_value, bool):
+ value_str = "true" if change_value else "false"
+ else:
+ value_str = normalize_enum_value(change_prop, value_str)
+ new_nodes = import_fragment(f"<{change_prop}>{esc_xml(value_str)}{change_prop}>")
+ if new_nodes:
+ insert_property_in_order(props_el, new_nodes[0], attr_prop_order, change_prop)
+ info(f"Created {xml_tag} '{elem_name}'.{change_prop} = {value_str}")
+ modify_count += 1
def process_modify(modify_def):
@@ -2128,6 +2150,68 @@ complex_property_map = {
"InputByString": {"tag": "xr:Field", "attr": None},
}
+# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
+# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка.
+known_object_props = {
+ 'ActionPeriod', 'ActionPeriodUse', 'Addressing', 'AutoOrderByCode', 'Autonumbering', 'AuxiliaryChoiceForm',
+ 'AuxiliaryFolderChoiceForm', 'AuxiliaryFolderForm', 'AuxiliaryForm', 'AuxiliaryListForm', 'AuxiliaryObjectForm',
+ 'AuxiliaryRecordForm', 'AuxiliarySettingsForm', 'BaseCalculationTypes', 'BasePeriod', 'BasedOn',
+ 'CharacteristicExtValues', 'Characteristics', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'CheckUnique',
+ 'ChoiceDataGetModeOnInputByString', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput', 'ChoiceMode',
+ 'ChoiceParameterLinks', 'ChoiceParameters', 'CodeAllowedLength', 'CodeLength', 'CodeMask', 'CodeSeries', 'CodeType',
+ 'Comment', 'Correspondence', 'CreateOnInput', 'CreateTaskInPrivilegedMode', 'CurrentPerformer', 'DataHistory',
+ 'DataLockControlMode', 'DataLockFields', 'DefaultChoiceForm', 'DefaultFolderChoiceForm', 'DefaultFolderForm',
+ 'DefaultForm', 'DefaultListForm', 'DefaultObjectForm', 'DefaultPresentation', 'DefaultRecordForm', 'DefaultSettingsForm',
+ 'DefaultVariantForm', 'DependenceOnCalculationTypes', 'DescriptionLength', 'DistributedInfoBase', 'EditFormat',
+ 'EditType', 'EnableTotalsSliceFirst', 'EnableTotalsSliceLast', 'EnableTotalsSplitting',
+ 'ExecuteAfterWriteDataHistoryVersionProcessing', 'Explanation', 'ExtDimensionTypes', 'ExtendedEdit',
+ 'ExtendedListPresentation', 'ExtendedObjectPresentation', 'ExtendedPresentation', 'ExtendedRecordPresentation',
+ 'FillChecking', 'FoldersOnTop', 'Format', 'FullTextSearch', 'FullTextSearchOnInputByString', 'Hierarchical',
+ 'HierarchyType', 'IncludeConfigurationExtensions', 'IncludeHelpInContents', 'InformationRegisterPeriodicity',
+ 'InputByString', 'LevelCount', 'LimitLevelCount', 'LinkByType', 'ListPresentation', 'MainAddressingAttribute',
+ 'MainDataCompositionSchema', 'MainFilterOnPeriod', 'MarkNegatives', 'Mask', 'MaxExtDimensionCount', 'MaxValue',
+ 'MinValue', 'MultiLine', 'Name', 'NumberAllowedLength', 'NumberLength', 'NumberPeriodicity', 'NumberType', 'Numerator',
+ 'ObjectPresentation', 'OrderLength', 'Owners', 'PasswordMode', 'PeriodAdjustmentLength', 'Periodicity',
+ 'PostInPrivilegedMode', 'Posting', 'PredefinedDataUpdate', 'QuickChoice', 'RealTimePosting', 'RecordPresentation',
+ 'RegisterRecords', 'RegisterRecordsDeletion', 'RegisterRecordsWritingOnPost', 'RegisterType', 'RegisteredDocuments',
+ 'Schedule', 'ScheduleDate', 'ScheduleValue', 'SearchStringModeOnInputByString', 'SequenceFilling', 'SettingsStorage',
+ 'StandardAttributes', 'StandardTabularSections', 'SubordinationUse', 'Synonym', 'Task', 'TaskNumberAutoPrefix',
+ 'ToolTip', 'Type', 'UnpostInPrivilegedMode', 'UpdateDataHistoryImmediatelyAfterWrite', 'UseStandardCommands',
+ 'VariantsStorage', 'WriteMode',
+}
+
+# Известные свойства дочерних элементов (union Attribute/Dimension/Resource) — allowlist default-ветки modify-child.
+known_child_props = {
+ 'AccountingFlag', 'Balance', 'BaseDimension', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput',
+ 'ChoiceParameterLinks', 'ChoiceParameters', 'Comment', 'CreateOnInput', 'DataHistory', 'DenyIncompleteValues',
+ 'DocumentMap', 'EditFormat', 'ExtDimensionAccountingFlag', 'ExtendedEdit', 'FillChecking', 'FillFromFillingValue',
+ 'FillValue', 'Format', 'FullTextSearch', 'Indexing', 'LinkByType', 'MainFilter', 'MarkNegatives', 'Mask', 'Master',
+ 'MaxValue', 'MinValue', 'MultiLine', 'Name', 'PasswordMode', 'QuickChoice', 'RegisterRecordsMap', 'ScheduleLink',
+ 'Synonym', 'ToolTip', 'Type', 'Use', 'UseInTotals',
+}
+
+# Канонический порядок свойств реквизита (последовательность build_attribute_fragment) — для вставки в позицию.
+attr_prop_order = [
+ 'Name', 'Synonym', 'Comment', 'Type', 'PasswordMode', 'Format', 'EditFormat', 'ToolTip', 'MarkNegatives', 'Mask',
+ 'MultiLine', 'ExtendedEdit', 'MinValue', 'MaxValue', 'FillFromFillingValue', 'FillValue', 'FillChecking',
+ 'ChoiceFoldersAndItems', 'ChoiceParameterLinks', 'ChoiceParameters', 'QuickChoice', 'CreateOnInput', 'ChoiceForm',
+ 'LinkByType', 'ChoiceHistoryOnInput', 'Use', 'Indexing', 'FullTextSearch', 'DataHistory',
+]
+
+
+def insert_property_in_order(props_el, new_node, order_array, prop_name):
+ """Вставить новое свойство в Properties в канонической позиции (по order_array); иначе append."""
+ child_indent = get_child_indent(props_el)
+ ref_node = None
+ idx = order_array.index(prop_name) if (order_array and prop_name in order_array) else -1
+ if idx >= 0:
+ for ch in props_el:
+ cl = localname(ch)
+ if cl in order_array and order_array.index(cl) > idx:
+ ref_node = ch
+ break
+ insert_before_element(props_el, new_node, ref_node, child_indent)
+
def find_property_element(prop_name):
for child in properties_el:
diff --git a/tests/skills/cases/meta-edit/error-modify-attribute-typo.json b/tests/skills/cases/meta-edit/error-modify-attribute-typo.json
new file mode 100644
index 00000000..c9eca253
--- /dev/null
+++ b/tests/skills/cases/meta-edit/error-modify-attribute-typo.json
@@ -0,0 +1,14 @@
+{
+ "name": "modify-attribute с опечаткой в имени свойства реквизита → ошибка (не тихий no-op)",
+ "setup": "empty-config",
+ "preRun": [
+ {
+ "script": "meta-compile/scripts/meta-compile",
+ "input": { "type": "Catalog", "name": "Спр", "attributes": ["Комм: String(50)"] },
+ "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "objectPath": "Catalogs/Спр" },
+ "input": { "modify": { "attributes": { "Комм": { "ЧёТоНеТо": "1" } } } },
+ "expectError": true
+}
diff --git a/tests/skills/cases/meta-edit/error-modify-property-typo.json b/tests/skills/cases/meta-edit/error-modify-property-typo.json
new file mode 100644
index 00000000..b251ced9
--- /dev/null
+++ b/tests/skills/cases/meta-edit/error-modify-property-typo.json
@@ -0,0 +1,14 @@
+{
+ "name": "modify-property с опечаткой в имени свойства → ошибка (не тихий no-op)",
+ "setup": "empty-config",
+ "preRun": [
+ {
+ "script": "meta-compile/scripts/meta-compile",
+ "input": { "type": "Catalog", "name": "Спр" },
+ "args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "objectPath": "Catalogs/Спр" },
+ "input": { "modify": { "properties": { "CodeLenght": "11" } } },
+ "expectError": true
+}
diff --git a/tests/skills/integration/meta-edit-create-if-missing.test.mjs b/tests/skills/integration/meta-edit-create-if-missing.test.mjs
new file mode 100644
index 00000000..70e6d3f7
--- /dev/null
+++ b/tests/skills/integration/meta-edit-create-if-missing.test.mjs
@@ -0,0 +1,38 @@
+// meta-edit-create-if-missing.test.mjs — Integration: create-if-missing свойства объекта
+// Steps: cf-init → meta-compile Catalog → editFile (удалить DataHistory) → meta-edit modify-property
+// DataHistory=Use (свойство отсутствует → должно СОЗДАТЬСЯ, а не тихий no-op) → assertContains
+
+export const name = 'meta-edit create-if-missing: пересоздание отсутствующего свойства объекта';
+export const setup = 'none';
+
+export const steps = [
+ {
+ name: 'cf-init: пустая конфигурация',
+ script: 'cf-init/scripts/cf-init',
+ args: { '-Name': 'ТестПравки', '-OutputDir': '{workDir}/config' },
+ },
+ {
+ name: 'meta-compile: Catalog Спр',
+ script: 'meta-compile/scripts/meta-compile',
+ input: { type: 'Catalog', name: 'Спр' },
+ args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/config' },
+ },
+ {
+ name: 'editFile: удалить свойство DataHistory (симуляция отсутствия)',
+ editFile: '{workDir}/config/Catalogs/Спр.xml',
+ replace: 'DontUse',
+ with: '',
+ },
+ {
+ name: 'meta-edit: modify-property DataHistory=Use (create-if-missing)',
+ script: 'meta-edit/scripts/meta-edit',
+ input: { modify: { properties: { DataHistory: 'Use' } } },
+ args: { '-DefinitionFile': '{inputFile}', '-ObjectPath': '{workDir}/config/Catalogs/Спр.xml' },
+ validate: { script: 'meta-validate/scripts/meta-validate', flag: '-ObjectPath', path: 'config/Catalogs/Спр.xml' },
+ },
+ {
+ name: 'assertContains: DataHistory создан',
+ assertContains: '{workDir}/config/Catalogs/Спр.xml',
+ expect: 'Use',
+ },
+];